diff --git a/.clang-tidy b/.clang-tidy index 56b7e40a28916..c784c9532b1c1 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, @@ -37,7 +38,6 @@ CheckOptions: # of them related to musl). misc-include-cleaner.IgnoreHeaders: ' endian\.h; - getopt\.h; sys/stat\.h; sys/statvfs\.h; sys/syscall\.h; @@ -50,8 +50,26 @@ 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' +RemovedArgs: ['-fwide-exec-charset=UCS2', '-maccumulate-outgoing-args'] WarningsAsErrors: '*' ExcludeHeaderFilterRegex: 'blkid\.h|gmessages\.h|gstring\.h' HeaderFileExtensions: diff --git a/.clangd b/.clangd index 24886efe9e86a..b0deab559c881 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.h). Meson detects it during configure + # with GCC and enables it unconditionally, so clangd flags every use. + Suppress: [unknown-attributes] diff --git a/.dir-locals.el b/.dir-locals.el index 6944406da07ba..47da56f939021 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -22,6 +22,14 @@ (meson-mode . ((meson-indent-basic . 8))) (sh-mode . ((sh-indentation . 4))) (awk-mode . ((c-basic-offset . 8))) + (python-mode . ((indent-tabs-mode . nil) + (tab-width . 4) + (fill-column . 109) + (python-indent-def-block-scale . 1))) + (python-ts-mode . ((indent-tabs-mode . nil) + (tab-width . 4) + (fill-column . 109) + (python-indent-def-block-scale . 1))) (nil . ((indent-tabs-mode . nil) (tab-width . 8) (fill-column . 79))) ) 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/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 313234f82a791..a0547b8b52ae1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -82,6 +82,7 @@ body: - 'homectl' - 'hostnamectl' - 'hardware database files' + - 'importctl' - 'journalctl' - 'kernel-install' - 'loginctl' @@ -112,7 +113,7 @@ body: - 'systemd-homed' - 'systemd-hostnamed' - 'systemd-hwdb' - - 'systemd-import' + - 'systemd-importd' - 'systemd-journal-gatewayd' - 'systemd-journal-remote' - 'systemd-journal-upload' diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index f6f8ac0f75210..5e5996d7574ed 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -21,6 +21,7 @@ body: - 'homectl' - 'hostnamectl' - 'hardware database files' + - 'importctl' - 'journalctl' - 'kernel-install' - 'loginctl' @@ -51,7 +52,7 @@ body: - 'systemd-homed' - 'systemd-hostnamed' - 'systemd-hwdb' - - 'systemd-import' + - 'systemd-importd' - 'systemd-journal-gatewayd' - 'systemd-journal-remote' - 'systemd-journal-upload' diff --git a/.github/advanced-issue-labeler.yml b/.github/advanced-issue-labeler.yml index 4e19392598cb5..b06cc20292f6f 100644 --- a/.github/advanced-issue-labeler.yml +++ b/.github/advanced-issue-labeler.yml @@ -53,7 +53,7 @@ policy: keys: ['systemd-hwdb', 'hardware database files'] - name: import - keys: ['systemd-import'] + keys: ['systemd-importd', 'importctl'] - name: journal keys: ['systemd-journald', 'journalctl'] 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/dependabot.yml b/.github/dependabot.yml index cb8f6ab23e13f..37b0fcc35379f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,8 +10,10 @@ updates: actions: patterns: - "*" - exclude-patterns: - - "systemd/mkosi" + ignore: + # We pin systemd/mkosi to a specific commit on purpose so the CI environment stays + # reproducible across runs; bump it with tools/fetch-mkosi.py when we need a newer mkosi. + - dependency-name: "systemd/mkosi" cooldown: default-days: 7 open-pull-requests-limit: 2 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/armhf-clang.cross b/.github/workflows/armhf-clang.cross new file mode 100644 index 0000000000000..1da21afd1f6fa --- /dev/null +++ b/.github/workflows/armhf-clang.cross @@ -0,0 +1,21 @@ +[binaries] +c = ['clang', '--target=arm-linux-gnueabihf'] +cpp = ['clang++', '--target=arm-linux-gnueabihf'] +ar = 'arm-linux-gnueabihf-ar' +nm = 'arm-linux-gnueabihf-nm' +strip = 'arm-linux-gnueabihf-strip' +pkgconfig = 'pkg-config' + +[properties] +needs_exe_wrapper = false +pkg_config_libdir = '/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/share/pkgconfig' +c_args = ['-O2', '-pipe', '-g', '-feliminate-unused-debug-types'] +c_link_args = ['-fuse-ld=bfd', '-Wl,-O1', '-Wl,--hash-style=gnu', '-Wl,--as-needed', '-Wl,-z,relro,-z,now,-z,noexecstack'] +cpp_args = [] +cpp_link_args = ['-fuse-ld=bfd', '-Wl,-O1', '-Wl,--hash-style=gnu', '-Wl,--as-needed', '-Wl,-z,relro,-z,now,-z,noexecstack'] + +[host_machine] +system = 'linux' +cpu_family = 'arm' +cpu = 'armv7' +endian = 'little' diff --git a/.github/workflows/armhf-gcc.cross b/.github/workflows/armhf-gcc.cross new file mode 100644 index 0000000000000..8fd2bf7670846 --- /dev/null +++ b/.github/workflows/armhf-gcc.cross @@ -0,0 +1,21 @@ +[binaries] +c = 'arm-linux-gnueabihf-gcc' +cpp = 'arm-linux-gnueabihf-g++' +ar = 'arm-linux-gnueabihf-gcc-ar' +nm = 'arm-linux-gnueabihf-gcc-nm' +strip = 'arm-linux-gnueabihf-strip' +pkgconfig = 'pkg-config' + +[properties] +needs_exe_wrapper = false +pkg_config_libdir = '/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/share/pkgconfig' +c_args = ['-O2', '-pipe', '-g', '-feliminate-unused-debug-types'] +c_link_args = ['-fuse-ld=bfd', '-Wl,-O1', '-Wl,--hash-style=gnu', '-Wl,--as-needed', '-Wl,-z,relro,-z,now,-z,noexecstack'] +cpp_args = [] +cpp_link_args = ['-fuse-ld=bfd', '-Wl,-O1', '-Wl,--hash-style=gnu', '-Wl,--as-needed', '-Wl,-z,relro,-z,now,-z,noexecstack'] + +[host_machine] +system = 'linux' +cpu_family = 'arm' +cpu = 'armv7' +endian = 'little' diff --git a/.github/workflows/build-test.sh b/.github/workflows/build-test.sh index 3c6c6c50feebe..9c3d2e73382e2 100755 --- a/.github/workflows/build-test.sh +++ b/.github/workflows/build-test.sh @@ -12,13 +12,14 @@ 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" "-Db_ndebug=true" "--auto-features=disabled -Dcompat-mutable-uid-boundaries=true" ) +# Packages that are always native (i.e.: tools that run on the host) go in this list PACKAGES=( cryptsetup-bin expect @@ -28,6 +29,26 @@ PACKAGES=( isc-dhcp-client itstool kbd + linux-tools-generic + mold + mount + net-tools + python3-evdev + python3-jinja2 + python3-lxml + python3-pefile + python3-pip + python3-pyelftools + python3-pyparsing + python3-setuptools + quota + strace + unifont + util-linux + zstd +) +# Packages that are needed for the target architecture (i.e.: libraries) go in this list +DEVEL_PACKAGES=( libarchive-dev libblkid-dev libbpf-dev @@ -47,37 +68,51 @@ PACKAGES=( libxkbcommon-dev libxtables-dev libzstd-dev - linux-tools-generic - mold - mount - net-tools - python3-evdev - python3-jinja2 - python3-lxml - python3-pefile - python3-pip - python3-pyelftools - python3-pyparsing - python3-setuptools - quota - strace - unifont - util-linux - zstd ) +FEATURES=() COMPILER="${COMPILER:?}" COMPILER_VERSION="${COMPILER_VERSION:?}" LINKER="${LINKER:?}" RELEASE="$(lsb_release -cs)" +CROSS_ARCH="${CROSS_ARCH:-}" +CROSS_FILE=() -if [ "$(uname -m)" = "aarch64" ] || [ "$(uname -m)" = "x86_64" ]; then - PACKAGES+=(libxen-dev) +if [[ -z "$CROSS_ARCH" ]] && [[ "$(uname -m)" =~ ^(aarch64|x86_64)$ ]]; then + DEVEL_PACKAGES+=(libxen-dev) fi +# If CROSS_ARCH is set, append : to the packages so they get installed for the target +PACKAGES+=("${DEVEL_PACKAGES[@]/%/${CROSS_ARCH:+:$CROSS_ARCH}}") + # Note: As we use postfixed clang/gcc binaries, we need to override $AR # as well, otherwise meson falls back to ar from binutils which # doesn't work with LTO -if [[ "$COMPILER" == clang ]]; then +if [[ -n "$CROSS_ARCH" ]]; then + if [[ "$COMPILER" != gcc ]]; then + fatal "$CROSS_ARCH cross builds are only supported with gcc" + fi + + case "$CROSS_ARCH" in + armhf) + triplet=arm-linux-gnueabihf + ;; + riscv64) + triplet=riscv64-linux-gnu + ;; + *) + fatal "Unsupported cross architecture: $CROSS_ARCH" + ;; + esac + + CC="$triplet-gcc" + CXX="$triplet-g++" + AR="$triplet-gcc-ar" + CFLAGS="-Wno-maybe-uninitialized" + CXXFLAGS="" + CROSS_FILE=(--cross-file ".github/workflows/$CROSS_ARCH-gcc.cross") + FEATURES+=(-Ddbus-interfaces-dir=no -Dsbat-distro=) + PACKAGES+=("crossbuild-essential-$CROSS_ARCH") +elif [[ "$COMPILER" == clang ]]; then CC="clang-$COMPILER_VERSION" CXX="clang++-$COMPILER_VERSION" AR="llvm-ar-$COMPILER_VERSION" @@ -108,6 +143,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 @@ -128,14 +168,19 @@ sudo rm -f /etc/apt/sources.list.d/microsoft-prod.{list,sources} if grep -q 'VERSION_CODENAME=jammy' /usr/lib/os-release; then sudo add-apt-repository -y --no-update ppa:upstream-systemd-ci/systemd-ci sudo add-apt-repository -y --no-update --enable-source + # Jammy's kernel is too old and there's no vmlinux.h + FEATURES+=("-Dbpf-framework=disabled") else # add-apt-repository --enable-source does not work on deb822 style sources. for f in /etc/apt/sources.list.d/*.sources; do sudo sed -i "s/Types: deb/Types: deb deb-src/g" "$f" done fi +if [[ -n "$CROSS_ARCH" ]]; then + sudo dpkg --add-architecture "$CROSS_ARCH" +fi sudo apt-get -y update -sudo apt-get -y build-dep systemd +sudo apt-get -y build-dep systemd ${CROSS_ARCH:+--host-architecture=$CROSS_ARCH} sudo apt-get -y install "${PACKAGES[@]}" # Install more or less recent meson and ninja with pip, since the distro versions don't # always support all the features we need (like --optimization=). Since the build-dep @@ -151,8 +196,8 @@ if [ -n "$bpftool_dir" ]; then fi if [[ -n "$CUSTOM_PYTHON" ]]; then - # If CUSTOM_PYTHON is set we need to pull jinja2 from pip, as a local interpreter is used - pip3 install --user --break-system-packages jinja2 + # If CUSTOM_PYTHON is set we need to pull dependencies from pip, as a local interpreter is used + pip3 install --user --break-system-packages jinja2 pefile fi $CC --version @@ -162,11 +207,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" \ @@ -175,6 +215,8 @@ for args in "${ARGS[@]}"; do meson setup \ -Dtests=unsafe -Dslow-tests=true -Dfuzz-tests=true --werror \ -Dnobody-group=nogroup -Ddebug=false \ + "${FEATURES[@]}" \ + "${CROSS_FILE[@]}" \ $args build; then cat build/meson-logs/meson-log.txt diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 5325954031fb3..a5b202713956b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -39,6 +39,12 @@ jobs: - env: { COMPILER: "gcc", COMPILER_VERSION: "13", LINKER: "mold" } runner: [ ubuntu-24.04-arm ] python-version: '' + - env: { COMPILER: "gcc", COMPILER_VERSION: "13", LINKER: "bfd", CROSS_ARCH: "armhf" } + runner: [ ubuntu-24.04-arm ] + python-version: '' + - env: { COMPILER: "gcc", COMPILER_VERSION: "13", LINKER: "bfd", CROSS_ARCH: "riscv64" } + runner: [ ubuntu-24.04-arm ] + python-version: '' - env: { COMPILER: "clang", COMPILER_VERSION: "18", LINKER: "lld" } runner: [ ubuntu-24.04-s390x ] python-version: '' diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index bb301793ca1b7..73f5f1c762068 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -60,14 +60,14 @@ 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 path: ./out/artifacts - name: Upload Sarif if: always() && steps.build.outcome == 'success' - uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e + uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa with: # Path to SARIF file relative to the root of the repository sarif_file: cifuzz-sarif/results.sarif diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml new file mode 100644 index 0000000000000..4de5a462bff79 --- /dev/null +++ b/.github/workflows/claude-review.yml @@ -0,0 +1,690 @@ +# 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@3a2844b7e9c422d3c10d287c895573f7108da1b3 + 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" + # Chronological commit order, oldest first. The worktree dirs are + # SHA-named, so listing them doesn't preserve order — reviewers read + # this manifest to review commits in the order they were authored. + git log --reverse --format=%H HEAD..FETCH_HEAD > commit-order.txt + while read -r sha; 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 < commit-order.txt + + - 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@acca2b1b2070338fb9fd1ca27ecee81d687e58e5 + 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" + # Pin the `opus` alias (used by the review subagents) to 4.8 so they + # don't silently resolve to an older model the Bedrock alias points at. + ANTHROPIC_DEFAULT_OPUS_MODEL: us.anthropic.claude-opus-4-8 + 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 the PR through multiple lenses + + First, read `review-schema.json` and `commit-order.txt` in the repository + root. `commit-order.txt` lists the commit SHAs in chronological order, + oldest first — this is the order in which commits must be reviewed. 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). + + You will review the PR through a set of lenses. Every review uses these + four base lenses: + 1. Correctness & memory safety — logic errors, edge cases, mishandled + error paths, use-after-free, double-free, memory/fd leaks, NULL + dereferences, uninitialized reads, integer/buffer overflow, off-by-one. + 2. Resource lifetimes & concurrency — ownership and reference counting, + cleanup and unref paths, reentrancy, event-loop and async behavior, + signal safety, assert/assert_return contracts, and state-machine + invariants. + 3. Security & robustness — handling of untrusted input, parsing, bounds + checks, resource exhaustion / DoS, privilege boundaries, path and + symlink handling, format strings, TOCTOU races. + 4. API design, style & maintainability — interface design, consistency + with existing systemd idioms and CODING_STYLE, naming, error-propagation + conventions, dead code, and needless complexity. + + ### Add PR-specific lenses + + Before spawning, briefly inspect what this PR actually changes so you can + add domain-specific lenses. Read each worktree's `commit-message.txt` and + skim the changed file paths and hunk headers in each `commit.patch`, in + chronological order — just enough to understand which subsystems and + problem domains are touched. Do + NOT perform the review yourself or read the full source; that is the + subagents' job. + + Based on that, add 1 to 3 EXTRA lenses targeting the specific concerns of + this PR. Each extra lens needs a short name and a one-sentence description + of what it must check. For example: + - a PR changing DNS logic in resolved → a "DNS protocol conformance" lens + (RFC compliance, packet/label parsing, name/length limits, DNSSEC, + EDNS, caching/TTL semantics). + - a PR changing D-Bus/Varlink interfaces → an "IPC interface + compatibility" lens (backward compatibility, method/signal signatures, + polkit authorization). + - a PR changing unit file parsing → a "unit file format & compatibility" + lens (directive semantics, ordering, backward compatibility). + - a PR touching sd-boot/UEFI → a "boot/firmware safety" lens. + Only add extra lenses genuinely warranted by the diff — for a small or + generic change the four base lenses may be enough, in which case add none. + + ### Spawn the reviewers + + Then spawn exactly one review subagent per lens (the base lenses plus any + extra lenses you derived), all in a single message so they run + concurrently. Each subagent reviews EVERY commit (every worktree + directory), but only through the perspective of its assigned lens. + + Each subagent must be spawned with `model: "opus"`. + + Each subagent prompt must include: + - The name and full description of its assigned lens, with an instruction + to report ONLY findings that fall under that lens and to ignore issues + belonging to the other lenses — those are covered by other reviewers. + - 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 list of every worktree path in chronological order (oldest commit + first, matching `commit-order.txt`), with an instruction to review the + commits in that order and read each one's `commit-message.txt` and + `commit.patch`. + - Instructions that each finding's `commit` field must be the SHA of the + worktree the finding belongs to, and that every `line` and `start_line` + value must be verified against the hunk ranges in that commit's + `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). Because the lenses overlap, + the same issue is often reported by more than one lens — collapse + these into a single comment, keeping the clearest wording. + 2. Drop any issue whose suggestion is to add a code comment, docstring, + or documentation (e.g. "add a comment explaining…", "missing + docstring", "document this function", "would benefit from a + comment"). Project style is to write no comments unless the WHY is + non-obvious, so these are noise — drop them from `comments` and from + the `summary`. + 3. Drop issues that already have a review comment on the same file about + the same problem, or where the PR author replied disagreeing. + 4. 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. + 5. 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 with a single `StructuredOutput` call that + contains all of these fields together: + - `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. + + `comments` and `resolve` are required — include them in the same call even + when they are empty (`[]`). Never emit a call with `summary` alone; a call + missing `comments` is rejected and wastes a full retry. Build `comments` + first, then write `summary` from it, then emit everything in one call. + + Keep `summary` concise: each checkbox is a short title, `path:line`, and a + one-line explanation — the detailed reasoning belongs in the matching + `comments` entry, not the summary. Do not restate full findings prose in + the summary. + PROMPT + + claude \ + --model us.anthropic.claude-opus-4-8 \ + --effort xhigh \ + --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@3a2844b7e9c422d3c10d287c895573f7108da1b3 + 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..7e919bccbf62a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -10,7 +10,6 @@ on: pull_request: branches: - main - - v[0-9]+-stable paths: - .github/workflows/coverage.yml - test/integration-tests/integration-test-wrapper.py @@ -27,7 +26,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: persist-credentials: false - - uses: systemd/mkosi@66d51024b7149f40be4702e84275c936373ace97 + - uses: systemd/mkosi@f7762b71437227922a367bb89597843c77494ef9 # Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space # immediately, we remove the files in the background. However, we first move them to a different location @@ -129,8 +128,12 @@ jobs: --max-lines 300 \ --quiet + - name: Fix journal ownership + if: failure() && (github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable') + run: sudo chown -R "$(id -u):$(id -g)" build/test/journal build/meson-logs || true + - 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..d0ad47aab3dbb 100644 --- a/.github/workflows/gather-pr-metadata.yml +++ b/.github/workflows/gather-pr-metadata.yml @@ -22,10 +22,10 @@ jobs: - id: metadata name: Gather Pull Request Metadata - uses: redhat-plumbers-in-action/gather-pull-request-metadata@b86d1eaf7038cf88a56b26ba3e504f10e07b0ce5 + uses: redhat-plumbers-in-action/gather-pull-request-metadata@ecc2e46fe4f0b2e9a7b236d6012bc9f74af318d0 - 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..022d499f7f526 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 }}" @@ -44,7 +44,7 @@ jobs: sync-labels: false - name: Set or remove labels based on systemd development workflow - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 if: startsWith(github.event_name, 'pull_request') && github.event.action != 'closed' && !github.event.pull_request.draft with: script: | @@ -85,7 +85,7 @@ jobs: } - name: Add please-review label on command in issue comment - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 if: github.event_name == 'issue_comment' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/please-review') with: script: | @@ -97,7 +97,7 @@ jobs: }) - name: Remove specific labels when PR is closed or merged - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 if: startsWith(github.event_name, 'pull_request') && github.event.action == 'closed' with: script: | diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index ba293cf8be135..df2d50c3cdbba 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -30,7 +30,7 @@ jobs: persist-credentials: false - name: Lint Code Base - uses: super-linter/super-linter/slim@61abc07d755095a68f4987d1c2c3d1d64408f1f9 + uses: super-linter/super-linter/slim@9e863354e3ff62e0727d37183162c4a88873df41 env: DEFAULT_BRANCH: main MULTI_STATUS: false @@ -40,7 +40,7 @@ jobs: GITHUB_ACTIONS_CONFIG_FILE: actionlint.yml ENABLE_GITHUB_PULL_REQUEST_SUMMARY_COMMENT: false - - uses: systemd/mkosi@66d51024b7149f40be4702e84275c936373ace97 + - uses: systemd/mkosi@f7762b71437227922a367bb89597843c77494ef9 - name: Check that tabs are not used in Python code run: sh -c '! git grep -P "\\t" -- src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py' @@ -63,16 +63,21 @@ jobs: - name: Run ruff format run: | mkosi box -- ruff --version - if ! mkosi box -- ruff format --check src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/test/generate-sym-test.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py + if ! mkosi box -- ruff format --check then echo "Please run 'ruff format' on the above files or apply the diffs below manually" - mkosi box -- ruff format --check --quiet --diff src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/test/generate-sym-test.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py + mkosi box -- ruff format --check --quiet --diff fi - name: Run ruff check run: | mkosi box -- ruff --version - mkosi box -- ruff check src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/test/generate-sym-test.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py + if ! mkosi box -- ruff check + then + echo "Please fix the errors shown by 'ruff check' on the above files or add noqa comments where appropriate." + echo "Diffs for fixes that can be programmatically generated are shown below:" + mkosi box -- ruff check --diff + fi - name: Configure meson run: mkosi box -- env CC=clang CXX=clang++ meson setup build @@ -80,13 +85,5 @@ jobs: - name: Run clang-tidy run: mkosi box -- meson test -C build --suite=clang-tidy --print-errorlogs --no-stdsplit --quiet - - name: Build with musl - run: | - mkosi box -- \ - env \ - CC=musl-gcc \ - CXX=musl-gcc \ - CFLAGS="-idirafter /usr/include" \ - CXXFLAGS="-idirafter /usr/include" \ - meson setup -Dlibc=musl -Ddbus-interfaces-dir=no musl - mkosi box -- ninja -C musl + - name: Run coccinelle checks + run: mkosi box -- meson test -C build --suite=coccinelle --print-errorlogs --no-stdsplit diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml index 72daed60ef293..170d5d49d294e 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@b4309332981a82ec1c5618f44dd2e27cc8bfbfda 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..3fd692da2fca9 100644 --- a/.github/workflows/mkosi.yml +++ b/.github/workflows/mkosi.yml @@ -46,9 +46,9 @@ permissions: jobs: ci: - runs-on: ${{ matrix.runner }} + runs-on: ${{ matrix.runner || 'ubuntu-24.04' }} concurrency: - group: ${{ github.workflow }}-${{ matrix.distro }}-${{ matrix.release }}-${{ github.ref }}-${{ matrix.runner }} + group: ${{ github.workflow }}-${{ matrix.distro }}-${{ matrix.release }}-${{ github.ref }}-${{ matrix.runner || 'ubuntu-24.04' }} cancel-in-progress: true strategy: fail-fast: false @@ -56,120 +56,44 @@ jobs: include: - distro: arch release: rolling - runner: ubuntu-24.04 - sanitizers: "" - llvm: 0 cflags: "-O2 -D_FORTIFY_SOURCE=3" - relabel: no vm: 1 - no_qemu: 0 - no_kvm: 0 - shim: 0 - distro: debian release: stable - runner: ubuntu-24.04 - sanitizers: "" - llvm: 0 - cflags: "-Og" - relabel: no - vm: 0 - no_qemu: 0 - no_kvm: 0 - shim: 0 - distro: debian release: testing - runner: ubuntu-24.04 - sanitizers: "" - llvm: 0 - cflags: "-Og" - relabel: no - vm: 0 - no_qemu: 0 - no_kvm: 0 shim: 1 - distro: debian release: testing runner: ubuntu-24.04-arm - sanitizers: "" - llvm: 0 - cflags: "-Og" - relabel: no - vm: 0 no_qemu: 1 no_kvm: 1 - shim: 0 - distro: ubuntu release: noble - runner: ubuntu-24.04 - sanitizers: "" - llvm: 0 - cflags: "-Og" - relabel: no - vm: 0 - no_qemu: 0 - no_kvm: 0 - shim: 0 + - distro: ubuntu + release: resolute - distro: fedora release: "43" - runner: ubuntu-24.04 sanitizers: address,undefined llvm: 1 - cflags: "-Og" - relabel: yes - vm: 0 - no_qemu: 0 - no_kvm: 0 - shim: 0 - distro: fedora release: rawhide - runner: ubuntu-24.04 - sanitizers: "" - llvm: 0 - cflags: "-Og" - relabel: yes - vm: 0 - no_qemu: 0 - no_kvm: 0 - shim: 0 - distro: opensuse release: tumbleweed - runner: ubuntu-24.04 - sanitizers: "" - llvm: 0 - cflags: "-Og" - relabel: yes - vm: 0 - no_qemu: 0 - no_kvm: 0 - shim: 0 - distro: centos release: "9" - runner: ubuntu-24.04 - sanitizers: "" - llvm: 0 - cflags: "-Og" - relabel: yes - vm: 0 - no_qemu: 0 - no_kvm: 0 - shim: 0 - distro: centos release: "10" - runner: ubuntu-24.04 - sanitizers: "" - llvm: 0 - cflags: "-Og" - relabel: yes - vm: 0 - no_qemu: 0 - no_kvm: 0 - shim: 0 + - distro: postmarketos + release: edge + libc: musl + skip: TEST-92-TPM2-SWTPM steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: persist-credentials: false - - uses: systemd/mkosi@66d51024b7149f40be4702e84275c936373ace97 + - uses: systemd/mkosi@f7762b71437227922a367bb89597843c77494ef9 # Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space # immediately, we remove the files in the background. However, we first move them to a different location @@ -216,19 +140,25 @@ jobs: Environment= # Build debuginfo packages since we'll be publishing the packages as artifacts. WITH_DEBUG=1 - CFLAGS="${{ matrix.cflags }}" - SANITIZERS=${{ matrix.sanitizers }} + CFLAGS="${{ matrix.cflags || '-Og' }}" + SANITIZERS="${{ matrix.sanitizers || '' }}" MESON_OPTIONS=--werror - LLVM=${{ matrix.llvm }} + LLVM="${{ matrix.llvm || 0 }}" SYSEXT=1 - [Content] - SELinuxRelabel=${{ matrix.relabel }} - [Runtime] RAM=4G EOF + # Preferred for cloud/CIs + if [ "${{ matrix.distro }}" = opensuse ]; then + tee -a mkosi/mkosi.local.conf <>mkosi/mkosi.local.conf @@ -288,7 +219,7 @@ jobs: MAX_LINES=() fi - if [ "${{ matrix.no_kvm }}" = 1 ] && [ "${{ matrix.no_qemu }}" = 0 ]; then + if [ "${{ matrix.no_kvm || 0 }}" = 1 ] && [ "${{ matrix.no_qemu || 0 }}" = 0 ]; then timeout_multiplier=4 fi @@ -297,10 +228,11 @@ jobs: # of failed tests. sudo --preserve-env mkosi box -- \ env \ - TEST_PREFER_QEMU=${{ matrix.vm }} \ - TEST_NO_QEMU=${{ matrix.no_qemu }} \ - TEST_NO_KVM=${{ matrix.no_kvm }} \ - TEST_RUNNER=${{ matrix.runner }} \ + TEST_PREFER_QEMU="${{ matrix.vm || 0 }}" \ + TEST_NO_QEMU="${{ matrix.no_qemu || 0 }}" \ + TEST_NO_KVM="${{ matrix.no_kvm || 0 }}" \ + TEST_RUNNER="${{ matrix.runner || 'ubuntu-24.04' }}" \ + TEST_SKIP="${{ matrix.skip }}" \ meson test \ -C build \ --timeout-multiplier="${timeout_multiplier:-1}" \ @@ -312,11 +244,15 @@ jobs: --num-processes "$(($(nproc) - 1))" \ "${MAX_LINES[@]}" + - name: Fix journal ownership + if: failure() && (github.repository == 'systemd/systemd' || github.repository == 'systemd/systemd-stable') + run: sudo chown -R "$(id -u):$(id -g)" build/test/journal build/meson-logs || true + - 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 + name: ci-mkosi-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.distro }}-${{ matrix.release }}-${{ matrix.runner || 'ubuntu-24.04' }}-failed-test-journals path: | build/test/journal/*.journal build/meson-logs/* diff --git a/.github/workflows/requirements.txt b/.github/workflows/requirements.txt index 08af02c80fcb4..6e412a6318a23 100644 --- a/.github/workflows/requirements.txt +++ b/.github/workflows/requirements.txt @@ -1,21 +1,22 @@ -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.11.1 \ + --hash=sha256:9b3a023657e393dbc5335b95c561337d49b7a458f5541e47ec44f2cc566e0d80 +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/riscv64-gcc.cross b/.github/workflows/riscv64-gcc.cross new file mode 100644 index 0000000000000..8096e4519223c --- /dev/null +++ b/.github/workflows/riscv64-gcc.cross @@ -0,0 +1,21 @@ +[binaries] +c = 'riscv64-linux-gnu-gcc' +cpp = 'riscv64-linux-gnu-g++' +ar = 'riscv64-linux-gnu-gcc-ar' +nm = 'riscv64-linux-gnu-gcc-nm' +strip = 'riscv64-linux-gnu-strip' +pkgconfig = 'pkg-config' + +[properties] +needs_exe_wrapper = true +pkg_config_libdir = '/usr/lib/riscv64-linux-gnu/pkgconfig:/usr/share/pkgconfig' +c_args = ['-O2', '-pipe', '-g', '-feliminate-unused-debug-types'] +c_link_args = ['-fuse-ld=bfd', '-Wl,-O1', '-Wl,--hash-style=gnu', '-Wl,--as-needed', '-Wl,-z,relro,-z,now,-z,noexecstack'] +cpp_args = [] +cpp_link_args = ['-fuse-ld=bfd', '-Wl,-O1', '-Wl,--hash-style=gnu', '-Wl,--as-needed', '-Wl,-z,relro,-z,now,-z,noexecstack'] + +[host_machine] +system = 'linux' +cpu_family = 'riscv64' +cpu = 'riscv64' +endian = 'little' diff --git a/.github/workflows/unit-tests-musl.sh b/.github/workflows/unit-tests-musl.sh deleted file mode 100755 index 5a9e68710a294..0000000000000 --- a/.github/workflows/unit-tests-musl.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: LGPL-2.1-or-later - -# shellcheck disable=SC2206 -PHASES=(${@:-SETUP BUILD RUN CLEANUP}) - -function info() { - echo -e "\033[33;1m$1\033[0m" -} - -function run_meson() { - if ! meson "$@"; then - find . -type f -name meson-log.txt -exec cat '{}' + - return 1 - fi -} - -set -ex - -for phase in "${PHASES[@]}"; do - case $phase in - SETUP) - info "Setup phase" - - # Alpine still uses split-usr. - for i in /bin/* /sbin/*; do - ln -rs "$i" "/usr/$i"; - done - ;; - BUILD) - info "Build systemd phase" - - run_meson setup --werror -Dtests=unsafe -Dslow-tests=true -Dfuzz-tests=true -Dlibc=musl build - ninja -v -C build - ;; - RUN) - info "Run phase" - - # Create dummy machine ID. - echo '052e58f661f94bd080e258b96aea3f7b' >/etc/machine-id - - # Start dbus for several unit tests. - mkdir -p /var/run/dbus - /usr/bin/dbus-daemon --system || : - - # Here, we explicitly set SYSTEMD_IN_CHROOT=yes as unfortunately runnin_in_chroot() does not - # correctly detect the environment. - env \ - SYSTEMD_IN_CHROOT=yes \ - meson test -C build --print-errorlogs --no-stdsplit --quiet - ;; - CLEANUP) - info "Cleanup phase" - ;; - *) - echo >&2 "Unknown phase '$phase'" - exit 1 - esac -done diff --git a/.github/workflows/unit-tests-musl.yml b/.github/workflows/unit-tests-musl.yml deleted file mode 100644 index 2120eddeeb1dc..0000000000000 --- a/.github/workflows/unit-tests-musl.yml +++ /dev/null @@ -1,115 +0,0 @@ ---- -# vi: ts=2 sw=2 et: -# SPDX-License-Identifier: LGPL-2.1-or-later -# -name: Unit tests (musl) -on: - pull_request: - paths: - - '**/meson.build' - - '.github/workflows/**' - - 'meson_options.txt' - - 'src/**' - - 'test/fuzz/**' - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Repository checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - persist-credentials: false - - - name: Install build dependencies - uses: jirutka/setup-alpine@v1 - with: - arch: x86_64 - branch: edge - packages: > - acl - acl-dev - audit-dev - bash - bash-completion-dev - bpftool - build-base - bzip2-dev - coreutils - cryptsetup-dev - curl-dev - dbus - dbus-dev - elfutils-dev - gettext-dev - git - glib-dev - gnutls-dev - gperf - grep - iproute2 - iptables-dev - kbd - kexec-tools - kmod - kmod-dev - libapparmor-dev - libarchive-dev - libbpf-dev - libcap-dev - libcap-utils - libfido2-dev - libgcrypt-dev - libidn2-dev - libmicrohttpd-dev - libpwquality-dev - libqrencode-dev - libseccomp-dev - libselinux-dev - libxkbcommon-dev - linux-pam-dev - lz4-dev - meson - openssl - openssl-dev - p11-kit-dev - pcre2-dev - pkgconf - polkit-dev - py3-elftools - py3-jinja2 - py3-pefile - py3-pytest - py3-lxml - quota-tools - rsync - sfdisk - tpm2-tss-dev - tpm2-tss-esys - tpm2-tss-rc - tpm2-tss-tcti-device - tzdata - util-linux-dev - util-linux-login - util-linux-misc - utmps-dev - valgrind-dev - xen-dev - zlib-dev - zstd-dev - - - name: Setup - run: .github/workflows/unit-tests-musl.sh SETUP - shell: alpine.sh --root {0} - - name: Build - run: .github/workflows/unit-tests-musl.sh BUILD - shell: alpine.sh {0} - - name: Run - run: .github/workflows/unit-tests-musl.sh RUN - shell: alpine.sh --root {0} - - name: Cleanup - run: .github/workflows/unit-tests-musl.sh CLEANUP - shell: alpine.sh --root {0} diff --git a/.github/workflows/unit-tests.sh b/.github/workflows/unit-tests.sh index c87b7d2e9a366..f910e21691fc0 100755 --- a/.github/workflows/unit-tests.sh +++ b/.github/workflows/unit-tests.sh @@ -3,11 +3,23 @@ # shellcheck disable=SC2206 PHASES=(${@:-SETUP RUN RUN_ASAN_UBSAN CLEANUP}) +# Packages that are always native (i.e.: tools that run on the host) go in this list ADDITIONAL_DEPS=( clang expect fdisk jekyll + linux-tools-generic + python3-libevdev + python3-pip + python3-pyelftools + python3-pyparsing + python3-pytest + rpm + zstd +) +# Packages that are needed for the target architecture (i.e.: libraries) go in this list +ADDITIONAL_TARGET_DEPS=( libbpf-dev libfdisk-dev libfido2-dev @@ -18,15 +30,8 @@ ADDITIONAL_DEPS=( libtss2-dev libxkbcommon-dev libzstd-dev - linux-tools-generic - python3-libevdev - python3-pip - python3-pyelftools - python3-pyparsing - python3-pytest - rpm - zstd ) +CROSS_ARCH="${CROSS_ARCH:-}" function info() { echo -e "\033[33;1m$1\033[0m" @@ -48,6 +53,12 @@ if [ "$(uname -m)" = "aarch64" ] || [ "$(uname -m)" = "x86_64" ]; then ADDITIONAL_DEPS+=(systemd-boot-efi) fi +if [[ -n "$CROSS_ARCH" ]]; then + ADDITIONAL_DEPS+=(crossbuild-essential-$CROSS_ARCH) +fi +# Append : to the packages so they get installed for the target +ADDITIONAL_DEPS+=("${ADDITIONAL_TARGET_DEPS[@]/%/${CROSS_ARCH:+:$CROSS_ARCH}}") + # (Re)set the current oom-{score-}adj. For some reason root on GH actions is able to _decrease_ # its oom-score even after dropping all capabilities (including CAP_SYS_RESOURCE), until the # score is explicitly changed after sudo. No idea what's going on, but it breaks @@ -64,8 +75,11 @@ for phase in "${PHASES[@]}"; do for f in /etc/apt/sources.list.d/*.sources; do sed -i "s/Types: deb/Types: deb deb-src/g" "$f" done + if [[ -n "$CROSS_ARCH" ]]; then + dpkg --add-architecture "$CROSS_ARCH" + fi apt-get -y update - apt-get -y build-dep systemd + apt-get -y build-dep systemd ${CROSS_ARCH:+--host-architecture=$CROSS_ARCH} apt-get -y install "${ADDITIONAL_DEPS[@]}" pip3 install -r .github/workflows/requirements.txt --require-hashes --break-system-packages @@ -99,6 +113,14 @@ for phase in "${PHASES[@]}"; do fi fi + if [[ -n "$CROSS_ARCH" ]]; then + if [[ "$phase" =~ ^RUN_GCC ]]; then + MESON_ARGS+=(-Ddbus-interfaces-dir=no -Dsbat-distro= --cross-file ".github/workflows/$CROSS_ARCH-gcc.cross") + elif [[ "$phase" =~ ^RUN_CLANG ]]; then + MESON_ARGS+=(-Dbpf-framework=disabled -Dbootloader=disabled -Defi=false -Ddbus-interfaces-dir=no -Dsbat-distro= --cross-file ".github/workflows/$CROSS_ARCH-clang.cross") + fi + fi + # On ppc64le the workers are slower and some slow tests time out MESON_TEST_ARGS=() if [[ "$(uname -m)" != "x86_64" ]] && [[ "$(uname -m)" != "aarch64" ]]; then diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 799506401781c..b9d8bacccb46c 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -16,7 +16,7 @@ jobs: build: runs-on: ${{ matrix.runner }} concurrency: - group: ${{ github.workflow }}-${{ matrix.run_phase }}-${{ github.ref }}-${{ matrix.runner }} + group: ${{ github.workflow }}-${{ matrix.run_phase }}-${{ matrix.cross_arch || 'native' }}-${{ github.ref }}-${{ matrix.runner }} cancel-in-progress: true strategy: fail-fast: false @@ -27,6 +27,9 @@ jobs: - run_phase: GCC - run_phase: GCC runner: ubuntu-24.04-arm + - run_phase: GCC + runner: ubuntu-24.04-arm + cross_arch: armhf - run_phase: GCC runner: ubuntu-24.04-ppc64le - run_phase: GCC @@ -34,10 +37,15 @@ jobs: - run_phase: CLANG - run_phase: CLANG runner: ubuntu-24.04-arm + - run_phase: CLANG + runner: ubuntu-24.04-arm + cross_arch: armhf - run_phase: CLANG runner: ubuntu-24.04-ppc64le - run_phase: CLANG runner: ubuntu-24.04-s390x + env: + CROSS_ARCH: ${{ matrix.cross_arch || '' }} steps: - name: Repository checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd @@ -50,6 +58,44 @@ jobs: sudo sed -i '/^XDG_/d' /etc/environment # Pass only specific env variables through sudo, to avoid having # the already existing XDG_* stuff on the "other side" - sudo --preserve-env=GITHUB_ACTIONS,CI .github/workflows/unit-tests.sh SETUP + sudo --preserve-env=GITHUB_ACTIONS,CI,CROSS_ARCH .github/workflows/unit-tests.sh SETUP - name: Build & test - run: sudo --preserve-env=GITHUB_ACTIONS,CI .github/workflows/unit-tests.sh RUN_${{ matrix.run_phase }} + run: sudo --preserve-env=GITHUB_ACTIONS,CI,CROSS_ARCH .github/workflows/unit-tests.sh RUN_${{ matrix.run_phase }} + + build-musl: + name: Build & test (musl, postmarketOS) + runs-on: ubuntu-24.04 + concurrency: + group: ${{ github.workflow }}-musl-${{ github.ref }} + cancel-in-progress: true + steps: + - name: Repository checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - uses: systemd/mkosi@f7762b71437227922a367bb89597843c77494ef9 + + - name: Build tools tree + run: | + tee mkosi/mkosi.local.conf < Sandy Carter Scott James Remnant Scott James Remnant +Sebastian Bernardt Seraphime Kirkovski Shawn Landden Shawn Landden diff --git a/.obs/workflows.yml b/.obs/workflows.yml index b0bfbc5f72405..de17bc88c1aa8 100644 --- a/.obs/workflows.yml +++ b/.obs/workflows.yml @@ -8,3 +8,46 @@ rebuild: branches: only: - main +ci: + steps: + - branch_package: + source_project: system:systemd + source_package: systemd + target_project: system:systemd:ci + # Also prepare some images, but disabled by default to avoid wasting cpu/disk + - link_package: + source_project: system:systemd + source_package: particleos-debian + target_project: system:systemd:ci + - link_package: + source_project: system:systemd + source_package: particleos-debian-arm + target_project: system:systemd:ci + - link_package: + source_project: system:systemd + source_package: particleos-fedora + target_project: system:systemd:ci + - link_package: + source_project: system:systemd + source_package: uki-debian + target_project: system:systemd:ci + - link_package: + source_project: system:systemd + source_package: uki-fedora + target_project: system:systemd:ci + - set_flags: + flags: + # First disable all builds (including images linked in above) + - type: build + status: disable + project: system:systemd:ci + # Then enable systemd package builds only, so that images stay disabled + - type: build + status: enable + project: system:systemd:ci + package: systemd + filters: + event: pull_request + branches: + only: + - main diff --git a/.packit.yml b/.packit.yml index 499b28f7c47fd..7a908de495b77 100644 --- a/.packit.yml +++ b/.packit.yml @@ -28,18 +28,30 @@ actions: jobs: - job: copr_build trigger: pull_request + # Install gdb into the buildroot so post-failure hooks can pull backtraces from any cores the + # %check phase leaves behind. targets: - - fedora-rawhide-aarch64 - - fedora-rawhide-i386 - - fedora-rawhide-ppc64le - - fedora-rawhide-s390x - - fedora-rawhide-x86_64 + fedora-rawhide-aarch64: + additional_packages: + - gdb + fedora-rawhide-i386: + additional_packages: + - gdb + fedora-rawhide-ppc64le: + additional_packages: + - gdb + fedora-rawhide-s390x: + additional_packages: + - gdb + fedora-rawhide-x86_64: + additional_packages: + - gdb - job: tests 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: 9cb09470c9c5a437f8e9c1e0e449b87de83733eb 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-runner.sh b/.semaphore/semaphore-runner.sh index 171cac8e1c702..22dc9fc4ffd73 100755 --- a/.semaphore/semaphore-runner.sh +++ b/.semaphore/semaphore-runner.sh @@ -68,8 +68,8 @@ EOF for phase in "${PHASES[@]}"; do case "$phase" in SETUP) - # remove semaphore repos, some of them don't work and cause error messages - sudo rm -rf /etc/apt/sources.list.d/* + # remove chrome repo, we don't need it + sudo rm -rf /etc/apt/sources.list.d/google-chrome.sources # enable backports for latest LXC echo "deb http://archive.ubuntu.com/ubuntu $UBUNTU_RELEASE-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/backports.list 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/.ycm_extra_conf.py b/.ycm_extra_conf.py index 46b5ecd9d9abd..c64cc48a7be24 100755 --- a/.ycm_extra_conf.py +++ b/.ycm_extra_conf.py @@ -43,11 +43,11 @@ import glob import os -import ycm_core +import ycm_core -SOURCE_EXTENSIONS = (".C", ".cpp", ".cxx", ".cc", ".c", ".m", ".mm") -HEADER_EXTENSIONS = (".H", ".h", ".hxx", ".hpp", ".hh") +SOURCE_EXTENSIONS = ('.C', '.cpp', '.cxx', '.cc', '.c', '.m', '.mm') +HEADER_EXTENSIONS = ('.H', '.h', '.hxx', '.hpp', '.hh') def DirectoryOfThisScript(): @@ -69,19 +69,18 @@ def GuessBuildDirectory(): containing '.ninja_log' file two levels above the current directory; returns this single directory only if there is one candidate. """ - result = os.path.join(DirectoryOfThisScript(), "build") + result = os.path.join(DirectoryOfThisScript(), 'build') if os.path.exists(result): return result - result = glob.glob(os.path.join(DirectoryOfThisScript(), - "..", "..", "*", ".ninja_log")) + result = glob.glob(os.path.join(DirectoryOfThisScript(), '..', '..', '*', '.ninja_log')) if not result: - return "" + return '' if 1 != len(result): - return "" + return '' return os.path.split(result[0])[0] @@ -106,9 +105,7 @@ def TraverseByDepth(root, include_extensions): # print(subdirs) if include_extensions: get_ext = os.path.splitext - subdir_extensions = { - get_ext(f)[-1] for f in file_list if get_ext(f)[-1] - } + subdir_extensions = {get_ext(f)[-1] for f in file_list if get_ext(f)[-1]} if subdir_extensions & include_extensions: result.add(root_dir) else: @@ -119,11 +116,11 @@ def TraverseByDepth(root, include_extensions): return result -_project_src_dir = os.path.join(DirectoryOfThisScript(), "src") -_include_dirs_set = TraverseByDepth(_project_src_dir, frozenset({".h"})) +_project_src_dir = os.path.join(DirectoryOfThisScript(), 'src') +_include_dirs_set = TraverseByDepth(_project_src_dir, frozenset({'.h'})) flags = [ - "-x", - "c" + '-x', + 'c', # The following flags are partially redundant due to the existence of # 'compile_commands.json'. # '-Wall', @@ -135,7 +132,7 @@ def TraverseByDepth(root, include_extensions): ] for include_dir in _include_dirs_set: - flags.append("-I" + include_dir) + flags.append('-I' + include_dir) # Set this to the absolute path to the folder (NOT the file!) containing the # compile_commands.json file to use that instead of 'flags'. See here for @@ -165,13 +162,13 @@ def MakeRelativePathsInFlagsAbsolute(flags, working_directory): return list(flags) new_flags = [] make_next_absolute = False - path_flags = ["-isystem", "-I", "-iquote", "--sysroot="] + path_flags = ['-isystem', '-I', '-iquote', '--sysroot='] for flag in flags: new_flag = flag if make_next_absolute: make_next_absolute = False - if not flag.startswith("/"): + if not flag.startswith('/'): new_flag = os.path.join(working_directory, flag) for path_flag in path_flags: @@ -180,7 +177,7 @@ def MakeRelativePathsInFlagsAbsolute(flags, working_directory): break if flag.startswith(path_flag): - path = flag[len(path_flag):] + path = flag[len(path_flag) :] new_flag = path_flag + os.path.join(working_directory, path) break @@ -213,8 +210,7 @@ def GetCompilationInfoForFile(filename): for extension in SOURCE_EXTENSIONS: replacement_file = basename + extension if os.path.exists(replacement_file): - compilation_info = \ - database.GetCompilationInfoForFile(replacement_file) + compilation_info = database.GetCompilationInfoForFile(replacement_file) if compilation_info.compiler_flags_: return compilation_info return None @@ -238,13 +234,14 @@ def FlagsForFile(filename, **kwargs): final_flags = MakeRelativePathsInFlagsAbsolute( compilation_info.compiler_flags_, - compilation_info.compiler_working_dir_) + compilation_info.compiler_working_dir_, + ) else: relative_to = DirectoryOfThisScript() final_flags = MakeRelativePathsInFlagsAbsolute(flags, relative_to) return { - "flags": final_flags, - "do_cache": True + 'flags': final_flags, + 'do_cache': True, } diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000000..5b90310dc0fb7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,42 @@ +# 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. + +## Legal + + - Only human beings can ever be credited within commit messages. This means no Co-Developed-By or + Co-Authored-By or anything similar that lists an AI model instead of a human being. + +## 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. 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..90550d85c78ac 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 @@ -66,8 +67,8 @@ The following exceptions apply: * the tools/chromiumos/gen_autosuspend_rules.py script is licensed under the **BSD-3-Clause** license. * the following sources are under **Public Domain** (LicenseRef-alg-sha1-public-domain): - - src/fundamental/sha1-fundamental.c - - src/fundamental/sha1-fundamental.h + - src/fundamental/sha1.c + - src/fundamental/sha1.h * the following files are licensed under **BSD-3-Clause** license: - src/boot/chid.c - src/boot/chid.h diff --git a/NEWS b/NEWS index f858e9119f346..d34215377ebc9 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,850 @@ systemd System and Service Manager -CHANGES WITH 260 in spe: +CHANGES WITH 262: + + Feature Removals and Incompatible Changes: + + * systemd-repart's MakeSymlinks= option now expands "%" specifiers in + the symlink target. Previously, these were only expanded in the + source. + + * As part of changes to systemd-sysupdate, the existing + "systemd-sysupdate.service" and "systemd-sysupdate.timer" units – + which periodically ran "systemd-sysupdate" to update the host system – + have been renamed to "systemd-sysupdate-update.service" and + "systemd-sysupdate-update.timer" respectively. Compatibility symlinks + have been provided. This clears the way for a new + "systemd-sysupdate@.service" unit for varlink activation of sysupdate. + +CHANGES WITH 261: + + Announcements of Future Feature Removals and Incompatible Changes: + + * systemd-logind's integration with the UAPI.1 Boot Loader + Specification (which allows the 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. + + * The experimental "systemd-sysupdated" D-Bus API is going to be + removed in the next release. The plan is that in its place + clients should directly talk to systemd-sysupdate (i.e. the backend + of "systemd-sysupdated") via Varlink IPC. The "updatectl" tool will + be reworked along these lines. + + 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 + forms (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 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 some of the events systemd-stub measures to + the TPM were not also measured to the hardware CC registers (e.g. + Intel TDX RTMRs) via EFI_CC_MEASUREMENT_PROTOCOL. In particular, + devicetree, initrd, ucode addons and the UKI profile were only + measured to the TPM. The missing measurements for CC have now been + added; however, this changes the expected register values. This + may need to be reflected in the attestation environments which use + hardware CC registers (in place of TPM quotes). + + * 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. + + * A new service unit "systemd-pcrosseparator.service" will now measure + a new separator measurement during early userspace into PCRs 0-7, 9, + 12-14, in order to isolate firmware/pre-boot measurements from host + measurements. This is a safety concept to protect firmware + measurements on systems where the regular firmware separator + measurement is missing. It's also useful in environments where a + software TPM is used, i.e. where TPM functionality is only available + starting with the OS, but not before. Note that this new measurement + has an effect on all indicated PCRs, hence might affect relevant TPM + policies. + + * Support for udev's old database version 0 has been removed. This + effectively means live upgrades from versions older than v247 are not + supported anymore. + + * systemd-networkd gained a new sd-dhcp-relay backend for DHCP relay + agent support. As part of this change, the following [DHCPServer] + settings are deprecated: + - BindToInterface= + - RelayTarget= + - RelayAgentCircuitId= + - RelayAgentRemoteId= + They are replaced by DHCPRelay= in [Network], along with new + [DHCPRelay] section settings in .network files: + - AgentAddress= + - GatewayAddress= + - CircuitId= + - VirtualSubnetSelection= + - ExtraOption= + - InterfacePriority= + and in networkd.conf: + - ServerAddress= + - OverrideServerIdentifier= + - RemoteId= + - ExtraOption= + + * Required version of musl (when built with -Dlibc=musl) has been raised + from 1.2.5 to 1.2.6. + + * libsystemd is no longer guaranteed to be linked against libm. Whether + the dependency is recorded depends on whether the compiler chooses to + emit builtins for all calls to libm symbols. Consumers that rely on + libsystemd transitively pulling in libm should link against it + themselves. There is at least one known case that is still unsolved: + rsyslog crashes on launch due to libfastjson using libm without linking + to it, which was previously masked because libsystemd linked to it. If + forcing a link against libm is required as a workaround, + '-Wl,--push-state,--no-as-needed,-lm,--pop-state' can be added to the + link flags, or passed to systemd's meson build options via + '-Dc_link_args=-Wl,--push-state,--no-as-needed,-lm,--pop-state'. + + Changes in the system and service manager: + + * PID1 now supports the kernel's Live Update Orchestration (LUO) / + Kexec Handover (KHO) systems when present and enabled. System units' + FD Stores are now preserved through kexec, and units will get back + stashed (named) file descriptors after kexec, if the kernel supports + the FD type (at the time of writing only memfds are supported). + Units can also create their own LUO Sessions by talking to the kernel + directly, and store them in their FD Stores, and those will also be + preserved and passed down to the unit after kexec. Units must set + 'FileDescriptorStorePreserve=yes' in order to enable this feature. + + * User session managers now support persisting user units' FD Stores + by receiving FDs via the notify socket, and passing them down via + $LISTEN_FDS when the user session is restarted, if the + 'FileDescriptorStorePreserve=yes' and 'FileDescriptorStoreMax=' + options are set in the user@.service unit. Combined with the LUO + support, this lets user units persist state (e.g.: memfds) across + not only user session restarts, but also kexec reboots. + + * The manager exposes a new ReloadCount property on its D-Bus and + Varlink interfaces (org.freedesktop.systemd1.Manager and + io.systemd.Manager respectively). The counter increments after + each successfully completed daemon-reload, and it is reset on + daemon-reexec. + + * A new unit setting CPUSetPartition= has been added that allows + configuring the cpuset cgroup partition type (e.g. "root", + "isolated", "member") for a service. + + * A new RestrictFileSystemAccess= setting has been added that uses a + BPF LSM program to restrict execution to only binaries that are + stored on a signed and verified dm-verity-protected filesystem. + + * The io.systemd.Unit.StartTransient() Varlink method has been added + for invoking service units transiently. + + * A new set of Varlink methods has been added to the + io.systemd.Manager interface to request system shutdown: + PowerOff(), Reboot(), SoftReboot(), Halt() and Kexec(). These + complement the existing D-Bus interfaces. + + * The io.systemd.Manager.ListUnitsByNames() Varlink method allows + querying multiple units in one call and supports a result limit. + + * A new DefaultMemoryZSwapWriteback= manager setting has been added + that provides a system-wide default for the existing + MemoryZSwapWriteback= per-unit setting. + + * A new io.systemd.Job Varlink interface exposes information about + pending and running manager jobs. + + * The service manager knows two new global knobs + EventLoopRateLimitIntervalSec=/EventLoopRateLimitBurst= to configure + PID1's event loop ratelimit logic. This permits fine-tuning the + safety logic in PID 1 that slows down operation in case PID 1 starts + to busy loop. + + * The service manager gained new per-unit settings + CPUPressureWatch=/CPUPressureThresholdSec=/IOPressureWatch=/IOPressureThresholdSec= + which enable services to get generic notifications on CPU or IO + pressure events. + + * A new global service manager knob MinimumUptimeSec= has been added + that defines a minimum uptime for the system. It defaults to 15s. If + the system is shut down more quickly than the specified time a delay + is inserted in the last part of shutdown, in order to avoid tight + boot loops. + + * The FileDescriptorStorePreserve= unit setting can now take a new option + 'on-success', which preserves the FD Store when the unit is stopped, + but only if it exited successfully, and discards it otherwise. + + * The service manager now implements a new Varlink interface + io.systemd.Job for listing/cancelling any queued jobs. + + * A new knob ConditionFraction= enables scheduling of units on a + specified fraction of the fleet of systems only. It takes a "tag" + string and a percentage. The system's machine ID is hashed together + with the tag into a 32bit integer, and the result is compared with + the percentage of 2^32. If below, the condition is true, otherwise + false. This allows staged rollout of services: if multiple systems + are provisioned with the same units only roughly the specified + percentage of systems will run the service, the rest will not. + + * A new knob ConditionMachineTag= allows conditioning a unit based on + per-mach "tag" strings, as configured in /etc/machine-info, see below. + + New IMDS (Cloud "Instance Metadata Service") Subsystem: + + * The hardware database now contains a new database hwdb.d/40-imds.hwdb + that recognizes various established public clouds by their SMBIOS + information, and provides information on how to reach local IMDS + functionality on the node. Currently, Amazon EC2, Microsoft Azure, + Google Compute Engine, Hetzner, Oracle Cloud, Scaleway, Tencent + Cloud, Alibaba ECS and Vultr are recognized. + + * An IMDS subsystem has been added. Specifically, there's now + systemd-imdsd which provides a local Varlink IPC API that makes IMDS + services accessible to local programs. It provides both a relatively + low-level interface for querying arbitrary fields, and a higher-level + interface for querying certain well-known keys in a generic way + (which maps to various cloud-specific keys via the hwdb). The service + can be pulled into the boot transaction automatically if a supported + cloud is recognized via the systemd-imds-generator + functionality. This permits implementation of truly generic images + that can interact with IMDS if available, but operate without if + not. A tool systemd-imds acts as a client to systemd-imdsd and + imports various IMDS-provided fields into local system credentials, + which can then be consumed by later services. The acquired IMDS data + is measured before being imported. + + * 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 the TPM Subsystem: + + * A new ConditionSecurity=measured-os unit condition has been added + that checks whether the system was booted with measured-boot + semantics (i.e. via systemd-stub or an equivalent verified-boot + mechanism that measured the OS to the TPM). This is very similar to + the pre-existing ConditionSecurity=measured-uki, but is more + generic, as it can also cover environments where the firmware/UKI does + not have a TPM but the OS has (which is for example the case if the + TPM is implemented purely in software). + + * A new service systemd-tpm2-swtpm.service has been added that can run + the IBM "swtpm" as a software TPM, for use as an (optional) automatic + fallback for systems that lack a physical TPM but where TPM + functionality should be made available nonetheless. (This + functionality must be enabled via systemd.tpm2_software_fallback= on + the kernel command line.) Of course a software TPM running as part of + a system's userspace does not provide a security posture in any way + equivalent to that of a discrete hardware TPM, but in various + use cases it might still be preferable to having no TPM functionality + at all. The software TPM uses a key derived from the new "boot + secret" functionality for encryption, and stores its state in the + disk's ESP. This provides at least some protection, and reasonable + persistency from initrd on. + + * systemd-boot and systemd-stub will now measure SMBIOS Type 1, Type 2 + and Type 11 in PCR 1, since some firmwares do not measure them, even + though they are supposed to. + + * systemd-tpm2-setup.service will now allocate NvPCRs in an orer + configurable via the "priority" field of their defining JSON object. + As NV index space is very constrained, it's essential to allocate them + in the order of relevance, so that the least relevant NvPCRs are + dropped, and the most relevant NvPCRs kept. + + Changes in systemd-tmpfiles and systemd-sysusers: + + * A new tmpfiles.d/root.conf has been added that sets permissions on + the root directory (/) to 0555. This is particularly useful in + environments where the root file system is created fresh and empty + with only /usr/ mounted in – but it is also useful as a general + safety net. + + * systemd-tmpfiles gained a new --inline switch which permits passing + tmpfiles.d/ directives directly on the command line rather than via a + configuration file or STDIN. This is similar to the switch of the + same name to systemd-sysusers. + + * New directive types 'k/K' have been added to systemd-tmpfiles for + setting file capabilities. + + 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. + + * A kernel command line kill switch that entirely disables + systemd-sysext and systemd-confext merging is now honoured. + + Changes in systemd-networkd and networkctl: + + * A new 'networkctl dhcp-lease INTERFACE' command has been added to + dump acquired DHCP leases. This may be useful for inspecting the + DHCP options provided by the server. + + * systemd-networkd implements the io.systemd.service.Reload() Varlink + method, and exposes new io.systemd.Network.Link.Describe(), + Reconfigure(), Renew() and ForceRenew() methods. 'networkctl' now + uses these Varlink methods in preference to the legacy D-Bus API + where possible. + + * A new IPv4SrcValidMark= setting has been added to .network files. + + * The VRF.Table= setting now accepts symbolic route table names (as + configured via RouteTable= in networkd.conf) in addition to + numeric table IDs. + + * New DHCPServerPoolSize= and DHCPServerPoolOffset= properties have + been added to the D-Bus interface, mirroring the existing + configuration file options. + + * The DHCPv4 server gained support for serving the SIP server option + (RFC 3361) to clients. + + * The Varlink Describe() output now reports interface bit rates. + + * .link files gained knobs to control IRQ affinity. + + Changes in systemd-resolved: + + * systemd-resolved will now read additional DNS resource record + definitions to resolve locally from JSON drop-in files in + {/etc,/run,/usr/local/lib,/usr/lib}/systemd/resolve/static.d/. This + is a generalization of /etc/hosts, but is intended to be + more flexible (i.e. other RR types than just A/AAAA + PTR can be + configured, even if right now not too many are hooked up yet) and + follow the usual drop-in pattern that avoids ownership conflicts. + + * New 'DNSCacheSize=', 'MulticastDNSCacheSize=' and 'LLMNRCacheSize=' + settings are now supported to allow overriding the default + per-interface cache sizes for the respective protocols. + + * Insecure DNSSEC answers using unsupported signature or digest + algorithms are now correctly accepted as insecure, rather than + being rejected outright. + + * When StaleRetentionSec= is set, the resolver no longer flushes its + cache on server switch or re-probe, keeping potentially useful + stale entries available. + + * /etc/hosts entries are now re-read on reload (SIGHUP / D-Bus + Reload() / Varlink Reload()). + + Changes in systemd-udevd, hwdb and udev rules: + + * The DMI ID device (/sys/class/dmi/id) is now tagged so that + early-boot consumers can reliably order against it. + + * udev's "blkid" builtin will now set a new udev property + ID_PART_GPT_AUTO_ROOT_DISK_NEEDS_LOOP=1 on boot block devices where a + GPT partition table is detected for a sector size different from the + native sector size of the device. (This typically happens if a Hybrid + ISO9660/GPT disk image is booted as CDROM, where the native sector + size is 2048 but the GPT header uses a 512-byte sector size). If this + happens then a systemd-loop@.service instance is automatically pulled + in via a udev rule that generates a loopback block device from the + discovered block device, exposing the device with the corrected + sector size. Or in other words: booting a fully valid GPT disk image + on a block device with a non-matching sector size will now just work, + and automatically result in a matching loopback device popping + up. The new property is also set if the boot block device carries a + GPT header (i.e. is partitioned) but the block device has partition + table processing turned off. + + * Persistent network interface naming has been extended to auxiliary + sub-function (SF) network devices (such as mlx5_core SFs), using an + "S" suffix appended to the parent PCI function's name (e.g. + "enp193s0f0S88"). + + Changes in systemd-boot, systemd-stub, bootctl, ukify: + + * systemd-stub will now maintain a "boot secret" and pass it to the OS + in the /.extra/boot-secret file in the initrd. This boot secret is + derived from a persistent EFI variable that is not accessible by the + OS (i.e. only accessible in the UEFI environment). The EFI variable + is automatically initialized to a randomly generated value if not set + yet. It is intended to be used for certain fallback codepaths in case + a local TPM is not available, but a UEFI environment is. If a TPM is + available, it's highly recommended to use it as a better source of + per-system key material, but in the absence of a TPM it often might be an + acceptable fallback for local, persistent key material. Applications + should never use the key as-is, but derive their own key from it, + through hashing. + + * systemd-stub now auto-detects the active EFI serial console device + and appends an appropriate "console=" parameter to the kernel command + line, simplifying serial-console UKI deployments: the serial console + output configuration of UEFI is now automatically propagated to + Linux. + + * systemd-stub will now query the firmware's keyboard mapping and pass + it to the OS via the LoaderKeyboardLayout EFI variable. This variable + is then used by systemd-vconsole-setup as a fallback keyboard mapping + if no mapping is explicitly configured otherwise. On modern laptops + this means there's a good chance that the keyboard mapping of the + built-in keyboard will be automatically detected and set up without + requiring user intervention. + + * A new "extra" Type #1 Boot Loader Specification stanza is parsed and + used to deliver additional resources to a UKI without modifying its + contents. This may be used to pass confext DDIs, sysext DDIs or + encrypted credentials to a UKI kernel. The generic "addon" handling + has been generalized so that all UKI sidecar artifacts (initrds, + command-line overlays, devicetree blobs, etc.) follow the same lookup + rules. + + * systemd-boot will never auto-boot a non-default UKI profile, + preventing accidental boots into alternative profiles after a + single timeout expiry. + + * systemd-stub: El Torito CDROM boot catalog partition UUIDs are now + discovered and exposed via the same mechanism as GPT/MBR partitions, + enabling unified ISO image dissection. + + * systemd-stub will now incorporate any initrd already configured via + the LINUX_INITRD_MEDIA_GUID UEFI device into the set of initrds it + passes to the kernel (previously it would fail if one was already + set). This means systemd-stub now operates in a purely incremental + mode regarding initrds passed in from earlier boot steps. + + * bootctl gained a new '--print-efi-architecture' option that prints + the EFI architecture identifier of the running system, which is + useful from scripts. + + * bootctl gained a new 'link' verb (with a matching Varlink API) that + installs a Type #1 boot loader entry based on a UKI in combination + with confext DDIs, sysext DDIs or system credentials. + + * bootctl's 'unlink' verb is now also accessible via a Varlink API. + + * bootctl now stores the existing systemd-boot binary as a fallback when + installing a new version, and installs a fallback UEFI boot entry, to + allow a system to recover from a non-working version being installed. + + Changes in systemd-repart: + + * A new EncryptKDF= setting controls the KDF used for LUKS2 + partitions (e.g. argon2id, argon2i, pbkdf2). + + * A new VolumeName= setting allows specifying the LUKS2 volume + name independently of the on-disk partition label. + + * A new BlockDeviceReplace= setting allows partitions to atomically + migrate the contents of an existing block device to a different + partition. This may be used for OS installers that migrate the + running OS as a whole from an in-memory block device onto a disk, + requiring no reboot as part of the installation cycle. + + * systemd-repart now supports a new --grain-size= switch to explicitly + select the desired "grain" size (i.e. alignment granularity) when + placing partitions. It defaults to 4K (as before), but can now be set + to any other power of 2 larger than the sector size. + + * A new --el-torito= command line option causes a minimal El + Torito boot catalog to be written for EFI boot on hybrid ISO + images. + + * --shrink now uses mkfs.btrfs's native minimal-filesystem support + when available. + + * A new per-partition Discard= setting may be used to control + the persistent "allow-discards" flag of LUKS encrypted partitions. + + Changes in systemd-sysupdate: + + * systemd-sysupdate now emits READY=1 via sd_notify() after the + install step completes, allowing for tighter integration with + orchestration tooling. + + * systemd-sysupdate is now installed in /usr/bin/ alongside the + other user-facing tools, as it is no longer considered experimental. + + Changes in systemd-nspawn, systemd-vmspawn, systemd-machined: + + * systemd-nspawn now supports persisting the payload's system manager + FD Store by receiving FDs via the notify socket, and passing them + down via $LISTEN_FDS when the container is restarted, if the + 'FileDescriptorStorePreserve=yes' and 'FileDescriptorStoreMax=' + options are set in the unit inside which systemd-nspawn is running. + Combined with the LUO support in PID1, this lets containers persist + state (e.g.: memfds) across not only container restarts, but also + kexec reboots. + + * systemd-nspawn gained new --forward-journal= and + --forward-journal-NAME= options to forward journal entries from + the payload to specified journal sockets. + + * systemd-vmspawn gained a new --bind-volume= option that binds volumes + provided by the storage provider Varlink logic (see below) into a VM. + + * systemd-vmspawn gained a new --console-transport= option that + controls how the VM console is presented (PTY, native, headless, + etc.); a PTY is now provided for the native console mode, and + headless console operation is supported. + + * systemd-vmspawn's --console= switch gained a new value "headless" to + spawn a VM in truly headless mode, i.e. without a console or display. + + * systemd-vmspawn gained a new switch --efi-nvram-state= for + controlling whether and where to persist the EFI variable NVRAM + between VM invocations. It's modelled after --tpm-state= in + behaviour. There's also a new --efi-nvram-template= knob for + selecting a template file to initialize the EFI NVRAM state from on + first boot. + + * systemd-vmspawn's TPM logic will now ensure an endorsement + certificate is installed. + + * systemd-vmspawn gained a new --firmware-features= option that + enables or disables individual firmware features (with a + "~feature" prefix for negation). + + * systemd-vmspawn now searches XDG_DATA_DIRS for QEMU firmware + descriptors. + + * systemd-vmspawn now supports direct kernel boot without UEFI + firmware. + + * systemd-vmspawn gained support for a new --image-disk-type= switch + for selecting the block storage type (virtio-blk, virtio-scsi, nvme, + scsi-cd) for block devices exposed to the VM. The --extra-drive= + switch can now optionally configure this too. + + * The io.systemd.MachineInstance Varlink interface gained + AddStorage(), RemoveStorage() and ReplaceStorage() methods for + runtime storage manipulation, implemented by systemd-vmspawn. + + * systemd-vmspawn now pre-allocates PCIe root ports to allow PCIe + device hotplug, with multifunction packing where supported. + + * systemd-vmspawn now uses the QEMU built-in vdagent (clipboard, + resolution sync) instead of spicevmc. + + * systemd-vmspawn's --grow-image now detects and rejects qcow2 + images, where the operation is not supported. + + * systemd-vmspawn now propagates the host TERM environment variable + into the VM. + + * systemd-vmspawn gained support for a new --coco= switch for enabling + Confidential Computing. Currently, it only supports AMD SEV-SNP. + + * A new 'storagectl' command line tool and an accompanying + io.systemd.StorageProvider Varlink interface have been added, + alongside the new generic providers systemd-storage-fs@.service and + systemd-storage-block@.service. These allow exposing storage + resources (filesystems, block devices) in a unified manner for use + as managed user storage. + + * systemd-machined Machine.List/Register output now includes a + 'controlAddress' field describing the manager's bus address, + where known. + + * Querying metadata of registered machines is now gated behind + dedicated polkit actions + (org.freedesktop.machine1.inspect-machines and inspect-images). + + * machinectl gained 'bind-volume' / 'unbind-volume' verbs to + manage runtime bind mounts of host paths into running machines, + and new verbs to control the lifecycle of VMs (pause, resume, + power-off, etc.) via the io.systemd.MachineInstance Varlink + interface. + + Changes in systemd-coredump and coredumpctl: + + * 'coredumpctl info' has gained JSON output (--json=). + + * The crashing thread's TID and name are now captured and + recorded alongside the existing PID/comm metadata. + + * systemd-coredump will now pick up a new field COREDUMP_CODE= for all + coredumps that happen. This is a field provided by kernel 7.1 that + contains details about the reason for the coredump, with various + details depending on the architecture. "coredumpctl info" has been + updated in order to be able to decode this new field. + + Changes in systemd-creds, systemd-cryptsetup and + systemd-cryptenroll: + + * systemd-creds only locks against the public-key TPM2 PCR when + booting on UEFI firmware that supports TPMs, avoiding spurious + errors on systems without a TPM. + + * libcryptsetup is now loaded via dlopen() in the cryptsetup + binaries, eliminating the hard runtime dependency for systems that do + not actually use it. + + * systemd-cryptenroll now defaults to sealing the LUKS2 key using + RSA-OAEP with SHA-256 (or SHA-1 if the hardware doesn't support it), + in order to make the setup more robust against theoretical future + brute force attacks. Existing PKCS#1 v1.5 enrollment remain supported + by systemd-cryptsetup for backward compatibility. + + Changes in Dynamic Linking: + + * libgnutls, libmicrohttpd, libcurl, libcrypto, libssl, libfdisk + and libcryptsetup are now consistently loaded via dlopen() + throughout the codebase, further reducing the set of mandatory + dependencies from all binaries. + + * Individual binaries now also embed the dlopen ELF metadata note + (https://uapi-group.org/specifications/specs/elf_dlopen_metadata/) for + their dlopen() dependencies, which allows packaging tools to reliably + discover the set of optional dependencies of a binary, as opposed to + having the note only on the libsystemd-shared.so ELF object as before. + + * The unused dependency on libgpg-error has been dropped. + + │ This means all direct shared library linking against external │ + │ libraries has now been replaced by dlopen()-based linking, with the │ + │ sole exception of libc. │ + + Changes related to Varlink: + + * sd-varlink gained a new call sd_varlink_set_sentinel() that + simplifies generating responses to method calls that have "more" set. + + * sd-varlink gained a new call sd_varlink_call_and_upgrade() that + permits calling a method call with the Varlink "upgrade" feature + enabled, i.e. that allows switching from Varlink to a different + protocol. varlinkctl acquired a new --upgrade switch to expose this + functionality. A new call sd_varlink_reply_and_upgrade() supports + "upgrade" mode on the server side. + + * The 'ret' argument of sd_varlink_idl_parse() is now optional. + + * sd-varlink's per-UID connection limit has been reduced to 128. + + * varlinkctl gained a new 'serve' verb that wraps an arbitrary + command as a Varlink server, and a new '--upgrade' option + (along with '--exec') to consume the protocol upgrade API. + + Changes in libsystemd: + + * A new public 'sd-dlopen' header-only API has been added that + provides macros (SD_ELF_NOTE_DLOPEN()) for annotating dlopen'd + dependencies via the UAPI.12 ELF metadata specification + (https://uapi-group.org/specifications/specs/elf_dlopen_metadata/). + This header is licensed under MIT-0 to facilitate embedding it + directly in other projects. + + * sd_json_parse() (and related calls) now supports a pair of new flags + SD_JSON_PARSE_MUST_BE_OBJECT and SD_JSON_PARSE_MUST_BE_ARRAY. If + specified, these flags cause the parser to fail if the top-level + parsed JSON variant is not an object/array. + + * sd-json gained a new helper sd_json_parse_fd() that parses JSON data + from a file referenced by a file descriptor. It works similar to + sd_json_parse_file(), which operates on a FILE*. Moreover, a new + flag SD_JSON_PARSE_SEEK0 has been added which explicitly resets the + file offset to 0 when parsing via sd_json_parse_file() or + sd_json_parse_fd(). + + * sd-event gained native support for CPU and IO pressure events, in + addition to the pre-existing support for memory pressure events. This + is useful for slowing down or pausing worker threads or so if CPU or + IO is under pressure. + + * sd-path now exposes the XDG 'projects' user directory. + + Changes in systemd-hostnamed: + + * systemd-hostnamed now provides a D-Bus API to acquire arbitrary + fields from /etc/machine-info. + + * systemd-hostnamed is now available in early boot too (i.e. before + basic.target). Note that D-Bus only becomes available later, and it + can hence only be contacted via Varlink that early. + + * systemd-hostnamed and /etc/machine-info now support a new Tags= key, + which can be used to tag a machine with an arbitrary set of strings. + Units can match on these tags via the new ConditionMachineTag= setting, + and systemd-firstboot can set the tags via command line parameters or + credentials. + + Changes in systemd-logind: + + * A new systemd-pcrlogin@.service service will now measure a minimized + user record into the new 'login' NvPCR upon first login. + + * A new io.systemd.Shutdown Varlink interface has been introduced + to request system shutdown. The peer connection identifier of + the requester is logged. + + * systemd-logind gained new Varlink APIs for listing sessions, users, + seats and inhibitors + + Changes related to kexec: + + * 'systemctl kexec' gained a new --kernel-cmdline= argument that + overrides the kernel command line for kexec invocations. + + * 'systemctl kexec' now prefers invoking the 'kexec_file_load()' system + call directly, and uses the 'kexec' binary only as a fallback if that + is not available, so that on most systems the dependency on + 'kexec-tools' is no longer necessary. + + Changes in systemd-firstboot: + + * systemd-firstboot will now pre-fill the input prompts for keyboard + and local with the corresponding settings from the firmware if + supported. There's a good chance, this means on recent hardware you + can just keep hitting Enter in the prompts and will nonetheless get + the right keyboard mapping set up. bootctl will show this data too, + if available. + + * systemd-firstboot will now honour a new "firstboot.hostname" system + credential for persistently setting the system hostname on first + boot. This is different from the pre-existing "system.hostname", + which sets the hostname only for the boot the credential is passed + on, and which is not made persistent. + + Other changes: + + * The systemd-report framework introduced in v260 has been + substantially extended. Basic system metrics + (PhysicalMemoryBytes, CPUsOnline, SMBIOS fields, /etc/machine-info + fields, Confidential Computing vendor info, TPM2 vendor info) are + now provided by a new systemd-report-basic@.service that is enabled + by default via its report-basic.socket activation unit. Per-cgroup + metrics (CPU time, etc.) and per-service metrics are exposed through + dedicated Varlink services. systemd-report gained the ability to + upload collected reports via a Varlink socket directory or HTTP + destinations, and to inject custom HTTP headers when doing so. + + * JSON user database records may now optionally carry a birth date + field to close the gap with LDAP/OpenID/FreeIPA/etc. homectl gained + a new switch --birth-date= to set it. + + * systemd-vconsole-setup will now gracefully handle the case where the + setfont/loadkeys tools are not installed, and skip operation cleanly + in that case. + + * The _netdev pseudo mount option is now also supported for swap + devices, i.e. enabling correct boot time ordering to allow swapping + on network block devices. + + * systemd-run gained a new --output= switch for controlling log output + formatting when using "-v" mode. + + * A new component systemd-sysinstall has been added that implements a + simple, modern textual installer for an OS. It's a wrapper around + Varlink calls to systemd-repart (to set up a partition table and + stream in the OS partitions), bootctl link (to install kernel and + boot menu items for the OS), bootctl install (to install the + systemd-boot boot loader), systemd-creds (to configure the minimal + amount of system settings, such as keyboard mappings, locale for the + newly installed system), followed by a request to reboot. It operates + either interactively or command-line driven. + + * systemd-oomd gained support for OOM rulesets. These allow fine-tuning + OOM policy handling, and may be defined in /etc/systemd/oomd/rules.d/ + and then enabled on a service unit via the new OOMRule= option. + + * systemd-socket-proxy now optionally implements the "PROXY protocol + v1", as defined by "haproxy". See the new --proxy-protocol= switch + for details. + + Contributions from: A S Alam, Adam Dinwoodie, Adam Williamson, + Adolfo Jayme Barrientos, Adrian Wannenmacher, Aksel Azwaw, + Aleksa Sarai, Alexander Shopov, Alexey Rubtsov, Ali Ciloglu, + Alyssa Ross, Ambareesh Balaji, Américo Monteiro, Anders Jonsson, + Andika Triwidada, Andreas K. Hüttel, Andrei Stepanov, + Anselm Schueler, Antonio Alvarez Feijoo, Arif Budiman, + Aritra Basu, Arnout Engelen, Artem Proskurnev, + Asier Sarasua Garmendia, Balázs Meskó, Balázs Úr, + Baurzhan Muftakhidinov, Bone NI, Bret Comnes, ButterflyOfFire, + Charles Lee, Cheng-Chia Tseng, Chitoku, Chris Down, Chris Hofer, + Chris Mason, Chris Patterson, Christian Brauner, + Christian Goeschel Ndjomouo, Christian Hesse, Christian Kirbach, + Christian Wehrli, Claude Opus 4.6, Clayton Craft, Cynthia, + Daan De Meyer, Dan Anderson, Daniel Nylander, Daniel Rusek, + Dark Cronyx, David Santamaría Rogado, David Tardon, Deleted User, + Derek J. Clark, Diego Viola, Dimitrys Meliates, Dirga Yuza, + Dmitry Konishchev, Duncan Overbruck, Dylan M. Taylor, + Efstathios Iosifidis, Emanuele Rocca, Emilio Herrera, + Emilio Sepulveda, Emir SARI, Ettore Atalan, + Fco. Javier F. Serrador, Felix Pehla, Fran Diéguez, Franck Bui, + Frantisek Sumsal, Fábio Rodrigues Ribeiro, Gabriel Elyas, + George Tsiamasiotis, Geraldo S. Simião Kutz, Gogo Gogsi, + Gustavo Costa, Göran Uddeborg, Hadi Chokr, Hang Li, Hela Basa, + Henri Aunin, Heran Yang, Honza Hejzl, Hugo Carvalho, + Icenowy Zheng, Ivan Kruglov, Ivan Shapovalov, Jan Kalabza, + Jan Kuparinen, Jarl Gullberg, Jarne Förster, Jesse Guo, + Jim Spentzos, Jiri Grönroos, Jiri Pirko, Jonas Dreßler, + Jonas Rebmann, Jonathan Davies, Jordan Petridis, Jose Ortuno, + José Miguel Sarasola, João Taveira Araújo, Julian Sparber, + Justinas Kairys, Jörg Behrmann, Kai Lüke, Kajus Naujokaitis, + Kit Dallege, Kristjan Schmidt, Lennart Poettering, + LevitatingBusinessMan (Rein Fernhout), Liu Zhangjian, LuK1337, + Luan Vitor Simião Oliveira, Luan Vitor Simião oliveira, + Luca Boccassi, Luke Na, Luna Jernberg, Léane GRASSER, Maarten, + Malformed C, Marcel Leismann, Marcel Ziswiler, Marek Adamski, + Marin Kresic, Martin Srebotnjak, Massii Aqvayli, + Matheus Afonso Martins Moreira, Matteo Croce, Matthew Schwartz, + Max Chernoff, Michael Ferrari, Michael Vogt, Michal Rybecky, + Michal Sekletár, Mike Yuan, Mikhail Nogin, Milan Kyselica, + Milo Casagrande, Moisticules, Morten Linderud, Nandakumar Raghavan, + Nathan, Nick Rosbrook, Nikolas Kyx, Nita Vesa, Oblivionsage, + Olga Smirnova, Oğuz Ersen, Patrick Rohr, Patrick Wicki, + Paul Meyer, Pavel Borecki, Petru Rebeja, Philip Withnall, + Piotr Drąg, Pjotr Vertaalt, Poesty Li, Priit Jõerüüt, + Quentin Deslandes, Rafael Fontenelle, Raito Bezarius, + Richard E. van der Luit, Ricky Tigg, RiskoZS, Robin Ebert, + Rocker Zhang, Ronan Pigott, Salvatore Cocuzza, Samuel Dainard, + Samuel Holland, Scrambled 777, Sebastian Bernardt, + Sergei Trofimovich, Sergey A., Simon Lucido, Simon de Vlieger, + Simran Singh, Sriman Achanta, Stephane Chazelas, Takuro Onoue, + Temuri Doghonadze, Tiago Rocha Cunha, Tim Vangehugten, + Tobias Heider, Tobias Stoeckmann, Todd Zullinger, TristanInSec, + Ulrich Ölmann, Valentin David, Vincent Mihalkovic, + Vitaly Kuznetsov, Vlad, Walter McKelvie, Whired Planck, + Will Fancher, Y T, Yaping Li, Yaron Shahrabani, Yu Watanabe, + Yuri Chornoivan, Zbigniew Jędrzejewski-Szmek, Zmicer Turok, + albertescanes, arvind froiland, azureuser, blutch112, djantti, + dongshengyuan, doof, drhydroxide, elysia090, favilances, fecet, glemco, + guido, hanjinpeng, hschloss, hsu zangmen, ipv6, ishwarbb, jeffhuang, + jmestwa-coder, joo es, kakolla, kanitha chim, kostich, lumingzh, + lzwind, mburucuyapy, mooo, mukunda katta, naly zzwd, nikstur, + noxiouz, r-vdp, roib, rusty-snake, scootergrisen, seaeunlee, ssahani, + sykikxo, vad, vlefebvre, z z, Дамјан Георгиевски, + Марко Костић (Marko Kostić), наб, 我超厉害, + 김인수 + + — Edinburgh, 2026/06/19 + +CHANGES WITH 260: Feature Removals and Incompatible Changes: @@ -68,12 +912,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 +982,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 +1012,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 +1075,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 +1178,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: @@ -450,12 +1312,22 @@ CHANGES WITH 260 in spe: suppress any operation in case no images where added, removed or changed. To force a umount/mount operation in this case (i.e. get back to the status quo ante) a new --always-refresh= option has been - added. + added. Note that the change detection identifies extensions by + identity (verity hash, or else by file handle/inode, mount ID, + creation time and modification time), and does not look at their + contents. In particular, for directory-based extensions, adding, + removing or modifying files inside the directory (or touching the + directory itself) is not detected, and such a refresh is suppressed + by default. Use --always-refresh=yes to force a refresh in these + cases. * systemd-oomd acquired "prekill hook" functionality, permitting other 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 +1338,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 +1512,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. @@ -736,6 +1619,12 @@ CHANGES WITH 259: firstboot operation is requested. The invocation in systemd-homed-firstboot.service now turns both off by default. + * systemd-homed's fscrypt backend gained a new key sealing format that + aims to be more future-proof and robust against advances in brute + forcing techniques. Compatibiilty with the existing format is maintained, + but new enrollments will happen with the new format. Users enrolling + new systems must thus take care in not downgrading to an older version. + systemd-boot/systemd-stub: * systemd-boot now supports log levels. The level may be set via @@ -797,6 +1686,9 @@ CHANGES WITH 259: wrappers and other APIs it provides have been reimplemented directly in systemd, which reduced the codebase and the dependency tree. + → In summary: all direct shared library linking is gone now from + systemd, with the one exception of libc. + systemd-machined/systemd-importd: * systemd-machined gained support for RegisterMachineEx() + @@ -3323,7 +4215,7 @@ CHANGES WITH 257: systemd-importd: - * A new generator sytemd-import-generator has been added to synthesize + * A new generator systemd-import-generator has been added to synthesize image download jobs. This provides functionality similar to importctl, but is configured via the kernel command line and system credentials. It may be used to automatically download sysext, @@ -4500,7 +5392,7 @@ CHANGES WITH 256: OpenSSH 9.4 or newer. * systemd-sysext gained support for enabling system extensions in - mutable fashion, where a writeable upperdir is stored under + mutable fashion, where a writable upperdir is stored under /var/lib/extensions.mutable/, and a new --mutable= option to configure this behaviour. An "ephemeral" mode is not also supported where the mutable layer is configured to be a tmpfs that is @@ -14219,7 +15111,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. @@ -15202,7 +16094,7 @@ CHANGES WITH 231: * The InaccessableDirectories=, ReadOnlyDirectories= and ReadWriteDirectories= unit file settings have been renamed to - InaccessablePaths=, ReadOnlyPaths= and ReadWritePaths= and may now be + InaccessiblePaths=, ReadOnlyPaths= and ReadWritePaths= and may now be applied to all kinds of file nodes, and not just directories, with the exception of symlinks. Specifically these settings may now be used on block and character device nodes, UNIX sockets and FIFOS as @@ -20813,7 +21705,7 @@ CHANGES WITH 189: udev_device_new_from_device_id() call. * The logic for file system namespace (ReadOnlyDirectory=, - ReadWriteDirectoy=, PrivateTmp=) has been reworked not to + ReadWriteDirectories=, PrivateTmp=) has been reworked not to require pivot_root() anymore. This means fewer temporary directories are created below /tmp for this feature. diff --git a/README b/README index 0b2d53de1c895..5553915be0021 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,24 +60,25 @@ 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 of systemd. Taint flag 'old-kernel' will be set. systemd will most likely still function, but upstream support and testing are limited. - Linux kernel ≥ 6.3 for MFD_EXEC/MFD_NOEXEC_SEAL and tmpfs noswap option + Linux kernel ≥ 5.15 for MPOL_PREFERRED_MANY NUMA policy + ≥ 6.3 for MFD_EXEC/MFD_NOEXEC_SEAL and tmpfs noswap option ≥ 6.5 for name_to_handle_at() AT_HANDLE_FID, SO_PEERPIDFD/SO_PASSPIDFD, and MOVE_MOUNT_BENEATH ≥ 6.6 for quota support on tmpfs ≥ 6.7 for cgroup2fs memory_hugetlb_accounting option ≥ 6.8 for STATX_MNT_ID_UNIQUE - ≥ 6.9 for pidfs + ≥ 6.9 for pidfs and MPOL_WEIGHTED_INTERLEAVE NUMA policy ≥ 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 @@ -205,8 +206,7 @@ REQUIREMENTS: CONFIG_MEMCG glibc >= 2.34 - musl >= 1.2.5 with fde29c04adbab9d5b081bf6717b5458188647f1c - (required when building systemd with -Dlibc=musl) + musl >= 1.2.6 (required when building systemd with -Dlibc=musl) libxcrypt >= 4.4.0 (optional) libmount >= 2.30 (from util-linux) (util-linux *must* be built without --enable-libmount-support-mtab) @@ -219,7 +219,7 @@ REQUIREMENTS: libacl (optional) libbpf >= 0.1.0 (optional), >= 1.4.0 is required for using GCC as a bpf compiler - libfdisk >= 2.32 (from util-linux) (optional) + libfdisk >= 2.35 (from util-linux) (optional) libselinux >= 2.1.9 (optional) libapparmor >= 2.13 (optional) libxenctrl >= 4.9 (optional) @@ -231,7 +231,6 @@ REQUIREMENTS: libarchive >= 3.0 (optional) libxkbcommon >= 0.3.0 (optional) libpcre2 (optional) - libgcrypt (optional) libqrencode >= 3 (optional) libmicrohttpd >= 0.9.33 (optional) libcurl >= 7.32.0 (optional) @@ -264,9 +263,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 55% rename from TODO rename to TODO.md index 072ce83f0aeee..d82371bbc9213 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,679 +59,768 @@ 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: - -* sd-lldp: pick up 802.3 maximum frame size/mtu, to be able to detect jumbo - frame capable networks - -* networkd: maintain a file in /run/ that can be symlinked into /run/issue.d/ - that always shows the current primary IP address - -* oci: add support for blake hashes for layers - -* 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). - -* oci: support "data" in any OCI descriptor, not just manifest config. - -* 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 +- drop socket_xattr_supported() once our baseline is kernel 7.0 + +## Features + +- **report:** + - implement signer for TPM2 that adds a quote + event log excerpt as signing + object. should include a TPM timestamp, and some "generation ID" provided + by an orchestrator to guarantee freshness. + - implement metrics provider in logind: report number of active + sessions, and number of sessions since boot. + - implement metrics provider in journald that reports number of log messages + received since boot, by log priority + - allow to compile statically (together with the basic and cgroup + backends) + - make sure backends can also be invoked via forking off + - allow metrics providers to indicate which reported values mean + "nothing"/"invalid"/"zero"/"please-suppress". Then use that to reduce noise + in systemd-report output. + - teach cgroup metrics provider to expose PSI information + - implement metrics provider that reports local IP addresses, and bound open + IP ports + - implement current system load via metrics - metrics from pid1: suppress metrics form units that are inactive and have nothing to report + - pass filtering hints to services, so that they can also be applied server-side, not just client side - 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 -* 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. +- bootctl set-tries for setting retry counters on boot entries -* downgrade the uid/gid disposition enforcement in udev +- implement enough of PCP in a new sd-pcp-client library that networkd can use + to punch holes for wireguard into common NAT routers. use that in networkd. alternatively: just use libjuice -* have a signal that reloads every unit that supports reloading +- measure an uapi16 manifest of /etc/ during early boot (so that + pre-initialized /etc/ can be detected when systems are enrolled into some + subsystem) -* systemd: add storage API via varlink, where everyone can drop a socket in a - dir, similar, do the same thing for networking +- optionally turn off import of imds on non-firstboot creds (so that IMDS can + be considered an attack vector, except for TOFU) -* do a console daemon that takes stdio fds for services and allows to reconnect - to them later +- store workload identity OIDC server contact info in cloud imds hwdb. -* report: have something that requests cloud workload identity bearer tokens - and includes it in the report +- systemd-analyze unit-shell-me-harder that has both host and unit trees around + but mostly lives in unit namespces -* sysupdate: download multiple arbitrary patterns from same source +- os-release consumption at boot: version validation, and maybe in os-release -* sysupdate: SHA256SUMS format with bearer tokens for each resource to download +- ed25519 authentication for sd-boot upgrades for the dm-verity key logic -* sysupdate: decrypt SHA256SUMS with key from tpm +- change machine tags into key/value pairs instead of just labels -* sysupdate: clean up stuff on disk that disappears from SHA256SUMS +- in sysupdate resolve %C or so as specifier in transfer fiels to the value of + a specific machine tag channel= or so. -* sysupdate: turn http backend stuff int plugin via varlink +- make vmspawn parse UKIs for direct kernel boot -* 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. +- portabled driving by system credential -* 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. +- sysinstall: add fully automatic mode that automatically picks target disk, + non-interactively. Should wait to ensure system is up for a certain amount of + minimal time (alternatively: certain amount of time since the last disk + showed up), to ensure disks have shown up before making the decision. Usecase + for this: redfish style server provisioning. -* 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. +- nspawn: optionally provide a /dev/tpm0 + /dev/tpmrm0 that is backed by swtpm, + much like we do in vmspawn. let's us minimize differences between + environments systemd runs in. -* systemd-sysupdate: support "revoked" items, which cause the client to - downgrade/upgrade +- nspawn/vmspawn: add a concept how we can hand into the payload some proof + that it is runnin on a certain host, which it can then include in the report, + and which allows us to put together a map about which node runs as payload of + which other note. in particular useful for transient nodes, as it gives them + a better location -* portable services: attach not only unit files to host, but also simple - binaries to a tmpfs path in $PATH. +- add a small varlink service that wraps the raw sftp logic (without ssh) after a + varlink protocol upgrade, which enables varlink clients to do file transfers, + which is in particular useful when accessing a system via http varlink proxy -* 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 small varlink service that allocates a pty and then does ptyfwd stuff + after a protocol upgrade on the incoming connection. Then spawn a shell/getty + on it. This enables varlink clients to acquire a fully featured ssh-like + interactive tty/shell via varlink, which is again useful via http varlink + proxy. -* 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 something like podman's conmon as a native systemd subsystem: + i.e. allocate ptys, that can be bound to stdio/console of containers and VMs, + that maintain a bit of a scrollback buffer, and one can reconnect to + later. fun idea: might even make /dev/tty1 and friends accessible via + /dev/vcsa1 under the same protocol. this subsystem should potentially be the + same as the varlink ssh-like thing listed above. -* 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 introduce a new ansi sequence that allows propagate SIGWINCH + inline. Idea would be: to enable inline notification of window sizes client + sends a new, to be defined ANSI sequence with its current assumption of + terminal size. Server compares it with current state. If the same it sends + nothing immediately, but does send exactly one update if it changes, and + disables the logic. If not the same sends correction immediately, and + disables the logic. Client has to reissue sequence immediately after getting + notification to get live updates. Benefit of all of this: better terminal + experience if we just forward terminal bytes through a serial link/stream + connection, as terminal sizes will be properly propagated. Write a UAPI spec + for all this. ptyfwd could translate turn upstream SIGWINCH into upstream + sequences of this type, so that every step of the way we get the right + behaviour. -* machined: gc for OCI layers that are not referenced anymore by any .mstack/ links. +- implement "varlinkctl trace" or so, that watches socket traffic on a group of + processes (select by pid, select by cgroup, select by all machine), and shows + traffic of all sockets marked via the new varlink socket xattrs. Use BPF for + all of that of course. -* pull-oci: progress notification +- add tooling for generating dictionary-based hostnames -* networkd/machined: implement reverse name lookups in the resolved hook +- do not pull dbus daemon/broker anymore, instead lazy activate it. Given how + the Varlinkifcation has progressed various non-desktop usescase might not + need D-Bus running at all anymore. -* 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. +- format-table: introduce the concept of a "title" for a table, which remains + closely associated with the table. in most cases where want to output + multiple tables from the same tool we want to separate things with a title, + hence we might as well associate the title with the table itself, and + streamline a few things. -* 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. +- sysupdate: offer reading transfer files/components/features optionally from + some JSON fragment rather than transfer files, so that we can update it + independently from any DDI, and it needs no activation cycle. Why? so that + making additional transfers/components/features available can be done without + reloading confext/sysext, and out-band with other configuration changes. -* journald: log pidfid as another field, i.e. _PIDFDID= +- sysupdate: go through all components, and update them all, one by one. -* measure all log-in attempts into a new nvpcr +- sysupdate: add concept for enabling/disabling specific components explicitly, + just like features. -* measure all DDI activations into a new nvpcr +- hostnamectl: management, collation of all tags. four sources: udev, + /etc/machine-info, credentials, and /etc/machine-tags.d/*.conf -* maybe rework systemd-modules-load to be a generator that just instantiates - modprobe@.service a bunch of times +- sysupdate: add conditions to transfer files, copying what we have for unit + files and .network files -* 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. +- pid1,sysupdate,network: add support for a new "tags" condition, that checks + all of the above. -* Add ELF section to make systemd main binary recognizable cleanly, the same - way as we make sd-boot recognizable via PE section. +- sysupdate: write out database of all files created, and support gc of it -* Add knob to cryptsetup, to trigger automatic reboot on failure to unlock - disk. Enable this by default for rootfs, also in gpt-auto-generator +- pcrextend: we probably should measure /etc/machine-info during boot somehow -* Add RebootUptimeMinSec= knob to PID 1, that makes systemd-shutdown sleep - until the specified uptime has passed, to lengthen tight boot loops. +- pcrextend: we should measure something when we enter developer mode, by some + definition of developer mode. -* replace bootctl's PE version check to actually use APIs from pe-binary.[ch] - to find binary version. +- firstboot: optionally accept credentials at firstboot without authentication + +- firstboot/sysinstall: add simple interface for prompting users to enable + "features" exposed by of sysupdate. + +- a tool that can prep credentials, put them in the ESP, for provisioning + systems for SBC or UEFI/HTTP boot. Should be doing what sysinstall does with + the credentials, and maybe even *be* sysinstall. + +- make sure we always pass O_NOFOLLOW on O_CREAT + +- xopenat(): maybe imply O_NOFOLLOW on O_CREAT + +- StorageProvider interface + storagectl + - hook-up in systemd-nspawn + - hook-up in service manager (BindVolume=) + - introduce a locking concept: right now all access to volumes is fully + shared. Let's add a basic locking concept: supporting backends can take an + additional locking flag (which has to be combined with Varlink's "more"), + in which case access would only be handed out to one client at a time, with + the lock's lifetime synced up with the Varlink connection lifetime. + - introduce a volume lifecycle concept: optionally support volumes whose + whole lifecycle is associated with the varlink connections they are tied + to: when the last varlink connection that acquired them goes away, the + volume is auto-destroyed. Would be exposed via a new flag on the Acquire + call, similar to the locking logic above. + +- clean up credential naming a bit: let's say encrypted creds always should + carry .cred suffix, and unencrypted should not. + +- clean up naming of sidecar files in sd-stub: let's put global ones strictly + into /loader/extras/ + +- 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. -* 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(). +- a way for container managers to turn off getty starting via $container_headless= or so... -* 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 "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; … -* 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 "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) -* similar: add a plugin for factory reset logic that erases certain parts of - the ESP, but leaves others in place. +- 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. -* 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. +- **systemd-sysinstall:** + - make systemd-sysinstall itself a varlink service + - read installation definition from json file + - polkit support in sysinstall + - sysinstall: permit driving installer via credentials + - add --offline=no mode where we talk to socket based services rather than forking off + - if a user doesn't pick a locale during boot into installer, don't ask again after install, because we suppressed credential propagation -* Similar flush_accept() should look at sockdiag queued sockets count and exit - once we flushed out the specified number of connections. +- repart: add MatchLabel= which matches against partition label, so that we + truly can install different images in parallel -* maybe introduce a new per-unit drop-in directory .confext.d/ that may contain - symlinks to confext images to enable for the unit. +- 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. -* nspawn: map foreign UID range through 1:1 +- 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. -* 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 "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. -* 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 --vacuum-xyz options to coredumpctl, matching those journalctl already has. -* 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. +- sysupdate: in .transfer files have a 2nd url that is used if we + auto-rollbacked the OS before. -* 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. +- sysupdate: optionally enrich URL with countme=1 once a week -* loginctl: show argv[] of "leader" process in tabular list-sessions output +- sysupdate: have an explicit concept of update policies: i.e. a choice of at least + - download list + report updates in motd – but do not auto update + - download list + download new version – but do not apply it + - download list + download new version + apply it – but do not reboot + - download list + download new version + apply it + reboot + Other things the policy should contain is when to place the reboot. + This would all decouple the updating of the package list from the application + of it. Which is great for "countme" style stuff. -* loginctl: show "service identifier" in tabular list-sessions output, to make - run0 sessions easily visible. +- 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. -* run0: maybe enable utmp for run0 sessions, so that they are easily visible. +- 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. -* 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 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. -* 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 a dbus call to generate target from current state -* honour validatefs xattrs in dissect-image.c too +- add a dependency on standard-conf.xml and other included files to man pages -* pcrextend: maybe add option to disable measurements entirely via kernel cmdline +- 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 -* tpm2-setup: reboot if we detect SRK changed +- 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. -* 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 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 -* pcrextend: when we fail to measure, reboot the system (at least optionally). - important because certain measurements are supposed to "destroy" tpm object - access. +- 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.) -* 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. +- 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. -* 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 a new flag to chase() that stops chasing once the first missing + component is found and then allows the caller to create the rest. -* 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 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. -* 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 a new specifier to unit files that figures out the DDI the unit file is + from, tracing through overlayfs, DM, loopback block device. -* unify how blockdev_get_root() and sysupdate find the default root block device +- 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. -* Maybe rename pkcs7 and public verbs of systemd-keyutil to be more verb like. +- add a new syscall group "@esoteric" for more esoteric stuff such as bpf() and + usefaultd() and make systemd-analyze check for 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 a new verb "systemctl top" -* 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 a pam module that on password changes updates any LUKS slot where the password matches -* 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 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. -* 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 a plugin for factory reset logic that erases certain parts of the ESP, + but leaves others in place. -* 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 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. -* sd-boot: optionally ask for confirmation before executing certain operations - (e.g. factory resets, storagetm with world access, and so on) +- add a system-wide seccomp filter list for syscalls, kill "acct()" "@obsolete" + and a few other legacy syscalls that way. -* add field to bls type 1 and type 2 profiles that ensures an item is never - considered for automatic selection +- 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.) -* 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; … +- 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. -* 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. +- 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 -* 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. +- 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. -* 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. +- Add ACL-based access management to .socket units. i.e. add AllowPeerUser= + + AllowPeerGroup= that installs additional user/group ACL entries on AF_UNIX + sockets. -* make nspawn containers, portable services and vmspawn VMs optionally survive - soft reboot wholesale. +- 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. -* 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. +- add an explicit parser for LimitRTPRIO= that verifies + the specified range and generates sane error messages for incorrect + specifications. -* 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. +- Add and pickup tpm2 metadata for creds structure. -* 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. +- 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. -* 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 +- add bus API for creating unit files in /etc, reusing the code for transient units -* 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. +- add bus api to query unit file's X fields. -* 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. +- add bus API to remove unit files from /etc -* add a system-wide seccomp filter list for syscalls, kill "acct()" "@obsolete" - and a few other legacy syscalls that way. +- add bus API to retrieve current unit file contents (i.e. implement "systemctl cat" on the bus only) -* maybe introduce "@icky" as a seccomp filter group, which contains acct() and - certain other syscalls that aren't quite obsolete, but certainly icky. +- Add ConditionDirectoryNotEmpty= handle non-absolute 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. -* 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) +- 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. -* also parse out primary GPT disk label uuid from gpt partition device path at - boot and pass it as efi var to OS. +- Add ELF section to make systemd main binary recognizable cleanly, the same + way as we make sd-boot recognizable via PE section. -* 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. +- 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. -* support specifying download hash sum in systemd-import-generator expression - to pin image/tarball. +- add field to bls type 1 and type 2 profiles that ensures an item is never + considered for automatic selection -* 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. +- 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 -* ptyfwd: use osc context information in vmspawn/nspawn/… to optionally only - listen to ^]]] key when no further vmspawn/nspawn context is allocated +- add growvol and makevol options for /etc/crypttab, similar to + x-systemd.growfs and x-systemd-makefs. -* 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 +- Add knob to cryptsetup, to trigger automatic reboot on failure to unlock + disk. Enable this by default for rootfs, also in gpt-auto-generator -* nspawn/vmspawn: define hotkey that one can hit on the primary interface to - ask for a friendly, acpi style shutdown. +- add linker script that implicitly adds symbol for build ID and new coredump + json package metadata, and use that when logging -* for better compat with major clouds: implement simple PTP device support in - timesyncd +- add new gpt type for btrfs volumes -* 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, … +- 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. -* 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. +- add option to sockets to avoid activation. Instead just drop packets/connections, see http://cyberelk.net/tim/2012/02/15/portreserve-systemd-solution/ -* 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. +- add PR_SET_DUMPABLE service setting + +- 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. -* fix bug around run0 background color on ls in fresh terminal +- 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. -* Reset TPM2 DA bit on each successful boot +- 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. -* 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 +- Add service unit setting ConnectStream= which takes IP addresses and connects to them. -* systemd-cryptenroll: add --firstboot or so, that will interactively ask user - whether recovery key shall be enrolled and do so +- 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: -* bootctl: add tool for registering BootXXX entry that boots from some http - server of your choice (i.e. like kernel-bootcfg --add-uri=) + ReadWritePaths=:/var/lib/foobar -* 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? +- 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. + +- 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. + +- add support for "portablectl attach http://foobar.com/waaa.raw (i.e. importd integration) + +- 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 + +- 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. + +- Add support for extra verity configuration options to systemd-repart (FEC, + hash type, etc) + +- Add SUPPORT_END_URL= field to os-release with more *actionable* information + what to do if support ended + +- Add systemd-analyze security checks for RestrictFileSystems= and + RestrictNetworkInterfaces= + +- 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+= + +- 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? -* systemd-firstboot: optionally install an ssh key for root for offline use. +- add systemd.abort_on_kill or some other such flag to send SIGABRT instead of SIGKILL + (throughout the codebase, not only PID1) + +- 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. -* Allocate UIDs/GIDs automatically in userdbctl load-credentials if none are +- Allocate UIDs/GIDs automatically in userdbctl load-credentials if none are included in the user/group record credentials -* introduce new ANSI sequence for communicating log level and structured error - metadata to terminals. +- allow dynamic modifications of ConcurrencyHardMax= and ConcurrencySoftMax= + via DBus (and with that also by daemon-reload). Similar for portabled. -* 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. -* allow dynamic modifications of ConcurrencyHardMax= and ConcurrencySoftMax= - via DBus (and with that also by daemon-reload) +- also parse out primary GPT disk label uuid from gpt partition device path at + boot and pass it as efi var to OS. -* sysupdated: introduce per-user version that can update per-user installed dDIs +- as soon as we have sender timestamps, revisit coalescing multiple parallel daemon reloads: + https://lists.freedesktop.org/archives/systemd-devel/2014-December/025862.html -* portabled: similar +- 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. -* resolved: make resolved process DNR DHCP info +- 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 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. +- 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. -* start using STATX_SUBVOL in btrfs_is_subvol(). Also, make use of it - generically, so that image discovery recognizes bcachefs subvols too. +- automatically ignore threaded cgroups in cg_xyz(). -* foreign uid: - - add support to export-fs, import-fs - - systemd-dissect should learn mappings, too, when doing mtree and such +- automatically mount one virtiofs during early boot phase to /run/host/, + similar to how we do that for nspawn, based on some clear tag. -* 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. +- 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. -* journald: beef up ClientContext logic to store pidfd_id of peer, to validate - we really use the right cache entry +- automatically reset specific EFI vars on factory reset (make this generic + enough so that infra can be used to erase shim's mok vars?) -* journald: log client's pidfd id as a new automatic field _PIDFDID= or so. +- be able to specify a forced restart of service A where service B depends on, in case B + needs to be auto-respawned? -* 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. +- be more careful what we export on the bus as (usec_t) 0 and (usec_t) -1 -* system lsmbpf policy that prohibits creating files owned by "nobody" - system-wide +- 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. -* 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. +- beef up pam_systemd to take unit file settings such as cgroups properties as + parameters -* 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) +- blog about fd store and restartable services -* $SYSTEMD_EXECPID that the service manager sets should - be augmented with $SYSTEMD_EXECPIDFD (and similar for - other env vars we might send). +- **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 -* port copy.c over to use LabelOps for all labelling. +- BootLoaderSpec: define a way how an installer can figure out whether a BLS + compliant boot loader is installed. -* get rid of compat with libbpf.so.0 (retainly only for libbpf.so.1) +- BootLoaderSpec: document @saved pseudo-entry, update mention in BLI -* 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 - issue parallel calls to these interfaces from the systemd-report tool, - combine into one json document, and include measurement logs and tpm - quote. tpm quote should protect the json doc via the nonce field - studd. Allow shipping this off elsewhere for analyze. +- 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. -* 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. +- 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. -* 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(). +- 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. -* 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. +- 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. -* 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. +- 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. -* 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. +- build short web pages out of each catalog entry, build them along with man + pages, and include hyperlinks to them in the journal output -* generic interface for varlink for setting log level and stuff that all our daemons can implement +- busctl: maybe expose a verb "ping" for pinging a dbus service to see if it + exists and responds. -* 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. +- bypass SIGTERM state in unit files if KillSignal is SIGKILL -* 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 +- cache sd_event_now() result from before the first iteration... -* 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. +- calenderspec: add support for week numbers and day numbers within a + year. This would allow us to define "bi-weekly" triggers safely. -* go through our codebase, and convert "vertical tables" (i.e. things such as - "systemctl status") to use table_new_vertical() for output +- cgroups: use inotify to get notified when somebody else modifies cgroups + owned by us, then log a friendly warning. -* pcrlock: add support for multi-profile UKIs +- **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 -* initrd: when transitioning from initrd to host, validate that - /lib/modules/`uname -r` exists, refuse otherwise +- chase(): take inspiration from path_extract_filename() and return + O_DIRECTORY if input path contains trailing slash. -* 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. +- 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. -* 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.) +- 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. -* 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. +- clean up date formatting and parsing so that all absolute/relative timestamps we format can also be parsed -* 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. +- **complete varlink introspection comments:** + - io.systemd.ManagedOOM + - io.systemd.Network + - io.systemd.PCRLock + - io.systemd.Resolve.Monitor + - io.systemd.Resolve + - io.systemd.oom + - io.systemd.sysext -* 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. +- 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. -* 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?) +- consider adding a new partition type, just for /opt/ for usage in system + extensions -* 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. +- 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 -* 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. +- **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 + +- **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. -* cryptenroll/cryptsetup/homed: add unlock mechanism that combines tpm2 and +- 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 @@ -732,1559 +828,2020 @@ Features: returns a signature which is handed to the tpm, which then reveals the volume key to the PC. -* cryptenroll/cryptsetup/homed: similar to this, implement TOTP backed by TPM. +- cryptenroll/cryptsetup/homed: similar to this, implement TOTP backed by TPM. -* expose the handoff timestamp fully via the D-Bus properties that contain - ExecStatus information +- cryptsetup/homed: implement TOTP authentication backed by TPM2 and its + internal clock. -* 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. +- **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. + +- 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. -* 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. +- ddi must be listed as block device fstype -* 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. +- define a JSON format for units, separating out unit definitions from unit + runtime state. Then, expose it: -* 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. + 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 -* systemd-nspawn should get the same SSH key support that vmspawn now has. +- define gpt header bits to select volatility mode -* 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. +- 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 -* 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). +- 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. -* 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. - -* 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. - -* 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. - -* add a new specifier to unit files that figures out the DDI the unit file is - from, tracing through overlayfs, DM, loopback block device. - -* importd/importctl - - complete varlink interface - - download images into .v/ dirs - -* 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=. - -* 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 - -* 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. - -* 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. - -* rewrite bpf-devices in libbpf/C code, rather than home-grown BPF assembly, to - match bpf-restrict-fs, bpf-restrict-ifaces, bpf-socket-bind - -* ditto: rewrite bpf-firewall in libbpf/C code - -* 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. +- do a console daemon that takes stdio fds for services and allows to reconnect + to them later -* 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. +- doc: prep a document explaining PID 1's internal logic, i.e. transactions, + jobs, units -* credentials: add a flag to the scoped credentials that if set require PK - reauthentication when unlocking a secret. +- doc: prep a document explaining resolved's internal objects, i.e. Query + vs. Question vs. Transaction vs. Stream and so on. -* 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. +- docs: bring https://systemd.io/MY_SERVICE_CANT_GET_REALTIME up to date -* 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. +- document Environment=SYSTEMD_LOG_LEVEL=debug drop-in in debugging document -* machined: optionally track nspawn unix-export/ runtime for each machined, and - then update systemd-ssh-proxy so that it can connect to that. +- document org.freedesktop.MemoryAllocation1 -* introduce mntid_t, and make it 64bit, as apparently the kernel switched to - 64bit mount ids +- **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 -* 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 +- dot output for --test showing the 'initial transaction' -* 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. +- drop nss-myhostname in favour of nss-resolve? -* 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. +- 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) -* 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. +- **EFI:** + - honor timezone efi variables for default timezone selection (if there are any?) -* 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. +- enable LockMLOCK to take a percentage value relative to physical memory -* 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. +- Enable RestrictFileSystems= for all our long-running services (similar: + RestrictNetworkInterfaces=) -* 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. +- encode type1 entries in some UKI section to add additional entries to the + menu. -* bootctl: add reboot-to-disk which takes a block device name, and - automatically sets things up so that system reboots into that device next. +- 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) -* maybe: in PID1, when we detect we run in an initrd, make superblock read-only - early on, but provide opt-out via kernel cmdline. +- /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. -* 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. +- exponential backoff in timesyncd when we cannot reach a server -* 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 +- expose MS_NOSYMFOLLOW in various places -* 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 +- expose the handoff timestamp fully via the D-Bus properties that contain + ExecStatus information -* 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 +- 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. -* 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 +- 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. -* 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. +- figure out a nice way how we can let the admin know what child/sibling unit causes cgroup membership for a specific unit -* 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. +- Figure out how to do unittests of networkd's state serialization -* 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" +- Figure out naming of verbs in systemd-analyze: we have (singular) capability, + exit-status, but (plural) filesystems, architectures. -* 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 +- 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? -* 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) +- figure out when we can use the coarse timers -* 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) +- Find a solution for SMACK capabilities stuff: + https://lists.freedesktop.org/archives/systemd-devel/2014-December/026188.html -* automatically mount one virtiofs during early boot phase to /run/host/, - similar to how we do that for nspawn, based on some clear tag. +- fix bug around run0 background color on ls in fresh terminal -* 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. +- Fix DECIMAL_STR_MAX or DECIMAL_STR_WIDTH. One includes a trailing NUL, the + other doesn't. What a disaster. Probably to exclude it. -* encode type1 entries in some UKI section to add additional entries to the - menu. +- 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 ACL-based access management to .socket units. i.e. add AllowPeerUser= + - AllowPeerGroup= that installs additional user/group ACL entries on AF_UNIX - sockets. +- 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. -* 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) +- flush_accept() should look at sockdiag queued sockets count and exit once we + flushed out the specified number of connections. -* 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 +- 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 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. +- for better compat with major clouds: implement simple PTP device support in + timesyncd -* 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 +- 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. -* ddi must be listed as block device fstype +- 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, … -* measure some string via pcrphase whenever we end up booting into emergency - mode. +- 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. -* similar, measure some string via pcrphase whenever we resume from hibernate +- **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 -* 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. +- **foreign uid:** + - add support to export-fs, import-fs + - systemd-dissect should learn mappings, too, when doing mtree and such -* use sd-event ratelimit feature optionally for journal stream clients that log - too much +- fstab-generator: default to tmpfs-as-root if only usr= is specified on the kernel cmdline -* systemd-mount should only consider modern file systems when mounting, similar - to systemd-dissect +- generator that automatically discovers btrfs subvolumes, identifies their purpose based on some xattr on them. -* 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. +- generic interface for varlink for setting log level and stuff that all our daemons can implement -* also include packaging metadata (á la - https://systemd.io/PACKAGE_METADATA_FOR_EXECUTABLE_FILES/) in our UEFI PE - binaries, using the same JSON format. +- get rid of compat with libbpf.so.0 (retainly only for libbpf.so.1) -* 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. +- 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. -* 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 - shim. Then, I want to include the fedora keyring in it, so that kmods work. - But I might not want to enroll the fedora key in shim, because this would - 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. +- 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. -* 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. +- 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 -* 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. +- go through our codebase, and convert "vertical tables" (i.e. things such as + "systemctl status") to use table_new_vertical() for output -* introduce a .acpitable section for early ACPI table override +- **gpt-auto-generator:** + - Make /home automount rather than mount? -* 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. +- have a signal that reloads every unit that supports reloading -* 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? +- hibernate/s2h: if swap is on weird storage and refuse if so -* 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) +- homed/pam_systemd: allow authentication by ssh-agent, so that run0/polkit can + be allowed if caller comes with the right ssh-agent keys. -* 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. +- 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. -* 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. - -* 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. - -* 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. - -* 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. - -* 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. - -* remove tomoyo support, it's obsolete and unmaintained apparently - -* 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. - -* .service with invalid Sockets= starts successfully. - -* 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. +- **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) -* 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. +- 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. -* 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+= +- Hook up systemd-journal-upload with RESTART_RESET=1 logic (should probably + be conditioned on the num of successfully uploaded entries?) -* udevd: extend memory pressure logic: also kill any idle worker processes +- hostnamectl: show root image uuid -* 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)? +- if /usr/bin/swapoff fails due to OOM, log a friendly explanatory message about it -* SIGRTMIN+18 and memory pressure handling should still be added to: hostnamed, - localed, oomd, timedated. +- 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. -* 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. +- 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. -* 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. +- 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" -* 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. +- imds: maybe do smarter api version handling -* 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. +- 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. -* 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. +- implement Distribute= in socket units to allow running multiple + service instances processing the listening socket, and open this up + for ReusePort= -* homed: if for some reason the partition ended up being much smaller than - whole disk, recover from that, and grow it again. +- **importd/importctl:** + - complete varlink interface + - download images into .v/ dirs -* 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. +- importd: support image signature verification with PKCS#7 + OpenBSD signify + logic, as alternative to crummy gpg -* 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. +- 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. -* 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 +- In DynamicUser= mode: before selecting a UID, use disk quota APIs on relevant + disks to see if the UID is already in use. -* 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. +- 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. -* 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. +- 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. -* sd-stub: add ".bootcfg" section for kernel bootconfig data (as per - https://docs.kernel.org/admin-guide/bootconfig.html) +- in networkd, when matching device types, fix up DEVTYPE rubbish the kernel passes to us -* 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. +- 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. -* Lennart: big blog story about DDIs +- 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=. -* Lennart: big blog story about building initrds +- in pid1: include ExecStart= cmdlines (and other Exec*= cmdlines) in polkit + request, so that policies can match against command lines. -* Lennart: big blog story about "why systemd-boot" +- in sd-id128: also parse UUIDs in RFC4122 URN syntax (i.e. chop off urn:uuid: prefix) -* 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. +- 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 + shim. Then, I want to include the fedora keyring in it, so that kmods work. + But I might not want to enroll the fedora key in shim, because this would + 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. -* 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. +- 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. -* 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. +- in the initrd: derive the default machine ID to pass to the host PID 1 via + $machine_id from the same seed credential. -* consider adding a new partition type, just for /opt/ for usage in system - extensions +- 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. -* 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. +- 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. -* 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. +- initialize the hostname from the fs label of /, if /etc/hostname does not exist? -* systemd-repart: also derive the volume key from the seed value, for the - aforementioned purpose. +- initrd: when transitioning from initrd to host, validate that + /lib/modules/`uname -r` exists, refuse otherwise -* in the initrd: derive the default machine ID to pass to the host PID 1 via - $machine_id from the same seed credential. +- 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. -* 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? +- 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. -* 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) +- introduce a .acpitable section for early ACPI table override -* 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). +- 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. -* 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. +- introduce a new group to own TPM devices -* 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. +- 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. -* systemd-repart: add support for generating ISO9660 images +- 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 -* 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. +- introduce mntid_t, and make it 64bit, as apparently the kernel switched to + 64bit mount ids -* 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. +- introduce new ANSI sequence for communicating log level and structured error + metadata to terminals. -* 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. +- 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(). -* systemd-sysupdate: make transport pluggable, so people can plug casync or - similar behind it, instead of http. +- introduce per-unit (i.e. per-slice, per-service) journal log size limits. -* systemd-tmpfiles: add concept for conditionalizing lines on factory reset - boot, or on first boot. +- investigate whether the gnome pty helper should be moved into systemd, to provide cgroup support. -* 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). - -* Once the root fs LUKS volume key is measured into PCR 15, default to binding - credentials to PCR 15 in "systemd-creds" - -* 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. +- **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. -* 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 - enhanced remote attestation quote. Use case: a simple orchestrator could use - this: have the report tool upload these reports every 3min somewhere. Then - have the orchestrator collect these reports centrally over a 3min time - window, and use them to determine what which node should now start/stop what, - and generate a small confext for each node, that uses Uphold= to pin services - on each node. The confext would be encrypted using the asymmetric encryption - proposed above, so that it can only be activated on the specific host, if the - software is in a good state, and within a specific time frame. Then run a - loop on each node that sends report to orchestrator and then sysupdate to - update confext. Orchestrator would be stateless, i.e. operate on desired - config and collected reports in the last 3min time window only, and thus can - be trivially scaled up since all instances of the orchestrator should come to - the same conclusions given the same inputs of reports/desired workload info. - Could also be used to deliver Wireguard secrets and thus to clients, thus - permitting zero-trust networking: secrets are rolled over via confext updates, - 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. +- 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. -* sd-boot: for each installed OS, grey out older entries (i.e. all but the - newest), to indicate they are obsolete +- 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. -* 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. +- Lennart: big blog story about "why systemd-boot" -* 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. +- Lennart: big blog story about building initrds -* 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=). +- Lennart: big blog story about DDIs -* 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. +- let's not GC a unit while its ratelimits are still pending -* 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 +- libsystemd-journal, libsystemd-login, libudev: add calls to easily attach these objects to sd-event event loops -* lock down acceptable encrypted credentials at boot, via simple allowlist, +- 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 -* Merge systemd-creds options --uid= (which accepts user names) and --user. +- 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. -* Add support for extra verity configuration options to systemd-repart (FEC, - hash type, etc) +- loginctl: show "service identifier" in tabular list-sessions output, to make + run0 sessions easily visible. -* chase(): take inspiration from path_extract_filename() and return - O_DIRECTORY if input path contains trailing slash. +- loginctl: show argv[] of "leader" process in tabular list-sessions output -* measure credentials picked up from SMBIOS to some suitable PCR +- **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. -* measure GPT and LUKS headers somewhere when we use them (i.e. in - systemd-gpt-auto-generator/systemd-repart and in systemd-cryptsetup?) +- 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. -* pick up creds from EFI vars +- make cryptsetup lower --iter-time -* Add and pickup tpm2 metadata for creds structure. +- Make it possible to set the keymap independently from the font on + the kernel cmdline. Right now setting one resets also the other. -* 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. +- 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. -* systemd-measure tool: - - pre-calculate PCR 12 (command line) + PCR 13 (sysext) the same way we can precalculate PCR 11 +- 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. -* 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) +- make nspawn containers, portable services and vmspawn VMs optionally survive + soft reboot wholesale. -* sd-device: maybe pin the sysfs dir with an fd, during the entire runtime of - an sd_device, then always work based on that. +- 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. -* 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. +- 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. -* 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 +- make repeated alt-ctrl-del presses printing a dump -* 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. +- make rfkill uaccess controllable by default, i.e. steal rule from + gnome-bluetooth and friends -* 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. +- 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. -* 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. +- make sure systemd-ask-password-wall does not shutdown systemd-ask-password-console too early -* 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. +- make sure the ratelimit object can deal with USEC_INFINITY as way to turn off things -* define a JSON format for units, separating out unit definitions from unit - runtime state. Then, expose it: +- 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 - 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 +- 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. -* Add SUPPORT_END_URL= field to os-release with more *actionable* information - what to do if support ended +- make use of ethtool veth peer info in machined, for automatically finding out + host-side interface pointing to the container. -* pam_systemd: on interactive logins, maybe show SUPPORT_END information at - login time, à la motd +- make use of new glibc 2.32 APIs sigabbrev_np(). -* 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. +- 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. -* 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 +- 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. -* 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. +- man: the documentation of Restart= currently is very misleading and suggests the tools from ExecStartPre= might get restarted. -* 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) +- 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.slice, RefuseManualStart=yes, RefuseManualStop=yes and a + couple of other things. -* 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. +- 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 PR_SET_DUMPABLE service setting +- maybe add kernel cmdline params: to force random seed crediting -* 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. +- 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 add support for binding and connecting AF_UNIX sockets in the file +- 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. -* 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. +- 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. -* 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. +- maybe allow timer units with an empty Units= setting, so that they + can be used for resuming the system but nothing else. -* 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 +- 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. + +- 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. -* 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) +- maybe do not install getty@tty1.service symlink in /etc but in /usr? -* 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 +- maybe extend .path units to expose fanotify() per-mount change events + +- 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. -* Maybe extend the service protocol to support handling of some specific SIGRT +- 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. -* 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. +- maybe implicitly attach monotonic+realtime timestamps to outgoing messages in + log.c and sd-journal-send -* 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. +- maybe introduce "@icky" as a seccomp filter group, which contains acct() and + certain other syscalls that aren't quite obsolete, but certainly icky. -* 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. +- 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-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). +- 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-event: add 1st class event source for clock changes +- maybe introduce a new per-unit drop-in directory .confext.d/ that may contain + symlinks to confext images to enable for the unit. -* sd-event: add 1st class event source for timezone changes +- 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. -* sysext: measure all activated sysext into a TPM PCR +- 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. -* 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). +- 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-dissect: add --cat switch for dumping files such as /etc/os-release +- 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. -* 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). +- 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. -* doc: prep a document explaining resolved's internal objects, i.e. Query - vs. Question vs. Transaction vs. Stream and so on. +- 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 -* doc: prep a document explaining PID 1's internal logic, i.e. transactions, - jobs, units +- Maybe rename pkcs7 and public verbs of systemd-keyutil to be more verb like. -* automatically ignore threaded cgroups in cg_xyz(). +- maybe rework systemd-modules-load to be a generator that just instantiates + modprobe@.service a bunch of times -* add linker script that implicitly adds symbol for build ID and new coredump - json package metadata, and use that when logging +- 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. -* Enable RestrictFileSystems= for all our long-running services (similar: - RestrictNetworkInterfaces=) +- maybe trigger a uevent "change" on a device if "systemctl reload xyz.device" + is issued. -* Add systemd-analyze security checks for RestrictFileSystems= and - RestrictNetworkInterfaces= +- maybe: in PID1, when we detect we run in an initrd, make superblock read-only + early on, but provide opt-out via kernel cmdline. -* cryptsetup/homed: implement TOTP authentication backed by TPM2 and its - internal clock. +- measure GPT and LUKS headers somewhere when we use them (i.e. in + systemd-gpt-auto-generator/systemd-repart and in systemd-cryptsetup?) -* 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. +- measure some string via pcrphase whenever we end up booting into emergency + mode. -* 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. +- measure some string via pcrphase whenever we resume from hibernate -* 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. +- Merge systemd-creds options --uid= (which accepts user names) and --user. -* 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. +- merge unit_kill_common() and unit_kill_context() -* 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. +- MessageQueueMessageSize= (and suchlike) should use parse_iec_size(). -* 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 +- 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. -* /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. +- 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 -* rework recursive read-only remount to use new mount API +- 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. -* 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. +- 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. -* 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 +- mount: turn dependency information from /proc/self/mountinfo into dependency information between systemd units. -* in sd-id128: also parse UUIDs in RFC4122 URN syntax (i.e. chop off urn:uuid: prefix) +- MountFlags=shared acts as MountFlags=slave right now. -* systemd-sysext: optionally, run it in initrd already, before transitioning - into host, to open up possibility for services shipped like that. +- **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 -* whenever we receive fds via SCM_RIGHTS make sure none got dropped due to the - reception limit the kernel silently enforces. +- 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. -* Add service unit setting ConnectStream= which takes IP addresses and connects to them. +- move multiseat vid/pid matches from logind udev rule to hwdb -* 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 +- Move RestrictAddressFamily= to the new cgroup create socket -* 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. +- 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 concept of ListenStream=anonymous to socket units: listen on a socket - that is deleted in the fs. Use case would be with ConnectSocket= above. +- networkd/machined: implement reverse name lookups in the resolved hook -* importd: support image signature verification with PKCS#7 + OpenBSD signify - logic, as alternative to crummy gpg +- networkd: maintain a file in /run/ that can be symlinked into /run/issue.d/ + that always shows the current primary IP address -* 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. +- **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 -* expose MS_NOSYMFOLLOW in various places +- nspawn/vmspawn: define hotkey that one can hit on the primary interface to + ask for a friendly, acpi style shutdown. -* 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 +- **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. -* TPM2: auto-reenroll in cryptsetup, as fallback for hosed firmware upgrades - and such +- 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 focuses on the "OCI image spec", i.e. downloads + from the web (i.e. acquiring via URLs). -* introduce a new group to own TPM devices +- oci: add support for blake hashes for layers -* cryptsetup: add option for automatically removing empty password slot on boot +- oci: support "data" in any OCI descriptor, not just manifest config. -* cryptsetup: optionally, when run during boot-up and password is never - entered, and we are on battery power (or so), power off machine again +- 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 -* cryptsetup: when waiting for FIDO2/PKCS#11 token, tell plymouth that, and - allow plymouth to abort the waiting and enter pw instead +- on shutdown: move utmp, wall, audit logic all into PID 1 (or logind?) -* make cryptsetup lower --iter-time +- once swtpm's sd_notify() support has landed in the distributions, remove the + invocation in tpm2-swtpm.c and let swtpm handle it. -* cryptsetup: allow encoding key directly in /etc/crypttab, maybe with a - "base64:" prefix. Useful in particular for pkcs11 mode. +- Once the root fs LUKS volume key is measured into PCR 15, default to binding + credentials to PCR 15 in "systemd-creds" -* cryptsetup: reimplement the mkswap/mke2fs in cryptsetup-generator to use - systemd-makefs.service instead. +- optionally, also require WATCHDOG=1 notifications during service start-up and shutdown -* cryptsetup: - - cryptsetup-generator: allow specification of passwords in crypttab itself - - support rd.luks.allow-discards= kernel cmdline params in cryptsetup generator - -* systemd-analyze netif that explains predictable interface (or networkctl) +- 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. -* systemd-analyze inspect-elf should show other notes too, at least build-id. +- optionally: turn on cgroup delegation for per-session scope units -* Figure out naming of verbs in systemd-analyze: we have (singular) capability, - exit-status, but (plural) filesystems, architectures. +- pam_systemd: on interactive logins, maybe show SUPPORT_END information at + login time, à la motd -* special case some calls of chase() to use openat2() internally, so - that the kernel does what we otherwise do. +- 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 -* 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. +- 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(). -* make use of new glibc 2.32 APIs sigabbrev_np(). +- 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. -* if /usr/bin/swapoff fails due to OOM, log a friendly explanatory message about it +- 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. -* pid1: also remove PID files of a service when the service starts, not just - when it exits +- pcrextend: maybe add option to disable measurements entirely via kernel cmdline -* 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. +- pcrextend: when we fail to measure, reboot the system (at least optionally). + important because certain measurements are supposed to "destroy" tpm object + access. -* systemd-path: Add "private" runtime/state/cache dir enum, mapping to - $RUNTIME_DIRECTORY, $STATE_DIRECTORY and such +- pcrlock: add support for multi-profile UKIs -* seccomp: by default mask x32 ABI system wide on x86-64. it's on its way out +- **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 -* seccomp: don't install filters for ABIs that are masked anyway for the - specific service +- 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). -* busctl: maybe expose a verb "ping" for pinging a dbus service to see if it - exists and responds. +- Permit masking specific netlink APIs with RestrictAddressFamily= -* socket units: allow creating a udev monitor socket with ListenDevices= or so, - with matches, then activate app through that passing socket over +- pick up creds from EFI vars -* 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 +- PID 1 should send out sd_notify("WATCHDOG=1") messages (for usage in the --user mode, and when run via nspawn) -* add growvol and makevol options for /etc/crypttab, similar to - x-systemd.growfs and x-systemd-makefs. +- **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 -* userdb: allow existence checks +- port copy.c over to use LabelOps for all labelling. -* pid1: activation by journal search expression +- portable services: attach not only unit files to host, but also simple + binaries to a tmpfs path in $PATH. -* 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. +- 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 -* 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. +- print a nicer explanation if people use variable/specifier expansion in ExecStart= for the first word -* 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 +- **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 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. +- properly handle loop back mounts via fstab, especially regards to fsck/passno -* 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 +- 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. -* teach parse_timestamp() timezones like the calendar spec already knows it +- ProtectClock= (drops CAP_SYS_TIMES, adds seccomp filters for settimeofday, adjtimex), sets DeviceAllow o /dev/rtc -* We should probably replace /etc/rc.d/README with a symlink to doc - content. After all it is constant vendor data. +- ProtectKeyRing= to take keyring calls away -* maybe add kernel cmdline params: to force random seed crediting +- ProtectMount= (drop mount/umount/pivot_root from seccomp, disallow fuse via DeviceAllow, imply Mountflags=slave) -* let's not GC a unit while its ratelimits are still pending +- 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 -* when killing due to service watchdog timeout maybe detect whether target - process is under ptracing and then log loudly and continue instead. +- ProtectTracing= (drops CAP_SYS_PTRACE, blocks ptrace syscall, makes /sys/kernel/tracing go away) -* make rfkill uaccess controllable by default, i.e. steal rule from - gnome-bluetooth and friends +- ptyfwd: use osc context information in vmspawn/nspawn/… to optionally only + listen to ^]]] key when no further vmspawn/nspawn context is allocated -* 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. +- 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 -* maybe trigger a uevent "change" on a device if "systemctl reload xyz.device" - is issued. +- pull-oci: progress notification -* when importing an fs tree with machined, optionally apply userns-rec-chown +- 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 -* when importing an fs tree with machined, complain if image is not an OS +- refcounting in sd-resolve is borked -* 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. +- refuse boot if /usr/lib/os-release is missing or /etc/machine-id cannot be set up -* 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. +- remove any syslog support from log.c — we probably cannot do this before split-off udev is gone for good -* when no locale is configured, default to UEFI's PlatformLang variable +- remove tomoyo support, it's obsolete and unmaintained apparently -* add a new syscall group "@esoteric" for more esoteric stuff such as bpf() and - usefaultd() and make systemd-analyze check for it. +- RemoveKeyRing= to remove all keyring entries of the specified user -* 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(). +- 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. -* Move RestrictAddressFamily= to the new cgroup create socket +- 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. -* optionally: turn on cgroup delegation for per-session scope units +- 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. -* sd-boot: optionally, show boot menu when previous default boot item has - non-zero "tries done" count +- 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. -* 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. +- 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. -* 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. +- 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. -* maybe extend .path units to expose fanotify() per-mount change events +- replace bootctl's PE version check to actually use APIs from pe-binary.[ch] + to find binary version. -* hibernate/s2h: if swap is on weird storage and refuse if so +- 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(). -* cgroups: use inotify to get notified when somebody else modifies cgroups - owned by us, then log a friendly warning. +- report: have something that requests cloud workload identity bearer tokens + and includes it in the report -* 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. +- Reset TPM2 DA bit on each successful boot -* add support for "portablectl attach http://foobar.com/waaa.raw (i.e. importd integration) +- **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) -* 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) +- 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. -* Fix DECIMAL_STR_MAX or DECIMAL_STR_WIDTH. One includes a trailing NUL, the - other doesn't. What a disaster. Probably to exclude it. +- rework fopen_temporary() to make use of open_tmpfile_linkable() (problem: the + kernel doesn't support linkat() that replaces existing files, currently) -* 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. +- 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. -* systemctl, machinectl, loginctl: port "status" commands over to - format-table.c's vertical output logic. +- 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. -* pid1: lock image configured with RootDirectory=/RootImage= using the usual nspawn semantics while the unit is up +- rework recursive read-only remount to use new mount API -* add --vacuum-xyz options to coredumpctl, matching those journalctl already has. +- 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. -* 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. +- rewrite bpf-devices in libbpf/C code, rather than home-grown BPF assembly, to + match bpf-restrict-fs, bpf-restrict-ifaces, bpf-socket-bind -* bypass SIGTERM state in unit files if KillSignal is SIGKILL +- rewrite bpf-firewall in libbpf/C code -* 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. +- 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 -* 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 +- 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. -* calenderspec: add support for week numbers and day numbers within a - year. This would allow us to define "bi-weekly" triggers safely. +- run0: maybe enable utmp for run0 sessions, so that they are easily visible. + +- **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. -* sd-bus: add vtable flag, that may be used to request client creds implicitly - and asynchronously before dispatching the operation +- sd-lldp: pick up 802.3 maximum frame size/mtu, to be able to detect jumbo + frame capable networks -* sd-bus: parse addresses given in sd_bus_set_addresses immediately and not - only when used. Add unit tests. +- **sd-rtnl:** + - add support for more attribute types + - inbuilt piping support (essentially degenerate async)? see loopback-setup.c and other places -* make use of ethtool veth peer info in machined, for automatically finding out - host-side interface pointing to the container. +- **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) -* 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. +- 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. -* deprecate RootDirectoryStartOnly= in favour of a new ExecStart= prefix char +- **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 -* support projid-based quota in machinectl for containers +- 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. -* 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 +- .service with invalid Sockets= starts successfully. -* blog about fd store and restartable services +- 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. -* document Environment=SYSTEMD_LOG_LEVEL=debug drop-in in debugging document +- 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) -* 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. +- show whether a service has out-of-date configuration in "systemctl status" by + using mtime data of ConfigurationDirectory=. -* 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. +- shutdown logging: store to EFI var, and store to USB stick? -* 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. +- 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. -* beef up pam_systemd to take unit file settings such as cgroups properties as - parameters +- SIGRTMIN+18 and memory pressure handling should still be added to: localed, + oomd, timedated. -* In DynamicUser= mode: before selecting a UID, use disk quota APIs on relevant - disks to see if the UID is already in use. +- socket units: allow creating a udev monitor socket with ListenDevices= or so, + with matches, then activate app through that passing socket over -* 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. +- special case some calls of chase() to use openat2() internally, so + that the kernel does what we otherwise do. -* 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: +- 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. - ReadWritePaths=:/var/lib/foobar +- start making use of the new --graceful switch to util-linux' umount command -* 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. +- start using STATX_SUBVOL in btrfs_is_subvol(). Also, make use of it + generically, so that image discovery recognizes bcachefs subvols too. -* 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. +- 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. -* 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. +- **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 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. +- 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. -* enable LockMLOCK to take a percentage value relative to physical memory +- support crash reporting operation modes (https://live.gnome.org/GnomeOS/Design/Whiteboards/ProblemReporting) -* Permit masking specific netlink APIs with RestrictAddressFamily= +- support projid-based quota in machinectl for containers -* define gpt header bits to select volatility mode +- Support ReadWritePaths/ReadOnlyPaths/InaccessiblePaths in systemd --user instances + via the new unprivileged Landlock LSM (https://landlock.io) -* ProtectClock= (drops CAP_SYS_TIMES, adds seecomp filters for settimeofday, adjtimex), sets DeviceAllow o /dev/rtc +- support specifying download hash sum in systemd-import-generator expression + to pin image/tarball. -* ProtectTracing= (drops CAP_SYS_PTRACE, blocks ptrace syscall, makes /sys/kernel/tracing go away) +- 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) -* ProtectMount= (drop mount/umount/pivot_root from seccomp, disallow fuse via DeviceAllow, imply Mountflags=slave) +- synchronize console access with BSD locks: + https://lists.freedesktop.org/archives/systemd-devel/2014-October/024582.html -* ProtectKeyRing= to take keyring calls away +- 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. -* RemoveKeyRing= to remove all keyring entries of the specified user +- sysext: measure all activated sysext into a TPM PCR -* 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 +- 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) -* Support ReadWritePaths/ReadOnlyPaths/InaccessiblePaths in systemd --user instances - via the new unprivileged Landlock LSM (https://landlock.io) +- system BPF LSM policy that prohibits creating files owned by "nobody" + system-wide -* make sure the ratelimit object can deal with USEC_INFINITY as way to turn off things +- 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. -* 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. +- "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 bus API for creating unit files in /etc, reusing the code for transient units +- systemctl, machinectl, loginctl: port "status" commands over to + format-table.c's vertical output logic. -* add bus API to remove unit files from /etc +- **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? -* add bus API to retrieve current unit file contents (i.e. implement "systemctl cat" on the bus only) +- systemd-analyze inspect-elf should show other notes too, at least build-id. -* rework fopen_temporary() to make use of open_tmpfile_linkable() (problem: the - kernel doesn't support linkat() that replaces existing files, currently) +- systemd-analyze netif that explains predictable interface (or networkctl) -* transient units: don't bother with actually setting unit properties, we - reload the unit file anyway +- 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?) -* optionally, also require WATCHDOG=1 notifications during service start-up and shutdown +- 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. -* cache sd_event_now() result from before the first iteration... +- 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. -* PID1: find a way how we can reload unit file configuration for - specific units only, without reloading the whole of systemd +- systemd-dissect: add --cat switch for dumping files such as /etc/os-release -* add an explicit parser for LimitRTPRIO= that verifies - the specified range and generates sane error messages for incorrect - specifications. +- 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). -* when we detect that there are waiting jobs but no running jobs, do something +- systemd-firstboot: optionally install an ssh key for root for offline use. -* PID 1 should send out sd_notify("WATCHDOG=1") messages (for usage in the --user mode, and when run via nspawn) +- 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. -* 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 +- systemd-inhibit: make taking delay locks useful: support sending SIGINT or SIGTERM on PrepareForSleep() -* fstab-generator: default to tmpfs-as-root if only usr= is specified on the kernel cmdline +- **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 -* docs: bring https://systemd.io/MY_SERVICE_CANT_GET_REALTIME up to date +- systemd-path: Add "private" runtime/state/cache dir enum, mapping to + $RUNTIME_DIRECTORY, $STATE_DIRECTORY and such -* 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 +- **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. -* 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. +- **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. + - 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. -* hostnamectl: show root image uuid +- 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. -* Find a solution for SMACK capabilities stuff: - https://lists.freedesktop.org/archives/systemd-devel/2014-December/026188.html +- 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) -* synchronize console access with BSD locks: - https://lists.freedesktop.org/archives/systemd-devel/2014-October/024582.html +- 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. -* as soon as we have sender timestamps, revisit coalescing multiple parallel daemon reloads: - https://lists.freedesktop.org/archives/systemd-devel/2014-December/025862.html +- add a networking provider API, inspired by the StorageProvider. Make networkd + a provider that exposes interfaces for adding tap, tun, veth via the api, + base this on .netdev logic somehow. -* figure out when we can use the coarse timers +- $SYSTEMD_EXECPID that the service manager sets should + be augmented with $SYSTEMD_EXECPIDFD (and similar for + other env vars we might send). -* maybe allow timer units with an empty Units= setting, so that they - can be used for resuming the system but nothing else. +- **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. -* what to do about udev db binary stability for apps? (raw access is not an option) +- teach parse_timestamp() timezones like the calendar spec already knows it -* exponential backoff in timesyncd when we cannot reach a server +- 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. -* timesyncd: add ugly bus calls to set NTP servers per-interface, for usage by NM +- **test/:** + - add unit tests for config_parse_device_allow() -* add systemd.abort_on_kill or some other such flag to send SIGABRT instead of SIGKILL - (throughout the codebase, not only PID1) +- 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. -* drop nss-myhostname in favour of nss-resolve? +- the pub/priv key pair generated on the TPM2 should probably also be one you + can use to get a remote attestation quote. -* 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 (?) +- 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. -* refcounting in sd-resolve is borked +- There's currently no way to cancel fsck (used to be possible via C-c or c on the console) -* add new gpt type for btrfs volumes +- 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 -* generator that automatically discovers btrfs subvolumes, identifies their purpose based on some xattr on them. +- 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. -* a way for container managers to turn off getty starting via $container_headless= or so... +- **timer units:** + - timer units should get the ability to trigger when DST changes + - Modulate timer frequency based on battery state -* figure out a nice way how we can let the admin know what child/sibling unit causes cgroup membership for a specific unit +- timesyncd: add ugly bus calls to set NTP servers per-interface, for usage by NM -* 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. +- 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. -* add bus api to query unit file's X fields. +- 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. -* gpt-auto-generator: - - Make /home automount rather than mount? +- **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. + - allow conditionalizing on factory reset boot, or on first boot. + - when cleaning up directories, take care of btrfs subvolumes too + (ID 927 gen 217573 top level 256 path var/tmp/.#test-btrfs4e8ef947c1122031 + ID 928 gen 217573 top level 927 path var/tmp/.#test-btrfs4e8ef947c1122031/rec + ID 929 gen 217574 top level 928 path var/tmp/.#test-btrfs4e8ef947c1122031/rec/sv2 + ID 930 gen 217575 top level 928 path var/tmp/.#test-btrfs4e8ef947c1122031/rec/sv3 + ID 931 gen 217576 top level 930 path var/tmp/.#test-btrfs4e8ef947c1122031/rec/sv3/sub + ID 932 gen 217577 top level 928 path var/tmp/.#test-btrfs4e8ef947c1122031/rec/dir/sv4 + ID 933 gen 217578 top level 932 path var/tmp/.#test-btrfs4e8ef947c1122031/rec/dir/sv4/dir/sv5) + +- 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) -* 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 +- tpm2-setup: reboot if we detect SRK changed -* MessageQueueMessageSize= (and suchlike) should use parse_iec_size(). +- 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. -* implement Distribute= in socket units to allow running multiple - service instances processing the listening socket, and open this up - for ReusePort= +- TPM2: auto-reenroll in cryptsetup, as fallback for hosed firmware upgrades + and such -* 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 +- 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. + +- transient units: don't bother with actually setting unit properties, we + reload the unit file anyway -* transient units: +- **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 +- 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. -* be more careful what we export on the bus as (usec_t) 0 and (usec_t) -1 +- 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. -* 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 +- 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 -* 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. +- **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 -* refuse boot if /usr/lib/os-release is missing or /etc/machine-id cannot be set up +- **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. -* man: the documentation of Restart= currently is very misleading and suggests the tools from ExecStartPre= might get restarted. +- **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)? -* There's currently no way to cancel fsck (used to be possible via C-c or c on the console) +- udevd: extend memory pressure logic: also kill any idle worker processes -* add option to sockets to avoid activation. Instead just drop packets/connections, see http://cyberelk.net/tim/2012/02/15/portreserve-systemd-solution/ +- unify how blockdev_get_root() and sysupdate find the default root block device -* make sure systemd-ask-password-wall does not shutdown systemd-ask-password-console too early +- **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 -* 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 - -* Make it possible to set the keymap independently from the font on - the kernel cmdline. Right now setting one resets also the other. - -* and a dbus call to generate target from current state - -* investigate whether the gnome pty helper should be moved into systemd, to provide cgroup support. - -* dot output for --test showing the 'initial transaction' - -* be able to specify a forced restart of service A where service B depends on, in case B - needs to be auto-respawned? - -* 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 - -* unit files: +- **unit files:** - allow port=0 in .socket units - maybe introduce ExecRestartPre= - implement Register= switch in .socket units to enable registration @@ -2296,601 +2853,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. - -* when isolating, try to figure out a way how we implicitly can order - all units we stop before the isolating unit... - -* teach ConditionKernelCommandLine= globs or regexes (in order to match foobar={no,0,off}) - -* 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. - -* BootLoaderSpec: define a way how an installer can figure out whether a BLS - compliant boot loader is installed. - -* BootLoaderSpec: document @saved pseudo-entry, update mention in BLI - -* 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. - -* systemd-inhibit: make taking delay locks useful: support sending SIGINT or SIGTERM on PrepareForSleep() - -* remove any syslog support from log.c — we probably cannot do this before split-off udev is gone for good - -* shutdown logging: store to EFI var, and store to USB stick? - -* merge unit_kill_common() and unit_kill_context() - -* add a dependency on standard-conf.xml and other included files to man pages - -* MountFlags=shared acts as MountFlags=slave right now. - -* properly handle loop back mounts via fstab, especially regards to fsck/passno - -* initialize the hostname from the fs label of /, if /etc/hostname does not exist? - -* 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 - -* 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 - -* 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. - -* document org.freedesktop.MemoryAllocation1 - -* maybe do not install getty@tty1.service symlink in /etc but in /usr? - -* print a nicer explanation if people use variable/specifier expansion in ExecStart= for the first word - -* mount: turn dependency information from /proc/self/mountinfo into dependency information between systemd units. - -* 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 - -* 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 - -* 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 - -* move multiseat vid/pid matches from logind udev rule to hwdb - -* 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 - -* 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 +- **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 -* 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. +- 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 -* systemd-repart: allow sizing partitions as factor of available RAM, so that - we can reasonably size swap partitions for hibernation. +- 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. -* 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. +- use sd-event ratelimit feature optionally for journal stream clients that log + too much -* systemd-repart: add per-partition option to never reuse existing partition - and always create anew even if matching partition already exists. +- userdb: allow existence checks -* 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. +- 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. -* 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. +- 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-repart: make it a static checker during early boot for existence and - absence of other partitions for trusted boot environments +- 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. -* 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. +- Varlinkification of the following command line tools, to open them up to + other programs via IPC: + - coredumpctl + - systemd-bless-boot + - systemd-measure + - systemd-dissect + - systemd-sysupdate + - systemd-analyze + - kernel-install + - systemd-mount (with PK so that desktop environments could use it to mount disks) -* systemd-repart: do not print "Successfully resized …" when no change was done. +- 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 -* 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 +- 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). -* 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 +- **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 -* 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. +- **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). -* 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. +- 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. -* 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. +- We should probably replace /etc/rc.d/README with a symlink to doc + content. After all it is constant vendor data. -* show whether a service has out-of-date configuration in "systemctl status" by - using mtime data of ConfigurationDirectory=. +- 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). -* "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) +- what to do about udev db binary stability for apps? (raw access is not an option) -* systemctl: if some operation fails, show log output? +- when importing an fs tree with machined, complain if image is not an OS -* Add a new verb "systemctl top" +- when importing an fs tree with machined, optionally apply userns-rec-chown -* 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... -* 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. +- when killing due to service watchdog timeout maybe detect whether target + process is under ptracing and then log loudly and continue instead. -* 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 +- 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. -* 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. +- 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. -* 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 +- 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 -* support crash reporting operation modes (https://live.gnome.org/GnomeOS/Design/Whiteboards/ProblemReporting) +- when we detect that there are waiting jobs but no running jobs, do something -* 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 +- whenever we receive fds via SCM_RIGHTS make sure none got dropped due to the + reception limit the kernel silently enforces. -* 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) +- after option+verb introspection is added, add a test to verify that the + list in proc-cmdline.c matches the actual option list in systemd and shutdown. -* 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..a4d286f9553a7 100644 --- a/catalog/systemd.catalog.in +++ b/catalog/systemd.catalog.in @@ -455,7 +455,7 @@ Support: %SUPPORT_URL% The directory @WHERE@ is specified as the mount point (second field in /etc/fstab or Where= field in systemd unit file) and is not empty. -This does not interfere with mounting, but the pre-exisiting files in +This does not interfere with mounting, but the pre-existing files in this directory become inaccessible. To see those over-mounted files, please manually mount the underlying file system to a secondary location. @@ -1015,3 +1015,25 @@ 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. + +-- eac6d394c925493deac2ba99f3c2bb11 +Subject: An optional dependency for @FEATURE@ is not installed +Defined-By: systemd +Support: %SUPPORT_URL% + +The @FEATURE@ feature that was requested depends on an optional component that +is loaded at runtime (for example a shared library opened via dlopen(3)), but +that component is not installed on this system. + +As a result @FEATURE@ is unavailable, and is either skipped, disabled, or +degraded to a fallback mode. Refer to the accompanying log message for the +specific dependency that is missing, then install it to enable @FEATURE@. 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..536a2d2670c04 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-util.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/BOOT_LOADER_INTERFACE.md b/docs/BOOT_LOADER_INTERFACE.md index 5c2e74f29011d..efe5c1cce29fa 100644 --- a/docs/BOOT_LOADER_INTERFACE.md +++ b/docs/BOOT_LOADER_INTERFACE.md @@ -143,6 +143,10 @@ Variables will be listed below using the Linux efivarfs naming, * `1 << 18` → The boot loader reports active TPM2 PCR banks in the EFI variable `LoaderTpm2ActivePcrBanks-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`. * `1 << 19` → The boot loader supports the `LoaderEntryPreferred` variable when set. + * `1 << 20` → The boot loader reports the firmware-configured keyboard layout in the + EFI variable `LoaderKeyboardLayout-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`. + * `1 << 21` → The boot loader measures SMBIOS information into a TPM2 PCR and reports the PCR index in the + EFI variable `LoaderPcrSMBIOS-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`. * The EFI variable `LoaderSystemToken-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f` contains binary random data, @@ -171,6 +175,28 @@ Variables will be listed below using the Linux efivarfs naming, the TCG EFI ProtocolSpecification for TPM 2.0 as `EFI_TCG2_BOOT_HASH_ALG_*`. If no TPM2 support or no active banks were detected, will be set to `0`. +* The EFI variable `LoaderKeyboardLayout-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f` + contains the RFC 4646 (BCP 47) language tag of the currently-active keyboard + layout as reported by the UEFI HII database (e.g. `en-US`, `de-DE`). + It is formatted as a NUL-terminated UTF-16 string. + The boot loader sets this variable from the layout returned by + `EFI_HII_DATABASE_PROTOCOL.GetKeyboardLayout()`, + if that protocol is implemented by the firmware. + Userspace (notably `systemd-vconsole-setup`) + uses this as a lowest-priority fallback keyboard layout + when no explicit configuration is provided. + +* The EFI variable `LoaderPcrSMBIOS-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f` + contains the index of the TPM2 PCR (as a decimal ASCII string formatted as a + NUL-terminated UTF-16 string, e.g. `1`) into which the boot loader measured + select SMBIOS structures: type 1 (system information, with the volatile + "Wake-up Type" field zeroed out), type 2 (baseboard information) and type 11 + (OEM strings). This is a volatile (non-persistent) variable, set only if a + measurement was successfully completed, and remains unset otherwise. Both + `systemd-boot` and `systemd-stub` perform this measurement; whichever runs + first sets the variable, and its presence suppresses a second measurement of + the same data into the same PCR during the same boot. + If `LoaderTimeInitUSec` and `LoaderTimeExecUSec` are set, `systemd-analyze` will include them in its boot-time analysis. If `LoaderDevicePartUUID` is set, systemd will mount the ESP that was used for the boot to `/boot`, but only if 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..51551962f5e06 100644 --- a/docs/CODING_STYLE.md +++ b/docs/CODING_STYLE.md @@ -94,6 +94,28 @@ SPDX-License-Identifier: LGPL-2.1-or-later } ``` +- Braces in `if` blocks are not required to be symmetric. Write this: + + ```c + if (foobar) + waldo(); + else { + foo(); + bar(); + } + ``` + + instead of this: + + ```c + if (foobar) { + waldo(); + } else { + foo(); + bar(); + } + ``` + - Do not write `foo ()`, write `foo()`. - `else` blocks should generally start on the same line as the closing `}`: @@ -366,10 +388,10 @@ SPDX-License-Identifier: LGPL-2.1-or-later - `src/shared`: `shared-forward.h` Header files that extend other header files can include the original header - file. For example, `iovec-util.h` includes `iovec-fundamental.h` and - `sys/uio.h`. To identify headers that are exported from other headers, add a - `IWYU pragma: export` comment to the includes so that these exports are - recognized by clang static analysis tooling. + file. For example, `iovec-util.h` includes `sys/uio.h`. To identify headers + that are exported from other headers, add a `IWYU pragma: export` comment + to the includes so that these exports are recognized by clang static analysis + tooling. Bad: @@ -754,9 +776,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..8639cb0a877c8 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -50,21 +50,30 @@ See [reporting of security vulnerabilities](https://systemd.io/SECURITY). * Github will automatically add the `please-review` label when a pull request is opened or updated. If you need more information after a review, you can comment `/please-review` on the pull request to have Github add the `please-review` label to the pull request. -## 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. - -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 -ensuring it accurately follows [Coding Style](https://systemd.io/CODING_STYLE) at the very minimum. Please do -not fire-and-forget pull requests without any human intervention and review, as that will likely result in -low-quality results that will not be accepted, and if done repeatedly, may result in the account being -blocked. As with any other submissions, authors are responsible for doing due diligence and ensuring their -submissions are compatible with the project's license as documented in LICENSES/README.md. - -As a guideline, if someone notices that a contribution (code, issues, comments) was made with the help of AI, -there was likely a lack of human review of the AI generated output. +## Policy on the use of Large Language Models (LLMs) and AI tooling + +We expect everyone contributing to systemd to fully own their +contribution, be able to reason about it, be able to explain why things +were done a particular way and act as the full owner of that code. AI +tools are treated the same as traditional tooling like `sed`, `awk` or +`coccinelle`. + +For the purpose of this project, AI tools CANNOT be treated as author, +co-author or be credited in any way that would suggest any ownership +over the contribution. + +The contributor should have done all the thinking, planning and +understanding of the changes needed to resolve an issue or implement a +new feature prior to using automated tooling to perform the grunt work. + +Unguided use of those tools or the inability to prove understanding of +the code contributed will result in a loss of trust in that contributor +by project maintainers which can then lead to exclusion from any further +contribution to the project. + +As with any other submissions, authors are responsible for doing due +diligence and ensuring their submissions are compatible with the +project's license as documented in LICENSES/README.md. ## Reviewing Pull Requests diff --git a/docs/CREDENTIALS.md b/docs/CREDENTIALS.md index abfdfb0ad297b..149cf2f7db274 100644 --- a/docs/CREDENTIALS.md +++ b/docs/CREDENTIALS.md @@ -52,9 +52,9 @@ purpose. Specifically, the following features are provided: 7. Credentials may be acquired from a hosting VM hypervisor (SMBIOS OEM strings or qemu `fw_cfg`), a hosting container manager, the kernel command line, - from the initrd, or from the UEFI environment via the EFI System Partition - (via `systemd-stub`). Such system credentials may then be propagated into - individual services as needed. + from the initrd, from the UEFI environment via the EFI System Partition + (via `systemd-stub`), or from a cloud Instance Metadata Service (IMDS). Such + system credentials may then be propagated into individual services as needed. 8. Credentials are an effective way to pass parameters into services that run with `RootImage=` or `RootDirectory=` and thus cannot read these resources @@ -67,7 +67,7 @@ purpose. Specifically, the following features are provided: ## Configuring per-Service Credentials -Within unit files, there are the following settings to configure service +Within unit files, there are the following settings to configure service credentials. 1. `LoadCredential=` may be used to load a credential from disk, from an @@ -170,7 +170,7 @@ plaintext form will be placed in `$CREDENTIALS_DIRECTORY`. Use a command such as `systemd-creds --system cat …` to access both forms of credentials, and decrypt them if needed (see [systemd-creds(1)](https://www.freedesktop.org/software/systemd/man/latest/systemd-creds.html) -for details. +for details). Note that generators typically run very early during boot (similar to initrd code), earlier than the `/var/` file system is necessarily mounted (which is @@ -366,6 +366,71 @@ Or propagated to services further down: systemd-run -p ImportCredential=mycred -P --wait systemd-creds cat mycred ``` +## Acquisition from Cloud Instance Metadata Services (IMDS) + +Most public cloud environments provide an "Instance Metadata Service" (IMDS), +i.e. a node-local network endpoint from which a virtual machine may acquire +information about itself and the parameters it was invoked with, including data +suitable for use as `systemd` credentials. `systemd` can automatically acquire +such data from the IMDS and import it into the system's credential store, where +it is then available to services via `ImportCredential=` and friends, the same +way as credentials acquired through any of the mechanisms described above. + +This functionality is implemented by +[`systemd-imdsd@.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-imdsd@.service.html), +which provides a local Varlink IPC interface to the IMDS endpoint, and the +[`systemd-imds`](https://www.freedesktop.org/software/systemd/man/latest/systemd-imds.html) +client tool. On recognized clouds these are pulled into the boot transaction +automatically by +[`systemd-imds-generator`](https://www.freedesktop.org/software/systemd/man/latest/systemd-imds-generator.html), +which detects the cloud environment via the SMBIOS/DMI hardware database +(`hwdb.d/40-imds.hwdb`). + +At boot the `systemd-imds-import.service` runs `systemd-imds --import`, which +acquires data from the IMDS endpoint and writes the relevant fields as +credentials into `/run/credstore/` (and `/run/credstore.encrypted/` for +encrypted credentials). Because these directories are part of the credential +search path (see "Relevant Paths" below), credentials imported this way are +automatically picked up by `ImportCredential=` in consuming units. Specifically, +the following is imported: + +1. The instance's "userdata" field, if it is a JSON object containing a + `systemd.credentials` array. Each array entry carries a `name` field plus + one of `text` (a literal string), `data` (Base64-encoded binary data), or + `encrypted` (a Base64-encoded encrypted credential, as produced by + `systemd-creds encrypt`). This is the primary mechanism for passing + arbitrary credentials into a cloud instance. Note: if a traditional IMDS + client (such as `cloud-init`) is used the "userdata" field might be used for + that, and `systemd-imds-import.service` will gracefully ignore the data. Or + in other words: this functionality is not available if `cloud-init` is used. + +2. The instance's SSH public key, imported into the `ssh.authorized_keys.root` + credential (used to provision SSH access for the `root` user). + +3. The instance's hostname, imported into the `firstboot.hostname` credential + (consumed by `systemd-firstboot.service`). + +The acquired userdata is measured into the TPM2 (PCR 12) before it is imported, +so that the cloud-provided parameterization may be subjected to attestation. + +An example `systemd.credentials` userdata payload (passed as the instance's user +data via the cloud's provisioning interface) looks like this: + +```json +{ + "systemd.credentials": [ + { + "name": "mycred", + "text": "supersecret" + }, + { + "name": "mybinarycred", + "data": "YmFyCg==" + } + ] +} +``` + ## Well-Known Credentials Various services shipped with `systemd` consume credentials for tweaking behaviour: diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 5390754661879..23f7c9cd5f073 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -35,11 +35,15 @@ All tools: no-ops. If that's what's explicitly desired, you might consider setting `$SYSTEMD_OFFLINE=1`. +* `$SYSTEMD_IN_INITRD` — takes a boolean. If set, overrides initrd detection. + This is useful for debugging and testing initrd-only programs in the main + system. + * `$SYSTEMD_FIRST_BOOT=0|1` — if set, assume "first boot" condition to be false or true, instead of checking the flag file created by PID 1. -* `$SD_EVENT_PROFILE_DELAYS=1` — if set, the sd-event event loop implementation - will print latency information at runtime. +* `$SYSTEMD_INVOKED_AS=name` — override argv[0] for detection of a multicall + binary. E.g. `SYSTEMD_INVOKED_AS=systemd-udevd build/udevadm`. * `$SYSTEMD_PROC_CMDLINE` — if set, the contents are used as the kernel command line instead of the actual one in `/proc/cmdline`. This is useful for @@ -72,13 +76,21 @@ 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). -* `$SYSTEMD_IN_INITRD` — takes a boolean. If set, overrides initrd detection. - This is useful for debugging and testing initrd-only programs in the main - system. +* `$SYSTEMD_HOSTNAME_WORDLIST_PATH` — search this directory for the numbered + hostname word list files used by the `$` wildcard in hostname patterns (see + `hostname(5)`), instead of the built-in search path. Only useful for + debugging and testing. + +* `$SD_EVENT_PROFILE_DELAYS=1` — if set, the sd-event event loop implementation + will print latency information at runtime. * `$SYSTEMD_BUS_TIMEOUT=SECS` — specifies the maximum time to wait for method call completion. If no time unit is specified, assumes seconds. The usual other units @@ -679,6 +691,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. @@ -857,3 +874,10 @@ Tools using the Varlink protocol (such as `varlinkctl`) or sd-bus (such as 'freshness' check via `BEST-BEFORE-YYYY-MM-DD` files in `SHA256SUMS` manifest files is disabled, and updating from outdated manifests will not result in an error. + +* `$SYSTEMD_SYSUPDATE_FORCE_NOTIFY` – takes a boolean. If true the notification + callouts in `/run/systemd/sysupdate/notify/` are invoked even when no update + was applied (i.e. the system was already up-to-date). In this case the + notification carries no list of updated resources. This is useful to + unconditionally trigger follow-up work such as relinking a kernel or + recomputing a TPM policy. diff --git a/docs/FILE_DESCRIPTOR_STORE.md b/docs/FILE_DESCRIPTOR_STORE.md index 231af87c912d4..6e1744510592c 100644 --- a/docs/FILE_DESCRIPTOR_STORE.md +++ b/docs/FILE_DESCRIPTOR_STORE.md @@ -123,7 +123,10 @@ This behavior can be modified via the [`FileDescriptorStorePreserve=`](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#FileDescriptorStorePreserve=) setting in service unit files. If set to `yes` the fdstore will be kept as long as the service definition is loaded into memory by the service manager, i.e. as -long as at least one other loaded unit has a reference to it. +long as at least one other loaded unit has a reference to it. If set to +`on-success` the behaviour is the same as `yes`, except that the fdstore is +discarded once the service enters the permanent `failed` state, i.e. after all +automated restart attempts driven by `Restart=` have been exhausted. The `systemctl clean --what=fdstore …` command may be used to explicitly clear the fdstore of a service. This is only allowed when the service is fully @@ -181,6 +184,39 @@ continuously). For further details see [Resource Pass-Through](https://www.freedesktop.org/software/systemd/man/latest/systemd-soft-reboot.service.html#Resource%20Pass-Through). +## Kernel Live Update (kexec) + +On kernels that support the [Live Update +Orchestrator](https://docs.kernel.org/userspace-api/liveupdate.html) +(LUO), the fdstore may also be preserved across a `kexec`-based reboot into a +new kernel. This allows updating the kernel itself without losing pinned +resources such as serialized service state, analogous to soft reboot, but for +the kernel. + +Only file descriptors that reference LUO-compatible kernel objects can be +preserved this way. Currently the kernel supports `memfd` only for LUO, but +more types are being worked on. Other kinds of file descriptors (sockets, +regular files, etc.) will be dropped from the store during the kexec transition. + +LUO preservation of the fdstore is triggered automatically whenever a +kexec-based reboot is initiated on an LUO-capable kernel, and is gated by a +similar rule as soft-reboot: the service must have +`FileDescriptorStorePreserve=yes` set, so that its fdstore remains loaded. On +the other side of the kexec, the system manager rebuilds the mapping of fds +back to their original service units, so that when those services are +re-activated the fds are passed to them using the normal fdstore protocol. +Adding a `FDNAME=…` string identifying the fd is also highly recommended, +otherwise in case multiple fds are stored, it will be impossible to +distinguish them, as they will all carry the default name (`stored`). + +Services that need to preserve additional kernel state may also create their +own LUO sessions by opening `/dev/liveupdate` directly (see the kernel +documentation linked above) and pushing the obtained session fd into their +fdstore (it is recommended to use a `FDNAME=…` string, as above). systemd +detects such fds and arranges for them to survive the kexec as well, so that +the session, and any supported file descriptors preserved inside it, is +handed back to the service on the other side of the reboot. + ## Initrd Transitions The fdstore may also be used to pass file descriptors for resources from the @@ -198,6 +234,27 @@ The soft reboot cycle transition and the initrd→host transition are semantically very similar, hence similar rules apply, and in both cases it is recommended to use the fdstore if pinned resources shall be passed over. +## Propagation Across Manager Boundaries + +When a service that has `FileDescriptorStorePreserve=yes` set is itself running +under another service manager, for example a service of the per-user manager +(`user@.service`), or a payload running inside a +[`systemd-nspawn`](https://www.freedesktop.org/software/systemd/man/latest/systemd-nspawn.html) +container, fds pushed into its fdstore are automatically forwarded one level up +the supervisor chain via the enveloping manager's `$NOTIFY_SOCKET`. This allows +the fdstore contents of inner services to be preserved across restarts, re-execs, +soft-reboots, etc. of the *outer* manager, even when the inner manager (or the +container payload) is itself restarted along the way. On the way up, each fd is +tagged with its originating unit id and the original `FDNAME=…` value, so that +when the fds are eventually handed back down (via the regular +`$LISTEN_FDS`/`$LISTEN_FDNAMES` protocol), each manager along the chain can +route them back to the correct unit's fdstore. `FDSTOREREMOVE=1` notifications +are forwarded the same way, so that explicit removals propagate all the way up too. + +For this to work the enveloping unit must itself enable the fdstore (i.e. set +`FileDescriptorStoreMax=` to a sufficiently large value and +`FileDescriptorStorePreserve=yes`). + ## Debugging The diff --git a/docs/HOME_DIRECTORY.md b/docs/HOME_DIRECTORY.md index 57d87f5eeac08..0ef39e9737254 100644 --- a/docs/HOME_DIRECTORY.md +++ b/docs/HOME_DIRECTORY.md @@ -54,11 +54,15 @@ mechanism, except that the home directory is encrypted using `fscrypt`. Key management is implemented via extended attributes on the directory itself: for each password an extended attribute `trusted.fscrypt_slot0`, `trusted.fscrypt_slot1`, `trusted.fscrypt_slot2`, … is maintained. -Its value contains a colon-separated pair of Base64 encoded data fields. -The first field contains a salt value, the second field the encrypted volume key. -The latter is encrypted using AES256 in counter mode, using a key derived from the password via PBKDF2-HMAC-SHA512, -together with the salt value. -The construction is similar to what LUKS does for`dm-crypt` encrypted volumes. +New slots are written in the `v2` format: a colon-separated string of the form +`$v2:::::`, where `` is a decimal integer +and the salt, IV, ciphertext and tag fields are Base64 encoded. +The volume key is wrapped with AES-256-GCM (authenticated encryption with a random IV) +under a key derived from the password via PBKDF2-HMAC-SHA512 with the stored iteration count. +For backward compatibility, legacy slots written by older versions of systemd-homed +(a colon-separated `:` pair encrypted with AES-256-CTR and a 0xFFFF-iteration +PBKDF2-HMAC-SHA512 KDF) continue to be readable, and they are upgraded to the v2 format the next +time the password is changed. Note that extended attributes are not encrypted by `fscrypt` and hence are suitable for carrying the key slots. Moreover, by using extended attributes, the slots are directly attached to the directory and an independent sidecar key database is not required. diff --git a/docs/INHIBITOR_LOCKS.md b/docs/INHIBITOR_LOCKS.md index 220c085e09a43..e33183817e434 100644 --- a/docs/INHIBITOR_LOCKS.md +++ b/docs/INHIBITOR_LOCKS.md @@ -26,14 +26,12 @@ 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. +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. -- _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. - -Two different modes of locks are supported: +Three different modes of locks are supported: 1. _block_ inhibits operations entirely until the lock is released. If such a lock is taken the operation will fail (but still may be overridden if the user possesses the necessary privileges). 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..2caeb102aa4b4 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)** (preferred) +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..faa918f6319f6 100644 --- a/docs/TPM2_PCR_MEASUREMENTS.md +++ b/docs/TPM2_PCR_MEASUREMENTS.md @@ -58,7 +58,22 @@ away there's a naming concept, so that nvindexes are referenced by name string rather than number. NvPCRs are defined in little JSON snippets in `/usr/lib/nvpcr/*.nvpcr`, that -match up index number and name, as well as pick a hash algorithm. +match up index number and name, as well as pick a hash algorithm. The recognized +fields are: + +* `name` — the NvPCR name (string), which must match the file name (without the + `.nvpcr` suffix). Mandatory. +* `nvIndex` — the fixed TPM2 NV index handle (number) to allocate for this NvPCR. + Mandatory. +* `algorithm` — the hash algorithm to use (string), e.g. `sha256` (the default). +* `priority` — an unsigned integer allocation priority, defaulting to `1000`. + Lower values are considered more important and are allocated first. This only + affects the order in which `systemd-tpm2-setup.service` attempts allocation at + boot: if the TPM's NV index space is too small to fit all NvPCRs, the most + important ones (lowest `priority` value) win the available space, and the + least important ones are skipped gracefully rather than the allocation failing + arbitrarily. Ties are broken by name. Priority does not affect the NV index, + the algorithm, or anything measured into the NvPCR. There's one complication: these NV indexes (like any NV indexes) can be deleted by anyone with access to the TPM, and then be recreated. This could be used to @@ -76,6 +91,45 @@ must be employed when designing a system that uses this feature. ## PCR Measurements Made by `systemd-boot` (UEFI) +### PCR 1, `EV_EVENT_TAG`, SMBIOS information + +Select SMBIOS structures provided by the firmware are measured into PCR 1 (the +TCG-defined register for platform configuration data), one tagged event per +structure: + +* SMBIOS type 1 (system information). The volatile "Wake-up Type" field is + zeroed before measuring, since it varies depending on how the machine was + powered on (cold boot, resume from sleep, AC restore, …) and would otherwise + make the measurement non-reproducible. +* SMBIOS type 2 (baseboard information). +* SMBIOS type 11 (OEM strings). There may be more than one such structure; all + are measured. + +Note that these measurements are – strictly speaking – redundant, since +firmwares are supposed to measure SMBIOS data anyway on their own. However, it +has been found this is not the case on many real-life implementations. Since in +particular SMBIOS type 11 may carry highly relevant input for the OS +(e.g. system credentials), an explicit measurement is made here to ensure all +parameters for the OS are comprehensively measured even on flaky firmwares. + +→ **Event Tag** `0xd5cb7cbc` for type 1, `0xe0d47bc8` for type 2, `0xc0b3bd23` +for type 11. + +→ **Description** in the event log record is `smbios:type1`, `smbios:type2` or +`smbios:type11` respectively, in UTF-16. + +→ **Measured hash** covers the raw bytes of the SMBIOS structure (formatted area +plus trailing string set), with the type 1 "Wake-up Type" field zeroed out as +described above. + +This measurement is also performed by `systemd-stub` (see below), so that systems +that boot a UKI directly, bypassing `systemd-boot`, still get it. Whichever +component runs first performs the measurement and sets the volatile +`LoaderPcrSMBIOS` EFI variable to the PCR index used; its presence suppresses a +second measurement of the same data into the same PCR during the same boot. Note +that the firmware itself typically also extends PCR 1, so its final value is not +solely determined by this measurement. + ### PCR 5, `EV_EVENT_TAG`, `loader.conf` The content of `systemd-boot`'s configuration file, `loader/loader.conf`, is @@ -105,6 +159,14 @@ trailing NUL bytes). ## PCR Measurements Made by `systemd-stub` (UEFI) +### PCR 1, `EV_EVENT_TAG`, SMBIOS information + +Identical to the SMBIOS measurement described above for `systemd-boot`. When +`systemd-stub` is invoked by `systemd-boot`, the measurement has typically already +been made (tracked via the `LoaderPcrSMBIOS` EFI variable) and is not repeated; +when the UKI is booted directly by the firmware, `systemd-stub` performs it +itself. + ### PCR 11, `EV_IPL`, PE section name A measurement is made for each PE section of the UKI that is defined by the @@ -203,7 +265,7 @@ on-the-fly by `systemd-stub`). ### PCR 9, NvPCR Initializations -The `systemd-tpm2-setup.service` service initializes any NvPCRs defined via +The `systemd-tpm2-setup-early.service` service initializes any NvPCRs defined via `*.nvpcr` files. For each initialized NvPCR it will measure an event into PCR 9. @@ -260,10 +322,27 @@ colon-separated strings, identifying the file system type, UUID, label as well as the GPT partition entry UUID, entry type UUID and entry label (in UTF-8, without trailing NUL bytes). +### NvPCR `login` (base+3), user logins + +The `systemd-pcrlogin@.service` service (a per-UID template unit started by +`systemd-logind.service` on a user's first login of the current boot) will +measure that user's record into the `login` NvPCR. Each user is measured exactly +once per boot (the unit is `Type=oneshot`/`RemainAfterExit=yes` and is never +stopped again), so the NvPCR forms an append-only record of which user +identities were activated during the current boot. Note that its value is +inherently dynamic: it depends on *which* users log in and *in which order*. + +→ **Measured hash** covers the string "login:", suffixed by the (escaped) +user name, a colon, and the user's record reduced to its `regular`, `perMachine` +and `binding` sections (i.e. with the `privileged`, `secret`, `status` and +`signature` sections stripped), normalized and serialized to canonical, +single-line JSON. Example string: +`login:lennart:{"userName":"lennart","uid":1000,…}`. + ### PCR 9, NvPCR initialization separator -After completion of `systemd-tpm2-setup.service` (which initializes all NvPCRs -and measures their initial state) at arly boot the `systemd-pcrnvdone.service` +After completion of `systemd-tpm2-setup-early.service` (which initializes all NvPCRs +and measures their initial state) at early boot the `systemd-pcrnvdone.service` service will measure a separator event into PCR 9, isolating the early-boot NvPCR initializations from any later additions. @@ -293,3 +372,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/TRANSIENT-SETTINGS.md b/docs/TRANSIENT-SETTINGS.md index 652ac3d95e64c..3ea51163499bb 100644 --- a/docs/TRANSIENT-SETTINGS.md +++ b/docs/TRANSIENT-SETTINGS.md @@ -307,6 +307,7 @@ All cgroup/resource control settings are available for transient units ✓ StartupAllowedCPUs= ✓ AllowedMemoryNodes= ✓ StartupAllowedMemoryNodes= +✓ CPUSetPartition= ✓ DisableControllers= ✓ Delegate= ✓ MemoryMin= 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/hostname-wordlist/README b/hostname-wordlist/README new file mode 100644 index 0000000000000..0ef3a3fc5fd37 --- /dev/null +++ b/hostname-wordlist/README @@ -0,0 +1,61 @@ +Hostname word lists +==================== + +These files provide the word lists for the "$" wildcard understood by +/etc/hostname (see hostname(5)). The "$" token is positional: the n-th "$" in a +template is replaced by a word from the list file named "n", i.e. the first "$" +uses the file "1", the second "2", and so on. A template such as: + + $-$-$-???? -> wildly-happy-octopus-92a9 + +is expanded deterministically from the machine ID, so a given machine always +gets the same name. + +The numbered files are shipped as symlinks to the semantic lists, so the same +words back both names: + + 1 -> adverbs + 2 -> adjectives + 3 -> nouns + +This keeps the lookup flexible (a deployment can add a "4", "5", … or repoint +the symlinks) while the actual word lists keep meaningful names. + +Files +----- + +Each file is a plain list of words, one per line. Blank lines and lines starting +with "#" are treated as comments and skipped. Each word must be a valid single +hostname label (lowercase letters, digits, hyphens); invalid entries are skipped. +The file is used as-is from the highest-priority directory that provides it (/etc +-> /run -> /usr/local/lib -> /usr/lib); files are not merged across directories. + +Search path (highest priority first): + + /etc/systemd/hostname-wordlist/{1,2,3,...} + /run/systemd/hostname-wordlist/... + /usr/local/lib/systemd/hostname-wordlist/... + /usr/lib/systemd/hostname-wordlist/... + +Caveats +------- + +The word for each token is derived deterministically from the machine ID and +recomputed on every boot; it is not persisted. The position is folded into the +hash, so repeated "$" tokens stay independent even when they resolve to the same +list. Changing a word list may change the name a machine gets. If a referenced +list is missing the name is treated as invalid and the built-in fallback +hostname is used. + +Words are picked uniformly without reading the whole list into memory: an offset +is chosen by hashing and accepted only when it lands on the start of a line +(otherwise another offset is tried), so a word's chance does not depend on its +own length or that of its neighbours. + +Origin +------ + +These are the "small" word lists taken from the petname project +(https://github.com/dustinkirkland/petname), distributed under the Apache +License 2.0. Distributions are encouraged to ship larger lists (petname also +provides "medium" and "large") for a bigger name space. diff --git a/hostname-wordlist/adjectives b/hostname-wordlist/adjectives new file mode 100644 index 0000000000000..bc952f4187cbb --- /dev/null +++ b/hostname-wordlist/adjectives @@ -0,0 +1,449 @@ +able +above +absolute +accepted +accurate +ace +active +actual +adapted +adapting +adequate +adjusted +advanced +alert +alive +allowed +allowing +amazed +amazing +ample +amused +amusing +apparent +apt +arriving +artistic +assured +assuring +awaited +awake +aware +balanced +becoming +beloved +better +big +blessed +bold +boss +brave +brief +bright +bursting +busy +calm +capable +capital +careful +caring +casual +causal +central +certain +champion +charmed +charming +cheerful +chief +choice +civil +classic +clean +clear +clever +climbing +close +closing +coherent +comic +communal +complete +composed +concise +concrete +content +cool +correct +cosmic +crack +creative +credible +crisp +crucial +cuddly +cunning +curious +current +cute +daring +darling +dashing +dear +decent +deciding +deep +definite +delicate +desired +destined +devoted +direct +discrete +distinct +diverse +divine +dominant +driven +driving +dynamic +eager +easy +electric +elegant +emerging +eminent +enabled +enabling +endless +engaged +engaging +enhanced +enjoyed +enormous +enough +epic +equal +equipped +eternal +ethical +evident +evolved +evolving +exact +excited +exciting +exotic +expert +factual +fair +faithful +famous +fancy +fast +feasible +fine +finer +firm +first +fit +fitting +fleet +flexible +flowing +fluent +flying +fond +frank +free +fresh +full +fun +funky +funny +game +generous +gentle +genuine +giving +glad +glorious +glowing +golden +good +gorgeous +grand +grateful +great +growing +grown +guided +guiding +handy +happy +hardy +harmless +healthy +helped +helpful +helping +heroic +hip +holy +honest +hopeful +hot +huge +humane +humble +humorous +ideal +immense +immortal +immune +improved +in +included +infinite +informed +innocent +inspired +integral +intense +intent +internal +intimate +inviting +joint +just +keen +key +kind +knowing +known +large +lasting +leading +learning +legal +legible +lenient +liberal +light +liked +literate +live +living +logical +loved +loving +loyal +lucky +magical +magnetic +main +major +many +massive +master +mature +maximum +measured +meet +merry +mighty +mint +model +modern +modest +moral +more +moved +moving +musical +mutual +national +native +natural +nearby +neat +needed +neutral +new +next +nice +noble +normal +notable +noted +novel +obliging +on +one +open +optimal +optimum +organic +oriented +outgoing +patient +peaceful +perfect +pet +picked +pleasant +pleased +pleasing +poetic +polished +polite +popular +positive +possible +powerful +precious +precise +premium +prepared +present +pretty +primary +prime +pro +probable +profound +promoted +prompt +proper +proud +proven +pumped +pure +quality +quick +quiet +rapid +rare +rational +ready +real +refined +regular +related +relative +relaxed +relaxing +relevant +relieved +renewed +renewing +resolved +rested +rich +right +robust +romantic +ruling +sacred +safe +saved +saving +secure +select +selected +sensible +set +settled +settling +sharing +sharp +shining +simple +sincere +singular +skilled +smart +smashing +smiling +smooth +social +solid +sought +sound +special +splendid +square +stable +star +steady +sterling +still +stirred +stirring +striking +strong +stunning +subtle +suitable +suited +summary +sunny +super +superb +supreme +sure +sweeping +sweet +talented +teaching +tender +thankful +thorough +tidy +tight +together +tolerant +top +topical +tops +touched +touching +tough +true +trusted +trusting +trusty +ultimate +unbiased +uncommon +unified +unique +united +up +upright +upward +usable +useful +valid +valued +vast +verified +viable +vital +vocal +wanted +warm +wealthy +welcome +welcomed +well +whole +willing +winning +wired +wise +witty +wondrous +workable +working +worthy diff --git a/hostname-wordlist/adverbs b/hostname-wordlist/adverbs new file mode 100644 index 0000000000000..25b23068782e9 --- /dev/null +++ b/hostname-wordlist/adverbs @@ -0,0 +1,261 @@ +abnormally +absolutely +accurately +actively +actually +adequately +admittedly +adversely +allegedly +amazingly +annually +apparently +arguably +awfully +badly +barely +basically +blatantly +blindly +briefly +brightly +broadly +carefully +centrally +certainly +cheaply +cleanly +clearly +closely +commonly +completely +constantly +conversely +correctly +curiously +currently +daily +deadly +deeply +definitely +directly +distinctly +duly +eagerly +early +easily +eminently +endlessly +enormously +entirely +equally +especially +evenly +evidently +exactly +explicitly +externally +extremely +factually +fairly +finally +firmly +firstly +forcibly +formally +formerly +frankly +freely +frequently +friendly +fully +generally +gently +genuinely +ghastly +gladly +globally +gradually +gratefully +greatly +grossly +happily +hardly +heartily +heavily +hideously +highly +honestly +hopefully +hopelessly +horribly +hugely +humbly +ideally +illegally +immensely +implicitly +incredibly +indirectly +infinitely +informally +inherently +initially +instantly +intensely +internally +jointly +jolly +kindly +largely +lately +legally +lightly +likely +literally +lively +locally +logically +loosely +loudly +lovely +luckily +mainly +manually +marginally +mentally +merely +mildly +miserably +mistakenly +moderately +monthly +morally +mostly +multiply +mutually +namely +nationally +naturally +nearly +neatly +needlessly +newly +nicely +nominally +normally +notably +noticeably +obviously +oddly +officially +only +openly +optionally +overly +painfully +partially +partly +perfectly +personally +physically +plainly +pleasantly +poorly +positively +possibly +precisely +preferably +presently +presumably +previously +primarily +privately +probably +promptly +properly +publicly +purely +quickly +quietly +radically +randomly +rapidly +rarely +rationally +readily +really +reasonably +recently +regularly +reliably +remarkably +remotely +repeatedly +rightly +roughly +routinely +sadly +safely +scarcely +secondly +secretly +seemingly +sensibly +separately +seriously +severely +sharply +shortly +similarly +simply +sincerely +singularly +slightly +slowly +smoothly +socially +solely +specially +steadily +strangely +strictly +strongly +subtly +suddenly +suitably +supposedly +surely +terminally +terribly +thankfully +thoroughly +tightly +totally +trivially +truly +typically +ultimately +unduly +uniformly +uniquely +unlikely +urgently +usefully +usually +utterly +vaguely +vastly +verbally +vertically +vigorously +violently +virtually +visually +weekly +wholly +widely +wildly +willingly +wrongly +yearly diff --git a/hostname-wordlist/meson.build b/hostname-wordlist/meson.build new file mode 100644 index 0000000000000..65abcb96d7585 --- /dev/null +++ b/hostname-wordlist/meson.build @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +if get_option('hostname-wordlist') + install_data( + 'adverbs', + 'adjectives', + 'nouns', + install_dir : libexecdir / 'hostname-wordlist') + + # The '$' hostname tokens look up word lists by position ("1", "2", "3", …); ship those names as + # symlinks to the semantic lists so the same files back both. + foreach link : [['1', 'adverbs'], ['2', 'adjectives'], ['3', 'nouns']] + install_symlink( + link[0], + install_dir : libexecdir / 'hostname-wordlist', + pointing_to : link[1]) + endforeach +endif diff --git a/hostname-wordlist/nouns b/hostname-wordlist/nouns new file mode 100644 index 0000000000000..dbe898fc3a588 --- /dev/null +++ b/hostname-wordlist/nouns @@ -0,0 +1,449 @@ +ox +ant +ape +asp +bat +bee +boa +bug +cat +cod +cow +cub +doe +dog +eel +eft +elf +elk +emu +ewe +fly +fox +gar +gnu +hen +hog +imp +jay +kid +kit +koi +lab +man +owl +pig +pug +pup +ram +rat +ray +yak +bass +bear +bird +boar +buck +bull +calf +chow +clam +colt +crab +crow +dane +deer +dodo +dory +dove +drum +duck +fawn +fish +flea +foal +fowl +frog +gnat +goat +grub +gull +hare +hawk +ibex +joey +kite +kiwi +lamb +lark +lion +loon +lynx +mako +mink +mite +mole +moth +mule +mutt +newt +orca +oryx +pika +pony +puma +seal +shad +slug +sole +stag +stud +swan +tahr +teal +tick +toad +tuna +wasp +wolf +worm +wren +yeti +adder +akita +alien +aphid +bison +boxer +bream +bunny +burro +camel +chimp +civet +cobra +coral +corgi +crane +dingo +drake +eagle +egret +filly +finch +gator +gecko +ghost +ghoul +goose +guppy +heron +hippo +horse +hound +husky +hyena +koala +krill +leech +lemur +liger +llama +louse +macaw +midge +molly +moose +moray +mouse +panda +perch +prawn +quail +racer +raven +rhino +robin +satyr +shark +sheep +shrew +skink +skunk +sloth +snail +snake +snipe +squid +stork +swift +tapir +tetra +tiger +troll +trout +viper +wahoo +whale +zebra +alpaca +amoeba +baboon +badger +beagle +bedbug +beetle +bengal +bobcat +caiman +cattle +cicada +collie +condor +cougar +coyote +dassie +dragon +earwig +falcon +feline +ferret +gannet +gibbon +glider +goblin +gopher +grouse +guinea +hermit +hornet +iguana +impala +insect +jackal +jaguar +jennet +kitten +kodiak +lizard +locust +maggot +magpie +mammal +mantis +marlin +marmot +marten +martin +mayfly +minnow +monkey +mullet +muskox +ocelot +oriole +osprey +oyster +parrot +pigeon +piglet +poodle +possum +python +quagga +rabbit +raptor +rodent +roughy +salmon +sawfly +serval +shiner +shrimp +spider +sponge +tarpon +thrush +tomcat +toucan +turkey +turtle +urchin +vervet +walrus +weasel +weevil +wombat +anchovy +anemone +bluejay +buffalo +bulldog +buzzard +caribou +catfish +chamois +cheetah +chicken +chigger +cowbird +crappie +crawdad +cricket +dogfish +dolphin +firefly +garfish +gazelle +gelding +giraffe +gobbler +gorilla +goshawk +grackle +griffon +grizzly +grouper +haddock +hagfish +halibut +hamster +herring +javelin +jawfish +jaybird +katydid +ladybug +lamprey +lemming +leopard +lioness +lobster +macaque +mallard +mammoth +manatee +mastiff +meerkat +mollusk +monarch +mongrel +monitor +monster +mudfish +muskrat +mustang +narwhal +oarfish +octopus +opossum +ostrich +panther +pegasus +pelican +penguin +phoenix +piranha +polecat +primate +quetzal +raccoon +rattler +redbird +redfish +reptile +rooster +sawfish +sculpin +seagull +skylark +snapper +spaniel +sparrow +sunbeam +sunbird +sunfish +tadpole +terrier +unicorn +vulture +wallaby +walleye +warthog +whippet +wildcat +aardvark +airedale +albacore +anteater +antelope +arachnid +barnacle +basilisk +blowfish +bluebird +bluegill +bonefish +bullfrog +cardinal +chipmunk +crayfish +dinosaur +doberman +duckling +elephant +escargot +flamingo +flounder +foxhound +glowworm +goldfish +grubworm +hedgehog +honeybee +hookworm +humpback +kangaroo +killdeer +kingfish +labrador +lacewing +ladybird +lionfish +longhorn +mackerel +malamute +marmoset +mastodon +moccasin +mongoose +monkfish +mosquito +pangolin +parakeet +pheasant +pipefish +platypus +polliwog +porpoise +reindeer +ringtail +sailfish +scorpion +seahorse +seasnail +sheepdog +shepherd +silkworm +squirrel +stallion +starfish +starling +stingray +stinkbug +sturgeon +terrapin +titmouse +tortoise +treefrog +werewolf diff --git a/hwdb.d/20-OUI.hwdb b/hwdb.d/20-OUI.hwdb index 0235e11838b22..6cf7f4093b61e 100644 --- a/hwdb.d/20-OUI.hwdb +++ b/hwdb.d/20-OUI.hwdb @@ -2658,7 +2658,7 @@ OUI:000373* ID_OUI_FROM_DATABASE=Aselsan A.S OUI:000374* - ID_OUI_FROM_DATABASE=Control Microsystems + ID_OUI_FROM_DATABASE=Schneider Electric OUI:000375* ID_OUI_FROM_DATABASE=NetMedia, Inc. @@ -2916,7 +2916,7 @@ OUI:0003C9* ID_OUI_FROM_DATABASE=TECOM Co., Ltd. OUI:0003CA* - ID_OUI_FROM_DATABASE=MTS Systems Corp. + ID_OUI_FROM_DATABASE=Temposonics, LLC OUI:0003CB* ID_OUI_FROM_DATABASE=SystemGear Co., Ltd. @@ -3180,7 +3180,7 @@ OUI:000421* ID_OUI_FROM_DATABASE=Ocular Networks OUI:000422* - ID_OUI_FROM_DATABASE=Studio Technologies, Inc + ID_OUI_FROM_DATABASE=Studio Technologies, Inc. OUI:000423* ID_OUI_FROM_DATABASE=Intel Corporation @@ -3945,7 +3945,7 @@ OUI:000520* ID_OUI_FROM_DATABASE=Smartronix, Inc. OUI:000521* - ID_OUI_FROM_DATABASE=Control Microsystems + ID_OUI_FROM_DATABASE=Schneider Electric OUI:000522* ID_OUI_FROM_DATABASE=LEA*D Corporation, Inc. @@ -5883,7 +5883,7 @@ OUI:0007A6* ID_OUI_FROM_DATABASE=Leviton Manufacturing Co., Inc. OUI:0007A7* - ID_OUI_FROM_DATABASE=A-Z Inc. + ID_OUI_FROM_DATABASE=Glory Technical Solutions Co., Ltd. OUI:0007A8* ID_OUI_FROM_DATABASE=Haier Group Technologies Ltd @@ -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 @@ -31487,6 +31487,9 @@ OUI:003C10* OUI:003C84* ID_OUI_FROM_DATABASE=Silicon Laboratories +OUI:003CB7* + ID_OUI_FROM_DATABASE=AzureWave Technology Inc. + OUI:003CC5* ID_OUI_FROM_DATABASE=WONWOO Engineering Co., Ltd @@ -34019,6 +34022,9 @@ OUI:006619* OUI:00664B* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:0066DC* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:006762* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD @@ -34214,6 +34220,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 +34265,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 @@ -35247,7 +35259,7 @@ OUI:00901C* ID_OUI_FROM_DATABASE=mps Software Gmbh OUI:00901D* - ID_OUI_FROM_DATABASE=PEC (NZ) LTD. + ID_OUI_FROM_DATABASE=Gallagher Group Limited OUI:00901E* ID_OUI_FROM_DATABASE=Selesta Ingegneria S.p.A. @@ -36693,7 +36705,7 @@ OUI:00A0E2* ID_OUI_FROM_DATABASE=Keisokugiken Corporation OUI:00A0E3* - ID_OUI_FROM_DATABASE=XKL SYSTEMS CORP. + ID_OUI_FROM_DATABASE=XKL LLC OUI:00A0E4* ID_OUI_FROM_DATABASE=OPTIQUEST @@ -37599,7 +37611,7 @@ OUI:00C095* ID_OUI_FROM_DATABASE=ZNYX Networks, Inc. OUI:00C096* - ID_OUI_FROM_DATABASE=TAMURA CORPORATION + ID_OUI_FROM_DATABASE=Tamu Radiance Corporation OUI:00C097* ID_OUI_FROM_DATABASE=ARCHIPEL SA @@ -37985,6 +37997,9 @@ OUI:00CBB4* OUI:00CBBD* ID_OUI_FROM_DATABASE=Cambridge Broadband Networks Group +OUI:00CC05* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:00CC34* ID_OUI_FROM_DATABASE=Juniper Networks @@ -40064,6 +40079,9 @@ OUI:041B94* OUI:041BBA* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:041C6C* + ID_OUI_FROM_DATABASE=Intel Corporate + OUI:041CDB* ID_OUI_FROM_DATABASE=Siba Service @@ -40109,6 +40127,9 @@ OUI:0422E7* OUI:042322* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:0423A3* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:042405* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -40160,6 +40181,9 @@ OUI:042EC1* OUI:042F56* ID_OUI_FROM_DATABASE=ATOCS (Shenzhen) LTD +OUI:0430FA* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:043110* ID_OUI_FROM_DATABASE=Inspur Group Co., Ltd. @@ -40418,6 +40442,9 @@ OUI:04585DE* OUI:04586F* ID_OUI_FROM_DATABASE=Sichuan Whayer information industry Co.,LTD +OUI:045A3D* + ID_OUI_FROM_DATABASE=Nothing Technology Limited + OUI:045A95* ID_OUI_FROM_DATABASE=Nokia Corporation @@ -40640,6 +40667,9 @@ OUI:047E4A* OUI:047F0E* ID_OUI_FROM_DATABASE=Barrot Technology Co.,LTD +OUI:04801A* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:0480A7* ID_OUI_FROM_DATABASE=ShenZhen TianGang Micro Technology CO.LTD @@ -40892,6 +40922,9 @@ OUI:04B247* OUI:04B3B6* ID_OUI_FROM_DATABASE=Seamap (UK) Ltd +OUI:04B3C9* + ID_OUI_FROM_DATABASE=WNC Corporation + OUI:04B429* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -41013,7 +41046,7 @@ OUI:04C3E63* ID_OUI_FROM_DATABASE=Extech Electronics Co., LTD. OUI:04C3E64* - ID_OUI_FROM_DATABASE=Innovusion Inc. + ID_OUI_FROM_DATABASE=Seyond OUI:04C3E65* ID_OUI_FROM_DATABASE=Invasys @@ -41510,6 +41543,9 @@ OUI:04FA3F* OUI:04FA83* ID_OUI_FROM_DATABASE=Qingdao Haier Technology Co.,Ltd +OUI:04FB66* + ID_OUI_FROM_DATABASE=Ericsson AB + OUI:04FDE8* ID_OUI_FROM_DATABASE=Technoalpin @@ -42122,6 +42158,9 @@ OUI:0823B2* OUI:0823C6* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:08240B* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:082522* ID_OUI_FROM_DATABASE=ADVANSEE @@ -42545,6 +42584,12 @@ OUI:086332E* OUI:086361* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:08638A* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + +OUI:086480* + ID_OUI_FROM_DATABASE=Black Sesame Technologies Co., Ltd + OUI:086518* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -42630,7 +42675,7 @@ OUI:087572* ID_OUI_FROM_DATABASE=Obelux Oy OUI:087618* - ID_OUI_FROM_DATABASE=ViE Technologies Sdn. Bhd. + ID_OUI_FROM_DATABASE=ViTrox Technologies Sdn. Bhd OUI:087671* ID_OUI_FROM_DATABASE=Juniper Networks @@ -42653,6 +42698,9 @@ OUI:087999* OUI:087A4C* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:087B0F* + ID_OUI_FROM_DATABASE=Amazon Technologies Inc. + OUI:087B12* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS @@ -42674,6 +42722,9 @@ OUI:087CBE* OUI:087D21* ID_OUI_FROM_DATABASE=Altasec technology corporation +OUI:087D60* + ID_OUI_FROM_DATABASE=SAMJIN Co.ltd + OUI:087E64* ID_OUI_FROM_DATABASE=Vantiva USA LLC @@ -42878,6 +42929,9 @@ OUI:08ACA5* OUI:08ACC4* ID_OUI_FROM_DATABASE=FMTech +OUI:08AD0A* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:08AED6* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -43028,6 +43082,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 @@ -43118,6 +43175,9 @@ OUI:08DA33E* OUI:08DD03* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +OUI:08DD82* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:08DDEB* ID_OUI_FROM_DATABASE=Silicon Laboratories @@ -43142,6 +43202,9 @@ OUI:08E5DA* OUI:08E63B* ID_OUI_FROM_DATABASE=zte corporation +OUI:08E64B* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:08E672* ID_OUI_FROM_DATABASE=JEBSEE ELECTRONICS CO.,LTD. @@ -43397,6 +43460,9 @@ OUI:0C01DB* OUI:0C0227* ID_OUI_FROM_DATABASE=Vantiva USA LLC +OUI:0C025B* + ID_OUI_FROM_DATABASE=Microchip Technology Inc. + OUI:0C02BD* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -43424,6 +43490,51 @@ OUI:0C0CEA* OUI:0C0E76* ID_OUI_FROM_DATABASE=D-Link International +OUI:0C0EC10* + ID_OUI_FROM_DATABASE=Spintronica LLC + +OUI:0C0EC11* + ID_OUI_FROM_DATABASE=DELTACAST.TV + +OUI:0C0EC12* + ID_OUI_FROM_DATABASE=MTS Systems Corp. + +OUI:0C0EC13* + ID_OUI_FROM_DATABASE=Lupa Tecnologia e Sistemas Ltda + +OUI:0C0EC14* + ID_OUI_FROM_DATABASE=SWANN COMMUNICATIONS PTY LTD + +OUI:0C0EC15* + ID_OUI_FROM_DATABASE=Micron Systems + +OUI:0C0EC16* + ID_OUI_FROM_DATABASE=COGITO TECH COMPANY LIMITED + +OUI:0C0EC17* + ID_OUI_FROM_DATABASE=naext gmbh + +OUI:0C0EC18* + ID_OUI_FROM_DATABASE=BITS AND BYTE IT CONSULTING PVT LTD + +OUI:0C0EC19* + ID_OUI_FROM_DATABASE=GO.N.GO Ltd + +OUI:0C0EC1A* + ID_OUI_FROM_DATABASE=Sonoro Audio GmbH + +OUI:0C0EC1B* + ID_OUI_FROM_DATABASE=tecget GmbH + +OUI:0C0EC1C* + ID_OUI_FROM_DATABASE=Insky Communications Private Limited + +OUI:0C0EC1D* + ID_OUI_FROM_DATABASE=Aerora North America + +OUI:0C0EC1E* + ID_OUI_FROM_DATABASE=t0.technology Inc. + OUI:0C0ECB* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -43457,6 +43568,9 @@ OUI:0C1563* OUI:0C15C5* ID_OUI_FROM_DATABASE=SDTEC Co., Ltd. +OUI:0C166E* + ID_OUI_FROM_DATABASE=WirelessMobility Engineering Centre SDN. BHD + OUI:0C1773* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -43523,6 +43637,9 @@ OUI:0C238D* OUI:0C2576* ID_OUI_FROM_DATABASE=LONGCHEER TELECOMMUNICATION LIMITED +OUI:0C2643* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:0C2724* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -43604,6 +43721,9 @@ OUI:0C37DC* OUI:0C383E* ID_OUI_FROM_DATABASE=Fanvil Technology Co., Ltd. +OUI:0C393D* + ID_OUI_FROM_DATABASE=eero inc. + OUI:0C3956* ID_OUI_FROM_DATABASE=Observator instruments @@ -43742,6 +43862,9 @@ OUI:0C4F9B* OUI:0C5101* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:0C5141* + ID_OUI_FROM_DATABASE=NXP Semiconductors Taiwan Ltd. + OUI:0C517E* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -43763,6 +43886,9 @@ OUI:0C53B7* OUI:0C5415* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:0C5427* + ID_OUI_FROM_DATABASE=Dongguan Huayin Electronic Technology Co., Ltd. + OUI:0C54A5* ID_OUI_FROM_DATABASE=PEGATRON CORPORATION @@ -44963,6 +45089,9 @@ OUI:0CFE5DE* OUI:0CFE7B* ID_OUI_FROM_DATABASE=Vantiva USA LLC +OUI:0CFEE5* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:100000* ID_OUI_FROM_DATABASE=Private @@ -45206,6 +45335,9 @@ OUI:10189E* OUI:101965* ID_OUI_FROM_DATABASE=New H3C Technologies Co., Ltd +OUI:101988* + ID_OUI_FROM_DATABASE=Mother Computer Inc. + OUI:101A92* ID_OUI_FROM_DATABASE=AKEBONO BRAKE INDUSTRY CO.,LTD. @@ -45335,6 +45467,9 @@ OUI:103034* OUI:103047* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:103089* + ID_OUI_FROM_DATABASE=Resideo + OUI:10321D* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -45377,6 +45512,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 +45581,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. @@ -45770,6 +45911,9 @@ OUI:1078CE* OUI:1078D2* ID_OUI_FROM_DATABASE=Elitegroup Computer Systems Co.,Ltd. +OUI:107A2A* + ID_OUI_FROM_DATABASE=Microsoft Corporation + OUI:107A86* ID_OUI_FROM_DATABASE=U&U ENGINEERING INC. @@ -45929,6 +46073,9 @@ OUI:1098C3* OUI:109AB9* ID_OUI_FROM_DATABASE=Tosibox Oy +OUI:109ABA* + ID_OUI_FROM_DATABASE=Intel Corporate + OUI:109ADD* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -45944,6 +46091,9 @@ OUI:109D9C* OUI:109E3A* ID_OUI_FROM_DATABASE=Zhejiang Tmall Technology Co., Ltd. +OUI:109E6B* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:109F41* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -46028,6 +46178,9 @@ OUI:10AEA5* OUI:10AF78* ID_OUI_FROM_DATABASE=Shenzhen ATUE Technology Co., Ltd +OUI:10B06E* + ID_OUI_FROM_DATABASE=Shenzhen Phaten Tech. LTD + OUI:10B1DF* ID_OUI_FROM_DATABASE=CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. @@ -46133,6 +46286,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 +46520,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. @@ -46418,6 +46577,9 @@ OUI:10F068* OUI:10F163* ID_OUI_FROM_DATABASE=TNK CO.,LTD +OUI:10F1C7* + ID_OUI_FROM_DATABASE=Tachyon Networks Inc + OUI:10F1F2* ID_OUI_FROM_DATABASE=LG Electronics (Mobile Communications) @@ -46526,6 +46688,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 @@ -46832,6 +46997,9 @@ OUI:14373B* OUI:14375E* ID_OUI_FROM_DATABASE=Symbotic LLC +OUI:1438FA* + ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company + OUI:14392F* ID_OUI_FROM_DATABASE=LEAR @@ -46859,6 +47027,9 @@ OUI:143E60* OUI:143EBF* ID_OUI_FROM_DATABASE=zte corporation +OUI:143EC2* + ID_OUI_FROM_DATABASE=Intel Corporate + OUI:143F27* ID_OUI_FROM_DATABASE=Noccela Oy @@ -47567,6 +47738,9 @@ OUI:14C67D* OUI:14C697* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +OUI:14C7C4* + ID_OUI_FROM_DATABASE=Zyxel Communications Corporation + OUI:14C88B* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -47624,6 +47798,9 @@ OUI:14D424* OUI:14D4FE* ID_OUI_FROM_DATABASE=Commscope +OUI:14D537* + ID_OUI_FROM_DATABASE=All Inspire Health Inc. + OUI:14D5C6* ID_OUI_FROM_DATABASE=slash dev slash agents, inc @@ -48564,7 +48741,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. @@ -48608,6 +48785,9 @@ OUI:1889DF* OUI:188A6A* ID_OUI_FROM_DATABASE=AVPro Global Hldgs +OUI:188AF1* + ID_OUI_FROM_DATABASE=LEDVANCE, LLC + OUI:188B0E* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -49112,6 +49292,9 @@ OUI:18D071* OUI:18D0C5* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +OUI:18D0E1* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:18D225* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD @@ -49196,6 +49379,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 +49502,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. @@ -49377,7 +49566,7 @@ OUI:18FDCB2* ID_OUI_FROM_DATABASE=Cabtronix AG OUI:18FDCB3* - ID_OUI_FROM_DATABASE=Staclar, Inc. + ID_OUI_FROM_DATABASE=Blahaj Studio OUI:18FDCB4* ID_OUI_FROM_DATABASE=Gosuncn Technology Group Co.,LTD. @@ -49613,6 +49802,9 @@ OUI:1C232C* OUI:1C234F* ID_OUI_FROM_DATABASE=EDMI Europe Ltd +OUI:1C23A2* + ID_OUI_FROM_DATABASE=FRITZ! Technology GmbH + OUI:1C24CD* ID_OUI_FROM_DATABASE=ASKEY COMPUTER CORP @@ -49643,6 +49835,9 @@ OUI:1C2AB0* OUI:1C2CE0* ID_OUI_FROM_DATABASE=Shanghai Mountain View Silicon +OUI:1C2D60* + ID_OUI_FROM_DATABASE=Extreme Networks Headquarters + OUI:1C2E1B* ID_OUI_FROM_DATABASE=Suzhou Tremenet Communication Technology Co., Ltd. @@ -49748,6 +49943,9 @@ OUI:1C4190* OUI:1C427D* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +OUI:1C42C2* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:1C4363* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -49790,6 +49988,9 @@ OUI:1C48F9* OUI:1C497B* ID_OUI_FROM_DATABASE=Gemtek Technology Co., Ltd. +OUI:1C4A3B* + ID_OUI_FROM_DATABASE=ACCTON TECHNOLOGY CORPORATION + OUI:1C4AF7* ID_OUI_FROM_DATABASE=AMON INC @@ -49916,6 +50117,9 @@ OUI:1C5A3E* OUI:1C5A6B* ID_OUI_FROM_DATABASE=Philips Electronics Nederland BV +OUI:1C5BA2* + ID_OUI_FROM_DATABASE=HP GLOBALES MEXICO + OUI:1C5C55* ID_OUI_FROM_DATABASE=PRIMA Cinema, Inc @@ -50738,6 +50942,9 @@ OUI:1CB9C4* OUI:1CBA8C* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:1CBAB8* + ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. + OUI:1CBBA8* ID_OUI_FROM_DATABASE=OJSC Ufimskiy Zavod Promsvyaz @@ -50832,7 +51039,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 @@ -50924,6 +51131,9 @@ OUI:1CD1D7* OUI:1CD1E0* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:1CD21E* + ID_OUI_FROM_DATABASE=Juniper Networks + OUI:1CD3AF* ID_OUI_FROM_DATABASE=LG Innotek @@ -50972,12 +51182,18 @@ 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 OUI:1CE57F* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:1CE587* + ID_OUI_FROM_DATABASE=Cisco Meraki + OUI:1CE61D* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -51240,7 +51456,7 @@ OUI:200CC8* ID_OUI_FROM_DATABASE=NETGEAR OUI:200D3D* - ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co., Ltd. + ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. OUI:200DB0* ID_OUI_FROM_DATABASE=Shenzhen Four Seas Global Link Network Technology Co., Ltd. @@ -51353,6 +51569,9 @@ OUI:201F3B* OUI:201F54* ID_OUI_FROM_DATABASE=Raisecom Technology CO., LTD +OUI:201F55* + ID_OUI_FROM_DATABASE=DJI Osmo Technology Co., Ltd. + OUI:202027* ID_OUI_FROM_DATABASE=Shenzhen Sundray Technologies company Limited @@ -51569,6 +51788,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. @@ -51635,6 +51857,9 @@ OUI:204E7F* OUI:204EF6* ID_OUI_FROM_DATABASE=AzureWave Technology Inc. +OUI:20500D* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:20500F* ID_OUI_FROM_DATABASE=Fiber Groep B.V. @@ -51644,6 +51869,9 @@ OUI:2050E7* OUI:2051F5* ID_OUI_FROM_DATABASE=Earda Technologies co Ltd +OUI:20521D* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:205383* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -51683,6 +51911,9 @@ OUI:205869* OUI:2059A0* ID_OUI_FROM_DATABASE=Paragon Technologies Inc. +OUI:2059D1* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:205A00* ID_OUI_FROM_DATABASE=Coval @@ -51863,6 +52094,9 @@ OUI:2082C0* OUI:2083F8* ID_OUI_FROM_DATABASE=Advanced Digital Broadcast SA +OUI:20845F* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:2084F5* ID_OUI_FROM_DATABASE=Yufei Innovation Software(Shenzhen) Co., Ltd. @@ -51971,6 +52205,9 @@ OUI:209339* OUI:20934D* ID_OUI_FROM_DATABASE=FUJIAN STAR-NET COMMUNICATION CO.,LTD +OUI:209395* + ID_OUI_FROM_DATABASE=nVent + OUI:20968A* ID_OUI_FROM_DATABASE=China Mobile (Hangzhou) Information Technology Co., Ltd. @@ -52031,6 +52268,9 @@ OUI:20A2E4* OUI:20A2E7* ID_OUI_FROM_DATABASE=Lee-Dickens Ltd +OUI:20A366* + ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. + OUI:20A5CB* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -52088,6 +52328,9 @@ OUI:20AC9C* OUI:20AD56* ID_OUI_FROM_DATABASE=AUMOVIO Systems, Inc. +OUI:20AEB6* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:20AF1B* ID_OUI_FROM_DATABASE=SteelSeries ApS @@ -52097,6 +52340,51 @@ OUI:20B001* OUI:20B0F7* ID_OUI_FROM_DATABASE=Enclustra GmbH +OUI:20B37F0* + ID_OUI_FROM_DATABASE=B810 SPA + +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=Shenzhen Hengbang Xinchuang Technology Co.,Ltd + +OUI:20B37FC* + ID_OUI_FROM_DATABASE=EGSTON Power Electronics GmbH + +OUI:20B37FD* + ID_OUI_FROM_DATABASE=Xunmu Information Technology (Shanghai) Co., Ltd. + +OUI:20B37FE* + ID_OUI_FROM_DATABASE=Kawasaki Thermal Engineering Co.,Ltd. + OUI:20B399* ID_OUI_FROM_DATABASE=Enterasys @@ -52340,6 +52628,9 @@ OUI:20E407* OUI:20E46F* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. +OUI:20E525* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:20E52A* ID_OUI_FROM_DATABASE=NETGEAR @@ -52413,7 +52704,7 @@ OUI:20F3A3* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD OUI:20F41B* - ID_OUI_FROM_DATABASE=Shenzhen Bilian electronic CO.,LTD + ID_OUI_FROM_DATABASE=SHENZHEN BILIAN ELECTRONIC CO.,LTD OUI:20F44F* ID_OUI_FROM_DATABASE=Nokia @@ -52658,6 +52949,9 @@ OUI:241A8C* OUI:241AE6* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:241AF7* + ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD + OUI:241B13* ID_OUI_FROM_DATABASE=Shanghai Nutshell Electronic Co., Ltd. @@ -52682,6 +52976,9 @@ OUI:241EEB* OUI:241F2C* ID_OUI_FROM_DATABASE=Calsys, Inc. +OUI:241F49* + ID_OUI_FROM_DATABASE=NXP Semiconductors Taiwan Ltd. + OUI:241FA0* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -52790,6 +53087,9 @@ OUI:243408* OUI:2435CC* ID_OUI_FROM_DATABASE=Zhongshan Scinan Internet of Things Co.,Ltd. +OUI:243672* + ID_OUI_FROM_DATABASE=AMPAK Technology Inc. + OUI:2436DA* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -52928,6 +53228,9 @@ OUI:244E7BD* OUI:244E7BE* ID_OUI_FROM_DATABASE=WithWin Technology ShenZhen CO.,LTD +OUI:244ECD* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:244F1D* ID_OUI_FROM_DATABASE=iRule LLC @@ -53075,6 +53378,9 @@ OUI:2462C6* OUI:2462CE* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise +OUI:246404* + ID_OUI_FROM_DATABASE=GSD VIET NAM TECHNOLOGY COMPANY LIMITED + OUI:246477* ID_OUI_FROM_DATABASE=Beijing Xiaomi Mobile Software Co., Ltd @@ -53090,6 +53396,9 @@ OUI:246511* OUI:2465E1* ID_OUI_FROM_DATABASE=Ciena Corporation +OUI:246800* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:246830* ID_OUI_FROM_DATABASE=Shenzhen Shokzhear Co., Ltd @@ -53219,6 +53528,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 @@ -53591,6 +53903,9 @@ OUI:24BA13* OUI:24BA30* ID_OUI_FROM_DATABASE=Technical Consumer Products, Inc. +OUI:24BA79* + ID_OUI_FROM_DATABASE=New H3C Technologies Co., Ltd + OUI:24BBC1* ID_OUI_FROM_DATABASE=Absolute Analysis @@ -53927,6 +54242,9 @@ OUI:24F68D* OUI:24FAD4* ID_OUI_FROM_DATABASE=ShenZhen More Star Technology Co.,LTD +OUI:24FAD6* + ID_OUI_FROM_DATABASE=EMSTONE + OUI:24FAF3* ID_OUI_FROM_DATABASE=Shanghai Flexem Technology Co.,Ltd. @@ -53978,6 +54296,9 @@ OUI:280245* OUI:2802D8* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:28047A* + ID_OUI_FROM_DATABASE=WNC Corporation + OUI:2804C6* ID_OUI_FROM_DATABASE=Wanan Hongsheng Electronic Co.Ltd @@ -54279,7 +54600,7 @@ OUI:2836136* ID_OUI_FROM_DATABASE=ESI Ventures, LLC OUI:2836137* - ID_OUI_FROM_DATABASE=shenzhen technology limited + ID_OUI_FROM_DATABASE=SHENZHEN OFEIXIN TECHNOLOGY LIMITED OUI:2836138* ID_OUI_FROM_DATABASE=Fuzhou Lesi Intelligent Technology Co., Ltd @@ -54560,6 +54881,9 @@ OUI:286847* OUI:2868D2* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:286926* + ID_OUI_FROM_DATABASE=OPTOKON, a.s. + OUI:286AB8* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -54608,6 +54932,9 @@ OUI:287184* OUI:2872C5* ID_OUI_FROM_DATABASE=Smartmatic Corp +OUI:2872C6* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:2872F0* ID_OUI_FROM_DATABASE=ATHENA @@ -54665,6 +54992,9 @@ OUI:287FCF* OUI:288023* ID_OUI_FROM_DATABASE=Hewlett Packard +OUI:28806E* + ID_OUI_FROM_DATABASE=Pirelli Tyre S.p.A. + OUI:288088* ID_OUI_FROM_DATABASE=NETGEAR @@ -54701,9 +55031,15 @@ 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 +OUI:2887AF* + ID_OUI_FROM_DATABASE=Advantech Technology (CHINA) Co., Ltd. + OUI:2887BA* ID_OUI_FROM_DATABASE=TP-Link Systems Inc @@ -54725,6 +55061,9 @@ OUI:288EEC* OUI:288FF6* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:289104* + ID_OUI_FROM_DATABASE=TP-Link Systems Inc. + OUI:289176* ID_OUI_FROM_DATABASE=Indyme Solutions, LLC @@ -54896,6 +55235,9 @@ OUI:28B371* OUI:28B3AB* ID_OUI_FROM_DATABASE=Genmark Automation +OUI:28B3AF* + ID_OUI_FROM_DATABASE=Qualynxus Inc + OUI:28B446* ID_OUI_FROM_DATABASE=SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD @@ -54971,6 +55313,9 @@ OUI:28BA18* OUI:28BAB5* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:28BAB9* + ID_OUI_FROM_DATABASE=ACME LIGHTING + OUI:28BB59* ID_OUI_FROM_DATABASE=RNET Technologies, Inc. @@ -55311,7 +55656,7 @@ OUI:28F358* ID_OUI_FROM_DATABASE=2C - Trifonov & Co OUI:28F366* - ID_OUI_FROM_DATABASE=Shenzhen Bilian electronic CO.,LTD + ID_OUI_FROM_DATABASE=SHENZHEN BILIAN ELECTRONIC CO.,LTD OUI:28F49B* ID_OUI_FROM_DATABASE=LEETEK @@ -55541,6 +55886,9 @@ OUI:2C0369* OUI:2C0547* ID_OUI_FROM_DATABASE=Shenzhen Phaten Tech. LTD +OUI:2C0613* + ID_OUI_FROM_DATABASE=China Mobile Group Device Co.,Ltd. + OUI:2C0623* ID_OUI_FROM_DATABASE=Win Leader Inc. @@ -55847,6 +56195,9 @@ OUI:2C28B7* OUI:2C2997* ID_OUI_FROM_DATABASE=Microsoft Corporation +OUI:2C2B86* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:2C2BDB* ID_OUI_FROM_DATABASE=eero inc. @@ -55961,6 +56312,9 @@ OUI:2C3F38* OUI:2C3F3E* ID_OUI_FROM_DATABASE=Alge-Timing GmbH +OUI:2C3F87* + ID_OUI_FROM_DATABASE=Private + OUI:2C402B* ID_OUI_FROM_DATABASE=Smart iBlue Technology Limited @@ -56048,6 +56402,9 @@ OUI:2C4881* OUI:2C4A11* ID_OUI_FROM_DATABASE=Ciena Corporation +OUI:2C4B14* + ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD + OUI:2C4C15* ID_OUI_FROM_DATABASE=Juniper Networks @@ -56654,6 +57011,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 +57053,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 @@ -57020,6 +57383,12 @@ OUI:2CE38E* OUI:2CE412* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS +OUI:2CE5BD* + ID_OUI_FROM_DATABASE=Ubiquiti Inc + +OUI:2CE64D* + ID_OUI_FROM_DATABASE=GD Midea Air-Conditioning Equipment Co.,Ltd. + OUI:2CE6CC* ID_OUI_FROM_DATABASE=Ruckus Wireless @@ -57165,7 +57534,16 @@ OUI:30074D* ID_OUI_FROM_DATABASE=SAMSUNG ELECTRO-MECHANICS(THAILAND) OUI:30075C* - ID_OUI_FROM_DATABASE=43403 + ID_OUI_FROM_DATABASE=Netis Technology Co., Ltd. + +OUI:3007A3* + ID_OUI_FROM_DATABASE=Shenzhen Skyworth Digital Technology CO., Ltd + +OUI:30084D* + ID_OUI_FROM_DATABASE=Trumpf Hüttinger + +OUI:300916* + ID_OUI_FROM_DATABASE=Apple, Inc. OUI:3009C0* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company @@ -57314,6 +57692,9 @@ OUI:30144A* OUI:301518* ID_OUI_FROM_DATABASE=Ubiquitous Communication Co. ltd. +OUI:301577* + ID_OUI_FROM_DATABASE=Zyxel Communications Corporation + OUI:30168D* ID_OUI_FROM_DATABASE=ProLon @@ -57344,6 +57725,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 @@ -57711,7 +58095,7 @@ OUI:3049509* ID_OUI_FROM_DATABASE=Shanghai gatang technology CO.,LTD OUI:304950A* - ID_OUI_FROM_DATABASE=Ledworks SRL + ID_OUI_FROM_DATABASE=Illucere Srl OUI:304950B* ID_OUI_FROM_DATABASE=HANGZHOU EV-TECH CO.,LTD @@ -57782,6 +58166,9 @@ OUI:30525A* OUI:3052CB* ID_OUI_FROM_DATABASE=Liteon Technology Corporation +OUI:30535B* + ID_OUI_FROM_DATABASE=Shenzhen Comnect Technology Co.,LTD + OUI:3053C1* ID_OUI_FROM_DATABASE=CRESYN @@ -57848,6 +58235,9 @@ OUI:306118* OUI:3061A2* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:306222* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:30636B* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -57884,6 +58274,9 @@ OUI:306A85* OUI:306CBE* ID_OUI_FROM_DATABASE=Skymotion Technology (HK) Limited +OUI:306D34* + ID_OUI_FROM_DATABASE=Wu Qi Technologies,Inc. + OUI:306DF9* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -58022,6 +58415,9 @@ OUI:3089A6* OUI:3089D3* ID_OUI_FROM_DATABASE=HONGKONG UCLOUDLINK NETWORK TECHNOLOGY LIMITED +OUI:3089EC* + ID_OUI_FROM_DATABASE=Nintendo Co.,Ltd + OUI:308AF7* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -58133,6 +58529,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 @@ -58286,6 +58685,9 @@ OUI:30C7AE* OUI:30C82A* ID_OUI_FROM_DATABASE=WI-BIZ srl +OUI:30C8A2* + ID_OUI_FROM_DATABASE=SHENZHEN TRANSCHAN TECHNOLOGY LIMITED + OUI:30C91B* ID_OUI_FROM_DATABASE=Zhen Shi Information Technology(Shanghai)Co.,Ltd. @@ -58406,6 +58808,9 @@ OUI:30E171* OUI:30E1F1* ID_OUI_FROM_DATABASE=Intelbras +OUI:30E226* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:30E271* ID_OUI_FROM_DATABASE=Fsas Technologies Inc. @@ -58562,6 +58967,9 @@ OUI:30FCEB* OUI:30FD11* ID_OUI_FROM_DATABASE=MACROTECH (USA) INC. +OUI:30FD34* + ID_OUI_FROM_DATABASE=COMMANDO Networks Pvt Ltd. + OUI:30FD38* ID_OUI_FROM_DATABASE=Google, Inc. @@ -58745,6 +59153,9 @@ OUI:34105D* OUI:3410BE* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:3410D0* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:3410F4* ID_OUI_FROM_DATABASE=Silicon Laboratories @@ -58835,6 +59246,9 @@ OUI:3420E3* OUI:342109* ID_OUI_FROM_DATABASE=Jensen Scandinavia AS +OUI:3422CF* + ID_OUI_FROM_DATABASE=AUMOVIO Systems, Inc. + OUI:342387* ID_OUI_FROM_DATABASE=Hon Hai Precision Ind. Co.,Ltd. @@ -58853,6 +59267,9 @@ OUI:3425B4* OUI:3425BE* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:342601* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:342606* ID_OUI_FROM_DATABASE=CarePredict, Inc. @@ -58865,6 +59282,9 @@ OUI:342792* OUI:342840* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:342844* + ID_OUI_FROM_DATABASE=Kyung In Electronics + OUI:342865* ID_OUI_FROM_DATABASE=Juniper Networks @@ -59132,6 +59552,9 @@ OUI:344DF7* OUI:344E2F* ID_OUI_FROM_DATABASE=LEAR +OUI:344EE2* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:344F3F* ID_OUI_FROM_DATABASE=IO-Power Technology Co., Ltd. @@ -59450,6 +59873,9 @@ OUI:34885D* OUI:348A12* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise +OUI:348A3B* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:348A7B* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -59765,6 +60191,9 @@ OUI:34C103* OUI:34C1E9* ID_OUI_FROM_DATABASE=Ulak Communications Inc. +OUI:34C232* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:34C3AC* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -60047,6 +60476,9 @@ OUI:34DD04* OUI:34DD7E* ID_OUI_FROM_DATABASE=Umeox Innovations Co.,Ltd +OUI:34DDCC* + ID_OUI_FROM_DATABASE=Google, Inc. + OUI:34DE1A* ID_OUI_FROM_DATABASE=Intel Corporate @@ -60200,6 +60632,9 @@ OUI:34F015* OUI:34F043* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:34F084* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:34F0CA* ID_OUI_FROM_DATABASE=Shenzhen Linghangyuan Digital Technology Co.,Ltd. @@ -60236,6 +60671,9 @@ OUI:34F716* OUI:34F86E* ID_OUI_FROM_DATABASE=Parker Hannifin Corporation +OUI:34F8DD* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:34F8E7* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -60440,12 +60878,18 @@ OUI:381428* OUI:38144E* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD +OUI:3814A1* + ID_OUI_FROM_DATABASE=LG Innotek + OUI:38165A* ID_OUI_FROM_DATABASE=zte corporation OUI:381672* ID_OUI_FROM_DATABASE=Shenzhen SuperElectron Technology Co.,Ltd. +OUI:3816B3* + ID_OUI_FROM_DATABASE=HP Inc. + OUI:3816D1* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -60623,6 +61067,9 @@ OUI:382B78* OUI:382C4A* ID_OUI_FROM_DATABASE=ASUSTek COMPUTER INC. +OUI:382CDB* + ID_OUI_FROM_DATABASE=Arista Networks + OUI:382CE5* ID_OUI_FROM_DATABASE=Tuya Smart Inc. @@ -60644,6 +61091,9 @@ OUI:38315A* OUI:3831AC* ID_OUI_FROM_DATABASE=WEG +OUI:38327A* + ID_OUI_FROM_DATABASE=Routerboard.com + OUI:3833C5* ID_OUI_FROM_DATABASE=Microsoft Corporation @@ -60659,6 +61109,9 @@ OUI:38384B* OUI:3838A6* ID_OUI_FROM_DATABASE=Arista Networks +OUI:383904* + ID_OUI_FROM_DATABASE=ittim + OUI:38396C* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -60803,6 +61256,9 @@ OUI:384C90* OUI:384DD2* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:384E56* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:384F49* ID_OUI_FROM_DATABASE=Juniper Networks @@ -60926,6 +61382,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. @@ -60941,6 +61400,9 @@ OUI:386EB2* OUI:386F6B* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +OUI:386FF4* + ID_OUI_FROM_DATABASE=Amazon Technologies Inc. + OUI:38700C* ID_OUI_FROM_DATABASE=Commscope @@ -61058,6 +61520,9 @@ OUI:388602* OUI:3886F7* ID_OUI_FROM_DATABASE=Google, Inc. +OUI:38879C* + ID_OUI_FROM_DATABASE=Ei Electronics + OUI:3887D5* ID_OUI_FROM_DATABASE=Intel Corporate @@ -61196,6 +61661,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 +61793,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 @@ -61377,7 +61890,7 @@ OUI:38B3F7* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. OUI:38B4D3* - ID_OUI_FROM_DATABASE=BSH Hausgeraete GmbH + ID_OUI_FROM_DATABASE=BSH Hausgeräte GmbH OUI:38B54D* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -61473,7 +61986,7 @@ OUI:38BD7A* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise OUI:38BEAB* - ID_OUI_FROM_DATABASE=AltoBeam (China) Inc. + ID_OUI_FROM_DATABASE=AltoBeam Inc. OUI:38BF2F* ID_OUI_FROM_DATABASE=Espec Corp. @@ -61487,6 +62000,9 @@ OUI:38C096* OUI:38C0EA* ID_OUI_FROM_DATABASE=Fortinet, Inc. +OUI:38C22D* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:38C2BA* ID_OUI_FROM_DATABASE=CCTV NEOTECH @@ -61595,6 +62111,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 @@ -61706,6 +62225,9 @@ OUI:38F3AB* OUI:38F3FB* ID_OUI_FROM_DATABASE=Asperiq +OUI:38F406* + ID_OUI_FROM_DATABASE=Jinan USR IOT Technology Limited + OUI:38F45E* ID_OUI_FROM_DATABASE=H1-Radio co.,ltd @@ -61880,6 +62402,9 @@ OUI:38FF13* OUI:38FF36* ID_OUI_FROM_DATABASE=Ruckus Wireless +OUI:38FF59* + ID_OUI_FROM_DATABASE=Dell Inc. + OUI:3C01EF* ID_OUI_FROM_DATABASE=Sony Corporation @@ -61997,6 +62522,9 @@ OUI:3C13CC* OUI:3C1512* ID_OUI_FROM_DATABASE=Shenzhen Huanhu Technology Co.,Ltd +OUI:3C155A* + ID_OUI_FROM_DATABASE=Nokia + OUI:3C15C2* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -62073,7 +62601,7 @@ OUI:3C219C* ID_OUI_FROM_DATABASE=Intel Corporate OUI:3C227F* - ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co., Ltd. + ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. OUI:3C22FB* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -62147,6 +62675,9 @@ OUI:3C286D* OUI:3C28A6* ID_OUI_FROM_DATABASE=ALE International +OUI:3C2983* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:3C2AB3* ID_OUI_FROM_DATABASE=Telesystem communications Pte Ltd @@ -62202,7 +62733,7 @@ OUI:3C32B9* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD OUI:3C3300* - ID_OUI_FROM_DATABASE=Shenzhen Bilian electronic CO.,LTD + ID_OUI_FROM_DATABASE=SHENZHEN BILIAN ELECTRONIC CO.,LTD OUI:3C3332* ID_OUI_FROM_DATABASE=D-Link Corporation @@ -62213,6 +62744,9 @@ OUI:3C3464* OUI:3C3556* ID_OUI_FROM_DATABASE=Cognitec Systems GmbH +OUI:3C3558* + ID_OUI_FROM_DATABASE=CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + OUI:3C3576* ID_OUI_FROM_DATABASE=ITEL MOBILE LIMITED @@ -62231,6 +62765,9 @@ OUI:3C3712* OUI:3C3786* ID_OUI_FROM_DATABASE=NETGEAR +OUI:3C381F* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:3C3824* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd @@ -62621,6 +63158,9 @@ OUI:3C6FF7* OUI:3C7059* ID_OUI_FROM_DATABASE=MakerBot Industries +OUI:3C714B* + ID_OUI_FROM_DATABASE=HUMAX NETWORKS + OUI:3C71BF* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -62672,6 +63212,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. @@ -62735,6 +63278,9 @@ OUI:3C8AB0* OUI:3C8AE5* ID_OUI_FROM_DATABASE=Tensun Information Technology(Hangzhou) Co.,LTD +OUI:3C8B6E* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:3C8B7F* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -62843,6 +63389,9 @@ OUI:3C9FC3* OUI:3C9FCD* ID_OUI_FROM_DATABASE=Shenzhen Neoway Technology Co.,Ltd. +OUI:3CA00E* + ID_OUI_FROM_DATABASE=Shenzhen Skyworth Digital Technology CO., Ltd + OUI:3CA067* ID_OUI_FROM_DATABASE=Liteon Technology Corporation @@ -62855,6 +63404,9 @@ OUI:3CA10D* OUI:3CA161* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:3CA239* + ID_OUI_FROM_DATABASE=DGSQ Co.,Ltd + OUI:3CA2C3* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. @@ -62966,6 +63518,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 @@ -63038,6 +63593,9 @@ OUI:3CC683* OUI:3CC786* ID_OUI_FROM_DATABASE=DONGGUAN HUARONG COMMUNICATION TECHNOLOGIES CO.,LTD. +OUI:3CC801* + ID_OUI_FROM_DATABASE=Shenzhen Sundray Technologies company Limited + OUI:3CC99E* ID_OUI_FROM_DATABASE=Huiyang Technology Co., Ltd @@ -63104,6 +63662,9 @@ OUI:3CD1C9* OUI:3CD2E5* ID_OUI_FROM_DATABASE=New H3C Technologies Co., Ltd +OUI:3CD35C* + ID_OUI_FROM_DATABASE=Luna Innovations + OUI:3CD4D6* ID_OUI_FROM_DATABASE=WirelessWERX, Inc @@ -63437,6 +63998,9 @@ OUI:400E67* OUI:400E85* ID_OUI_FROM_DATABASE=SAMSUNG ELECTRO-MECHANICS(THAILAND) +OUI:400EB9* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:400EF3* ID_OUI_FROM_DATABASE=zte corporation @@ -63497,6 +64061,9 @@ OUI:4011C3* OUI:4011DC* ID_OUI_FROM_DATABASE=Sonance +OUI:401277* + ID_OUI_FROM_DATABASE=Microsoft Corporation + OUI:4012E4* ID_OUI_FROM_DATABASE=Compass-EOS @@ -63908,6 +64475,9 @@ OUI:405846* OUI:405899* ID_OUI_FROM_DATABASE=Logitech Far East +OUI:405925* + ID_OUI_FROM_DATABASE=Kaon Broadband CO., LTD. + OUI:405A9B* ID_OUI_FROM_DATABASE=ANOVO @@ -64061,6 +64631,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 @@ -64304,6 +64877,9 @@ OUI:40A5EF* OUI:40A63D* ID_OUI_FROM_DATABASE=SignalFire Telemetry +OUI:40A654* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:40A677* ID_OUI_FROM_DATABASE=Juniper Networks @@ -64391,6 +64967,9 @@ OUI:40B4CD* OUI:40B4F0* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:40B570* + ID_OUI_FROM_DATABASE=Hangzhou Hikvision Digital Technology Co.,Ltd. + OUI:40B5C1* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -64430,6 +65009,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. @@ -64649,6 +65231,9 @@ OUI:40ECF8* OUI:40ED00* ID_OUI_FROM_DATABASE=TP-Link Systems Inc +OUI:40ED7B* + ID_OUI_FROM_DATABASE=Zscaler + OUI:40ED980* ID_OUI_FROM_DATABASE=Tsinghua Tongfang Co., LTD @@ -65033,6 +65618,9 @@ OUI:441D64* OUI:441DB1* ID_OUI_FROM_DATABASE=APTIV SERVICES US, LLC +OUI:441DE5* + ID_OUI_FROM_DATABASE=XCENA Inc. + OUI:441E91* ID_OUI_FROM_DATABASE=ARVIDA Intelligent Electronics Technology Co.,Ltd. @@ -65079,7 +65667,7 @@ OUI:4428A3* ID_OUI_FROM_DATABASE=Jiangsu fulian Communication Technology Co., Ltd. OUI:44291E* - ID_OUI_FROM_DATABASE=AltoBeam (China) Inc. + ID_OUI_FROM_DATABASE=AltoBeam Inc. OUI:442938* ID_OUI_FROM_DATABASE=NietZsche enterprise Co.Ltd. @@ -65102,6 +65690,9 @@ OUI:44303F* OUI:443192* ID_OUI_FROM_DATABASE=Hewlett Packard +OUI:44321D* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:44322A* ID_OUI_FROM_DATABASE=Avaya Inc @@ -65115,7 +65706,7 @@ OUI:4432C8* ID_OUI_FROM_DATABASE=Vantiva USA LLC OUI:44334C* - ID_OUI_FROM_DATABASE=Shenzhen Bilian electronic CO.,LTD + ID_OUI_FROM_DATABASE=SHENZHEN BILIAN ELECTRONIC CO.,LTD OUI:44348F* ID_OUI_FROM_DATABASE=MXT INDUSTRIAL LTDA @@ -65213,6 +65804,9 @@ OUI:44422F* OUI:444450* ID_OUI_FROM_DATABASE=OttoQ +OUI:444520* + ID_OUI_FROM_DATABASE=EM Microelectronic + OUI:44456F* ID_OUI_FROM_DATABASE=SHENZHEN ONEGA TECHNOLOGY CO.,LTD @@ -65246,9 +65840,15 @@ OUI:444963* OUI:444988* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:4449C0* + ID_OUI_FROM_DATABASE=NVIDIA Corporation + 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. @@ -65333,6 +65933,9 @@ OUI:445CE9* OUI:445D5E* ID_OUI_FROM_DATABASE=SHENZHEN Coolkit Technology CO.,LTD +OUI:445E82* + ID_OUI_FROM_DATABASE=Zyxel Communications Corporation + OUI:445ECD* ID_OUI_FROM_DATABASE=Razer Inc @@ -65360,6 +65963,9 @@ OUI:446246* OUI:446370* ID_OUI_FROM_DATABASE=LCFC(Hefei) Electronics Technology co., ltd +OUI:4463B6* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:4463C2* ID_OUI_FROM_DATABASE=Zyxel Communications Corporation @@ -65510,9 +66116,15 @@ 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 +OUI:447B45* + ID_OUI_FROM_DATABASE=Amazon Technologies Inc. + OUI:447BBB* ID_OUI_FROM_DATABASE=Shenzhen YOUHUA Technology Co., Ltd @@ -65588,6 +66200,9 @@ OUI:44896D* OUI:448A5B* ID_OUI_FROM_DATABASE=Micro-Star INT'L CO., LTD. +OUI:448A7F* + ID_OUI_FROM_DATABASE=Murata Manufacturing Co., Ltd. + OUI:448C00* ID_OUI_FROM_DATABASE=Realme Chongqing Mobile Telecommunications Corp.,Ltd. @@ -65618,6 +66233,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 +66266,12 @@ OUI:44962B* OUI:44975A* ID_OUI_FROM_DATABASE=SHENZHEN FAST TECHNOLOGIES CO.,LTD +OUI:44995B* + ID_OUI_FROM_DATABASE=GX India Pvt Ltd + +OUI:449A52* + ID_OUI_FROM_DATABASE=zte corporation + OUI:449B78* ID_OUI_FROM_DATABASE=The Now Factory @@ -65813,6 +66437,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. @@ -65837,6 +66464,9 @@ OUI:44B433* OUI:44B462* ID_OUI_FROM_DATABASE=Flextronics Tech.(Ind) Pvt Ltd +OUI:44B4A0* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:44B4B2* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. @@ -65858,6 +66488,9 @@ OUI:44BA46* OUI:44BB3B* ID_OUI_FROM_DATABASE=Google, Inc. +OUI:44BD8D* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:44BDC8* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd @@ -65924,9 +66557,15 @@ OUI:44CB8B* OUI:44CBAD* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd +OUI:44CC6E* + ID_OUI_FROM_DATABASE=Rockwell Automation + OUI:44CD0E* ID_OUI_FROM_DATABASE=FLEXTRONICS MANUFACTURING(ZHUHAI)CO.,LTD. +OUI:44CE1D* + ID_OUI_FROM_DATABASE=Nokia + OUI:44CE3A* ID_OUI_FROM_DATABASE=Jiangsu Huacun Electronic Technology Co., Ltd. @@ -66155,6 +66794,9 @@ OUI:44EE14* OUI:44EE30* ID_OUI_FROM_DATABASE=Budelmann Elektronik GmbH +OUI:44EF26* + ID_OUI_FROM_DATABASE=Qingdao Intelligent&Precise Electronics Co.,Ltd. + OUI:44EFBF* ID_OUI_FROM_DATABASE=China Dragon Technology Limited @@ -66314,6 +66956,12 @@ OUI:4808EBD* OUI:4808EBE* ID_OUI_FROM_DATABASE=uniline energy systems pvt ltd +OUI:480951* + ID_OUI_FROM_DATABASE=Guangzhou Trustmo Information System Co.,LTD + +OUI:480A28* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:480BB20* ID_OUI_FROM_DATABASE=Ridango AS @@ -66386,6 +67034,9 @@ OUI:48128F* OUI:48137E* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:481389* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:4813F3* ID_OUI_FROM_DATABASE=BBK EDUCATIONAL ELECTRONICS CORP.,LTD. @@ -66971,6 +67622,9 @@ OUI:487D2E* OUI:487E48* ID_OUI_FROM_DATABASE=Earda Technologies co Ltd +OUI:487F80* + ID_OUI_FROM_DATABASE=FRITZ! Technology GmbH + OUI:488002* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -67085,6 +67739,9 @@ OUI:489507* OUI:4896D9* ID_OUI_FROM_DATABASE=zte corporation +OUI:4898AB* + ID_OUI_FROM_DATABASE=Wistron InfoComm(Chongqing)Co.,Ltd. + OUI:4898CA* ID_OUI_FROM_DATABASE=Sichuan AI-Link Technology Co., Ltd. @@ -67196,6 +67853,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 @@ -67487,6 +68147,9 @@ OUI:48E1E9* OUI:48E244* ID_OUI_FROM_DATABASE=Hon Hai Precision Ind. Co.,Ltd. +OUI:48E27E* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:48E2AD* ID_OUI_FROM_DATABASE=HUMAX NETWORKS @@ -67610,6 +68273,9 @@ OUI:48EA62* OUI:48EA63* ID_OUI_FROM_DATABASE=Zhejiang Uniview Technologies Co., Ltd. +OUI:48EAA9* + ID_OUI_FROM_DATABASE=ShenZhen C&D Electronics CO.Ltd. + OUI:48EB30* ID_OUI_FROM_DATABASE=ETERNA TECHNOLOGY, INC. @@ -67761,7 +68427,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 @@ -68015,6 +68681,9 @@ OUI:4C43F6* OUI:4C445B* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:4C4553* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:4C4576* ID_OUI_FROM_DATABASE=China Mobile(Hangzhou) Information Technology Co.,Ltd. @@ -68039,6 +68708,9 @@ OUI:4C496C* OUI:4C49E3* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd +OUI:4C4AB4* + ID_OUI_FROM_DATABASE=Juniper Networks + OUI:4C4B1F* ID_OUI_FROM_DATABASE=CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. @@ -68129,6 +68801,9 @@ OUI:4C53FD* OUI:4C5427* ID_OUI_FROM_DATABASE=Linepro Sp. z o.o. +OUI:4C548B* + ID_OUI_FROM_DATABASE=Cerebras System Inc. + OUI:4C5499* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -68288,9 +68963,57 @@ OUI:4C6BE8* OUI:4C6C13* ID_OUI_FROM_DATABASE=IoT Company Solucoes Tecnologicas Ltda +OUI:4C6CA1* + ID_OUI_FROM_DATABASE=Chipsea Technologies (Shenzhen) Crop. + OUI:4C6D58* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:4C6E440* + ID_OUI_FROM_DATABASE=Quasonix + +OUI:4C6E441* + ID_OUI_FROM_DATABASE=Shenzhen Xmitech Electronic Co.,Ltd + +OUI:4C6E442* + ID_OUI_FROM_DATABASE=Accutrol LLC + +OUI:4C6E443* + ID_OUI_FROM_DATABASE=Qingting Intelligent Technology(Suzhou)Co.,Ltd. + +OUI:4C6E444* + ID_OUI_FROM_DATABASE=Private + +OUI:4C6E445* + ID_OUI_FROM_DATABASE=Shenzhen Langji Guangnian Technology Co., Ltd. + +OUI:4C6E446* + ID_OUI_FROM_DATABASE=Panache DigiLife Limited + +OUI:4C6E447* + ID_OUI_FROM_DATABASE=Luxshare Electronic Technology (KunShan) Ltd + +OUI:4C6E448* + ID_OUI_FROM_DATABASE=Shenzhen iTayga Technology Co.,Ltd + +OUI:4C6E449* + ID_OUI_FROM_DATABASE=Chengdu Ruibitechuang Technology Co.,Ltd + +OUI:4C6E44A* + ID_OUI_FROM_DATABASE=Swistec GmbH + +OUI:4C6E44B* + ID_OUI_FROM_DATABASE=NovaFly LLC + +OUI:4C6E44C* + ID_OUI_FROM_DATABASE=Windar Photonics A/S + +OUI:4C6E44D* + ID_OUI_FROM_DATABASE=1Home Solutions GmbH + +OUI:4C6E44E* + ID_OUI_FROM_DATABASE=Shenzhen Jooan Technology Co., Ltd + OUI:4C6E6E* ID_OUI_FROM_DATABASE=Comnect Technology CO.,LTD @@ -68438,6 +69161,9 @@ OUI:4C8093* OUI:4C80BA* ID_OUI_FROM_DATABASE=Wuhan Tianyu Information Industry Co., Ltd. +OUI:4C80FB* + ID_OUI_FROM_DATABASE=Google, Inc. + OUI:4C8120* ID_OUI_FROM_DATABASE=Taicang T&W Electronics @@ -68642,6 +69368,9 @@ OUI:4C9FF1* OUI:4CA003* ID_OUI_FROM_DATABASE=VITEC +OUI:4CA03D* + ID_OUI_FROM_DATABASE=Bouffalo Lab (Nanjing) Co., Ltd. + OUI:4CA0D4* ID_OUI_FROM_DATABASE=Telink Semiconductor (Shanghai) Co., Ltd. @@ -68936,6 +69665,9 @@ OUI:4CCE2D* OUI:4CCF7C* ID_OUI_FROM_DATABASE=HP Inc. +OUI:4CD012* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:4CD08A* ID_OUI_FROM_DATABASE=HUMAX Co., Ltd. @@ -68978,6 +69710,9 @@ OUI:4CD637* OUI:4CD717* ID_OUI_FROM_DATABASE=Dell Inc. +OUI:4CD73A* + ID_OUI_FROM_DATABASE=ShenZhen XinZhongXin Technology Co., Ltd + OUI:4CD74A* ID_OUI_FROM_DATABASE=Vantiva USA LLC @@ -69080,6 +69815,9 @@ OUI:4CE20F* OUI:4CE2F1* ID_OUI_FROM_DATABASE=Udino srl +OUI:4CE4B6* + ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company + OUI:4CE5AE* ID_OUI_FROM_DATABASE=Tianjin Beebox Intelligent Technology Co.,Ltd. @@ -69170,6 +69908,9 @@ OUI:4CEBD6* OUI:4CEC0F* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:4CECEE* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:4CECEF* ID_OUI_FROM_DATABASE=Soraa, Inc. @@ -69467,6 +70208,9 @@ OUI:5021EC* OUI:502267* ID_OUI_FROM_DATABASE=PixeLINK +OUI:5022C9* + ID_OUI_FROM_DATABASE=Bel Power Solutions, Inc. + OUI:50236D* ID_OUI_FROM_DATABASE=Nintendo Co.,Ltd @@ -69584,6 +70328,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 @@ -69647,6 +70394,9 @@ OUI:503F56* OUI:503F98* ID_OUI_FROM_DATABASE=CMITECH +OUI:504035* + ID_OUI_FROM_DATABASE=Vantiva USA LLC + OUI:504061* ID_OUI_FROM_DATABASE=Nokia @@ -69857,6 +70607,9 @@ OUI:505DAC* OUI:505E24* ID_OUI_FROM_DATABASE=zte corporation +OUI:505E3A* + ID_OUI_FROM_DATABASE=GD Midea Air-Conditioning Equipment Co.,Ltd. + OUI:505E5C* ID_OUI_FROM_DATABASE=SUNITEC TECHNOLOGY CO.,LIMITED @@ -69875,6 +70628,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 +70691,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 +70827,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. @@ -70085,6 +70844,9 @@ OUI:508492* OUI:508569* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:50857C* + ID_OUI_FROM_DATABASE=eero inc. + OUI:50874D* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -70550,6 +71312,9 @@ OUI:50D753* OUI:50DA00* ID_OUI_FROM_DATABASE=Hangzhou H3C Technologies Co., Limited +OUI:50DA9E* + ID_OUI_FROM_DATABASE=SHEN ZHEN TENDA TECHNOLOGY CO.,LTD + OUI:50DAD6* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd @@ -70616,6 +71381,9 @@ OUI:50DE19D* OUI:50DE19E* ID_OUI_FROM_DATABASE=DTEN Inc. +OUI:50DE92* + ID_OUI_FROM_DATABASE=shenzhen worldelite electronics co., LTD + OUI:50DF95* ID_OUI_FROM_DATABASE=Lytx @@ -70646,6 +71414,9 @@ OUI:50E24E* OUI:50E452* ID_OUI_FROM_DATABASE=Chipsea Technologies (Shenzhen) Corp. +OUI:50E467* + ID_OUI_FROM_DATABASE=Ring LLC + OUI:50E478* ID_OUI_FROM_DATABASE=Sichuan AI-Link Technology Co., Ltd. @@ -71051,6 +71822,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 @@ -71060,6 +71834,9 @@ OUI:541473* OUI:5414A7* ID_OUI_FROM_DATABASE=Nanjing Qinheng Microelectronics Co., Ltd. +OUI:5414E9* + ID_OUI_FROM_DATABASE=AltoBeam Inc. + OUI:5414F3* ID_OUI_FROM_DATABASE=Intel Corporate @@ -71252,6 +72029,9 @@ OUI:543ADF* OUI:543B30* ID_OUI_FROM_DATABASE=duagon AG +OUI:543CED* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:543D37* ID_OUI_FROM_DATABASE=Ruckus Wireless @@ -71483,6 +72263,12 @@ OUI:54725E* OUI:54726E* ID_OUI_FROM_DATABASE=Daimler Truck AG +OUI:54735A* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + +OUI:547370* + ID_OUI_FROM_DATABASE=The LEGO Group + OUI:547398* ID_OUI_FROM_DATABASE=Toyo Electronics Corporation @@ -71585,6 +72371,9 @@ OUI:5486BC* OUI:54880E* ID_OUI_FROM_DATABASE=SAMSUNG ELECTRO-MECHANICS(THAILAND) +OUI:5488D5* + ID_OUI_FROM_DATABASE=zte corporation + OUI:5488DE* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -71936,6 +72725,9 @@ OUI:54BAD6* OUI:54BAD9* ID_OUI_FROM_DATABASE=Intelbras +OUI:54BB8F* + ID_OUI_FROM_DATABASE=ACCTON TECHNOLOGY CORPORATION + OUI:54BD79* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -72131,6 +72923,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 @@ -72182,6 +72977,9 @@ OUI:54F15F* OUI:54F201* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:54F283* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:54F294* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -72302,6 +73100,9 @@ OUI:580AD4* OUI:580D0D* ID_OUI_FROM_DATABASE=GREE ELECTRIC APPLIANCES, INC. OF ZHUHAI +OUI:580FA5* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:581031* ID_OUI_FROM_DATABASE=Hon Hai Precision IND.CO.,LTD @@ -72332,6 +73133,9 @@ OUI:58170C* OUI:581862* ID_OUI_FROM_DATABASE=Sony Corporation +OUI:5818B4* + ID_OUI_FROM_DATABASE=Chengdu Quanjing Intelligent Technology Co.,Ltd + OUI:5819F8* ID_OUI_FROM_DATABASE=Commscope @@ -72422,6 +73226,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 +73304,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 @@ -72833,6 +73643,9 @@ OUI:586B14* OUI:586C25* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:586D0C* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:586D67* ID_OUI_FROM_DATABASE=Intel Corporate @@ -73028,6 +73841,9 @@ OUI:588D64* OUI:588E81* ID_OUI_FROM_DATABASE=Silicon Laboratories +OUI:588FA7* + ID_OUI_FROM_DATABASE=LG Innotek + OUI:588FCF* ID_OUI_FROM_DATABASE=Hangzhou Ezviz Software Co.,Ltd. @@ -73040,6 +73856,9 @@ OUI:589153* OUI:5891CF* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:589204* + ID_OUI_FROM_DATABASE=zte corporation + OUI:58920D* ID_OUI_FROM_DATABASE=Kinetic Avionics Limited @@ -73643,6 +74462,9 @@ OUI:58F39C* OUI:58F496* ID_OUI_FROM_DATABASE=Source Chain +OUI:58F658* + ID_OUI_FROM_DATABASE=Edifier International + OUI:58F67B* ID_OUI_FROM_DATABASE=Xia Men UnionCore Technology LTD. @@ -73856,6 +74678,9 @@ OUI:5C167D* OUI:5C16C7* ID_OUI_FROM_DATABASE=Arista Networks +OUI:5C1715* + ID_OUI_FROM_DATABASE=ODrive Robotics + OUI:5C1720* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -73910,6 +74735,9 @@ OUI:5C22DA* OUI:5C2316* ID_OUI_FROM_DATABASE=Squirrels Research Labs LLC +OUI:5C23C2* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:5C241F* ID_OUI_FROM_DATABASE=Qingdao Haier Technology Co.,Ltd @@ -74039,6 +74867,9 @@ OUI:5C4058* OUI:5C4071* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd +OUI:5C4097* + ID_OUI_FROM_DATABASE=ACCTON TECHNOLOGY CORPORATION + OUI:5C40E3* ID_OUI_FROM_DATABASE=NOVAON @@ -74114,6 +74945,9 @@ OUI:5C5181* OUI:5C5188* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company +OUI:5C51DF* + ID_OUI_FROM_DATABASE=eero inc. + OUI:5C521E* ID_OUI_FROM_DATABASE=Nintendo Co.,Ltd @@ -74162,6 +74996,9 @@ OUI:5C58E6* OUI:5C5948* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:5C5A35* + ID_OUI_FROM_DATABASE=eero inc. + OUI:5C5A4C0* ID_OUI_FROM_DATABASE=Jinchuan Group Co.,Ltd @@ -74282,6 +75119,9 @@ OUI:5C5F67* OUI:5C60BA* ID_OUI_FROM_DATABASE=HP Inc. +OUI:5C6117* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:5C6152* ID_OUI_FROM_DATABASE=NXP Semiconductor (Tianjin) LTD. @@ -74294,6 +75134,9 @@ OUI:5C625A* OUI:5C628B* ID_OUI_FROM_DATABASE=TP-Link Systems Inc +OUI:5C63B0* + ID_OUI_FROM_DATABASE=Fortinet, Inc. + OUI:5C63BF* ID_OUI_FROM_DATABASE=TP-LINK TECHNOLOGIES CO.,LTD. @@ -74459,6 +75302,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 @@ -74477,6 +75323,9 @@ OUI:5C843C* OUI:5C8486* ID_OUI_FROM_DATABASE=Brightsource Industries Israel LTD +OUI:5C8505* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:5C857E0* ID_OUI_FROM_DATABASE=28 Gorilla @@ -74553,7 +75402,7 @@ OUI:5C87D81* ID_OUI_FROM_DATABASE=COMET SYSTEM, s.r.o. OUI:5C87D82* - ID_OUI_FROM_DATABASE=Freeus LLC + ID_OUI_FROM_DATABASE=Becklar, LLC OUI:5C87D83* ID_OUI_FROM_DATABASE=Shenzhen Beiens Import and Export Co.,Ltd @@ -74624,6 +75473,9 @@ OUI:5C8D4E* OUI:5C8DE5* ID_OUI_FROM_DATABASE=Delta Electronics, Inc. +OUI:5C8DFD* + ID_OUI_FROM_DATABASE=Clariphotonics CO., Ltd + OUI:5C8E10* ID_OUI_FROM_DATABASE=TimeWatch Infocom Pvt. Ltd. @@ -74741,6 +75593,9 @@ OUI:5CA721* OUI:5CA86A* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:5CA931* + ID_OUI_FROM_DATABASE=Ubee Interactive Co., Limited + OUI:5CA933* ID_OUI_FROM_DATABASE=Luma Home @@ -74789,6 +75644,9 @@ OUI:5CB26D* OUI:5CB29E* ID_OUI_FROM_DATABASE=ASCO Power Technologies +OUI:5CB2DF* + ID_OUI_FROM_DATABASE=Shenzhen Powerleader Storage Technology Co., Ltd. + OUI:5CB395* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -74813,6 +75671,9 @@ OUI:5CB559* OUI:5CB6CC* ID_OUI_FROM_DATABASE=NovaComm Technologies Inc. +OUI:5CB8B7* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:5CB8CB* ID_OUI_FROM_DATABASE=Allis Communications @@ -74825,6 +75686,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 +75843,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 @@ -75039,7 +75903,7 @@ OUI:5CE747* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD OUI:5CE753* - ID_OUI_FROM_DATABASE=Private + ID_OUI_FROM_DATABASE=Shenzhen Intellirocks Tech. Co. Ltd. OUI:5CE7A0* ID_OUI_FROM_DATABASE=Nokia @@ -75218,6 +76082,9 @@ OUI:5CF838E* OUI:5CF8A1* ID_OUI_FROM_DATABASE=Murata Manufacturing Co., Ltd. +OUI:5CF92B* + ID_OUI_FROM_DATABASE=CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + OUI:5CF938* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -75362,6 +76229,9 @@ OUI:601521* OUI:60152B* ID_OUI_FROM_DATABASE=Palo Alto Networks +OUI:60156F* + ID_OUI_FROM_DATABASE=TP-Link Systems Inc. + OUI:6015920* ID_OUI_FROM_DATABASE=S Labs sp. z o.o. @@ -75572,6 +76442,9 @@ OUI:602D74* OUI:602E20* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:602ED5* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:6030B3* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -75746,6 +76619,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 +76949,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. @@ -76571,6 +77450,9 @@ OUI:60DC0D* OUI:60DC81* ID_OUI_FROM_DATABASE=AltoBeam Inc. +OUI:60DD10* + ID_OUI_FROM_DATABASE=Shenzhen Angxun Technology Co.,Ltd + OUI:60DD70* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -76694,6 +77576,9 @@ OUI:60F673* OUI:60F677* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:60F723* + ID_OUI_FROM_DATABASE=Beijing Xiaomi Mobile Software Co., Ltd + OUI:60F81D* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -76919,6 +77804,9 @@ OUI:6420E0* OUI:642184* ID_OUI_FROM_DATABASE=Nippon Denki Kagaku Co.,LTD +OUI:6421FD* + ID_OUI_FROM_DATABASE=Guang zhou Xradio Technology Co., Ltd + OUI:642216* ID_OUI_FROM_DATABASE=Shandong Taixin Electronic co.,Ltd @@ -77468,6 +78356,9 @@ OUI:647791* OUI:647924* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:647999* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:6479A7* ID_OUI_FROM_DATABASE=Phison Electronics Corp. @@ -77492,6 +78383,9 @@ OUI:647C34* OUI:647CE8* ID_OUI_FROM_DATABASE=Palo Alto Networks +OUI:647D4D* + ID_OUI_FROM_DATABASE=Arcadyan Corporation + OUI:647D81* ID_OUI_FROM_DATABASE=YOKOTA INDUSTRIAL CO,.LTD @@ -77558,6 +78452,9 @@ OUI:64956C* OUI:649714* ID_OUI_FROM_DATABASE=eero inc. +OUI:649746* + ID_OUI_FROM_DATABASE=AzureWave Technology Inc. + OUI:649829* ID_OUI_FROM_DATABASE=Integrated Device Technology (Malaysia) Sdn. Bhd. @@ -77609,6 +78506,9 @@ OUI:649D99* OUI:649E31* ID_OUI_FROM_DATABASE=Beijing Xiaomi Mobile Software Co., Ltd +OUI:649E58* + ID_OUI_FROM_DATABASE=MM Devices Pty. Ltd. + OUI:649EF3* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -77678,6 +78578,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 @@ -77771,6 +78674,9 @@ OUI:64BF6B* OUI:64C01A* ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD +OUI:64C045* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:64C17E* ID_OUI_FROM_DATABASE=cheilelectric @@ -77819,6 +78725,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 @@ -77876,6 +78785,9 @@ OUI:64D2C4* OUI:64D315* ID_OUI_FROM_DATABASE=HMD Global Oy +OUI:64D363* + ID_OUI_FROM_DATABASE=Seyond + OUI:64D4BD* ID_OUI_FROM_DATABASE=ALPSALPINE CO,.LTD @@ -78185,6 +79097,9 @@ OUI:68070A* OUI:680715* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:68080D* + ID_OUI_FROM_DATABASE=Shenzhen Yingsheng Technology Co., LTD + OUI:680927* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -78225,7 +79140,7 @@ OUI:681590* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS OUI:6815D3* - ID_OUI_FROM_DATABASE=Zaklady Elektroniki i Mechaniki Precyzyjnej R&G S.A. + ID_OUI_FROM_DATABASE=R&G PLUS Sp. z o.o. OUI:681605* ID_OUI_FROM_DATABASE=Systems And Electronic Development FZCO @@ -78239,6 +79154,9 @@ OUI:6818D9* OUI:68193F* ID_OUI_FROM_DATABASE=Digital Airways +OUI:681977* + ID_OUI_FROM_DATABASE=New H3C Technologies Co., Ltd + OUI:6819AC* ID_OUI_FROM_DATABASE=Guangzhou Xianyou Intelligent Technogoly CO., LTD @@ -78287,6 +79205,9 @@ OUI:68215F* OUI:68228E* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:68229F* + ID_OUI_FROM_DATABASE=Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + OUI:6822E5* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -78350,6 +79271,9 @@ OUI:682D83* OUI:682DDC* ID_OUI_FROM_DATABASE=Wuhan Changjiang Electro-Communication Equipment CO.,LTD +OUI:682E3C* + ID_OUI_FROM_DATABASE=Ubiquiti Inc + OUI:682F67* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -78395,6 +79319,9 @@ OUI:683A1E* OUI:683A48* ID_OUI_FROM_DATABASE=SAMJIN Co., Ltd. +OUI:683B09* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:683B1E* ID_OUI_FROM_DATABASE=Countwise LTD @@ -78464,6 +79391,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 @@ -78647,6 +79577,9 @@ OUI:686E23* OUI:686E48* ID_OUI_FROM_DATABASE=Prophet Electronic Technology Corp.,Ltd +OUI:68709E* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:687161* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -78890,6 +79823,9 @@ OUI:6891D0E* OUI:689234* ID_OUI_FROM_DATABASE=Ruckus Wireless +OUI:689268* + ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company + OUI:689320* ID_OUI_FROM_DATABASE=New H3C Technologies Co., Ltd @@ -78962,6 +79898,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 +80072,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 +80300,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 @@ -79439,6 +80384,9 @@ OUI:68FCB6* OUI:68FCCA* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:68FDE8* + ID_OUI_FROM_DATABASE=Ruckus Wireless + OUI:68FE71* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -79569,7 +80517,7 @@ OUI:6C15243* ID_OUI_FROM_DATABASE=Forcite Helmet Systems Pty Ltd OUI:6C15244* - ID_OUI_FROM_DATABASE=Magicyo Technology CO., LTD. + ID_OUI_FROM_DATABASE=Magicyo Technology CO.,Ltd OUI:6C15245* ID_OUI_FROM_DATABASE=Shenzhen Electron Technology Co., LTD. @@ -79892,6 +80840,9 @@ OUI:6C3C7C* OUI:6C3C8C* ID_OUI_FROM_DATABASE=Dell Inc. +OUI:6C3DD8* + ID_OUI_FROM_DATABASE=Espressif Systems (Singapore) Pte. Ltd + OUI:6C3E51* ID_OUI_FROM_DATABASE=Mindray North America @@ -80072,6 +81023,9 @@ OUI:6C55B1* OUI:6C55E8* ID_OUI_FROM_DATABASE=Vantiva USA LLC +OUI:6C55F6* + ID_OUI_FROM_DATABASE=eero inc. + OUI:6C5640* ID_OUI_FROM_DATABASE=BLU Products Inc @@ -80204,6 +81158,9 @@ OUI:6C6567* OUI:6C67EF* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:6C688A* + ID_OUI_FROM_DATABASE=Amazon Technologies Inc. + OUI:6C68A4* ID_OUI_FROM_DATABASE=Guangzhou V-Solution Telecommunication Technology Co.,Ltd. @@ -80255,6 +81212,9 @@ OUI:6C7220* OUI:6C724A* ID_OUI_FROM_DATABASE=Onkyo Technology K.K. +OUI:6C72B8* + ID_OUI_FROM_DATABASE=LCFC(Hefei) Electronics Technology co., ltd + OUI:6C72E2* ID_OUI_FROM_DATABASE=amitek @@ -80378,6 +81338,9 @@ OUI:6C92BF* OUI:6C92CF* ID_OUI_FROM_DATABASE=Broadcom Limited +OUI:6C92F6* + ID_OUI_FROM_DATABASE=Arista Networks + OUI:6C93080* ID_OUI_FROM_DATABASE=Braums @@ -80423,6 +81386,9 @@ OUI:6C9308D* OUI:6C9308E* ID_OUI_FROM_DATABASE=ANDDORO LLC +OUI:6C9313* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:6C9354* ID_OUI_FROM_DATABASE=Yaojin Technology (Shenzhen) Co., LTD. @@ -80504,6 +81470,9 @@ OUI:6CA4D1* OUI:6CA604* ID_OUI_FROM_DATABASE=Commscope +OUI:6CA613* + ID_OUI_FROM_DATABASE=AltoBeam Inc. + OUI:6CA682* ID_OUI_FROM_DATABASE=EDAM information & communications @@ -80648,6 +81617,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 @@ -80780,6 +81752,9 @@ OUI:6CDDEF* OUI:6CDEA9* ID_OUI_FROM_DATABASE=Cisco Meraki +OUI:6CDFD9* + ID_OUI_FROM_DATABASE=Concept2, Inc. + OUI:6CDFFB0* ID_OUI_FROM_DATABASE=Shenzhen HDCVT Technology @@ -80831,6 +81806,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 @@ -81165,7 +82143,7 @@ OUI:7022FE* ID_OUI_FROM_DATABASE=Apple, Inc. OUI:702393* - ID_OUI_FROM_DATABASE=fos4X GmbH + ID_OUI_FROM_DATABASE=Polytech A/S OUI:702526* ID_OUI_FROM_DATABASE=Nokia @@ -81182,6 +82160,9 @@ OUI:702661* OUI:702804* ID_OUI_FROM_DATABASE=Realme Chongqing Mobile Telecommunications Corp.,Ltd. +OUI:70287D* + ID_OUI_FROM_DATABASE=Google, Inc. + OUI:70288B* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -81206,6 +82187,9 @@ OUI:702C09* OUI:702C1F* ID_OUI_FROM_DATABASE=Wisol +OUI:702D81* + ID_OUI_FROM_DATABASE=Infinix mobility limited + OUI:702D84* ID_OUI_FROM_DATABASE=i4C Innovations @@ -81599,6 +82583,9 @@ OUI:7061EE* OUI:7062B8* ID_OUI_FROM_DATABASE=D-Link International +OUI:7062CB* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:706417* ID_OUI_FROM_DATABASE=ORBIS TECNOLOGIA ELECTRICA S.A. @@ -81713,6 +82700,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 @@ -81734,6 +82724,9 @@ OUI:7072CF* OUI:7072FE* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:70733A* + ID_OUI_FROM_DATABASE=Jiangxi Remote lntelligence Technology Co.,Ltd + OUI:707362* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -81800,6 +82793,9 @@ OUI:707DB9* OUI:707E43* ID_OUI_FROM_DATABASE=Commscope +OUI:707EDA* + ID_OUI_FROM_DATABASE=GSD VIET NAM TECHNOLOGY COMPANY LIMITED + OUI:707EDE* ID_OUI_FROM_DATABASE=NASTEC LTD. @@ -89091,7 +90087,7 @@ OUI:70B3D591D* ID_OUI_FROM_DATABASE=Cubitech OUI:70B3D591E* - ID_OUI_FROM_DATABASE=Creotech Instruments S.A. + ID_OUI_FROM_DATABASE=Creotech Quantum SA OUI:70B3D591F* ID_OUI_FROM_DATABASE=JSC InformInvestGroup @@ -90894,7 +91890,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 @@ -93834,7 +94830,7 @@ OUI:70B3D5F4B* ID_OUI_FROM_DATABASE=Chengdu Lingya Technology Co., Ltd. OUI:70B3D5F4C* - ID_OUI_FROM_DATABASE=PolyTech A/S + ID_OUI_FROM_DATABASE=Polytech A/S OUI:70B3D5F4D* ID_OUI_FROM_DATABASE=Honeywell @@ -94436,6 +95432,9 @@ OUI:70BF3E* OUI:70BF92* ID_OUI_FROM_DATABASE=GN Audio A/S +OUI:70C288* + ID_OUI_FROM_DATABASE=Intel Corporate + OUI:70C59C* ID_OUI_FROM_DATABASE=Silicon Laboratories @@ -94826,6 +95825,12 @@ OUI:7412B3* OUI:7412BB* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD +OUI:741348* + ID_OUI_FROM_DATABASE=Blink by Amazon + +OUI:74136A* + ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company + OUI:7413EA* ID_OUI_FROM_DATABASE=Intel Corporate @@ -94988,6 +95993,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 @@ -95057,6 +96065,9 @@ OUI:742857* OUI:742869* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:7428AE* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:742917* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -95109,7 +96120,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. @@ -95231,6 +96242,9 @@ OUI:743C18* OUI:743C24* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:743CDE* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:743E2B* ID_OUI_FROM_DATABASE=Ruckus Wireless @@ -95418,7 +96432,7 @@ OUI:745D22* ID_OUI_FROM_DATABASE=LCFC(Hefei) Electronics Technology co., ltd OUI:745D43* - ID_OUI_FROM_DATABASE=BSH Hausgeraete GmbH + ID_OUI_FROM_DATABASE=BSH Hausgeräte GmbH OUI:745D68* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD @@ -95669,6 +96683,9 @@ OUI:748A28* OUI:748A69* ID_OUI_FROM_DATABASE=Korea Image Technology Co., Ltd +OUI:748B23* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:748B29* ID_OUI_FROM_DATABASE=Micobiomed @@ -95696,6 +96713,9 @@ OUI:748F3C* OUI:748F4D* ID_OUI_FROM_DATABASE=duagon Germany GmbH +OUI:748FBF* + ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + OUI:748FC2* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -95798,6 +96818,36 @@ OUI:74A02F* OUI:74A063* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:74A2350* + ID_OUI_FROM_DATABASE=Flextronics Technologies India Private Limited + +OUI:74A2351* + ID_OUI_FROM_DATABASE=Efference + +OUI:74A2352* + ID_OUI_FROM_DATABASE=DeepSea Technologies + +OUI:74A2353* + ID_OUI_FROM_DATABASE=SEE Telecom + +OUI:74A2354* + ID_OUI_FROM_DATABASE=Zeroport LTD + +OUI:74A2355* + ID_OUI_FROM_DATABASE=Shenzhen Xhorse3D Technology Co.,LTD. + +OUI:74A2357* + ID_OUI_FROM_DATABASE=Bots Unlimited LLC + +OUI:74A2358* + ID_OUI_FROM_DATABASE=Procode Technology Litd + +OUI:74A235B* + ID_OUI_FROM_DATABASE=Enterprise Software Solutions Lab Pvt Ltd + +OUI:74A235D* + ID_OUI_FROM_DATABASE=Neuromod Devices Ltd. + OUI:74A2E6* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -95834,6 +96884,9 @@ OUI:74A78E* OUI:74A7EA* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:74A981* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:74AB93* ID_OUI_FROM_DATABASE=Blink by Amazon @@ -96026,6 +97079,9 @@ OUI:74D5B8* OUI:74D5C6* ID_OUI_FROM_DATABASE=Microchip Technologies Inc +OUI:74D5E8* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:74D637* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. @@ -96050,6 +97106,9 @@ OUI:74D713* OUI:74D7CA* ID_OUI_FROM_DATABASE=Panasonic Automotive Systems Co.,Ltd +OUI:74D809* + ID_OUI_FROM_DATABASE=Microsoft Corporation + OUI:74D83E* ID_OUI_FROM_DATABASE=Intel Corporate @@ -96065,6 +97124,9 @@ OUI:74D9EB* OUI:74DA38* ID_OUI_FROM_DATABASE=Edimax Technology Co. Ltd. +OUI:74DA78* + ID_OUI_FROM_DATABASE=HP Inc. + OUI:74DA88* ID_OUI_FROM_DATABASE=TP-LINK TECHNOLOGIES CO.,LTD. @@ -96188,6 +97250,9 @@ OUI:74E5F9* OUI:74E60F* ID_OUI_FROM_DATABASE=TECNO MOBILE LIMITED +OUI:74E665* + ID_OUI_FROM_DATABASE=Dynabook Technology (Hangzhou) Inc. + OUI:74E6B8* ID_OUI_FROM_DATABASE=LG Electronics @@ -96275,6 +97340,9 @@ OUI:74F661* OUI:74F67A* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:74F714* + ID_OUI_FROM_DATABASE=Lushare Precision Industry Co.,LTD + OUI:74F726* ID_OUI_FROM_DATABASE=Neuron Robotics @@ -96431,12 +97499,21 @@ 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. OUI:780B8C* ID_OUI_FROM_DATABASE=Private +OUI:780C26* + ID_OUI_FROM_DATABASE=Trafag AG + +OUI:780C48* + ID_OUI_FROM_DATABASE=Hong Kong Yihao Electronic Technology Co., Limited + OUI:780C71* ID_OUI_FROM_DATABASE=Inseego Wireless, Inc @@ -96704,6 +97781,9 @@ OUI:78312B* OUI:7831C1* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:7831C4* + ID_OUI_FROM_DATABASE=Panascais ehf. + OUI:78321B* ID_OUI_FROM_DATABASE=D-Link International @@ -96719,6 +97799,9 @@ OUI:783409* OUI:783486* ID_OUI_FROM_DATABASE=Nokia +OUI:7834B4* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:7834FD* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. @@ -96839,6 +97922,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. @@ -96902,6 +97988,9 @@ OUI:78521A* OUI:785237* ID_OUI_FROM_DATABASE=zte corporation +OUI:785249* + ID_OUI_FROM_DATABASE=Loxone Electronics GmbH + OUI:78524A* ID_OUI_FROM_DATABASE=Optonic GmbH @@ -97064,6 +98153,9 @@ OUI:7864C0* OUI:7864E6* ID_OUI_FROM_DATABASE=Green Motive Technology Limited +OUI:7864F0* + ID_OUI_FROM_DATABASE=Beijing Soynetic Co., Ltd + OUI:78653B* ID_OUI_FROM_DATABASE=Shaoxing Ourten Electronics Co., Ltd. @@ -97106,6 +98198,9 @@ OUI:786A1F* OUI:786A89* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:786BA5* + ID_OUI_FROM_DATABASE=Changchun Jetty Automotive Technology Co., LTD + OUI:786C1C* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -97187,6 +98282,9 @@ OUI:787689* OUI:7876D9* ID_OUI_FROM_DATABASE=EXARA Group +OUI:787826* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:7878350* ID_OUI_FROM_DATABASE=ATsens @@ -97316,6 +98414,9 @@ OUI:788A20* OUI:788A86* ID_OUI_FROM_DATABASE=China Dragon Technology Limited +OUI:788AFB* + ID_OUI_FROM_DATABASE=HANSHOW TECHNOLOGY CO.,LTD. + OUI:788B2A* ID_OUI_FROM_DATABASE=Zhen Shi Information Technology (Shanghai) Co., Ltd. @@ -97376,6 +98477,9 @@ OUI:7895EB* OUI:78960D* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:78966E* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:789682* ID_OUI_FROM_DATABASE=zte corporation @@ -97577,6 +98681,9 @@ OUI:78BAD0* OUI:78BAF9* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:78BB5C* + ID_OUI_FROM_DATABASE=Nokia Solutions and Networks India Private Limited + OUI:78BB88* ID_OUI_FROM_DATABASE=Maxio Technology (Hangzhou) Ltd. @@ -97973,6 +99080,9 @@ OUI:78DEE4* OUI:78DF72* ID_OUI_FROM_DATABASE=Shanghai Imilab Technology Co.Ltd +OUI:78E0C5* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:78E103* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. @@ -98072,6 +99182,9 @@ OUI:78EC22* OUI:78EC74* ID_OUI_FROM_DATABASE=Kyland-USA +OUI:78ECB5* + ID_OUI_FROM_DATABASE=Ruijie Networks Co.,LTD + OUI:78ED25* ID_OUI_FROM_DATABASE=New H3C Technologies Co., Ltd @@ -98222,6 +99335,9 @@ OUI:7C0A50* OUI:7C0BC6* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:7C0C5F* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:7C0C92* ID_OUI_FROM_DATABASE=Suzhou Mobydata Smart System Co.,Ltd. @@ -98261,6 +99377,9 @@ OUI:7C152D* OUI:7C160D* ID_OUI_FROM_DATABASE=Saia-Burgess Controls AG +OUI:7C162A* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:7C1689* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS @@ -98597,8 +99716,14 @@ OUI:7C4E09* OUI:7C4F7D* ID_OUI_FROM_DATABASE=Sawwave +OUI:7C4FAD* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:7C4FB5* - ID_OUI_FROM_DATABASE=Arcadyan Technology Corporation + ID_OUI_FROM_DATABASE=Arcadyan Corporation + +OUI:7C4FCD* + ID_OUI_FROM_DATABASE=Apple, Inc. OUI:7C5049* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -98636,12 +99761,18 @@ OUI:7C574E* OUI:7C5758* ID_OUI_FROM_DATABASE=HP Inc. +OUI:7C59B1* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:7C5A1C* ID_OUI_FROM_DATABASE=Sophos Ltd 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 @@ -98957,6 +100088,9 @@ OUI:7C8334E* OUI:7C8437* ID_OUI_FROM_DATABASE=China Post Communications Equipment Co., Ltd. +OUI:7C852F* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:7C8530* ID_OUI_FROM_DATABASE=Nokia @@ -99005,6 +100139,9 @@ OUI:7C8EE4* OUI:7C8FDE* ID_OUI_FROM_DATABASE=DWnet Technologies(Suzhou) Corporation +OUI:7C90E9* + ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + OUI:7C9122* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -99032,6 +100169,9 @@ OUI:7C9763* OUI:7C97E1* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:7C9824* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:7C992E* ID_OUI_FROM_DATABASE=Shanghai Notion lnformatio Technology Co.,Ltd. @@ -99053,6 +100193,9 @@ OUI:7C9EBD* OUI:7C9F07* ID_OUI_FROM_DATABASE=CIG SHANGHAI CO LTD +OUI:7CA05E* + ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company + OUI:7CA15D* ID_OUI_FROM_DATABASE=GN ReSound A/S @@ -99095,6 +100238,9 @@ OUI:7CA62A* OUI:7CA7B0* ID_OUI_FROM_DATABASE=SHENZHEN BILIAN ELECTRONIC CO.,LTD +OUI:7CA85D* + ID_OUI_FROM_DATABASE=RONGCHEENG GOER TECHNOLOGY CO.,LTD. + OUI:7CA8EC* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise @@ -99503,6 +100649,9 @@ OUI:7CD4A8* OUI:7CD566* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:7CD62C* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:7CD661* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd @@ -99593,6 +100742,9 @@ OUI:7CE712* OUI:7CE87F* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS +OUI:7CE8B1* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:7CE913* ID_OUI_FROM_DATABASE=Fantasia Trading LLC @@ -99935,6 +101087,51 @@ OUI:801970* OUI:8019FE* ID_OUI_FROM_DATABASE=JianLing Technology CO., LTD +OUI:801D0D0* + ID_OUI_FROM_DATABASE=KbDevice,Inc. + +OUI:801D0D1* + ID_OUI_FROM_DATABASE=Hörmann Warnsysteme GmbH + +OUI:801D0D2* + ID_OUI_FROM_DATABASE=SZ Spinning Power Top Boundary Technology Co.Ltd. + +OUI:801D0D3* + ID_OUI_FROM_DATABASE=Lecoo Technology Co.,Ltd. + +OUI:801D0D4* + ID_OUI_FROM_DATABASE=GTL Tecnologia e Sistemas Ltda + +OUI:801D0D5* + ID_OUI_FROM_DATABASE=LONGI METER CO.,LTD. + +OUI:801D0D6* + ID_OUI_FROM_DATABASE=Drowsy Digital Inc + +OUI:801D0D7* + ID_OUI_FROM_DATABASE=HANGZHOU INNOWAVEPOWER ELECTRONIC TECHNOLOGY CO.,LTD + +OUI:801D0D8* + ID_OUI_FROM_DATABASE=CRESTCHIC (UK) LIMITED + +OUI:801D0D9* + ID_OUI_FROM_DATABASE=WARNER ELECTRONICS (I) PVT. LTD. + +OUI:801D0DA* + ID_OUI_FROM_DATABASE=Luxshare Electronic Technology (KunShan) Ltd + +OUI:801D0DB* + ID_OUI_FROM_DATABASE=Syrma SGS Technology + +OUI:801D0DC* + ID_OUI_FROM_DATABASE=LOGICOM SA + +OUI:801D0DD* + ID_OUI_FROM_DATABASE=802 Secure + +OUI:801D0DE* + ID_OUI_FROM_DATABASE=Shanghai ReveISpark Technologies Co.,Ltd. + OUI:801D39* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -100085,6 +101282,9 @@ OUI:803AF4* OUI:803B2A* ID_OUI_FROM_DATABASE=ABB Xiamen Low Voltage Equipment Co.,Ltd. +OUI:803B70* + ID_OUI_FROM_DATABASE=Private + OUI:803B9A* ID_OUI_FROM_DATABASE=ghe-ces electronic ag @@ -100127,6 +101327,9 @@ OUI:80433F* OUI:8044FD* ID_OUI_FROM_DATABASE=China Mobile (Hangzhou) Information Technology Co., Ltd. +OUI:80456B* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:8045DD* ID_OUI_FROM_DATABASE=Intel Corporate @@ -100229,6 +101432,9 @@ OUI:8059FD* OUI:805A04* ID_OUI_FROM_DATABASE=LG Electronics (Mobile Communications) +OUI:805A70* + ID_OUI_FROM_DATABASE=Fortinet, Inc. + OUI:805B65* ID_OUI_FROM_DATABASE=LG Innotek @@ -100334,6 +101540,9 @@ OUI:806D71* OUI:806D97* ID_OUI_FROM_DATABASE=Private +OUI:806DDE* + ID_OUI_FROM_DATABASE=Beken Corporation + OUI:806F1C* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -100643,6 +101852,9 @@ OUI:809733* OUI:80999B* ID_OUI_FROM_DATABASE=Murata Manufacturing Co., Ltd. +OUI:8099CF* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:8099E7* ID_OUI_FROM_DATABASE=Sony Corporation @@ -100679,6 +101891,9 @@ OUI:80A1D7* OUI:80A235* ID_OUI_FROM_DATABASE=Edgecore Networks Corporation +OUI:80A2FC* + ID_OUI_FROM_DATABASE=AzureWave Technology Inc. + OUI:80A5790* ID_OUI_FROM_DATABASE=Benano Inc. @@ -100727,6 +101942,9 @@ OUI:80A579E* OUI:80A589* ID_OUI_FROM_DATABASE=AzureWave Technology Inc. +OUI:80A63C* + ID_OUI_FROM_DATABASE=Amazon Technologies Inc. + OUI:80A796* ID_OUI_FROM_DATABASE=Neuralink Corp. @@ -100964,6 +102182,9 @@ OUI:80D10A* OUI:80D160* ID_OUI_FROM_DATABASE=Integrated Device Technology (Malaysia) Sdn. Bhd. +OUI:80D175* + ID_OUI_FROM_DATABASE=iPanel.TV Inc. + OUI:80D18B* ID_OUI_FROM_DATABASE=Hangzhou I'converge Technology Co.,Ltd @@ -101096,6 +102317,9 @@ OUI:80E869* OUI:80E86F* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:80E8A4* + ID_OUI_FROM_DATABASE=zte corporation + OUI:80E94A* ID_OUI_FROM_DATABASE=LEAPS s.r.o. @@ -101207,6 +102431,9 @@ OUI:8400D2* OUI:840112* ID_OUI_FROM_DATABASE=Kaon Group Co., Ltd. +OUI:84016E* + ID_OUI_FROM_DATABASE=Honor Device Co., Ltd. + OUI:8401A7* ID_OUI_FROM_DATABASE=Greyware Automation Products, Inc @@ -101348,6 +102575,9 @@ OUI:84183A* OUI:841888* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:841985* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:841A24* ID_OUI_FROM_DATABASE=UNIONMAN TECHNOLOGY CO.,LTD @@ -101423,6 +102653,9 @@ OUI:842690* OUI:842712* ID_OUI_FROM_DATABASE=Silicon Laboratories +OUI:842789* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:8427B6* ID_OUI_FROM_DATABASE=China Mobile IOT Company Limited @@ -101672,6 +102905,9 @@ OUI:845733* OUI:845787* ID_OUI_FROM_DATABASE=DVR C&C Co., Ltd. +OUI:8457EB* + ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company + OUI:8457F7* ID_OUI_FROM_DATABASE=Meta Platforms, Inc. @@ -101726,6 +102962,9 @@ OUI:84652B* OUI:846569* ID_OUI_FROM_DATABASE=New H3C Technologies Co., Ltd +OUI:84679A* + ID_OUI_FROM_DATABASE=Arm Ltd + OUI:84683E* ID_OUI_FROM_DATABASE=Intel Corporate @@ -101817,7 +103056,7 @@ OUI:847A88* ID_OUI_FROM_DATABASE=HTC Corporation OUI:847AB6* - ID_OUI_FROM_DATABASE=AltoBeam (China) Inc. + ID_OUI_FROM_DATABASE=AltoBeam Inc. OUI:847ADF* ID_OUI_FROM_DATABASE=FUJIAN STAR-NET COMMUNICATION CO.,LTD @@ -102065,6 +103304,9 @@ OUI:8497B8* OUI:849866* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:8498A7* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:849A40* ID_OUI_FROM_DATABASE=Hangzhou Hikvision Digital Technology Co.,Ltd. @@ -102075,7 +103317,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 +103424,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. @@ -102299,6 +103544,9 @@ OUI:84BE52* OUI:84BE8B* ID_OUI_FROM_DATABASE=Chengdu Geeker Technology Co., Ltd. +OUI:84C065* + ID_OUI_FROM_DATABASE=Nintendo Co.,Ltd + OUI:84C0EF* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -102446,6 +103694,9 @@ OUI:84DBFC* OUI:84DD20* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:84DD84* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:84DDB7* ID_OUI_FROM_DATABASE=Cilag GmbH International @@ -102557,6 +103808,9 @@ OUI:84EAD2* OUI:84EAED* ID_OUI_FROM_DATABASE=Roku, Inc +OUI:84EB0C* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:84EB18* ID_OUI_FROM_DATABASE=Texas Instruments @@ -102680,11 +103934,14 @@ OUI:8801F9* OUI:880264* ID_OUI_FROM_DATABASE=Pascal Audio +OUI:880348* + ID_OUI_FROM_DATABASE=SERCOMM PHILIPPINES INC + 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 @@ -102767,6 +104024,9 @@ OUI:8815C5* OUI:8817A3* ID_OUI_FROM_DATABASE=Integrated Device Technology (Malaysia) Sdn. Bhd. +OUI:8817A8* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:8818AE* ID_OUI_FROM_DATABASE=Tamron Co., Ltd @@ -102837,7 +104097,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. @@ -102846,7 +104106,7 @@ OUI:88263F* ID_OUI_FROM_DATABASE=Zhejiang Uniview Technologies Co.,Ltd. OUI:88287D* - ID_OUI_FROM_DATABASE=AltoBeam (China) Inc. + ID_OUI_FROM_DATABASE=AltoBeam Inc. OUI:8828B3* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -103841,12 +105101,18 @@ OUI:88C174* OUI:88C227* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:88C22D* + ID_OUI_FROM_DATABASE=BUFFALO.INC + OUI:88C242* ID_OUI_FROM_DATABASE=Poynt Co. OUI:88C255* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:88C344* + ID_OUI_FROM_DATABASE=Google, Inc. + OUI:88C36E* ID_OUI_FROM_DATABASE=Beijing Ereneben lnformation Technology Limited @@ -104009,6 +105275,9 @@ OUI:88DA33* OUI:88DA36* ID_OUI_FROM_DATABASE=Calix Inc. +OUI:88DB08* + ID_OUI_FROM_DATABASE=EM Microelectronic + OUI:88DC96* ID_OUI_FROM_DATABASE=EnGenius Technologies, Inc. @@ -104102,6 +105371,9 @@ OUI:88F155* OUI:88F2BD* ID_OUI_FROM_DATABASE=GD Midea Air-Conditioning Equipment Co.,Ltd. +OUI:88F3D5* + ID_OUI_FROM_DATABASE=Zyxel Communications Corporation + OUI:88F488* ID_OUI_FROM_DATABASE=cellon communications technology(shenzhen)Co.,Ltd. @@ -104186,6 +105458,9 @@ OUI:8C0734* OUI:8C078C* ID_OUI_FROM_DATABASE=FLOW DATA INC +OUI:8C083C* + ID_OUI_FROM_DATABASE=EM Microelectronic + OUI:8C0879* ID_OUI_FROM_DATABASE=Texas Instruments @@ -104249,6 +105524,9 @@ OUI:8C12C2* OUI:8C13E2* ID_OUI_FROM_DATABASE=NETLINK ICT +OUI:8C142A* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:8C147D0* ID_OUI_FROM_DATABASE=Nio @@ -104280,7 +105558,7 @@ OUI:8C147D9* ID_OUI_FROM_DATABASE=Anyware Solutions ApS OUI:8C147DA* - ID_OUI_FROM_DATABASE=Bluemega Document & Print Services + ID_OUI_FROM_DATABASE=KPAX OUI:8C147DB* ID_OUI_FROM_DATABASE=Bausch Datacom NV/SA @@ -104312,6 +105590,9 @@ OUI:8C1759* OUI:8C17B6* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:8C1801* + ID_OUI_FROM_DATABASE=zte corporation + OUI:8C1850* ID_OUI_FROM_DATABASE=China Mobile (Hangzhou) Information Technology Co., Ltd. @@ -104453,6 +105734,9 @@ OUI:8C1F64003* OUI:8C1F64006* ID_OUI_FROM_DATABASE=DUNASYS INGENIERIE +OUI:8C1F64007* + ID_OUI_FROM_DATABASE=INTOWN CO., LTD. + OUI:8C1F64009* ID_OUI_FROM_DATABASE=Converging Systems Inc. @@ -104513,6 +105797,9 @@ OUI:8C1F64024* OUI:8C1F64025* ID_OUI_FROM_DATABASE=SMITEC S.p.A. +OUI:8C1F64027* + ID_OUI_FROM_DATABASE=Zhejiang Tengen Electric Co.,Ltd. + OUI:8C1F64028* ID_OUI_FROM_DATABASE=eyrise B.V. @@ -104543,6 +105830,9 @@ OUI:8C1F64035* OUI:8C1F64036* ID_OUI_FROM_DATABASE=Photon Counting Systems LLC +OUI:8C1F64037* + ID_OUI_FROM_DATABASE=ENLESS WIRELESS + OUI:8C1F6403A* ID_OUI_FROM_DATABASE=ORION COMPUTERS @@ -104555,6 +105845,9 @@ OUI:8C1F6403C* OUI:8C1F6403D* ID_OUI_FROM_DATABASE=HORIZON.INC +OUI:8C1F6403E* + ID_OUI_FROM_DATABASE=YUYAMA MFG Co.,Ltd + OUI:8C1F64042* ID_OUI_FROM_DATABASE=HEITEC AG @@ -104702,6 +105995,9 @@ OUI:8C1F64082* OUI:8C1F64083* ID_OUI_FROM_DATABASE=Avionica +OUI:8C1F64084* + ID_OUI_FROM_DATABASE=KST technology + OUI:8C1F64085* ID_OUI_FROM_DATABASE=SORB ENGINEERING LLC @@ -104777,6 +106073,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 +106085,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 @@ -104798,6 +106100,9 @@ OUI:8C1F640AC* OUI:8C1F640AD* ID_OUI_FROM_DATABASE=E2 Nova Corporation +OUI:8C1F640AE* + ID_OUI_FROM_DATABASE=Gogo BA + OUI:8C1F640AF* ID_OUI_FROM_DATABASE=FORSEE POWER @@ -104861,6 +106166,9 @@ OUI:8C1F640CD* OUI:8C1F640CE* ID_OUI_FROM_DATABASE=Digitella Inc. +OUI:8C1F640CF* + ID_OUI_FROM_DATABASE=R2Vision GmbH + OUI:8C1F640D0* ID_OUI_FROM_DATABASE=Lumiplan-Duhamel @@ -104882,6 +106190,9 @@ OUI:8C1F640D6* OUI:8C1F640D8* ID_OUI_FROM_DATABASE=Power Electronics Espana, S.L. +OUI:8C1F640DE* + ID_OUI_FROM_DATABASE=ACE TECH + OUI:8C1F640DF* ID_OUI_FROM_DATABASE=Leidos @@ -105041,6 +106352,9 @@ OUI:8C1F6412D* OUI:8C1F6412E* ID_OUI_FROM_DATABASE=inomatic GmbH +OUI:8C1F6412F* + ID_OUI_FROM_DATABASE=MB connect line GmbH + OUI:8C1F64133* ID_OUI_FROM_DATABASE=Vtron Pty Ltd @@ -105185,12 +106499,21 @@ 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 +OUI:8C1F64182* + ID_OUI_FROM_DATABASE=Private + OUI:8C1F64183* ID_OUI_FROM_DATABASE=NICE Total Cash Management Co., Ltd. +OUI:8C1F64184* + ID_OUI_FROM_DATABASE=GJD Manufacturing + OUI:8C1F64185* ID_OUI_FROM_DATABASE=BIOTAGE GB LTD @@ -105308,6 +106631,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 @@ -105317,6 +106643,9 @@ OUI:8C1F641CA* OUI:8C1F641CB* ID_OUI_FROM_DATABASE=SASYS e.K. +OUI:8C1F641CC* + ID_OUI_FROM_DATABASE=TEX COMPUTER SRL + OUI:8C1F641CE* ID_OUI_FROM_DATABASE=Eiden Co.,Ltd. @@ -105434,6 +106763,9 @@ OUI:8C1F64203* OUI:8C1F64204* ID_OUI_FROM_DATABASE=castcore +OUI:8C1F64205* + ID_OUI_FROM_DATABASE=Anuvu AB + OUI:8C1F64206* ID_OUI_FROM_DATABASE=KRYFS TECHNOLOGIES PRIVATE LIMITED @@ -105458,6 +106790,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 +106940,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. @@ -105677,6 +107015,9 @@ OUI:8C1F6428C* OUI:8C1F6428D* ID_OUI_FROM_DATABASE=AVA Monitoring AB +OUI:8C1F6428F* + ID_OUI_FROM_DATABASE=MDA SatConn UK + OUI:8C1F64290* ID_OUI_FROM_DATABASE=UBIQ TECHNOLOGIES INTERNATIONAL LTD @@ -105833,6 +107174,9 @@ OUI:8C1F642D6* OUI:8C1F642D8* ID_OUI_FROM_DATABASE=CONTROL SYSTEMS Srl +OUI:8C1F642DA* + ID_OUI_FROM_DATABASE=AT-Automation Technology GmbH + OUI:8C1F642DC* ID_OUI_FROM_DATABASE=TimeMachines Inc. @@ -105884,6 +107228,9 @@ OUI:8C1F642F4* OUI:8C1F642F5* ID_OUI_FROM_DATABASE=Florida R&D Associates LLC +OUI:8C1F642F6* + ID_OUI_FROM_DATABASE=Aether Energy Alliance LLC + OUI:8C1F642F7* ID_OUI_FROM_DATABASE=Sealink Technology B.V @@ -105923,6 +107270,9 @@ OUI:8C1F64305* OUI:8C1F64306* ID_OUI_FROM_DATABASE=Corigine,Inc. +OUI:8C1F64308* + ID_OUI_FROM_DATABASE=Eon Instrumentation + OUI:8C1F64309* ID_OUI_FROM_DATABASE=MECT SRL @@ -105941,6 +107291,9 @@ OUI:8C1F6430E* OUI:8C1F6430F* ID_OUI_FROM_DATABASE=EAST PHOTONICS +OUI:8C1F64310* + ID_OUI_FROM_DATABASE=CrossBar, Inc. + OUI:8C1F64313* ID_OUI_FROM_DATABASE=SB-GROUP LTD @@ -105971,6 +107324,9 @@ OUI:8C1F6431D* OUI:8C1F6431F* ID_OUI_FROM_DATABASE=STV Electronic GmbH +OUI:8C1F64320* + ID_OUI_FROM_DATABASE=VITREA Smart Home Technologies Ltd. + OUI:8C1F64324* ID_OUI_FROM_DATABASE=Kinetic Technologies @@ -106016,6 +107372,9 @@ OUI:8C1F6433C* OUI:8C1F6433D* ID_OUI_FROM_DATABASE=ARROW (CHINA) ELECTRONICS TRADING CO., LTD. +OUI:8C1F6433F* + ID_OUI_FROM_DATABASE=Lotec Teknoloji Limited Sirketi + OUI:8C1F64340* ID_OUI_FROM_DATABASE=BRS Sistemas Eletrônicos @@ -106064,6 +107423,9 @@ OUI:8C1F64357* OUI:8C1F64358* ID_OUI_FROM_DATABASE=Denso Manufacturing Tennessee +OUI:8C1F6435A* + ID_OUI_FROM_DATABASE=Korea Electric Vehicle Infra Technology + OUI:8C1F6435C* ID_OUI_FROM_DATABASE=Opgal Optronic Industries ltd @@ -106073,6 +107435,9 @@ OUI:8C1F6435D* OUI:8C1F6435E* ID_OUI_FROM_DATABASE=Test21 Taiwan Corp +OUI:8C1F64360* + ID_OUI_FROM_DATABASE=SPIE Dürr Austria GmbH + OUI:8C1F64362* ID_OUI_FROM_DATABASE=Power Electronics Espana, S.L. @@ -106103,6 +107468,12 @@ OUI:8C1F6436A* OUI:8C1F6436B* ID_OUI_FROM_DATABASE=ViewSonic Corp +OUI:8C1F6436C* + ID_OUI_FROM_DATABASE=Scramble Tools LLC + +OUI:8C1F6436D* + ID_OUI_FROM_DATABASE=WAVEBAND TECHNOLOGIES PVT LIMITED + OUI:8C1F6436E* ID_OUI_FROM_DATABASE=Abbott Diagnostics Technologies AS @@ -106154,6 +107525,9 @@ OUI:8C1F6437F* OUI:8C1F64380* ID_OUI_FROM_DATABASE=YSLAB +OUI:8C1F64381* + ID_OUI_FROM_DATABASE=TimeMachines Inc. + OUI:8C1F64382* ID_OUI_FROM_DATABASE=Shenzhen ROLSTONE Technology Co., Ltd @@ -106220,6 +107594,9 @@ OUI:8C1F6439A* OUI:8C1F6439B* ID_OUI_FROM_DATABASE=Deviceworx Technologies Inc. +OUI:8C1F6439C* + ID_OUI_FROM_DATABASE=HBTECH + OUI:8C1F6439E* ID_OUI_FROM_DATABASE=Abbott Diagnostics Technologies AS @@ -106235,6 +107612,12 @@ OUI:8C1F643A3* OUI:8C1F643A4* ID_OUI_FROM_DATABASE=QLM Technology Ltd +OUI:8C1F643A5* + ID_OUI_FROM_DATABASE=TECHMOVERS SYSTEMS INDIA LIMITED + +OUI:8C1F643A7* + ID_OUI_FROM_DATABASE=FormFactor + OUI:8C1F643AC* ID_OUI_FROM_DATABASE=Benison Tech @@ -106283,6 +107666,9 @@ OUI:8C1F643C1* OUI:8C1F643C2* ID_OUI_FROM_DATABASE=Samuel Cosgrove +OUI:8C1F643C3* + ID_OUI_FROM_DATABASE=Digilube Systems Inc + OUI:8C1F643C4* ID_OUI_FROM_DATABASE=NavSys Technology Inc. @@ -106346,6 +107732,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 @@ -106385,6 +107774,9 @@ OUI:8C1F643F3* OUI:8C1F643F4* ID_OUI_FROM_DATABASE=ACTELSER S.L. +OUI:8C1F643F5* + ID_OUI_FROM_DATABASE=Wuxi Eutron Electronics Technology Co.,Ltd + OUI:8C1F643F6* ID_OUI_FROM_DATABASE=ANTARA TECHNOLOGIES @@ -106430,6 +107822,9 @@ OUI:8C1F6440E* OUI:8C1F64410* ID_OUI_FROM_DATABASE=Roboteq +OUI:8C1F64411* + ID_OUI_FROM_DATABASE=Pioneer Century Infocomm Pte Ltd + OUI:8C1F64412* ID_OUI_FROM_DATABASE=Comercial Electronica Studio-2 s.l. @@ -106487,12 +107882,21 @@ OUI:8C1F6442D* OUI:8C1F6442F* ID_OUI_FROM_DATABASE=Tomorrow Companies Inc +OUI:8C1F64430* + ID_OUI_FROM_DATABASE=Editech Co., Ltd. + +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 +108002,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. @@ -106673,6 +108083,9 @@ OUI:8C1F64490* OUI:8C1F64491* ID_OUI_FROM_DATABASE=Phospec Industries Inc. +OUI:8C1F64492* + ID_OUI_FROM_DATABASE=DEUTA-WERKE GmbH + OUI:8C1F64493* ID_OUI_FROM_DATABASE=Security Products International, LLC @@ -106745,6 +108158,9 @@ OUI:8C1F644AF* OUI:8C1F644B0* ID_OUI_FROM_DATABASE=U -MEI-DAH INT'L ENTERPRISE CO.,LTD. +OUI:8C1F644B1* + ID_OUI_FROM_DATABASE=Real Random + OUI:8C1F644B2* ID_OUI_FROM_DATABASE=GV Technology Co.,Ltd. @@ -106754,12 +108170,21 @@ OUI:8C1F644B3* OUI:8C1F644B4* ID_OUI_FROM_DATABASE=Point One Navigation +OUI:8C1F644B6* + ID_OUI_FROM_DATABASE=Pantherun Technologies Pvt Ltd + +OUI:8C1F644B7* + ID_OUI_FROM_DATABASE=ATTE POWER + OUI:8C1F644B8* ID_OUI_FROM_DATABASE=astTECS Communications Private Limited OUI:8C1F644BB* ID_OUI_FROM_DATABASE=IWS Global Pty Ltd +OUI:8C1F644BD* + ID_OUI_FROM_DATABASE=ZTZ Services International + OUI:8C1F644BF* ID_OUI_FROM_DATABASE=Smart Monitoring Innovations Private Limited @@ -106877,6 +108302,9 @@ OUI:8C1F644FA* OUI:8C1F644FB* ID_OUI_FROM_DATABASE=MESA TECHNOLOGIES LLC +OUI:8C1F644FC* + ID_OUI_FROM_DATABASE=QUBIX SPA + OUI:8C1F64500* ID_OUI_FROM_DATABASE=Nepean Networks Pty Ltd @@ -106952,9 +108380,15 @@ OUI:8C1F64519* OUI:8C1F6451A* ID_OUI_FROM_DATABASE=TELE Haase Steuergeräte Ges.m.b.H +OUI:8C1F6451C* + ID_OUI_FROM_DATABASE=JS Tech Co., Ltd. + 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 @@ -107132,6 +108566,9 @@ OUI:8C1F6457A* OUI:8C1F6457B* ID_OUI_FROM_DATABASE=Potter Electric Signal Co. LLC +OUI:8C1F6457C* + ID_OUI_FROM_DATABASE=EA Elektro-Automatik GmbH + OUI:8C1F6457D* ID_OUI_FROM_DATABASE=ISDI Ltd @@ -107156,6 +108593,9 @@ OUI:8C1F64585* OUI:8C1F64589* ID_OUI_FROM_DATABASE=HVRND +OUI:8C1F6458A* + ID_OUI_FROM_DATABASE=Wacebo Europe Srl + OUI:8C1F6458B* ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. @@ -107165,6 +108605,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 +108644,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 @@ -107219,6 +108665,9 @@ OUI:8C1F645AA* OUI:8C1F645AC* ID_OUI_FROM_DATABASE=YUYAMA MFG Co.,Ltd +OUI:8C1F645AD* + ID_OUI_FROM_DATABASE=TAKAHATA PRECISION Co., LTD. + OUI:8C1F645AE* ID_OUI_FROM_DATABASE=Suzhou Motorcomm Electronic Technology Co., Ltd @@ -107276,6 +108725,9 @@ OUI:8C1F645C6* OUI:8C1F645C9* ID_OUI_FROM_DATABASE=Abbott Diagnostics Technologies AS +OUI:8C1F645CA* + ID_OUI_FROM_DATABASE=Vitaltrace PTY LTD + OUI:8C1F645CB* ID_OUI_FROM_DATABASE=dinosys @@ -107360,6 +108812,9 @@ OUI:8C1F645F1* OUI:8C1F645F2* ID_OUI_FROM_DATABASE=CMC Applied Technology institute +OUI:8C1F645F4* + ID_OUI_FROM_DATABASE=Jiangsu Yi Rong Mstar Technology Ltd. + OUI:8C1F645F5* ID_OUI_FROM_DATABASE=HongSeok Ltd. @@ -107381,6 +108836,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 @@ -107390,6 +108848,9 @@ OUI:8C1F64600* OUI:8C1F64601* ID_OUI_FROM_DATABASE=Camius +OUI:8C1F64602* + ID_OUI_FROM_DATABASE=Power Electronics Espana, S.L. + OUI:8C1F64603* ID_OUI_FROM_DATABASE=Fuku Energy Technology Co., Ltd. @@ -107426,6 +108887,9 @@ OUI:8C1F64610* OUI:8C1F64611* ID_OUI_FROM_DATABASE=Siemens Industry Software Inc. +OUI:8C1F64614* + ID_OUI_FROM_DATABASE=Instawork + OUI:8C1F64616* ID_OUI_FROM_DATABASE=DEUTA Werke GmbH @@ -107483,6 +108947,9 @@ OUI:8C1F6462F* OUI:8C1F64631* ID_OUI_FROM_DATABASE=HAIYANG OLIX CO.,LTD. +OUI:8C1F64633* + ID_OUI_FROM_DATABASE=Gyros Protein Technologies AB + OUI:8C1F64634* ID_OUI_FROM_DATABASE=AML @@ -107507,6 +108974,9 @@ OUI:8C1F6463E* OUI:8C1F6463F* ID_OUI_FROM_DATABASE=PREO INDUSTRIES FAR EAST LTD +OUI:8C1F64640* + ID_OUI_FROM_DATABASE=Transports Publics Genevois + OUI:8C1F64641* ID_OUI_FROM_DATABASE=biosilver .co.,ltd @@ -107519,6 +108989,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 +109052,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 @@ -107753,6 +109229,9 @@ OUI:8C1F646B9* OUI:8C1F646BB* ID_OUI_FROM_DATABASE=Season Electronics Ltd +OUI:8C1F646BC* + ID_OUI_FROM_DATABASE=Harik Tech + OUI:8C1F646BD* ID_OUI_FROM_DATABASE=IoT Water Analytics S.L. @@ -107771,6 +109250,9 @@ OUI:8C1F646C4* OUI:8C1F646C6* ID_OUI_FROM_DATABASE=FIT +OUI:8C1F646C7* + ID_OUI_FROM_DATABASE=Pionierkraft GmbH + OUI:8C1F646C8* ID_OUI_FROM_DATABASE=Taiko Audio B.V. @@ -107864,9 +109346,15 @@ OUI:8C1F646ED* OUI:8C1F646EE* ID_OUI_FROM_DATABASE=Cortical Labs Pte Ltd +OUI:8C1F646F0* + ID_OUI_FROM_DATABASE=Right Time Sports LLC + OUI:8C1F646F4* ID_OUI_FROM_DATABASE=Elsist Srl +OUI:8C1F646F5* + ID_OUI_FROM_DATABASE=Logic Fruit Technologies Pvt. Ltd. + OUI:8C1F646F7* ID_OUI_FROM_DATABASE=EddyWorks Co.,Ltd @@ -107885,6 +109373,9 @@ OUI:8C1F646FC* OUI:8C1F64700* ID_OUI_FROM_DATABASE=QUANTAFLOW +OUI:8C1F64701* + ID_OUI_FROM_DATABASE=Wilton Arthur Poth + OUI:8C1F64702* ID_OUI_FROM_DATABASE=AIDirections @@ -108035,6 +109526,9 @@ OUI:8C1F64749* OUI:8C1F6474B* ID_OUI_FROM_DATABASE=AR Modular RF +OUI:8C1F6474D* + ID_OUI_FROM_DATABASE=IDUN Technologies AG + OUI:8C1F6474E* ID_OUI_FROM_DATABASE=OpenPark Technologies Kft @@ -108146,6 +109640,9 @@ OUI:8C1F6477B* OUI:8C1F6477C* ID_OUI_FROM_DATABASE=Orange Tree Technologies Ltd +OUI:8C1F6477D* + ID_OUI_FROM_DATABASE=Event Acoustics B.V. + OUI:8C1F6477E* ID_OUI_FROM_DATABASE=Institute of geophysics, China earthquake administration @@ -108182,6 +109679,9 @@ OUI:8C1F6478F* OUI:8C1F64791* ID_OUI_FROM_DATABASE=Otis Technology and Development(Shanghai) Co., Ltd. +OUI:8C1F64792* + ID_OUI_FROM_DATABASE=MDS TECH + OUI:8C1F64793* ID_OUI_FROM_DATABASE=Aditec GmbH @@ -108242,6 +109742,9 @@ OUI:8C1F647B0* OUI:8C1F647B1* ID_OUI_FROM_DATABASE=EA Elektro-Automatik GmbH +OUI:8C1F647B2* + ID_OUI_FROM_DATABASE=Neosem Inc. + OUI:8C1F647B5* ID_OUI_FROM_DATABASE=Guan Show Technologe Co., Ltd. @@ -108356,6 +109859,9 @@ OUI:8C1F647E7* OUI:8C1F647E8* ID_OUI_FROM_DATABASE=EA Elektro-Automatik GmbH +OUI:8C1F647EA* + ID_OUI_FROM_DATABASE=Abbott Diagnostics Technologies AS + OUI:8C1F647EC* ID_OUI_FROM_DATABASE=Methods2Business B.V. @@ -108383,6 +109889,9 @@ OUI:8C1F647F4* OUI:8C1F647F6* ID_OUI_FROM_DATABASE=Abbott Diagnostics Technologies AS +OUI:8C1F647F7* + ID_OUI_FROM_DATABASE=ADITUS GmbH + OUI:8C1F647F8* ID_OUI_FROM_DATABASE=FleetSafe India Private Limited @@ -108413,6 +109922,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 @@ -108473,6 +109985,9 @@ OUI:8C1F64820* OUI:8C1F64822* ID_OUI_FROM_DATABASE=IP Devices +OUI:8C1F64823* + ID_OUI_FROM_DATABASE=Lumiplan Duhamel + OUI:8C1F64824* ID_OUI_FROM_DATABASE=LOGICUBE INC @@ -108536,6 +110051,9 @@ OUI:8C1F64843* OUI:8C1F64846* ID_OUI_FROM_DATABASE=Fo Xie Optoelectronics Technology Co., Ltd +OUI:8C1F64847* + ID_OUI_FROM_DATABASE=SLAT + OUI:8C1F64848* ID_OUI_FROM_DATABASE=Jena-Optronik GmbH @@ -108545,6 +110063,9 @@ OUI:8C1F64849* OUI:8C1F6484A* ID_OUI_FROM_DATABASE=Bitmapper Integration Technologies Private Limited +OUI:8C1F6484B* + ID_OUI_FROM_DATABASE=Unlimited Bandwidth LLC + OUI:8C1F6484C* ID_OUI_FROM_DATABASE=AvMap srlu @@ -108683,6 +110204,9 @@ OUI:8C1F64891* OUI:8C1F64892* ID_OUI_FROM_DATABASE=MDI Industrial +OUI:8C1F64893* + ID_OUI_FROM_DATABASE=Portrait Displays, Inc. + OUI:8C1F64895* ID_OUI_FROM_DATABASE=Dacom West GmbH @@ -108692,9 +110216,15 @@ OUI:8C1F64898* OUI:8C1F64899* ID_OUI_FROM_DATABASE=American Edge IP +OUI:8C1F6489A* + ID_OUI_FROM_DATABASE=National Control Devices, LLC + OUI:8C1F6489E* ID_OUI_FROM_DATABASE=Cinetix Srl +OUI:8C1F6489F* + ID_OUI_FROM_DATABASE=Potter Electric Signal Co LLC + OUI:8C1F648A0* ID_OUI_FROM_DATABASE=H&abyz @@ -108734,6 +110264,9 @@ OUI:8C1F648B2* OUI:8C1F648B3* ID_OUI_FROM_DATABASE=Hubbell Power Systems +OUI:8C1F648B4* + ID_OUI_FROM_DATABASE=MYIR Electronics Limited + OUI:8C1F648B5* ID_OUI_FROM_DATABASE=Ashton Bentley Collaboration Spaces @@ -108749,6 +110282,9 @@ OUI:8C1F648B8* OUI:8C1F648B9* ID_OUI_FROM_DATABASE=Zynex Monitoring Solutions +OUI:8C1F648BB* + ID_OUI_FROM_DATABASE=swiss electronic creation GmbH + OUI:8C1F648BC* ID_OUI_FROM_DATABASE=Peter Huber Kaeltemaschinenbau SE @@ -108854,9 +110390,15 @@ OUI:8C1F648ED* OUI:8C1F648EE* ID_OUI_FROM_DATABASE=Abbott Diagnostics Technologies AS +OUI:8C1F648EF* + ID_OUI_FROM_DATABASE=Osec + OUI:8C1F648F0* ID_OUI_FROM_DATABASE=IGL +OUI:8C1F648F3* + ID_OUI_FROM_DATABASE=EverBot Technology CO., LTD + OUI:8C1F648F4* ID_OUI_FROM_DATABASE=Loadrite (Auckland) Limited @@ -108896,6 +110438,9 @@ OUI:8C1F64904* OUI:8C1F64905* ID_OUI_FROM_DATABASE=Qualitrol LLC +OUI:8C1F64906* + ID_OUI_FROM_DATABASE=YUYAMA MFG Co.,Ltd + OUI:8C1F64907* ID_OUI_FROM_DATABASE=Sicon srl @@ -108974,6 +110519,9 @@ OUI:8C1F6492A* OUI:8C1F6492D* ID_OUI_FROM_DATABASE=IVOR Intelligent Electrical Appliance Co., Ltd +OUI:8C1F64930* + ID_OUI_FROM_DATABASE=Wuhan HYAIEV (华异) Technology Co., Ltd + OUI:8C1F64931* ID_OUI_FROM_DATABASE=Noptel Oy @@ -109034,6 +110582,9 @@ OUI:8C1F6494F* OUI:8C1F64953* ID_OUI_FROM_DATABASE=VAF Instruments BV +OUI:8C1F64955* + ID_OUI_FROM_DATABASE=Talleres de Escoriaza SAU + OUI:8C1F64956* ID_OUI_FROM_DATABASE=Paulmann Licht GmbH @@ -109082,6 +110633,9 @@ OUI:8C1F6496A* OUI:8C1F6496B* ID_OUI_FROM_DATABASE=Vantageo Private Limited +OUI:8C1F6496D* + ID_OUI_FROM_DATABASE=Sörgel Elektronik + OUI:8C1F6496E* ID_OUI_FROM_DATABASE=inomatic GmbH @@ -109100,6 +110654,15 @@ OUI:8C1F64972* OUI:8C1F64973* ID_OUI_FROM_DATABASE=Dorsett Technologies Inc +OUI:8C1F64974* + ID_OUI_FROM_DATABASE=REO AG + +OUI:8C1F64975* + ID_OUI_FROM_DATABASE=Preston Industries dba PolyScience + +OUI:8C1F64976* + ID_OUI_FROM_DATABASE=JES Electronic Systems Private Limited + OUI:8C1F64978* ID_OUI_FROM_DATABASE=Planet Innovation Products Inc. @@ -109145,6 +110708,9 @@ OUI:8C1F6498C* OUI:8C1F6498D* ID_OUI_FROM_DATABASE=Aksel sp. z o.o. +OUI:8C1F6498E* + ID_OUI_FROM_DATABASE=Rational Production srl Unipersonale + OUI:8C1F6498F* ID_OUI_FROM_DATABASE=Breas Medical AB @@ -109157,6 +110723,9 @@ OUI:8C1F64993* OUI:8C1F64994* ID_OUI_FROM_DATABASE=uHave Control, Inc +OUI:8C1F64997* + ID_OUI_FROM_DATABASE=Daifuku CO., Ltd. + OUI:8C1F64998* ID_OUI_FROM_DATABASE=EVLO Stockage Énergie @@ -109172,6 +110741,9 @@ OUI:8C1F6499C* OUI:8C1F6499E* ID_OUI_FROM_DATABASE=EIDOS s.r.l. +OUI:8C1F649A0* + ID_OUI_FROM_DATABASE=NEOX Networks + OUI:8C1F649A1* ID_OUI_FROM_DATABASE=Pacific Software Development Co., Ltd. @@ -109196,6 +110768,9 @@ OUI:8C1F649A7* OUI:8C1F649A9* ID_OUI_FROM_DATABASE=TIAMA +OUI:8C1F649AA* + ID_OUI_FROM_DATABASE=xTools Inc. + OUI:8C1F649AB* ID_OUI_FROM_DATABASE=DAVE SRL @@ -109319,6 +110894,9 @@ OUI:8C1F649EA* OUI:8C1F649EC* ID_OUI_FROM_DATABASE=Specialized Communications Corp. +OUI:8C1F649EE* + ID_OUI_FROM_DATABASE=Möbus Engineering GmbH + OUI:8C1F649F0* ID_OUI_FROM_DATABASE=ePlant, Inc. @@ -109424,6 +111002,9 @@ OUI:8C1F64A1F* OUI:8C1F64A20* ID_OUI_FROM_DATABASE=Intenseye Inc. +OUI:8C1F64A25* + ID_OUI_FROM_DATABASE=Potter Electric Signal Company + OUI:8C1F64A26* ID_OUI_FROM_DATABASE=Automatic Pty Ltd @@ -109487,6 +111068,9 @@ OUI:8C1F64A3E* OUI:8C1F64A3F* ID_OUI_FROM_DATABASE=ViewSonic Corp +OUI:8C1F64A41* + ID_OUI_FROM_DATABASE=Guan Show Technologe Co., Ltd. + OUI:8C1F64A42* ID_OUI_FROM_DATABASE=Rodgers Instruments US LLC @@ -109559,6 +111143,9 @@ OUI:8C1F64A67* OUI:8C1F64A6A* ID_OUI_FROM_DATABASE=Sphere Com Services Pvt Ltd +OUI:8C1F64A6C* + ID_OUI_FROM_DATABASE=Arctic Instruments Oy + OUI:8C1F64A6D* ID_OUI_FROM_DATABASE=CyberneX Co., Ltd @@ -109658,6 +111245,9 @@ OUI:8C1F64A9D* OUI:8C1F64A9E* ID_OUI_FROM_DATABASE=Optimum Instruments Inc. +OUI:8C1F64A9F* + ID_OUI_FROM_DATABASE=DADHWAL AI PRIVATE LIMITED + OUI:8C1F64AA0* ID_OUI_FROM_DATABASE=Flextronics International Kft @@ -109709,6 +111299,9 @@ OUI:8C1F64AB7* OUI:8C1F64AB8* ID_OUI_FROM_DATABASE=Private +OUI:8C1F64ABA* + ID_OUI_FROM_DATABASE=FLUGCOM GmbH + OUI:8C1F64ABE* ID_OUI_FROM_DATABASE=TAIYO DENON Corporation @@ -109730,6 +111323,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,9 +111341,15 @@ 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 +OUI:8C1F64AD1* + ID_OUI_FROM_DATABASE=Hatteland Technology AS + OUI:8C1F64AD2* ID_OUI_FROM_DATABASE=YUYAMA MFG Co.,Ltd @@ -109769,6 +111371,9 @@ OUI:8C1F64AD8* OUI:8C1F64AD9* ID_OUI_FROM_DATABASE=Vision Systems Safety Tech +OUI:8C1F64ADA* + ID_OUI_FROM_DATABASE=Ability Intelligent Corp. + OUI:8C1F64ADB* ID_OUI_FROM_DATABASE=Hebei Weiji Electric Co.,Ltd @@ -109796,6 +111401,9 @@ OUI:8C1F64AE9* OUI:8C1F64AEA* ID_OUI_FROM_DATABASE=INHEMETER Co.,Ltd +OUI:8C1F64AEB* + ID_OUI_FROM_DATABASE=Sedna + OUI:8C1F64AEC* ID_OUI_FROM_DATABASE=Pixel Design & Manufacturing Sdn. Bhd. @@ -109845,7 +111453,7 @@ OUI:8C1F64B00* ID_OUI_FROM_DATABASE=Gets MSS OUI:8C1F64B01* - ID_OUI_FROM_DATABASE=Blue Ocean UG + ID_OUI_FROM_DATABASE=Terranova Industries UG (haftungsbeschränkt) OUI:8C1F64B03* ID_OUI_FROM_DATABASE=Shenzhen Pisoftware Technology Co.,Ltd. @@ -109943,6 +111551,9 @@ OUI:8C1F64B31* OUI:8C1F64B32* ID_OUI_FROM_DATABASE=Plug Power +OUI:8C1F64B33* + ID_OUI_FROM_DATABASE=Skylark Lasers + OUI:8C1F64B35* ID_OUI_FROM_DATABASE=RADA Electronics Industries Ltd. @@ -110225,6 +111836,9 @@ OUI:8C1F64BB9* OUI:8C1F64BBA* ID_OUI_FROM_DATABASE=elysia GmbH +OUI:8C1F64BBB* + ID_OUI_FROM_DATABASE=SiLC Technologies + OUI:8C1F64BBC* ID_OUI_FROM_DATABASE=Liberator Pty Ltd @@ -110249,6 +111863,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 +111890,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. @@ -110282,6 +111902,9 @@ OUI:8C1F64BD2* OUI:8C1F64BD3* ID_OUI_FROM_DATABASE=IO Master Technology +OUI:8C1F64BD4* + ID_OUI_FROM_DATABASE=AVEA Group, Inc. + OUI:8C1F64BD5* ID_OUI_FROM_DATABASE=Pro-Custom Group @@ -110315,6 +111938,9 @@ OUI:8C1F64BE8* OUI:8C1F64BEA* ID_OUI_FROM_DATABASE=Microchip Technologies Inc +OUI:8C1F64BEB* + ID_OUI_FROM_DATABASE=aelettronica group srl + OUI:8C1F64BED* ID_OUI_FROM_DATABASE=Genius Sports SS LLC @@ -110402,6 +112028,9 @@ OUI:8C1F64C0D* OUI:8C1F64C0E* ID_OUI_FROM_DATABASE=Goodtech AS dep Fredrikstad +OUI:8C1F64C0F* + ID_OUI_FROM_DATABASE=KONAX BV + OUI:8C1F64C12* ID_OUI_FROM_DATABASE=PHYSEC GmbH @@ -110456,6 +112085,9 @@ OUI:8C1F64C2B* OUI:8C1F64C2D* ID_OUI_FROM_DATABASE=iENSO Inc. +OUI:8C1F64C2E* + ID_OUI_FROM_DATABASE=ICS SOLUTIONS INC. + OUI:8C1F64C2F* ID_OUI_FROM_DATABASE=Power Electronics Espana, S.L. @@ -110507,6 +112139,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. @@ -110561,6 +112196,9 @@ OUI:8C1F64C62* OUI:8C1F64C64* ID_OUI_FROM_DATABASE=Ajeco Oy +OUI:8C1F64C65* + ID_OUI_FROM_DATABASE=Headwave + OUI:8C1F64C67* ID_OUI_FROM_DATABASE=Oriux @@ -110594,6 +112232,9 @@ OUI:8C1F64C74* OUI:8C1F64C75* ID_OUI_FROM_DATABASE=Abbott Diagnostics Technologies AS +OUI:8C1F64C77* + ID_OUI_FROM_DATABASE=VNET Corp. + OUI:8C1F64C78* ID_OUI_FROM_DATABASE=POLON-ALFA S.A. @@ -110609,12 +112250,18 @@ OUI:8C1F64C7C* OUI:8C1F64C7D* ID_OUI_FROM_DATABASE=Glasson Electronics Ltd +OUI:8C1F64C7F* + ID_OUI_FROM_DATABASE=Brightwell Dosing ltd + OUI:8C1F64C80* ID_OUI_FROM_DATABASE=VECOS Europe B.V. OUI:8C1F64C81* ID_OUI_FROM_DATABASE=Taolink Technologies Corporation +OUI:8C1F64C82* + ID_OUI_FROM_DATABASE=SekureTrak Inc. dba TraknProtect + OUI:8C1F64C83* ID_OUI_FROM_DATABASE=Power Electronics Espana, S.L. @@ -110636,6 +112283,9 @@ OUI:8C1F64C91* OUI:8C1F64C92* ID_OUI_FROM_DATABASE=EQ Earthquake Ltd. +OUI:8C1F64C95* + ID_OUI_FROM_DATABASE=LEMIER + OUI:8C1F64C96* ID_OUI_FROM_DATABASE=Smart Data (Shenzhen) Intelligent System Co., Ltd. @@ -110729,6 +112379,9 @@ OUI:8C1F64CC1* OUI:8C1F64CC2* ID_OUI_FROM_DATABASE=TOYOGIKEN CO.,LTD. +OUI:8C1F64CC3* + ID_OUI_FROM_DATABASE=KRONOTECH SRL + OUI:8C1F64CC5* ID_OUI_FROM_DATABASE=Potter Electric Signal Co. LLC @@ -110954,6 +112607,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. @@ -110963,9 +112619,15 @@ OUI:8C1F64D2F* OUI:8C1F64D30* ID_OUI_FROM_DATABASE=FMC Technologies Measurement Solutions Inc +OUI:8C1F64D33* + ID_OUI_FROM_DATABASE=SPACE CREATORS ALLIANCE Inc. + OUI:8C1F64D34* ID_OUI_FROM_DATABASE=KRONOTECH SRL +OUI:8C1F64D35* + ID_OUI_FROM_DATABASE=Xi'an Biangu Information Technology Co., Ltd. + OUI:8C1F64D38* ID_OUI_FROM_DATABASE=CUU LONG TECHNOLOGY AND TRADING COMPANY LIMITED @@ -111029,6 +112691,9 @@ OUI:8C1F64D55* OUI:8C1F64D56* ID_OUI_FROM_DATABASE=Wisdom Audio +OUI:8C1F64D57* + ID_OUI_FROM_DATABASE=Integer.pl S.A. + OUI:8C1F64D58* ID_OUI_FROM_DATABASE=Zumbach Electronic AG @@ -111059,12 +112724,21 @@ 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:8C1F64D6D* + ID_OUI_FROM_DATABASE=Viettel High Tech + +OUI:8C1F64D6F* + ID_OUI_FROM_DATABASE=ARKTRON ELECTRONICS + OUI:8C1F64D71* ID_OUI_FROM_DATABASE=Computech International @@ -111098,6 +112772,12 @@ OUI:8C1F64D80* OUI:8C1F64D81* ID_OUI_FROM_DATABASE=Mitsubishi Electric India Pvt. Ltd. +OUI:8C1F64D82* + ID_OUI_FROM_DATABASE=Shanghai Jarue Microsystem.CO.,Ltd. + +OUI:8C1F64D87* + ID_OUI_FROM_DATABASE=EXPERIO TECH PRIVATE LIMITED + OUI:8C1F64D88* ID_OUI_FROM_DATABASE=University of Geneva - Department of Particle Physics @@ -111155,6 +112835,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 @@ -111242,6 +112925,9 @@ OUI:8C1F64DCB* OUI:8C1F64DCF* ID_OUI_FROM_DATABASE=REO AG +OUI:8C1F64DD0* + ID_OUI_FROM_DATABASE=GENIUS SPORTS + OUI:8C1F64DD2* ID_OUI_FROM_DATABASE=SHIELD-CCTV CO.,LTD. @@ -111296,6 +112982,12 @@ OUI:8C1F64DEB* OUI:8C1F64DED* ID_OUI_FROM_DATABASE=PhotonPath +OUI:8C1F64DF0* + ID_OUI_FROM_DATABASE=Cyberkar Systems inc. + +OUI:8C1F64DF2* + ID_OUI_FROM_DATABASE=LOMAR SRL + OUI:8C1F64DF5* ID_OUI_FROM_DATABASE=Concept Pro Surveillance @@ -111350,6 +113042,9 @@ OUI:8C1F64E0B* OUI:8C1F64E0C* ID_OUI_FROM_DATABASE=TELESTRIDER SA +OUI:8C1F64E0D* + ID_OUI_FROM_DATABASE=MyDefence A/S + OUI:8C1F64E0E* ID_OUI_FROM_DATABASE=Nokeval Oy @@ -111389,6 +113084,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 @@ -111401,6 +113099,9 @@ OUI:8C1F64E2A* OUI:8C1F64E2B* ID_OUI_FROM_DATABASE=Glotech Exim Private Limited +OUI:8C1F64E2C* + ID_OUI_FROM_DATABASE=Kairos Water, Inc + OUI:8C1F64E2D* ID_OUI_FROM_DATABASE=RADA Electronics Industries Ltd. @@ -111521,6 +113222,9 @@ OUI:8C1F64E66* OUI:8C1F64E68* ID_OUI_FROM_DATABASE=LHA Systems (Pty) Ltd +OUI:8C1F64E69* + ID_OUI_FROM_DATABASE=FullohmKD + OUI:8C1F64E6B* ID_OUI_FROM_DATABASE=Terratel Technology s.r.o. @@ -111608,6 +113312,9 @@ OUI:8C1F64E92* OUI:8C1F64E94* ID_OUI_FROM_DATABASE=ZIN TECHNOLOGIES +OUI:8C1F64E97* + ID_OUI_FROM_DATABASE=Lucint Systems, Inc. + OUI:8C1F64E98* ID_OUI_FROM_DATABASE=Luxshare Electronic Technology (Kunshan) LTD @@ -111695,6 +113402,12 @@ OUI:8C1F64EC1* OUI:8C1F64EC2* ID_OUI_FROM_DATABASE=HARBIN DIGITAL ECONOMY DEVELOPMENT CO.,LTD +OUI:8C1F64EC3* + ID_OUI_FROM_DATABASE=Scenario Automation + +OUI:8C1F64EC5* + ID_OUI_FROM_DATABASE=Stanley Black & Decker Engineered Fastening (Nantong) Co., Ltd. + OUI:8C1F64ECC* ID_OUI_FROM_DATABASE=Baldwin Jimek AB @@ -111782,6 +113495,9 @@ OUI:8C1F64EFB* OUI:8C1F64EFD* ID_OUI_FROM_DATABASE=Novatera(Shenzhen)Technologies Co.,Ltd. +OUI:8C1F64EFF* + ID_OUI_FROM_DATABASE=Automata GmbH & Co. KG + OUI:8C1F64F03* ID_OUI_FROM_DATABASE=Faust ApS @@ -111818,6 +113534,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 @@ -112016,6 +113735,9 @@ OUI:8C1F64F79* OUI:8C1F64F7A* ID_OUI_FROM_DATABASE=SiEngine Technology Co., Ltd. +OUI:8C1F64F7B* + ID_OUI_FROM_DATABASE=XPS ELETRONICA LTDA + OUI:8C1F64F7C* ID_OUI_FROM_DATABASE=General Dynamics IT @@ -112028,6 +113750,9 @@ OUI:8C1F64F7F* OUI:8C1F64F80* ID_OUI_FROM_DATABASE=Vinfast Trading and Production JSC +OUI:8C1F64F82* + ID_OUI_FROM_DATABASE=SEORIM TECHNOLOGY + OUI:8C1F64F83* ID_OUI_FROM_DATABASE=Vishay Nobel AB @@ -112046,6 +113771,9 @@ OUI:8C1F64F88* OUI:8C1F64F8C* ID_OUI_FROM_DATABASE=BK LAB +OUI:8C1F64F8E* + ID_OUI_FROM_DATABASE=Chengdu Aplux Inteligence Technology Ltd. + OUI:8C1F64F90* ID_OUI_FROM_DATABASE=Enfabrica @@ -112118,6 +113846,9 @@ OUI:8C1F64FB0* OUI:8C1F64FB1* ID_OUI_FROM_DATABASE=ABB +OUI:8C1F64FB3* + ID_OUI_FROM_DATABASE=Camius + OUI:8C1F64FB4* ID_OUI_FROM_DATABASE=Thales Nederland BV @@ -112139,6 +113870,9 @@ OUI:8C1F64FBA* OUI:8C1F64FBB* ID_OUI_FROM_DATABASE=Telica +OUI:8C1F64FBC* + ID_OUI_FROM_DATABASE=Immersive Audio Technologies + OUI:8C1F64FBD* ID_OUI_FROM_DATABASE=SAN-AI Electronic Industries Co.,Ltd. @@ -112160,6 +113894,9 @@ OUI:8C1F64FC5* OUI:8C1F64FC6* ID_OUI_FROM_DATABASE=Invertek Drives Ltd +OUI:8C1F64FC8* + ID_OUI_FROM_DATABASE=Metaphase Technologies + OUI:8C1F64FCA* ID_OUI_FROM_DATABASE=Elektrotechnik & Elektronik Oltmann GmbH @@ -112217,6 +113954,9 @@ OUI:8C1F64FE3* OUI:8C1F64FE5* ID_OUI_FROM_DATABASE=Truenorth +OUI:8C1F64FE6* + ID_OUI_FROM_DATABASE=Cognicom, Inc. + OUI:8C1F64FE9* ID_OUI_FROM_DATABASE=ALZAJEL MODERN TELECOMMUNICATION @@ -112235,6 +113975,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 @@ -112262,6 +114005,9 @@ OUI:8C1F64FFC* OUI:8C1F94* ID_OUI_FROM_DATABASE=RF Surgical System Inc. +OUI:8C20F1* + ID_OUI_FROM_DATABASE=OpenAI + OUI:8C210A* ID_OUI_FROM_DATABASE=TP-LINK TECHNOLOGIES CO.,LTD. @@ -112466,6 +114212,9 @@ OUI:8C497A* OUI:8C49B6* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. +OUI:8C49CF* + ID_OUI_FROM_DATABASE=Private + OUI:8C4AEE* ID_OUI_FROM_DATABASE=GIGA TMS INC @@ -112550,6 +114299,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 @@ -112817,6 +114569,9 @@ OUI:8C64D4* OUI:8C65A3* ID_OUI_FROM_DATABASE=Silicon Laboratories +OUI:8C65EC* + ID_OUI_FROM_DATABASE=TUBITAK MAM + OUI:8C6794* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. @@ -112946,6 +114701,9 @@ OUI:8C8126* OUI:8C8172* ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD +OUI:8C8283* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:8C82A8* ID_OUI_FROM_DATABASE=Insigma Technology Co.,Ltd @@ -113123,6 +114881,9 @@ OUI:8C9806* OUI:8C986B* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:8C9885* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:8C99E6* ID_OUI_FROM_DATABASE=TCT mobile ltd @@ -113339,6 +115100,9 @@ OUI:8CBFEA* OUI:8CC121* ID_OUI_FROM_DATABASE=Panasonic Corporation AVC Networks Company +OUI:8CC246* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:8CC573* ID_OUI_FROM_DATABASE=Xsight Labs LTD. @@ -113468,6 +115232,9 @@ OUI:8CD0B2* OUI:8CD17B* ID_OUI_FROM_DATABASE=CG Mobile +OUI:8CD1A6* + ID_OUI_FROM_DATABASE=eero inc. + OUI:8CD2E9* ID_OUI_FROM_DATABASE=YOKOTE SEIKO CO., LTD. @@ -113594,6 +115361,9 @@ OUI:8CEC7B* OUI:8CEDE1* ID_OUI_FROM_DATABASE=Ubiquiti Inc +OUI:8CEE17* + ID_OUI_FROM_DATABASE=GYGES LABS PTE.LTD + OUI:8CEEC6* ID_OUI_FROM_DATABASE=Precepscion Pty. Ltd. @@ -113717,6 +115487,9 @@ OUI:9003B7* OUI:900628* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:9006DB* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:9006F2* ID_OUI_FROM_DATABASE=Texas Instruments @@ -113738,6 +115511,9 @@ OUI:900A39* OUI:900A3A* ID_OUI_FROM_DATABASE=PSG Plastic Service GmbH +OUI:900A48* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:900A62* ID_OUI_FROM_DATABASE=Inventus Power Eletronica do Brasil LTDA @@ -113765,6 +115541,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. @@ -113972,6 +115751,9 @@ OUI:902E1C* OUI:902E87* ID_OUI_FROM_DATABASE=LabJack +OUI:9030D6* + ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. + OUI:90314B* ID_OUI_FROM_DATABASE=AltoBeam Inc. @@ -113999,6 +115781,9 @@ OUI:9035A2* OUI:9035EA* ID_OUI_FROM_DATABASE=Silicon Laboratories +OUI:9036B2* + ID_OUI_FROM_DATABASE=TRATON AB + OUI:903809* ID_OUI_FROM_DATABASE=Ericsson AB @@ -114128,6 +115913,9 @@ OUI:904D4A* OUI:904DC3* ID_OUI_FROM_DATABASE=Flonidan A/S +OUI:904DE2* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:904E2B* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -114269,6 +116057,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 @@ -114461,6 +116252,9 @@ OUI:90842B* OUI:90848B* ID_OUI_FROM_DATABASE=HDR10+ Technologies, LLC +OUI:90848E* + ID_OUI_FROM_DATABASE=FUJIAN STAR-NET COMMUNICATION CO.,LTD + OUI:908674* ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD @@ -114563,6 +116357,9 @@ OUI:909060* OUI:909164* ID_OUI_FROM_DATABASE=ChongQing Lavid Technology Co., Ltd. +OUI:90922C* + ID_OUI_FROM_DATABASE=Changzhi City Zhouyi Hengtong Information Security Co.,Ltd. + OUI:9092B4* ID_OUI_FROM_DATABASE=Diehl BGT Defence GmbH & Co. KG @@ -114830,6 +116627,9 @@ OUI:90B97D* OUI:90B9F9* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company +OUI:90BA09* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:90BDE6* ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. @@ -114971,6 +116771,9 @@ OUI:90D473* OUI:90D689* ID_OUI_FROM_DATABASE=Huahao Fangzhou Technology Co.,Ltd +OUI:90D733* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:90D74F* ID_OUI_FROM_DATABASE=Bookeen @@ -114995,6 +116798,9 @@ OUI:90DA4E* OUI:90DA6A* ID_OUI_FROM_DATABASE=FOCUS H&S Co., Ltd. +OUI:90DA72* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:90DAF9* ID_OUI_FROM_DATABASE=Siemens Rail Automation SAU @@ -115142,6 +116948,9 @@ OUI:90EF68* OUI:90F005* ID_OUI_FROM_DATABASE=Xi'an Molead Technology Co., Ltd +OUI:90F04C* + ID_OUI_FROM_DATABASE=Nokia Solutions (Shanghai) Co.,Ltd. + OUI:90F052* ID_OUI_FROM_DATABASE=MEIZU Technology Co., Ltd. @@ -115262,6 +117071,9 @@ OUI:90FB93* OUI:90FBA6* ID_OUI_FROM_DATABASE=Hon Hai Precision Ind. Co.,Ltd. +OUI:90FC55* + ID_OUI_FROM_DATABASE=Hyve Solutions + OUI:90FD61* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -115415,6 +117227,9 @@ OUI:940E6B* OUI:940EE7* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:940F3B* + ID_OUI_FROM_DATABASE=ASKEY COMPUTER CORP + OUI:94103E* ID_OUI_FROM_DATABASE=Belkin International Inc. @@ -115706,6 +117521,9 @@ OUI:944452* OUI:944560* ID_OUI_FROM_DATABASE=Google, Inc. +OUI:944667* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:944696* ID_OUI_FROM_DATABASE=BaudTec Corporation @@ -115769,6 +117587,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. @@ -115790,6 +117611,9 @@ OUI:9458CB* OUI:945907* ID_OUI_FROM_DATABASE=Shanghai HITE-BELDEN Network Technology Co., Ltd. +OUI:945915* + ID_OUI_FROM_DATABASE=Amazon Technologies Inc. + OUI:94592D* ID_OUI_FROM_DATABASE=EKE Building Technology Systems Ltd @@ -115835,6 +117659,9 @@ OUI:9463D1* OUI:946424* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise +OUI:946442* + ID_OUI_FROM_DATABASE=CELESTICA INC. + OUI:94652D* ID_OUI_FROM_DATABASE=OnePlus Technology (Shenzhen) Co., Ltd @@ -115943,6 +117770,9 @@ OUI:9487E0* OUI:948815* ID_OUI_FROM_DATABASE=Infinique Worldwide Inc +OUI:948835* + ID_OUI_FROM_DATABASE=CRESTRON ELECTRONICS, INC. + OUI:948854* ID_OUI_FROM_DATABASE=Texas Instruments @@ -117107,9 +118937,15 @@ OUI:9829A6* OUI:982A0A* ID_OUI_FROM_DATABASE=Intelbras +OUI:982AC3* + ID_OUI_FROM_DATABASE=Silicon Laboratories + 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 +119021,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 @@ -117788,6 +119627,9 @@ OUI:989E63* OUI:989E80* ID_OUI_FROM_DATABASE=tonies GmbH +OUI:989E85* + ID_OUI_FROM_DATABASE=Honor Device Co., Ltd. + OUI:989F1A* ID_OUI_FROM_DATABASE=Private @@ -118112,6 +119954,9 @@ OUI:98E165* OUI:98E255* ID_OUI_FROM_DATABASE=Nintendo Co.,Ltd +OUI:98E301* + ID_OUI_FROM_DATABASE=Shenzhen Sundray Technologies company Limited + OUI:98E476* ID_OUI_FROM_DATABASE=Zentan @@ -118367,6 +120212,9 @@ OUI:98FE03* OUI:98FE3E* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:98FE54* + ID_OUI_FROM_DATABASE=Raspberry Pi (Trading) Ltd + OUI:98FE94* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -118427,6 +120275,9 @@ OUI:9C0971* OUI:9C098B* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:9C09CA* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:9C0B05* ID_OUI_FROM_DATABASE=eero inc. @@ -118473,7 +120324,7 @@ OUI:9C1C12* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise OUI:9C1C37* - ID_OUI_FROM_DATABASE=AltoBeam (China) Inc. + ID_OUI_FROM_DATABASE=AltoBeam Inc. OUI:9C1C6D* ID_OUI_FROM_DATABASE=HEFEI DATANG STORAGE TECHNOLOGY CO.,LTD @@ -118502,6 +120353,9 @@ OUI:9C1FCA* OUI:9C1FDD* ID_OUI_FROM_DATABASE=Accupix Inc. +OUI:9C1FE6* + ID_OUI_FROM_DATABASE=Shenzhen Skyworth Display Technologies Co.,Ltd + OUI:9C207B* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -118772,6 +120626,9 @@ OUI:9C50D1* OUI:9C50EE* ID_OUI_FROM_DATABASE=Cambridge Industries(Group) Co.,Ltd. +OUI:9C5187* + ID_OUI_FROM_DATABASE=SUNITEC TECHNOLOGY CO.,LIMITED + OUI:9C52F8* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -119069,6 +120926,9 @@ OUI:9C7DC0* OUI:9C7F57* ID_OUI_FROM_DATABASE=UNIC Memory Technology Co Ltd +OUI:9C7F64* + ID_OUI_FROM_DATABASE=Nanjing Qinheng Microelectronics Co., Ltd. + OUI:9C7F81* ID_OUI_FROM_DATABASE=SHENZHEN FAST TECHNOLOGIES CO.,LTD @@ -119076,7 +120936,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. @@ -119102,6 +120962,9 @@ OUI:9C84BF* OUI:9C8566* ID_OUI_FROM_DATABASE=Wingtech Mobile Communications Co.,Ltd. +OUI:9C862B* + ID_OUI_FROM_DATABASE=MOTOROLA SOLUTIONS MALAYSIA SDN. BHD. + OUI:9C86DA* ID_OUI_FROM_DATABASE=Phoenix Geophysics Ltd. @@ -119222,6 +121085,9 @@ OUI:9C9C1F* OUI:9C9C40* ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD +OUI:9C9D07* + ID_OUI_FROM_DATABASE=FN-LINK TECHNOLOGY Ltd. + OUI:9C9D5D* ID_OUI_FROM_DATABASE=Raden Inc @@ -119357,6 +121223,9 @@ OUI:9CBAC9* OUI:9CBB98* ID_OUI_FROM_DATABASE=Shen Zhen RND Electronic Co.,LTD +OUI:9CBCA6* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:9CBCF0* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd @@ -119429,6 +121298,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 +121310,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 @@ -119693,6 +121568,9 @@ OUI:9CF155* OUI:9CF1D4* ID_OUI_FROM_DATABASE=Roku, Inc +OUI:9CF27E* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:9CF387* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -119936,6 +121814,9 @@ OUI:A013CB* OUI:A0143D* ID_OUI_FROM_DATABASE=PARROT SA +OUI:A0146D* + ID_OUI_FROM_DATABASE=Suzhou NODKA Automation Technology Co.,Ltd + OUI:A0165C* ID_OUI_FROM_DATABASE=Triteka LTD @@ -120002,6 +121883,9 @@ OUI:A019B2E* OUI:A01AE3* ID_OUI_FROM_DATABASE=Edgecore Americas Networking Corporation +OUI:A01B04* + ID_OUI_FROM_DATABASE=Hefei Huanxin Microelectronics Technology Co., Ltd. + OUI:A01B29* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS @@ -120101,6 +121985,51 @@ OUI:A0239F* OUI:A02442* ID_OUI_FROM_DATABASE=Shenzhenshi Xinzhongxin Technology Co.Ltd +OUI:A024900* + ID_OUI_FROM_DATABASE=Jackal Design LLC + +OUI:A024901* + ID_OUI_FROM_DATABASE=SHENZHEN ATTEN TECHNOLOGY CO.,LTD + +OUI:A024902* + ID_OUI_FROM_DATABASE=nodor international + +OUI:A024903* + ID_OUI_FROM_DATABASE=Bosch Rexroth AG + +OUI:A024904* + ID_OUI_FROM_DATABASE=Nefence Inc. + +OUI:A024905* + ID_OUI_FROM_DATABASE=Nektar Inc. + +OUI:A024906* + ID_OUI_FROM_DATABASE=SIAME + +OUI:A024907* + ID_OUI_FROM_DATABASE=VELVU TECHNOLOGIES PRIVATE LIMITED + +OUI:A024908* + ID_OUI_FROM_DATABASE=Elisity, Inc. + +OUI:A024909* + ID_OUI_FROM_DATABASE=SANSHA ELECTRIC MANUFACTURING CO., LTD. + +OUI:A02490A* + ID_OUI_FROM_DATABASE=Briggs & Stratton LLC + +OUI:A02490B* + ID_OUI_FROM_DATABASE=Tauro Technologies + +OUI:A02490C* + ID_OUI_FROM_DATABASE=WiE GmbH - Werk für industrielle Elektronik + +OUI:A02490D* + ID_OUI_FROM_DATABASE=JKIN + +OUI:A02490E* + ID_OUI_FROM_DATABASE=Mini Motor Spa + OUI:A024F9* ID_OUI_FROM_DATABASE=Chengdu InnovaTest Technology Co., Ltd @@ -120398,6 +122327,9 @@ OUI:A04E01* OUI:A04E04* ID_OUI_FROM_DATABASE=Nokia Corporation +OUI:A04E8D* + ID_OUI_FROM_DATABASE=Cisco Meraki + OUI:A04EA7* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -120473,6 +122405,9 @@ OUI:A05AA4* OUI:A05B21* ID_OUI_FROM_DATABASE=ENVINET GmbH +OUI:A05D0E* + ID_OUI_FROM_DATABASE=ALPSALPINE CO.,LTD. + OUI:A05DC1* ID_OUI_FROM_DATABASE=TMCT Co., LTD. @@ -120653,6 +122588,9 @@ OUI:A086C6* OUI:A086EC* ID_OUI_FROM_DATABASE=SAEHAN HITEC Co., Ltd +OUI:A087BE* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:A0885E* ID_OUI_FROM_DATABASE=Anhui Xiangyao New Energy Technology Co., Ltd. @@ -120779,6 +122717,9 @@ OUI:A09B17* OUI:A09BBD* ID_OUI_FROM_DATABASE=Total Aviation Solutions Pty Ltd +OUI:A09C19* + ID_OUI_FROM_DATABASE=CIG SHANGHAI CO LTD + OUI:A09D22* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -120887,6 +122828,9 @@ OUI:A0B0BD* OUI:A0B100* ID_OUI_FROM_DATABASE=ShenZhen Cando Electronics Co.,Ltd +OUI:A0B15C* + ID_OUI_FROM_DATABASE=Google, Inc. + OUI:A0B339* ID_OUI_FROM_DATABASE=Intel Corporate @@ -121067,6 +123011,9 @@ OUI:A0C5F2D* OUI:A0C5F2E* ID_OUI_FROM_DATABASE=Synapsys Solutions Ltd. +OUI:A0C6A5* + ID_OUI_FROM_DATABASE=Lierda Science & Technology Group Co., Ltd + OUI:A0C6EC* ID_OUI_FROM_DATABASE=ShenZhen ANYK Technology Co.,LTD @@ -121079,6 +123026,9 @@ OUI:A0C98B* OUI:A0C9A0* ID_OUI_FROM_DATABASE=Murata Manufacturing Co., Ltd. +OUI:A0CA4A* + ID_OUI_FROM_DATABASE=AltoBeam Inc. + OUI:A0CAA5* ID_OUI_FROM_DATABASE=INTELLIGENCE TECHNOLOGY OF CEC CO., LTD @@ -121259,6 +123209,9 @@ OUI:A0EF84* OUI:A0F217* ID_OUI_FROM_DATABASE=GE Medical System(China) Co., Ltd. +OUI:A0F261* + ID_OUI_FROM_DATABASE=Palo Alto Networks + OUI:A0F262* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -121421,6 +123374,9 @@ OUI:A40E2B* OUI:A40E75* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise +OUI:A40F25* + ID_OUI_FROM_DATABASE=eero inc. + OUI:A40F98* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -121724,6 +123680,9 @@ OUI:A43BFAE* OUI:A43BFAF* ID_OUI_FROM_DATABASE=Private +OUI:A43CD4* + ID_OUI_FROM_DATABASE=JBL Professional + OUI:A43CD7* ID_OUI_FROM_DATABASE=NTX Electronics YangZhou co.,LTD @@ -121862,6 +123821,51 @@ OUI:A44F29E* OUI:A44F29F* ID_OUI_FROM_DATABASE=Private +OUI:A44F3E0* + ID_OUI_FROM_DATABASE=United Automotive Electronic Systems Co.,Ltd + +OUI:A44F3E1* + ID_OUI_FROM_DATABASE=Netshield Europe Srl + +OUI:A44F3E2* + ID_OUI_FROM_DATABASE=RINVENT INDUSTRIES PRIVATE LIMITED + +OUI:A44F3E3* + ID_OUI_FROM_DATABASE=Annapurna labs + +OUI:A44F3E4* + ID_OUI_FROM_DATABASE=ShenZhen hionetech Co,.ltd + +OUI:A44F3E5* + ID_OUI_FROM_DATABASE=Mobilint + +OUI:A44F3E6* + ID_OUI_FROM_DATABASE=Maven Pet Inc + +OUI:A44F3E7* + ID_OUI_FROM_DATABASE=Vinfast Trading and Production JSC + +OUI:A44F3E8* + ID_OUI_FROM_DATABASE=Neurable + +OUI:A44F3E9* + ID_OUI_FROM_DATABASE=LINK Group Inc. + +OUI:A44F3EA* + ID_OUI_FROM_DATABASE=CMCNI Co., Ltd + +OUI:A44F3EB* + ID_OUI_FROM_DATABASE=Suzhou AIDomex Intelligent Technology Co., Ltd. + +OUI:A44F3EC* + ID_OUI_FROM_DATABASE=JOYAR TECHNOLOGY (HONG KONG) COMPANY LIMITED + +OUI:A44F3ED* + ID_OUI_FROM_DATABASE=NTT sonority, Inc. + +OUI:A44F3EE* + ID_OUI_FROM_DATABASE=ShenZhen Chainway Information Technology Co., Ltd. + OUI:A45006* ID_OUI_FROM_DATABASE=SHENZHEN HUACHUANG SHIDAI TECHNOLOGYCO.,LTD @@ -122297,6 +124301,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 +124682,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 @@ -122886,7 +124896,7 @@ OUI:A4EE57* ID_OUI_FROM_DATABASE=Seiko Epson Corporation OUI:A4EF15* - ID_OUI_FROM_DATABASE=AltoBeam (China) Inc. + ID_OUI_FROM_DATABASE=AltoBeam Inc. OUI:A4EF52* ID_OUI_FROM_DATABASE=Telewave Co., Ltd. @@ -123095,6 +125105,12 @@ OUI:A81FAF* OUI:A82066* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:A8213A* + ID_OUI_FROM_DATABASE=Shenzhen Talent Technology Comapny Limited 深圳市泰霖科技有限公司 + +OUI:A821C8* + ID_OUI_FROM_DATABASE=shenzhen phoenix telecom technology Co.,Ltd. + OUI:A82316* ID_OUI_FROM_DATABASE=Nokia @@ -123125,6 +125141,9 @@ OUI:A82948* OUI:A8294C* ID_OUI_FROM_DATABASE=Precision Optical Transceivers, Inc. +OUI:A829DC* + ID_OUI_FROM_DATABASE=TP-Link Systems Inc. + OUI:A82AD6* ID_OUI_FROM_DATABASE=Arthrex Inc. @@ -123587,6 +125606,9 @@ OUI:A8741D* OUI:A87484* ID_OUI_FROM_DATABASE=zte corporation +OUI:A8754E* + ID_OUI_FROM_DATABASE=Nexlawn Intelligent Technology (Suzhou) Co., Ltd. + OUI:A875D6* ID_OUI_FROM_DATABASE=FreeTek International Co., Ltd. @@ -124005,7 +126027,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 @@ -124043,6 +126065,9 @@ OUI:A8DC5A* OUI:A8DD9F* ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. +OUI:A8DDEC* + ID_OUI_FROM_DATABASE=Hangzhou BroadLink Technology Co., Ltd + OUI:A8DE68* ID_OUI_FROM_DATABASE=Beijing Wide Technology Co.,Ltd @@ -124127,6 +126152,9 @@ OUI:A8F038* OUI:A8F059* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:A8F07C* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:A8F1B2* ID_OUI_FROM_DATABASE=Allwinner Technology Co., Ltd @@ -124235,6 +126263,9 @@ OUI:AC0481* OUI:AC04AA* ID_OUI_FROM_DATABASE=GoPro +OUI:AC05C7* + ID_OUI_FROM_DATABASE=Intel Corporate + OUI:AC0613* ID_OUI_FROM_DATABASE=Senselogix Ltd @@ -124427,6 +126458,9 @@ OUI:AC2205* OUI:AC220B* ID_OUI_FROM_DATABASE=ASUSTek COMPUTER INC. +OUI:AC2241* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:AC2316* ID_OUI_FROM_DATABASE=Mist Systems, Inc. @@ -124436,6 +126470,12 @@ OUI:AC2334* OUI:AC233F* ID_OUI_FROM_DATABASE=Shenzhen Minew Technologies Co., Ltd. +OUI:AC236E* + ID_OUI_FROM_DATABASE=Apismoon Electric Pte. Ltd. + +OUI:AC2477* + ID_OUI_FROM_DATABASE=Shenzhen Tinno Mobile Technology Corp + OUI:AC276E* ID_OUI_FROM_DATABASE=Espressif Inc. @@ -124454,6 +126494,9 @@ OUI:AC2AA1* OUI:AC2B6E* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:AC2BA2* + ID_OUI_FROM_DATABASE=The Chamberlain Group, Inc + OUI:AC2DA3* ID_OUI_FROM_DATABASE=TXTR GmbH @@ -124463,6 +126506,9 @@ OUI:AC2DA9* OUI:AC2FA8* ID_OUI_FROM_DATABASE=Humannix Co.,Ltd. +OUI:AC3019* + ID_OUI_FROM_DATABASE=Shenzhen Hailingwei Electronics Co., Ltd. + OUI:AC3184* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -124580,12 +126626,18 @@ 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 OUI:AC45EF* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:AC46A7* + ID_OUI_FROM_DATABASE=SERCOMM PHILIPPINES INC + OUI:AC471B* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -124658,6 +126710,9 @@ OUI:AC51AB* OUI:AC51EE* ID_OUI_FROM_DATABASE=Adtran Inc +OUI:AC5322* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:AC5474* ID_OUI_FROM_DATABASE=China Mobile IOT Company Limited @@ -124739,6 +126794,9 @@ OUI:AC61EA* OUI:AC620D* ID_OUI_FROM_DATABASE=Jabil Circuit(Wuxi) Co.,Ltd +OUI:AC62FF* + ID_OUI_FROM_DATABASE=Vantiva USA LLC + OUI:AC63BE* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. @@ -125018,6 +127076,9 @@ OUI:AC86D1D* OUI:AC86D1E* ID_OUI_FROM_DATABASE=Retina Development B.V. +OUI:AC8746* + ID_OUI_FROM_DATABASE=Huizhou BYD Electronic Co., Ltd. + OUI:AC87A3* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -125139,7 +127200,7 @@ OUI:ACA09D* ID_OUI_FROM_DATABASE=Juniper Networks OUI:ACA213* - ID_OUI_FROM_DATABASE=Shenzhen Bilian electronic CO.,LTD + ID_OUI_FROM_DATABASE=SHENZHEN BILIAN ELECTRONIC CO.,LTD OUI:ACA22C* ID_OUI_FROM_DATABASE=Baycity Technologies Ltd @@ -125171,6 +127232,9 @@ OUI:ACA7F1* OUI:ACA88E* ID_OUI_FROM_DATABASE=SHARP Corporation +OUI:ACA899* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:ACA919* ID_OUI_FROM_DATABASE=TrekStor GmbH @@ -125192,6 +127256,9 @@ OUI:ACACE2* OUI:ACAD4B* ID_OUI_FROM_DATABASE=zte corporation +OUI:ACADEF* + ID_OUI_FROM_DATABASE=Wanan Hongsheng Electronic Co.Ltd + OUI:ACAE19* ID_OUI_FROM_DATABASE=Roku, Inc @@ -125480,9 +127547,15 @@ OUI:ACE42E* OUI:ACE4B5* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:ACE4D8* + ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd + 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. @@ -125684,6 +127757,9 @@ OUI:B0027E* OUI:B00594* ID_OUI_FROM_DATABASE=Liteon Technology Corporation +OUI:B006EC* + ID_OUI_FROM_DATABASE=Nexquome Pte Limited + OUI:B00875* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -125858,6 +127934,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 @@ -125933,6 +128012,9 @@ OUI:B03CDC* OUI:B03D96* ID_OUI_FROM_DATABASE=Vision Valley FZ LLC +OUI:B03DBF* + ID_OUI_FROM_DATABASE=shenzhen ceita communications technology co.,ltd + OUI:B03DC2* ID_OUI_FROM_DATABASE=Wasp artificial intelligence(Shenzhen) Co.,ltd @@ -125945,6 +128027,9 @@ OUI:B03EB0* OUI:B03F64* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:B03FD3* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:B04089* ID_OUI_FROM_DATABASE=Senient Systems LTD @@ -126149,6 +128234,9 @@ OUI:B061A9* OUI:B061C7* ID_OUI_FROM_DATABASE=Ericsson-LG Enterprise +OUI:B064E0* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:B0653A* ID_OUI_FROM_DATABASE=Murata Manufacturing Co., Ltd. @@ -126251,6 +128339,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 @@ -126422,9 +128513,15 @@ OUI:B09AE2* OUI:B09BD4* ID_OUI_FROM_DATABASE=GNH Software India Private Limited +OUI:B09C18* + ID_OUI_FROM_DATABASE=Shenzhen Taichi Technology Limited + OUI:B09C63* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd +OUI:B09CB2* + ID_OUI_FROM_DATABASE=Google, Inc. + OUI:B09E1B* ID_OUI_FROM_DATABASE=Butlr Technologies, Inc. @@ -126599,6 +128696,9 @@ OUI:B0B98A* OUI:B0BB8B* ID_OUI_FROM_DATABASE=WAVETEL TECHNOLOGY LIMITED +OUI:B0BBA9* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:B0BBE5* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS @@ -126839,6 +128939,9 @@ OUI:B0D5CC* OUI:B0D5FB* ID_OUI_FROM_DATABASE=Google, Inc. +OUI:B0D616* + ID_OUI_FROM_DATABASE=Super Micro Computer, Inc. + OUI:B0D77E* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -126869,6 +128972,9 @@ OUI:B0DD74* OUI:B0DE28* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:B0DE31* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:B0DF3A* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -126941,6 +129047,9 @@ OUI:B0EB57* OUI:B0EB7F* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:B0EC69* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:B0EC71* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -126962,6 +129071,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. @@ -127226,6 +129338,9 @@ OUI:B41513* OUI:B4157E* ID_OUI_FROM_DATABASE=Celona Inc. +OUI:B41584* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:B41678* ID_OUI_FROM_DATABASE=Juniper Networks @@ -127256,6 +129371,9 @@ OUI:B41C30* OUI:B41CAB* ID_OUI_FROM_DATABASE=ICR, inc. +OUI:B41CAF* + ID_OUI_FROM_DATABASE=UAB TELTONIKA NETWORKS + OUI:B41D2B* ID_OUI_FROM_DATABASE=Shenzhen YOUHUA Technology Co., Ltd @@ -127454,6 +129572,9 @@ OUI:B43A96* OUI:B43AE2* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:B43B52* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:B43D08* ID_OUI_FROM_DATABASE=GX International BV @@ -127709,6 +129830,9 @@ OUI:B4636F* OUI:B46415* ID_OUI_FROM_DATABASE=Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +OUI:B465DC* + ID_OUI_FROM_DATABASE=CHINA DRAGON TECHNOLOGY LIMITED + OUI:B46698* ID_OUI_FROM_DATABASE=Zealabs srl @@ -127805,6 +129929,9 @@ OUI:B47CA6* OUI:B47D76* ID_OUI_FROM_DATABASE=KNS Group LLC +OUI:B47E9F* + ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd + OUI:B47F5E* ID_OUI_FROM_DATABASE=Foresight Manufacture (S) Pte Ltd @@ -127868,6 +129995,9 @@ OUI:B48B19* OUI:B48C9D* ID_OUI_FROM_DATABASE=AzureWave Technology Inc. +OUI:B490E5* + ID_OUI_FROM_DATABASE=GD Midea Air-Conditioning Equipment Co.,Ltd. + OUI:B49107* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -127895,6 +130025,9 @@ OUI:B4994C* OUI:B499BA* ID_OUI_FROM_DATABASE=Hewlett Packard +OUI:B49A7D* + ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + OUI:B49A95* ID_OUI_FROM_DATABASE=Shenzhen Boomtech Industrial Corporation @@ -128144,6 +130277,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 @@ -128321,6 +130457,9 @@ OUI:B4DE31* OUI:B4DEDF* ID_OUI_FROM_DATABASE=zte corporation +OUI:B4DF09* + ID_OUI_FROM_DATABASE=FLUX:: + OUI:B4DF3B* ID_OUI_FROM_DATABASE=Chromlech @@ -128490,7 +130629,7 @@ OUI:B4FA48* ID_OUI_FROM_DATABASE=Apple, Inc. OUI:B4FBE3* - ID_OUI_FROM_DATABASE=AltoBeam (China) Inc. + ID_OUI_FROM_DATABASE=AltoBeam Inc. OUI:B4FBE4* ID_OUI_FROM_DATABASE=Ubiquiti Inc @@ -128549,12 +130688,18 @@ 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 OUI:B80BDA* ID_OUI_FROM_DATABASE=GD Midea Air-Conditioning Equipment Co.,Ltd. +OUI:B80E1D* + ID_OUI_FROM_DATABASE=PAX Computer Technology(Shenzhen) Ltd. + OUI:B810D4* ID_OUI_FROM_DATABASE=Masimo Corporation @@ -128624,6 +130769,9 @@ OUI:B81E9E* OUI:B81EA4* ID_OUI_FROM_DATABASE=Liteon Technology Corporation +OUI:B81F3F* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:B81F5E* ID_OUI_FROM_DATABASE=Apption Labs Limited @@ -128711,6 +130859,9 @@ OUI:B83241* OUI:B8328F* ID_OUI_FROM_DATABASE=eero inc. +OUI:B83446* + ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company + OUI:B836D8* ID_OUI_FROM_DATABASE=Videoswitch @@ -128729,6 +130880,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 +131066,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 @@ -129068,6 +131225,9 @@ OUI:B8797E* OUI:B87AC9* ID_OUI_FROM_DATABASE=Siemens Ltd. +OUI:B87B4D* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:B87BC5* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -129224,6 +131384,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. @@ -129269,6 +131432,9 @@ OUI:B89BE4* OUI:B89C13* ID_OUI_FROM_DATABASE=Alps Alpine +OUI:B89DE5* + ID_OUI_FROM_DATABASE=ASIX Electronics Corporation + OUI:B89EA6* ID_OUI_FROM_DATABASE=SPBEC-MINING CO.LTD @@ -129524,12 +131690,18 @@ OUI:B8CEF6* OUI:B8D06F* ID_OUI_FROM_DATABASE=GUANGZHOU HKUST FOK YING TUNG RESEARCH INSTITUTE +OUI:B8D08F* + ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. + OUI:B8D0F0* ID_OUI_FROM_DATABASE=FCNT LLC OUI:B8D309* ID_OUI_FROM_DATABASE=Cox Communications, Inc +OUI:B8D3EF* + ID_OUI_FROM_DATABASE=UNISEM (M) BERHAD + OUI:B8D43E* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. @@ -129557,6 +131729,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. @@ -129623,6 +131798,9 @@ OUI:B8D94D* OUI:B8D9CE* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:B8DA5E* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:B8DAE8* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -129638,6 +131816,12 @@ OUI:B8DB1C* OUI:B8DB38* ID_OUI_FROM_DATABASE=Google, Inc. +OUI:B8DC28* + ID_OUI_FROM_DATABASE=Extreme Networks Headquarters + +OUI:B8DC7D* + ID_OUI_FROM_DATABASE=VusionGroup + OUI:B8DC87* ID_OUI_FROM_DATABASE=IAI Corporation @@ -129905,6 +132089,9 @@ OUI:BC13A8* OUI:BC1401* ID_OUI_FROM_DATABASE=Hitron Technologies. Inc +OUI:BC1469* + ID_OUI_FROM_DATABASE=Phyplus Technology (Shanghai) Co., Ltd + OUI:BC1485* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -130124,6 +132311,9 @@ OUI:BC3340* OUI:BC33AC* ID_OUI_FROM_DATABASE=Silicon Laboratories +OUI:BC33DB* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:BC34000* ID_OUI_FROM_DATABASE=Redvision CCTV @@ -130304,6 +132494,9 @@ OUI:BC4E3C* OUI:BC4E5D* ID_OUI_FROM_DATABASE=ZhongMiao Technology Co., Ltd. +OUI:BC4F2D* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:BC515F* ID_OUI_FROM_DATABASE=Nokia Solutions and Networks India Private Limited @@ -130463,6 +132656,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. @@ -130628,6 +132824,9 @@ OUI:BC89A6* OUI:BC89A7* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:BC89C1* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:BC89F8* ID_OUI_FROM_DATABASE=GD Midea Air-Conditioning Equipment Co.,Ltd. @@ -130670,6 +132869,9 @@ OUI:BC9307* OUI:BC9325* ID_OUI_FROM_DATABASE=Ningbo Joyson Preh Car Connect Co.,Ltd. +OUI:BC932A* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:BC9424* ID_OUI_FROM_DATABASE=TCT mobile ltd @@ -130829,6 +133031,9 @@ OUI:BCA5A9* OUI:BCA68D* ID_OUI_FROM_DATABASE=Continetal Automotive Systems Sibiu +OUI:BCA6E7* + ID_OUI_FROM_DATABASE=Sichuan Odot Automation System Co., Ltd. + OUI:BCA8A6* ID_OUI_FROM_DATABASE=Intel Corporate @@ -130895,6 +133100,9 @@ OUI:BCB2CC* OUI:BCB308* ID_OUI_FROM_DATABASE=HONGKONG RAGENTEK COMMUNICATION TECHNOLOGY CO.,LIMITED +OUI:BCB30E* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:BCB4FD* ID_OUI_FROM_DATABASE=NXP Semiconductor (Tianjin) LTD. @@ -130937,6 +133145,9 @@ OUI:BCBD9E* OUI:BCBEFB* ID_OUI_FROM_DATABASE=ASL Xiamen Technology CO., LTD +OUI:BCBF2E* + ID_OUI_FROM_DATABASE=ASUSTek COMPUTER INC. + OUI:BCC00F* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD @@ -130958,6 +133169,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 @@ -131228,6 +133442,9 @@ OUI:BCF9F2* OUI:BCFAB8* ID_OUI_FROM_DATABASE=Guangzhou Shiyuan Electronic Technology Company Limited +OUI:BCFABA* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:BCFAEB* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -131705,6 +133922,9 @@ OUI:C04E8A* OUI:C05064* ID_OUI_FROM_DATABASE=SHENNAN CIRCUITS CO.,LTD +OUI:C05097* + ID_OUI_FROM_DATABASE=EG4 Electronics + OUI:C0515C* ID_OUI_FROM_DATABASE=zte corporation @@ -131849,6 +134069,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. @@ -132020,6 +134243,9 @@ OUI:C08ADE* OUI:C08B05* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:C08B27* + ID_OUI_FROM_DATABASE=FN-LINK TECHNOLOGY Ltd. + OUI:C08B2A* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -132188,6 +134414,9 @@ OUI:C0A26D* OUI:C0A364* ID_OUI_FROM_DATABASE=3D Systems Massachusetts +OUI:C0A36D* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:C0A36E* ID_OUI_FROM_DATABASE=SKY UK LIMITED @@ -132203,6 +134432,9 @@ OUI:C0A476* OUI:C0A4B9* ID_OUI_FROM_DATABASE=Sichuan AI-Link Technology Co., Ltd. +OUI:C0A4CF* + ID_OUI_FROM_DATABASE=Nintendo Co.,Ltd + OUI:C0A53E* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -132291,7 +134523,7 @@ OUI:C0B8E6* ID_OUI_FROM_DATABASE=Ruijie Networks Co.,LTD OUI:C0BA1F* - ID_OUI_FROM_DATABASE=Private + ID_OUI_FROM_DATABASE=Xiamen Milesight IoT Co., Ltd. OUI:C0BAE6* ID_OUI_FROM_DATABASE=Zenitel GB Ltd @@ -132347,6 +134579,9 @@ OUI:C0C687* OUI:C0C70A* ID_OUI_FROM_DATABASE=Ruckus Wireless +OUI:C0C7B5* + ID_OUI_FROM_DATABASE=Infinix mobility limited + OUI:C0C7DB* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -132362,6 +134597,9 @@ OUI:C0C989* OUI:C0C9E3* ID_OUI_FROM_DATABASE=TP-LINK TECHNOLOGIES CO.,LTD. +OUI:C0CAA2* + ID_OUI_FROM_DATABASE=ESSYS + OUI:C0CB38* ID_OUI_FROM_DATABASE=Hon Hai Precision Ind. Co.,Ltd. @@ -132545,6 +134783,9 @@ OUI:C0E434* OUI:C0E54E* ID_OUI_FROM_DATABASE=ARIES Embedded GmbH +OUI:C0E579* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:C0E5DA* ID_OUI_FROM_DATABASE=Qingdao Intelligent&Precise Electronics Co.,Ltd. @@ -132713,6 +134954,9 @@ OUI:C0FBF9D* OUI:C0FBF9E* ID_OUI_FROM_DATABASE=Navitas Digital Safety Ltd +OUI:C0FD6F* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:C0FD84* ID_OUI_FROM_DATABASE=zte corporation @@ -133265,6 +135509,9 @@ OUI:C468D0* OUI:C4693E* ID_OUI_FROM_DATABASE=Turbulence Design Inc. +OUI:C46940* + ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd + OUI:C469F0* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -133445,6 +135692,51 @@ 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:C482723* + ID_OUI_FROM_DATABASE=NextSilicon + +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. @@ -133523,6 +135815,9 @@ OUI:C489ED* OUI:C48A5A* ID_OUI_FROM_DATABASE=JFCONTROL +OUI:C48ACE* + ID_OUI_FROM_DATABASE=HISENSE VISUAL TECHNOLOGY CO.,LTD + OUI:C48B66* ID_OUI_FROM_DATABASE=Hui Zhou Gaoshengda Technology Co.,LTD @@ -133550,6 +135845,9 @@ OUI:C491CF* OUI:C4924C* ID_OUI_FROM_DATABASE=KEISOKUKI CENTER CO.,LTD. +OUI:C492D9* + ID_OUI_FROM_DATABASE=zte corporation + OUI:C49300* ID_OUI_FROM_DATABASE=8Devices @@ -133619,6 +135917,9 @@ OUI:C4955F* OUI:C495A2* ID_OUI_FROM_DATABASE=SHENZHEN WEIJIU INDUSTRY AND TRADE DEVELOPMENT CO., LTD +OUI:C4969F* + ID_OUI_FROM_DATABASE=Amazon Technologies Inc. + OUI:C49805* ID_OUI_FROM_DATABASE=Minieum Networks, Inc @@ -133826,6 +136127,9 @@ OUI:C4A81D* OUI:C4A9B8* ID_OUI_FROM_DATABASE=XIAMENSHI C-CHIP TECHNOLOGY CO.,LTD +OUI:C4AA43* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:C4AA99* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -133907,6 +136211,9 @@ OUI:C4BB03* OUI:C4BB4C* ID_OUI_FROM_DATABASE=Zebra Information Tech Co. Ltd +OUI:C4BB89* + ID_OUI_FROM_DATABASE=Shanghai High-Flying Electronics Technology Co.,Ltd + OUI:C4BBEA* ID_OUI_FROM_DATABASE=Pakedge Device and Software Inc @@ -134355,7 +136662,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 +136859,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 @@ -134636,6 +136946,9 @@ OUI:C82E47* OUI:C82E94* ID_OUI_FROM_DATABASE=Halfa Enterprise Co., Ltd. +OUI:C83049* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:C83168* ID_OUI_FROM_DATABASE=eZEX corporation @@ -134717,6 +137030,9 @@ OUI:C84029* OUI:C84052* ID_OUI_FROM_DATABASE=PAX Computer Technology(Shenzhen) Ltd. +OUI:C8412E* + ID_OUI_FROM_DATABASE=AM Telecom co., Ltd. + OUI:C8418A* ID_OUI_FROM_DATABASE=Samsung Electronics.,LTD @@ -134928,7 +137244,7 @@ OUI:C86314B* ID_OUI_FROM_DATABASE=Shenzhen Lihewei Electronics Co.,Ltd.Hunan Branch OUI:C86314C* - ID_OUI_FROM_DATABASE=Freeus LLC + ID_OUI_FROM_DATABASE=Becklar, LLC OUI:C86314D* ID_OUI_FROM_DATABASE=Telematix AG @@ -135140,6 +137456,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 +137894,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. @@ -135651,7 +137973,7 @@ OUI:C8D719* ID_OUI_FROM_DATABASE=Cisco-Linksys, LLC OUI:C8D778* - ID_OUI_FROM_DATABASE=BSH Hausgeraete GmbH + ID_OUI_FROM_DATABASE=BSH Hausgeräte GmbH OUI:C8D779* ID_OUI_FROM_DATABASE=QING DAO HAIER TELECOM CO.,LTD. @@ -136007,6 +138329,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 @@ -136031,6 +138356,9 @@ OUI:CC115A* OUI:CC1228* ID_OUI_FROM_DATABASE=HISENSE VISUAL TECHNOLOGY CO.,LTD +OUI:CC13F3* + ID_OUI_FROM_DATABASE=Hangzhou Hikvision Digital Technology Co.,Ltd. + OUI:CC14A6* ID_OUI_FROM_DATABASE=Yichun MyEnergy Domain, Inc @@ -136112,6 +138440,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 +138578,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 @@ -136550,6 +138884,9 @@ OUI:CC60BB* OUI:CC60C8* ID_OUI_FROM_DATABASE=Microsoft Corporation +OUI:CC6146* + ID_OUI_FROM_DATABASE=GSD VIET NAM TECHNOLOGY COMPANY LIMITED + OUI:CC61E5* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company @@ -136679,6 +139016,9 @@ OUI:CC79D7* OUI:CC7A30* ID_OUI_FROM_DATABASE=CMAX Wireless Co., Ltd. +OUI:CC7A8B* + ID_OUI_FROM_DATABASE=SHENZHEN TECNO TECHNOLOGY + OUI:CC7B35* ID_OUI_FROM_DATABASE=zte corporation @@ -136712,6 +139052,9 @@ OUI:CC808F* OUI:CC812A* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. +OUI:CC8130* + ID_OUI_FROM_DATABASE=Intelbras + OUI:CC817D* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -136865,6 +139208,9 @@ OUI:CCA223* OUI:CCA260* ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD +OUI:CCA30C* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:CCA374* ID_OUI_FROM_DATABASE=Guangdong Guanglian Electronic Technology Co.Ltd @@ -137075,6 +139421,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 @@ -137273,6 +139622,9 @@ OUI:CCDBA7* OUI:CCDC55* ID_OUI_FROM_DATABASE=Dragonchip Limited +OUI:CCDD28* + ID_OUI_FROM_DATABASE=ACCTON TECHNOLOGY CORPORATION + OUI:CCDD58* ID_OUI_FROM_DATABASE=Robert Bosch GmbH @@ -137309,6 +139661,51 @@ OUI:CCE686* OUI:CCE798* ID_OUI_FROM_DATABASE=My Social Stuff +OUI:CCE7DE0* + ID_OUI_FROM_DATABASE=Shenzhen Jooan Technology Co., Ltd + +OUI:CCE7DE1* + ID_OUI_FROM_DATABASE=Kaze.AI Technology Co.,Ltd. + +OUI:CCE7DE2* + ID_OUI_FROM_DATABASE=Octopus Energy Ltd + +OUI:CCE7DE3* + ID_OUI_FROM_DATABASE=Private + +OUI:CCE7DE4* + ID_OUI_FROM_DATABASE=Fareco + +OUI:CCE7DE5* + ID_OUI_FROM_DATABASE=Chengdu Vantron Technology Co., Ltd. + +OUI:CCE7DE6* + ID_OUI_FROM_DATABASE=Skylight + +OUI:CCE7DE7* + ID_OUI_FROM_DATABASE=Shanghai Dabuziduo Information and Technology Co., Ltd. + +OUI:CCE7DE8* + ID_OUI_FROM_DATABASE=Private + +OUI:CCE7DE9* + ID_OUI_FROM_DATABASE=Shenzhen Qichang Intelligent Technology Co., Ltd. + +OUI:CCE7DEA* + ID_OUI_FROM_DATABASE=Opal Camera Inc. + +OUI:CCE7DEB* + ID_OUI_FROM_DATABASE=3D Computing + +OUI:CCE7DEC* + ID_OUI_FROM_DATABASE=Guangdong Sirivision Communication Technology Co.,LTD. + +OUI:CCE7DED* + ID_OUI_FROM_DATABASE=Juke Audio + +OUI:CCE7DEE* + ID_OUI_FROM_DATABASE=Shenzhen Xingyi Intelligent Technology Co.,Ltd + OUI:CCE7DF* ID_OUI_FROM_DATABASE=American Magnetics, Inc. @@ -137411,6 +139808,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 @@ -137444,6 +139844,9 @@ OUI:D003EB* OUI:D00401* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company +OUI:D00477* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:D00492* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD @@ -137477,6 +139880,9 @@ OUI:D00AAB* OUI:D00B27* ID_OUI_FROM_DATABASE=Murata Manufacturing Co., Ltd. +OUI:D00C5E* + ID_OUI_FROM_DATABASE=Nanjing Qinheng Microelectronics Co., Ltd. + OUI:D00DF7* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -137504,6 +139910,9 @@ OUI:D012CB* OUI:D0131E* ID_OUI_FROM_DATABASE=Sunrex Technology Corp +OUI:D013C1* + ID_OUI_FROM_DATABASE=Shenzhen Skyworth Digital Technology CO., Ltd + OUI:D013FD* ID_OUI_FROM_DATABASE=LG Electronics (Mobile Communications) @@ -137798,6 +140207,9 @@ OUI:D03169* OUI:D032C3* ID_OUI_FROM_DATABASE=D-Link Corporation +OUI:D032E7* + ID_OUI_FROM_DATABASE=Tuya Smart Inc. + OUI:D03311* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -137948,6 +140360,9 @@ OUI:D05475* OUI:D05509* ID_OUI_FROM_DATABASE=Nintendo Co.,Ltd +OUI:D05533* + ID_OUI_FROM_DATABASE=Palo Alto Networks + OUI:D055B2* ID_OUI_FROM_DATABASE=Integrated Device Technology (Malaysia) Sdn. Bhd. @@ -138296,9 +140711,18 @@ OUI:D0817A* OUI:D081C5* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:D082E8* + ID_OUI_FROM_DATABASE=Tuya Smart Inc. + +OUI:D082EB* + ID_OUI_FROM_DATABASE=Tuya Smart Inc. + OUI:D083D4* ID_OUI_FROM_DATABASE=Xtel Wireless ApS +OUI:D0845D* + ID_OUI_FROM_DATABASE=B&C Transit, Inc. + OUI:D084B0* ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS @@ -138458,6 +140882,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. @@ -138671,6 +141098,9 @@ OUI:D0AFB6* OUI:D0B0CD* ID_OUI_FROM_DATABASE=Moen +OUI:D0B0DF* + ID_OUI_FROM_DATABASE=Mimosa Networks + OUI:D0B128* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -138713,6 +141143,9 @@ OUI:D0B5C2* OUI:D0B60A* ID_OUI_FROM_DATABASE=Xingluo Technology Company Limited +OUI:D0B646* + ID_OUI_FROM_DATABASE=NXP Semiconductors Taiwan Ltd. + OUI:D0B66F* ID_OUI_FROM_DATABASE=SERNET (SUZHOU) TECHNOLOGIES CORPORATION @@ -138914,6 +141347,9 @@ OUI:D0D471* OUI:D0D49F* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:D0D4FB* + ID_OUI_FROM_DATABASE=Home Control Singapore Pte Ltd + OUI:D0D6CC* ID_OUI_FROM_DATABASE=Wintop @@ -139025,6 +141461,9 @@ OUI:D0E828* OUI:D0EA11* ID_OUI_FROM_DATABASE=Routerboard.com +OUI:D0EA30* + ID_OUI_FROM_DATABASE=NXP Semiconductors Taiwan Ltd. + OUI:D0EB03* ID_OUI_FROM_DATABASE=Zhehua technology limited @@ -139142,6 +141581,9 @@ OUI:D404E6* OUI:D404FF* ID_OUI_FROM_DATABASE=Juniper Networks +OUI:D40592* + ID_OUI_FROM_DATABASE=Espressif Inc. + OUI:D40598* ID_OUI_FROM_DATABASE=Commscope @@ -139388,6 +141830,12 @@ OUI:D429A7* OUI:D429EA* ID_OUI_FROM_DATABASE=Zimory GmbH +OUI:D42B6F* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + +OUI:D42BF0* + ID_OUI_FROM_DATABASE=Tiinlab Corporation + OUI:D42C0F* ID_OUI_FROM_DATABASE=Commscope @@ -139532,6 +141980,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 +142019,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 @@ -139610,6 +142064,9 @@ OUI:D4548B* OUI:D45556* ID_OUI_FROM_DATABASE=Fiber Mountain Inc. +OUI:D455AD* + ID_OUI_FROM_DATABASE=go-e GmbH + OUI:D455BE* ID_OUI_FROM_DATABASE=SHENZHEN FAST TECHNOLOGIES CO.,LTD @@ -139751,6 +142208,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 @@ -139826,6 +142286,9 @@ OUI:D47208* OUI:D47226* ID_OUI_FROM_DATABASE=zte corporation +OUI:D47327* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:D47350* ID_OUI_FROM_DATABASE=DBG Commnunications Technology Co., Ltd. @@ -140054,6 +142517,9 @@ OUI:D49400* OUI:D4945A* ID_OUI_FROM_DATABASE=COSMO CO., LTD +OUI:D49477* + ID_OUI_FROM_DATABASE=FONEX Data Systems Inc. + OUI:D494A1* ID_OUI_FROM_DATABASE=Texas Instruments @@ -140138,6 +142604,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 @@ -140291,6 +142760,9 @@ OUI:D4B5CD* OUI:D4B680* ID_OUI_FROM_DATABASE=Shanghai Linkyum Microeletronics Co.,Ltd +OUI:D4B6C9* + ID_OUI_FROM_DATABASE=Nokia + OUI:D4B709* ID_OUI_FROM_DATABASE=zte corporation @@ -140465,6 +142937,9 @@ OUI:D4D51B* OUI:D4D659* ID_OUI_FROM_DATABASE=Meta Platforms, Inc. +OUI:D4D6DF* + ID_OUI_FROM_DATABASE=TP-Link Systems Inc. + OUI:D4D748* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -140873,6 +143348,9 @@ OUI:D825DF* OUI:D826B9* ID_OUI_FROM_DATABASE=Guangdong Coagent Electronics S&T Co.,Ltd. +OUI:D826D3* + ID_OUI_FROM_DATABASE=eero inc. + OUI:D826FA* ID_OUI_FROM_DATABASE=Jiangxi Zhentian Technology CO.,LTD @@ -141035,6 +143513,9 @@ OUI:D84567* OUI:D84606* ID_OUI_FROM_DATABASE=Silicon Valley Global Marketing +OUI:D846CE* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:D84710* ID_OUI_FROM_DATABASE=Sichuan Changhong Electric Ltd. @@ -141143,6 +143624,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. @@ -141170,12 +143654,18 @@ OUI:D860B0* OUI:D860B3* ID_OUI_FROM_DATABASE=Guangdong Global Electronic Technology CO.,LTD +OUI:D860C5* + ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + OUI:D86162* ID_OUI_FROM_DATABASE=WNC Corporation 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 +143825,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. @@ -141536,6 +144029,9 @@ OUI:D8A315* OUI:D8A35C* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:D8A469* + ID_OUI_FROM_DATABASE=Sonova AG + OUI:D8A491* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -141605,6 +144101,9 @@ OUI:D8B12A* OUI:D8B190* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:D8B1DE* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:D8B249* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -141659,6 +144158,9 @@ OUI:D8BE1F* OUI:D8BE65* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:D8BF42* + ID_OUI_FROM_DATABASE=Intel Corporate + OUI:D8BF4C* ID_OUI_FROM_DATABASE=Victory Concept Electronics Limited @@ -141674,6 +144176,9 @@ OUI:D8C06A* OUI:D8C0A6* ID_OUI_FROM_DATABASE=AzureWave Technology Inc. +OUI:D8C262* + ID_OUI_FROM_DATABASE=Ubiquiti Inc + OUI:D8C3FB* ID_OUI_FROM_DATABASE=DETRACOM @@ -141737,6 +144242,9 @@ OUI:D8CF89* OUI:D8CF9C* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:D8CFB1* + ID_OUI_FROM_DATABASE=BRIGHT TECHNOLOGIES INDIA PRIVATE LIMITED + OUI:D8CFBF* ID_OUI_FROM_DATABASE=Motorola Mobility LLC, a Lenovo Company @@ -141938,6 +144446,9 @@ OUI:D8F8AF* OUI:D8FB11* ID_OUI_FROM_DATABASE=AXACORE +OUI:D8FB58* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:D8FB5E* ID_OUI_FROM_DATABASE=ASKEY COMPUTER CORP @@ -142082,6 +144593,9 @@ OUI:DC1A01* OUI:DC1AC5* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. +OUI:DC1B48* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:DC1BA1* ID_OUI_FROM_DATABASE=Intel Corporate @@ -142125,7 +144639,7 @@ OUI:DC2834* ID_OUI_FROM_DATABASE=HAKKO Corporation OUI:DC2919* - ID_OUI_FROM_DATABASE=AltoBeam (Xiamen) Technology Ltd, Co. + ID_OUI_FROM_DATABASE=AltoBeam(Xiamen)Technology Co., Ltd. OUI:DC293A* ID_OUI_FROM_DATABASE=Shenzhen Nuoshi Technology Co., LTD. @@ -142164,7 +144678,7 @@ OUI:DC2DCB* ID_OUI_FROM_DATABASE=Beijing Unis HengYue Technology Co., Ltd. OUI:DC2DDE* - ID_OUI_FROM_DATABASE=Ledworks SRL + ID_OUI_FROM_DATABASE=Illucere Srl OUI:DC2E6A* ID_OUI_FROM_DATABASE=HCT. Co., Ltd. @@ -142193,6 +144707,9 @@ OUI:DC3262* OUI:DC330D* ID_OUI_FROM_DATABASE=QING DAO HAIER TELECOM CO.,LTD. +OUI:DC330E* + ID_OUI_FROM_DATABASE=Qingdao Haier Technology Co.Ltd + OUI:DC333D* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -142619,6 +145136,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 @@ -142727,6 +145247,9 @@ OUI:DC86D8* OUI:DC87CB* ID_OUI_FROM_DATABASE=Beijing Perfectek Technologies Co., Ltd. +OUI:DC87F8* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:DC88A1* ID_OUI_FROM_DATABASE=ITEL MOBILE LIMITED @@ -142751,6 +145274,9 @@ OUI:DC8D91* OUI:DC8DB7* ID_OUI_FROM_DATABASE=ATW TECHNOLOGY, INC. +OUI:DC8E6D* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:DC8E8D* ID_OUI_FROM_DATABASE=Netis Technology Co., Ltd. @@ -142832,6 +145358,9 @@ OUI:DC9C99* OUI:DC9C9F* ID_OUI_FROM_DATABASE=Shenzhen YOUHUA Technology Co., Ltd +OUI:DC9DED* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:DC9E8F* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -142973,6 +145502,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 @@ -143030,6 +145562,9 @@ OUI:DCC793* OUI:DCC8F5* ID_OUI_FROM_DATABASE=Shanghai UMEinfo CO.,LTD. +OUI:DCCB35* + ID_OUI_FROM_DATABASE=EM Microelectronic + OUI:DCCBA8* ID_OUI_FROM_DATABASE=Explora Technologies Inc @@ -143144,6 +145679,9 @@ OUI:DCDCE2* OUI:DCDD24* ID_OUI_FROM_DATABASE=Energica Motor Company SpA +OUI:DCDE4B* + ID_OUI_FROM_DATABASE=SHENZHEN TRANSCHAN TECHNOLOGY LIMITED + OUI:DCDE4F* ID_OUI_FROM_DATABASE=Gionee Communication Equipment Co Ltd @@ -143291,6 +145829,9 @@ OUI:DCF090* OUI:DCF110* ID_OUI_FROM_DATABASE=Nokia Corporation +OUI:DCF144* + ID_OUI_FROM_DATABASE=Ocean Solution Technology + OUI:DCF31C* ID_OUI_FROM_DATABASE=Texas Instruments @@ -143873,6 +146414,9 @@ OUI:E04E7A* OUI:E04F43* ID_OUI_FROM_DATABASE=Universal Global Scientific Industrial., Ltd +OUI:E04F95* + ID_OUI_FROM_DATABASE=Sagemcom Broadband SAS + OUI:E04FBD* ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD @@ -144033,7 +146577,7 @@ OUI:E06C4E* ID_OUI_FROM_DATABASE=Shenzhen TINNO Mobile Technology Corp. OUI:E06CA6* - ID_OUI_FROM_DATABASE=Creotech Instruments S.A. + ID_OUI_FROM_DATABASE=Creotech Quantum SA OUI:E06CC5* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -144122,6 +146666,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 @@ -144185,6 +146732,9 @@ OUI:E092A7* OUI:E09467* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:E09559* + ID_OUI_FROM_DATABASE=Arcadyan Corporation + OUI:E09579* ID_OUI_FROM_DATABASE=ORTHOsoft inc, d/b/a Zimmer CAS @@ -144266,6 +146816,9 @@ OUI:E0A366* OUI:E0A3AC* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:E0A447* + ID_OUI_FROM_DATABASE=zte corporation + OUI:E0A498* ID_OUI_FROM_DATABASE=SHENZHEN ORFA TECH CO.,LTD @@ -144551,15 +147104,24 @@ OUI:E0D173* OUI:E0D1E6* ID_OUI_FROM_DATABASE=Aliph dba Jawbone +OUI:E0D239* + ID_OUI_FROM_DATABASE=TECHNOLID, LLC + OUI:E0D31A* ID_OUI_FROM_DATABASE=EQUES Technology Co., Limited 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 +OUI:E0D3F0* + ID_OUI_FROM_DATABASE=AltoBeam Inc. + OUI:E0D462* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -144632,6 +147194,9 @@ OUI:E0DEF2* OUI:E0DF13* ID_OUI_FROM_DATABASE=Hangzhou Hikvision Digital Technology Co.,Ltd. +OUI:E0DFB7* + ID_OUI_FROM_DATABASE=Zeica Labs Pte Ltd + OUI:E0E0C2* ID_OUI_FROM_DATABASE=China Mobile Group Device Co.,Ltd. @@ -144674,6 +147239,9 @@ OUI:E0E751* OUI:E0E7BB* ID_OUI_FROM_DATABASE=Nureva, Inc. +OUI:E0E805* + ID_OUI_FROM_DATABASE=SERNET (SUZHOU) TECHNOLOGIES CORPORATION + OUI:E0E8BB* ID_OUI_FROM_DATABASE=Unicom Vsens Telecommunications Co., Ltd. @@ -144764,6 +147332,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 +147359,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 @@ -145346,6 +147920,9 @@ OUI:E46E8A* OUI:E46F13* ID_OUI_FROM_DATABASE=D-Link International +OUI:E47010* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:E470B8* ID_OUI_FROM_DATABASE=Intel Corporate @@ -145508,6 +148085,9 @@ OUI:E48F34* OUI:E48F65* ID_OUI_FROM_DATABASE=Yelatma Instrument Making Enterprise, JSC +OUI:E48FB7* + ID_OUI_FROM_DATABASE=Arista Networks + OUI:E4902A* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -145601,6 +148181,9 @@ OUI:E498D1* OUI:E498D6* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:E498E0* + ID_OUI_FROM_DATABASE=Tonly Technology Co. Ltd + OUI:E4995F* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -145814,6 +148397,9 @@ OUI:E4C90B* OUI:E4CA12* ID_OUI_FROM_DATABASE=zte corporation +OUI:E4CA5F* + ID_OUI_FROM_DATABASE=Murata Manufacturing Co., Ltd. + OUI:E4CB59* ID_OUI_FROM_DATABASE=Beijing Loveair Science and Technology Co. Ltd. @@ -145850,6 +148436,9 @@ OUI:E4D3AA* OUI:E4D3F1* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:E4D436* + ID_OUI_FROM_DATABASE=Nokia Solutions and Networks India Private Limited + OUI:E4D53D* ID_OUI_FROM_DATABASE=Hon Hai Precision Ind. Co.,Ltd. @@ -145994,6 +148583,9 @@ OUI:E4FA5B* OUI:E4FAC4* ID_OUI_FROM_DATABASE=TP-Link Systems Inc +OUI:E4FADE* + ID_OUI_FROM_DATABASE=Microsoft Corporation + OUI:E4FAE4* ID_OUI_FROM_DATABASE=Shenzhen SDMC Technology CP,.LTD @@ -146003,6 +148595,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 @@ -146030,6 +148625,9 @@ OUI:E4FED9* OUI:E4FEE4* ID_OUI_FROM_DATABASE=Ciena Corporation +OUI:E4FF69* + ID_OUI_FROM_DATABASE=Holiday Robotics + OUI:E4FFDD* ID_OUI_FROM_DATABASE=ELECTRON INDIA @@ -146237,6 +148835,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 +149135,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. @@ -147056,6 +149660,12 @@ OUI:E8B723* OUI:E8B748* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:E8B853* + ID_OUI_FROM_DATABASE=GSD VIET NAM TECHNOLOGY COMPANY LIMITED + +OUI:E8BA17* + ID_OUI_FROM_DATABASE=Beijing Xiaomi Mobile Software Co., Ltd + OUI:E8BA70* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -147107,6 +149717,9 @@ OUI:E8C320* OUI:E8C386* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:E8C3C5* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:E8C417* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD @@ -147200,6 +149813,9 @@ OUI:E8D4E0* OUI:E8D52B* ID_OUI_FROM_DATABASE=Google, Inc. +OUI:E8D5A1* + ID_OUI_FROM_DATABASE=Earda Technologies co Ltd + OUI:E8D765* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -147305,6 +149921,9 @@ OUI:E8E8B7* OUI:E8E98E* ID_OUI_FROM_DATABASE=SOLAR controls s.r.o. +OUI:E8EA34* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:E8EA4D* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -147398,6 +150017,9 @@ OUI:E8F654* OUI:E8F673* ID_OUI_FROM_DATABASE=Microsoft Corporation +OUI:E8F674* + ID_OUI_FROM_DATABASE=Jiang Su Fulian Communication Technology Co.,Ltd + OUI:E8F6D70* ID_OUI_FROM_DATABASE=Mono Technologies Inc. @@ -147497,6 +150119,9 @@ OUI:E8FDE8* OUI:E8FDF8* ID_OUI_FROM_DATABASE=Shanghai High-Flying Electronics Technology Co., Ltd +OUI:E8FEBE* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:E8FF1E0* ID_OUI_FROM_DATABASE=Private @@ -147725,6 +150350,9 @@ OUI:EC2AF0* OUI:EC2BEB* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:EC2C0D* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:EC2C11* ID_OUI_FROM_DATABASE=CWD INNOVATION LIMITED @@ -147774,7 +150402,7 @@ OUI:EC316D* ID_OUI_FROM_DATABASE=Hansgrohe OUI:EC34E2* - ID_OUI_FROM_DATABASE=Private + ID_OUI_FROM_DATABASE=Yasmina Labs Trading FZE OUI:EC3532* ID_OUI_FROM_DATABASE=Tactrix Inc. @@ -148082,15 +150710,24 @@ 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 +OUI:EC715E* + ID_OUI_FROM_DATABASE=Freefly Systems Inc + OUI:EC71DB* ID_OUI_FROM_DATABASE=Reolink Innovation Limited 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. @@ -148241,6 +150878,9 @@ OUI:EC84B4* OUI:EC852F* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:EC86C4* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:EC888F* ID_OUI_FROM_DATABASE=TP-LINK TECHNOLOGIES CO.,LTD. @@ -148286,6 +150926,9 @@ OUI:EC8EAE* OUI:EC8EB5* ID_OUI_FROM_DATABASE=Hewlett Packard +OUI:EC8F72* + ID_OUI_FROM_DATABASE=Barrot Technology Co.,Ltd. + OUI:EC90C1* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -148463,6 +151106,9 @@ OUI:EC9F0DE* OUI:ECA138* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:ECA1CC* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:ECA1D1* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -148586,6 +151232,9 @@ OUI:ECB1D7* OUI:ECB1E0* ID_OUI_FROM_DATABASE=Eltex Enterprise LTD +OUI:ECB293* + ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise + OUI:ECB313* ID_OUI_FROM_DATABASE=SHENZHEN GONGJIN ELECTRONICS CO.,LT @@ -148601,6 +151250,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 @@ -148613,9 +151265,15 @@ OUI:ECB878* OUI:ECB907* ID_OUI_FROM_DATABASE=CloudGenix Inc +OUI:ECB931* + ID_OUI_FROM_DATABASE=TP-Link Systems Inc. + OUI:ECB970* ID_OUI_FROM_DATABASE=Ruijie Networks Co.,LTD +OUI:ECB9A5* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:ECBAFE* ID_OUI_FROM_DATABASE=GIROPTIC @@ -148739,6 +151397,9 @@ OUI:ECDA59* OUI:ECDB86* ID_OUI_FROM_DATABASE=API-K +OUI:ECDCAA* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:ECDD24* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -149414,8 +152075,11 @@ OUI:F040AFD* OUI:F040AFE* ID_OUI_FROM_DATABASE=Shanghai Kanghai Information System CO.,LTD. +OUI:F040D9* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:F040EC* - ID_OUI_FROM_DATABASE=RainX PTE. LTD. + ID_OUI_FROM_DATABASE=LOOPDESIGNLAB PTE. LTD OUI:F041C6* ID_OUI_FROM_DATABASE=Heat Tech Company, Ltd. @@ -149615,6 +152279,9 @@ OUI:F065DD* OUI:F06728* ID_OUI_FROM_DATABASE=GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +OUI:F067B1* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:F06853* ID_OUI_FROM_DATABASE=Integrated Corporation @@ -149867,6 +152534,9 @@ OUI:F0A35A* OUI:F0A3B2* ID_OUI_FROM_DATABASE=Hui Zhou Gaoshengda Technology Co.,LTD +OUI:F0A4EA* + ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. + OUI:F0A654* ID_OUI_FROM_DATABASE=CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. @@ -149990,6 +152660,9 @@ OUI:F0B11D* OUI:F0B13F* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. +OUI:F0B163* + ID_OUI_FROM_DATABASE=Texas Instruments + OUI:F0B2B9* ID_OUI_FROM_DATABASE=Intel Corporate @@ -150077,6 +152750,9 @@ OUI:F0C478* OUI:F0C558* ID_OUI_FROM_DATABASE=U.D.Electronic Corp. +OUI:F0C71F* + ID_OUI_FROM_DATABASE=Arista Networks + OUI:F0C725* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -150116,6 +152792,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 @@ -150413,6 +153092,12 @@ OUI:F0FEE7* OUI:F40046* ID_OUI_FROM_DATABASE=ON Semiconductor +OUI:F400A2* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + +OUI:F401CC* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:F40223* ID_OUI_FROM_DATABASE=PAX Computer Technology(Shenzhen) Ltd. @@ -150563,6 +153248,9 @@ OUI:F41563* OUI:F415FD* ID_OUI_FROM_DATABASE=Shanghai Pateo Electronic Equipment Manufacturing Co., Ltd. +OUI:F416E7* + ID_OUI_FROM_DATABASE=Skyverse Limited + OUI:F417B8* ID_OUI_FROM_DATABASE=AirTies Wireless Networks @@ -150620,6 +153308,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. @@ -150659,6 +153350,9 @@ OUI:F42012* OUI:F42015* ID_OUI_FROM_DATABASE=Guangzhou Shiyuan Electronic Technology Company Limited +OUI:F4204D* + ID_OUI_FROM_DATABASE=Mellanox Technologies, Inc. + OUI:F420550* ID_OUI_FROM_DATABASE=AsicFlag @@ -150722,6 +153416,9 @@ OUI:F42462* OUI:F4248B* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:F4253C* + ID_OUI_FROM_DATABASE=eero inc. + OUI:F42679* ID_OUI_FROM_DATABASE=Intel Corporate @@ -150770,6 +153467,9 @@ OUI:F42E48* OUI:F42E7F* ID_OUI_FROM_DATABASE=Hewlett Packard Enterprise +OUI:F42F97* + ID_OUI_FROM_DATABASE=Embrava USA, Inc + OUI:F4308B* ID_OUI_FROM_DATABASE=Xiaomi Communications Co Ltd @@ -151019,6 +153719,9 @@ OUI:F45B29* OUI:F45B73* ID_OUI_FROM_DATABASE=Wanjiaan Interconnected Technology Co., Ltd +OUI:F45BB4* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:F45C42* ID_OUI_FROM_DATABASE=Huawei Device Co., Ltd. @@ -151227,7 +153930,7 @@ OUI:F4700CC* ID_OUI_FROM_DATABASE=G.S.D GROUP INC. OUI:F4700CD* - ID_OUI_FROM_DATABASE=Freeus LLC + ID_OUI_FROM_DATABASE=Becklar, LLC OUI:F4700CE* ID_OUI_FROM_DATABASE=Shenzhen WeProTalk Technology Co., Ltd. @@ -151502,6 +154205,9 @@ OUI:F4A157* OUI:F4A17F* ID_OUI_FROM_DATABASE=Marquardt Electronics Technology (Shanghai) Co.Ltd +OUI:F4A1A6* + ID_OUI_FROM_DATABASE=Apple, Inc. + OUI:F4A294* ID_OUI_FROM_DATABASE=EAGLE WORLD DEVELOPMENT CO., LIMITED @@ -151596,7 +154302,7 @@ OUI:F4B164* ID_OUI_FROM_DATABASE=Lightning Telecommunications Technology Co. Ltd OUI:F4B19C* - ID_OUI_FROM_DATABASE=AltoBeam (China) Inc. + ID_OUI_FROM_DATABASE=AltoBeam Inc. OUI:F4B1C2* ID_OUI_FROM_DATABASE=Zhejiang Dahua Technology Co., Ltd. @@ -151652,6 +154358,9 @@ OUI:F4B7B3* OUI:F4B7E2* ID_OUI_FROM_DATABASE=Hon Hai Precision Ind. Co.,Ltd. +OUI:F4B821* + ID_OUI_FROM_DATABASE=Cisco Systems, Inc + OUI:F4B85E* ID_OUI_FROM_DATABASE=Texas Instruments @@ -151976,6 +154685,9 @@ OUI:F4F647* OUI:F4F70C* ID_OUI_FROM_DATABASE=Avang - neterbit +OUI:F4F91E* + ID_OUI_FROM_DATABASE=INGRAM MICRO SERVICES + OUI:F4F951* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -152186,6 +154898,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 @@ -152411,6 +155126,9 @@ OUI:F83094* OUI:F8313E* ID_OUI_FROM_DATABASE=endeavour GmbH +OUI:F832BA* + ID_OUI_FROM_DATABASE=VusionGroup + OUI:F832E4* ID_OUI_FROM_DATABASE=ASUSTek COMPUTER INC. @@ -152483,6 +155201,9 @@ OUI:F83EB0* OUI:F83F51* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd +OUI:F84068* + ID_OUI_FROM_DATABASE=SZ DJI Ronin Technology Co., Ltd. + OUI:F84288* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -152663,12 +155384,18 @@ OUI:F860F0* OUI:F86214* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:F8622A* + ID_OUI_FROM_DATABASE=eero inc. + OUI:F862AA* ID_OUI_FROM_DATABASE=xn systems OUI:F8633F* ID_OUI_FROM_DATABASE=Intel Corporate +OUI:F86347* + ID_OUI_FROM_DATABASE=Sichuan AI-Link Technology Co., Ltd. + OUI:F863D9* ID_OUI_FROM_DATABASE=Commscope @@ -152759,6 +155486,51 @@ OUI:F873A2* OUI:F873DF* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:F875280* + ID_OUI_FROM_DATABASE=Qube Cinema Technologies Pvt Ltd + +OUI:F875281* + ID_OUI_FROM_DATABASE=Wuhan Xingtuxinke ELectronic Co.,Ltd + +OUI:F875282* + ID_OUI_FROM_DATABASE=Lyte AI + +OUI:F875283* + ID_OUI_FROM_DATABASE=SGSG Science&Technology Co., Ltd. Zhuhai + +OUI:F875284* + ID_OUI_FROM_DATABASE=Annapurna labs + +OUI:F875285* + ID_OUI_FROM_DATABASE=NORBIT ASA + +OUI:F875286* + ID_OUI_FROM_DATABASE=KUNSHAN WONDERTEK TECHNOLOGY CO.,LTD. + +OUI:F875287* + ID_OUI_FROM_DATABASE=PANASONIC AUTOMOTIVE SYSTEM MALAYSIA + +OUI:F875288* + ID_OUI_FROM_DATABASE=Myers Emergency Power Systems + +OUI:F875289* + ID_OUI_FROM_DATABASE=RLS d.o.o. + +OUI:F87528A* + ID_OUI_FROM_DATABASE=Siact Hinton (Beijing) Intelligent Control Technology Co., Ltd. + +OUI:F87528B* + ID_OUI_FROM_DATABASE=SHENZHEN WISEWING INTERNET TECHNOLOGY CO.,LTD + +OUI:F87528C* + ID_OUI_FROM_DATABASE=Origin Acoustics, LLC + +OUI:F87528D* + ID_OUI_FROM_DATABASE=After Technologies + +OUI:F87528E* + ID_OUI_FROM_DATABASE=Ingersoll-Rand + OUI:F87588* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD @@ -153095,6 +155867,9 @@ OUI:F8A4FB* OUI:F8A5C5* ID_OUI_FROM_DATABASE=Cisco Systems, Inc +OUI:F8A5E6* + ID_OUI_FROM_DATABASE=Magicyo Technology CO.,Ltd + OUI:F8A73A* ID_OUI_FROM_DATABASE=Cisco Systems, Inc @@ -153164,6 +155939,9 @@ OUI:F8B156* OUI:F8B1DD* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:F8B22C* + ID_OUI_FROM_DATABASE=Roku, Inc + OUI:F8B2F3* ID_OUI_FROM_DATABASE=GUANGZHOU BOSMA TECHNOLOGY CO.,LTD @@ -153236,6 +156014,9 @@ OUI:F8B8B4* OUI:F8B95A* ID_OUI_FROM_DATABASE=LG Innotek +OUI:F8BA98* + ID_OUI_FROM_DATABASE=HUAQIN TECHNOLOGY CO., LTD + OUI:F8BAE6* ID_OUI_FROM_DATABASE=Nokia @@ -153311,6 +156092,51 @@ OUI:F8C903* OUI:F8C96C* ID_OUI_FROM_DATABASE=Fiberhome Telecommunication Technologies Co.,LTD +OUI:F8C9D60* + ID_OUI_FROM_DATABASE=Ningbo Ming Sing Optical R&D Co.,Ltd + +OUI:F8C9D61* + ID_OUI_FROM_DATABASE=Beijing Mlink Technology Inc. + +OUI:F8C9D62* + ID_OUI_FROM_DATABASE=Annapurna labs + +OUI:F8C9D63* + ID_OUI_FROM_DATABASE=CPflight_srl + +OUI:F8C9D64* + ID_OUI_FROM_DATABASE=Active Research Limited + +OUI:F8C9D65* + ID_OUI_FROM_DATABASE=Annapurna labs + +OUI:F8C9D66* + ID_OUI_FROM_DATABASE=Shanghai Innovatech Information Technology Co., Ltd. + +OUI:F8C9D67* + ID_OUI_FROM_DATABASE=Lecip Arcontia AB + +OUI:F8C9D68* + ID_OUI_FROM_DATABASE=Miri Technologies, Inc + +OUI:F8C9D69* + ID_OUI_FROM_DATABASE=Dimetix AG + +OUI:F8C9D6A* + ID_OUI_FROM_DATABASE=DIAS Infrared GmbH + +OUI:F8C9D6B* + ID_OUI_FROM_DATABASE=VT100 SRL + +OUI:F8C9D6C* + ID_OUI_FROM_DATABASE=Zhongzhen Huachuang(Shenzhen)Technology Co.,LTD + +OUI:F8C9D6D* + ID_OUI_FROM_DATABASE=Fortis Medical Devices LTD + +OUI:F8C9D6E* + ID_OUI_FROM_DATABASE=Shenzhen smart-core technology co.,ltd. + OUI:F8CA59* ID_OUI_FROM_DATABASE=NetComm Wireless @@ -153389,6 +156215,9 @@ OUI:F8D758* OUI:F8D7BF* ID_OUI_FROM_DATABASE=REV Ritter GmbH +OUI:F8D811* + ID_OUI_FROM_DATABASE=Quectel Wireless Solutions Co.,Ltd. + OUI:F8D9B8* ID_OUI_FROM_DATABASE=Open Mesh, Inc. @@ -153428,6 +156257,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 @@ -153467,6 +156299,9 @@ OUI:F8E61A* OUI:F8E71E* ID_OUI_FROM_DATABASE=Ruckus Wireless +OUI:F8E73C* + ID_OUI_FROM_DATABASE=Ufispace Co., LTD. + OUI:F8E7A0* ID_OUI_FROM_DATABASE=vivo Mobile Communication Co., Ltd. @@ -153494,6 +156329,9 @@ OUI:F8E968* OUI:F8EA0A* ID_OUI_FROM_DATABASE=Dipl.-Math. Michael Rauch +OUI:F8ECFE* + ID_OUI_FROM_DATABASE=Owl Home Inc. + OUI:F8EDA5* ID_OUI_FROM_DATABASE=Commscope @@ -153656,6 +156494,9 @@ OUI:FC0C45* OUI:FC0F4B* ID_OUI_FROM_DATABASE=Texas Instruments +OUI:FC0F76* + ID_OUI_FROM_DATABASE=Amazon Technologies Inc. + OUI:FC0FE6* ID_OUI_FROM_DATABASE=Sony Interactive Entertainment Inc. @@ -153671,6 +156512,9 @@ OUI:FC10BD* OUI:FC10C6* ID_OUI_FROM_DATABASE=Taicang T&W Electronics +OUI:FC1119* + ID_OUI_FROM_DATABASE=Silicon Laboratories + OUI:FC1165* ID_OUI_FROM_DATABASE=Cambium Networks Limited @@ -153710,6 +156554,9 @@ OUI:FC1803* OUI:FC183C* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:FC186B* + ID_OUI_FROM_DATABASE=Dot Origin Ltd + OUI:FC1910* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -153779,6 +156626,9 @@ OUI:FC2325* OUI:FC23CD* ID_OUI_FROM_DATABASE=SHENZHEN BILIAN ELECTRONIC CO.,LTD +OUI:FC2422* + ID_OUI_FROM_DATABASE=Hangzhou Ezviz Software Co.,Ltd. + OUI:FC253F* ID_OUI_FROM_DATABASE=Apple, Inc. @@ -153860,6 +156710,9 @@ OUI:FC35E6* OUI:FC372B* ID_OUI_FROM_DATABASE=Sichuan Tianyi Comheart Telecom Co.,LTD +OUI:FC376D* + ID_OUI_FROM_DATABASE=SHENZHEN BILIAN ELECTRONIC CO.,LTD + OUI:FC386A* ID_OUI_FROM_DATABASE=Shenzhen Skyworth Digital Technology CO., Ltd @@ -153962,6 +156815,9 @@ OUI:FC48EF* OUI:FC492D* ID_OUI_FROM_DATABASE=Amazon Technologies Inc. +OUI:FC4A47* + ID_OUI_FROM_DATABASE=Nokia + OUI:FC4AE9* ID_OUI_FROM_DATABASE=Castlenet Technology Inc. @@ -154046,6 +156902,9 @@ OUI:FC58DF* OUI:FC58FA* ID_OUI_FROM_DATABASE=Shen Zhen Shi Xin Zhong Xin Technology Co.,Ltd. +OUI:FC597A* + ID_OUI_FROM_DATABASE=Zebra Technologies Inc. + OUI:FC599F* ID_OUI_FROM_DATABASE=Ruijie Networks Co.,LTD @@ -154193,6 +157052,9 @@ OUI:FC6DC0* OUI:FC6DD1* ID_OUI_FROM_DATABASE=APRESIA Systems, Ltd. +OUI:FC6E83* + ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd + OUI:FC6FB7* ID_OUI_FROM_DATABASE=Commscope @@ -154337,6 +157199,9 @@ OUI:FC9257* OUI:FC931D* ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD +OUI:FC934E* + ID_OUI_FROM_DATABASE=REALTEK SEMICONDUCTOR CORP. + OUI:FC936B* ID_OUI_FROM_DATABASE=Samsung Electronics Co.,Ltd @@ -154652,6 +157517,9 @@ OUI:FCB6D8* OUI:FCB7F0* ID_OUI_FROM_DATABASE=Idaho National Laboratory +OUI:FCB948* + ID_OUI_FROM_DATABASE=McScience Inc. + OUI:FCB97E* ID_OUI_FROM_DATABASE=GE Appliances @@ -154700,6 +157568,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 +157769,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 @@ -154970,6 +157844,9 @@ OUI:FCE9D8* OUI:FCEA50* ID_OUI_FROM_DATABASE=Integrated Device Technology (Malaysia) Sdn. Bhd. +OUI:FCEB7B* + ID_OUI_FROM_DATABASE=HUAWEI TECHNOLOGIES CO.,LTD + OUI:FCECDA* ID_OUI_FROM_DATABASE=Ubiquiti Inc @@ -155033,6 +157910,9 @@ OUI:FCFBFB* OUI:FCFC48* ID_OUI_FROM_DATABASE=Apple, Inc. +OUI:FCFD71* + ID_OUI_FROM_DATABASE=AltoBeam Inc. + OUI:FCFE77* ID_OUI_FROM_DATABASE=Hitachi Reftechno, Inc. diff --git a/hwdb.d/20-acpi-vendor.hwdb b/hwdb.d/20-acpi-vendor.hwdb index 3828fea8ef9b9..22947c3759976 100644 --- a/hwdb.d/20-acpi-vendor.hwdb +++ b/hwdb.d/20-acpi-vendor.hwdb @@ -150,6 +150,9 @@ acpi:FTSC*: acpi:FUJI*: ID_VENDOR_FROM_DATABASE=Fujitsu Limited +acpi:GHIP*: + ID_VENDOR_FROM_DATABASE=Green Hippo + acpi:GHSW*: ID_VENDOR_FROM_DATABASE=Green Hills Software @@ -252,6 +255,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. @@ -381,6 +387,9 @@ acpi:SHRP*: acpi:SILC*: ID_VENDOR_FROM_DATABASE=Silicom Ltd. Connectivity Solutions +acpi:SIPL*: + ID_VENDOR_FROM_DATABASE=SIPEARL + acpi:SNSL*: ID_VENDOR_FROM_DATABASE=Sensel, Inc. @@ -417,6 +426,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..d58fd0e101674 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-06-19 10:08:54.454998323 +0100 ++++ 20-acpi-vendor.hwdb 2026-06-19 10:08:54.458998418 +0100 @@ -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 @@ +@@ -466,6 +468,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 @@ +@@ -493,6 +498,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 @@ +@@ -538,7 +546,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 @@ +@@ -817,6 +825,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 @@ +@@ -865,6 +876,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 @@ +@@ -874,6 +888,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 @@ +@@ -1055,7 +1072,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 @@ +@@ -1135,6 +1152,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 @@ +@@ -1294,6 +1314,9 @@ acpi:BML*: ID_VENDOR_FROM_DATABASE=BIOMED Lab @@ -97,7 +97,7 @@ acpi:BMS*: ID_VENDOR_FROM_DATABASE=BIOMEDISYS -@@ -1294,6 +1317,9 @@ +@@ -1306,6 +1329,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 @@ +@@ -1552,6 +1578,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 @@ +@@ -1717,6 +1746,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 @@ +@@ -2135,7 +2167,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 @@ +@@ -2488,6 +2520,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 @@ +@@ -2758,6 +2793,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 @@ +@@ -3134,7 +3172,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 @@ +@@ -3244,6 +3282,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 @@ +@@ -3379,6 +3420,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 @@ +@@ -3505,6 +3549,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 @@ +@@ -3601,6 +3648,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 @@ +@@ -3649,6 +3699,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 @@ +@@ -4177,6 +4230,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 @@ +@@ -4228,6 +4284,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 @@ +@@ -4246,6 +4305,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 @@ +@@ -4312,6 +4374,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 @@ +@@ -4346,7 +4411,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 @@ +@@ -4522,6 +4587,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 @@ +@@ -4768,6 +4836,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 @@ +@@ -5008,6 +5079,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 @@ +@@ -5548,6 +5622,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 @@ +@@ -5611,9 +5688,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 @@ +@@ -5704,9 +5778,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 @@ +@@ -6184,9 +6255,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 @@ +@@ -6361,9 +6429,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 @@ +@@ -6847,6 +6912,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 @@ +@@ -6931,6 +6999,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 @@ +@@ -7021,6 +7092,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 @@ +@@ -7063,6 +7137,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 @@ +@@ -7192,9 +7269,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 @@ +@@ -7507,14 +7581,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 @@ +@@ -7549,6 +7623,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 @@ +@@ -7810,9 +7887,6 @@ acpi:WAL*: ID_VENDOR_FROM_DATABASE=Wave Access @@ -435,7 +435,7 @@ acpi:WAV*: ID_VENDOR_FROM_DATABASE=Wavephore -@@ -7928,7 +8002,7 @@ +@@ -7940,7 +8014,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 @@ +@@ -7954,9 +8028,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 @@ +@@ -7990,9 +8061,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 @@ +@@ -8059,6 +8127,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..b92f2eacfd7bb 100644 --- a/hwdb.d/20-pci-vendor-model.hwdb +++ b/hwdb.d/20-pci-vendor-model.hwdb @@ -14,6 +14,24 @@ pci:v00000010d00008139* pci:v00000014* ID_VENDOR_FROM_DATABASE=Loongson Technology LLC +pci:v00000014d00003B0F* + ID_MODEL_FROM_DATABASE=DMA Adress Translation Unit [Loongson 3 Processor Family] + +pci:v00000014d00003C09* + ID_MODEL_FROM_DATABASE=Internal PCI to PCI Bridge [Loongson 3 Processor Family] + +pci:v00000014d00003C0F* + ID_MODEL_FROM_DATABASE=DMA Adress Translation Unit [Loongson 3 Processor Family] + +pci:v00000014d00003C19* + ID_MODEL_FROM_DATABASE=PCI Express x16 Root Port [Loongson 3 Processor Family] + +pci:v00000014d00003C29* + ID_MODEL_FROM_DATABASE=PCI Express x8 Root Port [Loongson 3 Processor Family] + +pci:v00000014d00003C39* + ID_MODEL_FROM_DATABASE=PCI Express x4 Root Port [Loongson 3 Processor Family] + pci:v00000014d00007A00* ID_MODEL_FROM_DATABASE=7A1000 Chipset Hyper Transport Bridge Controller @@ -33,7 +51,7 @@ pci:v00000014d00007A06* ID_MODEL_FROM_DATABASE=2K1000 / 7A1000 Chipset Display Controller pci:v00000014d00007A07* - ID_MODEL_FROM_DATABASE=2K1000/2000 / 7A1000/2000 Chipset HD Audio Controller + ID_MODEL_FROM_DATABASE=2K1000/2000/3000 / 3B6000M / 7A1000/2000 Chipset HD Audio Controller pci:v00000014d00007A08* ID_MODEL_FROM_DATABASE=2K1000 / 7A1000 Chipset 3Gb/s SATA AHCI Controller @@ -72,7 +90,7 @@ pci:v00000014d00007A17* ID_MODEL_FROM_DATABASE=7A1000 Chipset AC97 Audio Controller pci:v00000014d00007A18* - ID_MODEL_FROM_DATABASE=2K2000 / 7A2000 Chipset 6Gb/s SATA AHCI Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M / 7A2000 Chipset 6Gb/s SATA AHCI Controller pci:v00000014d00007A19* ID_MODEL_FROM_DATABASE=PCI-to-PCI Bridge @@ -81,10 +99,10 @@ pci:v00000014d00007A1A* ID_MODEL_FROM_DATABASE=2K2000 Configuration Bus pci:v00000014d00007A1B* - ID_MODEL_FROM_DATABASE=2K2000 / 7A2000 Chipset SPI Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M / 7A2000 Chipset SPI Controller pci:v00000014d00007A1D* - ID_MODEL_FROM_DATABASE=2K2000 RapidIO Interface + ID_MODEL_FROM_DATABASE=2K2000 / 2K3000 / 3B6000M RapidIO Interface pci:v00000014d00007A1E* ID_MODEL_FROM_DATABASE=2K2000 DES Controller @@ -92,6 +110,9 @@ pci:v00000014d00007A1E* pci:v00000014d00007A22* ID_MODEL_FROM_DATABASE=2K2000 Advanced Peripheral Bus Controller +pci:v00000014d00007A23* + ID_MODEL_FROM_DATABASE=Gigabit Ethernet Controller [Chipset / CPU inside] + pci:v00000014d00007A24* ID_MODEL_FROM_DATABASE=2K1000 / 7A1000/2000 Chipset USB OHCI Controller @@ -102,7 +123,7 @@ pci:v00000014d00007A26* ID_MODEL_FROM_DATABASE=2K1000 Camera Controller pci:v00000014d00007A27* - ID_MODEL_FROM_DATABASE=2K2000 / 7A2000 Chipset I2S Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M / 7A2000 Chipset I2S Controller pci:v00000014d00007A29* ID_MODEL_FROM_DATABASE=7A1000 Chipset PCIe x8 Bridge @@ -114,13 +135,16 @@ pci:v00000014d00007A2F* ID_MODEL_FROM_DATABASE=2K2000 DMA Controller pci:v00000014d00007A34* - ID_MODEL_FROM_DATABASE=2K2000 / 7A2000 Chipset USB 3.0 xHCI Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M / 7A2000 Chipset USB 3.0 xHCI Controller + +pci:v00000014d00007A35* + ID_MODEL_FROM_DATABASE=LG200 GPU pci:v00000014d00007A36* ID_MODEL_FROM_DATABASE=2K2000 / 7A2000 Chipset Display Controller pci:v00000014d00007A37* - ID_MODEL_FROM_DATABASE=2K2000 HDMI Audio Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M / 7A2000 Chipset HDMI Audio Controller pci:v00000014d00007A39* ID_MODEL_FROM_DATABASE=2K2000 / 7A2000 Chipset PCIe x1 Root Port @@ -128,11 +152,20 @@ pci:v00000014d00007A39* pci:v00000014d00007A3E* ID_MODEL_FROM_DATABASE=2K2000 RNG Controller +pci:v00000014d00007A42* + ID_MODEL_FROM_DATABASE=Advanced Peripheral Bus Controller [Chipset / CPU inside] + pci:v00000014d00007A44* - ID_MODEL_FROM_DATABASE=2K2000 USB 2.0 xHCI Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M USB 2.0 xHCI Controller + +pci:v00000014d00007A46* + ID_MODEL_FROM_DATABASE=Video Display Controller [Chipset / CPU inside] + +pci:v00000014d00007A47* + ID_MODEL_FROM_DATABASE=Pulse-Code Modulation [Chipset / CPU inside] pci:v00000014d00007A48* - ID_MODEL_FROM_DATABASE=2K2000 SDIO Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M SDIO Controller pci:v00000014d00007A49* ID_MODEL_FROM_DATABASE=2K2000 / 7A2000 Chipset PCIe x4 Root Port @@ -140,9 +173,15 @@ pci:v00000014d00007A49* pci:v00000014d00007A54* ID_MODEL_FROM_DATABASE=2K2000 OTG USB Controller +pci:v00000014d00007A56* + ID_MODEL_FROM_DATABASE=Video Processing Unit Decoder [Chipset / CPU inside] + pci:v00000014d00007A59* ID_MODEL_FROM_DATABASE=7A2000 Chipset PCIe x8 Root Port +pci:v00000014d00007A66* + ID_MODEL_FROM_DATABASE=Video Processing Unit Encoder [Chipset / CPU inside] + pci:v00000014d00007A69* ID_MODEL_FROM_DATABASE=7A2000 Chipset PCIe x16 Root Port @@ -150,11 +189,17 @@ pci:v00000014d00007A79* ID_MODEL_FROM_DATABASE=2K2000 PCIe Root Complex pci:v00000014d00007A88* - ID_MODEL_FROM_DATABASE=2K2000 eMMC Controller + ID_MODEL_FROM_DATABASE=2K2000/3000 / 3B6000M eMMC Controller + +pci:v00000014d00007A89* + ID_MODEL_FROM_DATABASE=PCI Express x1 Root Port [Chipset / CPU inside] pci:v00000014d00007A8E* ID_MODEL_FROM_DATABASE=2K2000 SE Controller +pci:v00000014d00007A99* + ID_MODEL_FROM_DATABASE=PCI Express x4 Root Port [Chipset / CPU inside] + pci:v00000014d00007AF9* ID_MODEL_FROM_DATABASE=2K2000 PCIe Endpoint @@ -434,6 +479,18 @@ pci:v00000721* pci:v00000731* ID_VENDOR_FROM_DATABASE=Jingjia Microelectronics Co Ltd +pci:v00000731d00000100* + ID_MODEL_FROM_DATABASE=JMN100 + +pci:v00000731d00000100sv00000731sd00000101* + ID_MODEL_FROM_DATABASE=JMN100 (16G) + +pci:v00000731d00001050* + ID_MODEL_FROM_DATABASE=JM1050 + +pci:v00000731d00001050sv00000731sd00001051* + ID_MODEL_FROM_DATABASE=JM1050 (8G) + pci:v00000731d00001100* ID_MODEL_FROM_DATABASE=JM1100 @@ -458,6 +515,21 @@ pci:v00000731d00001100sv00000731sd00001106* pci:v00000731d00001100sv00000731sd00001107* ID_MODEL_FROM_DATABASE=JM1100 (-EM) +pci:v00000731d00001100sv00000731sd00001108* + ID_MODEL_FROM_DATABASE=JM1100 (JY1008) + +pci:v00000731d00001100sv00000731sd00001109* + ID_MODEL_FROM_DATABASE=JM1100 (JY1032) + +pci:v00000731d00001102* + ID_MODEL_FROM_DATABASE=JM1100-Y + +pci:v00000731d00001102sv00000731sd00001121* + ID_MODEL_FROM_DATABASE=JM1100-Y (JY1032 LE 64G) + +pci:v00000731d00001102sv00000731sd00001122* + ID_MODEL_FROM_DATABASE=JM1100-Y (JY1032 LE 32G) + pci:v00000731d00007200* ID_MODEL_FROM_DATABASE=JM7200 Series GPU @@ -584,6 +656,24 @@ pci:v00000731d0000F111* pci:v00000731d0000FF11* ID_MODEL_FROM_DATABASE=JM1100-YV +pci:v00000731d0000FF11sv00000731sd00001105* + ID_MODEL_FROM_DATABASE=JM1100-YV (JM11 Series) + +pci:v00000731d0000FF11sv00000731sd00001108* + ID_MODEL_FROM_DATABASE=JM1100-YV (JY1008 vGPU) + +pci:v00000731d0000FF11sv00000731sd00001109* + ID_MODEL_FROM_DATABASE=JM1100-YV (JY1032 vGPU) + +pci:v00000731d0000FF11sv00000731sd00001121* + ID_MODEL_FROM_DATABASE=JM1100-YV (JY1032 LE vGPU) + +pci:v00000731d0000FF11sv00000731sd00001122* + ID_MODEL_FROM_DATABASE=JM1100-YV (JY1032 LE vGPU) + +pci:v00000771* + ID_VENDOR_FROM_DATABASE=Xi'an Microelectronics Technology Institute + pci:v00000777* ID_VENDOR_FROM_DATABASE=Ubiquiti Networks, Inc. @@ -893,6 +983,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 @@ -1775,6 +1871,9 @@ pci:v00001000d00000072sv00001000sd00003020* pci:v00001000d00000072sv00001000sd00003040* ID_MODEL_FROM_DATABASE=SAS2008 PCI-Express Fusion-MPT SAS-2 [Falcon] (9210-8i) +pci:v00001000d00000072sv00001000sd00003060* + ID_MODEL_FROM_DATABASE=SAS2008 PCI-Express Fusion-MPT SAS-2 [Falcon] (9212-4i4e) + pci:v00001000d00000072sv00001000sd00003080* ID_MODEL_FROM_DATABASE=SAS2008 PCI-Express Fusion-MPT SAS-2 [Falcon] (9200-8e [LSI SAS 6Gb/s SAS/SATA PCIe x8 External HBA]) @@ -2207,6 +2306,9 @@ pci:v00001000d00000095* pci:v00001000d00000096* ID_MODEL_FROM_DATABASE=SAS3004 PCI-Express Fusion-MPT SAS-3 +pci:v00001000d00000096sv00001000sd00003110* + ID_MODEL_FROM_DATABASE=SAS3004 PCI-Express Fusion-MPT SAS-3 (SAS9300-4i) + pci:v00001000d00000097* ID_MODEL_FROM_DATABASE=SAS3008 PCI-Express Fusion-MPT SAS-3 @@ -3422,6 +3524,9 @@ pci:v00001000d0000C034sv00001000sd00002004* pci:v00001000d0000C034sv00001000sd00002005* ID_MODEL_FROM_DATABASE=PEX890xx PCIe Gen 5 Switch (PEX89000 Virtual PCIe gDMA Endpoint) +pci:v00001000d0000C040* + ID_MODEL_FROM_DATABASE=PEX90xxx PCIe Gen 6 Switch + pci:v00001001* ID_VENDOR_FROM_DATABASE=Kolter Electronic @@ -3542,6 +3647,9 @@ pci:v00001002d0000131D* pci:v00001002d000013C0* ID_MODEL_FROM_DATABASE=Granite Ridge [Radeon Graphics] +pci:v00001002d000013DB* + ID_MODEL_FROM_DATABASE=Cyan Skillfish [PlayStation 5 APU] + pci:v00001002d000013E9* ID_MODEL_FROM_DATABASE=Ariel/Navi10Lite @@ -11736,13 +11844,13 @@ pci:v00001002d0000731Fsv00001DA2sd0000E411* ID_MODEL_FROM_DATABASE=Navi 10 [Radeon RX 5600 OEM/5600 XT / 5700/5700 XT] (Navi 10 [Radeon RX 5600 OEM/5600 XT / 5700/5700 XT]) pci:v00001002d00007340* - ID_MODEL_FROM_DATABASE=Navi 14 [Radeon RX 5500/5500M / Pro 5300/5500M] + ID_MODEL_FROM_DATABASE=Navi 14 [Radeon RX 5500/5500M / Pro 5300/5300M/5500M] pci:v00001002d00007340sv0000106Bsd00000210* - ID_MODEL_FROM_DATABASE=Navi 14 [Radeon RX 5500/5500M / Pro 5300/5500M] (Radeon Pro 5300M) + ID_MODEL_FROM_DATABASE=Navi 14 [Radeon RX 5500/5500M / Pro 5300/5300M/5500M] (MacBookPro16,1 (16", 2019) [Radeon Pro 5300M]) pci:v00001002d00007340sv0000106Bsd00000219* - ID_MODEL_FROM_DATABASE=Navi 14 [Radeon RX 5500/5500M / Pro 5300/5500M] (iMac (Retina 5K, 27-inch, 2020) [Radeon Pro 5300]) + ID_MODEL_FROM_DATABASE=Navi 14 [Radeon RX 5500/5500M / Pro 5300/5300M/5500M] (iMac (Retina 5K, 27-inch, 2020) [Radeon Pro 5300]) pci:v00001002d00007341* ID_MODEL_FROM_DATABASE=Navi 14 [Radeon Pro W5500] @@ -11963,6 +12071,9 @@ pci:v00001002d00007446* pci:v00001002d00007448* ID_MODEL_FROM_DATABASE=Navi 31 [Radeon Pro W7900] +pci:v00001002d00007449* + ID_MODEL_FROM_DATABASE=Navi 31 [Radeon Pro W7800 48GB] + pci:v00001002d0000744A* ID_MODEL_FROM_DATABASE=Navi 31 [Radeon Pro W7900 Dual Slot] @@ -12003,7 +12114,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] @@ -12074,8 +12185,14 @@ pci:v00001002d000074BD* pci:v00001002d00007550* ID_MODEL_FROM_DATABASE=Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] +pci:v00001002d00007550sv00001458sd00002437* + ID_MODEL_FROM_DATABASE=Navi 48 [Radeon RX 9070/9070 XT/9070 GRE] (Navi 48 XTX [Radeon RX 9070 XT Gaming OC ICE 16G]) + 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]) @@ -12086,6 +12203,9 @@ pci:v00001002d00007551* pci:v00001002d00007590* ID_MODEL_FROM_DATABASE=Navi 44 [Radeon RX 9060 XT] +pci:v00001002d00007590sv00001043sd00000639* + ID_MODEL_FROM_DATABASE=Navi 44 [Radeon RX 9060 XT] + pci:v00001002d00007590sv00001458sd00002429* ID_MODEL_FROM_DATABASE=Navi 44 [Radeon RX 9060 XT] (GV-R9060XTGAMING OC-16GD [Radeon RX 9060 XT GAMING OC 16G]) @@ -12419,6 +12539,9 @@ pci:v00001002d00009501* pci:v00001002d00009501sv0000174Bsd0000E620* ID_MODEL_FROM_DATABASE=RV670 [Radeon HD 3870] (Radeon HD 3870) +pci:v00001002d00009501sv00001787sd00002244* + ID_MODEL_FROM_DATABASE=RV670 [Radeon HD 3870] (Radeon HD 3870) + pci:v00001002d00009504* ID_MODEL_FROM_DATABASE=RV670/M88 [Mobility Radeon HD 3850] @@ -21071,6 +21194,9 @@ pci:v0000104Cd00008241sv00001014sd000004B2* pci:v0000104Cd00008400* ID_MODEL_FROM_DATABASE=ACX 100 22Mbps Wireless Interface +pci:v0000104Cd00008400sv0000111Asd00001021* + ID_MODEL_FROM_DATABASE=ACX 100 22Mbps Wireless Interface (SpeedStream 1021 (SS1021) Wireless Cardbus PC Card) + pci:v0000104Cd00008400sv00001186sd00003B00* ID_MODEL_FROM_DATABASE=ACX 100 22Mbps Wireless Interface (DWL-650+ PC Card cardbus 22Mbs Wireless Adapter [AirPlus]) @@ -21080,6 +21206,12 @@ pci:v0000104Cd00008400sv00001186sd00003B01* pci:v0000104Cd00008400sv00001395sd00002201* ID_MODEL_FROM_DATABASE=ACX 100 22Mbps Wireless Interface (WL22-PC) +pci:v0000104Cd00008400sv0000167Dsd0000B230* + ID_MODEL_FROM_DATABASE=ACX 100 22Mbps Wireless Interface (Netopia TER/WPC11N1 Wireless LAN Card) + +pci:v0000104Cd00008400sv000016A5sd00001801* + ID_MODEL_FROM_DATABASE=ACX 100 22Mbps Wireless Interface (WE302-TF) + pci:v0000104Cd00008400sv000016ABsd00008501* ID_MODEL_FROM_DATABASE=ACX 100 22Mbps Wireless Interface (WL-8305 IEEE802.11b+ Wireless LAN PCI Adapter) @@ -21113,6 +21245,9 @@ pci:v0000104Cd00009066sv0000104Csd00009067* pci:v0000104Cd00009066sv0000104Csd00009096* ID_MODEL_FROM_DATABASE=ACX 111 54Mbps Wireless Interface (Trendnet TEW-412PC Wireless PCI Adapter (Version A)) +pci:v0000104Cd00009066sv00001154sd0000032C* + ID_MODEL_FROM_DATABASE=ACX 111 54Mbps Wireless Interface (WLI-CB-G54L) + pci:v0000104Cd00009066sv00001186sd00003B04* ID_MODEL_FROM_DATABASE=ACX 111 54Mbps Wireless Interface (DWL-G520+ Wireless PCI Adapter) @@ -21131,6 +21266,12 @@ pci:v0000104Cd00009066sv000013D1sd0000ABA0* pci:v0000104Cd00009066sv000014EAsd0000AB07* ID_MODEL_FROM_DATABASE=ACX 111 54Mbps Wireless Interface (GW-NS54GM Wireless Cardbus Adapter) +pci:v0000104Cd00009066sv0000167Dsd0000B260* + ID_MODEL_FROM_DATABASE=ACX 111 54Mbps Wireless Interface (SWL-2600N) + +pci:v0000104Cd00009066sv000016ABsd00009067* + ID_MODEL_FROM_DATABASE=ACX 111 54Mbps Wireless Interface (A90-200WG-01 802.11g Wireless PC Card) + pci:v0000104Cd00009066sv000016ECsd0000010D* ID_MODEL_FROM_DATABASE=ACX 111 54Mbps Wireless Interface (USR5416 802.11g Wireless Turbo PCI Adapter) @@ -22277,9 +22418,54 @@ pci:v0000105B* pci:v0000105Bd00009602* ID_MODEL_FROM_DATABASE=RS780/RS880 PCI to PCI bridge (int gfx) +pci:v0000105Bd0000E0AB* + ID_MODEL_FROM_DATABASE=T99W175 5G Modem [Snapdragon X55] + +pci:v0000105Bd0000E0B0* + ID_MODEL_FROM_DATABASE=DW5930e 5G Modem [Snapdragon X55] + +pci:v0000105Bd0000E0B1* + ID_MODEL_FROM_DATABASE=DW5930e 5G Modem [Snapdragon X55] + +pci:v0000105Bd0000E0BF* + ID_MODEL_FROM_DATABASE=T99W175 5G Modem [Snapdragon X55] + pci:v0000105Bd0000E0C3* ID_MODEL_FROM_DATABASE=T99W175 5G Modem [Snapdragon X55] +pci:v0000105Bd0000E0D8* + ID_MODEL_FROM_DATABASE=T99W368 5G Modem [Snapdragon X65] + +pci:v0000105Bd0000E0D9* + ID_MODEL_FROM_DATABASE=T99W373 5G Modem [Snapdragon X62] + +pci:v0000105Bd0000E0F0* + ID_MODEL_FROM_DATABASE=T99W510 4G Modem [Snapdragon X24] + +pci:v0000105Bd0000E0F1* + ID_MODEL_FROM_DATABASE=T99W510 4G Modem [Snapdragon X24] + +pci:v0000105Bd0000E0F2* + ID_MODEL_FROM_DATABASE=T99W510 4G Modem [Snapdragon X24] + +pci:v0000105Bd0000E0F5* + ID_MODEL_FROM_DATABASE=DW5932e-eSIM 5G Modem [Snapdragon X62] + +pci:v0000105Bd0000E0F9* + ID_MODEL_FROM_DATABASE=DW5932e 5G Modem [Snapdragon X62] + +pci:v0000105Bd0000E118* + ID_MODEL_FROM_DATABASE=T99W640 5G Modem [Snapdragon X72] + +pci:v0000105Bd0000E11D* + ID_MODEL_FROM_DATABASE=DW5934e-eSIM 5G Modem [Snapdragon X72] + +pci:v0000105Bd0000E11E* + ID_MODEL_FROM_DATABASE=DW5934e 5G Modem [Snapdragon X72] + +pci:v0000105Bd0000E123* + ID_MODEL_FROM_DATABASE=T99W760 5G Redcap Modem [Snapdragon X35] + pci:v0000105C* ID_VENDOR_FROM_DATABASE=Wipro Infotech Limited @@ -23006,6 +23192,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 @@ -30029,95 +30218,89 @@ pci:v000010DEd00000029sv000014AFsd00005820* pci:v000010DEd00000029sv00004843sd00004F34* ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Ultra] (Dynamite) -pci:v000010DEd0000002A* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2] - -pci:v000010DEd0000002B* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2] - pci:v000010DEd0000002C* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] pci:v000010DEd0000002Csv00001043sd00000200* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (AGP-V3800 Combat SDRAM) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (AGP-V3800 Combat SDRAM) pci:v000010DEd0000002Csv00001043sd00000201* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (AGP-V3800 Combat) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (AGP-V3800 Combat) pci:v000010DEd0000002Csv00001048sd00000C20* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (TNT2 Vanta) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (TNT2 Vanta) pci:v000010DEd0000002Csv00001048sd00000C21* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (TNT2 Vanta) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (TNT2 Vanta) pci:v000010DEd0000002Csv00001048sd00000C25* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (TNT2 Vanta 16MB) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (TNT2 Vanta 16MB) pci:v000010DEd0000002Csv00001092sd00006820* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (Viper V730) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (Viper V730) pci:v000010DEd0000002Csv00001102sd00001031* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (CT6938 VANTA 8MB) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (CT6938 VANTA 8MB) pci:v000010DEd0000002Csv00001102sd00001034* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (CT6894 VANTA 16MB) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (CT6894 VANTA 16MB) pci:v000010DEd0000002Csv000014AFsd00005008* - ID_MODEL_FROM_DATABASE=NV5 [Vanta / Vanta LT] (Maxi Gamer Phoenix 2) + ID_MODEL_FROM_DATABASE=NV6 [Vanta LT / Vanta / Vanta-16] (Maxi Gamer Phoenix 2) pci:v000010DEd0000002D* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] pci:v000010DEd0000002Dsv00001043sd00000200* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (AGP-V3800M) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (AGP-V3800M) pci:v000010DEd0000002Dsv00001043sd00000201* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (AGP-V3800M) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (AGP-V3800M) pci:v000010DEd0000002Dsv00001048sd00000C3A* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (Erazor III LT) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (Erazor III LT) pci:v000010DEd0000002Dsv00001048sd00000C3B* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (Erazor III LT) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (Erazor III LT) pci:v000010DEd0000002Dsv0000107Dsd00002137* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (WinFast 3D S325) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (WinFast 3D S325) pci:v000010DEd0000002Dsv000010DEsd00000006* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (RIVA TNT2 Model 64/Model 64 Pro) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (RIVA TNT2 Model 64/Model 64 Pro) pci:v000010DEd0000002Dsv000010DEsd0000001E* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (M64 AGP4x) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (M64 AGP4x) pci:v000010DEd0000002Dsv00001102sd00001023* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (CT6892 RIVA TNT2 Value) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (CT6892 RIVA TNT2 Value) pci:v000010DEd0000002Dsv00001102sd00001024* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (CT6932 RIVA TNT2 Value 32Mb) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (CT6932 RIVA TNT2 Value 32Mb) pci:v000010DEd0000002Dsv00001102sd0000102C* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (CT6931 RIVA TNT2 Value [Jumper]) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (CT6931 RIVA TNT2 Value [Jumper]) pci:v000010DEd0000002Dsv00001102sd00001030* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (CT6931 RIVA TNT2 Value) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (CT6931 RIVA TNT2 Value) pci:v000010DEd0000002Dsv0000110Asd0000006F* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (GM1000-16) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (GM1000-16) pci:v000010DEd0000002Dsv0000110Asd00000081* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (GM1000-16) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (GM1000-16) pci:v000010DEd0000002Dsv00001462sd00008808* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (MSI-8808) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (MSI-8808) pci:v000010DEd0000002Dsv000014AFsd00005620* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (Gamer Cougar Video Edition) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (Gamer Cougar Video Edition) pci:v000010DEd0000002Dsv00001554sd00001041* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (Pixelview RIVA TNT2 M64) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (Pixelview RIVA TNT2 M64) pci:v000010DEd0000002Dsv00001569sd0000002D* - ID_MODEL_FROM_DATABASE=NV5 [Riva TNT2 Model 64 / Model 64 Pro] (Palit Microsystems Daytona TNT2 M64) + ID_MODEL_FROM_DATABASE=NV6 [Riva TNT2 Model 64 / Model 64 Pro] (Palit Microsystems Daytona TNT2 M64) pci:v000010DEd00000034* ID_MODEL_FROM_DATABASE=MCP04 SMBus @@ -38960,6 +39143,9 @@ pci:v000010DEd00001F81* pci:v000010DEd00001F82* ID_MODEL_FROM_DATABASE=TU117 [GeForce GTX 1650] +pci:v000010DEd00001F82sv000019DAsd00003595* + ID_MODEL_FROM_DATABASE=TU117 [GeForce GTX 1650] (GeForce GTX 1650 OC GDDR6) + pci:v000010DEd00001F83* ID_MODEL_FROM_DATABASE=TU117 [GeForce GTX 1630] @@ -39197,6 +39383,9 @@ pci:v000010DEd00002204* pci:v000010DEd00002204sv000010DEsd0000147D* ID_MODEL_FROM_DATABASE=GA102 [GeForce RTX 3090] (GeForce RTX 3090 Founders Edition) +pci:v000010DEd00002204sv00001462sd00003881* + ID_MODEL_FROM_DATABASE=GA102 [GeForce RTX 3090] (MSI RTX 3090 VENTUS 3X OC) + pci:v000010DEd00002204sv00003842sd00003973* ID_MODEL_FROM_DATABASE=GA102 [GeForce RTX 3090] (GeForce RTX 3090 XC3) @@ -39546,7 +39735,7 @@ pci:v000010DEd000024E0* ID_MODEL_FROM_DATABASE=GA104M [Geforce RTX 3070 Ti Laptop GPU] pci:v000010DEd000024FA* - ID_MODEL_FROM_DATABASE=GA104 [RTX A4500 Embedded GPU ] + ID_MODEL_FROM_DATABASE=GA104 [RTX A4500 Embedded GPU] pci:v000010DEd00002501* ID_MODEL_FROM_DATABASE=GA106 [GeForce RTX 3060] @@ -39693,7 +39882,7 @@ pci:v000010DEd000025ED* ID_MODEL_FROM_DATABASE=GA107 [GeForce RTX 2050] pci:v000010DEd000025F9* - ID_MODEL_FROM_DATABASE=GA107 [RTX A1000 Embedded GPU ] + ID_MODEL_FROM_DATABASE=GA107 [RTX A1000 Embedded GPU] pci:v000010DEd000025FA* ID_MODEL_FROM_DATABASE=GA107 [RTX A2000 Embedded GPU] @@ -39893,6 +40082,9 @@ pci:v000010DEd00002900* pci:v000010DEd00002901* ID_MODEL_FROM_DATABASE=GB100 [B200] +pci:v000010DEd00002909* + ID_MODEL_FROM_DATABASE=GB100 [HGX B200 168GB] + pci:v000010DEd00002920* ID_MODEL_FROM_DATABASE=GB100 [TS4 / B100] @@ -39993,7 +40185,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] @@ -40055,6 +40247,9 @@ pci:v000010DEd00002DD8* pci:v000010DEd00002DF9* ID_MODEL_FROM_DATABASE=GB207GLM [RTX PRO 500 Blackwell Embedded GPU] +pci:v000010DEd00002E03* + ID_MODEL_FROM_DATABASE=GB20B [JMJWOA-Generic-GPU] + pci:v000010DEd00002E12* ID_MODEL_FROM_DATABASE=GB20B [GB10] @@ -40064,6 +40259,9 @@ pci:v000010DEd00002E2A* pci:v000010DEd00002F04* ID_MODEL_FROM_DATABASE=GB205 [GeForce RTX 5070] +pci:v000010DEd00002F06* + ID_MODEL_FROM_DATABASE=GB205 [GeForce RTX 5060] + pci:v000010DEd00002F18* ID_MODEL_FROM_DATABASE=GB205M [GeForce RTX 5070 Ti Mobile] @@ -40076,6 +40274,12 @@ pci:v000010DEd00002F58* pci:v000010DEd00002F80* ID_MODEL_FROM_DATABASE=GB205 High Definition Audio Controller +pci:v000010DEd00003040* + ID_MODEL_FROM_DATABASE=GR100 + +pci:v000010DEd000030C0* + ID_MODEL_FROM_DATABASE=GR102 + pci:v000010DEd00003180* ID_MODEL_FROM_DATABASE=GB110 [Reserved Dev ID A] @@ -40091,6 +40295,9 @@ pci:v000010DEd000031C0* pci:v000010DEd000031C2* ID_MODEL_FROM_DATABASE=GB110 [GB300] +pci:v000010DEd000031C3* + ID_MODEL_FROM_DATABASE=GB110 [GB300] + pci:v000010DEd000031FE* ID_MODEL_FROM_DATABASE=GB110 @@ -42144,10 +42351,13 @@ pci:v00001102d0000000Bsv00001102sd00000062* ID_MODEL_FROM_DATABASE=EMU20k2 [Sound Blaster X-Fi Titanium Series] (SB1270 [SoundBlaster X-Fi Titanium HD]) pci:v00001102d00000010* - ID_MODEL_FROM_DATABASE=CA0132 Sound Core3D [Sound Blaster AE-7] + ID_MODEL_FROM_DATABASE=CA0132 Sound Core3D [Sound Blaster AE-7/AE-9] + +pci:v00001102d00000010sv00001102sd00000071* + ID_MODEL_FROM_DATABASE=CA0132 Sound Core3D [Sound Blaster AE-7/AE-9] (Sound Blaster AE-9) pci:v00001102d00000010sv00001102sd00000081* - ID_MODEL_FROM_DATABASE=CA0132 Sound Core3D [Sound Blaster AE-7] (Sound Blaster AE-7) + ID_MODEL_FROM_DATABASE=CA0132 Sound Core3D [Sound Blaster AE-7/AE-9] (Sound Blaster AE-7) pci:v00001102d00000012* ID_MODEL_FROM_DATABASE=CA0132 Sound Core3D [Sound Blaster Recon3D / Z-Series / Sound BlasterX AE-5 Plus] @@ -47924,6 +48134,12 @@ pci:v0000117Cd000000BBsv0000117Csd000000CA* pci:v0000117Cd000000BBsv0000117Csd000000D4* ID_MODEL_FROM_DATABASE=Celerity FC 32/64Gb/s Gen 7 Fibre Channel HBA (Celerity FC-644E) +pci:v0000117Cd000000BBsv0000117Csd000040D8* + ID_MODEL_FROM_DATABASE=Celerity FC 32/64Gb/s Gen 7 Fibre Channel HBA (ThunderLink FC 5642) + +pci:v0000117Cd000000BBsv0000117Csd000040D9* + ID_MODEL_FROM_DATABASE=Celerity FC 32/64Gb/s Gen 7 Fibre Channel HBA (ThunderLink FC 5322) + pci:v0000117Cd000000C5* ID_MODEL_FROM_DATABASE=ExpressNVM PCIe Gen4 Switch @@ -47951,6 +48167,21 @@ pci:v0000117Cd000000E6sv0000117Csd000000C3* pci:v0000117Cd000000E6sv0000117Csd000000C4* ID_MODEL_FROM_DATABASE=ExpressSAS GT 12Gb/s SAS/SATA HBA (ExpressSAS H120F GT) +pci:v0000117Cd000000E6sv0000117Csd000000E1* + ID_MODEL_FROM_DATABASE=ExpressSAS GT 12Gb/s SAS/SATA HBA (ExpressSAS H1280 GT) + +pci:v0000117Cd000000E6sv0000117Csd000000E2* + ID_MODEL_FROM_DATABASE=ExpressSAS GT 12Gb/s SAS/SATA HBA (ExpressSAS H1208 GT) + +pci:v0000117Cd000000E6sv0000117Csd000000E3* + ID_MODEL_FROM_DATABASE=ExpressSAS GT 12Gb/s SAS/SATA HBA (ExpressSAS H1244 GT) + +pci:v0000117Cd000000E6sv0000117Csd000040E0* + ID_MODEL_FROM_DATABASE=ExpressSAS GT 12Gb/s SAS/SATA HBA (ThunderLink SH 5128) + +pci:v0000117Cd000000E6sv0000117Csd000040E4* + ID_MODEL_FROM_DATABASE=ExpressSAS GT 12Gb/s SAS/SATA HBA (ThunderLink SH 5128) + pci:v0000117Cd00008013* ID_MODEL_FROM_DATABASE=ExpressPCI UL4D @@ -47973,7 +48204,7 @@ pci:v0000117Cd00008070sv0000117Csd00000080* ID_MODEL_FROM_DATABASE=ExpressSAS 12Gb/s SAS/SATA HBA (ExpressSAS H1244) pci:v0000117Cd00008070sv0000117Csd000040AE* - ID_MODEL_FROM_DATABASE=ExpressSAS 12Gb/s SAS/SATA HBA (ThunderLink TLSH-3128) + ID_MODEL_FROM_DATABASE=ExpressSAS 12Gb/s SAS/SATA HBA (ThunderLink SH 3128) pci:v0000117Cd00008072* ID_MODEL_FROM_DATABASE=ExpressSAS 12Gb/s SAS/SATA HBA @@ -48836,12 +49067,21 @@ pci:v000011ABd00001FA6* pci:v000011ABd00001FA6sv00001186sd00003B08* ID_MODEL_FROM_DATABASE=Marvell W8300 802.11 Adapter (AirPlus G DWL-G630 Wireless Cardbus Adapter (rev.A1)) +pci:v000011ABd00001FA6sv00001186sd00003B09* + ID_MODEL_FROM_DATABASE=Marvell W8300 802.11 Adapter (AirPlus G DWL-G510 Wireless G PCI Card) + pci:v000011ABd00001FA7* ID_MODEL_FROM_DATABASE=88W8310 and 88W8000G [Libertas] 802.11g client chipset pci:v000011ABd00001FAA* ID_MODEL_FROM_DATABASE=88w8335 [Libertas] 802.11b/g Wireless +pci:v000011ABd00001FAAsv00001186sd00003B21* + ID_MODEL_FROM_DATABASE=88w8335 [Libertas] 802.11b/g Wireless (EH101 Wireless G Notebook Adapter) + +pci:v000011ABd00001FAAsv00001186sd00003B22* + ID_MODEL_FROM_DATABASE=88w8335 [Libertas] 802.11b/g Wireless (EH102 Wireless G Desktop Adapter) + pci:v000011ABd00001FAAsv00001385sd00004E00* ID_MODEL_FROM_DATABASE=88w8335 [Libertas] 802.11b/g Wireless (WG511v2 54 Mbps Wireless PC Card) @@ -48851,6 +49091,9 @@ pci:v000011ABd00001FAAsv00001385sd00006B00* pci:v000011ABd00001FAAsv00001737sd00000040* ID_MODEL_FROM_DATABASE=88w8335 [Libertas] 802.11b/g Wireless (WPC54G v5 802.11g Wireless-G Notebook Adapter) +pci:v000011ABd00001FAAsv0000A727sd00006802* + ID_MODEL_FROM_DATABASE=88w8335 [Libertas] 802.11b/g Wireless (3CRGPC10075 OfficeConnect Wireless 54Mbps 11g PC Card) + pci:v000011ABd00002211* ID_MODEL_FROM_DATABASE=88SB2211 PCI Express to PCI Bridge @@ -48872,6 +49115,9 @@ pci:v000011ABd00002A02sv00001385sd00007C01* pci:v000011ABd00002A02sv00001385sd00007E00* ID_MODEL_FROM_DATABASE=88W8361 [TopDog] 802.11n Wireless (WN311T RangeMax Next 300 Mbps Wireless PCI Adapter) +pci:v000011ABd00002A02sv00001737sd00000065* + ID_MODEL_FROM_DATABASE=88W8361 [TopDog] 802.11n Wireless (WPC4400N Wireless-N Business Notebook Adapter) + pci:v000011ABd00002A02sv00001799sd0000801B* ID_MODEL_FROM_DATABASE=88W8361 [TopDog] 802.11n Wireless (F5D8011 v2 802.11n N1 Wireless Notebook Card) @@ -48881,6 +49127,18 @@ pci:v000011ABd00002A08* pci:v000011ABd00002A0A* ID_MODEL_FROM_DATABASE=88W8363 [TopDog] 802.11n Wireless +pci:v000011ABd00002A0B* + ID_MODEL_FROM_DATABASE=88W8363 [TopDog] 802.11n Wireless + +pci:v000011ABd00002A0Bsv00001154sd00000356* + ID_MODEL_FROM_DATABASE=88W8363 [TopDog] 802.11n Wireless (WLI-CB-AMG144N) + +pci:v000011ABd00002A0Bsv00001154sd00000357* + ID_MODEL_FROM_DATABASE=88W8363 [TopDog] 802.11n Wireless (WLI-CB-AG300N Nfiniti 802.11a/g/b + Draft 802.11n CardBus Card) + +pci:v000011ABd00002A0Bsv00001154sd0000035C* + ID_MODEL_FROM_DATABASE=88W8363 [TopDog] 802.11n Wireless (WLI-CB-AMG300N) + pci:v000011ABd00002A0C* ID_MODEL_FROM_DATABASE=88W8363 [TopDog] 802.11n Wireless @@ -50522,18 +50780,87 @@ pci:v000011F7* pci:v000011F8* ID_VENDOR_FROM_DATABASE=Microchip Technology +pci:v000011F8d00004000* + ID_MODEL_FROM_DATABASE=PM40100 Switchtec PFX 100xG4 Fanout PCIe Switch + +pci:v000011F8d00004028* + ID_MODEL_FROM_DATABASE=PM40028 Switchtec PFX 28xG4 Fanout PCIe Switch + pci:v000011F8d00004036* ID_MODEL_FROM_DATABASE=PM40036 Switchtec PFX 36xG4 Fanout PCIe Switch pci:v000011F8d00004052* ID_MODEL_FROM_DATABASE=PM40052 Switchtec PFX 52xG4 Fanout PCIe Switch +pci:v000011F8d00004068* + ID_MODEL_FROM_DATABASE=PM40068 Switchtec PFX 68xG4 Fanout PCIe Switch + pci:v000011F8d00004084* ID_MODEL_FROM_DATABASE=PM40084 Switchtec PFX 84xG4 Fanout PCIe Switch +pci:v000011F8d00004100* + ID_MODEL_FROM_DATABASE=PM41100 Switchtec PSX 100xG4 Programmable PCIe Switch + pci:v000011F8d00004128* ID_MODEL_FROM_DATABASE=PM41028 Switchtec PSX 28xG4 Programmable PCIe Switch +pci:v000011F8d00004136* + ID_MODEL_FROM_DATABASE=PM41036 Switchtec PSX 36xG4 Programmable PCIe Switch + +pci:v000011F8d00004152* + ID_MODEL_FROM_DATABASE=PM41052 Switchtec PSX 52xG4 Programmable PCIe Switch + +pci:v000011F8d00004168* + ID_MODEL_FROM_DATABASE=PM41068 Switchtec PSX 68xG4 Programmable PCIe Switch + +pci:v000011F8d00004184* + ID_MODEL_FROM_DATABASE=PM41084 Switchtec PSX 84xG4 Programmable PCIe Switch + +pci:v000011F8d00004200* + ID_MODEL_FROM_DATABASE=PM42100 Switchtec PAX 100xG4 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004228* + ID_MODEL_FROM_DATABASE=PM42028 Switchtec PAX 28xG4 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004236* + ID_MODEL_FROM_DATABASE=PM42036 Switchtec PAX 36xG4 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004252* + ID_MODEL_FROM_DATABASE=PM42052 Switchtec PAX 52xG4 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004268* + ID_MODEL_FROM_DATABASE=PM42068 Switchtec PAX 68xG4 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004284* + ID_MODEL_FROM_DATABASE=PM42084 Switchtec PAX 84xG4 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004328* + ID_MODEL_FROM_DATABASE=PM43028 Switchtec PFXA 28xG4 Automotive Fanout PCIe Switch + +pci:v000011F8d00004336* + ID_MODEL_FROM_DATABASE=PM43036 Switchtec PFXA 36xG4 Automotive Fanout PCIe Switch + +pci:v000011F8d00004352* + ID_MODEL_FROM_DATABASE=PM43052 Switchtec PFXA 52xG4 Automotive Fanout PCIe Switch + +pci:v000011F8d00004428* + ID_MODEL_FROM_DATABASE=PM44028 Switchtec PSXA 28xG4 Automotive Programmable PCIe Switch + +pci:v000011F8d00004436* + ID_MODEL_FROM_DATABASE=PM44036 Switchtec PSXA 36xG4 Automotive Programmable PCIe Switch + +pci:v000011F8d00004452* + ID_MODEL_FROM_DATABASE=PM44052 Switchtec PSXA 52xG4 Automotive Programmable PCIe Switch + +pci:v000011F8d00004528* + ID_MODEL_FROM_DATABASE=PM45028 Switchtec PAXA 28xG4 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004536* + ID_MODEL_FROM_DATABASE=PM45036 Switchtec PAXA 36xG4 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00004552* + ID_MODEL_FROM_DATABASE=PM45052 Switchtec PAXA 52xG4 Automotive Programmable Advanced Fabric PCIe Switch + pci:v000011F8d00005000* ID_MODEL_FROM_DATABASE=PM50100 Switchtec PFX 100xG5 Fanout PCIe Switch @@ -50570,9 +50897,165 @@ pci:v000011F8d00005168* pci:v000011F8d00005184* ID_MODEL_FROM_DATABASE=PM51084 Switchtec PSX 84xG5 Programmable PCIe Switch +pci:v000011F8d00005200* + ID_MODEL_FROM_DATABASE=PM52100 Switchtec PAX 100xG5 Programmable Advanced Fabric PCIe Switch + pci:v000011F8d00005220* ID_MODEL_FROM_DATABASE=BR522x [PMC-Sierra maxRAID SAS Controller] +pci:v000011F8d00005228* + ID_MODEL_FROM_DATABASE=PM52028 Switchtec PAX 28xG5 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005236* + ID_MODEL_FROM_DATABASE=PM52036 Switchtec PAX 36xG5 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005252* + ID_MODEL_FROM_DATABASE=PM52052 Switchtec PAX 52xG5 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005268* + ID_MODEL_FROM_DATABASE=PM52068 Switchtec PAX 68xG5 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005284* + ID_MODEL_FROM_DATABASE=PM52084 Switchtec PAX 84xG5 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005300* + ID_MODEL_FROM_DATABASE=PM53100 Switchtec PFXA 100xG5 Automotive Fanout PCIe Switch + +pci:v000011F8d00005328* + ID_MODEL_FROM_DATABASE=PM53028 Switchtec PFXA 28xG5 Automotive Fanout PCIe Switch + +pci:v000011F8d00005336* + ID_MODEL_FROM_DATABASE=PM53036 Switchtec PFXA 36xG5 Automotive Fanout PCIe Switch + +pci:v000011F8d00005352* + ID_MODEL_FROM_DATABASE=PM53052 Switchtec PFXA 52xG5 Automotive Fanout PCIe Switch + +pci:v000011F8d00005368* + ID_MODEL_FROM_DATABASE=PM53068 Switchtec PFXA 68xG5 Automotive Fanout PCIe Switch + +pci:v000011F8d00005384* + ID_MODEL_FROM_DATABASE=PM53084 Switchtec PFXA 84xG5 Automotive Fanout PCIe Switch + +pci:v000011F8d00005400* + ID_MODEL_FROM_DATABASE=PM54100 Switchtec PSXA 100xG5 Automotive Programmable PCIe Switch + +pci:v000011F8d00005428* + ID_MODEL_FROM_DATABASE=PM54028 Switchtec PSXA 28xG5 Automotive Programmable PCIe Switch + +pci:v000011F8d00005436* + ID_MODEL_FROM_DATABASE=PM54036 Switchtec PSXA 36xG5 Automotive Programmable PCIe Switch + +pci:v000011F8d00005452* + ID_MODEL_FROM_DATABASE=PM54052 Switchtec PSXA 52xG5 Automotive Programmable PCIe Switch + +pci:v000011F8d00005468* + ID_MODEL_FROM_DATABASE=PM54068 Switchtec PSXA 68xG5 Automotive Programmable PCIe Switch + +pci:v000011F8d00005484* + ID_MODEL_FROM_DATABASE=PM54084 Switchtec PSXA 84xG5 Automotive Programmable PCIe Switch + +pci:v000011F8d00005500* + ID_MODEL_FROM_DATABASE=PM55100 Switchtec PAXA 100xG5 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005528* + ID_MODEL_FROM_DATABASE=PM55028 Switchtec PAXA 28xG5 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005536* + ID_MODEL_FROM_DATABASE=PM55036 Switchtec PAXA 36xG5 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005552* + ID_MODEL_FROM_DATABASE=PM55052 Switchtec PAXA 52xG5 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005568* + ID_MODEL_FROM_DATABASE=PM55068 Switchtec PAXA 68xG5 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00005584* + ID_MODEL_FROM_DATABASE=PM55084 Switchtec PAXA 84xG5 Automotive Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00006044* + ID_MODEL_FROM_DATABASE=PM60144 Switchtec PFXs 144xG6 Secure-capable Fanout PCIe Switch + +pci:v000011F8d00006048* + ID_MODEL_FROM_DATABASE=PM60048 Switchtec PFXs 48xG6 Secure-capable Fanout PCIe Switch + +pci:v000011F8d00006060* + ID_MODEL_FROM_DATABASE=PM60160 Switchtec PFXs 160xG6 Secure-capable Fanout PCIe Switch + +pci:v000011F8d00006064* + ID_MODEL_FROM_DATABASE=PM60064 Switchtec PFXs 64xG6 Secure-capable Fanout PCIe Switch + +pci:v000011F8d00006144* + ID_MODEL_FROM_DATABASE=PM61144 Switchtec PSXs 144xG6 Secure-capable Programmable PCIe Switch + +pci:v000011F8d00006148* + ID_MODEL_FROM_DATABASE=PM61048 Switchtec PSXs 48xG6 Secure-capable Programmable PCIe Switch + +pci:v000011F8d00006160* + ID_MODEL_FROM_DATABASE=PM61160 Switchtec PSXs 160xG6 Secure-capable Programmable PCIe Switch + +pci:v000011F8d00006164* + ID_MODEL_FROM_DATABASE=PM61064 Switchtec PSXs 64xG6 Secure-capable Programmable PCIe Switch + +pci:v000011F8d00006244* + ID_MODEL_FROM_DATABASE=PM62144 Switchtec PFX 144xG6 Fanout PCIe Switch + +pci:v000011F8d00006248* + ID_MODEL_FROM_DATABASE=PM62048 Switchtec PFX 48xG6 Fanout PCIe Switch + +pci:v000011F8d00006260* + ID_MODEL_FROM_DATABASE=PM62160 Switchtec PFX 160xG6 Fanout PCIe Switch + +pci:v000011F8d00006264* + ID_MODEL_FROM_DATABASE=PM62064 Switchtec PFX 64xG6 Fanout PCIe Switch + +pci:v000011F8d00006344* + ID_MODEL_FROM_DATABASE=PM63144 Switchtec PSX 144xG6 Programmable PCIe Switch + +pci:v000011F8d00006348* + ID_MODEL_FROM_DATABASE=PM63048 Switchtec PSX 48xG6 Programmable PCIe Switch + +pci:v000011F8d00006360* + ID_MODEL_FROM_DATABASE=PM63160 Switchtec PSX 160xG6 Programmable PCIe Switch + +pci:v000011F8d00006364* + ID_MODEL_FROM_DATABASE=PM63064 Switchtec PSX 64xG6 Programmable PCIe Switch + +pci:v000011F8d00007044* + ID_MODEL_FROM_DATABASE=PM70144 Switchtec PFXs 144xG7 Secure-capable Fanout PCIe Switch + +pci:v000011F8d00007048* + ID_MODEL_FROM_DATABASE=PM70048 Switchtec PFXs 48xG7 Secure-capable Fanout PCIe Switch + +pci:v000011F8d00007060* + ID_MODEL_FROM_DATABASE=PM70160 Switchtec PFXs 160xG7 Secure-capable Fanout PCIe Switch + +pci:v000011F8d00007064* + ID_MODEL_FROM_DATABASE=PM70064 Switchtec PFXs 64xG7 Secure-capable Fanout PCIe Switch + +pci:v000011F8d00007144* + ID_MODEL_FROM_DATABASE=PM71144 Switchtec PSXs 144xG7 Secure-capable Programmable PCIe Switch + +pci:v000011F8d00007148* + ID_MODEL_FROM_DATABASE=PM71048 Switchtec PSXs 48xG7 Secure-capable Programmable PCIe Switch + +pci:v000011F8d00007160* + ID_MODEL_FROM_DATABASE=PM71160 Switchtec PSXs 160xG7 Secure-capable Programmable PCIe Switch + +pci:v000011F8d00007164* + ID_MODEL_FROM_DATABASE=PM71064 Switchtec PSXs 64xG7 Secure-capable Programmable PCIe Switch + +pci:v000011F8d00007244* + ID_MODEL_FROM_DATABASE=PM72144 Switchtec PFX 144xG7 Fanout PCIe Switch + +pci:v000011F8d00007248* + ID_MODEL_FROM_DATABASE=PM72048 Switchtec PFX 48xG7 Fanout PCIe Switch + +pci:v000011F8d00007260* + ID_MODEL_FROM_DATABASE=PM72160 Switchtec PFX 160xG7 Fanout PCIe Switch + +pci:v000011F8d00007264* + ID_MODEL_FROM_DATABASE=PM72064 Switchtec PFX 64xG7 Fanout PCIe Switch + pci:v000011F8d00007364* ID_MODEL_FROM_DATABASE=PM7364 [FREEDM - 32 Frame Engine & Datalink Mgr] @@ -50582,6 +51065,18 @@ pci:v000011F8d00007375* pci:v000011F8d00007384* ID_MODEL_FROM_DATABASE=PM7384 [FREEDM - 84P672 Frm Engine & Datalink Mgr] +pci:v000011F8d00007444* + ID_MODEL_FROM_DATABASE=PM74144 Switchtec PSX 144xG7 Programmable PCIe Switch + +pci:v000011F8d00007448* + ID_MODEL_FROM_DATABASE=PM74048 Switchtec PSX 48xG7 Programmable PCIe Switch + +pci:v000011F8d00007460* + ID_MODEL_FROM_DATABASE=PM74160 Switchtec PSX 160xG7 Programmable PCIe Switch + +pci:v000011F8d00007464* + ID_MODEL_FROM_DATABASE=PM74064 Switchtec PSX 64xG7 Programmable PCIe Switch + pci:v000011F8d00008000* ID_MODEL_FROM_DATABASE=PM8000 [SPC - SAS Protocol Controller] @@ -50657,12 +51152,78 @@ pci:v000011F8d00008536* pci:v000011F8d00008536sv00001BD4sd00000081* ID_MODEL_FROM_DATABASE=PM8536 PFX 96xG3 PCIe Fanout Switch +pci:v000011F8d00008541* + ID_MODEL_FROM_DATABASE=PM8541 PSX 24xG3 Programmable PCIe Switch + +pci:v000011F8d00008542* + ID_MODEL_FROM_DATABASE=PM8542 PSX 32xG3 Programmable PCIe Switch + +pci:v000011F8d00008543* + ID_MODEL_FROM_DATABASE=PM8543 PSX 48xG3 Programmable PCIe Switch + +pci:v000011F8d00008544* + ID_MODEL_FROM_DATABASE=PM8544 PSX 64xG3 Programmable PCIe Switch + +pci:v000011F8d00008545* + ID_MODEL_FROM_DATABASE=PM8545 PSX 80xG3 Programmable PCIe Switch + pci:v000011F8d00008546* ID_MODEL_FROM_DATABASE=PM8546 B-FEIP PSX 96xG3 PCIe Storage Switch +pci:v000011F8d00008551* + ID_MODEL_FROM_DATABASE=PM8551 PAX 24xG3 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00008552* + ID_MODEL_FROM_DATABASE=PM8552 PAX 32xG3 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00008553* + ID_MODEL_FROM_DATABASE=PM8553 PAX 48xG3 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00008554* + ID_MODEL_FROM_DATABASE=PM8554 PAX 64xG3 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00008555* + ID_MODEL_FROM_DATABASE=PM8555 PAX 80xG3 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00008556* + ID_MODEL_FROM_DATABASE=PM8556 PAX 96xG3 Programmable Advanced Fabric PCIe Switch + +pci:v000011F8d00008561* + ID_MODEL_FROM_DATABASE=PM8561 Switchtec PFX-L 24xG3 Fanout-Lite PCIe Switch + pci:v000011F8d00008562* ID_MODEL_FROM_DATABASE=PM8562 Switchtec PFX-L 32xG3 Fanout-Lite PCIe Gen3 Switch +pci:v000011F8d00008563* + ID_MODEL_FROM_DATABASE=PM8563 Switchtec PFX-L 48xG3 Fanout-Lite PCIe Switch + +pci:v000011F8d00008564* + ID_MODEL_FROM_DATABASE=PM8564 Switchtec PFX-L 64xG3 Fanout-Lite PCIe Switch + +pci:v000011F8d00008565* + ID_MODEL_FROM_DATABASE=PM8565 Switchtec PFX-L 80xG3 Fanout-Lite PCIe Switch + +pci:v000011F8d00008566* + ID_MODEL_FROM_DATABASE=PM8566 Switchtec PFX-L 96xG3 Fanout-Lite PCIe Switch + +pci:v000011F8d00008571* + ID_MODEL_FROM_DATABASE=PM8571 Switchtec PFX-I 24xG3 Industrial Fanout PCIe Switch + +pci:v000011F8d00008572* + ID_MODEL_FROM_DATABASE=PM8572 Switchtec PFX-I 32xG3 Industrial Fanout PCIe Switch + +pci:v000011F8d00008573* + ID_MODEL_FROM_DATABASE=PM8573 Switchtec PFX-I 48xG3 Industrial Fanout PCIe Switch + +pci:v000011F8d00008574* + ID_MODEL_FROM_DATABASE=PM8574 Switchtec PFX-I 64xG3 Industrial Fanout PCIe Switch + +pci:v000011F8d00008575* + ID_MODEL_FROM_DATABASE=PM8575 Switchtec PFX-I 80xG3 Industrial Fanout PCIe Switch + +pci:v000011F8d00008576* + ID_MODEL_FROM_DATABASE=PM8576 Switchtec PFX-I 96xG3 Industrial Fanout PCIe Switch + pci:v000011F9* ID_VENDOR_FROM_DATABASE=I-Cube Inc @@ -51846,7 +52407,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 @@ -52037,9 +52598,15 @@ pci:v00001260d00003872sv00001468sd00000202* pci:v00001260d00003873* ID_MODEL_FROM_DATABASE=ISL3874 [Prism 2.5]/ISL3872 [Prism 3] +pci:v00001260d00003873sv000010B7sd00000001* + ID_MODEL_FROM_DATABASE=ISL3874 [Prism 2.5]/ISL3872 [Prism 3] (3CRDW696 rev A Wireless LAN PCI Adapter [ISL3874]) + pci:v00001260d00003873sv000010CFsd00001169* ID_MODEL_FROM_DATABASE=ISL3874 [Prism 2.5]/ISL3872 [Prism 3] (MBH7WM01-8734 802.11b Wireless Mini PCI Card [ISL3874]) +pci:v00001260d00003873sv000010FCsd0000D009* + ID_MODEL_FROM_DATABASE=ISL3874 [Prism 2.5]/ISL3872 [Prism 3] (WN-B11/PCIH) + pci:v00001260d00003873sv00001186sd00003501* ID_MODEL_FROM_DATABASE=ISL3874 [Prism 2.5]/ISL3872 [Prism 3] (DWL-520 Wireless PCI Adapter (rev A or B) [ISL3874]) @@ -52052,9 +52619,15 @@ pci:v00001260d00003873sv00001385sd00004105* pci:v00001260d00003873sv00001668sd00000414* ID_MODEL_FROM_DATABASE=ISL3874 [Prism 2.5]/ISL3872 [Prism 3] (HWP01170-01 802.11b PCI Wireless Adapter) +pci:v00001260d00003873sv00001668sd00001406* + ID_MODEL_FROM_DATABASE=ISL3874 [Prism 2.5]/ISL3872 [Prism 3] (802MIP 802.11b Mini PCI Adapter [ISL3874]) + pci:v00001260d00003873sv000016A5sd00001601* ID_MODEL_FROM_DATABASE=ISL3874 [Prism 2.5]/ISL3872 [Prism 3] (AIR.mate PC-400 PCI Wireless LAN Adapter) +pci:v00001260d00003873sv000016BEsd00002001* + ID_MODEL_FROM_DATABASE=ISL3874 [Prism 2.5]/ISL3872 [Prism 3] (CTX712) + pci:v00001260d00003873sv00001737sd00003874* ID_MODEL_FROM_DATABASE=ISL3874 [Prism 2.5]/ISL3872 [Prism 3] (WMP11 v1 802.11b Wireless-B PCI Adapter [ISL3874]) @@ -55265,6 +55838,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 @@ -56756,6 +57368,12 @@ pci:v000013B4* pci:v000013B5* ID_VENDOR_FROM_DATABASE=ARM +pci:v000013B5d00000300* + ID_MODEL_FROM_DATABASE=Integrated [AGI CPU] PCIe/CXL Root Port + +pci:v000013B5d00000301* + ID_MODEL_FROM_DATABASE=Arm Integrated [AGI CPU] Platform PCIe Root Port + pci:v000013B6* ID_VENDOR_FROM_DATABASE=Dlog GmbH @@ -57908,6 +58526,18 @@ pci:v00001414d00005831* pci:v00001414d00005841* ID_MODEL_FROM_DATABASE=Xenos GPU (Slim) +pci:v00001414d0000C030* + ID_MODEL_FROM_DATABASE=OpenVMM PCIe Root Port + +pci:v00001414d0000C031* + ID_MODEL_FROM_DATABASE=OpenVMM PCIe Switch Upstream Port + +pci:v00001414d0000C032* + ID_MODEL_FROM_DATABASE=OpenVMM PCIe Switch Downstream Port + +pci:v00001414d0000C03E* + ID_MODEL_FROM_DATABASE=OpenVMM NVMe Controller + pci:v00001415* ID_VENDOR_FROM_DATABASE=Oxford Semiconductor Ltd @@ -61182,7 +61812,7 @@ pci:v0000144Dd0000A900sv00001028sd00002343* ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI U.2 3.84TB) pci:v0000144Dd0000A900sv00001028sd00002344* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI U.2 7.68GTB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI U.2 7.68TB) pci:v0000144Dd0000A900sv00001028sd00002345* ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI U.2 15.36TB) @@ -61203,28 +61833,28 @@ pci:v0000144Dd0000A900sv00001028sd0000234A* ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI U.2 15.36TB) pci:v0000144Dd0000A900sv00001028sd0000234D* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3s 1.92TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3.S 1.92TB) pci:v0000144Dd0000A900sv00001028sd0000234E* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3s 3.84TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3.S 3.84TB) pci:v0000144Dd0000A900sv00001028sd0000234F* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3s 7.68GTB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3.S 7.68TB) pci:v0000144Dd0000A900sv00001028sd00002350* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3s 15.36TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D3a RI E3.S 15.36TB) pci:v0000144Dd0000A900sv00001028sd00002351* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3s 1.92TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3.S 1.92TB) pci:v0000144Dd0000A900sv00001028sd00002352* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3s 3.84TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3.S 3.84TB) pci:v0000144Dd0000A900sv00001028sd00002353* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3s 7.68TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3.S 7.68TB) pci:v0000144Dd0000A900sv00001028sd00002354* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3s 15.36TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe FIPS PM9D3a RI E3.S 15.36TB) pci:v0000144Dd0000A900sv00001028sd00002355* ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU U.2 800GB) @@ -61239,13 +61869,13 @@ pci:v0000144Dd0000A900sv00001028sd00002358* ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU U.2 6.4TB) pci:v0000144Dd0000A900sv00001028sd00002359* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU E3.s 1.6TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU E3.S 1.6TB) pci:v0000144Dd0000A900sv00001028sd0000235A* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU E3.s 3.2TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU E3.S 3.2TB) pci:v0000144Dd0000A900sv00001028sd0000235B* - ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU E3.s 6.4TB) + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM9DXa (DC NVMe PM9D5a MU E3.S 6.4TB) pci:v0000144Dd0000AA00* ID_MODEL_FROM_DATABASE=NVMe SSD Controller BM1743 @@ -61280,6 +61910,81 @@ pci:v0000144Dd0000AA00sv00001028sd00002367* pci:v0000144Dd0000AC00* ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x +pci:v0000144Dd0000AC00sv00001028sd00002383* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L91T9HFJAAD9) + +pci:v0000144Dd0000AC00sv00001028sd00002384* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L91T9HFJAAD3) + +pci:v0000144Dd0000AC00sv00001028sd00002385* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L93T8HFJAAD9) + +pci:v0000144Dd0000AC00sv00001028sd00002386* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L93T8HFJAAD3) + +pci:v0000144Dd0000AC00sv00001028sd00002387* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L97T6HFLTAD9) + +pci:v0000144Dd0000AC00sv00001028sd00002388* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L97T6HFLTAD3) + +pci:v0000144Dd0000AC00sv00001028sd00002389* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L915THBLCAD9) + +pci:v0000144Dd0000AC00sv00001028sd0000238A* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L915THBLCAD3) + +pci:v0000144Dd0000AC00sv00001028sd0000238B* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L930THBLFAD9) + +pci:v0000144Dd0000AC00sv00001028sd0000238C* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L930THBLFAD3) + +pci:v0000144Dd0000AC00sv00001028sd0000238D* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L91T6HFJAAD9) + +pci:v0000144Dd0000AC00sv00001028sd0000238E* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L91T6HFJAAD3) + +pci:v0000144Dd0000AC00sv00001028sd0000238F* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L93T2HFJAAD9) + +pci:v0000144Dd0000AC00sv00001028sd00002390* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L93T2HFJAAD3) + +pci:v0000144Dd0000AC00sv00001028sd00002391* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L96T4HFLTAD9) + +pci:v0000144Dd0000AC00sv00001028sd00002392* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZ3L96T4HFLTAD3) + +pci:v0000144Dd0000AC00sv00001028sd00002394* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL91T9HFJAAD3) + +pci:v0000144Dd0000AC00sv00001028sd00002396* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL93T8HFLTAD3) + +pci:v0000144Dd0000AC00sv00001028sd00002398* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL97T6HFLAAD3) + +pci:v0000144Dd0000AC00sv00001028sd0000239A* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL915THBLFAD3) + +pci:v0000144Dd0000AC00sv00001028sd0000239B* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL930THBLFAD9) + +pci:v0000144Dd0000AC00sv00001028sd0000239C* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL930THBLFAD3) + +pci:v0000144Dd0000AC00sv00001028sd0000239E* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL91T6HFJAAD3) + +pci:v0000144Dd0000AC00sv00001028sd0000239F* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL93T2HFLTAD3) + +pci:v0000144Dd0000AC00sv00001028sd000023A0* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller PM175x (MZWL96T4HFLAAD3) + pci:v0000144Dd0000ECEC* ID_MODEL_FROM_DATABASE=Exynos 8895 PCIe Root Complex @@ -61880,6 +62585,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] @@ -61916,6 +62624,9 @@ pci:v000014C3d00007992* pci:v000014C3d0000799A* ID_MODEL_FROM_DATABASE=MT7992 secondary link PCIe Wi-Fi 7(802.11be) 160MHz Wireless Network Adapter [Filogic 660] +pci:v000014C3d00008188* + ID_MODEL_FROM_DATABASE=MT8188 [Kompanio 838] Root Complex + pci:v000014C3d00008650* ID_MODEL_FROM_DATABASE=MT7650 Bluetooth @@ -63494,6 +64205,9 @@ pci:v000014E4d000016D7* pci:v000014E4d000016D7sv0000117Csd000000CC* ID_MODEL_FROM_DATABASE=BCM57414 NetXtreme-E 10Gb/25Gb RDMA Ethernet Controller (FastFrame N422 Dual-port 25Gb Ethernet Adapter) +pci:v000014E4d000016D7sv0000117Csd000040D7* + ID_MODEL_FROM_DATABASE=BCM57414 NetXtreme-E 10Gb/25Gb RDMA Ethernet Controller (ThunderLink NS 5252 Dual-port 25Gb Ethernet Adapter) + pci:v000014E4d000016D7sv000014E4sd00001402* ID_MODEL_FROM_DATABASE=BCM57414 NetXtreme-E 10Gb/25Gb RDMA Ethernet Controller (BCM957414A4142CC 10Gb/25Gb Ethernet PCIe) @@ -63699,7 +64413,7 @@ pci:v000014E4d00001750sv0000117Csd000000CF* ID_MODEL_FROM_DATABASE=BCM57508 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb/200Gb Ethernet (FastFrame N412 Dual-port 100Gb Ethernet Adapter) pci:v000014E4d00001750sv0000117Csd000040D6* - ID_MODEL_FROM_DATABASE=BCM57508 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb/200Gb Ethernet (ThunderLink TLNS-5102 Dual-port 100Gb Ethernet Adapter) + ID_MODEL_FROM_DATABASE=BCM57508 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb/200Gb Ethernet (ThunderLink NS 5102 Dual-port 100Gb Ethernet Adapter) pci:v000014E4d00001750sv000014E4sd00002100* ID_MODEL_FROM_DATABASE=BCM57508 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb/200Gb Ethernet (NetXtreme-E Dual-port 100G QSFP56 Ethernet PCIe4.0 x16 Adapter (BCM957508-P2100G)) @@ -63731,6 +64445,9 @@ pci:v000014E4d00001751sv00001028sd00000B1B* pci:v000014E4d00001751sv0000117Csd000000DA* ID_MODEL_FROM_DATABASE=BCM57504 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb Ethernet (FastFrame N424 Quad-port 25Gb Ethernet Adapter) +pci:v000014E4d00001751sv0000117Csd000040DF* + ID_MODEL_FROM_DATABASE=BCM57504 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb Ethernet (ThunderLink NS 5254 Quad-port 25Gb Ethernet Adapter) + pci:v000014E4d00001751sv000014E4sd00004250* ID_MODEL_FROM_DATABASE=BCM57504 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb Ethernet (NetXtreme-E Quad-port 25G SFP28 Ethernet PCIe4.0 x16 Adapter (BCM957504-P425G)) @@ -63761,6 +64478,9 @@ pci:v000014E4d00001752* pci:v000014E4d00001760* ID_MODEL_FROM_DATABASE=BCM57608 25Gb/50Gb/100Gb/200Gb/400Gb Ethernet +pci:v000014E4d00001760sv0000117Csd000000CF* + ID_MODEL_FROM_DATABASE=BCM57608 25Gb/50Gb/100Gb/200Gb/400Gb Ethernet (FastFrame N522 Dual-port 200Gb Ethernet Adapter) + pci:v000014E4d00001760sv000014E4sd00009110* ID_MODEL_FROM_DATABASE=BCM57608 25Gb/50Gb/100Gb/200Gb/400Gb Ethernet (BCM57608 1x400G PCIe Ethernet NIC) @@ -63806,6 +64526,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 @@ -64157,6 +64883,9 @@ pci:v000014E4d00004320sv0000144Fsd00007050* pci:v000014E4d00004320sv0000144Fsd00007051* ID_MODEL_FROM_DATABASE=BCM4306 802.11b/g Wireless LAN Controller (Sonnet Aria Extreme PCI) +pci:v000014E4d00004320sv000016A5sd00001604* + ID_MODEL_FROM_DATABASE=BCM4306 802.11b/g Wireless LAN Controller (WE602-B) + pci:v000014E4d00004320sv00001737sd00000013* ID_MODEL_FROM_DATABASE=BCM4306 802.11b/g Wireless LAN Controller (WMP54G v1 802.11g PCI Adapter) @@ -64427,6 +65156,12 @@ pci:v000014E4d000043BB* pci:v000014E4d000043BC* ID_MODEL_FROM_DATABASE=BCM43602 802.11ac Wireless LAN SoC +pci:v000014E4d000043C3* + ID_MODEL_FROM_DATABASE=BCM4366/BCM43465 802.11ac Wave2 4x4 Wireless Network Adapter + +pci:v000014E4d000043C3sv00001043sd000086FB* + ID_MODEL_FROM_DATABASE=BCM4366/BCM43465 802.11ac Wave2 4x4 Wireless Network Adapter (PCE-AC88) + pci:v000014E4d000043D3* ID_MODEL_FROM_DATABASE=BCM43567 802.11ac Wireless Network Adapter @@ -64673,6 +65408,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 @@ -67031,6 +67769,9 @@ pci:v000015B3d0000027D* pci:v000015B3d0000027E* ID_MODEL_FROM_DATABASE=Spectrum-7 Tile +pci:v000015B3d0000027F* + ID_MODEL_FROM_DATABASE=Spectrum-8 tile + pci:v000015B3d00000281* ID_MODEL_FROM_DATABASE=NPS-600 Flash Recovery @@ -67053,7 +67794,7 @@ pci:v000015B3d00000287* ID_MODEL_FROM_DATABASE=LibraE RMA pci:v000015B3d00000288* - ID_MODEL_FROM_DATABASE=Arcus2 + ID_MODEL_FROM_DATABASE=Arcus2 Flash Recovery pci:v000015B3d00000289* ID_MODEL_FROM_DATABASE=Arcus2 RMA @@ -67068,19 +67809,19 @@ pci:v000015B3d00000293* ID_MODEL_FROM_DATABASE=Arcus3 RMA pci:v000015B3d00000294* - ID_MODEL_FROM_DATABASE=Ophy 2.1 (SagittaZ) + ID_MODEL_FROM_DATABASE=OPHY2.1 [SagittaZ] pci:v000015B3d00000296* - ID_MODEL_FROM_DATABASE=OPHY2.6 + ID_MODEL_FROM_DATABASE=OPHY2.6 [Sagitta] pci:v000015B3d00000298* - ID_MODEL_FROM_DATABASE=OPHY3.0 + ID_MODEL_FROM_DATABASE=OPHY3.0 [Sagitta] pci:v000015B3d0000029A* - ID_MODEL_FROM_DATABASE=OPHY3.1 + ID_MODEL_FROM_DATABASE=OPHY3.1 [Sagitta] pci:v000015B3d0000029C* - ID_MODEL_FROM_DATABASE=OPHY3.5 + ID_MODEL_FROM_DATABASE=OPHY3.5 [Sagitta] pci:v000015B3d000002A0* ID_MODEL_FROM_DATABASE=NVLink-8 Switch in Flash Recovery Mode @@ -67100,6 +67841,12 @@ pci:v000015B3d000002A6* pci:v000015B3d000002A7* ID_MODEL_FROM_DATABASE=OrionR RMA +pci:v000015B3d000002A8* + ID_MODEL_FROM_DATABASE=Spectrum-8 in Flash Recovery Mode + +pci:v000015B3d000002A9* + ID_MODEL_FROM_DATABASE=Spectrum-8 RMA + pci:v000015B3d00001002* ID_MODEL_FROM_DATABASE=MT25400 Family [ConnectX-2 Virtual Function] @@ -67511,6 +68258,9 @@ pci:v000015B3d00002100* pci:v000015B3d00002101* ID_MODEL_FROM_DATABASE=CX10 Family [ConnectX-10 NVLink-C2C] +pci:v000015B3d00002300* + ID_MODEL_FROM_DATABASE=ConnectX/BlueField Family Hardware Performance Monitors [HWPM] + pci:v000015B3d00004117* ID_MODEL_FROM_DATABASE=MT27712A0-FDCF-AE @@ -67736,6 +68486,9 @@ pci:v000015B3d0000C738* pci:v000015B3d0000C739* ID_MODEL_FROM_DATABASE=MT51136 GW +pci:v000015B3d0000C788* + ID_MODEL_FROM_DATABASE=Spectrum-8 + pci:v000015B3d0000C838* ID_MODEL_FROM_DATABASE=MT52236 @@ -67973,6 +68726,9 @@ pci:v000015B7d0000504A* pci:v000015B7d00005050* ID_MODEL_FROM_DATABASE=WD PC SN8050S / WD_BLACK SN8100 NVMe SSD +pci:v000015B7d00005062* + ID_MODEL_FROM_DATABASE=WD PC SN5100S NVMe SSD (DRAM-less) + pci:v000015B7d00005063* ID_MODEL_FROM_DATABASE=WD Blue SN5100 NVMe SSD (DRAM-less) @@ -69362,6 +70118,9 @@ pci:v0000168Cd00000020sv00001186sd00003A68* pci:v0000168Cd00000020sv0000187Esd0000340E* ID_MODEL_FROM_DATABASE=AR5513 802.11abg Wireless NIC (M-302 802.11g Wireless PCI Adapter) +pci:v0000168Cd00000020sv00001976sd00001600* + ID_MODEL_FROM_DATABASE=AR5513 802.11abg Wireless NIC (TEW-603PI 108Mbps 802.11g Wireless PCI Adapter) + pci:v0000168Cd00000020sv00001976sd00002003* ID_MODEL_FROM_DATABASE=AR5513 802.11abg Wireless NIC (TEW-601PC 802.11g Wireless CardBus Adapter) @@ -69413,9 +70172,18 @@ pci:v0000168Cd00000024* pci:v0000168Cd00000024sv0000106Bsd00000087* ID_MODEL_FROM_DATABASE=AR5418 Wireless Network Adapter [AR5008E 802.11(a)bgn] (PCI-Express) (AirPort Extreme) +pci:v0000168Cd00000024sv00001154sd00000366* + ID_MODEL_FROM_DATABASE=AR5418 Wireless Network Adapter [AR5008E 802.11(a)bgn] (PCI-Express) (WLP-EXC-AG300 802.11abgn ExpressCard) + +pci:v0000168Cd00000024sv00001186sd00003A6F* + ID_MODEL_FROM_DATABASE=AR5418 Wireless Network Adapter [AR5008E 802.11(a)bgn] (PCI-Express) (DWA-643 Xtreme N Notebook ExpressCard) + pci:v0000168Cd00000024sv00001186sd00003A70* ID_MODEL_FROM_DATABASE=AR5418 Wireless Network Adapter [AR5008E 802.11(a)bgn] (PCI-Express) (DWA-556 Xtreme N PCI Express Desktop Adapter) +pci:v0000168Cd00000024sv00001799sd00008071* + ID_MODEL_FROM_DATABASE=AR5418 Wireless Network Adapter [AR5008E 802.11(a)bgn] (PCI-Express) (F5D8071 v1 N1 Wireless ExpressCard) + pci:v0000168Cd00000027* ID_MODEL_FROM_DATABASE=AR9160 Wireless Network Adapter [AR9001 802.11(a)bgn] @@ -71006,6 +71774,9 @@ pci:v0000177Dd0000AF00* pci:v0000177Dd0000AF84* ID_MODEL_FROM_DATABASE=CN99xx [ThunderX2] Integrated PCI Express RP Bridge +pci:v0000177Dd0000F010* + ID_MODEL_FROM_DATABASE=XPliant CNX880xx Network Processor + pci:v00001787* ID_VENDOR_FROM_DATABASE=Hightech Information System Ltd. @@ -71279,21 +72050,36 @@ pci:v000017CB* pci:v000017CBd00000001* ID_MODEL_FROM_DATABASE=AGN100 802.11 a/b/g True MIMO Wireless Card +pci:v000017CBd00000001sv00001154sd00000337* + ID_MODEL_FROM_DATABASE=AGN100 802.11 a/b/g True MIMO Wireless Card (WLI-CB-G108) + pci:v000017CBd00000001sv00001385sd00005C00* ID_MODEL_FROM_DATABASE=AGN100 802.11 a/b/g True MIMO Wireless Card (WGM511 Pre-N 802.11g Wireless CardBus Adapter) +pci:v000017CBd00000001sv000014EAsd00006101* + ID_MODEL_FROM_DATABASE=AGN100 802.11 a/b/g True MIMO Wireless Card (CQW-NS108G) + +pci:v000017CBd00000001sv00001737sd00000037* + ID_MODEL_FROM_DATABASE=AGN100 802.11 a/b/g True MIMO Wireless Card (WPC54GX Wireless-G Notebook Adapter with SRX) + pci:v000017CBd00000001sv00001737sd00000045* ID_MODEL_FROM_DATABASE=AGN100 802.11 a/b/g True MIMO Wireless Card (WMP54GX v1 802.11g Wireless-G PCI Adapter with SRX) pci:v000017CBd00000002* ID_MODEL_FROM_DATABASE=AGN300 802.11 a/b/g True MIMO Wireless Card +pci:v000017CBd00000002sv00001043sd0000106F* + ID_MODEL_FROM_DATABASE=AGN300 802.11 a/b/g True MIMO Wireless Card (WL-106gM 240 MIMO Wireless CardBus Adapter) + pci:v000017CBd00000002sv00001385sd00006D00* ID_MODEL_FROM_DATABASE=AGN300 802.11 a/b/g True MIMO Wireless Card (WPNT511 RangeMax 240 Mbps Wireless CardBus Adapter) pci:v000017CBd00000002sv00001737sd00000054* ID_MODEL_FROM_DATABASE=AGN300 802.11 a/b/g True MIMO Wireless Card (WPC54GX4 v1 802.11g Wireless-G Notebook Adapter with SRX400) +pci:v000017CBd00000002sv00001737sd00000056* + ID_MODEL_FROM_DATABASE=AGN300 802.11 a/b/g True MIMO Wireless Card (WMP54GX4 Wireless-G PCI Adapter with SRX400) + pci:v000017CBd00000104* ID_MODEL_FROM_DATABASE=APQ8096 PCIe Root Complex [Snapdragon 820] @@ -71346,7 +72132,40 @@ pci:v000017CBd00000306* ID_MODEL_FROM_DATABASE=SDX55 [Snapdragon X55 5G] pci:v000017CBd00000308* - ID_MODEL_FROM_DATABASE=SDX62 [Snapdragon X62 5G] + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] + +pci:v000017CBd00000308sv0000105Bsd0000E142* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E143* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E144* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E145* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E146* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E150* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E151* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E152* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E153* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E154* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) + +pci:v000017CBd00000308sv0000105Bsd0000E155* + ID_MODEL_FROM_DATABASE=SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] (T99W696 5G Modem [Snapdragon X61]) pci:v000017CBd00000400* ID_MODEL_FROM_DATABASE=Datacenter Technologies QDF2432 PCI Express Root Port @@ -71936,6 +72755,9 @@ pci:v000017FEd00002120sv00001737sd00000020* pci:v000017FEd00002220* ID_MODEL_FROM_DATABASE=IPN 2220 802.11g +pci:v000017FEd00002220sv00001154sd00000334* + ID_MODEL_FROM_DATABASE=IPN 2220 802.11g (WLI2-CB-G54L) + pci:v000017FEd00002220sv00001468sd00000305* ID_MODEL_FROM_DATABASE=IPN 2220 802.11g (T60N871 802.11g Mini PCI Wireless Adapter) @@ -72161,6 +72983,9 @@ pci:v00001814d00000301sv00001186sd00003C08* pci:v00001814d00000301sv00001186sd00003C09* ID_MODEL_FROM_DATABASE=RT2561/RT61 802.11g PCI (DWL-G510 Rev C) +pci:v00001814d00000301sv00001259sd0000C123* + ID_MODEL_FROM_DATABASE=RT2561/RT61 802.11g PCI (CG-WLCB54GPX) + pci:v00001814d00000301sv000013D1sd0000ABE3* ID_MODEL_FROM_DATABASE=RT2561/RT61 802.11g PCI (miniPCI Pluscom 802.11 a/b/g) @@ -72173,6 +72998,9 @@ pci:v00001814d00000301sv00001458sd0000E934* pci:v00001814d00000301sv00001462sd0000B833* ID_MODEL_FROM_DATABASE=RT2561/RT61 802.11g PCI (MP54G5 (MS-6833B)) +pci:v00001814d00000301sv00001462sd0000B834* + ID_MODEL_FROM_DATABASE=RT2561/RT61 802.11g PCI (PC60G (MS-6834B) Wireless 11g Turbo G PCI Card) + pci:v00001814d00000301sv00001737sd00000055* ID_MODEL_FROM_DATABASE=RT2561/RT61 802.11g PCI (WMP54G v4.1) @@ -72188,6 +73016,9 @@ pci:v00001814d00000301sv000017F9sd00000012* pci:v00001814d00000301sv00001814sd00002561* ID_MODEL_FROM_DATABASE=RT2561/RT61 802.11g PCI (EW-7108PCg/EW-7128g) +pci:v00001814d00000301sv0000182Dsd000090AA* + ID_MODEL_FROM_DATABASE=RT2561/RT61 802.11g PCI (WL-170 Wireless Network Cardbus Card) + pci:v00001814d00000302* ID_MODEL_FROM_DATABASE=RT2561/RT61 rev B 802.11g @@ -72236,6 +73067,12 @@ pci:v00001814d00000681* pci:v00001814d00000681sv00001458sd0000E939* ID_MODEL_FROM_DATABASE=RT2890 Wireless 802.11n PCIe (GN-WS30N-RH 802.11bgn Mini PCIe Card) +pci:v00001814d00000681sv00001799sd00008073* + ID_MODEL_FROM_DATABASE=RT2890 Wireless 802.11n PCIe (F5D8073 v1 N Wireless ExpressCard Adapter) + +pci:v00001814d00000681sv00001799sd0000807C* + ID_MODEL_FROM_DATABASE=RT2890 Wireless 802.11n PCIe (F5D8071 v3 N1 Wireless ExpressCard) + pci:v00001814d00000701* ID_MODEL_FROM_DATABASE=RT2760 Wireless 802.11n 1T/2R @@ -72248,9 +73085,15 @@ pci:v00001814d00000781* pci:v00001814d00000781sv000011ADsd00007600* ID_MODEL_FROM_DATABASE=RT2790 Wireless 802.11n 1T/2R PCIe (HP WN7600R) +pci:v00001814d00000781sv00001799sd0000817C* + ID_MODEL_FROM_DATABASE=RT2790 Wireless 802.11n 1T/2R PCIe (F5D8073 v3 N Wireless ExpressCard Adapter) + pci:v00001814d00000781sv00001814sd00002790* ID_MODEL_FROM_DATABASE=RT2790 Wireless 802.11n 1T/2R PCIe +pci:v00001814d00000781sv00001976sd00002600* + ID_MODEL_FROM_DATABASE=RT2790 Wireless 802.11n 1T/2R PCIe (TEW-642EC Wireless N ExpressCard) + pci:v00001814d00003060* ID_MODEL_FROM_DATABASE=RT3060 Wireless 802.11n 1T/1R @@ -74288,6 +75131,9 @@ pci:v00001987d00005029* pci:v00001987d00005031* ID_MODEL_FROM_DATABASE=PS5031-E31T PCIe5 NVMe Controller +pci:v00001987d00005037* + ID_MODEL_FROM_DATABASE=PS5037-E37T PCIe5 NVMe Controller (DRAM-less) + pci:v00001987d00005302* ID_MODEL_FROM_DATABASE=PS5302-X2 PCIe5 NVMe Controller @@ -74312,6 +75158,9 @@ pci:v00001998* pci:v00001998d00000001* ID_MODEL_FROM_DATABASE=TOBOLT1 51987 NVMe SSD +pci:v00001998d00000001sv00001998sd00000192* + ID_MODEL_FROM_DATABASE=TOBOLT1 51987 NVMe SSD (TOBOLT1 45967 1920G M.2 NVMe SSD) + pci:v00001998d00000001sv00001998sd00000384* ID_MODEL_FROM_DATABASE=TOBOLT1 51987 NVMe SSD (TOBOLT1 51987 3840G 2.5" U.2 NVMe SSD) @@ -74321,6 +75170,9 @@ pci:v00001998d00000001sv00001998sd00000768* pci:v00001998d00000001sv00001998sd00002012* ID_MODEL_FROM_DATABASE=TOBOLT1 51987 NVMe SSD (TOBOLT1 51987 3840G 2.5" U.2 NVMe SSD) +pci:v00001998d00000001sv00001998sd00006144* + ID_MODEL_FROM_DATABASE=TOBOLT1 51987 NVMe SSD (TOBOLT1 51995 6144G 2.5" U.2 NVMe SSD) + pci:v00001999* ID_VENDOR_FROM_DATABASE=A-Logics @@ -74645,6 +75497,24 @@ pci:v000019E5d00000222sv000019E5sd00000052* pci:v000019E5d00000222sv000019E5sd000000A1* ID_MODEL_FROM_DATABASE=Hi1822 Family (Hi1822 SP670 (2*100GE)) +pci:v000019E5d00000222sv000019E5sd00000152* + ID_MODEL_FROM_DATABASE=Hi1822 Family (Hi1822 SP623Q (4*25/10GE)) + +pci:v000019E5d00000222sv000019E5sd000001A1* + ID_MODEL_FROM_DATABASE=Hi1822 Family (Hi1822 SP625D (2*100GE)) + +pci:v000019E5d00000229* + ID_MODEL_FROM_DATABASE=Hi1872 Family + +pci:v000019E5d0000022A* + ID_MODEL_FROM_DATABASE=Hi1872 Family Virtual Function + +pci:v000019E5d00000230* + ID_MODEL_FROM_DATABASE=Hi1825 Family + +pci:v000019E5d00000231* + ID_MODEL_FROM_DATABASE=Hi1825 Family Virtual Function + pci:v000019E5d00001710* ID_MODEL_FROM_DATABASE=iBMA Virtual Network Adapter @@ -74945,6 +75815,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)) @@ -75413,9 +76286,18 @@ pci:v00001AA9d00000017* pci:v00001AA9d00000018* ID_MODEL_FROM_DATABASE=SEL-3390E4 Ethernet Adapter +pci:v00001AA9d00000019* + ID_MODEL_FROM_DATABASE=SEL-2241-2/SEL-3361 Mainboard + pci:v00001AA9d0000001C* ID_MODEL_FROM_DATABASE=SEL-3390E4 Ethernet Adapter +pci:v00001AA9d0000001D* + ID_MODEL_FROM_DATABASE=SEL-3350 GPIO Expansion Board + +pci:v00001AA9d0000001E* + ID_MODEL_FROM_DATABASE=SEL-3350 Serial Expansion Board + pci:v00001AAB* ID_VENDOR_FROM_DATABASE=Silver Creations AG @@ -77058,7 +77940,7 @@ pci:v00001BB1d00005026* ID_MODEL_FROM_DATABASE=FireCuda 540 SSD pci:v00001BB1d00005027* - ID_MODEL_FROM_DATABASE=LaCie Rugged SSD Pro5 + ID_MODEL_FROM_DATABASE=BarraCuda 530 SSD / LaCie Rugged SSD Pro5 pci:v00001BB1d00005100* ID_MODEL_FROM_DATABASE=PCIe Gen3 SSD @@ -77120,12 +78002,27 @@ pci:v00001BC0d00001002* pci:v00001BC0d00001160* ID_MODEL_FROM_DATABASE=PCIe 3TE2 Controller +pci:v00001BC0d00001202* + ID_MODEL_FROM_DATABASE=PCIe 3TE8 Controller + +pci:v00001BC0d0000120A* + ID_MODEL_FROM_DATABASE=PCIe 3IE8 Controller + +pci:v00001BC0d0000120B* + ID_MODEL_FROM_DATABASE=PCIe 3TO8 Controller + pci:v00001BC0d00001321* ID_MODEL_FROM_DATABASE=PCIe 4TG-P Controller pci:v00001BC0d00001322* ID_MODEL_FROM_DATABASE=PCIe 4TE Controller +pci:v00001BC0d00001602* + ID_MODEL_FROM_DATABASE=PCIe 4TE3 Controller + +pci:v00001BC0d0000160A* + ID_MODEL_FROM_DATABASE=PCIe 4IE3 Controller + pci:v00001BC0d00002262* ID_MODEL_FROM_DATABASE=PCIe 3TG3-P Controller @@ -77133,11 +78030,29 @@ pci:v00001BC0d00005208* ID_MODEL_FROM_DATABASE=PCIe 3TE7 Controller pci:v00001BC0d00005216* - ID_MODEL_FROM_DATABASE=PCIe 3TE8 Controller + ID_MODEL_FROM_DATABASE=PCIe 3TE9 controller + +pci:v00001BC0d0000521A* + ID_MODEL_FROM_DATABASE=PCIe 3IE9 Controller + +pci:v00001BC0d00005220* + ID_MODEL_FROM_DATABASE=PCIe 4TE2 Controller + +pci:v00001BC0d0000522A* + ID_MODEL_FROM_DATABASE=PCIe 4IE2 Controller pci:v00001BC0d00005236* ID_MODEL_FROM_DATABASE=PCIe 4TG2-P Controller +pci:v00001BC0d0000523A* + ID_MODEL_FROM_DATABASE=PCIe 4TS2-P Controller + +pci:v00001BC0d0000523B* + ID_MODEL_FROM_DATABASE=PCIe 4IG2-P Controller + +pci:v00001BC0d00008366* + ID_MODEL_FROM_DATABASE=PCIe 5TS-P Controller + pci:v00001BCD* ID_VENDOR_FROM_DATABASE=Apacer Technology @@ -77597,6 +78512,9 @@ pci:v00001C2Cd0000A016* pci:v00001C2Cd0000A017* ID_MODEL_FROM_DATABASE=FB2CGHH Capture 8x25Gb [Tivoli] a017 +pci:v00001C2Cd0000A01B* + ID_MODEL_FROM_DATABASE=FB2CDG1 Capture 2x100Gb [Thunderfjord] + pci:v00001C32* ID_VENDOR_FROM_DATABASE=Highland Technology, Inc. @@ -78080,12 +78998,48 @@ pci:v00001C5Fd00000027sv00001C5Fsd00001431* pci:v00001C5Fd00000027sv00001C5Fsd00001441* ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 7680G 2.5" U.2) +pci:v00001C5Fd00000027sv00001C5Fsd00001631* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 3840G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00001641* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 7680G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00001651* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 15360G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00001661* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 30720G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00001751* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 Ocean 15360G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00001761* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 Ocean 30720G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00001771* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 Ocean 61440G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00001781* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A40 Ocean 122880G 2.5" U.2) + pci:v00001C5Fd00000027sv00001C5Fsd00005431* ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A46 3200G 2.5" U.2) pci:v00001C5Fd00000027sv00001C5Fsd00005441* ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A46 6400G 2.5" U.2) +pci:v00001C5Fd00000027sv00001C5Fsd00005631* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A46 3200G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00005641* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A46 6400G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00005651* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A46 12800G 2.5" U.2) + +pci:v00001C5Fd00000027sv00001C5Fsd00005661* + ID_MODEL_FROM_DATABASE=PBlaze7 7A40/7A46 NVMe SSD (NVMe SSD PBlaze7 7A46 25600G 2.5" U.2) + pci:v00001C5Fd0000003D* ID_MODEL_FROM_DATABASE=PBlaze5 920/926 @@ -78431,6 +79385,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) @@ -78503,6 +79460,9 @@ pci:v00001CC1d0000635A* pci:v00001CC1d0000636A* ID_MODEL_FROM_DATABASE=XPG GAMMIX S55 NVMe SSD (DRAM-less) +pci:v00001CC1d0000641A* + ID_MODEL_FROM_DATABASE=LEGEND 970 PRO NVMe SSD + pci:v00001CC1d0000642A* ID_MODEL_FROM_DATABASE=XPG GAMMIX S50 CORE NVMe SSD (DRAM-less) @@ -78662,6 +79622,12 @@ pci:v00001CC4d00006A13* pci:v00001CC4d00006A14* ID_MODEL_FROM_DATABASE=AM6A1 PCIe 4.0 NVMe SSD 1024GB (DRAM-less) +pci:v00001CC4d00006B02* + ID_MODEL_FROM_DATABASE=AM6B0 PCIe 4.0 NVMe SSD 256GB (DRAM-less) + +pci:v00001CC4d00006B03* + ID_MODEL_FROM_DATABASE=RPETJ512MMW1MDQ PCIe 4.0 NVMe SSD 512GB (DRAM-less) + pci:v00001CC4d00006B04* ID_MODEL_FROM_DATABASE=AM6B0 PCIe 4.0 NVMe SSD @@ -79214,6 +80180,9 @@ pci:v00001D0Fd0000EFA2* pci:v00001D0Fd0000EFA3* ID_MODEL_FROM_DATABASE=Elastic Fabric Adapter (EFA) +pci:v00001D0Fd0000EFA4* + ID_MODEL_FROM_DATABASE=Elastic Fabric Adapter (EFA) + pci:v00001D17* ID_VENDOR_FROM_DATABASE=Zhaoxin @@ -79242,28 +80211,28 @@ pci:v00001D17d00000716* ID_MODEL_FROM_DATABASE=ZX-D PCI Express Root Port pci:v00001D17d00000717* - ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Express Root Port + ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Express Root Port pci:v00001D17d00000718* - ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Express Root Port + ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Express Root Port pci:v00001D17d00000719* - ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Express Root Port + ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Express Root Port pci:v00001D17d0000071A* ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000 PCI Express Root Port pci:v00001D17d0000071B* - ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Express Root Port + ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Express Root Port pci:v00001D17d0000071C* - ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Express Root Port + ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Express Root Port pci:v00001D17d0000071D* - ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Express Root Port + ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Express Root Port pci:v00001D17d0000071E* - ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Express Root Port + ID_MODEL_FROM_DATABASE=KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Express Root Port pci:v00001D17d0000071F* ID_MODEL_FROM_DATABASE=ZX-200 Upstream Port of PCI Express Switch @@ -79310,11 +80279,104 @@ pci:v00001D17d0000073A* pci:v00001D17d0000073B* ID_MODEL_FROM_DATABASE=KX-7000 PCIE Express Root Port +pci:v00001D17d0000073C* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d0000073D* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d0000073E* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d0000073F* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000740* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000741* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000742* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000743* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000744* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000745* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000746* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000747* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000748* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000749* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d0000074A* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d0000074B* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d0000074C* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d0000074D* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d0000074E* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d0000074F* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000750* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000751* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000752* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000757* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000758* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d00000759* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d0000075A* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d0000075B* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d0000075C* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d0000075D* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + +pci:v00001D17d0000075E* + ID_MODEL_FROM_DATABASE=KH-50000 PCI Express Root Port + pci:v00001D17d00001000* ID_MODEL_FROM_DATABASE=ZX-D Standard Host Bridge pci:v00001D17d00001001* - ID_MODEL_FROM_DATABASE=ZX-D/ZX-E/KH-40000/KX-7000 Miscellaneous Bus + ID_MODEL_FROM_DATABASE=ZX-D/ZX-E/KH-40000/KX-7000/KH-50000 Miscellaneous Bus pci:v00001D17d00001003* ID_MODEL_FROM_DATABASE=ZX-E Standard Host Bridge @@ -79328,6 +80390,9 @@ pci:v00001D17d00001006* pci:v00001D17d00001007* ID_MODEL_FROM_DATABASE=KX-7000 Standard Host Bridge +pci:v00001D17d00001008* + ID_MODEL_FROM_DATABASE=KH-50000 Standard Host Bridge + pci:v00001D17d00003001* ID_MODEL_FROM_DATABASE=ZX-100 Standard Host Bridge @@ -79335,31 +80400,31 @@ pci:v00001D17d0000300A* ID_MODEL_FROM_DATABASE=ZX-100 Miscellaneous Bus pci:v00001D17d00003038* - ID_MODEL_FROM_DATABASE=ZX-100/ZX-200/KX-6000/KX-6000G/KH-40000/KX-7000 Standard Universal PCI to USB Host Controller + ID_MODEL_FROM_DATABASE=ZX-100/ZX-200/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 Standard Universal PCI to USB Host Controller pci:v00001D17d00003104* - ID_MODEL_FROM_DATABASE=ZX-100/ZX-200/KX-6000/KX-6000G/KH-40000/KX-7000 Standard Enhanced PCI to USB Host Controller + ID_MODEL_FROM_DATABASE=ZX-100/ZX-200/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 Standard Enhanced PCI to USB Host Controller pci:v00001D17d000031B0* - ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 Standard Host Bridge + ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 Standard Host Bridge pci:v00001D17d000031B1* - ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 Standard Host Bridge + ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 Standard Host Bridge pci:v00001D17d000031B2* ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 DRAM Controller pci:v00001D17d000031B3* - ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 Power Management Controller + ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 Power Management Controller pci:v00001D17d000031B4* - ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 I/O APIC + ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 I/O APIC pci:v00001D17d000031B5* - ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 Scratch Device + ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 Scratch Device pci:v00001D17d000031B7* - ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 Standard Host Bridge + ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 Standard Host Bridge pci:v00001D17d000031B8* ID_MODEL_FROM_DATABASE=ZX-100/ZX-D PCI to PCI Bridge @@ -79368,7 +80433,7 @@ pci:v00001D17d00003200* ID_MODEL_FROM_DATABASE=KX-7000 Host Bridge pci:v00001D17d00003288* - ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 High Definition Audio Controller + ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 High Definition Audio Controller pci:v00001D17d0000345B* ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000 Miscellaneous Bus @@ -79407,13 +80472,13 @@ pci:v00001D17d00003D01* ID_MODEL_FROM_DATABASE=KX-6000G C-1080 GPU pci:v00001D17d00009002* - ID_MODEL_FROM_DATABASE=ZX-100/ZX-200/KH-40000/KX-7000 EIDE Controller + ID_MODEL_FROM_DATABASE=ZX-100/ZX-200/KH-40000/KX-7000/KH-50000 EIDE Controller pci:v00001D17d00009003* ID_MODEL_FROM_DATABASE=ZX-100/KX-6000/KX-6000G EIDE Controller pci:v00001D17d00009043* - ID_MODEL_FROM_DATABASE=KX-6000G/KH-40000/KX-7000 RAID Controller + ID_MODEL_FROM_DATABASE=KX-6000G/KH-40000/KX-7000/KH-50000 RAID Controller pci:v00001D17d00009045* ID_MODEL_FROM_DATABASE=ZX-100/ZX-D/ZX-E RAID Accelerator 0 @@ -79422,7 +80487,7 @@ pci:v00001D17d00009046* ID_MODEL_FROM_DATABASE=ZX-D/ZX-E RAID Accelerator 1 pci:v00001D17d00009083* - ID_MODEL_FROM_DATABASE=ZX-100/ZX-200/KX-6000/KX-6000G/KH-40000/KX-7000 StorX AHCI Controller + ID_MODEL_FROM_DATABASE=ZX-100/ZX-200/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 StorX AHCI Controller pci:v00001D17d00009084* ID_MODEL_FROM_DATABASE=ZX-100 StorX AHCI Controller @@ -79472,11 +80537,14 @@ pci:v00001D17d00009205* pci:v00001D17d00009206* ID_MODEL_FROM_DATABASE=KX-7000 USB4 Contoller +pci:v00001D17d00009207* + ID_MODEL_FROM_DATABASE=KH-50000 USB eXtensible Host Controller + pci:v00001D17d00009286* ID_MODEL_FROM_DATABASE=ZX-D eMMC Host Controller pci:v00001D17d00009300* - ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 eSPI Host Controller + ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 eSPI Host Controller pci:v00001D17d00009500* ID_MODEL_FROM_DATABASE=KX-7000 I2S Controller @@ -79488,7 +80556,7 @@ pci:v00001D17d000095D0* ID_MODEL_FROM_DATABASE=ZX-100 Universal SD Host Controller pci:v00001D17d0000F410* - ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Com Port + ID_MODEL_FROM_DATABASE=ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Com Port pci:v00001D18* ID_VENDOR_FROM_DATABASE=RME @@ -79877,6 +80945,9 @@ pci:v00001D6Cd0000102E* pci:v00001D6Cd0000102F* ID_MODEL_FROM_DATABASE=AR-TK242-FX2 [1x400GbE Gen5 Packet Capture-Replay Device] +pci:v00001D6Cd00001030* + ID_MODEL_FROM_DATABASE=AR-ARKSTREAM [Arkville Streaming DMA] + pci:v00001D6Cd00004200* ID_MODEL_FROM_DATABASE=A5PL-E1-10GETI [10 GbE Ethernet Traffic Instrument] @@ -82124,6 +83195,9 @@ pci:v00001E0Fd0000001A* pci:v00001E0Fd0000001B* ID_MODEL_FROM_DATABASE=NVMe SSD Controller EG6 (DRAM-less) +pci:v00001E0Fd0000001E* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller LD2-L + pci:v00001E0Fd0000001F* ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD8 @@ -82268,15 +83342,267 @@ pci:v00001E0Fd00000033* pci:v00001E0Fd00000034* ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD +pci:v00001E0Fd00000034sv00001028sd0000240F* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9FRJE1T92) + +pci:v00001E0Fd00000034sv00001028sd00002410* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9FRJE3T84) + +pci:v00001E0Fd00000034sv00001028sd00002411* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9FRJE7T68) + +pci:v00001E0Fd00000034sv00001028sd00002412* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9FRJE15T3) + +pci:v00001E0Fd00000034sv00001028sd00002413* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9FRJE30T7) + +pci:v00001E0Fd00000034sv00001028sd00002414* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9XRJE1T92) + +pci:v00001E0Fd00000034sv00001028sd00002415* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9XRJE3T84) + +pci:v00001E0Fd00000034sv00001028sd00002416* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9XRJE7T68) + +pci:v00001E0Fd00000034sv00001028sd00002417* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9XRJE15T3) + +pci:v00001E0Fd00000034sv00001028sd00002418* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9XRJE30T7) + +pci:v00001E0Fd00000034sv00001028sd00002419* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9FVJE1T60) + +pci:v00001E0Fd00000034sv00001028sd0000241A* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9FVJE3T20) + +pci:v00001E0Fd00000034sv00001028sd0000241B* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9FVJE6T40) + +pci:v00001E0Fd00000034sv00001028sd0000241C* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9FVJE12T8) + +pci:v00001E0Fd00000034sv00001028sd0000241D* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9XVJE1T60) + +pci:v00001E0Fd00000034sv00001028sd0000241E* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9XVJE3T20) + +pci:v00001E0Fd00000034sv00001028sd0000241F* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9XVJE6T40) + +pci:v00001E0Fd00000034sv00001028sd00002420* + ID_MODEL_FROM_DATABASE=CM9-based E3.S NVMe SSD (KCM9XVJE12T8) + pci:v00001E0Fd00000035* ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD +pci:v00001E0Fd00000035sv00001028sd000023FB* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9FRUL1T92) + +pci:v00001E0Fd00000035sv00001028sd000023FC* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9FRUL3T84) + +pci:v00001E0Fd00000035sv00001028sd000023FD* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9FRUL7T68) + +pci:v00001E0Fd00000035sv00001028sd000023FE* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9FRUL15T3) + +pci:v00001E0Fd00000035sv00001028sd000023FF* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9FRUL30T7) + +pci:v00001E0Fd00000035sv00001028sd00002400* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9FRUL61T4) + +pci:v00001E0Fd00000035sv00001028sd00002401* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9XRUL1T92) + +pci:v00001E0Fd00000035sv00001028sd00002402* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9XRUL3T84) + +pci:v00001E0Fd00000035sv00001028sd00002403* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9XRUL7T68) + +pci:v00001E0Fd00000035sv00001028sd00002404* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9XRUL15T3) + +pci:v00001E0Fd00000035sv00001028sd00002405* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9XRUL30T7) + +pci:v00001E0Fd00000035sv00001028sd00002406* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9XRUL61T4) + +pci:v00001E0Fd00000035sv00001028sd00002407* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9FVUL1T60) + +pci:v00001E0Fd00000035sv00001028sd00002408* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9FVUL3T20) + +pci:v00001E0Fd00000035sv00001028sd00002409* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9FVUL6T40) + +pci:v00001E0Fd00000035sv00001028sd0000240A* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9FVUL12T8) + +pci:v00001E0Fd00000035sv00001028sd0000240B* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9XVUL1T60) + +pci:v00001E0Fd00000035sv00001028sd0000240C* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9XVUL3T20) + +pci:v00001E0Fd00000035sv00001028sd0000240D* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9XVUL6T40) + +pci:v00001E0Fd00000035sv00001028sd0000240E* + ID_MODEL_FROM_DATABASE=CM9-based U3 NVMe SSD (KCM9XVUL12T8) + +pci:v00001E0Fd00000036* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 + +pci:v00001E0Fd00000036sv00001028sd000023D5* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9DPUG1T92) + +pci:v00001E0Fd00000036sv00001028sd000023D6* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9DPUG3T84) + +pci:v00001E0Fd00000036sv00001028sd000023D7* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9DPUG7T68) + +pci:v00001E0Fd00000036sv00001028sd000023D8* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9DPUG15T3) + +pci:v00001E0Fd00000036sv00001028sd000023D9* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9DPUG30T7) + +pci:v00001E0Fd00000036sv00001028sd000023DA* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9DPUG61T4) + +pci:v00001E0Fd00000036sv00001028sd000023DB* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9XPUG1T92) + +pci:v00001E0Fd00000036sv00001028sd000023DC* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9XPUG3T84) + +pci:v00001E0Fd00000036sv00001028sd000023DD* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9XPUG7T68) + +pci:v00001E0Fd00000036sv00001028sd000023DE* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9XPUG15T3) + +pci:v00001E0Fd00000036sv00001028sd000023DF* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9XPUG30T7) + +pci:v00001E0Fd00000036sv00001028sd000023E0* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9XPUG61T4) + +pci:v00001E0Fd00000036sv00001028sd000023E1* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9DPUG1T60) + +pci:v00001E0Fd00000036sv00001028sd000023E2* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9DPUG3T20) + +pci:v00001E0Fd00000036sv00001028sd000023E3* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9DPUG6T40) + +pci:v00001E0Fd00000036sv00001028sd000023E4* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9DPUG12T8) + +pci:v00001E0Fd00000036sv00001028sd000023E5* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9XPUG1T60) + +pci:v00001E0Fd00000036sv00001028sd000023E6* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9XPUG3T20) + +pci:v00001E0Fd00000036sv00001028sd000023E7* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9XPUG6T40) + +pci:v00001E0Fd00000036sv00001028sd000023E8* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P U.2 (KCD9XPUG12T8) + +pci:v00001E0Fd00000037* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S + +pci:v00001E0Fd00000037sv00001028sd000023E9* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9DPJE1T92) + +pci:v00001E0Fd00000037sv00001028sd000023EA* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9DPJE3T84) + +pci:v00001E0Fd00000037sv00001028sd000023EB* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9DPJE7T68) + +pci:v00001E0Fd00000037sv00001028sd000023EC* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9DPJE15T3) + +pci:v00001E0Fd00000037sv00001028sd000023ED* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9DPJE30T7) + +pci:v00001E0Fd00000037sv00001028sd000023EE* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9XPJE1T92) + +pci:v00001E0Fd00000037sv00001028sd000023EF* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9XPJE3T84) + +pci:v00001E0Fd00000037sv00001028sd000023F0* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9XPJE7T68) + +pci:v00001E0Fd00000037sv00001028sd000023F1* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9XPJE15T3) + +pci:v00001E0Fd00000037sv00001028sd000023F2* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9XPJE30T7) + +pci:v00001E0Fd00000037sv00001028sd000023F3* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9DPJE1T60) + +pci:v00001E0Fd00000037sv00001028sd000023F4* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9DPJE3T20) + +pci:v00001E0Fd00000037sv00001028sd000023F5* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9DPJE6T40) + +pci:v00001E0Fd00000037sv00001028sd000023F6* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9DPJE12T8) + +pci:v00001E0Fd00000037sv00001028sd000023F7* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9XPJE1T60) + +pci:v00001E0Fd00000037sv00001028sd000023F8* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9XPJE3T20) + +pci:v00001E0Fd00000037sv00001028sd000023F9* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9XPJE6T40) + +pci:v00001E0Fd00000037sv00001028sd000023FA* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller CD9P E3.S (KCD9XPJE12T8) + +pci:v00001E0Fd0000003A* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller BG8 (DRAM-less) + +pci:v00001E0Fd0000003B* + ID_MODEL_FROM_DATABASE=Exceria Basic NVMe SSD (DRAM-less) + +pci:v00001E0Fd0000003D* + ID_MODEL_FROM_DATABASE=LC9 E3.L NVMe SSD + +pci:v00001E0Fd0000003Dsv00001028sd0000244F* + ID_MODEL_FROM_DATABASE=LC9 E3.L NVMe SSD (RLC9GZV245T) + +pci:v00001E0Fd0000003Dsv00001028sd00002450* + ID_MODEL_FROM_DATABASE=LC9 E3.L NVMe SSD (RLC9CZV245T) + pci:v00001E0Fd0000003E* ID_MODEL_FROM_DATABASE=LC9 E3.S NVMe SSD pci:v00001E0Fd0000003F* ID_MODEL_FROM_DATABASE=LC9 U.2 NVMe SSD +pci:v00001E0Fd00000042* + ID_MODEL_FROM_DATABASE=NVMe SSD Controller LD4 + pci:v00001E17* ID_VENDOR_FROM_DATABASE=Arnold & Richter Cine Technik GmbH & Co. Betriebs KG @@ -82319,6 +83645,9 @@ pci:v00001E30d00001684* pci:v00001E30d00002042* ID_MODEL_FROM_DATABASE=SG2042 Root Complex +pci:v00001E31* + ID_VENDOR_FROM_DATABASE=SORD CORPORATION + pci:v00001E36* ID_VENDOR_FROM_DATABASE=Shanghai Enflame Technology Co. Ltd @@ -83462,6 +84791,9 @@ pci:v00001E81d00006206* pci:v00001E81d00006A02* ID_MODEL_FROM_DATABASE=AM6A0 NVMe SSD (DRAM-less) +pci:v00001E81d00006C14* + ID_MODEL_FROM_DATABASE=RPEYJ1T24 NVMe SSD (DRAM-less) + pci:v00001E83* ID_VENDOR_FROM_DATABASE=Huaqin Technology Co.Ltd @@ -83487,7 +84819,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 +84843,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) @@ -83537,6 +84869,24 @@ pci:v00001E95d0000100Dsv00001E95sd00000002* pci:v00001E95d0000100Dsv00001E95sd00000003* ID_MODEL_FROM_DATABASE=PJ1 Series NVMe SSD (M.2 22110 3840 GB) +pci:v00001E95d0000100Dsv00001E95sd00000004* + ID_MODEL_FROM_DATABASE=PJ1 Series NVMe SSD (U.2 1920 GB) + +pci:v00001E95d0000100Dsv00001E95sd00000005* + ID_MODEL_FROM_DATABASE=PJ1 Series NVMe SSD (U.2 3840 GB) + +pci:v00001E95d0000100Dsv00001E95sd00000006* + ID_MODEL_FROM_DATABASE=PJ1 Series NVMe SSD (U.2 7680 GB) + +pci:v00001E95d0000100Dsv00001E95sd00000007* + ID_MODEL_FROM_DATABASE=PJ1 Series NVMe SSD (U.2 1600 GB) + +pci:v00001E95d0000100Dsv00001E95sd00000008* + ID_MODEL_FROM_DATABASE=PJ1 Series NVMe SSD (U.2 3200 GB) + +pci:v00001E95d0000100Dsv00001E95sd00000009* + ID_MODEL_FROM_DATABASE=PJ1 Series NVMe SSD (U.2 6400 GB) + pci:v00001E95d0000100F* ID_MODEL_FROM_DATABASE=EJ5-2W3840 NVMe SSD U.2 @@ -84146,6 +85496,102 @@ pci:v00001EE4d00001180sv00001EE4sd00000626* pci:v00001EE4d00001180sv00001EE4sd00000627* ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 6.4TB (P8128Z3)) +pci:v00001EE4d00001180sv00001EE4sd00000715* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.92TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00000716* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.84TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00000717* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 7.68TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00000718* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 15.36TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00000725* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.6TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00000726* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.2TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00000727* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 6.4TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00000728* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 12.8TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00000815* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.92TB (P8118H4)) + +pci:v00001EE4d00001180sv00001EE4sd00000816* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.84TB (P8118H4)) + +pci:v00001EE4d00001180sv00001EE4sd00000817* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 7.68TB (P8118H4)) + +pci:v00001EE4d00001180sv00001EE4sd00000825* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.6TB (P8118H4)) + +pci:v00001EE4d00001180sv00001EE4sd00000826* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.2TB (P8118H4)) + +pci:v00001EE4d00001180sv00001EE4sd00000827* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 6.4TB (P8118H4)) + +pci:v00001EE4d00001180sv00001EE4sd00000915* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.92TB (P8118E2)) + +pci:v00001EE4d00001180sv00001EE4sd00000916* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.84TB (P8118E2)) + +pci:v00001EE4d00001180sv00001EE4sd00000917* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 7.68TB (P8118E2)) + +pci:v00001EE4d00001180sv00001EE4sd00000925* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.6TB (P8118E2)) + +pci:v00001EE4d00001180sv00001EE4sd00000926* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.2TB (P8118E2)) + +pci:v00001EE4d00001180sv00001EE4sd00000927* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 6.4TB (P8118E2)) + +pci:v00001EE4d00001180sv00001EE4sd00000A15* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.92TB (P8118H2)) + +pci:v00001EE4d00001180sv00001EE4sd00000A16* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.84TB (P8118H2)) + +pci:v00001EE4d00001180sv00001EE4sd00000A17* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 7.68TB (P8118H2)) + +pci:v00001EE4d00001180sv00001EE4sd00000A25* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.6TB (P8118H2)) + +pci:v00001EE4d00001180sv00001EE4sd00000A26* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.2TB (P8118H2)) + +pci:v00001EE4d00001180sv00001EE4sd00000A27* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 6.4TB (P8118H2)) + +pci:v00001EE4d00001180sv00001EE4sd00000B15* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.92TB (P8118H3)) + +pci:v00001EE4d00001180sv00001EE4sd00000B16* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.84TB (P8118H3)) + +pci:v00001EE4d00001180sv00001EE4sd00000B17* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 7.68TB (P8118H3)) + +pci:v00001EE4d00001180sv00001EE4sd00000B25* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 1.6TB (P8118H3)) + +pci:v00001EE4d00001180sv00001EE4sd00000B26* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 3.2TB (P8118H3)) + +pci:v00001EE4d00001180sv00001EE4sd00000B27* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2 6.4TB (P8118H3)) + pci:v00001EE4d00001180sv00001EE4sd00003013* ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD AIC 480GB (P8118E)) @@ -84290,6 +85736,30 @@ pci:v00001EE4d00001180sv00001EE4sd00003626* pci:v00001EE4d00001180sv00001EE4sd00003627* ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD AIC 6.4TB (P8128Z3)) +pci:v00001EE4d00001180sv00001EE4sd00003715* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD AIC 1.92TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00003716* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD AIC 3.84TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00003717* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD AIC 7.68TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00003718* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD AIC 15.36TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00003725* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD AIC 1.6TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00003726* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD AIC 3.2TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00003727* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD AIC 6.4TB (P8118Z4)) + +pci:v00001EE4d00001180sv00001EE4sd00003728* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD AIC 12.8TB (P8118Z4)) + pci:v00001EE4d00001180sv00001EE4sd0000ABCD* ID_MODEL_FROM_DATABASE=PETA8118 NVMe SSD Series (NVMe SSD U.2) @@ -84422,6 +85892,30 @@ pci:v00001EE4d00001181sv00001EE4sd00002626* pci:v00001EE4d00001181sv00001EE4sd00002627* ID_MODEL_FROM_DATABASE=PETA8118 NVMe E1S Series (NVMe SSD E1.S 6.4TB (P8128Z3)) +pci:v00001EE4d00001181sv00001EE4sd00002715* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe E1S Series (NVMe SSD E1.S 1.92TB (P8118Z4)) + +pci:v00001EE4d00001181sv00001EE4sd00002716* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe E1S Series (NVMe SSD E1.S 3.84TB (P8118Z4)) + +pci:v00001EE4d00001181sv00001EE4sd00002717* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe E1S Series (NVMe SSD E1.S 7.68TB (P8118Z4)) + +pci:v00001EE4d00001181sv00001EE4sd00002718* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe E1S Series (NVMe SSD E1.S 15.36TB (P8118Z4)) + +pci:v00001EE4d00001181sv00001EE4sd00002725* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe E1S Series (NVMe SSD E1.S 1.6TB (P8118Z4)) + +pci:v00001EE4d00001181sv00001EE4sd00002726* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe E1S Series (NVMe SSD E1.S 3.2TB (P8118Z4)) + +pci:v00001EE4d00001181sv00001EE4sd00002727* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe E1S Series (NVMe SSD E1.S 6.4TB (P8118Z4)) + +pci:v00001EE4d00001181sv00001EE4sd00002728* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe E1S Series (NVMe SSD E1.S 12.8TB (P8118Z4)) + pci:v00001EE4d00001182* ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series @@ -84617,6 +86111,54 @@ pci:v00001EE4d00001182sv00001EE4sd00001626* pci:v00001EE4d00001182sv00001EE4sd00001627* ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 6.4TB (P8128Z3)) +pci:v00001EE4d00001182sv00001EE4sd00001714* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 960GB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001715* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 1.92TB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001716* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 3.84TB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001717* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 7.68TB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001724* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 800GB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001725* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 1.6TB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001726* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 3.2TB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001727* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 6.4TB (P8118Z4)) + +pci:v00001EE4d00001182sv00001EE4sd00001814* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 960GB (P8118H4)) + +pci:v00001EE4d00001182sv00001EE4sd00001815* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 1.92TB (P8118H4)) + +pci:v00001EE4d00001182sv00001EE4sd00001816* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 3.84TB (P8118H4)) + +pci:v00001EE4d00001182sv00001EE4sd00001817* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 7.68TB (P8118H4)) + +pci:v00001EE4d00001182sv00001EE4sd00001824* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 800GB (P8118H4)) + +pci:v00001EE4d00001182sv00001EE4sd00001825* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 1.6TB (P8118H4)) + +pci:v00001EE4d00001182sv00001EE4sd00001826* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 3.2TB (P8118H4)) + +pci:v00001EE4d00001182sv00001EE4sd00001827* + ID_MODEL_FROM_DATABASE=PETA8118 NVMe M2 Series (NVMe SSD M.2 6.4TB (P8118H4)) + pci:v00001EE9* ID_VENDOR_FROM_DATABASE=SUSE LLC @@ -84818,6 +86360,9 @@ pci:v00001F03* pci:v00001F03d00001202* ID_MODEL_FROM_DATABASE=MAP1202-Based NVMe SSD (DRAM-less) +pci:v00001F03d00001608* + ID_MODEL_FROM_DATABASE=SCY C5000 NVMe SSD (DRAM-less) + pci:v00001F03d00002262* ID_MODEL_FROM_DATABASE=SM2262EN-based OEM SSD @@ -85056,10 +86601,16 @@ pci:v00001F0Fd00003504* ID_MODEL_FROM_DATABASE=M18305 Family BASE-T pci:v00001F0Fd00003504sv00001F0Fsd00000001* - ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2025XT, 2x 10GbE, Base-T, PCIe 4.0 x8, Fan) + ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2025XT, 2x 10GbE, Base-T, PCIe 4.0 x8) pci:v00001F0Fd00003504sv00001F0Fsd00000002* - ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2025XT, 2x 10GbE, Base-T, PCIe 4.0 x8) + ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2025XT, 2x 10GbE, BASE-T, PCIe 4.0 x4, Fan) + +pci:v00001F0Fd00003504sv00001F0Fsd00000003* + ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2045XT, 4x 10GbE, Base-T, PCIe 4.0 x8) + +pci:v00001F0Fd00003504sv00001F0Fsd00000004* + ID_MODEL_FROM_DATABASE=M18305 Family BASE-T (S2045XT, 4x 10GbE, BASE-T, PCIe 4.0 x8, Fan) pci:v00001F0Fd0000350A* ID_MODEL_FROM_DATABASE=M18305 Family Virtual Function @@ -85196,6 +86747,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 @@ -85430,15 +86987,45 @@ pci:v00001F47d00001013* pci:v00001F47d00001105* ID_MODEL_FROM_DATABASE=CONFLUX-2200P NVMe Controller [Virtual Function] -pci:v00001F47d00001203* - ID_MODEL_FROM_DATABASE=K2-Pro Family [FLEXFLOW-2200T RoCEv2 Network Controller] - pci:v00001F47d00002018* ID_MODEL_FROM_DATABASE=DPU Card pci:v00001F47d00002020* ID_MODEL_FROM_DATABASE=DPU +pci:v00001F47d00003011* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] + +pci:v00001F47d00003011sv00001F47sd0000000A* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 1*100GE Ethernet Adapter) + +pci:v00001F47d00003011sv00001F47sd0000000B* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 2*100GE Ethernet Adapter) + +pci:v00001F47d00003011sv00001F47sd0000000C* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 4*100GE Ethernet Adapter) + +pci:v00001F47d00003011sv00001F47sd0000000D* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 8*100GE Ethernet Adapter) + +pci:v00001F47d00003011sv00001F47sd0000000E* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 1*100GE Ethernet Adapter) + +pci:v00001F47d00003011sv00001F47sd0000000F* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 2*100GE Ethernet Adapter) + +pci:v00001F47d00003011sv00001F47sd00000010* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 4*100GE Ethernet Adapter) + +pci:v00001F47d00003011sv00001F47sd00000011* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 8*100GE Ethernet Adapter) + +pci:v00001F47d00003012* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R Virtual Function] + +pci:v00001F47d00003013* + ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R MGMT Function] + pci:v00001F47d00003101* ID_MODEL_FROM_DATABASE=FLEXFLOW-2100R Ethernet Controller @@ -85493,18 +87080,6 @@ pci:v00001F47d00003201sv00001F47sd00000007* pci:v00001F47d00003201sv00001F47sd00000008* ID_MODEL_FROM_DATABASE=FLEXFLOW-2200R Ethernet Controller (Ethernet 100G 2P FLEXFLOW-2200R) -pci:v00001F47d00003301* - ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] - -pci:v00001F47d00003301sv00001F47sd00000001* - ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R] (FLEXFLOW-3100R 1*100GE Ethernet Adapter) - -pci:v00001F47d00003302* - ID_MODEL_FROM_DATABASE=K3 Family [FLEXFLOW-3100R Virtual Function] - -pci:v00001F47d00003303* - ID_MODEL_FROM_DATABASE=K3 Family [CONFLUX-3100R MGMT Function] - pci:v00001F47d00004001* ID_MODEL_FROM_DATABASE=K2-Pro Family [CONFLUX-2200E] @@ -85541,8 +87116,26 @@ pci:v00001F47d00004003* pci:v00001F47d00004004* ID_MODEL_FROM_DATABASE=K2-Pro Family [CONFLUX-2200E DATA Offload Engine] -pci:v00001F47d00004203* - ID_MODEL_FROM_DATABASE=K2-Pro Family [CONFLUX-2200E RoCEv2 Network Controller] +pci:v00001F47d00004011* + ID_MODEL_FROM_DATABASE=K3 Family [CONFLUX-3100E] + +pci:v00001F47d00004011sv00001F47sd00000001* + ID_MODEL_FROM_DATABASE=K3 Family [CONFLUX-3100E] (CONFLUX-3100E 2*10GE Ethernet Adapter) + +pci:v00001F47d00004011sv00001F47sd00000002* + ID_MODEL_FROM_DATABASE=K3 Family [CONFLUX-3100E] (CONFLUX-3100E 4*10GE Ethernet Adapter) + +pci:v00001F47d00004011sv00001F47sd00000003* + ID_MODEL_FROM_DATABASE=K3 Family [CONFLUX-3100E] (CONFLUX-3100E 2*25GE Ethernet Adapter) + +pci:v00001F47d00004011sv00001F47sd00000004* + ID_MODEL_FROM_DATABASE=K3 Family [CONFLUX-3100E] (CONFLUX-3100E 4*25GE Ethernet Adapter) + +pci:v00001F47d00004012* + ID_MODEL_FROM_DATABASE=K3 Family [CONFLUX-3100E Virtual Function] + +pci:v00001F47d00004013* + ID_MODEL_FROM_DATABASE=K3 Family [CONFLUX-3100E MGMT Function] pci:v00001F47d00005001* ID_MODEL_FROM_DATABASE=CONFLUX-2200P Ethernet Controller @@ -85689,7 +87282,7 @@ pci:v00001F52* ID_VENDOR_FROM_DATABASE=MangoBoost Inc. pci:v00001F52d00001008* - ID_MODEL_FROM_DATABASE=Mango GPUBoost - RDMA + ID_MODEL_FROM_DATABASE=Mango BoostX - RoCE AI pci:v00001F52d00001020* ID_MODEL_FROM_DATABASE=Mango NetworkBoost - TCP @@ -85784,6 +87377,27 @@ pci:v00001F82d00000011* pci:v00001F82d000000F1* ID_MODEL_FROM_DATABASE=JetStream [DMX F1 Transparent NIC] +pci:v00001F8C* + ID_VENDOR_FROM_DATABASE=Exascend,INC. + +pci:v00001F8Cd00008008* + ID_MODEL_FROM_DATABASE=SM8008 NVMe SSD [Px5 Series SSD] + +pci:v00001F8Cd00008008sv00001F8Csd00000001* + ID_MODEL_FROM_DATABASE=SM8008 NVMe SSD [Px5 Series SSD] (PD5 Series General) + +pci:v00001F8Cd00008008sv00001F8Csd00000002* + ID_MODEL_FROM_DATABASE=SM8008 NVMe SSD [Px5 Series SSD] (Px5 Series General) + +pci:v00001F8Cd00008366* + ID_MODEL_FROM_DATABASE=SM8366 NVMe SSD [PD5 Series SSD] + +pci:v00001F8Cd00008366sv00001F8Csd00000001* + ID_MODEL_FROM_DATABASE=SM8366 NVMe SSD [PD5 Series SSD] (PD5 Series General) + +pci:v00001F8Cd00008366sv00001F8Csd00000002* + ID_MODEL_FROM_DATABASE=SM8366 NVMe SSD [PD5 Series SSD] (Px5 Series General) + pci:v00001F90* ID_VENDOR_FROM_DATABASE=Quside Technologies @@ -85799,6 +87413,9 @@ pci:v00001F99* pci:v00001F99d00001202* ID_MODEL_FROM_DATABASE=TE3420 series / Patriot P320 M.2 NVMe SSD (DRAM-less) +pci:v00001F99d00001602* + ID_MODEL_FROM_DATABASE=PCIe Gen4 x4 M.2 2280 (DRAM-less) + pci:v00001F99d00001608* ID_MODEL_FROM_DATABASE=PCIe Gen4 x4 M.2 2280 @@ -86019,7 +87636,7 @@ pci:v00001FBDd00000006* ID_MODEL_FROM_DATABASE=D20 Pro N pci:v00001FBDd00000101* - ID_MODEL_FROM_DATABASE=D2 + ID_MODEL_FROM_DATABASE=T1 pci:v00001FC0* ID_VENDOR_FROM_DATABASE=Ascom (Finland) Oy @@ -86453,6 +88070,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 @@ -86498,20 +88127,26 @@ pci:v00001FF2d000030A2sv00001FF2sd00000B24* pci:v00001FF4* ID_VENDOR_FROM_DATABASE=DEEPX Co., Ltd. -pci:v00001FF4d00000000* - ID_MODEL_FROM_DATABASE=DX_M1 +pci:v00001FF4d00000100* + ID_MODEL_FROM_DATABASE=M1 [Series] + +pci:v00001FF4d00000101* + ID_MODEL_FROM_DATABASE=M1 [H1] -pci:v00001FF4d00000001* - ID_MODEL_FROM_DATABASE=DX_M1A +pci:v00001FF4d00000102* + ID_MODEL_FROM_DATABASE=M1 [H1 V-NPU] -pci:v00001FF4d00001000* - ID_MODEL_FROM_DATABASE=DX_H1 +pci:v00001FF4d00000110* + ID_MODEL_FROM_DATABASE=M1M [Series] -pci:v00001FF4d00002000* - ID_MODEL_FROM_DATABASE=DX_VNPU_M1 +pci:v00001FF4d00000111* + ID_MODEL_FROM_DATABASE=M1M [H1M] + +pci:v00001FF4d00000112* + ID_MODEL_FROM_DATABASE=M1M [H1M V-NPU] pci:v00001FF4d00002001* - ID_MODEL_FROM_DATABASE=DX_VNPU + ID_MODEL_FROM_DATABASE=VPU [H1/H1M V-NPU] pci:v00001FF8* ID_VENDOR_FROM_DATABASE=Beijing Gengtu Technology Co.Ltd @@ -86702,6 +88337,51 @@ pci:v00002036d00008000sv00002036sd00001006* pci:v0000203B* ID_VENDOR_FROM_DATABASE=XTX Markets Technologies Ltd. +pci:v00002042* + ID_VENDOR_FROM_DATABASE=Xi'an UniIC Semiconductors Co., Ltd + +pci:v00002042d0000DB00* + ID_MODEL_FROM_DATABASE=UWSC256DDQYACC-N + +pci:v00002042d0000DB01* + ID_MODEL_FROM_DATABASE=UWSC512DDQLBCC-N + +pci:v00002042d0000DB02* + ID_MODEL_FROM_DATABASE=UWSC512DDQLACC-N + +pci:v00002042d0000DB03* + ID_MODEL_FROM_DATABASE=UWSC1T0DDQLACC-N + +pci:v00002042d0000DC00* + ID_MODEL_FROM_DATABASE=UWSD512DDQYACA-N + +pci:v00002042d0000DC01* + ID_MODEL_FROM_DATABASE=UWSD1T0DDQYACA-N + +pci:v00002042d0000DC02* + ID_MODEL_FROM_DATABASE=UWSD2T0DDQYACA-N + +pci:v00002042d0000DC03* + ID_MODEL_FROM_DATABASE=UWSD1T0DDTYACA-N + +pci:v00002042d0000DC04* + ID_MODEL_FROM_DATABASE=UWSD2T0DDTYACA-N + +pci:v00002042d0000DC05* + ID_MODEL_FROM_DATABASE=UWSD4T0DDTYACA-N + +pci:v00002042d0000DC06* + ID_MODEL_FROM_DATABASE=UWSD512DDQYACB-N + +pci:v00002042d0000DC07* + ID_MODEL_FROM_DATABASE=UWSD1T0DDQYACB-N + +pci:v00002042d0000DC08* + ID_MODEL_FROM_DATABASE=UWSD512DDTMACA-N + +pci:v00002042d0000DC09* + ID_MODEL_FROM_DATABASE=UWSD1T0DDTMACA-N + pci:v00002044* ID_VENDOR_FROM_DATABASE=Shenzhen Jiahua Zhongli Technology Co., LTD. @@ -87101,11 +88781,14 @@ pci:v0000209Bd00001000* pci:v0000209Bd00001001* ID_MODEL_FROM_DATABASE=TCU Family - TCU-1 Virtual Function +pci:v0000209Bd00001002* + ID_MODEL_FROM_DATABASE=TCU Family - TCU-1 PCIe Switch + pci:v0000209F* ID_VENDOR_FROM_DATABASE=Mobilint, Inc. pci:v000020A1* - ID_VENDOR_FROM_DATABASE=Etched AI, Inc. + ID_VENDOR_FROM_DATABASE=Etched, Inc. pci:v000020A1d00000001* ID_MODEL_FROM_DATABASE=Sohu @@ -87119,6 +88802,9 @@ pci:v000020A7* pci:v000020A8* ID_VENDOR_FROM_DATABASE=Rayson HI-TECH(SZ) Co., Ltd. +pci:v000020A8d00001202* + ID_MODEL_FROM_DATABASE=RS512GSSD510 PCIe 3 NVMe SSD (DRAM-less) + pci:v000020A9* ID_VENDOR_FROM_DATABASE=LDA Technologies Ltd. @@ -87128,6 +88814,12 @@ pci:v000020A9d00001008* pci:v000020A9d00001104* ID_MODEL_FROM_DATABASE=NEOTAPX FPGA Timing Synchronization Card +pci:v000020A9d00001200* + ID_MODEL_FROM_DATABASE=MUX Ultimate FPGA Accelerator Card + +pci:v000020A9d00001201* + ID_MODEL_FROM_DATABASE=MUX Ultimate FPGA Accelerator Card + pci:v000020B5* ID_VENDOR_FROM_DATABASE=Shanghai StarFive Technology Co., Ltd. @@ -87236,6 +88928,15 @@ pci:v000020DC* pci:v000020DCd00001202* ID_MODEL_FROM_DATABASE=M.2 2280 PCIe Gen3 x 4 Series. +pci:v000020DCd00001202sv00001202sd00001256* + ID_MODEL_FROM_DATABASE=M.2 2280 PCIe Gen3 x 4 Series. (M.2 2280 256GB 1202+N38A) + +pci:v000020DCd00005000* + ID_MODEL_FROM_DATABASE=E5000 U.2 15mm 3.84TB NVMe SSD + +pci:v000020DCd00005101* + ID_MODEL_FROM_DATABASE=E5000 U.2 15mm 7.68TB NVMe SSD + pci:v000020E1* ID_VENDOR_FROM_DATABASE=CECloud Computing Technology Co., Ltd. @@ -87248,6 +88949,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 +88979,12 @@ pci:v000020F6d00000001* pci:v000020F9* ID_VENDOR_FROM_DATABASE=Shenzhen Silicon Dynamic Networks Co., Ltd. +pci:v00002100* + ID_VENDOR_FROM_DATABASE=Shenzhen Kimviking Semiconductor 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,9 +88994,27 @@ 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:v00002108d00002401* + ID_MODEL_FROM_DATABASE=PCIe4.0 to USB3.2 Gen2 Host Controller + +pci:v00002114* + ID_VENDOR_FROM_DATABASE=EigenQ, Inc. + +pci:v00002114d00000007* + ID_MODEL_FROM_DATABASE=QMA Board M.2 Gen2 + +pci:v00002114d0000000A* + ID_MODEL_FROM_DATABASE=QMA Board PCIe Gen2 + pci:v00002116* ID_VENDOR_FROM_DATABASE=ZyDAS Technology Corp. +pci:v00002123* + ID_VENDOR_FROM_DATABASE=Shanghai Warpdrive Technology Co., Ltd + pci:v000021B4* ID_VENDOR_FROM_DATABASE=Hunan Goke Microelectronics Co., Ltd @@ -87339,10 +89076,10 @@ pci:v00002646d0000500B* ID_MODEL_FROM_DATABASE=DC1000M NVMe SSD [SM2270] pci:v00002646d0000500C* - ID_MODEL_FROM_DATABASE=OM8PCP Design-In PCIe 3 NVMe SSD (DRAM-less) + ID_MODEL_FROM_DATABASE=OM8PCP3 PCIe 3 NVMe SSD (DRAM-less) pci:v00002646d0000500D* - ID_MODEL_FROM_DATABASE=OM3PDP3 NVMe SSD + ID_MODEL_FROM_DATABASE=OM3PDP3 PCIe 3 NVMe SSD (DRAM-less) pci:v00002646d0000500E* ID_MODEL_FROM_DATABASE=NV1 NVMe SSD [E13T] (DRAM-less) @@ -87360,10 +89097,10 @@ pci:v00002646d00005013* ID_MODEL_FROM_DATABASE=KC3000/FURY Renegade NVMe SSD [E18] pci:v00002646d00005014* - ID_MODEL_FROM_DATABASE=OM8SEP4 Design-In PCIe 4 NVMe SSD (TLC) (DRAM-less) + ID_MODEL_FROM_DATABASE=OM8SEP4 PCIe 4 NVMe SSD (TLC) (DRAM-less) pci:v00002646d00005016* - ID_MODEL_FROM_DATABASE=OM3PGP4 NVMe SSD (DRAM-less) + ID_MODEL_FROM_DATABASE=OM3PGP4 PCIe 4 NVMe SSD (DRAM-less) pci:v00002646d00005017* ID_MODEL_FROM_DATABASE=NV2 NVMe SSD [SM2267XT] (DRAM-less) @@ -87375,7 +89112,7 @@ pci:v00002646d00005019* ID_MODEL_FROM_DATABASE=NV2 NVMe SSD [E21T] (DRAM-less) pci:v00002646d0000501A* - ID_MODEL_FROM_DATABASE=OM8PGP4 Design-In PCIe 4 NVMe SSD (TLC) (DRAM-less) + ID_MODEL_FROM_DATABASE=OM8PGP4 PCIe 4 NVMe SSD (TLC) (DRAM-less) pci:v00002646d0000501B* ID_MODEL_FROM_DATABASE=OM8PGP4 NVMe PCIe SSD (DRAM-less) @@ -87393,10 +89130,10 @@ pci:v00002646d0000501F* ID_MODEL_FROM_DATABASE=FURY Renegade NVMe SSD [E18] (Heatsink) pci:v00002646d00005021* - ID_MODEL_FROM_DATABASE=OM8SEP4 Design-In PCIe 4 NVMe SSD (QLC) (DRAM-less) + ID_MODEL_FROM_DATABASE=OM8SEP4 PCIe 4 NVMe SSD (QLC) (DRAM-less) pci:v00002646d00005022* - ID_MODEL_FROM_DATABASE=OM8PGP4 Design-In PCIe 4 NVMe SSD (QLC) (DRAM-less) + ID_MODEL_FROM_DATABASE=OM8PGP4 PCIe 4 NVMe SSD (QLC) (DRAM-less) pci:v00002646d00005023* ID_MODEL_FROM_DATABASE=NV2 NVMe SSD [SM2269XT] (DRAM-less) @@ -87428,9 +89165,15 @@ pci:v00002646d0000502C* pci:v00002646d0000502D* ID_MODEL_FROM_DATABASE=OM8TAP4 PCIe 4 NVMe SSD (QLC) (DRAM-less) +pci:v00002646d0000502F* + ID_MODEL_FROM_DATABASE=OM3SGP4 PCIe 4 NVMe SSD (TLC) + pci:v00002646d00005030* ID_MODEL_FROM_DATABASE=NV3 2230 NVMe SSD [SM2268XT2] (DRAM-less) +pci:v00002646d00005034* + ID_MODEL_FROM_DATABASE=NV3 NVMe SSD [E33T] (DRAM-less) + pci:v0000270B* ID_VENDOR_FROM_DATABASE=Xantel Corporation @@ -88010,9 +89753,24 @@ pci:v0000434Ed00000001sv0000434Esd00000004* pci:v0000434Ed00000002* ID_MODEL_FROM_DATABASE=CN6000 HFI Silicon, Dual Port, BGA [discrete] +pci:v0000434Ed00000002sv0000434Esd00000001* + ID_MODEL_FROM_DATABASE=CN6000 HFI Silicon, Dual Port, BGA [discrete] (CN6000 SuperNIC, Single Port, QSFP-DD, x16 PCIe Gen 6) + pci:v0000434Ed00008001* ID_MODEL_FROM_DATABASE=CN5000 Switch Silicon, 48 Port, BGA +pci:v0000434Ed00008001sv0000434Esd00000101* + ID_MODEL_FROM_DATABASE=CN5000 Switch Silicon, 48 Port, BGA (CN5000 Switch) + +pci:v0000434Ed00008001sv0000434Esd00000103* + ID_MODEL_FROM_DATABASE=CN5000 Switch Silicon, 48 Port, BGA (CN5000 Director Class Switch Spine) + +pci:v0000434Ed00008001sv0000434Esd00000104* + ID_MODEL_FROM_DATABASE=CN5000 Switch Silicon, 48 Port, BGA (CN5000 Director Class Switch Leaf) + +pci:v0000434Ed00008001sv0000434Esd00000106* + ID_MODEL_FROM_DATABASE=CN5000 Switch Silicon, 48 Port, BGA (CN6000 Switch) + pci:v00004444* ID_VENDOR_FROM_DATABASE=Internext Compression Inc @@ -88697,6 +90455,24 @@ pci:v00004C54* pci:v00004C54d00005000* ID_MODEL_FROM_DATABASE=LISUAN 7G100 Series Graphics +pci:v00004C54d00005001* + ID_MODEL_FROM_DATABASE=LISUAN 7G100 Series Graphics + +pci:v00004C54d00005002* + ID_MODEL_FROM_DATABASE=LISUAN 7G100 Series Graphics + +pci:v00004C54d00005003* + ID_MODEL_FROM_DATABASE=LISUAN 7G100 Series Graphics + +pci:v00004C54d00005004* + ID_MODEL_FROM_DATABASE=LISUAN 7G100 Series Graphics + +pci:v00004C54d00005005* + ID_MODEL_FROM_DATABASE=LISUAN 7G100 Series Graphics + +pci:v00004C54d00005006* + ID_MODEL_FROM_DATABASE=LISUAN 7G100 Series Graphics + pci:v00004CA1* ID_VENDOR_FROM_DATABASE=Seanix Technology Inc @@ -89556,7 +91332,7 @@ pci:v00006666d00004000* ID_MODEL_FROM_DATABASE=WatchDog Card pci:v00006688* - ID_VENDOR_FROM_DATABASE=Zycoo Co., Ltd + ID_VENDOR_FROM_DATABASE=GUANGZHOU MAXSUN INFORMATION TECHNOLOGY CO., LTD. pci:v00006688d00001200* ID_MODEL_FROM_DATABASE=CooVox TDM Analog Module @@ -90150,10 +91926,10 @@ pci:v00008086d00000155sv00008086sd00002010* ID_MODEL_FROM_DATABASE=Xeon E3-1200 v2/3rd Gen Core processor PCI Express Root Port (Server Board S1200BTS) pci:v00008086d00000156* - ID_MODEL_FROM_DATABASE=3rd Gen Core processor Graphics Controller + ID_MODEL_FROM_DATABASE=Ivy Bridge mobile GT1 [HD Graphics] pci:v00008086d00000156sv00001043sd0000108D* - ID_MODEL_FROM_DATABASE=3rd Gen Core processor Graphics Controller (VivoBook X202EV) + ID_MODEL_FROM_DATABASE=Ivy Bridge mobile GT1 [HD Graphics] (VivoBook X202EV) pci:v00008086d00000158* ID_MODEL_FROM_DATABASE=Xeon E3-1200 v2/Ivy Bridge DRAM Controller @@ -90192,16 +91968,16 @@ pci:v00008086d00000162sv00001849sd00000162* ID_MODEL_FROM_DATABASE=IvyBridge GT2 [HD Graphics 4000] (Motherboard) pci:v00008086d00000166* - ID_MODEL_FROM_DATABASE=3rd Gen Core processor Graphics Controller + ID_MODEL_FROM_DATABASE=Ivy Bridge mobile GT2 [HD Graphics 4000] pci:v00008086d00000166sv00001043sd00001517* - ID_MODEL_FROM_DATABASE=3rd Gen Core processor Graphics Controller (Zenbook Prime UX31A) + ID_MODEL_FROM_DATABASE=Ivy Bridge mobile GT2 [HD Graphics 4000] (Zenbook Prime UX31A) pci:v00008086d00000166sv00001043sd00002103* - ID_MODEL_FROM_DATABASE=3rd Gen Core processor Graphics Controller (N56VZ) + ID_MODEL_FROM_DATABASE=Ivy Bridge mobile GT2 [HD Graphics 4000] (N56VZ) pci:v00008086d00000166sv000010CFsd000016C1* - ID_MODEL_FROM_DATABASE=3rd Gen Core processor Graphics Controller (LIFEBOOK E752) + ID_MODEL_FROM_DATABASE=Ivy Bridge mobile GT2 [HD Graphics 4000] (LIFEBOOK E752) pci:v00008086d0000016A* ID_MODEL_FROM_DATABASE=Xeon E3-1200 v2/3rd Gen Core processor Graphics Controller @@ -90219,142 +91995,220 @@ pci:v00008086d00000201* ID_MODEL_FROM_DATABASE=Arctic Sound pci:v00008086d00000284* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP LPC Premium Controller/eSPI Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCH-LP Prem-U LPC/eSPI Controller pci:v00008086d00000284sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP LPC Premium Controller/eSPI Controller (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCH-LP Prem-U LPC/eSPI Controller (Latitude 7410) + +pci:v00008086d00000285* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCH-LP Mainstream/Base U LPC/eSPI Controller + +pci:v00008086d000002A0* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package P2SB + +pci:v00008086d000002A1* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PMC pci:v00008086d000002A3* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP SMBus Host Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SMBus pci:v00008086d000002A3sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP SMBus Host Controller (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SMBus (Latitude 7410) pci:v00008086d000002A4* - ID_MODEL_FROM_DATABASE=Comet Lake SPI (flash) Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SPI (flash) Controller pci:v00008086d000002A4sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake SPI (flash) Controller (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SPI (flash) Controller (Latitude 7410) pci:v00008086d000002A6* - ID_MODEL_FROM_DATABASE=Comet Lake North Peak + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Trace Hub + +pci:v00008086d000002A8* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package UART #0 + +pci:v00008086d000002A9* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package UART #1 + +pci:v00008086d000002AA* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SPI #0 + +pci:v00008086d000002AB* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SPI #1 pci:v00008086d000002B0* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #9 pci:v00008086d000002B1* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #10 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #10 + +pci:v00008086d000002B2* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #11 pci:v00008086d000002B3* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #12 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #12 pci:v00008086d000002B4* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #13 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #13 pci:v00008086d000002B5* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #14 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #14 + +pci:v00008086d000002B6* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #15 + +pci:v00008086d000002B7* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #16 pci:v00008086d000002B8* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #1 + +pci:v00008086d000002B9* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #2 + +pci:v00008086d000002BA* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #3 + +pci:v00008086d000002BB* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #4 pci:v00008086d000002BC* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #5 + +pci:v00008086d000002BD* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #6 + +pci:v00008086d000002BE* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #7 pci:v00008086d000002BF* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package PCIe Root Port #8 + +pci:v00008086d000002C4* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package eMMC pci:v00008086d000002C5* - ID_MODEL_FROM_DATABASE=Comet Lake Serial IO I2C Host Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #4 pci:v00008086d000002C5sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake Serial IO I2C Host Controller (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #4 (Latitude 7410) + +pci:v00008086d000002C6* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #5 + +pci:v00008086d000002C7* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package UART #2 pci:v00008086d000002C8* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP cAVS + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package HD Audio pci:v00008086d000002C8sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP cAVS (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package HD Audio (Latitude 7410) pci:v00008086d000002D3* - ID_MODEL_FROM_DATABASE=Comet Lake SATA AHCI Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SATA Controller (AHCI) + +pci:v00008086d000002D5* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) no premium pci:v00008086d000002D7* - ID_MODEL_FROM_DATABASE=Comet Lake RAID Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) premium pci:v00008086d000002E0* - ID_MODEL_FROM_DATABASE=Comet Lake Management Engine Interface + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package MEI #1 pci:v00008086d000002E0sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake Management Engine Interface (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package MEI #1 (Latitude 7410) + +pci:v00008086d000002E1* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package MEI #2 + +pci:v00008086d000002E2* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package IDE Redirection (IDER-R) pci:v00008086d000002E3* - ID_MODEL_FROM_DATABASE=Comet Lake AMT SOL Redirection + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Keyboard and Text (KT) Redirection + +pci:v00008086d000002E4* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package MEI #3 + +pci:v00008086d000002E5* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package MEI #4 pci:v00008086d000002E8* - ID_MODEL_FROM_DATABASE=Serial IO I2C Host Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #0 pci:v00008086d000002E8sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Serial IO I2C Host Controller (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #0 (Latitude 7410) pci:v00008086d000002E9* - ID_MODEL_FROM_DATABASE=Comet Lake Serial IO I2C Host Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #1 pci:v00008086d000002E9sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake Serial IO I2C Host Controller (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #1 (Latitude 7410) pci:v00008086d000002EA* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP LPSS: I2C Controller #2 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #2 + +pci:v00008086d000002EB* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package I2C #3 pci:v00008086d000002ED* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP USB 3.1 xHCI Host Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package USB 3.2 Gen 2x1 (10 Gbs) xHCI Host Controller pci:v00008086d000002EDsv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP USB 3.1 xHCI Host Controller (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package USB 3.2 Gen 2x1 (10 Gbs) xHCI Host Controller (Latitude 7410) + +pci:v00008086d000002EE* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) pci:v00008086d000002EF* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP Shared SRAM + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Shared SRAM pci:v00008086d000002EFsv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP Shared SRAM (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Shared SRAM (Latitude 7410) pci:v00008086d000002F0* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP CNVi WiFi + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package CNVi WiFi pci:v00008086d000002F0sv00008086sd00000034* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP CNVi WiFi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9560 160MHz 2x2 [Jefferson Peak]) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package CNVi WiFi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9560 160MHz 2x2 [Jefferson Peak]) pci:v00008086d000002F0sv00008086sd00000070* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP CNVi WiFi (Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak]) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package CNVi WiFi (Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak]) pci:v00008086d000002F0sv00008086sd00000074* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP CNVi WiFi (Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak]) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package CNVi WiFi (Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak]) pci:v00008086d000002F0sv00008086sd00000234* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP CNVi WiFi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9560 80MHz 2x2 [Jefferson Peak]) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package CNVi WiFi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9560 80MHz 2x2 [Jefferson Peak]) pci:v00008086d000002F0sv00008086sd00000264* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP CNVi WiFi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9461 80MHz 1x1 [Jefferson Peak]) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package CNVi WiFi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9461 80MHz 1x1 [Jefferson Peak]) pci:v00008086d000002F0sv00008086sd000002A4* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP CNVi WiFi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9462 80MHz 1x1 [Jefferson Peak]) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package CNVi WiFi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9462 80MHz 1x1 [Jefferson Peak]) pci:v00008086d000002F0sv00008086sd00004070* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP CNVi WiFi (Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak]) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package CNVi WiFi (Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak]) pci:v00008086d000002F5* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-LP SCS3 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SDXC pci:v00008086d000002F9* - ID_MODEL_FROM_DATABASE=Comet Lake Thermal Subsytem + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Thermal Subsystem pci:v00008086d000002F9sv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake Thermal Subsytem (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Thermal Subsystem (Latitude 7410) + +pci:v00008086d000002FB* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package SPI #2 pci:v00008086d000002FC* - ID_MODEL_FROM_DATABASE=Comet Lake Integrated Sensor Solution + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Integrated Sensor Hub pci:v00008086d000002FCsv00001028sd000009BE* - ID_MODEL_FROM_DATABASE=Comet Lake Integrated Sensor Solution (Latitude 7410) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package Integrated Sensor Hub (Latitude 7410) pci:v00008086d00000309* ID_MODEL_FROM_DATABASE=80303 I/O Processor PCI-to-PCI Bridge @@ -90611,116 +92465,197 @@ pci:v00008086d0000068E* pci:v00008086d00000697* ID_MODEL_FROM_DATABASE=W480 Chipset LPC/eSPI Controller +pci:v00008086d000006A0* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family P2SB + +pci:v00008086d000006A1* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PMC + pci:v00008086d000006A3* - ID_MODEL_FROM_DATABASE=Comet Lake PCH SMBus Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family SMBus pci:v00008086d000006A4* - ID_MODEL_FROM_DATABASE=Comet Lake PCH SPI Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family SPI (flash) Controller + +pci:v00008086d000006A6* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Trace Hub pci:v00008086d000006A8* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO UART Host Controller #0 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family UART #0 pci:v00008086d000006A9* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO UART Host Controller #1 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family UART #1 pci:v00008086d000006AA* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO SPI Controller #0 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family GSPI #0 pci:v00008086d000006AB* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO SPI Controller #1 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family GSPI #1 pci:v00008086d000006AC* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #21 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #21 + +pci:v00008086d000006AD* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #22 + +pci:v00008086d000006AE* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #23 + +pci:v00008086d000006AF* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #24 pci:v00008086d000006B0* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #9 + +pci:v00008086d000006B1* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #10 + +pci:v00008086d000006B2* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #11 + +pci:v00008086d000006B3* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #12 + +pci:v00008086d000006B4* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #13 + +pci:v00008086d000006B5* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #14 + +pci:v00008086d000006B6* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #15 + +pci:v00008086d000006B7* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #16 pci:v00008086d000006B8* - ID_MODEL_FROM_DATABASE=Comet Lake PCIe Root Port #1 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #1 + +pci:v00008086d000006B9* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #2 pci:v00008086d000006BA* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #3 pci:v00008086d000006BB* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #4 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #4 + +pci:v00008086d000006BC* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #5 pci:v00008086d000006BD* - ID_MODEL_FROM_DATABASE=Comet Lake PCIe Port #6 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #6 pci:v00008086d000006BE* - ID_MODEL_FROM_DATABASE=Comet Lake PCIe Root Port #7 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #7 pci:v00008086d000006BF* - ID_MODEL_FROM_DATABASE=Comet Lake PCIe Port #8 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #8 pci:v00008086d000006C0* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #17 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #17 + +pci:v00008086d000006C1* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #18 + +pci:v00008086d000006C2* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #19 + +pci:v00008086d000006C3* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family PCIe Root Port #20 + +pci:v00008086d000006C7* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family UART #2 pci:v00008086d000006C8* - ID_MODEL_FROM_DATABASE=Comet Lake PCH cAVS + ID_MODEL_FROM_DATABASE=400 Series Chipset Family HD Audio pci:v00008086d000006D2* - ID_MODEL_FROM_DATABASE=Comet Lake SATA AHCI Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family SATA Controller (AHCI) (Desktop) + +pci:v00008086d000006D3* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family SATA Controller (AHCI) (Mobile) + +pci:v00008086d000006D5* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family SATA Controller (RAID 0/1/5/10) no premium (Mobile) pci:v00008086d000006D6* ID_MODEL_FROM_DATABASE=Comet Lake PCH-H RAID pci:v00008086d000006D7* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-H RAID + ID_MODEL_FROM_DATABASE=400 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium (Mobile) + +pci:v00008086d000006DE* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family SATA Controller (AHCI) Optane Caching pci:v00008086d000006E0* - ID_MODEL_FROM_DATABASE=Comet Lake HECI Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family HECI #1 + +pci:v00008086d000006E1* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family HECI #2 + +pci:v00008086d000006E2* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family IDE Redirection (IDE-R) pci:v00008086d000006E3* - ID_MODEL_FROM_DATABASE=Comet Lake Keyboard and Text (KT) Redirection + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Keyboard and Text (KT) Redirection + +pci:v00008086d000006E4* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family HECI #3 + +pci:v00008086d000006E5* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family HECI #4 pci:v00008086d000006E8* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family I2C #0 pci:v00008086d000006E9* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family I2C #1 pci:v00008086d000006EA* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO I2C Controller #2 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family I2C #2 pci:v00008086d000006EB* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO I2C Controller #3 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family I2C #3 pci:v00008086d000006ED* - ID_MODEL_FROM_DATABASE=Comet Lake USB 3.1 xHCI Host Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family USB 3.2 Gen 2x1 (10 Gbs) xHCI Host Controller pci:v00008086d000006EF* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Shared SRAM + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Shared SRAM pci:v00008086d000006F0* - ID_MODEL_FROM_DATABASE=Comet Lake PCH CNVi WiFi + ID_MODEL_FROM_DATABASE=400 Series Chipset Family CNVi Wi-Fi pci:v00008086d000006F0sv00001A56sd00001651* - ID_MODEL_FROM_DATABASE=Comet Lake PCH CNVi WiFi (Dual Band Wi-Fi 6(802.11ax) Killer AX1650s 160MHz 2x2 [Cyclone Peak]) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family CNVi Wi-Fi (Dual Band Wi-Fi 6(802.11ax) Killer AX1650s 160MHz 2x2 [Cyclone Peak]) pci:v00008086d000006F0sv00001A56sd00001652* - ID_MODEL_FROM_DATABASE=Comet Lake PCH CNVi WiFi (Dual Band Wi-Fi 6(802.11ax) Killer AX1650i 160MHz 2x2 [Cyclone Peak]) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family CNVi Wi-Fi (Dual Band Wi-Fi 6(802.11ax) Killer AX1650i 160MHz 2x2 [Cyclone Peak]) pci:v00008086d000006F0sv00008086sd00000034* - ID_MODEL_FROM_DATABASE=Comet Lake PCH CNVi WiFi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9560 160MHz 2x2 [Jefferson Peak]) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family CNVi Wi-Fi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9560 160MHz 2x2 [Jefferson Peak]) pci:v00008086d000006F0sv00008086sd00000074* - ID_MODEL_FROM_DATABASE=Comet Lake PCH CNVi WiFi (Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak]) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family CNVi Wi-Fi (Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak]) pci:v00008086d000006F0sv00008086sd000002A4* - ID_MODEL_FROM_DATABASE=Comet Lake PCH CNVi WiFi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9462 80MHz 1x1 [Jefferson Peak]) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family CNVi Wi-Fi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9462 80MHz 1x1 [Jefferson Peak]) pci:v00008086d000006F0sv00008086sd000042A4* - ID_MODEL_FROM_DATABASE=Comet Lake PCH CNVi WiFi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9462 80MHz 1x1 [Jefferson Peak]) + ID_MODEL_FROM_DATABASE=400 Series Chipset Family CNVi Wi-Fi (Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9462 80MHz 1x1 [Jefferson Peak]) + +pci:v00008086d000006F5* + ID_MODEL_FROM_DATABASE=400 Series Chipset Family SCS3 SDXC pci:v00008086d000006F9* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Thermal Controller + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Thermal Subsystem pci:v00008086d000006FB* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Serial IO SPI Controller #2 + ID_MODEL_FROM_DATABASE=400 Series Chipset Family GSPI #2 pci:v00008086d000006FC* - ID_MODEL_FROM_DATABASE=Comet Lake PCH Integrated Sensor Solution + ID_MODEL_FROM_DATABASE=400 Series Chipset Family Integrated Sensor Hub pci:v00008086d00000700* ID_MODEL_FROM_DATABASE=CE Media Processor A/V Bridge @@ -91521,22 +93456,22 @@ pci:v00008086d00000975* ID_MODEL_FROM_DATABASE=Optane NVME SSD H10 with Solid State Storage [Teton Glacier] pci:v00008086d00000998* - ID_MODEL_FROM_DATABASE=Ice Lake IEH + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors IEH pci:v00008086d000009A2* - ID_MODEL_FROM_DATABASE=Ice Lake Memory Map/VT-d + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors VT-d pci:v00008086d000009A3* - ID_MODEL_FROM_DATABASE=Ice Lake RAS + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors RAS pci:v00008086d000009A4* - ID_MODEL_FROM_DATABASE=Ice Lake Mesh 2 PCIe + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors UFI pci:v00008086d000009A6* - ID_MODEL_FROM_DATABASE=Ice Lake MSM + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors MSM pci:v00008086d000009A7* - ID_MODEL_FROM_DATABASE=Ice Lake PMON MSM + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PMON MSM pci:v00008086d000009AB* ID_MODEL_FROM_DATABASE=RST VMD Managed Controller @@ -91770,7 +93705,7 @@ pci:v00008086d00000B00* ID_MODEL_FROM_DATABASE=Ice Lake CBDMA [QuickData Technology] pci:v00008086d00000B23* - ID_MODEL_FROM_DATABASE=Xeon Root Event Collector + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors IEH pci:v00008086d00000B25* ID_MODEL_FROM_DATABASE=Data Streaming Accelerator (DSA) @@ -92172,19 +94107,19 @@ pci:v00008086d00000D36* ID_MODEL_FROM_DATABASE=Crystal Well Integrated Graphics Controller pci:v00008086d00000D4C* - ID_MODEL_FROM_DATABASE=Ethernet Connection (11) I219-LM + ID_MODEL_FROM_DATABASE=400 Series Chipset Family GbE Controller (Corporate/vPro) pci:v00008086d00000D4D* - ID_MODEL_FROM_DATABASE=Ethernet Connection (11) I219-V + ID_MODEL_FROM_DATABASE=400 Series Chipset Family GbE Controller (Consumer) pci:v00008086d00000D4Dsv00008086sd00000D4D* - ID_MODEL_FROM_DATABASE=Ethernet Connection (11) I219-V + ID_MODEL_FROM_DATABASE=400 Series Chipset Family GbE Controller (Consumer) (Ethernet Connection (11) I219-V) pci:v00008086d00000D4E* - ID_MODEL_FROM_DATABASE=Ethernet Connection (10) I219-LM + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package GbE Controller (Corporate/vPro) pci:v00008086d00000D4F* - ID_MODEL_FROM_DATABASE=Ethernet Connection (10) I219-V + ID_MODEL_FROM_DATABASE=400 Series Chipset Family On-Package GbE Controller (Consumer) pci:v00008086d00000D53* ID_MODEL_FROM_DATABASE=Ethernet Connection (12) I219-LM @@ -92204,6 +94139,33 @@ pci:v00008086d00000D58sv00008086sd00000001* pci:v00008086d00000D9F* ID_MODEL_FROM_DATABASE=Ethernet Controller I225-IT +pci:v00008086d00000DB0* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #0 + +pci:v00008086d00000DB1* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #1 + +pci:v00008086d00000DB2* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #2 + +pci:v00008086d00000DB3* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #3 + +pci:v00008086d00000DB4* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors NTB + +pci:v00008086d00000DB6* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #4 + +pci:v00008086d00000DB7* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #5 + +pci:v00008086d00000DB8* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #6 + +pci:v00008086d00000DB9* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #7 + pci:v00008086d00000DC5* ID_MODEL_FROM_DATABASE=Ethernet Connection (23) I219-LM @@ -94514,6 +96476,9 @@ pci:v00008086d000011C3* pci:v00008086d000011C4* ID_MODEL_FROM_DATABASE=Quark SoC X1000 PCIe Root Port 1 +pci:v00008086d000011DF* + ID_MODEL_FROM_DATABASE=Infrastructure Data Path Function + pci:v00008086d000011EB* ID_MODEL_FROM_DATABASE=Simics NVMe Controller @@ -96629,6 +98594,9 @@ pci:v00008086d0000158Bsv00008086sd0000000D* pci:v00008086d0000158Bsv00008086sd00004001* ID_MODEL_FROM_DATABASE=Ethernet Controller XXV710 for 25GbE SFP28 (Ethernet Network Adapter XXV710-2) +pci:v00008086d00001590* + ID_MODEL_FROM_DATABASE=Ethernet Connection E810-C + pci:v00008086d00001591* ID_MODEL_FROM_DATABASE=Ethernet Controller E810-C for backplane @@ -96773,6 +98741,15 @@ pci:v00008086d00001593sv00008086sd00004013* pci:v00008086d00001593sv00008086sd0000401C* ID_MODEL_FROM_DATABASE=Ethernet Controller E810-C for SFP (Ethernet Network Adapter E810-XXV-4 for OCP 3.0) +pci:v00008086d00001594* + ID_MODEL_FROM_DATABASE=Ethernet Controller E810-C/X557-AT 10GBASE-T + +pci:v00008086d00001595* + ID_MODEL_FROM_DATABASE=Ethernet Controller E810-C 1GbE + +pci:v00008086d00001598* + ID_MODEL_FROM_DATABASE=Ethernet Connection E810-XXV + pci:v00008086d00001599* ID_MODEL_FROM_DATABASE=Ethernet Controller E810-XXV for backplane @@ -96839,6 +98816,12 @@ pci:v00008086d0000159Bsv00008086sd00004003* pci:v00008086d0000159Bsv00008086sd00004015* ID_MODEL_FROM_DATABASE=Ethernet Controller E810-XXV for SFP (Ethernet Network Adapter E810-XXV-2 for OCP 3.0) +pci:v00008086d0000159C* + ID_MODEL_FROM_DATABASE=Ethernet Controller E810-XXV/X557-AT 10GBASE-T + +pci:v00008086d0000159D* + ID_MODEL_FROM_DATABASE=Ethernet Controller E810-XXV 1GbE + pci:v00008086d000015A0* ID_MODEL_FROM_DATABASE=Ethernet Connection (2) I218-LM @@ -97113,10 +99096,10 @@ pci:v00008086d000015F6* ID_MODEL_FROM_DATABASE=I210 Gigabit Ethernet Connection pci:v00008086d000015F9* - ID_MODEL_FROM_DATABASE=Ethernet Connection (14) I219-LM + ID_MODEL_FROM_DATABASE=500 Series Chipset Family GbE Controller (Corporate/vPro) pci:v00008086d000015FA* - ID_MODEL_FROM_DATABASE=Ethernet Connection (14) I219-V + ID_MODEL_FROM_DATABASE=500 Series Chipset Family GbE Controller (Consumer) pci:v00008086d000015FB* ID_MODEL_FROM_DATABASE=Ethernet Connection (13) I219-LM @@ -103427,6 +105410,9 @@ pci:v00008086d00002710* pci:v00008086d00002714* ID_MODEL_FROM_DATABASE=Dynamic Load Balancer 2.5 (DLB) +pci:v00008086d00002715* + ID_MODEL_FROM_DATABASE=Dynamic Load Balancer (DLB) Virtual Function + pci:v00008086d00002723* ID_MODEL_FROM_DATABASE=Wi-Fi 6 AX200 @@ -104700,22 +106686,22 @@ pci:v00008086d00002821* ID_MODEL_FROM_DATABASE=82801HR/HO/HH (ICH8R/DO/DH) 6 port SATA Controller [AHCI mode] pci:v00008086d00002822* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID mode] + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Desktop RST) pci:v00008086d00002822sv00001028sd0000020D* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID mode] (Inspiron 530) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Desktop RST) (Inspiron 530) pci:v00008086d00002822sv0000103Csd00002A6F* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID mode] (Asus IPIBL-LB Motherboard) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Desktop RST) (Asus IPIBL-LB Motherboard) pci:v00008086d00002822sv00001043sd00008277* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID mode] (P5K PRO Motherboard: 82801IR [ICH9R]) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Desktop RST) (P5K PRO Motherboard: 82801IR [ICH9R]) pci:v00008086d00002822sv00001462sd00007345* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID mode] (MS-7345 Motherboard: Intel 82801I/IR [ICH9/ICH9R]) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Desktop RST) (MS-7345 Motherboard: Intel 82801I/IR [ICH9/ICH9R]) pci:v00008086d00002823* - ID_MODEL_FROM_DATABASE=sSATA Controller [RAID Mode] + ID_MODEL_FROM_DATABASE=SSATA Controller (RAID 0/1/5/10) pci:v00008086d00002824* ID_MODEL_FROM_DATABASE=82801HB (ICH8) 4 port SATA Controller [AHCI mode] @@ -104733,28 +106719,28 @@ pci:v00008086d00002825sv00001462sd00007235* ID_MODEL_FROM_DATABASE=82801HR/HO/HH (ICH8R/DO/DH) 2 port SATA Controller [IDE mode] (P965 Neo MS-7235 mainboard) pci:v00008086d00002826* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID Mode] + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) pci:v00008086d00002826sv00001D49sd00000100* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID Mode] (Intel RSTe SATA Software RAID) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) (Intel RSTe SATA Software RAID) pci:v00008086d00002826sv00001D49sd00000101* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID Mode] (Intel RSTe SATA Software RAID) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) (Intel RSTe SATA Software RAID) pci:v00008086d00002826sv00001D49sd00000102* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID Mode] (Intel RSTe SATA Software RAID) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) (Intel RSTe SATA Software RAID) pci:v00008086d00002826sv00001D49sd00000103* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID Mode] (Intel RSTe SATA Software RAID) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) (Intel RSTe SATA Software RAID) pci:v00008086d00002826sv00001D49sd00000104* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID Mode] (Intel RSTe SATA Software RAID) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) (Intel RSTe SATA Software RAID) pci:v00008086d00002826sv00001D49sd00000105* - ID_MODEL_FROM_DATABASE=SATA Controller [RAID Mode] (Intel RSTe SATA Software RAID) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) (Intel RSTe SATA Software RAID) pci:v00008086d00002827* - ID_MODEL_FROM_DATABASE=sSATA Controller [RAID Mode] + ID_MODEL_FROM_DATABASE=SSATA Controller (RAID 0/1/5/10) pci:v00008086d00002828* ID_MODEL_FROM_DATABASE=82801HM/HEM (ICH8M/ICH8M-E) SATA Controller [IDE mode] @@ -104811,13 +106797,13 @@ pci:v00008086d00002829sv0000E4BFsd0000CC47* ID_MODEL_FROM_DATABASE=82801HM/HEM (ICH8M/ICH8M-E) SATA Controller [AHCI mode] (CCG-RUMBA) pci:v00008086d0000282A* - ID_MODEL_FROM_DATABASE=82801 Mobile SATA Controller [RAID mode] + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Mobile RST) pci:v00008086d0000282Asv00001028sd0000040B* - ID_MODEL_FROM_DATABASE=82801 Mobile SATA Controller [RAID mode] (Latitude E6510) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Mobile RST) (Latitude E6510) pci:v00008086d0000282Asv0000E4BFsd000050C1* - ID_MODEL_FROM_DATABASE=82801 Mobile SATA Controller [RAID mode] (PC1-GROOVE) + ID_MODEL_FROM_DATABASE=SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Mobile RST) (PC1-GROOVE) pci:v00008086d0000282B* ID_MODEL_FROM_DATABASE=C740 Series (Emmitsburg) Chipsets SATA2 Controller (RAID) Alternate ID @@ -105441,10 +107427,10 @@ pci:v00008086d00002880* ID_MODEL_FROM_DATABASE=Ice Lake DDRIO Registers pci:v00008086d000028C0* - ID_MODEL_FROM_DATABASE=Volume Management Device NVMe RAID Controller + ID_MODEL_FROM_DATABASE=Volume Management Device (VMD) pci:v00008086d000028C0sv00001D49sd0000011A* - ID_MODEL_FROM_DATABASE=Volume Management Device NVMe RAID Controller (Intel VROC (VMD NVMe RAID) for ThinkSystem V4 PL) + ID_MODEL_FROM_DATABASE=Volume Management Device (VMD) (Intel VROC (VMD NVMe RAID) for ThinkSystem V4 PL) pci:v00008086d00002912* ID_MODEL_FROM_DATABASE=82801IH (ICH9DH) LPC Interface Controller @@ -108206,17 +110192,38 @@ pci:v00008086d00003200* pci:v00008086d00003200sv00001775sd0000C200* ID_MODEL_FROM_DATABASE=GD31244 PCI-X SATA HBA (C2K onboard SATA host bus adapter) +pci:v00008086d00003240* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors UPI Misc + +pci:v00008086d00003241* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors UPI link/Phy0 + +pci:v00008086d00003242* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors UPI Phy0 + pci:v00008086d00003245* - ID_MODEL_FROM_DATABASE=Xeon UPI Mesh Stop M2UPI Registers + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors UPI pci:v00008086d0000324A* - ID_MODEL_FROM_DATABASE=Xeon IMC0 Mesh to Mem Registers + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors IMC pci:v00008086d0000324C* - ID_MODEL_FROM_DATABASE=Xeon Unicast Group1 CHA Registers + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors CHA Unicast Group 1 pci:v00008086d0000324D* - ID_MODEL_FROM_DATABASE=Xeon Unicast Group0 CHA Registers + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors CHA Unicast Group 0 + +pci:v00008086d00003250* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors Ubox Event Control + +pci:v00008086d00003251* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors Ubox Register Access Control Unit + +pci:v00008086d00003252* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors Ubox Decode + +pci:v00008086d00003256* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors Trace Hub pci:v00008086d00003258* ID_MODEL_FROM_DATABASE=Power Control Unit (PCU) CR0 @@ -108495,7 +110502,7 @@ pci:v00008086d0000344D* ID_MODEL_FROM_DATABASE=Ice Lake CHA Registers pci:v00008086d0000344F* - ID_MODEL_FROM_DATABASE=Ice Lake CHA Registers + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors CHA All 0 pci:v00008086d00003450* ID_MODEL_FROM_DATABASE=Ice Lake Ubox Registers @@ -108513,7 +110520,7 @@ pci:v00008086d00003456* ID_MODEL_FROM_DATABASE=Ice Lake NorthPeak pci:v00008086d00003457* - ID_MODEL_FROM_DATABASE=Ice Lake CHA Registers + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors CHA All 1 pci:v00008086d00003458* ID_MODEL_FROM_DATABASE=Ice Lake PCU Registers @@ -109250,32 +111257,41 @@ pci:v00008086d0000372C* pci:v00008086d0000373F* ID_MODEL_FROM_DATABASE=Xeon C5500/C3500 IOxAPIC +pci:v00008086d000037B1* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family Thermal Sensor + pci:v00008086d000037C0* - ID_MODEL_FROM_DATABASE=C62x chipset series PCIe x16/x8 Upstream Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Uplink (x16) + +pci:v00008086d000037C1* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Uplink (x8) pci:v00008086d000037C2* - ID_MODEL_FROM_DATABASE=C62x chipset series PCIe Virtual Switch Port 0 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family Virtual Switch Port 0 pci:v00008086d000037C3* - ID_MODEL_FROM_DATABASE=C62x chipset series PCIe Virtual Switch Port 1 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family Virtual Switch Port 1 pci:v00008086d000037C4* - ID_MODEL_FROM_DATABASE=C62x chipset series PCIe Virtual Switch Port 2 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family Virtual Switch Port 2 pci:v00008086d000037C5* - ID_MODEL_FROM_DATABASE=C62x chipset series PCIe Virtual Switch Port 3 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family Virtual Switch Port 3 + +pci:v00008086d000037C7* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family Virtual Switch Port 5 pci:v00008086d000037C8* - ID_MODEL_FROM_DATABASE=C62x Chipset series QuickAssist Technology Physical Function 0~2 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family QAT pci:v00008086d000037C8sv00008086sd00000001* - ID_MODEL_FROM_DATABASE=C62x Chipset series QuickAssist Technology Physical Function 0~2 (QuickAssist Adapter 8960) + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family QAT (QuickAssist Adapter 8960) pci:v00008086d000037C8sv00008086sd00000002* - ID_MODEL_FROM_DATABASE=C62x Chipset series QuickAssist Technology Physical Function 0~2 (QuickAssist Adapter 8970) + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family QAT (QuickAssist Adapter 8970) pci:v00008086d000037C9* - ID_MODEL_FROM_DATABASE=C62x Chipset QuickAssist Technology Virtual Function + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family QAT Virtual Function pci:v00008086d000037CC* ID_MODEL_FROM_DATABASE=Ethernet Connection X722 @@ -110591,6 +112607,9 @@ pci:v00008086d00003E9A* pci:v00008086d00003E9B* ID_MODEL_FROM_DATABASE=CoffeeLake-H GT2 [UHD Graphics 630] +pci:v00008086d00003E9Bsv0000106Bsd0000019C* + ID_MODEL_FROM_DATABASE=CoffeeLake-H GT2 [UHD Graphics 630] (MacBookPro16,1 (16", 2019)) + pci:v00008086d00003E9C* ID_MODEL_FROM_DATABASE=Coffee Lake-S GT1 [UHD Graphics 610] @@ -111027,40 +113046,40 @@ pci:v00008086d0000423Dsv00008086sd00001316* ID_MODEL_FROM_DATABASE=WiMAX/WiFi Link 5150 (ABG) pci:v00008086d00004384* - ID_MODEL_FROM_DATABASE=Q570 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Q570 Chipset eSPI Controller pci:v00008086d00004385* - ID_MODEL_FROM_DATABASE=Z590 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Z590 Chipset eSPI Controller pci:v00008086d00004386* - ID_MODEL_FROM_DATABASE=H570 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=H570 Chipset eSPI Controller pci:v00008086d00004387* - ID_MODEL_FROM_DATABASE=B560 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=B560 Chipset eSPI Controller pci:v00008086d00004388* - ID_MODEL_FROM_DATABASE=H510 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=H510 Chipset eSPI Controller pci:v00008086d00004389* - ID_MODEL_FROM_DATABASE=WM590 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=WM590 Chipset eSPI Controller pci:v00008086d0000438A* - ID_MODEL_FROM_DATABASE=QM580 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=QM580 Chipset eSPI Controller pci:v00008086d0000438B* - ID_MODEL_FROM_DATABASE=HM570 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=HM570 Chipset eSPI Controller pci:v00008086d0000438C* - ID_MODEL_FROM_DATABASE=C252 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C252 Chipset eSPI Controller pci:v00008086d0000438D* - ID_MODEL_FROM_DATABASE=C256 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C256 Chipset eSPI Controller pci:v00008086d0000438E* ID_MODEL_FROM_DATABASE=H310D LPC/eSPI Controller pci:v00008086d0000438F* - ID_MODEL_FROM_DATABASE=W580 LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=W580 Chipset eSPI Controller pci:v00008086d00004390* ID_MODEL_FROM_DATABASE=RM590E LPC/eSPI Controller @@ -111068,77 +113087,248 @@ pci:v00008086d00004390* pci:v00008086d00004391* ID_MODEL_FROM_DATABASE=R580E LPC/eSPI Controller +pci:v00008086d000043A0* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family P2SB + +pci:v00008086d000043A1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PMC + pci:v00008086d000043A3* - ID_MODEL_FROM_DATABASE=Tiger Lake-H SMBus Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SMBus pci:v00008086d000043A4* - ID_MODEL_FROM_DATABASE=Tiger Lake-H SPI Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SPI (flash) Controller + +pci:v00008086d000043A6* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family Trace Hub + +pci:v00008086d000043A7* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family UART #2 + +pci:v00008086d000043A8* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family UART #0 + +pci:v00008086d000043A9* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family UART #1 + +pci:v00008086d000043AA* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family GSPI #0 + +pci:v00008086d000043AB* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family GSPI #1 + +pci:v00008086d000043AD* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family I2C #4 + +pci:v00008086d000043AE* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family I2C #5 pci:v00008086d000043B0* - ID_MODEL_FROM_DATABASE=Tiger Lake-H PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #9 + +pci:v00008086d000043B1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #10 + +pci:v00008086d000043B2* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #11 + +pci:v00008086d000043B3* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #12 + +pci:v00008086d000043B4* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #13 + +pci:v00008086d000043B5* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #14 + +pci:v00008086d000043B6* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #15 + +pci:v00008086d000043B7* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #16 pci:v00008086d000043B8* - ID_MODEL_FROM_DATABASE=Tiger Lake-H PCIe Root Port #1 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #1 + +pci:v00008086d000043B9* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #2 pci:v00008086d000043BA* - ID_MODEL_FROM_DATABASE=Tiger Lake-H PCIe Root Port #3 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #3 pci:v00008086d000043BB* - ID_MODEL_FROM_DATABASE=Tiger Lake-H PCIe Root Port #4 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #4 pci:v00008086d000043BC* - ID_MODEL_FROM_DATABASE=Tiger Lake-H PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #5 + +pci:v00008086d000043BD* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #6 pci:v00008086d000043BE* - ID_MODEL_FROM_DATABASE=11th Gen Core Processor PCIe Root Port #7 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #7 + +pci:v00008086d000043BF* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #8 pci:v00008086d000043C0* - ID_MODEL_FROM_DATABASE=Tiger Lake-H PCIe Root Port #17 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #17 + +pci:v00008086d000043C1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #18 + +pci:v00008086d000043C2* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #19 + +pci:v00008086d000043C3* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #20 + +pci:v00008086d000043C4* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #21 + +pci:v00008086d000043C5* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #22 + +pci:v00008086d000043C6* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #23 pci:v00008086d000043C7* - ID_MODEL_FROM_DATABASE=Tiger Lake-H PCIe Root Port #24 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family PCIe Root Port #24 pci:v00008086d000043C8* - ID_MODEL_FROM_DATABASE=Tiger Lake-H HD Audio Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043C9* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043CA* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043CB* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043CC* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043CD* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043CE* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043CF* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family HD Audio + +pci:v00008086d000043D0* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family Touch Host Controller (THC) #0 + +pci:v00008086d000043D1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family Touch Host Controller (THC) #1 + +pci:v00008086d000043D2* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SATA Controller (AHCI) (Server/Desktop) pci:v00008086d000043D3* - ID_MODEL_FROM_DATABASE=Tiger Lake SATA AHCI Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SATA Controller (AHCI) (Mobile) + +pci:v00008086d000043D4* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SATA Controller (RAID 0/1/5/10) no premium (Desktop) + +pci:v00008086d000043D5* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SATA Controller (RAID 0/1/5/10) no premium (Mobile) + +pci:v00008086d000043D6* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium (Server/Desktop) + +pci:v00008086d000043D7* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium (Mobile) + +pci:v00008086d000043D8* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family I2C #6 + +pci:v00008086d000043DA* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family UART #3 pci:v00008086d000043E0* - ID_MODEL_FROM_DATABASE=Tiger Lake-H Management Engine Interface + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CSME HECI #1 + +pci:v00008086d000043E1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CSME HECI #2 + +pci:v00008086d000043E2* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CSME IDE Redirection (IDE-R) pci:v00008086d000043E3* - ID_MODEL_FROM_DATABASE=Tiger Lake AMT SOL Redirection + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CSME Keyboard and Text (KT) Redirection + +pci:v00008086d000043E4* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CSME HECI #3 + +pci:v00008086d000043E5* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CSME HECI #4 pci:v00008086d000043E8* - ID_MODEL_FROM_DATABASE=Tiger Lake-H Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family I2C #0 pci:v00008086d000043E9* - ID_MODEL_FROM_DATABASE=Tiger Lake-H Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family I2C #1 + +pci:v00008086d000043EA* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family I2C #2 + +pci:v00008086d000043EB* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family I2C #3 pci:v00008086d000043ED* - ID_MODEL_FROM_DATABASE=Tiger Lake-H USB 3.2 Gen 2x1 xHCI Host Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family USB 3.2 Gen 2x2 (20 Gbs) xHCI Host Controller + +pci:v00008086d000043EE* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) pci:v00008086d000043EF* - ID_MODEL_FROM_DATABASE=Tiger Lake-H Shared SRAM + ID_MODEL_FROM_DATABASE=500 Series Chipset Family Shared SRAM pci:v00008086d000043F0* - ID_MODEL_FROM_DATABASE=Tiger Lake PCH CNVi WiFi + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CNVi Wi-Fi pci:v00008086d000043F0sv00008086sd00000034* - ID_MODEL_FROM_DATABASE=Tiger Lake PCH CNVi WiFi (Wireless-AC 9560) + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CNVi Wi-Fi (Wireless-AC 9560) pci:v00008086d000043F0sv00008086sd00000074* - ID_MODEL_FROM_DATABASE=Tiger Lake PCH CNVi WiFi (Wi-Fi 6 AX201 160MHz) + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CNVi Wi-Fi (Wi-Fi 6 AX201 160MHz) pci:v00008086d000043F0sv00008086sd00000264* - ID_MODEL_FROM_DATABASE=Tiger Lake PCH CNVi WiFi (Wireless-AC 9461) + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CNVi Wi-Fi (Wireless-AC 9461) pci:v00008086d000043F0sv00008086sd000002A4* - ID_MODEL_FROM_DATABASE=Tiger Lake PCH CNVi WiFi (Wireless-AC 9462) + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CNVi Wi-Fi (Wireless-AC 9462) + +pci:v00008086d000043F1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CNVi Wi-Fi + +pci:v00008086d000043F2* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CNVi Wi-Fi + +pci:v00008086d000043F3* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CNVi Wi-Fi + +pci:v00008086d000043F5* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CNVi Bluetooth + +pci:v00008086d000043F6* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CNVi Bluetooth + +pci:v00008086d000043F7* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family CNVi Bluetooth + +pci:v00008086d000043FB* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family GSPI #2 pci:v00008086d000043FC* - ID_MODEL_FROM_DATABASE=Tiger Lake-H Integrated Sensor Hub + ID_MODEL_FROM_DATABASE=500 Series Chipset Family Integrated Sensor Hub + +pci:v00008086d000043FD* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family GSPI #3 pci:v00008086d0000444E* ID_MODEL_FROM_DATABASE=Turbo Memory Controller @@ -112149,10 +114339,10 @@ pci:v00008086d0000550B* ID_MODEL_FROM_DATABASE=Ethernet Connection (18) I219-LM pci:v00008086d0000550C* - ID_MODEL_FROM_DATABASE=Ethernet Connection (19) I219-LM + ID_MODEL_FROM_DATABASE=800 Series Chipset Family GbE Controller (Corporate/vPro) pci:v00008086d0000550D* - ID_MODEL_FROM_DATABASE=Ethernet Connection (19) I219-V + ID_MODEL_FROM_DATABASE=800 Series Chipset Family GbE Controller (Consumer) pci:v00008086d0000550E* ID_MODEL_FROM_DATABASE=Ethernet Connection (20) I219-LM @@ -112298,14 +114488,23 @@ pci:v00008086d00005786* pci:v00008086d00005787* ID_MODEL_FROM_DATABASE=JHL9480 Thunderbolt 5 80/120G USB Controller [Barlow Ridge Hub 80G 2023] +pci:v00008086d00005792* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors ILMI + +pci:v00008086d00005793* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors ACPI + pci:v00008086d00005794* - ID_MODEL_FROM_DATABASE=Granite Rapids SPI Controller + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors SPI pci:v00008086d00005795* - ID_MODEL_FROM_DATABASE=Granite Rapids Chipset LPC Controller + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors eSPI pci:v00008086d00005796* - ID_MODEL_FROM_DATABASE=Granite Rapids SMBus Controller + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors SMBus + +pci:v00008086d00005797* + ID_MODEL_FROM_DATABASE=Xeon 6900 6700 6500 Series with P-Cores Processors UART pci:v00008086d0000579C* ID_MODEL_FROM_DATABASE=Ethernet Connection E825-C for backplane @@ -112331,8 +114530,11 @@ pci:v00008086d000057A4* pci:v00008086d000057A5* ID_MODEL_FROM_DATABASE=JHL9440 Thunderbolt 4 USB Controller [Barlow Ridge Hub 40G 2023] +pci:v00008086d000057AC* + ID_MODEL_FROM_DATABASE=Ethernet Controller E610 + pci:v00008086d000057AD* - ID_MODEL_FROM_DATABASE=E610 Virtual Function + ID_MODEL_FROM_DATABASE=Ethernet Controller E610 Virtual Function pci:v00008086d000057AE* ID_MODEL_FROM_DATABASE=Ethernet Controller E610 Backplane @@ -112341,31 +114543,31 @@ pci:v00008086d000057AF* ID_MODEL_FROM_DATABASE=Ethernet Controller E610 SFP pci:v00008086d000057B0* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 10GBASE T + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-XT/E610-XT2 10GBASE-T pci:v00008086d000057B0sv00008086sd00000001* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 10GBASE T (Ethernet Network Adapter E610-XT4) + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-XT/E610-XT2 10GBASE-T (Ethernet Network Adapter E610-XT4) pci:v00008086d000057B0sv00008086sd00000002* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 10GBASE T (Ethernet Network Adapter E610-XT2) + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-XT/E610-XT2 10GBASE-T (Ethernet Network Adapter E610-XT2) pci:v00008086d000057B0sv00008086sd00000003* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 10GBASE T (Ethernet Network Adapter E610-XT4 for OCP 3.0) + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-XT/E610-XT2 10GBASE-T (Ethernet Network Adapter E610-XT4 for OCP 3.0) pci:v00008086d000057B0sv00008086sd00000004* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 10GBASE T (Ethernet Network Adapter E610-XT2 for OCP 3.0) + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-XT/E610-XT2 10GBASE-T (Ethernet Network Adapter E610-XT2 for OCP 3.0) pci:v00008086d000057B1* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 2.5GBASE T + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-AT2 1000BASE-T pci:v00008086d000057B1sv00008086sd00000000* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 2.5GBASE T (Ethernet Converged Network Adapter E610) + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-AT2 1000BASE-T (Ethernet Converged Network Adapter E610) pci:v00008086d000057B1sv00008086sd00000002* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 2.5GBASE T (Ethernet Network Adapter E610-IT4) + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-AT2 1000BASE-T (Ethernet Network Adapter E610-IT4) pci:v00008086d000057B1sv00008086sd00000003* - ID_MODEL_FROM_DATABASE=Ethernet Controller E610 2.5GBASE T (Ethernet Network Adapter E610-IT4 for OCP 3.0) + ID_MODEL_FROM_DATABASE=Ethernet Controller E610-AT2 1000BASE-T (Ethernet Network Adapter E610-IT4 for OCP 3.0) pci:v00008086d000057B2* ID_MODEL_FROM_DATABASE=Ethernet Controller E610 SGMII @@ -112634,23 +114836,26 @@ pci:v00008086d00005AEE* pci:v00008086d00005AF0* ID_MODEL_FROM_DATABASE=Celeron N3350/Pentium N4200/Atom E3900 Series Host Bridge +pci:v00008086d00006400* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors with 4 P-Cores 4 E-Cores Host Bridge + pci:v00008086d0000641D* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Dynamic Tuning Technology + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors Dynamic Tuning Technology (DTT) pci:v00008086d00006420* ID_MODEL_FROM_DATABASE=Lunar Lake [Intel Graphics] pci:v00008086d0000643E* - ID_MODEL_FROM_DATABASE=Lunar Lake NPU + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors NPU pci:v00008086d0000645D* - ID_MODEL_FROM_DATABASE=Lunar Lake IPU + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors IPU pci:v00008086d0000647D* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Crashlog and Telemetry + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors Crash Log and Telemetry pci:v00008086d000064A0* - ID_MODEL_FROM_DATABASE=Lunar Lake [Intel Arc Graphics 130V / 140V] + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors Arc Graphics 130V/140V GPU pci:v00008086d000064B0* ID_MODEL_FROM_DATABASE=Lunar Lake [Intel Graphics] @@ -112718,6 +114923,18 @@ pci:v00008086d000065FF* pci:v00008086d0000674C* ID_MODEL_FROM_DATABASE=CRI +pci:v00008086d0000674D* + ID_MODEL_FROM_DATABASE=CRI + +pci:v00008086d0000674E* + ID_MODEL_FROM_DATABASE=CRI + +pci:v00008086d0000674F* + ID_MODEL_FROM_DATABASE=CRI + +pci:v00008086d00006750* + ID_MODEL_FROM_DATABASE=CRI + pci:v00008086d00006E23* ID_MODEL_FROM_DATABASE=Nova Lake PCH-S SMbus Controller @@ -113522,6 +115739,12 @@ pci:v00008086d000071A2* pci:v00008086d000071A2sv00004C53sd00001000* ID_MODEL_FROM_DATABASE=440GX - 82443GX Host bridge (AGP disabled) (CC7/CR7/CP7/VC7/VP7/VR7 mainboard) +pci:v00008086d00007202* + ID_MODEL_FROM_DATABASE=Core Ultra 200H Series Processors eSPI Controller + +pci:v00008086d00007203* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors eSPI Controller + pci:v00008086d00007360* ID_MODEL_FROM_DATABASE=XMM7360 LTE Advanced Modem @@ -113543,98 +115766,152 @@ pci:v00008086d00007603* pci:v00008086d00007702* ID_MODEL_FROM_DATABASE=Arrow Lake-H eSPI Controller +pci:v00008086d00007720* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors P2SB (SOC) + +pci:v00008086d00007721* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors PMC (SOC) + pci:v00008086d00007722* - ID_MODEL_FROM_DATABASE=Arrow Lake SMBus Controller + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors SMBus pci:v00008086d00007723* - ID_MODEL_FROM_DATABASE=Arrow Lake SPI Controller + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors SPI (flash) Controller + +pci:v00008086d00007724* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors Trace Hub pci:v00008086d00007725* - ID_MODEL_FROM_DATABASE=Arrow Lake-H [PCH Serial IO UART Host Controller] + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors UART #0 pci:v00008086d00007726* - ID_MODEL_FROM_DATABASE=Arrow Lake-H PCH Serial IO UART Host Controller] + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors UART #1 pci:v00008086d00007727* - ID_MODEL_FROM_DATABASE=Arrow Lake-H [LPC/eSPI Controller] + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors GSPI #0 pci:v00008086d00007728* - ID_MODEL_FROM_DATABASE=Arrow Lake cAVS + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors HD Audio pci:v00008086d00007730* ID_MODEL_FROM_DATABASE=Arrow Lake-H [LPC/eSPI Controller] pci:v00008086d00007738* - ID_MODEL_FROM_DATABASE=Arrow Lake-H/U PCIe Root Port #1 + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors PCIe Root Port #1 pci:v00008086d00007739* - ID_MODEL_FROM_DATABASE=Arrow Lake-H/U PCIe Root Port #2 + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors PCIe Root Port #2 pci:v00008086d0000773A* - ID_MODEL_FROM_DATABASE=Arrow Lake-H/U PCIe Root Port #3 + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors PCIe Root Port #3 pci:v00008086d0000773B* - ID_MODEL_FROM_DATABASE=Arrow Lake-H/U PCIe Root Port #4 + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors PCIe Root Port #4 pci:v00008086d0000773C* - ID_MODEL_FROM_DATABASE=Arrow Lake-H/U PCIe Root Port #5 + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors PCIe Root Port #5 pci:v00008086d0000773D* - ID_MODEL_FROM_DATABASE=Arrow Lake-H/U PCIe Root Port #6 + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors PCIe Root Port #6 pci:v00008086d0000773E* - ID_MODEL_FROM_DATABASE=Arrow Lake-H/U PCIe Root Port #7 + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors PCIe Root Port #7 pci:v00008086d0000773F* - ID_MODEL_FROM_DATABASE=Arrow Lake-H/U PCIe Root Port #8 + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors PCIe Root Port #8 pci:v00008086d00007740* ID_MODEL_FROM_DATABASE=Arrow Lake CNVi WiFi pci:v00008086d00007745* - ID_MODEL_FROM_DATABASE=Arrow Lake Integrated Sensor Hub + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors Integrated Sensor Hub pci:v00008086d00007746* - ID_MODEL_FROM_DATABASE=Arrow Lake-H [LPC/eSPI Controller] + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors GSPI #2 + +pci:v00008086d00007748* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors Touch Host Controller (THC) #0 ID1 + +pci:v00008086d00007749* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors Touch Host Controller (THC) #0 ID2 + +pci:v00008086d0000774A* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors Touch Host Controller (THC) #1 ID1 + +pci:v00008086d0000774B* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors Touch Host Controller (THC) #1 ID2 pci:v00008086d0000774C* - ID_MODEL_FROM_DATABASE=Arrow Lake Gaussian & Neural Accelerator + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors Gaussian & Neural-Network Accelerator (GNA) pci:v00008086d0000774D* - ID_MODEL_FROM_DATABASE=Arrow Lake-H/U PCIe Root Port #9 (PXPC) + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors PCIe Root Port #9 (PXPC) pci:v00008086d00007750* - ID_MODEL_FROM_DATABASE=Arrow Lake-H [Serial IO I2C Host Controller] + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors I2C #4 pci:v00008086d00007751* - ID_MODEL_FROM_DATABASE=Arrow Lake-H [Serial IO I2C Host Controller] + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors I2C #5 pci:v00008086d00007752* - ID_MODEL_FROM_DATABASE=Arrow Lake-H [PCH Serial IO UART Host Controller] + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors UART #2 + +pci:v00008086d00007758* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors CSME HECI #1 + +pci:v00008086d00007759* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors CSME HECI #2 + +pci:v00008086d0000775A* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors CSME HECI #3 + +pci:v00008086d00007763* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors SATA Controller (AHCI) + +pci:v00008086d00007767* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors SATA Controller (RAID 0/1/5/10) premium pci:v00008086d00007770* - ID_MODEL_FROM_DATABASE=Arrow Lake HECI Controller #1 + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors CSME HECI #1 + +pci:v00008086d00007771* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors CSME HECI #2 + +pci:v00008086d00007772* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors CSME IDE Redirection (IDE-R) pci:v00008086d00007773* - ID_MODEL_FROM_DATABASE=Arrow Lake Keyboard and Text (KT) Redirection + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors CSME Keyboard and Text (KT) Redirection + +pci:v00008086d00007774* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors CSME HECI #3 + +pci:v00008086d00007775* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors CSME HECI #4 pci:v00008086d00007778* - ID_MODEL_FROM_DATABASE=Arrow Lake-H [Serial IO I2C Host Controller] + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors I2C #0 pci:v00008086d00007779* - ID_MODEL_FROM_DATABASE=Arrow Lake-H [Serial IO I2C Host Controller] + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors I2C #1 pci:v00008086d0000777A* - ID_MODEL_FROM_DATABASE=Arrow Lake-H [Serial IO I2C Host Controller] + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors I2C #2 pci:v00008086d0000777B* - ID_MODEL_FROM_DATABASE=Arrow Lake-H [Serial IO I2C Host Controller] + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors I2C #3 + +pci:v00008086d0000777C* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors I3C pci:v00008086d0000777D* - ID_MODEL_FROM_DATABASE=Arrow Lake USB 3.2 xHCI Controller + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors Standalone xHCI Controller + +pci:v00008086d0000777E* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors Standalone USB Device Controller pci:v00008086d0000777F* - ID_MODEL_FROM_DATABASE=Arrow Lake Shared SRAM + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors Shared SRAM pci:v00008086d00007800* ID_MODEL_FROM_DATABASE=82740 (i740) AGP Graphics Accelerator @@ -113661,349 +115938,535 @@ pci:v00008086d00007800sv00008086sd00000100* ID_MODEL_FROM_DATABASE=82740 (i740) AGP Graphics Accelerator (Intel740 Graphics Accelerator) pci:v00008086d00007A04* - ID_MODEL_FROM_DATABASE=Z790 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Z790 Chipset eSPI Controller pci:v00008086d00007A05* - ID_MODEL_FROM_DATABASE=H770 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=H770 Chipset eSPI Controller pci:v00008086d00007A06* - ID_MODEL_FROM_DATABASE=B760 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=B760 Chipset eSPI Controller pci:v00008086d00007A0C* - ID_MODEL_FROM_DATABASE=HM770 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=HM770 Chipset eSPI Controller pci:v00008086d00007A0D* ID_MODEL_FROM_DATABASE=WM790 Chipset LPC/eSPI Controller pci:v00008086d00007A13* - ID_MODEL_FROM_DATABASE=C266 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C266 Chipset eSPI Controller pci:v00008086d00007A14* - ID_MODEL_FROM_DATABASE=C262 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C262 Chipset eSPI Controller pci:v00008086d00007A20* - ID_MODEL_FROM_DATABASE=700 Series Chipset P2SB + ID_MODEL_FROM_DATABASE=700 Series Chipset Family P2SB pci:v00008086d00007A21* - ID_MODEL_FROM_DATABASE=700 Series Chipset Power Management Controller + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PMC pci:v00008086d00007A23* - ID_MODEL_FROM_DATABASE=700 Series Chipset SMBus Controller + ID_MODEL_FROM_DATABASE=700 Series Chipset Family SMBus pci:v00008086d00007A24* - ID_MODEL_FROM_DATABASE=Raptor Lake SPI (flash) Controller + ID_MODEL_FROM_DATABASE=700 Series Chipset Family SPI (flash) Controller + +pci:v00008086d00007A26* + ID_MODEL_FROM_DATABASE=700 Series Chipset Family Trace Hub pci:v00008086d00007A27* - ID_MODEL_FROM_DATABASE=Raptor Lake PCH Shared SRAM + ID_MODEL_FROM_DATABASE=700 Series Chipset Family Shared SRAM pci:v00008086d00007A28* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO UART Controller #0 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family UART #0 pci:v00008086d00007A29* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO UART Controller #1 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family UART #1 pci:v00008086d00007A2A* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO GSPI Controller #0 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family GSPI #0 pci:v00008086d00007A2B* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO GSPI Controller #1 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family GSPI #1 pci:v00008086d00007A30* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #9 pci:v00008086d00007A31* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #10 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #10 pci:v00008086d00007A32* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #11 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #11 pci:v00008086d00007A33* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #12 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #12 pci:v00008086d00007A34* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #13 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #13 pci:v00008086d00007A35* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #14 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #14 pci:v00008086d00007A36* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #15 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #15 pci:v00008086d00007A37* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #16 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #16 pci:v00008086d00007A38* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #1 pci:v00008086d00007A39* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #2 pci:v00008086d00007A3A* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #3 pci:v00008086d00007A3B* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #4 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #4 pci:v00008086d00007A3C* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #5 pci:v00008086d00007A3D* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #6 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #6 pci:v00008086d00007A3E* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #7 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #7 pci:v00008086d00007A3F* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #8 pci:v00008086d00007A40* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #17 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #17 pci:v00008086d00007A41* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #18 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #18 pci:v00008086d00007A42* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #19 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #19 pci:v00008086d00007A43* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #20 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #20 pci:v00008086d00007A44* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #21 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #21 pci:v00008086d00007A45* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #22 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #22 pci:v00008086d00007A46* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #23 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #23 pci:v00008086d00007A47* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #24 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #24 pci:v00008086d00007A48* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #25 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #25 pci:v00008086d00007A49* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #26 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #26 pci:v00008086d00007A4A* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #27 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #27 pci:v00008086d00007A4B* - ID_MODEL_FROM_DATABASE=Raptor Lake PCI Express Root Port #28 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family PCIe Root Port #28 pci:v00008086d00007A4C* - ID_MODEL_FROM_DATABASE=Raptor Lake Serial IO I2C Host Controller #0 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family I2C Controller #0 pci:v00008086d00007A4D* - ID_MODEL_FROM_DATABASE=Raptor Lake Serial IO I2C Host Controller #1 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family I2C Controller #1 pci:v00008086d00007A4E* - ID_MODEL_FROM_DATABASE=Raptor Lake Serial IO I2C Host Controller #2 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family I2C Controller #2 pci:v00008086d00007A4F* - ID_MODEL_FROM_DATABASE=Raptor Lake Serial IO I2C Host Controller #3 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family I2C Controller #3 pci:v00008086d00007A50* - ID_MODEL_FROM_DATABASE=Raptor Lake High Definition Audio Controller + ID_MODEL_FROM_DATABASE=700 Series Chipset Family HD Audio + +pci:v00008086d00007A51* + ID_MODEL_FROM_DATABASE=700 Series Chipset Family HD Audio + +pci:v00008086d00007A52* + ID_MODEL_FROM_DATABASE=700 Series Chipset Family HD Audio + +pci:v00008086d00007A53* + ID_MODEL_FROM_DATABASE=700 Series Chipset Family HD Audio + +pci:v00008086d00007A54* + ID_MODEL_FROM_DATABASE=700 Series Chipset Family HD Audio + +pci:v00008086d00007A55* + ID_MODEL_FROM_DATABASE=700 Series Chipset Family HD Audio + +pci:v00008086d00007A56* + ID_MODEL_FROM_DATABASE=700 Series Chipset Family HD Audio + +pci:v00008086d00007A57* + ID_MODEL_FROM_DATABASE=700 Series Chipset Family HD Audio pci:v00008086d00007A5C* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO UART Controller #3 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family UART #3 pci:v00008086d00007A60* - ID_MODEL_FROM_DATABASE=Raptor Lake USB 3.2 Gen 2x2 (20 Gb/s) XHCI Host Controller + ID_MODEL_FROM_DATABASE=700 Series Chipset Family USB 3.2 Gen 2x2 (20 Gbs) xHCI Host Controller pci:v00008086d00007A61* - ID_MODEL_FROM_DATABASE=Raptor Lake USB 3.2 Gen 1x1 (5 Gb/s) xDCI Device Controller + ID_MODEL_FROM_DATABASE=700 Series Chipset Family USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) pci:v00008086d00007A62* - ID_MODEL_FROM_DATABASE=Raptor Lake SATA AHCI Controller + ID_MODEL_FROM_DATABASE=700 Series Chipset Family SATA Controller (AHCI) pci:v00008086d00007A68* - ID_MODEL_FROM_DATABASE=Raptor Lake CSME HECI #1 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CSME HECI #1 pci:v00008086d00007A69* - ID_MODEL_FROM_DATABASE=Raptor Lake CSME HECI #2 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CSME HECI #2 pci:v00008086d00007A6A* - ID_MODEL_FROM_DATABASE=Raptor Lake CSME IDE Redirection + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CSME IDE Redirection (IDE-R) pci:v00008086d00007A6B* - ID_MODEL_FROM_DATABASE=Raptor Lake CSME Keyboard and Text (KT) Redirection + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CSME Keyboard and Text (KT) Redirection pci:v00008086d00007A6C* - ID_MODEL_FROM_DATABASE=Raptor Lake CSME HECI #3 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CSME HECI #3 pci:v00008086d00007A6D* - ID_MODEL_FROM_DATABASE=Raptor Lake CSME HECI #4 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CSME HECI #4 pci:v00008086d00007A70* - ID_MODEL_FROM_DATABASE=700 Series Chipset CNVi WiFi + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CNVi Wi-Fi pci:v00008086d00007A70sv00008086sd00000074* - ID_MODEL_FROM_DATABASE=700 Series Chipset CNVi WiFi (Wi-Fi 6 AX201 160MHz) + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CNVi Wi-Fi (Wi-Fi 6 AX201 160MHz) pci:v00008086d00007A70sv00008086sd00000090* - ID_MODEL_FROM_DATABASE=700 Series Chipset CNVi WiFi (WiFi 6E AX211 160MHz) + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CNVi Wi-Fi (WiFi 6E AX211 160MHz) + +pci:v00008086d00007A71* + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CNVi Wi-Fi + +pci:v00008086d00007A72* + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CNVi Wi-Fi + +pci:v00008086d00007A73* + ID_MODEL_FROM_DATABASE=700 Series Chipset Family CNVi Wi-Fi + +pci:v00008086d00007A78* + ID_MODEL_FROM_DATABASE=700 Series Chipset Family Integrated Sensor Hub pci:v00008086d00007A79* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO GSPI Controller #3 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family GSPI #3 pci:v00008086d00007A7B* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO GSPI Controller #2 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family GSPI #2 pci:v00008086d00007A7C* - ID_MODEL_FROM_DATABASE=Raptor Lake Serial IO I2C Host Controller #4 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family I2C Controller #4 pci:v00008086d00007A7D* - ID_MODEL_FROM_DATABASE=Raptor Lake Serial IO I2C Host Controller #5 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family I2C Controller #5 pci:v00008086d00007A7E* - ID_MODEL_FROM_DATABASE=700 Series Chipset Serial IO UART Controller #2 + ID_MODEL_FROM_DATABASE=700 Series Chipset Family UART #2 pci:v00008086d00007A83* - ID_MODEL_FROM_DATABASE=Q670 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Q670 Chipset eSPI Controller pci:v00008086d00007A84* - ID_MODEL_FROM_DATABASE=Z690 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Z690 Chipset eSPI Controller pci:v00008086d00007A85* - ID_MODEL_FROM_DATABASE=H670 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=H670 Chipset eSPI Controller pci:v00008086d00007A86* - ID_MODEL_FROM_DATABASE=B660 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=B660 Chipset eSPI Controller pci:v00008086d00007A87* - ID_MODEL_FROM_DATABASE=H610 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=H610 Chipset eSPI Controller pci:v00008086d00007A88* - ID_MODEL_FROM_DATABASE=W680 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=W680 Chipset eSPI Controller + +pci:v00008086d00007A8A* + ID_MODEL_FROM_DATABASE=W790 Chipset eSPI Controller pci:v00008086d00007A8C* - ID_MODEL_FROM_DATABASE=HM670 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=HM670 Chipset eSPI Controller pci:v00008086d00007A8D* - ID_MODEL_FROM_DATABASE=WM690 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=WM690 Chipset eSPI Controller + +pci:v00008086d00007AA0* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family P2SB + +pci:v00008086d00007AA1* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PMC pci:v00008086d00007AA3* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH SMBus Controller + ID_MODEL_FROM_DATABASE=600 Series Chipset Family SMBus pci:v00008086d00007AA4* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH SPI Controller + ID_MODEL_FROM_DATABASE=600 Series Chipset Family SPI (flash) Controller + +pci:v00008086d00007AA6* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family Trace Hub pci:v00008086d00007AA7* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Shared SRAM + ID_MODEL_FROM_DATABASE=600 Series Chipset Family Shared SRAM pci:v00008086d00007AA8* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO UART #0 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family UART #0 + +pci:v00008086d00007AA9* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family UART #1 + +pci:v00008086d00007AAA* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family GSPI #0 pci:v00008086d00007AAB* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO SPI Controller #1 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family GSPI #1 pci:v00008086d00007AB0* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #9 + +pci:v00008086d00007AB1* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #10 + +pci:v00008086d00007AB2* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #11 + +pci:v00008086d00007AB3* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #12 pci:v00008086d00007AB4* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #13 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #13 + +pci:v00008086d00007AB5* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #14 + +pci:v00008086d00007AB6* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #15 + +pci:v00008086d00007AB7* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #16 pci:v00008086d00007AB8* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #1 pci:v00008086d00007AB9* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #2 pci:v00008086d00007ABA* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #3 + +pci:v00008086d00007ABB* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #4 pci:v00008086d00007ABC* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #5 pci:v00008086d00007ABD* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #6 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #6 + +pci:v00008086d00007ABE* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #7 pci:v00008086d00007ABF* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #8 + +pci:v00008086d00007AC0* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #17 + +pci:v00008086d00007AC1* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #18 + +pci:v00008086d00007AC2* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #19 + +pci:v00008086d00007AC3* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #20 pci:v00008086d00007AC4* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #21 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #21 + +pci:v00008086d00007AC5* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #22 + +pci:v00008086d00007AC6* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #23 + +pci:v00008086d00007AC7* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #24 pci:v00008086d00007AC8* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH PCI Express Root Port #25 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #25 + +pci:v00008086d00007AC9* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #26 + +pci:v00008086d00007ACA* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #27 + +pci:v00008086d00007ACB* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family PCIe Root Port #28 pci:v00008086d00007ACC* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family I2C Controller #0 pci:v00008086d00007ACD* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family I2C Controller #1 pci:v00008086d00007ACE* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO I2C Controller #2 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family I2C Controller #2 pci:v00008086d00007ACF* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO I2C Controller #3 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family 12C Controller #3 pci:v00008086d00007AD0* - ID_MODEL_FROM_DATABASE=Alder Lake-S HD Audio Controller + ID_MODEL_FROM_DATABASE=600 Series Chipset Family HD Audio + +pci:v00008086d00007AD1* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family HD Audio + +pci:v00008086d00007AD2* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family HD Audio + +pci:v00008086d00007AD3* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family HD Audio + +pci:v00008086d00007AD4* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family HD Audio + +pci:v00008086d00007AD5* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family HD Audio + +pci:v00008086d00007AD6* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family HD Audio + +pci:v00008086d00007AD7* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family HD Audio + +pci:v00008086d00007ADC* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family UART #3 pci:v00008086d00007AE0* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH USB 3.2 Gen 2x2 XHCI Controller + ID_MODEL_FROM_DATABASE=600 Series Chipset Family USB 3.2 Gen 2x2 (20Gbs) XHCI Host Controller pci:v00008086d00007AE1* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH USB 3.2 Gen 1x1 xDCI Controller + ID_MODEL_FROM_DATABASE=600 Series Chipset Family USB 3.2 Gen 1x1 (5Gbs) Device Controller (xDCI) pci:v00008086d00007AE2* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH SATA Controller [AHCI Mode] + ID_MODEL_FROM_DATABASE=600 Series Chipset Family SATA Controller (AHCI) pci:v00008086d00007AE8* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH HECI Controller #1 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CSME HECI #1 + +pci:v00008086d00007AE9* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CSME HECI #2 + +pci:v00008086d00007AEA* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CSME IDE Redirection (IDE-R) pci:v00008086d00007AEB* - ID_MODEL_FROM_DATABASE=Alder Lake-S Keyboard and Text (KT) Redirection + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CSME Keyboard and Text (KT) Redirection + +pci:v00008086d00007AEC* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CSME HECI #3 + +pci:v00008086d00007AED* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CSME HECI #4 pci:v00008086d00007AF0* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH CNVi WiFi + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CNVi Wi-Fi pci:v00008086d00007AF0sv00008086sd00000034* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH CNVi WiFi (Wireless-AC 9560) + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CNVi Wi-Fi (Wireless-AC 9560) pci:v00008086d00007AF0sv00008086sd00000070* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH CNVi WiFi (Wi-Fi 6 AX201 160MHz) + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CNVi Wi-Fi (Wi-Fi 6 AX201 160MHz) pci:v00008086d00007AF0sv00008086sd00000094* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH CNVi WiFi (Wi-Fi 6 AX201 160MHz) + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CNVi Wi-Fi (Wi-Fi 6 AX201 160MHz) + +pci:v00008086d00007AF1* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CNVi Wi-Fi + +pci:v00008086d00007AF2* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CNVi Wi-Fi + +pci:v00008086d00007AF3* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family CNVi Wi-Fi pci:v00008086d00007AF8* - ID_MODEL_FROM_DATABASE=Alder Lake-S Integrated Sensor Hub + ID_MODEL_FROM_DATABASE=600 Series Chipset Family Integrated Sensor Hub + +pci:v00008086d00007AF9* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family GSPI #3 + +pci:v00008086d00007AFB* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family GSPI #2 pci:v00008086d00007AFC* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO I2C Controller #4 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family I2C Controller #4 pci:v00008086d00007AFD* - ID_MODEL_FROM_DATABASE=Alder Lake-S PCH Serial IO I2C Controller #5 + ID_MODEL_FROM_DATABASE=600 Series Chipset Family I2C Controller #5 + +pci:v00008086d00007AFE* + ID_MODEL_FROM_DATABASE=600 Series Chipset Family UART #2 pci:v00008086d00007D01* ID_MODEL_FROM_DATABASE=Meteor Lake-H 6p+8e cores Host Bridge/DRAM Controller pci:v00008086d00007D03* - ID_MODEL_FROM_DATABASE=Meteor Lake-P Dynamic Tuning Technology + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors Dynamic Tuning Technology (DTT) pci:v00008086d00007D06* - ID_MODEL_FROM_DATABASE=Arrow Lake-H 6p+8e cores Host Bridge/DRAM Controller + ID_MODEL_FROM_DATABASE=Core Ultra 200H Series Processors with 6 P-Cores 8 E-Cores Host Bridge pci:v00008086d00007D0B* - ID_MODEL_FROM_DATABASE=Volume Management Device NVMe RAID Controller Intel Corporation + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors VMD pci:v00008086d00007D0D* - ID_MODEL_FROM_DATABASE=Meteor Lake-P Platform Monitoring Technology + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors Platform Monitoring Technology (PMT) pci:v00008086d00007D19* - ID_MODEL_FROM_DATABASE=Meteor Lake IPU + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors IPU + +pci:v00008086d00007D1A* + ID_MODEL_FROM_DATABASE=Core Ultra 200S/200S-Plus Series Processors with 8 P-Cores 16 E-Cores Host Bridge + +pci:v00008086d00007D1B* + ID_MODEL_FROM_DATABASE=Core Ultra 200S Series Processors with 8 P-Cores 12 E-Cores Host Bridge pci:v00008086d00007D1C* - ID_MODEL_FROM_DATABASE=Arrow Lake-HX 8p+16e cores Host Bridge + ID_MODEL_FROM_DATABASE=Core Ultra 200HX/HX-Plus Series Processors with 8 P-Cores 16 E-Cores Host Bridge pci:v00008086d00007D1D* - ID_MODEL_FROM_DATABASE=Meteor Lake NPU + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors NPU + +pci:v00008086d00007D29* + ID_MODEL_FROM_DATABASE=Core Ultra 200S-Plus Series Processors with 6 P-Cores 12 E-Cores Host Bridge + +pci:v00008086d00007D2A* + ID_MODEL_FROM_DATABASE=Core Ultra 200S Series Processors with 6 P-Cores 8 E-Cores Host Bridge + +pci:v00008086d00007D2D* + ID_MODEL_FROM_DATABASE=Core Ultra 200HX/HX-Plus Series Processors with 8 P-Cores 12 E-Cores Host Bridge + +pci:v00008086d00007D2F* + ID_MODEL_FROM_DATABASE=Core Ultra 200HX Series Processors with 6 P-Cores 8 E-Cores Host Bridge + +pci:v00008086d00007D30* + ID_MODEL_FROM_DATABASE=Core Ultra 200U Series Processors with 2 P-Cores 8 E-Cores Host Bridge + +pci:v00008086d00007D35* + ID_MODEL_FROM_DATABASE=Core Ultra 200S Series Processors with 6 P-Cores 4 E-Cores Host Bridge pci:v00008086d00007D40* ID_MODEL_FROM_DATABASE=Meteor Lake-M [Intel Graphics] @@ -114120,118 +116583,268 @@ pci:v00008086d00007E7F* ID_MODEL_FROM_DATABASE=Meteor Lake-H/U Shared SRAM pci:v00008086d00007EC0* - ID_MODEL_FROM_DATABASE=Meteor Lake-P Thunderbolt 4 USB Controller + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors USB xHCI + +pci:v00008086d00007EC1* + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors USB xDCI pci:v00008086d00007EC2* - ID_MODEL_FROM_DATABASE=Meteor Lake-P Thunderbolt 4 NHI #0 + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors Thunderbolt DMA0 pci:v00008086d00007EC3* - ID_MODEL_FROM_DATABASE=Meteor Lake-P Thunderbolt 4 NHI #1 + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors Thunderbolt DMA1 pci:v00008086d00007EC4* - ID_MODEL_FROM_DATABASE=Meteor Lake-P Thunderbolt 4 PCI Express Root Port #0 + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors USB Type-C Subsystem PCIe Root Port #16 pci:v00008086d00007EC5* - ID_MODEL_FROM_DATABASE=Meteor Lake-P Thunderbolt 4 PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors USB Type-C Subsystem PCIe Root Port #17 pci:v00008086d00007EC6* - ID_MODEL_FROM_DATABASE=Meteor Lake-P Thunderbolt 4 PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors USB Type-C Subsystem PCIe Root Port #18 pci:v00008086d00007EC7* - ID_MODEL_FROM_DATABASE=Meteor Lake-P Thunderbolt 4 PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors USB Type-C Subsystem PCIe Root Port #19 + +pci:v00008086d00007EC8* + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors P2SB (IOE) + +pci:v00008086d00007EC9* + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors IEH (IOE) pci:v00008086d00007ECA* - ID_MODEL_FROM_DATABASE=Meteor Lake-H/U PCIe Root Port #10 + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors PCIe Root Port #10 pci:v00008086d00007ECB* - ID_MODEL_FROM_DATABASE=Arrow Lake-H/U PCIe Root Port #11 (PXPE) + ID_MODEL_FROM_DATABASE=Core Ultra 200H/200V Series Processors PCIe Root Port #11 (PXPE) pci:v00008086d00007ECC* - ID_MODEL_FROM_DATABASE=Meteor Lake-H PCIe Root Port #12 + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors PCIe Root Port #12 + +pci:v00008086d00007ECE* + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors PMC (IOE) + +pci:v00008086d00007ECF* + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors Shared SRAM (IOE) pci:v00008086d00007F03* - ID_MODEL_FROM_DATABASE=Q870 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Q870 Chipset eSPI Controller pci:v00008086d00007F04* - ID_MODEL_FROM_DATABASE=Z890 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Z890 Chipset eSPI Controller pci:v00008086d00007F06* - ID_MODEL_FROM_DATABASE=B860 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=B860 Chipset eSPI Controller pci:v00008086d00007F07* - ID_MODEL_FROM_DATABASE=H810 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=H810 Chipset eSPI Controller pci:v00008086d00007F08* - ID_MODEL_FROM_DATABASE=W880 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=W880 Chipset eSPI Controller pci:v00008086d00007F0C* - ID_MODEL_FROM_DATABASE=HM870 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=HM870 Chipset eSPI Controller pci:v00008086d00007F0D* - ID_MODEL_FROM_DATABASE=WM880 Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=WM880 Chipset eSPI Controller pci:v00008086d00007F20* - ID_MODEL_FROM_DATABASE=800 Series PCH P2SB + ID_MODEL_FROM_DATABASE=800 Series Chipset Family P2SB pci:v00008086d00007F21* - ID_MODEL_FROM_DATABASE=800 Series PCH Power Management Controller + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PMC pci:v00008086d00007F23* - ID_MODEL_FROM_DATABASE=800 Series PCH SMBus Controller + ID_MODEL_FROM_DATABASE=800 Series Chipset Family SMBus pci:v00008086d00007F24* - ID_MODEL_FROM_DATABASE=800 Series PCH SPI (flash) Controller + ID_MODEL_FROM_DATABASE=800 Series Chipset Family SPI (flash) Controller + +pci:v00008086d00007F26* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Trace Hub pci:v00008086d00007F27* - ID_MODEL_FROM_DATABASE=800 Series PCH Shared SRAM + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Shared SRAM + +pci:v00008086d00007F28* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family UART #0 + +pci:v00008086d00007F29* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family UART #1 + +pci:v00008086d00007F2A* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family GSPI #0 + +pci:v00008086d00007F2B* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family GSPI #1 pci:v00008086d00007F30* - ID_MODEL_FROM_DATABASE=800 Series PCH PCIe Root Port #9 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #9 + +pci:v00008086d00007F31* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #10 + +pci:v00008086d00007F32* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #11 + +pci:v00008086d00007F33* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #12 pci:v00008086d00007F34* - ID_MODEL_FROM_DATABASE=800 Series PCH PCIe Root Port #13 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #13 + +pci:v00008086d00007F35* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #14 pci:v00008086d00007F36* - ID_MODEL_FROM_DATABASE=800 Series PCH PCIe Root Port #15 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #15 + +pci:v00008086d00007F37* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #16 pci:v00008086d00007F38* - ID_MODEL_FROM_DATABASE=800 Series PCH PCIe Root Port #1 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #1 + +pci:v00008086d00007F39* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #2 + +pci:v00008086d00007F3A* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #3 + +pci:v00008086d00007F3B* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #4 + +pci:v00008086d00007F3C* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #5 + +pci:v00008086d00007F3D* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #6 pci:v00008086d00007F3E* - ID_MODEL_FROM_DATABASE=800 Series PCH PCIe Root Port #7 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #7 + +pci:v00008086d00007F3F* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #8 pci:v00008086d00007F40* - ID_MODEL_FROM_DATABASE=800 Series PCH PCIe Root Port #17 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #17 + +pci:v00008086d00007F41* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #18 + +pci:v00008086d00007F42* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #19 + +pci:v00008086d00007F43* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #20 pci:v00008086d00007F44* - ID_MODEL_FROM_DATABASE=800 Series PCH PCIe Root Port #21 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #21 + +pci:v00008086d00007F45* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #22 + +pci:v00008086d00007F46* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #23 + +pci:v00008086d00007F47* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family PCIe Root Port #24 pci:v00008086d00007F4C* - ID_MODEL_FROM_DATABASE=800 Series PCH I2C Controller #0 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family I2C #0 + +pci:v00008086d00007F4D* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family I2C #1 + +pci:v00008086d00007F4E* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family I2C #2 pci:v00008086d00007F4F* - ID_MODEL_FROM_DATABASE=800 Series PCH I2C Controller #3 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family I2C #3 pci:v00008086d00007F50* - ID_MODEL_FROM_DATABASE=800 Series ACE (Audio Context Engine) + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Audio Context Engine (ACE) + +pci:v00008086d00007F58* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Touch Host Controller (THC) #0 ID1 + +pci:v00008086d00007F59* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Touch Host Controller (THC) #0 ID2 + +pci:v00008086d00007F5A* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Touch Host Controller (THC) #1 ID1 + +pci:v00008086d00007F5B* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Touch Host Controller (THC) #1 ID2 + +pci:v00008086d00007F5C* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family UART #2 + +pci:v00008086d00007F5D* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family UART #3 + +pci:v00008086d00007F5E* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family GSPI #2 + +pci:v00008086d00007F5F* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family GSPI #3 + +pci:v00008086d00007F62* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family SATA Controller (AHCI) + +pci:v00008086d00007F66* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium pci:v00008086d00007F68* - ID_MODEL_FROM_DATABASE=800 Series PCH HECI #1 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family CSME HECI #1 + +pci:v00008086d00007F69* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family CSME HECI #2 + +pci:v00008086d00007F6A* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family IDE Redirection (IDER-R) + +pci:v00008086d00007F6B* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Keyboard and Text (KT) Redirection + +pci:v00008086d00007F6C* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family CSME HECI #3 + +pci:v00008086d00007F6D* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family CSME HECI #4 pci:v00008086d00007F6E* - ID_MODEL_FROM_DATABASE=800 Series PCH USB 3.1 xHCI HC + ID_MODEL_FROM_DATABASE=800 Series Chipset Family USB 3.1 xHCI HC + +pci:v00008086d00007F6F* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family USB Device Controller (OTG) (xDCI) pci:v00008086d00007F70* - ID_MODEL_FROM_DATABASE=Arrow Lake-S PCH CNVi WiFi + ID_MODEL_FROM_DATABASE=800 Series Chipset Family CNVi Wi-Fi pci:v00008086d00007F70sv00008086sd00000094* - ID_MODEL_FROM_DATABASE=Arrow Lake-S PCH CNVi WiFi (WiFi 6E AX211 160MHz) + ID_MODEL_FROM_DATABASE=800 Series Chipset Family CNVi Wi-Fi (WiFi 6E AX211 160MHz) + +pci:v00008086d00007F78* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Integrated Sensor Hub + +pci:v00008086d00007F79* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family I3C pci:v00008086d00007F7A* - ID_MODEL_FROM_DATABASE=800 Series PCH I2C Controller #4 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family I2C #4 pci:v00008086d00007F7B* - ID_MODEL_FROM_DATABASE=800 Series PCH I2C Controller #5 + ID_MODEL_FROM_DATABASE=800 Series Chipset Family I2C #5 + +pci:v00008086d00007F7C* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Silicon Security Engine HECI #1 + +pci:v00008086d00007F7D* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Silicon Security Engine HECI #2 + +pci:v00008086d00007F7E* + ID_MODEL_FROM_DATABASE=800 Series Chipset Family Silicon Security Engine HECI #3 pci:v00008086d00008002* ID_MODEL_FROM_DATABASE=Trusted Execution Technology Registers @@ -116445,88 +119058,139 @@ pci:v00008086d0000A013* ID_MODEL_FROM_DATABASE=Atom Processor D4xx/D5xx/N4xx/N5xx CHAPS counter pci:v00008086d0000A082* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP LPC Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package eSPI Controller + +pci:v00008086d0000A0A0* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Primary-to-Sideband Bridge (P2SB) + +pci:v00008086d0000A0A1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Power Management Controller (PMC) pci:v00008086d0000A0A3* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP SMBus Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package System Management Bus (SMBus) pci:v00008086d0000A0A4* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP SPI Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package SPI (flash) Controller pci:v00008086d0000A0A6* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Trace Hub + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Trace Hub pci:v00008086d0000A0A8* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO UART Controller #0 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package UART Controller #0 pci:v00008086d0000A0A9* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO UART Controller #1 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package UART Controller #1 + +pci:v00008086d0000A0AA* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Generic SPI (GSPI) #0 pci:v00008086d0000A0AB* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO SPI Controller #1 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Generic SPI (GSPI) #1 pci:v00008086d0000A0B0* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #9 pci:v00008086d0000A0B1* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #10 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #10 + +pci:v00008086d0000A0B2* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #11 pci:v00008086d0000A0B3* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #12 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #12 pci:v00008086d0000A0B8* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #0 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #1 + +pci:v00008086d0000A0B9* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #2 + +pci:v00008086d0000A0BA* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #3 pci:v00008086d0000A0BB* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #4 pci:v00008086d0000A0BC* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #5 pci:v00008086d0000A0BD* - ID_MODEL_FROM_DATABASE=Tigerlake PCH-LP PCI Express Root Port #6 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #6 pci:v00008086d0000A0BE* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #7 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #7 pci:v00008086d0000A0BF* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package PCI Express Root Port #8 pci:v00008086d0000A0C5* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO I2C Controller #4 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package I2C Controller #4 pci:v00008086d0000A0C6* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO I2C Controller #5 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package I2C Controller #5 + +pci:v00008086d0000A0C7* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package UART Controller #2 pci:v00008086d0000A0C8* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Smart Sound Technology Audio Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package High Definition Audio (HD Audio) + +pci:v00008086d0000A0D0* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Touch Host Controller #0 + +pci:v00008086d0000A0D1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Touch Host Controller #1 pci:v00008086d0000A0D3* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP SATA Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package SATA Controller (AHCI) + +pci:v00008086d0000A0D5* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) no premium + +pci:v00008086d0000A0D7* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) premium + +pci:v00008086d0000A0DA* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package UART Controller #3 pci:v00008086d0000A0E0* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Management Engine Interface + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package CSME HECI #1 + +pci:v00008086d0000A0E1* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package CSME HECI #2 + +pci:v00008086d0000A0E2* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package CSME IDE Redirection (IDE-R) pci:v00008086d0000A0E3* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Active Management Technology - SOL + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package CSME Keyboard and Text (KT) Redirection + +pci:v00008086d0000A0E4* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package CSME HECI #3 + +pci:v00008086d0000A0E5* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package CSME HECI #4 pci:v00008086d0000A0E8* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package I2C Controller #0 pci:v00008086d0000A0E9* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package I2C Controller #1 pci:v00008086d0000A0EA* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO I2C Controller #2 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package I2C Controller #2 pci:v00008086d0000A0EB* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Serial IO I2C Controller #3 + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package I2C Controller #3 pci:v00008086d0000A0ED* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP USB 3.2 Gen 2x1 xHCI Host Controller + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package USB 3.2 Gen 2x1 (10 Gbs) xHCI Host Controller + +pci:v00008086d0000A0EE* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) pci:v00008086d0000A0EF* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Shared SRAM + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Shared SRAM pci:v00008086d0000A0F0* ID_MODEL_FROM_DATABASE=Wi-Fi 6 AX201 @@ -116534,8 +119198,14 @@ pci:v00008086d0000A0F0* pci:v00008086d0000A0F0sv00008086sd00000244* ID_MODEL_FROM_DATABASE=Wi-Fi 6 AX201 (Wi-Fi 6 AX101NGW) +pci:v00008086d0000A0FB* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Generic SPI (GSPI) #2 + pci:v00008086d0000A0FC* - ID_MODEL_FROM_DATABASE=Tiger Lake-LP Integrated Sensor Hub + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Integrated Sensor Hub + +pci:v00008086d0000A0FD* + ID_MODEL_FROM_DATABASE=500 Series Chipset Family On-Package Generic SPI (GSPI) #3 pci:v00008086d0000A102* ID_MODEL_FROM_DATABASE=Q170/Q150/B150/H170/H110/Z170/CM236 Chipset SATA Controller [AHCI Mode] @@ -116568,181 +119238,181 @@ pci:v00008086d0000A10F* ID_MODEL_FROM_DATABASE=Sunrise Point-H SATA Controller [RAID mode] pci:v00008086d0000A110* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #1 pci:v00008086d0000A111* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #2 pci:v00008086d0000A112* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #3 pci:v00008086d0000A113* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #4 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #4 pci:v00008086d0000A114* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #5 pci:v00008086d0000A114sv00001043sd00008694* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #5 (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #5 (H110I-PLUS Motherboard) pci:v00008086d0000A115* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #6 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #6 pci:v00008086d0000A116* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #7 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #7 pci:v00008086d0000A117* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #8 pci:v00008086d0000A118* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #9 pci:v00008086d0000A118sv00001043sd00008694* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #9 (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #9 (H110I-PLUS Motherboard) pci:v00008086d0000A119* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #10 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #10 pci:v00008086d0000A119sv00001043sd00008694* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #10 (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #10 (H110I-PLUS Motherboard) pci:v00008086d0000A11A* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #11 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #11 pci:v00008086d0000A11B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #12 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #12 pci:v00008086d0000A11C* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #13 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #13 pci:v00008086d0000A11D* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #14 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #14 pci:v00008086d0000A11E* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #15 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #15 pci:v00008086d0000A11F* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #16 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #16 pci:v00008086d0000A120* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family P2SB + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family P2SB pci:v00008086d0000A121* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Power Management Controller + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PMC pci:v00008086d0000A121sv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Power Management Controller (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PMC (XPS 15 9550) pci:v00008086d0000A121sv0000103Csd0000825B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Power Management Controller (OMEN-17-w001nv) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PMC (OMEN-17-w001nv) pci:v00008086d0000A121sv00001043sd00008694* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Power Management Controller (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PMC (H110I-PLUS Motherboard) pci:v00008086d0000A121sv00001462sd00007994* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Power Management Controller (H110M ECO/GAMING) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PMC (H110M ECO/GAMING) pci:v00008086d0000A122* ID_MODEL_FROM_DATABASE=Sunrise Point-H cAVS pci:v00008086d0000A123* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family SMBus + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family SMBus pci:v00008086d0000A123sv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family SMBus (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family SMBus (XPS 15 9550) pci:v00008086d0000A123sv0000103Csd0000825B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family SMBus (OMEN-17-w001nv) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family SMBus (OMEN-17-w001nv) pci:v00008086d0000A123sv00001043sd00008694* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family SMBus (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family SMBus (H110I-PLUS Motherboard) pci:v00008086d0000A123sv00001462sd00007994* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family SMBus (H110M ECO/GAMING) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family SMBus (H110M ECO/GAMING) pci:v00008086d0000A124* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family SPI Controller + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family SPI Controller pci:v00008086d0000A125* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Gigabit Ethernet Controller + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family GbE Controller pci:v00008086d0000A126* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Trace Hub + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family Trace Hub pci:v00008086d0000A127* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO UART #0 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family UART #0 pci:v00008086d0000A128* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO UART #1 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family UART #1 pci:v00008086d0000A129* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO GSPI #0 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family GSPI #0 pci:v00008086d0000A12A* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO GSPI #1 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family GSPI #1 pci:v00008086d0000A12F* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family USB 3.0 xHCI Controller pci:v00008086d0000A12Fsv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family USB 3.0 xHCI Controller (XPS 15 9550) pci:v00008086d0000A12Fsv0000103Csd0000825B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller (OMEN-17-w001nv) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family USB 3.0 xHCI Controller (OMEN-17-w001nv) pci:v00008086d0000A12Fsv00001043sd00008694* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family USB 3.0 xHCI Controller (H110I-PLUS Motherboard) pci:v00008086d0000A12Fsv00001462sd00007994* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller (H110M ECO/GAMING) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family USB 3.0 xHCI Controller (H110M ECO/GAMING) pci:v00008086d0000A130* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family USB Device Controller (OTG) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family USB Device Controller (OTG) pci:v00008086d0000A131* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Thermal Subsystem + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family Thermal Subsystem pci:v00008086d0000A131sv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Thermal Subsystem (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family Thermal Subsystem (XPS 15 9550) pci:v00008086d0000A131sv0000103Csd0000825B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Thermal Subsystem (OMEN-17-w001nv) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family Thermal Subsystem (OMEN-17-w001nv) pci:v00008086d0000A131sv00001462sd00007994* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Thermal Subsystem (H110M ECO/GAMING) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family Thermal Subsystem (H110M ECO/GAMING) pci:v00008086d0000A133* ID_MODEL_FROM_DATABASE=Sunrise Point-H Northpeak ACPI Function pci:v00008086d0000A135* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Integrated Sensor Hub + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family ISH pci:v00008086d0000A13A* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family MEI Controller #1 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family MEI #1 pci:v00008086d0000A13Asv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family MEI Controller #1 (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family MEI #1 (XPS 15 9550) pci:v00008086d0000A13Asv0000103Csd0000825B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family MEI Controller #1 (OMEN-17-w001nv) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family MEI #1 (OMEN-17-w001nv) pci:v00008086d0000A13Asv00001043sd00008694* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family MEI Controller #1 (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family MEI #1 (H110I-PLUS Motherboard) pci:v00008086d0000A13Asv00001462sd00007994* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family MEI Controller #1 (H110M ECO/GAMING) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family MEI #1 (H110M ECO/GAMING) pci:v00008086d0000A13B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family MEI Controller #2 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family MEI #2 pci:v00008086d0000A13C* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family IDE Redirection + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family IDE Redirection pci:v00008086d0000A13D* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family KT Redirection + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family Keyboard and Text (KT) Redirection pci:v00008086d0000A13E* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family MEI Controller #3 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family MEI #3 pci:v00008086d0000A140* ID_MODEL_FROM_DATABASE=Sunrise Point-H LPC Controller @@ -116853,121 +119523,121 @@ pci:v00008086d0000A15F* ID_MODEL_FROM_DATABASE=Sunrise Point-H LPC Controller pci:v00008086d0000A160* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family I2C #0 pci:v00008086d0000A160sv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO I2C Controller #0 (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family I2C #0 (XPS 15 9550) pci:v00008086d0000A160sv0000103Csd0000825B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO I2C Controller #0 (OMEN-17-w001nv) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family I2C #0 (OMEN-17-w001nv) pci:v00008086d0000A161* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family I2C #1 pci:v00008086d0000A161sv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO I2C Controller #1 (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family I2C #1 (XPS 15 9550) pci:v00008086d0000A162* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO I2C Controller #2 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family I2C #2 pci:v00008086d0000A163* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO I2C Controller #3 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family I2C #3 pci:v00008086d0000A166* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family Serial IO UART Controller #2 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family UART #2 pci:v00008086d0000A167* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #17 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #17 pci:v00008086d0000A168* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #18 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #18 pci:v00008086d0000A169* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #19 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #19 pci:v00008086d0000A16A* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family PCI Express Root Port #20 + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family PCIe Root Port #20 pci:v00008086d0000A170* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family HD Audio Controller + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family HD Audio pci:v00008086d0000A170sv00001028sd000006E4* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family HD Audio Controller (XPS 15 9550) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family HD Audio (XPS 15 9550) pci:v00008086d0000A170sv0000103Csd0000825B* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family HD Audio Controller (OMEN-17-w001nv) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family HD Audio (OMEN-17-w001nv) pci:v00008086d0000A170sv00001043sd000086C7* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family HD Audio Controller (H110I-PLUS Motherboard) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family HD Audio (H110I-PLUS Motherboard) pci:v00008086d0000A170sv00001462sd0000F994* - ID_MODEL_FROM_DATABASE=100 Series/C230 Series Chipset Family HD Audio Controller (H110M ECO/GAMING) + ID_MODEL_FROM_DATABASE=100/C230 Series Chipset Family HD Audio (H110M ECO/GAMING) pci:v00008086d0000A171* ID_MODEL_FROM_DATABASE=HM175/QM175/CM238 HD Audio Controller pci:v00008086d0000A182* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family SATA Controller [AHCI mode] + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family SATA Controller (AHCI) pci:v00008086d0000A186* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family SATA Controller [RAID mode] + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family SATA Controller (RAID 0/1/5/10) pci:v00008086d0000A190* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #0 pci:v00008086d0000A191* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #1 pci:v00008086d0000A192* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #2 pci:v00008086d0000A193* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #4 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #3 pci:v00008086d0000A194* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #4 pci:v00008086d0000A195* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #6 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #5 pci:v00008086d0000A196* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #7 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #6 pci:v00008086d0000A197* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #7 pci:v00008086d0000A198* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #8 pci:v00008086d0000A199* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #10 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #9 pci:v00008086d0000A19A* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #11 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #10 pci:v00008086d0000A19B* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #12 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #11 pci:v00008086d0000A19C* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #13 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #12 pci:v00008086d0000A19D* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #14 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #13 pci:v00008086d0000A19E* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #15 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #14 pci:v00008086d0000A19F* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #16 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #15 pci:v00008086d0000A1A0* ID_MODEL_FROM_DATABASE=C620 Series Chipset Family P2SB pci:v00008086d0000A1A1* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family Power Management Controller + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PMC pci:v00008086d0000A1A1sv000015D9sd0000095D* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family Power Management Controller (X11SPM-TF) + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PMC (X11SPM-TF) pci:v00008086d0000A1A2* ID_MODEL_FROM_DATABASE=C620 Series Chipset Family cAVS @@ -117000,70 +119670,73 @@ pci:v00008086d0000A1B1sv000015D9sd0000095D* ID_MODEL_FROM_DATABASE=C620 Series Chipset Family Thermal Subsystem (X11SPM-TF) pci:v00008086d0000A1BA* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MEI Controller #1 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MEI HECI #1 pci:v00008086d0000A1BAsv000015D9sd0000095D* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MEI Controller #1 (X11SPM-TF) + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MEI HECI #1 (X11SPM-TF) pci:v00008086d0000A1BB* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MEI Controller #2 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MEI HECI #2 pci:v00008086d0000A1BBsv000015D9sd0000095D* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MEI Controller #2 (X11SPM-TF) + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MEI HECI #2 (X11SPM-TF) pci:v00008086d0000A1BC* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family IDE Redirection + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MEI IDE Redirection pci:v00008086d0000A1BD* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family KT Redirection + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MEI Keyboard and Text (KT) Redirection pci:v00008086d0000A1BE* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MEI Controller #3 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MEI HECI #3 pci:v00008086d0000A1BEsv000015D9sd0000095D* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MEI Controller #3 (X11SPM-TF) + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MEI HECI #3 (X11SPM-TF) pci:v00008086d0000A1C1* - ID_MODEL_FROM_DATABASE=C621 Series Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C621 Chipset LPC/eSPI Controller pci:v00008086d0000A1C2* - ID_MODEL_FROM_DATABASE=C622 Series Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C622 Chipset LPC/eSPI Controller pci:v00008086d0000A1C2sv000015D9sd0000095D* - ID_MODEL_FROM_DATABASE=C622 Series Chipset LPC/eSPI Controller (X11SPM-TF) + ID_MODEL_FROM_DATABASE=C622 Chipset LPC/eSPI Controller (X11SPM-TF) pci:v00008086d0000A1C3* - ID_MODEL_FROM_DATABASE=C624 Series Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C624 Chipset LPC/eSPI Controller pci:v00008086d0000A1C4* - ID_MODEL_FROM_DATABASE=C625 Series Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C625 Chipset LPC/eSPI Controller pci:v00008086d0000A1C5* - ID_MODEL_FROM_DATABASE=C626 Series Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C626 Chipset LPC/eSPI Controller pci:v00008086d0000A1C6* - ID_MODEL_FROM_DATABASE=C627 Series Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C627 Chipset LPC/eSPI Controller pci:v00008086d0000A1C7* - ID_MODEL_FROM_DATABASE=C628 Series Chipset LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=C628 Chipset LPC/eSPI Controller + +pci:v00008086d0000A1CA* + ID_MODEL_FROM_DATABASE=C629 Chipset LPC/eSPI Controller pci:v00008086d0000A1D2* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family SSATA Controller [AHCI mode] + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family SSATA Controller (AHCI) pci:v00008086d0000A1D6* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family SSATA Controller [RAID mode] + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family SSATA Controller (RAID 0/1/5/10) pci:v00008086d0000A1E7* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #17 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #16 pci:v00008086d0000A1E8* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #18 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #17 pci:v00008086d0000A1E9* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #19 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #18 pci:v00008086d0000A1EA* - ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCI Express Root Port #20 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family PCIe Root Port #19 pci:v00008086d0000A1EC* ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MROM 0 @@ -117072,211 +119745,271 @@ pci:v00008086d0000A1ED* ID_MODEL_FROM_DATABASE=C620 Series Chipset Family MROM 1 pci:v00008086d0000A1F0* - ID_MODEL_FROM_DATABASE=C62x HD Audio Controller + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family HD Audio pci:v00008086d0000A1F8* - ID_MODEL_FROM_DATABASE=Lewisburg IE: HECI #1 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family IE HECI #1 pci:v00008086d0000A1F9* - ID_MODEL_FROM_DATABASE=Lewisburg IE: HECI #2 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family IE HECI #2 pci:v00008086d0000A1FA* - ID_MODEL_FROM_DATABASE=Lewisburg IE: IDE-r + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family IE IDE Redirection pci:v00008086d0000A1FB* - ID_MODEL_FROM_DATABASE=Lewisburg IE: KT Controller + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family IE Keyboard and Text Redirection pci:v00008086d0000A1FC* - ID_MODEL_FROM_DATABASE=Lewisburg IE: HECI #3 + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family IE HECI #3 pci:v00008086d0000A202* - ID_MODEL_FROM_DATABASE=Lewisburg SATA Controller [AHCI mode] + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) SATA Controller (AHCI) pci:v00008086d0000A206* - ID_MODEL_FROM_DATABASE=Lewisburg SATA Controller [RAID mode] + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) SATA Controller (RAID 0/1/5/10) pci:v00008086d0000A210* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #0 pci:v00008086d0000A211* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #1 pci:v00008086d0000A212* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #2 pci:v00008086d0000A213* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #3 pci:v00008086d0000A214* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #4 pci:v00008086d0000A215* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #5 pci:v00008086d0000A216* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #6 pci:v00008086d0000A217* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #7 pci:v00008086d0000A218* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #8 pci:v00008086d0000A219* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #9 pci:v00008086d0000A21A* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #10 pci:v00008086d0000A21B* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #11 pci:v00008086d0000A21C* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #12 pci:v00008086d0000A21D* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #13 pci:v00008086d0000A21E* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #14 pci:v00008086d0000A21F* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #15 + +pci:v00008086d0000A220* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) P2SB pci:v00008086d0000A221* - ID_MODEL_FROM_DATABASE=Lewisburg Power Management Controller + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PMC pci:v00008086d0000A223* - ID_MODEL_FROM_DATABASE=Lewisburg SMBus + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) SMBus pci:v00008086d0000A224* - ID_MODEL_FROM_DATABASE=Lewisburg SPI Controller + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) SPI Controller + +pci:v00008086d0000A226* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) Trace Hub + +pci:v00008086d0000A22F* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) USB 3.0 xHCI Controller + +pci:v00008086d0000A231* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) Thermal Subsystem + +pci:v00008086d0000A23A* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) MEI HECI #1 + +pci:v00008086d0000A23B* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) MEI HECI #2 + +pci:v00008086d0000A23C* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) MEI IDE Redirection + +pci:v00008086d0000A23D* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) MEI Keyboard and Text (KT) Redirection + +pci:v00008086d0000A23E* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) MEI HECI #3 pci:v00008086d0000A242* - ID_MODEL_FROM_DATABASE=Lewisburg LPC or eSPI Controller + ID_MODEL_FROM_DATABASE=C624 Chipset (Super) LPC/eSPI Controller pci:v00008086d0000A243* - ID_MODEL_FROM_DATABASE=Lewisburg LPC or eSPI Controller + ID_MODEL_FROM_DATABASE=C627 Chipset (Super) LPC/eSPI Controller + +pci:v00008086d0000A244* + ID_MODEL_FROM_DATABASE=C621 Chipset (Super) LPC/eSPI Controller + +pci:v00008086d0000A245* + ID_MODEL_FROM_DATABASE=C627 Chipset (Super) LPC/eSPI Controller + +pci:v00008086d0000A246* + ID_MODEL_FROM_DATABASE=C628 Chipset (Super) LPC/eSPI Controller pci:v00008086d0000A252* - ID_MODEL_FROM_DATABASE=Lewisburg SSATA Controller [AHCI mode] + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) SSATA Controller (AHCI) pci:v00008086d0000A256* - ID_MODEL_FROM_DATABASE=Lewisburg SSATA Controller [RAID mode] + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) SSATA Controller (RAID 0/1/5/10) pci:v00008086d0000A267* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #16 pci:v00008086d0000A268* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #17 pci:v00008086d0000A269* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #18 pci:v00008086d0000A26A* - ID_MODEL_FROM_DATABASE=Lewisburg PCI Express Root Port + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) PCIe Root Port #19 + +pci:v00008086d0000A26C* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) MROM 0 + +pci:v00008086d0000A270* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) Audio + +pci:v00008086d0000A278* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) IE HECI #1 + +pci:v00008086d0000A279* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) IE HECI #2 + +pci:v00008086d0000A27A* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) IE IDE Redirection + +pci:v00008086d0000A27B* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) IE Keyboard and Text Redirection + +pci:v00008086d0000A27C* + ID_MODEL_FROM_DATABASE=C620 Series Chipset Family (Super) IE HECI #3 pci:v00008086d0000A282* - ID_MODEL_FROM_DATABASE=200 Series PCH SATA controller [AHCI mode] + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SATA Controller (AHCI) pci:v00008086d0000A282sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH SATA controller [AHCI mode] (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SATA Controller (AHCI) (H270 PC MATE) pci:v00008086d0000A286* - ID_MODEL_FROM_DATABASE=200 Series PCH SATA controller [RAID mode] + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SATA Controller (RAID) Premium + +pci:v00008086d0000A28E* + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SATA Controller (RST and Optane Technology) pci:v00008086d0000A290* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #1 pci:v00008086d0000A291* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #2 pci:v00008086d0000A292* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #3 pci:v00008086d0000A293* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #4 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #4 pci:v00008086d0000A294* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #5 pci:v00008086d0000A294sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #5 (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #5 (H270 PC MATE) pci:v00008086d0000A295* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #6 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #6 pci:v00008086d0000A296* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #7 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #7 pci:v00008086d0000A296sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #7 (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #7 (H270 PC MATE) pci:v00008086d0000A297* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #8 pci:v00008086d0000A298* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #9 pci:v00008086d0000A298sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #9 (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #9 (H270 PC MATE) pci:v00008086d0000A299* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #10 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #10 pci:v00008086d0000A29A* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #11 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #11 pci:v00008086d0000A29B* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #12 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #12 pci:v00008086d0000A29C* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #13 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #13 pci:v00008086d0000A29D* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #14 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #14 pci:v00008086d0000A29E* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #15 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #15 pci:v00008086d0000A29F* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #16 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #16 pci:v00008086d0000A2A0* ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family P2SB pci:v00008086d0000A2A1* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Power Management Controller + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PMC pci:v00008086d0000A2A1sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Power Management Controller (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PMC (H270 PC MATE) pci:v00008086d0000A2A3* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SMBus Controller + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SMBus pci:v00008086d0000A2A3sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SMBus Controller (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SMBus (H270 PC MATE) pci:v00008086d0000A2A4* ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family SPI Controller pci:v00008086d0000A2A5* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Gigabit Ethernet Controller + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family GbE Controller pci:v00008086d0000A2A6* ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Trace Hub pci:v00008086d0000A2A7* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Serial IO UART Controller #0 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family UART #0 pci:v00008086d0000A2A8* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Serial IO UART Controller #1 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family UART #1 pci:v00008086d0000A2A9* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Serial IO SPI Controller #0 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family GSPI #0 pci:v00008086d0000A2AA* - ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Serial IO SPI Controller #1 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family GSPI #1 pci:v00008086d0000A2AF* ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family USB 3.0 xHCI Controller @@ -117284,41 +120017,53 @@ pci:v00008086d0000A2AF* pci:v00008086d0000A2AFsv00001462sd00007A72* ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family USB 3.0 xHCI Controller (H270 PC MATE) +pci:v00008086d0000A2B0* + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family USB Device Controller (OTG) + pci:v00008086d0000A2B1* - ID_MODEL_FROM_DATABASE=200 Series PCH Thermal Subsystem + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Thermal Subsystem pci:v00008086d0000A2B1sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH Thermal Subsystem (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Thermal Subsystem (H270 PC MATE) + +pci:v00008086d0000A2B5* + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family ISH pci:v00008086d0000A2BA* - ID_MODEL_FROM_DATABASE=200 Series PCH CSME HECI #1 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family MEI #1 pci:v00008086d0000A2BAsv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH CSME HECI #1 (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family MEI #1 (H270 PC MATE) pci:v00008086d0000A2BB* - ID_MODEL_FROM_DATABASE=200 Series PCH CSME HECI #2 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family MEI #2 + +pci:v00008086d0000A2BC* + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family IDE Redirection pci:v00008086d0000A2BD* - ID_MODEL_FROM_DATABASE=200 Series Chipset Family KT Redirection + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family Keyboard and Text (KT) Redirection + +pci:v00008086d0000A2BE* + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family MEI #3 pci:v00008086d0000A2C4* - ID_MODEL_FROM_DATABASE=200 Series PCH LPC Controller (H270) + ID_MODEL_FROM_DATABASE=H270 Chipset LPC/eSPI Controller pci:v00008086d0000A2C4sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH LPC Controller (H270) (H270 PC MATE) + ID_MODEL_FROM_DATABASE=H270 Chipset LPC/eSPI Controller (H270 PC MATE) pci:v00008086d0000A2C5* - ID_MODEL_FROM_DATABASE=200 Series PCH LPC Controller (Z270) + ID_MODEL_FROM_DATABASE=Z270 Chipset LPC/eSPI Controller pci:v00008086d0000A2C6* - ID_MODEL_FROM_DATABASE=200 Series PCH LPC Controller (Q270) + ID_MODEL_FROM_DATABASE=Q270 Chipset LPC/eSPI Controller pci:v00008086d0000A2C7* - ID_MODEL_FROM_DATABASE=200 Series PCH LPC Controller (Q250) + ID_MODEL_FROM_DATABASE=Q250 Chipset LPC/eSPI Controller pci:v00008086d0000A2C8* - ID_MODEL_FROM_DATABASE=200 Series PCH LPC Controller (B250) + ID_MODEL_FROM_DATABASE=B250 Chipset LPC/eSPI Controller pci:v00008086d0000A2C9* ID_MODEL_FROM_DATABASE=Z370 Chipset LPC/eSPI Controller @@ -117330,52 +120075,52 @@ pci:v00008086d0000A2D3* ID_MODEL_FROM_DATABASE=C422 Chipset LPC/eSPI Controller pci:v00008086d0000A2E0* - ID_MODEL_FROM_DATABASE=200 Series PCH Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family I2C #0 pci:v00008086d0000A2E1* - ID_MODEL_FROM_DATABASE=200 Series PCH Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family I2C #1 pci:v00008086d0000A2E2* - ID_MODEL_FROM_DATABASE=200 Series PCH Serial IO I2C Controller #2 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family I2C #2 pci:v00008086d0000A2E3* - ID_MODEL_FROM_DATABASE=200 Series PCH Serial IO I2C Controller #3 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family I2C #3 pci:v00008086d0000A2E6* - ID_MODEL_FROM_DATABASE=200 Series PCH Serial IO UART Controller #2 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family UART #2 pci:v00008086d0000A2E7* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #17 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #17 pci:v00008086d0000A2E8* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #18 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #18 pci:v00008086d0000A2E9* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #19 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #19 pci:v00008086d0000A2EA* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #20 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #20 pci:v00008086d0000A2EB* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #21 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #21 pci:v00008086d0000A2EC* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #22 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #22 pci:v00008086d0000A2ED* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #23 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #23 pci:v00008086d0000A2EE* - ID_MODEL_FROM_DATABASE=200 Series PCH PCI Express Root Port #24 + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family PCIe Root Port #24 pci:v00008086d0000A2F0* - ID_MODEL_FROM_DATABASE=200 Series PCH HD Audio + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family HD Audio pci:v00008086d0000A2F0sv00001462sd00007A72* - ID_MODEL_FROM_DATABASE=200 Series PCH HD Audio (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family HD Audio (H270 PC MATE) pci:v00008086d0000A2F0sv00001462sd0000FA72* - ID_MODEL_FROM_DATABASE=200 Series PCH HD Audio (H270 PC MATE) + ID_MODEL_FROM_DATABASE=200 Series/Z370 Chipset Family HD Audio (H270 PC MATE) pci:v00008086d0000A303* ID_MODEL_FROM_DATABASE=H310 Chipset LPC/eSPI Controller @@ -117416,188 +120161,329 @@ pci:v00008086d0000A30E* pci:v00008086d0000A313* ID_MODEL_FROM_DATABASE=Cannon Lake LPC/eSPI Controller +pci:v00008086d0000A320* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family P2SB + +pci:v00008086d0000A321* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PMC + pci:v00008086d0000A323* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH SMBus Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SMBus pci:v00008086d0000A323sv00001028sd00000869* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH SMBus Controller (Vostro 3470) + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SMBus (Vostro 3470) pci:v00008086d0000A324* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH SPI Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SPI (Flash) Controller pci:v00008086d0000A324sv00001028sd00000869* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH SPI Controller (Vostro 3470) + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SPI (Flash) Controller (Vostro 3470) + +pci:v00008086d0000A326* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family Trace Hub pci:v00008086d0000A328* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Serial IO UART Host Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family UART #0 + +pci:v00008086d0000A329* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family UART #1 + +pci:v00008086d0000A32A* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family GSPI #0 pci:v00008086d0000A32B* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH SPI Host Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family GSPI #1 pci:v00008086d0000A32C* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #21 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #21 pci:v00008086d0000A32D* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #22 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #22 pci:v00008086d0000A32E* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #23 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #23 pci:v00008086d0000A32F* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #24 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #24 pci:v00008086d0000A330* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #9 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #9 pci:v00008086d0000A331* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #10 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #10 pci:v00008086d0000A332* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #11 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #11 pci:v00008086d0000A333* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #12 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #12 pci:v00008086d0000A334* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #13 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #13 pci:v00008086d0000A335* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #14 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #14 pci:v00008086d0000A336* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #15 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #15 pci:v00008086d0000A337* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #16 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #16 pci:v00008086d0000A338* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #1 pci:v00008086d0000A339* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #2 pci:v00008086d0000A33A* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #3 pci:v00008086d0000A33B* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #4 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #4 pci:v00008086d0000A33C* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #5 pci:v00008086d0000A33D* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #6 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #6 pci:v00008086d0000A33E* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #7 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #7 pci:v00008086d0000A33F* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #8 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #8 pci:v00008086d0000A340* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #17 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #17 pci:v00008086d0000A341* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #18 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #18 pci:v00008086d0000A342* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #19 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #19 pci:v00008086d0000A343* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH PCI Express Root Port #20 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family PCIe Root Port #20 + +pci:v00008086d0000A347* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family UART #2 pci:v00008086d0000A348* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH cAVS + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family HD Audio pci:v00008086d0000A348sv00001028sd00000869* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH cAVS (Vostro 3470) + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family HD Audio (Vostro 3470) pci:v00008086d0000A352* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH SATA AHCI Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SATA Controller (AHCI) pci:v00008086d0000A352sv00001028sd00000869* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH SATA AHCI Controller (Vostro 3470) + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SATA Controller (AHCI) (Vostro 3470) pci:v00008086d0000A353* - ID_MODEL_FROM_DATABASE=Cannon Lake Mobile PCH SATA AHCI Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SATA Controller (AHCI) + +pci:v00008086d0000A355* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SATA Controller (RAID 0/1/5/10) + +pci:v00008086d0000A356* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SATA Controller (RAID 0/1/5/10) + +pci:v00008086d0000A357* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SATA Controller (RAID 0/1/5/10) + +pci:v00008086d0000A35E* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SATA Controller Optane Memory pci:v00008086d0000A360* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH HECI Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family HECI #1 pci:v00008086d0000A360sv00001028sd00000869* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH HECI Controller (Vostro 3470) + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family HECI #1 (Vostro 3470) + +pci:v00008086d0000A361* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family HECI #2 + +pci:v00008086d0000A362* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family IDE Redirection (IDER-R) pci:v00008086d0000A363* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Active Management Technology - SOL + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family Keyboard and Text (KT) Redirection pci:v00008086d0000A364* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH HECI Controller #2 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family HECI #3 + +pci:v00008086d0000A365* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family HECI #4 pci:v00008086d0000A368* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family I2C Controller #0 pci:v00008086d0000A369* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family I2C Controller #1 pci:v00008086d0000A36A* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Serial IO I2C Controller #2 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family I2C Controller #2 pci:v00008086d0000A36B* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Serial IO I2C Controller #3 + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family I2C Controller #3 pci:v00008086d0000A36D* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH USB 3.1 xHCI Host Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family USB 3.1 xHCI pci:v00008086d0000A36Dsv00001028sd00000869* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH USB 3.1 xHCI Host Controller (Vostro 3470) + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family USB 3.1 xHCI (Vostro 3470) + +pci:v00008086d0000A36E* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family USB Device Controller (Dual Role) pci:v00008086d0000A36F* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Shared SRAM + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family Shared SRAM pci:v00008086d0000A370* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH CNVi WiFi + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family CNVi Wi-Fi pci:v00008086d0000A370sv00001A56sd00001552* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH CNVi WiFi (Killer(R) Wireless-AC 1550i Wireless Network Adapter (9560NGW)) + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family CNVi Wi-Fi (Killer(R) Wireless-AC 1550i Wireless Network Adapter (9560NGW)) pci:v00008086d0000A370sv00008086sd00000034* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH CNVi WiFi (Wireless-AC 9560) + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family CNVi Wi-Fi (Wireless-AC 9560) + +pci:v00008086d0000A371* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family CNVi Wi-Fi + +pci:v00008086d0000A372* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family CNVi Wi-Fi + +pci:v00008086d0000A373* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family CNVi Wi-Fi pci:v00008086d0000A379* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Thermal Controller + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family Thermal Subsystem pci:v00008086d0000A379sv00001028sd00000869* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Thermal Controller (Vostro 3470) + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family Thermal Subsystem (Vostro 3470) + +pci:v00008086d0000A37B* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family SPI #2 + +pci:v00008086d0000A37C* + ID_MODEL_FROM_DATABASE=300/C240 Series Chipset Family Integrated Sensor Hub pci:v00008086d0000A382* - ID_MODEL_FROM_DATABASE=400 Series Chipset Family SATA AHCI Controller + ID_MODEL_FROM_DATABASE=B460/H410 Chipset SATA Controller (AHCI) + +pci:v00008086d0000A384* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset SATA Controller (RAID 0/1/5/10) Not Premium + +pci:v00008086d0000A386* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset SATA Controller (RAID 0/1/5/10) Premium + +pci:v00008086d0000A38E* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset SATA Controller (RST Optane) + +pci:v00008086d0000A390* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #1 + +pci:v00008086d0000A391* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #2 + +pci:v00008086d0000A392* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #3 + +pci:v00008086d0000A393* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #4 pci:v00008086d0000A394* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #05 + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #5 + +pci:v00008086d0000A395* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #6 + +pci:v00008086d0000A396* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #7 pci:v00008086d0000A397* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #08 + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #8 pci:v00008086d0000A398* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port 9 + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #9 + +pci:v00008086d0000A399* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #10 pci:v00008086d0000A39A* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port 11 + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #11 + +pci:v00008086d0000A39B* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #12 + +pci:v00008086d0000A39C* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #13 + +pci:v00008086d0000A39D* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #14 + +pci:v00008086d0000A39E* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #15 + +pci:v00008086d0000A39F* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #16 + +pci:v00008086d0000A3A0* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset P2SB pci:v00008086d0000A3A1* - ID_MODEL_FROM_DATABASE=Cannon Lake PCH Power Management Controller + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PMC pci:v00008086d0000A3A3* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-V SMBus Host Controller + ID_MODEL_FROM_DATABASE=B460/H410 Chipset SMBus + +pci:v00008086d0000A3A4* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset SPI Controller + +pci:v00008086d0000A3A6* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset Trace Hub + +pci:v00008086d0000A3A7* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset UART #0 + +pci:v00008086d0000A3A8* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset UART #1 + +pci:v00008086d0000A3A9* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset SPI #0 + +pci:v00008086d0000A3AA* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset SPI #1 pci:v00008086d0000A3AF* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-V USB Controller + ID_MODEL_FROM_DATABASE=B460/H410 Chipset USB 3.2 Gen 1x1 (5 Gbs) xHCI Controller + +pci:v00008086d0000A3B0* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset USB Device Controller pci:v00008086d0000A3B1* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-V Thermal Subsystem + ID_MODEL_FROM_DATABASE=B460/H410 Chipset Thermal Subsystem + +pci:v00008086d0000A3B5* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset ISH pci:v00008086d0000A3BA* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-V HECI Controller + ID_MODEL_FROM_DATABASE=B460/H410 Chipset CSME HECI #1 + +pci:v00008086d0000A3BB* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset CSME HECI #2 + +pci:v00008086d0000A3BC* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset CSME IDE Redirection + +pci:v00008086d0000A3BD* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset CSME Keyboard and Text (KT) Redirection + +pci:v00008086d0000A3BE* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset CSME HECI #3 pci:v00008086d0000A3C8* ID_MODEL_FROM_DATABASE=B460 Chipset LPC/eSPI Controller @@ -117605,11 +120491,68 @@ pci:v00008086d0000A3C8* pci:v00008086d0000A3DA* ID_MODEL_FROM_DATABASE=H410 Chipset LPC/eSPI Controller +pci:v00008086d0000A3E0* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset I2C #0 + +pci:v00008086d0000A3E1* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset I2C #1 + +pci:v00008086d0000A3E2* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset I2C #2 + +pci:v00008086d0000A3E3* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset I2C #3 + +pci:v00008086d0000A3E6* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset UART #2 + +pci:v00008086d0000A3E7* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #17 + +pci:v00008086d0000A3E8* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #18 + +pci:v00008086d0000A3E9* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #19 + +pci:v00008086d0000A3EA* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #20 + pci:v00008086d0000A3EB* - ID_MODEL_FROM_DATABASE=Comet Lake PCI Express Root Port #21 + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #21 + +pci:v00008086d0000A3EC* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #22 + +pci:v00008086d0000A3ED* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #23 + +pci:v00008086d0000A3EE* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset PCIe Root Port #24 pci:v00008086d0000A3F0* - ID_MODEL_FROM_DATABASE=Comet Lake PCH-V cAVS + ID_MODEL_FROM_DATABASE=B460/H410 Chipset HD Audio + +pci:v00008086d0000A3F1* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset HD Audio + +pci:v00008086d0000A3F2* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset HD Audio + +pci:v00008086d0000A3F3* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset HD Audio + +pci:v00008086d0000A3F4* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset HD Audio + +pci:v00008086d0000A3F5* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset HD Audio + +pci:v00008086d0000A3F6* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset HD Audio + +pci:v00008086d0000A3F7* + ID_MODEL_FROM_DATABASE=B460/H410 Chipset HD Audio pci:v00008086d0000A620* ID_MODEL_FROM_DATABASE=6400/6402 Advanced Memory Buffer (AMB) @@ -117749,152 +120692,260 @@ pci:v00008086d0000A7AD* pci:v00008086d0000A806* ID_MODEL_FROM_DATABASE=Lunar Lake-M LPC/eSPI Controller +pci:v00008086d0000A807* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors eSPI Controller + +pci:v00008086d0000A820* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors P2SB (8 bit) + +pci:v00008086d0000A821* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors PMC + pci:v00008086d0000A822* ID_MODEL_FROM_DATABASE=Lunar Lake-M SMbus Controller pci:v00008086d0000A823* - ID_MODEL_FROM_DATABASE=Lunar Lake-M SPI Controller + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors SPI (flash) Controller pci:v00008086d0000A824* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Trace Hub + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors Trace Hub pci:v00008086d0000A825* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Serial IO UART Controller #0 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors UART #0 pci:v00008086d0000A826* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Serial IO UART Controller #1 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors UART #1 pci:v00008086d0000A827* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Serial IO SPI Controller #0 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors GSPI #0 pci:v00008086d0000A828* - ID_MODEL_FROM_DATABASE=Lunar Lake-M HD Audio Controller + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors HD Audio pci:v00008086d0000A830* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Serial IO SPI Controller #1 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors GSPI #1 pci:v00008086d0000A831* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Thunderbolt 4 USB Controller + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors Type-C Subsystem xHCI pci:v00008086d0000A833* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Thunderbolt 4 NHI #0 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors Thunderbolt DMA0 pci:v00008086d0000A834* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Thunderbolt 4 NHI #1 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors Thunderbolt DMA1 pci:v00008086d0000A838* - ID_MODEL_FROM_DATABASE=Lunar Lake-M PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors PCIe Root Port #1 pci:v00008086d0000A839* - ID_MODEL_FROM_DATABASE=Lunar Lake-M PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors PCIe Root Port #2 pci:v00008086d0000A83A* - ID_MODEL_FROM_DATABASE=Lunar Lake-M PCI Express Root Port #3 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors PCIe Root Port #3 pci:v00008086d0000A83B* - ID_MODEL_FROM_DATABASE=Lunar Lake-M PCI Express Root Port #4 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors PCIe Root Port #4 pci:v00008086d0000A83C* - ID_MODEL_FROM_DATABASE=Lunar Lake-M PCI Express Root Port #5 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors PCIe Root Port #5 pci:v00008086d0000A83D* - ID_MODEL_FROM_DATABASE=Lunar Lake-M PCI Express Root Port #6 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors PCIe Root Port #6 pci:v00008086d0000A840* - ID_MODEL_FROM_DATABASE=BE201 320MHz + ID_MODEL_FROM_DATABASE=BE200 Series Wi-Fi 7 pci:v00008086d0000A845* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Integrated Sensor Hub + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors Integrated Sensor Hub (ISH) + +pci:v00008086d0000A846* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors GSPI #2 pci:v00008086d0000A847* ID_MODEL_FROM_DATABASE=Lunar Lake-M UFS Controller pci:v00008086d0000A848* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Touch Host Controller #0 ID1 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors Touch Host Controller (THC) #0 ID1 pci:v00008086d0000A849* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Touch Host Controller #0 ID2 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors Touch Host Controller (THC) #0 ID2 pci:v00008086d0000A84A* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Touch Host Controller #1 ID1 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors Touch Host Controller (THC) #1 ID1 pci:v00008086d0000A84B* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Touch Host Controller #1 ID2 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors Touch Host Controller (THC) #1 ID2 + +pci:v00008086d0000A84C* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors P2SB (16 bit) pci:v00008086d0000A84E* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Thunderbolt 4 PCI Express Root Port #0 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors USB Type-C Subsystem PCIe Root Port #21 pci:v00008086d0000A84F* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Thunderbolt 4 PCI Express Root Port #1 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors USB Type-C Subsystem PCIe Root Port #22 + +pci:v00008086d0000A850* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors I2C #4 + +pci:v00008086d0000A851* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors I2C #5 + +pci:v00008086d0000A852* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors UART #2 + +pci:v00008086d0000A85D* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors CSME HECI #1 + +pci:v00008086d0000A85E* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors CSME HECI #2 + +pci:v00008086d0000A85F* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors CSME HECI #3 pci:v00008086d0000A860* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Thunderbolt 4 PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors USB Type-C Subsystem PCIe Root Port #23 + +pci:v00008086d0000A862* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors CSME HECI #1 + +pci:v00008086d0000A863* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors CSME HECI #2 + +pci:v00008086d0000A864* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors CSME HECI #3 pci:v00008086d0000A870* - ID_MODEL_FROM_DATABASE=Lunar Lake-M CSME HECI #1 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors CSME HECI #1 (CSE) + +pci:v00008086d0000A871* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors CSME HECI #2 (CSE) + +pci:v00008086d0000A872* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors CSME IDE Redirection (IDE-R) pci:v00008086d0000A873* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Keyboard and Text (KT) Redirection + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors CSME Keyboard and Text (KT) Redirection + +pci:v00008086d0000A874* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors CSME HECI #3 (CSE) + +pci:v00008086d0000A875* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors CSME HECI #4 (CSE) + +pci:v00008086d0000A877* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors I3C #2 pci:v00008086d0000A878* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors I2C #0 pci:v00008086d0000A879* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors I2C #1 pci:v00008086d0000A87A* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Serial IO I2C Controller #2 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors I2C #2 pci:v00008086d0000A87B* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Serial IO I2C Controller #3 + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors I2C #3 + +pci:v00008086d0000A87C* + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors I3C #1 pci:v00008086d0000A87D* - ID_MODEL_FROM_DATABASE=Lunar Lake-M USB 3.2 Gen 2x1 xHCI Host Controller + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors Standalone xHCI Controller pci:v00008086d0000A87F* - ID_MODEL_FROM_DATABASE=Lunar Lake-M Shared SRAM + ID_MODEL_FROM_DATABASE=Core Ultra 200V Series Processors Shared SRAM pci:v00008086d0000ABC0* ID_MODEL_FROM_DATABASE=Omni-Path Fabric Switch Silicon 100 Series +pci:v00008086d0000AD03* + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors Dynamic Tuning Technology (DTT) + pci:v00008086d0000AD0B* - ID_MODEL_FROM_DATABASE=Volume Management Device NVMe RAID Controller Intel Corporation + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors VMD pci:v00008086d0000AD0D* - ID_MODEL_FROM_DATABASE=Arrow Lake-HX Crash Log & Telemetry + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors Crash Log and Telemetry pci:v00008086d0000AD1D* - ID_MODEL_FROM_DATABASE=Arrow Lake NPU + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors NPU pci:v00008086d0000AE10* ID_MODEL_FROM_DATABASE=Arrow Lake-HX Direct eSPI Controller +pci:v00008086d0000AE20* + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors P2SB (SOC-S) + +pci:v00008086d0000AE21* + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors PMC (SOC-S) + +pci:v00008086d0000AE22* + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors SMBus + pci:v00008086d0000AE23* - ID_MODEL_FROM_DATABASE=Arrow Lake-HX SPI (flash) Controller + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors SPI (flash) Controller + +pci:v00008086d0000AE24* + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors Trace Hub pci:v00008086d0000AE4C* - ID_MODEL_FROM_DATABASE=Arrow Lake-HX Gauss Newton Algorithm (GNA) + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors Gauss Newton Algorithm (GNA) pci:v00008086d0000AE4D* - ID_MODEL_FROM_DATABASE=Arrow Lake-HX PCIe Root Port #13 + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors PCIe Root Port #13 + +pci:v00008086d0000AE4E* + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors PCIe Root Port #14 + +pci:v00008086d0000AE4F* + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors PCIe Root Port #15 + +pci:v00008086d0000AE70* + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors CSME HECI #1 + +pci:v00008086d0000AE71* + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors CSME HECI #2 + +pci:v00008086d0000AE74* + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors CSME HECI #3 pci:v00008086d0000AE7F* - ID_MODEL_FROM_DATABASE=Arrow Lake-HX Shared SRAM (SOC-S) + ID_MODEL_FROM_DATABASE=Core Ultra 200 Series Processors Shared SRAM (SOC-S) + +pci:v00008086d0000B000* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PTL-404 + +pci:v00008086d0000B001* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PTL-H12Xe pci:v00008086d0000B002* - ID_MODEL_FROM_DATABASE=Panther Lake Host Bridge/DRAM Controller + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PTL-H484 + +pci:v00008086d0000B003* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PTL-204 + +pci:v00008086d0000B004* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PTL-H444 + +pci:v00008086d0000B005* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PTL-H444 pci:v00008086d0000B01D* - ID_MODEL_FROM_DATABASE=Panther Lake Innovation Platform Framework Processor Participant + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) DTT + +pci:v00008086d0000B02D* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) IAA pci:v00008086d0000B03E* - ID_MODEL_FROM_DATABASE=Panther Lake NPU + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) NPU pci:v00008086d0000B05D* - ID_MODEL_FROM_DATABASE=Panther Lake IPU + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) IPU pci:v00008086d0000B07D* - ID_MODEL_FROM_DATABASE=Panther Lake Platform Monitoring Technology (PMT) + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Crashlog and Telemetry pci:v00008086d0000B080* ID_MODEL_FROM_DATABASE=Panther Lake [Arc B390] @@ -118043,6 +121094,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 +121181,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] @@ -118110,100 +121242,199 @@ pci:v00008086d0000E221* ID_MODEL_FROM_DATABASE=Battlemage G31 [Intel Graphics] pci:v00008086d0000E222* - ID_MODEL_FROM_DATABASE=Battlemage G31 [Intel Graphics] + ID_MODEL_FROM_DATABASE=Battlemage G31 [Arc Pro B65] pci:v00008086d0000E223* - ID_MODEL_FROM_DATABASE=Battlemage G31 [Intel Graphics] + ID_MODEL_FROM_DATABASE=Battlemage G31 [Arc Pro B70] + +pci:v00008086d0000E300* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) eSPI pci:v00008086d0000E302* - ID_MODEL_FROM_DATABASE=Panther Lake LPC/eSPI Controller + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) eSPI + +pci:v00008086d0000E31F* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) eSPI + +pci:v00008086d0000E320* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) P2SB + +pci:v00008086d0000E321* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PMC pci:v00008086d0000E322* - ID_MODEL_FROM_DATABASE=Panther Lake SMBus Controller + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) SMBus pci:v00008086d0000E323* - ID_MODEL_FROM_DATABASE=Panther Lake SPI(flash) Controller + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) SPI (flash) Controller + +pci:v00008086d0000E324* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Trace Hub pci:v00008086d0000E325* - ID_MODEL_FROM_DATABASE=Panther Lake Serial IO UART Controller #0 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) UART #0 pci:v00008086d0000E326* - ID_MODEL_FROM_DATABASE=Panther Lake Serial IO UART Controller #1 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) UART #1 pci:v00008086d0000E327* - ID_MODEL_FROM_DATABASE=Panther Lake Serial IO SPI Host Controller #0 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) GSPI #0 pci:v00008086d0000E328* - ID_MODEL_FROM_DATABASE=Panther Lake Smart Sound Technology BUS + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E329* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E32A* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E32B* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E32C* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E32D* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E32E* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E32F* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio pci:v00008086d0000E330* - ID_MODEL_FROM_DATABASE=Panther Lake Serial IO SPI Host Controller #1 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) GSPI #1 pci:v00008086d0000E331* - ID_MODEL_FROM_DATABASE=Panther Lake TCSS USB3 xHCI Controller + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Type-C Subsystem xHCI + +pci:v00008086d0000E332* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Type-C Subsystem xDCI pci:v00008086d0000E333* - ID_MODEL_FROM_DATABASE=Panther Lake Thunderbolt 4 NHI #0 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Thunderbolt DMA0 pci:v00008086d0000E334* - ID_MODEL_FROM_DATABASE=Panther Lake Thunderbolt 4 NHI #1 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Thunderbolt DMA1 + +pci:v00008086d0000E337* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #24 pci:v00008086d0000E338* - ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port A1 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #1 pci:v00008086d0000E339* - ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port A2 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #2 pci:v00008086d0000E33A* - ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port A3 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #3 pci:v00008086d0000E33B* - ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port A4 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #4 pci:v00008086d0000E33C* - ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port B1 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #5 pci:v00008086d0000E33D* - ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port B2 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #6 pci:v00008086d0000E33E* - ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port B3 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #7 pci:v00008086d0000E33F* - ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port B4 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #8 pci:v00008086d0000E340* - ID_MODEL_FROM_DATABASE=Panther Lake PCH CNVi WiFi + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CNVi Wi-Fi pci:v00008086d0000E340sv00008086sd00000094* - ID_MODEL_FROM_DATABASE=Panther Lake PCH CNVi WiFi (Wi-Fi 6E AX211 160MHz) + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CNVi Wi-Fi (Wi-Fi 6E AX211 160MHz) + +pci:v00008086d0000E341* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CNVi Wi-Fi + +pci:v00008086d0000E342* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CNVi Wi-Fi + +pci:v00008086d0000E343* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CNVi Wi-Fi + +pci:v00008086d0000E344* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) IEH #0 + +pci:v00008086d0000E345* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) ISH + +pci:v00008086d0000E346* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) GSPI #2 + +pci:v00008086d0000E348* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) THC #0 ID1 + +pci:v00008086d0000E349* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) THC #0 ID2 + +pci:v00008086d0000E34A* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) THC #1 ID1 + +pci:v00008086d0000E34B* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) THC #1 ID2 + +pci:v00008086d0000E34C* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) P2SB + +pci:v00008086d0000E34D* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) IEH #1 pci:v00008086d0000E34E* - ID_MODEL_FROM_DATABASE=Panther Lake Thunderbolt 4 PCI Express Root Port #0 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #21 + +pci:v00008086d0000E34F* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #22 + +pci:v00008086d0000E350* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I2C #4 + +pci:v00008086d0000E351* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I2C #5 + +pci:v00008086d0000E352* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) UART #2 + +pci:v00008086d0000E35C* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #10 pci:v00008086d0000E35D* - ID_MODEL_FROM_DATABASE=Panther Lake CSME HECI #1 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #1 + +pci:v00008086d0000E35E* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #2 + +pci:v00008086d0000E35F* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #3 pci:v00008086d0000E360* - ID_MODEL_FROM_DATABASE=Panther Lake Thunderbolt 4 PCI Express Root Port #2 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #23 pci:v00008086d0000E361* - ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port C1 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #9 pci:v00008086d0000E362* - ID_MODEL_FROM_DATABASE=Panther Lake Primary to Sideband (P2SB) Bridge IOE + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #1 pci:v00008086d0000E363* - ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port C3 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #2 pci:v00008086d0000E364* - ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port C4 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #3 pci:v00008086d0000E365* - ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port D1 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #11 pci:v00008086d0000E366* - ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port D2 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #12 pci:v00008086d0000E367* ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port D3 @@ -118224,34 +121455,280 @@ pci:v00008086d0000E36C* ID_MODEL_FROM_DATABASE=Panther Lake PCI Express Root Port D8 pci:v00008086d0000E36F* - ID_MODEL_FROM_DATABASE=Panther Lake I3C Host Controller #0 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I3C #2 pci:v00008086d0000E370* - ID_MODEL_FROM_DATABASE=Panther Lake MEI Controller + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #1 (CSE) + +pci:v00008086d0000E371* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #2 (CSE) + +pci:v00008086d0000E372* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME IDE Redirection (IDE-R) + +pci:v00008086d0000E373* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME Keyboard and Text (KT) Redirection + +pci:v00008086d0000E374* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #3 (CSE) + +pci:v00008086d0000E375* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #4 (CSE) pci:v00008086d0000E376* - ID_MODEL_FROM_DATABASE=Panther Lake Bluetooth PCI Enumerator + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CNVi Bluetooth pci:v00008086d0000E378* - ID_MODEL_FROM_DATABASE=Panther Lake Serial IO I2C Controller #0 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I2C #0 pci:v00008086d0000E379* - ID_MODEL_FROM_DATABASE=Panther Lake Serial IO I2C Controller #1 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I2C #1 pci:v00008086d0000E37A* - ID_MODEL_FROM_DATABASE=Panther Lake Serial IO I2C Controller #2 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I2C #2 pci:v00008086d0000E37B* - ID_MODEL_FROM_DATABASE=Panther Lake Serial IO I2C Controller #3 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I2C #3 pci:v00008086d0000E37C* - ID_MODEL_FROM_DATABASE=Panther Lake I3C Host Controller #1 + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I3C #1 pci:v00008086d0000E37D* - ID_MODEL_FROM_DATABASE=Panther Lake USB 3.2 xHCI Controller + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Standalone xHCI Controller + +pci:v00008086d0000E37E* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Standalone USB Device Controller pci:v00008086d0000E37F* - ID_MODEL_FROM_DATABASE=Panther Lake Shared SRAM + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Shared SRAM + +pci:v00008086d0000E400* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) eSPI + +pci:v00008086d0000E41F* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) eSPI + +pci:v00008086d0000E420* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) P2SB + +pci:v00008086d0000E421* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PMC + +pci:v00008086d0000E422* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) SMBus + +pci:v00008086d0000E423* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) SPI (flash) Controller + +pci:v00008086d0000E424* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Trace Hub + +pci:v00008086d0000E425* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) UART #0 + +pci:v00008086d0000E426* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) UART #1 + +pci:v00008086d0000E427* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) GSPI #0 + +pci:v00008086d0000E428* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E429* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E42A* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E42B* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E42C* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E42D* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E42E* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E42F* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) HD Audio + +pci:v00008086d0000E430* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) GSPI #1 + +pci:v00008086d0000E431* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Type-C Subsystem xHCI + +pci:v00008086d0000E432* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Type-C Subsystem xDCI + +pci:v00008086d0000E433* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Thunderbolt DMA0 + +pci:v00008086d0000E434* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Thunderbolt DMA1 + +pci:v00008086d0000E437* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #24 + +pci:v00008086d0000E438* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #1 + +pci:v00008086d0000E439* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #2 + +pci:v00008086d0000E43A* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #3 + +pci:v00008086d0000E43B* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #4 + +pci:v00008086d0000E43C* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #5 + +pci:v00008086d0000E43D* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #6 + +pci:v00008086d0000E43E* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #7 + +pci:v00008086d0000E43F* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #8 + +pci:v00008086d0000E440* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CNVi Wi-Fi + +pci:v00008086d0000E440sv00008086sd00000114* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CNVi Wi-Fi (Wi-Fi 7 BE211 320MHz) + +pci:v00008086d0000E441* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CNVi Wi-Fi + +pci:v00008086d0000E442* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CNVi Wi-Fi + +pci:v00008086d0000E443* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CNVi Wi-Fi + +pci:v00008086d0000E444* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) IEH #0 + +pci:v00008086d0000E445* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) ISH + +pci:v00008086d0000E446* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) GSPI #2 + +pci:v00008086d0000E448* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) THC #0 ID1 + +pci:v00008086d0000E449* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) THC #0 ID2 + +pci:v00008086d0000E44A* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) THC #1 ID1 + +pci:v00008086d0000E44B* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) THC #1 ID2 + +pci:v00008086d0000E44C* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) P2SB + +pci:v00008086d0000E44D* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) IEH #1 + +pci:v00008086d0000E44E* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #21 + +pci:v00008086d0000E44F* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #22 + +pci:v00008086d0000E450* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I2C #4 + +pci:v00008086d0000E451* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I2C #5 + +pci:v00008086d0000E452* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) UART #2 + +pci:v00008086d0000E45C* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #10 + +pci:v00008086d0000E45D* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #1 + +pci:v00008086d0000E45E* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #2 + +pci:v00008086d0000E45F* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #3 + +pci:v00008086d0000E460* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #23 + +pci:v00008086d0000E461* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) PCIe Root Port #9 + +pci:v00008086d0000E462* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #1 + +pci:v00008086d0000E463* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #2 + +pci:v00008086d0000E464* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #3 + +pci:v00008086d0000E46F* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I3C #2 + +pci:v00008086d0000E470* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #1 (CSE) + +pci:v00008086d0000E471* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #2 (CSE) + +pci:v00008086d0000E472* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME IDE Redirection (IDE-R) + +pci:v00008086d0000E473* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME Keyboard and Text (KT) Redirection + +pci:v00008086d0000E474* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #3 (CSE) + +pci:v00008086d0000E475* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CSME HECI #4 (CSE) + +pci:v00008086d0000E476* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) CNVi Bluetooth + +pci:v00008086d0000E478* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I2C #0 + +pci:v00008086d0000E479* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I2C #1 + +pci:v00008086d0000E47A* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I2C #2 + +pci:v00008086d0000E47B* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I2C #3 + +pci:v00008086d0000E47C* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) I3C #1 + +pci:v00008086d0000E47D* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Standalone xHCI Controller + +pci:v00008086d0000E47E* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Standalone USB Device Controller + +pci:v00008086d0000E47F* + ID_MODEL_FROM_DATABASE=Core Ultra Processors (Series 3) Shared SRAM pci:v00008086d0000F1A5* ID_MODEL_FROM_DATABASE=SSD 600P Series @@ -118649,6 +122126,9 @@ pci:v00008510d00000201sv00008510sd00000014* pci:v00008510d00000201sv00008510sd00000201* ID_MODEL_FROM_DATABASE=GenBu02 Series GPU (GB2062-PUB-DDR) +pci:v00008510d00000301* + ID_MODEL_FROM_DATABASE=GenBu03 Series GPU + pci:v00008686* ID_VENDOR_FROM_DATABASE=SAP @@ -118739,6 +122219,15 @@ pci:v00008848d00001081* pci:v00008848d00001083* ID_MODEL_FROM_DATABASE=Ethernet Controller N400 Series Virtual Function +pci:v00008848d00008208* + ID_MODEL_FROM_DATABASE=Ethernet Controller N210M for 1GbE 1-port RJ45 + +pci:v00008848d00008209* + ID_MODEL_FROM_DATABASE=Ethernet Controller N210 Series Virtual Function + +pci:v00008848d0000820A* + ID_MODEL_FROM_DATABASE=Ethernet Controller N210L for 1GbE 1-port RJ45 + pci:v00008848d00008308* ID_MODEL_FROM_DATABASE=Ethernet Controller N500 Series for 1GbE (Quad-port, Copper RJ45) @@ -118772,6 +122261,9 @@ pci:v00008848d00008500sv00008848sd00000800* pci:v00008848d00008500sv00008848sd00008800* ID_MODEL_FROM_DATABASE=Ethernet Controller N20 Series for 25GbE (Ethernet Network Adapter N20 for RDMA 25GbE SFP28 2-port) +pci:v00008848d00008500sv00008848sd0000C800* + ID_MODEL_FROM_DATABASE=Ethernet Controller N20 Series for 25GbE (Ethernet Network Adapter N20 for RDMA 25GbE SFP28 2-port OCP 3.0) + pci:v00008848d00008501* ID_MODEL_FROM_DATABASE=Ethernet Controller N20 Series for 100GbE @@ -118784,6 +122276,21 @@ pci:v00008848d00008502* pci:v00008848d00008502sv00008848sd00008800* ID_MODEL_FROM_DATABASE=Ethernet Controller N20 Series for 40GbE (Ethernet Network Adapter N20 for RDMA 40GbE QSFP+ 2-port) +pci:v00008848d00008503* + ID_MODEL_FROM_DATABASE=Ethernet Controller N20 Series Virtual Function + +pci:v00008848d00008507* + ID_MODEL_FROM_DATABASE=Ethernet Controller B-Series for 25GbE + +pci:v00008848d00008508* + ID_MODEL_FROM_DATABASE=Ethernet Controller B-Series for 100GbE + +pci:v00008848d00008509* + ID_MODEL_FROM_DATABASE=Ethernet Controller B-Series for 40GbE + +pci:v00008848d0000850A* + ID_MODEL_FROM_DATABASE=Ethernet Controller B-Series Virtual Function + pci:v00008866* ID_VENDOR_FROM_DATABASE=T-Square Design Inc. diff --git a/hwdb.d/20-usb-vendor-model.hwdb b/hwdb.d/20-usb-vendor-model.hwdb index 88620b0ab0a69..0409abca4e8eb 100644 --- a/hwdb.d/20-usb-vendor-model.hwdb +++ b/hwdb.d/20-usb-vendor-model.hwdb @@ -39110,6 +39110,9 @@ usb:v0A5Cp5832* usb:v0A5Cp5843* ID_MODEL_FROM_DATABASE=BCM58200 ControlVault 3 (FingerPrint sensor + Contacted SmartCard) +usb:v0A5Cp5865* + ID_MODEL_FROM_DATABASE=Fingerprint Reader (Dell Control Vault) + usb:v0A5Cp6300* ID_MODEL_FROM_DATABASE=Pirelli Remote NDIS Device diff --git a/hwdb.d/40-imds.hwdb b/hwdb.d/40-imds.hwdb new file mode 100644 index 0000000000000..eeb560d35dc3f --- /dev/null +++ b/hwdb.d/40-imds.hwdb @@ -0,0 +1,143 @@ +# 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 + +# https://www.vultr.com/metadata/ +dmi:bvnVultr:* + IMDS_VENDOR=vultr + IMDS_DATA_URL=http://169.254.169.254 + IMDS_ADDRESS_IPV4=169.254.169.254 + IMDS_KEY_HOSTNAME=/v1/hostname + IMDS_KEY_REGION=/v1/region/regioncode + IMDS_KEY_IPV4_PUBLIC=/v1/interfaces/0/ipv4/address + IMDS_KEY_IPV6_PUBLIC=/v1/interfaces/0/ipv6/address + IMDS_KEY_SSH_KEY=/v1/public-keys + IMDS_KEY_USERDATA=/user-data/user-data diff --git a/hwdb.d/60-autosuspend-fingerprint-reader.hwdb b/hwdb.d/60-autosuspend-fingerprint-reader.hwdb index 85083b0f3d5fb..d4f77ea7db183 100644 --- a/hwdb.d/60-autosuspend-fingerprint-reader.hwdb +++ b/hwdb.d/60-autosuspend-fingerprint-reader.hwdb @@ -81,6 +81,12 @@ usb:v1C7Ap0571* ID_AUTOSUSPEND=1 ID_PERSIST=0 +# Supported by libfprint driver egis_etu905 +usb:v1C7Ap05AE* +usb:v1C7Ap9201* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + # Supported by libfprint driver egismoc usb:v1C7Ap0582* usb:v1C7Ap0583* @@ -166,12 +172,14 @@ usb:v04F3p0C8C* usb:v04F3p0C8D* usb:v04F3p0C98* usb:v04F3p0C99* +usb:v04F3p0C9C* usb:v04F3p0C9D* usb:v04F3p0C9F* usb:v04F3p0CA3* usb:v04F3p0CA7* usb:v04F3p0CA8* usb:v04F3p0CB0* +usb:v04F3p0CB2* ID_AUTOSUSPEND=1 ID_PERSIST=0 @@ -188,9 +196,12 @@ usb:v2808pA959* usb:v2808pA99A* usb:v2808pA57A* usb:v2808pA78A* +usb:v2808pA97A* usb:v2808p1579* usb:v2808p077A* usb:v2808p079A* +usb:v2808p5158* +usb:v2808p6553* ID_AUTOSUSPEND=1 ID_PERSIST=0 @@ -204,12 +215,14 @@ usb:v10A5pD205* usb:v10A5p9524* usb:v10A5p9544* usb:v10A5pC844* +usb:v10A5p9B24* ID_AUTOSUSPEND=1 ID_PERSIST=0 # Supported by libfprint driver goodixmoc usb:v27C6p5840* usb:v27C6p6014* +usb:v27C6p6090* usb:v27C6p6092* usb:v27C6p6094* usb:v27C6p609A* @@ -239,8 +252,15 @@ usb:v27C6p659A* usb:v27C6p659C* usb:v27C6p6A94* usb:v27C6p6512* +usb:v27C6p6890* usb:v27C6p689A* usb:v27C6p66A9* +usb:v27C6p6984* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + +# Supported by libfprint driver mafpmoc +usb:v3274p8012* ID_AUTOSUSPEND=1 ID_PERSIST=0 @@ -256,6 +276,11 @@ usb:v2541pFA03* ID_AUTOSUSPEND=1 ID_PERSIST=0 +# Supported by libfprint driver secugen +usb:v1162p2200* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + # Supported by libfprint driver synaptics usb:v06CBp00BD* usb:v06CBp00C2* @@ -287,6 +312,7 @@ usb:v06CBp0174* usb:v06CBp019D* usb:v06CBp019F* usb:v06CBp01A0* +usb:v06CBp01A4* ID_AUTOSUSPEND=1 ID_PERSIST=0 @@ -493,7 +519,6 @@ usb:v2808pA553* usb:v298Dp2020* usb:v298Dp2033* usb:v2DF0p0003* -usb:v3274p8012* usb:v3538p0930* ID_AUTOSUSPEND=1 ID_PERSIST=0 diff --git a/hwdb.d/60-evdev.hwdb b/hwdb.d/60-evdev.hwdb index 92b43fe1b29d1..e1e30700c4470 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 ######################################### @@ -540,6 +551,17 @@ evdev:input:b0003v256Cp006B* EVDEV_ABS_35=::40 EVDEV_ABS_36=::42 +######################################### +# Infinix +######################################### + +# Infinix Y3 Max YL-613 +evdev:name:SYNA3602:00 093A:35ED Touchpad:dmi:*svnInfinix:*pnY3Max** + EVDEV_ABS_00=::12 + EVDEV_ABS_01=::12 + EVDEV_ABS_35=::12 + EVDEV_ABS_36=::12 + ######################################### # Lenovo ######################################### 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..0494f0311f0da 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -237,10 +237,17 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnAcer*:pnNitro*AN*515-47:pvr* # Nitro AN515-58 evdev:atkbd:dmi:bvn*:bvr*:bd*:svnAcer*:pnNitro*AN*515-58:pvr* - KEYBOARD_KEY_ef=kbdillumup # Fn+F10 - KEYBOARD_KEY_f0=kbdillumdown # Fn+F9 + KEYBOARD_KEY_ef=!kbdillumup # Fn+F10 + KEYBOARD_KEY_f0=!kbdillumdown # Fn+F9 KEYBOARD_KEY_8a=micmute # Microphone mute button KEYBOARD_KEY_55=power + KEYBOARD_KEY_f5=prog1 # NitroSense button + +# 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* @@ -320,7 +327,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 +719,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 @@ -954,6 +960,15 @@ evdev:name:Huawei WMI hotkeys:dmi:bvn*:bvr*:bd*:svnHUAWEI*:pnEUL-WX9:* KEYBOARD_KEY_281=unknown # Brightness Down, also emitted by acpi-video, ignore KEYBOARD_KEY_282=unknown # Brightness Up, also emitted by acpi-video, ignore +########################################################### +# Infinix +########################################################### + +# Infinix Y3 Max YL-613 +evdev:atkbd:dmi:*:svnInfinix:*pnY3Max:* + KEYBOARD_KEY_64=prog1 # Power Boost key (top-right corner) + KEYBOARD_KEY_76=f21 # Fn+F8 Touchpad toggle, sends Ctrl+Meta+f21 + ########################################################### # IBM ########################################################### @@ -1068,6 +1083,10 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*3000*:pvr* evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO:pn0769AP2:pvr3000N200:* KEYBOARD_KEY_b4=prog1 +# Lenovo ThinkPad T14 Gen 1 AMD, Brazilian keyboard +evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO:pn20UES5TQ00:pvr* + KEYBOARD_KEY_9d=ro # slash/question key + # Lenovo IdeaPad evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*IdeaPad*:pvr* evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pnS10-*:* @@ -1131,6 +1150,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 @@ -1190,6 +1211,10 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO:pn21LG:pvr* KEYBOARD_KEY_0a=!9 KEYBOARD_KEY_0b=!0 +# Lenovo LOQ 15IRX9 (83DV) +evdev:atkbd:dmi:*:svnLENOVO:pn*:pvrLOQ15IRX9:* + KEYBOARD_KEY_81=prog1 # Fn+F9; Lenovo Vantage key + # Lenovo Legion Go Translated evdev:name:AT Translated Set 2 keyboard:dmi:*:svnLENOVO:pn83E1:* # Lenovo Legion Go S Translated @@ -1207,6 +1232,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 ########################################################### @@ -1593,10 +1623,22 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMicro-Star*:pn*:* evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMICRO-STAR*:pnGF63*:* KEYBOARD_KEY_85=touchpad_toggle # Toggle touchpad, sends meta+ctrl+toggle +# MSI GS66 Stealth toggles touchpad using Fn+F3 where the keyboard key is 76 +evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMICRO-STAR*:pn*Stealth GS66*:* + KEYBOARD_KEY_76=touchpad_toggle # Toggle touchpad + evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMICRO-STAR*:pnGE60*:* evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMICRO-STAR*:pnGE70*:* KEYBOARD_KEY_c2=ejectcd +# MSI GE76 Raider 10UG uses Fn+F3 for touchpad and Fn+F6 and Fn+F7 +# for crosshair-mode and gaming-mode respectively but the latter +# two were previously not generating any keycodes under Linux +evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMicro-Star*:pnGE76Raider10UG:* + KEYBOARD_KEY_76=touchpad_toggle + KEYBOARD_KEY_f3=prog2 + KEYBOARD_KEY_8a=prog3 + # some MSI models generate ACPI/input events on the LNXVIDEO input devices, # plus some extra synthesized ones on atkbd as an echo of actually changing the # brightness; so ignore those atkbd ones, to avoid loops @@ -1628,13 +1670,22 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMicro-Star*:pn*Modern*:* KEYBOARD_KEY_97=unknown # Lid close KEYBOARD_KEY_98=unknown # Lid open -# MSI Claw A1M, MSI Claw 7 AI+ A2VM, MSI Claw 8 AI+ A2VM -evdev:name:AT Translated Set 2 keyboard:dmi:*:svnMicro-StarInternationalCo.,Ltd.:pnClawA1M:* -evdev:name:AT Translated Set 2 keyboard:dmi:*:svnMicro-StarInternationalCo.,Ltd.:pnClaw7AI+A2VM:* -evdev:name:AT Translated Set 2 keyboard:dmi:*:svnMicro-StarInternationalCo.,Ltd.:pnClaw8AI+A2VM:* +# Keymaps for MSI Modern 15 H AI C1MG +evdev:atkbd:dmi:bvn*:bvr*:bd*:svnMicro-Star*:pnModern15HAIC1MG:* + KEYBOARD_KEY_d7=bluetooth # Fn+F6 Bluetooth key + +# MSI Claw A1M, MSI Claw 7 AI+ A2VM, MSI Claw 8 AI+ A2VM, MSI Claw A8 BZ2EM +evdev:name:AT Translated Set 2 keyboard:dmi:*:rvnMicro-StarInternationalCo.,Ltd.:rnMS-1T41:* +evdev:name:AT Translated Set 2 keyboard:dmi:*:rvnMicro-StarInternationalCo.,Ltd.:rnMS-1T42:* +evdev:name:AT Translated Set 2 keyboard:dmi:*:rvnMicro-StarInternationalCo.,Ltd.:rnMS-1T52:* +evdev:name:AT Translated Set 2 keyboard:dmi:*:rvnMicro-StarInternationalCo.,Ltd.:rnMS-1T8K:* KEYBOARD_KEY_b9=f15 # Right Face Button KEYBOARD_KEY_ba=f16 # Left Face Button +# MSI Katana GF66 12UD toggles touchpad using Fn+F4 where the keyboard key is 76 +evdev:atkbd:dmi:*svnMicro-Star*:pnKatanaGF6612UD:* + KEYBOARD_KEY_76=touchpad_toggle # Toggle touchpad + ########################################## # NEC ########################################## @@ -1808,6 +1859,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 +2206,15 @@ 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 + KEYBOARD_KEY_c1=f21 + ########################################################### # WeiHeng ########################################################### @@ -2170,6 +2238,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:atkbd:dmi:bvn*:bvr*:bd*:svnX-Plus.tech:pnX+Piccolo:pvr* + 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..af5a81a51b23d 100644 --- a/hwdb.d/60-sensor.hwdb +++ b/hwdb.d/60-sensor.hwdb @@ -109,15 +109,22 @@ sensor:modalias:acpi:KIOX0009:*:dmi:*:svnAcer:*:rnOneS1003:* # One 10 (S1003) sensor:modalias:acpi:BMA250E:*:dmi:*:svnAcer:*:rnAigner:* # Iconia Tab 8W (W1-810) ACCEL_MOUNT_MATRIX=1, 0, 0; 0, -1, 0; 0, 0, 1 +######################################### +# Advan +######################################### + +sensor:modalias:acpi:NSA2513:*:dmi*:svnADVAN*:pn1301:* # Evo-X 13 (Intel N150) + ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, -1, 0; 0, 0, 1 + ######################################### # Aquarius ######################################### 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 +228,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,15 +522,12 @@ 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 - ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, -1 +sensor:modalias:acpi:MXC6655:*:dmi:*:svnGPD:pnG1688-08:* # MicroPC 2 + ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, 1, 0; 0, 0, -1 ######################################### # Hometech @@ -621,6 +625,14 @@ sensor:modalias:acpi:KIOX0009:*:dmi:*:bvrJumper12x.WJ2012.bsBKRCP*:svnJumper:pnE sensor:modalias:acpi:KIOX000A:*:dmi:bvnAmericanMegatrendsInc.:bvrZB-BI-11.6-SF133AR200-059-J:*:svnjumper:pnEZpad:* ACCEL_MOUNT_MATRIX=1, 0, 0; 0, -1, 0; 0, 0, 1 +######################################### +# Juno Computers +######################################### + +# Juno Tablet 4 (Wifi) +sensor:modalias:acpi:MXC6655*:dmi:bvnAMI:bvr*:svnJunoComputers:pnjunotab4-wifi:* + ACCEL_MOUNT_MATRIX=0, -1, 0; 1, 0, 0; 0, 0, 1 + ######################################### # Kazam ######################################### @@ -1088,6 +1100,14 @@ sensor:modalias:acpi:SMO8500*:dmi:*:svnUMAX:pnVisionBook10WiPlus:* sensor:modalias:acpi:MXC6655*:dmi:*:svnUMAX:pnVisionbook12WrTab:* ACCEL_MOUNT_MATRIX=0, 1, 0; 1, 0, 0; 0, 0, 1 +######################################### +# Valve +######################################### + +# Lizard mode must be off for the IMU to activate +evdev:input:b0003v28DEp1205* + ACCEL_MOUNT_MATRIX=0, -1, 0; 1, 0, 0; 0, 0, 1 + ######################################### # Voyo ######################################### 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-av-production.hwdb b/hwdb.d/70-av-production.hwdb index c130acfb8f24a..6c0fd6104e174 100644 --- a/hwdb.d/70-av-production.hwdb +++ b/hwdb.d/70-av-production.hwdb @@ -122,6 +122,13 @@ usb:v0FD9p0084* usb:v0FD9p0086* ID_AV_PRODUCTION_CONTROLLER=1 +################ +# Griffin +################ +# PowerMate +usb:v077Dp0410* + ID_AV_PRODUCTION_CONTROLLER=1 + ############################# # Hercules (Guillemot Corp) ############################# @@ -235,6 +242,10 @@ usb:v17CCp1210* usb:v17CCp1130* ID_AV_PRODUCTION_CONTROLLER=1 +# Traktor MX2 +usb:v17CCp2420* + ID_AV_PRODUCTION_CONTROLLER=1 + #################### # Pioneer #################### diff --git a/hwdb.d/70-debug-appliance.hwdb b/hwdb.d/70-debug-appliance.hwdb new file mode 100644 index 0000000000000..8f535f750bb48 --- /dev/null +++ b/hwdb.d/70-debug-appliance.hwdb @@ -0,0 +1,17 @@ +# This file is part of systemd. +# +# Database for debug appliances. +# +# Permitted keys: +# ID_DEBUG_APPLIANCE=?* + +# Samsung devices in download mode +# Used to interact with devices over Odin protocol, see: +# https://en.wikipedia.org/wiki/Odin_(firmware_flashing_software) +# +# The idVendor and idProduct used are documented in source code here: +# https://github.com/Benjamin-Dobell/Heimdall/blob/3997d5cc607e6c603c6e7c0d07e42e9868c62af2/heimdall/source/BridgeManager.h#L69-L79 +usb:v04E8p6601* +usb:v04E8p685D* +usb:v04E8p68C3* + ID_DEBUG_APPLIANCE=samsung_odin diff --git a/hwdb.d/70-mouse.hwdb b/hwdb.d/70-mouse.hwdb index dc482fee8d266..3009ea2fdce1d 100644 --- a/hwdb.d/70-mouse.hwdb +++ b/hwdb.d/70-mouse.hwdb @@ -280,6 +280,21 @@ mouse:usb:v056ep01a9:name:ELECOM ELECOM Bridge G1000 Mouse:* mouse:bluetooth:v056ep018a:name:ELECOM IST PRO Mouse:* ID_INPUT_TRACKBALL=1 +# Elecom HUGE Plus (via wired usb) (M-HT1MR) +mouse:usb:v056ep01aa:name:HUGE PLUS HUGE PLUS:* + ID_INPUT_TRACKBALL=1 + MOUSE_DPI=500 *1000 1500 + +# Elecom HUGE Plus (via usb receiver) (M-HT1MR) +mouse:usb:v056ep01ab:name:HUGE PLUS HUGE PLUS:* + ID_INPUT_TRACKBALL=1 + MOUSE_DPI=500 *1000 1500 + +# Elecom HUGE Plus (via Bluetooth) (M-HT1MR) +mouse:bluetooth:v056ep01ac:name:HUGE PLUS:* + ID_INPUT_TRACKBALL=1 + MOUSE_DPI=500 *1000 1500 + ########################################## # Fujitsu Siemens ########################################## diff --git a/hwdb.d/70-sound-card.hwdb b/hwdb.d/70-sound-card.hwdb index 4c53a861ecc34..340e6b54e3c5e 100644 --- a/hwdb.d/70-sound-card.hwdb +++ b/hwdb.d/70-sound-card.hwdb @@ -20,6 +20,13 @@ # Allowed properties are: # SOUND_FORM_FACTOR +########################################################### +# Bestechnic (Edifier) +########################################################### +# Edifier M60 +usb:v2D99pA094* + SOUND_FORM_FACTOR=speaker + ########################################################### # Corsair ########################################################### @@ -27,6 +34,13 @@ usb:v1B1Cp0A51* SOUND_FORM_FACTOR=headset +########################################################### +# Fractal +########################################################### +# Fractal Scape Dongle +usb:v36BCp0001* + SOUND_FORM_FACTOR=headset + ########################################################### # Microsoft ########################################################### @@ -55,6 +69,7 @@ usb:v1038p2216* usb:v1038p2236* usb:v1038p12C2* usb:v1038p1290* +usb:v1038p1294* usb:v1038p12EC* usb:v1038p2269* usb:v1038p226D* @@ -69,6 +84,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/70-vsock.hwdb b/hwdb.d/70-vsock.hwdb new file mode 100644 index 0000000000000..ca2b8e559bc94 --- /dev/null +++ b/hwdb.d/70-vsock.hwdb @@ -0,0 +1,10 @@ +# This file is part of systemd. +# +# Database for AF_VSOCK driver implementations. +# +# Permitted keys: +# Specify if the driver uses VMADDR_CID_ANY as the local CID. +# VSOCK_ACCEPT_VMADDR_CID_ANY=1|0 + +dmi:bvnMicrosoftCorporation:*:pvrHyper-V* + VSOCK_ACCEPT_VMADDR_CID_ANY=1 diff --git a/hwdb.d/acpi-update.py b/hwdb.d/acpi-update.py index 41670b32bbc7c..66a11d8502dbd 100755 --- a/hwdb.d/acpi-update.py +++ b/hwdb.d/acpi-update.py @@ -5,6 +5,7 @@ # pylint: disable=consider-using-with + def read_table(filename): table = list(reader(open(filename, newline=''))) table = table[1:] # Skip header @@ -15,6 +16,7 @@ def read_table(filename): # a mistake, strip it. print(f'\nacpi:{row[1].strip()}*:\n ID_VENDOR_FROM_DATABASE={row[0].strip()}') + print('''\ # This file is part of systemd. # diff --git a/hwdb.d/acpi_id_registry.csv b/hwdb.d/acpi_id_registry.csv index d4daa95c28997..5ebadcfa24cd4 100644 --- a/hwdb.d/acpi_id_registry.csv +++ b/hwdb.d/acpi_id_registry.csv @@ -1,150 +1,154 @@ Company,"ACPI ID","Approved On Date" -"Aava Mobile Oy",AAVA,04/02/2014 -AMD,AMDI,08/06/2014 -"Applied Micro Circuits Corporation",APMC,11/14/2013 -"Aptina Imaging Corporation",APTA,10/28/2013 -"ARM Ltd.",ARMH,08/13/2012 -"ARM Ltd.",ARML,02/24/2015 -ASUS,ASUS,09/13/2012 -Atmel,ATML,11/17/2011 -AuthenTec,AUTH,06/01/2012 -"Broadcom Corporation",BRCM,05/14/2015 -"Capella Microsystems Inc.",CPLM,05/09/2012 -"Dell, Inc.",DLLK,04/17/2012 -"Dell, Inc.",DELL,07/26/2012 -"ELAN MICROELECTRONICS CORPORATION",ELAN,11/14/2014 -"Everest Semiconductor Co., Ltd.",ESSX,11/17/2014 -"FocalTech Systems Co., Ltd.",FTSC,07/23/2013 -"Freescale, Inc",FRSC,01/01/2010 -"Fuzhou Rockchip Electronics Co., Ltd.",RKCP,07/20/2015 -"Google, Inc.",GOOG,12/05/2013 -"Hewlett-Packard Company",HPQC,11/26/2012 -"Hewlett Packard Enterprise",HWPE,01/15/2015 -"Himax Technologies, Inc.",HIMX,03/19/2014 -"HiSilicon Technologies Co., Ltd.",HISI,11/14/2014 -"HP Inc.",HPIC,01/15/2015 -"HTBLuVA Mödling",HTLM,02/18/2014 -IBM,IBMX,11/15/2012 -Impinj,IMPJ,08/14/2012 -"Inphi Corporation",IPHI,07/15/2014 -"Intel Corporation",ACPI,11/18/2011 -"Intel Corporation",INTC,01/01/2010 -"Intel Corporation",INTL,01/01/2010 -"Invensense, Inc",INVN,02/09/2012 -"IP3 Technology Ltd.",IP3T,11/11/2013 -"Kionix, Inc.",KIOX,12/23/2013 -"Lenovo Beijing Co. Ltd.",IDEA,05/22/2012 -"Linaro, Ltd.",LNRO,11/26/2013 -"Microsoft Corporation",MSAY,03/01/2012 -"Microsoft Corporation",MSFT,01/01/2010 -"Microsoft Corporation",MSHW,01/10/2011 -"MIPI Alliance",MIPI,04/17/2015 -"Nuvoton Technology Corporation",NVTN,11/14/2014 -Nvidia,NVDA,01/01/2010 -"OmniVision Technologies, Inc.",OVTI,02/26/2014 -"Pegatron Corporation",PEGA,08/27/2013 -"Qualcomm Inc",QCOM,01/01/2010 -"REALTEK Semiconductor Corp.",OBDA,11/07/2013 -"Red Hat, Inc.",QEMU,07/30/2015 -"Robert Bosch GmbH",BOSC,05/16/2014 -"Rozsnyó, s.r.o.",RZSN,03/24/2014 -"Sharp Corporation",SHRP,01/27/2015 -"Shenzhen DSO Microelectronics Co.,Ltd.",DSUO,10/10/2013 -"Shenzhen South-Top Computer Co., Ltd.",ST86,12/06/2013 -"Shenzhen three Connaught Information Technology Co., Ltd. (3nod Group)",3NOD,09/23/2013 -"Sierra Wireless",SWEM,01/22/2013 -"Sony Corporation",SONY,09/12/2012 -"Synaptics Inc",SYNA,11/17/2011 -"Teracue AG",TCAG,12/07/2012 -"Texas Instruments",TXNW,01/01/2010 -"The Linux Foundation",LNUX,04/04/2014 -"Dynabook Inc.",TOSB,07/07/2015 -"VAIO Corporation",VAIO,04/18/2014 -"Validity Sensors, Inc",VFSI,06/17/2013 -Wacom,WCOM,11/17/2011 -"Winsider Seminars & Solutions Inc.",WSDR,07/07/2015 -"Maxim Integrated",MXIM,10/16/2015 -"Xiaomi Inc.",XMCC,12/08/2015 -"u-blox AG",UBLX,12/08/2015 -"Raydium Semiconductor Corporation",RAYD,04/13/2016 -"Dialog Semiconductor PLC",DLGS,04/27/2016 -OmniPreSense,OMPS,05/25/2016 -"CoreOS, Inc",CORE,07/01/2016 -"Microchip Technology Inc",MCHP,08/03/2016 -"Green Hills Software",GHSW,08/11/2016 -"AAEON Technology Inc.",AANT,09/01/2016 -"VR Technology Holdings Limited",3GVR,01/19/2017 -"Exar Corporation",EXAR,02/28/2017 -"Coreboot Project",BOOT,02/28/2017 -"Marvell Technology Group Ltd.",MRVL,05/25/2017 -"IHSE GmbH",IHSE,06/22/2017 -"Insyde Software",INSY,11/10/2017 -"Nexstgo Company Limited",NXGO,11/13/2017 -"Ampere Computing",AMPC,03/29/2018 -IDEMIA,IDEM,06/26/2018 -"Vishay Intertechnology, Inc.",VSHY,07/09/2018 -"DMIST RESEARCH LTD",DMST,07/09/2018 -"COMHEAR, INC.",CMHR,08/02/2018 -"Sensel, Inc.",SNSL,08/20/2018 -"G2touch Co., LTD",GTCH,12/04/2018 -"Guizhou Huaxintong Semiconductor Technology Co., Ltd",HXTS,01/18/2019 -"Amazon Corporation",AMZN,02/06/2019 -"ASEM S.p.A.",ASEM,04/29/2019 -"Fujitsu Limited",FUJI,06/18/2019 -"Phytium Technology Co. Ltd.",PHYT,02/14/2020 -"CHENGDU HAIGUANG IC DESIGN CO., LTD",HYGO,07/15/2020 -"PixArt imaging inc.",PIXA,07/15/2020 -"Loongson Technology Corporation Limited",LOON,09/10/2020 -"Seiko Epson Corporation",SECC,02/16/2021 -"Alibaba Co., Ltd.",BABA,02/02/2021 -"Juniper Systems, Inc.",JSYS,03/18/2021 -"Framework Computer LLC",FRMW,03/22/2021 -"Pensando Systems, Inc.",PNSO,03/24/2021 -"Dynabook Inc.",DNBK,06/01/2021 -"Dioo Microcircuits Co., Ltd. Jiangsu",DIOO,06/04/2021 -"Purism SPC",PURI,06/10/2021 -"Lontium Semiconductor Corporation",LTSC,07/21/2021 -"Wacom Technology",WACF,09/21/2021 -"Shanghai Aiwei Electronic Technology Co., Ltd.",AWDZ,12/31/2021 -"Silicom Ltd. Connectivity Solutions",SILC,03/28/2022 -"NOLO Co., Ltd.",NOLO,03/28/2022 -"GoUp Co.,Ltd",GOUP,06/24/2022 -"Shenzhen Jaguar Microsystems Co.,Ltd.",JMIC,09/23/2022 -"Elliptic Laboratories AS",ELAS,10/20/2022 -"Micro Crystal AG",MCRY,11/10/2022 -"Cix Technology (Shanghai) Co., Ltd.",CIXH,11/16/2022 -"EyeTech Digital Systems",ETDS,11/29/2022 -"Raspberry Pi",RPIL,02/22/2023 -"Shenzhen Istaric Technology Co., Ltd.",ISIC,04/24/2023 -"Cirque Corporation",CIRQ,05/09/2023 -"Rivos Inc.",RVOS,06/07/2023 -"Beijer Electronics AB",BECS,06/07/2023 -"ILI Technology Corp",ILIT,06/20/2023 -"Hangzhou hj-micro Technology Co., Ltd",HJMC,07/31/2023 -"Vervent Audio Group",NAIM,01/04/2024 -"Das U-Boot",UBOO,02/14/2024 -3mdeb,DSHR,06/13/2024 -"SigmaSense, LLC",SGSN,06/13/2024 -"INIT - Innovative Informatikanwendungen GmbH",INIT,08/28/2024 -"RISC-V International",RSCV,10/23/2023 -"Ventana Micro Systems",VNTN,09/16/2024 -"DreamBig Semiconductor Inc.",DBSI,01/28/2025 -"Wuxi Institute of Advanced Technology",SUNW,01/28/2025 -"Sophgo Technologies Ltd.",SOPH,04/07/2025 -"SmartSens Technology (Shanghai) CO., Ltd.",SSLC,04/07/2025 -"Areus GmbH",AREU,04/07/2025 -"Rockwell Automation, Inc",ROKL,04/18/2025 -"JUMPtec GmbH",JUMP,04/22/2025 -"Fsas Technologies Inc.",FSAS,04/30/2025 -"JP Morgan Chase N.A.",JPMC,05/30/2025 -"Roku, Inc.",ROKU,07/15/2025 -"UltraRISC Technology (Shanghai) Co., Ltd",ULRV,09/15/2025 -"SYNCS / Aviot Systems Pte Ltd",SYNC,10/21/2025 -"Advantech Co., Ltd.",AHCL,10/23/2025 -"Picoheart (SG) Pte. Ltd.",PICO,10/30/2025 -"Kontron France",KOMF,12/09/2025 -"Ubiquiti Inc.",UBTI,12/10/2025 -"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 +"Aava Mobile Oy",AAVA,2014-04-02 +AMD,AMDI,2014-08-06 +"Applied Micro Circuits Corporation",APMC,2013-11-14 +"Aptina Imaging Corporation",APTA,2013-10-28 +"ARM Ltd.",ARMH,2012-08-13 +"ARM Ltd.",ARML,2015-02-24 +ASUS,ASUS,2012-09-13 +Atmel,ATML,2011-11-17 +AuthenTec,AUTH,2012-06-01 +"Broadcom Corporation",BRCM,2015-05-14 +"Capella Microsystems Inc.",CPLM,2012-05-09 +"Dell, Inc.",DLLK,2012-04-17 +"Dell, Inc.",DELL,2012-07-26 +"ELAN MICROELECTRONICS CORPORATION",ELAN,2014-11-14 +"Everest Semiconductor Co., Ltd.",ESSX,2014-11-17 +"FocalTech Systems Co., Ltd.",FTSC,2013-07-23 +"Freescale, Inc",FRSC,2010-01-01 +"Fuzhou Rockchip Electronics Co., Ltd.",RKCP,2015-07-20 +"Google, Inc.",GOOG,2013-12-05 +"Hewlett-Packard Company",HPQC,2012-11-26 +"Hewlett Packard Enterprise",HWPE,2015-01-15 +"Himax Technologies, Inc.",HIMX,2014-03-19 +"HiSilicon Technologies Co., Ltd.",HISI,2014-11-14 +"HP Inc.",HPIC,2015-01-15 +"HTBLuVA Mödling",HTLM,2014-02-18 +IBM,IBMX,2012-11-15 +Impinj,IMPJ,2012-08-14 +"Inphi Corporation",IPHI,2014-07-15 +"Intel Corporation",ACPI,2011-11-18 +"Intel Corporation",INTC,2010-01-01 +"Intel Corporation",INTL,2010-01-01 +"Invensense, Inc",INVN,2012-02-09 +"IP3 Technology Ltd.",IP3T,2013-11-11 +"Kionix, Inc.",KIOX,2013-12-23 +"Lenovo Beijing Co. Ltd.",IDEA,2012-05-22 +"Linaro, Ltd.",LNRO,2013-11-26 +"Microsoft Corporation",MSAY,2012-03-01 +"Microsoft Corporation",MSFT,2010-01-01 +"Microsoft Corporation",MSHW,2011-01-10 +"MIPI Alliance",MIPI,2015-04-17 +"Nuvoton Technology Corporation",NVTN,2014-11-14 +Nvidia,NVDA,2010-01-01 +"OmniVision Technologies, Inc.",OVTI,2014-02-26 +"Pegatron Corporation",PEGA,2013-08-27 +"Qualcomm Inc",QCOM,2010-01-01 +"REALTEK Semiconductor Corp.",OBDA,2013-11-07 +"Red Hat, Inc.",QEMU,2015-07-30 +"Robert Bosch GmbH",BOSC,2014-05-16 +"Rozsnyó, s.r.o.",RZSN,2014-03-24 +"Sharp Corporation",SHRP,2015-01-27 +"Shenzhen DSO Microelectronics Co.,Ltd.",DSUO,2013-10-10 +"Shenzhen South-Top Computer Co., Ltd.",ST86,2013-12-06 +"Shenzhen three Connaught Information Technology Co., Ltd. (3nod Group)",3NOD,2013-09-23 +"Sierra Wireless",SWEM,2013-01-22 +"Sony Corporation",SONY,2012-09-12 +"Synaptics Inc",SYNA,2011-11-17 +"Teracue AG",TCAG,2012-12-07 +"Texas Instruments",TXNW,2010-01-01 +"The Linux Foundation",LNUX,2014-04-04 +"Dynabook Inc.",TOSB,2015-07-07 +"VAIO Corporation",VAIO,2014-04-18 +"Validity Sensors, Inc",VFSI,2013-06-17 +Wacom,WCOM,2011-11-17 +"Winsider Seminars & Solutions Inc.",WSDR,2015-07-07 +"Maxim Integrated",MXIM,2015-10-16 +"Xiaomi Inc.",XMCC,2015-12-08 +"u-blox AG",UBLX,2015-12-08 +"Raydium Semiconductor Corporation",RAYD,2016-04-13 +"Dialog Semiconductor PLC",DLGS,2016-04-27 +OmniPreSense,OMPS,2016-05-25 +"CoreOS, Inc",CORE,2016-07-01 +"Microchip Technology Inc",MCHP,2016-08-03 +"Green Hills Software",GHSW,2016-08-11 +"AAEON Technology Inc.",AANT,2016-09-01 +"VR Technology Holdings Limited",3GVR,2017-01-19 +"Exar Corporation",EXAR,2017-02-28 +"Coreboot Project",BOOT,2017-02-28 +"Marvell Technology Group Ltd.",MRVL,2017-05-25 +"IHSE GmbH",IHSE,2017-06-22 +"Insyde Software",INSY,2017-11-10 +"Nexstgo Company Limited",NXGO,2017-11-13 +"Ampere Computing",AMPC,2018-03-29 +IDEMIA,IDEM,2018-06-26 +"Vishay Intertechnology, Inc.",VSHY,2018-07-09 +"DMIST RESEARCH LTD",DMST,2018-07-09 +"COMHEAR, INC.",CMHR,2018-08-02 +"Sensel, Inc.",SNSL,2018-08-20 +"G2touch Co., LTD",GTCH,2018-12-04 +"Guizhou Huaxintong Semiconductor Technology Co., Ltd",HXTS,2019-01-18 +"Amazon Corporation",AMZN,2019-02-06 +"ASEM S.p.A.",ASEM,2019-04-29 +"Fujitsu Limited",FUJI,2019-06-18 +"Phytium Technology Co. Ltd.",PHYT,2020-02-14 +"CHENGDU HAIGUANG IC DESIGN CO., LTD",HYGO,2020-07-15 +"PixArt imaging inc.",PIXA,2020-07-15 +"Loongson Technology Corporation Limited",LOON,2020-09-10 +"Seiko Epson Corporation",SECC,2021-02-16 +"Alibaba Co., Ltd.",BABA,2021-02-02 +"Juniper Systems, Inc.",JSYS,2021-03-18 +"Framework Computer LLC",FRMW,2021-03-22 +"Pensando Systems, Inc.",PNSO,2021-03-24 +"Dynabook Inc.",DNBK,2021-06-01 +"Dioo Microcircuits Co., Ltd. Jiangsu",DIOO,2021-06-04 +"Purism SPC",PURI,2021-06-10 +"Lontium Semiconductor Corporation",LTSC,2021-07-21 +"Wacom Technology",WACF,2021-09-21 +"Shanghai Aiwei Electronic Technology Co., Ltd.",AWDZ,2021-12-31 +"Silicom Ltd. Connectivity Solutions",SILC,2022-03-28 +"NOLO Co., Ltd.",NOLO,2022-03-28 +"GoUp Co.,Ltd",GOUP,2022-06-24 +"Shenzhen Jaguar Microsystems Co.,Ltd.",JMIC,2022-09-23 +"Elliptic Laboratories AS",ELAS,2022-10-20 +"Micro Crystal AG",MCRY,2022-11-10 +"Cix Technology (Shanghai) Co., Ltd.",CIXH,2022-11-16 +"EyeTech Digital Systems",ETDS,2022-11-29 +"Raspberry Pi",RPIL,2023-02-22 +"Shenzhen Istaric Technology Co., Ltd.",ISIC,2023-04-24 +"Cirque Corporation",CIRQ,2023-05-09 +"Rivos Inc.",RVOS,2023-06-07 +"Beijer Electronics AB",BECS,2023-06-07 +"ILI Technology Corp",ILIT,2023-06-20 +"Hangzhou hj-micro Technology Co., Ltd",HJMC,2023-07-31 +"Vervent Audio Group",NAIM,2024-01-04 +"Das U-Boot",UBOO,2024-02-14 +3mdeb,DSHR,2024-06-13 +"SigmaSense, LLC",SGSN,2024-06-13 +"INIT - Innovative Informatikanwendungen GmbH",INIT,2024-08-28 +"RISC-V International",RSCV,2023-10-23 +"Ventana Micro Systems",VNTN,2024-09-16 +"DreamBig Semiconductor Inc.",DBSI,2025-01-28 +"Wuxi Institute of Advanced Technology",SUNW,2025-01-28 +"Sophgo Technologies Ltd.",SOPH,2025-04-07 +"SmartSens Technology (Shanghai) CO., Ltd.",SSLC,2025-04-07 +"Areus GmbH",AREU,2025-04-07 +"Rockwell Automation, Inc",ROKL,2025-04-18 +"JUMPtec GmbH",JUMP,2025-04-22 +"Fsas Technologies Inc.",FSAS,2025-04-30 +"JP Morgan Chase N.A.",JPMC,2025-05-30 +"Roku, Inc.",ROKU,2025-07-15 +"UltraRISC Technology (Shanghai) Co., Ltd",ULRV,2025-09-15 +"SYNCS / Aviot Systems Pte Ltd",SYNC,2025-10-21 +"Advantech Co., Ltd.",AHCL,2025-10-23 +"Picoheart (SG) Pte. Ltd.",PICO,2025-10-30 +"Kontron France",KOMF,2025-12-09 +"Ubiquiti Inc.",UBTI,2025-12-10 +"KAYA N CO., LTD.",KAYA,2026-01-06 +Mesiontech,MITH,2026-01-30 +"Nexthop Systems Inc.",NXHP,2026-02-23 +"Megapolis-Telecom Region LLC",MPTR,2026-02-23 +"Nanjing Tianyihexin Electronics Ltd",TYHX,2026-02-27 +"Theo End (Shenzhen) Computing Technology Co., Ltd.",LECA,2026-02-27 +SIPEARL,SIPL,2026-04-08 +"Green Hippo",GHIP,2026-06-01 \ No newline at end of file diff --git a/hwdb.d/ids_parser.py b/hwdb.d/ids_parser.py index ed2c615508def..d517f6b9090ac 100755 --- a/hwdb.d/ids_parser.py +++ b/hwdb.d/ids_parser.py @@ -3,11 +3,24 @@ import re import sys -from pyparsing import (Word, White, Literal, Regex, - LineEnd, SkipTo, - ZeroOrMore, OneOrMore, Combine, Optional, Suppress, - Group, ParserElement, - stringEnd, pythonStyleComment) + +from pyparsing import ( + Combine, + Group, + LineEnd, + Literal, + OneOrMore, + Optional, + ParserElement, + Regex, + SkipTo, + Suppress, + White, + Word, + ZeroOrMore, + pythonStyleComment, + stringEnd, +) EOL = LineEnd().suppress() NUM1 = Word('0123456789abcdefABCDEF', exact=1) @@ -18,11 +31,16 @@ TAB = White('\t', exact=1).suppress() COMMENTLINE = pythonStyleComment + EOL EMPTYLINE = LineEnd() -text_eol = lambda name: Regex(r'[^\n]+')(name) + EOL ParserElement.setDefaultWhitespaceChars(' \n') + +def text_eol(name): + return Regex(r'[^\n]+')(name) + EOL + + def klass_grammar(): + # fmt: off klass_line = Literal('C ').suppress() + NUM2('klass') + text_eol('text') subclass_line = TAB + NUM2('subclass') + text_eol('text') protocol_line = TAB + TAB + NUM2('protocol') + text_eol('name') @@ -32,9 +50,12 @@ def klass_grammar(): klass = (klass_line('KLASS') - ZeroOrMore(Group(subclass)('SUBCLASSES*') ^ COMMENTLINE.suppress())) + # fmt: on return klass + def usb_ids_grammar(): + # fmt: off vendor_line = NUM4('vendor') + text_eol('text') device_line = TAB + NUM4('device') + text_eol('text') interface_line = TAB + TAB + NUM4('interface') + NUM4('interface2') + text_eol('text') @@ -55,11 +76,14 @@ def usb_ids_grammar(): grammar = OneOrMore(Group(vendor)('VENDORS*') ^ Group(klass)('CLASSES*') ^ other_group.suppress() ^ commentgroup) + stringEnd() + # fmt: on grammar.parseWithTabs() return grammar + def pci_ids_grammar(): + # fmt: off vendor_line = NUM4('vendor') + text_eol('text') device_line = TAB + NUM4('device') + text_eol('text') subvendor_line = TAB + TAB + NUM4('a') + White(' ') + NUM4('b') + text_eol('name') @@ -75,11 +99,14 @@ def pci_ids_grammar(): grammar = OneOrMore(Group(vendor)('VENDORS*') ^ Group(klass)('CLASSES*') ^ commentgroup) + stringEnd() + # fmt: on grammar.parseWithTabs() return grammar + def sdio_ids_grammar(): + # fmt: off vendor_line = NUM4('vendor') + text_eol('text') device_line = TAB + NUM4('device') + text_eol('text') vendor = (vendor_line('VENDOR') + @@ -91,11 +118,14 @@ def sdio_ids_grammar(): grammar = OneOrMore(Group(vendor)('VENDORS*') ^ Group(klass)('CLASSES*') ^ commentgroup) + stringEnd() + # fmt: on grammar.parseWithTabs() return grammar + def oui_grammar(type): + # fmt: off prefix_line = (Combine(NUM2 - Suppress('-') - NUM2 - Suppress('-') - NUM2)('prefix') - Literal('(hex)') - text_eol('text')) if type == 'small': @@ -115,18 +145,23 @@ def oui_grammar(type): grammar = (Literal('OUI') + text_eol('header') + text_eol('header') + text_eol('header') + EMPTYLINE + OneOrMore(Group(vendor)('VENDORS*')) + stringEnd()) + # fmt: on grammar.parseWithTabs() return grammar def header(file, *sources): - print('''\ + sep = ' ' if len(sources) == 1 else '\n# ' + joined = sep + sep.join(sources) + print( + f'''\ # This file is part of systemd. # -# Data imported from:{}{}'''.format(' ' if len(sources) == 1 else '\n# ', - '\n# '.join(sources)), - file=file) +# Data imported from:{joined}''', + file=file, + ) + def add_item(items, key, value): if key in items: @@ -134,6 +169,7 @@ def add_item(items, key, value): else: items[key] = value + def usb_vendor_model(p): items = {} @@ -147,19 +183,19 @@ def usb_vendor_model(p): text = vendor_dev.text.strip() add_item(items, (vendor, device), text) - with open('20-usb-vendor-model.hwdb', 'wt') as out: + with open('20-usb-vendor-model.hwdb', 'w') as out: header(out, 'http://www.linux-usb.org/usb.ids') for key in sorted(items): if len(key) == 1: p, n = 'usb:v{}*', 'VENDOR' else: - p, n = 'usb:v{}p{}*', 'MODEL', - print('', p.format(*key), - f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + p, n = 'usb:v{}p{}*', 'MODEL' + print('', p.format(*key), f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def usb_classes(p): items = {} @@ -182,7 +218,7 @@ def usb_classes(p): if klass != '00' and not re.match(r'(\?|None|Unused)\s*$', text): add_item(items, (klass, subclass, protocol), text) - with open('20-usb-classes.hwdb', 'wt') as out: + with open('20-usb-classes.hwdb', 'w') as out: header(out, 'http://www.linux-usb.org/usb.ids') for key in sorted(items): @@ -192,11 +228,11 @@ def usb_classes(p): p, n = 'usb:v*p*d*dc{}dsc{}*', 'SUBCLASS' else: p, n = 'usb:v*p*d*dc{}dsc{}dp{}*', 'PROTOCOL' - print('', p.format(*key), - f' ID_USB_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + print('', p.format(*key), f' ID_USB_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def pci_vendor_model(p): items = {} @@ -215,12 +251,12 @@ def pci_vendor_model(p): sub_model = subvendor_group.b.upper() sub_text = subvendor_group.name.strip() if sub_text.startswith(text): - sub_text = sub_text[len(text):].lstrip() + sub_text = sub_text[len(text) :].lstrip() if sub_text: sub_text = f' ({sub_text})' add_item(items, (vendor, device, sub_vendor, sub_model), text + sub_text) - with open('20-pci-vendor-model.hwdb', 'wt') as out: + with open('20-pci-vendor-model.hwdb', 'w') as out: header(out, 'http://pci-ids.ucw.cz/v2.2/pci.ids') for key in sorted(items): @@ -230,11 +266,11 @@ def pci_vendor_model(p): p, n = 'pci:v0000{}d0000{}*', 'MODEL' else: p, n = 'pci:v0000{}d0000{}sv0000{}sd0000{}*', 'MODEL' - print('', p.format(*key), - f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + print('', p.format(*key), f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def pci_classes(p): items = {} @@ -253,7 +289,7 @@ def pci_classes(p): text = protocol_group.name.strip() add_item(items, (klass, subclass, protocol), text) - with open('20-pci-classes.hwdb', 'wt') as out: + with open('20-pci-classes.hwdb', 'w') as out: header(out, 'http://pci-ids.ucw.cz/v2.2/pci.ids') for key in sorted(items): @@ -263,11 +299,11 @@ def pci_classes(p): p, n = 'pci:v*d*sv*sd*bc{}sc{}*', 'SUBCLASS' else: p, n = 'pci:v*d*sv*sd*bc{}sc{}i{}*', 'INTERFACE' - print('', p.format(*key), - f' ID_PCI_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + print('', p.format(*key), f' ID_PCI_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def sdio_vendor_model(p): items = {} @@ -281,7 +317,7 @@ def sdio_vendor_model(p): text = device_group.text.strip() add_item(items, (vendor, device), text) - with open('20-sdio-vendor-model.hwdb', 'wt') as out: + with open('20-sdio-vendor-model.hwdb', 'w') as out: header(out, 'hwdb.d/sdio.ids') for key in sorted(items): @@ -289,11 +325,11 @@ def sdio_vendor_model(p): p, n = 'sdio:c*v{}*', 'VENDOR' else: p, n = 'sdio:c*v{}d{}*', 'MODEL' - print('', p.format(*key), - f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + print('', p.format(*key), f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def sdio_classes(p): items = {} @@ -302,16 +338,21 @@ def sdio_classes(p): text = klass_group.text.strip() add_item(items, klass, text) - with open('20-sdio-classes.hwdb', 'wt') as out: + with open('20-sdio-classes.hwdb', 'w') as out: header(out, 'hwdb.d/sdio.ids') for klass in sorted(items): - print(f'', - f'sdio:c{klass}v*d*', - f' ID_SDIO_CLASS_FROM_DATABASE={items[klass]}', sep='\n', file=out) + print( + '', + f'sdio:c{klass}v*d*', + f' ID_SDIO_CLASS_FROM_DATABASE={items[klass]}', + sep='\n', + file=out, + ) print(f'Wrote {out.name}') + # MAC Address Block Large/Medium/Small # Large MA-L 24/24 bit (OUI) # Medium MA-M 28/20 bit (OUI prefix owned by IEEE) @@ -338,19 +379,20 @@ def oui(p1, p2, p3): key = prefix + start if end else prefix add_item(items, key, text) - with open('20-OUI.hwdb', 'wt') as out: - header(out, - 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-L&format=txt', - 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-M&format=txt', - 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-S&format=txt') + with open('20-OUI.hwdb', 'w') as out: + header( + out, + 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-L&format=txt', + 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-M&format=txt', + 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-S&format=txt', + ) for pattern in sorted(items): - print(f'', - f'OUI:{pattern}*', - f' ID_OUI_FROM_DATABASE={items[pattern]}', sep='\n', file=out) + print('', f'OUI:{pattern}*', f' ID_OUI_FROM_DATABASE={items[pattern]}', sep='\n', file=out) print(f'Wrote {out.name}') + if __name__ == '__main__': args = sys.argv[1:] diff --git a/hwdb.d/ma-large.txt b/hwdb.d/ma-large.txt index 50bcdf32fb476..0c1fdbe2b7ee9 100644 --- a/hwdb.d/ma-large.txt +++ b/hwdb.d/ma-large.txt @@ -80,12 +80,6 @@ B42D56 (base 16) Extreme Networks Headquarters Bloomington MN 55438 US -B0-5B-99 (hex) Sagemcom Broadband SAS -B05B99 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - E8-0A-B9 (hex) Cisco Systems, Inc E80AB9 (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -143,12 +137,6 @@ B84C87 (base 16) IEEE Registration Authority E4-F1-4C (hex) Private E4F14C (base 16) Private -10-06-1C (hex) Espressif Inc. -10061C (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - E0-06-30 (hex) HUAWEI TECHNOLOGIES CO.,LTD E00630 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -389,12 +377,6 @@ F42E48 (base 16) zte corporation shenzhen guangdong 518057 CN -AC-D7-5B (hex) Sagemcom Broadband SAS -ACD75B (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - B4-C2-E0 (hex) Bouffalo Lab (Nanjing) Co., Ltd. B4C2E0 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China @@ -851,18 +833,6 @@ C4799F (base 16) Haiguang Smart Device Co.,Ltd. seoul 02797 KR -68-DD-B7 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -68DDB7 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - -14-D8-64 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -14D864 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - B8-51-A9 (hex) Nokia B851A9 (base 16) Nokia 600 March Road @@ -2357,12 +2327,6 @@ C4A559 (base 16) IEEE Registration Authority Reno NV 89507 US -CC-00-F1 (hex) Sagemcom Broadband SAS -CC00F1 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 30-82-16 (hex) Apple, Inc. 308216 (base 16) Apple, Inc. 1 Infinite Loop @@ -2465,12 +2429,6 @@ C828E5 (base 16) Cisco Systems, Inc San Jose CA 94568 US -B0-FC-88 (hex) Sagemcom Broadband SAS -B0FC88 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 34-87-3D (hex) Quectel Wireless Solutions Co.,Ltd. 34873D (base 16) Quectel Wireless Solutions Co.,Ltd. RM501,Building 13,No.99 TianZhou Road,Xuhui District,Shanghai,China @@ -2705,12 +2663,6 @@ D03957 (base 16) Liteon Technology Corporation New Taipei City Taiwan 23585 TW -48-E7-29 (hex) Espressif Inc. -48E729 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - E8-BA-E2 (hex) Xplora Technologies AS E8BAE2 (base 16) Xplora Technologies AS Nedre Slottsgate 8 @@ -3323,12 +3275,6 @@ EC1D9E (base 16) Quectel Wireless Solutions Co.,Ltd. Shanghai 200233 CN -80-64-6F (hex) Espressif Inc. -80646F (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 3C-A7-AE (hex) zte corporation 3CA7AE (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China @@ -3449,12 +3395,6 @@ B8FBAF (base 16) Xiamen IPRT Technology CO.,LTD xiamen fujian 361000 CN -34-85-18 (hex) Espressif Inc. -348518 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - E0-51-D8 (hex) China Dragon Technology Limited E051D8 (base 16) China Dragon Technology Limited B4 Bldg.Haoshan 1st Industry Park, @@ -3473,12 +3413,6 @@ ECE6A2 (base 16) Fiberhome Telecommunication Technologies Co.,LTD Wuhan Hubei 430074 CN -AC-84-C6 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -AC84C6 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 1C-0E-D3 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD 1C0ED3 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County @@ -3905,12 +3839,6 @@ CC6618 (base 16) Adtran Inc Huntsville AL 35806-2807 US -44-29-1E (hex) AltoBeam (China) Inc. -44291E (base 16) AltoBeam (China) Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN - 24-EB-ED (hex) HUAWEI TECHNOLOGIES CO.,LTD 24EBED (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -4049,24 +3977,12 @@ D4BD4F (base 16) Ruckus Wireless Beijing Beijing 100192 CN -6C-B1-58 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -6CB158 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - B0-A4-F0 (hex) HUAWEI TECHNOLOGIES CO.,LTD B0A4F0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -04-E3-1A (hex) Sagemcom Broadband SAS -04E31A (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - B4-69-5F (hex) TCT mobile ltd B4695F (base 16) TCT mobile ltd No.86 hechang 7th road, zhongkai, Hi-Tech District @@ -4451,12 +4367,6 @@ A45FB9 (base 16) DreamBig Semiconductor, Inc. San Jose CA 95134 US -34-5D-9E (hex) Sagemcom Broadband SAS -345D9E (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 00-0F-32 (hex) Lootom Telcovideo Network (Wuxi) Co Ltd 000F32 (base 16) Lootom Telcovideo Network (Wuxi) Co Ltd 5F, 9Building, @@ -4529,12 +4439,6 @@ C02B31 (base 16) Phytium Technology Co.,Ltd. Tianjin 300450 CN -58-CF-79 (hex) Espressif Inc. -58CF79 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 78-62-99 (hex) BITSTREAM sp. z o.o. 786299 (base 16) BITSTREAM sp. z o.o. Melgiewska, 7/9 @@ -4589,12 +4493,6 @@ D42C46 (base 16) BUFFALO.INC Nagoya Aichi Pref. 460-8315 JP -10-91-A8 (hex) Espressif Inc. -1091A8 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 78-04-7A (hex) Edge Networks LLC 78047A (base 16) Edge Networks LLC 14 Whistler Hill Lane @@ -4613,12 +4511,6 @@ B03CDC (base 16) Intel Corporate Shenzhen Guangdong 518000 CN -7C-16-89 (hex) Sagemcom Broadband SAS -7C1689 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 5C-83-CD (hex) New platforms 5C83CD (base 16) New platforms Warshavskoe shosse, 35 bld 1 @@ -4703,12 +4595,6 @@ C47D9F (base 16) Samsung Electronics Co.,Ltd Gumi Gyeongbuk 730-350 KR -A4-EF-15 (hex) AltoBeam (China) Inc. -A4EF15 (base 16) AltoBeam (China) Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN - 1C-45-86 (hex) Nintendo Co.,Ltd 1C4586 (base 16) Nintendo Co.,Ltd 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU @@ -5027,12 +4913,6 @@ AC5E14 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Moscow 107564 RU -90-38-0C (hex) Espressif Inc. -90380C (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - BC-22-28 (hex) D-Link International BC2228 (base 16) D-Link International 1 Internal Business Park, #03-12,The Synergy @@ -5309,12 +5189,6 @@ EC1C5D (base 16) Siemens AG BILLERICA MA 01821 US -C8-C9-A3 (hex) Espressif Inc. -C8C9A3 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - E4-3B-C9 (hex) HISENSE VISUAL TECHNOLOGY CO.,LTD E43BC9 (base 16) HISENSE VISUAL TECHNOLOGY CO.,LTD Qianwangang Road 218 @@ -5375,12 +5249,6 @@ BC062D (base 16) Wacom Co.,Ltd. Sheffield S1 2BJ GB -8C-4B-14 (hex) Espressif Inc. -8C4B14 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - D8-80-83 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. D88083 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China @@ -5399,12 +5267,6 @@ FC4265 (base 16) Zhejiang Tmall Technology Co., Ltd. London UK N7 9AH GB -44-D4-54 (hex) Sagemcom Broadband SAS -44D454 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 68-87-C6 (hex) Cisco Systems, Inc 6887C6 (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -5609,12 +5471,6 @@ FC58DF (base 16) Interphone Service Anyang-si Gyeonggi-do 14057 KR -38-A6-59 (hex) Sagemcom Broadband SAS -38A659 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 78-13-05 (hex) IEEE Registration Authority 781305 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -5663,12 +5519,6 @@ F44637 (base 16) Intel Corporate Hui Zhou Guang Dong 516006 CN -A8-48-FA (hex) Espressif Inc. -A848FA (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 50-5D-7A (hex) zte corporation 505D7A (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China @@ -5981,12 +5831,6 @@ E44E2D (base 16) Cisco Systems, Inc San Jose CA 94568 US -98-42-65 (hex) Sagemcom Broadband SAS -984265 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - DC-21-5C (hex) Intel Corporate DC215C (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -6023,12 +5867,6 @@ C42360 (base 16) Intel Corporate Chongqing China 401120 CN -9C-1C-37 (hex) AltoBeam (China) Inc. -9C1C37 (base 16) AltoBeam (China) Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN - 2C-08-23 (hex) Sercomm France Sarl 2C0823 (base 16) Sercomm France Sarl 2/4 Rue Maurice Hartmann 92370 Issy Les Moulineaux France @@ -6131,12 +5969,6 @@ F84CDA (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -BC-FF-4D (hex) Espressif Inc. -BCFF4D (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - A4-05-6E (hex) Tiinlab Corporation A4056E (base 16) Tiinlab Corporation 35F,Tower A,Tanglang City,3333 Liuxian Avenue,Nanshan District @@ -6239,12 +6071,6 @@ A439B6 (base 16) SHENZHEN PEIZHE MICROELECTRONICS CO .LTD Calabasas CA 91302 US -C4-5B-BE (hex) Espressif Inc. -C45BBE (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 00-26-18 (hex) ASUSTek COMPUTER INC. 002618 (base 16) ASUSTek COMPUTER INC. 15,Li-Te Rd.,Peitou,Taipei 112 ,Taiwan @@ -6323,12 +6149,6 @@ ECC5D2 (base 16) Huawei Device Co., Ltd. Beijing Haidian District 100085 CN -84-1E-A3 (hex) Sagemcom Broadband SAS -841EA3 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 8C-2A-8E (hex) DongGuan Ramaxel Memory Technology 8C2A8E (base 16) DongGuan Ramaxel Memory Technology No.32, Industrial East Road,Innovation Park, High-tech Industrial Development Zone, Songshan Lake, Dongguan City, Guangdong Province,China @@ -6377,12 +6197,6 @@ F40223 (base 16) PAX Computer Technology(Shenzhen) Ltd. Kulim Kedah 09000 MY -08-10-86 (hex) NEC Platforms, Ltd. -081086 (base 16) NEC Platforms, Ltd. - 2-3 Kandatsukasamachi - Chiyodaku Tokyo 101-8532 - JP - 78-65-3B (hex) Shaoxing Ourten Electronics Co., Ltd. 78653B (base 16) Shaoxing Ourten Electronics Co., Ltd. 3rd Floor # 7, No. 1732 Yanhua industrial park West Renmin Road,Shangyu @@ -6443,12 +6257,6 @@ FC584A (base 16) xiamenshi c-chip technology co., ltd Hangzhou Zhejiang 310052 CN -24-69-68 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -246968 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - FC-A9-DC (hex) Renesas Electronics (Penang) Sdn. Bhd. FCA9DC (base 16) Renesas Electronics (Penang) Sdn. Bhd. Phase 3, Bayan Lepas FIZ @@ -6749,12 +6557,6 @@ F4B301 (base 16) Intel Corporate Kulim Kedah 09000 MY -E8-D2-FF (hex) Sagemcom Broadband SAS -E8D2FF (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 0C-96-CD (hex) MERCURY CORPORATION 0C96CD (base 16) MERCURY CORPORATION 90, Gajaeul-ro, Seo-gu @@ -6863,12 +6665,6 @@ C4D438 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Tokyo Japan 141-8610 JP -44-D4-53 (hex) Sagemcom Broadband SAS -44D453 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - DC-A1-20 (hex) Nokia DCA120 (base 16) Nokia 600 March Road @@ -7043,12 +6839,6 @@ B456E3 (base 16) Apple, Inc. Hong Kong KOWLOON 999077 HK -B0-BB-E5 (hex) Sagemcom Broadband SAS -B0BBE5 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 1C-9F-4E (hex) COOSEA GROUP (HK) COMPANY LIMITED 1C9F4E (base 16) COOSEA GROUP (HK) COMPANY LIMITED UNIT 5-6 16/F MULTIFIELD PLAZA 3-7A PRAT AVENUE TSIMSHATSUI @@ -7271,12 +7061,6 @@ E433AE (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD Wuhan Hubei 430074 CN -8C-CE-4E (hex) Espressif Inc. -8CCE4E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 40-B5-C1 (hex) Cisco Systems, Inc 40B5C1 (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -8045,12 +7829,6 @@ D8714D (base 16) Texas Instruments Dallas TX 75243 US -70-03-9F (hex) Espressif Inc. -70039F (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 70-9F-2D (hex) zte corporation 709F2D (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China @@ -8483,18 +8261,6 @@ F419E2 (base 16) Volterra Santa Clara CA 95054 US -D8-07-B6 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -D807B6 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - -64-6E-97 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -646E97 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 6C-0D-34 (hex) Nokia 6C0D34 (base 16) Nokia 600 March Road @@ -8777,12 +8543,6 @@ D83BBF (base 16) Intel Corporate Reno NV 89507 US -FC-F5-C4 (hex) Espressif Inc. -FCF5C4 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - E8-26-B6 (hex) Companies House to GlucoRx Technologies Ltd. E826B6 (base 16) Companies House to GlucoRx Technologies Ltd. Strathpeffer Road @@ -8837,12 +8597,6 @@ E826B6 (base 16) Companies House to GlucoRx Technologies Ltd. Suzhou Jiangsu 215412 CN -30-93-BC (hex) Sagemcom Broadband SAS -3093BC (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - F4-FE-FB (hex) Samsung Electronics Co.,Ltd F4FEFB (base 16) Samsung Electronics Co.,Ltd 129, Samsung-ro, Youngtongl-Gu @@ -8885,12 +8639,6 @@ F86FDE (base 16) Shenzhen Goodix Technology Co.,Ltd. Reno NV 89507 US -64-66-24 (hex) Sagemcom Broadband SAS -646624 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - B0-F5-30 (hex) Hitron Technologies. Inc B0F530 (base 16) Hitron Technologies. Inc No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C @@ -9239,12 +8987,6 @@ CCEF03 (base 16) Hunan Keyshare Communication Technology Co., Ltd. Changsha Hunan 410205 CN -EC-BE-DD (hex) Sagemcom Broadband SAS -ECBEDD (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 88-57-1D (hex) Seongji Industry Company 88571D (base 16) Seongji Industry Company 54-33, Dongtanhana 1-gil @@ -9515,12 +9257,6 @@ CCCD64 (base 16) SM-Electronic GmbH KYOTO KYOTO 601-8501 JP -F8-08-4F (hex) Sagemcom Broadband SAS -F8084F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 18-02-AE (hex) vivo Mobile Communication Co., Ltd. 1802AE (base 16) vivo Mobile Communication Co., Ltd. #283,BBK Road @@ -9725,18 +9461,6 @@ F8ADCB (base 16) HMD Global Oy Espoo 02600 FI -D0-37-45 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -D03745 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - -60-3A-7C (hex) TP-LINK TECHNOLOGIES CO.,LTD. -603A7C (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 00-01-78 (hex) MARGI Systems, Inc. 000178 (base 16) MARGI Systems, Inc. 3155 Kearney Street. - Ste.#200 @@ -10037,12 +9761,6 @@ C82C2B (base 16) IEEE Registration Authority Piscataway NJ 08554 US -80-20-DA (hex) Sagemcom Broadband SAS -8020DA (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - D4-20-B0 (hex) Mist Systems, Inc. D420B0 (base 16) Mist Systems, Inc. 1601 South De Anza Blvd, Suite 248 @@ -10289,24 +10007,12 @@ D8BC59 (base 16) Shenzhen DAPU Microelectronics Co., Ltd Suwon Gyeonggi-Do 16677 KR -24-6F-28 (hex) Espressif Inc. -246F28 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 00-12-2A (hex) VTech Telecommunications Ltd. 00122A (base 16) VTech Telecommunications Ltd. 23/F, Tai Ping Industrial Centre, Block 1 NA 00000 HK -00-04-22 (hex) Studio Technologies, Inc -000422 (base 16) Studio Technologies, Inc - 7440 Frontage Rd - Skokie IL 60077-3212 - US - 80-DA-13 (hex) eero inc. 80DA13 (base 16) eero inc. 660 3rd Street @@ -10568,12 +10274,6 @@ A8E2C1 (base 16) Texas Instruments DONG GUAN GUANG DONG 523860 CN -84-A0-6E (hex) Sagemcom Broadband SAS -84A06E (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 88-4A-18 (hex) Opulinks 884A18 (base 16) Opulinks F 28, No.328, Huashan Rd @@ -11156,12 +10856,6 @@ E0795E (base 16) Wuxi Xiaohu Technology Co.,Ltd. Taipei Taiwan 112 TW -E8-AD-A6 (hex) Sagemcom Broadband SAS -E8ADA6 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - D0-B6-0A (hex) Xingluo Technology Company Limited D0B60A (base 16) Xingluo Technology Company Limited 28F, Building A, Aerospace Science And Technology Square, Nanshan District @@ -11372,12 +11066,6 @@ F8D9B8 (base 16) Open Mesh, Inc. Wusha,Chang'An DongGuan City,Guangdong, 523860 CN -D8-7D-7F (hex) Sagemcom Broadband SAS -D87D7F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - EC-C4-0D (hex) Nintendo Co.,Ltd ECC40D (base 16) Nintendo Co.,Ltd 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU @@ -11396,12 +11084,6 @@ ECC40D (base 16) Nintendo Co.,Ltd Suzhou Jiangsu 215021 CN -2C-79-D7 (hex) Sagemcom Broadband SAS -2C79D7 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 00-B4-F5 (hex) DongGuan Siyoto Electronics Co., Ltd 00B4F5 (base 16) DongGuan Siyoto Electronics Co., Ltd Hecheng Industrial District, QiaoTou Town @@ -12194,12 +11876,6 @@ F85C4D (base 16) Nokia Westford MA 01886-4113 US -38-35-FB (hex) Sagemcom Broadband SAS -3835FB (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 00-01-AE (hex) Trex Enterprises 0001AE (base 16) Trex Enterprises 590 Lipoa Parkway @@ -12584,12 +12260,6 @@ B0ACD2 (base 16) zte corporation San Jose CA 94568 US -6C-E4-DA (hex) NEC Platforms, Ltd. -6CE4DA (base 16) NEC Platforms, Ltd. - 2-3 Kandatsukasamachi - Chiyodaku Tokyo 101-8532 - JP - E4-C4-83 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD E4C483 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD NO.18 HAIBIN ROAD, @@ -13028,12 +12698,6 @@ F8B568 (base 16) IEEE Registration Authority Santa Clara CA 95054 US -B0-98-2B (hex) Sagemcom Broadband SAS -B0982B (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 34-FA-9F (hex) Ruckus Wireless 34FA9F (base 16) Ruckus Wireless 350 West Java Drive @@ -13100,12 +12764,6 @@ E820E2 (base 16) HUMAX Co., Ltd. Seongnam-si Gyeonggi-do 463-875 KR -18-90-D8 (hex) Sagemcom Broadband SAS -1890D8 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 88-83-5D (hex) FN-LINK TECHNOLOGY LIMITED 88835D (base 16) FN-LINK TECHNOLOGY LIMITED No.8, Litong Road, Liuyang Economic & Technical Development Zone @@ -13520,12 +13178,6 @@ E81DA8 (base 16) Ruckus Wireless Shanghai 200000 CN -68-C6-3A (hex) Espressif Inc. -68C63A (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 40-8B-F6 (hex) Shenzhen TCL New Technology Co., Ltd 408BF6 (base 16) Shenzhen TCL New Technology Co., Ltd TCL Building, #5 Central Nanhai Road, Nanshan District, @@ -14804,12 +14456,6 @@ F483E1 (base 16) Shanghai Clouder Semiconductor Co.,Ltd Shanghai Shanghai 200336 CN -08-3E-5D (hex) Sagemcom Broadband SAS -083E5D (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 54-03-84 (hex) Hongkong Nano IC Technologies Co., Ltd 540384 (base 16) Hongkong Nano IC Technologies Co., Ltd Rm. 19C, Lockhart Ctr., 301-307 Lockhart Rd., Wan Chai, Hong Kong @@ -15731,12 +15377,6 @@ E4C1F1 (base 16) SHENZHEN SPOTMAU INFORMATION TECHNOLIGY CO., Ltd Shenzhen Guangdong 518057 CN -24-0A-C4 (hex) Espressif Inc. -240AC4 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - F0-6B-CA (hex) Samsung Electronics Co.,Ltd F06BCA (base 16) Samsung Electronics Co.,Ltd #94-1,Imsoo-Dong @@ -17681,12 +17321,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 @@ -17777,12 +17411,6 @@ A8D3F7 (base 16) Arcadyan Technology Corporation Shanghai Shanghai 201203 CN -AC-D0-74 (hex) Espressif Inc. -ACD074 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 38-E3-C5 (hex) Taicang T&W Electronics 38E3C5 (base 16) Taicang T&W Electronics 89# Jiang Nan RD @@ -18179,24 +17807,6 @@ C8F230 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD Kulim Kedah 09000 MY -00-15-56 (hex) Sagemcom Broadband SAS -001556 (base 16) Sagemcom Broadband SAS - Le Ponnant de Paris - CEDEX Paris 75512 - FR - -C0-D0-44 (hex) Sagemcom Broadband SAS -C0D044 (base 16) Sagemcom Broadband SAS - 250, Route de l'Empereur - RUEIL-MALMAISON 92500 - FR - -A0-1B-29 (hex) Sagemcom Broadband SAS -A01B29 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison Cedex Hauts de Seine 92848 - FR - 74-E1-4A (hex) IEEE Registration Authority 74E14A (base 16) IEEE Registration Authority 445 Hoes Lane @@ -18317,12 +17927,6 @@ B499BA (base 16) Hewlett Packard SAN JOSE CA 95134 US -34-8A-AE (hex) Sagemcom Broadband SAS -348AAE (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - RUEIL MALMAISON CEDEX Hauts de Seine 92848 - FR - F8-2C-18 (hex) 2Wire Inc F82C18 (base 16) 2Wire Inc 1764 Automation Parkway @@ -18365,24 +17969,6 @@ E0D7BA (base 16) Texas Instruments Dallas TX 75243 US -7C-03-D8 (hex) Sagemcom Broadband SAS -7C03D8 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - RUEIL MALMAISON CEDEX Hauts de Seine 92848 - FR - -C0-AC-54 (hex) Sagemcom Broadband SAS -C0AC54 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison HAUTS DE SEINE 92848 - FR - -2C-39-96 (hex) Sagemcom Broadband SAS -2C3996 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison HAUTS DE SEINE 92848 - FR - F0-82-61 (hex) Sagemcom Broadband SAS F08261 (base 16) Sagemcom Broadband SAS 250 route de l'Empereur @@ -22799,12 +22385,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 @@ -40088,12 +39668,6 @@ CC1E56 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -A0-55-1F (hex) Sagemcom Broadband SAS -A0551F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 4C-B9-11 (hex) Raisecom Technology CO., LTD 4CB911 (base 16) Raisecom Technology CO., LTD No. 11, East Area, No. 10 Block, East Xibeiwang Road @@ -40436,12 +40010,6 @@ D0460C (base 16) Dell Inc. Hezhou Guangxi 542800 CN -38-E1-F4 (hex) Sagemcom Broadband SAS -38E1F4 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 64-53-E0 (hex) HUAWEI TECHNOLOGIES CO.,LTD 6453E0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -40754,12 +40322,6 @@ DC54AD (base 16) Hangzhou RunZhou Fiber Technologies Co.,Ltd Hangzhou Zhejiang 310030 CN -B0-92-4A (hex) Sagemcom Broadband SAS -B0924A (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 40-DE-24 (hex) Samsung Electronics Co.,Ltd 40DE24 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong @@ -40868,12 +40430,6 @@ C4E7AE (base 16) Chengdu Meross Technology Co., Ltd. Chengdu Sichuan 610000 CN -AC-15-18 (hex) Espressif Inc. -AC1518 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 64-8F-DB (hex) Huaqin Technology Co.LTD 648FDB (base 16) Huaqin Technology Co.LTD 11th Floor,Unit 12,No.399 Keyuan Road,Pudong, Shanghai  @@ -41228,12 +40784,6 @@ C0D941 (base 16) Shenzhen VMAX Software Co., Ltd. Hangzhou Zhejiang 310052 CN -F0-9E-9E (hex) Espressif Inc. -F09E9E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 48-B3-13 (hex) Idesco Oy 48B313 (base 16) Idesco Oy Teknologiantie 9 @@ -41795,18 +41345,6 @@ EC8E77 (base 16) Intel Corporate Mebane NC 27302 US -EC-FC-2F (hex) Sagemcom Broadband SAS -ECFC2F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -60-45-CD (hex) Sagemcom Broadband SAS -6045CD (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 30-E3-A4 (hex) Intel Corporate 30E3A4 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -41927,12 +41465,6 @@ E01F34 (base 16) HMD Global Oy Dongguan Guangdong 523808 CN -D8-CF-61 (hex) Sagemcom Broadband SAS -D8CF61 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - FC-41-16 (hex) Google, Inc. FC4116 (base 16) Google, Inc. 1600 Amphitheatre Parkway @@ -42239,12 +41771,6 @@ AC1794 (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD Nagaokakyo-shi Kyoto 617-8555 JP -DC-06-75 (hex) Espressif Inc. -DC0675 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 00-60-D5 (hex) AMADA CO., LTD 0060D5 (base 16) AMADA CO., LTD 200 Ishida, Isehara-shi, @@ -42533,12 +42059,6 @@ ACF473 (base 16) iRobot Corporation xiamen Fujian 361015 CN -38-17-B1 (hex) Sagemcom Broadband SAS -3817B1 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - DC-90-09 (hex) Intel Corporate DC9009 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -43097,12 +42617,6 @@ C87023 (base 16) Altice Labs Aveiro 3810-106 PT -38-22-28 (hex) Telink Micro LLC -382228 (base 16) Telink Micro LLC - 2975 Scott Blvd #120 - Santa Clara 95054 - US - 54-27-22 (hex) Lacroix 542722 (base 16) Lacroix 17 Rue Océane @@ -43211,12 +42725,6 @@ BCF2E5 (base 16) Powerful Devices Piscataway NJ 08554 US -58-8C-81 (hex) Espressif Inc. -588C81 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 34-86-DA (hex) Honor Device Co., Ltd. 3486DA (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 @@ -43418,12 +42926,6 @@ C890A8 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. Nanjing Jiangsu 211800 CN -FC-01-2C (hex) Espressif Inc. -FC012C (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - C8-A4-CD (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. C8A4CD (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. @@ -44174,18 +43676,6 @@ F02B18 (base 16) Nanjing Jiahao Technology Co., Ltd. Hwaseong-si Gyeonggi-do 18423 KR -80-F3-DA (hex) Espressif Inc. -80F3DA (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -80-B5-4E (hex) Espressif Inc. -80B54E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - D8-4A-83 (hex) vivo Mobile Communication Co., Ltd. D84A83 (base 16) vivo Mobile Communication Co., Ltd. No.1, vivo Road, Chang'an @@ -44366,11 +43856,17 @@ A401DE (base 16) SERCOMM PHILIPPINES INC Singapore 408533 SG -A8-ED-71 (hex) Analogue Enterprises Limited -A8ED71 (base 16) Analogue Enterprises Limited - 2-6 Foo Ming Street, 2J Po Ming Building - Causeway Bay 999077 - HK +0C-8D-DB (hex) Cisco Meraki +0C8DDB (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + +CC-03-D9 (hex) Cisco Meraki +CC03D9 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US 10-D6-57 (hex) Siemens Industrial Automation Products Ltd., Chengdu 10D657 (base 16) Siemens Industrial Automation Products Ltd., Chengdu @@ -44384,11 +43880,11 @@ A8ED71 (base 16) Analogue Enterprises Limited shenzhen guangdong 518057 CN -48-C3-81 (hex) TP-Link Systems Inc. -48C381 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US +A8-ED-71 (hex) Analogue Enterprises Limited +A8ED71 (base 16) Analogue Enterprises Limited + 2-6 Foo Ming Street, 2J Po Ming Building + Causeway Bay 999077 + HK 0C-1C-31 (hex) MERCUSYS TECHNOLOGIES CO., LTD. 0C1C31 (base 16) MERCUSYS TECHNOLOGIES CO., LTD. @@ -44402,24 +43898,12 @@ A8ED71 (base 16) Analogue Enterprises Limited Shanghai 200233 CN -0C-8D-DB (hex) Cisco Meraki -0C8DDB (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US - -CC-03-D9 (hex) Cisco Meraki -CC03D9 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 +48-C3-81 (hex) TP-Link Systems Inc. +48C381 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US -F0-40-EC (hex) RainX PTE. LTD. -F040EC (base 16) RainX PTE. LTD. - 83B Tanjong Pagar Road - Singapore 088504 - SG - C8-77-F3 (hex) VusionGroup C877F3 (base 16) VusionGroup Kalsdorfer Straße 12 @@ -44462,6 +43946,18 @@ A8469D (base 16) Cisco Meraki San Francisco 94158 US +D4-24-DD (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +D424DD (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +BC-2E-C3 (hex) Shenzhen Tianruixiang Communication Equipment Co.,Ltd +BC2EC3 (base 16) Shenzhen Tianruixiang Communication Equipment Co.,Ltd + 12/F, Building B,Longhua Digital Innovation Center,Longhua District, Shenzhen,China. + Shenzhen Guangdong 518000 + CN + 38-70-F2 (hex) HUAWEI TECHNOLOGIES CO.,LTD 3870F2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -44480,56 +43976,56 @@ C4BB03 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -BC-2E-C3 (hex) Shenzhen Tianruixiang Communication Equipment Co.,Ltd -BC2EC3 (base 16) Shenzhen Tianruixiang Communication Equipment Co.,Ltd - 12/F, Building B,Longhua Digital Innovation Center,Longhua District, Shenzhen,China. - Shenzhen Guangdong 518000 - CN - -D0-12-CB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -D012CB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +98-9B-CB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +989BCB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -D4-24-DD (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -D424DD (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +D0-12-CB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +D012CB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -08-9B-F1 (hex) eero inc. -089BF1 (base 16) eero inc. +9C-57-BC (hex) eero inc. +9C57BC (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -30-34-22 (hex) eero inc. -303422 (base 16) eero inc. +84-70-D7 (hex) eero inc. +8470D7 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -9C-57-BC (hex) eero inc. -9C57BC (base 16) eero inc. +78-76-89 (hex) eero inc. +787689 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -98-9B-CB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -989BCB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE +28-EC-22 (hex) eero inc. +28EC22 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US -84-70-D7 (hex) eero inc. -8470D7 (base 16) eero inc. +C8-C6-FE (hex) eero inc. +C8C6FE (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -78-76-89 (hex) eero inc. -787689 (base 16) eero inc. +08-9B-F1 (hex) eero inc. +089BF1 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +30-34-22 (hex) eero inc. +303422 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US @@ -44648,28 +44144,22 @@ DC7CF7 (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN -28-EC-22 (hex) eero inc. -28EC22 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -C8-C6-FE (hex) eero inc. -C8C6FE (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - 30-29-2B (hex) eero inc. 30292B (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -14-08-08 (hex) Espressif Inc. -140808 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +34-09-C9 (hex) Dongguan Huayin Electronic Technology Co., Ltd. +3409C9 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. + Room 101, No.8 Xinglong 3rd Road, Shipai Town + Dongguan Guangdong 523000 + CN + +70-85-6C (hex) Ruijie Networks Co.,LTD +70856C (base 16) Ruijie Networks Co.,LTD + Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District + Fuzhou 35000 CN 8C-E4-DB (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd @@ -44684,23 +44174,11 @@ C8C6FE (base 16) eero inc. Hangzhou 311200 CN -34-09-C9 (hex) Dongguan Huayin Electronic Technology Co., Ltd. -3409C9 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. - Room 101, No.8 Xinglong 3rd Road, Shipai Town - Dongguan Guangdong 523000 - CN - -70-85-6C (hex) Ruijie Networks Co.,LTD -70856C (base 16) Ruijie Networks Co.,LTD - Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District - Fuzhou 35000 - CN - -BC-9D-37 (hex) Telink Micro LLC -BC9D37 (base 16) Telink Micro LLC - 2975 Scott Blvd #120 - Santa Clara 95054 - US +94-8E-6D (hex) Nintendo Co.,Ltd +948E6D (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP 0C-27-79 (hex) New H3C Technologies Co., Ltd 0C2779 (base 16) New H3C Technologies Co., Ltd @@ -44768,12 +44246,6 @@ B87029 (base 16) Shenzhen Ruiyuanchuangxin Technology Co.,Ltd. Shanghai Shanghai 201203 CN -94-8E-6D (hex) Nintendo Co.,Ltd -948E6D (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - C8-08-8B (hex) Arista Networks C8088B (base 16) Arista Networks 5453 Great America Parkway @@ -44822,11 +44294,11 @@ E04934 (base 16) Calix Inc. Bac Ninh 16000 VN -58-E6-C5 (hex) Espressif Inc. -58E6C5 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +CC-BA-BD (hex) TP-Link Systems Inc. +CCBABD (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US B4-5B-86 (hex) Funshion Online Technologies Co.,Ltd B45B86 (base 16) Funshion Online Technologies Co.,Ltd @@ -44858,11 +44330,11 @@ AC017A (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD Chengdu Sichuan 611330 CN -CC-BA-BD (hex) TP-Link Systems Inc. -CCBABD (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US +08-73-6F (hex) EM Microelectronic +08736F (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH 78-0F-81 (hex) Cisco Meraki 780F81 (base 16) Cisco Meraki @@ -44870,9 +44342,6 @@ CCBABD (base 16) TP-Link Systems Inc. San Francisco 94158 US -5C-E7-53 (hex) Private -5CE753 (base 16) Private - B4-1F-4D (hex) Sony Interactive Entertainment Inc. B41F4D (base 16) Sony Interactive Entertainment Inc. 1-7-1 Konan @@ -44927,12 +44396,6 @@ A02B44 (base 16) WaveGo Tech LLC Cupertino CA 95014 US -08-73-6F (hex) EM Microelectronic -08736F (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH - C8-90-09 (hex) Budderfly Inc. C89009 (base 16) Budderfly Inc. 2 Trap Falls Road @@ -44945,36 +44408,60 @@ F8F7D2 (base 16) Equal Optics, LLC Newport Beach CA 92660 US +90-4C-02 (hex) vivo Mobile Communication Co., Ltd. +904C02 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + +04-1F-B8 (hex) vivo Mobile Communication Co., Ltd. +041FB8 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + A4-7B-52 (hex) JoulWatt Technology Co., Ltd A47B52 (base 16) JoulWatt Technology Co., Ltd 9th Floor, Chuangye Building, No.99 Huaxing Road, Xihu District, Hangzhou, China Hangzhou Zhejiang 311100 CN -30-C9-CC (hex) Samsung Electronics Co.,Ltd -30C9CC (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR - -3C-C5-C7 (hex) HUAWEI TECHNOLOGIES CO.,LTD -3CC5C7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +44-09-C6 (hex) HUAWEI TECHNOLOGIES CO.,LTD +4409C6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -44-09-C6 (hex) HUAWEI TECHNOLOGIES CO.,LTD -4409C6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +3C-C5-C7 (hex) HUAWEI TECHNOLOGIES CO.,LTD +3CC5C7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN +30-C9-CC (hex) Samsung Electronics Co.,Ltd +30C9CC (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR + 04-55-B2 (hex) Huaqin Technology Co.,Ltd 0455B2 (base 16) Huaqin Technology Co.,Ltd Pudong New Area Shanghai 201203 CN +1C-D3-AF (hex) LG Innotek +1CD3AF (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR + +C8-26-E2 (hex) CHINA DRAGON TECHNOLOGY LIMITED +C826E2 (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 + FC-4C-EA (hex) Dell Inc. FC4CEA (base 16) Dell Inc. One Dell Way @@ -44987,28 +44474,16 @@ FC4CEA (base 16) Dell Inc. Santa Clara CA 95054 US -1C-D3-AF (hex) LG Innotek -1CD3AF (base 16) LG Innotek - 26, HANAMSANDAN 5BEON-RO - Gwangju Gwangsan-gu 506-731 - KR - F4-4E-B4 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. F44EB4 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China Nanning Guangxi 530007 CN -90-4C-02 (hex) vivo Mobile Communication Co., Ltd. -904C02 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN - -04-1F-B8 (hex) vivo Mobile Communication Co., Ltd. -041FB8 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +F4-AB-5C (hex) Quectel Wireless Solutions Co.,Ltd. +F4AB5C (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN 80-AE-3C (hex) Taicang T&W Electronics @@ -45023,6 +44498,30 @@ F06FCE (base 16) Ruckus Wireless Sunnyvale CA 94089 US +A0-1B-9E (hex) Samsung Electronics Co.,Ltd +A01B9E (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +D8-71-54 (hex) Samsung Electronics Co.,Ltd +D87154 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +78-33-C6 (hex) Samsung Electronics Co.,Ltd +7833C6 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +2C-C1-F4 (hex) Nokia Solutions and Networks India Private Limited +2CC1F4 (base 16) Nokia Solutions and Networks India Private Limited + Plot 45, Fathima Nagar, Nemilicherry + Chennai Tamilnadu 600044 + IN + 34-FD-70 (hex) Intel Corporate 34FD70 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -45041,40 +44540,16 @@ B07C8E (base 16) Brother Industries, LTD. NAGOYA 4678561 JP -A0-1B-D6 (hex) Nautitech Mining Systems Pty. Ltd. -A01BD6 (base 16) Nautitech Mining Systems Pty. Ltd. - 3/9 Packard AvenueCastle Hill - CASTLE HILL 2154 - AU - -90-F0-05 (hex) Xi'an Molead Technology Co., Ltd -90F005 (base 16) Xi'an Molead Technology Co., Ltd - No.34 Fenghui South Road,High-Tech Zone - Xian Shaanxi 710065 +F0-7A-55 (hex) zte corporation +F07A55 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -A0-1B-9E (hex) Samsung Electronics Co.,Ltd -A01B9E (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -D8-71-54 (hex) Samsung Electronics Co.,Ltd -D87154 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -78-33-C6 (hex) Samsung Electronics Co.,Ltd -7833C6 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -C8-26-E2 (hex) CHINA DRAGON TECHNOLOGY LIMITED -C826E2 (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 +D4-61-95 (hex) zte corporation +D46195 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN E0-D5-5D (hex) Intel Corporate @@ -45095,29 +44570,23 @@ A08527 (base 16) Intel Corporate Kulim Kedah 09000 MY -F0-7A-55 (hex) zte corporation -F07A55 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -D4-61-95 (hex) zte corporation -D46195 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +90-F0-05 (hex) Xi'an Molead Technology Co., Ltd +90F005 (base 16) Xi'an Molead Technology Co., Ltd + No.34 Fenghui South Road,High-Tech Zone + Xian Shaanxi 710065 CN -F4-AB-5C (hex) Quectel Wireless Solutions Co.,Ltd. -F4AB5C (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN +A0-1B-D6 (hex) Nautitech Mining Systems Pty. Ltd. +A01BD6 (base 16) Nautitech Mining Systems Pty. Ltd. + 3/9 Packard AvenueCastle Hill + CASTLE HILL 2154 + AU -2C-C1-F4 (hex) Nokia Solutions and Networks India Private Limited -2CC1F4 (base 16) Nokia Solutions and Networks India Private Limited - Plot 45, Fathima Nagar, Nemilicherry - Chennai Tamilnadu 600044 - IN +60-73-C8 (hex) Voyetra Turtle Beach, Inc. +6073C8 (base 16) Voyetra Turtle Beach, Inc. + 15822 Bernardo Center Drive, Suite 105 + San Diego CA 92127 + US 5C-E1-A4 (hex) Pleneo 5CE1A4 (base 16) Pleneo @@ -45131,12 +44600,6 @@ FCE498 (base 16) IEEE Registration Authority Piscataway NJ 08554 US -60-73-C8 (hex) Voyetra Turtle Beach, Inc. -6073C8 (base 16) Voyetra Turtle Beach, Inc. - 15822 Bernardo Center Drive, Suite 105 - San Diego CA 92127 - US - 24-B5-B9 (hex) Motorola Mobility LLC, a Lenovo Company 24B5B9 (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza @@ -45173,11 +44636,11 @@ ECBB78 (base 16) Cisco Systems, Inc San Jose CA 95126 US -10-2B-AA (hex) Sagemcom Broadband SAS -102BAA (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +54-9B-24 (hex) Mellanox Technologies, Inc. +549B24 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US 50-62-45 (hex) Annapurna labs 506245 (base 16) Annapurna labs @@ -45218,18 +44681,6 @@ D4532A (base 16) Beijing Xiaomi Mobile Software Co., Ltd Beijing Beijing 100085 CN -F0-57-8D (hex) JetHome LLC -F0578D (base 16) JetHome LLC - Serebristy boulevard, 21, letter A, office 79-N - St. Petersburg 197341 - RU - -78-C8-84 (hex) Huawei Device Co., Ltd. -78C884 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - 98-7E-B5 (hex) Huawei Device Co., Ltd. 987EB5 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -45242,11 +44693,11 @@ F0578D (base 16) JetHome LLC Dongguan Guangdong 523808 CN -54-9B-24 (hex) Mellanox Technologies, Inc. -549B24 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +78-C8-84 (hex) Huawei Device Co., Ltd. +78C884 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN 18-95-78 (hex) DENSO CORPORATION 189578 (base 16) DENSO CORPORATION @@ -45284,12 +44735,6 @@ F8D554 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. beijing beijing 100000 CN -00-50-CA (hex) Zhone Technologies, Inc. -0050CA (base 16) Zhone Technologies, Inc. - 680 CENTRAL AVENUE - STE. #301 - DOVER NH 03820 - US - 4C-82-0C (hex) Apple, Inc. 4C820C (base 16) Apple, Inc. 1 Infinite Loop @@ -45308,34 +44753,22 @@ F8D554 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Cupertino CA 95014 US -F4-06-3C (hex) Texas Instruments -F4063C (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - -E0-DE-F2 (hex) Texas Instruments -E0DEF2 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - 74-95-33 (hex) Westala Technologies Inc. 749533 (base 16) Westala Technologies Inc. 3333 Preston Road STE 300 FRISCO TX 75034 US -44-8D-D5 (hex) Cisco Systems, Inc -448DD5 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +F0-57-8D (hex) JetHome LLC +F0578D (base 16) JetHome LLC + Serebristy boulevard, 21, letter A, office 79-N + St. Petersburg 197341 + RU -8C-91-A4 (hex) Apple, Inc. -8C91A4 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +00-50-CA (hex) Zhone Technologies, Inc. +0050CA (base 16) Zhone Technologies, Inc. + 680 CENTRAL AVENUE - STE. #301 + DOVER NH 03820 US 94-97-4F (hex) Liteon Technology Corporation @@ -45356,11 +44789,17 @@ E0DEF2 (base 16) Texas Instruments Irvine CA 92618 US -F4-D7-E4 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -F4D7E4 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 - CN +E0-DE-F2 (hex) Texas Instruments +E0DEF2 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +44-8D-D5 (hex) Cisco Systems, Inc +448DD5 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US 20-0A-87 (hex) Guangzhou On-Bright Electronics Co., Ltd. 200A87 (base 16) Guangzhou On-Bright Electronics Co., Ltd. @@ -45368,24 +44807,30 @@ F4D7E4 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. Guangzhou Guangdong 510520 CN -BC-34-D6 (hex) Extreme Networks Headquarters -BC34D6 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US - -E0-8C-FE (hex) Espressif Inc. -E08CFE (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 38-C6-CE (hex) Nintendo Co.,Ltd 38C6CE (base 16) Nintendo Co.,Ltd 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU KYOTO KYOTO 601-8501 JP +F4-06-3C (hex) Texas Instruments +F4063C (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +8C-91-A4 (hex) Apple, Inc. +8C91A4 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +18-16-28 (hex) SharkNinja Operating LLC +181628 (base 16) SharkNinja Operating LLC + 89 A Street, Suite 100 02494 Needham + Needham MA 02494 + US + 4C-C5-D9 (hex) Dell Inc. 4CC5D9 (base 16) Dell Inc. One Dell Way @@ -45404,6 +44849,18 @@ E08CFE (base 16) Espressif Inc. Beijing Beijing 100085 CN +BC-34-D6 (hex) Extreme Networks Headquarters +BC34D6 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +CC-E4-D1 (hex) Arista Networks +CCE4D1 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US + E8-7E-EF (hex) Xiaomi Communications Co Ltd E87EEF (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road @@ -45416,17 +44873,11 @@ E87EEF (base 16) Xiaomi Communications Co Ltd Beijing Haidian District 100085 CN -18-16-28 (hex) SharkNinja Operating LLC -181628 (base 16) SharkNinja Operating LLC - 89 A Street, Suite 100 02494 Needham - Needham MA 02494 - US - -CC-E4-D1 (hex) Arista Networks -CCE4D1 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 - US +F4-D7-E4 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +F4D7E4 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 + CN 0C-9A-E6 (hex) SZ DJI TECHNOLOGY CO.,LTD 0C9AE6 (base 16) SZ DJI TECHNOLOGY CO.,LTD @@ -45434,18 +44885,6 @@ CCE4D1 (base 16) Arista Networks shenzhen Guangdong 518057 CN -88-56-A6 (hex) Espressif Inc. -8856A6 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -C0-40-8D (hex) ALPSALPINE CO,.LTD -C0408D (base 16) ALPSALPINE CO,.LTD - nishida 6-1 - Kakuda-City Miyagi-Pref 981-1595 - JP - BC-09-B9 (hex) Hui Zhou Gaoshengda Technology Co.,LTD BC09B9 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD No.2,Jin-da Road,Huinan Industrial Park @@ -45476,23 +44915,17 @@ FC8B1F (base 16) GUTOR Electronic Dongguan 523808 CN -24-FA-D4 (hex) ShenZhen More Star Technology Co.,LTD -24FAD4 (base 16) ShenZhen More Star Technology Co.,LTD - Room 301, 3F, Building 25, Keyuan West, No. 5, Kezhi West Road, Yuehai street - Shenzhen GuangDong 518000 - CN - F4-B0-FF (hex) Shanghai Baud Data Communication Co.,Ltd. F4B0FF (base 16) Shanghai Baud Data Communication Co.,Ltd. NO.123 JULI RD PUDONG ZHANGJIANG HIGH-TECH PARK SHANGHAI 201203 CN -10-2B-1C (hex) Motorola Mobility LLC, a Lenovo Company -102B1C (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +C0-40-8D (hex) ALPSALPINE CO,.LTD +C0408D (base 16) ALPSALPINE CO,.LTD + nishida 6-1 + Kakuda-City Miyagi-Pref 981-1595 + JP 04-C8-B0 (hex) Google, Inc. 04C8B0 (base 16) Google, Inc. @@ -45506,24 +44939,18 @@ D86DD0 (base 16) InnoCare Optoelectronics Tainan 74144 TW -EC-46-84 (hex) Microsoft Corporation -EC4684 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US - -D4-A7-EA (hex) Solar76 -D4A7EA (base 16) Solar76 - 400 Maple Street - Commerce TX 75428 - US - C4-D4-D0 (hex) SHENZHEN TECNO TECHNOLOGY C4D4D0 (base 16) SHENZHEN TECNO TECHNOLOGY 101,Building 24,Waijing Industrial Park,Fumin Community,Fucheng Street,Longhua District,Shenzhen City,P.R.China Shenzhen guangdong 518000 CN +24-FA-D4 (hex) ShenZhen More Star Technology Co.,LTD +24FAD4 (base 16) ShenZhen More Star Technology Co.,LTD + Room 301, 3F, Building 25, Keyuan West, No. 5, Kezhi West Road, Yuehai street + Shenzhen GuangDong 518000 + CN + 64-F2-FB (hex) Hangzhou Ezviz Software Co.,Ltd. 64F2FB (base 16) Hangzhou Ezviz Software Co.,Ltd. 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District @@ -45536,11 +44963,11 @@ C4D4D0 (base 16) SHENZHEN TECNO TECHNOLOGY Hangzhou Zhejiang 310051 CN -68-E6-D4 (hex) FURUNO SYSTEMS CO.,LTD. -68E6D4 (base 16) FURUNO SYSTEMS CO.,LTD. - 6F, JEI Ryogoku Building, 3-25-5 Ryogoku - Sumida-ku Tokyo 130-0026 - JP +10-2B-1C (hex) Motorola Mobility LLC, a Lenovo Company +102B1C (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US DC-D8-3B (hex) Cisco Systems, Inc DCD83B (base 16) Cisco Systems, Inc @@ -45548,30 +44975,54 @@ DCD83B (base 16) Cisco Systems, Inc San Jose CA 94568 US -C8-6C-9A (hex) SNUC System -C86C9A (base 16) SNUC System - 495 Round Rock West Drive - Round Rock TX 78681 - US - 90-FE-E2 (hex) ISIF 90FEE2 (base 16) ISIF Lasnamäe linnaosa, Sepapaja tn 6 Tallinn Harju maakond 15551 EE +EC-46-84 (hex) Microsoft Corporation +EC4684 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +D4-A7-EA (hex) Solar76 +D4A7EA (base 16) Solar76 + 400 Maple Street + Commerce TX 75428 + US + 6C-43-29 (hex) COSMIQ EDUSNAP PRIVATE LIMITED 6C4329 (base 16) COSMIQ EDUSNAP PRIVATE LIMITED C-185, 2nd Floor, Naraina Industrial Area, Phase 1 NEW DELHI DELHI 110028 IN +68-E6-D4 (hex) FURUNO SYSTEMS CO.,LTD. +68E6D4 (base 16) FURUNO SYSTEMS CO.,LTD. + 6F, JEI Ryogoku Building, 3-25-5 Ryogoku + Sumida-ku Tokyo 130-0026 + JP + +C8-6C-9A (hex) SNUC System +C86C9A (base 16) SNUC System + 495 Round Rock West Drive + Round Rock TX 78681 + US + 00-0E-72 (hex) Sesami Technologies Srl 000E72 (base 16) Sesami Technologies Srl via Statale 17 Bollengo Torino 10012 IT +44-39-AA (hex) G.Tech Technology Ltd. +4439AA (base 16) G.Tech Technology Ltd. + No.8,Jinyuan 1st Road,Tangjiawan Town, High-tech Zone + Zhuhai Guangdong 519085 + CN + 58-27-45 (hex) Angelbird Technologies GmbH 582745 (base 16) Angelbird Technologies GmbH Steinebach 18 @@ -45584,6 +45035,12 @@ C86C9A (base 16) SNUC System Suzhou 215021 CN +30-F6-5D (hex) Hewlett Packard Enterprise +30F65D (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + F0-3E-05 (hex) Murata Manufacturing Co., Ltd. F03E05 (base 16) Murata Manufacturing Co., Ltd. 1-10-1, Higashikotari @@ -45596,22 +45053,16 @@ B0A604 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -44-39-AA (hex) G.Tech Technology Ltd. -4439AA (base 16) G.Tech Technology Ltd. - No.8,Jinyuan 1st Road,Tangjiawan Town, High-tech Zone - Zhuhai Guangdong 519085 - CN - C0-2E-DF (hex) AltoBeam Inc. C02EDF (base 16) AltoBeam Inc. B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian Beijing Beijing 100083 CN -8C-3B-4A (hex) Universal Global Scientific Industrial., Ltd -8C3B4A (base 16) Universal Global Scientific Industrial., Ltd - 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen - Nan-Tou Taiwan 54261 +70-3E-76 (hex) Arcadyan Corporation +703E76 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 TW F4-5C-42 (hex) Huawei Device Co., Ltd. @@ -45638,6 +45089,18 @@ E4AEE4 (base 16) Tuya Smart Inc. Mannheim 68167 DE +80-48-63 (hex) Electralsys Networks +804863 (base 16) Electralsys Networks + 45 Bharat Nagar, New Friends Colony + NEW DELHI DELHI 110025 + IN + +7C-CF-4E (hex) FINE TRIUMPH TECHNOLOGY CORP.,LTD. +7CCF4E (base 16) FINE TRIUMPH TECHNOLOGY CORP.,LTD. + kihitech Industrial Park,DongChen Town, RuGao,jiangsu + RuGao,jiangsu 226571 + CN + 70-F3-95 (hex) Universal Global Scientific Industrial., Ltd 70F395 (base 16) Universal Global Scientific Industrial., Ltd 141, LANE 351,SEC.1, TAIPING RD. @@ -45656,23 +45119,23 @@ E02A82 (base 16) Universal Global Scientific Industrial., Ltd Nan-Tou Taiwan 54261 TW -30-F6-5D (hex) Hewlett Packard Enterprise -30F65D (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 - US +8C-3B-4A (hex) Universal Global Scientific Industrial., Ltd +8C3B4A (base 16) Universal Global Scientific Industrial., Ltd + 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen + Nan-Tou Taiwan 54261 + TW -64-FA-2B (hex) Sagemcom Broadband SAS -64FA2B (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +8C-19-52 (hex) Amazon Technologies Inc. +8C1952 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -70-3E-76 (hex) Arcadyan Corporation -703E76 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +04-72-EF (hex) Apple, Inc. +0472EF (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US D4-FF-1A (hex) Apple, Inc. D4FF1A (base 16) Apple, Inc. @@ -45698,12 +45161,6 @@ F4B599 (base 16) Apple, Inc. Cupertino CA 95014 US -24-6D-10 (hex) Apple, Inc. -246D10 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - A0-F7-C3 (hex) Ficosa Automotive SLU A0F7C3 (base 16) Ficosa Automotive SLU Pol. Ind. Can Mitjans,Vial San Jordi s/n @@ -45716,18 +45173,6 @@ B8FBB3 (base 16) TP-Link Systems Inc. Irvine CA 92618 US -20-46-3A (hex) Apple, Inc. -20463A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -04-72-EF (hex) Apple, Inc. -0472EF (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - 50-93-CE (hex) HUAWEI TECHNOLOGIES CO.,LTD 5093CE (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -45740,24 +45185,24 @@ B8FBB3 (base 16) TP-Link Systems Inc. Dongguan 523808 CN -80-48-63 (hex) Electralsys Networks -804863 (base 16) Electralsys Networks - 45 Bharat Nagar, New Friends Colony - NEW DELHI DELHI 110025 - IN - -7C-CF-4E (hex) FINE TRIUMPH TECHNOLOGY CORP.,LTD. -7CCF4E (base 16) FINE TRIUMPH TECHNOLOGY CORP.,LTD. - kihitech Industrial Park,DongChen Town, RuGao,jiangsu - RuGao,jiangsu 226571 - CN +24-6D-10 (hex) Apple, Inc. +246D10 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -8C-19-52 (hex) Amazon Technologies Inc. -8C1952 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +20-46-3A (hex) Apple, Inc. +20463A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US +10-E8-A7 (hex) WNC Corporation +10E8A7 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + AC-91-9B (hex) WNC Corporation AC919B (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park @@ -45782,12 +45227,24 @@ DC4BA1 (base 16) WNC Corporation Hsin-Chu R.O.C. 308 TW -74-6F-F7 (hex) WNC Corporation -746FF7 (base 16) WNC Corporation +B0-00-73 (hex) WNC Corporation +B00073 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +98-49-14 (hex) WNC Corporation +984914 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW +30-41-DB (hex) vivo Mobile Communication Co., Ltd. +3041DB (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + A8-54-B2 (hex) WNC Corporation A854B2 (base 16) WNC Corporation 20 Park Avenue II, Hsin Science Park, Hsinchu 308, Taiwan @@ -45800,29 +45257,23 @@ A854B2 (base 16) WNC Corporation Hsinchu 308 TW -B0-00-73 (hex) WNC Corporation -B00073 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - -98-49-14 (hex) WNC Corporation -984914 (base 16) WNC Corporation +74-6F-F7 (hex) WNC Corporation +746FF7 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW -10-E8-A7 (hex) WNC Corporation -10E8A7 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +2C-65-8D (hex) Cisco Systems, Inc +2C658D (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -30-41-DB (hex) vivo Mobile Communication Co., Ltd. -3041DB (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN +94-AA-07 (hex) Nokia +94AA07 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA 68-A3-4F (hex) Nokia 68A34F (base 16) Nokia @@ -45836,17 +45287,17 @@ B00073 (base 16) WNC Corporation Nanzi Dist. Kaohsiung 811643 TW -EC-79-C0 (hex) zte corporation -EC79C0 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +20-CB-CC (hex) GridVisibility, inc. +20CBCC (base 16) GridVisibility, inc. + 1502 Meeker Dr + Longmont CO 80504 + US -6C-11-BA (hex) zte corporation -6C11BA (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +F4-9A-B1 (hex) Hewlett Packard Enterprise +F49AB1 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US FC-9E-53 (hex) Intel Corporate FC9E53 (base 16) Intel Corporate @@ -45860,64 +45311,40 @@ D494A9 (base 16) Intel Corporate Kulim Kedah 09000 MY -E4-65-66 (hex) Maple IoT Solutions LLC -E46566 (base 16) Maple IoT Solutions LLC - N8408 MUIRFIELD WAY - MENASHA WI 54952 - US - -80-B2-69 (hex) Subtle Computing -80B269 (base 16) Subtle Computing - 855 El Camino Real, Suite 13A - 230 - Palo Alto CA 94301 - US - -2C-65-8D (hex) Cisco Systems, Inc -2C658D (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -94-AA-07 (hex) Nokia -94AA07 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA - 84-08-3A (hex) Intel Corporate 84083A (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY +EC-79-C0 (hex) zte corporation +EC79C0 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +6C-11-BA (hex) zte corporation +6C11BA (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + 10-16-B1 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD 1016B1 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD NO.18 HAIBIN ROAD, DONG GUAN GUANG DONG 523860 CN -20-CB-CC (hex) GridVisibility, inc. -20CBCC (base 16) GridVisibility, inc. - 1502 Meeker Dr - Longmont CO 80504 - US - -F4-9A-B1 (hex) Hewlett Packard Enterprise -F49AB1 (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 - US - -E0-FA-5B (hex) Arista Networks -E0FA5B (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 +E4-65-66 (hex) Maple IoT Solutions LLC +E46566 (base 16) Maple IoT Solutions LLC + N8408 MUIRFIELD WAY + MENASHA WI 54952 US -74-33-36 (hex) IEEE Registration Authority -743336 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +80-B2-69 (hex) Subtle Computing +80B269 (base 16) Subtle Computing + 855 El Camino Real, Suite 13A - 230 + Palo Alto CA 94301 US 40-08-77 (hex) Xiaomi Communications Co Ltd @@ -45926,16 +45353,16 @@ E0FA5B (base 16) Arista Networks Beijing Haidian District 100085 CN -7C-D4-A8 (hex) Sagemcom Broadband SAS -7CD4A8 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +08-B3-39 (hex) Xiaomi Communications Co Ltd +08B339 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -90-31-96 (hex) SHENZHEN IP-COM NETWORKS CO.,LTD. -903196 (base 16) SHENZHEN IP-COM NETWORKS CO.,LTD. - Room 101, Unit A, First Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China - SHENZHEN Guangdong Province 518052 +B8-53-84 (hex) Xiaomi Communications Co Ltd +B85384 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN CC-2D-D2 (hex) Ruckus Wireless @@ -45950,29 +45377,11 @@ B0D7DE (base 16) Hangzhou Linovision Co., Ltd. Hangzhou Zhejiang 310023 CN -18-AC-C2 (hex) TCL King Electrical Appliances(Huizhou)Co.,Ltd -18ACC2 (base 16) TCL King Electrical Appliances(Huizhou)Co.,Ltd - B Area, 10th floor, TCL multimedia Building, TCL International E City, #1001 Zhonshanyuan road,Shenzhen - guangdong China 518058 - CN - -08-B3-39 (hex) Xiaomi Communications Co Ltd -08B339 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN - -B8-53-84 (hex) Xiaomi Communications Co Ltd -B85384 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN - -00-BC-99 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -00BC99 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN +98-42-AB (hex) GN Hearing A/S +9842AB (base 16) GN Hearing A/S + Lautrupbjerg 7 + Ballerup 2750 + DK 5C-33-B1 (hex) EM Microelectronic 5C33B1 (base 16) EM Microelectronic @@ -45980,17 +45389,17 @@ B85384 (base 16) Xiaomi Communications Co Ltd Marin-Epagnier Neuchatel 2074 CH -00-15-EA (hex) Hensoldt South Africa (Pty) Ltd -0015EA (base 16) Hensoldt South Africa (Pty) Ltd - 64/74 White Road - Cape Town Western Province 7945 - ZA +E0-FA-5B (hex) Arista Networks +E0FA5B (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US -98-42-AB (hex) GN Hearing A/S -9842AB (base 16) GN Hearing A/S - Lautrupbjerg 7 - Ballerup 2750 - DK +74-33-36 (hex) IEEE Registration Authority +743336 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US 9C-CD-42 (hex) Dongguan Liesheng Electronic Co., Ltd. 9CCD42 (base 16) Dongguan Liesheng Electronic Co., Ltd. @@ -45998,16 +45407,22 @@ B85384 (base 16) Xiaomi Communications Co Ltd dongguan guangdong 523000 CN -70-4B-CA (hex) Espressif Inc. -704BCA (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +90-31-96 (hex) SHENZHEN IP-COM NETWORKS CO.,LTD. +903196 (base 16) SHENZHEN IP-COM NETWORKS CO.,LTD. + Room 101, Unit A, First Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China + SHENZHEN Guangdong Province 518052 CN -8C-FD-49 (hex) Espressif Inc. -8CFD49 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +18-AC-C2 (hex) TCL King Electrical Appliances(Huizhou)Co.,Ltd +18ACC2 (base 16) TCL King Electrical Appliances(Huizhou)Co.,Ltd + B Area, 10th floor, TCL multimedia Building, TCL International E City, #1001 Zhonshanyuan road,Shenzhen + guangdong China 518058 + CN + +00-BC-99 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +00BC99 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 CN 4C-3C-8F (hex) Shenzhen Jingxun Technology Co., Ltd. @@ -46028,17 +45443,11 @@ BCD767 (base 16) BAE Systems Sunnyvale CA 94085 US -C4-3D-C7 (hex) NETGEAR -C43DC7 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -4C-60-DE (hex) NETGEAR -4C60DE (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +00-15-EA (hex) Hensoldt South Africa (Pty) Ltd +0015EA (base 16) Hensoldt South Africa (Pty) Ltd + 64/74 White Road + Cape Town Western Province 7945 + ZA F8-10-37 (hex) ENTOUCH Controls F81037 (base 16) ENTOUCH Controls @@ -46052,18 +45461,6 @@ F81037 (base 16) ENTOUCH Controls Piscataway NJ 08554 US -80-8F-97 (hex) Xiaomi Communications Co Ltd -808F97 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN - -4C-E2-0F (hex) Xiaomi Communications Co Ltd -4CE20F (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN - 10-0C-6B (hex) NETGEAR 100C6B (base 16) NETGEAR 3553 N. First Street @@ -46082,30 +45479,12 @@ F81037 (base 16) ENTOUCH Controls San Jose CA 95134 US -30-91-8F (hex) Vantiva Technologies Belgium -30918F (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -E0-B9-E5 (hex) Vantiva Technologies Belgium -E0B9E5 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - 44-FB-76 (hex) vivo Mobile Communication Co., Ltd. 44FB76 (base 16) vivo Mobile Communication Co., Ltd. No.1, vivo Road, Chang'an Dongguan Guangdong 523860 CN -A0-55-2E (hex) zte corporation -A0552E (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - B0-7F-B9 (hex) NETGEAR B07FB9 (base 16) NETGEAR 3553 N. First Street @@ -46118,11 +45497,17 @@ B07FB9 (base 16) NETGEAR San Jose CA 95134 US -9C-97-26 (hex) Vantiva Technologies Belgium -9C9726 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE +C4-3D-C7 (hex) NETGEAR +C43DC7 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +4C-60-DE (hex) NETGEAR +4C60DE (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US 08-BD-43 (hex) NETGEAR 08BD43 (base 16) NETGEAR @@ -46154,12 +45539,42 @@ C05724 (base 16) Honor Device Co., Ltd. Milan IT20126 IT +80-8F-97 (hex) Xiaomi Communications Co Ltd +808F97 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +4C-E2-0F (hex) Xiaomi Communications Co Ltd +4CE20F (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + C4-CD-50 (hex) Shenzhen C-Data Technology Co., Ltd. C4CD50 (base 16) Shenzhen C-Data Technology Co., Ltd. #201, Building A4, Nanshan Zhiyuan, No.1001, Xueyuan Avenue, Changyuan Community,Taoyuan,Nanshan Shenzhen Guangdong 518055 CN +9C-97-26 (hex) Vantiva Technologies Belgium +9C9726 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + +30-91-8F (hex) Vantiva Technologies Belgium +30918F (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + +E0-B9-E5 (hex) Vantiva Technologies Belgium +E0B9E5 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE + AC-8A-C7 (hex) HUAWEI TECHNOLOGIES CO.,LTD AC8AC7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -46172,12 +45587,6 @@ AC8AC7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -DC-04-5A (hex) Nanjing Qinheng Microelectronics Co., Ltd. -DC045A (base 16) Nanjing Qinheng Microelectronics Co., Ltd. - No.18, Ningshuang Road - Nanjing Jiangsu 210012 - CN - 24-DB-94 (hex) Juniper Networks 24DB94 (base 16) Juniper Networks 1133 Innovation Way @@ -46190,11 +45599,11 @@ DC045A (base 16) Nanjing Qinheng Microelectronics Co., Ltd. Sunnyvale CA 94089 US -8C-77-79 (hex) Arcadyan Corporation -8C7779 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +A0-55-2E (hex) zte corporation +A0552E (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN 54-AE-BC (hex) CHINA DRAGON TECHNOLOGY LIMITED 54AEBC (base 16) CHINA DRAGON TECHNOLOGY LIMITED @@ -46232,17 +45641,17 @@ C8806D (base 16) Apple, Inc. Cupertino CA 95014 US -98-CF-7D (hex) Apple, Inc. -98CF7D (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +DC-04-5A (hex) Nanjing Qinheng Microelectronics Co., Ltd. +DC045A (base 16) Nanjing Qinheng Microelectronics Co., Ltd. + No.18, Ningshuang Road + Nanjing Jiangsu 210012 + CN -74-29-59 (hex) Apple, Inc. -742959 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +8C-77-79 (hex) Arcadyan Corporation +8C7779 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW 04-C9-DE (hex) Qingdao HaierTechnology Co.,Ltd 04C9DE (base 16) Qingdao HaierTechnology Co.,Ltd @@ -46256,17 +45665,17 @@ C8806D (base 16) Apple, Inc. Redmond WA 98052 US -80-B8-2A (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -80B82A (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 - CN +98-CF-7D (hex) Apple, Inc. +98CF7D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -44-25-38 (hex) WNC Corporation -442538 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +74-29-59 (hex) Apple, Inc. +742959 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US E8-6E-3E (hex) Sichuan Tianyi Comheart Telecom Co.,LTD E86E3E (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD @@ -46280,17 +45689,35 @@ D8D7F3 (base 16) New H3C Technologies Co., Ltd Hangzhou Zhejiang 310052 CN +44-25-38 (hex) WNC Corporation +442538 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + 1C-CF-82 (hex) Palo Alto Networks 1CCF82 (base 16) Palo Alto Networks 3000 Tannery Way Santa Clara CA 95054 US -B0-43-5D (hex) MechoShade -B0435D (base 16) MechoShade - 1497 Poinsettia Ave. - Vista CA 92081 - US +80-B8-2A (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +80B82A (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 + CN + +18-5C-A1 (hex) Jiangxi Risound Electronics Co.,LTD +185CA1 (base 16) Jiangxi Risound Electronics Co.,LTD + No 271,innovation Avenue, Jinggangshan economic and Technological Development Zone + Ji'an Jiangxi 343100 + CN + +3C-2A-B3 (hex) Telesystem communications Pte Ltd +3C2AB3 (base 16) Telesystem communications Pte Ltd + 3F, No.7 Xing Hua Rd., + Taowan Taiwan 33068 + TW 9C-04-B6 (hex) Quectel Wireless Solutions Co.,Ltd. 9C04B6 (base 16) Quectel Wireless Solutions Co.,Ltd. @@ -46304,12 +45731,24 @@ B0435D (base 16) MechoShade Hwaseong-si Gyeonggi-do 18423 KR +4C-D7-C8 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +4CD7C8 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District + Guangzhou Guangdong 510663 + CN + BC-AA-82 (hex) Fiberhome Telecommunication Technologies Co.,LTD BCAA82 (base 16) Fiberhome Telecommunication Technologies Co.,LTD No.5 DongXin Road Wuhan Hubei 430074 CN +44-93-8D (hex) Innolux Corporation +44938D (base 16) Innolux Corporation + No. 160, Kexue Rd., Zhunan Township + Miaoli County 35053 + TW + C8-CC-21 (hex) eero inc. C8CC21 (base 16) eero inc. 660 3rd Street @@ -46328,54 +45767,6 @@ E01ADF (base 16) Google, Inc. Mountain View CA 94043 US -3C-2A-B3 (hex) Telesystem communications Pte Ltd -3C2AB3 (base 16) Telesystem communications Pte Ltd - 3F, No.7 Xing Hua Rd., - Taowan Taiwan 33068 - TW - -F8-5B-1B (hex) Espressif Inc. -F85B1B (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -44-1B-F6 (hex) Espressif Inc. -441BF6 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -4C-D7-C8 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. -4CD7C8 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. - 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District - Guangzhou Guangdong 510663 - CN - -18-5C-A1 (hex) Jiangxi Risound Electronics Co.,LTD -185CA1 (base 16) Jiangxi Risound Electronics Co.,LTD - No 271,innovation Avenue, Jinggangshan economic and Technological Development Zone - Ji'an Jiangxi 343100 - CN - -C8-91-43 (hex) Nintendo Co.,Ltd -C89143 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - -44-93-8D (hex) Innolux Corporation -44938D (base 16) Innolux Corporation - No. 160, Kexue Rd., Zhunan Township - Miaoli County 35053 - TW - -70-AD-43 (hex) Blink by Amazon -70AD43 (base 16) Blink by Amazon - 100 Riverpark Drive - North Reading MA 01864 - US - 70-3A-8C (hex) Shenzhen Skyworth Digital Technology CO., Ltd 703A8C (base 16) Shenzhen Skyworth Digital Technology CO., Ltd 4F,Block A, Skyworth?Building, @@ -46388,12 +45779,6 @@ C89143 (base 16) Nintendo Co.,Ltd Werkendam 4251 LT NL -88-5E-54 (hex) Samsung Electronics Co.,Ltd -885E54 (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR - D0-98-B1 (hex) GScoolink Microelectronics (Beijing) Co.,LTD D098B1 (base 16) GScoolink Microelectronics (Beijing) Co.,LTD Room 101, 3rd Floor, Building 23, No. 8 Dongbeiwang West Road, Haidian District @@ -46412,6 +45797,12 @@ D098B1 (base 16) GScoolink Microelectronics (Beijing) Co.,LTD Dongguan 523808 CN +C8-91-43 (hex) Nintendo Co.,Ltd +C89143 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + C8-AF-F0 (hex) CDVI Wireless SpA C8AFF0 (base 16) CDVI Wireless SpA via Piave 23 @@ -46442,29 +45833,11 @@ E4FAE4 (base 16) Shenzhen SDMC Technology CP,.LTD Gumi Gyeongbuk 730-350 KR -B8-1E-61 (hex) TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO -B81E61 (base 16) TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO - Av. Buriti, 1900 – Setor B – Distrito Industrial - Manaus Amazonas 69075-000 - BR - -40-6E-0F (hex) SKYASTAR TECHNOLOGLES(ZHUHAI) LTD -406E0F (base 16) SKYASTAR TECHNOLOGLES(ZHUHAI) LTD - 3F, 5# Building, Maker Town, Jinwan, Zhuhai, Guangdong, 519090 China - ZHUHAI Guangdong 519090 - CN - -EC-B5-0A (hex) Quectel Wireless Solutions Co.,Ltd. -ECB50A (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN - -38-E0-54 (hex) Security Design, Inc. -38E054 (base 16) Security Design, Inc. - Nishiki-cho Trad Square 4F, 3-20 Kanda Nishiki-cho - Chiyoda-ku Tokyo 101-0054 - JP +88-5E-54 (hex) Samsung Electronics Co.,Ltd +885E54 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR 8C-A3-EC (hex) Samsung Electronics Co.,Ltd 8CA3EC (base 16) Samsung Electronics Co.,Ltd @@ -46496,6 +45869,18 @@ AC3AE2 (base 16) NVIDIA Corporation Santa Clara CA 95050 US +70-AD-43 (hex) Blink by Amazon +70AD43 (base 16) Blink by Amazon + 100 Riverpark Drive + North Reading MA 01864 + US + +D4-00-CA (hex) AUMOVIO Systems Romania S.R.L. +D400CA (base 16) AUMOVIO Systems Romania S.R.L. + Str. Salzburg Nr. 8, 550018 + Sibiu Sibiu 550018 + RO + 40-85-56 (hex) AUMOVIO Technologies Romania S.R.L. 408556 (base 16) AUMOVIO Technologies Romania S.R.L. Str Siemens no.1, 300701 Timisoara, Romania @@ -46520,6 +45905,42 @@ D494FB (base 16) AUMOVIO Systems, Inc. Deer Park IL 60010 US +B8-1E-61 (hex) TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO +B81E61 (base 16) TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO + Av. Buriti, 1900 – Setor B – Distrito Industrial + Manaus Amazonas 69075-000 + BR + +40-6E-0F (hex) SKYASTAR TECHNOLOGLES(ZHUHAI) LTD +406E0F (base 16) SKYASTAR TECHNOLOGLES(ZHUHAI) LTD + 3F, 5# Building, Maker Town, Jinwan, Zhuhai, Guangdong, 519090 China + ZHUHAI Guangdong 519090 + CN + +EC-B5-0A (hex) Quectel Wireless Solutions Co.,Ltd. +ECB50A (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + +38-E0-54 (hex) Security Design, Inc. +38E054 (base 16) Security Design, Inc. + Nishiki-cho Trad Square 4F, 3-20 Kanda Nishiki-cho + Chiyoda-ku Tokyo 101-0054 + JP + +44-7C-AC (hex) Invictus-AV +447CAC (base 16) Invictus-AV + 17650 Hillcrest Drive + Meadow Vista CA 95722 + US + +6C-D5-52 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +6CD552 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 + CN + 44-20-63 (hex) AUMOVIO Germany GmbH 442063 (base 16) AUMOVIO Germany GmbH Siemensstr. 12 @@ -46532,24 +45953,6 @@ E41E33 (base 16) AUMOVIO Germany GmbH Villingen-Schwenningen 78052 DE -6C-D5-52 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD -6CD552 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD - NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district - shenzhen guangdong 518000 - CN - -D4-00-CA (hex) AUMOVIO Systems Romania S.R.L. -D400CA (base 16) AUMOVIO Systems Romania S.R.L. - Str. Salzburg Nr. 8, 550018 - Sibiu Sibiu 550018 - RO - -44-7C-AC (hex) Invictus-AV -447CAC (base 16) Invictus-AV - 17650 Hillcrest Drive - Meadow Vista CA 95722 - US - 00-02-DC (hex) GENERAL Inc. 0002DC (base 16) GENERAL Inc. 3-3-17,Suenaga,Takatsu-ku @@ -46586,24 +45989,6 @@ D02C39 (base 16) Cisco Systems, Inc San Jose CA 94568 US -1C-FF-3F (hex) Cust2mate -1CFF3F (base 16) Cust2mate - 4 Ariel Sharon St - Givatayim 5320047 - IL - -74-83-A0 (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd -7483A0 (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd - 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District - Shenzhen Guangdong 518110 - CN - -18-69-45 (hex) TP-Link Systems Inc. -186945 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US - 48-76-96 (hex) Huaan Zhongyun Co., Ltd. 487696 (base 16) Huaan Zhongyun Co., Ltd. Room 201, 2nd Floor, Building A, No. 128 Qiming Road, Yinzhou District, Ningbo City @@ -46616,17 +46001,11 @@ D02C39 (base 16) Cisco Systems, Inc Dongguan 523808 CN -20-4B-2E (hex) Pizzato Elettrica S.r.l. -204B2E (base 16) Pizzato Elettrica S.r.l. - Via Torino, 1 - Marostica VI 36063 - IT - -50-61-7E (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd -50617E (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd - 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District - Shenzhen Guangdong 518110 - CN +18-69-45 (hex) TP-Link Systems Inc. +186945 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US 80-BF-21 (hex) vivo Mobile Communication Co., Ltd. 80BF21 (base 16) vivo Mobile Communication Co., Ltd. @@ -46646,23 +46025,17 @@ D0B324 (base 16) Apple, Inc. Cupertino CA 95014 US -2C-79-BE (hex) TP-LINK TECHNOLOGIES CO.,LTD. -2C79BE (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - -4C-09-97 (hex) Arista Networks -4C0997 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 - US +1C-FF-3F (hex) Cust2mate +1CFF3F (base 16) Cust2mate + 4 Ariel Sharon St + Givatayim 5320047 + IL -74-DC-13 (hex) Telink Micro LLC -74DC13 (base 16) Telink Micro LLC - 2975 Scott Blvd #120 - Santa Clara 95054 - US +74-83-A0 (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd +7483A0 (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd + 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District + Shenzhen Guangdong 518110 + CN 60-25-ED (hex) Hewlett Packard Enterprise 6025ED (base 16) Hewlett Packard Enterprise @@ -46670,52 +46043,46 @@ D0B324 (base 16) Apple, Inc. San Jose CA 95002 US -00-21-04 (hex) Gigaset Technologies GmbH -002104 (base 16) Gigaset Technologies GmbH - Frankenstrasse 2 - 46395 Bocholt - DE +20-4B-2E (hex) Pizzato Elettrica S.r.l. +204B2E (base 16) Pizzato Elettrica S.r.l. + Via Torino, 1 + Marostica VI 36063 + IT -AC-D2-0C (hex) Chengdu SingCore Technology Co.,Ltd. -ACD20C (base 16) Chengdu SingCore Technology Co.,Ltd. - Room 4, 16th Floor, Building 10, No. 399 West Fucheng Avenue, Chengdu High-Tech Zone, China (Sichuan) Pilot Free Trade Zone,Chengdu, Sichuan Province, China. - Chengdu Sichuan 610041 +50-61-7E (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd +50617E (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd + 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District + Shenzhen Guangdong 518110 CN +4C-09-97 (hex) Arista Networks +4C0997 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US + 04-64-FA (hex) Dell Inc. 0464FA (base 16) Dell Inc. One Dell Way Round Rock TX 78682 US -8C-37-B7 (hex) Hosin Global Electronics Co.,Ltd -8C37B7 (base 16) Hosin Global Electronics Co.,Ltd - Rm 2501, Bldg 2, Shenzhen Next Generation Industrial Park, No.136 Zhongkang Rd, Futian Dist - Shenzhen 518000 +68-78-A8 (hex) Fiberhome Telecommunication Technologies Co.,LTD +6878A8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -F0-6D-93 (hex) EM Microelectronic -F06D93 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH - B8-1E-0B (hex) Extreme Networks Headquarters B81E0B (base 16) Extreme Networks Headquarters 2121 RDU Center Drive Morrisville NC 27560 US -8C-94-DF (hex) Espressif Inc. -8C94DF (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -28-84-85 (hex) Espressif Inc. -288485 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +AC-D2-0C (hex) Chengdu SingCore Technology Co.,Ltd. +ACD20C (base 16) Chengdu SingCore Technology Co.,Ltd. + Room 4, 16th Floor, Building 10, No. 399 West Fucheng Avenue, Chengdu High-Tech Zone, China (Sichuan) Pilot Free Trade Zone,Chengdu, Sichuan Province, China. + Chengdu Sichuan 610041 CN 10-D8-CC (hex) Guangzhou FiiO Electronics Technology CO., LTD @@ -46724,15 +46091,51 @@ B81E0B (base 16) Extreme Networks Headquarters Guangzhou 510540 CN -68-78-A8 (hex) Fiberhome Telecommunication Technologies Co.,LTD -6878A8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN - -90-0A-75 (hex) New H3C Technologies Co., Ltd -900A75 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District +00-21-04 (hex) Gigaset Technologies GmbH +002104 (base 16) Gigaset Technologies GmbH + Frankenstrasse 2 + 46395 Bocholt + DE + +8C-37-B7 (hex) Hosin Global Electronics Co.,Ltd +8C37B7 (base 16) Hosin Global Electronics Co.,Ltd + Rm 2501, Bldg 2, Shenzhen Next Generation Industrial Park, No.136 Zhongkang Rd, Futian Dist + Shenzhen 518000 + CN + +F0-6D-93 (hex) EM Microelectronic +F06D93 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +0C-C9-8A (hex) Intel Corporate +0CC98A (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +EC-F3-3C (hex) Intel Corporate +ECF33C (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +40-EC-BD (hex) Intel Corporate +40ECBD (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +6C-10-41 (hex) Shenzhen Skyworth Digital Technology CO., Ltd +6C1041 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + +90-0A-75 (hex) New H3C Technologies Co., Ltd +900A75 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District Hangzhou Zhejiang 310052 CN @@ -46742,6 +46145,12 @@ B81E0B (base 16) Extreme Networks Headquarters Dongguan Guangdong 523808 CN +8C-8C-29 (hex) Espressif Inc. +8C8C29 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + E4-06-E0 (hex) HUAWEI TECHNOLOGIES CO.,LTD E406E0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -46760,16 +46169,10 @@ DCB43F (base 16) eero inc. San Francisco CA 94107 US -6C-10-41 (hex) Shenzhen Skyworth Digital Technology CO., Ltd -6C1041 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 - CN - -8C-8C-29 (hex) Espressif Inc. -8C8C29 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +14-90-7A (hex) Beijing Xiaomi Mobile Software Co., Ltd +14907A (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN 9C-97-74 (hex) HUAWEI TECHNOLOGIES CO.,LTD @@ -46802,48 +46205,6 @@ C05BBD (base 16) HUAWEI TECHNOLOGIES CO.,LTD Chengdu Sichuan 611330 CN -EC-F3-3C (hex) Intel Corporate -ECF33C (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -40-EC-BD (hex) Intel Corporate -40ECBD (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -0C-C9-8A (hex) Intel Corporate -0CC98A (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -14-90-7A (hex) Beijing Xiaomi Mobile Software Co., Ltd -14907A (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN - -1C-8F-57 (hex) Espressif Inc. -1C8F57 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -94-AE-E3 (hex) Belden Hirschmann industries (Suzhou) Limited -94AEE3 (base 16) Belden Hirschmann industries (Suzhou) Limited - 333 Yanhu Road, Huaqiao Town - Kunshan Jiangsu 215332 - CN - -94-10-5A (hex) Dell Inc. -94105A (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US - 44-83-46 (hex) Texas Instruments 448346 (base 16) Texas Instruments 12500 TI Blvd @@ -46868,17 +46229,17 @@ DCDEE3 (base 16) Texas Instruments ShenZhen 518100 CN -10-BD-A3 (hex) Espressif Inc. -10BDA3 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +94-AE-E3 (hex) Belden Hirschmann industries (Suzhou) Limited +94AEE3 (base 16) Belden Hirschmann industries (Suzhou) Limited + 333 Yanhu Road, Huaqiao Town + Kunshan Jiangsu 215332 CN -E4-72-9D (hex) Nokia Shanghai Bell Co., Ltd. -E4729D (base 16) Nokia Shanghai Bell Co., Ltd. - No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai - Shanghai 201206 - CN +94-10-5A (hex) Dell Inc. +94105A (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US 7C-CF-0F (hex) LCFC(Hefei) Electronics Technology co., ltd 7CCF0F (base 16) LCFC(Hefei) Electronics Technology co., ltd @@ -46910,10 +46271,10 @@ A02605 (base 16) Belden Hirschmann industries (Suzhou) Limited Suzhou Jiangsu 215332 CN -C0-2F-CD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -C02FCD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +E4-72-9D (hex) Nokia Shanghai Bell Co., Ltd. +E4729D (base 16) Nokia Shanghai Bell Co., Ltd. + No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai + Shanghai 201206 CN F8-84-75 (hex) i5LED, LLC @@ -46922,11 +46283,29 @@ F88475 (base 16) i5LED, LLC Sacramento CA 95827 US -44-9F-79 (hex) onsemi -449F79 (base 16) onsemi - 5701 N Pima Rd - Scottsdale AZ 85250 - US +C0-2F-CD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +C02FCD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +04-DB-D9 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +04DBD9 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +54-01-4A (hex) Guangzhou Shiyuan Electronic Technology Company Limited +54014A (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 + CN + +FC-8F-A4 (hex) NXP Semiconductors Taiwan Ltd. +FC8FA4 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW A4-61-77 (hex) AMOSENSE A46177 (base 16) AMOSENSE @@ -46937,35 +46316,23 @@ A46177 (base 16) AMOSENSE 58-DF-70 (hex) Private 58DF70 (base 16) Private -54-01-4A (hex) Guangzhou Shiyuan Electronic Technology Company Limited -54014A (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 - CN - 50-EE-9B (hex) AltoBeam Inc. 50EE9B (base 16) AltoBeam Inc. B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian Beijing Beijing 100083 CN -EC-73-F6 (hex) Sichuan AI-Link Technology Co., Ltd. -EC73F6 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 - CN - -04-DB-D9 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -04DBD9 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN +90-DF-06 (hex) Ciena Corporation +90DF06 (base 16) Ciena Corporation + 7035 Ridge Road + Hanover MD 21076 + US -FC-8F-A4 (hex) NXP Semiconductors Taiwan Ltd. -FC8FA4 (base 16) NXP Semiconductors Taiwan Ltd. - No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan - Nanzi Dist. Kaohsiung 811643 - TW +44-9F-79 (hex) onsemi +449F79 (base 16) onsemi + 5701 N Pima Rd + Scottsdale AZ 85250 + US 2C-DE-F5 (hex) TVS REGZA Corporation 2CDEF5 (base 16) TVS REGZA Corporation @@ -46973,17 +46340,11 @@ FC8FA4 (base 16) NXP Semiconductors Taiwan Ltd. Kawasaki-shi Kanagawa 2120013 JP -90-DF-06 (hex) Ciena Corporation -90DF06 (base 16) Ciena Corporation - 7035 Ridge Road - Hanover MD 21076 - US - -50-EE-87 (hex) HPRO -50EE87 (base 16) HPRO - 8500 Balboa Blvd - Northridge CA 91329 - US +EC-73-F6 (hex) Sichuan AI-Link Technology Co., Ltd. +EC73F6 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 + CN 00-26-89 (hex) General Dynamics Land Systems Inc. 002689 (base 16) General Dynamics Land Systems Inc. @@ -46997,15968 +46358,17132 @@ 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 - -00-01-30 (hex) Extreme Networks Headquarters -000130 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US - -FC-0A-81 (hex) Extreme Networks Headquarters -FC0A81 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 +50-EE-87 (hex) HPRO +50EE87 (base 16) HPRO + 8500 Balboa Blvd + Northridge CA 91329 US -00-74-9C (hex) Ruijie Networks Co.,LTD -00749C (base 16) Ruijie Networks Co.,LTD - 19# Building,Star-net Science Plaza,Juyuanzhou, 618 Jinshan Road - Fuzhou Fujian 350002 +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 -00-0F-EA (hex) GIGA-BYTE TECHNOLOGY CO.,LTD. -000FEA (base 16) GIGA-BYTE TECHNOLOGY CO.,LTD. - No.215,Nan-Ping Road,Ping-Jen City, - Ping-Jen Taoyuan 324 - TW - -48-9B-D5 (hex) Extreme Networks Headquarters -489BD5 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US - -DC-B8-08 (hex) Extreme Networks Headquarters -DCB808 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US - -F4-6E-95 (hex) Extreme Networks Headquarters -F46E95 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +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 -E0-1C-41 (hex) Extreme Networks Headquarters -E01C41 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +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 -BC-F3-10 (hex) Extreme Networks Headquarters -BCF310 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +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 -D8-54-A2 (hex) Extreme Networks Headquarters -D854A2 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +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 -C8-66-5D (hex) Extreme Networks Headquarters -C8665D (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 +B0-2B-64 (hex) Cisco Systems, Inc +B02B64 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -8C-49-7A (hex) Extreme Networks Headquarters -8C497A (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 +10-E6-76 (hex) Cisco Systems, Inc +10E676 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -B0-27-CF (hex) Extreme Networks Headquarters -B027CF (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +FC-50-0C (hex) Sitehop Ltd +FC500C (base 16) Sitehop Ltd + 9 South Street + Sheffield South Yorkshire S2 5QX + GB -C8-51-FB (hex) Extreme Networks Headquarters -C851FB (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +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 -DC-23-3B (hex) Extreme Networks Headquarters -DC233B (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US +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 -E4-DB-AE (hex) Extreme Networks Headquarters -E4DBAE (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 +90-0E-84 (hex) eero inc. +900E84 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US -38-AD-2B (hex) Hitron Technologies. Inc -38AD2B (base 16) Hitron Technologies. Inc - No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C - Hsin-chu Taiwan 300 - TW +F4-C6-D7 (hex) blackned GmbH +F4C6D7 (base 16) blackned GmbH + Zugspitzstrasse 1 + Bavaria Heimertingen 87751 + DE -E8-45-8B (hex) MitraStar Technology Corp. -E8458B (base 16) MitraStar Technology Corp. - No. 6, Innovation Road II, - Hsinchu 300 - TW +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 -D0-44-33 (hex) Clourney Semiconductor -D04433 (base 16) Clourney Semiconductor - Floor 6, Building 8, 2777 Nong, Jinxiu East Road, Pudong Dist. - Shanghai Shanghai 201206 +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 -40-86-CB (hex) D-Link Corporation -4086CB (base 16) D-Link Corporation - No.289, Sinhu 3rd Rd., Neihu District, - Taipei City 114 - TW - -48-41-7B (hex) Nokia Solutions and Networks GmbH & Co. KG -48417B (base 16) Nokia Solutions and Networks GmbH & Co. KG - Werinherstrasse 91 - München Bavaria D-81541 - DE - -60-8C-DF (hex) Beamtrail-Sole Proprietorship -608CDF (base 16) Beamtrail-Sole Proprietorship - Level 7 Aldar HQ - Abu Dhabi Abu Dhabi 29836 - AE +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 -30-27-CF (hex) Canopy Growth Corp -3027CF (base 16) Canopy Growth Corp - 350 Leggett Drive - Ottawa Ontario K2K 2W7 - CA +D4-4A-85 (hex) Silicon Laboratories +D44A85 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -00-12-77 (hex) Beijer Electronics Corp. -001277 (base 16) Beijer Electronics Corp. - 11F-1, No. 108, MinQuan Rd. - Xindian City Taipei 231 +A8-D3-F7 (hex) Arcadyan Corporation +A8D3F7 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd., + Hsinchu City Hsinchu 30071 TW -20-47-B5 (hex) Sagemcom Broadband SAS -2047B5 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +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 -60-45-2E (hex) Intel Corporate -60452E (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +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 -1C-AB-48 (hex) TECNO MOBILE LIMITED -1CAB48 (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 - Hong Kong Hong Kong 999077 - HK +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 -88-B4-36 (hex) FUJIFILM Corporation -88B436 (base 16) FUJIFILM Corporation - 1-324,Uetake,Kita-ku - Saitama Saitama 331-9624 - JP +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 -CC-D3-42 (hex) Cisco Systems, Inc -CCD342 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +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 -40-B8-C2 (hex) OSMOZIS -40B8C2 (base 16) OSMOZIS - 7 Avenue de l'Europe - Clapiers 34830 - FR - -8C-E9-EE (hex) Intel Corporate -8CE9EE (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +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 -94-45-60 (hex) Google, Inc. -944560 (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 +B8-38-65 (hex) Hewlett Packard Enterprise +B83865 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 US -5C-33-7B (hex) Google, Inc. -5C337B (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 +08-D0-1E (hex) Juniper Networks +08D01E (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -2C-9C-58 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -2C9C58 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 +30-15-77 (hex) Zyxel Communications Corporation +301577 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + +50-5E-3A (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +505E3A (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN -B8-F4-4F (hex) u-blox AG -B8F44F (base 16) u-blox AG - Zuercherstrasse, 68 - Thalwil Switzerland CH-8800 - CH +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-09-29 (hex) Inventus Power Eletronica do Brasil LTDA -540929 (base 16) Inventus Power Eletronica do Brasil LTDA - Av Buriti, 4285 Distrito Industrial - Manaus Amazonas 69075000 - BR +54-13-8F (hex) GEOIDE Crypto&Com +54138F (base 16) GEOIDE Crypto&Com + 18 Rue Alain Savary + BESANCON 25000 + FR -28-31-F8 (hex) HUAWEI TECHNOLOGIES CO.,LTD -2831F8 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +90-06-DB (hex) HUAWEI TECHNOLOGIES CO.,LTD +9006DB (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -C4-AA-99 (hex) HUAWEI TECHNOLOGIES CO.,LTD -C4AA99 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +78-78-26 (hex) HUAWEI TECHNOLOGIES CO.,LTD +787826 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -A8-F5-E1 (hex) Shenzhen Shokz Co., Ltd. -A8F5E1 (base 16) Shenzhen Shokz Co., Ltd. - Baoan District Shiyan street Shancheng Industrial zone 26 building - Shenzhen Guangdong 518108 +5C-61-17 (hex) HUAWEI TECHNOLOGIES CO.,LTD +5C6117 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -60-66-82 (hex) SHENZHEN ATEKO PHOTOELECTRICITY CO.,LTD -606682 (base 16) SHENZHEN ATEKO PHOTOELECTRICITY CO.,LTD - 4-5F,E1 Building,TCL International E City,No.1001 Zhongshanyuan Road,Nanshan District,Shenzhen - SHENZHEN GUANGDONG 518052 +34-8A-3B (hex) Huawei Device Co., Ltd. +348A3B (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -78-8D-AF (hex) Sagemcom Broadband SAS -788DAF (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -2C-BA-CA (hex) Cosonic Electroacoustic Technology Co., Ltd. -2CBACA (base 16) Cosonic Electroacoustic Technology Co., Ltd. - No.151, Shipai Section, Dongyuan Avenue, Shipai Town - Dongguan Guangdong 523331 +20-E5-25 (hex) Huawei Device Co., Ltd. +20E525 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -04-FA-3F (hex) OptiCore Inc. -04FA3F (base 16) OptiCore Inc. - 97 Jungbudaero448beongil, Yeongtonggu - Suwonsi Gyeonggido 16521 - KR - -44-1D-B1 (hex) APTIV SERVICES US, LLC -441DB1 (base 16) APTIV SERVICES US, LLC - 5725 Innovation Drive - Troy MI 48098 - US - -00-9C-C0 (hex) vivo Mobile Communication Co., Ltd. -009CC0 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +F8-BA-98 (hex) HUAQIN TECHNOLOGY CO., LTD +F8BA98 (base 16) HUAQIN TECHNOLOGY CO., LTD + No.699, Lvke Road + Pudong New Area Shanghai 200000 CN -E8-9E-13 (hex) CRESYN -E89E13 (base 16) CRESYN - 8-22,Jamwon-dong - Seoul Seocho-Gu #137-902 +AC-53-22 (hex) Samsung Electronics Co.,Ltd +AC5322 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 KR -BC-18-96 (hex) HUAWEI TECHNOLOGIES CO.,LTD -BC1896 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -BC-0F-FE (hex) Juniper Networks -BC0FFE (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +88-C3-44 (hex) Google, Inc. +88C344 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US -E0-D3-B4 (hex) Cisco Meraki -E0D3B4 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 +B0-43-5D (hex) MechoShade +B0435D (base 16) MechoShade + 4203 35th Street + Long Island City NY 11101 US -FC-8C-11 (hex) Microsoft Corporation -FC8C11 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US +AC-AD-EF (hex) Wanan Hongsheng Electronic Co.Ltd +ACADEF (base 16) Wanan Hongsheng Electronic Co.Ltd +  1st section of industrial pack,Wan'An County,Ji'An City + Ji'An City ,jiangxi province 343800 + CN -A4-94-DC (hex) Infinite Clouds -A494DC (base 16) Infinite Clouds - Office 406 Block 333 Road 3307, Um Al Hassam, Kingdom of Bahrain - Manama 973 - BH +D4-2B-F0 (hex) Tiinlab Corporation +D42BF0 (base 16) Tiinlab Corporation + Building A Room 201 Cooperation District between Shenzhen and HongKong,Qianwan Road No.1,Shenzhen City, Business Address:No. 3333, Liuxian AvenueTower A, 35th Floor,Tanglang City, Nanshan District, Shenzhen, China + Shenzhen Guangdong 518000 + CN -4C-43-41 (hex) Calix Inc. -4C4341 (base 16) Calix Inc. - 2777 Orchard Pkwy - San Jose CA 95131 +B8-DC-28 (hex) Extreme Networks Headquarters +B8DC28 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 US -E4-6C-D1 (hex) Calix Inc. -E46CD1 (base 16) Calix Inc. - 2777 Orchard Pkwy - San Jose CA 95131 +1C-2D-60 (hex) Extreme Networks Headquarters +1C2D60 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 US -D0-F2-7F (hex) BrewLogix, LLC -D0F27F (base 16) BrewLogix, LLC - 6 East Washing Street, Suite 200 - Indianapolis IN 46204 +84-EB-0C (hex) Mellanox Technologies, Inc. +84EB0C (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -7C-FC-FD (hex) Fiberhome Telecommunication Technologies Co.,LTD -7CFCFD (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN - -48-87-B8 (hex) TCL King Electrical Appliances(Huizhou)Co.,Ltd -4887B8 (base 16) TCL King Electrical Appliances(Huizhou)Co.,Ltd - B Area, 10th floor, TCL multimedia Building, TCL International E City, #1001 Zhonshanyuan road,Shenzhen - guangdong China 518058 +54-14-E9 (hex) AltoBeam Inc. +5414E9 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -70-58-A4 (hex) Actiontec Electronics Inc. -7058A4 (base 16) Actiontec Electronics Inc. - 2445 Augustine Dr #501 - Santa Clara CA 95054 - US +F4-F9-1E (hex) INGRAM MICRO SERVICES +F4F91E (base 16) INGRAM MICRO SERVICES + 100 CHEMIN DE BAILLOT + MONTAUBAN 82000 + FR -18-80-25 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -188025 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 +D8-60-C5 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +D860C5 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -F8-F0-9D (hex) Hangzhou Prevail Communication Technology Co., Ltd -F8F09D (base 16) Hangzhou Prevail Communication Technology Co., Ltd - No. 11809,Jianshe 4th road,Guali twon,Xiaoshan district - Hangzhou City Zhejiang Province 311241 +5C-F9-2B (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +5CF92B (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 CN -E4-78-76 (hex) Arista Networks -E47876 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 +14-38-FA (hex) Motorola Mobility LLC, a Lenovo Company +1438FA (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -A4-A9-30 (hex) Beijing Xiaomi Mobile Software Co., Ltd -A4A930 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN - -A0-22-52 (hex) Astra Wireless Technology FZ-LLC -A02252 (base 16) Astra Wireless Technology FZ-LLC - T1-4F-63, RAKEZ Amenity Center, Al Hamra Industrial Zone-FZ - Ras Al Khaimah 7100 - AE - -BC-10-2F (hex) SJI Industry Company -BC102F (base 16) SJI Industry Company - 54-33, Dongtanhana 1-gil - Hwaseong-si Gyeonggi-do 18423 - KR - -FC-93-6B (hex) Samsung Electronics Co.,Ltd -FC936B (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -BC-B6-FB (hex) P4Q ELECTRONICS, S.L. -BCB6FB (base 16) P4Q ELECTRONICS, S.L. - Calle Nuestra Señora de la Guía Número 19 - Alonsotegi Bizkaia 48810 - ES - -7C-75-2D (hex) Samsung Electronics Co.,Ltd -7C752D (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -84-EE-E4 (hex) Samsung Electronics Co.,Ltd -84EEE4 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +F0-40-EC (hex) LOOPDESIGNLAB PTE. LTD +F040EC (base 16) LOOPDESIGNLAB PTE. LTD + 83B Tanjong Pagar Road + Singapore 088504 + SG -44-35-D3 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -4435D3 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 +9C-9D-07 (hex) FN-LINK TECHNOLOGY Ltd. +9C9D07 (base 16) FN-LINK TECHNOLOGY Ltd. + No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China + Changsha Hunan 410329 CN -88-F0-0F (hex) Miraeil -88F00F (base 16) Miraeil - 70, Gasan digital 2-ro, Geumcheon-gusuite 1012 - Seoul 08589 - KR +D8-BF-42 (hex) Intel Corporate +D8BF42 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -18-A7-88 (hex) Shenzhen MEK Intellisys Pte Ltd -18A788 (base 16) Shenzhen MEK Intellisys Pte Ltd - Room 6C, 6th Floor, KeChuang Mansion, Quanzhi Technology Park, HouTing, Shajing Town, BaoAn District - Shenzhen GuangDong 518104 +7C-A8-5D (hex) RONGCHEENG GOER TECHNOLOGY CO.,LTD. +7CA85D (base 16) RONGCHEENG GOER TECHNOLOGY CO.,LTD. + Rongcheng Goer Technology Co., Ltd., No. 699, Jiangjun South Road, Rongcheng City, Weihai City, Shandong Province + Weihai Shandong 264300 CN -BC-85-29 (hex) Jiangxi Remote lntelligence Technology Co.,Ltd -BC8529 (base 16) Jiangxi Remote lntelligence Technology Co.,Ltd - No. 1, Chemical Avenue, Guixi335400, Yingtan, Jiangxi - Yingtan Jiangxi 360600 - CN +C8-41-2E (hex) AM Telecom co., Ltd. +C8412E (base 16) AM Telecom co., Ltd. + 20, Gwacheon-daero 7ga-gil + Gwacheon-si Gyeonggi-do 13840 + KR -28-94-01 (hex) NETGEAR -289401 (base 16) NETGEAR - 350 East Plumeria Drive - San Jose CA 95134 +4C-54-8B (hex) Cerebras System Inc. +4C548B (base 16) Cerebras System Inc. + 1237 E Arques Ave + Sunnyvale CA 94085-4701 US -D8-78-C9 (hex) SERVERCOM (INDIA) PRIVATE LIMITED -D878C9 (base 16) SERVERCOM (INDIA) PRIVATE LIMITED - E-43/1 OKHLA INDUSTRIAL AREA PHASE-II NEW DELHI SOUTH DELHI - NEW DELHI NA - IN - -A8-5E-F2 (hex) TECNO MOBILE LIMITED -A85EF2 (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 - Hong Kong Hong Kong 999077 - HK +80-A6-3C (hex) Amazon Technologies Inc. +80A63C (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -78-9A-18 (hex) Routerboard.com -789A18 (base 16) Routerboard.com - Mikrotikls SIA - Riga Riga LV1009 - LV +A4-0F-25 (hex) eero inc. +A40F25 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US -84-DB-2F (hex) Sierra Wireless, ULC -84DB2F (base 16) Sierra Wireless, ULC - 1381 Wireless Way - Richmond BC V6V 3A4 - CA +78-31-C4 (hex) Panascais ehf. +7831C4 (base 16) Panascais ehf. + Suðurlandsbraut 4 + Reykjavík 108 + IS -74-C5-30 (hex) vivo Mobile Communication Co., Ltd. -74C530 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +4C-6C-A1 (hex) Chipsea Technologies (Shenzhen) Crop. +4C6CA1 (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 -BC-96-E5 (hex) SERCOMM PHILIPPINES INC -BC96E5 (base 16) SERCOMM PHILIPPINES INC - Lot 1 & 5, Phase 1, Filinvest Technology Park 1, Brgy. Punta, Calamba City - Calamba Lot 1 - PH +90-84-8E (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD +90848E (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD + 19-22# Building, Star-net Science Plaza, Juyuanzhou, + FUZHOU FUJIAN 350002 + CN -A8-99-AD (hex) Chaoyue Technology Co., Ltd. -A899AD (base 16) Chaoyue Technology Co., Ltd. - No. 2877 Kehang Road, Suncun Town, High tech Zone, Jinan City, Shandong Province, China - Jinan SHANDONG 250104 +8C-18-01 (hex) zte corporation +8C1801 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -D0-15-BB (hex) IEEE Registration Authority -D015BB (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +D0-84-5D (hex) B&C Transit, Inc. +D0845D (base 16) B&C Transit, Inc. + 701 Seco Road + Monroeville PA 15146 US -64-CE-6E (hex) Sierra Wireless, ULC -64CE6E (base 16) Sierra Wireless, ULC - 13811 Wireless Way - Richmond BC V6V 3A4 - CA +40-ED-7B (hex) Zscaler +40ED7B (base 16) Zscaler + 120 Holger Way + San Jose CA 95134 + US -78-DF-72 (hex) Shanghai Imilab Technology Co.Ltd -78DF72 (base 16) Shanghai Imilab Technology Co.Ltd - 29F, A Tower, New Caohejing International Business Center, Guiping Road, Xuhui District - Shanghai Shanghai 200000 +5C-B2-DF (hex) Shenzhen Powerleader Storage Technology Co., Ltd. +5CB2DF (base 16) Shenzhen Powerleader Storage Technology Co., Ltd. + 2E, Unit 9, Mingjia Fuju, West of Yiyuan Road, Nantou Subdistrict, Nanshan District, Shenzhen + Shenzhen Guangdong 518000 CN -60-2B-58 (hex) EM Microelectronic -602B58 (base 16) EM Microelectronic +44-45-20 (hex) EM Microelectronic +444520 (base 16) EM Microelectronic Rue des Sors 3 Marin-Epagnier Neuchatel 2074 CH -10-9D-9C (hex) EM Microelectronic -109D9C (base 16) EM Microelectronic +88-DB-08 (hex) EM Microelectronic +88DB08 (base 16) EM Microelectronic Rue des Sors 3 Marin-Epagnier Neuchatel 2074 CH -28-FF-5F (hex) HG Genuine Intelligent Terminal (Xiaogan) Co.,Ltd. -28FF5F (base 16) HG Genuine Intelligent Terminal (Xiaogan) Co.,Ltd. - Building 62, YinHu Technology Industrial Park, No.38 XiaoHan Road, Xiaonan District, Xiaogan, Hubei P.R. China - Xiaogan Hubei 432000 - CN - -60-76-23 (hex) Shenzhen E-Superlink Technology Co., Ltd -607623 (base 16) Shenzhen E-Superlink Technology Co., Ltd - Floor11, NO.9996 Shen Nan Road, High Tech Park, Nan Shan District, Shen Zhen - ShenZhen Guangdong 518000 - CN - -74-92-BA (hex) Movesense Ltd -7492BA (base 16) Movesense Ltd - Tammiston kauppatie 7a - Vantaa 01510 - FI - -D0-48-4F (hex) Nokia Solutions and Networks GmbH & Co. KG -D0484F (base 16) Nokia Solutions and Networks GmbH & Co. KG - Werinherstrasse 91 - München Bavaria D-81541 - DE - -2C-45-9A (hex) Dixon Technologies (India) Limited -2C459A (base 16) Dixon Technologies (India) Limited - B 14-15 Phase 2 NOIDA - GautamBudh Nagar Uttarpradesh 201305 - IN - -6C-6E-07 (hex) CE LINK LIMITED -6C6E07 (base 16) CE LINK LIMITED - 2/F, Building G, Licheng Tech. Ind. Zone - Shenzhen Guangdong 518104 +FC-FD-71 (hex) AltoBeam Inc. +FCFD71 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -6C-62-86 (hex) Nokia -6C6286 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA - -50-48-2C (hex) IEEE Registration Authority -50482C (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +38-2C-DB (hex) Arista Networks +382CDB (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 US -B8-FC-28 (hex) Valeo Vision Systems -B8FC28 (base 16) Valeo Vision Systems - Dunmore Road - Tuam Co. Galway H54 Y276 - IE - -00-20-FC (hex) Matrox Central Services Inc -0020FC (base 16) Matrox Central Services Inc - 1055 ST. REGIS - DORVAL QUEBEC H9P-2T4 - CA - -88-81-87 (hex) Umeox Innovations Co.,Ltd -888187 (base 16) Umeox Innovations Co.,Ltd - Room 1208-09, Research Building, Tsinghua Information Port, No. 1, Xindong Road, Nanshan District, Shenzhen - Shenzhen Guangdong 518000 - CN - -D4-0F-9E (hex) Apple, Inc. -D40F9E (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +BC-93-2A (hex) Silicon Laboratories +BC932A (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -6C-A3-67 (hex) Avlinkpro -6CA367 (base 16) Avlinkpro - 380 US Highway 46 - Totowa NJ 07512 +AC-A8-99 (hex) Texas Instruments +ACA899 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -48-C4-61 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -48C461 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN - -B8-B7-DB (hex) GOIP Global Services Pvt. Ltd. -B8B7DB (base 16) GOIP Global Services Pvt. Ltd. - H68, Sector 63, Noida 201301 - Noida Uttar Pradesh 201301 - IN +38-4E-56 (hex) Texas Instruments +384E56 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -88-0C-E0 (hex) Texas Instruments -880CE0 (base 16) Texas Instruments +B8-DA-5E (hex) Texas Instruments +B8DA5E (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -B8-3D-F6 (hex) Texas Instruments -B83DF6 (base 16) Texas Instruments +F0-B1-63 (hex) Texas Instruments +F0B163 (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -B4-DD-E0 (hex) Shanghai Amphenol Airwave Communication Electronics Co.,Ltd. -B4DDE0 (base 16) Shanghai Amphenol Airwave Communication Electronics Co.,Ltd. - NO. 689 Shen Nan Road, Xin Zhuang Industry ParkShanghai 201108 P. R. China - Shanghai Shanghai 201108 +B4-DF-09 (hex) FLUX:: +B4DF09 (base 16) FLUX:: + 8500 Balboa Boulevard + Northridge CA 91325 + US + +5C-51-DF (hex) eero inc. +5C51DF (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +5C-63-B0 (hex) Fortinet, Inc. +5C63B0 (base 16) Fortinet, Inc. + 899 Kifer Road + Sunnyvale 94086 + US + +88-F3-D5 (hex) Zyxel Communications Corporation +88F3D5 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + +A0-1B-04 (hex) Hefei Huanxin Microelectronics Technology Co., Ltd. +A01B04 (base 16) Hefei Huanxin Microelectronics Technology Co., Ltd. + 22nd Floor, Building A1, China Vision, No. 99 Longchuan Road, Baohe District, Hefei City, Anhui Province, China + Hefei Anhui 230001 CN -90-64-AD (hex) HUAWEI TECHNOLOGIES CO.,LTD -9064AD (base 16) HUAWEI TECHNOLOGIES CO.,LTD +74-A9-81 (hex) HUAWEI TECHNOLOGIES CO.,LTD +74A981 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -D4-D8-92 (hex) HUAWEI TECHNOLOGIES CO.,LTD -D4D892 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +40-A6-54 (hex) HUAWEI TECHNOLOGIES CO.,LTD +40A654 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -98-D3-D7 (hex) HUAWEI TECHNOLOGIES CO.,LTD -98D3D7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +90-BA-09 (hex) HUAWEI TECHNOLOGIES CO.,LTD +90BA09 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -88-6E-EB (hex) HUAWEI TECHNOLOGIES CO.,LTD -886EEB (base 16) HUAWEI TECHNOLOGIES CO.,LTD +E8-EA-34 (hex) HUAWEI TECHNOLOGIES CO.,LTD +E8EA34 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -98-0D-AF (hex) Apple, Inc. -980DAF (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +78-34-B4 (hex) HUAWEI TECHNOLOGIES CO.,LTD +7834B4 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -DC-6D-BC (hex) Apple, Inc. -DC6DBC (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +A0-C6-A5 (hex) Lierda Science & Technology Group Co., Ltd +A0C6A5 (base 16) Lierda Science & Technology Group Co., Ltd + Lierda Science Park,No.1326 WenyiWestRoad + Hangzhou ZheJiang 311121 + CN -A4-D2-3E (hex) Apple, Inc. -A4D23E (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +20-A3-66 (hex) vivo Mobile Communication Co., Ltd. +20A366 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN -30-E0-4F (hex) Apple, Inc. -30E04F (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +68-FD-E8 (hex) Ruckus Wireless +68FDE8 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 US -64-E4-A5 (hex) LG Electronics -64E4A5 (base 16) LG Electronics - 222 LG-ro, JINWI-MYEON - Pyeongtaek-si Gyeonggi-do 451-713 - KR +7C-0C-5F (hex) Espressif Inc. +7C0C5F (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN -48-BD-CE (hex) Vantiva USA LLC -48BDCE (base 16) Vantiva USA LLC - 4855 Peachtree Industrial Blvd, Suite 20 - Norcross GA 30902 - US +7C-E8-B1 (hex) Espressif Inc. +7CE8B1 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN -94-04-E3 (hex) Vantiva USA LLC -9404E3 (base 16) Vantiva USA LLC - 5030 Sugarloaf Parkway Bldg 6 - Lawrenceville GA 30044 +FC-59-7A (hex) Zebra Technologies Inc. +FC597A (base 16) Zebra Technologies Inc. + ONE ZEBRA PLAZA + HOLTSVILLE NY 11742 US -B4-2A-0E (hex) Vantiva USA LLC -B42A0E (base 16) Vantiva USA LLC - 5030 Sugarloaf Parkway Bldg 6 - Lawrenceville GA 30044 - US +F4-00-A2 (hex) Samsung Electronics Co.,Ltd +F400A2 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -60-3D-26 (hex) Vantiva USA LLC -603D26 (base 16) Vantiva USA LLC - 5030 Sugarloaf Parkway Bldg 6 - Lawrenceville GA 30044 - US +3C-29-83 (hex) Samsung Electronics Co.,Ltd +3C2983 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -54-A6-5C (hex) Vantiva USA LLC -54A65C (base 16) Vantiva USA LLC - 5030 Sugarloaf Parkway Bldg 6 - Lawrenceville GA 30044 - US +1C-42-C2 (hex) Huawei Device Co., Ltd. +1C42C2 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -DC-EB-69 (hex) Vantiva USA LLC -DCEB69 (base 16) Vantiva USA LLC - 5030 Sugarloaf Parkway Bldg 6 - Lawrenceville GA 30044 +F4-01-CC (hex) Silicon Laboratories +F401CC (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -94-6A-77 (hex) Vantiva USA LLC -946A77 (base 16) Vantiva USA LLC - 5030 Sugarloaf Parkway Bldg 6 - Lawrenceville GA 30044 - US +D0-D4-FB (hex) Home Control Singapore Pte Ltd +D0D4FB (base 16) Home Control Singapore Pte Ltd + 1 Paya Lebar Link, PLQ 1, #04-01(Office 448) + Singapore 408533 + SG -D4-B9-2F (hex) Vantiva USA LLC -D4B92F (base 16) Vantiva USA LLC - 5030 Sugarloaf Parkway Bldg 6 - Lawrenceville GA 30044 - US +48-EA-A9 (hex) ShenZhen C&D Electronics CO.Ltd. +48EAA9 (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 -58-96-30 (hex) Vantiva USA LLC -589630 (base 16) Vantiva USA LLC - 5030 Sugarloaf Parkway Bldg 6 - Lawrenceville GA 30044 - US +50-DE-92 (hex) shenzhen worldelite electronics co., LTD +50DE92 (base 16) shenzhen worldelite electronics co., LTD + Office 5 F, Xiang Yu Industrial Park, Longsheng Road, Longgang Dist + Shenzhen Guangdong 51800 + CN -98-9D-5D (hex) Vantiva USA LLC -989D5D (base 16) Vantiva USA LLC - 5030 Sugarloaf Parkway Bldg 6 - Lawrenceville GA 30044 - US +34-F0-84 (hex) Samsung Electronics Co.,Ltd +34F084 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -48-4B-D4 (hex) Vantiva USA LLC -484BD4 (base 16) Vantiva USA LLC - 5030 Sugarloaf Parkway Bldg 6 - Lawrenceville GA 30044 - US +FC-6E-83 (hex) Samsung Electronics Co.,Ltd +FC6E83 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -E0-DB-D1 (hex) Vantiva USA LLC -E0DBD1 (base 16) Vantiva USA LLC - 5030 Sugarloaf Parkway Bldg 6 - Lawrenceville GA 30044 - US +BC-BF-2E (hex) ASUSTek COMPUTER INC. +BCBF2E (base 16) ASUSTek COMPUTER INC. + 15,Li-Te Rd., Peitou, Taipei 112, Taiwan + Taipei Taiwan 112 + TW -EC-A8-1F (hex) Vantiva USA LLC -ECA81F (base 16) Vantiva USA LLC - 4855 Peachtree Industrial Blvd, Suite 20 - Norcross GA 30902 +F8-C9-D6 (hex) IEEE Registration Authority +F8C9D6 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -C4-27-95 (hex) Vantiva USA LLC -C42795 (base 16) Vantiva USA LLC - 5030 Sugarloaf Parkway Bldg 6 - Lawrenceville GA 30044 +0C-39-3D (hex) eero inc. +0C393D (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US -E0-88-5D (hex) Vantiva USA LLC -E0885D (base 16) Vantiva USA LLC - 5030 Sugarloaf Parkway Bldg 6 - Lawrenceville GA 30044 - US +B0-BB-E5 (hex) Sagemcom Broadband SAS +B0BBE5 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR -80-29-94 (hex) Vantiva USA LLC -802994 (base 16) Vantiva USA LLC - 5030 Sugarloaf Parkway Bldg 6 - Lawrenceville GA 30044 - US +E8-D2-FF (hex) Sagemcom Broadband SAS +E8D2FF (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR -E4-0D-3B (hex) Ericsson AB -E40D3B (base 16) Ericsson AB - Torshamnsgatan 36 - Stockholm SE-164 80 - SE +98-42-65 (hex) Sagemcom Broadband SAS +984265 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR -EC-53-82 (hex) Honor Device Co., Ltd. -EC5382 (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 +30-93-BC (hex) Sagemcom Broadband SAS +3093BC (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR -9C-09-71 (hex) New H3C Technologies Co., Ltd -9C0971 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN +B4-1C-AF (hex) UAB TELTONIKA NETWORKS +B41CAF (base 16) UAB TELTONIKA NETWORKS + K. Baršausko g. 66 + Kaunas Kauno m. sav. LT -51436 + LT -98-AB-15 (hex) Fujian Youyike Technology Co.,Ltd -98AB15 (base 16) Fujian Youyike Technology Co.,Ltd - No. 97-1, Sizhi South Road, Liuyi Fourth Road, Lianqiao Village, Chengxi Street, Gutian County, Ningde City, Fujian Province - Ningde Fujian 352000 - CN +8C-08-3C (hex) EM Microelectronic +8C083C (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH -00-18-AD (hex) NIDEC INSTRUMENTS CORPORATION -0018AD (base 16) NIDEC INSTRUMENTS CORPORATION - 5329 Shimosuwa-cho - Suwa-gun Nagano 393-8511 - JP +38-35-FB (hex) Sagemcom Broadband SAS +3835FB (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR -A4-CC-B3 (hex) Xiaomi Communications Co Ltd -A4CCB3 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +34-28-44 (hex) Kyung In Electronics +342844 (base 16) Kyung In Electronics + #1411, Byucksan Digital Valley 2, 184, Gasan Digital2-ro, Geumcheon-gu + Seoul 08501 + KR -BC-31-98 (hex) IEEE Registration Authority -BC3198 (base 16) IEEE Registration Authority +44-49-C0 (hex) NVIDIA Corporation +4449C0 (base 16) NVIDIA Corporation + 2701 San Tomas Expressway + Santa Clara CA 95050 + US + +D8-B1-DE (hex) Hewlett Packard Enterprise +D8B1DE (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +F8-75-28 (hex) IEEE Registration Authority +F87528 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -EC-A2-A0 (hex) Taicang T&W Electronics -ECA2A0 (base 16) Taicang T&W Electronics - 89# Jiang Nan RD - Suzhou Jiangsu 215412 +C0-8B-27 (hex) FN-LINK TECHNOLOGY Ltd. +C08B27 (base 16) FN-LINK TECHNOLOGY Ltd. + No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China + Changsha Hunan 410329 CN -0C-70-43 (hex) Sony Interactive Entertainment Inc. -0C7043 (base 16) Sony Interactive Entertainment Inc. - 1-7-1 Konan - Minato-ku Tokyo 108-0075 +DC-F1-44 (hex) Ocean Solution Technology +DCF144 (base 16) Ocean Solution Technology + 203-1 Arifukucho + Sasebo City NAGASAKI 859-3241 JP -CC-40-85 (hex) WiZ -CC4085 (base 16) WiZ - Unit 1203-5, 12/F, Tower 1, Enterprise Square, 9 Sheung Yuet Road - Kowloon Bay Hong Kong 0000 - HK +F8-E7-3C (hex) Ufispace Co., LTD. +F8E73C (base 16) Ufispace Co., LTD. + 9F., No. 81 Jhongcheng Rd., Tucheng Dist., + New Taipei 23674 + TW -DC-CD-18 (hex) Nintendo Co.,Ltd -DCCD18 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP +F8-32-BA (hex) VusionGroup +F832BA (base 16) VusionGroup + Kalsdorfer Straße 12 + Fernitz-Mellach Steiermark 8072 + AT -54-49-FC (hex) Ubee Interactive Co., Limited -5449FC (base 16) Ubee Interactive Co., Limited - Flat/RM 1202, 12/F, AT Tower - North Point Hong Kong 180 - HK +F8-D8-11 (hex) Quectel Wireless Solutions Co.,Ltd. +F8D811 (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN -4C-49-6C (hex) Intel Corporate -4C496C (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +38-39-04 (hex) ittim +383904 (base 16) ittim + 1202, No.6, Zhongguancun South Street, Haidian District, + beijing 100080 + CN -00-11-A9 (hex) Nurivoice Co., Ltd -0011A9 (base 16) Nurivoice Co., Ltd - NURI Bld, 16 Sapyeong-daero - Seoul Seocho-gu 06552 - KR +78-6B-A5 (hex) Changchun Jetty Automotive Technology Co., LTD +786BA5 (base 16) Changchun Jetty Automotive Technology Co., LTD + 1st Floor, No. 957 Shunda Road,High-tech Development Zone + Changchun Jilin 130012 + CN -18-F9-35 (hex) Cisco Systems, Inc -18F935 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +D0-13-C1 (hex) Shenzhen Skyworth Digital Technology CO., Ltd +D013C1 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + +10-B0-6E (hex) Shenzhen Phaten Tech. LTD +10B06E (base 16) Shenzhen Phaten Tech. LTD + C-6 ideamonto industril 7002 Songbai Road Guangming District Shenzhen City Guangdong, China + Shenzhen 518108 + CN + +20-93-95 (hex) nVent +209395 (base 16) nVent + 1665 Utica Avenue, Suite 700 + St. Louis Park MN 55416 US -58-8B-1C (hex) Cisco Systems, Inc -588B1C (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +EC-86-C4 (hex) Mellanox Technologies, Inc. +EC86C4 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -C8-15-4E (hex) Intel Corporate -C8154E (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +D0-55-33 (hex) Palo Alto Networks +D05533 (base 16) Palo Alto Networks + 3000 Tannery Way + Santa Clara CA 95054 + US -78-9F-38 (hex) Shenzhen Feasycom Co., Ltd -789F38 (base 16) Shenzhen Feasycom Co., Ltd - Box 508, Building A, Phoenix Wisdom Valley, No. 50, Tiezi Road, Xixiang, Bao'an, Shenzhen - Shenzhen 518102 - CN +1C-E5-87 (hex) Cisco Meraki +1CE587 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US -54-4C-8A (hex) Microsoft Corporation -544C8A (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 +FC-18-6B (hex) Dot Origin Ltd +FC186B (base 16) Dot Origin Ltd + Unit 7 Coopers PlaceCombe Lane, Wormley + Godalming GU8 5SZ + GB + +70-7E-DA (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED +707EDA (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 + +00-04-22 (hex) Studio Technologies, Inc. +000422 (base 16) Studio Technologies, Inc. + 7440 Frontage Rd + Skokie IL 60077-3212 US -90-70-D3 (hex) Fiberhome Telecommunication Technologies Co.,LTD -9070D3 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN +0C-51-41 (hex) NXP Semiconductors Taiwan Ltd. +0C5141 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW -6C-48-A6 (hex) Fiberhome Telecommunication Technologies Co.,LTD -6C48A6 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN +C4-AA-43 (hex) Cisco Systems, Inc +C4AA43 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -3C-0D-2C (hex) Liquid-Markets GmbH -3C0D2C (base 16) Liquid-Markets GmbH - Obermühle 8 - Baar Zug 6340 - CH +3C-D3-5C (hex) Luna Innovations +3CD35C (base 16) Luna Innovations + 14351 Pipeline Ave + Chino CA 91710 + US -FC-B9-DF (hex) Motorola Mobility LLC, a Lenovo Company -FCB9DF (base 16) Motorola Mobility LLC, a Lenovo Company +B8-34-46 (hex) Motorola Mobility LLC, a Lenovo Company +B83446 (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza Chicago IL 60654 US -D0-81-C5 (hex) Juniper Networks -D081C5 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +38-17-B1 (hex) Sagemcom Broadband SAS +3817B1 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -84-AC-60 (hex) Guangxi Hesheng Electronics Co., Ltd. -84AC60 (base 16) Guangxi Hesheng Electronics Co., Ltd. - Hexin Tech Park, Binzhou Industrial Zone, Binyang County, Nanning City, Guangxi Zhuang Autonomous Region - Nanning 530000 - CN +D8-CF-61 (hex) Sagemcom Broadband SAS +D8CF61 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -9C-A3-89 (hex) Nokia -9CA389 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +EC-FC-2F (hex) Sagemcom Broadband SAS +ECFC2F (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -5C-10-1E (hex) zte corporation -5C101E (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +60-45-CD (hex) Sagemcom Broadband SAS +6045CD (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -20-17-46 (hex) Paradromics, Inc. -201746 (base 16) Paradromics, Inc. - 4030 W. Braker LaneBldg. 2 Suite 250 - Austin TX 78759 - US +B0-92-4A (hex) Sagemcom Broadband SAS +B0924A (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -C4-60-26 (hex) SKY UK LIMITED -C46026 (base 16) SKY UK LIMITED - Grant Way - Isleworth Middlesex TW7 5QD - GB +38-E1-F4 (hex) Sagemcom Broadband SAS +38E1F4 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -90-5A-08 (hex) Super Micro Computer, Inc. -905A08 (base 16) Super Micro Computer, Inc. - 980 Rock Ave - San Jose CA 95131 - US +34-5D-9E (hex) Sagemcom Broadband SAS +345D9E (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -FC-F7-63 (hex) KunGao Micro (JiangSu) Co., LTd -FCF763 (base 16) KunGao Micro (JiangSu) Co., LTd - 11th floor, Block C, Haichuang Building, #288 Dengyun Road, Yushan Town, Kunshan City - Kunshan Jiang Su 215300 - CN +7C-16-89 (hex) Sagemcom Broadband SAS +7C1689 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -80-8C-97 (hex) Kaon Group Co., Ltd. -808C97 (base 16) Kaon Group Co., Ltd. - 884-3, Seongnam-daero, Bundang-gu - Seongnam-si Gyeonggi-do 13517 - KR +44-D4-54 (hex) Sagemcom Broadband SAS +44D454 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -4C-12-E8 (hex) VIETNAM POST AND TELECOMMUNICATION INDUSTRY TECHNOLOGY JOIN STOCK COMPANY -4C12E8 (base 16) VIETNAM POST AND TELECOMMUNICATION INDUSTRY TECHNOLOGY JOIN STOCK COMPANY - High Tech Industrial Zone I, Hoa Lac High Tech Park, Ha Bang Commune - Ha Noi Thach That 100000 - VN +38-A6-59 (hex) Sagemcom Broadband SAS +38A659 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -20-02-FE (hex) Hangzhou Dangbei Network Technology Co., Ltd -2002FE (base 16) Hangzhou Dangbei Network Technology Co., Ltd - Floor 2, Block C, Wanfu Center, 228 Binkang Road, Binjiang District, - Hangzhou zhejiang 310051 - CN +84-1E-A3 (hex) Sagemcom Broadband SAS +841EA3 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -74-27-2C (hex) Advanced Micro Devices, Inc. -74272C (base 16) Advanced Micro Devices, Inc. - 7171 Southwest Pkwy - Austin TX 78735 +44-D4-53 (hex) Sagemcom Broadband SAS +44D453 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +64-66-24 (hex) Sagemcom Broadband SAS +646624 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +EC-BE-DD (hex) Sagemcom Broadband SAS +ECBEDD (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +F8-08-4F (hex) Sagemcom Broadband SAS +F8084F (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +A0-55-1F (hex) Sagemcom Broadband SAS +A0551F (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +B0-5B-99 (hex) Sagemcom Broadband SAS +B05B99 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +AC-D7-5B (hex) Sagemcom Broadband SAS +ACD75B (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +7C-D4-A8 (hex) Sagemcom Broadband SAS +7CD4A8 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +64-FA-2B (hex) Sagemcom Broadband SAS +64FA2B (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +10-2B-AA (hex) Sagemcom Broadband SAS +102BAA (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +38-22-28 (hex) Telink Micro LLC +382228 (base 16) Telink Micro LLC + 2975 Scott Blvd #120 + Santa Clara CA 95054 US -20-88-10 (hex) Dell Inc. -208810 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 +BC-9D-37 (hex) Telink Micro LLC +BC9D37 (base 16) Telink Micro LLC + 2975 Scott Blvd #120 + Santa Clara CA 95054 US -A0-62-60 (hex) Private -A06260 (base 16) Private +74-DC-13 (hex) Telink Micro LLC +74DC13 (base 16) Telink Micro LLC + 2975 Scott Blvd #120 + Santa Clara CA 95054 + US -E4-45-19 (hex) Beijing Xiaomi Electronics Co.,Ltd -E44519 (base 16) Beijing Xiaomi Electronics Co.,Ltd - Xiaomi Campus - Beijing Beijing 100085 - CN +CC-00-F1 (hex) Sagemcom Broadband SAS +CC00F1 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -C0-5B-44 (hex) Beijing Xiaomi Mobile Software Co., Ltd -C05B44 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN +B0-FC-88 (hex) Sagemcom Broadband SAS +B0FC88 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -E4-B2-24 (hex) HUAWEI TECHNOLOGIES CO.,LTD -E4B224 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +24-4E-CD (hex) Sagemcom Broadband SAS +244ECD (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -A8-3E-D3 (hex) HUAWEI TECHNOLOGIES CO.,LTD -A83ED3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +48-AA-BB (hex) Sagemcom Broadband SAS +48AABB (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -64-67-CD (hex) HUAWEI TECHNOLOGIES CO.,LTD -6467CD (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +04-E3-1A (hex) Sagemcom Broadband SAS +04E31A (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -34-EC-B6 (hex) Phyplus Microelectronics Limited -34ECB6 (base 16) Phyplus Microelectronics Limited - 304 Building 1 No.608 Sheng Xia Road - Shanghai 200000 - CN +80-20-DA (hex) Sagemcom Broadband SAS +8020DA (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -4C-D2-FB (hex) UNIONMAN TECHNOLOGY CO.,LTD -4CD2FB (base 16) UNIONMAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 - CN +84-A0-6E (hex) Sagemcom Broadband SAS +84A06E (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -9C-1E-CF (hex) Valeo Telematik und Akustik GmbH -9C1ECF (base 16) Valeo Telematik und Akustik GmbH - Max-Planck-Straße 28-32 - Friedrichsdorf 61381 - DE +E8-AD-A6 (hex) Sagemcom Broadband SAS +E8ADA6 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -00-0D-39 (hex) Nevion -000D39 (base 16) Nevion - Lysaker Torg 5 - Lysaker NO-1366 - NO +2C-79-D7 (hex) Sagemcom Broadband SAS +2C79D7 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -80-76-77 (hex) hangzhou puwell cloud tech co., ltd. -807677 (base 16) hangzhou puwell cloud tech co., ltd. - 1405 Chuling Data Mansion Wulianwang street 259 - Hangzhou Zhejiang 315000 - CN +D8-7D-7F (hex) Sagemcom Broadband SAS +D87D7F (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -F4-2B-8C (hex) Samsung Electronics Co.,Ltd -F42B8C (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +7C-03-D8 (hex) Sagemcom Broadband SAS +7C03D8 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -E4-1A-1D (hex) NOVEA ENERGIES -E41A1D (base 16) NOVEA ENERGIES - 3rue Joseph Fourier - BEAUCOUZE Pays de la Loire 49070 +C0-AC-54 (hex) Sagemcom Broadband SAS +C0AC54 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 FR -F4-CA-E7 (hex) Arcadyan Corporation -F4CAE7 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +2C-39-96 (hex) Sagemcom Broadband SAS +2C3996 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -3C-EF-42 (hex) TCT mobile ltd -3CEF42 (base 16) TCT mobile ltd - No.86 hechang 7th road, zhongkai, Hi-Tech District - Hui Zhou Guang Dong 516006 - CN +B4-3B-52 (hex) Sagemcom Broadband SAS +B43B52 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -B0-A3-F2 (hex) Huaqin Technology Co. LTD -B0A3F2 (base 16) Huaqin Technology Co. LTD - 11th Floor, Unit, No.399 Keyuan Road - Pudong Shanghai 201203 - CN +F8-5B-1B (hex) Espressif Inc. +F85B1B (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -F8-66-91 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -F86691 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County - Chengdu Sichuan 611330 - CN +44-1B-F6 (hex) Espressif Inc. +441BF6 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -E4-38-19 (hex) Shenzhen Hi-Link Electronic CO.,Ltd. -E43819 (base 16) Shenzhen Hi-Link Electronic CO.,Ltd. - Room 1705, 1706, 1709A, 17th Floor, Building E, Xinghe WORLD, Minle Community, Minzhi Street, Longhua District - Shenzhen Guangdong 518000 - CN +70-4B-CA (hex) Espressif Inc. +704BCA (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -84-F1-75 (hex) Jiangxi Xunte Intelligent Terminal Co., Ltd -84F175 (base 16) Jiangxi Xunte Intelligent Terminal Co., Ltd - 16 # 1-3/F, Zhongxing Nanchang Software Industrial Park, No. 688, Aixihu North Road, Nanchang High-tech Industrial Development Zone, Nanchang, Jiangxi Province - Nanchang Jiangxi 330000 - CN +8C-FD-49 (hex) Espressif Inc. +8CFD49 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -C8-98-28 (hex) zte corporation -C89828 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +1C-8F-57 (hex) Espressif Inc. +1C8F57 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -DC-36-42 (hex) zte corporation -DC3642 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +10-BD-A3 (hex) Espressif Inc. +10BDA3 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -78-8A-86 (hex) China Dragon Technology Limited -788A86 (base 16) China Dragon Technology Limited - B4 Bldg.Haoshan 1st Industry Park, - Shenzhen Guangdong 518104 - CN +8C-94-DF (hex) Espressif Inc. +8C94DF (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -60-82-46 (hex) Apple, Inc. -608246 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +28-84-85 (hex) Espressif Inc. +288485 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -98-B3-79 (hex) Apple, Inc. -98B379 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +B0-98-2B (hex) Sagemcom Broadband SAS +B0982B (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -6C-2A-DF (hex) IEEE Registration Authority -6C2ADF (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +18-90-D8 (hex) Sagemcom Broadband SAS +1890D8 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -84-72-93 (hex) Texas Instruments -847293 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +08-3E-5D (hex) Sagemcom Broadband SAS +083E5D (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -60-DC-81 (hex) AltoBeam Inc. -60DC81 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN +C0-D0-44 (hex) Sagemcom Broadband SAS +C0D044 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -20-E4-6F (hex) vivo Mobile Communication Co., Ltd. -20E46F (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN +A0-1B-29 (hex) Sagemcom Broadband SAS +A01B29 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -88-30-37 (hex) Juniper Networks -883037 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +34-8A-AE (hex) Sagemcom Broadband SAS +348AAE (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -E8-24-04 (hex) Quectel Wireless Solutions Co.,Ltd. -E82404 (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN +88-56-A6 (hex) Espressif Inc. +8856A6 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -F8-2E-0C (hex) Texas Instruments -F82E0C (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +E0-8C-FE (hex) Espressif Inc. +E08CFE (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -90-06-F2 (hex) Texas Instruments -9006F2 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +80-45-6B (hex) Espressif Inc. +80456B (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -E8-6E-3A (hex) Sony Interactive Entertainment Inc. -E86E3A (base 16) Sony Interactive Entertainment Inc. - 1-7-1 Konan - Minato-ku Tokyo 108-0075 - JP +58-2A-BD (hex) Espressif Inc. +582ABD (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -BC-B1-D3 (hex) Cisco Meraki -BCB1D3 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US +00-15-56 (hex) Sagemcom Broadband SAS +001556 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -B8-7B-D4 (hex) Google, Inc. -B87BD4 (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US +C4-5B-BE (hex) Espressif Inc. +C45BBE (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -C8-13-8B (hex) Shenzhen Skyworth Digital Technology CO., Ltd -C8138B (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 - CN +BC-FF-4D (hex) Espressif Inc. +BCFF4D (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -34-AA-31 (hex) Shenzhen Skyworth Digital Technology CO., Ltd -34AA31 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 - CN +A8-48-FA (hex) Espressif Inc. +A848FA (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -A0-4C-0C (hex) Shenzhen Skyworth Digital Technology CO., Ltd -A04C0C (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 - CN +C8-C9-A3 (hex) Espressif Inc. +C8C9A3 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -6C-C2-42 (hex) Shenzhen Skyworth Digital Technology CO., Ltd -6CC242 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 - CN +8C-4B-14 (hex) Espressif Inc. +8C4B14 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -04-9D-05 (hex) Apple, Inc. -049D05 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +90-38-0C (hex) Espressif Inc. +90380C (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -E0-73-E7 (hex) HP Inc. -E073E7 (base 16) HP Inc. - 10300 Energy Dr - Spring TX 77389 - US +10-91-A8 (hex) Espressif Inc. +1091A8 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -54-08-3B (hex) IEEE Registration Authority -54083B (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +58-CF-79 (hex) Espressif Inc. +58CF79 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -54-EF-43 (hex) HUAWEI TECHNOLOGIES CO.,LTD -54EF43 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +58-E6-C5 (hex) Espressif Inc. +58E6C5 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -D8-1B-B5 (hex) HUAWEI TECHNOLOGIES CO.,LTD -D81BB5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +AC-D0-74 (hex) Espressif Inc. +ACD074 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -84-64-DD (hex) HUAWEI TECHNOLOGIES CO.,LTD -8464DD (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +24-0A-C4 (hex) Espressif Inc. +240AC4 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -68-A4-6A (hex) HUAWEI TECHNOLOGIES CO.,LTD -68A46A (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +24-6F-28 (hex) Espressif Inc. +246F28 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -F4-8E-38 (hex) Dell Inc. -F48E38 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +FC-F5-C4 (hex) Espressif Inc. +FCF5C4 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -28-F1-0E (hex) Dell Inc. -28F10E (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +70-03-9F (hex) Espressif Inc. +70039F (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -90-B1-1C (hex) Dell Inc. -90B11C (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +8C-CE-4E (hex) Espressif Inc. +8CCE4E (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -64-00-6A (hex) Dell Inc. -64006A (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +80-64-6F (hex) Espressif Inc. +80646F (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -10-98-36 (hex) Dell Inc. -109836 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +48-E7-29 (hex) Espressif Inc. +48E729 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -C8-1F-66 (hex) Dell Inc. -C81F66 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +68-C6-3A (hex) Espressif Inc. +68C63A (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -F8-DB-88 (hex) Dell Inc. -F8DB88 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +34-85-18 (hex) Espressif Inc. +348518 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -00-14-22 (hex) Dell Inc. -001422 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +10-06-1C (hex) Espressif Inc. +10061C (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -00-15-C5 (hex) Dell Inc. -0015C5 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +DC-06-75 (hex) Espressif Inc. +DC0675 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -78-45-C4 (hex) Dell Inc. -7845C4 (base 16) Dell Inc. - One Dell way - Round Rock 78682 - US +58-8C-81 (hex) Espressif Inc. +588C81 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -5C-26-0A (hex) Dell Inc. -5C260A (base 16) Dell Inc. - One Dell Way, MS RR5-45 - Round Rock 78682 - US +FC-01-2C (hex) Espressif Inc. +FC012C (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -00-1E-4F (hex) Dell Inc. -001E4F (base 16) Dell Inc. - One Dell Way, MS RR5-45 - Round Rock 78682 - US +80-F3-DA (hex) Espressif Inc. +80F3DA (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -00-26-B9 (hex) Dell Inc. -0026B9 (base 16) Dell Inc. - One Dell Way, MS RR5-45 - Round Rock 78682 - US +80-B5-4E (hex) Espressif Inc. +80B54E (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -D0-67-E5 (hex) Dell Inc. -D067E5 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +14-08-08 (hex) Espressif Inc. +140808 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -54-4E-45 (hex) Private -544E45 (base 16) Private +AC-15-18 (hex) Espressif Inc. +AC1518 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -20-97-27 (hex) TELTONIKA NETWORKS UAB -209727 (base 16) TELTONIKA NETWORKS UAB - K. Baršausko st. 66, Kaunas - Kaunas LT-51436 - LT +F0-9E-9E (hex) Espressif Inc. +F09E9E (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -00-1A-E8 (hex) Unify Software and Solutions GmbH & Co. KG -001AE8 (base 16) Unify Software and Solutions GmbH & Co. KG - Otto-Hahn-Ring 6 - Munich 81739 - DE +6C-E4-DA (hex) NEC Platforms, Ltd. +6CE4DA (base 16) NEC Platforms, Ltd. + 14-1, Shiba 4-chome + Minato-ku Tokyo 108-8556 + JP -2C-FE-4F (hex) Xiaomi Communications Co Ltd -2CFE4F (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +08-10-86 (hex) NEC Platforms, Ltd. +081086 (base 16) NEC Platforms, Ltd. + 14-1, Shiba 4-chome + Minato-ku Tokyo 108-8556 + JP -90-7E-43 (hex) zte corporation -907E43 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +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 -94-3E-E4 (hex) WiSA Technologies Inc -943EE4 (base 16) WiSA Technologies Inc - 15268 Northwest Greenbrier Parkway - Beaverton OR 97006 - US - -A8-BD-3A (hex) UNION MAN TECHNOLOGY CO.,LTD -A8BD3A (base 16) UNION MAN TECHNOLOGY CO.,LTD - 18F, HUAYANG TOWER,YANDAYI ROAD - Huizhou Guangdong 516007 +9C-1C-37 (hex) AltoBeam Inc. +9C1C37 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -48-CA-C6 (hex) UNION MAN TECHNOLOGY CO.,LTD -48CAC6 (base 16) UNION MAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 +A4-EF-15 (hex) AltoBeam Inc. +A4EF15 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -D4-38-44 (hex) UNION MAN TECHNOLOGY CO.,LTD -D43844 (base 16) UNION MAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 +44-29-1E (hex) AltoBeam Inc. +44291E (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -6C-3C-8C (hex) Dell Inc. -6C3C8C (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US - -C4-5A-B1 (hex) Dell Inc. -C45AB1 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US - -A8-B0-28 (hex) CubePilot Pty Ltd -A8B028 (base 16) CubePilot Pty Ltd - 153 Mercer Street - Geelong Victoria 3220 - AU - -40-7F-5F (hex) Juniper Networks -407F5F (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +2C-79-BE (hex) TP-LINK TECHNOLOGIES CO.,LTD. +2C79BE (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN -2C-EA-7F (hex) Dell Inc. -2CEA7F (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +B8-D3-EF (hex) UNISEM (M) BERHAD +B8D3EF (base 16) UNISEM (M) BERHAD + LOT 302285, JALAN INDUSTRI 1/1,KAWASAN PERINDUSTRIAN GOPENG, + GOPENG PERAK 31600 + MY -F0-D4-E2 (hex) Dell Inc. -F0D4E2 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +5C-40-97 (hex) ACCTON TECHNOLOGY CORPORATION +5C4097 (base 16) ACCTON TECHNOLOGY CORPORATION + No.1, Creation Road 3, Hsinchu Science Park, + Hsinchu 30077 + TW -CC-48-3A (hex) Dell Inc. -CC483A (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +04-FB-66 (hex) Ericsson AB +04FB66 (base 16) Ericsson AB + Torshamnsgatan 36 + Stockholm SE-164 80 + SE -30-D0-42 (hex) Dell Inc. -30D042 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +5C-E7-53 (hex) Shenzhen Intellirocks Tech. Co. Ltd. +5CE753 (base 16) Shenzhen Intellirocks Tech. Co. Ltd. + + + -8C-04-BA (hex) Dell Inc. -8C04BA (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 +38-6F-F4 (hex) Amazon Technologies Inc. +386FF4 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -E4-54-E8 (hex) Dell Inc. -E454E8 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +C4-69-40 (hex) Xiaomi Communications Co Ltd +C46940 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -A4-BB-6D (hex) Dell Inc. -A4BB6D (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +24-69-68 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +246968 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN -E4-43-4B (hex) Dell Inc. -E4434B (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +D8-07-B6 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +D807B6 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN -E0-1F-6A (hex) Huawei Device Co., Ltd. -E01F6A (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +64-6E-97 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +646E97 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -00-56-6D (hex) Huawei Device Co., Ltd. -00566D (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +D0-37-45 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +D03745 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -90-CC-7A (hex) Huawei Device Co., Ltd. -90CC7A (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +60-3A-7C (hex) TP-LINK TECHNOLOGIES CO.,LTD. +603A7C (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -8C-C5-8C (hex) ShenZhen Elsky Technology Co.,LTD -8CC58C (base 16) ShenZhen Elsky Technology Co.,LTD - 401, building A, wanguocheng, No. 9, Pingji Avenue, Shanglilang community, Nanwan street, Longgang District, Shenzhen - ShenZhen GuangDong 518000 +AC-84-C6 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +AC84C6 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -2C-70-4F (hex) zte corporation -2C704F (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +6C-B1-58 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +6CB158 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -64-52-34 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -645234 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County - Chengdu Sichuan 611330 +68-DD-B7 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +68DDB7 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -38-16-72 (hex) Shenzhen SuperElectron Technology Co.,Ltd. -381672 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. - 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city - Shenzhen Guangdong 518000 +14-D8-64 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +14D864 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -6C-29-D2 (hex) Cisco Systems, Inc -6C29D2 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +B0-EC-69 (hex) Apple, Inc. +B0EC69 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -08-CC-81 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -08CC81 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN - -F4-54-33 (hex) Rockwell Automation -F45433 (base 16) Rockwell Automation - 1 Allen-Bradley Dr. - Mayfield Heights OH 44124-6118 +20-84-5F (hex) Apple, Inc. +20845F (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -34-C0-F9 (hex) Rockwell Automation -34C0F9 (base 16) Rockwell Automation - 1 Allen-Bradley Dr. - Mayfield Heights OH 44124-6118 +B0-BB-A9 (hex) Apple, Inc. +B0BBA9 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -5C-88-16 (hex) Rockwell Automation -5C8816 (base 16) Rockwell Automation - 1 Allen-Bradley Dr. - Mayfield Heights OH 44124-6118 +04-23-A3 (hex) Mellanox Technologies, Inc. +0423A3 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -D4-53-47 (hex) Merytronic 2012, S.L. -D45347 (base 16) Merytronic 2012, S.L. - Parque empresarial BoroaParcela 2C-1 - Amorebieta Bizkaia 48340 - ES - -D0-7B-6F (hex) Zhuhai Yunmai Technology Co.,Ltd -D07B6F (base 16) Zhuhai Yunmai Technology Co.,Ltd - Unit 1201-1203, Youte Headquarters Building, No. 88 Xingye Road, Xiangzhou District - Zhuhai Guangdong 519000 - CN - -04-A5-26 (hex) Nokia -04A526 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +2C-E5-BD (hex) Ubiquiti Inc +2CE5BD (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 + US -20-BA-36 (hex) u-blox AG -20BA36 (base 16) u-blox AG - Zuercherstrasse, 68 - Thalwil Switzerland CH-8800 - CH +00-01-30 (hex) Extreme Networks Headquarters +000130 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US -D8-0A-E6 (hex) zte corporation -D80AE6 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +FC-0A-81 (hex) Extreme Networks Headquarters +FC0A81 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US -48-9E-9D (hex) Hui Zhou Gaoshengda Technology Co.,LTD -489E9D (base 16) Hui Zhou Gaoshengda Technology Co.,LTD - No.2,Jin-da Road,Huinan Industrial Park - Hui Zhou Guangdong 516025 +00-74-9C (hex) Ruijie Networks Co.,LTD +00749C (base 16) Ruijie Networks Co.,LTD + 19# Building,Star-net Science Plaza,Juyuanzhou, 618 Jinshan Road + Fuzhou Fujian 350002 CN -18-F4-6B (hex) Telenor Connexion AB -18F46B (base 16) Telenor Connexion AB - 116 88 - Stockholm Sverige SE-116 88 - SE +00-0F-EA (hex) GIGA-BYTE TECHNOLOGY CO.,LTD. +000FEA (base 16) GIGA-BYTE TECHNOLOGY CO.,LTD. + No.215,Nan-Ping Road,Ping-Jen City, + Ping-Jen Taoyuan 324 + TW -28-EA-0B (hex) Microsoft Corporation -28EA0B (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 +48-9B-D5 (hex) Extreme Networks Headquarters +489BD5 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 US -A4-14-37 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -A41437 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.469,Jianghui Road - Hangzhou Zhejiang 310052 - CN +DC-B8-08 (hex) Extreme Networks Headquarters +DCB808 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US -F8-4D-FC (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -F84DFC (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN +F4-6E-95 (hex) Extreme Networks Headquarters +F46E95 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US -84-9A-40 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -849A40 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN +E0-1C-41 (hex) Extreme Networks Headquarters +E01C41 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US -C0-51-7E (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -C0517E (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN +BC-F3-10 (hex) Extreme Networks Headquarters +BCF310 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US -2C-A5-9C (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -2CA59C (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN +D8-54-A2 (hex) Extreme Networks Headquarters +D854A2 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US -40-AC-BF (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -40ACBF (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN +C8-66-5D (hex) Extreme Networks Headquarters +C8665D (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US -98-F1-12 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -98F112 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN +8C-49-7A (hex) Extreme Networks Headquarters +8C497A (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US -98-9D-E5 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -989DE5 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN +B0-27-CF (hex) Extreme Networks Headquarters +B027CF (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US -3C-1B-F8 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -3C1BF8 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN +C8-51-FB (hex) Extreme Networks Headquarters +C851FB (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US -9C-54-16 (hex) Cisco Systems, Inc -9C5416 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +DC-23-3B (hex) Extreme Networks Headquarters +DC233B (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 US -48-BD-A7 (hex) Honor Device Co., Ltd. -48BDA7 (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 +E4-DB-AE (hex) Extreme Networks Headquarters +E4DBAE (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US -EC-21-50 (hex) vivo Mobile Communication Co., Ltd. -EC2150 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN +38-AD-2B (hex) Hitron Technologies. Inc +38AD2B (base 16) Hitron Technologies. Inc + No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C + Hsin-chu Taiwan 300 + TW -A0-AF-12 (hex) HUAWEI TECHNOLOGIES CO.,LTD -A0AF12 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +E8-45-8B (hex) MitraStar Technology Corp. +E8458B (base 16) MitraStar Technology Corp. + No. 6, Innovation Road II, + Hsinchu 300 + TW -60-96-A4 (hex) HUAWEI TECHNOLOGIES CO.,LTD -6096A4 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +D0-44-33 (hex) Clourney Semiconductor +D04433 (base 16) Clourney Semiconductor + Floor 6, Building 8, 2777 Nong, Jinxiu East Road, Pudong Dist. + Shanghai Shanghai 201206 CN -5C-5E-AB (hex) Juniper Networks -5C5EAB (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +40-86-CB (hex) D-Link Corporation +4086CB (base 16) D-Link Corporation + No.289, Sinhu 3rd Rd., Neihu District, + Taipei City 114 + TW -78-19-F7 (hex) Juniper Networks -7819F7 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +48-41-7B (hex) Nokia Solutions and Networks GmbH & Co. KG +48417B (base 16) Nokia Solutions and Networks GmbH & Co. KG + Werinherstrasse 91 + München Bavaria D-81541 + DE -2C-21-72 (hex) Juniper Networks -2C2172 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +60-8C-DF (hex) Beamtrail-Sole Proprietorship +608CDF (base 16) Beamtrail-Sole Proprietorship + Level 7 Aldar HQ + Abu Dhabi Abu Dhabi 29836 + AE -4C-96-14 (hex) Juniper Networks -4C9614 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +30-27-CF (hex) Canopy Growth Corp +3027CF (base 16) Canopy Growth Corp + 350 Leggett Drive + Ottawa Ontario K2K 2W7 + CA -78-45-58 (hex) Ubiquiti Inc -784558 (base 16) Ubiquiti Inc - 685 Third Avenue, 27th Floor - New York NY New York NY 10017 - US +00-12-77 (hex) Beijer Electronics Corp. +001277 (base 16) Beijer Electronics Corp. + 11F-1, No. 108, MinQuan Rd. + Xindian City Taipei 231 + TW -AC-8B-A9 (hex) Ubiquiti Inc -AC8BA9 (base 16) Ubiquiti Inc - 685 Third Avenue, 27th Floor - New York NY New York NY 10017 - US +60-45-2E (hex) Intel Corporate +60452E (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -9C-05-D6 (hex) Ubiquiti Inc -9C05D6 (base 16) Ubiquiti Inc - 685 Third Avenue, 27th Floor - New York NY New York NY 10017 - US +1C-AB-48 (hex) TECNO MOBILE LIMITED +1CAB48 (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 + Hong Kong Hong Kong 999077 + HK -28-70-4E (hex) Ubiquiti Inc -28704E (base 16) Ubiquiti Inc - 685 Third Avenue, 27th Floor - New York NY New York NY 10017 - US +88-B4-36 (hex) FUJIFILM Corporation +88B436 (base 16) FUJIFILM Corporation + 1-324,Uetake,Kita-ku + Saitama Saitama 331-9624 + JP -00-17-CB (hex) Juniper Networks -0017CB (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +CC-D3-42 (hex) Cisco Systems, Inc +CCD342 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -00-1F-12 (hex) Juniper Networks -001F12 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +40-B8-C2 (hex) OSMOZIS +40B8C2 (base 16) OSMOZIS + 7 Avenue de l'Europe + Clapiers 34830 + FR -00-24-DC (hex) Juniper Networks -0024DC (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +8C-E9-EE (hex) Intel Corporate +8CE9EE (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -00-12-1E (hex) Juniper Networks -00121E (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +94-45-60 (hex) Google, Inc. +944560 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US -00-10-DB (hex) Juniper Networks -0010DB (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +5C-33-7B (hex) Google, Inc. +5C337B (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US -00-22-83 (hex) Juniper Networks -002283 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +2C-9C-58 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +2C9C58 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 + CN -10-0E-7E (hex) Juniper Networks -100E7E (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +B8-F4-4F (hex) u-blox AG +B8F44F (base 16) u-blox AG + Zuercherstrasse, 68 + Thalwil Switzerland CH-8800 + CH -44-F4-77 (hex) Juniper Networks -44F477 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +54-09-29 (hex) Inventus Power Eletronica do Brasil LTDA +540929 (base 16) Inventus Power Eletronica do Brasil LTDA + Av Buriti, 4285 Distrito Industrial + Manaus Amazonas 69075000 + BR -5C-6A-EC (hex) IEEE Registration Authority -5C6AEC (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +28-31-F8 (hex) HUAWEI TECHNOLOGIES CO.,LTD +2831F8 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -CC-9F-7A (hex) Chiun Mai Communication System, Inc -CC9F7A (base 16) Chiun Mai Communication System, Inc - No.4, Minsheng St., Tucheng District - New Taipei City 23678 - TW +C4-AA-99 (hex) HUAWEI TECHNOLOGIES CO.,LTD +C4AA99 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -54-14-A7 (hex) Nanjing Qinheng Microelectronics Co., Ltd. -5414A7 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. - No.18, Ningshuang Road - Nanjing Jiangsu 210012 +A8-F5-E1 (hex) Shenzhen Shokz Co., Ltd. +A8F5E1 (base 16) Shenzhen Shokz Co., Ltd. + Baoan District Shiyan street Shancheng Industrial zone 26 building + Shenzhen Guangdong 518108 CN -4C-E7-05 (hex) Siemens Industrial Automation Products Ltd., Chengdu -4CE705 (base 16) Siemens Industrial Automation Products Ltd., Chengdu - Tianyuan Road No.99, High Tech Zone West - Chengdu Sichuan Province 611731 +60-66-82 (hex) SHENZHEN ATEKO PHOTOELECTRICITY CO.,LTD +606682 (base 16) SHENZHEN ATEKO PHOTOELECTRICITY CO.,LTD + 4-5F,E1 Building,TCL International E City,No.1001 Zhongshanyuan Road,Nanshan District,Shenzhen + SHENZHEN GUANGDONG 518052 CN -80-DB-17 (hex) Juniper Networks -80DB17 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +78-8D-AF (hex) Sagemcom Broadband SAS +788DAF (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR -B8-F0-15 (hex) Juniper Networks -B8F015 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +2C-BA-CA (hex) Cosonic Electroacoustic Technology Co., Ltd. +2CBACA (base 16) Cosonic Electroacoustic Technology Co., Ltd. + No.151, Shipai Section, Dongyuan Avenue, Shipai Town + Dongguan Guangdong 523331 + CN -E4-23-3C (hex) Juniper Networks -E4233C (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +04-FA-3F (hex) OptiCore Inc. +04FA3F (base 16) OptiCore Inc. + 97 Jungbudaero448beongil, Yeongtonggu + Suwonsi Gyeonggido 16521 + KR -98-86-8B (hex) Juniper Networks -98868B (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +44-1D-B1 (hex) APTIV SERVICES US, LLC +441DB1 (base 16) APTIV SERVICES US, LLC + 5725 Innovation Drive + Troy MI 48098 US -00-58-28 (hex) Axon Networks Inc. -005828 (base 16) Axon Networks Inc. - 15420 Laguna Canyon rd. - Irvine CA 92618 - US +00-9C-C0 (hex) vivo Mobile Communication Co., Ltd. +009CC0 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN -30-B6-4F (hex) Juniper Networks -30B64F (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +E8-9E-13 (hex) CRESYN +E89E13 (base 16) CRESYN + 8-22,Jamwon-dong + Seoul Seocho-Gu #137-902 + KR -08-B2-58 (hex) Juniper Networks -08B258 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +BC-18-96 (hex) HUAWEI TECHNOLOGIES CO.,LTD +BC1896 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -F4-A7-39 (hex) Juniper Networks -F4A739 (base 16) Juniper Networks +BC-0F-FE (hex) Juniper Networks +BC0FFE (base 16) Juniper Networks 1133 Innovation Way Sunnyvale CA 94089 US -4C-16-FC (hex) Juniper Networks -4C16FC (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +E0-D3-B4 (hex) Cisco Meraki +E0D3B4 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 US -C8-E7-F0 (hex) Juniper Networks -C8E7F0 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +FC-8C-11 (hex) Microsoft Corporation +FC8C11 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 US -7C-25-86 (hex) Juniper Networks -7C2586 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +A4-94-DC (hex) Infinite Clouds +A494DC (base 16) Infinite Clouds + Office 406 Block 333 Road 3307, Um Al Hassam, Kingdom of Bahrain + Manama 973 + BH -EC-38-73 (hex) Juniper Networks -EC3873 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +4C-43-41 (hex) Calix Inc. +4C4341 (base 16) Calix Inc. + 2777 Orchard Pkwy + San Jose CA 95131 US -C0-03-80 (hex) Juniper Networks -C00380 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +E4-6C-D1 (hex) Calix Inc. +E46CD1 (base 16) Calix Inc. + 2777 Orchard Pkwy + San Jose CA 95131 US -20-D8-0B (hex) Juniper Networks -20D80B (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +D0-F2-7F (hex) BrewLogix, LLC +D0F27F (base 16) BrewLogix, LLC + 6 East Washing Street, Suite 200 + Indianapolis IN 46204 US -94-BF-94 (hex) Juniper Networks -94BF94 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +7C-FC-FD (hex) Fiberhome Telecommunication Technologies Co.,LTD +7CFCFD (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN -4C-6D-58 (hex) Juniper Networks -4C6D58 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +48-87-B8 (hex) TCL King Electrical Appliances(Huizhou)Co.,Ltd +4887B8 (base 16) TCL King Electrical Appliances(Huizhou)Co.,Ltd + B Area, 10th floor, TCL multimedia Building, TCL International E City, #1001 Zhonshanyuan road,Shenzhen + guangdong China 518058 + CN -40-8F-9D (hex) Juniper Networks -408F9D (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +70-58-A4 (hex) Actiontec Electronics Inc. +7058A4 (base 16) Actiontec Electronics Inc. + 2445 Augustine Dr #501 + Santa Clara CA 95054 US -AC-78-D1 (hex) Juniper Networks -AC78D1 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +18-80-25 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +188025 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN -68-22-8E (hex) Juniper Networks -68228E (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +F8-F0-9D (hex) Hangzhou Prevail Communication Technology Co., Ltd +F8F09D (base 16) Hangzhou Prevail Communication Technology Co., Ltd + No. 11809,Jianshe 4th road,Guali twon,Xiaoshan district + Hangzhou City Zhejiang Province 311241 + CN -D8-53-9A (hex) Juniper Networks -D8539A (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +E4-78-76 (hex) Arista Networks +E47876 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 US -F8-C1-16 (hex) Juniper Networks -F8C116 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +A4-A9-30 (hex) Beijing Xiaomi Mobile Software Co., Ltd +A4A930 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN -88-0A-A3 (hex) Juniper Networks -880AA3 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +A0-22-52 (hex) Astra Wireless Technology FZ-LLC +A02252 (base 16) Astra Wireless Technology FZ-LLC + T1-4F-63, RAKEZ Amenity Center, Al Hamra Industrial Zone-FZ + Ras Al Khaimah 7100 + AE -FC-33-42 (hex) Juniper Networks -FC3342 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +BC-10-2F (hex) SJI Industry Company +BC102F (base 16) SJI Industry Company + 54-33, Dongtanhana 1-gil + Hwaseong-si Gyeonggi-do 18423 + KR -3C-8C-93 (hex) Juniper Networks -3C8C93 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +FC-93-6B (hex) Samsung Electronics Co.,Ltd +FC936B (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -0C-81-26 (hex) Juniper Networks -0C8126 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +BC-B6-FB (hex) P4Q ELECTRONICS, S.L. +BCB6FB (base 16) P4Q ELECTRONICS, S.L. + Calle Nuestra Señora de la Guía Número 19 + Alonsotegi Bizkaia 48810 + ES -18-2A-D3 (hex) Juniper Networks -182AD3 (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +7C-75-2D (hex) Samsung Electronics Co.,Ltd +7C752D (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -74-95-A7 (hex) Keyence Corporation -7495A7 (base 16) Keyence Corporation - 1-3-14, Higashinakajima, Higashiyodogawa - Osaka Osaka 5338555 - JP +84-EE-E4 (hex) Samsung Electronics Co.,Ltd +84EEE4 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -A0-A3-B3 (hex) Espressif Inc. -A0A3B3 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +44-35-D3 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +4435D3 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN -34-98-7A (hex) Espressif Inc. -34987A (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +88-F0-0F (hex) Miraeil +88F00F (base 16) Miraeil + 70, Gasan digital 2-ro, Geumcheon-gusuite 1012 + Seoul 08589 + KR -68-59-32 (hex) Sunitec Enterprise Co.,Ltd -685932 (base 16) Sunitec Enterprise Co.,Ltd - 3F.,No.98-1,Mincyuan Rd.Sindian City - Taipei County 231 231141 +18-A7-88 (hex) Shenzhen MEK Intellisys Pte Ltd +18A788 (base 16) Shenzhen MEK Intellisys Pte Ltd + Room 6C, 6th Floor, KeChuang Mansion, Quanzhi Technology Park, HouTing, Shajing Town, BaoAn District + Shenzhen GuangDong 518104 CN -30-B2-16 (hex) Hitachi Energy Germany AG -30B216 (base 16) Hitachi Energy Germany AG - Havellandstr. 10-14 - Mannheim 68309 - DE +BC-85-29 (hex) Jiangxi Remote lntelligence Technology Co.,Ltd +BC8529 (base 16) Jiangxi Remote lntelligence Technology Co.,Ltd + No. 1, Chemical Avenue, Guixi335400, Yingtan, Jiangxi + Yingtan Jiangxi 360600 + CN -88-62-5D (hex) BITNETWORKS CO.,LTD -88625D (base 16) BITNETWORKS CO.,LTD - No.606, 83, Samwon-ro, Deogyang-gu, Goyang-si, Gyeonggi-do,Korea - Goyang-si 10550 - KR +28-94-01 (hex) NETGEAR +289401 (base 16) NETGEAR + 350 East Plumeria Drive + San Jose CA 95134 + US -38-07-16 (hex) FREEBOX SAS -380716 (base 16) FREEBOX SAS - 16 rue de la Ville l'Eveque - PARIS IdF 75008 - FR +D8-78-C9 (hex) SERVERCOM (INDIA) PRIVATE LIMITED +D878C9 (base 16) SERVERCOM (INDIA) PRIVATE LIMITED + E-43/1 OKHLA INDUSTRIAL AREA PHASE-II NEW DELHI SOUTH DELHI + NEW DELHI NA + IN -C8-36-A3 (hex) GERTEC BRASIL LTDA -C836A3 (base 16) GERTEC BRASIL LTDA - Avenida Jabaquara, 3060, room 601 - Sao Paulo São Paulo 04046500 - BR +A8-5E-F2 (hex) TECNO MOBILE LIMITED +A85EF2 (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 + Hong Kong Hong Kong 999077 + HK -F4-EE-31 (hex) Cisco Systems, Inc -F4EE31 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +78-9A-18 (hex) Routerboard.com +789A18 (base 16) Routerboard.com + Mikrotikls SIA + Riga Riga LV1009 + LV -40-45-C4 (hex) HUAWEI TECHNOLOGIES CO.,LTD -4045C4 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +84-DB-2F (hex) Sierra Wireless, ULC +84DB2F (base 16) Sierra Wireless, ULC + 1381 Wireless Way + Richmond BC V6V 3A4 + CA -74-87-2E (hex) HUAWEI TECHNOLOGIES CO.,LTD -74872E (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +74-C5-30 (hex) vivo Mobile Communication Co., Ltd. +74C530 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN -3C-1E-B5 (hex) Apple, Inc. -3C1EB5 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -AC-86-A3 (hex) Apple, Inc. -AC86A3 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +BC-96-E5 (hex) SERCOMM PHILIPPINES INC +BC96E5 (base 16) SERCOMM PHILIPPINES INC + Lot 1 & 5, Phase 1, Filinvest Technology Park 1, Brgy. Punta, Calamba City + Calamba Lot 1 + PH -00-1F-D6 (hex) Shenzhen Allywll -001FD6 (base 16) Shenzhen Allywll - Kejiyuan Nanshan - Shenzhen Guangdong 518057 +A8-99-AD (hex) Chaoyue Technology Co., Ltd. +A899AD (base 16) Chaoyue Technology Co., Ltd. + No. 2877 Kehang Road, Suncun Town, High tech Zone, Jinan City, Shandong Province, China + Jinan SHANDONG 250104 CN -80-D2-66 (hex) ScaleFlux -80D266 (base 16) ScaleFlux - 900 N. McCarthy Blvd.Suite 200 - Milpitas CA 95035 +D0-15-BB (hex) IEEE Registration Authority +D015BB (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -E4-21-50 (hex) Shanghai Chint low voltage electrical technology Co.,Ltd. -E42150 (base 16) Shanghai Chint low voltage electrical technology Co.,Ltd. - 3857 Sixian Road, Songjiang District, Shanghai - Shanghai Shanghai 201616 +64-CE-6E (hex) Sierra Wireless, ULC +64CE6E (base 16) Sierra Wireless, ULC + 13811 Wireless Way + Richmond BC V6V 3A4 + CA + +78-DF-72 (hex) Shanghai Imilab Technology Co.Ltd +78DF72 (base 16) Shanghai Imilab Technology Co.Ltd + 29F, A Tower, New Caohejing International Business Center, Guiping Road, Xuhui District + Shanghai Shanghai 200000 CN -D4-04-E6 (hex) Broadcom Limited -D404E6 (base 16) Broadcom Limited - 15191 Alton Parkway - Irvine CA 92618 - US +60-2B-58 (hex) EM Microelectronic +602B58 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH -28-EB-A6 (hex) Nex-T LLC -28EBA6 (base 16) Nex-T LLC - Volgogradsky prospect, 42, building 5, floor 1, room I - Moscow Select State 109316 - RU +10-9D-9C (hex) EM Microelectronic +109D9C (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH -C0-B3-C8 (hex) LLC NTC Rotek -C0B3C8 (base 16) LLC NTC Rotek - Russian Federation, Moscow, Nizhnyaya Krasnoselskaya st., house 5, building 6, room 03 - Moscow 107140 - RU +28-FF-5F (hex) HG Genuine Intelligent Terminal (Xiaogan) Co.,Ltd. +28FF5F (base 16) HG Genuine Intelligent Terminal (Xiaogan) Co.,Ltd. + Building 62, YinHu Technology Industrial Park, No.38 XiaoHan Road, Xiaonan District, Xiaogan, Hubei P.R. China + Xiaogan Hubei 432000 + CN -CC-4D-74 (hex) Fujian Newland Payment Technology Co., Ltd. -CC4D74 (base 16) Fujian Newland Payment Technology Co., Ltd. - No. B602, Building #1, HaixiaJingmao Plaza, Fuzhou Bonded Area - Fuzhou 350015 +60-76-23 (hex) Shenzhen E-Superlink Technology Co., Ltd +607623 (base 16) Shenzhen E-Superlink Technology Co., Ltd + Floor11, NO.9996 Shen Nan Road, High Tech Park, Nan Shan District, Shen Zhen + ShenZhen Guangdong 518000 CN -A0-31-EB (hex) Semikron Elektronik GmbH & Co. KG -A031EB (base 16) Semikron Elektronik GmbH & Co. KG - Sigmundstrasse 200 - Nürnberg Bavaria 90431 +74-92-BA (hex) Movesense Ltd +7492BA (base 16) Movesense Ltd + Tammiston kauppatie 7a + Vantaa 01510 + FI + +D0-48-4F (hex) Nokia Solutions and Networks GmbH & Co. KG +D0484F (base 16) Nokia Solutions and Networks GmbH & Co. KG + Werinherstrasse 91 + München Bavaria D-81541 DE -F8-D7-58 (hex) Veratron AG -F8D758 (base 16) Veratron AG - Industriestrasse 18 - Rüthi St.Gallen 9464 - CH +2C-45-9A (hex) Dixon Technologies (India) Limited +2C459A (base 16) Dixon Technologies (India) Limited + B 14-15 Phase 2 NOIDA + GautamBudh Nagar Uttarpradesh 201305 + IN -24-FE-9A (hex) CyberTAN Technology Inc. -24FE9A (base 16) CyberTAN Technology Inc. - 99 Park Ave III, Hsinchu Science Park - Hsinchu 308 - TW +6C-6E-07 (hex) CE LINK LIMITED +6C6E07 (base 16) CE LINK LIMITED + 2/F, Building G, Licheng Tech. Ind. Zone + Shenzhen Guangdong 518104 + CN -B4-CB-B8 (hex) Universal Electronics, Inc. -B4CBB8 (base 16) Universal Electronics, Inc. - 201 E Sandpointe Ave - SANTA ANA CA 927075778 +6C-62-86 (hex) Nokia +6C6286 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +50-48-2C (hex) IEEE Registration Authority +50482C (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -BC-32-B2 (hex) Samsung Electronics Co.,Ltd -BC32B2 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +B8-FC-28 (hex) Valeo Vision Systems +B8FC28 (base 16) Valeo Vision Systems + Dunmore Road + Tuam Co. Galway H54 Y276 + IE -EC-8A-48 (hex) Arista Networks -EC8A48 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 - US +00-20-FC (hex) Matrox Central Services Inc +0020FC (base 16) Matrox Central Services Inc + 1055 ST. REGIS + DORVAL QUEBEC H9P-2T4 + CA -14-1A-97 (hex) Apple, Inc. -141A97 (base 16) Apple, Inc. +88-81-87 (hex) Umeox Innovations Co.,Ltd +888187 (base 16) Umeox Innovations Co.,Ltd + Room 1208-09, Research Building, Tsinghua Information Port, No. 1, Xindong Road, Nanshan District, Shenzhen + Shenzhen Guangdong 518000 + CN + +D4-0F-9E (hex) Apple, Inc. +D40F9E (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -8C-5D-B2 (hex) IEEE Registration Authority -8C5DB2 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +6C-A3-67 (hex) Avlinkpro +6CA367 (base 16) Avlinkpro + 380 US Highway 46 + Totowa NJ 07512 US -88-CE-3F (hex) HUAWEI TECHNOLOGIES CO.,LTD -88CE3F (base 16) HUAWEI TECHNOLOGIES CO.,LTD +48-C4-61 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +48C461 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +B8-B7-DB (hex) GOIP Global Services Pvt. Ltd. +B8B7DB (base 16) GOIP Global Services Pvt. Ltd. + H68, Sector 63, Noida 201301 + Noida Uttar Pradesh 201301 + IN + +88-0C-E0 (hex) Texas Instruments +880CE0 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +B8-3D-F6 (hex) Texas Instruments +B83DF6 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +B4-DD-E0 (hex) Shanghai Amphenol Airwave Communication Electronics Co.,Ltd. +B4DDE0 (base 16) Shanghai Amphenol Airwave Communication Electronics Co.,Ltd. + NO. 689 Shen Nan Road, Xin Zhuang Industry ParkShanghai 201108 P. R. China + Shanghai Shanghai 201108 + CN + +90-64-AD (hex) HUAWEI TECHNOLOGIES CO.,LTD +9064AD (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -EC-F8-D0 (hex) HUAWEI TECHNOLOGIES CO.,LTD -ECF8D0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +D4-D8-92 (hex) HUAWEI TECHNOLOGIES CO.,LTD +D4D892 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -30-DF-17 (hex) ALPSALPINE CO,.LTD -30DF17 (base 16) ALPSALPINE CO,.LTD - nishida 6-1 - kakuda-City Miyagi-Pref 981-1595 - JP +98-D3-D7 (hex) HUAWEI TECHNOLOGIES CO.,LTD +98D3D7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -D8-02-8A (hex) Shenzhen YOUHUA Technology Co., Ltd -D8028A (base 16) Shenzhen YOUHUA Technology Co., Ltd - Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District - Shenzhen Guangdong 518055 +88-6E-EB (hex) HUAWEI TECHNOLOGIES CO.,LTD +886EEB (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -B8-3C-28 (hex) Apple, Inc. -B83C28 (base 16) Apple, Inc. +98-0D-AF (hex) Apple, Inc. +980DAF (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -3C-6D-89 (hex) Apple, Inc. -3C6D89 (base 16) Apple, Inc. +DC-6D-BC (hex) Apple, Inc. +DC6DBC (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -AC-45-00 (hex) Apple, Inc. -AC4500 (base 16) Apple, Inc. +A4-D2-3E (hex) Apple, Inc. +A4D23E (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -E8-65-38 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -E86538 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 - CN - -24-2A-04 (hex) Cisco Systems, Inc -242A04 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +30-E0-4F (hex) Apple, Inc. +30E04F (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -14-1A-AA (hex) Metal Work SpA -141AAA (base 16) Metal Work SpA - Via Segni 5-7-9 25062 Concesio - Brescia Italy 25062 - IT - -CC-DE-DE (hex) Nokia -CCDEDE (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +64-E4-A5 (hex) LG Electronics +64E4A5 (base 16) LG Electronics + 222 LG-ro, JINWI-MYEON + Pyeongtaek-si Gyeonggi-do 451-713 + KR -74-86-69 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -748669 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN +48-BD-CE (hex) Vantiva USA LLC +48BDCE (base 16) Vantiva USA LLC + 4855 Peachtree Industrial Blvd, Suite 20 + Norcross GA 30902 + US -C8-0A-35 (hex) Qingdao Hisense Smart Life Technology Co., Ltd -C80A35 (base 16) Qingdao Hisense Smart Life Technology Co., Ltd - No.399, Songling Road, Laoshan District - Qingdao Shandong 266100 - CN +94-04-E3 (hex) Vantiva USA LLC +9404E3 (base 16) Vantiva USA LLC + 5030 Sugarloaf Parkway Bldg 6 + Lawrenceville GA 30044 + US -88-6D-2D (hex) Huawei Device Co., Ltd. -886D2D (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +B4-2A-0E (hex) Vantiva USA LLC +B42A0E (base 16) Vantiva USA LLC + 5030 Sugarloaf Parkway Bldg 6 + Lawrenceville GA 30044 + US -40-FF-40 (hex) GloquadTech -40FF40 (base 16) GloquadTech - 801, Tower-A, 58-1, Giheung-ro, Giheung-gu, Yongin-si, Gyeonggi-do, 16976, Republic of Korea - Yongin-si Gyeonggi-do 16976 - KR +60-3D-26 (hex) Vantiva USA LLC +603D26 (base 16) Vantiva USA LLC + 5030 Sugarloaf Parkway Bldg 6 + Lawrenceville GA 30044 + US -24-95-2F (hex) Google, Inc. -24952F (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 +54-A6-5C (hex) Vantiva USA LLC +54A65C (base 16) Vantiva USA LLC + 5030 Sugarloaf Parkway Bldg 6 + Lawrenceville GA 30044 US -10-BE-99 (hex) Netberg -10BE99 (base 16) Netberg - 2F-1 No.36, Park St., Nangang District - Taipei 11560 - TW +DC-EB-69 (hex) Vantiva USA LLC +DCEB69 (base 16) Vantiva USA LLC + 5030 Sugarloaf Parkway Bldg 6 + Lawrenceville GA 30044 + US -84-D3-52 (hex) Tonly Technology Co. Ltd -84D352 (base 16) Tonly Technology Co. Ltd - Section 37, Zhongkai Hi-Tech Development Zone - Huizhou Guangdong 516006 - CN +94-6A-77 (hex) Vantiva USA LLC +946A77 (base 16) Vantiva USA LLC + 5030 Sugarloaf Parkway Bldg 6 + Lawrenceville GA 30044 + US -04-70-56 (hex) Arcadyan Corporation -047056 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +D4-B9-2F (hex) Vantiva USA LLC +D4B92F (base 16) Vantiva USA LLC + 5030 Sugarloaf Parkway Bldg 6 + Lawrenceville GA 30044 + US -B8-94-D9 (hex) Texas Instruments -B894D9 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +58-96-30 (hex) Vantiva USA LLC +589630 (base 16) Vantiva USA LLC + 5030 Sugarloaf Parkway Bldg 6 + Lawrenceville GA 30044 US -7C-E2-69 (hex) Texas Instruments -7CE269 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +98-9D-5D (hex) Vantiva USA LLC +989D5D (base 16) Vantiva USA LLC + 5030 Sugarloaf Parkway Bldg 6 + Lawrenceville GA 30044 US -08-04-B4 (hex) Texas Instruments -0804B4 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +48-4B-D4 (hex) Vantiva USA LLC +484BD4 (base 16) Vantiva USA LLC + 5030 Sugarloaf Parkway Bldg 6 + Lawrenceville GA 30044 US -30-AF-7E (hex) Texas Instruments -30AF7E (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +E0-DB-D1 (hex) Vantiva USA LLC +E0DBD1 (base 16) Vantiva USA LLC + 5030 Sugarloaf Parkway Bldg 6 + Lawrenceville GA 30044 US -84-B1-E4 (hex) Apple, Inc. -84B1E4 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +EC-A8-1F (hex) Vantiva USA LLC +ECA81F (base 16) Vantiva USA LLC + 4855 Peachtree Industrial Blvd, Suite 20 + Norcross GA 30902 US -54-EB-E9 (hex) Apple, Inc. -54EBE9 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +C4-27-95 (hex) Vantiva USA LLC +C42795 (base 16) Vantiva USA LLC + 5030 Sugarloaf Parkway Bldg 6 + Lawrenceville GA 30044 US -AC-16-15 (hex) Apple, Inc. -AC1615 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +E0-88-5D (hex) Vantiva USA LLC +E0885D (base 16) Vantiva USA LLC + 5030 Sugarloaf Parkway Bldg 6 + Lawrenceville GA 30044 US -EC-73-79 (hex) Apple, Inc. -EC7379 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +80-29-94 (hex) Vantiva USA LLC +802994 (base 16) Vantiva USA LLC + 5030 Sugarloaf Parkway Bldg 6 + Lawrenceville GA 30044 US -1C-C3-16 (hex) Xiamen Milesight IoT Co., Ltd. -1CC316 (base 16) Xiamen Milesight IoT Co., Ltd. - Building C09, Software Park Phase III - Xiamen Fujian 361024 - CN +E4-0D-3B (hex) Ericsson AB +E40D3B (base 16) Ericsson AB + Torshamnsgatan 36 + Stockholm SE-164 80 + SE -3C-6A-48 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -3C6A48 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 +EC-53-82 (hex) Honor Device Co., Ltd. +EC5382 (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 -FC-2A-46 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -FC2A46 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 +9C-09-71 (hex) New H3C Technologies Co., Ltd +9C0971 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -9C-FA-3C (hex) Daeyoung Electronics -9CFA3C (base 16) Daeyoung Electronics - Pyeongdongsandan 8beon-ro 54-5 - Gwangju 62466 - KR +98-AB-15 (hex) Fujian Youyike Technology Co.,Ltd +98AB15 (base 16) Fujian Youyike Technology Co.,Ltd + No. 97-1, Sizhi South Road, Liuyi Fourth Road, Lianqiao Village, Chengxi Street, Gutian County, Ningde City, Fujian Province + Ningde Fujian 352000 + CN -28-CF-51 (hex) Nintendo Co.,Ltd -28CF51 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 +00-18-AD (hex) NIDEC INSTRUMENTS CORPORATION +0018AD (base 16) NIDEC INSTRUMENTS CORPORATION + 5329 Shimosuwa-cho + Suwa-gun Nagano 393-8511 JP -FC-B0-DE (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -FCB0DE (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 +A4-CC-B3 (hex) Xiaomi Communications Co Ltd +A4CCB3 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -04-4A-6A (hex) niliwi nanjing big data Co,.Ltd -044A6A (base 16) niliwi nanjing big data Co,.Ltd - Building 6, No. 699-27, Xuanwu Avenue, Xuanwu District, Nanjing - Nanjing Jangsu 210023 +BC-31-98 (hex) IEEE Registration Authority +BC3198 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +EC-A2-A0 (hex) Taicang T&W Electronics +ECA2A0 (base 16) Taicang T&W Electronics + 89# Jiang Nan RD + Suzhou Jiangsu 215412 CN -64-C6-D2 (hex) Seiko Epson Corporation -64C6D2 (base 16) Seiko Epson Corporation - 2070 Kotobuki Koaka - Matsumoto-shi Nagano-ken 399-8702 +0C-70-43 (hex) Sony Interactive Entertainment Inc. +0C7043 (base 16) Sony Interactive Entertainment Inc. + 1-7-1 Konan + Minato-ku Tokyo 108-0075 JP -0C-35-26 (hex) Microsoft Corporation -0C3526 (base 16) Microsoft Corporation +CC-40-85 (hex) WiZ +CC4085 (base 16) WiZ + Unit 1203-5, 12/F, Tower 1, Enterprise Square, 9 Sheung Yuet Road + Kowloon Bay Hong Kong 0000 + HK + +DC-CD-18 (hex) Nintendo Co.,Ltd +DCCD18 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + +54-49-FC (hex) Ubee Interactive Co., Limited +5449FC (base 16) Ubee Interactive Co., Limited + Flat/RM 1202, 12/F, AT Tower + North Point Hong Kong 180 + HK + +4C-49-6C (hex) Intel Corporate +4C496C (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +00-11-A9 (hex) Nurivoice Co., Ltd +0011A9 (base 16) Nurivoice Co., Ltd + NURI Bld, 16 Sapyeong-daero + Seoul Seocho-gu 06552 + KR + +18-F9-35 (hex) Cisco Systems, Inc +18F935 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +58-8B-1C (hex) Cisco Systems, Inc +588B1C (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +C8-15-4E (hex) Intel Corporate +C8154E (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +78-9F-38 (hex) Shenzhen Feasycom Co., Ltd +789F38 (base 16) Shenzhen Feasycom Co., Ltd + Box 508, Building A, Phoenix Wisdom Valley, No. 50, Tiezi Road, Xixiang, Bao'an, Shenzhen + Shenzhen 518102 + CN + +54-4C-8A (hex) Microsoft Corporation +544C8A (base 16) Microsoft Corporation One Microsoft Way REDMOND WA 98052 US -80-61-6C (hex) New H3C Technologies Co., Ltd -80616C (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +90-70-D3 (hex) Fiberhome Telecommunication Technologies Co.,LTD +9070D3 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -40-B6-07 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -40B607 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +6C-48-A6 (hex) Fiberhome Telecommunication Technologies Co.,LTD +6C48A6 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -78-24-59 (hex) Alcatel-Lucent Enterprise -782459 (base 16) Alcatel-Lucent Enterprise - 26801 West Agoura Rd - Calabasas CA 91301 - US - -F4-4D-5C (hex) Zyxel Communications Corporation -F44D5C (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW +3C-0D-2C (hex) Liquid-Markets GmbH +3C0D2C (base 16) Liquid-Markets GmbH + Obermühle 8 + Baar Zug 6340 + CH -10-68-38 (hex) AzureWave Technology Inc. -106838 (base 16) AzureWave Technology Inc. - 8F., No.94, Baozhong Rd., Xindian - Taipei 231 +FC-B9-DF (hex) Motorola Mobility LLC, a Lenovo Company +FCB9DF (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -78-07-1C (hex) Green Energy Options Ltd -78071C (base 16) Green Energy Options Ltd - 3 St. Mary's Court, Main Street - Cambridge Cambridgeshire CB23 7QS - GB +D0-81-C5 (hex) Juniper Networks +D081C5 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -1C-8B-EF (hex) Beijing Xiaomi Electronics Co.,Ltd -1C8BEF (base 16) Beijing Xiaomi Electronics Co.,Ltd - Xiaomi Campus - Beijing Beijing 100085 +84-AC-60 (hex) Guangxi Hesheng Electronics Co., Ltd. +84AC60 (base 16) Guangxi Hesheng Electronics Co., Ltd. + Hexin Tech Park, Binzhou Industrial Zone, Binyang County, Nanning City, Guangxi Zhuang Autonomous Region + Nanning 530000 CN -28-E2-97 (hex) Shanghai InfoTM Microelectronics Co.,Ltd -28E297 (base 16) Shanghai InfoTM Microelectronics Co.,Ltd - building 11,NO.115,lane 572,BiBo Road, - ShangHai 201203 +9C-A3-89 (hex) Nokia +9CA389 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +5C-10-1E (hex) zte corporation +5C101E (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -58-F8-5C (hex) LLC Proizvodstvennaya Kompania TransService -58F85C (base 16) LLC Proizvodstvennaya Kompania TransService - Ulitsa Podolskih Kursantov, build. 3, of. 133 - Moscow Moscow 117545 - RU +20-17-46 (hex) Paradromics, Inc. +201746 (base 16) Paradromics, Inc. + 4030 W. Braker LaneBldg. 2 Suite 250 + Austin TX 78759 + US -30-44-49 (hex) PLATH Signal Products GmbH & Co. KG -304449 (base 16) PLATH Signal Products GmbH & Co. KG - Gotenstrasse 18 - Hamburg 20097 - DE +C4-60-26 (hex) SKY UK LIMITED +C46026 (base 16) SKY UK LIMITED + Grant Way + Isleworth Middlesex TW7 5QD + GB -EC-C3-B0 (hex) zte corporation -ECC3B0 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +90-5A-08 (hex) Super Micro Computer, Inc. +905A08 (base 16) Super Micro Computer, Inc. + 980 Rock Ave + San Jose CA 95131 + US + +FC-F7-63 (hex) KunGao Micro (JiangSu) Co., LTd +FCF763 (base 16) KunGao Micro (JiangSu) Co., LTd + 11th floor, Block C, Haichuang Building, #288 Dengyun Road, Yushan Town, Kunshan City + Kunshan Jiang Su 215300 CN -BC-BD-84 (hex) zte corporation -BCBD84 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +80-8C-97 (hex) Kaon Group Co., Ltd. +808C97 (base 16) Kaon Group Co., Ltd. + 884-3, Seongnam-daero, Bundang-gu + Seongnam-si Gyeonggi-do 13517 + KR + +4C-12-E8 (hex) VIETNAM POST AND TELECOMMUNICATION INDUSTRY TECHNOLOGY JOIN STOCK COMPANY +4C12E8 (base 16) VIETNAM POST AND TELECOMMUNICATION INDUSTRY TECHNOLOGY JOIN STOCK COMPANY + High Tech Industrial Zone I, Hoa Lac High Tech Park, Ha Bang Commune + Ha Noi Thach That 100000 + VN + +20-02-FE (hex) Hangzhou Dangbei Network Technology Co., Ltd +2002FE (base 16) Hangzhou Dangbei Network Technology Co., Ltd + Floor 2, Block C, Wanfu Center, 228 Binkang Road, Binjiang District, + Hangzhou zhejiang 310051 CN -5C-B1-2E (hex) Cisco Systems, Inc -5CB12E (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +74-27-2C (hex) Advanced Micro Devices, Inc. +74272C (base 16) Advanced Micro Devices, Inc. + 7171 Southwest Pkwy + Austin TX 78735 US -BC-6B-FF (hex) Guangzhou Shiyuan Electronic Technology Company Limited -BC6BFF (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 - CN +20-88-10 (hex) Dell Inc. +208810 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US -00-30-1A (hex) SMARTBRIDGES PTE. LTD. -00301A (base 16) SMARTBRIDGES PTE. LTD. - 745 Toa Payoh Lorong 5 - 319455 - SG +A0-62-60 (hex) Private +A06260 (base 16) Private -B4-57-E6 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -B457E6 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +E4-45-19 (hex) Beijing Xiaomi Electronics Co.,Ltd +E44519 (base 16) Beijing Xiaomi Electronics Co.,Ltd + Xiaomi Campus + Beijing Beijing 100085 CN -70-03-3F (hex) Pimax Technology(ShangHai)Co.,Ltd -70033F (base 16) Pimax Technology(ShangHai)Co.,Ltd - 3000 Longdong Avenue,Pudong New Area - Shanghai 200120 +C0-5B-44 (hex) Beijing Xiaomi Mobile Software Co., Ltd +C05B44 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -80-F1-A4 (hex) HUAWEI TECHNOLOGIES CO.,LTD -80F1A4 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +E4-B2-24 (hex) HUAWEI TECHNOLOGIES CO.,LTD +E4B224 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -A4-6C-24 (hex) HUAWEI TECHNOLOGIES CO.,LTD -A46C24 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +A8-3E-D3 (hex) HUAWEI TECHNOLOGIES CO.,LTD +A83ED3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -08-7B-12 (hex) Sagemcom Broadband SAS -087B12 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +64-67-CD (hex) HUAWEI TECHNOLOGIES CO.,LTD +6467CD (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -50-D4-5C (hex) Amazon Technologies Inc. -50D45C (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +34-EC-B6 (hex) Phyplus Microelectronics Limited +34ECB6 (base 16) Phyplus Microelectronics Limited + 304 Building 1 No.608 Sheng Xia Road + Shanghai 200000 + CN -44-D5-06 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -44D506 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County - Chengdu Sichuan 611330 +4C-D2-FB (hex) UNIONMAN TECHNOLOGY CO.,LTD +4CD2FB (base 16) UNIONMAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 CN -2C-69-CC (hex) Valeo Detection Systems -2C69CC (base 16) Valeo Detection Systems - Laiernstrasse 12 - Bietigheim-Bissingen baden württemberg 74321 +9C-1E-CF (hex) Valeo Telematik und Akustik GmbH +9C1ECF (base 16) Valeo Telematik und Akustik GmbH + Max-Planck-Straße 28-32 + Friedrichsdorf 61381 DE -D4-61-37 (hex) IEEE Registration Authority -D46137 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +00-0D-39 (hex) Nevion +000D39 (base 16) Nevion + Lysaker Torg 5 + Lysaker NO-1366 + NO -08-08-5C (hex) Luna Products -08085C (base 16) Luna Products - 3145 Tiger Run Ct, Ste 110 - Carlsbad CA 92010 - US +80-76-77 (hex) hangzhou puwell cloud tech co., ltd. +807677 (base 16) hangzhou puwell cloud tech co., ltd. + 1405 Chuling Data Mansion Wulianwang street 259 + Hangzhou Zhejiang 315000 + CN -2C-8A-C7 (hex) Ubee Interactive Co., Limited -2C8AC7 (base 16) Ubee Interactive Co., Limited - Flat/RM 1202, 12/F, AT Tower, 180 Electric Road - North Point 00000 - HK +F4-2B-8C (hex) Samsung Electronics Co.,Ltd +F42B8C (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -D0-CF-0E (hex) Sagemcom Broadband SAS -D0CF0E (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 +E4-1A-1D (hex) NOVEA ENERGIES +E41A1D (base 16) NOVEA ENERGIES + 3rue Joseph Fourier + BEAUCOUZE Pays de la Loire 49070 FR -80-DE-CC (hex) HYBE Co.,LTD -80DECC (base 16) HYBE Co.,LTD - 42, Hangang-daero - Yongsan-gu Seoul 04389 - KR +F4-CA-E7 (hex) Arcadyan Corporation +F4CAE7 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW -A0-ED-6D (hex) Ubee Interactive Co., Limited -A0ED6D (base 16) Ubee Interactive Co., Limited - Flat/RM 1202, 12/F, AT Tower, 180 Electric Road - North Point 00000 - HK +3C-EF-42 (hex) TCT mobile ltd +3CEF42 (base 16) TCT mobile ltd + No.86 hechang 7th road, zhongkai, Hi-Tech District + Hui Zhou Guang Dong 516006 + CN -C4-EB-42 (hex) Sagemcom Broadband SAS -C4EB42 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +B0-A3-F2 (hex) Huaqin Technology Co. LTD +B0A3F2 (base 16) Huaqin Technology Co. LTD + 11th Floor, Unit, No.399 Keyuan Road + Pudong Shanghai 201203 + CN -F8-34-5A (hex) Hitron Technologies. Inc -F8345A (base 16) Hitron Technologies. Inc - No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C - Hsin-chu Taiwan 300 - TW +F8-66-91 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +F86691 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 + CN -70-C9-32 (hex) Dreame Technology (Suzhou) Limited -70C932 (base 16) Dreame Technology (Suzhou) Limited - Shangjiwan Headquarter, Building E3 Economic Garden, 2288 Wuzhong Blvd - Suzhou Jiangsu 215000 +E4-38-19 (hex) Shenzhen Hi-Link Electronic CO.,Ltd. +E43819 (base 16) Shenzhen Hi-Link Electronic CO.,Ltd. + Room 1705, 1706, 1709A, 17th Floor, Building E, Xinghe WORLD, Minle Community, Minzhi Street, Longhua District + Shenzhen Guangdong 518000 CN -30-19-84 (hex) HUAWEI TECHNOLOGIES CO.,LTD -301984 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +84-F1-75 (hex) Jiangxi Xunte Intelligent Terminal Co., Ltd +84F175 (base 16) Jiangxi Xunte Intelligent Terminal Co., Ltd + 16 # 1-3/F, Zhongxing Nanchang Software Industrial Park, No. 688, Aixihu North Road, Nanchang High-tech Industrial Development Zone, Nanchang, Jiangxi Province + Nanchang Jiangxi 330000 CN -F8-C2-49 (hex) AMPERE COMPUTING LLC -F8C249 (base 16) AMPERE COMPUTING LLC - 4555 GREAT AMERICA PARKWAY - SANTA CLARA CA 95054 +C8-98-28 (hex) zte corporation +C89828 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +DC-36-42 (hex) zte corporation +DC3642 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +78-8A-86 (hex) China Dragon Technology Limited +788A86 (base 16) China Dragon Technology Limited + B4 Bldg.Haoshan 1st Industry Park, + Shenzhen Guangdong 518104 + CN + +60-82-46 (hex) Apple, Inc. +608246 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -64-8C-BB (hex) Texas Instruments -648CBB (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +98-B3-79 (hex) Apple, Inc. +98B379 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -74-B8-39 (hex) Texas Instruments -74B839 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +6C-2A-DF (hex) IEEE Registration Authority +6C2ADF (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -C4-D3-6A (hex) Texas Instruments -C4D36A (base 16) Texas Instruments +84-72-93 (hex) Texas Instruments +847293 (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -98-89-24 (hex) Texas Instruments -988924 (base 16) Texas Instruments +60-DC-81 (hex) AltoBeam Inc. +60DC81 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN + +20-E4-6F (hex) vivo Mobile Communication Co., Ltd. +20E46F (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + +88-30-37 (hex) Juniper Networks +883037 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US + +E8-24-04 (hex) Quectel Wireless Solutions Co.,Ltd. +E82404 (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + +F8-2E-0C (hex) Texas Instruments +F82E0C (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -34-15-93 (hex) Ruckus Wireless -341593 (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +90-06-F2 (hex) Texas Instruments +9006F2 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -B0-8B-BE (hex) ABL GmbH -B08BBE (base 16) ABL GmbH - Albert-Buettner-Str. 11 - Lauf 91207 - DE +E8-6E-3A (hex) Sony Interactive Entertainment Inc. +E86E3A (base 16) Sony Interactive Entertainment Inc. + 1-7-1 Konan + Minato-ku Tokyo 108-0075 + JP -0C-B9-37 (hex) Ubee Interactive Co., Limited -0CB937 (base 16) Ubee Interactive Co., Limited - Flat/RM 1202, 12/F, AT Tower - North Point Hong Kong 180 - HK +BC-B1-D3 (hex) Cisco Meraki +BCB1D3 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US -44-0C-EE (hex) Robert Bosch Elektronikai Kft. -440CEE (base 16) Robert Bosch Elektronikai Kft. - Robert Bosch u. 1. - Hatvan Heves County 3000 - HU +B8-7B-D4 (hex) Google, Inc. +B87BD4 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US -A0-17-F1 (hex) Allwinner Technology Co., Ltd -A017F1 (base 16) Allwinner Technology Co., Ltd - No.9 Technology Road 2, High-Tech Zone - Zhuhai Guangdong 519085 +C8-13-8B (hex) Shenzhen Skyworth Digital Technology CO., Ltd +C8138B (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 CN -2C-6F-4E (hex) Hubei Yuan Times Technology Co.,Ltd. -2C6F4E (base 16) Hubei Yuan Times Technology Co.,Ltd. - No. B1345, Chuanggu Start-up Area, Taizi Lake Cultural and Digital Creative Industry Park, No. 18 Shenlong Avenue, Wuhan Economic & Technological Development Zone - wuhan hubei 430050 +34-AA-31 (hex) Shenzhen Skyworth Digital Technology CO., Ltd +34AA31 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 CN -48-8F-4C (hex) shenzhen trolink Technology Co.,Ltd -488F4C (base 16) shenzhen trolink Technology Co.,Ltd - F/5 Building -E ,Fenda Hight Technology Park,Sanwei Hangcheng Street,Bao'an ,Shenzhen - shenzhen gangdong 518101 +A0-4C-0C (hex) Shenzhen Skyworth Digital Technology CO., Ltd +A04C0C (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 CN -74-4D-6D (hex) HUAWEI TECHNOLOGIES CO.,LTD -744D6D (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +6C-C2-42 (hex) Shenzhen Skyworth Digital Technology CO., Ltd +6CC242 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 CN -C4-35-D9 (hex) Apple, Inc. -C435D9 (base 16) Apple, Inc. +04-9D-05 (hex) Apple, Inc. +049D05 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -AC-C9-06 (hex) Apple, Inc. -ACC906 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +E0-73-E7 (hex) HP Inc. +E073E7 (base 16) HP Inc. + 10300 Energy Dr + Spring TX 77389 US -04-BC-6D (hex) Apple, Inc. -04BC6D (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +54-08-3B (hex) IEEE Registration Authority +54083B (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -A8-F1-B2 (hex) Allwinner Technology Co., Ltd -A8F1B2 (base 16) Allwinner Technology Co., Ltd - No.9 Technology Road 2, High-Tech Zone - Zhuhai Guangdong 519085 +54-EF-43 (hex) HUAWEI TECHNOLOGIES CO.,LTD +54EF43 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -FC-E9-D8 (hex) Amazon Technologies Inc. -FCE9D8 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - -64-29-43 (hex) D-Link Corporation -642943 (base 16) D-Link Corporation - No.289, Sinhu 3rd Rd., Neihu District, - Taipei City 114 - TW - -58-5B-69 (hex) TVT CO., LTD -585B69 (base 16) TVT CO., LTD - 23rd Floor Building B4 Block 9, Shenzhen Bay science and technology ecological garden, Nanshan District, - Shenzhen Guangdong 518057 +D8-1B-B5 (hex) HUAWEI TECHNOLOGIES CO.,LTD +D81BB5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -B8-B2-F7 (hex) DRIMAES INC. -B8B2F7 (base 16) DRIMAES INC. - #301, 19, Seongsuil-ro, Seongdong-gu - SEOUL 04779 - KR - -C4-98-94 (hex) IEEE Registration Authority -C49894 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - -8C-FA-DD (hex) HUAWEI TECHNOLOGIES CO.,LTD -8CFADD (base 16) HUAWEI TECHNOLOGIES CO.,LTD +84-64-DD (hex) HUAWEI TECHNOLOGIES CO.,LTD +8464DD (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -F8-9A-25 (hex) HUAWEI TECHNOLOGIES CO.,LTD -F89A25 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +68-A4-6A (hex) HUAWEI TECHNOLOGIES CO.,LTD +68A46A (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -08-3A-8D (hex) Espressif Inc. -083A8D (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +F4-8E-38 (hex) Dell Inc. +F48E38 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US -84-23-88 (hex) Ruckus Wireless -842388 (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +28-F1-0E (hex) Dell Inc. +28F10E (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 US -38-65-04 (hex) Honor Device Co., Ltd. -386504 (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 +90-B1-1C (hex) Dell Inc. +90B11C (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US -F8-71-0C (hex) Xiaomi Communications Co Ltd -F8710C (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +64-00-6A (hex) Dell Inc. +64006A (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US -3C-13-5A (hex) Xiaomi Communications Co Ltd -3C135A (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +10-98-36 (hex) Dell Inc. +109836 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US -90-F8-2E (hex) Amazon Technologies Inc. -90F82E (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +C8-1F-66 (hex) Dell Inc. +C81F66 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 US -C0-84-E0 (hex) HUAWEI TECHNOLOGIES CO.,LTD -C084E0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +F8-DB-88 (hex) Dell Inc. +F8DB88 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US -90-01-17 (hex) HUAWEI TECHNOLOGIES CO.,LTD -900117 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +00-14-22 (hex) Dell Inc. +001422 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US -E0-B6-68 (hex) zte corporation -E0B668 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +00-15-C5 (hex) Dell Inc. +0015C5 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US -A4-9D-DD (hex) Samsung Electronics Co.,Ltd -A49DDD (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +78-45-C4 (hex) Dell Inc. +7845C4 (base 16) Dell Inc. + One Dell way + Round Rock 78682 + US -6C-55-63 (hex) Samsung Electronics Co.,Ltd -6C5563 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +5C-26-0A (hex) Dell Inc. +5C260A (base 16) Dell Inc. + One Dell Way, MS RR5-45 + Round Rock 78682 + US -10-9F-4F (hex) New H3C Intelligence Terminal Co., Ltd. -109F4F (base 16) New H3C Intelligence Terminal Co., Ltd. - Room 406-100, 1 Yichuang Street, China-Singapore Guangzhou Knowledge City, Huangpu District, Guangzhou. - Guangzhou Guangdong 510030 - CN +00-1E-4F (hex) Dell Inc. +001E4F (base 16) Dell Inc. + One Dell Way, MS RR5-45 + Round Rock 78682 + US -38-9E-80 (hex) zte corporation -389E80 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +00-26-B9 (hex) Dell Inc. +0026B9 (base 16) Dell Inc. + One Dell Way, MS RR5-45 + Round Rock 78682 + US -88-0F-A2 (hex) Sagemcom Broadband SAS -880FA2 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +D0-67-E5 (hex) Dell Inc. +D067E5 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US -DC-97-E6 (hex) Sagemcom Broadband SAS -DC97E6 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +54-4E-45 (hex) Private +544E45 (base 16) Private -D4-92-B9 (hex) ORION NOVA, S.L. -D492B9 (base 16) ORION NOVA, S.L. - CALLE LARRAMENDI 12C 6A - TOLOSA PAIS VASCO 20400 - ES +20-97-27 (hex) TELTONIKA NETWORKS UAB +209727 (base 16) TELTONIKA NETWORKS UAB + K. Baršausko st. 66, Kaunas + Kaunas LT-51436 + LT -00-1B-B5 (hex) Cherry GmbH -001BB5 (base 16) Cherry GmbH - Cherrystraße 1 - Auerbach i. d. Opf. Bayern D-91275 +00-1A-E8 (hex) Unify Software and Solutions GmbH & Co. KG +001AE8 (base 16) Unify Software and Solutions GmbH & Co. KG + Otto-Hahn-Ring 6 + Munich 81739 DE -68-B8-BB (hex) Beijing Xiaomi Electronics Co.,Ltd -68B8BB (base 16) Beijing Xiaomi Electronics Co.,Ltd - Xiaomi Campus - Beijing Beijing 100085 +2C-FE-4F (hex) Xiaomi Communications Co Ltd +2CFE4F (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -14-AC-60 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -14AC60 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 +90-7E-43 (hex) zte corporation +907E43 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -1C-22-85 (hex) Serrature Meroni SpA -1C2285 (base 16) Serrature Meroni SpA - Via Valsorda - INVERIGO CO 22044 - IT +94-3E-E4 (hex) WiSA Technologies Inc +943EE4 (base 16) WiSA Technologies Inc + 15268 Northwest Greenbrier Parkway + Beaverton OR 97006 + US -58-93-51 (hex) Huawei Device Co., Ltd. -589351 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +A8-BD-3A (hex) UNION MAN TECHNOLOGY CO.,LTD +A8BD3A (base 16) UNION MAN TECHNOLOGY CO.,LTD + 18F, HUAYANG TOWER,YANDAYI ROAD + Huizhou Guangdong 516007 CN -2C-A7-EF (hex) OnePlus Technology (Shenzhen) Co., Ltd -2CA7EF (base 16) OnePlus Technology (Shenzhen) Co., Ltd - 18C02, 18C03, 18C04 ,18C05,TAIRAN BUILDING, - Shenzhen Guangdong 518000 +48-CA-C6 (hex) UNION MAN TECHNOLOGY CO.,LTD +48CAC6 (base 16) UNION MAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 CN -50-FD-D5 (hex) SJI Industry Company -50FDD5 (base 16) SJI Industry Company - 54-33, Dongtanhana 1-gil - Hwaseong-si Gyeonggi-do 18423 - KR - -D0-F4-F7 (hex) Huawei Device Co., Ltd. -D0F4F7 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +D4-38-44 (hex) UNION MAN TECHNOLOGY CO.,LTD +D43844 (base 16) UNION MAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 CN -D4-E9-8A (hex) Intel Corporate -D4E98A (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -D4-20-00 (hex) IEEE Registration Authority -D42000 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +6C-3C-8C (hex) Dell Inc. +6C3C8C (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 US -70-4D-E7 (hex) TECNO MOBILE LIMITED -704DE7 (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 - Hong Kong Hong Kong 999077 - HK +C4-5A-B1 (hex) Dell Inc. +C45AB1 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US -74-D8-73 (hex) GUANGDONG GENIUS TECHNOLOGY CO., LTD. -74D873 (base 16) GUANGDONG GENIUS TECHNOLOGY CO., LTD. - No.168, Middle Road Of East Gate - Xiaobian Community Chang'an Town 523851 - CN +A8-B0-28 (hex) CubePilot Pty Ltd +A8B028 (base 16) CubePilot Pty Ltd + 153 Mercer Street + Geelong Victoria 3220 + AU -68-26-24 (hex) Ergatta -682624 (base 16) Ergatta - 40 W 25th St Fl 9 - New York NY 10010 +40-7F-5F (hex) Juniper Networks +407F5F (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -A0-21-8B (hex) ACE Antenna Co., ltd -A0218B (base 16) ACE Antenna Co., ltd - Dong Van II Industrial Zone, Bach Thuong Ward, Duy Tien Town - Hanam 400000 - VN +2C-EA-7F (hex) Dell Inc. +2CEA7F (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US -20-84-F5 (hex) Yufei Innovation Software(Shenzhen) Co., Ltd. -2084F5 (base 16) Yufei Innovation Software(Shenzhen) Co., Ltd. - 115, Building 15, Maker Town, No.4109, Liuxian Avenue, Pingshan Community, Taoyuan Street, Nanshan District, Shenzhen - Shenzhen 518051 - CN +F0-D4-E2 (hex) Dell Inc. +F0D4E2 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US -14-21-03 (hex) Calix Inc. -142103 (base 16) Calix Inc. - 2777 Orchard Pkwy - San Jose CA 95131 +CC-48-3A (hex) Dell Inc. +CC483A (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 US -40-B1-5C (hex) HUAWEI TECHNOLOGIES CO.,LTD -40B15C (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +30-D0-42 (hex) Dell Inc. +30D042 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US -28-80-8A (hex) HUAWEI TECHNOLOGIES CO.,LTD -28808A (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +8C-04-BA (hex) Dell Inc. +8C04BA (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US + +E4-54-E8 (hex) Dell Inc. +E454E8 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US + +A4-BB-6D (hex) Dell Inc. +A4BB6D (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US + +E4-43-4B (hex) Dell Inc. +E4434B (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US + +E0-1F-6A (hex) Huawei Device Co., Ltd. +E01F6A (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -34-66-79 (hex) HUAWEI TECHNOLOGIES CO.,LTD -346679 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +00-56-6D (hex) Huawei Device Co., Ltd. +00566D (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -44-8C-AB (hex) Beijing Flitlink Vientiane Technology Co., LTD -448CAB (base 16) Beijing Flitlink Vientiane Technology Co., LTD - Building 23, No. 18, Anning Zhuang East Road, Qinghe, Haidian District, Beijing - Beijing 100083 +90-CC-7A (hex) Huawei Device Co., Ltd. +90CC7A (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -C4-D4-96 (hex) Shenzhen Excelsecu Data Technology Co.,Ltd -C4D496 (base 16) Shenzhen Excelsecu Data Technology Co.,Ltd - Unit 701-708,7/F,South Block,SDGI Building A,No.2,Kefeng Road,YueHai Street , Nanshan District,Shenzhen, China. - Shenzhen 518057 +8C-C5-8C (hex) ShenZhen Elsky Technology Co.,LTD +8CC58C (base 16) ShenZhen Elsky Technology Co.,LTD + 401, building A, wanguocheng, No. 9, Pingji Avenue, Shanglilang community, Nanwan street, Longgang District, Shenzhen + ShenZhen GuangDong 518000 CN -6C-97-AA (hex) AI TECHNOLOGY CO.,LTD. -6C97AA (base 16) AI TECHNOLOGY CO.,LTD. - 2-4-5,AZABUDAI,MINATO-KU - Tokyo 106-0041 - JP +2C-70-4F (hex) zte corporation +2C704F (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -6C-65-67 (hex) BELIMO Automation AG -6C6567 (base 16) BELIMO Automation AG - brunnenbachstrasse 1 - Hinwil Zurich 8340 - CH +64-52-34 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +645234 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 + CN -D0-DA-D7 (hex) Apple, Inc. -D0DAD7 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +38-16-72 (hex) Shenzhen SuperElectron Technology Co.,Ltd. +381672 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. + 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city + Shenzhen Guangdong 518000 + CN -C4-AC-AA (hex) Apple, Inc. -C4ACAA (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +6C-29-D2 (hex) Cisco Systems, Inc +6C29D2 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -4C-24-CE (hex) Sichuan AI-Link Technology Co., Ltd. -4C24CE (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 +08-CC-81 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +08CC81 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 CN -1C-97-FB (hex) CoolBitX Ltd. -1C97FB (base 16) CoolBitX Ltd. - Suite 102, Cannon Place, P.O. Box 712, N. Sound Rd - George Town Grand Cayman KY1-9006 - KY - -2C-32-6A (hex) Apple, Inc. -2C326A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +F4-54-33 (hex) Rockwell Automation +F45433 (base 16) Rockwell Automation + 1 Allen-Bradley Dr. + Mayfield Heights OH 44124-6118 US -6C-B1-33 (hex) Apple, Inc. -6CB133 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +34-C0-F9 (hex) Rockwell Automation +34C0F9 (base 16) Rockwell Automation + 1 Allen-Bradley Dr. + Mayfield Heights OH 44124-6118 US -28-E7-1D (hex) Arista Networks -28E71D (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 +5C-88-16 (hex) Rockwell Automation +5C8816 (base 16) Rockwell Automation + 1 Allen-Bradley Dr. + Mayfield Heights OH 44124-6118 US -10-E8-40 (hex) ZOWEE TECHNOLOGY(HEYUAN) CO., LTD. -10E840 (base 16) ZOWEE TECHNOLOGY(HEYUAN) CO., LTD. - Runye Precision Manufacturing Industrial Park,among the north of Xiangjing Road, the west of Xinpi Road and the south of Yangzi Road, locatd in the High-tech Zone, Heyuan City Guangdong Province - Heyuan Guangdong 517000 - CN - -44-05-E8 (hex) twareLAB -4405E8 (base 16) twareLAB - 338 Gwanggyojungang-ro - yongin gyeonggi 16942 - KR +D4-53-47 (hex) Merytronic 2012, S.L. +D45347 (base 16) Merytronic 2012, S.L. + Parque empresarial BoroaParcela 2C-1 + Amorebieta Bizkaia 48340 + ES -78-F1-C6 (hex) Cisco Systems, Inc -78F1C6 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +D0-7B-6F (hex) Zhuhai Yunmai Technology Co.,Ltd +D07B6F (base 16) Zhuhai Yunmai Technology Co.,Ltd + Unit 1201-1203, Youte Headquarters Building, No. 88 Xingye Road, Xiangzhou District + Zhuhai Guangdong 519000 + CN -34-1B-2D (hex) Cisco Systems, Inc -341B2D (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +04-A5-26 (hex) Nokia +04A526 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -84-3C-4C (hex) Robert Bosch SRL -843C4C (base 16) Robert Bosch SRL - Horia Macelariu 30-34 - Bucharest 013937 - RO +20-BA-36 (hex) u-blox AG +20BA36 (base 16) u-blox AG + Zuercherstrasse, 68 + Thalwil Switzerland CH-8800 + CH -88-28-7D (hex) AltoBeam (China) Inc. -88287D (base 16) AltoBeam (China) Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 +D8-0A-E6 (hex) zte corporation +D80AE6 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -44-5A-DF (hex) MIKAMI & CO., LTD. -445ADF (base 16) MIKAMI & CO., LTD. - 1-5-23 Eda-Nishi, Aoba-Ku - Yokohama-Shi Kanagawa-Pre 225-0014 - JP - -08-6E-9C (hex) Huawei Device Co., Ltd. -086E9C (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +48-9E-9D (hex) Hui Zhou Gaoshengda Technology Co.,LTD +489E9D (base 16) Hui Zhou Gaoshengda Technology Co.,LTD + No.2,Jin-da Road,Huinan Industrial Park + Hui Zhou Guangdong 516025 CN -44-16-FA (hex) Samsung Electronics Co.,Ltd -4416FA (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +18-F4-6B (hex) Telenor Connexion AB +18F46B (base 16) Telenor Connexion AB + 116 88 + Stockholm Sverige SE-116 88 + SE -FC-67-1F (hex) Tuya Smart Inc. -FC671F (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 +28-EA-0B (hex) Microsoft Corporation +28EA0B (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 US -78-C5-7D (hex) Zyxel Communications Corporation -78C57D (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW +A4-14-37 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +A41437 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.469,Jianghui Road + Hangzhou Zhejiang 310052 + CN -8C-35-92 (hex) Guangzhou Shiyuan Electronic Technology Company Limited -8C3592 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 +F8-4D-FC (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +F84DFC (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 CN -A0-46-5A (hex) Motorola Mobility LLC, a Lenovo Company -A0465A (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +84-9A-40 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +849A40 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN -64-B5-F2 (hex) Samsung Electronics Co.,Ltd -64B5F2 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +C0-51-7E (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +C0517E (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN -8C-06-CB (hex) Toradex AG -8C06CB (base 16) Toradex AG - Ebenaustrasse 10 - Horw LU 6048 - CH +2C-A5-9C (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +2CA59C (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN -38-A8-9B (hex) Fiberhome Telecommunication Technologies Co.,LTD -38A89B (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +40-AC-BF (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +40ACBF (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 CN -64-37-A4 (hex) TOKYOSHUHA CO.,LTD. -6437A4 (base 16) TOKYOSHUHA CO.,LTD. - 1-8-9 KANDAIZUMICHO - CHIYODA-KU TOKYO 101-0024 - JP +98-F1-12 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +98F112 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN -90-CA-FA (hex) Google, Inc. -90CAFA (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US +98-9D-E5 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +989DE5 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN -DC-AA-43 (hex) Shenzhen Terca Information Technology Co., Ltd. -DCAA43 (base 16) Shenzhen Terca Information Technology Co., Ltd. - Room1401, Block A, Building 12 , Shenzhen Bay Technology and Eco-Park , No. 18 Keji South Road , Nanshan District , Shenzhen - SHENZHEN GUANGDONG 518000 +3C-1B-F8 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +3C1BF8 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 CN -44-71-47 (hex) Beijing Xiaomi Electronics Co.,Ltd -447147 (base 16) Beijing Xiaomi Electronics Co.,Ltd - Xiaomi Campus - Beijing Beijing 100085 +9C-54-16 (hex) Cisco Systems, Inc +9C5416 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +48-BD-A7 (hex) Honor Device Co., Ltd. +48BDA7 (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 -F4-BB-C7 (hex) vivo Mobile Communication Co., Ltd. -F4BBC7 (base 16) vivo Mobile Communication Co., Ltd. +EC-21-50 (hex) vivo Mobile Communication Co., Ltd. +EC2150 (base 16) vivo Mobile Communication Co., Ltd. No.1, vivo Road, Chang'an Dongguan Guangdong 523860 CN -00-FB-F9 (hex) Axiado Corporation -00FBF9 (base 16) Axiado Corporation - 2610 Orchard Parkway, Suite 300 - San Jose CA 95134 - US +A0-AF-12 (hex) HUAWEI TECHNOLOGIES CO.,LTD +A0AF12 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -98-66-10 (hex) zte corporation -986610 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +60-96-A4 (hex) HUAWEI TECHNOLOGIES CO.,LTD +6096A4 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -A8-DC-5A (hex) Digital Watchdog -A8DC5A (base 16) Digital Watchdog - 16220 Bloomfield Ave - Cerritos CA 90703 +5C-5E-AB (hex) Juniper Networks +5C5EAB (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -1C-24-CD (hex) ASKEY COMPUTER CORP -1C24CD (base 16) ASKEY COMPUTER CORP - 10F, No.119, JIANKANG RD.,ZHINGHE DIST, - NEW TAIPEI CITY 23585 - TW - -6C-72-E2 (hex) amitek -6C72E2 (base 16) amitek - #311, KETI, 226, Chemdangwari-ro, Buk-gu, Gwangju, 61011, Rep. of KOREA - Gwangju 61011 - KR +78-19-F7 (hex) Juniper Networks +7819F7 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -04-D9-C8 (hex) Hon Hai Precision Industry Co., Ltd. -04D9C8 (base 16) Hon Hai Precision Industry Co., Ltd. - GuangDongShenZhen - ShenZhen GuangDong 518109 - CN +2C-21-72 (hex) Juniper Networks +2C2172 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -90-65-60 (hex) EM Microelectronic -906560 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +4C-96-14 (hex) Juniper Networks +4C9614 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -A0-FB-83 (hex) Honor Device Co., Ltd. -A0FB83 (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 +78-45-58 (hex) Ubiquiti Inc +784558 (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 + US -DC-0B-09 (hex) Cisco Systems, Inc -DC0B09 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +AC-8B-A9 (hex) Ubiquiti Inc +AC8BA9 (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 US -08-F3-FB (hex) Cisco Systems, Inc -08F3FB (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +9C-05-D6 (hex) Ubiquiti Inc +9C05D6 (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 US -A0-36-BC (hex) ASUSTek COMPUTER INC. -A036BC (base 16) ASUSTek COMPUTER INC. - 15,Li-Te Rd., Peitou, Taipei 112, Taiwan - Taipei Taiwan 112 - TW +28-70-4E (hex) Ubiquiti Inc +28704E (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 + US -84-0B-BB (hex) MitraStar Technology Corp. -840BBB (base 16) MitraStar Technology Corp. - No. 6, Innovation Road II, - Hsinchu 300 - TW +00-17-CB (hex) Juniper Networks +0017CB (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -E0-08-71 (hex) Dongguan Liesheng Electronic Co., Ltd. -E00871 (base 16) Dongguan Liesheng Electronic Co., Ltd. - F5, Building B, North Block, Gaosheng Tech Park, No. 84 Zhongli Road, Nancheng District, Dongguan Ci - dongguan guangdong 523000 - CN +00-1F-12 (hex) Juniper Networks +001F12 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -9C-95-6E (hex) Microchip Technology Inc. -9C956E (base 16) Microchip Technology Inc. - 2355 W. Chandler Blvd. - Chandler AZ 85224 +00-24-DC (hex) Juniper Networks +0024DC (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -10-73-EB (hex) Infiniti Electro-Optics -1073EB (base 16) Infiniti Electro-Optics - 15 - 9th Ave S - Cranbrook British Columbia V1C 2L9 - CA +00-12-1E (hex) Juniper Networks +00121E (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -2C-A7-74 (hex) Texas Instruments -2CA774 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +00-10-DB (hex) Juniper Networks +0010DB (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -18-7A-3E (hex) Silicon Laboratories -187A3E (base 16) Silicon Laboratories - 400 West Cesar Chavez Street - Austin TX 78701 +00-22-83 (hex) Juniper Networks +002283 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -30-05-05 (hex) Intel Corporate -300505 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +10-0E-7E (hex) Juniper Networks +100E7E (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -B0-DC-EF (hex) Intel Corporate -B0DCEF (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +44-F4-77 (hex) Juniper Networks +44F477 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -74-13-EA (hex) Intel Corporate -7413EA (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +5C-6A-EC (hex) IEEE Registration Authority +5C6AEC (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -78-C2-13 (hex) Sagemcom Broadband SAS -78C213 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +CC-9F-7A (hex) Chiun Mai Communication System, Inc +CC9F7A (base 16) Chiun Mai Communication System, Inc + No.4, Minsheng St., Tucheng District + New Taipei City 23678 + TW -40-22-D8 (hex) Espressif Inc. -4022D8 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +54-14-A7 (hex) Nanjing Qinheng Microelectronics Co., Ltd. +5414A7 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. + No.18, Ningshuang Road + Nanjing Jiangsu 210012 CN -18-4E-03 (hex) HMD Global Oy -184E03 (base 16) HMD Global Oy - Bertel Jungin aukio 9 - Espoo 02600 - FI +4C-E7-05 (hex) Siemens Industrial Automation Products Ltd., Chengdu +4CE705 (base 16) Siemens Industrial Automation Products Ltd., Chengdu + Tianyuan Road No.99, High Tech Zone West + Chengdu Sichuan Province 611731 + CN -70-B3-06 (hex) Apple, Inc. -70B306 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +80-DB-17 (hex) Juniper Networks +80DB17 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -B8-49-6D (hex) Apple, Inc. -B8496D (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +B8-F0-15 (hex) Juniper Networks +B8F015 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -9C-92-4F (hex) Apple, Inc. -9C924F (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +E4-23-3C (hex) Juniper Networks +E4233C (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -20-0E-2B (hex) Apple, Inc. -200E2B (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +98-86-8B (hex) Juniper Networks +98868B (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -F0-D7-93 (hex) Apple, Inc. -F0D793 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +00-58-28 (hex) Axon Networks Inc. +005828 (base 16) Axon Networks Inc. + 15420 Laguna Canyon rd. + Irvine CA 92618 US -DC-F3-1C (hex) Texas Instruments -DCF31C (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +30-B6-4F (hex) Juniper Networks +30B64F (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -54-45-38 (hex) Texas Instruments -544538 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +08-B2-58 (hex) Juniper Networks +08B258 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -30-3D-51 (hex) IEEE Registration Authority -303D51 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +F4-A7-39 (hex) Juniper Networks +F4A739 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -38-F0-C8 (hex) Logitech -38F0C8 (base 16) Logitech - 7700 Gateway Blvd - Newark CA 94560 +4C-16-FC (hex) Juniper Networks +4C16FC (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -34-FE-1C (hex) CHOUNG HWA TECH CO.,LTD -34FE1C (base 16) CHOUNG HWA TECH CO.,LTD - #31 Jangja-ro, Namdong-gu - Incheon-si 21532 - KR - -60-CF-69 (hex) meerecompany -60CF69 (base 16) meerecompany - 69-12, Jeongmunsongsan-ro, Yanggam-myeon, Hwaseong-si, Gyeonggi-do, Republic of Korea - Hwaseong-si 18630 - KR - -4C-62-7B (hex) SmartCow AI Technologies Taiwan Ltd. -4C627B (base 16) SmartCow AI Technologies Taiwan Ltd. - 16F., No. 102, Songlong Rd., Xinyi Dist., - Taipei City 110059 - TW - -28-BC-05 (hex) BLU Products Inc -28BC05 (base 16) BLU Products Inc - 10814 NW 33rd Street - Miami FL 33172 +C8-E7-F0 (hex) Juniper Networks +C8E7F0 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -1C-EF-03 (hex) Guangzhou V-SOLUTION Electronic Technology Co., Ltd. -1CEF03 (base 16) Guangzhou V-SOLUTION Electronic Technology Co., Ltd. - Room 601,Originality Building B2, NO.162 Science Avenue,Science Town - Guangzhou Guangdong 510663 - CN - -58-B0-3E (hex) Nintendo Co.,Ltd -58B03E (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - -AC-4E-65 (hex) Fiberhome Telecommunication Technologies Co.,LTD -AC4E65 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN +7C-25-86 (hex) Juniper Networks +7C2586 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -A8-80-38 (hex) ShenZhen MovingComm Technology Co., Limited -A88038 (base 16) ShenZhen MovingComm Technology Co., Limited - 5F, FuXinFa Industrial Park, LiuXianDong Industrial Zone, - ShenZhen GuangDong 518055 - CN +EC-38-73 (hex) Juniper Networks +EC3873 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -BC-5D-A3 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -BC5DA3 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County - Chengdu Sichuan 611330 - CN +C0-03-80 (hex) Juniper Networks +C00380 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -BC-7B-72 (hex) Huawei Device Co., Ltd. -BC7B72 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +20-D8-0B (hex) Juniper Networks +20D80B (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -F8-2B-7F (hex) Huawei Device Co., Ltd. -F82B7F (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +94-BF-94 (hex) Juniper Networks +94BF94 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -40-C3-BC (hex) Huawei Device Co., Ltd. -40C3BC (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +4C-6D-58 (hex) Juniper Networks +4C6D58 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -28-53-E0 (hex) Sintela Ltd -2853E0 (base 16) Sintela Ltd - The Distillery, The Old Brewery, 9-11 Lodway, - Pill Bristol BS20 0DH - GB +40-8F-9D (hex) Juniper Networks +408F9D (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -D8-68-A0 (hex) Samsung Electronics Co.,Ltd -D868A0 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +AC-78-D1 (hex) Juniper Networks +AC78D1 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -04-29-2E (hex) Samsung Electronics Co.,Ltd -04292E (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +68-22-8E (hex) Juniper Networks +68228E (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -34-5D-A8 (hex) Cisco Systems, Inc -345DA8 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +D8-53-9A (hex) Juniper Networks +D8539A (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -E0-80-6B (hex) Xiaomi Communications Co Ltd -E0806B (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +F8-C1-16 (hex) Juniper Networks +F8C116 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -70-50-E7 (hex) IEEE Registration Authority -7050E7 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +88-0A-A3 (hex) Juniper Networks +880AA3 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -70-AC-08 (hex) Silicon Laboratories -70AC08 (base 16) Silicon Laboratories - 400 West Cesar Chavez Street - Austin TX 78701 +FC-33-42 (hex) Juniper Networks +FC3342 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -38-FD-F5 (hex) Renesas Electronics (Penang) Sdn. Bhd. -38FDF5 (base 16) Renesas Electronics (Penang) Sdn. Bhd. - Phase 3, Bayan Lepas FIZ - Bayan Lepas Penang 11900 - MY +3C-8C-93 (hex) Juniper Networks +3C8C93 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -3C-26-E4 (hex) Cisco Systems, Inc -3C26E4 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +0C-81-26 (hex) Juniper Networks +0C8126 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -38-91-B7 (hex) Cisco Systems, Inc -3891B7 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +18-2A-D3 (hex) Juniper Networks +182AD3 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -DC-36-0C (hex) Hitron Technologies. Inc -DC360C (base 16) Hitron Technologies. Inc - No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C - Hsin-chu Taiwan 300 - TW +74-95-A7 (hex) Keyence Corporation +7495A7 (base 16) Keyence Corporation + 1-3-14, Higashinakajima, Higashiyodogawa + Osaka Osaka 5338555 + JP -38-12-7B (hex) Crenet Labs Co., Ltd. -38127B (base 16) Crenet Labs Co., Ltd. - Rm. 1, 10F., No. 181, Sec. 1, Datong Rd. - New Taipei City Xizhi Dist. 221451 - TW +A0-A3-B3 (hex) Espressif Inc. +A0A3B3 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN -B0-E4-5C (hex) Samsung Electronics Co.,Ltd -B0E45C (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR +34-98-7A (hex) Espressif Inc. +34987A (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN -88-F2-BD (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -88F2BD (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 +68-59-32 (hex) Sunitec Enterprise Co.,Ltd +685932 (base 16) Sunitec Enterprise Co.,Ltd + 3F.,No.98-1,Mincyuan Rd.Sindian City + Taipei County 231 231141 CN -6C-08-31 (hex) ANALOG SYSTEMS -6C0831 (base 16) ANALOG SYSTEMS - UNIT 12, 38 DLF INDUSTRIAL AREA KIRTI NAGAR NEW DELHI - NEW DELHI DELHI 110015 - IN +30-B2-16 (hex) Hitachi Energy Germany AG +30B216 (base 16) Hitachi Energy Germany AG + Havellandstr. 10-14 + Mannheim 68309 + DE -A4-7E-FA (hex) Withings -A47EFA (base 16) Withings - 2 rue Maurice Hartmann - Issy-les-Moulineaux 92130 +88-62-5D (hex) BITNETWORKS CO.,LTD +88625D (base 16) BITNETWORKS CO.,LTD + No.606, 83, Samwon-ro, Deogyang-gu, Goyang-si, Gyeonggi-do,Korea + Goyang-si 10550 + KR + +38-07-16 (hex) FREEBOX SAS +380716 (base 16) FREEBOX SAS + 16 rue de la Ville l'Eveque + PARIS IdF 75008 FR -78-91-DE (hex) Guangdong ACIGA Science&Technology Co.,Ltd -7891DE (base 16) Guangdong ACIGA Science&Technology Co.,Ltd - L203 Biguiyuan International Club, Beijiao Town, Shunde District - Fo Shan Guangdong 528312 - CN +C8-36-A3 (hex) GERTEC BRASIL LTDA +C836A3 (base 16) GERTEC BRASIL LTDA + Avenida Jabaquara, 3060, room 601 + Sao Paulo São Paulo 04046500 + BR -BC-4C-A0 (hex) HUAWEI TECHNOLOGIES CO.,LTD -BC4CA0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +F4-EE-31 (hex) Cisco Systems, Inc +F4EE31 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -74-34-2B (hex) HUAWEI TECHNOLOGIES CO.,LTD -74342B (base 16) HUAWEI TECHNOLOGIES CO.,LTD +40-45-C4 (hex) HUAWEI TECHNOLOGIES CO.,LTD +4045C4 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -C4-12-EC (hex) HUAWEI TECHNOLOGIES CO.,LTD -C412EC (base 16) HUAWEI TECHNOLOGIES CO.,LTD +74-87-2E (hex) HUAWEI TECHNOLOGIES CO.,LTD +74872E (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -AC-A3-2F (hex) Solidigm Technology -ACA32F (base 16) Solidigm Technology - 1921 Corporate Center Circle, Suite 3B - Longmont 80501 +3C-1E-B5 (hex) Apple, Inc. +3C1EB5 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -4C-9E-6C (hex) BROADEX TECHNOLOGIES CO.LTD -4C9E6C (base 16) BROADEX TECHNOLOGIES CO.LTD - NO.306 YATAI ROAD - JIAXING ZHEJIANG 314006 - CN +AC-86-A3 (hex) Apple, Inc. +AC86A3 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -8C-98-06 (hex) SHENZHEN SEI ROBOTICS CO.,LTD -8C9806 (base 16) SHENZHEN SEI ROBOTICS CO.,LTD - the 4th floor,Productivity Building D,#5 Hi-Tech Middle 2nd Road,Shenzhen Hi-Tech Industrial Park, Nanshan District,Shenzhen,China - Shenzhen 518000 +00-1F-D6 (hex) Shenzhen Allywll +001FD6 (base 16) Shenzhen Allywll + Kejiyuan Nanshan + Shenzhen Guangdong 518057 CN -20-08-89 (hex) zte corporation -200889 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +80-D2-66 (hex) ScaleFlux +80D266 (base 16) ScaleFlux + 900 N. McCarthy Blvd.Suite 200 + Milpitas CA 95035 + US + +E4-21-50 (hex) Shanghai Chint low voltage electrical technology Co.,Ltd. +E42150 (base 16) Shanghai Chint low voltage electrical technology Co.,Ltd. + 3857 Sixian Road, Songjiang District, Shanghai + Shanghai Shanghai 201616 CN -20-0B-CF (hex) Nintendo Co.,Ltd -200BCF (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP +D4-04-E6 (hex) Broadcom Limited +D404E6 (base 16) Broadcom Limited + 15191 Alton Parkway + Irvine CA 92618 + US -70-70-FC (hex) GOLD&WATER INDUSTRIAL LIMITED -7070FC (base 16) GOLD&WATER INDUSTRIAL LIMITED - NO.77 Leighton Road, 17/F Leighton Centre Causeway Bay ,HongKong - HongKong 999077 - HK +28-EB-A6 (hex) Nex-T LLC +28EBA6 (base 16) Nex-T LLC + Volgogradsky prospect, 42, building 5, floor 1, room I + Moscow Select State 109316 + RU -98-D9-3D (hex) Demant Enterprise A/S -98D93D (base 16) Demant Enterprise A/S - Kongebakken 9 - Smorum 2765 - DK +C0-B3-C8 (hex) LLC NTC Rotek +C0B3C8 (base 16) LLC NTC Rotek + Russian Federation, Moscow, Nizhnyaya Krasnoselskaya st., house 5, building 6, room 03 + Moscow 107140 + RU -B4-A6-78 (hex) Zhejiang Tmall Technology Co., Ltd. -B4A678 (base 16) Zhejiang Tmall Technology Co., Ltd. - No.969 Wenyi West Road, Wuchang Street, Yuhang District - Hangzhou Zhejiang 310024 +CC-4D-74 (hex) Fujian Newland Payment Technology Co., Ltd. +CC4D74 (base 16) Fujian Newland Payment Technology Co., Ltd. + No. B602, Building #1, HaixiaJingmao Plaza, Fuzhou Bonded Area + Fuzhou 350015 CN -AC-C4-BD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -ACC4BD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN +A0-31-EB (hex) Semikron Elektronik GmbH & Co. KG +A031EB (base 16) Semikron Elektronik GmbH & Co. KG + Sigmundstrasse 200 + Nürnberg Bavaria 90431 + DE -E4-B6-33 (hex) Wuxi Stars Microsystem Technology Co., Ltd -E4B633 (base 16) Wuxi Stars Microsystem Technology Co., Ltd - Room 2101, Tower C, Swan Tower, Wuxi Software Park, 111 Linghu Avenue, Xinwu District - Wuxi 214135 - CN +F8-D7-58 (hex) Veratron AG +F8D758 (base 16) Veratron AG + Industriestrasse 18 + Rüthi St.Gallen 9464 + CH -08-51-04 (hex) Huawei Device Co., Ltd. -085104 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +24-FE-9A (hex) CyberTAN Technology Inc. +24FE9A (base 16) CyberTAN Technology Inc. + 99 Park Ave III, Hsinchu Science Park + Hsinchu 308 + TW -78-5B-64 (hex) Huawei Device Co., Ltd. -785B64 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +B4-CB-B8 (hex) Universal Electronics, Inc. +B4CBB8 (base 16) Universal Electronics, Inc. + 201 E Sandpointe Ave + SANTA ANA CA 927075778 + US -54-E1-5B (hex) Huawei Device Co., Ltd. -54E15B (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +BC-32-B2 (hex) Samsung Electronics Co.,Ltd +BC32B2 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -24-26-D6 (hex) HUAWEI TECHNOLOGIES CO.,LTD -2426D6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +EC-8A-48 (hex) Arista Networks +EC8A48 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US + +14-1A-97 (hex) Apple, Inc. +141A97 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +8C-5D-B2 (hex) IEEE Registration Authority +8C5DB2 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +88-CE-3F (hex) HUAWEI TECHNOLOGIES CO.,LTD +88CE3F (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -EC-81-9C (hex) HUAWEI TECHNOLOGIES CO.,LTD -EC819C (base 16) HUAWEI TECHNOLOGIES CO.,LTD +EC-F8-D0 (hex) HUAWEI TECHNOLOGIES CO.,LTD +ECF8D0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -C4-A1-0E (hex) IEEE Registration Authority -C4A10E (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +30-DF-17 (hex) ALPSALPINE CO,.LTD +30DF17 (base 16) ALPSALPINE CO,.LTD + nishida 6-1 + kakuda-City Miyagi-Pref 981-1595 + JP + +D8-02-8A (hex) Shenzhen YOUHUA Technology Co., Ltd +D8028A (base 16) Shenzhen YOUHUA Technology Co., Ltd + Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District + Shenzhen Guangdong 518055 + CN + +B8-3C-28 (hex) Apple, Inc. +B83C28 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -54-2F-04 (hex) Shanghai Longcheer Technology Co., Ltd. -542F04 (base 16) Shanghai Longcheer Technology Co., Ltd. - Bldg 1,No.401,Caobao RD,Xuhui Dist - Shanghai 200233 +3C-6D-89 (hex) Apple, Inc. +3C6D89 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +AC-45-00 (hex) Apple, Inc. +AC4500 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +E8-65-38 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +E86538 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 CN -1C-A4-10 (hex) Amlogic, Inc. -1CA410 (base 16) Amlogic, Inc. - 2518 Mission College Blvd, Suite 120 - Santa Clara CA 95054 +24-2A-04 (hex) Cisco Systems, Inc +242A04 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -98-C8-1C (hex) BAYTEC LIMITED -98C81C (base 16) BAYTEC LIMITED - 107C, 31/f, The gateway, Tower 5, Harbour City, 15 canton road, Tsim Sha Tsui, Hong Kong - Harbour 999077 - HK +14-1A-AA (hex) Metal Work SpA +141AAA (base 16) Metal Work SpA + Via Segni 5-7-9 25062 Concesio + Brescia Italy 25062 + IT -74-84-69 (hex) Nintendo Co.,Ltd -748469 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP +CC-DE-DE (hex) Nokia +CCDEDE (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -8C-CB-DF (hex) FOXCONN INTERCONNECT TECHNOLOGY -8CCBDF (base 16) FOXCONN INTERCONNECT TECHNOLOGY - 66-1 Zhongshan Road, Tucheng District - New Taipei City Taiwan 23680 +74-86-69 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +748669 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +C8-0A-35 (hex) Qingdao Hisense Smart Life Technology Co., Ltd +C80A35 (base 16) Qingdao Hisense Smart Life Technology Co., Ltd + No.399, Songling Road, Laoshan District + Qingdao Shandong 266100 + CN + +88-6D-2D (hex) Huawei Device Co., Ltd. +886D2D (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +40-FF-40 (hex) GloquadTech +40FF40 (base 16) GloquadTech + 801, Tower-A, 58-1, Giheung-ro, Giheung-gu, Yongin-si, Gyeonggi-do, 16976, Republic of Korea + Yongin-si Gyeonggi-do 16976 + KR + +24-95-2F (hex) Google, Inc. +24952F (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +10-BE-99 (hex) Netberg +10BE99 (base 16) Netberg + 2F-1 No.36, Park St., Nangang District + Taipei 11560 TW -20-0B-16 (hex) Texas Instruments -200B16 (base 16) Texas Instruments +84-D3-52 (hex) Tonly Technology Co. Ltd +84D352 (base 16) Tonly Technology Co. Ltd + Section 37, Zhongkai Hi-Tech Development Zone + Huizhou Guangdong 516006 + CN + +04-70-56 (hex) Arcadyan Corporation +047056 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW + +B8-94-D9 (hex) Texas Instruments +B894D9 (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -88-01-F9 (hex) Texas Instruments -8801F9 (base 16) Texas Instruments +7C-E2-69 (hex) Texas Instruments +7CE269 (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -F8-55-48 (hex) Texas Instruments -F85548 (base 16) Texas Instruments +08-04-B4 (hex) Texas Instruments +0804B4 (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -68-E7-4A (hex) Texas Instruments -68E74A (base 16) Texas Instruments +30-AF-7E (hex) Texas Instruments +30AF7E (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -70-A6-BD (hex) Honor Device Co., Ltd. -70A6BD (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 - -84-69-93 (hex) HP Inc. -846993 (base 16) HP Inc. - 10300 Energy Dr - Spring TX 77389 - US - -74-6F-88 (hex) zte corporation -746F88 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -74-71-8B (hex) Apple, Inc. -74718B (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -70-31-7F (hex) Apple, Inc. -70317F (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -A4-CF-99 (hex) Apple, Inc. -A4CF99 (base 16) Apple, Inc. +84-B1-E4 (hex) Apple, Inc. +84B1E4 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -4C-2E-B4 (hex) Apple, Inc. -4C2EB4 (base 16) Apple, Inc. +54-EB-E9 (hex) Apple, Inc. +54EBE9 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -D0-98-9C (hex) ConMet -D0989C (base 16) ConMet - 5701 SE Columbia Way - Vancouver WA 98661 - US - -34-AD-61 (hex) CELESTICA INC. -34AD61 (base 16) CELESTICA INC. - 1900-5140 Yonge Street PO Box 42 - Toronto Ontario M2N 6L7 - CA - -2C-55-3C (hex) Vecima Networks Inc. -2C553C (base 16) Vecima Networks Inc. - 150 Cardinal Place - Saskatoon SK S7L 6H7 - CA - -54-43-B2 (hex) Espressif Inc. -5443B2 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -B4-19-74 (hex) Apple, Inc. -B41974 (base 16) Apple, Inc. +AC-16-15 (hex) Apple, Inc. +AC1615 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -60-95-BD (hex) Apple, Inc. -6095BD (base 16) Apple, Inc. +EC-73-79 (hex) Apple, Inc. +EC7379 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -EC-11-27 (hex) Texas Instruments -EC1127 (base 16) Texas Instruments - 12500 TI BLVD - Dallas 75243 - US - -04-E8-92 (hex) SHENNAN CIRCUITS CO.,LTD -04E892 (base 16) SHENNAN CIRCUITS CO.,LTD - Gao Qiao Industrial Park East,Long Gang District, - Shenzhen Guangdong 518117 +1C-C3-16 (hex) Xiamen Milesight IoT Co., Ltd. +1CC316 (base 16) Xiamen Milesight IoT Co., Ltd. + Building C09, Software Park Phase III + Xiamen Fujian 361024 CN -BC-E9-E2 (hex) Brocade Communications Systems LLC -BCE9E2 (base 16) Brocade Communications Systems LLC - 1320 Ridder Park Dr - San Jose CA 95131 - US - -18-A5-9C (hex) IEEE Registration Authority -18A59C (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - -10-96-1A (hex) CHIPSEA TECHNOLOGIES (SHENZHEN) CORP. -10961A (base 16) CHIPSEA TECHNOLOGIES (SHENZHEN) CORP. - 9F,BLOCK A,GARDEN CITY DIGITAL BUILDING,NO.1079 NANHAI ROAD,NANSHAN DISTRICT - SHEN ZHEN GUANG DONG 518000 +FC-2A-46 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +FC2A46 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 CN -AC-BF-71 (hex) Bose Corporation -ACBF71 (base 16) Bose Corporation - The Mountain - Framingham MA 01701-9168 - US - -AC-D3-1D (hex) Cisco Meraki -ACD31D (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US - -74-56-3C (hex) GIGA-BYTE TECHNOLOGY CO.,LTD. -74563C (base 16) GIGA-BYTE TECHNOLOGY CO.,LTD. - Pin-Jen City, Taoyuan, Taiwan, R.O.C. - Pin-Jen Taoyuan 324 - TW - -EC-55-1C (hex) HUAWEI TECHNOLOGIES CO.,LTD -EC551C (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +9C-FA-3C (hex) Daeyoung Electronics +9CFA3C (base 16) Daeyoung Electronics + Pyeongdongsandan 8beon-ro 54-5 + Gwangju 62466 + KR -B4-83-51 (hex) Intel Corporate -B48351 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +28-CF-51 (hex) Nintendo Co.,Ltd +28CF51 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP -BC-F4-D4 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -BCF4D4 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +FC-B0-DE (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +FCB0DE (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China Nanning Guangxi 530007 CN -CC-F3-05 (hex) SHENZHEN TIAN XING CHUANG ZHAN ELECTRONIC CO.,LTD -CCF305 (base 16) SHENZHEN TIAN XING CHUANG ZHAN ELECTRONIC CO.,LTD - Second floor, Building A, FengHangAvenue, Hangcheng Street, Bao'an District - Shenzhen Guangdong 518126 +04-4A-6A (hex) niliwi nanjing big data Co,.Ltd +044A6A (base 16) niliwi nanjing big data Co,.Ltd + Building 6, No. 699-27, Xuanwu Avenue, Xuanwu District, Nanjing + Nanjing Jangsu 210023 CN -04-B9-7D (hex) AiVIS Co., Itd. -04B97D (base 16) AiVIS Co., Itd. - 112, Dumipo-ro, Jung-gu - Incheon Incheon 22394 - KR +64-C6-D2 (hex) Seiko Epson Corporation +64C6D2 (base 16) Seiko Epson Corporation + 2070 Kotobuki Koaka + Matsumoto-shi Nagano-ken 399-8702 + JP -C4-C0-63 (hex) New H3C Technologies Co., Ltd -C4C063 (base 16) New H3C Technologies Co., Ltd +0C-35-26 (hex) Microsoft Corporation +0C3526 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +80-61-6C (hex) New H3C Technologies Co., Ltd +80616C (base 16) New H3C Technologies Co., Ltd 466 Changhe Road, Binjiang District Hangzhou Zhejiang 310052 CN -E0-79-8D (hex) Silicon Laboratories -E0798D (base 16) Silicon Laboratories - 400 West Cesar Chavez Street - Austin TX 78701 - US - -34-BD-20 (hex) Hangzhou Hikrobot Technology Co., Ltd. -34BD20 (base 16) Hangzhou Hikrobot Technology Co., Ltd. - Room 304, Unit B, Building 2, 399 Danfeng Road, Binjiang District, Hangzhou, Zhejiang - Hangzhou 310052 +40-B6-07 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +40B607 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -AC-2A-A1 (hex) Cisco Systems, Inc -AC2AA1 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -F8-E9-4F (hex) Cisco Systems, Inc -F8E94F (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +78-24-59 (hex) Alcatel-Lucent Enterprise +782459 (base 16) Alcatel-Lucent Enterprise + 26801 West Agoura Rd + Calabasas CA 91301 US -30-89-4A (hex) Intel Corporate -30894A (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +F4-4D-5C (hex) Zyxel Communications Corporation +F44D5C (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW -D8-9C-8E (hex) Comcast Cable Corporation -D89C8E (base 16) Comcast Cable Corporation - 1800 Arch Street - Philadelphia PA 19103 +10-68-38 (hex) AzureWave Technology Inc. +106838 (base 16) AzureWave Technology Inc. + 8F., No.94, Baozhong Rd., Xindian + Taipei 231 US -E0-6C-C5 (hex) Huawei Device Co., Ltd. -E06CC5 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +78-07-1C (hex) Green Energy Options Ltd +78071C (base 16) Green Energy Options Ltd + 3 St. Mary's Court, Main Street + Cambridge Cambridgeshire CB23 7QS + GB -30-96-3B (hex) Huawei Device Co., Ltd. -30963B (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +1C-8B-EF (hex) Beijing Xiaomi Electronics Co.,Ltd +1C8BEF (base 16) Beijing Xiaomi Electronics Co.,Ltd + Xiaomi Campus + Beijing Beijing 100085 CN -8C-6B-DB (hex) Huawei Device Co., Ltd. -8C6BDB (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +28-E2-97 (hex) Shanghai InfoTM Microelectronics Co.,Ltd +28E297 (base 16) Shanghai InfoTM Microelectronics Co.,Ltd + building 11,NO.115,lane 572,BiBo Road, + ShangHai 201203 CN -10-DA-49 (hex) Huawei Device Co., Ltd. -10DA49 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +58-F8-5C (hex) LLC Proizvodstvennaya Kompania TransService +58F85C (base 16) LLC Proizvodstvennaya Kompania TransService + Ulitsa Podolskih Kursantov, build. 3, of. 133 + Moscow Moscow 117545 + RU -60-18-3A (hex) Huawei Device Co., Ltd. -60183A (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +30-44-49 (hex) PLATH Signal Products GmbH & Co. KG +304449 (base 16) PLATH Signal Products GmbH & Co. KG + Gotenstrasse 18 + Hamburg 20097 + DE -18-C0-07 (hex) Huawei Device Co., Ltd. -18C007 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +EC-C3-B0 (hex) zte corporation +ECC3B0 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -98-59-7A (hex) Intel Corporate -98597A (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -64-49-7D (hex) Intel Corporate -64497D (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -B8-D6-1A (hex) Espressif Inc. -B8D61A (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +BC-BD-84 (hex) zte corporation +BCBD84 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -BC-6E-6D (hex) EM Microelectronic -BC6E6D (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH - -00-D4-9E (hex) Intel Corporate -00D49E (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -34-92-C2 (hex) Square Route Co., Ltd. -3492C2 (base 16) Square Route Co., Ltd. - Area-Shinagawa 13F, 1-9-36, Konan, Minato-ku - Tokyo Tokyo 108-0075 - JP +5C-B1-2E (hex) Cisco Systems, Inc +5CB12E (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -8C-17-59 (hex) Intel Corporate -8C1759 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +BC-6B-FF (hex) Guangzhou Shiyuan Electronic Technology Company Limited +BC6BFF (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 + CN -30-CB-36 (hex) Belden Singapore Pte. Ltd. -30CB36 (base 16) Belden Singapore Pte. Ltd. - 151 Lorong Chuan #05-01 New Tech Park Singapore - Singapore 556741 +00-30-1A (hex) SMARTBRIDGES PTE. LTD. +00301A (base 16) SMARTBRIDGES PTE. LTD. + 745 Toa Payoh Lorong 5 + 319455 SG -30-BB-7D (hex) OnePlus Technology (Shenzhen) Co., Ltd -30BB7D (base 16) OnePlus Technology (Shenzhen) Co., Ltd - 18C02, 18C03, 18C04 ,18C05,TAIRAN BUILDING, - Shenzhen Guangdong 518000 +B4-57-E6 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +B457E6 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -6C-67-EF (hex) HUAWEI TECHNOLOGIES CO.,LTD -6C67EF (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +70-03-3F (hex) Pimax Technology(ShangHai)Co.,Ltd +70033F (base 16) Pimax Technology(ShangHai)Co.,Ltd + 3000 Longdong Avenue,Pudong New Area + Shanghai 200120 CN -88-69-3D (hex) HUAWEI TECHNOLOGIES CO.,LTD -88693D (base 16) HUAWEI TECHNOLOGIES CO.,LTD +80-F1-A4 (hex) HUAWEI TECHNOLOGIES CO.,LTD +80F1A4 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -00-99-1D (hex) HUAWEI TECHNOLOGIES CO.,LTD -00991D (base 16) HUAWEI TECHNOLOGIES CO.,LTD +A4-6C-24 (hex) HUAWEI TECHNOLOGIES CO.,LTD +A46C24 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -28-3E-0C (hex) Preferred Robotics, Inc. -283E0C (base 16) Preferred Robotics, Inc. - Otemachi Bldg. 1-6-1 Otemachi - Chiyoda-ku Tokyo 100-0004 - JP - -04-BC-9F (hex) Calix Inc. -04BC9F (base 16) Calix Inc. - 2777 Orchard Pkwy - San Jose CA 95131 +50-D4-5C (hex) Amazon Technologies Inc. +50D45C (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -6C-A4-01 (hex) essensys plc -6CA401 (base 16) essensys plc - Aldgate Tower, Leman Street - London E1 8FA - GB +44-D5-06 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +44D506 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 + CN -B0-4A-6A (hex) Samsung Electronics Co.,Ltd -B04A6A (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +2C-69-CC (hex) Valeo Detection Systems +2C69CC (base 16) Valeo Detection Systems + Laiernstrasse 12 + Bietigheim-Bissingen baden württemberg 74321 + DE -A8-79-8D (hex) Samsung Electronics Co.,Ltd -A8798D (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +D4-61-37 (hex) IEEE Registration Authority +D46137 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -5C-ED-F4 (hex) Samsung Electronics Co.,Ltd -5CEDF4 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +08-08-5C (hex) Luna Products +08085C (base 16) Luna Products + 3145 Tiger Run Ct, Ste 110 + Carlsbad CA 92010 + US -28-3D-C2 (hex) Samsung Electronics Co.,Ltd -283DC2 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 +2C-8A-C7 (hex) Ubee Interactive Co., Limited +2C8AC7 (base 16) Ubee Interactive Co., Limited + Flat/RM 1202, 12/F, AT Tower, 180 Electric Road + North Point 00000 + HK + +80-DE-CC (hex) HYBE Co.,LTD +80DECC (base 16) HYBE Co.,LTD + 42, Hangang-daero + Yongsan-gu Seoul 04389 KR -B4-28-75 (hex) Futecho Solutions Private Limited -B42875 (base 16) Futecho Solutions Private Limited - 504, Commercial Complex, Savitri Cinema Compound, GK2 - New Delhi 110048 - IN +A0-ED-6D (hex) Ubee Interactive Co., Limited +A0ED6D (base 16) Ubee Interactive Co., Limited + Flat/RM 1202, 12/F, AT Tower, 180 Electric Road + North Point 00000 + HK -30-23-64 (hex) Nokia Shanghai Bell Co., Ltd. -302364 (base 16) Nokia Shanghai Bell Co., Ltd. - No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai - Shanghai 201206 - CN +F8-34-5A (hex) Hitron Technologies. Inc +F8345A (base 16) Hitron Technologies. Inc + No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C + Hsin-chu Taiwan 300 + TW -48-51-D0 (hex) Jiangsu Xinsheng Intelligent Technology Co., Ltd. -4851D0 (base 16) Jiangsu Xinsheng Intelligent Technology Co., Ltd. - 18th Floor,Inno laser Building,18-69 Changwu Mid Road,Changzhou Science & Education Town,Wujin District,Changzhou,Jiangsu213000,China - Changzhou Jiangsu 213000 +70-C9-32 (hex) Dreame Technology (Suzhou) Limited +70C932 (base 16) Dreame Technology (Suzhou) Limited + Shangjiwan Headquarter, Building E3 Economic Garden, 2288 Wuzhong Blvd + Suzhou Jiangsu 215000 CN -DC-9A-7D (hex) HISENSE VISUAL TECHNOLOGY CO.,LTD -DC9A7D (base 16) HISENSE VISUAL TECHNOLOGY CO.,LTD - Qianwangang Road 218 - Qingdao Shandong 266510 +30-19-84 (hex) HUAWEI TECHNOLOGIES CO.,LTD +301984 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -84-F1-D0 (hex) EHOOME IOT PRIVATE LIMITED -84F1D0 (base 16) EHOOME IOT PRIVATE LIMITED - A-13, SECTOR-83, - NOIDA UTTAR PRADESH 201301 - IN +F8-C2-49 (hex) AMPERE COMPUTING LLC +F8C249 (base 16) AMPERE COMPUTING LLC + 4555 GREAT AMERICA PARKWAY + SANTA CLARA CA 95054 + US -20-8B-D1 (hex) NXP Semiconductor (Tianjin) LTD. -208BD1 (base 16) NXP Semiconductor (Tianjin) LTD. - No.15 Xinghua Avenue, Xiqing Economic Development Area - Tianjin 300385 - CN +64-8C-BB (hex) Texas Instruments +648CBB (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -2C-07-F6 (hex) SKG Health Technologies Co., Ltd. -2C07F6 (base 16) SKG Health Technologies Co., Ltd. - 23A Floor,Building 3,Zhongke R&D Park,No.009,Gaoxin South 1st Road, High-tech Zone Community,Yuehai street, Nanshan District,Shenzhen City,Guangdong Province,P.R.China - Shenzhen 518000 - CN +74-B8-39 (hex) Texas Instruments +74B839 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -38-68-BE (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -3868BE (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County - Chengdu Sichuan 611330 - CN +C4-D3-6A (hex) Texas Instruments +C4D36A (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -28-A5-3F (hex) vivo Mobile Communication Co., Ltd. -28A53F (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN +98-89-24 (hex) Texas Instruments +988924 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -8C-49-B6 (hex) vivo Mobile Communication Co., Ltd. -8C49B6 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN +34-15-93 (hex) Ruckus Wireless +341593 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US -80-77-A4 (hex) TECNO MOBILE LIMITED -8077A4 (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 - Hong Kong Hong Kong 999077 +B0-8B-BE (hex) ABL GmbH +B08BBE (base 16) ABL GmbH + Albert-Buettner-Str. 11 + Lauf 91207 + DE + +0C-B9-37 (hex) Ubee Interactive Co., Limited +0CB937 (base 16) Ubee Interactive Co., Limited + Flat/RM 1202, 12/F, AT Tower + North Point Hong Kong 180 HK -7C-6C-F0 (hex) Shenzhen TINNO Mobile Technology Corp. -7C6CF0 (base 16) Shenzhen TINNO Mobile Technology Corp. - 4/F, H-3 Building, Qiao Cheng Eastern Industrial Park, Overseas Chinese Town, Shenzhen - Shenzhen guangdong 518053 +44-0C-EE (hex) Robert Bosch Elektronikai Kft. +440CEE (base 16) Robert Bosch Elektronikai Kft. + Robert Bosch u. 1. + Hatvan Heves County 3000 + HU + +A0-17-F1 (hex) Allwinner Technology Co., Ltd +A017F1 (base 16) Allwinner Technology Co., Ltd + No.9 Technology Road 2, High-Tech Zone + Zhuhai Guangdong 519085 CN -00-C3-0A (hex) Xiaomi Communications Co Ltd -00C30A (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +2C-6F-4E (hex) Hubei Yuan Times Technology Co.,Ltd. +2C6F4E (base 16) Hubei Yuan Times Technology Co.,Ltd. + No. B1345, Chuanggu Start-up Area, Taizi Lake Cultural and Digital Creative Industry Park, No. 18 Shenlong Avenue, Wuhan Economic & Technological Development Zone + wuhan hubei 430050 CN -88-52-EB (hex) Xiaomi Communications Co Ltd -8852EB (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +48-8F-4C (hex) shenzhen trolink Technology Co.,Ltd +488F4C (base 16) shenzhen trolink Technology Co.,Ltd + F/5 Building -E ,Fenda Hight Technology Park,Sanwei Hangcheng Street,Bao'an ,Shenzhen + shenzhen gangdong 518101 CN -08-57-FB (hex) Amazon Technologies Inc. -0857FB (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +74-4D-6D (hex) HUAWEI TECHNOLOGIES CO.,LTD +744D6D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -00-D0-FC (hex) GRANITE MICROSYSTEMS -00D0FC (base 16) GRANITE MICROSYSTEMS - 10202 N. ENTERPRISE DRIVE - MEQUON WI 53092 +C4-35-D9 (hex) Apple, Inc. +C435D9 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -B0-DE-28 (hex) Apple, Inc. -B0DE28 (base 16) Apple, Inc. +AC-C9-06 (hex) Apple, Inc. +ACC906 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -7C-13-1D (hex) SERNET (SUZHOU) TECHNOLOGIES CORPORATION -7C131D (base 16) SERNET (SUZHOU) TECHNOLOGIES CORPORATION - NO.8 Tangzhuang Road,Suzhou Industrial Park,Su ZhouCity,JiangSu Province,China - Suzhou 215021 - CN - -D4-9F-DD (hex) Huawei Device Co., Ltd. -D49FDD (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -00-D8-A2 (hex) Huawei Device Co., Ltd. -00D8A2 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +04-BC-6D (hex) Apple, Inc. +04BC6D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -DC-6B-1B (hex) Huawei Device Co., Ltd. -DC6B1B (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +A8-F1-B2 (hex) Allwinner Technology Co., Ltd +A8F1B2 (base 16) Allwinner Technology Co., Ltd + No.9 Technology Road 2, High-Tech Zone + Zhuhai Guangdong 519085 CN -D0-6D-C9 (hex) Sagemcom Broadband SAS -D06DC9 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +FC-E9-D8 (hex) Amazon Technologies Inc. +FCE9D8 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -98-CA-20 (hex) Shanghai SIMCOM Ltd. -98CA20 (base 16) Shanghai SIMCOM Ltd. - Building A, SIM Technology Building, No.633, Jinzhong Road, Changning District - Shanghai 200335 - CN +64-29-43 (hex) D-Link Corporation +642943 (base 16) D-Link Corporation + No.289, Sinhu 3rd Rd., Neihu District, + Taipei City 114 + TW -D8-50-A1 (hex) Hunan Danuo Technology Co.,LTD -D850A1 (base 16) Hunan Danuo Technology Co.,LTD - No. 89, Guoqing South Road, Beihu District, Chenzhou City, Hunan Province (in Xiangnan International Logistics Park) - Hunan 423000 +58-5B-69 (hex) TVT CO., LTD +585B69 (base 16) TVT CO., LTD + 23rd Floor Building B4 Block 9, Shenzhen Bay science and technology ecological garden, Nanshan District, + Shenzhen Guangdong 518057 CN -44-1A-AC (hex) Elektrik Uretim AS EOS -441AAC (base 16) Elektrik Uretim AS EOS - Mustafa Kemal Mahallesi No:166 Çankaya - Ankara 06520 - TR - -34-28-40 (hex) Apple, Inc. -342840 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +B8-B2-F7 (hex) DRIMAES INC. +B8B2F7 (base 16) DRIMAES INC. + #301, 19, Seongsuil-ro, Seongdong-gu + SEOUL 04779 + KR -18-E7-B0 (hex) Apple, Inc. -18E7B0 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +C4-98-94 (hex) IEEE Registration Authority +C49894 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -50-57-8A (hex) Apple, Inc. -50578A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +8C-FA-DD (hex) HUAWEI TECHNOLOGIES CO.,LTD +8CFADD (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -D8-33-B7 (hex) Sagemcom Broadband SAS -D833B7 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +F8-9A-25 (hex) HUAWEI TECHNOLOGIES CO.,LTD +F89A25 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -D4-FB-8E (hex) Apple, Inc. -D4FB8E (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +84-23-88 (hex) Ruckus Wireless +842388 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 US -2C-78-4C (hex) Iton Technology Corp. -2C784C (base 16) Iton Technology Corp. - Room 1302, Block A, Building 4, Tianan Cyber Park, Huangge Road,Longgang District - Shenzhen Guangdong 518116 +38-65-04 (hex) Honor Device Co., Ltd. +386504 (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 -48-87-59 (hex) Xiaomi Communications Co Ltd -488759 (base 16) Xiaomi Communications Co Ltd +F8-71-0C (hex) Xiaomi Communications Co Ltd +F8710C (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road Beijing Haidian District 100085 CN -AC-1E-9E (hex) Xiaomi Communications Co Ltd -AC1E9E (base 16) Xiaomi Communications Co Ltd +3C-13-5A (hex) Xiaomi Communications Co Ltd +3C135A (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road Beijing Haidian District 100085 CN -D0-1B-1F (hex) OHSUNG -D01B1F (base 16) OHSUNG - 335-4,SANHODAERO,GUMI,GYEONG BUK,KOREA - GUMI GYEONG BUK 730-030 - KR +90-F8-2E (hex) Amazon Technologies Inc. +90F82E (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -E0-AE-A2 (hex) HUAWEI TECHNOLOGIES CO.,LTD -E0AEA2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +C0-84-E0 (hex) HUAWEI TECHNOLOGIES CO.,LTD +C084E0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -44-45-6F (hex) SHENZHEN ONEGA TECHNOLOGY CO.,LTD -44456F (base 16) SHENZHEN ONEGA TECHNOLOGY CO.,LTD - 2 / F, building 3, zone B, Xusheng Xifa, Bao'an District, Shenzhen - Shenzhen Guangdong 518126 +90-01-17 (hex) HUAWEI TECHNOLOGIES CO.,LTD +900117 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -E8-FD-F8 (hex) Shanghai High-Flying Electronics Technology Co., Ltd -E8FDF8 (base 16) Shanghai High-Flying Electronics Technology Co., Ltd - Room 1002,#1Building,No.3000 Longdong Avenue,Pudong - Shanghai Shanghai 201202 +E0-B6-68 (hex) zte corporation +E0B668 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -C0-18-03 (hex) HP Inc. -C01803 (base 16) HP Inc. - 10300 Energy Dr - Spring TX 77389 - US +A4-9D-DD (hex) Samsung Electronics Co.,Ltd +A49DDD (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -04-CF-4B (hex) Intel Corporate -04CF4B (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +6C-55-63 (hex) Samsung Electronics Co.,Ltd +6C5563 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -FC-38-C4 (hex) China Grand Communications Co.,Ltd. -FC38C4 (base 16) China Grand Communications Co.,Ltd. - 2712, Block A, Phase II, Qianhai Economic and Trade Center, China Merchants Group, No. 3041, Yihai Avenue, Nanshan street, Shenzhen Hong Kong cooperation zone, Shenzhen - Shenzhen Guangdong 518066 +10-9F-4F (hex) New H3C Intelligence Terminal Co., Ltd. +109F4F (base 16) New H3C Intelligence Terminal Co., Ltd. + Room 406-100, 1 Yichuang Street, China-Singapore Guangzhou Knowledge City, Huangpu District, Guangzhou. + Guangzhou Guangdong 510030 CN -5C-F5-1A (hex) Zhejiang Dahua Technology Co., Ltd. -5CF51A (base 16) Zhejiang Dahua Technology Co., Ltd. - No.1199,Waterfront Road - Hangzhou Zhejiang 310053 +38-9E-80 (hex) zte corporation +389E80 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -10-1D-51 (hex) 8Mesh Networks Limited -101D51 (base 16) 8Mesh Networks Limited - Unit 607, 6/F, Yen Sheng Centre, - 64 Hoi Yuen Road Kwun Tong 000 - HK +D4-92-B9 (hex) ORION NOVA, S.L. +D492B9 (base 16) ORION NOVA, S.L. + CALLE LARRAMENDI 12C 6A + TOLOSA PAIS VASCO 20400 + ES -34-97-6F (hex) Rootech, Inc. -34976F (base 16) Rootech, Inc. - 102-611 Digital Empire2, 88, Sin won-ro - Yeongtong-gu, Suwon Gyeonggi-do 16681 - KR +00-1B-B5 (hex) Cherry GmbH +001BB5 (base 16) Cherry GmbH + Cherrystraße 1 + Auerbach i. d. Opf. Bayern D-91275 + DE -6C-6C-0F (hex) HUAWEI TECHNOLOGIES CO.,LTD -6C6C0F (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +68-B8-BB (hex) Beijing Xiaomi Electronics Co.,Ltd +68B8BB (base 16) Beijing Xiaomi Electronics Co.,Ltd + Xiaomi Campus + Beijing Beijing 100085 CN -CC-8C-BF (hex) Tuya Smart Inc. -CC8CBF (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 - US +14-AC-60 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +14AC60 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 + CN -F8-97-A9 (hex) Ericsson AB -F897A9 (base 16) Ericsson AB - Torshamnsgatan 36 - Stockholm SE-164 80 - SE - -AC-93-C4 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -AC93C4 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN - -00-06-88 (hex) Telways Communication Co., Ltd. -000688 (base 16) Telways Communication Co., Ltd. - 3F, No. 53, Lane 258 - Taipei 114 - TW - -A8-13-06 (hex) vivo Mobile Communication Co., Ltd. -A81306 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN - -1C-34-F1 (hex) Silicon Laboratories -1C34F1 (base 16) Silicon Laboratories - 400 West Cesar Chavez Street - Austin TX 78701 - US +1C-22-85 (hex) Serrature Meroni SpA +1C2285 (base 16) Serrature Meroni SpA + Via Valsorda + INVERIGO CO 22044 + IT -CC-7D-5B (hex) Telink Semiconductor (Shanghai) Co., Ltd. -CC7D5B (base 16) Telink Semiconductor (Shanghai) Co., Ltd. - No. 1500 Zuchongzhi Rd, Building #3 - Shanghai 201203 +58-93-51 (hex) Huawei Device Co., Ltd. +589351 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -74-95-52 (hex) Xuzhou WIKA Electronics Control Technology Co., Ltd. -749552 (base 16) Xuzhou WIKA Electronics Control Technology Co., Ltd. - No.11 BaoLianSi Road Xuzhou Economic Development Zone - Xuzhou JiangSu,PRC 221001 +2C-A7-EF (hex) OnePlus Technology (Shenzhen) Co., Ltd +2CA7EF (base 16) OnePlus Technology (Shenzhen) Co., Ltd + 18C02, 18C03, 18C04 ,18C05,TAIRAN BUILDING, + Shenzhen Guangdong 518000 CN -44-EA-30 (hex) Samsung Electronics Co.,Ltd -44EA30 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -84-5F-04 (hex) Samsung Electronics Co.,Ltd -845F04 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 +50-FD-D5 (hex) SJI Industry Company +50FDD5 (base 16) SJI Industry Company + 54-33, Dongtanhana 1-gil + Hwaseong-si Gyeonggi-do 18423 KR -9C-2F-9D (hex) Liteon Technology Corporation -9C2F9D (base 16) Liteon Technology Corporation - 4F, 90, Chien 1 Road - New Taipei City Taiwan 23585 - TW - -A4-F3-3B (hex) zte corporation -A4F33B (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -30-E7-BC (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -30E7BC (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN - -4C-FE-2E (hex) DongGuan Siyoto Electronics Co., Ltd -4CFE2E (base 16) DongGuan Siyoto Electronics Co., Ltd - Hecheng Industrial District, QiaoTou Town - DongGuan City Guangdong 523520 +D0-F4-F7 (hex) Huawei Device Co., Ltd. +D0F4F7 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -0C-4E-C0 (hex) Maxlinear Inc -0C4EC0 (base 16) Maxlinear Inc - 1060 Rincon Circle - San Jose CA 95131 - US - -90-11-95 (hex) Amazon Technologies Inc. -901195 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - -7C-B5-66 (hex) Intel Corporate -7CB566 (base 16) Intel Corporate +D4-E9-8A (hex) Intel Corporate +D4E98A (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -40-32-9D (hex) Union Image Co.,Ltd -40329D (base 16) Union Image Co.,Ltd - Building A2, Ding Bao Hong Green High Garden, Shiyan Street, Bao 'an District - SHENZHEN GuangDong 518108 - CN - -50-A0-30 (hex) IEEE Registration Authority -50A030 (base 16) IEEE Registration Authority +D4-20-00 (hex) IEEE Registration Authority +D42000 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -6C-4B-B4 (hex) HUMAX Co., Ltd. -6C4BB4 (base 16) HUMAX Co., Ltd. - HUMAX Village, 216, Hwangsaeul-ro, Bu - Seongnam-si Gyeonggi-do 463-875 - KR +70-4D-E7 (hex) TECNO MOBILE LIMITED +704DE7 (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 + Hong Kong Hong Kong 999077 + HK -58-9B-F7 (hex) Hefei Radio Communication Technology Co., Ltd -589BF7 (base 16) Hefei Radio Communication Technology Co., Ltd - No.108, YinXing Road, High-tech Development Zone - Hefei Anhui 230088 +74-D8-73 (hex) GUANGDONG GENIUS TECHNOLOGY CO., LTD. +74D873 (base 16) GUANGDONG GENIUS TECHNOLOGY CO., LTD. + No.168, Middle Road Of East Gate + Xiaobian Community Chang'an Town 523851 CN -00-0E-24 (hex) Huwell Technology Inc. -000E24 (base 16) Huwell Technology Inc. - 1F 82-21, Majin Building - Seoul 135-010 - KR +68-26-24 (hex) Ergatta +682624 (base 16) Ergatta + 40 W 25th St Fl 9 + New York NY 10010 + US -4C-77-13 (hex) Renesas Electronics (Penang) Sdn. Bhd. -4C7713 (base 16) Renesas Electronics (Penang) Sdn. Bhd. - Phase 3, Bayan Lepas FIZ - Bayan Lepas Penang 11900 - MY +A0-21-8B (hex) ACE Antenna Co., ltd +A0218B (base 16) ACE Antenna Co., ltd + Dong Van II Industrial Zone, Bach Thuong Ward, Duy Tien Town + Hanam 400000 + VN -D8-AA-59 (hex) Tonly Technology Co. Ltd -D8AA59 (base 16) Tonly Technology Co. Ltd - Section 37, Zhongkai Hi-Tech Development Zone - Huizhou Guangdong 516006 +20-84-F5 (hex) Yufei Innovation Software(Shenzhen) Co., Ltd. +2084F5 (base 16) Yufei Innovation Software(Shenzhen) Co., Ltd. + 115, Building 15, Maker Town, No.4109, Liuxian Avenue, Pingshan Community, Taoyuan Street, Nanshan District, Shenzhen + Shenzhen 518051 CN -64-CF-13 (hex) Weigao Nikkiso(Weihai)Dialysis Equipment Co.,Ltd -64CF13 (base 16) Weigao Nikkiso(Weihai)Dialysis Equipment Co.,Ltd - No.20,Xingshan Road,Wego Industrial Zone,Chucun,Weihai,Shandong,China - Weihai Shandong 264209 +14-21-03 (hex) Calix Inc. +142103 (base 16) Calix Inc. + 2777 Orchard Pkwy + San Jose CA 95131 + US + +40-B1-5C (hex) HUAWEI TECHNOLOGIES CO.,LTD +40B15C (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -50-E9-DF (hex) Quectel Wireless Solutions Co.,Ltd. -50E9DF (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 +28-80-8A (hex) HUAWEI TECHNOLOGIES CO.,LTD +28808A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -E8-FA-23 (hex) Huawei Device Co., Ltd. -E8FA23 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +34-66-79 (hex) HUAWEI TECHNOLOGIES CO.,LTD +346679 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -EC-3A-52 (hex) Huawei Device Co., Ltd. -EC3A52 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +44-8C-AB (hex) Beijing Flitlink Vientiane Technology Co., LTD +448CAB (base 16) Beijing Flitlink Vientiane Technology Co., LTD + Building 23, No. 18, Anning Zhuang East Road, Qinghe, Haidian District, Beijing + Beijing 100083 CN -44-1B-88 (hex) Apple, Inc. -441B88 (base 16) Apple, Inc. +C4-D4-96 (hex) Shenzhen Excelsecu Data Technology Co.,Ltd +C4D496 (base 16) Shenzhen Excelsecu Data Technology Co.,Ltd + Unit 701-708,7/F,South Block,SDGI Building A,No.2,Kefeng Road,YueHai Street , Nanshan District,Shenzhen, China. + Shenzhen 518057 + CN + +6C-97-AA (hex) AI TECHNOLOGY CO.,LTD. +6C97AA (base 16) AI TECHNOLOGY CO.,LTD. + 2-4-5,AZABUDAI,MINATO-KU + Tokyo 106-0041 + JP + +6C-65-67 (hex) BELIMO Automation AG +6C6567 (base 16) BELIMO Automation AG + brunnenbachstrasse 1 + Hinwil Zurich 8340 + CH + +D0-DA-D7 (hex) Apple, Inc. +D0DAD7 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -80-04-5F (hex) Apple, Inc. -80045F (base 16) Apple, Inc. +C4-AC-AA (hex) Apple, Inc. +C4ACAA (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -9C-3E-53 (hex) Apple, Inc. -9C3E53 (base 16) Apple, Inc. +4C-24-CE (hex) Sichuan AI-Link Technology Co., Ltd. +4C24CE (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 + CN + +1C-97-FB (hex) CoolBitX Ltd. +1C97FB (base 16) CoolBitX Ltd. + Suite 102, Cannon Place, P.O. Box 712, N. Sound Rd + George Town Grand Cayman KY1-9006 + KY + +2C-32-6A (hex) Apple, Inc. +2C326A (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -C8-89-F3 (hex) Apple, Inc. -C889F3 (base 16) Apple, Inc. +6C-B1-33 (hex) Apple, Inc. +6CB133 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -10-B9-C4 (hex) Apple, Inc. -10B9C4 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +28-E7-1D (hex) Arista Networks +28E71D (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 US -60-10-9E (hex) HUAWEI TECHNOLOGIES CO.,LTD -60109E (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +10-E8-40 (hex) ZOWEE TECHNOLOGY(HEYUAN) CO., LTD. +10E840 (base 16) ZOWEE TECHNOLOGY(HEYUAN) CO., LTD. + Runye Precision Manufacturing Industrial Park,among the north of Xiangjing Road, the west of Xinpi Road and the south of Yangzi Road, locatd in the High-tech Zone, Heyuan City Guangdong Province + Heyuan Guangdong 517000 CN -48-02-86 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -480286 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 - CN +44-05-E8 (hex) twareLAB +4405E8 (base 16) twareLAB + 338 Gwanggyojungang-ro + yongin gyeonggi 16942 + KR -94-DE-B8 (hex) Silicon Laboratories -94DEB8 (base 16) Silicon Laboratories - 400 West Cesar Chavez Street - Austin TX 78701 +78-F1-C6 (hex) Cisco Systems, Inc +78F1C6 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -04-0D-84 (hex) Silicon Laboratories -040D84 (base 16) Silicon Laboratories - 400 West Cesar Chavez Street - Austin TX 78701 +34-1B-2D (hex) Cisco Systems, Inc +341B2D (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -4C-19-5D (hex) Sagemcom Broadband SAS -4C195D (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +84-3C-4C (hex) Robert Bosch SRL +843C4C (base 16) Robert Bosch SRL + Horia Macelariu 30-34 + Bucharest 013937 + RO -9C-74-03 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -9C7403 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN +44-5A-DF (hex) MIKAMI & CO., LTD. +445ADF (base 16) MIKAMI & CO., LTD. + 1-5-23 Eda-Nishi, Aoba-Ku + Yokohama-Shi Kanagawa-Pre 225-0014 + JP -3C-EC-DE (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD -3CECDE (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD - 19-22# Building, Star-net Science Plaza, Juyuanzhou, - FUZHOU FUJIAN 350002 +08-6E-9C (hex) Huawei Device Co., Ltd. +086E9C (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -9C-6B-00 (hex) ASRock Incorporation -9C6B00 (base 16) ASRock Incorporation - 2F.,No.37, Sec.2, Jhongyang S.Rd., Beitou Distric, - Taipei 112 - TW +44-16-FA (hex) Samsung Electronics Co.,Ltd +4416FA (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -54-F8-2A (hex) u-blox AG -54F82A (base 16) u-blox AG - Zuercherstrasse 68 - Thalwil 8800 - CH +FC-67-1F (hex) Tuya Smart Inc. +FC671F (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US -D4-35-38 (hex) Beijing Xiaomi Mobile Software Co., Ltd -D43538 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +78-C5-7D (hex) Zyxel Communications Corporation +78C57D (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + +8C-35-92 (hex) Guangzhou Shiyuan Electronic Technology Company Limited +8C3592 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 CN -10-CE-02 (hex) Amazon Technologies Inc. -10CE02 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +A0-46-5A (hex) Motorola Mobility LLC, a Lenovo Company +A0465A (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -98-67-2E (hex) Skullcandy -98672E (base 16) Skullcandy - 6301 N. LANDMARK DRIVE - Park City 84098 - US +64-B5-F2 (hex) Samsung Electronics Co.,Ltd +64B5F2 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -A8-3A-48 (hex) Ubiqcom India Pvt Ltd -A83A48 (base 16) Ubiqcom India Pvt Ltd - First Floor, D-92, Sector-63 - Noida Uttar Pradesh 201301 - IN +8C-06-CB (hex) Toradex AG +8C06CB (base 16) Toradex AG + Ebenaustrasse 10 + Horw LU 6048 + CH -C8-5A-CF (hex) HP Inc. -C85ACF (base 16) HP Inc. - 10300 Energy Dr - Spring TX 77389 - US +38-A8-9B (hex) Fiberhome Telecommunication Technologies Co.,LTD +38A89B (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN -FC-29-E3 (hex) Infinix mobility limited -FC29E3 (base 16) Infinix mobility limited - RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG - HongKong HongKong 999077 - HK +64-37-A4 (hex) TOKYOSHUHA CO.,LTD. +6437A4 (base 16) TOKYOSHUHA CO.,LTD. + 1-8-9 KANDAIZUMICHO + CHIYODA-KU TOKYO 101-0024 + JP -9C-1C-6D (hex) HEFEI DATANG STORAGE TECHNOLOGY CO.,LTD -9C1C6D (base 16) HEFEI DATANG STORAGE TECHNOLOGY CO.,LTD - 7F BLOCK C J2 BUILDING INNOVATION PARK HIGH TECH DISTRICT - HEFEI AN HUI PROVINCE PR CHINA 220038 +90-CA-FA (hex) Google, Inc. +90CAFA (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +DC-AA-43 (hex) Shenzhen Terca Information Technology Co., Ltd. +DCAA43 (base 16) Shenzhen Terca Information Technology Co., Ltd. + Room1401, Block A, Building 12 , Shenzhen Bay Technology and Eco-Park , No. 18 Keji South Road , Nanshan District , Shenzhen + SHENZHEN GUANGDONG 518000 CN -64-1A-BA (hex) Dryad Networks GmbH -641ABA (base 16) Dryad Networks GmbH - Eisenbahnstr. 37 - Eberswalde Brandenburg 16225 - DE +44-71-47 (hex) Beijing Xiaomi Electronics Co.,Ltd +447147 (base 16) Beijing Xiaomi Electronics Co.,Ltd + Xiaomi Campus + Beijing Beijing 100085 + CN -34-94-54 (hex) Espressif Inc. -349454 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +F4-BB-C7 (hex) vivo Mobile Communication Co., Ltd. +F4BBC7 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN -00-13-DC (hex) IBTEK INC. -0013DC (base 16) IBTEK INC. - 16F, 30, Pei-Ping East Rd., - Taipei 100 - TW +00-FB-F9 (hex) Axiado Corporation +00FBF9 (base 16) Axiado Corporation + 2610 Orchard Parkway, Suite 300 + San Jose CA 95134 + US -D8-4A-2B (hex) zte corporation -D84A2B (base 16) zte corporation +98-66-10 (hex) zte corporation +986610 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China shenzhen guangdong 518057 CN -D0-F9-9B (hex) zte corporation -D0F99B (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +A8-DC-5A (hex) Digital Watchdog +A8DC5A (base 16) Digital Watchdog + 16220 Bloomfield Ave + Cerritos CA 90703 + US -F4-13-99 (hex) Aerospace new generation communications Co.,Ltd -F41399 (base 16) Aerospace new generation communications Co.,Ltd - Building 3, No. 36 Xiyong Avenue - CHONG QING 401332 - CN +1C-24-CD (hex) ASKEY COMPUTER CORP +1C24CD (base 16) ASKEY COMPUTER CORP + 10F, No.119, JIANKANG RD.,ZHINGHE DIST, + NEW TAIPEI CITY 23585 + TW -94-A9-A8 (hex) Texas Instruments -94A9A8 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +6C-72-E2 (hex) amitek +6C72E2 (base 16) amitek + #311, KETI, 226, Chemdangwari-ro, Buk-gu, Gwangju, 61011, Rep. of KOREA + Gwangju 61011 + KR -48-B4-23 (hex) Amazon Technologies Inc. -48B423 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +04-D9-C8 (hex) Hon Hai Precision Industry Co., Ltd. +04D9C8 (base 16) Hon Hai Precision Industry Co., Ltd. + GuangDongShenZhen + ShenZhen GuangDong 518109 + CN -70-70-AA (hex) Amazon Technologies Inc. -7070AA (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +90-65-60 (hex) EM Microelectronic +906560 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH -64-2F-C7 (hex) New H3C Technologies Co., Ltd -642FC7 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +A0-FB-83 (hex) Honor Device Co., Ltd. +A0FB83 (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 -60-26-AA (hex) Cisco Systems, Inc -6026AA (base 16) Cisco Systems, Inc +DC-0B-09 (hex) Cisco Systems, Inc +DC0B09 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -5C-31-92 (hex) Cisco Systems, Inc -5C3192 (base 16) Cisco Systems, Inc +08-F3-FB (hex) Cisco Systems, Inc +08F3FB (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -88-C2-27 (hex) HUAWEI TECHNOLOGIES CO.,LTD -88C227 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -80-54-D9 (hex) HUAWEI TECHNOLOGIES CO.,LTD -8054D9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +A0-36-BC (hex) ASUSTek COMPUTER INC. +A036BC (base 16) ASUSTek COMPUTER INC. + 15,Li-Te Rd., Peitou, Taipei 112, Taiwan + Taipei Taiwan 112 + TW -9C-85-66 (hex) Wingtech Mobile Communications Co.,Ltd. -9C8566 (base 16) Wingtech Mobile Communications Co.,Ltd. - 1F-3F,Yinfeng Mansion,No.5097 of Luosha Road,Luohu District - Shenzhen 518011 - CN +84-0B-BB (hex) MitraStar Technology Corp. +840BBB (base 16) MitraStar Technology Corp. + No. 6, Innovation Road II, + Hsinchu 300 + TW -B0-33-66 (hex) vivo Mobile Communication Co., Ltd. -B03366 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +E0-08-71 (hex) Dongguan Liesheng Electronic Co., Ltd. +E00871 (base 16) Dongguan Liesheng Electronic Co., Ltd. + F5, Building B, North Block, Gaosheng Tech Park, No. 84 Zhongli Road, Nancheng District, Dongguan Ci + dongguan guangdong 523000 CN -7C-C2-25 (hex) Samsung Electronics Co.,Ltd -7CC225 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -D4-13-F8 (hex) Peplink International Ltd. -D413F8 (base 16) Peplink International Ltd. - A5, 5/F, HK Spinners Industrial Building, Phase 6, 481 Castle Peak Road - Cheung Sha Wan Kowloon 0 - HK - -00-11-17 (hex) CESNET -001117 (base 16) CESNET - Zikova 4 - Praha 6 160 00 - CZ +9C-95-6E (hex) Microchip Technology Inc. +9C956E (base 16) Microchip Technology Inc. + 2355 W. Chandler Blvd. + Chandler AZ 85224 + US -9C-EC-61 (hex) Huawei Device Co., Ltd. -9CEC61 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +10-73-EB (hex) Infiniti Electro-Optics +1073EB (base 16) Infiniti Electro-Optics + 15 - 9th Ave S + Cranbrook British Columbia V1C 2L9 + CA -74-70-69 (hex) Huawei Device Co., Ltd. -747069 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +2C-A7-74 (hex) Texas Instruments +2CA774 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -14-FB-70 (hex) Huawei Device Co., Ltd. -14FB70 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +18-7A-3E (hex) Silicon Laboratories +187A3E (base 16) Silicon Laboratories + 400 West Cesar Chavez Street + Austin TX 78701 + US -84-D3-D5 (hex) Huawei Device Co., Ltd. -84D3D5 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +30-05-05 (hex) Intel Corporate +300505 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -9C-00-D3 (hex) SHENZHEN IK WORLD Technology Co., Ltd -9C00D3 (base 16) SHENZHEN IK WORLD Technology Co., Ltd - Aike intelligent industrial park, 167 gongchang Road, Xinhu street, Guangming New District, - SHENZHEN GUANGDONG 518000 - CN +B0-DC-EF (hex) Intel Corporate +B0DCEF (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -28-2D-06 (hex) AMPAK Technology,Inc. -282D06 (base 16) AMPAK Technology,Inc. - 3F, No.15-1 Zhonghua Road, Hsinchu Industrail Park, Hukou, - Hsinchu Hsinchu,Taiwan R.O.C. 30352 - TW +74-13-EA (hex) Intel Corporate +7413EA (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -20-2B-20 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -202B20 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 - CN +18-4E-03 (hex) HMD Global Oy +184E03 (base 16) HMD Global Oy + Bertel Jungin aukio 9 + Espoo 02600 + FI -A0-A3-09 (hex) Apple, Inc. -A0A309 (base 16) Apple, Inc. +70-B3-06 (hex) Apple, Inc. +70B306 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -5C-50-D9 (hex) Apple, Inc. -5C50D9 (base 16) Apple, Inc. +B8-49-6D (hex) Apple, Inc. +B8496D (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -E8-CA-C8 (hex) Hui Zhou Gaoshengda Technology Co.,LTD -E8CAC8 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD - No.75,Zhongkai High-Tech Development District,Huizhou - Hui Zhou Guangdong 516006 - CN - -D4-19-F6 (hex) NXP Semiconductor (Tianjin) LTD. -D419F6 (base 16) NXP Semiconductor (Tianjin) LTD. - No.15 Xinghua Avenue, Xiqing Economic Development Area - Tianjin 300385 - CN - -64-BF-6B (hex) HUAWEI TECHNOLOGIES CO.,LTD -64BF6B (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -30-49-9E (hex) HUAWEI TECHNOLOGIES CO.,LTD -30499E (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +9C-92-4F (hex) Apple, Inc. +9C924F (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -88-4D-7C (hex) Apple, Inc. -884D7C (base 16) Apple, Inc. +20-0E-2B (hex) Apple, Inc. +200E2B (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -A8-FE-9D (hex) Apple, Inc. -A8FE9D (base 16) Apple, Inc. +F0-D7-93 (hex) Apple, Inc. +F0D793 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -DC-97-3A (hex) Verana Networks -DC973A (base 16) Verana Networks - 100 Apollo Drive, Suite 201 - Chelmsford MA 01824 +DC-F3-1C (hex) Texas Instruments +DCF31C (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -FC-76-92 (hex) Semptian Co.,Ltd. -FC7692 (base 16) Semptian Co.,Ltd. - Floor 19, Block 1A, Phase 1, International Innovation Valley, Xili Community, Nanshan District - Shenzhen Guangdong 518052 - CN +54-45-38 (hex) Texas Instruments +544538 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -FC-AF-BE (hex) TireCheck GmbH -FCAFBE (base 16) TireCheck GmbH - Schmelzofenvorstadt 33 - Heidenheim/Brenz Baden-Württemberg 89520 - DE +30-3D-51 (hex) IEEE Registration Authority +303D51 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -18-48-BE (hex) Amazon Technologies Inc. -1848BE (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +38-F0-C8 (hex) Logitech +38F0C8 (base 16) Logitech + 7700 Gateway Blvd + Newark CA 94560 US -68-67-25 (hex) Espressif Inc. -686725 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +34-FE-1C (hex) CHOUNG HWA TECH CO.,LTD +34FE1C (base 16) CHOUNG HWA TECH CO.,LTD + #31 Jangja-ro, Namdong-gu + Incheon-si 21532 + KR + +60-CF-69 (hex) meerecompany +60CF69 (base 16) meerecompany + 69-12, Jeongmunsongsan-ro, Yanggam-myeon, Hwaseong-si, Gyeonggi-do, Republic of Korea + Hwaseong-si 18630 + KR + +4C-62-7B (hex) SmartCow AI Technologies Taiwan Ltd. +4C627B (base 16) SmartCow AI Technologies Taiwan Ltd. + 16F., No. 102, Songlong Rd., Xinyi Dist., + Taipei City 110059 + TW + +28-BC-05 (hex) BLU Products Inc +28BC05 (base 16) BLU Products Inc + 10814 NW 33rd Street + Miami FL 33172 + US + +1C-EF-03 (hex) Guangzhou V-SOLUTION Electronic Technology Co., Ltd. +1CEF03 (base 16) Guangzhou V-SOLUTION Electronic Technology Co., Ltd. + Room 601,Originality Building B2, NO.162 Science Avenue,Science Town + Guangzhou Guangdong 510663 CN -88-6E-E1 (hex) Erbe Elektromedizin GmbH -886EE1 (base 16) Erbe Elektromedizin GmbH - Waldhoernlestrasse 17 - Tuebingen Baden-Wuerttemberg 72072 - DE +58-B0-3E (hex) Nintendo Co.,Ltd +58B03E (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP -C4-75-AB (hex) Intel Corporate -C475AB (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +AC-4E-65 (hex) Fiberhome Telecommunication Technologies Co.,LTD +AC4E65 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN -C8-41-8A (hex) Samsung Electronics.,LTD -C8418A (base 16) Samsung Electronics.,LTD - 129, Samsung-ro, Yeongtong-gu - Suwon Gyeonggi-Do 443-742 - KR +A8-80-38 (hex) ShenZhen MovingComm Technology Co., Limited +A88038 (base 16) ShenZhen MovingComm Technology Co., Limited + 5F, FuXinFa Industrial Park, LiuXianDong Industrial Zone, + ShenZhen GuangDong 518055 + CN -54-7D-40 (hex) Powervision Tech Inc. -547D40 (base 16) Powervision Tech Inc. - Zone E,Ocean Venture Valley, No.40, Yangguang Rd, Nanhai new District - Weihai Shandong 264200 +BC-5D-A3 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +BC5DA3 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 CN -C0-D7-AA (hex) Arcadyan Corporation -C0D7AA (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +BC-7B-72 (hex) Huawei Device Co., Ltd. +BC7B72 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -AC-AC-E2 (hex) CHANGHONG (HONGKONG) TRADING LIMITED -ACACE2 (base 16) CHANGHONG (HONGKONG) TRADING LIMITED - Unit 1412, 14/F., West Tower, Shun Tak Centre, 168-200 Connaught Road Central, HongKong - HONG KONG HONG KONG 999077 - HK +F8-2B-7F (hex) Huawei Device Co., Ltd. +F82B7F (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -80-B7-45 (hex) The Silk Technologies ILC LTD -80B745 (base 16) The Silk Technologies ILC LTD - Haozma 1 - Yoqneam 20692 - IL +40-C3-BC (hex) Huawei Device Co., Ltd. +40C3BC (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -F8-9E-94 (hex) Intel Corporate -F89E94 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +28-53-E0 (hex) Sintela Ltd +2853E0 (base 16) Sintela Ltd + The Distillery, The Old Brewery, 9-11 Lodway, + Pill Bristol BS20 0DH + GB -C4-03-A8 (hex) Intel Corporate -C403A8 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +D8-68-A0 (hex) Samsung Electronics Co.,Ltd +D868A0 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -0C-B0-88 (hex) AITelecom -0CB088 (base 16) AITelecom - 1018,hanshin IT Tower Digital-ro 272 Guro-gu - seoul 08389 +04-29-2E (hex) Samsung Electronics Co.,Ltd +04292E (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 KR -20-66-FD (hex) CONSTELL8 NV -2066FD (base 16) CONSTELL8 NV - Sint-Bernardse steenweg 72 - Hemiksem 2620 - BE +34-5D-A8 (hex) Cisco Systems, Inc +345DA8 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -64-42-12 (hex) Shenzhen Water World Information Co.,Ltd. -644212 (base 16) Shenzhen Water World Information Co.,Ltd. - Room 201, No.26, Yifenghua Innovation Industrial Park, Xinshi Community, Dalang Subdistrict, Longhua District. - Shenzhen Guangdong 518000 +E0-80-6B (hex) Xiaomi Communications Co Ltd +E0806B (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -4C-AB-F8 (hex) ASKEY COMPUTER CORP -4CABF8 (base 16) ASKEY COMPUTER CORP - 10F,No.119,JIANKANG RD,ZHONGHE DIST - NEW TAIPEI TAIWAN 23585 +70-50-E7 (hex) IEEE Registration Authority +7050E7 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +70-AC-08 (hex) Silicon Laboratories +70AC08 (base 16) Silicon Laboratories + 400 West Cesar Chavez Street + Austin TX 78701 + US + +38-FD-F5 (hex) Renesas Electronics (Penang) Sdn. Bhd. +38FDF5 (base 16) Renesas Electronics (Penang) Sdn. Bhd. + Phase 3, Bayan Lepas FIZ + Bayan Lepas Penang 11900 + MY + +3C-26-E4 (hex) Cisco Systems, Inc +3C26E4 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +38-91-B7 (hex) Cisco Systems, Inc +3891B7 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +DC-36-0C (hex) Hitron Technologies. Inc +DC360C (base 16) Hitron Technologies. Inc + No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C + Hsin-chu Taiwan 300 TW -8C-56-46 (hex) LG Electronics -8C5646 (base 16) LG Electronics - 222 LG-ro, Jinwi-Myeon - Pyeongtaek-si Gyeonggi-do 17709 +38-12-7B (hex) Crenet Labs Co., Ltd. +38127B (base 16) Crenet Labs Co., Ltd. + Rm. 1, 10F., No. 181, Sec. 1, Datong Rd. + New Taipei City Xizhi Dist. 221451 + TW + +B0-E4-5C (hex) Samsung Electronics Co.,Ltd +B0E45C (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 KR -CC-71-90 (hex) VIETNAM POST AND TELECOMMUNICATION INDUSTRY TECHNOLOGY JOINT STOCK COMPANY -CC7190 (base 16) VIETNAM POST AND TELECOMMUNICATION INDUSTRY TECHNOLOGY JOINT STOCK COMPANY - HIGH TECH INDUSTRIAL ZONE I,HOA LAC, HIGH TECH PARK, HA BANG, THACH THAT - HANOI Hanoi 100000 - VN +88-F2-BD (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +88F2BD (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN -48-BD-4A (hex) HUAWEI TECHNOLOGIES CO.,LTD -48BD4A (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +6C-08-31 (hex) ANALOG SYSTEMS +6C0831 (base 16) ANALOG SYSTEMS + UNIT 12, 38 DLF INDUSTRIAL AREA KIRTI NAGAR NEW DELHI + NEW DELHI DELHI 110015 + IN + +A4-7E-FA (hex) Withings +A47EFA (base 16) Withings + 2 rue Maurice Hartmann + Issy-les-Moulineaux 92130 + FR + +78-91-DE (hex) Guangdong ACIGA Science&Technology Co.,Ltd +7891DE (base 16) Guangdong ACIGA Science&Technology Co.,Ltd + L203 Biguiyuan International Club, Beijiao Town, Shunde District + Fo Shan Guangdong 528312 CN -A8-D4-E0 (hex) HUAWEI TECHNOLOGIES CO.,LTD -A8D4E0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +BC-4C-A0 (hex) HUAWEI TECHNOLOGIES CO.,LTD +BC4CA0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -A0-40-6F (hex) HUAWEI TECHNOLOGIES CO.,LTD -A0406F (base 16) HUAWEI TECHNOLOGIES CO.,LTD +74-34-2B (hex) HUAWEI TECHNOLOGIES CO.,LTD +74342B (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -00-4F-1A (hex) HUAWEI TECHNOLOGIES CO.,LTD -004F1A (base 16) HUAWEI TECHNOLOGIES CO.,LTD +C4-12-EC (hex) HUAWEI TECHNOLOGIES CO.,LTD +C412EC (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -74-33-A6 (hex) Shenzhen SuperElectron Technology Co.,Ltd. -7433A6 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. - 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city - Shenzhen Guangdong 518000 - CN - -2C-0D-A7 (hex) Intel Corporate -2C0DA7 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -D4-63-DE (hex) vivo Mobile Communication Co., Ltd. -D463DE (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN - -D4-B7-D0 (hex) Ciena Corporation -D4B7D0 (base 16) Ciena Corporation - 7035 Ridge Road - Hanover MD 21076 +AC-A3-2F (hex) Solidigm Technology +ACA32F (base 16) Solidigm Technology + 1921 Corporate Center Circle, Suite 3B + Longmont 80501 US -14-00-E9 (hex) Mitel Networks Corporation -1400E9 (base 16) Mitel Networks Corporation - 4000 Innovation Drive - Kanata Ontario K2K3K1 - CA - -0C-90-43 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -0C9043 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 +4C-9E-6C (hex) BROADEX TECHNOLOGIES CO.LTD +4C9E6C (base 16) BROADEX TECHNOLOGIES CO.LTD + NO.306 YATAI ROAD + JIAXING ZHEJIANG 314006 CN -54-1F-8D (hex) zte corporation -541F8D (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +8C-98-06 (hex) SHENZHEN SEI ROBOTICS CO.,LTD +8C9806 (base 16) SHENZHEN SEI ROBOTICS CO.,LTD + the 4th floor,Productivity Building D,#5 Hi-Tech Middle 2nd Road,Shenzhen Hi-Tech Industrial Park, Nanshan District,Shenzhen,China + Shenzhen 518000 CN -2C-F1-BB (hex) zte corporation -2CF1BB (base 16) zte corporation +20-08-89 (hex) zte corporation +200889 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China shenzhen guangdong 518057 CN -BC-2C-E6 (hex) Cisco Systems, Inc -BC2CE6 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -CC-ED-4D (hex) Cisco Systems, Inc -CCED4D (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +20-0B-CF (hex) Nintendo Co.,Ltd +200BCF (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP -18-A9-A6 (hex) Nebra Ltd -18A9A6 (base 16) Nebra Ltd - Unit 4, Bells Yew Green Business Court - Bells Yew Green East Sussex TN3 9BJ - GB - -B0-F7-C4 (hex) Amazon Technologies Inc. -B0F7C4 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - -4C-8D-53 (hex) HUAWEI TECHNOLOGIES CO.,LTD -4C8D53 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -DC-2C-6E (hex) Routerboard.com -DC2C6E (base 16) Routerboard.com - Mikrotikls SIA - Riga Riga LV1009 - LV - -C4-BD-E5 (hex) Intel Corporate -C4BDE5 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +70-70-FC (hex) GOLD&WATER INDUSTRIAL LIMITED +7070FC (base 16) GOLD&WATER INDUSTRIAL LIMITED + NO.77 Leighton Road, 17/F Leighton Centre Causeway Bay ,HongKong + HongKong 999077 + HK -34-53-D2 (hex) Sagemcom Broadband SAS -3453D2 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +98-D9-3D (hex) Demant Enterprise A/S +98D93D (base 16) Demant Enterprise A/S + Kongebakken 9 + Smorum 2765 + DK -34-29-EF (hex) Qingdao Haier Technology Co.,Ltd -3429EF (base 16) Qingdao Haier Technology Co.,Ltd - A01, No.1, Haier Road, Laoshan District, - Qingdao Shan dong 266000 +B4-A6-78 (hex) Zhejiang Tmall Technology Co., Ltd. +B4A678 (base 16) Zhejiang Tmall Technology Co., Ltd. + No.969 Wenyi West Road, Wuchang Street, Yuhang District + Hangzhou Zhejiang 310024 CN -54-1D-61 (hex) YEESTOR Microelectronics Co., Ltd -541D61 (base 16) YEESTOR Microelectronics Co., Ltd - 7th Floor, Block A1, Digital Technology Park, Gaoxin 7th Road South, - Shenzhen 518057 +AC-C4-BD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +ACC4BD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -70-09-71 (hex) Samsung Electronics Co.,Ltd -700971 (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR - -D0-1B-49 (hex) Samsung Electronics Co.,Ltd -D01B49 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -FC-5F-49 (hex) Zhejiang Dahua Technology Co., Ltd. -FC5F49 (base 16) Zhejiang Dahua Technology Co., Ltd. - No.1199,Waterfront Road - Hangzhou Zhejiang 310053 +E4-B6-33 (hex) Wuxi Stars Microsystem Technology Co., Ltd +E4B633 (base 16) Wuxi Stars Microsystem Technology Co., Ltd + Room 2101, Tower C, Swan Tower, Wuxi Software Park, 111 Linghu Avenue, Xinwu District + Wuxi 214135 CN -30-35-C5 (hex) Huawei Device Co., Ltd. -3035C5 (base 16) Huawei Device Co., Ltd. +08-51-04 (hex) Huawei Device Co., Ltd. +085104 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -18-EF-3A (hex) Sichuan AI-Link Technology Co., Ltd. -18EF3A (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 - CN - -A0-FF-22 (hex) SHENZHEN APICAL TECHNOLOGY CO., LTD -A0FF22 (base 16) SHENZHEN APICAL TECHNOLOGY CO., LTD - 9/F.B Building,Tsinghua Unis Infoport ,LangShan Road,North District,Hi-tech Industrial Park,Nanshan - Shenzhen 518000 +78-5B-64 (hex) Huawei Device Co., Ltd. +785B64 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -F8-20-A9 (hex) Huawei Device Co., Ltd. -F820A9 (base 16) Huawei Device Co., Ltd. +54-E1-5B (hex) Huawei Device Co., Ltd. +54E15B (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -64-9E-31 (hex) Beijing Xiaomi Mobile Software Co., Ltd -649E31 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +24-26-D6 (hex) HUAWEI TECHNOLOGIES CO.,LTD +2426D6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -6C-B0-FD (hex) Shenzhen Xinghai Iot Technology Co.,Ltd -6CB0FD (base 16) Shenzhen Xinghai Iot Technology Co.,Ltd - South 8th Road, science and Technology Park, Nanshan District - Shenzhen 518063 +EC-81-9C (hex) HUAWEI TECHNOLOGIES CO.,LTD +EC819C (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -24-CD-8D (hex) Murata Manufacturing Co., Ltd. -24CD8D (base 16) Murata Manufacturing Co., Ltd. - 1-10-1, Higashikotari - Nagaokakyo-shi Kyoto 617-8555 - JP +C4-A1-0E (hex) IEEE Registration Authority +C4A10E (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -5C-02-14 (hex) Beijing Xiaomi Mobile Software Co., Ltd -5C0214 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +54-2F-04 (hex) Shanghai Longcheer Technology Co., Ltd. +542F04 (base 16) Shanghai Longcheer Technology Co., Ltd. + Bldg 1,No.401,Caobao RD,Xuhui Dist + Shanghai 200233 CN -E4-28-05 (hex) Pivotal Optics -E42805 (base 16) Pivotal Optics - 125 Wolf Road Suite 315 - Albany NY 12205 - US - -38-1F-8D (hex) Tuya Smart Inc. -381F8D (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 +1C-A4-10 (hex) Amlogic, Inc. +1CA410 (base 16) Amlogic, Inc. + 2518 Mission College Blvd, Suite 120 + Santa Clara CA 95054 US -20-8C-47 (hex) Tenstorrent Inc -208C47 (base 16) Tenstorrent Inc - 150 Ferrand Dr #901 - Toronto ON M3C 3E5 - CA - -68-15-D3 (hex) Zaklady Elektroniki i Mechaniki Precyzyjnej R&G S.A. -6815D3 (base 16) Zaklady Elektroniki i Mechaniki Precyzyjnej R&G S.A. - ul. Traugutta 7 - Mielec 39-300 - PL - -00-80-E7 (hex) Leonardo UK Ltd -0080E7 (base 16) Leonardo UK Ltd - Christopher Martin Road - Basildon Essex SS14 3EL - GB - -3C-9F-C3 (hex) Beijing Sinead Technology Co., Ltd. -3C9FC3 (base 16) Beijing Sinead Technology Co., Ltd. - Room 504,Block A, New material Building, Yongfeng industrial, Haiding District, Beijing.China - Beijing Beijing 100094 - CN +98-C8-1C (hex) BAYTEC LIMITED +98C81C (base 16) BAYTEC LIMITED + 107C, 31/f, The gateway, Tower 5, Harbour City, 15 canton road, Tsim Sha Tsui, Hong Kong + Harbour 999077 + HK -00-B0-EC (hex) EACEM -00B0EC (base 16) EACEM - Avenue Louise 140, Bte 6 - B-1050 BRUSSELS - BE +74-84-69 (hex) Nintendo Co.,Ltd +748469 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP -6C-D3-EE (hex) ZIMI CORPORATION -6CD3EE (base 16) ZIMI CORPORATION - Room A913, 159 Chengjiang Road - Jiangyin City Jiangsu Province 214400 - CN +8C-CB-DF (hex) FOXCONN INTERCONNECT TECHNOLOGY +8CCBDF (base 16) FOXCONN INTERCONNECT TECHNOLOGY + 66-1 Zhongshan Road, Tucheng District + New Taipei City Taiwan 23680 + TW -04-CD-15 (hex) Silicon Laboratories -04CD15 (base 16) Silicon Laboratories - 400 West Cesar Chavez Street - Austin 78701 +20-0B-16 (hex) Texas Instruments +200B16 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -78-5E-A2 (hex) Sunitec Enterprise Co.,Ltd -785EA2 (base 16) Sunitec Enterprise Co.,Ltd - 3F.,No.98-1,Mincyuan Rd.Sindian City - Taipei County 231 231141 - CN +88-01-F9 (hex) Texas Instruments +8801F9 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -B0-BE-83 (hex) Apple, Inc. -B0BE83 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +F8-55-48 (hex) Texas Instruments +F85548 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -10-32-1D (hex) HUAWEI TECHNOLOGIES CO.,LTD -10321D (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +68-E7-4A (hex) Texas Instruments +68E74A (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -E4-93-6A (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -E4936A (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +70-A6-BD (hex) Honor Device Co., Ltd. +70A6BD (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 -48-77-BD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -4877BD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN +84-69-93 (hex) HP Inc. +846993 (base 16) HP Inc. + 10300 Energy Dr + Spring TX 77389 + US -F8-56-C3 (hex) zte corporation -F856C3 (base 16) zte corporation +74-6F-88 (hex) zte corporation +746F88 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China shenzhen guangdong 518057 CN -38-FD-F8 (hex) Cisco Systems, Inc -38FDF8 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +74-71-8B (hex) Apple, Inc. +74718B (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -DC-F4-CA (hex) Apple, Inc. -DCF4CA (base 16) Apple, Inc. +70-31-7F (hex) Apple, Inc. +70317F (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -7C-FC-16 (hex) Apple, Inc. -7CFC16 (base 16) Apple, Inc. +A4-CF-99 (hex) Apple, Inc. +A4CF99 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -88-B9-45 (hex) Apple, Inc. -88B945 (base 16) Apple, Inc. +4C-2E-B4 (hex) Apple, Inc. +4C2EB4 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -18-E1-DE (hex) Chengdu ChipIntelli Technology Co., Ltd -18E1DE (base 16) Chengdu ChipIntelli Technology Co., Ltd - No. 106, 1st floor, building 6, No. 1480, North Tianfu Avenue, Chengdu high tech Zone - Chengdu Sichuan 610041 - CN - -8C-F6-81 (hex) Silicon Laboratories -8CF681 (base 16) Silicon Laboratories - 400 West Cesar Chavez Street - Austin TX 78701 +D0-98-9C (hex) ConMet +D0989C (base 16) ConMet + 5701 SE Columbia Way + Vancouver WA 98661 US -7C-66-EF (hex) Hon Hai Precision IND.CO.,LTD -7C66EF (base 16) Hon Hai Precision IND.CO.,LTD - No. 66 Chung Shan Road TU-Cheng Industrial district TAIPEI TAIWAN - TAIPEI TAIWAN 33859 - CN +34-AD-61 (hex) CELESTICA INC. +34AD61 (base 16) CELESTICA INC. + 1900-5140 Yonge Street PO Box 42 + Toronto Ontario M2N 6L7 + CA -44-17-93 (hex) Espressif Inc. -441793 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +2C-55-3C (hex) Vecima Networks Inc. +2C553C (base 16) Vecima Networks Inc. + 150 Cardinal Place + Saskatoon SK S7L 6H7 + CA -00-A3-88 (hex) SKY UK LIMITED -00A388 (base 16) SKY UK LIMITED - 130 Kings Road - Brentwood Essex 08854 - GB +B4-19-74 (hex) Apple, Inc. +B41974 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -7C-C1-77 (hex) INGRAM MICRO SERVICES -7CC177 (base 16) INGRAM MICRO SERVICES - 100 CHEMIN DE BAILLOT - MONTAUBAN 82000 - FR +60-95-BD (hex) Apple, Inc. +6095BD (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -78-76-D9 (hex) EXARA Group -7876D9 (base 16) EXARA Group - Andropova pr. 18 1 - Moscow 115432 - RU +EC-11-27 (hex) Texas Instruments +EC1127 (base 16) Texas Instruments + 12500 TI BLVD + Dallas 75243 + US -94-A4-08 (hex) Shenzhen Trolink Technology CO, LTD -94A408 (base 16) Shenzhen Trolink Technology CO, LTD - 201 B building 4 shijie, Chashu industry 505 block, Baoan airport Sanwei community, Hangcheng street Baoan area. - Shenzhen GuangDong 518000 +04-E8-92 (hex) SHENNAN CIRCUITS CO.,LTD +04E892 (base 16) SHENNAN CIRCUITS CO.,LTD + Gao Qiao Industrial Park East,Long Gang District, + Shenzhen Guangdong 518117 CN -7C-84-37 (hex) China Post Communications Equipment Co., Ltd. -7C8437 (base 16) China Post Communications Equipment Co., Ltd. - 6 / F, block D, No.156, Beijing International Financial Building, Fuxingmennei street,Xicheng District - Beijing Beijing 100031 - CN +BC-E9-E2 (hex) Brocade Communications Systems LLC +BCE9E2 (base 16) Brocade Communications Systems LLC + 1320 Ridder Park Dr + San Jose CA 95131 + US -B8-4D-43 (hex) HUNAN FN-LINK TECHNOLOGY LIMITED -B84D43 (base 16) HUNAN FN-LINK TECHNOLOGY LIMITED - No.8, Litong Road, Liuyan Economic & Tec - Changsha HUNAN 410329 +18-A5-9C (hex) IEEE Registration Authority +18A59C (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +10-96-1A (hex) CHIPSEA TECHNOLOGIES (SHENZHEN) CORP. +10961A (base 16) CHIPSEA TECHNOLOGIES (SHENZHEN) CORP. + 9F,BLOCK A,GARDEN CITY DIGITAL BUILDING,NO.1079 NANHAI ROAD,NANSHAN DISTRICT + SHEN ZHEN GUANG DONG 518000 CN -D0-F8-65 (hex) ITEL MOBILE LIMITED -D0F865 (base 16) ITEL MOBILE LIMITED - RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K - Hong Kong KOWLOON 999077 - HK +AC-BF-71 (hex) Bose Corporation +ACBF71 (base 16) Bose Corporation + The Mountain + Framingham MA 01701-9168 + US -C8-9F-1A (hex) HUAWEI TECHNOLOGIES CO.,LTD -C89F1A (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +AC-D3-1D (hex) Cisco Meraki +ACD31D (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US -D8-68-52 (hex) HUAWEI TECHNOLOGIES CO.,LTD -D86852 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +74-56-3C (hex) GIGA-BYTE TECHNOLOGY CO.,LTD. +74563C (base 16) GIGA-BYTE TECHNOLOGY CO.,LTD. + Pin-Jen City, Taoyuan, Taiwan, R.O.C. + Pin-Jen Taoyuan 324 + TW -AC-64-90 (hex) HUAWEI TECHNOLOGIES CO.,LTD -AC6490 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +EC-55-1C (hex) HUAWEI TECHNOLOGIES CO.,LTD +EC551C (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -A8-4A-63 (hex) TPV Display Technology(Xiamen) Co.,Ltd. -A84A63 (base 16) TPV Display Technology(Xiamen) Co.,Ltd. - No.1, Xianghai Road, Xiamen Torch Hi-Tech Industrial Development Zone, China - Xiamen Fujian 361101 - CN - -60-8F-A4 (hex) Nokia Solutions and Networks GmbH & Co. KG -608FA4 (base 16) Nokia Solutions and Networks GmbH & Co. KG - Werinherstrasse 91 - München Bavaria D-81541 - DE - -24-3F-AA (hex) Huawei Device Co., Ltd. -243FAA (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -D8-67-D3 (hex) Huawei Device Co., Ltd. -D867D3 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -48-47-4B (hex) Huawei Device Co., Ltd. -48474B (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +B4-83-51 (hex) Intel Corporate +B48351 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -80-6F-1C (hex) Huawei Device Co., Ltd. -806F1C (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +BC-F4-D4 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +BCF4D4 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 CN -E0-A2-58 (hex) Wanbang Digital Energy Co.,Ltd -E0A258 (base 16) Wanbang Digital Energy Co.,Ltd - NO.39 Longhui Road,Wujin District - Changzhou 213100 +CC-F3-05 (hex) SHENZHEN TIAN XING CHUANG ZHAN ELECTRONIC CO.,LTD +CCF305 (base 16) SHENZHEN TIAN XING CHUANG ZHAN ELECTRONIC CO.,LTD + Second floor, Building A, FengHangAvenue, Hangcheng Street, Bao'an District + Shenzhen Guangdong 518126 CN -4C-BC-E9 (hex) LG Innotek -4CBCE9 (base 16) LG Innotek - 26, Hanamsandan 5beon-ro - Gwangju Gwangsan-gu 506-731 +04-B9-7D (hex) AiVIS Co., Itd. +04B97D (base 16) AiVIS Co., Itd. + 112, Dumipo-ro, Jung-gu + Incheon Incheon 22394 KR -38-5C-76 (hex) PLANTRONICS, INC. -385C76 (base 16) PLANTRONICS, INC. - 345 ENCINAL STREET - SANTA CRUZ CA 95060 - US - -9C-50-D1 (hex) Murata Manufacturing Co., Ltd. -9C50D1 (base 16) Murata Manufacturing Co., Ltd. - 1-10-1, Higashikotari - Nagaokakyo-shi Kyoto 617-8555 - JP - -14-00-20 (hex) LongSung Technology (Shanghai) Co.,Ltd. -140020 (base 16) LongSung Technology (Shanghai) Co.,Ltd. - Room 606, Block B, Bldg. 1, No. 3000 Longdong Avenue., Zhangjiang Hi-Tech Park, Pudong District - ShangHai 201203 - CN - -30-B9-30 (hex) zte corporation -30B930 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -94-98-69 (hex) zte corporation -949869 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -50-3D-EB (hex) Zhejiang Tmall Technology Co., Ltd. -503DEB (base 16) Zhejiang Tmall Technology Co., Ltd. - Ali Center,No.3331 Keyuan South RD (Shenzhen bay), Nanshan District, Shenzhen Guangdong province - Shenzhen GuangDong 518000 +C4-C0-63 (hex) New H3C Technologies Co., Ltd +C4C063 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -F8-7F-A5 (hex) GREATEK -F87FA5 (base 16) GREATEK - ESTRADA MUNICIPAL PEDRO R SILVA - EXTREMA MG 37640000 - BR +E0-79-8D (hex) Silicon Laboratories +E0798D (base 16) Silicon Laboratories + 400 West Cesar Chavez Street + Austin TX 78701 + US -74-5C-FA (hex) Shenzhen Shunrui Gaojie Technology Co., Ltd. -745CFA (base 16) Shenzhen Shunrui Gaojie Technology Co., Ltd. - 502,Building A,No.18,Gongye 2nd Road,Dakan Industrial Zone,Dakan community,Xili street,Nanshan District - Shenzhen Guangdong 518055 +34-BD-20 (hex) Hangzhou Hikrobot Technology Co., Ltd. +34BD20 (base 16) Hangzhou Hikrobot Technology Co., Ltd. + Room 304, Unit B, Building 2, 399 Danfeng Road, Binjiang District, Hangzhou, Zhejiang + Hangzhou 310052 CN -5C-DF-B8 (hex) Shenzhen Unionmemory Information System Limited -5CDFB8 (base 16) Shenzhen Unionmemory Information System Limited - Factory Flat D24/F-02, Dong Jiao Tou, Houhai Road, Shekou, Nan Shan District - Shenzhen Guangdong 518067 - CN +AC-2A-A1 (hex) Cisco Systems, Inc +AC2AA1 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -2C-1A-05 (hex) Cisco Systems, Inc -2C1A05 (base 16) Cisco Systems, Inc +F8-E9-4F (hex) Cisco Systems, Inc +F8E94F (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -B4-0E-CF (hex) Bouffalo Lab (Nanjing) Co., Ltd. -B40ECF (base 16) Bouffalo Lab (Nanjing) Co., Ltd. - 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China - Nanjing Jiangsu 211800 - CN +30-89-4A (hex) Intel Corporate +30894A (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -F8-38-69 (hex) LG Electronics -F83869 (base 16) LG Electronics - Science Park W5, 10, Magokjungang 10-ro, Gangseo-gu - Seoul 07796 - KR +D8-9C-8E (hex) Comcast Cable Corporation +D89C8E (base 16) Comcast Cable Corporation + 1800 Arch Street + Philadelphia PA 19103 + US -2C-05-47 (hex) Shenzhen Phaten Tech. LTD -2C0547 (base 16) Shenzhen Phaten Tech. LTD - C-6 ideamonto industril 7002 Songbai Road Guangming District Shenzhen City Guangdong, China - Shenzhen 518108 +E0-6C-C5 (hex) Huawei Device Co., Ltd. +E06CC5 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -E0-C5-8F (hex) China Mobile IOT Company Limited -E0C58F (base 16) China Mobile IOT Company Limited - NO.8 Yu Ma Road, NanAn Area - Chongqing Chongqing 401336 +30-96-3B (hex) Huawei Device Co., Ltd. +30963B (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -E0-0A-F6 (hex) Liteon Technology Corporation -E00AF6 (base 16) Liteon Technology Corporation - 4F, 90, Chien 1 Road - New Taipei City Taiwan 23585 - TW - -F0-5E-CD (hex) Texas Instruments -F05ECD (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +8C-6B-DB (hex) Huawei Device Co., Ltd. +8C6BDB (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -2C-3A-91 (hex) Huawei Device Co., Ltd. -2C3A91 (base 16) Huawei Device Co., Ltd. +10-DA-49 (hex) Huawei Device Co., Ltd. +10DA49 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -64-F7-05 (hex) Huawei Device Co., Ltd. -64F705 (base 16) Huawei Device Co., Ltd. +60-18-3A (hex) Huawei Device Co., Ltd. +60183A (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -78-50-05 (hex) MOKO TECHNOLOGY Ltd -785005 (base 16) MOKO TECHNOLOGY Ltd - 2F, Building1,No.37 Xiaxintang Xintang village,Fucheng Street,Longhua Distric - Shenzhen Guangdong 518110 +18-C0-07 (hex) Huawei Device Co., Ltd. +18C007 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -F0-B6-1E (hex) Intel Corporate -F0B61E (base 16) Intel Corporate +98-59-7A (hex) Intel Corporate +98597A (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -C8-4D-44 (hex) Shenzhen Jiapeng Huaxiang Technology Co.,Ltd -C84D44 (base 16) Shenzhen Jiapeng Huaxiang Technology Co.,Ltd - 2nd floor, building 5, taihemei Industrial Zone, 128 Chunfeng Road, longbeiling community, Tangxia Town - Shenzhen Guangdong 518109 +64-49-7D (hex) Intel Corporate +64497D (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +B8-D6-1A (hex) Espressif Inc. +B8D61A (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN -98-B1-77 (hex) LANDIS + GYR -98B177 (base 16) LANDIS + GYR - 78th km Old National Road Athens-Corinth - Corinth 20100 - GR +BC-6E-6D (hex) EM Microelectronic +BC6E6D (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH -A0-29-BD (hex) Team Group Inc -A029BD (base 16) Team Group Inc - 3F., No. 166 Jian 1st Rd., - Zhonghe Dist. New Taipei City, 235 - TW +00-D4-9E (hex) Intel Corporate +00D49E (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -64-DC-DE (hex) ZheJiang FuChunJiang Information Technology Co.,Ltd -64DCDE (base 16) ZheJiang FuChunJiang Information Technology Co.,Ltd - 608 Golf Road, Dongzhou Street, Fuyang District, Hangzhou City, Zhejiang Province, China - Hangzhou City Zhejiang Province 311400 - CN +34-92-C2 (hex) Square Route Co., Ltd. +3492C2 (base 16) Square Route Co., Ltd. + Area-Shinagawa 13F, 1-9-36, Konan, Minato-ku + Tokyo Tokyo 108-0075 + JP -00-91-9E (hex) Intel Corporate -00919E (base 16) Intel Corporate +8C-17-59 (hex) Intel Corporate +8C1759 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -60-CE-41 (hex) HUAWEI TECHNOLOGIES CO.,LTD -60CE41 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +30-CB-36 (hex) Belden Singapore Pte. Ltd. +30CB36 (base 16) Belden Singapore Pte. Ltd. + 151 Lorong Chuan #05-01 New Tech Park Singapore + Singapore 556741 + SG + +30-BB-7D (hex) OnePlus Technology (Shenzhen) Co., Ltd +30BB7D (base 16) OnePlus Technology (Shenzhen) Co., Ltd + 18C02, 18C03, 18C04 ,18C05,TAIRAN BUILDING, + Shenzhen Guangdong 518000 + CN + +6C-67-EF (hex) HUAWEI TECHNOLOGIES CO.,LTD +6C67EF (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -28-17-09 (hex) HUAWEI TECHNOLOGIES CO.,LTD -281709 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +88-69-3D (hex) HUAWEI TECHNOLOGIES CO.,LTD +88693D (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -60-6E-E8 (hex) Xiaomi Communications Co Ltd -606EE8 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +00-99-1D (hex) HUAWEI TECHNOLOGIES CO.,LTD +00991D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -D0-B6-6F (hex) SERNET (SUZHOU) TECHNOLOGIES CORPORATION -D0B66F (base 16) SERNET (SUZHOU) TECHNOLOGIES CORPORATION - NO.8 Tangzhuang Road,Suzhou Industrial Park,Su ZhouCity,JiangSu Province,China - Suzhou 215021 - CN +28-3E-0C (hex) Preferred Robotics, Inc. +283E0C (base 16) Preferred Robotics, Inc. + Otemachi Bldg. 1-6-1 Otemachi + Chiyoda-ku Tokyo 100-0004 + JP -30-09-C0 (hex) Motorola Mobility LLC, a Lenovo Company -3009C0 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 +04-BC-9F (hex) Calix Inc. +04BC9F (base 16) Calix Inc. + 2777 Orchard Pkwy + San Jose CA 95131 US -50-F9-08 (hex) Wizardlab Co., Ltd. -50F908 (base 16) Wizardlab Co., Ltd. - #1603, 5, Gasan digital 1-ro, Geumcheon-gu, Seoul - Seoul 08594 +6C-A4-01 (hex) essensys plc +6CA401 (base 16) essensys plc + Aldgate Tower, Leman Street + London E1 8FA + GB + +B0-4A-6A (hex) Samsung Electronics Co.,Ltd +B04A6A (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 KR -EC-94-CB (hex) Espressif Inc. -EC94CB (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +A8-79-8D (hex) Samsung Electronics Co.,Ltd +A8798D (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +5C-ED-F4 (hex) Samsung Electronics Co.,Ltd +5CEDF4 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +28-3D-C2 (hex) Samsung Electronics Co.,Ltd +283DC2 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +B4-28-75 (hex) Futecho Solutions Private Limited +B42875 (base 16) Futecho Solutions Private Limited + 504, Commercial Complex, Savitri Cinema Compound, GK2 + New Delhi 110048 + IN + +30-23-64 (hex) Nokia Shanghai Bell Co., Ltd. +302364 (base 16) Nokia Shanghai Bell Co., Ltd. + No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai + Shanghai 201206 CN -50-C2-E8 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -50C2E8 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 +48-51-D0 (hex) Jiangsu Xinsheng Intelligent Technology Co., Ltd. +4851D0 (base 16) Jiangsu Xinsheng Intelligent Technology Co., Ltd. + 18th Floor,Inno laser Building,18-69 Changwu Mid Road,Changzhou Science & Education Town,Wujin District,Changzhou,Jiangsu213000,China + Changzhou Jiangsu 213000 CN -B0-46-92 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -B04692 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +DC-9A-7D (hex) HISENSE VISUAL TECHNOLOGY CO.,LTD +DC9A7D (base 16) HISENSE VISUAL TECHNOLOGY CO.,LTD + Qianwangang Road 218 + Qingdao Shandong 266510 CN -AC-76-4C (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -AC764C (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +84-F1-D0 (hex) EHOOME IOT PRIVATE LIMITED +84F1D0 (base 16) EHOOME IOT PRIVATE LIMITED + A-13, SECTOR-83, + NOIDA UTTAR PRADESH 201301 + IN + +20-8B-D1 (hex) NXP Semiconductor (Tianjin) LTD. +208BD1 (base 16) NXP Semiconductor (Tianjin) LTD. + No.15 Xinghua Avenue, Xiqing Economic Development Area + Tianjin 300385 CN -5C-58-E6 (hex) Palo Alto Networks -5C58E6 (base 16) Palo Alto Networks - 3000 Tannery Way - Santa Clara CA 95054 - US +2C-07-F6 (hex) SKG Health Technologies Co., Ltd. +2C07F6 (base 16) SKG Health Technologies Co., Ltd. + 23A Floor,Building 3,Zhongke R&D Park,No.009,Gaoxin South 1st Road, High-tech Zone Community,Yuehai street, Nanshan District,Shenzhen City,Guangdong Province,P.R.China + Shenzhen 518000 + CN -0C-E1-59 (hex) Shenzhen iStartek Technology Co., Ltd. -0CE159 (base 16) Shenzhen iStartek Technology Co., Ltd. - Zone B, 4/F, Building A6, Qinghu Dongli Industrial Park, No. 416 Xuegang North Road, Longhua District, - Shenzhen Guangdong 518109 +38-68-BE (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +3868BE (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 CN -00-14-13 (hex) Trebing & Himstedt Prozeßautomation GmbH & Co. KG -001413 (base 16) Trebing & Himstedt Prozeßautomation GmbH & Co. KG - Wilhelm Hennemann Straße 13 - Schwerin Mecklenburg-Vorpommern 19061 - DE +28-A5-3F (hex) vivo Mobile Communication Co., Ltd. +28A53F (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN -00-07-99 (hex) Tipping Point Technologies, Inc. -000799 (base 16) Tipping Point Technologies, Inc. - 7501 B N. Capital of TX Hwy. - Austin TX 78731 - US +8C-49-B6 (hex) vivo Mobile Communication Co., Ltd. +8C49B6 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN -DC-8D-8A (hex) Nokia Solutions and Networks GmbH & Co. KG -DC8D8A (base 16) Nokia Solutions and Networks GmbH & Co. KG - Werinherstrasse 91 - München Bavaria D-81541 - DE +80-77-A4 (hex) TECNO MOBILE LIMITED +8077A4 (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 + Hong Kong Hong Kong 999077 + HK -70-86-CE (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -7086CE (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 +7C-6C-F0 (hex) Shenzhen TINNO Mobile Technology Corp. +7C6CF0 (base 16) Shenzhen TINNO Mobile Technology Corp. + 4/F, H-3 Building, Qiao Cheng Eastern Industrial Park, Overseas Chinese Town, Shenzhen + Shenzhen guangdong 518053 CN -50-0F-59 (hex) STMicrolectronics International NV -500F59 (base 16) STMicrolectronics International NV - 39, Chemin du Champ-des-Filles - Geneva, Plan-les-Quates 1228 - CH +00-C3-0A (hex) Xiaomi Communications Co Ltd +00C30A (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -E4-2A-AC (hex) Microsoft Corporation -E42AAC (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 +88-52-EB (hex) Xiaomi Communications Co Ltd +8852EB (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +08-57-FB (hex) Amazon Technologies Inc. +0857FB (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -20-E7-B6 (hex) Universal Electronics, Inc. -20E7B6 (base 16) Universal Electronics, Inc. - 201 E. Sandpointe Ave - Santa Ana CA 92707 +00-D0-FC (hex) GRANITE MICROSYSTEMS +00D0FC (base 16) GRANITE MICROSYSTEMS + 10202 N. ENTERPRISE DRIVE + MEQUON WI 53092 US -D4-3D-F3 (hex) Zyxel Communications Corporation -D43DF3 (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW +B0-DE-28 (hex) Apple, Inc. +B0DE28 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -20-57-9E (hex) HUNAN FN-LINK TECHNOLOGY LIMITED -20579E (base 16) HUNAN FN-LINK TECHNOLOGY LIMITED - No.8, Litong Road, Liuyan Economic & Tec - Changsha HUNAN 410329 +7C-13-1D (hex) SERNET (SUZHOU) TECHNOLOGIES CORPORATION +7C131D (base 16) SERNET (SUZHOU) TECHNOLOGIES CORPORATION + NO.8 Tangzhuang Road,Suzhou Industrial Park,Su ZhouCity,JiangSu Province,China + Suzhou 215021 CN -C8-84-CF (hex) HUAWEI TECHNOLOGIES CO.,LTD -C884CF (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +D4-9F-DD (hex) Huawei Device Co., Ltd. +D49FDD (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -E0-23-D7 (hex) Sleep Number -E023D7 (base 16) Sleep Number - 1001 Third Avenue South - Minneapolis MN 55404 - US - -24-1D-48 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -241D48 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County - Chengdu Sichuan 611330 +00-D8-A2 (hex) Huawei Device Co., Ltd. +00D8A2 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -28-77-77 (hex) zte corporation -287777 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +DC-6B-1B (hex) Huawei Device Co., Ltd. +DC6B1B (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -60-E3-2B (hex) Intel Corporate -60E32B (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -D8-BE-65 (hex) Amazon Technologies Inc. -D8BE65 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - -28-DE-A8 (hex) zte corporation -28DEA8 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +98-CA-20 (hex) Shanghai SIMCOM Ltd. +98CA20 (base 16) Shanghai SIMCOM Ltd. + Building A, SIM Technology Building, No.633, Jinzhong Road, Changning District + Shanghai 200335 CN -80-8A-BD (hex) Samsung Electronics Co.,Ltd -808ABD (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR - -3C-CD-57 (hex) Beijing Xiaomi Mobile Software Co., Ltd -3CCD57 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +D8-50-A1 (hex) Hunan Danuo Technology Co.,LTD +D850A1 (base 16) Hunan Danuo Technology Co.,LTD + No. 89, Guoqing South Road, Beihu District, Chenzhou City, Hunan Province (in Xiangnan International Logistics Park) + Hunan 423000 CN -80-6A-10 (hex) Whisker Labs - Ting -806A10 (base 16) Whisker Labs - Ting - 12410 Milestone Center Dr, Suite 325 - Germantown MD 20876 - US - -C0-F8-27 (hex) Rapidmax Technology Corporation -C0F827 (base 16) Rapidmax Technology Corporation - 3F., No.531, Zhongzheng Rd. Xindian Dist. - New Taipei City 23148 - TW +44-1A-AC (hex) Elektrik Uretim AS EOS +441AAC (base 16) Elektrik Uretim AS EOS + Mustafa Kemal Mahallesi No:166 Çankaya + Ankara 06520 + TR -24-5E-48 (hex) Apple, Inc. -245E48 (base 16) Apple, Inc. +34-28-40 (hex) Apple, Inc. +342840 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -08-C7-29 (hex) Apple, Inc. -08C729 (base 16) Apple, Inc. +18-E7-B0 (hex) Apple, Inc. +18E7B0 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -C4-C3-6B (hex) Apple, Inc. -C4C36B (base 16) Apple, Inc. +50-57-8A (hex) Apple, Inc. +50578A (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -E8-A7-30 (hex) Apple, Inc. -E8A730 (base 16) Apple, Inc. +D4-FB-8E (hex) Apple, Inc. +D4FB8E (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -60-06-E3 (hex) Apple, Inc. -6006E3 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +2C-78-4C (hex) Iton Technology Corp. +2C784C (base 16) Iton Technology Corp. + Room 1302, Block A, Building 4, Tianan Cyber Park, Huangge Road,Longgang District + Shenzhen Guangdong 518116 + CN -10-6F-D9 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -106FD9 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 +48-87-59 (hex) Xiaomi Communications Co Ltd +488759 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -58-4D-42 (hex) Dragos, Inc. -584D42 (base 16) Dragos, Inc. - 1745 Dorsey Rd, Suite R - Hanover MD 21076 - US +AC-1E-9E (hex) Xiaomi Communications Co Ltd +AC1E9E (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -60-8A-10 (hex) Microchip Technology Inc. -608A10 (base 16) Microchip Technology Inc. - 2355 W. Chandler Blvd. - Chandler AZ 85224 - US +D0-1B-1F (hex) OHSUNG +D01B1F (base 16) OHSUNG + 335-4,SANHODAERO,GUMI,GYEONG BUK,KOREA + GUMI GYEONG BUK 730-030 + KR -A8-64-F1 (hex) Intel Corporate -A864F1 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +E0-AE-A2 (hex) HUAWEI TECHNOLOGIES CO.,LTD +E0AEA2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -D0-1E-1D (hex) SaiNXT Technologies LLP -D01E1D (base 16) SaiNXT Technologies LLP - Shop No. 7, Sonawala Building, 1st Floor, Proctor Road, Grant Road (E) - Mumbai Maharashtra 400007 - IN - -E0-67-81 (hex) Dongguan Liesheng Electronic Co., Ltd. -E06781 (base 16) Dongguan Liesheng Electronic Co., Ltd. - F5, Building B, North Block, Gaosheng Tech Park, No. 84 Zhongli Road, Nancheng District, Dongguan Ci - dongguan guangdong 523000 +44-45-6F (hex) SHENZHEN ONEGA TECHNOLOGY CO.,LTD +44456F (base 16) SHENZHEN ONEGA TECHNOLOGY CO.,LTD + 2 / F, building 3, zone B, Xusheng Xifa, Bao'an District, Shenzhen + Shenzhen Guangdong 518126 CN -B8-DA-E8 (hex) Huawei Device Co., Ltd. -B8DAE8 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +E8-FD-F8 (hex) Shanghai High-Flying Electronics Technology Co., Ltd +E8FDF8 (base 16) Shanghai High-Flying Electronics Technology Co., Ltd + Room 1002,#1Building,No.3000 Longdong Avenue,Pudong + Shanghai Shanghai 201202 CN -B8-AE-1D (hex) Guangzhou Xingyi Electronic Technology Co.,Ltd -B8AE1D (base 16) Guangzhou Xingyi Electronic Technology Co.,Ltd - Room 805-808, Room 801, Self-made Building 4, No. 1, 3 and 5, Kesheng Road, Guangzhou Private Science Park, No. 1633, Beitai Road, Baiyun District, Guangzhou - Guangzhou 51000 - CN +C0-18-03 (hex) HP Inc. +C01803 (base 16) HP Inc. + 10300 Energy Dr + Spring TX 77389 + US -D4-54-8B (hex) Intel Corporate -D4548B (base 16) Intel Corporate +04-CF-4B (hex) Intel Corporate +04CF4B (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -10-09-F9 (hex) Amazon Technologies Inc. -1009F9 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +FC-38-C4 (hex) China Grand Communications Co.,Ltd. +FC38C4 (base 16) China Grand Communications Co.,Ltd. + 2712, Block A, Phase II, Qianhai Economic and Trade Center, China Merchants Group, No. 3041, Yihai Avenue, Nanshan street, Shenzhen Hong Kong cooperation zone, Shenzhen + Shenzhen Guangdong 518066 + CN -D0-3E-7D (hex) CHIPSEA TECHNOLOGIES (SHENZHEN) CORP. -D03E7D (base 16) CHIPSEA TECHNOLOGIES (SHENZHEN) CORP. - 9F,BLOCK A,GARDEN CITY DIGITAL BUILDING,NO.1079 NANHAI ROAD,NANSHAN DISTRICT - SHEN ZHEN GUANG DONG 518000 +5C-F5-1A (hex) Zhejiang Dahua Technology Co., Ltd. +5CF51A (base 16) Zhejiang Dahua Technology Co., Ltd. + No.1199,Waterfront Road + Hangzhou Zhejiang 310053 CN -90-69-76 (hex) Withrobot Inc. -906976 (base 16) Withrobot Inc. - #1001, Seoul Forest M-tower, 31, Ttukseom-ro 1-gil, Seongdong-gu - Seoul Seoul 04778 - KR +10-1D-51 (hex) 8Mesh Networks Limited +101D51 (base 16) 8Mesh Networks Limited + Unit 607, 6/F, Yen Sheng Centre, + 64 Hoi Yuen Road Kwun Tong 000 + HK -FC-92-57 (hex) Renesas Electronics (Penang) Sdn. Bhd. -FC9257 (base 16) Renesas Electronics (Penang) Sdn. Bhd. - Phase 3, Bayan Lepas FIZ - Bayan Lepas Penang 11900 - MY +34-97-6F (hex) Rootech, Inc. +34976F (base 16) Rootech, Inc. + 102-611 Digital Empire2, 88, Sin won-ro + Yeongtong-gu, Suwon Gyeonggi-do 16681 + KR -60-DB-15 (hex) New H3C Technologies Co., Ltd -60DB15 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +6C-6C-0F (hex) HUAWEI TECHNOLOGIES CO.,LTD +6C6C0F (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -5C-A7-21 (hex) New H3C Technologies Co., Ltd -5CA721 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +CC-8C-BF (hex) Tuya Smart Inc. +CC8CBF (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US + +F8-97-A9 (hex) Ericsson AB +F897A9 (base 16) Ericsson AB + Torshamnsgatan 36 + Stockholm SE-164 80 + SE + +AC-93-C4 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +AC93C4 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN -A4-2A-71 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -A42A71 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County - Chengdu Sichuan 611330 +00-06-88 (hex) Telways Communication Co., Ltd. +000688 (base 16) Telways Communication Co., Ltd. + 3F, No. 53, Lane 258 + Taipei 114 + TW + +A8-13-06 (hex) vivo Mobile Communication Co., Ltd. +A81306 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN -60-A5-E2 (hex) Intel Corporate -60A5E2 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +1C-34-F1 (hex) Silicon Laboratories +1C34F1 (base 16) Silicon Laboratories + 400 West Cesar Chavez Street + Austin TX 78701 + US -48-F8-FF (hex) CHENGDU KT ELECTRONIC HI-TECH CO.,LTD -48F8FF (base 16) CHENGDU KT ELECTRONIC HI-TECH CO.,LTD - No.9, 3rd Wuke Road, Wuhou District - Chengdu Sichuan Province 610045 +CC-7D-5B (hex) Telink Semiconductor (Shanghai) Co., Ltd. +CC7D5B (base 16) Telink Semiconductor (Shanghai) Co., Ltd. + No. 1500 Zuchongzhi Rd, Building #3 + Shanghai 201203 CN -E8-C1-E8 (hex) Shenzhen Xiao Bi En Culture Education Technology Co.,Ltd. -E8C1E8 (base 16) Shenzhen Xiao Bi En Culture Education Technology Co.,Ltd. - 4GH Unit,Block D,Central Avenue,Intersection of Xixiang Avenue and Baoyuan Road,Labor Community,Xixiang Street,Baoan District - Shenzhen China 518102 +74-95-52 (hex) Xuzhou WIKA Electronics Control Technology Co., Ltd. +749552 (base 16) Xuzhou WIKA Electronics Control Technology Co., Ltd. + No.11 BaoLianSi Road Xuzhou Economic Development Zone + Xuzhou JiangSu,PRC 221001 CN -7C-70-DB (hex) Intel Corporate -7C70DB (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +44-EA-30 (hex) Samsung Electronics Co.,Ltd +44EA30 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -8C-94-CC (hex) SFR -8C94CC (base 16) SFR - 12 rue jean-philippe Rameau CS 80001 - La plaine saint denis FRANCE 93634 - FR +84-5F-04 (hex) Samsung Electronics Co.,Ltd +845F04 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -98-F2-17 (hex) Castlenet Technology Inc. -98F217 (base 16) Castlenet Technology Inc. - 5F., No. 10, Daye Rd., Beitou Dist. - Taipei City 112030 +9C-2F-9D (hex) Liteon Technology Corporation +9C2F9D (base 16) Liteon Technology Corporation + 4F, 90, Chien 1 Road + New Taipei City Taiwan 23585 TW -84-EB-EF (hex) Cisco Systems, Inc -84EBEF (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +A4-F3-3B (hex) zte corporation +A4F33B (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -A8-23-16 (hex) Nokia -A82316 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +30-E7-BC (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +30E7BC (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN -38-E3-9F (hex) Motorola Mobility LLC, a Lenovo Company -38E39F (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 +4C-FE-2E (hex) DongGuan Siyoto Electronics Co., Ltd +4CFE2E (base 16) DongGuan Siyoto Electronics Co., Ltd + Hecheng Industrial District, QiaoTou Town + DongGuan City Guangdong 523520 + CN + +0C-4E-C0 (hex) Maxlinear Inc +0C4EC0 (base 16) Maxlinear Inc + 1060 Rincon Circle + San Jose CA 95131 US -90-80-60 (hex) Nilfisk A/S -908060 (base 16) Nilfisk A/S - Kornmarksvej 1 - Broendby 2605 - DK +90-11-95 (hex) Amazon Technologies Inc. +901195 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -70-A6-CC (hex) Intel Corporate -70A6CC (base 16) Intel Corporate +7C-B5-66 (hex) Intel Corporate +7CB566 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -6C-43-3C (hex) TECNO MOBILE LIMITED -6C433C (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 - Hong Kong Hong Kong 999077 - HK - -78-BB-88 (hex) Maxio Technology (Hangzhou) Ltd. -78BB88 (base 16) Maxio Technology (Hangzhou) Ltd. - 6F, Building C, No.459 Qianmo Road, Juguang Center - Hangzhou Zhejiang 310051 - CN - -44-67-52 (hex) Wistron INFOCOMM (Zhongshan) CORPORATION -446752 (base 16) Wistron INFOCOMM (Zhongshan) CORPORATION - 15 Cuiwei Road, Cuiheng New District - zhongshan Guangdong 528400 +40-32-9D (hex) Union Image Co.,Ltd +40329D (base 16) Union Image Co.,Ltd + Building A2, Ding Bao Hong Green High Garden, Shiyan Street, Bao 'an District + SHENZHEN GuangDong 518108 CN -60-B6-E1 (hex) Texas Instruments -60B6E1 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +50-A0-30 (hex) IEEE Registration Authority +50A030 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -6C-79-B8 (hex) Texas Instruments -6C79B8 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +6C-4B-B4 (hex) HUMAX Co., Ltd. +6C4BB4 (base 16) HUMAX Co., Ltd. + HUMAX Village, 216, Hwangsaeul-ro, Bu + Seongnam-si Gyeonggi-do 463-875 + KR -8C-A3-99 (hex) SERVERCOM (INDIA) PRIVATE LIMITED -8CA399 (base 16) SERVERCOM (INDIA) PRIVATE LIMITED - E-43/1 OKHLA INDUSTRIAL AREA PHASE-II NEW DELHI SOUTH DELHI - NEW DELHI NA - IN +58-9B-F7 (hex) Hefei Radio Communication Technology Co., Ltd +589BF7 (base 16) Hefei Radio Communication Technology Co., Ltd + No.108, YinXing Road, High-tech Development Zone + Hefei Anhui 230088 + CN -D4-8D-D9 (hex) Meld Technology, Inc -D48DD9 (base 16) Meld Technology, Inc - 725 San Aleso Ave - Sunnyvale CA 94085 - US +00-0E-24 (hex) Huwell Technology Inc. +000E24 (base 16) Huwell Technology Inc. + 1F 82-21, Majin Building + Seoul 135-010 + KR -58-2F-F7 (hex) Sagemcom Broadband SAS -582FF7 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +4C-77-13 (hex) Renesas Electronics (Penang) Sdn. Bhd. +4C7713 (base 16) Renesas Electronics (Penang) Sdn. Bhd. + Phase 3, Bayan Lepas FIZ + Bayan Lepas Penang 11900 + MY -AC-E7-7B (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -ACE77B (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - FL12,TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, - Chengdu Sichuan 610000 +D8-AA-59 (hex) Tonly Technology Co. Ltd +D8AA59 (base 16) Tonly Technology Co. Ltd + Section 37, Zhongkai Hi-Tech Development Zone + Huizhou Guangdong 516006 CN -40-F4-20 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -40F420 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - FL12,TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, - Chengdu Sichuan 610000 +64-CF-13 (hex) Weigao Nikkiso(Weihai)Dialysis Equipment Co.,Ltd +64CF13 (base 16) Weigao Nikkiso(Weihai)Dialysis Equipment Co.,Ltd + No.20,Xingshan Road,Wego Industrial Zone,Chucun,Weihai,Shandong,China + Weihai Shandong 264209 CN -64-5D-92 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -645D92 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - FL12,TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, Chengdu, Sichuan - Chengdu Sichuan 610000 +50-E9-DF (hex) Quectel Wireless Solutions Co.,Ltd. +50E9DF (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN -8C-FD-15 (hex) Imagine Marketing Private Limited -8CFD15 (base 16) Imagine Marketing Private Limited - 501B, Shri Guru Har Krishan Bhavan Charat Singh Colony Road, Chakala, Andheri East, - Mumbai Maharashtra 400093 - IN - -14-22-3B (hex) Google, Inc. -14223B (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US - -C4-FF-22 (hex) Huawei Device Co., Ltd. -C4FF22 (base 16) Huawei Device Co., Ltd. +E8-FA-23 (hex) Huawei Device Co., Ltd. +E8FA23 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -A0-A0-DC (hex) Huawei Device Co., Ltd. -A0A0DC (base 16) Huawei Device Co., Ltd. +EC-3A-52 (hex) Huawei Device Co., Ltd. +EC3A52 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -C4-80-8A (hex) Cloud Diagnostics Canada ULC -C4808A (base 16) Cloud Diagnostics Canada ULC - 72 Victoria St. S., Unit 100 - Kitchener Ontario N2G 4Y9 - CA +44-1B-88 (hex) Apple, Inc. +441B88 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -2C-DD-E9 (hex) Arista Networks -2CDDE9 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 +80-04-5F (hex) Apple, Inc. +80045F (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -70-97-41 (hex) Arcadyan Corporation -709741 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +9C-3E-53 (hex) Apple, Inc. +9C3E53 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -E4-08-E7 (hex) Quectel Wireless Solutions Co.,Ltd. -E408E7 (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN +C8-89-F3 (hex) Apple, Inc. +C889F3 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -0C-8D-CA (hex) Samsung Electronics Co.,Ltd -0C8DCA (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +10-B9-C4 (hex) Apple, Inc. +10B9C4 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -CC-0D-E7 (hex) B METERS S.R.L. -CC0DE7 (base 16) B METERS S.R.L. - VIA FRIULI 3 - GONARS UDINE 33050 - IT +60-10-9E (hex) HUAWEI TECHNOLOGIES CO.,LTD +60109E (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -44-56-E2 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -4456E2 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County - Chengdu Sichuan 611330 +48-02-86 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +480286 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 CN -68-3A-48 (hex) SAMJIN Co., Ltd. -683A48 (base 16) SAMJIN Co., Ltd. - 199-6, Anyang 7-dong, Manan-gu - Anyang-si Gyeonggi-do 430-817 - KR +94-DE-B8 (hex) Silicon Laboratories +94DEB8 (base 16) Silicon Laboratories + 400 West Cesar Chavez Street + Austin TX 78701 + US -CC-A2-60 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -CCA260 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - FL12,TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, - Chengdu Sichuan 610000 - CN +04-0D-84 (hex) Silicon Laboratories +040D84 (base 16) Silicon Laboratories + 400 West Cesar Chavez Street + Austin TX 78701 + US -9C-61-21 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -9C6121 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - FL12,TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, - Chengdu Sichuan 610000 +9C-74-03 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +9C7403 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -E0-4F-BD (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -E04FBD (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - FL12,TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, - Chengdu Sichuan 610000 +3C-EC-DE (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD +3CECDE (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD + 19-22# Building, Star-net Science Plaza, Juyuanzhou, + FUZHOU FUJIAN 350002 CN -5C-A1-76 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -5CA176 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - FL12, TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, - Chengdu Sichuan 610000 +9C-6B-00 (hex) ASRock Incorporation +9C6B00 (base 16) ASRock Incorporation + 2F.,No.37, Sec.2, Jhongyang S.Rd., Beitou Distric, + Taipei 112 + TW + +54-F8-2A (hex) u-blox AG +54F82A (base 16) u-blox AG + Zuercherstrasse 68 + Thalwil 8800 + CH + +D4-35-38 (hex) Beijing Xiaomi Mobile Software Co., Ltd +D43538 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -4C-D3-AF (hex) HMD Global Oy -4CD3AF (base 16) HMD Global Oy - Bertel Jungin aukio 9 - Espoo 02600 - FI +10-CE-02 (hex) Amazon Technologies Inc. +10CE02 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -C8-51-42 (hex) Samsung Electronics Co.,Ltd -C85142 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +98-67-2E (hex) Skullcandy +98672E (base 16) Skullcandy + 6301 N. LANDMARK DRIVE + Park City 84098 + US -10-E4-C2 (hex) Samsung Electronics Co.,Ltd -10E4C2 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +A8-3A-48 (hex) Ubiqcom India Pvt Ltd +A83A48 (base 16) Ubiqcom India Pvt Ltd + First Floor, D-92, Sector-63 + Noida Uttar Pradesh 201301 + IN -AC-D8-29 (hex) Bouffalo Lab (Nanjing) Co., Ltd. -ACD829 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. - 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China - Nanjing Jiangsu 211800 +C8-5A-CF (hex) HP Inc. +C85ACF (base 16) HP Inc. + 10300 Energy Dr + Spring TX 77389 + US + +FC-29-E3 (hex) Infinix mobility limited +FC29E3 (base 16) Infinix mobility limited + RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG + HongKong HongKong 999077 + HK + +9C-1C-6D (hex) HEFEI DATANG STORAGE TECHNOLOGY CO.,LTD +9C1C6D (base 16) HEFEI DATANG STORAGE TECHNOLOGY CO.,LTD + 7F BLOCK C J2 BUILDING INNOVATION PARK HIGH TECH DISTRICT + HEFEI AN HUI PROVINCE PR CHINA 220038 CN -5C-62-5A (hex) CANON INC. -5C625A (base 16) CANON INC. - 30-2 Shimomaruko 3-chome, - Ohta-ku Tokyo 146-8501 - JP +64-1A-BA (hex) Dryad Networks GmbH +641ABA (base 16) Dryad Networks GmbH + Eisenbahnstr. 37 + Eberswalde Brandenburg 16225 + DE -7C-0A-3F (hex) Samsung Electronics Co.,Ltd -7C0A3F (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR +00-13-DC (hex) IBTEK INC. +0013DC (base 16) IBTEK INC. + 16F, 30, Pei-Ping East Rd., + Taipei 100 + TW -08-AA-89 (hex) zte corporation -08AA89 (base 16) zte corporation +D8-4A-2B (hex) zte corporation +D84A2B (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 + shenzhen guangdong 518057 CN -04-D6-0E (hex) FUNAI ELECTRIC CO., LTD. -04D60E (base 16) FUNAI ELECTRIC CO., LTD. - 7-7-1, Nakagaito - Daito Osaka 574-0013 - JP +D0-F9-9B (hex) zte corporation +D0F99B (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -B0-C9-52 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -B0C952 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +F4-13-99 (hex) Aerospace new generation communications Co.,Ltd +F41399 (base 16) Aerospace new generation communications Co.,Ltd + Building 3, No. 36 Xiyong Avenue + CHONG QING 401332 CN -D0-E0-42 (hex) Cisco Systems, Inc -D0E042 (base 16) Cisco Systems, Inc +94-A9-A8 (hex) Texas Instruments +94A9A8 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +48-B4-23 (hex) Amazon Technologies Inc. +48B423 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +70-70-AA (hex) Amazon Technologies Inc. +7070AA (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +64-2F-C7 (hex) New H3C Technologies Co., Ltd +642FC7 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN + +60-26-AA (hex) Cisco Systems, Inc +6026AA (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -C4-74-69 (hex) BT9 -C47469 (base 16) BT9 - Dolev 33 - Tefen 2495900 - IL +5C-31-92 (hex) Cisco Systems, Inc +5C3192 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -F4-6F-A4 (hex) Physik Instrumente GmbH & Co. KG -F46FA4 (base 16) Physik Instrumente GmbH & Co. KG - Auf der Roemerstr. 1 - Karlsruhe 76228 - DE +88-C2-27 (hex) HUAWEI TECHNOLOGIES CO.,LTD +88C227 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -C8-BB-81 (hex) Huawei Device Co., Ltd. -C8BB81 (base 16) Huawei Device Co., Ltd. +80-54-D9 (hex) HUAWEI TECHNOLOGIES CO.,LTD +8054D9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +9C-85-66 (hex) Wingtech Mobile Communications Co.,Ltd. +9C8566 (base 16) Wingtech Mobile Communications Co.,Ltd. + 1F-3F,Yinfeng Mansion,No.5097 of Luosha Road,Luohu District + Shenzhen 518011 + CN + +B0-33-66 (hex) vivo Mobile Communication Co., Ltd. +B03366 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + +7C-C2-25 (hex) Samsung Electronics Co.,Ltd +7CC225 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +D4-13-F8 (hex) Peplink International Ltd. +D413F8 (base 16) Peplink International Ltd. + A5, 5/F, HK Spinners Industrial Building, Phase 6, 481 Castle Peak Road + Cheung Sha Wan Kowloon 0 + HK + +00-11-17 (hex) CESNET +001117 (base 16) CESNET + Zikova 4 + Praha 6 160 00 + CZ + +9C-EC-61 (hex) Huawei Device Co., Ltd. +9CEC61 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -1C-47-2F (hex) Huawei Device Co., Ltd. -1C472F (base 16) Huawei Device Co., Ltd. +74-70-69 (hex) Huawei Device Co., Ltd. +747069 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -20-5E-64 (hex) Huawei Device Co., Ltd. -205E64 (base 16) Huawei Device Co., Ltd. +14-FB-70 (hex) Huawei Device Co., Ltd. +14FB70 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -F4-41-9E (hex) Huawei Device Co., Ltd. -F4419E (base 16) Huawei Device Co., Ltd. +84-D3-D5 (hex) Huawei Device Co., Ltd. +84D3D5 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -90-F9-B7 (hex) HUAWEI TECHNOLOGIES CO.,LTD -90F9B7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +9C-00-D3 (hex) SHENZHEN IK WORLD Technology Co., Ltd +9C00D3 (base 16) SHENZHEN IK WORLD Technology Co., Ltd + Aike intelligent industrial park, 167 gongchang Road, Xinhu street, Guangming New District, + SHENZHEN GUANGDONG 518000 + CN + +28-2D-06 (hex) AMPAK Technology,Inc. +282D06 (base 16) AMPAK Technology,Inc. + 3F, No.15-1 Zhonghua Road, Hsinchu Industrail Park, Hukou, + Hsinchu Hsinchu,Taiwan R.O.C. 30352 + TW + +20-2B-20 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +202B20 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 + CN + +A0-A3-09 (hex) Apple, Inc. +A0A309 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +5C-50-D9 (hex) Apple, Inc. +5C50D9 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +E8-CA-C8 (hex) Hui Zhou Gaoshengda Technology Co.,LTD +E8CAC8 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD + No.75,Zhongkai High-Tech Development District,Huizhou + Hui Zhou Guangdong 516006 + CN + +D4-19-F6 (hex) NXP Semiconductor (Tianjin) LTD. +D419F6 (base 16) NXP Semiconductor (Tianjin) LTD. + No.15 Xinghua Avenue, Xiqing Economic Development Area + Tianjin 300385 + CN + +64-BF-6B (hex) HUAWEI TECHNOLOGIES CO.,LTD +64BF6B (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -F4-45-88 (hex) HUAWEI TECHNOLOGIES CO.,LTD -F44588 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +30-49-9E (hex) HUAWEI TECHNOLOGIES CO.,LTD +30499E (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -54-AE-D2 (hex) CSL Dualcom Ltd -54AED2 (base 16) CSL Dualcom Ltd - Salamander Quay West, Park Lane - Harefield Middlesex UB9 6NZ - GB +88-4D-7C (hex) Apple, Inc. +884D7C (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -80-B6-55 (hex) Intel Corporate -80B655 (base 16) Intel Corporate +A8-FE-9D (hex) Apple, Inc. +A8FE9D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +DC-97-3A (hex) Verana Networks +DC973A (base 16) Verana Networks + 100 Apollo Drive, Suite 201 + Chelmsford MA 01824 + US + +FC-76-92 (hex) Semptian Co.,Ltd. +FC7692 (base 16) Semptian Co.,Ltd. + Floor 19, Block 1A, Phase 1, International Innovation Valley, Xili Community, Nanshan District + Shenzhen Guangdong 518052 + CN + +FC-AF-BE (hex) TireCheck GmbH +FCAFBE (base 16) TireCheck GmbH + Schmelzofenvorstadt 33 + Heidenheim/Brenz Baden-Württemberg 89520 + DE + +18-48-BE (hex) Amazon Technologies Inc. +1848BE (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +88-6E-E1 (hex) Erbe Elektromedizin GmbH +886EE1 (base 16) Erbe Elektromedizin GmbH + Waldhoernlestrasse 17 + Tuebingen Baden-Wuerttemberg 72072 + DE + +C4-75-AB (hex) Intel Corporate +C475AB (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -0C-CB-0C (hex) iSYS RTS GmbH -0CCB0C (base 16) iSYS RTS GmbH - Moosacher Str. 88 - Munich Bavaria 80809 - DE +C8-41-8A (hex) Samsung Electronics.,LTD +C8418A (base 16) Samsung Electronics.,LTD + 129, Samsung-ro, Yeongtong-gu + Suwon Gyeonggi-Do 443-742 + KR -F4-4E-E3 (hex) Intel Corporate -F44EE3 (base 16) Intel Corporate +54-7D-40 (hex) Powervision Tech Inc. +547D40 (base 16) Powervision Tech Inc. + Zone E,Ocean Venture Valley, No.40, Yangguang Rd, Nanhai new District + Weihai Shandong 264200 + CN + +C0-D7-AA (hex) Arcadyan Corporation +C0D7AA (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW + +AC-AC-E2 (hex) CHANGHONG (HONGKONG) TRADING LIMITED +ACACE2 (base 16) CHANGHONG (HONGKONG) TRADING LIMITED + Unit 1412, 14/F., West Tower, Shun Tak Centre, 168-200 Connaught Road Central, HongKong + HONG KONG HONG KONG 999077 + HK + +80-B7-45 (hex) The Silk Technologies ILC LTD +80B745 (base 16) The Silk Technologies ILC LTD + Haozma 1 + Yoqneam 20692 + IL + +F8-9E-94 (hex) Intel Corporate +F89E94 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -78-F2-35 (hex) Sichuan AI-Link Technology Co., Ltd. -78F235 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 +C4-03-A8 (hex) Intel Corporate +C403A8 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +0C-B0-88 (hex) AITelecom +0CB088 (base 16) AITelecom + 1018,hanshin IT Tower Digital-ro 272 Guro-gu + seoul 08389 + KR + +20-66-FD (hex) CONSTELL8 NV +2066FD (base 16) CONSTELL8 NV + Sint-Bernardse steenweg 72 + Hemiksem 2620 + BE + +64-42-12 (hex) Shenzhen Water World Information Co.,Ltd. +644212 (base 16) Shenzhen Water World Information Co.,Ltd. + Room 201, No.26, Yifenghua Innovation Industrial Park, Xinshi Community, Dalang Subdistrict, Longhua District. + Shenzhen Guangdong 518000 CN -08-2F-E9 (hex) HUAWEI TECHNOLOGIES CO.,LTD -082FE9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +4C-AB-F8 (hex) ASKEY COMPUTER CORP +4CABF8 (base 16) ASKEY COMPUTER CORP + 10F,No.119,JIANKANG RD,ZHONGHE DIST + NEW TAIPEI TAIWAN 23585 + TW + +8C-56-46 (hex) LG Electronics +8C5646 (base 16) LG Electronics + 222 LG-ro, Jinwi-Myeon + Pyeongtaek-si Gyeonggi-do 17709 + KR + +CC-71-90 (hex) VIETNAM POST AND TELECOMMUNICATION INDUSTRY TECHNOLOGY JOINT STOCK COMPANY +CC7190 (base 16) VIETNAM POST AND TELECOMMUNICATION INDUSTRY TECHNOLOGY JOINT STOCK COMPANY + HIGH TECH INDUSTRIAL ZONE I,HOA LAC, HIGH TECH PARK, HA BANG, THACH THAT + HANOI Hanoi 100000 + VN + +48-BD-4A (hex) HUAWEI TECHNOLOGIES CO.,LTD +48BD4A (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -98-48-74 (hex) HUAWEI TECHNOLOGIES CO.,LTD -984874 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +A8-D4-E0 (hex) HUAWEI TECHNOLOGIES CO.,LTD +A8D4E0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -1C-A6-81 (hex) HUAWEI TECHNOLOGIES CO.,LTD -1CA681 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +A0-40-6F (hex) HUAWEI TECHNOLOGIES CO.,LTD +A0406F (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -E8-A6-60 (hex) HUAWEI TECHNOLOGIES CO.,LTD -E8A660 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +00-4F-1A (hex) HUAWEI TECHNOLOGIES CO.,LTD +004F1A (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -CC-24-2E (hex) Shenzhen SuperElectron Technology Co.,Ltd. -CC242E (base 16) Shenzhen SuperElectron Technology Co.,Ltd. +74-33-A6 (hex) Shenzhen SuperElectron Technology Co.,Ltd. +7433A6 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city Shenzhen Guangdong 518000 CN -F0-01-6E (hex) Tianyi Telecom Terminals Company Limited -F0016E (base 16) Tianyi Telecom Terminals Company Limited - 6/F Changshang Building, No.29 North Xinhua Street, Xicheng District - Beijing 100031 - CN +2C-0D-A7 (hex) Intel Corporate +2C0DA7 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -EC-0B-AE (hex) Hangzhou BroadLink Technology Co.,Ltd -EC0BAE (base 16) Hangzhou BroadLink Technology Co.,Ltd - Room 101,1/F,Unit C,Building 1,No.57 Jiang'er Road,Changhe Street,Binjiang District,Hangzhou,Zhejiang,P.R.China - Hangzhou Zhejiang 310052 +D4-63-DE (hex) vivo Mobile Communication Co., Ltd. +D463DE (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN -80-25-11 (hex) ITEL MOBILE LIMITED -802511 (base 16) ITEL MOBILE LIMITED - RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K - Hong Kong KOWLOON 999077 - HK +D4-B7-D0 (hex) Ciena Corporation +D4B7D0 (base 16) Ciena Corporation + 7035 Ridge Road + Hanover MD 21076 + US -38-02-DE (hex) Sercomm Corporation. -3802DE (base 16) Sercomm Corporation. - 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen - Miao-Lih Hsuan 115 - TW +14-00-E9 (hex) Mitel Networks Corporation +1400E9 (base 16) Mitel Networks Corporation + 4000 Innovation Drive + Kanata Ontario K2K3K1 + CA -D0-CF-D8 (hex) Huizhou Boshijie Technology Co.,Ltd -D0CFD8 (base 16) Huizhou Boshijie Technology Co.,Ltd - No.1 Xisan road, Huifeng west road, Zhongkai high-tech zone - Huizhou Guangdong 516006 +0C-90-43 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +0C9043 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 CN -2C-BE-EB (hex) Nothing Technology Limited -2CBEEB (base 16) Nothing Technology Limited - 11 Staple Inn - London London WC1V 7QH - GB +54-1F-8D (hex) zte corporation +541F8D (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -74-09-AC (hex) Quext, LLC -7409AC (base 16) Quext, LLC - 5214 68th St., Suite 201 - Lubbock TX 79424 - US +2C-F1-BB (hex) zte corporation +2CF1BB (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -F0-4A-02 (hex) Cisco Systems, Inc -F04A02 (base 16) Cisco Systems, Inc +BC-2C-E6 (hex) Cisco Systems, Inc +BC2CE6 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -50-9A-46 (hex) Safetrust Inc -509A46 (base 16) Safetrust Inc - 8112 Mill Creek Rd - Fremont CA 94539 +CC-ED-4D (hex) Cisco Systems, Inc +CCED4D (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -A4-35-2D (hex) TRIZ Networks corp. -A4352D (base 16) TRIZ Networks corp. - 815 Daewangpangyo-ro - Sujeong-gu, Seongnam-si Gyeonggi-do 13449 - KR - -B8-81-FA (hex) Apple, Inc. -B881FA (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +18-A9-A6 (hex) Nebra Ltd +18A9A6 (base 16) Nebra Ltd + Unit 4, Bells Yew Green Business Court + Bells Yew Green East Sussex TN3 9BJ + GB -9C-76-0E (hex) Apple, Inc. -9C760E (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +B0-F7-C4 (hex) Amazon Technologies Inc. +B0F7C4 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -94-EA-32 (hex) Apple, Inc. -94EA32 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +4C-8D-53 (hex) HUAWEI TECHNOLOGIES CO.,LTD +4C8D53 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -50-F4-EB (hex) Apple, Inc. -50F4EB (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +DC-2C-6E (hex) Routerboard.com +DC2C6E (base 16) Routerboard.com + Mikrotikls SIA + Riga Riga LV1009 + LV -28-C7-09 (hex) Apple, Inc. -28C709 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +C4-BD-E5 (hex) Intel Corporate +C4BDE5 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -08-2E-36 (hex) Huawei Device Co., Ltd. -082E36 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +34-29-EF (hex) Qingdao Haier Technology Co.,Ltd +3429EF (base 16) Qingdao Haier Technology Co.,Ltd + A01, No.1, Haier Road, Laoshan District, + Qingdao Shan dong 266000 CN -C8-BF-FE (hex) Huawei Device Co., Ltd. -C8BFFE (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +54-1D-61 (hex) YEESTOR Microelectronics Co., Ltd +541D61 (base 16) YEESTOR Microelectronics Co., Ltd + 7th Floor, Block A1, Digital Technology Park, Gaoxin 7th Road South, + Shenzhen 518057 CN -04-9F-81 (hex) NETSCOUT SYSTEMS INC -049F81 (base 16) NETSCOUT SYSTEMS INC - 310 Littleton Road - Westford MA 01886 - US +70-09-71 (hex) Samsung Electronics Co.,Ltd +700971 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR -00-80-8C (hex) NETSCOUT SYSTEMS INC -00808C (base 16) NETSCOUT SYSTEMS INC - 310 Littleton Road - Westford MA 01886 - US +D0-1B-49 (hex) Samsung Electronics Co.,Ltd +D01B49 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -40-24-B2 (hex) Sichuan AI-Link Technology Co., Ltd. -4024B2 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 +FC-5F-49 (hex) Zhejiang Dahua Technology Co., Ltd. +FC5F49 (base 16) Zhejiang Dahua Technology Co., Ltd. + No.1199,Waterfront Road + Hangzhou Zhejiang 310053 CN -64-0D-22 (hex) LG Electronics (Mobile Communications) -640D22 (base 16) LG Electronics (Mobile Communications) - 60-39, Gasan-dong, Geumcheon-gu - Seoul 153-801 - KR - -8C-31-E2 (hex) DAYOUPLUS -8C31E2 (base 16) DAYOUPLUS - 3F 509, Dunchon-daero, Jungwon-gu, Seongnam-si, Gyeonggi-do, Republic of Korea - Seongnam-si Gyeonggi-do 13217 - KR - -10-39-4E (hex) Hisense broadband multimedia technology Co.,Ltd -10394E (base 16) Hisense broadband multimedia technology Co.,Ltd - Song ling Road 399 - Qingdao 266000 - CN - -20-A7-F9 (hex) SHENZHEN OLANBOA TECHNOLOGY CO., LTD -20A7F9 (base 16) SHENZHEN OLANBOA TECHNOLOGY CO., LTD - 4/F, Building B, Block A, Longquan Science Park, Tongfuyu Phase II, Tongsheng Community, Dalang Street, Longhua District, Shenzhen - shenzhen Guangdong 518000 - CN - -F4-C7-AA (hex) Marvell Semiconductors -F4C7AA (base 16) Marvell Semiconductors - 15485 Sand Canyon Ave - IRVINE CA 92618 - US - -70-F0-88 (hex) Nintendo Co.,Ltd -70F088 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - -CC-ED-21 (hex) Nokia Shanghai Bell Co., Ltd. -CCED21 (base 16) Nokia Shanghai Bell Co., Ltd. - No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai - Shanghai 201206 +30-35-C5 (hex) Huawei Device Co., Ltd. +3035C5 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -FC-4D-A6 (hex) HUAWEI TECHNOLOGIES CO.,LTD -FC4DA6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +18-EF-3A (hex) Sichuan AI-Link Technology Co., Ltd. +18EF3A (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 CN -3C-15-12 (hex) Shenzhen Huanhu Technology Co.,Ltd -3C1512 (base 16) Shenzhen Huanhu Technology Co.,Ltd - 7 / F, building C4, Hengfeng Industrial City, 739 Zhoushi Road, Hezhou community, Hangcheng street, Bao'an District, Shenzhen +A0-FF-22 (hex) SHENZHEN APICAL TECHNOLOGY CO., LTD +A0FF22 (base 16) SHENZHEN APICAL TECHNOLOGY CO., LTD + 9/F.B Building,Tsinghua Unis Infoport ,LangShan Road,North District,Hi-tech Industrial Park,Nanshan Shenzhen 518000 CN -18-C2-41 (hex) SonicWall -18C241 (base 16) SonicWall - 1033 McCarthy Blvd - Milpitas CA 95035 - US - -58-45-4C (hex) Ericsson AB -58454C (base 16) Ericsson AB - Torshamnsgatan 36 - Stockholm SE-164 80 - SE - -B4-14-E6 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B414E6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -AC-99-29 (hex) HUAWEI TECHNOLOGIES CO.,LTD -AC9929 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -04-BA-1C (hex) Huawei Device Co., Ltd. -04BA1C (base 16) Huawei Device Co., Ltd. +F8-20-A9 (hex) Huawei Device Co., Ltd. +F820A9 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -7C-3D-2B (hex) Huawei Device Co., Ltd. -7C3D2B (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +64-9E-31 (hex) Beijing Xiaomi Mobile Software Co., Ltd +649E31 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -14-51-7E (hex) New H3C Technologies Co., Ltd -14517E (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +6C-B0-FD (hex) Shenzhen Xinghai Iot Technology Co.,Ltd +6CB0FD (base 16) Shenzhen Xinghai Iot Technology Co.,Ltd + South 8th Road, science and Technology Park, Nanshan District + Shenzhen 518063 CN -08-3A-38 (hex) New H3C Technologies Co., Ltd -083A38 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN +24-CD-8D (hex) Murata Manufacturing Co., Ltd. +24CD8D (base 16) Murata Manufacturing Co., Ltd. + 1-10-1, Higashikotari + Nagaokakyo-shi Kyoto 617-8555 + JP -F8-AB-E5 (hex) shenzhen worldelite electronics co., LTD -F8ABE5 (base 16) shenzhen worldelite electronics co., LTD - Office 5 F, Xiang Yu Industrial Park, Longsheng Road, Longgang Dist - Shenzhen Guangdong 51800 +5C-02-14 (hex) Beijing Xiaomi Mobile Software Co., Ltd +5C0214 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -08-BB-3C (hex) Flextronics Tech.(Ind) Pvt Ltd -08BB3C (base 16) Flextronics Tech.(Ind) Pvt Ltd - 365, Benjamin Road - Sricity Vardahiah Palem(M),Chilamathur Village, Chittoor Distict 517646 - IN +E4-28-05 (hex) Pivotal Optics +E42805 (base 16) Pivotal Optics + 125 Wolf Road Suite 315 + Albany NY 12205 + US -10-D5-61 (hex) Tuya Smart Inc. -10D561 (base 16) Tuya Smart Inc. +38-1F-8D (hex) Tuya Smart Inc. +381F8D (base 16) Tuya Smart Inc. 160 Greentree Drive, Suite 101 Dover DE 19904 US -F0-A3-B2 (hex) Hui Zhou Gaoshengda Technology Co.,LTD -F0A3B2 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD - No.75,Zhongkai High-Tech Development District,Huizhou - Hui Zhou Guangdong 516006 - CN +20-8C-47 (hex) Tenstorrent Inc +208C47 (base 16) Tenstorrent Inc + 150 Ferrand Dr #901 + Toronto ON M3C 3E5 + CA -18-48-59 (hex) Castlenet Technology Inc. -184859 (base 16) Castlenet Technology Inc. - 5th Fl., No.159-1, Sec.3, Beishen Rd., Shenkeng Dist., - New Taipei City 222004 - TW +00-80-E7 (hex) Leonardo UK Ltd +0080E7 (base 16) Leonardo UK Ltd + Christopher Martin Road + Basildon Essex SS14 3EL + GB -2C-FD-B3 (hex) Tonly Technology Co. Ltd -2CFDB3 (base 16) Tonly Technology Co. Ltd - Section 37, Zhongkai Hi-Tech Development Zone - Huizhou Guangdong 516006 +3C-9F-C3 (hex) Beijing Sinead Technology Co., Ltd. +3C9FC3 (base 16) Beijing Sinead Technology Co., Ltd. + Room 504,Block A, New material Building, Yongfeng industrial, Haiding District, Beijing.China + Beijing Beijing 100094 CN -2C-CE-1E (hex) Cloudtronics Pty Ltd -2CCE1E (base 16) Cloudtronics Pty Ltd - Unit 1 6 Powells Road Brookvale - Sydney NSW 2100 - AU +00-B0-EC (hex) EACEM +00B0EC (base 16) EACEM + Avenue Louise 140, Bte 6 + B-1050 BRUSSELS + BE -DC-9A-8E (hex) Nanjing Cocomm electronics co., LTD -DC9A8E (base 16) Nanjing Cocomm electronics co., LTD - Room 201,the Qinheng technology park building - Nanjing Jiangsu 210012 +6C-D3-EE (hex) ZIMI CORPORATION +6CD3EE (base 16) ZIMI CORPORATION + Room A913, 159 Chengjiang Road + Jiangyin City Jiangsu Province 214400 CN -58-AE-F1 (hex) Fiberhome Telecommunication Technologies Co.,LTD -58AEF1 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +04-CD-15 (hex) Silicon Laboratories +04CD15 (base 16) Silicon Laboratories + 400 West Cesar Chavez Street + Austin 78701 + US + +78-5E-A2 (hex) Sunitec Enterprise Co.,Ltd +785EA2 (base 16) Sunitec Enterprise Co.,Ltd + 3F.,No.98-1,Mincyuan Rd.Sindian City + Taipei County 231 231141 CN -E8-6C-C7 (hex) IEEE Registration Authority -E86CC7 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +B0-BE-83 (hex) Apple, Inc. +B0BE83 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -C0-D4-6B (hex) Huawei Device Co., Ltd. -C0D46B (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +10-32-1D (hex) HUAWEI TECHNOLOGIES CO.,LTD +10321D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -9C-95-67 (hex) Huawei Device Co., Ltd. -9C9567 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +E4-93-6A (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +E4936A (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -A4-7B-1A (hex) Huawei Device Co., Ltd. -A47B1A (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +48-77-BD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +4877BD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -D8-59-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD -D85982 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +F8-56-C3 (hex) zte corporation +F856C3 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -48-B2-5D (hex) HUAWEI TECHNOLOGIES CO.,LTD -48B25D (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +38-FD-F8 (hex) Cisco Systems, Inc +38FDF8 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -14-7D-05 (hex) SERCOMM PHILIPPINES INC -147D05 (base 16) SERCOMM PHILIPPINES INC - Lot 1 & 5, Phase 1, Filinvest Technology Park 1, Brgy. Punta, Calamba City - Calamba Lot 1 - PH +DC-F4-CA (hex) Apple, Inc. +DCF4CA (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -78-7A-6F (hex) Juice Technology AG -787A6F (base 16) Juice Technology AG - Gewerbestrasse 7 - Cham Select State CH-6330 - CH +7C-FC-16 (hex) Apple, Inc. +7CFC16 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -64-17-59 (hex) Intellivision Holdings, LLC -641759 (base 16) Intellivision Holdings, LLC - 1844 E Carnegie - Santa Ana CA 92705 +88-B9-45 (hex) Apple, Inc. +88B945 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -50-43-48 (hex) ThingsMatrix Inc. -504348 (base 16) ThingsMatrix Inc. - 9442 North Capital of Texas Hwy Plaza One Suite 500 Austin - Austin TX 78759 +18-E1-DE (hex) Chengdu ChipIntelli Technology Co., Ltd +18E1DE (base 16) Chengdu ChipIntelli Technology Co., Ltd + No. 106, 1st floor, building 6, No. 1480, North Tianfu Avenue, Chengdu high tech Zone + Chengdu Sichuan 610041 + CN + +8C-F6-81 (hex) Silicon Laboratories +8CF681 (base 16) Silicon Laboratories + 400 West Cesar Chavez Street + Austin TX 78701 US -80-45-DD (hex) Intel Corporate -8045DD (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +7C-66-EF (hex) Hon Hai Precision IND.CO.,LTD +7C66EF (base 16) Hon Hai Precision IND.CO.,LTD + No. 66 Chung Shan Road TU-Cheng Industrial district TAIPEI TAIWAN + TAIPEI TAIWAN 33859 + CN -00-2D-B3 (hex) AMPAK Technology,Inc. -002DB3 (base 16) AMPAK Technology,Inc. - 3F, No.15-1 Zhonghua Road, Hsinchu Industrail Park, Hukou, - Hsinchu Hsinchu,Taiwan R.O.C. 30352 - TW +00-A3-88 (hex) SKY UK LIMITED +00A388 (base 16) SKY UK LIMITED + 130 Kings Road + Brentwood Essex 08854 + GB -E4-FD-45 (hex) Intel Corporate -E4FD45 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +7C-C1-77 (hex) INGRAM MICRO SERVICES +7CC177 (base 16) INGRAM MICRO SERVICES + 100 CHEMIN DE BAILLOT + MONTAUBAN 82000 + FR -14-50-51 (hex) SHARP Corporation -145051 (base 16) SHARP Corporation - 1 Takumi-cho, Sakai-ku - Sakai City Osaka 590-8522 - JP +78-76-D9 (hex) EXARA Group +7876D9 (base 16) EXARA Group + Andropova pr. 18 1 + Moscow 115432 + RU -3C-C7-86 (hex) DONGGUAN HUARONG COMMUNICATION TECHNOLOGIES CO.,LTD. -3CC786 (base 16) DONGGUAN HUARONG COMMUNICATION TECHNOLOGIES CO.,LTD. - No.130 Dongxing East Road, Dongkeng Town - DONGGUAN 523450 +94-A4-08 (hex) Shenzhen Trolink Technology CO, LTD +94A408 (base 16) Shenzhen Trolink Technology CO, LTD + 201 B building 4 shijie, Chashu industry 505 block, Baoan airport Sanwei community, Hangcheng street Baoan area. + Shenzhen GuangDong 518000 CN -A4-7E-36 (hex) EM Microelectronic -A47E36 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH - -98-38-7D (hex) ITRONIC TECHNOLOGY CO . , LTD . -98387D (base 16) ITRONIC TECHNOLOGY CO . , LTD . - 2F C Building Fu Xin Lin lndustrial Park Hangcheng - lndustrial Zone Xixiang Street Baoan District Shenzhen 518100 +7C-84-37 (hex) China Post Communications Equipment Co., Ltd. +7C8437 (base 16) China Post Communications Equipment Co., Ltd. + 6 / F, block D, No.156, Beijing International Financial Building, Fuxingmennei street,Xicheng District + Beijing Beijing 100031 CN -AC-D6-18 (hex) OnePlus Technology (Shenzhen) Co., Ltd -ACD618 (base 16) OnePlus Technology (Shenzhen) Co., Ltd - 18C02, 18C03, 18C04 ,18C05,TAIRAN BUILDING, - Shenzhen Guangdong 518000 +B8-4D-43 (hex) HUNAN FN-LINK TECHNOLOGY LIMITED +B84D43 (base 16) HUNAN FN-LINK TECHNOLOGY LIMITED + No.8, Litong Road, Liuyan Economic & Tec + Changsha HUNAN 410329 CN -88-C3-E5 (hex) Betop Techonologies -88C3E5 (base 16) Betop Techonologies - 6F., No. 669, Bannan Road, Zhonghe District - New Taipei City 235 - TW +D0-F8-65 (hex) ITEL MOBILE LIMITED +D0F865 (base 16) ITEL MOBILE LIMITED + RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K + Hong Kong KOWLOON 999077 + HK -E4-28-A4 (hex) Prama India Private Limited -E428A4 (base 16) Prama India Private Limited - Off 103, 765 Fly Edge, TPS III Jn of S V Rd, Nr Kora Kendra - Borivali West, Mumbai Maharashtra 400092 - IN +C8-9F-1A (hex) HUAWEI TECHNOLOGIES CO.,LTD +C89F1A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -94-3A-91 (hex) Amazon Technologies Inc. -943A91 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +D8-68-52 (hex) HUAWEI TECHNOLOGIES CO.,LTD +D86852 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -C4-D0-E3 (hex) Intel Corporate -C4D0E3 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +AC-64-90 (hex) HUAWEI TECHNOLOGIES CO.,LTD +AC6490 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -28-C8-7C (hex) zte corporation -28C87C (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +A8-4A-63 (hex) TPV Display Technology(Xiamen) Co.,Ltd. +A84A63 (base 16) TPV Display Technology(Xiamen) Co.,Ltd. + No.1, Xianghai Road, Xiamen Torch Hi-Tech Industrial Development Zone, China + Xiamen Fujian 361101 CN -30-7C-4A (hex) Huawei Device Co., Ltd. -307C4A (base 16) Huawei Device Co., Ltd. +60-8F-A4 (hex) Nokia Solutions and Networks GmbH & Co. KG +608FA4 (base 16) Nokia Solutions and Networks GmbH & Co. KG + Werinherstrasse 91 + München Bavaria D-81541 + DE + +24-3F-AA (hex) Huawei Device Co., Ltd. +243FAA (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -C8-D8-84 (hex) Universal Electronics, Inc. -C8D884 (base 16) Universal Electronics, Inc. - 201 E. Sandpointe Ave - Santa Ana CA 92707 - US - -B4-A2-5C (hex) Cambium Networks Limited -B4A25C (base 16) Cambium Networks Limited - Unit B2, Linhay Business Park, - Ashburton Devon TQ13 7UP - GB - -2C-71-FF (hex) Amazon Technologies Inc. -2C71FF (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +D8-67-D3 (hex) Huawei Device Co., Ltd. +D867D3 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -DC-00-77 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -DC0077 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 +48-47-4B (hex) Huawei Device Co., Ltd. +48474B (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -48-78-5E (hex) Amazon Technologies Inc. -48785E (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +80-6F-1C (hex) Huawei Device Co., Ltd. +806F1C (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -20-C7-4F (hex) SensorPush -20C74F (base 16) SensorPush - PO Box 211 - Garrison NY 10524 - US +E0-A2-58 (hex) Wanbang Digital Energy Co.,Ltd +E0A258 (base 16) Wanbang Digital Energy Co.,Ltd + NO.39 Longhui Road,Wujin District + Changzhou 213100 + CN -24-E8-53 (hex) LG Innotek -24E853 (base 16) LG Innotek +4C-BC-E9 (hex) LG Innotek +4CBCE9 (base 16) LG Innotek 26, Hanamsandan 5beon-ro Gwangju Gwangsan-gu 506-731 KR -48-29-52 (hex) Sagemcom Broadband SAS -482952 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -60-6C-63 (hex) Hitron Technologies. Inc -606C63 (base 16) Hitron Technologies. Inc - No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C - Hsin-chu Taiwan 300 - TW - -00-F3-61 (hex) Amazon Technologies Inc. -00F361 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +38-5C-76 (hex) PLANTRONICS, INC. +385C76 (base 16) PLANTRONICS, INC. + 345 ENCINAL STREET + SANTA CRUZ CA 95060 US -00-40-DD (hex) HONG TECHNOLOGIES -0040DD (base 16) HONG TECHNOLOGIES - 532 WEDDELL DRIVE - SUNNYVALE CA 94089 - US +9C-50-D1 (hex) Murata Manufacturing Co., Ltd. +9C50D1 (base 16) Murata Manufacturing Co., Ltd. + 1-10-1, Higashikotari + Nagaokakyo-shi Kyoto 617-8555 + JP -40-8C-1F (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -408C1F (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +14-00-20 (hex) LongSung Technology (Shanghai) Co.,Ltd. +140020 (base 16) LongSung Technology (Shanghai) Co.,Ltd. + Room 606, Block B, Bldg. 1, No. 3000 Longdong Avenue., Zhangjiang Hi-Tech Park, Pudong District + ShangHai 201203 CN -04-F0-3E (hex) Huawei Device Co., Ltd. -04F03E (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +30-B9-30 (hex) zte corporation +30B930 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -E8-D7-65 (hex) HUAWEI TECHNOLOGIES CO.,LTD -E8D765 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +94-98-69 (hex) zte corporation +949869 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -50-2D-FB (hex) IGShare Co., Ltd. -502DFB (base 16) IGShare Co., Ltd. - 410-ho, 28, Digital-ro 30-gil - Guro-gu, SEOUL 08389 - KR - -40-EE-15 (hex) Zioncom Electronics (Shenzhen) Ltd. -40EE15 (base 16) Zioncom Electronics (Shenzhen) Ltd. - A1&A2 Building,Lantian Technology Park, Xinyu Road, Xingqiao Henggang Block, Shajing Street, Baoan District - Shenzhen Guangdong 518000 +50-3D-EB (hex) Zhejiang Tmall Technology Co., Ltd. +503DEB (base 16) Zhejiang Tmall Technology Co., Ltd. + Ali Center,No.3331 Keyuan South RD (Shenzhen bay), Nanshan District, Shenzhen Guangdong province + Shenzhen GuangDong 518000 CN -6C-0D-E1 (hex) Dongguan Cannice Precision Manufacturing Co., Ltd. -6C0DE1 (base 16) Dongguan Cannice Precision Manufacturing Co., Ltd. - Dongguan Cannice Precision Manufacturing Co., Ltd. - Dongguan Guangdong 523170 +F8-7F-A5 (hex) GREATEK +F87FA5 (base 16) GREATEK + ESTRADA MUNICIPAL PEDRO R SILVA + EXTREMA MG 37640000 + BR + +74-5C-FA (hex) Shenzhen Shunrui Gaojie Technology Co., Ltd. +745CFA (base 16) Shenzhen Shunrui Gaojie Technology Co., Ltd. + 502,Building A,No.18,Gongye 2nd Road,Dakan Industrial Zone,Dakan community,Xili street,Nanshan District + Shenzhen Guangdong 518055 CN -6C-03-09 (hex) Cisco Systems, Inc -6C0309 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +5C-DF-B8 (hex) Shenzhen Unionmemory Information System Limited +5CDFB8 (base 16) Shenzhen Unionmemory Information System Limited + Factory Flat D24/F-02, Dong Jiao Tou, Houhai Road, Shekou, Nan Shan District + Shenzhen Guangdong 518067 + CN -BC-D2-95 (hex) Cisco Systems, Inc -BCD295 (base 16) Cisco Systems, Inc +2C-1A-05 (hex) Cisco Systems, Inc +2C1A05 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -80-C5-01 (hex) OctoGate IT Security Systems GmbH -80C501 (base 16) OctoGate IT Security Systems GmbH - Friedrich List Strasse 42 - Paderborn NRW 33100 - DE - -14-D1-9E (hex) Apple, Inc. -14D19E (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -40-C7-11 (hex) Apple, Inc. -40C711 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +B4-0E-CF (hex) Bouffalo Lab (Nanjing) Co., Ltd. +B40ECF (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 + CN -5C-70-17 (hex) Apple, Inc. -5C7017 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +F8-38-69 (hex) LG Electronics +F83869 (base 16) LG Electronics + Science Park W5, 10, Magokjungang 10-ro, Gangseo-gu + Seoul 07796 + KR -8C-EC-7B (hex) Apple, Inc. -8CEC7B (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +2C-05-47 (hex) Shenzhen Phaten Tech. LTD +2C0547 (base 16) Shenzhen Phaten Tech. LTD + C-6 ideamonto industril 7002 Songbai Road Guangming District Shenzhen City Guangdong, China + Shenzhen 518108 + CN -C4-30-CA (hex) SD Biosensor -C430CA (base 16) SD Biosensor - C-4th & 5th Floor, Digital Empire Building, 980-3 - Suwon-si Kyonggi-do ASI|KR|KS002|SUWON - KR +E0-C5-8F (hex) China Mobile IOT Company Limited +E0C58F (base 16) China Mobile IOT Company Limited + NO.8 Yu Ma Road, NanAn Area + Chongqing Chongqing 401336 + CN -28-05-2E (hex) Dematic Corp -28052E (base 16) Dematic Corp - 507 Plymouth Ave NE - Grand Rapids MI 49505 - US +E0-0A-F6 (hex) Liteon Technology Corporation +E00AF6 (base 16) Liteon Technology Corporation + 4F, 90, Chien 1 Road + New Taipei City Taiwan 23585 + TW -E4-5E-1B (hex) Google, Inc. -E45E1B (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 +F0-5E-CD (hex) Texas Instruments +F05ECD (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -78-E2-2C (hex) Huawei Device Co., Ltd. -78E22C (base 16) Huawei Device Co., Ltd. +2C-3A-91 (hex) Huawei Device Co., Ltd. +2C3A91 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -C0-D0-26 (hex) Huawei Device Co., Ltd. -C0D026 (base 16) Huawei Device Co., Ltd. +64-F7-05 (hex) Huawei Device Co., Ltd. +64F705 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -A4-42-3B (hex) Intel Corporate -A4423B (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -EC-BE-5F (hex) Vestel Elektronik San ve Tic. A.S. -ECBE5F (base 16) Vestel Elektronik San ve Tic. A.S. - Organize san - Manisa Turket 45030 - TR - -70-CF-49 (hex) Intel Corporate -70CF49 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +78-50-05 (hex) MOKO TECHNOLOGY Ltd +785005 (base 16) MOKO TECHNOLOGY Ltd + 2F, Building1,No.37 Xiaxintang Xintang village,Fucheng Street,Longhua Distric + Shenzhen Guangdong 518110 + CN -48-51-C5 (hex) Intel Corporate -4851C5 (base 16) Intel Corporate +F0-B6-1E (hex) Intel Corporate +F0B61E (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -F4-2A-7D (hex) TP-LINK TECHNOLOGIES CO.,LTD. -F42A7D (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 +C8-4D-44 (hex) Shenzhen Jiapeng Huaxiang Technology Co.,Ltd +C84D44 (base 16) Shenzhen Jiapeng Huaxiang Technology Co.,Ltd + 2nd floor, building 5, taihemei Industrial Zone, 128 Chunfeng Road, longbeiling community, Tangxia Town + Shenzhen Guangdong 518109 CN -04-F9-93 (hex) Infinix mobility limited -04F993 (base 16) Infinix mobility limited - RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG - HongKong HongKong 999077 - HK +98-B1-77 (hex) LANDIS + GYR +98B177 (base 16) LANDIS + GYR + 78th km Old National Road Athens-Corinth + Corinth 20100 + GR -BC-BD-9E (hex) ITEL MOBILE LIMITED -BCBD9E (base 16) ITEL MOBILE LIMITED - RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K - Hong Kong KOWLOON 999077 - HK +A0-29-BD (hex) Team Group Inc +A029BD (base 16) Team Group Inc + 3F., No. 166 Jian 1st Rd., + Zhonghe Dist. New Taipei City, 235 + TW -BC-6D-05 (hex) Dusun Electron Co.,Ltd. -BC6D05 (base 16) Dusun Electron Co.,Ltd. - NO.640 FengQing str.,DeQing, ZheJiang, China - huzhou zhejiang 313200 +64-DC-DE (hex) ZheJiang FuChunJiang Information Technology Co.,Ltd +64DCDE (base 16) ZheJiang FuChunJiang Information Technology Co.,Ltd + 608 Golf Road, Dongzhou Street, Fuyang District, Hangzhou City, Zhejiang Province, China + Hangzhou City Zhejiang Province 311400 CN -C0-E0-18 (hex) HUAWEI TECHNOLOGIES CO.,LTD -C0E018 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +00-91-9E (hex) Intel Corporate +00919E (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +60-CE-41 (hex) HUAWEI TECHNOLOGIES CO.,LTD +60CE41 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -5C-E7-47 (hex) HUAWEI TECHNOLOGIES CO.,LTD -5CE747 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +28-17-09 (hex) HUAWEI TECHNOLOGIES CO.,LTD +281709 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -A8-FF-BA (hex) HUAWEI TECHNOLOGIES CO.,LTD -A8FFBA (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +60-6E-E8 (hex) Xiaomi Communications Co Ltd +606EE8 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -F0-39-65 (hex) Samsung Electronics Co.,Ltd -F03965 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +D0-B6-6F (hex) SERNET (SUZHOU) TECHNOLOGIES CORPORATION +D0B66F (base 16) SERNET (SUZHOU) TECHNOLOGIES CORPORATION + NO.8 Tangzhuang Road,Suzhou Industrial Park,Su ZhouCity,JiangSu Province,China + Suzhou 215021 + CN -EC-7C-B6 (hex) Samsung Electronics Co.,Ltd -EC7CB6 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +30-09-C0 (hex) Motorola Mobility LLC, a Lenovo Company +3009C0 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US -58-A6-39 (hex) Samsung Electronics Co.,Ltd -58A639 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 +50-F9-08 (hex) Wizardlab Co., Ltd. +50F908 (base 16) Wizardlab Co., Ltd. + #1603, 5, Gasan digital 1-ro, Geumcheon-gu, Seoul + Seoul 08594 KR -A4-E5-7C (hex) Espressif Inc. -A4E57C (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +50-C2-E8 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +50C2E8 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 CN -8C-0F-C9 (hex) Huawei Device Co., Ltd. -8C0FC9 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +B0-46-92 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +B04692 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -30-4E-1B (hex) Huawei Device Co., Ltd. -304E1B (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +AC-76-4C (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +AC764C (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -74-50-4E (hex) New H3C Technologies Co., Ltd -74504E (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN +5C-58-E6 (hex) Palo Alto Networks +5C58E6 (base 16) Palo Alto Networks + 3000 Tannery Way + Santa Clara CA 95054 + US -08-9A-C7 (hex) zte corporation -089AC7 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +0C-E1-59 (hex) Shenzhen iStartek Technology Co., Ltd. +0CE159 (base 16) Shenzhen iStartek Technology Co., Ltd. + Zone B, 4/F, Building A6, Qinghu Dongli Industrial Park, No. 416 Xuegang North Road, Longhua District, + Shenzhen Guangdong 518109 CN -74-4C-A1 (hex) Liteon Technology Corporation -744CA1 (base 16) Liteon Technology Corporation - 4F, 90, Chien 1 Road - New Taipei City Taiwan 23585 - TW +00-14-13 (hex) Trebing & Himstedt Prozeßautomation GmbH & Co. KG +001413 (base 16) Trebing & Himstedt Prozeßautomation GmbH & Co. KG + Wilhelm Hennemann Straße 13 + Schwerin Mecklenburg-Vorpommern 19061 + DE -B4-B2-91 (hex) LG Electronics -B4B291 (base 16) LG Electronics - 222 LG-ro, JINWI-MYEON - Pyeongtaek-si Gyeonggi-do 451-713 - KR +00-07-99 (hex) Tipping Point Technologies, Inc. +000799 (base 16) Tipping Point Technologies, Inc. + 7501 B N. Capital of TX Hwy. + Austin TX 78731 + US -D8-00-93 (hex) Aurender Inc. -D80093 (base 16) Aurender Inc. - #1612, OBIZTOWER, 126, Beolmal-ro, Dongan-gu - Anyang-si Gyeonggi-do 14057 - KR +DC-8D-8A (hex) Nokia Solutions and Networks GmbH & Co. KG +DC8D8A (base 16) Nokia Solutions and Networks GmbH & Co. KG + Werinherstrasse 91 + München Bavaria D-81541 + DE -38-90-52 (hex) HUAWEI TECHNOLOGIES CO.,LTD -389052 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +70-86-CE (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +7086CE (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN -C0-F6-EC (hex) HUAWEI TECHNOLOGIES CO.,LTD -C0F6EC (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +50-0F-59 (hex) STMicrolectronics International NV +500F59 (base 16) STMicrolectronics International NV + 39, Chemin du Champ-des-Filles + Geneva, Plan-les-Quates 1228 + CH -CC-20-8C (hex) HUAWEI TECHNOLOGIES CO.,LTD -CC208C (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +E4-2A-AC (hex) Microsoft Corporation +E42AAC (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US -94-8E-D3 (hex) Arista Networks -948ED3 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 +20-E7-B6 (hex) Universal Electronics, Inc. +20E7B6 (base 16) Universal Electronics, Inc. + 201 E. Sandpointe Ave + Santa Ana CA 92707 US -24-E4-C8 (hex) Fiberhome Telecommunication Technologies Co.,LTD -24E4C8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +D4-3D-F3 (hex) Zyxel Communications Corporation +D43DF3 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + +20-57-9E (hex) HUNAN FN-LINK TECHNOLOGY LIMITED +20579E (base 16) HUNAN FN-LINK TECHNOLOGY LIMITED + No.8, Litong Road, Liuyan Economic & Tec + Changsha HUNAN 410329 CN -68-76-27 (hex) Zhuhai Dingzhi Electronic Technology Co., Ltd -687627 (base 16) Zhuhai Dingzhi Electronic Technology Co., Ltd - 6th floor, No.2 Jinliang Road, Hongqi Town, Jinwan District - Zhuhai GuangDong 519000 +C8-84-CF (hex) HUAWEI TECHNOLOGIES CO.,LTD +C884CF (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -C8-6C-3D (hex) Amazon Technologies Inc. -C86C3D (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +E0-23-D7 (hex) Sleep Number +E023D7 (base 16) Sleep Number + 1001 Third Avenue South + Minneapolis MN 55404 US -70-61-7B (hex) Cisco Systems, Inc -70617B (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +24-1D-48 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +241D48 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 + CN -A0-02-4A (hex) IEEE Registration Authority -A0024A (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +28-77-77 (hex) zte corporation +287777 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -04-E7-7E (hex) We Corporation Inc. -04E77E (base 16) We Corporation Inc. - 201, 33, Deokcheon-ro, Manan-gu - Anyang-si Gyeonggi-do 14088 - KR +60-E3-2B (hex) Intel Corporate +60E32B (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -74-EC-B2 (hex) Amazon Technologies Inc. -74ECB2 (base 16) Amazon Technologies Inc. +D8-BE-65 (hex) Amazon Technologies Inc. +D8BE65 (base 16) Amazon Technologies Inc. P.O Box 8102 Reno NV 89507 US -4C-52-EC (hex) SOLARWATT GmbH -4C52EC (base 16) SOLARWATT GmbH - Maria-Reiche-Str. 2a - Dresden 01109 - DE - -E0-1F-ED (hex) Nokia Shanghai Bell Co., Ltd. -E01FED (base 16) Nokia Shanghai Bell Co., Ltd. - No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai - Shanghai 201206 +28-DE-A8 (hex) zte corporation +28DEA8 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -30-D9-41 (hex) Raydium Semiconductor Corp. -30D941 (base 16) Raydium Semiconductor Corp. - 2F, No. 23, LiHsin Rd., Hsinchu Science Park - Hsinchu, Taiwan, R.O.C. TW 300 - TW +80-8A-BD (hex) Samsung Electronics Co.,Ltd +808ABD (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR -14-6B-9A (hex) zte corporation -146B9A (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +3C-CD-57 (hex) Beijing Xiaomi Mobile Software Co., Ltd +3CCD57 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -9C-28-B3 (hex) Apple, Inc. -9C28B3 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +80-6A-10 (hex) Whisker Labs - Ting +806A10 (base 16) Whisker Labs - Ting + 12410 Milestone Center Dr, Suite 325 + Germantown MD 20876 US -A0-78-17 (hex) Apple, Inc. -A07817 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +C0-F8-27 (hex) Rapidmax Technology Corporation +C0F827 (base 16) Rapidmax Technology Corporation + 3F., No.531, Zhongzheng Rd. Xindian Dist. + New Taipei City 23148 + TW -5C-87-30 (hex) Apple, Inc. -5C8730 (base 16) Apple, Inc. +24-5E-48 (hex) Apple, Inc. +245E48 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -A4-AA-FE (hex) Huawei Device Co., Ltd. -A4AAFE (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -F8-3B-7E (hex) Huawei Device Co., Ltd. -F83B7E (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -88-15-C5 (hex) Huawei Device Co., Ltd. -8815C5 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -B4-1B-B0 (hex) Apple, Inc. -B41BB0 (base 16) Apple, Inc. +08-C7-29 (hex) Apple, Inc. +08C729 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -58-D3-49 (hex) Apple, Inc. -58D349 (base 16) Apple, Inc. +C4-C3-6B (hex) Apple, Inc. +C4C36B (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -F4-34-F0 (hex) Apple, Inc. -F434F0 (base 16) Apple, Inc. +E8-A7-30 (hex) Apple, Inc. +E8A730 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -B0-8C-75 (hex) Apple, Inc. -B08C75 (base 16) Apple, Inc. +60-06-E3 (hex) Apple, Inc. +6006E3 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -70-40-FF (hex) Huawei Device Co., Ltd. -7040FF (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +10-6F-D9 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +106FD9 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 CN -34-D6-93 (hex) Huawei Device Co., Ltd. -34D693 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +58-4D-42 (hex) Dragos, Inc. +584D42 (base 16) Dragos, Inc. + 1745 Dorsey Rd, Suite R + Hanover MD 21076 + US -9C-9A-C0 (hex) LEGO System A/S -9C9AC0 (base 16) LEGO System A/S - Aastvej 1 - Billund DK-7190 - DK +60-8A-10 (hex) Microchip Technology Inc. +608A10 (base 16) Microchip Technology Inc. + 2355 W. Chandler Blvd. + Chandler AZ 85224 + US -A0-9F-10 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD -A09F10 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD - NO.268? Fuqian Rd, Jutang community, Guanlan Town, Longhua New district - shenzhen guangdong 518000 - CN +A8-64-F1 (hex) Intel Corporate +A864F1 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -20-1B-88 (hex) Dongguan Liesheng Electronic Co., Ltd. -201B88 (base 16) Dongguan Liesheng Electronic Co., Ltd. +D0-1E-1D (hex) SaiNXT Technologies LLP +D01E1D (base 16) SaiNXT Technologies LLP + Shop No. 7, Sonawala Building, 1st Floor, Proctor Road, Grant Road (E) + Mumbai Maharashtra 400007 + IN + +E0-67-81 (hex) Dongguan Liesheng Electronic Co., Ltd. +E06781 (base 16) Dongguan Liesheng Electronic Co., Ltd. F5, Building B, North Block, Gaosheng Tech Park, No. 84 Zhongli Road, Nancheng District, Dongguan Ci dongguan guangdong 523000 CN -E8-EB-34 (hex) Cisco Systems, Inc -E8EB34 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +B8-DA-E8 (hex) Huawei Device Co., Ltd. +B8DAE8 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -3C-BD-C5 (hex) Arcadyan Corporation -3CBDC5 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +B8-AE-1D (hex) Guangzhou Xingyi Electronic Technology Co.,Ltd +B8AE1D (base 16) Guangzhou Xingyi Electronic Technology Co.,Ltd + Room 805-808, Room 801, Self-made Building 4, No. 1, 3 and 5, Kesheng Road, Guangzhou Private Science Park, No. 1633, Beitai Road, Baiyun District, Guangzhou + Guangzhou 51000 + CN -A8-DA-0C (hex) SERVERCOM (INDIA) PRIVATE LIMITED -A8DA0C (base 16) SERVERCOM (INDIA) PRIVATE LIMITED - E-43/1 OKHLA INDUSTRIAL AREA PHASE-II NEW DELHI SOUTH DELHI - NEW DELHI 110001 - IN +D4-54-8B (hex) Intel Corporate +D4548B (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -24-94-93 (hex) FibRSol Global Network Limited -249493 (base 16) FibRSol Global Network Limited - 17, Deep vihar, Vikas Nagar,, Near Mayank hospital,, Uttam Nagar, - New Delhi Delhi 110059 - IN +10-09-F9 (hex) Amazon Technologies Inc. +1009F9 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -28-D0-44 (hex) Shenzhen Xinyin technology company -28D044 (base 16) Shenzhen Xinyin technology company - 2/F, Building C, Jianxing Technology Building, Shahe West Road, Xili Street, Nanshan District - Shenzhen Guangdong 518055 +D0-3E-7D (hex) CHIPSEA TECHNOLOGIES (SHENZHEN) CORP. +D03E7D (base 16) CHIPSEA TECHNOLOGIES (SHENZHEN) CORP. + 9F,BLOCK A,GARDEN CITY DIGITAL BUILDING,NO.1079 NANHAI ROAD,NANSHAN DISTRICT + SHEN ZHEN GUANG DONG 518000 CN -5C-10-C5 (hex) Samsung Electronics Co.,Ltd -5C10C5 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 +90-69-76 (hex) Withrobot Inc. +906976 (base 16) Withrobot Inc. + #1001, Seoul Forest M-tower, 31, Ttukseom-ro 1-gil, Seongdong-gu + Seoul Seoul 04778 KR -A8-40-7D (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -A8407D (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN - -FC-4B-57 (hex) Peerless Instrument Division of Curtiss-Wright -FC4B57 (base 16) Peerless Instrument Division of Curtiss-Wright - 1966D Broadhollow Road - East Farmingdale NY 11735 - US - -74-B7-B3 (hex) Shenzhen YOUHUA Technology Co., Ltd -74B7B3 (base 16) Shenzhen YOUHUA Technology Co., Ltd - Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District - Shenzhen Guangdong 518055 - CN +FC-92-57 (hex) Renesas Electronics (Penang) Sdn. Bhd. +FC9257 (base 16) Renesas Electronics (Penang) Sdn. Bhd. + Phase 3, Bayan Lepas FIZ + Bayan Lepas Penang 11900 + MY -DC-9B-D6 (hex) TCT mobile ltd -DC9BD6 (base 16) TCT mobile ltd - No.86 hechang 7th road, zhongkai, Hi-Tech District - Hui Zhou Guang Dong 516006 +60-DB-15 (hex) New H3C Technologies Co., Ltd +60DB15 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -DC-8C-1B (hex) vivo Mobile Communication Co., Ltd. -DC8C1B (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +5C-A7-21 (hex) New H3C Technologies Co., Ltd +5CA721 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -F8-53-29 (hex) HUAWEI TECHNOLOGIES CO.,LTD -F85329 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +A4-2A-71 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +A42A71 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 CN -E8-0A-EC (hex) Jiangsu Hengtong Optic-Electric Co., LTD -E80AEC (base 16) Jiangsu Hengtong Optic-Electric Co., LTD - 88 Hengtong Dadao, Qidu Town, Wujiang District - Suzhou Jiangsu Province 215200 - CN +60-A5-E2 (hex) Intel Corporate +60A5E2 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -C4-DE-7B (hex) Huawei Device Co., Ltd. -C4DE7B (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +48-F8-FF (hex) CHENGDU KT ELECTRONIC HI-TECH CO.,LTD +48F8FF (base 16) CHENGDU KT ELECTRONIC HI-TECH CO.,LTD + No.9, 3rd Wuke Road, Wuhou District + Chengdu Sichuan Province 610045 CN -6C-1A-75 (hex) Huawei Device Co., Ltd. -6C1A75 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +E8-C1-E8 (hex) Shenzhen Xiao Bi En Culture Education Technology Co.,Ltd. +E8C1E8 (base 16) Shenzhen Xiao Bi En Culture Education Technology Co.,Ltd. + 4GH Unit,Block D,Central Avenue,Intersection of Xixiang Avenue and Baoyuan Road,Labor Community,Xixiang Street,Baoan District + Shenzhen China 518102 CN -40-D4-BD (hex) SK Networks Service CO., LTD. -40D4BD (base 16) SK Networks Service CO., LTD. - 120, Jangan-ro, Jangan-gu - Suwon-si Gyeonggi-do 16312 - KR - -84-1B-77 (hex) Intel Corporate -841B77 (base 16) Intel Corporate +7C-70-DB (hex) Intel Corporate +7C70DB (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -7C-C2-94 (hex) Beijing Xiaomi Mobile Software Co., Ltd -7CC294 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN +8C-94-CC (hex) SFR +8C94CC (base 16) SFR + 12 rue jean-philippe Rameau CS 80001 + La plaine saint denis FRANCE 93634 + FR -E0-E2-E6 (hex) Espressif Inc. -E0E2E6 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +98-F2-17 (hex) Castlenet Technology Inc. +98F217 (base 16) Castlenet Technology Inc. + 5F., No. 10, Daye Rd., Beitou Dist. + Taipei City 112030 + TW -6C-76-37 (hex) Huawei Device Co., Ltd. -6C7637 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +84-EB-EF (hex) Cisco Systems, Inc +84EBEF (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -68-D4-8B (hex) Hailo Technologies Ltd. -68D48B (base 16) Hailo Technologies Ltd. - 94 Yigal Alon - Tel Aviv 6789139 - IL +A8-23-16 (hex) Nokia +A82316 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -C4-37-72 (hex) Virtuozzo International GmbH -C43772 (base 16) Virtuozzo International GmbH - Vordergasse 59 - Schaffhausen 8200 - CH +38-E3-9F (hex) Motorola Mobility LLC, a Lenovo Company +38E39F (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US -CC-3B-27 (hex) TECNO MOBILE LIMITED -CC3B27 (base 16) TECNO MOBILE LIMITED +90-80-60 (hex) Nilfisk A/S +908060 (base 16) Nilfisk A/S + Kornmarksvej 1 + Broendby 2605 + DK + +70-A6-CC (hex) Intel Corporate +70A6CC (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +6C-43-3C (hex) TECNO MOBILE LIMITED +6C433C (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 Hong Kong Hong Kong 999077 HK -3C-D2-E5 (hex) New H3C Technologies Co., Ltd -3CD2E5 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +78-BB-88 (hex) Maxio Technology (Hangzhou) Ltd. +78BB88 (base 16) Maxio Technology (Hangzhou) Ltd. + 6F, Building C, No.459 Qianmo Road, Juguang Center + Hangzhou Zhejiang 310051 CN -9C-73-70 (hex) HUAWEI TECHNOLOGIES CO.,LTD -9C7370 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +44-67-52 (hex) Wistron INFOCOMM (Zhongshan) CORPORATION +446752 (base 16) Wistron INFOCOMM (Zhongshan) CORPORATION + 15 Cuiwei Road, Cuiheng New District + zhongshan Guangdong 528400 CN -98-3F-60 (hex) HUAWEI TECHNOLOGIES CO.,LTD -983F60 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +60-B6-E1 (hex) Texas Instruments +60B6E1 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -C0-3F-DD (hex) HUAWEI TECHNOLOGIES CO.,LTD -C03FDD (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +6C-79-B8 (hex) Texas Instruments +6C79B8 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -30-32-35 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. -303235 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. - No.218 Qianwangang Road - Qingdao Shangdong 266510 - CN +8C-A3-99 (hex) SERVERCOM (INDIA) PRIVATE LIMITED +8CA399 (base 16) SERVERCOM (INDIA) PRIVATE LIMITED + E-43/1 OKHLA INDUSTRIAL AREA PHASE-II NEW DELHI SOUTH DELHI + NEW DELHI NA + IN -00-30-1F (hex) OPTICAL NETWORKS, INC. -00301F (base 16) OPTICAL NETWORKS, INC. - 166 BAYPOINTE PARKWAY - SAN JOSE CA 95134 +D4-8D-D9 (hex) Meld Technology, Inc +D48DD9 (base 16) Meld Technology, Inc + 725 San Aleso Ave + Sunnyvale CA 94085 US -08-58-A5 (hex) Beijing Vrv Software Corpoaration Limited. -0858A5 (base 16) Beijing Vrv Software Corpoaration Limited. - Room 1602, block C, Zhongguancun Science and technology development building, 34 Zhongguancun South Street - Beijing Beijing 100000 +AC-E7-7B (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +ACE77B (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + FL12,TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, + Chengdu Sichuan 610000 CN -00-13-91 (hex) OUEN CO.,LTD. -001391 (base 16) OUEN CO.,LTD. - Gotanda NT Bldg.7F, - Shinagawa-ku Tokyo 141-0022 - JP +40-F4-20 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +40F420 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + FL12,TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, + Chengdu Sichuan 610000 + CN -58-24-29 (hex) Google, Inc. -582429 (base 16) Google, Inc. +64-5D-92 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +645D92 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + FL12,TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, Chengdu, Sichuan + Chengdu Sichuan 610000 + CN + +8C-FD-15 (hex) Imagine Marketing Private Limited +8CFD15 (base 16) Imagine Marketing Private Limited + 501B, Shri Guru Har Krishan Bhavan Charat Singh Colony Road, Chakala, Andheri East, + Mumbai Maharashtra 400093 + IN + +14-22-3B (hex) Google, Inc. +14223B (base 16) Google, Inc. 1600 Amphitheatre Parkway Mountain View CA 94043 US -7C-73-EB (hex) Huawei Device Co., Ltd. -7C73EB (base 16) Huawei Device Co., Ltd. +C4-FF-22 (hex) Huawei Device Co., Ltd. +C4FF22 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -E4-8F-1D (hex) Huawei Device Co., Ltd. -E48F1D (base 16) Huawei Device Co., Ltd. +A0-A0-DC (hex) Huawei Device Co., Ltd. +A0A0DC (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -CC-F5-5F (hex) E FOCUS INSTRUMENTS INDIA PRIVATE LIMITED -CCF55F (base 16) E FOCUS INSTRUMENTS INDIA PRIVATE LIMITED - PLOT NO 21, 1ST FLOOR, NO 22, SAMAYAPURAM MAIN ROAD PORUR - CHENNAI TAMIL NADU 600116 - IN +C4-80-8A (hex) Cloud Diagnostics Canada ULC +C4808A (base 16) Cloud Diagnostics Canada ULC + 72 Victoria St. S., Unit 100 + Kitchener Ontario N2G 4Y9 + CA -08-7C-39 (hex) Amazon Technologies Inc. -087C39 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +2C-DD-E9 (hex) Arista Networks +2CDDE9 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 US -14-13-33 (hex) AzureWave Technology Inc. -141333 (base 16) AzureWave Technology Inc. - 8F., No. 94, Baozhong Rd. - New Taipei City Taiwan 231 +70-97-41 (hex) Arcadyan Corporation +709741 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 TW -A4-17-8B (hex) HUAWEI TECHNOLOGIES CO.,LTD -A4178B (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +E4-08-E7 (hex) Quectel Wireless Solutions Co.,Ltd. +E408E7 (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN -94-B2-71 (hex) HUAWEI TECHNOLOGIES CO.,LTD -94B271 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +0C-8D-CA (hex) Samsung Electronics Co.,Ltd +0C8DCA (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +CC-0D-E7 (hex) B METERS S.R.L. +CC0DE7 (base 16) B METERS S.R.L. + VIA FRIULI 3 + GONARS UDINE 33050 + IT + +44-56-E2 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +4456E2 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 CN -80-8F-E8 (hex) Intelbras -808FE8 (base 16) Intelbras - BR 101, km 210, S/N° - São José Santa Catarina 88104800 - BR +68-3A-48 (hex) SAMJIN Co., Ltd. +683A48 (base 16) SAMJIN Co., Ltd. + 199-6, Anyang 7-dong, Manan-gu + Anyang-si Gyeonggi-do 430-817 + KR -18-CC-18 (hex) Intel Corporate -18CC18 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +CC-A2-60 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +CCA260 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + FL12,TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, + Chengdu Sichuan 610000 + CN -C0-C9-E3 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -C0C9E3 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 +9C-61-21 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +9C6121 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + FL12,TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, + Chengdu Sichuan 610000 CN -F8-8C-21 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -F88C21 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 +E0-4F-BD (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +E04FBD (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + FL12,TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, + Chengdu Sichuan 610000 CN -C4-27-8C (hex) Huawei Device Co., Ltd. -C4278C (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +5C-A1-76 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +5CA176 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + FL12, TowerB,Tianyi international Hotel,No.2 West Section One, Second Ring Road, + Chengdu Sichuan 610000 CN -78-05-8C (hex) mMax Communications, Inc. -78058C (base 16) mMax Communications, Inc. - 5151 California Ave., Suite 100 - Irvine CA 92617 - US +4C-D3-AF (hex) HMD Global Oy +4CD3AF (base 16) HMD Global Oy + Bertel Jungin aukio 9 + Espoo 02600 + FI -C4-A7-2B (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD -C4A72B (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD - Unit East Block22-24/F,Skyworth semiconductor design Bldg., Gaoxin Ave.4.S.,Nanshan District,Shenzhen,China - SHENZHEN GUANGDONG 518057 - CN +C8-51-42 (hex) Samsung Electronics Co.,Ltd +C85142 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -D0-C3-1E (hex) JUNGJIN Electronics Co.,Ltd -D0C31E (base 16) JUNGJIN Electronics Co.,Ltd - 41-11, Yangjipyeon-ro - Uiwang-si Gyeonggi-do 16007 +10-E4-C2 (hex) Samsung Electronics Co.,Ltd +10E4C2 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 KR -5C-34-00 (hex) Hisense Electric Co.,Ltd -5C3400 (base 16) Hisense Electric Co.,Ltd - Qianwangang Road 218 - Qingdao Shandong 266510 +AC-D8-29 (hex) Bouffalo Lab (Nanjing) Co., Ltd. +ACD829 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 CN -D8-10-CB (hex) Andrea Informatique -D810CB (base 16) Andrea Informatique - 30 Rue Jules Guesde - Paris 75014 - FR +5C-62-5A (hex) CANON INC. +5C625A (base 16) CANON INC. + 30-2 Shimomaruko 3-chome, + Ohta-ku Tokyo 146-8501 + JP -E0-91-3C (hex) Kyeungin CNS Co., Ltd. -E0913C (base 16) Kyeungin CNS Co., Ltd. - 13, Gyeongin-ro, Sosa-gu - Bucheon-si Gyeonggi-do 14730 +7C-0A-3F (hex) Samsung Electronics Co.,Ltd +7C0A3F (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 KR -D0-59-19 (hex) zte corporation -D05919 (base 16) zte corporation +08-AA-89 (hex) zte corporation +08AA89 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China shenzhen guangdong 518057 CN -00-B8-81 (hex) New platforms LLC -00B881 (base 16) New platforms LLC - Varshavskoe shosse, 35, Bld. 1 - Moscow 117105 - RU - -B0-98-BC (hex) Huawei Device Co., Ltd. -B098BC (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -24-01-6F (hex) Huawei Device Co., Ltd. -24016F (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +04-D6-0E (hex) FUNAI ELECTRIC CO., LTD. +04D60E (base 16) FUNAI ELECTRIC CO., LTD. + 7-7-1, Nakagaito + Daito Osaka 574-0013 + JP -84-E3-42 (hex) Tuya Smart Inc. -84E342 (base 16) Tuya Smart Inc. - FLAT/RM 806 BLK ? 8/F CHEUNG SHA WAN PLAZA 833 CHEUNG SHA WAN ROAD KL - hongkong Hong Kong 999077 +B0-C9-52 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +B0C952 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -FC-CD-2F (hex) IEEE Registration Authority -FCCD2F (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +D0-E0-42 (hex) Cisco Systems, Inc +D0E042 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -C0-3C-59 (hex) Intel Corporate -C03C59 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -4C-3B-DF (hex) Microsoft Corporation -4C3BDF (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US +C4-74-69 (hex) BT9 +C47469 (base 16) BT9 + Dolev 33 + Tefen 2495900 + IL -B4-31-61 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -B43161 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 - CN +F4-6F-A4 (hex) Physik Instrumente GmbH & Co. KG +F46FA4 (base 16) Physik Instrumente GmbH & Co. KG + Auf der Roemerstr. 1 + Karlsruhe 76228 + DE -B4-FB-E3 (hex) AltoBeam (China) Inc. -B4FBE3 (base 16) AltoBeam (China) Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 +C8-BB-81 (hex) Huawei Device Co., Ltd. +C8BB81 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -18-87-40 (hex) Xiaomi Communications Co Ltd -188740 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +1C-47-2F (hex) Huawei Device Co., Ltd. +1C472F (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -34-1C-F0 (hex) Xiaomi Communications Co Ltd -341CF0 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +20-5E-64 (hex) Huawei Device Co., Ltd. +205E64 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -30-AF-CE (hex) vivo Mobile Communication Co., Ltd. -30AFCE (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +F4-41-9E (hex) Huawei Device Co., Ltd. +F4419E (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -64-F9-47 (hex) Senscomm Semiconductor Co., Ltd. -64F947 (base 16) Senscomm Semiconductor Co., Ltd. - Room 303-309, 3rd Floor International Building, NO.2 Suzhou Avenue West - Suzhou Jiangsu 215000 +90-F9-B7 (hex) HUAWEI TECHNOLOGIES CO.,LTD +90F9B7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -DC-00-B0 (hex) FREEBOX SAS -DC00B0 (base 16) FREEBOX SAS - 16 rue de la Ville l'Eveque - PARIS IdF 75008 - FR - -70-74-14 (hex) Murata Manufacturing Co., Ltd. -707414 (base 16) Murata Manufacturing Co., Ltd. - 1-10-1, Higashikotari - Nagaokakyo-shi Kyoto 617-8555 - JP - -A0-76-4E (hex) Espressif Inc. -A0764E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +F4-45-88 (hex) HUAWEI TECHNOLOGIES CO.,LTD +F44588 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -CC-4F-5C (hex) IEEE Registration Authority -CC4F5C (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - -FC-6D-D1 (hex) APRESIA Systems, Ltd. -FC6DD1 (base 16) APRESIA Systems, Ltd. - Tsukuba Network Technical Center, Kidamari 3550 - Tsuchiura-shi Ibaraki-ken 300-0026 - JP +54-AE-D2 (hex) CSL Dualcom Ltd +54AED2 (base 16) CSL Dualcom Ltd + Salamander Quay West, Park Lane + Harefield Middlesex UB9 6NZ + GB -6C-09-BF (hex) Fiberhome Telecommunication Technologies Co.,LTD -6C09BF (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN +80-B6-55 (hex) Intel Corporate +80B655 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -10-D7-B0 (hex) Sagemcom Broadband SAS -10D7B0 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +0C-CB-0C (hex) iSYS RTS GmbH +0CCB0C (base 16) iSYS RTS GmbH + Moosacher Str. 88 + Munich Bavaria 80809 + DE -44-59-43 (hex) zte corporation -445943 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +F4-4E-E3 (hex) Intel Corporate +F44EE3 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -34-36-54 (hex) zte corporation -343654 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +78-F2-35 (hex) Sichuan AI-Link Technology Co., Ltd. +78F235 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 CN -C8-E6-00 (hex) HUAWEI TECHNOLOGIES CO.,LTD -C8E600 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +08-2F-E9 (hex) HUAWEI TECHNOLOGIES CO.,LTD +082FE9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -D4-D5-1B (hex) HUAWEI TECHNOLOGIES CO.,LTD -D4D51B (base 16) HUAWEI TECHNOLOGIES CO.,LTD +98-48-74 (hex) HUAWEI TECHNOLOGIES CO.,LTD +984874 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -24-91-BB (hex) HUAWEI TECHNOLOGIES CO.,LTD -2491BB (base 16) HUAWEI TECHNOLOGIES CO.,LTD +1C-A6-81 (hex) HUAWEI TECHNOLOGIES CO.,LTD +1CA681 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -64-5E-10 (hex) HUAWEI TECHNOLOGIES CO.,LTD -645E10 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +E8-A6-60 (hex) HUAWEI TECHNOLOGIES CO.,LTD +E8A660 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -EC-F2-2B (hex) TECNO MOBILE LIMITED -ECF22B (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 - Hong Kong Hong Kong 999077 - HK - -30-96-10 (hex) Huawei Device Co., Ltd. -309610 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +CC-24-2E (hex) Shenzhen SuperElectron Technology Co.,Ltd. +CC242E (base 16) Shenzhen SuperElectron Technology Co.,Ltd. + 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city + Shenzhen Guangdong 518000 CN -FC-B3-BC (hex) Intel Corporate -FCB3BC (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -C8-16-DA (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -C816DA (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 +F0-01-6E (hex) Tianyi Telecom Terminals Company Limited +F0016E (base 16) Tianyi Telecom Terminals Company Limited + 6/F Changshang Building, No.29 North Xinhua Street, Xicheng District + Beijing 100031 CN -B0-44-14 (hex) New H3C Technologies Co., Ltd -B04414 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District +EC-0B-AE (hex) Hangzhou BroadLink Technology Co.,Ltd +EC0BAE (base 16) Hangzhou BroadLink Technology Co.,Ltd + Room 101,1/F,Unit C,Building 1,No.57 Jiang'er Road,Changhe Street,Binjiang District,Hangzhou,Zhejiang,P.R.China Hangzhou Zhejiang 310052 CN -E4-5F-01 (hex) Raspberry Pi Trading Ltd -E45F01 (base 16) Raspberry Pi Trading Ltd - Maurice Wilkes Building, Cowley Road - Cambridge CB4 0DS +80-25-11 (hex) ITEL MOBILE LIMITED +802511 (base 16) ITEL MOBILE LIMITED + RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K + Hong Kong KOWLOON 999077 + HK + +38-02-DE (hex) Sercomm Corporation. +3802DE (base 16) Sercomm Corporation. + 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen + Miao-Lih Hsuan 115 + TW + +D0-CF-D8 (hex) Huizhou Boshijie Technology Co.,Ltd +D0CFD8 (base 16) Huizhou Boshijie Technology Co.,Ltd + No.1 Xisan road, Huifeng west road, Zhongkai high-tech zone + Huizhou Guangdong 516006 + CN + +2C-BE-EB (hex) Nothing Technology Limited +2CBEEB (base 16) Nothing Technology Limited + 11 Staple Inn + London London WC1V 7QH GB -74-8F-3C (hex) Apple, Inc. -748F3C (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +74-09-AC (hex) Quext, LLC +7409AC (base 16) Quext, LLC + 5214 68th St., Suite 201 + Lubbock TX 79424 US -40-F9-46 (hex) Apple, Inc. -40F946 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +F0-4A-02 (hex) Cisco Systems, Inc +F04A02 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -1C-FE-2B (hex) Amazon Technologies Inc. -1CFE2B (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +50-9A-46 (hex) Safetrust Inc +509A46 (base 16) Safetrust Inc + 8112 Mill Creek Rd + Fremont CA 94539 US -A4-AE-12 (hex) Hon Hai Precision Industry Co., Ltd. -A4AE12 (base 16) Hon Hai Precision Industry Co., Ltd. - GuangDongShenZhen - ShenZhen GuangDong 518109 - CN - -9C-9D-7E (hex) Beijing Xiaomi Mobile Software Co., Ltd -9C9D7E (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN - -DC-A3-A2 (hex) Feng mi(Beijing)technology co., LTD -DCA3A2 (base 16) Feng mi(Beijing)technology co., LTD - RenHe Town barracks south street 10 yuan 33 level 301 - shunyi district Beijing 101300 - CN +A4-35-2D (hex) TRIZ Networks corp. +A4352D (base 16) TRIZ Networks corp. + 815 Daewangpangyo-ro + Sujeong-gu, Seongnam-si Gyeonggi-do 13449 + KR -EC-57-0D (hex) AFE Inc. -EC570D (base 16) AFE Inc. - 11210 County Line Rd - Mount Pleasant WI 53177 +B8-81-FA (hex) Apple, Inc. +B881FA (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -7C-25-DA (hex) FN-LINK TECHNOLOGY LIMITED -7C25DA (base 16) FN-LINK TECHNOLOGY LIMITED - A Building,HuiXin industial park,No 31, YongHe road, Fuyong town, Bao'an District - SHENZHEN GUANGDONG 518100 - CN - -30-59-B7 (hex) Microsoft -3059B7 (base 16) Microsoft - 1 Microsoft Way - Redmond Washington 98052 +9C-76-0E (hex) Apple, Inc. +9C760E (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -74-12-B3 (hex) CHONGQING FUGUI ELECTRONICS CO.,LTD. -7412B3 (base 16) CHONGQING FUGUI ELECTRONICS CO.,LTD. - Building D21,No.1, East Zone 1st Road,Xiyong Town,Shapingba District - Chongqing Chongqing 401332 - CN - -94-47-B0 (hex) BEIJING ESWIN COMPUTING TECHNOLOGY CO., LTD -9447B0 (base 16) BEIJING ESWIN COMPUTING TECHNOLOGY CO., LTD - Room 2179, Floor2,Block D, Building 33, Centralised Office Area, No.99, Kechuangshisi Road, BDA, Beijing - BEIJING BEIJING 100176 - CN - -E4-5A-D4 (hex) Eltex Enterprise Ltd. -E45AD4 (base 16) Eltex Enterprise Ltd. - Okruzhnaya st. 29v - Novosibirsk 630020 - RU +94-EA-32 (hex) Apple, Inc. +94EA32 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -44-A5-4E (hex) Qorvo International Pte. Ltd. -44A54E (base 16) Qorvo International Pte. Ltd. - 1 Changi Business Park Avenue 1 - #04-01 486058 - SG +50-F4-EB (hex) Apple, Inc. +50F4EB (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -A8-69-8C (hex) Oracle Corporation -A8698C (base 16) Oracle Corporation - 500 Oracle Parkway - Redwood Shores CA 94065 +28-C7-09 (hex) Apple, Inc. +28C709 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -A4-AC-0F (hex) Huawei Device Co., Ltd. -A4AC0F (base 16) Huawei Device Co., Ltd. +08-2E-36 (hex) Huawei Device Co., Ltd. +082E36 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -CC-FF-90 (hex) Huawei Device Co., Ltd. -CCFF90 (base 16) Huawei Device Co., Ltd. +C8-BF-FE (hex) Huawei Device Co., Ltd. +C8BFFE (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -00-30-54 (hex) Castlenet Technology Inc. -003054 (base 16) Castlenet Technology Inc. - 5F., No. 10, Daye Rd., Beitou Dist. - Taipei City 112030 - TW - -00-13-68 (hex) Saab Danmark A/S -001368 (base 16) Saab Danmark A/S - Alsion 2 - Soenderborg DK 6400 - DK - -B0-30-C8 (hex) Teal Drones, Inc. -B030C8 (base 16) Teal Drones, Inc. - 5200 South Highland Drive - Holladay UT 84117 +04-9F-81 (hex) NETSCOUT SYSTEMS INC +049F81 (base 16) NETSCOUT SYSTEMS INC + 310 Littleton Road + Westford MA 01886 US -D4-F3-37 (hex) Xunison Ltd. -D4F337 (base 16) Xunison Ltd. - 25 Kilbarbery Business Park, Upper Nangor Road - Dublin 22 Co. Dublin D22 NH32 - IE +00-80-8C (hex) NETSCOUT SYSTEMS INC +00808C (base 16) NETSCOUT SYSTEMS INC + 310 Littleton Road + Westford MA 01886 + US -48-A2-B8 (hex) Chengdu Vision-Zenith Tech.Co,.Ltd -48A2B8 (base 16) Chengdu Vision-Zenith Tech.Co,.Ltd - China (Sichuan) Free Trade Test Zone Chengdu Hi-tech Zone 300 Jiaozi Avenue 3 buildings 22 - Chengdu Sichuan 610041 +40-24-B2 (hex) Sichuan AI-Link Technology Co., Ltd. +4024B2 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 CN -58-E8-73 (hex) HANGZHOU DANGBEI NETWORK TECH.Co.,Ltd -58E873 (base 16) HANGZHOU DANGBEI NETWORK TECH.Co.,Ltd - Build C,Wanfu Center,Binkang Road No.228,Binjiang Area - China 210051 - CN +64-0D-22 (hex) LG Electronics (Mobile Communications) +640D22 (base 16) LG Electronics (Mobile Communications) + 60-39, Gasan-dong, Geumcheon-gu + Seoul 153-801 + KR -A0-68-1C (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -A0681C (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 +8C-31-E2 (hex) DAYOUPLUS +8C31E2 (base 16) DAYOUPLUS + 3F 509, Dunchon-daero, Jungwon-gu, Seongnam-si, Gyeonggi-do, Republic of Korea + Seongnam-si Gyeonggi-do 13217 + KR + +10-39-4E (hex) Hisense broadband multimedia technology Co.,Ltd +10394E (base 16) Hisense broadband multimedia technology Co.,Ltd + Song ling Road 399 + Qingdao 266000 CN -6C-44-2A (hex) HUAWEI TECHNOLOGIES CO.,LTD -6C442A (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +20-A7-F9 (hex) SHENZHEN OLANBOA TECHNOLOGY CO., LTD +20A7F9 (base 16) SHENZHEN OLANBOA TECHNOLOGY CO., LTD + 4/F, Building B, Block A, Longquan Science Park, Tongfuyu Phase II, Tongsheng Community, Dalang Street, Longhua District, Shenzhen + shenzhen Guangdong 518000 CN -A4-7C-C9 (hex) HUAWEI TECHNOLOGIES CO.,LTD -A47CC9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +F4-C7-AA (hex) Marvell Semiconductors +F4C7AA (base 16) Marvell Semiconductors + 15485 Sand Canyon Ave + IRVINE CA 92618 + US + +70-F0-88 (hex) Nintendo Co.,Ltd +70F088 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + +CC-ED-21 (hex) Nokia Shanghai Bell Co., Ltd. +CCED21 (base 16) Nokia Shanghai Bell Co., Ltd. + No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai + Shanghai 201206 CN -C4-0D-96 (hex) HUAWEI TECHNOLOGIES CO.,LTD -C40D96 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +FC-4D-A6 (hex) HUAWEI TECHNOLOGIES CO.,LTD +FC4DA6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -44-AF-28 (hex) Intel Corporate -44AF28 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +3C-15-12 (hex) Shenzhen Huanhu Technology Co.,Ltd +3C1512 (base 16) Shenzhen Huanhu Technology Co.,Ltd + 7 / F, building C4, Hengfeng Industrial City, 739 Zhoushi Road, Hezhou community, Hangcheng street, Bao'an District, Shenzhen + Shenzhen 518000 + CN -D4-A6-51 (hex) Tuya Smart Inc. -D4A651 (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 +18-C2-41 (hex) SonicWall +18C241 (base 16) SonicWall + 1033 McCarthy Blvd + Milpitas CA 95035 US -84-22-5E (hex) SHENZHEN TECHNEWCHIP TECHNOLOGY CO.,LTD. -84225E (base 16) SHENZHEN TECHNEWCHIP TECHNOLOGY CO.,LTD. - XILI STREET - SHENZHEN GUANGDONG 5180000 - CN +58-45-4C (hex) Ericsson AB +58454C (base 16) Ericsson AB + Torshamnsgatan 36 + Stockholm SE-164 80 + SE -F8-5C-7D (hex) Shenzhen Honesty Electronics Co.,Ltd. -F85C7D (base 16) Shenzhen Honesty Electronics Co.,Ltd. - 5/F,Zone B,Chitat Industrial Park,West Longping Road, Longgang District,Shenzhen City - Shenzhen Guangdong 518172 +B4-14-E6 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B414E6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -84-2A-FD (hex) HP Inc. -842AFD (base 16) HP Inc. - 10300 Energy Dr - Spring TX 77389 - US - -C0-B8-E6 (hex) Ruijie Networks Co.,LTD -C0B8E6 (base 16) Ruijie Networks Co.,LTD - No. 2, 7th floor, xingwangruijie, haixi hi-tech industrial park, high-tech zone, fuzhou city - Fuzhou Fujian 350002 +AC-99-29 (hex) HUAWEI TECHNOLOGIES CO.,LTD +AC9929 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -90-F6-44 (hex) Huawei Device Co., Ltd. -90F644 (base 16) Huawei Device Co., Ltd. +04-BA-1C (hex) Huawei Device Co., Ltd. +04BA1C (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -A8-35-12 (hex) Huawei Device Co., Ltd. -A83512 (base 16) Huawei Device Co., Ltd. +7C-3D-2B (hex) Huawei Device Co., Ltd. +7C3D2B (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -DC-41-A9 (hex) Intel Corporate -DC41A9 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +14-51-7E (hex) New H3C Technologies Co., Ltd +14517E (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN -8C-94-1F (hex) Cisco Systems, Inc -8C941F (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +08-3A-38 (hex) New H3C Technologies Co., Ltd +083A38 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN -68-7D-B4 (hex) Cisco Systems, Inc -687DB4 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +F8-AB-E5 (hex) shenzhen worldelite electronics co., LTD +F8ABE5 (base 16) shenzhen worldelite electronics co., LTD + Office 5 F, Xiang Yu Industrial Park, Longsheng Road, Longgang Dist + Shenzhen Guangdong 51800 + CN + +08-BB-3C (hex) Flextronics Tech.(Ind) Pvt Ltd +08BB3C (base 16) Flextronics Tech.(Ind) Pvt Ltd + 365, Benjamin Road + Sricity Vardahiah Palem(M),Chilamathur Village, Chittoor Distict 517646 + IN + +10-D5-61 (hex) Tuya Smart Inc. +10D561 (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 US -84-6B-48 (hex) ShenZhen EepuLink Co., Ltd. -846B48 (base 16) ShenZhen EepuLink Co., Ltd. - 4th Floor, Building 3, Nangang 1st industrial zone, Xili street, Nanshan district, - ShenZhen Guangdong 518000 +F0-A3-B2 (hex) Hui Zhou Gaoshengda Technology Co.,LTD +F0A3B2 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD + No.75,Zhongkai High-Tech Development District,Huizhou + Hui Zhou Guangdong 516006 CN -B4-60-ED (hex) Beijing Xiaomi Mobile Software Co., Ltd -B460ED (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +18-48-59 (hex) Castlenet Technology Inc. +184859 (base 16) Castlenet Technology Inc. + 5th Fl., No.159-1, Sec.3, Beishen Rd., Shenkeng Dist., + New Taipei City 222004 + TW + +2C-FD-B3 (hex) Tonly Technology Co. Ltd +2CFDB3 (base 16) Tonly Technology Co. Ltd + Section 37, Zhongkai Hi-Tech Development Zone + Huizhou Guangdong 516006 CN -30-CC-21 (hex) zte corporation -30CC21 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +2C-CE-1E (hex) Cloudtronics Pty Ltd +2CCE1E (base 16) Cloudtronics Pty Ltd + Unit 1 6 Powells Road Brookvale + Sydney NSW 2100 + AU + +DC-9A-8E (hex) Nanjing Cocomm electronics co., LTD +DC9A8E (base 16) Nanjing Cocomm electronics co., LTD + Room 201,the Qinheng technology park building + Nanjing Jiangsu 210012 CN -64-6C-80 (hex) CHONGQING FUGUI ELECTRONICS CO.,LTD. -646C80 (base 16) CHONGQING FUGUI ELECTRONICS CO.,LTD. - Building D21,No.1, East Zone 1st Road,Xiyong Town,Shapingba District - Chongqing Chongqing 401332 +58-AE-F1 (hex) Fiberhome Telecommunication Technologies Co.,LTD +58AEF1 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -00-30-0B (hex) mPHASE Technologies, Inc. -00300B (base 16) mPHASE Technologies, Inc. - 250 14th Street - Atlanta GA 30318 +E8-6C-C7 (hex) IEEE Registration Authority +E86CC7 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -C0-18-50 (hex) Quanta Computer Inc. -C01850 (base 16) Quanta Computer Inc. - No. 211, Wen-Hwa 2nd Rd.,Kuei-Shan Dist. - Taoyuan City Taiwan 33377 - TW +C0-D4-6B (hex) Huawei Device Co., Ltd. +C0D46B (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -44-27-F3 (hex) 70mai Co.,Ltd. -4427F3 (base 16) 70mai Co.,Ltd. - Room 2220, building 2, No. 588, Zixing road - Shanghai MinHang District 201100 +9C-95-67 (hex) Huawei Device Co., Ltd. +9C9567 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -00-08-96 (hex) Printronix, Inc. -000896 (base 16) Printronix, Inc. - 14600 Myford Rd. - Irvine California 92623-9559 +A4-7B-1A (hex) Huawei Device Co., Ltd. +A47B1A (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +D8-59-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD +D85982 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +48-B2-5D (hex) HUAWEI TECHNOLOGIES CO.,LTD +48B25D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +14-7D-05 (hex) SERCOMM PHILIPPINES INC +147D05 (base 16) SERCOMM PHILIPPINES INC + Lot 1 & 5, Phase 1, Filinvest Technology Park 1, Brgy. Punta, Calamba City + Calamba Lot 1 + PH + +78-7A-6F (hex) Juice Technology AG +787A6F (base 16) Juice Technology AG + Gewerbestrasse 7 + Cham Select State CH-6330 + CH + +64-17-59 (hex) Intellivision Holdings, LLC +641759 (base 16) Intellivision Holdings, LLC + 1844 E Carnegie + Santa Ana CA 92705 US -00-25-DC (hex) Sumitomo Electric Industries, Ltd -0025DC (base 16) Sumitomo Electric Industries, Ltd - 1-1-3, Shimaya, Konohana-ku - Osaka 554-0024 - JP +50-43-48 (hex) ThingsMatrix Inc. +504348 (base 16) ThingsMatrix Inc. + 9442 North Capital of Texas Hwy Plaza One Suite 500 Austin + Austin TX 78759 + US -04-7D-7B (hex) Quanta Computer Inc. -047D7B (base 16) Quanta Computer Inc. - NO. 211, WEN HWA 2RD.,KUEI SHAN HSIANG, TAO YUAN SHIEN, - TAIPEI TAIWAN 333 - TW +80-45-DD (hex) Intel Corporate +8045DD (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -08-9E-01 (hex) Quanta Computer Inc. -089E01 (base 16) Quanta Computer Inc. - No.211, Wen Hwa 2nd Rd - Taoyuan Taiwan 33377 +00-2D-B3 (hex) AMPAK Technology,Inc. +002DB3 (base 16) AMPAK Technology,Inc. + 3F, No.15-1 Zhonghua Road, Hsinchu Industrail Park, Hukou, + Hsinchu Hsinchu,Taiwan R.O.C. 30352 TW -A8-1E-84 (hex) Quanta Computer Inc. -A81E84 (base 16) Quanta Computer Inc. - No.211, Wen Hwa 2nd Rd., Kuei Shan Hsiang - Tao Yuan 33377 +E4-FD-45 (hex) Intel Corporate +E4FD45 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +14-50-51 (hex) SHARP Corporation +145051 (base 16) SHARP Corporation + 1 Takumi-cho, Sakai-ku + Sakai City Osaka 590-8522 + JP + +3C-C7-86 (hex) DONGGUAN HUARONG COMMUNICATION TECHNOLOGIES CO.,LTD. +3CC786 (base 16) DONGGUAN HUARONG COMMUNICATION TECHNOLOGIES CO.,LTD. + No.130 Dongxing East Road, Dongkeng Town + DONGGUAN 523450 + CN + +A4-7E-36 (hex) EM Microelectronic +A47E36 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +98-38-7D (hex) ITRONIC TECHNOLOGY CO . , LTD . +98387D (base 16) ITRONIC TECHNOLOGY CO . , LTD . + 2F C Building Fu Xin Lin lndustrial Park Hangcheng + lndustrial Zone Xixiang Street Baoan District Shenzhen 518100 + CN + +AC-D6-18 (hex) OnePlus Technology (Shenzhen) Co., Ltd +ACD618 (base 16) OnePlus Technology (Shenzhen) Co., Ltd + 18C02, 18C03, 18C04 ,18C05,TAIRAN BUILDING, + Shenzhen Guangdong 518000 + CN + +88-C3-E5 (hex) Betop Techonologies +88C3E5 (base 16) Betop Techonologies + 6F., No. 669, Bannan Road, Zhonghe District + New Taipei City 235 TW -B4-6F-2D (hex) Wahoo Fitness -B46F2D (base 16) Wahoo Fitness - 90 W Wieuca Rd, Suite 110 - Atlanta GA 30342 +E4-28-A4 (hex) Prama India Private Limited +E428A4 (base 16) Prama India Private Limited + Off 103, 765 Fly Edge, TPS III Jn of S V Rd, Nr Kora Kendra + Borivali West, Mumbai Maharashtra 400092 + IN + +94-3A-91 (hex) Amazon Technologies Inc. +943A91 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -BC-7F-7B (hex) Huawei Device Co., Ltd. -BC7F7B (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +C4-D0-E3 (hex) Intel Corporate +C4D0E3 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +28-C8-7C (hex) zte corporation +28C87C (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -F0-FA-C7 (hex) Huawei Device Co., Ltd. -F0FAC7 (base 16) Huawei Device Co., Ltd. +30-7C-4A (hex) Huawei Device Co., Ltd. +307C4A (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -00-26-C0 (hex) EnergyHub -0026C0 (base 16) EnergyHub - 232 3rd St C201 - Brooklyn NY 11215 +C8-D8-84 (hex) Universal Electronics, Inc. +C8D884 (base 16) Universal Electronics, Inc. + 201 E. Sandpointe Ave + Santa Ana CA 92707 US -80-EA-CA (hex) Dialog Semiconductor Hellas SA -80EACA (base 16) Dialog Semiconductor Hellas SA - Leoforos Syggrou 143 - Athens Attiki 17121 - GR +B4-A2-5C (hex) Cambium Networks Limited +B4A25C (base 16) Cambium Networks Limited + Unit B2, Linhay Business Park, + Ashburton Devon TQ13 7UP + GB -5C-85-7E (hex) IEEE Registration Authority -5C857E (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +2C-71-FF (hex) Amazon Technologies Inc. +2C71FF (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -88-03-E9 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -8803E9 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +48-78-5E (hex) Amazon Technologies Inc. +48785E (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +20-C7-4F (hex) SensorPush +20C74F (base 16) SensorPush + PO Box 211 + Garrison NY 10524 + US + +24-E8-53 (hex) LG Innotek +24E853 (base 16) LG Innotek + 26, Hanamsandan 5beon-ro + Gwangju Gwangsan-gu 506-731 + KR + +48-29-52 (hex) Sagemcom Broadband SAS +482952 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR + +60-6C-63 (hex) Hitron Technologies. Inc +606C63 (base 16) Hitron Technologies. Inc + No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C + Hsin-chu Taiwan 300 + TW + +00-F3-61 (hex) Amazon Technologies Inc. +00F361 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +00-40-DD (hex) HONG TECHNOLOGIES +0040DD (base 16) HONG TECHNOLOGIES + 532 WEDDELL DRIVE + SUNNYVALE CA 94089 + US + +40-8C-1F (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +408C1F (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD NO.18 HAIBIN ROAD, DONG GUAN GUANG DONG 523860 CN -0C-DC-7E (hex) Espressif Inc. -0CDC7E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +04-F0-3E (hex) Huawei Device Co., Ltd. +04F03E (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -20-98-D8 (hex) Shenzhen Yingdakang Technology CO., LTD -2098D8 (base 16) Shenzhen Yingdakang Technology CO., LTD - 8004,Building 51,Block 2,Shangtang Songzi Park, MinZhi St., Longhua Dist - Shenzhen 518055 +E8-D7-65 (hex) HUAWEI TECHNOLOGIES CO.,LTD +E8D765 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -18-26-49 (hex) Intel Corporate -182649 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +50-2D-FB (hex) IGShare Co., Ltd. +502DFB (base 16) IGShare Co., Ltd. + 410-ho, 28, Digital-ro 30-gil + Guro-gu, SEOUL 08389 + KR -A0-CF-F5 (hex) zte corporation -A0CFF5 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +40-EE-15 (hex) Zioncom Electronics (Shenzhen) Ltd. +40EE15 (base 16) Zioncom Electronics (Shenzhen) Ltd. + A1&A2 Building,Lantian Technology Park, Xinyu Road, Xingqiao Henggang Block, Shajing Street, Baoan District + Shenzhen Guangdong 518000 CN -98-B3-EF (hex) Huawei Device Co., Ltd. -98B3EF (base 16) Huawei Device Co., Ltd. +6C-0D-E1 (hex) Dongguan Cannice Precision Manufacturing Co., Ltd. +6C0DE1 (base 16) Dongguan Cannice Precision Manufacturing Co., Ltd. + Dongguan Cannice Precision Manufacturing Co., Ltd. + Dongguan Guangdong 523170 + CN + +6C-03-09 (hex) Cisco Systems, Inc +6C0309 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +BC-D2-95 (hex) Cisco Systems, Inc +BCD295 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +80-C5-01 (hex) OctoGate IT Security Systems GmbH +80C501 (base 16) OctoGate IT Security Systems GmbH + Friedrich List Strasse 42 + Paderborn NRW 33100 + DE + +14-D1-9E (hex) Apple, Inc. +14D19E (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +40-C7-11 (hex) Apple, Inc. +40C711 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +5C-70-17 (hex) Apple, Inc. +5C7017 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +8C-EC-7B (hex) Apple, Inc. +8CEC7B (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +C4-30-CA (hex) SD Biosensor +C430CA (base 16) SD Biosensor + C-4th & 5th Floor, Digital Empire Building, 980-3 + Suwon-si Kyonggi-do ASI|KR|KS002|SUWON + KR + +28-05-2E (hex) Dematic Corp +28052E (base 16) Dematic Corp + 507 Plymouth Ave NE + Grand Rapids MI 49505 + US + +E4-5E-1B (hex) Google, Inc. +E45E1B (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +78-E2-2C (hex) Huawei Device Co., Ltd. +78E22C (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -50-F9-58 (hex) Huawei Device Co., Ltd. -50F958 (base 16) Huawei Device Co., Ltd. +C0-D0-26 (hex) Huawei Device Co., Ltd. +C0D026 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -C0-A3-6E (hex) SKY UK LIMITED -C0A36E (base 16) SKY UK LIMITED - 130 Kings Road - Brentwood Essex 08854 - GB - -34-C9-3D (hex) Intel Corporate -34C93D (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 +A4-42-3B (hex) Intel Corporate +A4423B (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -1C-01-2D (hex) Ficer Technology -1C012D (base 16) Ficer Technology - 2F, No.138, Daye Rd., Beitou Dist., - Taipei City 11268 - TW - -00-05-C9 (hex) LG Innotek -0005C9 (base 16) LG Innotek - LG Component R&D Center - Ansan-si Gyeonggi-do 426-791 - KR +EC-BE-5F (hex) Vestel Elektronik San ve Tic. A.S. +ECBE5F (base 16) Vestel Elektronik San ve Tic. A.S. + Organize san + Manisa Turket 45030 + TR -AC-F1-08 (hex) LG Innotek -ACF108 (base 16) LG Innotek - 26, Hanamsandan 5beon-ro - Gwangju Gwangsan-gu 506-731 - KR +70-CF-49 (hex) Intel Corporate +70CF49 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -E4-1F-7B (hex) Cisco Systems, Inc -E41F7B (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +48-51-C5 (hex) Intel Corporate +4851C5 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -60-32-B1 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -6032B1 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. +F4-2A-7D (hex) TP-LINK TECHNOLOGIES CO.,LTD. +F42A7D (base 16) TP-LINK TECHNOLOGIES CO.,LTD. Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan Shenzhen Guangdong 518057 CN -7C-FD-6B (hex) Xiaomi Communications Co Ltd -7CFD6B (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +04-F9-93 (hex) Infinix mobility limited +04F993 (base 16) Infinix mobility limited + RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG + HongKong HongKong 999077 + HK -7C-C7-7E (hex) Fiberhome Telecommunication Technologies Co.,LTD -7CC77E (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +BC-BD-9E (hex) ITEL MOBILE LIMITED +BCBD9E (base 16) ITEL MOBILE LIMITED + RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K + Hong Kong KOWLOON 999077 + HK + +BC-6D-05 (hex) Dusun Electron Co.,Ltd. +BC6D05 (base 16) Dusun Electron Co.,Ltd. + NO.640 FengQing str.,DeQing, ZheJiang, China + huzhou zhejiang 313200 CN -34-58-40 (hex) HUAWEI TECHNOLOGIES CO.,LTD -345840 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +C0-E0-18 (hex) HUAWEI TECHNOLOGIES CO.,LTD +C0E018 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -5C-64-7A (hex) HUAWEI TECHNOLOGIES CO.,LTD -5C647A (base 16) HUAWEI TECHNOLOGIES CO.,LTD +5C-E7-47 (hex) HUAWEI TECHNOLOGIES CO.,LTD +5CE747 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -DC-EF-80 (hex) HUAWEI TECHNOLOGIES CO.,LTD -DCEF80 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +A8-FF-BA (hex) HUAWEI TECHNOLOGIES CO.,LTD +A8FFBA (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -30-49-50 (hex) IEEE Registration Authority -304950 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +F0-39-65 (hex) Samsung Electronics Co.,Ltd +F03965 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -20-9E-79 (hex) Universal Electronics, Inc. -209E79 (base 16) Universal Electronics, Inc. - 201 E. Sandpointe Ave - Santa Ana CA 92707 - US +EC-7C-B6 (hex) Samsung Electronics Co.,Ltd +EC7CB6 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -08-ED-9D (hex) TECNO MOBILE LIMITED -08ED9D (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 - Hong Kong Hong Kong 999077 - HK +58-A6-39 (hex) Samsung Electronics Co.,Ltd +58A639 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -FC-14-99 (hex) Aimore Acoustics Incorporation -FC1499 (base 16) Aimore Acoustics Incorporation - 16F,Tianliao Building(New Material industrial Park), Xueyuan Avenue, Nanshan District - Shenzhen Guangdong 518055 +8C-0F-C9 (hex) Huawei Device Co., Ltd. +8C0FC9 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -88-94-8F (hex) Xi'an Zhisensor Technologies Co.,Ltd -88948F (base 16) Xi'an Zhisensor Technologies Co.,Ltd - No.52 Jinye 1st Road Xi'an,Shaanxi,China - Xi an Xi an High-Tech Zone 710077 +30-4E-1B (hex) Huawei Device Co., Ltd. +304E1B (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -5C-D5-B5 (hex) Shenzhen WiSiYiLink Technology Co.,Ltd -5CD5B5 (base 16) Shenzhen WiSiYiLink Technology Co.,Ltd - Building a 3, huafengzhigu Yuanshan hi tech Industrial Park, No.62, Yinhe Road, he'ao community, Yuanshan street, Longgang District - Shenzhen 518100 +74-50-4E (hex) New H3C Technologies Co., Ltd +74504E (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -AC-5F-EA (hex) OnePlus Technology (Shenzhen) Co., Ltd -AC5FEA (base 16) OnePlus Technology (Shenzhen) Co., Ltd - 18C02, 18C03, 18C04 ,18C05,TAIRAN BUILDING, - Shenzhen Guangdong 518000 +08-9A-C7 (hex) zte corporation +089AC7 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -00-BE-D5 (hex) New H3C Technologies Co., Ltd -00BED5 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN +74-4C-A1 (hex) Liteon Technology Corporation +744CA1 (base 16) Liteon Technology Corporation + 4F, 90, Chien 1 Road + New Taipei City Taiwan 23585 + TW -E8-6D-CB (hex) Samsung Electronics Co.,Ltd -E86DCB (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 +B4-B2-91 (hex) LG Electronics +B4B291 (base 16) LG Electronics + 222 LG-ro, JINWI-MYEON + Pyeongtaek-si Gyeonggi-do 451-713 KR -18-95-52 (hex) 1MORE -189552 (base 16) 1MORE - TianliaoBuilding F14, New Materials Industrial Park, Xueyuan Blvd?Nanshan - Shenzhen Guangdong 518055 - CN +D8-00-93 (hex) Aurender Inc. +D80093 (base 16) Aurender Inc. + #1612, OBIZTOWER, 126, Beolmal-ro, Dongan-gu + Anyang-si Gyeonggi-do 14057 + KR -98-C7-A4 (hex) Shenzhen HS Fiber Communication Equipment CO., LTD -98C7A4 (base 16) Shenzhen HS Fiber Communication Equipment CO., LTD - 6F, Bld#A, Dezhong Industrial Park, Yangmei Village, Bantian Town, Longgang District - Shenzhen Guangdong 518129 +38-90-52 (hex) HUAWEI TECHNOLOGIES CO.,LTD +389052 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -04-4A-C6 (hex) Aipon Electronics Co., Ltd -044AC6 (base 16) Aipon Electronics Co., Ltd - #78, Qiaojiao Dong road, Qiaolong, Tangxia Town - Dongguan City Guangdong Province 523-710 +C0-F6-EC (hex) HUAWEI TECHNOLOGIES CO.,LTD +C0F6EC (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -C0-FF-A8 (hex) HUAWEI TECHNOLOGIES CO.,LTD -C0FFA8 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +CC-20-8C (hex) HUAWEI TECHNOLOGIES CO.,LTD +CC208C (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -C8-7E-A1 (hex) TCL MOKA International Limited -C87EA1 (base 16) TCL MOKA International Limited - 7/F, Building 22E 22 Science Park East Avenue - Hong Kong 999077 - HK +94-8E-D3 (hex) Arista Networks +948ED3 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US -64-2C-0F (hex) vivo Mobile Communication Co., Ltd. -642C0F (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +24-E4-C8 (hex) Fiberhome Telecommunication Technologies Co.,LTD +24E4C8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -30-66-D0 (hex) Huawei Device Co., Ltd. -3066D0 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +68-76-27 (hex) Zhuhai Dingzhi Electronic Technology Co., Ltd +687627 (base 16) Zhuhai Dingzhi Electronic Technology Co., Ltd + 6th floor, No.2 Jinliang Road, Hongqi Town, Jinwan District + Zhuhai GuangDong 519000 CN -3C-B2-33 (hex) Huawei Device Co., Ltd. -3CB233 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +C8-6C-3D (hex) Amazon Technologies Inc. +C86C3D (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -CC-C2-61 (hex) IEEE Registration Authority -CCC261 (base 16) IEEE Registration Authority +70-61-7B (hex) Cisco Systems, Inc +70617B (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +A0-02-4A (hex) IEEE Registration Authority +A0024A (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -5C-FE-9E (hex) Wiwynn Corporation Tainan Branch -5CFE9E (base 16) Wiwynn Corporation Tainan Branch - 4F, NO. 8, Beiyuan 3rd Rd., Anding Dist., - Tainan 745 - TW +04-E7-7E (hex) We Corporation Inc. +04E77E (base 16) We Corporation Inc. + 201, 33, Deokcheon-ro, Manan-gu + Anyang-si Gyeonggi-do 14088 + KR -88-1C-95 (hex) ITEL MOBILE LIMITED -881C95 (base 16) ITEL MOBILE LIMITED - RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K - Hong Kong KOWLOON 999077 - HK +74-EC-B2 (hex) Amazon Technologies Inc. +74ECB2 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -00-20-03 (hex) PIXEL POWER LTD. -002003 (base 16) PIXEL POWER LTD. - Unit 5 College Business Park - CAMBRIDGE CB1 3HD - GB +4C-52-EC (hex) SOLARWATT GmbH +4C52EC (base 16) SOLARWATT GmbH + Maria-Reiche-Str. 2a + Dresden 01109 + DE -F0-2E-51 (hex) Casa Systems -F02E51 (base 16) Casa Systems - 18-20 Orion Road Lane Cove West - LANE COVE NSW 2066 - AU +E0-1F-ED (hex) Nokia Shanghai Bell Co., Ltd. +E01FED (base 16) Nokia Shanghai Bell Co., Ltd. + No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai + Shanghai 201206 + CN -F4-69-42 (hex) ASKEY COMPUTER CORP -F46942 (base 16) ASKEY COMPUTER CORP - 10F,No.119,JIANKANG RD,ZHONGHE DIST - NEW TAIPEI TAIWAN 23585 +30-D9-41 (hex) Raydium Semiconductor Corp. +30D941 (base 16) Raydium Semiconductor Corp. + 2F, No. 23, LiHsin Rd., Hsinchu Science Park + Hsinchu, Taiwan, R.O.C. TW 300 TW -08-E9-F6 (hex) AMPAK Technology,Inc. -08E9F6 (base 16) AMPAK Technology,Inc. - 3F, No.15-1 Zhonghua Road, Hsinchu Industrail Park, Hukou, - Hsinchu Hsinchu,Taiwan R.O.C. 30352 - TW +14-6B-9A (hex) zte corporation +146B9A (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -C8-E4-2F (hex) Technical Research Design and Development -C8E42F (base 16) Technical Research Design and Development - 186 Lincoln street - Boston MA 02111 +9C-28-B3 (hex) Apple, Inc. +9C28B3 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -68-0A-E2 (hex) Silicon Laboratories -680AE2 (base 16) Silicon Laboratories - 7000 W. William Cannon Dr. - Austin TX 78735 +A0-78-17 (hex) Apple, Inc. +A07817 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -24-14-07 (hex) Xiamen Sigmastar Technology Ltd. -241407 (base 16) Xiamen Sigmastar Technology Ltd. - 15th Floor ,Unit A,Chuangxin Building, Software Park, Xiamen Torch Hi-Tech Industrial Development Zone, Xiamen,China - Xiamen Fujian 361005 +5C-87-30 (hex) Apple, Inc. +5C8730 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +A4-AA-FE (hex) Huawei Device Co., Ltd. +A4AAFE (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -B4-79-47 (hex) Nutanix -B47947 (base 16) Nutanix - 1740 Technology Drive Ste #150 - San Jose CA 95110 - US +F8-3B-7E (hex) Huawei Device Co., Ltd. +F83B7E (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -04-F8-F8 (hex) Edgecore Networks Corporation -04F8F8 (base 16) Edgecore Networks Corporation - 1 Creation RD 3. - Hsinchu 30077 - TW +88-15-C5 (hex) Huawei Device Co., Ltd. +8815C5 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -64-0B-D7 (hex) Apple, Inc. -640BD7 (base 16) Apple, Inc. +B4-1B-B0 (hex) Apple, Inc. +B41BB0 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -A8-91-3D (hex) Apple, Inc. -A8913D (base 16) Apple, Inc. +58-D3-49 (hex) Apple, Inc. +58D349 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -0C-3B-50 (hex) Apple, Inc. -0C3B50 (base 16) Apple, Inc. +F4-34-F0 (hex) Apple, Inc. +F434F0 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -4C-C9-5E (hex) Samsung Electronics Co.,Ltd -4CC95E (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR - -5C-91-57 (hex) HUAWEI TECHNOLOGIES CO.,LTD -5C9157 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +B0-8C-75 (hex) Apple, Inc. +B08C75 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -04-CB-88 (hex) Shenzhen Jingxun Software Telecommunication Technology Co.,Ltd -04CB88 (base 16) Shenzhen Jingxun Software Telecommunication Technology Co.,Ltd - 3/F,A5 Building Zhiyuan Community No.1001,Xueyuan Road Nanshan District - Shenzhen Guangdong 518055 +70-40-FF (hex) Huawei Device Co., Ltd. +7040FF (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -FC-A5-D0 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -FCA5D0 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +34-D6-93 (hex) Huawei Device Co., Ltd. +34D693 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -34-7D-F6 (hex) Intel Corporate -347DF6 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +9C-9A-C0 (hex) LEGO System A/S +9C9AC0 (base 16) LEGO System A/S + Aastvej 1 + Billund DK-7190 + DK -C8-5B-A0 (hex) Shenzhen Qihu Intelligent Technology Company Limited -C85BA0 (base 16) Shenzhen Qihu Intelligent Technology Company Limited - Room 201, Block A, No.1, Qianwan Road 1,Qianhai Shenzhen HongKong Modern Service Industry Cooperation Zone - Shenzhen Guangdong 518057 +A0-9F-10 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +A09F10 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268? Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 CN -E0-BE-03 (hex) Lite-On Network Communication (Dongguan) Limited -E0BE03 (base 16) Lite-On Network Communication (Dongguan) Limited - 30#Keji Rd,YinHu Industrial Area,Qingxi Town Dongguan City,Guang Dong China - Dongguan Guang Dong 523648 +20-1B-88 (hex) Dongguan Liesheng Electronic Co., Ltd. +201B88 (base 16) Dongguan Liesheng Electronic Co., Ltd. + F5, Building B, North Block, Gaosheng Tech Park, No. 84 Zhongli Road, Nancheng District, Dongguan Ci + dongguan guangdong 523000 CN -A4-88-73 (hex) Cisco Systems, Inc -A48873 (base 16) Cisco Systems, Inc +E8-EB-34 (hex) Cisco Systems, Inc +E8EB34 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -98-CB-A4 (hex) Benchmark Electronics -98CBA4 (base 16) Benchmark Electronics - Free Industrial Zone, Phase 1, - Bayan Lepas Penang 11900 - MY +3C-BD-C5 (hex) Arcadyan Corporation +3CBDC5 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW -B4-62-93 (hex) Samsung Electronics Co.,Ltd -B46293 (base 16) Samsung Electronics Co.,Ltd - #94-1,Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +A8-DA-0C (hex) SERVERCOM (INDIA) PRIVATE LIMITED +A8DA0C (base 16) SERVERCOM (INDIA) PRIVATE LIMITED + E-43/1 OKHLA INDUSTRIAL AREA PHASE-II NEW DELHI SOUTH DELHI + NEW DELHI 110001 + IN -00-0D-A1 (hex) MIRAE ITS Co.,LTD. -000DA1 (base 16) MIRAE ITS Co.,LTD. - 7F, ChangHyun B/D,960-1,HoGye1Dong - AnYang KyongiGiDo 431-840 +24-94-93 (hex) FibRSol Global Network Limited +249493 (base 16) FibRSol Global Network Limited + 17, Deep vihar, Vikas Nagar,, Near Mayank hospital,, Uttam Nagar, + New Delhi Delhi 110059 + IN + +28-D0-44 (hex) Shenzhen Xinyin technology company +28D044 (base 16) Shenzhen Xinyin technology company + 2/F, Building C, Jianxing Technology Building, Shahe West Road, Xili Street, Nanshan District + Shenzhen Guangdong 518055 + CN + +5C-10-C5 (hex) Samsung Electronics Co.,Ltd +5C10C5 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 KR -CC-D9-AC (hex) Intel Corporate -CCD9AC (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +A8-40-7D (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +A8407D (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN -B8-80-4F (hex) Texas Instruments -B8804F (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +FC-4B-57 (hex) Peerless Instrument Division of Curtiss-Wright +FC4B57 (base 16) Peerless Instrument Division of Curtiss-Wright + 1966D Broadhollow Road + East Farmingdale NY 11735 US -00-0F-2D (hex) CHUNG-HSIN ELECTRIC & MACHINERY MFG.CORP. -000F2D (base 16) CHUNG-HSIN ELECTRIC & MACHINERY MFG.CORP. - NO. 25, Wen-Te Rd., Lo-Shan Village - Kwei Shan Hsiang Taoyuan Hsien 330 - TW - -18-3C-B7 (hex) Huawei Device Co., Ltd. -183CB7 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +74-B7-B3 (hex) Shenzhen YOUHUA Technology Co., Ltd +74B7B3 (base 16) Shenzhen YOUHUA Technology Co., Ltd + Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District + Shenzhen Guangdong 518055 CN -5C-90-12 (hex) Owl Cyber Defense Solutions, LLC -5C9012 (base 16) Owl Cyber Defense Solutions, LLC - 38A Grove Street - Ridgefield CT 06877 - US +DC-9B-D6 (hex) TCT mobile ltd +DC9BD6 (base 16) TCT mobile ltd + No.86 hechang 7th road, zhongkai, Hi-Tech District + Hui Zhou Guang Dong 516006 + CN -48-16-93 (hex) Lear Corporation GmbH -481693 (base 16) Lear Corporation GmbH - Industriestrasse 48 - Kronach Bavaria 96317 - DE +DC-8C-1B (hex) vivo Mobile Communication Co., Ltd. +DC8C1B (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN -A4-BD-C4 (hex) HUAWEI TECHNOLOGIES CO.,LTD -A4BDC4 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +F8-53-29 (hex) HUAWEI TECHNOLOGIES CO.,LTD +F85329 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -38-F7-CD (hex) IEEE Registration Authority -38F7CD (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +E8-0A-EC (hex) Jiangsu Hengtong Optic-Electric Co., LTD +E80AEC (base 16) Jiangsu Hengtong Optic-Electric Co., LTD + 88 Hengtong Dadao, Qidu Town, Wujiang District + Suzhou Jiangsu Province 215200 + CN -9C-29-76 (hex) Intel Corporate -9C2976 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 +C4-DE-7B (hex) Huawei Device Co., Ltd. +C4DE7B (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +6C-1A-75 (hex) Huawei Device Co., Ltd. +6C1A75 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +40-D4-BD (hex) SK Networks Service CO., LTD. +40D4BD (base 16) SK Networks Service CO., LTD. + 120, Jangan-ro, Jangan-gu + Suwon-si Gyeonggi-do 16312 + KR + +84-1B-77 (hex) Intel Corporate +841B77 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -18-F6-97 (hex) Axiom Memory Solutions, Inc. -18F697 (base 16) Axiom Memory Solutions, Inc. - 8619 Wall Street Bldg 4, Suite 400 - Austin TX 78754-4591 - US - -5C-6B-D7 (hex) Foshan VIOMI Electric Appliance Technology Co. Ltd. -5C6BD7 (base 16) Foshan VIOMI Electric Appliance Technology Co. Ltd. - No.2 North Xinxi Fourth Road, Xiashi Village Committee,Lunjiao Sub-district Office, Shunde District - Foshan Guandong 528308 +7C-C2-94 (hex) Beijing Xiaomi Mobile Software Co., Ltd +7CC294 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -18-48-CA (hex) Murata Manufacturing Co., Ltd. -1848CA (base 16) Murata Manufacturing Co., Ltd. - 1-10-1, Higashikotari - Nagaokakyo-shi Kyoto 617-8555 - JP +E0-E2-E6 (hex) Espressif Inc. +E0E2E6 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN -CC-0D-F2 (hex) Motorola Mobility LLC, a Lenovo Company -CC0DF2 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +6C-76-37 (hex) Huawei Device Co., Ltd. +6C7637 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -94-43-4D (hex) Ciena Corporation -94434D (base 16) Ciena Corporation - 7035 Ridge Road - Hanover MD 21076 - US +68-D4-8B (hex) Hailo Technologies Ltd. +68D48B (base 16) Hailo Technologies Ltd. + 94 Yigal Alon + Tel Aviv 6789139 + IL -00-01-CD (hex) ARtem -0001CD (base 16) ARtem - Olgastraße 152 - - DE - -58-A8-7B (hex) Fitbit, Inc. -58A87B (base 16) Fitbit, Inc. - 199 Fremont Street, 14th Fl - San Francisco CA 94105 - US - -C4-89-ED (hex) Solid Optics EU N.V. -C489ED (base 16) Solid Optics EU N.V. - De Huchtstraat 35 - Almere Flevoland 1327 EC - NL +C4-37-72 (hex) Virtuozzo International GmbH +C43772 (base 16) Virtuozzo International GmbH + Vordergasse 59 + Schaffhausen 8200 + CH -48-8F-5A (hex) Routerboard.com -488F5A (base 16) Routerboard.com - Mikrotikls SIA - Riga Riga LV1009 - LV +CC-3B-27 (hex) TECNO MOBILE LIMITED +CC3B27 (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 + Hong Kong Hong Kong 999077 + HK -10-06-45 (hex) Sagemcom Broadband SAS -100645 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +3C-D2-E5 (hex) New H3C Technologies Co., Ltd +3CD2E5 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN -A0-27-B6 (hex) Samsung Electronics Co.,Ltd -A027B6 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +9C-73-70 (hex) HUAWEI TECHNOLOGIES CO.,LTD +9C7370 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -10-39-17 (hex) Samsung Electronics Co.,Ltd -103917 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +98-3F-60 (hex) HUAWEI TECHNOLOGIES CO.,LTD +983F60 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -98-80-EE (hex) Samsung Electronics Co.,Ltd -9880EE (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +C0-3F-DD (hex) HUAWEI TECHNOLOGIES CO.,LTD +C03FDD (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -90-EE-C7 (hex) Samsung Electronics Co.,Ltd -90EEC7 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +30-32-35 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. +303235 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. + No.218 Qianwangang Road + Qingdao Shangdong 266510 + CN -10-29-AB (hex) Samsung Electronics Co.,Ltd -1029AB (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +00-30-1F (hex) OPTICAL NETWORKS, INC. +00301F (base 16) OPTICAL NETWORKS, INC. + 166 BAYPOINTE PARKWAY + SAN JOSE CA 95134 + US -18-4E-CB (hex) Samsung Electronics Co.,Ltd -184ECB (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +08-58-A5 (hex) Beijing Vrv Software Corpoaration Limited. +0858A5 (base 16) Beijing Vrv Software Corpoaration Limited. + Room 1602, block C, Zhongguancun Science and technology development building, 34 Zhongguancun South Street + Beijing Beijing 100000 + CN -80-22-A7 (hex) NEC Platforms, Ltd. -8022A7 (base 16) NEC Platforms, Ltd. - 2-3 Kandatsukasamachi - Chiyodaku Tokyo 101-8532 +00-13-91 (hex) OUEN CO.,LTD. +001391 (base 16) OUEN CO.,LTD. + Gotanda NT Bldg.7F, + Shinagawa-ku Tokyo 141-0022 JP -5C-23-16 (hex) Squirrels Research Labs LLC -5C2316 (base 16) Squirrels Research Labs LLC - 8050 Freedom Ave NW Suite B - North Canton OH 44720 +58-24-29 (hex) Google, Inc. +582429 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US -AC-67-B2 (hex) Espressif Inc. -AC67B2 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +7C-73-EB (hex) Huawei Device Co., Ltd. +7C73EB (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -98-59-49 (hex) LUXOTTICA GROUP S.P.A. -985949 (base 16) LUXOTTICA GROUP S.P.A. - Piazzale Cadrona, 3 - Milano MI 20132 - IT - -64-E1-72 (hex) Shenzhen Qihoo Intelligent Technology Co.,Ltd -64E172 (base 16) Shenzhen Qihoo Intelligent Technology Co.,Ltd - Room 201,Block A.No.1,Qianwan Road1 Qianhai Shenzhen-HONGKONG Cooperation Zone - Shenzhen Guangdong 5181000 +E4-8F-1D (hex) Huawei Device Co., Ltd. +E48F1D (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -C4-D8-F3 (hex) iZotope -C4D8F3 (base 16) iZotope - 60 Hampshire St - Cambridge MA 02139 - US +CC-F5-5F (hex) E FOCUS INSTRUMENTS INDIA PRIVATE LIMITED +CCF55F (base 16) E FOCUS INSTRUMENTS INDIA PRIVATE LIMITED + PLOT NO 21, 1ST FLOOR, NO 22, SAMAYAPURAM MAIN ROAD PORUR + CHENNAI TAMIL NADU 600116 + IN -A8-40-25 (hex) Oxide Computer Company -A84025 (base 16) Oxide Computer Company - 1251 Park Avenue - Emeryville CA 94608 +08-7C-39 (hex) Amazon Technologies Inc. +087C39 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -60-F4-3A (hex) Edifier International -60F43A (base 16) Edifier International - Suit 2207, 22nd floor, Tower II, Lippo centre, 89 Queensway - Hong Kong 070 - CN +14-13-33 (hex) AzureWave Technology Inc. +141333 (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW -FC-B6-98 (hex) Cambridge Industries(Group) Co.,Ltd. -FCB698 (base 16) Cambridge Industries(Group) Co.,Ltd. - 22 Floor,Qilai Tower;889 Yishan Road - Shanghai CHINA 200233 +A4-17-8B (hex) HUAWEI TECHNOLOGIES CO.,LTD +A4178B (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -04-21-44 (hex) Sunitec Enterprise Co.,Ltd -042144 (base 16) Sunitec Enterprise Co.,Ltd - 3F.,No.98-1,Mincyuan Rd.Sindian City - Taipei County 231 231141 +94-B2-71 (hex) HUAWEI TECHNOLOGIES CO.,LTD +94B271 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -4C-33-29 (hex) Sweroam -4C3329 (base 16) Sweroam - Stortorget 16 - Orebro N/A 70211 - SE +80-8F-E8 (hex) Intelbras +808FE8 (base 16) Intelbras + BR 101, km 210, S/N° + São José Santa Catarina 88104800 + BR -78-2B-46 (hex) Intel Corporate -782B46 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 +18-CC-18 (hex) Intel Corporate +18CC18 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -78-57-73 (hex) HUAWEI TECHNOLOGIES CO.,LTD -785773 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +C0-C9-E3 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +C0C9E3 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan + Shenzhen Guangdong 518057 CN -AC-60-89 (hex) HUAWEI TECHNOLOGIES CO.,LTD -AC6089 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +F8-8C-21 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +F88C21 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan + Shenzhen Guangdong 518057 CN -84-3E-92 (hex) HUAWEI TECHNOLOGIES CO.,LTD -843E92 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +C4-27-8C (hex) Huawei Device Co., Ltd. +C4278C (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -70-8C-B6 (hex) HUAWEI TECHNOLOGIES CO.,LTD -708CB6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +78-05-8C (hex) mMax Communications, Inc. +78058C (base 16) mMax Communications, Inc. + 5151 California Ave., Suite 100 + Irvine CA 92617 + US -50-46-4A (hex) HUAWEI TECHNOLOGIES CO.,LTD -50464A (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +C4-A7-2B (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD +C4A72B (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD + Unit East Block22-24/F,Skyworth semiconductor design Bldg., Gaoxin Ave.4.S.,Nanshan District,Shenzhen,China + SHENZHEN GUANGDONG 518057 CN -C4-A4-02 (hex) HUAWEI TECHNOLOGIES CO.,LTD -C4A402 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +D0-C3-1E (hex) JUNGJIN Electronics Co.,Ltd +D0C31E (base 16) JUNGJIN Electronics Co.,Ltd + 41-11, Yangjipyeon-ro + Uiwang-si Gyeonggi-do 16007 + KR + +5C-34-00 (hex) Hisense Electric Co.,Ltd +5C3400 (base 16) Hisense Electric Co.,Ltd + Qianwangang Road 218 + Qingdao Shandong 266510 CN -68-33-2C (hex) KENSTEL NETWORKS LIMITED -68332C (base 16) KENSTEL NETWORKS LIMITED - 34D SECTOR 57 HSIIDC INDUSTRIAL AREA PHASE 4 - KUNDLI HARYANA 131028 - IN +D8-10-CB (hex) Andrea Informatique +D810CB (base 16) Andrea Informatique + 30 Rue Jules Guesde + Paris 75014 + FR -9C-BD-6E (hex) DERA Co., Ltd -9CBD6E (base 16) DERA Co., Ltd - Zhichun road NO7 Building B Room1203 Haidian District - Beijing 100191 - CN +E0-91-3C (hex) Kyeungin CNS Co., Ltd. +E0913C (base 16) Kyeungin CNS Co., Ltd. + 13, Gyeongin-ro, Sosa-gu + Bucheon-si Gyeonggi-do 14730 + KR -00-06-B3 (hex) Diagraph Corporation -0006B3 (base 16) Diagraph Corporation - 3401 Rider Trail South - Earth City MO 63045-1110 - US +D0-59-19 (hex) zte corporation +D05919 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -D8-78-7F (hex) Ubee Interactive Co., Limited -D8787F (base 16) Ubee Interactive Co., Limited - Flat/RM 1202, 12/F, AT Tower, 180 Electric Road - North Point 00000 - HK +00-B8-81 (hex) New platforms LLC +00B881 (base 16) New platforms LLC + Varshavskoe shosse, 35, Bld. 1 + Moscow 117105 + RU -DC-33-3D (hex) Huawei Device Co., Ltd. -DC333D (base 16) Huawei Device Co., Ltd. +B0-98-BC (hex) Huawei Device Co., Ltd. +B098BC (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -28-54-71 (hex) Huawei Device Co., Ltd. -285471 (base 16) Huawei Device Co., Ltd. +24-01-6F (hex) Huawei Device Co., Ltd. +24016F (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -B8-8E-82 (hex) Huawei Device Co., Ltd. -B88E82 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +84-E3-42 (hex) Tuya Smart Inc. +84E342 (base 16) Tuya Smart Inc. + FLAT/RM 806 BLK ? 8/F CHEUNG SHA WAN PLAZA 833 CHEUNG SHA WAN ROAD KL + hongkong Hong Kong 999077 CN -34-E5-EC (hex) Palo Alto Networks -34E5EC (base 16) Palo Alto Networks - 3000 Tannery Way - Santa Clara CA 95054 +FC-CD-2F (hex) IEEE Registration Authority +FCCD2F (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -08-87-C6 (hex) INGRAM MICRO SERVICES -0887C6 (base 16) INGRAM MICRO SERVICES - 100 CHEMIN DE BAILLOT - MONTAUBAN 82000 - FR +C0-3C-59 (hex) Intel Corporate +C03C59 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -D4-D2-D6 (hex) FN-LINK TECHNOLOGY LIMITED -D4D2D6 (base 16) FN-LINK TECHNOLOGY LIMITED - A Building,HuiXin industial park,No 31, YongHe road, Fuyong town, Bao'an District - SHENZHEN GUANGDONG 518100 +4C-3B-DF (hex) Microsoft Corporation +4C3BDF (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +B4-31-61 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +B43161 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 CN -10-50-72 (hex) Sercomm Corporation. -105072 (base 16) Sercomm Corporation. - 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen - Miao-Lih Hsuan 115 - TW +18-87-40 (hex) Xiaomi Communications Co Ltd +188740 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -98-FA-A7 (hex) INNONET -98FAA7 (base 16) INNONET - C-417, Munjeong Hyundai Knowledge Industry Center, Beobwon-ro 11-gil-7 - Songpa-gu Seoul 05836 - KR +34-1C-F0 (hex) Xiaomi Communications Co Ltd +341CF0 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -08-6B-D1 (hex) Shenzhen SuperElectron Technology Co.,Ltd. -086BD1 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. - 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city - Shenzhen Guangdong 518000 +30-AF-CE (hex) vivo Mobile Communication Co., Ltd. +30AFCE (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN -90-5D-7C (hex) New H3C Technologies Co., Ltd -905D7C (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +64-F9-47 (hex) Senscomm Semiconductor Co., Ltd. +64F947 (base 16) Senscomm Semiconductor Co., Ltd. + Room 303-309, 3rd Floor International Building, NO.2 Suzhou Avenue West + Suzhou Jiangsu 215000 CN -3C-B5-3D (hex) HUNAN GOKE MICROELECTRONICS CO.,LTD -3CB53D (base 16) HUNAN GOKE MICROELECTRONICS CO.,LTD - No.9, East 10th Road(South), Xingsha, Changsha - Changsha HUNAN 410131 +DC-00-B0 (hex) FREEBOX SAS +DC00B0 (base 16) FREEBOX SAS + 16 rue de la Ville l'Eveque + PARIS IdF 75008 + FR + +70-74-14 (hex) Murata Manufacturing Co., Ltd. +707414 (base 16) Murata Manufacturing Co., Ltd. + 1-10-1, Higashikotari + Nagaokakyo-shi Kyoto 617-8555 + JP + +A0-76-4E (hex) Espressif Inc. +A0764E (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN -AC-3A-67 (hex) Cisco Systems, Inc -AC3A67 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +CC-4F-5C (hex) IEEE Registration Authority +CC4F5C (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -00-B7-A8 (hex) Heinzinger electronic GmbH -00B7A8 (base 16) Heinzinger electronic GmbH - Anton Jakob Str.4 - Rosenheim BY 83026 - DE +FC-6D-D1 (hex) APRESIA Systems, Ltd. +FC6DD1 (base 16) APRESIA Systems, Ltd. + Tsukuba Network Technical Center, Kidamari 3550 + Tsuchiura-shi Ibaraki-ken 300-0026 + JP -34-CF-F6 (hex) Intel Corporate -34CFF6 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +6C-09-BF (hex) Fiberhome Telecommunication Technologies Co.,LTD +6C09BF (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN -B8-E3-B1 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B8E3B1 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +44-59-43 (hex) zte corporation +445943 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +34-36-54 (hex) zte corporation +343654 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +C8-E6-00 (hex) HUAWEI TECHNOLOGIES CO.,LTD +C8E600 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -98-0D-51 (hex) Huawei Device Co., Ltd. -980D51 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +D4-D5-1B (hex) HUAWEI TECHNOLOGIES CO.,LTD +D4D51B (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -00-AD-D5 (hex) Huawei Device Co., Ltd. -00ADD5 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +24-91-BB (hex) HUAWEI TECHNOLOGIES CO.,LTD +2491BB (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -EC-79-49 (hex) FUJITSU LIMITED -EC7949 (base 16) FUJITSU LIMITED - 403, Kosugi-cho 1-chome, Nakahara-ku - Kawasaki Kanagawa 211-0063 - JP +64-5E-10 (hex) HUAWEI TECHNOLOGIES CO.,LTD +645E10 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -AC-4A-67 (hex) Cisco Systems, Inc -AC4A67 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +EC-F2-2B (hex) TECNO MOBILE LIMITED +ECF22B (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 + Hong Kong Hong Kong 999077 + HK -90-0A-84 (hex) Mellanox Technologies, Inc. -900A84 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +30-96-10 (hex) Huawei Device Co., Ltd. +309610 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -70-CA-97 (hex) Ruckus Wireless -70CA97 (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 - US +FC-B3-BC (hex) Intel Corporate +FCB3BC (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -34-51-80 (hex) TCL King Electrical Appliances (Huizhou) Co., Ltd -345180 (base 16) TCL King Electrical Appliances (Huizhou) Co., Ltd - 10F, TCL Multimedia Building, TCL International E City, No.1001 Zhongshanyuan Rd., Nanshan District - Shenzhen Guangdong 518052 +C8-16-DA (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +C816DA (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 CN -6C-D9-4C (hex) vivo Mobile Communication Co., Ltd. -6CD94C (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +B0-44-14 (hex) New H3C Technologies Co., Ltd +B04414 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -AC-1E-D0 (hex) Temic Automotive Philippines Inc. -AC1ED0 (base 16) Temic Automotive Philippines Inc. - Bagsakan Road, FTI estate - Taguig 1630 - PH - -00-88-BA (hex) NC&C -0088BA (base 16) NC&C - Gurogu - Seoul 08390 - KR +E4-5F-01 (hex) Raspberry Pi Trading Ltd +E45F01 (base 16) Raspberry Pi Trading Ltd + Maurice Wilkes Building, Cowley Road + Cambridge CB4 0DS + GB -4C-A6-4D (hex) Cisco Systems, Inc -4CA64D (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +74-8F-3C (hex) Apple, Inc. +748F3C (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -40-2B-69 (hex) Kumho Electric Inc. -402B69 (base 16) Kumho Electric Inc. - 309, Bongmu-ro, Namsa-myeon, Cheoin-gu - Yongin-si Gyeonggi-do 17118 - KR +40-F9-46 (hex) Apple, Inc. +40F946 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -A4-08-01 (hex) Amazon Technologies Inc. -A40801 (base 16) Amazon Technologies Inc. +1C-FE-2B (hex) Amazon Technologies Inc. +1CFE2B (base 16) Amazon Technologies Inc. P.O Box 8102 Reno NV 89507 US -A8-A0-97 (hex) ScioTeq bvba -A8A097 (base 16) ScioTeq bvba - President Kennedypark 35A - Kortrijk 8500 - BE - -F4-73-35 (hex) Logitech Far East -F47335 (base 16) Logitech Far East - #2 Creation Rd. 4, - Hsinchu 300 - TW - -90-AD-FC (hex) Telechips, Inc. -90ADFC (base 16) Telechips, Inc. - 19F~23F,Luther Bldg.42, Olympic-ro 35da-gil, Songpa-gu, - Seoul Seoul 05510 - KR +A4-AE-12 (hex) Hon Hai Precision Industry Co., Ltd. +A4AE12 (base 16) Hon Hai Precision Industry Co., Ltd. + GuangDongShenZhen + ShenZhen GuangDong 518109 + CN -BC-16-95 (hex) zte corporation -BC1695 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +9C-9D-7E (hex) Beijing Xiaomi Mobile Software Co., Ltd +9C9D7E (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -A4-CF-D2 (hex) Ubee Interactive Co., Limited -A4CFD2 (base 16) Ubee Interactive Co., Limited - Flat/RM 1202, 12/F, AT Tower, 180 Electric Road - North Point 00000 - HK +DC-A3-A2 (hex) Feng mi(Beijing)technology co., LTD +DCA3A2 (base 16) Feng mi(Beijing)technology co., LTD + RenHe Town barracks south street 10 yuan 33 level 301 + shunyi district Beijing 101300 + CN -E8-E9-8E (hex) SOLAR controls s.r.o. -E8E98E (base 16) SOLAR controls s.r.o. - Brojova 25 - Plzen 32600 - CZ +EC-57-0D (hex) AFE Inc. +EC570D (base 16) AFE Inc. + 11210 County Line Rd + Mount Pleasant WI 53177 + US -64-F6-BB (hex) Fibocom Wireless Inc. -64F6BB (base 16) Fibocom Wireless Inc. - 5/F,TowerA,Technology Building 2,1057 Nanhai Blvd, Nanshan - Shenzhen 518000 Guangdong +7C-25-DA (hex) FN-LINK TECHNOLOGY LIMITED +7C25DA (base 16) FN-LINK TECHNOLOGY LIMITED + A Building,HuiXin industial park,No 31, YongHe road, Fuyong town, Bao'an District + SHENZHEN GUANGDONG 518100 CN -5C-A6-2D (hex) Cisco Systems, Inc -5CA62D (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +30-59-B7 (hex) Microsoft +3059B7 (base 16) Microsoft + 1 Microsoft Way + Redmond Washington 98052 US -EC-97-B2 (hex) SUMEC Machinery & Electric Co.,Ltd. -EC97B2 (base 16) SUMEC Machinery & Electric Co.,Ltd. - 198# ChangJiang Road, XuanWu District, 17F, SUMEC Building - Nanjing JiangSu 210018 +74-12-B3 (hex) CHONGQING FUGUI ELECTRONICS CO.,LTD. +7412B3 (base 16) CHONGQING FUGUI ELECTRONICS CO.,LTD. + Building D21,No.1, East Zone 1st Road,Xiyong Town,Shapingba District + Chongqing Chongqing 401332 CN -28-FA-7A (hex) Zhejiang Tmall Technology Co., Ltd. -28FA7A (base 16) Zhejiang Tmall Technology Co., Ltd. - Ali Center,No.3331 Keyuan South RD (Shenzhen bay), Nanshan District, Shenzhen Guangdong province - Shenzhen GuangDong 518000 +94-47-B0 (hex) BEIJING ESWIN COMPUTING TECHNOLOGY CO., LTD +9447B0 (base 16) BEIJING ESWIN COMPUTING TECHNOLOGY CO., LTD + Room 2179, Floor2,Block D, Building 33, Centralised Office Area, No.99, Kechuangshisi Road, BDA, Beijing + BEIJING BEIJING 100176 CN -10-05-E1 (hex) Nokia -1005E1 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +E4-5A-D4 (hex) Eltex Enterprise Ltd. +E45AD4 (base 16) Eltex Enterprise Ltd. + Okruzhnaya st. 29v + Novosibirsk 630020 + RU -BC-54-2F (hex) Intel Corporate -BC542F (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +44-A5-4E (hex) Qorvo International Pte. Ltd. +44A54E (base 16) Qorvo International Pte. Ltd. + 1 Changi Business Park Avenue 1 + #04-01 486058 + SG -84-2E-14 (hex) Silicon Laboratories -842E14 (base 16) Silicon Laboratories - 7000 W. William Cannon Dr. - Austin TX 78735 +A8-69-8C (hex) Oracle Corporation +A8698C (base 16) Oracle Corporation + 500 Oracle Parkway + Redwood Shores CA 94065 US -00-20-E1 (hex) ALAMAR ELECTRONICS -0020E1 (base 16) ALAMAR ELECTRONICS - 489 DIVISION STREET - CAMPBELL CA 95008 - US +A4-AC-0F (hex) Huawei Device Co., Ltd. +A4AC0F (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -08-F4-58 (hex) Huawei Device Co., Ltd. -08F458 (base 16) Huawei Device Co., Ltd. +CC-FF-90 (hex) Huawei Device Co., Ltd. +CCFF90 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -34-37-94 (hex) Hamee Corp. -343794 (base 16) Hamee Corp. - Square O2 2-12-10 Sakae-cho - Odawara Kanagawa 250-0011 - JP +00-30-54 (hex) Castlenet Technology Inc. +003054 (base 16) Castlenet Technology Inc. + 5F., No. 10, Daye Rd., Beitou Dist. + Taipei City 112030 + TW -D4-DC-09 (hex) Mist Systems, Inc. -D4DC09 (base 16) Mist Systems, Inc. - 1601 South De Anza Blvd, Suite 248 - Cupertino CA 95014 +00-13-68 (hex) Saab Danmark A/S +001368 (base 16) Saab Danmark A/S + Alsion 2 + Soenderborg DK 6400 + DK + +B0-30-C8 (hex) Teal Drones, Inc. +B030C8 (base 16) Teal Drones, Inc. + 5200 South Highland Drive + Holladay UT 84117 US -44-10-FE (hex) Huizhou Foryou General Electronics Co., Ltd. -4410FE (base 16) Huizhou Foryou General Electronics Co., Ltd. - North Shangxia Road, Dongjiang Hi-tech Industry Park - Huizhou Guangdong 516000 - CN +D4-F3-37 (hex) Xunison Ltd. +D4F337 (base 16) Xunison Ltd. + 25 Kilbarbery Business Park, Upper Nangor Road + Dublin 22 Co. Dublin D22 NH32 + IE -EC-31-6D (hex) Hansgrohe -EC316D (base 16) Hansgrohe - Auestraße 5-9 - Schiltach 77761 - DE +48-A2-B8 (hex) Chengdu Vision-Zenith Tech.Co,.Ltd +48A2B8 (base 16) Chengdu Vision-Zenith Tech.Co,.Ltd + China (Sichuan) Free Trade Test Zone Chengdu Hi-tech Zone 300 Jiaozi Avenue 3 buildings 22 + Chengdu Sichuan 610041 + CN -CC-7F-75 (hex) Cisco Systems, Inc -CC7F75 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +58-E8-73 (hex) HANGZHOU DANGBEI NETWORK TECH.Co.,Ltd +58E873 (base 16) HANGZHOU DANGBEI NETWORK TECH.Co.,Ltd + Build C,Wanfu Center,Binkang Road No.228,Binjiang Area + China 210051 + CN -20-E8-74 (hex) Apple, Inc. -20E874 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +A0-68-1C (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +A0681C (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN -D0-3F-AA (hex) Apple, Inc. -D03FAA (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +6C-44-2A (hex) HUAWEI TECHNOLOGIES CO.,LTD +6C442A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -7C-AB-60 (hex) Apple, Inc. -7CAB60 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +A4-7C-C9 (hex) HUAWEI TECHNOLOGIES CO.,LTD +A47CC9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -44-C6-5D (hex) Apple, Inc. -44C65D (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +C4-0D-96 (hex) HUAWEI TECHNOLOGIES CO.,LTD +C40D96 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -40-EC-99 (hex) Intel Corporate -40EC99 (base 16) Intel Corporate +44-AF-28 (hex) Intel Corporate +44AF28 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -F4-A4-D6 (hex) HUAWEI TECHNOLOGIES CO.,LTD -F4A4D6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +D4-A6-51 (hex) Tuya Smart Inc. +D4A651 (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US + +84-22-5E (hex) SHENZHEN TECHNEWCHIP TECHNOLOGY CO.,LTD. +84225E (base 16) SHENZHEN TECHNEWCHIP TECHNOLOGY CO.,LTD. + XILI STREET + SHENZHEN GUANGDONG 5180000 CN -18-7E-B9 (hex) Apple, Inc. -187EB9 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +F8-5C-7D (hex) Shenzhen Honesty Electronics Co.,Ltd. +F85C7D (base 16) Shenzhen Honesty Electronics Co.,Ltd. + 5/F,Zone B,Chitat Industrial Park,West Longping Road, Longgang District,Shenzhen City + Shenzhen Guangdong 518172 + CN + +84-2A-FD (hex) HP Inc. +842AFD (base 16) HP Inc. + 10300 Energy Dr + Spring TX 77389 US -1C-97-C5 (hex) Ynomia Pty Ltd -1C97C5 (base 16) Ynomia Pty Ltd - 153 Tooronga Rd - Glen Iris 3146 - AU +C0-B8-E6 (hex) Ruijie Networks Co.,LTD +C0B8E6 (base 16) Ruijie Networks Co.,LTD + No. 2, 7th floor, xingwangruijie, haixi hi-tech industrial park, high-tech zone, fuzhou city + Fuzhou Fujian 350002 + CN -5C-C1-D7 (hex) Samsung Electronics Co.,Ltd -5CC1D7 (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR +90-F6-44 (hex) Huawei Device Co., Ltd. +90F644 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -38-01-46 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD -380146 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD - NO.268? Fuqian Rd, Jutang community, Guanlan Town, Longhua New district - shenzhen guangdong 518000 +A8-35-12 (hex) Huawei Device Co., Ltd. +A83512 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -2C-57-41 (hex) Cisco Systems, Inc -2C5741 (base 16) Cisco Systems, Inc +DC-41-A9 (hex) Intel Corporate +DC41A9 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +8C-94-1F (hex) Cisco Systems, Inc +8C941F (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -B0-B3-53 (hex) IEEE Registration Authority -B0B353 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +68-7D-B4 (hex) Cisco Systems, Inc +687DB4 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -1C-91-9D (hex) Dongguan Liesheng Electronic Co., Ltd. -1C919D (base 16) Dongguan Liesheng Electronic Co., Ltd. - F5, Building B, North Block, Gaosheng Tech Park, No. 84 Zhongli Road, Nancheng District, Dongguan Ci - dongguan guangdong 523000 +84-6B-48 (hex) ShenZhen EepuLink Co., Ltd. +846B48 (base 16) ShenZhen EepuLink Co., Ltd. + 4th Floor, Building 3, Nangang 1st industrial zone, Xili street, Nanshan district, + ShenZhen Guangdong 518000 CN -20-11-4E (hex) MeteRSit S.R.L. -20114E (base 16) MeteRSit S.R.L. - Viale dell'Industria 31 - Padova 35129 - IT +B4-60-ED (hex) Beijing Xiaomi Mobile Software Co., Ltd +B460ED (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN -FC-F2-9F (hex) China Mobile Iot Limited company -FCF29F (base 16) China Mobile Iot Limited company - No. 8 Yangliu North Road, Yubei District, Chongqing, China - Chong Qing Chong Qing 401120 +30-CC-21 (hex) zte corporation +30CC21 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -F8-1F-32 (hex) Motorola Mobility LLC, a Lenovo Company -F81F32 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 +64-6C-80 (hex) CHONGQING FUGUI ELECTRONICS CO.,LTD. +646C80 (base 16) CHONGQING FUGUI ELECTRONICS CO.,LTD. + Building D21,No.1, East Zone 1st Road,Xiyong Town,Shapingba District + Chongqing Chongqing 401332 + CN + +00-30-0B (hex) mPHASE Technologies, Inc. +00300B (base 16) mPHASE Technologies, Inc. + 250 14th Street + Atlanta GA 30318 US -A8-7E-EA (hex) Intel Corporate -A87EEA (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +C0-18-50 (hex) Quanta Computer Inc. +C01850 (base 16) Quanta Computer Inc. + No. 211, Wen-Hwa 2nd Rd.,Kuei-Shan Dist. + Taoyuan City Taiwan 33377 + TW -B0-0A-D5 (hex) zte corporation -B00AD5 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +44-27-F3 (hex) 70mai Co.,Ltd. +4427F3 (base 16) 70mai Co.,Ltd. + Room 2220, building 2, No. 588, Zixing road + Shanghai MinHang District 201100 CN -A8-4D-4A (hex) Audiowise Technology Inc. -A84D4A (base 16) Audiowise Technology Inc. - 2F, No 1-1, Innovation RD I, Hsinchu Science Park - Hsincu Taiwan 30076 +00-08-96 (hex) Printronix, Inc. +000896 (base 16) Printronix, Inc. + 14600 Myford Rd. + Irvine California 92623-9559 + US + +00-25-DC (hex) Sumitomo Electric Industries, Ltd +0025DC (base 16) Sumitomo Electric Industries, Ltd + 1-1-3, Shimaya, Konohana-ku + Osaka 554-0024 + JP + +04-7D-7B (hex) Quanta Computer Inc. +047D7B (base 16) Quanta Computer Inc. + NO. 211, WEN HWA 2RD.,KUEI SHAN HSIANG, TAO YUAN SHIEN, + TAIPEI TAIWAN 333 TW -7C-DF-A1 (hex) Espressif Inc. -7CDFA1 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +08-9E-01 (hex) Quanta Computer Inc. +089E01 (base 16) Quanta Computer Inc. + No.211, Wen Hwa 2nd Rd + Taoyuan Taiwan 33377 + TW + +A8-1E-84 (hex) Quanta Computer Inc. +A81E84 (base 16) Quanta Computer Inc. + No.211, Wen Hwa 2nd Rd., Kuei Shan Hsiang + Tao Yuan 33377 + TW + +B4-6F-2D (hex) Wahoo Fitness +B46F2D (base 16) Wahoo Fitness + 90 W Wieuca Rd, Suite 110 + Atlanta GA 30342 + US + +BC-7F-7B (hex) Huawei Device Co., Ltd. +BC7F7B (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -98-00-6A (hex) zte corporation -98006A (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +F0-FA-C7 (hex) Huawei Device Co., Ltd. +F0FAC7 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -78-94-E8 (hex) Radio Bridge -7894E8 (base 16) Radio Bridge - 8601 73rd Ave N, Suite 38 - Brooklyn Park MN 55428 +00-26-C0 (hex) EnergyHub +0026C0 (base 16) EnergyHub + 232 3rd St C201 + Brooklyn NY 11215 US -54-7F-BC (hex) iodyne -547FBC (base 16) iodyne - 35 Miller Ave #175 - Mill Valley CA 94941 - US +80-EA-CA (hex) Dialog Semiconductor Hellas SA +80EACA (base 16) Dialog Semiconductor Hellas SA + Leoforos Syggrou 143 + Athens Attiki 17121 + GR -FC-E1-4F (hex) BRK Brands, Inc. -FCE14F (base 16) BRK Brands, Inc. - 3901 Liberty Street - Aurora IL 60504 +5C-85-7E (hex) IEEE Registration Authority +5C857E (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -88-96-55 (hex) Zitte corporation -889655 (base 16) Zitte corporation - 4F Yokohama Kusunoki-cho Building,4-7 Kusunoki-cho,Nishi-ku - Yokohama Kanagawa 2200003 - JP - -18-30-0C (hex) Hisense Electric Co.,Ltd -18300C (base 16) Hisense Electric Co.,Ltd - Qianwangang Roard 218 - Qingdao Shandong 266510 +88-03-E9 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +8803E9 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -5C-27-D4 (hex) Shenzhen Qihu Intelligent Technology Company Limited -5C27D4 (base 16) Shenzhen Qihu Intelligent Technology Company Limited - Room 201, Block A, No.1, Qianwan Road 1,Qianhai Shenzhen HongKong Modern Service Industry Cooperation Zone - Shenzhen Guangdong 518057 +20-98-D8 (hex) Shenzhen Yingdakang Technology CO., LTD +2098D8 (base 16) Shenzhen Yingdakang Technology CO., LTD + 8004,Building 51,Block 2,Shangtang Songzi Park, MinZhi St., Longhua Dist + Shenzhen 518055 CN -74-D8-3E (hex) Intel Corporate -74D83E (base 16) Intel Corporate +18-26-49 (hex) Intel Corporate +182649 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -88-B6-EE (hex) Dish Technologies Corp -88B6EE (base 16) Dish Technologies Corp - 94 Inverness Terrace E - Englewood CO 80112 - US +A0-CF-F5 (hex) zte corporation +A0CFF5 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -50-50-A4 (hex) Samsung Electronics Co.,Ltd -5050A4 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -08-F8-BC (hex) Apple, Inc. -08F8BC (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -90-A2-5B (hex) Apple, Inc. -90A25B (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -88-A4-79 (hex) Apple, Inc. -88A479 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -04-72-95 (hex) Apple, Inc. -047295 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -D4-46-E1 (hex) Apple, Inc. -D446E1 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -14-14-59 (hex) Vodafone Italia S.p.A. -141459 (base 16) Vodafone Italia S.p.A. - Via Lorenteggio nr. 240 - Milan Italy 20147 - IT - -50-43-B9 (hex) OktoInform RUS -5043B9 (base 16) OktoInform RUS - Bolshoy Tishinskiy pereulok, d. 26, korp.13-14, ofis 4R - Moscow 123557 - RU - -88-54-1F (hex) Google, Inc. -88541F (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US - -90-0C-C8 (hex) Google, Inc. -900CC8 (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US - -00-10-4F (hex) Oracle Corporation -00104F (base 16) Oracle Corporation - 500 Oracle Parkway - Redwood Shores CA 94065 - US - -08-D2-3E (hex) Intel Corporate -08D23E (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -48-7B-5E (hex) SMT TELECOMM HK -487B5E (base 16) SMT TELECOMM HK - Unit C 8/F Charmhill Centre 50 Hillwood RD. - Tsim Sha Tsui Kowloon 999077 - HK - -00-25-A9 (hex) Shanghai Embedway Information Technologies Co.,Ltd -0025A9 (base 16) Shanghai Embedway Information Technologies Co.,Ltd - 2F,Building 9,Lujiazui Software Park, No.20,Lane 91,E'Shan Road - Shanghai 200127 - CN - -68-21-5F (hex) Edgecore Networks Corporation -68215F (base 16) Edgecore Networks Corporation - 1 Creation RD 3. - Hsinchu 30077 - TW - -3C-BF-60 (hex) Apple, Inc. -3CBF60 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -AC-15-F4 (hex) Apple, Inc. -AC15F4 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -78-D1-62 (hex) Apple, Inc. -78D162 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -44-C7-FC (hex) Huawei Device Co., Ltd. -44C7FC (base 16) Huawei Device Co., Ltd. +98-B3-EF (hex) Huawei Device Co., Ltd. +98B3EF (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -78-85-F4 (hex) Huawei Device Co., Ltd. -7885F4 (base 16) Huawei Device Co., Ltd. +50-F9-58 (hex) Huawei Device Co., Ltd. +50F958 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -80-86-D9 (hex) Samsung Electronics Co.,Ltd -8086D9 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -38-6A-77 (hex) Samsung Electronics Co.,Ltd -386A77 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -00-B8-10 (hex) Yichip Microelectronics (Hangzhou) Co.,Ltd -00B810 (base 16) Yichip Microelectronics (Hangzhou) Co.,Ltd - Room 401, Building 15, No.498 Guoshoujing Road, Pudong Software Park - Shanghai 200120 - CN - -A4-B2-39 (hex) Cisco Systems, Inc -A4B239 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +C0-A3-6E (hex) SKY UK LIMITED +C0A36E (base 16) SKY UK LIMITED + 130 Kings Road + Brentwood Essex 08854 + GB -54-8D-5A (hex) Intel Corporate -548D5A (base 16) Intel Corporate +34-C9-3D (hex) Intel Corporate +34C93D (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -F4-49-55 (hex) MIMO TECH Co., Ltd. -F44955 (base 16) MIMO TECH Co., Ltd. - 21F.-6, No. 7, Sec. 3, New Taipei Blvd., Xinzhuang Dist., - New Taipei City Taiwan 24250 +1C-01-2D (hex) Ficer Technology +1C012D (base 16) Ficer Technology + 2F, No.138, Daye Rd., Beitou Dist., + Taipei City 11268 TW -08-09-C7 (hex) Zhuhai Unitech Power Technology Co., Ltd. -0809C7 (base 16) Zhuhai Unitech Power Technology Co., Ltd. - 102, Yinhua Road - Zhuhai Guangdong 519000 - CN - -58-96-1D (hex) Intel Corporate -58961D (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +00-05-C9 (hex) LG Innotek +0005C9 (base 16) LG Innotek + LG Component R&D Center + Ansan-si Gyeonggi-do 426-791 + KR -68-AF-FF (hex) Shanghai Cambricon Information Technology Co., Ltd. -68AFFF (base 16) Shanghai Cambricon Information Technology Co., Ltd. - 888 West Huanhu Road No.2, Nanhui New Town, Pudong New Area - Shanghai Shanghai 200000 - CN +AC-F1-08 (hex) LG Innotek +ACF108 (base 16) LG Innotek + 26, Hanamsandan 5beon-ro + Gwangju Gwangsan-gu 506-731 + KR -D0-1C-3C (hex) TECNO MOBILE LIMITED -D01C3C (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 - Hong Kong Hong Kong 999077 - HK +E4-1F-7B (hex) Cisco Systems, Inc +E41F7B (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -04-1D-C7 (hex) zte corporation -041DC7 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +7C-FD-6B (hex) Xiaomi Communications Co Ltd +7CFD6B (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -00-1A-4D (hex) GIGA-BYTE TECHNOLOGY CO.,LTD. -001A4D (base 16) GIGA-BYTE TECHNOLOGY CO.,LTD. - Pin-Jen City, Taoyuan, Taiwan, R.O.C. - Pin-Jen Taoyuan 324 - TW - -00-1F-D0 (hex) GIGA-BYTE TECHNOLOGY CO.,LTD. -001FD0 (base 16) GIGA-BYTE TECHNOLOGY CO.,LTD. - Pin-Jen City, Taoyuan, Taiwan, R.O.C. - Pin-Jen Taoyuan 324 - TW - -B8-9A-2A (hex) Intel Corporate -B89A2A (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -DC-21-E2 (hex) HUAWEI TECHNOLOGIES CO.,LTD -DC21E2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +7C-C7-7E (hex) Fiberhome Telecommunication Technologies Co.,LTD +7CC77E (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -FC-1B-D1 (hex) HUAWEI TECHNOLOGIES CO.,LTD -FC1BD1 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +34-58-40 (hex) HUAWEI TECHNOLOGIES CO.,LTD +345840 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -58-25-75 (hex) HUAWEI TECHNOLOGIES CO.,LTD -582575 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +5C-64-7A (hex) HUAWEI TECHNOLOGIES CO.,LTD +5C647A (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -28-DE-E5 (hex) HUAWEI TECHNOLOGIES CO.,LTD -28DEE5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +DC-EF-80 (hex) HUAWEI TECHNOLOGIES CO.,LTD +DCEF80 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -18-C0-4D (hex) GIGA-BYTE TECHNOLOGY CO.,LTD. -18C04D (base 16) GIGA-BYTE TECHNOLOGY CO.,LTD. - Pin-Jen City, Taoyuan, Taiwan, R.O.C. - Pin-Jen Taoyuan 324 - TW - -40-2C-76 (hex) IEEE Registration Authority -402C76 (base 16) IEEE Registration Authority +30-49-50 (hex) IEEE Registration Authority +304950 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -80-C9-55 (hex) Redpine Signals, Inc. -80C955 (base 16) Redpine Signals, Inc. - Plot 87, Sagar Society - Hyderabad AP 500034 - IN - -5C-68-D0 (hex) Aurora Innovation Inc. -5C68D0 (base 16) Aurora Innovation Inc. - 1880 Embarcadero Rd. - Palo Alto CA 94303 +20-9E-79 (hex) Universal Electronics, Inc. +209E79 (base 16) Universal Electronics, Inc. + 201 E. Sandpointe Ave + Santa Ana CA 92707 US -10-36-4A (hex) Boston Dynamics -10364A (base 16) Boston Dynamics - 78 4TH AVE - Waltham MA 02451 - US +08-ED-9D (hex) TECNO MOBILE LIMITED +08ED9D (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 + Hong Kong Hong Kong 999077 + HK -8C-AA-B5 (hex) Espressif Inc. -8CAAB5 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +FC-14-99 (hex) Aimore Acoustics Incorporation +FC1499 (base 16) Aimore Acoustics Incorporation + 16F,Tianliao Building(New Material industrial Park), Xueyuan Avenue, Nanshan District + Shenzhen Guangdong 518055 CN -00-0B-CC (hex) JUSAN, S.A. -000BCC (base 16) JUSAN, S.A. - Vivero, 5 - MADRID 28040 - ES - -00-E0-59 (hex) CONTROLLED ENVIRONMENTS, LTD. -00E059 (base 16) CONTROLLED ENVIRONMENTS, LTD. - 590 BERRY STREET - WINNEPEG R3H OR9 - CA - -0C-8E-29 (hex) Arcadyan Corporation -0C8E29 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW - -A0-22-4E (hex) IEEE Registration Authority -A0224E (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - -C4-98-78 (hex) SHANGHAI MOAAN INTELLIGENT TECHNOLOGY CO.,LTD -C49878 (base 16) SHANGHAI MOAAN INTELLIGENT TECHNOLOGY CO.,LTD - BLOCK B, 4TH FLOOR, BUILDING 2, NO. 401 CAOBAO ROAD, XUHUI DISTRICT, SHANGHAI - SHANGHAI 200030 +88-94-8F (hex) Xi'an Zhisensor Technologies Co.,Ltd +88948F (base 16) Xi'an Zhisensor Technologies Co.,Ltd + No.52 Jinye 1st Road Xi'an,Shaanxi,China + Xi an Xi an High-Tech Zone 710077 CN -00-0A-23 (hex) Parama Networks Inc -000A23 (base 16) Parama Networks Inc - 1955 The Alameda - San Jose CA 95126 - US - -38-43-E5 (hex) Grotech Inc -3843E5 (base 16) Grotech Inc - 19, Ojeongongeop-gil - Uiwang-si Gyeonggi-do 16072 - KR +5C-D5-B5 (hex) Shenzhen WiSiYiLink Technology Co.,Ltd +5CD5B5 (base 16) Shenzhen WiSiYiLink Technology Co.,Ltd + Building a 3, huafengzhigu Yuanshan hi tech Industrial Park, No.62, Yinhe Road, he'ao community, Yuanshan street, Longgang District + Shenzhen 518100 + CN -6C-06-D6 (hex) Huawei Device Co., Ltd. -6C06D6 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +AC-5F-EA (hex) OnePlus Technology (Shenzhen) Co., Ltd +AC5FEA (base 16) OnePlus Technology (Shenzhen) Co., Ltd + 18C02, 18C03, 18C04 ,18C05,TAIRAN BUILDING, + Shenzhen Guangdong 518000 CN -18-E7-77 (hex) vivo Mobile Communication Co., Ltd. -18E777 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +00-BE-D5 (hex) New H3C Technologies Co., Ltd +00BED5 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -0C-7A-15 (hex) Intel Corporate -0C7A15 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +E8-6D-CB (hex) Samsung Electronics Co.,Ltd +E86DCB (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -94-D6-DB (hex) NexFi -94D6DB (base 16) NexFi - Room 417, Building 14, No. 498, Guoshoujing Road, Pudong New Area - Shanghai 201203 +18-95-52 (hex) 1MORE +189552 (base 16) 1MORE + TianliaoBuilding F14, New Materials Industrial Park, Xueyuan Blvd?Nanshan + Shenzhen Guangdong 518055 CN -78-B4-6A (hex) HUAWEI TECHNOLOGIES CO.,LTD -78B46A (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +98-C7-A4 (hex) Shenzhen HS Fiber Communication Equipment CO., LTD +98C7A4 (base 16) Shenzhen HS Fiber Communication Equipment CO., LTD + 6F, Bld#A, Dezhong Industrial Park, Yangmei Village, Bantian Town, Longgang District + Shenzhen Guangdong 518129 CN -6C-EB-B6 (hex) HUAWEI TECHNOLOGIES CO.,LTD -6CEBB6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +04-4A-C6 (hex) Aipon Electronics Co., Ltd +044AC6 (base 16) Aipon Electronics Co., Ltd + #78, Qiaojiao Dong road, Qiaolong, Tangxia Town + Dongguan City Guangdong Province 523-710 CN -4C-F5-5B (hex) HUAWEI TECHNOLOGIES CO.,LTD -4CF55B (base 16) HUAWEI TECHNOLOGIES CO.,LTD +C0-FF-A8 (hex) HUAWEI TECHNOLOGIES CO.,LTD +C0FFA8 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -64-31-39 (hex) IEEE Registration Authority -643139 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +C8-7E-A1 (hex) TCL MOKA International Limited +C87EA1 (base 16) TCL MOKA International Limited + 7/F, Building 22E 22 Science Park East Avenue + Hong Kong 999077 + HK -A4-4B-D5 (hex) Xiaomi Communications Co Ltd -A44BD5 (base 16) Xiaomi Communications Co Ltd - The Rainbow City of China Resources - NO.68, Qinghe Middle Street Haidian District, Beijing 100085 +64-2C-0F (hex) vivo Mobile Communication Co., Ltd. +642C0F (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN -14-87-6A (hex) Apple, Inc. -14876A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -E0-B5-5F (hex) Apple, Inc. -E0B55F (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -F8-FF-C2 (hex) Apple, Inc. -F8FFC2 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -E0-EB-40 (hex) Apple, Inc. -E0EB40 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -40-77-A9 (hex) New H3C Technologies Co., Ltd -4077A9 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +30-66-D0 (hex) Huawei Device Co., Ltd. +3066D0 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -64-69-4E (hex) Texas Instruments -64694E (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - -F8-33-31 (hex) Texas Instruments -F83331 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - -40-A6-B7 (hex) Intel Corporate -40A6B7 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -B4-EC-F2 (hex) Shanghai Listent Medical Tech Co., Ltd. -B4ECF2 (base 16) Shanghai Listent Medical Tech Co., Ltd. - No. 668 Qingdai Road Pudong District - Shanghai Shanghai 201318 +3C-B2-33 (hex) Huawei Device Co., Ltd. +3CB233 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -C4-95-4D (hex) IEEE Registration Authority -C4954D (base 16) IEEE Registration Authority +CC-C2-61 (hex) IEEE Registration Authority +CCC261 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -64-95-6C (hex) LG Electronics -64956C (base 16) LG Electronics - 222 LG-ro, JINWI-MYEON - Pyeongtaek-si Gyeonggi-do 451-713 - KR +5C-FE-9E (hex) Wiwynn Corporation Tainan Branch +5CFE9E (base 16) Wiwynn Corporation Tainan Branch + 4F, NO. 8, Beiyuan 3rd Rd., Anding Dist., + Tainan 745 + TW -E8-3F-67 (hex) Huawei Device Co., Ltd. -E83F67 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +88-1C-95 (hex) ITEL MOBILE LIMITED +881C95 (base 16) ITEL MOBILE LIMITED + RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K + Hong Kong KOWLOON 999077 + HK -34-46-EC (hex) Huawei Device Co., Ltd. -3446EC (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +00-20-03 (hex) PIXEL POWER LTD. +002003 (base 16) PIXEL POWER LTD. + Unit 5 College Business Park + CAMBRIDGE CB1 3HD + GB -1C-BF-C0 (hex) CHONGQING FUGUI ELECTRONICS CO.,LTD. -1CBFC0 (base 16) CHONGQING FUGUI ELECTRONICS CO.,LTD. - Building D21,No.1, East Zone 1st Road,Xiyong Town,Shapingba District - Chongqing Chongqing 401332 - CN +F0-2E-51 (hex) Casa Systems +F02E51 (base 16) Casa Systems + 18-20 Orion Road Lane Cove West + LANE COVE NSW 2066 + AU -34-49-5B (hex) Sagemcom Broadband SAS -34495B (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +F4-69-42 (hex) ASKEY COMPUTER CORP +F46942 (base 16) ASKEY COMPUTER CORP + 10F,No.119,JIANKANG RD,ZHONGHE DIST + NEW TAIPEI TAIWAN 23585 + TW -80-16-09 (hex) Sleep Number -801609 (base 16) Sleep Number - 1001 Third Avenue South - Minneapolis MN 55404 - US +08-E9-F6 (hex) AMPAK Technology,Inc. +08E9F6 (base 16) AMPAK Technology,Inc. + 3F, No.15-1 Zhonghua Road, Hsinchu Industrail Park, Hukou, + Hsinchu Hsinchu,Taiwan R.O.C. 30352 + TW -AC-8B-9C (hex) Primera Technology, Inc. -AC8B9C (base 16) Primera Technology, Inc. - 2 Carlson Parkway N, Ste 375 - Plymouth MN 55447 +C8-E4-2F (hex) Technical Research Design and Development +C8E42F (base 16) Technical Research Design and Development + 186 Lincoln street + Boston MA 02111 US -1C-C1-BC (hex) Yichip Microelectronics (Hangzhou) Co.,Ltd -1CC1BC (base 16) Yichip Microelectronics (Hangzhou) Co.,Ltd - Room 401, Building 15, No.498 Guoshoujing Road, Pudong Software Park - Shanghai 200120 - CN - -2C-3A-FD (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -2C3AFD (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -AC-61-B9 (hex) WAMA Technology Limited -AC61B9 (base 16) WAMA Technology Limited - Room 2205, Westley Square, 48 Hoi Yuen Road, Kwun Tong, Kowloon - Hong Kong 00000 - HK - -F8-55-CD (hex) Visteon Corporation -F855CD (base 16) Visteon Corporation - One Village Center Drive - Van Buren Twp MI 48111 +68-0A-E2 (hex) Silicon Laboratories +680AE2 (base 16) Silicon Laboratories + 7000 W. William Cannon Dr. + Austin TX 78735 US -44-18-47 (hex) HUNAN SCROWN ELECTRONIC INFORMATION TECH.CO.,LTD -441847 (base 16) HUNAN SCROWN ELECTRONIC INFORMATION TECH.CO.,LTD - Building No.4,Changsha Zhongdian Software Park No.39,Jianshan Road - Changsha Hunan 410006 +24-14-07 (hex) Xiamen Sigmastar Technology Ltd. +241407 (base 16) Xiamen Sigmastar Technology Ltd. + 15th Floor ,Unit A,Chuangxin Building, Software Park, Xiamen Torch Hi-Tech Industrial Development Zone, Xiamen,China + Xiamen Fujian 361005 CN -00-30-B7 (hex) Teletrol Systems, Inc. -0030B7 (base 16) Teletrol Systems, Inc. - Technology Center - Manchester NH 03101 +B4-79-47 (hex) Nutanix +B47947 (base 16) Nutanix + 1740 Technology Drive Ste #150 + San Jose CA 95110 US -C8-F3-19 (hex) LG Electronics (Mobile Communications) -C8F319 (base 16) LG Electronics (Mobile Communications) - 60-39, Gasan-dong, Geumcheon-gu - Seoul 153-801 - KR - -00-90-96 (hex) ASKEY COMPUTER CORP -009096 (base 16) ASKEY COMPUTER CORP - 2F, NO. 2, LANE 497 - TAIPEI 23136 12345 +04-F8-F8 (hex) Edgecore Networks Corporation +04F8F8 (base 16) Edgecore Networks Corporation + 1 Creation RD 3. + Hsinchu 30077 TW -24-74-F7 (hex) GoPro -2474F7 (base 16) GoPro - 3000 Clearview Way - San Mateo CA 94402 +64-0B-D7 (hex) Apple, Inc. +640BD7 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -7C-D5-66 (hex) Amazon Technologies Inc. -7CD566 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +A8-91-3D (hex) Apple, Inc. +A8913D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -68-63-50 (hex) Hella India Automotive Pvt Ltd -686350 (base 16) Hella India Automotive Pvt Ltd - Unit no 201A to 201B Nano Space Surveyno.5/1B/2 BanerBaner Pashan Link road - Pune Maharastra 411045 - IN +0C-3B-50 (hex) Apple, Inc. +0C3B50 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -18-70-3B (hex) Huawei Device Co., Ltd. -18703B (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +4C-C9-5E (hex) Samsung Electronics Co.,Ltd +4CC95E (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR -44-22-95 (hex) China Mobile Iot Limited company -442295 (base 16) China Mobile Iot Limited company - No. 8 Yangliu North Road, Yubei District, Chongqing, China - Chong Qing Chong Qing 401120 +5C-91-57 (hex) HUAWEI TECHNOLOGIES CO.,LTD +5C9157 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -E8-5A-8B (hex) Xiaomi Communications Co Ltd -E85A8B (base 16) Xiaomi Communications Co Ltd - The Rainbow City of China Resources - NO.68, Qinghe Middle Street Haidian District, Beijing 100085 +04-CB-88 (hex) Shenzhen Jingxun Software Telecommunication Technology Co.,Ltd +04CB88 (base 16) Shenzhen Jingxun Software Telecommunication Technology Co.,Ltd + 3/F,A5 Building Zhiyuan Community No.1001,Xueyuan Road Nanshan District + Shenzhen Guangdong 518055 CN -80-75-1F (hex) SKY UK LIMITED -80751F (base 16) SKY UK LIMITED - 130 Kings Road - Brentwood Essex 08854 - GB - -D8-9E-61 (hex) Huawei Device Co., Ltd. -D89E61 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +FC-A5-D0 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +FCA5D0 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -34-7E-00 (hex) Huawei Device Co., Ltd. -347E00 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +34-7D-F6 (hex) Intel Corporate +347DF6 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +C8-5B-A0 (hex) Shenzhen Qihu Intelligent Technology Company Limited +C85BA0 (base 16) Shenzhen Qihu Intelligent Technology Company Limited + Room 201, Block A, No.1, Qianwan Road 1,Qianhai Shenzhen HongKong Modern Service Industry Cooperation Zone + Shenzhen Guangdong 518057 CN -5C-E5-0C (hex) Beijing Xiaomi Mobile Software Co., Ltd -5CE50C (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +E0-BE-03 (hex) Lite-On Network Communication (Dongguan) Limited +E0BE03 (base 16) Lite-On Network Communication (Dongguan) Limited + 30#Keji Rd,YinHu Industrial Area,Qingxi Town Dongguan City,Guang Dong China + Dongguan Guang Dong 523648 CN -5C-71-0D (hex) Cisco Systems, Inc -5C710D (base 16) Cisco Systems, Inc - 80 West Tasman Dr. +A4-88-73 (hex) Cisco Systems, Inc +A48873 (base 16) Cisco Systems, Inc + 80 West Tasman Drive San Jose CA 94568 US -98-AF-65 (hex) Intel Corporate -98AF65 (base 16) Intel Corporate +98-CB-A4 (hex) Benchmark Electronics +98CBA4 (base 16) Benchmark Electronics + Free Industrial Zone, Phase 1, + Bayan Lepas Penang 11900 + MY + +B4-62-93 (hex) Samsung Electronics Co.,Ltd +B46293 (base 16) Samsung Electronics Co.,Ltd + #94-1,Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +00-0D-A1 (hex) MIRAE ITS Co.,LTD. +000DA1 (base 16) MIRAE ITS Co.,LTD. + 7F, ChangHyun B/D,960-1,HoGye1Dong + AnYang KyongiGiDo 431-840 + KR + +CC-D9-AC (hex) Intel Corporate +CCD9AC (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -F8-89-3C (hex) Inventec Appliances Corp. -F8893C (base 16) Inventec Appliances Corp. - 37 Wugong 5th road, New Taipei Industrial Park, - New Taipei City Wugu District 24890 +B8-80-4F (hex) Texas Instruments +B8804F (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +00-0F-2D (hex) CHUNG-HSIN ELECTRIC & MACHINERY MFG.CORP. +000F2D (base 16) CHUNG-HSIN ELECTRIC & MACHINERY MFG.CORP. + NO. 25, Wen-Te Rd., Lo-Shan Village + Kwei Shan Hsiang Taoyuan Hsien 330 TW -74-9E-F5 (hex) Samsung Electronics Co.,Ltd -749EF5 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +18-3C-B7 (hex) Huawei Device Co., Ltd. +183CB7 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -68-BF-C4 (hex) Samsung Electronics Co.,Ltd -68BFC4 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +5C-90-12 (hex) Owl Cyber Defense Solutions, LLC +5C9012 (base 16) Owl Cyber Defense Solutions, LLC + 38A Grove Street + Ridgefield CT 06877 + US -B8-2A-DC (hex) EFR Europäische Funk-Rundsteuerung GmbH -B82ADC (base 16) EFR Europäische Funk-Rundsteuerung GmbH - Nymphenburger Straße 20b - Munich 80335 +48-16-93 (hex) Lear Corporation GmbH +481693 (base 16) Lear Corporation GmbH + Industriestrasse 48 + Kronach Bavaria 96317 DE -A0-DF-15 (hex) HUAWEI TECHNOLOGIES CO.,LTD -A0DF15 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +A4-BD-C4 (hex) HUAWEI TECHNOLOGIES CO.,LTD +A4BDC4 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -C4-AD-34 (hex) Routerboard.com -C4AD34 (base 16) Routerboard.com - Mikrotikls SIA - Riga Riga LV1009 - LV - -00-07-33 (hex) DANCONTROL Engineering -000733 (base 16) DANCONTROL Engineering - Italiensvej 1-5 - - DK +38-F7-CD (hex) IEEE Registration Authority +38F7CD (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -A8-5E-45 (hex) ASUSTek COMPUTER INC. -A85E45 (base 16) ASUSTek COMPUTER INC. - 15,Li-Te Rd., Peitou, Taipei 112, Taiwan - Taipei Taiwan 112 - TW +9C-29-76 (hex) Intel Corporate +9C2976 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -64-C9-01 (hex) INVENTEC Corporation -64C901 (base 16) INVENTEC Corporation - No.66, Hougang St., Shilin Dist., Taipei City 111, Taiwan (R.O.C.) - Taipei 111 - TW +18-F6-97 (hex) Axiom Memory Solutions, Inc. +18F697 (base 16) Axiom Memory Solutions, Inc. + 8619 Wall Street Bldg 4, Suite 400 + Austin TX 78754-4591 + US -B4-39-39 (hex) Shenzhen TINNO Mobile Technology Corp. -B43939 (base 16) Shenzhen TINNO Mobile Technology Corp. - Building, No.33, Xiandong Rd, Xili - Nanshan District, Shenzhen PRC 518053 +5C-6B-D7 (hex) Foshan VIOMI Electric Appliance Technology Co. Ltd. +5C6BD7 (base 16) Foshan VIOMI Electric Appliance Technology Co. Ltd. + No.2 North Xinxi Fourth Road, Xiashi Village Committee,Lunjiao Sub-district Office, Shunde District + Foshan Guandong 528308 CN -5C-CD-5B (hex) Intel Corporate -5CCD5B (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +18-48-CA (hex) Murata Manufacturing Co., Ltd. +1848CA (base 16) Murata Manufacturing Co., Ltd. + 1-10-1, Higashikotari + Nagaokakyo-shi Kyoto 617-8555 + JP -84-C8-07 (hex) ADVA Optical Networking Ltd. -84C807 (base 16) ADVA Optical Networking Ltd. - ADVAntage House - York YO30 4RY - GB +CC-0D-F2 (hex) Motorola Mobility LLC, a Lenovo Company +CC0DF2 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US -10-B3-C6 (hex) Cisco Systems, Inc -10B3C6 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +94-43-4D (hex) Ciena Corporation +94434D (base 16) Ciena Corporation + 7035 Ridge Road + Hanover MD 21076 US -10-B3-D6 (hex) Cisco Systems, Inc -10B3D6 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +00-01-CD (hex) ARtem +0001CD (base 16) ARtem + Olgastraße 152 + + DE + +58-A8-7B (hex) Fitbit, Inc. +58A87B (base 16) Fitbit, Inc. + 199 Fremont Street, 14th Fl + San Francisco CA 94105 US -A0-AB-51 (hex) WEIFANG GOERTEK ELECTRONICS CO.,LTD -A0AB51 (base 16) WEIFANG GOERTEK ELECTRONICS CO.,LTD - Gaoxin 2 Road, Free Trade Zone,Weifang,Shandong,261205,P.R.China - Weifang Shandong 261205 - CN +C4-89-ED (hex) Solid Optics EU N.V. +C489ED (base 16) Solid Optics EU N.V. + De Huchtstraat 35 + Almere Flevoland 1327 EC + NL -04-B1-A1 (hex) Samsung Electronics Co.,Ltd -04B1A1 (base 16) Samsung Electronics Co.,Ltd +48-8F-5A (hex) Routerboard.com +488F5A (base 16) Routerboard.com + Mikrotikls SIA + Riga Riga LV1009 + LV + +A0-27-B6 (hex) Samsung Electronics Co.,Ltd +A027B6 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -CC-46-4E (hex) Samsung Electronics Co.,Ltd -CC464E (base 16) Samsung Electronics Co.,Ltd +10-39-17 (hex) Samsung Electronics Co.,Ltd +103917 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -30-6F-07 (hex) Nations Technologies Inc. -306F07 (base 16) Nations Technologies Inc. - 18F, Nations Tower, Nanshan District - Shenzhen 518057 - CN +98-80-EE (hex) Samsung Electronics Co.,Ltd +9880EE (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -3C-89-4D (hex) Dr. Ing. h.c. F. Porsche AG -3C894D (base 16) Dr. Ing. h.c. F. Porsche AG - Porscheplatz 1 - Stuttgart 70435 - DE +90-EE-C7 (hex) Samsung Electronics Co.,Ltd +90EEC7 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -F8-54-B8 (hex) Amazon Technologies Inc. -F854B8 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +10-29-AB (hex) Samsung Electronics Co.,Ltd +1029AB (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -78-17-35 (hex) Nokia Shanghai Bell Co., Ltd. -781735 (base 16) Nokia Shanghai Bell Co., Ltd. - No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai - Shanghai 201206 - CN +18-4E-CB (hex) Samsung Electronics Co.,Ltd +184ECB (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -9C-FF-C2 (hex) AVI Systems GmbH -9CFFC2 (base 16) AVI Systems GmbH - Dr. Franz Wilhelmstraße 2A - Krems a. d. Donau 3500 - AT +5C-23-16 (hex) Squirrels Research Labs LLC +5C2316 (base 16) Squirrels Research Labs LLC + 8050 Freedom Ave NW Suite B + North Canton OH 44720 + US -44-D8-78 (hex) Hui Zhou Gaoshengda Technology Co.,LTD -44D878 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD - No.75,Zhongkai High-Tech Development District,Huizhou - Hui Zhou Guangdong 516006 - CN +98-59-49 (hex) LUXOTTICA GROUP S.P.A. +985949 (base 16) LUXOTTICA GROUP S.P.A. + Piazzale Cadrona, 3 + Milano MI 20132 + IT -78-97-C3 (hex) DINGXIN INFORMATION TECHNOLOGY CO.,LTD -7897C3 (base 16) DINGXIN INFORMATION TECHNOLOGY CO.,LTD - No.6 huasui Road,ZhuJiang Xincheng - Guangzhou Guangdong 510623 +64-E1-72 (hex) Shenzhen Qihoo Intelligent Technology Co.,Ltd +64E172 (base 16) Shenzhen Qihoo Intelligent Technology Co.,Ltd + Room 201,Block A.No.1,Qianwan Road1 Qianhai Shenzhen-HONGKONG Cooperation Zone + Shenzhen Guangdong 5181000 CN -F4-D6-20 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -F4D620 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +C4-D8-F3 (hex) iZotope +C4D8F3 (base 16) iZotope + 60 Hampshire St + Cambridge MA 02139 + US + +A8-40-25 (hex) Oxide Computer Company +A84025 (base 16) Oxide Computer Company + 1251 Park Avenue + Emeryville CA 94608 + US + +60-F4-3A (hex) Edifier International +60F43A (base 16) Edifier International + Suit 2207, 22nd floor, Tower II, Lippo centre, 89 Queensway + Hong Kong 070 CN -94-90-34 (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD -949034 (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD - Unit East Block22-24/F,Skyworth semiconductor design Bldg., Gaoxin Ave.4.S.,Nanshan District,Shenzhen,China - SHENZHEN GUANGDONG 518057 +FC-B6-98 (hex) Cambridge Industries(Group) Co.,Ltd. +FCB698 (base 16) Cambridge Industries(Group) Co.,Ltd. + 22 Floor,Qilai Tower;889 Yishan Road + Shanghai CHINA 200233 CN -3C-8F-06 (hex) Shenzhen Libtor Technology Co.,Ltd -3C8F06 (base 16) Shenzhen Libtor Technology Co.,Ltd - Room 608, Building A - Shenzhen City Guangdong Province 518000 +04-21-44 (hex) Sunitec Enterprise Co.,Ltd +042144 (base 16) Sunitec Enterprise Co.,Ltd + 3F.,No.98-1,Mincyuan Rd.Sindian City + Taipei County 231 231141 CN -98-7A-10 (hex) Ericsson AB -987A10 (base 16) Ericsson AB - Torshamnsgatan 36 - Stockholm SE-164 80 +4C-33-29 (hex) Sweroam +4C3329 (base 16) Sweroam + Stortorget 16 + Orebro N/A 70211 SE -20-26-81 (hex) TECNO MOBILE LIMITED -202681 (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 - Hong Kong Hong Kong 999077 - HK +78-2B-46 (hex) Intel Corporate +782B46 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -A0-D8-07 (hex) Huawei Device Co., Ltd. -A0D807 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +78-57-73 (hex) HUAWEI TECHNOLOGIES CO.,LTD +785773 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -2C-78-0E (hex) Huawei Device Co., Ltd. -2C780E (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +AC-60-89 (hex) HUAWEI TECHNOLOGIES CO.,LTD +AC6089 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -34-B2-0A (hex) Huawei Device Co., Ltd. -34B20A (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +84-3E-92 (hex) HUAWEI TECHNOLOGIES CO.,LTD +843E92 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -98-F4-AB (hex) Espressif Inc. -98F4AB (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +70-8C-B6 (hex) HUAWEI TECHNOLOGIES CO.,LTD +708CB6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -D8-BF-C0 (hex) Espressif Inc. -D8BFC0 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +50-46-4A (hex) HUAWEI TECHNOLOGIES CO.,LTD +50464A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -B0-08-75 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B00875 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +C4-A4-02 (hex) HUAWEI TECHNOLOGIES CO.,LTD +C4A402 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -64-FB-92 (hex) PPC Broadband Inc. -64FB92 (base 16) PPC Broadband Inc. - 6176 E Molloy Rd - East Syracuse NY 13057 +68-33-2C (hex) KENSTEL NETWORKS LIMITED +68332C (base 16) KENSTEL NETWORKS LIMITED + 34D SECTOR 57 HSIIDC INDUSTRIAL AREA PHASE 4 + KUNDLI HARYANA 131028 + IN + +9C-BD-6E (hex) DERA Co., Ltd +9CBD6E (base 16) DERA Co., Ltd + Zhichun road NO7 Building B Room1203 Haidian District + Beijing 100191 + CN + +00-06-B3 (hex) Diagraph Corporation +0006B3 (base 16) Diagraph Corporation + 3401 Rider Trail South + Earth City MO 63045-1110 US -14-13-46 (hex) Skyworth Digital Technology(Shenzhen) Co.,Ltd -141346 (base 16) Skyworth Digital Technology(Shenzhen) Co.,Ltd - 7F,Block A,Skyworth Building, - Shenzhen Guangdong 518057 +D8-78-7F (hex) Ubee Interactive Co., Limited +D8787F (base 16) Ubee Interactive Co., Limited + Flat/RM 1202, 12/F, AT Tower, 180 Electric Road + North Point 00000 + HK + +DC-33-3D (hex) Huawei Device Co., Ltd. +DC333D (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -54-2B-DE (hex) New H3C Technologies Co., Ltd -542BDE (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +28-54-71 (hex) Huawei Device Co., Ltd. +285471 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -4C-90-DB (hex) JL Audio -4C90DB (base 16) JL Audio - 10369 N Commerce Pkwy - Mirimar FL 33025 - US +B8-8E-82 (hex) Huawei Device Co., Ltd. +B88E82 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -8C-F1-12 (hex) Motorola Mobility LLC, a Lenovo Company -8CF112 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 +34-E5-EC (hex) Palo Alto Networks +34E5EC (base 16) Palo Alto Networks + 3000 Tannery Way + Santa Clara CA 95054 US -B8-99-AE (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd -B899AE (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd - Chudong science and technology park, 111 shaxin road, tangxia town, - dongguan city guangdong province 523710 - CN +08-87-C6 (hex) INGRAM MICRO SERVICES +0887C6 (base 16) INGRAM MICRO SERVICES + 100 CHEMIN DE BAILLOT + MONTAUBAN 82000 + FR -E8-D0-B9 (hex) Taicang T&W Electronics -E8D0B9 (base 16) Taicang T&W Electronics - 89# Jiang Nan RD - Suzhou Jiangsu 215412 +D4-D2-D6 (hex) FN-LINK TECHNOLOGY LIMITED +D4D2D6 (base 16) FN-LINK TECHNOLOGY LIMITED + A Building,HuiXin industial park,No 31, YongHe road, Fuyong town, Bao'an District + SHENZHEN GUANGDONG 518100 CN -58-85-A2 (hex) Realme Chongqing MobileTelecommunications Corp Ltd -5885A2 (base 16) Realme Chongqing MobileTelecommunications Corp Ltd - No.24 Nichang Boulevard, Huixing Block, Yubei District, Chongqing. - Chongqing China 401120 - CN +10-50-72 (hex) Sercomm Corporation. +105072 (base 16) Sercomm Corporation. + 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen + Miao-Lih Hsuan 115 + TW -A8-C0-EA (hex) Pepwave Limited -A8C0EA (base 16) Pepwave Limited - A5, 5/F, HK Spinners Industrial Building, Phase 6, 481 Castle Peak Road - Cheung Sha Wan Hong Kong 0 - HK +98-FA-A7 (hex) INNONET +98FAA7 (base 16) INNONET + C-417, Munjeong Hyundai Knowledge Industry Center, Beobwon-ro 11-gil-7 + Songpa-gu Seoul 05836 + KR -44-AE-AB (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -44AEAB (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +08-6B-D1 (hex) Shenzhen SuperElectron Technology Co.,Ltd. +086BD1 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. + 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city + Shenzhen Guangdong 518000 CN -A4-F0-5E (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -A4F05E (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +90-5D-7C (hex) New H3C Technologies Co., Ltd +905D7C (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -00-07-1C (hex) AT&T -00071C (base 16) AT&T - 3300 E Renner Road - Richardson TX 75082 +3C-B5-3D (hex) HUNAN GOKE MICROELECTRONICS CO.,LTD +3CB53D (base 16) HUNAN GOKE MICROELECTRONICS CO.,LTD + No.9, East 10th Road(South), Xingsha, Changsha + Changsha HUNAN 410131 + CN + +AC-3A-67 (hex) Cisco Systems, Inc +AC3A67 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -84-76-37 (hex) HUAWEI TECHNOLOGIES CO.,LTD -847637 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +00-B7-A8 (hex) Heinzinger electronic GmbH +00B7A8 (base 16) Heinzinger electronic GmbH + Anton Jakob Str.4 + Rosenheim BY 83026 + DE + +34-CF-F6 (hex) Intel Corporate +34CFF6 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +B8-E3-B1 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B8E3B1 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -FC-94-35 (hex) HUAWEI TECHNOLOGIES CO.,LTD -FC9435 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +98-0D-51 (hex) Huawei Device Co., Ltd. +980D51 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -E0-24-81 (hex) HUAWEI TECHNOLOGIES CO.,LTD -E02481 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +00-AD-D5 (hex) Huawei Device Co., Ltd. +00ADD5 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -2C-F8-9B (hex) Cisco Systems, Inc -2CF89B (base 16) Cisco Systems, Inc +EC-79-49 (hex) FUJITSU LIMITED +EC7949 (base 16) FUJITSU LIMITED + 403, Kosugi-cho 1-chome, Nakahara-ku + Kawasaki Kanagawa 211-0063 + JP + +AC-4A-67 (hex) Cisco Systems, Inc +AC4A67 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -80-B0-7B (hex) zte corporation -80B07B (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +90-0A-84 (hex) Mellanox Technologies, Inc. +900A84 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US -C8-5A-9F (hex) zte corporation -C85A9F (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +70-CA-97 (hex) Ruckus Wireless +70CA97 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US + +34-51-80 (hex) TCL King Electrical Appliances (Huizhou) Co., Ltd +345180 (base 16) TCL King Electrical Appliances (Huizhou) Co., Ltd + 10F, TCL Multimedia Building, TCL International E City, No.1001 Zhongshanyuan Rd., Nanshan District + Shenzhen Guangdong 518052 CN -50-02-91 (hex) Espressif Inc. -500291 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +6C-D9-4C (hex) vivo Mobile Communication Co., Ltd. +6CD94C (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN -00-1D-DF (hex) Sunitec Enterprise Co.,Ltd -001DDF (base 16) Sunitec Enterprise Co.,Ltd - 3F., No. 98-1, Mincyuan Rd. - Sindian City Taipei County 231 - TW +AC-1E-D0 (hex) Temic Automotive Philippines Inc. +AC1ED0 (base 16) Temic Automotive Philippines Inc. + Bagsakan Road, FTI estate + Taguig 1630 + PH -8C-0F-FA (hex) Hutec co.,ltd -8C0FFA (base 16) Hutec co.,ltd - 46, Gunpocheomdansaneop 2-ro - Gunpo-si Gyeonggi-do 15880 +00-88-BA (hex) NC&C +0088BA (base 16) NC&C + Gurogu + Seoul 08390 KR -AC-FE-05 (hex) ITEL MOBILE LIMITED -ACFE05 (base 16) ITEL MOBILE LIMITED - RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K - Hong Kong KOWLOON 999077 - HK - -BC-C3-1B (hex) Kygo Life A -BCC31B (base 16) Kygo Life A - Sjolyst Plass 3 - Oslo 0278 - NO +4C-A6-4D (hex) Cisco Systems, Inc +4CA64D (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -64-DF-10 (hex) JingLue Semiconductor(SH) Ltd. -64DF10 (base 16) JingLue Semiconductor(SH) Ltd. - No.800 Naxian Rd - Shanghai 201210 - CN +40-2B-69 (hex) Kumho Electric Inc. +402B69 (base 16) Kumho Electric Inc. + 309, Bongmu-ro, Namsa-myeon, Cheoin-gu + Yongin-si Gyeonggi-do 17118 + KR -10-01-77 (hex) HUAWEI TECHNOLOGIES CO.,LTD -100177 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +A4-08-01 (hex) Amazon Technologies Inc. +A40801 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -44-A1-91 (hex) HUAWEI TECHNOLOGIES CO.,LTD -44A191 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +A8-A0-97 (hex) ScioTeq bvba +A8A097 (base 16) ScioTeq bvba + President Kennedypark 35A + Kortrijk 8500 + BE -24-52-6A (hex) Zhejiang Dahua Technology Co., Ltd. -24526A (base 16) Zhejiang Dahua Technology Co., Ltd. - No.1199,Waterfront Road - Hangzhou Zhejiang 310053 - CN +F4-73-35 (hex) Logitech Far East +F47335 (base 16) Logitech Far East + #2 Creation Rd. 4, + Hsinchu 300 + TW -20-DF-B9 (hex) Google, Inc. -20DFB9 (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US +90-AD-FC (hex) Telechips, Inc. +90ADFC (base 16) Telechips, Inc. + 19F~23F,Luther Bldg.42, Olympic-ro 35da-gil, Songpa-gu, + Seoul Seoul 05510 + KR -5C-CA-D3 (hex) CHIPSEA TECHNOLOGIES (SHENZHEN) CORP. -5CCAD3 (base 16) CHIPSEA TECHNOLOGIES (SHENZHEN) CORP. - 9F,BLOCK A,GARDEN CITY DIGITAL BUILDING,NO.1079 NANHAI ROAD,NANSHAN DISTRICT - SHEN ZHEN GUANG DONG 518000 +BC-16-95 (hex) zte corporation +BC1695 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -78-2A-79 (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. -782A79 (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. - Phase 3, Bayan Lepas FIZ - Bayan Lepas Penang 11900 - MY +A4-CF-D2 (hex) Ubee Interactive Co., Limited +A4CFD2 (base 16) Ubee Interactive Co., Limited + Flat/RM 1202, 12/F, AT Tower, 180 Electric Road + North Point 00000 + HK -78-65-59 (hex) Sagemcom Broadband SAS -786559 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +E8-E9-8E (hex) SOLAR controls s.r.o. +E8E98E (base 16) SOLAR controls s.r.o. + Brojova 25 + Plzen 32600 + CZ -50-D2-F5 (hex) Beijing Xiaomi Mobile Software Co., Ltd -50D2F5 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +64-F6-BB (hex) Fibocom Wireless Inc. +64F6BB (base 16) Fibocom Wireless Inc. + 5/F,TowerA,Technology Building 2,1057 Nanhai Blvd, Nanshan + Shenzhen 518000 Guangdong CN -1C-68-7E (hex) Shenzhen Qihu Intelligent Technology Company Limited -1C687E (base 16) Shenzhen Qihu Intelligent Technology Company Limited - Room 201, Block A, No.1, Qianwan Road 1,Qianhai Shenzhen HongKong Modern Service Industry Cooperation Zone - Shenzhen Guangdong 518057 - CN +5C-A6-2D (hex) Cisco Systems, Inc +5CA62D (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -C0-36-56 (hex) Fiberhome Telecommunication Technologies Co.,LTD -C03656 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +EC-97-B2 (hex) SUMEC Machinery & Electric Co.,Ltd. +EC97B2 (base 16) SUMEC Machinery & Electric Co.,Ltd. + 198# ChangJiang Road, XuanWu District, 17F, SUMEC Building + Nanjing JiangSu 210018 CN -B0-3E-51 (hex) SKY UK LIMITED -B03E51 (base 16) SKY UK LIMITED - 130 Kings Road - Brentwood Essex 08854 - GB - -28-16-7F (hex) Xiaomi Communications Co Ltd -28167F (base 16) Xiaomi Communications Co Ltd - The Rainbow City of China Resources - NO.68, Qinghe Middle Street Haidian District, Beijing 100085 +28-FA-7A (hex) Zhejiang Tmall Technology Co., Ltd. +28FA7A (base 16) Zhejiang Tmall Technology Co., Ltd. + Ali Center,No.3331 Keyuan South RD (Shenzhen bay), Nanshan District, Shenzhen Guangdong province + Shenzhen GuangDong 518000 CN -08-71-90 (hex) Intel Corporate -087190 (base 16) Intel Corporate +10-05-E1 (hex) Nokia +1005E1 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +BC-54-2F (hex) Intel Corporate +BC542F (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -5C-E8-83 (hex) HUAWEI TECHNOLOGIES CO.,LTD -5CE883 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +84-2E-14 (hex) Silicon Laboratories +842E14 (base 16) Silicon Laboratories + 7000 W. William Cannon Dr. + Austin TX 78735 + US + +00-20-E1 (hex) ALAMAR ELECTRONICS +0020E1 (base 16) ALAMAR ELECTRONICS + 489 DIVISION STREET + CAMPBELL CA 95008 + US + +08-F4-58 (hex) Huawei Device Co., Ltd. +08F458 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -60-23-A4 (hex) Sichuan AI-Link Technology Co., Ltd. -6023A4 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou,Industrial Park - Anzhou,Industrial Park Sichuan 621000 +34-37-94 (hex) Hamee Corp. +343794 (base 16) Hamee Corp. + Square O2 2-12-10 Sakae-cho + Odawara Kanagawa 250-0011 + JP + +D4-DC-09 (hex) Mist Systems, Inc. +D4DC09 (base 16) Mist Systems, Inc. + 1601 South De Anza Blvd, Suite 248 + Cupertino CA 95014 + US + +44-10-FE (hex) Huizhou Foryou General Electronics Co., Ltd. +4410FE (base 16) Huizhou Foryou General Electronics Co., Ltd. + North Shangxia Road, Dongjiang Hi-tech Industry Park + Huizhou Guangdong 516000 CN -A4-53-0E (hex) Cisco Systems, Inc -A4530E (base 16) Cisco Systems, Inc +EC-31-6D (hex) Hansgrohe +EC316D (base 16) Hansgrohe + Auestraße 5-9 + Schiltach 77761 + DE + +CC-7F-75 (hex) Cisco Systems, Inc +CC7F75 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -00-40-3A (hex) IMPACT TECHNOLOGIES -00403A (base 16) IMPACT TECHNOLOGIES - 6 RUE DE L'ACADIE - - FR - -C8-B1-CD (hex) Apple, Inc. -C8B1CD (base 16) Apple, Inc. +20-E8-74 (hex) Apple, Inc. +20E874 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -14-60-CB (hex) Apple, Inc. -1460CB (base 16) Apple, Inc. +D0-3F-AA (hex) Apple, Inc. +D03FAA (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -B8-F1-2A (hex) Apple, Inc. -B8F12A (base 16) Apple, Inc. +7C-AB-60 (hex) Apple, Inc. +7CAB60 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -F8-87-F1 (hex) Apple, Inc. -F887F1 (base 16) Apple, Inc. +44-C6-5D (hex) Apple, Inc. +44C65D (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -60-44-7A (hex) Water-i.d. GmbH -60447A (base 16) Water-i.d. GmbH - Daimlerstr. 20 - Eggenstein 76344 - DE - -80-72-15 (hex) SKY UK LIMITED -807215 (base 16) SKY UK LIMITED - 130 Kings Road - Brentwood Essex 08854 - GB - -74-D6-37 (hex) Amazon Technologies Inc. -74D637 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - -74-84-E1 (hex) Dongguan Haoyuan Electronics Co.,Ltd -7484E1 (base 16) Dongguan Haoyuan Electronics Co.,Ltd - NO.161 Kejizhong RoadLiuwu Shijie Town - Dongguan Guangdong 523290 - CN - -40-74-E0 (hex) Intel Corporate -4074E0 (base 16) Intel Corporate +40-EC-99 (hex) Intel Corporate +40EC99 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -D0-5F-64 (hex) IEEE Registration Authority -D05F64 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - -44-FB-5A (hex) zte corporation -44FB5A (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +F4-A4-D6 (hex) HUAWEI TECHNOLOGIES CO.,LTD +F4A4D6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -C4-63-FB (hex) Neatframe AS -C463FB (base 16) Neatframe AS - Martin Linges Vei 25 - Fornebu Fornebu 1364 - NO - -30-57-14 (hex) Apple, Inc. -305714 (base 16) Apple, Inc. +18-7E-B9 (hex) Apple, Inc. +187EB9 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -04-A2-22 (hex) Arcadyan Corporation -04A222 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +1C-97-C5 (hex) Ynomia Pty Ltd +1C97C5 (base 16) Ynomia Pty Ltd + 153 Tooronga Rd + Glen Iris 3146 + AU -04-AB-6A (hex) Chun-il Co.,Ltd. -04AB6A (base 16) Chun-il Co.,Ltd. - 13-7, Gimhae-daero 2694 beon-gil, - Gimhae-si Gyeongsangnam-do 50936 +5C-C1-D7 (hex) Samsung Electronics Co.,Ltd +5CC1D7 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 KR -00-35-FF (hex) Texas Instruments -0035FF (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +38-01-46 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +380146 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268? Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 + CN + +2C-57-41 (hex) Cisco Systems, Inc +2C5741 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -DC-54-D7 (hex) Amazon Technologies Inc. -DC54D7 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +B0-B3-53 (hex) IEEE Registration Authority +B0B353 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -28-FE-65 (hex) DongGuan Siyoto Electronics Co., Ltd -28FE65 (base 16) DongGuan Siyoto Electronics Co., Ltd - Hecheng Industrial District, QiaoTou Town - DongGuan City Guangdong 523520 +1C-91-9D (hex) Dongguan Liesheng Electronic Co., Ltd. +1C919D (base 16) Dongguan Liesheng Electronic Co., Ltd. + F5, Building B, North Block, Gaosheng Tech Park, No. 84 Zhongli Road, Nancheng District, Dongguan Ci + dongguan guangdong 523000 CN -18-06-F5 (hex) RAD Data Communications, Ltd. -1806F5 (base 16) RAD Data Communications, Ltd. - 24 Raoul Wallenberg St. - Tel Aviv 69719 - IL - -88-9F-AA (hex) Hella Gutmann Solutions GmbH -889FAA (base 16) Hella Gutmann Solutions GmbH - Am Krebsbach 2 - Ihringen Baden Württemberg 79241 - DE - -04-C8-07 (hex) Xiaomi Communications Co Ltd -04C807 (base 16) Xiaomi Communications Co Ltd - The Rainbow City of China Resources - NO.68, Qinghe Middle Street Haidian District, Beijing 100085 - CN +20-11-4E (hex) MeteRSit S.R.L. +20114E (base 16) MeteRSit S.R.L. + Viale dell'Industria 31 + Padova 35129 + IT -44-59-E3 (hex) HUAWEI TECHNOLOGIES CO.,LTD -4459E3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +FC-F2-9F (hex) China Mobile Iot Limited company +FCF29F (base 16) China Mobile Iot Limited company + No. 8 Yangliu North Road, Yubei District, Chongqing, China + Chong Qing Chong Qing 401120 CN -68-3F-1E (hex) EFFECT Photonics B.V. -683F1E (base 16) EFFECT Photonics B.V. - Kastanjelaan 400 - Eindhoven Noord-Brabant 5616 LZ - NL - -44-D3-CA (hex) Cisco Systems, Inc -44D3CA (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +F8-1F-32 (hex) Motorola Mobility LLC, a Lenovo Company +F81F32 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -84-0B-7C (hex) Hitron Technologies. Inc -840B7C (base 16) Hitron Technologies. Inc - No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C - Hsin-chu Taiwan 300 - TW - -48-A7-3C (hex) Sichuan tianyi kanghe communications co., LTD -48A73C (base 16) Sichuan tianyi kanghe communications co., LTD - No.198, section 1, xueshan avenue, jinyuan town, dayi county - chengdu sichuan 611330 - CN +A8-7E-EA (hex) Intel Corporate +A87EEA (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -F8-A7-63 (hex) Zhejiang Tmall Technology Co., Ltd. -F8A763 (base 16) Zhejiang Tmall Technology Co., Ltd. - Ali Center,No.3331 Keyuan South RD (Shenzhen bay), Nanshan District, Shenzhen Guangdong province - Shenzhen GuangDong 518000 +B0-0A-D5 (hex) zte corporation +B00AD5 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -6C-29-90 (hex) WiZ Connected Lighting Company Limited -6C2990 (base 16) WiZ Connected Lighting Company Limited - Room 3805, 148 Electric Road - Hong Kong 0000 0000 - HK +A8-4D-4A (hex) Audiowise Technology Inc. +A84D4A (base 16) Audiowise Technology Inc. + 2F, No 1-1, Innovation RD I, Hsinchu Science Park + Hsincu Taiwan 30076 + TW -98-35-ED (hex) HUAWEI TECHNOLOGIES CO.,LTD -9835ED (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +98-00-6A (hex) zte corporation +98006A (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -80-76-93 (hex) Newag SA -807693 (base 16) Newag SA - Wyspianskiego 3 - Nowy Sacz woj. Malopolskie 33-300 - PL +78-94-E8 (hex) Radio Bridge +7894E8 (base 16) Radio Bridge + 8601 73rd Ave N, Suite 38 + Brooklyn Park MN 55428 + US -BC-97-40 (hex) IEEE Registration Authority -BC9740 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +54-7F-BC (hex) iodyne +547FBC (base 16) iodyne + 35 Miller Ave #175 + Mill Valley CA 94941 US -08-4F-0A (hex) HUAWEI TECHNOLOGIES CO.,LTD -084F0A (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +FC-E1-4F (hex) BRK Brands, Inc. +FCE14F (base 16) BRK Brands, Inc. + 3901 Liberty Street + Aurora IL 60504 + US -A8-49-4D (hex) HUAWEI TECHNOLOGIES CO.,LTD -A8494D (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +88-96-55 (hex) Zitte corporation +889655 (base 16) Zitte corporation + 4F Yokohama Kusunoki-cho Building,4-7 Kusunoki-cho,Nishi-ku + Yokohama Kanagawa 2200003 + JP -44-00-4D (hex) HUAWEI TECHNOLOGIES CO.,LTD -44004D (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +18-30-0C (hex) Hisense Electric Co.,Ltd +18300C (base 16) Hisense Electric Co.,Ltd + Qianwangang Roard 218 + Qingdao Shandong 266510 CN -20-96-8A (hex) China Mobile (Hangzhou) Information Technology Co., Ltd. -20968A (base 16) China Mobile (Hangzhou) Information Technology Co., Ltd. - No. 1600 Yuhangtang Road, Wuchang Street, Yuhang District - Hangzhou Zhejiang 310000 +5C-27-D4 (hex) Shenzhen Qihu Intelligent Technology Company Limited +5C27D4 (base 16) Shenzhen Qihu Intelligent Technology Company Limited + Room 201, Block A, No.1, Qianwan Road 1,Qianhai Shenzhen HongKong Modern Service Industry Cooperation Zone + Shenzhen Guangdong 518057 CN -8C-18-50 (hex) China Mobile (Hangzhou) Information Technology Co., Ltd. -8C1850 (base 16) China Mobile (Hangzhou) Information Technology Co., Ltd. - No. 1600 Yuhangtang Road, Wuchang Street, Yuhang District - Hangzhou Hangzhou 310000 - CN +74-D8-3E (hex) Intel Corporate +74D83E (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -D8-D4-E6 (hex) Hytec Inter Co., Ltd. -D8D4E6 (base 16) Hytec Inter Co., Ltd. - 3-28-6 Yoyogi - Shibuya-ku Tokyo 1510053 - JP +88-B6-EE (hex) Dish Technologies Corp +88B6EE (base 16) Dish Technologies Corp + 94 Inverness Terrace E + Englewood CO 80112 + US -F8-E7-A0 (hex) vivo Mobile Communication Co., Ltd. -F8E7A0 (base 16) vivo Mobile Communication Co., Ltd. - #283,BBK Road - Wusha,Chang'An DongGuan City,Guangdong, 523860 - CN +50-50-A4 (hex) Samsung Electronics Co.,Ltd +5050A4 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -2C-FF-EE (hex) vivo Mobile Communication Co., Ltd. -2CFFEE (base 16) vivo Mobile Communication Co., Ltd. - #283,BBK Road - Wusha,Chang'An DongGuan City,Guangdong, 523860 - CN +08-F8-BC (hex) Apple, Inc. +08F8BC (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -C8-5D-38 (hex) HUMAX Co., Ltd. -C85D38 (base 16) HUMAX Co., Ltd. - HUMAX Village, 216, Hwangsaeul-ro, Bu - Seongnam-si Gyeonggi-do 463-875 - KR +90-A2-5B (hex) Apple, Inc. +90A25B (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -18-CF-24 (hex) HUAWEI TECHNOLOGIES CO.,LTD -18CF24 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +88-A4-79 (hex) Apple, Inc. +88A479 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -50-F8-A5 (hex) eWBM Co., Ltd. -50F8A5 (base 16) eWBM Co., Ltd. - 14F, 9, Teheran-ro 20-gil - Gangnam-gu, Seoul 06236 - KR +04-72-95 (hex) Apple, Inc. +047295 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -D8-9B-3B (hex) HUAWEI TECHNOLOGIES CO.,LTD -D89B3B (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +D4-46-E1 (hex) Apple, Inc. +D446E1 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -88-40-3B (hex) HUAWEI TECHNOLOGIES CO.,LTD -88403B (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +14-14-59 (hex) Vodafone Italia S.p.A. +141459 (base 16) Vodafone Italia S.p.A. + Via Lorenteggio nr. 240 + Milan Italy 20147 + IT -FC-87-43 (hex) HUAWEI TECHNOLOGIES CO.,LTD -FC8743 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +50-43-B9 (hex) OktoInform RUS +5043B9 (base 16) OktoInform RUS + Bolshoy Tishinskiy pereulok, d. 26, korp.13-14, ofis 4R + Moscow 123557 + RU -04-88-5F (hex) HUAWEI TECHNOLOGIES CO.,LTD -04885F (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +88-54-1F (hex) Google, Inc. +88541F (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US -C8-50-CE (hex) HUAWEI TECHNOLOGIES CO.,LTD -C850CE (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +90-0C-C8 (hex) Google, Inc. +900CC8 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US -00-0B-E4 (hex) Hosiden Corporation -000BE4 (base 16) Hosiden Corporation - 4-33 - Yao-city Osaka 581-0071 - JP +00-10-4F (hex) Oracle Corporation +00104F (base 16) Oracle Corporation + 500 Oracle Parkway + Redwood Shores CA 94065 + US -44-13-D0 (hex) zte corporation -4413D0 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +08-D2-3E (hex) Intel Corporate +08D23E (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -24-62-AB (hex) Espressif Inc. -2462AB (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +48-7B-5E (hex) SMT TELECOMM HK +487B5E (base 16) SMT TELECOMM HK + Unit C 8/F Charmhill Centre 50 Hillwood RD. + Tsim Sha Tsui Kowloon 999077 + HK + +00-25-A9 (hex) Shanghai Embedway Information Technologies Co.,Ltd +0025A9 (base 16) Shanghai Embedway Information Technologies Co.,Ltd + 2F,Building 9,Lujiazui Software Park, No.20,Lane 91,E'Shan Road + Shanghai 200127 CN -14-49-BC (hex) DrayTek Corp. -1449BC (base 16) DrayTek Corp. - No. 26, Fushing Rd., Hukou, Hsinchu Industrial Park, - Hsinchu county 30352 +68-21-5F (hex) Edgecore Networks Corporation +68215F (base 16) Edgecore Networks Corporation + 1 Creation RD 3. + Hsinchu 30077 TW -20-F4-78 (hex) Xiaomi Communications Co Ltd -20F478 (base 16) Xiaomi Communications Co Ltd - The Rainbow City of China Resources - NO.68, Qinghe Middle Street Haidian District, Beijing 100085 - CN - -74-38-B7 (hex) CANON INC. -7438B7 (base 16) CANON INC. - 30-2 Shimomaruko 3-chome - Ohta-ku Tokyo 146-8501 - JP +3C-BF-60 (hex) Apple, Inc. +3CBF60 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -90-73-5A (hex) Motorola Mobility LLC, a Lenovo Company -90735A (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 +AC-15-F4 (hex) Apple, Inc. +AC15F4 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-04-DF (hex) TERACOM TELEMATICA S.A -0004DF (base 16) TERACOM TELEMATICA S.A - RUA AMERICA N.1000 - Eldorado do Sul - RS Brazil - BR +78-D1-62 (hex) Apple, Inc. +78D162 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -18-B6-F7 (hex) NEW POS TECHNOLOGY LIMITED -18B6F7 (base 16) NEW POS TECHNOLOGY LIMITED - AB Unit, 14th Floor,Block A, Financial Technology Building.No. 11 Keyuan Rd - Shenzhen 518057 +44-C7-FC (hex) Huawei Device Co., Ltd. +44C7FC (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -1C-82-59 (hex) IEEE Registration Authority -1C8259 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +78-85-F4 (hex) Huawei Device Co., Ltd. +7885F4 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -00-FA-21 (hex) Samsung Electronics Co.,Ltd -00FA21 (base 16) Samsung Electronics Co.,Ltd +80-86-D9 (hex) Samsung Electronics Co.,Ltd +8086D9 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -7C-23-02 (hex) Samsung Electronics Co.,Ltd -7C2302 (base 16) Samsung Electronics Co.,Ltd +38-6A-77 (hex) Samsung Electronics Co.,Ltd +386A77 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -6C-AB-05 (hex) Cisco Systems, Inc -6CAB05 (base 16) Cisco Systems, Inc +00-B8-10 (hex) Yichip Microelectronics (Hangzhou) Co.,Ltd +00B810 (base 16) Yichip Microelectronics (Hangzhou) Co.,Ltd + Room 401, Building 15, No.498 Guoshoujing Road, Pudong Software Park + Shanghai 200120 + CN + +A4-B2-39 (hex) Cisco Systems, Inc +A4B239 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -5C-B1-5F (hex) Oceanblue Cloud Technology Limited -5CB15F (base 16) Oceanblue Cloud Technology Limited - 253-261 Hennessy Road - Hong Kong Hong Kong 999077 - HK +54-8D-5A (hex) Intel Corporate +548D5A (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -48-5D-EB (hex) Just Add Power -485DEB (base 16) Just Add Power - 12505 STARKEY RD STE A - LARGO FL 33773 - US +F4-49-55 (hex) MIMO TECH Co., Ltd. +F44955 (base 16) MIMO TECH Co., Ltd. + 21F.-6, No. 7, Sec. 3, New Taipei Blvd., Xinzhuang Dist., + New Taipei City Taiwan 24250 + TW -50-13-95 (hex) Sichuan AI-Link Technology Co., Ltd. -501395 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou,Industrial Park - Anzhou,Industrial Park Sichuan 621000 +08-09-C7 (hex) Zhuhai Unitech Power Technology Co., Ltd. +0809C7 (base 16) Zhuhai Unitech Power Technology Co., Ltd. + 102, Yinhua Road + Zhuhai Guangdong 519000 CN -18-D9-EF (hex) Shuttle Inc. -18D9EF (base 16) Shuttle Inc. - No. 30 Lane 76, Rei Kuang Rd - Taipei 114 - TW +58-96-1D (hex) Intel Corporate +58961D (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -88-DA-33 (hex) Beijing Xiaoyuer Network Technology Co., Ltd -88DA33 (base 16) Beijing Xiaoyuer Network Technology Co., Ltd - Block K1, North American International Business Centre, 86 Beiyuan Road, Chaoyang District - Beijing Beijing 100012 +68-AF-FF (hex) Shanghai Cambricon Information Technology Co., Ltd. +68AFFF (base 16) Shanghai Cambricon Information Technology Co., Ltd. + 888 West Huanhu Road No.2, Nanhui New Town, Pudong New Area + Shanghai Shanghai 200000 CN -84-C7-8F (hex) APS Networks GmbH -84C78F (base 16) APS Networks GmbH - Rosenwiesstr. 17 - Stuttgart 70567 - DE +D0-1C-3C (hex) TECNO MOBILE LIMITED +D01C3C (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 + Hong Kong Hong Kong 999077 + HK -C0-9F-E1 (hex) zte corporation -C09FE1 (base 16) zte corporation +04-1D-C7 (hex) zte corporation +041DC7 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China shenzhen guangdong 518057 CN -18-AA-CA (hex) Sichuan tianyi kanghe communications co., LTD -18AACA (base 16) Sichuan tianyi kanghe communications co., LTD - No.198, section 1, xueshan avenue, jinyuan town, dayi county, sichuan province - chengdu sichuan 611330 - CN - -D4-9D-C0 (hex) Samsung Electronics Co.,Ltd -D49DC0 (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR +00-1A-4D (hex) GIGA-BYTE TECHNOLOGY CO.,LTD. +001A4D (base 16) GIGA-BYTE TECHNOLOGY CO.,LTD. + Pin-Jen City, Taoyuan, Taiwan, R.O.C. + Pin-Jen Taoyuan 324 + TW -D0-19-6A (hex) Ciena Corporation -D0196A (base 16) Ciena Corporation - 7035 Ridge Road - Hanover MD 21076 - US +00-1F-D0 (hex) GIGA-BYTE TECHNOLOGY CO.,LTD. +001FD0 (base 16) GIGA-BYTE TECHNOLOGY CO.,LTD. + Pin-Jen City, Taoyuan, Taiwan, R.O.C. + Pin-Jen Taoyuan 324 + TW -84-FD-D1 (hex) Intel Corporate -84FDD1 (base 16) Intel Corporate +B8-9A-2A (hex) Intel Corporate +B89A2A (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -B0-70-0D (hex) Nokia -B0700D (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +DC-21-E2 (hex) HUAWEI TECHNOLOGIES CO.,LTD +DC21E2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -00-21-75 (hex) Pacific Satellite International Ltd. -002175 (base 16) Pacific Satellite International Ltd. - 20/F Tai Tung Building, - Wanchai 100000 - HK +FC-1B-D1 (hex) HUAWEI TECHNOLOGIES CO.,LTD +FC1BD1 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -34-6B-5B (hex) New H3C Technologies Co., Ltd -346B5B (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +58-25-75 (hex) HUAWEI TECHNOLOGIES CO.,LTD +582575 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -84-E8-92 (hex) Actiontec Electronics, Inc -84E892 (base 16) Actiontec Electronics, Inc - 301 Olcott St - Santa Clara CA 95054 +28-DE-E5 (hex) HUAWEI TECHNOLOGIES CO.,LTD +28DEE5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +18-C0-4D (hex) GIGA-BYTE TECHNOLOGY CO.,LTD. +18C04D (base 16) GIGA-BYTE TECHNOLOGY CO.,LTD. + Pin-Jen City, Taoyuan, Taiwan, R.O.C. + Pin-Jen Taoyuan 324 + TW + +40-2C-76 (hex) IEEE Registration Authority +402C76 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -78-E2-BD (hex) Vodafone Automotive S.p.A. -78E2BD (base 16) Vodafone Automotive S.p.A. - via Astico 41 - Varese Italy/VA 21100 - IT +80-C9-55 (hex) Redpine Signals, Inc. +80C955 (base 16) Redpine Signals, Inc. + Plot 87, Sagar Society + Hyderabad AP 500034 + IN -F8-48-FD (hex) China Mobile Group Device Co.,Ltd. -F848FD (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +5C-68-D0 (hex) Aurora Innovation Inc. +5C68D0 (base 16) Aurora Innovation Inc. + 1880 Embarcadero Rd. + Palo Alto CA 94303 + US + +10-36-4A (hex) Boston Dynamics +10364A (base 16) Boston Dynamics + 78 4TH AVE + Waltham MA 02451 + US + +00-0B-CC (hex) JUSAN, S.A. +000BCC (base 16) JUSAN, S.A. + Vivero, 5 + MADRID 28040 + ES + +00-E0-59 (hex) CONTROLLED ENVIRONMENTS, LTD. +00E059 (base 16) CONTROLLED ENVIRONMENTS, LTD. + 590 BERRY STREET + WINNEPEG R3H OR9 + CA + +0C-8E-29 (hex) Arcadyan Corporation +0C8E29 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW + +A0-22-4E (hex) IEEE Registration Authority +A0224E (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +C4-98-78 (hex) SHANGHAI MOAAN INTELLIGENT TECHNOLOGY CO.,LTD +C49878 (base 16) SHANGHAI MOAAN INTELLIGENT TECHNOLOGY CO.,LTD + BLOCK B, 4TH FLOOR, BUILDING 2, NO. 401 CAOBAO ROAD, XUHUI DISTRICT, SHANGHAI + SHANGHAI 200030 CN -50-41-B9 (hex) I-O DATA DEVICE,INC. -5041B9 (base 16) I-O DATA DEVICE,INC. - 3-10,Sakurada-machi - Kanazawa Ishikawa 920-8512 - JP +00-0A-23 (hex) Parama Networks Inc +000A23 (base 16) Parama Networks Inc + 1955 The Alameda + San Jose CA 95126 + US -80-DA-BC (hex) Megafone Limited -80DABC (base 16) Megafone Limited - Unit 702,7/F,Bankok Bank Building,NO.18 Bonham Strand West - Hong Kong 999077 - HK +38-43-E5 (hex) Grotech Inc +3843E5 (base 16) Grotech Inc + 19, Ojeongongeop-gil + Uiwang-si Gyeonggi-do 16072 + KR -20-DA-22 (hex) HUAWEI TECHNOLOGIES CO.,LTD -20DA22 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +6C-06-D6 (hex) Huawei Device Co., Ltd. +6C06D6 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -18-46-44 (hex) Home Control Singapore Pte Ltd -184644 (base 16) Home Control Singapore Pte Ltd - 151 Lorong Chuan - Singapore 556741 - SG +18-E7-77 (hex) vivo Mobile Communication Co., Ltd. +18E777 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN -C8-21-DA (hex) Shenzhen YOUHUA Technology Co., Ltd -C821DA (base 16) Shenzhen YOUHUA Technology Co., Ltd - Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District - Shenzhen Guangdong 518055 +0C-7A-15 (hex) Intel Corporate +0C7A15 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +94-D6-DB (hex) NexFi +94D6DB (base 16) NexFi + Room 417, Building 14, No. 498, Guoshoujing Road, Pudong New Area + Shanghai 201203 CN -20-65-8E (hex) HUAWEI TECHNOLOGIES CO.,LTD -20658E (base 16) HUAWEI TECHNOLOGIES CO.,LTD +78-B4-6A (hex) HUAWEI TECHNOLOGIES CO.,LTD +78B46A (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -18-3D-5E (hex) HUAWEI TECHNOLOGIES CO.,LTD -183D5E (base 16) HUAWEI TECHNOLOGIES CO.,LTD +6C-EB-B6 (hex) HUAWEI TECHNOLOGIES CO.,LTD +6CEBB6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -88-97-46 (hex) Sichuan AI-Link Technology Co., Ltd. -889746 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou,Industrial Park - Anzhou,Industrial Park Sichuan 621000 +4C-F5-5B (hex) HUAWEI TECHNOLOGIES CO.,LTD +4CF55B (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -1C-DE-57 (hex) Fiberhome Telecommunication Technologies Co.,LTD -1CDE57 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN +64-31-39 (hex) IEEE Registration Authority +643139 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -E0-DC-FF (hex) Xiaomi Communications Co Ltd -E0DCFF (base 16) Xiaomi Communications Co Ltd +A4-4B-D5 (hex) Xiaomi Communications Co Ltd +A44BD5 (base 16) Xiaomi Communications Co Ltd The Rainbow City of China Resources NO.68, Qinghe Middle Street Haidian District, Beijing 100085 CN -00-77-8D (hex) Cisco Systems, Inc -00778D (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +14-87-6A (hex) Apple, Inc. +14876A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-0E-8C (hex) Siemens AG -000E8C (base 16) Siemens AG - Siemensstraße 10 - Regensburg 93055 - DE +E0-B5-5F (hex) Apple, Inc. +E0B55F (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -00-87-64 (hex) Cisco Systems, Inc -008764 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +F8-FF-C2 (hex) Apple, Inc. +F8FFC2 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -84-6F-CE (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -846FCE (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN +E0-EB-40 (hex) Apple, Inc. +E0EB40 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -B0-E7-1D (hex) Shanghai Maigantech Co.,Ltd -B0E71D (base 16) Shanghai Maigantech Co.,Ltd - Room 2211,No.88 Caoxi North Rd,Xuhui District - Shanghai Shanghai 200030 +40-77-A9 (hex) New H3C Technologies Co., Ltd +4077A9 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -C4-68-D0 (hex) VTech Telecommunications Ltd. -C468D0 (base 16) VTech Telecommunications Ltd. - 23/F, Tai Ping Industrial Centre, Block 1, - HONG KONG NA 000000 - HK - -14-AE-DB (hex) VTech Telecommunications Ltd. -14AEDB (base 16) VTech Telecommunications Ltd. - 23/F, Tai Ping Industrial Centre, Block 1, - HONG KONG NA 000000 - HK - -78-DB-2F (hex) Texas Instruments -78DB2F (base 16) Texas Instruments +64-69-4E (hex) Texas Instruments +64694E (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -E0-B6-55 (hex) Beijing Xiaomi Electronics Co., Ltd. -E0B655 (base 16) Beijing Xiaomi Electronics Co., Ltd. - Building C, QingHe ShunShiJiaYe Technology Park, #66 ZhuFang Rd, HaiDian District - Beijing Beijing 10085 - CN - -8C-0F-A0 (hex) di-soric GmbH & Co. KG -8C0FA0 (base 16) di-soric GmbH & Co. KG - Steinbeisstrasse 6 - Urbach 73660 - DE +F8-33-31 (hex) Texas Instruments +F83331 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -DC-FB-48 (hex) Intel Corporate -DCFB48 (base 16) Intel Corporate +40-A6-B7 (hex) Intel Corporate +40A6B7 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -DC-71-37 (hex) zte corporation -DC7137 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +B4-EC-F2 (hex) Shanghai Listent Medical Tech Co., Ltd. +B4ECF2 (base 16) Shanghai Listent Medical Tech Co., Ltd. + No. 668 Qingdai Road Pudong District + Shanghai Shanghai 201318 CN -84-7C-9B (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -847C9B (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN +C4-95-4D (hex) IEEE Registration Authority +C4954D (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -34-41-A8 (hex) ER-Telecom -3441A8 (base 16) ER-Telecom - Ovchinnikovskaya embankment, 20, Building 1 - Moscow 115324 - RU +64-95-6C (hex) LG Electronics +64956C (base 16) LG Electronics + 222 LG-ro, JINWI-MYEON + Pyeongtaek-si Gyeonggi-do 451-713 + KR -34-DB-9C (hex) Sagemcom Broadband SAS -34DB9C (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +E8-3F-67 (hex) Huawei Device Co., Ltd. +E83F67 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -74-40-BE (hex) LG Innotek -7440BE (base 16) LG Innotek - 26, Hanamsandan 5beon-ro - Gwangju Gwangsan-gu 506-731 - KR +34-46-EC (hex) Huawei Device Co., Ltd. +3446EC (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -A4-CF-12 (hex) Espressif Inc. -A4CF12 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +1C-BF-C0 (hex) CHONGQING FUGUI ELECTRONICS CO.,LTD. +1CBFC0 (base 16) CHONGQING FUGUI ELECTRONICS CO.,LTD. + Building D21,No.1, East Zone 1st Road,Xiyong Town,Shapingba District + Chongqing Chongqing 401332 CN -FC-B6-62 (hex) IC Holdings LLC -FCB662 (base 16) IC Holdings LLC - 1277 Windmill Ln. - Midway UT 84049 +80-16-09 (hex) Sleep Number +801609 (base 16) Sleep Number + 1001 Third Avenue South + Minneapolis MN 55404 US -04-D4-C4 (hex) ASUSTek COMPUTER INC. -04D4C4 (base 16) ASUSTek COMPUTER INC. - 15,Li-Te Rd., Peitou, Taipei 112, Taiwan - Taipei Taiwan 112 - TW +AC-8B-9C (hex) Primera Technology, Inc. +AC8B9C (base 16) Primera Technology, Inc. + 2 Carlson Parkway N, Ste 375 + Plymouth MN 55447 + US -A0-51-0B (hex) Intel Corporate -A0510B (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +1C-C1-BC (hex) Yichip Microelectronics (Hangzhou) Co.,Ltd +1CC1BC (base 16) Yichip Microelectronics (Hangzhou) Co.,Ltd + Room 401, Building 15, No.498 Guoshoujing Road, Pudong Software Park + Shanghai 200120 + CN -74-88-BB (hex) Cisco Systems, Inc -7488BB (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +2C-3A-FD (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +2C3AFD (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +AC-61-B9 (hex) WAMA Technology Limited +AC61B9 (base 16) WAMA Technology Limited + Room 2205, Westley Square, 48 Hoi Yuen Road, Kwun Tong, Kowloon + Hong Kong 00000 + HK + +F8-55-CD (hex) Visteon Corporation +F855CD (base 16) Visteon Corporation + One Village Center Drive + Van Buren Twp MI 48111 US -A8-E2-C3 (hex) Shenzhen YOUHUA Technology Co., Ltd -A8E2C3 (base 16) Shenzhen YOUHUA Technology Co., Ltd - Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District - Shenzhen Guangdong 518055 +44-18-47 (hex) HUNAN SCROWN ELECTRONIC INFORMATION TECH.CO.,LTD +441847 (base 16) HUNAN SCROWN ELECTRONIC INFORMATION TECH.CO.,LTD + Building No.4,Changsha Zhongdian Software Park No.39,Jianshan Road + Changsha Hunan 410006 CN -0C-A0-6C (hex) Industrial Cyber Sensing Inc. -0CA06C (base 16) Industrial Cyber Sensing Inc. - Unit 1A - 343 Montrose Street North - Cambridge Ontario N3H 2H6 - CA - -FC-D2-B6 (hex) IEEE Registration Authority -FCD2B6 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +00-30-B7 (hex) Teletrol Systems, Inc. +0030B7 (base 16) Teletrol Systems, Inc. + Technology Center + Manchester NH 03101 US -80-4A-14 (hex) Apple, Inc. -804A14 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +C8-F3-19 (hex) LG Electronics (Mobile Communications) +C8F319 (base 16) LG Electronics (Mobile Communications) + 60-39, Gasan-dong, Geumcheon-gu + Seoul 153-801 + KR + +00-90-96 (hex) ASKEY COMPUTER CORP +009096 (base 16) ASKEY COMPUTER CORP + 2F, NO. 2, LANE 497 + TAIPEI 23136 12345 + TW + +24-74-F7 (hex) GoPro +2474F7 (base 16) GoPro + 3000 Clearview Way + San Mateo CA 94402 US -70-3C-69 (hex) Apple, Inc. -703C69 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +7C-D5-66 (hex) Amazon Technologies Inc. +7CD566 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -AC-2D-A9 (hex) TECNO MOBILE LIMITED -AC2DA9 (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 - Hong Kong Hong Kong 999077 - HK +68-63-50 (hex) Hella India Automotive Pvt Ltd +686350 (base 16) Hella India Automotive Pvt Ltd + Unit no 201A to 201B Nano Space Surveyno.5/1B/2 BanerBaner Pashan Link road + Pune Maharastra 411045 + IN -40-A9-3F (hex) Pivotal Commware, Inc. -40A93F (base 16) Pivotal Commware, Inc. - 1555 132nd Ave. NE - Bellevue WA 98005 - US +18-70-3B (hex) Huawei Device Co., Ltd. +18703B (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -4C-6A-F6 (hex) HMD Global Oy -4C6AF6 (base 16) HMD Global Oy - Bertel Jungin aukio 9 - Espoo 02600 - FI +44-22-95 (hex) China Mobile Iot Limited company +442295 (base 16) China Mobile Iot Limited company + No. 8 Yangliu North Road, Yubei District, Chongqing, China + Chong Qing Chong Qing 401120 + CN -48-9D-D1 (hex) Samsung Electronics Co.,Ltd -489DD1 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +E8-5A-8B (hex) Xiaomi Communications Co Ltd +E85A8B (base 16) Xiaomi Communications Co Ltd + The Rainbow City of China Resources + NO.68, Qinghe Middle Street Haidian District, Beijing 100085 + CN -C0-8C-71 (hex) Motorola Mobility LLC, a Lenovo Company -C08C71 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +80-75-1F (hex) SKY UK LIMITED +80751F (base 16) SKY UK LIMITED + 130 Kings Road + Brentwood Essex 08854 + GB -48-04-9F (hex) ELECOM CO., LTD -48049F (base 16) ELECOM CO., LTD - 9FLand Axis Tower.1-1 fushimi machi,4-chome chuoku - osaka 5418765 - JP +D8-9E-61 (hex) Huawei Device Co., Ltd. +D89E61 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -08-7F-98 (hex) vivo Mobile Communication Co., Ltd. -087F98 (base 16) vivo Mobile Communication Co., Ltd. - #283,BBK Road - Wusha,Chang'An DongGuan City,Guangdong, 523860 +34-7E-00 (hex) Huawei Device Co., Ltd. +347E00 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -C0-41-21 (hex) Nokia Solutions and Networks GmbH & Co. KG -C04121 (base 16) Nokia Solutions and Networks GmbH & Co. KG - Werinherstrasse 91 - München Bavaria D-81541 - DE +5C-E5-0C (hex) Beijing Xiaomi Mobile Software Co., Ltd +5CE50C (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN -F4-6F-4E (hex) Echowell -F46F4E (base 16) Echowell - 7F-8, No. 8, Sec 1, JunShing Rd. - New Taipei City 24872 +5C-71-0D (hex) Cisco Systems, Inc +5C710D (base 16) Cisco Systems, Inc + 80 West Tasman Dr. + San Jose CA 94568 + US + +98-AF-65 (hex) Intel Corporate +98AF65 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +F8-89-3C (hex) Inventec Appliances Corp. +F8893C (base 16) Inventec Appliances Corp. + 37 Wugong 5th road, New Taipei Industrial Park, + New Taipei City Wugu District 24890 TW -B0-6F-E0 (hex) Samsung Electronics Co.,Ltd -B06FE0 (base 16) Samsung Electronics Co.,Ltd +74-9E-F5 (hex) Samsung Electronics Co.,Ltd +749EF5 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -44-B9-94 (hex) Douglas Lighting Controls -44B994 (base 16) Douglas Lighting Controls - 280 - 3605 Gilmore Way - Burnaby BC V5G4X5 - CA - -24-79-F8 (hex) KUPSON spol. s r.o. -2479F8 (base 16) KUPSON spol. s r.o. - Hradecka 787/14 - Opava Czech Republic 74601 - CZ - -70-BF-92 (hex) GN Audio A/S -70BF92 (base 16) GN Audio A/S - Lautrupbjerg 7 - Ballerup DK-2750 - DK +68-BF-C4 (hex) Samsung Electronics Co.,Ltd +68BFC4 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -AC-BB-61 (hex) YSTen Technology Co.,Ltd -ACBB61 (base 16) YSTen Technology Co.,Ltd - Room 1715,17/F North Star Times Tower,Chaoyang District,Beijing. - Beijing 100101 - CN +B8-2A-DC (hex) EFR Europäische Funk-Rundsteuerung GmbH +B82ADC (base 16) EFR Europäische Funk-Rundsteuerung GmbH + Nymphenburger Straße 20b + Munich 80335 + DE -34-20-03 (hex) Shenzhen Feitengyun Technology Co.,LTD -342003 (base 16) Shenzhen Feitengyun Technology Co.,LTD - 7F 4building,Yalianhaoshida industrial Park - Shenzhen Guangdong 518100 +A0-DF-15 (hex) HUAWEI TECHNOLOGIES CO.,LTD +A0DF15 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -7C-FD-82 (hex) GUANGDONG GENIUS TECHNOLOGY CO., LTD. -7CFD82 (base 16) GUANGDONG GENIUS TECHNOLOGY CO., LTD. - No.168, Middle Road Of East Gate - Xiaobian Community Chang'an Town 523851 - CN +C4-AD-34 (hex) Routerboard.com +C4AD34 (base 16) Routerboard.com + Mikrotikls SIA + Riga Riga LV1009 + LV -00-2F-5C (hex) Cisco Systems, Inc -002F5C (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +00-07-33 (hex) DANCONTROL Engineering +000733 (base 16) DANCONTROL Engineering + Italiensvej 1-5 + + DK -F4-64-5D (hex) Toshiba -F4645D (base 16) Toshiba - 2-9,Suehiro-Cho - Ome Tokyo 1988710 - JP +A8-5E-45 (hex) ASUSTek COMPUTER INC. +A85E45 (base 16) ASUSTek COMPUTER INC. + 15,Li-Te Rd., Peitou, Taipei 112, Taiwan + Taipei Taiwan 112 + TW -F0-7D-68 (hex) D-Link Corporation -F07D68 (base 16) D-Link Corporation - No.289, Sinhu 3rd Rd., Neihu District, - Taipei City 114 +64-C9-01 (hex) INVENTEC Corporation +64C901 (base 16) INVENTEC Corporation + No.66, Hougang St., Shilin Dist., Taipei City 111, Taiwan (R.O.C.) + Taipei 111 TW -EC-41-18 (hex) XIAOMI Electronics,CO.,LTD -EC4118 (base 16) XIAOMI Electronics,CO.,LTD - Xiaomi Building, No.68 Qinghe Middle Street - Haidian District Beijing 100085 +B4-39-39 (hex) Shenzhen TINNO Mobile Technology Corp. +B43939 (base 16) Shenzhen TINNO Mobile Technology Corp. + Building, No.33, Xiandong Rd, Xili + Nanshan District, Shenzhen PRC 518053 CN -D8-86-0B (hex) IEEE Registration Authority -D8860B (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +5C-CD-5B (hex) Intel Corporate +5CCD5B (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -08-EC-F5 (hex) Cisco Systems, Inc -08ECF5 (base 16) Cisco Systems, Inc +84-C8-07 (hex) ADVA Optical Networking Ltd. +84C807 (base 16) ADVA Optical Networking Ltd. + ADVAntage House + York YO30 4RY + GB + +10-B3-C6 (hex) Cisco Systems, Inc +10B3C6 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -D0-76-50 (hex) IEEE Registration Authority -D07650 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +10-B3-D6 (hex) Cisco Systems, Inc +10B3D6 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -60-D0-A9 (hex) Samsung Electronics Co.,Ltd -60D0A9 (base 16) Samsung Electronics Co.,Ltd +A0-AB-51 (hex) WEIFANG GOERTEK ELECTRONICS CO.,LTD +A0AB51 (base 16) WEIFANG GOERTEK ELECTRONICS CO.,LTD + Gaoxin 2 Road, Free Trade Zone,Weifang,Shandong,261205,P.R.China + Weifang Shandong 261205 + CN + +04-B1-A1 (hex) Samsung Electronics Co.,Ltd +04B1A1 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -88-CE-FA (hex) HUAWEI TECHNOLOGIES CO.,LTD -88CEFA (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +CC-46-4E (hex) Samsung Electronics Co.,Ltd +CC464E (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +30-6F-07 (hex) Nations Technologies Inc. +306F07 (base 16) Nations Technologies Inc. + 18F, Nations Tower, Nanshan District + Shenzhen 518057 CN -00-27-06 (hex) YOISYS -002706 (base 16) YOISYS - 309-52 SUNGSU-2GA, 1DONG, SUNGDONG-GU - SEOUL 133-827 - KR +3C-89-4D (hex) Dr. Ing. h.c. F. Porsche AG +3C894D (base 16) Dr. Ing. h.c. F. Porsche AG + Porscheplatz 1 + Stuttgart 70435 + DE -04-2D-B4 (hex) First Property (Beijing) Co., Ltd Modern MOMA Branch -042DB4 (base 16) First Property (Beijing) Co., Ltd Modern MOMA Branch - Room 301A,Building No.10, No.1 Xiangheyuan Road, Dongcheng District, Beijing City - Beijing Beijing 100028 +F8-54-B8 (hex) Amazon Technologies Inc. +F854B8 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +78-17-35 (hex) Nokia Shanghai Bell Co., Ltd. +781735 (base 16) Nokia Shanghai Bell Co., Ltd. + No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai + Shanghai 201206 CN -38-F8-5E (hex) HUMAX Co., Ltd. -38F85E (base 16) HUMAX Co., Ltd. - HUMAX Village, 216, Hwangsaeul-ro, Bu - Seongnam-si Gyeonggi-do 463-875 - KR +9C-FF-C2 (hex) AVI Systems GmbH +9CFFC2 (base 16) AVI Systems GmbH + Dr. Franz Wilhelmstraße 2A + Krems a. d. Donau 3500 + AT -00-CB-51 (hex) Sagemcom Broadband SAS -00CB51 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +44-D8-78 (hex) Hui Zhou Gaoshengda Technology Co.,LTD +44D878 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD + No.75,Zhongkai High-Tech Development District,Huizhou + Hui Zhou Guangdong 516006 + CN -C4-64-B7 (hex) Fiberhome Telecommunication Technologies Co.,LTD -C464B7 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +78-97-C3 (hex) DINGXIN INFORMATION TECHNOLOGY CO.,LTD +7897C3 (base 16) DINGXIN INFORMATION TECHNOLOGY CO.,LTD + No.6 huasui Road,ZhuJiang Xincheng + Guangzhou Guangdong 510623 CN -04-E0-B0 (hex) Shenzhen YOUHUA Technology Co., Ltd -04E0B0 (base 16) Shenzhen YOUHUA Technology Co., Ltd - Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District - Shenzhen Guangdong 518055 +F4-D6-20 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +F4D620 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -38-B1-9E (hex) IEEE Registration Authority -38B19E (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +94-90-34 (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD +949034 (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD + Unit East Block22-24/F,Skyworth semiconductor design Bldg., Gaoxin Ave.4.S.,Nanshan District,Shenzhen,China + SHENZHEN GUANGDONG 518057 + CN -38-E2-6E (hex) ShenZhen Sweet Rain Electronics Co.,Ltd. -38E26E (base 16) ShenZhen Sweet Rain Electronics Co.,Ltd. - Xinghua Road - Shenzhen Bao'an 518101 +3C-8F-06 (hex) Shenzhen Libtor Technology Co.,Ltd +3C8F06 (base 16) Shenzhen Libtor Technology Co.,Ltd + Room 608, Building A + Shenzhen City Guangdong Province 518000 CN -70-C9-C6 (hex) Cisco Systems, Inc -70C9C6 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +98-7A-10 (hex) Ericsson AB +987A10 (base 16) Ericsson AB + Torshamnsgatan 36 + Stockholm SE-164 80 + SE -68-9A-87 (hex) Amazon Technologies Inc. -689A87 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +20-26-81 (hex) TECNO MOBILE LIMITED +202681 (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 + Hong Kong Hong Kong 999077 + HK -64-AE-88 (hex) Polytec GmbH -64AE88 (base 16) Polytec GmbH - Polytec Platz 1-7 - Waldbronn BW 76337 - DE +A0-D8-07 (hex) Huawei Device Co., Ltd. +A0D807 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -00-D0-50 (hex) Iskratel d.o.o. -00D050 (base 16) Iskratel d.o.o. - Ljubljanska cesta 24a - Kranj 4000 - SI +2C-78-0E (hex) Huawei Device Co., Ltd. +2C780E (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -78-DA-A2 (hex) Cynosure Technologies Co.,Ltd -78DAA2 (base 16) Cynosure Technologies Co.,Ltd - Room 2708/2710, Building No.9A, Shenzhen Bay Science and Technology Ecological Park,Nanshan - Shenzhen city Guangdong Province 518057 +34-B2-0A (hex) Huawei Device Co., Ltd. +34B20A (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -80-84-A9 (hex) oshkosh Corporation -8084A9 (base 16) oshkosh Corporation - 2307 Oregon Street - Oshkosh WI 54902 - US +98-F4-AB (hex) Espressif Inc. +98F4AB (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN -18-0D-2C (hex) Intelbras -180D2C (base 16) Intelbras - BR 101, km 210, S/N° - São José Santa Catarina 88104800 - BR +D8-BF-C0 (hex) Espressif Inc. +D8BFC0 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN -98-DA-C4 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -98DAC4 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 +B0-08-75 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B00875 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -00-17-7B (hex) Azalea Networks inc -00177B (base 16) Azalea Networks inc - 673 S Milpitas Blvd - Milpitas CA 95035 +64-FB-92 (hex) PPC Broadband Inc. +64FB92 (base 16) PPC Broadband Inc. + 6176 E Molloy Rd + East Syracuse NY 13057 US -50-2B-98 (hex) Es-tech International -502B98 (base 16) Es-tech International - 228-70, Saneop-ro 155beon-gil, Gwonseon-gu, Suwon-si, Gyeonggi-do, Korea - Suwon 16648 - KR - -C8-28-32 (hex) Beijing Xiaomi Electronics Co., Ltd. -C82832 (base 16) Beijing Xiaomi Electronics Co., Ltd. - Building C, QingHe ShunShiJiaYe Technology Park, #66 ZhuFang Rd, HaiDian District - Beijing Beijing 10085 +14-13-46 (hex) Skyworth Digital Technology(Shenzhen) Co.,Ltd +141346 (base 16) Skyworth Digital Technology(Shenzhen) Co.,Ltd + 7F,Block A,Skyworth Building, + Shenzhen Guangdong 518057 CN -70-1B-FB (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. -701BFB (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. - Phase 3, Bayan Lepas FIZ - Bayan Lepas Penang 11900 - MY +54-2B-DE (hex) New H3C Technologies Co., Ltd +542BDE (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN -04-76-6E (hex) ALPSALPINE CO,.LTD -04766E (base 16) ALPSALPINE CO,.LTD - 6-3-36 Furukawanakazato, - Osaki Miyagi-pref 989-6181 - JP +4C-90-DB (hex) JL Audio +4C90DB (base 16) JL Audio + 10369 N Commerce Pkwy + Mirimar FL 33025 + US -AC-7A-4D (hex) ALPSALPINE CO,.LTD -AC7A4D (base 16) ALPSALPINE CO,.LTD - 6-1 - KAKUDA-CITY MIYAGI-PREF 981-1595 - JP +8C-F1-12 (hex) Motorola Mobility LLC, a Lenovo Company +8CF112 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US -38-C0-96 (hex) ALPSALPINE CO,.LTD -38C096 (base 16) ALPSALPINE CO,.LTD - 6-1 - KAKUDA-CITY MIYAGI-PREF 981-1595 - JP +B8-99-AE (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd +B899AE (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd + Chudong science and technology park, 111 shaxin road, tangxia town, + dongguan city guangdong province 523710 + CN -C4-34-6B (hex) Hewlett Packard -C4346B (base 16) Hewlett Packard - 11445 Compaq Center Drive - Houston 77070 - US +E8-D0-B9 (hex) Taicang T&W Electronics +E8D0B9 (base 16) Taicang T&W Electronics + 89# Jiang Nan RD + Suzhou Jiangsu 215412 + CN -48-F1-7F (hex) Intel Corporate -48F17F (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +58-85-A2 (hex) Realme Chongqing MobileTelecommunications Corp Ltd +5885A2 (base 16) Realme Chongqing MobileTelecommunications Corp Ltd + No.24 Nichang Boulevard, Huixing Block, Yubei District, Chongqing. + Chongqing China 401120 + CN -00-26-43 (hex) ALPSALPINE CO,.LTD -002643 (base 16) ALPSALPINE CO,.LTD - 1-2-1, Okinouchi, - Soma-city, Fukushima-pref., 976-8501 - JP +A8-C0-EA (hex) Pepwave Limited +A8C0EA (base 16) Pepwave Limited + A5, 5/F, HK Spinners Industrial Building, Phase 6, 481 Castle Peak Road + Cheung Sha Wan Hong Kong 0 + HK -1C-34-77 (hex) Innovation Wireless -1C3477 (base 16) Innovation Wireless - 11869 Teale Street - Culver City CA 90230 - US +44-AE-AB (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +44AEAB (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN -4C-C7-D6 (hex) FLEXTRONICS MANUFACTURING(ZHUHAI)CO.,LTD. -4CC7D6 (base 16) FLEXTRONICS MANUFACTURING(ZHUHAI)CO.,LTD. - Xin Qing Science & Technology Industrial Park,Jin An Town,Doumen ,Zhuhai,Guangdong,PRC - Zhuhai Guangdong 519180 +A4-F0-5E (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +A4F05E (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -C8-08-73 (hex) Ruckus Wireless -C80873 (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +00-07-1C (hex) AT&T +00071C (base 16) AT&T + 3300 E Renner Road + Richardson TX 75082 US -00-16-97 (hex) NEC Corporation -001697 (base 16) NEC Corporation - 7-1, Shiba 5-chome Minato-ku, - tokyo Tokyo 108-8001 - JP - -00-30-13 (hex) NEC Corporation -003013 (base 16) NEC Corporation - 1-10 Nisshincho, Fuchu - Tokyo 183-8501 0000 - JP +84-76-37 (hex) HUAWEI TECHNOLOGIES CO.,LTD +847637 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -BC-3E-07 (hex) Hitron Technologies. Inc -BC3E07 (base 16) Hitron Technologies. Inc - No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C - Hsin-chu Taiwan 300 - TW +FC-94-35 (hex) HUAWEI TECHNOLOGIES CO.,LTD +FC9435 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -48-FD-A3 (hex) Xiaomi Communications Co Ltd -48FDA3 (base 16) Xiaomi Communications Co Ltd - The Rainbow City of China Resources - NO.68, Qinghe Middle Street Haidian District, Beijing 100085 +E0-24-81 (hex) HUAWEI TECHNOLOGIES CO.,LTD +E02481 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -90-86-9B (hex) zte corporation -90869B (base 16) zte corporation +2C-F8-9B (hex) Cisco Systems, Inc +2CF89B (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +80-B0-7B (hex) zte corporation +80B07B (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China shenzhen guangdong 518057 CN -E0-18-9F (hex) EM Microelectronic -E0189F (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH - -F8-13-08 (hex) Nokia -F81308 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +C8-5A-9F (hex) zte corporation +C85A9F (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -F8-A2-D6 (hex) Liteon Technology Corporation -F8A2D6 (base 16) Liteon Technology Corporation - 4F, 90, Chien 1 Road - New Taipei City Taiwan 23585 +00-1D-DF (hex) Sunitec Enterprise Co.,Ltd +001DDF (base 16) Sunitec Enterprise Co.,Ltd + 3F., No. 98-1, Mincyuan Rd. + Sindian City Taipei County 231 TW -04-9D-FE (hex) Hivesystem -049DFE (base 16) Hivesystem - 816 Kranz-techno Bldg. 388 dunchondaero, jun - Gyeonggi-do KSXX0024 +8C-0F-FA (hex) Hutec co.,ltd +8C0FFA (base 16) Hutec co.,ltd + 46, Gunpocheomdansaneop 2-ro + Gunpo-si Gyeonggi-do 15880 KR -D0-51-57 (hex) LEAX Arkivator Telecom -D05157 (base 16) LEAX Arkivator Telecom - NanShan District YueHaiMen Street - ShenZhen GuangDong 518061 +AC-FE-05 (hex) ITEL MOBILE LIMITED +ACFE05 (base 16) ITEL MOBILE LIMITED + RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K + Hong Kong KOWLOON 999077 + HK + +BC-C3-1B (hex) Kygo Life A +BCC31B (base 16) Kygo Life A + Sjolyst Plass 3 + Oslo 0278 + NO + +64-DF-10 (hex) JingLue Semiconductor(SH) Ltd. +64DF10 (base 16) JingLue Semiconductor(SH) Ltd. + No.800 Naxian Rd + Shanghai 201210 CN -FC-BE-7B (hex) vivo Mobile Communication Co., Ltd. -FCBE7B (base 16) vivo Mobile Communication Co., Ltd. - #283,BBK Road - Wusha,Chang'An DongGuan City,Guangdong, 523860 +10-01-77 (hex) HUAWEI TECHNOLOGIES CO.,LTD +100177 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -B4-0F-B3 (hex) vivo Mobile Communication Co., Ltd. -B40FB3 (base 16) vivo Mobile Communication Co., Ltd. - #283,BBK Road - Wusha,Chang'An DongGuan City,Guangdong, 523860 +44-A1-91 (hex) HUAWEI TECHNOLOGIES CO.,LTD +44A191 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -EC-5C-68 (hex) CHONGQING FUGUI ELECTRONICS CO.,LTD. -EC5C68 (base 16) CHONGQING FUGUI ELECTRONICS CO.,LTD. - Building D21,No.1, East Zone 1st Road,Xiyong Town,Shapingba District - Chongqing Chongqing 401332 +24-52-6A (hex) Zhejiang Dahua Technology Co., Ltd. +24526A (base 16) Zhejiang Dahua Technology Co., Ltd. + No.1199,Waterfront Road + Hangzhou Zhejiang 310053 CN -94-58-CB (hex) Nintendo Co.,Ltd -9458CB (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP +20-DF-B9 (hex) Google, Inc. +20DFB9 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US -74-36-6D (hex) Vodafone Italia S.p.A. -74366D (base 16) Vodafone Italia S.p.A. - Via Lorenteggio nr. 240 - Milan Italy 20147 - IT +5C-CA-D3 (hex) CHIPSEA TECHNOLOGIES (SHENZHEN) CORP. +5CCAD3 (base 16) CHIPSEA TECHNOLOGIES (SHENZHEN) CORP. + 9F,BLOCK A,GARDEN CITY DIGITAL BUILDING,NO.1079 NANHAI ROAD,NANSHAN DISTRICT + SHEN ZHEN GUANG DONG 518000 + CN -18-2A-44 (hex) HIROSE ELECTRONIC SYSTEM -182A44 (base 16) HIROSE ELECTRONIC SYSTEM - 1-9-6 Ebisuminami - Shibuya Tokyo 150-0022 - JP +78-2A-79 (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. +782A79 (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. + Phase 3, Bayan Lepas FIZ + Bayan Lepas Penang 11900 + MY -FC-94-CE (hex) zte corporation -FC94CE (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +50-D2-F5 (hex) Beijing Xiaomi Mobile Software Co., Ltd +50D2F5 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -0C-EC-84 (hex) Shenzhen TINNO Mobile Technology Corp. -0CEC84 (base 16) Shenzhen TINNO Mobile Technology Corp. - Building, No.33, Xiandong Rd, Xili - Nanshan District, Shenzhen PRC 518053 +1C-68-7E (hex) Shenzhen Qihu Intelligent Technology Company Limited +1C687E (base 16) Shenzhen Qihu Intelligent Technology Company Limited + Room 201, Block A, No.1, Qianwan Road 1,Qianhai Shenzhen HongKong Modern Service Industry Cooperation Zone + Shenzhen Guangdong 518057 CN -9C-DB-07 (hex) Yellowtec GmbH -9CDB07 (base 16) Yellowtec GmbH - Heinrich-Hertz-Strasse 1-3 - Monheim am Rhein NRW 40789 - DE +C0-36-56 (hex) Fiberhome Telecommunication Technologies Co.,LTD +C03656 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN -28-EC-9A (hex) Texas Instruments -28EC9A (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +B0-3E-51 (hex) SKY UK LIMITED +B03E51 (base 16) SKY UK LIMITED + 130 Kings Road + Brentwood Essex 08854 + GB -C4-E5-06 (hex) Piper Networks, Inc. -C4E506 (base 16) Piper Networks, Inc. - 3636 Nobel Drive - San Diego CA 92122 - US +28-16-7F (hex) Xiaomi Communications Co Ltd +28167F (base 16) Xiaomi Communications Co Ltd + The Rainbow City of China Resources + NO.68, Qinghe Middle Street Haidian District, Beijing 100085 + CN -0C-D0-F8 (hex) Cisco Systems, Inc -0CD0F8 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +08-71-90 (hex) Intel Corporate +087190 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -DC-DA-80 (hex) New H3C Technologies Co., Ltd -DCDA80 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +5C-E8-83 (hex) HUAWEI TECHNOLOGIES CO.,LTD +5CE883 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -B8-25-9A (hex) Thalmic Labs -B8259A (base 16) Thalmic Labs - 24 Charles Street West - Kitchener Ontario N2G 1H2 - CA - -30-EB-5A (hex) LANDIS + GYR -30EB5A (base 16) LANDIS + GYR - 78th km Old National Road Athens-Corinth - Corinth 20100 - GR +60-23-A4 (hex) Sichuan AI-Link Technology Co., Ltd. +6023A4 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou,Industrial Park + Anzhou,Industrial Park Sichuan 621000 + CN -F8-0F-6F (hex) Cisco Systems, Inc -F80F6F (base 16) Cisco Systems, Inc +A4-53-0E (hex) Cisco Systems, Inc +A4530E (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -00-80-E3 (hex) CORAL NETWORK CORPORATION -0080E3 (base 16) CORAL NETWORK CORPORATION - (NOW BAY NETWORKS) - SANTA CLARA CA 95052-8185 - US - -D8-F2-CA (hex) Intel Corporate -D8F2CA (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +00-40-3A (hex) IMPACT TECHNOLOGIES +00403A (base 16) IMPACT TECHNOLOGIES + 6 RUE DE L'ACADIE + + FR -B4-C6-2E (hex) Molex CMS -B4C62E (base 16) Molex CMS - 2222 Wellington Court - Lisle IL 60532 +C8-B1-CD (hex) Apple, Inc. +C8B1CD (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -28-25-36 (hex) SHENZHEN HOLATEK CO.,LTD -282536 (base 16) SHENZHEN HOLATEK CO.,LTD - Rm.1001,Unit 4,Bld.B,Kexing Science Park,Keyuan Road, Nanshan District - Shenzhen Guangdong 518000 - CN +14-60-CB (hex) Apple, Inc. +1460CB (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -B8-A1-75 (hex) Roku, Inc. -B8A175 (base 16) Roku, Inc. - 12980 Saratoga Ave - Saratoga CA 95070 +B8-F1-2A (hex) Apple, Inc. +B8F12A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -CC-D3-C1 (hex) Vestel Elektronik San ve Tic. A.S. -CCD3C1 (base 16) Vestel Elektronik San ve Tic. A.S. - Organize san - Manisa Turket 45030 - TR +F8-87-F1 (hex) Apple, Inc. +F887F1 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -A4-26-55 (hex) LTI Motion (Shanghai) Co., Ltd. -A42655 (base 16) LTI Motion (Shanghai) Co., Ltd. - NO.80, Lane 2927, LaiYang Road Pudong New District - Shanghai Shanghai 200137 - CN +60-44-7A (hex) Water-i.d. GmbH +60447A (base 16) Water-i.d. GmbH + Daimlerstr. 20 + Eggenstein 76344 + DE -60-A7-30 (hex) Shenzhen Yipinfang Internet Technology Co.,Ltd -60A730 (base 16) Shenzhen Yipinfang Internet Technology Co.,Ltd - Shenzhen Konka R & D Building, 28th floor 21 - GuangDong Nanshan District 518000 - CN +80-72-15 (hex) SKY UK LIMITED +807215 (base 16) SKY UK LIMITED + 130 Kings Road + Brentwood Essex 08854 + GB -3C-9B-D6 (hex) Vizio, Inc -3C9BD6 (base 16) Vizio, Inc - 39 Tesla - Irvine CA 92618 +74-D6-37 (hex) Amazon Technologies Inc. +74D637 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -50-DB-3F (hex) SHENZHEN GONGJIN ELECTRONICS CO.,LT -50DB3F (base 16) SHENZHEN GONGJIN ELECTRONICS CO.,LT - SONGGANG - SHENZHEN GUANGDONG 518105 - CN - -10-81-B4 (hex) Hunan Greatwall Galaxy Science and Technology Co.,Ltd. -1081B4 (base 16) Hunan Greatwall Galaxy Science and Technology Co.,Ltd. - No. 39, Jian Shan Road - Changsha Hunan 410205 +74-84-E1 (hex) Dongguan Haoyuan Electronics Co.,Ltd +7484E1 (base 16) Dongguan Haoyuan Electronics Co.,Ltd + NO.161 Kejizhong RoadLiuwu Shijie Town + Dongguan Guangdong 523290 CN -38-81-D7 (hex) Texas Instruments -3881D7 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +40-74-E0 (hex) Intel Corporate +4074E0 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -18-04-ED (hex) Texas Instruments -1804ED (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +D0-5F-64 (hex) IEEE Registration Authority +D05F64 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -D4-32-60 (hex) GoPro -D43260 (base 16) GoPro - 3000 Clearview Way - San Mateo CA 94402 - US +44-FB-5A (hex) zte corporation +44FB5A (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -F4-DD-9E (hex) GoPro -F4DD9E (base 16) GoPro - 3000 Clearview Way - San Mateo CA 94402 - US +C4-63-FB (hex) Neatframe AS +C463FB (base 16) Neatframe AS + Martin Linges Vei 25 + Fornebu Fornebu 1364 + NO -D4-D9-19 (hex) GoPro -D4D919 (base 16) GoPro - 3000 Clearview Way - San Mateo CA 94402 +30-57-14 (hex) Apple, Inc. +305714 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -CC-72-86 (hex) Xi'an Fengyu Information Technology Co., Ltd. -CC7286 (base 16) Xi'an Fengyu Information Technology Co., Ltd. - 5F, Block A, STRC, No.10, Zhangba 5th Road, Yanta - Xi'an Shaanxi 710077 - CN - -6C-A9-28 (hex) HMD Global Oy -6CA928 (base 16) HMD Global Oy - Bertel Jungin aukio 9 - Espoo 02600 - FI - -00-D8-61 (hex) Micro-Star INTL CO., LTD. -00D861 (base 16) Micro-Star INTL CO., LTD. - No.69, Lide St., - New Taipei City Taiwan 235 +04-A2-22 (hex) Arcadyan Corporation +04A222 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 TW -74-C1-7D (hex) Infinix mobility limited -74C17D (base 16) Infinix mobility limited - RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG - HongKong HongKong 999077 - HK +04-AB-6A (hex) Chun-il Co.,Ltd. +04AB6A (base 16) Chun-il Co.,Ltd. + 13-7, Gimhae-daero 2694 beon-gil, + Gimhae-si Gyeongsangnam-do 50936 + KR -14-11-14 (hex) TECNO MOBILE LIMITED -141114 (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 - Hong Kong Hong Kong 999077 - HK +00-35-FF (hex) Texas Instruments +0035FF (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -00-B8-B3 (hex) Cisco Systems, Inc -00B8B3 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +DC-54-D7 (hex) Amazon Technologies Inc. +DC54D7 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -74-5F-90 (hex) LAM Technologies -745F90 (base 16) LAM Technologies - Viale Ludovico Ariosto, 492/D - Sesto Fiorentino FIRENZE 50019 - IT +28-FE-65 (hex) DongGuan Siyoto Electronics Co., Ltd +28FE65 (base 16) DongGuan Siyoto Electronics Co., Ltd + Hecheng Industrial District, QiaoTou Town + DongGuan City Guangdong 523520 + CN -F8-5B-9C (hex) SB SYSTEMS Co.,Ltd -F85B9C (base 16) SB SYSTEMS Co.,Ltd - 2F Ventureforum, 323, Pangyo-ro, Bundang-gu - Seongnam-si Gyeonngi-do 453-400 - KR +18-06-F5 (hex) RAD Data Communications, Ltd. +1806F5 (base 16) RAD Data Communications, Ltd. + 24 Raoul Wallenberg St. + Tel Aviv 69719 + IL -00-42-79 (hex) Sunitec Enterprise Co.,Ltd -004279 (base 16) Sunitec Enterprise Co.,Ltd - 3F.,No.98-1,Mincyuan Rd.Sindian City - Taipei County 231 231141 - CN +88-9F-AA (hex) Hella Gutmann Solutions GmbH +889FAA (base 16) Hella Gutmann Solutions GmbH + Am Krebsbach 2 + Ihringen Baden Württemberg 79241 + DE -A4-50-46 (hex) Xiaomi Communications Co Ltd -A45046 (base 16) Xiaomi Communications Co Ltd +04-C8-07 (hex) Xiaomi Communications Co Ltd +04C807 (base 16) Xiaomi Communications Co Ltd The Rainbow City of China Resources NO.68, Qinghe Middle Street Haidian District, Beijing 100085 CN -F8-50-1C (hex) Tianjin Geneuo Technology Co.,Ltd -F8501C (base 16) Tianjin Geneuo Technology Co.,Ltd - Technology Avenue South JingHai Economic Development Area,Tianjin China - Tianjin 301609 - CN - -70-D3-13 (hex) HUAWEI TECHNOLOGIES CO.,LTD -70D313 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -9C-1D-36 (hex) HUAWEI TECHNOLOGIES CO.,LTD -9C1D36 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -CC-BB-FE (hex) HUAWEI TECHNOLOGIES CO.,LTD -CCBBFE (base 16) HUAWEI TECHNOLOGIES CO.,LTD +44-59-E3 (hex) HUAWEI TECHNOLOGIES CO.,LTD +4459E3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -44-07-0B (hex) Google, Inc. -44070B (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US - -EC-F6-BD (hex) SNCF MOBILITÉS -ECF6BD (base 16) SNCF MOBILITÉS - 9 rue Jean-Philippe Rameau - SAINT-DENIS 93200 - FR +68-3F-1E (hex) EFFECT Photonics B.V. +683F1E (base 16) EFFECT Photonics B.V. + Kastanjelaan 400 + Eindhoven Noord-Brabant 5616 LZ + NL -B8-31-B5 (hex) Microsoft Corporation -B831B5 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 +44-D3-CA (hex) Cisco Systems, Inc +44D3CA (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -00-7C-2D (hex) Samsung Electronics Co.,Ltd -007C2D (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR - -38-B4-D3 (hex) BSH Hausgeraete GmbH -38B4D3 (base 16) BSH Hausgeraete GmbH - Im Gewerbepark B10 - Regensburg 93059 - DE - -C8-47-82 (hex) Areson Technology Corp. -C84782 (base 16) Areson Technology Corp. - 11F., No. 646, Sec. 5, Chongxin Rd., Sanchong District - New Taipei City 24158 +84-0B-7C (hex) Hitron Technologies. Inc +840B7C (base 16) Hitron Technologies. Inc + No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C + Hsin-chu Taiwan 300 TW -E8-93-63 (hex) Nokia -E89363 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +48-A7-3C (hex) Sichuan tianyi kanghe communications co., LTD +48A73C (base 16) Sichuan tianyi kanghe communications co., LTD + No.198, section 1, xueshan avenue, jinyuan town, dayi county + chengdu sichuan 611330 + CN -7C-0C-F6 (hex) Guangdong Huiwei High-tech Co., Ltd. -7C0CF6 (base 16) Guangdong Huiwei High-tech Co., Ltd. - E Block No. 1 in Ecological Area in Puzhai NewArea - Fengshun County, Meizhou Guangdong province 514000 +F8-A7-63 (hex) Zhejiang Tmall Technology Co., Ltd. +F8A763 (base 16) Zhejiang Tmall Technology Co., Ltd. + Ali Center,No.3331 Keyuan South RD (Shenzhen bay), Nanshan District, Shenzhen Guangdong province + Shenzhen GuangDong 518000 CN -50-29-F5 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -5029F5 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +6C-29-90 (hex) WiZ Connected Lighting Company Limited +6C2990 (base 16) WiZ Connected Lighting Company Limited + Room 3805, 148 Electric Road + Hong Kong 0000 0000 + HK + +98-35-ED (hex) HUAWEI TECHNOLOGIES CO.,LTD +9835ED (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -30-0A-60 (hex) IEEE Registration Authority -300A60 (base 16) IEEE Registration Authority +80-76-93 (hex) Newag SA +807693 (base 16) Newag SA + Wyspianskiego 3 + Nowy Sacz woj. Malopolskie 33-300 + PL + +BC-97-40 (hex) IEEE Registration Authority +BC9740 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -80-D0-65 (hex) CKS Corporation -80D065 (base 16) CKS Corporation - 1-24-11 Akebono - Tachikawa Tokyo 190-0012 - JP - -00-D6-FE (hex) Cisco Systems, Inc -00D6FE (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -0C-BF-74 (hex) Morse Micro -0CBF74 (base 16) Morse Micro - 113 / 2-4 Cornwallis Street - Eveleigh NSW 2015 - AU - -B4-1D-2B (hex) Shenzhen YOUHUA Technology Co., Ltd -B41D2B (base 16) Shenzhen YOUHUA Technology Co., Ltd - Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District - Shenzhen Guangdong 518055 +08-4F-0A (hex) HUAWEI TECHNOLOGIES CO.,LTD +084F0A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -14-C2-13 (hex) Apple, Inc. -14C213 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -FC-8F-7D (hex) SHENZHEN GONGJIN ELECTRONICS CO.,LT -FC8F7D (base 16) SHENZHEN GONGJIN ELECTRONICS CO.,LT - SONGGANG - SHENZHEN GUANGDONG 518105 +A8-49-4D (hex) HUAWEI TECHNOLOGIES CO.,LTD +A8494D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -24-BE-18 (hex) DADOUTEK COMPANY LIMITED -24BE18 (base 16) DADOUTEK COMPANY LIMITED - 14/F, Wilson Logistics Centre,No.24-28 Kung Yip St - Kwai Chung New Territories 000 +44-00-4D (hex) HUAWEI TECHNOLOGIES CO.,LTD +44004D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -74-9D-79 (hex) Sercomm Corporation. -749D79 (base 16) Sercomm Corporation. - 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen - Miao-Lih Hsuan 115 - TW - -A4-D9-31 (hex) Apple, Inc. -A4D931 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -BC-FE-D9 (hex) Apple, Inc. -BCFED9 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -80-82-23 (hex) Apple, Inc. -808223 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +20-96-8A (hex) China Mobile (Hangzhou) Information Technology Co., Ltd. +20968A (base 16) China Mobile (Hangzhou) Information Technology Co., Ltd. + No. 1600 Yuhangtang Road, Wuchang Street, Yuhang District + Hangzhou Zhejiang 310000 + CN -CC-08-FB (hex) TP-LINK TECHNOLOGIES CO.,LTD. -CC08FB (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 +8C-18-50 (hex) China Mobile (Hangzhou) Information Technology Co., Ltd. +8C1850 (base 16) China Mobile (Hangzhou) Information Technology Co., Ltd. + No. 1600 Yuhangtang Road, Wuchang Street, Yuhang District + Hangzhou Hangzhou 310000 CN -BC-AF-91 (hex) TE Connectivity Sensor Solutions -BCAF91 (base 16) TE Connectivity Sensor Solutions - 4 rue Gaye-Marie, CS 83163 - Toulouse 31027 - FR +D8-D4-E6 (hex) Hytec Inter Co., Ltd. +D8D4E6 (base 16) Hytec Inter Co., Ltd. + 3-28-6 Yoyogi + Shibuya-ku Tokyo 1510053 + JP -28-31-66 (hex) vivo Mobile Communication Co., Ltd. -283166 (base 16) vivo Mobile Communication Co., Ltd. +F8-E7-A0 (hex) vivo Mobile Communication Co., Ltd. +F8E7A0 (base 16) vivo Mobile Communication Co., Ltd. #283,BBK Road Wusha,Chang'An DongGuan City,Guangdong, 523860 CN -C0-40-04 (hex) Medicaroid Corporation -C04004 (base 16) Medicaroid Corporation - 1-6-4, Minatojima-minamimachi, Chuo-ku - Kobe 650-0047 - JP - -A4-ED-43 (hex) IEEE Registration Authority -A4ED43 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - -94-29-8D (hex) Shanghai AdaptComm Technology Co., Ltd. -94298D (base 16) Shanghai AdaptComm Technology Co., Ltd. - 3rd Floor, Building 14, No. 518 Xinzhuan Road, Songjiang District, - Shanghai 201600 +2C-FF-EE (hex) vivo Mobile Communication Co., Ltd. +2CFFEE (base 16) vivo Mobile Communication Co., Ltd. + #283,BBK Road + Wusha,Chang'An DongGuan City,Guangdong, 523860 CN -00-AA-6E (hex) Cisco Systems, Inc -00AA6E (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +C8-5D-38 (hex) HUMAX Co., Ltd. +C85D38 (base 16) HUMAX Co., Ltd. + HUMAX Village, 216, Hwangsaeul-ro, Bu + Seongnam-si Gyeonggi-do 463-875 + KR -F0-D7-DC (hex) Wesine (Wuhan) Technology Co., Ltd. -F0D7DC (base 16) Wesine (Wuhan) Technology Co., Ltd. - 10th Floor, Building 2, SBI Venture Street, Hongshan District - Wuhan Hubei 430074 +18-CF-24 (hex) HUAWEI TECHNOLOGIES CO.,LTD +18CF24 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -00-72-04 (hex) Samsung Electronics Co., Ltd. ARTIK -007204 (base 16) Samsung Electronics Co., Ltd. ARTIK - 1-1, Samsungjeonja-ro - Hwaseong-si Gyeonggi-do 18448 +50-F8-A5 (hex) eWBM Co., Ltd. +50F8A5 (base 16) eWBM Co., Ltd. + 14F, 9, Teheran-ro 20-gil + Gangnam-gu, Seoul 06236 KR -40-C8-1F (hex) Shenzhen Xinguodu Technology Co., Ltd. -40C81F (base 16) Shenzhen Xinguodu Technology Co., Ltd. - F17A, JinSong Building, Tairan Industrial & Trade Park, Chegongmiao, Shennan Road,Futian District - Shenzhen Guangdong 518040 +D8-9B-3B (hex) HUAWEI TECHNOLOGIES CO.,LTD +D89B3B (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -0C-7C-28 (hex) Nokia Solutions and Networks GmbH & Co. KG -0C7C28 (base 16) Nokia Solutions and Networks GmbH & Co. KG - Werinherstrasse 91 - München Bavaria D-81541 - DE - -68-43-D7 (hex) Agilecom Photonics Solutions Guangdong Limited -6843D7 (base 16) Agilecom Photonics Solutions Guangdong Limited - No.1-6, Shenwan Industrial Park, Shenwan Town - Zhongshan Guangdong 528462 +88-40-3B (hex) HUAWEI TECHNOLOGIES CO.,LTD +88403B (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -B8-6A-97 (hex) Edgecore Networks Corporation -B86A97 (base 16) Edgecore Networks Corporation - 1 Creation RD 3. - Hsinchu 30077 - TW - -A8-10-87 (hex) Texas Instruments -A81087 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - -8C-8F-8B (hex) China Mobile Chongqing branch -8C8F8B (base 16) China Mobile Chongqing branch - 6 building, No. 2, Xingguang three road - Yubei District Chongqing 401120 +FC-87-43 (hex) HUAWEI TECHNOLOGIES CO.,LTD +FC8743 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -00-04-0B (hex) 3COM EUROPE LTD -00040B (base 16) 3COM EUROPE LTD - BOUNDARY WAY - vvvvv UNITED KINGDOM - GB - -C8-C2-F5 (hex) FLEXTRONICS MANUFACTURING(ZHUHAI)CO.,LTD. -C8C2F5 (base 16) FLEXTRONICS MANUFACTURING(ZHUHAI)CO.,LTD. - Xin Qing Science & Technology Industrial Park,Jin An Town,Doumen ,Zhuhai,Guangdong,PRC - Zhuhai Guangdong 519180 +04-88-5F (hex) HUAWEI TECHNOLOGIES CO.,LTD +04885F (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -F0-58-49 (hex) CareView Communications -F05849 (base 16) CareView Communications - 405 State HWY 121 BYP - Lewisville Texas 75067 - US - -8C-FE-74 (hex) Ruckus Wireless -8CFE74 (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 - US - -60-4B-AA (hex) Magic Leap, Inc. -604BAA (base 16) Magic Leap, Inc. - 1855 Griffin Rd, Room B454 - Dania Beach FL 33004 - US - -E4-34-93 (hex) HUAWEI TECHNOLOGIES CO.,LTD -E43493 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +C8-50-CE (hex) HUAWEI TECHNOLOGIES CO.,LTD +C850CE (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 + Dongguan 523808 CN -34-29-12 (hex) HUAWEI TECHNOLOGIES CO.,LTD -342912 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +00-0B-E4 (hex) Hosiden Corporation +000BE4 (base 16) Hosiden Corporation + 4-33 + Yao-city Osaka 581-0071 + JP + +44-13-D0 (hex) zte corporation +4413D0 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -48-A4-72 (hex) Intel Corporate -48A472 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +14-49-BC (hex) DrayTek Corp. +1449BC (base 16) DrayTek Corp. + No. 26, Fushing Rd., Hukou, Hsinchu Industrial Park, + Hsinchu county 30352 + TW -00-0A-5E (hex) 3COM -000A5E (base 16) 3COM - 5400 Bayfront Plaza - Santa Clara CA 95052-8145 - US +20-F4-78 (hex) Xiaomi Communications Co Ltd +20F478 (base 16) Xiaomi Communications Co Ltd + The Rainbow City of China Resources + NO.68, Qinghe Middle Street Haidian District, Beijing 100085 + CN -00-10-5A (hex) 3COM -00105A (base 16) 3COM - 5400 BAYFRONT PLAZA - SANTA CLARA CA 95052 - US +74-38-B7 (hex) CANON INC. +7438B7 (base 16) CANON INC. + 30-2 Shimomaruko 3-chome + Ohta-ku Tokyo 146-8501 + JP -00-60-97 (hex) 3COM -006097 (base 16) 3COM - 5400 BAYFRONT PLAZA - SANTA CLARA CA 95052 +90-73-5A (hex) Motorola Mobility LLC, a Lenovo Company +90735A (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -00-60-08 (hex) 3COM -006008 (base 16) 3COM - 5400 BAYFRONT PLAZA - SANTA CLARA CA 95052 - US +00-04-DF (hex) TERACOM TELEMATICA S.A +0004DF (base 16) TERACOM TELEMATICA S.A + RUA AMERICA N.1000 + Eldorado do Sul - RS Brazil + BR -00-01-02 (hex) 3COM -000102 (base 16) 3COM - 5400 BAYFRONT PLAZA - SANTA CLARA CA 95052 - US +18-B6-F7 (hex) NEW POS TECHNOLOGY LIMITED +18B6F7 (base 16) NEW POS TECHNOLOGY LIMITED + AB Unit, 14th Floor,Block A, Financial Technology Building.No. 11 Keyuan Rd + Shenzhen 518057 + CN -A0-28-33 (hex) IEEE Registration Authority -A02833 (base 16) IEEE Registration Authority +1C-82-59 (hex) IEEE Registration Authority +1C8259 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -00-1B-6E (hex) Keysight Technologies, Inc. -001B6E (base 16) Keysight Technologies, Inc. - 1400 Fountaingrove Pkwy. - Santa Rosa CA 95403 - US - -14-37-19 (hex) PT Prakarsa Visi Valutama -143719 (base 16) PT Prakarsa Visi Valutama - Jl. Cideng Timur No.11D - Jakarta Pusat Indonesia 10130 - ID - -58-2F-40 (hex) Nintendo Co.,Ltd -582F40 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - -08-90-BA (hex) Danlaw Inc -0890BA (base 16) Danlaw Inc - 23700 research Dr. - Farmington Hills 48335 - US - -4C-36-4E (hex) Panasonic Connect Co., Ltd. -4C364E (base 16) Panasonic Connect Co., Ltd. - 4-1-62 Minoshima, Hakata-ku - Fukuoka-shi Fukuoka 812-8531 - JP - -BC-A5-8B (hex) Samsung Electronics Co.,Ltd -BCA58B (base 16) Samsung Electronics Co.,Ltd +00-FA-21 (hex) Samsung Electronics Co.,Ltd +00FA21 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -80-CE-B9 (hex) Samsung Electronics Co.,Ltd -80CEB9 (base 16) Samsung Electronics Co.,Ltd +7C-23-02 (hex) Samsung Electronics Co.,Ltd +7C2302 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -E8-5D-86 (hex) CHANG YOW TECHNOLOGIES INTERNATIONAL CO.,LTD. -E85D86 (base 16) CHANG YOW TECHNOLOGIES INTERNATIONAL CO.,LTD. - No 88 Shuren 6th St Wufong District - Taichung 413 - TW - -94-A3-CA (hex) KonnectONE, LLC -94A3CA (base 16) KonnectONE, LLC - 30 N Gould Street STE 4004 - Sheridan WY 82801 - US - -D0-D3-FC (hex) Mios, Ltd. -D0D3FC (base 16) Mios, Ltd. - 645 W. 9th St. - Los Angeles CA 90015 - US - -6C-6C-D3 (hex) Cisco Systems, Inc -6C6CD3 (base 16) Cisco Systems, Inc +6C-AB-05 (hex) Cisco Systems, Inc +6CAB05 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -E0-49-ED (hex) Audeze LLC -E049ED (base 16) Audeze LLC - 3410 S Susan st - Santa Ana CA 92704 - US - -00-01-57 (hex) SYSWAVE CO., LTD -000157 (base 16) SYSWAVE CO., LTD - Dongho B/D 5F, 221-2 - KOREA 135-010 - KR +5C-B1-5F (hex) Oceanblue Cloud Technology Limited +5CB15F (base 16) Oceanblue Cloud Technology Limited + 253-261 Hennessy Road + Hong Kong Hong Kong 999077 + HK -A8-01-6D (hex) Aiwa Corporation -A8016D (base 16) Aiwa Corporation - 965 W Chicago Ave - Chicago IL 60642 +48-5D-EB (hex) Just Add Power +485DEB (base 16) Just Add Power + 12505 STARKEY RD STE A + LARGO FL 33773 US -04-40-A9 (hex) New H3C Technologies Co., Ltd -0440A9 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +50-13-95 (hex) Sichuan AI-Link Technology Co., Ltd. +501395 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou,Industrial Park + Anzhou,Industrial Park Sichuan 621000 CN -24-4C-E3 (hex) Amazon Technologies Inc. -244CE3 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno 89507 - US +18-D9-EF (hex) Shuttle Inc. +18D9EF (base 16) Shuttle Inc. + No. 30 Lane 76, Rei Kuang Rd + Taipei 114 + TW -B8-BE-F4 (hex) devolo AG -B8BEF4 (base 16) devolo AG - Charlottenburger Allee 67 - Aachen NRW 52068 +88-DA-33 (hex) Beijing Xiaoyuer Network Technology Co., Ltd +88DA33 (base 16) Beijing Xiaoyuer Network Technology Co., Ltd + Block K1, North American International Business Centre, 86 Beiyuan Road, Chaoyang District + Beijing Beijing 100012 + CN + +84-C7-8F (hex) APS Networks GmbH +84C78F (base 16) APS Networks GmbH + Rosenwiesstr. 17 + Stuttgart 70567 DE -58-FD-BE (hex) Shenzhen Taikaida Technology Co., Ltd -58FDBE (base 16) Shenzhen Taikaida Technology Co., Ltd - Shenzhen Baoan District Fuyong town Fengtang road Xintian building 613 - shenzhen 518102 +C0-9F-E1 (hex) zte corporation +C09FE1 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -F4-F1-97 (hex) EMTAKE Inc -F4F197 (base 16) EMTAKE Inc - 14, Pangyoyeok ro 192, Bundang gu - Seongnam city Kyeonggi do 13524 - KR +18-AA-CA (hex) Sichuan tianyi kanghe communications co., LTD +18AACA (base 16) Sichuan tianyi kanghe communications co., LTD + No.198, section 1, xueshan avenue, jinyuan town, dayi county, sichuan province + chengdu sichuan 611330 + CN -6C-ED-51 (hex) NEXCONTROL Co.,Ltd -6CED51 (base 16) NEXCONTROL Co.,Ltd - (#303-1007, Ssangyong 3th) 397, Seokcheon-ro - Bucheon-si Gyeonggi-do 14449 +D4-9D-C0 (hex) Samsung Electronics Co.,Ltd +D49DC0 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 KR -10-62-E5 (hex) Hewlett Packard -1062E5 (base 16) Hewlett Packard - 11445 Compaq Center Drive - Houston TX 77070 +D0-19-6A (hex) Ciena Corporation +D0196A (base 16) Ciena Corporation + 7035 Ridge Road + Hanover MD 21076 US -04-C3-E6 (hex) IEEE Registration Authority -04C3E6 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +84-FD-D1 (hex) Intel Corporate +84FDD1 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -98-BB-99 (hex) Phicomm (Sichuan) Co.,Ltd. -98BB99 (base 16) Phicomm (Sichuan) Co.,Ltd. - 125 longquan street park road,longquan district,chengdu city - Sichuan Chengdu 610015 +B0-70-0D (hex) Nokia +B0700D (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +00-21-75 (hex) Pacific Satellite International Ltd. +002175 (base 16) Pacific Satellite International Ltd. + 20/F Tai Tung Building, + Wanchai 100000 + HK + +34-6B-5B (hex) New H3C Technologies Co., Ltd +346B5B (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -00-26-22 (hex) COMPAL INFORMATION (KUNSHAN) CO., LTD. -002622 (base 16) COMPAL INFORMATION (KUNSHAN) CO., LTD. - NO. 25, THE 3RD Street KUNSHAN EXPORT PROCESSING ZONE - KUNSHAN SUZHOU 215300 +84-E8-92 (hex) Actiontec Electronics, Inc +84E892 (base 16) Actiontec Electronics, Inc + 301 Olcott St + Santa Clara CA 95054 + US + +78-E2-BD (hex) Vodafone Automotive S.p.A. +78E2BD (base 16) Vodafone Automotive S.p.A. + via Astico 41 + Varese Italy/VA 21100 + IT + +F8-48-FD (hex) China Mobile Group Device Co.,Ltd. +F848FD (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 CN -00-20-B5 (hex) YASKAWA ELECTRIC CORPORATION -0020B5 (base 16) YASKAWA ELECTRIC CORPORATION - 2-1 Kurosakishiroishi, Yahatanishi-ku, - Kitakyushu 806-0004 +50-41-B9 (hex) I-O DATA DEVICE,INC. +5041B9 (base 16) I-O DATA DEVICE,INC. + 3-10,Sakurada-machi + Kanazawa Ishikawa 920-8512 JP -28-63-36 (hex) Siemens AG -286336 (base 16) Siemens AG - Werner-von-Siemens Strasse 50 - Amberg 92224 - DE +80-DA-BC (hex) Megafone Limited +80DABC (base 16) Megafone Limited + Unit 702,7/F,Bankok Bank Building,NO.18 Bonham Strand West + Hong Kong 999077 + HK -14-D1-69 (hex) HUAWEI TECHNOLOGIES CO.,LTD -14D169 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +20-DA-22 (hex) HUAWEI TECHNOLOGIES CO.,LTD +20DA22 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -1C-C3-EB (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -1CC3EB (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN +18-46-44 (hex) Home Control Singapore Pte Ltd +184644 (base 16) Home Control Singapore Pte Ltd + 151 Lorong Chuan + Singapore 556741 + SG -F4-60-E2 (hex) Xiaomi Communications Co Ltd -F460E2 (base 16) Xiaomi Communications Co Ltd - The Rainbow City of China Resources - NO.68, Qinghe Middle Street Haidian District, Beijing 100085 +C8-21-DA (hex) Shenzhen YOUHUA Technology Co., Ltd +C821DA (base 16) Shenzhen YOUHUA Technology Co., Ltd + Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District + Shenzhen Guangdong 518055 CN -E4-D1-24 (hex) Mojo Networks, Inc. -E4D124 (base 16) Mojo Networks, Inc. - 339 N.Bernardo Ave - Mountain View CA 94043 - US - -9C-C9-50 (hex) Baumer Holding -9CC950 (base 16) Baumer Holding - Hummelstrasse 17 - Frauenfeld Thurgau 8501 - CH - -F8-99-10 (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. -F89910 (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. - Phase 3, Bayan Lepas FIZ - Bayan Lepas Penang 11900 - MY - -50-E0-EF (hex) Nokia -50E0EF (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA - -60-F1-8A (hex) HUAWEI TECHNOLOGIES CO.,LTD -60F18A (base 16) HUAWEI TECHNOLOGIES CO.,LTD +20-65-8E (hex) HUAWEI TECHNOLOGIES CO.,LTD +20658E (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -C4-95-00 (hex) Amazon Technologies Inc. -C49500 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - -68-DD-26 (hex) Shanghai Focus Vision Security Technology Co.,Ltd -68DD26 (base 16) Shanghai Focus Vision Security Technology Co.,Ltd - No.4888 Hunan Rd, Pudong New District - Shanghai Shanghai 201317 +18-3D-5E (hex) HUAWEI TECHNOLOGIES CO.,LTD +183D5E (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -28-66-E3 (hex) AzureWave Technology Inc. -2866E3 (base 16) AzureWave Technology Inc. - 8F., No. 94, Baozhong Rd. - New Taipei City Taiwan 231 - TW +88-97-46 (hex) Sichuan AI-Link Technology Co., Ltd. +889746 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou,Industrial Park + Anzhou,Industrial Park Sichuan 621000 + CN -84-8A-8D (hex) Cisco Systems, Inc -848A8D (base 16) Cisco Systems, Inc +1C-DE-57 (hex) Fiberhome Telecommunication Technologies Co.,LTD +1CDE57 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN + +E0-DC-FF (hex) Xiaomi Communications Co Ltd +E0DCFF (base 16) Xiaomi Communications Co Ltd + The Rainbow City of China Resources + NO.68, Qinghe Middle Street Haidian District, Beijing 100085 + CN + +00-77-8D (hex) Cisco Systems, Inc +00778D (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -E0-62-67 (hex) Xiaomi Communications Co Ltd -E06267 (base 16) Xiaomi Communications Co Ltd - The Rainbow City of China Resources - NO.68, Qinghe Middle Street Haidian District, Beijing 100085 +00-0E-8C (hex) Siemens AG +000E8C (base 16) Siemens AG + Siemensstraße 10 + Regensburg 93055 + DE + +00-87-64 (hex) Cisco Systems, Inc +008764 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +84-6F-CE (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +846FCE (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -70-B7-AA (hex) vivo Mobile Communication Co., Ltd. -70B7AA (base 16) vivo Mobile Communication Co., Ltd. - #283,BBK Road - Wusha,Chang'An DongGuan City,Guangdong, 523860 +B0-E7-1D (hex) Shanghai Maigantech Co.,Ltd +B0E71D (base 16) Shanghai Maigantech Co.,Ltd + Room 2211,No.88 Caoxi North Rd,Xuhui District + Shanghai Shanghai 200030 CN -00-13-A3 (hex) Siemens Home & Office Comm. Devices -0013A3 (base 16) Siemens Home & Office Comm. Devices - 4849 Alpha Road - Dallas 75244 +C4-68-D0 (hex) VTech Telecommunications Ltd. +C468D0 (base 16) VTech Telecommunications Ltd. + 23/F, Tai Ping Industrial Centre, Block 1, + HONG KONG NA 000000 + HK + +14-AE-DB (hex) VTech Telecommunications Ltd. +14AEDB (base 16) VTech Telecommunications Ltd. + 23/F, Tai Ping Industrial Centre, Block 1, + HONG KONG NA 000000 + HK + +78-DB-2F (hex) Texas Instruments +78DB2F (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -08-25-25 (hex) Xiaomi Communications Co Ltd -082525 (base 16) Xiaomi Communications Co Ltd - The Rainbow City of China Resources - NO.68, Qinghe Middle Street Haidian District, Beijing 100085 +E0-B6-55 (hex) Beijing Xiaomi Electronics Co., Ltd. +E0B655 (base 16) Beijing Xiaomi Electronics Co., Ltd. + Building C, QingHe ShunShiJiaYe Technology Park, #66 ZhuFang Rd, HaiDian District + Beijing Beijing 10085 CN -84-B3-1B (hex) Kinexon GmbH -84B31B (base 16) Kinexon GmbH - Schellingstrasse, 35 - München 80799 +8C-0F-A0 (hex) di-soric GmbH & Co. KG +8C0FA0 (base 16) di-soric GmbH & Co. KG + Steinbeisstrasse 6 + Urbach 73660 DE -F8-27-2E (hex) Mercku -F8272E (base 16) Mercku - 509 Beaver Creek Rd. - Waterloo Ontario N2V 2L3 - CA +DC-FB-48 (hex) Intel Corporate +DCFB48 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -CC-50-E3 (hex) Espressif Inc. -CC50E3 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +DC-71-37 (hex) zte corporation +DC7137 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -A8-3E-0E (hex) HMD Global Oy -A83E0E (base 16) HMD Global Oy - Bertel Jungin aukio 9 - Espoo 02600 - FI - -10-C1-72 (hex) HUAWEI TECHNOLOGIES CO.,LTD -10C172 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +84-7C-9B (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +847C9B (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN -00-0E-EE (hex) Muco Industrie BV -000EEE (base 16) Muco Industrie BV - Pleimuiden 12e - Amsterdam NH 1046 AG - NL +34-41-A8 (hex) ER-Telecom +3441A8 (base 16) ER-Telecom + Ovchinnikovskaya embankment, 20, Building 1 + Moscow 115324 + RU -7C-1C-4E (hex) LG Innotek -7C1C4E (base 16) LG Innotek +74-40-BE (hex) LG Innotek +7440BE (base 16) LG Innotek 26, Hanamsandan 5beon-ro Gwangju Gwangsan-gu 506-731 KR -D8-B6-B7 (hex) Comtrend Corporation -D8B6B7 (base 16) Comtrend Corporation - 3F-1, 10 Lane 609, Chongxin Road, Section 5, - New Taipei City, Taiwan 24159 - TW - -8C-14-B4 (hex) zte corporation -8C14B4 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +A4-CF-12 (hex) Espressif Inc. +A4CF12 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN -3C-98-72 (hex) Sercomm Corporation. -3C9872 (base 16) Sercomm Corporation. - 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen - Miao-Lih Hsuan 115 - TW - -40-C3-C6 (hex) SnapRoute -40C3C6 (base 16) SnapRoute - 3960 Freedom Circle, Suite 100 - Santa Clara CA 95054 +FC-B6-62 (hex) IC Holdings LLC +FCB662 (base 16) IC Holdings LLC + 1277 Windmill Ln. + Midway UT 84049 US -D0-1C-BB (hex) Beijing Ctimes Digital Technology Co., Ltd. -D01CBB (base 16) Beijing Ctimes Digital Technology Co., Ltd. - 7th Floor, Jinzhou Building, Suzhou Street, No.79, Haidian District, - Beijing 100089 - CN - -74-87-BB (hex) Ciena Corporation -7487BB (base 16) Ciena Corporation - 7035 Ridge Road - Hanover MD 21076 - US +04-D4-C4 (hex) ASUSTek COMPUTER INC. +04D4C4 (base 16) ASUSTek COMPUTER INC. + 15,Li-Te Rd., Peitou, Taipei 112, Taiwan + Taipei Taiwan 112 + TW -C0-B6-F9 (hex) Intel Corporate -C0B6F9 (base 16) Intel Corporate +A0-51-0B (hex) Intel Corporate +A0510B (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -DC-E3-05 (hex) AO -DCE305 (base 16) AO - Prospekt Mira - Moscow 129223 - RU - -A4-DA-32 (hex) Texas Instruments -A4DA32 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - -78-04-73 (hex) Texas Instruments -780473 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +74-88-BB (hex) Cisco Systems, Inc +7488BB (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -50-4C-7E (hex) THE 41ST INSTITUTE OF CETC -504C7E (base 16) THE 41ST INSTITUTE OF CETC - No.98 Xiangjiang Road,Huangdao District,Qingdao,Shandong - Qingdao Shangdong 266555 +A8-E2-C3 (hex) Shenzhen YOUHUA Technology Co., Ltd +A8E2C3 (base 16) Shenzhen YOUHUA Technology Co., Ltd + Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District + Shenzhen Guangdong 518055 CN -2C-47-59 (hex) Beijing MEGA preponderance Science & Technology Co. Ltd -2C4759 (base 16) Beijing MEGA preponderance Science & Technology Co. Ltd - Room 2201,No.8,Ruichuang International B Block,Wangjing East Road - Beijing 100102 - CN +0C-A0-6C (hex) Industrial Cyber Sensing Inc. +0CA06C (base 16) Industrial Cyber Sensing Inc. + Unit 1A - 343 Montrose Street North + Cambridge Ontario N3H 2H6 + CA -DC-E0-EB (hex) Nanjing Aozheng Information Technology Co.Ltd -DCE0EB (base 16) Nanjing Aozheng Information Technology Co.Ltd - #E1-453, Zidong Road #1,Qixia District - Nanjing jiangsu 210000 - CN +FC-D2-B6 (hex) IEEE Registration Authority +FCD2B6 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -00-40-9D (hex) DigiBoard -00409D (base 16) DigiBoard - 6400 FLYING CLOUD DRIVE - EDEN PRAIRIE MN 55344 +80-4A-14 (hex) Apple, Inc. +804A14 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -14-93-46 (hex) PNI sensor corporation -149346 (base 16) PNI sensor corporation - 2331 Circadian Way - Santa Rosa CA 95407 +70-3C-69 (hex) Apple, Inc. +703C69 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-13-8A (hex) Qingdao GoerTek Technology Co., Ltd. -00138A (base 16) Qingdao GoerTek Technology Co., Ltd. - Room 605,Innovation Building,Hi-tech Industrial Park, - QINGDAO SHANDONG 266061 - CN +AC-2D-A9 (hex) TECNO MOBILE LIMITED +AC2DA9 (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 + Hong Kong Hong Kong 999077 + HK -A8-30-AD (hex) WEIFANG GOERTEK ELECTRONICS CO.,LTD -A830AD (base 16) WEIFANG GOERTEK ELECTRONICS CO.,LTD - Wei fang Export processing Zone - Wei Fang Shan Dong 261205 - CN +40-A9-3F (hex) Pivotal Commware, Inc. +40A93F (base 16) Pivotal Commware, Inc. + 1555 132nd Ave. NE + Bellevue WA 98005 + US -A4-15-66 (hex) WEIFANG GOERTEK ELECTRONICS CO.,LTD -A41566 (base 16) WEIFANG GOERTEK ELECTRONICS CO.,LTD - Wei fang Export processing Zone - Wei Fang Shan Dong 261205 - CN +4C-6A-F6 (hex) HMD Global Oy +4C6AF6 (base 16) HMD Global Oy + Bertel Jungin aukio 9 + Espoo 02600 + FI -1C-96-5A (hex) WEIFANG GOERTEK ELECTRONICS CO.,LTD -1C965A (base 16) WEIFANG GOERTEK ELECTRONICS CO.,LTD - Gaoxin 2 Road,Free Trade Zone,Weifang,Shandong,261205,P.R.China - Wei Fang Shan Dong 261205 - CN +48-9D-D1 (hex) Samsung Electronics Co.,Ltd +489DD1 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -40-1B-5F (hex) WEIFANG GOERTEK ELECTRONICS CO.,LTD -401B5F (base 16) WEIFANG GOERTEK ELECTRONICS CO.,LTD - Gaoxin 2 Road,Free Trade Zone,Weifang,Shandong,261205,P.R.China - Weifang Shandong 261205 +C0-8C-71 (hex) Motorola Mobility LLC, a Lenovo Company +C08C71 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +48-04-9F (hex) ELECOM CO., LTD +48049F (base 16) ELECOM CO., LTD + 9FLand Axis Tower.1-1 fushimi machi,4-chome chuoku + osaka 5418765 + JP + +08-7F-98 (hex) vivo Mobile Communication Co., Ltd. +087F98 (base 16) vivo Mobile Communication Co., Ltd. + #283,BBK Road + Wusha,Chang'An DongGuan City,Guangdong, 523860 CN -14-4F-8A (hex) Intel Corporate -144F8A (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +C0-41-21 (hex) Nokia Solutions and Networks GmbH & Co. KG +C04121 (base 16) Nokia Solutions and Networks GmbH & Co. KG + Werinherstrasse 91 + München Bavaria D-81541 + DE -00-21-06 (hex) RIM Testing Services -002106 (base 16) RIM Testing Services - 440 Phillip Street - Waterloo ON N2L 5R9 +F4-6F-4E (hex) Echowell +F46F4E (base 16) Echowell + 7F-8, No. 8, Sec 1, JunShing Rd. + New Taipei City 24872 + TW + +B0-6F-E0 (hex) Samsung Electronics Co.,Ltd +B06FE0 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +44-B9-94 (hex) Douglas Lighting Controls +44B994 (base 16) Douglas Lighting Controls + 280 - 3605 Gilmore Way + Burnaby BC V5G4X5 CA -5C-CD-7C (hex) MEIZU Technology Co.,Ltd. -5CCD7C (base 16) MEIZU Technology Co.,Ltd. - MEIZU Tech Bldg., Technology& Innovation Coast - Zhuhai Guangdong 519085 - CN +24-79-F8 (hex) KUPSON spol. s r.o. +2479F8 (base 16) KUPSON spol. s r.o. + Hradecka 787/14 + Opava Czech Republic 74601 + CZ -5C-96-56 (hex) AzureWave Technology Inc. -5C9656 (base 16) AzureWave Technology Inc. - 8F., No. 94, Baozhong Rd. - New Taipei City Taiwan 231 - TW +70-BF-92 (hex) GN Audio A/S +70BF92 (base 16) GN Audio A/S + Lautrupbjerg 7 + Ballerup DK-2750 + DK -E0-60-66 (hex) Sercomm Corporation. -E06066 (base 16) Sercomm Corporation. - 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen - Miao-Lih Hsuan 115 - TW +AC-BB-61 (hex) YSTen Technology Co.,Ltd +ACBB61 (base 16) YSTen Technology Co.,Ltd + Room 1715,17/F North Star Times Tower,Chaoyang District,Beijing. + Beijing 100101 + CN -BC-5F-F6 (hex) MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. -BC5FF6 (base 16) MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. - Mid-Fourth Flr.,Building 28,Cui Xi Fourth Road,Ke Yuan West,Nanshan - Shenzhen Guangdong 518057 +34-20-03 (hex) Shenzhen Feitengyun Technology Co.,LTD +342003 (base 16) Shenzhen Feitengyun Technology Co.,LTD + 7F 4building,Yalianhaoshida industrial Park + Shenzhen Guangdong 518100 CN -C8-E7-D8 (hex) MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. -C8E7D8 (base 16) MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. - Mid-Fourth Flr., Building 28, Cui Xi Fourth Road,Ke Yuan West,Nanshan - Shenzhen Guangdong 518057 +7C-FD-82 (hex) GUANGDONG GENIUS TECHNOLOGY CO., LTD. +7CFD82 (base 16) GUANGDONG GENIUS TECHNOLOGY CO., LTD. + No.168, Middle Road Of East Gate + Xiaobian Community Chang'an Town 523851 CN -A8-E5-52 (hex) JUWEL Aquarium AG & Co. KG -A8E552 (base 16) JUWEL Aquarium AG & Co. KG - Karl-Göx-Straße 1 - Rotenburg / Wümme 27356 - DE +00-2F-5C (hex) Cisco Systems, Inc +002F5C (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -60-D2-1C (hex) Sunnovo International Limited -60D21C (base 16) Sunnovo International Limited - 1717 Haitai Building - Beijing Beijing 100083 - CN +F4-64-5D (hex) Toshiba +F4645D (base 16) Toshiba + 2-9,Suehiro-Cho + Ome Tokyo 1988710 + JP -CC-51-B4 (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. -CC51B4 (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. - Phase 3, Bayan Lepas FIZ - Bayan Lepas Penang 11900 - MY +F0-7D-68 (hex) D-Link Corporation +F07D68 (base 16) D-Link Corporation + No.289, Sinhu 3rd Rd., Neihu District, + Taipei City 114 + TW -00-C3-F4 (hex) Samsung Electronics Co.,Ltd -00C3F4 (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR +EC-41-18 (hex) XIAOMI Electronics,CO.,LTD +EC4118 (base 16) XIAOMI Electronics,CO.,LTD + Xiaomi Building, No.68 Qinghe Middle Street + Haidian District Beijing 100085 + CN -B8-8A-EC (hex) Nintendo Co.,Ltd -B88AEC (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP +D8-86-0B (hex) IEEE Registration Authority +D8860B (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -78-72-5D (hex) Cisco Systems, Inc -78725D (base 16) Cisco Systems, Inc +08-EC-F5 (hex) Cisco Systems, Inc +08ECF5 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -B4-6B-FC (hex) Intel Corporate -B46BFC (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -B0-FC-0D (hex) Amazon Technologies Inc. -B0FC0D (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +D0-76-50 (hex) IEEE Registration Authority +D07650 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -CC-C9-2C (hex) Schindler - PORT Technology -CCC92C (base 16) Schindler - PORT Technology - via della Pace 22 - Locarno Ticino 6600 - CH - -00-1E-39 (hex) Comsys Communication Ltd. -001E39 (base 16) Comsys Communication Ltd. - 9 Hamenofim st. - Herzelia 46725 - IL +60-D0-A9 (hex) Samsung Electronics Co.,Ltd +60D0A9 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -EC-8C-9A (hex) HUAWEI TECHNOLOGIES CO.,LTD -EC8C9A (base 16) HUAWEI TECHNOLOGIES CO.,LTD +88-CE-FA (hex) HUAWEI TECHNOLOGIES CO.,LTD +88CEFA (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -B4-86-55 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B48655 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +00-27-06 (hex) YOISYS +002706 (base 16) YOISYS + 309-52 SUNGSU-2GA, 1DONG, SUNGDONG-GU + SEOUL 133-827 + KR -D0-D7-83 (hex) HUAWEI TECHNOLOGIES CO.,LTD -D0D783 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +04-2D-B4 (hex) First Property (Beijing) Co., Ltd Modern MOMA Branch +042DB4 (base 16) First Property (Beijing) Co., Ltd Modern MOMA Branch + Room 301A,Building No.10, No.1 Xiangheyuan Road, Dongcheng District, Beijing City + Beijing Beijing 100028 CN -AC-3B-77 (hex) Sagemcom Broadband SAS -AC3B77 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -FC-E6-6A (hex) Industrial Software Co -FCE66A (base 16) Industrial Software Co - 85, Aleksandyr Malinov Blvd. Office 6 - Sofia 1715 - BG +38-F8-5E (hex) HUMAX Co., Ltd. +38F85E (base 16) HUMAX Co., Ltd. + HUMAX Village, 216, Hwangsaeul-ro, Bu + Seongnam-si Gyeonggi-do 463-875 + KR -78-36-CC (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -7836CC (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +C4-64-B7 (hex) Fiberhome Telecommunication Technologies Co.,LTD +C464B7 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -70-C8-33 (hex) Wirepas Oy -70C833 (base 16) Wirepas Oy - Visiokatu 4 - Tampere 33720 - FI +04-E0-B0 (hex) Shenzhen YOUHUA Technology Co., Ltd +04E0B0 (base 16) Shenzhen YOUHUA Technology Co., Ltd + Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District + Shenzhen Guangdong 518055 + CN -0C-73-EB (hex) IEEE Registration Authority -0C73EB (base 16) IEEE Registration Authority +38-B1-9E (hex) IEEE Registration Authority +38B19E (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -F0-B5-D1 (hex) Texas Instruments -F0B5D1 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - -00-E0-00 (hex) FUJITSU LIMITED -00E000 (base 16) FUJITSU LIMITED - 403, Kosugi-cho 1-chome, Nakahara-ku - Kawasaki Kanagawa 211-0063 - JP - -90-84-8B (hex) HDR10+ Technologies, LLC -90848B (base 16) HDR10+ Technologies, LLC - 3855 SW 153rd Drive - Beaverton OR 97006 - US - -0C-F5-A4 (hex) Cisco Systems, Inc -0CF5A4 (base 16) Cisco Systems, Inc - 170 West Tasman Drive - San Jose CA 95134 - US - -80-C7-C5 (hex) Fiberhome Telecommunication Technologies Co.,LTD -80C7C5 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +38-E2-6E (hex) ShenZhen Sweet Rain Electronics Co.,Ltd. +38E26E (base 16) ShenZhen Sweet Rain Electronics Co.,Ltd. + Xinghua Road + Shenzhen Bao'an 518101 CN -28-16-A8 (hex) Microsoft Corporation -2816A8 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 +70-C9-C6 (hex) Cisco Systems, Inc +70C9C6 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -04-8A-E1 (hex) FLEXTRONICS MANUFACTURING(ZHUHAI)CO.,LTD. -048AE1 (base 16) FLEXTRONICS MANUFACTURING(ZHUHAI)CO.,LTD. - Xin Qing Science & Technology Industrial Park,Jin An Town,Doumen ,Zhuhai,Guangdong,PRC - Zhuhai Guangdong 519180 - CN +68-9A-87 (hex) Amazon Technologies Inc. +689A87 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -8C-CF-5C (hex) BEFEGA GmbH -8CCF5C (base 16) BEFEGA GmbH - Reichenbacher Str. 22 - Schwabach Bavaria 91126 +64-AE-88 (hex) Polytec GmbH +64AE88 (base 16) Polytec GmbH + Polytec Platz 1-7 + Waldbronn BW 76337 DE -50-1D-93 (hex) HUAWEI TECHNOLOGIES CO.,LTD -501D93 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -C8-D7-79 (hex) QING DAO HAIER TELECOM CO.,LTD. -C8D779 (base 16) QING DAO HAIER TELECOM CO.,LTD. - No 1 Haier road,Hi-tech Zone,Qingdao,PR.China - Qingdao Shandong 266101 - CN - -10-B3-6F (hex) Bowei Technology Company Limited -10B36F (base 16) Bowei Technology Company Limited - 2F,Building No.6C,1658,Gumei Rd - Shanghai Shanghai 200233 - CN +00-D0-50 (hex) Iskratel d.o.o. +00D050 (base 16) Iskratel d.o.o. + Ljubljanska cesta 24a + Kranj 4000 + SI -FC-9B-C6 (hex) Sumavision Technologies Co.,Ltd -FC9BC6 (base 16) Sumavision Technologies Co.,Ltd - 6F, Block A2, Power Creative Building,No.1 Shangdi East Road, Haidian District - Beijing 100085 +78-DA-A2 (hex) Cynosure Technologies Co.,Ltd +78DAA2 (base 16) Cynosure Technologies Co.,Ltd + Room 2708/2710, Building No.9A, Shenzhen Bay Science and Technology Ecological Park,Nanshan + Shenzhen city Guangdong Province 518057 CN -C8-29-2A (hex) Barun Electronics -C8292A (base 16) Barun Electronics - 869, Jangji-ri, Dongtan-myeon - Hwaseong-si Gyeonggi-do 445812 - KR - -00-80-BA (hex) SPECIALIX (ASIA) PTE, LTD -0080BA (base 16) SPECIALIX (ASIA) PTE, LTD - 3 WINTERSELLS ROAD - UNITED KINGDOM - US - -48-0B-B2 (hex) IEEE Registration Authority -480BB2 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +80-84-A9 (hex) oshkosh Corporation +8084A9 (base 16) oshkosh Corporation + 2307 Oregon Street + Oshkosh WI 54902 US -CC-C0-79 (hex) Murata Manufacturing Co., Ltd. -CCC079 (base 16) Murata Manufacturing Co., Ltd. - 1-10-1, Higashikotari - Nagaokakyo-shi Kyoto 617-8555 - JP +18-0D-2C (hex) Intelbras +180D2C (base 16) Intelbras + BR 101, km 210, S/N° + São José Santa Catarina 88104800 + BR -F0-9C-D7 (hex) Guangzhou Blue Cheetah Intelligent Technology Co., Ltd. -F09CD7 (base 16) Guangzhou Blue Cheetah Intelligent Technology Co., Ltd. - Panyu District, Guangzhou City Panyu Avenue North 555 Panyu Energy Technology Park,Industry Building 2 seats 406-407 - Guangzhou Guangdong 511400 +98-DA-C4 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +98DAC4 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan + Shenzhen Guangdong 518057 CN -BC-E1-43 (hex) Apple, Inc. -BCE143 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +00-17-7B (hex) Azalea Networks inc +00177B (base 16) Azalea Networks inc + 673 S Milpitas Blvd + Milpitas CA 95035 US -E4-82-CC (hex) Jumptronic GmbH -E482CC (base 16) Jumptronic GmbH - An der Weide 5 - Springe 31832 - DE +50-2B-98 (hex) Es-tech International +502B98 (base 16) Es-tech International + 228-70, Saneop-ro 155beon-gil, Gwonseon-gu, Suwon-si, Gyeonggi-do, Korea + Suwon 16648 + KR -B0-41-6F (hex) Shenzhen Maxtang Computer Co.,Ltd -B0416F (base 16) Shenzhen Maxtang Computer Co.,Ltd - 6/F, Bldg.3, Honghui Industrial Park, Liuxian 2nd Rd., Bao'an Dist. - Shenzhen Guangdong 518101 +C8-28-32 (hex) Beijing Xiaomi Electronics Co., Ltd. +C82832 (base 16) Beijing Xiaomi Electronics Co., Ltd. + Building C, QingHe ShunShiJiaYe Technology Park, #66 ZhuFang Rd, HaiDian District + Beijing Beijing 10085 CN -E0-19-D8 (hex) BH TECHNOLOGIES -E019D8 (base 16) BH TECHNOLOGIES - 12 RUE AMPERE - GRENOBLE 38000 - FR +70-1B-FB (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. +701BFB (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. + Phase 3, Bayan Lepas FIZ + Bayan Lepas Penang 11900 + MY -48-60-5F (hex) LG Electronics (Mobile Communications) -48605F (base 16) LG Electronics (Mobile Communications) - 60-39, Gasan-dong, Geumcheon-gu - Seoul 153-801 - KR +04-76-6E (hex) ALPSALPINE CO,.LTD +04766E (base 16) ALPSALPINE CO,.LTD + 6-3-36 Furukawanakazato, + Osaki Miyagi-pref 989-6181 + JP -30-D9-D9 (hex) Apple, Inc. -30D9D9 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +AC-7A-4D (hex) ALPSALPINE CO,.LTD +AC7A4D (base 16) ALPSALPINE CO,.LTD + 6-1 + KAKUDA-CITY MIYAGI-PREF 981-1595 + JP -60-30-D4 (hex) Apple, Inc. -6030D4 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +38-C0-96 (hex) ALPSALPINE CO,.LTD +38C096 (base 16) ALPSALPINE CO,.LTD + 6-1 + KAKUDA-CITY MIYAGI-PREF 981-1595 + JP -F8-95-EA (hex) Apple, Inc. -F895EA (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +C4-34-6B (hex) Hewlett Packard +C4346B (base 16) Hewlett Packard + 11445 Compaq Center Drive + Houston 77070 US -18-F1-D8 (hex) Apple, Inc. -18F1D8 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +48-F1-7F (hex) Intel Corporate +48F17F (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -64-70-33 (hex) Apple, Inc. -647033 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +00-26-43 (hex) ALPSALPINE CO,.LTD +002643 (base 16) ALPSALPINE CO,.LTD + 1-2-1, Okinouchi, + Soma-city, Fukushima-pref., 976-8501 + JP -84-68-78 (hex) Apple, Inc. -846878 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +1C-34-77 (hex) Innovation Wireless +1C3477 (base 16) Innovation Wireless + 11869 Teale Street + Culver City CA 90230 US -C8-D0-83 (hex) Apple, Inc. -C8D083 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +4C-C7-D6 (hex) FLEXTRONICS MANUFACTURING(ZHUHAI)CO.,LTD. +4CC7D6 (base 16) FLEXTRONICS MANUFACTURING(ZHUHAI)CO.,LTD. + Xin Qing Science & Technology Industrial Park,Jin An Town,Doumen ,Zhuhai,Guangdong,PRC + Zhuhai Guangdong 519180 + CN + +C8-08-73 (hex) Ruckus Wireless +C80873 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 US -8C-4C-DC (hex) PLANEX COMMUNICATIONS INC. -8C4CDC (base 16) PLANEX COMMUNICATIONS INC. - Planex Volta Bldg., 2-11-9 Ebisu-Nishi,Shibuya-ku,Tokyo 150-0021,Japan - Tokyo Tokyo 150-0021 +00-16-97 (hex) NEC Corporation +001697 (base 16) NEC Corporation + 7-1, Shiba 5-chome Minato-ku, + tokyo Tokyo 108-8001 JP -BC-AB-7C (hex) TRnP KOREA Co Ltd -BCAB7C (base 16) TRnP KOREA Co Ltd - room1308,239 SoHyungRo,WonMiGu, - BuChunCity KyungKiDo 1135 - KR - -6C-38-38 (hex) Marking System Technology Co., Ltd. -6C3838 (base 16) Marking System Technology Co., Ltd. - 76-1, Hirakawa Yokomichi - Joyo-shi Kyoto 610-0101 +00-30-13 (hex) NEC Corporation +003013 (base 16) NEC Corporation + 1-10 Nisshincho, Fuchu + Tokyo 183-8501 0000 JP -9C-2E-A1 (hex) Xiaomi Communications Co Ltd -9C2EA1 (base 16) Xiaomi Communications Co Ltd +BC-3E-07 (hex) Hitron Technologies. Inc +BC3E07 (base 16) Hitron Technologies. Inc + No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C + Hsin-chu Taiwan 300 + TW + +48-FD-A3 (hex) Xiaomi Communications Co Ltd +48FDA3 (base 16) Xiaomi Communications Co Ltd The Rainbow City of China Resources NO.68, Qinghe Middle Street Haidian District, Beijing 100085 CN -0C-6A-BC (hex) Fiberhome Telecommunication Technologies Co.,LTD -0C6ABC (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +90-86-9B (hex) zte corporation +90869B (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -3C-CD-5D (hex) HUAWEI TECHNOLOGIES CO.,LTD -3CCD5D (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +E0-18-9F (hex) EM Microelectronic +E0189F (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH -7C-76-68 (hex) HUAWEI TECHNOLOGIES CO.,LTD -7C7668 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +F8-13-08 (hex) Nokia +F81308 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -00-13-86 (hex) ABB Inc/Totalflow -001386 (base 16) ABB Inc/Totalflow - 123 - Bartlesville OK 74006 - US +F8-A2-D6 (hex) Liteon Technology Corporation +F8A2D6 (base 16) Liteon Technology Corporation + 4F, 90, Chien 1 Road + New Taipei City Taiwan 23585 + TW -00-3C-10 (hex) Cisco Systems, Inc -003C10 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +04-9D-FE (hex) Hivesystem +049DFE (base 16) Hivesystem + 816 Kranz-techno Bldg. 388 dunchondaero, jun + Gyeonggi-do KSXX0024 + KR -F0-41-C8 (hex) IEEE Registration Authority -F041C8 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +D0-51-57 (hex) LEAX Arkivator Telecom +D05157 (base 16) LEAX Arkivator Telecom + NanShan District YueHaiMen Street + ShenZhen GuangDong 518061 + CN -CC-99-16 (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. -CC9916 (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. - Phase 3, Bayan Lepas FIZ - Bayan Lepas Penang 11900 - MY +FC-BE-7B (hex) vivo Mobile Communication Co., Ltd. +FCBE7B (base 16) vivo Mobile Communication Co., Ltd. + #283,BBK Road + Wusha,Chang'An DongGuan City,Guangdong, 523860 + CN -EC-7F-C6 (hex) ECCEL CORPORATION SAS -EC7FC6 (base 16) ECCEL CORPORATION SAS - CRA 106 15A 25 LT 88 MZ 17 BG 1, ZONA FRANCA BOGOTA - BOGOTA D.C. 110921 - CO +B4-0F-B3 (hex) vivo Mobile Communication Co., Ltd. +B40FB3 (base 16) vivo Mobile Communication Co., Ltd. + #283,BBK Road + Wusha,Chang'An DongGuan City,Guangdong, 523860 + CN -A4-38-CC (hex) Nintendo Co.,Ltd -A438CC (base 16) Nintendo Co.,Ltd +EC-5C-68 (hex) CHONGQING FUGUI ELECTRONICS CO.,LTD. +EC5C68 (base 16) CHONGQING FUGUI ELECTRONICS CO.,LTD. + Building D21,No.1, East Zone 1st Road,Xiyong Town,Shapingba District + Chongqing Chongqing 401332 + CN + +94-58-CB (hex) Nintendo Co.,Ltd +9458CB (base 16) Nintendo Co.,Ltd 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU KYOTO KYOTO 601-8501 JP -74-72-1E (hex) Edison Labs Inc. -74721E (base 16) Edison Labs Inc. - 1122 Stanyan St - San Francisco CA 94117 - US +74-36-6D (hex) Vodafone Italia S.p.A. +74366D (base 16) Vodafone Italia S.p.A. + Via Lorenteggio nr. 240 + Milan Italy 20147 + IT -78-0F-77 (hex) HangZhou Gubei Electronics Technology Co.,Ltd -780F77 (base 16) HangZhou Gubei Electronics Technology Co.,Ltd - HangZhou City, Zhejiang province Binjiang District Jiang Hong Road 611 Building 1 room 106 - Hangzhou ZheJiang 310052 - CN +18-2A-44 (hex) HIROSE ELECTRONIC SYSTEM +182A44 (base 16) HIROSE ELECTRONIC SYSTEM + 1-9-6 Ebisuminami + Shibuya Tokyo 150-0022 + JP -4C-AB-FC (hex) zte corporation -4CABFC (base 16) zte corporation +FC-94-CE (hex) zte corporation +FC94CE (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China shenzhen guangdong 518057 CN -7C-2A-31 (hex) Intel Corporate -7C2A31 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -0C-F3-46 (hex) Xiaomi Communications Co Ltd -0CF346 (base 16) Xiaomi Communications Co Ltd - The Rainbow City of China Resources - NO.68, Qinghe Middle Street Haidian District, Beijing 100085 +0C-EC-84 (hex) Shenzhen TINNO Mobile Technology Corp. +0CEC84 (base 16) Shenzhen TINNO Mobile Technology Corp. + Building, No.33, Xiandong Rd, Xili + Nanshan District, Shenzhen PRC 518053 CN -74-70-FD (hex) Intel Corporate -7470FD (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +9C-DB-07 (hex) Yellowtec GmbH +9CDB07 (base 16) Yellowtec GmbH + Heinrich-Hertz-Strasse 1-3 + Monheim am Rhein NRW 40789 + DE -50-65-F3 (hex) Hewlett Packard -5065F3 (base 16) Hewlett Packard - 11445 Compaq Center Drive - Houston 77070 +28-EC-9A (hex) Texas Instruments +28EC9A (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -3C-95-09 (hex) Liteon Technology Corporation -3C9509 (base 16) Liteon Technology Corporation - 4F, 90, Chien 1 Road - New Taipei City Taiwan 23585 - TW +C4-E5-06 (hex) Piper Networks, Inc. +C4E506 (base 16) Piper Networks, Inc. + 3636 Nobel Drive + San Diego CA 92122 + US -30-FD-38 (hex) Google, Inc. -30FD38 (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 +0C-D0-F8 (hex) Cisco Systems, Inc +0CD0F8 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -64-CB-5D (hex) SIA TeleSet -64CB5D (base 16) SIA TeleSet - Krāslavas iela 5 - Vecstropi, Naujenes par., Daugavpils distr. LV-5413 - LV +DC-DA-80 (hex) New H3C Technologies Co., Ltd +DCDA80 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN -58-21-E9 (hex) TWPI -5821E9 (base 16) TWPI - PMB# 335; 1121 Annapolis Road - Odenton MD 21113 +B8-25-9A (hex) Thalmic Labs +B8259A (base 16) Thalmic Labs + 24 Charles Street West + Kitchener Ontario N2G 1H2 + CA + +30-EB-5A (hex) LANDIS + GYR +30EB5A (base 16) LANDIS + GYR + 78th km Old National Road Athens-Corinth + Corinth 20100 + GR + +F8-0F-6F (hex) Cisco Systems, Inc +F80F6F (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -F0-E3-DC (hex) Tecon MT, LLC -F0E3DC (base 16) Tecon MT, LLC - 3rd Khoroshevskaya st - 20 - Moscow 123298 - RU +00-80-E3 (hex) CORAL NETWORK CORPORATION +0080E3 (base 16) CORAL NETWORK CORPORATION + (NOW BAY NETWORKS) + SANTA CLARA CA 95052-8185 + US -A8-DA-01 (hex) Shenzhen NUOLIJIA Digital Technology Co.,Ltd -A8DA01 (base 16) Shenzhen NUOLIJIA Digital Technology Co.,Ltd - A Area of The Second Flood and D Area of The First Floor,Factory Building A,Youxinda Industrial Park,Gengyu Road,Tianliao Community,Gongming Street Office,Guangming New District,Shenzhen City,Guangdong,P.R.China +D8-F2-CA (hex) Intel Corporate +D8F2CA (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +B4-C6-2E (hex) Molex CMS +B4C62E (base 16) Molex CMS + 2222 Wellington Court + Lisle IL 60532 + US + +28-25-36 (hex) SHENZHEN HOLATEK CO.,LTD +282536 (base 16) SHENZHEN HOLATEK CO.,LTD + Rm.1001,Unit 4,Bld.B,Kexing Science Park,Keyuan Road, Nanshan District Shenzhen Guangdong 518000 CN -88-E9-0F (hex) innomdlelab -88E90F (base 16) innomdlelab - Unnam 1 gil, 3 - Seocho-gu Seoul 06778 - KR - -00-10-D8 (hex) CALISTA -0010D8 (base 16) CALISTA - 56A Packhorse Road - Bucks SL9 8EF ENGLAND - GB +B8-A1-75 (hex) Roku, Inc. +B8A175 (base 16) Roku, Inc. + 12980 Saratoga Ave + Saratoga CA 95070 + US -00-21-94 (hex) Ping Communication -002194 (base 16) Ping Communication - Brenden 18 - Appenzell Meistersrüte AI 9050 - CH +CC-D3-C1 (hex) Vestel Elektronik San ve Tic. A.S. +CCD3C1 (base 16) Vestel Elektronik San ve Tic. A.S. + Organize san + Manisa Turket 45030 + TR -18-94-C6 (hex) ShenZhen Chenyee Technology Co., Ltd. -1894C6 (base 16) ShenZhen Chenyee Technology Co., Ltd. - 32F, Tower A, East Pacific International Center, No.7888, Shennan Avenue, Futian District - Shenzhen 518040 +A4-26-55 (hex) LTI Motion (Shanghai) Co., Ltd. +A42655 (base 16) LTI Motion (Shanghai) Co., Ltd. + NO.80, Lane 2927, LaiYang Road Pudong New District + Shanghai Shanghai 200137 CN -80-AD-16 (hex) Xiaomi Communications Co Ltd -80AD16 (base 16) Xiaomi Communications Co Ltd - The Rainbow City of China Resources - NO.68, Qinghe Middle Street Haidian District, Beijing 100085 +60-A7-30 (hex) Shenzhen Yipinfang Internet Technology Co.,Ltd +60A730 (base 16) Shenzhen Yipinfang Internet Technology Co.,Ltd + Shenzhen Konka R & D Building, 28th floor 21 + GuangDong Nanshan District 518000 CN -04-4E-AF (hex) LG Innotek -044EAF (base 16) LG Innotek - 26, Hanamsandan 5beon-ro - Gwangju Gwangsan-gu 506-731 - KR - -98-D8-63 (hex) Shanghai High-Flying Electronics Technology Co., Ltd -98D863 (base 16) Shanghai High-Flying Electronics Technology Co., Ltd - Room 1002 ,#1Building,No.3000 Longdong Avenue,Pudong District,Shanghai,China - shanghai shanghai 201203 - CN +3C-9B-D6 (hex) Vizio, Inc +3C9BD6 (base 16) Vizio, Inc + 39 Tesla + Irvine CA 92618 + US -C4-9F-4C (hex) HUAWEI TECHNOLOGIES CO.,LTD -C49F4C (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +50-DB-3F (hex) SHENZHEN GONGJIN ELECTRONICS CO.,LT +50DB3F (base 16) SHENZHEN GONGJIN ELECTRONICS CO.,LT + SONGGANG + SHENZHEN GUANGDONG 518105 CN -0C-70-4A (hex) HUAWEI TECHNOLOGIES CO.,LTD -0C704A (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +10-81-B4 (hex) Hunan Greatwall Galaxy Science and Technology Co.,Ltd. +1081B4 (base 16) Hunan Greatwall Galaxy Science and Technology Co.,Ltd. + No. 39, Jian Shan Road + Changsha Hunan 410205 CN -10-F9-EB (hex) Industria Fueguina de Relojería Electrónica s.a. -10F9EB (base 16) Industria Fueguina de Relojería Electrónica s.a. - Sarmiento 2920 - Rio Grande Tierra de Fuego V9420GIV - AR - -5C-5A-EA (hex) FORD -5C5AEA (base 16) FORD - 17425 Federal Drive - Allen Park MI 48101 +38-81-D7 (hex) Texas Instruments +3881D7 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -00-0B-7B (hex) Test-Um Inc. -000B7B (base 16) Test-Um Inc. - 808 Calle Plano - Camarillo CA 93012 +18-04-ED (hex) Texas Instruments +1804ED (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -BC-DD-C2 (hex) Espressif Inc. -BCDDC2 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +D4-32-60 (hex) GoPro +D43260 (base 16) GoPro + 3000 Clearview Way + San Mateo CA 94402 + US -C8-8F-26 (hex) Skyworth Digital Technology(Shenzhen) Co.,Ltd -C88F26 (base 16) Skyworth Digital Technology(Shenzhen) Co.,Ltd - 7F,Block A,Skyworth Building, - Shenzhen Guangdong 518057 - CN +F4-DD-9E (hex) GoPro +F4DD9E (base 16) GoPro + 3000 Clearview Way + San Mateo CA 94402 + US -20-36-5B (hex) Megafone Limited -20365B (base 16) Megafone Limited - Unit 702,7/F,Bankok Bank Building,NO.18 Bonham Strand West - Hong Kong 999077 - HK +D4-D9-19 (hex) GoPro +D4D919 (base 16) GoPro + 3000 Clearview Way + San Mateo CA 94402 + US -E8-DE-00 (hex) ChongQing GuanFang Technology Co.,LTD -E8DE00 (base 16) ChongQing GuanFang Technology Co.,LTD - 2F, A District,No.3 Middle Section of Mount Huangshan Avenue - ChongQing ChongQing 401121 +CC-72-86 (hex) Xi'an Fengyu Information Technology Co., Ltd. +CC7286 (base 16) Xi'an Fengyu Information Technology Co., Ltd. + 5F, Block A, STRC, No.10, Zhangba 5th Road, Yanta + Xi'an Shaanxi 710077 CN -FC-64-3A (hex) Samsung Electronics Co.,Ltd -FC643A (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +6C-A9-28 (hex) HMD Global Oy +6CA928 (base 16) HMD Global Oy + Bertel Jungin aukio 9 + Espoo 02600 + FI -00-30-74 (hex) EQUIINET LTD. -003074 (base 16) EQUIINET LTD. - EDISON HOUSE - SWINDON, SN3 5JA - GB +00-D8-61 (hex) Micro-Star INTL CO., LTD. +00D861 (base 16) Micro-Star INTL CO., LTD. + No.69, Lide St., + New Taipei City Taiwan 235 + TW -B0-B3-AD (hex) HUMAX Co., Ltd. -B0B3AD (base 16) HUMAX Co., Ltd. - HUMAX Village, 216, Hwangsaeul-ro, Bu - Seongnam-si Gyeonggi-do 463-875 - KR +74-C1-7D (hex) Infinix mobility limited +74C17D (base 16) Infinix mobility limited + RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG + HongKong HongKong 999077 + HK -40-BD-32 (hex) Texas Instruments -40BD32 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +14-11-14 (hex) TECNO MOBILE LIMITED +141114 (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 + Hong Kong Hong Kong 999077 + HK -CC-8E-71 (hex) Cisco Systems, Inc -CC8E71 (base 16) Cisco Systems, Inc +00-B8-B3 (hex) Cisco Systems, Inc +00B8B3 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -70-D0-81 (hex) Beijing Netpower Technologies Inc. -70D081 (base 16) Beijing Netpower Technologies Inc. - Room 201, Block B, NO. 15 Building, EastZone - Courtyard10, Xibeiwang East Road Haidian District, Beijing 100094 - CN - -70-C9-4E (hex) Liteon Technology Corporation -70C94E (base 16) Liteon Technology Corporation - 4F, 90, Chien 1 Road - New Taipei City Taiwan 23585 - TW - -38-F5-54 (hex) HISENSE ELECTRIC CO.,LTD -38F554 (base 16) HISENSE ELECTRIC CO.,LTD - No. 218, Qianwangang Rd - Qingdao Shandong 266555 - CN - -18-A2-8A (hex) Essel-T Co., Ltd -18A28A (base 16) Essel-T Co., Ltd - 1211 kranztechno, 388 Dunchon-daero - Seongnam-si Jungwon-gu, Gyeonggi-do 13403 - KR +74-5F-90 (hex) LAM Technologies +745F90 (base 16) LAM Technologies + Viale Ludovico Ariosto, 492/D + Sesto Fiorentino FIRENZE 50019 + IT -A8-51-5B (hex) Samsung Electronics Co.,Ltd -A8515B (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 +F8-5B-9C (hex) SB SYSTEMS Co.,Ltd +F85B9C (base 16) SB SYSTEMS Co.,Ltd + 2F Ventureforum, 323, Pangyo-ro, Bundang-gu + Seongnam-si Gyeonngi-do 453-400 KR -00-19-36 (hex) STERLITE OPTICAL TECHNOLOGIES LIMITED -001936 (base 16) STERLITE OPTICAL TECHNOLOGIES LIMITED - E-1,E-2,&E-3 - AURANGABAD MAHARASTRA 431136 - IN - -3C-E8-24 (hex) HUAWEI TECHNOLOGIES CO.,LTD -3CE824 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -54-B7-E5 (hex) Rayson Technology Co., Ltd. -54B7E5 (base 16) Rayson Technology Co., Ltd. - 1F No.9 R&D Rd.II, Science-Based Industrial Park - Hsin-Chu 300 - TW - -94-63-72 (hex) vivo Mobile Communication Co., Ltd. -946372 (base 16) vivo Mobile Communication Co., Ltd. - #283,BBK Road - Wusha,Chang'An DongGuan City,Guangdong, 523860 +00-42-79 (hex) Sunitec Enterprise Co.,Ltd +004279 (base 16) Sunitec Enterprise Co.,Ltd + 3F.,No.98-1,Mincyuan Rd.Sindian City + Taipei County 231 231141 CN -BC-0F-A7 (hex) Ouster -BC0FA7 (base 16) Ouster - 350 Treat Ave - San Francisco CA 94110 - US - -F0-C9-D1 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -F0C9D1 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 +A4-50-46 (hex) Xiaomi Communications Co Ltd +A45046 (base 16) Xiaomi Communications Co Ltd + The Rainbow City of China Resources + NO.68, Qinghe Middle Street Haidian District, Beijing 100085 CN -F8-C1-20 (hex) Xi'an Link-Science Technology Co.,Ltd -F8C120 (base 16) Xi'an Link-Science Technology Co.,Ltd - 1/F,Block F,Building zhichao Weilai,No.999,10#Caotan Road,Xi'an - xi'an 710016 +F8-50-1C (hex) Tianjin Geneuo Technology Co.,Ltd +F8501C (base 16) Tianjin Geneuo Technology Co.,Ltd + Technology Avenue South JingHai Economic Development Area,Tianjin China + Tianjin 301609 CN -B4-FB-F9 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B4FBF9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +70-D3-13 (hex) HUAWEI TECHNOLOGIES CO.,LTD +70D313 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -50-6F-77 (hex) HUAWEI TECHNOLOGIES CO.,LTD -506F77 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +9C-1D-36 (hex) HUAWEI TECHNOLOGIES CO.,LTD +9C1D36 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -0C-41-E9 (hex) HUAWEI TECHNOLOGIES CO.,LTD -0C41E9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +CC-BB-FE (hex) HUAWEI TECHNOLOGIES CO.,LTD +CCBBFE (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -70-F2-20 (hex) Actiontec Electronics, Inc -70F220 (base 16) Actiontec Electronics, Inc - 3301 Olcott St. - Santa Clara CA 95054 +44-07-0B (hex) Google, Inc. +44070B (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US -34-5A-06 (hex) SHARP Corporation -345A06 (base 16) SHARP Corporation - 1 Takumi-cho, Sakai-ku - Sakai City Osaka 590-8522 - JP +EC-F6-BD (hex) SNCF MOBILITÉS +ECF6BD (base 16) SNCF MOBILITÉS + 9 rue Jean-Philippe Rameau + SAINT-DENIS 93200 + FR -04-02-CA (hex) Shenzhen Vtsonic Co.,ltd -0402CA (base 16) Shenzhen Vtsonic Co.,ltd - No.35,the 2nd Industrial Zone,Tangxiayong Village,Songgang Town,Bao'an District,Shenzhen,China. - Shenzhen Guangdong 518102 - CN +B8-31-B5 (hex) Microsoft Corporation +B831B5 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US -3C-FB-5C (hex) Fiberhome Telecommunication Technologies Co.,LTD -3CFB5C (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN +00-7C-2D (hex) Samsung Electronics Co.,Ltd +007C2D (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR -74-40-BB (hex) Hon Hai Precision Ind. Co.,Ltd. -7440BB (base 16) Hon Hai Precision Ind. Co.,Ltd. - Building D21,No.1, East Zone 1st Road - Chongqing Chongqing 401332 - CN +C8-47-82 (hex) Areson Technology Corp. +C84782 (base 16) Areson Technology Corp. + 11F., No. 646, Sec. 5, Chongxin Rd., Sanchong District + New Taipei City 24158 + TW -88-BD-45 (hex) Samsung Electronics Co.,Ltd -88BD45 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +E8-93-63 (hex) Nokia +E89363 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -54-FC-F0 (hex) Samsung Electronics Co.,Ltd -54FCF0 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +7C-0C-F6 (hex) Guangdong Huiwei High-tech Co., Ltd. +7C0CF6 (base 16) Guangdong Huiwei High-tech Co., Ltd. + E Block No. 1 in Ecological Area in Puzhai NewArea + Fengshun County, Meizhou Guangdong province 514000 + CN -30-6A-85 (hex) Samsung Electronics Co.,Ltd -306A85 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +50-29-F5 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +5029F5 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN -4C-DD-31 (hex) Samsung Electronics Co.,Ltd -4CDD31 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +30-0A-60 (hex) IEEE Registration Authority +300A60 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -B4-DE-31 (hex) Cisco Systems, Inc -B4DE31 (base 16) Cisco Systems, Inc +80-D0-65 (hex) CKS Corporation +80D065 (base 16) CKS Corporation + 1-24-11 Akebono + Tachikawa Tokyo 190-0012 + JP + +00-D6-FE (hex) Cisco Systems, Inc +00D6FE (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -A4-40-27 (hex) zte corporation -A44027 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +0C-BF-74 (hex) Morse Micro +0CBF74 (base 16) Morse Micro + 113 / 2-4 Cornwallis Street + Eveleigh NSW 2015 + AU -B4-F7-A1 (hex) LG Electronics (Mobile Communications) -B4F7A1 (base 16) LG Electronics (Mobile Communications) - 60-39, Gasan-dong, Geumcheon-gu - Seoul 153-801 - KR +B4-1D-2B (hex) Shenzhen YOUHUA Technology Co., Ltd +B41D2B (base 16) Shenzhen YOUHUA Technology Co., Ltd + Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District + Shenzhen Guangdong 518055 + CN -70-EF-00 (hex) Apple, Inc. -70EF00 (base 16) Apple, Inc. +14-C2-13 (hex) Apple, Inc. +14C213 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -28-ED-E0 (hex) AMPAK Technology, Inc. -28EDE0 (base 16) AMPAK Technology, Inc. - No.1,Jen Ai Road Hsinchu Industrial Park, Hukou - Hsinchu Taiwan ROC. 30352 - TW +FC-8F-7D (hex) SHENZHEN GONGJIN ELECTRONICS CO.,LT +FC8F7D (base 16) SHENZHEN GONGJIN ELECTRONICS CO.,LT + SONGGANG + SHENZHEN GUANGDONG 518105 + CN -1C-11-61 (hex) Ciena Corporation -1C1161 (base 16) Ciena Corporation - 7035 Ridge Road - Hanover MD 21076 - US +24-BE-18 (hex) DADOUTEK COMPANY LIMITED +24BE18 (base 16) DADOUTEK COMPANY LIMITED + 14/F, Wilson Logistics Centre,No.24-28 Kung Yip St + Kwai Chung New Territories 000 + CN -C8-77-65 (hex) Tiesse SpA -C87765 (base 16) Tiesse SpA - Via Asti - Ivrea TO 10015 - IT +74-9D-79 (hex) Sercomm Corporation. +749D79 (base 16) Sercomm Corporation. + 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen + Miao-Lih Hsuan 115 + TW -D0-81-7A (hex) Apple, Inc. -D0817A (base 16) Apple, Inc. +A4-D9-31 (hex) Apple, Inc. +A4D931 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -98-CA-33 (hex) Apple, Inc. -98CA33 (base 16) Apple, Inc. +BC-FE-D9 (hex) Apple, Inc. +BCFED9 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -68-AB-1E (hex) Apple, Inc. -68AB1E (base 16) Apple, Inc. +80-82-23 (hex) Apple, Inc. +808223 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -BC-FF-EB (hex) Motorola Mobility LLC, a Lenovo Company -BCFFEB (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US - -2C-37-C5 (hex) Qingdao Haier Intelligent Home Appliance Technology Co.,Ltd -2C37C5 (base 16) Qingdao Haier Intelligent Home Appliance Technology Co.,Ltd - ingdao high-tech park haier road 1 - Qingdao Shandong 266101 - CN - -7C-76-30 (hex) Shenzhen YOUHUA Technology Co., Ltd -7C7630 (base 16) Shenzhen YOUHUA Technology Co., Ltd - Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District - Shenzhen Guangdong 518055 +CC-08-FB (hex) TP-LINK TECHNOLOGIES CO.,LTD. +CC08FB (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan + Shenzhen Guangdong 518057 CN -98-22-EF (hex) Liteon Technology Corporation -9822EF (base 16) Liteon Technology Corporation - 4F, 90, Chien 1 Road - New Taipei City Taiwan 23585 - TW - -7C-76-35 (hex) Intel Corporate -7C7635 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +BC-AF-91 (hex) TE Connectivity Sensor Solutions +BCAF91 (base 16) TE Connectivity Sensor Solutions + 4 rue Gaye-Marie, CS 83163 + Toulouse 31027 + FR -B8-07-16 (hex) vivo Mobile Communication Co., Ltd. -B80716 (base 16) vivo Mobile Communication Co., Ltd. +28-31-66 (hex) vivo Mobile Communication Co., Ltd. +283166 (base 16) vivo Mobile Communication Co., Ltd. #283,BBK Road Wusha,Chang'An DongGuan City,Guangdong, 523860 CN -78-80-38 (hex) FUNAI ELECTRIC CO., LTD. -788038 (base 16) FUNAI ELECTRIC CO., LTD. - 7-1, NAKAGAITO 7-CHOME - DAITO OSAKA 5740013 +C0-40-04 (hex) Medicaroid Corporation +C04004 (base 16) Medicaroid Corporation + 1-6-4, Minatojima-minamimachi, Chuo-ku + Kobe 650-0047 JP -F0-45-DA (hex) Texas Instruments -F045DA (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US - -1C-EE-C9 (hex) Elo touch solutions -1CEEC9 (base 16) Elo touch solutions - 1033 McCarthy Boulevard - Milpitas CA 95035 - US - -00-18-62 (hex) Seagate Technology -001862 (base 16) Seagate Technology - 1280 Disc Drive - Shakopee MN 55379 - US - -00-0C-50 (hex) Seagate Technology -000C50 (base 16) Seagate Technology - M/S NW1F01 - Longmont CO 80503 - US - -E4-F0-42 (hex) Google, Inc. -E4F042 (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US - -20-B3-99 (hex) Enterasys -20B399 (base 16) Enterasys - 50 Minuteman Rd - Andover MA 01810 +A4-ED-43 (hex) IEEE Registration Authority +A4ED43 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -CC-2D-21 (hex) Tenda Technology Co.,Ltd.Dongguan branch -CC2D21 (base 16) Tenda Technology Co.,Ltd.Dongguan branch - Room 79,Yuanyi Road,Dalang Town,Dongguan Guangdong 523770 - Dongguan Guangdong 523770 +94-29-8D (hex) Shanghai AdaptComm Technology Co., Ltd. +94298D (base 16) Shanghai AdaptComm Technology Co., Ltd. + 3rd Floor, Building 14, No. 518 Xinzhuan Road, Songjiang District, + Shanghai 201600 CN -00-40-97 (hex) DATEX DIVISION OF -004097 (base 16) DATEX DIVISION OF - INSTRUMENTARIUM CORP. - - FI - -40-48-FD (hex) IEEE Registration Authority -4048FD (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +00-AA-6E (hex) Cisco Systems, Inc +00AA6E (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -A8-EE-C6 (hex) Muuselabs NV/SA -A8EEC6 (base 16) Muuselabs NV/SA - Rue du Tocsin 12 - Brussels 1000 - BE - -9C-4F-CF (hex) TCT mobile ltd -9C4FCF (base 16) TCT mobile ltd - No.86 hechang 7th road, zhongkai, Hi-Tech District - Hui Zhou Guang Dong 516006 +F0-D7-DC (hex) Wesine (Wuhan) Technology Co., Ltd. +F0D7DC (base 16) Wesine (Wuhan) Technology Co., Ltd. + 10th Floor, Building 2, SBI Venture Street, Hongshan District + Wuhan Hubei 430074 CN -D8-96-E0 (hex) Alibaba Cloud Computing Ltd. -D896E0 (base 16) Alibaba Cloud Computing Ltd. - Yuhang District of Hangzhou Wenyi Road, Building 1, No. 969 Xixi Park, Zhejiang Province - Hangzhou Zhejiang 310000 +00-72-04 (hex) Samsung Electronics Co., Ltd. ARTIK +007204 (base 16) Samsung Electronics Co., Ltd. ARTIK + 1-1, Samsungjeonja-ro + Hwaseong-si Gyeonggi-do 18448 + KR + +40-C8-1F (hex) Shenzhen Xinguodu Technology Co., Ltd. +40C81F (base 16) Shenzhen Xinguodu Technology Co., Ltd. + F17A, JinSong Building, Tairan Industrial & Trade Park, Chegongmiao, Shennan Road,Futian District + Shenzhen Guangdong 518040 CN -20-78-52 (hex) Nokia Solutions and Networks GmbH & Co. KG -207852 (base 16) Nokia Solutions and Networks GmbH & Co. KG +0C-7C-28 (hex) Nokia Solutions and Networks GmbH & Co. KG +0C7C28 (base 16) Nokia Solutions and Networks GmbH & Co. KG Werinherstrasse 91 München Bavaria D-81541 DE -F4-17-B8 (hex) AirTies Wireless Networks -F417B8 (base 16) AirTies Wireless Networks - Esentepe Mah., Kore ?ehitleri Cad. - Istanbul ?i?li 34360 - TR - -38-F7-3D (hex) Amazon Technologies Inc. -38F73D (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - -B8-F7-4A (hex) RCNTEC -B8F74A (base 16) RCNTEC - Polkovaya street 3 - Moscow 127018 - RU - -EC-F4-51 (hex) Arcadyan Corporation -ECF451 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW - -58-12-43 (hex) AcSiP Technology Corp. -581243 (base 16) AcSiP Technology Corp. - 3F., No.22, Dalin Rd., - Taoyuan Taoyuan County 33067 - TW - -58-C1-7A (hex) Cambium Networks Limited -58C17A (base 16) Cambium Networks Limited - Unit B2, Linhay Business Park, - Ashburton Devon TQ13 7UP - GB - -E0-AA-DB (hex) Nanjing PANENG Technology Development Co.,Ltd -E0AADB (base 16) Nanjing PANENG Technology Development Co.,Ltd - NO.6 Paneng Road,Nanjing High-tech Zone,Jiang Su,China - Nanjing 210061 +68-43-D7 (hex) Agilecom Photonics Solutions Guangdong Limited +6843D7 (base 16) Agilecom Photonics Solutions Guangdong Limited + No.1-6, Shenwan Industrial Park, Shenwan Town + Zhongshan Guangdong 528462 CN -C8-DE-C9 (hex) Coriant -C8DEC9 (base 16) Coriant - 1415 W. Diehl Rd - Naperville IL 60563 - US +B8-6A-97 (hex) Edgecore Networks Corporation +B86A97 (base 16) Edgecore Networks Corporation + 1 Creation RD 3. + Hsinchu 30077 + TW -34-2A-F1 (hex) Texas Instruments -342AF1 (base 16) Texas Instruments +A8-10-87 (hex) Texas Instruments +A81087 (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -0C-61-11 (hex) Anda Technologies SAC -0C6111 (base 16) Anda Technologies SAC - Av. Santa Cruz 888, Miraflores - Lima Peru Lima18 - PE +8C-8F-8B (hex) China Mobile Chongqing branch +8C8F8B (base 16) China Mobile Chongqing branch + 6 building, No. 2, Xingguang three road + Yubei District Chongqing 401120 + CN -00-22-C4 (hex) epro GmbH -0022C4 (base 16) epro GmbH - Joebkesweg 3 - Gronau NRW 48599 - DE +00-04-0B (hex) 3COM EUROPE LTD +00040B (base 16) 3COM EUROPE LTD + BOUNDARY WAY + vvvvv UNITED KINGDOM + GB -0C-15-39 (hex) Apple, Inc. -0C1539 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +C8-C2-F5 (hex) FLEXTRONICS MANUFACTURING(ZHUHAI)CO.,LTD. +C8C2F5 (base 16) FLEXTRONICS MANUFACTURING(ZHUHAI)CO.,LTD. + Xin Qing Science & Technology Industrial Park,Jin An Town,Doumen ,Zhuhai,Guangdong,PRC + Zhuhai Guangdong 519180 + CN -1C-33-0E (hex) PernixData -1C330E (base 16) PernixData - 1745 Technology Drive, Suite 800 - San Jose CA 95110 +F0-58-49 (hex) CareView Communications +F05849 (base 16) CareView Communications + 405 State HWY 121 BYP + Lewisville Texas 75067 US -44-D5-A5 (hex) AddOn Computer -44D5A5 (base 16) AddOn Computer - 15775 Gateway cir - tustin CA 92780 +8C-FE-74 (hex) Ruckus Wireless +8CFE74 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 US -64-51-06 (hex) Hewlett Packard -645106 (base 16) Hewlett Packard - 11445 Compaq Center Drive - Houston 77070 +60-4B-AA (hex) Magic Leap, Inc. +604BAA (base 16) Magic Leap, Inc. + 1855 Griffin Rd, Room B454 + Dania Beach FL 33004 US -00-BE-9E (hex) Fiberhome Telecommunication Technologies Co.,LTD -00BE9E (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +E4-34-93 (hex) HUAWEI TECHNOLOGIES CO.,LTD +E43493 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -54-C5-7A (hex) Sunnovo International Limited -54C57A (base 16) Sunnovo International Limited - 1717 Haitai Building - Beijing Beijing 100083 +34-29-12 (hex) HUAWEI TECHNOLOGIES CO.,LTD +342912 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -6C-56-97 (hex) Amazon Technologies Inc. -6C5697 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +48-A4-72 (hex) Intel Corporate +48A472 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -F8-7B-20 (hex) Cisco Systems, Inc -F87B20 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +00-0A-5E (hex) 3COM +000A5E (base 16) 3COM + 5400 Bayfront Plaza + Santa Clara CA 95052-8145 US -38-AD-8E (hex) New H3C Technologies Co., Ltd -38AD8E (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN - -34-D0-B8 (hex) IEEE Registration Authority -34D0B8 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +00-10-5A (hex) 3COM +00105A (base 16) 3COM + 5400 BAYFRONT PLAZA + SANTA CLARA CA 95052 US -EC-FA-F4 (hex) SenRa Tech Pvt. Ltd -ECFAF4 (base 16) SenRa Tech Pvt. Ltd - 133, First Floor, Lane No. 1, Westend Marg, Saidulajab - New Delhi 110030 - IN +00-60-97 (hex) 3COM +006097 (base 16) 3COM + 5400 BAYFRONT PLAZA + SANTA CLARA CA 95052 + US -00-05-FF (hex) SNS Solutions, Inc. -0005FF (base 16) SNS Solutions, Inc. - 2nd Fl. Hill House, - - KR +00-60-08 (hex) 3COM +006008 (base 16) 3COM + 5400 BAYFRONT PLAZA + SANTA CLARA CA 95052 + US -D8-8F-76 (hex) Apple, Inc. -D88F76 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +00-01-02 (hex) 3COM +000102 (base 16) 3COM + 5400 BAYFRONT PLAZA + SANTA CLARA CA 95052 US -40-9C-28 (hex) Apple, Inc. -409C28 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +A0-28-33 (hex) IEEE Registration Authority +A02833 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -00-1C-73 (hex) Arista Networks -001C73 (base 16) Arista Networks - 5470 Great America Pkwy - Santa Clara 95054 +00-1B-6E (hex) Keysight Technologies, Inc. +001B6E (base 16) Keysight Technologies, Inc. + 1400 Fountaingrove Pkwy. + Santa Rosa CA 95403 US -2C-8A-72 (hex) HTC Corporation -2C8A72 (base 16) HTC Corporation - No. 23, Xinghua Rd., Taoyuan City - Taoyuan County Taiwan 330 - TW +14-37-19 (hex) PT Prakarsa Visi Valutama +143719 (base 16) PT Prakarsa Visi Valutama + Jl. Cideng Timur No.11D + Jakarta Pusat Indonesia 10130 + ID -38-01-9F (hex) SHENZHEN FAST TECHNOLOGIES CO.,LTD -38019F (base 16) SHENZHEN FAST TECHNOLOGIES CO.,LTD - Room 202,Building No.5,Section 30,No.2 of Kefa Road,Nanshan District,Shenzhen,P.R.China - Shenzhen Guangdong 518057 - CN +58-2F-40 (hex) Nintendo Co.,Ltd +582F40 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP -24-5C-CB (hex) AXIe Consortium, Inc. -245CCB (base 16) AXIe Consortium, Inc. - P.O. Box 1016 - Niwot CO 80544-1016 +08-90-BA (hex) Danlaw Inc +0890BA (base 16) Danlaw Inc + 23700 research Dr. + Farmington Hills 48335 US -60-9B-C8 (hex) Hipad Intelligent Technology Co., Ltd. -609BC8 (base 16) Hipad Intelligent Technology Co., Ltd. - No. 688, East of Huangtang Street, LinkongEconomy District - Nanchang Jiangxi 330000 - CN +4C-36-4E (hex) Panasonic Connect Co., Ltd. +4C364E (base 16) Panasonic Connect Co., Ltd. + 4-1-62 Minoshima, Hakata-ku + Fukuoka-shi Fukuoka 812-8531 + JP -40-6A-8E (hex) Hangzhou Puwell OE Tech Ltd. -406A8E (base 16) Hangzhou Puwell OE Tech Ltd. - Letel Technology Park, 500 Qiuyi Road, Binjiang District - Hangzhou Zhejiang 310052 - CN +BC-A5-8B (hex) Samsung Electronics Co.,Ltd +BCA58B (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -1C-0F-AF (hex) Lucid Vision Labs -1C0FAF (base 16) Lucid Vision Labs - Unit 130 - 13200 Delf Place - Richmond BC V6V2A2 - CA +80-CE-B9 (hex) Samsung Electronics Co.,Ltd +80CEB9 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -88-B4-A6 (hex) Motorola Mobility LLC, a Lenovo Company -88B4A6 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 +E8-5D-86 (hex) CHANG YOW TECHNOLOGIES INTERNATIONAL CO.,LTD. +E85D86 (base 16) CHANG YOW TECHNOLOGIES INTERNATIONAL CO.,LTD. + No 88 Shuren 6th St Wufong District + Taichung 413 + TW + +94-A3-CA (hex) KonnectONE, LLC +94A3CA (base 16) KonnectONE, LLC + 30 N Gould Street STE 4004 + Sheridan WY 82801 US -F4-49-EF (hex) EMSTONE -F449EF (base 16) EMSTONE - #310, Ace Techno Tower 3rd, 38 Digital-ro-29-gil - Guro-Gu Seoul 08381 - KR +D0-D3-FC (hex) Mios, Ltd. +D0D3FC (base 16) Mios, Ltd. + 645 W. 9th St. + Los Angeles CA 90015 + US -54-DF-24 (hex) Fiberhome Telecommunication Technologies Co.,LTD -54DF24 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN +6C-6C-D3 (hex) Cisco Systems, Inc +6C6CD3 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -AC-1D-DF (hex) IEEE Registration Authority -AC1DDF (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +E0-49-ED (hex) Audeze LLC +E049ED (base 16) Audeze LLC + 3410 S Susan st + Santa Ana CA 92704 US -24-B2-09 (hex) Avaya Inc -24B209 (base 16) Avaya Inc - 360 Mt Kemble Ave - Morristown NJ 07960 +00-01-57 (hex) SYSWAVE CO., LTD +000157 (base 16) SYSWAVE CO., LTD + Dongho B/D 5F, 221-2 + KOREA 135-010 + KR + +A8-01-6D (hex) Aiwa Corporation +A8016D (base 16) Aiwa Corporation + 965 W Chicago Ave + Chicago IL 60642 US -FC-65-DE (hex) Amazon Technologies Inc. -FC65DE (base 16) Amazon Technologies Inc. +04-40-A9 (hex) New H3C Technologies Co., Ltd +0440A9 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN + +24-4C-E3 (hex) Amazon Technologies Inc. +244CE3 (base 16) Amazon Technologies Inc. P.O Box 8102 - Reno NV 89507 + Reno 89507 US -BC-90-3A (hex) Robert Bosch GmbH -BC903A (base 16) Robert Bosch GmbH - Postfach 1661 - Leonberg 71226 +B8-BE-F4 (hex) devolo AG +B8BEF4 (base 16) devolo AG + Charlottenburger Allee 67 + Aachen NRW 52068 DE -E8-D8-19 (hex) AzureWave Technology Inc. -E8D819 (base 16) AzureWave Technology Inc. - 8F., No. 94, Baozhong Rd. - New Taipei City Taiwan 231 - TW - -B0-6E-BF (hex) ASUSTek COMPUTER INC. -B06EBF (base 16) ASUSTek COMPUTER INC. - 15,Li-Te Rd., Peitou, Taipei 112, Taiwan - Taipei Taiwan 112 - TW - -00-21-28 (hex) Oracle Corporation -002128 (base 16) Oracle Corporation - 17 Network Circle - Menlo Park CA 95025 - US +58-FD-BE (hex) Shenzhen Taikaida Technology Co., Ltd +58FDBE (base 16) Shenzhen Taikaida Technology Co., Ltd + Shenzhen Baoan District Fuyong town Fengtang road Xintian building 613 + shenzhen 518102 + CN -28-CF-08 (hex) ESSYS -28CF08 (base 16) ESSYS - gaetbeol-ro - Incheon 21999 +F4-F1-97 (hex) EMTAKE Inc +F4F197 (base 16) EMTAKE Inc + 14, Pangyoyeok ro 192, Bundang gu + Seongnam city Kyeonggi do 13524 KR -74-86-0B (hex) Cisco Systems, Inc -74860B (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -18-2D-98 (hex) Jinwoo Industrial system -182D98 (base 16) Jinwoo Industrial system - 7F,Jinwoo Building,149 dosan-daero - seoul gangnamgu 06036 +6C-ED-51 (hex) NEXCONTROL Co.,Ltd +6CED51 (base 16) NEXCONTROL Co.,Ltd + (#303-1007, Ssangyong 3th) 397, Seokcheon-ro + Bucheon-si Gyeonggi-do 14449 KR -78-2D-7E (hex) TRENDnet, Inc. -782D7E (base 16) TRENDnet, Inc. - 20675 Manhattan Place - Torrance CA 90501 +10-62-E5 (hex) Hewlett Packard +1062E5 (base 16) Hewlett Packard + 11445 Compaq Center Drive + Houston TX 77070 US -74-1A-E0 (hex) IEEE Registration Authority -741AE0 (base 16) IEEE Registration Authority +04-C3-E6 (hex) IEEE Registration Authority +04C3E6 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -58-38-79 (hex) RICOH COMPANY, LTD. -583879 (base 16) RICOH COMPANY, LTD. - 1005, Shimo-ogino - Atsugi-City Kanagawa-Pref. 243-0298 - JP - -F4-4C-70 (hex) Skyworth Digital Technology(Shenzhen) Co.,Ltd -F44C70 (base 16) Skyworth Digital Technology(Shenzhen) Co.,Ltd - 7F,Block A,Skyworth Building, - Shenzhen Guangdong 518057 +98-BB-99 (hex) Phicomm (Sichuan) Co.,Ltd. +98BB99 (base 16) Phicomm (Sichuan) Co.,Ltd. + 125 longquan street park road,longquan district,chengdu city + Sichuan Chengdu 610015 CN -78-32-1B (hex) D-Link International -78321B (base 16) D-Link International - 1 Internal Business Park, #03-12,The Synergy - Singapore Singapore 609917 - SG +00-26-22 (hex) COMPAL INFORMATION (KUNSHAN) CO., LTD. +002622 (base 16) COMPAL INFORMATION (KUNSHAN) CO., LTD. + NO. 25, THE 3RD Street KUNSHAN EXPORT PROCESSING ZONE + KUNSHAN SUZHOU 215300 + CN -D8-A5-34 (hex) Spectronix Corporation -D8A534 (base 16) Spectronix Corporation - 3-28-15, Tarumi-cho - Suita-city Osaka 564-0062 +00-20-B5 (hex) YASKAWA ELECTRIC CORPORATION +0020B5 (base 16) YASKAWA ELECTRIC CORPORATION + 2-1 Kurosakishiroishi, Yahatanishi-ku, + Kitakyushu 806-0004 JP -00-A0-96 (hex) MITSUMI ELECTRIC CO.,LTD. -00A096 (base 16) MITSUMI ELECTRIC CO.,LTD. - 2-11-2, Tsurumaki - Tama-shi Tokyo 206-8567 - JP +28-63-36 (hex) Siemens AG +286336 (base 16) Siemens AG + Werner-von-Siemens Strasse 50 + Amberg 92224 + DE -78-61-7C (hex) MITSUMI ELECTRIC CO.,LTD. -78617C (base 16) MITSUMI ELECTRIC CO.,LTD. - 2-11-2, Tsurumaki - Tama-shi Tokyo 206-8567 - JP +14-D1-69 (hex) HUAWEI TECHNOLOGIES CO.,LTD +14D169 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -EC-51-BC (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -EC51BC (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +1C-C3-EB (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +1CC3EB (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD NO.18 HAIBIN ROAD, DONG GUAN GUANG DONG 523860 CN -F0-79-E8 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -F079E8 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +F4-60-E2 (hex) Xiaomi Communications Co Ltd +F460E2 (base 16) Xiaomi Communications Co Ltd + The Rainbow City of China Resources + NO.68, Qinghe Middle Street Haidian District, Beijing 100085 CN -D0-B1-28 (hex) Samsung Electronics Co.,Ltd -D0B128 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -BC-54-51 (hex) Samsung Electronics Co.,Ltd -BC5451 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -08-66-1F (hex) Palo Alto Networks -08661F (base 16) Palo Alto Networks - 3000 Tannery Way - Santa Clara CA 95054 +E4-D1-24 (hex) Mojo Networks, Inc. +E4D124 (base 16) Mojo Networks, Inc. + 339 N.Bernardo Ave + Mountain View CA 94043 US -74-EA-C8 (hex) New H3C Technologies Co., Ltd -74EAC8 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN +9C-C9-50 (hex) Baumer Holding +9CC950 (base 16) Baumer Holding + Hummelstrasse 17 + Frauenfeld Thurgau 8501 + CH -B4-D6-4E (hex) Caldero Limited -B4D64E (base 16) Caldero Limited - Concordia Works, 30 Sovereign Street - Leeds West Yorkshire LS1 4BA - GB +F8-99-10 (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. +F89910 (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. + Phase 3, Bayan Lepas FIZ + Bayan Lepas Penang 11900 + MY -F8-9D-BB (hex) Tintri -F89DBB (base 16) Tintri - 303 Ravendale Dr - Mountain View CA 94070 - US +50-E0-EF (hex) Nokia +50E0EF (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -D8-A0-1D (hex) Espressif Inc. -D8A01D (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +60-F1-8A (hex) HUAWEI TECHNOLOGIES CO.,LTD +60F18A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -90-4E-91 (hex) IEEE Registration Authority -904E91 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +C4-95-00 (hex) Amazon Technologies Inc. +C49500 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -DC-0C-2D (hex) WEIFANG GOERTEK ELECTRONICS CO.,LTD -DC0C2D (base 16) WEIFANG GOERTEK ELECTRONICS CO.,LTD - Gaoxin 2 Road, Free Trade Zone,Weifang,Shandong,261205,P.R.China - Weifang Shandong 261205 +68-DD-26 (hex) Shanghai Focus Vision Security Technology Co.,Ltd +68DD26 (base 16) Shanghai Focus Vision Security Technology Co.,Ltd + No.4888 Hunan Rd, Pudong New District + Shanghai Shanghai 201317 CN -C4-F3-12 (hex) Texas Instruments -C4F312 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +28-66-E3 (hex) AzureWave Technology Inc. +2866E3 (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW + +84-8A-8D (hex) Cisco Systems, Inc +848A8D (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -60-18-03 (hex) Daikin Air-conditioning (Shanghai) Co., Ltd. -601803 (base 16) Daikin Air-conditioning (Shanghai) Co., Ltd. - 318 Shen Fu Road, Xin Zhuang Industry Zone, Shanghai, 201108, China - Shanghai 201108 +E0-62-67 (hex) Xiaomi Communications Co Ltd +E06267 (base 16) Xiaomi Communications Co Ltd + The Rainbow City of China Resources + NO.68, Qinghe Middle Street Haidian District, Beijing 100085 CN -8C-FE-B4 (hex) VSOONTECH ELECTRONICS CO., LIMITED -8CFEB4 (base 16) VSOONTECH ELECTRONICS CO., LIMITED - 18th, Floor, On Hong Commericial Building, 145 Hennessy Road, Wanchai, HONG KONG - HongKong 999077 - HK - -94-0E-6B (hex) HUAWEI TECHNOLOGIES CO.,LTD -940E6B (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +70-B7-AA (hex) vivo Mobile Communication Co., Ltd. +70B7AA (base 16) vivo Mobile Communication Co., Ltd. + #283,BBK Road + Wusha,Chang'An DongGuan City,Guangdong, 523860 CN -64-FB-50 (hex) RoomReady/Zdi, Inc. -64FB50 (base 16) RoomReady/Zdi, Inc. - 2200 N. Main Street - Normal IL 61761 +00-13-A3 (hex) Siemens Home & Office Comm. Devices +0013A3 (base 16) Siemens Home & Office Comm. Devices + 4849 Alpha Road + Dallas 75244 US -58-B4-2D (hex) YSTen Technology Co.,Ltd -58B42D (base 16) YSTen Technology Co.,Ltd - Room 1715,17/F North Star Times Tower,Chaoyang District,Beijing. - Beijing 100101 +08-25-25 (hex) Xiaomi Communications Co Ltd +082525 (base 16) Xiaomi Communications Co Ltd + The Rainbow City of China Resources + NO.68, Qinghe Middle Street Haidian District, Beijing 100085 CN -34-29-8F (hex) IEEE Registration Authority -34298F (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - -5C-EA-1D (hex) Hon Hai Precision Ind. Co.,Ltd. -5CEA1D (base 16) Hon Hai Precision Ind. Co.,Ltd. - Building D21,No.1, East Zone 1st Road - Chongqing Chongqing 401332 - CN +84-B3-1B (hex) Kinexon GmbH +84B31B (base 16) Kinexon GmbH + Schellingstrasse, 35 + München 80799 + DE -18-14-56 (hex) Nokia Corporation -181456 (base 16) Nokia Corporation - Elektroniikkatie 10 - Oulu Ou 90590 - FI +F8-27-2E (hex) Mercku +F8272E (base 16) Mercku + 509 Beaver Creek Rd. + Waterloo Ontario N2V 2L3 + CA -78-CA-04 (hex) Nokia Corporation -78CA04 (base 16) Nokia Corporation - Elektroniikkatie 10 - Oulu 90570 +A8-3E-0E (hex) HMD Global Oy +A83E0E (base 16) HMD Global Oy + Bertel Jungin aukio 9 + Espoo 02600 FI -10-4B-46 (hex) Mitsubishi Electric Corporation -104B46 (base 16) Mitsubishi Electric Corporation - 2-7-3 - Chiyoda-ku Tokyo 100-8310 - JP +10-C1-72 (hex) HUAWEI TECHNOLOGIES CO.,LTD +10C172 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -00-17-C8 (hex) KYOCERA Display Corporation -0017C8 (base 16) KYOCERA Display Corporation - 2-14-9, Tamagawadai - Tokyo 158-8610 - JP +00-0E-EE (hex) Muco Industrie BV +000EEE (base 16) Muco Industrie BV + Pleimuiden 12e + Amsterdam NH 1046 AG + NL -5C-E8-B7 (hex) Oraimo Technology Limited -5CE8B7 (base 16) Oraimo Technology Limited - RMS 05-15,13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG - HONG KONG HONG KONG 999077 - HK +7C-1C-4E (hex) LG Innotek +7C1C4E (base 16) LG Innotek + 26, Hanamsandan 5beon-ro + Gwangju Gwangsan-gu 506-731 + KR -CC-66-B2 (hex) Nokia -CC66B2 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +D8-B6-B7 (hex) Comtrend Corporation +D8B6B7 (base 16) Comtrend Corporation + 3F-1, 10 Lane 609, Chongxin Road, Section 5, + New Taipei City, Taiwan 24159 + TW -38-E2-DD (hex) zte corporation -38E2DD (base 16) zte corporation +8C-14-B4 (hex) zte corporation +8C14B4 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China shenzhen guangdong 518057 CN -88-5D-FB (hex) zte corporation -885DFB (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +3C-98-72 (hex) Sercomm Corporation. +3C9872 (base 16) Sercomm Corporation. + 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen + Miao-Lih Hsuan 115 + TW + +40-C3-C6 (hex) SnapRoute +40C3C6 (base 16) SnapRoute + 3960 Freedom Circle, Suite 100 + Santa Clara CA 95054 + US + +D0-1C-BB (hex) Beijing Ctimes Digital Technology Co., Ltd. +D01CBB (base 16) Beijing Ctimes Digital Technology Co., Ltd. + 7th Floor, Jinzhou Building, Suzhou Street, No.79, Haidian District, + Beijing 100089 CN -68-EC-C5 (hex) Intel Corporate -68ECC5 (base 16) Intel Corporate +74-87-BB (hex) Ciena Corporation +7487BB (base 16) Ciena Corporation + 7035 Ridge Road + Hanover MD 21076 + US + +C0-B6-F9 (hex) Intel Corporate +C0B6F9 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -3C-11-B2 (hex) Fraunhofer FIT -3C11B2 (base 16) Fraunhofer FIT - Schloss Birlinghoven - Sankt Augustin 53754 - DE +DC-E3-05 (hex) AO +DCE305 (base 16) AO + Prospekt Mira + Moscow 129223 + RU -28-8C-B8 (hex) zte corporation -288CB8 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +A4-DA-32 (hex) Texas Instruments +A4DA32 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -78-BC-1A (hex) Cisco Systems, Inc -78BC1A (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +78-04-73 (hex) Texas Instruments +780473 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -00-5C-86 (hex) SHENZHEN FAST TECHNOLOGIES CO.,LTD -005C86 (base 16) SHENZHEN FAST TECHNOLOGIES CO.,LTD - Room 202,Building No.5,Section 30,No.2 of Kefa Road,Nanshan District,Shenzhen,P.R.China - Shenzhen Guangdong 518057 +50-4C-7E (hex) THE 41ST INSTITUTE OF CETC +504C7E (base 16) THE 41ST INSTITUTE OF CETC + No.98 Xiangjiang Road,Huangdao District,Qingdao,Shandong + Qingdao Shangdong 266555 CN -5C-54-6D (hex) HUAWEI TECHNOLOGIES CO.,LTD -5C546D (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +2C-47-59 (hex) Beijing MEGA preponderance Science & Technology Co. Ltd +2C4759 (base 16) Beijing MEGA preponderance Science & Technology Co. Ltd + Room 2201,No.8,Ruichuang International B Block,Wangjing East Road + Beijing 100102 CN -48-BC-A6 (hex) ​ASUNG TECHNO CO.,Ltd -48BCA6 (base 16) ​ASUNG TECHNO CO.,Ltd - 462, Dogok-ro, Songpa-gu, Seoul, Republic of Korea - SEOUL Repubilc of KOREA 05574 - KR +DC-E0-EB (hex) Nanjing Aozheng Information Technology Co.Ltd +DCE0EB (base 16) Nanjing Aozheng Information Technology Co.Ltd + #E1-453, Zidong Road #1,Qixia District + Nanjing jiangsu 210000 + CN -3C-10-E6 (hex) PHAZR Inc. -3C10E6 (base 16) PHAZR Inc. - 8, Presitige Circle, Suite 104 - Allen TX 75002 +00-40-9D (hex) DigiBoard +00409D (base 16) DigiBoard + 6400 FLYING CLOUD DRIVE + EDEN PRAIRIE MN 55344 US -30-05-3F (hex) JTI Co.,Ltd. -30053F (base 16) JTI Co.,Ltd. - 102-1508, 36, Bucheon-ro 198beon-gil, - Buchcheon-si Gyeonggi-do 14557 - KR - -B0-09-DA (hex) Ring Solutions -B009DA (base 16) Ring Solutions - 1200 Atwater Drive, Suite 225 - Malvern PA 19355 +14-93-46 (hex) PNI sensor corporation +149346 (base 16) PNI sensor corporation + 2331 Circadian Way + Santa Rosa CA 95407 US -00-05-4F (hex) Garmin International -00054F (base 16) Garmin International - 1200 E. 151st St - Olathe KS 66062 - US +00-13-8A (hex) Qingdao GoerTek Technology Co., Ltd. +00138A (base 16) Qingdao GoerTek Technology Co., Ltd. + Room 605,Innovation Building,Hi-tech Industrial Park, + QINGDAO SHANDONG 266061 + CN -90-45-06 (hex) Tokyo Boeki Medisys Inc. -904506 (base 16) Tokyo Boeki Medisys Inc. - 1-14-21, Higashitoyoda - Hino Tokyo 191-0052 - JP +A8-30-AD (hex) WEIFANG GOERTEK ELECTRONICS CO.,LTD +A830AD (base 16) WEIFANG GOERTEK ELECTRONICS CO.,LTD + Wei fang Export processing Zone + Wei Fang Shan Dong 261205 + CN -00-21-A1 (hex) Cisco Systems, Inc -0021A1 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +A4-15-66 (hex) WEIFANG GOERTEK ELECTRONICS CO.,LTD +A41566 (base 16) WEIFANG GOERTEK ELECTRONICS CO.,LTD + Wei fang Export processing Zone + Wei Fang Shan Dong 261205 + CN -E0-48-D3 (hex) MOBIWIRE MOBILES (NINGBO) CO.,LTD -E048D3 (base 16) MOBIWIRE MOBILES (NINGBO) CO.,LTD - No.999,Dacheng East Road, - Fenghua Zhejiang 315500 +1C-96-5A (hex) WEIFANG GOERTEK ELECTRONICS CO.,LTD +1C965A (base 16) WEIFANG GOERTEK ELECTRONICS CO.,LTD + Gaoxin 2 Road,Free Trade Zone,Weifang,Shandong,261205,P.R.China + Wei Fang Shan Dong 261205 CN -B8-DB-1C (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. -B8DB1C (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. - Phase 3, Bayan Lepas FIZ - Bayan Lepas Penang 11900 +40-1B-5F (hex) WEIFANG GOERTEK ELECTRONICS CO.,LTD +401B5F (base 16) WEIFANG GOERTEK ELECTRONICS CO.,LTD + Gaoxin 2 Road,Free Trade Zone,Weifang,Shandong,261205,P.R.China + Weifang Shandong 261205 + CN + +14-4F-8A (hex) Intel Corporate +144F8A (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 MY -58-E2-8F (hex) Apple, Inc. -58E28F (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +00-21-06 (hex) RIM Testing Services +002106 (base 16) RIM Testing Services + 440 Phillip Street + Waterloo ON N2L 5R9 + CA -78-7B-8A (hex) Apple, Inc. -787B8A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +5C-CD-7C (hex) MEIZU Technology Co.,Ltd. +5CCD7C (base 16) MEIZU Technology Co.,Ltd. + MEIZU Tech Bldg., Technology& Innovation Coast + Zhuhai Guangdong 519085 + CN -00-0C-03 (hex) HDMI Licensing, LLC -000C03 (base 16) HDMI Licensing, LLC - 1060 East Arques Ave. - Sunnyvale CA 94085 - US +5C-96-56 (hex) AzureWave Technology Inc. +5C9656 (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW -F4-93-9F (hex) Hon Hai Precision Industry Co., Ltd. -F4939F (base 16) Hon Hai Precision Industry Co., Ltd. - GuangDongShenZhen - ShenZhen GuangDong 518109 - CN +E0-60-66 (hex) Sercomm Corporation. +E06066 (base 16) Sercomm Corporation. + 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen + Miao-Lih Hsuan 115 + TW -00-07-26 (hex) SHENZHEN GONGJIN ELECTRONICS CO.,LT -000726 (base 16) SHENZHEN GONGJIN ELECTRONICS CO.,LT - A211-A213 & B201-B210, 2F, Baiying Building, 1019#, Nanhai RD, Shekou Party, Nanshan District, - Shenzhen Guangdong 518067 +BC-5F-F6 (hex) MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. +BC5FF6 (base 16) MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. + Mid-Fourth Flr.,Building 28,Cui Xi Fourth Road,Ke Yuan West,Nanshan + Shenzhen Guangdong 518057 CN -FC-8B-97 (hex) SHENZHEN GONGJIN ELECTRONICS CO.,LT -FC8B97 (base 16) SHENZHEN GONGJIN ELECTRONICS CO.,LT - B116,B118,A211-A213,B201-B213,A311-A313,B411-413,BF08-09 Nanshan Medical Instrument Industry Park, - Shenzhen Guangdong 518067 +C8-E7-D8 (hex) MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. +C8E7D8 (base 16) MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. + Mid-Fourth Flr., Building 28, Cui Xi Fourth Road,Ke Yuan West,Nanshan + Shenzhen Guangdong 518057 CN -84-A1-D1 (hex) Sagemcom Broadband SAS -84A1D1 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -FC-7F-56 (hex) CoSyst Control Systems GmbH -FC7F56 (base 16) CoSyst Control Systems GmbH - Martin-Albert-Str. 1 - Nürnberg Bayern 90491 +A8-E5-52 (hex) JUWEL Aquarium AG & Co. KG +A8E552 (base 16) JUWEL Aquarium AG & Co. KG + Karl-Göx-Straße 1 + Rotenburg / Wümme 27356 DE -2C-40-53 (hex) Samsung Electronics Co.,Ltd -2C4053 (base 16) Samsung Electronics Co.,Ltd +60-D2-1C (hex) Sunnovo International Limited +60D21C (base 16) Sunnovo International Limited + 1717 Haitai Building + Beijing Beijing 100083 + CN + +CC-51-B4 (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. +CC51B4 (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. + Phase 3, Bayan Lepas FIZ + Bayan Lepas Penang 11900 + MY + +00-C3-F4 (hex) Samsung Electronics Co.,Ltd +00C3F4 (base 16) Samsung Electronics Co.,Ltd 129, Samsung-ro, Youngtongl-Gu Suwon Gyeonggi-Do 16677 KR -0C-8F-FF (hex) HUAWEI TECHNOLOGIES CO.,LTD -0C8FFF (base 16) HUAWEI TECHNOLOGIES CO.,LTD +B8-8A-EC (hex) Nintendo Co.,Ltd +B88AEC (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + +78-72-5D (hex) Cisco Systems, Inc +78725D (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +B4-6B-FC (hex) Intel Corporate +B46BFC (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +B0-FC-0D (hex) Amazon Technologies Inc. +B0FC0D (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +CC-C9-2C (hex) Schindler - PORT Technology +CCC92C (base 16) Schindler - PORT Technology + via della Pace 22 + Locarno Ticino 6600 + CH + +00-1E-39 (hex) Comsys Communication Ltd. +001E39 (base 16) Comsys Communication Ltd. + 9 Hamenofim st. + Herzelia 46725 + IL + +EC-8C-9A (hex) HUAWEI TECHNOLOGIES CO.,LTD +EC8C9A (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -54-B1-21 (hex) HUAWEI TECHNOLOGIES CO.,LTD -54B121 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +B4-86-55 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B48655 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -A8-0C-63 (hex) HUAWEI TECHNOLOGIES CO.,LTD -A80C63 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +D0-D7-83 (hex) HUAWEI TECHNOLOGIES CO.,LTD +D0D783 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -5C-C3-07 (hex) HUAWEI TECHNOLOGIES CO.,LTD -5CC307 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +FC-E6-6A (hex) Industrial Software Co +FCE66A (base 16) Industrial Software Co + 85, Aleksandyr Malinov Blvd. Office 6 + Sofia 1715 + BG + +78-36-CC (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +7836CC (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -78-8C-4D (hex) Indyme Solutions, LLC -788C4D (base 16) Indyme Solutions, LLC - 8295 Aero Place Ste 260 - San Diego CA 92123 +70-C8-33 (hex) Wirepas Oy +70C833 (base 16) Wirepas Oy + Visiokatu 4 + Tampere 33720 + FI + +0C-73-EB (hex) IEEE Registration Authority +0C73EB (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -DC-EB-53 (hex) Wuhan QianXiao Elecronic Technology CO.,LTD -DCEB53 (base 16) Wuhan QianXiao Elecronic Technology CO.,LTD - Guanggu Xinzhongxin, No.303 of Guanggu road, East lake development zone - wuhan hubei 430000 - CN +F0-B5-D1 (hex) Texas Instruments +F0B5D1 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -EC-8A-C7 (hex) Fiberhome Telecommunication Technologies Co.,LTD -EC8AC7 (base 16) Fiberhome Telecommunication Technologies Co.,LTD +00-E0-00 (hex) FUJITSU LIMITED +00E000 (base 16) FUJITSU LIMITED + 403, Kosugi-cho 1-chome, Nakahara-ku + Kawasaki Kanagawa 211-0063 + JP + +90-84-8B (hex) HDR10+ Technologies, LLC +90848B (base 16) HDR10+ Technologies, LLC + 3855 SW 153rd Drive + Beaverton OR 97006 + US + +0C-F5-A4 (hex) Cisco Systems, Inc +0CF5A4 (base 16) Cisco Systems, Inc + 170 West Tasman Drive + San Jose CA 95134 + US + +80-C7-C5 (hex) Fiberhome Telecommunication Technologies Co.,LTD +80C7C5 (base 16) Fiberhome Telecommunication Technologies Co.,LTD No.5 DongXin Road Wuhan Hubei 430074 CN -88-36-5F (hex) LG Electronics (Mobile Communications) -88365F (base 16) LG Electronics (Mobile Communications) - 60-39, Gasan-dong, Geumcheon-gu - Seoul 153-801 - KR +28-16-A8 (hex) Microsoft Corporation +2816A8 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US -24-B2-DE (hex) Espressif Inc. -24B2DE (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +04-8A-E1 (hex) FLEXTRONICS MANUFACTURING(ZHUHAI)CO.,LTD. +048AE1 (base 16) FLEXTRONICS MANUFACTURING(ZHUHAI)CO.,LTD. + Xin Qing Science & Technology Industrial Park,Jin An Town,Doumen ,Zhuhai,Guangdong,PRC + Zhuhai Guangdong 519180 CN -F0-EF-D2 (hex) TF PAYMENT SERVICE CO., LTD -F0EFD2 (base 16) TF PAYMENT SERVICE CO., LTD - 5F Shibadaimon Center Building, 1-10-11 Shibadaimon - Minato-ku Tokyo 105-0012 - JP - -00-0E-59 (hex) Sagemcom Broadband SAS -000E59 (base 16) Sagemcom Broadband SAS - 2, rue du petit Albi - CERGY SAINT CHRISTOPHE val d'Oise 95800 - FR +8C-CF-5C (hex) BEFEGA GmbH +8CCF5C (base 16) BEFEGA GmbH + Reichenbacher Str. 22 + Schwabach Bavaria 91126 + DE -78-81-02 (hex) Sercomm Corporation. -788102 (base 16) Sercomm Corporation. - 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen - Miao-Lih Hsuan 115 - TW +50-1D-93 (hex) HUAWEI TECHNOLOGIES CO.,LTD +501D93 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -84-AA-9C (hex) MitraStar Technology Corp. -84AA9C (base 16) MitraStar Technology Corp. - No. 6, Innovation Road II, - Hsinchu 300 - TW +C8-D7-79 (hex) QING DAO HAIER TELECOM CO.,LTD. +C8D779 (base 16) QING DAO HAIER TELECOM CO.,LTD. + No 1 Haier road,Hi-tech Zone,Qingdao,PR.China + Qingdao Shandong 266101 + CN -2C-AB-25 (hex) SHENZHEN GONGJIN ELECTRONICS CO.,LT -2CAB25 (base 16) SHENZHEN GONGJIN ELECTRONICS CO.,LT - B116,B118,A211-A213,B201-B213,A311-A313,B411-413,BF08-09 Nanshan Medical Instrument Industry Park, - Shenzhen Guangdong 518067 +10-B3-6F (hex) Bowei Technology Company Limited +10B36F (base 16) Bowei Technology Company Limited + 2F,Building No.6C,1658,Gumei Rd + Shanghai Shanghai 200233 CN -1C-A5-32 (hex) SHENZHEN GONGJIN ELECTRONICS CO.,LT -1CA532 (base 16) SHENZHEN GONGJIN ELECTRONICS CO.,LT - 1#SongGang RD, Bao'an Dist., Shenzhen, Guangdong,China - Shenzhen Guangdong 518109 +FC-9B-C6 (hex) Sumavision Technologies Co.,Ltd +FC9BC6 (base 16) Sumavision Technologies Co.,Ltd + 6F, Block A2, Power Creative Building,No.1 Shangdi East Road, Haidian District + Beijing 100085 CN -00-1F-92 (hex) Motorola Solutions Inc. -001F92 (base 16) Motorola Solutions Inc. - 500 W Monroe Street, Ste 4400 - Chicago IL 60661-3781 - US +C8-29-2A (hex) Barun Electronics +C8292A (base 16) Barun Electronics + 869, Jangji-ri, Dongtan-myeon + Hwaseong-si Gyeonggi-do 445812 + KR -00-25-C4 (hex) Ruckus Wireless -0025C4 (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +00-80-BA (hex) SPECIALIX (ASIA) PTE, LTD +0080BA (base 16) SPECIALIX (ASIA) PTE, LTD + 3 WINTERSELLS ROAD + UNITED KINGDOM US -C0-C5-20 (hex) Ruckus Wireless -C0C520 (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +48-0B-B2 (hex) IEEE Registration Authority +480BB2 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -70-DE-F9 (hex) FAI WAH INTERNATIONAL (HONG KONG) LIMITED -70DEF9 (base 16) FAI WAH INTERNATIONAL (HONG KONG) LIMITED - Room 19, 8F.,Corporation Park, 11 On Lai Street,Shatin,Shek Mun,N.T.Hong Kong - Hong Kong 999077 - HK +CC-C0-79 (hex) Murata Manufacturing Co., Ltd. +CCC079 (base 16) Murata Manufacturing Co., Ltd. + 1-10-1, Higashikotari + Nagaokakyo-shi Kyoto 617-8555 + JP -4C-B0-08 (hex) Shenzhen Gwelltimes Technology Co.,Ltd -4CB008 (base 16) Shenzhen Gwelltimes Technology Co.,Ltd - A4 building 15floor Zhongying Jewelry Industrial Park Bulan Road No.31 Nanwan Street Longgang District Shenzhen City China - Shenzhen 518112 +F0-9C-D7 (hex) Guangzhou Blue Cheetah Intelligent Technology Co., Ltd. +F09CD7 (base 16) Guangzhou Blue Cheetah Intelligent Technology Co., Ltd. + Panyu District, Guangzhou City Panyu Avenue North 555 Panyu Energy Technology Park,Industry Building 2 seats 406-407 + Guangzhou Guangdong 511400 CN -E8-6F-F2 (hex) Actiontec Electronics, Inc -E86FF2 (base 16) Actiontec Electronics, Inc - 3301 Olcott St. - Santa Clara CA 95054 +BC-E1-43 (hex) Apple, Inc. +BCE143 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-50-18 (hex) AMIT, Inc. -005018 (base 16) AMIT, Inc. - No.32, Huangong Rd., - Yongkang City, Tainan County 71041 - TW +E4-82-CC (hex) Jumptronic GmbH +E482CC (base 16) Jumptronic GmbH + An der Weide 5 + Springe 31832 + DE -FC-2F-6B (hex) Everspin Technologies, Inc. -FC2F6B (base 16) Everspin Technologies, Inc. - 1347 N. Alma School Rd., Suite 220 - Chandler AZ 85224 +B0-41-6F (hex) Shenzhen Maxtang Computer Co.,Ltd +B0416F (base 16) Shenzhen Maxtang Computer Co.,Ltd + 6/F, Bldg.3, Honghui Industrial Park, Liuxian 2nd Rd., Bao'an Dist. + Shenzhen Guangdong 518101 + CN + +E0-19-D8 (hex) BH TECHNOLOGIES +E019D8 (base 16) BH TECHNOLOGIES + 12 RUE AMPERE + GRENOBLE 38000 + FR + +48-60-5F (hex) LG Electronics (Mobile Communications) +48605F (base 16) LG Electronics (Mobile Communications) + 60-39, Gasan-dong, Geumcheon-gu + Seoul 153-801 + KR + +30-D9-D9 (hex) Apple, Inc. +30D9D9 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -10-1B-54 (hex) HUAWEI TECHNOLOGIES CO.,LTD -101B54 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - D1-4,Huawei Industrial Base,Bantian,Longgang - ShenZhen GuangDong 518129 - CN +60-30-D4 (hex) Apple, Inc. +6030D4 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -7C-BA-CC (hex) IEEE Registration Authority -7CBACC (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +F8-95-EA (hex) Apple, Inc. +F895EA (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -54-02-37 (hex) Teltronic AG -540237 (base 16) Teltronic AG - Gewerbestrasse 9 - Biberist 4562 - CH +18-F1-D8 (hex) Apple, Inc. +18F1D8 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -E0-10-7F (hex) Ruckus Wireless -E0107F (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +64-70-33 (hex) Apple, Inc. +647033 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -C4-01-7C (hex) Ruckus Wireless -C4017C (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +84-68-78 (hex) Apple, Inc. +846878 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -40-42-29 (hex) Layer3TV, Inc -404229 (base 16) Layer3TV, Inc - 1660 Wynkoop St - Suite 800 - Denver CO 80202 +C8-D0-83 (hex) Apple, Inc. +C8D083 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -F8-1D-90 (hex) Solidwintech -F81D90 (base 16) Solidwintech - 6th Floor, SOLiD Space 220 Pangyoyeok-ro, Bundang-gu - Seongnam-si Gyeongi-do 13493 - KR +8C-4C-DC (hex) PLANEX COMMUNICATIONS INC. +8C4CDC (base 16) PLANEX COMMUNICATIONS INC. + Planex Volta Bldg., 2-11-9 Ebisu-Nishi,Shibuya-ku,Tokyo 150-0021,Japan + Tokyo Tokyo 150-0021 + JP -B0-EA-BC (hex) ASKEY COMPUTER CORP -B0EABC (base 16) ASKEY COMPUTER CORP - 10F,No.119,JIANKANG RD,ZHONGHE DIST - NEW TAIPEI TAIWAN 23585 - TW +BC-AB-7C (hex) TRnP KOREA Co Ltd +BCAB7C (base 16) TRnP KOREA Co Ltd + room1308,239 SoHyungRo,WonMiGu, + BuChunCity KyungKiDo 1135 + KR -94-C6-91 (hex) EliteGroup Computer Systems Co., LTD -94C691 (base 16) EliteGroup Computer Systems Co., LTD - No.239, Sec. 2, TiDing Blvd. Nei-Hu Dist. - Taipei Taiwan 11439 - TW +6C-38-38 (hex) Marking System Technology Co., Ltd. +6C3838 (base 16) Marking System Technology Co., Ltd. + 76-1, Hirakawa Yokomichi + Joyo-shi Kyoto 610-0101 + JP -3C-F5-91 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -3CF591 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +9C-2E-A1 (hex) Xiaomi Communications Co Ltd +9C2EA1 (base 16) Xiaomi Communications Co Ltd + The Rainbow City of China Resources + NO.68, Qinghe Middle Street Haidian District, Beijing 100085 CN -60-21-01 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -602101 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +0C-6A-BC (hex) Fiberhome Telecommunication Technologies Co.,LTD +0C6ABC (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -7C-EB-7F (hex) Dmet Products Corp. -7CEB7F (base 16) Dmet Products Corp. - 118, Fujisoft Akihabara Bldg 12F, Kanda Neribeicho 3 - Chiyodaku Tokyo 1010022 - JP - -8C-85-80 (hex) Smart Innovation LLC -8C8580 (base 16) Smart Innovation LLC - 7F,Tower B,Jianxing - ShenZhen GuangZhou 518055 +3C-CD-5D (hex) HUAWEI TECHNOLOGIES CO.,LTD +3CCD5D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -28-7B-09 (hex) zte corporation -287B09 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +7C-76-68 (hex) HUAWEI TECHNOLOGIES CO.,LTD +7C7668 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -A0-6A-44 (hex) Vizio, Inc -A06A44 (base 16) Vizio, Inc - 39 Tesla - Irvine CA 92618 +00-13-86 (hex) ABB Inc/Totalflow +001386 (base 16) ABB Inc/Totalflow + 123 + Bartlesville OK 74006 US -88-B1-11 (hex) Intel Corporate -88B111 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +00-3C-10 (hex) Cisco Systems, Inc +003C10 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -DC-BE-7A (hex) Zhejiang Nurotron Biotechnology Co. -DCBE7A (base 16) Zhejiang Nurotron Biotechnology Co. - Building4, No.99 Xiaomao Rd - Hangzhou zhejiang 310011 - CN +F0-41-C8 (hex) IEEE Registration Authority +F041C8 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -34-38-B7 (hex) HUMAX Co., Ltd. -3438B7 (base 16) HUMAX Co., Ltd. - HUMAX Village, 216, Hwangsaeul-ro, Bu - Seongnam-si Gyeonggi-do 463-875 - KR +CC-99-16 (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. +CC9916 (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. + Phase 3, Bayan Lepas FIZ + Bayan Lepas Penang 11900 + MY -78-B2-8D (hex) Beijing Tengling Technology CO.Ltd -78B28D (base 16) Beijing Tengling Technology CO.Ltd - beijing haidian shangdi san jie - beijing 100086 - CN +EC-7F-C6 (hex) ECCEL CORPORATION SAS +EC7FC6 (base 16) ECCEL CORPORATION SAS + CRA 106 15A 25 LT 88 MZ 17 BG 1, ZONA FRANCA BOGOTA + BOGOTA D.C. 110921 + CO -78-45-01 (hex) Biamp Systems -784501 (base 16) Biamp Systems - 9300 SW Gemini Dr - Beaverton OR 97008 - US +A4-38-CC (hex) Nintendo Co.,Ltd +A438CC (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP -54-D7-51 (hex) Proximus -54D751 (base 16) Proximus - Bld du Roi Albert II 27 - Brussels 1030 - BE +74-72-1E (hex) Edison Labs Inc. +74721E (base 16) Edison Labs Inc. + 1122 Stanyan St + San Francisco CA 94117 + US -CC-06-77 (hex) Fiberhome Telecommunication Technologies Co.,LTD -CC0677 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +78-0F-77 (hex) HangZhou Gubei Electronics Technology Co.,Ltd +780F77 (base 16) HangZhou Gubei Electronics Technology Co.,Ltd + HangZhou City, Zhejiang province Binjiang District Jiang Hong Road 611 Building 1 room 106 + Hangzhou ZheJiang 310052 CN -14-78-0B (hex) Varex Imaging Deutschland AG -14780B (base 16) Varex Imaging Deutschland AG - Zweigniederlassung/Branch Walluf - In der Rehbach 22 Walluf 65396 - DE +4C-AB-FC (hex) zte corporation +4CABFC (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -00-80-C2 (hex) IEEE 802.1 Chair -0080C2 (base 16) IEEE 802.1 Chair - c/o RAC Administrator , IEEE - Piscataway NJ 08554 - US +7C-2A-31 (hex) Intel Corporate +7C2A31 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -EC-F3-42 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -ECF342 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +0C-F3-46 (hex) Xiaomi Communications Co Ltd +0CF346 (base 16) Xiaomi Communications Co Ltd + The Rainbow City of China Resources + NO.68, Qinghe Middle Street Haidian District, Beijing 100085 CN -D4-25-8B (hex) Intel Corporate -D4258B (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 +74-70-FD (hex) Intel Corporate +7470FD (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -04-1B-6D (hex) LG Electronics (Mobile Communications) -041B6D (base 16) LG Electronics (Mobile Communications) - 60-39, Gasan-dong, Geumcheon-gu - Seoul 153-801 - KR - -F4-41-56 (hex) Arrikto Inc. -F44156 (base 16) Arrikto Inc. - 3505 El Camino Real - Palo Alto CA 94306 +50-65-F3 (hex) Hewlett Packard +5065F3 (base 16) Hewlett Packard + 11445 Compaq Center Drive + Houston 77070 US -98-6C-5C (hex) Jiangxi Gosun Guard Security Co.,Ltd -986C5C (base 16) Jiangxi Gosun Guard Security Co.,Ltd - 2rd floor,8 building Middle,Zhongxing Software Park,Changdong Avenue,Nanchang High Tech Zone,Nanchang city,Jiangxi province - Nan Chang City Jiang Xi Province 330000 - CN - -50-FF-20 (hex) Keenetic Limited -50FF20 (base 16) Keenetic Limited - 1202, 12/F., AT TOWER, 180 ELECTRIC ROAD, NORTH POINT - HONG KONG 852 - HK - -68-8D-B6 (hex) AETEK INC. -688DB6 (base 16) AETEK INC. - 3F, No.192, Lien-Cheng Rd., Chung-Ho, - New Taipei City 23553 +3C-95-09 (hex) Liteon Technology Corporation +3C9509 (base 16) Liteon Technology Corporation + 4F, 90, Chien 1 Road + New Taipei City Taiwan 23585 TW -00-F8-2C (hex) Cisco Systems, Inc -00F82C (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +30-FD-38 (hex) Google, Inc. +30FD38 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US -00-C1-B1 (hex) Cisco Systems, Inc -00C1B1 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +64-CB-5D (hex) SIA TeleSet +64CB5D (base 16) SIA TeleSet + Krāslavas iela 5 + Vecstropi, Naujenes par., Daugavpils distr. LV-5413 + LV + +58-21-E9 (hex) TWPI +5821E9 (base 16) TWPI + PMB# 335; 1121 Annapolis Road + Odenton MD 21113 US -F4-FC-B1 (hex) JJ Corp -F4FCB1 (base 16) JJ Corp - 88 Soha-ro - Gwangmyeong-si 14316 - KR +F0-E3-DC (hex) Tecon MT, LLC +F0E3DC (base 16) Tecon MT, LLC + 3rd Khoroshevskaya st - 20 + Moscow 123298 + RU -8C-39-5C (hex) Bit4id Srl -8C395C (base 16) Bit4id Srl - Via Diocleziano, 107 - Naples 80125 - IT +A8-DA-01 (hex) Shenzhen NUOLIJIA Digital Technology Co.,Ltd +A8DA01 (base 16) Shenzhen NUOLIJIA Digital Technology Co.,Ltd + A Area of The Second Flood and D Area of The First Floor,Factory Building A,Youxinda Industrial Park,Gengyu Road,Tianliao Community,Gongming Street Office,Guangming New District,Shenzhen City,Guangdong,P.R.China + Shenzhen Guangdong 518000 + CN -30-9C-23 (hex) Micro-Star INTL CO., LTD. -309C23 (base 16) Micro-Star INTL CO., LTD. - No.69, Lide St., - New Taipei City Taiwan 235 - TW +88-E9-0F (hex) innomdlelab +88E90F (base 16) innomdlelab + Unnam 1 gil, 3 + Seocho-gu Seoul 06778 + KR -14-5E-45 (hex) Bamboo Systems Group -145E45 (base 16) Bamboo Systems Group - Sheraton House, Castle Park - Cambridge CAMBRIDGESHIRE CB3 0AX +00-10-D8 (hex) CALISTA +0010D8 (base 16) CALISTA + 56A Packhorse Road + Bucks SL9 8EF ENGLAND GB -AC-AF-B9 (hex) Samsung Electronics Co.,Ltd -ACAFB9 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 +00-21-94 (hex) Ping Communication +002194 (base 16) Ping Communication + Brenden 18 + Appenzell Meistersrüte AI 9050 + CH + +18-94-C6 (hex) ShenZhen Chenyee Technology Co., Ltd. +1894C6 (base 16) ShenZhen Chenyee Technology Co., Ltd. + 32F, Tower A, East Pacific International Center, No.7888, Shennan Avenue, Futian District + Shenzhen 518040 + CN + +80-AD-16 (hex) Xiaomi Communications Co Ltd +80AD16 (base 16) Xiaomi Communications Co Ltd + The Rainbow City of China Resources + NO.68, Qinghe Middle Street Haidian District, Beijing 100085 + CN + +04-4E-AF (hex) LG Innotek +044EAF (base 16) LG Innotek + 26, Hanamsandan 5beon-ro + Gwangju Gwangsan-gu 506-731 KR -18-12-12 (hex) Cepton Technologies -181212 (base 16) Cepton Technologies - 103 Bonaventura Dr - San Jose CA 95134 - US +98-D8-63 (hex) Shanghai High-Flying Electronics Technology Co., Ltd +98D863 (base 16) Shanghai High-Flying Electronics Technology Co., Ltd + Room 1002 ,#1Building,No.3000 Longdong Avenue,Pudong District,Shanghai,China + shanghai shanghai 201203 + CN -70-D9-23 (hex) vivo Mobile Communication Co., Ltd. -70D923 (base 16) vivo Mobile Communication Co., Ltd. - #283,BBK Road - Wusha,Chang'an DongGuan City,Guangdong 523860 +C4-9F-4C (hex) HUAWEI TECHNOLOGIES CO.,LTD +C49F4C (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -B8-3A-08 (hex) Tenda Technology Co.,Ltd.Dongguan branch -B83A08 (base 16) Tenda Technology Co.,Ltd.Dongguan branch - Room 79,Yuanyi Road,Dalang Town,Dongguan Guangdong 523770 - Dongguan Guangdong 523770 +0C-70-4A (hex) HUAWEI TECHNOLOGIES CO.,LTD +0C704A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -50-D3-7F (hex) Yu Fly Mikly Way Science and Technology Co., Ltd. -50D37F (base 16) Yu Fly Mikly Way Science and Technology Co., Ltd. - 6F, No. 1168 Huyi Road, Nanxiang Town Jiading District Shanghai 201800 CHINA - shanghai 201800 +10-F9-EB (hex) Industria Fueguina de Relojería Electrónica s.a. +10F9EB (base 16) Industria Fueguina de Relojería Electrónica s.a. + Sarmiento 2920 + Rio Grande Tierra de Fuego V9420GIV + AR + +5C-5A-EA (hex) FORD +5C5AEA (base 16) FORD + 17425 Federal Drive + Allen Park MI 48101 + US + +00-0B-7B (hex) Test-Um Inc. +000B7B (base 16) Test-Um Inc. + 808 Calle Plano + Camarillo CA 93012 + US + +C8-8F-26 (hex) Skyworth Digital Technology(Shenzhen) Co.,Ltd +C88F26 (base 16) Skyworth Digital Technology(Shenzhen) Co.,Ltd + 7F,Block A,Skyworth Building, + Shenzhen Guangdong 518057 CN -B0-3D-96 (hex) Vision Valley FZ LLC -B03D96 (base 16) Vision Valley FZ LLC - Dubai Internet City - Dubai Dubai 500294 - AE +20-36-5B (hex) Megafone Limited +20365B (base 16) Megafone Limited + Unit 702,7/F,Bankok Bank Building,NO.18 Bonham Strand West + Hong Kong 999077 + HK -F8-94-C2 (hex) Intel Corporate -F894C2 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +E8-DE-00 (hex) ChongQing GuanFang Technology Co.,LTD +E8DE00 (base 16) ChongQing GuanFang Technology Co.,LTD + 2F, A District,No.3 Middle Section of Mount Huangshan Avenue + ChongQing ChongQing 401121 + CN -70-D3-79 (hex) Cisco Systems, Inc -70D379 (base 16) Cisco Systems, Inc +FC-64-3A (hex) Samsung Electronics Co.,Ltd +FC643A (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +00-30-74 (hex) EQUIINET LTD. +003074 (base 16) EQUIINET LTD. + EDISON HOUSE + SWINDON, SN3 5JA + GB + +B0-B3-AD (hex) HUMAX Co., Ltd. +B0B3AD (base 16) HUMAX Co., Ltd. + HUMAX Village, 216, Hwangsaeul-ro, Bu + Seongnam-si Gyeonggi-do 463-875 + KR + +40-BD-32 (hex) Texas Instruments +40BD32 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +CC-8E-71 (hex) Cisco Systems, Inc +CC8E71 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -10-05-01 (hex) PEGATRON CORPORATION -100501 (base 16) PEGATRON CORPORATION - 5F No. 76, Ligong St., Beitou District - Taipei City Taiwan 112 +70-D0-81 (hex) Beijing Netpower Technologies Inc. +70D081 (base 16) Beijing Netpower Technologies Inc. + Room 201, Block B, NO. 15 Building, EastZone + Courtyard10, Xibeiwang East Road Haidian District, Beijing 100094 + CN + +70-C9-4E (hex) Liteon Technology Corporation +70C94E (base 16) Liteon Technology Corporation + 4F, 90, Chien 1 Road + New Taipei City Taiwan 23585 TW -D8-C8-E9 (hex) Phicomm (Shanghai) Co., Ltd. -D8C8E9 (base 16) Phicomm (Shanghai) Co., Ltd. - 3666 SiXian Rd.,Songjiang District - Shanghai Shanghai 201616 +38-F5-54 (hex) HISENSE ELECTRIC CO.,LTD +38F554 (base 16) HISENSE ELECTRIC CO.,LTD + No. 218, Qianwangang Rd + Qingdao Shandong 266555 CN -7C-B9-60 (hex) Shanghai X-Cheng telecom LTD -7CB960 (base 16) Shanghai X-Cheng telecom LTD - ROOM 401, Building 5, No.3000 LONG DONG Avenue, Pudong New District, Shanghai - Shanghai Shanghai 201203 - CN +18-A2-8A (hex) Essel-T Co., Ltd +18A28A (base 16) Essel-T Co., Ltd + 1211 kranztechno, 388 Dunchon-daero + Seongnam-si Jungwon-gu, Gyeonggi-do 13403 + KR -28-B4-48 (hex) HUAWEI TECHNOLOGIES CO.,LTD -28B448 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +A8-51-5B (hex) Samsung Electronics Co.,Ltd +A8515B (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +00-19-36 (hex) STERLITE OPTICAL TECHNOLOGIES LIMITED +001936 (base 16) STERLITE OPTICAL TECHNOLOGIES LIMITED + E-1,E-2,&E-3 + AURANGABAD MAHARASTRA 431136 + IN + +3C-E8-24 (hex) HUAWEI TECHNOLOGIES CO.,LTD +3CE824 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -70-DB-98 (hex) Cisco Systems, Inc -70DB98 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +54-B7-E5 (hex) Rayson Technology Co., Ltd. +54B7E5 (base 16) Rayson Technology Co., Ltd. + 1F No.9 R&D Rd.II, Science-Based Industrial Park + Hsin-Chu 300 + TW -30-D3-86 (hex) zte corporation -30D386 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +94-63-72 (hex) vivo Mobile Communication Co., Ltd. +946372 (base 16) vivo Mobile Communication Co., Ltd. + #283,BBK Road + Wusha,Chang'An DongGuan City,Guangdong, 523860 CN -24-79-2A (hex) Ruckus Wireless -24792A (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +BC-0F-A7 (hex) Ouster +BC0FA7 (base 16) Ouster + 350 Treat Ave + San Francisco CA 94110 US -A4-9B-F5 (hex) Hybridserver Tec GmbH -A49BF5 (base 16) Hybridserver Tec GmbH - Gutenbergring 26a - Norderstedt Schleswig-Holstein 22848 - DE +F0-C9-D1 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +F0C9D1 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN -F4-70-AB (hex) vivo Mobile Communication Co., Ltd. -F470AB (base 16) vivo Mobile Communication Co., Ltd. - #283,BBK Road - Wusha,Chang'An DongGuan City,Guangdong, 523860 +F8-C1-20 (hex) Xi'an Link-Science Technology Co.,Ltd +F8C120 (base 16) Xi'an Link-Science Technology Co.,Ltd + 1/F,Block F,Building zhichao Weilai,No.999,10#Caotan Road,Xi'an + xi'an 710016 CN -2C-5A-0F (hex) Cisco Systems, Inc -2C5A0F (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +B4-FB-F9 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B4FBF9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -2C-31-24 (hex) Cisco Systems, Inc -2C3124 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +50-6F-77 (hex) HUAWEI TECHNOLOGIES CO.,LTD +506F77 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -9C-C8-AE (hex) Becton, Dickinson and Company -9CC8AE (base 16) Becton, Dickinson and Company - 1 Becton Drive - Franklin Lakes MA 07417-1880 +0C-41-E9 (hex) HUAWEI TECHNOLOGIES CO.,LTD +0C41E9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +70-F2-20 (hex) Actiontec Electronics, Inc +70F220 (base 16) Actiontec Electronics, Inc + 3301 Olcott St. + Santa Clara CA 95054 US -B0-35-9F (hex) Intel Corporate -B0359F (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +34-5A-06 (hex) SHARP Corporation +345A06 (base 16) SHARP Corporation + 1 Takumi-cho, Sakai-ku + Sakai City Osaka 590-8522 + JP -C0-D9-62 (hex) ASKEY COMPUTER CORP -C0D962 (base 16) ASKEY COMPUTER CORP - 10F,NO.119,JIANKANG RD.,ZHONGHE DIST XINBEI CITY - taipei TAIPEI 23585 - TW +04-02-CA (hex) Shenzhen Vtsonic Co.,ltd +0402CA (base 16) Shenzhen Vtsonic Co.,ltd + No.35,the 2nd Industrial Zone,Tangxiayong Village,Songgang Town,Bao'an District,Shenzhen,China. + Shenzhen Guangdong 518102 + CN -F8-0B-CB (hex) Cisco Systems, Inc -F80BCB (base 16) Cisco Systems, Inc +3C-FB-5C (hex) Fiberhome Telecommunication Technologies Co.,LTD +3CFB5C (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN + +74-40-BB (hex) Hon Hai Precision Ind. Co.,Ltd. +7440BB (base 16) Hon Hai Precision Ind. Co.,Ltd. + Building D21,No.1, East Zone 1st Road + Chongqing Chongqing 401332 + CN + +88-BD-45 (hex) Samsung Electronics Co.,Ltd +88BD45 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +54-FC-F0 (hex) Samsung Electronics Co.,Ltd +54FCF0 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +30-6A-85 (hex) Samsung Electronics Co.,Ltd +306A85 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +4C-DD-31 (hex) Samsung Electronics Co.,Ltd +4CDD31 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +B4-DE-31 (hex) Cisco Systems, Inc +B4DE31 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -B4-7C-9C (hex) Amazon Technologies Inc. -B47C9C (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +A4-40-27 (hex) zte corporation +A44027 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -50-3D-A1 (hex) Samsung Electronics Co.,Ltd -503DA1 (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 +B4-F7-A1 (hex) LG Electronics (Mobile Communications) +B4F7A1 (base 16) LG Electronics (Mobile Communications) + 60-39, Gasan-dong, Geumcheon-gu + Seoul 153-801 KR -50-32-37 (hex) Apple, Inc. -503237 (base 16) Apple, Inc. +70-EF-00 (hex) Apple, Inc. +70EF00 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -B0-48-1A (hex) Apple, Inc. -B0481A (base 16) Apple, Inc. +28-ED-E0 (hex) AMPAK Technology, Inc. +28EDE0 (base 16) AMPAK Technology, Inc. + No.1,Jen Ai Road Hsinchu Industrial Park, Hukou + Hsinchu Taiwan ROC. 30352 + TW + +1C-11-61 (hex) Ciena Corporation +1C1161 (base 16) Ciena Corporation + 7035 Ridge Road + Hanover MD 21076 + US + +C8-77-65 (hex) Tiesse SpA +C87765 (base 16) Tiesse SpA + Via Asti + Ivrea TO 10015 + IT + +D0-81-7A (hex) Apple, Inc. +D0817A (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -B4-9C-DF (hex) Apple, Inc. -B49CDF (base 16) Apple, Inc. +98-CA-33 (hex) Apple, Inc. +98CA33 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -48-BF-6B (hex) Apple, Inc. -48BF6B (base 16) Apple, Inc. +68-AB-1E (hex) Apple, Inc. +68AB1E (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -2C-FA-A2 (hex) Alcatel-Lucent Enterprise -2CFAA2 (base 16) Alcatel-Lucent Enterprise - 26801 West Agoura Rd - Calabasas CA 91301 +BC-FF-EB (hex) Motorola Mobility LLC, a Lenovo Company +BCFFEB (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -E8-E7-32 (hex) Alcatel-Lucent Enterprise -E8E732 (base 16) Alcatel-Lucent Enterprise - 26801 West Agoura Road - Calabasas CA 91301 - US +2C-37-C5 (hex) Qingdao Haier Intelligent Home Appliance Technology Co.,Ltd +2C37C5 (base 16) Qingdao Haier Intelligent Home Appliance Technology Co.,Ltd + ingdao high-tech park haier road 1 + Qingdao Shandong 266101 + CN -7C-26-64 (hex) Sagemcom Broadband SAS -7C2664 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +7C-76-30 (hex) Shenzhen YOUHUA Technology Co., Ltd +7C7630 (base 16) Shenzhen YOUHUA Technology Co., Ltd + Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District + Shenzhen Guangdong 518055 + CN -AC-6B-0F (hex) CADENCE DESIGN SYSTEMS INC -AC6B0F (base 16) CADENCE DESIGN SYSTEMS INC - 2670 SEELY AVE - SAN JOSE CA 95134 - US +98-22-EF (hex) Liteon Technology Corporation +9822EF (base 16) Liteon Technology Corporation + 4F, 90, Chien 1 Road + New Taipei City Taiwan 23585 + TW -7C-38-66 (hex) Texas Instruments -7C3866 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +7C-76-35 (hex) Intel Corporate +7C7635 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -0C-61-CF (hex) Texas Instruments -0C61CF (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +B8-07-16 (hex) vivo Mobile Communication Co., Ltd. +B80716 (base 16) vivo Mobile Communication Co., Ltd. + #283,BBK Road + Wusha,Chang'An DongGuan City,Guangdong, 523860 + CN -9C-1D-58 (hex) Texas Instruments -9C1D58 (base 16) Texas Instruments +78-80-38 (hex) FUNAI ELECTRIC CO., LTD. +788038 (base 16) FUNAI ELECTRIC CO., LTD. + 7-1, NAKAGAITO 7-CHOME + DAITO OSAKA 5740013 + JP + +F0-45-DA (hex) Texas Instruments +F045DA (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -58-82-1D (hex) H. Schomäcker GmbH -58821D (base 16) H. Schomäcker GmbH - Heidestr. 183 - Köln 51147 - DE +1C-EE-C9 (hex) Elo touch solutions +1CEEC9 (base 16) Elo touch solutions + 1033 McCarthy Boulevard + Milpitas CA 95035 + US -D8-A1-05 (hex) Syslane, Co., Ltd. -D8A105 (base 16) Syslane, Co., Ltd. - #1201, Megacenter, SKntechno-park,, Sangdaeweon-dong, Joongweon-gu - Seongnam Outside the US, Mexico, or Canada 462-721 - KR +00-18-62 (hex) Seagate Technology +001862 (base 16) Seagate Technology + 1280 Disc Drive + Shakopee MN 55379 + US -3C-05-18 (hex) Samsung Electronics Co.,Ltd -3C0518 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +00-0C-50 (hex) Seagate Technology +000C50 (base 16) Seagate Technology + M/S NW1F01 + Longmont CO 80503 + US -90-06-28 (hex) Samsung Electronics Co.,Ltd -900628 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +E4-F0-42 (hex) Google, Inc. +E4F042 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US -9C-84-BF (hex) Apple, Inc. -9C84BF (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +20-B3-99 (hex) Enterasys +20B399 (base 16) Enterasys + 50 Minuteman Rd + Andover MA 01810 US -B4-A9-FE (hex) GHIA Technology (Shenzhen) LTD -B4A9FE (base 16) GHIA Technology (Shenzhen) LTD - RM 1606, BLDG 3rd, COFCO Clouds Mansion - Shenzhen 518101 +CC-2D-21 (hex) Tenda Technology Co.,Ltd.Dongguan branch +CC2D21 (base 16) Tenda Technology Co.,Ltd.Dongguan branch + Room 79,Yuanyi Road,Dalang Town,Dongguan Guangdong 523770 + Dongguan Guangdong 523770 CN -F0-97-E5 (hex) TAMIO, INC -F097E5 (base 16) TAMIO, INC - 12F-2, No.33, Sec. 1 , Mingsheng Rd.,Banqiao Dist - New Taipei City 22069 - TW - -4C-1A-3D (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -4C1A3D (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN +00-40-97 (hex) DATEX DIVISION OF +004097 (base 16) DATEX DIVISION OF + INSTRUMENTARIUM CORP. + + FI -1C-A0-D3 (hex) IEEE Registration Authority -1CA0D3 (base 16) IEEE Registration Authority +40-48-FD (hex) IEEE Registration Authority +4048FD (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -9C-FC-D1 (hex) Aetheris Technology (Shanghai) Co., Ltd. -9CFCD1 (base 16) Aetheris Technology (Shanghai) Co., Ltd. - Room 225, Building No. 8, 433 Yuyuan Road - Shanghai Shanghai 200040 +A8-EE-C6 (hex) Muuselabs NV/SA +A8EEC6 (base 16) Muuselabs NV/SA + Rue du Tocsin 12 + Brussels 1000 + BE + +9C-4F-CF (hex) TCT mobile ltd +9C4FCF (base 16) TCT mobile ltd + No.86 hechang 7th road, zhongkai, Hi-Tech District + Hui Zhou Guang Dong 516006 CN -BC-A0-42 (hex) SHANGHAI FLYCO ELECTRICAL APPLIANCE CO.,LTD -BCA042 (base 16) SHANGHAI FLYCO ELECTRICAL APPLIANCE CO.,LTD - No.555,Guang Fu Lin east Road,Songjiang District - Shanghai Shanghai 201613 +D8-96-E0 (hex) Alibaba Cloud Computing Ltd. +D896E0 (base 16) Alibaba Cloud Computing Ltd. + Yuhang District of Hangzhou Wenyi Road, Building 1, No. 969 Xixi Park, Zhejiang Province + Hangzhou Zhejiang 310000 CN -0C-F4-D5 (hex) Ruckus Wireless -0CF4D5 (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 - US +20-78-52 (hex) Nokia Solutions and Networks GmbH & Co. KG +207852 (base 16) Nokia Solutions and Networks GmbH & Co. KG + Werinherstrasse 91 + München Bavaria D-81541 + DE -00-11-1B (hex) Targa Systems Div L-3 Communications -00111B (base 16) Targa Systems Div L-3 Communications - 2081 Merivale Rd - Ottawa Ont K2G 1G9 - CA +F4-17-B8 (hex) AirTies Wireless Networks +F417B8 (base 16) AirTies Wireless Networks + Esentepe Mah., Kore ?ehitleri Cad. + Istanbul ?i?li 34360 + TR -68-54-C1 (hex) ColorTokens, Inc. -6854C1 (base 16) ColorTokens, Inc. - 2101 Tasman Dr. Suite 200A - Santa Clara CA 95054 +38-F7-3D (hex) Amazon Technologies Inc. +38F73D (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -34-6E-9D (hex) Ericsson AB -346E9D (base 16) Ericsson AB - Torshamnsgatan 36 - Stockholm SE-164 80 - SE - -F4-DC-41 (hex) YOUNGZONE CULTURE (SHANGHAI) CORP -F4DC41 (base 16) YOUNGZONE CULTURE (SHANGHAI) CORP - 7-8th floor, #1 Building, 1006 Jinshajiang Road - Shanghai Shanghai 200062 - CN - -00-0F-4F (hex) PCS Systemtechnik GmbH -000F4F (base 16) PCS Systemtechnik GmbH - 66 Hillside Rd - Auckland 1310 - NZ +B8-F7-4A (hex) RCNTEC +B8F74A (base 16) RCNTEC + Polkovaya street 3 + Moscow 127018 + RU -6C-75-0D (hex) WiFiSONG -6C750D (base 16) WiFiSONG - Rm. 605, Building 3, No. 75 Wenyi West Road - Hangzhou Zhejiang 310012 - CN +EC-F4-51 (hex) Arcadyan Corporation +ECF451 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW -38-05-AC (hex) Piller Group GmbH -3805AC (base 16) Piller Group GmbH - Abgunst 24 - Osterode 37520 - DE +58-12-43 (hex) AcSiP Technology Corp. +581243 (base 16) AcSiP Technology Corp. + 3F., No.22, Dalin Rd., + Taoyuan Taoyuan County 33067 + TW -BC-3F-8F (hex) HUAWEI TECHNOLOGIES CO.,LTD -BC3F8F (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +58-C1-7A (hex) Cambium Networks Limited +58C17A (base 16) Cambium Networks Limited + Unit B2, Linhay Business Park, + Ashburton Devon TQ13 7UP + GB -14-30-04 (hex) HUAWEI TECHNOLOGIES CO.,LTD -143004 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +E0-AA-DB (hex) Nanjing PANENG Technology Development Co.,Ltd +E0AADB (base 16) Nanjing PANENG Technology Development Co.,Ltd + NO.6 Paneng Road,Nanjing High-tech Zone,Jiang Su,China + Nanjing 210061 CN -38-AA-3C (hex) SAMSUNG ELECTRO MECHANICS CO., LTD. -38AA3C (base 16) SAMSUNG ELECTRO MECHANICS CO., LTD. - 314, Maetan3-Dong, Yeongtong-Gu - Suwon 443-743 - US - -50-A4-D0 (hex) IEEE Registration Authority -50A4D0 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +C8-DE-C9 (hex) Coriant +C8DEC9 (base 16) Coriant + 1415 W. Diehl Rd + Naperville IL 60563 US -80-00-10 (hex) AT&T -800010 (base 16) AT&T - 3300 E Renner Road - Richardson TX 75082 +34-2A-F1 (hex) Texas Instruments +342AF1 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -14-AB-C5 (hex) Intel Corporate -14ABC5 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +0C-61-11 (hex) Anda Technologies SAC +0C6111 (base 16) Anda Technologies SAC + Av. Santa Cruz 888, Miraflores + Lima Peru Lima18 + PE -00-24-F1 (hex) Shenzhen Fanhai Sanjiang Electronics Co., Ltd. -0024F1 (base 16) Shenzhen Fanhai Sanjiang Electronics Co., Ltd. - 3Floor-Guangcai Xintiandi Building,Nanshan Rd,Nanshan, - Shenzhen Guangdong 518054 - CN +00-22-C4 (hex) epro GmbH +0022C4 (base 16) epro GmbH + Joebkesweg 3 + Gronau NRW 48599 + DE -50-D2-13 (hex) CviLux Corporation -50D213 (base 16) CviLux Corporation - 9F,No.9,Lane 3,Sec.1,Chung-Cheng East Road, Tamshui - New Taipei City 25147 - TW +0C-15-39 (hex) Apple, Inc. +0C1539 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -00-1E-29 (hex) Hypertherm Inc -001E29 (base 16) Hypertherm Inc - 15 Great Hollow Rd - Hanover NH 03755 +1C-33-0E (hex) PernixData +1C330E (base 16) PernixData + 1745 Technology Drive, Suite 800 + San Jose CA 95110 US -7C-C6-C4 (hex) Kolff Computer Supplies b.v. -7CC6C4 (base 16) Kolff Computer Supplies b.v. - Kuipershaven 22 - Dordrecht Zuid-Holland 3311 AL - NL +44-D5-A5 (hex) AddOn Computer +44D5A5 (base 16) AddOn Computer + 15775 Gateway cir + tustin CA 92780 + US -C4-83-6F (hex) Ciena Corporation -C4836F (base 16) Ciena Corporation - 7035 Ridge Road - Hanover MD 21076 +64-51-06 (hex) Hewlett Packard +645106 (base 16) Hewlett Packard + 11445 Compaq Center Drive + Houston 77070 US -50-04-B8 (hex) HUAWEI TECHNOLOGIES CO.,LTD -5004B8 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +00-BE-9E (hex) Fiberhome Telecommunication Technologies Co.,LTD +00BE9E (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -5C-FF-35 (hex) Wistron Corporation -5CFF35 (base 16) Wistron Corporation - 21F, 88, Sec.1, Hsin Tai Wu Rd., Hsichih, - Taipei Hsien 221 - TW - -78-F2-9E (hex) PEGATRON CORPORATION -78F29E (base 16) PEGATRON CORPORATION - 5F No. 76, Ligong St., Beitou District - Taipei City Taiwan 112 - TW - -64-77-7D (hex) Hitron Technologies. Inc -64777D (base 16) Hitron Technologies. Inc - No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C - Hsin-chu Taiwan 300 - TW - -60-D2-62 (hex) Tzukuri Pty Ltd -60D262 (base 16) Tzukuri Pty Ltd - 6 Lenthall Street - Kensington NSW 2033 - AU +54-C5-7A (hex) Sunnovo International Limited +54C57A (base 16) Sunnovo International Limited + 1717 Haitai Building + Beijing Beijing 100083 + CN -14-2F-FD (hex) LT SECURITY INC -142FFD (base 16) LT SECURITY INC - 18738 SAN JOSE AVE - CITY OF INDUSTRY CA 91748 +6C-56-97 (hex) Amazon Technologies Inc. +6C5697 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -00-D0-B2 (hex) Xiotech Corporation -00D0B2 (base 16) Xiotech Corporation - 6455 FLYING CLOUD DRIVE - EDEN PRAIRIE MN 55344 +F8-7B-20 (hex) Cisco Systems, Inc +F87B20 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -9C-50-EE (hex) Cambridge Industries(Group) Co.,Ltd. -9C50EE (base 16) Cambridge Industries(Group) Co.,Ltd. - 5/F,Building 8, 2388 ChenHang Road, MinHang District - shanghai 201114 +38-AD-8E (hex) New H3C Technologies Co., Ltd +38AD8E (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -40-ED-98 (hex) IEEE Registration Authority -40ED98 (base 16) IEEE Registration Authority +34-D0-B8 (hex) IEEE Registration Authority +34D0B8 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -AC-DC-E5 (hex) Procter & Gamble Company -ACDCE5 (base 16) Procter & Gamble Company - 2 Procter & Gamble Plaza - Cincinnati OH 45202 - US +EC-FA-F4 (hex) SenRa Tech Pvt. Ltd +ECFAF4 (base 16) SenRa Tech Pvt. Ltd + 133, First Floor, Lane No. 1, Westend Marg, Saidulajab + New Delhi 110030 + IN -00-B3-62 (hex) Apple, Inc. -00B362 (base 16) Apple, Inc. +00-05-FF (hex) SNS Solutions, Inc. +0005FF (base 16) SNS Solutions, Inc. + 2nd Fl. Hill House, + + KR + +D8-8F-76 (hex) Apple, Inc. +D88F76 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -E4-E4-AB (hex) Apple, Inc. -E4E4AB (base 16) Apple, Inc. +40-9C-28 (hex) Apple, Inc. +409C28 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -54-FA-96 (hex) Nokia Solutions and Networks GmbH & Co. KG -54FA96 (base 16) Nokia Solutions and Networks GmbH & Co. KG - Werinherstrasse 91 - München Bavaria D-81541 - DE +00-1C-73 (hex) Arista Networks +001C73 (base 16) Arista Networks + 5470 Great America Pkwy + Santa Clara 95054 + US -60-33-4B (hex) Apple, Inc. -60334B (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +2C-8A-72 (hex) HTC Corporation +2C8A72 (base 16) HTC Corporation + No. 23, Xinghua Rd., Taoyuan City + Taoyuan County Taiwan 330 + TW + +38-01-9F (hex) SHENZHEN FAST TECHNOLOGIES CO.,LTD +38019F (base 16) SHENZHEN FAST TECHNOLOGIES CO.,LTD + Room 202,Building No.5,Section 30,No.2 of Kefa Road,Nanshan District,Shenzhen,P.R.China + Shenzhen Guangdong 518057 + CN + +24-5C-CB (hex) AXIe Consortium, Inc. +245CCB (base 16) AXIe Consortium, Inc. + P.O. Box 1016 + Niwot CO 80544-1016 US -C8-91-F9 (hex) Sagemcom Broadband SAS -C891F9 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison HAUTS DE SEINE 92848 - FR +60-9B-C8 (hex) Hipad Intelligent Technology Co., Ltd. +609BC8 (base 16) Hipad Intelligent Technology Co., Ltd. + No. 688, East of Huangtang Street, LinkongEconomy District + Nanchang Jiangxi 330000 + CN -84-04-D2 (hex) Kirale Technologies SL -8404D2 (base 16) Kirale Technologies SL - General Vara de Rey 9, 5B - Logrono La Rioja 26001 - ES +40-6A-8E (hex) Hangzhou Puwell OE Tech Ltd. +406A8E (base 16) Hangzhou Puwell OE Tech Ltd. + Letel Technology Park, 500 Qiuyi Road, Binjiang District + Hangzhou Zhejiang 310052 + CN -38-AF-D7 (hex) FUJITSU LIMITED -38AFD7 (base 16) FUJITSU LIMITED - 403, Kosugi-cho 1-chome, Nakahara-ku - Kawasaki Kanagawa 211-0063 - JP +1C-0F-AF (hex) Lucid Vision Labs +1C0FAF (base 16) Lucid Vision Labs + Unit 130 - 13200 Delf Place + Richmond BC V6V2A2 + CA -28-99-3A (hex) Arista Networks -28993A (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 +88-B4-A6 (hex) Motorola Mobility LLC, a Lenovo Company +88B4A6 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -60-42-7F (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD -60427F (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD - Unit East Block22-24/F,Skyworth semiconductor design Bldg., Gaoxin Ave.4.S.,Nanshan District,Shenzhen,China - SHENZHEN GUANGDONG 518057 +54-DF-24 (hex) Fiberhome Telecommunication Technologies Co.,LTD +54DF24 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -BC-A8-A6 (hex) Intel Corporate -BCA8A6 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +AC-1D-DF (hex) IEEE Registration Authority +AC1DDF (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -74-FF-4C (hex) Skyworth Digital Technology(Shenzhen) Co.,Ltd -74FF4C (base 16) Skyworth Digital Technology(Shenzhen) Co.,Ltd - 7F,Block A,Skyworth Building, - Shenzhen Guangdong 518057 - CN +24-B2-09 (hex) Avaya Inc +24B209 (base 16) Avaya Inc + 360 Mt Kemble Ave + Morristown NJ 07960 + US -A0-2C-36 (hex) FN-LINK TECHNOLOGY LIMITED -A02C36 (base 16) FN-LINK TECHNOLOGY LIMITED - A Building,HuiXin industial park,No 31, YongHe road, Fuyong town, Bao'an District - SHENZHEN GUANGDONG 518100 - CN +FC-65-DE (hex) Amazon Technologies Inc. +FC65DE (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -F4-8C-50 (hex) Intel Corporate -F48C50 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +BC-90-3A (hex) Robert Bosch GmbH +BC903A (base 16) Robert Bosch GmbH + Postfach 1661 + Leonberg 71226 + DE -DC-D2-55 (hex) Kinpo Electronics, Inc. -DCD255 (base 16) Kinpo Electronics, Inc. - No.147, Sec. 3, Beishen Rd - Shenkeng Dist. New Taipei City 222 +E8-D8-19 (hex) AzureWave Technology Inc. +E8D819 (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 TW -64-EB-8C (hex) Seiko Epson Corporation -64EB8C (base 16) Seiko Epson Corporation - 80 Harashinden - Shiojiri-shi Nagano-ken 399-0785 - JP +B0-6E-BF (hex) ASUSTek COMPUTER INC. +B06EBF (base 16) ASUSTek COMPUTER INC. + 15,Li-Te Rd., Peitou, Taipei 112, Taiwan + Taipei Taiwan 112 + TW -00-13-51 (hex) Niles Audio Corporation -001351 (base 16) Niles Audio Corporation - 5919 Sea Otter Place - Carlsbad CA 92010 +00-21-28 (hex) Oracle Corporation +002128 (base 16) Oracle Corporation + 17 Network Circle + Menlo Park CA 95025 US -00-03-20 (hex) Xpeed, Inc. -000320 (base 16) Xpeed, Inc. - 99 W. Tasman Drive - San Jose CA 95134 - US +28-CF-08 (hex) ESSYS +28CF08 (base 16) ESSYS + gaetbeol-ro + Incheon 21999 + KR -AC-1F-6B (hex) Super Micro Computer, Inc. -AC1F6B (base 16) Super Micro Computer, Inc. - 980 Rock Ave - San Jose CA 95131 +74-86-0B (hex) Cisco Systems, Inc +74860B (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -50-8A-0F (hex) SHENZHEN FISE TECHNOLOGY HOLDING CO.,LTD. -508A0F (base 16) SHENZHEN FISE TECHNOLOGY HOLDING CO.,LTD. - No.6 Building, Longfu Industrial Area, Huarong Road, Tongsheng Community, Dalang Street, Longhua New District - Shenzhen Guangdong 518000 - CN +18-2D-98 (hex) Jinwoo Industrial system +182D98 (base 16) Jinwoo Industrial system + 7F,Jinwoo Building,149 dosan-daero + seoul gangnamgu 06036 + KR -7C-CB-E2 (hex) IEEE Registration Authority -7CCBE2 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +78-2D-7E (hex) TRENDnet, Inc. +782D7E (base 16) TRENDnet, Inc. + 20675 Manhattan Place + Torrance CA 90501 US -A8-A5-E2 (hex) MSF-Vathauer Antriebstechnik GmbH & Co KG -A8A5E2 (base 16) MSF-Vathauer Antriebstechnik GmbH & Co KG - Am Hessentuch 6-8 - Detmold Nordrhein-Westfalen 32758 - DE - -C0-28-8D (hex) Logitech, Inc -C0288D (base 16) Logitech, Inc - 4700 NW Camas Meadows Drive - Camas WA 98607 +74-1A-E0 (hex) IEEE Registration Authority +741AE0 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -6C-EC-5A (hex) Hon Hai Precision Ind. CO.,Ltd. -6CEC5A (base 16) Hon Hai Precision Ind. CO.,Ltd. - B22 Building, NO.52,Tongle Road, Foxconn Industrial Park, District Jiangnan, Nanning, Guangxi, China - Nanning Guangxi 530031 - CN +58-38-79 (hex) RICOH COMPANY, LTD. +583879 (base 16) RICOH COMPANY, LTD. + 1005, Shimo-ogino + Atsugi-City Kanagawa-Pref. 243-0298 + JP -44-C3-46 (hex) HUAWEI TECHNOLOGIES CO.,LTD -44C346 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +F4-4C-70 (hex) Skyworth Digital Technology(Shenzhen) Co.,Ltd +F44C70 (base 16) Skyworth Digital Technology(Shenzhen) Co.,Ltd + 7F,Block A,Skyworth Building, + Shenzhen Guangdong 518057 CN -30-74-96 (hex) HUAWEI TECHNOLOGIES CO.,LTD -307496 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +78-32-1B (hex) D-Link International +78321B (base 16) D-Link International + 1 Internal Business Park, #03-12,The Synergy + Singapore Singapore 609917 + SG -70-8A-09 (hex) HUAWEI TECHNOLOGIES CO.,LTD -708A09 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -18-06-75 (hex) Dilax Intelcom GmbH -180675 (base 16) Dilax Intelcom GmbH - Alt-Moabit 96b - Berlin 10559 - DE - -00-0F-C2 (hex) Uniwell Corporation -000FC2 (base 16) Uniwell Corporation - 5-25, 3-chome, Tenma, Kita-ku - Osaka 530-0043 +D8-A5-34 (hex) Spectronix Corporation +D8A534 (base 16) Spectronix Corporation + 3-28-15, Tarumi-cho + Suita-city Osaka 564-0062 JP -CC-C5-EF (hex) Co-Comm Servicios Telecomunicaciones S.L. -CCC5EF (base 16) Co-Comm Servicios Telecomunicaciones S.L. - Lisboa, 20 Las Rozas - Madrid Madrid 28232 - ES +00-A0-96 (hex) MITSUMI ELECTRIC CO.,LTD. +00A096 (base 16) MITSUMI ELECTRIC CO.,LTD. + 2-11-2, Tsurumaki + Tama-shi Tokyo 206-8567 + JP -90-02-A9 (hex) Zhejiang Dahua Technology Co., Ltd. -9002A9 (base 16) Zhejiang Dahua Technology Co., Ltd. - NO.1199 BinAn Road - Hangzhou Zhejiang 310053 - CN +78-61-7C (hex) MITSUMI ELECTRIC CO.,LTD. +78617C (base 16) MITSUMI ELECTRIC CO.,LTD. + 2-11-2, Tsurumaki + Tama-shi Tokyo 206-8567 + JP -E8-9E-B4 (hex) Hon Hai Precision Ind. Co.,Ltd. -E89EB4 (base 16) Hon Hai Precision Ind. Co.,Ltd. - Building D21,No.1, East Zone 1st Road - Chongqing Chongqing 401332 +EC-51-BC (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +EC51BC (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -D4-6A-6A (hex) Hon Hai Precision Ind. Co.,Ltd. -D46A6A (base 16) Hon Hai Precision Ind. Co.,Ltd. - Building D21,No.1, East Zone 1st Road - Chongqing Chongqing 401332 +F0-79-E8 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +F079E8 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -98-FD-74 (hex) ACT.CO.LTD -98FD74 (base 16) ACT.CO.LTD - 3-RD Floor 93, Sanbon-ro - Gunpo-si Gyeonggi-do 15849 +D0-B1-28 (hex) Samsung Electronics Co.,Ltd +D0B128 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 KR -E0-50-8B (hex) Zhejiang Dahua Technology Co., Ltd. -E0508B (base 16) Zhejiang Dahua Technology Co., Ltd. - No.1199,Waterfront Road - Hangzhou Zhejiang 310053 - CN - -00-00-64 (hex) Yokogawa Digital Computer Corporation -000064 (base 16) Yokogawa Digital Computer Corporation - Shinjuku MIDWEST Bldg.4-30-3 - Yoyogi Shibuya-ku, Tokyo 151-0053 - JP - -D0-F7-3B (hex) Helmut Mauell GmbH Werk Weida -D0F73B (base 16) Helmut Mauell GmbH Werk Weida - Am Rosenhügel 1-7 - Velbert 42553 - DE - -0C-49-33 (hex) Sichuan Jiuzhou Electronic Technology Co., Ltd. -0C4933 (base 16) Sichuan Jiuzhou Electronic Technology Co., Ltd. - No. 259, Jiuzhou Road - Mianyang City Sichuan Province 621000 - CN +BC-54-51 (hex) Samsung Electronics Co.,Ltd +BC5451 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -9C-1E-95 (hex) Actiontec Electronics, Inc -9C1E95 (base 16) Actiontec Electronics, Inc - 3301 Olcott St. +08-66-1F (hex) Palo Alto Networks +08661F (base 16) Palo Alto Networks + 3000 Tannery Way Santa Clara CA 95054 US -64-DB-43 (hex) Motorola (Wuhan) Mobility Technologies Communication Co., Ltd. -64DB43 (base 16) Motorola (Wuhan) Mobility Technologies Communication Co., Ltd. - No.19, Gaoxin 4th Road, Wuhan East Lake High-tech Zone, Wuhan - Wuhan Hubei 430000 +74-EA-C8 (hex) New H3C Technologies Co., Ltd +74EAC8 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -68-AF-13 (hex) Futura Mobility -68AF13 (base 16) Futura Mobility - 515 PENNSYLVANIA AVE - FORT WASHINTON PA 19034 - US - -68-1A-B2 (hex) zte corporation -681AB2 (base 16) zte corporation - 12/F.,zte R&D building,kejinan Road, - shenzhen guangdong 518057 - CN +B4-D6-4E (hex) Caldero Limited +B4D64E (base 16) Caldero Limited + Concordia Works, 30 Sovereign Street + Leeds West Yorkshire LS1 4BA + GB -7C-EB-AE (hex) Ridgeline Instruments -7CEBAE (base 16) Ridgeline Instruments - 4803 Innovation Drive, Suite 3B - Fort Collins CO 80525 +F8-9D-BB (hex) Tintri +F89DBB (base 16) Tintri + 303 Ravendale Dr + Mountain View CA 94070 US -00-25-90 (hex) Super Micro Computer, Inc. -002590 (base 16) Super Micro Computer, Inc. - 980 Rock Avenue - San Jose California 95131 +90-4E-91 (hex) IEEE Registration Authority +904E91 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -24-C1-BD (hex) CRRC DALIAN R&D CO.,LTD. -24C1BD (base 16) CRRC DALIAN R&D CO.,LTD. - No.1 Haoyang North Street,Lvshun Economic Deveopment Zone - Dalian Liaoning 116052 +DC-0C-2D (hex) WEIFANG GOERTEK ELECTRONICS CO.,LTD +DC0C2D (base 16) WEIFANG GOERTEK ELECTRONICS CO.,LTD + Gaoxin 2 Road, Free Trade Zone,Weifang,Shandong,261205,P.R.China + Weifang Shandong 261205 CN -00-A2-EE (hex) Cisco Systems, Inc -00A2EE (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +C4-F3-12 (hex) Texas Instruments +C4F312 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -00-59-DC (hex) Cisco Systems, Inc -0059DC (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +60-18-03 (hex) Daikin Air-conditioning (Shanghai) Co., Ltd. +601803 (base 16) Daikin Air-conditioning (Shanghai) Co., Ltd. + 318 Shen Fu Road, Xin Zhuang Industry Zone, Shanghai, 201108, China + Shanghai 201108 + CN -C8-D3-FF (hex) Hewlett Packard -C8D3FF (base 16) Hewlett Packard - 11445 Compaq Center Drive - Houston TX 77070 - US +8C-FE-B4 (hex) VSOONTECH ELECTRONICS CO., LIMITED +8CFEB4 (base 16) VSOONTECH ELECTRONICS CO., LIMITED + 18th, Floor, On Hong Commericial Building, 145 Hennessy Road, Wanchai, HONG KONG + HongKong 999077 + HK -C4-BE-84 (hex) Texas Instruments -C4BE84 (base 16) Texas Instruments - 12500 TI Blvd - Dallas 75243 - US +94-0E-6B (hex) HUAWEI TECHNOLOGIES CO.,LTD +940E6B (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -F4-F5-24 (hex) Motorola Mobility LLC, a Lenovo Company -F4F524 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 +64-FB-50 (hex) RoomReady/Zdi, Inc. +64FB50 (base 16) RoomReady/Zdi, Inc. + 2200 N. Main Street + Normal IL 61761 US -00-0B-2E (hex) Cal-Comp Electronics & Communications Company Ltd. -000B2E (base 16) Cal-Comp Electronics & Communications Company Ltd. - No.147, Sec. 3, Beishen Rd - Shenkeng Dist New Taipei City --- - TW +58-B4-2D (hex) YSTen Technology Co.,Ltd +58B42D (base 16) YSTen Technology Co.,Ltd + Room 1715,17/F North Star Times Tower,Chaoyang District,Beijing. + Beijing 100101 + CN -48-65-EE (hex) IEEE Registration Authority -4865EE (base 16) IEEE Registration Authority +34-29-8F (hex) IEEE Registration Authority +34298F (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -F4-CA-E5 (hex) FREEBOX SAS -F4CAE5 (base 16) FREEBOX SAS - 8 rue de la Ville l'Eveque - PARIS IdF 75008 - FR +5C-EA-1D (hex) Hon Hai Precision Ind. Co.,Ltd. +5CEA1D (base 16) Hon Hai Precision Ind. Co.,Ltd. + Building D21,No.1, East Zone 1st Road + Chongqing Chongqing 401332 + CN -00-BB-C1 (hex) CANON INC. -00BBC1 (base 16) CANON INC. - 30-2 Shimomaruko 3-chome, - Ohta-ku Tokyo 146-8501 - JP +18-14-56 (hex) Nokia Corporation +181456 (base 16) Nokia Corporation + Elektroniikkatie 10 + Oulu Ou 90590 + FI -00-13-A5 (hex) General Solutions, LTD. -0013A5 (base 16) General Solutions, LTD. - 5902 Sovereign Drive - Houston Texas 77036 - US +78-CA-04 (hex) Nokia Corporation +78CA04 (base 16) Nokia Corporation + Elektroniikkatie 10 + Oulu 90570 + FI -50-98-F3 (hex) Rheem Australia Pty Ltd -5098F3 (base 16) Rheem Australia Pty Ltd - 1 Alan Street - Rydalmere NSW 2116 - AU +10-4B-46 (hex) Mitsubishi Electric Corporation +104B46 (base 16) Mitsubishi Electric Corporation + 2-7-3 + Chiyoda-ku Tokyo 100-8310 + JP -50-6B-8D (hex) Nutanix -506B8D (base 16) Nutanix - 1740 Technology Drive Ste #150 - San Jose CA 95110 - US +00-17-C8 (hex) KYOCERA Display Corporation +0017C8 (base 16) KYOCERA Display Corporation + 2-14-9, Tamagawadai + Tokyo 158-8610 + JP -00-38-DF (hex) Cisco Systems, Inc -0038DF (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +5C-E8-B7 (hex) Oraimo Technology Limited +5CE8B7 (base 16) Oraimo Technology Limited + RMS 05-15,13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG + HONG KONG HONG KONG 999077 + HK -40-F4-13 (hex) Rubezh -40F413 (base 16) Rubezh - Ulyanovskaya str. 28 - Saratov 410056 - RU +CC-66-B2 (hex) Nokia +CC66B2 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -90-00-4E (hex) Hon Hai Precision Ind. Co.,Ltd. -90004E (base 16) Hon Hai Precision Ind. Co.,Ltd. - Building D21,No.1, East Zone 1st Road - Chongqing Chongqing 401332 +38-E2-DD (hex) zte corporation +38E2DD (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -24-88-94 (hex) shenzhen lensun Communication Technology LTD -248894 (base 16) shenzhen lensun Communication Technology LTD - RM 201, Block 19, Zhiheng industry Park, Nantou Check point - Shenzhen Guangdong 518000 +88-5D-FB (hex) zte corporation +885DFB (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -EC-10-7B (hex) Samsung Electronics Co.,Ltd -EC107B (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +68-EC-C5 (hex) Intel Corporate +68ECC5 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -B0-4B-BF (hex) PT HAN SUNG ELECTORONICS INDONESIA -B04BBF (base 16) PT HAN SUNG ELECTORONICS INDONESIA - JL.PALEM 1 BLOK DS-6 - KAWASAN INDUSTRI BATIK LIPPO CIKARANG, DESA CIBATU, KECAMATAN CIKARANG SELATAN BEKASI JAWA BARAT 17550 - ID +3C-11-B2 (hex) Fraunhofer FIT +3C11B2 (base 16) Fraunhofer FIT + Schloss Birlinghoven + Sankt Augustin 53754 + DE -CC-2D-83 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -CC2D83 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +28-8C-B8 (hex) zte corporation +288CB8 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -D4-6E-0E (hex) TP-LINK TECHNOLOGIES CO.,LTD. -D46E0E (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan +78-BC-1A (hex) Cisco Systems, Inc +78BC1A (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +00-5C-86 (hex) SHENZHEN FAST TECHNOLOGIES CO.,LTD +005C86 (base 16) SHENZHEN FAST TECHNOLOGIES CO.,LTD + Room 202,Building No.5,Section 30,No.2 of Kefa Road,Nanshan District,Shenzhen,P.R.China Shenzhen Guangdong 518057 CN -88-36-6C (hex) EFM Networks -88366C (base 16) EFM Networks - 6F, Benposra II 1197-1 Bojeong Giheung Gu - Yong In Kyunggi do 446913 - KR - -48-DA-96 (hex) Eddy Smart Home Solutions Inc. -48DA96 (base 16) Eddy Smart Home Solutions Inc. - 1600-25 Sheppard Avenue West - Toronto Ontario M2N 6S6 - CA - -F0-74-E4 (hex) Thundercomm Technology Co., Ltd -F074E4 (base 16) Thundercomm Technology Co., Ltd - Building NO.4, 99# Xiantao Data Valley Zhonglu, Yubei District, Chongqing, China - chongqing 404100 +5C-54-6D (hex) HUAWEI TECHNOLOGIES CO.,LTD +5C546D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -A0-72-2C (hex) HUMAX Co., Ltd. -A0722C (base 16) HUMAX Co., Ltd. - HUMAX Village, 216, Hwangsaeul-ro, Bu - Seongnam-si Gyeonggi-do 463-875 +48-BC-A6 (hex) ​ASUNG TECHNO CO.,Ltd +48BCA6 (base 16) ​ASUNG TECHNO CO.,Ltd + 462, Dogok-ro, Songpa-gu, Seoul, Republic of Korea + SEOUL Repubilc of KOREA 05574 KR -C8-3D-D4 (hex) CyberTAN Technology Inc. -C83DD4 (base 16) CyberTAN Technology Inc. - 99 Park Ave III, Hsinchu Science Park - Hsinchu 308 - TW +3C-10-E6 (hex) PHAZR Inc. +3C10E6 (base 16) PHAZR Inc. + 8, Presitige Circle, Suite 104 + Allen TX 75002 + US -E0-B9-4D (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD -E0B94D (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD - NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district - shenzhen guangdong 518000 - CN +30-05-3F (hex) JTI Co.,Ltd. +30053F (base 16) JTI Co.,Ltd. + 102-1508, 36, Bucheon-ro 198beon-gil, + Buchcheon-si Gyeonggi-do 14557 + KR -38-D5-47 (hex) ASUSTek COMPUTER INC. -38D547 (base 16) ASUSTek COMPUTER INC. - 15,Li-Te Rd., Peitou, Taipei 112, Taiwan - Taipei Taiwan 112 - TW +B0-09-DA (hex) Ring Solutions +B009DA (base 16) Ring Solutions + 1200 Atwater Drive, Suite 225 + Malvern PA 19355 + US -B4-D1-35 (hex) Cloudistics -B4D135 (base 16) Cloudistics - 116000 Sunrise Valley Dr Suite 210 - Reston VA 20190 +00-05-4F (hex) Garmin International +00054F (base 16) Garmin International + 1200 E. 151st St + Olathe KS 66062 US -F0-2F-A7 (hex) HUAWEI TECHNOLOGIES CO.,LTD -F02FA7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +90-45-06 (hex) Tokyo Boeki Medisys Inc. +904506 (base 16) Tokyo Boeki Medisys Inc. + 1-14-21, Higashitoyoda + Hino Tokyo 191-0052 + JP -18-DE-D7 (hex) HUAWEI TECHNOLOGIES CO.,LTD -18DED7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +00-21-A1 (hex) Cisco Systems, Inc +0021A1 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +E0-48-D3 (hex) MOBIWIRE MOBILES (NINGBO) CO.,LTD +E048D3 (base 16) MOBIWIRE MOBILES (NINGBO) CO.,LTD + No.999,Dacheng East Road, + Fenghua Zhejiang 315500 CN -1C-23-2C (hex) Samsung Electronics Co.,Ltd -1C232C (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +B8-DB-1C (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. +B8DB1C (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. + Phase 3, Bayan Lepas FIZ + Bayan Lepas Penang 11900 + MY -78-88-8A (hex) CDR Sp. z o.o. Sp. k. -78888A (base 16) CDR Sp. z o.o. Sp. k. - Palki 15 - Zory 44-240 - PL +58-E2-8F (hex) Apple, Inc. +58E28F (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -FC-D8-48 (hex) Apple, Inc. -FCD848 (base 16) Apple, Inc. +78-7B-8A (hex) Apple, Inc. +787B8A (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -E0-0D-B9 (hex) Cree, Inc. -E00DB9 (base 16) Cree, Inc. - 4600 Silicon Drive - Durham NC 27703 +00-0C-03 (hex) HDMI Licensing, LLC +000C03 (base 16) HDMI Licensing, LLC + 1060 East Arques Ave. + Sunnyvale CA 94085 US -FC-83-C6 (hex) N-Radio Technologies Co., Ltd. -FC83C6 (base 16) N-Radio Technologies Co., Ltd. - 2#, 7F, Satellite Buiding, Keyuan Road, Nanshan - ShenZhen GuangDong 518000 +F4-93-9F (hex) Hon Hai Precision Industry Co., Ltd. +F4939F (base 16) Hon Hai Precision Industry Co., Ltd. + GuangDongShenZhen + ShenZhen GuangDong 518109 CN -DC-0D-30 (hex) Shenzhen Feasycom Technology Co., Ltd. -DC0D30 (base 16) Shenzhen Feasycom Technology Co., Ltd. - #2004, Huichao Science & Technology Building, Jinhai Road, Xixiang - Shenzhen Guangdong 18000 +00-07-26 (hex) SHENZHEN GONGJIN ELECTRONICS CO.,LT +000726 (base 16) SHENZHEN GONGJIN ELECTRONICS CO.,LT + A211-A213 & B201-B210, 2F, Baiying Building, 1019#, Nanhai RD, Shekou Party, Nanshan District, + Shenzhen Guangdong 518067 CN -F0-AC-D7 (hex) IEEE Registration Authority -F0ACD7 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +FC-8B-97 (hex) SHENZHEN GONGJIN ELECTRONICS CO.,LT +FC8B97 (base 16) SHENZHEN GONGJIN ELECTRONICS CO.,LT + B116,B118,A211-A213,B201-B213,A311-A313,B411-413,BF08-09 Nanshan Medical Instrument Industry Park, + Shenzhen Guangdong 518067 + CN -00-21-D2 (hex) Samsung Electronics Co.,Ltd -0021D2 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +FC-7F-56 (hex) CoSyst Control Systems GmbH +FC7F56 (base 16) CoSyst Control Systems GmbH + Martin-Albert-Str. 1 + Nürnberg Bayern 90491 + DE -00-21-D1 (hex) Samsung Electronics Co.,Ltd -0021D1 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 +2C-40-53 (hex) Samsung Electronics Co.,Ltd +2C4053 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 KR -00-1F-CC (hex) Samsung Electronics Co.,Ltd -001FCC (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +0C-8F-FF (hex) HUAWEI TECHNOLOGIES CO.,LTD +0C8FFF (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -A4-29-83 (hex) Boeing Defence Australia -A42983 (base 16) Boeing Defence Australia - GPO Box 767 - Brisbane Queensland 4001 - AU +54-B1-21 (hex) HUAWEI TECHNOLOGIES CO.,LTD +54B121 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -EC-88-92 (hex) Motorola Mobility LLC, a Lenovo Company -EC8892 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 Merchandise Mart Plaza, Suite 1800 - Chicago IL 60654 - US +A8-0C-63 (hex) HUAWEI TECHNOLOGIES CO.,LTD +A80C63 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -94-95-A0 (hex) Google, Inc. -9495A0 (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US +5C-C3-07 (hex) HUAWEI TECHNOLOGIES CO.,LTD +5CC307 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -00-A6-CA (hex) Cisco Systems, Inc -00A6CA (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +78-8C-4D (hex) Indyme Solutions, LLC +788C4D (base 16) Indyme Solutions, LLC + 8295 Aero Place Ste 260 + San Diego CA 92123 US -D8-45-2B (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. -D8452B (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. - Phase 3, Bayan Lepas FIZ - Bayan Lepas Penang 11900 - MY +DC-EB-53 (hex) Wuhan QianXiao Elecronic Technology CO.,LTD +DCEB53 (base 16) Wuhan QianXiao Elecronic Technology CO.,LTD + Guanggu Xinzhongxin, No.303 of Guanggu road, East lake development zone + wuhan hubei 430000 + CN -E4-18-6B (hex) Zyxel Communications Corporation -E4186B (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW +EC-8A-C7 (hex) Fiberhome Telecommunication Technologies Co.,LTD +EC8AC7 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN -D4-1D-71 (hex) Palo Alto Networks -D41D71 (base 16) Palo Alto Networks - 3000 Tannery Way - Santa Clara CA 95054 - US +88-36-5F (hex) LG Electronics (Mobile Communications) +88365F (base 16) LG Electronics (Mobile Communications) + 60-39, Gasan-dong, Geumcheon-gu + Seoul 153-801 + KR -00-87-31 (hex) Cisco Systems, Inc -008731 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +F0-EF-D2 (hex) TF PAYMENT SERVICE CO., LTD +F0EFD2 (base 16) TF PAYMENT SERVICE CO., LTD + 5F Shibadaimon Center Building, 1-10-11 Shibadaimon + Minato-ku Tokyo 105-0012 + JP -88-DE-A9 (hex) Roku, Inc. -88DEA9 (base 16) Roku, Inc. - 12980 Saratoga Ave - Saratoga CA 95070 - US +78-81-02 (hex) Sercomm Corporation. +788102 (base 16) Sercomm Corporation. + 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen + Miao-Lih Hsuan 115 + TW -00-4A-77 (hex) zte corporation -004A77 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +84-AA-9C (hex) MitraStar Technology Corp. +84AA9C (base 16) MitraStar Technology Corp. + No. 6, Innovation Road II, + Hsinchu 300 + TW + +2C-AB-25 (hex) SHENZHEN GONGJIN ELECTRONICS CO.,LT +2CAB25 (base 16) SHENZHEN GONGJIN ELECTRONICS CO.,LT + B116,B118,A211-A213,B201-B213,A311-A313,B411-413,BF08-09 Nanshan Medical Instrument Industry Park, + Shenzhen Guangdong 518067 CN -60-A1-0A (hex) Samsung Electronics Co.,Ltd -60A10A (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +1C-A5-32 (hex) SHENZHEN GONGJIN ELECTRONICS CO.,LT +1CA532 (base 16) SHENZHEN GONGJIN ELECTRONICS CO.,LT + 1#SongGang RD, Bao'an Dist., Shenzhen, Guangdong,China + Shenzhen Guangdong 518109 + CN -8C-71-F8 (hex) Samsung Electronics Co.,Ltd -8C71F8 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +00-1F-92 (hex) Motorola Solutions Inc. +001F92 (base 16) Motorola Solutions Inc. + 500 W Monroe Street, Ste 4400 + Chicago IL 60661-3781 + US -CC-05-1B (hex) Samsung Electronics Co.,Ltd -CC051B (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +00-25-C4 (hex) Ruckus Wireless +0025C4 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US -8C-77-12 (hex) Samsung Electronics Co.,Ltd -8C7712 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +C0-C5-20 (hex) Ruckus Wireless +C0C520 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US -94-63-D1 (hex) Samsung Electronics Co.,Ltd -9463D1 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +70-DE-F9 (hex) FAI WAH INTERNATIONAL (HONG KONG) LIMITED +70DEF9 (base 16) FAI WAH INTERNATIONAL (HONG KONG) LIMITED + Room 19, 8F.,Corporation Park, 11 On Lai Street,Shatin,Shek Mun,N.T.Hong Kong + Hong Kong 999077 + HK -1C-9D-3E (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. -1C9D3E (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. - Phase 3, Bayan Lepas FIZ - Bayan Lepas Penang 11900 - MY +4C-B0-08 (hex) Shenzhen Gwelltimes Technology Co.,Ltd +4CB008 (base 16) Shenzhen Gwelltimes Technology Co.,Ltd + A4 building 15floor Zhongying Jewelry Industrial Park Bulan Road No.31 Nanwan Street Longgang District Shenzhen City China + Shenzhen 518112 + CN -74-87-A9 (hex) OCT Technology Co., Ltd. -7487A9 (base 16) OCT Technology Co., Ltd. - 8F. -2, No. 94, Baojhong Rd. Sindian Dist. - New Taipei City 231 +E8-6F-F2 (hex) Actiontec Electronics, Inc +E86FF2 (base 16) Actiontec Electronics, Inc + 3301 Olcott St. + Santa Clara CA 95054 + US + +00-50-18 (hex) AMIT, Inc. +005018 (base 16) AMIT, Inc. + No.32, Huangong Rd., + Yongkang City, Tainan County 71041 TW -50-3F-98 (hex) CMITECH -503F98 (base 16) CMITECH - 904-ho, 25, Simin-daero 248beon-gil, Dongan-gu - Anyang-si Gyeonggi-do 14067 - KR +FC-2F-6B (hex) Everspin Technologies, Inc. +FC2F6B (base 16) Everspin Technologies, Inc. + 1347 N. Alma School Rd., Suite 220 + Chandler AZ 85224 + US -5C-49-7D (hex) Samsung Electronics Co.,Ltd -5C497D (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR +10-1B-54 (hex) HUAWEI TECHNOLOGIES CO.,LTD +101B54 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + D1-4,Huawei Industrial Base,Bantian,Longgang + ShenZhen GuangDong 518129 + CN -98-23-4E (hex) Micromedia AG -98234E (base 16) Micromedia AG - Gartenweg 46 - Buonas Zug 6343 +7C-BA-CC (hex) IEEE Registration Authority +7CBACC (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +54-02-37 (hex) Teltronic AG +540237 (base 16) Teltronic AG + Gewerbestrasse 9 + Biberist 4562 CH -78-20-79 (hex) ID Tech -782079 (base 16) ID Tech - 10721 Walker St - Cypress CA 90630 +E0-10-7F (hex) Ruckus Wireless +E0107F (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 US -78-D6-F0 (hex) SAMSUNG ELECTRO MECHANICS CO., LTD. -78D6F0 (base 16) SAMSUNG ELECTRO MECHANICS CO., LTD. - Metan Dong 314, Youngtong Gu - Suwon Kyung-gi Do. 443-743 - KR +C4-01-7C (hex) Ruckus Wireless +C4017C (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US -78-25-AD (hex) Samsung Electronics Co.,Ltd -7825AD (base 16) Samsung Electronics Co.,Ltd - 416, MAETAN-3DONG, PALDAL-GU - SUWON CITY KYUNGKI-DO 442-742 - KR +40-42-29 (hex) Layer3TV, Inc +404229 (base 16) Layer3TV, Inc + 1660 Wynkoop St - Suite 800 + Denver CO 80202 + US -EC-E0-9B (hex) Samsung Electronics Co.,Ltd -ECE09B (base 16) Samsung Electronics Co.,Ltd - 416, Maetan-3dong, Yeongtong-gu, - Suwon-City Gyeonggi-do 443-742 +F8-1D-90 (hex) Solidwintech +F81D90 (base 16) Solidwintech + 6th Floor, SOLiD Space 220 Pangyoyeok-ro, Bundang-gu + Seongnam-si Gyeongi-do 13493 KR -00-16-32 (hex) Samsung Electronics Co.,Ltd -001632 (base 16) Samsung Electronics Co.,Ltd - 416, METAN-3DONG, - SUWON KYUNGKI-DO 442-742 - KR +B0-EA-BC (hex) ASKEY COMPUTER CORP +B0EABC (base 16) ASKEY COMPUTER CORP + 10F,No.119,JIANKANG RD,ZHONGHE DIST + NEW TAIPEI TAIWAN 23585 + TW -0C-60-76 (hex) Hon Hai Precision Ind. Co.,Ltd. -0C6076 (base 16) Hon Hai Precision Ind. Co.,Ltd. - Building D21,No.1, East Zone 1st Road - Chongqing Chongqing 401332 - CN +94-C6-91 (hex) EliteGroup Computer Systems Co., LTD +94C691 (base 16) EliteGroup Computer Systems Co., LTD + No.239, Sec. 2, TiDing Blvd. Nei-Hu Dist. + Taipei Taiwan 11439 + TW -0C-EE-E6 (hex) Hon Hai Precision Ind. Co.,Ltd. -0CEEE6 (base 16) Hon Hai Precision Ind. Co.,Ltd. - Building D21,No.1, East Zone 1st Road - Chongqing Chongqing 401332 +3C-F5-91 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +3CF591 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -E4-D5-3D (hex) Hon Hai Precision Ind. Co.,Ltd. -E4D53D (base 16) Hon Hai Precision Ind. Co.,Ltd. - Building D21,No.1, East Zone 1st Road - Chongqing Chongqing 401332 +60-21-01 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +602101 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -C0-14-3D (hex) Hon Hai Precision Ind. Co.,Ltd. -C0143D (base 16) Hon Hai Precision Ind. Co.,Ltd. - Building D21,No.1, East Zone 1st Road - Chongqing Chongqing 401332 +7C-EB-7F (hex) Dmet Products Corp. +7CEB7F (base 16) Dmet Products Corp. + 118, Fujisoft Akihabara Bldg 12F, Kanda Neribeicho 3 + Chiyodaku Tokyo 1010022 + JP + +8C-85-80 (hex) Smart Innovation LLC +8C8580 (base 16) Smart Innovation LLC + 7F,Tower B,Jianxing + ShenZhen GuangZhou 518055 CN -C0-18-85 (hex) Hon Hai Precision Ind. Co.,Ltd. -C01885 (base 16) Hon Hai Precision Ind. Co.,Ltd. - Building D21,No.1, East Zone 1st Road - Chongqing Chongqing 401332 +28-7B-09 (hex) zte corporation +287B09 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -58-94-CF (hex) Vertex Standard LMR, Inc. -5894CF (base 16) Vertex Standard LMR, Inc. - 4-8-8 Nakameguro - Meguro-ku Tokyo 153-8644 - JP +A0-6A-44 (hex) Vizio, Inc +A06A44 (base 16) Vizio, Inc + 39 Tesla + Irvine CA 92618 + US -20-F8-5E (hex) Delta Electronics -20F85E (base 16) Delta Electronics - 252 Shangying Road - Taoyuan County Taiwan 33341 - TW +88-B1-11 (hex) Intel Corporate +88B111 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -E4-E0-C5 (hex) Samsung Electronics Co.,Ltd -E4E0C5 (base 16) Samsung Electronics Co.,Ltd - 416, Maetan-3dong, Yeongtong-gu - Suwon Gyeonggi-do 443742 - KR +DC-BE-7A (hex) Zhejiang Nurotron Biotechnology Co. +DCBE7A (base 16) Zhejiang Nurotron Biotechnology Co. + Building4, No.99 Xiaomao Rd + Hangzhou zhejiang 310011 + CN -20-D5-BF (hex) Samsung Electronics Co.,Ltd -20D5BF (base 16) Samsung Electronics Co.,Ltd - 416, Maetan 3dong, Yeongtong-Gu - Suwon Gyeonggi-Do 443742 +34-38-B7 (hex) HUMAX Co., Ltd. +3438B7 (base 16) HUMAX Co., Ltd. + HUMAX Village, 216, Hwangsaeul-ro, Bu + Seongnam-si Gyeonggi-do 463-875 KR -00-23-E4 (hex) IPnect co. ltd. -0023E4 (base 16) IPnect co. ltd. - 808 albatross B/D 237-18 - Seoul 153-801 - KR +78-B2-8D (hex) Beijing Tengling Technology CO.Ltd +78B28D (base 16) Beijing Tengling Technology CO.Ltd + beijing haidian shangdi san jie + beijing 100086 + CN -70-D4-F2 (hex) RIM -70D4F2 (base 16) RIM - Phillip Street - Waterloo Ontario N2L 3W8 - CA +78-45-01 (hex) Biamp Systems +784501 (base 16) Biamp Systems + 9300 SW Gemini Dr + Beaverton OR 97008 + US -00-16-DB (hex) Samsung Electronics Co.,Ltd -0016DB (base 16) Samsung Electronics Co.,Ltd - #94-1 - Gumi-City Gyeong-Buk 730-350 - KR +54-D7-51 (hex) Proximus +54D751 (base 16) Proximus + Bld du Roi Albert II 27 + Brussels 1030 + BE -00-1E-E2 (hex) Samsung Electronics Co.,Ltd -001EE2 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +CC-06-77 (hex) Fiberhome Telecommunication Technologies Co.,LTD +CC0677 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN -C0-BD-D1 (hex) SAMSUNG ELECTRO-MECHANICS(THAILAND) -C0BDD1 (base 16) SAMSUNG ELECTRO-MECHANICS(THAILAND) - 93Moo5T. Bangsamak - Bangpakong Chachoengsao 24180 - TH +14-78-0B (hex) Varex Imaging Deutschland AG +14780B (base 16) Varex Imaging Deutschland AG + Zweigniederlassung/Branch Walluf + In der Rehbach 22 Walluf 65396 + DE -B4-79-A7 (hex) SAMSUNG ELECTRO-MECHANICS(THAILAND) -B479A7 (base 16) SAMSUNG ELECTRO-MECHANICS(THAILAND) - 93Moo5T. Bangsamak - Bangpakong Chachoengsao 24180 - TH +00-80-C2 (hex) IEEE 802.1 Chair +0080C2 (base 16) IEEE 802.1 Chair + c/o RAC Administrator , IEEE + Piscataway NJ 08554 + US -7C-11-CB (hex) HUAWEI TECHNOLOGIES CO.,LTD -7C11CB (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +EC-F3-42 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +ECF342 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -1C-25-E1 (hex) China Mobile IOT Company Limited -1C25E1 (base 16) China Mobile IOT Company Limited - NO.8 Yu Ma Road, NanAn Area - Chongqing Chongqing 401336 - CN +D4-25-8B (hex) Intel Corporate +D4258B (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -C0-F6-36 (hex) Hangzhou Kuaiyue Technologies, Ltd. -C0F636 (base 16) Hangzhou Kuaiyue Technologies, Ltd. - Dongguan Hitech Park, Building 1-805, 288 Qiuyi Rd, Bingjiang District - Hangzhou Zhejiang 310053 +04-1B-6D (hex) LG Electronics (Mobile Communications) +041B6D (base 16) LG Electronics (Mobile Communications) + 60-39, Gasan-dong, Geumcheon-gu + Seoul 153-801 + KR + +F4-41-56 (hex) Arrikto Inc. +F44156 (base 16) Arrikto Inc. + 3505 El Camino Real + Palo Alto CA 94306 + US + +98-6C-5C (hex) Jiangxi Gosun Guard Security Co.,Ltd +986C5C (base 16) Jiangxi Gosun Guard Security Co.,Ltd + 2rd floor,8 building Middle,Zhongxing Software Park,Changdong Avenue,Nanchang High Tech Zone,Nanchang city,Jiangxi province + Nan Chang City Jiang Xi Province 330000 CN -BC-20-A4 (hex) Samsung Electronics Co.,Ltd -BC20A4 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +50-FF-20 (hex) Keenetic Limited +50FF20 (base 16) Keenetic Limited + 1202, 12/F., AT TOWER, 180 ELECTRIC ROAD, NORTH POINT + HONG KONG 852 + HK -08-D4-2B (hex) Samsung Electronics Co.,Ltd -08D42B (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +68-8D-B6 (hex) AETEK INC. +688DB6 (base 16) AETEK INC. + 3F, No.192, Lien-Cheng Rd., Chung-Ho, + New Taipei City 23553 + TW -78-9E-D0 (hex) Samsung Electronics Co.,Ltd -789ED0 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +00-F8-2C (hex) Cisco Systems, Inc +00F82C (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -B0-C4-E7 (hex) Samsung Electronics Co.,Ltd -B0C4E7 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +00-C1-B1 (hex) Cisco Systems, Inc +00C1B1 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -C8-14-79 (hex) Samsung Electronics Co.,Ltd -C81479 (base 16) Samsung Electronics Co.,Ltd - #94-1,Imsoo-Dong - Gumi Gyeongbuk 730-350 +F4-FC-B1 (hex) JJ Corp +F4FCB1 (base 16) JJ Corp + 88 Soha-ro + Gwangmyeong-si 14316 KR -00-24-90 (hex) Samsung Electronics Co.,Ltd -002490 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +8C-39-5C (hex) Bit4id Srl +8C395C (base 16) Bit4id Srl + Via Diocleziano, 107 + Naples 80125 + IT -00-23-D7 (hex) Samsung Electronics Co.,Ltd -0023D7 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +30-9C-23 (hex) Micro-Star INTL CO., LTD. +309C23 (base 16) Micro-Star INTL CO., LTD. + No.69, Lide St., + New Taipei City Taiwan 235 + TW -54-9B-12 (hex) Samsung Electronics Co.,Ltd -549B12 (base 16) Samsung Electronics Co.,Ltd +14-5E-45 (hex) Bamboo Systems Group +145E45 (base 16) Bamboo Systems Group + Sheraton House, Castle Park + Cambridge CAMBRIDGESHIRE CB3 0AX + GB + +AC-AF-B9 (hex) Samsung Electronics Co.,Ltd +ACAFB9 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -FC-A1-3E (hex) Samsung Electronics Co.,Ltd -FCA13E (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -A0-07-98 (hex) Samsung Electronics Co.,Ltd -A00798 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -00-1A-22 (hex) eQ-3 Entwicklung GmbH -001A22 (base 16) eQ-3 Entwicklung GmbH - Maiburger Str. 36 - Leer Niedersachsen D-26789 - DE - -1C-AF-05 (hex) Samsung Electronics Co.,Ltd -1CAF05 (base 16) Samsung Electronics Co.,Ltd - #94-1,Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -24-C6-96 (hex) Samsung Electronics Co.,Ltd -24C696 (base 16) Samsung Electronics Co.,Ltd - #94-1,Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -94-D7-71 (hex) Samsung Electronics Co.,Ltd -94D771 (base 16) Samsung Electronics Co.,Ltd - #94-1,Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +18-12-12 (hex) Cepton Technologies +181212 (base 16) Cepton Technologies + 103 Bonaventura Dr + San Jose CA 95134 + US -E8-4E-84 (hex) Samsung Electronics Co.,Ltd -E84E84 (base 16) Samsung Electronics Co.,Ltd - #94-1,Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +70-D9-23 (hex) vivo Mobile Communication Co., Ltd. +70D923 (base 16) vivo Mobile Communication Co., Ltd. + #283,BBK Road + Wusha,Chang'an DongGuan City,Guangdong 523860 + CN -B0-DF-3A (hex) Samsung Electronics Co.,Ltd -B0DF3A (base 16) Samsung Electronics Co.,Ltd - #94-1,Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +B8-3A-08 (hex) Tenda Technology Co.,Ltd.Dongguan branch +B83A08 (base 16) Tenda Technology Co.,Ltd.Dongguan branch + Room 79,Yuanyi Road,Dalang Town,Dongguan Guangdong 523770 + Dongguan Guangdong 523770 + CN -80-57-19 (hex) Samsung Electronics Co.,Ltd -805719 (base 16) Samsung Electronics Co.,Ltd - #94-1,Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +50-D3-7F (hex) Yu Fly Mikly Way Science and Technology Co., Ltd. +50D37F (base 16) Yu Fly Mikly Way Science and Technology Co., Ltd. + 6F, No. 1168 Huyi Road, Nanxiang Town Jiading District Shanghai 201800 CHINA + shanghai 201800 + CN -34-BE-00 (hex) Samsung Electronics Co.,Ltd -34BE00 (base 16) Samsung Electronics Co.,Ltd - #94-1,Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +B0-3D-96 (hex) Vision Valley FZ LLC +B03D96 (base 16) Vision Valley FZ LLC + Dubai Internet City + Dubai Dubai 500294 + AE -78-52-1A (hex) Samsung Electronics Co.,Ltd -78521A (base 16) Samsung Electronics Co.,Ltd - #94-1,Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +F8-94-C2 (hex) Intel Corporate +F894C2 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -00-1F-CD (hex) Samsung Electronics Co.,Ltd -001FCD (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +70-D3-79 (hex) Cisco Systems, Inc +70D379 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -38-EC-E4 (hex) Samsung Electronics Co.,Ltd -38ECE4 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +10-05-01 (hex) PEGATRON CORPORATION +100501 (base 16) PEGATRON CORPORATION + 5F No. 76, Ligong St., Beitou District + Taipei City Taiwan 112 + TW -94-51-03 (hex) Samsung Electronics Co.,Ltd -945103 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +D8-C8-E9 (hex) Phicomm (Shanghai) Co., Ltd. +D8C8E9 (base 16) Phicomm (Shanghai) Co., Ltd. + 3666 SiXian Rd.,Songjiang District + Shanghai Shanghai 201616 + CN -5C-E8-EB (hex) Samsung Electronics Co.,Ltd -5CE8EB (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +7C-B9-60 (hex) Shanghai X-Cheng telecom LTD +7CB960 (base 16) Shanghai X-Cheng telecom LTD + ROOM 401, Building 5, No.3000 LONG DONG Avenue, Pudong New District, Shanghai + Shanghai Shanghai 201203 + CN -24-0D-C2 (hex) TCT mobile ltd -240DC2 (base 16) TCT mobile ltd - No.86 hechang 7th road, zhongkai, Hi-Tech District - Hui Zhou Guang Dong 516006 +28-B4-48 (hex) HUAWEI TECHNOLOGIES CO.,LTD +28B448 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -04-FE-A1 (hex) Fihonest communication co.,Ltd -04FEA1 (base 16) Fihonest communication co.,Ltd - Room902,Park road,Zhixing business-building - Dongguan Guangdong 523560 +70-DB-98 (hex) Cisco Systems, Inc +70DB98 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +30-D3-86 (hex) zte corporation +30D386 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -EC-8C-A2 (hex) Ruckus Wireless -EC8CA2 (base 16) Ruckus Wireless +24-79-2A (hex) Ruckus Wireless +24792A (base 16) Ruckus Wireless 350 West Java Drive Sunnyvale CA 94089 US -EC-8E-AE (hex) Nagravision SA -EC8EAE (base 16) Nagravision SA - Route de Geneve 22-24, PO 7980 - Cheseaux Vaud 1033 - CH +A4-9B-F5 (hex) Hybridserver Tec GmbH +A49BF5 (base 16) Hybridserver Tec GmbH + Gutenbergring 26a + Norderstedt Schleswig-Holstein 22848 + DE -E0-E7-BB (hex) Nureva, Inc. -E0E7BB (base 16) Nureva, Inc. - 1000, 1221 8th Street SW - Calgary AB T2R 0L4 - CA +F4-70-AB (hex) vivo Mobile Communication Co., Ltd. +F470AB (base 16) vivo Mobile Communication Co., Ltd. + #283,BBK Road + Wusha,Chang'An DongGuan City,Guangdong, 523860 + CN -54-6C-0E (hex) Texas Instruments -546C0E (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +2C-5A-0F (hex) Cisco Systems, Inc +2C5A0F (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -A0-43-DB (hex) Sitael S.p.A. -A043DB (base 16) Sitael S.p.A. - Via San Sabino, 21 - Mola di Bari BA 70042 - IT - -20-BB-C6 (hex) Jabil Circuit Hungary Ltd. -20BBC6 (base 16) Jabil Circuit Hungary Ltd. - Huszár Andor 1. - Tiszaújváros H-3580 - HU +2C-31-24 (hex) Cisco Systems, Inc +2C3124 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -00-B0-B3 (hex) XSTREAMIS PLC -00B0B3 (base 16) XSTREAMIS PLC - Magdalen Centre - Oxford 0X4 4GA 0000 - GB +9C-C8-AE (hex) Becton, Dickinson and Company +9CC8AE (base 16) Becton, Dickinson and Company + 1 Becton Drive + Franklin Lakes MA 07417-1880 + US -00-23-63 (hex) Zhuhai Raysharp Technology Co.,Ltd -002363 (base 16) Zhuhai Raysharp Technology Co.,Ltd - No.119 of Huawei Road, Qianshan Science & Technology Park, - Zhuhai Guangdong 519070 - CN +B0-35-9F (hex) Intel Corporate +B0359F (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -00-90-58 (hex) Ultra Electronics Command & Control Systems -009058 (base 16) Ultra Electronics Command & Control Systems - Knaves Beech Business Centre - Hemel Hemstead Herts England HP2 7BW - GB +C0-D9-62 (hex) ASKEY COMPUTER CORP +C0D962 (base 16) ASKEY COMPUTER CORP + 10F,NO.119,JIANKANG RD.,ZHONGHE DIST XINBEI CITY + taipei TAIPEI 23585 + TW -00-1C-FD (hex) Universal Electronics, Inc. -001CFD (base 16) Universal Electronics, Inc. - 6101 Gateway Drive - Cypress 90630 +F8-0B-CB (hex) Cisco Systems, Inc +F80BCB (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -00-10-87 (hex) XSTREAMIS PLC -001087 (base 16) XSTREAMIS PLC - OXFORD SCIENCE PARK - OXFORD 0X4 4GA 00000 +B4-7C-9C (hex) Amazon Technologies Inc. +B47C9C (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -AC-48-2D (hex) Ralinwi Nanjing Electronic Technology Co., Ltd. -AC482D (base 16) Ralinwi Nanjing Electronic Technology Co., Ltd. - 3rd Floor, Building B,R&D Block 3, Xuzhuang Software Park, No. 699-27, Xuanwu Avenue - Nanjing Jiangsu 210046 - CN - -B8-00-18 (hex) Htel -B80018 (base 16) Htel - Dunchon-dearo, Jungwon-gu - Seongnam-si Gyeonggi-do 13229 +50-3D-A1 (hex) Samsung Electronics Co.,Ltd +503DA1 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 KR -74-72-B0 (hex) Guangzhou Shiyuan Electronics Co., Ltd. -7472B0 (base 16) Guangzhou Shiyuan Electronics Co., Ltd. - No.6, 4th Yunpu Road, Yunpu Industry District - Guangzhou Guangdong 510530 - CN - -DC-1A-01 (hex) Ecoliv Technology ( Shenzhen ) Ltd. -DC1A01 (base 16) Ecoliv Technology ( Shenzhen ) Ltd. - B-734, Tianhui building, Longhua Street, Pine Road, Longhua District - Shenzhen Guangdong 518109 - CN - -08-00-87 (hex) Xyplex, Inc. -080087 (base 16) Xyplex, Inc. - 295 FOSTER STREET - LITTLETON MA 01460 +50-32-37 (hex) Apple, Inc. +503237 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-54-9F (hex) Avaya Inc -00549F (base 16) Avaya Inc - 360 Mt Kemble Ave - Morristown NJ 07960 +B0-48-1A (hex) Apple, Inc. +B0481A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -04-9F-CA (hex) HUAWEI TECHNOLOGIES CO.,LTD -049FCA (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -50-01-6B (hex) HUAWEI TECHNOLOGIES CO.,LTD -50016B (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +B4-9C-DF (hex) Apple, Inc. +B49CDF (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -00-00-1B (hex) Novell, Inc. -00001B (base 16) Novell, Inc. - 122 EAST 1700 SOUTH - PROVO UT 84606 +48-BF-6B (hex) Apple, Inc. +48BF6B (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-04-DC (hex) Nortel Networks -0004DC (base 16) Nortel Networks - 8200 Dixie Rd - Brampton Ontario 0000 - CA +2C-FA-A2 (hex) Alcatel-Lucent Enterprise +2CFAA2 (base 16) Alcatel-Lucent Enterprise + 26801 West Agoura Rd + Calabasas CA 91301 + US -00-0C-F7 (hex) Nortel Networks -000CF7 (base 16) Nortel Networks - 8200 Dixie Rd - Brampton Ontario 0000 - CA +E8-E7-32 (hex) Alcatel-Lucent Enterprise +E8E732 (base 16) Alcatel-Lucent Enterprise + 26801 West Agoura Road + Calabasas CA 91301 + US -00-0F-CD (hex) Nortel Networks -000FCD (base 16) Nortel Networks - 8200 Dixie Rd - Brampton Ontario 0000 - CA +7C-26-64 (hex) Sagemcom Broadband SAS +7C2664 (base 16) Sagemcom Broadband SAS + 250, route de l'Empereur + Rueil Malmaison Cedex hauts de seine 92848 + FR -38-25-6B (hex) Microsoft Mobile Oy -38256B (base 16) Microsoft Mobile Oy - Keilalahdentie 4 - Espoo 02150 - FI +AC-6B-0F (hex) CADENCE DESIGN SYSTEMS INC +AC6B0F (base 16) CADENCE DESIGN SYSTEMS INC + 2670 SEELY AVE + SAN JOSE CA 95134 + US -20-3A-EF (hex) Sivantos GmbH -203AEF (base 16) Sivantos GmbH - Henri-Dunant-Strasse 100 - Erlangen Bavaria 91058 - DE +7C-38-66 (hex) Texas Instruments +7C3866 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -00-59-79 (hex) Networked Energy Services -005979 (base 16) Networked Energy Services - 5215 Hellyer Avenue - San Jose CA 95138 +0C-61-CF (hex) Texas Instruments +0C61CF (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -D8-47-10 (hex) Sichuan Changhong Electric Ltd. -D84710 (base 16) Sichuan Changhong Electric Ltd. - 35 East Mianxing Road,High-Tech Park, - MianYang SiChuan 621000 - CN +9C-1D-58 (hex) Texas Instruments +9C1D58 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -00-19-72 (hex) Plexus (Xiamen) Co.,ltd. -001972 (base 16) Plexus (Xiamen) Co.,ltd. - No.6 Xiangxing 2 Road - Xiamen Fujian 361006 - CN +58-82-1D (hex) H. Schomäcker GmbH +58821D (base 16) H. Schomäcker GmbH + Heidestr. 183 + Köln 51147 + DE -4C-B2-1C (hex) Maxphotonics Co.,Ltd -4CB21C (base 16) Maxphotonics Co.,Ltd - Maxphotonics Industrial Park,Third Furong Road,Furong Industrial Area,Shajing,BaoAn - ShenZhen GuangDong 518125 - CN +D8-A1-05 (hex) Syslane, Co., Ltd. +D8A105 (base 16) Syslane, Co., Ltd. + #1201, Megacenter, SKntechno-park,, Sangdaeweon-dong, Joongweon-gu + Seongnam Outside the US, Mexico, or Canada 462-721 + KR -20-5E-F7 (hex) Samsung Electronics Co.,Ltd -205EF7 (base 16) Samsung Electronics Co.,Ltd +3C-05-18 (hex) Samsung Electronics Co.,Ltd +3C0518 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -14-1F-78 (hex) Samsung Electronics Co.,Ltd -141F78 (base 16) Samsung Electronics Co.,Ltd +90-06-28 (hex) Samsung Electronics Co.,Ltd +900628 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -00-23-47 (hex) ProCurve Networking by HP -002347 (base 16) ProCurve Networking by HP - 60 Alexandra Terrace - 0000 118502 - SG - -00-24-A8 (hex) ProCurve Networking by HP -0024A8 (base 16) ProCurve Networking by HP - 60 Alexandra Terrace - 0000 118502 - SG - -C0-91-34 (hex) ProCurve Networking by HP -C09134 (base 16) ProCurve Networking by HP - 60 Alexandra Terrace - 0000 118502 - SG +9C-84-BF (hex) Apple, Inc. +9C84BF (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -00-1C-EF (hex) Primax Electronics Ltd. -001CEF (base 16) Primax Electronics Ltd. - 669, Ruey Kuang Road, Neihu - Taipei 114 - TW +B4-A9-FE (hex) GHIA Technology (Shenzhen) LTD +B4A9FE (base 16) GHIA Technology (Shenzhen) LTD + RM 1606, BLDG 3rd, COFCO Clouds Mansion + Shenzhen 518101 + CN -00-02-76 (hex) Primax Electronics Ltd. -000276 (base 16) Primax Electronics Ltd. - No. 669, Ruey Kuang Road, Neihu - Taipei Taiwan, R.O.C. +F0-97-E5 (hex) TAMIO, INC +F097E5 (base 16) TAMIO, INC + 12F-2, No.33, Sec. 1 , Mingsheng Rd.,Banqiao Dist + New Taipei City 22069 TW -A8-AD-3D (hex) Alcatel-Lucent Shanghai Bell Co., Ltd -A8AD3D (base 16) Alcatel-Lucent Shanghai Bell Co., Ltd - No. 389, Ningqiao Road, Pudong Jinqiao - Shanghai 201206 +4C-1A-3D (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +4C1A3D (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -24-AF-4A (hex) Alcatel-Lucent IPD -24AF4A (base 16) Alcatel-Lucent IPD - 701 E. Middlefield Rd. - Mountain View CA 94043 +1C-A0-D3 (hex) IEEE Registration Authority +1CA0D3 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -7C-20-64 (hex) Alcatel-Lucent IPD -7C2064 (base 16) Alcatel-Lucent IPD - 600 March Drive - Kanata Ontario K2K2E6 - CA +9C-FC-D1 (hex) Aetheris Technology (Shanghai) Co., Ltd. +9CFCD1 (base 16) Aetheris Technology (Shanghai) Co., Ltd. + Room 225, Building No. 8, 433 Yuyuan Road + Shanghai Shanghai 200040 + CN -48-F8-E1 (hex) Nokia -48F8E1 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +BC-A0-42 (hex) SHANGHAI FLYCO ELECTRICAL APPLIANCE CO.,LTD +BCA042 (base 16) SHANGHAI FLYCO ELECTRICAL APPLIANCE CO.,LTD + No.555,Guang Fu Lin east Road,Songjiang District + Shanghai Shanghai 201613 + CN -8C-90-D3 (hex) Nokia -8C90D3 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 +0C-F4-D5 (hex) Ruckus Wireless +0CF4D5 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US + +00-11-1B (hex) Targa Systems Div L-3 Communications +00111B (base 16) Targa Systems Div L-3 Communications + 2081 Merivale Rd + Ottawa Ont K2G 1G9 CA -20-7C-8F (hex) Quanta Microsystems,Inc. -207C8F (base 16) Quanta Microsystems,Inc. - No.5 Lane 91,Dongmei Rd. - Hsinchu 300 - TW +68-54-C1 (hex) ColorTokens, Inc. +6854C1 (base 16) ColorTokens, Inc. + 2101 Tasman Dr. Suite 200A + Santa Clara CA 95054 + US -00-0B-34 (hex) ShangHai Broadband Technologies CO.LTD -000B34 (base 16) ShangHai Broadband Technologies CO.LTD - 17F,No.122,HuangXing Road - ShangHai 200090 - CN +34-6E-9D (hex) Ericsson AB +346E9D (base 16) Ericsson AB + Torshamnsgatan 36 + Stockholm SE-164 80 + SE -30-92-F6 (hex) SHANGHAI SUNMON COMMUNICATION TECHNOGY CO.,LTD -3092F6 (base 16) SHANGHAI SUNMON COMMUNICATION TECHNOGY CO.,LTD - Suite 604-605,Xing Yuan Technology Plaza - 00000 ShangHai 200233 +F4-DC-41 (hex) YOUNGZONE CULTURE (SHANGHAI) CORP +F4DC41 (base 16) YOUNGZONE CULTURE (SHANGHAI) CORP + 7-8th floor, #1 Building, 1006 Jinshajiang Road + Shanghai Shanghai 200062 CN -00-1B-BA (hex) Nortel Networks -001BBA (base 16) Nortel Networks - 8200 Dixie Rd - Brampton Ontario 0000 - CA - -00-19-69 (hex) Nortel Networks -001969 (base 16) Nortel Networks - 8200 Dixie Rd - Brampton Ontario 0000 - CA +00-0F-4F (hex) PCS Systemtechnik GmbH +000F4F (base 16) PCS Systemtechnik GmbH + 66 Hillside Rd + Auckland 1310 + NZ -00-18-B0 (hex) Nortel Networks -0018B0 (base 16) Nortel Networks - 8200 Dixie Rd - Brampton Ontario 0000 - CA +6C-75-0D (hex) WiFiSONG +6C750D (base 16) WiFiSONG + Rm. 605, Building 3, No. 75 Wenyi West Road + Hangzhou Zhejiang 310012 + CN -00-16-CA (hex) Nortel Networks -0016CA (base 16) Nortel Networks - 8200 Dixie Rd - Brampton Ontario 0000 - CA +38-05-AC (hex) Piller Group GmbH +3805AC (base 16) Piller Group GmbH + Abgunst 24 + Osterode 37520 + DE -00-14-78 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -001478 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - 3/F,Bldg.R1-B,Hi-tech Industrial Park, - ShenZhen GuangDong 518057 +BC-3F-8F (hex) HUAWEI TECHNOLOGIES CO.,LTD +BC3F8F (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -00-14-0E (hex) Nortel Networks -00140E (base 16) Nortel Networks - 8200 Dixie Rd - Brampton Ontario 0000 - CA +14-30-04 (hex) HUAWEI TECHNOLOGIES CO.,LTD +143004 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -00-1E-1F (hex) Nortel Networks -001E1F (base 16) Nortel Networks - 8200 Dixie Rd - Brampton Ontario 0000 - CA +38-AA-3C (hex) SAMSUNG ELECTRO MECHANICS CO., LTD. +38AA3C (base 16) SAMSUNG ELECTRO MECHANICS CO., LTD. + 314, Maetan3-Dong, Yeongtong-Gu + Suwon 443-743 + US -C4-4B-D1 (hex) Wallys Communications Teachnologies Co.,Ltd. -C44BD1 (base 16) Wallys Communications Teachnologies Co.,Ltd. - 5-207, DongHong Pioneer Park, #99 YangYu Lane, - SuZhou JiangSu 215000 - CN +50-A4-D0 (hex) IEEE Registration Authority +50A4D0 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -6C-B9-C5 (hex) Delta Networks, Inc. -6CB9C5 (base 16) Delta Networks, Inc. - 256 Yang Guang Street, Neihu - Taipei Taiwan 11491 - TW +80-00-10 (hex) AT&T +800010 (base 16) AT&T + 3300 E Renner Road + Richardson TX 75082 + US -00-28-F8 (hex) Intel Corporate -0028F8 (base 16) Intel Corporate +14-AB-C5 (hex) Intel Corporate +14ABC5 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -58-BC-8F (hex) Cognitive Systems Corp. -58BC8F (base 16) Cognitive Systems Corp. - 560 Westmount Road North - Waterloo Ontario N2L 0A9 - CA - -D4-55-BE (hex) SHENZHEN FAST TECHNOLOGIES CO.,LTD -D455BE (base 16) SHENZHEN FAST TECHNOLOGIES CO.,LTD - Room 202,Building No.5,Section 30,No.2 of Kefa Road,Nanshan District,Shenzhen,P.R.China - Shenzhen Guangdong 518057 +00-24-F1 (hex) Shenzhen Fanhai Sanjiang Electronics Co., Ltd. +0024F1 (base 16) Shenzhen Fanhai Sanjiang Electronics Co., Ltd. + 3Floor-Guangcai Xintiandi Building,Nanshan Rd,Nanshan, + Shenzhen Guangdong 518054 CN -B8-BB-23 (hex) Guangdong Nufront CSC Co., Ltd -B8BB23 (base 16) Guangdong Nufront CSC Co., Ltd - A403-414, Building 13, No.232 Waihuan East Road, Higher Education Mega Center, Guangdong 510006, China - Guangzhou Guangdong 510006 - CN +50-D2-13 (hex) CviLux Corporation +50D213 (base 16) CviLux Corporation + 9F,No.9,Lane 3,Sec.1,Chung-Cheng East Road, Tamshui + New Taipei City 25147 + TW -34-EA-34 (hex) HangZhou Gubei Electronics Technology Co.,Ltd -34EA34 (base 16) HangZhou Gubei Electronics Technology Co.,Ltd - Room 106, No.611 Jianghong Road, Binjiang District, Hangzhou, Zhejiang, China - Hangzhou ZheJiang 310052 +00-1E-29 (hex) Hypertherm Inc +001E29 (base 16) Hypertherm Inc + 15 Great Hollow Rd + Hanover NH 03755 + US + +7C-C6-C4 (hex) Kolff Computer Supplies b.v. +7CC6C4 (base 16) Kolff Computer Supplies b.v. + Kuipershaven 22 + Dordrecht Zuid-Holland 3311 AL + NL + +C4-83-6F (hex) Ciena Corporation +C4836F (base 16) Ciena Corporation + 7035 Ridge Road + Hanover MD 21076 + US + +50-04-B8 (hex) HUAWEI TECHNOLOGIES CO.,LTD +5004B8 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -EC-26-FB (hex) TECC CO.,LTD. -EC26FB (base 16) TECC CO.,LTD. - Guam-ro 15-gil, Buk-gu - Daegu 720-849 - KR +5C-FF-35 (hex) Wistron Corporation +5CFF35 (base 16) Wistron Corporation + 21F, 88, Sec.1, Hsin Tai Wu Rd., Hsichih, + Taipei Hsien 221 + TW -44-DC-91 (hex) PLANEX COMMUNICATIONS INC. -44DC91 (base 16) PLANEX COMMUNICATIONS INC. - 2F F-NISSAY Ebisu Bldg 3-16-3 Higashi, - Shibuya-ku Tokyo 150-0011 - JP +78-F2-9E (hex) PEGATRON CORPORATION +78F29E (base 16) PEGATRON CORPORATION + 5F No. 76, Ligong St., Beitou District + Taipei City Taiwan 112 + TW -E0-9D-B8 (hex) PLANEX COMMUNICATIONS INC. -E09DB8 (base 16) PLANEX COMMUNICATIONS INC. - Planex Volta Bldg., 2-11-9 Ebisu-Nishi,Shibuya-ku, - 00000 1500021 +64-77-7D (hex) Hitron Technologies. Inc +64777D (base 16) Hitron Technologies. Inc + No. 1-8, Lising 1st Rd. Hsinchu Science Park, Hsinchu, 300, Taiwan, R.O.C + Hsin-chu Taiwan 300 + TW + +60-D2-62 (hex) Tzukuri Pty Ltd +60D262 (base 16) Tzukuri Pty Ltd + 6 Lenthall Street + Kensington NSW 2033 + AU + +14-2F-FD (hex) LT SECURITY INC +142FFD (base 16) LT SECURITY INC + 18738 SAN JOSE AVE + CITY OF INDUSTRY CA 91748 US -00-0F-59 (hex) Phonak AG -000F59 (base 16) Phonak AG - Länggasse 17 - Murten FR 3280 - CH +00-D0-B2 (hex) Xiotech Corporation +00D0B2 (base 16) Xiotech Corporation + 6455 FLYING CLOUD DRIVE + EDEN PRAIRIE MN 55344 + US -74-B5-7E (hex) zte corporation -74B57E (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +9C-50-EE (hex) Cambridge Industries(Group) Co.,Ltd. +9C50EE (base 16) Cambridge Industries(Group) Co.,Ltd. + 5/F,Building 8, 2388 ChenHang Road, MinHang District + shanghai 201114 CN -00-20-F4 (hex) SPECTRIX CORPORATION -0020F4 (base 16) SPECTRIX CORPORATION - 106 WILMOT ROAD, SUITE 250 - DEERFIELD IL 60015-5150 +40-ED-98 (hex) IEEE Registration Authority +40ED98 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -04-EE-91 (hex) x-fabric GmbH -04EE91 (base 16) x-fabric GmbH - Nachodstraße 7 - Berlin 10779 - DE +AC-DC-E5 (hex) Procter & Gamble Company +ACDCE5 (base 16) Procter & Gamble Company + 2 Procter & Gamble Plaza + Cincinnati OH 45202 + US -C4-9A-02 (hex) LG Electronics (Mobile Communications) -C49A02 (base 16) LG Electronics (Mobile Communications) - 60-39, Gasan-dong, Geumcheon-gu - Seoul 153-801 - KR +00-B3-62 (hex) Apple, Inc. +00B362 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -54-D2-72 (hex) Nuki Home Solutions GmbH -54D272 (base 16) Nuki Home Solutions GmbH - Muenzgrabenstrasse 92 - Graz 8010 - AT +E4-E4-AB (hex) Apple, Inc. +E4E4AB (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -B4-74-43 (hex) Samsung Electronics Co.,Ltd -B47443 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +54-FA-96 (hex) Nokia Solutions and Networks GmbH & Co. KG +54FA96 (base 16) Nokia Solutions and Networks GmbH & Co. KG + Werinherstrasse 91 + München Bavaria D-81541 + DE -30-76-6F (hex) LG Electronics (Mobile Communications) -30766F (base 16) LG Electronics (Mobile Communications) - 60-39, Gasan-dong, Geumcheon-gu - Seoul 153-801 - KR +60-33-4B (hex) Apple, Inc. +60334B (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -A8-92-2C (hex) LG Electronics (Mobile Communications) -A8922C (base 16) LG Electronics (Mobile Communications) - 60-39, Gasan-dong, Geumcheon-gu - Seoul 153-801 - KR +C8-91-F9 (hex) Sagemcom Broadband SAS +C891F9 (base 16) Sagemcom Broadband SAS + 250 route de l'Empereur + Rueil Malmaison HAUTS DE SEINE 92848 + FR -F8-0C-F3 (hex) LG Electronics (Mobile Communications) -F80CF3 (base 16) LG Electronics (Mobile Communications) - 60-39, Gasan-dong, Geumcheon-gu - Seoul 153-801 - KR +84-04-D2 (hex) Kirale Technologies SL +8404D2 (base 16) Kirale Technologies SL + General Vara de Rey 9, 5B + Logrono La Rioja 26001 + ES -00-1F-6B (hex) LG Electronics (Mobile Communications) -001F6B (base 16) LG Electronics (Mobile Communications) - 60-39, Gasan-dong, Geumcheon-gu - Seoul 153-801 - KR +38-AF-D7 (hex) FUJITSU LIMITED +38AFD7 (base 16) FUJITSU LIMITED + 403, Kosugi-cho 1-chome, Nakahara-ku + Kawasaki Kanagawa 211-0063 + JP -00-26-E2 (hex) LG Electronics (Mobile Communications) -0026E2 (base 16) LG Electronics (Mobile Communications) - 60-39, Gasan-dong, Geumcheon-gu - Seoul 153-801 - KR +28-99-3A (hex) Arista Networks +28993A (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US -E8-88-6C (hex) Shenzhen SC Technologies Co.,LTD -E8886C (base 16) Shenzhen SC Technologies Co.,LTD - 4/FL,2Block,LianChuang Industrial Park,Bulan Road,Longgang - Shenzhen Guangdong Province 518112 +60-42-7F (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD +60427F (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD + Unit East Block22-24/F,Skyworth semiconductor design Bldg., Gaoxin Ave.4.S.,Nanshan District,Shenzhen,China + SHENZHEN GUANGDONG 518057 CN -DC-35-F1 (hex) Positivo Tecnologia S.A. -DC35F1 (base 16) Positivo Tecnologia S.A. - João Bettega, 5200 - Curitiba Paraná 81350-000 - BR - -00-24-FF (hex) QLogic Corporation -0024FF (base 16) QLogic Corporation - 26650 Aliso Viejo Parkway - Aliso Viejo CA 92656 - US +BC-A8-A6 (hex) Intel Corporate +BCA8A6 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -00-1E-21 (hex) Qisda Corporation -001E21 (base 16) Qisda Corporation - 157 Shan-Ying Road, Gueishan - Taoyuan 333 - TW +74-FF-4C (hex) Skyworth Digital Technology(Shenzhen) Co.,Ltd +74FF4C (base 16) Skyworth Digital Technology(Shenzhen) Co.,Ltd + 7F,Block A,Skyworth Building, + Shenzhen Guangdong 518057 + CN -00-03-9D (hex) Qisda Corporation -00039D (base 16) Qisda Corporation - 157 Shan Ying Road - GueiShan Taoyuan 333 - TW +A0-2C-36 (hex) FN-LINK TECHNOLOGY LIMITED +A02C36 (base 16) FN-LINK TECHNOLOGY LIMITED + A Building,HuiXin industial park,No 31, YongHe road, Fuyong town, Bao'an District + SHENZHEN GUANGDONG 518100 + CN -00-08-0D (hex) Toshiba -00080D (base 16) Toshiba - 2-9, Suehiro-cho, - Tokyo 198-8710 - JP +F4-8C-50 (hex) Intel Corporate +F48C50 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -00-0E-7B (hex) Toshiba -000E7B (base 16) Toshiba - 2-9, Suehiro-Cho - Ome Tokyo 1988710 - JP +DC-D2-55 (hex) Kinpo Electronics, Inc. +DCD255 (base 16) Kinpo Electronics, Inc. + No.147, Sec. 3, Beishen Rd + Shenkeng Dist. New Taipei City 222 + TW -E8-E0-B7 (hex) Toshiba -E8E0B7 (base 16) Toshiba - 2-9,Suehiro-Cho - Ome Tokyo 1988710 +64-EB-8C (hex) Seiko Epson Corporation +64EB8C (base 16) Seiko Epson Corporation + 80 Harashinden + Shiojiri-shi Nagano-ken 399-0785 JP -00-03-B2 (hex) Radware -0003B2 (base 16) Radware - 8 Hamrpe Hochvim - 000 0000 - IL - -00-A0-C6 (hex) Qualcomm Inc. -00A0C6 (base 16) Qualcomm Inc. - 6455 LUSK BLVD - SAN DIEGO CA 92121 - US - -64-9C-81 (hex) Qualcomm Inc. -649C81 (base 16) Qualcomm Inc. - 5665 Morehouse Drive - San Diego CA 92071 +00-13-51 (hex) Niles Audio Corporation +001351 (base 16) Niles Audio Corporation + 5919 Sea Otter Place + Carlsbad CA 92010 US -18-39-19 (hex) Unicoi Systems -183919 (base 16) Unicoi Systems - 410 Peachtree Pkwy - Cumming GA 30041 +00-03-20 (hex) Xpeed, Inc. +000320 (base 16) Xpeed, Inc. + 99 W. Tasman Drive + San Jose CA 95134 US -00-C0-E4 (hex) SIEMENS BUILDING -00C0E4 (base 16) SIEMENS BUILDING - TECHNOLOGIES, INC. - BUFFALO GROVE IL 60089 +AC-1F-6B (hex) Super Micro Computer, Inc. +AC1F6B (base 16) Super Micro Computer, Inc. + 980 Rock Ave + San Jose CA 95131 US -00-0D-10 (hex) Embedtronics Oy -000D10 (base 16) Embedtronics Oy - Leväsentie 23 - Kuopio 70780 - FI - -FC-F6-47 (hex) Fiberhome Telecommunication Technologies Co.,LTD -FCF647 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan City Hubei Province 430074 - CN - -18-68-6A (hex) zte corporation -18686A (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +50-8A-0F (hex) SHENZHEN FISE TECHNOLOGY HOLDING CO.,LTD. +508A0F (base 16) SHENZHEN FISE TECHNOLOGY HOLDING CO.,LTD. + No.6 Building, Longfu Industrial Area, Huarong Road, Tongsheng Community, Dalang Street, Longhua New District + Shenzhen Guangdong 518000 CN -DC-44-27 (hex) IEEE Registration Authority -DC4427 (base 16) IEEE Registration Authority +7C-CB-E2 (hex) IEEE Registration Authority +7CCBE2 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -00-1A-6A (hex) Tranzas, Inc. -001A6A (base 16) Tranzas, Inc. - Queens tower C17 - Yokohama Kanagawa-ken 220-6217 - JP +A8-A5-E2 (hex) MSF-Vathauer Antriebstechnik GmbH & Co KG +A8A5E2 (base 16) MSF-Vathauer Antriebstechnik GmbH & Co KG + Am Hessentuch 6-8 + Detmold Nordrhein-Westfalen 32758 + DE -BC-34-00 (hex) IEEE Registration Authority -BC3400 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +C0-28-8D (hex) Logitech, Inc +C0288D (base 16) Logitech, Inc + 4700 NW Camas Meadows Drive + Camas WA 98607 US -A4-71-74 (hex) HUAWEI TECHNOLOGIES CO.,LTD -A47174 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +6C-EC-5A (hex) Hon Hai Precision Ind. CO.,Ltd. +6CEC5A (base 16) Hon Hai Precision Ind. CO.,Ltd. + B22 Building, NO.52,Tongle Road, Foxconn Industrial Park, District Jiangnan, Nanning, Guangxi, China + Nanning Guangxi 530031 CN -F4-CB-52 (hex) HUAWEI TECHNOLOGIES CO.,LTD -F4CB52 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +44-C3-46 (hex) HUAWEI TECHNOLOGIES CO.,LTD +44C346 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -B8-08-D7 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B808D7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +30-74-96 (hex) HUAWEI TECHNOLOGIES CO.,LTD +307496 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -94-61-1E (hex) Wata Electronics Co.,Ltd. -94611E (base 16) Wata Electronics Co.,Ltd. - No 142,South Tanshen Road, Tanzhou Town,Zhongshan City,Guangdong,China - Zhongshan Guangdong 528467 +70-8A-09 (hex) HUAWEI TECHNOLOGIES CO.,LTD +708A09 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -A0-20-A6 (hex) Espressif Inc. -A020A6 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +18-06-75 (hex) Dilax Intelcom GmbH +180675 (base 16) Dilax Intelcom GmbH + Alt-Moabit 96b + Berlin 10559 + DE -58-52-8A (hex) Mitsubishi Electric Corporation -58528A (base 16) Mitsubishi Electric Corporation - 2-7-3 Marunouchi Chiyoda-ku - Tokyo 100-8310 +00-0F-C2 (hex) Uniwell Corporation +000FC2 (base 16) Uniwell Corporation + 5-25, 3-chome, Tenma, Kita-ku + Osaka 530-0043 JP -68-07-15 (hex) Intel Corporate -680715 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +CC-C5-EF (hex) Co-Comm Servicios Telecomunicaciones S.L. +CCC5EF (base 16) Co-Comm Servicios Telecomunicaciones S.L. + Lisboa, 20 Las Rozas + Madrid Madrid 28232 + ES -3C-B6-B7 (hex) vivo Mobile Communication Co., Ltd. -3CB6B7 (base 16) vivo Mobile Communication Co., Ltd. +90-02-A9 (hex) Zhejiang Dahua Technology Co., Ltd. +9002A9 (base 16) Zhejiang Dahua Technology Co., Ltd. + NO.1199 BinAn Road + Hangzhou Zhejiang 310053 + CN + +E8-9E-B4 (hex) Hon Hai Precision Ind. Co.,Ltd. +E89EB4 (base 16) Hon Hai Precision Ind. Co.,Ltd. + Building D21,No.1, East Zone 1st Road + Chongqing Chongqing 401332 + CN + +D4-6A-6A (hex) Hon Hai Precision Ind. Co.,Ltd. +D46A6A (base 16) Hon Hai Precision Ind. Co.,Ltd. + Building D21,No.1, East Zone 1st Road + Chongqing Chongqing 401332 + CN + +98-FD-74 (hex) ACT.CO.LTD +98FD74 (base 16) ACT.CO.LTD + 3-RD Floor 93, Sanbon-ro + Gunpo-si Gyeonggi-do 15849 + KR + +E0-50-8B (hex) Zhejiang Dahua Technology Co., Ltd. +E0508B (base 16) Zhejiang Dahua Technology Co., Ltd. + No.1199,Waterfront Road + Hangzhou Zhejiang 310053 + CN + +00-00-64 (hex) Yokogawa Digital Computer Corporation +000064 (base 16) Yokogawa Digital Computer Corporation + Shinjuku MIDWEST Bldg.4-30-3 + Yoyogi Shibuya-ku, Tokyo 151-0053 + JP + +D0-F7-3B (hex) Helmut Mauell GmbH Werk Weida +D0F73B (base 16) Helmut Mauell GmbH Werk Weida + Am Rosenhügel 1-7 + Velbert 42553 + DE + +0C-49-33 (hex) Sichuan Jiuzhou Electronic Technology Co., Ltd. +0C4933 (base 16) Sichuan Jiuzhou Electronic Technology Co., Ltd. + No. 259, Jiuzhou Road + Mianyang City Sichuan Province 621000 + CN + +9C-1E-95 (hex) Actiontec Electronics, Inc +9C1E95 (base 16) Actiontec Electronics, Inc + 3301 Olcott St. + Santa Clara CA 95054 + US + +64-DB-43 (hex) Motorola (Wuhan) Mobility Technologies Communication Co., Ltd. +64DB43 (base 16) Motorola (Wuhan) Mobility Technologies Communication Co., Ltd. + No.19, Gaoxin 4th Road, Wuhan East Lake High-tech Zone, Wuhan + Wuhan Hubei 430000 + CN + +68-AF-13 (hex) Futura Mobility +68AF13 (base 16) Futura Mobility + 515 PENNSYLVANIA AVE + FORT WASHINTON PA 19034 + US + +68-1A-B2 (hex) zte corporation +681AB2 (base 16) zte corporation + 12/F.,zte R&D building,kejinan Road, + shenzhen guangdong 518057 + CN + +7C-EB-AE (hex) Ridgeline Instruments +7CEBAE (base 16) Ridgeline Instruments + 4803 Innovation Drive, Suite 3B + Fort Collins CO 80525 + US + +00-25-90 (hex) Super Micro Computer, Inc. +002590 (base 16) Super Micro Computer, Inc. + 980 Rock Avenue + San Jose California 95131 + US + +24-C1-BD (hex) CRRC DALIAN R&D CO.,LTD. +24C1BD (base 16) CRRC DALIAN R&D CO.,LTD. + No.1 Haoyang North Street,Lvshun Economic Deveopment Zone + Dalian Liaoning 116052 + CN + +00-A2-EE (hex) Cisco Systems, Inc +00A2EE (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +00-59-DC (hex) Cisco Systems, Inc +0059DC (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +C8-D3-FF (hex) Hewlett Packard +C8D3FF (base 16) Hewlett Packard + 11445 Compaq Center Drive + Houston TX 77070 + US + +C4-BE-84 (hex) Texas Instruments +C4BE84 (base 16) Texas Instruments + 12500 TI Blvd + Dallas 75243 + US + +F4-F5-24 (hex) Motorola Mobility LLC, a Lenovo Company +F4F524 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +00-0B-2E (hex) Cal-Comp Electronics & Communications Company Ltd. +000B2E (base 16) Cal-Comp Electronics & Communications Company Ltd. + No.147, Sec. 3, Beishen Rd + Shenkeng Dist New Taipei City --- + TW + +48-65-EE (hex) IEEE Registration Authority +4865EE (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +F4-CA-E5 (hex) FREEBOX SAS +F4CAE5 (base 16) FREEBOX SAS + 8 rue de la Ville l'Eveque + PARIS IdF 75008 + FR + +00-BB-C1 (hex) CANON INC. +00BBC1 (base 16) CANON INC. + 30-2 Shimomaruko 3-chome, + Ohta-ku Tokyo 146-8501 + JP + +00-13-A5 (hex) General Solutions, LTD. +0013A5 (base 16) General Solutions, LTD. + 5902 Sovereign Drive + Houston Texas 77036 + US + +50-98-F3 (hex) Rheem Australia Pty Ltd +5098F3 (base 16) Rheem Australia Pty Ltd + 1 Alan Street + Rydalmere NSW 2116 + AU + +50-6B-8D (hex) Nutanix +506B8D (base 16) Nutanix + 1740 Technology Drive Ste #150 + San Jose CA 95110 + US + +00-38-DF (hex) Cisco Systems, Inc +0038DF (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +40-F4-13 (hex) Rubezh +40F413 (base 16) Rubezh + Ulyanovskaya str. 28 + Saratov 410056 + RU + +90-00-4E (hex) Hon Hai Precision Ind. Co.,Ltd. +90004E (base 16) Hon Hai Precision Ind. Co.,Ltd. + Building D21,No.1, East Zone 1st Road + Chongqing Chongqing 401332 + CN + +24-88-94 (hex) shenzhen lensun Communication Technology LTD +248894 (base 16) shenzhen lensun Communication Technology LTD + RM 201, Block 19, Zhiheng industry Park, Nantou Check point + Shenzhen Guangdong 518000 + CN + +EC-10-7B (hex) Samsung Electronics Co.,Ltd +EC107B (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +B0-4B-BF (hex) PT HAN SUNG ELECTORONICS INDONESIA +B04BBF (base 16) PT HAN SUNG ELECTORONICS INDONESIA + JL.PALEM 1 BLOK DS-6 + KAWASAN INDUSTRI BATIK LIPPO CIKARANG, DESA CIBATU, KECAMATAN CIKARANG SELATAN BEKASI JAWA BARAT 17550 + ID + +CC-2D-83 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +CC2D83 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +D4-6E-0E (hex) TP-LINK TECHNOLOGIES CO.,LTD. +D46E0E (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan + Shenzhen Guangdong 518057 + CN + +88-36-6C (hex) EFM Networks +88366C (base 16) EFM Networks + 6F, Benposra II 1197-1 Bojeong Giheung Gu + Yong In Kyunggi do 446913 + KR + +48-DA-96 (hex) Eddy Smart Home Solutions Inc. +48DA96 (base 16) Eddy Smart Home Solutions Inc. + 1600-25 Sheppard Avenue West + Toronto Ontario M2N 6S6 + CA + +F0-74-E4 (hex) Thundercomm Technology Co., Ltd +F074E4 (base 16) Thundercomm Technology Co., Ltd + Building NO.4, 99# Xiantao Data Valley Zhonglu, Yubei District, Chongqing, China + chongqing 404100 + CN + +A0-72-2C (hex) HUMAX Co., Ltd. +A0722C (base 16) HUMAX Co., Ltd. + HUMAX Village, 216, Hwangsaeul-ro, Bu + Seongnam-si Gyeonggi-do 463-875 + KR + +C8-3D-D4 (hex) CyberTAN Technology Inc. +C83DD4 (base 16) CyberTAN Technology Inc. + 99 Park Ave III, Hsinchu Science Park + Hsinchu 308 + TW + +E0-B9-4D (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +E0B94D (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 + CN + +38-D5-47 (hex) ASUSTek COMPUTER INC. +38D547 (base 16) ASUSTek COMPUTER INC. + 15,Li-Te Rd., Peitou, Taipei 112, Taiwan + Taipei Taiwan 112 + TW + +B4-D1-35 (hex) Cloudistics +B4D135 (base 16) Cloudistics + 116000 Sunrise Valley Dr Suite 210 + Reston VA 20190 + US + +F0-2F-A7 (hex) HUAWEI TECHNOLOGIES CO.,LTD +F02FA7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +18-DE-D7 (hex) HUAWEI TECHNOLOGIES CO.,LTD +18DED7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +1C-23-2C (hex) Samsung Electronics Co.,Ltd +1C232C (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +78-88-8A (hex) CDR Sp. z o.o. Sp. k. +78888A (base 16) CDR Sp. z o.o. Sp. k. + Palki 15 + Zory 44-240 + PL + +FC-D8-48 (hex) Apple, Inc. +FCD848 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +E0-0D-B9 (hex) Cree, Inc. +E00DB9 (base 16) Cree, Inc. + 4600 Silicon Drive + Durham NC 27703 + US + +FC-83-C6 (hex) N-Radio Technologies Co., Ltd. +FC83C6 (base 16) N-Radio Technologies Co., Ltd. + 2#, 7F, Satellite Buiding, Keyuan Road, Nanshan + ShenZhen GuangDong 518000 + CN + +DC-0D-30 (hex) Shenzhen Feasycom Technology Co., Ltd. +DC0D30 (base 16) Shenzhen Feasycom Technology Co., Ltd. + #2004, Huichao Science & Technology Building, Jinhai Road, Xixiang + Shenzhen Guangdong 18000 + CN + +F0-AC-D7 (hex) IEEE Registration Authority +F0ACD7 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +00-21-D2 (hex) Samsung Electronics Co.,Ltd +0021D2 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +00-21-D1 (hex) Samsung Electronics Co.,Ltd +0021D1 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +00-1F-CC (hex) Samsung Electronics Co.,Ltd +001FCC (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +A4-29-83 (hex) Boeing Defence Australia +A42983 (base 16) Boeing Defence Australia + GPO Box 767 + Brisbane Queensland 4001 + AU + +EC-88-92 (hex) Motorola Mobility LLC, a Lenovo Company +EC8892 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 Merchandise Mart Plaza, Suite 1800 + Chicago IL 60654 + US + +94-95-A0 (hex) Google, Inc. +9495A0 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +00-A6-CA (hex) Cisco Systems, Inc +00A6CA (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +D8-45-2B (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. +D8452B (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. + Phase 3, Bayan Lepas FIZ + Bayan Lepas Penang 11900 + MY + +E4-18-6B (hex) Zyxel Communications Corporation +E4186B (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + +D4-1D-71 (hex) Palo Alto Networks +D41D71 (base 16) Palo Alto Networks + 3000 Tannery Way + Santa Clara CA 95054 + US + +00-87-31 (hex) Cisco Systems, Inc +008731 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +88-DE-A9 (hex) Roku, Inc. +88DEA9 (base 16) Roku, Inc. + 12980 Saratoga Ave + Saratoga CA 95070 + US + +00-4A-77 (hex) zte corporation +004A77 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +60-A1-0A (hex) Samsung Electronics Co.,Ltd +60A10A (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +8C-71-F8 (hex) Samsung Electronics Co.,Ltd +8C71F8 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +CC-05-1B (hex) Samsung Electronics Co.,Ltd +CC051B (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +8C-77-12 (hex) Samsung Electronics Co.,Ltd +8C7712 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +94-63-D1 (hex) Samsung Electronics Co.,Ltd +9463D1 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +1C-9D-3E (hex) Integrated Device Technology (Malaysia) Sdn. Bhd. +1C9D3E (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. + Phase 3, Bayan Lepas FIZ + Bayan Lepas Penang 11900 + MY + +74-87-A9 (hex) OCT Technology Co., Ltd. +7487A9 (base 16) OCT Technology Co., Ltd. + 8F. -2, No. 94, Baojhong Rd. Sindian Dist. + New Taipei City 231 + TW + +50-3F-98 (hex) CMITECH +503F98 (base 16) CMITECH + 904-ho, 25, Simin-daero 248beon-gil, Dongan-gu + Anyang-si Gyeonggi-do 14067 + KR + +5C-49-7D (hex) Samsung Electronics Co.,Ltd +5C497D (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR + +98-23-4E (hex) Micromedia AG +98234E (base 16) Micromedia AG + Gartenweg 46 + Buonas Zug 6343 + CH + +78-20-79 (hex) ID Tech +782079 (base 16) ID Tech + 10721 Walker St + Cypress CA 90630 + US + +78-D6-F0 (hex) SAMSUNG ELECTRO MECHANICS CO., LTD. +78D6F0 (base 16) SAMSUNG ELECTRO MECHANICS CO., LTD. + Metan Dong 314, Youngtong Gu + Suwon Kyung-gi Do. 443-743 + KR + +78-25-AD (hex) Samsung Electronics Co.,Ltd +7825AD (base 16) Samsung Electronics Co.,Ltd + 416, MAETAN-3DONG, PALDAL-GU + SUWON CITY KYUNGKI-DO 442-742 + KR + +EC-E0-9B (hex) Samsung Electronics Co.,Ltd +ECE09B (base 16) Samsung Electronics Co.,Ltd + 416, Maetan-3dong, Yeongtong-gu, + Suwon-City Gyeonggi-do 443-742 + KR + +00-16-32 (hex) Samsung Electronics Co.,Ltd +001632 (base 16) Samsung Electronics Co.,Ltd + 416, METAN-3DONG, + SUWON KYUNGKI-DO 442-742 + KR + +0C-60-76 (hex) Hon Hai Precision Ind. Co.,Ltd. +0C6076 (base 16) Hon Hai Precision Ind. Co.,Ltd. + Building D21,No.1, East Zone 1st Road + Chongqing Chongqing 401332 + CN + +0C-EE-E6 (hex) Hon Hai Precision Ind. Co.,Ltd. +0CEEE6 (base 16) Hon Hai Precision Ind. Co.,Ltd. + Building D21,No.1, East Zone 1st Road + Chongqing Chongqing 401332 + CN + +E4-D5-3D (hex) Hon Hai Precision Ind. Co.,Ltd. +E4D53D (base 16) Hon Hai Precision Ind. Co.,Ltd. + Building D21,No.1, East Zone 1st Road + Chongqing Chongqing 401332 + CN + +C0-14-3D (hex) Hon Hai Precision Ind. Co.,Ltd. +C0143D (base 16) Hon Hai Precision Ind. Co.,Ltd. + Building D21,No.1, East Zone 1st Road + Chongqing Chongqing 401332 + CN + +C0-18-85 (hex) Hon Hai Precision Ind. Co.,Ltd. +C01885 (base 16) Hon Hai Precision Ind. Co.,Ltd. + Building D21,No.1, East Zone 1st Road + Chongqing Chongqing 401332 + CN + +58-94-CF (hex) Vertex Standard LMR, Inc. +5894CF (base 16) Vertex Standard LMR, Inc. + 4-8-8 Nakameguro + Meguro-ku Tokyo 153-8644 + JP + +20-F8-5E (hex) Delta Electronics +20F85E (base 16) Delta Electronics + 252 Shangying Road + Taoyuan County Taiwan 33341 + TW + +E4-E0-C5 (hex) Samsung Electronics Co.,Ltd +E4E0C5 (base 16) Samsung Electronics Co.,Ltd + 416, Maetan-3dong, Yeongtong-gu + Suwon Gyeonggi-do 443742 + KR + +20-D5-BF (hex) Samsung Electronics Co.,Ltd +20D5BF (base 16) Samsung Electronics Co.,Ltd + 416, Maetan 3dong, Yeongtong-Gu + Suwon Gyeonggi-Do 443742 + KR + +00-23-E4 (hex) IPnect co. ltd. +0023E4 (base 16) IPnect co. ltd. + 808 albatross B/D 237-18 + Seoul 153-801 + KR + +70-D4-F2 (hex) RIM +70D4F2 (base 16) RIM + Phillip Street + Waterloo Ontario N2L 3W8 + CA + +00-16-DB (hex) Samsung Electronics Co.,Ltd +0016DB (base 16) Samsung Electronics Co.,Ltd + #94-1 + Gumi-City Gyeong-Buk 730-350 + KR + +00-1E-E2 (hex) Samsung Electronics Co.,Ltd +001EE2 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +C0-BD-D1 (hex) SAMSUNG ELECTRO-MECHANICS(THAILAND) +C0BDD1 (base 16) SAMSUNG ELECTRO-MECHANICS(THAILAND) + 93Moo5T. Bangsamak + Bangpakong Chachoengsao 24180 + TH + +B4-79-A7 (hex) SAMSUNG ELECTRO-MECHANICS(THAILAND) +B479A7 (base 16) SAMSUNG ELECTRO-MECHANICS(THAILAND) + 93Moo5T. Bangsamak + Bangpakong Chachoengsao 24180 + TH + +7C-11-CB (hex) HUAWEI TECHNOLOGIES CO.,LTD +7C11CB (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +1C-25-E1 (hex) China Mobile IOT Company Limited +1C25E1 (base 16) China Mobile IOT Company Limited + NO.8 Yu Ma Road, NanAn Area + Chongqing Chongqing 401336 + CN + +C0-F6-36 (hex) Hangzhou Kuaiyue Technologies, Ltd. +C0F636 (base 16) Hangzhou Kuaiyue Technologies, Ltd. + Dongguan Hitech Park, Building 1-805, 288 Qiuyi Rd, Bingjiang District + Hangzhou Zhejiang 310053 + CN + +BC-20-A4 (hex) Samsung Electronics Co.,Ltd +BC20A4 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +08-D4-2B (hex) Samsung Electronics Co.,Ltd +08D42B (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +78-9E-D0 (hex) Samsung Electronics Co.,Ltd +789ED0 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +B0-C4-E7 (hex) Samsung Electronics Co.,Ltd +B0C4E7 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +C8-14-79 (hex) Samsung Electronics Co.,Ltd +C81479 (base 16) Samsung Electronics Co.,Ltd + #94-1,Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +00-24-90 (hex) Samsung Electronics Co.,Ltd +002490 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +00-23-D7 (hex) Samsung Electronics Co.,Ltd +0023D7 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +54-9B-12 (hex) Samsung Electronics Co.,Ltd +549B12 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +FC-A1-3E (hex) Samsung Electronics Co.,Ltd +FCA13E (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +A0-07-98 (hex) Samsung Electronics Co.,Ltd +A00798 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +00-1A-22 (hex) eQ-3 Entwicklung GmbH +001A22 (base 16) eQ-3 Entwicklung GmbH + Maiburger Str. 36 + Leer Niedersachsen D-26789 + DE + +1C-AF-05 (hex) Samsung Electronics Co.,Ltd +1CAF05 (base 16) Samsung Electronics Co.,Ltd + #94-1,Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +24-C6-96 (hex) Samsung Electronics Co.,Ltd +24C696 (base 16) Samsung Electronics Co.,Ltd + #94-1,Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +94-D7-71 (hex) Samsung Electronics Co.,Ltd +94D771 (base 16) Samsung Electronics Co.,Ltd + #94-1,Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +E8-4E-84 (hex) Samsung Electronics Co.,Ltd +E84E84 (base 16) Samsung Electronics Co.,Ltd + #94-1,Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +B0-DF-3A (hex) Samsung Electronics Co.,Ltd +B0DF3A (base 16) Samsung Electronics Co.,Ltd + #94-1,Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +80-57-19 (hex) Samsung Electronics Co.,Ltd +805719 (base 16) Samsung Electronics Co.,Ltd + #94-1,Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +34-BE-00 (hex) Samsung Electronics Co.,Ltd +34BE00 (base 16) Samsung Electronics Co.,Ltd + #94-1,Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +78-52-1A (hex) Samsung Electronics Co.,Ltd +78521A (base 16) Samsung Electronics Co.,Ltd + #94-1,Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +00-1F-CD (hex) Samsung Electronics Co.,Ltd +001FCD (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +38-EC-E4 (hex) Samsung Electronics Co.,Ltd +38ECE4 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +94-51-03 (hex) Samsung Electronics Co.,Ltd +945103 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +5C-E8-EB (hex) Samsung Electronics Co.,Ltd +5CE8EB (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +24-0D-C2 (hex) TCT mobile ltd +240DC2 (base 16) TCT mobile ltd + No.86 hechang 7th road, zhongkai, Hi-Tech District + Hui Zhou Guang Dong 516006 + CN + +04-FE-A1 (hex) Fihonest communication co.,Ltd +04FEA1 (base 16) Fihonest communication co.,Ltd + Room902,Park road,Zhixing business-building + Dongguan Guangdong 523560 + CN + +EC-8C-A2 (hex) Ruckus Wireless +EC8CA2 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US + +EC-8E-AE (hex) Nagravision SA +EC8EAE (base 16) Nagravision SA + Route de Geneve 22-24, PO 7980 + Cheseaux Vaud 1033 + CH + +E0-E7-BB (hex) Nureva, Inc. +E0E7BB (base 16) Nureva, Inc. + 1000, 1221 8th Street SW + Calgary AB T2R 0L4 + CA + +54-6C-0E (hex) Texas Instruments +546C0E (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +A0-43-DB (hex) Sitael S.p.A. +A043DB (base 16) Sitael S.p.A. + Via San Sabino, 21 + Mola di Bari BA 70042 + IT + +20-BB-C6 (hex) Jabil Circuit Hungary Ltd. +20BBC6 (base 16) Jabil Circuit Hungary Ltd. + Huszár Andor 1. + Tiszaújváros H-3580 + HU + +00-B0-B3 (hex) XSTREAMIS PLC +00B0B3 (base 16) XSTREAMIS PLC + Magdalen Centre + Oxford 0X4 4GA 0000 + GB + +00-23-63 (hex) Zhuhai Raysharp Technology Co.,Ltd +002363 (base 16) Zhuhai Raysharp Technology Co.,Ltd + No.119 of Huawei Road, Qianshan Science & Technology Park, + Zhuhai Guangdong 519070 + CN + +00-90-58 (hex) Ultra Electronics Command & Control Systems +009058 (base 16) Ultra Electronics Command & Control Systems + Knaves Beech Business Centre + Hemel Hemstead Herts England HP2 7BW + GB + +00-1C-FD (hex) Universal Electronics, Inc. +001CFD (base 16) Universal Electronics, Inc. + 6101 Gateway Drive + Cypress 90630 + US + +00-10-87 (hex) XSTREAMIS PLC +001087 (base 16) XSTREAMIS PLC + OXFORD SCIENCE PARK + OXFORD 0X4 4GA 00000 + US + +AC-48-2D (hex) Ralinwi Nanjing Electronic Technology Co., Ltd. +AC482D (base 16) Ralinwi Nanjing Electronic Technology Co., Ltd. + 3rd Floor, Building B,R&D Block 3, Xuzhuang Software Park, No. 699-27, Xuanwu Avenue + Nanjing Jiangsu 210046 + CN + +B8-00-18 (hex) Htel +B80018 (base 16) Htel + Dunchon-dearo, Jungwon-gu + Seongnam-si Gyeonggi-do 13229 + KR + +74-72-B0 (hex) Guangzhou Shiyuan Electronics Co., Ltd. +7472B0 (base 16) Guangzhou Shiyuan Electronics Co., Ltd. + No.6, 4th Yunpu Road, Yunpu Industry District + Guangzhou Guangdong 510530 + CN + +DC-1A-01 (hex) Ecoliv Technology ( Shenzhen ) Ltd. +DC1A01 (base 16) Ecoliv Technology ( Shenzhen ) Ltd. + B-734, Tianhui building, Longhua Street, Pine Road, Longhua District + Shenzhen Guangdong 518109 + CN + +08-00-87 (hex) Xyplex, Inc. +080087 (base 16) Xyplex, Inc. + 295 FOSTER STREET + LITTLETON MA 01460 + US + +00-54-9F (hex) Avaya Inc +00549F (base 16) Avaya Inc + 360 Mt Kemble Ave + Morristown NJ 07960 + US + +04-9F-CA (hex) HUAWEI TECHNOLOGIES CO.,LTD +049FCA (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +50-01-6B (hex) HUAWEI TECHNOLOGIES CO.,LTD +50016B (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +00-00-1B (hex) Novell, Inc. +00001B (base 16) Novell, Inc. + 122 EAST 1700 SOUTH + PROVO UT 84606 + US + +00-04-DC (hex) Nortel Networks +0004DC (base 16) Nortel Networks + 8200 Dixie Rd + Brampton Ontario 0000 + CA + +00-0C-F7 (hex) Nortel Networks +000CF7 (base 16) Nortel Networks + 8200 Dixie Rd + Brampton Ontario 0000 + CA + +00-0F-CD (hex) Nortel Networks +000FCD (base 16) Nortel Networks + 8200 Dixie Rd + Brampton Ontario 0000 + CA + +38-25-6B (hex) Microsoft Mobile Oy +38256B (base 16) Microsoft Mobile Oy + Keilalahdentie 4 + Espoo 02150 + FI + +20-3A-EF (hex) Sivantos GmbH +203AEF (base 16) Sivantos GmbH + Henri-Dunant-Strasse 100 + Erlangen Bavaria 91058 + DE + +00-59-79 (hex) Networked Energy Services +005979 (base 16) Networked Energy Services + 5215 Hellyer Avenue + San Jose CA 95138 + US + +D8-47-10 (hex) Sichuan Changhong Electric Ltd. +D84710 (base 16) Sichuan Changhong Electric Ltd. + 35 East Mianxing Road,High-Tech Park, + MianYang SiChuan 621000 + CN + +00-19-72 (hex) Plexus (Xiamen) Co.,ltd. +001972 (base 16) Plexus (Xiamen) Co.,ltd. + No.6 Xiangxing 2 Road + Xiamen Fujian 361006 + CN + +4C-B2-1C (hex) Maxphotonics Co.,Ltd +4CB21C (base 16) Maxphotonics Co.,Ltd + Maxphotonics Industrial Park,Third Furong Road,Furong Industrial Area,Shajing,BaoAn + ShenZhen GuangDong 518125 + CN + +20-5E-F7 (hex) Samsung Electronics Co.,Ltd +205EF7 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +14-1F-78 (hex) Samsung Electronics Co.,Ltd +141F78 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +00-23-47 (hex) ProCurve Networking by HP +002347 (base 16) ProCurve Networking by HP + 60 Alexandra Terrace + 0000 118502 + SG + +00-24-A8 (hex) ProCurve Networking by HP +0024A8 (base 16) ProCurve Networking by HP + 60 Alexandra Terrace + 0000 118502 + SG + +C0-91-34 (hex) ProCurve Networking by HP +C09134 (base 16) ProCurve Networking by HP + 60 Alexandra Terrace + 0000 118502 + SG + +00-1C-EF (hex) Primax Electronics Ltd. +001CEF (base 16) Primax Electronics Ltd. + 669, Ruey Kuang Road, Neihu + Taipei 114 + TW + +00-02-76 (hex) Primax Electronics Ltd. +000276 (base 16) Primax Electronics Ltd. + No. 669, Ruey Kuang Road, Neihu + Taipei Taiwan, R.O.C. + TW + +A8-AD-3D (hex) Alcatel-Lucent Shanghai Bell Co., Ltd +A8AD3D (base 16) Alcatel-Lucent Shanghai Bell Co., Ltd + No. 389, Ningqiao Road, Pudong Jinqiao + Shanghai 201206 + CN + +24-AF-4A (hex) Alcatel-Lucent IPD +24AF4A (base 16) Alcatel-Lucent IPD + 701 E. Middlefield Rd. + Mountain View CA 94043 + US + +7C-20-64 (hex) Alcatel-Lucent IPD +7C2064 (base 16) Alcatel-Lucent IPD + 600 March Drive + Kanata Ontario K2K2E6 + CA + +48-F8-E1 (hex) Nokia +48F8E1 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +8C-90-D3 (hex) Nokia +8C90D3 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +20-7C-8F (hex) Quanta Microsystems,Inc. +207C8F (base 16) Quanta Microsystems,Inc. + No.5 Lane 91,Dongmei Rd. + Hsinchu 300 + TW + +00-0B-34 (hex) ShangHai Broadband Technologies CO.LTD +000B34 (base 16) ShangHai Broadband Technologies CO.LTD + 17F,No.122,HuangXing Road + ShangHai 200090 + CN + +30-92-F6 (hex) SHANGHAI SUNMON COMMUNICATION TECHNOGY CO.,LTD +3092F6 (base 16) SHANGHAI SUNMON COMMUNICATION TECHNOGY CO.,LTD + Suite 604-605,Xing Yuan Technology Plaza + 00000 ShangHai 200233 + CN + +00-1B-BA (hex) Nortel Networks +001BBA (base 16) Nortel Networks + 8200 Dixie Rd + Brampton Ontario 0000 + CA + +00-19-69 (hex) Nortel Networks +001969 (base 16) Nortel Networks + 8200 Dixie Rd + Brampton Ontario 0000 + CA + +00-18-B0 (hex) Nortel Networks +0018B0 (base 16) Nortel Networks + 8200 Dixie Rd + Brampton Ontario 0000 + CA + +00-16-CA (hex) Nortel Networks +0016CA (base 16) Nortel Networks + 8200 Dixie Rd + Brampton Ontario 0000 + CA + +00-14-78 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +001478 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 3/F,Bldg.R1-B,Hi-tech Industrial Park, + ShenZhen GuangDong 518057 + CN + +00-14-0E (hex) Nortel Networks +00140E (base 16) Nortel Networks + 8200 Dixie Rd + Brampton Ontario 0000 + CA + +00-1E-1F (hex) Nortel Networks +001E1F (base 16) Nortel Networks + 8200 Dixie Rd + Brampton Ontario 0000 + CA + +C4-4B-D1 (hex) Wallys Communications Teachnologies Co.,Ltd. +C44BD1 (base 16) Wallys Communications Teachnologies Co.,Ltd. + 5-207, DongHong Pioneer Park, #99 YangYu Lane, + SuZhou JiangSu 215000 + CN + +6C-B9-C5 (hex) Delta Networks, Inc. +6CB9C5 (base 16) Delta Networks, Inc. + 256 Yang Guang Street, Neihu + Taipei Taiwan 11491 + TW + +00-28-F8 (hex) Intel Corporate +0028F8 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +58-BC-8F (hex) Cognitive Systems Corp. +58BC8F (base 16) Cognitive Systems Corp. + 560 Westmount Road North + Waterloo Ontario N2L 0A9 + CA + +D4-55-BE (hex) SHENZHEN FAST TECHNOLOGIES CO.,LTD +D455BE (base 16) SHENZHEN FAST TECHNOLOGIES CO.,LTD + Room 202,Building No.5,Section 30,No.2 of Kefa Road,Nanshan District,Shenzhen,P.R.China + Shenzhen Guangdong 518057 + CN + +B8-BB-23 (hex) Guangdong Nufront CSC Co., Ltd +B8BB23 (base 16) Guangdong Nufront CSC Co., Ltd + A403-414, Building 13, No.232 Waihuan East Road, Higher Education Mega Center, Guangdong 510006, China + Guangzhou Guangdong 510006 + CN + +34-EA-34 (hex) HangZhou Gubei Electronics Technology Co.,Ltd +34EA34 (base 16) HangZhou Gubei Electronics Technology Co.,Ltd + Room 106, No.611 Jianghong Road, Binjiang District, Hangzhou, Zhejiang, China + Hangzhou ZheJiang 310052 + CN + +EC-26-FB (hex) TECC CO.,LTD. +EC26FB (base 16) TECC CO.,LTD. + Guam-ro 15-gil, Buk-gu + Daegu 720-849 + KR + +44-DC-91 (hex) PLANEX COMMUNICATIONS INC. +44DC91 (base 16) PLANEX COMMUNICATIONS INC. + 2F F-NISSAY Ebisu Bldg 3-16-3 Higashi, + Shibuya-ku Tokyo 150-0011 + JP + +E0-9D-B8 (hex) PLANEX COMMUNICATIONS INC. +E09DB8 (base 16) PLANEX COMMUNICATIONS INC. + Planex Volta Bldg., 2-11-9 Ebisu-Nishi,Shibuya-ku, + 00000 1500021 + US + +00-0F-59 (hex) Phonak AG +000F59 (base 16) Phonak AG + Länggasse 17 + Murten FR 3280 + CH + +74-B5-7E (hex) zte corporation +74B57E (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +00-20-F4 (hex) SPECTRIX CORPORATION +0020F4 (base 16) SPECTRIX CORPORATION + 106 WILMOT ROAD, SUITE 250 + DEERFIELD IL 60015-5150 + US + +04-EE-91 (hex) x-fabric GmbH +04EE91 (base 16) x-fabric GmbH + Nachodstraße 7 + Berlin 10779 + DE + +C4-9A-02 (hex) LG Electronics (Mobile Communications) +C49A02 (base 16) LG Electronics (Mobile Communications) + 60-39, Gasan-dong, Geumcheon-gu + Seoul 153-801 + KR + +54-D2-72 (hex) Nuki Home Solutions GmbH +54D272 (base 16) Nuki Home Solutions GmbH + Muenzgrabenstrasse 92 + Graz 8010 + AT + +B4-74-43 (hex) Samsung Electronics Co.,Ltd +B47443 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +30-76-6F (hex) LG Electronics (Mobile Communications) +30766F (base 16) LG Electronics (Mobile Communications) + 60-39, Gasan-dong, Geumcheon-gu + Seoul 153-801 + KR + +A8-92-2C (hex) LG Electronics (Mobile Communications) +A8922C (base 16) LG Electronics (Mobile Communications) + 60-39, Gasan-dong, Geumcheon-gu + Seoul 153-801 + KR + +F8-0C-F3 (hex) LG Electronics (Mobile Communications) +F80CF3 (base 16) LG Electronics (Mobile Communications) + 60-39, Gasan-dong, Geumcheon-gu + Seoul 153-801 + KR + +00-1F-6B (hex) LG Electronics (Mobile Communications) +001F6B (base 16) LG Electronics (Mobile Communications) + 60-39, Gasan-dong, Geumcheon-gu + Seoul 153-801 + KR + +00-26-E2 (hex) LG Electronics (Mobile Communications) +0026E2 (base 16) LG Electronics (Mobile Communications) + 60-39, Gasan-dong, Geumcheon-gu + Seoul 153-801 + KR + +E8-88-6C (hex) Shenzhen SC Technologies Co.,LTD +E8886C (base 16) Shenzhen SC Technologies Co.,LTD + 4/FL,2Block,LianChuang Industrial Park,Bulan Road,Longgang + Shenzhen Guangdong Province 518112 + CN + +DC-35-F1 (hex) Positivo Tecnologia S.A. +DC35F1 (base 16) Positivo Tecnologia S.A. + João Bettega, 5200 + Curitiba Paraná 81350-000 + BR + +00-24-FF (hex) QLogic Corporation +0024FF (base 16) QLogic Corporation + 26650 Aliso Viejo Parkway + Aliso Viejo CA 92656 + US + +00-1E-21 (hex) Qisda Corporation +001E21 (base 16) Qisda Corporation + 157 Shan-Ying Road, Gueishan + Taoyuan 333 + TW + +00-03-9D (hex) Qisda Corporation +00039D (base 16) Qisda Corporation + 157 Shan Ying Road + GueiShan Taoyuan 333 + TW + +00-08-0D (hex) Toshiba +00080D (base 16) Toshiba + 2-9, Suehiro-cho, + Tokyo 198-8710 + JP + +00-0E-7B (hex) Toshiba +000E7B (base 16) Toshiba + 2-9, Suehiro-Cho + Ome Tokyo 1988710 + JP + +E8-E0-B7 (hex) Toshiba +E8E0B7 (base 16) Toshiba + 2-9,Suehiro-Cho + Ome Tokyo 1988710 + JP + +00-03-B2 (hex) Radware +0003B2 (base 16) Radware + 8 Hamrpe Hochvim + 000 0000 + IL + +00-A0-C6 (hex) Qualcomm Inc. +00A0C6 (base 16) Qualcomm Inc. + 6455 LUSK BLVD + SAN DIEGO CA 92121 + US + +64-9C-81 (hex) Qualcomm Inc. +649C81 (base 16) Qualcomm Inc. + 5665 Morehouse Drive + San Diego CA 92071 + US + +18-39-19 (hex) Unicoi Systems +183919 (base 16) Unicoi Systems + 410 Peachtree Pkwy + Cumming GA 30041 + US + +00-C0-E4 (hex) SIEMENS BUILDING +00C0E4 (base 16) SIEMENS BUILDING + TECHNOLOGIES, INC. + BUFFALO GROVE IL 60089 + US + +00-0D-10 (hex) Embedtronics Oy +000D10 (base 16) Embedtronics Oy + Leväsentie 23 + Kuopio 70780 + FI + +FC-F6-47 (hex) Fiberhome Telecommunication Technologies Co.,LTD +FCF647 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan City Hubei Province 430074 + CN + +18-68-6A (hex) zte corporation +18686A (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +DC-44-27 (hex) IEEE Registration Authority +DC4427 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +00-1A-6A (hex) Tranzas, Inc. +001A6A (base 16) Tranzas, Inc. + Queens tower C17 + Yokohama Kanagawa-ken 220-6217 + JP + +BC-34-00 (hex) IEEE Registration Authority +BC3400 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +A4-71-74 (hex) HUAWEI TECHNOLOGIES CO.,LTD +A47174 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +F4-CB-52 (hex) HUAWEI TECHNOLOGIES CO.,LTD +F4CB52 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +B8-08-D7 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B808D7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +94-61-1E (hex) Wata Electronics Co.,Ltd. +94611E (base 16) Wata Electronics Co.,Ltd. + No 142,South Tanshen Road, Tanzhou Town,Zhongshan City,Guangdong,China + Zhongshan Guangdong 528467 + CN + +58-52-8A (hex) Mitsubishi Electric Corporation +58528A (base 16) Mitsubishi Electric Corporation + 2-7-3 Marunouchi Chiyoda-ku + Tokyo 100-8310 + JP + +68-07-15 (hex) Intel Corporate +680715 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +3C-B6-B7 (hex) vivo Mobile Communication Co., Ltd. +3CB6B7 (base 16) vivo Mobile Communication Co., Ltd. #283,BBK Road Wusha,Chang'An DongGuan City,Guangdong 523860 CN @@ -63305,12 +63830,6 @@ B8E779 (base 16) 9Solutions Oy Oulu 90590 FI -28-F3-66 (hex) Shenzhen Bilian electronic CO.,LTD -28F366 (base 16) Shenzhen Bilian electronic CO.,LTD - NO 268,Fuqian Rd,Jutang Community,Guanlan town , LongHua new district,Shenzhen,518110,China. - Shenzhen 518110 - CN - E0-A3-AC (hex) HUAWEI TECHNOLOGIES CO.,LTD E0A3AC (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -64259,18 +64778,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 @@ -64745,18 +65252,6 @@ E0757D (base 16) Motorola Mobility LLC, a Lenovo Company Kulim Kedah 09000 MY -4C-17-EB (hex) Sagemcom Broadband SAS -4C17EB (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison Cedex Hauts de Seine 92848 - FR - -CC-33-BB (hex) Sagemcom Broadband SAS -CC33BB (base 16) Sagemcom Broadband SAS - 250 ROUTE DE L'EMPEREUR - RUEIL MALMAISON CEDEX Choisissez l'état / la province 92848 - FR - 3C-A3-48 (hex) vivo Mobile Communication Co., Ltd. 3CA348 (base 16) vivo Mobile Communication Co., Ltd. #283,BBK Road @@ -64889,12 +65384,6 @@ D86CE9 (base 16) Sagemcom Broadband SAS RUEIL MALMAISON CEDEX Hauts de Seine 92848 FR -E8-F1-B0 (hex) Sagemcom Broadband SAS -E8F1B0 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - RUEIL MALMAISON CEDEX Hauts de Seine 92848 - FR - 00-15-E9 (hex) D-Link Corporation 0015E9 (base 16) D-Link Corporation 2F, No.233-2, Pao-Chiao Road @@ -66827,12 +67316,6 @@ EC64E7 (base 16) MOCACARE Corporation Wuhan Hubei 430074 CN -84-A4-23 (hex) Sagemcom Broadband SAS -84A423 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 98-F4-28 (hex) zte corporation 98F428 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China @@ -67001,12 +67484,6 @@ ACEE9E (base 16) Samsung Electronics Co.,Ltd Tokyo - 162-0067 -5C-CF-7F (hex) Espressif Inc. -5CCF7F (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - B8-57-D8 (hex) Samsung Electronics Co.,Ltd B857D8 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong @@ -77855,12 +78332,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 @@ -82007,12 +82478,6 @@ CC0080 (base 16) BETTINI SRL Taichung Taiwan 403, R.O.C. TW -00-03-74 (hex) Control Microsystems -000374 (base 16) Control Microsystems - 48 Steacie Drive - Ottawa Ontario K2K 2A9 - CA - 00-03-76 (hex) Graphtec Technology, Inc. 000376 (base 16) Graphtec Technology, Inc. 45 Parker, Suite A @@ -82361,12 +82826,6 @@ CC0080 (base 16) BETTINI SRL Lexington MA 02421 US -00-02-31 (hex) Ingersoll-Rand -000231 (base 16) Ingersoll-Rand - 1467 Route 31 South - Annandale NJ 08801 - US - 00-02-34 (hex) Imperial Technology, Inc. 000234 (base 16) Imperial Technology, Inc. 2305 Utah Avenue @@ -86537,12 +86996,6 @@ D4A23D (base 16) New H3C Technologies Co., Ltd Hangzhou Zhejiang 310052 CN -D8-13-2A (hex) Espressif Inc. -D8132A (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 9C-1E-CE (hex) ALT Co., Ltd. 9C1ECE (base 16) ALT Co., Ltd. #1201, 8, Seongnam-dearo 331beon-gil @@ -86942,12 +87395,6 @@ A0E025 (base 16) Provision-ISR Kfar Saba 4464310 IL -9C-24-72 (hex) Sagemcom Broadband SAS -9C2472 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 80-85-44 (hex) Intelbras 808544 (base 16) Intelbras BR 101, km 210, S/N° @@ -87449,12 +87896,6 @@ D07602 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD Piscataway NJ 08554 US -74-39-89 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -743989 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 28-39-84 (hex) Qidi Technology (shanghai) Co.,Ltd. 283984 (base 16) Qidi Technology (shanghai) Co.,Ltd. Building C, No. 888 Huanhu West Second Road, Pudong New District, Shanghai City, China @@ -87767,12 +88208,6 @@ CCB777 (base 16) zte corporation shenzhen guangdong 518057 CN -9C-9E-6E (hex) Espressif Inc. -9C9E6E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 30-89-A6 (hex) HUAWEI TECHNOLOGIES CO.,LTD 3089A6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -87839,18 +88274,6 @@ B0B867 (base 16) Hewlett Packard Enterprise Roseville CA 95747 US -DC-92-72 (hex) Sagemcom Broadband SAS -DC9272 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -18-0C-7A (hex) Sagemcom Broadband SAS -180C7A (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 00-FD-45 (hex) Hewlett Packard Enterprise 00FD45 (base 16) Hewlett Packard Enterprise 8000 Foothills Blvd. @@ -88103,12 +88526,6 @@ C07AD6 (base 16) Samsung Electronics Co.,Ltd Cambridge Cambridgeshire CB23 6DP GB -78-EE-4C (hex) Espressif Inc. -78EE4C (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 78-E6-1C (hex) Honor Device Co., Ltd. 78E61C (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 @@ -88337,12 +88754,6 @@ C01C6A (base 16) Google, Inc. Nanjing Jiangsu 211800 CN -F0-24-F9 (hex) Espressif Inc. -F024F9 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 68-CC-BA (hex) Dense Air Networks US LLC 68CCBA (base 16) Dense Air Networks US LLC 825 S. Waukegan Rd, A8 #223 @@ -88475,12 +88886,6 @@ C41375 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. shenzhen guangdong 518057 CN -8C-BF-EA (hex) Espressif Inc. -8CBFEA (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - B4-1D-C4 (hex) HUAWEI TECHNOLOGIES CO.,LTD B41DC4 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -88523,12 +88928,6 @@ C4600A (base 16) Huaqin Technology Co.LTD Shanghai  Pudong 201203 CN -00-F8-CC (hex) Sagemcom Broadband SAS -00F8CC (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 14-7E-19 (hex) Hewlett Packard Enterprise 147E19 (base 16) Hewlett Packard Enterprise 6280 America Center Dr @@ -88901,12 +89300,6 @@ B88EB0 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd Lehi UT 84043 US -C0-A3-C7 (hex) Telink Micro LLC -C0A3C7 (base 16) Telink Micro LLC - 2975 Scott Blvd #120 - Santa Clara 95054 - US - FC-4D-6A (hex) Silicon Laboratories FC4D6A (base 16) Silicon Laboratories 400 West Cesar Chavez @@ -88925,12 +89318,6 @@ E826CF (base 16) Shenzhen Jingxun Technology Co., Ltd. Shenzhen 518071 CN -F4-65-0B (hex) Espressif Inc. -F4650B (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - B4-16-78 (hex) Juniper Networks B41678 (base 16) Juniper Networks 1133 Innovation Way @@ -89141,12 +89528,6 @@ E86EAD (base 16) Guangzhou Gizwits loT Technology Co.,Ltd Spring TX 77389 US -B8-8C-2B (hex) Sagemcom Broadband SAS -B88C2B (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 04-E3-C8 (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD 04E3C8 (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD 19-22# Building, Star-net Science Plaza, Juyuanzhou, @@ -89756,12 +90137,6 @@ F8916F (base 16) Texas Instruments Shenzhen Guangdong 518100 CN -38-18-2B (hex) Espressif Inc. -38182B (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 28-4D-92 (hex) Luminator Technology Group Global LLC 284D92 (base 16) Luminator Technology Group Global LLC 900 Klein Road @@ -90083,18 +90458,6 @@ A0EEEE (base 16) CIG SHANGHAI CO LTD Marin-Epagnier Neuchatel 2074 CH -2C-FB-0F (hex) Sagemcom Broadband SAS -2CFB0F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -BC-D5-ED (hex) Sagemcom Broadband SAS -BCD5ED (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 94-E7-F3 (hex) HUAWEI TECHNOLOGIES CO.,LTD 94E7F3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -90359,12 +90722,6 @@ C02242 (base 16) Chauvet Shenzhen 518110 CN -10-20-BA (hex) Espressif Inc. -1020BA (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 8C-86-DD (hex) TP-Link Systems Inc. 8C86DD (base 16) TP-Link Systems Inc. 10 Mauchly @@ -90377,12 +90734,6 @@ ECF37A (base 16) Forecr OU Tallinn 10141 EE -B8-F8-62 (hex) Espressif Inc. -B8F862 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 30-BD-FE (hex) Sichuan Tianyi Comheart Telecom Co.,LTD 30BDFE (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County @@ -90461,12 +90812,6 @@ D4808B (base 16) Seiko Epson Corporation Matsumoto-shi Nagano-ken 399-8702 JP -8C-9A-8F (hex) Sagemcom Broadband SAS -8C9A8F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 10-66-6A (hex) Zabbly 10666A (base 16) Zabbly 24 Saxby S @@ -90740,18 +91085,6 @@ D84567 (base 16) TECNO MOBILE LIMITED Hong Kong Hong Kong 999077 HK -4C-C3-82 (hex) Espressif Inc. -4CC382 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -10-B4-1D (hex) Espressif Inc. -10B41D (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - B4-D3-1A (hex) LYSORA TECHNOLOGY INC. B4D31A (base 16) LYSORA TECHNOLOGY INC. 8 The Green, Ste A, ,Kent Country,D @@ -90836,6 +91169,18 @@ A4AD9E (base 16) NEXAIOT Shenzhen Guangdong 518057 CN +94-EF-50 (hex) TP-Link Systems Inc. +94EF50 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +90-3F-C3 (hex) Huawei Device Co., Ltd. +903FC3 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + A8-0C-CA (hex) Shenzhen Sundray Technologies company Limited A80CCA (base 16) Shenzhen Sundray Technologies company Limited 6th Floor,Block A1, Nanshan iPark, No.1001 XueYuan Road, Nanshan District @@ -90854,24 +91199,12 @@ A80CCA (base 16) Shenzhen Sundray Technologies company Limited Shenzhen Guangdong 518057 CN -90-3F-C3 (hex) Huawei Device Co., Ltd. -903FC3 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - C4-49-3E (hex) Motorola Mobility LLC, a Lenovo Company C4493E (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza Chicago IL 60654 US -94-EF-50 (hex) TP-Link Systems Inc. -94EF50 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US - FC-A2-DF (hex) IEEE Registration Authority FCA2DF (base 16) IEEE Registration Authority 445 Hoes Lane @@ -90902,78 +91235,60 @@ CC6200 (base 16) Honor Device Co., Ltd. Dongguan 523808 CN -7C-C8-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD -7CC882 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +18-CE-DF (hex) Quectel Wireless Solutions Co.,Ltd. +18CEDF (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN -3C-A6-2F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -3CA62F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +00-33-7A (hex) Tuya Smart Inc. +00337A (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US + +C0-54-4D (hex) Nokia Shanghai Bell Co., Ltd. +C0544D (base 16) Nokia Shanghai Bell Co., Ltd. + No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai + Shanghai 201206 + CN + +CC-CE-1E (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +CCCE1E (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -3C-37-12 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -3C3712 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +7C-FF-4D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +7CFF4D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -0C-72-74 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -0C7274 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +3C-A6-2F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +3CA62F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -04-B4-FE (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -04B4FE (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +3C-37-12 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +3C3712 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -C0-54-4D (hex) Nokia Shanghai Bell Co., Ltd. -C0544D (base 16) Nokia Shanghai Bell Co., Ltd. - No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai - Shanghai 201206 - CN - -CC-CE-1E (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -CCCE1E (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +0C-72-74 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +0C7274 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -7C-FF-4D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -7CFF4D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +04-B4-FE (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +04B4FE (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -60-57-7D (hex) eero inc. -60577D (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -AC-EC-85 (hex) eero inc. -ACEC85 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -0C-1C-1A (hex) eero inc. -0C1C1A (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -64-C2-69 (hex) eero inc. -64C269 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - 74-B6-B6 (hex) eero inc. 74B6B6 (base 16) eero inc. 660 3rd Street @@ -91010,18 +91325,12 @@ F8BBBF (base 16) eero inc. San Francisco CA 94107 US -18-CE-DF (hex) Quectel Wireless Solutions Co.,Ltd. -18CEDF (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 +7C-C8-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD +7CC882 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -00-33-7A (hex) Tuya Smart Inc. -00337A (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 - US - 48-1F-66 (hex) China Mobile Group Device Co.,Ltd. 481F66 (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -91082,6 +91391,30 @@ DC152D (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN +60-57-7D (hex) eero inc. +60577D (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +AC-EC-85 (hex) eero inc. +ACEC85 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +0C-1C-1A (hex) eero inc. +0C1C1A (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +64-C2-69 (hex) eero inc. +64C269 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + 70-93-C1 (hex) eero inc. 7093C1 (base 16) eero inc. 660 3rd Street @@ -91106,12 +91439,6 @@ DC152D (base 16) China Mobile Group Device Co.,Ltd. San Francisco CA 94107 US -54-92-6A (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -54926A (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN - 0C-93-A5 (hex) eero inc. 0C93A5 (base 16) eero inc. 660 3rd Street @@ -91154,12 +91481,6 @@ D83EEF (base 16) COOSEA GROUP (HK) COMPANY LIMITED Hong Kong 999077 CN -70-7D-A1 (hex) Sagemcom Broadband SAS -707DA1 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 04-58-5D (hex) IEEE Registration Authority 04585D (base 16) IEEE Registration Authority 445 Hoes Lane @@ -91178,11 +91499,11 @@ C4864F (base 16) Beijing BitIntelligence Information Technology Co. Ltd. Beijing Beijing 100080 CN -E0-E6-E3 (hex) TeamF1 Networks Pvt Limited -E0E6E3 (base 16) TeamF1 Networks Pvt Limited - Ascendas IT Park, Capella Block, Floor #2, Plot No: 17, Software Units Layout, Madhapur - Hyderabad Telangana 500081 - IN +54-92-6A (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +54926A (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN C8-7F-2B (hex) INGRAM MICRO SERVICES C87F2B (base 16) INGRAM MICRO SERVICES @@ -91214,6 +91535,12 @@ C87F2B (base 16) INGRAM MICRO SERVICES San Francisco 94158 US +E0-E6-E3 (hex) TeamF1 Networks Pvt Limited +E0E6E3 (base 16) TeamF1 Networks Pvt Limited + Ascendas IT Park, Capella Block, Floor #2, Plot No: 17, Software Units Layout, Madhapur + Hyderabad Telangana 500081 + IN + 34-FA-1C (hex) Beijing Xiaomi Mobile Software Co., Ltd 34FA1C (base 16) Beijing Xiaomi Mobile Software Co., Ltd The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District @@ -91226,42 +91553,18 @@ C87F2B (base 16) INGRAM MICRO SERVICES Beijing 100029 CN -44-35-B9 (hex) NetComm Wireless Pty Ltd -4435B9 (base 16) NetComm Wireless Pty Ltd - Level 1, 18-20 Orion Road - Sydney NSW 2066 - AU - 4C-CF-7C (hex) HP Inc. 4CCF7C (base 16) HP Inc. 10300 Energy Dr Spring TX 77389 US -DC-BB-3D (hex) Extreme Networks Headquarters -DCBB3D (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville 27560 - US - 20-3A-0C (hex) eero inc. 203A0C (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -FC-B2-14 (hex) Apple, Inc. -FCB214 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -2C-95-20 (hex) Apple, Inc. -2C9520 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - 00-15-FF (hex) Inseego Wireless, Inc 0015FF (base 16) Inseego Wireless, Inc 9710 Scranton Rd., Suite 200 @@ -91304,30 +91607,30 @@ FC9F2A (base 16) Zyxel Communications Corporation Hsichu Taiwan 300 TW +44-35-B9 (hex) NetComm Wireless Pty Ltd +4435B9 (base 16) NetComm Wireless Pty Ltd + Level 1, 18-20 Orion Road + Sydney NSW 2066 + AU + 64-75-DA (hex) Arcadyan Corporation 6475DA (base 16) Arcadyan Corporation No.8, Sec.2, Guangfu Rd. Hsinchu City Hsinchu 30071 TW +DC-BB-3D (hex) Extreme Networks Headquarters +DCBB3D (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville 27560 + US + B0-CC-CE (hex) IEEE Registration Authority B0CCCE (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -DC-B4-D9 (hex) Espressif Inc. -DCB4D9 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -98-32-68 (hex) Silicon Laboratories -983268 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - B8-9C-13 (hex) Alps Alpine B89C13 (base 16) Alps Alpine 20-1, Yoshima Industrial Park @@ -91358,22 +91661,34 @@ A81F79 (base 16) Yingling Innovations Pte. Ltd. Midview 573970 SG +FC-B2-14 (hex) Apple, Inc. +FCB214 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +2C-95-20 (hex) Apple, Inc. +2C9520 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + 80-23-95 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH 802395 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -04-8F-00 (hex) Rong-Paisa Electronics Co., Ltd. -048F00 (base 16) Rong-Paisa Electronics Co., Ltd. - Carrera 43f #14A-112 - Medellin Antioquia 050021 - CO +98-32-68 (hex) Silicon Laboratories +983268 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -8C-5C-53 (hex) AltoBeam Inc. -8C5C53 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 +DC-B4-D9 (hex) Espressif Inc. +DCB4D9 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN EC-7C-BA (hex) Hewlett Packard Enterprise @@ -91388,17 +91703,17 @@ EC7CBA (base 16) Hewlett Packard Enterprise Beckwith Knowle Harrogate HG3 1UF GB -50-2E-91 (hex) AzureWave Technology Inc. -502E91 (base 16) AzureWave Technology Inc. - 8F., No. 94, Baozhong Rd. - New Taipei City Taiwan 231 - TW +04-8F-00 (hex) Rong-Paisa Electronics Co., Ltd. +048F00 (base 16) Rong-Paisa Electronics Co., Ltd. + Carrera 43f #14A-112 + Medellin Antioquia 050021 + CO -E4-9F-7D (hex) Samsung Electronics Co.,Ltd -E49F7D (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +8C-5C-53 (hex) AltoBeam Inc. +8C5C53 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN E8-BF-E1 (hex) Intel Corporate E8BFE1 (base 16) Intel Corporate @@ -91442,6 +91757,12 @@ B43A96 (base 16) Arista Networks Fitzroy Victoria 3065 AU +E4-9F-7D (hex) Samsung Electronics Co.,Ltd +E49F7D (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + 60-98-49 (hex) Nokia Solutions and Networks India Private Limited 609849 (base 16) Nokia Solutions and Networks India Private Limited Radiance Ivy terrace, Block 4, 9R, Egattur, Chennai @@ -91454,6 +91775,12 @@ B43A96 (base 16) Arista Networks Chennai TamilNadu 600130 IN +50-2E-91 (hex) AzureWave Technology Inc. +502E91 (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW + 68-F7-D8 (hex) Microsoft Corporation 68F7D8 (base 16) Microsoft Corporation One Microsoft Way @@ -91472,10 +91799,10 @@ C0CDD6 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -8C-7A-B3 (hex) Guangzhou Shiyuan Electronic Technology Company Limited -8C7AB3 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 +88-B9-51 (hex) Xiaomi Communications Co Ltd +88B951 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN E8-CD-15 (hex) Vantiva USA LLC @@ -91496,10 +91823,10 @@ E8CD15 (base 16) Vantiva USA LLC Shanghai Shanghai 201203 CN -88-B9-51 (hex) Xiaomi Communications Co Ltd -88B951 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +8C-7A-B3 (hex) Guangzhou Shiyuan Electronic Technology Company Limited +8C7AB3 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 CN 74-24-CA (hex) Guangzhou Shiyuan Electronic Technology Company Limited @@ -91514,6 +91841,12 @@ E8CD15 (base 16) Vantiva USA LLC Sunnyvale CA 94089 US +EC-31-11 (hex) Sichuan AI-Link Technology Co., Ltd. +EC3111 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 + CN + 00-6A-5E (hex) IEEE Registration Authority 006A5E (base 16) IEEE Registration Authority 445 Hoes Lane @@ -91538,12 +91871,6 @@ FC50D6 (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN -EC-31-11 (hex) Sichuan AI-Link Technology Co., Ltd. -EC3111 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 - CN - D0-B1-CA (hex) Shenzhen Skyworth Digital Technology CO., Ltd D0B1CA (base 16) Shenzhen Skyworth Digital Technology CO., Ltd 4F,Block A, Skyworth?Building, @@ -91562,6 +91889,30 @@ D801EB (base 16) Infinity Electronics Ltd Stockholm SE-164 80 SE +28-35-3A (hex) HUAWEI TECHNOLOGIES CO.,LTD +28353A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +60-30-B3 (hex) HUAWEI TECHNOLOGIES CO.,LTD +6030B3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +E8-7E-1C (hex) HUAWEI TECHNOLOGIES CO.,LTD +E87E1C (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +C0-15-1B (hex) Sony Interactive Entertainment Inc. +C0151B (base 16) Sony Interactive Entertainment Inc. + 1-7-1 Konan + Minato-ku Tokyo 108-0075 + JP + D0-68-27 (hex) eero inc. D06827 (base 16) eero inc. 660 3rd Street @@ -91580,18 +91931,6 @@ BC4529 (base 16) zte corporation shenzhen guangdong 518057 CN -E8-7E-1C (hex) HUAWEI TECHNOLOGIES CO.,LTD -E87E1C (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -C0-15-1B (hex) Sony Interactive Entertainment Inc. -C0151B (base 16) Sony Interactive Entertainment Inc. - 1-7-1 Konan - Minato-ku Tokyo 108-0075 - JP - 9C-65-EE (hex) Zhone Technologies, Inc. 9C65EE (base 16) Zhone Technologies, Inc. DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu @@ -91604,36 +91943,30 @@ CC6C52 (base 16) Zhone Technologies, Inc. Plano TX 75024 US -28-35-3A (hex) HUAWEI TECHNOLOGIES CO.,LTD -28353A (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -60-30-B3 (hex) HUAWEI TECHNOLOGIES CO.,LTD -6030B3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - 8C-73-DA (hex) Silicon Laboratories 8C73DA (base 16) Silicon Laboratories 400 West Cesar Chavez Austin TX 78701 US -D4-E9-F4 (hex) Espressif Inc. -D4E9F4 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +34-55-06 (hex) GUANGDONG GENIUS TECHNOLOGY CO., LTD. +345506 (base 16) GUANGDONG GENIUS TECHNOLOGY CO., LTD. + No.168, Middle Road Of East Gate + Xiaobian Community Chang'an Town 523851 CN -1C-DB-D4 (hex) Espressif Inc. -1CDBD4 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +D4-7A-97 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +D47A97 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 CN +6C-1A-EA (hex) Texas Instruments +6C1AEA (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + 68-44-06 (hex) Texas Instruments 684406 (base 16) Texas Instruments 12500 TI Blvd @@ -91664,23 +91997,17 @@ E4B16C (base 16) Apple, Inc. Cupertino CA 95014 US -BC-5A-34 (hex) New H3C Technologies Co., Ltd -BC5A34 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN - 28-D5-B1 (hex) Apple, Inc. 28D5B1 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -80-D1-CE (hex) Apple, Inc. -80D1CE (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +BC-5A-34 (hex) New H3C Technologies Co., Ltd +BC5A34 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN 2C-CC-7A (hex) AltoBeam Inc. 2CCC7A (base 16) AltoBeam Inc. @@ -91688,18 +92015,12 @@ BC5A34 (base 16) New H3C Technologies Co., Ltd Beijing Beijing 100083 CN -6C-1A-EA (hex) Texas Instruments -6C1AEA (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +80-D1-CE (hex) Apple, Inc. +80D1CE (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -34-55-06 (hex) GUANGDONG GENIUS TECHNOLOGY CO., LTD. -345506 (base 16) GUANGDONG GENIUS TECHNOLOGY CO., LTD. - No.168, Middle Road Of East Gate - Xiaobian Community Chang'an Town 523851 - CN - F0-68-E3 (hex) AzureWave Technology Inc. F068E3 (base 16) AzureWave Technology Inc. 8F., No. 94, Baozhong Rd. @@ -91712,24 +92033,6 @@ F068E3 (base 16) AzureWave Technology Inc. Cupertino CA 95014 US -14-D5-C6 (hex) slash dev slash agents, inc -14D5C6 (base 16) slash dev slash agents, inc - 334 Brannan St, Floor 2 - San Francisco CA 94107 - US - -D8-85-AC (hex) Espressif Inc. -D885AC (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -D4-7A-97 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -D47A97 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 - CN - 4C-8E-19 (hex) Xiaomi Communications Co Ltd 4C8E19 (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road @@ -91742,12 +92045,30 @@ D47A97 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. Shenzhen 518102 CN +14-D5-C6 (hex) slash dev slash agents, inc +14D5C6 (base 16) slash dev slash agents, inc + 334 Brannan St, Floor 2 + San Francisco CA 94107 + US + 44-38-F3 (hex) EM Microelectronic 4438F3 (base 16) EM Microelectronic Rue des Sors 3 Marin-Epagnier Neuchatel 2074 CH +1C-D1-1A (hex) Fortinet, Inc. +1CD11A (base 16) Fortinet, Inc. + 899 Kifer Road + Sunnyvale 94086 + US + +50-51-4F (hex) Netbeam Technology Limited +50514F (base 16) Netbeam Technology Limited + Hudsun Chambers, P.O.Box 986, Road Town + Tortola VG1110 + VG + F8-D0-0E (hex) Vantiva USA LLC F8D00E (base 16) Vantiva USA LLC 4855 Peachtree Industrial Blvd, Suite 200 @@ -91766,18 +92087,6 @@ E4BD96 (base 16) Chengdu Hurray Data Technology co., Ltd. Chengdu 610000 CN -84-00-55 (hex) VusionGroup -840055 (base 16) VusionGroup - Kalsdorfer Straße 12 - Fernitz-Mellach Steiermark 8072 - AT - -14-B9-03 (hex) HUAWEI TECHNOLOGIES CO.,LTD -14B903 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - 18-69-0A (hex) Silicon Laboratories 18690A (base 16) Silicon Laboratories 400 West Cesar Chavez @@ -91796,22 +92105,16 @@ A46B40 (base 16) CHINA DRAGON TECHNOLOGY LIMITED Zhongshan Guangdong 528400 CN -1C-D1-1A (hex) Fortinet, Inc. -1CD11A (base 16) Fortinet, Inc. - 899 Kifer Road - Sunnyvale 94086 - US - -50-51-4F (hex) Netbeam Technology Limited -50514F (base 16) Netbeam Technology Limited - Hudsun Chambers, P.O.Box 986, Road Town - Tortola VG1110 - VG +84-00-55 (hex) VusionGroup +840055 (base 16) VusionGroup + Kalsdorfer Straße 12 + Fernitz-Mellach Steiermark 8072 + AT -60-C4-18 (hex) TPV Display Technology (Xiamen) Co.,Ltd. -60C418 (base 16) TPV Display Technology (Xiamen) Co.,Ltd. - No.1, Xianghai Road, Xiamen Torch Hi-Tech Industrial Development Zone - XM Fujian 361101 +14-B9-03 (hex) HUAWEI TECHNOLOGIES CO.,LTD +14B903 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN 50-0B-23 (hex) HUAWEI TECHNOLOGIES CO.,LTD @@ -91826,12 +92129,30 @@ C8B78A (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN +60-C4-18 (hex) TPV Display Technology (Xiamen) Co.,Ltd. +60C418 (base 16) TPV Display Technology (Xiamen) Co.,Ltd. + No.1, Xianghai Road, Xiamen Torch Hi-Tech Industrial Development Zone + XM Fujian 361101 + CN + +B0-2E-BA (hex) Earda Technologies co Ltd +B02EBA (base 16) Earda Technologies co Ltd + Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District + Guangzhou Guangdong 511455 + CN + 0C-3D-5E (hex) Nanjing Qinheng Microelectronics Co., Ltd. 0C3D5E (base 16) Nanjing Qinheng Microelectronics Co., Ltd. No.18, Ningshuang Road Nanjing Jiangsu 210012 CN +B8-CE-ED (hex) Broadcom +B8CEED (base 16) Broadcom + 1320 Ridder Park + San Jose CA 95131 + US + CC-0D-CB (hex) Microsoft Corporation CC0DCB (base 16) Microsoft Corporation One Microsoft Way @@ -91844,18 +92165,18 @@ CC0DCB (base 16) Microsoft Corporation Mountain View CA 94043 US -B8-CE-ED (hex) Broadcom -B8CEED (base 16) Broadcom - 1320 Ridder Park - San Jose CA 95131 - US - -B0-2E-BA (hex) Earda Technologies co Ltd -B02EBA (base 16) Earda Technologies co Ltd - Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District - Guangzhou Guangdong 511455 +EC-97-E0 (hex) Hangzhou Ezviz Software Co.,Ltd. +EC97E0 (base 16) Hangzhou Ezviz Software Co.,Ltd. + 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District + Hangzhou Zhejiang 310051 CN +60-5E-65 (hex) Mellanox Technologies, Inc. +605E65 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + 54-BA-D9 (hex) Intelbras 54BAD9 (base 16) Intelbras BR 101, km 210, S/N° @@ -91868,48 +92189,24 @@ 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 shenzhen guangdong 518057 CN -A4-F0-0F (hex) Espressif Inc. -A4F00F (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - F0-92-58 (hex) China Electronics Cloud Computing Technology Co., Ltd F09258 (base 16) China Electronics Cloud Computing Technology Co., Ltd N3013,3F,N R&D building, A.I. Technology Park, Economic and Technological Development Zone Wuhan Hubei 430090 CN -EC-97-E0 (hex) Hangzhou Ezviz Software Co.,Ltd. -EC97E0 (base 16) Hangzhou Ezviz Software Co.,Ltd. - 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District - Hangzhou Zhejiang 310051 +2C-8D-48 (hex) Smart Innovation LLC +2C8D48 (base 16) Smart Innovation LLC + 7F,Tower B,Jianxing + ShenZhen GuangZhou 518055 CN -60-5E-65 (hex) Mellanox Technologies, Inc. -605E65 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - -0C-C5-74 (hex) FRITZ! Technology GmbH -0CC574 (base 16) FRITZ! Technology GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - 38-8C-EF (hex) Samsung Electronics Co.,Ltd 388CEF (base 16) Samsung Electronics Co.,Ltd 129, Samsung-ro, Youngtongl-Gu @@ -91922,22 +92219,34 @@ EC97E0 (base 16) Hangzhou Ezviz Software Co.,Ltd. shenzhen guangdong 518100 CN +0C-C5-74 (hex) FRITZ! Technology GmbH +0CC574 (base 16) FRITZ! Technology GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +84-70-03 (hex) Axon Networks Inc. +847003 (base 16) Axon Networks Inc. + 15420 Laguna Canyon rd. + Irvine CA 92618 + US + A0-FF-FD (hex) HMD Global Oy A0FFFD (base 16) HMD Global Oy Bertel Jungin aukio 9 Espoo 02600 FI -2C-8D-48 (hex) Smart Innovation LLC -2C8D48 (base 16) Smart Innovation LLC - 7F,Tower B,Jianxing - ShenZhen GuangZhou 518055 - CN +30-7A-D2 (hex) Apple, Inc. +307AD2 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -84-70-03 (hex) Axon Networks Inc. -847003 (base 16) Axon Networks Inc. - 15420 Laguna Canyon rd. - Irvine CA 92618 +D4-2D-CC (hex) Apple, Inc. +D42DCC (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US 04-2E-C1 (hex) Apple, Inc. @@ -91964,32 +92273,29 @@ B45575 (base 16) Apple, Inc. Cupertino CA 95014 US -A0-E3-90 (hex) Apple, Inc. -A0E390 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - 6C-E4-A4 (hex) Silicon Laboratories 6CE4A4 (base 16) Silicon Laboratories 400 West Cesar Chavez Austin TX 78701 US +90-3F-86 (hex) New H3C Technologies Co., Ltd +903F86 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN + +6C-88-5F (hex) Private +6C885F (base 16) Private + 60-D4-AF (hex) Honor Device Co., Ltd. 60D4AF (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 -30-7A-D2 (hex) Apple, Inc. -307AD2 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -D4-2D-CC (hex) Apple, Inc. -D42DCC (base 16) Apple, Inc. +A0-E3-90 (hex) Apple, Inc. +A0E390 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US @@ -92018,15 +92324,6 @@ D42DCC (base 16) Apple, Inc. Dongguan 523808 CN -90-3F-86 (hex) New H3C Technologies Co., Ltd -903F86 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN - -6C-88-5F (hex) Private -6C885F (base 16) Private - 6C-7F-49 (hex) Huawei Device Co., Ltd. 6C7F49 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -92045,14 +92342,20 @@ D42DCC (base 16) Apple, Inc. Nan-Tou Taiwan 54261 TW -28-24-FF (hex) WNC Corporation -2824FF (base 16) WNC Corporation +B8-9F-09 (hex) WNC Corporation +B89F09 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW -B8-9F-09 (hex) WNC Corporation -B89F09 (base 16) WNC Corporation +88-5A-85 (hex) WNC Corporation +885A85 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +28-24-FF (hex) WNC Corporation +2824FF (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW @@ -92081,12 +92384,6 @@ A8A092 (base 16) CHINA DRAGON TECHNOLOGY LIMITED Kanata Ontario K2K 2E6 CA -5C-BF-03 (hex) EMOCO -5CBF03 (base 16) EMOCO - Valhallavägen 5 - Lidingö 18151 - SE - EC-9E-68 (hex) Anhui Taoyun Technology Co., Ltd EC9E68 (base 16) Anhui Taoyun Technology Co., Ltd 6/F and 23/F, Scientific Research Building, Building 2, Zone A, China Sound Valley, No. 3333, Xiyou Road, High tech Zone Hefei Anhui @@ -92105,29 +92402,17 @@ A8CEE7 (base 16) Fiberhome Telecommunication Technologies Co.,LTD Wuhan Hubei 430074 CN -B8-82-F2 (hex) WNC Corporation -B882F2 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - -88-5A-85 (hex) WNC Corporation -885A85 (base 16) WNC Corporation +B8-82-F2 (hex) WNC Corporation +B882F2 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW -80-13-16 (hex) Intel Corporate -801316 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -2C-EA-FC (hex) Intel Corporate -2CEAFC (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +5C-BF-03 (hex) EMOCO +5CBF03 (base 16) EMOCO + Valhallavägen 5 + Lidingö 18151 + SE 04-24-05 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD 042405 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -92147,6 +92432,18 @@ D056F2 (base 16) BUFFALO.INC Nagoya Aichi Pref. 460-8315 JP +80-13-16 (hex) Intel Corporate +801316 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +2C-EA-FC (hex) Intel Corporate +2CEAFC (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + 74-F9-2C (hex) Ubiquiti Inc 74F92C (base 16) Ubiquiti Inc 685 Third Avenue, 27th Floor @@ -92165,12 +92462,6 @@ D056F2 (base 16) BUFFALO.INC Zoetermeer Zoetermeer 2712PN NL -30-4D-1F (hex) Amazon Technologies Inc. -304D1F (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - AC-F9-32 (hex) NXP Semiconductor (Tianjin) LTD. ACF932 (base 16) NXP Semiconductor (Tianjin) LTD. No.15 Xinghua Avenue, Xiqing Economic Development Area @@ -92195,6 +92486,12 @@ ACF932 (base 16) NXP Semiconductor (Tianjin) LTD. Austin TX 78701 US +30-4D-1F (hex) Amazon Technologies Inc. +304D1F (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + F0-4E-A4 (hex) HP Inc. F04EA4 (base 16) HP Inc. 10300 Energy Dr @@ -92213,48 +92510,18 @@ A8DD9F (base 16) Quectel Wireless Solutions Co.,Ltd. Shanghai 200233 CN -E0-72-A1 (hex) Espressif Inc. -E072A1 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 74-67-5F (hex) COMPAL INFORMATION(KUNSHAN)CO.,LTD. 74675F (base 16) COMPAL INFORMATION(KUNSHAN)CO.,LTD. No.25 , THE 3RD Street KUNSHAN EXPORT PROCESSING ZONE KUNSHAN SUZHOU 215300 CN -AC-A7-04 (hex) Espressif Inc. -ACA704 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 0C-BF-B4 (hex) IEEE Registration Authority 0CBFB4 (base 16) IEEE Registration Authority 445 Hoes Lane 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 - San Jose CA 95134 - US - -00-1B-2F (hex) NETGEAR -001B2F (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - 50-61-3F (hex) eero inc. 50613F (base 16) eero inc. 660 3rd Street @@ -92351,17 +92618,29 @@ E8FCAF (base 16) NETGEAR Kąty Wrocławskie dolnośląskie 55-080 PL -A4-2A-26 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -A42A26 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN +00-1F-33 (hex) NETGEAR +001F33 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US -74-08-AA (hex) Ruijie Networks Co.,LTD -7408AA (base 16) Ruijie Networks Co.,LTD - Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District - Fuzhou 35000 - CN +00-1B-2F (hex) NETGEAR +001B2F (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +38-33-C5 (hex) Microsoft Corporation +3833C5 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +90-1C-9E (hex) Alcatel-Lucent Enterprise +901C9E (base 16) Alcatel-Lucent Enterprise + 2000 Corporate Center Dr Suite A + Thousand Oaks 91320 + US 18-24-39 (hex) YIPPEE ELECTRONICS CP.,LIMITED 182439 (base 16) YIPPEE ELECTRONICS CP.,LIMITED @@ -92375,18 +92654,18 @@ A42A26 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Planegg 82152 DE -38-33-C5 (hex) Microsoft Corporation -3833C5 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US - 50-AB-29 (hex) Trackunit ApS 50AB29 (base 16) Trackunit ApS Gasvaerksvej 24, 4. sal Aalborg 9000 DK +A4-2A-26 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +A42A26 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN + EC-A7-8D (hex) Cisco Systems, Inc ECA78D (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -92411,11 +92690,11 @@ FC7288 (base 16) Cisco Systems, Inc Dongguan Guangdong 523860 CN -90-1C-9E (hex) Alcatel-Lucent Enterprise -901C9E (base 16) Alcatel-Lucent Enterprise - 2000 Corporate Center Dr Suite A - Thousand Oaks 91320 - US +74-08-AA (hex) Ruijie Networks Co.,LTD +7408AA (base 16) Ruijie Networks Co.,LTD + Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District + Fuzhou 35000 + CN 50-E0-F9 (hex) GE Vernova 50E0F9 (base 16) GE Vernova @@ -92465,22 +92744,16 @@ A0B53C (base 16) Vantiva Technologies Belgium Cedarburg WI 53012 US -24-19-A5 (hex) New H3C Technologies Co., Ltd -2419A5 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN - -6C-AF-AB (hex) UAB Teltonika Telematics -6CAFAB (base 16) UAB Teltonika Telematics - Saltoniskiu str. 9B-1 - Vilnius LT-08105 - LT +0C-88-32 (hex) Nokia Solutions and Networks India Private Limited +0C8832 (base 16) Nokia Solutions and Networks India Private Limited + Plot 45, Fathima NagarNemilicherry,Chrompet + Chennai Taminadu 600044 + IN -1C-8E-2A (hex) Apple, Inc. -1C8E2A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +94-3B-22 (hex) NETGEAR +943B22 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US 30-0E-43 (hex) Apple, Inc. @@ -92501,18 +92774,6 @@ A0B53C (base 16) Vantiva Technologies Belgium New Taipei City 238035 TW -0C-88-32 (hex) Nokia Solutions and Networks India Private Limited -0C8832 (base 16) Nokia Solutions and Networks India Private Limited - Plot 45, Fathima NagarNemilicherry,Chrompet - Chennai Taminadu 600044 - IN - -94-3B-22 (hex) NETGEAR -943B22 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - B8-A7-92 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD B8A792 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County @@ -92525,42 +92786,54 @@ C8E713 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. Nanjing Jiangsu 211800 CN +1C-8E-2A (hex) Apple, Inc. +1C8E2A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + 58-76-07 (hex) IEEE Registration Authority 587607 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US +24-19-A5 (hex) New H3C Technologies Co., Ltd +2419A5 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN + +6C-AF-AB (hex) UAB Teltonika Telematics +6CAFAB (base 16) UAB Teltonika Telematics + Saltoniskiu str. 9B-1 + Vilnius LT-08105 + LT + 54-83-BB (hex) Honda Motor Co., Ltd 5483BB (base 16) Honda Motor Co., Ltd Toranomon Alcea Tower, 2-2-3 Toranomon, Minato-ku, Tokyo 105-8404 JP -E0-96-E8 (hex) Fiberhome Telecommunication Technologies Co.,LTD -E096E8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN - A8-13-78 (hex) Nokia A81378 (base 16) Nokia 600 March Road Kanata Ontario K2K 2E6 CA -B4-64-15 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. -B46415 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. - 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District - Guangzhou Guangdong 510663 - CN - 1C-8E-E6 (hex) VTECH TELECOMMUNICATIONS LIMITED 1C8EE6 (base 16) VTECH TELECOMMUNICATIONS LIMITED BLOCK 01 23-24/F TAT PING INDUSTRIAL CENTRE 57 TING KOK ROAD TAI PONT DONG GUAN GUANG ZHOU 52300 CN +E0-96-E8 (hex) Fiberhome Telecommunication Technologies Co.,LTD +E096E8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN + 84-5B-0C (hex) eFAB P.S.A. 845B0C (base 16) eFAB P.S.A. al. Solidarości 129/131/197VATID: PL5272968735 @@ -92579,6 +92852,24 @@ F0C88B (base 16) Wyze Labs Inc BOTHELL WA 98021 US +34-02-9C (hex) D-Link Corporation +34029C (base 16) D-Link Corporation + No.289, Sinhu 3rd Rd., Neihu District, + Taipei City 114 + TW + +B4-64-15 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +B46415 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District + Guangzhou Guangdong 510663 + CN + +6C-77-F0 (hex) Huawei Device Co., Ltd. +6C77F0 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + 24-62-C6 (hex) Huawei Device Co., Ltd. 2462C6 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -92615,18 +92906,6 @@ B472D4 (base 16) zte corporation Guangzhou Guangdong 511455 CN -6C-77-F0 (hex) Huawei Device Co., Ltd. -6C77F0 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - -34-02-9C (hex) D-Link Corporation -34029C (base 16) D-Link Corporation - No.289, Sinhu 3rd Rd., Neihu District, - Taipei City 114 - TW - 5C-1B-17 (hex) Bosch Automotive Electronics India Pvt. Ltd. 5C1B17 (base 16) Bosch Automotive Electronics India Pvt. Ltd. Naganathapura @@ -92639,14 +92918,14 @@ B472D4 (base 16) zte corporation SHENZHEN Guangdong Province 518052 CN -A8-D1-62 (hex) Samsung Electronics Co.,Ltd -A8D162 (base 16) Samsung Electronics Co.,Ltd +78-60-89 (hex) Samsung Electronics Co.,Ltd +786089 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -4C-EB-B0 (hex) Samsung Electronics Co.,Ltd -4CEBB0 (base 16) Samsung Electronics Co.,Ltd +A8-D1-62 (hex) Samsung Electronics Co.,Ltd +A8D162 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR @@ -92657,14 +92936,8 @@ A8D162 (base 16) Samsung Electronics Co.,Ltd Beijing 100190 CN -8C-05-28 (hex) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD -8C0528 (base 16) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD - 6-8 Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China - Shenzhen 518052 - CN - -78-60-89 (hex) Samsung Electronics Co.,Ltd -786089 (base 16) Samsung Electronics Co.,Ltd +4C-EB-B0 (hex) Samsung Electronics Co.,Ltd +4CEBB0 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR @@ -92681,11 +92954,65 @@ FC5708 (base 16) Broadcom Limited Austin TX 78735 US -9C-28-BF (hex) AUMOVIO Czech Republic s.r.o. -9C28BF (base 16) AUMOVIO Czech Republic s.r.o. - Průmyslová 1851 - Brandýs nad Labem 250 01 - CZ +34-87-FB (hex) GTAI +3487FB (base 16) GTAI + Room 208, Building B11, Yantian Industrial Zone, Yantian Community, Xixiang Street, Bao 'an District, + Shenzhen Guangdong 518102 + CN + +8C-DD-30 (hex) Ruijie Networks Co.,LTD +8CDD30 (base 16) Ruijie Networks Co.,LTD + Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District + Fuzhou 35000 + CN + +60-83-DA (hex) Silicon Laboratories +6083DA (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +E0-72-91 (hex) Silicon Laboratories +E07291 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +AC-D3-FB (hex) Arycs Technologies Inc +ACD3FB (base 16) Arycs Technologies Inc + 718 University Ave Suite 200 + Los Gatos 95032 + US + +2C-9D-90 (hex) Mellanox Technologies, Inc. +2C9D90 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +E4-6D-AB (hex) Mellanox Technologies, Inc. +E46DAB (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +8C-05-28 (hex) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD +8C0528 (base 16) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD + 6-8 Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China + Shenzhen 518052 + CN + +90-74-AE (hex) AzureWave Technology Inc. +9074AE (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW + +E8-2D-79 (hex) AltoBeam Inc. +E82D79 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN 18-4C-AE (hex) AUMOVIO France S.A.S. 184CAE (base 16) AUMOVIO France S.A.S. @@ -92693,569 +93020,1808 @@ FC5708 (base 16) Broadcom Limited TOULOUSE 31100 FR -E8-2D-79 (hex) AltoBeam Inc. -E82D79 (base 16) AltoBeam Inc. +00-54-AF (hex) AUMOVIO Systems, Inc. +0054AF (base 16) AUMOVIO Systems, Inc. + 21440 W. Lake Cook Rd. + Deer Park IL 60010 + US + +20-AD-56 (hex) AUMOVIO Systems, Inc. +20AD56 (base 16) AUMOVIO Systems, Inc. + 21440 W. Lake Cook Rd. + Deer Park IL 60010 + US + +6C-81-66 (hex) Private +6C8166 (base 16) Private + +D0-EA-11 (hex) Routerboard.com +D0EA11 (base 16) Routerboard.com + Mikrotikls SIA + Riga Riga LV1009 + LV + +D8-FC-92 (hex) Tuya Smart Inc. +D8FC92 (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US + +B4-E2-5B (hex) HP Inc. +B4E25B (base 16) HP Inc. + 10300 Energy Dr + Spring TX 77389 + US + +DC-74-CE (hex) ITOCHU Techno-Solutions Corporation +DC74CE (base 16) ITOCHU Techno-Solutions Corporation + Kamiyacho Trust Tower, 4-1-1, Toranomon, Minato-ku, + Tokyo Tokyo 105-6950 + JP + +B8-51-1D (hex) TELECHIPS, INC +B8511D (base 16) TELECHIPS, INC + 27, Geumto-ro 80beon-gil, Sujeong-gu, + Seongnam-si, Gyeonggi-do, 13453 + KR + +10-03-CD (hex) Calix Inc. +1003CD (base 16) Calix Inc. + 2777 Orchard Pkwy + San Jose CA 95131 + US + +9C-28-BF (hex) AUMOVIO Czech Republic s.r.o. +9C28BF (base 16) AUMOVIO Czech Republic s.r.o. + Průmyslová 1851 + Brandýs nad Labem 250 01 + CZ + +0C-0F-D8 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED +0C0FD8 (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 + +14-4F-F0 (hex) NXP Semiconductors Taiwan Ltd. +144FF0 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW + +D4-13-68 (hex) Shenzhen Intellirocks Tech. Co. Ltd. +D41368 (base 16) Shenzhen Intellirocks Tech. Co. Ltd. + No. 3301, Block C, Section 1, Chuangzhi Yuncheng Building, Liuxian Avenue,Xili Community, Xili Street, Nanshan District + Shenzhen Guangdong 518000 + CN + +F4-70-18 (hex) Hangzhou Ezviz Software Co.,Ltd. +F47018 (base 16) Hangzhou Ezviz Software Co.,Ltd. + 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District + Hangzhou Zhejiang 310051 + CN + +E4-53-41 (hex) Apple, Inc. +E45341 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +68-A7-29 (hex) Apple, Inc. +68A729 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +74-29-17 (hex) Apple, Inc. +742917 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +4C-55-B2 (hex) Xiaomi Communications Co Ltd +4C55B2 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +A4-22-B6 (hex) Motorola Mobility LLC, a Lenovo Company +A422B6 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +CC-80-8F (hex) Apple, Inc. +CC808F (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +E0-BA-78 (hex) Apple, Inc. +E0BA78 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +98-2A-FD (hex) HUAWEI TECHNOLOGIES CO.,LTD +982AFD (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +2C-AE-46 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +2CAE46 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 + CN + +6C-77-42 (hex) zte corporation +6C7742 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +10-CD-54 (hex) HUAWEI TECHNOLOGIES CO.,LTD +10CD54 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +94-A2-5D (hex) HUAWEI TECHNOLOGIES CO.,LTD +94A25D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +B4-C3-D9 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B4C3D9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +54-26-18 (hex) HUAWEI TECHNOLOGIES CO.,LTD +542618 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +90-20-D7 (hex) Microsoft Corporation +9020D7 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +28-B2-0B (hex) NXP USA, Inc +28B20B (base 16) NXP USA, Inc + 6501 William Cannon Dr. - West + Austin TX 78735 + US + +80-AF-9F (hex) eero inc. +80AF9F (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +BC-9C-8D (hex) Ruckus Wireless +BC9C8D (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US + +64-70-84 (hex) AltoBeam Inc. +647084 (base 16) AltoBeam Inc. B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian Beijing Beijing 100083 CN -AC-D3-FB (hex) Arycs Technologies Inc -ACD3FB (base 16) Arycs Technologies Inc - 718 University Ave Suite 200 - Los Gatos 95032 +00-15-1E (hex) B&R Industrial Automation GmbH +00151E (base 16) B&R Industrial Automation GmbH + B&R Strasse 1 + Eggelsberg       5142 + AT + +80-2A-F6 (hex) Honor Device Co., Ltd. +802AF6 (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 + +00-A3-07 (hex) Honor Device Co., Ltd. +00A307 (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-D5-62 (hex) Huawei Device Co., Ltd. +64D562 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +60-95-F8 (hex) Arcadyan Corporation +6095F8 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW + +00-11-1E (hex) B&R Industrial Automation GmbH +00111E (base 16) B&R Industrial Automation GmbH + B&R Strasse 1 + Eggelsberg       5142 + AT + +DC-15-5C (hex) Anntec (Beijing) Technology Co.,Ltd. +DC155C (base 16) Anntec (Beijing) Technology Co.,Ltd. + F803, Shangdi Third Street, No.9,HaiDian District + Beijing 100080 + CN + +08-94-EC (hex) Huawei Device Co., Ltd. +0894EC (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +CC-B7-75 (hex) Huawei Device Co., Ltd. +CCB775 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +98-A3-75 (hex) Huawei Device Co., Ltd. +98A375 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +B8-75-2E (hex) Huawei Device Co., Ltd. +B8752E (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +10-A8-79 (hex) Intel Corporate +10A879 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +90-B1-76 (hex) Intel Corporate +90B176 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +70-A0-4B (hex) Intel Corporate +70A04B (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +0C-AE-39 (hex) Intel Corporate +0CAE39 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +04-A6-C8 (hex) Intel Corporate +04A6C8 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +0C-52-7F (hex) Check Point Software Technologies Ltd. +0C527F (base 16) Check Point Software Technologies Ltd. + 5 Ha'solelim St + Tel Aviv 67897 + IL + +88-FE-B6 (hex) ASKEY COMPUTER CORP +88FEB6 (base 16) ASKEY COMPUTER CORP + 10F,No.119,JIANKANG RD,ZHONGHE DIST + NEW TAIPEI TAIWAN 23585 + TW + +6C-56-40 (hex) BLU Products Inc +6C5640 (base 16) BLU Products Inc + 8600 NW 36th Street Suite 200 + Miami FL 33166 + US + +80-FD-7B (hex) BLU Products Inc +80FD7B (base 16) BLU Products Inc + 8600 NW 36th Street Suite 200 + Miami FL 33166 + US + +0C-0C-EA (hex) BLU Products Inc +0C0CEA (base 16) BLU Products Inc + 8600 NW 36th Street Suite 200 + Miami FL 33166 + US + +68-1E-A3 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +681EA3 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 + CN + +9C-47-11 (hex) ACCTON TECHNOLOGY CORPORATION +9C4711 (base 16) ACCTON TECHNOLOGY CORPORATION + No.1, Creation Road 3, Hsinchu Science Park, + Hsinchu 30077 + TW + +60-46-4C (hex) Ligent Tech,Inc +60464C (base 16) Ligent Tech,Inc + 2580 N 1st, STE400 + San Jose CA 95131 + US + +E8-EA-7C (hex) Shenzhen Amazwear Holdings Co., Ltd +E8EA7C (base 16) Shenzhen Amazwear Holdings Co., Ltd + 34th Floor, Chang Jiang Center, Crossroads of Renmin Road and Jianshe Road, Jingxin Community, Longhua Street,Longhua District + Shenzhen Guangdong 518000 + CN + +EC-9B-75 (hex) Roku, Inc +EC9B75 (base 16) Roku, Inc + 1173 Coleman Ave + San Jose CA 95110 + US + +A4-A6-4E (hex) Mellanox Technologies, Inc. +A4A64E (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +2C-B1-B7 (hex) Mellanox Technologies, Inc. +2CB1B7 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +0C-85-09 (hex) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD +0C8509 (base 16) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD + 6-8 Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China + Shenzhen 518052 + CN + +94-9C-BE (hex) HUAWEI TECHNOLOGIES CO.,LTD +949CBE (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +88-BA-74 (hex) Silicon Laboratories +88BA74 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +80-79-EF (hex) SUB-ZERO GROUP, INC. +8079EF (base 16) SUB-ZERO GROUP, INC. + 2835 Buds Drive + Fitchburg WI 53719 + US + +84-50-29 (hex) Arista Networks +845029 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US + +68-12-68 (hex) Quectel Wireless Solutions Co.,Ltd. +681268 (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + +18-5F-27 (hex) Motorola Mobility LLC, a Lenovo Company +185F27 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +98-F0-4C (hex) Cisco Systems, Inc +98F04C (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +88-C0-93 (hex) GIGAMEDIA +88C093 (base 16) GIGAMEDIA + 312 RUE DES HAUTS DE SAIGHIN CRT4 + LESQUIN FRANCE 59811 + FR + +3C-BE-8E (hex) Guangzhou Shiyuan Electronic Technology Company Limited +3CBE8E (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 + CN + +34-4A-86 (hex) Honor Device Co., Ltd. +344A86 (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 + +DC-69-CC (hex) LG Innotek +DC69CC (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR + +B4-C0-C3 (hex) TP-Link Systems Inc. +B4C0C3 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +20-33-89 (hex) Google, Inc. +203389 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +00-05-BA (hex) XK22 Enterprises, LLC +0005BA (base 16) XK22 Enterprises, LLC + 2646 Wooster Rd. + Rocky River OH 44116 + US + +F8-1E-49 (hex) Apple, Inc. +F81E49 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +BC-74-EA (hex) Apple, Inc. +BC74EA (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +70-AE-2A (hex) Apple, Inc. +70AE2A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +C0-2D-10 (hex) MOHAN ELECTRONICS AND SYSTEMS +C02D10 (base 16) MOHAN ELECTRONICS AND SYSTEMS + 571, STREET NO. 6, CHANDERLOK, SHAHDARA, DELHI + DELHI DELHI 110093 + IN + +18-B8-42 (hex) Apple, Inc. +18B842 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +2C-1C-F7 (hex) Apple, Inc. +2C1CF7 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +74-98-F4 (hex) BUFFALO.INC +7498F4 (base 16) BUFFALO.INC + AKAMONDORI Bld.,30-20,Ohsu 3-chome,Naka-ku + Nagoya Aichi Pref. 460-8315 + JP + +0C-83-F4 (hex) Canopy Works, Inc. +0C83F4 (base 16) Canopy Works, Inc. + 1875 Mission St, Ste 103 + San Francisco CA 94103 + US + +D0-C6-BE (hex) HPRO-Video +D0C6BE (base 16) HPRO-Video + 8500 Balboa Blvd + Northridge CA 91329 + 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 + +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 + +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 + +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 + +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 + +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 + +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 + +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 + +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 + +00-24-AE (hex) IDEMIA PUBLIC SECURITY FRANCE +0024AE (base 16) IDEMIA PUBLIC SECURITY FRANCE + 2 Place Samuel de Champlain + Courbevoie 92400 + FR + +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 + +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 + +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 + +78-E0-C5 (hex) Samsung Electronics Co.,Ltd +78E0C5 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +8C-EE-17 (hex) GYGES LABS PTE.LTD +8CEE17 (base 16) GYGES LABS PTE.LTD + 160 Robinson Road, #25-09, SBF Center, Singapore 068914 + SINGAPORE 068914 + SG + +2C-B4-71 (hex) Tuya Smart Inc. +2CB471 (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US + +00-76-B6 (hex) Ford Motor Company +0076B6 (base 16) Ford Motor Company + 20300 Rotunda Drive + Dearborn MI 48124 + US + +80-1D-0D (hex) IEEE Registration Authority +801D0D (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +EC-96-BF (hex) Kontron eSystems GmbH +EC96BF (base 16) Kontron eSystems GmbH + Bahnhofstr. 96 + Wendlingen 73240 + DE + +B0-3D-BF (hex) shenzhen ceita communications technology co.,ltd +B03DBF (base 16) shenzhen ceita communications technology co.,ltd + 4/F, 3/B, Shayi North Yongfa Science and Technology Park, Shajing Town, Bao'an District, Shenzhen. + shenzhen Select State 518104 + CN + +C0-A3-6D (hex) HUAWEI TECHNOLOGIES CO.,LTD +C0A36D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +34-10-D0 (hex) HUAWEI TECHNOLOGIES CO.,LTD +3410D0 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +CC-13-F3 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +CC13F3 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN + +9C-7F-64 (hex) Nanjing Qinheng Microelectronics Co., Ltd. +9C7F64 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. + No.18, Ningshuang Road + Nanjing Jiangsu 210012 + CN + +EC-A1-CC (hex) Cisco Systems, Inc +ECA1CC (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +F4-B8-21 (hex) Cisco Systems, Inc +F4B821 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +C0-E5-79 (hex) Huawei Device Co., Ltd. +C0E579 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +CC-81-30 (hex) Intelbras +CC8130 (base 16) Intelbras + BR 101, km 210, S/N° + São José Santa Catarina 88104800 + BR + +90-36-B2 (hex) TRATON AB +9036B2 (base 16) TRATON AB + Lärlingsvägen 3 + Södertälje 15165 + SE + +AC-30-19 (hex) Shenzhen Hailingwei Electronics Co., Ltd. +AC3019 (base 16) Shenzhen Hailingwei Electronics Co., Ltd. + 2nd Floor, Building 9, Longwangmiao Industrial Zone, East District, Baishixia Community, Fuyong Street, Bao'an District, Shenzhen + Shenzhen Guangdong 518000 + CN + +A0-F2-61 (hex) Palo Alto Networks +A0F261 (base 16) Palo Alto Networks + 3000 Tannery Way + Santa Clara CA 95054 + US + +74-F7-14 (hex) Lushare Precision Industry Co.,LTD +74F714 (base 16) Lushare Precision Industry Co.,LTD + No.313, Beihuan Road, Tiesong, Qingxi Town, + Dongguan Guangdong 523650 + CN + +A0-14-6D (hex) Suzhou NODKA Automation Technology Co.,Ltd +A0146D (base 16) Suzhou NODKA Automation Technology Co.,Ltd + NO.480, Yinzang Road, Linhu Town, Wuzhong District + Suzhou Jiangsu 215106 + CN + +8C-49-CF (hex) Private +8C49CF (base 16) Private + +90-F0-4C (hex) Nokia Solutions (Shanghai) Co.,Ltd. +90F04C (base 16) Nokia Solutions (Shanghai) Co.,Ltd. + No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai 201206,P.R.China + Shanghai Pudong New Area 201206 + CN + +68-15-D3 (hex) R&G PLUS Sp. z o.o. +6815D3 (base 16) R&G PLUS Sp. z o.o. + ul. Traugutta 7 + Mielec 39-300 + PL + +74-8F-BF (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +748FBF (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +3C-A2-39 (hex) DGSQ Co.,Ltd +3CA239 (base 16) DGSQ Co.,Ltd + Building A-B, Dongxing Industrial Park, Kengmei Village, Dongkeng Town, Dongguan City, Guangdong Province, China + Dongguan Guangdong 523455 + CN + +68-19-77 (hex) New H3C Technologies Co., Ltd +681977 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN + +C4-92-D9 (hex) zte corporation +C492D9 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +54-BB-8F (hex) ACCTON TECHNOLOGY CORPORATION +54BB8F (base 16) ACCTON TECHNOLOGY CORPORATION + No.1, Creation Road 3, Hsinchu Science Park, + Hsinchu 30077 + TW + +70-C2-88 (hex) Intel Corporate +70C288 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +74-13-48 (hex) Blink by Amazon +741348 (base 16) Blink by Amazon + 100 Riverpark Drive + North Reading MA 01864 + US + +80-3B-70 (hex) Private +803B70 (base 16) Private + +78-8A-FB (hex) HANSHOW TECHNOLOGY CO.,LTD. +788AFB (base 16) HANSHOW TECHNOLOGY CO.,LTD. + The 1st Floor Podium and Floor 4 of Building 1, Floor 7 of Building 5, JiaxingPhotovoltaic Technology Innovation Park, No.1288, Kanghe Road, Xiuzhou District,Jiaxing City, Zhejiang Prov,P.R.China + JIAXING 314000 + CN + +40-12-77 (hex) Microsoft Corporation +401277 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +3C-A0-0E (hex) Shenzhen Skyworth Digital Technology CO., Ltd +3CA00E (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + +FC-24-22 (hex) Hangzhou Ezviz Software Co.,Ltd. +FC2422 (base 16) Hangzhou Ezviz Software Co.,Ltd. + 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District + Hangzhou Zhejiang 310051 + CN + +90-92-2C (hex) Changzhi City Zhouyi Hengtong Information Security Co.,Ltd. +90922C (base 16) Changzhi City Zhouyi Hengtong Information Security Co.,Ltd. + No. 108, Haisen Street, High-tech Industrial Development Zone + Changzhi Shanxi 046000 + CN + +BC-FA-BA (hex) Mellanox Technologies, Inc. +BCFABA (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +D4-94-77 (hex) FONEX Data Systems Inc. +D49477 (base 16) FONEX Data Systems Inc. + 5400 Saint-Francois + Saint-Laurent QC H4S 1P6 + CA + +DC-1B-48 (hex) Texas Instruments +DC1B48 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +64-D3-63 (hex) Seyond +64D363 (base 16) Seyond + 160 San Gabriel Dr + Sunnyvale CA 94086 US -34-87-FB (hex) GTAI -3487FB (base 16) GTAI - Room 208, Building B11, Yantian Industrial Zone, Yantian Community, Xixiang Street, Bao 'an District, - Shenzhen Guangdong 518102 - CN +F4-49-EF (hex) EMSTONE +F449EF (base 16) EMSTONE + #1201, Byeoksan Digital Valley 3rd, 271, Digital-ro + Guro-Gu Seoul 08381 + KR -8C-DD-30 (hex) Ruijie Networks Co.,LTD -8CDD30 (base 16) Ruijie Networks Co.,LTD - Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District - Fuzhou 35000 +AC-87-46 (hex) Huizhou BYD Electronic Co., Ltd. +AC8746 (base 16) Huizhou BYD Electronic Co., Ltd. + Xiangshui River, Economic Development Zone, Daya Bay, Huizhou, Guangdong, China + Huizhou Guangdong 516000 CN -60-83-DA (hex) Silicon Laboratories -6083DA (base 16) Silicon Laboratories +78-0C-48 (hex) Hong Kong Yihao Electronic Technology Co., Limited +780C48 (base 16) Hong Kong Yihao Electronic Technology Co., Limited + FLAT/RM 1618B 16/F PIONEER CTR 750 NATHAN RD MONG KOK + Hong Kong 999077 + HK + +68-70-9E (hex) Silicon Laboratories +68709E (base 16) Silicon Laboratories 400 West Cesar Chavez Austin TX 78701 US -E0-72-91 (hex) Silicon Laboratories -E07291 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 +74-DA-78 (hex) HP Inc. +74DA78 (base 16) HP Inc. + 10300 Energy Dr + Spring TX 77389 US -6C-81-66 (hex) Private -6C8166 (base 16) Private +80-5A-70 (hex) Fortinet, Inc. +805A70 (base 16) Fortinet, Inc. + 899 Kifer Road + Sunnyvale 94086 + US -D0-EA-11 (hex) Routerboard.com -D0EA11 (base 16) Routerboard.com - Mikrotikls SIA - Riga Riga LV1009 - LV +9C-1F-E6 (hex) Shenzhen Skyworth Display Technologies Co.,Ltd +9C1FE6 (base 16) Shenzhen Skyworth Display Technologies Co.,Ltd + 1st floor, Experimental Factory, Skyworth Technology Industrial Park, Tangtou Community, Shiyan Street, Bao'an District + Shenzhen Guangdong 518108 + CN -2C-9D-90 (hex) Mellanox Technologies, Inc. -2C9D90 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +B4-65-DC (hex) CHINA DRAGON TECHNOLOGY LIMITED +B465DC (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 -E4-6D-AB (hex) Mellanox Technologies, Inc. -E46DAB (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 +F4-2F-97 (hex) Embrava USA, Inc +F42F97 (base 16) Embrava USA, Inc + 31 Hudson Yards, FL 11 + New York NY 10001 US -00-54-AF (hex) AUMOVIO Systems, Inc. -0054AF (base 16) AUMOVIO Systems, Inc. - 21440 W. Lake Cook Rd. - Deer Park IL 60010 - US +34-26-01 (hex) HUAWEI TECHNOLOGIES CO.,LTD +342601 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -20-AD-56 (hex) AUMOVIO Systems, Inc. -20AD56 (base 16) AUMOVIO Systems, Inc. - 21440 W. Lake Cook Rd. - Deer Park IL 60010 - US +AC-24-77 (hex) Shenzhen Tinno Mobile Technology Corp +AC2477 (base 16) Shenzhen Tinno Mobile Technology Corp + 3F.15F(N).24-28F, Tianlong Mobile Headquarters BuildingTongfa South Road, Xilli Community, Xili Street, Nanshan DistrictShenzhen City, Guangdong Province, P. R. China + Shenzhen Guangdong 518053 + CN -90-74-AE (hex) AzureWave Technology Inc. -9074AE (base 16) AzureWave Technology Inc. - 8F., No. 94, Baozhong Rd. - New Taipei City Taiwan 231 - TW +F8-A5-E6 (hex) Magicyo Technology CO.,Ltd +F8A5E6 (base 16) Magicyo Technology CO.,Ltd + Room 518, Incubation Center Building, China Academy of Science and Technology Development, High tech Community, Yuehai Street, Nanshan District, Shenzhen, Guangdong, China + Shenzhen Guangdong 518057 + CN -B8-51-1D (hex) TELECHIPS, INC -B8511D (base 16) TELECHIPS, INC - 27, Geumto-ro 80beon-gil, Sujeong-gu, - Seongnam-si, Gyeonggi-do, 13453 - KR +E8-B8-53 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED +E8B853 (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 -D8-FC-92 (hex) Tuya Smart Inc. -D8FC92 (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 +C4-8A-CE (hex) HISENSE VISUAL TECHNOLOGY CO.,LTD +C48ACE (base 16) HISENSE VISUAL TECHNOLOGY CO.,LTD + Qianwangang Road 218 + Qingdao Shandong 266510 + CN + +5C-A9-31 (hex) Ubee Interactive Co., Limited +5CA931 (base 16) Ubee Interactive Co., Limited + Flat/RM 1202, 12/F, AT Tower + North Point Hong Kong 180 + HK + +64-21-FD (hex) Guang zhou Xradio Technology Co., Ltd +6421FD (base 16) Guang zhou Xradio Technology Co., Ltd + Room 405 ,BuildingB, No. 18 Science Avenue, Guangzhou Science City + Guangzhou Guangdong 510700 + CN + +98-FE-54 (hex) Raspberry Pi (Trading) Ltd +98FE54 (base 16) Raspberry Pi (Trading) Ltd + Maurice Wilkes Building, St Johns Innovation Park + Cambridge Cambridgeshire CB4 0DS + GB + +DC-8E-6D (hex) Huawei Device Co., Ltd. +DC8E6D (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +CC-A3-0C (hex) Silicon Laboratories +CCA30C (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -B4-E2-5B (hex) HP Inc. -B4E25B (base 16) HP Inc. - 10300 Energy Dr - Spring TX 77389 +70-28-7D (hex) Google, Inc. +70287D (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US -F4-70-18 (hex) Hangzhou Ezviz Software Co.,Ltd. -F47018 (base 16) Hangzhou Ezviz Software Co.,Ltd. - 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District - Hangzhou Zhejiang 310051 +E8-BA-17 (hex) Beijing Xiaomi Mobile Software Co., Ltd +E8BA17 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -DC-74-CE (hex) ITOCHU Techno-Solutions Corporation -DC74CE (base 16) ITOCHU Techno-Solutions Corporation - Kamiyacho Trust Tower, 4-1-1, Toranomon, Minato-ku, - Tokyo Tokyo 105-6950 - JP +B0-64-E0 (hex) Samsung Electronics Co.,Ltd +B064E0 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -4C-55-B2 (hex) Xiaomi Communications Co Ltd -4C55B2 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +94-64-42 (hex) CELESTICA INC. +946442 (base 16) CELESTICA INC. + 1900-5140 Yonge Street PO Box 42 + Toronto Ontario M2N 6L7 + CA -10-03-CD (hex) Calix Inc. -1003CD (base 16) Calix Inc. - 2777 Orchard Pkwy - San Jose CA 95131 +50-85-7C (hex) eero inc. +50857C (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US -98-2A-FD (hex) HUAWEI TECHNOLOGIES CO.,LTD -982AFD (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +24-1A-F7 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +241AF7 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 CN -2C-AE-46 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -2CAE46 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +3C-35-58 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +3C3558 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China Nanning Guangxi 530007 CN -6C-77-42 (hex) zte corporation -6C7742 (base 16) zte corporation +78-65-59 (hex) Sagemcom Broadband SAS +786559 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +10-D7-B0 (hex) Sagemcom Broadband SAS +10D7B0 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +70-73-3A (hex) Jiangxi Remote lntelligence Technology Co.,Ltd +70733A (base 16) Jiangxi Remote lntelligence Technology Co.,Ltd + No. 1, Chemical Avenue, Guixi335400, Yingtan, Jiangxi + Yingtan Jiangxi 360600 + CN + +54-88-D5 (hex) zte corporation +5488D5 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China shenzhen guangdong 518057 CN -10-CD-54 (hex) HUAWEI TECHNOLOGIES CO.,LTD -10CD54 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +88-0F-A2 (hex) Sagemcom Broadband SAS +880FA2 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR -94-A2-5D (hex) HUAWEI TECHNOLOGIES CO.,LTD -94A25D (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +DC-97-E6 (hex) Sagemcom Broadband SAS +DC97E6 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR -B4-C3-D9 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B4C3D9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +4C-19-5D (hex) Sagemcom Broadband SAS +4C195D (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR -54-26-18 (hex) HUAWEI TECHNOLOGIES CO.,LTD -542618 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +EC-50-A6 (hex) Sagemcom Broadband SAS +EC50A6 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR -0C-0F-D8 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED -0C0FD8 (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 +60-15-6F (hex) TP-Link Systems Inc. +60156F (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US -14-4F-F0 (hex) NXP Semiconductors Taiwan Ltd. -144FF0 (base 16) NXP Semiconductors Taiwan Ltd. - No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan - Nanzi Dist. Kaohsiung 811643 - TW +84-67-9A (hex) Arm Ltd +84679A (base 16) Arm Ltd + 110 Fulbourn Road + Cambridge Cambridgeshire CB19NJ + GB -D4-13-68 (hex) Shenzhen Intellirocks Tech. Co. Ltd. -D41368 (base 16) Shenzhen Intellirocks Tech. Co. Ltd. - No. 3301, Block C, Section 1, Chuangzhi Yuncheng Building, Liuxian Avenue,Xili Community, Xili Street, Nanshan District - Shenzhen Guangdong 518000 +38-B4-D3 (hex) BSH Hausgeräte GmbH +38B4D3 (base 16) BSH Hausgeräte GmbH + Im Gewerbepark B10 + Regensburg 93059 + DE + +98-9E-85 (hex) Honor Device Co., Ltd. +989E85 (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 -E4-53-41 (hex) Apple, Inc. -E45341 (base 16) Apple, Inc. +0C-54-27 (hex) Dongguan Huayin Electronic Technology Co., Ltd. +0C5427 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. + Room 101, No.8 Xinglong 3rd Road, Shipai Town + Dongguan Guangdong 523000 + CN + +BC-4F-2D (hex) Apple, Inc. +BC4F2D (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -68-A7-29 (hex) Apple, Inc. -68A729 (base 16) Apple, Inc. +8C-82-83 (hex) Apple, Inc. +8C8283 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -74-29-17 (hex) Apple, Inc. -742917 (base 16) Apple, Inc. +5C-B8-B7 (hex) Apple, Inc. +5CB8B7 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -A4-22-B6 (hex) Motorola Mobility LLC, a Lenovo Company -A422B6 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 +10-9E-6B (hex) Apple, Inc. +109E6B (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -CC-80-8F (hex) Apple, Inc. -CC808F (base 16) Apple, Inc. +EC-2C-0D (hex) Apple, Inc. +EC2C0D (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -E0-BA-78 (hex) Apple, Inc. -E0BA78 (base 16) Apple, Inc. +D8-46-CE (hex) Apple, Inc. +D846CE (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -90-20-D7 (hex) Microsoft Corporation -9020D7 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 +14-C7-C4 (hex) Zyxel Communications Corporation +14C7C4 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + +00-02-31 (hex) Ingersoll-Rand +000231 (base 16) Ingersoll-Rand + 53 Frontage Road Suite 250 + Hampton NJ 08827 US -80-2A-F6 (hex) Honor Device Co., Ltd. -802AF6 (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 +00-03-74 (hex) Schneider Electric +000374 (base 16) Schneider Electric + 35 Rue Joseph Monier + Rueil-Malmaison 92500 + CA -88-99-86 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -889986 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN +74-28-AE (hex) Mellanox Technologies, Inc. +7428AE (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US -60-95-F8 (hex) Arcadyan Corporation -6095F8 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 +20-59-D1 (hex) Mellanox Technologies, Inc. +2059D1 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +64-97-46 (hex) AzureWave Technology Inc. +649746 (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 TW -28-B2-0B (hex) NXP USA, Inc -28B20B (base 16) NXP USA, Inc - 6501 William Cannon Dr. - West - Austin TX 78735 +44-99-5B (hex) GX India Pvt Ltd +44995B (base 16) GX India Pvt Ltd + 595, SECTOR-8, IMT MANESAR + GURGAON Haryana 122051 + IN + +78-0C-26 (hex) Trafag AG +780C26 (base 16) Trafag AG + Industriestrasse 11 + Bubikon Zuerich 8608 + CH + +24-1F-49 (hex) NXP Semiconductors Taiwan Ltd. +241F49 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW + +7C-98-24 (hex) Sagemcom Broadband SAS +7C9824 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +04-30-FA (hex) Cisco Systems, Inc +0430FA (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -00-11-1E (hex) B&R Industrial Automation GmbH -00111E (base 16) B&R Industrial Automation GmbH - B&R Strasse 1 - Eggelsberg       5142 +F4-5B-B4 (hex) Cisco Systems, Inc +F45BB4 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +D4-55-AD (hex) go-e GmbH +D455AD (base 16) go-e GmbH + Satellitenstrasse 1 + Feldkirchen Kaernten 9560 AT -80-AF-9F (hex) eero inc. -80AF9F (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +8C-20-F1 (hex) OpenAI +8C20F1 (base 16) OpenAI + 1455 3rd Street + San Francisco CA 94158 US -BC-9C-8D (hex) Ruckus Wireless -BC9C8D (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +DC-DE-4B (hex) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED +DCDE4B (base 16) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED + Room 03, 23/F, Unit B Building, No 9, Shenzhen Bay Eco -Technology Park, Yuehai Street, Nanshan District, Shenzhen, China + Shenzhen 518000 + CN + +B8-8C-2B (hex) Sagemcom Broadband SAS +B88C2B (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +00-F8-CC (hex) Sagemcom Broadband SAS +00F8CC (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +DC-92-72 (hex) Sagemcom Broadband SAS +DC9272 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +18-0C-7A (hex) Sagemcom Broadband SAS +180C7A (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +70-7D-A1 (hex) Sagemcom Broadband SAS +707DA1 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +8C-9A-8F (hex) Sagemcom Broadband SAS +8C9A8F (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +2C-FB-0F (hex) Sagemcom Broadband SAS +2CFB0F (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +BC-D5-ED (hex) Sagemcom Broadband SAS +BCD5ED (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +9C-24-72 (hex) Sagemcom Broadband SAS +9C2472 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +20-47-B5 (hex) Sagemcom Broadband SAS +2047B5 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +C0-A3-C7 (hex) Telink Micro LLC +C0A3C7 (base 16) Telink Micro LLC + 2975 Scott Blvd #120 + Santa Clara CA 95054 US -EC-50-A6 (hex) Sagemcom Broadband SAS -EC50A6 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 +AC-3B-77 (hex) Sagemcom Broadband SAS +AC3B77 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 FR -0C-52-7F (hex) Check Point Software Technologies Ltd. -0C527F (base 16) Check Point Software Technologies Ltd. - 5 Ha'solelim St - Tel Aviv 67897 - IL +00-0E-59 (hex) Sagemcom Broadband SAS +000E59 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -00-15-1E (hex) B&R Industrial Automation GmbH -00151E (base 16) B&R Industrial Automation GmbH - B&R Strasse 1 - Eggelsberg       5142 - AT +84-A1-D1 (hex) Sagemcom Broadband SAS +84A1D1 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -64-D5-62 (hex) Huawei Device Co., Ltd. -64D562 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +78-C2-13 (hex) Sagemcom Broadband SAS +78C213 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -08-94-EC (hex) Huawei Device Co., Ltd. -0894EC (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +D8-33-B7 (hex) Sagemcom Broadband SAS +D833B7 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -CC-B7-75 (hex) Huawei Device Co., Ltd. -CCB775 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +D0-6D-C9 (hex) Sagemcom Broadband SAS +D06DC9 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -64-70-84 (hex) AltoBeam Inc. -647084 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN +08-7B-12 (hex) Sagemcom Broadband SAS +087B12 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -98-A3-75 (hex) Huawei Device Co., Ltd. -98A375 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +D0-CF-0E (hex) Sagemcom Broadband SAS +D0CF0E (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -B8-75-2E (hex) Huawei Device Co., Ltd. -B8752E (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +C4-EB-42 (hex) Sagemcom Broadband SAS +C4EB42 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -00-A3-07 (hex) Honor Device Co., Ltd. -00A307 (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 +48-E2-7E (hex) Sagemcom Broadband SAS +48E27E (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -DC-15-5C (hex) Anntec (Beijing) Technology Co.,Ltd. -DC155C (base 16) Anntec (Beijing) Technology Co.,Ltd. - F803, Shangdi Third Street, No.9,HaiDian District - Beijing 100080 - CN +BC-33-DB (hex) Sagemcom Broadband SAS +BC33DB (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -10-A8-79 (hex) Intel Corporate -10A879 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +34-53-D2 (hex) Sagemcom Broadband SAS +3453D2 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -90-B1-76 (hex) Intel Corporate -90B176 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +58-2F-F7 (hex) Sagemcom Broadband SAS +582FF7 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -70-A0-4B (hex) Intel Corporate -70A04B (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +34-DB-9C (hex) Sagemcom Broadband SAS +34DB9C (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -0C-AE-39 (hex) Intel Corporate -0CAE39 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +00-CB-51 (hex) Sagemcom Broadband SAS +00CB51 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -04-A6-C8 (hex) Intel Corporate -04A6C8 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +10-06-45 (hex) Sagemcom Broadband SAS +100645 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -88-FE-B6 (hex) ASKEY COMPUTER CORP -88FEB6 (base 16) ASKEY COMPUTER CORP - 10F,No.119,JIANKANG RD,ZHONGHE DIST - NEW TAIPEI TAIWAN 23585 - TW +34-49-5B (hex) Sagemcom Broadband SAS +34495B (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -EC-9B-75 (hex) Roku, Inc -EC9B75 (base 16) Roku, Inc - 1173 Coleman Ave - San Jose CA 95110 - US +E8-F1-B0 (hex) Sagemcom Broadband SAS +E8F1B0 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -6C-56-40 (hex) BLU Products Inc -6C5640 (base 16) BLU Products Inc - 8600 NW 36th Street Suite 200 - Miami FL 33166 - US +84-A4-23 (hex) Sagemcom Broadband SAS +84A423 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -80-FD-7B (hex) BLU Products Inc -80FD7B (base 16) BLU Products Inc - 8600 NW 36th Street Suite 200 - Miami FL 33166 - US +4C-17-EB (hex) Sagemcom Broadband SAS +4C17EB (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -0C-0C-EA (hex) BLU Products Inc -0C0CEA (base 16) BLU Products Inc - 8600 NW 36th Street Suite 200 - Miami FL 33166 - US +CC-33-BB (hex) Sagemcom Broadband SAS +CC33BB (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -68-1E-A3 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -681EA3 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County - Chengdu Sichuan 611330 - CN +24-B2-DE (hex) Espressif Inc. +24B2DE (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -A4-A6-4E (hex) Mellanox Technologies, Inc. -A4A64E (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +D8-A0-1D (hex) Espressif Inc. +D8A01D (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -2C-B1-B7 (hex) Mellanox Technologies, Inc. -2CB1B7 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +BC-DD-C2 (hex) Espressif Inc. +BCDDC2 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -9C-47-11 (hex) ACCTON TECHNOLOGY CORPORATION -9C4711 (base 16) ACCTON TECHNOLOGY CORPORATION - No.1, Creation Road 3, Hsinchu Science Park, - Hsinchu 30077 - TW +CC-50-E3 (hex) Espressif Inc. +CC50E3 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -60-46-4C (hex) Ligent Tech,Inc -60464C (base 16) Ligent Tech,Inc - 2580 N 1st, STE400 - San Jose CA 95131 - US +A4-F0-0F (hex) Espressif Inc. +A4F00F (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -94-9C-BE (hex) HUAWEI TECHNOLOGIES CO.,LTD -949CBE (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +D8-85-AC (hex) Espressif Inc. +D885AC (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -88-BA-74 (hex) Silicon Laboratories -88BA74 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 +D4-E9-F4 (hex) Espressif Inc. +D4E9F4 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +1C-DB-D4 (hex) Espressif Inc. +1CDBD4 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +24-62-AB (hex) Espressif Inc. +2462AB (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +50-02-91 (hex) Espressif Inc. +500291 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +8C-AA-B5 (hex) Espressif Inc. +8CAAB5 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +7C-DF-A1 (hex) Espressif Inc. +7CDFA1 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +A4-E5-7C (hex) Espressif Inc. +A4E57C (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +EC-94-CB (hex) Espressif Inc. +EC94CB (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +44-17-93 (hex) Espressif Inc. +441793 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +5C-CF-7F (hex) Espressif Inc. +5CCF7F (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +A0-20-A6 (hex) Espressif Inc. +A020A6 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +E0-72-A1 (hex) Espressif Inc. +E072A1 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +AC-A7-04 (hex) Espressif Inc. +ACA704 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +68-67-25 (hex) Espressif Inc. +686725 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +34-94-54 (hex) Espressif Inc. +349454 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +AC-67-B2 (hex) Espressif Inc. +AC67B2 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +0C-DC-7E (hex) Espressif Inc. +0CDC7E (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +20-50-0D (hex) Espressif Inc. +20500D (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +68-EE-8F (hex) Espressif Inc. +68EE8F (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +F4-65-0B (hex) Espressif Inc. +F4650B (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +38-18-2B (hex) Espressif Inc. +38182B (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +10-20-BA (hex) Espressif Inc. +1020BA (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +B8-F8-62 (hex) Espressif Inc. +B8F862 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +4C-C3-82 (hex) Espressif Inc. +4CC382 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +10-B4-1D (hex) Espressif Inc. +10B41D (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +F0-24-F9 (hex) Espressif Inc. +F024F9 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +8C-BF-EA (hex) Espressif Inc. +8CBFEA (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +9C-9E-6E (hex) Espressif Inc. +9C9E6E (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +78-EE-4C (hex) Espressif Inc. +78EE4C (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +40-22-D8 (hex) Espressif Inc. +4022D8 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +08-3A-8D (hex) Espressif Inc. +083A8D (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +D8-13-2A (hex) Espressif Inc. +D8132A (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +54-43-B2 (hex) Espressif Inc. +5443B2 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +58-6D-0C (hex) Hewlett Packard Enterprise +586D0C (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 US -88-C0-93 (hex) GIGAMEDIA -88C093 (base 16) GIGAMEDIA - 312 RUE DES HAUTS DE SAIGHIN CRT4 - LESQUIN FRANCE 59811 - FR +BC-14-69 (hex) Phyplus Technology (Shanghai) Co., Ltd +BC1469 (base 16) Phyplus Technology (Shanghai) Co., Ltd + 3th Floor, Building 23, 676 Wuxing Road, Pudong New District, Shanghai + Shanghai Shanghai 201204 + CN -E8-EA-7C (hex) Shenzhen Amazwear Holdings Co., Ltd -E8EA7C (base 16) Shenzhen Amazwear Holdings Co., Ltd - 34th Floor, Chang Jiang Center, Crossroads of Renmin Road and Jianshe Road, Jingxin Community, Longhua Street,Longhua District - Shenzhen Guangdong 518000 +28-F3-66 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +28F366 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO 268,Fuqian Rd,Jutang Community,Guanlan town , LongHua new district,Shenzhen,518110,China. + Shenzhen 518110 CN -18-5F-27 (hex) Motorola Mobility LLC, a Lenovo Company -185F27 (base 16) Motorola Mobility LLC, a Lenovo Company +84-57-EB (hex) Motorola Mobility LLC, a Lenovo Company +8457EB (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza Chicago IL 60654 US -0C-85-09 (hex) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD -0C8509 (base 16) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD - 6-8 Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China - Shenzhen 518052 - CN +54-3C-ED (hex) Sagemcom Broadband SAS +543CED (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -80-79-EF (hex) SUB-ZERO GROUP, INC. -8079EF (base 16) SUB-ZERO GROUP, INC. - 2835 Buds Drive - Fitchburg WI 53719 - US +80-22-A7 (hex) NEC Platforms, Ltd. +8022A7 (base 16) NEC Platforms, Ltd. + 14-1, Shiba 4-chome + Minato-ku Tokyo 108-8556 + JP -84-50-29 (hex) Arista Networks -845029 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 +D8-26-D3 (hex) eero inc. +D826D3 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US -68-12-68 (hex) Quectel Wireless Solutions Co.,Ltd. -681268 (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 +74-39-89 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +743989 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -B4-C0-C3 (hex) TP-Link Systems Inc. -B4C0C3 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US +3C-6A-48 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +3C6A48 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN -3C-BE-8E (hex) Guangzhou Shiyuan Electronic Technology Company Limited -3CBE8E (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 +DC-00-77 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +DC0077 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -98-F0-4C (hex) Cisco Systems, Inc -98F04C (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +88-99-86 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +889986 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN -00-05-BA (hex) XK22 Enterprises, LLC -0005BA (base 16) XK22 Enterprises, LLC - 2646 Wooster Rd. - Rocky River OH 44116 - US +60-32-B1 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +6032B1 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN -34-4A-86 (hex) Honor Device Co., Ltd. -344A86 (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 +B4-FB-E3 (hex) AltoBeam Inc. +B4FBE3 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -DC-69-CC (hex) LG Innotek -DC69CC (base 16) LG Innotek - 26, HANAMSANDAN 5BEON-RO - Gwangju Gwangsan-gu 506-731 - KR +88-28-7D (hex) AltoBeam Inc. +88287D (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN -C0-2D-10 (hex) MOHAN ELECTRONICS AND SYSTEMS -C02D10 (base 16) MOHAN ELECTRONICS AND SYSTEMS - 571, STREET NO. 6, CHANDERLOK, SHAHDARA, DELHI - DELHI DELHI 110093 - IN +48-7F-80 (hex) FRITZ! Technology GmbH +487F80 (base 16) FRITZ! Technology GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE -74-98-F4 (hex) BUFFALO.INC -7498F4 (base 16) BUFFALO.INC - AKAMONDORI Bld.,30-20,Ohsu 3-chome,Naka-ku - Nagoya Aichi Pref. 460-8315 - JP +2C-3F-87 (hex) Private +2C3F87 (base 16) Private -0C-83-F4 (hex) Canopy Works, Inc. -0C83F4 (base 16) Canopy Works, Inc. - 1875 Mission St, Ste 103 - San Francisco CA 94103 +0C-16-6E (hex) WirelessMobility Engineering Centre SDN. BHD +0C166E (base 16) WirelessMobility Engineering Centre SDN. BHD + SummerSkye Square, NO. 1-2-13 & 1-2, 13A, Jalan Sungai Tiram 8, 11900 Bayan Lepas + Penang 11900 + MY + +D8-FB-58 (hex) Mellanox Technologies, Inc. +D8FB58 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -20-33-89 (hex) Google, Inc. -203389 (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 +18-D0-E1 (hex) Mellanox Technologies, Inc. +18D0E1 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -D0-C6-BE (hex) HPRO-Video -D0C6BE (base 16) HPRO-Video - 8500 Balboa Blvd - Northridge CA 91329 +64-C0-45 (hex) Apple, Inc. +64C045 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -F8-1E-49 (hex) Apple, Inc. -F81E49 (base 16) Apple, Inc. +E8-FE-BE (hex) Apple, Inc. +E8FEBE (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -BC-74-EA (hex) Apple, Inc. -BC74EA (base 16) Apple, Inc. +88-17-A8 (hex) Apple, Inc. +8817A8 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -70-AE-2A (hex) Apple, Inc. -70AE2A (base 16) Apple, Inc. +74-D5-E8 (hex) Apple, Inc. +74D5E8 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -18-B8-42 (hex) Apple, Inc. -18B842 (base 16) Apple, Inc. +44-63-B6 (hex) Apple, Inc. +4463B6 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -2C-1C-F7 (hex) Apple, Inc. -2C1CF7 (base 16) Apple, Inc. +68-3B-09 (hex) Apple, Inc. +683B09 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US @@ -93848,18 +95414,6 @@ FCC233 (base 16) ASUSTek COMPUTER INC. Wuhan Hubei 430074 CN -FC-B4-67 (hex) Espressif Inc. -FCB467 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -D8-BC-38 (hex) Espressif Inc. -D8BC38 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 38-31-5A (hex) Rinnai 38315A (base 16) Rinnai 103 International Drive @@ -93938,18 +95492,6 @@ D0F928 (base 16) zte corporation Chengdu CN 610041 CN -18-6A-81 (hex) Sagemcom Broadband SAS -186A81 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -A0-7F-8A (hex) Sagemcom Broadband SAS -A07F8A (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 2C-99-75 (hex) Samsung Electronics Co.,Ltd 2C9975 (base 16) Samsung Electronics Co.,Ltd 129, Samsung-ro, Youngtongl-Gu @@ -93998,12 +95540,6 @@ C47EE0 (base 16) Cisco Systems, Inc Kulim Kedah 09000 MY -1C-7C-98 (hex) NEC Platforms, Ltd. -1C7C98 (base 16) NEC Platforms, Ltd. - 2-3 Kandatsukasamachi - Chiyodaku Tokyo 101-8532 - JP - 00-E0-4D (hex) INTERNET INITIATIVE JAPAN, INC 00E04D (base 16) INTERNET INITIATIVE JAPAN, INC Iidabashi Grand Bloom 2-10-2 Fujimi @@ -95000,12 +96536,6 @@ E4BCAA (base 16) Xiaomi Communications Co Ltd Seoul Seoul 08503 KR -40-4C-CA (hex) Espressif Inc. -404CCA (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 30-7C-5E (hex) Juniper Networks 307C5E (base 16) Juniper Networks 1133 Innovation Way @@ -95546,12 +97076,6 @@ DCB347 (base 16) SHENZHEN FAST TECHNOLOGIES CO.,LTD Shenzhen Guangdong 518057 CN -60-29-2B (hex) TP-LINK TECHNOLOGIES CO.,LTD. -60292B (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - A4-86-AE (hex) Quectel Wireless Solutions Co.,Ltd. A486AE (base 16) Quectel Wireless Solutions Co.,Ltd. No.1801 Hongmei Road, Xuhui District @@ -95708,12 +97232,6 @@ F4E975 (base 16) New H3C Technologies Co., Ltd Hangzhou Zhejiang 310052 CN -D4-D4-DA (hex) Espressif Inc. -D4D4DA (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 38-64-07 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. 386407 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. No.218 Qianwangang Road @@ -95786,18 +97304,6 @@ ACC4A9 (base 16) Fiberhome Telecommunication Technologies Co.,LTD HongKong HongKong 999077 HK -C4-EB-43 (hex) Sagemcom Broadband SAS -C4EB43 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -C4-EB-41 (hex) Sagemcom Broadband SAS -C4EB41 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 74-3E-39 (hex) YUSUR Technology Co., Ltd. 743E39 (base 16) YUSUR Technology Co., Ltd. Room 1401,building 4,yard 1, Beiqing Road No.81, Haidian District @@ -96188,12 +97694,6 @@ A41CB4 (base 16) DFI Inc Taoyuan City 333424 TW -2C-F2-A5 (hex) Sagemcom Broadband SAS -2CF2A5 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 74-08-DE (hex) Fujian Landi Commercial Technology Co., Ltd. 7408DE (base 16) Fujian Landi Commercial Technology Co., Ltd. Building 3A, Block A, Fuzhou Software Park, No.89 Software Road, Gulou District @@ -96206,12 +97706,6 @@ A41CB4 (base 16) DFI Inc Dongguan Guangdong 523808 CN -94-3C-96 (hex) Sagemcom Broadband SAS -943C96 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 70-B7-E4 (hex) Broadcom Limited 70B7E4 (base 16) Broadcom Limited 15191 Alton Parkway @@ -96428,12 +97922,6 @@ F0C1CE (base 16) GoodWe Technologies CO., Ltd REDMOND WA 98052 US -C4-EB-39 (hex) Sagemcom Broadband SAS -C4EB39 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 00-52-C8 (hex) Made Studio Design Ltd. 0052C8 (base 16) Made Studio Design Ltd. 10F., No. 169, Sec. 4, Zhongxiao E. Rd., Da-an Dist. @@ -96608,12 +98096,6 @@ C06911 (base 16) Arista Networks Moulineaux 92370 FR -78-44-FD (hex) TP-LINK TECHNOLOGIES CO.,LTD. -7844FD (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - B4-46-6B (hex) REALTIMEID AS B4466B (base 16) REALTIMEID AS Busk Bruns veg 1 , 7760 Snåsa (Norway) @@ -96944,12 +98426,6 @@ C0A938 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Beijing 100089 CN -64-FD-96 (hex) Sagemcom Broadband SAS -64FD96 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 64-98-9E (hex) TRINNOV AUDIO 64989E (base 16) TRINNOV AUDIO 5 rue Edmond Michelet @@ -97112,12 +98588,6 @@ E0036B (base 16) Samsung Electronics Co.,Ltd Tianjin 300385 CN -F4-84-8D (hex) TP-LINK TECHNOLOGIES CO.,LTD. -F4848D (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 58-11-22 (hex) ASUSTek COMPUTER INC. 581122 (base 16) ASUSTek COMPUTER INC. 15,Li-Te Rd., Peitou, Taipei 112, Taiwan @@ -97424,12 +98894,6 @@ E8F375 (base 16) Nokia Kanata Ontario K2K 2E6 CA -E8-31-CD (hex) Espressif Inc. -E831CD (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 38-1E-C7 (hex) Chipsea Technologies(Shenzhen) Corp. 381EC7 (base 16) Chipsea Technologies(Shenzhen) Corp. 9F,Block A,Garden City Digital Building,No.1079 Nanhai Road,Nanshan District,Shenzhen @@ -97898,12 +99362,6 @@ D80A60 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -D4-F9-8D (hex) Espressif Inc. -D4F98D (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 9C-90-19 (hex) Beyless 9C9019 (base 16) Beyless #725,42,Changeop-ro, Sujeong-gu @@ -98174,12 +99632,6 @@ D86C5A (base 16) HUMAX Co., Ltd. Seongnam-si Gyeonggi-do 463-875 KR -E4-C0-E2 (hex) Sagemcom Broadband SAS -E4C0E2 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 4C-3F-A7 (hex) uGrid Network Inc. 4C3FA7 (base 16) uGrid Network Inc. 602 Gabriola Way @@ -98222,12 +99674,6 @@ D0C35A (base 16) Jabil Circuit de Chihuahua Amsterdam Noord-Holland 1097 JB NL -68-77-24 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -687724 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 18-2D-F7 (hex) JY COMPANY 182DF7 (base 16) JY COMPANY A-1811 Ho, SamboTechno Tower,122, Jomaru-ro 385 Beon-gil, @@ -98444,18 +99890,6 @@ F4239C (base 16) SERNET (SUZHOU) TECHNOLOGIES CORPORATION Suzhou 215021 CN -4C-EB-D6 (hex) Espressif Inc. -4CEBD6 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -F4-B1-9C (hex) AltoBeam (China) Inc. -F4B19C (base 16) AltoBeam (China) Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN - A8-D0-81 (hex) Huawei Device Co., Ltd. A8D081 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -98486,12 +99920,6 @@ A8D081 (base 16) Huawei Device Co., Ltd. Shenzhen Guangdong 518100 CN -1C-9D-C2 (hex) Espressif Inc. -1C9DC2 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 64-0E-9B (hex) ISHIDA MEDICAL CO., LTD. 640E9B (base 16) ISHIDA MEDICAL CO., LTD. 60-1 Shogoin Rengezocho, Sakyo-Ku @@ -98708,12 +100136,6 @@ AC0BFB (base 16) Espressif Inc. Kanata Ontario K2K 2E6 CA -84-F7-03 (hex) Espressif Inc. -84F703 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 48-5A-67 (hex) Shaanxi Ruixun Electronic Information Technology Co., Ltd 485A67 (base 16) Shaanxi Ruixun Electronic Information Technology Co., Ltd 11th Floor, Building A, Xi 'an National Digital Publishing Base, No. 996, Tiangu 7th Road, Software New City, Xi 'an High-tech Zone, Shaanxi Province @@ -99356,12 +100778,6 @@ B48901 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Cupertino CA 95014 US -34-86-5D (hex) Espressif Inc. -34865D (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 1C-A0-EF (hex) IEEE Registration Authority 1CA0EF (base 16) IEEE Registration Authority 445 Hoes Lane @@ -99392,12 +100808,6 @@ D097FE (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. Chongqing Chongqing 401331 CN -98-CD-AC (hex) Espressif Inc. -98CDAC (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - F8-45-C4 (hex) Shenzhen Netforward Micro-Electronic Co., Ltd. F845C4 (base 16) Shenzhen Netforward Micro-Electronic Co., Ltd. Room 611-2?6st Floor,Building 1, The Sunmax Technology Park, No 8 Keyuan Road, Nanshan District @@ -99566,12 +100976,6 @@ D8CD2C (base 16) WUXI NEIHUA NETWORK TECHNOLOGY CO., LTD SHENZHEN GUANGDONG 518101 CN -40-3F-8C (hex) TP-LINK TECHNOLOGIES CO.,LTD. -403F8C (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - CC-DB-04 (hex) DataRemote Inc. CCDB04 (base 16) DataRemote Inc. 18001 Old Cutler Rd. Suite 600 @@ -99794,12 +101198,6 @@ C45D83 (base 16) Samsung Electronics Co.,Ltd Gumi Gyeongbuk 730-350 KR -9C-9C-1F (hex) Espressif Inc. -9C9C1F (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 24-0B-88 (hex) Taicang T&W Electronics 240B88 (base 16) Taicang T&W Electronics 89# Jiang Nan RD @@ -99944,12 +101342,6 @@ EC75ED (base 16) Citrix Systems, Inc. Fort Lauderdale FL 33309 US -4C-75-25 (hex) Espressif Inc. -4C7525 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 00-13-40 (hex) AD.EL s.r.l. 001340 (base 16) AD.EL s.r.l. via S. Pertini,5 @@ -100490,12 +101882,6 @@ F46FED (base 16) Fiberhome Telecommunication Technologies Co.,LTD Wuhan Hubei 430074 CN -94-B9-7E (hex) Espressif Inc. -94B97E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - CC-E0-DA (hex) Baidu Online Network Technology (Beijing) Co., Ltd CCE0DA (base 16) Baidu Online Network Technology (Beijing) Co., Ltd Baidu Campus, No.10 Shangdi 10th Street, Haidian District @@ -100730,12 +102116,6 @@ C8BC9C (base 16) Huawei Device Co., Ltd. Kulim Kedah 09000 MY -6C-BA-B8 (hex) Sagemcom Broadband SAS -6CBAB8 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - E0-D4-64 (hex) Intel Corporate E0D464 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -100754,12 +102134,6 @@ C0F6C2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Shenzhen Guangdong 518104 CN -08-3A-F2 (hex) Espressif Inc. -083AF2 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 74-3A-20 (hex) New H3C Technologies Co., Ltd 743A20 (base 16) New H3C Technologies Co., Ltd 466 Changhe Road, Binjiang District @@ -100832,12 +102206,6 @@ F469D5 (base 16) IEEE Registration Authority Piscataway NJ 08554 US -8C-C5-B4 (hex) Sagemcom Broadband SAS -8CC5B4 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - F8-1B-04 (hex) Zhong Shan City Richsound Electronic Industrial Ltd F81B04 (base 16) Zhong Shan City Richsound Electronic Industrial Ltd Qunle Industrial Area,East ShaGang Road,GangKou ZhongShan,GuangDong,China @@ -100970,12 +102338,6 @@ C8E265 (base 16) Intel Corporate Guangzhou Guangdong 510530 CN -80-EA-07 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -80EA07 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 80-12-DF (hex) Shenzhen SuperElectron Technology Co.,Ltd. 8012DF (base 16) Shenzhen SuperElectron Technology Co.,Ltd. 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city @@ -101228,12 +102590,6 @@ EC2651 (base 16) Apple, Inc. Sunnyvale CA 94089 US -D4-F8-29 (hex) Sagemcom Broadband SAS -D4F829 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - DC-50-3A (hex) Nanjing Ticom Tech Co., Ltd. DC503A (base 16) Nanjing Ticom Tech Co., Ltd. No.35 Fenghui Road, Yuhuatai District @@ -101498,18 +102854,6 @@ C88314 (base 16) Tempo Communications Palo Alto CA 94304 US -C4-42-68 (hex) CRESTRON ELECTRONICS, INC. -C44268 (base 16) CRESTRON ELECTRONICS, INC. - 15 Volvo Drive - Rockleigh NJ 07647 - US - -40-F5-20 (hex) Espressif Inc. -40F520 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 78-AA-82 (hex) New H3C Technologies Co., Ltd 78AA82 (base 16) New H3C Technologies Co., Ltd 466 Changhe Road, Binjiang District @@ -101792,12 +103136,6 @@ F4032A (base 16) Amazon Technologies Inc. NO.68, Qinghe Middle Street Haidian District, Beijing 100085 CN -10-52-1C (hex) Espressif Inc. -10521C (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 80-30-49 (hex) Liteon Technology Corporation 803049 (base 16) Liteon Technology Corporation 4F, 90, Chien 1 Road @@ -102170,18 +103508,6 @@ DC543D (base 16) ITEL MOBILE LIMITED Chicago IL 60654 US -E0-98-06 (hex) Espressif Inc. -E09806 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -F4-CF-A2 (hex) Espressif Inc. -F4CFA2 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 28-77-F1 (hex) Apple, Inc. 2877F1 (base 16) Apple, Inc. 1 Infinite Loop @@ -102572,12 +103898,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 @@ -102986,12 +104306,6 @@ E419C1 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -B8-66-85 (hex) Sagemcom Broadband SAS -B86685 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - B4-52-A9 (hex) Texas Instruments B452A9 (base 16) Texas Instruments 12500 TI Blvd @@ -103130,12 +104444,6 @@ C40683 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -50-D4-F7 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -50D4F7 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 00-1F-47 (hex) MCS Logic Inc. 001F47 (base 16) MCS Logic Inc. 6F. Samho Center B Bldg., 275-6 @@ -103244,12 +104552,6 @@ D8CA06 (base 16) Titan DataCenters France Piscataway NJ 08554 US -D8-F1-5B (hex) Espressif Inc. -D8F15B (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 6C-F1-7E (hex) Zhejiang Uniview Technologies Co.,Ltd. 6CF17E (base 16) Zhejiang Uniview Technologies Co.,Ltd. No.88,Jiangling Road @@ -104174,12 +105476,6 @@ A89CA4 (base 16) Furrion Limited Dallas TX 75243 US -30-24-78 (hex) Sagemcom Broadband SAS -302478 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 18-4B-DF (hex) Caavo Inc 184BDF (base 16) Caavo Inc 1525 McCarthy Blvd., #1182 @@ -104312,12 +105608,6 @@ E0A509 (base 16) Bitmain Technologies Inc Reno NV 89507 US -D8-A7-56 (hex) Sagemcom Broadband SAS -D8A756 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 54-83-3A (hex) Zyxel Communications Corporation 54833A (base 16) Zyxel Communications Corporation No. 6 Innovation Road II, Science Park @@ -104780,12 +106070,6 @@ F09FFC (base 16) SHARP Corporation Wuhan Hubei 430074 CN -70-0B-01 (hex) Sagemcom Broadband SAS -700B01 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 5C-26-23 (hex) WaveLynx Technologies Corporation 5C2623 (base 16) WaveLynx Technologies Corporation 100 Technology Drive, Building B, Ste 150 @@ -107372,12 +108656,6 @@ A8D579 (base 16) Beijing Chushang Science and Technology Co.,Ltd East Palo Alto CA 94303 US -A0-8E-78 (hex) Sagemcom Broadband SAS -A08E78 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 60-BA-18 (hex) nextLAP GmbH 60BA18 (base 16) nextLAP GmbH Hofmannstr. 61 @@ -108098,12 +109376,6 @@ F015B9 (base 16) PlayFusion Limited Saarbruecken 66121 DE -98-F1-99 (hex) NEC Platforms, Ltd. -98F199 (base 16) NEC Platforms, Ltd. - 2-3 Kandatsukasamachi - Chiyodaku Tokyo 101-8532 - JP - 18-40-A4 (hex) Shenzhen Trylong Smart Science and Technology Co., Ltd. 1840A4 (base 16) Shenzhen Trylong Smart Science and Technology Co., Ltd. 15E, qingdian building, No#6007, Binhe road, futian district @@ -108290,12 +109562,6 @@ CC9470 (base 16) Kinestral Technologies, Inc. San Jose CA 94568 US -30-AE-A4 (hex) Espressif Inc. -30AEA4 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - CC-61-E5 (hex) Motorola Mobility LLC, a Lenovo Company CC61E5 (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza @@ -108932,12 +110198,6 @@ E492FB (base 16) Samsung Electronics Co.,Ltd Gumi Gyeongbuk 730-350 KR -24-7F-20 (hex) Sagemcom Broadband SAS -247F20 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 68-48-98 (hex) Samsung Electronics Co.,Ltd 684898 (base 16) Samsung Electronics Co.,Ltd #94-1,Imsoo-Dong @@ -109340,12 +110600,6 @@ F00786 (base 16) Shandong Bittel Electronics Co., Ltd Seoul 137-724 KR -88-A6-C6 (hex) Sagemcom Broadband SAS -88A6C6 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 94-D4-69 (hex) Cisco Systems, Inc 94D469 (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -109382,24 +110636,6 @@ C0E42D (base 16) TP-LINK TECHNOLOGIES CO.,LTD. Shenzhen Guangdong 518057 CN -44-33-4C (hex) Shenzhen Bilian electronic CO.,LTD -44334C (base 16) Shenzhen Bilian electronic CO.,LTD - NO 268 - Shenzhen Guangdong 518110 - CN - -AC-A2-13 (hex) Shenzhen Bilian electronic CO.,LTD -ACA213 (base 16) Shenzhen Bilian electronic CO.,LTD - NO 268,Fuqian Rd,Jutang Community - shenzhen guangdong 518110 - CN - -3C-33-00 (hex) Shenzhen Bilian electronic CO.,LTD -3C3300 (base 16) Shenzhen Bilian electronic CO.,LTD - NO 268,Fuqian Rd,Jutang Community,Guanlan town , LongHua new district - Shenzhen Guangdong 518110 - CN - 00-90-CC (hex) PLANEX COMMUNICATIONS INC. 0090CC (base 16) PLANEX COMMUNICATIONS INC. 2F FENISSAY Ebisu Bldg @@ -109910,12 +111146,6 @@ B0CF4D (base 16) MI-Zone Technology Ireland Hui Zhou Guang Dong 516006 CN -90-4D-4A (hex) Sagemcom Broadband SAS -904D4A (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 5C-DD-70 (hex) Hangzhou H3C Technologies Co., Limited 5CDD70 (base 16) Hangzhou H3C Technologies Co., Limited 310 Liuhe Road, Zhijiang Science Park @@ -110738,24 +111968,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 +112010,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 @@ -111356,42 +112556,12 @@ C0830A (base 16) 2Wire Inc San Jose CA 95131 US -18-62-2C (hex) Sagemcom Broadband SAS -18622C (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - RUEIL MALMAISON CEDEX Hauts de Seine 92848 - FR - -3C-81-D8 (hex) Sagemcom Broadband SAS -3C81D8 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - RUEIL MALMAISON CEDEX Hauts de Seine 92848 - FR - -40-F2-01 (hex) Sagemcom Broadband SAS -40F201 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison HAUTS DE SEINE 92848 - FR - -D0-84-B0 (hex) Sagemcom Broadband SAS -D084B0 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison HAUTS DE SEINE 92848 - FR - 18-1E-78 (hex) Sagemcom Broadband SAS 181E78 (base 16) Sagemcom Broadband SAS 250 route de l'Empereur Rueil Malmaison HAUTS DE SEINE 92848 FR -00-37-B7 (hex) Sagemcom Broadband SAS -0037B7 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison HAUTS DE SEINE 92848 - FR - 00-54-BD (hex) Swelaser AB 0054BD (base 16) Swelaser AB Tullgårdsgatan 8 @@ -111458,24 +112628,12 @@ B4EED4 (base 16) Texas Instruments Darmstadt 64297 DE -40-65-A3 (hex) Sagemcom Broadband SAS -4065A3 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 00-19-4B (hex) Sagemcom Broadband SAS 00194B (base 16) Sagemcom Broadband SAS Le Ponnant de Paris CEDEX Paris 75512 FR -00-1E-74 (hex) Sagemcom Broadband SAS -001E74 (base 16) Sagemcom Broadband SAS - Le Ponnant de Paris - CEDEX Paris 75512 - FR - 14-91-82 (hex) Belkin International Inc. 149182 (base 16) Belkin International Inc. 12045 E. Waterfront Drive @@ -111548,12 +112706,6 @@ D03761 (base 16) Texas Instruments Dallas TX 75243 US -54-64-D9 (hex) Sagemcom Broadband SAS -5464D9 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - RUEIL MALMAISON CEDEX Hauts de Seine 92848 - FR - 9C-8E-99 (hex) Hewlett Packard 9C8E99 (base 16) Hewlett Packard 11445 Compaq Center Drive @@ -116681,12 +117833,6 @@ A40BED (base 16) Carry Technology Co.,Ltd Jhonghe Dist. New Taipei City 23585 TW -70-23-93 (hex) fos4X GmbH -702393 (base 16) fos4X GmbH - Thalkirchner Str. 210, Geb. 6 - 81371 München - DE - F8-5F-2A (hex) Nokia Corporation F85F2A (base 16) Nokia Corporation Elektroniikkatie 10 @@ -119600,12 +120746,6 @@ A05DE7 (base 16) DIRECTV, Inc. El Segundo CA 90245 US -08-76-18 (hex) ViE Technologies Sdn. Bhd. -087618 (base 16) ViE Technologies Sdn. Bhd. - no. 85-A, Lintang Bayan Lepas 11, - Bayan Lepas Penang 11900 - MY - D0-E4-0B (hex) Wearable Inc. D0E40B (base 16) Wearable Inc. 3825 Charles Dr. @@ -127262,12 +128402,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, @@ -127892,12 +129026,6 @@ B4B5AF (base 16) Minsung Electronics Rogersville TN 37857 US -00-05-21 (hex) Control Microsystems -000521 (base 16) Control Microsystems - 48 Steacie Drive - Ottawa Ontario K2K 2A9 - CA - 00-05-1F (hex) Taijin Media Co., Ltd. 00051F (base 16) Taijin Media Co., Ltd. 640-8 Tungchon-Dong @@ -128312,12 +129440,6 @@ B4B5AF (base 16) Minsung Electronics St. Louis MO 63141 US -00-03-CA (hex) MTS Systems Corp. -0003CA (base 16) MTS Systems Corp. - 3001 Sheldon Drive - Cary, NC 27513 - US - 00-03-CB (hex) SystemGear Co., Ltd. 0003CB (base 16) SystemGear Co., Ltd. 1-9-14 Edobori @@ -131543,8127 +132665,9342 @@ B4B5AF (base 16) Minsung Electronics MILPITAS CA 95035 US -00-1C-7C (hex) PERQ SYSTEMS CORPORATION -001C7C (base 16) PERQ SYSTEMS CORPORATION - 2600 LIBERTY AVENUE - PITTSBURGH PA 15230 - US +00-1C-7C (hex) PERQ SYSTEMS CORPORATION +001C7C (base 16) PERQ SYSTEMS CORPORATION + 2600 LIBERTY AVENUE + PITTSBURGH PA 15230 + US + +00-C0-39 (hex) Teridian Semiconductor Corporation +00C039 (base 16) Teridian Semiconductor Corporation + 6440 Oak Canyon + Irvine CA 92618 + US + +00-C0-A9 (hex) BARRON MCCANN LTD. +00C0A9 (base 16) BARRON MCCANN LTD. + BEMAC HOUSE + UNITED KINGDOM + GB + +00-C0-19 (hex) LEAP TECHNOLOGY, INC. +00C019 (base 16) LEAP TECHNOLOGY, INC. + 20 + BURLINGTON MA 01803 + US + +00-C0-CF (hex) IMATRAN VOIMA OY +00C0CF (base 16) IMATRAN VOIMA OY + IVO + + FI + +00-C0-7D (hex) RISC DEVELOPMENTS LTD. +00C07D (base 16) RISC DEVELOPMENTS LTD. + 117 HATFIELD ROAD + ENGLAND + GB + +00-C0-B5 (hex) CORPORATE NETWORK SYSTEMS,INC. +00C0B5 (base 16) CORPORATE NETWORK SYSTEMS,INC. + 5711 SIX FORKS ROAD--STE #306 + RALEIGH NC 27609 + US + +00-C0-4B (hex) CREATIVE MICROSYSTEMS +00C04B (base 16) CREATIVE MICROSYSTEMS + 9, AVENUE DU CANADA + 91966 LES ULIS---FRANC + FR + +00-C0-B9 (hex) FUNK SOFTWARE, INC. +00C0B9 (base 16) FUNK SOFTWARE, INC. + 222 THIRD STREET + CAMBRIDGE MA 02142 + US + +00-C0-15 (hex) NEW MEDIA CORPORATION +00C015 (base 16) NEW MEDIA CORPORATION + 15375 BARRANCA PARKWAY + IRVINE CA 92718 + US + +00-C0-83 (hex) TRACE MOUNTAIN PRODUCTS, INC. +00C083 (base 16) TRACE MOUNTAIN PRODUCTS, INC. + 1040 EAST BROKAW ROAD + SAN JOSE CA 95131 + US + +00-C0-94 (hex) VMX INC. +00C094 (base 16) VMX INC. + 2115 O'NEL DRIVE + SAN JOSE CA 95131 + US + +00-C0-F9 (hex) Artesyn Embedded Technologies +00C0F9 (base 16) Artesyn Embedded Technologies + 2900 S. Diablo Way Suite 190 + Tempe AZ 85282 + US + +00-C0-75 (hex) XANTE CORPORATION +00C075 (base 16) XANTE CORPORATION + 2559 EMOGENE STREET + MOBILE AL 36606 + US + +00-C0-5B (hex) NETWORKS NORTHWEST, INC. +00C05B (base 16) NETWORKS NORTHWEST, INC. + P.O. BOX 1188 + ISSAQUAH WA 98027 + US + +00-C0-08 (hex) SECO SRL +00C008 (base 16) SECO SRL + VIA CALAMANDREI 91 + + IT + +00-C0-B7 (hex) AMERICAN POWER CONVERSION CORP +00C0B7 (base 16) AMERICAN POWER CONVERSION CORP + 267 BOSTON ROAD #2 + NORTH BILLERICA MA 01862 + US + +00-C0-FC (hex) ELASTIC REALITY, INC. +00C0FC (base 16) ELASTIC REALITY, INC. + 925 STEWART STREET + MADISON WI 53713 + US + +00-C0-BB (hex) FORVAL CREATIVE, INC. +00C0BB (base 16) FORVAL CREATIVE, INC. + 3-27-12 HONGO + + JP + +00-20-D2 (hex) RAD DATA COMMUNICATIONS, LTD. +0020D2 (base 16) RAD DATA COMMUNICATIONS, LTD. + 8 HANECHOSHET STREET + + KZ + +00-20-02 (hex) SERITECH ENTERPRISE CO., LTD. +002002 (base 16) SERITECH ENTERPRISE CO., LTD. + FL. 182, NO. 531-1 + TAIWAN TAIWAN R.O.C. + TW + +00-20-4B (hex) AUTOCOMPUTER CO., LTD. +00204B (base 16) AUTOCOMPUTER CO., LTD. + NO. 18, PEI YUAN ROAD + TAIWAN TAIWAN R.O.C. + TW + +00-20-8C (hex) GALAXY NETWORKS, INC. +00208C (base 16) GALAXY NETWORKS, INC. + 9348 DE SOTO AVENUE + CHATSWORTH CA 91311 + US + +00-20-A6 (hex) Proxim Wireless +0020A6 (base 16) Proxim Wireless + 2114 Ringwood Ave + San Jose CA 95131 + US + +00-C0-43 (hex) STRATACOM +00C043 (base 16) STRATACOM + 1400 PARKMOOR AVENUE + SAN JOSE CA 95126 + US + +00-C0-28 (hex) JASCO CORPORATION +00C028 (base 16) JASCO CORPORATION + 2967-5 ISHIKAWA-CHO, + + JP + +00-C0-8D (hex) TRONIX PRODUCT DEVELOPMENT +00C08D (base 16) TRONIX PRODUCT DEVELOPMENT + 4908 E. MCDOWELL RD. STE.#100 + PHOENIX AZ 85008 + US + +00-C0-2A (hex) OHKURA ELECTRIC CO., LTD. +00C02A (base 16) OHKURA ELECTRIC CO., LTD. + 2-90-20 SHIRAKO WAKO CITY + 351-01 + JP + +00-C0-EF (hex) ABIT CORPORATION +00C0EF (base 16) ABIT CORPORATION + 29-11 HIRAOKA-CHO + 192 + JP + +00-C0-61 (hex) SOLECTEK CORPORATION +00C061 (base 16) SOLECTEK CORPORATION + 6370 NANCY RIDGE DR.-STE.#109 + SAN DIEGO CA 92121 + US + +00-C0-AD (hex) MARBEN COMMUNICATION SYSTEMS +00C0AD (base 16) MARBEN COMMUNICATION SYSTEMS + 1 RUE DU BOIS CHALAND + + FR + +00-C0-7F (hex) NUPON COMPUTING CORP. +00C07F (base 16) NUPON COMPUTING CORP. + 1391 WARNER AVE., -SUITE + TUSTIN CA 92680 + US + +00-C0-57 (hex) MYCO ELECTRONICS +00C057 (base 16) MYCO ELECTRONICS + MUSSERONGRAND 1G + + SE + +00-C0-56 (hex) SOMELEC +00C056 (base 16) SOMELEC + BP 7010 - 95050 + + FR + +00-C0-27 (hex) CIPHER SYSTEMS, INC. +00C027 (base 16) CIPHER SYSTEMS, INC. + 22115 NW Imbrie Dr #285 + Hillsboro OR 97124 + US + +00-C0-5C (hex) ELONEX PLC +00C05C (base 16) ELONEX PLC + 2 APSLEY WAY + UNITED KINGDOM + GB + +00-C0-ED (hex) US ARMY ELECTRONIC +00C0ED (base 16) US ARMY ELECTRONIC + PROVING GROUND + SIERRA VISTA AZ 85635 + US + +00-40-BD (hex) STARLIGHT NETWORKS, INC. +0040BD (base 16) STARLIGHT NETWORKS, INC. + 444 CASTRO STREET STE + MOUNTAIN VIEW CA 94041 + US + +00-40-ED (hex) NETWORK CONTROLS INT'NATL INC. +0040ED (base 16) NETWORK CONTROLS INT'NATL INC. + 9 WOODLAWN GREEN + CHARLOTTE NC 28217 + US + +00-40-E5 (hex) SYBUS CORPORATION +0040E5 (base 16) SYBUS CORPORATION + 2300 TALL PINE DRIVE-STE. #100 + LARGO FL 34641 + US + +00-40-A5 (hex) CLINICOMP INTL. +0040A5 (base 16) CLINICOMP INTL. + 4510 EXECCUTIVE DRIVE-STE.#200 + SAN DIEGO CA 92121 + US + +00-40-05 (hex) ANI COMMUNICATIONS INC. +004005 (base 16) ANI COMMUNICATIONS INC. + 8 ANZIO + IRVINE CA 92714 + US + +00-C0-30 (hex) INTEGRATED ENGINEERING B. V. +00C030 (base 16) INTEGRATED ENGINEERING B. V. + ELLERMANSTRAAT 15 + THE + NL + +00-C0-A6 (hex) EXICOM AUSTRALIA PTY. LTD +00C0A6 (base 16) EXICOM AUSTRALIA PTY. LTD + 44-46 MANDARIN STREET + + AU + +00-C0-CB (hex) CONTROL TECHNOLOGY CORPORATION +00C0CB (base 16) CONTROL TECHNOLOGY CORPORATION + 25 SOUTH STREET + HOPKINTON MA 01748 + US + +00-C0-D1 (hex) COMTREE TECHNOLOGY CORPORATION +00C0D1 (base 16) COMTREE TECHNOLOGY CORPORATION + 5F-7, NO. 1, FU-HSING NORTH RD + TAIWAN R.O.C. + CN + +00-C0-38 (hex) RASTER IMAGE PROCESSING SYSTEM +00C038 (base 16) RASTER IMAGE PROCESSING SYSTEM + 4665 NAUTILUS COURT SOUTH + BOULDER CO 80301 + US + +00-C0-92 (hex) MENNEN MEDICAL INC. +00C092 (base 16) MENNEN MEDICAL INC. + 10123 MAIN STREET + CLARENCE NY 14031-2095 + US + +00-C0-52 (hex) BURR-BROWN +00C052 (base 16) BURR-BROWN + P.O. BOX 11400 + TUCSON AZ 85734-1400 + US + +00-C0-EB (hex) SEH COMPUTERTECHNIK GMBH +00C0EB (base 16) SEH COMPUTERTECHNIK GMBH + Suedring 11 + + DE + +00-40-DB (hex) ADVANCED TECHNICAL SOLUTIONS +0040DB (base 16) ADVANCED TECHNICAL SOLUTIONS + 8050 SEMINOLE OFFICE CENTER + SEMINOLE FL 34642 + US + +00-40-9B (hex) HAL COMPUTER SYSTEMS INC. +00409B (base 16) HAL COMPUTER SYSTEMS INC. + 1315 DELL AVENUE + CAMPBELL CA 95008 + US + +00-40-EB (hex) MARTIN MARIETTA CORPORATION +0040EB (base 16) MARTIN MARIETTA CORPORATION + 12506 LAKE UNDERHILL + ORLANDO FL 32825 + US + +00-C0-32 (hex) I-CUBED LIMITED +00C032 (base 16) I-CUBED LIMITED + UNIT J1, THE POADDOCKS + CB1 4DH CB1 4DH ENGLAND + GB + +00-C0-A5 (hex) DICKENS DATA SYSTEMS +00C0A5 (base 16) DICKENS DATA SYSTEMS + 1175 NORTHMEADOW PKWY-STE #150 + ROSWELL GA 30076 + US + +00-C0-D3 (hex) OLYMPUS IMAGE SYSTEMS, INC. +00C0D3 (base 16) OLYMPUS IMAGE SYSTEMS, INC. + 15271 BARRANCA PARKWAY + IRVINE CA 92718-2201 + US + +00-C0-E8 (hex) PLEXCOM, INC. +00C0E8 (base 16) PLEXCOM, INC. + 65 MORELAND ROADENUYE + SIMI VALLEY CA 93065 + US + +00-40-0E (hex) MEMOTEC, INC. +00400E (base 16) MEMOTEC, INC. + 7755 Henri-Bourassa + MONTREAL, QUEBEC H4S 1P7 + CA + +00-C0-3D (hex) WIESEMANN & THEIS GMBH +00C03D (base 16) WIESEMANN & THEIS GMBH + WITTENER STR. 312 + + DE + +00-40-4C (hex) HYPERTEC PTY LTD. +00404C (base 16) HYPERTEC PTY LTD. + P.O. BOX 1782 + + AU + +00-C0-E0 (hex) DSC COMMUNICATION CORP. +00C0E0 (base 16) DSC COMMUNICATION CORP. + 1000 COIT ROAD, MS#ADVP 3 + PLANO TX 75075 + US + +00-C0-DA (hex) NICE SYSTEMS LTD. +00C0DA (base 16) NICE SYSTEMS LTD. + 3 TEVUOT HA'ARETZ ST + + IL + +00-40-C8 (hex) MILAN TECHNOLOGY CORPORATION +0040C8 (base 16) MILAN TECHNOLOGY CORPORATION + 894 ROSS DRIVE--STE #105 + SUNNYVALE CA 94089 + US + +00-40-BA (hex) ALLIANT COMPUTER SYSTEMS CORP. +0040BA (base 16) ALLIANT COMPUTER SYSTEMS CORP. + ONE MONARCH DRIVE + LITTLETON MA 01460 + US + +00-40-CE (hex) NET-SOURCE, INC. +0040CE (base 16) NET-SOURCE, INC. + 1265 EL CAMINO REAL + SANTA CLARA CA 95050 + US + +00-40-62 (hex) E-SYSTEMS, INC./GARLAND DIV. +004062 (base 16) E-SYSTEMS, INC./GARLAND DIV. + P.O. BOX 660023 + DALLAS TX 75266-0023 + US + +00-40-D9 (hex) AMERICAN MEGATRENDS INC. +0040D9 (base 16) AMERICAN MEGATRENDS INC. + 6145F N BELT PARKWAY + NORCROSS GA 30071 + US + +00-40-21 (hex) RASTER GRAPHICS +004021 (base 16) RASTER GRAPHICS + 285 N. WOLFE ROAD + SUNNYVALE CA 94086 + US + +00-40-C1 (hex) BIZERBA-WERKE WILHEIM KRAUT +0040C1 (base 16) BIZERBA-WERKE WILHEIM KRAUT + GMBH & CO. KG, + D-7460 BALINGEN D-7460 BALINGEN + DE + +00-40-E1 (hex) MARNER INTERNATIONAL, INC. +0040E1 (base 16) MARNER INTERNATIONAL, INC. + 1617 93RD LANE NE + BLAINE MN 55449 + US + +00-40-FE (hex) SYMPLEX COMMUNICATIONS +0040FE (base 16) SYMPLEX COMMUNICATIONS + 5 RESEARCH DRIVE + ANN ARBOR MI 48103 + US + +00-40-D4 (hex) GAGE TALKER CORP. +0040D4 (base 16) GAGE TALKER CORP. + 13680 NE 16TH STREET + BELLEVUE WA 98005 + US + +00-40-38 (hex) TALENT ELECTRIC INCORPORATED +004038 (base 16) TALENT ELECTRIC INCORPORATED + 3RD FL., NO. 260, PA TEH ROAD + TAIWAN TAIWAN R.O.C. + TW + +00-40-D8 (hex) OCEAN OFFICE AUTOMATION LTD. +0040D8 (base 16) OCEAN OFFICE AUTOMATION LTD. + 4TH & 5TH FLOOR, KADER BLDG. + HONG KONG + HK + +00-40-C6 (hex) FIBERNET RESEARCH, INC. +0040C6 (base 16) FIBERNET RESEARCH, INC. + 1 TARA BOULEVARD-#405 + NASHUA NH 03062 + US + +00-40-32 (hex) DIGITAL COMMUNICATIONS +004032 (base 16) DIGITAL COMMUNICATIONS + ASSOCIATES, INC. + SAN JOSE CA 95131 + US + +00-40-C2 (hex) APPLIED COMPUTING DEVICES +0040C2 (base 16) APPLIED COMPUTING DEVICES + ALEPH PARK + TERRE HAUTE IN 47802 + US + +00-40-88 (hex) MOBIUS TECHNOLOGIES, INC. +004088 (base 16) MOBIUS TECHNOLOGIES, INC. + 5835 DOYLE STREET + EMERYVILLE CA 94608 + US + +00-80-AA (hex) MAXPEED +0080AA (base 16) MAXPEED + 1120 CHESS DRIVE + FOSTER CITY CA 94404 + US + +00-C0-50 (hex) TOYO DENKI SEIZO K.K. +00C050 (base 16) TOYO DENKI SEIZO K.K. + 4-6-32 HIGASHIKASHIWAGAYA + KANAGAWA JAPAN 243-04 + JP + +00-40-1C (hex) AST RESEARCH, INC. +00401C (base 16) AST RESEARCH, INC. + MS 2-78 + IRVINE CA 92618 + US + +00-40-0F (hex) DATACOM TECHNOLOGIES +00400F (base 16) DATACOM TECHNOLOGIES + 11001 31ST PLACE WEST + EVERETT WA 98204 + US + +00-40-06 (hex) SAMPO TECHNOLOGY CORPORATION +004006 (base 16) SAMPO TECHNOLOGY CORPORATION + 26-2 TING-HU, + 33334 TAIWAN 33334 TAIWAN R.O.C. + TW + +00-40-34 (hex) BUSTEK CORPORATION +004034 (base 16) BUSTEK CORPORATION + 4151 BURTON DRIVE + SANTA CLARA CA 95054 + US + +00-80-93 (hex) XYRON CORPORATION +008093 (base 16) XYRON CORPORATION + 7864 LILY COURT + CUPERTINO CA 95014 + US + +00-80-92 (hex) Silex Technology, Inc. +008092 (base 16) Silex Technology, Inc. + 2-3-1 Hikaridai, + Kyoto 619-0237 + JP + +00-80-5A (hex) TULIP COMPUTERS INTERNAT'L B.V +00805A (base 16) TULIP COMPUTERS INTERNAT'L B.V + P.O. BOX 3333 + THE + NL + +00-80-7E (hex) SOUTHERN PACIFIC LTD. +00807E (base 16) SOUTHERN PACIFIC LTD. + SANWA BLDG., 2-16-20 + JAPAN JAPAN 220 + JP + +00-80-EF (hex) RATIONAL +0080EF (base 16) RATIONAL + 3320 SCOTT BOULEVARD + SANTA CLARA CA 95054 + US + +00-80-F0 (hex) Panasonic Communications Co., Ltd. +0080F0 (base 16) Panasonic Communications Co., Ltd. + 4-1-62 Minoshima Hakata Fukuoka + 812-8531 + JP + +00-80-51 (hex) FIBERMUX +008051 (base 16) FIBERMUX + 9310 TOPANGA CANYON BLVD. + CHATSWORTH CA 91311 + US + +00-80-C6 (hex) NATIONAL DATACOMM CORPORATION +0080C6 (base 16) NATIONAL DATACOMM CORPORATION + 2F, 28, INDUSTRY EAST 9TH RD. + TAIWAN 30077 TAIWAN 30077 R.O.C. + TW + +00-40-50 (hex) IRONICS, INCORPORATED +004050 (base 16) IRONICS, INCORPORATED + 767 WARREN RD + ITHACA N.Y. 14850 + US + +00-40-47 (hex) WIND RIVER SYSTEMS +004047 (base 16) WIND RIVER SYSTEMS + 1010 ATLANTIC AVENUE + ALAMEDA CA 94501 + US + +00-40-41 (hex) FUJIKURA LTD. +004041 (base 16) FUJIKURA LTD. + 1-5-1, KIBA, KOTO-KU + + JP + +00-80-00 (hex) MULTITECH SYSTEMS, INC. +008000 (base 16) MULTITECH SYSTEMS, INC. + 2205 WOODALE DRIVE + MOUNDS VIEW MN 55112 + US + +00-80-69 (hex) COMPUTONE SYSTEMS +008069 (base 16) COMPUTONE SYSTEMS + 1100 NORTHMEADOW PARKWAY + ROSWELL GA 30076 + US + +00-80-35 (hex) TECHNOLOGY WORKS, INC. +008035 (base 16) TECHNOLOGY WORKS, INC. + 4030 BRAKER LANE #350 + AUSTIN TX 78759 + US + +00-80-4E (hex) APEX COMPUTER COMPANY +00804E (base 16) APEX COMPUTER COMPANY + 4500 150TH AVENUE, NE + REDMOND WA 98052 + US + +00-80-55 (hex) FERMILAB +008055 (base 16) FERMILAB + P.O. BOX 500, MS-234 + BATAVIA IL 60510 + US + +00-80-2A (hex) TEST SYSTEMS & SIMULATIONS INC +00802A (base 16) TEST SYSTEMS & SIMULATIONS INC + 32429 INDUSTRIAL DRIVE + MADISON HEIGHTS MI 48071-1528 + US + +00-80-1D (hex) INTEGRATED INFERENCE MACHINES +00801D (base 16) INTEGRATED INFERENCE MACHINES + 1468 EAST KATELLA + ANAHEIM CA 92805 + US + +00-80-C0 (hex) PENRIL DATACOMM +0080C0 (base 16) PENRIL DATACOMM + 1300 QUINCE ORCHARD BLVD. + GAITHERSBURG MD 20878 + US + +00-80-75 (hex) PARSYTEC GMBH +008075 (base 16) PARSYTEC GMBH + JUELICHER STR. 338 + F.R. + DE + +00-80-ED (hex) IQ TECHNOLOGIES, INC. +0080ED (base 16) IQ TECHNOLOGIES, INC. + 11811 NE FIRST STREET + BELLEVUE WA 98005 + US + +00-80-9A (hex) NOVUS NETWORKS LTD +00809A (base 16) NOVUS NETWORKS LTD + JOHN SCOTT HOUSE + ENGLAND + GB + +00-80-4A (hex) PRO-LOG +00804A (base 16) PRO-LOG + 12 UPPER RAGSDALE DRIVE + MONTEREY CA 93940 + US + +00-80-04 (hex) ANTLOW COMMUNICATIONS, LTD. +008004 (base 16) ANTLOW COMMUNICATIONS, LTD. + 4 COLTHROP WAY + ENGLAND + GB + +00-80-D0 (hex) COMPUTER PERIPHERALS, INC. +0080D0 (base 16) COMPUTER PERIPHERALS, INC. + 667 RANCHO CONEJO BLVD. + NEWBURY PARK CA 91320 + US + +00-80-24 (hex) KALPANA, INC. +008024 (base 16) KALPANA, INC. + 1154 EAST ARQUES AVENUE + SUNNYVALE CA 94086 + US + +00-80-40 (hex) JOHN FLUKE MANUFACTURING CO. +008040 (base 16) JOHN FLUKE MANUFACTURING CO. + P.O. BOX C9090-M/S 244F + EVERETT WA 98206 + US + +00-80-21 (hex) Alcatel Canada Inc. +008021 (base 16) Alcatel Canada Inc. + 349 Terry Fox Drive + Kanata Ontario K2K 2V6 + CA + +00-80-E8 (hex) CUMULUS CORPORATIION +0080E8 (base 16) CUMULUS CORPORATIION + 23500 MERCANTILE ROAD + CLEVELAND OH 44122 + US + +00-00-66 (hex) TALARIS SYSTEMS, INC. +000066 (base 16) TALARIS SYSTEMS, INC. + 11339 SORRENTO VALLEY ROAD + SAN DIEGO CA 92121 + US + +00-00-49 (hex) APRICOT COMPUTERS, LTD +000049 (base 16) APRICOT COMPUTERS, LTD + 90 VINCENT DRIVE + ENGLAND + GB + +00-00-FA (hex) MICROSAGE COMPUTER SYSTEMS INC +0000FA (base 16) MICROSAGE COMPUTER SYSTEMS INC + 680 SOUTH ROCK BLVD + RENO NE 89502 + US + +00-00-D4 (hex) PURE DATA LTD. +0000D4 (base 16) PURE DATA LTD. + 200 WEST BEAVER CREEK ROAD + L4B 1B4 + CA + +00-00-19 (hex) APPLIED DYNAMICS INTERNATIONAL +000019 (base 16) APPLIED DYNAMICS INTERNATIONAL + 3800 STONE SCHOOL ROAD + ANN ARBOR MI 48104-2499 + US + +00-00-AB (hex) LOGIC MODELING CORPORATION +0000AB (base 16) LOGIC MODELING CORPORATION + 1520 MCCANDLESS DRIVE + MILPITAS CA 95035 + US + +00-80-F2 (hex) RAYCOM SYSTEMS INC +0080F2 (base 16) RAYCOM SYSTEMS INC + 16525 SHERMAN WAY #C-8 + VAN NUYS CA 91406 + US + +00-80-BD (hex) THE FURUKAWA ELECTRIC CO., LTD +0080BD (base 16) THE FURUKAWA ELECTRIC CO., LTD + 6-1, MARUNOUCHI 2-CHOME + 100 + JP + +00-80-25 (hex) Telit Wireless Solutions GmbH +008025 (base 16) Telit Wireless Solutions GmbH + Mendelssohnstrasse15D + Hamburg 22761 + DE + +00-80-EA (hex) ADVA Optical Networking Ltd. +0080EA (base 16) ADVA Optical Networking Ltd. + ADVAntage House + York YO30 4RY + GB + +00-80-0D (hex) VOSSWINKEL F.U. +00800D (base 16) VOSSWINKEL F.U. + AM JOSTENHOF 15 + + DE + +00-80-D1 (hex) KIMTRON CORPORATION +0080D1 (base 16) KIMTRON CORPORATION + 1709 JUNCTION COURT + SAN JOSE CA 95112 + US + +00-00-1E (hex) TELSIST INDUSTRIA ELECTRONICA +00001E (base 16) TELSIST INDUSTRIA ELECTRONICA + RUA VILHENA DE MORAES, 380 + + BR + +00-00-50 (hex) RADISYS CORPORATION +000050 (base 16) RADISYS CORPORATION + 15025 S.W. KOLL PARKWAY + BEAVERTON OR 97006-6056 + US + +00-80-2E (hex) CASTLE ROCK COMPUTING +00802E (base 16) CASTLE ROCK COMPUTING + 20837 BOYCE LANE + SARATOGA CA 95070-4806 + US + +00-00-4F (hex) LOGICRAFT, INC. +00004F (base 16) LOGICRAFT, INC. + 22 COTTON ROAD + NASHUA NH 03063 + US + +00-00-15 (hex) DATAPOINT CORPORATION +000015 (base 16) DATAPOINT CORPORATION + 9725 DATAPOINT DRIVE + SAN ANTONIO TX 78284 + US + +00-00-1C (hex) BELL TECHNOLOGIES +00001C (base 16) BELL TECHNOLOGIES + 330 WARREN AVENUE + FREMONT CA 94539 + US + +00-00-34 (hex) NETWORK RESOURCES CORPORATION +000034 (base 16) NETWORK RESOURCES CORPORATION + 61 EAST DAGGETT DRIVE + SAN JOSE CA 95134 + US + +00-00-22 (hex) VISUAL TECHNOLOGY INC. +000022 (base 16) VISUAL TECHNOLOGY INC. + 1703 MIDDLESEX STREET + LOWELL MA 01851 + US + +00-00-B5 (hex) DATABILITY SOFTWARE SYS. INC. +0000B5 (base 16) DATABILITY SOFTWARE SYS. INC. + ONE PALMER TERRACE + CARLSTADT NJ 07072 + US + +00-00-E0 (hex) QUADRAM CORP. +0000E0 (base 16) QUADRAM CORP. + ONE QUAD WAY + NORCROSS GA 30093 + US + +00-00-27 (hex) JAPAN RADIO COMPANY +000027 (base 16) JAPAN RADIO COMPANY + LABORATORY + + JP + +00-00-E8 (hex) ACCTON TECHNOLOGY CORP. +0000E8 (base 16) ACCTON TECHNOLOGY CORP. + 46750 FREMONT BLVD. #104 + FREMONT CA 94538 + US + +00-00-2F (hex) TIMEPLEX INC. +00002F (base 16) TIMEPLEX INC. + 530 CHESTNUT RIDGE ROAD + WOODCLIFF LAKE NJ 07675 + US + +00-00-E6 (hex) APTOR PRODUITS DE COMM INDUST +0000E6 (base 16) APTOR PRODUITS DE COMM INDUST + 61, CHEMIN DU VIEUX-CHENE + + FR + +00-00-9A (hex) RC COMPUTER A/S +00009A (base 16) RC COMPUTER A/S + LAUTRUPBJERG 1 + + DK + +00-00-4B (hex) ICL DATA OY +00004B (base 16) ICL DATA OY + KUTOMOTIE 16-18 + + FI + +00-80-42 (hex) Artesyn Embedded Technologies +008042 (base 16) Artesyn Embedded Technologies + 2900 S. Diablo Way + Tempe AZ 85282 + US + +00-80-AC (hex) IMLOGIX, DIVISION OF GENESYS +0080AC (base 16) IMLOGIX, DIVISION OF GENESYS + 1900 SUMMIT TOWER BLVD.STE#770 + ORLANDO FL 32810 + US + +00-00-80 (hex) CRAY COMMUNICATIONS A/S +000080 (base 16) CRAY COMMUNICATIONS A/S + SMEDEHOLM 12-14 + + DK + +08-00-89 (hex) Kinetics +080089 (base 16) Kinetics + + Walnut Creek CA + US + +08-00-86 (hex) KONICA MINOLTA HOLDINGS, INC. +080086 (base 16) KONICA MINOLTA HOLDINGS, INC. + 1-6-1, Marunouchi, + Tokyo 100-0005 + JP + +08-00-83 (hex) Seiko Instruments Inc. +080083 (base 16) Seiko Instruments Inc. + 8, Nakase 1-chome Mihama-ku + Chiba-shi Chiba 261-8507 + JP + +00-00-73 (hex) SIECOR CORPORATION +000073 (base 16) SIECOR CORPORATION + P.O. BOX 13625 + RESEARCH TRIANGLE PK NC 27709 + US + +00-00-B9 (hex) MCDONNELL DOUGLAS COMPUTER SYS +0000B9 (base 16) MCDONNELL DOUGLAS COMPUTER SYS + DIV MCDONNELL DOUGLAS INF SYS + ENGLAND + GB + +00-00-BF (hex) SYMMETRIC COMPUTER SYSTEMS +0000BF (base 16) SYMMETRIC COMPUTER SYSTEMS + 1620 OAKLAND ROAD SUITE D-200 + SAN JOSE CA 95131 + US + +00-00-2D (hex) CHROMATICS INC +00002D (base 16) CHROMATICS INC + 2558 MOUNTAIN INDUSTRIAL BLVD + TUCKER GA 30084 + US + +00-00-59 (hex) Hellige GMBH +000059 (base 16) Hellige GMBH + Heinrich-von-Stephan-Str. 4 + West Gernany + DE + +00-00-69 (hex) CONCORD COMMUNICATIONS INC +000069 (base 16) CONCORD COMMUNICATIONS INC + 753 FOREST STREET + MARLBOROUGH MA 01752 + US + +00-00-E7 (hex) Star Gate Technologies +0000E7 (base 16) Star Gate Technologies + 29300 Aurora Road + Solon OH 44139 + US + +00-00-4D (hex) DCI CORPORATION +00004D (base 16) DCI CORPORATION + 64J PRINCETON-HIGHTSTOWN RD + PRINCETON JUNCTION NJ 08550 + US + +00-00-6F (hex) Madge Ltd. +00006F (base 16) Madge Ltd. + Madge House + Maindenhead Berkshire SL6 2HP + GB + +00-00-78 (hex) LABTAM LIMITED +000078 (base 16) LABTAM LIMITED + 43 MALCOLM ROAD P.O. BOX297 + + AU + +00-00-5A (hex) SysKonnect GmbH +00005A (base 16) SysKonnect GmbH + SIEMENSSTRAßE 23 + + DE + +00-00-71 (hex) ADRA SYSTEMS INC. +000071 (base 16) ADRA SYSTEMS INC. + 59 TECHNOLOGY DRIVE + LOWELL MA 01851 + US + +00-00-23 (hex) ABB INDUSTRIAL SYSTEMS AB +000023 (base 16) ABB INDUSTRIAL SYSTEMS AB + DEPT. SEISY/LKSB + + SE -00-C0-39 (hex) Teridian Semiconductor Corporation -00C039 (base 16) Teridian Semiconductor Corporation - 6440 Oak Canyon - Irvine CA 92618 +00-00-18 (hex) WEBSTER COMPUTER CORPORATION +000018 (base 16) WEBSTER COMPUTER CORPORATION + 16040 REDWOOD LODGE ROAD + LOS GATOS CA 95033-9260 US -00-C0-A9 (hex) BARRON MCCANN LTD. -00C0A9 (base 16) BARRON MCCANN LTD. - BEMAC HOUSE +00-00-D5 (hex) MICROGNOSIS INTERNATIONAL +0000D5 (base 16) MICROGNOSIS INTERNATIONAL + 63 QUEEN VICTORIA STREET UNITED KINGDOM GB -00-C0-19 (hex) LEAP TECHNOLOGY, INC. -00C019 (base 16) LEAP TECHNOLOGY, INC. - 20 - BURLINGTON MA 01803 +00-00-3A (hex) CHYRON CORPORATION +00003A (base 16) CHYRON CORPORATION + 265 SPAGNOLI ROAD + MELVILLE NY 11747 US -00-C0-CF (hex) IMATRAN VOIMA OY -00C0CF (base 16) IMATRAN VOIMA OY - IVO - - FI - -00-C0-7D (hex) RISC DEVELOPMENTS LTD. -00C07D (base 16) RISC DEVELOPMENTS LTD. - 117 HATFIELD ROAD - ENGLAND - GB - -00-C0-B5 (hex) CORPORATE NETWORK SYSTEMS,INC. -00C0B5 (base 16) CORPORATE NETWORK SYSTEMS,INC. - 5711 SIX FORKS ROAD--STE #306 - RALEIGH NC 27609 +00-00-BE (hex) THE NTI GROUP +0000BE (base 16) THE NTI GROUP + 4701 PATRICK HENRY DRIVE + SANTA CLARA CA 95054 US -00-C0-4B (hex) CREATIVE MICROSYSTEMS -00C04B (base 16) CREATIVE MICROSYSTEMS - 9, AVENUE DU CANADA - 91966 LES ULIS---FRANC - FR +00-00-D9 (hex) NIPPON TELEGRAPH & TELEPHONE +0000D9 (base 16) NIPPON TELEGRAPH & TELEPHONE + CORPORATION (NTT) + TOKYO 100-8116 + JP -00-C0-B9 (hex) FUNK SOFTWARE, INC. -00C0B9 (base 16) FUNK SOFTWARE, INC. - 222 THIRD STREET - CAMBRIDGE MA 02142 +08-00-24 (hex) 10NET COMMUNICATIONS/DCA +080024 (base 16) 10NET COMMUNICATIONS/DCA + 7777 WASHINGTON VILLAGE DR. + DAYTON OH 45459-3957 US -00-C0-15 (hex) NEW MEDIA CORPORATION -00C015 (base 16) NEW MEDIA CORPORATION - 15375 BARRANCA PARKWAY - IRVINE CA 92718 +08-00-22 (hex) NBI INC. +080022 (base 16) NBI INC. + 3450 MITCHELL LANE + BOULDER CO 80301 US -00-C0-83 (hex) TRACE MOUNTAIN PRODUCTS, INC. -00C083 (base 16) TRACE MOUNTAIN PRODUCTS, INC. - 1040 EAST BROKAW ROAD - SAN JOSE CA 95131 +08-00-20 (hex) Oracle Corporation +080020 (base 16) Oracle Corporation + 17 Network Circle + Menlo Park CA 95025 US -00-C0-94 (hex) VMX INC. -00C094 (base 16) VMX INC. - 2115 O'NEL DRIVE - SAN JOSE CA 95131 - US +08-00-1F (hex) SHARP CORPORATION +08001F (base 16) SHARP CORPORATION + ENGINEERING DEPARTMENT 6 + NARA 639-11 + JP -00-C0-F9 (hex) Artesyn Embedded Technologies -00C0F9 (base 16) Artesyn Embedded Technologies - 2900 S. Diablo Way Suite 190 - Tempe AZ 85282 +02-07-01 (hex) RACAL-DATACOM +020701 (base 16) RACAL-DATACOM + LAN INTERNETWORKING DIVISION + BOXBOROUGH MA 01719 US -00-C0-75 (hex) XANTE CORPORATION -00C075 (base 16) XANTE CORPORATION - 2559 EMOGENE STREET - MOBILE AL 36606 - US +08-00-06 (hex) SIEMENS AG +080006 (base 16) SIEMENS AG + Siemens IT Solutions and Services, SIS GO QM O + POB 2353 Fuerth 90713 + DE -00-C0-5B (hex) NETWORKS NORTHWEST, INC. -00C05B (base 16) NETWORKS NORTHWEST, INC. - P.O. BOX 1188 - ISSAQUAH WA 98027 +08-00-2A (hex) MOSAIC TECHNOLOGIES INC. +08002A (base 16) MOSAIC TECHNOLOGIES INC. + 47 MANNING ROAD + BILLERICA MA 01821-3970 US -00-C0-08 (hex) SECO SRL -00C008 (base 16) SECO SRL - VIA CALAMANDREI 91 +08-00-13 (hex) Exxon +080013 (base 16) Exxon + - IT + US -00-C0-B7 (hex) AMERICAN POWER CONVERSION CORP -00C0B7 (base 16) AMERICAN POWER CONVERSION CORP - 267 BOSTON ROAD #2 - NORTH BILLERICA MA 01862 +02-1C-7C (hex) PERQ SYSTEMS CORPORATION +021C7C (base 16) PERQ SYSTEMS CORPORATION + 2600 LIBERTY AVENUE + PITTSBURGH PA 15230 US -00-C0-FC (hex) ELASTIC REALITY, INC. -00C0FC (base 16) ELASTIC REALITY, INC. - 925 STEWART STREET - MADISON WI 53713 +08-00-61 (hex) JAROGATE LTD. +080061 (base 16) JAROGATE LTD. + 197-213 LYHAM ROAD + UNITED KINGDOM + GB + +08-00-5F (hex) SABER TECHNOLOGY CORP. +08005F (base 16) SABER TECHNOLOGY CORP. + 2381 BERING DRIVE + SAN JOSE CA 95131-1125 US -00-C0-BB (hex) FORVAL CREATIVE, INC. -00C0BB (base 16) FORVAL CREATIVE, INC. - 3-27-12 HONGO - - JP +08-00-58 (hex) SYSTEMS CONCEPTS +080058 (base 16) SYSTEMS CONCEPTS + 520 THIRD STREET + SAN FRANCISCO CA 94107 + US -00-20-D2 (hex) RAD DATA COMMUNICATIONS, LTD. -0020D2 (base 16) RAD DATA COMMUNICATIONS, LTD. - 8 HANECHOSHET STREET +04-E0-C4 (hex) TRIUMPH-ADLER AG +04E0C4 (base 16) TRIUMPH-ADLER AG + HUNDINGSTRAßE 11B - KZ - -00-20-02 (hex) SERITECH ENTERPRISE CO., LTD. -002002 (base 16) SERITECH ENTERPRISE CO., LTD. - FL. 182, NO. 531-1 - TAIWAN TAIWAN R.O.C. - TW + DE -00-20-4B (hex) AUTOCOMPUTER CO., LTD. -00204B (base 16) AUTOCOMPUTER CO., LTD. - NO. 18, PEI YUAN ROAD - TAIWAN TAIWAN R.O.C. - TW +08-00-49 (hex) UNIVATION +080049 (base 16) UNIVATION + 1037 NORTH FAIR OAKS AVE. + SUNNYVALE CA 94089 + US -00-20-8C (hex) GALAXY NETWORKS, INC. -00208C (base 16) GALAXY NETWORKS, INC. - 9348 DE SOTO AVENUE - CHATSWORTH CA 91311 +00-00-05 (hex) XEROX CORPORATION +000005 (base 16) XEROX CORPORATION + M/S 105-50C + WEBSTER NY 14580 US -00-20-A6 (hex) Proxim Wireless -0020A6 (base 16) Proxim Wireless - 2114 Ringwood Ave - San Jose CA 95131 +00-DD-08 (hex) UNGERMANN-BASS INC. +00DD08 (base 16) UNGERMANN-BASS INC. + 3900 FREEDOM CIRCLE + SANTA CLARA CA 95054 US -00-C0-43 (hex) STRATACOM -00C043 (base 16) STRATACOM - 1400 PARKMOOR AVENUE - SAN JOSE CA 95126 +AA-00-00 (hex) DIGITAL EQUIPMENT CORPORATION +AA0000 (base 16) DIGITAL EQUIPMENT CORPORATION + LKG 1-2/A19 + LITTLETON MA 01460-1289 US -00-C0-28 (hex) JASCO CORPORATION -00C028 (base 16) JASCO CORPORATION - 2967-5 ISHIKAWA-CHO, - - JP +AA-00-01 (hex) DIGITAL EQUIPMENT CORPORATION +AA0001 (base 16) DIGITAL EQUIPMENT CORPORATION + LKG 1-2/A19 + LITTLETON MA 01460-1289 + US -00-C0-8D (hex) TRONIX PRODUCT DEVELOPMENT -00C08D (base 16) TRONIX PRODUCT DEVELOPMENT - 4908 E. MCDOWELL RD. STE.#100 - PHOENIX AZ 85008 +AA-00-02 (hex) DIGITAL EQUIPMENT CORPORATION +AA0002 (base 16) DIGITAL EQUIPMENT CORPORATION + LKG 1-2/A19 + LITTLETON MA 01460-1289 US -00-C0-2A (hex) OHKURA ELECTRIC CO., LTD. -00C02A (base 16) OHKURA ELECTRIC CO., LTD. - 2-90-20 SHIRAKO WAKO CITY - 351-01 - JP +08-00-14 (hex) EXCELAN +080014 (base 16) EXCELAN + 1599 FLICKINGER AVENUE + SAN JOSE CA 95131 + US -00-C0-EF (hex) ABIT CORPORATION -00C0EF (base 16) ABIT CORPORATION - 29-11 HIRAOKA-CHO - 192 - JP +08-00-65 (hex) GENRAD INC. +080065 (base 16) GENRAD INC. + 300 BAKER AVENUE + CONCORD MA 01742 + US -00-C0-61 (hex) SOLECTEK CORPORATION -00C061 (base 16) SOLECTEK CORPORATION - 6370 NANCY RIDGE DR.-STE.#109 - SAN DIEGO CA 92121 +00-00-07 (hex) XEROX CORPORATION +000007 (base 16) XEROX CORPORATION + M/S 105-50C + WEBSTER NY 14580 US -00-C0-AD (hex) MARBEN COMMUNICATION SYSTEMS -00C0AD (base 16) MARBEN COMMUNICATION SYSTEMS - 1 RUE DU BOIS CHALAND +00-80-1F (hex) KRUPP ATLAS ELECTRONIK GMBH +00801F (base 16) KRUPP ATLAS ELECTRONIK GMBH + P.O. BOX 448545 - FR + DE -00-C0-7F (hex) NUPON COMPUTING CORP. -00C07F (base 16) NUPON COMPUTING CORP. - 1391 WARNER AVE., -SUITE - TUSTIN CA 92680 +02-AA-3C (hex) OLIVETTI TELECOMM SPA (OLTECO) +02AA3C (base 16) OLIVETTI TELECOMM SPA (OLTECO) + 20300 STEVENS CREEK BLVD. + CUPERTINO CA 95014 US -00-C0-57 (hex) MYCO ELECTRONICS -00C057 (base 16) MYCO ELECTRONICS - MUSSERONGRAND 1G - - SE - -00-C0-56 (hex) SOMELEC -00C056 (base 16) SOMELEC - BP 7010 - 95050 +08-00-59 (hex) A/S MYCRON +080059 (base 16) A/S MYCRON + PO BOX 6199 - FR + NO -00-C0-27 (hex) CIPHER SYSTEMS, INC. -00C027 (base 16) CIPHER SYSTEMS, INC. - 22115 NW Imbrie Dr #285 - Hillsboro OR 97124 +08-00-08 (hex) BOLT BERANEK AND NEWMAN INC. +080008 (base 16) BOLT BERANEK AND NEWMAN INC. + 70 FAWCETT STREET + CAMBRIDGE MA 02138 US -00-C0-5C (hex) ELONEX PLC -00C05C (base 16) ELONEX PLC - 2 APSLEY WAY - UNITED KINGDOM +84-87-FF (hex) Shenzhen Skyworth Digital Technology CO., Ltd +8487FF (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + +64-57-25 (hex) Hui Zhou Gaoshengda Technology Co.,LTD +645725 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD + No.2,Jin-da Road,Huinan Industrial Park + Hui Zhou Guangdong 516025 + CN + +00-80-E9 (hex) Madge Ltd. +0080E9 (base 16) Madge Ltd. + Madge House + Maindenhead Berkshire SL6 2HP GB -00-C0-ED (hex) US ARMY ELECTRONIC -00C0ED (base 16) US ARMY ELECTRONIC - PROVING GROUND - SIERRA VISTA AZ 85635 - US +00-40-D6 (hex) LOCAMATION B.V. +0040D6 (base 16) LOCAMATION B.V. + POSTBOX 360 + HOLLAND + NL -00-40-BD (hex) STARLIGHT NETWORKS, INC. -0040BD (base 16) STARLIGHT NETWORKS, INC. - 444 CASTRO STREET STE - MOUNTAIN VIEW CA 94041 +08-00-4B (hex) Planning Research Corp. +08004B (base 16) Planning Research Corp. + 1508 Kennedy Drive + Bellvue NE 68005 US -00-40-ED (hex) NETWORK CONTROLS INT'NATL INC. -0040ED (base 16) NETWORK CONTROLS INT'NATL INC. - 9 WOODLAWN GREEN - CHARLOTTE NC 28217 +04-25-E8 (hex) Texas Instruments +0425E8 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -00-40-E5 (hex) SYBUS CORPORATION -0040E5 (base 16) SYBUS CORPORATION - 2300 TALL PINE DRIVE-STE. #100 - LARGO FL 34641 +28-3C-90 (hex) Texas Instruments +283C90 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -00-40-A5 (hex) CLINICOMP INTL. -0040A5 (base 16) CLINICOMP INTL. - 4510 EXECCUTIVE DRIVE-STE.#200 - SAN DIEGO CA 92121 +E4-FA-5B (hex) Texas Instruments +E4FA5B (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -00-40-05 (hex) ANI COMMUNICATIONS INC. -004005 (base 16) ANI COMMUNICATIONS INC. - 8 ANZIO - IRVINE CA 92714 +00-00-09 (hex) XEROX CORPORATION +000009 (base 16) XEROX CORPORATION + M/S 105-50C + WEBSTER NY 14580 US -00-C0-30 (hex) INTEGRATED ENGINEERING B. V. -00C030 (base 16) INTEGRATED ENGINEERING B. V. - ELLERMANSTRAAT 15 - THE - NL +44-C3-B6 (hex) HUAWEI TECHNOLOGIES CO.,LTD +44C3B6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -00-C0-A6 (hex) EXICOM AUSTRALIA PTY. LTD -00C0A6 (base 16) EXICOM AUSTRALIA PTY. LTD - 44-46 MANDARIN STREET - - AU +E8-F7-2F (hex) HUAWEI TECHNOLOGIES CO.,LTD +E8F72F (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -00-C0-CB (hex) CONTROL TECHNOLOGY CORPORATION -00C0CB (base 16) CONTROL TECHNOLOGY CORPORATION - 25 SOUTH STREET - HOPKINTON MA 01748 +50-A1-F3 (hex) Huawei Device Co., Ltd. +50A1F3 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +DC-10-57 (hex) Apple, Inc. +DC1057 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-C0-D1 (hex) COMTREE TECHNOLOGY CORPORATION -00C0D1 (base 16) COMTREE TECHNOLOGY CORPORATION - 5F-7, NO. 1, FU-HSING NORTH RD - TAIWAN R.O.C. +30-D8-75 (hex) Apple, Inc. +30D875 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +48-5F-DF (hex) zte corporation +485FDF (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -00-C0-38 (hex) RASTER IMAGE PROCESSING SYSTEM -00C038 (base 16) RASTER IMAGE PROCESSING SYSTEM - 4665 NAUTILUS COURT SOUTH - BOULDER CO 80301 +6C-15-44 (hex) Microsoft Corporation +6C1544 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 US -00-C0-92 (hex) MENNEN MEDICAL INC. -00C092 (base 16) MENNEN MEDICAL INC. - 10123 MAIN STREET - CLARENCE NY 14031-2095 +68-3E-C0 (hex) Apple, Inc. +683EC0 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-C0-52 (hex) BURR-BROWN -00C052 (base 16) BURR-BROWN - P.O. BOX 11400 - TUCSON AZ 85734-1400 - US +3C-55-DB (hex) Shenzhen Skyworth Digital Technology CO., Ltd +3C55DB (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN -00-C0-EB (hex) SEH COMPUTERTECHNIK GMBH -00C0EB (base 16) SEH COMPUTERTECHNIK GMBH - Suedring 11 - - DE +F0-F8-4A (hex) BUFFALO.INC +F0F84A (base 16) BUFFALO.INC + AKAMONDORI Bld.,30-20,Ohsu 3-chome,Naka-ku + Nagoya Aichi Pref. 460-8315 + JP -00-40-DB (hex) ADVANCED TECHNICAL SOLUTIONS -0040DB (base 16) ADVANCED TECHNICAL SOLUTIONS - 8050 SEMINOLE OFFICE CENTER - SEMINOLE FL 34642 +EC-6E-79 (hex) InHand Networks, INC. +EC6E79 (base 16) InHand Networks, INC. + 43671 Trade Center Place Suite 100 + Dulles VA 20166 US -00-40-9B (hex) HAL COMPUTER SYSTEMS INC. -00409B (base 16) HAL COMPUTER SYSTEMS INC. - 1315 DELL AVENUE - CAMPBELL CA 95008 +A4-00-4E (hex) Cisco Systems, Inc +A4004E (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -00-40-EB (hex) MARTIN MARIETTA CORPORATION -0040EB (base 16) MARTIN MARIETTA CORPORATION - 12506 LAKE UNDERHILL - ORLANDO FL 32825 +2C-9E-E0 (hex) Cavli Inc. +2C9EE0 (base 16) Cavli Inc. + 99 South Almaden Blvd + San Jose CA 95113 US -00-C0-32 (hex) I-CUBED LIMITED -00C032 (base 16) I-CUBED LIMITED - UNIT J1, THE POADDOCKS - CB1 4DH CB1 4DH ENGLAND - GB +64-C1-7E (hex) cheilelectric +64C17E (base 16) cheilelectric + 555, Eulsukdo-daero, Saha-gu, Busan, Republic of Korea + pusan 49437 + KR -00-C0-A5 (hex) DICKENS DATA SYSTEMS -00C0A5 (base 16) DICKENS DATA SYSTEMS - 1175 NORTHMEADOW PKWY-STE #150 - ROSWELL GA 30076 - US +B0-2E-E0 (hex) Huawei Device Co., Ltd. +B02EE0 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -00-C0-D3 (hex) OLYMPUS IMAGE SYSTEMS, INC. -00C0D3 (base 16) OLYMPUS IMAGE SYSTEMS, INC. - 15271 BARRANCA PARKWAY - IRVINE CA 92718-2201 - US +A4-37-3E (hex) Huawei Device Co., Ltd. +A4373E (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -00-C0-E8 (hex) PLEXCOM, INC. -00C0E8 (base 16) PLEXCOM, INC. - 65 MORELAND ROADENUYE - SIMI VALLEY CA 93065 - US +C4-4F-5F (hex) Huawei Device Co., Ltd. +C44F5F (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -00-40-0E (hex) MEMOTEC, INC. -00400E (base 16) MEMOTEC, INC. - 7755 Henri-Bourassa - MONTREAL, QUEBEC H4S 1P7 - CA +78-5F-28 (hex) EM Microelectronic +785F28 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH -00-C0-3D (hex) WIESEMANN & THEIS GMBH -00C03D (base 16) WIESEMANN & THEIS GMBH - WITTENER STR. 312 - - DE +38-EF-E3 (hex) INGENICO TERMINALS SAS +38EFE3 (base 16) INGENICO TERMINALS SAS + 13-17 Rue Pagès + Suresnes 92150 + FR -00-40-4C (hex) HYPERTEC PTY LTD. -00404C (base 16) HYPERTEC PTY LTD. - P.O. BOX 1782 - - AU +FC-8D-13 (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD +FC8D13 (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD + 19-22# Building, Star-net Science Plaza, Juyuanzhou, + FUZHOU FUJIAN 350002 + CN -00-C0-E0 (hex) DSC COMMUNICATION CORP. -00C0E0 (base 16) DSC COMMUNICATION CORP. - 1000 COIT ROAD, MS#ADVP 3 - PLANO TX 75075 - US +9C-B1-DC (hex) Earda Technologies co Ltd +9CB1DC (base 16) Earda Technologies co Ltd + Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District + Guangzhou Guangdong 511455 + CN -00-C0-DA (hex) NICE SYSTEMS LTD. -00C0DA (base 16) NICE SYSTEMS LTD. - 3 TEVUOT HA'ARETZ ST - - IL +24-04-62 (hex) Siemens Energy Global GmbH & Co.KG - GT PRM +240462 (base 16) Siemens Energy Global GmbH & Co.KG - GT PRM + Paulsternstrasse 26 + Berlin Berlin 13629 + DE -00-40-C8 (hex) MILAN TECHNOLOGY CORPORATION -0040C8 (base 16) MILAN TECHNOLOGY CORPORATION - 894 ROSS DRIVE--STE #105 - SUNNYVALE CA 94089 - US +38-88-71 (hex) ASKEY COMPUTER CORP +388871 (base 16) ASKEY COMPUTER CORP + 10F,No.119,JIANKANG RD,ZHONGHE DIST + NEW TAIPEI TAIWAN 23585 + TW -00-40-BA (hex) ALLIANT COMPUTER SYSTEMS CORP. -0040BA (base 16) ALLIANT COMPUTER SYSTEMS CORP. - ONE MONARCH DRIVE - LITTLETON MA 01460 - US +BC-F8-7E (hex) Arcadyan Corporation +BCF87E (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW -00-40-CE (hex) NET-SOURCE, INC. -0040CE (base 16) NET-SOURCE, INC. - 1265 EL CAMINO REAL - SANTA CLARA CA 95050 - US +28-4E-E9 (hex) mercury corperation +284EE9 (base 16) mercury corperation + 90,gajaeul-ro,seo-gu,incheon + incheon 22830 + KR -00-40-62 (hex) E-SYSTEMS, INC./GARLAND DIV. -004062 (base 16) E-SYSTEMS, INC./GARLAND DIV. - P.O. BOX 660023 - DALLAS TX 75266-0023 - US +58-E3-59 (hex) Interroll Software & Electronics GmbH +58E359 (base 16) Interroll Software & Electronics GmbH + Im Südpark 183 + Linz 4030 + AT -00-40-D9 (hex) AMERICAN MEGATRENDS INC. -0040D9 (base 16) AMERICAN MEGATRENDS INC. - 6145F N BELT PARKWAY - NORCROSS GA 30071 - US +8C-32-23 (hex) JWIPC Technology Co.,Ltd. +8C3223 (base 16) JWIPC Technology Co.,Ltd. + 13/F, Haisong Building B, Tairan 9th Rd, Futian District + Shenzhen Guang Dong 5128042 + CN -00-40-21 (hex) RASTER GRAPHICS -004021 (base 16) RASTER GRAPHICS - 285 N. WOLFE ROAD - SUNNYVALE CA 94086 +00-C8-96 (hex) CIG SHANGHAI CO LTD +00C896 (base 16) CIG SHANGHAI CO LTD + 5th Floor, Building 8 No 2388 Chenhang Road + SHANGHAI 201114 + CN + +00-1B-09 (hex) MATRIX COMSEC PRIVATE LIMITED +001B09 (base 16) MATRIX COMSEC PRIVATE LIMITED + 394, GIDC, Makarpura, + Vadodara Gujarat 390010 + IN + +28-00-AF (hex) Dell Inc. +2800AF (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 US -00-40-C1 (hex) BIZERBA-WERKE WILHEIM KRAUT -0040C1 (base 16) BIZERBA-WERKE WILHEIM KRAUT - GMBH & CO. KG, - D-7460 BALINGEN D-7460 BALINGEN +00-04-13 (hex) snom technology GmbH +000413 (base 16) snom technology GmbH + Aroser Allee 66 + Berlin 13407 DE -00-40-E1 (hex) MARNER INTERNATIONAL, INC. -0040E1 (base 16) MARNER INTERNATIONAL, INC. - 1617 93RD LANE NE - BLAINE MN 55449 +74-B0-59 (hex) Motorola Mobility LLC, a Lenovo Company +74B059 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -00-40-FE (hex) SYMPLEX COMMUNICATIONS -0040FE (base 16) SYMPLEX COMMUNICATIONS - 5 RESEARCH DRIVE - ANN ARBOR MI 48103 - US +E4-84-29 (hex) New H3C Technologies Co., Ltd +E48429 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN -00-40-D4 (hex) GAGE TALKER CORP. -0040D4 (base 16) GAGE TALKER CORP. - 13680 NE 16TH STREET - BELLEVUE WA 98005 +F4-1A-79 (hex) IEEE Registration Authority +F41A79 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -00-40-38 (hex) TALENT ELECTRIC INCORPORATED -004038 (base 16) TALENT ELECTRIC INCORPORATED - 3RD FL., NO. 260, PA TEH ROAD - TAIWAN TAIWAN R.O.C. - TW +8C-07-34 (hex) Private +8C0734 (base 16) Private -00-40-D8 (hex) OCEAN OFFICE AUTOMATION LTD. -0040D8 (base 16) OCEAN OFFICE AUTOMATION LTD. - 4TH & 5TH FLOOR, KADER BLDG. - HONG KONG +98-7A-9B (hex) TCL MOKA International Limited +987A9B (base 16) TCL MOKA International Limited + 7/F, Building 22E 22 Science Park East Avenue + Hong Kong 999077 HK -00-40-C6 (hex) FIBERNET RESEARCH, INC. -0040C6 (base 16) FIBERNET RESEARCH, INC. - 1 TARA BOULEVARD-#405 - NASHUA NH 03062 - US +78-16-99 (hex) HUAWEI TECHNOLOGIES CO.,LTD +781699 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -00-40-32 (hex) DIGITAL COMMUNICATIONS -004032 (base 16) DIGITAL COMMUNICATIONS - ASSOCIATES, INC. - SAN JOSE CA 95131 - US +E4-0A-16 (hex) HUAWEI TECHNOLOGIES CO.,LTD +E40A16 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -00-40-C2 (hex) APPLIED COMPUTING DEVICES -0040C2 (base 16) APPLIED COMPUTING DEVICES - ALEPH PARK - TERRE HAUTE IN 47802 - US +40-9C-A7 (hex) CHINA DRAGON TECHNOLOGY LIMITED +409CA7 (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 -00-40-88 (hex) MOBIUS TECHNOLOGIES, INC. -004088 (base 16) MOBIUS TECHNOLOGIES, INC. - 5835 DOYLE STREET - EMERYVILLE CA 94608 +D0-AD-08 (hex) HP Inc. +D0AD08 (base 16) HP Inc. + 10300 Energy Dr + Spring TX 77389 US -00-80-AA (hex) MAXPEED -0080AA (base 16) MAXPEED - 1120 CHESS DRIVE - FOSTER CITY CA 94404 +60-04-5C (hex) NXP Semiconductor (Tianjin) LTD. +60045C (base 16) NXP Semiconductor (Tianjin) LTD. + No.15 Xinghua Avenue, Xiqing Economic Development Area + Tianjin 300385 + CN + +84-9C-02 (hex) Druid Software +849C02 (base 16) Druid Software + Block D, Civic Centre + Bray Wicklow A98 E1W9 + IE + +CC-40-B2 (hex) ECI Telecom Ltd. +CC40B2 (base 16) ECI Telecom Ltd. + 30 Hasivim St. + Petah Tikva 49133 + IL + +A4-E2-87 (hex) Xiaomi Communications Co Ltd +A4E287 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +80-95-3A (hex) Apple, Inc. +80953A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-C0-50 (hex) TOYO DENKI SEIZO K.K. -00C050 (base 16) TOYO DENKI SEIZO K.K. - 4-6-32 HIGASHIKASHIWAGAYA - KANAGAWA JAPAN 243-04 - JP +68-AE-04 (hex) Shenzhen SuperElectron Technology Co.,Ltd. +68AE04 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. + 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city + Shenzhen Guangdong 518000 + CN -00-40-1C (hex) AST RESEARCH, INC. -00401C (base 16) AST RESEARCH, INC. - MS 2-78 - IRVINE CA 92618 +CC-60-23 (hex) Apple, Inc. +CC6023 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-40-0F (hex) DATACOM TECHNOLOGIES -00400F (base 16) DATACOM TECHNOLOGIES - 11001 31ST PLACE WEST - EVERETT WA 98204 +0C-DB-EA (hex) Apple, Inc. +0CDBEA (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-40-06 (hex) SAMPO TECHNOLOGY CORPORATION -004006 (base 16) SAMPO TECHNOLOGY CORPORATION - 26-2 TING-HU, - 33334 TAIWAN 33334 TAIWAN R.O.C. - TW +68-45-CC (hex) Apple, Inc. +6845CC (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -00-40-34 (hex) BUSTEK CORPORATION -004034 (base 16) BUSTEK CORPORATION - 4151 BURTON DRIVE - SANTA CLARA CA 95054 +AC-97-38 (hex) Apple, Inc. +AC9738 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-80-93 (hex) XYRON CORPORATION -008093 (base 16) XYRON CORPORATION - 7864 LILY COURT - CUPERTINO CA 95014 +08-C2-24 (hex) Amazon Technologies Inc. +08C224 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -00-80-92 (hex) Silex Technology, Inc. -008092 (base 16) Silex Technology, Inc. - 2-3-1 Hikaridai, - Kyoto 619-0237 - JP +68-93-2E (hex) Habana Labs LTD. +68932E (base 16) Habana Labs LTD. + 9 Granite st. + Caesarea Select ... 3079821 + IL -00-80-5A (hex) TULIP COMPUTERS INTERNAT'L B.V -00805A (base 16) TULIP COMPUTERS INTERNAT'L B.V - P.O. BOX 3333 - THE - NL +2C-C4-4F (hex) IEEE Registration Authority +2CC44F (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -00-80-7E (hex) SOUTHERN PACIFIC LTD. -00807E (base 16) SOUTHERN PACIFIC LTD. - SANWA BLDG., 2-16-20 - JAPAN JAPAN 220 - JP +1C-4C-27 (hex) World WLAN Application Alliance +1C4C27 (base 16) World WLAN Application Alliance + 5th Floor, Block B, Shenzhen-Hong Kong International Science and Technology Park, No. 14 Taohua Road, Futian District, Shenzhen + shenzhen guangdong province 518017 + CN -00-80-EF (hex) RATIONAL -0080EF (base 16) RATIONAL - 3320 SCOTT BOULEVARD - SANTA CLARA CA 95054 - US +D0-C9-01 (hex) GLA ELECTRONICS PVT LTD +D0C901 (base 16) GLA ELECTRONICS PVT LTD + B 14/2 JHILMIL INDUSTRIAL AREA DELHI + DELHI DELHI 110095 + IN -00-80-F0 (hex) Panasonic Communications Co., Ltd. -0080F0 (base 16) Panasonic Communications Co., Ltd. - 4-1-62 Minoshima Hakata Fukuoka - 812-8531 - JP +40-99-E3 (hex) Guangzhou Mudi Information Technology Co., Ltd +4099E3 (base 16) Guangzhou Mudi Information Technology Co., Ltd + Room 403, 404, No. 8, Yongtai Taixing Road, Yongping Street, Baiyun District, Guangzhou city + Guangzhou City Guangdong Province 510000 + CN -00-80-51 (hex) FIBERMUX -008051 (base 16) FIBERMUX - 9310 TOPANGA CANYON BLVD. - CHATSWORTH CA 91311 +7C-88-99 (hex) FN-LINK TECHNOLOGY Ltd. +7C8899 (base 16) FN-LINK TECHNOLOGY Ltd. + No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China + Changsha Hunan 410329 + CN + +FC-70-2E (hex) Sichuan AI-Link Technology Co., Ltd. +FC702E (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 + CN + +B4-F9-5D (hex) Juniper Networks +B4F95D (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -00-80-C6 (hex) NATIONAL DATACOMM CORPORATION -0080C6 (base 16) NATIONAL DATACOMM CORPORATION - 2F, 28, INDUSTRY EAST 9TH RD. - TAIWAN 30077 TAIWAN 30077 R.O.C. +04-68-74 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +046874 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 + CN + +74-90-BC (hex) Arcadyan Corporation +7490BC (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 TW -00-40-50 (hex) IRONICS, INCORPORATED -004050 (base 16) IRONICS, INCORPORATED - 767 WARREN RD - ITHACA N.Y. 14850 +B0-47-E9 (hex) Intel Corporate +B047E9 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +6C-2F-80 (hex) Intel Corporate +6C2F80 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +D0-65-78 (hex) Intel Corporate +D06578 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +98-BD-80 (hex) Intel Corporate +98BD80 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +D4-76-A0 (hex) Fortinet, Inc. +D476A0 (base 16) Fortinet, Inc. + 899 Kifer Road + Sunnyvale 94086 US -00-40-47 (hex) WIND RIVER SYSTEMS -004047 (base 16) WIND RIVER SYSTEMS - 1010 ATLANTIC AVENUE - ALAMEDA CA 94501 +94-FF-3C (hex) Fortinet, Inc. +94FF3C (base 16) Fortinet, Inc. + 899 Kifer Road + Sunnyvale 94086 US -00-40-41 (hex) FUJIKURA LTD. -004041 (base 16) FUJIKURA LTD. - 1-5-1, KIBA, KOTO-KU - - JP +A0-F5-09 (hex) IEI Integration Corp. +A0F509 (base 16) IEI Integration Corp. + 4F., No. 29, Zhongxing Rd., Xizhi Dist., + New Taipei City 221 + TW -00-80-00 (hex) MULTITECH SYSTEMS, INC. -008000 (base 16) MULTITECH SYSTEMS, INC. - 2205 WOODALE DRIVE - MOUNDS VIEW MN 55112 +6C-45-C4 (hex) Cloudflare, Inc. +6C45C4 (base 16) Cloudflare, Inc. + 101 Townsend Street + San Francisco CA 94107 US -00-80-69 (hex) COMPUTONE SYSTEMS -008069 (base 16) COMPUTONE SYSTEMS - 1100 NORTHMEADOW PARKWAY - ROSWELL GA 30076 +00-0D-97 (hex) Hitachi Energy USA Inc. +000D97 (base 16) Hitachi Energy USA Inc. + 901 Main Campus Drive + Raleigh NC 27606 US -00-80-35 (hex) TECHNOLOGY WORKS, INC. -008035 (base 16) TECHNOLOGY WORKS, INC. - 4030 BRAKER LANE #350 - AUSTIN TX 78759 +18-7F-88 (hex) Ring LLC +187F88 (base 16) Ring LLC + 1523 26th St + Santa Monica CA 90404 US -00-80-4E (hex) APEX COMPUTER COMPANY -00804E (base 16) APEX COMPUTER COMPANY - 4500 150TH AVENUE, NE - REDMOND WA 98052 +B8-7E-40 (hex) Huawei Device Co., Ltd. +B87E40 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +1C-2F-A2 (hex) Guangzhou Shiyuan Electronic Technology Company Limited +1C2FA2 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 + CN + +A4-3F-68 (hex) Arista Network, Inc. +A43F68 (base 16) Arista Network, Inc. + 5453 Great America Parkway + Santa Clara CA 95054 US -00-80-55 (hex) FERMILAB -008055 (base 16) FERMILAB - P.O. BOX 500, MS-234 - BATAVIA IL 60510 +BC-9D-4E (hex) Shenzhen Skyworth Digital Technology CO., Ltd +BC9D4E (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + +1C-3B-01 (hex) Shanghai Xiaodu Technology Limited +1C3B01 (base 16) Shanghai Xiaodu Technology Limited + 4th Floor Building No.1 , No.701 Naxian Road Pilot Free Trade Zone Shanghai China + Shanghai 200000 + CN + +94-70-6C (hex) Quectel Wireless Solutions Co.,Ltd. +94706C (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + +64-D8-1B (hex) Vestel Elektronik San ve Tic. A.S. +64D81B (base 16) Vestel Elektronik San ve Tic. A.S. + Organize san + Manisa Turket 45030 + TR + +30-DC-E7 (hex) zte corporation +30DCE7 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +D0-12-55 (hex) Hui Zhou Gaoshengda Technology Co.,LTD +D01255 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD + No.2,Jin-da Road,Huinan Industrial Park + Hui Zhou Guangdong 516025 + CN + +3C-2D-9E (hex) Vantiva - Connected Home +3C2D9E (base 16) Vantiva - Connected Home + 4855 Peachtree Industrial Blvd, #200 + Norcross GA 30092 US -00-80-2A (hex) TEST SYSTEMS & SIMULATIONS INC -00802A (base 16) TEST SYSTEMS & SIMULATIONS INC - 32429 INDUSTRIAL DRIVE - MADISON HEIGHTS MI 48071-1528 +08-C7-F5 (hex) Vantiva Connected Home - Technologies Telco +08C7F5 (base 16) Vantiva Connected Home - Technologies Telco + 4855 Peachtree Industrial Blvd, Suite 200 + Norcross GA 30902 US -00-80-1D (hex) INTEGRATED INFERENCE MACHINES -00801D (base 16) INTEGRATED INFERENCE MACHINES - 1468 EAST KATELLA - ANAHEIM CA 92805 - US +C0-16-93 (hex) Xiaomi Communications Co Ltd +C01693 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -00-80-C0 (hex) PENRIL DATACOMM -0080C0 (base 16) PENRIL DATACOMM - 1300 QUINCE ORCHARD BLVD. - GAITHERSBURG MD 20878 - US +FC-5B-8C (hex) Xiaomi Communications Co Ltd +FC5B8C (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -00-80-75 (hex) PARSYTEC GMBH -008075 (base 16) PARSYTEC GMBH - JUELICHER STR. 338 - F.R. - DE +28-4E-44 (hex) HUAWEI TECHNOLOGIES CO.,LTD +284E44 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -00-80-ED (hex) IQ TECHNOLOGIES, INC. -0080ED (base 16) IQ TECHNOLOGIES, INC. - 11811 NE FIRST STREET - BELLEVUE WA 98005 - US +80-2E-C3 (hex) HUAWEI TECHNOLOGIES CO.,LTD +802EC3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -00-80-9A (hex) NOVUS NETWORKS LTD -00809A (base 16) NOVUS NETWORKS LTD - JOHN SCOTT HOUSE - ENGLAND - GB +9C-9E-03 (hex) awayfrom +9C9E03 (base 16) awayfrom + A-2723 Ho, Sambo Techon Tower,122,jomaru-ro 385 Beon-gil + bucheon-si Gyeonnggi-do 14556 + KR -00-80-4A (hex) PRO-LOG -00804A (base 16) PRO-LOG - 12 UPPER RAGSDALE DRIVE - MONTEREY CA 93940 +80-05-3A (hex) CHeKT Inc. +80053A (base 16) CHeKT Inc. + 5870 Greenwood Rd. + Shreveport LA 71119 US -00-80-04 (hex) ANTLOW COMMUNICATIONS, LTD. -008004 (base 16) ANTLOW COMMUNICATIONS, LTD. - 4 COLTHROP WAY - ENGLAND - GB +D8-B3-2F (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +D8B32F (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 + CN -00-80-D0 (hex) COMPUTER PERIPHERALS, INC. -0080D0 (base 16) COMPUTER PERIPHERALS, INC. - 667 RANCHO CONEJO BLVD. - NEWBURY PARK CA 91320 - US +54-10-4F (hex) Samsung Electronics Co.,Ltd +54104F (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -00-80-24 (hex) KALPANA, INC. -008024 (base 16) KALPANA, INC. - 1154 EAST ARQUES AVENUE - SUNNYVALE CA 94086 - US +B0-54-76 (hex) Samsung Electronics Co.,Ltd +B05476 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -00-80-40 (hex) JOHN FLUKE MANUFACTURING CO. -008040 (base 16) JOHN FLUKE MANUFACTURING CO. - P.O. BOX C9090-M/S 244F - EVERETT WA 98206 - US +EC-90-C1 (hex) Samsung Electronics Co.,Ltd +EC90C1 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -00-80-21 (hex) Alcatel Canada Inc. -008021 (base 16) Alcatel Canada Inc. - 349 Terry Fox Drive - Kanata Ontario K2K 2V6 +30-D9-59 (hex) Shanghai Longcheer Technology Co., Ltd. +30D959 (base 16) Shanghai Longcheer Technology Co., Ltd. + Bldg 1,No.401,Caobao RD,Xuhui Dist + Shanghai 200233 + CN + +00-78-39 (hex) Nokia +007839 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 CA -00-80-E8 (hex) CUMULUS CORPORATIION -0080E8 (base 16) CUMULUS CORPORATIION - 23500 MERCANTILE ROAD - CLEVELAND OH 44122 - US +10-8A-7B (hex) Nokia +108A7B (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -00-00-66 (hex) TALARIS SYSTEMS, INC. -000066 (base 16) TALARIS SYSTEMS, INC. - 11339 SORRENTO VALLEY ROAD - SAN DIEGO CA 92121 +A0-52-AB (hex) AVM ELECTRONICS PTE LTD +A052AB (base 16) AVM ELECTRONICS PTE LTD + 362, UPPER PAYA LEBAR ROAD, #05-07 Da Jin Factory Building + Singapore Singapore 534963 + SG + +BC-74-4B (hex) Nintendo Co.,Ltd +BC744B (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + +6C-6F-18 (hex) Stereotaxis, Inc. +6C6F18 (base 16) Stereotaxis, Inc. + 710 N. Tucker Blvd, Suite 110 + St. Louis MO 63101 US -00-00-49 (hex) APRICOT COMPUTERS, LTD -000049 (base 16) APRICOT COMPUTERS, LTD - 90 VINCENT DRIVE - ENGLAND - GB +F0-86-6F (hex) EM Microelectronic +F0866F (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH -00-00-FA (hex) MICROSAGE COMPUTER SYSTEMS INC -0000FA (base 16) MICROSAGE COMPUTER SYSTEMS INC - 680 SOUTH ROCK BLVD - RENO NE 89502 +5C-B2-60 (hex) EM Microelectronic +5CB260 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +A8-BB-56 (hex) Apple, Inc. +A8BB56 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-00-D4 (hex) PURE DATA LTD. -0000D4 (base 16) PURE DATA LTD. - 200 WEST BEAVER CREEK ROAD - L4B 1B4 - CA +28-2D-7F (hex) Apple, Inc. +282D7F (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -00-00-19 (hex) APPLIED DYNAMICS INTERNATIONAL -000019 (base 16) APPLIED DYNAMICS INTERNATIONAL - 3800 STONE SCHOOL ROAD - ANN ARBOR MI 48104-2499 +8C-26-AA (hex) Apple, Inc. +8C26AA (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-00-AB (hex) LOGIC MODELING CORPORATION -0000AB (base 16) LOGIC MODELING CORPORATION - 1520 MCCANDLESS DRIVE - MILPITAS CA 95035 +90-62-3F (hex) Apple, Inc. +90623F (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-80-F2 (hex) RAYCOM SYSTEMS INC -0080F2 (base 16) RAYCOM SYSTEMS INC - 16525 SHERMAN WAY #C-8 - VAN NUYS CA 91406 +5C-07-A4 (hex) Ciena Corporation +5C07A4 (base 16) Ciena Corporation + 900 International Drive + Linthicum MD 21090-2200 US -00-80-BD (hex) THE FURUKAWA ELECTRIC CO., LTD -0080BD (base 16) THE FURUKAWA ELECTRIC CO., LTD - 6-1, MARUNOUCHI 2-CHOME - 100 - JP +00-4B-F3 (hex) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. +004BF3 (base 16) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. + 6th Floor, South Section, Building 23, Keyuan West, NO.1 Kezhi West Road, Science and Technology Community, Yuehai Street, Nanshan District + Shenzhen Guangdong 518057 + CN -00-80-25 (hex) Telit Wireless Solutions GmbH -008025 (base 16) Telit Wireless Solutions GmbH - Mendelssohnstrasse15D - Hamburg 22761 - DE +BC-54-FC (hex) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. +BC54FC (base 16) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. + 6th Floor, South Section, Building 23, Keyuan West, NO.1 Kezhi West Road, Science and Technology Community, Yuehai Street, Nanshan District + Shenzhen Guangdong 518057 + CN -00-80-EA (hex) ADVA Optical Networking Ltd. -0080EA (base 16) ADVA Optical Networking Ltd. - ADVAntage House - York YO30 4RY - GB +90-76-9F (hex) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. +90769F (base 16) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. + 6th Floor, South Section, Building 23, Keyuan West, NO.1 Kezhi West Road, Science and Technology Community, Yuehai Street, Nanshan District + Shenzhen Guangdong 518057 + CN -00-80-0D (hex) VOSSWINKEL F.U. -00800D (base 16) VOSSWINKEL F.U. - AM JOSTENHOF 15 - - DE +4C-77-66 (hex) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. +4C7766 (base 16) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. + 6th Floor, South Section, Building 23, Keyuan West, NO.1 Kezhi West Road, Science and Technology Community, Yuehai Street, Nanshan District + Shenzhen Guangdong 518057 + CN -00-80-D1 (hex) KIMTRON CORPORATION -0080D1 (base 16) KIMTRON CORPORATION - 1709 JUNCTION COURT - SAN JOSE CA 95112 - US +00-5C-C2 (hex) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. +005CC2 (base 16) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. + 6th Floor, South Section, Building 23, Keyuan West, NO.1 Kezhi West Road, Science and Technology Community, Yuehai Street, Nanshan District + Shenzhen Guangdong 518057 + CN -00-00-1E (hex) TELSIST INDUSTRIA ELECTRONICA -00001E (base 16) TELSIST INDUSTRIA ELECTRONICA - RUA VILHENA DE MORAES, 380 - - BR +88-54-8E (hex) vivo Mobile Communication Co., Ltd. +88548E (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN -00-00-50 (hex) RADISYS CORPORATION -000050 (base 16) RADISYS CORPORATION - 15025 S.W. KOLL PARKWAY - BEAVERTON OR 97006-6056 - US +58-1D-C9 (hex) MSE CO.,LTD. +581DC9 (base 16) MSE CO.,LTD. + 3-1-6, Kyutaromachi, Chuo-ku, + OSAKA OSAKA 5410056 + JP -00-80-2E (hex) CASTLE ROCK COMPUTING -00802E (base 16) CASTLE ROCK COMPUTING - 20837 BOYCE LANE - SARATOGA CA 95070-4806 +50-5B-1D (hex) Shenzhen C-Data Technology Co., Ltd. +505B1D (base 16) Shenzhen C-Data Technology Co., Ltd. + #201, Building A4, Nanshan Zhiyuan, No.1001, Xueyuan Avenue, Changyuan Community,Taoyuan,Nanshan + Shenzhen Guangdong 518055 + CN + +88-01-0C (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +88010C (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 + CN + +A4-9E-69 (hex) Silicon Laboratories +A49E69 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -00-00-4F (hex) LOGICRAFT, INC. -00004F (base 16) LOGICRAFT, INC. - 22 COTTON ROAD - NASHUA NH 03063 +F0-82-C0 (hex) Silicon Laboratories +F082C0 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -00-00-15 (hex) DATAPOINT CORPORATION -000015 (base 16) DATAPOINT CORPORATION - 9725 DATAPOINT DRIVE - SAN ANTONIO TX 78284 +60-EF-AB (hex) Silicon Laboratories +60EFAB (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -00-00-1C (hex) BELL TECHNOLOGIES -00001C (base 16) BELL TECHNOLOGIES - 330 WARREN AVENUE - FREMONT CA 94539 +D8-7A-3B (hex) Silicon Laboratories +D87A3B (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -00-00-34 (hex) NETWORK RESOURCES CORPORATION -000034 (base 16) NETWORK RESOURCES CORPORATION - 61 EAST DAGGETT DRIVE - SAN JOSE CA 95134 +3C-2E-F5 (hex) Silicon Laboratories +3C2EF5 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -00-00-22 (hex) VISUAL TECHNOLOGY INC. -000022 (base 16) VISUAL TECHNOLOGY INC. - 1703 MIDDLESEX STREET - LOWELL MA 01851 +34-10-F4 (hex) Silicon Laboratories +3410F4 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -00-00-B5 (hex) DATABILITY SOFTWARE SYS. INC. -0000B5 (base 16) DATABILITY SOFTWARE SYS. INC. - ONE PALMER TERRACE - CARLSTADT NJ 07072 +0C-EF-F6 (hex) Silicon Laboratories +0CEFF6 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -00-00-E0 (hex) QUADRAM CORP. -0000E0 (base 16) QUADRAM CORP. - ONE QUAD WAY - NORCROSS GA 30093 +EC-F6-4C (hex) Silicon Laboratories +ECF64C (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -00-00-27 (hex) JAPAN RADIO COMPANY -000027 (base 16) JAPAN RADIO COMPANY - LABORATORY - - JP +70-C9-12 (hex) Sichuan AI-Link Technology Co., Ltd. +70C912 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 + CN -00-00-E8 (hex) ACCTON TECHNOLOGY CORP. -0000E8 (base 16) ACCTON TECHNOLOGY CORP. - 46750 FREMONT BLVD. #104 - FREMONT CA 94538 +48-8E-B7 (hex) Zebra Technologies Inc. +488EB7 (base 16) Zebra Technologies Inc. + ONE ZEBRA PLAZA + HOLTSVILLE NY 11742 US -00-00-2F (hex) TIMEPLEX INC. -00002F (base 16) TIMEPLEX INC. - 530 CHESTNUT RIDGE ROAD - WOODCLIFF LAKE NJ 07675 +90-AB-96 (hex) Silicon Laboratories +90AB96 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -00-00-E6 (hex) APTOR PRODUITS DE COMM INDUST -0000E6 (base 16) APTOR PRODUITS DE COMM INDUST - 61, CHEMIN DU VIEUX-CHENE - - FR +0C-82-D5 (hex) Maxio Technology Hangzhou Co., Ltd. +0C82D5 (base 16) Maxio Technology Hangzhou Co., Ltd. + Block C, Juguang Center, Qianmo Road, Binjiang District, Hangzhou City, Zhejiang Province + Hangzhou 310000 + CN -00-00-9A (hex) RC COMPUTER A/S -00009A (base 16) RC COMPUTER A/S - LAUTRUPBJERG 1 - - DK +04-01-BB (hex) TECNO MOBILE LIMITED +0401BB (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 + Hong Kong Hong Kong 999077 + HK -00-00-4B (hex) ICL DATA OY -00004B (base 16) ICL DATA OY - KUTOMOTIE 16-18 - - FI +AC-72-DD (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +AC72DD (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN -00-80-42 (hex) Artesyn Embedded Technologies -008042 (base 16) Artesyn Embedded Technologies - 2900 S. Diablo Way - Tempe AZ 85282 - US +64-BB-1E (hex) Earda Technologies co Ltd +64BB1E (base 16) Earda Technologies co Ltd + Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District + Guangzhou Guangdong 511455 + CN -00-80-AC (hex) IMLOGIX, DIVISION OF GENESYS -0080AC (base 16) IMLOGIX, DIVISION OF GENESYS - 1900 SUMMIT TOWER BLVD.STE#770 - ORLANDO FL 32810 - US +64-82-14 (hex) FN-LINK TECHNOLOGY Ltd. +648214 (base 16) FN-LINK TECHNOLOGY Ltd. + No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China + Changsha Hunan 410329 + CN -00-00-80 (hex) CRAY COMMUNICATIONS A/S -000080 (base 16) CRAY COMMUNICATIONS A/S - SMEDEHOLM 12-14 - - DK +E0-9C-E5 (hex) Shanghai Tricheer Technology Co.,Ltd. +E09CE5 (base 16) Shanghai Tricheer Technology Co.,Ltd. + Rm 907, Building 1, Lane 399, Shengxia Road,Zhangjiang Hi-Tech Park,Pudong District,Shanghai + Shanghai Shanghai 201203 + CN -08-00-89 (hex) Kinetics -080089 (base 16) Kinetics - - Walnut Creek CA - US +FC-D2-90 (hex) SKY UK LIMITED +FCD290 (base 16) SKY UK LIMITED + Grant Way + Isleworth Middlesex TW7 5QD + GB -08-00-86 (hex) KONICA MINOLTA HOLDINGS, INC. -080086 (base 16) KONICA MINOLTA HOLDINGS, INC. - 1-6-1, Marunouchi, - Tokyo 100-0005 - JP +78-30-5D (hex) zte corporation +78305D (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -08-00-83 (hex) Seiko Instruments Inc. -080083 (base 16) Seiko Instruments Inc. - 8, Nakase 1-chome Mihama-ku - Chiba-shi Chiba 261-8507 - JP +94-0B-83 (hex) zte corporation +940B83 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -00-00-73 (hex) SIECOR CORPORATION -000073 (base 16) SIECOR CORPORATION - P.O. BOX 13625 - RESEARCH TRIANGLE PK NC 27709 +E4-74-50 (hex) Shenzhen Grandsun Electronic Co.,Ltd. +E47450 (base 16) Shenzhen Grandsun Electronic Co.,Ltd. + Gaoqiao Industrial Zone,Baishitang Village, + Shenzhen Guangdong 518116 + CN + +B4-A3-BD (hex) Extreme Networks Headquarters +B4A3BD (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 US -00-00-B9 (hex) MCDONNELL DOUGLAS COMPUTER SYS -0000B9 (base 16) MCDONNELL DOUGLAS COMPUTER SYS - DIV MCDONNELL DOUGLAS INF SYS - ENGLAND - GB +7C-D3-E5 (hex) HUAWEI TECHNOLOGIES CO.,LTD +7CD3E5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -00-00-BF (hex) SYMMETRIC COMPUTER SYSTEMS -0000BF (base 16) SYMMETRIC COMPUTER SYSTEMS - 1620 OAKLAND ROAD SUITE D-200 - SAN JOSE CA 95131 - US +B8-87-6E (hex) Intertech Services AG +B8876E (base 16) Intertech Services AG + Werftestrasse 4, + Luzern 6005 + CH -00-00-2D (hex) CHROMATICS INC -00002D (base 16) CHROMATICS INC - 2558 MOUNTAIN INDUSTRIAL BLVD - TUCKER GA 30084 +3C-0B-4F (hex) Intertech Services AG +3C0B4F (base 16) Intertech Services AG + Werftestrasse 4, + Luzern 6005 + CH + +6C-62-FE (hex) Juniper Networks +6C62FE (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -00-00-59 (hex) Hellige GMBH -000059 (base 16) Hellige GMBH - Heinrich-von-Stephan-Str. 4 - West Gernany - DE +9C-9E-D5 (hex) Xiaomi Communications Co Ltd +9C9ED5 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -00-00-69 (hex) CONCORD COMMUNICATIONS INC -000069 (base 16) CONCORD COMMUNICATIONS INC - 753 FOREST STREET - MARLBOROUGH MA 01752 - US +74-38-22 (hex) Xiaomi Communications Co Ltd +743822 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -00-00-E7 (hex) Star Gate Technologies -0000E7 (base 16) Star Gate Technologies - 29300 Aurora Road - Solon OH 44139 - US +B8-3B-AB (hex) Arcadyan Corporation +B83BAB (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW -00-00-4D (hex) DCI CORPORATION -00004D (base 16) DCI CORPORATION - 64J PRINCETON-HIGHTSTOWN RD - PRINCETON JUNCTION NJ 08550 +40-A6-3D (hex) SignalFire Telemetry +40A63D (base 16) SignalFire Telemetry + 140 Locke Dr. Suite B + Marlborough MA 01752 US -00-00-6F (hex) Madge Ltd. -00006F (base 16) Madge Ltd. - Madge House - Maindenhead Berkshire SL6 2HP - GB +60-54-64 (hex) Eyedro Green Solutions Inc. +605464 (base 16) Eyedro Green Solutions Inc. + 606 Colby Dr, Unit E + Waterloo Ontario N2V1A2 + CA -00-00-78 (hex) LABTAM LIMITED -000078 (base 16) LABTAM LIMITED - 43 MALCOLM ROAD P.O. BOX297 - - AU +CC-E5-36 (hex) ittim +CCE536 (base 16) ittim + 1202, No.6, Zhongguancun South Street, Haidian District, + beijing 100080 + CN -00-00-5A (hex) SysKonnect GmbH -00005A (base 16) SysKonnect GmbH - SIEMENSSTRAßE 23 - - DE +BC-00-04 (hex) Fiberhome Telecommunication Technologies Co.,LTD +BC0004 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN -00-00-71 (hex) ADRA SYSTEMS INC. -000071 (base 16) ADRA SYSTEMS INC. - 59 TECHNOLOGY DRIVE - LOWELL MA 01851 - US +98-A8-29 (hex) AltoBeam Inc. +98A829 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN -00-00-23 (hex) ABB INDUSTRIAL SYSTEMS AB -000023 (base 16) ABB INDUSTRIAL SYSTEMS AB - DEPT. SEISY/LKSB - - SE +84-E9-C1 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +84E9C1 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 + CN -00-00-18 (hex) WEBSTER COMPUTER CORPORATION -000018 (base 16) WEBSTER COMPUTER CORPORATION - 16040 REDWOOD LODGE ROAD - LOS GATOS CA 95033-9260 - US +B4-62-2E (hex) Zhong Ge Smart Technology Co., Ltd. +B4622E (base 16) Zhong Ge Smart Technology Co., Ltd. + Zhong Ge Smart, 5th floor building G + Shanghai 201199 + CN -00-00-D5 (hex) MICROGNOSIS INTERNATIONAL -0000D5 (base 16) MICROGNOSIS INTERNATIONAL - 63 QUEEN VICTORIA STREET - UNITED KINGDOM - GB +C4-6E-33 (hex) Zhong Ge Smart Technology Co., Ltd. +C46E33 (base 16) Zhong Ge Smart Technology Co., Ltd. + Zhong Ge Smart, 5th floor building G + Shanghai 201199 + CN -00-00-3A (hex) CHYRON CORPORATION -00003A (base 16) CHYRON CORPORATION - 265 SPAGNOLI ROAD - MELVILLE NY 11747 - US +2C-67-BE (hex) DWnet Technologies(Suzhou) Corporation +2C67BE (base 16) DWnet Technologies(Suzhou) Corporation + No.8,Tangzhuang Road, Suzhou Industrial Park, Jiangsu, China + Suzhou 21500 + CN -00-00-BE (hex) THE NTI GROUP -0000BE (base 16) THE NTI GROUP - 4701 PATRICK HENRY DRIVE - SANTA CLARA CA 95054 +EC-19-2E (hex) Cisco Systems, Inc +EC192E (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -00-00-D9 (hex) NIPPON TELEGRAPH & TELEPHONE -0000D9 (base 16) NIPPON TELEGRAPH & TELEPHONE - CORPORATION (NTT) - TOKYO 100-8116 - JP +F8-96-FE (hex) LG Innotek +F896FE (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR -08-00-24 (hex) 10NET COMMUNICATIONS/DCA -080024 (base 16) 10NET COMMUNICATIONS/DCA - 7777 WASHINGTON VILLAGE DR. - DAYTON OH 45459-3957 +A4-C4-0D (hex) WAC Lighting +A4C40D (base 16) WAC Lighting + 44 Harbor Park Dr + Port Washington NY 11050 US -08-00-22 (hex) NBI INC. -080022 (base 16) NBI INC. - 3450 MITCHELL LANE - BOULDER CO 80301 - US +58-30-6E (hex) Nokia +58306E (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -08-00-20 (hex) Oracle Corporation -080020 (base 16) Oracle Corporation - 17 Network Circle - Menlo Park CA 95025 +A8-CA-77 (hex) Amazon Technologies Inc. +A8CA77 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -08-00-1F (hex) SHARP CORPORATION -08001F (base 16) SHARP CORPORATION - ENGINEERING DEPARTMENT 6 - NARA 639-11 - JP +D0-C9-07 (hex) Private +D0C907 (base 16) Private -02-07-01 (hex) RACAL-DATACOM -020701 (base 16) RACAL-DATACOM - LAN INTERNETWORKING DIVISION - BOXBOROUGH MA 01719 - US +C4-A4-51 (hex) TECNO MOBILE LIMITED +C4A451 (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 + Hong Kong Hong Kong 999077 + HK -08-00-06 (hex) SIEMENS AG -080006 (base 16) SIEMENS AG - Siemens IT Solutions and Services, SIS GO QM O - POB 2353 Fuerth 90713 - DE +F8-9D-9D (hex) Shenzhen MinewSemi Co.,LTD. +F89D9D (base 16) Shenzhen MinewSemi Co.,LTD. + 3Floor, I Building, Gangzhilong Tech Park, No.6, Qinglong Road, Longhua District + Shenzhen 518109 + CN -08-00-2A (hex) MOSAIC TECHNOLOGIES INC. -08002A (base 16) MOSAIC TECHNOLOGIES INC. - 47 MANNING ROAD - BILLERICA MA 01821-3970 +F0-61-F3 (hex) Comcast Cable Corporation +F061F3 (base 16) Comcast Cable Corporation + 1800 Arch Street + Philadelphia PA 19103 US -08-00-13 (hex) Exxon -080013 (base 16) Exxon - - - US +D4-8D-26 (hex) LG Innotek +D48D26 (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR -02-1C-7C (hex) PERQ SYSTEMS CORPORATION -021C7C (base 16) PERQ SYSTEMS CORPORATION - 2600 LIBERTY AVENUE - PITTSBURGH PA 15230 - US +F4-F2-8A (hex) HUAWEI TECHNOLOGIES CO.,LTD +F4F28A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -08-00-61 (hex) JAROGATE LTD. -080061 (base 16) JAROGATE LTD. - 197-213 LYHAM ROAD - UNITED KINGDOM - GB +94-E3-00 (hex) HUAWEI TECHNOLOGIES CO.,LTD +94E300 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -08-00-5F (hex) SABER TECHNOLOGY CORP. -08005F (base 16) SABER TECHNOLOGY CORP. - 2381 BERING DRIVE - SAN JOSE CA 95131-1125 - US +0C-E5-B5 (hex) HUAWEI TECHNOLOGIES CO.,LTD +0CE5B5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -08-00-58 (hex) SYSTEMS CONCEPTS -080058 (base 16) SYSTEMS CONCEPTS - 520 THIRD STREET - SAN FRANCISCO CA 94107 - US +FC-F7-38 (hex) HUAWEI TECHNOLOGIES CO.,LTD +FCF738 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -04-E0-C4 (hex) TRIUMPH-ADLER AG -04E0C4 (base 16) TRIUMPH-ADLER AG - HUNDINGSTRAßE 11B - - DE +8C-86-2A (hex) HUAWEI TECHNOLOGIES CO.,LTD +8C862A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -08-00-49 (hex) UNIVATION -080049 (base 16) UNIVATION - 1037 NORTH FAIR OAKS AVE. - SUNNYVALE CA 94089 - US +C0-33-79 (hex) HUAWEI TECHNOLOGIES CO.,LTD +C03379 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -00-00-05 (hex) XEROX CORPORATION -000005 (base 16) XEROX CORPORATION - M/S 105-50C - WEBSTER NY 14580 - US +24-1E-2B (hex) Zhejiang Cainiao Supply Chain Management Co., Ltd +241E2B (base 16) Zhejiang Cainiao Supply Chain Management Co., Ltd + Block B1, XIXI center, No.588 West Wenyi Road, Xihu District + Hangzhou Zhejiang 310000 + CN -00-DD-08 (hex) UNGERMANN-BASS INC. -00DD08 (base 16) UNGERMANN-BASS INC. - 3900 FREEDOM CIRCLE - SANTA CLARA CA 95054 +AC-7F-8D (hex) Extreme Networks Headquarters +AC7F8D (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 US -AA-00-00 (hex) DIGITAL EQUIPMENT CORPORATION -AA0000 (base 16) DIGITAL EQUIPMENT CORPORATION - LKG 1-2/A19 - LITTLETON MA 01460-1289 - US +24-AC-AC (hex) Polar Electro Oy +24ACAC (base 16) Polar Electro Oy + Professorintie 5 + Kempele 90440 + FI -AA-00-01 (hex) DIGITAL EQUIPMENT CORPORATION -AA0001 (base 16) DIGITAL EQUIPMENT CORPORATION - LKG 1-2/A19 - LITTLETON MA 01460-1289 - US +50-20-6B (hex) Copeland - Transportation Solutions ApS +50206B (base 16) Copeland - Transportation Solutions ApS + Boeletvej 1 + Ry 8680 + DK -AA-00-02 (hex) DIGITAL EQUIPMENT CORPORATION -AA0002 (base 16) DIGITAL EQUIPMENT CORPORATION - LKG 1-2/A19 - LITTLETON MA 01460-1289 - US +80-D1-0A (hex) Sichuan AI-Link Technology Co., Ltd. +80D10A (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 + CN -08-00-14 (hex) EXCELAN -080014 (base 16) EXCELAN - 1599 FLICKINGER AVENUE - SAN JOSE CA 95131 - US +10-47-E7 (hex) Shenzhen YOUHUA Technology Co., Ltd +1047E7 (base 16) Shenzhen YOUHUA Technology Co., Ltd + Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District + Shenzhen Guangdong 518055 + CN -08-00-65 (hex) GENRAD INC. -080065 (base 16) GENRAD INC. - 300 BAKER AVENUE - CONCORD MA 01742 - US +94-F6-F2 (hex) Honor Device Co., Ltd. +94F6F2 (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 -00-00-07 (hex) XEROX CORPORATION -000007 (base 16) XEROX CORPORATION - M/S 105-50C - WEBSTER NY 14580 - US +44-90-46 (hex) Honor Device Co., Ltd. +449046 (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 -00-80-1F (hex) KRUPP ATLAS ELECTRONIK GMBH -00801F (base 16) KRUPP ATLAS ELECTRONIK GMBH - P.O. BOX 448545 - - DE +B0-C3-8E (hex) Huawei Device Co., Ltd. +B0C38E (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -02-AA-3C (hex) OLIVETTI TELECOMM SPA (OLTECO) -02AA3C (base 16) OLIVETTI TELECOMM SPA (OLTECO) - 20300 STEVENS CREEK BLVD. - CUPERTINO CA 95014 - US +14-3B-51 (hex) Huawei Device Co., Ltd. +143B51 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -08-00-59 (hex) A/S MYCRON -080059 (base 16) A/S MYCRON - PO BOX 6199 - - NO +D0-73-80 (hex) Huawei Device Co., Ltd. +D07380 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -08-00-08 (hex) BOLT BERANEK AND NEWMAN INC. -080008 (base 16) BOLT BERANEK AND NEWMAN INC. - 70 FAWCETT STREET - CAMBRIDGE MA 02138 - US +AC-FC-E3 (hex) EM Microelectronic +ACFCE3 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH -84-87-FF (hex) Shenzhen Skyworth Digital Technology CO., Ltd -8487FF (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 - CN +04-2D-AD (hex) Areus GmbH +042DAD (base 16) Areus GmbH + Einsteinstrasse 13 + Herrenberg Baden-Wuerttemberg 71083 + DE -64-57-25 (hex) Hui Zhou Gaoshengda Technology Co.,LTD -645725 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD - No.2,Jin-da Road,Huinan Industrial Park - Hui Zhou Guangdong 516025 +78-1C-1E (hex) Chongqing Yipingfang Technology Co., Ltd. +781C1E (base 16) Chongqing Yipingfang Technology Co., Ltd. + No. 1-10, Tieshan Road, Biquan Street, Bishan District, Chongqing + ChongQing 402760 CN -00-80-E9 (hex) Madge Ltd. -0080E9 (base 16) Madge Ltd. - Madge House - Maindenhead Berkshire SL6 2HP - GB +78-A1-3E (hex) New H3C Technologies Co., Ltd +78A13E (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN -00-40-D6 (hex) LOCAMATION B.V. -0040D6 (base 16) LOCAMATION B.V. - POSTBOX 360 - HOLLAND +7C-BA-C0 (hex) EVBox BV +7CBAC0 (base 16) EVBox BV + Kabelweg 47 + Amsterdam Noord holland 1014 BA NL -08-00-4B (hex) Planning Research Corp. -08004B (base 16) Planning Research Corp. - 1508 Kennedy Drive - Bellvue NE 68005 +98-25-6E (hex) Private +98256E (base 16) Private + +B0-A7-D2 (hex) Fiberhome Telecommunication Technologies Co.,LTD +B0A7D2 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN + +8C-87-D0 (hex) Shenzhen Uascent Technology Co.,Ltd +8C87D0 (base 16) Shenzhen Uascent Technology Co.,Ltd + 701, block D, building 1, Lot 1, Chuangzhi Yuncheng building, Liuxian Avenue, Xili street, Nanshan District + Shenzhen 518000 + CN + +5C-17-83 (hex) Edgecore Americas Networking Corporation +5C1783 (base 16) Edgecore Americas Networking Corporation + 20 Mason + Irvine CA 92618 US -04-25-E8 (hex) Texas Instruments -0425E8 (base 16) Texas Instruments +64-70-60 (hex) Texas Instruments +647060 (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -28-3C-90 (hex) Texas Instruments -283C90 (base 16) Texas Instruments +00-AA-FD (hex) Texas Instruments +00AAFD (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -E4-FA-5B (hex) Texas Instruments -E4FA5B (base 16) Texas Instruments +0C-4B-EE (hex) Texas Instruments +0C4BEE (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -00-00-09 (hex) XEROX CORPORATION -000009 (base 16) XEROX CORPORATION - M/S 105-50C - WEBSTER NY 14580 - US +D4-13-B3 (hex) Wu Qi Technologies,Inc. +D413B3 (base 16) Wu Qi Technologies,Inc. + 14/F, 107 Middle Road, Xiantao Big Data Valley, Yubei District + Chongqing Chongqing 401120 + CN -44-C3-B6 (hex) HUAWEI TECHNOLOGIES CO.,LTD -44C3B6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +60-E5-D8 (hex) zte corporation +60E5D8 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -E8-F7-2F (hex) HUAWEI TECHNOLOGIES CO.,LTD -E8F72F (base 16) HUAWEI TECHNOLOGIES CO.,LTD +54-35-E9 (hex) Feitian Technologies Co., Ltd +5435E9 (base 16) Feitian Technologies Co., Ltd + Floor 17, Tower B, Huizhi Mansion, No.9 Xueqing Rd, Haidian District + Beijing 100085 + CN + +7C-B5-9F (hex) HUAWEI TECHNOLOGIES CO.,LTD +7CB59F (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -50-A1-F3 (hex) Huawei Device Co., Ltd. -50A1F3 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +50-5C-88 (hex) Cisco Systems, Inc +505C88 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +30-13-8B (hex) HP Inc. +30138B (base 16) HP Inc. + 10300 Energy Dr + Spring TX 77389 + US + +60-68-32 (hex) Guangdong Seneasy Intelligent Technology Co., Ltd. +606832 (base 16) Guangdong Seneasy Intelligent Technology Co., Ltd. + No. 63, Huitai Industrial Park, + Huizhou City, Guangdong Province 516000 + CN + +84-33-F2 (hex) Shenzhen Stellamore Technology Co.,Ltd +8433F2 (base 16) Shenzhen Stellamore Technology Co.,Ltd + 8th Floor, R&D Complex BuildingBaoyunda Logistics CenterFuhua Community, Xixiang Street, Bao'an + Shenzhen Guangdong 518100 + CN + +28-D0-43 (hex) AzureWave Technology Inc. +28D043 (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW -DC-10-57 (hex) Apple, Inc. -DC1057 (base 16) Apple, Inc. +E4-D3-AA (hex) FCNT LLC +E4D3AA (base 16) FCNT LLC + Sanki Yamato Building, 7-10-1 Chuorinkan + Yamato Kanagawa 242-8588 + JP + +B0-D5-76 (hex) Apple, Inc. +B0D576 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -30-D8-75 (hex) Apple, Inc. -30D875 (base 16) Apple, Inc. +14-28-76 (hex) Apple, Inc. +142876 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -48-5F-DF (hex) zte corporation -485FDF (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -6C-15-44 (hex) Microsoft Corporation -6C1544 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US - -68-3E-C0 (hex) Apple, Inc. -683EC0 (base 16) Apple, Inc. +14-7F-CE (hex) Apple, Inc. +147FCE (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -3C-55-DB (hex) Shenzhen Skyworth Digital Technology CO., Ltd -3C55DB (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 - CN - -F0-F8-4A (hex) BUFFALO.INC -F0F84A (base 16) BUFFALO.INC - AKAMONDORI Bld.,30-20,Ohsu 3-chome,Naka-ku - Nagoya Aichi Pref. 460-8315 - JP - -EC-6E-79 (hex) InHand Networks, INC. -EC6E79 (base 16) InHand Networks, INC. - 43671 Trade Center Place Suite 100 - Dulles VA 20166 +6C-C3-B2 (hex) Cisco Meraki +6CC3B2 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 US -A4-00-4E (hex) Cisco Systems, Inc -A4004E (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +F8-CE-07 (hex) ZHEJIANG DAHUA TECHNOLOGYCO.,LTD +F8CE07 (base 16) ZHEJIANG DAHUA TECHNOLOGYCO.,LTD + 1st Floor, Building 1, No. 1399, Binxing Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province 311200 + CN -2C-9E-E0 (hex) Cavli Inc. -2C9EE0 (base 16) Cavli Inc. - 99 South Almaden Blvd - San Jose CA 95113 - US +20-98-ED (hex) AltoBeam Inc. +2098ED (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN -64-C1-7E (hex) cheilelectric -64C17E (base 16) cheilelectric - 555, Eulsukdo-daero, Saha-gu, Busan, Republic of Korea - pusan 49437 +98-39-10 (hex) Kaon Group Co., Ltd. +983910 (base 16) Kaon Group Co., Ltd. + 884-3, Seongnam-daero, Bundang-gu + Seongnam-si Gyeonggi-do 13517 KR -B0-2E-E0 (hex) Huawei Device Co., Ltd. -B02EE0 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +80-4C-5D (hex) NXP Semiconductor (Tianjin) LTD. +804C5D (base 16) NXP Semiconductor (Tianjin) LTD. + No.15 Xinghua Avenue, Xiqing Economic Development Area + Tianjin 300385 CN -A4-37-3E (hex) Huawei Device Co., Ltd. -A4373E (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +F8-8F-C8 (hex) Chipsea Technologies (Shenzhen) Corp. +F88FC8 (base 16) Chipsea Technologies (Shenzhen) Corp. + 3 / F, Block A, Building 2, Shenzhen Bay Innovation Technology Center, No.3156 keyuan South Road, Yuehai Street, Nanshan District, Shenzhen + Shenzhen Guangdong 518000 CN -C4-4F-5F (hex) Huawei Device Co., Ltd. -C44F5F (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +28-91-76 (hex) Indyme Solutions, LLC +289176 (base 16) Indyme Solutions, LLC + 8295 Aero Place Ste 260 + San Diego CA 92123 + US -78-5F-28 (hex) EM Microelectronic -785F28 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +28-A0-6B (hex) Intel Corporate +28A06B (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -38-EF-E3 (hex) INGENICO TERMINALS SAS -38EFE3 (base 16) INGENICO TERMINALS SAS - 13-17 Rue Pagès - Suresnes 92150 - FR +7C-8B-C1 (hex) Infinix mobility limited +7C8BC1 (base 16) Infinix mobility limited + RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG + HongKong HongKong 999077 + HK -FC-8D-13 (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD -FC8D13 (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD - 19-22# Building, Star-net Science Plaza, Juyuanzhou, - FUZHOU FUJIAN 350002 - CN +3C-CA-61 (hex) TECNO MOBILE LIMITED +3CCA61 (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 + Hong Kong Hong Kong 999077 + HK -9C-B1-DC (hex) Earda Technologies co Ltd -9CB1DC (base 16) Earda Technologies co Ltd - Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District - Guangzhou Guangdong 511455 +EC-A9-71 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +ECA971 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 CN -24-04-62 (hex) Siemens Energy Global GmbH & Co.KG - GT PRM -240462 (base 16) Siemens Energy Global GmbH & Co.KG - GT PRM - Paulsternstrasse 26 - Berlin Berlin 13629 - DE +68-A2-AA (hex) Acres Manufacturing +68A2AA (base 16) Acres Manufacturing + 6415 S Tenaya Way + Las Vegas NV 89113 + US -38-88-71 (hex) ASKEY COMPUTER CORP -388871 (base 16) ASKEY COMPUTER CORP - 10F,No.119,JIANKANG RD,ZHONGHE DIST - NEW TAIPEI TAIWAN 23585 - TW +88-AE-35 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +88AE35 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 + CN -BC-F8-7E (hex) Arcadyan Corporation -BCF87E (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 +70-49-A2 (hex) Zyxel Communications Corporation +7049A2 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 TW -28-4E-E9 (hex) mercury corperation -284EE9 (base 16) mercury corperation - 90,gajaeul-ro,seo-gu,incheon - incheon 22830 +A0-7D-9C (hex) Samsung Electronics Co.,Ltd +A07D9C (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 KR -58-E3-59 (hex) Interroll Software & Electronics GmbH -58E359 (base 16) Interroll Software & Electronics GmbH - Im Südpark 183 - Linz 4030 - AT - -8C-32-23 (hex) JWIPC Technology Co.,Ltd. -8C3223 (base 16) JWIPC Technology Co.,Ltd. - 13/F, Haisong Building B, Tairan 9th Rd, Futian District - Shenzhen Guang Dong 5128042 - CN - -00-C8-96 (hex) CIG SHANGHAI CO LTD -00C896 (base 16) CIG SHANGHAI CO LTD - 5th Floor, Building 8 No 2388 Chenhang Road - SHANGHAI 201114 +F8-C3-F1 (hex) Raytron Photonics Co.,Ltd. +F8C3F1 (base 16) Raytron Photonics Co.,Ltd. + 3rd Floor, Building A, No. 66 Huaxiu Road, Xinwu District + Wuxi 214000 CN -00-1B-09 (hex) MATRIX COMSEC PRIVATE LIMITED -001B09 (base 16) MATRIX COMSEC PRIVATE LIMITED - 394, GIDC, Makarpura, - Vadodara Gujarat 390010 - IN +D8-95-63 (hex) Taiwan Digital Streaming Co. +D89563 (base 16) Taiwan Digital Streaming Co. + No. 50-15, Ln. 60, Jianping 9th St., Anping Dist. + Tainan City 70848 + TW -28-00-AF (hex) Dell Inc. -2800AF (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +E8-A8-48 (hex) Wacom Co.,Ltd. +E8A848 (base 16) Wacom Co.,Ltd. + Sumitomo Fudosan Shinjuku Grand Tower 31F,8-17-1 + Nishi-shinjuku,Shinjuku-ku Tokyo 160-6131 + JP -00-04-13 (hex) snom technology GmbH -000413 (base 16) snom technology GmbH - Aroser Allee 66 - Berlin 13407 - DE +5C-A3-EB (hex) SKODA DIGITAL s.r.o. +5CA3EB (base 16) SKODA DIGITAL s.r.o. + Na Rovince 874 + Ostrava Hrabova 720 00 + CZ -74-B0-59 (hex) Motorola Mobility LLC, a Lenovo Company -74B059 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +5C-BE-05 (hex) ISPEC +5CBE05 (base 16) ISPEC + 8 Palyong-ro, Uichang-gu, Changwon-si + Gyeongsangnam-do 51395 + KR -E4-84-29 (hex) New H3C Technologies Co., Ltd -E48429 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +08-52-4E (hex) Shenzhen Fangcheng Baiyi Technology Co., Ltd. +08524E (base 16) Shenzhen Fangcheng Baiyi Technology Co., Ltd. + 1401, Block B, Rongchao Binhai Building, No. 2021, Haixiu Road, Area N26, Haibin Community, Xin'an Street, Xixiang + Shenzhen Guangdong 518101 CN -F4-1A-79 (hex) IEEE Registration Authority -F41A79 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - -8C-07-34 (hex) Private -8C0734 (base 16) Private - -98-7A-9B (hex) TCL MOKA International Limited -987A9B (base 16) TCL MOKA International Limited - 7/F, Building 22E 22 Science Park East Avenue - Hong Kong 999077 - HK +AC-F4-2C (hex) Earda Technologies co Ltd +ACF42C (base 16) Earda Technologies co Ltd + Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District + Guangzhou Guangdong 511455 + CN -78-16-99 (hex) HUAWEI TECHNOLOGIES CO.,LTD -781699 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +C0-AB-2B (hex) Huawei Device Co., Ltd. +C0AB2B (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -E4-0A-16 (hex) HUAWEI TECHNOLOGIES CO.,LTD -E40A16 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +50-8A-7F (hex) HUAWEI TECHNOLOGIES CO.,LTD +508A7F (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -40-9C-A7 (hex) CHINA DRAGON TECHNOLOGY LIMITED -409CA7 (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 +C4-CB-BE (hex) Great Talent Technology Limited +C4CBBE (base 16) Great Talent Technology Limited + 35F, HBC HuiLong Center Building-II Minzhi Street, Longhua + Shenzhen Guangdong 518110 CN -D0-AD-08 (hex) HP Inc. -D0AD08 (base 16) HP Inc. - 10300 Energy Dr - Spring TX 77389 - US - -60-04-5C (hex) NXP Semiconductor (Tianjin) LTD. -60045C (base 16) NXP Semiconductor (Tianjin) LTD. - No.15 Xinghua Avenue, Xiqing Economic Development Area - Tianjin 300385 +E8-B5-27 (hex) Phyplus Technology (Shanghai) Co., Ltd +E8B527 (base 16) Phyplus Technology (Shanghai) Co., Ltd + 3th Floor, Building 23, 676 Wuxing Road, Pudong New District, Shanghai + Shanghai Shanghai 201204 CN -84-9C-02 (hex) Druid Software -849C02 (base 16) Druid Software - Block D, Civic Centre - Bray Wicklow A98 E1W9 - IE - -CC-40-B2 (hex) ECI Telecom Ltd. -CC40B2 (base 16) ECI Telecom Ltd. - 30 Hasivim St. - Petah Tikva 49133 - IL +98-A8-78 (hex) Agnigate Technologies Private Limited +98A878 (base 16) Agnigate Technologies Private Limited + 75-ROHIT NAGAR, PHASE-2, E-8 EXTENSION, BAWADIYA KALAN, BHOPAL + BHOPAL Madhya Pradesh 462026 + IN -A4-E2-87 (hex) Xiaomi Communications Co Ltd -A4E287 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +30-52-23 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +305223 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN -80-95-3A (hex) Apple, Inc. -80953A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +DC-49-65 (hex) DASAN Newtork Solutions +DC4965 (base 16) DASAN Newtork Solutions + DASAN Tower, 49, Daewangpangyo-ro644Beon-gil, Bundang-gu, Seongnam-si, Gyeonggi-do, 13493 KOREA + Seongnam-si Gyeonggi-do 13493 + KR -68-AE-04 (hex) Shenzhen SuperElectron Technology Co.,Ltd. -68AE04 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. - 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city - Shenzhen Guangdong 518000 +18-9E-2D (hex) Allwinner Technology Co., Ltd +189E2D (base 16) Allwinner Technology Co., Ltd + No.9 Technology Road 2, High-Tech Zone + Zhuhai Guangdong 519085 CN -CC-60-23 (hex) Apple, Inc. -CC6023 (base 16) Apple, Inc. +9C-60-76 (hex) Apple, Inc. +9C6076 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -0C-DB-EA (hex) Apple, Inc. -0CDBEA (base 16) Apple, Inc. +38-E1-3D (hex) Apple, Inc. +38E13D (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -68-45-CC (hex) Apple, Inc. -6845CC (base 16) Apple, Inc. +D0-D4-9F (hex) Apple, Inc. +D0D49F (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -AC-97-38 (hex) Apple, Inc. -AC9738 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +9C-2D-49 (hex) Nanowell Info Tech Co., Limited +9C2D49 (base 16) Nanowell Info Tech Co., Limited + 1205 Building 2, Xunmei Science Technology Plaza, No 8 Keyuan Road, Nanshan District + Shenzhen Guangdong 518057 + CN -08-C2-24 (hex) Amazon Technologies Inc. -08C224 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +88-7F-D5 (hex) zte corporation +887FD5 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -68-93-2E (hex) Habana Labs LTD. -68932E (base 16) Habana Labs LTD. - 9 Granite st. - Caesarea Select ... 3079821 - IL +F4-74-70 (hex) Cisco Systems, Inc +F47470 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -2C-C4-4F (hex) IEEE Registration Authority -2CC44F (base 16) IEEE Registration Authority +60-A4-34 (hex) IEEE Registration Authority +60A434 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -1C-4C-27 (hex) World WLAN Application Alliance -1C4C27 (base 16) World WLAN Application Alliance - 5th Floor, Block B, Shenzhen-Hong Kong International Science and Technology Park, No. 14 Taohua Road, Futian District, Shenzhen - shenzhen guangdong province 518017 +38-7B-01 (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd +387B01 (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd + 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District + Shenzhen Guangdong 518110 CN -D0-C9-01 (hex) GLA ELECTRONICS PVT LTD -D0C901 (base 16) GLA ELECTRONICS PVT LTD - B 14/2 JHILMIL INDUSTRIAL AREA DELHI - DELHI DELHI 110095 - IN +0C-ED-C8 (hex) Xiaomi Communications Co Ltd +0CEDC8 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -40-99-E3 (hex) Guangzhou Mudi Information Technology Co., Ltd -4099E3 (base 16) Guangzhou Mudi Information Technology Co., Ltd - Room 403, 404, No. 8, Yongtai Taixing Road, Yongping Street, Baiyun District, Guangzhou city - Guangzhou City Guangdong Province 510000 +88-BC-AC (hex) Zebra Technologies Inc. +88BCAC (base 16) Zebra Technologies Inc. + ONE ZEBRA PLAZA + HOLTSVILLE NY 11742 + US + +C8-8D-D4 (hex) Markone technology Co., Ltd. +C88DD4 (base 16) Markone technology Co., Ltd. + A-2401,2402,2403,2404, 606, Seobusaet-gil, Geumcheon-gu + Seoul 08504 + KR + +2C-36-F2 (hex) HUAWEI TECHNOLOGIES CO.,LTD +2C36F2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -7C-88-99 (hex) FN-LINK TECHNOLOGY Ltd. -7C8899 (base 16) FN-LINK TECHNOLOGY Ltd. - No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China - Changsha Hunan 410329 +E4-99-5F (hex) HUAWEI TECHNOLOGIES CO.,LTD +E4995F (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -FC-70-2E (hex) Sichuan AI-Link Technology Co., Ltd. -FC702E (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 +A8-F0-59 (hex) HUAWEI TECHNOLOGIES CO.,LTD +A8F059 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -B4-F9-5D (hex) Juniper Networks -B4F95D (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +28-3D-E8 (hex) Guangzhou Shiyuan Electronic Technology Company Limited +283DE8 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 + CN -04-68-74 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -046874 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 +18-04-03 (hex) vivo Mobile Communication Co., Ltd. +180403 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN -74-90-BC (hex) Arcadyan Corporation -7490BC (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +20-6B-D5 (hex) vivo Mobile Communication Co., Ltd. +206BD5 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN -B0-47-E9 (hex) Intel Corporate -B047E9 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +B4-7A-F1 (hex) Hewlett Packard Enterprise +B47AF1 (base 16) Hewlett Packard Enterprise + 8000 Foothills Blvd. + Roseville CA 95747 + US -6C-2F-80 (hex) Intel Corporate -6C2F80 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +48-9E-CB (hex) Hewlett Packard Enterprise +489ECB (base 16) Hewlett Packard Enterprise + 8000 Foothills Blvd. + Roseville 95747 + US -D0-65-78 (hex) Intel Corporate -D06578 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +9C-1C-12 (hex) Hewlett Packard Enterprise +9C1C12 (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 + US -98-BD-80 (hex) Intel Corporate -98BD80 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +04-BD-88 (hex) Hewlett Packard Enterprise +04BD88 (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 + US -D4-76-A0 (hex) Fortinet, Inc. -D476A0 (base 16) Fortinet, Inc. - 899 Kifer Road - Sunnyvale 94086 +E8-F7-24 (hex) Hewlett Packard Enterprise +E8F724 (base 16) Hewlett Packard Enterprise + 8000 Foothills Blvd. + Roseville CA 95747 US -94-FF-3C (hex) Fortinet, Inc. -94FF3C (base 16) Fortinet, Inc. - 899 Kifer Road - Sunnyvale 94086 +DC-68-0C (hex) Hewlett Packard Enterprise +DC680C (base 16) Hewlett Packard Enterprise + 8000 Foothills Blvd. + Roseville CA 95747 US -A0-F5-09 (hex) IEI Integration Corp. -A0F509 (base 16) IEI Integration Corp. - 4F., No. 29, Zhongxing Rd., Xizhi Dist., - New Taipei City 221 - TW +48-DF-37 (hex) Hewlett Packard Enterprise +48DF37 (base 16) Hewlett Packard Enterprise + 8000 Foothills Blvd. + Roseville CA 95747 + US -6C-45-C4 (hex) Cloudflare, Inc. -6C45C4 (base 16) Cloudflare, Inc. - 101 Townsend Street - San Francisco CA 94107 +20-67-7C (hex) Hewlett Packard Enterprise +20677C (base 16) Hewlett Packard Enterprise + 8000 Foothills Blvd. + Roseville CA 95747 US -00-0D-97 (hex) Hitachi Energy USA Inc. -000D97 (base 16) Hitachi Energy USA Inc. - 901 Main Campus Drive - Raleigh NC 27606 +44-48-C1 (hex) Hewlett Packard Enterprise +4448C1 (base 16) Hewlett Packard Enterprise + 8000 Foothills Blvd. + Roseville CA 95747 US -18-7F-88 (hex) Ring LLC -187F88 (base 16) Ring LLC - 1523 26th St - Santa Monica CA 90404 +68-28-CF (hex) Hewlett Packard Enterprise +6828CF (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 US -B8-7E-40 (hex) Huawei Device Co., Ltd. -B87E40 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +E4-DE-40 (hex) Hewlett Packard Enterprise +E4DE40 (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 + US -1C-2F-A2 (hex) Guangzhou Shiyuan Electronic Technology Company Limited -1C2FA2 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 - CN +48-00-20 (hex) Hewlett Packard Enterprise +480020 (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 + US -A4-3F-68 (hex) Arista Network, Inc. -A43F68 (base 16) Arista Network, Inc. - 5453 Great America Parkway - Santa Clara CA 95054 +5C-A4-7D (hex) Hewlett Packard Enterprise +5CA47D (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 US -BC-9D-4E (hex) Shenzhen Skyworth Digital Technology CO., Ltd -BC9D4E (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 +E8-95-05 (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd +E89505 (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd + 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District + Shenzhen Guangdong 518110 CN -1C-3B-01 (hex) Shanghai Xiaodu Technology Limited -1C3B01 (base 16) Shanghai Xiaodu Technology Limited - 4th Floor Building No.1 , No.701 Naxian Road Pilot Free Trade Zone Shanghai China - Shanghai 200000 - CN +DC-B7-AC (hex) Hewlett Packard Enterprise +DCB7AC (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 + US -94-70-6C (hex) Quectel Wireless Solutions Co.,Ltd. -94706C (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN +54-D7-E3 (hex) Hewlett Packard Enterprise +54D7E3 (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 + US -64-D8-1B (hex) Vestel Elektronik San ve Tic. A.S. -64D81B (base 16) Vestel Elektronik San ve Tic. A.S. - Organize san - Manisa Turket 45030 - TR +14-AB-EC (hex) Hewlett Packard Enterprise +14ABEC (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 + US -30-DC-E7 (hex) zte corporation -30DCE7 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +10-4F-58 (hex) Hewlett Packard Enterprise +104F58 (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 + US + +CC-D0-83 (hex) Hewlett Packard Enterprise +CCD083 (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 + US + +BC-46-32 (hex) Fiberhome Telecommunication Technologies Co.,LTD +BC4632 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -D0-12-55 (hex) Hui Zhou Gaoshengda Technology Co.,LTD -D01255 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD - No.2,Jin-da Road,Huinan Industrial Park - Hui Zhou Guangdong 516025 +68-53-77 (hex) Bouffalo Lab (Nanjing) Co., Ltd. +685377 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 CN -3C-2D-9E (hex) Vantiva - Connected Home -3C2D9E (base 16) Vantiva - Connected Home - 4855 Peachtree Industrial Blvd, #200 - Norcross GA 30092 +F0-5C-19 (hex) Hewlett Packard Enterprise +F05C19 (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 US -08-C7-F5 (hex) Vantiva Connected Home - Technologies Telco -08C7F5 (base 16) Vantiva Connected Home - Technologies Telco - 4855 Peachtree Industrial Blvd, Suite 200 - Norcross GA 30902 +7C-57-3C (hex) Hewlett Packard Enterprise +7C573C (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 US -C0-16-93 (hex) Xiaomi Communications Co Ltd -C01693 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +34-8A-12 (hex) Hewlett Packard Enterprise +348A12 (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 + US + +10-63-A3 (hex) IEEE Registration Authority +1063A3 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +DC-A7-06 (hex) CHENGDU KT ELECTRONIC HI-TECH CO.,LTD +DCA706 (base 16) CHENGDU KT ELECTRONIC HI-TECH CO.,LTD + No.9, 3rd Wuke Road, Wuhou District + Chengdu Sichuan Province 610045 CN -FC-5B-8C (hex) Xiaomi Communications Co Ltd -FC5B8C (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +E0-60-4A (hex) Fiberhome Telecommunication Technologies Co.,LTD +E0604A (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -28-4E-44 (hex) HUAWEI TECHNOLOGIES CO.,LTD -284E44 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +50-06-F5 (hex) Roku, Inc +5006F5 (base 16) Roku, Inc + 1173 Coleman Ave + San Jose CA 95110 + US + +98-16-CD (hex) leapio +9816CD (base 16) leapio + beijing,haidian + beijing beijing 100000 + CN + +34-51-6F (hex) Skychers Creations ShenZhen Limited +34516F (base 16) Skychers Creations ShenZhen Limited + Room 907A, 9/F, Block T2, FongDa City, Longjing Village, Longzhu Avenue, Nanshan District + Shenzhen Guangdong 518073 + CN + +C8-6E-08 (hex) Intel Corporate +C86E08 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +B8-37-4B (hex) Hewlett Packard Enterprise +B8374B (base 16) Hewlett Packard Enterprise + 1701 E Mossy Oaks Rd + Spring TX 77389 + US + +18-86-37 (hex) INGRAM MICRO SERVICES +188637 (base 16) INGRAM MICRO SERVICES + 100 CHEMIN DE BAILLOT + MONTAUBAN 82000 + FR + +78-07-8F (hex) HUAWEI TECHNOLOGIES CO.,LTD +78078F (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -80-2E-C3 (hex) HUAWEI TECHNOLOGIES CO.,LTD -802EC3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +30-9E-62 (hex) HUAWEI TECHNOLOGIES CO.,LTD +309E62 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -9C-9E-03 (hex) awayfrom -9C9E03 (base 16) awayfrom - A-2723 Ho, Sambo Techon Tower,122,jomaru-ro 385 Beon-gil - bucheon-si Gyeonnggi-do 14556 - KR +10-C0-D5 (hex) HOLOEYE Photonics AG +10C0D5 (base 16) HOLOEYE Photonics AG + Volmerstr. 1 + Berlin 12489 + DE -80-05-3A (hex) CHeKT Inc. -80053A (base 16) CHeKT Inc. - 5870 Greenwood Rd. - Shreveport LA 71119 +54-FA-89 (hex) Medtronic CRM +54FA89 (base 16) Medtronic CRM + 8200 Coral Sea Street NEMVN22 + Mounds View MN 55112 US -D8-B3-2F (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -D8B32F (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 +24-16-51 (hex) Chipsea Technologies (Shenzhen) Corp. +241651 (base 16) Chipsea Technologies (Shenzhen) Corp. + 3 / F, Block A, Building 2, Shenzhen Bay Innovation Technology Center, No.3156 keyuan South Road, Yuehai Street, Nanshan District, Shenzhen + Shenzhen Guangdong 518000 CN -54-10-4F (hex) Samsung Electronics Co.,Ltd -54104F (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +58-76-9C (hex) Palo Alto Networks +58769C (base 16) Palo Alto Networks + 3000 Tannery Way + Santa Clara CA 95054 + US -B0-54-76 (hex) Samsung Electronics Co.,Ltd -B05476 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +B4-C5-56 (hex) Shanghai Kenmyond Industrial Network Equipment Co., Ltd +B4C556 (base 16) Shanghai Kenmyond Industrial Network Equipment Co., Ltd + 5th floor,Building3,NO69,yuanfeng Road,Baoshan + Shanghai Shanghai 021-56561181 + CN -EC-90-C1 (hex) Samsung Electronics Co.,Ltd -EC90C1 (base 16) Samsung Electronics Co.,Ltd +EC-72-5B (hex) zte corporation +EC725B (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +E4-92-82 (hex) Samsung Electronics Co.,Ltd +E49282 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -30-D9-59 (hex) Shanghai Longcheer Technology Co., Ltd. -30D959 (base 16) Shanghai Longcheer Technology Co., Ltd. - Bldg 1,No.401,Caobao RD,Xuhui Dist - Shanghai 200233 - CN - -00-78-39 (hex) Nokia -007839 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 +00-09-F7 (hex) Calian Advanced Technologies +0009F7 (base 16) Calian Advanced Technologies + 18 Innnovation Boulevard + Saskatoon SK S7N 3R1 CA -10-8A-7B (hex) Nokia -108A7B (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +04-94-E9 (hex) FAXedge Technologies, LLC +0494E9 (base 16) FAXedge Technologies, LLC + 400 Liberty Park Court + Flowood MS 39232 + US -A0-52-AB (hex) AVM ELECTRONICS PTE LTD -A052AB (base 16) AVM ELECTRONICS PTE LTD - 362, UPPER PAYA LEBAR ROAD, #05-07 Da Jin Factory Building - Singapore Singapore 534963 - SG +38-7A-CC (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +387ACC (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 + CN -BC-74-4B (hex) Nintendo Co.,Ltd -BC744B (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP +7C-72-E7 (hex) Texas Instruments +7C72E7 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -6C-6F-18 (hex) Stereotaxis, Inc. -6C6F18 (base 16) Stereotaxis, Inc. - 710 N. Tucker Blvd, Suite 110 - St. Louis MO 63101 +30-30-D0 (hex) Texas Instruments +3030D0 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -F0-86-6F (hex) EM Microelectronic -F0866F (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +54-FE-EB (hex) Texas Instruments +54FEEB (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -5C-B2-60 (hex) EM Microelectronic -5CB260 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +08-84-FB (hex) Honor Device Co., Ltd. +0884FB (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 -A8-BB-56 (hex) Apple, Inc. -A8BB56 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +14-BE-FC (hex) Nanjing Jiahao Technology Co., Ltd. +14BEFC (base 16) Nanjing Jiahao Technology Co., Ltd. + Moling Industrial Park, Development Zone, Jiangning, Nanjing + Nanjing Jiangsu 211111 + CN -28-2D-7F (hex) Apple, Inc. -282D7F (base 16) Apple, Inc. +90-5F-7A (hex) Apple, Inc. +905F7A (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -8C-26-AA (hex) Apple, Inc. -8C26AA (base 16) Apple, Inc. +F8-F5-8C (hex) Apple, Inc. +F8F58C (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -90-62-3F (hex) Apple, Inc. -90623F (base 16) Apple, Inc. +0C-85-E1 (hex) Apple, Inc. +0C85E1 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -5C-07-A4 (hex) Ciena Corporation -5C07A4 (base 16) Ciena Corporation - 900 International Drive - Linthicum MD 21090-2200 - US - -00-4B-F3 (hex) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. -004BF3 (base 16) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. - 6th Floor, South Section, Building 23, Keyuan West, NO.1 Kezhi West Road, Science and Technology Community, Yuehai Street, Nanshan District - Shenzhen Guangdong 518057 - CN - -BC-54-FC (hex) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. -BC54FC (base 16) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. - 6th Floor, South Section, Building 23, Keyuan West, NO.1 Kezhi West Road, Science and Technology Community, Yuehai Street, Nanshan District - Shenzhen Guangdong 518057 - CN - -90-76-9F (hex) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. -90769F (base 16) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. - 6th Floor, South Section, Building 23, Keyuan West, NO.1 Kezhi West Road, Science and Technology Community, Yuehai Street, Nanshan District - Shenzhen Guangdong 518057 - CN - -4C-77-66 (hex) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. -4C7766 (base 16) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. - 6th Floor, South Section, Building 23, Keyuan West, NO.1 Kezhi West Road, Science and Technology Community, Yuehai Street, Nanshan District - Shenzhen Guangdong 518057 - CN - -00-5C-C2 (hex) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. -005CC2 (base 16) SHENZHEN MERCURY COMMUNICATION TECHNOLOGIES CO.,LTD. - 6th Floor, South Section, Building 23, Keyuan West, NO.1 Kezhi West Road, Science and Technology Community, Yuehai Street, Nanshan District - Shenzhen Guangdong 518057 - CN - -FC-E8-C0 (hex) Espressif Inc. -FCE8C0 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -88-54-8E (hex) vivo Mobile Communication Co., Ltd. -88548E (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN - -58-1D-C9 (hex) MSE CO.,LTD. -581DC9 (base 16) MSE CO.,LTD. - 3-1-6, Kyutaromachi, Chuo-ku, - OSAKA OSAKA 5410056 - JP - -CC-7B-5C (hex) Espressif Inc. -CC7B5C (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -50-5B-1D (hex) Shenzhen C-Data Technology Co., Ltd. -505B1D (base 16) Shenzhen C-Data Technology Co., Ltd. - #201, Building A4, Nanshan Zhiyuan, No.1001, Xueyuan Avenue, Changyuan Community,Taoyuan,Nanshan - Shenzhen Guangdong 518055 +90-FB-5D (hex) Beijing Xiaomi Mobile Software Co., Ltd +90FB5D (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -88-01-0C (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -88010C (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County - Chengdu Sichuan 611330 +44-16-90 (hex) Wuxi Ranke Technology Co., Ltd. +441690 (base 16) Wuxi Ranke Technology Co., Ltd. + 110-268, 1st Floor, Building A10, No,777 Jianzhu West Road, Livuan Street Binhy District + Wuxi Jiangsu 214062 CN -A4-9E-69 (hex) Silicon Laboratories -A49E69 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - -F0-82-C0 (hex) Silicon Laboratories -F082C0 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - -60-EF-AB (hex) Silicon Laboratories -60EFAB (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - -D8-7A-3B (hex) Silicon Laboratories -D87A3B (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - -3C-2E-F5 (hex) Silicon Laboratories -3C2EF5 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - -34-10-F4 (hex) Silicon Laboratories -3410F4 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - -0C-EF-F6 (hex) Silicon Laboratories -0CEFF6 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US +9C-A6-D8 (hex) Fiberhome Telecommunication Technologies Co.,LTD +9CA6D8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN -EC-F6-4C (hex) Silicon Laboratories -ECF64C (base 16) Silicon Laboratories +0C-2A-6F (hex) Silicon Laboratories +0C2A6F (base 16) Silicon Laboratories 400 West Cesar Chavez Austin TX 78701 US -70-C9-12 (hex) Sichuan AI-Link Technology Co., Ltd. -70C912 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 - CN - -48-8E-B7 (hex) Zebra Technologies Inc. -488EB7 (base 16) Zebra Technologies Inc. - ONE ZEBRA PLAZA - HOLTSVILLE NY 11742 +20-DB-EA (hex) Cisco Systems, Inc +20DBEA (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -90-AB-96 (hex) Silicon Laboratories -90AB96 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 +A0-47-9B (hex) PROCITEC GmbH +A0479B (base 16) PROCITEC GmbH + Rastatter Str. 41 + Pforzheim 75179 + DE + +74-C9-0F (hex) Microchip Technologies Inc +74C90F (base 16) Microchip Technologies Inc + 2355 W Chandler Blvd + Chandler AZ 85224-6199 US -0C-82-D5 (hex) Maxio Technology Hangzhou Co., Ltd. -0C82D5 (base 16) Maxio Technology Hangzhou Co., Ltd. - Block C, Juguang Center, Qianmo Road, Binjiang District, Hangzhou City, Zhejiang Province - Hangzhou 310000 +18-E2-04 (hex) BEIJING COOLSHARK TECHNOLOGY CO.,LTD. +18E204 (base 16) BEIJING COOLSHARK TECHNOLOGY CO.,LTD. + B503,Zhongguancun Science and Technology Service Building, No.2 Guanzhuang Road + Beijing Chaoyang District 100101 CN -04-01-BB (hex) TECNO MOBILE LIMITED -0401BB (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 - Hong Kong Hong Kong 999077 - HK +2C-B7-A1 (hex) Huawei Device Co., Ltd. +2CB7A1 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -AC-72-DD (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -AC72DD (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 +6C-82-43 (hex) Huawei Device Co., Ltd. +6C8243 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -64-BB-1E (hex) Earda Technologies co Ltd -64BB1E (base 16) Earda Technologies co Ltd - Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District - Guangzhou Guangdong 511455 +B4-76-A4 (hex) Huawei Device Co., Ltd. +B476A4 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -64-82-14 (hex) FN-LINK TECHNOLOGY Ltd. -648214 (base 16) FN-LINK TECHNOLOGY Ltd. - No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China - Changsha Hunan 410329 +50-26-D2 (hex) AVIRE Trading Limited +5026D2 (base 16) AVIRE Trading Limited + Unit 1 The Switchback, Gardner Road + Maidenhead Berkshire SL6 7RJ + GB + +94-24-53 (hex) HUAWEI TECHNOLOGIES CO.,LTD +942453 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -E0-9C-E5 (hex) Shanghai Tricheer Technology Co.,Ltd. -E09CE5 (base 16) Shanghai Tricheer Technology Co.,Ltd. - Rm 907, Building 1, Lane 399, Shengxia Road,Zhangjiang Hi-Tech Park,Pudong District,Shanghai - Shanghai Shanghai 201203 +18-4F-43 (hex) UNIONMAN TECHNOLOGY CO.,LTD +184F43 (base 16) UNIONMAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 CN -FC-D2-90 (hex) SKY UK LIMITED -FCD290 (base 16) SKY UK LIMITED - Grant Way - Isleworth Middlesex TW7 5QD - GB +F8-6B-FA (hex) Infinix mobility limited +F86BFA (base 16) Infinix mobility limited + RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG + HongKong HongKong 999077 + HK -78-30-5D (hex) zte corporation -78305D (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +B0-32-26 (hex) Keheng Information Industry Co., Ltd. +B03226 (base 16) Keheng Information Industry Co., Ltd. + Building 4, Lushang Guo Olympic City, No. 9777 Jingshi Road, Lixia District, Jinan City, Shandong Province. + Jinan Shandong 250098 CN -94-0B-83 (hex) zte corporation -940B83 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +84-CB-85 (hex) EM Microelectronic +84CB85 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +AC-F2-3C (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +ACF23C (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 CN -E4-74-50 (hex) Shenzhen Grandsun Electronic Co.,Ltd. -E47450 (base 16) Shenzhen Grandsun Electronic Co.,Ltd. - Gaoqiao Industrial Zone,Baishitang Village, - Shenzhen Guangdong 518116 +E4-57-68 (hex) vivo Mobile Communication Co., Ltd. +E45768 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN -B4-A3-BD (hex) Extreme Networks Headquarters -B4A3BD (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 +48-E6-C6 (hex) IEEE Registration Authority +48E6C6 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -7C-D3-E5 (hex) HUAWEI TECHNOLOGIES CO.,LTD -7CD3E5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +00-04-17 (hex) Schneider Electric +000417 (base 16) Schneider Electric + Schneiderplatz 1 + Marktheidenfeld 97828 + DE -B8-87-6E (hex) Intertech Services AG -B8876E (base 16) Intertech Services AG - Werftestrasse 4, - Luzern 6005 - CH +D0-1B-BE (hex) Onward Brands +D01BBE (base 16) Onward Brands + 767 Fifth Ave, 37F + New York NY 10153 + US -3C-0B-4F (hex) Intertech Services AG -3C0B4F (base 16) Intertech Services AG - Werftestrasse 4, - Luzern 6005 - CH +24-6A-0E (hex) HP Inc. +246A0E (base 16) HP Inc. + 10300 Energy Dr + Spring TX 77389 + US -6C-62-FE (hex) Juniper Networks -6C62FE (base 16) Juniper Networks - 1133 Innovation Way +B0-7C-51 (hex) Ruckus Wireless +B07C51 (base 16) Ruckus Wireless + 350 West Java Drive Sunnyvale CA 94089 US -9C-9E-D5 (hex) Xiaomi Communications Co Ltd -9C9ED5 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +88-4F-59 (hex) Cisco Systems, Inc +884F59 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +7C-FA-80 (hex) JiangSu Fulian Communication Technology Co., Ltd +7CFA80 (base 16) JiangSu Fulian Communication Technology Co., Ltd + Jiang Su Fulian Communication Technology Co., Ltd + Danyang Jiangsu 212300 CN -74-38-22 (hex) Xiaomi Communications Co Ltd -743822 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +C0-87-06 (hex) Shenzhen Qianfenyi Intelligent Technology Co.,LTD +C08706 (base 16) Shenzhen Qianfenyi Intelligent Technology Co.,LTD + Room 2101, Building 3, Nanshan i ParkChongwen, No. 3370 Liuxian Avenue,Nanshan District + Shenzhen Guangdong 518000 CN -B8-3B-AB (hex) Arcadyan Corporation -B83BAB (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +C4-B3-49 (hex) Apple, Inc. +C4B349 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -40-A6-3D (hex) SignalFire Telemetry -40A63D (base 16) SignalFire Telemetry - 140 Locke Dr. Suite B - Marlborough MA 01752 +34-F6-8D (hex) Apple, Inc. +34F68D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -60-54-64 (hex) Eyedro Green Solutions Inc. -605464 (base 16) Eyedro Green Solutions Inc. - 606 Colby Dr, Unit E - Waterloo Ontario N2V1A2 - CA +AC-B7-22 (hex) Qingdao Haier Technology Co.,Ltd +ACB722 (base 16) Qingdao Haier Technology Co.,Ltd + Building C01,Haier Information Park,No.1 Haier Road + Qingdao 266101 + CN -CC-E5-36 (hex) ittim -CCE536 (base 16) ittim - 1202, No.6, Zhongguancun South Street, Haidian District, - beijing 100080 +CC-27-46 (hex) Apple, Inc. +CC2746 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +28-59-23 (hex) Xiaomi Communications Co Ltd +285923 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -BC-00-04 (hex) Fiberhome Telecommunication Technologies Co.,LTD -BC0004 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +D0-CE-C0 (hex) Xiaomi Communications Co Ltd +D0CEC0 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -98-A8-29 (hex) AltoBeam Inc. -98A829 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 +0C-01-A5 (hex) zte corporation +0C01A5 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -84-E9-C1 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -84E9C1 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 +98-FA-9B (hex) LCFC(Hefei) Electronics Technology co., ltd +98FA9B (base 16) LCFC(Hefei) Electronics Technology co., ltd + YunGu Road 3188-1 + Hefei Anhui 230000 CN -B4-62-2E (hex) Zhong Ge Smart Technology Co., Ltd. -B4622E (base 16) Zhong Ge Smart Technology Co., Ltd. - Zhong Ge Smart, 5th floor building G - Shanghai 201199 +F8-75-A4 (hex) LCFC(Hefei) Electronics Technology co., ltd +F875A4 (base 16) LCFC(Hefei) Electronics Technology co., ltd + YunGu Road 3188-1 + Hefei Anhui 230000 CN -C4-6E-33 (hex) Zhong Ge Smart Technology Co., Ltd. -C46E33 (base 16) Zhong Ge Smart Technology Co., Ltd. - Zhong Ge Smart, 5th floor building G - Shanghai 201199 +8C-8C-AA (hex) LCFC(Hefei) Electronics Technology co., ltd +8C8CAA (base 16) LCFC(Hefei) Electronics Technology co., ltd + YunGu Road 3188-1 + Hefei Anhui 230000 CN -2C-67-BE (hex) DWnet Technologies(Suzhou) Corporation -2C67BE (base 16) DWnet Technologies(Suzhou) Corporation - No.8,Tangzhuang Road, Suzhou Industrial Park, Jiangsu, China - Suzhou 21500 +70-19-88 (hex) Nanjing Qinheng Microelectronics Co., Ltd. +701988 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. + No.18, Ningshuang Road + Nanjing Jiangsu 210012 CN -EC-19-2E (hex) Cisco Systems, Inc -EC192E (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +FC-1A-46 (hex) Samsung Electronics Co.,Ltd +FC1A46 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -F8-96-FE (hex) LG Innotek -F896FE (base 16) LG Innotek - 26, HANAMSANDAN 5BEON-RO - Gwangju Gwangsan-gu 506-731 +98-3D-AE (hex) Espressif Inc. +983DAE (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +5C-5E-0A (hex) Samsung Electronics Co.,Ltd +5C5E0A (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 KR -A4-C4-0D (hex) WAC Lighting -A4C40D (base 16) WAC Lighting - 44 Harbor Park Dr - Port Washington NY 11050 +CC-B0-B3 (hex) Microsoft Corporation +CCB0B3 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 US -58-30-6E (hex) Nokia -58306E (base 16) Nokia +80-D5-2C (hex) Beijing Cheering Networks Technology Co.,Ltd. +80D52C (base 16) Beijing Cheering Networks Technology Co.,Ltd. + Room859,Floor8,Building2,No.18,Yangfangdian Road, Haidian District, Beijing + Beijing Beijing 100038 + CN + +88-B2-AB (hex) Fiberhome Telecommunication Technologies Co.,LTD +88B2AB (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN + +24-E3-A4 (hex) Fiberhome Telecommunication Technologies Co.,LTD +24E3A4 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN + +30-00-FC (hex) Nokia +3000FC (base 16) Nokia 600 March Road Kanata Ontario K2K 2E6 CA -A8-CA-77 (hex) Amazon Technologies Inc. -A8CA77 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - -D0-C9-07 (hex) Private -D0C907 (base 16) Private +64-DE-6D (hex) Intel Corporate +64DE6D (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -14-2B-2F (hex) Espressif Inc. -142B2F (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +EC-4C-8C (hex) Intel Corporate +EC4C8C (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -C4-A4-51 (hex) TECNO MOBILE LIMITED -C4A451 (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 - Hong Kong Hong Kong 999077 - HK +EC-66-52 (hex) Info Fiber Solutions Pvt Ltd +EC6652 (base 16) Info Fiber Solutions Pvt Ltd + Basement B 64, Sector 65, Noida + Gautam Buddha Nagar UP 201301 + IN -F8-9D-9D (hex) Shenzhen MinewSemi Co.,LTD. -F89D9D (base 16) Shenzhen MinewSemi Co.,LTD. - 3Floor, I Building, Gangzhilong Tech Park, No.6, Qinglong Road, Longhua District - Shenzhen 518109 +CC-76-3A (hex) zte corporation +CC763A (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -F0-61-F3 (hex) Comcast Cable Corporation -F061F3 (base 16) Comcast Cable Corporation - 1800 Arch Street - Philadelphia PA 19103 - US +30-04-75 (hex) QBIC COMMUNICATIONS DMCC +300475 (base 16) QBIC COMMUNICATIONS DMCC + 1003 Palladium Tower JLT Dubai, UAE + Dubai 393518 + AE -D4-8D-26 (hex) LG Innotek -D48D26 (base 16) LG Innotek - 26, HANAMSANDAN 5BEON-RO - Gwangju Gwangsan-gu 506-731 - KR +00-0A-F6 (hex) Copeland LP +000AF6 (base 16) Copeland LP + 1640 Airport Rd + Kennesaw GA 30144-7038 + US -F4-F2-8A (hex) HUAWEI TECHNOLOGIES CO.,LTD -F4F28A (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +5C-18-DD (hex) CIG SHANGHAI CO LTD +5C18DD (base 16) CIG SHANGHAI CO LTD + 5th Floor, Building 8 No 2388 Chenhang Road + SHANGHAI 201114 CN -94-E3-00 (hex) HUAWEI TECHNOLOGIES CO.,LTD -94E300 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +14-36-0E (hex) Zyxel Communications Corporation +14360E (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + +5C-4E-EE (hex) AltoBeam Inc. +5C4EEE (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -0C-E5-B5 (hex) HUAWEI TECHNOLOGIES CO.,LTD -0CE5B5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +5C-C1-F2 (hex) HUAWEI TECHNOLOGIES CO.,LTD +5CC1F2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -FC-F7-38 (hex) HUAWEI TECHNOLOGIES CO.,LTD -FCF738 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +C8-05-A4 (hex) Motorola(Wuhan) Mobility Technologies Communication Co.,Ltd +C805A4 (base 16) Motorola(Wuhan) Mobility Technologies Communication Co.,Ltd + No.19, Gaoxin 4th Road,WUhan East Lake High-tech Zone, Wuhan + Wuhan Hubei 430000 CN -8C-86-2A (hex) HUAWEI TECHNOLOGIES CO.,LTD -8C862A (base 16) HUAWEI TECHNOLOGIES CO.,LTD +00-72-EE (hex) Intel Corporate +0072EE (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +C8-58-B3 (hex) Intel Corporate +C858B3 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +E4-1F-D5 (hex) Intel Corporate +E41FD5 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +EC-1B-5F (hex) Hewlett Packard Enterprise +EC1B5F (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +1C-B4-6C (hex) HUAWEI TECHNOLOGIES CO.,LTD +1CB46C (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -C0-33-79 (hex) HUAWEI TECHNOLOGIES CO.,LTD -C03379 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +E8-F6-73 (hex) Microsoft Corporation +E8F673 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +78-64-A0 (hex) Cisco Systems, Inc +7864A0 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +5C-DB-36 (hex) Calix Inc. +5CDB36 (base 16) Calix Inc. + 2777 Orchard Pkwy + San Jose CA 95131 + US + +4C-7B-35 (hex) UNIONMAN TECHNOLOGY CO.,LTD +4C7B35 (base 16) UNIONMAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 CN -24-1E-2B (hex) Zhejiang Cainiao Supply Chain Management Co., Ltd -241E2B (base 16) Zhejiang Cainiao Supply Chain Management Co., Ltd - Block B1, XIXI center, No.588 West Wenyi Road, Xihu District - Hangzhou Zhejiang 310000 +9C-45-F0 (hex) SKYLARK ELECTRONICS PVT LTD +9C45F0 (base 16) SKYLARK ELECTRONICS PVT LTD + 3, SAKLAT PLACE 3RD FLOOR, HINDUSTAN BUILDING, KOLKATA + KOLKATA WEST BENGAL 700072 + IN + +40-92-49 (hex) Shanghai Baud Data Communication Co.,Ltd. +409249 (base 16) Shanghai Baud Data Communication Co.,Ltd. + NO.123 JULI RD + PUDONG ZHANGJIANG HIGH-TECH PARK SHANGHAI 201203 CN -AC-7F-8D (hex) Extreme Networks Headquarters -AC7F8D (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 +0C-7E-24 (hex) Garmin International +0C7E24 (base 16) Garmin International + 1200 E. 151st St + Olathe KS 66062 US -24-AC-AC (hex) Polar Electro Oy -24ACAC (base 16) Polar Electro Oy - Professorintie 5 - Kempele 90440 - FI - -50-20-6B (hex) Copeland - Transportation Solutions ApS -50206B (base 16) Copeland - Transportation Solutions ApS - Boeletvej 1 - Ry 8680 - DK +74-75-DF (hex) TECLINK +7475DF (base 16) TECLINK + 171 avenue des gresillons + gennevilliers 92230 + FR -80-D1-0A (hex) Sichuan AI-Link Technology Co., Ltd. -80D10A (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 +68-6B-6A (hex) Phytium Technology Co.,Ltd. +686B6A (base 16) Phytium Technology Co.,Ltd. + Building5,XinAn Business Square,Haiyuan Middle Road Binhai New District, + Tianjin 300450 CN -10-47-E7 (hex) Shenzhen YOUHUA Technology Co., Ltd -1047E7 (base 16) Shenzhen YOUHUA Technology Co., Ltd - Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District - Shenzhen Guangdong 518055 +34-07-AC (hex) PRONYX TRADING LLC +3407AC (base 16) PRONYX TRADING LLC + Office 2906, Citadel Tower, Marasi Drive, Business Bay + Dubai Dubai 16186 + AE + +58-B8-58 (hex) SZ DJI TECHNOLOGY CO.,LTD +58B858 (base 16) SZ DJI TECHNOLOGY CO.,LTD + DJI Sky City, No55 Xianyuan Road, Nanshan District + Shenzhen Guangdong 518057 CN -94-F6-F2 (hex) Honor Device Co., Ltd. -94F6F2 (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 +44-B4-23 (hex) HANWHA VISION VIETNAM COMPANY LIMITED +44B423 (base 16) HANWHA VISION VIETNAM COMPANY LIMITED + LOT O-2, QUE VO INDUSTRIAL ZONE EXTENDED AREA, NAM SON WARD + BAC NINH CITY BAC NINH PROVINCE 790000 + VN + +68-87-BD (hex) zte corporation +6887BD (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -44-90-46 (hex) Honor Device Co., Ltd. -449046 (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 +30-58-EB (hex) zte corporation +3058EB (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -B0-C3-8E (hex) Huawei Device Co., Ltd. -B0C38E (base 16) Huawei Device Co., Ltd. +44-05-B8 (hex) Huawei Device Co., Ltd. +4405B8 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -14-3B-51 (hex) Huawei Device Co., Ltd. -143B51 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +84-1A-24 (hex) UNIONMAN TECHNOLOGY CO.,LTD +841A24 (base 16) UNIONMAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 CN -D0-73-80 (hex) Huawei Device Co., Ltd. -D07380 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +78-6C-AB (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +786CAB (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -AC-FC-E3 (hex) EM Microelectronic -ACFCE3 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +00-7D-3B (hex) Samsung Electronics Co.,Ltd +007D3B (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR -04-2D-AD (hex) Areus GmbH -042DAD (base 16) Areus GmbH - Einsteinstrasse 13 - Herrenberg Baden-Wuerttemberg 71083 - DE +5C-40-E3 (hex) NOVAON +5C40E3 (base 16) NOVAON + 171 avenue des Gresillons + Gennevilliers 92230 + FR -78-1C-1E (hex) Chongqing Yipingfang Technology Co., Ltd. -781C1E (base 16) Chongqing Yipingfang Technology Co., Ltd. - No. 1-10, Tieshan Road, Biquan Street, Bishan District, Chongqing - ChongQing 402760 +98-62-97 (hex) Shenzhen Techwinsemi Technology Co., Ltd. +986297 (base 16) Shenzhen Techwinsemi Technology Co., Ltd. + Room 2301, 2401, 2501, Building 1, Shenzhen New Generation Industrial Park, No. 136 Zhongkang Road, Futian District + Shenzhen Guangdong 518000 CN -78-A1-3E (hex) New H3C Technologies Co., Ltd -78A13E (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN +E8-BC-E4 (hex) Cisco Systems, Inc +E8BCE4 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -48-CA-43 (hex) Espressif Inc. -48CA43 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +8C-87-26 (hex) VAST Data Inc +8C8726 (base 16) VAST Data Inc + 240 W 37th St + New York NY 10018 + US -7C-BA-C0 (hex) EVBox BV -7CBAC0 (base 16) EVBox BV - Kabelweg 47 - Amsterdam Noord holland 1014 BA - NL +68-62-8A (hex) vivo Mobile Communication Co., Ltd. +68628A (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN -98-25-6E (hex) Private -98256E (base 16) Private +28-44-F4 (hex) Honor Device Co., Ltd. +2844F4 (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 -B0-A7-D2 (hex) Fiberhome Telecommunication Technologies Co.,LTD -B0A7D2 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +BC-31-E2 (hex) New H3C Technologies Co., Ltd +BC31E2 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -8C-87-D0 (hex) Shenzhen Uascent Technology Co.,Ltd -8C87D0 (base 16) Shenzhen Uascent Technology Co.,Ltd - 701, block D, building 1, Lot 1, Chuangzhi Yuncheng building, Liuxian Avenue, Xili street, Nanshan District - Shenzhen 518000 +18-E8-EC (hex) STMicrolectronics International NV +18E8EC (base 16) STMicrolectronics International NV + 39, Chemin du Champ-des-Filles + Geneva, Plan-les-Quates 1228 + CH + +28-F5-2B (hex) FN-LINK TECHNOLOGY Ltd. +28F52B (base 16) FN-LINK TECHNOLOGY Ltd. + No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China + Changsha Hunan 410329 CN -5C-17-83 (hex) Edgecore Americas Networking Corporation -5C1783 (base 16) Edgecore Americas Networking Corporation - 20 Mason - Irvine CA 92618 +F8-EF-5D (hex) Motorola Mobility LLC, a Lenovo Company +F8EF5D (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -64-70-60 (hex) Texas Instruments -647060 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +A4-09-B3 (hex) HUAWEI TECHNOLOGIES CO.,LTD +A409B3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +3C-07-D7 (hex) Apple, Inc. +3C07D7 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-AA-FD (hex) Texas Instruments -00AAFD (base 16) Texas Instruments +A4-B0-F5 (hex) Texas Instruments +A4B0F5 (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -0C-4B-EE (hex) Texas Instruments -0C4BEE (base 16) Texas Instruments +2C-D3-AD (hex) Texas Instruments +2CD3AD (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -D4-13-B3 (hex) Wu Qi Technologies,Inc. -D413B3 (base 16) Wu Qi Technologies,Inc. - 14/F, 107 Middle Road, Xiantao Big Data Valley, Yubei District - Chongqing Chongqing 401120 - CN - -60-E5-D8 (hex) zte corporation -60E5D8 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -54-35-E9 (hex) Feitian Technologies Co., Ltd -5435E9 (base 16) Feitian Technologies Co., Ltd - Floor 17, Tower B, Huizhi Mansion, No.9 Xueqing Rd, Haidian District - Beijing 100085 - CN +54-FB-5A (hex) Optomind Inc. +54FB5A (base 16) Optomind Inc. + 16 Deogyeong-daero #1556, Suite B-301, Yeongtong-gu + Suwon Gyeonggi-do 16690 + KR -7C-B5-9F (hex) HUAWEI TECHNOLOGIES CO.,LTD -7CB59F (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +6C-D7-A0 (hex) WIKO Terminal Technology (Dongguan) Co., Ltd. +6CD7A0 (base 16) WIKO Terminal Technology (Dongguan) Co., Ltd. + Building 3, No. 6, Hongmian Road, Songshan Lake Park, Dongguan City, Guangdong Province + Dongguan Guangdong 523808 CN -50-5C-88 (hex) Cisco Systems, Inc -505C88 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -30-13-8B (hex) HP Inc. -30138B (base 16) HP Inc. - 10300 Energy Dr - Spring TX 77389 - US - -60-68-32 (hex) Guangdong Seneasy Intelligent Technology Co., Ltd. -606832 (base 16) Guangdong Seneasy Intelligent Technology Co., Ltd. - No. 63, Huitai Industrial Park, - Huizhou City, Guangdong Province 516000 +F8-E3-5F (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +F8E35F (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 CN -84-33-F2 (hex) Shenzhen Stellamore Technology Co.,Ltd -8433F2 (base 16) Shenzhen Stellamore Technology Co.,Ltd - 8th Floor, R&D Complex BuildingBaoyunda Logistics CenterFuhua Community, Xixiang Street, Bao'an - Shenzhen Guangdong 518100 +E8-E7-C3 (hex) zte corporation +E8E7C3 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -28-D0-43 (hex) AzureWave Technology Inc. -28D043 (base 16) AzureWave Technology Inc. - 8F., No. 94, Baozhong Rd. - New Taipei City Taiwan 231 +24-B2-B9 (hex) Liteon Technology Corporation +24B2B9 (base 16) Liteon Technology Corporation + 4F, 90, Chien 1 Road + New Taipei City Taiwan 23585 TW -E4-D3-AA (hex) FCNT LLC -E4D3AA (base 16) FCNT LLC - Sanki Yamato Building, 7-10-1 Chuorinkan - Yamato Kanagawa 242-8588 - JP - -B0-D5-76 (hex) Apple, Inc. -B0D576 (base 16) Apple, Inc. +1C-1D-D3 (hex) Apple, Inc. +1C1DD3 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -14-28-76 (hex) Apple, Inc. -142876 (base 16) Apple, Inc. +C8-1F-E8 (hex) Apple, Inc. +C81FE8 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -14-7F-CE (hex) Apple, Inc. -147FCE (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +08-B9-5F (hex) Silicon Laboratories +08B95F (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -6C-C3-B2 (hex) Cisco Meraki -6CC3B2 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 +74-78-47 (hex) Interdisciplinary Consulting Corporation +747847 (base 16) Interdisciplinary Consulting Corporation + 2405 NW 66th Ct + Gainesville FL 32653-1633 US -F8-CE-07 (hex) ZHEJIANG DAHUA TECHNOLOGYCO.,LTD -F8CE07 (base 16) ZHEJIANG DAHUA TECHNOLOGYCO.,LTD - 1st Floor, Building 1, No. 1399, Binxing Road, Changhe Street, Binjiang District - Hangzhou City Zhejiang Province 311200 +F0-D5-06 (hex) Ubee Interactive Co., Limited +F0D506 (base 16) Ubee Interactive Co., Limited + UNIT 2703A, 27/F., 148 ELECTRIC ROAD, NORTH POINT, HONG KONG + NORTH POINT 00000 + HK + +7C-E5-3F (hex) HUAWEI TECHNOLOGIES CO.,LTD +7CE53F (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -20-98-ED (hex) AltoBeam Inc. -2098ED (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 +B4-23-A2 (hex) Google, Inc. +B423A2 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +38-D0-9C (hex) HUAWEI TECHNOLOGIES CO.,LTD +38D09C (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -98-39-10 (hex) Kaon Group Co., Ltd. -983910 (base 16) Kaon Group Co., Ltd. - 884-3, Seongnam-daero, Bundang-gu - Seongnam-si Gyeonggi-do 13517 - KR +CC-22-DF (hex) EM Microelectronic +CC22DF (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH -80-4C-5D (hex) NXP Semiconductor (Tianjin) LTD. -804C5D (base 16) NXP Semiconductor (Tianjin) LTD. - No.15 Xinghua Avenue, Xiqing Economic Development Area - Tianjin 300385 +8C-2A-85 (hex) Amazon Technologies Inc. +8C2A85 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +E8-C6-E6 (hex) CHANGHONG (HONGKONG) TRADING LIMITED +E8C6E6 (base 16) CHANGHONG (HONGKONG) TRADING LIMITED + Unit 1412, 14/F., West Tower, Shun Tak Centre, 168-200 Connaught Road Central, HongKong + HONG KONG HONG KONG 999077 + HK + +D4-A3-EB (hex) Shenzhen iComm Semiconductor CO.,LTD +D4A3EB (base 16) Shenzhen iComm Semiconductor CO.,LTD + Room204,scientific research building,Tsinghua Hi-Tech Park,No.13 Langshan Road,Nanshan District + Shenzhen Guangdong 518067 CN -F8-8F-C8 (hex) Chipsea Technologies (Shenzhen) Corp. -F88FC8 (base 16) Chipsea Technologies (Shenzhen) Corp. - 3 / F, Block A, Building 2, Shenzhen Bay Innovation Technology Center, No.3156 keyuan South Road, Yuehai Street, Nanshan District, Shenzhen - Shenzhen Guangdong 518000 +9C-84-B6 (hex) Shenzhen iComm Semiconductor CO.,LTD +9C84B6 (base 16) Shenzhen iComm Semiconductor CO.,LTD + Room204,scientific research building,Tsinghua Hi-Tech Park,No.13 Langshan Road,Nanshan District + Shenzhen Guangdong 518067 CN -4C-10-D5 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -4C10D5 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 +20-67-E0 (hex) Shenzhen iComm Semiconductor CO.,LTD +2067E0 (base 16) Shenzhen iComm Semiconductor CO.,LTD + Room204,scientific research building,Tsinghua Hi-Tech Park,No.13 Langshan Road,Nanshan District + Shenzhen Guangdong 518067 CN -28-91-76 (hex) Indyme Solutions, LLC -289176 (base 16) Indyme Solutions, LLC - 8295 Aero Place Ste 260 - San Diego CA 92123 +E4-D5-8B (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +E4D58B (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN + +0C-97-9B (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD +0C979B (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD + 19-22# Building, Star-net Science Plaza, Juyuanzhou, + FUZHOU FUJIAN 350002 + CN + +50-04-01 (hex) TelHi Corporation +500401 (base 16) TelHi Corporation + 1-32-2, Kosuge + Katsushika-ku Tokyo 1240001 + JP + +08-F4-F0 (hex) Cisco Systems, Inc +08F4F0 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -28-A0-6B (hex) Intel Corporate -28A06B (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +28-DB-02 (hex) zte corporation +28DB02 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -7C-8B-C1 (hex) Infinix mobility limited -7C8BC1 (base 16) Infinix mobility limited - RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG - HongKong HongKong 999077 - HK +88-15-66 (hex) Huawei Device Co., Ltd. +881566 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -3C-CA-61 (hex) TECNO MOBILE LIMITED -3CCA61 (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 - Hong Kong Hong Kong 999077 - HK +28-45-AC (hex) Huawei Device Co., Ltd. +2845AC (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -EC-A9-71 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -ECA971 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 +A4-43-80 (hex) Huawei Device Co., Ltd. +A44380 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -68-A2-AA (hex) Acres Manufacturing -68A2AA (base 16) Acres Manufacturing - 6415 S Tenaya Way - Las Vegas NV 89113 - US +10-86-F4 (hex) Huawei Device Co., Ltd. +1086F4 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -88-AE-35 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -88AE35 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 +D4-50-EE (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +D450EE (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN -70-49-A2 (hex) Zyxel Communications Corporation -7049A2 (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW +24-21-5E (hex) Quectel Wireless Solutions Co.,Ltd. +24215E (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN -A0-7D-9C (hex) Samsung Electronics Co.,Ltd -A07D9C (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 +E8-05-DC (hex) Verifone, Inc. +E805DC (base 16) Verifone, Inc. + 2560 North First Street, Suite 220 + San Jose CA 95131 + US + +A0-FB-68 (hex) Miba Battery Systems GmbH +A0FB68 (base 16) Miba Battery Systems GmbH + Maximilianstrasse 4 + Bad Leonfelden 4190 + AT + +28-6B-B4 (hex) SJIT Co., Ltd. +286BB4 (base 16) SJIT Co., Ltd. + 54-33 Dongtanhana 1-gil + Hwaseong-si Gyeonggi-do 18423 KR -F8-C3-F1 (hex) Raytron Photonics Co.,Ltd. -F8C3F1 (base 16) Raytron Photonics Co.,Ltd. - 3rd Floor, Building A, No. 66 Huaxiu Road, Xinwu District - Wuxi 214000 +98-C9-BE (hex) Shenzhen SDMC Technology CO., LTD +98C9BE (base 16) Shenzhen SDMC Technology CO., LTD + Xin'an 3rd Road, Dalang Community, Xin'an Street + Shenzhen Guangdong 518000 CN -D8-95-63 (hex) Taiwan Digital Streaming Co. -D89563 (base 16) Taiwan Digital Streaming Co. - No. 50-15, Ln. 60, Jianping 9th St., Anping Dist. - Tainan City 70848 - TW - -E8-A8-48 (hex) Wacom Co.,Ltd. -E8A848 (base 16) Wacom Co.,Ltd. - Sumitomo Fudosan Shinjuku Grand Tower 31F,8-17-1 - Nishi-shinjuku,Shinjuku-ku Tokyo 160-6131 - JP +6C-83-75 (hex) Broadcom Limited +6C8375 (base 16) Broadcom Limited + 15191 Alton Parkway + Irvine CA 92618 + US -5C-A3-EB (hex) SKODA DIGITAL s.r.o. -5CA3EB (base 16) SKODA DIGITAL s.r.o. - Na Rovince 874 - Ostrava Hrabova 720 00 - CZ +D4-BE-DC (hex) Roku, Inc +D4BEDC (base 16) Roku, Inc + 1173 Coleman Ave + San Jose CA 95110 + US -5C-BE-05 (hex) ISPEC -5CBE05 (base 16) ISPEC - 8 Palyong-ro, Uichang-gu, Changwon-si - Gyeongsangnam-do 51395 +00-CD-90 (hex) MAS Elektronik AG +00CD90 (base 16) MAS Elektronik AG + Carl-Zeiss-Straße 31 + Buxtehude 21614 + DE + +30-ED-96 (hex) LS Mecapion +30ED96 (base 16) LS Mecapion + 12-9, Hosandong-ro, Dalseo-gu, Deagu, Korea 42714 + Deagu Select State KS002 KR -08-52-4E (hex) Shenzhen Fangcheng Baiyi Technology Co., Ltd. -08524E (base 16) Shenzhen Fangcheng Baiyi Technology Co., Ltd. - 1401, Block B, Rongchao Binhai Building, No. 2021, Haixiu Road, Area N26, Haibin Community, Xin'an Street, Xixiang - Shenzhen Guangdong 518101 +FC-06-8C (hex) SHENZHEN MICIPC TECHNOLOGY CO.,LTD +FC068C (base 16) SHENZHEN MICIPC TECHNOLOGY CO.,LTD + 508,building 8,Hengda fashion Huigu,Fulong Road,Dalang street,Longhua District,Shenzhen + Shenzhen 518000 CN -AC-F4-2C (hex) Earda Technologies co Ltd -ACF42C (base 16) Earda Technologies co Ltd - Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District - Guangzhou Guangdong 511455 +DC-86-8D (hex) HUAWEI TECHNOLOGIES CO.,LTD +DC868D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -C0-AB-2B (hex) Huawei Device Co., Ltd. -C0AB2B (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +E8-F4-94 (hex) AltoBeam Inc. +E8F494 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -50-8A-7F (hex) HUAWEI TECHNOLOGIES CO.,LTD -508A7F (base 16) HUAWEI TECHNOLOGIES CO.,LTD +48-CF-A9 (hex) HUAWEI TECHNOLOGIES CO.,LTD +48CFA9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -C4-CB-BE (hex) Great Talent Technology Limited -C4CBBE (base 16) Great Talent Technology Limited - 35F, HBC HuiLong Center Building-II Minzhi Street, Longhua - Shenzhen Guangdong 518110 +D0-87-B5 (hex) SAFEMO PTE. LTD. +D087B5 (base 16) SAFEMO PTE. LTD. + 61 BUKIT BATOK CRESCENT #05-505 HENG LOONG BUILDING + Singapore 658078 + SG + +74-25-84 (hex) IEEE Registration Authority +742584 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +40-48-6E (hex) Nokia Solutions and Networks GmbH & Co. KG +40486E (base 16) Nokia Solutions and Networks GmbH & Co. KG + Werinherstrasse 91 + München Bavaria D-81541 + DE + +D8-B0-61 (hex) SHENZHEN WENXUN TECHNOLOGY CO.,LTD +D8B061 (base 16) SHENZHEN WENXUN TECHNOLOGY CO.,LTD + JUNLINTIANXIA A-3705, XINSHA ROAD, FUTIAN DISTRICT + SHENZHEN 518048 CN -E8-B5-27 (hex) Phyplus Technology (Shanghai) Co., Ltd -E8B527 (base 16) Phyplus Technology (Shanghai) Co., Ltd - 3th Floor, Building 23, 676 Wuxing Road, Pudong New District, Shanghai - Shanghai Shanghai 201204 +08-DD-03 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +08DD03 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -98-A8-78 (hex) Agnigate Technologies Private Limited -98A878 (base 16) Agnigate Technologies Private Limited - 75-ROHIT NAGAR, PHASE-2, E-8 EXTENSION, BAWADIYA KALAN, BHOPAL - BHOPAL Madhya Pradesh 462026 - IN +E4-4E-12 (hex) zte corporation +E44E12 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -30-52-23 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -305223 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 +CC-B8-5E (hex) Shenzhen Phaten Tech. LTD +CCB85E (base 16) Shenzhen Phaten Tech. LTD + C-6 ideamonto industril 7002 Songbai Road Guangming District Shenzhen City Guangdong, China + Shenzhen 518108 CN -DC-49-65 (hex) DASAN Newtork Solutions -DC4965 (base 16) DASAN Newtork Solutions - DASAN Tower, 49, Daewangpangyo-ro644Beon-gil, Bundang-gu, Seongnam-si, Gyeonggi-do, 13493 KOREA - Seongnam-si Gyeonggi-do 13493 - KR +C8-A7-02 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +C8A702 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN -18-9E-2D (hex) Allwinner Technology Co., Ltd -189E2D (base 16) Allwinner Technology Co., Ltd - No.9 Technology Road 2, High-Tech Zone - Zhuhai Guangdong 519085 +2C-FE-8B (hex) Microchip Technologies Inc +2CFE8B (base 16) Microchip Technologies Inc + 2355 W Chandler Blvd + Chandler AZ 85224-6199 + US + +68-EF-AB (hex) Vention +68EFAB (base 16) Vention + 4767 Dagenais, Suite 201B + Montreal Quebec H4C 1L8 + CA + +B0-92-00 (hex) Apple, Inc. +B09200 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +A0-EE-1A (hex) Apple, Inc. +A0EE1A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +78-1E-B8 (hex) Shenzhen iComm Semiconductor CO.,LTD +781EB8 (base 16) Shenzhen iComm Semiconductor CO.,LTD + Room204,scientific research building,Tsinghua Hi-Tech Park,No.13 Langshan Road,Nanshan District + Shenzhen Guangdong 518067 CN -9C-60-76 (hex) Apple, Inc. -9C6076 (base 16) Apple, Inc. +58-41-46 (hex) Guangzhou Shiyuan Electronic Technology Company Limited +584146 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 + CN + +9C-F3-AC (hex) Apple, Inc. +9CF3AC (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -38-E1-3D (hex) Apple, Inc. -38E13D (base 16) Apple, Inc. +08-5D-53 (hex) Apple, Inc. +085D53 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -D0-D4-9F (hex) Apple, Inc. -D0D49F (base 16) Apple, Inc. +54-29-06 (hex) Apple, Inc. +542906 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -9C-2D-49 (hex) Nanowell Info Tech Co., Limited -9C2D49 (base 16) Nanowell Info Tech Co., Limited - 1205 Building 2, Xunmei Science Technology Plaza, No 8 Keyuan Road, Nanshan District - Shenzhen Guangdong 518057 +70-15-FB (hex) Intel Corporate +7015FB (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +B0-47-5E (hex) IEEE Registration Authority +B0475E (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +EC-8E-12 (hex) Nokia +EC8E12 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +C0-84-FF (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +C084FF (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN -88-7F-D5 (hex) zte corporation -887FD5 (base 16) zte corporation +10-00-3B (hex) Espressif Inc. +10003B (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +F8-73-1A (hex) zte corporation +F8731A (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China shenzhen guangdong 518057 CN -F4-74-70 (hex) Cisco Systems, Inc -F47470 (base 16) Cisco Systems, Inc +CC-65-AD (hex) Commscope +CC65AD (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US + +78-96-84 (hex) Commscope +789684 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US + +E8-ED-05 (hex) Commscope +E8ED05 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US + +D4-04-CD (hex) Commscope +D404CD (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US + +20-3D-66 (hex) Commscope +203D66 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US + +D4-0A-A9 (hex) Commscope +D40AA9 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US + +28-A4-4A (hex) Intel Corporate +28A44A (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +10-96-C6 (hex) Cisco Systems, Inc +1096C6 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -60-A4-34 (hex) IEEE Registration Authority -60A434 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +8C-09-F4 (hex) Commscope +8C09F4 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -38-7B-01 (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd -387B01 (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd - 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District - Shenzhen Guangdong 518110 - CN +90-0D-CB (hex) Commscope +900DCB (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -0C-ED-C8 (hex) Xiaomi Communications Co Ltd -0CEDC8 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +00-13-71 (hex) Commscope +001371 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -88-BC-AC (hex) Zebra Technologies Inc. -88BCAC (base 16) Zebra Technologies Inc. - ONE ZEBRA PLAZA - HOLTSVILLE NY 11742 +00-1E-5A (hex) Commscope +001E5A (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -C8-8D-D4 (hex) Markone technology Co., Ltd. -C88DD4 (base 16) Markone technology Co., Ltd. - A-2401,2402,2403,2404, 606, Seobusaet-gil, Geumcheon-gu - Seoul 08504 - KR +00-1C-C1 (hex) Commscope +001CC1 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -2C-36-F2 (hex) HUAWEI TECHNOLOGIES CO.,LTD -2C36F2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +00-1D-D2 (hex) Commscope +001DD2 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -E4-99-5F (hex) HUAWEI TECHNOLOGIES CO.,LTD -E4995F (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +00-1D-CD (hex) Commscope +001DCD (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -A8-F0-59 (hex) HUAWEI TECHNOLOGIES CO.,LTD -A8F059 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +00-1A-DE (hex) Commscope +001ADE (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -28-3D-E8 (hex) Guangzhou Shiyuan Electronic Technology Company Limited -283DE8 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 - CN +00-23-EE (hex) Commscope +0023EE (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -18-04-03 (hex) vivo Mobile Communication Co., Ltd. -180403 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN +E8-3E-FC (hex) Commscope +E83EFC (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -20-6B-D5 (hex) vivo Mobile Communication Co., Ltd. -206BD5 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN +7C-BF-B1 (hex) Commscope +7CBFB1 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -B4-7A-F1 (hex) Hewlett Packard Enterprise -B47AF1 (base 16) Hewlett Packard Enterprise - 8000 Foothills Blvd. - Roseville CA 95747 +00-50-E3 (hex) Commscope +0050E3 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -48-9E-CB (hex) Hewlett Packard Enterprise -489ECB (base 16) Hewlett Packard Enterprise - 8000 Foothills Blvd. - Roseville 95747 +00-16-26 (hex) Commscope +001626 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -9C-1C-12 (hex) Hewlett Packard Enterprise -9C1C12 (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +00-13-11 (hex) Commscope +001311 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -04-BD-88 (hex) Hewlett Packard Enterprise -04BD88 (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +00-19-A6 (hex) Commscope +0019A6 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -E8-F7-24 (hex) Hewlett Packard Enterprise -E8F724 (base 16) Hewlett Packard Enterprise - 8000 Foothills Blvd. - Roseville CA 95747 +80-F5-03 (hex) Commscope +80F503 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -DC-68-0C (hex) Hewlett Packard Enterprise -DC680C (base 16) Hewlett Packard Enterprise - 8000 Foothills Blvd. - Roseville CA 95747 +A0-55-DE (hex) Commscope +A055DE (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -48-DF-37 (hex) Hewlett Packard Enterprise -48DF37 (base 16) Hewlett Packard Enterprise - 8000 Foothills Blvd. - Roseville CA 95747 +D4-2C-0F (hex) Commscope +D42C0F (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -20-67-7C (hex) Hewlett Packard Enterprise -20677C (base 16) Hewlett Packard Enterprise - 8000 Foothills Blvd. - Roseville CA 95747 +FC-6F-B7 (hex) Commscope +FC6FB7 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -44-48-C1 (hex) Hewlett Packard Enterprise -4448C1 (base 16) Hewlett Packard Enterprise - 8000 Foothills Blvd. - Roseville CA 95747 +FC-8E-7E (hex) Commscope +FC8E7E (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -68-28-CF (hex) Hewlett Packard Enterprise -6828CF (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +A4-15-88 (hex) Commscope +A41588 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -E4-DE-40 (hex) Hewlett Packard Enterprise -E4DE40 (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +04-4E-5A (hex) Commscope +044E5A (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -48-00-20 (hex) Hewlett Packard Enterprise -480020 (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +38-70-0C (hex) Commscope +38700C (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -5C-A4-7D (hex) Hewlett Packard Enterprise -5CA47D (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +FC-51-A4 (hex) Commscope +FC51A4 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -E8-95-05 (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd -E89505 (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd - 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District - Shenzhen Guangdong 518110 - CN +2C-9E-5F (hex) Commscope +2C9E5F (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -DC-B7-AC (hex) Hewlett Packard Enterprise -DCB7AC (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +40-FC-89 (hex) Commscope +40FC89 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -54-D7-E3 (hex) Hewlett Packard Enterprise -54D7E3 (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +E4-64-49 (hex) Commscope +E46449 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -14-AB-EC (hex) Hewlett Packard Enterprise -14ABEC (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +74-56-12 (hex) Commscope +745612 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -10-4F-58 (hex) Hewlett Packard Enterprise -104F58 (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +28-7A-EE (hex) Commscope +287AEE (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -CC-D0-83 (hex) Hewlett Packard Enterprise -CCD083 (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +BC-64-4B (hex) Commscope +BC644B (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -BC-46-32 (hex) Fiberhome Telecommunication Technologies Co.,LTD -BC4632 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN +44-AA-F5 (hex) Commscope +44AAF5 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -68-53-77 (hex) Bouffalo Lab (Nanjing) Co., Ltd. -685377 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. - 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China - Nanjing Jiangsu 211800 - CN +00-36-76 (hex) Commscope +003676 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -F0-5C-19 (hex) Hewlett Packard Enterprise -F05C19 (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +78-23-AE (hex) Commscope +7823AE (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -7C-57-3C (hex) Hewlett Packard Enterprise -7C573C (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +10-56-11 (hex) Commscope +105611 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -34-8A-12 (hex) Hewlett Packard Enterprise -348A12 (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +18-B8-1F (hex) Commscope +18B81F (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -10-63-A3 (hex) IEEE Registration Authority -1063A3 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +98-4B-4A (hex) Commscope +984B4A (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -E8-06-90 (hex) Espressif Inc. -E80690 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +E8-82-5B (hex) Commscope +E8825B (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -DC-A7-06 (hex) CHENGDU KT ELECTRONIC HI-TECH CO.,LTD -DCA706 (base 16) CHENGDU KT ELECTRONIC HI-TECH CO.,LTD - No.9, 3rd Wuke Road, Wuhou District - Chengdu Sichuan Province 610045 - CN +00-21-36 (hex) Commscope +002136 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -E0-60-4A (hex) Fiberhome Telecommunication Technologies Co.,LTD -E0604A (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN +00-26-36 (hex) Commscope +002636 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -50-06-F5 (hex) Roku, Inc -5006F5 (base 16) Roku, Inc - 1173 Coleman Ave - San Jose CA 95110 +50-95-51 (hex) Commscope +509551 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -98-16-CD (hex) leapio -9816CD (base 16) leapio - beijing,haidian - beijing beijing 100000 - CN +24-0A-63 (hex) Commscope +240A63 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -34-51-6F (hex) Skychers Creations ShenZhen Limited -34516F (base 16) Skychers Creations ShenZhen Limited - Room 907A, 9/F, Block T2, FongDa City, Longjing Village, Longzhu Avenue, Nanshan District - Shenzhen Guangdong 518073 - CN +F8-8B-37 (hex) Commscope +F88B37 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -C8-6E-08 (hex) Intel Corporate -C86E08 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +88-96-4E (hex) Commscope +88964E (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -B8-37-4B (hex) Hewlett Packard Enterprise -B8374B (base 16) Hewlett Packard Enterprise - 1701 E Mossy Oaks Rd - Spring TX 77389 +CC-75-E2 (hex) Commscope +CC75E2 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -18-86-37 (hex) INGRAM MICRO SERVICES -188637 (base 16) INGRAM MICRO SERVICES - 100 CHEMIN DE BAILLOT - MONTAUBAN 82000 - FR +14-D4-FE (hex) Commscope +14D4FE (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -78-07-8F (hex) HUAWEI TECHNOLOGIES CO.,LTD -78078F (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +E4-9F-1E (hex) Commscope +E49F1E (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -30-9E-62 (hex) HUAWEI TECHNOLOGIES CO.,LTD -309E62 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +88-C4-8E (hex) UNEEVIU TECHNOLOGIES INDIA PRIVATE LIMITED +88C48E (base 16) UNEEVIU TECHNOLOGIES INDIA PRIVATE LIMITED + Sadguru Villa PL 123/128, Gorai Rd, Borivali West + Mumbai Maharashtra 400091 + IN -10-C0-D5 (hex) HOLOEYE Photonics AG -10C0D5 (base 16) HOLOEYE Photonics AG - Volmerstr. 1 - Berlin 12489 - DE +40-2B-50 (hex) Commscope +402B50 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US -54-FA-89 (hex) Medtronic CRM -54FA89 (base 16) Medtronic CRM - 8200 Coral Sea Street NEMVN22 - Mounds View MN 55112 +70-54-25 (hex) Commscope +705425 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -24-16-51 (hex) Chipsea Technologies (Shenzhen) Corp. -241651 (base 16) Chipsea Technologies (Shenzhen) Corp. - 3 / F, Block A, Building 2, Shenzhen Bay Innovation Technology Center, No.3156 keyuan South Road, Yuehai Street, Nanshan District, Shenzhen - Shenzhen Guangdong 518000 +EC-A9-40 (hex) Commscope +ECA940 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US + +E4-F7-5B (hex) Commscope +E4F75B (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US + +F8-79-0A (hex) Commscope +F8790A (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US + +1C-93-7C (hex) Commscope +1C937C (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US + +DC-2E-97 (hex) Quectel Wireless Solutions Co.,Ltd. +DC2E97 (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN -58-76-9C (hex) Palo Alto Networks -58769C (base 16) Palo Alto Networks - 3000 Tannery Way - Santa Clara CA 95054 +80-E5-40 (hex) Commscope +80E540 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 US -B4-C5-56 (hex) Shanghai Kenmyond Industrial Network Equipment Co., Ltd -B4C556 (base 16) Shanghai Kenmyond Industrial Network Equipment Co., Ltd - 5th floor,Building3,NO69,yuanfeng Road,Baoshan - Shanghai Shanghai 021-56561181 +C0-94-35 (hex) Commscope +C09435 (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US + +74-8A-0D (hex) Commscope +748A0D (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US + +8C-76-3F (hex) Commscope +8C763F (base 16) Commscope + 6450 Sequence Drive + San Diego CA 92121 + US + +BC-45-48 (hex) Beijing gpthink technology co.,LTD. +BC4548 (base 16) Beijing gpthink technology co.,LTD. + Building 6-4, Jinke Lane, Industrial Zone, Daxing District, Beijing + Beijing 102627 CN -EC-72-5B (hex) zte corporation -EC725B (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +B8-52-E0 (hex) Beijing Xiaomi Electronics Co.,Ltd +B852E0 (base 16) Beijing Xiaomi Electronics Co.,Ltd + Xiaomi Campus + Beijing Beijing 100085 CN -E4-92-82 (hex) Samsung Electronics Co.,Ltd -E49282 (base 16) Samsung Electronics Co.,Ltd +28-9F-04 (hex) Samsung Electronics Co.,Ltd +289F04 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -00-09-F7 (hex) Calian Advanced Technologies -0009F7 (base 16) Calian Advanced Technologies - 18 Innnovation Boulevard - Saskatoon SK S7N 3R1 - CA +E0-85-4D (hex) LG Innotek +E0854D (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR -04-94-E9 (hex) FAXedge Technologies, LLC -0494E9 (base 16) FAXedge Technologies, LLC - 400 Liberty Park Court - Flowood MS 39232 +AC-EF-92 (hex) IEEE Registration Authority +ACEF92 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -38-7A-CC (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD -387ACC (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD - NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district - shenzhen guangdong 518000 +40-45-A0 (hex) vivo Mobile Communication Co., Ltd. +4045A0 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN -7C-72-E7 (hex) Texas Instruments -7C72E7 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +0C-E6-7C (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +0CE67C (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 + CN -30-30-D0 (hex) Texas Instruments -3030D0 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +58-FC-20 (hex) Altice Labs +58FC20 (base 16) Altice Labs + NIF 504705610, Rua Eng. José Ferreira Pinto Basto + Aveiro 3810-106 + PT -54-FE-EB (hex) Texas Instruments -54FEEB (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +1C-57-3E (hex) Altice Labs +1C573E (base 16) Altice Labs + NIF 504705610, Rua Eng. José Ferreira Pinto Basto + Aveiro 3810-106 + PT -08-84-FB (hex) Honor Device Co., Ltd. -0884FB (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 +78-12-3E (hex) TECNO MOBILE LIMITED +78123E (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 + Hong Kong Hong Kong 999077 + HK -14-BE-FC (hex) Nanjing Jiahao Technology Co., Ltd. -14BEFC (base 16) Nanjing Jiahao Technology Co., Ltd. - Moling Industrial Park, Development Zone, Jiangning, Nanjing - Nanjing Jiangsu 211111 +14-B5-CD (hex) Liteon Technology Corporation +14B5CD (base 16) Liteon Technology Corporation + 4F, 90, Chien 1 Road + New Taipei City Taiwan 23585 + TW + +E0-3E-CB (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. +E03ECB (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. + No.218 Qianwangang Road + Qingdao Shangdong 266510 CN -90-5F-7A (hex) Apple, Inc. -905F7A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +3C-A9-AB (hex) Nintendo Co.,Ltd +3CA9AB (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP -F8-F5-8C (hex) Apple, Inc. -F8F58C (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +6C-A3-1E (hex) ITEL MOBILE LIMITED +6CA31E (base 16) ITEL MOBILE LIMITED + RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K + Hong Kong KOWLOON 999077 + HK -0C-85-E1 (hex) Apple, Inc. -0C85E1 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +F8-F3-D3 (hex) Shenzhen Gotron electronic CO.,LTD +F8F3D3 (base 16) Shenzhen Gotron electronic CO.,LTD + Floor 7, Building A, Block 1, Anhongji Tianyao Plaza, Longhua District, Shenzhen + Shenzhen 518000 + CN + +0C-47-A9 (hex) IEEE Registration Authority +0C47A9 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -90-FB-5D (hex) Beijing Xiaomi Mobile Software Co., Ltd -90FB5D (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +C8-FB-54 (hex) iMin Technology Pte. Ltd. +C8FB54 (base 16) iMin Technology Pte. Ltd. + 11 Bishan Street 21 #03-05 Singapore 573943 + Singapore 573943 + SG + +54-84-50 (hex) Tiinlab Corporation +548450 (base 16) Tiinlab Corporation + Building A Room 201 Cooperation District between Shenzhen and HongKong,Qianwan Road No.1,Shenzhen City, Business Address:No. 3333, Liuxian AvenueTower A, 35th Floor,Tanglang City, Nanshan District, Shenzhen, China + Shenzhen Guangdong 518000 CN -44-16-90 (hex) Wuxi Ranke Technology Co., Ltd. -441690 (base 16) Wuxi Ranke Technology Co., Ltd. - 110-268, 1st Floor, Building A10, No,777 Jianzhu West Road, Livuan Street Binhy District - Wuxi Jiangsu 214062 +68-95-75 (hex) Zhejiang Bodyguard Electronic Co., Ltd +689575 (base 16) Zhejiang Bodyguard Electronic Co., Ltd + 598 Xinwen Road, Xinqiao Town, Luqiao + Taizhou Zhejiang 318050 CN -9C-A6-D8 (hex) Fiberhome Telecommunication Technologies Co.,LTD -9CA6D8 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +20-53-8D (hex) Hon Hai Precision Industry Co., Ltd. +20538D (base 16) Hon Hai Precision Industry Co., Ltd. + GuangDongShenZhen + ShenZhen GuangDong 518109 CN -0C-2A-6F (hex) Silicon Laboratories -0C2A6F (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US +9C-E9-1E (hex) TEJAS NETWORKS LTD +9CE91E (base 16) TEJAS NETWORKS LTD + Plot 25 JP Software Park Electronics City Phase-1 + Bangalore Karnataka 560100 + IN -20-DB-EA (hex) Cisco Systems, Inc -20DBEA (base 16) Cisco Systems, Inc +58-DF-59 (hex) Cisco Systems, Inc +58DF59 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -A0-47-9B (hex) PROCITEC GmbH -A0479B (base 16) PROCITEC GmbH - Rastatter Str. 41 - Pforzheim 75179 - DE +04-47-CA (hex) GREE ELECTRIC APPLIANCES, INC. OF ZHUHAI +0447CA (base 16) GREE ELECTRIC APPLIANCES, INC. OF ZHUHAI + West Jinji Rd, Qianshan + Zhuhai Guangdong 519070 + CN -74-C9-0F (hex) Microchip Technologies Inc -74C90F (base 16) Microchip Technologies Inc - 2355 W Chandler Blvd - Chandler AZ 85224-6199 +C0-F8-53 (hex) Tuya Smart Inc. +C0F853 (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 US -34-CD-B0 (hex) Espressif Inc. -34CDB0 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +00-BC-2F (hex) Actiontec Electronics Inc. +00BC2F (base 16) Actiontec Electronics Inc. + 2445 Augustine Dr #501 + Santa Clara CA 95054 + US -18-E2-04 (hex) BEIJING COOLSHARK TECHNOLOGY CO.,LTD. -18E204 (base 16) BEIJING COOLSHARK TECHNOLOGY CO.,LTD. - B503,Zhongguancun Science and Technology Service Building, No.2 Guanzhuang Road - Beijing Chaoyang District 100101 - CN +E4-FD-8C (hex) Extreme Networks Headquarters +E4FD8C (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US -2C-B7-A1 (hex) Huawei Device Co., Ltd. -2CB7A1 (base 16) Huawei Device Co., Ltd. +30-EB-15 (hex) Huawei Device Co., Ltd. +30EB15 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -6C-82-43 (hex) Huawei Device Co., Ltd. -6C8243 (base 16) Huawei Device Co., Ltd. +68-9B-43 (hex) Huawei Device Co., Ltd. +689B43 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -B4-76-A4 (hex) Huawei Device Co., Ltd. -B476A4 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +1C-4E-A2 (hex) Shenzhen V-Link Technology CO., LTD. +1C4EA2 (base 16) Shenzhen V-Link Technology CO., LTD. + Room 1803, BaiRuiDa Building, Bantian Sub-district, LongGang District + Shenzhen GuangDong 518000 CN -50-26-D2 (hex) AVIRE Trading Limited -5026D2 (base 16) AVIRE Trading Limited - Unit 1 The Switchback, Gardner Road - Maidenhead Berkshire SL6 7RJ - GB +E0-22-A1 (hex) AltoBeam Inc. +E022A1 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN -94-24-53 (hex) HUAWEI TECHNOLOGIES CO.,LTD -942453 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +04-B0-66 (hex) Private +04B066 (base 16) Private + +4C-FA-9A (hex) Shenzhen Quanxing Technology Co., Ltd +4CFA9A (base 16) Shenzhen Quanxing Technology Co., Ltd + 深圳市福田区沙头街道万科滨海置地大厦八层铨兴科技 + 深圳市 广东 518000 CN -18-4F-43 (hex) UNIONMAN TECHNOLOGY CO.,LTD -184F43 (base 16) UNIONMAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 +28-49-92 (hex) Luminator Technology Group Global LLC +284992 (base 16) Luminator Technology Group Global LLC + 900 Klein Road + Plano TX 75074 + US + +A8-EA-E4 (hex) Weiser +A8EAE4 (base 16) Weiser + 110 Sargent Dr. + New Haven CT 06511 + US + +94-38-AA (hex) Technology Innovation Institute +9438AA (base 16) Technology Innovation Institute + Masdar City, Abu Dhabi + Abu Dhabi 9639 + AE + +60-A3-E3 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +60A3E3 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan + Shenzhen Guangdong 518057 + CN + +64-63-06 (hex) Xiaomi Communications Co Ltd +646306 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -F8-6B-FA (hex) Infinix mobility limited -F86BFA (base 16) Infinix mobility limited - RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG - HongKong HongKong 999077 - HK +6C-9A-B4 (hex) Brodersen A/S +6C9AB4 (base 16) Brodersen A/S + Islevdalvej 187 + Rødovre Hovedstaden 2610 + DK + +E4-5D-39 (hex) Texas Instruments +E45D39 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +04-90-C0 (hex) Forvia +0490C0 (base 16) Forvia + Rixbecker Straße 75 + Lippstadt 59552 + DE -B0-32-26 (hex) Keheng Information Industry Co., Ltd. -B03226 (base 16) Keheng Information Industry Co., Ltd. - Building 4, Lushang Guo Olympic City, No. 9777 Jingshi Road, Lixia District, Jinan City, Shandong Province. - Jinan Shandong 250098 +50-EE-32 (hex) Hon Hai Precision Industry Co.,LTD +50EE32 (base 16) Hon Hai Precision Industry Co.,LTD + 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI CITY,23678 , TAIWAN CHINA + TAIPEI 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI 33859 CN -3C-58-36 (hex) Sagemcom Broadband SAS -3C5836 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -84-CB-85 (hex) EM Microelectronic -84CB85 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +08-3F-21 (hex) Motorola Mobility LLC, a Lenovo Company +083F21 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US -AC-F2-3C (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -ACF23C (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 +98-E7-D5 (hex) NXP Semiconductor (Tianjin) LTD. +98E7D5 (base 16) NXP Semiconductor (Tianjin) LTD. + No.15 Xinghua Avenue, Xiqing Economic Development Area + Tianjin 300385 CN -E4-57-68 (hex) vivo Mobile Communication Co., Ltd. -E45768 (base 16) vivo Mobile Communication Co., Ltd. +E4-41-D4 (hex) vivo Mobile Communication Co., Ltd. +E441D4 (base 16) vivo Mobile Communication Co., Ltd. No.1, vivo Road, Chang'an Dongguan Guangdong 523860 CN -48-E6-C6 (hex) IEEE Registration Authority -48E6C6 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +64-44-7B (hex) vivo Mobile Communication Co., Ltd. +64447B (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN -00-04-17 (hex) Schneider Electric -000417 (base 16) Schneider Electric - Schneiderplatz 1 - Marktheidenfeld 97828 - DE +4C-85-8A (hex) BUFFALO.INC +4C858A (base 16) BUFFALO.INC + AKAMONDORI Bld.,30-20,Ohsu 3-chome,Naka-ku + Nagoya Aichi Pref. 460-8315 + JP -D0-1B-BE (hex) Onward Brands -D01BBE (base 16) Onward Brands - 767 Fifth Ave, 37F - New York NY 10153 +10-A4-50 (hex) Kwikset +10A450 (base 16) Kwikset + 110 Sargent Dr. + New Haven CT 06511 US -24-6A-0E (hex) HP Inc. -246A0E (base 16) HP Inc. - 10300 Energy Dr - Spring TX 77389 - US +C8-48-05 (hex) Nintendo Co.,Ltd +C84805 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP -B0-7C-51 (hex) Ruckus Wireless -B07C51 (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 - US +80-A1-97 (hex) u-blox AG +80A197 (base 16) u-blox AG + Zuercherstrasse, 68 + Thalwil Switzerland CH-8800 + CH -88-4F-59 (hex) Cisco Systems, Inc -884F59 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +8C-B6-C5 (hex) Mimosa Networks +8CB6C5 (base 16) Mimosa Networks + 3150 Coronado Dr + Santa Clara CA 95054 US -7C-FA-80 (hex) JiangSu Fulian Communication Technology Co., Ltd -7CFA80 (base 16) JiangSu Fulian Communication Technology Co., Ltd - Jiang Su Fulian Communication Technology Co., Ltd - Danyang Jiangsu 212300 - CN +84-EA-D2 (hex) KOGANEI CORPORATION +84EAD2 (base 16) KOGANEI CORPORATION + 3-11-28, Midori-cho + Koganei-shi Tokyo 184-8533 + JP -C0-87-06 (hex) Shenzhen Qianfenyi Intelligent Technology Co.,LTD -C08706 (base 16) Shenzhen Qianfenyi Intelligent Technology Co.,LTD - Room 2101, Building 3, Nanshan i ParkChongwen, No. 3370 Liuxian Avenue,Nanshan District - Shenzhen Guangdong 518000 +F0-DB-2A (hex) LANNER ELECTRONICS, INC. +F0DB2A (base 16) LANNER ELECTRONICS, INC. + 7F, No. 173, Section 2, Datong Rd, Xizhi District + New Taipei City 22184 + TW + +D8-0F-B5 (hex) SHENZHEN ULTRAEASY TECHNOLOGY CO LTD +D80FB5 (base 16) SHENZHEN ULTRAEASY TECHNOLOGY CO LTD + 22nd Floor,Block A,Huizhi R&D Center,NO.381 Xixiang Section,Guangshen Highway,Baoan District,Shenzhen City,Guangdong Province + SHENZHEN 518101 CN -C4-B3-49 (hex) Apple, Inc. -C4B349 (base 16) Apple, Inc. +EC-FF-3A (hex) Apple, Inc. +ECFF3A (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -34-F6-8D (hex) Apple, Inc. -34F68D (base 16) Apple, Inc. +70-13-84 (hex) Apple, Inc. +701384 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -94-54-C5 (hex) Espressif Inc. -9454C5 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -AC-B7-22 (hex) Qingdao Haier Technology Co.,Ltd -ACB722 (base 16) Qingdao Haier Technology Co.,Ltd - Building C01,Haier Information Park,No.1 Haier Road - Qingdao 266101 - CN +84-A8-24 (hex) Google, Inc. +84A824 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US -CC-27-46 (hex) Apple, Inc. -CC2746 (base 16) Apple, Inc. +34-66-91 (hex) Apple, Inc. +346691 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -28-59-23 (hex) Xiaomi Communications Co Ltd -285923 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +E0-1C-A7 (hex) Arista Networks +E01CA7 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara 95054 + US -D0-CE-C0 (hex) Xiaomi Communications Co Ltd -D0CEC0 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +A8-D3-C8 (hex) Wachendorff Automation GmbH & CO.KG +A8D3C8 (base 16) Wachendorff Automation GmbH & CO.KG + Industriestraße 7 + Geisenheim 65366 + DE -0C-01-A5 (hex) zte corporation -0C01A5 (base 16) zte corporation +E4-CD-A7 (hex) zte corporation +E4CDA7 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China shenzhen guangdong 518057 CN -98-FA-9B (hex) LCFC(Hefei) Electronics Technology co., ltd -98FA9B (base 16) LCFC(Hefei) Electronics Technology co., ltd - YunGu Road 3188-1 - Hefei Anhui 230000 - CN - -F8-75-A4 (hex) LCFC(Hefei) Electronics Technology co., ltd -F875A4 (base 16) LCFC(Hefei) Electronics Technology co., ltd - YunGu Road 3188-1 - Hefei Anhui 230000 - CN +48-8C-78 (hex) Alpha Networks Inc. +488C78 (base 16) Alpha Networks Inc. + No. 8, Li-Hsin 7th Rd., Hsinchu Science Park, + Hsinchu Taiwan 300094 + TW -8C-8C-AA (hex) LCFC(Hefei) Electronics Technology co., ltd -8C8CAA (base 16) LCFC(Hefei) Electronics Technology co., ltd - YunGu Road 3188-1 - Hefei Anhui 230000 +90-95-07 (hex) HUAWEI TECHNOLOGIES CO.,LTD +909507 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -70-19-88 (hex) Nanjing Qinheng Microelectronics Co., Ltd. -701988 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. - No.18, Ningshuang Road - Nanjing Jiangsu 210012 +88-63-C5 (hex) HUAWEI TECHNOLOGIES CO.,LTD +8863C5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -FC-1A-46 (hex) Samsung Electronics Co.,Ltd -FC1A46 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +50-0B-88 (hex) Moxa.Inc +500B88 (base 16) Moxa.Inc + 13F, No. 3, Sec. 4, New Taipei Blvd. Xinzhuang Dist. + New Taipei City 242032 + TW -98-3D-AE (hex) Espressif Inc. -983DAE (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +24-FB-E3 (hex) HP Inc. +24FBE3 (base 16) HP Inc. + 10300 Energy Dr + Spring TX 77389 + US -5C-5E-0A (hex) Samsung Electronics Co.,Ltd -5C5E0A (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +54-0B-B6 (hex) Variscite LTD +540BB6 (base 16) Variscite LTD + 15 Ismargad St + Kiryat Gat 8202041 + IL -CC-B0-B3 (hex) Microsoft Corporation -CCB0B3 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US +98-9F-1A (hex) Private +989F1A (base 16) Private -80-D5-2C (hex) Beijing Cheering Networks Technology Co.,Ltd. -80D52C (base 16) Beijing Cheering Networks Technology Co.,Ltd. - Room859,Floor8,Building2,No.18,Yangfangdian Road, Haidian District, Beijing - Beijing Beijing 100038 - CN +94-0E-2A (hex) NXP Semiconductors Taiwan Ltd. +940E2A (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW -88-B2-AB (hex) Fiberhome Telecommunication Technologies Co.,LTD -88B2AB (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN +F0-74-BF (hex) Silicon Laboratories +F074BF (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -24-E3-A4 (hex) Fiberhome Telecommunication Technologies Co.,LTD -24E3A4 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN +5C-8E-10 (hex) TimeWatch Infocom Pvt. Ltd. +5C8E10 (base 16) TimeWatch Infocom Pvt. Ltd. + D-162, Okhla Indl. Area Phase -1 + New Delhi Delhi 110020 + IN -30-00-FC (hex) Nokia -3000FC (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +00-52-45 (hex) GANATECHWIN +005245 (base 16) GANATECHWIN + 32-77, Deulseong-ro 9-gil, Goa-eup + Gumi-si Gyeongsangbuk-do 39146 + KR -64-DE-6D (hex) Intel Corporate -64DE6D (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +44-C2-0C (hex) Cisco Systems, Inc +44C20C (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -EC-4C-8C (hex) Intel Corporate -EC4C8C (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +F8-97-B0 (hex) Goki Pty Ltd +F897B0 (base 16) Goki Pty Ltd + 17 Terrace Road + Dulwich Hill NSW 2203 + AU -EC-66-52 (hex) Info Fiber Solutions Pvt Ltd -EC6652 (base 16) Info Fiber Solutions Pvt Ltd - Basement B 64, Sector 65, Noida - Gautam Buddha Nagar UP 201301 - IN +88-94-8E (hex) Max Weishaupt SE +88948E (base 16) Max Weishaupt SE + Max-Weishaupt-Str. 14 + Schwendi 88475 + DE -CC-76-3A (hex) zte corporation -CC763A (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +64-7B-40 (hex) Sichuan AI-Link Technology Co., Ltd. +647B40 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 CN -30-04-75 (hex) QBIC COMMUNICATIONS DMCC -300475 (base 16) QBIC COMMUNICATIONS DMCC - 1003 Palladium Tower JLT Dubai, UAE - Dubai 393518 - AE +2C-2D-48 (hex) Commend International GmbH +2C2D48 (base 16) Commend International GmbH + Saalachstrasse 51 + Salzburg Salzburg A-5020 + AT -00-0A-F6 (hex) Copeland LP -000AF6 (base 16) Copeland LP - 1640 Airport Rd - Kennesaw GA 30144-7038 - US +18-60-41 (hex) Arcadyan Corporation +186041 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW -5C-18-DD (hex) CIG SHANGHAI CO LTD -5C18DD (base 16) CIG SHANGHAI CO LTD - 5th Floor, Building 8 No 2388 Chenhang Road - SHANGHAI 201114 +A0-6B-4A (hex) TCT mobile ltd +A06B4A (base 16) TCT mobile ltd + No.86 hechang 7th road, zhongkai, Hi-Tech District + Hui Zhou Guang Dong 516006 CN -14-36-0E (hex) Zyxel Communications Corporation -14360E (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW - -5C-4E-EE (hex) AltoBeam Inc. -5C4EEE (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 +0C-79-55 (hex) Hui Zhou Gaoshengda Technology Co.,LTD +0C7955 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD + No.2,Jin-da Road,Huinan Industrial Park + Hui Zhou Guangdong 516025 CN -5C-C1-F2 (hex) HUAWEI TECHNOLOGIES CO.,LTD -5CC1F2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +74-AD-CB (hex) New H3C Technologies Co., Ltd +74ADCB (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -C8-05-A4 (hex) Motorola(Wuhan) Mobility Technologies Communication Co.,Ltd -C805A4 (base 16) Motorola(Wuhan) Mobility Technologies Communication Co.,Ltd - No.19, Gaoxin 4th Road,WUhan East Lake High-tech Zone, Wuhan - Wuhan Hubei 430000 +4C-99-E8 (hex) ZHEJIANG DAHUA TECHNOLOGYCO.,LTD +4C99E8 (base 16) ZHEJIANG DAHUA TECHNOLOGYCO.,LTD + 1st Floor, Building 1, No. 1399, Binxing Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province 311200 CN -00-72-EE (hex) Intel Corporate -0072EE (base 16) Intel Corporate +D8-19-09 (hex) Wiwynn Technology Service Malaysia +D81909 (base 16) Wiwynn Technology Service Malaysia + No. 5, Jalan Kargo 1, Senai Airport City, 81400 Senai, Johor, Malaysia + Senai Johor 81400 + MY + +38-77-CD (hex) KOKUSAI ELECTRIC CORPORATION +3877CD (base 16) KOKUSAI ELECTRIC CORPORATION + 2-1 Yasuuchi, Yatsuo-Machi Toyama + Toyama Japan 939-2393 + JP + +80-84-89 (hex) Intel Corporate +808489 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -C8-58-B3 (hex) Intel Corporate -C858B3 (base 16) Intel Corporate +FC-B3-AA (hex) Intel Corporate +FCB3AA (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -E4-1F-D5 (hex) Intel Corporate -E41FD5 (base 16) Intel Corporate +4C-0F-3E (hex) Intel Corporate +4C0F3E (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -EC-1B-5F (hex) Hewlett Packard Enterprise -EC1B5F (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 - US - -1C-B4-6C (hex) HUAWEI TECHNOLOGIES CO.,LTD -1CB46C (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -E8-F6-73 (hex) Microsoft Corporation -E8F673 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US - -78-64-A0 (hex) Cisco Systems, Inc -7864A0 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -5C-DB-36 (hex) Calix Inc. -5CDB36 (base 16) Calix Inc. - 2777 Orchard Pkwy - San Jose CA 95131 - US - -4C-7B-35 (hex) UNIONMAN TECHNOLOGY CO.,LTD -4C7B35 (base 16) UNIONMAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 - CN - -9C-45-F0 (hex) SKYLARK ELECTRONICS PVT LTD -9C45F0 (base 16) SKYLARK ELECTRONICS PVT LTD - 3, SAKLAT PLACE 3RD FLOOR, HINDUSTAN BUILDING, KOLKATA - KOLKATA WEST BENGAL 700072 - IN - -40-92-49 (hex) Shanghai Baud Data Communication Co.,Ltd. -409249 (base 16) Shanghai Baud Data Communication Co.,Ltd. - NO.123 JULI RD - PUDONG ZHANGJIANG HIGH-TECH PARK SHANGHAI 201203 - CN - -0C-7E-24 (hex) Garmin International -0C7E24 (base 16) Garmin International - 1200 E. 151st St - Olathe KS 66062 - US - -74-75-DF (hex) TECLINK -7475DF (base 16) TECLINK - 171 avenue des gresillons - gennevilliers 92230 - FR - -68-6B-6A (hex) Phytium Technology Co.,Ltd. -686B6A (base 16) Phytium Technology Co.,Ltd. - Building5,XinAn Business Square,Haiyuan Middle Road Binhai New District, - Tianjin 300450 - CN - -34-07-AC (hex) PRONYX TRADING LLC -3407AC (base 16) PRONYX TRADING LLC - Office 2906, Citadel Tower, Marasi Drive, Business Bay - Dubai Dubai 16186 - AE - -58-B8-58 (hex) SZ DJI TECHNOLOGY CO.,LTD -58B858 (base 16) SZ DJI TECHNOLOGY CO.,LTD - DJI Sky City, No55 Xianyuan Road, Nanshan District - Shenzhen Guangdong 518057 - CN - -44-B4-23 (hex) HANWHA VISION VIETNAM COMPANY LIMITED -44B423 (base 16) HANWHA VISION VIETNAM COMPANY LIMITED - LOT O-2, QUE VO INDUSTRIAL ZONE EXTENDED AREA, NAM SON WARD - BAC NINH CITY BAC NINH PROVINCE 790000 - VN - -68-87-BD (hex) zte corporation -6887BD (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - -30-58-EB (hex) zte corporation -3058EB (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +94-C7-A8 (hex) Jiangsu Huitong Group Co.,Ltd. +94C7A8 (base 16) Jiangsu Huitong Group Co.,Ltd. + No. 24, Block 2, Taohuawu New District + Zhenjiang Jiangsu 212003 CN -44-05-B8 (hex) Huawei Device Co., Ltd. -4405B8 (base 16) Huawei Device Co., Ltd. +88-DD-B8 (hex) Huawei Device Co., Ltd. +88DDB8 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -84-1A-24 (hex) UNIONMAN TECHNOLOGY CO.,LTD -841A24 (base 16) UNIONMAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 +00-9B-08 (hex) Quectel Wireless Solutions Co.,Ltd. +009B08 (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN -78-6C-AB (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -786CAB (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN +BC-DF-E1 (hex) IEEE Registration Authority +BCDFE1 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -00-7D-3B (hex) Samsung Electronics Co.,Ltd -007D3B (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 +58-96-0A (hex) LG Electronics +58960A (base 16) LG Electronics + 222 LG-ro, JINWI-MYEON + Pyeongtaek-si Gyeonggi-do 451-713 KR -5C-40-E3 (hex) NOVAON -5C40E3 (base 16) NOVAON - 171 avenue des Gresillons - Gennevilliers 92230 - FR - -98-62-97 (hex) Shenzhen Techwinsemi Technology Co., Ltd. -986297 (base 16) Shenzhen Techwinsemi Technology Co., Ltd. - Room 2301, 2401, 2501, Building 1, Shenzhen New Generation Industrial Park, No. 136 Zhongkang Road, Futian District - Shenzhen Guangdong 518000 +8C-BD-37 (hex) Shenzhen Phaten Tech. LTD +8CBD37 (base 16) Shenzhen Phaten Tech. LTD + C-6 ideamonto industril 7002 Songbai Road Guangming District Shenzhen City Guangdong, China + Shenzhen 518108 CN -E8-BC-E4 (hex) Cisco Systems, Inc -E8BCE4 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -8C-87-26 (hex) VAST Data Inc -8C8726 (base 16) VAST Data Inc - 240 W 37th St - New York NY 10018 +C4-D6-D3 (hex) Dell Inc. +C4D6D3 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 US -68-62-8A (hex) vivo Mobile Communication Co., Ltd. -68628A (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN - -28-44-F4 (hex) Honor Device Co., Ltd. -2844F4 (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 - -BC-31-E2 (hex) New H3C Technologies Co., Ltd -BC31E2 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN - -18-E8-EC (hex) STMicrolectronics International NV -18E8EC (base 16) STMicrolectronics International NV - 39, Chemin du Champ-des-Filles - Geneva, Plan-les-Quates 1228 +30-0A-9D (hex) Axino Solutions AG +300A9D (base 16) Axino Solutions AG + Glutz – Blotzheim – Strasse 1 + Solothurn 4500 CH -28-F5-2B (hex) FN-LINK TECHNOLOGY Ltd. -28F52B (base 16) FN-LINK TECHNOLOGY Ltd. - No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China - Changsha Hunan 410329 +2C-D8-AE (hex) Shenzhen SEI Robotics Co.,Ltd +2CD8AE (base 16) Shenzhen SEI Robotics Co.,Ltd + 11th Floor, Kangtai Group Building, No. 222 Kefa Road, Science and Technology Park Community, Yuehai Street, Nanshan District, Shenzhen + Shenzhen Guangdong 518000 CN -F8-EF-5D (hex) Motorola Mobility LLC, a Lenovo Company -F8EF5D (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US - -A4-09-B3 (hex) HUAWEI TECHNOLOGIES CO.,LTD -A409B3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +C4-B7-57 (hex) ALPSALPINE CO,.LTD +C4B757 (base 16) ALPSALPINE CO,.LTD + nishida 6-1 + Kakuda-City Miyagi-Pref 981-1595 + JP -3C-07-D7 (hex) Apple, Inc. -3C07D7 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +88-F7-15 (hex) Arista Networks +88F715 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 US -A4-B0-F5 (hex) Texas Instruments -A4B0F5 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +90-E6-43 (hex) Tesla,Inc. +90E643 (base 16) Tesla,Inc. + 3500 Deer Creek Rd. + PALO ALTO CA 94304 US -2C-D3-AD (hex) Texas Instruments -2CD3AD (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +40-B3-FA (hex) Apple, Inc. +40B3FA (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -54-FB-5A (hex) Optomind Inc. -54FB5A (base 16) Optomind Inc. - 16 Deogyeong-daero #1556, Suite B-301, Yeongtong-gu - Suwon Gyeonggi-do 16690 - KR +9C-DA-36 (hex) TECNO MOBILE LIMITED +9CDA36 (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 + Hong Kong Hong Kong 999077 + HK -6C-D7-A0 (hex) WIKO Terminal Technology (Dongguan) Co., Ltd. -6CD7A0 (base 16) WIKO Terminal Technology (Dongguan) Co., Ltd. - Building 3, No. 6, Hongmian Road, Songshan Lake Park, Dongguan City, Guangdong Province - Dongguan Guangdong 523808 +24-42-E3 (hex) Shenzhen Ai-Thinker Technology Co.,Ltd +2442E3 (base 16) Shenzhen Ai-Thinker Technology Co.,Ltd + 410, Block C, Huafeng Smart Innovation Port, Gushu 2nd Road, GushuCommunity, Xixiang Street, Baoan District, Shenzhen, China + Shenzhen 518100 CN -F8-E3-5F (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -F8E35F (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County - Chengdu Sichuan 611330 +8C-BE-6F (hex) Tianyi Telecom Terminals Company Limited +8CBE6F (base 16) Tianyi Telecom Terminals Company Limited + 12-19 floors inside 101, Floor 4-33, Building 1, Yard 1, Fenghuang Zui Street, Fengtai District, Beijing + Beijing 100073 CN -E8-E7-C3 (hex) zte corporation -E8E7C3 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +D0-C1-BF (hex) Xiaomi Communications Co Ltd +D0C1BF (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -24-B2-B9 (hex) Liteon Technology Corporation -24B2B9 (base 16) Liteon Technology Corporation - 4F, 90, Chien 1 Road - New Taipei City Taiwan 23585 - TW +A8-2B-D5 (hex) Xiaomi Communications Co Ltd +A82BD5 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -1C-1D-D3 (hex) Apple, Inc. -1C1DD3 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +C0-C9-89 (hex) Edgecore Americas Networking Corporation +C0C989 (base 16) Edgecore Americas Networking Corporation + 20 Mason + Irvine CA 92618 US -C8-1F-E8 (hex) Apple, Inc. -C81FE8 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +04-00-6E (hex) Google, Inc. +04006E (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US -08-B9-5F (hex) Silicon Laboratories -08B95F (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 +BC-D7-D4 (hex) Roku, Inc +BCD7D4 (base 16) Roku, Inc + 1173 Coleman Ave + San Jose CA 95110 US -74-78-47 (hex) Interdisciplinary Consulting Corporation -747847 (base 16) Interdisciplinary Consulting Corporation - 2405 NW 66th Ct - Gainesville FL 32653-1633 +F8-14-DD (hex) Cisco Systems, Inc +F814DD (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -78-42-1C (hex) Espressif Inc. -78421C (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +0C-82-47 (hex) CIG SHANGHAI CO LTD +0C8247 (base 16) CIG SHANGHAI CO LTD + 5th Floor, Building 8 No 2388 Chenhang Road + SHANGHAI 201114 CN -F0-D5-06 (hex) Ubee Interactive Co., Limited -F0D506 (base 16) Ubee Interactive Co., Limited - UNIT 2703A, 27/F., 148 ELECTRIC ROAD, NORTH POINT, HONG KONG - NORTH POINT 00000 - HK +78-ED-25 (hex) New H3C Technologies Co., Ltd +78ED25 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN -7C-E5-3F (hex) HUAWEI TECHNOLOGIES CO.,LTD -7CE53F (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +44-76-09 (hex) New H3C Technologies Co., Ltd +447609 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -B4-23-A2 (hex) Google, Inc. -B423A2 (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 +04-C8-45 (hex) TP-Link Systems Inc. +04C845 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US -38-D0-9C (hex) HUAWEI TECHNOLOGIES CO.,LTD -38D09C (base 16) HUAWEI TECHNOLOGIES CO.,LTD +B0-C6-1C (hex) HUAWEI TECHNOLOGIES CO.,LTD +B0C61C (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -CC-22-DF (hex) EM Microelectronic -CC22DF (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH - -8C-2A-85 (hex) Amazon Technologies Inc. -8C2A85 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - -E8-C6-E6 (hex) CHANGHONG (HONGKONG) TRADING LIMITED -E8C6E6 (base 16) CHANGHONG (HONGKONG) TRADING LIMITED - Unit 1412, 14/F., West Tower, Shun Tak Centre, 168-200 Connaught Road Central, HongKong - HONG KONG HONG KONG 999077 - HK - -D4-A3-EB (hex) Shenzhen iComm Semiconductor CO.,LTD -D4A3EB (base 16) Shenzhen iComm Semiconductor CO.,LTD - Room204,scientific research building,Tsinghua Hi-Tech Park,No.13 Langshan Road,Nanshan District - Shenzhen Guangdong 518067 - CN - -9C-84-B6 (hex) Shenzhen iComm Semiconductor CO.,LTD -9C84B6 (base 16) Shenzhen iComm Semiconductor CO.,LTD - Room204,scientific research building,Tsinghua Hi-Tech Park,No.13 Langshan Road,Nanshan District - Shenzhen Guangdong 518067 - CN - -20-67-E0 (hex) Shenzhen iComm Semiconductor CO.,LTD -2067E0 (base 16) Shenzhen iComm Semiconductor CO.,LTD - Room204,scientific research building,Tsinghua Hi-Tech Park,No.13 Langshan Road,Nanshan District - Shenzhen Guangdong 518067 +44-BE-0B (hex) HUAWEI TECHNOLOGIES CO.,LTD +44BE0B (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -E4-D5-8B (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -E4D58B (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN +7C-99-46 (hex) Sector Alarm Tech S.L. +7C9946 (base 16) Sector Alarm Tech S.L. + Calle Verónica, Esq. Calle Tulipán, Esq Calle Geranio + Las Lagunas- Mijas Malaga 29651 + ES -0C-97-9B (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD -0C979B (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD - 19-22# Building, Star-net Science Plaza, Juyuanzhou, - FUZHOU FUJIAN 350002 +5C-5E-BB (hex) HUAWEI TECHNOLOGIES CO.,LTD +5C5EBB (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -50-04-01 (hex) TelHi Corporation -500401 (base 16) TelHi Corporation - 1-32-2, Kosuge - Katsushika-ku Tokyo 1240001 - JP +40-25-08 (hex) Highway 9 Networks, Inc. +402508 (base 16) Highway 9 Networks, Inc. + 2350 Mission College BlvdSte 490 + Santa Clara 95054 + US -08-F4-F0 (hex) Cisco Systems, Inc -08F4F0 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +04-A1-6F (hex) IEEE Registration Authority +04A16F (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -28-DB-02 (hex) zte corporation -28DB02 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +14-39-2F (hex) LEAR +14392F (base 16) LEAR + Carrer Fuster 54 + Valls Tarragona 43800 + ES -88-15-66 (hex) Huawei Device Co., Ltd. -881566 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +8C-3F-44 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +8C3F44 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN -28-45-AC (hex) Huawei Device Co., Ltd. -2845AC (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +34-97-D7 (hex) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. +3497D7 (base 16) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. + 309, 3th Floor, No.16, Yun Ding North Road, Huli District + xiamen Fujian 361015 CN -A4-43-80 (hex) Huawei Device Co., Ltd. -A44380 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +68-F6-2B (hex) ITEL MOBILE LIMITED +68F62B (base 16) ITEL MOBILE LIMITED + RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K + Hong Kong KOWLOON 999077 + HK -10-86-F4 (hex) Huawei Device Co., Ltd. -1086F4 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +28-43-DC (hex) United Memory Technology (Jiangsu) Limited +2843DC (base 16) United Memory Technology (Jiangsu) Limited + C5-401-03,Fuli Center,Xinyuan Road,Wuxi Economic Development Zone + Wuxi JiangSu 214000 CN -D4-50-EE (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -D450EE (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 +98-12-E0 (hex) Xiaomi Communications Co Ltd +9812E0 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -24-21-5E (hex) Quectel Wireless Solutions Co.,Ltd. -24215E (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN +64-DD-68 (hex) Zyxel Communications Corporation +64DD68 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW -5C-01-3B (hex) Espressif Inc. -5C013B (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +38-77-07 (hex) AltoBeam Inc. +387707 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -E8-05-DC (hex) Verifone, Inc. -E805DC (base 16) Verifone, Inc. - 2560 North First Street, Suite 220 - San Jose CA 95131 +74-CA-60 (hex) Sonos, Inc. +74CA60 (base 16) Sonos, Inc. + 301 Coromar Dr + Goleta CA 93117 US -A0-FB-68 (hex) Miba Battery Systems GmbH -A0FB68 (base 16) Miba Battery Systems GmbH - Maximilianstrasse 4 - Bad Leonfelden 4190 - AT - -28-6B-B4 (hex) SJIT Co., Ltd. -286BB4 (base 16) SJIT Co., Ltd. - 54-33 Dongtanhana 1-gil - Hwaseong-si Gyeonggi-do 18423 - KR - -98-C9-BE (hex) Shenzhen SDMC Technology CO., LTD -98C9BE (base 16) Shenzhen SDMC Technology CO., LTD - Xin'an 3rd Road, Dalang Community, Xin'an Street - Shenzhen Guangdong 518000 - CN - -6C-83-75 (hex) Broadcom Limited -6C8375 (base 16) Broadcom Limited - 15191 Alton Parkway - Irvine CA 92618 +38-42-0B (hex) Sonos, Inc. +38420B (base 16) Sonos, Inc. + 301 Coromar Dr + Goleta CA 93117 US -D4-BE-DC (hex) Roku, Inc -D4BEDC (base 16) Roku, Inc - 1173 Coleman Ave - San Jose CA 95110 - US +A4-4B-D9 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +A44BD9 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN -00-CD-90 (hex) MAS Elektronik AG -00CD90 (base 16) MAS Elektronik AG - Carl-Zeiss-Straße 31 - Buxtehude 21614 - DE +EC-1B-FA (hex) EM Microelectronic +EC1BFA (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH -30-ED-96 (hex) LS Mecapion -30ED96 (base 16) LS Mecapion - 12-9, Hosandong-ro, Dalseo-gu, Deagu, Korea 42714 - Deagu Select State KS002 - KR +80-E4-BA (hex) Intel Corporate +80E4BA (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -FC-06-8C (hex) SHENZHEN MICIPC TECHNOLOGY CO.,LTD -FC068C (base 16) SHENZHEN MICIPC TECHNOLOGY CO.,LTD - 508,building 8,Hengda fashion Huigu,Fulong Road,Dalang street,Longhua District,Shenzhen - Shenzhen 518000 - CN +4C-D0-F9 (hex) Cisco Systems, Inc +4CD0F9 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -DC-86-8D (hex) HUAWEI TECHNOLOGIES CO.,LTD -DC868D (base 16) HUAWEI TECHNOLOGIES CO.,LTD +A8-79-71 (hex) HUAWEI TECHNOLOGIES CO.,LTD +A87971 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -E8-F4-94 (hex) AltoBeam Inc. -E8F494 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN - -48-CF-A9 (hex) HUAWEI TECHNOLOGIES CO.,LTD -48CFA9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +60-DE-94 (hex) HUAWEI TECHNOLOGIES CO.,LTD +60DE94 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -D0-87-B5 (hex) SAFEMO PTE. LTD. -D087B5 (base 16) SAFEMO PTE. LTD. - 61 BUKIT BATOK CRESCENT #05-505 HENG LOONG BUILDING - Singapore 658078 - SG - -74-25-84 (hex) IEEE Registration Authority -742584 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - -40-48-6E (hex) Nokia Solutions and Networks GmbH & Co. KG -40486E (base 16) Nokia Solutions and Networks GmbH & Co. KG - Werinherstrasse 91 - München Bavaria D-81541 - DE - -D8-B0-61 (hex) SHENZHEN WENXUN TECHNOLOGY CO.,LTD -D8B061 (base 16) SHENZHEN WENXUN TECHNOLOGY CO.,LTD - JUNLINTIANXIA A-3705, XINSHA ROAD, FUTIAN DISTRICT - SHENZHEN 518048 - CN - -08-DD-03 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -08DD03 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN - -28-37-2F (hex) Espressif Inc. -28372F (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +48-F7-BC (hex) HUAWEI TECHNOLOGIES CO.,LTD +48F7BC (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -E4-4E-12 (hex) zte corporation -E44E12 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +1C-32-AC (hex) HUAWEI TECHNOLOGIES CO.,LTD +1C32AC (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -CC-B8-5E (hex) Shenzhen Phaten Tech. LTD -CCB85E (base 16) Shenzhen Phaten Tech. LTD - C-6 ideamonto industril 7002 Songbai Road Guangming District Shenzhen City Guangdong, China - Shenzhen 518108 +18-33-86 (hex) HUAWEI TECHNOLOGIES CO.,LTD +183386 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -C8-A7-02 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -C8A702 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN +30-E2-71 (hex) Fsas Technologies Inc. +30E271 (base 16) Fsas Technologies Inc. + JR Kawasaki Tower, 1-5 Omiya-cho, Saiwai-ku + Kawasaki City Kanagawa Prefecture 212-0014 + JP -2C-FE-8B (hex) Microchip Technologies Inc -2CFE8B (base 16) Microchip Technologies Inc - 2355 W Chandler Blvd - Chandler AZ 85224-6199 +A4-7A-72 (hex) Arista Networks +A47A72 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 US -68-EF-AB (hex) Vention -68EFAB (base 16) Vention - 4767 Dagenais, Suite 201B - Montreal Quebec H4C 1L8 - CA - -B0-92-00 (hex) Apple, Inc. -B09200 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +F8-0D-4B (hex) Nextracker, Inc. +F80D4B (base 16) Nextracker, Inc. + 6200 Paseo Padre Pkwy + Fremont CA 94555 US -A0-EE-1A (hex) Apple, Inc. -A0EE1A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +20-1B-A5 (hex) Vizio, Inc +201BA5 (base 16) Vizio, Inc + 39 Tesla + Irvine CA 92618 US -78-1E-B8 (hex) Shenzhen iComm Semiconductor CO.,LTD -781EB8 (base 16) Shenzhen iComm Semiconductor CO.,LTD +A4-B0-39 (hex) Shenzhen iComm Semiconductor CO.,LTD +A4B039 (base 16) Shenzhen iComm Semiconductor CO.,LTD Room204,scientific research building,Tsinghua Hi-Tech Park,No.13 Langshan Road,Nanshan District Shenzhen Guangdong 518067 CN -58-41-46 (hex) Guangzhou Shiyuan Electronic Technology Company Limited -584146 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 +88-DE-39 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +88DE39 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 CN -9C-F3-AC (hex) Apple, Inc. -9CF3AC (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +C0-E3-50 (hex) CHINA DRAGON TECHNOLOGY LIMITED +C0E350 (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 -08-5D-53 (hex) Apple, Inc. -085D53 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +F8-2A-53 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +F82A53 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN + +C8-61-D0 (hex) SHEN ZHEN KTC TECHNOLOGY.,LTD. +C861D0 (base 16) SHEN ZHEN KTC TECHNOLOGY.,LTD. + NO 4023,Wuhe Road,Bantian,Longgang district,Shenzhen,Guangdong,China + SHEN ZHEN GUANG DONG 518100 + CN + +58-23-9B (hex) Fiberhome Telecommunication Technologies Co.,LTD +58239B (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN + +3C-0B-59 (hex) Tuya Smart Inc. +3C0B59 (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 US -54-29-06 (hex) Apple, Inc. -542906 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +00-05-5B (hex) Charles Industries +00055B (base 16) Charles Industries + 5600 Apollo Dr. + Rolling Meadows IL 60008 US -70-15-FB (hex) Intel Corporate -7015FB (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +20-36-26 (hex) TP-Link Systems Inc +203626 (base 16) TP-Link Systems Inc + Room 901,9/F.New East Ocean Centre, 9 Science Museum Road + Tsim Sha Tsui Kowloon 999077 + HK -B0-47-5E (hex) IEEE Registration Authority -B0475E (base 16) IEEE Registration Authority +4C-DE-48 (hex) Huawei Device Co., Ltd. +4CDE48 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +98-E5-5B (hex) Intelbras +98E55B (base 16) Intelbras + BR 101, km 210, S/N° + São José Santa Catarina 88104800 + BR + +F8-2B-E6 (hex) IEEE Registration Authority +F82BE6 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -EC-8E-12 (hex) Nokia -EC8E12 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +E4-A4-30 (hex) Samsung Electronics Co.,Ltd +E4A430 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR -C0-84-FF (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -C084FF (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 +18-3D-2D (hex) LCFC(Hefei) Electronics Technology co., ltd +183D2D (base 16) LCFC(Hefei) Electronics Technology co., ltd + No. 3188-1 Yungu Road (Comprehensive Bonded Zone), Hefei Economic & Technological Development Area,Anhui + HEFEI ANHUI 230601 CN -10-00-3B (hex) Espressif Inc. -10003B (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +00-62-01 (hex) Motorola Mobility LLC, a Lenovo Company +006201 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US -F8-73-1A (hex) zte corporation -F8731A (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +08-CE-94 (hex) EM Microelectronic +08CE94 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +78-E1-67 (hex) Launch Tech Co., Ltd. +78E167 (base 16) Launch Tech Co., Ltd. + Launch Industrial Park, No.4012, North of Wuhe Road, Bantian Street, Longgang District, + Shenzhen Guangdong 518100 CN -CC-65-AD (hex) Commscope -CC65AD (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +B0-A7-B9 (hex) TP-Link Systems Inc +B0A7B9 (base 16) TP-Link Systems Inc + 5 Peters Canyon Rd Suit 300 + Irvine CA 92606 + HK + +6C-5A-B0 (hex) TP-Link Systems Inc +6C5AB0 (base 16) TP-Link Systems Inc + 5 Peters Canyon Rd Suit 300 + Irvine CA 92606 + HK + +5C-62-8B (hex) TP-Link Systems Inc +5C628B (base 16) TP-Link Systems Inc + 5 Peters Canyon Rd Suit 300 + Irvine CA 92606 + HK + +AC-15-A2 (hex) TP-Link Systems Inc +AC15A2 (base 16) TP-Link Systems Inc + 5 Peters Canyon Rd Suit 300 + Irvine CA 92606 + HK + +18-9C-E1 (hex) Arista Networks +189CE1 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 US -78-96-84 (hex) Commscope -789684 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +8C-E9-FF (hex) Dell Inc. +8CE9FF (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 US -E8-ED-05 (hex) Commscope -E8ED05 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +10-4E-20 (hex) HSE SMART +104E20 (base 16) HSE SMART + 14453 Hillcroft Street, Suite + Houston TX 77085 US -D4-04-CD (hex) Commscope -D404CD (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +88-25-08 (hex) Meta Platforms, Inc. +882508 (base 16) Meta Platforms, Inc. + 1601 Willow Rd + Menlo Park CA 94025 US -20-3D-66 (hex) Commscope -203D66 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +94-F9-29 (hex) Meta Platforms, Inc. +94F929 (base 16) Meta Platforms, Inc. + 1601 Willow Rd + Menlo Park CA 94025 US -D4-0A-A9 (hex) Commscope -D40AA9 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +D4-D6-59 (hex) Meta Platforms, Inc. +D4D659 (base 16) Meta Platforms, Inc. + 1601 Willow Rd + Menlo Park CA 94025 US -28-A4-4A (hex) Intel Corporate -28A44A (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +78-55-36 (hex) shenzhen AZW Technology(Group) Co.,Ltd +785536 (base 16) shenzhen AZW Technology(Group) Co.,Ltd + 7th floor, Block C, Building 8, Hengda Shishang Huigu, Fulong Road, Dalang Street, Longhua District + shenzhen 518000 + CN -10-96-C6 (hex) Cisco Systems, Inc -1096C6 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +44-1D-64 (hex) Espressif Inc. +441D64 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN -8C-09-F4 (hex) Commscope -8C09F4 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +F0-09-0D (hex) TP-Link Systems Inc +F0090D (base 16) TP-Link Systems Inc + 5 Peters Canyon Rd Suit 300 + Irvine CA 92606 US -90-0D-CB (hex) Commscope -900DCB (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +E8-48-B8 (hex) TP-Link Systems Inc +E848B8 (base 16) TP-Link Systems Inc + 5 Peters Canyon Rd Suit 300 + Irvine CA 92606 US -00-13-71 (hex) Commscope -001371 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +8C-50-1A (hex) Private +8C501A (base 16) Private -00-1E-5A (hex) Commscope -001E5A (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +14-D8-81 (hex) Beijing Xiaomi Mobile Software Co., Ltd +14D881 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN -00-1C-C1 (hex) Commscope -001CC1 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +AC-91-5D (hex) Digital Control Technology Limited +AC915D (base 16) Digital Control Technology Limited + Unit 1805-06, 18/F, Hollywood Plaza, 610 Nathan Road, Mongkok, Kowloon, + Hong Kong Hong Kong 00000 + HK -00-1D-D2 (hex) Commscope -001DD2 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +04-35-9B (hex) WuLu Networks Pty Ltd +04359B (base 16) WuLu Networks Pty Ltd + 135 Coal Point Road + Coal Point NSW 2283 + AU -00-1D-CD (hex) Commscope -001DCD (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +C0-43-27 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +C04327 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 + CN -00-1A-DE (hex) Commscope -001ADE (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +0C-A9-4A (hex) Shenzhen Skyworth Digital Technology CO., Ltd +0CA94A (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN -00-23-EE (hex) Commscope -0023EE (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +BC-AD-AE (hex) AltoBeam Inc. +BCADAE (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN -E8-3E-FC (hex) Commscope -E83EFC (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +1C-FF-AD (hex) HUAWEI TECHNOLOGIES CO.,LTD +1CFFAD (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -7C-BF-B1 (hex) Commscope -7CBFB1 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +40-89-C6 (hex) Amazon Technologies Inc. +4089C6 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -00-50-E3 (hex) Commscope -0050E3 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +64-9D-38 (hex) Google, Inc. +649D38 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US -00-16-26 (hex) Commscope -001626 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +40-58-46 (hex) vivo Mobile Communication Co., Ltd. +405846 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN -00-13-11 (hex) Commscope -001311 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +D0-B2-70 (hex) Visteon Portuguesa, Ltd +D0B270 (base 16) Visteon Portuguesa, Ltd + Estrada Nacional 252-km12 + Palmela 2951-503 + PT -00-19-A6 (hex) Commscope -0019A6 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +94-A4-CE (hex) Sensear Pty Ltd +94A4CE (base 16) Sensear Pty Ltd + 4 Hehir Street + Belmont Western Australia 6104 + AU -80-F5-03 (hex) Commscope -80F503 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +A0-99-21 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +A09921 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN -A0-55-DE (hex) Commscope -A055DE (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +8C-8A-CD (hex) HUAWEI TECHNOLOGIES CO.,LTD +8C8ACD (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -D4-2C-0F (hex) Commscope -D42C0F (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +1C-FC-2A (hex) HUAWEI TECHNOLOGIES CO.,LTD +1CFC2A (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -FC-6F-B7 (hex) Commscope -FC6FB7 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +EC-2F-90 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +EC2F90 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN -FC-8E-7E (hex) Commscope -FC8E7E (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +A8-88-CE (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +A888CE (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN -A4-15-88 (hex) Commscope -A41588 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +4C-0A-4E (hex) Extreme Networks Headquarters +4C0A4E (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 US -04-4E-5A (hex) Commscope -044E5A (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +BC-7E-C3 (hex) Zyxel Communications Corporation +BC7EC3 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW -38-70-0C (hex) Commscope -38700C (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +78-20-51 (hex) TP-Link Systems Inc. +782051 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US -FC-51-A4 (hex) Commscope -FC51A4 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +2C-DA-46 (hex) Samsung Electronics Co.,Ltd +2CDA46 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -2C-9E-5F (hex) Commscope -2C9E5F (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +48-EF-1C (hex) Samsung Electronics Co.,Ltd +48EF1C (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -40-FC-89 (hex) Commscope -40FC89 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +EC-B5-50 (hex) Samsung Electronics Co.,Ltd +ECB550 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -E4-64-49 (hex) Commscope -E46449 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +E4-43-89 (hex) Apple, Inc. +E44389 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -74-56-12 (hex) Commscope -745612 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +3C-CD-40 (hex) Apple, Inc. +3CCD40 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -28-7A-EE (hex) Commscope -287AEE (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +00-97-F1 (hex) Apple, Inc. +0097F1 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -BC-64-4B (hex) Commscope -BC644B (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +70-21-7F (hex) Xiaomi Communications Co Ltd +70217F (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN -44-AA-F5 (hex) Commscope -44AAF5 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +B8-4F-A7 (hex) Apple, Inc. +B84FA7 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-36-76 (hex) Commscope -003676 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +F4-81-C4 (hex) Apple, Inc. +F481C4 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -78-23-AE (hex) Commscope -7823AE (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +30-FC-8C (hex) Vantiva - Connected Home +30FC8C (base 16) Vantiva - Connected Home + 4855 Peachtree Industrial Blvd, Suite 200 + Norcross GA 30902 US -10-56-11 (hex) Commscope -105611 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +8C-ED-E1 (hex) Ubiquiti Inc +8CEDE1 (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 US -18-B8-1F (hex) Commscope -18B81F (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +24-F6-8D (hex) Nokia +24F68D (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -98-4B-4A (hex) Commscope -984B4A (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +34-6F-11 (hex) Qingdao Zhipai Information Technology Co., Ltd. +346F11 (base 16) Qingdao Zhipai Information Technology Co., Ltd. + Room 1401, No. 15, Miaoling Road, Laoshan District, Qingdao, Shandong, China + Qingdao 266100 + CN -E8-82-5B (hex) Commscope -E8825B (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +A8-E6-E8 (hex) Tonly Technology Co. Ltd +A8E6E8 (base 16) Tonly Technology Co. Ltd + Section 37, Zhongkai Hi-Tech Development Zone + Huizhou Guangdong 516006 + CN -00-21-36 (hex) Commscope -002136 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +9C-89-1E (hex) FireBrick Ltd +9C891E (base 16) FireBrick Ltd + c/o Andrews & Arnold Ltd Enterprise Court Downmill Road + Bracknell Berkshire RG12 1QS + GB -00-26-36 (hex) Commscope -002636 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +B4-EF-30 (hex) Shanghai SYH Technology CO.,LTD +B4EF30 (base 16) Shanghai SYH Technology CO.,LTD + Building 4, No. 686 Nanfeng Road,Fengcheng Town, Fengxian District + Shanghai 201400 + CN -50-95-51 (hex) Commscope -509551 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +30-52-53 (hex) BuildJet, Inc. +305253 (base 16) BuildJet, Inc. + 8 The Green, Suite# 13184 + Dover DE 19901 US -24-0A-63 (hex) Commscope -240A63 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +94-FF-34 (hex) HANSHOW TECHNOLOGY CO.,LTD. +94FF34 (base 16) HANSHOW TECHNOLOGY CO.,LTD. + The 1st Floor Podium and Floor 4 of Building 1, Floor 7 of Building 5, JiaxingPhotovoltaic Technology Innovation Park, No.1288, Kanghe Road, Xiuzhou District,Jiaxing City, Zhejiang Prov,P.R.China + JIAXING 314000 + CN -F8-8B-37 (hex) Commscope -F88B37 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +A0-4F-E4 (hex) PAX Computer Technology(Shenzhen) Ltd. +A04FE4 (base 16) PAX Computer Technology(Shenzhen) Ltd. + 4/F, No.3 Building, Software Park, Second Central Science-Tech Road, High-Tech + Shenzhen GuangDong 518057 + CN -88-96-4E (hex) Commscope -88964E (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +3C-B8-D6 (hex) Bluebank Communication Technology Co.,Ltd. +3CB8D6 (base 16) Bluebank Communication Technology Co.,Ltd. + No. 6, Chanyi Road, + Yubei District Chongqing 401120 + CN -CC-75-E2 (hex) Commscope -CC75E2 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +B4-5B-D1 (hex) TP-Link Systems Inc. +B45BD1 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US -14-D4-FE (hex) Commscope -14D4FE (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +80-3C-04 (hex) TP-Link Systems Inc. +803C04 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US -E4-9F-1E (hex) Commscope -E49F1E (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +2C-7A-F4 (hex) IEEE Registration Authority +2C7AF4 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -88-C4-8E (hex) UNEEVIU TECHNOLOGIES INDIA PRIVATE LIMITED -88C48E (base 16) UNEEVIU TECHNOLOGIES INDIA PRIVATE LIMITED - Sadguru Villa PL 123/128, Gorai Rd, Borivali West - Mumbai Maharashtra 400091 - IN +C8-C4-32 (hex) SG Armaturen AS +C8C432 (base 16) SG Armaturen AS + Skytterheia 25, N-4790 + Lillesand 4790 + NO -40-2B-50 (hex) Commscope -402B50 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +8C-BA-FC (hex) JOYNEXT GmbH +8CBAFC (base 16) JOYNEXT GmbH + Gewerbepark Merbitz 5 + Dresden Saxony 01156 + DE -70-54-25 (hex) Commscope -705425 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +18-97-F1 (hex) KOSTAL (Shanghai) Management Co., Ltd. +1897F1 (base 16) KOSTAL (Shanghai) Management Co., Ltd. + No. 189, Xingping Road, Jiading District, Shanghai, P.R.China + Shanghai Shanghai 201822 + CN + +F4-F5-0B (hex) TP-Link Systems Inc. +F4F50B (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US -EC-A9-40 (hex) Commscope -ECA940 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +04-0F-66 (hex) TP-Link Systems Inc. +040F66 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US -E4-F7-5B (hex) Commscope -E4F75B (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +34-56-FE (hex) Cisco Meraki +3456FE (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 US -F8-79-0A (hex) Commscope -F8790A (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +B8-07-56 (hex) Cisco Meraki +B80756 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 US -1C-93-7C (hex) Commscope -1C937C (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +A4-D5-30 (hex) Avaya LLC +A4D530 (base 16) Avaya LLC + 350 Mt Kimble + Morristown NJ 07960 US -DC-2E-97 (hex) Quectel Wireless Solutions Co.,Ltd. -DC2E97 (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 +94-14-57 (hex) Shenzhen Sundray Technologies company Limited +941457 (base 16) Shenzhen Sundray Technologies company Limited + 5th Floor, Block A4, Nanshan ipark,NO.1001 Xue Yuan Road, Nanshan District, Shenzhen 518055, P.R. China + Shenzhen Guangdong 518057 CN -80-E5-40 (hex) Commscope -80E540 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +AC-FC-82 (hex) Shenzhen Sundray Technologies company Limited +ACFC82 (base 16) Shenzhen Sundray Technologies company Limited + 5th Floor, Block A4, Nanshan ipark,NO.1001 Xue Yuan Road, Nanshan District, Shenzhen 518055, P.R. China + Shenzhen Guangdong 518057 + CN -C0-94-35 (hex) Commscope -C09435 (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +C8-A2-3B (hex) Shenzhen Sundray Technologies company Limited +C8A23B (base 16) Shenzhen Sundray Technologies company Limited + 5th Floor, Block A4, Nanshan ipark,NO.1001 Xue Yuan Road, Nanshan District, Shenzhen 518055, P.R. China + Shenzhen Guangdong 518057 + CN -74-8A-0D (hex) Commscope -748A0D (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 - US +7C-15-2D (hex) Renesas Electronics (Penang) Sdn. Bhd. +7C152D (base 16) Renesas Electronics (Penang) Sdn. Bhd. + Phase 3, Bayan Lepas FIZ + Bayan Lepas Penang 11900 + MY -8C-76-3F (hex) Commscope -8C763F (base 16) Commscope - 6450 Sequence Drive - San Diego CA 92121 +D8-F1-2E (hex) TP-Link Systems Inc. +D8F12E (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US -BC-45-48 (hex) Beijing gpthink technology co.,LTD. -BC4548 (base 16) Beijing gpthink technology co.,LTD. - Building 6-4, Jinke Lane, Industrial Zone, Daxing District, Beijing - Beijing 102627 +A0-88-5E (hex) Anhui Xiangyao New Energy Technology Co., Ltd. +A0885E (base 16) Anhui Xiangyao New Energy Technology Co., Ltd. + No. 2, District 4, Intelligent Industrial Park, South District, Lieshan Economic Development Zone + Huaibei City Anhui Province 235065 CN -B8-52-E0 (hex) Beijing Xiaomi Electronics Co.,Ltd -B852E0 (base 16) Beijing Xiaomi Electronics Co.,Ltd - Xiaomi Campus - Beijing Beijing 100085 +4C-EF-56 (hex) Shenzhen Sundray Technologies company Limited +4CEF56 (base 16) Shenzhen Sundray Technologies company Limited + 5th Floor, Block A4, Nanshan ipark,NO.1001 Xue Yuan Road, Nanshan District, Shenzhen 518055, P.R. China + Shenzhen Guangdong 518057 CN -28-9F-04 (hex) Samsung Electronics Co.,Ltd -289F04 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +A4-DB-4C (hex) RAI Institute +A4DB4C (base 16) RAI Institute + 145 Broadway + Cambridge MA 02142 + US -E0-85-4D (hex) LG Innotek -E0854D (base 16) LG Innotek - 26, HANAMSANDAN 5BEON-RO - Gwangju Gwangsan-gu 506-731 - KR +34-BC-5E (hex) eero inc. +34BC5E (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US -AC-EF-92 (hex) IEEE Registration Authority -ACEF92 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +2C-91-AB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +2C91AB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +DC-15-C8 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +DC15C8 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +B0-F2-08 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +B0F208 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +BC-35-1E (hex) Tuya Smart Inc. +BC351E (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 US -40-45-A0 (hex) vivo Mobile Communication Co., Ltd. -4045A0 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +38-10-D5 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +3810D5 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +C8-0E-14 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +C80E14 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +00-84-97 (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd +008497 (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd + 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District + Shenzhen Guangdong 518110 CN -0C-E6-7C (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -0CE67C (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 +28-7A-B4 (hex) HUAWEI TECHNOLOGIES CO.,LTD +287AB4 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -58-FC-20 (hex) Altice Labs -58FC20 (base 16) Altice Labs - NIF 504705610, Rua Eng. José Ferreira Pinto Basto - Aveiro 3810-106 - PT +78-DC-87 (hex) HUAWEI TECHNOLOGIES CO.,LTD +78DC87 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -1C-57-3E (hex) Altice Labs -1C573E (base 16) Altice Labs - NIF 504705610, Rua Eng. José Ferreira Pinto Basto - Aveiro 3810-106 - PT +E0-08-55 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +E00855 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE -78-12-3E (hex) TECNO MOBILE LIMITED -78123E (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 - Hong Kong Hong Kong 999077 - HK +10-3D-3E (hex) China Mobile Group Device Co.,Ltd. +103D3E (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN -14-B5-CD (hex) Liteon Technology Corporation -14B5CD (base 16) Liteon Technology Corporation - 4F, 90, Chien 1 Road - New Taipei City Taiwan 23585 - TW +90-47-3C (hex) China Mobile Group Device Co.,Ltd. +90473C (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN -E0-3E-CB (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. -E03ECB (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. - No.218 Qianwangang Road - Qingdao Shangdong 266510 +74-AD-B7 (hex) China Mobile Group Device Co.,Ltd. +74ADB7 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 CN -3C-A9-AB (hex) Nintendo Co.,Ltd -3CA9AB (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP +78-C3-13 (hex) China Mobile Group Device Co.,Ltd. +78C313 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN -6C-A3-1E (hex) ITEL MOBILE LIMITED -6CA31E (base 16) ITEL MOBILE LIMITED - RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K - Hong Kong KOWLOON 999077 - HK +CC-5C-DE (hex) China Mobile Group Device Co.,Ltd. +CC5CDE (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN -F8-F3-D3 (hex) Shenzhen Gotron electronic CO.,LTD -F8F3D3 (base 16) Shenzhen Gotron electronic CO.,LTD - Floor 7, Building A, Block 1, Anhongji Tianyao Plaza, Longhua District, Shenzhen - Shenzhen 518000 +0C-14-D2 (hex) China Mobile Group Device Co.,Ltd. +0C14D2 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 CN -0C-47-A9 (hex) IEEE Registration Authority -0C47A9 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +78-10-53 (hex) China Mobile Group Device Co.,Ltd. +781053 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN -C8-FB-54 (hex) iMin Technology Pte. Ltd. -C8FB54 (base 16) iMin Technology Pte. Ltd. - 11 Bishan Street 21 #03-05 Singapore 573943 - Singapore 573943 - SG +94-BE-09 (hex) China Mobile Group Device Co.,Ltd. +94BE09 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN -54-84-50 (hex) Tiinlab Corporation -548450 (base 16) Tiinlab Corporation - Building A Room 201 Cooperation District between Shenzhen and HongKong,Qianwan Road No.1,Shenzhen City, Business Address:No. 3333, Liuxian AvenueTower A, 35th Floor,Tanglang City, Nanshan District, Shenzhen, China - Shenzhen Guangdong 518000 +BC-9E-2C (hex) China Mobile Group Device Co.,Ltd. +BC9E2C (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 CN -68-95-75 (hex) Zhejiang Bodyguard Electronic Co., Ltd -689575 (base 16) Zhejiang Bodyguard Electronic Co., Ltd - 598 Xinwen Road, Xinqiao Town, Luqiao - Taizhou Zhejiang 318050 +C8-0C-53 (hex) China Mobile Group Device Co.,Ltd. +C80C53 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +54-4D-D4 (hex) China Mobile Group Device Co.,Ltd. +544DD4 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +C0-2D-2E (hex) China Mobile Group Device Co.,Ltd. +C02D2E (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +70-89-CC (hex) China Mobile Group Device Co.,Ltd. +7089CC (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 CN -20-53-8D (hex) Hon Hai Precision Industry Co., Ltd. -20538D (base 16) Hon Hai Precision Industry Co., Ltd. - GuangDongShenZhen - ShenZhen GuangDong 518109 +B4-D0-A9 (hex) China Mobile Group Device Co.,Ltd. +B4D0A9 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 CN -9C-E9-1E (hex) TEJAS NETWORKS LTD -9CE91E (base 16) TEJAS NETWORKS LTD - Plot 25 JP Software Park Electronics City Phase-1 - Bangalore Karnataka 560100 - IN +AC-71-0C (hex) China Mobile Group Device Co.,Ltd. +AC710C (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN -58-DF-59 (hex) Cisco Systems, Inc -58DF59 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +D0-CB-DD (hex) eero inc. +D0CBDD (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US -04-47-CA (hex) GREE ELECTRIC APPLIANCES, INC. OF ZHUHAI -0447CA (base 16) GREE ELECTRIC APPLIANCES, INC. OF ZHUHAI - West Jinji Rd, Qianshan - Zhuhai Guangdong 519070 - CN +44-AC-85 (hex) eero inc. +44AC85 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US -C0-F8-53 (hex) Tuya Smart Inc. -C0F853 (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 +18-A9-ED (hex) eero inc. +18A9ED (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US -00-BC-2F (hex) Actiontec Electronics Inc. -00BC2F (base 16) Actiontec Electronics Inc. - 2445 Augustine Dr #501 - Santa Clara CA 95054 +80-B9-7A (hex) eero inc. +80B97A (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US -E4-FD-8C (hex) Extreme Networks Headquarters -E4FD8C (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 +9C-A5-70 (hex) eero inc. +9CA570 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US -30-EB-15 (hex) Huawei Device Co., Ltd. -30EB15 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +FC-3F-A6 (hex) eero inc. +FC3FA6 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US -68-9B-43 (hex) Huawei Device Co., Ltd. -689B43 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +78-68-29 (hex) eero inc. +786829 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US -1C-4E-A2 (hex) Shenzhen V-Link Technology CO., LTD. -1C4EA2 (base 16) Shenzhen V-Link Technology CO., LTD. - Room 1803, BaiRuiDa Building, Bantian Sub-district, LongGang District - Shenzhen GuangDong 518000 - CN +10-1D-6E (hex) Hewlett Packard Enterprise +101D6E (base 16) Hewlett Packard Enterprise + 3333 Scott Blvd + 6280 America Center Dr CA 95002 + US -E0-22-A1 (hex) AltoBeam Inc. -E022A1 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 +04-4F-7A (hex) China Mobile Group Device Co.,Ltd. +044F7A (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 CN -04-B0-66 (hex) Private -04B066 (base 16) Private +D0-16-7C (hex) eero inc. +D0167C (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US -4C-FA-9A (hex) Shenzhen Quanxing Technology Co., Ltd -4CFA9A (base 16) Shenzhen Quanxing Technology Co., Ltd - 深圳市福田区沙头街道万科滨海置地大厦八层铨兴科技 - 深圳市 广东 518000 +24-7B-A4 (hex) China Mobile Group Device Co.,Ltd. +247BA4 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 CN -28-49-92 (hex) Luminator Technology Group Global LLC -284992 (base 16) Luminator Technology Group Global LLC - 900 Klein Road - Plano TX 75074 +F4-52-14 (hex) Mellanox Technologies, Inc. +F45214 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -A8-EA-E4 (hex) Weiser -A8EAE4 (base 16) Weiser - 110 Sargent Dr. - New Haven CT 06511 +00-25-8B (hex) Mellanox Technologies, Inc. +00258B (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -94-38-AA (hex) Technology Innovation Institute -9438AA (base 16) Technology Innovation Institute - Masdar City, Abu Dhabi - Abu Dhabi 9639 - AE - -60-A3-E3 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -60A3E3 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - -64-63-06 (hex) Xiaomi Communications Co Ltd -646306 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN - -6C-9A-B4 (hex) Brodersen A/S -6C9AB4 (base 16) Brodersen A/S - Islevdalvej 187 - Rødovre Hovedstaden 2610 - DK - -E4-5D-39 (hex) Texas Instruments -E45D39 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +0C-42-A1 (hex) Mellanox Technologies, Inc. +0C42A1 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -04-90-C0 (hex) Forvia -0490C0 (base 16) Forvia - Rixbecker Straße 75 - Lippstadt 59552 - DE +08-C0-EB (hex) Mellanox Technologies, Inc. +08C0EB (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US -50-EE-32 (hex) Hon Hai Precision Industry Co.,LTD -50EE32 (base 16) Hon Hai Precision Industry Co.,LTD - 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI CITY,23678 , TAIWAN CHINA - TAIPEI 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI 33859 - CN +9C-05-91 (hex) Mellanox Technologies, Inc. +9C0591 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US -08-3F-21 (hex) Motorola Mobility LLC, a Lenovo Company -083F21 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 +58-A2-E1 (hex) Mellanox Technologies, Inc. +58A2E1 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -98-E7-D5 (hex) NXP Semiconductor (Tianjin) LTD. -98E7D5 (base 16) NXP Semiconductor (Tianjin) LTD. - No.15 Xinghua Avenue, Xiqing Economic Development Area - Tianjin 300385 - CN +B0-CF-0E (hex) Mellanox Technologies, Inc. +B0CF0E (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US -E4-41-D4 (hex) vivo Mobile Communication Co., Ltd. -E441D4 (base 16) vivo Mobile Communication Co., Ltd. +E4-96-52 (hex) vivo Mobile Communication Co., Ltd. +E49652 (base 16) vivo Mobile Communication Co., Ltd. No.1, vivo Road, Chang'an Dongguan Guangdong 523860 CN -64-44-7B (hex) vivo Mobile Communication Co., Ltd. -64447B (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +D4-A5-B4 (hex) Hengji Jiaye (Hangzhou) Technology Co., Ltd +D4A5B4 (base 16) Hengji Jiaye (Hangzhou) Technology Co., Ltd + Room 809, Building B, Yingfeite Technology Park, No. 459 Jianghong Road, Changhe Street, Binjiang District, Hangzhou City, Zhejiang Province + Hangzhou Zhejiang 310000 CN -4C-85-8A (hex) BUFFALO.INC -4C858A (base 16) BUFFALO.INC - AKAMONDORI Bld.,30-20,Ohsu 3-chome,Naka-ku - Nagoya Aichi Pref. 460-8315 - JP - -10-A4-50 (hex) Kwikset -10A450 (base 16) Kwikset - 110 Sargent Dr. - New Haven CT 06511 - US - -C8-48-05 (hex) Nintendo Co.,Ltd -C84805 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - -80-A1-97 (hex) u-blox AG -80A197 (base 16) u-blox AG - Zuercherstrasse, 68 - Thalwil Switzerland CH-8800 - CH - -8C-B6-C5 (hex) Mimosa Networks -8CB6C5 (base 16) Mimosa Networks - 3150 Coronado Dr - Santa Clara CA 95054 +20-4D-52 (hex) Mellanox Technologies, Inc. +204D52 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -84-EA-D2 (hex) KOGANEI CORPORATION -84EAD2 (base 16) KOGANEI CORPORATION - 3-11-28, Midori-cho - Koganei-shi Tokyo 184-8533 - JP - -F0-DB-2A (hex) LANNER ELECTRONICS, INC. -F0DB2A (base 16) LANNER ELECTRONICS, INC. - 7F, No. 173, Section 2, Datong Rd, Xizhi District - New Taipei City 22184 - TW +F0-2C-59 (hex) Chipsea Technologies (Shenzhen) Crop. +F02C59 (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 -D8-0F-B5 (hex) SHENZHEN ULTRAEASY TECHNOLOGY CO LTD -D80FB5 (base 16) SHENZHEN ULTRAEASY TECHNOLOGY CO LTD - 22nd Floor,Block A,Huizhi R&D Center,NO.381 Xixiang Section,Guangshen Highway,Baoan District,Shenzhen City,Guangdong Province - SHENZHEN 518101 +04-4A-69 (hex) Shenzhen Phaten Tech. LTD +044A69 (base 16) Shenzhen Phaten Tech. LTD + C-6 ideamonto industril 7002 Songbai Road Guangming District Shenzhen City Guangdong, China + Shenzhen 518108 CN -EC-FF-3A (hex) Apple, Inc. -ECFF3A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +FC-39-5A (hex) SonicWall +FC395A (base 16) SonicWall + 1033 McCarthy Blvd + Milpitas CA 95035 US -70-13-84 (hex) Apple, Inc. -701384 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +0C-FE-7B (hex) Vantiva USA LLC +0CFE7B (base 16) Vantiva USA LLC + 4855 Peachtree Industrial Blvd, Suite 200 + Norcross GA 30902 US -84-A8-24 (hex) Google, Inc. -84A824 (base 16) Google, Inc. +B0-D5-FB (hex) Google, Inc. +B0D5FB (base 16) Google, Inc. 1600 Amphitheatre Parkway Mountain View CA 94043 US -34-66-91 (hex) Apple, Inc. -346691 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -E0-1C-A7 (hex) Arista Networks -E01CA7 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara 95054 - US - -A8-D3-C8 (hex) Wachendorff Automation GmbH & CO.KG -A8D3C8 (base 16) Wachendorff Automation GmbH & CO.KG - Industriestraße 7 - Geisenheim 65366 - DE +48-D0-1C (hex) AltoBeam Inc. +48D01C (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN -E4-CD-A7 (hex) zte corporation -E4CDA7 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +58-7A-B1 (hex) Shanghai Lixun Information Technology Co., Ltd. +587AB1 (base 16) Shanghai Lixun Information Technology Co., Ltd. + Room 2111-L, No. 89 Yunling East Road + Putuo District Shanghai 200333 CN -48-8C-78 (hex) Alpha Networks Inc. -488C78 (base 16) Alpha Networks Inc. - No. 8, Li-Hsin 7th Rd., Hsinchu Science Park, - Hsinchu Taiwan 300094 - TW +04-B5-C1 (hex) ITEL MOBILE LIMITED +04B5C1 (base 16) ITEL MOBILE LIMITED + RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K + Hong Kong KOWLOON 999077 + HK -90-95-07 (hex) HUAWEI TECHNOLOGIES CO.,LTD -909507 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +28-C9-7A (hex) New H3C Technologies Co., Ltd +28C97A (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -88-63-C5 (hex) HUAWEI TECHNOLOGIES CO.,LTD -8863C5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +C4-9A-89 (hex) Suzhou K-Hiragawa Electronic Technology Co.,Ltd +C49A89 (base 16) Suzhou K-Hiragawa Electronic Technology Co.,Ltd + No.1 Zhipu Road, Qiandeng Town + Suzhou Jiangsu 215341 CN -50-0B-88 (hex) Moxa.Inc -500B88 (base 16) Moxa.Inc - 13F, No. 3, Sec. 4, New Taipei Blvd. Xinzhuang Dist. - New Taipei City 242032 - TW - -24-FB-E3 (hex) HP Inc. -24FBE3 (base 16) HP Inc. - 10300 Energy Dr - Spring TX 77389 +AC-BD-F7 (hex) Cisco Meraki +ACBDF7 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 US -54-0B-B6 (hex) Variscite LTD -540BB6 (base 16) Variscite LTD - 15 Ismargad St - Kiryat Gat 8202041 - IL - -98-9F-1A (hex) Private -989F1A (base 16) Private - -94-0E-2A (hex) NXP Semiconductors Taiwan Ltd. -940E2A (base 16) NXP Semiconductors Taiwan Ltd. - No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan - Nanzi Dist. Kaohsiung 811643 - TW - -F0-74-BF (hex) Silicon Laboratories -F074BF (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US +C8-17-F5 (hex) Nanjing Qinheng Microelectronics Co., Ltd. +C817F5 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. + No.18, Ningshuang Road + Nanjing Jiangsu 210012 + CN -5C-8E-10 (hex) TimeWatch Infocom Pvt. Ltd. -5C8E10 (base 16) TimeWatch Infocom Pvt. Ltd. - D-162, Okhla Indl. Area Phase -1 - New Delhi Delhi 110020 - IN +D8-B2-AA (hex) zte corporation +D8B2AA (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -00-52-45 (hex) GANATECHWIN -005245 (base 16) GANATECHWIN - 32-77, Deulseong-ro 9-gil, Goa-eup - Gumi-si Gyeongsangbuk-do 39146 - KR +90-14-AF (hex) Cambium Networks Limited +9014AF (base 16) Cambium Networks Limited + Unit B2, Linhay Business Park, + Ashburton Devon TQ13 7UP + GB -44-C2-0C (hex) Cisco Systems, Inc -44C20C (base 16) Cisco Systems, Inc +2C-F8-14 (hex) Cisco Systems, Inc +2CF814 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -F8-97-B0 (hex) Goki Pty Ltd -F897B0 (base 16) Goki Pty Ltd - 17 Terrace Road - Dulwich Hill NSW 2203 - AU - -88-94-8E (hex) Max Weishaupt SE -88948E (base 16) Max Weishaupt SE - Max-Weishaupt-Str. 14 - Schwendi 88475 - DE +F4-15-63 (hex) F5 Inc. +F41563 (base 16) F5 Inc. + 1322 North Whitman Lane + Liberty Lake WA 99019 + US -64-7B-40 (hex) Sichuan AI-Link Technology Co., Ltd. -647B40 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 +CC-67-D8 (hex) Telin Semiconductor (Wuhan) Co.,Ltd +CC67D8 (base 16) Telin Semiconductor (Wuhan) Co.,Ltd + Room 1003 Buliding 5 , 2377 Shenkun Road ,Minhang District + Shanghai 201106 CN -2C-2D-48 (hex) Commend International GmbH -2C2D48 (base 16) Commend International GmbH - Saalachstrasse 51 - Salzburg Salzburg A-5020 - AT +64-AC-2B (hex) Juniper Networks +64AC2B (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -18-60-41 (hex) Arcadyan Corporation -186041 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +CC-22-FE (hex) Apple, Inc. +CC22FE (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -A0-6B-4A (hex) TCT mobile ltd -A06B4A (base 16) TCT mobile ltd - No.86 hechang 7th road, zhongkai, Hi-Tech District - Hui Zhou Guang Dong 516006 +34-DA-A1 (hex) Apple, Inc. +34DAA1 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +2C-9D-5A (hex) Flaircomm Microelectronics,Inc. +2C9D5A (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 -14-33-5C (hex) Espressif Inc. -14335C (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +88-DA-04 (hex) HUAWEI TECHNOLOGIES CO.,LTD +88DA04 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -98-88-E0 (hex) Espressif Inc. -9888E0 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +04-74-9E (hex) HUAWEI TECHNOLOGIES CO.,LTD +04749E (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -0C-79-55 (hex) Hui Zhou Gaoshengda Technology Co.,LTD -0C7955 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD - No.2,Jin-da Road,Huinan Industrial Park - Hui Zhou Guangdong 516025 +24-34-08 (hex) Edgecore Americas Networking Corporation +243408 (base 16) Edgecore Americas Networking Corporation + 20 Mason + Irvine CA 92618 + US + +84-D0-DB (hex) Guangdong Juan Intelligent Technology Joint Stock Co., Ltd. +84D0DB (base 16) Guangdong Juan Intelligent Technology Joint Stock Co., Ltd. + The first and second floors of Building 2  (Plant No. 2), West Side of Shanxi Village, Dashi Street,Panyu District, Guangzhou + Guangzhou Guangdong 510000 CN -74-AD-CB (hex) New H3C Technologies Co., Ltd -74ADCB (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +A8-6D-04 (hex) Siemens AG +A86D04 (base 16) Siemens AG + Hermann-Papst-Strasse 1 + St. Georgen 78112 + DE + +4C-46-D1 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +4C46D1 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + 601,Building B2,No.162,Science Avenue,Science City,Guangzhou High-tech Industrial Development Zone,Guangdong Province,China + Guangzhou Guangdong 510663 CN -4C-99-E8 (hex) ZHEJIANG DAHUA TECHNOLOGYCO.,LTD -4C99E8 (base 16) ZHEJIANG DAHUA TECHNOLOGYCO.,LTD - 1st Floor, Building 1, No. 1399, Binxing Road, Changhe Street, Binjiang District - Hangzhou City Zhejiang Province 311200 +DC-70-35 (hex) Shengzhen Gongjin Electronics +DC7035 (base 16) Shengzhen Gongjin Electronics + No. 2 Danzi North Road, Kengzi Street, Pingshan District + Shenzhen Guangdong 518122 CN -D8-19-09 (hex) Wiwynn Technology Service Malaysia -D81909 (base 16) Wiwynn Technology Service Malaysia - No. 5, Jalan Kargo 1, Senai Airport City, 81400 Senai, Johor, Malaysia - Senai Johor 81400 +EC-ED-04 (hex) Intel Corporate +ECED04 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 MY -38-77-CD (hex) KOKUSAI ELECTRIC CORPORATION -3877CD (base 16) KOKUSAI ELECTRIC CORPORATION - 2-1 Yasuuchi, Yatsuo-Machi Toyama - Toyama Japan 939-2393 - JP - -80-84-89 (hex) Intel Corporate -808489 (base 16) Intel Corporate +9C-97-1B (hex) Intel Corporate +9C971B (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -FC-B3-AA (hex) Intel Corporate -FCB3AA (base 16) Intel Corporate +F8-CF-52 (hex) Intel Corporate +F8CF52 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -4C-0F-3E (hex) Intel Corporate -4C0F3E (base 16) Intel Corporate +5C-67-83 (hex) Intel Corporate +5C6783 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -94-C7-A8 (hex) Jiangsu Huitong Group Co.,Ltd. -94C7A8 (base 16) Jiangsu Huitong Group Co.,Ltd. - No. 24, Block 2, Taohuawu New District - Zhenjiang Jiangsu 212003 - CN +44-38-8C (hex) Sumitomo Electric Industries, Ltd +44388C (base 16) Sumitomo Electric Industries, Ltd + 1-1-3, Shimaya, Konohana-ku + Osaka 554-0024 + JP -88-DD-B8 (hex) Huawei Device Co., Ltd. -88DDB8 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +BC-51-5F (hex) Nokia Solutions and Networks India Private Limited +BC515F (base 16) Nokia Solutions and Networks India Private Limited + Plot 45, Fathima NagarNemilicherry,Chrompet + Chennai Taminadu 600044 + IN + +28-D4-1E (hex) Barrot Technology Co.,Ltd. +28D41E (base 16) Barrot Technology Co.,Ltd. + A1009,Block A,Jia Hua Building,No.9 Shangdi 3rd Street,Haidian District,Beijing + beijing beijing 100000 CN -00-9B-08 (hex) Quectel Wireless Solutions Co.,Ltd. -009B08 (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 +D4-9D-9D (hex) Shenzhen Goodocom lnformation Technology Co.,Ltd. +D49D9D (base 16) Shenzhen Goodocom lnformation Technology Co.,Ltd. + 3rd Floor, Xia Valley,Meisheng Huigu Science and Technology Industrial Park, 83 Dabao Road, Baoan District 33, Shenzhen + Shenzhen 518000 CN -BC-DF-E1 (hex) IEEE Registration Authority -BCDFE1 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +7C-7B-BF (hex) Samsung Electronics Co.,Ltd +7C7BBF (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -58-96-0A (hex) LG Electronics -58960A (base 16) LG Electronics - 222 LG-ro, JINWI-MYEON - Pyeongtaek-si Gyeonggi-do 451-713 +8C-2E-72 (hex) Samsung Electronics Co.,Ltd +8C2E72 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 KR -8C-BD-37 (hex) Shenzhen Phaten Tech. LTD -8CBD37 (base 16) Shenzhen Phaten Tech. LTD - C-6 ideamonto industril 7002 Songbai Road Guangming District Shenzhen City Guangdong, China - Shenzhen 518108 +80-F1-B2 (hex) Espressif Inc. +80F1B2 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN -C4-D6-D3 (hex) Dell Inc. -C4D6D3 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US +00-70-07 (hex) Espressif Inc. +007007 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN -30-0A-9D (hex) Axino Solutions AG -300A9D (base 16) Axino Solutions AG - Glutz – Blotzheim – Strasse 1 - Solothurn 4500 - CH +88-12-7D (hex) Shenzhen Melon Electronics Co.,Ltd +88127D (base 16) Shenzhen Melon Electronics Co.,Ltd + 2F, Building E, Digital Silicon Valley, No.89 Hengping Road, Yuanshan Subdistrict, Longgang District, Shenzhen, Guangdong, China + Shenzhen Guangdong 518100 + CN -2C-D8-AE (hex) Shenzhen SEI Robotics Co.,Ltd -2CD8AE (base 16) Shenzhen SEI Robotics Co.,Ltd - 11th Floor, Kangtai Group Building, No. 222 Kefa Road, Science and Technology Park Community, Yuehai Street, Nanshan District, Shenzhen - Shenzhen Guangdong 518000 +44-CB-AD (hex) Xiaomi Communications Co Ltd +44CBAD (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -C4-B7-57 (hex) ALPSALPINE CO,.LTD -C4B757 (base 16) ALPSALPINE CO,.LTD - nishida 6-1 - Kakuda-City Miyagi-Pref 981-1595 +54-4E-F0 (hex) Roku, Inc +544EF0 (base 16) Roku, Inc + 1173 Coleman Ave + San Jose CA 95110 + US + +0C-80-2F (hex) Murata Manufacturing Co., Ltd. +0C802F (base 16) Murata Manufacturing Co., Ltd. + 1-10-1, Higashikotari + Nagaokakyo-shi Kyoto 617-8555 JP -88-F7-15 (hex) Arista Networks -88F715 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 +58-87-85 (hex) Adtran Inc +588785 (base 16) Adtran Inc + 901 Explorer Blvd. + Huntsville AL 35806-2807 US -90-E6-43 (hex) Tesla,Inc. -90E643 (base 16) Tesla,Inc. - 3500 Deer Creek Rd. - PALO ALTO CA 94304 +EC-9E-EA (hex) Xtra Technology LLC +EC9EEA (base 16) Xtra Technology LLC + 3422 Old Capitol Trail, Suite 700 + WiImington 19808-6124 US -40-B3-FA (hex) Apple, Inc. -40B3FA (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +10-65-19 (hex) Shenzhen iComm Semiconductor CO.,LTD +106519 (base 16) Shenzhen iComm Semiconductor CO.,LTD + Room204,scientific research building,Tsinghua Hi-Tech Park,No.13 Langshan Road,Nanshan District + Shenzhen Guangdong 518067 + CN -9C-DA-36 (hex) TECNO MOBILE LIMITED -9CDA36 (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 - Hong Kong Hong Kong 999077 +18-EB-D4 (hex) Shenzhen Skyworth Digital Technology CO., Ltd +18EBD4 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + +E4-B7-31 (hex) Hangzhou Advance IOT Connectivity System Co., Ltd. +E4B731 (base 16) Hangzhou Advance IOT Connectivity System Co., Ltd. + Room 406, building 1, No. 4280, South Ring Road, Puyan street, Binjiang District + Hangzhou Zhejiang 310051 + CN + +FC-38-82 (hex) Infinix mobility limited +FC3882 (base 16) Infinix mobility limited + RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG + HongKong HongKong 999077 HK -24-42-E3 (hex) Shenzhen Ai-Thinker Technology Co.,Ltd -2442E3 (base 16) Shenzhen Ai-Thinker Technology Co.,Ltd - 410, Block C, Huafeng Smart Innovation Port, Gushu 2nd Road, GushuCommunity, Xixiang Street, Baoan District, Shenzhen, China - Shenzhen 518100 +48-A4-8C (hex) Shanghai Zenchant Electornics Co.,LTD +48A48C (base 16) Shanghai Zenchant Electornics Co.,LTD + Room 1202, building a, Noble international, 908 Xiuwen Road, Minhang District + ShangHai ShangHai 201199 CN -8C-BE-6F (hex) Tianyi Telecom Terminals Company Limited -8CBE6F (base 16) Tianyi Telecom Terminals Company Limited - 12-19 floors inside 101, Floor 4-33, Building 1, Yard 1, Fenghuang Zui Street, Fengtai District, Beijing - Beijing 100073 +C4-DB-AD (hex) Ring LLC +C4DBAD (base 16) Ring LLC + 12515 Cerise Ave + Hawthorne CA 12515 + US + +94-FF-7D (hex) AltoBeam Inc. +94FF7D (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -D0-C1-BF (hex) Xiaomi Communications Co Ltd -D0C1BF (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +00-4B-0D (hex) Huawei Device Co., Ltd. +004B0D (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -A8-2B-D5 (hex) Xiaomi Communications Co Ltd -A82BD5 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +E0-40-27 (hex) Huawei Device Co., Ltd. +E04027 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -C0-C9-89 (hex) Edgecore Americas Networking Corporation -C0C989 (base 16) Edgecore Americas Networking Corporation - 20 Mason - Irvine CA 92618 - US +40-1C-D4 (hex) Huawei Device Co., Ltd. +401CD4 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -04-00-6E (hex) Google, Inc. -04006E (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US +D4-7B-6B (hex) Shanghai Cygnus Semiconductor Co., Ltd. +D47B6B (base 16) Shanghai Cygnus Semiconductor Co., Ltd. + Rooms 401 and 402, Building 5, 690 Bibo Road, China (Shanghai) Pilot Free Trade Zone + Shanghai Shanghai 201203 + CN -BC-D7-D4 (hex) Roku, Inc -BCD7D4 (base 16) Roku, Inc - 1173 Coleman Ave - San Jose CA 95110 +00-02-71 (hex) Zhone Technologies, Inc. +000271 (base 16) Zhone Technologies, Inc. + 7001 Oakport Street + Oakland CA 94621 US -F8-14-DD (hex) Cisco Systems, Inc -F814DD (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +00-01-47 (hex) Zhone Technologies, Inc. +000147 (base 16) Zhone Technologies, Inc. + 7001 Oakport Street + Oakland CA 94621 US -0C-82-47 (hex) CIG SHANGHAI CO LTD -0C8247 (base 16) CIG SHANGHAI CO LTD - 5th Floor, Building 8 No 2388 Chenhang Road - SHANGHAI 201114 - CN +D0-96-FB (hex) Zhone Technologies, Inc. +D096FB (base 16) Zhone Technologies, Inc. + DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu + Seongnam-si Gyeonggi-do 13493 + KR -78-ED-25 (hex) New H3C Technologies Co., Ltd -78ED25 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +30-4F-75 (hex) Zhone Technologies, Inc. +304F75 (base 16) Zhone Technologies, Inc. + DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu + Seongnam-si Gyeonggi-do 13493 + KR + +7C-C5-18 (hex) vivo Mobile Communication Co., Ltd. +7CC518 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN -44-76-09 (hex) New H3C Technologies Co., Ltd -447609 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +7C-7D-21 (hex) zte corporation +7C7D21 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -04-C8-45 (hex) TP-Link Systems Inc. -04C845 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US +A8-9A-8C (hex) zte corporation +A89A8C (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -B0-C6-1C (hex) HUAWEI TECHNOLOGIES CO.,LTD -B0C61C (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +F8-83-06 (hex) Beijing Xiaomi Mobile Software Co., Ltd +F88306 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -44-BE-0B (hex) HUAWEI TECHNOLOGIES CO.,LTD -44BE0B (base 16) HUAWEI TECHNOLOGIES CO.,LTD +98-61-10 (hex) HUAWEI TECHNOLOGIES CO.,LTD +986110 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -7C-99-46 (hex) Sector Alarm Tech S.L. -7C9946 (base 16) Sector Alarm Tech S.L. - Calle Verónica, Esq. Calle Tulipán, Esq Calle Geranio - Las Lagunas- Mijas Malaga 29651 - ES +F4-85-AE (hex) Senbiosys SA +F485AE (base 16) Senbiosys SA + Route des Gouttes-d'Or 40 + Neuchâtel 2000 + CH -5C-5E-BB (hex) HUAWEI TECHNOLOGIES CO.,LTD -5C5EBB (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +60-55-56 (hex) Jiangxi Risound Electronics Co.,LTD +605556 (base 16) Jiangxi Risound Electronics Co.,LTD + No 271,innovation Avenue, Jinggangshan economic and Technological Development Zone + Ji'an Jiangxi 343100 CN -40-25-08 (hex) Highway 9 Networks, Inc. -402508 (base 16) Highway 9 Networks, Inc. - 2350 Mission College BlvdSte 490 - Santa Clara 95054 +AC-82-F0 (hex) Apple, Inc. +AC82F0 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -04-A1-6F (hex) IEEE Registration Authority -04A16F (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +0C-CC-5D (hex) Apple, Inc. +0CCC5D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -14-39-2F (hex) LEAR -14392F (base 16) LEAR - Carrer Fuster 54 - Valls Tarragona 43800 - ES +34-0E-22 (hex) Apple, Inc. +340E22 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -8C-3F-44 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -8C3F44 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN +68-4A-5F (hex) Apple, Inc. +684A5F (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -34-97-D7 (hex) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. -3497D7 (base 16) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. - 309, 3th Floor, No.16, Yun Ding North Road, Huli District - xiamen Fujian 361015 - CN +E8-98-EE (hex) Apple, Inc. +E898EE (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -68-F6-2B (hex) ITEL MOBILE LIMITED -68F62B (base 16) ITEL MOBILE LIMITED - RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K - Hong Kong KOWLOON 999077 - HK +34-28-65 (hex) Juniper Networks +342865 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -28-43-DC (hex) United Memory Technology (Jiangsu) Limited -2843DC (base 16) United Memory Technology (Jiangsu) Limited - C5-401-03,Fuli Center,Xinyuan Road,Wuxi Economic Development Zone - Wuxi JiangSu 214000 - CN +D8-1D-13 (hex) Texas Instruments +D81D13 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -98-12-E0 (hex) Xiaomi Communications Co Ltd -9812E0 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +10-5A-95 (hex) TP-Link Systems Inc. +105A95 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US -64-DD-68 (hex) Zyxel Communications Corporation -64DD68 (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW +F8-20-97 (hex) Aditya Infotech Ltd. +F82097 (base 16) Aditya Infotech Ltd. + Khemka Square, A-12, Sector-4, + Noida Uttar Pradesh 201301 + IN -38-77-07 (hex) AltoBeam Inc. -387707 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN +5C-7B-6C (hex) Tradit Co., Ltd +5C7B6C (base 16) Tradit Co., Ltd + #617, 416 Hwagok-ro, Gangseo-gu + Seoul 07548 + KR -74-CA-60 (hex) Sonos, Inc. -74CA60 (base 16) Sonos, Inc. - 301 Coromar Dr - Goleta CA 93117 +A0-7E-16 (hex) EM Microelectronic +A07E16 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +CC-5E-A5 (hex) Palo Alto Networks +CC5EA5 (base 16) Palo Alto Networks + 3000 Tannery Way + Santa Clara CA 95054 US -38-42-0B (hex) Sonos, Inc. -38420B (base 16) Sonos, Inc. - 301 Coromar Dr - Goleta CA 93117 +14-75-E5 (hex) ELMAX Srl +1475E5 (base 16) ELMAX Srl + Via dei Parietai, 2 + Molfetta BA 70056 + IT + +E0-23-3B (hex) IEEE Registration Authority +E0233B (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -A4-4B-D9 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -A44BD9 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN +88-33-74 (hex) ASKEY COMPUTER CORP +883374 (base 16) ASKEY COMPUTER CORP + 10F,No.119,JIANKANG RD,ZHONGHE DIST + NEW TAIPEI TAIWAN 23585 + TW -EC-1B-FA (hex) EM Microelectronic -EC1BFA (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +E4-56-CA (hex) Fractal BMS +E456CA (base 16) Fractal BMS + 8656 W Hwy 71 + AUSTIN TX 78735 + US -80-E4-BA (hex) Intel Corporate -80E4BA (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +48-08-EB (hex) IEEE Registration Authority +4808EB (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -4C-D0-F9 (hex) Cisco Systems, Inc -4CD0F9 (base 16) Cisco Systems, Inc +B8-FE-90 (hex) Cisco Systems, Inc +B8FE90 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -A8-79-71 (hex) HUAWEI TECHNOLOGIES CO.,LTD -A87971 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +34-C3-FD (hex) Cisco Systems, Inc +34C3FD (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -60-DE-94 (hex) HUAWEI TECHNOLOGIES CO.,LTD -60DE94 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +64-84-34 (hex) BEMER Int. AG +648434 (base 16) BEMER Int. AG + Austrasse 15 + Triesen 9495 + LI -48-F7-BC (hex) HUAWEI TECHNOLOGIES CO.,LTD -48F7BC (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +F0-44-D3 (hex) Silicon Laboratories +F044D3 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -1C-32-AC (hex) HUAWEI TECHNOLOGIES CO.,LTD -1C32AC (base 16) HUAWEI TECHNOLOGIES CO.,LTD +08-FD-58 (hex) HUAWEI TECHNOLOGIES CO.,LTD +08FD58 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -18-33-86 (hex) HUAWEI TECHNOLOGIES CO.,LTD -183386 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +B4-43-89 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B44389 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -30-E2-71 (hex) Fsas Technologies Inc. -30E271 (base 16) Fsas Technologies Inc. - JR Kawasaki Tower, 1-5 Omiya-cho, Saiwai-ku - Kawasaki City Kanagawa Prefecture 212-0014 - JP - -98-A3-16 (hex) Espressif Inc. -98A316 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -A4-7A-72 (hex) Arista Networks -A47A72 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 - US - -F8-0D-4B (hex) Nextracker, Inc. -F80D4B (base 16) Nextracker, Inc. - 6200 Paseo Padre Pkwy - Fremont CA 94555 - US - -20-1B-A5 (hex) Vizio, Inc -201BA5 (base 16) Vizio, Inc - 39 Tesla - Irvine CA 92618 +6C-47-25 (hex) Rochester Network Supply, Inc. +6C4725 (base 16) Rochester Network Supply, Inc. + 1319 Research Forest, + Macedon NY 14502 US -A4-B0-39 (hex) Shenzhen iComm Semiconductor CO.,LTD -A4B039 (base 16) Shenzhen iComm Semiconductor CO.,LTD - Room204,scientific research building,Tsinghua Hi-Tech Park,No.13 Langshan Road,Nanshan District - Shenzhen Guangdong 518067 +80-49-BF (hex) Taicang T&W Electronics +8049BF (base 16) Taicang T&W Electronics + 89# Jiang Nan RD + Suzhou Jiangsu 215412 CN -88-DE-39 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -88DE39 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 - CN +6C-D8-FB (hex) Qorvo Utrecht B.V. +6CD8FB (base 16) Qorvo Utrecht B.V. + Leidseveer 10 + Utrecht Utrecht 3511 SB + NL -C0-E3-50 (hex) CHINA DRAGON TECHNOLOGY LIMITED -C0E350 (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 +B0-97-E6 (hex) FUJIAN FUCAN WECON CO LTD +B097E6 (base 16) FUJIAN FUCAN WECON CO LTD + Wecon Tech Park, No.58 Jiangbin East Avenue, Mawei, Fuzhou, China + FUZHOU FUJIAN 350015 CN -F8-2A-53 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -F82A53 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. +B4-5C-B5 (hex) Mellanox Technologies, Inc. +B45CB5 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +BC-89-F8 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +BC89F8 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Midea Global Innovation Center,Beijiao Town,Shunde Foshan Guangdong 528311 CN -C8-61-D0 (hex) SHEN ZHEN KTC TECHNOLOGY.,LTD. -C861D0 (base 16) SHEN ZHEN KTC TECHNOLOGY.,LTD. - NO 4023,Wuhe Road,Bantian,Longgang district,Shenzhen,Guangdong,China - SHEN ZHEN GUANG DONG 518100 - CN - -58-23-9B (hex) Fiberhome Telecommunication Technologies Co.,LTD -58239B (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +80-57-22 (hex) Wuxi Sunning Smart Devices Co., Ltd +805722 (base 16) Wuxi Sunning Smart Devices Co., Ltd + No.52, Zone C, Huigu Pioneer Park, Huishan Economic Development Zone, Wuxi + Wuxi jiangsu 214174 CN -3C-0B-59 (hex) Tuya Smart Inc. -3C0B59 (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 - US - -00-05-5B (hex) Charles Industries -00055B (base 16) Charles Industries - 5600 Apollo Dr. - Rolling Meadows IL 60008 - US - -20-36-26 (hex) TP-Link Systems Inc -203626 (base 16) TP-Link Systems Inc - Room 901,9/F.New East Ocean Centre, 9 Science Museum Road - Tsim Sha Tsui Kowloon 999077 - HK - -4C-DE-48 (hex) Huawei Device Co., Ltd. -4CDE48 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +F8-EF-B1 (hex) Hangzhou Zhongxinghui Intelligent Technology Co., Ltd. +F8EFB1 (base 16) Hangzhou Zhongxinghui Intelligent Technology Co., Ltd. + Room 809, Building B, No. 567 Yueming Road, Xixing Street, + Hangzhou Binjiang Distric 310000 CN -98-E5-5B (hex) Intelbras -98E55B (base 16) Intelbras - BR 101, km 210, S/N° - São José Santa Catarina 88104800 - BR +C0-2E-5F (hex) Zyxel Communications Corporation +C02E5F (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW -F8-2B-E6 (hex) IEEE Registration Authority -F82BE6 (base 16) IEEE Registration Authority +24-A1-0D (hex) IEEE Registration Authority +24A10D (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -E4-A4-30 (hex) Samsung Electronics Co.,Ltd -E4A430 (base 16) Samsung Electronics Co.,Ltd - 129, Samsung-ro, Youngtongl-Gu - Suwon Gyeonggi-Do 16677 - KR - -18-3D-2D (hex) LCFC(Hefei) Electronics Technology co., ltd -183D2D (base 16) LCFC(Hefei) Electronics Technology co., ltd - No. 3188-1 Yungu Road (Comprehensive Bonded Zone), Hefei Economic & Technological Development Area,Anhui - HEFEI ANHUI 230601 - CN +00-27-13 (hex) Universal Global Scientific Industrial., Ltd +002713 (base 16) Universal Global Scientific Industrial., Ltd + 141, LANE 351,SEC.1, TAIPING RD. + TSAOTUEN, NANTOU 54261 + TW -00-62-01 (hex) Motorola Mobility LLC, a Lenovo Company -006201 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +CC-52-AF (hex) Universal Global Scientific Industrial., Ltd +CC52AF (base 16) Universal Global Scientific Industrial., Ltd + 141, LANE 351, TAIPING RD. + nan tou NAN-TOU 542 + TW -08-CE-94 (hex) EM Microelectronic -08CE94 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +FC-4D-D4 (hex) Universal Global Scientific Industrial., Ltd +FC4DD4 (base 16) Universal Global Scientific Industrial., Ltd + 141, Lane 351, Taiping Rd. Sec. 1, Tsao Tuen, + Nan-Tou Hsien, 542 + TW -C0-BA-1F (hex) Private -C0BA1F (base 16) Private +08-3A-88 (hex) Universal Global Scientific Industrial., Ltd +083A88 (base 16) Universal Global Scientific Industrial., Ltd + 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen + Nan-Tou Taiwan 54261 + TW -78-E1-67 (hex) Launch Tech Co., Ltd. -78E167 (base 16) Launch Tech Co., Ltd. - Launch Industrial Park, No.4012, North of Wuhe Road, Bantian Street, Longgang District, - Shenzhen Guangdong 518100 +F8-F2-F0 (hex) Chipsea Technologies (Shenzhen) Crop. +F8F2F0 (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 -B0-A7-B9 (hex) TP-Link Systems Inc -B0A7B9 (base 16) TP-Link Systems Inc - 5 Peters Canyon Rd Suit 300 - Irvine CA 92606 - HK - -6C-5A-B0 (hex) TP-Link Systems Inc -6C5AB0 (base 16) TP-Link Systems Inc - 5 Peters Canyon Rd Suit 300 - Irvine CA 92606 - HK - -5C-62-8B (hex) TP-Link Systems Inc -5C628B (base 16) TP-Link Systems Inc - 5 Peters Canyon Rd Suit 300 - Irvine CA 92606 - HK - -AC-15-A2 (hex) TP-Link Systems Inc -AC15A2 (base 16) TP-Link Systems Inc - 5 Peters Canyon Rd Suit 300 - Irvine CA 92606 - HK - -18-9C-E1 (hex) Arista Networks -189CE1 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 - US - -8C-E9-FF (hex) Dell Inc. -8CE9FF (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 - US - -10-4E-20 (hex) HSE SMART -104E20 (base 16) HSE SMART - 14453 Hillcroft Street, Suite - Houston TX 77085 +E4-2F-37 (hex) Apple, Inc. +E42F37 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -88-25-08 (hex) Meta Platforms, Inc. -882508 (base 16) Meta Platforms, Inc. - 1601 Willow Rd - Menlo Park CA 94025 - US +A4-93-AD (hex) Huawei Device Co., Ltd. +A493AD (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -94-F9-29 (hex) Meta Platforms, Inc. -94F929 (base 16) Meta Platforms, Inc. - 1601 Willow Rd - Menlo Park CA 94025 - US +2C-3A-B1 (hex) Huawei Device Co., Ltd. +2C3AB1 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -D4-D6-59 (hex) Meta Platforms, Inc. -D4D659 (base 16) Meta Platforms, Inc. - 1601 Willow Rd - Menlo Park CA 94025 +20-F1-B2 (hex) Tuya Smart Inc. +20F1B2 (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 US -78-55-36 (hex) shenzhen AZW Technology(Group) Co.,Ltd -785536 (base 16) shenzhen AZW Technology(Group) Co.,Ltd - 7th floor, Block C, Building 8, Hengda Shishang Huigu, Fulong Road, Dalang Street, Longhua District - shenzhen 518000 +A8-C0-50 (hex) Quectel Wireless Solutions Co.,Ltd. +A8C050 (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN -44-1D-64 (hex) Espressif Inc. -441D64 (base 16) Espressif Inc. +1C-C3-AB (hex) Espressif Inc. +1CC3AB (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN -F0-09-0D (hex) TP-Link Systems Inc -F0090D (base 16) TP-Link Systems Inc - 5 Peters Canyon Rd Suit 300 - Irvine CA 92606 +58-04-4F (hex) TP-Link Systems Inc. +58044F (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US -E8-48-B8 (hex) TP-Link Systems Inc -E848B8 (base 16) TP-Link Systems Inc - 5 Peters Canyon Rd Suit 300 - Irvine CA 92606 - US +B4-B8-53 (hex) Honor Device Co., Ltd. +B4B853 (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 -8C-50-1A (hex) Private -8C501A (base 16) Private +E0-4F-43 (hex) Universal Global Scientific Industrial., Ltd +E04F43 (base 16) Universal Global Scientific Industrial., Ltd + 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen + Nan-Tou Taiwan 54261 + TW -14-D8-81 (hex) Beijing Xiaomi Mobile Software Co., Ltd -14D881 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +08-3B-C1 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +083BC1 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 CN -AC-91-5D (hex) Digital Control Technology Limited -AC915D (base 16) Digital Control Technology Limited - Unit 1805-06, 18/F, Hollywood Plaza, 610 Nathan Road, Mongkok, Kowloon, - Hong Kong Hong Kong 00000 - HK +64-BD-6D (hex) Apple, Inc. +64BD6D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -04-35-9B (hex) WuLu Networks Pty Ltd -04359B (base 16) WuLu Networks Pty Ltd - 135 Coal Point Road - Coal Point NSW 2283 - AU +E8-C3-86 (hex) Apple, Inc. +E8C386 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -C0-43-27 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -C04327 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 - CN +2C-DC-AD (hex) WNC Corporation +2CDCAD (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW -0C-A9-4A (hex) Shenzhen Skyworth Digital Technology CO., Ltd -0CA94A (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 - CN +B8-B7-F1 (hex) WNC Corporation +B8B7F1 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW -BC-AD-AE (hex) AltoBeam Inc. -BCADAE (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN +44-E4-EE (hex) WNC Corporation +44E4EE (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW -1C-FF-AD (hex) HUAWEI TECHNOLOGIES CO.,LTD -1CFFAD (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +2C-9F-FB (hex) WNC Corporation +2C9FFB (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW -40-89-C6 (hex) Amazon Technologies Inc. -4089C6 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +1C-D6-BE (hex) WNC Corporation +1CD6BE (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW -64-9D-38 (hex) Google, Inc. -649D38 (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US +70-61-BE (hex) WNC Corporation +7061BE (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW -40-58-46 (hex) vivo Mobile Communication Co., Ltd. -405846 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN +60-E6-F0 (hex) WNC Corporation +60E6F0 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW -D0-B2-70 (hex) Visteon Portuguesa, Ltd -D0B270 (base 16) Visteon Portuguesa, Ltd - Estrada Nacional 252-km12 - Palmela 2951-503 - PT +80-EA-23 (hex) WNC Corporation +80EA23 (base 16) WNC Corporation + 20 Park Avenue II, Hsin Science Park, Hsinchu 308, Taiwan + HsinChu Taiwan 308 + TW -94-A4-CE (hex) Sensear Pty Ltd -94A4CE (base 16) Sensear Pty Ltd - 4 Hehir Street - Belmont Western Australia 6104 - AU +48-A9-D2 (hex) WNC Corporation +48A9D2 (base 16) WNC Corporation + 20 Park Avenue II, Hsin Science Park, Hsinchu 308, Taiwan + HsinChu Taiwan 308 + TW -A0-99-21 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -A09921 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN +BC-30-7D (hex) WNC Corporation +BC307D (base 16) WNC Corporation + 20 Park Avenue II, Hsin Science Park, Hsinchu 308, Taiwan + HsinChu Taiwan 308 + TW -8C-8A-CD (hex) HUAWEI TECHNOLOGIES CO.,LTD -8C8ACD (base 16) HUAWEI TECHNOLOGIES CO.,LTD +BC-30-7E (hex) WNC Corporation +BC307E (base 16) WNC Corporation + 20 Park Avenue II. + Hsinchu 30808854 + TW + +00-1B-B1 (hex) WNC Corporation +001BB1 (base 16) WNC Corporation + No. 10-1, Li-hsin Road I, Hsinchu Science Park, + Hsinchu 300 + TW + +E4-8E-C5 (hex) HUAWEI TECHNOLOGIES CO.,LTD +E48EC5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -1C-FC-2A (hex) HUAWEI TECHNOLOGIES CO.,LTD -1CFC2A (base 16) HUAWEI TECHNOLOGIES CO.,LTD +4C-E6-5E (hex) HUAWEI TECHNOLOGIES CO.,LTD +4CE65E (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -EC-2F-90 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -EC2F90 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN +A4-61-85 (hex) Tools for Humanity Corporation +A46185 (base 16) Tools for Humanity Corporation + 650 7th St + San Francisco CA 94103 + US -A8-88-CE (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -A888CE (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN +8C-57-9B (hex) WNC Corporation +8C579B (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW -4C-0A-4E (hex) Extreme Networks Headquarters -4C0A4E (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 +7C-A2-36 (hex) Verizon Connect +7CA236 (base 16) Verizon Connect + 5055 North Point Pkwy + Alpharetta GA 30022 US -BC-7E-C3 (hex) Zyxel Communications Corporation -BC7EC3 (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW +88-A6-8D (hex) Shanghai MXCHIP Information Technology Co., Ltd. +88A68D (base 16) Shanghai MXCHIP Information Technology Co., Ltd. + 9th Floor, No. 5 Building, 2145 Jinshajiang Rd., Putuo District + Shanghai 200333 + CN -78-20-51 (hex) TP-Link Systems Inc. -782051 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 +D4-4F-14 (hex) Tesla,Inc. +D44F14 (base 16) Tesla,Inc. + 3500 Deer Creek Rd. + PALO ALTO CA 94304 US -2C-DA-46 (hex) Samsung Electronics Co.,Ltd -2CDA46 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +34-26-E6 (hex) CIG SHANGHAI CO LTD +3426E6 (base 16) CIG SHANGHAI CO LTD + 5th Floor, Building 8 No 2388 Chenhang Road + SHANGHAI 201114 + CN -48-EF-1C (hex) Samsung Electronics Co.,Ltd -48EF1C (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +38-B8-00 (hex) WNC Corporation +38B800 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW -EC-B5-50 (hex) Samsung Electronics Co.,Ltd -ECB550 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +40-1A-58 (hex) WNC Corporation +401A58 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW -E4-43-89 (hex) Apple, Inc. -E44389 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +28-2E-89 (hex) WNC Corporation +282E89 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW -3C-CD-40 (hex) Apple, Inc. -3CCD40 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +38-8D-3D (hex) WNC Corporation +388D3D (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW -00-97-F1 (hex) Apple, Inc. -0097F1 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +8C-8B-5B (hex) WNC Corporation +8C8B5B (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +68-EF-A8 (hex) AutomationDirect.com +68EFA8 (base 16) AutomationDirect.com + 3505 Hutchinson Road + Cumming GA 30040 US -70-21-7F (hex) Xiaomi Communications Co Ltd -70217F (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +C0-6E-3D (hex) SHENZHEN TECNO TECHNOLOGY +C06E3D (base 16) SHENZHEN TECNO TECHNOLOGY + 101,Building 24,Waijing Industrial Park,Fumin Community,Fucheng Street,Longhua District,Shenzhen City,P.R.China + Shenzhen guangdong 518000 CN -B8-4F-A7 (hex) Apple, Inc. -B84FA7 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +0C-74-74 (hex) Fiberhome Telecommunication Technologies Co.,LTD +0C7474 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN -F4-81-C4 (hex) Apple, Inc. -F481C4 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +94-27-0E (hex) Intel Corporate +94270E (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -30-FC-8C (hex) Vantiva - Connected Home -30FC8C (base 16) Vantiva - Connected Home - 4855 Peachtree Industrial Blvd, Suite 200 - Norcross GA 30902 - US +7C-01-3E (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED +7C013E (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 -8C-ED-E1 (hex) Ubiquiti Inc -8CEDE1 (base 16) Ubiquiti Inc +10-83-21 (hex) Yichip Microelectronics (Hangzhou) Co.,Ltd +108321 (base 16) Yichip Microelectronics (Hangzhou) Co.,Ltd + Room 401, Building 15, No.498 Guoshoujing Road, Pudong Software Park + Shanghai 200120 + CN + +18-CC-88 (hex) Hitachi Global Life Solutions, Inc. +18CC88 (base 16) Hitachi Global Life Solutions, Inc. + 390, Muramatsu, + Shimizu Village Shizuoka Prefecture 424-0926 + JP + +54-96-CB (hex) AMPAK Technology Inc. +5496CB (base 16) AMPAK Technology Inc. + 6F., No23, Huanke 1st Rd. + Zhubei City Hsinchu County 302047 + TW + +90-41-B2 (hex) Ubiquiti Inc +9041B2 (base 16) Ubiquiti Inc 685 Third Avenue, 27th Floor New York NY New York NY 10017 US -24-F6-8D (hex) Nokia -24F68D (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA - -34-6F-11 (hex) Qingdao Zhipai Information Technology Co., Ltd. -346F11 (base 16) Qingdao Zhipai Information Technology Co., Ltd. - Room 1401, No. 15, Miaoling Road, Laoshan District, Qingdao, Shandong, China - Qingdao 266100 - CN - -A8-E6-E8 (hex) Tonly Technology Co. Ltd -A8E6E8 (base 16) Tonly Technology Co. Ltd - Section 37, Zhongkai Hi-Tech Development Zone - Huizhou Guangdong 516006 - CN +D0-17-B7 (hex) Atios AG +D017B7 (base 16) Atios AG + Obere Postmatte 19 + Seedorf Uri 6462 + CH -9C-89-1E (hex) FireBrick Ltd -9C891E (base 16) FireBrick Ltd - c/o Andrews & Arnold Ltd Enterprise Court Downmill Road - Bracknell Berkshire RG12 1QS - GB +F0-D3-2B (hex) Juniper Networks +F0D32B (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US -B4-EF-30 (hex) Shanghai SYH Technology CO.,LTD -B4EF30 (base 16) Shanghai SYH Technology CO.,LTD - Building 4, No. 686 Nanfeng Road,Fengcheng Town, Fengxian District - Shanghai 201400 - CN +00-1E-D1 (hex) TKH Security B.V. +001ED1 (base 16) TKH Security B.V. + Werner von Siemensstraat 7 + Zoetermeer ZH 2712PN + NL -30-52-53 (hex) BuildJet, Inc. -305253 (base 16) BuildJet, Inc. - 8 The Green, Suite# 13184 - Dover DE 19901 - US +18-FD-00 (hex) Marelli +18FD00 (base 16) Marelli + Avenida da Emancipação, 801 + Hostolândia São Paulo 13184-9074 + BR -94-FF-34 (hex) HANSHOW TECHNOLOGY CO.,LTD. -94FF34 (base 16) HANSHOW TECHNOLOGY CO.,LTD. - The 1st Floor Podium and Floor 4 of Building 1, Floor 7 of Building 5, JiaxingPhotovoltaic Technology Innovation Park, No.1288, Kanghe Road, Xiuzhou District,Jiaxing City, Zhejiang Prov,P.R.China - JIAXING 314000 +A4-C1-39 (hex) Dongguan Huayin Electronic Technology Co., Ltd. +A4C139 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. + Room 101, No.8 Xinglong 3rd Road, Shipai Town + Dongguan Guangdong 523000 CN -A0-4F-E4 (hex) PAX Computer Technology(Shenzhen) Ltd. -A04FE4 (base 16) PAX Computer Technology(Shenzhen) Ltd. - 4/F, No.3 Building, Software Park, Second Central Science-Tech Road, High-Tech - Shenzhen GuangDong 518057 +60-D8-77 (hex) Phyplus Technology (Shanghai) Co., Ltd +60D877 (base 16) Phyplus Technology (Shanghai) Co., Ltd + 3th Floor, Building 23, 676 Wuxing Road, Pudong New District, Shanghai + Shanghai Shanghai 201204 CN -3C-B8-D6 (hex) Bluebank Communication Technology Co.,Ltd. -3CB8D6 (base 16) Bluebank Communication Technology Co.,Ltd. - No. 6, Chanyi Road, - Yubei District Chongqing 401120 +84-FC-14 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +84FC14 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 CN -B4-5B-D1 (hex) TP-Link Systems Inc. -B45BD1 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 +54-39-76 (hex) Groq +543976 (base 16) Groq + 2700 Zanker Road, Suite 150 + San Jose CA 95134 US -80-3C-04 (hex) TP-Link Systems Inc. -803C04 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 +18-96-77 (hex) Annapurna labs +189677 (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +78-5F-A4 (hex) Ciena Corporation +785FA4 (base 16) Ciena Corporation + 7035 Ridge Road + Hanover MD 21076 US -2C-7A-F4 (hex) IEEE Registration Authority -2C7AF4 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +98-11-CC (hex) Ciena Corporation +9811CC (base 16) Ciena Corporation + 7035 Ridge Road + Hanover MD 21076 US -C8-C4-32 (hex) SG Armaturen AS -C8C432 (base 16) SG Armaturen AS - Skytterheia 25, N-4790 - Lillesand 4790 - NO +90-FB-93 (hex) Renesas Design US Inc. +90FB93 (base 16) Renesas Design US Inc. + 6024 Silver Creek valley Road San Jose, CA 95138 United States + San Jose 95138 + US -8C-BA-FC (hex) JOYNEXT GmbH -8CBAFC (base 16) JOYNEXT GmbH - Gewerbepark Merbitz 5 - Dresden Saxony 01156 - DE +6C-A0-42 (hex) Silicon Laboratories +6CA042 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -18-97-F1 (hex) KOSTAL (Shanghai) Management Co., Ltd. -1897F1 (base 16) KOSTAL (Shanghai) Management Co., Ltd. - No. 189, Xingping Road, Jiading District, Shanghai, P.R.China - Shanghai Shanghai 201822 +D8-3A-36 (hex) AltoBeam Inc. +D83A36 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -F4-F5-0B (hex) TP-Link Systems Inc. -F4F50B (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US +F0-55-82 (hex) Arashi Vision Inc. +F05582 (base 16) Arashi Vision Inc. + Room 1101, 1102, 1103, 11th Floor, Building 2, Jinlitong Financial Center, 1100 Xingye Road, Haiwang Community, Xin'an Street, Bao'an District, Shenzhen, Guangdong, China + Shenzhen 518000 + CN -04-0F-66 (hex) TP-Link Systems Inc. -040F66 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US +6C-76-F7 (hex) MainStreaming SpA +6C76F7 (base 16) MainStreaming SpA + Viale Sarca, 336/F + Milan MI 20126 + IT -A4-D5-30 (hex) Avaya LLC -A4D530 (base 16) Avaya LLC - 350 Mt Kimble - Morristown NJ 07960 +F0-BC-50 (hex) Mellanox Technologies, Inc. +F0BC50 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -34-56-FE (hex) Cisco Meraki -3456FE (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 +40-A4-4A (hex) Google, Inc. +40A44A (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US -4C-EF-56 (hex) Shenzhen Sundray Technologies company Limited -4CEF56 (base 16) Shenzhen Sundray Technologies company Limited - 5th Floor, Block A4, Nanshan ipark,NO.1001 Xue Yuan Road, Nanshan District, Shenzhen 518055, P.R. China - Shenzhen Guangdong 518057 - CN - -94-14-57 (hex) Shenzhen Sundray Technologies company Limited -941457 (base 16) Shenzhen Sundray Technologies company Limited - 5th Floor, Block A4, Nanshan ipark,NO.1001 Xue Yuan Road, Nanshan District, Shenzhen 518055, P.R. China - Shenzhen Guangdong 518057 - CN - -AC-FC-82 (hex) Shenzhen Sundray Technologies company Limited -ACFC82 (base 16) Shenzhen Sundray Technologies company Limited - 5th Floor, Block A4, Nanshan ipark,NO.1001 Xue Yuan Road, Nanshan District, Shenzhen 518055, P.R. China - Shenzhen Guangdong 518057 +0C-F2-F5 (hex) Sichuan AI-Link Technology Co., Ltd. +0CF2F5 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 CN -C8-A2-3B (hex) Shenzhen Sundray Technologies company Limited -C8A23B (base 16) Shenzhen Sundray Technologies company Limited - 5th Floor, Block A4, Nanshan ipark,NO.1001 Xue Yuan Road, Nanshan District, Shenzhen 518055, P.R. China +D0-45-8D (hex) Shenzhen Skyworth Digital Technology CO., Ltd +D0458D (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, Shenzhen Guangdong 518057 CN -A0-88-5E (hex) Anhui Xiangyao New Energy Technology Co., Ltd. -A0885E (base 16) Anhui Xiangyao New Energy Technology Co., Ltd. - No. 2, District 4, Intelligent Industrial Park, South District, Lieshan Economic Development Zone - Huaibei City Anhui Province 235065 +9C-24-10 (hex) Bouffalo Lab (Nanjing) Co., Ltd. +9C2410 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 CN -7C-15-2D (hex) Renesas Electronics (Penang) Sdn. Bhd. -7C152D (base 16) Renesas Electronics (Penang) Sdn. Bhd. - Phase 3, Bayan Lepas FIZ - Bayan Lepas Penang 11900 - MY - -A4-DB-4C (hex) RAI Institute -A4DB4C (base 16) RAI Institute - 145 Broadway - Cambridge MA 02142 +94-18-65 (hex) NETGEAR +941865 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -34-BC-5E (hex) eero inc. -34BC5E (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +E0-46-EE (hex) NETGEAR +E046EE (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -B8-07-56 (hex) Cisco Meraki -B80756 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 +00-18-4D (hex) NETGEAR +00184D (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -2C-91-AB (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -2C91AB (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -DC-15-C8 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -DC15C8 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -B0-F2-08 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -B0F208 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -BC-35-1E (hex) Tuya Smart Inc. -BC351E (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 +00-1E-2A (hex) NETGEAR +001E2A (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -38-10-D5 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -3810D5 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -C8-0E-14 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -C80E14 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE +00-14-6C (hex) NETGEAR +00146C (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US -00-84-97 (hex) Shenzhen MiaoMing Intelligent Technology Co.,Ltd -008497 (base 16) Shenzhen MiaoMing Intelligent Technology Co.,Ltd - 3F, Building 2, No. 1310, Kukeng Sightseeing Road, Kukeng Community, Guanlan Street, Longhua District - Shenzhen Guangdong 518110 - CN +E0-91-F5 (hex) NETGEAR +E091F5 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US -28-7A-B4 (hex) HUAWEI TECHNOLOGIES CO.,LTD -287AB4 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +74-44-01 (hex) NETGEAR +744401 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US -78-DC-87 (hex) HUAWEI TECHNOLOGIES CO.,LTD -78DC87 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +44-94-FC (hex) NETGEAR +4494FC (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US -E0-08-55 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -E00855 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE +20-E5-2A (hex) NETGEAR +20E52A (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US -D8-F1-2E (hex) TP-Link Systems Inc. -D8F12E (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 +B0-39-56 (hex) NETGEAR +B03956 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -90-47-3C (hex) China Mobile Group Device Co.,Ltd. -90473C (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +3C-1A-CC (hex) Quectel Wireless Solutions Co.,Ltd. +3C1ACC (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN -74-AD-B7 (hex) China Mobile Group Device Co.,Ltd. -74ADB7 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +38-FB-A0 (hex) Shenzhen Baseus Technology CoLtd +38FBA0 (base 16) Shenzhen Baseus Technology CoLtd + 2nd Floor, Building B, Baseus Intelligence Park, No.2008, Xuegang Rd,Gangtou Community,Bantian Street, Longgang District + Shenzhen 518172 CN -78-C3-13 (hex) China Mobile Group Device Co.,Ltd. -78C313 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +A4-B2-56 (hex) Shenzhen Incar Technology Co., Ltd. +A4B256 (base 16) Shenzhen Incar Technology Co., Ltd. + 18th Floor, Zhongxi ECO Building, Shuiku Road, Xixiang Street, Bao'an District, + Shenzhen City Guangdong Province 518102 CN -CC-5C-DE (hex) China Mobile Group Device Co.,Ltd. -CC5CDE (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +58-98-35 (hex) Vantiva Technologies Belgium +589835 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE -0C-14-D2 (hex) China Mobile Group Device Co.,Ltd. -0C14D2 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +C4-EA-1D (hex) Vantiva Technologies Belgium +C4EA1D (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE -78-10-53 (hex) China Mobile Group Device Co.,Ltd. -781053 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +A4-91-B1 (hex) Vantiva Technologies Belgium +A491B1 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE -70-89-CC (hex) China Mobile Group Device Co.,Ltd. -7089CC (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +D8-E3-74 (hex) Xiaomi Communications Co Ltd +D8E374 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -B4-D0-A9 (hex) China Mobile Group Device Co.,Ltd. -B4D0A9 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +D8-78-F0 (hex) Silicon Laboratories +D878F0 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -AC-71-0C (hex) China Mobile Group Device Co.,Ltd. -AC710C (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +D4-35-1D (hex) Vantiva Technologies Belgium +D4351D (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE -10-3D-3E (hex) China Mobile Group Device Co.,Ltd. -103D3E (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +00-7A-A4 (hex) FRITZ! Technology GmbH +007AA4 (base 16) FRITZ! Technology GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +20-B8-3D (hex) UNIONMAN TECHNOLOGY CO.,LTD +20B83D (base 16) UNIONMAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 CN -D0-CB-DD (hex) eero inc. -D0CBDD (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US +AC-EA-70 (hex) ZUNDA Inc. +ACEA70 (base 16) ZUNDA Inc. + 3/F Kamon Bldg, Shibuya 2-6-11 + Shibuya-ku Tokyo 150-0002 + JP -44-AC-85 (hex) eero inc. -44AC85 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +20-E1-5D (hex) TP-Link Systems Inc. +20E15D (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US -18-A9-ED (hex) eero inc. -18A9ED (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US +1C-04-60 (hex) NXP Semiconductors Taiwan Ltd. +1C0460 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW -80-B9-7A (hex) eero inc. -80B97A (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US +EC-5B-71 (hex) Inventec(Chongqing) Corporation +EC5B71 (base 16) Inventec(Chongqing) Corporation + No.66 West District 2nd Rd, Shapingba District + Chongqing Chongqing 401331 + CN -9C-A5-70 (hex) eero inc. -9CA570 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US +74-D0-82 (hex) HISENSE VISUAL TECHNOLOGY CO.,LTD +74D082 (base 16) HISENSE VISUAL TECHNOLOGY CO.,LTD + Qianwangang Road 218 + Qingdao Shandong 266510 + CN -FC-3F-A6 (hex) eero inc. -FC3FA6 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US +98-53-5F (hex) HUAWEI TECHNOLOGIES CO.,LTD +98535F (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -78-68-29 (hex) eero inc. -786829 (base 16) eero inc. +B0-FB-15 (hex) Ezurio, LLC +B0FB15 (base 16) Ezurio, LLC + 3F.-1, No.145, Xianzheng 9th Rd., + Zhubei 30251 + TW + +EC-C0-7A (hex) Ezurio, LLC +ECC07A (base 16) Ezurio, LLC + 3F.-1, No.145, Xianzheng 9th Rd., + Zhubei 30251 + TW + +2C-2F-F4 (hex) eero inc. +2C2FF4 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -10-1D-6E (hex) Hewlett Packard Enterprise -101D6E (base 16) Hewlett Packard Enterprise - 3333 Scott Blvd - 6280 America Center Dr CA 95002 +24-53-ED (hex) Dell Inc. +2453ED (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 US -04-4F-7A (hex) China Mobile Group Device Co.,Ltd. -044F7A (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +A4-CF-03 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. +A4CF03 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. + No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. + Chongqing China 401120 CN -D0-16-7C (hex) eero inc. -D0167C (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -24-7B-A4 (hex) China Mobile Group Device Co.,Ltd. -247BA4 (base 16) China Mobile Group Device Co.,Ltd. +FC-E6-C6 (hex) China Mobile Group Device Co.,Ltd. +FCE6C6 (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District Beijing 100053 CN -F4-52-14 (hex) Mellanox Technologies, Inc. -F45214 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 +6C-3E-51 (hex) Mindray North America +6C3E51 (base 16) Mindray North America + 800 MacArthur Blvd + Mahwah NJ 07430 US -00-25-8B (hex) Mellanox Technologies, Inc. -00258B (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +14-18-26 (hex) Nokia +141826 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -0C-42-A1 (hex) Mellanox Technologies, Inc. -0C42A1 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +20-E5-9B (hex) Panasonic Automotive Systems +20E59B (base 16) Panasonic Automotive Systems + 1578-5 , Uegawa-chou + Matsusaka Mie 5150041 + JP -08-C0-EB (hex) Mellanox Technologies, Inc. -08C0EB (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +0C-E7-09 (hex) Sentyron B.V +0CE709 (base 16) Sentyron B.V + P.O. box 638 + Delft ZH 2600 AP + NL -9C-05-91 (hex) Mellanox Technologies, Inc. -9C0591 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +B0-82-E2 (hex) ASUSTek COMPUTER INC. +B082E2 (base 16) ASUSTek COMPUTER INC. + 15,Li-Te Rd., Peitou, Taipei 112, Taiwan + Taipei Taiwan 112 + TW -58-A2-E1 (hex) Mellanox Technologies, Inc. -58A2E1 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +3C-49-FF (hex) UNIONMAN TECHNOLOGY CO.,LTD +3C49FF (base 16) UNIONMAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 + CN -B0-CF-0E (hex) Mellanox Technologies, Inc. -B0CF0E (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 +24-55-9A (hex) Apple, Inc. +24559A (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -94-BE-09 (hex) China Mobile Group Device Co.,Ltd. -94BE09 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +4C-E6-50 (hex) Apple, Inc. +4CE650 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -BC-9E-2C (hex) China Mobile Group Device Co.,Ltd. -BC9E2C (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +EC-1A-C3 (hex) Ugreen Group Limited +EC1AC3 (base 16) Ugreen Group Limited + 4F, Plant 6, 1F-6/F, Block 7, YuAn Zone, Gaofeng Community, Dalang Street, Longhua District + Shenzhen Guangdong 518109 CN -C8-0C-53 (hex) China Mobile Group Device Co.,Ltd. -C80C53 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +E4-73-19 (hex) Huawei Device Co., Ltd. +E47319 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -54-4D-D4 (hex) China Mobile Group Device Co.,Ltd. -544DD4 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +BC-BC-CA (hex) Huawei Device Co., Ltd. +BCBCCA (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -C0-2D-2E (hex) China Mobile Group Device Co.,Ltd. -C02D2E (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +A0-FD-D9 (hex) UNIONMAN TECHNOLOGY CO.,LTD +A0FDD9 (base 16) UNIONMAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 CN -88-57-21 (hex) Espressif Inc. -885721 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +BC-2B-1E (hex) Cresyn Co., Ltd. +BC2B1E (base 16) Cresyn Co., Ltd. + CRESYN B/D, Gangnam-daero 107-gil, Seocho-gu, Seoul + Seoul CRESYN B/D, Gangnam-daero 107-gil 06254 + KR -E4-96-52 (hex) vivo Mobile Communication Co., Ltd. -E49652 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +6C-68-A4 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +6C68A4 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District + Guangzhou Guangdong 510663 CN -D4-A5-B4 (hex) Hengji Jiaye (Hangzhou) Technology Co., Ltd -D4A5B4 (base 16) Hengji Jiaye (Hangzhou) Technology Co., Ltd - Room 809, Building B, Yingfeite Technology Park, No. 459 Jianghong Road, Changhe Street, Binjiang District, Hangzhou City, Zhejiang Province - Hangzhou Zhejiang 310000 - CN +A4-C0-B0 (hex) Drivenets +A4C0B0 (base 16) Drivenets + 1st Zarhin St. + Raanana Israel 4366235 + IL -20-4D-52 (hex) Mellanox Technologies, Inc. -204D52 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 +90-C9-52 (hex) Durin, Inc +90C952 (base 16) Durin, Inc + 440 N Wolfe Rd Sunnyvale CA 94085 US -F0-2C-59 (hex) Chipsea Technologies (Shenzhen) Crop. -F02C59 (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 +DC-22-6F (hex) HangZhou Nano IC Technologies Co., Ltd +DC226F (base 16) HangZhou Nano IC Technologies Co., Ltd + No. 11, F Building, 1st Floor, Building A, Tiantang Software Park, 3 West Doudimen Road, Xihu District + Hangzhou Zhejiang 310013 CN -04-4A-69 (hex) Shenzhen Phaten Tech. LTD -044A69 (base 16) Shenzhen Phaten Tech. LTD - C-6 ideamonto industril 7002 Songbai Road Guangming District Shenzhen City Guangdong, China - Shenzhen 518108 - CN +0C-C7-63 (hex) eero inc. +0CC763 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US -58-7A-B1 (hex) Shanghai Lixun Information Technology Co., Ltd. -587AB1 (base 16) Shanghai Lixun Information Technology Co., Ltd. - Room 2111-L, No. 89 Yunling East Road - Putuo District Shanghai 200333 +98-A9-42 (hex) Tozed Kangwei Tech Co., Ltd +98A942 (base 16) Tozed Kangwei Tech Co., Ltd + Room 1301, NO. 37 Jinlong , Nansha Street, Xiangjiang Financial Business Center, Nansha District + Guangzhou Guangdong 511458 CN -04-B5-C1 (hex) ITEL MOBILE LIMITED -04B5C1 (base 16) ITEL MOBILE LIMITED - RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K - Hong Kong KOWLOON 999077 - HK - -28-C9-7A (hex) New H3C Technologies Co., Ltd -28C97A (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 +28-D6-EC (hex) HUAWEI TECHNOLOGIES CO.,LTD +28D6EC (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -A0-3C-20 (hex) Sagemcom Broadband SAS -A03C20 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -C4-9A-89 (hex) Suzhou K-Hiragawa Electronic Technology Co.,Ltd -C49A89 (base 16) Suzhou K-Hiragawa Electronic Technology Co.,Ltd - No.1 Zhipu Road, Qiandeng Town - Suzhou Jiangsu 215341 +AC-E0-11 (hex) HUAWEI TECHNOLOGIES CO.,LTD +ACE011 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -AC-BD-F7 (hex) Cisco Meraki -ACBDF7 (base 16) Cisco Meraki +B8-64-68 (hex) BBSakura Networks, Inc. +B86468 (base 16) BBSakura Networks, Inc. + Sumitomo Fudosan Nishishinjuku Building, 7-20-1 Nishi-shinjuku + Shinjuku-ku Tokyo 160-0023 + JP + +08-6A-0B (hex) Cisco Meraki +086A0B (base 16) Cisco Meraki 500 Terry A. Francois Blvd San Francisco 94158 US -FC-39-5A (hex) SonicWall -FC395A (base 16) SonicWall - 1033 McCarthy Blvd - Milpitas CA 95035 - US - -0C-FE-7B (hex) Vantiva USA LLC -0CFE7B (base 16) Vantiva USA LLC - 4855 Peachtree Industrial Blvd, Suite 200 - Norcross GA 30902 - US - -B0-D5-FB (hex) Google, Inc. -B0D5FB (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 +C8-63-40 (hex) Cisco Meraki +C86340 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 US -48-D0-1C (hex) AltoBeam Inc. -48D01C (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 +34-F0-15 (hex) Beijing Xiaomi Mobile Software Co., Ltd +34F015 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -C8-17-F5 (hex) Nanjing Qinheng Microelectronics Co., Ltd. -C817F5 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. - No.18, Ningshuang Road - Nanjing Jiangsu 210012 - CN +FC-26-8C (hex) Signify B.V. +FC268C (base 16) Signify B.V. + High Tech Campus 7 + Eindhoven 5656AE + NL -D8-B2-AA (hex) zte corporation -D8B2AA (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +E8-3D-C1 (hex) Espressif Inc. +E83DC1 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN -90-14-AF (hex) Cambium Networks Limited -9014AF (base 16) Cambium Networks Limited - Unit B2, Linhay Business Park, - Ashburton Devon TQ13 7UP - GB - -F4-15-63 (hex) F5 Inc. -F41563 (base 16) F5 Inc. - 1322 North Whitman Lane - Liberty Lake WA 99019 - US - -34-DA-A1 (hex) Apple, Inc. -34DAA1 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +24-71-21 (hex) Cisco Systems, Inc +247121 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -CC-22-FE (hex) Apple, Inc. -CC22FE (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +BC-AB-F5 (hex) Cisco Systems, Inc +BCABF5 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -CC-67-D8 (hex) Telin Semiconductor (Wuhan) Co.,Ltd -CC67D8 (base 16) Telin Semiconductor (Wuhan) Co.,Ltd - Room 1003 Buliding 5 , 2377 Shenkun Road ,Minhang District - Shanghai 201106 - CN - -2C-F8-14 (hex) Cisco Systems, Inc -2CF814 (base 16) Cisco Systems, Inc +B8-C9-24 (hex) Cisco Systems, Inc +B8C924 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -88-DA-04 (hex) HUAWEI TECHNOLOGIES CO.,LTD -88DA04 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -04-74-9E (hex) HUAWEI TECHNOLOGIES CO.,LTD -04749E (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - -CC-FA-F1 (hex) Sagemcom Broadband SAS -CCFAF1 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -2C-9D-5A (hex) Flaircomm Microelectronics,Inc. -2C9D5A (base 16) Flaircomm Microelectronics,Inc. - 7F,Guomai Building,Guomai Science and Technology Park,116 Jiangbin East Avenue,Mawei District,Fuzhou City - Fuzhou FUJIAN 350015 +A0-F2-62 (hex) Espressif Inc. +A0F262 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN -24-34-08 (hex) Edgecore Americas Networking Corporation -243408 (base 16) Edgecore Americas Networking Corporation - 20 Mason - Irvine CA 92618 - US +A0-58-66 (hex) Qorvo Utrecht B.V. +A05866 (base 16) Qorvo Utrecht B.V. + Leidseveer 10 + Utrecht Utrecht 3511 SB + NL -64-AC-2B (hex) Juniper Networks -64AC2B (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +7C-6D-12 (hex) Microsoft Corporation +7C6D12 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 US -84-D0-DB (hex) Guangdong Juan Intelligent Technology Joint Stock Co., Ltd. -84D0DB (base 16) Guangdong Juan Intelligent Technology Joint Stock Co., Ltd. - The first and second floors of Building 2  (Plant No. 2), West Side of Shanxi Village, Dashi Street,Panyu District, Guangzhou - Guangzhou Guangdong 510000 - CN - -A8-6D-04 (hex) Siemens AG -A86D04 (base 16) Siemens AG - Hermann-Papst-Strasse 1 - St. Georgen 78112 - DE - -4C-46-D1 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. -4C46D1 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. - 601,Building B2,No.162,Science Avenue,Science City,Guangzhou High-tech Industrial Development Zone,Guangdong Province,China - Guangzhou Guangdong 510663 +CC-C4-B2 (hex) Shenzhen Trolink Technology Co.,LTD +CCC4B2 (base 16) Shenzhen Trolink Technology Co.,LTD + 7th Floor, Building 5, Phase 2, Donghua Intelligent Manufacturing Park, Sanwei Community, Hangcheng Street, Bao'an District + Shenzhen Guangdong 518101 CN -44-38-8C (hex) Sumitomo Electric Industries, Ltd -44388C (base 16) Sumitomo Electric Industries, Ltd - 1-1-3, Shimaya, Konohana-ku - Osaka 554-0024 - JP - -7C-7B-BF (hex) Samsung Electronics Co.,Ltd -7C7BBF (base 16) Samsung Electronics Co.,Ltd +60-B4-A2 (hex) Samsung Electronics Co.,Ltd +60B4A2 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -8C-2E-72 (hex) Samsung Electronics Co.,Ltd -8C2E72 (base 16) Samsung Electronics Co.,Ltd +14-0B-9E (hex) Samsung Electronics Co.,Ltd +140B9E (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR -DC-70-35 (hex) Shengzhen Gongjin Electronics -DC7035 (base 16) Shengzhen Gongjin Electronics - No. 2 Danzi North Road, Kengzi Street, Pingshan District - Shenzhen Guangdong 518122 +64-CE-0C (hex) Funshion Online Technologies Co.,Ltd +64CE0C (base 16) Funshion Online Technologies Co.,Ltd + 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing + Beijing 100029 CN -EC-ED-04 (hex) Intel Corporate -ECED04 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -9C-97-1B (hex) Intel Corporate -9C971B (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -F8-CF-52 (hex) Intel Corporate -F8CF52 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +88-13-C2 (hex) Tendyron Corporation +8813C2 (base 16) Tendyron Corporation + Tendyron Building,Zhongguancun NO.1 Park,Beiqing Road,Haidian District,Beijing,China + Beijing 100000 + CN -5C-67-83 (hex) Intel Corporate -5C6783 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +84-48-80 (hex) Amazon Technologies Inc. +844880 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -BC-51-5F (hex) Nokia Solutions and Networks India Private Limited -BC515F (base 16) Nokia Solutions and Networks India Private Limited - Plot 45, Fathima NagarNemilicherry,Chrompet - Chennai Taminadu 600044 - IN +00-12-4F (hex) Chemelex LLC +00124F (base 16) Chemelex LLC + 1665 Utica Avenue, Suite 700 + St Louis Park MN 55416 + US -28-D4-1E (hex) Barrot Technology Co.,Ltd. -28D41E (base 16) Barrot Technology Co.,Ltd. - A1009,Block A,Jia Hua Building,No.9 Shangdi 3rd Street,Haidian District,Beijing - beijing beijing 100000 - CN +F0-30-12 (hex) AUMOVIO Autonomous Mobility Germany GmbH +F03012 (base 16) AUMOVIO Autonomous Mobility Germany GmbH + Ringlerstr. 17 + Ingolstadt 85057 + DE -D4-9D-9D (hex) Shenzhen Goodocom lnformation Technology Co.,Ltd. -D49D9D (base 16) Shenzhen Goodocom lnformation Technology Co.,Ltd. - 3rd Floor, Xia Valley,Meisheng Huigu Science and Technology Industrial Park, 83 Dabao Road, Baoan District 33, Shenzhen - Shenzhen 518000 - CN +B4-DD-D0 (hex) AUMOVIO Hungary Kft. +B4DDD0 (base 16) AUMOVIO Hungary Kft. + Napmátka u. 6. + Budapest Pest H-1106 + HU -80-F1-B2 (hex) Espressif Inc. -80F1B2 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +30-F0-3A (hex) UEI Electronics Private Ltd. +30F03A (base 16) UEI Electronics Private Ltd. + #49, 1 st floor, East wing, Khanjabhavan, Racecourse Road. + Bengaluru Karnataka 560001 + IN -88-12-7D (hex) Shenzhen Melon Electronics Co.,Ltd -88127D (base 16) Shenzhen Melon Electronics Co.,Ltd - 2F, Building E, Digital Silicon Valley, No.89 Hengping Road, Yuanshan Subdistrict, Longgang District, Shenzhen, Guangdong, China - Shenzhen Guangdong 518100 - CN +94-2D-3A (hex) PRIZOR VIZTECH LIMITED +942D3A (base 16) PRIZOR VIZTECH LIMITED + 514, Maple Trade Ctr Rd, near Surdhara Circle, Thaltej + Ahmedabad Gujarat 380052 + IN -00-70-07 (hex) Espressif Inc. -007007 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +08-18-14 (hex) Hewlett Packard Enterprise +081814 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US -44-CB-AD (hex) Xiaomi Communications Co Ltd -44CBAD (base 16) Xiaomi Communications Co Ltd +44-BD-C8 (hex) Xiaomi Communications Co Ltd +44BDC8 (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road Beijing Haidian District 100085 CN -54-4E-F0 (hex) Roku, Inc -544EF0 (base 16) Roku, Inc - 1173 Coleman Ave - San Jose CA 95110 +30-48-7D (hex) Tuya Smart Inc. +30487D (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 US -0C-80-2F (hex) Murata Manufacturing Co., Ltd. -0C802F (base 16) Murata Manufacturing Co., Ltd. - 1-10-1, Higashikotari - Nagaokakyo-shi Kyoto 617-8555 - JP +D0-CD-BF (hex) LG Electronics +D0CDBF (base 16) LG Electronics + 222 LG-ro, JINWI-MYEON + Pyeongtaek-si Gyeonggi-do 451-713 + KR -58-87-85 (hex) Adtran Inc -588785 (base 16) Adtran Inc - 901 Explorer Blvd. - Huntsville AL 35806-2807 +94-EA-E7 (hex) Lynq Technologies +94EAE7 (base 16) Lynq Technologies + 11101 West 120th Avenue + Broomfield CO 80021 US -EC-9E-EA (hex) Xtra Technology LLC -EC9EEA (base 16) Xtra Technology LLC - 3422 Old Capitol Trail, Suite 700 - WiImington 19808-6124 +90-8A-80 (hex) Cisco Systems, Inc +908A80 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -10-65-19 (hex) Shenzhen iComm Semiconductor CO.,LTD -106519 (base 16) Shenzhen iComm Semiconductor CO.,LTD - Room204,scientific research building,Tsinghua Hi-Tech Park,No.13 Langshan Road,Nanshan District - Shenzhen Guangdong 518067 - CN - -18-EB-D4 (hex) Shenzhen Skyworth Digital Technology CO., Ltd -18EBD4 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 - CN - -E4-B7-31 (hex) Hangzhou Advance IOT Connectivity System Co., Ltd. -E4B731 (base 16) Hangzhou Advance IOT Connectivity System Co., Ltd. - Room 406, building 1, No. 4280, South Ring Road, Puyan street, Binjiang District - Hangzhou Zhejiang 310051 - CN - -FC-38-82 (hex) Infinix mobility limited -FC3882 (base 16) Infinix mobility limited - RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG - HongKong HongKong 999077 - HK - -48-A4-8C (hex) Shanghai Zenchant Electornics Co.,LTD -48A48C (base 16) Shanghai Zenchant Electornics Co.,LTD - Room 1202, building a, Noble international, 908 Xiuwen Road, Minhang District - ShangHai ShangHai 201199 - CN - -C4-DB-AD (hex) Ring LLC -C4DBAD (base 16) Ring LLC +00-B4-63 (hex) Ring LLC +00B463 (base 16) Ring LLC 12515 Cerise Ave - Hawthorne CA 12515 + Hawthorne 90250 US -94-FF-7D (hex) AltoBeam Inc. -94FF7D (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN +A4-F0-1F (hex) CANON INC. +A4F01F (base 16) CANON INC. + 30-2 Shimomaruko 3-chome, + Ohta-ku Tokyo 146-8501 + JP -40-1C-D4 (hex) Huawei Device Co., Ltd. -401CD4 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +90-B9-F9 (hex) Motorola Mobility LLC, a Lenovo Company +90B9F9 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US -00-4B-0D (hex) Huawei Device Co., Ltd. -004B0D (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +04-6F-00 (hex) LG Electronics +046F00 (base 16) LG Electronics + Lot CN02, Trang Due Industrial Park,An Phong Ward, Hai Phong City, Vietnam + Hai Phong 184956 + VN -E0-40-27 (hex) Huawei Device Co., Ltd. -E04027 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +5C-13-AC (hex) Apple, Inc. +5C13AC (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -00-02-71 (hex) Zhone Technologies, Inc. -000271 (base 16) Zhone Technologies, Inc. - 7001 Oakport Street - Oakland CA 94621 +0C-E5-A1 (hex) Apple, Inc. +0CE5A1 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -00-01-47 (hex) Zhone Technologies, Inc. -000147 (base 16) Zhone Technologies, Inc. - 7001 Oakport Street - Oakland CA 94621 +0C-A3-B2 (hex) Apple, Inc. +0CA3B2 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -F8-83-06 (hex) Beijing Xiaomi Mobile Software Co., Ltd -F88306 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN +24-2A-EA (hex) Apple, Inc. +242AEA (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -7C-C5-18 (hex) vivo Mobile Communication Co., Ltd. -7CC518 (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN +98-E8-59 (hex) Apple, Inc. +98E859 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -7C-7D-21 (hex) zte corporation -7C7D21 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +D8-3E-EB (hex) AltoBeam Inc. +D83EEB (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -D0-96-FB (hex) Zhone Technologies, Inc. -D096FB (base 16) Zhone Technologies, Inc. - DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu - Seongnam-si Gyeonggi-do 13493 - KR +5C-48-79 (hex) HUAWEI TECHNOLOGIES CO.,LTD +5C4879 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -30-4F-75 (hex) Zhone Technologies, Inc. -304F75 (base 16) Zhone Technologies, Inc. - DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu - Seongnam-si Gyeonggi-do 13493 - KR +DC-57-5C (hex) PR Electronics A/S +DC575C (base 16) PR Electronics A/S + Lerbakken 10 + Følle 8410 + DK -D4-7B-6B (hex) Shanghai Cygnus Semiconductor Co., Ltd. -D47B6B (base 16) Shanghai Cygnus Semiconductor Co., Ltd. - Rooms 401 and 402, Building 5, 690 Bibo Road, China (Shanghai) Pilot Free Trade Zone - Shanghai Shanghai 201203 +38-47-12 (hex) Luxottica Tristar (Dongguan) Optical Co.,Ltd +384712 (base 16) Luxottica Tristar (Dongguan) Optical Co.,Ltd + Oudeng Zone, Gaobu Town, Dongguan City, GuangDong, China + Dongguan GuangDong 523000 CN -98-61-10 (hex) HUAWEI TECHNOLOGIES CO.,LTD -986110 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +64-B4-E8 (hex) Shenzhen D-Robotics Co., Ltd. +64B4E8 (base 16) Shenzhen D-Robotics Co., Ltd. + Room 1107-1108, Block C, Building 1, Section 1, Chuangzhi Yuncheng, Nanshan District + Shenzhen Guangdong 518000 CN -F4-85-AE (hex) Senbiosys SA -F485AE (base 16) Senbiosys SA - Route des Gouttes-d'Or 40 - Neuchâtel 2000 - CH - -60-55-56 (hex) Jiangxi Risound Electronics Co.,LTD -605556 (base 16) Jiangxi Risound Electronics Co.,LTD - No 271,innovation Avenue, Jinggangshan economic and Technological Development Zone - Ji'an Jiangxi 343100 +54-6C-50 (hex) Nanjing Qinheng Microelectronics Co., Ltd. +546C50 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. + No.18, Ningshuang Road + Nanjing Jiangsu 210012 CN -AC-82-F0 (hex) Apple, Inc. -AC82F0 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +E4-85-FB (hex) Quectel Wireless Solutions Co.,Ltd. +E485FB (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN -0C-CC-5D (hex) Apple, Inc. -0CCC5D (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +B8-BA-66 (hex) Microsoft Corporation +B8BA66 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 US -34-0E-22 (hex) Apple, Inc. -340E22 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +28-AD-EA (hex) Mallow SAS +28ADEA (base 16) Mallow SAS + 229, rue St Honore + Paris IDF 75001 + FR -A8-9A-8C (hex) zte corporation -A89A8C (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +60-65-F4 (hex) Chipsea Technologies (Shenzhen) Crop. +6065F4 (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 -68-4A-5F (hex) Apple, Inc. -684A5F (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +B0-7A-16 (hex) ROEHN +B07A16 (base 16) ROEHN + Av. Manuel Bandeira, 291 + Sao Paulo Sp 05317020 + BR -E8-98-EE (hex) Apple, Inc. -E898EE (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +58-9E-C6 (hex) Gigaset Technologies GmbH +589EC6 (base 16) Gigaset Technologies GmbH + Frankenstrasse 2 + Bocholt NRW 46395 + DE -34-28-65 (hex) Juniper Networks -342865 (base 16) Juniper Networks +B8-61-FC (hex) Juniper Networks +B861FC (base 16) Juniper Networks 1133 Innovation Way Sunnyvale CA 94089 US -10-5A-95 (hex) TP-Link Systems Inc. -105A95 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 +E8-47-F3 (hex) upscale ai +E847F3 (base 16) upscale ai + 3101 Jay St. + Santa Clara CA 95054 US -F8-20-97 (hex) Aditya Infotech Ltd. -F82097 (base 16) Aditya Infotech Ltd. - Khemka Square, A-12, Sector-4, - Noida Uttar Pradesh 201301 - IN - -5C-7B-6C (hex) Tradit Co., Ltd -5C7B6C (base 16) Tradit Co., Ltd - #617, 416 Hwagok-ro, Gangseo-gu - Seoul 07548 - KR +40-68-F9 (hex) Shenzhen SuperElectron Technology Co.,Ltd. +4068F9 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. + 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city + Shenzhen Guangdong 518000 + CN -A0-7E-16 (hex) EM Microelectronic -A07E16 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +98-12-23 (hex) Tarmoc Network LTD +981223 (base 16) Tarmoc Network LTD + Taihao Road No.22, 6th Floor, Sandong Town, Huicheng District, Huizhou City + Huizhou City GuangDong 518000 + CN -CC-5E-A5 (hex) Palo Alto Networks -CC5EA5 (base 16) Palo Alto Networks - 3000 Tannery Way - Santa Clara CA 95054 - US +2C-F8-EC (hex) Quectel Wireless Solutions Co.,Ltd. +2CF8EC (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN -D8-1D-13 (hex) Texas Instruments -D81D13 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +84-C6-65 (hex) Taicang T&W Electronics +84C665 (base 16) Taicang T&W Electronics + 89# Jiang Nan RD + Suzhou Jiangsu 215412 + CN -14-75-E5 (hex) ELMAX Srl -1475E5 (base 16) ELMAX Srl - Via dei Parietai, 2 - Molfetta BA 70056 - IT +6C-91-88 (hex) Nokia +6C9188 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -E0-23-3B (hex) IEEE Registration Authority -E0233B (base 16) IEEE Registration Authority +08-3C-03 (hex) IEEE Registration Authority +083C03 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -88-33-74 (hex) ASKEY COMPUTER CORP -883374 (base 16) ASKEY COMPUTER CORP - 10F,No.119,JIANKANG RD,ZHONGHE DIST - NEW TAIPEI TAIWAN 23585 +00-09-D8 (hex) Telia Company AB +0009D8 (base 16) Telia Company AB + Östermalmsgatan 63A + Umeå 903 35 + SE + +34-6F-3F (hex) Hon Hai Precision Industry Co.,LTD +346F3F (base 16) Hon Hai Precision Industry Co.,LTD + 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI CITY,23678 , TAIWAN CHINA + TAIPEI 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI 33859 + CN + +14-49-C5 (hex) Huawei Device Co., Ltd. +1449C5 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +B4-54-F2 (hex) Huawei Device Co., Ltd. +B454F2 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +68-F9-0F (hex) Intel Corporate +68F90F (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +D0-8E-17 (hex) ACCTON TECHNOLOGY CORPORATION +D08E17 (base 16) ACCTON TECHNOLOGY CORPORATION + No.1, Creation Road 3, Hsinchu Science Park, + Hsinchu 30077 TW -E4-56-CA (hex) Fractal BMS -E456CA (base 16) Fractal BMS - 8656 W Hwy 71 - AUSTIN TX 78735 - US +54-A0-AB (hex) Maiyue Future Intelligent Technology (Suzhou) Co.,Ltd. +54A0AB (base 16) Maiyue Future Intelligent Technology (Suzhou) Co.,Ltd. + Room 1283, Building 3, No.288 Jiushenggang Road, Guoxiang Street, Economic Development Zone, Suzhou,China + Suzhou 215000 + CN -48-08-EB (hex) IEEE Registration Authority -4808EB (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +80-C2-F0 (hex) Xiamen Yeastar Digital Technology Co.,Ltd +80C2F0 (base 16) Xiamen Yeastar Digital Technology Co.,Ltd + Building C09, Software Park Phase III, Xiamen 361024, Fujian, China + XIAMEN FUJIAN 361024 + CN -F0-44-D3 (hex) Silicon Laboratories -F044D3 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 +E4-C8-01 (hex) BLU Products Inc +E4C801 (base 16) BLU Products Inc + 8600 NW 36th Street Suite 200 + Miami FL 33166 US -6C-47-25 (hex) Rochester Network Supply, Inc. -6C4725 (base 16) Rochester Network Supply, Inc. - 1319 Research Forest, - Macedon NY 14502 - US +8C-69-14 (hex) FREEBOX SAS +8C6914 (base 16) FREEBOX SAS + 16 rue de la Ville l'Eveque + PARIS IdF 75008 + FR -B4-43-89 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B44389 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +24-64-77 (hex) Beijing Xiaomi Mobile Software Co., Ltd +246477 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -08-FD-58 (hex) HUAWEI TECHNOLOGIES CO.,LTD -08FD58 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +7C-B4-0F (hex) Fibocom Wireless Inc. +7CB40F (base 16) Fibocom Wireless Inc. + 1101,Tower A, Building 6, Shenzhen International Innovation Valley, Dashi 1st Rd, Nanshan + Shenzhen Guangdong 518055 + CN + +50-9F-B9 (hex) Shenzhen Skyworth Digital Technology CO., Ltd +509FB9 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + +DC-7E-F5 (hex) HUAWEI TECHNOLOGIES CO.,LTD +DC7EF5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -80-49-BF (hex) Taicang T&W Electronics -8049BF (base 16) Taicang T&W Electronics - 89# Jiang Nan RD - Suzhou Jiangsu 215412 +8C-D0-66 (hex) Texas Instruments +8CD066 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +44-63-C2 (hex) Zyxel Communications Corporation +4463C2 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + +34-20-D3 (hex) SHENZHEN IP-COM NETWORKS CO.,LTD. +3420D3 (base 16) SHENZHEN IP-COM NETWORKS CO.,LTD. + Room 101, Unit A, First Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China + SHENZHEN Guangdong Province 518052 + CN + +78-D3-66 (hex) GuangZhou Dazzleview Intelligent Technology Co., Ltd +78D366 (base 16) GuangZhou Dazzleview Intelligent Technology Co., Ltd + 4th Floor, Building33, No.2519 Xingang East Road + Haizhu District Guangzhou 510000 CN -B0-97-E6 (hex) FUJIAN FUCAN WECON CO LTD -B097E6 (base 16) FUJIAN FUCAN WECON CO LTD - Wecon Tech Park, No.58 Jiangbin East Avenue, Mawei, Fuzhou, China - FUZHOU FUJIAN 350015 +64-6B-E7 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. +646BE7 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. + No.218 Qianwangang Road + Qingdao Shangdong 266510 CN -B8-FE-90 (hex) Cisco Systems, Inc -B8FE90 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +24-8A-B3 (hex) ICTK Co., Ltd. +248AB3 (base 16) ICTK Co., Ltd. + 13F, JACE Tower, 16, Gangnam-daero 84-gil, Gangnam-gu + Seoul Select State 06241 + KR -34-C3-FD (hex) Cisco Systems, Inc -34C3FD (base 16) Cisco Systems, Inc +7C-62-E7 (hex) Cisco Systems, Inc +7C62E7 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -B4-5C-B5 (hex) Mellanox Technologies, Inc. -B45CB5 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +10-D8-B1 (hex) AUO Corporation +10D8B1 (base 16) AUO Corporation + No. 1, Li-Hsin Rd. 2, Hsinchu Science Park, + Hsinchu 300094 + TW -E8-F6-0A (hex) Espressif Inc. -E8F60A (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +BC-00-23 (hex) Honor Device Co., Ltd. +BC0023 (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 -6C-D8-FB (hex) Qorvo Utrecht B.V. -6CD8FB (base 16) Qorvo Utrecht B.V. - Leidseveer 10 - Utrecht Utrecht 3511 SB - NL +88-7C-C1 (hex) Zebronics India Pvt Ltd +887CC1 (base 16) Zebronics India Pvt Ltd + No 13/7, Smith Road, Royapettah + Chennai Tamil Nadu 600002 + IN -64-84-34 (hex) BEMER Int. AG -648434 (base 16) BEMER Int. AG - Austrasse 15 - Triesen 9495 - LI +3C-CB-01 (hex) Beijing Lingji innovations Technology Co., LTD. +3CCB01 (base 16) Beijing Lingji innovations Technology Co., LTD. + Room 106, 1F, A1 Bldg. Zhongguancun Dongsheng Technology Park (Northern Territory), No. 66, Xixiaokou Rd, Haidian Dist., Beijing, China + Beijing Beijing 100192 + CN -24-A1-0D (hex) IEEE Registration Authority -24A10D (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +18-14-54 (hex) CIG SHANGHAI CO LTD +181454 (base 16) CIG SHANGHAI CO LTD + 5th Floor, Building 8 No 2388 Chenhang Road + SHANGHAI 201114 + CN + +9C-FA-96 (hex) T3 Technology Co., Ltd. +9CFA96 (base 16) T3 Technology Co., Ltd. + No.65/113, Chamnan Phenjati, 12A Floor, Rama9 road + Bangkok Bangkok 10310 + TH + +E4-FE-E4 (hex) Ciena Corporation +E4FEE4 (base 16) Ciena Corporation + 7035 Ridge Road + Hanover MD 21076 US -BC-89-F8 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -BC89F8 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. +80-B5-C6 (hex) OMRON Corporation +80B5C6 (base 16) OMRON Corporation + Shiokoji Horikawa, Shimogyo-ku + Kyoto 600-8530 + JP + +48-78-5B (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +48785B (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN + +60-7A-D8 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +607AD8 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Midea Global Innovation Center,Beijiao Town,Shunde Foshan Guangdong 528311 CN -80-57-22 (hex) Wuxi Sunning Smart Devices Co., Ltd -805722 (base 16) Wuxi Sunning Smart Devices Co., Ltd - No.52, Zone C, Huigu Pioneer Park, Huishan Economic Development Zone, Wuxi - Wuxi jiangsu 214174 - CN +F0-74-C1 (hex) Blink by Amazon +F074C1 (base 16) Blink by Amazon + 100 Riverpark Drive + North Reading MA 01864 + US -F8-F2-F0 (hex) Chipsea Technologies (Shenzhen) Crop. -F8F2F0 (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 +00-0B-7C (hex) Electro-Voice Dynacord LLC +000B7C (base 16) Electro-Voice Dynacord LLC + 130 Perinton Parkway + Fairport NY 14450 + US -E4-2F-37 (hex) Apple, Inc. -E42F37 (base 16) Apple, Inc. +8C-4E-BB (hex) Amazon Technologies Inc. +8C4EBB (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +EC-31-5F (hex) Amazon Technologies Inc. +EC315F (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +70-96-84 (hex) Apple, Inc. +709684 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -64-BD-6D (hex) Apple, Inc. -64BD6D (base 16) Apple, Inc. +CC-BE-61 (hex) Apple, Inc. +CCBE61 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -F8-EF-B1 (hex) Hangzhou Zhongxinghui Intelligent Technology Co., Ltd. -F8EFB1 (base 16) Hangzhou Zhongxinghui Intelligent Technology Co., Ltd. - Room 809, Building B, No. 567 Yueming Road, Xixing Street, - Hangzhou Binjiang Distric 310000 +B8-57-D6 (hex) Cisco Systems, Inc +B857D6 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + 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 -A8-C0-50 (hex) Quectel Wireless Solutions Co.,Ltd. -A8C050 (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 +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 -1C-C3-AB (hex) Espressif Inc. -1CC3AB (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +00-7F-1D (hex) Fantasia Trading LLC +007F1D (base 16) Fantasia Trading LLC + 5350 Ontario Mills Pkwy, Suite 100 + Ontario CA 91764 + US + +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 -B4-B8-53 (hex) Honor Device Co., Ltd. -B4B853 (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 +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 -08-3B-C1 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -083BC1 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 +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 -C0-2E-5F (hex) Zyxel Communications Corporation -C02E5F (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 +00-07-8B (hex) Wegener Communications, Inc. +00078B (base 16) Wegener Communications, Inc. + 930 Interstate Ridge Drive, Ste. A, + Gainesville GA 30501 + US + +00-1A-2A (hex) Arcadyan Corporation +001A2A (base 16) Arcadyan Corporation + 4F., No. 9 , Park Avenue II, + Hsinchu 300 TW -E8-C3-86 (hex) Apple, Inc. -E8C386 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +5C-DC-96 (hex) Arcadyan Corporation +5CDC96 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd., + Hsinchu City 30071, 12345 + TW -00-27-13 (hex) Universal Global Scientific Industrial., Ltd -002713 (base 16) Universal Global Scientific Industrial., Ltd - 141, LANE 351,SEC.1, TAIPING RD. - TSAOTUEN, NANTOU 54261 +74-31-70 (hex) Arcadyan Corporation +743170 (base 16) Arcadyan Corporation + 4F. , No. 9 , Park Avenue II, + Hsinchu 300 TW -CC-52-AF (hex) Universal Global Scientific Industrial., Ltd -CC52AF (base 16) Universal Global Scientific Industrial., Ltd - 141, LANE 351, TAIPING RD. - nan tou NAN-TOU 542 +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 + +18-83-BF (hex) Arcadyan Corporation +1883BF (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 TW -FC-4D-D4 (hex) Universal Global Scientific Industrial., Ltd -FC4DD4 (base 16) Universal Global Scientific Industrial., Ltd - 141, Lane 351, Taiping Rd. Sec. 1, Tsao Tuen, - Nan-Tou Hsien, 542 +1C-C6-3C (hex) Arcadyan Corporation +1CC63C (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 TW -08-3A-88 (hex) Universal Global Scientific Industrial., Ltd -083A88 (base 16) Universal Global Scientific Industrial., Ltd - 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen - Nan-Tou Taiwan 54261 +88-25-2C (hex) Arcadyan Corporation +88252C (base 16) Arcadyan Corporation + 4F., NO.9, Park Avenue II , + Hsinchu 300 TW -20-0D-3D (hex) Quectel Wireless Solutions Co., Ltd. -200D3D (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 +E4-0A-75 (hex) Silicon Laboratories +E40A75 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -F4-2D-C9 (hex) Espressif Inc. -F42DC9 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +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-CB-D8 (hex) Espressif Inc. -B0CBD8 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +D8-5C-11 (hex) Optiview USA +D85C11 (base 16) Optiview USA + 5211 Fairmont Street + Jacksonville FL 32207 + US + +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 -8C-57-9B (hex) WNC Corporation -8C579B (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 +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 -80-EA-23 (hex) WNC Corporation -80EA23 (base 16) WNC Corporation - 20 Park Avenue II, Hsin Science Park, Hsinchu 308, Taiwan - HsinChu Taiwan 308 - TW +18-DC-12 (hex) Silicon Laboratories +18DC12 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -48-A9-D2 (hex) WNC Corporation -48A9D2 (base 16) WNC Corporation - 20 Park Avenue II, Hsin Science Park, Hsinchu 308, Taiwan - HsinChu Taiwan 308 - TW +F8-E0-00 (hex) FUJI ELECTRIC CO., LTD. +F8E000 (base 16) FUJI ELECTRIC CO., LTD. + 1-27, Fuji-cho + Yokkaichi 510-0013 + JP -BC-30-7D (hex) WNC Corporation -BC307D (base 16) WNC Corporation - 20 Park Avenue II, Hsin Science Park, Hsinchu 308, Taiwan - HsinChu Taiwan 308 - TW +8C-98-85 (hex) Samsung Electronics Co.,Ltd +8C9885 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -BC-30-7E (hex) WNC Corporation -BC307E (base 16) WNC Corporation - 20 Park Avenue II. - Hsinchu 30808854 - TW +F8-EC-FE (hex) Owl Home Inc. +F8ECFE (base 16) Owl Home Inc. + 1007n Orange St, 4th Floor Suite 5077 + Wilmington DE 19801 + US -E4-8E-C5 (hex) HUAWEI TECHNOLOGIES CO.,LTD -E48EC5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +E0-E8-05 (hex) SERNET (SUZHOU) TECHNOLOGIES CORPORATION +E0E805 (base 16) SERNET (SUZHOU) TECHNOLOGIES CORPORATION + NO.8 Tangzhuang Road,Suzhou Industrial Park,Su ZhouCity,JiangSu Province,China + Suzhou 215021 CN -4C-E6-5E (hex) HUAWEI TECHNOLOGIES CO.,LTD -4CE65E (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +44-CE-1D (hex) Nokia +44CE1D (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +D0-B6-46 (hex) NXP Semiconductors Taiwan Ltd. +D0B646 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW + +54-73-5A (hex) Huawei Device Co., Ltd. +54735A (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -A4-93-AD (hex) Huawei Device Co., Ltd. -A493AD (base 16) Huawei Device Co., Ltd. +E8-C3-C5 (hex) Huawei Device Co., Ltd. +E8C3C5 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -2C-3A-B1 (hex) Huawei Device Co., Ltd. -2C3AB1 (base 16) Huawei Device Co., Ltd. +C8-30-49 (hex) Huawei Device Co., Ltd. +C83049 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -20-F1-B2 (hex) Tuya Smart Inc. -20F1B2 (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 +D4-2B-6F (hex) Cisco Systems, Inc +D42B6F (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -58-04-4F (hex) TP-Link Systems Inc. -58044F (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US +AC-22-41 (hex) HUAWEI TECHNOLOGIES CO.,LTD +AC2241 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -E0-4F-43 (hex) Universal Global Scientific Industrial., Ltd -E04F43 (base 16) Universal Global Scientific Industrial., Ltd - 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen - Nan-Tou Taiwan 54261 - TW +D0-04-77 (hex) HUAWEI TECHNOLOGIES CO.,LTD +D00477 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -A4-61-85 (hex) Tools for Humanity Corporation -A46185 (base 16) Tools for Humanity Corporation - 650 7th St - San Francisco CA 94103 - US +D4-73-27 (hex) HUAWEI TECHNOLOGIES CO.,LTD +D47327 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -D4-4F-14 (hex) Tesla,Inc. -D44F14 (base 16) Tesla,Inc. - 3500 Deer Creek Rd. - PALO ALTO CA 94304 +34-22-CF (hex) AUMOVIO Systems, Inc. +3422CF (base 16) AUMOVIO Systems, Inc. + 21440 W. Lake Cook Rd. + Deer Park IL 60010 US -2C-9F-FB (hex) WNC Corporation -2C9FFB (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +D0-82-EB (hex) Tuya Smart Inc. +D082EB (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US -1C-D6-BE (hex) WNC Corporation -1CD6BE (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +3C-71-4B (hex) HUMAX NETWORKS +3C714B (base 16) HUMAX NETWORKS + HUMAX VILLAGE, 216Hwangsaeul-ro, Bundang gu + Seongnam-si Gyeonggi-do 13595 + KR -70-61-BE (hex) WNC Corporation -7061BE (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +08-64-80 (hex) Black Sesame Technologies Co., Ltd +086480 (base 16) Black Sesame Technologies Co., Ltd + 29th / 30th Floor, Building A, CHUNG HING TIMES DIGITAL TRADE PORT, No. 79, Xudong Street, Hongshan District, Wuhan City, Hubei Province + Wuhan Hubei 430000 + CN -60-E6-F0 (hex) WNC Corporation -60E6F0 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +C0-BA-1F (hex) Xiamen Milesight IoT Co., Ltd. +C0BA1F (base 16) Xiamen Milesight IoT Co., Ltd. + + + -38-B8-00 (hex) WNC Corporation -38B800 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 +E0-95-59 (hex) Arcadyan Corporation +E09559 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 TW -40-1A-58 (hex) WNC Corporation -401A58 (base 16) WNC Corporation +28-04-7A (hex) WNC Corporation +28047A (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW -00-1B-B1 (hex) WNC Corporation -001BB1 (base 16) WNC Corporation - No. 10-1, Li-hsin Road I, Hsinchu Science Park, - Hsinchu 300 - TW +B8-D0-8F (hex) Quectel Wireless Solutions Co.,Ltd. +B8D08F (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN -2C-DC-AD (hex) WNC Corporation -2CDCAD (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +F8-40-68 (hex) SZ DJI Ronin Technology Co., Ltd. +F84068 (base 16) SZ DJI Ronin Technology Co., Ltd. + Skyworth Semiconductor Design Building18 Gaoxin South 4th Avenue, Nanshan District + shenzhen Guangdong 518057 + CN -B8-B7-F1 (hex) WNC Corporation -B8B7F1 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +24-BA-79 (hex) New H3C Technologies Co., Ltd +24BA79 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN -44-E4-EE (hex) WNC Corporation -44E4EE (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +30-53-5B (hex) Shenzhen Comnect Technology Co.,LTD +30535B (base 16) Shenzhen Comnect Technology Co.,LTD + 5F, Building 8A, Shenzhen International Innovation Valley, Dashi 1st Road, Nanshan District + Shenzhen Guangdong 518055 + CN -7C-A2-36 (hex) Verizon Connect -7CA236 (base 16) Verizon Connect - 5055 North Point Pkwy - Alpharetta GA 30022 +0C-02-5B (hex) Microchip Technology Inc. +0C025B (base 16) Microchip Technology Inc. + 2355 W. Chandler Blvd. + Chandler AZ 85224 US -88-A6-8D (hex) Shanghai MXCHIP Information Technology Co., Ltd. -88A68D (base 16) Shanghai MXCHIP Information Technology Co., Ltd. - 9th Floor, No. 5 Building, 2145 Jinshajiang Rd., Putuo District - Shanghai 200333 +A8-75-4E (hex) Nexlawn Intelligent Technology (Suzhou) Co., Ltd. +A8754E (base 16) Nexlawn Intelligent Technology (Suzhou) Co., Ltd. + 10/F, Building B, No. 7 Qian Zhu Road, Taihu Street, + Suzhou Jiangsu Province 215100 CN -28-2E-89 (hex) WNC Corporation -282E89 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +58-92-04 (hex) zte corporation +589204 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -38-8D-3D (hex) WNC Corporation -388D3D (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +48-09-51 (hex) Guangzhou Trustmo Information System Co.,LTD +480951 (base 16) Guangzhou Trustmo Information System Co.,LTD + 19th Floor, No.123 Tiyu West Road, Tianhe District, Guangzhou City,Guangdong P.R. China + Guangzhou Guangdong 510000 + CN -8C-8B-5B (hex) WNC Corporation -8C8B5B (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +74-E6-65 (hex) Dynabook Technology (Hangzhou) Inc. +74E665 (base 16) Dynabook Technology (Hangzhou) Inc. + 2/F,Building2,NO.3,East Gate,Comprehensive bonded Zone,Qiantang District,Hangzhou,Zhejiang + Hangzhou 310018 + CN -68-EF-A8 (hex) AutomationDirect.com -68EFA8 (base 16) AutomationDirect.com - 3505 Hutchinson Road - Cumming GA 30040 +EC-B2-93 (hex) Hewlett Packard Enterprise +ECB293 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 US -C0-6E-3D (hex) SHENZHEN TECNO TECHNOLOGY -C06E3D (base 16) SHENZHEN TECNO TECHNOLOGY - 101,Building 24,Waijing Industrial Park,Fumin Community,Fucheng Street,Longhua District,Shenzhen City,P.R.China - Shenzhen guangdong 518000 +A4-3C-D4 (hex) JBL Professional +A43CD4 (base 16) JBL Professional + 8500 Balboa Boulevard + Northridge CA 91325 + US + +FC-37-6D (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +FC376D (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 CN -0C-74-74 (hex) Fiberhome Telecommunication Technologies Co.,LTD -0C7474 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +50-DA-9E (hex) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD +50DA9E (base 16) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD + 6-8 Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China + Shenzhen 518052 CN -7C-01-3E (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED -7C013E (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 +3C-8B-6E (hex) Mellanox Technologies, Inc. +3C8B6E (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US -10-83-21 (hex) Yichip Microelectronics (Hangzhou) Co.,Ltd -108321 (base 16) Yichip Microelectronics (Hangzhou) Co.,Ltd - Room 401, Building 15, No.498 Guoshoujing Road, Pudong Software Park - Shanghai 200120 +7C-4F-AD (hex) Espressif Inc. +7C4FAD (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN -18-CC-88 (hex) Hitachi Global Life Solutions, Inc. -18CC88 (base 16) Hitachi Global Life Solutions, Inc. - 390, Muramatsu, - Shimizu Village Shizuoka Prefecture 424-0926 - JP - -34-26-E6 (hex) CIG SHANGHAI CO LTD -3426E6 (base 16) CIG SHANGHAI CO LTD - 5th Floor, Building 8 No 2388 Chenhang Road - SHANGHAI 201114 +90-D7-33 (hex) HUAWEI TECHNOLOGIES CO.,LTD +90D733 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -54-96-CB (hex) AMPAK Technology Inc. -5496CB (base 16) AMPAK Technology Inc. - 6F., No23, Huanke 1st Rd. - Zhubei City Hsinchu County 302047 - TW +1C-5B-A2 (hex) HP GLOBALES MEXICO +1C5BA2 (base 16) HP GLOBALES MEXICO + JAVIER BARROS SIERRA + Mexico ALVARO OBREGON 01376 + MX -D0-17-B7 (hex) Atios AG -D017B7 (base 16) Atios AG - Obere Postmatte 19 - Seedorf Uri 6462 - CH +C4-96-9F (hex) Amazon Technologies Inc. +C4969F (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US -94-27-0E (hex) Intel Corporate -94270E (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +34-4E-E2 (hex) Huawei Device Co., Ltd. +344EE2 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -00-1E-D1 (hex) TKH Security B.V. -001ED1 (base 16) TKH Security B.V. - Werner von Siemensstraat 7 - Zoetermeer ZH 2712PN - NL +7C-85-2F (hex) Huawei Device Co., Ltd. +7C852F (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -90-41-B2 (hex) Ubiquiti Inc -9041B2 (base 16) Ubiquiti Inc - 685 Third Avenue, 27th Floor - New York NY New York NY 10017 - US +78-96-6E (hex) Huawei Device Co., Ltd. +78966E (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -54-39-76 (hex) Groq -543976 (base 16) Groq - 2700 Zanker Road, Suite 150 - San Jose CA 95134 - US +3C-38-1F (hex) Huawei Device Co., Ltd. +3C381F (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN -18-96-77 (hex) Annapurna labs -189677 (base 16) Annapurna labs - Matam Scientific Industries Center, Building 8.2 - Mail box 15123 Haifa 3508409 - IL +DC-87-F8 (hex) Samsung Electronics Co.,Ltd +DC87F8 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -78-5F-A4 (hex) Ciena Corporation -785FA4 (base 16) Ciena Corporation - 7035 Ridge Road - Hanover MD 21076 - US +4C-EC-EE (hex) Samsung Electronics Co.,Ltd +4CECEE (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -98-11-CC (hex) Ciena Corporation -9811CC (base 16) Ciena Corporation - 7035 Ridge Road - Hanover MD 21076 - US +28-72-C6 (hex) Samsung Electronics Co.,Ltd +2872C6 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -90-FB-93 (hex) Renesas Design US Inc. -90FB93 (base 16) Renesas Design US Inc. - 6024 Silver Creek valley Road San Jose, CA 95138 United States - San Jose 95138 +84-19-85 (hex) Samsung Electronics Co.,Ltd +841985 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +5C-17-15 (hex) ODrive Robotics +5C1715 (base 16) ODrive Robotics + 2041 East St PMB 309 + Concord CA 94520 US -6C-A0-42 (hex) Silicon Laboratories -6CA042 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 +D8-CF-B1 (hex) BRIGHT TECHNOLOGIES INDIA PRIVATE LIMITED +D8CFB1 (base 16) BRIGHT TECHNOLOGIES INDIA PRIVATE LIMITED + PLOT 42/4, BLOCK E, OKHLA INDUSTRIAL ESTATE PHASE 2, NEW DELHI + NEW DELHI DELHI 110020 + IN + +0C-26-43 (hex) Cisco Systems, Inc +0C2643 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -F0-D3-2B (hex) Juniper Networks -F0D32B (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 +08-63-8A (hex) Cisco Systems, Inc +08638A (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -18-FD-00 (hex) Marelli -18FD00 (base 16) Marelli - Avenida da Emancipação, 801 - Hostolândia São Paulo 13184-9074 - BR +C0-A4-CF (hex) Nintendo Co.,Ltd +C0A4CF (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP -A4-C1-39 (hex) Dongguan Huayin Electronic Technology Co., Ltd. -A4C139 (base 16) Dongguan Huayin Electronic Technology Co., Ltd. - Room 101, No.8 Xinglong 3rd Road, Shipai Town - Dongguan Guangdong 523000 +2C-4B-14 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +2C4B14 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 CN -60-D8-77 (hex) Phyplus Technology (Shanghai) Co., Ltd -60D877 (base 16) Phyplus Technology (Shanghai) Co., Ltd - 3th Floor, Building 23, 676 Wuxing Road, Pudong New District, Shanghai - Shanghai Shanghai 201204 - CN +08-76-18 (hex) ViTrox Technologies Sdn. Bhd +087618 (base 16) ViTrox Technologies Sdn. Bhd + 746, Persiaran Cassia Selatan 3 Batu Kawan Industrial Park + Bandar Cassia Penang 14110 + MY -84-FC-14 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD -84FC14 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD - NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district - shenzhen guangdong 518000 +A0-5D-0E (hex) ALPSALPINE CO.,LTD. +A05D0E (base 16) ALPSALPINE CO.,LTD. + nishida 6-1 + Kakuda-City Miyagi-Pref 981-1595 + JP + +60-F7-23 (hex) Beijing Xiaomi Mobile Software Co., Ltd +60F723 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -68-AB-A9 (hex) Sagemcom Broadband SAS -68ABA9 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 +FC-66-37 (hex) Sagemcom Broadband SAS +FC6637 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 FR -6C-76-F7 (hex) MainStreaming SpA -6C76F7 (base 16) MainStreaming SpA - Viale Sarca, 336/F - Milan MI 20126 - IT +64-FD-96 (hex) Sagemcom Broadband SAS +64FD96 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR -40-A4-4A (hex) Google, Inc. -40A44A (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 - US +E4-C0-E2 (hex) Sagemcom Broadband SAS +E4C0E2 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR -D8-3A-36 (hex) AltoBeam Inc. -D83A36 (base 16) AltoBeam Inc. +3C-58-36 (hex) Sagemcom Broadband SAS +3C5836 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +70-23-93 (hex) Polytech A/S +702393 (base 16) Polytech A/S + Thalkirchner Str. 210, Geb. 6 + 81371 München 0 + DE + +6C-A6-13 (hex) AltoBeam Inc. +6CA613 (base 16) AltoBeam Inc. B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian Beijing Beijing 100083 CN -F0-55-82 (hex) Arashi Vision Inc. -F05582 (base 16) Arashi Vision Inc. - Room 1101, 1102, 1103, 11th Floor, Building 2, Jinlitong Financial Center, 1100 Xingye Road, Haiwang Community, Xin'an Street, Bao'an District, Shenzhen, Guangdong, China - Shenzhen 518000 +58-18-B4 (hex) Chengdu Quanjing Intelligent Technology Co.,Ltd +5818B4 (base 16) Chengdu Quanjing Intelligent Technology Co.,Ltd + Building A2, Chi Yuen Technology Park, 1001 College Avenue, Nanshan District, Shenzhen,P.R.C. + Shenzhen Guangdong 518000 CN -F0-BC-50 (hex) Mellanox Technologies, Inc. -F0BC50 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +D0-0C-5E (hex) Nanjing Qinheng Microelectronics Co., Ltd. +D00C5E (base 16) Nanjing Qinheng Microelectronics Co., Ltd. + No.18, Ningshuang Road + Nanjing Jiangsu 210012 + CN -00-18-4D (hex) NETGEAR -00184D (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +38-14-A1 (hex) LG Innotek +3814A1 (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR -00-1E-2A (hex) NETGEAR -001E2A (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +30-89-EC (hex) Nintendo Co.,Ltd +3089EC (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP -00-14-6C (hex) NETGEAR -00146C (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +7C-90-E9 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +7C90E9 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN -E0-91-F5 (hex) NETGEAR -E091F5 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +00-05-21 (hex) Schneider Electric +000521 (base 16) Schneider Electric + 35 Rue Joseph Monier + Rueil-Malmaison 92500 + CA -74-44-01 (hex) NETGEAR -744401 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +C4-42-68 (hex) CRESTRON ELECTRONICS, INC. +C44268 (base 16) CRESTRON ELECTRONICS, INC. + 88 Ramland Road + Orangeburg NJ 10962 US -44-94-FC (hex) NETGEAR -4494FC (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +30-09-16 (hex) Apple, Inc. +300916 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -20-E5-2A (hex) NETGEAR -20E52A (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +60-2E-D5 (hex) Apple, Inc. +602ED5 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -B0-39-56 (hex) NETGEAR -B03956 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +7C-4F-CD (hex) Apple, Inc. +7C4FCD (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -0C-F2-F5 (hex) Sichuan AI-Link Technology Co., Ltd. -0CF2F5 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 - CN +F4-A1-A6 (hex) Apple, Inc. +F4A1A6 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -D0-45-8D (hex) Shenzhen Skyworth Digital Technology CO., Ltd -D0458D (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 - CN +14-D5-37 (hex) All Inspire Health Inc. +14D537 (base 16) All Inspire Health Inc. + 19 Morris Avenue, Building 128, Cumberland Gate + Brooklyn NY 11205 + US -3C-1A-CC (hex) Quectel Wireless Solutions Co.,Ltd. -3C1ACC (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN +FC-B9-48 (hex) McScience Inc. +FCB948 (base 16) McScience Inc. + B-1102, Digital Empire Bldg. 1556-16 Deogyeong Blvd., Yeongtong + Suwon 16690 + KR -A4-B2-56 (hex) Shenzhen Incar Technology Co., Ltd. -A4B256 (base 16) Shenzhen Incar Technology Co., Ltd. - 18th Floor, Zhongxi ECO Building, Shuiku Road, Xixiang Street, Bao'an District, - Shenzhen City Guangdong Province 518102 - CN +44-1D-E5 (hex) XCENA Inc. +441DE5 (base 16) XCENA Inc. + 8F, 20, Pangyoyeok-ro 241 beon-gil, Bundang-gu + Seongnam-si Gyeonggi-do 13494 + KR -9C-24-10 (hex) Bouffalo Lab (Nanjing) Co., Ltd. -9C2410 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. - 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China - Nanjing Jiangsu 211800 +58-F6-58 (hex) Edifier International +58F658 (base 16) Edifier International + Suit 2207, 22nd floor, Tower II, Lippo centre, 89 Queensway + Hong Kong 070 CN -D8-E3-74 (hex) Xiaomi Communications Co Ltd -D8E374 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +E0-D2-39 (hex) TECHNOLID, LLC +E0D239 (base 16) TECHNOLID, LLC + Nobelya, 7 + Moscow 121205 + RU -94-18-65 (hex) NETGEAR -941865 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +A0-87-BE (hex) Apple, Inc. +A087BE (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -E0-46-EE (hex) NETGEAR -E046EE (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +34-F8-DD (hex) Apple, Inc. +34F8DD (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -60-3F-FB (hex) Telink Micro LLC -603FFB (base 16) Telink Micro LLC - 2975 Scott Blvd #120 - Santa Clara 95054 +7C-D6-2C (hex) Apple, Inc. +7CD62C (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -AC-EA-70 (hex) ZUNDA Inc. -ACEA70 (base 16) ZUNDA Inc. - 3/F Kamon Bldg, Shibuya 2-6-11 - Shibuya-ku Tokyo 150-0002 - JP - -20-E1-5D (hex) TP-Link Systems Inc. -20E15D (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 +6C-68-8A (hex) Amazon Technologies Inc. +6C688A (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -1C-04-60 (hex) NXP Semiconductors Taiwan Ltd. -1C0460 (base 16) NXP Semiconductors Taiwan Ltd. - No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan - Nanzi Dist. Kaohsiung 811643 +94-0F-3B (hex) ASKEY COMPUTER CORP +940F3B (base 16) ASKEY COMPUTER CORP + 10F,No.119,JIANKANG RD,ZHONGHE DIST + NEW TAIPEI TAIWAN 23585 TW -38-FB-A0 (hex) Shenzhen Baseus Technology CoLtd -38FBA0 (base 16) Shenzhen Baseus Technology CoLtd - 2nd Floor, Building B, Baseus Intelligence Park, No.2008, Xuegang Rd,Gangtou Community,Bantian Street, Longgang District - Shenzhen 518172 - CN - -C4-EA-1D (hex) Vantiva Technologies Belgium -C4EA1D (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -A4-91-B1 (hex) Vantiva Technologies Belgium -A491B1 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -D4-35-1D (hex) Vantiva Technologies Belgium -D4351D (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE +6C-92-F6 (hex) Arista Networks +6C92F6 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US -EC-5B-71 (hex) Inventec(Chongqing) Corporation -EC5B71 (base 16) Inventec(Chongqing) Corporation - No.66 West District 2nd Rd, Shapingba District - Chongqing Chongqing 401331 - CN +7C-A0-5E (hex) Motorola Mobility LLC, a Lenovo Company +7CA05E (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US -74-D0-82 (hex) HISENSE VISUAL TECHNOLOGY CO.,LTD -74D082 (base 16) HISENSE VISUAL TECHNOLOGY CO.,LTD - Qianwangang Road 218 - Qingdao Shandong 266510 - CN +AC-23-6E (hex) Apismoon Electric Pte. Ltd. +AC236E (base 16) Apismoon Electric Pte. Ltd. + Blk 6, #04-24, JTC DEFU INDUSTRIAL CITY, DEFU SOUTH STREET 1, SINGAPORE 533757, SG + SINGAPORE 533757 + SG -98-53-5F (hex) HUAWEI TECHNOLOGIES CO.,LTD -98535F (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +30-FD-34 (hex) COMMANDO Networks Pvt Ltd. +30FD34 (base 16) COMMANDO Networks Pvt Ltd. + Millennium Business Park - IT Park + Navi Mumbai MH 400710 + IN -D8-78-F0 (hex) Silicon Laboratories -D878F0 (base 16) Silicon Laboratories +FC-11-19 (hex) Silicon Laboratories +FC1119 (base 16) Silicon Laboratories 400 West Cesar Chavez Austin TX 78701 US -2C-2F-F4 (hex) eero inc. -2C2FF4 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +F0-C7-1F (hex) Arista Networks +F0C71F (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 US -58-98-35 (hex) Vantiva Technologies Belgium -589835 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -3C-49-FF (hex) UNIONMAN TECHNOLOGY CO.,LTD -3C49FF (base 16) UNIONMAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 - CN - -00-7A-A4 (hex) FRITZ! Technology GmbH -007AA4 (base 16) FRITZ! Technology GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -20-B8-3D (hex) UNIONMAN TECHNOLOGY CO.,LTD -20B83D (base 16) UNIONMAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 - CN - -4C-E6-50 (hex) Apple, Inc. -4CE650 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +00-03-CA (hex) Temposonics, LLC +0003CA (base 16) Temposonics, LLC + 3001 Sheldon Drive + Cary, NC 27513 US -24-55-9A (hex) Apple, Inc. -24559A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +E4-D4-36 (hex) Nokia Solutions and Networks India Private Limited +E4D436 (base 16) Nokia Solutions and Networks India Private Limited + Plot 45, Fathima NagarNemilicherry,Chrompet + Chennai Taminadu 600044 + IN -24-53-ED (hex) Dell Inc. -2453ED (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 +60-3F-FB (hex) Telink Micro LLC +603FFB (base 16) Telink Micro LLC + 2975 Scott Blvd #120 + Santa Clara CA 95054 US -B0-FB-15 (hex) Ezurio, LLC -B0FB15 (base 16) Ezurio, LLC - 3F.-1, No.145, Xianzheng 9th Rd., - Zhubei 30251 - TW - -EC-C0-7A (hex) Ezurio, LLC -ECC07A (base 16) Ezurio, LLC - 3F.-1, No.145, Xianzheng 9th Rd., - Zhubei 30251 - TW - -A4-CF-03 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. -A4CF03 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. - No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. - Chongqing China 401120 - CN - -FC-E6-C6 (hex) China Mobile Group Device Co.,Ltd. -FCE6C6 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -6C-3E-51 (hex) Mindray North America -6C3E51 (base 16) Mindray North America - 800 MacArthur Blvd - Mahwah NJ 07430 +B0-D6-16 (hex) Super Micro Computer, Inc. +B0D616 (base 16) Super Micro Computer, Inc. + 980 Rock Ave + San Jose 95131 US -14-18-26 (hex) Nokia -141826 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA - -20-E5-9B (hex) Panasonic Automotive Systems -20E59B (base 16) Panasonic Automotive Systems - 1578-5 , Uegawa-chou - Matsusaka Mie 5150041 - JP - -0C-E7-09 (hex) Sentyron B.V -0CE709 (base 16) Sentyron B.V - P.O. box 638 - Delft ZH 2600 AP - NL +24-7F-20 (hex) Sagemcom Broadband SAS +247F20 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -B0-82-E2 (hex) ASUSTek COMPUTER INC. -B082E2 (base 16) ASUSTek COMPUTER INC. - 15,Li-Te Rd., Peitou, Taipei 112, Taiwan - Taipei Taiwan 112 - TW +88-A6-C6 (hex) Sagemcom Broadband SAS +88A6C6 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -6C-68-A4 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. -6C68A4 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. - 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District - Guangzhou Guangdong 510663 - CN +90-4D-4A (hex) Sagemcom Broadband SAS +904D4A (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -90-C9-52 (hex) Durin, Inc -90C952 (base 16) Durin, Inc - 440 N Wolfe Rd - Sunnyvale CA 94085 - US +40-65-A3 (hex) Sagemcom Broadband SAS +4065A3 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -DC-22-6F (hex) HangZhou Nano IC Technologies Co., Ltd -DC226F (base 16) HangZhou Nano IC Technologies Co., Ltd - No. 11, F Building, 1st Floor, Building A, Tiantang Software Park, 3 West Doudimen Road, Xihu District - Hangzhou Zhejiang 310013 - CN +00-1E-74 (hex) Sagemcom Broadband SAS +001E74 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -0C-C7-63 (hex) eero inc. -0CC763 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US +B8-66-85 (hex) Sagemcom Broadband SAS +B86685 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -EC-1A-C3 (hex) Ugreen Group Limited -EC1AC3 (base 16) Ugreen Group Limited - 4F, Plant 6, 1F-6/F, Block 7, YuAn Zone, Gaofeng Community, Dalang Street, Longhua District - Shenzhen Guangdong 518109 - CN +30-24-78 (hex) Sagemcom Broadband SAS +302478 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -E4-73-19 (hex) Huawei Device Co., Ltd. -E47319 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +D8-A7-56 (hex) Sagemcom Broadband SAS +D8A756 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -BC-BC-CA (hex) Huawei Device Co., Ltd. -BCBCCA (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +70-0B-01 (hex) Sagemcom Broadband SAS +700B01 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -B8-64-68 (hex) BBSakura Networks, Inc. -B86468 (base 16) BBSakura Networks, Inc. - Sumitomo Fudosan Nishishinjuku Building, 7-20-1 Nishi-shinjuku - Shinjuku-ku Tokyo 160-0023 - JP +5C-8D-FD (hex) Clariphotonics CO., Ltd +5C8DFD (base 16) Clariphotonics CO., Ltd + 7F-2, No. 20, Fuxing 1st Rd., Zhubei + Zhubei 30256 + TW -A0-FD-D9 (hex) UNIONMAN TECHNOLOGY CO.,LTD -A0FDD9 (base 16) UNIONMAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 - CN +64-79-99 (hex) Sagemcom Broadband SAS +647999 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -BC-2B-1E (hex) Cresyn Co., Ltd. -BC2B1E (base 16) Cresyn Co., Ltd. - CRESYN B/D, Gangnam-daero 107-gil, Seocho-gu, Seoul - Seoul CRESYN B/D, Gangnam-daero 107-gil 06254 - KR +94-3C-96 (hex) Sagemcom Broadband SAS +943C96 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -08-6A-0B (hex) Cisco Meraki -086A0B (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US +2C-F2-A5 (hex) Sagemcom Broadband SAS +2CF2A5 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -C8-63-40 (hex) Cisco Meraki -C86340 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US +C4-EB-39 (hex) Sagemcom Broadband SAS +C4EB39 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -A4-C0-B0 (hex) Drivenets -A4C0B0 (base 16) Drivenets - 1st Zarhin St. - Raanana Israel 4366235 - IL +68-AB-A9 (hex) Sagemcom Broadband SAS +68ABA9 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -34-F0-15 (hex) Beijing Xiaomi Mobile Software Co., Ltd -34F015 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN +CC-FA-F1 (hex) Sagemcom Broadband SAS +CCFAF1 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -A0-F2-62 (hex) Espressif Inc. -A0F262 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +A0-3C-20 (hex) Sagemcom Broadband SAS +A03C20 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -A0-58-66 (hex) Qorvo Utrecht B.V. -A05866 (base 16) Qorvo Utrecht B.V. - Leidseveer 10 - Utrecht Utrecht 3511 SB - NL +6C-BA-B8 (hex) Sagemcom Broadband SAS +6CBAB8 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -7C-6D-12 (hex) Microsoft Corporation -7C6D12 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US +8C-C5-B4 (hex) Sagemcom Broadband SAS +8CC5B4 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -98-A9-42 (hex) Tozed Kangwei Tech Co., Ltd -98A942 (base 16) Tozed Kangwei Tech Co., Ltd - Room 1301, NO. 37 Jinlong , Nansha Street, Xiangjiang Financial Business Center, Nansha District - Guangzhou Guangdong 511458 - CN +D4-F8-29 (hex) Sagemcom Broadband SAS +D4F829 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -28-D6-EC (hex) HUAWEI TECHNOLOGIES CO.,LTD -28D6EC (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +18-6A-81 (hex) Sagemcom Broadband SAS +186A81 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -AC-E0-11 (hex) HUAWEI TECHNOLOGIES CO.,LTD -ACE011 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +A0-7F-8A (hex) Sagemcom Broadband SAS +A07F8A (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -CC-C4-B2 (hex) Shenzhen Trolink Technology Co.,LTD -CCC4B2 (base 16) Shenzhen Trolink Technology Co.,LTD - 7th Floor, Building 5, Phase 2, Donghua Intelligent Manufacturing Park, Sanwei Community, Hangcheng Street, Bao'an District - Shenzhen Guangdong 518101 - CN +C4-EB-43 (hex) Sagemcom Broadband SAS +C4EB43 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -60-B4-A2 (hex) Samsung Electronics Co.,Ltd -60B4A2 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +C4-EB-41 (hex) Sagemcom Broadband SAS +C4EB41 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -14-0B-9E (hex) Samsung Electronics Co.,Ltd -140B9E (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +A0-8E-78 (hex) Sagemcom Broadband SAS +A08E78 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -64-CE-0C (hex) Funshion Online Technologies Co.,Ltd -64CE0C (base 16) Funshion Online Technologies Co.,Ltd - 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing - Beijing 100029 - CN +D8-F1-5B (hex) Espressif Inc. +D8F15B (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -84-48-80 (hex) Amazon Technologies Inc. -844880 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +E0-98-06 (hex) Espressif Inc. +E09806 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -FC-26-8C (hex) Signify B.V. -FC268C (base 16) Signify B.V. - High Tech Campus 7 - Eindhoven 5656AE - NL +F4-CF-A2 (hex) Espressif Inc. +F4CFA2 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -30-F0-3A (hex) UEI Electronics Private Ltd. -30F03A (base 16) UEI Electronics Private Ltd. - #49, 1 st floor, East wing, Khanjabhavan, Racecourse Road. - Bengaluru Karnataka 560001 - IN +10-52-1C (hex) Espressif Inc. +10521C (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -E8-3D-C1 (hex) Espressif Inc. -E83DC1 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +3C-81-D8 (hex) Sagemcom Broadband SAS +3C81D8 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -24-71-21 (hex) Cisco Systems, Inc -247121 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +40-F2-01 (hex) Sagemcom Broadband SAS +40F201 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -BC-AB-F5 (hex) Cisco Systems, Inc -BCABF5 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +D0-84-B0 (hex) Sagemcom Broadband SAS +D084B0 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -B8-C9-24 (hex) Cisco Systems, Inc -B8C924 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +00-37-B7 (hex) Sagemcom Broadband SAS +0037B7 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -88-13-C2 (hex) Tendyron Corporation -8813C2 (base 16) Tendyron Corporation - Tendyron Building,Zhongguancun NO.1 Park,Beiqing Road,Haidian District,Beijing,China - Beijing 100000 - CN +F4-2D-C9 (hex) Espressif Inc. +F42DC9 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -00-12-4F (hex) Chemelex LLC -00124F (base 16) Chemelex LLC - 1665 Utica Avenue, Suite 700 - St Louis Park MN 55416 - US +B0-CB-D8 (hex) Espressif Inc. +B0CBD8 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -F0-30-12 (hex) AUMOVIO Autonomous Mobility Germany GmbH -F03012 (base 16) AUMOVIO Autonomous Mobility Germany GmbH - Ringlerstr. 17 - Ingolstadt 85057 - DE +44-BD-8D (hex) Espressif Inc. +44BD8D (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -B4-DD-D0 (hex) AUMOVIO Hungary Kft. -B4DDD0 (base 16) AUMOVIO Hungary Kft. - Napmátka u. 6. - Budapest Pest H-1106 - HU +C8-85-41 (hex) Espressif Inc. +C88541 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -44-BD-C8 (hex) Xiaomi Communications Co Ltd -44BDC8 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 - CN +54-64-D9 (hex) Sagemcom Broadband SAS +5464D9 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -30-48-7D (hex) Tuya Smart Inc. -30487D (base 16) Tuya Smart Inc. - 160 Greentree Drive, Suite 101 - Dover DE 19904 - US +18-62-2C (hex) Sagemcom Broadband SAS +18622C (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -94-2D-3A (hex) PRIZOR VIZTECH LIMITED -942D3A (base 16) PRIZOR VIZTECH LIMITED - 514, Maple Trade Ctr Rd, near Surdhara Circle, Thaltej - Ahmedabad Gujarat 380052 - IN +E8-F6-0A (hex) Espressif Inc. +E8F60A (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -08-18-14 (hex) Hewlett Packard Enterprise -081814 (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 - US +90-64-9B (hex) Espressif Inc. +90649B (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -D0-CD-BF (hex) LG Electronics -D0CDBF (base 16) LG Electronics - 222 LG-ro, JINWI-MYEON - Pyeongtaek-si Gyeonggi-do 451-713 - KR +30-AE-A4 (hex) Espressif Inc. +30AEA4 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -A4-F0-1F (hex) CANON INC. -A4F01F (base 16) CANON INC. - 30-2 Shimomaruko 3-chome, - Ohta-ku Tokyo 146-8501 - JP +FC-4A-47 (hex) Nokia +FC4A47 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -90-B9-F9 (hex) Motorola Mobility LLC, a Lenovo Company -90B9F9 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +4C-75-25 (hex) Espressif Inc. +4C7525 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -04-6F-00 (hex) LG Electronics -046F00 (base 16) LG Electronics - Lot CN02, Trang Due Industrial Park,An Phong Ward, Hai Phong City, Vietnam - Hai Phong 184956 - VN +9C-9C-1F (hex) Espressif Inc. +9C9C1F (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -94-EA-E7 (hex) Lynq Technologies -94EAE7 (base 16) Lynq Technologies - 11101 West 120th Avenue - Broomfield CO 80021 - US +98-CD-AC (hex) Espressif Inc. +98CDAC (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -90-8A-80 (hex) Cisco Systems, Inc -908A80 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +34-86-5D (hex) Espressif Inc. +34865D (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -00-B4-63 (hex) Ring LLC -00B463 (base 16) Ring LLC - 12515 Cerise Ave - Hawthorne 90250 - US +84-F7-03 (hex) Espressif Inc. +84F703 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -D8-3E-EB (hex) AltoBeam Inc. -D83EEB (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN +5C-01-3B (hex) Espressif Inc. +5C013B (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -5C-48-79 (hex) HUAWEI TECHNOLOGIES CO.,LTD -5C4879 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +28-37-2F (hex) Espressif Inc. +28372F (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -DC-57-5C (hex) PR Electronics A/S -DC575C (base 16) PR Electronics A/S - Lerbakken 10 - Følle 8410 - DK +14-33-5C (hex) Espressif Inc. +14335C (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -5C-13-AC (hex) Apple, Inc. -5C13AC (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +98-88-E0 (hex) Espressif Inc. +9888E0 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -0C-E5-A1 (hex) Apple, Inc. -0CE5A1 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +98-A3-16 (hex) Espressif Inc. +98A316 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -0C-A3-B2 (hex) Apple, Inc. -0CA3B2 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +1C-9D-C2 (hex) Espressif Inc. +1C9DC2 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -54-6C-50 (hex) Nanjing Qinheng Microelectronics Co., Ltd. -546C50 (base 16) Nanjing Qinheng Microelectronics Co., Ltd. - No.18, Ningshuang Road - Nanjing Jiangsu 210012 - CN +4C-EB-D6 (hex) Espressif Inc. +4CEBD6 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -E4-85-FB (hex) Quectel Wireless Solutions Co.,Ltd. -E485FB (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN +D4-F9-8D (hex) Espressif Inc. +D4F98D (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -24-2A-EA (hex) Apple, Inc. -242AEA (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +E8-31-CD (hex) Espressif Inc. +E831CD (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -98-E8-59 (hex) Apple, Inc. -98E859 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +D8-BC-38 (hex) Espressif Inc. +D8BC38 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -B8-BA-66 (hex) Microsoft Corporation -B8BA66 (base 16) Microsoft Corporation - One Microsoft Way - REDMOND WA 98052 - US +CC-7B-5C (hex) Espressif Inc. +CC7B5C (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -60-65-F4 (hex) Chipsea Technologies (Shenzhen) Crop. -6065F4 (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 +FC-E8-C0 (hex) Espressif Inc. +FCE8C0 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -44-66-90 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -446690 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN +D4-D4-DA (hex) Espressif Inc. +D4D4DA (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -38-47-12 (hex) Luxottica Tristar (Dongguan) Optical Co.,Ltd -384712 (base 16) Luxottica Tristar (Dongguan) Optical Co.,Ltd - Oudeng Zone, Gaobu Town, Dongguan City, GuangDong, China - Dongguan GuangDong 523000 - CN +40-4C-CA (hex) Espressif Inc. +404CCA (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -64-B4-E8 (hex) Shenzhen D-Robotics Co., Ltd. -64B4E8 (base 16) Shenzhen D-Robotics Co., Ltd. - Room 1107-1108, Block C, Building 1, Section 1, Chuangzhi Yuncheng, Nanshan District - Shenzhen Guangdong 518000 - CN +FC-B4-67 (hex) Espressif Inc. +FCB467 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -58-9E-C6 (hex) Gigaset Technologies GmbH -589EC6 (base 16) Gigaset Technologies GmbH - Frankenstrasse 2 - Bocholt NRW 46395 - DE +88-57-21 (hex) Espressif Inc. +885721 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -E8-47-F3 (hex) upscale ai -E847F3 (base 16) upscale ai - 3101 Jay St. - Santa Clara CA 95054 - US +B8-1F-3F (hex) Espressif Inc. +B81F3F (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -B0-7A-16 (hex) ROEHN -B07A16 (base 16) ROEHN - Av. Manuel Bandeira, 291 - Sao Paulo Sp 05317020 - BR +90-DA-72 (hex) Espressif Inc. +90DA72 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -28-AD-EA (hex) Mallow SAS -28ADEA (base 16) Mallow SAS - 229, rue St Honore - Paris IDF 75001 - FR +14-2B-2F (hex) Espressif Inc. +142B2F (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -98-12-23 (hex) Tarmoc Network LTD -981223 (base 16) Tarmoc Network LTD - Taihao Road No.22, 6th Floor, Sandong Town, Huicheng District, Huizhou City - Huizhou City GuangDong 518000 - CN +48-CA-43 (hex) Espressif Inc. +48CA43 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -84-C6-65 (hex) Taicang T&W Electronics -84C665 (base 16) Taicang T&W Electronics - 89# Jiang Nan RD - Suzhou Jiangsu 215412 - CN +E8-06-90 (hex) Espressif Inc. +E80690 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -B8-61-FC (hex) Juniper Networks -B861FC (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US +40-F5-20 (hex) Espressif Inc. +40F520 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -08-3C-03 (hex) IEEE Registration Authority -083C03 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +08-3A-F2 (hex) Espressif Inc. +083AF2 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -40-68-F9 (hex) Shenzhen SuperElectron Technology Co.,Ltd. -4068F9 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. - 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city - Shenzhen Guangdong 518000 - CN +94-B9-7E (hex) Espressif Inc. +94B97E (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -14-49-C5 (hex) Huawei Device Co., Ltd. -1449C5 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +34-CD-B0 (hex) Espressif Inc. +34CDB0 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -B4-54-F2 (hex) Huawei Device Co., Ltd. -B454F2 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +94-54-C5 (hex) Espressif Inc. +9454C5 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -80-C2-F0 (hex) Xiamen Yeastar Digital Technology Co.,Ltd -80C2F0 (base 16) Xiamen Yeastar Digital Technology Co.,Ltd - Building C09, Software Park Phase III, Xiamen 361024, Fujian, China - XIAMEN FUJIAN 361024 - CN +78-42-1C (hex) Espressif Inc. +78421C (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -FC-66-37 (hex) Sagemcom Broadband SAS -FC6637 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR +74-8B-23 (hex) Samsung Electronics Co.,Ltd +748B23 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR -2C-F8-EC (hex) Quectel Wireless Solutions Co.,Ltd. -2CF8EC (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District +20-0D-3D (hex) Quectel Wireless Solutions Co.,Ltd. +200D3D (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 -68-F9-0F (hex) Intel Corporate -68F90F (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -6C-91-88 (hex) Nokia -6C9188 (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +4C-80-FB (hex) Google, Inc. +4C80FB (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US -00-09-D8 (hex) Telia Company AB -0009D8 (base 16) Telia Company AB - Östermalmsgatan 63A - Umeå 903 35 - SE +98-F1-99 (hex) NEC Platforms, Ltd. +98F199 (base 16) NEC Platforms, Ltd. + 14-1, Shiba 4-chome + Minato-ku Tokyo 108-8556 + JP -34-6F-3F (hex) Hon Hai Precision Industry Co.,LTD -346F3F (base 16) Hon Hai Precision Industry Co.,LTD - 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI CITY,23678 , TAIWAN CHINA - TAIPEI 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI 33859 - CN +1C-7C-98 (hex) NEC Platforms, Ltd. +1C7C98 (base 16) NEC Platforms, Ltd. + 14-1, Shiba 4-chome + Minato-ku Tokyo 108-8556 + JP -54-A0-AB (hex) Maiyue Future Intelligent Technology (Suzhou) Co.,Ltd. -54A0AB (base 16) Maiyue Future Intelligent Technology (Suzhou) Co.,Ltd. - Room 1283, Building 3, No.288 Jiushenggang Road, Guoxiang Street, Economic Development Zone, Suzhou,China - Suzhou 215000 - CN +44-5E-82 (hex) Zyxel Communications Corporation +445E82 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW -DC-7E-F5 (hex) HUAWEI TECHNOLOGIES CO.,LTD -DC7EF5 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +A0-CA-4A (hex) AltoBeam Inc. +A0CA4A (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -D0-8E-17 (hex) ACCTON TECHNOLOGY CORPORATION -D08E17 (base 16) ACCTON TECHNOLOGY CORPORATION +1C-4A-3B (hex) ACCTON TECHNOLOGY CORPORATION +1C4A3B (base 16) ACCTON TECHNOLOGY CORPORATION No.1, Creation Road 3, Hsinchu Science Park, Hsinchu 30077 TW -E4-C8-01 (hex) BLU Products Inc -E4C801 (base 16) BLU Products Inc - 8600 NW 36th Street Suite 200 - Miami FL 33166 - US - -8C-69-14 (hex) FREEBOX SAS -8C6914 (base 16) FREEBOX SAS - 16 rue de la Ville l'Eveque - PARIS IdF 75008 - FR - -8C-D0-66 (hex) Texas Instruments -8CD066 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +74-A2-35 (hex) IEEE Registration Authority +74A235 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -34-20-D3 (hex) SHENZHEN IP-COM NETWORKS CO.,LTD. -3420D3 (base 16) SHENZHEN IP-COM NETWORKS CO.,LTD. - Room 101, Unit A, First Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China - SHENZHEN Guangdong Province 518052 +78-44-FD (hex) TP-LINK TECHNOLOGIES CO.,LTD. +7844FD (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -78-D3-66 (hex) GuangZhou Dazzleview Intelligent Technology Co., Ltd -78D366 (base 16) GuangZhou Dazzleview Intelligent Technology Co., Ltd - 4th Floor, Building33, No.2519 Xingang East Road - Haizhu District Guangzhou 510000 +F4-84-8D (hex) TP-LINK TECHNOLOGIES CO.,LTD. +F4848D (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -24-64-77 (hex) Beijing Xiaomi Mobile Software Co., Ltd -246477 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +68-77-24 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +687724 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -64-6B-E7 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. -646BE7 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. - No.218 Qianwangang Road - Qingdao Shangdong 266510 +40-3F-8C (hex) TP-LINK TECHNOLOGIES CO.,LTD. +403F8C (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -7C-B4-0F (hex) Fibocom Wireless Inc. -7CB40F (base 16) Fibocom Wireless Inc. - 1101,Tower A, Building 6, Shenzhen International Innovation Valley, Dashi 1st Rd, Nanshan - Shenzhen Guangdong 518055 +B0-06-EC (hex) Nexquome Pte Limited +B006EC (base 16) Nexquome Pte Limited + 200 Cantoment Road#05-03 Southpoint + SINGAPORE 089763 + SG + +38-16-B3 (hex) HP Inc. +3816B3 (base 16) HP Inc. + 10300 Energy Dr + Spring TX 77389 + US + +44-66-90 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +446690 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -50-9F-B9 (hex) Shenzhen Skyworth Digital Technology CO., Ltd -509FB9 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 +3C-33-00 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +3C3300 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO 268,Fuqian Rd,Jutang Community,Guanlan town , LongHua new district + Shenzhen Guangdong 518110 CN -24-8A-B3 (hex) ICTK Co., Ltd. -248AB3 (base 16) ICTK Co., Ltd. - 13F, JACE Tower, 16, Gangnam-daero 84-gil, Gangnam-gu - Seoul Select State 06241 - KR +AC-A2-13 (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +ACA213 (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO 268,Fuqian Rd,Jutang Community + shenzhen guangdong 518110 + CN -10-D8-B1 (hex) AUO Corporation -10D8B1 (base 16) AUO Corporation - No. 1, Li-Hsin Rd. 2, Hsinchu Science Park, - Hsinchu 300094 - TW +44-33-4C (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +44334C (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO 268 + Shenzhen Guangdong 518110 + CN -44-63-C2 (hex) Zyxel Communications Corporation -4463C2 (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW +4C-10-D5 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +4C10D5 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN -9C-FA-96 (hex) T3 Technology Co., Ltd. -9CFA96 (base 16) T3 Technology Co., Ltd. - No.65/113, Chamnan Phenjati, 12A Floor, Rama9 road - Bangkok Bangkok 10310 - TH +60-29-2B (hex) TP-LINK TECHNOLOGIES CO.,LTD. +60292B (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN -E4-FE-E4 (hex) Ciena Corporation -E4FEE4 (base 16) Ciena Corporation - 7035 Ridge Road - Hanover MD 21076 +9C-BC-A6 (hex) Hewlett Packard Enterprise +9CBCA6 (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 US -80-B5-C6 (hex) OMRON Corporation -80B5C6 (base 16) OMRON Corporation - Shiokoji Horikawa, Shimogyo-ku - Kyoto 600-8530 - JP +98-2A-C3 (hex) Silicon Laboratories +982AC3 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -60-7A-D8 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -607AD8 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 +80-EA-07 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +80EA07 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -48-78-5B (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -48785B (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 +F4-B1-9C (hex) AltoBeam Inc. +F4B19C (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -F0-74-C1 (hex) Blink by Amazon -F074C1 (base 16) Blink by Amazon - 100 Riverpark Drive - North Reading MA 01864 - US - -7C-62-E7 (hex) Cisco Systems, Inc -7C62E7 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +28-80-6E (hex) Pirelli Tyre S.p.A. +28806E (base 16) Pirelli Tyre S.p.A. + Via Piero e Alberto Pirelli 25 + Milan Lombardia 20126 + IT -18-14-54 (hex) CIG SHANGHAI CO LTD -181454 (base 16) CIG SHANGHAI CO LTD - 5th Floor, Building 8 No 2388 Chenhang Road - SHANGHAI 201114 +50-D4-F7 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +50D4F7 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 CN -00-0B-7C (hex) Electro-Voice Dynacord LLC -000B7C (base 16) Electro-Voice Dynacord LLC - 130 Perinton Parkway - Fairport NY 14450 +68-2E-3C (hex) Ubiquiti Inc +682E3C (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 US -BC-00-23 (hex) Honor Device Co., Ltd. -BC0023 (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 +58-8F-A7 (hex) LG Innotek +588FA7 (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR -88-7C-C1 (hex) Zebronics India Pvt Ltd -887CC1 (base 16) Zebronics India Pvt Ltd - No 13/7, Smith Road, Royapettah - Chennai Tamil Nadu 600002 - IN +6C-3D-D8 (hex) Espressif Systems (Singapore) Pte. Ltd +6C3DD8 (base 16) Espressif Systems (Singapore) Pte. Ltd + 1 FUSIONOPOLIS VIEW#07-02ECLIPSE + Singapore 138577 + SG -3C-CB-01 (hex) Beijing Lingji innovations Technology Co., LTD. -3CCB01 (base 16) Beijing Lingji innovations Technology Co., LTD. - Room 106, 1F, A1 Bldg. Zhongguancun Dongsheng Technology Park (Northern Territory), No. 66, Xixiaokou Rd, Haidian Dist., Beijing, China - Beijing Beijing 100192 - CN +4C-D0-12 (hex) Apple, Inc. +4CD012 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -8C-4E-BB (hex) Amazon Technologies Inc. -8C4EBB (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +08-E6-4B (hex) Apple, Inc. +08E64B (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -EC-31-5F (hex) Amazon Technologies Inc. -EC315F (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +8C-C2-46 (hex) Apple, Inc. +8CC246 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -70-96-84 (hex) Apple, Inc. -709684 (base 16) Apple, Inc. +5C-23-C2 (hex) Apple, Inc. +5C23C2 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -CC-BE-61 (hex) Apple, Inc. -CCBE61 (base 16) Apple, Inc. +00-66-DC (hex) Apple, Inc. +0066DC (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +7C-59-B1 (hex) Apple, Inc. +7C59B1 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US +B4-7E-9F (hex) Xiaomi Communications Co Ltd +B47E9F (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +C0-C7-B5 (hex) Infinix mobility limited +C0C7B5 (base 16) Infinix mobility limited + RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG + HongKong HongKong 999077 + HK + +D0-82-E8 (hex) Tuya Smart Inc. +D082E8 (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 + US + +80-D1-75 (hex) iPanel.TV Inc. +80D175 (base 16) iPanel.TV Inc. + 6th Floor, Building B, Jinfengcheng Mansion, Shennan East Road No. 5015, Luohu District + Shenzhen Guangdong 518004 + CN + 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 @@ -139808,18 +142145,6 @@ F8FE5E (base 16) Intel Corporate Shenzhen 518071 CN -7C-73-98 (hex) Espressif Inc. -7C7398 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -6C-B4-56 (hex) Espressif Inc. -6CB456 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - E4-E2-6C (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD E4E26C (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD NO.18 HAIBIN ROAD, @@ -139868,12 +142193,6 @@ C022F1 (base 16) IEEE Registration Authority Tel Aviv 6706206 IL -30-30-F9 (hex) Espressif Inc. -3030F9 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 54-22-59 (hex) HUAWEI TECHNOLOGIES CO.,LTD 542259 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -140657,12 +142976,6 @@ B440DC (base 16) Samsung Electronics Co.,Ltd Taipei City 114 TW -DC-2D-DE (hex) Ledworks SRL -DC2DDE (base 16) Ledworks SRL - Via Tortona 37 - Milano Milano 20144 - IT - 64-17-CD (hex) Samsung Electronics Co.,Ltd 6417CD (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong @@ -140765,12 +143078,6 @@ C4D666 (base 16) Cisco Meraki Shenzhen 518000 CN -44-05-3F (hex) Sagemcom Broadband SAS -44053F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 80-80-2C (hex) Fortinet, Inc. 80802C (base 16) Fortinet, Inc. 899 Kifer Road @@ -141470,18 +143777,6 @@ D8B370 (base 16) Ubiquiti Inc New York NY New York NY 10017 US -B0-A7-32 (hex) Espressif Inc. -B0A732 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -B0-B2-1C (hex) Espressif Inc. -B0B21C (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - B4-FB-E4 (hex) Ubiquiti Inc B4FBE4 (base 16) Ubiquiti Inc 685 Third Avenue, 27th Floor @@ -142136,18 +144431,6 @@ E0382D (base 16) IEEE Registration Authority Bucheon-si Gyeonggi-do 14557 KR -78-60-5B (hex) TP-LINK TECHNOLOGIES CO.,LTD. -78605B (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - -04-F9-F8 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -04F9F8 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 38-47-F2 (hex) Recogni Inc 3847F2 (base 16) Recogni Inc 2590 N 1ST STSuite 320 @@ -142388,12 +144671,6 @@ E88F6F (base 16) TCT mobile ltd Hui Zhou Guang Dong 516006 CN -84-FC-E6 (hex) Espressif Inc. -84FCE6 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 58-B9-65 (hex) Apple, Inc. 58B965 (base 16) Apple, Inc. 1 Infinite Loop @@ -142442,12 +144719,6 @@ CCCC77 (base 16) Zaram Technology. Inc. Bundang-gu 13496 KR -EC-DA-3B (hex) Espressif Inc. -ECDA3B (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 68-13-E2 (hex) Eltex Enterprise LTD 6813E2 (base 16) Eltex Enterprise LTD Timiryazeva street, 17 @@ -142868,24 +145139,12 @@ D4AD20 (base 16) Jinan USR IOT Technology Limited San Jose CA 94568 US -E0-5A-1B (hex) Espressif Inc. -E05A1B (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 48-8A-E8 (hex) vivo Mobile Communication Co., Ltd. 488AE8 (base 16) vivo Mobile Communication Co., Ltd. No.1, vivo Road, Chang'an Dongguan Guangdong 523860 CN -F4-05-95 (hex) Sagemcom Broadband SAS -F40595 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 10-AE-60 (hex) Amazon Technologies Inc. 10AE60 (base 16) Amazon Technologies Inc. P.O Box 8102 @@ -142958,12 +145217,6 @@ F4931C (base 16) Universal Electronics, Inc. Santa Ana CA 92707 US -08-B6-1F (hex) Espressif Inc. -08B61F (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - CC-BA-6F (hex) HUAWEI TECHNOLOGIES CO.,LTD CCBA6F (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -143036,12 +145289,6 @@ D49B74 (base 16) Kinetic Technologies San Jose CA 95119 US -48-0E-EC (hex) TP-LINK TECHNOLOGIES CO.,LTD. -480EEC (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 50-F2-61 (hex) Photon Sail Technologies 50F261 (base 16) Photon Sail Technologies 8 Robinson Road, ASO Building @@ -143072,12 +145319,6 @@ B4BA9D (base 16) SKY UK LIMITED Piscataway NJ 08554 US -50-3E-AA (hex) TP-LINK TECHNOLOGIES CO.,LTD. -503EAA (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 3C-FE-AC (hex) Cisco Systems, Inc 3CFEAC (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -143204,18 +145445,6 @@ A490CE (base 16) vivo Mobile Communication Co., Ltd. Singapore 556741 SG -A0-B7-65 (hex) Espressif Inc. -A0B765 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -CC-DB-A7 (hex) Espressif Inc. -CCDBA7 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - C8-6C-20 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD C86C20 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County @@ -143474,12 +145703,6 @@ AC2929 (base 16) Infinix mobility limited Vancouver WA 98661 US -F0-4D-D4 (hex) Sagemcom Broadband SAS -F04DD4 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 68-4E-05 (hex) HUNAN FN-LINK TECHNOLOGY LIMITED 684E05 (base 16) HUNAN FN-LINK TECHNOLOGY LIMITED No.8, Litong Road, Liuyan Economic & Tec @@ -143738,12 +145961,6 @@ FC101A (base 16) Palo Alto Networks Kanata Ontario K2K 2E6 CA -0C-AC-8A (hex) Sagemcom Broadband SAS -0CAC8A (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 80-97-33 (hex) Shenzhen Elebao Technology Co., Ltd 809733 (base 16) Shenzhen Elebao Technology Co., Ltd F/6, Tower A, Zhihuichuangxin Center Bldg,Qianjin Road, XixiangTown, Bao’an District @@ -143774,12 +145991,6 @@ FC101A (base 16) Palo Alto Networks Shanghai Shanghai 200042 CN -F4-6D-2F (hex) TP-LINK TECHNOLOGIES CO.,LTD. -F46D2F (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 10-54-D2 (hex) IEEE Registration Authority 1054D2 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -144062,12 +146273,6 @@ A439B3 (base 16) Beijing Xiaomi Mobile Software Co., Ltd Beijing Beijing 100085 CN -6C-FF-CE (hex) Sagemcom Broadband SAS -6CFFCE (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - C8-99-B2 (hex) Arcadyan Corporation C899B2 (base 16) Arcadyan Corporation No.8, Sec.2, Guangfu Rd. @@ -144092,12 +146297,6 @@ C8727E (base 16) Nokia Kanata Ontario K2K 2E6 CA -0C-B8-15 (hex) Espressif Inc. -0CB815 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 64-25-EC (hex) guangdong kesheng zhixun technology 6425EC (base 16) guangdong kesheng zhixun technology 1603?building B,kupai building,high-tech Industrial park,Nanshan district,shengzhen @@ -144296,12 +146495,6 @@ E81656 (base 16) Hangzhou BroadLink Technology Co.,Ltd Moscow 117587 RU -70-B8-F6 (hex) Espressif Inc. -70B8F6 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - BC-47-60 (hex) Samsung Electronics Co.,Ltd BC4760 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong @@ -144566,12 +146759,6 @@ DC6294 (base 16) Guangzhou Lango Electronics Technology Co.,Ltd. Bayan Lepas Penang 11900 MY -48-55-19 (hex) Espressif Inc. -485519 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 5C-46-B0 (hex) SIMCom Wireless Solutions Limited 5C46B0 (base 16) SIMCom Wireless Solutions Limited SIMCom Headquarters Building, Building 3, No. 289 Linhong Road, Changning District, Shanghai P.R. China @@ -144638,12 +146825,6 @@ D45763 (base 16) Apple, Inc. SINGAPORE 437872 SG -E8-9F-6D (hex) Espressif Inc. -E89F6D (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - F8-29-C0 (hex) Availink, Inc. F829C0 (base 16) Availink, Inc. Scotia Centre P.O. Box 268GT,Grand Cayman, Cayman Islands @@ -144794,18 +146975,6 @@ E8979A (base 16) Quectel Wireless Solutions Co.,Ltd. Shanghai 200233 CN -18-F2-2C (hex) TP-LINK TECHNOLOGIES CO.,LTD. -18F22C (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - -98-97-CC (hex) TP-LINK TECHNOLOGIES CO.,LTD. -9897CC (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 18-0E-AC (hex) SHENZHEN FAST TECHNOLOGIES CO.,LTD 180EAC (base 16) SHENZHEN FAST TECHNOLOGIES CO.,LTD Room 202,Building No.5,Section 30,No.2 of Kefa Road,Nanshan District,Shenzhen,P.R.China @@ -145052,12 +147221,6 @@ B85600 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Kusatsu City Shiga 525-8555 JP -40-91-51 (hex) Espressif Inc. -409151 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - B0-E9-FE (hex) Woan Technology (Shenzhen) Co., Ltd. B0E9FE (base 16) Woan Technology (Shenzhen) Co., Ltd. 1-2F, Building B4, Yintian Industrial Zone, Yantian Community, Xixiang Street, Bao'an District, Shenzhen, Guangdong, P.R.China @@ -146000,12 +148163,6 @@ ECB970 (base 16) Ruijie Networks Co.,LTD Fuzhou Fujian 350002 CN -30-83-98 (hex) Espressif Inc. -308398 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - F8-89-D2 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. F889D2 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China @@ -146246,12 +148403,6 @@ A4CEDA (base 16) Arcadyan Corporation Hsinchu City Hsinchu 30071 TW -84-7A-B6 (hex) AltoBeam (China) Inc. -847AB6 (base 16) AltoBeam (China) Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN - 28-41-EC (hex) HUAWEI TECHNOLOGIES CO.,LTD 2841EC (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -146402,12 +148553,6 @@ E455A8 (base 16) Cisco Meraki München Bavaria D-81541 DE -18-A6-F7 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -18A6F7 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - E4-C9-0B (hex) Radwin E4C90B (base 16) Radwin Habarzel 27 @@ -146876,18 +149021,6 @@ F02F74 (base 16) ASUSTek COMPUTER INC. Shenzhen Guangdong 518057 CN -D0-6E-DE (hex) Sagemcom Broadband SAS -D06EDE (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -08-CB-E5 (hex) R3 Solutions GmbH -08CBE5 (base 16) R3 Solutions GmbH - Kurfürstendamm 21 - Berlin 10719 - DE - 48-25-67 (hex) Poly 482567 (base 16) Poly 6001 America Center Drive @@ -147794,12 +149927,6 @@ A09B17 (base 16) Taicang T&W Electronics Suzhou Jiangsu 215412 CN -E0-6C-A6 (hex) Creotech Instruments S.A. -E06CA6 (base 16) Creotech Instruments S.A. - ul. Gen. L. Okulickiego 7/9 - Piaseczno Mazovia 05-500 - PL - A0-D8-3D (hex) Fiberhome Telecommunication Technologies Co.,LTD A0D83D (base 16) Fiberhome Telecommunication Technologies Co.,LTD No.5 DongXin Road @@ -147842,12 +149969,6 @@ C086B3 (base 16) Shenzhen Voxtech Co., Ltd. Shenzhen 518000 CN -44-AD-B1 (hex) Sagemcom Broadband SAS -44ADB1 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 64-3A-EA (hex) Cisco Systems, Inc 643AEA (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -148034,12 +150155,6 @@ BC13A8 (base 16) Shenzhen YOUHUA Technology Co., Ltd Shenzhen Guangdong 518055 CN -6C-99-61 (hex) Sagemcom Broadband SAS -6C9961 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 20-69-80 (hex) Apple, Inc. 206980 (base 16) Apple, Inc. 1 Infinite Loop @@ -148310,12 +150425,6 @@ F45420 (base 16) TELLESCOM INDUSTRIA E COMERCIO EM TELECOMUNICACAO HYOGO PREF. 670 JP -7C-9E-BD (hex) Espressif Inc. -7C9EBD (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 1C-02-19 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD 1C0219 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD NO.18 HAIBIN ROAD, @@ -148532,12 +150641,6 @@ CC418E (base 16) MSA Innovation Incheon 21999 KR -C8-D7-78 (hex) BSH Hausgeraete GmbH -C8D778 (base 16) BSH Hausgeraete GmbH - Im Gewerbepark B10 - Regensburg 93059 - DE - C0-95-DA (hex) NXP India Private Limited C095DA (base 16) NXP India Private Limited 1st Floor, Muttha Towers, Don Bosco Marg, Off Airport Road, Yerwada @@ -148706,12 +150809,6 @@ CCF9E4 (base 16) Intel Corporate Jacksonville OR 97530 US -F0-08-D1 (hex) Espressif Inc. -F008D1 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 14-AE-85 (hex) IEEE Registration Authority 14AE85 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -148742,12 +150839,6 @@ CCD42E (base 16) Arcadyan Corporation ??? ??? 518000 CN -B0-95-75 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -B09575 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 14-16-9D (hex) Cisco Systems, Inc 14169D (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -148784,12 +150875,6 @@ E0BB9E (base 16) Seiko Epson Corporation Matsumoto-shi Nagano-ken 399-8702 JP -48-D2-4F (hex) Sagemcom Broadband SAS -48D24F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - E4-AA-EC (hex) Tianjin Hualai Technology Co., Ltd E4AAEC (base 16) Tianjin Hualai Technology Co., Ltd Overseas Chinese business building No. 10, Jinping Road, Nankai District, Tianjin @@ -148814,12 +150899,6 @@ E4AAEC (base 16) Tianjin Hualai Technology Co., Ltd Reno NV 89507 US -48-3F-DA (hex) Espressif Inc. -483FDA (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 2C-DC-D7 (hex) AzureWave Technology Inc. 2CDCD7 (base 16) AzureWave Technology Inc. 8F., No. 94, Baozhong Rd. @@ -150074,12 +152153,6 @@ AC00D0 (base 16) zte corporation shenzhen guangdong 518057 CN -98-1E-19 (hex) Sagemcom Broadband SAS -981E19 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 84-B8-66 (hex) Beijing XiaoLu technology co. LTD 84B866 (base 16) Beijing XiaoLu technology co. LTD Room 002, floor 2, building 1, yard 4, BeiTuCheng East Road, ChaoYang district, Beijing @@ -151358,12 +153431,6 @@ E00EE1 (base 16) We Corporation Inc. Anyang-si Gyeonggi-do 14088 KR -A8-9A-93 (hex) Sagemcom Broadband SAS -A89A93 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 8C-92-46 (hex) Oerlikon Textile Gmbh&Co.KG 8C9246 (base 16) Oerlikon Textile Gmbh&Co.KG NO.9 Changyang Street @@ -151628,12 +153695,6 @@ B0027E (base 16) MULLER SERVICES Shanghai 200438 CN -28-9E-FC (hex) Sagemcom Broadband SAS -289EFC (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 00-C0-55 (hex) MODULAR COMPUTING TECHNOLOGIES 00C055 (base 16) MODULAR COMPUTING TECHNOLOGIES 2352 MAIN STREET @@ -151940,12 +154001,6 @@ E48F65 (base 16) Yelatma Instrument Making Enterprise, JSC Yelatma Ryazan Region 391351 RU -84-0D-8E (hex) Espressif Inc. -840D8E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 00-BB-3A (hex) Amazon Technologies Inc. 00BB3A (base 16) Amazon Technologies Inc. P.O Box 8102 @@ -152174,24 +154229,12 @@ C819F7 (base 16) Samsung Electronics Co.,Ltd Gumi Gyeongbuk 730-350 KR -DC-29-19 (hex) AltoBeam (Xiamen) Technology Ltd, Co. -DC2919 (base 16) AltoBeam (Xiamen) Technology Ltd, Co. - South Building 203-38,Huoju Square ,No.56-58,Huoju Road, Huoju Park, Huoju High-tech District - Xiamen 361000 - CN - 64-5A-ED (hex) Apple, Inc. 645AED (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -84-F3-EB (hex) Espressif Inc. -84F3EB (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 00-1B-48 (hex) Shenzhen Lantech Electronics Co., Ltd. 001B48 (base 16) Shenzhen Lantech Electronics Co., Ltd. 306 Room, Nanshan Water Building, @@ -152330,12 +154373,6 @@ DC729B (base 16) HUAWEI TECHNOLOGIES CO.,LTD ERNAKULAM Time Kids day care 686662 IN -3C-17-10 (hex) Sagemcom Broadband SAS -3C1710 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 34-02-9B (hex) Plexonics Technologies LImited 34029B (base 16) Plexonics Technologies LImited 1st Floor, 181/23 Industrial Area Phase 1 @@ -153302,12 +155339,6 @@ BC9680 (base 16) SHENZHEN GONGJIN ELECTRONICS CO.,LT Shenzhen Guangdong 518067 CN -A4-7B-9D (hex) Espressif Inc. -A47B9D (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 7C-2E-DD (hex) Samsung Electronics Co.,Ltd 7C2EDD (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong @@ -155186,12 +157217,6 @@ C4047B (base 16) Shenzhen YOUHUA Technology Co., Ltd Shenzhen Guangdong 518055 CN -20-F4-1B (hex) Shenzhen Bilian electronic CO.,LTD -20F41B (base 16) Shenzhen Bilian electronic CO.,LTD - NO 268, Fuqian Rd,Jutang Community,Guanlan town, - ShenZhen Guangdong 518110 - CN - 00-23-3E (hex) Alcatel-Lucent IPD 00233E (base 16) Alcatel-Lucent IPD 701 E. Middlefield Rd. @@ -155582,12 +157607,6 @@ B0E235 (base 16) Xiaomi Communications Co Ltd NO.68, Qinghe Middle Street Haidian District, Beijing 100085 CN -40-C7-29 (hex) Sagemcom Broadband SAS -40C729 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 00-13-49 (hex) Zyxel Communications Corporation 001349 (base 16) Zyxel Communications Corporation No. 6 Innovation Road II, Science Park @@ -156212,12 +158231,6 @@ FC084A (base 16) FUJITSU LIMITED San Jose CA 94568 US -48-83-C7 (hex) Sagemcom Broadband SAS -4883C7 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 40-16-3B (hex) Samsung Electronics Co.,Ltd 40163B (base 16) Samsung Electronics Co.,Ltd 129, Samsung-ro, Youngtongl-Gu @@ -156752,36 +158765,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 @@ -157010,12 +158999,6 @@ E8617E (base 16) Liteon Technology Corporation TaiPei TaiWan 23585 TW -18-FE-34 (hex) Espressif Inc. -18FE34 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 00-22-43 (hex) AzureWave Technology Inc. 002243 (base 16) AzureWave Technology Inc. 8F., No. 94, Baozhong Rd., Xindian @@ -157298,42 +159281,6 @@ E46F13 (base 16) D-Link International Shenzhen Guangdong 518057 CN -00-26-91 (hex) Sagemcom Broadband SAS -002691 (base 16) Sagemcom Broadband SAS - Le Ponnant de Paris - CEDEX Paris 75512 - FR - -98-8B-5D (hex) Sagemcom Broadband SAS -988B5D (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison 92848 - FR - -90-01-3B (hex) Sagemcom Broadband SAS -90013B (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison Cedex Hauts de Seine 92848 - FR - -7C-03-4C (hex) Sagemcom Broadband SAS -7C034C (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison Cedex Hauts de Seine 92848 - FR - -6C-2E-85 (hex) Sagemcom Broadband SAS -6C2E85 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison Cedex Hauts de Seine 92848 - FR - -94-FE-F4 (hex) Sagemcom Broadband SAS -94FEF4 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison Cedex Hauts de Seine 92848 - FR - 28-FA-A0 (hex) vivo Mobile Communication Co., Ltd. 28FAA0 (base 16) vivo Mobile Communication Co., Ltd. #283,BBK Road @@ -157352,24 +159299,6 @@ ECDF3A (base 16) vivo Mobile Communication Co., Ltd. Zhubei City Hsinchu County 30265 TW -00-60-4C (hex) Sagemcom Broadband SAS -00604C (base 16) Sagemcom Broadband SAS - 27 RUE LEBLANC - CEDEX 15 PARIS 75512 - FR - -00-1F-95 (hex) Sagemcom Broadband SAS -001F95 (base 16) Sagemcom Broadband SAS - Le Ponnant de Paris - CEDEX Paris 75512 - FR - -00-23-48 (hex) Sagemcom Broadband SAS -002348 (base 16) Sagemcom Broadband SAS - Le Ponnant de Paris - CEDEX Paris 75512 - FR - 44-87-23 (hex) HOYA SERVICE CORPORATION 448723 (base 16) HOYA SERVICE CORPORATION 4-10-2 Nakano @@ -159359,12 +161288,6 @@ D47208 (base 16) Bragi GmbH A4-A6-A9 (hex) Private A4A6A9 (base 16) Private -8C-10-D4 (hex) Sagemcom Broadband SAS -8C10D4 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - F8-98-B9 (hex) HUAWEI TECHNOLOGIES CO.,LTD F898B9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -161804,12 +163727,6 @@ D073D5 (base 16) LIFI LABS MANAGEMENT PTY LTD LUXEMBOURG L-1260 US -48-F9-25 (hex) Maestronic -48F925 (base 16) Maestronic - Futura plaza 2103 - Kwun Tong 88 - HK - 68-83-1A (hex) Pandora Mobility Corporation 68831A (base 16) Pandora Mobility Corporation 1F., No.33, Fude St @@ -169556,12 +171473,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 @@ -173741,12 +175652,6 @@ A07332 (base 16) Cashmaster International Limited Campbell CA 95008 US -00-07-A7 (hex) A-Z Inc. -0007A7 (base 16) A-Z Inc. - 5-5-17 Kamikoushien - - JP - 00-07-A6 (hex) Leviton Manufacturing Co., Inc. 0007A6 (base 16) Leviton Manufacturing Co., Inc. 4330 Michoud Blvd @@ -179702,12 +181607,6 @@ E4E66C (base 16) Tiandy Technologies Co.,LTD Tianjin Tianjin 300384 CN -84-3E-03 (hex) Sagemcom Broadband SAS -843E03 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 54-9A-8F (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. 549A8F (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. @@ -179930,18 +181829,6 @@ B8DB38 (base 16) Google, Inc. Seoul 08506 KR -24-58-7C (hex) Espressif Inc. -24587C (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -CC-8D-A2 (hex) Espressif Inc. -CC8DA2 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - EC-31-4A (hex) Chengdu Quanjing Intelligent Technology Co.,Ltd EC314A (base 16) Chengdu Quanjing Intelligent Technology Co.,Ltd Building A2, Chi Yuen Technology Park, 1001 College Avenue, Nanshan District, Shenzhen,P.R.C. @@ -180014,12 +181901,6 @@ E82725 (base 16) Axis Communications AB shenzhen guangdong 518057 CN -A0-DD-6C (hex) Espressif Inc. -A0DD6C (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 54-78-85 (hex) SHENZHEN GIEC DIGITAL CO.,LTD 547885 (base 16) SHENZHEN GIEC DIGITAL CO.,LTD 1st&3rd Building,No.26 Puzai Road, Pingdi, Longgang @@ -180050,18 +181931,6 @@ A0DD6C (base 16) Espressif Inc. Shenzhen 18000 CN -D0-EF-76 (hex) Espressif Inc. -D0EF76 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -C4-D8-D5 (hex) Espressif Inc. -C4D8D5 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - E8-F0-85 (hex) HUAWEI TECHNOLOGIES CO.,LTD E8F085 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -180323,12 +182192,6 @@ CC6A33 (base 16) Cisco Systems, Inc Staefa 8712 CH -80-AE-54 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -80AE54 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - F4-6E-D6 (hex) EM Microelectronic F46ED6 (base 16) EM Microelectronic Rue des Sors 3 @@ -180557,12 +182420,6 @@ A4A459 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. Hangzhou Zhejiang 310052 CN -24-EC-4A (hex) Espressif Inc. -24EC4A (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 98-1D-AC (hex) Cyviz AS 981DAC (base 16) Cyviz AS Grenseveien 21 @@ -180959,12 +182816,6 @@ B0653A (base 16) Murata Manufacturing Co., Ltd. Huizhou Guangdong 516006 CN -90-15-06 (hex) Espressif Inc. -901506 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 44-27-45 (hex) LG Innotek 442745 (base 16) LG Innotek 26, HANAMSANDAN 5BEON-RO @@ -181187,12 +183038,6 @@ F45B29 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Mianyang Sichuan 622650 CN -64-7B-1E (hex) Sagemcom Broadband SAS -647B1E (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 98-FA-2E (hex) Sony Interactive Entertainment Inc. 98FA2E (base 16) Sony Interactive Entertainment Inc. 1-7-1 Konan @@ -181835,12 +183680,6 @@ CCA150 (base 16) SystemX Co.,Ltd. Shanghai 200333 CN -CC-BA-97 (hex) Espressif Inc. -CCBA97 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 50-CF-14 (hex) Quectel Wireless Solutions Co.,Ltd. 50CF14 (base 16) Quectel Wireless Solutions Co.,Ltd. 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District @@ -182798,12 +184637,6 @@ D4E3C5 (base 16) zte corporation shenzhen guangdong 518057 CN -04-83-08 (hex) Espressif Inc. -048308 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 58-0D-0D (hex) GREE ELECTRIC APPLIANCES, INC. OF ZHUHAI 580D0D (base 16) GREE ELECTRIC APPLIANCES, INC. OF ZHUHAI West Jinji Rd, Qianshan @@ -183128,12 +184961,6 @@ A47F1B (base 16) Juniper Networks Suzhou 215021 CN -EC-E3-34 (hex) Espressif Inc. -ECE334 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - BC-A2-31 (hex) HUAWEI TECHNOLOGIES CO.,LTD BCA231 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -183452,12 +185279,6 @@ BC9829 (base 16) vivo Mobile Communication Co., Ltd. Sunnyvale CA 94085 US -10-51-DB (hex) Espressif Inc. -1051DB (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 2C-BE-EE (hex) Nothing Technology Limited 2CBEEE (base 16) Nothing Technology Limited 11 Staple Inn @@ -183596,12 +185417,6 @@ BCA68D (base 16) Continetal Automotive Systems Sibiu HEiFEI High-Tech Zone 230000 CN -34-38-39 (hex) NEC Platforms, Ltd. -343839 (base 16) NEC Platforms, Ltd. - 2-3 Kandatsukasamachi - Chiyodaku Tokyo 101-8532 - JP - A0-9A-52 (hex) Shenzhen MoreSense Technology Co., Ltd. A09A52 (base 16) Shenzhen MoreSense Technology Co., Ltd. #206 Building A1,#663 Bulong Road,Dafapu Community,Bantian Street, @@ -183758,12 +185573,6 @@ C0DA5E (base 16) Huawei Device Co., Ltd. Paris Paris 75007 FR -44-15-24 (hex) Sagemcom Broadband SAS -441524 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - C8-A1-DC (hex) Motorola Mobility LLC, a Lenovo Company C8A1DC (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza @@ -183794,12 +185603,6 @@ D484D0 (base 16) Shanghai Xiaodu Technology Limited Shanghai 200000 CN -0C-4E-A0 (hex) Espressif Inc. -0C4EA0 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - A0-28-84 (hex) Garmin International A02884 (base 16) Garmin International 1200 E. 151st St @@ -184178,12 +185981,6 @@ CC40F3 (base 16) Mellanox Technologies, Inc. Sunnyvale CA 94085 US -84-1F-E8 (hex) Espressif Inc. -841FE8 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 94-C2-EF (hex) ASKEY COMPUTER CORP 94C2EF (base 16) ASKEY COMPUTER CORP 10F,No.119,JIANKANG RD,ZHONGHE DIST @@ -184202,23 +185999,17 @@ E467A6 (base 16) BSH Hausgeräte GmbH Suzhou 215000 CN -74-A5-7E (hex) Panasonic Ecology Systems -74A57E (base 16) Panasonic Ecology Systems - 4017, Azashimonakata, Takaki-cho - Kasugai Aichi 4868522 - JP - -7C-E9-13 (hex) Fantasia Trading LLC -7CE913 (base 16) Fantasia Trading LLC - 5350 Ontario Mills Pkwy, Suite 100 - Ontario CA 91764 - US +30-D5-1F (hex) Prolights +30D51F (base 16) Prolights + Via Adriano Olivetti snc + Minturno Latina 04026 + IT -E0-CB-BC (hex) Cisco Meraki -E0CBBC (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US +18-FB-8E (hex) VusionGroup +18FB8E (base 16) VusionGroup + Kalsdorfer Straße 12 + Fernitz-Mellach Steiermark 8072 + AT 68-3A-1E (hex) Cisco Meraki 683A1E (base 16) Cisco Meraki @@ -184238,23 +186029,35 @@ F89E28 (base 16) Cisco Meraki San Francisco 94158 US +74-A5-7E (hex) Panasonic Ecology Systems +74A57E (base 16) Panasonic Ecology Systems + 4017, Azashimonakata, Takaki-cho + Kasugai Aichi 4868522 + JP + +6C-15-DB (hex) Arcadyan Corporation +6C15DB (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW + +7C-E9-13 (hex) Fantasia Trading LLC +7CE913 (base 16) Fantasia Trading LLC + 5350 Ontario Mills Pkwy, Suite 100 + Ontario CA 91764 + US + 38-2A-8B (hex) nFore Technology Co., Ltd. 382A8B (base 16) nFore Technology Co., Ltd. 5F, No.31, Ln. 258, Ruiguang Rd., Neihu Dist., Taipei city 114 TW -18-FB-8E (hex) VusionGroup -18FB8E (base 16) VusionGroup - Kalsdorfer Straße 12 - Fernitz-Mellach Steiermark 8072 - AT - -30-D5-1F (hex) Prolights -30D51F (base 16) Prolights - Via Adriano Olivetti snc - Minturno Latina 04026 - IT +E0-CB-BC (hex) Cisco Meraki +E0CBBC (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US D4-68-BA (hex) Shenzhen Sundray Technologies company Limited D468BA (base 16) Shenzhen Sundray Technologies company Limited @@ -184274,6 +186077,12 @@ D468BA (base 16) Shenzhen Sundray Technologies company Limited Shenzhen Guangdong 518057 CN +08-B3-D6 (hex) Huawei Device Co., Ltd. +08B3D6 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + 78-2B-60 (hex) Huawei Device Co., Ltd. 782B60 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone @@ -184292,36 +186101,24 @@ FC79DD (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN -F4-14-BF (hex) LG Innotek -F414BF (base 16) LG Innotek - 26, HANAMSANDAN 5BEON-RO - Gwangju Gwangsan-gu 506-731 - KR +C8-53-E1 (hex) Douyin Vision Co., Ltd +C853E1 (base 16) Douyin Vision Co., Ltd + No.1 Building, Zhonghang Square, West Road of the Northern 3rd Circuit, Haidian Distrct + Beijing Beijing 100098 + CN -5C-49-79 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -5C4979 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +DC-39-6F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +DC396F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -DC-39-6F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -DC396F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH +5C-49-79 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +5C4979 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 Berlin Berlin 10559 DE -C8-53-E1 (hex) Douyin Vision Co., Ltd -C853E1 (base 16) Douyin Vision Co., Ltd - No.1 Building, Zhonghang Square, West Road of the Northern 3rd Circuit, Haidian Distrct - Beijing Beijing 100098 - CN - -08-B3-D6 (hex) Huawei Device Co., Ltd. -08B3D6 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - 2C-6F-37 (hex) Nokia 2C6F37 (base 16) Nokia 600 March Road @@ -184334,18 +186131,24 @@ C853E1 (base 16) Douyin Vision Co., Ltd Kanata Ontario K2K 2E6 CA -6C-15-DB (hex) Arcadyan Corporation -6C15DB (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW - 58-79-61 (hex) Microsoft Corporation 587961 (base 16) Microsoft Corporation One Microsoft Way REDMOND WA 98052 US +F4-14-BF (hex) LG Innotek +F414BF (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR + +50-E6-36 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +50E636 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + 60-B5-8D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH 60B58D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH Alt-Moabit 95 @@ -184358,6 +186161,12 @@ C853E1 (base 16) Douyin Vision Co., Ltd Berlin Berlin 10559 DE +48-DD-0C (hex) eero inc. +48DD0C (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + 3C-5C-F1 (hex) eero inc. 3C5CF1 (base 16) eero inc. 660 3rd Street @@ -184376,12 +186185,6 @@ C4F174 (base 16) eero inc. San Francisco CA 94107 US -64-97-14 (hex) eero inc. -649714 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - 40-47-5E (hex) eero inc. 40475E (base 16) eero inc. 660 3rd Street @@ -184406,54 +186209,54 @@ D88ED4 (base 16) eero inc. San Francisco CA 94107 US -14-22-DB (hex) eero inc. -1422DB (base 16) eero inc. +64-97-14 (hex) eero inc. +649714 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -18-90-88 (hex) eero inc. -189088 (base 16) eero inc. +20-BE-CD (hex) eero inc. +20BECD (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -48-DD-0C (hex) eero inc. -48DD0C (base 16) eero inc. +C8-B8-2F (hex) eero inc. +C8B82F (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -50-E6-36 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -50E636 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -E8-D3-EB (hex) eero inc. -E8D3EB (base 16) eero inc. +14-22-DB (hex) eero inc. +1422DB (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -C0-6F-98 (hex) eero inc. -C06F98 (base 16) eero inc. +18-90-88 (hex) eero inc. +189088 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -20-BE-CD (hex) eero inc. -20BECD (base 16) eero inc. +E8-D3-EB (hex) eero inc. +E8D3EB (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -C8-B8-2F (hex) eero inc. -C8B82F (base 16) eero inc. +C0-6F-98 (hex) eero inc. +C06F98 (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US +8C-53-D2 (hex) China Mobile Group Device Co.,Ltd. +8C53D2 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + 24-61-5A (hex) China Mobile Group Device Co.,Ltd. 24615A (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District @@ -184514,20 +186317,26 @@ C875F4 (base 16) China Mobile Group Device Co.,Ltd. Beijing 100053 CN -B8-CE-F6 (hex) Mellanox Technologies, Inc. -B8CEF6 (base 16) Mellanox Technologies, Inc. +B8-E9-24 (hex) Mellanox Technologies, Inc. +B8E924 (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 Sunnyvale CA 94085 US -C4-70-BD (hex) Mellanox Technologies, Inc. -C470BD (base 16) Mellanox Technologies, Inc. +2C-5E-AB (hex) Mellanox Technologies, Inc. +2C5EAB (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 Sunnyvale CA 94085 US -B8-E9-24 (hex) Mellanox Technologies, Inc. -B8E924 (base 16) Mellanox Technologies, Inc. +B8-CE-F6 (hex) Mellanox Technologies, Inc. +B8CEF6 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +C4-70-BD (hex) Mellanox Technologies, Inc. +C470BD (base 16) Mellanox Technologies, Inc. 350 Oakmead Parkway, Suite 100 Sunnyvale CA 94085 US @@ -184562,16 +186371,10 @@ E83A4B (base 16) China Mobile Group Device Co.,Ltd. Dongguan Guangdong 523860 CN -2C-5E-AB (hex) Mellanox Technologies, Inc. -2C5EAB (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - -8C-53-D2 (hex) China Mobile Group Device Co.,Ltd. -8C53D2 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +58-72-C9 (hex) zte corporation +5872C9 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN 38-E5-63 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD @@ -184580,10 +186383,10 @@ E83A4B (base 16) China Mobile Group Device Co.,Ltd. DONG GUAN GUANG DONG 523860 CN -58-72-C9 (hex) zte corporation -5872C9 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +58-FC-E3 (hex) Funshion Online Technologies Co.,Ltd +58FCE3 (base 16) Funshion Online Technologies Co.,Ltd + 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing + Beijing 100029 CN 30-C5-99 (hex) ASUSTek COMPUTER INC. @@ -184598,22 +186401,10 @@ E83A4B (base 16) China Mobile Group Device Co.,Ltd. Qingdao 266101 CN -B8-DD-E8 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD -B8DDE8 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD - No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County - Chengdu Sichuan 611330 - CN - -30-FE-FA (hex) Cisco Systems, Inc -30FEFA (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -6C-4F-A1 (hex) Cisco Systems, Inc -6C4FA1 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +00-23-E9 (hex) F5 Inc. +0023E9 (base 16) F5 Inc. + 401 Elliott Ave. W. + Seattle WA 98119 US 40-BC-68 (hex) Funshion Online Technologies Co.,Ltd @@ -184628,17 +186419,29 @@ B8DDE8 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD Gunpo-si Gyeonggi-do 15849 KR +B8-DD-E8 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD +B8DDE8 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD + No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County + Chengdu Sichuan 611330 + CN + 78-A1-D8 (hex) ShenzhenEnjoyTechnologyCo.,Ltd 78A1D8 (base 16) ShenzhenEnjoyTechnologyCo.,Ltd Building A, No.1 Qianwan 1st Road, QianHai Shenzhen HongKong Cooperation Zone, Shenzhen,China shenzhen guangdong 518108 CN -58-FC-E3 (hex) Funshion Online Technologies Co.,Ltd -58FCE3 (base 16) Funshion Online Technologies Co.,Ltd - 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing - Beijing 100029 - CN +30-FE-FA (hex) Cisco Systems, Inc +30FEFA (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +6C-4F-A1 (hex) Cisco Systems, Inc +6C4FA1 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US 40-95-95 (hex) TP-Link Systems Inc. 409595 (base 16) TP-Link Systems Inc. @@ -184646,12 +186449,6 @@ B8DDE8 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD Irvine CA 92618 US -00-23-E9 (hex) F5 Inc. -0023E9 (base 16) F5 Inc. - 401 Elliott Ave. W. - Seattle WA 98119 - US - 48-CA-68 (hex) Apple, Inc. 48CA68 (base 16) Apple, Inc. 1 Infinite Loop @@ -184670,12 +186467,6 @@ D842F7 (base 16) Tozed Kangwei Tech Co.,Ltd GuangZhou 511466 CN -E0-86-14 (hex) Inseego Wireless, Inc -E08614 (base 16) Inseego Wireless, Inc - 9710 Scranton Rd., Suite 200 - San Diego CA 92121 - US - 18-86-C3 (hex) Nokia 1886C3 (base 16) Nokia 600 March Road @@ -184706,23 +186497,11 @@ E8CA50 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. Sunnyvale CA 94085 US -68-FE-71 (hex) Espressif Inc. -68FE71 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -D8-6B-83 (hex) Nintendo Co.,Ltd -D86B83 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP - -40-EB-21 (hex) HUAWEI TECHNOLOGIES CO.,LTD -40EB21 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +E0-86-14 (hex) Inseego Wireless, Inc +E08614 (base 16) Inseego Wireless, Inc + 9710 Scranton Rd., Suite 200 + San Diego CA 92121 + US A8-C4-07 (hex) HUAWEI TECHNOLOGIES CO.,LTD A8C407 (base 16) HUAWEI TECHNOLOGIES CO.,LTD @@ -184742,11 +186521,23 @@ DC121D (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -2C-03-69 (hex) ACCTON TECHNOLOGY CORPORATION -2C0369 (base 16) ACCTON TECHNOLOGY CORPORATION - No.1, Creation Road 3, Hsinchu Science Park, - Hsinchu 30077 - TW +40-EB-21 (hex) HUAWEI TECHNOLOGIES CO.,LTD +40EB21 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +68-FE-71 (hex) Espressif Inc. +68FE71 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +D8-6B-83 (hex) Nintendo Co.,Ltd +D86B83 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP C0-74-15 (hex) IntelPro Inc. C07415 (base 16) IntelPro Inc. @@ -184772,23 +186563,17 @@ C07415 (base 16) IntelPro Inc. Hangzhou Zhejiang 310052 CN -54-78-F0 (hex) zte corporation -5478F0 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN - BC-D2-2C (hex) Intel Corporate BCD22C (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -E0-3A-AA (hex) Intel Corporate -E03AAA (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +2C-03-69 (hex) ACCTON TECHNOLOGY CORPORATION +2C0369 (base 16) ACCTON TECHNOLOGY CORPORATION + No.1, Creation Road 3, Hsinchu Science Park, + Hsinchu 30077 + TW 50-99-03 (hex) Meta Platforms, Inc. 509903 (base 16) Meta Platforms, Inc. @@ -184796,29 +186581,47 @@ E03AAA (base 16) Intel Corporate Menlo Park CA 94025 US +64-68-1A (hex) DASAN Network Solutions +64681A (base 16) DASAN Network Solutions + 401, 20, Gwacheon-daero 7-gil, + Gwacheon-si Gyeonggi-do 13493 + KR + 40-26-8E (hex) Shenzhen Photon Leap Technology Co., Ltd. 40268E (base 16) Shenzhen Photon Leap Technology Co., Ltd. 15/F, Building 2, Yongxin Times Square, Interchange of Dongbin Road and Nanguang Road Shenzhen 518054 CN +54-78-F0 (hex) zte corporation +5478F0 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + 74-F4-41 (hex) Samsung Electronics Co.,Ltd 74F441 (base 16) Samsung Electronics Co.,Ltd #94-1, Imsoo-Dong Gumi Gyeongbuk 730-350 KR +E0-3A-AA (hex) Intel Corporate +E03AAA (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + 34-39-16 (hex) Google, Inc. 343916 (base 16) Google, Inc. 1600 Amphitheatre Parkway Mountain View CA 94043 US -64-68-1A (hex) DASAN Network Solutions -64681A (base 16) DASAN Network Solutions - 401, 20, Gwacheon-daero 7-gil, - Gwacheon-si Gyeonggi-do 13493 - KR +64-D9-C2 (hex) eero inc. +64D9C2 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US 20-E7-C8 (hex) Espressif Inc. 20E7C8 (base 16) Espressif Inc. @@ -184832,12 +186635,24 @@ E03AAA (base 16) Intel Corporate San Diego CA 92127 US -64-D9-C2 (hex) eero inc. -64D9C2 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +28-87-61 (hex) LG Innotek +288761 (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR + +78-0C-71 (hex) Inseego Wireless, Inc +780C71 (base 16) Inseego Wireless, Inc + 9710 Scranton Rd., Suite 200 + San Diego CA 92121 US +A8-2B-DD (hex) LCFC(Hefei) Electronics Technology co., ltd +A82BDD (base 16) LCFC(Hefei) Electronics Technology co., ltd + No. 3188-1 Yungu Road (Comprehensive Bonded Zone), Hefei Economic & Technological Development Area,Anhui + HEFEI ANHUI 230601 + CN + 80-82-FE (hex) Arcadyan Corporation 8082FE (base 16) Arcadyan Corporation No.8, Sec.2, Guangfu Rd. @@ -184850,42 +186665,36 @@ CCCFFE (base 16) Henan Lingyunda Information Technology Co., Ltd Zhengzhou Henan Province 450000 CN -28-87-61 (hex) LG Innotek -288761 (base 16) LG Innotek - 26, HANAMSANDAN 5BEON-RO - Gwangju Gwangsan-gu 506-731 - KR - -78-0C-71 (hex) Inseego Wireless, Inc -780C71 (base 16) Inseego Wireless, Inc - 9710 Scranton Rd., Suite 200 - San Diego CA 92121 +34-B5-F3 (hex) IEEE Registration Authority +34B5F3 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -D4-27-FF (hex) Sagemcom Broadband SAS -D427FF (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 40-49-7C (hex) eero inc. 40497C (base 16) eero inc. 660 3rd Street San Francisco CA 94107 US -A8-2B-DD (hex) LCFC(Hefei) Electronics Technology co., ltd -A82BDD (base 16) LCFC(Hefei) Electronics Technology co., ltd - No. 3188-1 Yungu Road (Comprehensive Bonded Zone), Hefei Economic & Technological Development Area,Anhui - HEFEI ANHUI 230601 - CN +C0-AF-F2 (hex) Dyson Limited +C0AFF2 (base 16) Dyson Limited + Tetbury Hill + Malmesbury Wiltshire SN16 0RP + GB -34-B5-F3 (hex) IEEE Registration Authority -34B5F3 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +20-10-B1 (hex) Amazon Technologies Inc. +2010B1 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US +84-53-CD (hex) China Mobile Group Device Co.,Ltd. +8453CD (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + F8-55-4B (hex) WirelessMobility Engineering Centre SDN. BHD F8554B (base 16) WirelessMobility Engineering Centre SDN. BHD SummerSkye Square, NO. 1-2-13 & 1-2, 13A, Jalan Sungai Tiram 8, 11900 Bayan Lepas @@ -184910,12 +186719,6 @@ E489CA (base 16) Cisco Systems, Inc San Jose CA 95126 US -C0-AF-F2 (hex) Dyson Limited -C0AFF2 (base 16) Dyson Limited - Tetbury Hill - Malmesbury Wiltshire SN16 0RP - GB - 14-BC-68 (hex) Cisco Systems, Inc 14BC68 (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -184928,10 +186731,34 @@ C0AFF2 (base 16) Dyson Limited Shanghai 200000 CN -AC-84-FA (hex) Zhejiang Weilai Jingling Artificial Intelligence Technology Co., Ltd. -AC84FA (base 16) Zhejiang Weilai Jingling Artificial Intelligence Technology Co., Ltd. - B2, 6th Floor (6-7 section), Xixi Campus, Ai Cheng Street, Wuchang Sub-district, Yuhang District, Hangzhou, Zhejiang Province, China - Hangzhou Zhejiang 310024 +98-F6-7A (hex) Chipsea Technologies (Shenzhen) Crop. +98F67A (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 + +C4-9A-31 (hex) Zyxel Communications Corporation +C49A31 (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + +48-0E-13 (hex) ittim +480E13 (base 16) ittim + 1202, No.6, Zhongguancun South Street, Haidian District, + beijing 100080 + CN + +74-34-91 (hex) Shenzhen Kings IoT Co., Ltd +743491 (base 16) Shenzhen Kings IoT Co., Ltd + D102, Yibao Garden, Hangcheng Road, Xixiang, BaoanBaoan district + Shenzhen City 518126 + CN + +08-D9-45 (hex) HUAWEI TECHNOLOGIES CO.,LTD +08D945 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN 00-A0-1B (hex) Zhone Technologies, Inc. @@ -184958,36 +186785,24 @@ AC84FA (base 16) Zhejiang Weilai Jingling Artificial Intelligence Technolog Plano TX 75024 US +AC-84-FA (hex) Zhejiang Weilai Jingling Artificial Intelligence Technology Co., Ltd. +AC84FA (base 16) Zhejiang Weilai Jingling Artificial Intelligence Technology Co., Ltd. + B2, 6th Floor (6-7 section), Xixi Campus, Ai Cheng Street, Wuchang Sub-district, Yuhang District, Hangzhou, Zhejiang Province, China + Hangzhou Zhejiang 310024 + CN + 3C-40-15 (hex) 12mm Health Technology (Hainan) Co., Ltd. 3C4015 (base 16) 12mm Health Technology (Hainan) Co., Ltd. Room A20-860, 5th Floor, Building A,Entrepreneurship Incubation Center,No. 266 Nanhai Avenue,National Hi-Tech Industrial Development Zone,Haikou City, Hainan Province, China Haikou Hainan 570100 CN -20-10-B1 (hex) Amazon Technologies Inc. -2010B1 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - -84-53-CD (hex) China Mobile Group Device Co.,Ltd. -8453CD (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN - -98-F6-7A (hex) Chipsea Technologies (Shenzhen) Crop. -98F67A (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 +64-2E-41 (hex) HUAWEI TECHNOLOGIES CO.,LTD +642E41 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -C4-9A-31 (hex) Zyxel Communications Corporation -C49A31 (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW - 0C-1A-61 (hex) Neox FZCO 0C1A61 (base 16) Neox FZCO S60517 Jebel Ali Freezone @@ -185036,24 +186851,6 @@ F4289D (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. Nanning Guangxi 530007 CN -48-0E-13 (hex) ittim -480E13 (base 16) ittim - 1202, No.6, Zhongguancun South Street, Haidian District, - beijing 100080 - CN - -74-34-91 (hex) Shenzhen Kings IoT Co., Ltd -743491 (base 16) Shenzhen Kings IoT Co., Ltd - D102, Yibao Garden, Hangcheng Road, Xixiang, BaoanBaoan district - Shenzhen City 518126 - CN - -08-D9-45 (hex) HUAWEI TECHNOLOGIES CO.,LTD -08D945 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN - 9C-C3-94 (hex) Apple, Inc. 9CC394 (base 16) Apple, Inc. 1 Infinite Loop @@ -185066,11 +186863,11 @@ F4289D (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. Cupertino CA 95014 US -64-2E-41 (hex) HUAWEI TECHNOLOGIES CO.,LTD -642E41 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +9C-3B-91 (hex) VSSL +9C3B91 (base 16) VSSL + 192 North Old Highway 91, Building 1 + Hurricane UT 84737 + US E0-26-11 (hex) Apple, Inc. E02611 (base 16) Apple, Inc. @@ -185084,6 +186881,24 @@ F4979D (base 16) IEEE Registration Authority Piscataway NJ 08554 US +D8-E0-16 (hex) Extreme Networks Headquarters +D8E016 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville NC 27560 + US + +B0-F1-AE (hex) eero inc. +B0F1AE (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +0C-58-7B (hex) Quectel Wireless Solutions Co.,Ltd. +0C587B (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 + CN + E8-A5-5A (hex) Juniper Networks E8A55A (base 16) Juniper Networks 1133 Innovation Way @@ -185114,70 +186929,34 @@ B0BC8E (base 16) SkyMirr Melbourne FL 32901 US -9C-3B-91 (hex) VSSL -9C3B91 (base 16) VSSL - 192 North Old Highway 91, Building 1 - Hurricane UT 84737 - US - 88-54-6B (hex) Texas Instruments 88546B (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -B0-14-DF (hex) MitraStar Technology Corp. -B014DF (base 16) MitraStar Technology Corp. - No. 6, Innovation Road II, - Hsinchu 300 - TW - -10-E6-6B (hex) Kaon Broadband CO., LTD. -10E66B (base 16) Kaon Broadband CO., LTD. - 884-3, Seongnam-daero, Bundang-gu - Seongnam-si 13517 - KR - -28-05-A5 (hex) Espressif Inc. -2805A5 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -B0-F1-AE (hex) eero inc. -B0F1AE (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US - -0C-58-7B (hex) Quectel Wireless Solutions Co.,Ltd. -0C587B (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN - -D8-E0-16 (hex) Extreme Networks Headquarters -D8E016 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville NC 27560 - US - +B0-14-DF (hex) MitraStar Technology Corp. +B014DF (base 16) MitraStar Technology Corp. + No. 6, Innovation Road II, + Hsinchu 300 + TW + +10-E6-6B (hex) Kaon Broadband CO., LTD. +10E66B (base 16) Kaon Broadband CO., LTD. + 884-3, Seongnam-daero, Bundang-gu + Seongnam-si 13517 + KR + AC-F4-66 (hex) HP Inc. ACF466 (base 16) HP Inc. 10300 Energy Dr Spring TX 77389 US -40-3E-22 (hex) VusionGroup -403E22 (base 16) VusionGroup - Kalsdorfer Straße 12 - Fernitz-Mellach Steiermark 8072 - AT - -D4-A2-54 (hex) HUAWEI TECHNOLOGIES CO.,LTD -D4A254 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +74-68-59 (hex) SUNITEC TECHNOLOGY CO.,LIMITED +746859 (base 16) SUNITEC TECHNOLOGY CO.,LIMITED + Floor 1-4, building C, Weixlangtal industrial park, no, 725, Dasan Village、Xingfu community, Fucheng Street, Longhua district + Shenzhen 518110 CN C0-62-F2 (hex) Beijing Cotytech Co.,LTD @@ -185192,10 +186971,22 @@ C062F2 (base 16) Beijing Cotytech Co.,LTD Beijing 100094 CN -74-68-59 (hex) SUNITEC TECHNOLOGY CO.,LIMITED -746859 (base 16) SUNITEC TECHNOLOGY CO.,LIMITED - Floor 1-4, building C, Weixlangtal industrial park, no, 725, Dasan Village、Xingfu community, Fucheng Street, Longhua district - Shenzhen 518110 +24-EE-5D (hex) Vizio, Inc +24EE5D (base 16) Vizio, Inc + 39 Tesla + Irvine CA 92618 + US + +40-3E-22 (hex) VusionGroup +403E22 (base 16) VusionGroup + Kalsdorfer Straße 12 + Fernitz-Mellach Steiermark 8072 + AT + +D4-A2-54 (hex) HUAWEI TECHNOLOGIES CO.,LTD +D4A254 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN EC-81-52 (hex) HUAWEI TECHNOLOGIES CO.,LTD @@ -185204,22 +186995,22 @@ EC8152 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN +68-CC-AE (hex) Fortinet, Inc. +68CCAE (base 16) Fortinet, Inc. + 899 Kifer Road + Sunnyvale 94086 + US + 10-BD-43 (hex) Robert Bosch Elektronikai Kft. 10BD43 (base 16) Robert Bosch Elektronikai Kft. Robert Bosch út 1. Hatvan Heves 3000 HU -24-EE-5D (hex) Vizio, Inc -24EE5D (base 16) Vizio, Inc - 39 Tesla - Irvine CA 92618 - US - -68-CC-AE (hex) Fortinet, Inc. -68CCAE (base 16) Fortinet, Inc. - 899 Kifer Road - Sunnyvale 94086 +78-11-9D (hex) Cisco Systems, Inc +78119D (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US 58-8F-CF (hex) Hangzhou Ezviz Software Co.,Ltd. @@ -185246,30 +187037,6 @@ EC8152 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Hangzhou Zhejiang 310051 CN -78-11-9D (hex) Cisco Systems, Inc -78119D (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US - -00-0B-0F (hex) Bosch Rexroth AG -000B0F (base 16) Bosch Rexroth AG - Bgm.-Dr.Nebel-Str.2 - Lohr am Main 97816 - NL - -A4-05-FD (hex) Bouffalo Lab (Nanjing) Co., Ltd. -A405FD (base 16) Bouffalo Lab (Nanjing) Co., Ltd. - 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China - Nanjing Jiangsu 211800 - CN - -3C-22-7F (hex) Quectel Wireless Solutions Co., Ltd. -3C227F (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-0D-AB (hex) Shenzhen Cudy Technology Co., Ltd. D40DAB (base 16) Shenzhen Cudy Technology Co., Ltd. 7th Floor, West Tower, Lepu building, Nanshan @@ -185282,6 +187049,12 @@ D40DAB (base 16) Shenzhen Cudy Technology Co., Ltd. shenzhen guangdong 518057 CN +00-0B-0F (hex) Bosch Rexroth AG +000B0F (base 16) Bosch Rexroth AG + Bgm.-Dr.Nebel-Str.2 + Lohr am Main 97816 + NL + 84-93-EC (hex) Guangzhou Shiyuan Electronic Technology Company Limited 8493EC (base 16) Guangzhou Shiyuan Electronic Technology Company Limited No.6, 4th Yunpu Road, Yunpu industry District @@ -185294,17 +187067,29 @@ F07084 (base 16) Actiontec Electronics Inc. Santa Clara CA 95054 US +40-44-F7 (hex) Nintendo Co.,Ltd +4044F7 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + +A4-05-FD (hex) Bouffalo Lab (Nanjing) Co., Ltd. +A405FD (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 + CN + A0-90-B5 (hex) Tiinlab Corporation A090B5 (base 16) Tiinlab Corporation Building A Room 201 Cooperation District between Shenzhen and HongKong,Qianwan Road No.1,Shenzhen City, Business Address:No. 3333, Liuxian AvenueTower A, 35th Floor,Tanglang City, Nanshan District, Shenzhen, China Shenzhen Guangdong 518000 CN -6C-7A-63 (hex) Arista Networks -6C7A63 (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 - US +28-83-28 (hex) EMALDO TECHNOLOGY(HK)LIMITED +288328 (base 16) EMALDO TECHNOLOGY(HK)LIMITED + 13/F., Golden Dragon Comm. Bldg., 522 Nathan Road, Yau Ma Tei, Kowloon + HONG KONG 999077 + HK AC-EB-E6 (hex) Espressif Inc. ACEBE6 (base 16) Espressif Inc. @@ -185312,17 +187097,29 @@ ACEBE6 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -28-83-28 (hex) EMALDO TECHNOLOGY(HK)LIMITED -288328 (base 16) EMALDO TECHNOLOGY(HK)LIMITED - 13/F., Golden Dragon Comm. Bldg., 522 Nathan Road, Yau Ma Tei, Kowloon - HONG KONG 999077 - HK +E8-B3-EE (hex) Pixelent Inc. +E8B3EE (base 16) Pixelent Inc. + #402 HanGuk Mediventure Center + 76, Dongnae-ro, Dong-gu Daegu 41061 + KR -40-44-F7 (hex) Nintendo Co.,Ltd -4044F7 (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP +6C-7A-63 (hex) Arista Networks +6C7A63 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US + +C4-16-8F (hex) Apple, Inc. +C4168F (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +F8-2A-E2 (hex) Apple, Inc. +F82AE2 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US 84-5C-31 (hex) Dell Inc. 845C31 (base 16) Dell Inc. @@ -185372,6 +187169,12 @@ ACEBE6 (base 16) Espressif Inc. TSAOTUEN, NANTOU 54261 TW +1C-7D-51 (hex) HANSHOW TECHNOLOGY CO.,LTD. +1C7D51 (base 16) HANSHOW TECHNOLOGY CO.,LTD. + The 1st Floor Podium and Floor 4 of Building 1, Floor 7 of Building 5, JiaxingPhotovoltaic Technology Innovation Park, No.1288, Kanghe Road, Xiuzhou District,Jiaxing City, Zhejiang Prov,P.R.China + JIAXING 314000 + CN + 40-2C-F4 (hex) Universal Global Scientific Industrial., Ltd 402CF4 (base 16) Universal Global Scientific Industrial., Ltd 141, Lane 351, Taiping Rd. Sec. 1, Tsao Tuen, @@ -185390,16 +187193,10 @@ ACEBE6 (base 16) Espressif Inc. Nan-Tou Taiwan 54261 TW -B0-1F-F4 (hex) Sagemcom Broadband SAS -B01FF4 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -E8-B3-EE (hex) Pixelent Inc. -E8B3EE (base 16) Pixelent Inc. - #402 HanGuk Mediventure Center - 76, Dongnae-ro, Dong-gu Daegu 41061 +50-FB-FF (hex) Franklin Technology Inc. +50FBFF (base 16) Franklin Technology Inc. + 906(Gasan-Dong, JEI Platz), 186, Gasan digital 1-ro, Geumcheon-gu + Seoul 08502 KR E0-CD-B8 (hex) Huawei Device Co., Ltd. @@ -185432,29 +187229,23 @@ B4E5C5 (base 16) Huawei Device Co., Ltd. Dongguan 523808 CN -C4-16-8F (hex) Apple, Inc. -C4168F (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -F8-2A-E2 (hex) Apple, Inc. -F82AE2 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +88-29-BF (hex) Amazon Technologies Inc. +8829BF (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -60-02-B4 (hex) WNC Corporation -6002B4 (base 16) WNC Corporation - No.20 Park Avenue II - Hsinchu 308 - TW +00-1A-B9 (hex) Groupe Carrus +001AB9 (base 16) Groupe Carrus + 56, avenue Raspail + Saint Maur 94100 + FR -00-0B-6B (hex) WNC Corporation -000B6B (base 16) WNC Corporation - No. 10-1, Li-Hsin Road I, Science-based - Hsinchu 300 - TW +C4-67-A1 (hex) Accelight Technologies (Wuhan) Inc. +C467A1 (base 16) Accelight Technologies (Wuhan) Inc. + 777 Guanggu 3rd, Bldg. #16, 5th Floor, + Wuhan Hubei, P. R. 430205 + CN E0-37-BF (hex) WNC Corporation E037BF (base 16) WNC Corporation @@ -185468,12 +187259,6 @@ D86162 (base 16) WNC Corporation Hsin-Chu R.O.C. 308 TW -50-FB-FF (hex) Franklin Technology Inc. -50FBFF (base 16) Franklin Technology Inc. - 906(Gasan-Dong, JEI Platz), 186, Gasan digital 1-ro, Geumcheon-gu - Seoul 08502 - KR - 64-FF-0A (hex) WNC Corporation 64FF0A (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park @@ -185492,35 +187277,29 @@ F46C68 (base 16) WNC Corporation Hsin-Chu R.O.C. 308 TW -1C-7D-51 (hex) HANSHOW TECHNOLOGY CO.,LTD. -1C7D51 (base 16) HANSHOW TECHNOLOGY CO.,LTD. - The 1st Floor Podium and Floor 4 of Building 1, Floor 7 of Building 5, JiaxingPhotovoltaic Technology Innovation Park, No.1288, Kanghe Road, Xiuzhou District,Jiaxing City, Zhejiang Prov,P.R.China - JIAXING 314000 - CN - -3C-0F-02 (hex) Espressif Inc. -3C0F02 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 58-96-71 (hex) WNC Corporation 589671 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park Hsin-Chu R.O.C. 308 TW -88-29-BF (hex) Amazon Technologies Inc. -8829BF (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +D8-33-2A (hex) Ruijie Networks Co.,LTD +D8332A (base 16) Ruijie Networks Co.,LTD + Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District + Fuzhou 35000 + CN -00-1A-B9 (hex) Groupe Carrus -001AB9 (base 16) Groupe Carrus - 56, avenue Raspail - Saint Maur 94100 - FR +60-02-B4 (hex) WNC Corporation +6002B4 (base 16) WNC Corporation + No.20 Park Avenue II + Hsinchu 308 + TW + +00-0B-6B (hex) WNC Corporation +000B6B (base 16) WNC Corporation + No. 10-1, Li-Hsin Road I, Science-based + Hsinchu 300 + TW 24-D5-3B (hex) Motorola Mobility LLC, a Lenovo Company 24D53B (base 16) Motorola Mobility LLC, a Lenovo Company @@ -185534,24 +187313,42 @@ C834E5 (base 16) Cisco Systems, Inc San Jose CA 94568 US -80-61-32 (hex) Cisco Systems, Inc -806132 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +98-9E-80 (hex) tonies GmbH +989E80 (base 16) tonies GmbH + Oststraße 119 + Düsseldorf NRW 40210 + DE + +24-C3-5D (hex) Duke University +24C35D (base 16) Duke University + 300 Fuller Street Box 104100 + Durham NC 27708 US -C4-67-A1 (hex) Accelight Technologies (Wuhan) Inc. -C467A1 (base 16) Accelight Technologies (Wuhan) Inc. - 777 Guanggu 3rd, Bldg. #16, 5th Floor, - Wuhan Hubei, P. R. 430205 +50-92-6A (hex) Beijing Xiaomi Mobile Software Co., Ltd +50926A (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -D8-33-2A (hex) Ruijie Networks Co.,LTD -D8332A (base 16) Ruijie Networks Co.,LTD - Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District - Fuzhou 35000 +04-1C-DB (hex) Siba Service +041CDB (base 16) Siba Service + 6F, Kobe Commerce, Industry and Trade Center Building, 5-1-14 Hamabe-dori, Chuo-ku + Kobe-shi Hyogo-ken 6510083 + JP + +98-3F-A4 (hex) zte corporation +983FA4 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN +80-61-32 (hex) Cisco Systems, Inc +806132 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + 88-18-F1 (hex) Nokia 8818F1 (base 16) Nokia 600 March Road @@ -185570,17 +187367,23 @@ E41613 (base 16) Extreme Networks Headquarters Morrisville NC 27560 US -98-3F-A4 (hex) zte corporation -983FA4 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +F0-FB-7F (hex) Mellanox Technologies, Inc. +F0FB7F (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US -E0-C9-32 (hex) Intel Corporate -E0C932 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +84-45-A0 (hex) Tube investments of India Limited +8445A0 (base 16) Tube investments of India Limited + Chola crest, 4th floor, No. C 54-55 & Super B4, Thiru Vi Ka Industrial Area, Guindy, Chennai - 600032 + Chennai Other 600032 + IN + +30-BC-4F (hex) Beijing Jianguo Bite Technology Co., Ltd. +30BC4F (base 16) Beijing Jianguo Bite Technology Co., Ltd. + RM1321, Building 2, Taihua Longqi Square, 19 Huangping ROAD, Changping district + Beijing Beijing 100096 + CN 54-36-31 (hex) Intel Corporate 543631 (base 16) Intel Corporate @@ -185600,29 +187403,17 @@ E0C932 (base 16) Intel Corporate Kulim Kedah 09000 MY -98-9E-80 (hex) tonies GmbH -989E80 (base 16) tonies GmbH - Oststraße 119 - Düsseldorf NRW 40210 - DE - -24-C3-5D (hex) Duke University -24C35D (base 16) Duke University - 300 Fuller Street Box 104100 - Durham NC 27708 - US - -50-92-6A (hex) Beijing Xiaomi Mobile Software Co., Ltd -50926A (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN +94-53-FF (hex) Intel Corporate +9453FF (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -04-1C-DB (hex) Siba Service -041CDB (base 16) Siba Service - 6F, Kobe Commerce, Industry and Trade Center Building, 5-1-14 Hamabe-dori, Chuo-ku - Kobe-shi Hyogo-ken 6510083 - JP +E0-C9-32 (hex) Intel Corporate +E0C932 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY A4-3F-A7 (hex) Hewlett Packard Enterprise A43FA7 (base 16) Hewlett Packard Enterprise @@ -185636,11 +187427,17 @@ A43FA7 (base 16) Hewlett Packard Enterprise New York NY New York NY 10017 US -94-53-FF (hex) Intel Corporate -9453FF (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +54-C1-D3 (hex) Guangzhou TR Intelligent Manufacturing Technology Co., Ltd +54C1D3 (base 16) Guangzhou TR Intelligent Manufacturing Technology Co., Ltd + Room 3101, TCL Tower, 18 Haizhou Road, Haizhu District, Guangzhou, Guangdong + Guangzhou City Guangdong Province 510000 + CN + +E0-31-5D (hex) EM Microelectronic +E0315D (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH 00-12-C1 (hex) Check Point Software Technologies Ltd. 0012C1 (base 16) Check Point Software Technologies Ltd. @@ -185666,46 +187463,10 @@ F0ABFA (base 16) Shenzhen Rayin Technology Co.,Ltd shenzhen guangdong 518000 CN -F0-FB-7F (hex) Mellanox Technologies, Inc. -F0FB7F (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US - -84-45-A0 (hex) Tube investments of India Limited -8445A0 (base 16) Tube investments of India Limited - Chola crest, 4th floor, No. C 54-55 & Super B4, Thiru Vi Ka Industrial Area, Guindy, Chennai - 600032 - Chennai Other 600032 - IN - -30-BC-4F (hex) Beijing Jianguo Bite Technology Co., Ltd. -30BC4F (base 16) Beijing Jianguo Bite Technology Co., Ltd. - RM1321, Building 2, Taihua Longqi Square, 19 Huangping ROAD, Changping district - Beijing Beijing 100096 - CN - -68-9F-D4 (hex) Amazon Technologies Inc. -689FD4 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - -54-C1-D3 (hex) Guangzhou TR Intelligent Manufacturing Technology Co., Ltd -54C1D3 (base 16) Guangzhou TR Intelligent Manufacturing Technology Co., Ltd - Room 3101, TCL Tower, 18 Haizhou Road, Haizhu District, Guangzhou, Guangdong - Guangzhou City Guangdong Province 510000 - CN - -E0-31-5D (hex) EM Microelectronic -E0315D (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH - -50-D0-6D (hex) Bird Buddy -50D06D (base 16) Bird Buddy - 169 Madison Avenue, Suite 15233 - New York NY 10016 +A4-4A-64 (hex) Maverick Mobile LLC +A44A64 (base 16) Maverick Mobile LLC + 8350 N. Central Expwy #1900 + Dallas TX 75206 US 5C-C4-1D (hex) Stone Devices Sdn. Bhd. @@ -185714,22 +187475,10 @@ E0315D (base 16) EM Microelectronic SENAI JOHOR 81400 MY -30-76-F5 (hex) Espressif Inc. -3076F5 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -A4-4A-64 (hex) Maverick Mobile LLC -A44A64 (base 16) Maverick Mobile LLC - 8350 N. Central Expwy #1900 - Dallas TX 75206 - US - -AC-E6-BB (hex) Google, Inc. -ACE6BB (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 +68-9F-D4 (hex) Amazon Technologies Inc. +689FD4 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US DC-44-B1 (hex) Hilti Corporation @@ -185744,6 +187493,12 @@ F4525B (base 16) Antare Technology Ltd London WC2A 2JR GB +50-D0-6D (hex) Bird Buddy +50D06D (base 16) Bird Buddy + 169 Madison Avenue, Suite 15233 + New York NY 10016 + US + 34-EF-8B (hex) NTT DOCOMO BUSINESS, Inc. 34EF8B (base 16) NTT DOCOMO BUSINESS, Inc. NTT DOCOMO BUSINESS Karagasaki Bldg. 1-11-7 Chuo-cho @@ -185762,28 +187517,10 @@ E0A366 (base 16) Motorola Mobility LLC, a Lenovo Company Shenzhen 518052 CN -38-44-BE (hex) Espressif Inc. -3844BE (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -30-46-9A (hex) NETGEAR -30469A (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -E0-46-9A (hex) NETGEAR -E0469A (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -A0-04-60 (hex) NETGEAR -A00460 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +AC-E6-BB (hex) Google, Inc. +ACE6BB (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US 2C-30-33 (hex) NETGEAR @@ -185792,11 +187529,17 @@ A00460 (base 16) NETGEAR San Jose CA 95134 US -50-4A-6E (hex) NETGEAR -504A6E (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +E4-1B-43 (hex) Beijing Xiaomi Mobile Software Co., Ltd +E41B43 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN + +38-0F-E4 (hex) Dedicated Network Partners Oy +380FE4 (base 16) Dedicated Network Partners Oy + Valimotie 13a + Helsinki 00380 + FI 54-07-7D (hex) NETGEAR 54077D (base 16) NETGEAR @@ -185840,24 +187583,6 @@ BCA511 (base 16) NETGEAR San Jose CA 95134 US -E4-1B-43 (hex) Beijing Xiaomi Mobile Software Co., Ltd -E41B43 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN - -28-B6-7C (hex) KEBODA Intelligent TECHNOLOGY CO., LTD. -28B67C (base 16) KEBODA Intelligent TECHNOLOGY CO., LTD. - Building 7, Lane 36, Xuelin Road, Pudong New Area - Shanghai Shanghai 200120 - CN - -F0-ED-51 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. -F0ED51 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. - No.218 Qianwangang Road - Qingdao Shangdong 266510 - CN - 60-A9-54 (hex) Cisco Systems, Inc 60A954 (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -185870,16 +187595,34 @@ F0ED51 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. San Jose CA 94568 US -38-0F-E4 (hex) Dedicated Network Partners Oy -380FE4 (base 16) Dedicated Network Partners Oy - Valimotie 13a - Helsinki 00380 - FI +30-46-9A (hex) NETGEAR +30469A (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US -68-2A-DD (hex) zte corporation -682ADD (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +E0-46-9A (hex) NETGEAR +E0469A (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +A0-04-60 (hex) NETGEAR +A00460 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +50-4A-6E (hex) NETGEAR +504A6E (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US + +F0-ED-51 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. +F0ED51 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. + No.218 Qianwangang Road + Qingdao Shangdong 266510 CN FC-3D-98 (hex) ACCTON TECHNOLOGY CORPORATION @@ -185888,6 +187631,18 @@ FC3D98 (base 16) ACCTON TECHNOLOGY CORPORATION Hsinchu 30077 TW +28-B6-7C (hex) KEBODA Intelligent TECHNOLOGY CO., LTD. +28B67C (base 16) KEBODA Intelligent TECHNOLOGY CO., LTD. + Building 7, Lane 36, Xuelin Road, Pudong New Area + Shanghai Shanghai 200120 + CN + +68-2A-DD (hex) zte corporation +682ADD (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + 60-29-72 (hex) Arista Networks 602972 (base 16) Arista Networks 5453 Great America Parkway @@ -185918,6 +187673,12 @@ A4B1E9 (base 16) Vantiva Technologies Belgium Toronto Ontario M2N 6L7 CA +48-FC-7C (hex) Shenzhen Huidu Technology Co., Ltd. +48FC7C (base 16) Shenzhen Huidu Technology Co., Ltd. + 18F, No. 196 Tangtou Street, Shiyan Town, Baoan District, Shenzhen + Shenzhen 518000 + CN + 9C-DF-8A (hex) HUAWEI TECHNOLOGIES CO.,LTD 9CDF8A (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -185942,17 +187703,23 @@ FCA27E (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -48-FC-7C (hex) Shenzhen Huidu Technology Co., Ltd. -48FC7C (base 16) Shenzhen Huidu Technology Co., Ltd. - 18F, No. 196 Tangtou Street, Shiyan Town, Baoan District, Shenzhen - Shenzhen 518000 - CN +00-92-35 (hex) Apple, Inc. +009235 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -F4-64-B6 (hex) Sercomm Corporation. -F464B6 (base 16) Sercomm Corporation. - 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen - Miao-Lih Hsuan 115 - TW +F0-2F-BA (hex) Apple, Inc. +F02FBA (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +E4-CE-58 (hex) Anhui Realloong Automotive Electronics Co.,Ltd +E4CE58 (base 16) Anhui Realloong Automotive Electronics Co.,Ltd + Anhui Realloong Automotive Electronics Co.,Ltd + Hefei Anhui 230088 + CN 74-14-D0 (hex) Apple, Inc. 7414D0 (base 16) Apple, Inc. @@ -185960,12 +187727,6 @@ F464B6 (base 16) Sercomm Corporation. Cupertino CA 95014 US -C0-EE-40 (hex) Ezurio, LLC -C0EE40 (base 16) Ezurio, LLC - 50 South Main St - Akron 44308 - US - 3C-FB-02 (hex) Apple, Inc. 3CFB02 (base 16) Apple, Inc. 1 Infinite Loop @@ -185990,22 +187751,43 @@ F478AC (base 16) Apple, Inc. Cupertino CA 95014 US -00-92-35 (hex) Apple, Inc. -009235 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +7C-24-6A (hex) Scita Solutions +7C246A (base 16) Scita Solutions + 218, 2nd Cross, ISRO Layout + Bangalore Karnataka 560078 + IN -F0-2F-BA (hex) Apple, Inc. -F02FBA (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +F4-64-B6 (hex) Sercomm Corporation. +F464B6 (base 16) Sercomm Corporation. + 3F,No.81,Yu-Yih Rd.,Chu-Nan Chen + Miao-Lih Hsuan 115 + TW + +C0-EE-40 (hex) Ezurio, LLC +C0EE40 (base 16) Ezurio, LLC + 50 South Main St + Akron 44308 US -E4-CE-58 (hex) Anhui Realloong Automotive Electronics Co.,Ltd -E4CE58 (base 16) Anhui Realloong Automotive Electronics Co.,Ltd - Anhui Realloong Automotive Electronics Co.,Ltd - Hefei Anhui 230088 +C8-AD-E7 (hex) Shenzhen Shengxi Industrial Co.,Ltd +C8ADE7 (base 16) Shenzhen Shengxi Industrial Co.,Ltd + Unit 402, Building 10, Yuanling New Village, Yuanling Community + Yuanling Street Futian District 518028 + CN + +AC-3D-FA (hex) Hangzhou Huacheng Network Technology Co.,Ltd +AC3DFA (base 16) Hangzhou Huacheng Network Technology Co.,Ltd + 13th Floor, Building 3, No. 582, Liye Road, Changhe Street, Binjiang District, Hangzhou, China (Zhejiang) Pilot Free Trade Zone + Hangzhou 311200 + CN + +8C-A4-54 (hex) Private +8CA454 (base 16) Private + +C8-74-1B (hex) Fiberhome Telecommunication Technologies Co.,LTD +C8741B (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN F4-E2-5D (hex) AltoBeam Inc. @@ -186014,12 +187796,6 @@ F4E25D (base 16) AltoBeam Inc. Beijing Beijing 100083 CN -7C-24-6A (hex) Scita Solutions -7C246A (base 16) Scita Solutions - 218, 2nd Cross, ISRO Layout - Bangalore Karnataka 560078 - IN - CC-36-BB (hex) Silicon Laboratories CC36BB (base 16) Silicon Laboratories 400 West Cesar Chavez @@ -186038,63 +187814,18 @@ CC7645 (base 16) Microsoft Corporation Singapore 068902 SG -C8-AD-E7 (hex) Shenzhen Shengxi Industrial Co.,Ltd -C8ADE7 (base 16) Shenzhen Shengxi Industrial Co.,Ltd - Unit 402, Building 10, Yuanling New Village, Yuanling Community - Yuanling Street Futian District 518028 - CN - -AC-3D-FA (hex) Hangzhou Huacheng Network Technology Co.,Ltd -AC3DFA (base 16) Hangzhou Huacheng Network Technology Co.,Ltd - 13th Floor, Building 3, No. 582, Liye Road, Changhe Street, Binjiang District, Hangzhou, China (Zhejiang) Pilot Free Trade Zone - Hangzhou 311200 - CN - -54-56-18 (hex) Huawei Device Co., Ltd. -545618 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - 8C-5D-54 (hex) Kisi 8C5D54 (base 16) Kisi 45 Main St Brooklyn NY 11210 US -C8-74-1B (hex) Fiberhome Telecommunication Technologies Co.,LTD -C8741B (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN - -F0-16-1D (hex) Espressif Inc. -F0161D (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -64-A3-37 (hex) Garmin International -64A337 (base 16) Garmin International - 1200 E. 151st St - Olathe KS 66062 - US - -8C-A4-54 (hex) Private -8CA454 (base 16) Private - -C0-CF-64 (hex) Hangzhou Zenith Electron Co.,Ltd -C0CF64 (base 16) Hangzhou Zenith Electron Co.,Ltd - Room 1702, No.888, Zhongxin Road, Beigan Street. Xiaoshan District, Hangzhou City, Zhejiang - Hangzhou Zhejiang 310000 +54-56-18 (hex) Huawei Device Co., Ltd. +545618 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -30-77-DF (hex) Terex Corporation -3077DF (base 16) Terex Corporation - 18620 NE 67th Ct - Redmond WA 98052 - US - 58-50-9F (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. 58509F (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China @@ -186119,17 +187850,17 @@ C0CF64 (base 16) Hangzhou Zenith Electron Co.,Ltd Beijing 100029 CN -B4-38-36 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B43836 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +64-A3-37 (hex) Garmin International +64A337 (base 16) Garmin International + 1200 E. 151st St + Olathe KS 66062 + US -38-2F-B0 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -382FB0 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN +5C-51-36 (hex) Samsung Electronics Co.,Ltd +5C5136 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR 34-56-ED (hex) Goerdyna Group Co., Ltd 3456ED (base 16) Goerdyna Group Co., Ltd @@ -186137,18 +187868,18 @@ B43836 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Qingdao City Shandong Province 266000 CN -BC-AF-6E (hex) Arcadyan Corporation -BCAF6E (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW - -08-9C-74 (hex) UNIONMAN TECHNOLOGY CO.,LTD -089C74 (base 16) UNIONMAN TECHNOLOGY CO.,LTD - No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway - Huizhou Guangdong 516025 +C0-CF-64 (hex) Hangzhou Zenith Electron Co.,Ltd +C0CF64 (base 16) Hangzhou Zenith Electron Co.,Ltd + Room 1702, No.888, Zhongxin Road, Beigan Street. Xiaoshan District, Hangzhou City, Zhejiang + Hangzhou Zhejiang 310000 CN +30-77-DF (hex) Terex Corporation +3077DF (base 16) Terex Corporation + 18620 NE 67th Ct + Redmond WA 98052 + US + 90-1F-09 (hex) Silicon Laboratories 901F09 (base 16) Silicon Laboratories 400 West Cesar Chavez @@ -186167,17 +187898,17 @@ B4BFE9 (base 16) Espressif Inc. Shanghai Shanghai 201203 CN -5C-51-36 (hex) Samsung Electronics Co.,Ltd -5C5136 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +B4-38-36 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B43836 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -BC-27-7A (hex) Samsung Electronics Co.,Ltd -BC277A (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +B0-42-B7 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B042B7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN 80-0D-3F (hex) Samsung Electronics Co.,Ltd 800D3F (base 16) Samsung Electronics Co.,Ltd @@ -186185,10 +187916,10 @@ BC277A (base 16) Samsung Electronics Co.,Ltd Suwon Gyeonggi-Do 16677 KR -B0-42-B7 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B042B7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +38-2F-B0 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +382FB0 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN 30-8B-23 (hex) Annapurna labs @@ -186221,11 +187952,23 @@ A49DB8 (base 16) SHENZHEN TECNO TECHNOLOGY Shenzhen guangdong 518000 CN -AC-C3-58 (hex) AUMOVIO Czech Republic s.r.o. -ACC358 (base 16) AUMOVIO Czech Republic s.r.o. - Průmyslová 1851 - Brandýs nad Labem 250 01 - CZ +08-9C-74 (hex) UNIONMAN TECHNOLOGY CO.,LTD +089C74 (base 16) UNIONMAN TECHNOLOGY CO.,LTD + No.5,Huitai Road,Huinan High-Tech Park,Huiao Highway + Huizhou Guangdong 516025 + CN + +BC-27-7A (hex) Samsung Electronics Co.,Ltd +BC277A (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +BC-AF-6E (hex) Arcadyan Corporation +BCAF6E (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW E4-12-26 (hex) AUMOVIO Technologies Romania S.R.L. E41226 (base 16) AUMOVIO Technologies Romania S.R.L. @@ -186233,23 +187976,11 @@ E41226 (base 16) AUMOVIO Technologies Romania S.R.L. Timisoara 300701 RO -A8-57-BA (hex) Shenzhen YOUHUA Technology Co., Ltd -A857BA (base 16) Shenzhen YOUHUA Technology Co., Ltd - Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District - Shenzhen Guangdong 518055 - CN - -64-18-DF (hex) Sagemcom Broadband SAS -6418DF (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -98-78-00 (hex) TCT mobile ltd -987800 (base 16) TCT mobile ltd - No.86 hechang 7th road, zhongkai, Hi-Tech District - Hui Zhou Guang Dong 516006 - CN +AC-C3-58 (hex) AUMOVIO Czech Republic s.r.o. +ACC358 (base 16) AUMOVIO Czech Republic s.r.o. + Průmyslová 1851 + Brandýs nad Labem 250 01 + CZ 00-05-DB (hex) PSI Software SE, 0005DB (base 16) PSI Software SE, @@ -186257,6 +187988,18 @@ A857BA (base 16) Shenzhen YOUHUA Technology Co., Ltd Karlsruhe 76131 DE +C0-A4-B9 (hex) Sichuan AI-Link Technology Co., Ltd. +C0A4B9 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 + CN + +40-5A-DD (hex) Actions Microelectronics +405ADD (base 16) Actions Microelectronics + 201, No. 9 Building, Software Park, KeJiZhongEr Road., GaoXingQu, NanShan + Shenzhen Guangdong 518057 + CN + 80-E6-3C (hex) Xiaomi Communications Co Ltd 80E63C (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road @@ -186275,22 +188018,10 @@ A857BA (base 16) Shenzhen YOUHUA Technology Co., Ltd San Jose CA 95002 US -88-45-58 (hex) Amicro Technology Co., Ltd. -884558 (base 16) Amicro Technology Co., Ltd. - 14F Novotown Huixin Office Building,No. 88, Zhishui Road, Hengqin - Zhuhai Guangdong 519000 - CN - -10-CB-33 (hex) NXP Semiconductors Taiwan Ltd. -10CB33 (base 16) NXP Semiconductors Taiwan Ltd. - No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan - Nanzi Dist. Kaohsiung 811643 - TW - -40-5A-DD (hex) Actions Microelectronics -405ADD (base 16) Actions Microelectronics - 201, No. 9 Building, Software Park, KeJiZhongEr Road., GaoXingQu, NanShan - Shenzhen Guangdong 518057 +A8-57-BA (hex) Shenzhen YOUHUA Technology Co., Ltd +A857BA (base 16) Shenzhen YOUHUA Technology Co., Ltd + Room 407 Shenzhen University-town Business Park,Lishan Road,Taoyuan Street,Nanshan District + Shenzhen Guangdong 518055 CN 7C-87-67 (hex) Cisco Systems, Inc @@ -186305,29 +188036,23 @@ A857BA (base 16) Shenzhen YOUHUA Technology Co., Ltd San Jose CA 94568 US -24-A5-FF (hex) Fairbanks Scales -24A5FF (base 16) Fairbanks Scales - 2176 Portland Street - St.Johnsbury VT 05819 - US - -C0-A4-B9 (hex) Sichuan AI-Link Technology Co., Ltd. -C0A4B9 (base 16) Sichuan AI-Link Technology Co., Ltd. - Anzhou, Industrial Park - Mianyang Sichuan 622650 +98-78-00 (hex) TCT mobile ltd +987800 (base 16) TCT mobile ltd + No.86 hechang 7th road, zhongkai, Hi-Tech District + Hui Zhou Guang Dong 516006 CN -8C-22-D2 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. -8C22D2 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. - No.555 Qianmo Road - Hangzhou Zhejiang 310052 +88-45-58 (hex) Amicro Technology Co., Ltd. +884558 (base 16) Amicro Technology Co., Ltd. + 14F Novotown Huixin Office Building,No. 88, Zhishui Road, Hengqin + Zhuhai Guangdong 519000 CN -20-9B-DD (hex) HUAWEI TECHNOLOGIES CO.,LTD -209BDD (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +10-CB-33 (hex) NXP Semiconductors Taiwan Ltd. +10CB33 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW C4-49-1B (hex) Apple, Inc. C4491B (base 16) Apple, Inc. @@ -186347,16 +188072,10 @@ C4491B (base 16) Apple, Inc. Cupertino CA 95014 US -08-02-99 (hex) HC Corporation -080299 (base 16) HC Corporation - 1201, 12th F, Seongnam Woolim Lion’s Valley 1Bldg, 27, Dunchen-Daero 457beon-gil, Jungwon-gu - Seongnam Gyengido 13219 - KR - -80-77-86 (hex) IEEE Registration Authority -807786 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +24-A5-FF (hex) Fairbanks Scales +24A5FF (base 16) Fairbanks Scales + 2176 Portland Street + St.Johnsbury VT 05819 US 74-29-20 (hex) MCX-PRO Kft. @@ -186371,29 +188090,29 @@ C4491B (base 16) Apple, Inc. Cupertino CA 95014 US -F8-0C-9A (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -F80C9A (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN - -54-1F-CD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -541FCD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN - 60-47-0A (hex) Shenzhen Zenith Intelligent Technology Co., Ltd. 60470A (base 16) Shenzhen Zenith Intelligent Technology Co., Ltd. Room 1606, Building C3, Nanshan Kexing Science Park, Nanshan District Shenzhen 518000 CN -94-FC-87 (hex) Hirschmann Automation and Control GmbH -94FC87 (base 16) Hirschmann Automation and Control GmbH - Stuttgarter Straße 45-51 - Neckartenzlingen D-72654 - DE +8C-22-D2 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +8C22D2 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN + +20-9B-DD (hex) HUAWEI TECHNOLOGIES CO.,LTD +209BDD (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +B8-32-8F (hex) eero inc. +B8328F (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US F4-A3-C2 (hex) Shenzhen iComm Semiconductor CO.,LTD F4A3C2 (base 16) Shenzhen iComm Semiconductor CO.,LTD @@ -186407,11 +188126,11 @@ F4A3C2 (base 16) Shenzhen iComm Semiconductor CO.,LTD Shenzhen GuangDong 518000 CN -64-31-36 (hex) Mellanox Technologies, Inc. -643136 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +08-02-99 (hex) HC Corporation +080299 (base 16) HC Corporation + 1201, 12th F, Seongnam Woolim Lion’s Valley 1Bldg, 27, Dunchen-Daero 457beon-gil, Jungwon-gu + Seongnam Gyengido 13219 + KR 3C-65-D1 (hex) HUAWEI TECHNOLOGIES CO.,LTD 3C65D1 (base 16) HUAWEI TECHNOLOGIES CO.,LTD @@ -186419,34 +188138,52 @@ F4A3C2 (base 16) Shenzhen iComm Semiconductor CO.,LTD Dongguan 523808 CN -B8-32-8F (hex) eero inc. -B8328F (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +80-77-86 (hex) IEEE Registration Authority +807786 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -E8-F0-94 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -E8F094 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 +F8-0C-9A (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +F80C9A (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -B8-CD-58 (hex) Shenzhen SuperElectron Technology Co.,Ltd. -B8CD58 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. - 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city - Shenzhen Guangdong 518000 +54-1F-CD (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +541FCD (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 CN -BC-8D-2D (hex) Fiberhome Telecommunication Technologies Co.,LTD -BC8D2D (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +AC-27-6E (hex) Espressif Inc. +AC276E (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 CN -EC-30-DD (hex) eero inc. -EC30DD (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +64-31-36 (hex) Mellanox Technologies, Inc. +643136 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +88-F1-55 (hex) Espressif Inc. +88F155 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +94-FC-87 (hex) Hirschmann Automation and Control GmbH +94FC87 (base 16) Hirschmann Automation and Control GmbH + Stuttgarter Straße 45-51 + Neckartenzlingen D-72654 + DE + +E4-79-3F (hex) Juniper Networks +E4793F (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US B8-55-EA (hex) Yantai Jahport Electronic Technology Co., Ltd. @@ -186461,18 +188198,36 @@ B855EA (base 16) Yantai Jahport Electronic Technology Co., Ltd. HO CHI MINH CITY HO CHI MINH 820000 VN -88-F1-55 (hex) Espressif Inc. -88F155 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +B8-CD-58 (hex) Shenzhen SuperElectron Technology Co.,Ltd. +B8CD58 (base 16) Shenzhen SuperElectron Technology Co.,Ltd. + 1213-1214, haosheng business center, dongbin road, nanshan street, nanshan district, shenzhen city + Shenzhen Guangdong 518000 CN -AC-27-6E (hex) Espressif Inc. -AC276E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +BC-8D-2D (hex) Fiberhome Telecommunication Technologies Co.,LTD +BC8D2D (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN +EC-30-DD (hex) eero inc. +EC30DD (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +E8-F0-94 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +E8F094 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN + +3C-F7-5D (hex) Zyxel Communications Corporation +3CF75D (base 16) Zyxel Communications Corporation + No. 6 Innovation Road II, Science Park + Hsichu Taiwan 300 + TW + 00-B8-1D (hex) Extreme Networks Headquarters 00B81D (base 16) Extreme Networks Headquarters 2121 RDU Center Drive @@ -186485,23 +188240,11 @@ AC276E (base 16) Espressif Inc. Qingdao 266101 CN -E4-79-3F (hex) Juniper Networks -E4793F (base 16) Juniper Networks - 1133 Innovation Way - Sunnyvale CA 94089 - US - CC-58-C7 (hex) Nokia CC58C7 (base 16) Nokia 600 March Road - Kanata Ontario K2K 2E6 - CA - -B0-95-01 (hex) EM Microelectronic -B09501 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH + Kanata Ontario K2K 2E6 + CA 64-1B-85 (hex) Vantiva USA LLC 641B85 (base 16) Vantiva USA LLC @@ -186515,6 +188258,12 @@ B09501 (base 16) EM Microelectronic Qingdao 266000 CN +B0-95-01 (hex) EM Microelectronic +B09501 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + D8-5B-27 (hex) WNC Corporation D85B27 (base 16) WNC Corporation No.20,Park Avenue II,Hsinchu Science Park @@ -186527,6 +188276,12 @@ C42C7B (base 16) VIETNAM POST AND TELECOMMUNICATION INDUSTRY TECHNOLOGY JOI Hanoi 100000 VN +F4-A1-57 (hex) Huawei Device Co., Ltd. +F4A157 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + A8-72-4D (hex) Intel Corporate A8724D (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -186539,18 +188294,6 @@ A8724D (base 16) Intel Corporate Kulim Kedah 09000 MY -3C-F7-5D (hex) Zyxel Communications Corporation -3CF75D (base 16) Zyxel Communications Corporation - No. 6 Innovation Road II, Science Park - Hsichu Taiwan 300 - TW - -F4-A1-57 (hex) Huawei Device Co., Ltd. -F4A157 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN - 34-D7-F5 (hex) IEEE Registration Authority 34D7F5 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -186575,12 +188318,6 @@ C47BE3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -DC-5D-31 (hex) ITEL MOBILE LIMITED -DC5D31 (base 16) ITEL MOBILE LIMITED - RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K - Hong Kong KOWLOON 999077 - HK - 60-72-0B (hex) BLU Products Inc 60720B (base 16) BLU Products Inc 8600 NW 36th Street Suite 200 @@ -186593,11 +188330,11 @@ DC5D31 (base 16) ITEL MOBILE LIMITED Miami FL 33166 US -74-6A-84 (hex) Texas Instruments -746A84 (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +A8-CA-87 (hex) ZHEJIANG DAHUA ZHILIAN CO.,LTD +A8CA87 (base 16) ZHEJIANG DAHUA ZHILIAN CO.,LTD + No.28, Dongqiao Road, Dongzhou Street, Fuyang District, Hangzhou, P.R. China + HANGZHOU 311400 + CN A8-61-EC (hex) Texas Instruments A861EC (base 16) Texas Instruments @@ -186605,17 +188342,11 @@ A861EC (base 16) Texas Instruments Dallas TX 75243 US -A8-CA-87 (hex) ZHEJIANG DAHUA ZHILIAN CO.,LTD -A8CA87 (base 16) ZHEJIANG DAHUA ZHILIAN CO.,LTD - No.28, Dongqiao Road, Dongzhou Street, Fuyang District, Hangzhou, P.R. China - HANGZHOU 311400 - CN - -14-63-93 (hex) Espressif Inc. -146393 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +74-6A-84 (hex) Texas Instruments +746A84 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US B0-8C-B3 (hex) FN-LINK TECHNOLOGY Ltd. B08CB3 (base 16) FN-LINK TECHNOLOGY Ltd. @@ -186623,11 +188354,11 @@ B08CB3 (base 16) FN-LINK TECHNOLOGY Ltd. Changsha Hunan 410329 CN -F0-0C-51 (hex) zte corporation -F00C51 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +DC-5D-31 (hex) ITEL MOBILE LIMITED +DC5D31 (base 16) ITEL MOBILE LIMITED + RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K + Hong Kong KOWLOON 999077 + HK 80-E8-69 (hex) AltoBeam Inc. 80E869 (base 16) AltoBeam Inc. @@ -186641,18 +188372,6 @@ D489C1 (base 16) Ubiquiti Inc New York NY New York NY 10017 US -24-D6-60 (hex) Silicon Laboratories -24D660 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US - -18-C3-E4 (hex) IEEE Registration Authority -18C3E4 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - 8C-0F-7E (hex) TCL King Electrical Appliances(Huizhou)Co.,Ltd 8C0F7E (base 16) TCL King Electrical Appliances(Huizhou)Co.,Ltd B Area, 10th floor, TCL multimedia Building, TCL International E City, #1001 Zhonshanyuan road,Shenzhen @@ -186671,6 +188390,12 @@ D489C1 (base 16) Ubiquiti Inc Nanzi Dist. Kaohsiung 811643 TW +F0-0C-51 (hex) zte corporation +F00C51 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + 08-95-36 (hex) Actiontec Electronics Inc. 089536 (base 16) Actiontec Electronics Inc. 2445 Augustine Dr #501 @@ -186683,18 +188408,48 @@ D489C1 (base 16) Ubiquiti Inc San Jose CA 94568 US -C0-3A-55 (hex) TP-Link Systems Inc. -C03A55 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US - 90-6F-18 (hex) Afero, Inc. 906F18 (base 16) Afero, Inc. 4410 El Camino Real, Suite 200 Los Altos 94022 US +24-D6-60 (hex) Silicon Laboratories +24D660 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +18-C3-E4 (hex) IEEE Registration Authority +18C3E4 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +18-4F-5D (hex) Japan Radio Co., Ltd +184F5D (base 16) Japan Radio Co., Ltd + NAKANO CENTRAL PARK EAST 10-1, Nakano 4-chome + Nakano-ku Tokyo 164-8570 + JP + +6C-28-13 (hex) nFore Technology Co., Ltd. +6C2813 (base 16) nFore Technology Co., Ltd. + 5F, No.31, Ln. 258, Ruiguang Rd., Neihu Dist., + Taipei city 114 + TW + +08-35-7D (hex) Microsoft Corporation +08357D (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +C0-3A-55 (hex) TP-Link Systems Inc. +C03A55 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + B8-87-88 (hex) HP Inc. B88788 (base 16) HP Inc. 10300 Energy Dr @@ -186725,36 +188480,1350 @@ F8CB15 (base 16) Apple, Inc. Cupertino CA 95014 US -18-4F-5D (hex) Japan Radio Co., Ltd -184F5D (base 16) Japan Radio Co., Ltd - NAKANO CENTRAL PARK EAST 10-1, Nakano 4-chome - Nakano-ku Tokyo 164-8570 +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 + +D4-CE-40 (hex) Apple, Inc. +D4CE40 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +88-70-15 (hex) Apple, Inc. +887015 (base 16) Apple, Inc. + 1 Infinite Loop + 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 + +D8-62-CA (hex) Cisco Systems, Inc +D862CA (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +74-25-54 (hex) NVIDIA Corporation +742554 (base 16) NVIDIA Corporation + 2701 San Tomas Expressway + Santa Clara CA 95050 + 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-28-13 (hex) nFore Technology Co., Ltd. -6C2813 (base 16) nFore Technology Co., Ltd. - 5F, No.31, Ln. 258, Ruiguang Rd., Neihu Dist., - Taipei city 114 +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 + +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 + +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 + +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 + +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 + +E4-FB-1E (hex) Microsoft Corporation +E4FB1E (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + 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 -08-35-7D (hex) Microsoft Corporation -08357D (base 16) Microsoft Corporation +9C-80-DF (hex) Arcadyan Corporation +9C80DF (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +88-03-55 (hex) Arcadyan Corporation +880355 (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 + +28-87-5F (hex) Annapurna labs +28875F (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +38-6D-ED (hex) Juniper Networks +386DED (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US + +10-3A-5D (hex) Emerson +103A5D (base 16) Emerson + 6021 Innovation Blvd + Shakopee MN 55379 + US + +30-08-4D (hex) Trumpf Hüttinger +30084D (base 16) Trumpf Hüttinger + Bötzingerstraße 80 + Freiburg 79111 + DE + +4C-09-D4 (hex) Arcadyan Corporation +4C09D4 (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW + +DC-9D-ED (hex) Samsung Electronics Co.,Ltd +DC9DED (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +AC-62-FF (hex) Vantiva USA LLC +AC62FF (base 16) Vantiva USA LLC + 4855 Peachtree Industrial Blvd, Suite 200 + Norcross GA 30902 + US + +AC-46-A7 (hex) SERCOMM PHILIPPINES INC +AC46A7 (base 16) SERCOMM PHILIPPINES INC + Lot 1 & 5, Phase 1, Filinvest Technology Park 1, Brgy. Punta, Calamba City + Calamba Lot 1 + PH + +30-C8-A2 (hex) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED +30C8A2 (base 16) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED + Room 03, 23/F, Unit B Building, No 9, Shenzhen Bay Eco -Technology Park, Yuehai Street, Nanshan District, Shenzhen, China + Shenzhen 518000 + 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 + +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 + +34-C2-32 (hex) Samsung Electronics Co.,Ltd +34C232 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +9C-F2-7E (hex) Samsung Electronics Co.,Ltd +9CF27E (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +D0-EA-30 (hex) NXP Semiconductors Taiwan Ltd. +D0EA30 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW + +F0-A4-EA (hex) Huawei Device Co., Ltd. +F0A4EA (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +A8-F0-7C (hex) Huawei Device Co., Ltd. +A8F07C (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +98-E3-01 (hex) Shenzhen Sundray Technologies company Limited +98E301 (base 16) Shenzhen Sundray Technologies company Limited + 1st Floor Building A1, Nanshan i Park, No.1001 Xueyuan Road, Nanshan District, Shenzhen, Guangdong Province, P. R. China + Shenzhen GuangDong 518057 + CN + +08-7D-60 (hex) SAMJIN Co.ltd +087D60 (base 16) SAMJIN Co.ltd + 81, Anyangcheonseo-ro, Manan-gu + Anyang-si Gyeonggi-do 14087 + KR + +BC-B3-0E (hex) Cisco Systems, Inc +BCB30E (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +00-07-A7 (hex) Glory Technical Solutions Co., Ltd. +0007A7 (base 16) Glory Technical Solutions Co., Ltd. + 1-3-1 Shimoteno + Himeji Hyogo 00000 + JP + +3C-15-5A (hex) Nokia +3C155A (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +24-36-72 (hex) AMPAK Technology Inc. +243672 (base 16) AMPAK Technology Inc. + 6F., No23, Huanke 1st Rd. + Zhubei City Hsinchu County 302047 + TW + +30-07-A3 (hex) Shenzhen Skyworth Digital Technology CO., Ltd +3007A3 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + +EC-8F-72 (hex) Barrot Technology Co.,Ltd. +EC8F72 (base 16) Barrot Technology Co.,Ltd. + A1009,Block A,Jia Hua Building,No.9 Shangdi 3rd Street,Haidian District,Beijing + beijing beijing 100000 + CN + +EC-B9-31 (hex) TP-Link Systems Inc. +ECB931 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +D4-D6-DF (hex) TP-Link Systems Inc. +D4D6DF (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +00-3C-B7 (hex) AzureWave Technology Inc. +003CB7 (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW + +EC-71-5E (hex) Freefly Systems Inc +EC715E (base 16) Freefly Systems Inc + 16650 Woodinville-Redmond RD NE, Bldg D + Woodinville WA 98072 + US + +44-7B-45 (hex) Amazon Technologies Inc. +447B45 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +90-FC-55 (hex) Hyve Solutions +90FC55 (base 16) Hyve Solutions + 44201 Nobel Dr + Fremont CA 94538 + US + +CC-E7-DE (hex) IEEE Registration Authority +CCE7DE (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +48-F9-25 (hex) Maestronic +48F925 (base 16) Maestronic + #205-3689 1st Avenue East + Vancouver 88BC V5M 1C2 + CA + +4C-A0-3D (hex) Bouffalo Lab (Nanjing) Co., Ltd. +4CA03D (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 + CN + +E4-FA-DE (hex) Microsoft Corporation +E4FADE (base 16) Microsoft Corporation One Microsoft Way REDMOND WA 98052 US -D4-CE-40 (hex) Apple, Inc. -D4CE40 (base 16) Apple, Inc. +CC-DD-28 (hex) ACCTON TECHNOLOGY CORPORATION +CCDD28 (base 16) ACCTON TECHNOLOGY CORPORATION + No.1, Creation Road 3, Hsinchu Science Park, + Hsinchu 30077 + TW + +80-99-CF (hex) Texas Instruments +8099CF (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +38-FF-59 (hex) Dell Inc. +38FF59 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 + US + +28-69-26 (hex) OPTOKON, a.s. +286926 (base 16) OPTOKON, a.s. + Červený Kříž 250 + Jihlava 586 01 + CZ + +B0-9C-18 (hex) Shenzhen Taichi Technology Limited +B09C18 (base 16) Shenzhen Taichi Technology Limited + A1710, Nanshan Cloud Technology Building, Vanke Cloud City, Liuxin Third Street, Xili Community, Xili Street, Nanshan District, + Shenzhen Guangdong Province 518000 + CN + +8C-D1-A6 (hex) eero inc. +8CD1A6 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +08-CB-E5 (hex) R3 Solutions GmbH +08CBE5 (base 16) R3 Solutions GmbH + Kurfürstendamm 194 + Berlin 10707 + DE + +74-3C-DE (hex) Hewlett Packard Enterprise +743CDE (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +E4-8F-B7 (hex) Arista Networks +E48FB7 (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 + US + +0C-FE-E5 (hex) Texas Instruments +0CFEE5 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +D8-A4-69 (hex) Sonova AG +D8A469 (base 16) Sonova AG + Laubisruetistrasse 28 + Staefa 8712 + CH + +70-2D-81 (hex) Infinix mobility limited +702D81 (base 16) Infinix mobility limited + RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG + HongKong HongKong 999077 + HK + +94-59-15 (hex) Amazon Technologies Inc. +945915 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +A4-4F-3E (hex) IEEE Registration Authority +A44F3E (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +CC-7A-8B (hex) SHENZHEN TECNO TECHNOLOGY +CC7A8B (base 16) SHENZHEN TECNO TECHNOLOGY + 101,Building 24,Waijing Industrial Park,Fumin Community,Fucheng Street,Longhua District,Shenzhen City,P.R.China + Shenzhen guangdong 518000 + CN + +40-0E-B9 (hex) HUAWEI TECHNOLOGIES CO.,LTD +400EB9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +08-DD-82 (hex) HUAWEI TECHNOLOGIES CO.,LTD +08DD82 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +1C-BA-B8 (hex) vivo Mobile Communication Co., Ltd. +1CBAB8 (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 + CN + +6C-93-13 (hex) Mellanox Technologies, Inc. +6C9313 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +B0-9C-B2 (hex) Google, Inc. +B09CB2 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US + +68-08-0D (hex) Shenzhen Yingsheng Technology Co., LTD +68080D (base 16) Shenzhen Yingsheng Technology Co., LTD + Room 2701, Aerospace Innovation Building, No.7013 Liuxian Avenue, Nanshan District, Shenzhen + Shenzhen Guangdong 518000 + CN + +E0-6C-A6 (hex) Creotech Quantum SA +E06CA6 (base 16) Creotech Quantum SA + Migdalowa 4 + Warsaw Mazovia 02-796 + PL + +78-52-49 (hex) Loxone Electronics GmbH +785249 (base 16) Loxone Electronics GmbH + Smart Home 1 + Kollerschlag 4154 + AT + +9C-09-CA (hex) Huawei Device Co., Ltd. +9C09CA (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +5C-85-05 (hex) Huawei Device Co., Ltd. +5C8505 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +BC-A6-E7 (hex) Sichuan Odot Automation System Co., Ltd. +BCA6E7 (base 16) Sichuan Odot Automation System Co., Ltd. + Plant Building No. 204, Mianyang High-tech Zone,No.261 Eastern Section of Feiyun Avenue,Mianyang city Sichuan Province,China. + Mian Yang 621000 + CN + +2C-E6-4D (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +2CE64D (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 + CN + +1C-D2-1E (hex) Juniper Networks +1CD21E (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 + US + +DC-33-0E (hex) Qingdao Haier Technology Co.Ltd +DC330E (base 16) Qingdao Haier Technology Co.Ltd + Building C01,Haier Information Park,No.1 Haier Road + Qingdao 266101 + CN + +44-CC-6E (hex) Rockwell Automation +44CC6E (base 16) Rockwell Automation + 1 Allen-Bradley Dr. + Mayfield Heights OH 44124-6118 + US + +6C-55-F6 (hex) eero inc. +6C55F6 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +2C-06-13 (hex) China Mobile Group Device Co.,Ltd. +2C0613 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +64-18-DF (hex) Sagemcom Broadband SAS +6418DF (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +D0-6E-DE (hex) Sagemcom Broadband SAS +D06EDE (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +64-7B-1E (hex) Sagemcom Broadband SAS +647B1E (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +A8-29-DC (hex) TP-Link Systems Inc. +A829DC (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +28-91-04 (hex) TP-Link Systems Inc. +289104 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +F0-4D-D4 (hex) Sagemcom Broadband SAS +F04DD4 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +F4-16-E7 (hex) Skyverse Limited +F416E7 (base 16) Skyverse Limited + Room 101、102 No.1301-14,Guanguang Road,Xinlan community,Guanlan street,Longhua district, Shenzhen,PRC + Shenzhen Guangdong 518100 + CN + +B4-9A-7D (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +B49A7D (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +58-0F-A5 (hex) Apple, Inc. +580FA5 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -88-70-15 (hex) Apple, Inc. -887015 (base 16) Apple, Inc. +40-B5-70 (hex) Hangzhou Hikvision Digital Technology Co.,Ltd. +40B570 (base 16) Hangzhou Hikvision Digital Technology Co.,Ltd. + No.555 Qianmo Road + Hangzhou Zhejiang 310052 + CN + +C8-D7-78 (hex) BSH Hausgeräte GmbH +C8D778 (base 16) BSH Hausgeräte GmbH + Im Gewerbepark B10 + Regensburg 93059 + DE + +48-0A-28 (hex) Apple, Inc. +480A28 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +BC-89-C1 (hex) Apple, Inc. +BC89C1 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +70-62-CB (hex) Apple, Inc. +7062CB (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +54-73-70 (hex) The LEGO Group +547370 (base 16) The LEGO Group + Åstvej 17190 BillundDenmark + Billund Jylland 7190 + DK + +08-7B-0F (hex) Amazon Technologies Inc. +087B0F (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +A0-4E-8D (hex) Cisco Meraki +A04E8D (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + +44-B4-A0 (hex) Mellanox Technologies, Inc. +44B4A0 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +64-7D-4D (hex) Arcadyan Corporation +647D4D (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW + +B8-0E-1D (hex) PAX Computer Technology(Shenzhen) Ltd. +B80E1D (base 16) PAX Computer Technology(Shenzhen) Ltd. + Room 701, PAX Technology Building, Shanxia Community, Pinghu Sub-district, Longgang District, Shenzhen, China + shenzhen GuangDong 518057 + CN + +04-5A-3D (hex) Nothing Technology Limited +045A3D (base 16) Nothing Technology Limited + 11 Staple Inn + London London WC1V 7QH + GB + +54-F2-83 (hex) Silicon Laboratories +54F283 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +44-8A-7F (hex) Murata Manufacturing Co., Ltd. +448A7F (base 16) Murata Manufacturing Co., Ltd. + 1-10-1, Higashikotari + Nagaokakyo-shi Kyoto 617-8555 + JP + +E8-D5-A1 (hex) Earda Technologies co Ltd +E8D5A1 (base 16) Earda Technologies co Ltd + Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District + Guangzhou Guangdong 511455 + CN + +28-BA-B9 (hex) ACME LIGHTING +28BAB9 (base 16) ACME LIGHTING + No.898,Gaoming Avenue East,Hecheng Blvd,Gaoming District,Foshan City, Guangdong Province China. + FOSHAN GUANGDONG 528511 + CN + +C0-FD-6F (hex) Sagemcom Broadband SAS +C0FD6F (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +DC-2D-DE (hex) Illucere Srl +DC2DDE (base 16) Illucere Srl + Via Primo Maggio 1 + Mestrino PD 35035 + IT + +C0-CA-A2 (hex) ESSYS +C0CAA2 (base 16) ESSYS + gaetbeol-ro + Incheon 21999 + KR + +B0-1F-F4 (hex) Sagemcom Broadband SAS +B01FF4 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +D4-27-FF (hex) Sagemcom Broadband SAS +D427FF (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +44-15-24 (hex) Sagemcom Broadband SAS +441524 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +F4-05-95 (hex) Sagemcom Broadband SAS +F40595 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +0C-AC-8A (hex) Sagemcom Broadband SAS +0CAC8A (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +6C-FF-CE (hex) Sagemcom Broadband SAS +6CFFCE (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +44-AD-B1 (hex) Sagemcom Broadband SAS +44ADB1 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +6C-99-61 (hex) Sagemcom Broadband SAS +6C9961 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +48-D2-4F (hex) Sagemcom Broadband SAS +48D24F (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +98-1E-19 (hex) Sagemcom Broadband SAS +981E19 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +A8-9A-93 (hex) Sagemcom Broadband SAS +A89A93 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +28-9E-FC (hex) Sagemcom Broadband SAS +289EFC (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +3C-17-10 (hex) Sagemcom Broadband SAS +3C1710 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +D4-50-39 (hex) Sagemcom Broadband SAS +D45039 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +84-3E-03 (hex) Sagemcom Broadband SAS +843E03 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +44-05-3F (hex) Sagemcom Broadband SAS +44053F (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +00-26-91 (hex) Sagemcom Broadband SAS +002691 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +98-8B-5D (hex) Sagemcom Broadband SAS +988B5D (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +90-01-3B (hex) Sagemcom Broadband SAS +90013B (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +7C-03-4C (hex) Sagemcom Broadband SAS +7C034C (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +6C-2E-85 (hex) Sagemcom Broadband SAS +6C2E85 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +94-FE-F4 (hex) Sagemcom Broadband SAS +94FEF4 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +40-C7-29 (hex) Sagemcom Broadband SAS +40C729 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +48-83-C7 (hex) Sagemcom Broadband SAS +4883C7 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +00-60-4C (hex) Sagemcom Broadband SAS +00604C (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +00-1F-95 (hex) Sagemcom Broadband SAS +001F95 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +00-23-48 (hex) Sagemcom Broadband SAS +002348 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +B8-7B-4D (hex) Espressif Inc. +B87B4D (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +D4-05-92 (hex) Espressif Inc. +D40592 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +08-AD-0A (hex) Espressif Inc. +08AD0A (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +A4-7B-9D (hex) Espressif Inc. +A47B9D (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +84-F3-EB (hex) Espressif Inc. +84F3EB (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +84-0D-8E (hex) Espressif Inc. +840D8E (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +F0-16-1D (hex) Espressif Inc. +F0161D (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +38-44-BE (hex) Espressif Inc. +3844BE (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +30-76-F5 (hex) Espressif Inc. +3076F5 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +3C-0F-02 (hex) Espressif Inc. +3C0F02 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +28-05-A5 (hex) Espressif Inc. +2805A5 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +14-63-93 (hex) Espressif Inc. +146393 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +8C-10-D4 (hex) Sagemcom Broadband SAS +8C10D4 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +18-FE-34 (hex) Espressif Inc. +18FE34 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +08-B6-1F (hex) Espressif Inc. +08B61F (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +E0-5A-1B (hex) Espressif Inc. +E05A1B (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +EC-DA-3B (hex) Espressif Inc. +ECDA3B (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +84-FC-E6 (hex) Espressif Inc. +84FCE6 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +B0-A7-32 (hex) Espressif Inc. +B0A732 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +B0-B2-1C (hex) Espressif Inc. +B0B21C (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +30-30-F9 (hex) Espressif Inc. +3030F9 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +7C-73-98 (hex) Espressif Inc. +7C7398 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +6C-B4-56 (hex) Espressif Inc. +6CB456 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +24-58-7C (hex) Espressif Inc. +24587C (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +CC-8D-A2 (hex) Espressif Inc. +CC8DA2 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +40-91-51 (hex) Espressif Inc. +409151 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +E8-9F-6D (hex) Espressif Inc. +E89F6D (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +48-55-19 (hex) Espressif Inc. +485519 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +70-B8-F6 (hex) Espressif Inc. +70B8F6 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +0C-B8-15 (hex) Espressif Inc. +0CB815 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +A0-DD-6C (hex) Espressif Inc. +A0DD6C (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +D0-EF-76 (hex) Espressif Inc. +D0EF76 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +C4-D8-D5 (hex) Espressif Inc. +C4D8D5 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +24-EC-4A (hex) Espressif Inc. +24EC4A (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +48-3F-DA (hex) Espressif Inc. +483FDA (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +F0-08-D1 (hex) Espressif Inc. +F008D1 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +A0-B7-65 (hex) Espressif Inc. +A0B765 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +CC-DB-A7 (hex) Espressif Inc. +CCDBA7 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +90-15-06 (hex) Espressif Inc. +901506 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +CC-BA-97 (hex) Espressif Inc. +CCBA97 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +7C-9E-BD (hex) Espressif Inc. +7C9EBD (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +30-83-98 (hex) Espressif Inc. +308398 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +0C-4E-A0 (hex) Espressif Inc. +0C4EA0 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +84-1F-E8 (hex) Espressif Inc. +841FE8 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +B0-3F-D3 (hex) Espressif Inc. +B03FD3 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +04-83-08 (hex) Espressif Inc. +048308 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +EC-E3-34 (hex) Espressif Inc. +ECE334 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +10-51-DB (hex) Espressif Inc. +1051DB (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +3C-22-7F (hex) Quectel Wireless Solutions Co.,Ltd. +3C227F (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 + +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 + +2C-2B-86 (hex) Sagemcom Broadband SAS +2C2B86 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +34-38-39 (hex) NEC Platforms, Ltd. +343839 (base 16) NEC Platforms, Ltd. + 14-1, Shiba 4-chome + Minato-ku Tokyo 108-8556 + JP + +C0-50-97 (hex) EG4 Electronics +C05097 (base 16) EG4 Electronics + 1030 Como St + Sulphur Springs TX 75482 + US + +28-B3-AF (hex) Qualynxus Inc +28B3AF (base 16) Qualynxus Inc + 10080 N Wolfe Road Suite SW3273 + Cupertino 95014 + US + +DC-29-19 (hex) AltoBeam(Xiamen)Technology Co., Ltd. +DC2919 (base 16) AltoBeam(Xiamen)Technology Co., Ltd. + South Building 203-38,Huoju Square ,No.56-58,Huoju Road, Huoju Park, Huoju High-tech District + Xiamen 361000 + CN + +84-7A-B6 (hex) AltoBeam Inc. +847AB6 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN + +F8-62-2A (hex) eero inc. +F8622A (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +88-C2-2D (hex) BUFFALO.INC +88C22D (base 16) BUFFALO.INC + AKAMONDORI Bld.,30-20,Ohsu 3-chome,Naka-ku + Nagoya Aichi Pref. 460-8315 + JP + +4C-E4-B6 (hex) Motorola Mobility LLC, a Lenovo Company +4CE4B6 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +20-F4-1B (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +20F41B (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO 268, Fuqian Rd,Jutang Community,Guanlan town, + ShenZhen Guangdong 518110 + CN + +50-40-35 (hex) Vantiva USA LLC +504035 (base 16) Vantiva USA LLC + 4855 Peachtree Industrial Blvd, Suite 200 + Norcross GA 30902 + US + +80-AE-54 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +80AE54 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +78-60-5B (hex) TP-LINK TECHNOLOGIES CO.,LTD. +78605B (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +04-F9-F8 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +04F9F8 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +48-0E-EC (hex) TP-LINK TECHNOLOGIES CO.,LTD. +480EEC (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +50-3E-AA (hex) TP-LINK TECHNOLOGIES CO.,LTD. +503EAA (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +F4-6D-2F (hex) TP-LINK TECHNOLOGIES CO.,LTD. +F46D2F (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +18-F2-2C (hex) TP-LINK TECHNOLOGIES CO.,LTD. +18F22C (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +98-97-CC (hex) TP-LINK TECHNOLOGIES CO.,LTD. +9897CC (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +18-A6-F7 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +18A6F7 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +B0-95-75 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +B09575 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +10-30-89 (hex) Resideo +103089 (base 16) Resideo + 2 Corporate Center Dr. + Melville NY 11747 + US + +20-52-1D (hex) Apple, Inc. +20521D (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US +F0-40-D9 (hex) Apple, Inc. +F040D9 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +FC-0F-76 (hex) Amazon Technologies Inc. +FC0F76 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +AC-E4-D8 (hex) Xiaomi Communications Co Ltd +ACE4D8 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +6C-72-B8 (hex) LCFC(Hefei) Electronics Technology co., ltd +6C72B8 (base 16) LCFC(Hefei) Electronics Technology co., ltd + No. 3188-1 Yungu Road (Comprehensive Bonded Zone), Hefei Economic & Technological Development Area,Anhui + HEFEI ANHUI 230601 + CN + C8-5C-E2 (hex) IEEE Registration Authority C85CE2 (base 16) IEEE Registration Authority 445 Hoes Lane @@ -187004,12 +190073,6 @@ E822B8 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd Shenzhen Guangdong 518057 CN -74-4D-BD (hex) Espressif Inc. -744DBD (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 00-F9-52 (hex) HUAWEI TECHNOLOGIES CO.,LTD 00F952 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -187148,12 +190211,6 @@ CCECB7 (base 16) ShenZhen Linked-Z Intelligent Display Co., Ltd Shenzhen Guangdong 518128 CN -DC-DA-0C (hex) Espressif Inc. -DCDA0C (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - A8-B8-E0 (hex) Changwang Technology inc. A8B8E0 (base 16) Changwang Technology inc. No.37,Qinghutou,Renmin Road, Tangxia Town @@ -187241,12 +190298,6 @@ B01886 (base 16) SmarDTV Corporation ZI Athelia IV La Ciotat 13600 CH -A4-22-49 (hex) Sagemcom Broadband SAS -A42249 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - C8-A6-EF (hex) Samsung Electronics Co.,Ltd C8A6EF (base 16) Samsung Electronics Co.,Ltd 129, Samsung-ro, Youngtongl-Gu @@ -187646,18 +190697,6 @@ AC3B96 (base 16) NXP Semiconductor (Tianjin) LTD. Suzhou 215021 CN -48-5F-08 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -485F08 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - -F8-6F-B0 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -F86FB0 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 90-F8-91 (hex) Kaon Group Co., Ltd. 90F891 (base 16) Kaon Group Co., Ltd. 884-3, Seongnam-daero, Bundang-gu @@ -187862,12 +190901,6 @@ D8D45D (base 16) Orbic North America PALO ALTO CA 94304 US -20-B8-2B (hex) Sagemcom Broadband SAS -20B82B (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 14-18-44 (hex) Xenon Smart Teknoloji Ltd. 141844 (base 16) Xenon Smart Teknoloji Ltd. Tatlisu Mh. Akdag Cd. No:3-5 @@ -188630,12 +191663,6 @@ F492BF (base 16) Ubiquiti Inc Sunnyvale CA 94089 US -64-B7-08 (hex) Espressif Inc. -64B708 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - E8-A2-45 (hex) Juniper Networks E8A245 (base 16) Juniper Networks 1133 Innovation Way @@ -188894,18 +191921,6 @@ F4B52F (base 16) Juniper Networks Beijing Beijing 100085 CN -B8-6A-F1 (hex) Sagemcom Broadband SAS -B86AF1 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -CC-58-30 (hex) Sagemcom Broadband SAS -CC5830 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 44-DF-65 (hex) Beijing Xiaomi Mobile Software Co., Ltd 44DF65 (base 16) Beijing Xiaomi Mobile Software Co., Ltd The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District @@ -189314,12 +192329,6 @@ F4E8C7 (base 16) Apple, Inc. Reno NV 89507 US -54-47-CC (hex) Sagemcom Broadband SAS -5447CC (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - A0-A7-63 (hex) Polytron Vertrieb GmbH A0A763 (base 16) Polytron Vertrieb GmbH Langwiesenweg 64-71 @@ -189416,12 +192425,6 @@ FCD5D9 (base 16) Shenzhen SDMC Technology CO.,Ltd. Shanghai 200233 CN -3C-58-5D (hex) Sagemcom Broadband SAS -3C585D (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 38-95-92 (hex) Tendyron Corporation 389592 (base 16) Tendyron Corporation 1810,Tower B,Jin-ma,Building,17 East Qing Hua Road @@ -189608,12 +192611,6 @@ D4F0C9 (base 16) KYOCERA Document Solutions Inc. osaka Japan 540-8585 JP -64-E8-33 (hex) Espressif Inc. -64E833 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 24-B7-DA (hex) Fiberhome Telecommunication Technologies Co.,LTD 24B7DA (base 16) Fiberhome Telecommunication Technologies Co.,LTD No.5 DongXin Road @@ -189968,12 +192965,6 @@ F87907 (base 16) Huawei Device Co., Ltd. Dongguan Guangdong 523808 CN -7C-E8-7F (hex) Sagemcom Broadband SAS -7CE87F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 68-87-1C (hex) Motorola Mobility LLC, a Lenovo Company 68871C (base 16) Motorola Mobility LLC, a Lenovo Company 222 West Merchandise Mart Plaza @@ -190262,12 +193253,6 @@ DC0539 (base 16) Cisco Systems, Inc San Jose CA 94568 US -48-27-E2 (hex) Espressif Inc. -4827E2 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 38-86-F7 (hex) Google, Inc. 3886F7 (base 16) Google, Inc. 1600 Amphitheatre Parkway @@ -190616,18 +193601,6 @@ F06C5D (base 16) Xiaomi Communications Co Ltd San Francisco 94158 US -C8-F0-9E (hex) Espressif Inc. -C8F09E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -DC-54-75 (hex) Espressif Inc. -DC5475 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 00-14-01 (hex) Rivertree Networks Corp. 001401 (base 16) Rivertree Networks Corp. R#304, K-Center, 1591-9 @@ -190682,12 +193655,6 @@ DC5475 (base 16) Espressif Inc. Shenzhen City Guangdong Province 518100 CN -EC-62-60 (hex) Espressif Inc. -EC6260 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - B0-6E-72 (hex) Realme Chongqing Mobile Telecommunications Corp.,Ltd. B06E72 (base 16) Realme Chongqing Mobile Telecommunications Corp.,Ltd. No.178 Yulong Avenue, Yufengshan, Yubei District, Chongqing. @@ -190892,12 +193859,6 @@ AC330B (base 16) Japan Computer Vision Corp. Changsha HUNAN 410329 CN -EC-60-73 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -EC6073 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 74-DD-CB (hex) China Leadshine Technology Co.,Ltd 74DDCB (base 16) China Leadshine Technology Co.,Ltd 9-11, Building A3, Nanshan Ipark, No.1001 Xueyuan Avenue, Nanshan? @@ -191180,12 +194141,6 @@ BC2247 (base 16) New H3C Technologies Co., Ltd Shenzhen Guangdong 518000 CN -54-9B-49 (hex) NEC Platforms, Ltd. -549B49 (base 16) NEC Platforms, Ltd. - 2-3 Kandatsukasamachi - Chiyodaku Tokyo 101-8532 - JP - 7C-77-16 (hex) Zyxel Communications Corporation 7C7716 (base 16) Zyxel Communications Corporation No. 6 Innovation Road II, Science Park @@ -191480,12 +194435,6 @@ EC656E (base 16) The Things Industries B.V. Frankfurt am Main Hessen 60322 DE -78-21-84 (hex) Espressif Inc. -782184 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 34-13-43 (hex) GE Lighting 341343 (base 16) GE Lighting 1975 Noble Rd @@ -191528,12 +194477,6 @@ E07E5F (base 16) Renesas Electronics (Penang) Sdn. Bhd. Bayan Lepas Penang 11900 MY -38-BE-AB (hex) AltoBeam (China) Inc. -38BEAB (base 16) AltoBeam (China) Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN - 04-79-75 (hex) Honor Device Co., Ltd. 047975 (base 16) Honor Device Co., Ltd. A1701, Block AB, Building 1, Tianan Yungu Phase I, Gangtou Community, Bantian Street @@ -191876,12 +194819,6 @@ B45F84 (base 16) zte corporation Piscataway NJ 08554 US -10-97-BD (hex) Espressif Inc. -1097BD (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - B0-1C-0C (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD B01C0C (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD Unit East Block22-24/F,Skyworth semiconductor design Bldg., Gaoxin Ave.4.S.,Nanshan District,Shenzhen,China @@ -191912,12 +194849,6 @@ DC0E96 (base 16) Palo Alto Networks Santa Clara CA 95054 US -30-C6-F7 (hex) Espressif Inc. -30C6F7 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 94-02-30 (hex) Logitech 940230 (base 16) Logitech 7700 Gateway Blvd @@ -191990,12 +194921,6 @@ FCA84A (base 16) Sentinum GmbH New Taipei City Taiwan 231 TW -A4-1A-3A (hex) TP-LINK TECHNOLOGIES CO.,LTD. -A41A3A (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 34-84-E4 (hex) Texas Instruments 3484E4 (base 16) Texas Instruments 12500 TI Blvd @@ -192212,18 +195137,6 @@ FC1D2A (base 16) vivo Mobile Communication Co., Ltd. Kanata Ottawa K2K 2V6 CA -24-D7-EB (hex) Espressif Inc. -24D7EB (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -58-BF-25 (hex) Espressif Inc. -58BF25 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 80-D2-E5 (hex) Nintendo Co.,Ltd 80D2E5 (base 16) Nintendo Co.,Ltd 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU @@ -192452,12 +195365,6 @@ A01842 (base 16) Comtrend Corporation New Taipei City, Taiwan 24159 TW -50-6F-0C (hex) Sagemcom Broadband SAS -506F0C (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - D4-86-60 (hex) Arcadyan Corporation D48660 (base 16) Arcadyan Corporation No.8, Sec.2, Guangfu Rd. @@ -192524,12 +195431,6 @@ D86D17 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -7C-87-CE (hex) Espressif Inc. -7C87CE (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 8C-81-72 (hex) Sichuan Tianyi Comheart Telecom Co.,LTD 8C8172 (base 16) Sichuan Tianyi Comheart Telecom Co.,LTD No.198,First Section,Snow Mountain Avenue, Jinyuan Town, Dayi County @@ -192542,12 +195443,6 @@ D86D17 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Reno NV 89507 US -74-5D-43 (hex) BSH Hausgeraete GmbH -745D43 (base 16) BSH Hausgeraete GmbH - Im Gewerbepark B10 - Regensburg 93059 - DE - 00-0B-A5 (hex) Quasar Cipta Mandiri, PT 000BA5 (base 16) Quasar Cipta Mandiri, PT Jl. Palasari 9A @@ -193088,12 +195983,6 @@ A45802 (base 16) SHIN-IL TECH Shenzhen Guangdong 518000 CN -94-3C-C6 (hex) Espressif Inc. -943CC6 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - A4-DA-D4 (hex) Yamato Denki Co.,Ltd. A4DAD4 (base 16) Yamato Denki Co.,Ltd. 3-2-14,Koyama @@ -193262,12 +196151,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 @@ -193652,12 +196535,6 @@ BCF45F (base 16) zte corporation shenzhen guangdong 518057 CN -60-55-F9 (hex) Espressif Inc. -6055F9 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 54-8A-BA (hex) Cisco Systems, Inc 548ABA (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -194216,12 +197093,6 @@ A0D0DC (base 16) Amazon Technologies Inc. Charlotte NC 28273 US -20-9A-7D (hex) Sagemcom Broadband SAS -209A7D (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 50-C6-8E (hex) Biwin Semiconductor (HK) Company Limted 50C68E (base 16) Biwin Semiconductor (HK) Company Limted 5th/F., Block 4, Tongfuyu Industrial Park, Tanglang, Xili, Nanshan @@ -194558,12 +197429,6 @@ AC9572 (base 16) Jovision Technology Co., Ltd. Seongnam-si 13595 KR -A8-03-2A (hex) Espressif Inc. -A8032A (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 08-38-69 (hex) Hong Kong AMobile Intelligent Corp. Limited Taiwan Branch 083869 (base 16) Hong Kong AMobile Intelligent Corp. Limited Taiwan Branch 8F.-1, No.700, Zhongzheng Rd., Zhonghe Dist. @@ -194618,12 +197483,6 @@ EC753E (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -24-A1-60 (hex) Espressif Inc. -24A160 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - DC-AE-EB (hex) Ruckus Wireless DCAEEB (base 16) Ruckus Wireless 350 West Java Drive @@ -194642,18 +197501,6 @@ ECDB86 (base 16) API-K Saint Vincent de Mercuze Isère 38660 FR -3C-84-6A (hex) TP-LINK TECHNOLOGIES CO.,LTD. -3C846A (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - -84-D8-1B (hex) TP-LINK TECHNOLOGIES CO.,LTD. -84D81B (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 7C-2A-DB (hex) Xiaomi Communications Co Ltd 7C2ADB (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road @@ -195050,12 +197897,6 @@ C80739 (base 16) NAKAYO Inc Santa Clara CA 95054 US -A8-6A-BB (hex) Sagemcom Broadband SAS -A86ABB (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 24-43-E2 (hex) DASAN Network Solutions 2443E2 (base 16) DASAN Network Solutions DASAN Tower 8F, 49 Daewangpangyo-ro644beon-gil Bundang-gu @@ -195272,12 +198113,6 @@ BCFF21 (base 16) Smart Code(shenzhen)Technology Co.,Ltd Dallas TX 75243 US -F0-81-75 (hex) Sagemcom Broadband SAS -F08175 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 1C-63-BF (hex) SHENZHEN BROADTEL TELECOM CO.,LTD 1C63BF (base 16) SHENZHEN BROADTEL TELECOM CO.,LTD No.14-1, Tongqing Road, Baolong street, Longgang District @@ -195404,12 +198239,6 @@ D84C90 (base 16) Apple, Inc. Shenzhen Shenzhen 518101 CN -D8-47-32 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -D84732 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - A0-43-B0 (hex) Hangzhou BroadLink Technology Co.,Ltd A043B0 (base 16) Hangzhou BroadLink Technology Co.,Ltd Room 101,1/F,Unit C,Building 1,No.57 Jiang'er Road,Changhe Street,Binjiang District,Hangzhou,Zhejiang,P.R.China @@ -195992,12 +198821,6 @@ A0946A (base 16) Shenzhen XGTEC Technology Co,.Ltd. Brentwood Essex 08854 GB -5C-B1-3E (hex) Sagemcom Broadband SAS -5CB13E (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - E4-AA-EA (hex) Liteon Technology Corporation E4AAEA (base 16) Liteon Technology Corporation 4F, 90, Chien 1 Road @@ -196604,12 +199427,6 @@ C4C603 (base 16) Cisco Systems, Inc San Jose CA 94568 US -7C-B5-9B (hex) TP-LINK TECHNOLOGIES CO.,LTD. -7CB59B (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 2C-4F-52 (hex) Cisco Systems, Inc 2C4F52 (base 16) Cisco Systems, Inc 80 West Tasman Drive @@ -196682,12 +199499,6 @@ C4F7D5 (base 16) Cisco Systems, Inc New Taipei City, Taiwan 24159 TW -D0-57-94 (hex) Sagemcom Broadband SAS -D05794 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 04-D9-F5 (hex) ASUSTek COMPUTER INC. 04D9F5 (base 16) ASUSTek COMPUTER INC. 15,Li-Te Rd., Peitou, Taipei 112, Taiwan @@ -197270,12 +200081,6 @@ D45800 (base 16) Fiberhome Telecommunication Technologies Co.,LTD Basking Ridge 07030 US -34-6B-46 (hex) Sagemcom Broadband SAS -346B46 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 84-85-E6 (hex) Guangdong Asano Technology CO.,Ltd. 8485E6 (base 16) Guangdong Asano Technology CO.,Ltd. Changsheng Road, Songxia Industrial Park, Songgang, Shishan Town, Nanhai @@ -198416,12 +201221,6 @@ A0D635 (base 16) WBS Technology Silverwater New South Wales 2128 AU -3C-71-BF (hex) Espressif Inc. -3C71BF (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 90-2B-D2 (hex) HUAWEI TECHNOLOGIES CO.,LTD 902BD2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -199178,12 +201977,6 @@ F4844C (base 16) Texas Instruments NUremberg 90482 DE -A0-39-EE (hex) Sagemcom Broadband SAS -A039EE (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 68-98-61 (hex) Beacon Inc 689861 (base 16) Beacon Inc 82-1, Anyangcheondong-ro, Dongan-gu @@ -199532,12 +202325,6 @@ A4B52E (base 16) Integrated Device Technology (Malaysia) Sdn. Bhd. Suwon Gyeonggi-Do 16677 KR -DC-4F-22 (hex) Espressif Inc. -DC4F22 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - F8-6C-E1 (hex) Taicang T&W Electronics F86CE1 (base 16) Taicang T&W Electronics 89# Jiang Nan RD @@ -199673,12 +202460,6 @@ B8C111 (base 16) Apple, Inc. Suwon Gyeonggi-Do 16677 KR -EC-FA-BC (hex) Espressif Inc. -ECFABC (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - CC-2D-E0 (hex) Routerboard.com CC2DE0 (base 16) Routerboard.com Mikrotikls SIA @@ -199979,12 +202760,6 @@ EC0441 (base 16) ShenZhen TIGO Semiconductor Co., Ltd. shenzhen China / Guangdong 518048 CN -B8-EE-0E (hex) Sagemcom Broadband SAS -B8EE0E (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - C0-A5-3E (hex) Apple, Inc. C0A53E (base 16) Apple, Inc. 1 Infinite Loop @@ -200465,12 +203240,6 @@ AC4E2E (base 16) Shenzhen JingHanDa Electronics Co.Ltd Seoul Seoul 03920 KR -2C-3A-E8 (hex) Espressif Inc. -2C3AE8 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 88-BD-78 (hex) Flaircomm Microelectronics,Inc. 88BD78 (base 16) Flaircomm Microelectronics,Inc. 7F, Guomai Building, Guomai Science and Technology Park, 116 East JiangBin Road, @@ -200837,12 +203606,6 @@ E89FEC (base 16) CHENGDU KT ELECTRONIC HI-TECH CO.,LTD Singapore Singapore 609917 SG -F8-AB-05 (hex) Sagemcom Broadband SAS -F8AB05 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 50-3A-7D (hex) AlphaTech PLC Int’l Co., Ltd. 503A7D (base 16) AlphaTech PLC Int’l Co., Ltd. 13F., No.618, Sec. 7, New Taipei Blvd., Xinzhuang Dist., @@ -201287,12 +204050,6 @@ BC9FEF (base 16) Apple, Inc. Taipei 221 TW -A4-08-F5 (hex) Sagemcom Broadband SAS -A408F5 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 00-B0-91 (hex) Transmeta Corp. 00B091 (base 16) Transmeta Corp. 3940 Freedom Circle @@ -201530,12 +204287,6 @@ E49E12 (base 16) FREEBOX SAS Hsinchu 30077 TW -24-20-C7 (hex) Sagemcom Broadband SAS -2420C7 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 70-1C-E7 (hex) Intel Corporate 701CE7 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -202082,12 +204833,6 @@ C4731E (base 16) Samsung Electronics Co.,Ltd San Jose CA 94568 US -44-E9-DD (hex) Sagemcom Broadband SAS -44E9DD (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - RUEIL MALMAISON CEDEX Hauts de Seine 92848 - FR - 00-0F-5E (hex) Veo 000F5E (base 16) Veo 910 Rincon Circle @@ -202448,12 +205193,6 @@ C88D83 (base 16) HUAWEI TECHNOLOGIES CO.,LTD Dongguan 523808 CN -B0-B2-8F (hex) Sagemcom Broadband SAS -B0B28F (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 44-14-41 (hex) AudioControl Inc. 441441 (base 16) AudioControl Inc. 22410 70Th Ave West, STE 1 @@ -204260,30 +206999,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 @@ -204926,24 +207641,6 @@ B8FFFE (base 16) Texas Instruments Seoul 153-801 KR -F4-EB-38 (hex) Sagemcom Broadband SAS -F4EB38 (base 16) Sagemcom Broadband SAS - 15 Avenue Ambroise Croizat - DOMERAT Allier 03410 - FR - -00-1B-BF (hex) Sagemcom Broadband SAS -001BBF (base 16) Sagemcom Broadband SAS - 14 Rue Paul Dautier - Vélizy 78457 - FR - -00-25-69 (hex) Sagemcom Broadband SAS -002569 (base 16) Sagemcom Broadband SAS - Le Ponnant de Paris - CEDEX Paris 75512 - FR - C8-A0-30 (hex) Texas Instruments C8A030 (base 16) Texas Instruments 12500 TI Boulevard, MS 8723 @@ -204962,36 +207659,12 @@ C8A030 (base 16) Texas Instruments Nürnberg Bavaria 90409 DE -00-78-9E (hex) Sagemcom Broadband SAS -00789E (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison Cedex Hauts de Seine 92848 - FR - -E8-BE-81 (hex) Sagemcom Broadband SAS -E8BE81 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - Rueil Malmaison Cedex Hauts de Seine 92848 - FR - -68-15-90 (hex) Sagemcom Broadband SAS -681590 (base 16) Sagemcom Broadband SAS - 250 ROUTE DE L'EMPEREUR - RUEIL MALMAISON CEDEX Choisissez l'état / la province 92848 - FR - 10-F6-81 (hex) vivo Mobile Communication Co., Ltd. 10F681 (base 16) vivo Mobile Communication Co., Ltd. #283,BBK Road Wusha,Chang'An DongGuan City,Guangdong, 523860 CN -2C-E4-12 (hex) Sagemcom Broadband SAS -2CE412 (base 16) Sagemcom Broadband SAS - 250 route de l'Empereur - RUEIL MALMAISON CEDEX Hauts de Seine 92848 - FR - 04-E4-51 (hex) Texas Instruments 04E451 (base 16) Texas Instruments 12500 TI Boulevard, MS 8723 @@ -206912,12 +209585,6 @@ D00F6D (base 16) T&W Electronics Company Gumi Gyeongbuk 730-350 KR -C0-25-A2 (hex) NEC Platforms, Ltd. -C025A2 (base 16) NEC Platforms, Ltd. - 2-3 Kandatsukasamachi - Chiyodaku Tokyo 101-8532 - JP - 90-8D-78 (hex) D-Link International 908D78 (base 16) D-Link International 1 Internal Business Park, #03-12,The Synergy @@ -223253,12 +225920,6 @@ E80B13 (base 16) Akib Systems Taiwan, INC Garden Grove CA 92841 US -00-90-1D (hex) PEC (NZ) LTD. -00901D (base 16) PEC (NZ) LTD. - 2 STATION ROAD - MARTON - NZ - 00-90-7E (hex) VETRONIX CORP. 00907E (base 16) VETRONIX CORP. 2030 ALAMEDE PADRE SERRA @@ -223469,12 +226130,6 @@ E80B13 (base 16) Akib Systems Taiwan, INC 64625 BENSHEIM DE -00-10-7F (hex) CRESTRON ELECTRONICS, INC. -00107F (base 16) CRESTRON ELECTRONICS, INC. - 15 Volvo Drive - Rockleigh NJ 07647 - US - 00-10-D4 (hex) STORAGE COMPUTER CORPORATION 0010D4 (base 16) STORAGE COMPUTER CORPORATION 11 RIVERSIDE STREET @@ -224345,12 +227000,6 @@ E80B13 (base 16) Akib Systems Taiwan, INC SINGAPORE 2572 SG -00-A0-E3 (hex) XKL SYSTEMS CORP. -00A0E3 (base 16) XKL SYSTEMS CORP. - 8420 154TH AVE. NE - REDMOND WA 98052 - US - 00-A0-14 (hex) CSIR 00A014 (base 16) CSIR P.O. BOX 395 @@ -224867,12 +227516,6 @@ E80B13 (base 16) Akib Systems Taiwan, INC ENGLAND GB -00-C0-96 (hex) TAMURA CORPORATION -00C096 (base 16) TAMURA CORPORATION - COMMUNICATION SYSTEMS DIV. - - JP - 00-C0-4E (hex) COMTROL CORPORATION 00C04E (base 16) COMTROL CORPORATION 2675 PATTON ROAD @@ -226241,12 +228884,6 @@ F4DD06 (base 16) Samsung Electronics Co.,Ltd Tokyo 100-8310 JP -30-C9-22 (hex) Espressif Inc. -30C922 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - C4-95-5F (hex) Anhui Saida Technology Limited Liability Company C4955F (base 16) Anhui Saida Technology Limited Liability Company 3rd Floor, Building A4, Phase I, Zhongan Chuanggu Science and Technology Park, No. 900, Wangjiang West Road, High-tech Zone @@ -226526,12 +229163,6 @@ B402F2 (base 16) Synaptics, Inc San Jose CA 95131-1709 US -A0-2D-DB (hex) Sagemcom Broadband SAS -A02DDB (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 74-4D-DC (hex) Sonim Technologies, Inc 744DDC (base 16) Sonim Technologies, Inc 4445 Eastgate Mall,Suite 200 @@ -226664,18 +229295,6 @@ E4BEED (base 16) Netis Technology Co., Ltd. Shenzhen 518057 CN -EC-64-C9 (hex) Espressif Inc. -EC64C9 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -F0-F5-BD (hex) Espressif Inc. -F0F5BD (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - F8-42-88 (hex) Apple, Inc. F84288 (base 16) Apple, Inc. 1 Infinite Loop @@ -226982,12 +229601,6 @@ D825DF (base 16) CAME UK Castle Donington West Midlands DE74 2US GB -D4-B5-CD (hex) Sagemcom Broadband SAS -D4B5CD (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 68-49-83 (hex) HUAWEI TECHNOLOGIES CO.,LTD 684983 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park @@ -227054,12 +229667,6 @@ E0DAD7 (base 16) zte corporation New Taipei City Taiwan 231 TW -18-8B-0E (hex) Espressif Inc. -188B0E (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 30-A3-B5 (hex) Jiangsu Best Tone Information Service Co., Ltd 30A3B5 (base 16) Jiangsu Best Tone Information Service Co., Ltd No.501,Zhongshan South Road @@ -227192,12 +229799,6 @@ AC0775 (base 16) Apple, Inc. Eldorado do Sul Rio Grande do Sul 92990-000 BR -24-5A-5F (hex) TP-LINK TECHNOLOGIES CO.,LTD. -245A5F (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN - 00-10-9C (hex) MG Co., Ltd. 00109C (base 16) MG Co., Ltd. 13th floor, Tradepia Yodoyabashi, 2-5-8 Imabashi, Chuo-ku @@ -227306,12 +229907,6 @@ C4BD8D (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD Shenzhen Guangdong 518000 CN -30-F6-00 (hex) Sagemcom Broadband SAS -30F600 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - 98-5F-41 (hex) Intel Corporate 985F41 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 @@ -227372,24 +229967,12 @@ CC8C17 (base 16) ITEL MOBILE LIMITED Hong Kong KOWLOON 999077 HK -08-A6-F7 (hex) Espressif Inc. -08A6F7 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 34-CF-B5 (hex) Robotic d.o.o. 34CFB5 (base 16) Robotic d.o.o. Srednjaci 10 Zagreb 10000 HR -4C-82-37 (hex) Telink Micro LLC -4C8237 (base 16) Telink Micro LLC - 2975 Scott Blvd #120 - Santa Clara 95054 - US - 84-9C-A4 (hex) Mimosa Networks 849CA4 (base 16) Mimosa Networks 3150 Coronado Dr @@ -227402,18 +229985,6 @@ CC8C17 (base 16) ITEL MOBILE LIMITED Shanghai 200233 CN -EC-C9-FF (hex) Espressif Inc. -ECC9FF (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -2C-BC-BB (hex) Espressif Inc. -2CBCBB (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 88-3E-0D (hex) HD Hyundai Electric 883E0D (base 16) HD Hyundai Electric 477, Bundangsuseo-ro, Bundang-gu @@ -227486,12 +230057,6 @@ F873DF (base 16) Apple, Inc. shenzhen guangdong 518057 CN -E4-B0-63 (hex) Espressif Inc. -E4B063 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - A8-C2-46 (hex) Gemtek Technology Co., Ltd. A8C246 (base 16) Gemtek Technology Co., Ltd. No.15-1 Zhonghua Road @@ -227504,12 +230069,6 @@ CC817D (base 16) Apple, Inc. Cupertino CA 95014 US -1C-69-20 (hex) Espressif Inc. -1C6920 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 20-F0-94 (hex) Google, Inc. 20F094 (base 16) Google, Inc. 1600 Amphitheatre Parkway @@ -227540,18 +230099,6 @@ BCF212 (base 16) Telink Micro LLC Santa Clara 95054 US -88-13-BF (hex) Espressif Inc. -8813BF (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - -34-5F-45 (hex) Espressif Inc. -345F45 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 2C-0B-97 (hex) Xiaomi Communications Co Ltd 2C0B97 (base 16) Xiaomi Communications Co Ltd #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road @@ -227996,12 +230543,6 @@ B8C007 (base 16) tickIoT Inc. Beijing 100029 CN -C0-5D-89 (hex) Espressif Inc. -C05D89 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 40-03-26 (hex) NXP Semiconductor (Tianjin) LTD. 400326 (base 16) NXP Semiconductor (Tianjin) LTD. No.15 Xinghua Avenue, Xiqing Economic Development Area @@ -228335,12 +230876,6 @@ F49EA4 (base 16) Epiq Solutions Muenster 48163 DE -D4-8C-49 (hex) Espressif Inc. -D48C49 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - BC-04-35 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. BC0435 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. Midea Global Innovation Center,Beijiao Town,Shunde @@ -228701,28 +231236,16 @@ BCB4FD (base 16) NXP Semiconductor (Tianjin) LTD. SHANGHAI 201114 CN -7C-51-84 (hex) Unis Flash Memory Technology(Chengdu)Co.,Ltd. -7C5184 (base 16) Unis Flash Memory Technology(Chengdu)Co.,Ltd. - No. 2110, 21st Floor, Unit 1, Building 1, No. 505, West Section of Fucheng Avenue, High Tech. Zone, Chengdu City, Sichuan Province, China. - Chengdu Sichuan 610000 - CN - E8-06-EB (hex) ShieldSOS LLC E806EB (base 16) ShieldSOS LLC 3700 Kyle Crossing, Suite 500 KYLE TX 78640 US - -78-20-2E (hex) Skychers Creations ShenZhen Limited -78202E (base 16) Skychers Creations ShenZhen Limited - Room 907A, 9/F, Block T2, FongDa City, Longjing Village, Longzhu Avenue, Nanshan District - Shenzhen Guangdong 518073 - CN - -DC-1E-D5 (hex) Espressif Inc. -DC1ED5 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 + +78-20-2E (hex) Skychers Creations ShenZhen Limited +78202E (base 16) Skychers Creations ShenZhen Limited + Room 907A, 9/F, Block T2, FongDa City, Longjing Village, Longzhu Avenue, Nanshan District + Shenzhen Guangdong 518073 CN 94-6C-04 (hex) EM Microelectronic @@ -228911,12 +231434,6 @@ CC54FE (base 16) Mimosa Networks Nanjing Jiangsu 211800 CN -30-ED-A0 (hex) Espressif Inc. -30EDA0 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 04-CD-C0 (hex) Mist Systems, Inc. 04CDC0 (base 16) Mist Systems, Inc. 1601 South De Anza Blvd, Suite 248 @@ -229571,12 +232088,6 @@ C051F3 (base 16) CIG SHANGHAI CO LTD SHANGHAI 201114 CN -B4-3A-45 (hex) Espressif Inc. -B43A45 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 14-EA-A1 (hex) Micronet union Technology (chengdu) co., Ltd 14EAA1 (base 16) Micronet union Technology (chengdu) co., Ltd No. 2068, Aviation Kechuang Avenue, High-tech Industrial Park, Xindu District, Chengdu @@ -230090,12 +232601,6 @@ A0AD9F (base 16) ASUSTek COMPUTER INC. Taipei Taiwan 112 TW -68-25-DD (hex) Espressif Inc. -6825DD (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN - 78-5F-6C (hex) Arista Networks 785F6C (base 16) Arista Networks 5453 Great America Parkway @@ -230156,12 +232661,6 @@ C0DCAB (base 16) LG Innotek Piscataway NJ 08554 US -C4-22-4E (hex) Telink Micro LLC -C4224E (base 16) Telink Micro LLC - 2975 Scott Blvd #120 - Santa Clara 95054 - US - 9C-53-85 (hex) PT. Hartono Istana Teknologi 9C5385 (base 16) PT. Hartono Istana Teknologi KHR Asnawi @@ -230426,2837 +232925,4190 @@ C4CCF9 (base 16) zte corporation shenzhen guangdong 518057 CN -20-20-51 (hex) zte corporation -202051 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +20-20-51 (hex) zte corporation +202051 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +78-52-37 (hex) zte corporation +785237 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +60-BD-83 (hex) HUAWEI TECHNOLOGIES CO.,LTD +60BD83 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +10-67-A3 (hex) HUAWEI TECHNOLOGIES CO.,LTD +1067A3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +E0-0E-CE (hex) Fiberhome Telecommunication Technologies Co.,LTD +E00ECE (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 + CN + +78-81-8C (hex) Nintendo Co.,Ltd +78818C (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + +E0-13-33 (hex) General Motors +E01333 (base 16) General Motors + 3300 General Motors Rd + Milford MI 48380 + US + +3C-32-B9 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +3C32B9 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +DC-73-06 (hex) Vantiva Connected Home - Home Networks +DC7306 (base 16) Vantiva Connected Home - Home Networks + 4855 Peachtree Industrial BlvdSuite 200 / RM-138 + Norcross GA 30092 + US + +7C-F1-7E (hex) TP-Link Systems Inc +7CF17E (base 16) TP-Link Systems Inc + 7 Temasek Boulevard #29-03 Suntec Tower One + Singapore 038987 + SG + +FC-4C-EF (hex) Huawei Device Co., Ltd. +FC4CEF (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +44-36-59 (hex) Robert Bosch GmbH +443659 (base 16) Robert Bosch GmbH + Robert-Bosch-Platz 1 + Gerlingen-Schillerhöhe 70839 + DE + +98-25-4A (hex) TP-Link Systems Inc +98254A (base 16) TP-Link Systems Inc + 5 Peters Canyon Rd Suit 300 + Irvine CA 92606 + SG + +D8-B2-93 (hex) TOPSSD +D8B293 (base 16) TOPSSD + Room101,Building#11,Xingong Industrial Park, No.100 Luyun Road, Yuelu District + Changsha Hunan 410000 + CN + +AC-C3-E5 (hex) Cisco Meraki +ACC3E5 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + +C8-AA-B2 (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD +C8AAB2 (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD + Unit East Block22-24/F,Skyworth semiconductor design Bldg., Gaoxin Ave.4.S.,Nanshan District,Shenzhen,China + SHENZHEN GUANGDONG 518057 + CN + +74-FE-CE (hex) TP-Link Systems Inc +74FECE (base 16) TP-Link Systems Inc + 5 Peters Canyon Rd Suit 300 + Irvine CA 92606 + US + +24-2F-D0 (hex) TP-Link Systems Inc +242FD0 (base 16) TP-Link Systems Inc + 5 Peters Canyon Rd Suit 300 + Irvine CA 92606 + US + +80-F3-EF (hex) Meta Platforms, Inc. +80F3EF (base 16) Meta Platforms, Inc. + 1601 Willow Rd + Menlo Park CA 94025 + US + +84-57-F7 (hex) Meta Platforms, Inc. +8457F7 (base 16) Meta Platforms, Inc. + 1601 Willow Rd + Menlo Park CA 94025 + US + +50-3A-0F (hex) ALL Winner (Hong Kong) Limited +503A0F (base 16) ALL Winner (Hong Kong) Limited + Unit No.1301,13F,Sunbeam Plaza,1155 Canton Road,Mongkok,Kowloon,Hong Kong + Hong Kong 999077 + CN + +A8-46-74 (hex) Espressif Inc. +A84674 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +88-22-5B (hex) Hewlett Packard Enterprise +88225B (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +DC-62-79 (hex) TP-Link Systems Inc +DC6279 (base 16) TP-Link Systems Inc + 5 Peters Canyon Rd Suit 300 + Irvine CA 92606 + US + +D0-32-C3 (hex) D-Link Corporation +D032C3 (base 16) D-Link Corporation + No.289, Sinhu 3rd Rd., Neihu District, + Taipei City 114 + TW + +A8-D8-61 (hex) ITEL MOBILE LIMITED +A8D861 (base 16) ITEL MOBILE LIMITED + RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K + Hong Kong KOWLOON 999077 + HK + +00-5F-67 (hex) TP-Link Systems Inc +005F67 (base 16) TP-Link Systems Inc + 5 Peters Canyon Rd Suit 300 + Irvine CA 92606 + US + +60-A4-B7 (hex) TP-Link Systems Inc +60A4B7 (base 16) TP-Link Systems Inc + 5 Peters Canyon Rd Suit 300 + Irvine CA 92606 + US + +94-BE-36 (hex) ADOPT NETTECH PVT LTD +94BE36 (base 16) ADOPT NETTECH PVT LTD + F - 8, Phase 1, Oklha Industrial Area, + New Delhi DELHI 110020 + IN + +5C-E9-31 (hex) TP-Link Systems Inc +5CE931 (base 16) TP-Link Systems Inc + 5 Peters Canyon Rd Suit 300 + Irvine CA 92606 + US + +8C-B5-0E (hex) Cisco Systems, Inc +8CB50E (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +48-C0-30 (hex) Kogniza Inc. +48C030 (base 16) Kogniza Inc. + 7405 GLISTEN AVE + Atlanta GA 30328 + US + +A8-46-16 (hex) HUAWEI TECHNOLOGIES CO.,LTD +A84616 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +A4-C7-88 (hex) Xiaomi Communications Co Ltd +A4C788 (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +AC-06-50 (hex) Shanghai Baosight Software Co., Ltd +AC0650 (base 16) Shanghai Baosight Software Co., Ltd + 515 Guo Shoujing Road, China (Shanghai) Pilot Free Trade Zone + Shanghai Shanghai 201203 + CN + +00-86-67 (hex) LG Innotek +008667 (base 16) LG Innotek + 26, HANAMSANDAN 5BEON-RO + Gwangju Gwangsan-gu 506-731 + KR + +B8-E4-4A (hex) Xconnect +B8E44A (base 16) Xconnect + Kurmangazy 77 + Almaty Almaty 050000 + KZ + +CC-03-88 (hex) MangoBoost Inc +CC0388 (base 16) MangoBoost Inc + 3120 139th Ave SE, ste 500 + Bellevue WA 98005-4486 + US + +F0-F5-6D (hex) Kubota Corporation +F0F56D (base 16) Kubota Corporation + 2-47, Shikitsuhigashi 1-chome + Naniwa-ku, Osaka 556-8601 + JP + +00-0B-91 (hex) Xovis Germany GmbH +000B91 (base 16) Xovis Germany GmbH + Ullsteinstraße 140 + Berlin Berlin 12109 + DE + +A4-D5-01 (hex) Yucca Technology Company Limited. +A4D501 (base 16) Yucca Technology Company Limited. + Building 20, Caohejing Pujiang Tech Square, 2388 Chenhang Road, Pujiang Town, Minhang District + Shanghai Shanghai 201114 + CN + +A4-F6-E6 (hex) Apple, Inc. +A4F6E6 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +D4-63-C0 (hex) Apple, Inc. +D463C0 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +84-05-11 (hex) Apple, Inc. +840511 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +18-90-67 (hex) Shenzhen Jingxun Technology Co., Ltd. +189067 (base 16) Shenzhen Jingxun Technology Co., Ltd. + 3/F, A5 Building, Zhiyuan Community, No. 1001, Xueyuan Road, Nanshan District + Shenzhen 518071 + CN + +84-D7-DE (hex) HUAWEI TECHNOLOGIES CO.,LTD +84D7DE (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +8C-30-66 (hex) Ubiquiti Inc +8C3066 (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 + US + +D4-0A-9E (hex) GO development GmbH +D40A9E (base 16) GO development GmbH + Strohgäustrasse 3 + Neuhausen 73765 + DE + +2C-1B-3A (hex) Hui Zhou Gaoshengda Technology Co.,LTD +2C1B3A (base 16) Hui Zhou Gaoshengda Technology Co.,LTD + No.2,Jin-da Road,Huinan Industrial Park + Hui Zhou Guangdong 516025 + CN + +DC-81-3D (hex) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD +DC813D (base 16) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD + Room 211-5, Building 1, No. 290 Wankang Road, Minhang District + Shanghai Shanghai 201100 + CN + +60-83-F8 (hex) SICHUAN HUAKUN ZHENYU INTELLIGENT TECHNOLOGY CO.,LTD +6083F8 (base 16) SICHUAN HUAKUN ZHENYU INTELLIGENT TECHNOLOGY CO.,LTD + 24th Floor, Building C, Mall of Maoye Center,No. 28, North Section, Tianfu Avenue + Chengdu Sichuan 610000 + CN + +B0-34-FB (hex) ShenZhen Microtest Automation Co.,Ltd +B034FB (base 16) ShenZhen Microtest Automation Co.,Ltd + 1st Floor, No.2, Dongtang Industrial Park, Chuangxin Road, Shatou, Shajing, Bao'an District + Shenzhen City Guangdong Province 518104 + CN + +60-DC-0D (hex) TAIWAN SHIN KONG SECURITY CO., LTD +60DC0D (base 16) TAIWAN SHIN KONG SECURITY CO., LTD + No. 128, Xing'ai Rd., Neihu Dist., + Taipei City 114508 + TW + +F0-12-04 (hex) IEEE Registration Authority +F01204 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +00-16-45 (hex) Eaton Corporation +001645 (base 16) Eaton Corporation + 4200 Oakleys Ct. + Richmond VA 23223 + US + +7C-EE-7B (hex) Logically Us Ltd +7CEE7B (base 16) Logically Us Ltd + Reading Enterprise Center Whiteknights Road + Reading Berkshire RG6 6BU + GB + +58-C5-87 (hex) AltoBeam Inc. +58C587 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN + +60-3C-68 (hex) Garmin International +603C68 (base 16) Garmin International + 1200 E. 151st St + Olathe KS 66062 + US + +5C-A6-4F (hex) TP-Link Systems Inc. +5CA64F (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US + +64-CD-F1 (hex) KO & AL Co., Ltd +64CDF1 (base 16) KO & AL Co., Ltd + #202, 251 Yatap-ro, Bundang-gu + Seongnam-si Gyeonggi-do 13508 + KR + +18-51-11 (hex) Universal Electronics, Inc. +185111 (base 16) Universal Electronics, Inc. + 201 E. Sandpointe Ave + Santa Ana CA 92707 + US + +4C-BB-6F (hex) Infinix mobility limited +4CBB6F (base 16) Infinix mobility limited + RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG + HongKong HongKong 999077 + HK + +20-51-F5 (hex) Earda Technologies co Ltd +2051F5 (base 16) Earda Technologies co Ltd + Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District + Guangzhou Guangdong 511455 + CN + +AC-17-C8 (hex) Cisco Meraki +AC17C8 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + +98-18-88 (hex) Cisco Meraki +981888 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + +4C-C8-A1 (hex) Cisco Meraki +4CC8A1 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + +B4-E8-5C (hex) fünfeinhalb Funksysteme GmbH +B4E85C (base 16) fünfeinhalb Funksysteme GmbH + Chemnitzer Straße 78A + Dresden Saxony 01187 + DE + +64-A0-AC (hex) Adtran Inc +64A0AC (base 16) Adtran Inc + 901 Explorer Blvd. + Huntsville AL 35806-2807 + US + +A0-83-B4 (hex) Velorum B.V +A083B4 (base 16) Velorum B.V + Kalkhofseweg 20 + Haps 5443NA + NL + +60-0A-8C (hex) Shenzhen Sundray Technologies company Limited +600A8C (base 16) Shenzhen Sundray Technologies company Limited + 1st Floor Building A1, Nanshan i Park, No.1001 Xueyuan Road, Nanshan District, Shenzhen, Guangdong Province, P. R. China + Shenzhen GuangDong 518057 + CN + +10-2F-6E (hex) Shenzhen Sundray Technologies company Limited +102F6E (base 16) Shenzhen Sundray Technologies company Limited + 5th Floor, Block A4, Nanshan ipark,NO.1001 Xue Yuan Road, Nanshan District, Shenzhen 518055, P.R. China + Shenzhen Guangdong 518057 + CN + +B0-0B-22 (hex) Huawei Device Co., Ltd. +B00B22 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +8C-05-72 (hex) Huawei Device Co., Ltd. +8C0572 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +10-25-CE (hex) ELKA - Torantriebe GmbH u. Co. Betriebs KG +1025CE (base 16) ELKA - Torantriebe GmbH u. Co. Betriebs KG + Dithmarscher Straße 9 + Tönning 25832 + DE + +9C-7D-C0 (hex) Tech4home, Lda +9C7DC0 (base 16) Tech4home, Lda + Rua de Fundoes N151 + Sao Joao da Madeira Aveiro 3700-121 + PT + +20-A7-16 (hex) Silicon Laboratories +20A716 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +C0-9B-9E (hex) Silicon Laboratories +C09B9E (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +DC-EC-4F (hex) Guangzhou Shiyuan Electronic Technology Company Limited +DCEC4F (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 + CN + +D8-5A-49 (hex) INGCHIPS Technology Co., Ltd +D85A49 (base 16) INGCHIPS Technology Co., Ltd + 1009 ShuGuang Building,South 12th Road + Shenzhen Nanshan District 518000 + CN + +54-A6-37 (hex) HUAWEI TECHNOLOGIES CO.,LTD +54A637 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +30-61-A2 (hex) HUAWEI TECHNOLOGIES CO.,LTD +3061A2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +E0-28-6D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +E0286D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +44-4E-6D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +444E6D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +F0-B0-14 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +F0B014 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +48-5D-35 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +485D35 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +B4-FC-7D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +B4FC7D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +98-A9-65 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +98A965 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +EC-74-27 (hex) eero inc. +EC7427 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +C8-E3-06 (hex) eero inc. +C8E306 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +78-D6-D6 (hex) eero inc. +78D6D6 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +D4-3F-32 (hex) eero inc. +D43F32 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +A0-8E-24 (hex) eero inc. +A08E24 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +B4-20-46 (hex) eero inc. +B42046 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +C0-36-53 (hex) eero inc. +C03653 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +D4-05-DE (hex) eero inc. +D405DE (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +9C-0B-05 (hex) eero inc. +9C0B05 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +74-42-7F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +74427F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +1C-ED-6F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH +1CED6F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE + +5C-A5-BC (hex) eero inc. +5CA5BC (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +A8-B0-88 (hex) eero inc. +A8B088 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +B4-E5-3E (hex) Ruckus Wireless +B4E53E (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US + +88-67-46 (hex) eero inc. +886746 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +24-F3-E3 (hex) eero inc. +24F3E3 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +E4-19-7F (hex) eero inc. +E4197F (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +FC-3D-73 (hex) eero inc. +FC3D73 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +14-79-F3 (hex) China Mobile Group Device Co.,Ltd. +1479F3 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +3C-57-4F (hex) China Mobile Group Device Co.,Ltd. +3C574F (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +00-CF-C0 (hex) China Mobile Group Device Co.,Ltd. +00CFC0 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +40-62-EA (hex) China Mobile Group Device Co.,Ltd. +4062EA (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +E0-45-6D (hex) China Mobile Group Device Co.,Ltd. +E0456D (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +50-8C-F5 (hex) China Mobile Group Device Co.,Ltd. +508CF5 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +E4-C0-CC (hex) China Mobile Group Device Co.,Ltd. +E4C0CC (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +C0-16-92 (hex) China Mobile Group Device Co.,Ltd. +C01692 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +00-E2-2C (hex) China Mobile Group Device Co.,Ltd. +00E22C (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +E0-E0-C2 (hex) China Mobile Group Device Co.,Ltd. +E0E0C2 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +78-2E-56 (hex) China Mobile Group Device Co.,Ltd. +782E56 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +94-6D-AE (hex) Mellanox Technologies, Inc. +946DAE (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +FC-6A-1C (hex) Mellanox Technologies, Inc. +FC6A1C (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +A0-88-C2 (hex) Mellanox Technologies, Inc. +A088C2 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +7C-49-CF (hex) eero inc. +7C49CF (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +24-2D-6C (hex) eero inc. +242D6C (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +30-3A-4A (hex) eero inc. +303A4A (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +DC-69-B5 (hex) eero inc. +DC69B5 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US + +5C-75-C6 (hex) China Mobile Group Device Co.,Ltd. +5C75C6 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +24-12-81 (hex) China Mobile Group Device Co.,Ltd. +241281 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +64-C5-82 (hex) China Mobile Group Device Co.,Ltd. +64C582 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +E0-9D-73 (hex) Mellanox Technologies, Inc. +E09D73 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +50-00-E6 (hex) Mellanox Technologies, Inc. +5000E6 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US + +44-8E-EC (hex) China Mobile Group Device Co.,Ltd. +448EEC (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +50-CF-56 (hex) China Mobile Group Device Co.,Ltd. +50CF56 (base 16) China Mobile Group Device Co.,Ltd. + 32 Xuanwumen West Street,Xicheng District + Beijing 100053 + CN + +C8-24-78 (hex) Edifier International +C82478 (base 16) Edifier International + Suit 2207, 22nd floor, Tower II, Lippo centre, 89 Queensway + Hong Kong 070 + CN + +F8-F2-95 (hex) Annapurna labs +F8F295 (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +E0-42-6D (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD +E0426D (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD + NO.18 HAIBIN ROAD, + DONG GUAN GUANG DONG 523860 + CN + +80-03-0D (hex) CANON INC. +80030D (base 16) CANON INC. + 30-2 Shimomaruko 3-chome, + Ohta-ku Tokyo 146-8501 + JP + +D4-A0-FB (hex) IEEE Registration Authority +D4A0FB (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +18-C1-E2 (hex) Qolsys Inc. +18C1E2 (base 16) Qolsys Inc. + 1919 S Bascom Ave Suit 600 + Campbell CA 95008 + US + +F8-43-EF (hex) Xiaomi Communications Co Ltd +F843EF (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +A4-FF-9F (hex) Xiaomi Communications Co Ltd +A4FF9F (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +20-25-CC (hex) Xiaomi Communications Co Ltd +2025CC (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 + CN + +B0-F3-E9 (hex) PATEO CONNECT (Xiamen) Co., Ltd. +B0F3E9 (base 16) PATEO CONNECT (Xiamen) Co., Ltd. + No. 11-29, Fangyang West Road, Xiang’an District + Xiamen fujian 361115 + CN + +30-4A-C4 (hex) Barrot Technology Co.,LTD +304AC4 (base 16) Barrot Technology Co.,LTD + A1009, Block A, Jia Hua Building, No.9 Shangdisanjie St, Haidian District, + beijing beijing 100000 + CN + +64-75-20 (hex) zte corporation +647520 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +84-31-A8 (hex) Funshion Online Technologies Co.,Ltd +8431A8 (base 16) Funshion Online Technologies Co.,Ltd + 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing + Beijing 100029 + CN + +98-3F-66 (hex) Funshion Online Technologies Co.,Ltd +983F66 (base 16) Funshion Online Technologies Co.,Ltd + 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing + Beijing 100029 + CN + +D4-7A-EC (hex) Funshion Online Technologies Co.,Ltd +D47AEC (base 16) Funshion Online Technologies Co.,Ltd + 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing + Beijing 100029 + CN + +40-A7-86 (hex) TECNO MOBILE LIMITED +40A786 (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 + Hong Kong Hong Kong 999077 + HK + +9C-13-9E (hex) Espressif Inc. +9C139E (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN + +00-C8-4E (hex) Hewlett Packard Enterprise +00C84E (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +88-DA-36 (hex) Calix Inc. +88DA36 (base 16) Calix Inc. + 2777 Orchard Pkwy + San Jose CA 95131 + US + +98-17-1A (hex) Beijing Xiaomi Mobile Software Co., Ltd +98171A (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN + +EC-10-55 (hex) Beijing Xiaomi Electronics Co.,Ltd +EC1055 (base 16) Beijing Xiaomi Electronics Co.,Ltd + Xiaomi Campus + Beijing Beijing 100085 + CN + +2C-DC-C1 (hex) EM Microelectronic +2CDCC1 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +A4-97-00 (hex) Cisco Systems, Inc +A49700 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US + +94-4F-DB (hex) Nokia +944FDB (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA + +D8-53-AD (hex) Cisco Meraki +D853AD (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 + US + +40-10-ED (hex) G.Tech Technology Ltd. +4010ED (base 16) G.Tech Technology Ltd. + No.8,Jinyuan 1st Road,Tangjiawan Town, High-tech Zone + Zhuhai Guangdong 519085 + CN + +68-A5-93 (hex) Apple, Inc. +68A593 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +A8-2C-89 (hex) Apple, Inc. +A82C89 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +80-12-42 (hex) Apple, Inc. +801242 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +B8-01-1F (hex) Apple, Inc. +B8011F (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +14-14-16 (hex) Hui Zhou Gaoshengda Technology Co.,LTD +141416 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD + No.2,Jin-da Road,Huinan Industrial Park + Hui Zhou Guangdong 516025 + CN + +80-40-05 (hex) Guangdong COROS Sports Technology Co.,Ltd +804005 (base 16) Guangdong COROS Sports Technology Co.,Ltd + Room 601 & 701, Bld. 2, No.2, Science and Technology 9 Rd, Songshan Lake Hi-Tech Zone, Dongguan 523808, Guandong, China + Dongguan Guangdong 523808 + CN + +DC-93-96 (hex) Apple, Inc. +DC9396 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +18-E6-71 (hex) Apple, Inc. +18E671 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US + +CC-EA-27 (hex) GE Appliances +CCEA27 (base 16) GE Appliances + 4000 Buechel Bank Road + Louisville KY 40225 + US + +30-F8-56 (hex) Extreme Networks Headquarters +30F856 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville 27560 + US + +8C-3D-16 (hex) Shenzhen Four Seas Global Link Network Technology Co.,Ltd +8C3D16 (base 16) Shenzhen Four Seas Global Link Network Technology Co.,Ltd + 9/F, Block H, South China Digital Valley, No.1 South China Road, Longhua District, Shenzhen ,China + Shenzhen 518000 CN -78-52-37 (hex) zte corporation -785237 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +C0-88-40 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +C08840 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN -60-BD-83 (hex) HUAWEI TECHNOLOGIES CO.,LTD -60BD83 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +D0-C6-7F (hex) HUAWEI TECHNOLOGIES CO.,LTD +D0C67F (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -10-67-A3 (hex) HUAWEI TECHNOLOGIES CO.,LTD -1067A3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +98-F3-F6 (hex) HUAWEI TECHNOLOGIES CO.,LTD +98F3F6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -E0-0E-CE (hex) Fiberhome Telecommunication Technologies Co.,LTD -E00ECE (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 +B0-25-AA (hex) AIstone Global Limited +B025AA (base 16) AIstone Global Limited + 29/F. , One Exchange Square 8 + Connaught Place Centa Hong Kong 999077 CN -6C-C8-40 (hex) Espressif Inc. -6CC840 (base 16) Espressif Inc. +B4-89-31 (hex) Silicon Laboratories +B48931 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US + +4C-AD-DF (hex) Công ty Cổ phần Thiết bị Công nghiệp GEIC +4CADDF (base 16) Công ty Cổ phần Thiết bị Công nghiệp GEIC + 52 Lê Đại Hành, phường Lê Đại Hành, quận Hai Bà Trưng + Thành phố Hà Nội 000084 + VN + +64-CD-C2 (hex) Amazon Technologies Inc. +64CDC2 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +4C-60-AD (hex) Amazon Technologies Inc. +4C60AD (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 + US + +48-F6-EE (hex) Espressif Inc. +48F6EE (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN -78-81-8C (hex) Nintendo Co.,Ltd -78818C (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP +7C-31-FA (hex) Silicon Laboratories +7C31FA (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -E0-13-33 (hex) General Motors -E01333 (base 16) General Motors - 3300 General Motors Rd - Milford MI 48380 +3C-A0-70 (hex) Blink by Amazon +3CA070 (base 16) Blink by Amazon + 100 Riverpark Drive + North Reading MA 01864 US -3C-32-B9 (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -3C32B9 (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 +10-5E-AE (hex) New H3C Technologies Co., Ltd +105EAE (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -DC-73-06 (hex) Vantiva Connected Home - Home Networks -DC7306 (base 16) Vantiva Connected Home - Home Networks - 4855 Peachtree Industrial BlvdSuite 200 / RM-138 - Norcross GA 30092 - US +24-F4-0A (hex) Samsung Electronics Co.,Ltd +24F40A (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -7C-F1-7E (hex) TP-Link Systems Inc -7CF17E (base 16) TP-Link Systems Inc - 7 Temasek Boulevard #29-03 Suntec Tower One - Singapore 038987 - SG +78-C1-1D (hex) Samsung Electronics Co.,Ltd +78C11D (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -FC-4C-EF (hex) Huawei Device Co., Ltd. -FC4CEF (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +E8-C9-13 (hex) Samsung Electronics Co.,Ltd +E8C913 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR + +4C-A9-54 (hex) Intel Corporate +4CA954 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +08-EB-21 (hex) Intel Corporate +08EB21 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +14-C2-4D (hex) ATW TECHNOLOGY, INC. +14C24D (base 16) ATW TECHNOLOGY, INC. + 1F, No.236 Ba’ai Street, Shulin District + New Taipei City 23845 + TW + +58-E4-EB (hex) FN-LINK TECHNOLOGY Ltd. +58E4EB (base 16) FN-LINK TECHNOLOGY Ltd. + No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China + Changsha Hunan 410329 CN -44-36-59 (hex) Robert Bosch GmbH -443659 (base 16) Robert Bosch GmbH - Robert-Bosch-Platz 1 - Gerlingen-Schillerhöhe 70839 - DE +98-3A-1F (hex) Google, Inc. +983A1F (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US -28-56-2F (hex) Espressif Inc. -28562F (base 16) Espressif Inc. +F8-47-E3 (hex) Shenzhen Skyworth Digital Technology CO., Ltd +F847E3 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd + 4F,Block A, Skyworth?Building, + Shenzhen Guangdong 518057 + CN + +08-92-72 (hex) Espressif Inc. +089272 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN -98-25-4A (hex) TP-Link Systems Inc -98254A (base 16) TP-Link Systems Inc - 5 Peters Canyon Rd Suit 300 - Irvine CA 92606 - SG +B0-6B-11 (hex) Hui Zhou Gaoshengda Technology Co.,LTD +B06B11 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD + No.2,Jin-da Road,Huinan Industrial Park + Hui Zhou Guangdong 516025 + CN -D8-B2-93 (hex) TOPSSD -D8B293 (base 16) TOPSSD - Room101,Building#11,Xingong Industrial Park, No.100 Luyun Road, Yuelu District - Changsha Hunan 410000 +2C-0D-CF (hex) Xiaomi Communications Co Ltd +2C0DCF (base 16) Xiaomi Communications Co Ltd + #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road + Beijing Haidian District 100085 CN -AC-C3-E5 (hex) Cisco Meraki -ACC3E5 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 +14-05-89 (hex) Motorola Mobility LLC, a Lenovo Company +140589 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -C8-AA-B2 (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD -C8AAB2 (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD - Unit East Block22-24/F,Skyworth semiconductor design Bldg., Gaoxin Ave.4.S.,Nanshan District,Shenzhen,China - SHENZHEN GUANGDONG 518057 +80-6A-34 (hex) Bouffalo Lab (Nanjing) Co., Ltd. +806A34 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 CN -74-FE-CE (hex) TP-Link Systems Inc -74FECE (base 16) TP-Link Systems Inc - 5 Peters Canyon Rd Suit 300 - Irvine CA 92606 +54-DD-21 (hex) Huawei Device Co., Ltd. +54DD21 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +AC-10-65 (hex) KT Micro, Inc. +AC1065 (base 16) KT Micro, Inc. + Building 76, National Cybersecurity Industry Park, Beiwucun Road 23, Haidian District, Beijing + Beijing 100195 + CN + +D4-FF-26 (hex) OHSUNG +D4FF26 (base 16) OHSUNG + 335-4,SANHODAERO,GUMI,GYEONG BUK,KOREA + GUMI GYEONG BUK 730-030 + KR + +00-86-21 (hex) Amazon Technologies Inc. +008621 (base 16) Amazon Technologies Inc. + P.O Box 8102 + Reno NV 89507 US -24-2F-D0 (hex) TP-Link Systems Inc -242FD0 (base 16) TP-Link Systems Inc - 5 Peters Canyon Rd Suit 300 - Irvine CA 92606 +A8-24-50 (hex) Beijing Huadianzhongxin Tech.Co.,Ltd +A82450 (base 16) Beijing Huadianzhongxin Tech.Co.,Ltd + Room 318,the 3rd Floorl,Xingtianhaiyuan Building,Xianghuangqi East Rd,Nongda South Rd, Haidian District,Beijing,P.R.C + Bei Jing 100193 + CN + +8C-44-BB (hex) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD +8C44BB (base 16) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD + 6-8 Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China + Shenzhen 518052 + CN + +C8-E3-1D (hex) HUAWEI TECHNOLOGIES CO.,LTD +C8E31D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +50-AC-B9 (hex) HUAWEI TECHNOLOGIES CO.,LTD +50ACB9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN + +0C-33-1B (hex) TydenBrooks +0C331B (base 16) TydenBrooks + 2727 Paces Ferry Rd, Building 2, Suite 300 + Atlanta GA 30339 US -80-F3-EF (hex) Meta Platforms, Inc. -80F3EF (base 16) Meta Platforms, Inc. - 1601 Willow Rd - Menlo Park CA 94025 +24-2B-D6 (hex) Ring LLC +242BD6 (base 16) Ring LLC + 1523 26th St + Santa Monica CA 90404 US -84-57-F7 (hex) Meta Platforms, Inc. -8457F7 (base 16) Meta Platforms, Inc. - 1601 Willow Rd - Menlo Park CA 94025 +00-30-52 (hex) Zhone Technologies, Inc. +003052 (base 16) Zhone Technologies, Inc. + 6120 WINDWARD PARKWAY -STE#100 + ALPHARETTA GA 30005 US -50-3A-0F (hex) ALL Winner (Hong Kong) Limited -503A0F (base 16) ALL Winner (Hong Kong) Limited - Unit No.1301,13F,Sunbeam Plaza,1155 Canton Road,Mongkok,Kowloon,Hong Kong - Hong Kong 999077 +2C-4C-7D (hex) New H3C Technologies Co., Ltd +2C4C7D (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -A8-46-74 (hex) Espressif Inc. -A84674 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +E4-6E-8A (hex) BYD Lithium Battery Co., Ltd. +E46E8A (base 16) BYD Lithium Battery Co., Ltd. + No. 3001 Baohe Road, Baolong Industrial City, Longgang Street, Longgang District + Shen Zhen Guang Dong 518100 CN -88-22-5B (hex) Hewlett Packard Enterprise -88225B (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 +28-57-5D (hex) Apple, Inc. +28575D (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -DC-62-79 (hex) TP-Link Systems Inc -DC6279 (base 16) TP-Link Systems Inc - 5 Peters Canyon Rd Suit 300 - Irvine CA 92606 +34-10-BE (hex) Apple, Inc. +3410BE (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -EC-34-E2 (hex) Private -EC34E2 (base 16) Private +E4-56-AC (hex) Silicon Laboratories +E456AC (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -D0-32-C3 (hex) D-Link Corporation -D032C3 (base 16) D-Link Corporation - No.289, Sinhu 3rd Rd., Neihu District, - Taipei City 114 - TW +54-91-E1 (hex) Vitalacy Inc. +5491E1 (base 16) Vitalacy Inc. + 11859 Wilshire Blvd #500 + Los Angeles CA 90025 + US -A8-D8-61 (hex) ITEL MOBILE LIMITED -A8D861 (base 16) ITEL MOBILE LIMITED - RM B3 & B4 BLOCK B, KO FAI INDUSTRIAL BUILDING NO.7 KO FAI ROAD, YAU TONG, KLN, H.K - Hong Kong KOWLOON 999077 - HK +D8-52-FA (hex) Texas Instruments +D852FA (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US -00-5F-67 (hex) TP-Link Systems Inc -005F67 (base 16) TP-Link Systems Inc - 5 Peters Canyon Rd Suit 300 - Irvine CA 92606 +F4-33-B7 (hex) Apple, Inc. +F433B7 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -60-A4-B7 (hex) TP-Link Systems Inc -60A4B7 (base 16) TP-Link Systems Inc - 5 Peters Canyon Rd Suit 300 - Irvine CA 92606 +D4-BE-D7 (hex) Dell Inc. +D4BED7 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 US -94-BE-36 (hex) ADOPT NETTECH PVT LTD -94BE36 (base 16) ADOPT NETTECH PVT LTD - F - 8, Phase 1, Oklha Industrial Area, - New Delhi DELHI 110020 - IN +D4-2F-4B (hex) Hon Hai Precision Industry Co.,LTD +D42F4B (base 16) Hon Hai Precision Industry Co.,LTD + 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI CITY,23678 , TAIWAN CHINA + TAIPEI 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI 33859 + CN -5C-E9-31 (hex) TP-Link Systems Inc -5CE931 (base 16) TP-Link Systems Inc - 5 Peters Canyon Rd Suit 300 - Irvine CA 92606 +38-E2-C4 (hex) Texas Instruments +38E2C4 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -8C-B5-0E (hex) Cisco Systems, Inc -8CB50E (base 16) Cisco Systems, Inc +C8-C8-3F (hex) Texas Instruments +C8C83F (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 + US + +E0-D4-91 (hex) Cisco Systems, Inc +E0D491 (base 16) Cisco Systems, Inc 80 West Tasman Drive San Jose CA 94568 US -48-C0-30 (hex) Kogniza Inc. -48C030 (base 16) Kogniza Inc. - 7405 GLISTEN AVE - Atlanta GA 30328 +A4-DC-D5 (hex) Cisco Systems, Inc +A4DCD5 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -A8-46-16 (hex) HUAWEI TECHNOLOGIES CO.,LTD -A84616 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +54-FB-66 (hex) ASRock Incorporation +54FB66 (base 16) ASRock Incorporation + 2F., No.37, Sec. 2, Jhongyang S. Rd., Beitou District, + Taipei 112 + TW -A4-C7-88 (hex) Xiaomi Communications Co Ltd -A4C788 (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +90-29-62 (hex) Linkpower Microelectronics Co., Ltd. +902962 (base 16) Linkpower Microelectronics Co., Ltd. + 905, B1, 999-8, Gaolang East Road, Wuxi Economic Development Zone, Jiangsu Province + wuxi jiangsu 214131 CN -AC-06-50 (hex) Shanghai Baosight Software Co., Ltd -AC0650 (base 16) Shanghai Baosight Software Co., Ltd - 515 Guo Shoujing Road, China (Shanghai) Pilot Free Trade Zone - Shanghai Shanghai 201203 +2C-15-7E (hex) RADIODATA GmbH +2C157E (base 16) RADIODATA GmbH + Newtonstraße 18 + Berlin 12489 + DE + +44-F7-9F (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +44F79F (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 CN -00-86-67 (hex) LG Innotek -008667 (base 16) LG Innotek - 26, HANAMSANDAN 5BEON-RO - Gwangju Gwangsan-gu 506-731 - KR +10-C3-4D (hex) SHENZHEN WATER WORLD CO.,LTD. +10C34D (base 16) SHENZHEN WATER WORLD CO.,LTD. + Room 1201, 12F, Block B, Qinghua Information Port No.1, Songpingshan Xindong Road, Songpingshan Community, Xili Subdistrict, Nanshan District, Shenzhen + Shenzhen GuangDong 518000 + CN -B8-E4-4A (hex) Xconnect -B8E44A (base 16) Xconnect - Kurmangazy 77 - Almaty Almaty 050000 - KZ +88-49-2D (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD +88492D (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD + NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district + shenzhen guangdong 518000 + CN -CC-03-88 (hex) MangoBoost Inc -CC0388 (base 16) MangoBoost Inc - 3120 139th Ave SE, ste 500 - Bellevue WA 98005-4486 - US +A4-3A-39 (hex) AURORA TECHNOLOGIES CO.,LTD. +A43A39 (base 16) AURORA TECHNOLOGIES CO.,LTD. + ROOM 1006, BLOCK B, QIANHAI ECONOMIC AND TRADE CENTER, CHINA MERCHANTS GROUP, NO.151 WEST FREE TRADE STREET, QIANHAI, + SHENZHEN 518000 + CN -F0-F5-6D (hex) Kubota Corporation -F0F56D (base 16) Kubota Corporation - 2-47, Shikitsuhigashi 1-chome - Naniwa-ku, Osaka 556-8601 - JP +34-E1-D7 (hex) NXP Semiconductors Taiwan Ltd. +34E1D7 (base 16) NXP Semiconductors Taiwan Ltd. + No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan + Nanzi Dist. Kaohsiung 811643 + TW -00-0B-91 (hex) Xovis Germany GmbH -000B91 (base 16) Xovis Germany GmbH - Ullsteinstraße 140 - Berlin Berlin 12109 - DE +84-9D-4B (hex) Shenzhen Boomtech Industrial Corporation +849D4B (base 16) Shenzhen Boomtech Industrial Corporation + 905/906,BuildingA, Huizhi R&D Center. Xixiang,Bao'an District + Shenzhen 518100 + CN -A4-D5-01 (hex) Yucca Technology Company Limited. -A4D501 (base 16) Yucca Technology Company Limited. - Building 20, Caohejing Pujiang Tech Square, 2388 Chenhang Road, Pujiang Town, Minhang District - Shanghai Shanghai 201114 +70-A3-A4 (hex) Beijing Guming Communication Technology Co., Ltd. +70A3A4 (base 16) Beijing Guming Communication Technology Co., Ltd. + Room 202-6, 2nd Floor, Building 1, No. 8 Courtyard, Yongchang Middle Road, Beijing Economic and Technological Development Area, Beijing + Beijing Beijing 100176 CN -A4-F6-E6 (hex) Apple, Inc. -A4F6E6 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +94-EF-97 (hex) Edgecore Americas Networking Corporation +94EF97 (base 16) Edgecore Americas Networking Corporation + 20 Mason + Irvine CA 92618 US -D4-63-C0 (hex) Apple, Inc. -D463C0 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +2C-59-17 (hex) Arcadyan Corporation +2C5917 (base 16) Arcadyan Corporation + No.8, Sec.2, Guangfu Rd. + Hsinchu City Hsinchu 30071 + TW + +20-36-D0 (hex) Motorola Mobility LLC, a Lenovo Company +2036D0 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -84-05-11 (hex) Apple, Inc. -840511 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +84-D9-E0 (hex) eero inc. +84D9E0 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US -18-90-67 (hex) Shenzhen Jingxun Technology Co., Ltd. -189067 (base 16) Shenzhen Jingxun Technology Co., Ltd. - 3/F, A5 Building, Zhiyuan Community, No. 1001, Xueyuan Road, Nanshan District - Shenzhen 518071 +7C-6C-E1 (hex) Shenzhen Honesty Electronics Co.,Ltd. +7C6CE1 (base 16) Shenzhen Honesty Electronics Co.,Ltd. + 5/F,Zone B,Chitat Industrial Park,West Longping Road, Longgang District,Shenzhen City + Shenzhen Guangdong 518172 CN -84-D7-DE (hex) HUAWEI TECHNOLOGIES CO.,LTD -84D7DE (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +68-4A-6E (hex) Quectel Wireless Solutions Co.,Ltd. +684A6E (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN -8C-30-66 (hex) Ubiquiti Inc -8C3066 (base 16) Ubiquiti Inc - 685 Third Avenue, 27th Floor - New York NY New York NY 10017 - US - -D4-0A-9E (hex) GO development GmbH -D40A9E (base 16) GO development GmbH - Strohgäustrasse 3 - Neuhausen 73765 - DE - -2C-1B-3A (hex) Hui Zhou Gaoshengda Technology Co.,LTD -2C1B3A (base 16) Hui Zhou Gaoshengda Technology Co.,LTD - No.2,Jin-da Road,Huinan Industrial Park - Hui Zhou Guangdong 516025 +64-24-4D (hex) Hangzhou Ezviz Software Co.,Ltd. +64244D (base 16) Hangzhou Ezviz Software Co.,Ltd. + 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District + Hangzhou Zhejiang 310051 CN -DC-81-3D (hex) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD -DC813D (base 16) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD - Room 211-5, Building 1, No. 290 Wankang Road, Minhang District - Shanghai Shanghai 201100 +0C-A6-4C (hex) Hangzhou Ezviz Software Co.,Ltd. +0CA64C (base 16) Hangzhou Ezviz Software Co.,Ltd. + 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District + Hangzhou Zhejiang 310051 CN -60-83-F8 (hex) SICHUAN HUAKUN ZHENYU INTELLIGENT TECHNOLOGY CO.,LTD -6083F8 (base 16) SICHUAN HUAKUN ZHENYU INTELLIGENT TECHNOLOGY CO.,LTD - 24th Floor, Building C, Mall of Maoye Center,No. 28, North Section, Tianfu Avenue - Chengdu Sichuan 610000 +34-C6-DD (hex) Hangzhou Ezviz Software Co.,Ltd. +34C6DD (base 16) Hangzhou Ezviz Software Co.,Ltd. + 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District + Hangzhou Zhejiang 310051 CN -B0-34-FB (hex) ShenZhen Microtest Automation Co.,Ltd -B034FB (base 16) ShenZhen Microtest Automation Co.,Ltd - 1st Floor, No.2, Dongtang Industrial Park, Chuangxin Road, Shatou, Shajing, Bao'an District - Shenzhen City Guangdong Province 518104 +AC-1C-26 (hex) Hangzhou Ezviz Software Co.,Ltd. +AC1C26 (base 16) Hangzhou Ezviz Software Co.,Ltd. + 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District + Hangzhou Zhejiang 310051 CN -60-DC-0D (hex) TAIWAN SHIN KONG SECURITY CO., LTD -60DC0D (base 16) TAIWAN SHIN KONG SECURITY CO., LTD - No. 128, Xing'ai Rd., Neihu Dist., - Taipei City 114508 +C0-2E-1D (hex) Nokia Solutions and Networks GmbH & Co. KG +C02E1D (base 16) Nokia Solutions and Networks GmbH & Co. KG + Werinherstrasse 91 + München Bavaria D-81541 + DE + +BC-87-53 (hex) Sera Network Inc. +BC8753 (base 16) Sera Network Inc. + 2F, No.60, 321 Ln., Yangguang St., Neihu Dist., + Taipei Taiwan 114717 TW -F0-12-04 (hex) IEEE Registration Authority -F01204 (base 16) IEEE Registration Authority +50-FA-CB (hex) IEEE Registration Authority +50FACB (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -00-16-45 (hex) Eaton Corporation -001645 (base 16) Eaton Corporation - 4200 Oakleys Ct. - Richmond VA 23223 +B8-52-13 (hex) zte corporation +B85213 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN + +9C-6D-92 (hex) Shanghai Kanghai Infomation System CO.,LTD +9C6D92 (base 16) Shanghai Kanghai Infomation System CO.,LTD + Room 207, Building 1, 6055 Songze Avenue , Qingpu District, Shanghai + Shanghai 201706 + CN + +9C-E4-50 (hex) IEEE Registration Authority +9CE450 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -7C-EE-7B (hex) Logically Us Ltd -7CEE7B (base 16) Logically Us Ltd - Reading Enterprise Center Whiteknights Road - Reading Berkshire RG6 6BU - GB +E8-8F-16 (hex) Skullcandy +E88F16 (base 16) Skullcandy + 1441 Ute blvd. + Park City 84098 + US -D0-CF-13 (hex) Espressif Inc. -D0CF13 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +18-41-FE (hex) KATIM L.L.C +1841FE (base 16) KATIM L.L.C + Elektroniikkatie 8 + Oulu 90590 + FI -58-C5-87 (hex) AltoBeam Inc. -58C587 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 +00-1B-85 (hex) Everllence +001B85 (base 16) Everllence + Teglholmsgade 41 + Copenhagen 2450 + DK + +D8-91-1D (hex) Jiangsu Yuwell POCTech Biotechnology Co.,Ltd +D8911D (base 16) Jiangsu Yuwell POCTech Biotechnology Co.,Ltd + No.5 Baisheng Road Development Zone, 212300 Danyang, Jiangsu, PEOPLE’S REPUBLIC OF CHINA + Danyang 212300 CN -60-3C-68 (hex) Garmin International -603C68 (base 16) Garmin International - 1200 E. 151st St - Olathe KS 66062 +AC-39-3D (hex) eero inc. +AC393D (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US -5C-A6-4F (hex) TP-Link Systems Inc. -5CA64F (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 +00-E6-07 (hex) AURCORE TECHNOLOGY INC. +00E607 (base 16) AURCORE TECHNOLOGY INC. + 25691 ATLANTIC OCEAN DRIVE UNIT B10 + LAKE FOREST CA 92630 US -64-CD-F1 (hex) KO & AL Co., Ltd -64CDF1 (base 16) KO & AL Co., Ltd - #202, 251 Yatap-ro, Bundang-gu - Seongnam-si Gyeonggi-do 13508 - KR +2C-27-E4 (hex) Luxshare Precision Industry (Xuancheng) Co.,Ltd. +2C27E4 (base 16) Luxshare Precision Industry (Xuancheng) Co.,Ltd. + Address: No.5 Baishou Road, Xuancheng High-Tech Industrial Development Zone + Xuancheng Anhui 242000 + CN -18-51-11 (hex) Universal Electronics, Inc. -185111 (base 16) Universal Electronics, Inc. - 201 E. Sandpointe Ave - Santa Ana CA 92707 +4C-D7-4A (hex) Vantiva USA LLC +4CD74A (base 16) Vantiva USA LLC + 4855 Peachtree Industrial Blvd, Suite 200 + Norcross GA 30902 US -4C-BB-6F (hex) Infinix mobility limited -4CBB6F (base 16) Infinix mobility limited - RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG - HongKong HongKong 999077 - HK +FC-96-3E (hex) EM Microelectronic +FC963E (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH -20-51-F5 (hex) Earda Technologies co Ltd -2051F5 (base 16) Earda Technologies co Ltd - Block A,Lianfeng Creative Park, #2 Jisheng Rd., Nansha District - Guangzhou Guangdong 511455 +FC-CF-9F (hex) EM Microelectronic +FCCF9F (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH + +B4-04-29 (hex) Guangzhou Shiyuan Electronic Technology Company Limited +B40429 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited + No.6, 4th Yunpu Road, Yunpu industry District + Guangzhou Guangdong 510530 CN -AC-17-C8 (hex) Cisco Meraki -AC17C8 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 - US +D4-25-DE (hex) New H3C Technologies Co., Ltd +D425DE (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 + CN -98-18-88 (hex) Cisco Meraki -981888 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 +B0-E8-E8 (hex) Silicon Laboratories +B0E8E8 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -4C-C8-A1 (hex) Cisco Meraki -4CC8A1 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 +64-69-11 (hex) APTIV SERVICES US, LLC +646911 (base 16) APTIV SERVICES US, LLC + 5725 Innovation Drive + Troy MI 48098 US -B4-E8-5C (hex) fünfeinhalb Funksysteme GmbH -B4E85C (base 16) fünfeinhalb Funksysteme GmbH - Chemnitzer Straße 78A - Dresden Saxony 01187 - DE +04-D6-88 (hex) CIG SHANGHAI CO LTD +04D688 (base 16) CIG SHANGHAI CO LTD + 5th Floor, Building 8 No 2388 Chenhang Road + SHANGHAI 201114 + CN -64-A0-AC (hex) Adtran Inc -64A0AC (base 16) Adtran Inc - 901 Explorer Blvd. - Huntsville AL 35806-2807 +78-79-84 (hex) Apple, Inc. +787984 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -A0-83-B4 (hex) Velorum B.V -A083B4 (base 16) Velorum B.V - Kalkhofseweg 20 - Haps 5443NA - NL +6C-0B-84 (hex) Universal Global Scientific Industrial., Ltd +6C0B84 (base 16) Universal Global Scientific Industrial., Ltd + 141, Lane 351, TaiPing Road, Sec.1 + Tsao-Tuen Nan-Tou 54261 + TW -8C-05-72 (hex) Huawei Device Co., Ltd. -8C0572 (base 16) Huawei Device Co., Ltd. +38-7C-76 (hex) Universal Global Scientific Industrial., Ltd +387C76 (base 16) Universal Global Scientific Industrial., Ltd + 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen + Nan-Tou Taiwan 54261 + TW + +78-67-0E (hex) WNC Corporation +78670E (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +58-E4-03 (hex) WNC Corporation +58E403 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +F8-6D-CC (hex) WNC Corporation +F86DCC (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +70-EB-A5 (hex) Huawei Device Co., Ltd. +70EBA5 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -9C-7D-C0 (hex) Tech4home, Lda -9C7DC0 (base 16) Tech4home, Lda - Rua de Fundoes N151 - Sao Joao da Madeira Aveiro 3700-121 - PT +C8-90-F7 (hex) HUAWEI TECHNOLOGIES CO.,LTD +C890F7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 + CN -60-0A-8C (hex) Shenzhen Sundray Technologies company Limited -600A8C (base 16) Shenzhen Sundray Technologies company Limited - 1st Floor Building A1, Nanshan i Park, No.1001 Xueyuan Road, Nanshan District, Shenzhen, Guangdong Province, P. R. China - Shenzhen GuangDong 518057 +20-58-43 (hex) WNC Corporation +205843 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +30-14-4A (hex) WNC Corporation +30144A (base 16) WNC Corporation + 20 Park Avenue II, Hsin Science Park, Hsinchu 308, Taiwan + HsinChu Taiwan 308 + TW + +F0-40-AF (hex) IEEE Registration Authority +F040AF (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US + +28-B4-46 (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD +28B446 (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD + Unit East Block22-24/F,Skyworth semiconductor design Bldg., Gaoxin Ave.4.S.,Nanshan District,Shenzhen,China + SHENZHEN GUANGDONG 518057 CN -10-2F-6E (hex) Shenzhen Sundray Technologies company Limited -102F6E (base 16) Shenzhen Sundray Technologies company Limited - 5th Floor, Block A4, Nanshan ipark,NO.1001 Xue Yuan Road, Nanshan District, Shenzhen 518055, P.R. China - Shenzhen Guangdong 518057 +EC-3A-56 (hex) AzureWave Technology Inc. +EC3A56 (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW + +08-F9-7E (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +08F97E (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 CN -B0-0B-22 (hex) Huawei Device Co., Ltd. -B00B22 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 +50-FE-39 (hex) Beijing Xiaomi Mobile Software Co., Ltd +50FE39 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -10-25-CE (hex) ELKA - Torantriebe GmbH u. Co. Betriebs KG -1025CE (base 16) ELKA - Torantriebe GmbH u. Co. Betriebs KG - Dithmarscher Straße 9 - Tönning 25832 +28-12-D0 (hex) Motorola Mobility LLC, a Lenovo Company +2812D0 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +18-90-24 (hex) Astera LED Technology GmbH +189024 (base 16) Astera LED Technology GmbH + Schatzbogen 60 + Munich Bayern 81829 DE -B4-E5-3E (hex) Ruckus Wireless -B4E53E (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +C8-78-F7 (hex) Cisco Systems, Inc +C878F7 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -20-A7-16 (hex) Silicon Laboratories -20A716 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 +98-5B-76 (hex) Vantiva Connected Home - Orange Belgium +985B76 (base 16) Vantiva Connected Home - Orange Belgium + 4855 Pearchtree Industrial Blvd + Norcross GA 30092 US -C0-9B-9E (hex) Silicon Laboratories -C09B9E (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US +5C-45-46 (hex) Shenzhen Water World Information Co.,Ltd. +5C4546 (base 16) Shenzhen Water World Information Co.,Ltd. + 1F, Building 3, DexinChang Wisdom Park, No. 23, Heping Road, Qinghua Community, Longhua Subdistrict, Longhua District + Shenzhen Guangdong 518109 + CN -DC-EC-4F (hex) Guangzhou Shiyuan Electronic Technology Company Limited -DCEC4F (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 +E4-7C-1A (hex) mercury corperation +E47C1A (base 16) mercury corperation + 90,gajaeul-ro,seo-gu,incheon + incheon 22830 + KR + +04-5F-A6 (hex) Shenzhen SDMC Technology CP,.LTD +045FA6 (base 16) Shenzhen SDMC Technology CP,.LTD + 19/F, Changhong Science &Technology Mansion,No.18, Keji South 12th Road High-tech IndustrialPark Nanshan District,Shenzhen,China + Shenzhen China 518000 CN -D8-5A-49 (hex) INGCHIPS Technology Co., Ltd -D85A49 (base 16) INGCHIPS Technology Co., Ltd - 1009 ShuGuang Building,South 12th Road - Shenzhen Nanshan District 518000 +B4-9D-6B (hex) vivo Mobile Communication Co., Ltd. +B49D6B (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN -54-A6-37 (hex) HUAWEI TECHNOLOGIES CO.,LTD -54A637 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +F4-E1-FC (hex) Hewlett Packard Enterprise +F4E1FC (base 16) Hewlett Packard Enterprise + 6280 America Center Dr + San Jose CA 95002 + US + +B4-1D-62 (hex) Nokia Shanghai Bell Co., Ltd. +B41D62 (base 16) Nokia Shanghai Bell Co., Ltd. + No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai + Shanghai 201206 CN -30-61-A2 (hex) HUAWEI TECHNOLOGIES CO.,LTD -3061A2 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +E0-72-56 (hex) Intel Corporate +E07256 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY + +F4-1C-26 (hex) AltoBeam Inc. +F41C26 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -E0-28-6D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -E0286D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE +84-D1-C1 (hex) Intel Corporate +84D1C1 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -44-4E-6D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -444E6D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 +78-FD-F1 (hex) Shenzhen Huadian Communication Co., Ltd +78FDF1 (base 16) Shenzhen Huadian Communication Co., Ltd + The fourth floor of the State Micro R&D Building, No. 015, Gaoxin South 1st Road, Nanshan District, Shenzhen + Shenzhen 518000 + CN + +FC-F8-61 (hex) Harman/Becker Automotive Systems GmbH +FCF861 (base 16) Harman/Becker Automotive Systems GmbH + Becker-Göring-Straße 16 + Karlsbad Baden-Württemberg 76307 DE -F0-B0-14 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -F0B014 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 +98-0A-4B (hex) Nokia Solutions and Networks GmbH & Co. KG +980A4B (base 16) Nokia Solutions and Networks GmbH & Co. KG + Werinherstrasse 91 + München Bavaria D-81541 DE -EC-74-27 (hex) eero inc. -EC7427 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +74-61-D1 (hex) GOIP Global Services Pvt. Ltd. +7461D1 (base 16) GOIP Global Services Pvt. Ltd. + H68, Sector 63, Noida 201301 + Noida Uttar Pradesh 201301 + IN + +58-89-90 (hex) Starkey Labs Inc. +588990 (base 16) Starkey Labs Inc. + 6600 Washington Ave. S. + Eden Prairie MN 55344 US -C8-E3-06 (hex) eero inc. -C8E306 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +E8-F6-D7 (hex) IEEE Registration Authority +E8F6D7 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -78-D6-D6 (hex) eero inc. -78D6D6 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +38-9B-73 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED +389B73 (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 + +28-BB-B2 (hex) Infinix mobility limited +28BBB2 (base 16) Infinix mobility limited + RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG + HongKong HongKong 999077 + HK + +2C-AF-C4 (hex) Private +2CAFC4 (base 16) Private + +14-A4-54 (hex) Mist Systems, Inc. +14A454 (base 16) Mist Systems, Inc. + 1601 South De Anza Blvd, Suite 248 + Cupertino CA 95014 US -D4-3F-32 (hex) eero inc. -D43F32 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +04-39-CB (hex) Qingdao HaierTechnology Co.,Ltd +0439CB (base 16) Qingdao HaierTechnology Co.,Ltd + Building C01,Haier Information Park,No.1 Haier Road + Qingdao 266101 + CN + +68-15-79 (hex) BrosTrend Technology LLC +681579 (base 16) BrosTrend Technology LLC + 8 The Green, Suite A + Dover DE 19901 + US + +FC-DB-21 (hex) SAMSARA NETWORKS INC +FCDB21 (base 16) SAMSARA NETWORKS INC + 1 De Haro St + San Francisco CA 94103 + US + +DC-83-BF (hex) Seiko Epson Corporation +DC83BF (base 16) Seiko Epson Corporation + 2070 Kotobuki Koaka + Matsumoto-shi Nagano-ken 399-8702 + JP + +E4-F4-C6 (hex) NETGEAR +E4F4C6 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -A0-8E-24 (hex) eero inc. -A08E24 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +00-09-5B (hex) NETGEAR +00095B (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -74-42-7F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -74427F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -1C-ED-6F (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -1CED6F (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE - -48-5D-35 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -485D35 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE +00-0F-B5 (hex) NETGEAR +000FB5 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 + US -B4-20-46 (hex) eero inc. -B42046 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +00-22-3F (hex) NETGEAR +00223F (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -C0-36-53 (hex) eero inc. -C03653 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +00-26-F2 (hex) NETGEAR +0026F2 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -D4-05-DE (hex) eero inc. -D405DE (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +04-A1-51 (hex) NETGEAR +04A151 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -9C-0B-05 (hex) eero inc. -9C0B05 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +28-C6-8E (hex) NETGEAR +28C68E (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -7C-49-CF (hex) eero inc. -7C49CF (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +A4-2B-8C (hex) NETGEAR +A42B8C (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -24-2D-6C (hex) eero inc. -242D6C (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +80-37-73 (hex) NETGEAR +803773 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -30-3A-4A (hex) eero inc. -303A4A (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +0C-71-65 (hex) Motorola Mobility LLC, a Lenovo Company +0C7165 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -DC-69-B5 (hex) eero inc. -DC69B5 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +C0-3F-0E (hex) NETGEAR +C03F0E (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -B4-FC-7D (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -B4FC7D (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE +3C-EF-A5 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. +3CEFA5 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. + B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China + Nanning Guangxi 530007 + CN -98-A9-65 (hex) AVM Audiovisuelles Marketing und Computersysteme GmbH -98A965 (base 16) AVM Audiovisuelles Marketing und Computersysteme GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE +04-17-4C (hex) Nanjing SCIYON Wisdom Technology Group Co.,Ltd. +04174C (base 16) Nanjing SCIYON Wisdom Technology Group Co.,Ltd. + No. 1266 Qingshuiting East Road, Jiangning District Nanjing + Nanjing 211800 + CN -5C-A5-BC (hex) eero inc. -5CA5BC (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +CC-03-3D (hex) Beijing Xiaomi Mobile Software Co., Ltd +CC033D (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 + CN + +50-31-23 (hex) FN-LINK TECHNOLOGY Ltd. +503123 (base 16) FN-LINK TECHNOLOGY Ltd. + No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China + Changsha Hunan 410329 + CN + +E0-C2-50 (hex) NETGEAR +E0C250 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -A8-B0-88 (hex) eero inc. -A8B088 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +C8-10-2F (hex) NETGEAR +C8102F (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -88-67-46 (hex) eero inc. -886746 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +10-DA-43 (hex) NETGEAR +10DA43 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -24-F3-E3 (hex) eero inc. -24F3E3 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +A0-40-A0 (hex) NETGEAR +A040A0 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -E4-19-7F (hex) eero inc. -E4197F (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +08-36-C9 (hex) NETGEAR +0836C9 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -FC-3D-73 (hex) eero inc. -FC3D73 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +6C-CD-D6 (hex) NETGEAR +6CCDD6 (base 16) NETGEAR + 3553 N. First Street + San Jose CA 95134 US -14-79-F3 (hex) China Mobile Group Device Co.,Ltd. -1479F3 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +30-CB-89 (hex) OnLogic Inc +30CB89 (base 16) OnLogic Inc + 435 Community Drive + South Burlington VT 05403 + US -3C-57-4F (hex) China Mobile Group Device Co.,Ltd. -3C574F (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +E4-8F-09 (hex) ithinx GmbH +E48F09 (base 16) ithinx GmbH + Am Kabellager 5-7 + Koeln / Cologne 51063 + DE -E4-C0-CC (hex) China Mobile Group Device Co.,Ltd. -E4C0CC (base 16) China Mobile Group Device Co.,Ltd. +BC-D9-FB (hex) China Mobile Group Device Co.,Ltd. +BCD9FB (base 16) China Mobile Group Device Co.,Ltd. 32 Xuanwumen West Street,Xicheng District Beijing 100053 CN -C0-16-92 (hex) China Mobile Group Device Co.,Ltd. -C01692 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +40-0A-E7 (hex) BSH Hausgeräte GmbH +400AE7 (base 16) BSH Hausgeräte GmbH + Im Gewerbepark B35 + Regensburg Bayern 93059 + DE -00-E2-2C (hex) China Mobile Group Device Co.,Ltd. -00E22C (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +D4-92-5E (hex) Vantiva Technologies Belgium +D4925E (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE -E0-E0-C2 (hex) China Mobile Group Device Co.,Ltd. -E0E0C2 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +10-13-31 (hex) Vantiva Technologies Belgium +101331 (base 16) Vantiva Technologies Belgium + Prins Boudewijnlaan 47 + Edegem - Belgium B-2650 + BE -78-2E-56 (hex) China Mobile Group Device Co.,Ltd. -782E56 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +20-0E-0F (hex) Panasonic Marketing Middle East & Africa FZE +200E0F (base 16) Panasonic Marketing Middle East & Africa FZE + P.O Box 17985 Jebel Ali + Dubai Dubai 17985 + AE -00-CF-C0 (hex) China Mobile Group Device Co.,Ltd. -00CFC0 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +E8-CB-F5 (hex) Ezurio, LLC +E8CBF5 (base 16) Ezurio, LLC + 3F.-1, No.145, Xianzheng 9th Rd., + Zhubei 30251 + TW -40-62-EA (hex) China Mobile Group Device Co.,Ltd. -4062EA (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +D8-03-1A (hex) Ezurio, LLC +D8031A (base 16) Ezurio, LLC + 3F.-1, No.145, Xianzheng 9th Rd., + Zhubei 30251 + TW -E0-45-6D (hex) China Mobile Group Device Co.,Ltd. -E0456D (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +18-C2-93 (hex) Ezurio, LLC +18C293 (base 16) Ezurio, LLC + 3F.-1, No.145, Xianzheng 9th Rd., + Zhubei 30251 + TW -50-8C-F5 (hex) China Mobile Group Device Co.,Ltd. -508CF5 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +88-F9-C0 (hex) KTS Kommunikationstechnik und Systeme GmbH +88F9C0 (base 16) KTS Kommunikationstechnik und Systeme GmbH + Schlossstrasse 123 + Moenchengladbach NRW 41238 + DE -5C-75-C6 (hex) China Mobile Group Device Co.,Ltd. -5C75C6 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +14-5E-BC (hex) HUAWEI TECHNOLOGIES CO.,LTD +145EBC (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -24-12-81 (hex) China Mobile Group Device Co.,Ltd. -241281 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +10-88-D3 (hex) HUAWEI TECHNOLOGIES CO.,LTD +1088D3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -64-C5-82 (hex) China Mobile Group Device Co.,Ltd. -64C582 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +AC-A7-F1 (hex) TP-Link Systems Inc. +ACA7F1 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 + US -44-8E-EC (hex) China Mobile Group Device Co.,Ltd. -448EEC (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 - CN +E4-01-77 (hex) SafeOwl, Inc. +E40177 (base 16) SafeOwl, Inc. + 8350 N. Central Expwy #1150 + Dallas TX 75206 + US -94-6D-AE (hex) Mellanox Technologies, Inc. -946DAE (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 +28-1D-AA (hex) ASTI India Private Limited +281DAA (base 16) ASTI India Private Limited + Plot No. 75, Ukardi,Japanese Industrial Zone,Ukardi, Taluka-Mandal,Ahmedabad + Ahmedabad Gujarat 382120 + IN + +C0-18-8C (hex) Altus Sistemas de Automação S.A. +C0188C (base 16) Altus Sistemas de Automação S.A. + Av. Theodomiro Porto da Fonseca, 3101 - lote 01 - Cristo Rei + São Leopoldo Rio Grande do Sul 93022-715 + BR + +90-7A-BE (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED +907ABE (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 + +FC-88-27 (hex) Apple, Inc. +FC8827 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -FC-6A-1C (hex) Mellanox Technologies, Inc. -FC6A1C (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 +04-34-CF (hex) Apple, Inc. +0434CF (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -A0-88-C2 (hex) Mellanox Technologies, Inc. -A088C2 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 +60-DE-18 (hex) Apple, Inc. +60DE18 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -E0-9D-73 (hex) Mellanox Technologies, Inc. -E09D73 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 +30-EC-A3 (hex) Alfatron Electronics INC +30ECA3 (base 16) Alfatron Electronics INC + 6518 Old Wake Forest Road STE A + Raleigh NC 27616 US -50-00-E6 (hex) Mellanox Technologies, Inc. -5000E6 (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 +40-38-02 (hex) Silicon Laboratories +403802 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 US -50-CF-56 (hex) China Mobile Group Device Co.,Ltd. -50CF56 (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +00-89-C9 (hex) Extreme Networks Headquarters +0089C9 (base 16) Extreme Networks Headquarters + 2121 RDU Center Drive + Morrisville 27560 + US + +10-BC-36 (hex) Huawei Device Co., Ltd. +10BC36 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -C8-24-78 (hex) Edifier International -C82478 (base 16) Edifier International - Suit 2207, 22nd floor, Tower II, Lippo centre, 89 Queensway - Hong Kong 070 +B4-F4-9B (hex) Huawei Device Co., Ltd. +B4F49B (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -D4-A0-FB (hex) IEEE Registration Authority -D4A0FB (base 16) IEEE Registration Authority +14-D6-7C (hex) Uncord Technologies Private Limited +14D67C (base 16) Uncord Technologies Private Limited + 101, Corporate Arena, Sitaram Patkar Road, Goregaon West + Mumbai Maharashtra 400104 + IN + +80-F1-A8 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +80F1A8 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + 601,Building B2,No.162,Science Avenue,Science City,Guangzhou High-tech Industrial Development Zone,Guangdong Province,China + Guangzhou Guangdong 510663 + CN + +74-24-35 (hex) Huawei Device Co., Ltd. +742435 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +0C-0E-CB (hex) Huawei Device Co., Ltd. +0C0ECB (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +E8-80-E7 (hex) Huawei Device Co., Ltd. +E880E7 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 + CN + +70-A8-A5 (hex) Microsoft Corporation +70A8A5 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 + US + +5C-5C-75 (hex) IEEE Registration Authority +5C5C75 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -E0-42-6D (hex) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD -E0426D (base 16) GUANGDONG OPPO MOBILE TELECOMMUNICATIONS CORP.,LTD - NO.18 HAIBIN ROAD, - DONG GUAN GUANG DONG 523860 - CN +A4-F4-CA (hex) Private +A4F4CA (base 16) Private -F8-F2-95 (hex) Annapurna labs -F8F295 (base 16) Annapurna labs - Matam Scientific Industries Center, Building 8.2 - Mail box 15123 Haifa 3508409 - IL +F0-16-53 (hex) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. +F01653 (base 16) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. + 309, 3th Floor, No.16, Yun Ding North Road, Huli District + xiamen Fujian 361015 + CN -80-03-0D (hex) CANON INC. -80030D (base 16) CANON INC. - 30-2 Shimomaruko 3-chome, - Ohta-ku Tokyo 146-8501 - JP +B0-61-A9 (hex) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. +B061A9 (base 16) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. + 309, 3th Floor, No.16, Yun Ding North Road, Huli District + xiamen Fujian 361015 + CN -18-C1-E2 (hex) Qolsys Inc. -18C1E2 (base 16) Qolsys Inc. - 1919 S Bascom Ave Suit 600 - Campbell CA 95008 +6C-4E-B0 (hex) Castelion Corporation +6C4EB0 (base 16) Castelion Corporation + 19951 Mariner Ave + Torrance CA 90503 US -F8-43-EF (hex) Xiaomi Communications Co Ltd -F843EF (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +68-B5-E3 (hex) HUAWEI TECHNOLOGIES CO.,LTD +68B5E3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -A4-FF-9F (hex) Xiaomi Communications Co Ltd -A4FF9F (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +C4-6D-D1 (hex) HUAWEI TECHNOLOGIES CO.,LTD +C46DD1 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -20-25-CC (hex) Xiaomi Communications Co Ltd -2025CC (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +34-A1-37 (hex) HUAWEI TECHNOLOGIES CO.,LTD +34A137 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -B0-F3-E9 (hex) PATEO CONNECT (Xiamen) Co., Ltd. -B0F3E9 (base 16) PATEO CONNECT (Xiamen) Co., Ltd. - No. 11-29, Fangyang West Road, Xiang’an District - Xiamen fujian 361115 +F8-91-F5 (hex) Dingtian Technologies Co., Ltd +F891F5 (base 16) Dingtian Technologies Co., Ltd + Rm.3306, Building6, Runyueshan, No.33 Huangzhukeng Rd.,Biling Street,Pingshan District + Shenzhen Guangdong 518100 CN -30-4A-C4 (hex) Barrot Technology Co.,LTD -304AC4 (base 16) Barrot Technology Co.,LTD - A1009, Block A, Jia Hua Building, No.9 Shangdisanjie St, Haidian District, - beijing beijing 100000 +4C-43-F6 (hex) SZ DJI TECHNOLOGY CO.,LTD +4C43F6 (base 16) SZ DJI TECHNOLOGY CO.,LTD + DJI Sky City, No55 Xianyuan Road, Nanshan District + Shenzhen Guangdong 518057 CN -64-75-20 (hex) zte corporation -647520 (base 16) zte corporation +7C-A5-3E (hex) Motorola Mobility LLC, a Lenovo Company +7CA53E (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 + US + +D8-31-39 (hex) zte corporation +D83139 (base 16) zte corporation 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China shenzhen guangdong 518057 CN -00-C8-4E (hex) Hewlett Packard Enterprise -00C84E (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 +A0-59-11 (hex) Cisco Meraki +A05911 (base 16) Cisco Meraki + 500 Terry A. Francois Blvd + San Francisco 94158 US -9C-13-9E (hex) Espressif Inc. -9C139E (base 16) Espressif Inc. +54-7A-F4 (hex) Bouffalo Lab (Nanjing) Co., Ltd. +547AF4 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. + 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China + Nanjing Jiangsu 211800 + CN + +48-9D-31 (hex) Espressif Inc. +489D31 (base 16) Espressif Inc. Room 204, Building 2, 690 Bibo Rd, Pudong New Area Shanghai Shanghai 201203 CN -84-31-A8 (hex) Funshion Online Technologies Co.,Ltd -8431A8 (base 16) Funshion Online Technologies Co.,Ltd - 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing - Beijing 100029 - CN +14-E2-2A (hex) Cisco Systems, Inc +14E22A (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -98-3F-66 (hex) Funshion Online Technologies Co.,Ltd -983F66 (base 16) Funshion Online Technologies Co.,Ltd - 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing - Beijing 100029 +70-97-51 (hex) Beijing Xiaomi Mobile Software Co., Ltd +709751 (base 16) Beijing Xiaomi Mobile Software Co., Ltd + The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District + Beijing Beijing 100085 CN -40-A7-86 (hex) TECNO MOBILE LIMITED -40A786 (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 - Hong Kong Hong Kong 999077 - HK +5C-D3-3D (hex) Samsung Electronics Co.,Ltd +5CD33D (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -D4-7A-EC (hex) Funshion Online Technologies Co.,Ltd -D47AEC (base 16) Funshion Online Technologies Co.,Ltd - 2101, Floor 1-2, Building 9, Anzhen Xili District 3, Chaoyang District, Beijing - Beijing 100029 - CN +AC-DE-01 (hex) Ruckus Wireless +ACDE01 (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US -88-DA-36 (hex) Calix Inc. -88DA36 (base 16) Calix Inc. - 2777 Orchard Pkwy - San Jose CA 95131 +C0-B5-50 (hex) Broadcom Limited +C0B550 (base 16) Broadcom Limited + 15191 Alton Parkway + Irvine CA 92618 US -40-10-ED (hex) G.Tech Technology Ltd. -4010ED (base 16) G.Tech Technology Ltd. - No.8,Jinyuan 1st Road,Tangjiawan Town, High-tech Zone - Zhuhai Guangdong 519085 - CN +90-F8-61 (hex) u-blox AG +90F861 (base 16) u-blox AG + Zuercherstrasse, 68 + Thalwil Switzerland CH-8800 + CH -EC-10-55 (hex) Beijing Xiaomi Electronics Co.,Ltd -EC1055 (base 16) Beijing Xiaomi Electronics Co.,Ltd - Xiaomi Campus - Beijing Beijing 100085 - CN +58-AD-08 (hex) IEEE Registration Authority +58AD08 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 + US -20-6E-F1 (hex) Espressif Inc. -206EF1 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +58-BD-35 (hex) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD +58BD35 (base 16) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD + Room 211-5, Building 1, No. 290 Wankang Road, Minhang District + Shanghai Shanghai 201100 CN -98-17-1A (hex) Beijing Xiaomi Mobile Software Co., Ltd -98171A (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN +80-99-9B (hex) Murata Manufacturing Co., Ltd. +80999B (base 16) Murata Manufacturing Co., Ltd. + 1-10-1, Higashikotari + Nagaokakyo-shi Kyoto 617-8555 + JP -2C-DC-C1 (hex) EM Microelectronic -2CDCC1 (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +00-1E-AE (hex) AUMOVIO Systems, Inc. +001EAE (base 16) AUMOVIO Systems, Inc. + 21440 West Lake Cook Road + Deer Park IL 60010 + US -A4-97-00 (hex) Cisco Systems, Inc -A49700 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +D4-C1-A8 (hex) KYKXCOM Co., Ltd. +D4C1A8 (base 16) KYKXCOM Co., Ltd. + Building 2, No.8, Yuanhua Road, Xianlin UniversityTown, Xianlin Subdistrict, Qixia District + Nanjing Jiangsu 210033 + CN + +B8-58-FF (hex) Arista Networks +B858FF (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 US -94-4F-DB (hex) Nokia -944FDB (base 16) Nokia - 600 March Road - Kanata Ontario K2K 2E6 - CA +40-82-56 (hex) AUMOVIO Germany GmbH +408256 (base 16) AUMOVIO Germany GmbH + VDO-Strasse 1 + Babenhausen Garmany 64832 + DE -D8-53-AD (hex) Cisco Meraki -D853AD (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 +18-F7-F6 (hex) Ericsson AB +18F7F6 (base 16) Ericsson AB + Torshamnsgatan 36 + Stockholm SE-164 80 + SE + +9C-33-12 (hex) Treon Oy +9C3312 (base 16) Treon Oy + Visiokatu 1 + Tampere 33720 + FI + +44-45-BA (hex) Edgecore Americas Networking Corporation +4445BA (base 16) Edgecore Americas Networking Corporation + 20 Mason + Irvine CA 92618 US -30-F8-56 (hex) Extreme Networks Headquarters -30F856 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville 27560 +14-DD-48 (hex) Shield AI +14DD48 (base 16) Shield AI + 600 W. BroadwaySuite 600 + San Diego CA 92101 US -80-40-05 (hex) Guangdong COROS Sports Technology Co.,Ltd -804005 (base 16) Guangdong COROS Sports Technology Co.,Ltd - Room 601 & 701, Bld. 2, No.2, Science and Technology 9 Rd, Songshan Lake Hi-Tech Zone, Dongguan 523808, Guandong, China - Dongguan Guangdong 523808 - CN +D8-99-99 (hex) TECNO MOBILE LIMITED +D89999 (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 + Hong Kong Hong Kong 999077 + HK -68-A5-93 (hex) Apple, Inc. -68A593 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +84-C7-E2 (hex) VusionGroup +84C7E2 (base 16) VusionGroup + Kalsdorfer Straße 12 + Fernitz-Mellach Steiermark 8072 + AT + +40-E7-62 (hex) Calix Inc. +40E762 (base 16) Calix Inc. + 2777 Orchard Pkwy + San Jose CA 95131 US -A8-2C-89 (hex) Apple, Inc. -A82C89 (base 16) Apple, Inc. +68-1A-47 (hex) Apple, Inc. +681A47 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -80-12-42 (hex) Apple, Inc. -801242 (base 16) Apple, Inc. +28-49-E9 (hex) Apple, Inc. +2849E9 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -B8-01-1F (hex) Apple, Inc. -B8011F (base 16) Apple, Inc. +78-96-0D (hex) Apple, Inc. +78960D (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -14-14-16 (hex) Hui Zhou Gaoshengda Technology Co.,LTD -141416 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD - No.2,Jin-da Road,Huinan Industrial Park - Hui Zhou Guangdong 516025 - CN - -B0-25-AA (hex) AIstone Global Limited -B025AA (base 16) AIstone Global Limited - 29/F. , One Exchange Square 8 - Connaught Place Centa Hong Kong 999077 - CN - -DC-93-96 (hex) Apple, Inc. -DC9396 (base 16) Apple, Inc. +80-1D-39 (hex) Apple, Inc. +801D39 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -18-E6-71 (hex) Apple, Inc. -18E671 (base 16) Apple, Inc. +CC-72-2A (hex) Apple, Inc. +CC722A (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -CC-EA-27 (hex) GE Appliances -CCEA27 (base 16) GE Appliances - 4000 Buechel Bank Road - Louisville KY 40225 - US - -8C-3D-16 (hex) Shenzhen Four Seas Global Link Network Technology Co.,Ltd -8C3D16 (base 16) Shenzhen Four Seas Global Link Network Technology Co.,Ltd - 9/F, Block H, South China Digital Valley, No.1 South China Road, Longhua District, Shenzhen ,China - Shenzhen 518000 +AC-40-1E (hex) vivo Mobile Communication Co., Ltd. +AC401E (base 16) vivo Mobile Communication Co., Ltd. + No.1, vivo Road, Chang'an + Dongguan Guangdong 523860 CN -48-F6-EE (hex) Espressif Inc. -48F6EE (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +34-17-DD (hex) Sercomm France Sarl +3417DD (base 16) Sercomm France Sarl + 2/4 Rue Maurice Hartmann 92370 Issy Les Moulineaux France + Moulineaux 92370 + FR -7C-31-FA (hex) Silicon Laboratories -7C31FA (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 +60-15-9F (hex) IEEE Registration Authority +60159F (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -C0-88-40 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. -C08840 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. - Midea Global Innovation Center,Beijiao Town,Shunde - Foshan Guangdong 528311 - CN +74-E6-C7 (hex) LUXSHARE-ICT Co., Ltd. +74E6C7 (base 16) LUXSHARE-ICT Co., Ltd. + 1F, No. 22, Lane 35, Jihu Road, Neihu district + Taipei City Taiwan 114754 + TW -D0-C6-7F (hex) HUAWEI TECHNOLOGIES CO.,LTD -D0C67F (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +84-16-23 (hex) zte corporation +841623 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 CN -98-F3-F6 (hex) HUAWEI TECHNOLOGIES CO.,LTD -98F3F6 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +D0-F8-15 (hex) HUAWEI TECHNOLOGIES CO.,LTD +D0F815 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -A0-39-F9 (hex) Sagemcom Broadband SAS -A039F9 (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 - FR - -B4-89-31 (hex) Silicon Laboratories -B48931 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 +64-D4-F0 (hex) NETVUE,INC. +64D4F0 (base 16) NETVUE,INC. + 1055 East Colorado Boulevard 5th Floor + Pasadena CA 91106 US -10-5E-AE (hex) New H3C Technologies Co., Ltd -105EAE (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN +14-AE-E0 (hex) ETHERNEXION NETWORKS PTE. LTD. +14AEE0 (base 16) ETHERNEXION NETWORKS PTE. LTD. + 7500A BEACH ROAD#04-307 THE PLAZA + SINGAPORE SINGAPORE 199591 + SG -4C-AD-DF (hex) Công ty Cổ phần Thiết bị Công nghiệp GEIC -4CADDF (base 16) Công ty Cổ phần Thiết bị Công nghiệp GEIC - 52 Lê Đại Hành, phường Lê Đại Hành, quận Hai Bà Trưng - Thành phố Hà Nội 000084 - VN +04-5E-0A (hex) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED +045E0A (base 16) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED + Room 03, 23/F, Unit B Building, No 9, Shenzhen Bay Eco -Technology Park, Yuehai Street, Nanshan District, Shenzhen, China + Shenzhen 518000 + CN -64-CD-C2 (hex) Amazon Technologies Inc. -64CDC2 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US +A8-6A-CB (hex) EVAR +A86ACB (base 16) EVAR + 42, Changeop-ro, Sujeong-gu, Seongnam-si, Gyeonggi-do, Republic of Korea + Seoul Gyunggi-do 13449 + KR -4C-60-AD (hex) Amazon Technologies Inc. -4C60AD (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 +58-D8-12 (hex) TP-Link Systems Inc. +58D812 (base 16) TP-Link Systems Inc. + 10 Mauchly + Irvine CA 92618 US -08-EB-21 (hex) Intel Corporate -08EB21 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY - -3C-A0-70 (hex) Blink by Amazon -3CA070 (base 16) Blink by Amazon - 100 Riverpark Drive - North Reading MA 01864 +70-79-2D (hex) Mellanox Technologies, Inc. +70792D (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -E8-C9-13 (hex) Samsung Electronics Co.,Ltd -E8C913 (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -24-F4-0A (hex) Samsung Electronics Co.,Ltd -24F40A (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR - -58-E4-EB (hex) FN-LINK TECHNOLOGY Ltd. -58E4EB (base 16) FN-LINK TECHNOLOGY Ltd. - No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China - Changsha Hunan 410329 +A4-43-1B (hex) Dreamtek Intelligent Technology Co., Ltd +A4431B (base 16) Dreamtek Intelligent Technology Co., Ltd + Room 508, Building A2, Area one of Zhongan Chuanggu Science Park, No. 900 of Wangjiang West Road, High-tech Zone, Hefei, Anhui, China + Shanghai 230000 CN -78-C1-1D (hex) Samsung Electronics Co.,Ltd -78C11D (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +B8-84-11 (hex) Shenzhen Shokz Co., Ltd. +B88411 (base 16) Shenzhen Shokz Co., Ltd. + Baoan District Shiyan street Shancheng Industrial zone 26 building + Shenzhen Guangdong 518108 + CN -4C-A9-54 (hex) Intel Corporate -4CA954 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +94-6A-7C (hex) OnePlus Technology (Shenzhen) Co., Ltd +946A7C (base 16) OnePlus Technology (Shenzhen) Co., Ltd + 18C02, 18C03, 18C04 ,18C05,TAIRAN BUILDING, + Shenzhen Guangdong 518000 + CN -14-C2-4D (hex) ATW TECHNOLOGY, INC. -14C24D (base 16) ATW TECHNOLOGY, INC. - 1F, No.236 Ba’ai Street, Shulin District - New Taipei City 23845 - TW +20-9B-A9 (hex) Espressif Inc. +209BA9 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN -14-05-89 (hex) Motorola Mobility LLC, a Lenovo Company -140589 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +0C-88-2F (hex) Frog Innovations Limited +0C882F (base 16) Frog Innovations Limited + C23, Sector 80, Phase-II + Noida Uttar Pradesh 201305 + IN -98-3A-1F (hex) Google, Inc. -983A1F (base 16) Google, Inc. - 1600 Amphitheatre Parkway - Mountain View CA 94043 +F0-4F-E0 (hex) Vizio, Inc +F04FE0 (base 16) Vizio, Inc + 39 Tesla + Irvine CA 92618 US -F8-47-E3 (hex) Shenzhen Skyworth Digital Technology CO., Ltd -F847E3 (base 16) Shenzhen Skyworth Digital Technology CO., Ltd - 4F,Block A, Skyworth?Building, - Shenzhen Guangdong 518057 +2C-63-A1 (hex) Huawei Device Co., Ltd. +2C63A1 (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -08-92-72 (hex) Espressif Inc. -089272 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +50-0F-C6 (hex) solum +500FC6 (base 16) solum + 2354, Yonggu-daero, Giheung-gu + Yongin-si Gyeonggi-do 16921 + KR -B0-6B-11 (hex) Hui Zhou Gaoshengda Technology Co.,LTD -B06B11 (base 16) Hui Zhou Gaoshengda Technology Co.,LTD - No.2,Jin-da Road,Huinan Industrial Park - Hui Zhou Guangdong 516025 +04-22-E7 (hex) Fiberhome Telecommunication Technologies Co.,LTD +0422E7 (base 16) Fiberhome Telecommunication Technologies Co.,LTD + No.5 DongXin Road + Wuhan Hubei 430074 CN -2C-0D-CF (hex) Xiaomi Communications Co Ltd -2C0DCF (base 16) Xiaomi Communications Co Ltd - #019, 9th Floor, Building 6, 33 Xi'erqi Middle Road - Beijing Haidian District 100085 +2C-E1-87 (hex) New H3C Technologies Co., Ltd +2CE187 (base 16) New H3C Technologies Co., Ltd + 466 Changhe Road, Binjiang District + Hangzhou Zhejiang 310052 CN -AC-10-65 (hex) KT Micro, Inc. -AC1065 (base 16) KT Micro, Inc. - Building 76, National Cybersecurity Industry Park, Beiwucun Road 23, Haidian District, Beijing - Beijing 100195 +14-2E-43 (hex) Hisense broadband multimedia technology Co.,Ltd +142E43 (base 16) Hisense broadband multimedia technology Co.,Ltd + Song ling Road 399 + Qingdao 266000 CN -D4-FF-26 (hex) OHSUNG -D4FF26 (base 16) OHSUNG +48-92-C1 (hex) OHSUNG +4892C1 (base 16) OHSUNG 335-4,SANHODAERO,GUMI,GYEONG BUK,KOREA GUMI GYEONG BUK 730-030 KR -00-86-21 (hex) Amazon Technologies Inc. -008621 (base 16) Amazon Technologies Inc. - P.O Box 8102 - Reno NV 89507 - US - -80-6A-34 (hex) Bouffalo Lab (Nanjing) Co., Ltd. -806A34 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. - 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China - Nanjing Jiangsu 211800 - CN - -54-DD-21 (hex) Huawei Device Co., Ltd. -54DD21 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +4C-30-6A (hex) Nintendo Co.,Ltd +4C306A (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP -A8-24-50 (hex) Beijing Huadianzhongxin Tech.Co.,Ltd -A82450 (base 16) Beijing Huadianzhongxin Tech.Co.,Ltd - Room 318,the 3rd Floorl,Xingtianhaiyuan Building,Xianghuangqi East Rd,Nongda South Rd, Haidian District,Beijing,P.R.C - Bei Jing 100193 - CN +2C-2B-DB (hex) eero inc. +2C2BDB (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US -8C-44-BB (hex) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD -8C44BB (base 16) SHEN ZHEN TENDA TECHNOLOGY CO.,LTD - 6-8 Floor, Tower E3, No. 1001, Zhongshanyuan Road, Nanshan District, Shenzhen,China - Shenzhen 518052 - CN +DC-F3-4C (hex) PT HAN SUNG ELECTORONICS INDONESIA +DCF34C (base 16) PT HAN SUNG ELECTORONICS INDONESIA + JL.PALEM 1 BLOK DS-6 + KAWASAN INDUSTRI BATIK LIPPO CIKARANG, DESA CIBATU, KECAMATAN CIKARANG SELATAN BEKASI JAWA BARAT 17550 + ID -C8-E3-1D (hex) HUAWEI TECHNOLOGIES CO.,LTD -C8E31D (base 16) HUAWEI TECHNOLOGIES CO.,LTD +B4-91-07 (hex) HUAWEI TECHNOLOGIES CO.,LTD +B49107 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -50-AC-B9 (hex) HUAWEI TECHNOLOGIES CO.,LTD -50ACB9 (base 16) HUAWEI TECHNOLOGIES CO.,LTD +FC-EF-D7 (hex) HUAWEI TECHNOLOGIES CO.,LTD +FCEFD7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park Dongguan 523808 CN -E4-56-AC (hex) Silicon Laboratories -E456AC (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US +EC-58-65 (hex) Shenzhen Xinguodu Technology Co., Ltd +EC5865 (base 16) Shenzhen Xinguodu Technology Co., Ltd + 17B, Jinson Building, Tairan 4th Road, Shatou Subdistrict, Futian District + Shenzhen Guangdong 518048 + CN -0C-33-1B (hex) TydenBrooks -0C331B (base 16) TydenBrooks - 2727 Paces Ferry Rd, Building 2, Suite 300 - Atlanta GA 30339 - US +68-09-47 (hex) Espressif Inc. +680947 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN -24-2B-D6 (hex) Ring LLC -242BD6 (base 16) Ring LLC - 1523 26th St - Santa Monica CA 90404 - US +70-AF-09 (hex) Espressif Inc. +70AF09 (base 16) Espressif Inc. + Room 204, Building 2, 690 Bibo Rd, Pudong New Area + Shanghai Shanghai 201203 + CN -00-30-52 (hex) Zhone Technologies, Inc. -003052 (base 16) Zhone Technologies, Inc. - 6120 WINDWARD PARKWAY -STE#100 - ALPHARETTA GA 30005 - US +44-61-DF (hex) Skyquad Electronics & Appliances Pvt. Ltd. +4461DF (base 16) Skyquad Electronics & Appliances Pvt. Ltd. + 12-50/4/A, Adj to Industrial Estate, MedchalR R District, Hyderabad - 501401, Telangana, India. + Hyderabad Telangana 501401 + IN -2C-4C-7D (hex) New H3C Technologies Co., Ltd -2C4C7D (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN +90-47-C2 (hex) Intel Corporate +9047C2 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -E4-6E-8A (hex) BYD Lithium Battery Co., Ltd. -E46E8A (base 16) BYD Lithium Battery Co., Ltd. - No. 3001 Baohe Road, Baolong Industrial City, Longgang Street, Longgang District - Shen Zhen Guang Dong 518100 - CN +48-40-D5 (hex) Intel Corporate +4840D5 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -C8-C8-3F (hex) Texas Instruments -C8C83F (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 +1C-8C-6E (hex) Arista Networks +1C8C6E (base 16) Arista Networks + 5453 Great America Parkway + Santa Clara CA 95054 US -E0-D4-91 (hex) Cisco Systems, Inc -E0D491 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +84-1D-E8 (hex) CJ intelligent technology LTD. +841DE8 (base 16) CJ intelligent technology LTD. + 4F, No. 16, Zhongxin St., Shulin Dist. + New Taipei City 238035 + TW -A4-DC-D5 (hex) Cisco Systems, Inc -A4DCD5 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +C4-84-C0 (hex) Motorola Mobility LLC, a Lenovo Company +C484C0 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -D8-52-FA (hex) Texas Instruments -D852FA (base 16) Texas Instruments +E8-A9-27 (hex) LEAR +E8A927 (base 16) LEAR + Carrer Fuster 54 + Valls Tarragona 43800 + ES + +64-9B-8F (hex) Texas Instruments +649B8F (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -38-E2-C4 (hex) Texas Instruments -38E2C4 (base 16) Texas Instruments +28-9E-1E (hex) Texas Instruments +289E1E (base 16) Texas Instruments 12500 TI Blvd Dallas TX 75243 US -28-57-5D (hex) Apple, Inc. -28575D (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US - -34-10-BE (hex) Apple, Inc. -3410BE (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +CC-C5-30 (hex) AzureWave Technology Inc. +CCC530 (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW -54-91-E1 (hex) Vitalacy Inc. -5491E1 (base 16) Vitalacy Inc. - 11859 Wilshire Blvd #500 - Los Angeles CA 90025 +A4-F8-FF (hex) Ubiquiti Inc +A4F8FF (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 US -F4-33-B7 (hex) Apple, Inc. -F433B7 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +CC-35-D9 (hex) Ubiquiti Inc +CC35D9 (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 US -D4-BE-D7 (hex) Dell Inc. -D4BED7 (base 16) Dell Inc. - One Dell Way - Round Rock TX 78682 +6C-47-80 (hex) IEEE Registration Authority +6C4780 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -D4-2F-4B (hex) Hon Hai Precision Industry Co.,LTD -D42F4B (base 16) Hon Hai Precision Industry Co.,LTD - 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI CITY,23678 , TAIWAN CHINA - TAIPEI 66.Chung Shan RD, TU-CHENG Industrial , district new TAIPEI 33859 - CN - -90-29-62 (hex) Linkpower Microelectronics Co., Ltd. -902962 (base 16) Linkpower Microelectronics Co., Ltd. - 905, B1, 999-8, Gaolang East Road, Wuxi Economic Development Zone, Jiangsu Province - wuxi jiangsu 214131 - CN +50-C3-A2 (hex) nFore Technology Co., Ltd. +50C3A2 (base 16) nFore Technology Co., Ltd. + 5F., No.31, Ln. 258, Ruiguang Rd. Neihu Dist., Taipei City 114, Taiwan + Taipei 114 + TW -84-9D-4B (hex) Shenzhen Boomtech Industrial Corporation -849D4B (base 16) Shenzhen Boomtech Industrial Corporation - 905/906,BuildingA, Huizhi R&D Center. Xixiang,Bao'an District - Shenzhen 518100 - CN +A4-04-50 (hex) nFore Technology Co., Ltd. +A40450 (base 16) nFore Technology Co., Ltd. + 5F, NO 31, Ln 258, Rulguang Rd + Taipei Neihu District 11491 + TW -54-FB-66 (hex) ASRock Incorporation -54FB66 (base 16) ASRock Incorporation - 2F., No.37, Sec. 2, Jhongyang S. Rd., Beitou District, - Taipei 112 +00-17-53 (hex) nFore Technology Co., Ltd. +001753 (base 16) nFore Technology Co., Ltd. + 5F, NO 31, Ln 258, Rulguang Rd + Taipei Neihu District 11491 TW -2C-15-7E (hex) RADIODATA GmbH -2C157E (base 16) RADIODATA GmbH - Newtonstraße 18 - Berlin 12489 - DE +44-10-30 (hex) Google, Inc. +441030 (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 + US -44-F7-9F (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -44F79F (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 - CN +80-C4-29 (hex) Renesas Electronics Operations Services Limited +80C429 (base 16) Renesas Electronics Operations Services Limited + Dukes Meadow, Millboard Raod Bourne End BU + Bourne End BU SL8 5FH + GB -10-C3-4D (hex) SHENZHEN WATER WORLD CO.,LTD. -10C34D (base 16) SHENZHEN WATER WORLD CO.,LTD. - Room 1201, 12F, Block B, Qinghua Information Port No.1, Songpingshan Xindong Road, Songpingshan Community, Xili Subdistrict, Nanshan District, Shenzhen - Shenzhen GuangDong 518000 - CN +00-E0-AD (hex) Brandywine Communications UK Ltd. +00E0AD (base 16) Brandywine Communications UK Ltd. + 20a Westside Centre London Road, Stanway, Colchester + ESSEX England CO3 8PH + GB -88-49-2D (hex) SHENZHEN BILIAN ELECTRONIC CO.,LTD -88492D (base 16) SHENZHEN BILIAN ELECTRONIC CO.,LTD - NO.268, Fuqian Rd, Jutang community, Guanlan Town, Longhua New district - shenzhen guangdong 518000 - CN +58-8C-CF (hex) Silicon Laboratories +588CCF (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -34-E1-D7 (hex) NXP Semiconductors Taiwan Ltd. -34E1D7 (base 16) NXP Semiconductors Taiwan Ltd. - No. 10, Jing 5th Rd., Nanzi Dist., Kaohsiung City 811643, Taiwan - Nanzi Dist. Kaohsiung 811643 - TW +50-71-64 (hex) Cisco Systems, Inc +507164 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 + US -70-A3-A4 (hex) Beijing Guming Communication Technology Co., Ltd. -70A3A4 (base 16) Beijing Guming Communication Technology Co., Ltd. - Room 202-6, 2nd Floor, Building 1, No. 8 Courtyard, Yongchang Middle Road, Beijing Economic and Technological Development Area, Beijing - Beijing Beijing 100176 - CN +00-1C-44 (hex) Electro Voice Dynacord BV +001C44 (base 16) Electro Voice Dynacord BV + Achtseweg Zuid 173 + 5651 GW Eindhoven Eindhoven 5651 + NL -94-EF-97 (hex) Edgecore Americas Networking Corporation -94EF97 (base 16) Edgecore Americas Networking Corporation - 20 Mason - Irvine CA 92618 +60-A1-FE (hex) HPRO +60A1FE (base 16) HPRO + 8500 Balboa Blvd + Northridge CA 91329 US -2C-59-17 (hex) Arcadyan Corporation -2C5917 (base 16) Arcadyan Corporation - No.8, Sec.2, Guangfu Rd. - Hsinchu City Hsinchu 30071 - TW +A4-18-94 (hex) IQSIGHT B.V. +A41894 (base 16) IQSIGHT B.V. + Achtseweg Zuid 173 + Eindhoven 5651 GW + NL -A4-3A-39 (hex) AURORA TECHNOLOGIES CO.,LTD. -A43A39 (base 16) AURORA TECHNOLOGIES CO.,LTD. - ROOM 1006, BLOCK B, QIANHAI ECONOMIC AND TRADE CENTER, CHINA MERCHANTS GROUP, NO.151 WEST FREE TRADE STREET, QIANHAI, - SHENZHEN 518000 +B0-37-31 (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD +B03731 (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD + 19-22# Building, Star-net Science Plaza, Juyuanzhou, + FUZHOU FUJIAN 350002 CN -C0-2E-1D (hex) Nokia Solutions and Networks GmbH & Co. KG -C02E1D (base 16) Nokia Solutions and Networks GmbH & Co. KG - Werinherstrasse 91 - München Bavaria D-81541 +24-99-00 (hex) FRITZ! Technology GmbH +249900 (base 16) FRITZ! Technology GmbH + Alt-Moabit 95 + Berlin Berlin 10559 DE -20-36-D0 (hex) Motorola Mobility LLC, a Lenovo Company -2036D0 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 +18-A0-84 (hex) Apple, Inc. +18A084 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -84-D9-E0 (hex) eero inc. -84D9E0 (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +58-2A-93 (hex) Apple, Inc. +582A93 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -7C-6C-E1 (hex) Shenzhen Honesty Electronics Co.,Ltd. -7C6CE1 (base 16) Shenzhen Honesty Electronics Co.,Ltd. - 5/F,Zone B,Chitat Industrial Park,West Longping Road, Longgang District,Shenzhen City - Shenzhen Guangdong 518172 - CN +64-C9-05 (hex) Apple, Inc. +64C905 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -BC-87-53 (hex) Sera Network Inc. -BC8753 (base 16) Sera Network Inc. - 2F, No.60, 321 Ln., Yangguang St., Neihu Dist., - Taipei Taiwan 114717 - TW +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 -0C-A6-4C (hex) Hangzhou Ezviz Software Co.,Ltd. -0CA64C (base 16) Hangzhou Ezviz Software Co.,Ltd. - 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District - Hangzhou Zhejiang 310051 +6C-40-33 (hex) Beijing Megwang Technology Co., Ltd. +6C4033 (base 16) Beijing Megwang Technology Co., Ltd. + Room 1316, 1st Floor, Building 12, Jianzhong Road, Xisanqi Building Materials City, Haidian District, Beijing, China + Beijing 100096 CN -34-C6-DD (hex) Hangzhou Ezviz Software Co.,Ltd. -34C6DD (base 16) Hangzhou Ezviz Software Co.,Ltd. - 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District - Hangzhou Zhejiang 310051 - CN +E8-FC-5F (hex) Ruckus Wireless +E8FC5F (base 16) Ruckus Wireless + 350 West Java Drive + Sunnyvale CA 94089 + US -AC-1C-26 (hex) Hangzhou Ezviz Software Co.,Ltd. -AC1C26 (base 16) Hangzhou Ezviz Software Co.,Ltd. - 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District - Hangzhou Zhejiang 310051 +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 -64-24-4D (hex) Hangzhou Ezviz Software Co.,Ltd. -64244D (base 16) Hangzhou Ezviz Software Co.,Ltd. - 17th Floor, Building D, No.188 Qizhi East Stree, Xixing Subdistrict, Binjiang District - Hangzhou Zhejiang 310051 +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 -68-4A-6E (hex) Quectel Wireless Solutions Co.,Ltd. -684A6E (base 16) Quectel Wireless Solutions Co.,Ltd. - 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District - Shanghai 200233 - CN +3C-BF-D7 (hex) Apple, Inc. +3CBFD7 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -50-FA-CB (hex) IEEE Registration Authority -50FACB (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +88-7E-9B (hex) Apple, Inc. +887E9B (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -68-1D-4C (hex) Kontron eSystems GmbH -681D4C (base 16) Kontron eSystems GmbH - Bahnhofstraße 100 - Wendlingen 73240 - DE +54-2A-43 (hex) Apple, Inc. +542A43 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -B8-52-13 (hex) zte corporation -B85213 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +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 -2C-27-E4 (hex) Luxshare Precision Industry (Xuancheng) Co.,Ltd. -2C27E4 (base 16) Luxshare Precision Industry (Xuancheng) Co.,Ltd. - Address: No.5 Baishou Road, Xuancheng High-Tech Industrial Development Zone - Xuancheng Anhui 242000 +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 -9C-6D-92 (hex) Shanghai Kanghai Infomation System CO.,LTD -9C6D92 (base 16) Shanghai Kanghai Infomation System CO.,LTD - Room 207, Building 1, 6055 Songze Avenue , Qingpu District, Shanghai - Shanghai 201706 +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 -90-70-69 (hex) Espressif Inc. -907069 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +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 -9C-E4-50 (hex) IEEE Registration Authority -9CE450 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +E8-23-FB (hex) Redder +E823FB (base 16) Redder + Via B. Ferracina, 2 + Camisano Vicentino VI 36043 + IT -E8-8F-16 (hex) Skullcandy -E88F16 (base 16) Skullcandy - 1441 Ute blvd. - Park City 84098 +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 + +40-BA-09 (hex) Dell Inc. +40BA09 (base 16) Dell Inc. + One Dell Way + Round Rock TX 78682 US -18-41-FE (hex) KATIM L.L.C -1841FE (base 16) KATIM L.L.C - Elektroniikkatie 8 - Oulu 90590 - FI +00-0C-DE (hex) ABB AG +000CDE (base 16) ABB AG + Eppelheimer Straße 82 + Heidelberg Baden-Württemberg 69123 + DE -00-1B-85 (hex) Everllence -001B85 (base 16) Everllence - Teglholmsgade 41 - Copenhagen 2450 - DK +5C-82-17 (hex) DSE srl +5C8217 (base 16) DSE srl + Via La Valle 51 + San Mauro Torinese TO 10099 + IT -D8-91-1D (hex) Jiangsu Yuwell POCTech Biotechnology Co.,Ltd -D8911D (base 16) Jiangsu Yuwell POCTech Biotechnology Co.,Ltd - No.5 Baisheng Road Development Zone, 212300 Danyang, Jiangsu, PEOPLE’S REPUBLIC OF CHINA - Danyang 212300 +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 -30-07-5C (hex) 43403 -30075C (base 16) 43403 - 8 Floor, Bd B, information port, Langshan RD, Nanshan district, - Shenzhen Guangdong 518057 +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 -AC-39-3D (hex) eero inc. -AC393D (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 +00-1D-19 (hex) Arcadyan Corporation +001D19 (base 16) Arcadyan Corporation + 4F., No. 9 , Park Avenue II, + Hsinchu 300 + TW + +C0-6B-C7 (hex) Gallagher Group Limited +C06BC7 (base 16) Gallagher Group Limited + 181 Kahikatea Drive + Hamilton Waikato 3204 + NZ + +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 + +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 -00-E6-07 (hex) AURCORE TECHNOLOGY INC. -00E607 (base 16) AURCORE TECHNOLOGY INC. - 25691 ATLANTIC OCEAN DRIVE UNIT B10 - LAKE FOREST CA 92630 +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 -B4-04-29 (hex) Guangzhou Shiyuan Electronic Technology Company Limited -B40429 (base 16) Guangzhou Shiyuan Electronic Technology Company Limited - No.6, 4th Yunpu Road, Yunpu industry District - Guangzhou Guangdong 510530 - CN +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 -4C-D7-4A (hex) Vantiva USA LLC -4CD74A (base 16) Vantiva USA LLC - 4855 Peachtree Industrial Blvd, Suite 200 - Norcross GA 30902 - US +7C-4F-B5 (hex) Arcadyan Corporation +7C4FB5 (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW -FC-96-3E (hex) EM Microelectronic -FC963E (base 16) EM Microelectronic - Rue des Sors 3 - Marin-Epagnier Neuchatel 2074 - CH +50-7E-5D (hex) Arcadyan Corporation +507E5D (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II , + Hsinchu 300 + TW -FC-CF-9F (hex) EM Microelectronic -FCCF9F (base 16) EM Microelectronic +00-12-BF (hex) Arcadyan Corporation +0012BF (base 16) Arcadyan Corporation + 4F, No. 9, Park Avenue II + Hsinchu 300 + TW + +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 -D4-25-DE (hex) New H3C Technologies Co., Ltd -D425DE (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN +04-C2-9B (hex) Aura Home, Inc. +04C29B (base 16) Aura Home, Inc. + 148 Lafayette Street, Floor 5 + New York NY 10013 + US -B0-E8-E8 (hex) Silicon Laboratories -B0E8E8 (base 16) Silicon Laboratories +B8-97-34 (hex) Silicon Laboratories +B89734 (base 16) Silicon Laboratories 400 West Cesar Chavez Austin TX 78701 US -64-69-11 (hex) APTIV SERVICES US, LLC -646911 (base 16) APTIV SERVICES US, LLC - 5725 Innovation Drive - Troy MI 48098 - US - -04-D6-88 (hex) CIG SHANGHAI CO LTD -04D688 (base 16) CIG SHANGHAI CO LTD - 5th Floor, Building 8 No 2388 Chenhang Road - SHANGHAI 201114 +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 -78-79-84 (hex) Apple, Inc. -787984 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +80-6D-DE (hex) Beken Corporation +806DDE (base 16) Beken Corporation + Building 41, Capital of Tech Leaders, 1387 Zhangdong Road, Zhangjiang High-Tech Park, Pudong New District + Shanghai 201203 + CN -6C-0B-84 (hex) Universal Global Scientific Industrial., Ltd -6C0B84 (base 16) Universal Global Scientific Industrial., Ltd - 141, Lane 351, TaiPing Road, Sec.1 - Tsao-Tuen Nan-Tou 54261 - TW +68-1D-4C (hex) Kontron eSystems GmbH +681D4C (base 16) Kontron eSystems GmbH + Bahnhofstr. 96 + Wendlingen 73240 + DE -38-7C-76 (hex) Universal Global Scientific Industrial., Ltd -387C76 (base 16) Universal Global Scientific Industrial., Ltd - 141, Lane 351, Taiping Road, Sec.1,Tsao Tuen - Nan-Tou Taiwan 54261 - TW +24-68-00 (hex) Samsung Electronics Co.,Ltd +246800 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -78-67-0E (hex) WNC Corporation -78670E (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +F0-67-B1 (hex) Samsung Electronics Co.,Ltd +F067B1 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -58-E4-03 (hex) WNC Corporation -58E403 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW +90-0A-48 (hex) Samsung Electronics Co.,Ltd +900A48 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -70-EB-A5 (hex) Huawei Device Co., Ltd. -70EBA5 (base 16) Huawei Device Co., Ltd. +EC-B9-A5 (hex) Huawei Device Co., Ltd. +ECB9A5 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -C8-90-F7 (hex) HUAWEI TECHNOLOGIES CO.,LTD -C890F7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +04-80-1A (hex) Huawei Device Co., Ltd. +04801A (base 16) Huawei Device Co., Ltd. + No.2 of Xincheng Road, Songshan Lake Zone + Dongguan Guangdong 523808 CN -F8-6D-CC (hex) WNC Corporation -F86DCC (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - -20-58-43 (hex) WNC Corporation -205843 (base 16) WNC Corporation - No.20,Park Avenue II,Hsinchu Science Park - Hsin-Chu R.O.C. 308 - TW - -30-14-4A (hex) WNC Corporation -30144A (base 16) WNC Corporation - 20 Park Avenue II, Hsin Science Park, Hsinchu 308, Taiwan - HsinChu Taiwan 308 - TW - -F0-40-AF (hex) IEEE Registration Authority -F040AF (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - -E4-7C-1A (hex) mercury corperation -E47C1A (base 16) mercury corperation - 90,gajaeul-ro,seo-gu,incheon - incheon 22830 - KR - -28-B4-46 (hex) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD -28B446 (base 16) SHENZHEN CHUANGWEI-RGB ELECTRONICS CO.,LTD - Unit East Block22-24/F,Skyworth semiconductor design Bldg., Gaoxin Ave.4.S.,Nanshan District,Shenzhen,China - SHENZHEN GUANGDONG 518057 +4C-D7-3A (hex) ShenZhen XinZhongXin Technology Co., Ltd +4CD73A (base 16) ShenZhen XinZhongXin Technology Co., Ltd + Building A2,Donghuan Industrial Zone,No.293,Nanpu Road,Xinqiao Sub-district,Bao'an District,Shenzhen City,Guangdong Province + ShenZhen 518104 CN -EC-3A-56 (hex) AzureWave Technology Inc. -EC3A56 (base 16) AzureWave Technology Inc. - 8F., No. 94, Baozhong Rd. - New Taipei City Taiwan 231 - TW +9C-51-87 (hex) SUNITEC TECHNOLOGY CO.,LIMITED +9C5187 (base 16) SUNITEC TECHNOLOGY CO.,LIMITED + Floor 1-4, building C, Weixlangtal industrial park, no, 725, Dasan Village、Xingfu community, Fucheng Street, Longhua district + Shenzhen 518110 + CN -08-F9-7E (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -08F97E (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 +FC-EB-7B (hex) HUAWEI TECHNOLOGIES CO.,LTD +FCEB7B (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -50-FE-39 (hex) Beijing Xiaomi Mobile Software Co., Ltd -50FE39 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +00-CC-05 (hex) HUAWEI TECHNOLOGIES CO.,LTD +00CC05 (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -28-12-D0 (hex) Motorola Mobility LLC, a Lenovo Company -2812D0 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +E4-CA-5F (hex) Murata Manufacturing Co., Ltd. +E4CA5F (base 16) Murata Manufacturing Co., Ltd. + 1-10-1, Higashikotari + Nagaokakyo-shi Kyoto 617-8555 + JP -18-90-24 (hex) Astera LED Technology GmbH -189024 (base 16) Astera LED Technology GmbH - Schatzbogen 60 - Munich Bayern 81829 - DE +B4-15-84 (hex) Samsung Electronics Co.,Ltd +B41584 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -C8-78-F7 (hex) Cisco Systems, Inc -C878F7 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 +34-DD-CC (hex) Google, Inc. +34DDCC (base 16) Google, Inc. + 1600 Amphitheatre Parkway + Mountain View CA 94043 US -98-5B-76 (hex) Vantiva Connected Home - Orange Belgium -985B76 (base 16) Vantiva Connected Home - Orange Belgium - 4855 Pearchtree Industrial Blvd - Norcross GA 30092 - US +CC-61-46 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED +CC6146 (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 -5C-45-46 (hex) Shenzhen Water World Information Co.,Ltd. -5C4546 (base 16) Shenzhen Water World Information Co.,Ltd. - 1F, Building 3, DexinChang Wisdom Park, No. 23, Heping Road, Qinghua Community, Longhua Subdistrict, Longhua District - Shenzhen Guangdong 518109 - CN +38-87-9C (hex) Ei Electronics +38879C (base 16) Ei Electronics + Unit 40-47 Shannon Industrial Estate + Shannon Co. Clare V14 H020 + IE -04-5F-A6 (hex) Shenzhen SDMC Technology CP,.LTD -045FA6 (base 16) Shenzhen SDMC Technology CP,.LTD - 19/F, Changhong Science &Technology Mansion,No.18, Keji South 12th Road High-tech IndustrialPark Nanshan District,Shenzhen,China - Shenzhen China 518000 - CN +F4-20-4D (hex) Mellanox Technologies, Inc. +F4204D (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 + US -B4-9D-6B (hex) vivo Mobile Communication Co., Ltd. -B49D6B (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 +E8-F6-74 (hex) Jiang Su Fulian Communication Technology Co.,Ltd +E8F674 (base 16) Jiang Su Fulian Communication Technology Co.,Ltd + Yongan Community, the south of Lanling Road, Danyang Development Distinct + zhenjiang jiangsu 212300 CN -F4-E1-FC (hex) Hewlett Packard Enterprise -F4E1FC (base 16) Hewlett Packard Enterprise - 6280 America Center Dr - San Jose CA 95002 - US - -B4-1D-62 (hex) Nokia Shanghai Bell Co., Ltd. -B41D62 (base 16) Nokia Shanghai Bell Co., Ltd. - No.388 Ning Qiao Road,Jin Qiao Pudong Shanghai - Shanghai 201206 +7C-51-84 (hex) Unis Flash Memory Technology(Chengdu)Co.,Ltd. +7C5184 (base 16) Unis Flash Memory Technology(Chengdu)Co.,Ltd. + Room 302, Block B, Ziguang Xinyun, No. 2555 Yizhou Avenue, High-tech Zone, + Chengdu Sichuan 610000 CN -E0-72-56 (hex) Intel Corporate -E07256 (base 16) Intel Corporate +04-1C-6C (hex) Intel Corporate +041C6C (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -F4-1C-26 (hex) AltoBeam Inc. -F41C26 (base 16) AltoBeam Inc. - B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian - Beijing Beijing 100083 - CN +10-9A-BA (hex) Intel Corporate +109ABA (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -84-D1-C1 (hex) Intel Corporate -84D1C1 (base 16) Intel Corporate +AC-05-C7 (hex) Intel Corporate +AC05C7 (base 16) Intel Corporate Lot 8, Jalan Hi-Tech 2/3 Kulim Kedah 09000 MY -78-FD-F1 (hex) Shenzhen Huadian Communication Co., Ltd -78FDF1 (base 16) Shenzhen Huadian Communication Co., Ltd - The fourth floor of the State Micro R&D Building, No. 015, Gaoxin South 1st Road, Nanshan District, Shenzhen - Shenzhen 518000 +78-64-F0 (hex) Beijing Soynetic Co., Ltd +7864F0 (base 16) Beijing Soynetic Co., Ltd + Room 108-60, 1st Floor, Building 4, Yard 1, Kechuang 10th Street Beijing Economic and Technological Development Zone, Beijing + Beijing Beijing 100176 CN -FC-F8-61 (hex) Harman/Becker Automotive Systems GmbH -FCF861 (base 16) Harman/Becker Automotive Systems GmbH - Becker-Göring-Straße 16 - Karlsbad Baden-Württemberg 76307 - DE - -98-0A-4B (hex) Nokia Solutions and Networks GmbH & Co. KG -980A4B (base 16) Nokia Solutions and Networks GmbH & Co. KG - Werinherstrasse 91 - München Bavaria D-81541 - DE - -74-61-D1 (hex) GOIP Global Services Pvt. Ltd. -7461D1 (base 16) GOIP Global Services Pvt. Ltd. - H68, Sector 63, Noida 201301 - Noida Uttar Pradesh 201301 - IN - -58-89-90 (hex) Starkey Labs Inc. -588990 (base 16) Starkey Labs Inc. - 6600 Washington Ave. S. - Eden Prairie MN 55344 - US - -E8-F6-D7 (hex) IEEE Registration Authority -E8F6D7 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US - -38-9B-73 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED -389B73 (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 - -28-BB-B2 (hex) Infinix mobility limited -28BBB2 (base 16) Infinix mobility limited - RMS 05-15, 13A/F SOUTH TOWER WORLD FINANCE CTR HARBOUR CITY 17 CANTON RD TST KLN HONG KONG - HongKong HongKong 999077 - HK - -2C-AF-C4 (hex) Private -2CAFC4 (base 16) Private - -14-A4-54 (hex) Mist Systems, Inc. -14A454 (base 16) Mist Systems, Inc. - 1601 South De Anza Blvd, Suite 248 - Cupertino CA 95014 - US +68-22-9F (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. +68229F (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. + 13/F, Building 1, No.13 Bohua 4th Road, Huangpu District + Guangzhou Guangdong 510663 + CN -04-39-CB (hex) Qingdao HaierTechnology Co.,Ltd -0439CB (base 16) Qingdao HaierTechnology Co.,Ltd - Building C01,Haier Information Park,No.1 Haier Road - Qingdao 266101 +78-EC-B5 (hex) Ruijie Networks Co.,LTD +78ECB5 (base 16) Ruijie Networks Co.,LTD + Building 19,Juyuanzhou Industrial Park, No.618 Jinshan Avenue, Cangshan District + Fuzhou 35000 CN -68-15-79 (hex) BrosTrend Technology LLC -681579 (base 16) BrosTrend Technology LLC - 8 The Green, Suite A - Dover DE 19901 - US +44-EF-26 (hex) Qingdao Intelligent&Precise Electronics Co.,Ltd. +44EF26 (base 16) Qingdao Intelligent&Precise Electronics Co.,Ltd. + No.218 Qianwangang Road + Qingdao Shangdong 266510 + CN -FC-DB-21 (hex) SAMSARA NETWORKS INC -FCDB21 (base 16) SAMSARA NETWORKS INC - 1 De Haro St - San Francisco CA 94103 +68-92-68 (hex) Motorola Mobility LLC, a Lenovo Company +689268 (base 16) Motorola Mobility LLC, a Lenovo Company + 222 West Merchandise Mart Plaza + Chicago IL 60654 US -DC-83-BF (hex) Seiko Epson Corporation -DC83BF (base 16) Seiko Epson Corporation - 2070 Kotobuki Koaka - Matsumoto-shi Nagano-ken 399-8702 - JP - -E4-F4-C6 (hex) NETGEAR -E4F4C6 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +EC-34-E2 (hex) Yasmina Labs Trading FZE +EC34E2 (base 16) Yasmina Labs Trading FZE + LB182702WS08, Jebel Ali Freezone + Dubai Dubai LB182702WS08 + AE -00-09-5B (hex) NETGEAR -00095B (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +14-3E-C2 (hex) Intel Corporate +143EC2 (base 16) Intel Corporate + Lot 8, Jalan Hi-Tech 2/3 + Kulim Kedah 09000 + MY -00-0F-B5 (hex) NETGEAR -000FB5 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +F4-25-3C (hex) eero inc. +F4253C (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 US -00-22-3F (hex) NETGEAR -00223F (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +1C-23-A2 (hex) FRITZ! Technology GmbH +1C23A2 (base 16) FRITZ! Technology GmbH + Alt-Moabit 95 + Berlin Berlin 10559 + DE -00-26-F2 (hex) NETGEAR -0026F2 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +9C-86-2B (hex) MOTOROLA SOLUTIONS MALAYSIA SDN. BHD. +9C862B (base 16) MOTOROLA SOLUTIONS MALAYSIA SDN. BHD. + INNOPLEX, NO. 2A, MEDAN BAYAN LEPAS, BAYAN LEPAS TECHNOPLEX + BAYAN LEPAS PENANG 11900 + MY -04-A1-51 (hex) NETGEAR -04A151 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +E0-D3-F0 (hex) AltoBeam Inc. +E0D3F0 (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 + CN -28-C6-8E (hex) NETGEAR -28C68E (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +10-F1-C7 (hex) Tachyon Networks Inc +10F1C7 (base 16) Tachyon Networks Inc + 4783 W Stoney Brook Ln + Highland UT 84003 US -A4-2B-8C (hex) NETGEAR -A42B8C (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +E4-FF-69 (hex) Holiday Robotics +E4FF69 (base 16) Holiday Robotics + 4F, 70, Nonhyeon-ro 85-gil, Gangnam-gu + Seoul 06234 + KR -80-37-73 (hex) NETGEAR -803773 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +4C-6E-44 (hex) IEEE Registration Authority +4C6E44 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -0C-71-65 (hex) Motorola Mobility LLC, a Lenovo Company -0C7165 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +30-6D-34 (hex) Wu Qi Technologies,Inc. +306D34 (base 16) Wu Qi Technologies,Inc. + 14/F, 107 Middle Road, Xiantao Big Data Valley, Yubei District + Chongqing Chongqing 401120 + CN -C0-3F-0E (hex) NETGEAR -C03F0E (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +B8-9D-E5 (hex) ASIX Electronics Corporation +B89DE5 (base 16) ASIX Electronics Corporation + 4F, No. 8, Hsin Ann Road, Hsinchu Science Park + Hsinchu 30078 + TW -CC-03-3D (hex) Beijing Xiaomi Mobile Software Co., Ltd -CC033D (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 +E4-98-E0 (hex) Tonly Technology Co. Ltd +E498E0 (base 16) Tonly Technology Co. Ltd + Section 37, Zhongkai Hi-Tech Development Zone + Huizhou Guangdong 516006 CN -04-17-4C (hex) Nanjing SCIYON Wisdom Technology Group Co.,Ltd. -04174C (base 16) Nanjing SCIYON Wisdom Technology Group Co.,Ltd. - No. 1266 Qingshuiting East Road, Jiangning District Nanjing - Nanjing 211800 +A8-21-C8 (hex) shenzhen phoenix telecom technology Co.,Ltd. +A821C8 (base 16) shenzhen phoenix telecom technology Co.,Ltd. + Bldg A,Dedi Industrial Park,Jian'an Road,High-tech Development Zone,Fuhai Street,Bao'an District,Shenzhen,Guangdong,China + shenzhen 518103 CN -E0-C2-50 (hex) NETGEAR -E0C250 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US - -C8-10-2F (hex) NETGEAR -C8102F (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +78-BB-5C (hex) Nokia Solutions and Networks India Private Limited +78BB5C (base 16) Nokia Solutions and Networks India Private Limited + Plot 45, Fathima NagarNemilicherry,Chrompet + Chennai Taminadu 600044 + IN -10-DA-43 (hex) NETGEAR -10DA43 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +48-13-89 (hex) Mellanox Technologies, Inc. +481389 (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -A0-40-A0 (hex) NETGEAR -A040A0 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +18-8A-F1 (hex) LEDVANCE, LLC +188AF1 (base 16) LEDVANCE, LLC + 181 Ballardvale StreetSte 203 + Wilmington, MA MA 01887 US -08-36-C9 (hex) NETGEAR -0836C9 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 +84-98-A7 (hex) Texas Instruments +8498A7 (base 16) Texas Instruments + 12500 TI Blvd + Dallas TX 75243 US -6C-CD-D6 (hex) NETGEAR -6CCDD6 (base 16) NETGEAR - 3553 N. First Street - San Jose CA 95134 - US +80-A2-FC (hex) AzureWave Technology Inc. +80A2FC (base 16) AzureWave Technology Inc. + 8F., No. 94, Baozhong Rd. + New Taipei City Taiwan 231 + TW -3C-EF-A5 (hex) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. -3CEFA5 (base 16) CLOUD NETWORK TECHNOLOGY SINGAPORE PTE. LTD. - B22 Building,NO.51 Tongle Road, Shajing Town, Jiangnan District, Nanning, Guangxi Province, China - Nanning Guangxi 530007 +90-30-D6 (hex) Quectel Wireless Solutions Co.,Ltd. +9030D6 (base 16) Quectel Wireless Solutions Co.,Ltd. + 7th Floor, Hongye Building, No.1801 Hongmei Road, Xuhui District + Shanghai 200233 CN -30-CB-89 (hex) OnLogic Inc -30CB89 (base 16) OnLogic Inc - 435 Community Drive - South Burlington VT 05403 - US - -E4-8F-09 (hex) ithinx GmbH -E48F09 (base 16) ithinx GmbH - Am Kabellager 5-7 - Koeln / Cologne 51063 - DE - -50-31-23 (hex) FN-LINK TECHNOLOGY Ltd. -503123 (base 16) FN-LINK TECHNOLOGY Ltd. - No.8, Litong Road, Liuyang Economic & Technical Development Zone, Changsha, Hunan,China - Changsha Hunan 410329 +44-32-1D (hex) HUAWEI TECHNOLOGIES CO.,LTD +44321D (base 16) HUAWEI TECHNOLOGIES CO.,LTD + No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park + Dongguan 523808 CN -10-13-31 (hex) Vantiva Technologies Belgium -101331 (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -D4-92-5E (hex) Vantiva Technologies Belgium -D4925E (base 16) Vantiva Technologies Belgium - Prins Boudewijnlaan 47 - Edegem - Belgium B-2650 - BE - -BC-D9-FB (hex) China Mobile Group Device Co.,Ltd. -BCD9FB (base 16) China Mobile Group Device Co.,Ltd. - 32 Xuanwumen West Street,Xicheng District - Beijing 100053 +3C-C8-01 (hex) Shenzhen Sundray Technologies company Limited +3CC801 (base 16) Shenzhen Sundray Technologies company Limited + 1st Floor Building A1, Nanshan i Park, No.1001 Xueyuan Road, Nanshan District, Shenzhen, Guangdong Province, P. R. China + Shenzhen GuangDong 518057 CN -40-0A-E7 (hex) BSH Hausgeräte GmbH -400AE7 (base 16) BSH Hausgeräte GmbH - Im Gewerbepark B35 - Regensburg Bayern 93059 - DE - -20-0E-0F (hex) Panasonic Marketing Middle East & Africa FZE -200E0F (base 16) Panasonic Marketing Middle East & Africa FZE - P.O Box 17985 Jebel Ali - Dubai Dubai 17985 - AE - -E8-CB-F5 (hex) Ezurio, LLC -E8CBF5 (base 16) Ezurio, LLC - 3F.-1, No.145, Xianzheng 9th Rd., - Zhubei 30251 - TW - -D8-03-1A (hex) Ezurio, LLC -D8031A (base 16) Ezurio, LLC - 3F.-1, No.145, Xianzheng 9th Rd., - Zhubei 30251 - TW - -88-F9-C0 (hex) KTS Kommunikationstechnik und Systeme GmbH -88F9C0 (base 16) KTS Kommunikationstechnik und Systeme GmbH - Schlossstrasse 123 - Moenchengladbach NRW 41238 - DE +A8-DD-EC (hex) Hangzhou BroadLink Technology Co., Ltd +A8DDEC (base 16) Hangzhou BroadLink Technology Co., Ltd + Room 101,1/F,Unit C,Building 1,No.57 Jiang'er Road,Changhe Street,Binjiang District + Hangzhou ZheJiang 310052 + CN -10-88-D3 (hex) HUAWEI TECHNOLOGIES CO.,LTD -1088D3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +48-98-AB (hex) Wistron InfoComm(Chongqing)Co.,Ltd. +4898AB (base 16) Wistron InfoComm(Chongqing)Co.,Ltd. + No.18-9 Baohong Avenue, Wangjia Street, Yubei District, Chongqing + Chongqing Yubei 4001120 CN -14-5E-BC (hex) HUAWEI TECHNOLOGIES CO.,LTD -145EBC (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 +28-87-AF (hex) Advantech Technology (CHINA) Co., Ltd. +2887AF (base 16) Advantech Technology (CHINA) Co., Ltd. + No.666, Han-Pu Rd. Yu-Shan + Kun-Shan Jiang Su 215316 CN -AC-A7-F1 (hex) TP-Link Systems Inc. -ACA7F1 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 +50-E4-67 (hex) Ring LLC +50E467 (base 16) Ring LLC + 1523 26th St + Santa Monica CA 90404 US -E4-01-77 (hex) SafeOwl, Inc. -E40177 (base 16) SafeOwl, Inc. - 8350 N. Central Expwy #1150 - Dallas TX 75206 +4C-4A-B4 (hex) Juniper Networks +4C4AB4 (base 16) Juniper Networks + 1133 Innovation Way + Sunnyvale CA 94089 US -18-C2-93 (hex) Ezurio, LLC -18C293 (base 16) Ezurio, LLC - 3F.-1, No.145, Xianzheng 9th Rd., - Zhubei 30251 - TW - -90-7A-BE (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED -907ABE (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 - -FC-88-27 (hex) Apple, Inc. -FC8827 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +94-46-67 (hex) Cisco Systems, Inc +944667 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -04-34-CF (hex) Apple, Inc. -0434CF (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +8C-14-2A (hex) Cisco Systems, Inc +8C142A (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -30-EC-A3 (hex) Alfatron Electronics INC -30ECA3 (base 16) Alfatron Electronics INC - 6518 Old Wake Forest Road STE A - Raleigh NC 27616 +6C-DF-D9 (hex) Concept2, Inc. +6CDFD9 (base 16) Concept2, Inc. + 105 Industrial Park Drive + Morrisville VT 05661 US -00-89-C9 (hex) Extreme Networks Headquarters -0089C9 (base 16) Extreme Networks Headquarters - 2121 RDU Center Drive - Morrisville 27560 - US +38-F4-06 (hex) Jinan USR IOT Technology Limited +38F406 (base 16) Jinan USR IOT Technology Limited + Floor F1 & Part of Floor F2, Building No. 9,Diya shuang chuang Industrial Zone, No.2566,Century Main Road,Gaoxin District Jinan,Shandong China + Shandong Jinan 250014 + CN + +E0-A4-47 (hex) zte corporation +E0A447 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -60-DE-18 (hex) Apple, Inc. -60DE18 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +80-E8-A4 (hex) zte corporation +80E8A4 (base 16) zte corporation + 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China + shenzhen guangdong 518057 + CN -10-BC-36 (hex) Huawei Device Co., Ltd. -10BC36 (base 16) Huawei Device Co., Ltd. +20-AE-B6 (hex) Huawei Device Co., Ltd. +20AEB6 (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -B4-F4-9B (hex) Huawei Device Co., Ltd. -B4F49B (base 16) Huawei Device Co., Ltd. +7C-16-2A (hex) Huawei Device Co., Ltd. +7C162A (base 16) Huawei Device Co., Ltd. No.2 of Xincheng Road, Songshan Lake Zone Dongguan Guangdong 523808 CN -14-D6-7C (hex) Uncord Technologies Private Limited -14D67C (base 16) Uncord Technologies Private Limited - 101, Corporate Arena, Sitaram Patkar Road, Goregaon West - Mumbai Maharashtra 400104 - IN +B0-DE-31 (hex) Samsung Electronics Co.,Ltd +B0DE31 (base 16) Samsung Electronics Co.,Ltd + #94-1, Imsoo-Dong + Gumi Gyeongbuk 730-350 + KR -28-1D-AA (hex) ASTI India Private Limited -281DAA (base 16) ASTI India Private Limited - Plot No. 75, Ukardi,Japanese Industrial Zone,Ukardi, Taluka-Mandal,Ahmedabad - Ahmedabad Gujarat 382120 - IN +D0-57-94 (hex) Sagemcom Broadband SAS +D05794 (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR -C0-18-8C (hex) Altus Sistemas de Automação S.A. -C0188C (base 16) Altus Sistemas de Automação S.A. - Av. Theodomiro Porto da Fonseca, 3101 - lote 01 - Cristo Rei - São Leopoldo Rio Grande do Sul 93022-715 - BR +DC-CB-35 (hex) EM Microelectronic +DCCB35 (base 16) EM Microelectronic + Rue des Sors 3 + Marin-Epagnier Neuchatel 2074 + CH -74-24-35 (hex) Huawei Device Co., Ltd. -742435 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +5C-5A-35 (hex) eero inc. +5C5A35 (base 16) eero inc. + 660 3rd Street + San Francisco CA 94107 + US -0C-0E-CB (hex) Huawei Device Co., Ltd. -0C0ECB (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +00-10-7F (hex) CRESTRON ELECTRONICS, INC. +00107F (base 16) CRESTRON ELECTRONICS, INC. + 88 Ramland Road + Orangeburg NJ 10962 + US -E8-80-E7 (hex) Huawei Device Co., Ltd. -E880E7 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +94-88-35 (hex) CRESTRON ELECTRONICS, INC. +948835 (base 16) CRESTRON ELECTRONICS, INC. + 88 Ramland Road + Orangeburg NJ 10962 + US -70-A8-A5 (hex) Microsoft Corporation -70A8A5 (base 16) Microsoft Corporation +54-B2-7E (hex) Sagemcom Broadband SAS +54B27E (base 16) Sagemcom Broadband SAS + 4 Allée des + Messageries Bois Colombes 92270 + FR + +10-7A-2A (hex) Microsoft Corporation +107A2A (base 16) Microsoft Corporation One Microsoft Way REDMOND WA 98052 US -40-38-02 (hex) Silicon Laboratories -403802 (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 +74-D8-09 (hex) Microsoft Corporation +74D809 (base 16) Microsoft Corporation + One Microsoft Way + REDMOND WA 98052 US -5C-5C-75 (hex) IEEE Registration Authority -5C5C75 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 +50-22-C9 (hex) Bel Power Solutions, Inc. +5022C9 (base 16) Bel Power Solutions, Inc. + 1326 South Wolf Road + Wheeling IL 60090 US -A4-F4-CA (hex) Private -A4F4CA (base 16) Private +74-5D-43 (hex) BSH Hausgeräte GmbH +745D43 (base 16) BSH Hausgeräte GmbH + Im Gewerbepark B10 + Regensburg 93059 + DE -80-F1-A8 (hex) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. -80F1A8 (base 16) Guangzhou V-Solution Telecommunication Technology Co.,Ltd. - 601,Building B2,No.162,Science Avenue,Science City,Guangzhou High-tech Industrial Development Zone,Guangdong Province,China - Guangzhou Guangdong 510663 +F8-63-47 (hex) Sichuan AI-Link Technology Co., Ltd. +F86347 (base 16) Sichuan AI-Link Technology Co., Ltd. + Anzhou, Industrial Park + Mianyang Sichuan 622650 CN -F8-91-F5 (hex) Dingtian Technologies Co., Ltd -F891F5 (base 16) Dingtian Technologies Co., Ltd - Rm.3306, Building6, Runyueshan, No.33 Huangzhukeng Rd.,Biling Street,Pingshan District - Shenzhen Guangdong 518100 +24-64-04 (hex) GSD VIET NAM TECHNOLOGY COMPANY LIMITED +246404 (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 + +84-01-6E (hex) Honor Device Co., Ltd. +84016E (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 -4C-43-F6 (hex) SZ DJI TECHNOLOGY CO.,LTD -4C43F6 (base 16) SZ DJI TECHNOLOGY CO.,LTD - DJI Sky City, No55 Xianyuan Road, Nanshan District - Shenzhen Guangdong 518057 +B4-90-E5 (hex) GD Midea Air-Conditioning Equipment Co.,Ltd. +B490E5 (base 16) GD Midea Air-Conditioning Equipment Co.,Ltd. + Midea Global Innovation Center,Beijiao Town,Shunde + Foshan Guangdong 528311 CN -7C-A5-3E (hex) Motorola Mobility LLC, a Lenovo Company -7CA53E (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +B8-DC-7D (hex) VusionGroup +B8DC7D (base 16) VusionGroup + Kalsdorfer Straße 12 + Fernitz-Mellach Steiermark 8072 + AT -D8-31-39 (hex) zte corporation -D83139 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 +8C-65-EC (hex) TUBITAK MAM +8C65EC (base 16) TUBITAK MAM + Gebze Yerleskesi Marmara Arastirma Merkezi + KOCAELI TR 41470 + TR + +20-1F-55 (hex) DJI Osmo Technology Co., Ltd. +201F55 (base 16) DJI Osmo Technology Co., Ltd. + Tower 1, DJI Sky City, No. 55 Xianyuan Road, Xili Community, Xili Subdistrict, Nanshan District + shenzhen 518057 CN -A0-59-11 (hex) Cisco Meraki -A05911 (base 16) Cisco Meraki - 500 Terry A. Francois Blvd - San Francisco 94158 +90-4D-E2 (hex) Apple, Inc. +904DE2 (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 US -F0-16-53 (hex) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. -F01653 (base 16) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. - 309, 3th Floor, No.16, Yun Ding North Road, Huli District - xiamen Fujian 361015 - CN +08-24-0B (hex) Apple, Inc. +08240B (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -B0-61-A9 (hex) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. -B061A9 (base 16) YEALINK(XIAMEN) NETWORK TECHNOLOGY CO.,LTD. - 309, 3th Floor, No.16, Yun Ding North Road, Huli District - xiamen Fujian 361015 - CN +EC-DC-AA (hex) Apple, Inc. +ECDCAA (base 16) Apple, Inc. + 1 Infinite Loop + Cupertino CA 95014 + US -6C-4E-B0 (hex) Castelion Corporation -6C4EB0 (base 16) Castelion Corporation - 19951 Mariner Ave - Torrance CA 90503 +0C-0E-C1 (hex) IEEE Registration Authority +0C0EC1 (base 16) IEEE Registration Authority + 445 Hoes Lane + Piscataway NJ 08554 US -68-B5-E3 (hex) HUAWEI TECHNOLOGIES CO.,LTD -68B5E3 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +10-19-88 (hex) Mother Computer Inc. +101988 (base 16) Mother Computer Inc. + 103 Pioneer Way, Ste B + Mountain View CA 94041 + US -C4-6D-D1 (hex) HUAWEI TECHNOLOGIES CO.,LTD -C46DD1 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +F8-B2-2C (hex) Roku, Inc +F8B22C (base 16) Roku, Inc + 1173 Coleman Ave + San Jose CA 95110 + US -34-A1-37 (hex) HUAWEI TECHNOLOGIES CO.,LTD -34A137 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +00-90-1D (hex) Gallagher Group Limited +00901D (base 16) Gallagher Group Limited + 181 Kahikatea Drive + Hamilton 3206 + NZ -70-97-51 (hex) Beijing Xiaomi Mobile Software Co., Ltd -709751 (base 16) Beijing Xiaomi Mobile Software Co., Ltd - The Rainbow City Office Building, 68 Qinghe Middle Street Haidian District - Beijing Beijing 100085 - CN +00-C0-96 (hex) Tamu Radiance Corporation +00C096 (base 16) Tamu Radiance Corporation + Ebisu, Shibuya-ku, Tokyo + Tokyo - + JP -5C-D3-3D (hex) Samsung Electronics Co.,Ltd -5CD33D (base 16) Samsung Electronics Co.,Ltd - #94-1, Imsoo-Dong - Gumi Gyeongbuk 730-350 - KR +D0-B0-DF (hex) Mimosa Networks +D0B0DF (base 16) Mimosa Networks + 3150 Coronado Dr + Santa Clara CA 95054 + US -AC-DE-01 (hex) Ruckus Wireless -ACDE01 (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +88-03-48 (hex) SERCOMM PHILIPPINES INC +880348 (base 16) SERCOMM PHILIPPINES INC + Lot 1 & 5, Phase 1, Filinvest Technology Park 1, Brgy. Punta, Calamba City + Calamba Lot 1 + PH + +64-9E-58 (hex) MM Devices Pty. Ltd. +649E58 (base 16) MM Devices Pty. Ltd. + 75 Lorimer Street, Unit 10 + Southbank Vic 3006 + AU + +04-B3-C9 (hex) WNC Corporation +04B3C9 (base 16) WNC Corporation + No.20,Park Avenue II,Hsinchu Science Park + Hsin-Chu R.O.C. 308 + TW + +84-DD-84 (hex) Cisco Systems, Inc +84DD84 (base 16) Cisco Systems, Inc + 80 West Tasman Drive + San Jose CA 94568 US -58-AD-08 (hex) IEEE Registration Authority -58AD08 (base 16) IEEE Registration Authority +A0-24-90 (hex) IEEE Registration Authority +A02490 (base 16) IEEE Registration Authority 445 Hoes Lane Piscataway NJ 08554 US -54-7A-F4 (hex) Bouffalo Lab (Nanjing) Co., Ltd. -547AF4 (base 16) Bouffalo Lab (Nanjing) Co., Ltd. - 5F, Gongxiang Space, No.100 Tuanjie Road, Nanjing, China - Nanjing Jiangsu 211800 +60-DD-10 (hex) Shenzhen Angxun Technology Co.,Ltd +60DD10 (base 16) Shenzhen Angxun Technology Co.,Ltd + 4th# Huaguan Industrial Park, Guanlan Street,Longhua District, Shenzhen,Guangdong, China. + shenzhen Select State 518110 CN -48-9D-31 (hex) Espressif Inc. -489D31 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 +A8-21-3A (hex) Shenzhen Talent Technology Comapny Limited 深圳市泰霖科技有限公司 +A8213A (base 16) Shenzhen Talent Technology Comapny Limited 深圳市泰霖科技有限公司 + Room 107, Building T3, Software Park Gaoxin South 7th Road, Nanshan District Shenzhen, Guangdong + Shenzhen Guangdong 518000 CN -14-E2-2A (hex) Cisco Systems, Inc -14E22A (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +B8-6A-F1 (hex) Sagemcom Broadband SAS +B86AF1 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -58-BD-35 (hex) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD -58BD35 (base 16) SHANGHAI XIANGCHENG COMMUNICATION TECHNOLOGY CO., LTD - Room 211-5, Building 1, No. 290 Wankang Road, Minhang District - Shanghai Shanghai 201100 - CN +CC-58-30 (hex) Sagemcom Broadband SAS +CC5830 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -D4-C1-A8 (hex) KYKXCOM Co., Ltd. -D4C1A8 (base 16) KYKXCOM Co., Ltd. - Building 2, No.8, Yuanhua Road, Xianlin UniversityTown, Xianlin Subdistrict, Qixia District - Nanjing Jiangsu 210033 +54-47-CC (hex) Sagemcom Broadband SAS +5447CC (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +3C-58-5D (hex) Sagemcom Broadband SAS +3C585D (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +30-07-5C (hex) Netis Technology Co., Ltd. +30075C (base 16) Netis Technology Co., Ltd. + 8 Floor, Bd B, information port, Langshan RD, Nanshan district, + Shenzhen Guangdong 518057 CN -80-99-9B (hex) Murata Manufacturing Co., Ltd. -80999B (base 16) Murata Manufacturing Co., Ltd. - 1-10-1, Higashikotari - Nagaokakyo-shi Kyoto 617-8555 - JP +A0-2D-DB (hex) Sagemcom Broadband SAS +A02DDB (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -B8-58-FF (hex) Arista Networks -B858FF (base 16) Arista Networks - 5453 Great America Parkway +A4-22-49 (hex) Sagemcom Broadband SAS +A42249 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +20-B8-2B (hex) Sagemcom Broadband SAS +20B82B (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +30-F6-00 (hex) Sagemcom Broadband SAS +30F600 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +D4-B5-CD (hex) Sagemcom Broadband SAS +D4B5CD (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR + +4C-82-37 (hex) Telink Micro LLC +4C8237 (base 16) Telink Micro LLC + 2975 Scott Blvd #120 Santa Clara CA 95054 US -C0-B5-50 (hex) Broadcom Limited -C0B550 (base 16) Broadcom Limited - 15191 Alton Parkway - Irvine CA 92618 +C4-22-4E (hex) Telink Micro LLC +C4224E (base 16) Telink Micro LLC + 2975 Scott Blvd #120 + Santa Clara CA 95054 US -90-F8-61 (hex) u-blox AG -90F861 (base 16) u-blox AG - Zuercherstrasse, 68 - Thalwil Switzerland CH-8800 - CH +A0-39-F9 (hex) Sagemcom Broadband SAS +A039F9 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -40-82-56 (hex) AUMOVIO Germany GmbH -408256 (base 16) AUMOVIO Germany GmbH - VDO-Strasse 1 - Babenhausen Garmany 64832 - DE +24-FA-D6 (hex) EMSTONE +24FAD6 (base 16) EMSTONE + 1201, Byeoksan Digital Valley 271 Digital-ro + Guro-gu Seoul 08381 + KR -54-B2-7E (hex) Sagemcom Broadband SAS -54B27E (base 16) Sagemcom Broadband SAS - 250, route de l'Empereur - Rueil Malmaison Cedex hauts de seine 92848 +00-A0-E3 (hex) XKL LLC +00A0E3 (base 16) XKL LLC + 11601 Willows Rd NE, Suite 101 + Redmond WA 98052 + US + +24-20-C7 (hex) Sagemcom Broadband SAS +2420C7 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 FR -40-E7-62 (hex) Calix Inc. -40E762 (base 16) Calix Inc. - 2777 Orchard Pkwy - San Jose CA 95131 - US +44-E9-DD (hex) Sagemcom Broadband SAS +44E9DD (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -00-1E-AE (hex) AUMOVIO Systems, Inc. -001EAE (base 16) AUMOVIO Systems, Inc. - 21440 West Lake Cook Road - Deer Park IL 60010 - US +B0-B2-8F (hex) Sagemcom Broadband SAS +B0B28F (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -18-F7-F6 (hex) Ericsson AB -18F7F6 (base 16) Ericsson AB - Torshamnsgatan 36 - Stockholm SE-164 80 - SE +F4-EB-38 (hex) Sagemcom Broadband SAS +F4EB38 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -9C-33-12 (hex) Treon Oy -9C3312 (base 16) Treon Oy - Visiokatu 1 - Tampere 33720 - FI +00-1B-BF (hex) Sagemcom Broadband SAS +001BBF (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -44-45-BA (hex) Edgecore Americas Networking Corporation -4445BA (base 16) Edgecore Americas Networking Corporation - 20 Mason - Irvine CA 92618 - US +A0-39-EE (hex) Sagemcom Broadband SAS +A039EE (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -14-DD-48 (hex) Shield AI -14DD48 (base 16) Shield AI - 600 W. BroadwaySuite 600 - San Diego CA 92101 - US +B8-EE-0E (hex) Sagemcom Broadband SAS +B8EE0E (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -D8-99-99 (hex) TECNO MOBILE LIMITED -D89999 (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 - Hong Kong Hong Kong 999077 - HK +F8-AB-05 (hex) Sagemcom Broadband SAS +F8AB05 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -84-C7-E2 (hex) VusionGroup -84C7E2 (base 16) VusionGroup - Kalsdorfer Straße 12 - Fernitz-Mellach Steiermark 8072 - AT +A4-08-F5 (hex) Sagemcom Broadband SAS +A408F5 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -34-17-DD (hex) Sercomm France Sarl -3417DD (base 16) Sercomm France Sarl - 2/4 Rue Maurice Hartmann 92370 Issy Les Moulineaux France - Moulineaux 92370 +20-9A-7D (hex) Sagemcom Broadband SAS +209A7D (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 FR -60-15-9F (hex) IEEE Registration Authority -60159F (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +A8-6A-BB (hex) Sagemcom Broadband SAS +A86ABB (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -58-D8-12 (hex) TP-Link Systems Inc. -58D812 (base 16) TP-Link Systems Inc. - 10 Mauchly - Irvine CA 92618 - US +F0-81-75 (hex) Sagemcom Broadband SAS +F08175 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -74-E6-C7 (hex) LUXSHARE-ICT Co., Ltd. -74E6C7 (base 16) LUXSHARE-ICT Co., Ltd. - 1F, No. 22, Lane 35, Jihu Road, Neihu district - Taipei City Taiwan 114754 - TW +5C-B1-3E (hex) Sagemcom Broadband SAS +5CB13E (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -84-16-23 (hex) zte corporation -841623 (base 16) zte corporation - 12/F.,zte R&D building ,kejinan Road,Shenzhen,P.R.China - shenzhen guangdong 518057 - CN +00-25-69 (hex) Sagemcom Broadband SAS +002569 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -AC-40-1E (hex) vivo Mobile Communication Co., Ltd. -AC401E (base 16) vivo Mobile Communication Co., Ltd. - No.1, vivo Road, Chang'an - Dongguan Guangdong 523860 - CN +00-78-9E (hex) Sagemcom Broadband SAS +00789E (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -64-D4-F0 (hex) NETVUE,INC. -64D4F0 (base 16) NETVUE,INC. - 1055 East Colorado Boulevard 5th Floor - Pasadena CA 91106 - US +E8-BE-81 (hex) Sagemcom Broadband SAS +E8BE81 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -14-AE-E0 (hex) ETHERNEXION NETWORKS PTE. LTD. -14AEE0 (base 16) ETHERNEXION NETWORKS PTE. LTD. - 7500A BEACH ROAD#04-307 THE PLAZA - SINGAPORE SINGAPORE 199591 - SG +68-15-90 (hex) Sagemcom Broadband SAS +681590 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -68-1A-47 (hex) Apple, Inc. -681A47 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +2C-E4-12 (hex) Sagemcom Broadband SAS +2CE412 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -28-49-E9 (hex) Apple, Inc. -2849E9 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +E0-4F-95 (hex) Sagemcom Broadband SAS +E04F95 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -78-96-0D (hex) Apple, Inc. -78960D (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +7C-E8-7F (hex) Sagemcom Broadband SAS +7CE87F (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -80-1D-39 (hex) Apple, Inc. -801D39 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +50-6F-0C (hex) Sagemcom Broadband SAS +506F0C (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -CC-72-2A (hex) Apple, Inc. -CC722A (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +34-6B-46 (hex) Sagemcom Broadband SAS +346B46 (base 16) Sagemcom Broadband SAS + 4 Allée des Messageries + Bois-Colombes haut de seine 92270 + FR -D0-F8-15 (hex) HUAWEI TECHNOLOGIES CO.,LTD -D0F815 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +D4-B6-C9 (hex) Nokia +D4B6C9 (base 16) Nokia + 600 March Road + Kanata Ontario K2K 2E6 + CA -B8-84-11 (hex) Shenzhen Shokz Co., Ltd. -B88411 (base 16) Shenzhen Shokz Co., Ltd. - Baoan District Shiyan street Shancheng Industrial zone 26 building - Shenzhen Guangdong 518108 - CN +58-BF-25 (hex) Espressif Inc. +58BF25 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -84-B8-90 (hex) TP-LINK TECHNOLOGIES CO.,LTD. -84B890 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. - Building 24(floors 1,3,4,5)and 28(floors 1-4)Central Science and Technology Park,Shennan Road,Nanshan - Shenzhen Guangdong 518057 - CN +24-D7-EB (hex) Espressif Inc. +24D7EB (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -70-79-2D (hex) Mellanox Technologies, Inc. -70792D (base 16) Mellanox Technologies, Inc. - 350 Oakmead Parkway, Suite 100 - Sunnyvale CA 94085 - US +30-C6-F7 (hex) Espressif Inc. +30C6F7 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -A4-43-1B (hex) Dreamtek Intelligent Technology Co., Ltd -A4431B (base 16) Dreamtek Intelligent Technology Co., Ltd - Room 508, Building A2, Area one of Zhongan Chuanggu Science Park, No. 900 of Wangjiang West Road, High-tech Zone, Hefei, Anhui, China - Shanghai 230000 - CN +10-97-BD (hex) Espressif Inc. +1097BD (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -94-6A-7C (hex) OnePlus Technology (Shenzhen) Co., Ltd -946A7C (base 16) OnePlus Technology (Shenzhen) Co., Ltd - 18C02, 18C03, 18C04 ,18C05,TAIRAN BUILDING, - Shenzhen Guangdong 518000 - CN +78-21-84 (hex) Espressif Inc. +782184 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -20-9B-A9 (hex) Espressif Inc. -209BA9 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +EC-62-60 (hex) Espressif Inc. +EC6260 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -04-5E-0A (hex) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED -045E0A (base 16) SHENZHEN TRANSCHAN TECHNOLOGY LIMITED - Room 03, 23/F, Unit B Building, No 9, Shenzhen Bay Eco -Technology Park, Yuehai Street, Nanshan District, Shenzhen, China - Shenzhen 518000 - CN +C8-F0-9E (hex) Espressif Inc. +C8F09E (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -A8-6A-CB (hex) EVAR -A86ACB (base 16) EVAR - 42, Changeop-ro, Sujeong-gu, Seongnam-si, Gyeonggi-do, Republic of Korea - Seoul Gyunggi-do 13449 - KR +DC-54-75 (hex) Espressif Inc. +DC5475 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -F0-4F-E0 (hex) Vizio, Inc -F04FE0 (base 16) Vizio, Inc - 39 Tesla - Irvine CA 92618 - US +48-27-E2 (hex) Espressif Inc. +4827E2 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -A4-CB-8F (hex) Espressif Inc. -A4CB8F (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +2C-3A-E8 (hex) Espressif Inc. +2C3AE8 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -14-2E-43 (hex) Hisense broadband multimedia technology Co.,Ltd -142E43 (base 16) Hisense broadband multimedia technology Co.,Ltd - Song ling Road 399 - Qingdao 266000 - CN +EC-FA-BC (hex) Espressif Inc. +ECFABC (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -04-22-E7 (hex) Fiberhome Telecommunication Technologies Co.,LTD -0422E7 (base 16) Fiberhome Telecommunication Technologies Co.,LTD - No.5 DongXin Road - Wuhan Hubei 430074 - CN +DC-4F-22 (hex) Espressif Inc. +DC4F22 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -2C-63-A1 (hex) Huawei Device Co., Ltd. -2C63A1 (base 16) Huawei Device Co., Ltd. - No.2 of Xincheng Road, Songshan Lake Zone - Dongguan Guangdong 523808 - CN +3C-71-BF (hex) Espressif Inc. +3C71BF (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -2C-E1-87 (hex) New H3C Technologies Co., Ltd -2CE187 (base 16) New H3C Technologies Co., Ltd - 466 Changhe Road, Binjiang District - Hangzhou Zhejiang 310052 - CN +60-55-F9 (hex) Espressif Inc. +6055F9 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -0C-88-2F (hex) Frog Innovations Limited -0C882F (base 16) Frog Innovations Limited - C23, Sector 80, Phase-II - Noida Uttar Pradesh 201305 - IN +94-3C-C6 (hex) Espressif Inc. +943CC6 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -EC-58-65 (hex) Shenzhen Xinguodu Technology Co., Ltd -EC5865 (base 16) Shenzhen Xinguodu Technology Co., Ltd - 17B, Jinson Building, Tairan 4th Road, Shatou Subdistrict, Futian District - Shenzhen Guangdong 518048 - CN +7C-87-CE (hex) Espressif Inc. +7C87CE (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -68-09-47 (hex) Espressif Inc. -680947 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +44-B1-76 (hex) Espressif Inc. +44B176 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -70-AF-09 (hex) Espressif Inc. -70AF09 (base 16) Espressif Inc. - Room 204, Building 2, 690 Bibo Rd, Pudong New Area - Shanghai Shanghai 201203 - CN +9C-CC-01 (hex) Espressif Inc. +9CCC01 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -50-0F-C6 (hex) solum -500FC6 (base 16) solum - 2354, Yonggu-daero, Giheung-gu - Yongin-si Gyeonggi-do 16921 - KR +24-A1-60 (hex) Espressif Inc. +24A160 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -44-61-DF (hex) Skyquad Electronics & Appliances Pvt. Ltd. -4461DF (base 16) Skyquad Electronics & Appliances Pvt. Ltd. - 12-50/4/A, Adj to Industrial Estate, MedchalR R District, Hyderabad - 501401, Telangana, India. - Hyderabad Telangana 501401 - IN +A8-03-2A (hex) Espressif Inc. +A8032A (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -90-47-C2 (hex) Intel Corporate -9047C2 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +A4-CB-8F (hex) Espressif Inc. +A4CB8F (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -48-40-D5 (hex) Intel Corporate -4840D5 (base 16) Intel Corporate - Lot 8, Jalan Hi-Tech 2/3 - Kulim Kedah 09000 - MY +90-70-69 (hex) Espressif Inc. +907069 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -48-92-C1 (hex) OHSUNG -4892C1 (base 16) OHSUNG - 335-4,SANHODAERO,GUMI,GYEONG BUK,KOREA - GUMI GYEONG BUK 730-030 - KR +20-6E-F1 (hex) Espressif Inc. +206EF1 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -FC-EF-D7 (hex) HUAWEI TECHNOLOGIES CO.,LTD -FCEFD7 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +18-8B-0E (hex) Espressif Inc. +188B0E (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -4C-30-6A (hex) Nintendo Co.,Ltd -4C306A (base 16) Nintendo Co.,Ltd - 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU - KYOTO KYOTO 601-8501 - JP +08-A6-F7 (hex) Espressif Inc. +08A6F7 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -2C-2B-DB (hex) eero inc. -2C2BDB (base 16) eero inc. - 660 3rd Street - San Francisco CA 94107 - US +2C-BC-BB (hex) Espressif Inc. +2CBCBB (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -DC-F3-4C (hex) PT HAN SUNG ELECTORONICS INDONESIA -DCF34C (base 16) PT HAN SUNG ELECTORONICS INDONESIA - JL.PALEM 1 BLOK DS-6 - KAWASAN INDUSTRI BATIK LIPPO CIKARANG, DESA CIBATU, KECAMATAN CIKARANG SELATAN BEKASI JAWA BARAT 17550 - ID +EC-C9-FF (hex) Espressif Inc. +ECC9FF (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -B4-91-07 (hex) HUAWEI TECHNOLOGIES CO.,LTD -B49107 (base 16) HUAWEI TECHNOLOGIES CO.,LTD - No.2 Xin Cheng Road, Room R6,Songshan Lake Technology Park - Dongguan 523808 - CN +E4-B0-63 (hex) Espressif Inc. +E4B063 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -84-1D-E8 (hex) CJ intelligent technology LTD. -841DE8 (base 16) CJ intelligent technology LTD. - 4F, No. 16, Zhongxin St., Shulin Dist. - New Taipei City 238035 - TW +1C-69-20 (hex) Espressif Inc. +1C6920 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -C4-84-C0 (hex) Motorola Mobility LLC, a Lenovo Company -C484C0 (base 16) Motorola Mobility LLC, a Lenovo Company - 222 West Merchandise Mart Plaza - Chicago IL 60654 - US +88-13-BF (hex) Espressif Inc. +8813BF (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -CC-35-D9 (hex) Ubiquiti Inc -CC35D9 (base 16) Ubiquiti Inc - 685 Third Avenue, 27th Floor - New York NY New York NY 10017 - US +34-5F-45 (hex) Espressif Inc. +345F45 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -A4-F8-FF (hex) Ubiquiti Inc -A4F8FF (base 16) Ubiquiti Inc - 685 Third Avenue, 27th Floor - New York NY New York NY 10017 - US +DC-DA-0C (hex) Espressif Inc. +DCDA0C (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -64-9B-8F (hex) Texas Instruments -649B8F (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +74-4D-BD (hex) Espressif Inc. +744DBD (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -28-9E-1E (hex) Texas Instruments -289E1E (base 16) Texas Instruments - 12500 TI Blvd - Dallas TX 75243 - US +30-C9-22 (hex) Espressif Inc. +30C922 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -CC-C5-30 (hex) AzureWave Technology Inc. -CCC530 (base 16) AzureWave Technology Inc. - 8F., No. 94, Baozhong Rd. - New Taipei City Taiwan 231 - TW +EC-64-C9 (hex) Espressif Inc. +EC64C9 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -1C-8C-6E (hex) Arista Networks -1C8C6E (base 16) Arista Networks - 5453 Great America Parkway - Santa Clara CA 95054 - US +F0-F5-BD (hex) Espressif Inc. +F0F5BD (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -6C-47-80 (hex) IEEE Registration Authority -6C4780 (base 16) IEEE Registration Authority - 445 Hoes Lane - Piscataway NJ 08554 - US +6C-C8-40 (hex) Espressif Inc. +6CC840 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -80-C4-29 (hex) Renesas Electronics Operations Services Limited -80C429 (base 16) Renesas Electronics Operations Services Limited - Dukes Meadow, Millboard Raod Bourne End BU - Bourne End BU SL8 5FH - GB +28-56-2F (hex) Espressif Inc. +28562F (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -00-E0-AD (hex) Brandywine Communications UK Ltd. -00E0AD (base 16) Brandywine Communications UK Ltd. - 20a Westside Centre London Road, Stanway, Colchester - ESSEX England CO3 8PH - GB +D0-CF-13 (hex) Espressif Inc. +D0CF13 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -50-71-64 (hex) Cisco Systems, Inc -507164 (base 16) Cisco Systems, Inc - 80 West Tasman Drive - San Jose CA 94568 - US +C0-5D-89 (hex) Espressif Inc. +C05D89 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -E8-A9-27 (hex) LEAR -E8A927 (base 16) LEAR - Carrer Fuster 54 - Valls Tarragona 43800 - ES +D4-8C-49 (hex) Espressif Inc. +D48C49 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -00-1C-44 (hex) Electro Voice Dynacord BV -001C44 (base 16) Electro Voice Dynacord BV - Achtseweg Zuid 173 - 5651 GW Eindhoven Eindhoven 5651 - NL +DC-1E-D5 (hex) Espressif Inc. +DC1ED5 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -50-C3-A2 (hex) nFore Technology Co., Ltd. -50C3A2 (base 16) nFore Technology Co., Ltd. - 5F., No.31, Ln. 258, Ruiguang Rd. Neihu Dist., Taipei City 114, Taiwan - Taipei 114 - TW +30-ED-A0 (hex) Espressif Inc. +30EDA0 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -A4-04-50 (hex) nFore Technology Co., Ltd. -A40450 (base 16) nFore Technology Co., Ltd. - 5F, NO 31, Ln 258, Rulguang Rd - Taipei Neihu District 11491 - TW +B4-3A-45 (hex) Espressif Inc. +B43A45 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG -00-17-53 (hex) nFore Technology Co., Ltd. -001753 (base 16) nFore Technology Co., Ltd. - 5F, NO 31, Ln 258, Rulguang Rd - Taipei Neihu District 11491 +68-25-DD (hex) Espressif Inc. +6825DD (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +38-32-7A (hex) Routerboard.com +38327A (base 16) Routerboard.com + Mikrotikls SIA + Riga Riga LV1009 + LV + +84-C0-65 (hex) Nintendo Co.,Ltd +84C065 (base 16) Nintendo Co.,Ltd + 11-1 HOKOTATE-CHO KAMITOBA,MINAMI-KU + KYOTO KYOTO 601-8501 + JP + +64-E8-33 (hex) Espressif Inc. +64E833 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +64-B7-08 (hex) Espressif Inc. +64B708 (base 16) Espressif Inc. + Vistra Corporate Services Centre, Wickhams Cay II + Road Town Tortola VG1110 + VG + +30-62-22 (hex) Samsung Electronics Co.,Ltd +306222 (base 16) Samsung Electronics Co.,Ltd + 129, Samsung-ro, Youngtongl-Gu + Suwon Gyeonggi-Do 16677 + KR + +FC-93-4E (hex) REALTEK SEMICONDUCTOR CORP. +FC934E (base 16) REALTEK SEMICONDUCTOR CORP. + 1F, NO. 11, INDUSTRY E. RD. IX + HSINCHU 300 TW -B0-37-31 (hex) FUJIAN STAR-NET COMMUNICATION CO.,LTD -B03731 (base 16) FUJIAN STAR-NET COMMUNICATION CO.,LTD - 19-22# Building, Star-net Science Plaza, Juyuanzhou, - FUZHOU FUJIAN 350002 - CN +4C-45-53 (hex) Silicon Laboratories +4C4553 (base 16) Silicon Laboratories + 400 West Cesar Chavez + Austin TX 78701 + US -6C-40-33 (hex) Beijing Megwang Technology Co., Ltd. -6C4033 (base 16) Beijing Megwang Technology Co., Ltd. - Room 1316, 1st Floor, Building 12, Jianzhong Road, Xisanqi Building Materials City, Haidian District, Beijing, China - Beijing 100096 +C0-25-A2 (hex) NEC Platforms, Ltd. +C025A2 (base 16) NEC Platforms, Ltd. + 14-1, Shiba 4-chome + Minato-ku Tokyo 108-8556 + JP + +54-9B-49 (hex) NEC Platforms, Ltd. +549B49 (base 16) NEC Platforms, Ltd. + 14-1, Shiba 4-chome + Minato-ku Tokyo 108-8556 + JP + +38-BE-AB (hex) AltoBeam Inc. +38BEAB (base 16) AltoBeam Inc. + B808, Tsinghua Tongfang Hi-Tech Plaza, Haidian + Beijing Beijing 100083 CN -44-10-30 (hex) Google, Inc. -441030 (base 16) Google, Inc. +A0-B1-5C (hex) Google, Inc. +A0B15C (base 16) Google, Inc. 1600 Amphitheatre Parkway Mountain View CA 94043 US -60-A1-FE (hex) HPRO -60A1FE (base 16) HPRO - 8500 Balboa Blvd - Northridge CA 91329 +40-59-25 (hex) Kaon Broadband CO., LTD. +405925 (base 16) Kaon Broadband CO., LTD. + 884-3, Seongnam-daero, Bundang-gu + Seongnam-si 13517 + KR + +AC-2B-A2 (hex) The Chamberlain Group, Inc +AC2BA2 (base 16) The Chamberlain Group, Inc + 300 Windsor Drive + Oak Brook 60523 US -24-99-00 (hex) FRITZ! Technology GmbH -249900 (base 16) FRITZ! Technology GmbH - Alt-Moabit 95 - Berlin Berlin 10559 - DE +3C-84-6A (hex) TP-LINK TECHNOLOGIES CO.,LTD. +3C846A (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN -58-8C-CF (hex) Silicon Laboratories -588CCF (base 16) Silicon Laboratories - 400 West Cesar Chavez - Austin TX 78701 - US +84-D8-1B (hex) TP-LINK TECHNOLOGIES CO.,LTD. +84D81B (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN -A4-18-94 (hex) IQSIGHT B.V. -A41894 (base 16) IQSIGHT B.V. - Achtseweg Zuid 173 - Eindhoven 5651 GW - NL +D8-47-32 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +D84732 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN -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 +24-5A-5F (hex) TP-LINK TECHNOLOGIES CO.,LTD. +245A5F (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN -18-A0-84 (hex) Apple, Inc. -18A084 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 - US +48-5F-08 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +485F08 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN -58-2A-93 (hex) Apple, Inc. -582A93 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +F8-6F-B0 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +F86FB0 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +EC-60-73 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +EC6073 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +A4-1A-3A (hex) TP-LINK TECHNOLOGIES CO.,LTD. +A41A3A (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +7C-B5-9B (hex) TP-LINK TECHNOLOGIES CO.,LTD. +7CB59B (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +84-B8-90 (hex) TP-LINK TECHNOLOGIES CO.,LTD. +84B890 (base 16) TP-LINK TECHNOLOGIES CO.,LTD. + 2-7th floors of South Building and 3-11th floors of North Building, No. 396, Jucai Road, Changhe Street, Binjiang District + Hangzhou City Zhejiang Province, 310000 + CN + +38-C2-2D (hex) Mellanox Technologies, Inc. +38C22D (base 16) Mellanox Technologies, Inc. + 350 Oakmead Parkway, Suite 100 + Sunnyvale CA 94085 US -64-C9-05 (hex) Apple, Inc. -64C905 (base 16) Apple, Inc. - 1 Infinite Loop - Cupertino CA 95014 +D8-C2-62 (hex) Ubiquiti Inc +D8C262 (base 16) Ubiquiti Inc + 685 Third Avenue, 27th Floor + New York NY New York NY 10017 US -3C-BF-D7 (hex) Apple, Inc. -3CBFD7 (base 16) Apple, Inc. +E0-DF-B7 (hex) Zeica Labs Pte Ltd +E0DFB7 (base 16) Zeica Labs Pte Ltd + 380 Jalan Besar, #06-06, ARC 380 + Singapore 209000 + SG + +C4-BB-89 (hex) Shanghai High-Flying Electronics Technology Co.,Ltd +C4BB89 (base 16) Shanghai High-Flying Electronics Technology Co.,Ltd + #17 Building, No.1500 Zu Chongzhi Road, Pudong District, 201203, Shanghai, China + SHANGHAI SHANGHAI 201200 + CN + +E4-70-10 (hex) Apple, Inc. +E47010 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -88-7E-9B (hex) Apple, Inc. -887E9B (base 16) Apple, Inc. +30-E2-26 (hex) Apple, Inc. +30E226 (base 16) Apple, Inc. 1 Infinite Loop Cupertino CA 95014 US -54-2A-43 (hex) Apple, Inc. -542A43 (base 16) Apple, Inc. +84-27-89 (hex) Apple, Inc. +842789 (base 16) Apple, Inc. 1 Infinite Loop 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 +A0-9C-19 (hex) CIG SHANGHAI CO LTD +A09C19 (base 16) CIG SHANGHAI CO LTD + 5th Floor, Building 8 No 2388 Chenhang Road + SHANGHAI 201114 + CN -E8-FC-5F (hex) Ruckus Wireless -E8FC5F (base 16) Ruckus Wireless - 350 West Java Drive - Sunnyvale CA 94089 +D0-32-E7 (hex) Tuya Smart Inc. +D032E7 (base 16) Tuya Smart Inc. + 160 Greentree Drive, Suite 101 + Dover DE 19904 US diff --git a/hwdb.d/ma-medium.txt b/hwdb.d/ma-medium.txt index 0f268976eed05..ecd56f17f7591 100644 --- a/hwdb.d/ma-medium.txt +++ b/hwdb.d/ma-medium.txt @@ -95,12 +95,6 @@ C00000-CFFFFF (base 16) DanuTech Europe Kft Meguro-ku Tokyo-to 152-0035 JP -D0-15-BB (hex) Bluewaves Mobility Innovation Inc -D00000-DFFFFF (base 16) Bluewaves Mobility Innovation Inc - Unit 402-105 Gordon Baker Rd - Toronto Ontario M2H3P8 - CA - 88-A6-EF (hex) ShenZhen KZIot Technology LLC. 600000-6FFFFF (base 16) ShenZhen KZIot Technology LLC. 12th Floor, Block A, Kelu Building, Baoshen Road, Songpingshan Community, Xili Street, Nanshan District, @@ -1178,12 +1172,6 @@ F4-70-0C (hex) Annapurna labs ShenZhen 518000 CN -F4-70-0C (hex) Freeus LLC -D00000-DFFFFF (base 16) Freeus LLC - 1069 Stewart Drive, Suites 3-6 - Ogden UT 84404 - US - 78-72-64 (hex) SHENZHEN FANGZHICHENG TECHNOLOGY CO., LTD. 900000-9FFFFF (base 16) SHENZHEN FANGZHICHENG TECHNOLOGY CO., LTD. SHENZHEN FANGZHICHENG TECHNOLOGY CO., LTD. @@ -1250,12 +1238,6 @@ E00000-EFFFFF (base 16) OUTFORM Miami FL 33137 US -28-36-13 (hex) shenzhen technology limited -700000-7FFFFF (base 16) shenzhen technology limited - 903,No. 1 Shifeng Building, Xinzhuang Community Villa Road, Matian Street, Guangming District, Shenzhen City - Shenzhen Guangdong 518000 - CN - 78-5E-E8 (hex) Guangdong COROS Sports Technology Co., Ltd 600000-6FFFFF (base 16) Guangdong COROS Sports Technology Co., Ltd ROOM 601 ROOM 701,BLD.2,NO.2,SCIENCE AND TECHNOLOGY 9 RD,SONGSHAN LAKE HI-TECH ZONE @@ -1862,12 +1844,6 @@ C00000-CFFFFF (base 16) Ark Vision Systems GmbH & Co. KG Merenberg Hessen 35799 DE -18-FD-CB (hex) Staclar, Inc. -300000-3FFFFF (base 16) Staclar, Inc. - 2093 Philadelphia Pike - Claymont DE 19703 - US - 18-FD-CB (hex) KWANG YANG MOTOR CO.,LTD E00000-EFFFFF (base 16) KWANG YANG MOTOR CO.,LTD NO. 35, WAN HSING ST., SAN MIN DIST., KAOHSIUNG, TAIWAN, R.O.C @@ -2036,12 +2012,6 @@ C00000-CFFFFF (base 16) Anacove LLC LA JOLLA CA 92037 US -30-49-50 (hex) Ledworks SRL -A00000-AFFFFF (base 16) Ledworks SRL - Via Tortona 37 - Milano Milano 20144 - IT - 30-49-50 (hex) ATLI WORLD LIMITED 100000-1FFFFF (base 16) ATLI WORLD LIMITED 306 Beverley Commercial Center, 87-105 Chatham Road, TST, @@ -4190,12 +4160,6 @@ E00000-EFFFFF (base 16) Domotz Ltd Fremont CA 94538 US -04-71-4B (hex) Observables, Inc. -A00000-AFFFFF (base 16) Observables, Inc. - 117 N. MILPAS ST - SANTA BARBARA CA 93103 - US - F0-23-B9 (hex) Shenzhen Lachesis Mhealth Co., Ltd. C00000-CFFFFF (base 16) Shenzhen Lachesis Mhealth Co., Ltd. Bldg. C, No.43 Yanshan Rd, Nanshan District @@ -7109,6 +7073,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 +7097,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 +7229,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 +7241,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 +7265,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 +7307,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 +7361,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 +7403,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 +7421,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 +7469,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 +7481,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 +7499,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 +7523,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 +7565,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,12 +7583,222 @@ 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 +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 + +20-B3-7F (hex) Shenzhen Hengbang Xinchuang Technology Co.,Ltd +B00000-BFFFFF (base 16) Shenzhen Hengbang Xinchuang Technology Co.,Ltd + 2nd Floor East, Workshop 3A, No. 268 Baoshi East Road, Shiyan Sub-district, Bao'an District, Shenzhen + Shenzhen Guangdong 518108 + CN + +80-1D-0D (hex) WARNER ELECTRONICS (I) PVT. LTD. +900000-9FFFFF (base 16) WARNER ELECTRONICS (I) PVT. LTD. + Plot No. A114/6, Five Star Industrial Area, Mumbai – Nagpur Highway, Shendra + Aurangabad maharastra 431007 + IN + +CC-E7-DE (hex) Chengdu Vantron Technology Co., Ltd. +500000-5FFFFF (base 16) Chengdu Vantron Technology Co., Ltd. + 5th Floor, 1st Building, No.9 3rd WuKe East Street, WuHou District + Chengdu Sichuan 610045 + CN + +CC-E7-DE (hex) Shenzhen Xingyi Intelligent Technology Co.,Ltd +E00000-EFFFFF (base 16) Shenzhen Xingyi Intelligent Technology Co.,Ltd + 905,Bldg 1,Xinyi Lingyu R&D Center No.30,Honglang Beier Rd,Zone 69,Xingdong Community,Xin'an Sub-district,Bao'an Dist,Shenzhen,Guangdong,China + Shenzhen 518000 + CN + +CC-E7-DE (hex) Kaze.AI Technology Co.,Ltd. +100000-1FFFFF (base 16) Kaze.AI Technology Co.,Ltd. + Romm 5D, 5th Floor, Block A, Central Avenue, Xixiang Subdistrict, Bao'an District, Shenzhen + Romm 5D, 5th Floor, Block A, Central Avenue, Xixiang Subdistrict, Bao'an District, Shenzhen 518100 + CN + +4C-6E-44 (hex) Shenzhen iTayga Technology Co.,Ltd +800000-8FFFFF (base 16) Shenzhen iTayga Technology Co.,Ltd + Room 516, Building A, Huafeng Huayuan Technology Innovation Park, Xixiang Street, Bao'an District, Shenzhen + Shenzhen 粤 518100 + CN + +4C-6E-44 (hex) Shenzhen Langji Guangnian Technology Co., Ltd. +500000-5FFFFF (base 16) Shenzhen Langji Guangnian Technology Co., Ltd. + Unit 519, Building A, Jiada R&D Building, No. 5 Songpingshan Road, Songpingshan Community, Xili Street, Nanshan District, Shenzhen, Guangdong, China + Shenzhen 518000 + CN + +18-FD-CB (hex) Blahaj Studio +300000-3FFFFF (base 16) Blahaj Studio + Kurt-Schumacher-Str. 13 + Germering 82110 + DE + +4C-6E-44 (hex) NovaFly LLC +B00000-BFFFFF (base 16) NovaFly LLC + 2108 N ST STE N, SACRAMENTO, CA 95816 + SACRAMENTO CA 95816 + US + +A4-4F-3E (hex) RINVENT INDUSTRIES PRIVATE LIMITED +200000-2FFFFF (base 16) RINVENT INDUSTRIES PRIVATE LIMITED + 9-272/1A Angalakuduru + Tenali Andhra Pradesh 522211 + IN + +A4-4F-3E (hex) JOYAR TECHNOLOGY (HONG KONG) COMPANY LIMITED +C00000-CFFFFF (base 16) JOYAR TECHNOLOGY (HONG KONG) COMPANY LIMITED + FLAT/RM 1610, 16/F, SEAPOWER TOWER CONCORDIA PLAZA,1 SCIENCE MUSEUM ROAD, TSIM SHA TSUI, KOWLOON, HONG KONG + HONG KONG 999077 + HK + +F8-C9-D6 (hex) VT100 SRL +B00000-BFFFFF (base 16) VT100 SRL + Viale Dell'Artigianato 4 + Caldiero ITALY 37042 + IT + +A4-4F-3E (hex) ShenZhen Chainway Information Technology Co., Ltd. +E00000-EFFFFF (base 16) ShenZhen Chainway Information Technology Co., Ltd. + 9F Building2, Phase2, Gaoxinqi Industrial Park , Bao'an District + ShenZhen GuangDong 518102 + CN + +F8-C9-D6 (hex) Shanghai Innovatech Information Technology Co., Ltd. +600000-6FFFFF (base 16) Shanghai Innovatech Information Technology Co., Ltd. + Yijing Technology, 17th Floor, Building G, Weijing Center, Gu Dai Lu, Xinzhuang Town, Minhang District + Shanghai Shanghai 201199 + CN + +F8-75-28 (hex) SHENZHEN WISEWING INTERNET TECHNOLOGY CO.,LTD +B00000-BFFFFF (base 16) SHENZHEN WISEWING INTERNET TECHNOLOGY CO.,LTD + No.826,Zone 1,Block B,Famous industrial product display purchasing center,Baoyuan Road,Xixiang,Bao'an Dis., Shenzhen,P.R.China + shenzhen China 518102 + CN + +D0-15-BB (hex) Bluewaves Mobility Innovation Inc +D00000-DFFFFF (base 16) Bluewaves Mobility Innovation Inc + Suite 702, 2450 Victoria Park Ave + North York Ontario M2J4A2 + CA + +F8-75-28 (hex) SGSG Science&Technology Co., Ltd. Zhuhai +300000-3FFFFF (base 16) SGSG Science&Technology Co., Ltd. Zhuhai + Floor 9-15, Building 1, 199 Dingxing Road, High-tech Zone, Zhuhai City + Zhuhai Guangdong 519000 + CN + +F8-75-28 (hex) Ingersoll-Rand +E00000-EFFFFF (base 16) Ingersoll-Rand + 53 Frontage Road Suite 250 + Hampton NJ 08827 + US + +28-36-13 (hex) SHENZHEN OFEIXIN TECHNOLOGY LIMITED +700000-7FFFFF (base 16) SHENZHEN OFEIXIN TECHNOLOGY LIMITED + 902, Building 4, Guangming Fenghuang Plaza, Dongkeng Community, Fenghuang Street, Guangming District + Shenzhen 518000 + CN + +0C-0E-C1 (hex) Sonoro Audio GmbH +A00000-AFFFFF (base 16) Sonoro Audio GmbH + Hammer Landstraße 45 + Neuss 41460 + DE + +0C-0E-C1 (hex) Spintronica LLC +000000-0FFFFF (base 16) Spintronica LLC + Lubyanka M. Street + Moscow 101000 + RU + +0C-0E-C1 (hex) Aerora North America +D00000-DFFFFF (base 16) Aerora North America + 4784 N. Lombard St. Suite B, PMB #167 + Portland OR 97203 US +30-49-50 (hex) Illucere Srl +A00000-AFFFFF (base 16) Illucere Srl + Via Primo Maggio 1 + Mestrino PD 35035 + IT + +F4-70-0C (hex) Becklar, LLC +D00000-DFFFFF (base 16) Becklar, LLC + 1069 Stewart Drive, Suites 3-6 + Ogden UT 84404 + US + +04-71-4B (hex) Observables, Inc. +A00000-AFFFFF (base 16) Observables, Inc. + 5973 Encina Rd. Suite 109 + SANTA BARBARA CA 93117 + US + +74-A2-35 (hex) Bots Unlimited LLC +700000-7FFFFF (base 16) Bots Unlimited LLC + 5817 La Colonia + San Antonio TX 78218 + US + +A0-24-90 (hex) SIAME +600000-6FFFFF (base 16) SIAME + RUE DES MATHEMATIQUES + GROMBALIA 8030 + TN + +74-A2-35 (hex) Enterprise Software Solutions Lab Pvt Ltd +B00000-BFFFFF (base 16) Enterprise Software Solutions Lab Pvt Ltd + No 24 23rd Main Road Marenahalli + Bengaluru Karnataka 560078 + IN + +74-A2-35 (hex) Shenzhen Xhorse3D Technology Co.,LTD. +500000-5FFFFF (base 16) Shenzhen Xhorse3D Technology Co.,LTD. + 2007 to 2011, Building B, Building 6, Shenzhen International Innovation Valley, 1st Dashi Road, Yuncheng Community, Xili Sub-district, Nanshan District, Shenzhen, Guangdong Province, China + Shenzhen Guangdong Province 518100 + CN + B8-4C-87 (hex) Shenzhen Link-all Technology Co., Ltd 300000-3FFFFF (base 16) Shenzhen Link-all Technology Co., Ltd Floor 5th, Block 9th, Sunny Industrial Zone, Xili Town, Nanshan District, Shenzhen, China @@ -8375,12 +8555,6 @@ C00000-CFFFFF (base 16) shenzhen newbridge communication equipment CO.,LTD Kunshan Jiangsu 215300 CN -6C-15-24 (hex) Magicyo Technology CO., LTD. -400000-4FFFFF (base 16) Magicyo Technology CO., LTD. - 7F, Tower A, YuZhou Building, No.78 North Keyuan - Shenzhen Nanshan District 518057 - CN - 6C-15-24 (hex) D-HOME SMAART 900000-9FFFFF (base 16) D-HOME SMAART 8, rue Edouard Herriot @@ -8627,12 +8801,6 @@ B00000-BFFFFF (base 16) F-Plus Mobile LLC shenzhen guangdong 518057 CN -DC-36-43 (hex) Shenzhen smart-core technology co.,ltd. -600000-6FFFFF (base 16) Shenzhen smart-core technology co.,ltd. - 19/F., Finance & Technology Building, No.11 Keyuan Road, Nanshan Dist., Shenzhen, China - Shenzhen Guangdong 518057 - CN - DC-36-43 (hex) Wuhan Linptech Co. ,Ltd. 200000-2FFFFF (base 16) Wuhan Linptech Co. ,Ltd. 3F,Bldg 1A,Lianxiang Enterprise Center @@ -10331,12 +10499,6 @@ E00000-EFFFFF (base 16) Taylor Dynamometer Milwaukee WI 53208 US -C8-63-14 (hex) Freeus LLC -C00000-CFFFFF (base 16) Freeus LLC - 1069 Stewart Drive, Suites 3-6 - Ogden UT 84404 - US - C8-63-14 (hex) Western Reserve Controls, Inc. 000000-0FFFFF (base 16) Western Reserve Controls, Inc. 1485 Exeter Dr. @@ -10853,12 +11015,6 @@ D00000-DFFFFF (base 16) Amiosec Ltd Tewkesbury Gloucestershire GL20 8DN GB -04-C3-E6 (hex) Innovusion Inc. -400000-4FFFFF (base 16) Innovusion Inc. - 4920 El Camino Real - Los Altos CA 94022 - US - 04-C3-E6 (hex) SLOC GmbH 800000-8FFFFF (base 16) SLOC GmbH Nikolaiplatz 4 @@ -11549,12 +11705,6 @@ E00000-EFFFFF (base 16) Gimso Mobile Ltd Nanjing Jiangsu 210000 CN -8C-14-7D (hex) Bluemega Document & Print Services -A00000-AFFFFF (base 16) Bluemega Document & Print Services - 1 Allée de Londres - Batiment Les Bénares - Villejust Essonne 91140 - FR - 50-FF-99 (hex) IPC Global 400000-4FFFFF (base 16) IPC Global 4 Wadhurst Drive @@ -13838,12 +13988,6 @@ E00000-EFFFFF (base 16) JP Morgan Chase Bank, N.A. boston 02210 US -74-25-84 (hex) Shenzhen smart-core technology co.,ltd. -D00000-DFFFFF (base 16) Shenzhen smart-core technology co.,ltd. - 19/F., Finance & Technology Building, No.11 Keyuan Road, Nanshan Dist., Shenzhen, China - Shenzhen Guangdong 518057 - CN - 74-25-84 (hex) EZECONET 600000-6FFFFF (base 16) EZECONET 268-26. Jidong-gil, Apo-eup @@ -14252,12 +14396,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 +14414,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 +14462,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 +14474,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 +14504,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 +14534,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 +14546,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 +14558,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 +14576,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 +14600,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 +14678,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 +14690,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 +14720,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 +14744,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 +14762,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 +14780,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 +14816,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 +14828,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 +14852,273 @@ 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) 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 + +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 + +80-1D-0D (hex) GTL Tecnologia e Sistemas Ltda +400000-4FFFFF (base 16) GTL Tecnologia e Sistemas Ltda + Av. Marcos Penteado de Ulhôa Rodrigues 1119, 16 andar, sala 1611 + Barueri São Paulo 06460040 + BR + +80-1D-0D (hex) Drowsy Digital Inc +600000-6FFFFF (base 16) Drowsy Digital Inc + 1 COOK ST + WESTBOROUGH MA 01581 + US + +80-1D-0D (hex) SZ Spinning Power Top Boundary Technology Co.Ltd. +200000-2FFFFF (base 16) SZ Spinning Power Top Boundary Technology Co.Ltd. + 1261B Yingfeng Center, No.19 Haitian 2nd Road, Binhai Community, Yuehai Street, Nanshan District, Shenzhen + SHENZHEN GUANGDONG 518100 + CN + +CC-E7-DE (hex) Guangdong Sirivision Communication Technology Co.,LTD. +C00000-CFFFFF (base 16) Guangdong Sirivision Communication Technology Co.,LTD. + Building 6, No. 16 Nanmian Road, Humen Town + Dongguan City Guangdong Province 523899 + CN + +CC-E7-DE (hex) Octopus Energy Ltd +200000-2FFFFF (base 16) Octopus Energy Ltd + UK House, 5th Floor, United Kingdom House, 164-182 Oxford St + London W1D 1NN + GB + +CC-E7-DE (hex) Juke Audio +D00000-DFFFFF (base 16) Juke Audio + 112 20th street + Manhattan Beach CA 90266 + US + +CC-E7-DE (hex) Shenzhen Jooan Technology Co., Ltd +000000-0FFFFF (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 + +04-C3-E6 (hex) Seyond +400000-4FFFFF (base 16) Seyond + 160 San Gabriel Dr + Sunnyvale CA 94086 + US + +4C-6E-44 (hex) Windar Photonics A/S +C00000-CFFFFF (base 16) Windar Photonics A/S + Baldersbækvej 24 C, 2635 Ishøj + Ishøj Capital Region of Denmark 2635 + DK + +4C-6E-44 (hex) Private +400000-4FFFFF (base 16) Private + +6C-15-24 (hex) Magicyo Technology CO.,Ltd +400000-4FFFFF (base 16) Magicyo Technology CO.,Ltd + 7F, Tower A, YuZhou Building, No.78 North Keyuan + Shenzhen Nanshan District 518057 + CN + +A4-4F-3E (hex) Netshield Europe Srl +100000-1FFFFF (base 16) Netshield Europe Srl + Viale Bianca Maria 24 + Milano Italy 20129 + IT + +A4-4F-3E (hex) NTT sonority, Inc. +D00000-DFFFFF (base 16) NTT sonority, Inc. + 3-20-2, Nishishinjuku + Shinjuku-ku Tokyo 163-1432 + JP + +A4-4F-3E (hex) United Automotive Electronic Systems Co.,Ltd +000000-0FFFFF (base 16) United Automotive Electronic Systems Co.,Ltd + No.555 Rongqiao Road,Pudong district, Shanghai,China + Shanghai 201206 + CN + +A4-4F-3E (hex) Maven Pet Inc +600000-6FFFFF (base 16) Maven Pet Inc + 800 N King Street Suite 304 2873 Wilmington + Wilmington DE 19801 + US + +F8-C9-D6 (hex) Annapurna labs +500000-5FFFFF (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +F8-C9-D6 (hex) DIAS Infrared GmbH +A00000-AFFFFF (base 16) DIAS Infrared GmbH + Pforzheimer Str. 21 + Dresden Saxony 01189 + DE + +F8-C9-D6 (hex) Zhongzhen Huachuang(Shenzhen)Technology Co.,LTD +C00000-CFFFFF (base 16) Zhongzhen Huachuang(Shenzhen)Technology Co.,LTD + Zhongzhen Building, No. 68 Luofang Road, Luohu District + Shenzhen Guangdong Province 518003 + CN + +F8-C9-D6 (hex) Ningbo Ming Sing Optical R&D Co.,Ltd +000000-0FFFFF (base 16) Ningbo Ming Sing Optical R&D Co.,Ltd + No. 365 Middle Jingu Road (west), Panhuo Street,Yinzhou District, Ningbo,Zhejiang Province,China + Ningbo Zhejiang Province 315100 + CN + +74-25-84 (hex) Shenzhen smart-core technology co.,ltd. +D00000-DFFFFF (base 16) Shenzhen smart-core technology co.,ltd. + 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street Nanshan District + Shenzhen Guangdong 518057 + CN + +DC-36-43 (hex) Shenzhen smart-core technology co.,ltd. +600000-6FFFFF (base 16) Shenzhen smart-core technology co.,ltd. + 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street Nanshan District + Shenzhen Guangdong 518057 + CN + +F8-75-28 (hex) RLS d.o.o. +900000-9FFFFF (base 16) RLS d.o.o. + Pod vrbami 2 + Komenda 1218 + SI + +F8-75-28 (hex) KUNSHAN WONDERTEK TECHNOLOGY CO.,LTD. +600000-6FFFFF (base 16) KUNSHAN WONDERTEK TECHNOLOGY CO.,LTD. + No.369 Kangzhuang Road Zhoushi Town Kunshan City Jiangsu Province China 215300 + KUNSHAN Jiangsu 215300 + CN + +F8-75-28 (hex) Origin Acoustics, LLC +C00000-CFFFFF (base 16) Origin Acoustics, LLC + 6975 South Decatur Blvd, Suite 140 + Las Vegas NV 89118 + US + +0C-0E-C1 (hex) Micron Systems +500000-5FFFFF (base 16) Micron Systems + 20020 State Road + Cerritos CA 90703 + US + +0C-0E-C1 (hex) SWANN COMMUNICATIONS PTY LTD +400000-4FFFFF (base 16) SWANN COMMUNICATIONS PTY LTD + Unit 5B 706 Lorimer Street + Port Melbourne Victoria 3207 + AU + +8C-14-7D (hex) KPAX +A00000-AFFFFF (base 16) KPAX + 1 Allée de Londres - Batiment Les Bénares + Villejust Essonne 91140 + FR + +A0-24-90 (hex) VELVU TECHNOLOGIES PRIVATE LIMITED +700000-7FFFFF (base 16) VELVU TECHNOLOGIES PRIVATE LIMITED + F-2, RIICO INDUSTRIAL AREA, BINDAYAKA + JAIPUR RAJASTHAN 302012 + IN + +A0-24-90 (hex) JKIN +D00000-DFFFFF (base 16) JKIN + #615 Mecazone, 117 Hwanggeum-ro + Gimpo Gyeonggi 10048 + KR + +A0-24-90 (hex) Nektar Inc. +500000-5FFFFF (base 16) Nektar Inc. + 14420 154 Ave + Edmonton Alberta T6V 0K8 + CA + +C8-63-14 (hex) Becklar, LLC +C00000-CFFFFF (base 16) Becklar, LLC + 1069 Stewart Drive, Suites 3-6 + Ogden UT 84404 + US + +A0-24-90 (hex) Elisity, Inc. +800000-8FFFFF (base 16) Elisity, Inc. + 6203 San Ignacio Ave #110 + San Jose CA 95119 + US + +74-A2-35 (hex) Zeroport LTD +400000-4FFFFF (base 16) Zeroport LTD + 5 Sapir st. + Herzelia ISRAEL 4685209 + IL + +74-A2-35 (hex) Efference +100000-1FFFFF (base 16) Efference + 1643 Powell St + San Francisco CA 94133 + US + +74-A2-35 (hex) Procode Technology Litd +800000-8FFFFF (base 16) Procode Technology Litd + Hutwood Court, Bournemouth Road, Chandler's Ford, + Eastleigh Hampshire SO53 3QB + GB + B8-4C-87 (hex) Altronix , Corp A00000-AFFFFF (base 16) Altronix , Corp 140 58th St. Bldg A, Ste 2N @@ -14864,12 +15275,6 @@ E00000-EFFFFF (base 16) Waterkotte GmbH Herne 44628 DE -C8-6B-BC (hex) Shenzhen smart-core technology co.,ltd. -700000-7FFFFF (base 16) Shenzhen smart-core technology co.,ltd. - 19/F., Finance & Technology Building, No.11 Keyuan Road, Nanshan Dist., Shenzhen, China - Shenzhen Guangdong 518057 - CN - C8-6B-BC (hex) ZEUS C00000-CFFFFF (base 16) ZEUS 132, Annyeongnam-ro @@ -20735,12 +21140,6 @@ C00000-CFFFFF (base 16) Sichuan Zhongguang Lightning Protection Technologie REGULY 05-816 PL -10-06-48 (hex) Shenzhen smart-core technology co.,ltd. -B00000-BFFFFF (base 16) Shenzhen smart-core technology co.,ltd. - 19/F., Finance & Technology Building, No.11 Keyuan Road, Nanshan Dist., Shenzhen, China - Shenzhen Guangdong 518057 - CN - 10-06-48 (hex) Hong Kong BOZZ Co., Limited. 600000-6FFFFF (base 16) Hong Kong BOZZ Co., Limited. NO. 33 MONG KOK ROAD, KOWLOON @@ -21773,24 +22172,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 +22208,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 +22220,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 +22232,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 +22262,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 +22292,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 +22304,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,6 +22340,18 @@ F4-97-9D (hex) MERRY ELECTRONICS CO., LTD. TAICHUNG 408213 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 @@ -21959,42 +22370,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 +22406,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 +22424,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 +22451,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 +22517,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 +22565,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 +22583,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 +22619,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 +22655,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 +22667,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 +22691,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 +22709,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 +22721,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 +22760,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 +22802,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 +22832,180 @@ 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) Luxedo +700000-7FFFFF (base 16) Luxedo + 1232 Topside Rd. + Louisville TN 37777 + 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 + +80-1D-0D (hex) LOGICOM SA +C00000-CFFFFF (base 16) LOGICOM SA + 55 Rue de Lisbonne + PARIS 75008 + FR + +20-B3-7F (hex) Kawasaki Thermal Engineering Co.,Ltd. +E00000-EFFFFF (base 16) Kawasaki Thermal Engineering Co.,Ltd. + 1000 Aoji-cho + Kusatsu-shi Shiga 525-8558 + JP + +80-1D-0D (hex) LONGI METER CO.,LTD. +500000-5FFFFF (base 16) LONGI METER CO.,LTD. + No. 25 Guangming Road, Yinchuan (National Level) Economic and Technological Development Zone + Yinchuan Ningxia 750021 + CN + +CC-E7-DE (hex) Private +300000-3FFFFF (base 16) Private + +4C-6E-44 (hex) Swistec GmbH +A00000-AFFFFF (base 16) Swistec GmbH + Keldenicher Str. 18 + Bornheim 53332 + DE + +4C-6E-44 (hex) Quasonix +000000-0FFFFF (base 16) Quasonix + 6025 Schumacher Park Drive + West Chester OH 45069 + US + +CC-E7-DE (hex) Opal Camera Inc. +A00000-AFFFFF (base 16) Opal Camera Inc. + 150 POST STREET, SUITE 700 + SAN FRANCISCO CA 94108 + US + +CC-E7-DE (hex) Skylight +600000-6FFFFF (base 16) Skylight + 101a Clay Street #144 + San Francisco CA 94111 + US + +4C-6E-44 (hex) Accutrol LLC +200000-2FFFFF (base 16) Accutrol LLC + 21 Commerce Dr + Danbury CT 06810 + US + +A4-4F-3E (hex) ShenZhen hionetech Co,.ltd +400000-4FFFFF (base 16) ShenZhen hionetech Co,.ltd + 2112 Baoshan Times Building ,Minzhi Street + LonghuaDistrictShenzhen guangdong 518110 + CN + +10-06-48 (hex) Shenzhen smart-core technology co.,ltd. +B00000-BFFFFF (base 16) Shenzhen smart-core technology co.,ltd. + 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street Nanshan District + Shenzhen Guangdong 518057 + CN + +C8-6B-BC (hex) Shenzhen smart-core technology co.,ltd. +700000-7FFFFF (base 16) Shenzhen smart-core technology co.,ltd. + 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street Nanshan District + Shenzhen Guangdong 518057 + CN + +F8-75-28 (hex) Myers Emergency Power Systems +800000-8FFFFF (base 16) Myers Emergency Power Systems + 44 S Commerce Way + Bethlehem PA 18017 + US + +F8-75-28 (hex) After Technologies +D00000-DFFFFF (base 16) After Technologies + Gaustadalleen 21 + Oslo 0349 + NO + +F8-75-28 (hex) Wuhan Xingtuxinke ELectronic Co.,Ltd +100000-1FFFFF (base 16) Wuhan Xingtuxinke ELectronic Co.,Ltd + NO.C3-8F,Software Park,Optics Valley,East Lake Development Zone,Wuhan,Hubei,China + Wuhan Hubei 430074 + CN + +0C-0E-C1 (hex) naext gmbh +700000-7FFFFF (base 16) naext gmbh + Postweg 190 + Seevetal 21218 + DE + +A0-24-90 (hex) Nefence Inc. +400000-4FFFFF (base 16) Nefence Inc. + 37, Gwacheon-daero 7na-gil + Gwacheon-si Gyeonggi-do 13840 + KR + +A0-24-90 (hex) SHENZHEN ATTEN TECHNOLOGY CO.,LTD +100000-1FFFFF (base 16) SHENZHEN ATTEN TECHNOLOGY CO.,LTD + 7-8/F, Building B, Konka Guangming Technology Center, No. 288 Xingxin Road, Dongzhou Community, Guangming Street, Guangming District, Shenzhen + SHEN ZHEN GUANG DONG 518000 + CN + +A0-24-90 (hex) Bosch Rexroth AG +300000-3FFFFF (base 16) Bosch Rexroth AG + Rossbacher Weg 6 + Erbach 64711 + DE + +A0-24-90 (hex) nodor international +200000-2FFFFF (base 16) nodor international + red dragon darts + BRIDGEND Select a State/County CF31 3PT + GB + +74-A2-35 (hex) SEE Telecom +300000-3FFFFF (base 16) SEE Telecom + Avenue Robert Schuman 201 + Nivelles 1401 + BE + D0-14-11 (hex) P.B. Elettronica srl 100000-1FFFFF (base 16) P.B. Elettronica srl Via Santorelli, 8 @@ -23024,12 +23603,6 @@ E00000-EFFFFF (base 16) UAB Brolis sensor technology Vilnius 14259 LT -D4-61-37 (hex) Shenzhen smart-core technology co.,ltd. -100000-1FFFFF (base 16) Shenzhen smart-core technology co.,ltd. - 19/F., Finance & Technology Building, No.11 Keyuan Road, Nanshan Dist., Shenzhen, China - Shenzhen Guangdong 518057 - CN - D4-61-37 (hex) Robert Bosch Elektronikai Kft. 200000-2FFFFF (base 16) Robert Bosch Elektronikai Kft. Robert Bosch út 1. @@ -26186,12 +26759,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. @@ -28631,12 +29198,6 @@ D00000-DFFFFF (base 16) Zilia Technologies Mail box 15123 Haifa 3508409 IL -5C-87-D8 (hex) Freeus LLC -200000-2FFFFF (base 16) Freeus LLC - 1069 Stewart Drive, Suites 3-6 - Ogden UT 84404 - US - 5C-5A-4C (hex) Orchid Products Limited 100000-1FFFFF (base 16) Orchid Products Limited Unit 702, 7th Floor, One Hung To Road @@ -29264,23 +29825,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 +29843,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 +29861,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 +29897,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 +29915,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 +29957,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 +29975,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 +30023,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 +30041,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 +30077,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,6 +30101,30 @@ F0-40-AF (hex) Nuro.ai Mountain View CA 94070 US +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 + E8-F6-D7 (hex) Mono Technologies Inc. 000000-0FFFFF (base 16) Mono Technologies Inc. 600 N Broad Street, Suite 5 # 924 @@ -29570,42 +30137,36 @@ A00000-AFFFFF (base 16) Ivostud GmbH Breckerfeld 58339 DE -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 - 74-33-36 (hex) Shenzhen Handheld-Wireless Technology Co., Ltd. C00000-CFFFFF (base 16) Shenzhen Handheld-Wireless Technology Co., Ltd. 702-1, Building 5, Gonglian Fuji Innovation Park, No. 58 Ping'an Road, Dafu Community, Guanlan Street, Longhua District, 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 +30185,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 +30215,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 +30239,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 +30251,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 +30269,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 +30281,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 +30323,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 +30335,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 +30347,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,15 +30374,261 @@ 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 +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 + +80-1D-0D (hex) HANGZHOU INNOWAVEPOWER ELECTRONIC TECHNOLOGY CO.,LTD +700000-7FFFFF (base 16) HANGZHOU INNOWAVEPOWER ELECTRONIC TECHNOLOGY CO.,LTD + 99#, 8th Kenhui Road, Xinjie street, Xiaoshan District, Hangzhou, Zhejiang CN 311232 + HANGZHOU ZHEJIANG 311232 + CN + +80-1D-0D (hex) KbDevice,Inc. +000000-0FFFFF (base 16) KbDevice,Inc. + 22-2, Hontorocho, Shimogyo-ku, Kyoto-shi + Kyoto 600-8086 + JP + +80-1D-0D (hex) CRESTCHIC (UK) LIMITED +800000-8FFFFF (base 16) CRESTCHIC (UK) LIMITED + Second AvenueCentrum 100 + Burton upon Trent DE14 2WF + GB + +CC-E7-DE (hex) Shenzhen Qichang Intelligent Technology Co., Ltd. +900000-9FFFFF (base 16) Shenzhen Qichang Intelligent Technology Co., Ltd. + 13th F, Building 1, West Area, Hongrongyuan HonghuTechnology Park, No.22 Ping'an Road, ZhangxiCommunity, Guanhu Street + Shenzhen Guang dong 518000 + CN + +CC-E7-DE (hex) Shanghai Dabuziduo Information and Technology Co., Ltd. +700000-7FFFFF (base 16) Shanghai Dabuziduo Information and Technology Co., Ltd. + #818, #298 Guoxia Rd, Yangpu Dist + Shanghai Shanghai 200000 + CN + +CC-E7-DE (hex) Private 800000-8FFFFF (base 16) Private +C4-82-72 (hex) NextSilicon +300000-3FFFFF (base 16) NextSilicon + Derekh Begin 33 + Gibatayim 5348303 + IL + +4C-6E-44 (hex) Panache DigiLife Limited +600000-6FFFFF (base 16) Panache DigiLife Limited + B-507, Raheja Plaza Premises CSL, LBS Marg, Ghatkopar West , Maharashtra 400086 + Mumbai Maharashtra 400086 + IN + +4C-6E-44 (hex) Qingting Intelligent Technology(Suzhou)Co.,Ltd. +300000-3FFFFF (base 16) Qingting Intelligent Technology(Suzhou)Co.,Ltd. + Room 302, 3rd Floor, R&D Building 9, No. 9, 1999 Songjia Road, Guoxiang Street, Wuzhong Economic Development Zone + Suzhou JiangSu 215000 + CN + +4C-6E-44 (hex) 1Home Solutions GmbH +D00000-DFFFFF (base 16) 1Home Solutions GmbH + Friedrichstrasse 155 + Berlin 10117 + DE + +4C-6E-44 (hex) Shenzhen Jooan Technology Co., Ltd +E00000-EFFFFF (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 + +4C-6E-44 (hex) Luxshare Electronic Technology (KunShan) Ltd +700000-7FFFFF (base 16) Luxshare Electronic Technology (KunShan) Ltd + No. 699 Jinshang Road, Jinxi Town, Kunshan City, Jiangsu Province + Kunshan Jiangsu 215300 + CN + +4C-6E-44 (hex) Shenzhen Xmitech Electronic Co.,Ltd +100000-1FFFFF (base 16) Shenzhen Xmitech Electronic Co.,Ltd + Room 8B1888, Block AB, New Energy Building, No.2239, Nanhai Avenue, Nanguang Community, Nanshan Street, Nanshan District, Shenzhen + Shenzhen 518054 + CN + +A4-4F-3E (hex) Annapurna labs +300000-3FFFFF (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +A4-4F-3E (hex) Suzhou AIDomex Intelligent Technology Co., Ltd. +B00000-BFFFFF (base 16) Suzhou AIDomex Intelligent Technology Co., Ltd. + B422,18th.Zhanye RD. SIP. Suzhou China + Suzhou Jiangsu 215122 + CN + +F8-C9-D6 (hex) Annapurna labs +200000-2FFFFF (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +A4-4F-3E (hex) CMCNI Co., Ltd +A00000-AFFFFF (base 16) CMCNI Co., Ltd + B-601 Hangang Xi TowerYangcheonro 401Gangseogu + Seoul 07528 + KR + +D4-61-37 (hex) Shenzhen smart-core technology co.,ltd. +100000-1FFFFF (base 16) Shenzhen smart-core technology co.,ltd. + 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street Nanshan District + Shenzhen Guangdong 518057 + CN + +F8-C9-D6 (hex) Lecip Arcontia AB +700000-7FFFFF (base 16) Lecip Arcontia AB + Mässans Gata 10 + Gothenburg 40224 + SE + +F8-C9-D6 (hex) Beijing Mlink Technology Inc. +100000-1FFFFF (base 16) Beijing Mlink Technology Inc. + 5th Floor, North Lobby, Building B, East Side Science and Innovation Center, Phase III, Zhongguancun Dongsheng Science and Technology Park, Haidian District, Beijing + Beijing 100080 + CN + +F8-C9-D6 (hex) CPflight_srl +300000-3FFFFF (base 16) CPflight_srl + Via_Antica_Regina_24 + Tremezzina Como 22016 + IT + +F8-C9-D6 (hex) Dimetix AG +900000-9FFFFF (base 16) Dimetix AG + Degersheimerstrasse 14 + Herisau 9100 + CH + +F8-75-28 (hex) Qube Cinema Technologies Pvt Ltd +000000-0FFFFF (base 16) Qube Cinema Technologies Pvt Ltd + 42 Dr Ranga Road + Chennai Tamil Nadu 600004 + IN + +F8-C9-D6 (hex) Shenzhen smart-core technology co.,ltd. +E00000-EFFFFF (base 16) Shenzhen smart-core technology co.,ltd. + 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street 10th Floor, Building A, Chuangyi Kexing Science Park, No. 198, Keji Zhongyi Road, Yuehai Street Nanshan District + Shenzhen Guangdong 518057 + CN + +F8-75-28 (hex) PANASONIC AUTOMOTIVE SYSTEM MALAYSIA +700000-7FFFFF (base 16) PANASONIC AUTOMOTIVE SYSTEM MALAYSIA + PLOT 10, PHASE 4PRAI INDUSTRAIL ESTATE + PRAI PENANG 13600 + MY + +0C-0E-C1 (hex) tecget GmbH +B00000-BFFFFF (base 16) tecget GmbH + Schafjueckenweg 1 + Rastede 26180 + DE + +0C-0E-C1 (hex) DELTACAST.TV +100000-1FFFFF (base 16) DELTACAST.TV + Rue Gilles Magnee 92/6 + ANS 4430 + BE + +0C-0E-C1 (hex) Insky Communications Private Limited +C00000-CFFFFF (base 16) Insky Communications Private Limited + Block-D/102, Paras Maitri,Anant Nagar Phase 1 + Bengaluru Karnataka 560100 + IN + +0C-0E-C1 (hex) BITS AND BYTE IT CONSULTING PVT LTD +800000-8FFFFF (base 16) BITS AND BYTE IT CONSULTING PVT LTD + NO 52, 2ND FLOOR, VITTAL MALLYA ROAD + BANGALORE KARNATAKA 560001 + IN + +A0-24-90 (hex) SANSHA ELECTRIC MANUFACTURING CO., LTD. +900000-9FFFFF (base 16) SANSHA ELECTRIC MANUFACTURING CO., LTD. + 3-1-56, Nishiawaji, Higashiyodogawa-ku + Osaka 533-0031 + JP + +A0-24-90 (hex) Jackal Design LLC +000000-0FFFFF (base 16) Jackal Design LLC + 410 Atlantic Avenue + Rochester NY 14609 + US + +5C-87-D8 (hex) Becklar, LLC +200000-2FFFFF (base 16) Becklar, LLC + 1069 Stewart Drive, Suites 3-6 + Ogden UT 84404 + US + +A0-24-90 (hex) Mini Motor Spa +E00000-EFFFFF (base 16) Mini Motor Spa + Via E.Fermi 5 + Bagnolo in Piano italy 42011 + IT + +74-A2-35 (hex) Flextronics Technologies India Private Limited +000000-0FFFFF (base 16) Flextronics Technologies India Private Limited + NO 90, SURVEY NO 400, 402 ASV MINDSPACE SP ROAD,UTHUKKADU KATTAVAKKAM VILLAGE + WALLAJABAD TAMILNADU 636105 + IN + +74-A2-35 (hex) Neuromod Devices Ltd. +D00000-DFFFFF (base 16) Neuromod Devices Ltd. + Rainsford St + Dublin Select State D08 R2YP + IE + C8-5C-E2 (hex) Fela Management AG 000000-0FFFFF (base 16) Fela Management AG Basadingerstrasse 18 @@ -31055,12 +31862,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 +37574,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 +37586,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 +37598,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 +37616,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 +37634,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 +37658,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 +37676,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 @@ -36911,11 +37712,11 @@ FC-E4-98 (hex) E Haute Intelligent Technology Co., Ltd 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 +34-B5-F3 (hex) LAUMAS Elettronica s.r.l. +400000-4FFFFF (base 16) LAUMAS Elettronica s.r.l. + via I Maggio, 6 - IT01661140341 + Montechiarugolo 43022 + IT 00-6A-5E (hex) BroadMaster Biotech Corp 100000-1FFFFF (base 16) BroadMaster Biotech Corp @@ -36923,35 +37724,23 @@ FC-E4-98 (hex) E Haute Intelligent Technology Co., Ltd 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 - -34-B5-F3 (hex) LAUMAS Elettronica s.r.l. -400000-4FFFFF (base 16) LAUMAS Elettronica s.r.l. - via I Maggio, 6 - IT01661140341 - Montechiarugolo 43022 - IT - 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 +37754,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 +37796,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 +37850,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 +37928,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 +37958,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 +37970,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 +37988,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 +38012,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 +38030,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 +38054,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 +38090,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 +38120,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 +38144,272 @@ 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 + +80-1D-0D (hex) 802 Secure +D00000-DFFFFF (base 16) 802 Secure + 1285 66th Street + Emeryville CA 94608 + US + +80-1D-0D (hex) Luxshare Electronic Technology (KunShan) Ltd +A00000-AFFFFF (base 16) Luxshare Electronic Technology (KunShan) Ltd + No. 699 Jinshang Road, Jinxi Town, Kunshan City, Jiangsu Province + Kunshan Jiangsu 215300 + CN + +80-1D-0D (hex) Syrma SGS Technology +B00000-BFFFFF (base 16) Syrma SGS Technology + MEPTZ , TAMBARAM + Chennai Tamil Nadu 600045 + IN + +80-1D-0D (hex) Lecoo Technology Co.,Ltd. +300000-3FFFFF (base 16) Lecoo Technology Co.,Ltd. + Room 103, No. 1, Second Avenue, Tianjin Airport International Logistics Park, Tianjin Pilot Free TradeZone + Tianjin Tianjin 300000 + CN + +80-1D-0D (hex) Shanghai ReveISpark Technologies Co.,Ltd. +E00000-EFFFFF (base 16) Shanghai ReveISpark Technologies Co.,Ltd. + 2nd Floor,No.25-1 Hongcao Road,Xuhui District,Shanghai,China + Shanghai 200233 + CN + +20-B3-7F (hex) B810 SPA +000000-0FFFFF (base 16) B810 SPA + Via Enzo Lazzaretti, 2/1 + REGGIO EMILIA Reggio Emilia 42122 + IT + +CC-E7-DE (hex) 3D Computing +B00000-BFFFFF (base 16) 3D Computing + D102, 2nd floor, street no 5, laxmi nagar, delhi 110092 + Delhi Delhi 110092 + IN + +80-1D-0D (hex) Hörmann Warnsysteme GmbH +100000-1FFFFF (base 16) Hörmann Warnsysteme GmbH + Hauptstr. 45-47 + Kirchseeon Bavaria 85614 + DE + +CC-E7-DE (hex) Fareco +400000-4FFFFF (base 16) Fareco + Arteparc de Meyreuil Bat C Route de la cote AzurLe Canet + MEYREUIL 13590 + FR + +4C-6E-44 (hex) Chengdu Ruibitechuang Technology Co.,Ltd +900000-9FFFFF (base 16) Chengdu Ruibitechuang Technology Co.,Ltd + Room 34, 7th Floor, Unit 2, Building 5, No. 68 Yangzi Mountain Road, Chenghua District + Chengdu Sichuan 610000 + CN + +A4-4F-3E (hex) Vinfast Trading and Production JSC +700000-7FFFFF (base 16) Vinfast Trading and Production JSC + Dinh Vu - Cat Hai Economic Zone, Cai Hai Island, Cat Hai Sepcial Administrative Zone + Hai Phong Hai Phong 180000 + VN + +A4-4F-3E (hex) Neurable +800000-8FFFFF (base 16) Neurable + 45 Bromfield St + Boston 02108 + US + +A4-4F-3E (hex) LINK Group Inc. +900000-9FFFFF (base 16) LINK Group Inc. + 43855 Plymouth Oaks Blvd + Plymouth MI 48170 + US + +A4-4F-3E (hex) Mobilint +500000-5FFFFF (base 16) Mobilint + 35, Seolleung-ro 93-gil + Seoul Gangnam-gu 06151 + KR + +F8-C9-D6 (hex) Fortis Medical Devices LTD +D00000-DFFFFF (base 16) Fortis Medical Devices LTD + Barna Road + Galway Galway H91 HP83 + IE + +F8-C9-D6 (hex) Miri Technologies, Inc +800000-8FFFFF (base 16) Miri Technologies, Inc + 156 Madison Ave + Reading PA 19605 + US + +F8-C9-D6 (hex) Active Research Limited +400000-4FFFFF (base 16) Active Research Limited + 21 Harwell Road + Poole Dorset BH17 0GE + GB + +F8-75-28 (hex) NORBIT ASA +500000-5FFFFF (base 16) NORBIT ASA + Stiklestadveien 1 + Trondheim 7041 + NO + +F8-75-28 (hex) Siact Hinton (Beijing) Intelligent Control Technology Co., Ltd. +A00000-AFFFFF (base 16) Siact Hinton (Beijing) Intelligent Control Technology Co., Ltd. + 6th Floor, Building 6, Courtyard 5, West Laiguangying Road, Chaoyang District, Beijing + Beijing Beijing 100024 + CN + +F8-75-28 (hex) Lyte AI +200000-2FFFFF (base 16) Lyte AI + 185 N Wolfe Rd. + Sunnyvale CA 94583 + US + +F8-75-28 (hex) Annapurna labs +400000-4FFFFF (base 16) Annapurna labs + Matam Scientific Industries Center, Building 8.2 + Mail box 15123 Haifa 3508409 + IL + +0C-0E-C1 (hex) COGITO TECH COMPANY LIMITED +600000-6FFFFF (base 16) COGITO TECH COMPANY LIMITED + 21/F TAI YAU BLDG 181 JOHNSON RD WANCHAI HONG KONG + HONG KONG 999077 + CN + +0C-0E-C1 (hex) Lupa Tecnologia e Sistemas Ltda +300000-3FFFFF (base 16) Lupa Tecnologia e Sistemas Ltda + Rua Viscondessa de Cavalcanti, 50 - Poço Rico + Juiz de Fora Minas Gerais 36020-070 + BR + +0C-0E-C1 (hex) GO.N.GO Ltd +900000-9FFFFF (base 16) GO.N.GO Ltd + Hamada 8 + Herzliya 4673342 + IL + +A0-24-90 (hex) Tauro Technologies +B00000-BFFFFF (base 16) Tauro Technologies + 9205 W. Russell Rd, Suite 240 + Las Vegas NV 89148 + US + +0C-0E-C1 (hex) t0.technology Inc. +E00000-EFFFFF (base 16) t0.technology Inc. + 300 Rue Leo-Pariseau #2200 + Montreal Quebec H2X 4B3 + CA + +0C-0E-C1 (hex) MTS Systems Corp. +200000-2FFFFF (base 16) MTS Systems Corp. + 14000 Technology Drive + Eden Prairie MN 55344 + US + +A0-24-90 (hex) WiE GmbH - Werk für industrielle Elektronik +C00000-CFFFFF (base 16) WiE GmbH - Werk für industrielle Elektronik + Am Mühlgraben 3 + Kreischa 01731 + DE + +A0-24-90 (hex) Briggs & Stratton LLC +A00000-AFFFFF (base 16) Briggs & Stratton LLC + 12301 W. Wirth St. + Wauwatosa WI 53222 + US + +74-A2-35 (hex) DeepSea Technologies +200000-2FFFFF (base 16) DeepSea Technologies + Stadiou 24 + Athens 105 64 + GR diff --git a/hwdb.d/ma-small.txt b/hwdb.d/ma-small.txt index bf3496a60810e..8210c5a0d916c 100644 --- a/hwdb.d/ma-small.txt +++ b/hwdb.d/ma-small.txt @@ -170,12 +170,6 @@ AD8000-AD8FFF (base 16) Novanta IMS Marlborough CT 06447 US -8C-1F-64 (hex) Power Electronics Espana, S.L. -362000-362FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Mecos AG 48F000-48FFFF (base 16) Mecos AG Hardstrasse 319 @@ -383,12 +377,6 @@ A33000-A33FFF (base 16) TIAMA New Territories 852 HK -8C-1F-64 (hex) VT100 SRL -66D000-66DFFF (base 16) VT100 SRL - Via A. Meucci 11 - Caldiero ITALY 37042 - IT - 8C-1F-64 (hex) Flextronics International Kft 154000-154FFF (base 16) Flextronics International Kft 38. Zrinyi Str. @@ -479,12 +467,6 @@ BF1000-BF1FFF (base 16) Soha Jin Chandler 85286 US -8C-1F-64 (hex) Power Electronics Espana, S.L. -DA6000-DA6FFF (base 16) Power Electronics Espana, S.L. - Pol Industrial Carrases Ronda del Camp d’Aviació,4 - Lliria Valencia 46160 - ES - 8C-1F-64 (hex) Scarlet Tech Co., Ltd. 37F000-37FFFF (base 16) Scarlet Tech Co., Ltd. 4F-3, 347 HePing E Rd 2nd Sec, Daan Dist @@ -644,12 +626,6 @@ B08000-B08FFF (base 16) Cronus Electronics Koesching 85092 DE -8C-1F-64 (hex) Power Electronics Espana, S.L. -B9E000-B9EFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) GVA Lighting, Inc. 44E000-44EFFF (base 16) GVA Lighting, Inc. 2771 Bristol Circle @@ -1148,12 +1124,6 @@ EB2000-EB2FFF (base 16) Aqua Broadcast Ltd Windsor NJ 07762 US -8C-1F-64 (hex) Power Electronics Espana, S.L. -FE3000-FE3FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Aspen Spectra Sdn Bhd 41D000-41DFFF (base 16) Aspen Spectra Sdn Bhd 51-10, The Boulevard, Mid Valley City @@ -2282,12 +2252,6 @@ E46000-E46FFF (base 16) 7thSense Design Limited HONFLEUR 14600 FR -70-B3-D5 (hex) Power Electronics Espana, S.L. -632000-632FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) Hermann Sewerin GmbH 484000-484FFF (base 16) Hermann Sewerin GmbH Robert-Bosch-Str. 3 @@ -2618,12 +2582,6 @@ B95000-B95FFF (base 16) EPIImaging Los Altos CA 94024 US -70-B3-D5 (hex) Power Electronics Espana, S.L. -56E000-56EFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) Clecell 565000-565FFF (base 16) Clecell 26, Beobwon-ro 9-gil @@ -4418,12 +4376,6 @@ A73000-A73FFF (base 16) MobiPromo Champigny sur Marne France 94500 FR -70-B3-D5 (hex) Power Electronics Espana, S.L. -B56000-B56FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) Seraphim Optronics Ltd ADF000-ADFFFF (base 16) Seraphim Optronics Ltd 2 hacarmel st @@ -7463,12 +7415,6 @@ B17000-B17FFF (base 16) DAT Informatics Pvt Ltd NOF HAGLIL 1789062 IL -8C-1F-64 (hex) Power Electronics Espana, S.L. -82C000-82CFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Canfield Scientific Inc 470000-470FFF (base 16) Canfield Scientific Inc 4 Wood Hollow Road @@ -7787,6 +7733,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 +7751,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 +7763,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 +7787,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 +7824,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 +7877,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 +7895,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 +7907,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 +7937,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 +7955,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 +7967,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 +7985,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 +8039,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 +8051,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 +8063,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 +8081,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 +8093,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 +8117,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 +8141,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 +8171,240 @@ 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) NEOX Networks +9A0000-9A0FFF (base 16) NEOX Networks + Monzastraße 4 + Langen Hesse 63225 + DE + +8C-1F-64 (hex) Potter Electric Signal Company +A25000-A25FFF (base 16) Potter Electric Signal Company + 1609 Park 370 Place + Hazelwood MO 63042 + US + +8C-1F-64 (hex) AT-Automation Technology GmbH +2DA000-2DAFFF (base 16) AT-Automation Technology GmbH + Hermann-Boessow-Str. 6-8 + Bad Oldesloe D-23843 + DE + +8C-1F-64 (hex) Right Time Sports LLC +6F0000-6F0FFF (base 16) Right Time Sports LLC + PO Box 684 + Supply NC 28462 + US + +8C-1F-64 (hex) Abbott Diagnostics Technologies AS +7EA000-7EAFFF (base 16) Abbott Diagnostics Technologies AS + P. O. Box 6863 Rodeløkka + Oslo Oslo 0504 + NO + +8C-1F-64 (hex) Xi'an Biangu Information Technology Co., Ltd. +D35000-D35FFF (base 16) Xi'an Biangu Information Technology Co., Ltd. + Room 1601, Building A, Innovation and Entrepreneurship Center, No. 8989 Shangji Road, Economic and Technological Development Zone, Xi'an, Shaanxi Province, China. + Xi'an Shanxi 710048 + CN + +8C-1F-64 (hex) Headwave +C65000-C65FFF (base 16) Headwave + Marie-Elisab.-V.-Humboldt 35a + Berlin 13057 + DE + +8C-1F-64 (hex) Portrait Displays, Inc. +893000-893FFF (base 16) Portrait Displays, Inc. + 4637 Chabot Drive, Suite 115 + Pleasanton CA 94588 + US + +8C-1F-64 (hex) Cognicom, Inc. +FE6000-FE6FFF (base 16) Cognicom, Inc. + 5095 Murphy Canyon Rd, Ste 240 + San Diego CA 92123 + US + +8C-1F-64 (hex) TEX COMPUTER SRL +1CC000-1CCFFF (base 16) TEX COMPUTER SRL + Via O. Respighi 13 + CATTOLICA RIMINI 47841 + IT + +8C-1F-64 (hex) JS Tech Co., Ltd. +51C000-51CFFF (base 16) JS Tech Co., Ltd. + Room 807, Building A, 190 Soha-ro + Gwangmyeong-si Gyeonggi-do 14322 + KR + +8C-1F-64 (hex) VT100 SRL +66D000-66DFFF (base 16) VT100 SRL + Viale Dell'Artigianato 4 + Caldiero ITALY 37042 + IT + +8C-1F-64 (hex) MyDefence A/S +E0D000-E0DFFF (base 16) MyDefence A/S + Bouet Mollevej 5 + Norresundby NJ 9400 + DK + +8C-1F-64 (hex) Osec +8EF000-8EFFFF (base 16) Osec + netwerk 120 + Purmerend 1446XA + NL + +8C-1F-64 (hex) FLUGCOM GmbH +ABA000-ABAFFF (base 16) FLUGCOM GmbH + Lyoner str. 15 + Frankfurt am Main Hessen 60528 + DE + +8C-1F-64 (hex) Power Electronics Espana, S.L. +362000-362FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +DA6000-DA6FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +B9E000-B9EFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +FE3000-FE3FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +632000-632FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +56E000-56EFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Talleres de Escoriaza SAU +955000-955FFF (base 16) Talleres de Escoriaza SAU + Barrio Ventas 35, Irun + Irun Gipuzkoa 20305 + ES + +8C-1F-64 (hex) WAVEBAND TECHNOLOGIES PVT LIMITED +36D000-36DFFF (base 16) WAVEBAND TECHNOLOGIES PVT LIMITED + 103, VISHAL TOWER, JANAKPURI DISTRICT CENTRE, + NEW DELHI DELHI 110058 + IN + +8C-1F-64 (hex) Vitaltrace PTY LTD +5CA000-5CAFFF (base 16) Vitaltrace PTY LTD + 43 Action Road + Malaga WA 6090 + AU + +8C-1F-64 (hex) Power Electronics Espana, S.L. +82C000-82CFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +B56000-B56FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Pionierkraft GmbH +6C7000-6C7FFF (base 16) Pionierkraft GmbH + Osterwaldstr. 10 - D13 + Munich Bavaria 80805 + DE + +8C-1F-64 (hex) Brightwell Dosing ltd +C7F000-C7FFFF (base 16) Brightwell Dosing ltd + Avis Way + Newhaven East Sussex BN9 0DU + GB + +8C-1F-64 (hex) ACE TECH +0DE000-0DEFFF (base 16) ACE TECH + 57, Hyeoksinsandan 3-gil, Wanggok-myeon, Naju-si, Jeollanam-do, Republic of Korea + Naju-si Jeollanam-do 58296 + KR + +8C-1F-64 (hex) Immersive Audio Technologies +FBC000-FBCFFF (base 16) Immersive Audio Technologies + 13 rue Benoit Frachon + Saint Herblain Pays de la Loire 44800 + FR + +8C-1F-64 (hex) KST technology +084000-084FFF (base 16) KST technology + KST B/D 4-5, Wiryeseong-daero 12-gil + Songpa-gu Seoul 05636 + KR + +8C-1F-64 (hex) KRONOTECH SRL +CC3000-CC3FFF (base 16) KRONOTECH SRL + VIALE UNGHERIA 125 + UDINE ITALY/UDINE 33100 + IT + +8C-1F-64 (hex) HBTECH +39C000-39CFFF (base 16) HBTECH + 11-32, Sangdeok-ro, Jiksan-eup, Seobuk-gu, Cheonan-si, Chungcheongnam-do, Republic of Korea + Cheonan-si Chungcheongnam-do 31029 + KR + +8C-1F-64 (hex) SLAT +847000-847FFF (base 16) SLAT + 11 Rue Jean-Elysée DUPUY + Champagne au Mont d'Or Rhône 69543 + FR + 8C-1F-64 (hex) Jacobs Technology, Inc. A98000-A98FFF (base 16) Jacobs Technology, Inc. 7765 Old Telegraph Road @@ -8249,12 +8429,6 @@ B5A000-B5AFFF (base 16) YUYAMA MFG Co.,Ltd MEISHINGUCHI,TOYONAKA OSAKA 561-0841 JP -8C-1F-64 (hex) Power Electronics Espana, S.L. -E80000-E80FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) RCH SPA DA9000-DA9FFF (base 16) RCH SPA Via Cendon 39 @@ -8585,12 +8759,6 @@ DD4000-DD4FFF (base 16) Midlands Technical Co., Ltd. shinjyuku-ku Tokyo 169-0075 JP -8C-1F-64 (hex) Power Electronics Espana, S.L. -0D8000-0D8FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) 3D perception AS A81000-A81FFF (base 16) 3D perception AS Nye Vakas vei 14 @@ -11357,12 +11525,6 @@ EA1000-EA1FFF (base 16) Qntra Technology Shenzhen Guangdong 518102 CN -70-B3-D5 (hex) Power Electronics Espana, S.L. -AB2000-AB2FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) BIGHOUSE.,INC. B80000-B80FFF (base 16) BIGHOUSE.,INC. 72-11, Pyeongchangmunwha-ro @@ -14003,12 +14165,6 @@ FE7000-FE7FFF (base 16) VEILUX INC. Trigg Western Australia 6029 AU -70-B3-D5 (hex) Power Electronics Espana, S.L. -431000-431FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) JK DEVICE CORPORATION 4DC000-4DCFFF (base 16) JK DEVICE CORPORATION RYOGOKU 4-35-1-304 @@ -15020,12 +15176,6 @@ BB1000-BB1FFF (base 16) Transit Solutions, LLC. Yuseong-gu Daejeon 34044 KR -8C-1F-64 (hex) Power Electronics Espana, S.L. -C83000-C83FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Flextronics International Kft 2DD000-2DDFFF (base 16) Flextronics International Kft 38. Zrinyi Str. @@ -15983,18 +16133,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 +16169,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 +16223,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 +16331,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 +16343,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 +16385,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 +16397,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 +16427,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 @@ -16313,18 +16463,18 @@ A28000-A28FFF (base 16) Monnit Corporation Warriewood NSW 2102 AU -8C-1F-64 (hex) Power Electronics Espana, S.L. -EB8000-EB8FFF (base 16) Power Electronics Espana, S.L. - Ctra. CV-35, Salida 30 Parcela M-13. Pla de Carrases B - LIRIA, Valencia Valencia 46160 - ES - 70-B3-D5 (hex) BAE Systems 1D7000-1D7FFF (base 16) BAE Systems Waterside House, 170 Priestley Road, Surrey Research Park 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 +16487,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 +16541,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 +16565,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 +16583,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 +16595,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 +16631,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 +16655,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 +16685,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 +16700,228 @@ 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) Shanghai Jarue Microsystem.CO.,Ltd. +D82000-D82FFF (base 16) Shanghai Jarue Microsystem.CO.,Ltd. + No. G5-118, Lane 3188, XiuPu Road, Pudong New Area + Shanghai Shanghai 200000 + CN + +8C-1F-64 (hex) SPIE Dürr Austria GmbH +360000-360FFF (base 16) SPIE Dürr Austria GmbH + Frank-Stronach-Straße 5 + Gleisdorf 8200 + AT + +8C-1F-64 (hex) Eon Instrumentation +308000-308FFF (base 16) Eon Instrumentation + 16333 Raymer Street Suite B + Van Nuys CA 91406 + US + +8C-1F-64 (hex) Cyberkar Systems inc. +DF0000-DF0FFF (base 16) Cyberkar Systems inc. + 3026 Anderson suit 202 + Terrebonne Quebec J6Y1W1 + CA + +8C-1F-64 (hex) EA Elektro-Automatik GmbH +57C000-57CFFF (base 16) EA Elektro-Automatik GmbH + Helmholtzstraße 31-37 + Viersen Nordrhein-Westfalen 41747 + DE + +8C-1F-64 (hex) Gogo BA +0AE000-0AEFFF (base 16) Gogo BA + 105 Edgeview Drive + Broomfield CO 80021 + US + +8C-1F-64 (hex) LEMIER +C95000-C95FFF (base 16) LEMIER + Entuziastov 34-4-21 + Moscow 105118 + RU + +8C-1F-64 (hex) MB connect line GmbH +12F000-12FFFF (base 16) MB connect line GmbH + Winnettener Strasse 6 + Dinkelsbuehl Bavaria 91550 + DE + +8C-1F-64 (hex) Jiangsu Yi Rong Mstar Technology Ltd. +5F4000-5F4FFF (base 16) Jiangsu Yi Rong Mstar Technology Ltd. + Building 1-4, No. 9, Yongfu Road, Dafeng Economic Development Zone, + Yancheng City Jiangsu 224199 + CN + +8C-1F-64 (hex) ICS SOLUTIONS INC. +C2E000-C2EFFF (base 16) ICS SOLUTIONS INC. + E-705,706, songdo-miraero, younsu-gu + incheon 21990 + KR + +8C-1F-64 (hex) Gyros Protein Technologies AB +633000-633FFF (base 16) Gyros Protein Technologies AB + Kungsängstull 4 + Uppsala 75319 + SE + +8C-1F-64 (hex) Lumiplan Duhamel +823000-823FFF (base 16) Lumiplan Duhamel + 2 rue de l'industrie + Domène Isère 38420 + FR + +70-B3-D5 (hex) Power Electronics Espana, S.L. +AB2000-AB2FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +431000-431FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +EB8000-EB8FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +E80000-E80FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +0D8000-0D8FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +C83000-C83FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) XPS ELETRONICA LTDA +F7B000-F7BFFF (base 16) XPS ELETRONICA LTDA + AVENIDA JAÇANÃ, 470/474 - VILA NELSON + SÃO PAULO SÃO PAULO 02273-001 + BR + +8C-1F-64 (hex) YUYAMA MFG Co.,Ltd +906000-906FFF (base 16) YUYAMA MFG Co.,Ltd + 1-4-30 + MEISHINGUCHI,TOYONAKA OSAKA 561-0841 + JP + +8C-1F-64 (hex) Real Random +4B1000-4B1FFF (base 16) Real Random + 570 S. Barfield Dr + Marco Island FL 34145-5921 + US + +8C-1F-64 (hex) Integer.pl S.A. +D57000-D57FFF (base 16) Integer.pl S.A. + Wielicka 28 + Krakow 30-552 + PL + +8C-1F-64 (hex) Editech Co., Ltd. +430000-430FFF (base 16) Editech Co., Ltd. + #601 Anyang Sk V1 Center 25-32 + Dongan-Gu, Anyang-Si Gyeonggi-do 14118 + KR + +8C-1F-64 (hex) Rational Production srl Unipersonale +98E000-98EFFF (base 16) Rational Production srl Unipersonale + Via L. Galvani 7/H + Albano S. Alessandro Bergamo 24061 + IT + +8C-1F-64 (hex) SPACE CREATORS ALLIANCE Inc. +D33000-D33FFF (base 16) SPACE CREATORS ALLIANCE Inc. + 75 Waseda + Shinjuku Tokyo 162-0042 + JP + +8C-1F-64 (hex) ADITUS GmbH +7F7000-7F7FFF (base 16) ADITUS GmbH + Straße der Nationen 5 + Hannover 30539 + DE + +8C-1F-64 (hex) GJD Manufacturing +184000-184FFF (base 16) GJD Manufacturing + Unit 2, Birch Business Park, Whittle Lane + Heywood OL10 2SX + GB + +8C-1F-64 (hex) FullohmKD +E69000-E69FFF (base 16) FullohmKD + 150 GONGDAN-RO + GUNPO GYEONGI 15845 + KR + +8C-1F-64 (hex) Daifuku CO., Ltd. +997000-997FFF (base 16) Daifuku CO., Ltd. + 1225 Nakazaiji, Hino-cho, Gamo-gun, Shiga + Gamo-gun Shiga-ken 529-1692 + JP + 8C-1F-64 (hex) Vision Systems Safety Tech E6F000-E6FFFF (base 16) Vision Systems Safety Tech 5 Chemin de Chiradie @@ -21893,12 +22259,6 @@ A2E000-A2EFFF (base 16) Kokam Co., Ltd Suwon-si Gyeonggi-do 16203 KR -70-B3-D5 (hex) Power Electronics Espana, S.L. -148000-148FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) A.F.MENSAH, INC F5B000-F5BFFF (base 16) A.F.MENSAH, INC 252 NASSAU ST, 2ND FLOOR @@ -22319,12 +22679,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 @@ -22364,12 +22718,6 @@ C55000-C55FFF (base 16) Intelligent Energy Ltd Loughborough Leicestershire LE11 3GB GB -70-B3-D5 (hex) Creotech Instruments S.A. -91E000-91EFFF (base 16) Creotech Instruments S.A. - ul. Gen. L. Okulickiego 7/9 - Piaseczno Mazovia 05-500 - PL - 70-B3-D5 (hex) McKay Brothers LLC A4B000-A4BFFF (base 16) McKay Brothers LLC 2355 Broadway @@ -23111,12 +23459,6 @@ FB9000-FB9FFF (base 16) IWS Global Pty Ltd Perth Western Australia 6090 AU -8C-1F-64 (hex) Power Electronics Espana, S.L. -1DE000-1DEFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Infosoft Digital Design and Services P L C9A000-C9AFFF (base 16) Infosoft Digital Design and Services P L 484, SECTOR-8 ,IMT MANESER,GURGAONMANESER @@ -24152,12 +24494,6 @@ EBE000-EBEFFF (base 16) Trafag Italia S.r.l. Legnano Milano 20025 IT -8C-1F-64 (hex) Power Electronics Espana, S.L. -28B000-28BFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) GigaIO Networks, Inc. 4AA000-4AAFFF (base 16) GigaIO Networks, Inc. 5924 Balfour Ct., Suite 101 @@ -24272,6 +24608,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 +24626,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 +24644,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 +24686,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 +24710,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 +24759,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 +24770,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 +24800,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 +24818,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 +24878,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 +24908,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 +24932,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 +24962,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 +24974,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 +24986,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 +25004,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 +25040,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 +25064,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 +25076,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 +25100,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 +25142,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 +25172,16 @@ 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) 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 +25190,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 +25208,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 +25232,216 @@ 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) SekureTrak Inc. dba TraknProtect +C82000-C82FFF (base 16) SekureTrak Inc. dba TraknProtect + 1240 N. Lake Shore DriveUnit 5B + Chicago IL 60610 + US + +8C-1F-64 (hex) Wuxi Eutron Electronics Technology Co.,Ltd +3F5000-3F5FFF (base 16) Wuxi Eutron Electronics Technology Co.,Ltd + 1st floor , Building C , No. 40 Chunhui Middle Road, Xishan District, Wuxi City, Jiangsu + Wuxi 214000 + CN + +8C-1F-64 (hex) SiLC Technologies +BBB000-BBBFFF (base 16) SiLC Technologies + 181 W Huntington Dr. Ste 200 + Monrovia CA 91016 + US + +8C-1F-64 (hex) VITREA Smart Home Technologies Ltd. +320000-320FFF (base 16) VITREA Smart Home Technologies Ltd. + 3 Abraham Buma Shavit, 4A + Rishon Lezion 7559907 + IL + +8C-1F-64 (hex) DADHWAL AI PRIVATE LIMITED +A9F000-A9FFFF (base 16) DADHWAL AI PRIVATE LIMITED + F 74, INDUSTRIAL AREA PHASE 7 + SAS NAGAR PUNJAB 160055 + IN + +8C-1F-64 (hex) Viettel High Tech +D6D000-D6DFFF (base 16) Viettel High Tech + 380 Lac Long Quan St, Tay Ho + Hanoi 100000 + VN + +8C-1F-64 (hex) Ability Intelligent Corp. +ADA000-ADAFFF (base 16) Ability Intelligent Corp. + No. 200, Sec.3, Zhonghuan Rd., Xinzhuang Dist., New Taipei City 242030, Taiwan (R.O.C.) + New Taipei City 242030 + TW + +8C-1F-64 (hex) Guan Show Technologe Co., Ltd. +A41000-A41FFF (base 16) Guan Show Technologe Co., Ltd. + No.127, Jianguo 1st Rd., Lingya Dist. + Kaohsiung City 802 + TW + +70-B3-D5 (hex) Creotech Quantum SA +91E000-91EFFF (base 16) Creotech Quantum SA + Migdalowa 4 + Warsaw Mazovia 02-796 + PL + +8C-1F-64 (hex) MYIR Electronics Limited +8B4000-8B4FFF (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) TimeMachines Inc. +381000-381FFF (base 16) TimeMachines Inc. + 300 S 68th Street Place, Suite 100 + Lincoln NE 68510 + US + +8C-1F-64 (hex) Korea Electric Vehicle Infra Technology +35A000-35AFFF (base 16) Korea Electric Vehicle Infra Technology + 775, Gyeongin-ro, Yeongdeungpo-gu + Seoul Seoul-t'ukpyolsi 07299 + KR + +8C-1F-64 (hex) xTools Inc. +9AA000-9AAFFF (base 16) xTools Inc. + 651 North Broad Street, 201 + Middletown DE 19709 + US + +8C-1F-64 (hex) National Control Devices, LLC +89A000-89AFFF (base 16) National Control Devices, LLC + 430 Market St + Osceola MO 64776 + US + +8C-1F-64 (hex) Arctic Instruments Oy +A6C000-A6CFFF (base 16) Arctic Instruments Oy + Tekniikantie 14 + Espoo 02150 + FI + +8C-1F-64 (hex) IDUN Technologies AG +74D000-74DFFF (base 16) IDUN Technologies AG + Vega-Strasse 3 + Opfikon 8152 + CH + +8C-1F-64 (hex) Möbus Engineering GmbH +9EE000-9EEFFF (base 16) Möbus Engineering GmbH + Adam-Opel-Straße 3 + Trebur Hessen 65468 DE +8C-1F-64 (hex) Skylark Lasers +B33000-B33FFF (base 16) Skylark Lasers + Phase One, Ratho park, 88 Glasgow Rd, Ratho Station, Newbridge + Edinburgh EH28 8PP + GB + +70-B3-D5 (hex) Power Electronics Espana, S.L. +148000-148FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +773000-773FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Lotec Teknoloji Limited Sirketi +33F000-33FFFF (base 16) Lotec Teknoloji Limited Sirketi + Universiteler Mh. 1596 Cd. Hacettepe Teknokent 6. Ar-Ge C Blok No: 6C Ofis: 28 + Cankaya Ankara 06800 + TR + +8C-1F-64 (hex) Power Electronics Espana, S.L. +28B000-28BFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +1DE000-1DEFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Potter Electric Signal Co LLC +89F000-89FFFF (base 16) Potter Electric Signal Co LLC + 1609 Park 370 Place + Hazelwood MO 63043 + US + +8C-1F-64 (hex) Preston Industries dba PolyScience +975000-975FFF (base 16) Preston Industries dba PolyScience + 6600 W. Touhy Ave + Niles IL 60714-4588 + US + +8C-1F-64 (hex) Digilube Systems Inc +3C3000-3C3FFF (base 16) Digilube Systems Inc + 216 E Mill St + Springboro OH 45066 + US + +8C-1F-64 (hex) R2Vision GmbH +0CF000-0CFFFF (base 16) R2Vision GmbH + Lindener Str. 58 + Bochum 44879 + DE + +8C-1F-64 (hex) Event Acoustics B.V. +77D000-77DFFF (base 16) Event Acoustics B.V. + Proostwetering 50 + Utrecht 3543AH + NL + 8C-1F-64 (hex) Flow Power 82B000-82BFFF (base 16) Flow Power Suite 2, Level 3, 18 - 20 York St @@ -26435,12 +26975,6 @@ A57000-A57FFF (base 16) EkspertStroyProekt Moscow 129344 RU -70-B3-D5 (hex) Power Electronics Espana, S.L. -4CD000-4CDFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) Research Laboratory of Design Automation, Ltd. 223000-223FFF (base 16) Research Laboratory of Design Automation, Ltd. 8 Birzhevoy Spusk @@ -26705,12 +27239,6 @@ ED9000-ED9FFF (base 16) AADONA Communication Pvt Ltd Gandra Paredes 4585-362 PT -70-B3-D5 (hex) Power Electronics Espana, S.L. -BDB000-BDBFFF (base 16) Power Electronics Espana, S.L. - PI Pla de Carrases, CV-35 Salida 30Salida 30- - lliria Valencia 46160 - ES - 70-B3-D5 (hex) Velvac Incorporated 4DD000-4DDFFF (base 16) Velvac Incorporated 2183 Alpine Way @@ -28709,12 +29237,6 @@ D72000-D72FFF (base 16) OnYield Inc Ltd Praha 6 16000 CZ -70-B3-D5 (hex) Power Electronics Espana, S.L. -2A9000-2A9FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) Suprock Technologies 613000-613FFF (base 16) Suprock Technologies 45 Scott Hill Rd @@ -30083,12 +30605,6 @@ CCC000-CCCFFF (base 16) AEC s.r.l. ARGENTEUIL 95100 FR -70-B3-D5 (hex) Power Electronics Espana, S.L. -F4F000-F4FFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 70-B3-D5 (hex) NextEV Co., Ltd. 200000-200FFF (base 16) NextEV Co., Ltd. 20 Building, No. 56 AnTuo Road, Anting Town, Jiading @@ -30704,12 +31220,6 @@ BAD000-BADFFF (base 16) Technik & Design GmbH Berlin Berlin 12681 DE -70-B3-D5 (hex) PolyTech A/S -F4C000-F4CFFF (base 16) PolyTech A/S - HI Park 445 - Herning Herning 7400 - DK - 70-B3-D5 (hex) Birdland Audio AD5000-AD5FFF (base 16) Birdland Audio 484 Washington St. Ste.B-450 @@ -32684,18 +33194,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 +33296,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 +33344,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 +33356,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 @@ -32894,24 +33404,12 @@ BF7000-BF7FFF (base 16) Intellicon Private Limited Gandhinagar Gujarat 382028 IN -8C-1F-64 (hex) Power Electronics Espana, S.L. -29A000-29AFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Nihon Bouhan Camera Inc 9A7000-9A7FFF (base 16) Nihon Bouhan Camera Inc 7F Daiichikosan Bldg ,3-14-10,Ginza 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 +33419,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 +33437,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 +33479,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 +33515,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 +33533,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 +33563,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 +33581,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 +33617,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 +33641,222 @@ 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) QUBIX SPA +4FC000-4FCFFF (base 16) QUBIX SPA + VIA CANADA 22A + PADOVA ITALY 35127 + IT + +8C-1F-64 (hex) CMI, Inc. +5A2000-5A2FFF (base 16) CMI, Inc. + 316 East 9th Street + Owensboro KY 42303 + US + +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) Metaphase Technologies +FC8000-FC8FFF (base 16) Metaphase Technologies + 200 Rittenhouse Circle, West Unit 7 + Bristol PA 19007 + US + +8C-1F-64 (hex) Schildknecht AG +F16000-F16FFF (base 16) Schildknecht AG + Haugweg 26 + Murr 71711 + DE + +8C-1F-64 (hex) Hatteland Technology AS +AD1000-AD1FFF (base 16) Hatteland Technology AS + Eikeskogvegen 52 + Aksdal 5570 + NO + +8C-1F-64 (hex) Neosem Inc. +7B2000-7B2FFF (base 16) Neosem Inc. + 12-26, Simin-daero 327beon-gil, Dongan-Gu + Anyang-Si GYEONGGI-DO 14055 + KR + +8C-1F-64 (hex) ENLESS WIRELESS +037000-037FFF (base 16) ENLESS WIRELESS + 45 TER AVENUE DE VERDUN + BRUGES 33520 + FR + +8C-1F-64 (hex) LOMAR SRL +DF2000-DF2FFF (base 16) LOMAR SRL + VIA GALVANI, 35 + FLERO BRESCIA 25020 + IT + +8C-1F-64 (hex) Chengdu Aplux Inteligence Technology Ltd. +F8E000-F8EFFF (base 16) Chengdu Aplux Inteligence Technology Ltd. + #701-703, 7th Floor, 1A, Jingronghui, #200 Tianfu 5th Street, Hi-Tech Industrial Development Zone, Chengdu, China + Chengdu Sichuan 610095 + CN + +8C-1F-64 (hex) YUYAMA MFG Co.,Ltd +03E000-03EFFF (base 16) YUYAMA MFG Co.,Ltd + 1-4-30 + MEISHINGUCHI,TOYONAKA OSAKA 561-0841 + JP + +8C-1F-64 (hex) Zhejiang Tengen Electric Co.,Ltd. +027000-027FFF (base 16) Zhejiang Tengen Electric Co.,Ltd. + Sulv Industrial Area,Liushi Town + Yueqing Zhejiang 325604 + CN + +8C-1F-64 (hex) TAKAHATA PRECISION Co., LTD. +5AD000-5ADFFF (base 16) TAKAHATA PRECISION Co., LTD. + Nishi-Shinjuku, Shinjuku-ku, Tokyo 3-9-12 Building 9F + Nishi-Shinjuku 160-0023 + JP + +8C-1F-64 (hex) TECHMOVERS SYSTEMS INDIA LIMITED +3A5000-3A5FFF (base 16) TECHMOVERS SYSTEMS INDIA LIMITED + Unit no 327, 3rd floor, Bussa Industrial Estate, Behind Century Bazar, Prabhadevi + Mumbai Maharashtra 400025 + IN + +8C-1F-64 (hex) Aether Energy Alliance LLC +2F6000-2F6FFF (base 16) Aether Energy Alliance LLC + 353 Orchard Dale Drive + Clear Brook VA 22624 + US + +8C-1F-64 (hex) Instawork +614000-614FFF (base 16) Instawork + 55 Hawthorne Street + San Francisco CA 94105 + US + +8C-1F-64 (hex) Wacebo Europe Srl +58A000-58AFFF (base 16) Wacebo Europe Srl + viale Gianluigi Bonelli, 40 + Roma Roma 00127 + IT + +8C-1F-64 (hex) MDA SatConn UK +28F000-28FFFF (base 16) MDA SatConn UK + Spectrum Point,279 Farnborough Road, + Farnborough Hampshire GU14 7LS + GB + +8C-1F-64 (hex) Unlimited Bandwidth LLC +84B000-84BFFF (base 16) Unlimited Bandwidth LLC + 1320 W. Northwest Highway + Palatine 60067 + US + +8C-1F-64 (hex) Wuhan HYAIEV (华异) Technology Co., Ltd +930000-930FFF (base 16) Wuhan HYAIEV (华异) Technology Co., Ltd + Room 102, R&D Unit 1, Floors 1-2, Unit 2, Building C8, Rongke Zhigu Industrial Project(Phase Ⅲ), Liqiao Village, Hongshan District, Wuhan City + Wuhan 430000 + CN + +70-B3-D5 (hex) Polytech A/S +F4C000-F4CFFF (base 16) Polytech A/S + HI Park 445 + Herning Herning 7400 + DK + +70-B3-D5 (hex) Power Electronics Espana, S.L. +2A9000-2A9FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +F4F000-F4FFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +29A000-29AFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +4CD000-4CDFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +70-B3-D5 (hex) Power Electronics Espana, S.L. +BDB000-BDBFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) GENIUS SPORTS +DD0000-DD0FFF (base 16) GENIUS SPORTS + VIA ELETTRICISTA 10 + BOLOGNA BOLOGNA 40138 + IT + +8C-1F-64 (hex) ZTZ Services International +4BD000-4BDFFF (base 16) ZTZ Services International + 655 NW 128th Street + North Miami FL 33168 + US + +8C-1F-64 (hex) KONAX BV +C0F000-C0FFFF (base 16) KONAX BV + Hondekensmolenstraat 56 + Izegem 8870 + BE + +8C-1F-64 (hex) Logic Fruit Technologies Pvt. Ltd. +6F5000-6F5FFF (base 16) Logic Fruit Technologies Pvt. Ltd. + Logic Fruit Technologies Pvt. Ltd 7TH FLOOR, 703 BPTP PARK CENTRA, Sector 30 Star Mall, + Gurugram Haryana 122001 + IN + +8C-1F-64 (hex) Wilton Arthur Poth +701000-701FFF (base 16) Wilton Arthur Poth + Fasanenweg 3 + Roßdorf-Gundernhausen 64380 + DE + +8C-1F-64 (hex) FormFactor +3A7000-3A7FFF (base 16) FormFactor + 7005 Southfront Road + Livermore CA 94551 + US + +8C-1F-64 (hex) MDS TECH +792000-792FFF (base 16) MDS TECH + 9F, DTC Tower, 49, Daewangpangyo-ro 644beon-gil, Bundang-gu + Seongnam 13493 + KR + 8C-1F-64 (hex) Mobileye D63000-D63FFF (base 16) Mobileye 13 Hartom st. @@ -34022,12 +34748,6 @@ E0E000-E0EFFF (base 16) Nokeval Oy Pune Maharashtra 411051 IN -8C-1F-64 (hex) Power Electronics Espana, S.L. -D08000-D08FFF (base 16) Power Electronics Espana, S.L. - Poligono Industrial Carrases. Ronda del camp d Aviacio 4 - Lliria Valencia 46160 - ES - 8C-1F-64 (hex) VECOS Europe B.V. C80000-C80FFF (base 16) VECOS Europe B.V. ESP 237 @@ -34598,12 +35318,6 @@ EAC000-EACFFF (base 16) Miracle Healthcare, Inc. Geumcheon-gu Seoul, Republic of Korea 08511 KR -8C-1F-64 (hex) Power Electronics Espana, S.L. -C2F000-C2FFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Edgeware AB FD1000-FD1FFF (base 16) Edgeware AB Master Samuelsgatan 42 @@ -36410,12 +37124,6 @@ F88000-F88FFF (base 16) ODAWARAKIKI AUTO-MACHINE MFG.CO.,LTD Odawara Kanagawa 250-0005 JP -70-B3-D5 (hex) SODAQ -ADC000-ADCFFF (base 16) SODAQ - Laapersveld 75 - Hilversum - 1213 VB - NL - 70-B3-D5 (hex) Beijing HuaLian Technology Co, Ltd. 623000-623FFF (base 16) Beijing HuaLian Technology Co, Ltd. Floor4 16C, north district of Ufida software park, No. 68 Beiqing road, HaiDian district. @@ -39719,12 +40427,6 @@ C9B000-C9BFFF (base 16) J.M. Voith SE & Co. KG Heidenheim 89522 DE -8C-1F-64 (hex) Blue Ocean UG -B01000-B01FFF (base 16) Blue Ocean UG - Bruhl 62 - Leipzig 04109 - DE - 8C-1F-64 (hex) hiSky SCS Ltd C1B000-C1BFFF (base 16) hiSky SCS Ltd Haamal 24 @@ -40019,12 +40721,6 @@ D9D000-D9DFFF (base 16) MITSUBISHI HEAVY INDUSTRIES THERMAL SYSTEMS, LTD. Kiyosu Aichi 452-8561 JP -8C-1F-64 (hex) Power Electronics Espana, S.L. -1CA000-1CAFFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) SHANGHAI ANGWEI INFORMATION TECHNOLOGY CO.,LTD. 457000-457FFF (base 16) SHANGHAI ANGWEI INFORMATION TECHNOLOGY CO.,LTD. ROOM 607,BUILDING 2, No.2555 XIUPU ROAD,PUDONG NEW AREA, SHANGHAI @@ -40505,12 +41201,6 @@ D0C000-D0CFFF (base 16) KS Beschallungstechnik GmbH Hettenleidelheim Rhineland-Palatinate 67310 DE -8C-1F-64 (hex) Power Electronics Espana, S.L. -5E1000-5E1FFF (base 16) Power Electronics Espana, S.L. - C/ Leonardo Da Vinci, 24-26 - Paterna Valencia 46980 - ES - 8C-1F-64 (hex) Natron Energy DEA000-DEAFFF (base 16) Natron Energy 3542 Bassett St @@ -40793,11 +41483,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 +41501,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 +41519,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 +41567,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 +41579,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 +41753,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 +41774,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 +41816,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 +41834,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 +41858,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 +41870,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 +41888,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 +41906,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 +41936,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 +41948,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 +41960,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 +41974,246 @@ 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 + +8C-1F-64 (hex) Automata GmbH & Co. KG +EFF000-EFFFFF (base 16) Automata GmbH & Co. KG + Gewerbering 5 + Ried Bavaria 86510 + DE + +8C-1F-64 (hex) Kairos Water, Inc +E2C000-E2CFFF (base 16) Kairos Water, Inc + 1700 Northside Dr NWSuite A7, Unit 5543 + Atlanta GA 30318 + US + +8C-1F-64 (hex) Camius +FB3000-FB3FFF (base 16) Camius + 8880 RIO SAN DIEGO DR + SAN DIEGO CA 92108 + US + +8C-1F-64 (hex) EXPERIO TECH PRIVATE LIMITED +D87000-D87FFF (base 16) EXPERIO TECH PRIVATE LIMITED + Ground floor Shiv shakti apatment Near Radha Krishna Mandir , Village bagdola,Dwarka sector -08 New Delhi-110077 + New Delhi Delhi 110077 + IN + +8C-1F-64 (hex) EverBot Technology CO., LTD +8F3000-8F3FFF (base 16) EverBot Technology CO., LTD + 10 F., No. 360, Sec. 1, Jingmao Rd., Beitun Dist., + Taichung City 請選擇 406040 + TW + +8C-1F-64 (hex) aelettronica group srl +BEB000-BEBFFF (base 16) aelettronica group srl + via matteotti,22 + gaggiano milano 20083 + IT + +8C-1F-64 (hex) Anuvu AB +205000-205FFF (base 16) Anuvu AB + Forradsgatan 4 + Sundsvall Vasternorrland 85633 + SE + +8C-1F-64 (hex) Transports Publics Genevois +640000-640FFF (base 16) Transports Publics Genevois + Route de la Chapelle 1 + Grand-Lancy Genève 1212 + CH + +8C-1F-64 (hex) AVEA Group, Inc. +BD4000-BD4FFF (base 16) AVEA Group, Inc. + 55 E Monroe Street Suite 3800 + Chicago IL 60603 + US + +8C-1F-64 (hex) CrossBar, Inc. +310000-310FFF (base 16) CrossBar, Inc. + 2055 Laurelwood Rd. Suite 230 + Santa Clara CA 95054-2729 + US + +8C-1F-64 (hex) Stanley Black & Decker Engineered Fastening (Nantong) Co., Ltd. +EC5000-EC5FFF (base 16) Stanley Black & Decker Engineered Fastening (Nantong) Co., Ltd. + No.666 Huatong Road, High-tech Industrial Development Zone, Tongzhou District, Nantong City, Jiangsu Province, China + Nantong Jiangsu 226315 + CN + +8C-1F-64 (hex) INTOWN CO., LTD. +007000-007FFF (base 16) INTOWN CO., LTD. + No.401 Intelliumcentum,21,Centum 6-ro,Haeundae-gu + Busan 48059 + KR + +8C-1F-64 (hex) Scramble Tools LLC +36C000-36CFFF (base 16) Scramble Tools LLC + 1401 21st ST, #7216 + Sacramento 95811 + US + +8C-1F-64 (hex) VNET Corp. +C77000-C77FFF (base 16) VNET Corp. + B-1202, 128, Beobwon-ro, Songpa-gu + Seoul 05854 + KR + +8C-1F-64 (hex) Scenario Automation +EC3000-EC3FFF (base 16) Scenario Automation + Rua Paulo Elias, 216 + São Carlos São Paulo 13564400 + BR + +8C-1F-64 (hex) Private +182000-182FFF (base 16) Private + +8C-1F-64 (hex) Power Electronics Espana, S.L. +602000-602FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +D08000-D08FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +C2F000-C2FFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +5E1000-5E1FFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) Power Electronics Espana, S.L. +1CA000-1CAFFF (base 16) Power Electronics Espana, S.L. + RONDA DEL CAMP DE AVIACIO, NO. 4 CARRASES INDUSTRIAL ESTATE + LLÍRIA Valencia 46160 + ES + +8C-1F-64 (hex) swiss electronic creation GmbH +8BB000-8BBFFF (base 16) swiss electronic creation GmbH + Allmendstrasse 29 + Fehraltorf 8320 + CH + +8C-1F-64 (hex) SEORIM TECHNOLOGY +F82000-F82FFF (base 16) SEORIM TECHNOLOGY + 145, Gasan digital 1-ro Acehighend 3rd #Room904 + Geumcheon-gu Seoul 08506 + KR + +8C-1F-64 (hex) Sedna +AEB000-AEBFFF (base 16) Sedna + 35 Angus Crescent + Edenvale Gauteng 1609 + ZA + +70-B3-D5 (hex) SODAQ +ADC000-ADCFFF (base 16) SODAQ + Bussumerstraat 34 + Hilversum - 1211 BL + NL + +8C-1F-64 (hex) Terranova Industries UG (haftungsbeschränkt) +B01000-B01FFF (base 16) Terranova Industries UG (haftungsbeschränkt) + Kurt-Günther-Straße 22 + Leipzig 04317 + DE + +8C-1F-64 (hex) REO AG +974000-974FFF (base 16) REO AG + Brühlerstr. 100 + Solingen 42657 + DE + +8C-1F-64 (hex) Pioneer Century Infocomm Pte Ltd +411000-411FFF (base 16) Pioneer Century Infocomm Pte Ltd + 26 Sin Ming Lane #03-118 Midview City + Singapore Singapore 573971 + SG + +8C-1F-64 (hex) Pantherun Technologies Pvt Ltd +4B6000-4B6FFF (base 16) Pantherun Technologies Pvt Ltd + 311 6th main road Hal 2nd stage + Bangalore Karnataka 560038 + IN + +8C-1F-64 (hex) ATTE POWER +4B7000-4B7FFF (base 16) ATTE POWER + Ul. Cegielskiego 2A + Myslenice Małopolskie 32-400 + PL + +8C-1F-64 (hex) Harik Tech +6BC000-6BCFFF (base 16) Harik Tech + Rte de la Plaine, 4 + Chavannes-pres-Renens Vaud 1022 + CH + +8C-1F-64 (hex) Lucint Systems, Inc. +E97000-E97FFF (base 16) Lucint Systems, Inc. + 6635 N Baltimore AveSte 246 + Portland OR 97203 + US + +8C-1F-64 (hex) Sörgel Elektronik +96D000-96DFFF (base 16) Sörgel Elektronik + Oberer Garten 5 + Kressbronn 88079 + DE + +8C-1F-64 (hex) DEUTA-WERKE GmbH +492000-492FFF (base 16) DEUTA-WERKE GmbH + Paffrather Str. 140 + Bergisch Gladbach North Rhine-Westphalia 51465 + DE diff --git a/hwdb.d/meson.build b/hwdb.d/meson.build index 36a9937a60a3f..b031bd1d924e9 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,9 +27,11 @@ 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', + '70-debug-appliance.hwdb', '70-hardware-wallets.hwdb', '70-joystick.hwdb', '70-lights.hwdb', @@ -39,6 +42,7 @@ hwdb_files_test = files( '70-software-radio.hwdb', '70-sound-card.hwdb', '70-touchpad.hwdb', + '70-vsock.hwdb', '80-ieee1394-unit-function.hwdb', '82-net-auto-link-local.hwdb') diff --git a/hwdb.d/parse_hwdb.py b/hwdb.d/parse_hwdb.py index 1a56f40c46102..6402492dff2d3 100755 --- a/hwdb.d/parse_hwdb.py +++ b/hwdb.d/parse_hwdb.py @@ -26,16 +26,31 @@ # SOFTWARE. import glob +import os import string import sys -import os try: - from pyparsing import (Word, White, Literal, ParserElement, Regex, LineEnd, - OneOrMore, Combine, Or, Optional, Suppress, Group, - nums, alphanums, printables, - stringEnd, pythonStyleComment, - ParseBaseException) + from pyparsing import ( + Combine, + Group, + LineEnd, + Literal, + OneOrMore, + Optional, + Or, + ParseBaseException, + ParserElement, + Regex, + Suppress, + White, + Word, + alphanums, + nums, + printables, + pythonStyleComment, + stringEnd, + ) except ImportError: print('pyparsing is not available') sys.exit(77) @@ -57,11 +72,7 @@ ecodes = None print('WARNING: evdev is not available') -try: - from functools import lru_cache -except ImportError: - # don't do caching on old python - lru_cache = lambda: (lambda f: f) +from functools import lru_cache EOL = LineEnd().suppress() EMPTYLINE = LineEnd() @@ -72,192 +83,234 @@ UDEV_TAG = Word(string.ascii_uppercase, alphanums + '_') # Those patterns are used in type-specific matches -TYPES = {'mouse': ('usb', 'bluetooth', 'ps2', '*'), - 'evdev': ('name', 'atkbd', 'input'), - 'fb': ('pci', 'vmbus'), - 'id-input': ('modalias', 'bluetooth', 'i2c', 'usb'), - 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'), - 'joystick': ('i8042', 'rmi', 'bluetooth', 'usb'), - 'keyboard': ('name', ), - 'sensor': ('modalias', - 'accel-base', - 'accel-display', - 'accel-camera', - 'proximity-palmrest', - 'proximity-palmrest-left', - 'proximity-palmrest-right', - 'proximity-lap', - 'proximity-wifi', - 'proximity-lte', - 'proximity-wifi-lte', - 'proximity-wifi-left', - 'proximity-wifi-right', - ), - 'ieee1394-unit-function' : ('node', ), - 'camera': ('usb'), - } +TYPES = { + 'mouse': ('usb', 'bluetooth', 'ps2', '*'), + 'evdev': ('name', 'atkbd', 'input'), + 'fb': ('pci', 'vmbus'), + 'id-input': ('modalias', 'bluetooth', 'i2c', 'usb'), + 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'), + 'joystick': ('i8042', 'rmi', 'bluetooth', 'usb'), + 'keyboard': ('name',), + 'sensor': ( + 'modalias', + 'accel-base', + 'accel-display', + 'accel-camera', + 'proximity-palmrest', + 'proximity-palmrest-left', + 'proximity-palmrest-right', + 'proximity-lap', + 'proximity-wifi', + 'proximity-lte', + 'proximity-wifi-lte', + 'proximity-wifi-left', + 'proximity-wifi-right', + ), + 'ieee1394-unit-function': ('node',), + 'camera': ('usb'), +} # Patterns that are used to set general properties on a device -GENERAL_MATCHES = {'acpi', - 'bluetooth', - 'usb', - 'pci', - 'sdio', - 'vmbus', - 'OUI', - 'ieee1394', - 'dmi', - } +GENERAL_MATCHES = { + 'acpi', + 'bluetooth', + 'dmi', + 'ieee1394', + 'OUI', + 'pci', + 'sdio', + 'tpm2', + 'usb', + 'vmbus', +} + def upperhex_word(length): return Word(nums + 'ABCDEF', exact=length) -@lru_cache() + +@lru_cache def hwdb_grammar(): ParserElement.setDefaultWhitespaceChars('') - prefix = Or(category + ':' + Or(conn) + ':' - for category, conn in TYPES.items()) + prefix = Or(category + ':' + Or(conn) + ':' for category, conn in TYPES.items()) matchline_typed = Combine(prefix + Word(printables + ' ' + '®')) matchline_general = Combine(Or(GENERAL_MATCHES) + ':' + Word(printables + ' ' + '®')) matchline = (matchline_typed | matchline_general) + EOL - propertyline = (White(' ', exact=1).suppress() + - Combine(UDEV_TAG - '=' - Optional(Word(alphanums + '_=:@*.!-;, "/')) - - Optional(pythonStyleComment)) + - EOL) + propertyline = ( + White(' ', exact=1).suppress() + + Combine( + UDEV_TAG - '=' - Optional(Word(alphanums + '_=:@*.!-;, "/?&')) - Optional(pythonStyleComment) + ) + + EOL + ) propertycomment = White(' ', exact=1) + pythonStyleComment + EOL - group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) - - OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) - - (EMPTYLINE ^ stringEnd()).suppress()) + group = ( + OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) + - OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) + - (EMPTYLINE ^ stringEnd()).suppress() + ) commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress() grammar = OneOrMore(Group(group)('GROUPS*') ^ commentgroup) + stringEnd() return grammar -@lru_cache() + +@lru_cache def property_grammar(): ParserElement.setDefaultWhitespaceChars(' ') - dpi_setting = Group(Optional('*')('DEFAULT') + INTEGER('DPI') + Optional(Suppress('@') + INTEGER('HZ')))('SETTINGS*') + dpi_setting = Group(Optional('*')('DEFAULT') + INTEGER('DPI') + Optional(Suppress('@') + INTEGER('HZ')))( + 'SETTINGS*' + ) mount_matrix_row = SIGNED_REAL + ',' + SIGNED_REAL + ',' + SIGNED_REAL 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 + ' ') - props = (('MOUSE_DPI', Group(OneOrMore(dpi_setting))), - ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER), - ('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_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_INPUT', id_input_setting), - ('ID_INPUT_ACCELEROMETER', id_input_setting), - ('ID_INPUT_JOYSTICK', id_input_setting), - ('ID_INPUT_KEY', id_input_setting), - ('ID_INPUT_KEYBOARD', id_input_setting), - ('ID_INPUT_MOUSE', id_input_setting), - ('ID_INPUT_POINTINGSTICK', id_input_setting), - ('ID_INPUT_SWITCH', id_input_setting), - ('ID_INPUT_TABLET', id_input_setting), - ('ID_INPUT_TABLET_PAD', id_input_setting), - ('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')))), - ('POINTINGSTICK_SENSITIVITY', INTEGER), - ('ID_INTEGRATION', Or(('internal', 'external'))), - ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))), - ('XKB_FIXED_LAYOUT', xkb_setting), - ('XKB_FIXED_VARIANT', xkb_setting), - ('XKB_FIXED_MODEL', xkb_setting), - ('KEYBOARD_LED_NUMLOCK', Literal('0')), - ('KEYBOARD_LED_CAPSLOCK', Literal('0')), - ('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')))), - ('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_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_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')))), - ) - fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE') - for name, val in props] - kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME') - - Suppress('=') - - Group('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE') - ] - abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME') - - Suppress('=') - - Word('-' + nums + ':')('VALUE') - ] + props = ( + ('MOUSE_DPI', Group(OneOrMore(dpi_setting))), + ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER), + ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER), + ('MOUSE_WHEEL_CLICK_COUNT', INTEGER), + ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER), + ('ID_INPUT_3D_MOUSE', zero_one), + ('ID_AUTOSUSPEND', zero_one), + ('ID_AUTOSUSPEND_DELAY_MS', INTEGER), + ('ID_AV_PRODUCTION_CONTROLLER', zero_one), + ('ID_AV_LIGHTS', zero_one), + ('ID_DEBUG_APPLIANCE', name_literal), + ('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), + ('ID_INPUT_KEY', id_input_setting), + ('ID_INPUT_KEYBOARD', id_input_setting), + ('ID_INPUT_MOUSE', id_input_setting), + ('ID_INPUT_POINTINGSTICK', id_input_setting), + ('ID_INPUT_SWITCH', id_input_setting), + ('ID_INPUT_TABLET', id_input_setting), + ('ID_INPUT_TABLET_PAD', id_input_setting), + ('ID_INPUT_TOUCHPAD', id_input_setting), + ('ID_INPUT_TOUCHSCREEN', id_input_setting), + ('ID_INPUT_TRACKBALL', id_input_setting), + ('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'))), + ('XKB_FIXED_LAYOUT', xkb_setting), + ('XKB_FIXED_VARIANT', xkb_setting), + ('XKB_FIXED_MODEL', xkb_setting), + ('KEYBOARD_LED_NUMLOCK', Literal('0')), + ('KEYBOARD_LED_CAPSLOCK', Literal('0')), + ('ACCEL_MOUNT_MATRIX', mount_matrix), + ('ACCEL_LOCATION', Or(('display', 'base'))), + ('PROXIMITY_NEAR_LEVEL', INTEGER), + ('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', zero_one), + ('ID_CAMERA_DIRECTION', Or(('front', 'rear'))), + ( + 'SOUND_FORM_FACTOR', + Or(('internal', 'webcam', 'speaker', 'headphone', 'headset', 'handset', 'microphone')), + ), + ('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', 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), + ('VSOCK_ACCEPT_VMADDR_CID_ANY', zero_one), + ) + fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE') for name, val in props] + kbd_props = [ + Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME') + - Suppress('=') + - Group('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE') + ] + abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME') - Suppress('=') - Word('-' + nums + ':')('VALUE')] grammar = Or(fixed_props + kbd_props + abs_props) + EOL return grammar + ERROR = False + + def error(fmt, *args, **kwargs): global ERROR ERROR = True print(fmt.format(*args, **kwargs)) + def convert_properties(group): matches = [m[0] for m in group.MATCHES] props = [p[0] for p in group.PROPERTIES] return matches, props + def parse(fname): grammar = hwdb_grammar() try: - with open(fname, 'r', encoding='UTF-8') as f: + with open(fname, encoding='UTF-8') as f: parsed = grammar.parseFile(f) except ParseBaseException as e: error('Cannot parse {}: {}', fname, e) return [] return [convert_properties(g) for g in parsed.GROUPS] + def check_matches(groups): matches = sum((group[0] for group in groups), []) # This is a partial check. The other cases could be also done, but those # three are the most commonly wrong. grammars = { - 'bluetooth' : 'v' + upperhex_word(4) + Optional('p' + upperhex_word(4) + Optional(':')) + '*', - 'usb' : 'v' + upperhex_word(4) + Optional('p' + upperhex_word(4) + Optional(':')) + '*', - 'pci' : 'v' + upperhex_word(8) + Optional('d' + upperhex_word(8) + Optional(':')) + '*', + 'bluetooth': 'v' + upperhex_word(4) + Optional('p' + upperhex_word(4) + Optional(':')) + '*', + 'usb': 'v' + upperhex_word(4) + Optional('p' + upperhex_word(4) + Optional(':')) + '*', + 'pci': 'v' + upperhex_word(8) + Optional('d' + upperhex_word(8) + Optional(':')) + '*', } for match in matches: @@ -280,11 +333,13 @@ def check_matches(groups): error('Match {!r} is duplicated', match) prev = match + def check_one_default(prop, settings): defaults = [s for s in settings if s.DEFAULT] if len(defaults) > 1: error('More than one star entry: {!r}', prop) + def check_one_mount_matrix(prop, value): numbers = [s for s in value if s not in {';', ','}] if len(numbers) != 9: @@ -295,28 +350,33 @@ def check_one_mount_matrix(prop, value): error('Wrong accel matrix: {!r}', prop) bad_x, bad_y, bad_z = max(numbers[0:3]) == 0, max(numbers[3:6]) == 0, max(numbers[6:9]) == 0 if bad_x or bad_y or bad_z: - error('Mount matrix is all zero in {} row: {!r}', - 'x' if bad_x else ('y' if bad_y else 'z'), - prop) + error('Mount matrix is all zero in {} row: {!r}', 'x' if bad_x else ('y' if bad_y else 'z'), prop) + def check_one_keycode(value): if value != '!' and ecodes is not None: key = 'KEY_' + value.upper() - if not (key in ecodes or - value.upper() in ecodes or - # new keys added in kernel 5.5 - 'KBD_LCD_MENU' in key): + if not ( + key in ecodes + or value.upper() in ecodes + # new keys added in kernel 5.5 + or 'KBD_LCD_MENU' in key + ): # fmt: skip error('Keycode {} unknown', key) + def check_wheel_clicks(properties): - pairs = (('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', 'MOUSE_WHEEL_CLICK_COUNT'), - ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', 'MOUSE_WHEEL_CLICK_ANGLE'), - ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', 'MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL'), - ('MOUSE_WHEEL_CLICK_COUNT', 'MOUSE_WHEEL_CLICK_ANGLE')) + pairs = ( + ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', 'MOUSE_WHEEL_CLICK_COUNT'), + ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', 'MOUSE_WHEEL_CLICK_ANGLE'), + ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', 'MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL'), + ('MOUSE_WHEEL_CLICK_COUNT', 'MOUSE_WHEEL_CLICK_ANGLE'), + ) for pair in pairs: if pair[0] in properties and pair[1] not in properties: error('{} requires {} to be specified', *pair) + def check_properties(groups): grammar = property_grammar() for _, props in groups: @@ -343,6 +403,7 @@ def check_properties(groups): check_wheel_clicks(seen_props) + def print_summary(fname, groups): n_matches = sum(len(matches) for matches, props in groups) n_props = sum(len(props) for matches, props in groups) @@ -351,12 +412,15 @@ def print_summary(fname, groups): if n_matches == 0 or n_props == 0: print(f'{fname}: no matches or props') + if __name__ == '__main__': - args = sys.argv[1:] or sorted([ - os.path.dirname(sys.argv[0]) + '/20-dmi-id.hwdb', - os.path.dirname(sys.argv[0]) + '/20-net-ifname.hwdb', - *glob.glob(os.path.dirname(sys.argv[0]) + '/[678][0-9]-*.hwdb'), - ]) + args = sys.argv[1:] or sorted( + [ + os.path.dirname(sys.argv[0]) + '/20-dmi-id.hwdb', + os.path.dirname(sys.argv[0]) + '/20-net-ifname.hwdb', + *glob.glob(os.path.dirname(sys.argv[0]) + '/[678][0-9]-*.hwdb'), + ] + ) for fname in args: groups = parse(fname) diff --git a/hwdb.d/pci.ids b/hwdb.d/pci.ids index 369ef78ff6dce..c27d664b250db 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.06.19 +# Date: 2026-06-19 03:15:02 # # Maintained by Albert Pool, Martin Mares, and other volunteers from # the PCI ID Project at https://pci-ids.ucw.cz/. @@ -30,13 +30,19 @@ # This is a relabelled RTL-8139 8139 AT-2500TX V3 Ethernet 0014 Loongson Technology LLC + 3b0f DMA Adress Translation Unit [Loongson 3 Processor Family] + 3c09 Internal PCI to PCI Bridge [Loongson 3 Processor Family] + 3c0f DMA Adress Translation Unit [Loongson 3 Processor Family] + 3c19 PCI Express x16 Root Port [Loongson 3 Processor Family] + 3c29 PCI Express x8 Root Port [Loongson 3 Processor Family] + 3c39 PCI Express x4 Root Port [Loongson 3 Processor Family] 7a00 7A1000 Chipset Hyper Transport Bridge Controller 7a02 2K1000 / 7A1000 Chipset Advanced Peripheral Bus Controller 7a03 2K1000/2000 / 7A1000 Chipset Gigabit Ethernet Controller 7a04 2K1000 / 7A1000 Chipset OTG USB Controller 7a05 2K1000 Vivante GC1000 GPU 7a06 2K1000 / 7A1000 Chipset Display Controller - 7a07 2K1000/2000 / 7A1000/2000 Chipset HD Audio Controller + 7a07 2K1000/2000/3000 / 3B6000M / 7A1000/2000 Chipset HD Audio Controller 7a08 2K1000 / 7A1000 Chipset 3Gb/s SATA AHCI Controller 7a09 2K1000 / 7A1000 Chipset PCIe x1 Bridge 7a0b 7A1000 Chipset SPI Controller @@ -49,34 +55,43 @@ 7a15 7A1000 Chipset Vivante GC1000 GPU 7a16 2K1000/2000 VPU Decoder 7a17 7A1000 Chipset AC97 Audio Controller - 7a18 2K2000 / 7A2000 Chipset 6Gb/s SATA AHCI Controller + 7a18 2K2000/3000 / 3B6000M / 7A2000 Chipset 6Gb/s SATA AHCI Controller 7a19 PCI-to-PCI Bridge 7a1a 2K2000 Configuration Bus - 7a1b 2K2000 / 7A2000 Chipset SPI Controller - 7a1d 2K2000 RapidIO Interface + 7a1b 2K2000/3000 / 3B6000M / 7A2000 Chipset SPI Controller + 7a1d 2K2000 / 2K3000 / 3B6000M RapidIO Interface 7a1e 2K2000 DES Controller 7a22 2K2000 Advanced Peripheral Bus Controller + 7a23 Gigabit Ethernet Controller [Chipset / CPU inside] 7a24 2K1000 / 7A1000/2000 Chipset USB OHCI Controller 7a25 2K2000 / 7A2000 Chipset LG100 GPU 7a26 2K1000 Camera Controller - 7a27 2K2000 / 7A2000 Chipset I2S Controller + 7a27 2K2000/3000 / 3B6000M / 7A2000 Chipset I2S Controller 7a29 7A1000 Chipset PCIe x8 Bridge 7a2e 2K2000 RSA Controller 7a2f 2K2000 DMA Controller - 7a34 2K2000 / 7A2000 Chipset USB 3.0 xHCI Controller + 7a34 2K2000/3000 / 3B6000M / 7A2000 Chipset USB 3.0 xHCI Controller + 7a35 LG200 GPU 7a36 2K2000 / 7A2000 Chipset Display Controller - 7a37 2K2000 HDMI Audio Controller + 7a37 2K2000/3000 / 3B6000M / 7A2000 Chipset HDMI Audio Controller 7a39 2K2000 / 7A2000 Chipset PCIe x1 Root Port 7a3e 2K2000 RNG Controller - 7a44 2K2000 USB 2.0 xHCI Controller - 7a48 2K2000 SDIO Controller + 7a42 Advanced Peripheral Bus Controller [Chipset / CPU inside] + 7a44 2K2000/3000 / 3B6000M USB 2.0 xHCI Controller + 7a46 Video Display Controller [Chipset / CPU inside] + 7a47 Pulse-Code Modulation [Chipset / CPU inside] + 7a48 2K2000/3000 / 3B6000M SDIO Controller 7a49 2K2000 / 7A2000 Chipset PCIe x4 Root Port 7a54 2K2000 OTG USB Controller + 7a56 Video Processing Unit Decoder [Chipset / CPU inside] 7a59 7A2000 Chipset PCIe x8 Root Port + 7a66 Video Processing Unit Encoder [Chipset / CPU inside] 7a69 7A2000 Chipset PCIe x16 Root Port 7a79 2K2000 PCIe Root Complex - 7a88 2K2000 eMMC Controller + 7a88 2K2000/3000 / 3B6000M eMMC Controller + 7a89 PCI Express x1 Root Port [Chipset / CPU inside] 7a8e 2K2000 SE Controller + 7a99 PCI Express x4 Root Port [Chipset / CPU inside] 7af9 2K2000 PCIe Endpoint 0018 Fn-Link Technology Limited 6252 6252CPUB 802.11ax PCIe Wireless Network Adapter @@ -176,6 +191,10 @@ 0202 GP202 0721 Sapphire, Inc. 0731 Jingjia Microelectronics Co Ltd + 0100 JMN100 + 0731 0101 JMN100 16G + 1050 JM1050 + 0731 1051 JM1050 8G 1100 JM1100 0731 1101 JM1100-C 0731 1102 JM1100-II @@ -184,6 +203,11 @@ 0731 1105 JM1100-Y 0731 1106 JM1100-EI 0731 1107 JM1100-EM + 0731 1108 JY1008 + 0731 1109 JY1032 + 1102 JM1100-Y + 0731 1121 JY1032 LE 64G + 0731 1122 JY1032 LE 32G 7200 JM7200 Series GPU 0731 7201 JM7201 0731 7202 JM7202 @@ -226,6 +250,12 @@ f011 JM1100-IV f111 JM1100-MV ff11 JM1100-YV + 0731 1105 JM11 Series + 0731 1108 JY1008 vGPU + 0731 1109 JY1032 vGPU + 0731 1121 JY1032 LE vGPU + 0731 1122 JY1032 LE vGPU +0771 Xi'an Microelectronics Technology Institute 0777 Ubiquiti Networks, Inc. 0795 Wired Inc. 6663 Butane II (MPEG2 encoder board) @@ -329,6 +359,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. @@ -623,6 +656,7 @@ 0072 SAS2008 PCI-Express Fusion-MPT SAS-2 [Falcon] 1000 3020 9211-8i 1000 3040 9210-8i + 1000 3060 9212-4i4e 1000 3080 9200-8e [LSI SAS 6Gb/s SAS/SATA PCIe x8 External HBA] 1000 30b0 9200-8e [LSI SAS 6Gb/s SAS/SATA PCIe x8 External HBA] 1000 30e0 9202-16e @@ -768,6 +802,7 @@ 0094 SAS3108 PCI-Express Fusion-MPT SAS-3 0095 SAS3108 PCI-Express Fusion-MPT SAS-3 0096 SAS3004 PCI-Express Fusion-MPT SAS-3 + 1000 3110 SAS9300-4i 0097 SAS3008 PCI-Express Fusion-MPT SAS-3 1000 3090 SAS9311-8i 1000 30a0 SAS9300-8e @@ -1237,6 +1272,7 @@ 1000 2004 PEX89000 Virtual PCIe TWC/NT 2.0 Endpoint # Lower lane count PEX89000 switch 1000 2005 PEX89000 Virtual PCIe gDMA Endpoint + c040 PEX90xxx PCIe Gen 6 Switch 1001 Kolter Electronic 0010 PCI 1616 Measurement card with 32 digital I/O lines 0011 OPTO-PCI Opto-Isolated digital I/O board @@ -1278,6 +1314,8 @@ 131c Kaveri [Radeon R7 Graphics] 131d Kaveri [Radeon R6 Graphics] 13c0 Granite Ridge [Radeon Graphics] +# Used in the Sony PlayStation 5 Phat + 13db Cyan Skillfish [PlayStation 5 APU] 13e9 Ariel/Navi10Lite 13f9 Oberon/Navi12Lite 13fe Cyan Skillfish [BC-250] @@ -4028,8 +4066,8 @@ 1da2 e409 Sapphire Technology Limited Navi 10 [Radeon RX 5600 OEM/5600 XT / 5700/5700 XT] 1da2 e410 Sapphire NITRO+ RX 5700 XT 1da2 e411 Navi 10 [Radeon RX 5600 OEM/5600 XT / 5700/5700 XT]Navi 10 [Radeon RX 5600 OEM/5600 XT / 5700/5700 XT] - 7340 Navi 14 [Radeon RX 5500/5500M / Pro 5300/5500M] - 106b 0210 Radeon Pro 5300M + 7340 Navi 14 [Radeon RX 5500/5500M / Pro 5300/5300M/5500M] + 106b 0210 MacBookPro16,1 (16", 2019) [Radeon Pro 5300M] 106b 0219 iMac (Retina 5K, 27-inch, 2020) [Radeon Pro 5300] 7341 Navi 14 [Radeon Pro W5500] 7347 Navi 14 [Radeon Pro W5500M] @@ -4108,6 +4146,7 @@ 1da2 e457 PULSE AMD Radeon RX 6500 XT 7446 Navi 31 USB 7448 Navi 31 [Radeon Pro W7900] + 7449 Navi 31 [Radeon Pro W7800 48GB] 744a Navi 31 [Radeon Pro W7900 Dual Slot] 744b Navi 31 [Radeon Pro W7900D] 744c Navi 31 [Radeon RX 7900 XT/7900 XTX/7900 GRE/7900M] @@ -4121,7 +4160,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,10 +4184,14 @@ 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) + 1458 2437 Navi 48 XTX [Radeon RX 9070 XT Gaming OC ICE 16G] + 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] +# ASUS RX 9060 XT 16GB + 1043 0639 Navi 44 [Radeon RX 9060 XT] 1458 2429 GV-R9060XTGAMING OC-16GD [Radeon RX 9060 XT GAMING OC 16G] 1eae 8601 RX-96TS316W7 [SWIFT RX 9060 XT OC White Triple Fan Gaming Edition 16GB] 75a0 Aqua Vanjaram [Instinct MI350X] @@ -4260,6 +4303,7 @@ 9500 RV670 [Radeon HD 3850 X2] 9501 RV670 [Radeon HD 3870] 174b e620 Radeon HD 3870 + 1787 2244 Radeon HD 3870 9504 RV670/M88 [Mobility Radeon HD 3850] 9505 RV670 [Radeon HD 3690/3850] 148c 3000 Radeon HD 3850 @@ -7171,9 +7215,12 @@ 8241 TUSB73x0 SuperSpeed USB 3.0 xHCI Host Controller 1014 04b2 S824 (8286-42A) 8400 ACX 100 22Mbps Wireless Interface + 111a 1021 SpeedStream 1021 (SS1021) Wireless Cardbus PC Card 1186 3b00 DWL-650+ PC Card cardbus 22Mbs Wireless Adapter [AirPlus] 1186 3b01 DWL-520+ 22Mbps PCI Wireless Adapter 1395 2201 WL22-PC + 167d b230 Netopia TER/WPC11N1 Wireless LAN Card + 16a5 1801 WE302-TF 16ab 8501 WL-8305 IEEE802.11b+ Wireless LAN PCI Adapter 8401 ACX 100 22Mbps Wireless Interface 8888 Multicore DSP+ARM KeyStone II SOC @@ -7186,12 +7233,15 @@ # Found in Philips ADSL ANNEX A WLAN Router SNA6500/18 sold by Belgacom 104c 9067 TNETW1130GVF 104c 9096 Trendnet TEW-412PC Wireless PCI Adapter (Version A) + 1154 032c WLI-CB-G54L 1186 3b04 DWL-G520+ Wireless PCI Adapter 1186 3b05 DWL-G650+ AirPlusG+ CardBus Wireless LAN 1186 3b08 AirPlus G DWL-G630 Wireless Cardbus Adapter (rev.B1) 1385 4c00 WG311v2 802.11g Wireless PCI Adapter 13d1 aba0 SWLMP-54108 108Mbps Wireless mini PCI card 802.11g+ 14ea ab07 GW-NS54GM Wireless Cardbus Adapter + 167d b260 SWL-2600N + 16ab 9067 A90-200WG-01 802.11g Wireless PC Card 16ec 010d USR5416 802.11g Wireless Turbo PCI Adapter 16ec 010e USR5410 802.11g Wireless Cardbus Adapter 1737 0033 WPC54G v2 802.11g Wireless-G Notebook Adapter @@ -7577,7 +7627,37 @@ e350 80333 [SuperTrak EX24350] 105b Foxconn International, Inc. 9602 RS780/RS880 PCI to PCI bridge (int gfx) +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0ab T99W175 5G Modem [Snapdragon X55] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0b0 DW5930e 5G Modem [Snapdragon X55] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0b1 DW5930e 5G Modem [Snapdragon X55] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0bf T99W175 5G Modem [Snapdragon X55] e0c3 T99W175 5G Modem [Snapdragon X55] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0d8 T99W368 5G Modem [Snapdragon X65] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0d9 T99W373 5G Modem [Snapdragon X62] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0f0 T99W510 4G Modem [Snapdragon X24] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0f1 T99W510 4G Modem [Snapdragon X24] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0f2 T99W510 4G Modem [Snapdragon X24] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0f5 DW5932e-eSIM 5G Modem [Snapdragon X62] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e0f9 DW5932e 5G Modem [Snapdragon X62] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e118 T99W640 5G Modem [Snapdragon X72] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e11d DW5934e-eSIM 5G Modem [Snapdragon X72] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e11e DW5934e 5G Modem [Snapdragon X72] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + e123 T99W760 5G Redcap Modem [Snapdragon X35] 105c Wipro Infotech Limited 105d Number 9 Computer Company 2309 Imagine 128 @@ -7821,6 +7901,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 @@ -10187,9 +10268,7 @@ 1102 102f 3D Blaster RIVA TNT2 Ultra 14af 5820 Maxi Gamer Xentor 32 4843 4f34 Dynamite - 002a NV5 [Riva TNT2] - 002b NV5 [Riva TNT2] - 002c NV5 [Vanta / Vanta LT] + 002c NV6 [Vanta LT / Vanta / Vanta-16] 1043 0200 AGP-V3800 Combat SDRAM 1043 0201 AGP-V3800 Combat 1048 0c20 TNT2 Vanta @@ -10199,7 +10278,7 @@ 1102 1031 CT6938 VANTA 8MB 1102 1034 CT6894 VANTA 16MB 14af 5008 Maxi Gamer Phoenix 2 - 002d NV5 [Riva TNT2 Model 64 / Model 64 Pro] + 002d NV6 [Riva TNT2 Model 64 / Model 64 Pro] 1043 0200 AGP-V3800M 1043 0201 AGP-V3800M 1048 0c3a Erazor III LT @@ -13182,6 +13261,7 @@ 1f76 TU106GLM [Quadro RTX 3000 Mobile Refresh] 1f81 TU117 1f82 TU117 [GeForce GTX 1650] + 19da 3595 GeForce GTX 1650 OC GDDR6 1f83 TU117 [GeForce GTX 1630] 1f91 TU117M [GeForce GTX 1650 Mobile / Max-Q] 1f92 TU117M [GeForce GTX 1650 Mobile] @@ -13262,6 +13342,7 @@ 2203 GA102 [GeForce RTX 3090 Ti] 2204 GA102 [GeForce RTX 3090] 10de 147d GeForce RTX 3090 Founders Edition + 1462 3881 MSI RTX 3090 VENTUS 3X OC 3842 3973 GeForce RTX 3090 XC3 2205 GA102 [GeForce RTX 3080 Ti 20GB] 2206 GA102 [GeForce RTX 3080] @@ -13378,7 +13459,7 @@ 24dd GA104M [GeForce RTX 3070 Mobile / Max-Q] 24df GA104M 24e0 GA104M [Geforce RTX 3070 Ti Laptop GPU] - 24fa GA104 [RTX A4500 Embedded GPU ] + 24fa GA104 [RTX A4500 Embedded GPU] 2501 GA106 [GeForce RTX 3060] 2503 GA106 [GeForce RTX 3060] 2504 GA106 [GeForce RTX 3060 Lite Hash Rate] @@ -13428,7 +13509,7 @@ 25e5 GA107BM [GeForce RTX 3050 Mobile] 25ec GA107BM / GN20-P0-R-K2 [GeForce RTX 3050 6GB Laptop GPU] 25ed GA107 [GeForce RTX 2050] - 25f9 GA107 [RTX A1000 Embedded GPU ] + 25f9 GA107 [RTX A1000 Embedded GPU] 25fa GA107 [RTX A2000 Embedded GPU] 25fb GA107 [RTX A500 Embedded GPU] 2681 AD102 [RTX TITAN Ada] @@ -13495,6 +13576,7 @@ 28f8 AD107GLM [RTX 2000 Ada Generation Embedded GPU] 2900 GB100 [Reserved Dev ID A] 2901 GB100 [B200] + 2909 GB100 [HGX B200 168GB] 2920 GB100 [TS4 / B100] 2924 GB100 2925 GB100 @@ -13529,7 +13611,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] @@ -13550,19 +13632,24 @@ 2db9 GB207GLM [RTX PRO 500 Blackwell Generation Laptop GPU] 2dd8 GB207M [GeForce RTX 5050 Max-Q / Mobile] 2df9 GB207GLM [RTX PRO 500 Blackwell Embedded GPU] + 2e03 GB20B [JMJWOA-Generic-GPU] 2e12 GB20B [GB10] # N1X 2e2a GB20B [JMJWOA-Generic-GPU] 2f04 GB205 [GeForce RTX 5070] + 2f06 GB205 [GeForce RTX 5060] 2f18 GB205M [GeForce RTX 5070 Ti Mobile] 2f38 GB205GLM [RTX PRO 3000 Blackwell Generation Laptop GPU] 2f58 GB205M [GeForce RTX 5070 Ti Mobile] 2f80 GB205 High Definition Audio Controller + 3040 GR100 + 30c0 GR102 3180 GB110 [Reserved Dev ID A] 3182 GB110 [B300 SXM6 AC] 31a1 GB110 [GB300 MaxQ] 31c0 GB110 [Reserved Dev ID B] 31c2 GB110 [GB300] + 31c3 GB110 [GB300] 31fe GB110 3200 GB112 3224 GB112 @@ -14255,7 +14342,8 @@ 000b EMU20k2 [Sound Blaster X-Fi Titanium Series] 1102 0041 SB0880 [SoundBlaster X-Fi Titanium PCI-e] 1102 0062 SB1270 [SoundBlaster X-Fi Titanium HD] - 0010 CA0132 Sound Core3D [Sound Blaster AE-7] + 0010 CA0132 Sound Core3D [Sound Blaster AE-7/AE-9] + 1102 0071 Sound Blaster AE-9 1102 0081 Sound Blaster AE-7 0012 CA0132 Sound Core3D [Sound Blaster Recon3D / Z-Series / Sound BlasterX AE-5 Plus] 1102 0010 SB1570 SB Audigy Fx @@ -16210,6 +16298,8 @@ 117c 00c9 Celerity FC-641E 117c 00ca Celerity FC-642E 117c 00d4 Celerity FC-644E + 117c 40d8 ThunderLink FC 5642 + 117c 40d9 ThunderLink FC 5322 00c5 ExpressNVM PCIe Gen4 Switch 117c 00c6 ExpressNVM S48F PCIe Gen4 117c 00cb ExpressNVM S4FF PCIe Gen4 @@ -16219,6 +16309,11 @@ 117c 00c2 ExpressSAS H1244 GT 117c 00c3 ExpressSAS H12F0 GT 117c 00c4 ExpressSAS H120F GT + 117c 00e1 ExpressSAS H1280 GT + 117c 00e2 ExpressSAS H1208 GT + 117c 00e3 ExpressSAS H1244 GT + 117c 40e0 ThunderLink SH 5128 + 117c 40e4 ThunderLink SH 5128 8013 ExpressPCI UL4D 8014 ExpressPCI UL4S 8027 ExpressPCI UL5D @@ -16226,7 +16321,7 @@ 117c 0070 ExpressSAS H1280 117c 0071 ExpressSAS H1208 117c 0080 ExpressSAS H1244 - 117c 40ae ThunderLink TLSH-3128 + 117c 40ae ThunderLink SH 3128 8072 ExpressSAS 12Gb/s SAS/SATA HBA 117c 0072 ExpressSAS H12F0 117c 0073 ExpressSAS H120F @@ -16518,11 +16613,15 @@ 138f W8300 802.11 Adapter (rev 07) 1fa6 Marvell W8300 802.11 Adapter 1186 3b08 AirPlus G DWL-G630 Wireless Cardbus Adapter (rev.A1) + 1186 3b09 AirPlus G DWL-G510 Wireless G PCI Card 1fa7 88W8310 and 88W8000G [Libertas] 802.11g client chipset 1faa 88w8335 [Libertas] 802.11b/g Wireless + 1186 3b21 EH101 Wireless G Notebook Adapter + 1186 3b22 EH102 Wireless G Desktop Adapter 1385 4e00 WG511v2 54 Mbps Wireless PC Card 1385 6b00 WG311v3 802.11g Wireless PCI Adapter 1737 0040 WPC54G v5 802.11g Wireless-G Notebook Adapter + a727 6802 3CRGPC10075 OfficeConnect Wireless 54Mbps 11g PC Card 2211 88SB2211 PCI Express to PCI Bridge 2a01 88W8335 [Libertas] 802.11b/g Wireless 2a02 88W8361 [TopDog] 802.11n Wireless @@ -16530,9 +16629,14 @@ 1385 7c00 WN511T RangeMax Next 300 Mbps Wireless PC Card 1385 7c01 WN511T RangeMax Next 300 Mbps Wireless Notebook Adapter 1385 7e00 WN311T RangeMax Next 300 Mbps Wireless PCI Adapter + 1737 0065 WPC4400N Wireless-N Business Notebook Adapter 1799 801b F5D8011 v2 802.11n N1 Wireless Notebook Card 2a08 88W8362e [TopDog] 802.11a/b/g/n Wireless 2a0a 88W8363 [TopDog] 802.11n Wireless + 2a0b 88W8363 [TopDog] 802.11n Wireless + 1154 0356 WLI-CB-AMG144N + 1154 0357 WLI-CB-AG300N Nfiniti 802.11a/g/b + Draft 802.11n CardBus Card + 1154 035c WLI-CB-AMG300N 2a0c 88W8363 [TopDog] 802.11n Wireless 2a24 88W8363 [TopDog] 802.11n Wireless 2a2b 88W8687 [TopDog] 802.11b/g Wireless @@ -17087,10 +17191,33 @@ 11f7 Scientific Atlanta # née PMC-Sierra Inc. 11f8 Microchip Technology + 4000 PM40100 Switchtec PFX 100xG4 Fanout PCIe Switch + 4028 PM40028 Switchtec PFX 28xG4 Fanout PCIe Switch 4036 PM40036 Switchtec PFX 36xG4 Fanout PCIe Switch 4052 PM40052 Switchtec PFX 52xG4 Fanout PCIe Switch + 4068 PM40068 Switchtec PFX 68xG4 Fanout PCIe Switch 4084 PM40084 Switchtec PFX 84xG4 Fanout PCIe Switch + 4100 PM41100 Switchtec PSX 100xG4 Programmable PCIe Switch 4128 PM41028 Switchtec PSX 28xG4 Programmable PCIe Switch + 4136 PM41036 Switchtec PSX 36xG4 Programmable PCIe Switch + 4152 PM41052 Switchtec PSX 52xG4 Programmable PCIe Switch + 4168 PM41068 Switchtec PSX 68xG4 Programmable PCIe Switch + 4184 PM41084 Switchtec PSX 84xG4 Programmable PCIe Switch + 4200 PM42100 Switchtec PAX 100xG4 Programmable Advanced Fabric PCIe Switch + 4228 PM42028 Switchtec PAX 28xG4 Programmable Advanced Fabric PCIe Switch + 4236 PM42036 Switchtec PAX 36xG4 Programmable Advanced Fabric PCIe Switch + 4252 PM42052 Switchtec PAX 52xG4 Programmable Advanced Fabric PCIe Switch + 4268 PM42068 Switchtec PAX 68xG4 Programmable Advanced Fabric PCIe Switch + 4284 PM42084 Switchtec PAX 84xG4 Programmable Advanced Fabric PCIe Switch + 4328 PM43028 Switchtec PFXA 28xG4 Automotive Fanout PCIe Switch + 4336 PM43036 Switchtec PFXA 36xG4 Automotive Fanout PCIe Switch + 4352 PM43052 Switchtec PFXA 52xG4 Automotive Fanout PCIe Switch + 4428 PM44028 Switchtec PSXA 28xG4 Automotive Programmable PCIe Switch + 4436 PM44036 Switchtec PSXA 36xG4 Automotive Programmable PCIe Switch + 4452 PM44052 Switchtec PSXA 52xG4 Automotive Programmable PCIe Switch + 4528 PM45028 Switchtec PAXA 28xG4 Automotive Programmable Advanced Fabric PCIe Switch + 4536 PM45036 Switchtec PAXA 36xG4 Automotive Programmable Advanced Fabric PCIe Switch + 4552 PM45052 Switchtec PAXA 52xG4 Automotive Programmable Advanced Fabric PCIe Switch 5000 PM50100 Switchtec PFX 100xG5 Fanout PCIe Switch 5028 PM50028 Switchtec PFX 28xG5 Fanout PCIe Switch 5036 PM50036 Switchtec PFX 36xG5 Fanout PCIe Switch @@ -17103,10 +17230,66 @@ 5152 PM51052 Switchtec PSX 52xG5 Programmable PCIe Switch 5168 PM51068 Switchtec PSX 68xG5 Programmable PCIe Switch 5184 PM51084 Switchtec PSX 84xG5 Programmable PCIe Switch + 5200 PM52100 Switchtec PAX 100xG5 Programmable Advanced Fabric PCIe Switch 5220 BR522x [PMC-Sierra maxRAID SAS Controller] + 5228 PM52028 Switchtec PAX 28xG5 Programmable Advanced Fabric PCIe Switch + 5236 PM52036 Switchtec PAX 36xG5 Programmable Advanced Fabric PCIe Switch + 5252 PM52052 Switchtec PAX 52xG5 Programmable Advanced Fabric PCIe Switch + 5268 PM52068 Switchtec PAX 68xG5 Programmable Advanced Fabric PCIe Switch + 5284 PM52084 Switchtec PAX 84xG5 Programmable Advanced Fabric PCIe Switch + 5300 PM53100 Switchtec PFXA 100xG5 Automotive Fanout PCIe Switch + 5328 PM53028 Switchtec PFXA 28xG5 Automotive Fanout PCIe Switch + 5336 PM53036 Switchtec PFXA 36xG5 Automotive Fanout PCIe Switch + 5352 PM53052 Switchtec PFXA 52xG5 Automotive Fanout PCIe Switch + 5368 PM53068 Switchtec PFXA 68xG5 Automotive Fanout PCIe Switch + 5384 PM53084 Switchtec PFXA 84xG5 Automotive Fanout PCIe Switch + 5400 PM54100 Switchtec PSXA 100xG5 Automotive Programmable PCIe Switch + 5428 PM54028 Switchtec PSXA 28xG5 Automotive Programmable PCIe Switch + 5436 PM54036 Switchtec PSXA 36xG5 Automotive Programmable PCIe Switch + 5452 PM54052 Switchtec PSXA 52xG5 Automotive Programmable PCIe Switch + 5468 PM54068 Switchtec PSXA 68xG5 Automotive Programmable PCIe Switch + 5484 PM54084 Switchtec PSXA 84xG5 Automotive Programmable PCIe Switch + 5500 PM55100 Switchtec PAXA 100xG5 Automotive Programmable Advanced Fabric PCIe Switch + 5528 PM55028 Switchtec PAXA 28xG5 Automotive Programmable Advanced Fabric PCIe Switch + 5536 PM55036 Switchtec PAXA 36xG5 Automotive Programmable Advanced Fabric PCIe Switch + 5552 PM55052 Switchtec PAXA 52xG5 Automotive Programmable Advanced Fabric PCIe Switch + 5568 PM55068 Switchtec PAXA 68xG5 Automotive Programmable Advanced Fabric PCIe Switch + 5584 PM55084 Switchtec PAXA 84xG5 Automotive Programmable Advanced Fabric PCIe Switch + 6044 PM60144 Switchtec PFXs 144xG6 Secure-capable Fanout PCIe Switch + 6048 PM60048 Switchtec PFXs 48xG6 Secure-capable Fanout PCIe Switch + 6060 PM60160 Switchtec PFXs 160xG6 Secure-capable Fanout PCIe Switch + 6064 PM60064 Switchtec PFXs 64xG6 Secure-capable Fanout PCIe Switch + 6144 PM61144 Switchtec PSXs 144xG6 Secure-capable Programmable PCIe Switch + 6148 PM61048 Switchtec PSXs 48xG6 Secure-capable Programmable PCIe Switch + 6160 PM61160 Switchtec PSXs 160xG6 Secure-capable Programmable PCIe Switch + 6164 PM61064 Switchtec PSXs 64xG6 Secure-capable Programmable PCIe Switch + 6244 PM62144 Switchtec PFX 144xG6 Fanout PCIe Switch + 6248 PM62048 Switchtec PFX 48xG6 Fanout PCIe Switch + 6260 PM62160 Switchtec PFX 160xG6 Fanout PCIe Switch + 6264 PM62064 Switchtec PFX 64xG6 Fanout PCIe Switch + 6344 PM63144 Switchtec PSX 144xG6 Programmable PCIe Switch + 6348 PM63048 Switchtec PSX 48xG6 Programmable PCIe Switch + 6360 PM63160 Switchtec PSX 160xG6 Programmable PCIe Switch + 6364 PM63064 Switchtec PSX 64xG6 Programmable PCIe Switch + 7044 PM70144 Switchtec PFXs 144xG7 Secure-capable Fanout PCIe Switch + 7048 PM70048 Switchtec PFXs 48xG7 Secure-capable Fanout PCIe Switch + 7060 PM70160 Switchtec PFXs 160xG7 Secure-capable Fanout PCIe Switch + 7064 PM70064 Switchtec PFXs 64xG7 Secure-capable Fanout PCIe Switch + 7144 PM71144 Switchtec PSXs 144xG7 Secure-capable Programmable PCIe Switch + 7148 PM71048 Switchtec PSXs 48xG7 Secure-capable Programmable PCIe Switch + 7160 PM71160 Switchtec PSXs 160xG7 Secure-capable Programmable PCIe Switch + 7164 PM71064 Switchtec PSXs 64xG7 Secure-capable Programmable PCIe Switch + 7244 PM72144 Switchtec PFX 144xG7 Fanout PCIe Switch + 7248 PM72048 Switchtec PFX 48xG7 Fanout PCIe Switch + 7260 PM72160 Switchtec PFX 160xG7 Fanout PCIe Switch + 7264 PM72064 Switchtec PFX 64xG7 Fanout PCIe Switch 7364 PM7364 [FREEDM - 32 Frame Engine & Datalink Mgr] 7375 PM7375 [LASAR-155 ATM SAR] 7384 PM7384 [FREEDM - 84P672 Frm Engine & Datalink Mgr] + 7444 PM74144 Switchtec PSX 144xG7 Programmable PCIe Switch + 7448 PM74048 Switchtec PSX 48xG7 Programmable PCIe Switch + 7460 PM74160 Switchtec PSX 160xG7 Programmable PCIe Switch + 7464 PM74064 Switchtec PSX 64xG7 Programmable PCIe Switch 8000 PM8000 [SPC - SAS Protocol Controller] 8009 PM8009 SPCve 8x6G 8018 PM8018 Adaptec SAS Adaptor ASA-70165H PCIe Gen3 x8 6 Gbps 16-lane 4x SFF-8644 @@ -17132,8 +17315,30 @@ 8535 PM8535 PFX 80xG3 PCIe Fanout Switch 8536 PM8536 PFX 96xG3 PCIe Fanout Switch 1bd4 0081 PM8536 PFX 96xG3 PCIe Fanout Switch + 8541 PM8541 PSX 24xG3 Programmable PCIe Switch + 8542 PM8542 PSX 32xG3 Programmable PCIe Switch + 8543 PM8543 PSX 48xG3 Programmable PCIe Switch + 8544 PM8544 PSX 64xG3 Programmable PCIe Switch + 8545 PM8545 PSX 80xG3 Programmable PCIe Switch 8546 PM8546 B-FEIP PSX 96xG3 PCIe Storage Switch + 8551 PM8551 PAX 24xG3 Programmable Advanced Fabric PCIe Switch + 8552 PM8552 PAX 32xG3 Programmable Advanced Fabric PCIe Switch + 8553 PM8553 PAX 48xG3 Programmable Advanced Fabric PCIe Switch + 8554 PM8554 PAX 64xG3 Programmable Advanced Fabric PCIe Switch + 8555 PM8555 PAX 80xG3 Programmable Advanced Fabric PCIe Switch + 8556 PM8556 PAX 96xG3 Programmable Advanced Fabric PCIe Switch + 8561 PM8561 Switchtec PFX-L 24xG3 Fanout-Lite PCIe Switch 8562 PM8562 Switchtec PFX-L 32xG3 Fanout-Lite PCIe Gen3 Switch + 8563 PM8563 Switchtec PFX-L 48xG3 Fanout-Lite PCIe Switch + 8564 PM8564 Switchtec PFX-L 64xG3 Fanout-Lite PCIe Switch + 8565 PM8565 Switchtec PFX-L 80xG3 Fanout-Lite PCIe Switch + 8566 PM8566 Switchtec PFX-L 96xG3 Fanout-Lite PCIe Switch + 8571 PM8571 Switchtec PFX-I 24xG3 Industrial Fanout PCIe Switch + 8572 PM8572 Switchtec PFX-I 32xG3 Industrial Fanout PCIe Switch + 8573 PM8573 Switchtec PFX-I 48xG3 Industrial Fanout PCIe Switch + 8574 PM8574 Switchtec PFX-I 64xG3 Industrial Fanout PCIe Switch + 8575 PM8575 Switchtec PFX-I 80xG3 Industrial Fanout PCIe Switch + 8576 PM8576 Switchtec PFX-I 96xG3 Industrial Fanout PCIe Switch 11f9 I-Cube Inc 11fa Kasan Electronics Company, Ltd. 11fb Datel Inc @@ -17535,7 +17740,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 @@ -17602,12 +17807,16 @@ 3872 ISL3872 [Prism 3] 1468 0202 LAN-Express IEEE 802.11b Wireless LAN 3873 ISL3874 [Prism 2.5]/ISL3872 [Prism 3] + 10b7 0001 3CRDW696 rev A Wireless LAN PCI Adapter [ISL3874] 10cf 1169 MBH7WM01-8734 802.11b Wireless Mini PCI Card [ISL3874] + 10fc d009 WN-B11/PCIH 1186 3501 DWL-520 Wireless PCI Adapter (rev A or B) [ISL3874] 1186 3700 DWL-520 Wireless PCI Adapter (rev E1) [ISL3872] 1385 4105 MA311 802.11b wireless adapter [ISL3874] 1668 0414 HWP01170-01 802.11b PCI Wireless Adapter + 1668 1406 802MIP 802.11b Mini PCI Adapter [ISL3874] 16a5 1601 AIR.mate PC-400 PCI Wireless LAN Adapter + 16be 2001 CTX712 1737 3874 WMP11 v1 802.11b Wireless-B PCI Adapter [ISL3874] 4033 7033 PCW200 802.11b Wireless PCI Adapter [ISL3874] 8086 2510 M3AWEB Wireless 802.11b MiniPCI Adapter @@ -18692,6 +18901,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] @@ -19190,6 +19412,8 @@ 13b3 Lanart Corporation 13b4 Wellbean Co Inc 13b5 ARM + 0300 Integrated [AGI CPU] PCIe/CXL Root Port + 0301 Arm Integrated [AGI CPU] Platform PCIe Root Port 13b6 Dlog GmbH 13b7 Logic Devices Inc 13b8 Nokia Telecommunications oy @@ -19583,6 +19807,10 @@ 5821 Xenos GPU (Zephyr/Falcon) 5831 Xenos GPU (Jasper) 5841 Xenos GPU (Slim) + c030 OpenVMM PCIe Root Port + c031 OpenVMM PCIe Switch Upstream Port + c032 OpenVMM PCIe Switch Downstream Port + c03e OpenVMM NVMe Controller 1415 Oxford Semiconductor Ltd 8401 OX9162 Mode 1 (8-bit bus) 8403 OX9162 Mode 0 (parallel port) @@ -20682,31 +20910,31 @@ 1028 230f DC NVMe PM9D3a RI 80M.2 480GB ISE 1028 2310 DC NVMe PM9D3a RI 80M.2 960GB ISE 1028 2311 DC NVMe PM9D3a RI 80M.2 1.92TB ISE - 1028 2341 DC NVMe PM9D3a RI U.2 960GB  + 1028 2341 DC NVMe PM9D3a RI U.2 960GB 1028 2342 DC NVMe PM9D3a RI U.2 1.92TB 1028 2343 DC NVMe PM9D3a RI U.2 3.84TB - 1028 2344 DC NVMe PM9D3a RI U.2 7.68GTB + 1028 2344 DC NVMe PM9D3a RI U.2 7.68TB 1028 2345 DC NVMe PM9D3a RI U.2 15.36TB 1028 2346 DC NVMe FIPS PM9D3a RI U.2 960GB 1028 2347 DC NVMe FIPS PM9D3a RI U.2 1.92TB 1028 2348 DC NVMe FIPS PM9D3a RI U.2 3.84TB 1028 2349 DC NVMe FIPS PM9D3a RI U.2 7.68TB - 1028 234a DC NVMe FIPS PM9D3a RI U.2 15.36TB  - 1028 234d DC NVMe PM9D3a RI E3s 1.92TB - 1028 234e DC NVMe PM9D3a RI E3s 3.84TB  - 1028 234f DC NVMe PM9D3a RI E3s 7.68GTB - 1028 2350 DC NVMe PM9D3a RI E3s 15.36TB - 1028 2351 DC NVMe FIPS PM9D3a RI E3s 1.92TB - 1028 2352 DC NVMe FIPS PM9D3a RI E3s 3.84TB - 1028 2353 DC NVMe FIPS PM9D3a RI E3s 7.68TB - 1028 2354 DC NVMe FIPS PM9D3a RI E3s 15.36TB + 1028 234a DC NVMe FIPS PM9D3a RI U.2 15.36TB + 1028 234d DC NVMe PM9D3a RI E3.S 1.92TB + 1028 234e DC NVMe PM9D3a RI E3.S 3.84TB + 1028 234f DC NVMe PM9D3a RI E3.S 7.68TB + 1028 2350 DC NVMe PM9D3a RI E3.S 15.36TB + 1028 2351 DC NVMe FIPS PM9D3a RI E3.S 1.92TB + 1028 2352 DC NVMe FIPS PM9D3a RI E3.S 3.84TB + 1028 2353 DC NVMe FIPS PM9D3a RI E3.S 7.68TB + 1028 2354 DC NVMe FIPS PM9D3a RI E3.S 15.36TB 1028 2355 DC NVMe PM9D5a MU U.2 800GB 1028 2356 DC NVMe PM9D5a MU U.2 1.6TB 1028 2357 DC NVMe PM9D5a MU U.2 3.2TB 1028 2358 DC NVMe PM9D5a MU U.2 6.4TB - 1028 2359 DC NVMe PM9D5a MU E3.s 1.6TB - 1028 235a DC NVMe PM9D5a MU E3.s 3.2TB - 1028 235b DC NVMe PM9D5a MU E3.s 6.4TB + 1028 2359 DC NVMe PM9D5a MU E3.S 1.6TB + 1028 235a DC NVMe PM9D5a MU E3.S 3.2TB + 1028 235b DC NVMe PM9D5a MU E3.S 6.4TB aa00 NVMe SSD Controller BM1743 1028 2312 NVMe FIPS BM1743 QLC U.2 15.36TB 1028 2313 NVMe FIPS BM1743 QLC U.2 30.72TB @@ -20718,6 +20946,56 @@ 1028 2366 MZ3MO15THCLCAD3 1028 2367 MZ3MO30THCLFAD3 ac00 NVMe SSD Controller PM175x +# NVMe FIPS PM1753 RI E3.S 1.92TB + 1028 2383 MZ3L91T9HFJAAD9 +# NVMe PM1753 RI E3.S 1.92TB + 1028 2384 MZ3L91T9HFJAAD3 +# NVMe FIPS PM1753 RI E3.S 3.84TB + 1028 2385 MZ3L93T8HFJAAD9 +# NVMe PM1753 RI E3.S 3.84TB + 1028 2386 MZ3L93T8HFJAAD3 +# NVMe FIPS PM1753 RI E3.S 7.68TB + 1028 2387 MZ3L97T6HFLTAD9 +# NVMe PM1753 RI E3.S 7.68TB + 1028 2388 MZ3L97T6HFLTAD3 +# NVMe FIPS PM1753 RI E3.S 15.36TB + 1028 2389 MZ3L915THBLCAD9 +# NVMe PM1753 RI E3.S 15.36TB + 1028 238a MZ3L915THBLCAD3 +# NVMe FIPS PM1753 RI E3.S 30.72TB + 1028 238b MZ3L930THBLFAD9 +# NVMe PM1753 RI E3.S 30.72TB + 1028 238c MZ3L930THBLFAD3 +# NVMe FIPS PM1755 MU E3.S 1.6TB + 1028 238d MZ3L91T6HFJAAD9 +# NVMe PM1755 MU E3.S 1.6TB + 1028 238e MZ3L91T6HFJAAD3 +# NVMe FIPS PM1755 MU E3.S 3.2TB + 1028 238f MZ3L93T2HFJAAD9 +# NVMe PM1755 MU E3.S 3.2TB + 1028 2390 MZ3L93T2HFJAAD3 +# NVMe FIPS PM1755 MU E3.S 6.4TB + 1028 2391 MZ3L96T4HFLTAD9 +# NVMe PM1755 MU E3.S 6.4TB + 1028 2392 MZ3L96T4HFLTAD3 +# NVMe PM1753 RI U.2 1.92TB + 1028 2394 MZWL91T9HFJAAD3 +# NVMe PM1753 RI U.2 3.84TB + 1028 2396 MZWL93T8HFLTAD3 +# NVMe PM1753 RI U.2 7.68TB + 1028 2398 MZWL97T6HFLAAD3 +# NVMe PM1753 RI U.2 15.36TB + 1028 239a MZWL915THBLFAD3 +# NVMe FIPS PM1753 RI U.2 30.72TB + 1028 239b MZWL930THBLFAD9 +# NVMe PM1753 RI U.2 30.72TB + 1028 239c MZWL930THBLFAD3 +# NVMe PM1755 MU U.2 1.6TB + 1028 239e MZWL91T6HFJAAD3 +# NVMe PM1755 MU U.2 3.2TB + 1028 239f MZWL93T2HFLTAD3 +# NVMe PM1755 MU U.2 6.4TB + 1028 23a0 MZWL96T4HFLAAD3 ecec Exynos 8895 PCIe Root Complex 144e OLITEC 144f Askey Computer Corp. @@ -20925,6 +21203,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] @@ -20939,6 +21218,7 @@ 7991 MT7996 secondary link PCIe Wi-Fi 7(802.11be) 320MHz Wireless Network Adapter [Filogic 680] 7992 MT7992 primary link PCIe Wi-Fi 7(802.11be) 160MHz Wireless Network Adapter [Filogic 660] 799a MT7992 secondary link PCIe Wi-Fi 7(802.11be) 160MHz Wireless Network Adapter [Filogic 660] + 8188 MT8188 [Kompanio 838] Root Complex 8650 MT7650 Bluetooth 14c4 IWASAKI Information Systems Co Ltd 14c5 Automation Products AB @@ -21477,6 +21757,7 @@ 193d 1087 NIC-ETH531F-3S-2P 16d7 BCM57414 NetXtreme-E 10Gb/25Gb RDMA Ethernet Controller 117c 00cc FastFrame N422 Dual-port 25Gb Ethernet Adapter + 117c 40d7 ThunderLink NS 5252 Dual-port 25Gb Ethernet Adapter 14e4 1402 BCM957414A4142CC 10Gb/25Gb Ethernet PCIe 14e4 1404 BCM957414M4142C OCP 2x25G Type1 wRoCE 14e4 4140 NetXtreme E-Series Advanced Dual-port 25Gb SFP28 Network Daughter Card @@ -21548,7 +21829,7 @@ 17aa 3a23 IdeaPad S10e 1750 BCM57508 NetXtreme-E 10Gb/25Gb/40Gb/50Gb/100Gb/200Gb Ethernet 117c 00cf FastFrame N412 Dual-port 100Gb Ethernet Adapter - 117c 40d6 ThunderLink TLNS-5102 Dual-port 100Gb Ethernet Adapter + 117c 40d6 ThunderLink NS 5102 Dual-port 100Gb Ethernet Adapter 14e4 2100 NetXtreme-E Dual-port 100G QSFP56 Ethernet PCIe4.0 x16 Adapter (BCM957508-P2100G) 14e4 5208 NetXtreme-E Dual-port 100G QSFP56 Ethernet OCP 3.0 Adapter (BCM957508-N2100G) 14e4 520a NetXtreme-E Dual-port 100G DSFP Ethernet OCP 3.0 Adapter (BCM957508-N2100GD) @@ -21559,6 +21840,7 @@ 1028 09d4 PowerEdge XR11/XR12 LOM 1028 0b1b PowerEdge XR5610 LOM 117c 00da FastFrame N424 Quad-port 25Gb Ethernet Adapter + 117c 40df ThunderLink NS 5254 Quad-port 25Gb Ethernet Adapter 14e4 4250 NetXtreme-E Quad-port 25G SFP28 Ethernet PCIe4.0 x16 Adapter (BCM957504-P425G) 14e4 5045 NetXtreme-E BCM57504 4x25G OCP3.0 14e4 5100 NetXtreme-E Single-port 100G QSFP56 Ethernet OCP 3.0 Adapter (BCM957504-N1100G) @@ -21569,6 +21851,7 @@ 1590 0420 HPE Ethernet 25/50Gb 2-port 6310C Adapter 1752 BCM57502 NetXtreme-E 10Gb/25Gb/40Gb/50Gb Ethernet 1760 BCM57608 25Gb/50Gb/100Gb/200Gb/400Gb Ethernet + 117c 00cf FastFrame N522 Dual-port 200Gb Ethernet Adapter 14e4 9110 BCM57608 1x400G PCIe Ethernet NIC 14e4 9120 BCM57608 2x200G PCIe Ethernet NIC 14e4 9121 BCM57608 2x100G PCIe Ethernet NIC @@ -21584,6 +21867,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 @@ -21701,6 +21986,7 @@ 1154 0330 Buffalo WLI2-PCI-G54S High Speed Mode Wireless Desktop Adapter 144f 7050 eMachines M6805 802.11g Built-in Wireless 144f 7051 Sonnet Aria Extreme PCI + 16a5 1604 WE602-B 1737 0013 WMP54G v1 802.11g PCI Adapter 1737 0014 WMP54G v2 802.11g PCI Adapter 1737 0015 WMP54GS v1.0 802.11g Wireless-G PCI Adapter with SpeedBooster @@ -21792,6 +22078,8 @@ 43ba BCM43602 802.11ac Wireless LAN SoC 43bb BCM43602 802.11ac Wireless LAN SoC 43bc BCM43602 802.11ac Wireless LAN SoC + 43c3 BCM4366/BCM43465 802.11ac Wave2 4x4 Wireless Network Adapter + 1043 86fb PCE-AC88 43d3 BCM43567 802.11ac Wireless Network Adapter 43d9 BCM43570 802.11ac Wireless Network Adapter 43dc BCM4355 802.11ac Wireless LAN SoC @@ -21879,6 +22167,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 @@ -22641,7 +22930,6 @@ 0224 CX9 Family [ConnectX-9 Flash Recovery] 0225 CX9 Family [ConnectX-9 Secure Flash Recovery-RMA] 0226 CX10 Family [ConnectX-10 Flash Recovery] -# Name change request 0227 CX10 Family [ConnectX-10 RMA] 0228 CX9 PCIe Switch Family [ConnectX-9 PCIe Switch Flash Recovery] 0229 CX9 PCIe Switch Family [ConnectX-9 PCIe Switch Secure Flash Recovery-RMA] @@ -22675,6 +22963,8 @@ 027c NVLink-7 Switch in Flash Recovery Mode 027d NVLink-7 Switch RMA 027e Spectrum-7 Tile +# Spectrum-8 tile + 027f Spectrum-8 tile 0281 NPS-600 Flash Recovery 0282 ArcusE Flash recovery 0283 ArcusE RMA @@ -22682,21 +22972,16 @@ 0285 Sagitta RMA 0286 LibraE Flash Recovery 0287 LibraE RMA -# Flash recovery - 0288 Arcus2 + 0288 Arcus2 Flash Recovery 0289 Arcus2 RMA 0290 SagittaZ 0292 Arcus3 Flash Recovery 0293 Arcus3 RMA - 0294 Ophy 2.1 (SagittaZ) -# Sagitta - 0296 OPHY2.6 -# Sagitta - 0298 OPHY3.0 -# Sagitta - 029a OPHY3.1 -# Sagitta - 029c OPHY3.5 + 0294 OPHY2.1 [SagittaZ] + 0296 OPHY2.6 [Sagitta] + 0298 OPHY3.0 [Sagitta] + 029a OPHY3.1 [Sagitta] + 029c OPHY3.5 [Sagitta] 02a0 NVLink-8 Switch in Flash Recovery Mode 02a1 NVLink-8 Switch RMA 02a2 Spectrum-7 in Flash Recovery Mode @@ -22704,6 +22989,10 @@ # 400G Retimer 02a6 OrionR 02a7 OrionR RMA +# Spectrum-8 in Flash Recovery Mode + 02a8 Spectrum-8 in Flash Recovery Mode +# Spectrum-8 RMA + 02a9 Spectrum-8 RMA 1002 MT25400 Family [ConnectX-2 Virtual Function] 1003 MT27500 Family [ConnectX-3] 1014 04b5 PCIe3 40GbE RoCE Converged Host Bus Adapter for Power @@ -22845,6 +23134,8 @@ 2100 CX8 Family [CX8 Data Direct Interface] # NVLink Chip to Chip Link 2101 CX10 Family [ConnectX-10 NVLink-C2C] +# Starting with CX10 generation + 2300 ConnectX/BlueField Family Hardware Performance Monitors [HWPM] 4117 MT27712A0-FDCF-AE 1bd4 0039 SN10XMP2P25 1bd4 003a 25G SFP28 SP EO251FM9 Adapter @@ -22922,6 +23213,8 @@ # SwitchX-2, 40GbE switch c738 MT51136 c739 MT51136 GW +# Spectrum-8 + c788 Spectrum-8 c838 MT52236 c839 MT52236 router caf1 ConnectX-4 CAPI Function @@ -23002,6 +23295,7 @@ 5049 SN8000S NVMe SSD 504a WD Blue SN5000 NVMe SSD (DRAM-less) 5050 WD PC SN8050S / WD_BLACK SN8100 NVMe SSD + 5062 WD PC SN5100S NVMe SSD (DRAM-less) 5063 WD Blue SN5100 NVMe SSD (DRAM-less) 15b8 ADDI-DATA GmbH 1001 APCI1516 SP controller (16 digi outputs) @@ -23475,6 +23769,7 @@ 1186 3a67 DWL-G650M Super G MIMO Wireless Notebook Adapter 1186 3a68 DWL-G520M Wireless 108G MIMO Desktop Adapter 187e 340e M-302 802.11g Wireless PCI Adapter + 1976 1600 TEW-603PI 108Mbps 802.11g Wireless PCI Adapter 1976 2003 TEW-601PC 802.11g Wireless CardBus Adapter 0023 AR5416 Wireless Network Adapter [AR5008 802.11(a)bgn] 0308 340b NWD-170N 802.11bgn Wireless CardBus Adapter @@ -23492,7 +23787,10 @@ 1976 2008 TEW-621PC 802.11bgn Wireless CardBus Adapter 0024 AR5418 Wireless Network Adapter [AR5008E 802.11(a)bgn] (PCI-Express) 106b 0087 AirPort Extreme + 1154 0366 WLP-EXC-AG300 802.11abgn ExpressCard + 1186 3a6f DWA-643 Xtreme N Notebook ExpressCard 1186 3a70 DWA-556 Xtreme N PCI Express Desktop Adapter + 1799 8071 F5D8071 v1 N1 Wireless ExpressCard 0027 AR9160 Wireless Network Adapter [AR9001 802.11(a)bgn] 0777 4082 SR71-A 802.11abgn Wireless Mini PCI Adapter 0029 AR922X Wireless Network Adapter @@ -24045,6 +24343,8 @@ a300 OCTEON TX CN83XX af00 CN99xx [ThunderX2] Integrated PCI Host bridge af84 CN99xx [ThunderX2] Integrated PCI Express RP Bridge +# Name inferred from https://www.prnewswire.com/news-releases/cavium-and-xpliant-introduce-a-fully-programmable-switch-silicon-family-scaling-to-32-terabits-per-second-275262511.html + f010 XPliant CNX880xx Network Processor 1787 Hightech Information System Ltd. 1789 Ennyah Technologies Corp. # also used by Struck Innovative Systeme for joint developments @@ -24146,11 +24446,16 @@ # nee Airgo Networks, Inc. 17cb Qualcomm Technologies, Inc 0001 AGN100 802.11 a/b/g True MIMO Wireless Card + 1154 0337 WLI-CB-G108 1385 5c00 WGM511 Pre-N 802.11g Wireless CardBus Adapter + 14ea 6101 CQW-NS108G + 1737 0037 WPC54GX Wireless-G Notebook Adapter with SRX 1737 0045 WMP54GX v1 802.11g Wireless-G PCI Adapter with SRX 0002 AGN300 802.11 a/b/g True MIMO Wireless Card + 1043 106f WL-106gM 240 MIMO Wireless CardBus Adapter 1385 6d00 WPNT511 RangeMax 240 Mbps Wireless CardBus Adapter 1737 0054 WPC54GX4 v1 802.11g Wireless-G Notebook Adapter with SRX400 + 1737 0056 WMP54GX4 Wireless-G PCI Adapter with SRX400 0104 APQ8096 PCIe Root Complex [Snapdragon 820] 0105 MSM8998 PCIe Root Complex 0106 SDM850 PCIe Root Complex [Snapdragon 850] @@ -24168,7 +24473,28 @@ 0302 MDM9x55 LTE Modem [Snapdragon X16] 0304 SDX24 [Snapdragon X24 4G] 0306 SDX55 [Snapdragon X55 5G] - 0308 SDX62 [Snapdragon X62 5G] + 0308 SDX61/SDX62/SDX65 [Snapdragon 5G X6X-series] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e142 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e143 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e144 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e145 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e146 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e150 T99W696 5G Modem [Snapdragon X61] + 105b e151 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e152 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e153 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e154 T99W696 5G Modem [Snapdragon X61] +# Ref: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/bus/mhi/host/pci_generic.c + 105b e155 T99W696 5G Modem [Snapdragon X61] 0400 Datacenter Technologies QDF2432 PCI Express Root Port 0401 Datacenter Technologies QDF2400 PCI Express Root Port 1000 QCS405 PCIe Root Complex @@ -24380,6 +24706,7 @@ 2120 IPN 2120 802.11b 1737 0020 WMP11 v4 802.11b Wireless-B PCI Adapter 2220 IPN 2220 802.11g + 1154 0334 WLI2-CB-G54L 1468 0305 T60N871 802.11g Mini PCI Wireless Adapter 1737 0029 WPC54G v4 802.11g Wireless-G Notebook Adapter 17ff Benq Corporation @@ -24455,15 +24782,18 @@ 0301 RT2561/RT61 802.11g PCI 1186 3c08 AirPlus G DWL-G630 Wireless Cardbus Adapter (rev.E1) 1186 3c09 DWL-G510 Rev C + 1259 c123 CG-WLCB54GPX 13d1 abe3 miniPCI Pluscom 802.11 a/b/g 1458 e933 GN-WI01GS 1458 e934 GN-WP01GS 1462 b833 MP54G5 (MS-6833B) + 1462 b834 PC60G (MS-6834B) Wireless 11g Turbo G PCI Card 1737 0055 WMP54G v4.1 1799 700e F5D7000 v6000 Wireless G Desktop Card 1799 701e F5D7010 v6000 Wireless G Notebook Card 17f9 0012 AWLC3026T 802.11g Wireless CardBus Adapter 1814 2561 EW-7108PCg/EW-7128g + 182d 90aa WL-170 Wireless Network Cardbus Card 0302 RT2561/RT61 rev B 802.11g 1186 3a71 DWA-510 Wireless G Desktop Adapter 1186 3c08 AirPlus G DWL-G630 Wireless Cardbus Adapter (rev.E2) @@ -24480,11 +24810,15 @@ 187e 3412 NWD-310N 802.11n Wireless PCI Adapter 0681 RT2890 Wireless 802.11n PCIe 1458 e939 GN-WS30N-RH 802.11bgn Mini PCIe Card + 1799 8073 F5D8073 v1 N Wireless ExpressCard Adapter + 1799 807c F5D8071 v3 N1 Wireless ExpressCard 0701 RT2760 Wireless 802.11n 1T/2R 1737 0074 WMP110 v2 802.11n RangePlus Wireless PCI Adapter 0781 RT2790 Wireless 802.11n 1T/2R PCIe 11ad 7600 HP WN7600R + 1799 817c F5D8073 v3 N Wireless ExpressCard Adapter 1814 2790 RT2790 Wireless 802.11n 1T/2R PCIe + 1976 2600 TEW-642EC Wireless N ExpressCard 3060 RT3060 Wireless 802.11n 1T/1R 1186 3c04 DWA-525 Wireless N 150 Desktop Adapter (rev.A1) 3062 RT3062 Wireless 802.11n 2T/2R @@ -25195,6 +25529,7 @@ 5028 PS5028-E28 PCIe5 NVMe Controller 5029 PS5029-E29T PCIe4 NVMe Controller (DRAM-less) 5031 PS5031-E31T PCIe5 NVMe Controller + 5037 PS5037-E37T PCIe5 NVMe Controller (DRAM-less) 5302 PS5302-X2 PCIe5 NVMe Controller 1989 Montilio Inc. 0001 RapidFile Bridge @@ -25203,9 +25538,11 @@ 1993 Innominate Security Technologies AG 1998 Toyou Feiji Electronics Co., Ltd. 0001 TOBOLT1 51987 NVMe SSD + 1998 0192 TOBOLT1 45967 1920G M.2 NVMe SSD 1998 0384 TOBOLT1 51987 3840G 2.5" U.2 NVMe SSD 1998 0768 TOBOLT1 51987 7680G 2.5" U.2 NVMe SSD 1998 2012 TOBOLT1 51987 3840G 2.5" U.2 NVMe SSD + 1998 6144 TOBOLT1 51995 6144G 2.5" U.2 NVMe SSD 1999 A-Logics a900 AM-7209 Video Processor 199a Pulse-LINK, Inc. @@ -25318,6 +25655,12 @@ 19e5 0051 Hi1822 SP681 (2*25/10GE) 19e5 0052 Hi1822 SP680 (4*25/10GE) 19e5 00a1 Hi1822 SP670 (2*100GE) + 19e5 0152 Hi1822 SP623Q (4*25/10GE) + 19e5 01a1 Hi1822 SP625D (2*100GE) + 0229 Hi1872 Family + 022a Hi1872 Family Virtual Function + 0230 Hi1825 Family + 0231 Hi1825 Family Virtual Function 1710 iBMA Virtual Network Adapter 1711 Hi171x Series [iBMC Intelligent Management system chip w/VGA support] 1712 Intelligent Management system chip Virtual Network Adapter @@ -25418,6 +25761,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 @@ -25577,7 +25921,10 @@ 0016 SEL-3350 Serial Expansion Board 0017 SEL-3350 GPIO Expansion Board 0018 SEL-3390E4 Ethernet Adapter + 0019 SEL-2241-2/SEL-3361 Mainboard 001c SEL-3390E4 Ethernet Adapter + 001d SEL-3350 GPIO Expansion Board + 001e SEL-3350 Serial Expansion Board 1aab Silver Creations AG 7750 Sceye 10L 1aae Global Velocity, Inc. @@ -26213,7 +26560,7 @@ 5021 PCIe Gen4 SSD # 1TB 5026 FireCuda 540 SSD - 5027 LaCie Rugged SSD Pro5 + 5027 BarraCuda 530 SSD / LaCie Rugged SSD Pro5 5100 PCIe Gen3 SSD 5101 PCIe Gen5 SSD 1bb3 Bluecherry @@ -26234,12 +26581,25 @@ 1001 PCIe 3TG6-P Controller 1002 PCIe 3TE6 Controller (DRAM-less) 1160 PCIe 3TE2 Controller + 1202 PCIe 3TE8 Controller + 120a PCIe 3IE8 Controller + 120b PCIe 3TO8 Controller 1321 PCIe 4TG-P Controller 1322 PCIe 4TE Controller + 1602 PCIe 4TE3 Controller + 160a PCIe 4IE3 Controller 2262 PCIe 3TG3-P Controller 5208 PCIe 3TE7 Controller - 5216 PCIe 3TE8 Controller + 5216 PCIe 3TE9 controller + 521a PCIe 3IE9 Controller + 5220 PCIe 4TE2 Controller + 522a PCIe 4IE2 Controller 5236 PCIe 4TG2-P Controller +# based on PCIe 4TG2-P Controller, for 4TS2-P series + 523a PCIe 4TS2-P Controller + 523b PCIe 4IG2-P Controller +# based on SM8366 Controller + 8366 PCIe 5TS-P Controller 1bcd Apacer Technology 0120 NVMe SSD Drive 960GB 0180 PB4480 NVMe SSD (DRAM-less) @@ -26395,6 +26755,7 @@ a015 FB2CGHH Capture 2x100Gb [Tivoli] a016 FB2CG Capture 8x25Gb [Savona] a017 FB2CGHH Capture 8x25Gb [Tivoli] a017 + a01b FB2CDG1 Capture 2x100Gb [Thunderfjord] # Used on V120 VME Crate Controller 1c32 Highland Technology, Inc. 1c33 Daktronics, Inc @@ -26559,8 +26920,20 @@ 1c5f 1421 NVMe SSD PBlaze7 7A40 1920G 2.5" U.2 1c5f 1431 NVMe SSD PBlaze7 7A40 3840G 2.5" U.2 1c5f 1441 NVMe SSD PBlaze7 7A40 7680G 2.5" U.2 + 1c5f 1631 NVMe SSD PBlaze7 7A40 3840G 2.5" U.2 + 1c5f 1641 NVMe SSD PBlaze7 7A40 7680G 2.5" U.2 + 1c5f 1651 NVMe SSD PBlaze7 7A40 15360G 2.5" U.2 + 1c5f 1661 NVMe SSD PBlaze7 7A40 30720G 2.5" U.2 + 1c5f 1751 NVMe SSD PBlaze7 7A40 Ocean 15360G 2.5" U.2 + 1c5f 1761 NVMe SSD PBlaze7 7A40 Ocean 30720G 2.5" U.2 + 1c5f 1771 NVMe SSD PBlaze7 7A40 Ocean 61440G 2.5" U.2 + 1c5f 1781 NVMe SSD PBlaze7 7A40 Ocean 122880G 2.5" U.2 1c5f 5431 NVMe SSD PBlaze7 7A46 3200G 2.5" U.2 1c5f 5441 NVMe SSD PBlaze7 7A46 6400G 2.5" U.2 + 1c5f 5631 NVMe SSD PBlaze7 7A46 3200G 2.5" U.2 + 1c5f 5641 NVMe SSD PBlaze7 7A46 6400G 2.5" U.2 + 1c5f 5651 NVMe SSD PBlaze7 7A46 12800G 2.5" U.2 + 1c5f 5661 NVMe SSD PBlaze7 7A46 25600G 2.5" U.2 003d PBlaze5 920/926 1c5f 0a30 NVMe SSD PBlaze5 920 3840G AIC 1c5f 0a31 NVMe SSD PBlaze5 920 3840G 2.5" U.2 @@ -26685,6 +27058,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) @@ -26712,6 +27086,7 @@ 634c LEGEND 820 NVMe SSD (DRAM-less) 635a XPG GAMMIX S60 NVMe SSD (DRAM-less) 636a XPG GAMMIX S55 NVMe SSD (DRAM-less) + 641a LEGEND 970 PRO NVMe SSD 642a XPG GAMMIX S50 CORE NVMe SSD (DRAM-less) 646a XPG MARS 980 BLADE NVMe SSD 648a LEGEND 860 NVMe SSD (DRAM-less) @@ -26765,6 +27140,8 @@ 6a04 AM6A0 PCIe 4.0 NVMe SSD 1024GB (DRAM-less) 6a13 RPJYJ512MKN1QWQ PCIe 4.0 NVMe SSD 512GB (DRAM-less) 6a14 AM6A1 PCIe 4.0 NVMe SSD 1024GB (DRAM-less) + 6b02 AM6B0 PCIe 4.0 NVMe SSD 256GB (DRAM-less) + 6b03 RPETJ512MMW1MDQ PCIe 4.0 NVMe SSD 512GB (DRAM-less) 6b04 AM6B0 PCIe 4.0 NVMe SSD 6b13 AM6B1 PCIe 4.0 NVMe SSD 512GB (DRAM-less) 6b14 RPJYJ1T24MLR1HWY PCIe 4.0 NVMe SSD 1024GB (DRAM-less) @@ -26951,6 +27328,7 @@ efa1 Elastic Fabric Adapter (EFA) efa2 Elastic Fabric Adapter (EFA) efa3 Elastic Fabric Adapter (EFA) + efa4 Elastic Fabric Adapter (EFA) 1d17 Zhaoxin 070f ZX-100 PCI Express Root Port 0710 ZX-100/ZX-200 PCI Express Root Port @@ -26960,14 +27338,14 @@ 0714 ZX-100/ZX-200 PCI Express Root Port 0715 ZX-100/ZX-200 PCI Express Root Port 0716 ZX-D PCI Express Root Port - 0717 KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Express Root Port - 0718 KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Express Root Port - 0719 KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Express Root Port + 0717 KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Express Root Port + 0718 KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Express Root Port + 0719 KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Express Root Port 071a KX-5000/KX-6000/KX-6000G/KH-40000 PCI Express Root Port - 071b KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Express Root Port - 071c KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Express Root Port - 071d KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Express Root Port - 071e KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Express Root Port + 071b KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Express Root Port + 071c KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Express Root Port + 071d KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Express Root Port + 071e KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Express Root Port 071f ZX-200 Upstream Port of PCI Express Switch 0720 ZX-200 PCIE RC6 controller 0721 ZX-200 Downstream Port of PCI Express Switch @@ -26983,26 +27361,58 @@ 0739 KX-7000 PCIE Express Root Port 073a KX-7000 PCIE Express Root Port 073b KX-7000 PCIE Express Root Port + 073c KH-50000 PCI Express Root Port + 073d KH-50000 PCI Express Root Port + 073e KH-50000 PCI Express Root Port + 073f KH-50000 PCI Express Root Port + 0740 KH-50000 PCI Express Root Port + 0741 KH-50000 PCI Express Root Port + 0742 KH-50000 PCI Express Root Port + 0743 KH-50000 PCI Express Root Port + 0744 KH-50000 PCI Express Root Port + 0745 KH-50000 PCI Express Root Port + 0746 KH-50000 PCI Express Root Port + 0747 KH-50000 PCI Express Root Port + 0748 KH-50000 PCI Express Root Port + 0749 KH-50000 PCI Express Root Port + 074a KH-50000 PCI Express Root Port + 074b KH-50000 PCI Express Root Port + 074c KH-50000 PCI Express Root Port + 074d KH-50000 PCI Express Root Port + 074e KH-50000 PCI Express Root Port + 074f KH-50000 PCI Express Root Port + 0750 KH-50000 PCI Express Root Port + 0751 KH-50000 PCI Express Root Port + 0752 KH-50000 PCI Express Root Port + 0757 KH-50000 PCI Express Root Port + 0758 KH-50000 PCI Express Root Port + 0759 KH-50000 PCI Express Root Port + 075a KH-50000 PCI Express Root Port + 075b KH-50000 PCI Express Root Port + 075c KH-50000 PCI Express Root Port + 075d KH-50000 PCI Express Root Port + 075e KH-50000 PCI Express Root Port 1000 ZX-D Standard Host Bridge - 1001 ZX-D/ZX-E/KH-40000/KX-7000 Miscellaneous Bus + 1001 ZX-D/ZX-E/KH-40000/KX-7000/KH-50000 Miscellaneous Bus 1003 ZX-E Standard Host Bridge 1005 KH-40000 Standard Host Bridge 1006 KX-6000G Standard Host Bridge 1007 KX-7000 Standard Host Bridge + 1008 KH-50000 Standard Host Bridge 3001 ZX-100 Standard Host Bridge 300a ZX-100 Miscellaneous Bus - 3038 ZX-100/ZX-200/KX-6000/KX-6000G/KH-40000/KX-7000 Standard Universal PCI to USB Host Controller - 3104 ZX-100/ZX-200/KX-6000/KX-6000G/KH-40000/KX-7000 Standard Enhanced PCI to USB Host Controller - 31b0 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 Standard Host Bridge - 31b1 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 Standard Host Bridge + 3038 ZX-100/ZX-200/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 Standard Universal PCI to USB Host Controller + 3104 ZX-100/ZX-200/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 Standard Enhanced PCI to USB Host Controller + 31b0 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 Standard Host Bridge + 31b1 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 Standard Host Bridge 31b2 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 DRAM Controller - 31b3 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 Power Management Controller - 31b4 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 I/O APIC - 31b5 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 Scratch Device - 31b7 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 Standard Host Bridge + 31b3 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 Power Management Controller + 31b4 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 I/O APIC + 31b5 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 Scratch Device + 31b7 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 Standard Host Bridge 31b8 ZX-100/ZX-D PCI to PCI Bridge 3200 KX-7000 Host Bridge - 3288 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 High Definition Audio Controller + 3288 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 High Definition Audio Controller 345b ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000 Miscellaneous Bus 3a02 ZX-100 C-320 GPU 3a03 ZX-D C-860 GPU @@ -27015,12 +27425,12 @@ 3c00 KH-40000/KX-7000 DRAM Controller 3c02 KX-6000G DRAM Controller 3d01 KX-6000G C-1080 GPU - 9002 ZX-100/ZX-200/KH-40000/KX-7000 EIDE Controller + 9002 ZX-100/ZX-200/KH-40000/KX-7000/KH-50000 EIDE Controller 9003 ZX-100/KX-6000/KX-6000G EIDE Controller - 9043 KX-6000G/KH-40000/KX-7000 RAID Controller + 9043 KX-6000G/KH-40000/KX-7000/KH-50000 RAID Controller 9045 ZX-100/ZX-D/ZX-E RAID Accelerator 0 9046 ZX-D/ZX-E RAID Accelerator 1 - 9083 ZX-100/ZX-200/KX-6000/KX-6000G/KH-40000/KX-7000 StorX AHCI Controller + 9083 ZX-100/ZX-200/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 StorX AHCI Controller 9084 ZX-100 StorX AHCI Controller 9100 ZX-200 Cross bus 9101 ZX-200 Traffic Controller @@ -27037,12 +27447,13 @@ 9204 KX-6000/KX-6000G/KX-7000 USB3 xHCI Host Controller 9205 KH-40000 USB eXtensible Host Controller 9206 KX-7000 USB4 Contoller + 9207 KH-50000 USB eXtensible Host Controller 9286 ZX-D eMMC Host Controller - 9300 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 eSPI Host Controller + 9300 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 eSPI Host Controller 9500 KX-7000 I2S Controller 9501 KX-7000 I2S Controller 95d0 ZX-100 Universal SD Host Controller - f410 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000 PCI Com Port + f410 ZX-100/KX-5000/KX-6000/KX-6000G/KH-40000/KX-7000/KH-50000 PCI Com Port 1d18 RME 0001 Fireface UFX+ # acquired by Intel @@ -27177,6 +27588,7 @@ 102d AR-TK242-FX2 [8x10GbE Gen5 Packet Capture-Replay Device] 102e AR-TK242-FX2 [8x25GbE Gen5 Packet Capture-Replay Device] 102f AR-TK242-FX2 [1x400GbE Gen5 Packet Capture-Replay Device] + 1030 AR-ARKSTREAM [Arkville Streaming DMA] 4200 A5PL-E1-10GETI [10 GbE Ethernet Traffic Instrument] 1d72 Xiaomi 1d78 DERA Storage @@ -27951,6 +28363,7 @@ 0018 Exceria Pro NVMe SSD 001a NVMe SSD Controller BG6 (DRAM-less) 001b NVMe SSD Controller EG6 (DRAM-less) + 001e NVMe SSD Controller LD2-L 001f NVMe SSD Controller CD8 1028 2223 DC NVMe CD8 U.2 SED 15.36TB 1028 2224 DC NVMe CD8 U.2 SED 7.68TB @@ -27999,9 +28412,93 @@ 0032 PCIe 4.0 NVMe SSD Controller XG10d 0033 Exceria Plus G4 NVMe SSD (DRAM-less) 0034 CM9-based E3.S NVMe SSD + 1028 240f KCM9FRJE1T92 + 1028 2410 KCM9FRJE3T84 + 1028 2411 KCM9FRJE7T68 + 1028 2412 KCM9FRJE15T3 + 1028 2413 KCM9FRJE30T7 + 1028 2414 KCM9XRJE1T92 + 1028 2415 KCM9XRJE3T84 + 1028 2416 KCM9XRJE7T68 + 1028 2417 KCM9XRJE15T3 + 1028 2418 KCM9XRJE30T7 + 1028 2419 KCM9FVJE1T60 + 1028 241a KCM9FVJE3T20 + 1028 241b KCM9FVJE6T40 + 1028 241c KCM9FVJE12T8 + 1028 241d KCM9XVJE1T60 + 1028 241e KCM9XVJE3T20 + 1028 241f KCM9XVJE6T40 + 1028 2420 KCM9XVJE12T8 0035 CM9-based U3 NVMe SSD + 1028 23fb KCM9FRUL1T92 + 1028 23fc KCM9FRUL3T84 + 1028 23fd KCM9FRUL7T68 + 1028 23fe KCM9FRUL15T3 + 1028 23ff KCM9FRUL30T7 + 1028 2400 KCM9FRUL61T4 + 1028 2401 KCM9XRUL1T92 + 1028 2402 KCM9XRUL3T84 + 1028 2403 KCM9XRUL7T68 + 1028 2404 KCM9XRUL15T3 + 1028 2405 KCM9XRUL30T7 + 1028 2406 KCM9XRUL61T4 + 1028 2407 KCM9FVUL1T60 + 1028 2408 KCM9FVUL3T20 + 1028 2409 KCM9FVUL6T40 + 1028 240a KCM9FVUL12T8 + 1028 240b KCM9XVUL1T60 + 1028 240c KCM9XVUL3T20 + 1028 240d KCM9XVUL6T40 + 1028 240e KCM9XVUL12T8 + 0036 NVMe SSD Controller CD9P U.2 + 1028 23d5 KCD9DPUG1T92 + 1028 23d6 KCD9DPUG3T84 + 1028 23d7 KCD9DPUG7T68 + 1028 23d8 KCD9DPUG15T3 + 1028 23d9 KCD9DPUG30T7 + 1028 23da KCD9DPUG61T4 + 1028 23db KCD9XPUG1T92 + 1028 23dc KCD9XPUG3T84 + 1028 23dd KCD9XPUG7T68 + 1028 23de KCD9XPUG15T3 + 1028 23df KCD9XPUG30T7 + 1028 23e0 KCD9XPUG61T4 + 1028 23e1 KCD9DPUG1T60 + 1028 23e2 KCD9DPUG3T20 + 1028 23e3 KCD9DPUG6T40 + 1028 23e4 KCD9DPUG12T8 + 1028 23e5 KCD9XPUG1T60 + 1028 23e6 KCD9XPUG3T20 + 1028 23e7 KCD9XPUG6T40 + 1028 23e8 KCD9XPUG12T8 + 0037 NVMe SSD Controller CD9P E3.S + 1028 23e9 KCD9DPJE1T92 + 1028 23ea KCD9DPJE3T84 + 1028 23eb KCD9DPJE7T68 + 1028 23ec KCD9DPJE15T3 + 1028 23ed KCD9DPJE30T7 + 1028 23ee KCD9XPJE1T92 + 1028 23ef KCD9XPJE3T84 + 1028 23f0 KCD9XPJE7T68 + 1028 23f1 KCD9XPJE15T3 + 1028 23f2 KCD9XPJE30T7 + 1028 23f3 KCD9DPJE1T60 + 1028 23f4 KCD9DPJE3T20 + 1028 23f5 KCD9DPJE6T40 + 1028 23f6 KCD9DPJE12T8 + 1028 23f7 KCD9XPJE1T60 + 1028 23f8 KCD9XPJE3T20 + 1028 23f9 KCD9XPJE6T40 + 1028 23fa KCD9XPJE12T8 + 003a NVMe SSD Controller BG8 (DRAM-less) + 003b Exceria Basic NVMe SSD (DRAM-less) + 003d LC9 E3.L NVMe SSD + 1028 244f RLC9GZV245T + 1028 2450 RLC9CZV245T 003e LC9 E3.S NVMe SSD 003f LC9 U.2 NVMe SSD + 0042 NVMe SSD Controller LD4 1e17 Arnold & Richter Cine Technik GmbH & Co. Betriebs KG 1e18 Beijing GuangRunTong Technology Development Co.,Ltd 1e24 Squirrels Research Labs @@ -28020,6 +28517,8 @@ 1e30 Sophgo 1684 BM1684 [Sophon Series Deep Learning Accelerator] 2042 SG2042 Root Complex +# https://www.sord.co.jp/ +1e31 SORD CORPORATION 1e36 Shanghai Enflame Technology Co. Ltd 0001 T10 [CloudBlazer] 0002 T11 [CloudBlazer] @@ -28410,6 +28909,7 @@ 1e81 f123 NVMe SSD TP6500 series U.2 3840GB 6206 AM620 NVMe SSD 6a02 AM6A0 NVMe SSD (DRAM-less) + 6c14 RPEYJ1T24 NVMe SSD (DRAM-less) 1e83 Huaqin Technology Co.Ltd 1e85 HEITEC AG 1e89 ID Quantique SA @@ -28419,7 +28919,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 +28927,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) @@ -28436,6 +28936,12 @@ 1e95 0001 M.2 2280 960 GB 1e95 0002 M.2 2280 1920 GB 1e95 0003 M.2 22110 3840 GB + 1e95 0004 U.2 1920 GB + 1e95 0005 U.2 3840 GB + 1e95 0006 U.2 7680 GB + 1e95 0007 U.2 1600 GB + 1e95 0008 U.2 3200 GB + 1e95 0009 U.2 6400 GB 100f EJ5-2W3840 NVMe SSD U.2 1010 CX3 Series NVMe SSD 1e95 0000 M.2 2280 480 GB @@ -28640,6 +29146,38 @@ 1ee4 0625 NVMe SSD U.2 1.6TB (P8128Z3) 1ee4 0626 NVMe SSD U.2 3.2TB (P8128Z3) 1ee4 0627 NVMe SSD U.2 6.4TB (P8128Z3) + 1ee4 0715 NVMe SSD U.2 1.92TB (P8118Z4) + 1ee4 0716 NVMe SSD U.2 3.84TB (P8118Z4) + 1ee4 0717 NVMe SSD U.2 7.68TB (P8118Z4) + 1ee4 0718 NVMe SSD U.2 15.36TB (P8118Z4) + 1ee4 0725 NVMe SSD U.2 1.6TB (P8118Z4) + 1ee4 0726 NVMe SSD U.2 3.2TB (P8118Z4) + 1ee4 0727 NVMe SSD U.2 6.4TB (P8118Z4) + 1ee4 0728 NVMe SSD U.2 12.8TB (P8118Z4) + 1ee4 0815 NVMe SSD U.2 1.92TB (P8118H4) + 1ee4 0816 NVMe SSD U.2 3.84TB (P8118H4) + 1ee4 0817 NVMe SSD U.2 7.68TB (P8118H4) + 1ee4 0825 NVMe SSD U.2 1.6TB (P8118H4) + 1ee4 0826 NVMe SSD U.2 3.2TB (P8118H4) + 1ee4 0827 NVMe SSD U.2 6.4TB (P8118H4) + 1ee4 0915 NVMe SSD U.2 1.92TB (P8118E2) + 1ee4 0916 NVMe SSD U.2 3.84TB (P8118E2) + 1ee4 0917 NVMe SSD U.2 7.68TB (P8118E2) + 1ee4 0925 NVMe SSD U.2 1.6TB (P8118E2) + 1ee4 0926 NVMe SSD U.2 3.2TB (P8118E2) + 1ee4 0927 NVMe SSD U.2 6.4TB (P8118E2) + 1ee4 0a15 NVMe SSD U.2 1.92TB (P8118H2) + 1ee4 0a16 NVMe SSD U.2 3.84TB (P8118H2) + 1ee4 0a17 NVMe SSD U.2 7.68TB (P8118H2) + 1ee4 0a25 NVMe SSD U.2 1.6TB (P8118H2) + 1ee4 0a26 NVMe SSD U.2 3.2TB (P8118H2) + 1ee4 0a27 NVMe SSD U.2 6.4TB (P8118H2) + 1ee4 0b15 NVMe SSD U.2 1.92TB (P8118H3) + 1ee4 0b16 NVMe SSD U.2 3.84TB (P8118H3) + 1ee4 0b17 NVMe SSD U.2 7.68TB (P8118H3) + 1ee4 0b25 NVMe SSD U.2 1.6TB (P8118H3) + 1ee4 0b26 NVMe SSD U.2 3.2TB (P8118H3) + 1ee4 0b27 NVMe SSD U.2 6.4TB (P8118H3) 1ee4 3013 NVMe SSD AIC 480GB (P8118E) 1ee4 3014 NVMe SSD AIC 960GB (P8118E) 1ee4 3015 NVMe SSD AIC 1.92TB (P8118E) @@ -28688,6 +29226,14 @@ 1ee4 3625 NVMe SSD AIC 1.6TB (P8128Z3) 1ee4 3626 NVMe SSD AIC 3.2TB (P8128Z3) 1ee4 3627 NVMe SSD AIC 6.4TB (P8128Z3) + 1ee4 3715 NVMe SSD AIC 1.92TB (P8118Z4) + 1ee4 3716 NVMe SSD AIC 3.84TB (P8118Z4) + 1ee4 3717 NVMe SSD AIC 7.68TB (P8118Z4) + 1ee4 3718 NVMe SSD AIC 15.36TB (P8118Z4) + 1ee4 3725 NVMe SSD AIC 1.6TB (P8118Z4) + 1ee4 3726 NVMe SSD AIC 3.2TB (P8118Z4) + 1ee4 3727 NVMe SSD AIC 6.4TB (P8118Z4) + 1ee4 3728 NVMe SSD AIC 12.8TB (P8118Z4) 1ee4 abcd NVMe SSD U.2 1181 PETA8118 NVMe E1S Series 1ee4 2015 NVMe SSD E1.S 1.92TB (P8118E) @@ -28732,6 +29278,14 @@ 1ee4 2625 NVMe SSD E1.S 1.6TB (P8128Z3) 1ee4 2626 NVMe SSD E1.S 3.2TB (P8128Z3) 1ee4 2627 NVMe SSD E1.S 6.4TB (P8128Z3) + 1ee4 2715 NVMe SSD E1.S 1.92TB (P8118Z4) + 1ee4 2716 NVMe SSD E1.S 3.84TB (P8118Z4) + 1ee4 2717 NVMe SSD E1.S 7.68TB (P8118Z4) + 1ee4 2718 NVMe SSD E1.S 15.36TB (P8118Z4) + 1ee4 2725 NVMe SSD E1.S 1.6TB (P8118Z4) + 1ee4 2726 NVMe SSD E1.S 3.2TB (P8118Z4) + 1ee4 2727 NVMe SSD E1.S 6.4TB (P8118Z4) + 1ee4 2728 NVMe SSD E1.S 12.8TB (P8118Z4) 1182 PETA8118 NVMe M2 Series 1ee4 1013 NVMe SSD M.2 480GB (P8118E) 1ee4 1014 NVMe SSD M.2 960GB (P8118E) @@ -28797,6 +29351,22 @@ 1ee4 1625 NVMe SSD M.2 1.6TB (P8128Z3) 1ee4 1626 NVMe SSD M.2 3.2TB (P8128Z3) 1ee4 1627 NVMe SSD M.2 6.4TB (P8128Z3) + 1ee4 1714 NVMe SSD M.2 960GB (P8118Z4) + 1ee4 1715 NVMe SSD M.2 1.92TB (P8118Z4) + 1ee4 1716 NVMe SSD M.2 3.84TB (P8118Z4) + 1ee4 1717 NVMe SSD M.2 7.68TB (P8118Z4) + 1ee4 1724 NVMe SSD M.2 800GB (P8118Z4) + 1ee4 1725 NVMe SSD M.2 1.6TB (P8118Z4) + 1ee4 1726 NVMe SSD M.2 3.2TB (P8118Z4) + 1ee4 1727 NVMe SSD M.2 6.4TB (P8118Z4) + 1ee4 1814 NVMe SSD M.2 960GB (P8118H4) + 1ee4 1815 NVMe SSD M.2 1.92TB (P8118H4) + 1ee4 1816 NVMe SSD M.2 3.84TB (P8118H4) + 1ee4 1817 NVMe SSD M.2 7.68TB (P8118H4) + 1ee4 1824 NVMe SSD M.2 800GB (P8118H4) + 1ee4 1825 NVMe SSD M.2 1.6TB (P8118H4) + 1ee4 1826 NVMe SSD M.2 3.2TB (P8118H4) + 1ee4 1827 NVMe SSD M.2 6.4TB (P8118H4) 1ee9 SUSE LLC 1eec Viscore Technologies Ltd 0102 VSE250231S Dual-port 10Gb/25Gb Ethernet PCIe @@ -28864,6 +29434,7 @@ 1f02 Beijing Dayu Technology 1f03 Shenzhen Shichuangyi Electronics Co., Ltd 1202 MAP1202-Based NVMe SSD (DRAM-less) + 1608 SCY C5000 NVMe SSD (DRAM-less) 2262 SM2262EN-based OEM SSD 2263 SM2263XT-Based NVMe SSD (DRAM-less) 2268 SM2268XT-Based NVMe SSD (DRAM-less) @@ -28943,8 +29514,10 @@ 1f0f 0001 S2055AS, 2x 25GbE, SFP28, PCIe 4.0 x8 1f0f 0002 S2025XS, 2x 10GbE, SFP+, PCIe 4.0 x8 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 0001 S2025XT, 2x 10GbE, Base-T, PCIe 4.0 x8 + 1f0f 0002 S2025XT, 2x 10GbE, BASE-T, PCIe 4.0 x4, Fan + 1f0f 0003 S2045XT, 4x 10GbE, Base-T, PCIe 4.0 x8 + 1f0f 0004 S2045XT, 4x 10GbE, BASE-T, PCIe 4.0 x8, Fan 350a M18305 Family Virtual Function 1f0f 0001 M18305 Family Virtual Function 9088 D1055AS PCI Express Switch Downstream Port @@ -28990,6 +29563,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 @@ -29068,11 +29643,21 @@ 1012 K3 Family [FLEXFLOW-3100T Virtual Function] 1013 K3 Family [FLEXFLOW-3100T MGMT Function] 1105 CONFLUX-2200P NVMe Controller [Virtual Function] - 1203 K2-Pro Family [FLEXFLOW-2200T RoCEv2 Network Controller] # Network Accelerating Card 2018 DPU Card # Network Accelerating Card 2020 DPU + 3011 K3 Family [FLEXFLOW-3100R] + 1f47 000a FLEXFLOW-3100R 1*100GE Ethernet Adapter + 1f47 000b FLEXFLOW-3100R 2*100GE Ethernet Adapter + 1f47 000c FLEXFLOW-3100R 4*100GE Ethernet Adapter + 1f47 000d FLEXFLOW-3100R 8*100GE Ethernet Adapter + 1f47 000e FLEXFLOW-3100R 1*100GE Ethernet Adapter + 1f47 000f FLEXFLOW-3100R 2*100GE Ethernet Adapter + 1f47 0010 FLEXFLOW-3100R 4*100GE Ethernet Adapter + 1f47 0011 FLEXFLOW-3100R 8*100GE Ethernet Adapter + 3012 K3 Family [FLEXFLOW-3100R Virtual Function] + 3013 K3 Family [FLEXFLOW-3100R MGMT Function] 3101 FLEXFLOW-2100R Ethernet Controller 1f47 0001 Ethernet 10G 2P FLEXFLOW-2100R 1f47 0002 Ethernet 25G 2P FLEXFLOW-2100R @@ -29091,10 +29676,6 @@ 1f47 0006 Ethernet 25G 2P FLEXFLOW-2200R 1f47 0007 Ethernet 50G 2P FLEXFLOW-2200R 1f47 0008 Ethernet 100G 2P FLEXFLOW-2200R - 3301 K3 Family [FLEXFLOW-3100R] - 1f47 0001 FLEXFLOW-3100R 1*100GE Ethernet Adapter - 3302 K3 Family [FLEXFLOW-3100R Virtual Function] - 3303 K3 Family [CONFLUX-3100R MGMT Function] 4001 K2-Pro Family [CONFLUX-2200E] 1f47 0001 Ethernet 25G 2P CONFLUX-2200E 1f47 0002 Ethernet 40G 2P CONFLUX-2200E @@ -29107,7 +29688,13 @@ 4002 K2-Pro Family [CONFLUX-2200E Virtual Function] 4003 K2-Pro Family [CONFLUX-2200E MGMT Function] 4004 K2-Pro Family [CONFLUX-2200E DATA Offload Engine] - 4203 K2-Pro Family [CONFLUX-2200E RoCEv2 Network Controller] + 4011 K3 Family [CONFLUX-3100E] + 1f47 0001 CONFLUX-3100E 2*10GE Ethernet Adapter + 1f47 0002 CONFLUX-3100E 4*10GE Ethernet Adapter + 1f47 0003 CONFLUX-3100E 2*25GE Ethernet Adapter + 1f47 0004 CONFLUX-3100E 4*25GE Ethernet Adapter + 4012 K3 Family [CONFLUX-3100E Virtual Function] + 4013 K3 Family [CONFLUX-3100E MGMT Function] 5001 CONFLUX-2200P Ethernet Controller 1f47 0001 Ethernet 25G 2P CONFLUX-2200P 1f47 0003 Ethernet 100G 2P CONFLUX-2200P @@ -29156,7 +29743,7 @@ 1f49 NeuReality LTD 1f4b Axera Semiconductor Co., Ltd 1f52 MangoBoost Inc. - 1008 Mango GPUBoost - RDMA + 1008 Mango BoostX - RoCE AI 1020 Mango NetworkBoost - TCP 1022 Mango StorageBoost - NTI 1023 Mango StorageBoost - NTT @@ -29188,11 +29775,19 @@ 1f82 d-Matrix 0011 Corsair [DMX 1000 Series] 00f1 JetStream [DMX F1 Transparent NIC] +1f8c Exascend,INC. + 8008 SM8008 NVMe SSD [Px5 Series SSD] + 1f8c 0001 PD5 Series General + 1f8c 0002 Px5 Series General + 8366 SM8366 NVMe SSD [PD5 Series SSD] + 1f8c 0001 PD5 Series General + 1f8c 0002 Px5 Series General 1f90 Quside Technologies 7024 QRNG PCIe Device 7025 QRNG PCIe Device 1f99 Shenzhen Techwinsemi Technology Co., Ltd. 1202 TE3420 series / Patriot P320 M.2 NVMe SSD (DRAM-less) + 1602 PCIe Gen4 x4 M.2 2280 (DRAM-less) 1608 PCIe Gen4 x4 M.2 2280 1f88 TE3420 PCIe Gen3 x4 M.2 2280 2269 XE4403 Series NVMe PCIe Gen4x4 SSD @@ -29269,7 +29864,7 @@ 0004 D20 Pro 0005 D20 Plus N 0006 D20 Pro N - 0101 D2 + 0101 T1 # nee Tumsan Oy 1fc0 Ascom (Finland) Oy 0300 E2200 Dual E1/Rawpipe Card @@ -29419,6 +30014,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 @@ -29434,11 +30033,20 @@ 1ff2 0b23 3260-16i Tri-mode RAID Adapter 1ff2 0b24 3260-8i Tri-mode RAID Adapter 1ff4 DEEPX Co., Ltd. - 0000 DX_M1 - 0001 DX_M1A - 1000 DX_H1 - 2000 DX_VNPU_M1 - 2001 DX_VNPU +# DX-M1, DX-M1 M.2 Module + 0100 M1 [Series] +# DX-H1 Card with M1 + 0101 M1 [H1] +# DX-H1 V-NPU Card with M1 + 0102 M1 [H1 V-NPU] +# DX-M1M, DX-M1M M.2 Module + 0110 M1M [Series] +# DX-H1M Card with M1M + 0111 M1M [H1M] +# DX-H1M V-NPU Card with M1M + 0112 M1M [H1M V-NPU] +# DX-H1/H1M V-NPU Card Video Processing Unit + 2001 VPU [H1/H1M V-NPU] 1ff8 Beijing Gengtu Technology Co.Ltd 2000 GT6910 2010 GT6908 @@ -29502,6 +30110,36 @@ 2036 1005 NP36000 Virtual PCIe C2PMem 1.x Endpoint 2036 1006 NP36000 Virtual PCIe Pktgen 1.x Endpoint 203b XTX Markets Technologies Ltd. +# Vendor ID 2042 assigned by PCI-SIG +2042 Xi'an UniIC Semiconductors Co., Ltd +# PCIe NVMe SSD + db00 UWSC256DDQYACC-N +# PCIe NVMe SSD + db01 UWSC512DDQLBCC-N +# PCIe NVMe SSD + db02 UWSC512DDQLACC-N +# PCIe NVMe SSD + db03 UWSC1T0DDQLACC-N +# PCIe NVMe SSD + dc00 UWSD512DDQYACA-N +# PCIe NVMe SSD + dc01 UWSD1T0DDQYACA-N +# PCIe NVMe SSD + dc02 UWSD2T0DDQYACA-N +# PCIe NVMe SSD + dc03 UWSD1T0DDTYACA-N +# PCIe NVMe SSD + dc04 UWSD2T0DDTYACA-N +# PCIe NVMe SSD + dc05 UWSD4T0DDTYACA-N +# PCIe NVMe SSD + dc06 UWSD512DDQYACB-N +# PCIe NVMe SSD + dc07 UWSD1T0DDQYACB-N +# PCIe NVMe SSD + dc08 UWSD512DDTMACA-N +# PCIe NVMe SSD + dc09 UWSD1T0DDTMACA-N 2044 Shenzhen Jiahua Zhongli Technology Co., LTD. 8200 CeaCent CS211X 12G SAS RAID controller 2044 2110 CeaCent CS2110-8i @@ -29635,15 +30273,19 @@ 209b BitIntelligence Technology 1000 TCU Family - TCU-1 1001 TCU Family - TCU-1 Virtual Function + 1002 TCU Family - TCU-1 PCIe Switch 209f Mobilint, Inc. -20a1 Etched AI, Inc. +20a1 Etched, Inc. 0001 Sohu 20a6 XCENA, Inc. 20a7 EEVengers Inc. 20a8 Rayson HI-TECH(SZ) Co., Ltd. + 1202 RS512GSSD510 PCIe 3 NVMe SSD (DRAM-less) 20a9 LDA Technologies Ltd. 1008 NEOTAPX FPGA Accelerator Card 1104 NEOTAPX FPGA Timing Synchronization Card + 1200 MUX Ultimate FPGA Accelerator Card + 1201 MUX Ultimate FPGA Accelerator Card 20b5 Shanghai StarFive Technology Co., Ltd. 20ba Hangzhou Hikstorage Technology Co., Ltd. 1202 NAND memory controller @@ -29680,20 +30322,38 @@ 20d8 4201 TUNAN-7V 1x400GbE Controller 20dc Sharetronic Data Technology Co., Ltd. 1202 M.2 2280 PCIe Gen3 x 4 Series. + 1202 1256 M.2 2280 256GB 1202+N38A + 5000 E5000 U.2 15mm 3.84TB NVMe SSD + 5101 E5000 U.2 15mm 7.68TB NVMe SSD 20e1 CECloud Computing Technology Co., Ltd. 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. +2100 Shenzhen Kimviking Semiconductor 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. +# HXQ + 2401 PCIe4.0 to USB3.2 Gen2 Host Controller +2114 EigenQ, Inc. + 0007 QMA Board M.2 Gen2 + 000a QMA Board PCIe Gen2 2116 ZyDAS Technology Corp. +# Add Vendor id 0x2123 to pci.ids +2123 Shanghai Warpdrive Technology Co., Ltd 21b4 Hunan Goke Microelectronics Co., Ltd 21c3 21st Century Computer Corp. 22b8 Flex-Logix Technologies @@ -29714,27 +30374,26 @@ 5008 A1000/U-SNS8154P3 x2 NVMe SSD [E8] 500a DC1000B NVMe SSD [E12DC] 500b DC1000M NVMe SSD [SM2270] - 500c OM8PCP Design-In PCIe 3 NVMe SSD (DRAM-less) - 500d OM3PDP3 NVMe SSD + 500c OM8PCP3 PCIe 3 NVMe SSD (DRAM-less) + 500d OM3PDP3 PCIe 3 NVMe SSD (DRAM-less) 500e NV1 NVMe SSD [E13T] (DRAM-less) 500f NV1 NVMe SSD [SM2263XT] (DRAM-less) 5010 OM8SBP NVMe PCIe SSD (DRAM-less) 5012 DC1500M NVMe SSD [SM2270] 5013 KC3000/FURY Renegade NVMe SSD [E18] - 5014 OM8SEP4 Design-In PCIe 4 NVMe SSD (TLC) (DRAM-less) - 5016 OM3PGP4 NVMe SSD (DRAM-less) + 5014 OM8SEP4 PCIe 4 NVMe SSD (TLC) (DRAM-less) + 5016 OM3PGP4 PCIe 4 NVMe SSD (DRAM-less) 5017 NV2 NVMe SSD [SM2267XT] (DRAM-less) 5018 OM8SFP4 PCIe 4 NVMe SSD (DRAM-less) 5019 NV2 NVMe SSD [E21T] (DRAM-less) -# 128GB - 501a OM8PGP4 Design-In PCIe 4 NVMe SSD (TLC) (DRAM-less) + 501a OM8PGP4 PCIe 4 NVMe SSD (TLC) (DRAM-less) 501b OM8PGP4 NVMe PCIe SSD (DRAM-less) 501c NV2 NVMe SSD [E19T] (DRAM-less) 501d NV2 NVMe SSD [TC2200] (DRAM-less) 501e OM3PGP4 NVMe SSD (DRAM-less) 501f FURY Renegade NVMe SSD [E18] (Heatsink) - 5021 OM8SEP4 Design-In PCIe 4 NVMe SSD (QLC) (DRAM-less) - 5022 OM8PGP4 Design-In PCIe 4 NVMe SSD (QLC) (DRAM-less) + 5021 OM8SEP4 PCIe 4 NVMe SSD (QLC) (DRAM-less) + 5022 OM8PGP4 PCIe 4 NVMe SSD (QLC) (DRAM-less) 5023 NV2 NVMe SSD [SM2269XT] (DRAM-less) 5024 DC2000B NVMe SSD [E18DC] 5025 NV3 NVMe SSD [TC2201] (DRAM-less) @@ -29745,7 +30404,9 @@ 502b NV3 NVMe SSD [E29T] (DRAM-less) 502c DC3000ME NVMe SSD [SC5] 502d OM8TAP4 PCIe 4 NVMe SSD (QLC) (DRAM-less) + 502f OM3SGP4 PCIe 4 NVMe SSD (TLC) 5030 NV3 2230 NVMe SSD [SM2268XT2] (DRAM-less) + 5034 NV3 NVMe SSD [E33T] (DRAM-less) 270b Xantel Corporation 270f Chaintech Computer Co. Ltd 2711 AVID Technology Inc. @@ -29940,7 +30601,16 @@ 434e 0003 CN5000 SuperNIC, Single Port, QSFP, x16 PCIe Gen 5, II 434e 0004 CN5000 SuperNIC, Dual Port, QSFP-DD, x16 PCIe Gen 5, II 0002 CN6000 HFI Silicon, Dual Port, BGA [discrete] + 434e 0001 CN6000 SuperNIC, Single Port, QSFP-DD, x16 PCIe Gen 6 8001 CN5000 Switch Silicon, 48 Port, BGA +# Add CN5000 Switch + 434e 0101 CN5000 Switch +# Add CN5000 Director Class Switch Spine + 434e 0103 CN5000 Director Class Switch Spine +# Add CN5000 Director Class Switch Leaf + 434e 0104 CN5000 Director Class Switch Leaf +# Add CN6000 Switch + 434e 0106 CN6000 Switch 4444 Internext Compression Inc 0016 iTVC16 (CX23416) Video Decoder 0070 0003 WinTV PVR 250 @@ -30171,6 +30841,13 @@ 4c53 3002 PLUSTEST-MM card (PMC) 4c54 Lisuan Technology Co., Ltd. 5000 LISUAN 7G100 Series Graphics +# LISUAN 7G100 Series Graphics + 5001 LISUAN 7G100 Series Graphics + 5002 LISUAN 7G100 Series Graphics + 5003 LISUAN 7G100 Series Graphics + 5004 LISUAN 7G100 Series Graphics + 5005 LISUAN 7G100 Series Graphics + 5006 LISUAN 7G100 Series Graphics 4ca1 Seanix Technology Inc 4d51 MediaQ Inc. 0200 MQ-200 @@ -30459,7 +31136,7 @@ 1022 4 photo couple 4 relay Card 1025 16 photo couple 16 relay Card 4000 WatchDog Card -6688 Zycoo Co., Ltd +6688 GUANGZHOU MAXSUN INFORMATION TECHNOLOGY CO., LTD. 1200 CooVox TDM Analog Module 1400 CooVOX TDM GSM Module 1600 CooVOX TDM E1/T1 Module @@ -30659,7 +31336,7 @@ 10cf 16bf LIFEBOOK E752 0155 Xeon E3-1200 v2/3rd Gen Core processor PCI Express Root Port 8086 2010 Server Board S1200BTS - 0156 3rd Gen Core processor Graphics Controller + 0156 Ivy Bridge mobile GT1 [HD Graphics] 1043 108d VivoBook X202EV 0158 Xeon E3-1200 v2/Ivy Bridge DRAM Controller 1043 844d P8 series motherboard @@ -30673,7 +31350,7 @@ 0162 IvyBridge GT2 [HD Graphics 4000] 1043 84ca P8 series motherboard 1849 0162 Motherboard - 0166 3rd Gen Core processor Graphics Controller + 0166 Ivy Bridge mobile GT2 [HD Graphics 4000] 1043 1517 Zenbook Prime UX31A 1043 2103 N56VZ 10cf 16c1 LIFEBOOK E752 @@ -30682,40 +31359,65 @@ 0172 Xeon E3-1200 v2/3rd Gen Core processor Graphics Controller 0176 3rd Gen Core processor Graphics Controller 0201 Arctic Sound - 0284 Comet Lake PCH-LP LPC Premium Controller/eSPI Controller + 0284 400 Series Chipset Family On-Package PCH-LP Prem-U LPC/eSPI Controller 1028 09be Latitude 7410 - 02a3 Comet Lake PCH-LP SMBus Host Controller + 0285 400 Series Chipset Family On-Package PCH-LP Mainstream/Base U LPC/eSPI Controller + 02a0 400 Series Chipset Family On-Package P2SB + 02a1 400 Series Chipset Family On-Package PMC + 02a3 400 Series Chipset Family On-Package SMBus 1028 09be Latitude 7410 - 02a4 Comet Lake SPI (flash) Controller + 02a4 400 Series Chipset Family On-Package SPI (flash) Controller 1028 09be Latitude 7410 - 02a6 Comet Lake North Peak - 02b0 Comet Lake PCI Express Root Port #9 - 02b1 Comet Lake PCI Express Root Port #10 - 02b3 Comet Lake PCI Express Root Port #12 - 02b4 Comet Lake PCI Express Root Port #13 - 02b5 Comet Lake PCI Express Root Port #14 - 02b8 Comet Lake PCI Express Root Port #1 - 02bc Comet Lake PCI Express Root Port #5 - 02bf Comet Lake PCI Express Root Port #8 - 02c5 Comet Lake Serial IO I2C Host Controller + 02a6 400 Series Chipset Family On-Package Trace Hub + 02a8 400 Series Chipset Family On-Package UART #0 + 02a9 400 Series Chipset Family On-Package UART #1 + 02aa 400 Series Chipset Family On-Package SPI #0 + 02ab 400 Series Chipset Family On-Package SPI #1 + 02b0 400 Series Chipset Family On-Package PCIe Root Port #9 + 02b1 400 Series Chipset Family On-Package PCIe Root Port #10 + 02b2 400 Series Chipset Family On-Package PCIe Root Port #11 + 02b3 400 Series Chipset Family On-Package PCIe Root Port #12 + 02b4 400 Series Chipset Family On-Package PCIe Root Port #13 + 02b5 400 Series Chipset Family On-Package PCIe Root Port #14 + 02b6 400 Series Chipset Family On-Package PCIe Root Port #15 + 02b7 400 Series Chipset Family On-Package PCIe Root Port #16 + 02b8 400 Series Chipset Family On-Package PCIe Root Port #1 + 02b9 400 Series Chipset Family On-Package PCIe Root Port #2 + 02ba 400 Series Chipset Family On-Package PCIe Root Port #3 + 02bb 400 Series Chipset Family On-Package PCIe Root Port #4 + 02bc 400 Series Chipset Family On-Package PCIe Root Port #5 + 02bd 400 Series Chipset Family On-Package PCIe Root Port #6 + 02be 400 Series Chipset Family On-Package PCIe Root Port #7 + 02bf 400 Series Chipset Family On-Package PCIe Root Port #8 + 02c4 400 Series Chipset Family On-Package eMMC + 02c5 400 Series Chipset Family On-Package I2C #4 1028 09be Latitude 7410 - 02c8 Comet Lake PCH-LP cAVS + 02c6 400 Series Chipset Family On-Package I2C #5 + 02c7 400 Series Chipset Family On-Package UART #2 + 02c8 400 Series Chipset Family On-Package HD Audio 1028 09be Latitude 7410 - 02d3 Comet Lake SATA AHCI Controller - 02d7 Comet Lake RAID Controller - 02e0 Comet Lake Management Engine Interface + 02d3 400 Series Chipset Family On-Package SATA Controller (AHCI) + 02d5 400 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) no premium + 02d7 400 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) premium + 02e0 400 Series Chipset Family On-Package MEI #1 1028 09be Latitude 7410 - 02e3 Comet Lake AMT SOL Redirection - 02e8 Serial IO I2C Host Controller + 02e1 400 Series Chipset Family On-Package MEI #2 + 02e2 400 Series Chipset Family On-Package IDE Redirection (IDER-R) + 02e3 400 Series Chipset Family On-Package Keyboard and Text (KT) Redirection + 02e4 400 Series Chipset Family On-Package MEI #3 + 02e5 400 Series Chipset Family On-Package MEI #4 + 02e8 400 Series Chipset Family On-Package I2C #0 1028 09be Latitude 7410 - 02e9 Comet Lake Serial IO I2C Host Controller + 02e9 400 Series Chipset Family On-Package I2C #1 1028 09be Latitude 7410 - 02ea Comet Lake PCH-LP LPSS: I2C Controller #2 - 02ed Comet Lake PCH-LP USB 3.1 xHCI Host Controller + 02ea 400 Series Chipset Family On-Package I2C #2 + 02eb 400 Series Chipset Family On-Package I2C #3 + 02ed 400 Series Chipset Family On-Package USB 3.2 Gen 2x1 (10 Gbs) xHCI Host Controller 1028 09be Latitude 7410 - 02ef Comet Lake PCH-LP Shared SRAM + 02ee 400 Series Chipset Family On-Package USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) + 02ef 400 Series Chipset Family On-Package Shared SRAM 1028 09be Latitude 7410 - 02f0 Comet Lake PCH-LP CNVi WiFi + 02f0 400 Series Chipset Family On-Package CNVi WiFi 8086 0034 Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9560 160MHz 2x2 [Jefferson Peak] 8086 0070 Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak] 8086 0074 Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak] @@ -30723,10 +31425,11 @@ 8086 0264 Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9461 80MHz 1x1 [Jefferson Peak] 8086 02a4 Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9462 80MHz 1x1 [Jefferson Peak] 8086 4070 Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak] - 02f5 Comet Lake PCH-LP SCS3 - 02f9 Comet Lake Thermal Subsytem + 02f5 400 Series Chipset Family On-Package SDXC + 02f9 400 Series Chipset Family On-Package Thermal Subsystem 1028 09be Latitude 7410 - 02fc Comet Lake Integrated Sensor Solution + 02fb 400 Series Chipset Family On-Package SPI #2 + 02fc 400 Series Chipset Family On-Package Integrated Sensor Hub 1028 09be Latitude 7410 0309 80303 I/O Processor PCI-to-PCI Bridge 030d 80312 I/O Companion Chip PCI-to-PCI Bridge @@ -30818,43 +31521,70 @@ 068d HM470 Chipset LPC/eSPI Controller 068e WM490 Chipset LPC/eSPI Controller 0697 W480 Chipset LPC/eSPI Controller - 06a3 Comet Lake PCH SMBus Controller - 06a4 Comet Lake PCH SPI Controller - 06a8 Comet Lake PCH Serial IO UART Host Controller #0 - 06a9 Comet Lake PCH Serial IO UART Host Controller #1 - 06aa Comet Lake PCH Serial IO SPI Controller #0 - 06ab Comet Lake PCH Serial IO SPI Controller #1 - 06ac Comet Lake PCI Express Root Port #21 - 06b0 Comet Lake PCI Express Root Port #9 - 06b8 Comet Lake PCIe Root Port #1 - 06ba Comet Lake PCI Express Root Port #1 - 06bb Comet Lake PCI Express Root Port #4 - 06bd Comet Lake PCIe Port #6 - 06be Comet Lake PCIe Root Port #7 - 06bf Comet Lake PCIe Port #8 - 06c0 Comet Lake PCI Express Root Port #17 - 06c8 Comet Lake PCH cAVS - 06d2 Comet Lake SATA AHCI Controller + 06a0 400 Series Chipset Family P2SB + 06a1 400 Series Chipset Family PMC + 06a3 400 Series Chipset Family SMBus + 06a4 400 Series Chipset Family SPI (flash) Controller + 06a6 400 Series Chipset Family Trace Hub + 06a8 400 Series Chipset Family UART #0 + 06a9 400 Series Chipset Family UART #1 + 06aa 400 Series Chipset Family GSPI #0 + 06ab 400 Series Chipset Family GSPI #1 + 06ac 400 Series Chipset Family PCIe Root Port #21 + 06ad 400 Series Chipset Family PCIe Root Port #22 + 06ae 400 Series Chipset Family PCIe Root Port #23 + 06af 400 Series Chipset Family PCIe Root Port #24 + 06b0 400 Series Chipset Family PCIe Root Port #9 + 06b1 400 Series Chipset Family PCIe Root Port #10 + 06b2 400 Series Chipset Family PCIe Root Port #11 + 06b3 400 Series Chipset Family PCIe Root Port #12 + 06b4 400 Series Chipset Family PCIe Root Port #13 + 06b5 400 Series Chipset Family PCIe Root Port #14 + 06b6 400 Series Chipset Family PCIe Root Port #15 + 06b7 400 Series Chipset Family PCIe Root Port #16 + 06b8 400 Series Chipset Family PCIe Root Port #1 + 06b9 400 Series Chipset Family PCIe Root Port #2 + 06ba 400 Series Chipset Family PCIe Root Port #3 + 06bb 400 Series Chipset Family PCIe Root Port #4 + 06bc 400 Series Chipset Family PCIe Root Port #5 + 06bd 400 Series Chipset Family PCIe Root Port #6 + 06be 400 Series Chipset Family PCIe Root Port #7 + 06bf 400 Series Chipset Family PCIe Root Port #8 + 06c0 400 Series Chipset Family PCIe Root Port #17 + 06c1 400 Series Chipset Family PCIe Root Port #18 + 06c2 400 Series Chipset Family PCIe Root Port #19 + 06c3 400 Series Chipset Family PCIe Root Port #20 + 06c7 400 Series Chipset Family UART #2 + 06c8 400 Series Chipset Family HD Audio + 06d2 400 Series Chipset Family SATA Controller (AHCI) (Desktop) + 06d3 400 Series Chipset Family SATA Controller (AHCI) (Mobile) + 06d5 400 Series Chipset Family SATA Controller (RAID 0/1/5/10) no premium (Mobile) 06d6 Comet Lake PCH-H RAID - 06d7 Comet Lake PCH-H RAID - 06e0 Comet Lake HECI Controller - 06e3 Comet Lake Keyboard and Text (KT) Redirection - 06e8 Comet Lake PCH Serial IO I2C Controller #0 - 06e9 Comet Lake PCH Serial IO I2C Controller #1 - 06ea Comet Lake PCH Serial IO I2C Controller #2 - 06eb Comet Lake PCH Serial IO I2C Controller #3 - 06ed Comet Lake USB 3.1 xHCI Host Controller - 06ef Comet Lake PCH Shared SRAM - 06f0 Comet Lake PCH CNVi WiFi + 06d7 400 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium (Mobile) + 06de 400 Series Chipset Family SATA Controller (AHCI) Optane Caching + 06e0 400 Series Chipset Family HECI #1 + 06e1 400 Series Chipset Family HECI #2 + 06e2 400 Series Chipset Family IDE Redirection (IDE-R) + 06e3 400 Series Chipset Family Keyboard and Text (KT) Redirection + 06e4 400 Series Chipset Family HECI #3 + 06e5 400 Series Chipset Family HECI #4 + 06e8 400 Series Chipset Family I2C #0 + 06e9 400 Series Chipset Family I2C #1 + 06ea 400 Series Chipset Family I2C #2 + 06eb 400 Series Chipset Family I2C #3 + 06ed 400 Series Chipset Family USB 3.2 Gen 2x1 (10 Gbs) xHCI Host Controller + 06ef 400 Series Chipset Family Shared SRAM + 06f0 400 Series Chipset Family CNVi Wi-Fi 1a56 1651 Dual Band Wi-Fi 6(802.11ax) Killer AX1650s 160MHz 2x2 [Cyclone Peak] 1a56 1652 Dual Band Wi-Fi 6(802.11ax) Killer AX1650i 160MHz 2x2 [Cyclone Peak] 8086 0034 Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9560 160MHz 2x2 [Jefferson Peak] 8086 0074 Dual Band Wi-Fi 6(802.11ax) AX201 160MHz 2x2 [Harrison Peak] 8086 02a4 Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9462 80MHz 1x1 [Jefferson Peak] 8086 42a4 Dual Band Wi-Fi 5(802.11ac) Wireless-AC 9462 80MHz 1x1 [Jefferson Peak] - 06f9 Comet Lake PCH Thermal Controller - 06fb Comet Lake PCH Serial IO SPI Controller #2 - 06fc Comet Lake PCH Integrated Sensor Solution + 06f5 400 Series Chipset Family SCS3 SDXC + 06f9 400 Series Chipset Family Thermal Subsystem + 06fb 400 Series Chipset Family GSPI #2 + 06fc 400 Series Chipset Family Integrated Sensor Hub 0700 CE Media Processor A/V Bridge 0701 CE Media Processor NAND Flash Controller 0703 CE Media Processor Media Control Unit 1 @@ -31122,12 +31852,12 @@ 0962 80960RM (i960RM) Bridge 0964 80960RP (i960RP) Microprocessor/Bridge 0975 Optane NVME SSD H10 with Solid State Storage [Teton Glacier] - 0998 Ice Lake IEH - 09a2 Ice Lake Memory Map/VT-d - 09a3 Ice Lake RAS - 09a4 Ice Lake Mesh 2 PCIe - 09a6 Ice Lake MSM - 09a7 Ice Lake PMON MSM + 0998 Xeon 6900 6700 6500 Series with P-Cores Processors IEH + 09a2 Xeon 6900 6700 6500 Series with P-Cores Processors VT-d + 09a3 Xeon 6900 6700 6500 Series with P-Cores Processors RAS + 09a4 Xeon 6900 6700 6500 Series with P-Cores Processors UFI + 09a6 Xeon 6900 6700 6500 Series with P-Cores Processors MSM + 09a7 Xeon 6900 6700 6500 Series with P-Cores Processors PMON MSM 09ab RST VMD Managed Controller 09ad Optane NVME SSD H20 with Solid State Storage [Pyramid Glacier] 09c4 PAC with Intel Arria 10 GX FPGA @@ -31205,7 +31935,7 @@ 1028 1fe8 Express Flash NVMe 2.0TB HHHL AIC (P4600) 1028 1fe9 Express Flash NVMe 4.0TB HHHL AIC (P4600) 0b00 Ice Lake CBDMA [QuickData Technology] - 0b23 Xeon Root Event Collector + 0b23 Xeon 6900 6700 6500 Series with P-Cores Processors IEH 0b25 Data Streaming Accelerator (DSA) 0b26 Thunderbolt 4 Bridge [Goshen Ridge 2020] 0b27 Thunderbolt 4 USB Controller [Goshen Ridge 2020] @@ -31339,17 +32069,26 @@ 0d22 Crystal Well Integrated Iris Pro Graphics 5200 0d26 Crystal Well Integrated Graphics Controller 0d36 Crystal Well Integrated Graphics Controller - 0d4c Ethernet Connection (11) I219-LM - 0d4d Ethernet Connection (11) I219-V + 0d4c 400 Series Chipset Family GbE Controller (Corporate/vPro) + 0d4d 400 Series Chipset Family GbE Controller (Consumer) 8086 0d4d Ethernet Connection (11) I219-V - 0d4e Ethernet Connection (10) I219-LM - 0d4f Ethernet Connection (10) I219-V + 0d4e 400 Series Chipset Family On-Package GbE Controller (Corporate/vPro) + 0d4f 400 Series Chipset Family On-Package GbE Controller (Consumer) 0d53 Ethernet Connection (12) I219-LM 0d55 Ethernet Connection (12) I219-V 0d58 Ethernet Controller XXV710 Intel(R) FPGA Programmable Acceleration Card N3000 for Networking 8086 0000 Ethernet Controller XXV710 Intel(R) FPGA Programmable Acceleration Card N3000 for Networking 8086 0001 Ethernet Controller XXV710 Intel(R) FPGA Programmable Acceleration Card N3000 for Networking 0d9f Ethernet Controller I225-IT + 0db0 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #0 + 0db1 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #1 + 0db2 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #2 + 0db3 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #3 + 0db4 Xeon 6900 6700 6500 Series with P-Cores Processors NTB + 0db6 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #4 + 0db7 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #5 + 0db8 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #6 + 0db9 Xeon 6900 6700 6500 Series with P-Cores Processors PCIe and CXL.io Root Port #7 0dc5 Ethernet Connection (23) I219-LM 1028 0c06 Precision 3580 0dc6 Ethernet Connection (23) I219-V @@ -32121,6 +32860,7 @@ 11a5 Merrifield Serial IO PWM Controller 11c3 Quark SoC X1000 PCIe Root Port 0 11c4 Quark SoC X1000 PCIe Root Port 1 + 11df Infrastructure Data Path Function 11eb Simics NVMe Controller 1200 IXP1200 Network Processor 172a 0000 AEP SSL Accelerator @@ -32836,6 +33576,7 @@ 8086 000c Ethernet Network Adapter XXV710-DA2 for OCP 3.0 8086 000d Ethernet 25G 2P XXV710 OCP 8086 4001 Ethernet Network Adapter XXV710-2 + 1590 Ethernet Connection E810-C 1591 Ethernet Controller E810-C for backplane 8086 bcce Ethernet Controller E810-C for Intel(R) Open FPGA Stack 1592 Ethernet Controller E810-C for QSFP @@ -32884,6 +33625,9 @@ 8086 4010 Ethernet Network Adapter E810-XXV-4 8086 4013 Ethernet Network Adapter E810-XXV-4 for OCP 3.0 8086 401c Ethernet Network Adapter E810-XXV-4 for OCP 3.0 + 1594 Ethernet Controller E810-C/X557-AT 10GBASE-T + 1595 Ethernet Controller E810-C 1GbE + 1598 Ethernet Connection E810-XXV 1599 Ethernet Controller E810-XXV for backplane 8086 0001 Ethernet 25G 2P E810-XXV-k Mezz 159a Ethernet Controller E810-XXV for QSFP @@ -32907,6 +33651,8 @@ 8086 4002 Ethernet Network Adapter E810-XXV-2 for OCP 3.0 8086 4003 Ethernet Network Adapter E810-XXV-2 8086 4015 Ethernet Network Adapter E810-XXV-2 for OCP 3.0 + 159c Ethernet Controller E810-XXV/X557-AT 10GBASE-T + 159d Ethernet Controller E810-XXV 1GbE 15a0 Ethernet Connection (2) I218-LM 15a1 Ethernet Connection (2) I218-V 15a2 Ethernet Connection (3) I218-LM @@ -32998,8 +33744,8 @@ 15f4 Ethernet Connection (15) I219-LM 15f5 Ethernet Connection (15) I219-V 15f6 I210 Gigabit Ethernet Connection - 15f9 Ethernet Connection (14) I219-LM - 15fa Ethernet Connection (14) I219-V + 15f9 500 Series Chipset Family GbE Controller (Corporate/vPro) + 15fa 500 Series Chipset Family GbE Controller (Consumer) 15fb Ethernet Connection (13) I219-LM 15fc Ethernet Connection (13) I219-V 15ff Ethernet Controller X710 for 10GBASE-T @@ -35110,6 +35856,7 @@ 8086 3905 NVMe Datacenter SSD [Optane] 15mm 2.5" U.2 (P4800X) 2710 Dynamic Load Balancer 2.0 (DLB) 2714 Dynamic Load Balancer 2.5 (DLB) + 2715 Dynamic Load Balancer (DLB) Virtual Function 2723 Wi-Fi 6 AX200 1a56 1654 Killer Wi-Fi 6 AX1650x (AX200NGW) 8086 0084 Wi-Fi 6 AX200NGW @@ -35534,25 +36281,25 @@ 1028 01da OptiPlex 745 1462 7235 P965 Neo MS-7235 mainboard 2821 82801HR/HO/HH (ICH8R/DO/DH) 6 port SATA Controller [AHCI mode] - 2822 SATA Controller [RAID mode] + 2822 SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Desktop RST) 1028 020d Inspiron 530 103c 2a6f Asus IPIBL-LB Motherboard 1043 8277 P5K PRO Motherboard: 82801IR [ICH9R] 1462 7345 MS-7345 Motherboard: Intel 82801I/IR [ICH9/ICH9R] - 2823 sSATA Controller [RAID Mode] + 2823 SSATA Controller (RAID 0/1/5/10) 2824 82801HB (ICH8) 4 port SATA Controller [AHCI mode] 1043 81ec P5B 2825 82801HR/HO/HH (ICH8R/DO/DH) 2 port SATA Controller [IDE mode] 1028 01da OptiPlex 745 1462 7235 P965 Neo MS-7235 mainboard - 2826 SATA Controller [RAID Mode] + 2826 SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Server/Desktop RST) 1d49 0100 Intel RSTe SATA Software RAID 1d49 0101 Intel RSTe SATA Software RAID 1d49 0102 Intel RSTe SATA Software RAID 1d49 0103 Intel RSTe SATA Software RAID 1d49 0104 Intel RSTe SATA Software RAID 1d49 0105 Intel RSTe SATA Software RAID - 2827 sSATA Controller [RAID Mode] + 2827 SSATA Controller (RAID 0/1/5/10) 2828 82801HM/HEM (ICH8M/ICH8M-E) SATA Controller [IDE mode] 1028 01f3 Inspiron 1420 103c 30c0 Compaq 6710b @@ -35571,7 +36318,7 @@ 17aa 20a7 ThinkPad T61/R61 17c0 4083 Medion WIM 2210 Notebook PC [MD96850] e4bf cc47 CCG-RUMBA - 282a 82801 Mobile SATA Controller [RAID mode] + 282a SATA Controller (RAID 0/1/5/10) In-box Compatible ID (Mobile RST) 1028 040b Latitude E6510 e4bf 50c1 PC1-GROOVE 282b C740 Series (Emmitsburg) Chipsets SATA2 Controller (RAID) Alternate ID @@ -35781,7 +36528,8 @@ 17c0 4083 Medion WIM 2210 Notebook PC [MD96850] e4bf cc47 CCG-RUMBA 2880 Ice Lake DDRIO Registers - 28c0 Volume Management Device NVMe RAID Controller +# also seen in Xeon 6900 6700 6500 Series with P-Cores Processors + 28c0 Volume Management Device (VMD) 1d49 011a Intel VROC (VMD NVMe RAID) for ThinkSystem V4 PL 2912 82801IH (ICH9DH) LPC Interface Controller 2914 82801IO (ICH9DO) LPC Interface Controller @@ -36704,10 +37452,17 @@ 31f0 Gemini Lake Host Bridge 3200 GD31244 PCI-X SATA HBA 1775 c200 C2K onboard SATA host bus adapter - 3245 Xeon UPI Mesh Stop M2UPI Registers - 324a Xeon IMC0 Mesh to Mem Registers - 324c Xeon Unicast Group1 CHA Registers - 324d Xeon Unicast Group0 CHA Registers + 3240 Xeon 6900 6700 6500 Series with P-Cores Processors UPI Misc + 3241 Xeon 6900 6700 6500 Series with P-Cores Processors UPI link/Phy0 + 3242 Xeon 6900 6700 6500 Series with P-Cores Processors UPI Phy0 + 3245 Xeon 6900 6700 6500 Series with P-Cores Processors UPI + 324a Xeon 6900 6700 6500 Series with P-Cores Processors IMC + 324c Xeon 6900 6700 6500 Series with P-Cores Processors CHA Unicast Group 1 + 324d Xeon 6900 6700 6500 Series with P-Cores Processors CHA Unicast Group 0 + 3250 Xeon 6900 6700 6500 Series with P-Cores Processors Ubox Event Control + 3251 Xeon 6900 6700 6500 Series with P-Cores Processors Ubox Register Access Control Unit + 3252 Xeon 6900 6700 6500 Series with P-Cores Processors Ubox Decode + 3256 Xeon 6900 6700 6500 Series with P-Cores Processors Trace Hub 3258 Power Control Unit (PCU) CR0 3259 Power Control Unit (PCU) CR1 325a Power Control Unit (PCU) CR2 @@ -36800,13 +37555,13 @@ 344b Ice Lake PCU Registers 344c Ice Lake CHA Registers 344d Ice Lake CHA Registers - 344f Ice Lake CHA Registers + 344f Xeon 6900 6700 6500 Series with P-Cores Processors CHA All 0 3450 Ice Lake Ubox Registers 3451 Ice Lake Ubox Registers 3452 Ice Lake Ubox Registers 3455 Ice Lake Ubox Registers 3456 Ice Lake NorthPeak - 3457 Ice Lake CHA Registers + 3457 Xeon 6900 6700 6500 Series with P-Cores Processors CHA All 1 3458 Ice Lake PCU Registers 3459 Ice Lake PCU Registers 345a Ice Lake PCU Registers @@ -37052,15 +37807,18 @@ 372b Xeon C5500/C3500 Core 372c Xeon C5500/C3500 Reserved 373f Xeon C5500/C3500 IOxAPIC - 37c0 C62x chipset series PCIe x16/x8 Upstream Port - 37c2 C62x chipset series PCIe Virtual Switch Port 0 - 37c3 C62x chipset series PCIe Virtual Switch Port 1 - 37c4 C62x chipset series PCIe Virtual Switch Port 2 - 37c5 C62x chipset series PCIe Virtual Switch Port 3 - 37c8 C62x Chipset series QuickAssist Technology Physical Function 0~2 + 37b1 C620 Series Chipset Family Thermal Sensor + 37c0 C620 Series Chipset Family PCIe Uplink (x16) + 37c1 C620 Series Chipset Family PCIe Uplink (x8) + 37c2 C620 Series Chipset Family Virtual Switch Port 0 + 37c3 C620 Series Chipset Family Virtual Switch Port 1 + 37c4 C620 Series Chipset Family Virtual Switch Port 2 + 37c5 C620 Series Chipset Family Virtual Switch Port 3 + 37c7 C620 Series Chipset Family Virtual Switch Port 5 + 37c8 C620 Series Chipset Family QAT 8086 0001 QuickAssist Adapter 8960 8086 0002 QuickAssist Adapter 8970 - 37c9 C62x Chipset QuickAssist Technology Virtual Function + 37c9 C620 Series Chipset Family QAT Virtual Function 37cc Ethernet Connection X722 37cd Ethernet Virtual Function 700 Series 37ce Ethernet Connection X722 for 10GbE backplane @@ -37499,6 +38257,7 @@ 3e98 CoffeeLake-S GT2 [UHD Graphics 630] 3e9a Coffee Lake-S GT2 [UHD Graphics P630] 3e9b CoffeeLake-H GT2 [UHD Graphics 630] + 106b 019c MacBookPro16,1 (16", 2019) 3e9c Coffee Lake-S GT1 [UHD Graphics 610] 3ea0 WhiskeyLake-U GT2 [UHD Graphics 620] 1028 089e Inspiron 5482 @@ -37644,44 +38403,101 @@ 8086 1216 WiMAX/WiFi Link 5150 ABG 8086 1311 WiMAX/WiFi Link 5150 AGN 8086 1316 WiMAX/WiFi Link 5150 ABG - 4384 Q570 LPC/eSPI Controller - 4385 Z590 LPC/eSPI Controller - 4386 H570 LPC/eSPI Controller - 4387 B560 LPC/eSPI Controller - 4388 H510 LPC/eSPI Controller - 4389 WM590 LPC/eSPI Controller - 438a QM580 LPC/eSPI Controller - 438b HM570 LPC/eSPI Controller - 438c C252 LPC/eSPI Controller - 438d C256 LPC/eSPI Controller + 4384 Q570 Chipset eSPI Controller + 4385 Z590 Chipset eSPI Controller + 4386 H570 Chipset eSPI Controller + 4387 B560 Chipset eSPI Controller + 4388 H510 Chipset eSPI Controller + 4389 WM590 Chipset eSPI Controller + 438a QM580 Chipset eSPI Controller + 438b HM570 Chipset eSPI Controller + 438c C252 Chipset eSPI Controller + 438d C256 Chipset eSPI Controller 438e H310D LPC/eSPI Controller - 438f W580 LPC/eSPI Controller + 438f W580 Chipset eSPI Controller 4390 RM590E LPC/eSPI Controller 4391 R580E LPC/eSPI Controller - 43a3 Tiger Lake-H SMBus Controller - 43a4 Tiger Lake-H SPI Controller - 43b0 Tiger Lake-H PCI Express Root Port #9 - 43b8 Tiger Lake-H PCIe Root Port #1 - 43ba Tiger Lake-H PCIe Root Port #3 - 43bb Tiger Lake-H PCIe Root Port #4 - 43bc Tiger Lake-H PCI Express Root Port #5 - 43be 11th Gen Core Processor PCIe Root Port #7 - 43c0 Tiger Lake-H PCIe Root Port #17 - 43c7 Tiger Lake-H PCIe Root Port #24 - 43c8 Tiger Lake-H HD Audio Controller - 43d3 Tiger Lake SATA AHCI Controller - 43e0 Tiger Lake-H Management Engine Interface - 43e3 Tiger Lake AMT SOL Redirection - 43e8 Tiger Lake-H Serial IO I2C Controller #0 - 43e9 Tiger Lake-H Serial IO I2C Controller #1 - 43ed Tiger Lake-H USB 3.2 Gen 2x1 xHCI Host Controller - 43ef Tiger Lake-H Shared SRAM - 43f0 Tiger Lake PCH CNVi WiFi + 43a0 500 Series Chipset Family P2SB + 43a1 500 Series Chipset Family PMC + 43a3 500 Series Chipset Family SMBus + 43a4 500 Series Chipset Family SPI (flash) Controller + 43a6 500 Series Chipset Family Trace Hub + 43a7 500 Series Chipset Family UART #2 + 43a8 500 Series Chipset Family UART #0 + 43a9 500 Series Chipset Family UART #1 + 43aa 500 Series Chipset Family GSPI #0 + 43ab 500 Series Chipset Family GSPI #1 + 43ad 500 Series Chipset Family I2C #4 + 43ae 500 Series Chipset Family I2C #5 + 43b0 500 Series Chipset Family PCIe Root Port #9 + 43b1 500 Series Chipset Family PCIe Root Port #10 + 43b2 500 Series Chipset Family PCIe Root Port #11 + 43b3 500 Series Chipset Family PCIe Root Port #12 + 43b4 500 Series Chipset Family PCIe Root Port #13 + 43b5 500 Series Chipset Family PCIe Root Port #14 + 43b6 500 Series Chipset Family PCIe Root Port #15 + 43b7 500 Series Chipset Family PCIe Root Port #16 + 43b8 500 Series Chipset Family PCIe Root Port #1 + 43b9 500 Series Chipset Family PCIe Root Port #2 + 43ba 500 Series Chipset Family PCIe Root Port #3 + 43bb 500 Series Chipset Family PCIe Root Port #4 + 43bc 500 Series Chipset Family PCIe Root Port #5 + 43bd 500 Series Chipset Family PCIe Root Port #6 + 43be 500 Series Chipset Family PCIe Root Port #7 + 43bf 500 Series Chipset Family PCIe Root Port #8 + 43c0 500 Series Chipset Family PCIe Root Port #17 + 43c1 500 Series Chipset Family PCIe Root Port #18 + 43c2 500 Series Chipset Family PCIe Root Port #19 + 43c3 500 Series Chipset Family PCIe Root Port #20 + 43c4 500 Series Chipset Family PCIe Root Port #21 + 43c5 500 Series Chipset Family PCIe Root Port #22 + 43c6 500 Series Chipset Family PCIe Root Port #23 + 43c7 500 Series Chipset Family PCIe Root Port #24 + 43c8 500 Series Chipset Family HD Audio + 43c9 500 Series Chipset Family HD Audio + 43ca 500 Series Chipset Family HD Audio + 43cb 500 Series Chipset Family HD Audio + 43cc 500 Series Chipset Family HD Audio + 43cd 500 Series Chipset Family HD Audio + 43ce 500 Series Chipset Family HD Audio + 43cf 500 Series Chipset Family HD Audio + 43d0 500 Series Chipset Family Touch Host Controller (THC) #0 + 43d1 500 Series Chipset Family Touch Host Controller (THC) #1 + 43d2 500 Series Chipset Family SATA Controller (AHCI) (Server/Desktop) + 43d3 500 Series Chipset Family SATA Controller (AHCI) (Mobile) + 43d4 500 Series Chipset Family SATA Controller (RAID 0/1/5/10) no premium (Desktop) + 43d5 500 Series Chipset Family SATA Controller (RAID 0/1/5/10) no premium (Mobile) + 43d6 500 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium (Server/Desktop) + 43d7 500 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium (Mobile) + 43d8 500 Series Chipset Family I2C #6 + 43da 500 Series Chipset Family UART #3 + 43e0 500 Series Chipset Family CSME HECI #1 + 43e1 500 Series Chipset Family CSME HECI #2 + 43e2 500 Series Chipset Family CSME IDE Redirection (IDE-R) + 43e3 500 Series Chipset Family CSME Keyboard and Text (KT) Redirection + 43e4 500 Series Chipset Family CSME HECI #3 + 43e5 500 Series Chipset Family CSME HECI #4 + 43e8 500 Series Chipset Family I2C #0 + 43e9 500 Series Chipset Family I2C #1 + 43ea 500 Series Chipset Family I2C #2 + 43eb 500 Series Chipset Family I2C #3 + 43ed 500 Series Chipset Family USB 3.2 Gen 2x2 (20 Gbs) xHCI Host Controller + 43ee 500 Series Chipset Family USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) + 43ef 500 Series Chipset Family Shared SRAM + 43f0 500 Series Chipset Family CNVi Wi-Fi 8086 0034 Wireless-AC 9560 8086 0074 Wi-Fi 6 AX201 160MHz 8086 0264 Wireless-AC 9461 8086 02a4 Wireless-AC 9462 - 43fc Tiger Lake-H Integrated Sensor Hub + 43f1 500 Series Chipset Family CNVi Wi-Fi + 43f2 500 Series Chipset Family CNVi Wi-Fi + 43f3 500 Series Chipset Family CNVi Wi-Fi + 43f5 500 Series Chipset Family CNVi Bluetooth + 43f6 500 Series Chipset Family CNVi Bluetooth + 43f7 500 Series Chipset Family CNVi Bluetooth + 43fb 500 Series Chipset Family GSPI #2 + 43fc 500 Series Chipset Family Integrated Sensor Hub + 43fd 500 Series Chipset Family GSPI #3 444e Turbo Memory Controller 4511 Elkhart Lake Gaussian and Neural Accelerator 4538 Elkhart Lake PCI-e Root Complex @@ -38020,8 +38836,8 @@ 5503 Ethernet Controller I226-LMvP 550a Ethernet Connection (18) I219-LM 550b Ethernet Connection (18) I219-LM - 550c Ethernet Connection (19) I219-LM - 550d Ethernet Connection (19) I219-V + 550c 800 Series Chipset Family GbE Controller (Corporate/vPro) + 550d 800 Series Chipset Family GbE Controller (Consumer) 550e Ethernet Connection (20) I219-LM 550f Ethernet Connection (20) I219-V 5510 Ethernet Connection (21) I219-LM @@ -38070,9 +38886,12 @@ 5785 JHL9540 Thunderbolt 4 USB Controller [Barlow Ridge Host 40G 2023] 5786 JHL9480 Thunderbolt 5 80/120G Bridge [Barlow Ridge Hub 80G 2023] 5787 JHL9480 Thunderbolt 5 80/120G USB Controller [Barlow Ridge Hub 80G 2023] - 5794 Granite Rapids SPI Controller - 5795 Granite Rapids Chipset LPC Controller - 5796 Granite Rapids SMBus Controller + 5792 Xeon 6900 6700 6500 Series with P-Cores Processors ILMI + 5793 Xeon 6900 6700 6500 Series with P-Cores Processors ACPI + 5794 Xeon 6900 6700 6500 Series with P-Cores Processors SPI + 5795 Xeon 6900 6700 6500 Series with P-Cores Processors eSPI + 5796 Xeon 6900 6700 6500 Series with P-Cores Processors SMBus + 5797 Xeon 6900 6700 6500 Series with P-Cores Processors UART 579c Ethernet Connection E825-C for backplane 579d Ethernet Connection E825-C for QSFP 579e Ethernet Connection E825-C for SFP @@ -38081,15 +38900,16 @@ 57a1 Ethernet Connection (24) I219-V 57a4 JHL9440 Thunderbolt 4 Bridge [Barlow Ridge Hub 40G 2023] 57a5 JHL9440 Thunderbolt 4 USB Controller [Barlow Ridge Hub 40G 2023] - 57ad E610 Virtual Function + 57ac Ethernet Controller E610 + 57ad Ethernet Controller E610 Virtual Function 57ae Ethernet Controller E610 Backplane 57af Ethernet Controller E610 SFP - 57b0 Ethernet Controller E610 10GBASE T + 57b0 Ethernet Controller E610-XT/E610-XT2 10GBASE-T 8086 0001 Ethernet Network Adapter E610-XT4 8086 0002 Ethernet Network Adapter E610-XT2 8086 0003 Ethernet Network Adapter E610-XT4 for OCP 3.0 8086 0004 Ethernet Network Adapter E610-XT2 for OCP 3.0 - 57b1 Ethernet Controller E610 2.5GBASE T + 57b1 Ethernet Controller E610-AT2 1000BASE-T 8086 0000 Ethernet Converged Network Adapter E610 8086 0002 Ethernet Network Adapter E610-IT4 8086 0003 Ethernet Network Adapter E610-IT4 for OCP 3.0 @@ -38182,12 +39002,13 @@ 5ae8 Celeron N3350/Pentium N4200/Atom E3900 Series Low Pin Count Interface 5aee Celeron N3350/Pentium N4200/Atom E3900 Series HSUART Controller #4 5af0 Celeron N3350/Pentium N4200/Atom E3900 Series Host Bridge - 641d Lunar Lake-M Dynamic Tuning Technology + 6400 Core Ultra 200V Series Processors with 4 P-Cores 4 E-Cores Host Bridge + 641d Core Ultra 200V Series Processors Dynamic Tuning Technology (DTT) 6420 Lunar Lake [Intel Graphics] - 643e Lunar Lake NPU - 645d Lunar Lake IPU - 647d Lunar Lake-M Crashlog and Telemetry - 64a0 Lunar Lake [Intel Arc Graphics 130V / 140V] + 643e Core Ultra 200V Series Processors NPU + 645d Core Ultra 200V Series Processors IPU + 647d Core Ultra 200V Series Processors Crash Log and Telemetry + 64a0 Core Ultra 200V Series Processors Arc Graphics 130V/140V GPU 64b0 Lunar Lake [Intel Graphics] 65c0 5100 Chipset Memory Controller Hub 65e2 5100 Chipset PCI Express x4 Port 2 @@ -38210,6 +39031,10 @@ 65fa 5100 Chipset PCI Express x16 Port 4-7 65ff 5100 Chipset DMA Engine 674c CRI + 674d CRI + 674e CRI + 674f CRI + 6750 CRI 6e23 Nova Lake PCH-S SMbus Controller 6e24 Nova Lake PCH-S SPI Controller 6e28 Nova Lake PCH-S Serial IO UART Controller #0 @@ -38478,6 +39303,8 @@ 71a1 440GX - 82443GX AGP bridge 71a2 440GX - 82443GX Host bridge (AGP disabled) 4c53 1000 CC7/CR7/CP7/VC7/VP7/VR7 mainboard + 7202 Core Ultra 200H Series Processors eSPI Controller + 7203 Core Ultra 200V Series Processors eSPI Controller 7360 XMM7360 LTE Advanced Modem 7560 XMM7560 LTE Advanced Pro Modem 7600 82372FB PIIX5 ISA @@ -38485,37 +39312,55 @@ 7602 82372FB PIIX5 USB 7603 82372FB PIIX5 SMBus 7702 Arrow Lake-H eSPI Controller - 7722 Arrow Lake SMBus Controller - 7723 Arrow Lake SPI Controller - 7725 Arrow Lake-H [PCH Serial IO UART Host Controller] - 7726 Arrow Lake-H PCH Serial IO UART Host Controller] - 7727 Arrow Lake-H [LPC/eSPI Controller] - 7728 Arrow Lake cAVS + 7720 Core Ultra 200H/200V Series Processors P2SB (SOC) + 7721 Core Ultra 200H/200V Series Processors PMC (SOC) + 7722 Core Ultra 200H/200V Series Processors SMBus + 7723 Core Ultra 200H/200V Series Processors SPI (flash) Controller + 7724 Core Ultra 200H/200V Series Processors Trace Hub + 7725 Core Ultra 200H/200V Series Processors UART #0 + 7726 Core Ultra 200H/200V Series Processors UART #1 + 7727 Core Ultra 200H/200V Series Processors GSPI #0 + 7728 Core Ultra 200H/200V Series Processors HD Audio 7730 Arrow Lake-H [LPC/eSPI Controller] - 7738 Arrow Lake-H/U PCIe Root Port #1 - 7739 Arrow Lake-H/U PCIe Root Port #2 - 773a Arrow Lake-H/U PCIe Root Port #3 - 773b Arrow Lake-H/U PCIe Root Port #4 - 773c Arrow Lake-H/U PCIe Root Port #5 - 773d Arrow Lake-H/U PCIe Root Port #6 - 773e Arrow Lake-H/U PCIe Root Port #7 - 773f Arrow Lake-H/U PCIe Root Port #8 + 7738 Core Ultra 200H/200V Series Processors PCIe Root Port #1 + 7739 Core Ultra 200H/200V Series Processors PCIe Root Port #2 + 773a Core Ultra 200H/200V Series Processors PCIe Root Port #3 + 773b Core Ultra 200H/200V Series Processors PCIe Root Port #4 + 773c Core Ultra 200H/200V Series Processors PCIe Root Port #5 + 773d Core Ultra 200H/200V Series Processors PCIe Root Port #6 + 773e Core Ultra 200H/200V Series Processors PCIe Root Port #7 + 773f Core Ultra 200H/200V Series Processors PCIe Root Port #8 7740 Arrow Lake CNVi WiFi - 7745 Arrow Lake Integrated Sensor Hub - 7746 Arrow Lake-H [LPC/eSPI Controller] - 774c Arrow Lake Gaussian & Neural Accelerator - 774d Arrow Lake-H/U PCIe Root Port #9 (PXPC) - 7750 Arrow Lake-H [Serial IO I2C Host Controller] - 7751 Arrow Lake-H [Serial IO I2C Host Controller] - 7752 Arrow Lake-H [PCH Serial IO UART Host Controller] - 7770 Arrow Lake HECI Controller #1 - 7773 Arrow Lake Keyboard and Text (KT) Redirection - 7778 Arrow Lake-H [Serial IO I2C Host Controller] - 7779 Arrow Lake-H [Serial IO I2C Host Controller] - 777a Arrow Lake-H [Serial IO I2C Host Controller] - 777b Arrow Lake-H [Serial IO I2C Host Controller] - 777d Arrow Lake USB 3.2 xHCI Controller - 777f Arrow Lake Shared SRAM + 7745 Core Ultra 200H/200V Series Processors Integrated Sensor Hub + 7746 Core Ultra 200H/200V Series Processors GSPI #2 + 7748 Core Ultra 200H/200V Series Processors Touch Host Controller (THC) #0 ID1 + 7749 Core Ultra 200H/200V Series Processors Touch Host Controller (THC) #0 ID2 + 774a Core Ultra 200H/200V Series Processors Touch Host Controller (THC) #1 ID1 + 774b Core Ultra 200H/200V Series Processors Touch Host Controller (THC) #1 ID2 + 774c Core Ultra 200H/200V Series Processors Gaussian & Neural-Network Accelerator (GNA) + 774d Core Ultra 200H/200V Series Processors PCIe Root Port #9 (PXPC) + 7750 Core Ultra 200H/200V Series Processors I2C #4 + 7751 Core Ultra 200H/200V Series Processors I2C #5 + 7752 Core Ultra 200H/200V Series Processors UART #2 + 7758 Core Ultra 200H/200V Series Processors CSME HECI #1 + 7759 Core Ultra 200H/200V Series Processors CSME HECI #2 + 775a Core Ultra 200H/200V Series Processors CSME HECI #3 + 7763 Core Ultra 200H/200V Series Processors SATA Controller (AHCI) + 7767 Core Ultra 200H/200V Series Processors SATA Controller (RAID 0/1/5/10) premium + 7770 Core Ultra 200H/200V Series Processors CSME HECI #1 + 7771 Core Ultra 200H/200V Series Processors CSME HECI #2 + 7772 Core Ultra 200H/200V Series Processors CSME IDE Redirection (IDE-R) + 7773 Core Ultra 200H/200V Series Processors CSME Keyboard and Text (KT) Redirection + 7774 Core Ultra 200H/200V Series Processors CSME HECI #3 + 7775 Core Ultra 200H/200V Series Processors CSME HECI #4 + 7778 Core Ultra 200H/200V Series Processors I2C #0 + 7779 Core Ultra 200H/200V Series Processors I2C #1 + 777a Core Ultra 200H/200V Series Processors I2C #2 + 777b Core Ultra 200H/200V Series Processors I2C #3 + 777c Core Ultra 200H/200V Series Processors I3C + 777d Core Ultra 200H/200V Series Processors Standalone xHCI Controller + 777e Core Ultra 200H/200V Series Processors Standalone USB Device Controller + 777f Core Ultra 200H/200V Series Processors Shared SRAM 7800 82740 (i740) AGP Graphics Accelerator 003d 0008 Starfighter AGP 003d 000b Starfighter AGP @@ -38524,122 +39369,183 @@ 10b4 202f Lightspeed 740 8086 0000 Terminator 2x/i 8086 0100 Intel740 Graphics Accelerator - 7a04 Z790 Chipset LPC/eSPI Controller - 7a05 H770 Chipset LPC/eSPI Controller - 7a06 B760 Chipset LPC/eSPI Controller - 7a0c HM770 Chipset LPC/eSPI Controller + 7a04 Z790 Chipset eSPI Controller + 7a05 H770 Chipset eSPI Controller + 7a06 B760 Chipset eSPI Controller + 7a0c HM770 Chipset eSPI Controller 7a0d WM790 Chipset LPC/eSPI Controller - 7a13 C266 Chipset LPC/eSPI Controller - 7a14 C262 Chipset LPC/eSPI Controller - 7a20 700 Series Chipset P2SB - 7a21 700 Series Chipset Power Management Controller - 7a23 700 Series Chipset SMBus Controller - 7a24 Raptor Lake SPI (flash) Controller - 7a27 Raptor Lake PCH Shared SRAM - 7a28 700 Series Chipset Serial IO UART Controller #0 - 7a29 700 Series Chipset Serial IO UART Controller #1 - 7a2a 700 Series Chipset Serial IO GSPI Controller #0 - 7a2b 700 Series Chipset Serial IO GSPI Controller #1 - 7a30 Raptor Lake PCI Express Root Port #9 - 7a31 Raptor Lake PCI Express Root Port #10 - 7a32 Raptor Lake PCI Express Root Port #11 - 7a33 Raptor Lake PCI Express Root Port #12 - 7a34 Raptor Lake PCI Express Root Port #13 - 7a35 Raptor Lake PCI Express Root Port #14 - 7a36 Raptor Lake PCI Express Root Port #15 - 7a37 Raptor Lake PCI Express Root Port #16 - 7a38 Raptor Lake PCI Express Root Port #1 - 7a39 Raptor Lake PCI Express Root Port #2 - 7a3a Raptor Lake PCI Express Root Port #3 - 7a3b Raptor Lake PCI Express Root Port #4 - 7a3c Raptor Lake PCI Express Root Port #5 - 7a3d Raptor Lake PCI Express Root Port #6 - 7a3e Raptor Lake PCI Express Root Port #7 - 7a3f Raptor Lake PCI Express Root Port #8 - 7a40 Raptor Lake PCI Express Root Port #17 - 7a41 Raptor Lake PCI Express Root Port #18 - 7a42 Raptor Lake PCI Express Root Port #19 - 7a43 Raptor Lake PCI Express Root Port #20 - 7a44 Raptor Lake PCI Express Root Port #21 - 7a45 Raptor Lake PCI Express Root Port #22 - 7a46 Raptor Lake PCI Express Root Port #23 - 7a47 Raptor Lake PCI Express Root Port #24 - 7a48 Raptor Lake PCI Express Root Port #25 - 7a49 Raptor Lake PCI Express Root Port #26 - 7a4a Raptor Lake PCI Express Root Port #27 - 7a4b Raptor Lake PCI Express Root Port #28 - 7a4c Raptor Lake Serial IO I2C Host Controller #0 - 7a4d Raptor Lake Serial IO I2C Host Controller #1 - 7a4e Raptor Lake Serial IO I2C Host Controller #2 - 7a4f Raptor Lake Serial IO I2C Host Controller #3 - 7a50 Raptor Lake High Definition Audio Controller - 7a5c 700 Series Chipset Serial IO UART Controller #3 - 7a60 Raptor Lake USB 3.2 Gen 2x2 (20 Gb/s) XHCI Host Controller - 7a61 Raptor Lake USB 3.2 Gen 1x1 (5 Gb/s) xDCI Device Controller - 7a62 Raptor Lake SATA AHCI Controller - 7a68 Raptor Lake CSME HECI #1 - 7a69 Raptor Lake CSME HECI #2 - 7a6a Raptor Lake CSME IDE Redirection - 7a6b Raptor Lake CSME Keyboard and Text (KT) Redirection - 7a6c Raptor Lake CSME HECI #3 - 7a6d Raptor Lake CSME HECI #4 - 7a70 700 Series Chipset CNVi WiFi + 7a13 C266 Chipset eSPI Controller + 7a14 C262 Chipset eSPI Controller + 7a20 700 Series Chipset Family P2SB + 7a21 700 Series Chipset Family PMC + 7a23 700 Series Chipset Family SMBus + 7a24 700 Series Chipset Family SPI (flash) Controller + 7a26 700 Series Chipset Family Trace Hub + 7a27 700 Series Chipset Family Shared SRAM + 7a28 700 Series Chipset Family UART #0 + 7a29 700 Series Chipset Family UART #1 + 7a2a 700 Series Chipset Family GSPI #0 + 7a2b 700 Series Chipset Family GSPI #1 + 7a30 700 Series Chipset Family PCIe Root Port #9 + 7a31 700 Series Chipset Family PCIe Root Port #10 + 7a32 700 Series Chipset Family PCIe Root Port #11 + 7a33 700 Series Chipset Family PCIe Root Port #12 + 7a34 700 Series Chipset Family PCIe Root Port #13 + 7a35 700 Series Chipset Family PCIe Root Port #14 + 7a36 700 Series Chipset Family PCIe Root Port #15 + 7a37 700 Series Chipset Family PCIe Root Port #16 + 7a38 700 Series Chipset Family PCIe Root Port #1 + 7a39 700 Series Chipset Family PCIe Root Port #2 + 7a3a 700 Series Chipset Family PCIe Root Port #3 + 7a3b 700 Series Chipset Family PCIe Root Port #4 + 7a3c 700 Series Chipset Family PCIe Root Port #5 + 7a3d 700 Series Chipset Family PCIe Root Port #6 + 7a3e 700 Series Chipset Family PCIe Root Port #7 + 7a3f 700 Series Chipset Family PCIe Root Port #8 + 7a40 700 Series Chipset Family PCIe Root Port #17 + 7a41 700 Series Chipset Family PCIe Root Port #18 + 7a42 700 Series Chipset Family PCIe Root Port #19 + 7a43 700 Series Chipset Family PCIe Root Port #20 + 7a44 700 Series Chipset Family PCIe Root Port #21 + 7a45 700 Series Chipset Family PCIe Root Port #22 + 7a46 700 Series Chipset Family PCIe Root Port #23 + 7a47 700 Series Chipset Family PCIe Root Port #24 + 7a48 700 Series Chipset Family PCIe Root Port #25 + 7a49 700 Series Chipset Family PCIe Root Port #26 + 7a4a 700 Series Chipset Family PCIe Root Port #27 + 7a4b 700 Series Chipset Family PCIe Root Port #28 + 7a4c 700 Series Chipset Family I2C Controller #0 + 7a4d 700 Series Chipset Family I2C Controller #1 + 7a4e 700 Series Chipset Family I2C Controller #2 + 7a4f 700 Series Chipset Family I2C Controller #3 + 7a50 700 Series Chipset Family HD Audio + 7a51 700 Series Chipset Family HD Audio + 7a52 700 Series Chipset Family HD Audio + 7a53 700 Series Chipset Family HD Audio + 7a54 700 Series Chipset Family HD Audio + 7a55 700 Series Chipset Family HD Audio + 7a56 700 Series Chipset Family HD Audio + 7a57 700 Series Chipset Family HD Audio + 7a5c 700 Series Chipset Family UART #3 + 7a60 700 Series Chipset Family USB 3.2 Gen 2x2 (20 Gbs) xHCI Host Controller + 7a61 700 Series Chipset Family USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) + 7a62 700 Series Chipset Family SATA Controller (AHCI) + 7a68 700 Series Chipset Family CSME HECI #1 + 7a69 700 Series Chipset Family CSME HECI #2 + 7a6a 700 Series Chipset Family CSME IDE Redirection (IDE-R) + 7a6b 700 Series Chipset Family CSME Keyboard and Text (KT) Redirection + 7a6c 700 Series Chipset Family CSME HECI #3 + 7a6d 700 Series Chipset Family CSME HECI #4 + 7a70 700 Series Chipset Family CNVi Wi-Fi 8086 0074 Wi-Fi 6 AX201 160MHz 8086 0090 WiFi 6E AX211 160MHz - 7a79 700 Series Chipset Serial IO GSPI Controller #3 - 7a7b 700 Series Chipset Serial IO GSPI Controller #2 - 7a7c Raptor Lake Serial IO I2C Host Controller #4 - 7a7d Raptor Lake Serial IO I2C Host Controller #5 - 7a7e 700 Series Chipset Serial IO UART Controller #2 - 7a83 Q670 Chipset LPC/eSPI Controller -# Unlike other PCH components. The eSPI controller is specific to each chipset model - 7a84 Z690 Chipset LPC/eSPI Controller - 7a85 H670 Chipset LPC/eSPI Controller - 7a86 B660 Chipset LPC/eSPI Controller - 7a87 H610 Chipset LPC/eSPI Controller - 7a88 W680 Chipset LPC/eSPI Controller - 7a8c HM670 Chipset LPC/eSPI Controller - 7a8d WM690 Chipset LPC/eSPI Controller - 7aa3 Alder Lake-S PCH SMBus Controller - 7aa4 Alder Lake-S PCH SPI Controller - 7aa7 Alder Lake-S PCH Shared SRAM - 7aa8 Alder Lake-S PCH Serial IO UART #0 - 7aab Alder Lake-S PCH Serial IO SPI Controller #1 - 7ab0 Alder Lake-S PCH PCI Express Root Port #9 - 7ab4 Alder Lake-S PCH PCI Express Root Port #13 - 7ab8 Alder Lake-S PCH PCI Express Root Port #1 - 7ab9 Alder Lake-S PCH PCI Express Root Port #2 - 7aba Alder Lake-S PCH PCI Express Root Port #3 - 7abc Alder Lake-S PCH PCI Express Root Port #5 - 7abd Alder Lake-S PCH PCI Express Root Port #6 - 7abf Alder Lake-S PCH PCI Express Root Port #8 - 7ac4 Alder Lake-S PCH PCI Express Root Port #21 - 7ac8 Alder Lake-S PCH PCI Express Root Port #25 - 7acc Alder Lake-S PCH Serial IO I2C Controller #0 - 7acd Alder Lake-S PCH Serial IO I2C Controller #1 - 7ace Alder Lake-S PCH Serial IO I2C Controller #2 - 7acf Alder Lake-S PCH Serial IO I2C Controller #3 - 7ad0 Alder Lake-S HD Audio Controller - 7ae0 Alder Lake-S PCH USB 3.2 Gen 2x2 XHCI Controller - 7ae1 Alder Lake-S PCH USB 3.2 Gen 1x1 xDCI Controller - 7ae2 Alder Lake-S PCH SATA Controller [AHCI Mode] - 7ae8 Alder Lake-S PCH HECI Controller #1 - 7aeb Alder Lake-S Keyboard and Text (KT) Redirection - 7af0 Alder Lake-S PCH CNVi WiFi + 7a71 700 Series Chipset Family CNVi Wi-Fi + 7a72 700 Series Chipset Family CNVi Wi-Fi + 7a73 700 Series Chipset Family CNVi Wi-Fi + 7a78 700 Series Chipset Family Integrated Sensor Hub + 7a79 700 Series Chipset Family GSPI #3 + 7a7b 700 Series Chipset Family GSPI #2 + 7a7c 700 Series Chipset Family I2C Controller #4 + 7a7d 700 Series Chipset Family I2C Controller #5 + 7a7e 700 Series Chipset Family UART #2 + 7a83 Q670 Chipset eSPI Controller + 7a84 Z690 Chipset eSPI Controller + 7a85 H670 Chipset eSPI Controller + 7a86 B660 Chipset eSPI Controller + 7a87 H610 Chipset eSPI Controller + 7a88 W680 Chipset eSPI Controller + 7a8a W790 Chipset eSPI Controller + 7a8c HM670 Chipset eSPI Controller + 7a8d WM690 Chipset eSPI Controller + 7aa0 600 Series Chipset Family P2SB + 7aa1 600 Series Chipset Family PMC + 7aa3 600 Series Chipset Family SMBus + 7aa4 600 Series Chipset Family SPI (flash) Controller + 7aa6 600 Series Chipset Family Trace Hub + 7aa7 600 Series Chipset Family Shared SRAM + 7aa8 600 Series Chipset Family UART #0 + 7aa9 600 Series Chipset Family UART #1 + 7aaa 600 Series Chipset Family GSPI #0 + 7aab 600 Series Chipset Family GSPI #1 + 7ab0 600 Series Chipset Family PCIe Root Port #9 + 7ab1 600 Series Chipset Family PCIe Root Port #10 + 7ab2 600 Series Chipset Family PCIe Root Port #11 + 7ab3 600 Series Chipset Family PCIe Root Port #12 + 7ab4 600 Series Chipset Family PCIe Root Port #13 + 7ab5 600 Series Chipset Family PCIe Root Port #14 + 7ab6 600 Series Chipset Family PCIe Root Port #15 + 7ab7 600 Series Chipset Family PCIe Root Port #16 + 7ab8 600 Series Chipset Family PCIe Root Port #1 + 7ab9 600 Series Chipset Family PCIe Root Port #2 + 7aba 600 Series Chipset Family PCIe Root Port #3 + 7abb 600 Series Chipset Family PCIe Root Port #4 + 7abc 600 Series Chipset Family PCIe Root Port #5 + 7abd 600 Series Chipset Family PCIe Root Port #6 + 7abe 600 Series Chipset Family PCIe Root Port #7 + 7abf 600 Series Chipset Family PCIe Root Port #8 + 7ac0 600 Series Chipset Family PCIe Root Port #17 + 7ac1 600 Series Chipset Family PCIe Root Port #18 + 7ac2 600 Series Chipset Family PCIe Root Port #19 + 7ac3 600 Series Chipset Family PCIe Root Port #20 + 7ac4 600 Series Chipset Family PCIe Root Port #21 + 7ac5 600 Series Chipset Family PCIe Root Port #22 + 7ac6 600 Series Chipset Family PCIe Root Port #23 + 7ac7 600 Series Chipset Family PCIe Root Port #24 + 7ac8 600 Series Chipset Family PCIe Root Port #25 + 7ac9 600 Series Chipset Family PCIe Root Port #26 + 7aca 600 Series Chipset Family PCIe Root Port #27 + 7acb 600 Series Chipset Family PCIe Root Port #28 + 7acc 600 Series Chipset Family I2C Controller #0 + 7acd 600 Series Chipset Family I2C Controller #1 + 7ace 600 Series Chipset Family I2C Controller #2 + 7acf 600 Series Chipset Family 12C Controller #3 + 7ad0 600 Series Chipset Family HD Audio + 7ad1 600 Series Chipset Family HD Audio + 7ad2 600 Series Chipset Family HD Audio + 7ad3 600 Series Chipset Family HD Audio + 7ad4 600 Series Chipset Family HD Audio + 7ad5 600 Series Chipset Family HD Audio + 7ad6 600 Series Chipset Family HD Audio + 7ad7 600 Series Chipset Family HD Audio + 7adc 600 Series Chipset Family UART #3 + 7ae0 600 Series Chipset Family USB 3.2 Gen 2x2 (20Gbs) XHCI Host Controller + 7ae1 600 Series Chipset Family USB 3.2 Gen 1x1 (5Gbs) Device Controller (xDCI) + 7ae2 600 Series Chipset Family SATA Controller (AHCI) + 7ae8 600 Series Chipset Family CSME HECI #1 + 7ae9 600 Series Chipset Family CSME HECI #2 + 7aea 600 Series Chipset Family CSME IDE Redirection (IDE-R) + 7aeb 600 Series Chipset Family CSME Keyboard and Text (KT) Redirection + 7aec 600 Series Chipset Family CSME HECI #3 + 7aed 600 Series Chipset Family CSME HECI #4 + 7af0 600 Series Chipset Family CNVi Wi-Fi 8086 0034 Wireless-AC 9560 8086 0070 Wi-Fi 6 AX201 160MHz 8086 0094 Wi-Fi 6 AX201 160MHz - 7af8 Alder Lake-S Integrated Sensor Hub - 7afc Alder Lake-S PCH Serial IO I2C Controller #4 - 7afd Alder Lake-S PCH Serial IO I2C Controller #5 + 7af1 600 Series Chipset Family CNVi Wi-Fi + 7af2 600 Series Chipset Family CNVi Wi-Fi + 7af3 600 Series Chipset Family CNVi Wi-Fi + 7af8 600 Series Chipset Family Integrated Sensor Hub + 7af9 600 Series Chipset Family GSPI #3 + 7afb 600 Series Chipset Family GSPI #2 + 7afc 600 Series Chipset Family I2C Controller #4 + 7afd 600 Series Chipset Family I2C Controller #5 + 7afe 600 Series Chipset Family UART #2 7d01 Meteor Lake-H 6p+8e cores Host Bridge/DRAM Controller - 7d03 Meteor Lake-P Dynamic Tuning Technology - 7d06 Arrow Lake-H 6p+8e cores Host Bridge/DRAM Controller - 7d0b Volume Management Device NVMe RAID Controller Intel Corporation - 7d0d Meteor Lake-P Platform Monitoring Technology - 7d19 Meteor Lake IPU - 7d1c Arrow Lake-HX 8p+16e cores Host Bridge - 7d1d Meteor Lake NPU + 7d03 Core Ultra 200H/200V Series Processors Dynamic Tuning Technology (DTT) + 7d06 Core Ultra 200H Series Processors with 6 P-Cores 8 E-Cores Host Bridge + 7d0b Core Ultra 200H/200V Series Processors VMD + 7d0d Core Ultra 200H/200V Series Processors Platform Monitoring Technology (PMT) + 7d19 Core Ultra 200H/200V Series Processors IPU + 7d1a Core Ultra 200S/200S-Plus Series Processors with 8 P-Cores 16 E-Cores Host Bridge + 7d1b Core Ultra 200S Series Processors with 8 P-Cores 12 E-Cores Host Bridge + 7d1c Core Ultra 200HX/HX-Plus Series Processors with 8 P-Cores 16 E-Cores Host Bridge + 7d1d Core Ultra 200H/200V Series Processors NPU + 7d29 Core Ultra 200S-Plus Series Processors with 6 P-Cores 12 E-Cores Host Bridge + 7d2a Core Ultra 200S Series Processors with 6 P-Cores 8 E-Cores Host Bridge + 7d2d Core Ultra 200HX/HX-Plus Series Processors with 8 P-Cores 12 E-Cores Host Bridge + 7d2f Core Ultra 200HX Series Processors with 6 P-Cores 8 E-Cores Host Bridge + 7d30 Core Ultra 200U Series Processors with 2 P-Cores 8 E-Cores Host Bridge + 7d35 Core Ultra 200S Series Processors with 6 P-Cores 4 E-Cores Host Bridge 7d40 Meteor Lake-M [Intel Graphics] 7d41 Arrow Lake-U [Intel Graphics] 7d45 Meteor Lake-P [Intel Graphics] @@ -38679,44 +39585,94 @@ 7e7d Meteor Lake-P USB 3.2 Gen 2x1 xHCI Host Controller 7e7e Meteor Lake-P USB Device Controller 7e7f Meteor Lake-H/U Shared SRAM - 7ec0 Meteor Lake-P Thunderbolt 4 USB Controller - 7ec2 Meteor Lake-P Thunderbolt 4 NHI #0 - 7ec3 Meteor Lake-P Thunderbolt 4 NHI #1 - 7ec4 Meteor Lake-P Thunderbolt 4 PCI Express Root Port #0 - 7ec5 Meteor Lake-P Thunderbolt 4 PCI Express Root Port #1 - 7ec6 Meteor Lake-P Thunderbolt 4 PCI Express Root Port #2 - 7ec7 Meteor Lake-P Thunderbolt 4 PCI Express Root Port #3 - 7eca Meteor Lake-H/U PCIe Root Port #10 - 7ecb Arrow Lake-H/U PCIe Root Port #11 (PXPE) - 7ecc Meteor Lake-H PCIe Root Port #12 - 7f03 Q870 Chipset LPC/eSPI Controller - 7f04 Z890 Chipset LPC/eSPI Controller - 7f06 B860 Chipset LPC/eSPI Controller - 7f07 H810 Chipset LPC/eSPI Controller - 7f08 W880 Chipset LPC/eSPI Controller - 7f0c HM870 Chipset LPC/eSPI Controller - 7f0d WM880 Chipset LPC/eSPI Controller - 7f20 800 Series PCH P2SB - 7f21 800 Series PCH Power Management Controller - 7f23 800 Series PCH SMBus Controller - 7f24 800 Series PCH SPI (flash) Controller - 7f27 800 Series PCH Shared SRAM - 7f30 800 Series PCH PCIe Root Port #9 - 7f34 800 Series PCH PCIe Root Port #13 - 7f36 800 Series PCH PCIe Root Port #15 - 7f38 800 Series PCH PCIe Root Port #1 - 7f3e 800 Series PCH PCIe Root Port #7 - 7f40 800 Series PCH PCIe Root Port #17 - 7f44 800 Series PCH PCIe Root Port #21 - 7f4c 800 Series PCH I2C Controller #0 - 7f4f 800 Series PCH I2C Controller #3 - 7f50 800 Series ACE (Audio Context Engine) - 7f68 800 Series PCH HECI #1 - 7f6e 800 Series PCH USB 3.1 xHCI HC - 7f70 Arrow Lake-S PCH CNVi WiFi + 7ec0 Core Ultra 200 Series Processors USB xHCI + 7ec1 Core Ultra 200 Series Processors USB xDCI + 7ec2 Core Ultra 200 Series Processors Thunderbolt DMA0 + 7ec3 Core Ultra 200 Series Processors Thunderbolt DMA1 + 7ec4 Core Ultra 200 Series Processors USB Type-C Subsystem PCIe Root Port #16 + 7ec5 Core Ultra 200 Series Processors USB Type-C Subsystem PCIe Root Port #17 + 7ec6 Core Ultra 200 Series Processors USB Type-C Subsystem PCIe Root Port #18 + 7ec7 Core Ultra 200 Series Processors USB Type-C Subsystem PCIe Root Port #19 + 7ec8 Core Ultra 200 Series Processors P2SB (IOE) + 7ec9 Core Ultra 200H/200V Series Processors IEH (IOE) + 7eca Core Ultra 200 Series Processors PCIe Root Port #10 + 7ecb Core Ultra 200H/200V Series Processors PCIe Root Port #11 (PXPE) + 7ecc Core Ultra 200 Series Processors PCIe Root Port #12 + 7ece Core Ultra 200 Series Processors PMC (IOE) + 7ecf Core Ultra 200 Series Processors Shared SRAM (IOE) + 7f03 Q870 Chipset eSPI Controller + 7f04 Z890 Chipset eSPI Controller + 7f06 B860 Chipset eSPI Controller + 7f07 H810 Chipset eSPI Controller + 7f08 W880 Chipset eSPI Controller + 7f0c HM870 Chipset eSPI Controller + 7f0d WM880 Chipset eSPI Controller + 7f20 800 Series Chipset Family P2SB + 7f21 800 Series Chipset Family PMC + 7f23 800 Series Chipset Family SMBus + 7f24 800 Series Chipset Family SPI (flash) Controller + 7f26 800 Series Chipset Family Trace Hub + 7f27 800 Series Chipset Family Shared SRAM + 7f28 800 Series Chipset Family UART #0 + 7f29 800 Series Chipset Family UART #1 + 7f2a 800 Series Chipset Family GSPI #0 + 7f2b 800 Series Chipset Family GSPI #1 + 7f30 800 Series Chipset Family PCIe Root Port #9 + 7f31 800 Series Chipset Family PCIe Root Port #10 + 7f32 800 Series Chipset Family PCIe Root Port #11 + 7f33 800 Series Chipset Family PCIe Root Port #12 + 7f34 800 Series Chipset Family PCIe Root Port #13 + 7f35 800 Series Chipset Family PCIe Root Port #14 + 7f36 800 Series Chipset Family PCIe Root Port #15 + 7f37 800 Series Chipset Family PCIe Root Port #16 + 7f38 800 Series Chipset Family PCIe Root Port #1 + 7f39 800 Series Chipset Family PCIe Root Port #2 + 7f3a 800 Series Chipset Family PCIe Root Port #3 + 7f3b 800 Series Chipset Family PCIe Root Port #4 + 7f3c 800 Series Chipset Family PCIe Root Port #5 + 7f3d 800 Series Chipset Family PCIe Root Port #6 + 7f3e 800 Series Chipset Family PCIe Root Port #7 + 7f3f 800 Series Chipset Family PCIe Root Port #8 + 7f40 800 Series Chipset Family PCIe Root Port #17 + 7f41 800 Series Chipset Family PCIe Root Port #18 + 7f42 800 Series Chipset Family PCIe Root Port #19 + 7f43 800 Series Chipset Family PCIe Root Port #20 + 7f44 800 Series Chipset Family PCIe Root Port #21 + 7f45 800 Series Chipset Family PCIe Root Port #22 + 7f46 800 Series Chipset Family PCIe Root Port #23 + 7f47 800 Series Chipset Family PCIe Root Port #24 + 7f4c 800 Series Chipset Family I2C #0 + 7f4d 800 Series Chipset Family I2C #1 + 7f4e 800 Series Chipset Family I2C #2 + 7f4f 800 Series Chipset Family I2C #3 + 7f50 800 Series Chipset Family Audio Context Engine (ACE) + 7f58 800 Series Chipset Family Touch Host Controller (THC) #0 ID1 + 7f59 800 Series Chipset Family Touch Host Controller (THC) #0 ID2 + 7f5a 800 Series Chipset Family Touch Host Controller (THC) #1 ID1 + 7f5b 800 Series Chipset Family Touch Host Controller (THC) #1 ID2 + 7f5c 800 Series Chipset Family UART #2 + 7f5d 800 Series Chipset Family UART #3 + 7f5e 800 Series Chipset Family GSPI #2 + 7f5f 800 Series Chipset Family GSPI #3 + 7f62 800 Series Chipset Family SATA Controller (AHCI) + 7f66 800 Series Chipset Family SATA Controller (RAID 0/1/5/10) premium + 7f68 800 Series Chipset Family CSME HECI #1 + 7f69 800 Series Chipset Family CSME HECI #2 + 7f6a 800 Series Chipset Family IDE Redirection (IDER-R) + 7f6b 800 Series Chipset Family Keyboard and Text (KT) Redirection + 7f6c 800 Series Chipset Family CSME HECI #3 + 7f6d 800 Series Chipset Family CSME HECI #4 + 7f6e 800 Series Chipset Family USB 3.1 xHCI HC + 7f6f 800 Series Chipset Family USB Device Controller (OTG) (xDCI) + 7f70 800 Series Chipset Family CNVi Wi-Fi 8086 0094 WiFi 6E AX211 160MHz - 7f7a 800 Series PCH I2C Controller #4 - 7f7b 800 Series PCH I2C Controller #5 + 7f78 800 Series Chipset Family Integrated Sensor Hub + 7f79 800 Series Chipset Family I3C + 7f7a 800 Series Chipset Family I2C #4 + 7f7b 800 Series Chipset Family I2C #5 + 7f7c 800 Series Chipset Family Silicon Security Engine HECI #1 + 7f7d 800 Series Chipset Family Silicon Security Engine HECI #2 + 7f7e 800 Series Chipset Family Silicon Security Engine HECI #3 8002 Trusted Execution Technology Registers 8003 Trusted Execution Technology Registers 8100 US15W/US15X SCH [Poulsbo] Host Bridge @@ -39454,38 +40410,56 @@ 1043 83ac Eee PC 1015PX 144d c072 Notebook N150P a013 Atom Processor D4xx/D5xx/N4xx/N5xx CHAPS counter - a082 Tiger Lake-LP LPC Controller - a0a3 Tiger Lake-LP SMBus Controller - a0a4 Tiger Lake-LP SPI Controller - a0a6 Tiger Lake-LP Trace Hub - a0a8 Tiger Lake-LP Serial IO UART Controller #0 - a0a9 Tiger Lake-LP Serial IO UART Controller #1 - a0ab Tiger Lake-LP Serial IO SPI Controller #1 - a0b0 Tiger Lake-LP PCI Express Root Port #9 - a0b1 Tiger Lake-LP PCI Express Root Port #10 - a0b3 Tiger Lake-LP PCI Express Root Port #12 - a0b8 Tiger Lake-LP PCI Express Root Port #0 - a0bb Tiger Lake-LP PCI Express Root Port #3 - a0bc Tiger Lake-LP PCI Express Root Port #5 - a0bd Tigerlake PCH-LP PCI Express Root Port #6 - a0be Tiger Lake-LP PCI Express Root Port #7 - a0bf Tiger Lake-LP PCI Express Root Port #8 - a0c5 Tiger Lake-LP Serial IO I2C Controller #4 - a0c6 Tiger Lake-LP Serial IO I2C Controller #5 - a0c8 Tiger Lake-LP Smart Sound Technology Audio Controller -# SATA controller on Intel Tiger Lake based mobile platforms in AHCI mode. Could be found on Panasonic Let's Note CF-SV2. - a0d3 Tiger Lake-LP SATA Controller - a0e0 Tiger Lake-LP Management Engine Interface - a0e3 Tiger Lake-LP Active Management Technology - SOL - a0e8 Tiger Lake-LP Serial IO I2C Controller #0 - a0e9 Tiger Lake-LP Serial IO I2C Controller #1 - a0ea Tiger Lake-LP Serial IO I2C Controller #2 - a0eb Tiger Lake-LP Serial IO I2C Controller #3 - a0ed Tiger Lake-LP USB 3.2 Gen 2x1 xHCI Host Controller - a0ef Tiger Lake-LP Shared SRAM + a082 500 Series Chipset Family On-Package eSPI Controller + a0a0 500 Series Chipset Family On-Package Primary-to-Sideband Bridge (P2SB) + a0a1 500 Series Chipset Family On-Package Power Management Controller (PMC) + a0a3 500 Series Chipset Family On-Package System Management Bus (SMBus) + a0a4 500 Series Chipset Family On-Package SPI (flash) Controller + a0a6 500 Series Chipset Family On-Package Trace Hub + a0a8 500 Series Chipset Family On-Package UART Controller #0 + a0a9 500 Series Chipset Family On-Package UART Controller #1 + a0aa 500 Series Chipset Family On-Package Generic SPI (GSPI) #0 + a0ab 500 Series Chipset Family On-Package Generic SPI (GSPI) #1 + a0b0 500 Series Chipset Family On-Package PCI Express Root Port #9 + a0b1 500 Series Chipset Family On-Package PCI Express Root Port #10 + a0b2 500 Series Chipset Family On-Package PCI Express Root Port #11 + a0b3 500 Series Chipset Family On-Package PCI Express Root Port #12 + a0b8 500 Series Chipset Family On-Package PCI Express Root Port #1 + a0b9 500 Series Chipset Family On-Package PCI Express Root Port #2 + a0ba 500 Series Chipset Family On-Package PCI Express Root Port #3 + a0bb 500 Series Chipset Family On-Package PCI Express Root Port #4 + a0bc 500 Series Chipset Family On-Package PCI Express Root Port #5 + a0bd 500 Series Chipset Family On-Package PCI Express Root Port #6 + a0be 500 Series Chipset Family On-Package PCI Express Root Port #7 + a0bf 500 Series Chipset Family On-Package PCI Express Root Port #8 + a0c5 500 Series Chipset Family On-Package I2C Controller #4 + a0c6 500 Series Chipset Family On-Package I2C Controller #5 + a0c7 500 Series Chipset Family On-Package UART Controller #2 + a0c8 500 Series Chipset Family On-Package High Definition Audio (HD Audio) + a0d0 500 Series Chipset Family On-Package Touch Host Controller #0 + a0d1 500 Series Chipset Family On-Package Touch Host Controller #1 + a0d3 500 Series Chipset Family On-Package SATA Controller (AHCI) + a0d5 500 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) no premium + a0d7 500 Series Chipset Family On-Package SATA Controller (RAID 0/1/5/10) premium + a0da 500 Series Chipset Family On-Package UART Controller #3 + a0e0 500 Series Chipset Family On-Package CSME HECI #1 + a0e1 500 Series Chipset Family On-Package CSME HECI #2 + a0e2 500 Series Chipset Family On-Package CSME IDE Redirection (IDE-R) + a0e3 500 Series Chipset Family On-Package CSME Keyboard and Text (KT) Redirection + a0e4 500 Series Chipset Family On-Package CSME HECI #3 + a0e5 500 Series Chipset Family On-Package CSME HECI #4 + a0e8 500 Series Chipset Family On-Package I2C Controller #0 + a0e9 500 Series Chipset Family On-Package I2C Controller #1 + a0ea 500 Series Chipset Family On-Package I2C Controller #2 + a0eb 500 Series Chipset Family On-Package I2C Controller #3 + a0ed 500 Series Chipset Family On-Package USB 3.2 Gen 2x1 (10 Gbs) xHCI Host Controller + a0ee 500 Series Chipset Family On-Package USB 3.2 Gen 1x1 (5 Gbs) Device Controller (xDCI) + a0ef 500 Series Chipset Family On-Package Shared SRAM a0f0 Wi-Fi 6 AX201 8086 0244 Wi-Fi 6 AX101NGW - a0fc Tiger Lake-LP Integrated Sensor Hub + a0fb 500 Series Chipset Family On-Package Generic SPI (GSPI) #2 + a0fc 500 Series Chipset Family On-Package Integrated Sensor Hub + a0fd 500 Series Chipset Family On-Package Generic SPI (GSPI) #3 a102 Q170/Q150/B150/H170/H110/Z170/CM236 Chipset SATA Controller [AHCI Mode] 1043 8694 H110I-PLUS Motherboard 1462 7994 H110M ECO/GAMING @@ -39496,65 +40470,65 @@ a106 Q170/H170/Z170/CM236 Chipset SATA Controller [RAID Mode] a107 HM170/QM170 Chipset SATA Controller [RAID Mode] a10f Sunrise Point-H SATA Controller [RAID mode] - a110 100 Series/C230 Series Chipset Family PCI Express Root Port #1 - a111 100 Series/C230 Series Chipset Family PCI Express Root Port #2 - a112 100 Series/C230 Series Chipset Family PCI Express Root Port #3 - a113 100 Series/C230 Series Chipset Family PCI Express Root Port #4 - a114 100 Series/C230 Series Chipset Family PCI Express Root Port #5 + a110 100/C230 Series Chipset Family PCIe Root Port #1 + a111 100/C230 Series Chipset Family PCIe Root Port #2 + a112 100/C230 Series Chipset Family PCIe Root Port #3 + a113 100/C230 Series Chipset Family PCIe Root Port #4 + a114 100/C230 Series Chipset Family PCIe Root Port #5 1043 8694 H110I-PLUS Motherboard - a115 100 Series/C230 Series Chipset Family PCI Express Root Port #6 - a116 100 Series/C230 Series Chipset Family PCI Express Root Port #7 - a117 100 Series/C230 Series Chipset Family PCI Express Root Port #8 - a118 100 Series/C230 Series Chipset Family PCI Express Root Port #9 + a115 100/C230 Series Chipset Family PCIe Root Port #6 + a116 100/C230 Series Chipset Family PCIe Root Port #7 + a117 100/C230 Series Chipset Family PCIe Root Port #8 + a118 100/C230 Series Chipset Family PCIe Root Port #9 1043 8694 H110I-PLUS Motherboard - a119 100 Series/C230 Series Chipset Family PCI Express Root Port #10 + a119 100/C230 Series Chipset Family PCIe Root Port #10 1043 8694 H110I-PLUS Motherboard - a11a 100 Series/C230 Series Chipset Family PCI Express Root Port #11 - a11b 100 Series/C230 Series Chipset Family PCI Express Root Port #12 - a11c 100 Series/C230 Series Chipset Family PCI Express Root Port #13 - a11d 100 Series/C230 Series Chipset Family PCI Express Root Port #14 - a11e 100 Series/C230 Series Chipset Family PCI Express Root Port #15 - a11f 100 Series/C230 Series Chipset Family PCI Express Root Port #16 - a120 100 Series/C230 Series Chipset Family P2SB - a121 100 Series/C230 Series Chipset Family Power Management Controller + a11a 100/C230 Series Chipset Family PCIe Root Port #11 + a11b 100/C230 Series Chipset Family PCIe Root Port #12 + a11c 100/C230 Series Chipset Family PCIe Root Port #13 + a11d 100/C230 Series Chipset Family PCIe Root Port #14 + a11e 100/C230 Series Chipset Family PCIe Root Port #15 + a11f 100/C230 Series Chipset Family PCIe Root Port #16 + a120 100/C230 Series Chipset Family P2SB + a121 100/C230 Series Chipset Family PMC 1028 06e4 XPS 15 9550 103c 825b OMEN-17-w001nv 1043 8694 H110I-PLUS Motherboard 1462 7994 H110M ECO/GAMING a122 Sunrise Point-H cAVS - a123 100 Series/C230 Series Chipset Family SMBus + a123 100/C230 Series Chipset Family SMBus 1028 06e4 XPS 15 9550 103c 825b OMEN-17-w001nv 1043 8694 H110I-PLUS Motherboard 1462 7994 H110M ECO/GAMING - a124 100 Series/C230 Series Chipset Family SPI Controller - a125 100 Series/C230 Series Chipset Family Gigabit Ethernet Controller - a126 100 Series/C230 Series Chipset Family Trace Hub - a127 100 Series/C230 Series Chipset Family Serial IO UART #0 - a128 100 Series/C230 Series Chipset Family Serial IO UART #1 - a129 100 Series/C230 Series Chipset Family Serial IO GSPI #0 - a12a 100 Series/C230 Series Chipset Family Serial IO GSPI #1 - a12f 100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller + a124 100/C230 Series Chipset Family SPI Controller + a125 100/C230 Series Chipset Family GbE Controller + a126 100/C230 Series Chipset Family Trace Hub + a127 100/C230 Series Chipset Family UART #0 + a128 100/C230 Series Chipset Family UART #1 + a129 100/C230 Series Chipset Family GSPI #0 + a12a 100/C230 Series Chipset Family GSPI #1 + a12f 100/C230 Series Chipset Family USB 3.0 xHCI Controller 1028 06e4 XPS 15 9550 103c 825b OMEN-17-w001nv 1043 8694 H110I-PLUS Motherboard 1462 7994 H110M ECO/GAMING - a130 100 Series/C230 Series Chipset Family USB Device Controller (OTG) - a131 100 Series/C230 Series Chipset Family Thermal Subsystem + a130 100/C230 Series Chipset Family USB Device Controller (OTG) + a131 100/C230 Series Chipset Family Thermal Subsystem 1028 06e4 XPS 15 9550 103c 825b OMEN-17-w001nv 1462 7994 H110M ECO/GAMING a133 Sunrise Point-H Northpeak ACPI Function - a135 100 Series/C230 Series Chipset Family Integrated Sensor Hub - a13a 100 Series/C230 Series Chipset Family MEI Controller #1 + a135 100/C230 Series Chipset Family ISH + a13a 100/C230 Series Chipset Family MEI #1 1028 06e4 XPS 15 9550 103c 825b OMEN-17-w001nv 1043 8694 H110I-PLUS Motherboard 1462 7994 H110M ECO/GAMING - a13b 100 Series/C230 Series Chipset Family MEI Controller #2 - a13c 100 Series/C230 Series Chipset Family IDE Redirection - a13d 100 Series/C230 Series Chipset Family KT Redirection - a13e 100 Series/C230 Series Chipset Family MEI Controller #3 + a13b 100/C230 Series Chipset Family MEI #2 + a13c 100/C230 Series Chipset Family IDE Redirection + a13d 100/C230 Series Chipset Family Keyboard and Text (KT) Redirection + a13e 100/C230 Series Chipset Family MEI #3 a140 Sunrise Point-H LPC Controller a141 Sunrise Point-H LPC Controller a142 Sunrise Point-H LPC Controller @@ -39591,44 +40565,44 @@ a15d Sunrise Point-H LPC Controller a15e Sunrise Point-H LPC Controller a15f Sunrise Point-H LPC Controller - a160 100 Series/C230 Series Chipset Family Serial IO I2C Controller #0 + a160 100/C230 Series Chipset Family I2C #0 1028 06e4 XPS 15 9550 103c 825b OMEN-17-w001nv - a161 100 Series/C230 Series Chipset Family Serial IO I2C Controller #1 + a161 100/C230 Series Chipset Family I2C #1 1028 06e4 XPS 15 9550 - a162 100 Series/C230 Series Chipset Family Serial IO I2C Controller #2 - a163 100 Series/C230 Series Chipset Family Serial IO I2C Controller #3 - a166 100 Series/C230 Series Chipset Family Serial IO UART Controller #2 - a167 100 Series/C230 Series Chipset Family PCI Express Root Port #17 - a168 100 Series/C230 Series Chipset Family PCI Express Root Port #18 - a169 100 Series/C230 Series Chipset Family PCI Express Root Port #19 - a16a 100 Series/C230 Series Chipset Family PCI Express Root Port #20 - a170 100 Series/C230 Series Chipset Family HD Audio Controller + a162 100/C230 Series Chipset Family I2C #2 + a163 100/C230 Series Chipset Family I2C #3 + a166 100/C230 Series Chipset Family UART #2 + a167 100/C230 Series Chipset Family PCIe Root Port #17 + a168 100/C230 Series Chipset Family PCIe Root Port #18 + a169 100/C230 Series Chipset Family PCIe Root Port #19 + a16a 100/C230 Series Chipset Family PCIe Root Port #20 + a170 100/C230 Series Chipset Family HD Audio 1028 06e4 XPS 15 9550 103c 825b OMEN-17-w001nv 1043 86c7 H110I-PLUS Motherboard 1462 f994 H110M ECO/GAMING a171 HM175/QM175/CM238 HD Audio Controller - a182 C620 Series Chipset Family SATA Controller [AHCI mode] - a186 C620 Series Chipset Family SATA Controller [RAID mode] - a190 C620 Series Chipset Family PCI Express Root Port #1 - a191 C620 Series Chipset Family PCI Express Root Port #2 - a192 C620 Series Chipset Family PCI Express Root Port #3 - a193 C620 Series Chipset Family PCI Express Root Port #4 - a194 C620 Series Chipset Family PCI Express Root Port #5 - a195 C620 Series Chipset Family PCI Express Root Port #6 - a196 C620 Series Chipset Family PCI Express Root Port #7 - a197 C620 Series Chipset Family PCI Express Root Port #8 - a198 C620 Series Chipset Family PCI Express Root Port #9 - a199 C620 Series Chipset Family PCI Express Root Port #10 - a19a C620 Series Chipset Family PCI Express Root Port #11 - a19b C620 Series Chipset Family PCI Express Root Port #12 - a19c C620 Series Chipset Family PCI Express Root Port #13 - a19d C620 Series Chipset Family PCI Express Root Port #14 - a19e C620 Series Chipset Family PCI Express Root Port #15 - a19f C620 Series Chipset Family PCI Express Root Port #16 + a182 C620 Series Chipset Family SATA Controller (AHCI) + a186 C620 Series Chipset Family SATA Controller (RAID 0/1/5/10) + a190 C620 Series Chipset Family PCIe Root Port #0 + a191 C620 Series Chipset Family PCIe Root Port #1 + a192 C620 Series Chipset Family PCIe Root Port #2 + a193 C620 Series Chipset Family PCIe Root Port #3 + a194 C620 Series Chipset Family PCIe Root Port #4 + a195 C620 Series Chipset Family PCIe Root Port #5 + a196 C620 Series Chipset Family PCIe Root Port #6 + a197 C620 Series Chipset Family PCIe Root Port #7 + a198 C620 Series Chipset Family PCIe Root Port #8 + a199 C620 Series Chipset Family PCIe Root Port #9 + a19a C620 Series Chipset Family PCIe Root Port #10 + a19b C620 Series Chipset Family PCIe Root Port #11 + a19c C620 Series Chipset Family PCIe Root Port #12 + a19d C620 Series Chipset Family PCIe Root Port #13 + a19e C620 Series Chipset Family PCIe Root Port #14 + a19f C620 Series Chipset Family PCIe Root Port #15 a1a0 C620 Series Chipset Family P2SB - a1a1 C620 Series Chipset Family Power Management Controller + a1a1 C620 Series Chipset Family PMC 15d9 095d X11SPM-TF a1a2 C620 Series Chipset Family cAVS a1a3 C620 Series Chipset Family SMBus @@ -39640,131 +40614,155 @@ 15d9 095d X11SPM-TF a1b1 C620 Series Chipset Family Thermal Subsystem 15d9 095d X11SPM-TF - a1ba C620 Series Chipset Family MEI Controller #1 + a1ba C620 Series Chipset Family MEI HECI #1 15d9 095d X11SPM-TF - a1bb C620 Series Chipset Family MEI Controller #2 + a1bb C620 Series Chipset Family MEI HECI #2 15d9 095d X11SPM-TF - a1bc C620 Series Chipset Family IDE Redirection - a1bd C620 Series Chipset Family KT Redirection - a1be C620 Series Chipset Family MEI Controller #3 + a1bc C620 Series Chipset Family MEI IDE Redirection + a1bd C620 Series Chipset Family MEI Keyboard and Text (KT) Redirection + a1be C620 Series Chipset Family MEI HECI #3 15d9 095d X11SPM-TF - a1c1 C621 Series Chipset LPC/eSPI Controller - a1c2 C622 Series Chipset LPC/eSPI Controller + a1c1 C621 Chipset LPC/eSPI Controller + a1c2 C622 Chipset LPC/eSPI Controller 15d9 095d X11SPM-TF - a1c3 C624 Series Chipset LPC/eSPI Controller - a1c4 C625 Series Chipset LPC/eSPI Controller - a1c5 C626 Series Chipset LPC/eSPI Controller - a1c6 C627 Series Chipset LPC/eSPI Controller - a1c7 C628 Series Chipset LPC/eSPI Controller - a1d2 C620 Series Chipset Family SSATA Controller [AHCI mode] - a1d6 C620 Series Chipset Family SSATA Controller [RAID mode] - a1e7 C620 Series Chipset Family PCI Express Root Port #17 - a1e8 C620 Series Chipset Family PCI Express Root Port #18 - a1e9 C620 Series Chipset Family PCI Express Root Port #19 - a1ea C620 Series Chipset Family PCI Express Root Port #20 + a1c3 C624 Chipset LPC/eSPI Controller + a1c4 C625 Chipset LPC/eSPI Controller + a1c5 C626 Chipset LPC/eSPI Controller + a1c6 C627 Chipset LPC/eSPI Controller + a1c7 C628 Chipset LPC/eSPI Controller + a1ca C629 Chipset LPC/eSPI Controller + a1d2 C620 Series Chipset Family SSATA Controller (AHCI) + a1d6 C620 Series Chipset Family SSATA Controller (RAID 0/1/5/10) + a1e7 C620 Series Chipset Family PCIe Root Port #16 + a1e8 C620 Series Chipset Family PCIe Root Port #17 + a1e9 C620 Series Chipset Family PCIe Root Port #18 + a1ea C620 Series Chipset Family PCIe Root Port #19 a1ec C620 Series Chipset Family MROM 0 a1ed C620 Series Chipset Family MROM 1 - a1f0 C62x HD Audio Controller - a1f8 Lewisburg IE: HECI #1 - a1f9 Lewisburg IE: HECI #2 - a1fa Lewisburg IE: IDE-r - a1fb Lewisburg IE: KT Controller - a1fc Lewisburg IE: HECI #3 - a202 Lewisburg SATA Controller [AHCI mode] - a206 Lewisburg SATA Controller [RAID mode] - a210 Lewisburg PCI Express Root Port - a211 Lewisburg PCI Express Root Port - a212 Lewisburg PCI Express Root Port - a213 Lewisburg PCI Express Root Port - a214 Lewisburg PCI Express Root Port - a215 Lewisburg PCI Express Root Port - a216 Lewisburg PCI Express Root Port - a217 Lewisburg PCI Express Root Port - a218 Lewisburg PCI Express Root Port - a219 Lewisburg PCI Express Root Port - a21a Lewisburg PCI Express Root Port - a21b Lewisburg PCI Express Root Port - a21c Lewisburg PCI Express Root Port - a21d Lewisburg PCI Express Root Port - a21e Lewisburg PCI Express Root Port - a21f Lewisburg PCI Express Root Port - a221 Lewisburg Power Management Controller - a223 Lewisburg SMBus - a224 Lewisburg SPI Controller - a242 Lewisburg LPC or eSPI Controller - a243 Lewisburg LPC or eSPI Controller - a252 Lewisburg SSATA Controller [AHCI mode] - a256 Lewisburg SSATA Controller [RAID mode] - a267 Lewisburg PCI Express Root Port - a268 Lewisburg PCI Express Root Port - a269 Lewisburg PCI Express Root Port - a26a Lewisburg PCI Express Root Port - a282 200 Series PCH SATA controller [AHCI mode] + a1f0 C620 Series Chipset Family HD Audio + a1f8 C620 Series Chipset Family IE HECI #1 + a1f9 C620 Series Chipset Family IE HECI #2 + a1fa C620 Series Chipset Family IE IDE Redirection + a1fb C620 Series Chipset Family IE Keyboard and Text Redirection + a1fc C620 Series Chipset Family IE HECI #3 + a202 C620 Series Chipset Family (Super) SATA Controller (AHCI) + a206 C620 Series Chipset Family (Super) SATA Controller (RAID 0/1/5/10) + a210 C620 Series Chipset Family (Super) PCIe Root Port #0 + a211 C620 Series Chipset Family (Super) PCIe Root Port #1 + a212 C620 Series Chipset Family (Super) PCIe Root Port #2 + a213 C620 Series Chipset Family (Super) PCIe Root Port #3 + a214 C620 Series Chipset Family (Super) PCIe Root Port #4 + a215 C620 Series Chipset Family (Super) PCIe Root Port #5 + a216 C620 Series Chipset Family (Super) PCIe Root Port #6 + a217 C620 Series Chipset Family (Super) PCIe Root Port #7 + a218 C620 Series Chipset Family (Super) PCIe Root Port #8 + a219 C620 Series Chipset Family (Super) PCIe Root Port #9 + a21a C620 Series Chipset Family (Super) PCIe Root Port #10 + a21b C620 Series Chipset Family (Super) PCIe Root Port #11 + a21c C620 Series Chipset Family (Super) PCIe Root Port #12 + a21d C620 Series Chipset Family (Super) PCIe Root Port #13 + a21e C620 Series Chipset Family (Super) PCIe Root Port #14 + a21f C620 Series Chipset Family (Super) PCIe Root Port #15 + a220 C620 Series Chipset Family (Super) P2SB + a221 C620 Series Chipset Family (Super) PMC + a223 C620 Series Chipset Family (Super) SMBus + a224 C620 Series Chipset Family (Super) SPI Controller + a226 C620 Series Chipset Family (Super) Trace Hub + a22f C620 Series Chipset Family (Super) USB 3.0 xHCI Controller + a231 C620 Series Chipset Family (Super) Thermal Subsystem + a23a C620 Series Chipset Family (Super) MEI HECI #1 + a23b C620 Series Chipset Family (Super) MEI HECI #2 + a23c C620 Series Chipset Family (Super) MEI IDE Redirection + a23d C620 Series Chipset Family (Super) MEI Keyboard and Text (KT) Redirection + a23e C620 Series Chipset Family (Super) MEI HECI #3 + a242 C624 Chipset (Super) LPC/eSPI Controller + a243 C627 Chipset (Super) LPC/eSPI Controller + a244 C621 Chipset (Super) LPC/eSPI Controller + a245 C627 Chipset (Super) LPC/eSPI Controller + a246 C628 Chipset (Super) LPC/eSPI Controller + a252 C620 Series Chipset Family (Super) SSATA Controller (AHCI) + a256 C620 Series Chipset Family (Super) SSATA Controller (RAID 0/1/5/10) + a267 C620 Series Chipset Family (Super) PCIe Root Port #16 + a268 C620 Series Chipset Family (Super) PCIe Root Port #17 + a269 C620 Series Chipset Family (Super) PCIe Root Port #18 + a26a C620 Series Chipset Family (Super) PCIe Root Port #19 + a26c C620 Series Chipset Family (Super) MROM 0 + a270 C620 Series Chipset Family (Super) Audio + a278 C620 Series Chipset Family (Super) IE HECI #1 + a279 C620 Series Chipset Family (Super) IE HECI #2 + a27a C620 Series Chipset Family (Super) IE IDE Redirection + a27b C620 Series Chipset Family (Super) IE Keyboard and Text Redirection + a27c C620 Series Chipset Family (Super) IE HECI #3 + a282 200 Series/Z370 Chipset Family SATA Controller (AHCI) 1462 7a72 H270 PC MATE - a286 200 Series PCH SATA controller [RAID mode] - a290 200 Series PCH PCI Express Root Port #1 - a291 200 Series PCH PCI Express Root Port #2 - a292 200 Series PCH PCI Express Root Port #3 - a293 200 Series PCH PCI Express Root Port #4 - a294 200 Series PCH PCI Express Root Port #5 + a286 200 Series/Z370 Chipset Family SATA Controller (RAID) Premium + a28e 200 Series/Z370 Chipset Family SATA Controller (RST and Optane Technology) + a290 200 Series/Z370 Chipset Family PCIe Root Port #1 + a291 200 Series/Z370 Chipset Family PCIe Root Port #2 + a292 200 Series/Z370 Chipset Family PCIe Root Port #3 + a293 200 Series/Z370 Chipset Family PCIe Root Port #4 + a294 200 Series/Z370 Chipset Family PCIe Root Port #5 1462 7a72 H270 PC MATE - a295 200 Series PCH PCI Express Root Port #6 - a296 200 Series PCH PCI Express Root Port #7 + a295 200 Series/Z370 Chipset Family PCIe Root Port #6 + a296 200 Series/Z370 Chipset Family PCIe Root Port #7 1462 7a72 H270 PC MATE - a297 200 Series PCH PCI Express Root Port #8 - a298 200 Series PCH PCI Express Root Port #9 + a297 200 Series/Z370 Chipset Family PCIe Root Port #8 + a298 200 Series/Z370 Chipset Family PCIe Root Port #9 1462 7a72 H270 PC MATE - a299 200 Series PCH PCI Express Root Port #10 - a29a 200 Series PCH PCI Express Root Port #11 - a29b 200 Series PCH PCI Express Root Port #12 - a29c 200 Series PCH PCI Express Root Port #13 - a29d 200 Series PCH PCI Express Root Port #14 - a29e 200 Series PCH PCI Express Root Port #15 - a29f 200 Series PCH PCI Express Root Port #16 + a299 200 Series/Z370 Chipset Family PCIe Root Port #10 + a29a 200 Series/Z370 Chipset Family PCIe Root Port #11 + a29b 200 Series/Z370 Chipset Family PCIe Root Port #12 + a29c 200 Series/Z370 Chipset Family PCIe Root Port #13 + a29d 200 Series/Z370 Chipset Family PCIe Root Port #14 + a29e 200 Series/Z370 Chipset Family PCIe Root Port #15 + a29f 200 Series/Z370 Chipset Family PCIe Root Port #16 a2a0 200 Series/Z370 Chipset Family P2SB - a2a1 200 Series/Z370 Chipset Family Power Management Controller + a2a1 200 Series/Z370 Chipset Family PMC 1462 7a72 H270 PC MATE - a2a3 200 Series/Z370 Chipset Family SMBus Controller + a2a3 200 Series/Z370 Chipset Family SMBus 1462 7a72 H270 PC MATE a2a4 200 Series/Z370 Chipset Family SPI Controller - a2a5 200 Series/Z370 Chipset Family Gigabit Ethernet Controller + a2a5 200 Series/Z370 Chipset Family GbE Controller a2a6 200 Series/Z370 Chipset Family Trace Hub - a2a7 200 Series/Z370 Chipset Family Serial IO UART Controller #0 - a2a8 200 Series/Z370 Chipset Family Serial IO UART Controller #1 - a2a9 200 Series/Z370 Chipset Family Serial IO SPI Controller #0 - a2aa 200 Series/Z370 Chipset Family Serial IO SPI Controller #1 + a2a7 200 Series/Z370 Chipset Family UART #0 + a2a8 200 Series/Z370 Chipset Family UART #1 + a2a9 200 Series/Z370 Chipset Family GSPI #0 + a2aa 200 Series/Z370 Chipset Family GSPI #1 a2af 200 Series/Z370 Chipset Family USB 3.0 xHCI Controller 1462 7a72 H270 PC MATE - a2b1 200 Series PCH Thermal Subsystem + a2b0 200 Series/Z370 Chipset Family USB Device Controller (OTG) + a2b1 200 Series/Z370 Chipset Family Thermal Subsystem 1462 7a72 H270 PC MATE - a2ba 200 Series PCH CSME HECI #1 + a2b5 200 Series/Z370 Chipset Family ISH + a2ba 200 Series/Z370 Chipset Family MEI #1 1462 7a72 H270 PC MATE - a2bb 200 Series PCH CSME HECI #2 -# AMT serial over LAN - a2bd 200 Series Chipset Family KT Redirection - a2c4 200 Series PCH LPC Controller (H270) + a2bb 200 Series/Z370 Chipset Family MEI #2 + a2bc 200 Series/Z370 Chipset Family IDE Redirection + a2bd 200 Series/Z370 Chipset Family Keyboard and Text (KT) Redirection + a2be 200 Series/Z370 Chipset Family MEI #3 + a2c4 H270 Chipset LPC/eSPI Controller 1462 7a72 H270 PC MATE - a2c5 200 Series PCH LPC Controller (Z270) - a2c6 200 Series PCH LPC Controller (Q270) - a2c7 200 Series PCH LPC Controller (Q250) - a2c8 200 Series PCH LPC Controller (B250) + a2c5 Z270 Chipset LPC/eSPI Controller + a2c6 Q270 Chipset LPC/eSPI Controller + a2c7 Q250 Chipset LPC/eSPI Controller + a2c8 B250 Chipset LPC/eSPI Controller a2c9 Z370 Chipset LPC/eSPI Controller a2d2 X299 Chipset LPC/eSPI Controller a2d3 C422 Chipset LPC/eSPI Controller - a2e0 200 Series PCH Serial IO I2C Controller #0 - a2e1 200 Series PCH Serial IO I2C Controller #1 - a2e2 200 Series PCH Serial IO I2C Controller #2 - a2e3 200 Series PCH Serial IO I2C Controller #3 - a2e6 200 Series PCH Serial IO UART Controller #2 - a2e7 200 Series PCH PCI Express Root Port #17 - a2e8 200 Series PCH PCI Express Root Port #18 - a2e9 200 Series PCH PCI Express Root Port #19 - a2ea 200 Series PCH PCI Express Root Port #20 - a2eb 200 Series PCH PCI Express Root Port #21 - a2ec 200 Series PCH PCI Express Root Port #22 - a2ed 200 Series PCH PCI Express Root Port #23 - a2ee 200 Series PCH PCI Express Root Port #24 - a2f0 200 Series PCH HD Audio + a2e0 200 Series/Z370 Chipset Family I2C #0 + a2e1 200 Series/Z370 Chipset Family I2C #1 + a2e2 200 Series/Z370 Chipset Family I2C #2 + a2e3 200 Series/Z370 Chipset Family I2C #3 + a2e6 200 Series/Z370 Chipset Family UART #2 + a2e7 200 Series/Z370 Chipset Family PCIe Root Port #17 + a2e8 200 Series/Z370 Chipset Family PCIe Root Port #18 + a2e9 200 Series/Z370 Chipset Family PCIe Root Port #19 + a2ea 200 Series/Z370 Chipset Family PCIe Root Port #20 + a2eb 200 Series/Z370 Chipset Family PCIe Root Port #21 + a2ec 200 Series/Z370 Chipset Family PCIe Root Port #22 + a2ed 200 Series/Z370 Chipset Family PCIe Root Port #23 + a2ee 200 Series/Z370 Chipset Family PCIe Root Port #24 + a2f0 200 Series/Z370 Chipset Family HD Audio 1462 7a72 H270 PC MATE 1462 fa72 H270 PC MATE a303 H310 Chipset LPC/eSPI Controller @@ -39780,71 +40778,137 @@ a30d HM370 Chipset LPC/eSPI Controller a30e CM246 Chipset LPC/eSPI Controller a313 Cannon Lake LPC/eSPI Controller - a323 Cannon Lake PCH SMBus Controller + a320 300/C240 Series Chipset Family P2SB + a321 300/C240 Series Chipset Family PMC + a323 300/C240 Series Chipset Family SMBus 1028 0869 Vostro 3470 - a324 Cannon Lake PCH SPI Controller + a324 300/C240 Series Chipset Family SPI (Flash) Controller 1028 0869 Vostro 3470 - a328 Cannon Lake PCH Serial IO UART Host Controller - a32b Cannon Lake PCH SPI Host Controller - a32c Cannon Lake PCH PCI Express Root Port #21 - a32d Cannon Lake PCH PCI Express Root Port #22 - a32e Cannon Lake PCH PCI Express Root Port #23 - a32f Cannon Lake PCH PCI Express Root Port #24 - a330 Cannon Lake PCH PCI Express Root Port #9 - a331 Cannon Lake PCH PCI Express Root Port #10 - a332 Cannon Lake PCH PCI Express Root Port #11 - a333 Cannon Lake PCH PCI Express Root Port #12 - a334 Cannon Lake PCH PCI Express Root Port #13 - a335 Cannon Lake PCH PCI Express Root Port #14 - a336 Cannon Lake PCH PCI Express Root Port #15 - a337 Cannon Lake PCH PCI Express Root Port #16 - a338 Cannon Lake PCH PCI Express Root Port #1 - a339 Cannon Lake PCH PCI Express Root Port #2 - a33a Cannon Lake PCH PCI Express Root Port #3 - a33b Cannon Lake PCH PCI Express Root Port #4 - a33c Cannon Lake PCH PCI Express Root Port #5 - a33d Cannon Lake PCH PCI Express Root Port #6 - a33e Cannon Lake PCH PCI Express Root Port #7 - a33f Cannon Lake PCH PCI Express Root Port #8 - a340 Cannon Lake PCH PCI Express Root Port #17 - a341 Cannon Lake PCH PCI Express Root Port #18 - a342 Cannon Lake PCH PCI Express Root Port #19 - a343 Cannon Lake PCH PCI Express Root Port #20 - a348 Cannon Lake PCH cAVS + a326 300/C240 Series Chipset Family Trace Hub + a328 300/C240 Series Chipset Family UART #0 + a329 300/C240 Series Chipset Family UART #1 + a32a 300/C240 Series Chipset Family GSPI #0 + a32b 300/C240 Series Chipset Family GSPI #1 + a32c 300/C240 Series Chipset Family PCIe Root Port #21 + a32d 300/C240 Series Chipset Family PCIe Root Port #22 + a32e 300/C240 Series Chipset Family PCIe Root Port #23 + a32f 300/C240 Series Chipset Family PCIe Root Port #24 + a330 300/C240 Series Chipset Family PCIe Root Port #9 + a331 300/C240 Series Chipset Family PCIe Root Port #10 + a332 300/C240 Series Chipset Family PCIe Root Port #11 + a333 300/C240 Series Chipset Family PCIe Root Port #12 + a334 300/C240 Series Chipset Family PCIe Root Port #13 + a335 300/C240 Series Chipset Family PCIe Root Port #14 + a336 300/C240 Series Chipset Family PCIe Root Port #15 + a337 300/C240 Series Chipset Family PCIe Root Port #16 + a338 300/C240 Series Chipset Family PCIe Root Port #1 + a339 300/C240 Series Chipset Family PCIe Root Port #2 + a33a 300/C240 Series Chipset Family PCIe Root Port #3 + a33b 300/C240 Series Chipset Family PCIe Root Port #4 + a33c 300/C240 Series Chipset Family PCIe Root Port #5 + a33d 300/C240 Series Chipset Family PCIe Root Port #6 + a33e 300/C240 Series Chipset Family PCIe Root Port #7 + a33f 300/C240 Series Chipset Family PCIe Root Port #8 + a340 300/C240 Series Chipset Family PCIe Root Port #17 + a341 300/C240 Series Chipset Family PCIe Root Port #18 + a342 300/C240 Series Chipset Family PCIe Root Port #19 + a343 300/C240 Series Chipset Family PCIe Root Port #20 + a347 300/C240 Series Chipset Family UART #2 + a348 300/C240 Series Chipset Family HD Audio 1028 0869 Vostro 3470 - a352 Cannon Lake PCH SATA AHCI Controller + a352 300/C240 Series Chipset Family SATA Controller (AHCI) 1028 0869 Vostro 3470 - a353 Cannon Lake Mobile PCH SATA AHCI Controller - a360 Cannon Lake PCH HECI Controller + a353 300/C240 Series Chipset Family SATA Controller (AHCI) + a355 300/C240 Series Chipset Family SATA Controller (RAID 0/1/5/10) + a356 300/C240 Series Chipset Family SATA Controller (RAID 0/1/5/10) + a357 300/C240 Series Chipset Family SATA Controller (RAID 0/1/5/10) + a35e 300/C240 Series Chipset Family SATA Controller Optane Memory + a360 300/C240 Series Chipset Family HECI #1 1028 0869 Vostro 3470 - a363 Cannon Lake PCH Active Management Technology - SOL - a364 Cannon Lake PCH HECI Controller #2 - a368 Cannon Lake PCH Serial IO I2C Controller #0 - a369 Cannon Lake PCH Serial IO I2C Controller #1 - a36a Cannon Lake PCH Serial IO I2C Controller #2 - a36b Cannon Lake PCH Serial IO I2C Controller #3 - a36d Cannon Lake PCH USB 3.1 xHCI Host Controller + a361 300/C240 Series Chipset Family HECI #2 + a362 300/C240 Series Chipset Family IDE Redirection (IDER-R) + a363 300/C240 Series Chipset Family Keyboard and Text (KT) Redirection + a364 300/C240 Series Chipset Family HECI #3 + a365 300/C240 Series Chipset Family HECI #4 + a368 300/C240 Series Chipset Family I2C Controller #0 + a369 300/C240 Series Chipset Family I2C Controller #1 + a36a 300/C240 Series Chipset Family I2C Controller #2 + a36b 300/C240 Series Chipset Family I2C Controller #3 + a36d 300/C240 Series Chipset Family USB 3.1 xHCI 1028 0869 Vostro 3470 - a36f Cannon Lake PCH Shared SRAM - a370 Cannon Lake PCH CNVi WiFi + a36e 300/C240 Series Chipset Family USB Device Controller (Dual Role) + a36f 300/C240 Series Chipset Family Shared SRAM + a370 300/C240 Series Chipset Family CNVi Wi-Fi 1a56 1552 Killer(R) Wireless-AC 1550i Wireless Network Adapter (9560NGW) 8086 0034 Wireless-AC 9560 - a379 Cannon Lake PCH Thermal Controller + a371 300/C240 Series Chipset Family CNVi Wi-Fi + a372 300/C240 Series Chipset Family CNVi Wi-Fi + a373 300/C240 Series Chipset Family CNVi Wi-Fi + a379 300/C240 Series Chipset Family Thermal Subsystem 1028 0869 Vostro 3470 - a382 400 Series Chipset Family SATA AHCI Controller - a394 Comet Lake PCI Express Root Port #05 - a397 Comet Lake PCI Express Root Port #08 - a398 Comet Lake PCI Express Root Port 9 - a39a Comet Lake PCI Express Root Port 11 - a3a1 Cannon Lake PCH Power Management Controller - a3a3 Comet Lake PCH-V SMBus Host Controller - a3af Comet Lake PCH-V USB Controller - a3b1 Comet Lake PCH-V Thermal Subsystem - a3ba Comet Lake PCH-V HECI Controller + a37b 300/C240 Series Chipset Family SPI #2 + a37c 300/C240 Series Chipset Family Integrated Sensor Hub + a382 B460/H410 Chipset SATA Controller (AHCI) + a384 B460/H410 Chipset SATA Controller (RAID 0/1/5/10) Not Premium + a386 B460/H410 Chipset SATA Controller (RAID 0/1/5/10) Premium + a38e B460/H410 Chipset SATA Controller (RST Optane) + a390 B460/H410 Chipset PCIe Root Port #1 + a391 B460/H410 Chipset PCIe Root Port #2 + a392 B460/H410 Chipset PCIe Root Port #3 + a393 B460/H410 Chipset PCIe Root Port #4 + a394 B460/H410 Chipset PCIe Root Port #5 + a395 B460/H410 Chipset PCIe Root Port #6 + a396 B460/H410 Chipset PCIe Root Port #7 + a397 B460/H410 Chipset PCIe Root Port #8 + a398 B460/H410 Chipset PCIe Root Port #9 + a399 B460/H410 Chipset PCIe Root Port #10 + a39a B460/H410 Chipset PCIe Root Port #11 + a39b B460/H410 Chipset PCIe Root Port #12 + a39c B460/H410 Chipset PCIe Root Port #13 + a39d B460/H410 Chipset PCIe Root Port #14 + a39e B460/H410 Chipset PCIe Root Port #15 + a39f B460/H410 Chipset PCIe Root Port #16 + a3a0 B460/H410 Chipset P2SB + a3a1 B460/H410 Chipset PMC + a3a3 B460/H410 Chipset SMBus + a3a4 B460/H410 Chipset SPI Controller + a3a6 B460/H410 Chipset Trace Hub + a3a7 B460/H410 Chipset UART #0 + a3a8 B460/H410 Chipset UART #1 + a3a9 B460/H410 Chipset SPI #0 + a3aa B460/H410 Chipset SPI #1 + a3af B460/H410 Chipset USB 3.2 Gen 1x1 (5 Gbs) xHCI Controller + a3b0 B460/H410 Chipset USB Device Controller + a3b1 B460/H410 Chipset Thermal Subsystem + a3b5 B460/H410 Chipset ISH + a3ba B460/H410 Chipset CSME HECI #1 + a3bb B460/H410 Chipset CSME HECI #2 + a3bc B460/H410 Chipset CSME IDE Redirection + a3bd B460/H410 Chipset CSME Keyboard and Text (KT) Redirection + a3be B460/H410 Chipset CSME HECI #3 a3c8 B460 Chipset LPC/eSPI Controller a3da H410 Chipset LPC/eSPI Controller - a3eb Comet Lake PCI Express Root Port #21 - a3f0 Comet Lake PCH-V cAVS + a3e0 B460/H410 Chipset I2C #0 + a3e1 B460/H410 Chipset I2C #1 + a3e2 B460/H410 Chipset I2C #2 + a3e3 B460/H410 Chipset I2C #3 + a3e6 B460/H410 Chipset UART #2 + a3e7 B460/H410 Chipset PCIe Root Port #17 + a3e8 B460/H410 Chipset PCIe Root Port #18 + a3e9 B460/H410 Chipset PCIe Root Port #19 + a3ea B460/H410 Chipset PCIe Root Port #20 + a3eb B460/H410 Chipset PCIe Root Port #21 + a3ec B460/H410 Chipset PCIe Root Port #22 + a3ed B460/H410 Chipset PCIe Root Port #23 + a3ee B460/H410 Chipset PCIe Root Port #24 + a3f0 B460/H410 Chipset HD Audio + a3f1 B460/H410 Chipset HD Audio + a3f2 B460/H410 Chipset HD Audio + a3f3 B460/H410 Chipset HD Audio + a3f4 B460/H410 Chipset HD Audio + a3f5 B460/H410 Chipset HD Audio + a3f6 B460/H410 Chipset HD Audio + a3f7 B460/H410 Chipset HD Audio a620 6400/6402 Advanced Memory Buffer (AMB) a700 Raptor Lake-S Host Bridge/DRAM Controller a703 Raptor Lake-S Host Bridge/DRAM Controller @@ -39891,55 +40955,91 @@ a7ac Raptor Lake-U [Intel Graphics] a7ad Raptor Lake-U [Intel Graphics] a806 Lunar Lake-M LPC/eSPI Controller + a807 Core Ultra 200V Series Processors eSPI Controller + a820 Core Ultra 200V Series Processors P2SB (8 bit) + a821 Core Ultra 200V Series Processors PMC a822 Lunar Lake-M SMbus Controller - a823 Lunar Lake-M SPI Controller - a824 Lunar Lake-M Trace Hub - a825 Lunar Lake-M Serial IO UART Controller #0 - a826 Lunar Lake-M Serial IO UART Controller #1 - a827 Lunar Lake-M Serial IO SPI Controller #0 - a828 Lunar Lake-M HD Audio Controller - a830 Lunar Lake-M Serial IO SPI Controller #1 - a831 Lunar Lake-M Thunderbolt 4 USB Controller - a833 Lunar Lake-M Thunderbolt 4 NHI #0 - a834 Lunar Lake-M Thunderbolt 4 NHI #1 - a838 Lunar Lake-M PCI Express Root Port #1 - a839 Lunar Lake-M PCI Express Root Port #2 - a83a Lunar Lake-M PCI Express Root Port #3 - a83b Lunar Lake-M PCI Express Root Port #4 - a83c Lunar Lake-M PCI Express Root Port #5 - a83d Lunar Lake-M PCI Express Root Port #6 - a840 BE201 320MHz - a845 Lunar Lake-M Integrated Sensor Hub + a823 Core Ultra 200V Series Processors SPI (flash) Controller + a824 Core Ultra 200V Series Processors Trace Hub + a825 Core Ultra 200V Series Processors UART #0 + a826 Core Ultra 200V Series Processors UART #1 + a827 Core Ultra 200V Series Processors GSPI #0 + a828 Core Ultra 200V Series Processors HD Audio + a830 Core Ultra 200V Series Processors GSPI #1 + a831 Core Ultra 200V Series Processors Type-C Subsystem xHCI + a833 Core Ultra 200V Series Processors Thunderbolt DMA0 + a834 Core Ultra 200V Series Processors Thunderbolt DMA1 + a838 Core Ultra 200V Series Processors PCIe Root Port #1 + a839 Core Ultra 200V Series Processors PCIe Root Port #2 + a83a Core Ultra 200V Series Processors PCIe Root Port #3 + a83b Core Ultra 200V Series Processors PCIe Root Port #4 + a83c Core Ultra 200V Series Processors PCIe Root Port #5 + a83d Core Ultra 200V Series Processors PCIe Root Port #6 + a840 BE200 Series Wi-Fi 7 + a845 Core Ultra 200V Series Processors Integrated Sensor Hub (ISH) + a846 Core Ultra 200V Series Processors GSPI #2 a847 Lunar Lake-M UFS Controller - a848 Lunar Lake-M Touch Host Controller #0 ID1 - a849 Lunar Lake-M Touch Host Controller #0 ID2 - a84a Lunar Lake-M Touch Host Controller #1 ID1 - a84b Lunar Lake-M Touch Host Controller #1 ID2 - a84e Lunar Lake-M Thunderbolt 4 PCI Express Root Port #0 - a84f Lunar Lake-M Thunderbolt 4 PCI Express Root Port #1 - a860 Lunar Lake-M Thunderbolt 4 PCI Express Root Port #2 - a870 Lunar Lake-M CSME HECI #1 - a873 Lunar Lake-M Keyboard and Text (KT) Redirection - a878 Lunar Lake-M Serial IO I2C Controller #0 - a879 Lunar Lake-M Serial IO I2C Controller #1 - a87a Lunar Lake-M Serial IO I2C Controller #2 - a87b Lunar Lake-M Serial IO I2C Controller #3 - a87d Lunar Lake-M USB 3.2 Gen 2x1 xHCI Host Controller - a87f Lunar Lake-M Shared SRAM + a848 Core Ultra 200V Series Processors Touch Host Controller (THC) #0 ID1 + a849 Core Ultra 200V Series Processors Touch Host Controller (THC) #0 ID2 + a84a Core Ultra 200V Series Processors Touch Host Controller (THC) #1 ID1 + a84b Core Ultra 200V Series Processors Touch Host Controller (THC) #1 ID2 + a84c Core Ultra 200V Series Processors P2SB (16 bit) + a84e Core Ultra 200V Series Processors USB Type-C Subsystem PCIe Root Port #21 + a84f Core Ultra 200V Series Processors USB Type-C Subsystem PCIe Root Port #22 + a850 Core Ultra 200V Series Processors I2C #4 + a851 Core Ultra 200V Series Processors I2C #5 + a852 Core Ultra 200V Series Processors UART #2 + a85d Core Ultra 200V Series Processors CSME HECI #1 + a85e Core Ultra 200V Series Processors CSME HECI #2 + a85f Core Ultra 200V Series Processors CSME HECI #3 + a860 Core Ultra 200V Series Processors USB Type-C Subsystem PCIe Root Port #23 + a862 Core Ultra 200V Series Processors CSME HECI #1 + a863 Core Ultra 200V Series Processors CSME HECI #2 + a864 Core Ultra 200V Series Processors CSME HECI #3 + a870 Core Ultra 200V Series Processors CSME HECI #1 (CSE) + a871 Core Ultra 200V Series Processors CSME HECI #2 (CSE) + a872 Core Ultra 200V Series Processors CSME IDE Redirection (IDE-R) + a873 Core Ultra 200V Series Processors CSME Keyboard and Text (KT) Redirection + a874 Core Ultra 200V Series Processors CSME HECI #3 (CSE) + a875 Core Ultra 200V Series Processors CSME HECI #4 (CSE) + a877 Core Ultra 200V Series Processors I3C #2 + a878 Core Ultra 200V Series Processors I2C #0 + a879 Core Ultra 200V Series Processors I2C #1 + a87a Core Ultra 200V Series Processors I2C #2 + a87b Core Ultra 200V Series Processors I2C #3 + a87c Core Ultra 200V Series Processors I3C #1 + a87d Core Ultra 200V Series Processors Standalone xHCI Controller + a87f Core Ultra 200V Series Processors Shared SRAM abc0 Omni-Path Fabric Switch Silicon 100 Series - ad0b Volume Management Device NVMe RAID Controller Intel Corporation - ad0d Arrow Lake-HX Crash Log & Telemetry - ad1d Arrow Lake NPU + ad03 Core Ultra 200 Series Processors Dynamic Tuning Technology (DTT) + ad0b Core Ultra 200 Series Processors VMD + ad0d Core Ultra 200 Series Processors Crash Log and Telemetry + ad1d Core Ultra 200 Series Processors NPU ae10 Arrow Lake-HX Direct eSPI Controller - ae23 Arrow Lake-HX SPI (flash) Controller - ae4c Arrow Lake-HX Gauss Newton Algorithm (GNA) - ae4d Arrow Lake-HX PCIe Root Port #13 - ae7f Arrow Lake-HX Shared SRAM (SOC-S) - b002 Panther Lake Host Bridge/DRAM Controller - b01d Panther Lake Innovation Platform Framework Processor Participant - b03e Panther Lake NPU - b05d Panther Lake IPU - b07d Panther Lake Platform Monitoring Technology (PMT) + ae20 Core Ultra 200 Series Processors P2SB (SOC-S) + ae21 Core Ultra 200 Series Processors PMC (SOC-S) + ae22 Core Ultra 200 Series Processors SMBus + ae23 Core Ultra 200 Series Processors SPI (flash) Controller + ae24 Core Ultra 200 Series Processors Trace Hub + ae4c Core Ultra 200 Series Processors Gauss Newton Algorithm (GNA) + ae4d Core Ultra 200 Series Processors PCIe Root Port #13 + ae4e Core Ultra 200 Series Processors PCIe Root Port #14 + ae4f Core Ultra 200 Series Processors PCIe Root Port #15 + ae70 Core Ultra 200 Series Processors CSME HECI #1 + ae71 Core Ultra 200 Series Processors CSME HECI #2 + ae74 Core Ultra 200 Series Processors CSME HECI #3 + ae7f Core Ultra 200 Series Processors Shared SRAM (SOC-S) + b000 Core Ultra Processors (Series 3) PTL-404 + b001 Core Ultra Processors (Series 3) PTL-H12Xe + b002 Core Ultra Processors (Series 3) PTL-H484 + b003 Core Ultra Processors (Series 3) PTL-204 + b004 Core Ultra Processors (Series 3) PTL-H444 + b005 Core Ultra Processors (Series 3) PTL-H444 + b01d Core Ultra Processors (Series 3) DTT + b02d Core Ultra Processors (Series 3) IAA + b03e Core Ultra Processors (Series 3) NPU + b05d Core Ultra Processors (Series 3) IPU + b07d Core Ultra Processors (Series 3) Crashlog and Telemetry b080 Panther Lake [Arc B390] b081 Panther Lake [Arc B370] b082 Panther Lake [Arc B390] @@ -39990,6 +41090,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 +41119,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] @@ -40012,54 +41139,169 @@ e216 Battlemage G21 [Intel Graphics] e220 Battlemage G31 [Intel Graphics] e221 Battlemage G31 [Intel Graphics] - e222 Battlemage G31 [Intel Graphics] - e223 Battlemage G31 [Intel Graphics] - e302 Panther Lake LPC/eSPI Controller - e322 Panther Lake SMBus Controller - e323 Panther Lake SPI(flash) Controller - e325 Panther Lake Serial IO UART Controller #0 - e326 Panther Lake Serial IO UART Controller #1 - e327 Panther Lake Serial IO SPI Host Controller #0 - e328 Panther Lake Smart Sound Technology BUS - e330 Panther Lake Serial IO SPI Host Controller #1 - e331 Panther Lake TCSS USB3 xHCI Controller - e333 Panther Lake Thunderbolt 4 NHI #0 - e334 Panther Lake Thunderbolt 4 NHI #1 - e338 Panther Lake PCI Express Root Port A1 - e339 Panther Lake PCI Express Root Port A2 - e33a Panther Lake PCI Express Root Port A3 - e33b Panther Lake PCI Express Root Port A4 - e33c Panther Lake PCI Express Root Port B1 - e33d Panther Lake PCI Express Root Port B2 - e33e Panther Lake PCI Express Root Port B3 - e33f Panther Lake PCI Express Root Port B4 - e340 Panther Lake PCH CNVi WiFi + e222 Battlemage G31 [Arc Pro B65] + e223 Battlemage G31 [Arc Pro B70] + e300 Core Ultra Processors (Series 3) eSPI + e302 Core Ultra Processors (Series 3) eSPI + e31f Core Ultra Processors (Series 3) eSPI + e320 Core Ultra Processors (Series 3) P2SB + e321 Core Ultra Processors (Series 3) PMC + e322 Core Ultra Processors (Series 3) SMBus + e323 Core Ultra Processors (Series 3) SPI (flash) Controller + e324 Core Ultra Processors (Series 3) Trace Hub + e325 Core Ultra Processors (Series 3) UART #0 + e326 Core Ultra Processors (Series 3) UART #1 + e327 Core Ultra Processors (Series 3) GSPI #0 + e328 Core Ultra Processors (Series 3) HD Audio + e329 Core Ultra Processors (Series 3) HD Audio + e32a Core Ultra Processors (Series 3) HD Audio + e32b Core Ultra Processors (Series 3) HD Audio + e32c Core Ultra Processors (Series 3) HD Audio + e32d Core Ultra Processors (Series 3) HD Audio + e32e Core Ultra Processors (Series 3) HD Audio + e32f Core Ultra Processors (Series 3) HD Audio + e330 Core Ultra Processors (Series 3) GSPI #1 + e331 Core Ultra Processors (Series 3) Type-C Subsystem xHCI + e332 Core Ultra Processors (Series 3) Type-C Subsystem xDCI + e333 Core Ultra Processors (Series 3) Thunderbolt DMA0 + e334 Core Ultra Processors (Series 3) Thunderbolt DMA1 + e337 Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #24 + e338 Core Ultra Processors (Series 3) PCIe Root Port #1 + e339 Core Ultra Processors (Series 3) PCIe Root Port #2 + e33a Core Ultra Processors (Series 3) PCIe Root Port #3 + e33b Core Ultra Processors (Series 3) PCIe Root Port #4 + e33c Core Ultra Processors (Series 3) PCIe Root Port #5 + e33d Core Ultra Processors (Series 3) PCIe Root Port #6 + e33e Core Ultra Processors (Series 3) PCIe Root Port #7 + e33f Core Ultra Processors (Series 3) PCIe Root Port #8 + e340 Core Ultra Processors (Series 3) CNVi Wi-Fi 8086 0094 Wi-Fi 6E AX211 160MHz - e34e Panther Lake Thunderbolt 4 PCI Express Root Port #0 - e35d Panther Lake CSME HECI #1 - e360 Panther Lake Thunderbolt 4 PCI Express Root Port #2 - e361 Panther Lake PCI Express Root Port C1 - e362 Panther Lake Primary to Sideband (P2SB) Bridge IOE - e363 Panther Lake PCI Express Root Port C3 - e364 Panther Lake PCI Express Root Port C4 - e365 Panther Lake PCI Express Root Port D1 - e366 Panther Lake PCI Express Root Port D2 + e341 Core Ultra Processors (Series 3) CNVi Wi-Fi + e342 Core Ultra Processors (Series 3) CNVi Wi-Fi + e343 Core Ultra Processors (Series 3) CNVi Wi-Fi + e344 Core Ultra Processors (Series 3) IEH #0 + e345 Core Ultra Processors (Series 3) ISH + e346 Core Ultra Processors (Series 3) GSPI #2 + e348 Core Ultra Processors (Series 3) THC #0 ID1 + e349 Core Ultra Processors (Series 3) THC #0 ID2 + e34a Core Ultra Processors (Series 3) THC #1 ID1 + e34b Core Ultra Processors (Series 3) THC #1 ID2 + e34c Core Ultra Processors (Series 3) P2SB + e34d Core Ultra Processors (Series 3) IEH #1 + e34e Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #21 + e34f Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #22 + e350 Core Ultra Processors (Series 3) I2C #4 + e351 Core Ultra Processors (Series 3) I2C #5 + e352 Core Ultra Processors (Series 3) UART #2 + e35c Core Ultra Processors (Series 3) PCIe Root Port #10 + e35d Core Ultra Processors (Series 3) CSME HECI #1 + e35e Core Ultra Processors (Series 3) CSME HECI #2 + e35f Core Ultra Processors (Series 3) CSME HECI #3 + e360 Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #23 + e361 Core Ultra Processors (Series 3) PCIe Root Port #9 + e362 Core Ultra Processors (Series 3) CSME HECI #1 + e363 Core Ultra Processors (Series 3) CSME HECI #2 + e364 Core Ultra Processors (Series 3) CSME HECI #3 + e365 Core Ultra Processors (Series 3) PCIe Root Port #11 + e366 Core Ultra Processors (Series 3) PCIe Root Port #12 e367 Panther Lake PCI Express Root Port D3 e368 Panther Lake PCI Express Root Port D4 e369 Panther Lake PCI Express Root Port D5 e36a Panther Lake PCI Express Root Port D6 e36b Panther Lake PCI Express Root Port D7 e36c Panther Lake PCI Express Root Port D8 - e36f Panther Lake I3C Host Controller #0 - e370 Panther Lake MEI Controller - e376 Panther Lake Bluetooth PCI Enumerator - e378 Panther Lake Serial IO I2C Controller #0 - e379 Panther Lake Serial IO I2C Controller #1 - e37a Panther Lake Serial IO I2C Controller #2 - e37b Panther Lake Serial IO I2C Controller #3 - e37c Panther Lake I3C Host Controller #1 - e37d Panther Lake USB 3.2 xHCI Controller - e37f Panther Lake Shared SRAM + e36f Core Ultra Processors (Series 3) I3C #2 + e370 Core Ultra Processors (Series 3) CSME HECI #1 (CSE) + e371 Core Ultra Processors (Series 3) CSME HECI #2 (CSE) + e372 Core Ultra Processors (Series 3) CSME IDE Redirection (IDE-R) + e373 Core Ultra Processors (Series 3) CSME Keyboard and Text (KT) Redirection + e374 Core Ultra Processors (Series 3) CSME HECI #3 (CSE) + e375 Core Ultra Processors (Series 3) CSME HECI #4 (CSE) + e376 Core Ultra Processors (Series 3) CNVi Bluetooth + e378 Core Ultra Processors (Series 3) I2C #0 + e379 Core Ultra Processors (Series 3) I2C #1 + e37a Core Ultra Processors (Series 3) I2C #2 + e37b Core Ultra Processors (Series 3) I2C #3 + e37c Core Ultra Processors (Series 3) I3C #1 + e37d Core Ultra Processors (Series 3) Standalone xHCI Controller + e37e Core Ultra Processors (Series 3) Standalone USB Device Controller + e37f Core Ultra Processors (Series 3) Shared SRAM + e400 Core Ultra Processors (Series 3) eSPI + e41f Core Ultra Processors (Series 3) eSPI + e420 Core Ultra Processors (Series 3) P2SB + e421 Core Ultra Processors (Series 3) PMC + e422 Core Ultra Processors (Series 3) SMBus + e423 Core Ultra Processors (Series 3) SPI (flash) Controller + e424 Core Ultra Processors (Series 3) Trace Hub + e425 Core Ultra Processors (Series 3) UART #0 + e426 Core Ultra Processors (Series 3) UART #1 + e427 Core Ultra Processors (Series 3) GSPI #0 + e428 Core Ultra Processors (Series 3) HD Audio + e429 Core Ultra Processors (Series 3) HD Audio + e42a Core Ultra Processors (Series 3) HD Audio + e42b Core Ultra Processors (Series 3) HD Audio + e42c Core Ultra Processors (Series 3) HD Audio + e42d Core Ultra Processors (Series 3) HD Audio + e42e Core Ultra Processors (Series 3) HD Audio + e42f Core Ultra Processors (Series 3) HD Audio + e430 Core Ultra Processors (Series 3) GSPI #1 + e431 Core Ultra Processors (Series 3) Type-C Subsystem xHCI + e432 Core Ultra Processors (Series 3) Type-C Subsystem xDCI + e433 Core Ultra Processors (Series 3) Thunderbolt DMA0 + e434 Core Ultra Processors (Series 3) Thunderbolt DMA1 + e437 Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #24 + e438 Core Ultra Processors (Series 3) PCIe Root Port #1 + e439 Core Ultra Processors (Series 3) PCIe Root Port #2 + e43a Core Ultra Processors (Series 3) PCIe Root Port #3 + e43b Core Ultra Processors (Series 3) PCIe Root Port #4 + e43c Core Ultra Processors (Series 3) PCIe Root Port #5 + e43d Core Ultra Processors (Series 3) PCIe Root Port #6 + e43e Core Ultra Processors (Series 3) PCIe Root Port #7 + e43f Core Ultra Processors (Series 3) PCIe Root Port #8 + e440 Core Ultra Processors (Series 3) CNVi Wi-Fi + 8086 0114 Wi-Fi 7 BE211 320MHz + e441 Core Ultra Processors (Series 3) CNVi Wi-Fi + e442 Core Ultra Processors (Series 3) CNVi Wi-Fi + e443 Core Ultra Processors (Series 3) CNVi Wi-Fi + e444 Core Ultra Processors (Series 3) IEH #0 + e445 Core Ultra Processors (Series 3) ISH + e446 Core Ultra Processors (Series 3) GSPI #2 + e448 Core Ultra Processors (Series 3) THC #0 ID1 + e449 Core Ultra Processors (Series 3) THC #0 ID2 + e44a Core Ultra Processors (Series 3) THC #1 ID1 + e44b Core Ultra Processors (Series 3) THC #1 ID2 + e44c Core Ultra Processors (Series 3) P2SB + e44d Core Ultra Processors (Series 3) IEH #1 + e44e Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #21 + e44f Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #22 + e450 Core Ultra Processors (Series 3) I2C #4 + e451 Core Ultra Processors (Series 3) I2C #5 + e452 Core Ultra Processors (Series 3) UART #2 + e45c Core Ultra Processors (Series 3) PCIe Root Port #10 + e45d Core Ultra Processors (Series 3) CSME HECI #1 + e45e Core Ultra Processors (Series 3) CSME HECI #2 + e45f Core Ultra Processors (Series 3) CSME HECI #3 + e460 Core Ultra Processors (Series 3) USB Type-C Subsystem PCIe Root Port #23 + e461 Core Ultra Processors (Series 3) PCIe Root Port #9 + e462 Core Ultra Processors (Series 3) CSME HECI #1 + e463 Core Ultra Processors (Series 3) CSME HECI #2 + e464 Core Ultra Processors (Series 3) CSME HECI #3 + e46f Core Ultra Processors (Series 3) I3C #2 + e470 Core Ultra Processors (Series 3) CSME HECI #1 (CSE) + e471 Core Ultra Processors (Series 3) CSME HECI #2 (CSE) + e472 Core Ultra Processors (Series 3) CSME IDE Redirection (IDE-R) + e473 Core Ultra Processors (Series 3) CSME Keyboard and Text (KT) Redirection + e474 Core Ultra Processors (Series 3) CSME HECI #3 (CSE) + e475 Core Ultra Processors (Series 3) CSME HECI #4 (CSE) + e476 Core Ultra Processors (Series 3) CNVi Bluetooth + e478 Core Ultra Processors (Series 3) I2C #0 + e479 Core Ultra Processors (Series 3) I2C #1 + e47a Core Ultra Processors (Series 3) I2C #2 + e47b Core Ultra Processors (Series 3) I2C #3 + e47c Core Ultra Processors (Series 3) I3C #1 + e47d Core Ultra Processors (Series 3) Standalone xHCI Controller + e47e Core Ultra Processors (Series 3) Standalone USB Device Controller + e47f Core Ultra Processors (Series 3) Shared SRAM f1a5 SSD 600P Series 8086 390a SSDPEKKW256G7 256GB f1a6 SSD DC P4101/Pro 7600p/760p/E 6100p Series @@ -40196,6 +41438,7 @@ 8510 0013 GB02-PCIe-C25-HH 8510 0014 GB2064-VPX-V40 8510 0201 GB2062-PUB-DDR + 0301 GenBu03 Series GPU # nee ScaleMP 8686 SAP 1010 vSMP Foundation controller [vSMP CTL] @@ -40228,6 +41471,9 @@ 1080 Ethernet Controller N10 Series Virtual Function 1081 Ethernet Controller N400 Series Virtual Function 1083 Ethernet Controller N400 Series Virtual Function + 8208 Ethernet Controller N210M for 1GbE 1-port RJ45 + 8209 Ethernet Controller N210 Series Virtual Function + 820a Ethernet Controller N210L for 1GbE 1-port RJ45 8308 Ethernet Controller N500 Series for 1GbE (Quad-port, Copper RJ45) # NIC-ETH3M0T-3S-4P Quad-Port RJ45 Adapter for OCP 3.0 193d 1088 NIC-ETH3M0T-3S-4P @@ -40240,10 +41486,16 @@ 8500 Ethernet Controller N20 Series for 25GbE 8848 0800 Ethernet Network Adapter N20 for 25GbE SFP28 8848 8800 Ethernet Network Adapter N20 for RDMA 25GbE SFP28 2-port + 8848 c800 Ethernet Network Adapter N20 for RDMA 25GbE SFP28 2-port OCP 3.0 8501 Ethernet Controller N20 Series for 100GbE 8848 8800 Ethernet Network Adapter N20 for RDMA 100GbE QSFP28 2-port 8502 Ethernet Controller N20 Series for 40GbE 8848 8800 Ethernet Network Adapter N20 for RDMA 40GbE QSFP+ 2-port + 8503 Ethernet Controller N20 Series Virtual Function + 8507 Ethernet Controller B-Series for 25GbE + 8508 Ethernet Controller B-Series for 100GbE + 8509 Ethernet Controller B-Series for 40GbE + 850a Ethernet Controller B-Series Virtual Function 8866 T-Square Design Inc. 8888 Silicon Magic 8504 AVMatrix VC42 4-port HDMI Capture diff --git a/hwdb.d/pnp_id_registry.csv b/hwdb.d/pnp_id_registry.csv index 92806997f1924..2f40ebfca721f 100644 --- a/hwdb.d/pnp_id_registry.csv +++ b/hwdb.d/pnp_id_registry.csv @@ -1,2558 +1,2558 @@ Company,"PNP ID","Approved On Date" -"21ST CENTURY ENTERTAINMENT",BUT,04/25/2002 -"2-Tel B.V",TTL,03/20/1999 -"3Com Corporation",TCM,11/29/1996 -"3D Perception",TDP,05/16/2002 -3M,VSD,10/16/1998 -"3NOD Digital Technology Co. Ltd.",NOD,12/11/2014 -"A D S Exports",NGS,07/16/1998 -"A Plus Info Corporation",API,11/29/1996 -"A&R Cambridge Ltd.",ACG,06/13/2007 -"A/Vaux Electronics",AVX,08/29/2012 -"A+V Link",APV,01/27/2010 -"Aashima Technology B.V.",TRU,05/08/1998 -"Aava Mobile Oy",AAM,08/13/2013 -"ABBAHOME INC.",ABA,11/08/1999 -"Abeam Tech Ltd.",MEG,11/29/1996 -"Ably-Tech Corporation",ATC,11/29/1996 -"AboCom System Inc.",ABC,03/28/1997 -"ACC Microelectronics",WTC,11/29/1996 -"Access Works Comm Inc",AWC,11/29/1996 -"Acco UK Ltd.",PKA,05/12/2003 -"Accton Technology Corporation",ACC,11/29/1996 -Acculogic,ACU,11/29/1996 -"AccuScene Corporation Ltd",ASL,06/13/2007 -"Ace CAD Enterprise Company Ltd",ANT,11/29/1996 -"Acer Inc",CHE,11/29/1996 -"Acer Labs",ALI,11/29/1996 -"Acer Netxus Inc",ANX,11/29/1996 -"Acer Technologies",ACR,11/29/1996 -Acksys,ACK,11/29/1996 -"Acnhor Datacomm",ADC,11/29/1996 -Acon,CAL,11/29/1996 -"Acrolink Inc",ALK,03/12/1997 -"Acroloop Motion Control Systems Inc",ACM,03/26/1998 -"ACT Labs Ltd",LAB,09/02/1997 -"Actek Engineering Pty Ltd",ACE,11/29/1996 -"Actiontec Electric Inc",AEI,11/29/1996 -"ActivCard S.A",ACV,05/08/1998 -"Aculab Ltd",ACB,11/29/1996 -"Acutec Ltd.",ALM,11/08/1999 -"AD electronics",GLE,04/19/2000 -"Ad Lib MultiMedia Inc",ADM,04/23/1998 -"Adaptec Inc",ADP,11/29/1996 -"Adax Inc",ADX,11/29/1996 -ADC-Centre,RSH,11/08/1999 -"Add Value Enterpises (Asia) Pte Ltd",AVE,01/10/1999 -"Addi-Data GmbH",ADA,11/29/1996 -"ADI Systems Inc",ADI,11/29/1996 -"ADPM Synthesis sas",DPM,08/10/2000 -"Adrienne Electronics Corporation",AXB,10/07/1997 -Adtek,ADT,11/29/1996 -"Adtek System Science Company Ltd",ADK,11/29/1996 -"ADTI Media, Inc",FLE,09/15/2009 -"Adtran Inc",AND,11/29/1996 -"Advan Int'l Corporation",AGM,05/26/1998 -"Advance Computer Corporation",AVN,06/10/2010 -"Advanced Digital Systems",MSM,11/29/1996 -"Advanced Electronic Designs, Inc.",AED,07/12/2004 -"Advanced Engineering",RJS,06/25/1998 -"Advanced Gravis",GRV,11/29/1996 -"Advanced Integ. Research Inc",AIR,11/29/1996 -"Advanced Logic",ALR,11/29/1996 -"Advanced Micro Devices Inc",ADV,11/29/1996 -"Advanced Micro Peripherals Ltd",EVE,11/18/2011 -"Advanced Optics Electronics, Inc.",AOE,04/20/2004 -"Advanced Peripheral Devices Inc",ADD,11/29/1996 -"Advanced Research Technology",ABV,01/16/1997 -"Advanced Signal Processing Technologies",PSA,09/13/1999 -"Advantech Co., Ltd.",AHC,06/13/2007 -"Aerodata Holdings Ltd",ADH,11/11/1997 -"Aetas Peripheral International",AEP,11/08/1999 -"Aethra Telecomunicazioni S.r.l.",AET,12/13/1996 -"Agentur Chairos",CHS,03/15/2001 -"Agilent Technologies",AGT,10/08/2001 -"Ahead Systems",ASI,11/29/1996 -"AIMS Lab Inc",AIM,03/13/1998 -"Airlib, Inc",AYR,02/21/2000 -"Aironet Wireless Communications, Inc",AWL,08/11/1998 -"Aiwa Company Ltd",AIW,11/29/1996 -"AJA Video Systems, Inc.",AJA,10/11/2007 -"AKAMI Electric Co.,Ltd",AKE,09/03/2010 -"Akebia Ltd",AKB,11/29/1996 -"AKIA Corporation",AKI,12/23/1998 -"AL Systems",ALH,01/20/1999 -"Alacron Inc",ALA,11/29/1996 -"Alana Technologies",ALN,01/13/2000 -Alcatel,AOT,11/06/2001 -"Alcatel Bell",ABE,11/29/1996 -Aldebbaron,ADB,03/15/2001 -"Alenco BV",ALE,05/20/2014 -"ALEXON Co.,Ltd.",ALX,09/13/1999 -"Alfa Inc",AFA,11/29/1996 -"Algolith Inc.",ALO,05/02/2005 -"AlgolTek, Inc.",AGO,10/23/2013 -"Alien Internet Services",AIS,06/21/2001 -"Allen Bradley Company",ABD,11/29/1996 -"Alliance Semiconductor Corporation",ALL,11/29/1996 -"Allied Telesis KK",ATI,11/29/1996 -"Allied Telesyn International (Asia) Pte Ltd",ATA,11/10/1997 -"Allied Telesyn Int'l",ATK,11/29/1996 -"Allion Computer Inc.",ACO,10/23/2000 -"Alpha Data",XAD,10/08/2009 -"Alpha Telecom Inc",ATD,09/26/1997 -"Alpha-Top Corporation",ATP,12/04/1996 -"AlphaView LCD",ALV,11/01/2008 -"ALPS ALPINE CO., LTD.",APE,01/22/2013 -"ALPS ALPINE CO., LTD.",ALP,11/29/1996 -"ALPS ALPINE CO., LTD.",AUI,11/29/1996 -"Alta Research Corporation",ARC,11/29/1996 -"Altec Corporation",ALC,08/04/1998 -"Altec Lansing",ALJ,01/13/2000 -"ALTINEX, INC.",AIX,04/24/2001 -"Altmann Industrieelektronik",AIE,11/29/1996 -"Altos Computer Systems",ACS,11/29/1996 -"Altos India Ltd",AIL,11/29/1996 -"Alvedon Computers Ltd",CNC,11/06/1998 -"Ambient Technologies, Inc.",AMB,05/16/1999 -Altra,ALT,11/29/1996 -"Amdek Corporation",AMD,11/29/1996 -"America OnLine",AOL,11/29/1996 -"American Biometric Company",YOW,05/16/1999 -"American Express",AXP,07/16/1999 -"American Magnetics",AXI,03/15/2001 -"American Megatrends Inc",AMI,11/29/1996 -"American Nuclear Systems Inc",MCA,02/12/1997 -"American Power Conversion",CNB,03/15/2001 -"American Power Conversion",APC,11/29/1996 -"Amimon LTD.",AMN,06/13/2007 -"Amino Technologies PLC and Amino Communications Limited",AMO,12/09/2011 -"AMiT Ltd",AKL,12/02/1997 -"AMP Inc",AMP,11/29/1996 -"Amptron International Inc.",AII,05/24/2000 -"AMT International Industry",AMT,11/29/1996 -"AmTRAN Technology Co., Ltd.",AMR,06/10/2013 -"AMX LLC",AMX,07/06/2008 -Anakron,ANA,11/08/1999 -"Analog & Digital Devices Tel. Inc",ADN,03/14/1997 -"Analog Devices Inc",ADS,11/29/1996 -"Analog Way SAS",ANW,01/22/2014 -"Analogix Semiconductor, Inc",ANL,10/10/2005 -"Anchor Bay Technologies, Inc.",ABT,02/14/2006 -"Anatek Electronics Inc.",AAE,05/25/2004 -"Ancor Communications Inc",ACI,11/29/1996 -Ancot,ANC,11/29/1996 -"Anderson Multimedia Communications (HK) Limited",AML,01/03/2003 -"Andrew Network Production",ANP,11/29/1996 -"Anigma Inc",ANI,11/29/1996 -"Anko Electronic Company Ltd",ANK,03/24/1998 -"Ann Arbor Technologies",AAT,04/24/2001 -"an-najah university",BBB,03/15/2001 -"Anorad Corporation",ANO,01/13/2000 -"ANR Ltd",ANR,11/29/1996 -"Ansel Communication Company",ANS,11/29/1996 -"Antex Electronics Corporation",AEC,11/29/1996 -"AOpen Inc.",AOA,11/06/2001 -"AP Designs Ltd",APX,12/08/1997 -"Apache Micro Peripherals Inc",DNG,11/11/1997 -"Aplicom Oy",APL,05/02/2005 -"Appian Tech Inc",APN,11/29/1996 -"Apple Computer Inc",APP,11/29/1996 -AppliAdata,APD,11/29/1996 -"Applied Creative Technology",ACT,11/29/1996 -"Applied Memory Tech",APM,11/29/1996 -"Apricot Computers",ACL,11/29/1996 -"Aprilia s.p.a.",APR,02/22/1999 -"ArchiTek Corporation",ATJ,01/22/2014 -"Archtek Telecom Corporation",ACH,01/15/1997 -"Arcus Technology Ltd",ATL,11/29/1996 -"AREC Inc.",ARD,07/08/2013 -"Arescom Inc",ARS,11/29/1996 -Argolis,AGL,03/15/2001 -"Argosy Research Inc",ARI,02/24/1997 -"Argus Electronics Co., LTD",ARG,06/04/2004 -"Ariel Corporation",ACA,12/13/1996 -Arima,ARM,04/07/2004 -"Arithmos, Inc.",ADE,07/16/1999 -"Ark Logic Inc",ARK,11/29/1996 -"Arlotto Comnet Inc",ARL,04/29/1997 -"ARMSTEL, Inc.",AMS ,02/25/2011 -"Arnos Insturments & Computer Systems",AIC,11/29/1996 -"ARRIS Group, Inc.",ARR,01/27/2015 -"ART s.r.l.",IMB,01/27/2012 -"Artish Graphics Inc",AGI,11/29/1996 -Arvanics,NPA,03/05/2015 -"Asahi Kasei Microsystems Company Ltd",AKM,11/29/1996 -"Asante Tech Inc",ASN,11/29/1996 -"Ascom Business Systems",HER,01/20/1999 -"Ascom Strategic Technology Unit",ASC,11/29/1996 -"ASEM S.p.A.",ASM,03/15/2001 -"ASEM S.p.A.",AEM,11/29/1996 -"AseV Display Labs",ASE,10/16/1998 -"Ashton Bentley Concepts",ASH,09/20/2013 -"Asia Microelectronic Development Inc",AMA,09/24/1997 -"Ask A/S",ASK,11/29/1996 -"Askey Computer Corporation",DYN,07/22/1997 -"ASP Microelectronics Ltd",ASP,11/29/1996 -"Askey Computer Corporation",AKY,04/02/1997 -"Aspen Tech Inc",ACP,11/29/1996 -"AST Research Inc",AST,11/29/1996 -"Astec Inc",JAC,11/29/1996 -"ASTRA Security Products Ltd",ADL,07/30/1997 -"ASTRO DESIGN, INC.",ATO,06/06/2003 -"Asuscom Network Inc",ASU,11/29/1996 -AT&T,ATT,11/29/1996 -"AT&T Global Info Solutions",GIS,11/29/1996 -"AT&T Microelectronics",HSM,11/29/1996 -"AT&T Microelectronics",TME,11/29/1996 -"AT&T Paradyne",PDN,11/29/1996 -"Atelier Vision Corporation",AVJ,02/24/2015 -"Athena Informatica S.R.L.",ATH,01/29/1997 -"Athena Smartcard Solutions Ltd.",ATN,09/13/1999 -"Athenix Corporation",ATX,11/29/1996 -"ATI Tech Inc",BUJ,11/29/1996 -Atlantis,CFG,11/29/1996 -"ATM Ltd",ATM,11/29/1996 -"Atom Komplex Prylad",AKP,10/23/2000 -"Attachmate Corporation",AMC,11/29/1996 -"Attero Tech, LLC",FWA,04/20/2010 -"Audio Processing Technology Ltd",APT,03/18/1997 -AudioScience,ASX,11/29/1996 -"August Home, Inc.",AUG,06/11/2014 -"Auravision Corporation",AVC,11/29/1996 -"Aureal Semiconductor",AUR,11/29/1996 -"Autologic Inc",APS,11/29/1996 -"automated computer control systems",CLT,09/13/1999 -"Autotime Corporation",AUT,10/08/2001 -"Auvidea GmbH",AUV,04/21/2014 -"Avalue Technology Inc.",AVL,11/18/2011 -"Avance Logic Inc",ALS,11/29/1996 -"Avaya Communication",AVA,03/15/2001 -Avencall,AEN,01/27/2012 -"AVer Information Inc.",AVR,05/07/2010 -"Avid Electronics Corporation",AVD,11/29/1996 -"AVM GmbH",AVM,11/29/1996 -"Avolites Ltd",AAA,02/17/2012 -"Avocent Corporation",AVO,10/23/2000 -"Avtek (Electronics) Pty Ltd",AVT,11/29/1996 -"AWETA BV",ACD,01/20/1998 -Axel,AXL,11/29/1996 -"AXIOMTEK CO., LTD.",AXC,05/02/2005 -"Axonic Labs LLC",AXO,06/21/2012 -"Axtend Technologies Inc",AXT,12/01/1997 -"Axxon Computer Corporation",AXX,11/29/1996 -"AXYZ Automation Services, Inc",AXY,08/11/1998 -"Aydin Displays",AYD,06/13/2007 -"AZ Middelheim - Radiotherapy",AZM,11/14/2003 -"Aztech Systems Ltd",AZT,11/29/1996 -B&Bh,BBH,01/17/2003 -"B.& V. s.r.l.",SMR,03/21/1997 -"B.F. Engineering Corporation",BFE,11/29/1996 -"B.U.G., Inc.",BUG,08/30/2011 -"Bang & Olufsen",BNO,05/16/2003 -"Banksia Tech Pty Ltd",BNK,11/29/1996 -Banyan,BAN,11/29/1996 -BARC,BRC,08/10/2000 -"Barco Display Systems",BDS,09/13/1999 -"Barco GmbH",BCD,03/07/2011 -"Barco Graphics N.V",BGB,11/29/1996 -"Barco, N.V.",BPS,09/12/2000 -"Barco, N.V.",DDS,10/23/2000 -"Baug & Olufsen",BEO,11/29/1996 -"Beaver Computer Corporaton",BCC,11/29/1996 -"Beckhoff Automation",BEC,04/25/2002 -"Beckworth Enterprises Inc",BEI,07/16/1997 -"Beijing Aerospace Golden Card Electronic Engineering Co.,Ltd.",AGC,06/21/2001 -"Beijing AnHeng SecoTech Information Technology Co., Ltd.",AHS,03/24/2015 -"Beijing ANTVR Technology Co., Ltd.",ANV,08/24/2015 -"Beijing Northern Radiantelecom Co.",NRT,03/20/1999 -"Beko Elektronik A.S.",BEK,06/15/2005 -"Beltronic Industrieelektronik GmbH",BEL,09/05/2006 -"Benson Medical Instruments Company",BMI,12/04/1996 -"B&R Industrial Automation GmbH",BUR,11/29/1996 -"Best Buy",INZ,06/04/2004 -"Best Buy",VPR,05/16/2002 -"Best Power",BPU,11/29/1996 -"Biamp Systems Corporation",BIA,05/14/2015 -"BICC Data Networks Ltd",ICC,11/29/1996 -"Big Island Communications",BIC,05/13/1997 -"Billion Electric Company Ltd",BIL,12/11/1996 -"BioLink Technologies",BLN,08/10/2000 -"BioLink Technologies International, Inc.",BIO,05/24/2000 -"BIOMED Lab",BML,05/22/1997 -"Biomedical Systems Laboratory",BSL,10/16/1997 -BIOMEDISYS,BMS,05/24/2000 -"Biometric Access Corporation",BAC,05/19/1998 -"BioTao Ltd",BTO,03/21/2012 -"Bit 3 Computer",BIT,11/29/1996 -"Bit 3 Computer",BTC,11/29/1996 -"Bitfield Oy",BTF,11/29/1996 -"BitHeadz, Inc.",BHZ,09/29/2003 -"Bitworks Inc.",BWK,07/10/2003 -"Blackmagic Design",BMD,09/13/2012 -"Blonder Tongue Labs, Inc.",BDR,09/16/2008 -"Bloomberg L.P.",BLP,09/16/2008 -"Boca Research Inc",ZZZ,02/13/1997 -"Boca Research Inc",BRI,11/29/1996 -"BodySound Technologies, Inc.",BST,03/12/2008 -"Boeckeler Instruments Inc",BII,10/17/1996 -"Booria CAD/CAM systems",BCS,05/11/2005 -BOS,BOS,07/03/1997 -"Bose Corporation",BSE,09/05/2006 -"Boulder Nonlinear Systems",BNS,03/12/2008 -"Braemac Pty Ltd",BRA,11/18/2010 -"Braemar Inc",BRM,10/07/1997 -"Brahler ICS",BDO,06/04/1998 -"Brain Boxes Limited",BBL,10/02/2001 -"Bridge Information Co., Ltd",BRG,08/11/1998 -"BRIGHTSIGN, LLC",BSN,02/28/2012 -"Brilliant Technology",BTE,11/29/1996 -"Broadata Communications Inc.",BCI,11/19/2013 -Broadcom,BCM,04/01/2004 -"BROTHER INDUSTRIES,LTD.",BRO,02/21/2000 -"BTC Korea Co., Ltd",NFC,02/25/2002 -"Budzetron Inc",BGT,11/29/1996 -Bull,BUL,02/03/1998 -"Bull AB",BNE,10/06/1998 -Busicom,BLI,08/11/1998 -"BusTech Inc",BTI,11/29/1996 -BusTek,BUS,11/29/1996 -"Butterfly Communications",FLY,05/05/1997 -"Buxco Electronics",BXE,11/29/1996 -"byd:sign corporation",BYD,04/10/2008 -"C3PO S.L.",XMM,03/03/1998 -"CA & F Elettronica",CAC,05/16/1999 -"Cabletime Ltd",CBT,05/04/2010 -"Cabletron System Inc",CSI,11/29/1996 -Cache,CCI,11/29/1996 -CalComp,CAG,11/29/1996 -CalComp,CDP,11/29/1996 -"Calibre UK Ltd",CUK,09/15/2005 -"California Institute of Technology",CSO,03/20/1999 -"Cambridge Audio",CAM,08/09/2008 -"Cambridge Electronic Design Ltd",CED,11/29/1996 -"Cambridge Research Systems Ltd",CMR,04/25/2002 -"Canon Inc",CNN,11/29/1996 -"Canon Inc.",CAI,11/06/2001 -"Canonical Ltd.",UBU,05/24/2013 -"Canopus Company Ltd",CAN,11/29/1996 -"Capella Microsystems Inc.",CPM,05/09/2012 -"Capetronic USA Inc",CCP,11/29/1996 -"Capstone Visua lProduct Development",DJE,10/09/2008 -"Cardinal Company Ltd",CAR,11/29/1996 -"Cardinal Technical Inc",CRD,11/29/1996 -CardLogix,CLX,03/15/2001 -"Carina System Co., Ltd.",CKJ,09/03/2010 -"Carl Zeiss AG",CZE,06/03/2009 -"CASIO COMPUTER CO.,LTD",CAS,10/06/1998 -"Castles Automation Co., Ltd",CAA,01/13/2000 -"Cavium Networks, Inc",CAV,02/02/2011 -"C-C-C Group Plc",FVX,05/04/1998 -CCL/ITRI,CCL,03/31/1997 -"C-Cube Microsystems",CCC,11/29/1996 -C-DAC,CEP,11/29/1996 -"Cebra Tech A/S",CBR,11/29/1996 -"Cefar Digital Vision",CEF,02/19/1997 -"Centurion Technologies P/L",CEN,10/23/2000 -"Century Corporation",TCE,11/29/1996 -"Cerevo Inc.",CRV,07/13/2010 -Ceronix,CER,09/02/2008 -"Ceton Corporation",TOM,05/08/2014 -"CH Products",CHP,04/24/1997 -"ChangHong Electric Co.,Ltd",CHD,11/30/2001 -"Chase Research PLC",CHA,11/29/1996 -"Cherry GmbH",CHY,05/16/1999 -"Chi Mei Optoelectronics corp.",CMO,03/15/2001 -"CHIC TECHNOLOGY CORP.",CHM,07/16/1999 -"Chicony Electronics Company Ltd",CEC,11/29/1996 -"Chimei Innolux Corporation",CMN,09/02/2010 -"China Hualu Group Co., Ltd.",HLG,05/13/2013 -Chloride-R&D,CHL,11/29/1996 -"Christie Digital Systems Inc",CDG,04/24/2001 -"Chromatec Video Products Ltd",CVP,08/09/2013 -"Chrontel Inc",CHI,11/29/1996 -"Chunghwa Picture Tubes,LTD.",CHT,03/15/2001 -"Chunghwa Telecom Co., Ltd.",CTE,05/16/2002 -"Chunichi Denshi Co.,LTD.",KCD,12/23/2010 -"Chuomusen Co., Ltd.",QQQ,08/07/2002 -"Chyron Corp",CGS,11/13/2008 -Cine-tal,CNE,06/13/2007 -"Cipher Systems Inc",PTG,11/29/1996 -"Ciprico Inc",CIP,11/29/1996 -"Ciprico Inc",CPC,11/29/1996 -"Cirel Systemes",FPX,11/29/1996 -"Cirque Corporation",CRQ,11/29/1996 -"Cirrus Logic Inc",CIR,11/29/1996 -"Cirrus Logic Inc",CLI,11/29/1996 -"Cirtech (UK) Ltd",SNS,08/20/1997 -"CIS Technology Inc",WSC,11/29/1996 -"Cisco Systems Inc",CIS,11/29/1996 -"Citicom Infotech Private Limited",CIL,08/10/2000 -"Citifax Limited",CIT,07/16/1997 -"Citron GmbH",CIN,07/28/2005 -"Clarion Company Ltd",CLA,11/29/1996 -"Clarity Visual Systems",CVS,01/13/2000 -"Classe Audio",CLE,02/16/2006 -"Clevo Company",CLV,01/30/1998 -"Clinton Electronics Corp.",PPM,10/01/2003 -"Clone Computers",CLO,11/29/1996 -"Cloudium Systems Ltd.",CSL,02/14/2013 -"CMC Ltd",CMC,11/29/1996 -"C-Media Electronics",CMI,11/29/1996 -"CNet Technical Inc",JQE,11/29/1996 -"COBY Electronics Co., Ltd",COB,06/13/2007 -"CODAN Pty. Ltd.",COD,10/23/2000 -"Codec Inc.",COI,11/30/2001 -"Codenoll Technical Corporation",CDN,11/29/1996 -"COINT Multimedia Systems",CNT,03/20/1999 -Colin.de,CDE,01/18/2005 -"Colorado MicroDisplay, Inc.",CMD,03/20/1999 -"Colorado Video, Inc.",CVI,08/15/2012 -"COM 1",MVX,11/29/1996 -"Comex Electronics AB",CMX,05/28/2004 -"Comm. Intelligence Corporation",CIC,11/29/1996 -"COMMAT L.t.d.",CLD,08/10/2000 -"Communications Specialies, Inc.",SDH,09/06/2005 -"Communications Supply Corporation (A division of WESCO)",INX,11/07/2012 -"Compal Electronics Inc",CPL,11/29/1996 -"Compaq Computer Company",CPQ,11/29/1996 -"Compound Photonics",CPP,10/01/2013 -CompuAdd,CPD,11/29/1996 -"CompuMaster Srl",CMS,02/22/1999 -"Computer Diagnostic Systems",CDS,03/15/2001 -"Computer Peripherals Inc",CPI,11/29/1996 -"Computer Technology Corporation",CTP,03/26/1998 -"ComputerBoards Inc",CBI,02/03/1998 -"Computerm Corporation",CTM,11/29/1996 -"Computone Products",CTN,11/29/1996 -Comrex,COX,10/18/2011 -"Comtec Systems Co., Ltd.",CTS,04/25/2002 -"Comtime GmbH",CMM,09/23/2002 -"Comtrol Corporation",COM,11/29/1996 -"Concept Development Inc",CDI,11/29/1996 -"Concept Solutions & Engineering",CSE,12/11/1996 -"Concepts Inc",DCI,11/29/1996 -"Conexant Systems",CXT,01/20/1999 -"congatec AG",CGT,06/16/2011 -"Connect Int'l A/S",CNI,11/29/1996 -"Connectware Inc",CWR,11/29/1996 -"CONRAC GmbH",CRC,04/20/2004 -"Consultancy in Advanced Technology",CAT,09/19/1997 -"Consumer Electronics Association",CEA,09/05/2006 -"CONTEC CO.,LTD.",CCJ,08/10/2000 -"Contec Company Ltd",CON,11/29/1996 -"Contemporary Research Corp.",CRH,02/24/2015 -"Control4 Corporation",CTR,05/28/2014 -"Convergent Data Devices",CDD,02/27/2004 -"Convergent Design Inc.",CDV,09/05/2006 -"Core Dynamics Corporation",CDC,11/29/1996 -"Corion Industrial Corporation",ART,11/29/1996 -"Core Technology Inc",COT,04/19/2000 -CoreLogic,CLG,11/27/1998 -"Cornerstone Imaging",CRN,11/29/1996 -"Corollary Inc",COR,12/13/1996 -"Cosmic Engineering Inc.",CSM,04/18/2012 -"CoStar Corporation",COS,11/29/1996 -"CoSystems Inc",CTA,10/24/1998 -"Covia Inc.",CVA,05/11/2010 -cPATH,CPT,03/09/1998 -"CRALTECH ELECTRONICA, S.L.",CRA,03/24/2015 -"Cray Communications",CDK,11/29/1996 -"CRE Technology Corporation",IOA,06/30/1997 -"Creative Labs Inc",CRE,11/29/1996 -"Creative Logic  ",CRL,10/16/1997 -"Creative Technology Ltd",CTL,11/29/1996 -"Creatix Polymedia GmbH",CTX,11/29/1996 -"Crescendo Communication Inc",CRS,11/29/1996 -"Cresta Systems Inc",CSD,08/01/1997 -"Crestron Electronics, Inc.",CEI,05/08/2006 -"Crio Inc.",CRI,09/13/1999 -"Cromack Industries Inc",CII,01/22/1997 -"Crystal Computer",XTL,11/29/1996 -"Crystal Semiconductor",CSC,11/29/1996 -"CrystaLake Multimedia",CLM,11/29/1996 -"CSS Laboratories",CSS,01/02/1997 -"CSTI Inc",CST,11/29/1996 -"CTC Communication Development Company Ltd",CTC,10/21/1997 -"Cubix Corporation",CUB,11/29/1996 -"Curtiss-Wright Controls, Inc.",CWC,04/05/2013 -Cyberlabs,CYL,04/14/1998 -CyberVision,CYB,05/13/1997 -Cyberware,CYW,02/21/2000 -"Cybex Computer Products Corporation",CBX,11/08/1999 -"Cyclades Corporation",CYD,05/07/2001 -"Cylink Corporation",CYC,11/29/1996 -"Cyrix Corporation",CYX,10/21/1997 -"Cyrix Corporation",CRX,03/21/1997 -"Cytechinfo Inc",CYT,03/13/1998 -"Cyviz AS",CYV,04/25/2002 -"D&M Holdings Inc, Professional Business Company",DMP,09/05/2006 -"D.N.S. Corporation",OPI,11/29/1996 -"DA2 Technologies Corporation",DDA,03/13/2006 -"DA2 Technologies Inc",DAW,09/06/2005 -"Daewoo Electronics Company Ltd",DWE,11/29/1996 -"Dai Telecom S.p.A.",TLT,06/04/2003 -"Daintelecom Co., Ltd",DIN,11/08/1999 -"DAIS SET Ltd.",DAI,02/21/2000 -Daktronics,DAK,06/23/2004 -"Dale Computer Corporation",DCC,11/29/1996 -"Dancall Telecom A/S",DCT,08/12/1997 -"Danelec Marine A/S",DAN,12/24/2009 -"Danka Data Devices",DDD,11/29/1996 -"Daou Tech Inc",DAU,11/29/1996 -DAT,HCA,03/15/2001 -"Data Apex Ltd",DAX,11/29/1996 -"Data Display AG",DDI,07/17/2002 -"Data Expert Corporation",DXP,11/29/1996 -"Data Export Corporation",EXP,11/29/1996 -"Data Modul AG",DMO,12/03/2013 -"Data Price Informatica",EBH,05/24/2001 -"Data Race Inc",DRI,07/30/1997 -"Data Ray Corp.",DRC,11/30/2001 -"Data Translation",DTX,11/29/1996 -"Data Video",DVT,02/13/2007 -"Databook Inc",DBK,11/29/1996 -"Datacast LLC",DCD,12/02/1997 -"Datacommunicatie Tron B.V.",TRN,11/29/1996 -"Datacube Inc",DQB,11/29/1996 -"Datadesk Technologies Inc",DDT,11/27/1998 -"Datakey Inc",DKY,04/06/1998 -"Datalogic Corporation",LJX,11/29/1996 -"Datang Telephone Co",DTN,09/23/1998 -"Dataq Instruments Inc",DII,11/29/1996 -"Datasat Digital Entertainment",DDE,11/18/2011 -"Datatronics Technology Inc",DCV,01/02/1997 -"Datel Inc",DAT,11/29/1996 -"Datenerfassungs- und Informationssysteme",MSD,03/16/1998 -"Davicom Semiconductor Inc",DAV,01/15/1997 -"DAVIS AS",DAS,02/03/1998 -"DB Networks Inc",DBN,12/01/1997 -"DBA Hans Wedemeyer",HWC,03/20/1999 -"DCM Data Products",DCM,11/29/1996 -"Dearborn Group Technology",DGT,11/11/1997 -"DECIMATOR DESIGN PTY LTD",DXD,03/06/2012 -"Decros Ltd",DCR,11/29/1996 -"Deep Video Imaging Ltd",MLD,08/14/2003 -"DEI Holdings dba Definitive Technology",DFT,12/09/2011 -"Deico Electronics",DEI,11/29/1996 -"Dell Inc",DLL,03/27/2009 -"Dell Inc.",DEL,12/09/2009 -"Delphi Automotive LLP",DPH,10/15/2013 -"Delta Electronics Inc",DPC,11/29/1996 -"Delta Information Systems, Inc",DDV,01/03/2012 -DELTATEC,DTA,03/13/2009 -"Deltec Corporation",FPS,11/29/1996 -"DENON, Ltd.",DON,04/01/2004 -"Dension Audio Systems",DHD,03/04/2013 -"Densitron Computers Ltd",DEN,09/13/1999 -"Design & Test Technology, Inc.",DTT,09/30/2010 -"Design Technology",LPI,11/29/1996 -"Deterministic Networks Inc.",DNI,04/19/2000 -"Deutsche Telekom Berkom GmbH",BCQ,08/12/1997 -"Deutsche Thomson OHG",DTO,06/14/2007 -"Devolo AG",DVL,05/30/2002 -"Dextera Labs Inc",DXL,12/09/2009 -DFI,DFI,11/29/1996 -"DH Print",DHP,11/29/1996 -Diadem,DIA,11/29/1996 -"Diagsoft Inc",DGS,11/29/1996 -"Dialogue Technology Corporation",DCO,06/16/2004 -"Diamond Computer Systems Inc",DCS,11/29/1996 -"Diamond Lane Comm. Corporation",DLC,11/29/1996 -DiCon,DNV,12/15/2004 -"Dictaphone Corporation",DVD,04/03/1998 -"Diebold Inc.",DBD,09/05/2006 -"Digatron Industrie Elektronik GmbH",DAE,02/24/1997 -"DIGI International",DGI,11/29/1996 -"DigiBoard Inc",DBI,11/29/1996 -"Digicom S.p.A.",DIG,11/29/1996 -"Digicom Systems Inc",DMB,03/13/1998 -"Digicorp European sales S.A.",DGP,05/22/1997 -"Digiital Arts Inc",DGA,06/14/2007 -"Digipronix Control Systems",DXC,07/16/1999 -"Digital Acoustics Corporation",DAC,05/24/2000 -"Digital Audio Labs Inc",DAL,11/29/1996 -"Digital Communications Association",DCA,11/29/1996 -"Digital Discovery",SHR,09/24/1997 -"Schneider Electric Japan Holdings, Ltd.",PRF,01/02/2003 -"Digital Equipment Corporation",DEC,11/29/1996 -"Digital Processing Systems",DPS,11/29/1996 -"Digital Projection Limited",DPL,07/09/2002 -"DIGITAL REFLECTION INC.",DRD,02/21/2000 -"Digital Video System",DVS,11/29/1996 -"DigiTalk Pro AV",DPA,10/23/2000 -"Digital-Logic GmbH",DLG,09/02/2003 -"Digitan Systems Inc",DSI,11/29/1996 -"Digitelec Informatique Park Cadera",DLT,11/29/1996 -"Dimension Technologies, Inc.",DTE,05/03/2010 -"Dimond Multimedia Systems Inc",DMM,11/29/1996 -"Diseda S.A.",DIS,11/29/1996 -"Distributed Management Task Force, Inc. (DMTF)",DMT,03/31/2009 -"Diversified Technology, Inc.",DTI,11/29/1996 -"D-Link Systems Inc",ABO,11/29/1996 -"D-Link Systems Inc",DLK,11/29/1996 -"DNA Enterprises, Inc.",DNA,09/01/1998 -"DO NOT USE - AUO",AUO,09/16/2008 -"DO NOT USE - LPL",LPL,09/16/2008 -"DO NOT USE - PHI",PHI,11/29/1996 -"DO NOT USE - PTW",PTW,09/09/2009 -"DO NOT USE - PVC",PVC,09/09/2009 -"DO NOT USE - RTK",RTK,09/09/2009 -"DO NOT USE - SEG",SEG,09/09/2009 -"DO NOT USE - TNJ",TNJ,09/09/2009 -"DO NOT USE - UND",UND,11/29/1996 -"DO NOT USE - UNE",UNE,11/29/1996 -"DO NOT USE - UNF",UNF,11/29/1996 -"DO NOT USE - WAN",WAN,09/09/2009 -"DO NOT USE - XER",XER,09/09/2009 -"DO NOT USE - XOC",XOC,09/09/2009 -"Doble Engineering Company",DBL,11/29/1996 -DocuPoint,DPI,11/29/1996 -"Dolby Laboratories Inc.",DLB,01/27/2010 -"Dolman Technologies Group Inc",DOL,11/11/1997 -"Domain Technology Inc",DSP,11/29/1996 -"DOME imaging systems",DMS,10/23/2000 -"Dome Imaging Systems",DOM,11/29/1996 -"Dongguan Alllike Electronics Co., Ltd.",AIK,04/11/2015 -"Dosch & Amand GmbH & Company KG",DUA,12/02/1997 -"Dotronic Mikroelektronik GmbH",DOT,06/28/2002 -"dPict Imaging, Inc.",DIM,02/12/2008 -"DpiX, Inc.",DPX,09/23/1998 -DPT,DPT,11/29/1996 -"Dr. Bott KG",DRB,04/25/2002 -"Dr. Neuhous Telekommunikation GmbH",DNT,11/29/1996 -"Dragon Information Technology",DIT,11/29/1996 -"DRS Defense Solutions, LLC",DRS,10/18/2011 -"DS Multimedia Pte Ltd",DSD,02/14/2006 -"DSM Digital Services GmbH",DSM,11/29/1996 -"dSPACE GmbH",DCE,12/16/1996 -"DTC Tech Corporation",DTC,11/29/1996 -"DugoTech Co., LTD",DGK,06/14/2007 -"Dune Microsystems Corporation",DMC,11/29/1996 -"Dycam Inc",DYC,01/08/1998 -"Dymo-CoStar Corporation",DYM,12/28/1998 -"Dynamic Controls Ltd",DCL,05/24/2000 -"Dynax Electronics (HK) Ltd",DTK,11/29/1996 -"Dynax Electronics (HK) Ltd",DYX,11/29/1996 -"e.Digital Corporation",EDC,10/23/2000 -"E.E.P.D. GmbH",EEP,06/14/2007 -"Eagle Technology",EGL,11/29/1996 -"Eastman Kodak Company",KOD,05/24/2000 -"Eastman Kodak Company",EKC,11/29/1996 -"Easytel oy",TWI,07/16/1999 -"EBS Euchner Büro- und Schulsysteme GmbH",EBS,02/05/2013 -"Echo Speech Corporation",ECO,11/29/1996 -"Eclipse Tech Inc",ETI,11/29/1996 -"E-Cmos Tech Corporation",ECM,11/29/1996 -"Eden Sistemas de Computacao S/A",ESC,11/29/1996 -"Edimax Tech. Company Ltd",EDI,11/29/1996 -EDMI,EDM,07/16/1998 -"Edsun Laboratories",ELI,11/29/1996 -"EE Solutions, Inc.",EES,04/16/2003 -"EEH Datalink GmbH",EEH,07/03/1997 -"Efficient Networks",ENI,11/29/1996 -"Egenera, Inc.",EGN,10/08/2002 -"Eicon Technology Corporation",EIC,11/29/1996 -"EIZO GmbH Display Technologies",EGD,02/13/2009 -"Eizo Nanao Corporation",ENC,12/28/1998 -"EKSEN YAZILIM",EKS,04/25/2002 -"ELAD srl",ELA,04/25/2002 -"ELAN MICROELECTRONICS CORPORATION",ETD,11/03/2009 -"ELAN MICROELECTRONICS CORPORATION",TSH,11/14/2014 -"Elbit Systems of America",ESA,06/15/2009 -"ELCON Systemtechnik GmbH",ESG,07/16/1999 -"ELEA CardWare",LXS,06/25/1998 -"Elecom Company Ltd",ECP,11/29/1996 -"Elecom Company Ltd",ELE,11/29/1996 -"Electro Cam Corp.",ECA,08/10/2000 -"Electro Scientific Ind",ELC,11/29/1996 -"Electronic Measurements",MMM,11/29/1996 -"Electronic Trade Solutions Ltd",ETS,08/20/2002 -"Electronic-Design GmbH",EDG,08/12/1997 -"Electrosonic Ltd",ELL,09/13/1999 -"Element Labs, Inc.",ELT,10/11/2007 -"Elgato Systems LLC",EGA,02/08/2011 -"Elitegroup Computer Systems Company Ltd",ECS,11/29/1996 -"Elitegroup Computer Systems Company Ltd",UEG,11/29/1996 -"Elmeg GmbH Kommunikationstechnik",ELG,11/29/1996 -"Elmic Systems Inc",ELM,11/29/1996 -"ELMO COMPANY, LIMITED",EMO,06/26/2012 -"Elo TouchSystems Inc",ELO,11/29/1996 -"Elonex PLC",ELX,11/29/1996 -"El-PUSK Co., Ltd.",LPE,08/14/2001 -"ELSA GmbH",ELS,11/29/1996 -"ELTEC Elektronik AG",EAG,11/25/2014 -"Embedded computing inc ltd",EMB,02/25/2002 -"Embedded Solution Technology",EST,05/24/2000 -"Embrionix Design Inc.",EMD,07/24/2013 -"Emcore Corporation",EMK,05/31/2012 -"Emerging Display Technologies Corp",EDT,08/18/2009 -"EMG Consultants Inc",EMG,11/29/1996 -"EMiNE TECHNOLOGY COMPANY, LTD.",EME,06/16/2005 -Empac,EPC,12/04/1996 -"Emulex Corporation",EMU,11/29/1996 -"Enciris Technologies",ECI,11/01/2008 -"Enciris Technologies",ECT,11/01/2008 -"ENE Technology Inc.",ENE,03/15/2001 -"e-Net Inc",DTL,10/16/1997 -Enhansoft,EHN,11/16/2010 -"ENIDAN Technologies Ltd",END,04/19/2000 -"Ensemble Designs, Inc",ESD,12/09/2009 -"Ensoniq Corporation",ENS,11/29/1996 -"Enterprise Comm. & Computing Inc",ENT,11/29/1996 -"Envision Peripherals, Inc",EPI,02/22/1999 -"Eon Instrumentation, Inc.",EON,01/15/2015 -"EPiCON Inc.",EPN,09/23/1998 -"Epiphan Systems Inc. ",EPH ,03/14/2011 -"Epson Research",EHJ,11/29/1996 -"Equinox Systems Inc",EQX,11/29/1996 -"Equipe Electronics Ltd.",EQP,07/14/2005 -"Ergo Electronics",EGO,11/29/1996 -"Ergo System",ERG,11/29/1996 -"Ericsson Mobile Communications AB",ERI,10/22/1997 -"Ericsson Mobile Networks B.V.",EUT,04/14/1998 -"Ericsson, Inc.",ERN,09/23/1998 -ES&S,ESK,11/08/1999 -eSATURNUS,ESN,02/21/2012 -"Escort Insturments Corporation",ERT,05/02/1997 -"ESS Technology Inc",ESS,11/29/1996 -"ESSential Comm. Corporation",ECC,11/29/1996 -"Esterline Technologies",ESL,01/06/2012 -ScioTeq,ESB,01/15/2015 -"E-Systems Inc",ESY,11/29/1996 -"ET&T Technology Company Ltd",EEE,05/04/1998 -"E-Tech Inc",ETT,11/29/1996 -"eTEK Labs Inc.",ETK,07/16/1998 -"Etherboot Project",ETH,07/09/2010 -"Eugene Chukhlomin Sole Proprietorship, d.b.a.",ECK,05/03/2008 -"Euraplan GmbH",ERP,11/29/1996 -"Evans and Sutherland Computer",EAS,01/28/2003 -Everex,EVX,11/29/1996 -"Everton Technology Company Ltd",ETC,04/10/1997 -"Evertz Microsystems Ltd.",ETL,06/14/2007 -"eviateg GmbH",EVI,02/21/2000 -"Ex Machina Inc",EMI,11/29/1996 -"Exacom SA",YHW,11/29/1996 -"Exatech Computadores & Servicos Ltda",EXT,09/23/1998 -"Excel Company Ltd",ECL,05/27/1997 -"Excession Audio",EXC,11/06/1998 -"EXFO Electro Optical Engineering",XFO,04/29/1998 -"Exide Electronics",EXI,11/29/1996 -"Extended Systems, Inc.",ESI,07/16/1999 -"Exterity Ltd",EXY,02/12/2009 -"Extraordinary Technologies PTY Limited",CRO,04/11/2005 -"Exxact GmbH",EXX,11/29/1996 -"eyefactive Gmbh",EYF,07/07/2015 -"eyevis GmbH",EYE,11/18/2011 -"EzE Technologies",EZE,02/21/2005 -"F.J. Tieman BV",FJT,06/25/1998 -"Fairfield Industries",FFI,11/29/1996 -"Fantalooks Co., Ltd.",FAN,03/12/2014 -"Fanuc LTD",FNC,01/29/1997 -"Farallon Computing",FAR,11/29/1996 -"FARO Technologies",FRO,09/21/2012 -"Faroudja Laboratories",FLI,06/02/2004 -"Fast Multimedia AG",FMA,11/29/1996 -"FastPoint Technologies, Inc.",FTI,06/21/2001 -"Feature Integration Technology Inc.",FIT,08/11/2009 -"Fellowes & Questec",FEL,11/29/1996 -"Fellowes, Inc.",FMI,07/05/2001 -"Fen Systems Ltd.",FEN,05/04/2010 -"Ferranti Int'L",FER,11/29/1996 -"Ferrari Electronic GmbH",TLA,12/04/1996 -FHLP,FHL,11/29/1996 -"Fibernet Research Inc",FRI,11/29/1996 -"Finecom Co., Ltd.",FIN,11/27/1998 -"Fingerprint Cards AB",FPC,06/14/2013 -"First Industrial Computer Inc",PCG,11/29/1996 -"First International Computer Inc",LEO,09/19/1997 -"First International Computer Ltd",FCG,04/10/1997 -"First Virtual Corporation",FVC,11/29/1996 -"Flat Connections Inc",FWR,11/29/1996 -"FlightSafety International",SSD,08/10/2000 -"FLY-IT Simulators",FIS,09/08/1997 -"FocalTech Systems Co., Ltd.",FTS,07/23/2013 -"Focus Enhancements, Inc.",FCS,12/12/2002 -"Fokus Technologies GmbH",FOK,10/22/2013 -"FOR-A Company Limited",FOA,12/06/2008 -"Force Computers",FRC,11/29/1996 -"Ford Microelectronics Inc",FMC,03/11/1997 -"Fore Systems Inc",FSI,11/29/1996 -"Forefront Int'l Ltd",FIL,11/29/1996 -"Formosa Industrial Computing Inc",FIC,11/29/1996 -Formoza-Altair,FMZ,04/25/2003 -"Forth Dimension Displays Ltd",FDD,07/07/2015 -"Forvus Research Inc",FRE,04/24/1997 -"Foss Tecator",FOS,10/22/1997 -"Founder Group Shenzhen Co.",FZC,11/08/1999 -"Fountain Technologies Inc",FTN,11/29/1996 -"Fraunhofer Heinrich-Hertz-Institute",HHI,07/27/2012 -"Freedom Scientific BLV",FRD,06/15/2007 -"FREEMARS Heavy Industries",TCX,03/15/2001 -"Frontline Test Equipment Inc.",FTE,01/20/1999 -"FTG Data Systems",FTG,11/29/1996 -"Fuji Xerox",FXX,11/29/1996 -"FUJIFILM Corporation",FFC,08/22/2011 -"Fujitsu Display Technologies Corp.",FDT,10/23/2002 -"Fujitsu General Limited.",FGL,02/21/2000 -"Fujitsu Ltd",FUJ,11/29/1996 -"Fujitsu Microelect Ltd",FML,11/29/1996 -"Fujitsu Peripherals Ltd",FPE,08/19/1997 -"Fujitsu Siemens Computers GmbH",FUS,01/13/2000 -"Fujitsu Spain",FJS,11/29/1996 -"FCL COMPONENTS LIMITED",FJC,05/16/1999 -"FUJITSU TEN LIMITED",FTL,12/20/2011 -"Funai Electric Co., Ltd.",FNI,01/18/2005 -"Furukawa Electric Company Ltd",FCB,11/29/1996 -"FURUNO ELECTRIC CO., LTD.",FEC,11/29/1996 -"Future Designs, Inc.",FDI,09/29/2014 -"Future Domain",FDC,11/29/1996 -"Future Systems Consulting KK",FSC,11/29/1996 -"Futuretouch Corporation",FTC,11/29/1996 -"FZI Forschungszentrum Informatik",FZI,08/12/1997 -"G&W Instruments GmbH",SPH,02/25/2002 -"G. Diehl ISDN GmbH",GDI,11/29/1996 -"Gadget Labs LLC",GLS,11/29/1996 -"Gage Applied Sciences Inc",GAG,11/29/1996 -"GAI-Tronics, A Hubbell Company",HUB,03/26/2009 -"Galil Motion Control",GAL,11/29/1996 -"Garmin International",GRM,12/09/2011 -"Garnet System Company Ltd",GTM,11/29/1996 -"Gateway 2000",GWY,11/29/1996 -"Gateway Comm. Inc",GCI,11/29/1996 -"Gateworks Corporation",GWK,07/31/2013 -"Gaudi Co., Ltd.",GAU,03/31/2003 -"GCC Technologies Inc",GCC,06/05/1997 -GDS,GDS,06/23/2004 -"GE Fanuc Embedded Systems",GEF,06/14/2007 -"Abaco Systems, Inc.",GEH,09/03/2010 -"Gefen Inc.",GFN,10/11/2007 -"Gem Plus",GEM,02/27/1998 -"GEMINI 2000 Ltd",GMN,10/23/2000 -"General Datacom",GDC,11/29/1996 -"General Dynamics C4 Systems",GED,01/09/2013 -"General Information Systems",GML,01/13/2000 -"General Inst. Corporation",GIC,11/29/1996 -"General Standards Corporation",GSC,07/16/1998 -"General Touch Technology Co., Ltd.",GTT,11/21/2002 -"Genesys ATE Inc",GEN,11/29/1996 -"Genesys Logic",GLM,11/08/1999 -"Gennum Corporation",GND,09/05/2006 -"GEO Sense",GEO,11/29/1996 -"Geotest Marvin Test Systems Inc",GTS,02/24/1998 -"GERMANEERS GmbH",GER,12/20/2011 -"GES Singapore Pte Ltd",GES,03/15/2001 -"Getac Technology Corporation",GET,05/11/2010 -"GFMesstechnik GmbH",GFM,03/15/2001 -"GI Provision Ltd",GIP,02/08/2012 -"Global Data SA",PST,11/29/1996 -"Global Village Communication",GVL,11/29/1996 -"GMK Electronic Design GmbH",GMK,01/18/2008 -"GMM Research Inc",GMM,11/29/1996 -"GMX Inc",GMX,11/29/1996 -"GN Nettest Inc",GNN,07/30/1997 -"GOEPEL electronic GmbH",GOE,06/24/2013 -"Goldmund - Digital Audio SA",GLD,02/06/2012 -"GOLD RAIN ENTERPRISES CORP.",GRE,06/04/2003 -"Goldstar Company Ltd",GSM,11/29/1996 -Goldtouch,GTI,08/06/1997 -"Google Inc.",GGL,05/26/2010 -"GoPro, Inc.",GPR,01/15/2015 -"Granch Ltd",GRH,09/23/2002 -"Grand Junction Networks",GJN,11/29/1996 -"Grandstream Networks, Inc.",GSN,03/03/2014 -"Graphic SystemTechnology",GST,11/29/1996 -"Graphica Computer",GRA,11/29/1996 -"Graphtec Corporation",GTC,11/29/1996 -"Grass Valley Germany GmbH",TGV,06/14/2007 -"Grey Cell Systems Ltd",GCS,04/29/1997 -"Grossenbacher Systeme AG",GSY,04/19/2000 -"G-Tech Corporation",GTK,11/29/1996 -"Guillemont International",GIM,10/29/1997 -"GUNZE Limited",GZE,05/02/2005 -"Gunze Ltd",GNZ,11/29/1996 -"Guntermann & Drunck GmbH",GUD,03/10/2003 -"Guzik Technical Enterprises",GUZ,11/29/1996 -"GVC Corporation",GVC,11/29/1996 -"H.P.R. Electronics GmbH",HPR,08/29/2007 -"Hagiwara Sys-Com Company Ltd",HSC,11/29/1996 -"GW Instruments",GWI,11/29/1996 -"Haider electronics",HAE,07/05/2001 -"Haivision Systems Inc.",HAI,11/15/2007 -Halberthal,HAL,02/10/1998 -"Hall Research",HRI,05/10/2012 -"HAMAMATSU PHOTONICS K.K.",HPK,12/20/2006 -"Hampshire Company, Inc.",HTI,01/20/1999 -"Hanchang System Corporation",HAN,06/21/2003 -"HannStar Display Corp",HSD,08/11/2009 -"HannStar Display Corp",HSP,08/11/2009 -"HardCom Elektronik & Datateknik",HDC,04/14/1998 -"Harman International Industries, Inc",HII,01/09/2015 -"Harris & Jeffries Inc",HJI,11/29/1996 -"Harris Canada Inc",HWA,03/13/1998 -"Harris Corporation",HAR,12/20/2011 -"Harris Semiconductor",HRS,01/02/1997 -"Hauppauge Computer Works Inc",HCW,11/29/1996 -"Hayes Microcomputer Products Inc",HAY,11/29/1996 -"HCL America Inc",HCL,11/29/1996 -"HCL Peripherals",HCM,10/02/2001 -"HD-INFO d.o.o.",HDI,10/08/2001 -"Headplay, Inc.",HPI,04/30/2007 -"Heng Yu Technology (HK) Limited",HYT,10/23/2000 -Hercules,HRC,03/15/2001 -HERCULES,HRT,03/15/2001 -"HETEC Datensysteme GmbH",HET,02/03/2004 -"Hewlett Packard",HWP,03/15/2001 -"Hewlett Packard",HPD,05/02/1997 -"Hewlett-Packard Co.",HPC,08/10/2000 -"Hewlett-Packard Co.",HPQ,07/12/2004 -"Hexium Ltd.",HXM,04/15/2008 -"Hibino Corporation",HIB,07/09/2003 -"Highwater Designs Ltd",HWD,11/29/1996 -"Hikom Co., Ltd.",HIK,10/13/2003 -"Hilevel Technology",HIL,11/29/1996 -"HIRAKAWA HEWTECH CORP.",HHC,05/20/2008 -"Hitachi America Ltd",HIT,11/29/1996 -"Hitachi Consumer Electronics Co., Ltd",HCE,05/15/2009 -"Hitachi Information Technology Co., Ltd.",HIC,04/19/2000 -"Hitachi Ltd",HTC,11/29/1996 -"Hitachi Maxell, Ltd.",MXL,01/13/2000 -"Hitachi Micro Systems Europe Ltd",HEL,07/09/1997 -"Hitex Systementwicklung GmbH",HTX,01/30/1998 -"hmk Daten-System-Technik BmbH",HMK,09/30/1997 -"HOB Electronic GmbH",HOB,11/29/1996 -"Holoeye Photonics AG",HOL,02/02/2005 -"Holografika kft.",HDV,03/31/2005 -"Holtek Microelectronics Inc",HTK,11/29/1996 -"Home Row Inc",INC,11/29/1996 -"HON HAI PRECISON IND.CO.,LTD.",FOX,08/02/2010 -"HONKO MFG. CO., LTD.",HKA,12/01/2004 -"Hope Industrial Systems, Inc.",HIS,01/13/2014 -"Horner Electric Inc",APG,11/29/1996 -"Horsent Technology Co., Ltd.",HST,04/11/2015 -"Hosiden Corporation",HOE,08/05/1997 -"HTBLuVA Mödling",HTL,02/17/2014 -"Hualon Microelectric Corporation",HMC,11/29/1996 -"HUALONG TECHNOLOGY CO., LTD",EBT,06/15/2007 -"Hughes Network Systems",HNS,11/29/1996 -"HUMAX Co., Ltd.",HMX,02/14/2006 -"HYC CO., LTD.",HYO,04/12/2006 -"Hydis Technologies.Co.,LTD",HYD,11/22/2010 -"Hynix Semiconductor",HYV,11/29/2008 -"Hypercope Gmbh Aachen",HYC,12/01/1997 -"Hypertec Pty Ltd",HYR,11/29/1996 -"Hyphen Ltd",HYP,11/29/1996 -"I&T Telecom.",ITT,11/08/1999 -"I/OTech Inc",IOT,11/29/1996 -"IAT Germany GmbH",IAT,11/29/1996 -"IBM Brasil",IBM,11/29/1996 -"IBM Corporation",CDT,11/29/1996 -"IBP Instruments GmbH",IBP,09/23/1998 -"IBR GmbH",IBR,01/16/1998 -"IC Ensemble",ICE,09/19/1997 -"ICA Inc",ICA,05/20/2002 -"ICCC A/S",ICX,11/29/1996 -"ICD Inc",ICD,06/09/1997 -"ICET S.p.A.",ARE,05/16/1999 -"ICP Electronics, Inc./iEi Technology Corp.",ICP,09/07/2012 -ICSL,IUC,08/14/1997 -"Icuiti Corporation",XTD,06/14/2007 -"Icuiti Corporation",IWR,03/06/2007 -"Id3 Semiconductors",ISC,03/15/2001 -"IDE Associates",IDE,11/29/1996 -"IDEO Product Development",IDO,09/30/1997 -"idex displays",DEX,04/25/2002 -"IDEXX Labs",IDX,11/29/1996 -"IDK Corporation",IDK,04/16/2003 -"Idneo Technologies",IDN,07/05/2012 -IDTECH,ITS,06/17/2002 -IEE,IEE,06/21/2001 -"IGM Communi",IGM,11/29/1996 -"IINFRA Co., Ltd",IIN,05/09/2003 -"Iiyama North America",IVM,11/29/1996 -"Ikegami Tsushinki Co. Ltd.",IKE,11/14/2014 -"Ikos Systems Inc",IKS,11/29/1996 -ILC,IND,06/16/2004 -"Image Logic Corporation",ILC,11/29/1996 -"Image Stream Medical",ISM,05/27/2010 -"IMAGENICS Co., Ltd.",IMG,09/05/2006 -"IMAGEQUEST Co., Ltd",IQT,10/08/2002 -Imagraph,IME,12/04/1996 -Imagraph,IMA,11/29/1996 -"ImasDe Canarias S.A.",IMD,07/03/1997 -"IMC Networks",IMC,11/29/1996 -"Immersion Corporation",IMM,07/16/1997 -"IMP Electronics Ltd.",HUM,06/16/2004 -Impinj,IMP,08/14/2012 -"Impossible Production",IMN,08/10/2000 -"In Focus Systems Inc",IFS,11/29/1996 -"In4S Inc",ALD,12/05/1997 -INBINE.CO.LTD,IBI,11/06/2001 -"Indtek Co., Ltd.",INK,03/26/2007 -"IneoQuest Technologies, Inc",IQI,02/18/2011 -"Industrial Products Design, Inc.",IPD,07/16/1999 -"Ines GmbH",INS,11/29/1996 -"Infineon Technologies AG",IFX,04/19/2000 -"Infinite Z",IFZ,01/04/2012 -"Informatik Information Technologies",IIT,08/14/2013 -Informtech,IFT,11/29/1996 -"Infotek Communication Inc",ICI,11/29/1996 -"Infotronic America, Inc.",ITR,06/21/2001 -"Inframetrics Inc",INF,11/29/1996 -"Ingram Macrotron",VSN,08/10/2000 -"Ingram Macrotron Germany",VID,05/24/2000 -"InHand Electronics",IHE,04/20/2010 -"Initio Corporation",INI,11/29/1996 -"Inmax Technology Corporation",IMT,02/12/2003 -"Innolab Pte Ltd",INO,01/20/1999 -"InnoLux Display Corporation",INL,12/15/2004 -"InnoMedia Inc",INM,11/29/1996 -"Innotech Corporation",ILS,10/23/2000 -"Innovate Ltd",ATE,11/29/1996 -"Innovent Systems, Inc.",INN,04/19/2000 -"Innoware Inc",WII,01/30/1998 -"Inovatec S.p.A.",inu,03/15/2001 -"Inside Contactless",ICV,11/04/2010 -"Inside Out Networks",ION,12/28/1998 -"Insignia Solutions Inc",ISG,11/29/1996 -"INSIS Co., LTD.",ISR,02/12/2003 -"Institut f r angewandte Funksystemtechnik GmbH",IAF,03/20/1999 -"Integraph Corporation",ING,11/29/1996 -"Integrated Business Systems",IBC,11/29/1996 -"Integrated Device Technology, Inc.",IDP,01/27/2010 -"Integrated Tech Express Inc",ITE,11/29/1996 -"Integrated Tech Express Inc",SRC,11/29/1996 -"integrated Technology Express Inc",ITX,06/25/1997 -"Integration Associates, Inc.",IAI,03/17/2004 -"Intel Corp",ICO,08/10/2000 -"Intelligent Instrumentation",III,11/29/1996 -"Intelligent Platform Management Interface (IPMI) forum (Intel, HP, NEC, Dell)",IPI,05/24/2000 -"Intelliworxx, Inc.",IWX,05/16/1999 -"Intellix Corp.",SVC,01/18/2008 -"Interaction Systems, Inc",TCH,03/20/1999 -"Interactive Computer Products Inc",PEN,01/15/1997 -"Intercom Inc",ITC,11/29/1996 -"Interdigital Sistemas de Informacao",IDS,10/28/1997 -"Interface Corporation",FBI,11/29/1996 -"Interface Solutions",ISI,11/29/1996 -"Intergate Pty Ltd",IGC,11/29/1996 -"Interlace Engineering Corporation",IEC,11/29/1996 -"Interlink Electronics",IEI,10/16/1998 -"International Datacasting Corporation",IDC,02/25/1997 -"International Display Technology",IDT,05/16/2002 -"International Integrated Systems,Inc.(IISI)",ISY,08/10/2000 -"International Microsystems Inc",IMI,11/29/1996 -"International Power Technologies",IPT,04/11/1997 -"Internet Technology Corporation",ITD,12/05/1997 -"Interphase Corporation",INP,11/29/1996 -"Interphase Corporation",INT,11/29/1996 -"Intersil Corporation",LSD,03/14/2012 -"Intersolve Technologies",IST,03/20/1999 -Inter-Tel,ITL,03/21/1997 -"Intertex Data AB",IXD,11/29/1996 -"Intervoice Inc",IVI,11/29/1996 -"Intevac Photonics Inc.",IVS,02/16/2011 -"Intracom SA",ICM,08/03/1998 -"Intrada-SDD Ltd",SDD,11/21/2007 -"IntreSource Systems Pte Ltd",ISP,08/27/1997 -"Intuitive Surgical, Inc.",SRG,02/16/2006 -"Inventec Corporation",INA,09/13/2013 -"Inventec Electronics (M) Sdn. Bhd.",INE,07/21/1998 -"Inviso, Inc.",INV,10/23/2000 -"I-O Data Device Inc",IOD,11/29/1996 -"i-O Display System",IOS,03/15/2001 -Iomega,IOM,11/29/1996 -"IP Power Technologies GmbH",IPP,12/06/2010 -"IP3 Technology Ltd.",IPQ,11/11/2013 -"IPC Corporation",IPC,11/29/1996 -"IPM Industria Politecnica Meridionale SpA",IPM,09/23/1998 -"IPS, Inc. (Intellectual Property Solutions, Inc.)",IPS,09/05/2001 -"IPWireless, Inc",IPW,03/15/2001 -"ISIC Innoscan Industrial Computers A/S",IIC,07/23/2003 -"Isolation Systems",ISL,11/29/1996 -"ISS Inc",ISS,11/29/1996 -"Itausa Export North America",ITA,11/29/1996 -"Ithaca Peripherals",IPR,07/01/1997 -"ITK Telekommunikation AG",ITK,11/29/1996 -"ITM inc.",ITM,04/24/2001 -"IT-PRO Consulting und Systemhaus GmbH",ITP,10/23/2000 -"Jace Tech Inc",JCE,11/29/1996 -"Jaeik Information & Communication Co., Ltd.",JIC,10/23/2000 -"Jan Strapko - FOTO",XFG,05/07/2001 -"Janich & Klass Computertechnik GmbH",JUK,10/08/2002 -"Janz Automationssysteme AG",JAS,11/03/2009 -"Japan Aviation Electronics Industry, Limited",JAE,03/15/2001 -"Japan Digital Laboratory Co.,Ltd.",JDL,04/19/2000 -"Japan Display Inc.",JDI,04/18/2013 -"Jaton Corporation",JAT,09/24/1997 -"JET POWER TECHNOLOGY CO., LTD.",JET,03/15/2001 -"Jetway Information Co., Ltd",JWY,09/22/2003 -"jetway security micro,inc",JTY,11/11/2009 -"Jiangsu Shinco Electronic Group Co., Ltd",SHI,08/10/2004 -"Jones Futurex Inc",JFX,11/29/1996 -"Jongshine Tech Inc",LTI,11/29/1996 -"Josef Heim KG",HKG,11/29/1996 -"JPC Technology Limited",JPC,10/23/2000 -"JS DigiTech, Inc",JSD,10/23/2000 -"JS Motorsports",JTS,12/05/1997 -Junnila,TPJ,03/15/2001 -"Jupiter Systems",JUP,09/05/2006 -"Jupiter Systems, Inc.",JSI,06/14/2007 -JVC,JVC,10/23/2000 -"JVC KENWOOD Corporation",JKC,03/08/2012 -"JWSpencer & Co.",JWS,07/16/1999 -"Kansai Electric Company Ltd",SGE,12/04/1996 -"Kaohsiung Opto Electronics Americas, Inc.",HIQ,03/14/2012 -"Karn Solutions Ltd.",KSL,05/08/2006 -Karna,KAR,02/21/2000 -"Katron Tech Inc",KTN,11/29/1996 -"Kayser-Threde GmbH",KTG,11/29/1996 -"KDDI Technology Corporation",KDT,05/22/2012 -KDE,KDE,08/14/2001 -"KDS USA",KDS,11/29/1996 -"KEISOKU GIKEN Co.,Ltd.",KGL,04/17/2012 -"Kensington Microware Ltd",KML,11/29/1996 -"Kenwood Corporation",KWD,02/22/2008 -KEPS,EPS,11/29/1996 -"Kesa Corporation",KES,11/29/1996 -"Key Tech Inc",KEY,11/29/1996 -"Key Tronic Corporation",KTK,11/29/1996 -"Keycorp Ltd",KCL,05/20/1997 -KeyView,KVX,08/13/2012 -"Kidboard Inc",KBI,04/24/1997 -"KIMIN Electronics Co., Ltd.",KME,02/15/2011 -"Kinetic Systems Corporation",KSC,11/29/1996 -"King Phoenix Company",KPC,11/29/1996 -"King Tester Corporation",KSX,07/16/1998 -"Kingston Tech Corporation",KTC,11/29/1996 -"Kionix, Inc.",KIO,12/23/2013 -"KiSS Technology A/S",KIS,06/16/2005 -"Klos Technologies, Inc.",PVP,08/10/2000 -"Kobil Systems GmbH",KBL,03/15/2001 -"Kobil Systems GmbH",KOB,03/15/2001 -"Kodiak Tech",KDK,11/29/1996 -"Kofax Image Products",KFX,11/29/1996 -"Kollmorgen Motion Technologies Group",KOL,11/29/1996 -"KOLTER ELECTRONIC",KOE,03/15/2001 -"Komatsu Forest",KFE,04/20/2010 -"Konica corporation",KNC,08/05/1997 -"Konica Technical Inc",KTI,11/29/1996 -"Kontron Electronik",TWE,11/29/1996 -"Kontron Embedded Modules GmbH",KEM,08/29/2007 -"Kontron Europe GmbH",KEU,02/20/2014 -"Korea Data Systems Co., Ltd.",KDM,12/18/2003 -"KOUZIRO Co.,Ltd.",KOU,07/27/2012 -"KOWA Company,LTD.",KOW,03/12/2008 -"Kramer Electronics Ltd. International",KMR,07/10/2013 -"Krell Industries Inc.",KRL,08/03/2004 -"Kroma Telecom",KRM,05/05/2010 -"Kroy LLC",KRY,07/16/1998 -K-Tech,KTE,03/31/2003 -"KUPA China Shenzhen Micro Technology Co., Ltd. Gold Institute",KSG,04/22/2014 -"Kurta Corporation",KUR,11/29/1996 -"Kvaser AB",KVA,01/24/1997 -"KYE Syst Corporation",KYE,11/29/1996 -"Kyocera Corporation",KYC,11/29/1996 -"Kyushu Electronics Systems Inc",KEC,01/12/1998 -"K-Zone International",KZN,06/21/2001 -"K-Zone International co. Ltd.",KZI,08/10/2000 -"L-3 Communications",LLL,05/11/2010 -"La Commande Electronique",LCE,11/29/1996 -"Labcal Technologies",LCT,11/08/1999 -"Labtec Inc",LTC,12/08/1997 -"Labway Corporation",LWC,12/04/1996 -LaCie,LAC,12/28/1998 -"Laguna Systems",LAG,11/29/1996 -"Land Computer Company Ltd",LND,11/29/1996 -"LANETCO International",LNT,05/02/2003 -"Lanier Worldwide",LWW,11/29/1996 -"Lars Haagh ApS",LHA,01/09/1997 -"LASAT Comm. A/S",LAS,11/29/1996 -"Laser Master",LMT,11/29/1996 -"Laserdyne Technologies",LDN,10/16/2013 -"Lasergraphics, Inc.",LGX,02/21/2000 -"Latitude Comm.",LCM,11/29/1996 -"Lava Computer MFG Inc",LAV,04/14/1997 -LCI,LCC,08/10/2000 -"Lectron Company Ltd",LEC,03/27/1997 -"Leda Media Products",LMP,05/11/1998 -"Legerity, Inc",LEG,01/18/2005 -"Leitch Technology International Inc.",LTV,12/09/2003 -Lenovo,LNV,07/14/2005 -"Lenovo Beijing Co. Ltd.",LIN,05/22/2012 -"Lenovo Group Limited",LEN,06/03/2005 -"Lexical Ltd",LEX,11/29/1996 -LEXICON,LCN,03/01/2005 -"Leutron Vision",PRS,11/29/1996 -"Lexmark Int'l Inc",LMI,11/29/1996 -"LG Semicom Company Ltd",LGS,11/29/1996 -LGIC,MAN,02/21/2000 -"LifeSize Communications",LSC,02/14/2006 -"Lighthouse Technologies Limited",LHT,05/04/2010 -"Lightware Visual Engineering",LWR,02/04/2009 -"Lightware, Inc",LTW,10/16/1998 -"Lightwell Company Ltd",LZX,12/02/1997 -"Likom Technology Sdn. Bhd.",LKM,04/23/1998 -"Linear Systems Ltd.",LNR,10/11/2007 -"Link Tech Inc",LNK,11/29/1996 -"Linked IP GmbH",LIP,07/19/2010 -"Lisa Draexlmaier GmbH",FGD,02/22/1999 -"Litelogic Operations Ltd",LOL,12/09/2011 -"Lite-On Communication Inc",LCI,11/29/1996 -"Lithics Silicon Technology",LIT,03/15/2001 -"Litronic Inc",LTN,02/03/1998 -"Locamation B.V.",LOC,01/09/2004 -"Loewe Opta GmbH",LOE,05/02/2005 -"Logic Ltd",LGC,04/02/1994 -"Logical Solutions",LSL,11/29/1996 -"Logicode Technology Inc",LOG,11/29/1996 -"Logitech Inc",LGI,11/29/1996 -"LogiDataTech Electronic GmbH",LDT,03/15/2001 -"Logos Design A/S",SGO,04/24/2001 -"Long Engineering Design Inc",LED,11/29/1996 -"Longshine Electronics Company",LCS,11/29/1996 -"Loughborough Sound Images",LSI,11/29/1996 -"LSI Japan Company Ltd",LSJ,11/29/1996 -"LSI Systems Inc",LSY,11/29/1996 -"LTS Scale LLC",LTS,11/15/2007 -Lubosoft,LBO,04/24/2001 -"Lucent Technologies",LUC,04/19/2000 -"Lucent Technologies",LMG,01/13/1997 -"Lucidity Technology Company Ltd",LTK,05/18/1998 -"Lumagen, Inc.",LUM,08/12/2004 -"Lung Hwa Electronics Company Ltd",LHE,06/12/1998 -Luxeon,LXN,03/15/2001 -"Luxxell Research Inc",LUX,06/09/1997 -"LVI Low Vision International AB",LVI,01/21/2011 -"LXCO Technologies AG",LXC,01/11/2012 -"MAC System Company Ltd",MAC,09/26/1997 -"Mac-Eight Co., LTD.",MEJ,01/19/2011 -"Macraigor Systems Inc",OCD,03/23/1998 -"Macrocad Development Inc.",VHI,04/19/2000 -"Macronix Inc",MXI,11/29/1996 -"Madge Networks",MDG,11/29/1996 -"Maestro Pty Ltd",MAE,12/04/1996 -"MAG InnoVision",MAG,11/29/1996 -"Magic Leap",MLP,11/14/2014 -"Magni Systems Inc",MCP,11/29/1996 -"MagTek Inc.",EKA,02/14/2006 -"Magus Data Tech",MDT,11/29/1996 -"Mainpine Limited",MPN,06/30/2007 -"Mainpine Limited",MUK,09/13/1999 -"Many CNC System Co., Ltd.",PAK,03/12/2004 -"Maple Research Inst. Company Ltd",MPL,11/29/1996 -"MARANTZ JAPAN, INC.",MJI,10/23/2000 -"Marconi Instruments Ltd",MIL,11/29/1996 -"Marconi Simulation & Ty-Coch Way Training",MRC,11/29/1996 -"Marina Communicaitons",MCR,11/29/1996 -"Mark Levinson",MLN,02/28/2005 -"Mark of the Unicorn Inc",MTU,03/21/1997 -"Marseille, Inc.",MNI,02/27/2013 -"Marshall Electronics",MBM,03/13/2006 -"Mars-Tech Corporation",MTC,11/29/1996 -"Maruko & Company Ltd",MRK,11/29/1996 -"MASPRO DENKOH Corp.",MSR,10/25/2012 -"Mass Inc.",MAS,02/25/2002 -"Matelect Ltd.",MEQ,05/30/2002 -Matrox,MTX,11/29/1996 -"Mat's Computers",MCQ,07/22/2004 -"Matsushita Communication Industrial Co., Ltd.",WPA,03/15/2001 -"Panasonic Connect Co.,Ltd.",MAT,04/01/2022 -"MaxCom Technical Inc",MTI,11/29/1996 -"MaxData Computer AG",VOB,02/21/2000 -"MaxData Computer GmbH & Co.KG",MXD,04/19/2000 -"Maxpeed Corporation",MXP,02/19/1997 -"Maxtech Corporation",MXT,11/29/1996 -"MaxVision Corporation",MXV,07/16/1999 -"Maygay Machines, Ltd",DJP,08/10/2000 -"Maynard Electronics",MAY,11/29/1996 -"MAZeT GmbH",MAZ,08/11/1998 -MBC,MBC,11/29/1996 -"McDATA Corporation",MCD,11/29/1996 -"McIntosh Laboratory Inc.",MLI,01/18/2008 -"MCM Industrial Technology GmbH",MIT,10/29/2004 -"MEC Electronics GmbH",CEM,04/19/2000 -"Medar Inc",MDR,12/11/1996 -"Media Technologies Ltd.",MTB,01/05/2009 -"Media Tek Inc.",MKC,06/14/2007 -"Media Vision Inc",MVI,11/29/1996 -"Media4 Inc",MDA,03/20/1997 -"Mediacom Technologies Pte Ltd",OWL,11/29/1996 -"Mediaedge Corporation",MEK,11/19/2013 -"MediaFire Corp.",MFR,12/28/1998 -Mediasonic,FTR,11/29/1996 -"MediaTec GmbH",MTE,12/13/1996 -"Mediatek Corporation",MDK,03/13/1997 -"Mediatrix Peripherals Inc",MPI,04/24/1997 -"Medikro Oy",MRO,09/19/1997 -"Mega System Technologies Inc",MEC,12/29/1997 -"Mega System Technologies, Inc.",MGA,12/28/1998 -"Megasoft Inc",MSK,11/29/1996 -"Megatech R & D Company",MGT,11/29/1996 -"Meld Technology",MEP,08/16/2012 -"MEN Mikroelectronik Nueruberg GmbH",MEN,05/23/1997 -"Mentor Graphics Corporation",MGC,07/30/2009 -MEPCO,RLD,03/15/2001 -MEPhI,PPD,11/27/1998 -"Merging Technologies",MRT,11/29/1996 -"Meridian Audio Ltd",MAL,02/04/2009 -"Messeltronik Dresden GmbH",MED,11/29/1996 -"MET Development Inc",MDV,11/29/1996 -"Meta Watch Ltd",MTA,08/29/2013 -"Metheus Corporation",MET,11/29/1996 -"Metricom Inc",MCM,11/29/1996 -"Metronics Inc",QCH,11/29/1996 -"Mettler Toledo",NET,11/29/1996 -"Metz-Werke GmbH & Co KG",MCE,06/30/2005 -"M-G Technology Ltd",MGL,10/29/1997 -"Micom Communications Inc",MIC,05/05/1997 -"Micomsoft Co., Ltd.",MSX,04/10/2008 -"Micro Computer Systems",MCS,11/29/1996 -"Micro Design Inc",MDI,01/20/1998 -"Micro Display Systems Inc",MDS,11/29/1996 -"Micro Firmware",MFI,12/30/1997 -"Micro Industries",MCC,04/21/2003 -"Micro Solutions, Inc.",BPD,04/19/2000 -"Micro Systemation AB",MSA,11/08/1999 -"Micro Technical Company Ltd",JMT,11/29/1996 -"Microbus PLC",MBD,08/13/2002 -Microcom,MNP,11/29/1996 -"MicroDatec GmbH",MDX,09/13/1999 -"MicroDisplay Corporation",MRD,06/14/2007 -"Microdyne Inc",MDY,12/18/1996 -"MicroField Graphics Inc",MFG,11/29/1996 -Microlab,MPJ,05/23/1997 -Microline,LAF,09/13/1999 -"Micrologica AG",MLG,10/06/1998 -"Micromed Biotecnologia Ltd",MMD,12/11/1996 -"Micromedia AG",MMA,04/24/1997 -"Micron Electronics Inc",MCN,02/20/1997 -"Micronics Computers",MCI,11/29/1996 -micronpc.com,MIP,08/10/2000 -"Micronyx Inc",MYX,11/29/1996 -"Micropix Technologies, Ltd.",MPX,10/08/2001 -"MicroSlate Inc.",MSL,05/16/1999 -Microsoft,PNP,03/05/2004 -Microsoft,MSH,11/29/1996 -Microsoft,PNG,11/29/1996 -MicroSoftWare,WBN,01/14/1998 -Microstep,MSI,11/29/1996 -Microtec,MCT,11/29/1996 -"Micro-Tech Hearing Instruments",MTH,12/15/1997 -"MICROTEK Inc.",MKT,07/14/2005 -"Microtek International Inc.",MTK,02/25/2002 -"MicroTouch Systems Inc",MSY,08/10/2000 -Microvision,MVS,02/13/2009 -"Microvitec PLC",MVD,11/29/1996 -"Microway Inc",MWY,11/29/1996 -"Midori Electronics",MDC,11/29/1996 -"Mikroforum Ring 3",SFT,11/02/2004 -"Milestone EPE",MLS,08/11/1998 -"Millennium Engineering Inc",MLM,11/29/1996 -"Millogic Ltd.",MLL,01/09/2014 -"Millson Custom Solutions Inc.",MCX,10/17/2013 -"Miltope Corporation",VTM,09/23/2009 -"Mimio – A Newell Rubbermaid Company",MIM,07/31/2012 -"MindTech Display Co. Ltd",MTD,06/14/2007 -"MindTribe Product Engineering, Inc.",FTW,02/14/2011 -"Mini Micro Methods Ltd",MNC,11/29/1996 -"Minicom Digital Signage",MIN,08/13/2010 -"MiniMan Inc",MMN,11/29/1996 -"Minnesota Mining and Manufacturing",MMF,03/15/2001 -"Miranda Technologies Inc",MRA,11/29/1996 -Miratel,MRL,10/16/1998 -"Miro Computer Prod.",MIR,11/29/1996 -"miro Displays",MID,03/20/1999 -"Mistral Solutions [P] Ltd.",MSP,09/23/1998 -"Mitec Inc",MII,11/29/1996 -"Mitel Corporation",MTL,08/01/1997 -"Mitron computer Inc",MTR,11/29/1996 -"Mitsubishi Electric Corporation",MEL,11/29/1996 -"Mitsubishi Electric Engineering Co., Ltd.",MEE,10/03/2005 -"Mitsumi Company Ltd",KMC,11/29/1996 -"MJS Designs",MJS,11/29/1996 -"MK Seiko Co., Ltd.",MKS,06/18/2013 -"M-Labs Limited",OHW,11/27/2013 -"MMS Electronics",MMS,02/24/1998 -"Modesto PC Inc",FST,02/27/1997 -MODIS,MDD,11/08/1999 -"Modular Industrial Solutions Inc",MIS,11/29/1996 -"Modular Technology",MOD,06/09/1997 -"Momentum Data Systems",MOM,01/18/2008 -"Monorail Inc",MNL,02/18/1997 -Monydata,MYA,11/29/1996 -"Moreton Bay",MBV,01/13/2000 -"Moses Corporation",MOS,11/29/1996 -"Mosgi Corporation",MSV,11/29/1996 -"Motion Computing Inc.",MCO,05/30/2002 -Motium,MTM,06/19/2012 -motorola,MSU,03/15/2001 -"Motorola Communications Israel",MCL,07/02/2002 -"Motorola Computer Group",MCG,08/14/1997 -"Motorola UDS",MOT,11/29/1996 -"Mouse Systems Corporation",MSC,11/29/1996 -"M-Pact Inc",MPC,11/29/1996 -"mps Software GmbH",MPS,11/29/1996 -"MS Telematica",MST,04/28/1997 -"MSC Vertriebs GmbH",MEX,06/04/2012 -"MSI GmbH",MSG,09/13/1999 -"M-Systems Flash Disk Pioneers",MSF,12/17/1997 -"Mtron Storage Technology Co., Ltd.",MTN,06/17/2008 -"Multi-Dimension Institute",MUD,10/23/2000 -Multimax,MMI,11/29/1996 -"Multi-Tech Systems",MTS,11/29/1996 -"Multiwave Innovation Pte Ltd",MWI,11/29/1996 -"Mutoh America Inc",MAI,09/13/1999 -mware,MWR,04/24/2001 -"Mylex Corporation",MLX,11/29/1996 -"Myriad Solutions Ltd",MYR,11/29/1996 -"Myse Technology",WYS,11/29/1996 -"N*Able Technologies Inc",NBL,04/28/1998 -"NAD Electronics",NAD,06/14/2007 -"Naitoh Densei CO., LTD.",NDK,04/12/2006 -"Najing CEC Panda FPD Technology CO. ltd",NCP,02/24/2015 -"Nakano Engineering Co.,Ltd.",NAK,07/22/2009 -"Nakayo Relecommunications, Inc.",NYC,08/10/2000 -"Nanomach Anstalt",SCS,11/29/1996 -"Nasa Ames Research Center",ADR,11/29/1996 -"National DataComm Corporaiton",NDC,11/29/1996 -"National Display Systems",NDI,08/08/2003 -"National Instruments Corporation",NIC,11/29/1996 -"National Key Lab. on ISN",NBS,07/16/1998 -"National Semiconductor Corporation",NSC,11/29/1996 -"National Semiconductor Japan Ltd",TTB,04/14/1997 -"National Transcomm. Ltd",NTL,11/29/1996 -"Nationz Technologies Inc.",ZIC,03/12/2009 -"Natural Micro System",NMS,11/29/1996 -"NaturalPoint Inc.",NAT,09/03/2010 -"Navatek Engineering Corporation",NVT,03/02/1998 -"Navico, Inc.",NME,11/28/2012 -"Navigation Corporation",NAV,02/22/1999 -"Naxos Tecnologia",NAX,12/12/1997 -"NCR Corporation",DUN,04/25/2002 -"NCR Corporation",NCC,11/29/1996 -"NCR Electronics",NCR,11/29/1996 -"NDF Special Light Products B.V.",NDF,09/18/2014 -"NDS Ltd",DMV,06/25/1997 -"NEC Corporation",NEC,05/24/2000 -"NEC CustomTechnica, Ltd.",NCT,10/23/2002 -"NEC-Mitsubishi Electric Visual Systems Corporation",NMV,02/25/2002 -"NEO TELECOM CO.,LTD.",NEO,11/08/1999 -Neomagic,NMX,11/29/1996 -"NeoTech S.R.L",NTC,11/11/1997 -"Netaccess Inc",NTX,02/07/1997 -"NetComm Ltd",NCL,11/29/1996 -"NetVision Corporation",NVC,11/29/1996 -"Network Alchemy",NAL,09/30/1997 -"Network Designers",NDL,11/29/1996 -"Network General",NGC,08/26/1997 -"Network Info Technology",NIT,11/29/1996 -"Network Peripherals Inc",NPI,11/29/1996 -"Network Security Technology Co",NST,02/22/1999 -"Networth Inc",NTW,11/29/1996 -"NeuroSky, Inc.",NSA,08/28/2013 -"NEUROTEC - EMPRESA DE PESQUISA E DESENVOLVIMENTO EM BIOMEDICINA",NEU,03/15/2001 -"New Tech Int'l Company",NTI,11/29/1996 -"NewCom Inc",NCI,01/09/1997 -"Newisys, Inc.",NWS,10/08/2002 -"Newport Systems Solutions",NSS,11/29/1996 -Nexgen,NXG,11/29/1996 -"Nexgen Mediatech Inc.,",NEX,11/11/2003 -"Nexiq Technologies, Inc.",NXQ,10/08/2001 -"Next Level Communications",NLC,11/29/1996 -"NextCom K.K.",NXC,11/29/1996 -"NingBo Bestwinning Technology CO., Ltd",NBT,09/05/2006 -"NINGBO BOIGLE DIGITAL TECHNOLOGY CO.,LTD",BOI,11/25/2009 -"Nippon Avionics Co.,Ltd",AVI,10/23/2000 -"NIPPONDENCHI CO,.LTD",GSB,05/24/2000 -"NISSEI ELECTRIC CO.,LTD",NSI,01/13/2000 -"Nissei Electric Company",NIS,11/29/1996 -"Nits Technology Inc.",NTS,12/19/2006 -"Nixdorf Company",NCA,11/29/1996 -NNC,NNC,11/29/1996 -"Nokia Data",NDS,11/29/1996 -"Nokia Display Products",NOK,11/29/1996 -"Nokia Mobile Phones",NMP,11/29/1996 -"Norand Corporation",NOR,03/19/1997 -"Norcent Technology, Inc.",NCE,06/20/2007 -"NordicEye AB",NOE,09/23/2009 -"North Invent A/S",NOI,05/04/2010 -"Northgate Computer Systems",NCS,11/29/1996 -"Not Limited Inc",NOT,01/30/1998 -"NovaWeb Technologies Inc",NWP,06/12/1998 -"Novell Inc",NVL,11/29/1996 -"Nspire System Inc.",NSP,02/13/2007 -"N-trig Innovative Technologies, Inc.",NTR,10/03/2005 -"NTT Advanced Technology Corporation",NTT,08/19/2004 -"NU Inc.",NUI,08/29/2007 -"NU Technology, Inc.",NUG,04/16/2004 -"Number Five Software",NFS,02/22/1999 -"Nutech Marketing PTL",KNX,11/29/1996 -"NuVision US, Inc.",NVI,09/05/2006 -"Nuvoton Technology Corporation",NTN,10/09/2008 -Nvidia,NVD,11/29/1996 -N-Vision,JEN,10/23/2000 -"NXP Semiconductors bv.",NXP,06/14/2007 -"NW Computer Engineering",NWC,02/03/1997 -"Oak Tech Inc",OAK,11/29/1996 -"Oasys Technology Company",OAS,11/29/1996 -"OBJIX Multimedia Corporation",OMC,11/29/1996 -"OCTAL S.A.",PCB,02/24/1998 -"Oculus VR, Inc.",OVR,10/19/2012 -Odrac,ODR,06/21/2001 -"Office Depot, Inc.",ATV,06/13/2007 -"OKI Electric Industrial Company Ltd",OKI,11/29/1996 -"Oksori Company Ltd",OQI,11/29/1996 -"Oksori Company Ltd",OSR,11/29/1996 -Olfan,OCN,11/29/1996 -"Olicom A/S",OLC,11/29/1996 -"Olidata S.p.A.",OLD,03/13/2006 -"Olitec S.A.",OLT,11/29/1996 -"Olitec S.A.",OLV,11/29/1996 -Olivetti,OLI,11/29/1996 -"OLYMPUS CORPORATION",OLY,05/02/2005 -OmniTek,OTK,09/19/2013 -Omnitel,OMN,04/28/1998 -"Omron Corporation",OMR,11/29/1996 -"On Systems Inc",ONS,11/29/1996 -"Oneac Corporation",ONE,04/14/1998 -"ONKYO Corporation",ONK,06/16/2005 -"OnLive, Inc",ONL,09/03/2010 -"OOO Technoinvest",TIV,08/05/1997 -"Opcode Inc",OPC,11/29/1996 -"Open Connect Solutions",OCS,09/13/1999 -"OPEN Networks Ltd",ONW,04/25/2003 -"Open Stack, Inc.",OSI,07/22/2013 -"OPPO Digital, Inc.",OPP,06/19/2012 -"OPTi Inc",OPT,11/29/1996 -"Optibase Technologies",OBS,11/01/2010 -"Optical Systems Design Pty Ltd",OSD,06/03/2013 -"Option Industrial Computers",OIC,05/07/2001 -"Option International",OIN,10/23/2000 -"Option International",OIM,01/30/1997 -"OPTI-UPS Corporation",OSP,07/01/1997 -"Optivision Inc",OPV,11/29/1996 -"OPTO22, Inc.",OTT,10/06/1998 -"Optoma Corporation          ",OTM,04/20/2010 -"Optum Engineering Inc.",OEI,08/02/2010 -"Orchid Technology",OTI,11/29/1996 -"ORGA Kartensysteme GmbH",ORG,10/24/1998 -"Orion Communications Co., Ltd.",TOP,04/30/2007 -"ORION ELECTRIC CO., LTD.",ORN,01/19/2005 -"ORION ELECTRIC CO.,LTD",OEC,01/13/2000 -"OSAKA Micro Computer, Inc.",OSA,09/05/2003 -"OSR Open Systems Resources, Inc.",ORI,01/20/1999 -OSRAM,OOS,04/25/2002 -"OUK Company Ltd",OUK,11/29/1996 -outsidetheboxstuff.com,OTB,09/03/2010 -"Oxus Research S.A.",OXU,11/29/1996 -"OZ Corporation",OZC,08/07/2012 -"Pacific Avionics Corporation",PAC,11/29/1996 -"Pacific CommWare Inc",PCW,11/29/1996 -"Pacific Image Electronics Company Ltd",PIE,10/21/1997 -"Packard Bell Electronics",PBL,11/29/1996 -"Packard Bell NEC",PBN,11/29/1996 -"PACSGEAR, Inc.",PGI,08/13/2012 -"Padix Co., Inc.",QFF,09/13/1999 -"Pan Jit International Inc.",PJT,08/03/2004 -Panasonic,MDO,11/29/1996 -"Panasonic Avionics Corporation",PLF,08/13/2010 -"Panasonic Industry Company",MEI,11/29/1996 -"Panelview, Inc.",PNL,08/04/2003 -"Pantel Inc",PTL,11/29/1996 -"PAR Tech Inc.",PTA,01/26/2011 -"Parade Technologies, Ltd.",PRT,04/06/2012 -"Paradigm Advanced Research Centre",PGM,06/16/2005 -"Parallan Comp Inc",PAR,11/29/1996 -"Parallax Graphics",PLX,11/29/1996 -"Parc d'Activite des Bellevues",RCE,11/29/1996 -Parrot,POT,11/25/2014 -"Pathlight Technology Inc",PTH,11/29/1996 -"PC Xperten",PCX,02/24/1998 -PCBANK21,PCK,02/13/2007 -"PCM Systems Corporation",PCM,03/25/1997 -"PC-Tel Inc",PCT,05/02/1997 -"PD Systems International Ltd",PDS,03/20/1999 -"PDTS - Prozessdatentechnik und Systeme",PDT,02/10/1998 -"Pegatron Corporation",PEG,08/27/2013 -"PEI Electronics Inc",PEI,04/06/1998 -"Penta Studiotechnik GmbH",PVM,05/05/2010 -"pentel.co.,ltd",PCL,02/25/2002 -"Peppercon AG",PEP,04/12/2006 -"Perceptive Pixel Inc.",PPX,05/04/2010 -"Perceptive Signal Technologies",PER,05/13/1997 -PerComm,PRC,04/24/2001 -"Performance Concepts Inc.,",PCO,09/24/2002 -"Performance Technologies",IPN,02/24/2004 -"Perle Systems Limited",PSL,02/22/1999 -"Perpetual Technologies, LLC",PON,01/13/2000 -"Peter Antesberger Messtechnik",PAM,04/28/1998 -"Peus-Systems GmbH",PSD,11/29/1996 -"Philips BU Add On Card",PCA,11/29/1996 -"Philips Communication Systems",PHS,11/29/1996 -"Philips Consumer Electronics Company",PHL,11/29/1996 -"Philips Medical Systems Boeblingen GmbH",PHE,04/20/2010 -"Philips Semiconductors",PSC,11/29/1996 -"Phoenix Contact",PXC,02/27/2008 -"Phoenix Technologies, Ltd.",PNX,11/08/1999 -"Phoenixtec Power Company Ltd",PPC,05/16/1999 -"Photonics Systems Inc.",PHO,06/03/2002 -PhotoTelesis,RSC,03/16/1998 -"Phylon Communications",PHY,11/29/1996 -PicPro,PPR,10/18/2004 -"Pijnenburg Beheer N.V.",PHC,04/24/2001 -"Pioneer Computer Inc",PCI,11/29/1996 -"Pioneer Electronic Corporation",PIO,07/16/1997 -"Pitney Bowes",PBV,09/13/1999 -"Pitney Bowes",PBI,11/29/1996 -"Pixel Qi",PQI,06/24/2009 -"Pixel Vision",PVN,11/29/1996 -"PIXELA CORPORATION",PXE,11/21/2007 -"Pixie Tech Inc",PIX,11/29/1996 -"Plain Tree Systems Inc",PTS,11/29/1996 -"Planar Systems, Inc.",PNR,08/11/2003 -"PLUS Vision Corp.",PLV,07/05/2001 -"PMC Consumer Electronics Ltd",PMC,12/11/1996 -"pmns GmbH",SPR,10/08/2002 -"Point Multimedia System",PMM,06/09/1997 -"Polycom Inc.",PLY,06/19/2002 -"PolyComp (PTY) Ltd.",POL,02/14/2006 -"Polycow Productions",COW,03/15/2001 -"Portalis LC",POR,11/01/2008 -"Poso International B.V.",ARO,08/01/1997 -"POTRANS Electrical Corp.",PEC,07/16/1999 -"PowerCom Technology Company Ltd",PCC,09/02/1997 -"Powermatic Data Systems",CPX,11/29/1996 -"Practical Electronic Tools",PET,02/22/1999 -"Practical Peripherals",PPI,11/29/1996 -"Practical Solutions Pte., Ltd.",PSE,10/06/1998 -"Praim S.R.L.",PRD,11/29/1996 -"Primax Electric Ltd",PEL,11/29/1996 -"Prime Systems, Inc.",SYX,10/21/2003 -"Prime view international Co., Ltd",PVI,07/06/2009 -"Princeton Graphic Systems",PGS,11/29/1996 -"Prism, LLC",PIM,07/24/2007 -"Priva Hortimation BV",PRI,10/22/1997 -PRO/AUTOMATION,PRA,07/16/1999 -"Procomp USA Inc",PCP,11/29/1996 -"Prodea Systems Inc.",PSY,02/04/2013 -"Prodrive B.V.",PDV,01/18/2005 -Projecta,PJA,01/29/1997 -"Projectavision Inc",DHT,01/14/1998 -"Projectiondesign AS",PJD,09/23/2002 -"PROLINK Microsystems Corp.",PLM,02/25/2002 -"Pro-Log Corporation",PLC,11/29/1996 -"Promate Electronic Co., Ltd.",PMT,01/13/2003 -Prometheus,PRM,11/29/1996 -"Promise Technology Inc",PTI,01/02/1997 -"Promotion and Display Technology Ltd.",PAD,04/24/2001 -"Promotion and Display Technology Ltd.",TEL,04/24/2001 -"propagamma kommunikation",PGP,04/19/2000 -Prosum,PSM,11/29/1996 -Proteon,PRO,11/29/1996 -"Proview Global Co., Ltd",PVG,10/08/2002 -"Proxim Inc",PXM,09/19/1997 -"Proxima Corporation",PRX,11/29/1996 -"PS Technology Corporation",PTC,01/29/1997 -"Psion Dacom Plc.",PDM,11/08/1999 -"PSI-Perceptive Solutions Inc",PSI,11/29/1996 -"PT Hartono Istana Teknologi",PLT,05/05/2010 -"Pulse-Eight Ltd",PUL,09/12/2012 -"Pure Data Inc",PDR,11/29/1996 -"Purup Prepress AS",PPP,11/29/1996 -"Qingdao Haier Electronics Co., Ltd.",HRE,04/12/2006 -Q-Logic,QLC,11/29/1996 -"Qtronix Corporation",QTR,11/29/1996 -Quadram,DHQ,11/29/1996 -Quadram,QDM,11/29/1996 -"Quadrant Components Inc",QCL,04/03/1997 -"QuakeCom Company Ltd",QCC,03/23/1998 -"Qualcomm Inc",QCP,05/16/1999 -"Quanta Computer Inc",QCI,11/29/1996 -"Quanta Display Inc.",QDS,04/25/2002 -Quantum,QTM,11/29/1996 -"Quantum 3D Inc",QTD,05/23/1997 -"Quantum Data Incorporated",QDI,03/15/2001 -Quartics,QVU,11/04/2010 -"Quatographic AG",QUA,01/13/2000 -"Questech Ltd",QTH,01/13/2000 -"Questra Consulting",QUE,01/30/1998 -"Quick Corporation",QCK,11/29/1996 -"Quickflex, Inc",QFI,08/04/1998 -"Quicknet Technologies Inc",QTI,11/29/1996 -"R Squared",RSQ,11/08/1999 -R.P.T.Intergroups,RPT,11/29/1996 -"Racal Interlan Inc",RII,11/29/1996 -"Racal-Airtech Software Forge Ltd",TSF,11/29/1996 -"Racore Computer Products Inc",RAC,11/29/1996 -"Radicom Research Inc",RRI,12/02/1997 -"Radio Consult SRL",RCN,09/24/2002 -"RADIODATA GmbH",RDN,07/25/2012 -"RadioLAN Inc",RLN,11/29/1996 -"Radiospire Networks, Inc.",RSN,06/14/2007 -"Radisys Corporation",RAD,11/29/1996 -"Radius Inc",RDS,03/07/1997 -"RAFI GmbH & Co. KG",RFI,08/24/2015 -"Rainbow Displays, Inc.",RDI,09/23/1998 -"Rainbow Technologies",RNB,11/29/1996 -"Raintree Systems",RTS,10/02/2001 -"Rainy Orchard",BOB,02/21/2000 -"Rampage Systems Inc",RSI,11/29/1996 -"Rancho Tech Inc",RAN,11/29/1996 -"Rancho Tech Inc",RTI,11/29/1996 -"Rapid Tech Corporation",RSX,11/29/1996 -"Raritan Computer, Inc",RMC,11/27/1998 -"Raritan, Inc.",RAR,06/14/2007 -"RAScom Inc",RAS,11/29/1996 -"RATOC Systems, Inc.",REX,01/06/2012 -"Raylar Design, Inc.",RAY,01/13/2000 -"RC International",RCI,11/29/1996 -"Reach Technology Inc",RCH,02/09/1998 -"Reakin Technolohy Corporation",RKC,03/15/2001 -"Real D",REA,11/15/2007 -"Realtek Semiconductor Company Ltd",RTL,11/29/1996 -"Realtek Semiconductor Corp.",ALG,10/25/2002 -"Realvision Inc",RVI,11/29/1996 -ReCom,REC,05/16/1999 -"Red Wing Corporation",RWC,01/08/1998 -"Redfox Technologies Inc.",RFX,01/14/2014 -"Reflectivity, Inc.",REF,04/19/2000 -"Rehan Electronics Ltd.",REH,02/15/2012 -"Relia Technologies",RTC,11/29/1996 -"Reliance Electric Ind Corporation",REL,11/29/1996 -"Renesas Technology Corp.",REN,06/14/2007 -Rent-A-Tech,RAT,02/22/1999 -"Research Electronics Development Inc",RED,12/02/1997 -"Research Machines",RMP,11/29/1996 -"ResMed Pty Ltd",RES,02/21/2000 -"Resonance Technology, Inc.",RET,02/09/2011 -"Restek Electric Company Ltd",WTS,11/29/1996 -"Reveal Computer Prod",RVL,11/29/1996 -"Revolution Display, Inc.",REV,03/19/2014 -"RGB Spectrum",RGB,11/14/2012 -"RGB Systems, Inc. dba Extron Electronics",EXN,07/06/2008 -"RICOH COMPANY, LTD.",RIC,05/13/2010 -"RightHand Technologies",RHD,05/01/2012 -"Rios Systems Company Ltd",RIO,11/29/1996 -"Ritech Inc",RIT,04/14/1998 -"Rivulet Communications",RIV,07/19/2007 -"Robert Bosch GmbH",BSG,05/15/2014 -"Robert Gray Company",GRY,03/31/1998 -"Robertson Geologging Ltd",RGL,08/10/2000 -"Robust Electronics GmbH",ROB,01/18/2008 -"Rockwell Automation/Intecolor",RAI,03/13/1998 -"Rockwell Collins",RCO,09/10/2010 -"Rockwell Collins / Airshow Systems",ASY,12/02/2004 -"Rockwell Collins, Inc.",COL,06/14/2007 -"Rockwell International",ROK,11/29/1996 -"Rockwell Semiconductor Systems",RSS,11/29/1996 -"Rogen Tech Distribution Inc",MAX,11/29/1996 -"Rohde & Schwarz",ROS,01/20/2012 -"Rohm Co., Ltd.",ROH,06/16/2004 -"Rohm Company Ltd",RHM,05/13/1997 -"Roland Corporation",RJA,11/29/1996 -"RoomPro Technologies",RPI,07/09/2010 -"Roper International Ltd",ROP,05/16/1999 -"Roper Mobile",RMT,07/02/2010 -"Ross Video Ltd",RSV,06/11/2012 -"Royal Information",TRL,11/29/1996 -"Rozsnyó, s.r.o.",RZS,03/24/2014 -"RSI Systems Inc",RVC,04/28/1998 -"RUNCO International",RUN,04/01/2004 -"S&K Electronics",SNK,02/21/2000 -"S3 Inc",TLV,01/07/1997 -"S3 Inc",SIM,11/29/1996 -"S3 Inc",SSS,11/29/1996 -"Saab Aerotech",SAE,06/14/2007 -"Sage Inc",SAI,07/16/1997 -SAGEM,SGM,09/05/2003 -SAIT-Devlonics,SDK,11/29/1996 -"Saitek Ltd",SAK,05/16/1999 -"Salt Internatioinal Corp.",SLT,09/05/2006 -"Samsung Electric Company",SAM,11/29/1996 -"Samsung Electro-Mechanics Company Ltd",SKT,11/29/1996 -"Samsung Electronics America",STN,08/10/2000 -"Samsung Electronics America Inc",KYK,02/24/1998 -"Samsung Electronic Co.",SSE,08/10/2000 -"Samsung Electronics Company Ltd",SEM,11/29/1996 -"Samtron Displays Inc",SDI,11/29/1996 -"SANKEN ELECTRIC CO., LTD",JSK,09/13/1999 -"Sankyo Seiki Mfg.co., Ltd",SSJ,01/28/2003 -"Sanritz Automation Co.,Ltd.",SAA,02/25/2002 -"SANTAK CORP.",STK,11/27/1998 -"Santec Corporation",SOC,01/12/2015 -"Sanyo Electric Co.,Ltd.",SAN,11/08/1999 -"Sanyo Electric Company Ltd",SCD,11/29/1996 -"Sanyo Electric Company Ltd",SIB,11/29/1996 -"Sanyo Electric Company Ltd",TSC,11/29/1996 -"Sanyo Icon",ICN,11/29/1996 -"Sapience Corporation",SPN,11/29/1996 -"SAT (Societe Anonyme)",SDA,11/29/1996 -"SBS Technologies (Canada), Inc. (was Avvida Systems, Inc.)",AVV,12/17/2002 -"SBS-or Industrial Computers GmbH",SBS,12/28/1998 -"Scan Group Ltd",SGI,11/29/1996 -"Scanport, Inc.",SCN,08/05/2002 -"SCD Tech",KFC,10/23/2002 -"Sceptre Tech Inc",SPT,11/29/1996 -Schlumberger,SMB,07/16/1999 -"Schlumberger Cards",SCH,04/28/1998 -"Schlumberger Technology Corporate",SLR,08/10/2000 -"Schneider & Koch",SKD,11/29/1996 -"Schneider Electric S.A.",MGE,11/29/1996 -"Schnick-Schnack-Systems GmbH",SLS,05/06/2009 -"SCI Systems Inc.",REM,08/10/2000 -"SCM Microsystems Inc",SCM,11/29/1996 -"Scriptel Corporation",SCP,06/14/2007 -"SDR Systems",SDR,03/15/2001 -"SDS Technologies",STY,11/08/1999 -"SDX Business Systems Ltd",SDX,11/29/1996 -"Seanix Technology Inc",NIX,04/09/2007 -"Seanix Technology Inc.",SEA,02/24/1998 -Sedlbauer,SAG,11/29/1996 -"SeeColor Corporation",SEE,11/29/1996 -"SeeCubic B.V.",SCB,11/02/2012 -"SeeReal Technologies GmbH",SRT,06/27/2005 -"Seiko Epson Corporation",SEC,11/29/1996 -"Seiko Instruments Information Devices Inc",SID,12/16/1996 -"Seiko Instruments USA Inc",SIU,11/29/1996 -"Seitz & Associates Inc",SEI,01/30/1998 -"Sejin Electron Inc",SJE,08/20/1997 -"SELEX GALILEO",SXG,10/01/2012 -"Semtech Corporation",STH,11/30/2001 -"SendTek Corporation",SET,11/08/1999 -"Senseboard Technologies AB",SBT,09/03/2002 -Sencore,SEN,05/23/1997 -"Sentelic Corporation",STU,07/27/2012 -"SEOS Ltd",SEO,02/20/2003 -"Sentronic International Corp.",SNC,10/23/2000 -"SEP Eletronica Ltda.",SEP,05/07/2001 -"Sequent Computer Systems Inc",SQT,11/29/1996 -"Session Control LLC",SES,09/03/2010 -Setred,SRD,09/05/2006 -"SEVIT Co., Ltd.",SVT,06/25/2002 -SGEG,SVA,02/21/2000 -"Seyeon Tech Company Ltd",SYT,12/02/1997 -"SGS Thomson Microelectronics",STM,11/11/1997 -"Shadow Systems",OYO,11/29/1996 -"Shanghai Bell Telephone Equip Mfg Co",SBC,04/30/1998 -"Shanghai Guowei Science and Technology Co., Ltd.",SGW,01/28/2011 -"SHANGHAI SVA-DAV ELECTRONICS CO., LTD",XQU,07/24/2003 -"Sharedware Ltd",SWL,08/11/1998 -"Shark Multimedia Inc",SMM,11/29/1996 -"SharkTec A/S",DFK,02/14/2006 -"Sharp Corporation",SHP,11/29/1996 -"SHARP TAKAYA ELECTRONIC INDUSTRY CO.,LTD.",SXT,06/24/2010 -"Shenzhen ChuangZhiCheng Technology Co., Ltd.",CZC,10/23/2013 -"Shenzhen Inet Mobile Internet Technology Co., LTD",IXN,11/04/2014 -"Shenzhen MTC Co., Ltd",SZM,08/09/2013 -"Shenzhen Ramos Digital Technology Co., Ltd",RMS,10/29/2014 -"Shenzhen South-Top Computer Co., Ltd.",SSL,12/06/2013 -"Shenzhen three Connaught Information Technology Co., Ltd. (3nod Group)",AZH,09/17/2013 -"Shenzhen Zhuona Technology Co., Ltd.",XYE,10/01/2013 -"Shenzhen ZhuoYi HengTong Computer Technology Limited",HTR,12/13/2013 -"Shenzhen Zowee Technology Co., LTD",ZWE,05/26/2015 -"Sherwood Digital Electronics Corporation",SDE,11/29/1996 -"ShibaSoku Co., Ltd.",SHC,05/26/2005 -"Shin Ho Tech",SHT,11/29/1996 -"Shlumberger Ltd",SLB,11/29/1996 -"Shuttle Tech",SAT,11/29/1996 -"Sichuan Changhong Electric CO, LTD.",CHG,02/26/2003 -"Sichuang Changhong Corporation",CHO,11/30/2001 -Siemens,SIE,11/29/1996 -"Siemens AG",SDT,02/14/2006 -"SIEMENS AG",SIA,03/15/2001 -"Siemens Microdesign GmbH",SNI,11/29/1996 -"Siemens Nixdorf Info Systems",SNP,11/29/1996 -"Sierra Semiconductor Inc",SSC,11/29/1996 -"Sierra Wireless Inc.",SWI,07/10/2003 -"Sigma Designs Inc",SIG,11/29/1996 -"Sigma Designs, Inc.",SGD,02/14/2006 -"Sigmacom Co., Ltd.",SCL,04/25/2002 -"SigmaTel Inc",STL,03/03/1997 -Signet,DXS,10/23/2000 -"SII Ido-Tsushin Inc",STE,04/03/1997 -"Silcom Manufacturing Tech Inc",SMT,11/29/1996 -"Silex technology, Inc.",SXD,03/12/2009 -"Silicom Multimedia Systems Inc",SMS,12/04/1996 -"Silicon Graphics Inc",SGX,11/29/1996 -"Silicon Image, Inc.",SII,01/13/2000 -"Silicon Integrated Systems Corporation",SIS,11/29/1996 -"Silicon Laboratories, Inc",SIL,07/16/1998 -"Silicon Library Inc.",SLH,11/01/2008 -"Silicon Optix Corporation",SOI,07/28/2005 -"Silitek Corporation",SLK,07/16/1997 -"SIM2 Multimedia S.P.A.",SPU,09/05/2002 -"Simple Computing",SMP,11/29/1996 -"Simplex Time Recorder Co.",SPX,03/15/2001 -"Singular Technology Co., Ltd.",SIN,11/08/1999 -"SINOSUN TECHNOLOGY CO., LTD",SNO,06/27/2005 -"Sirius Technologies Pty Ltd",SIR,03/13/1998 -"sisel muhendislik",FUN,04/25/2002 -"SITECSYSTEM CO., LTD.",STS,03/16/2005 -Sitintel,SIT,11/29/1996 -"SKYDATA S.P.A.",SKY,09/19/1997 -"Smart Card Technology",SCT,08/10/2000 -"SMART Modular Technologies",SMA,04/04/1997 -"Smart Silicon Systems Pty Ltd",SPL,08/10/2000 -"Smart Tech Inc",STI,11/29/1996 -"SMART Technologies Inc.",SBI,06/14/2007 -"SMK CORPORATION",SMK,02/21/2000 -"Snell & Wilcox",SNW,04/25/2002 -"SOBO VISION",MVM,06/14/2007 -"Socionext Inc.",SCX,05/14/2015 -"Sodeman Lancom Inc",LAN,11/29/1996 -"SODIFF E&T CO., Ltd.",SDF,06/01/2007 -"Soft & Hardware development Goldammer GmbH",SHG,11/29/1996 -"Softbed - Consulting & Development Ltd",SBD,12/23/1997 -"Software Café",SWC,11/29/1996 -"Software Technologies Group,Inc.",SWT,11/29/2008 -"Solitron Technologies Inc",SOL,11/29/1996 -"Solomon Technology Corporation",SLM,01/16/1998 -SolutionInside,SXL,05/08/2001 -"SOMELEC Z.I. Du Vert Galanta",ONX,11/29/1996 -Sonitronix,HON,02/03/2011 -"Sonix Comm. Ltd",SNX,11/29/1996 -Sony,SNY,11/29/1996 -Sony,SON,11/29/1996 -"Sony Ericsson Mobile Communications Inc.",SER,04/16/2004 -"SORCUS Computer GmbH",SCO,01/13/2000 -"Sorcus Computer GmbH",SOR,11/29/1996 -"SORD Computer Corporation",SCC,11/29/1996 -"Sotec Company Ltd",SOT,05/21/1997 -"South Mountain Technologies, LTD",FRS,02/14/2006 -"SOYO Group, Inc",SOY,12/18/2006 -"SPACE-I Co., Ltd.",SPI,05/11/2005 -"SpaceLabs Medical Inc",SMI,11/29/1996 -"SPEA Software AG",SPE,11/29/1996 -SpeakerCraft,SPK,04/20/2010 -Specialix,SLX,11/29/1996 -"Spectragraphics Corporation",SGC,11/29/1996 -"Spectrum Signal Proecessing Inc",SSP,11/29/1996 -"SR-Systems e.K.",SRS,11/19/2012 -"S-S Technology Inc",SSI,11/29/1996 -"ST Electronics Systems Assembly Pte Ltd",STA,12/28/1998 -"STAC Electronics",STC,11/29/1996 -"Standard Microsystems Corporation",SMC,11/29/1996 -"Star Paging Telecom Tech (Shenzhen) Co. Ltd.",STT,09/23/1998 -"Starflight Electronics",STF,05/23/1997 -"Stargate Technology",SGT,11/29/1996 -StarLeaf,SLF,11/01/2010 -"Starlight Networks Inc",STR,11/29/1996 -"Starwin Inc.",STW,04/24/2001 -Static,SWS,05/16/1999 -"STB Systems Inc",STB,11/29/1996 -"STD Computer Inc",STD,11/29/1996 -"StereoGraphics Corp.",STG,10/02/2001 -ST-Ericsson,STX,12/09/2011 -STMicroelectronics,SMO,06/14/2007 -"Stollmann E+V GmbH",STO,03/27/1997 -"Stores Automated Systems Inc",SAS,03/19/1997 -"Storm Technology",EZP,10/17/1996 -"StreamPlay Ltd",STP,02/04/2009 -"Stryker Communications",SYK,10/10/2005 -"Subspace Comm. Inc",SUB,11/29/1996 -"Sumitomo Metal Industries, Ltd.",SML,09/13/1999 -"Summagraphics Corporation",SUM,11/29/1996 -"Sun Corporation",SCE,11/29/1996 -"Sun Electronics Corporation",SUN,11/29/1996 -"Sun Microsystems",SVI,01/13/2003 -"SUNNY ELEKTRONIK",SNN,11/14/2014 -"SunRiver Data System",SDS,11/29/1996 -"Super Gate Technology Company Ltd",SGL,12/30/1997 -"SuperNet Inc",SNT,04/23/1998 -"Supra Corporation",SUP,11/29/1996 -"Surenam Computer Corporation",SUR,11/29/1996 -"Surf Communication Solutions Ltd",SRF,03/23/1998 -"SVD Computer",SVD,04/14/1998 -SVSI,SVS,08/09/2008 -"SY Electronics Ltd",SYE,09/20/2010 -"Sylvania Computer Products",SYL,06/12/1998 -"Symbios Logic Inc",SLI,11/29/1996 -"Symbol Technologies",ISA,06/02/1997 -"Symicron Computer Communications Ltd.",SYM,11/29/1996 -"Synaptics Inc",SYN,11/29/1996 -"Synopsys Inc",SPS,11/29/1996 -Syntax-Brillian,SXB,05/08/2006 -"SYPRO Co Ltd",SYP,11/27/1998 -"Sysgration Ltd",SYS,04/28/1997 -"Syslogic Datentechnik AG",SLC,01/20/1999 -"Sysmate Company",SME,09/02/1997 -"Sysmate Corporation",SIC,05/05/1997 -Sysmic,SYC,11/29/1996 -"Systec Computer GmbH",SGZ,10/02/1997 -"System Craft",SCI,11/29/1996 -"system elektronik GmbH",SEB,04/19/2000 -"Systeme Lauer GmbH&Co KG",SLA,03/20/1999 -"Systems Enhancement",UPS,11/29/1996 -"SystemSoft Corporation",SST,11/29/1996 -"Systran Corporation",SCR,11/29/1996 -"SYVAX Inc",SYV,11/29/1996 -"T+A elektroakustik GmbH",TUA,01/05/2011 -"Taicom Data Systems Co., Ltd.",TCD,10/08/2001 -"Taicom International Inc",TMR,11/29/1996 -"Taiko Electric Works.LTD",TKC,03/15/2001 -"Taiwan Video & Monitor Corporation",TVM,11/29/1996 -"Takahata Electronics Co.,Ltd.",KTD,07/22/2009 -"Tamura Seisakusyo Ltd",TAM,07/17/1997 -Tandberg,TAA,10/21/2003 -"Tandberg Data Display AS",TDD,11/29/1996 -"Tandem Computer Europe Inc",TDM,11/29/1996 -"Tandon Corporation",TCC,11/29/1996 -"Tandy Electronics",TDY,11/29/1996 -"Taskit Rechnertechnik GmbH",TAS,12/15/1997 -"Tatung Company of America Inc",TCS,11/29/1996 -"Tatung UK Ltd",VIB,07/16/1999 -"Taugagreining hf",NRV,11/29/1996 -"Taxan (Europe) Ltd",TAX,03/13/1997 -"TDK USA Corporation",PMD,11/29/1996 -TDT,TDT,11/29/1996 -"TDVision Systems, Inc.",TDV,01/18/2008 -"TEAC System Corporation",TEA,11/29/1996 -"TEC CORPORATION",CET,07/16/1998 -"TEAC America Inc",TCJ,11/29/1996 -"Tech Source Inc.",TEZ,08/14/2013 -"Techmedia Computer Systems Corporation",TMC,02/10/1998 -"Technical Concepts Ltd",TCL,11/29/1996 -"Technical Illusions Inc.",TIL,02/14/2014 -"TechniSat Digital GmbH",TSD,07/14/2005 -"Technology Nexus Secure Open Systems AB",NXS,05/08/1998 -"Technology Power Enterprises Inc",TPE,11/29/1996 -"TechnoTrend Systemtechnik GmbH",TTS,11/29/1996 -"Tecmar Inc",TEC,11/29/1996 -"Tecnetics (PTY) Ltd",TCN,11/29/1996 -"TECNIMAGEN SA",TNM,05/02/2005 -Tecnovision,TVD,03/13/2006 -"Tectona SoftSolutions (P) Ltd.,",RXT,06/02/2004 -"Teknor Microsystem Inc",TKN,11/29/1996 -"Tekram Technology Company Ltd",TRM,11/29/1996 -"Tektronix Inc",TEK,05/16/1999 -"TEKWorx Limited",TWX,12/24/2009 -"Telecom Technology Centre Co. Ltd.",TCT,07/16/1999 -"Telecommunications Techniques Corporation",TTC,11/29/1996 -"Teleforce.,co,ltd",TLF,11/19/2012 -"Teleliaison Inc",TAT,04/29/1997 -"Telelink AG",TLK,09/01/1998 -"Teleprocessing Systeme GmbH",TPS,01/24/1997 -"Teles AG",TAG,11/29/1996 -"Teleste Educational OY",TLS,11/29/1996 -"TeleVideo Systems",TSI,11/29/1996 -"Telia ProSoft AB",PFT,09/13/1999 -Telindus,TLD,11/29/1996 -"Telxon Corporation",TLX,11/29/1996 -"Tennyson Tech Pty Ltd",TNY,11/29/1996 -Teradici,TDC,10/11/2007 -"TerraTec Electronic GmbH",TER,03/21/1997 -"Texas Insturments",TXN,11/29/1996 -"Texas Microsystem",TMI,11/29/1996 -"Textron Defense System",TXT,11/29/1996 -"The Concept Keyboard Company Ltd",CKC,06/02/1997 -"The Linux Foundation",LNX,04/04/2014 -"The Moving Pixel Company",PXL,11/24/2003 -"The NTI Group",ITN,11/29/1996 -"The OPEN Group",TOG,09/13/1999 -"The Panda Project",PAN,11/29/1996 -"The Phoenix Research Group Inc",PRG,09/19/1997 -"The Software Group Ltd",TSG,11/29/1996 -"Thermotrex Corporation",TMX,11/29/1996 -Thinklogical,TLL,06/01/2015 -"Thomas-Conrad Corporation",TCO,11/29/1996 -"Thomson Consumer Electronics",TCR,08/20/1998 -"Thruput Ltd",TPT,06/16/2010 -"Thundercom Holdings Sdn. Bhd.",THN,03/21/1997 -"Tidewater Association",TWA,11/29/1996 -"Time Management, Inc.",TMM,03/20/1999 -"TimeKeeping Systems, Inc.",TKS,08/31/1998 -"Times (Shanghai) Computer Co., Ltd.",TPD,12/12/2013 -"TIPTEL AG",TIP,02/24/1998 -"Tixi.Com GmbH",TIX,10/16/1998 -"T-Metrics Inc.",TMT,02/21/2000 -"TNC Industrial Company Ltd",TNC,02/27/1998 -"Todos Data System AB",TAB,08/20/1997 -"TOEI Electronics Co., Ltd.",TOE,10/02/2001 -TONNA,TON,03/14/2012 -"Top Victory Electronics ( Fujian ) Company Ltd",TPV,05/16/1999 -"TOPRE CORPORATION",TPK,02/13/2009 -"Topro Technology Inc",TPR,05/08/1998 -"Topson Technology Co., Ltd.",TTA,09/23/1998 -"TORNADO Company",SFM,04/15/1997 -"Torus Systems Ltd",TGS,11/29/1996 -"Torus Systems Ltd",TRS,11/29/1996 -"Toshiba America Info Systems Inc",TAI,11/29/1996 -"Toshiba America Info Systems Inc",TSB,11/29/1996 -"Dynabook Inc.",TOS,11/29/1996 -"Toshiba Corporation",TTP,07/07/2015 -"Toshiba Global Commerce Solutions, Inc.",TGC,06/26/2012 -"Toshiba Matsushita Display Technology Co., Ltd",LCD,05/24/2000 -"TOSHIBA PERSONAL COMPUTER SYSTEM CORPRATION",PCS,06/22/2010 -"TOSHIBA TELI CORPORATION",TLI,01/18/2008 -"Totoku Electric Company Ltd",TTK,11/29/1996 -"Tottori Sanyo Electric",TSE,11/29/1996 -"Tottori SANYO Electric Co., Ltd.",TSL,11/06/2001 -"Touch Panel Systems Corporation",TPC,09/02/1997 -"TouchKo, Inc.",TKO,01/12/2006 -"Touchstone Technology",TOU,05/07/2001 -TouchSystems,TSY,01/18/2008 -"TOWITOKO electronics GmbH",TWK,04/14/1998 -"Transtex SA",CSB,03/15/2001 -"Transtream Inc",TST,04/29/1997 -TRANSVIDEO,TSV,05/04/2010 -Tremetrics,TRE,04/24/1997 -"Tremon Enterprises Company Ltd",RDM,11/29/1996 -"Trenton Terminals Inc",TTI,11/29/1996 -"Trex Enterprises",TRX,02/21/2000 -"Tribe Computer Works Inc",OZO,11/29/1996 -"Tricord Systems",TRI,11/29/1996 -"Tri-Data Systems Inc",TDS,11/29/1996 -"TRIDELITY Display Solutions GmbH",TTY,07/19/2010 -"Trident Microsystem Inc",TRD,11/29/1996 -"Trident Microsystems Ltd",TMS,07/15/2002 -"TriGem Computer Inc",TGI,11/29/1996 -"TriGem Computer,Inc.",TGM,07/05/2001 -"Trigem KinfoComm",TIC,02/26/2003 -"Trioc AB",TRC,01/13/2000 -"Triple S Engineering Inc",TBB,09/26/1997 -"Tritec Electronic AG",TRT,01/11/2012 -"TriTech Microelectronics International",TRA,01/24/1997 -"Triumph Board a.s.",TRB,09/27/2013 -"Trivisio Prototyping GmbH",TRV,11/18/2011 -"Trixel Ltd",TXL,08/10/2000 -"Trtheim Technology",MKV,03/17/1997 -Truevision,TVI,11/29/1996 -"TTE, Inc.",TTE,01/18/2005 -"Tulip Computers Int'l B.V.",TCI,11/29/1996 -"Turbo Communication, Inc",TBC,09/01/1998 -"Turtle Beach System",TBS,11/29/1996 -"Tut Systems",TUT,08/19/1997 -"TV Interactive Corporation",TVR,11/29/1996 -"TV One Ltd",TVO,09/02/2008 -"TV1 GmbH",TVV,02/06/2012 -"TVS Electronics Limited",TVS,05/20/2008 -"Twinhead International Corporation",TWH,11/29/1996 -"Tyan Computer Corporation",TYN,11/29/1996 -"U. S. Electronics Inc.",USE,10/28/2013 -"U.S. Naval Research Lab",NRL,11/29/1996 -"U.S. Navy",TSP,10/17/2002 -"U.S. Digital Corporation",USD,11/29/1996 -"U.S. Robotics Inc",USR,11/29/1996 -"Ubinetics Ltd.",UBL,05/23/2002 -"Ueda Japan Radio Co., Ltd.",UJR,07/09/2003 -"UFO Systems Inc",UFO,11/29/1996 -"Ultima Associates Pte Ltd",UAS,01/02/1997 -"Ultima Electronics Corporation",UEC,09/01/1998 -"Ultra Network Tech",ULT,11/29/1996 -"Umezawa Giken Co.,Ltd",UMG,04/10/2008 -"Ungermann-Bass Inc",UBI,11/29/1996 -Unicate,UNY,07/21/1998 -"Uniden Corporation",UDN,10/18/2004 -"Uniform Industrial Corporation",UIC,11/29/1996 -"Uniform Industry Corp.",UNI,11/06/2001 -UNIGRAF-USA,UFG,10/09/2008 -"Unisys Corporation",UNB,11/29/1996 -"Unisys Corporation",UNC,11/29/1996 -"Unisys Corporation",UNM,11/29/1996 -"Unisys Corporation",UNO,11/29/1996 -"Unisys Corporation",UNS,11/29/1996 -"Unisys Corporation",UNT,11/29/1996 -"Unisys DSD",UNA,11/29/1996 -"Uni-Take Int'l Inc.",WKH,06/17/2002 -"United Microelectr Corporation",UMC,11/29/1996 -Unitop,UNP,11/06/2001 -"Universal Electronics Inc",UEI,08/20/1997 -"Universal Empowering Technologies",UET,09/26/1997 -"Universal Multimedia",UMM,10/08/2001 -"Universal Scientific Industrial Co., Ltd.",USI,11/04/2003 -"University College",JGD,11/29/1996 -"Uniwill Computer Corp.",UWC,04/16/2004 -"Up to Date Tech",UTD,11/29/1996 -UPPI,UPP,04/14/1998 -"Ups Manufactoring s.r.l.",RUP,03/15/2001 -"USC Information Sciences Institute",ASD,04/08/1997 -"Utimaco Safeware AG",USA,05/04/1998 -"Vaddio, LLC",VAD,11/30/2012 -Vadem,VDM,11/29/1996 -"VAIO Corporation",VAI,04/18/2014 -"Valence Computing Corporation",VAL,11/29/1996 -"Valley Board Ltda",VBT,03/15/2001 -"ValleyBoard Ltda.",VLB,04/05/1998 -"Valve Corporation",VLV,03/06/2013 -"VanErum Group",ITI,10/01/2013 -"Varian Australia Pty Ltd",VAR,04/19/2000 -"VATIV Technologies",VTV,04/12/2006 -"VBrick Systems Inc.",VBR,08/19/2009 -VCONEX,VCX,06/15/2005 -"VDC Display Systems",VDC,04/29/2009 -"Vector Informatik GmbH",VEC,09/10/1997 -"Vector Magnetics, LLC",VCM,04/12/2006 -Vektrex,VEK,12/13/1996 -"VeriFone Inc",VFI,05/29/1998 -"Vermont Microsystems",VMI,11/29/1996 -"Vestax Corporation",VTX,02/14/2012 -"Vestel Elektronik Sanayi ve Ticaret A. S.",VES,09/19/1997 -"Via Mons Ltd.",VIM,08/29/2012 -"VIA Tech Inc",VIA,11/29/1996 -"Victor Company of Japan, Limited",VCJ,02/06/2009 -"Victor Data Systems",VDA,05/24/2000 -"Victron B.V.",VIC,11/29/1996 -"Video & Display Oriented Corporation",VDO,11/29/1996 -"Video Computer S.p.A.",URD,02/24/1998 -"Video International Inc.",JWD,02/21/2000 -"Video Products Inc",VPI,05/04/2010 -"VideoLan Technologies",VLT,10/17/1997 -VideoServer,VSI,06/25/1997 -"Videotechnik Breithaupt",VTB,07/23/2013 -"VIDEOTRON CORP.",VTN,05/04/2010 -"Vidisys GmbH & Company",VDS,11/29/1996 -"Viditec, Inc.",VDT,11/08/1999 -"ViewSonic Corporation",VSC,11/29/1996 -"Viewteck Co., Ltd.",VTK,10/08/2001 -"Viking Connectors",VIK,11/29/1996 -"Vinca Corporation",VNC,11/29/1996 -"Vinci Labs",NHT,03/03/2006 -"Vine Micros Limited",VML,06/16/2004 -"Vine Micros Ltd",VIN,04/19/2000 -"Virtual Computer Corporation",VCC,11/29/1996 -"Virtual Resources Corporation",VRC,11/29/1996 -"Vision Quest",VQ@,10/26/2009 -"Vision Systems GmbH",VSP,11/29/1996 -Visioneer,VIS,11/29/1996 -"Visitech AS",VIT,09/05/2006 -"Vislink International Ltd",VLK,08/27/2012 -"VistaCom Inc",VCI,11/29/1996 -"Visual Interface, Inc",VIR,11/27/1998 -"Vivid Technology Pte Ltd",VTL,11/29/1996 -"VIZIO, Inc",VIZ,06/06/2012 -"VLSI Tech Inc",VTI,11/29/1996 -"VMware Inc.,",VMW,10/18/2011 -"Voice Technologies Group Inc",VTG,04/24/1997 -"Vortex Computersysteme GmbH",GDT,11/29/1996 -"VPixx Technologies Inc.",VPX,12/05/2013 -"VRmagic Holding AG",VRM,04/12/2013 -"V-Star Electronics Inc.",VSR,02/21/2000 -"VTech Computers Ltd",VTS,11/29/1996 -"VTel Corporation",VTC,11/29/1996 -"Vutrix (UK) Ltd",VUT,07/22/2003 -"Vweb Corp.",VWB,03/12/2004 -"Wacom Tech",WAC,11/29/1996 -"Wallis Hamilton Industries",JPW,07/16/1999 -"Wanlida Group Co., Ltd.",MLT,04/24/2014 -"Wave Access",WAL,12/13/1996 -"Wave Systems",AWS,11/29/1996 -"Wave Systems Corporation",WVM,12/05/1997 -Wavephore,WAV,11/29/1996 -"Way2Call Communications",SEL,03/20/1997 -"WB Systemtechnik GmbH",WBS,09/08/1997 -W-DEV,WEL ,11/01/2010 -"Wearnes Peripherals International (Pte) Ltd",WPI,03/31/1998 -"Wearnes Thakral Pte",WTK,11/29/1996 -"WebGear Inc",WEB,01/30/1998 -"Westermo Teleindustri AB",WMO,01/13/2000 -"Western Digital",WDC,11/29/1996 -"Westinghouse Digital Electronics",WDE,05/23/2003 -"WEY Design AG",WEY,10/18/2004 -"Whistle Communications",WHI,10/24/1998 -"Wildfire Communications Inc",WLD,02/13/1997 -"WillNet Inc.",WNI,04/19/2000 -"Winbond Electronics Corporation",WEC,11/29/1996 -"Diebold Nixdorf Systems GmbH",WNX,09/20/2004 -"Winmate Communication Inc",WMT,03/15/2001 -"Winnov L.P.",WNV,03/07/1997 -"WiNRADiO Communications",WRC,09/11/1997 -"Wintop Technology Inc",WIN,12/29/1996 -"Wipotec Wiege- und Positioniersysteme GmbH",WWP,04/08/2014 -"WIPRO Information Technology Ltd",WIL,11/29/1996 -"Wipro Infotech",WIP,01/09/2004 -"Wireless And Smart Products Inc.",WSP,03/20/1999 -"Wisecom Inc",WCI,11/29/1996 -"Wistron Corporation",WST,09/03/2010 -"Wolfson Microelectronics Ltd",WML,07/30/1997 -"WolfVision GmbH",WVV,09/18/2012 -"Woodwind Communications Systems Inc",WCS,11/29/1996 -"Wooyoung Image & Information Co.,Ltd.",WYT,01/18/2008 -"WorkStation Tech",WTI,11/29/1996 -"World Wide Video, Inc.",WWV,10/24/1998 -"Woxter Technology Co. Ltd",WXT,09/03/2010 -"X-10 (USA) Inc",XTN,02/24/1997 -"X2E GmbH",XTE,09/23/2009 -"XAC Automation Corp",XAC,02/22/1999 -"XDM Ltd.",XDM,11/22/2010 -"Xedia Corporation",MAD,11/29/1996 -"Xilinx, Inc.",XLX,08/01/2007 -"Xinex Networks Inc",XIN,02/07/1997 -"Xiotech Corporation",XIO,05/29/1998 -"Xircom Inc",XRC,11/29/1996 -"Xitel Pty ltd",XIT,11/29/1996 -"Xirocm Inc",XIR,11/29/1996 -"XN Technologies, Inc.",XNT,07/14/2003 -XOCECO,UHB,11/27/1998 -"XORO ELECTRONICS (CHENGDU) LIMITED",XRO,05/23/2005 -"XS Technologies Inc",XST,01/20/1998 -"Xscreen AS",XSN,02/14/2006 -XSYS,XSY,04/23/1998 -"Yamaha Corporation",YMH,11/29/1996 -"Xycotec Computer GmbH",XYC,09/03/2002 -"Yasuhiko Shirai Melco Inc",BUF,11/29/1996 -"Y-E Data Inc",YED,11/29/1996 -"Yokogawa Electric Corporation",YHQ,11/29/1996 -"Ypoaz Systems Inc",TPZ,11/29/1996 -"Z Microsystems",ZMZ,08/10/2005 -"Z3 Technology",ZTT,12/14/2010 -"Zalman Tech Co., Ltd.",ZMT,05/07/2007 -"Zandar Technologies plc",ZAN,12/03/2003 -"ZeeVee, Inc.",ZAZ,01/18/2008 -"Zebra Technologies International, LLC",ZBR,09/15/2003 -"Zefiro Acoustics",ZAX,11/29/1996 -"ZeitControl cardsystems GmbH",ZCT,01/20/1999 -"ZENIC Inc.",ZEN,04/17/2015 -"Zenith Data Systems",ZDS,11/29/1996 -"Zenith Data Systems",ZGT,11/29/1996 -"Zenith Data Systems",ZSE,11/29/1996 -"Zetinet Inc",ZNI,11/29/1996 -"Zhejiang Tianle Digital Electric Co., Ltd.",TLE,01/17/2014 -"Zhong Shan City Richsound Electronic Industrial Ltd.",RSR,01/27/2015 -"Znyx Adv. Systems",ZNX,11/29/1996 -"Zoom Telephonics Inc",ZTI,11/29/1996 -"Zoran Corporation",ZRN,03/31/2005 -"Zowie Intertainment, Inc",ZOW,02/22/1999 -"ZT Group Int'l Inc.",ZTM,06/14/2007 -"ZTE Corporation",ZTE,09/03/2010 -"Zuniq Data Corporation",SIX,11/29/1996 -"Zydacron Inc",ZYD,04/10/1997 -"ZyDAS Technology Corporation",ZTC,05/24/2000 -"Zypcom Inc",ZYP,03/19/1997 -"Zytex Computers",ZYT,11/29/1996 -"Zytor Communications",HPA,07/02/2010 -"Alpha Electronics Company",AEJ,11/29/1996 -BOE,BOE,12/02/2004 -"Chaplet Systems Inc",FIR,11/29/1996 -"Chenming Mold Ind. Corp.",CMG,11/14/2003 -"coolux GmbH",COO,09/30/2010 -"Data General Corporation",DGC,11/29/1996 -Exabyte,EXA,11/29/1996 -"Herolab GmbH",HRL,03/17/1998 -"Hitachi Computer Products Inc",HCP,11/29/1996 -"Integrated Circuit Systems",ICS,11/29/1996 -Irdata,IRD,04/24/2001 -"Jewell Instruments, LLC",JWL,06/21/2001 -"MultiQ Products AB",MQP,03/20/1999 -"Ncast Corporation",NAC,02/14/2006 -"ODME Inc.",ODM,09/23/1998 -Photomatrix,PMX,11/29/1996 -"Quantum Solutions, Inc.",QSI,01/13/2000 -"Red Hat, Inc.",RHT,02/17/2011 -Zyxel,ZYX,11/29/1996 -"Carrera Computer Inc",JAZ,01/01/1994 -"Chunghwa Picture Tubes, LTD",CGA,01/01/1994 -"eMicro Corporation",EMC,01/01/1994 -"Hisense Electric Co., Ltd.",HEC,01/01/1994 -PanaScope,PNS,01/01/1994 -"SpinCore Technologies, Inc",SPC,01/01/1994 -"Sensics, Inc.",SVR,08/27/2015 -"IAdea Corporation",IAD,09/10/2015 -"Express Industrial, Ltd.",ELU,09/10/2015 -"Hewlett Packard Enterprise",HPE,09/22/2015 -"Klipsch Group, Inc",KGI,09/22/2015 -"Tek Gear",TKG,10/16/2015 -"HangZhou ZMCHIVIN",ZMC,10/16/2015 -"HTC Corportation",HVR,10/16/2015 -"Zebax Technologies",ZBX,10/16/2015 -"Guangzhou Shirui Electronics Co., Ltd.",SWO,10/16/2015 -"Picturall Ltd.",PIC,11/13/2015 -"Guangzhou Teclast Information Technology Limited",SKM,11/18/2015 -"GreenArrays, Inc.",GAC,11/18/2015 -"Thales Avionics",TAV,11/18/2015 -"Explorer Inc.",EXR,11/18/2015 -"Avegant Corporation",AVG,12/02/2015 -"MicroImage Video Systems",MIV,12/08/2015 -"ASUSTek COMPUTER INC",AUS,12/21/2015 -"Synthetel Corporation",STQ,12/21/2015 -"HP Inc.",HPN,12/21/2015 -"MicroTechnica Co.,Ltd.",MTJ,01/04/2016 -"Gechic Corporation",GEC,01/04/2016 -"MPL AG, Elektronik-Unternehmen",MEU,01/15/2016 -"Display Solution AG",DSA,02/03/2016 -"UEFI Forum",PRP,02/03/2016 -"Taitex Corporation",TTX,02/03/2016 -"EchoStar Corporation",ECH,02/26/2016 -"TCL Corporation",TOL,03/30/2016 -"ADDER TECHNOLOGY LTD",ADZ,03/30/2016 -"HKC OVERSEAS LIMITED",HKC,03/30/2016 -"KEYENCE CORPORATION",KYN,03/30/2016 -"TETRADYNE CO., LTD.",TET,04/27/2016 -"Abaco Systems, Inc.",ABS,04/27/2016 -"Meta Company",MVN,05/25/2016 -"Eizo Rugged Solutions",ERS,05/25/2016 -"VersaLogic Corporation",VLC,05/25/2016 -"CYPRESS SEMICONDUCTOR CORPORATION",CYP,05/25/2016 -"MILDEF AB",MDF,06/23/2016 -"FOVE INC",FOV,07/01/2016 -INNES,NES,07/01/2016 -"Hoffmann + Krippner GmbH",HUK,07/01/2016 -"Axell Corporation",AXE,08/03/2016 -UltiMachine,UMT,08/11/2016 -"TPK Holding Co., Ltd",KPT,08/16/2016 -"AAEON Technology Inc.",AAN,09/01/2016 -"Six15 Technologies",TDG,09/14/2016 -"Inlife-Handnet Co., Ltd.",IVR,01/19/2017 -"VR Technology Holdings Limited",DSJ,01/19/2017 -"Pimax Tech. CO., LTD",PVR,02/07/2017 -"Total Vision LTD",TVL,02/07/2017 -"Shanghai Lexiang Technology Limited",DPN,02/07/2017 -"Black Box Corporation",BBX,02/28/2017 -"TRAPEZE GROUP",TRP,02/28/2017 -"Pabian Embedded Systems",PMS,02/28/2017 -"Televic Conference",TCF,02/28/2017 -"Shanghai Chai Ming Huang Info&Tech Co, Ltd",HYL,02/28/2017 -"Techlogix Networx",TLN,02/28/2017 -"G2TOUCH KOREA",GGT,05/25/2017 -"MediCapture, Inc.",MVR,05/25/2017 -"HOYA Corporation PENTAX Lifecare Division",PNT,05/25/2017 -"christmann informationstechnik + medien GmbH & Co. KG",CHR,05/25/2017 -Tencent,TEN,06/20/2017 -"VRstudios, Inc.",VRS,06/22/2017 -"Extreme Engineering Solutions, Inc.",XES,06/22/2017 -NewTek,NTK,06/22/2017 -"BlueBox Video Limited",BBV,06/22/2017 -"Televés, S.A.",TEV,06/22/2017 -"Avatron Software Inc.",AVS,08/23/2017 -"Positivo Tecnologia S.A.",POS,09/01/2017 -"VRgineers, Inc.",VRG,09/07/2017 -"Noritake Itron Corporation",NRI,11/13/2017 -"Matrix Orbital Corporation",MOC,11/13/2017 -"Elegant Invention",EIN,03/29/2018 -"Immersive Audio Technologies France",IMF,03/29/2018 -"Lightspace Technologies",LSP,03/29/2018 -"PixelNext Inc",PXN,03/29/2018 -"VRSHOW Technology Limited",TSW,03/29/2018 -"SONOVE GmbH",SNV,03/29/2018 -"Silex Inside",SXI,03/29/2018 -"Huawei Technologies Co., Inc.",HWV,04/25/2018 -"Varjo Technologies",VRT,11/17/2017 -"Japan E.M.Solutions Co., Ltd.",JEM,05/24/2018 -"QD Laser, Inc.",QDL,05/31/2018 -"VADATECH INC",VAT,07/09/2018 -"Medicaroid Corporation",MCJ,08/20/2018 -"Razer Taiwan Co. Ltd.",RZR,08/20/2018 -"GIGA-BYTE TECHNOLOGY CO., LTD.",GBT,09/05/2018 -"Kontron GmbH",KOM,09/05/2018 -"Convergent Engineering, Inc.",CIE,09/05/2018 -"WyreStorm Technologies LLC",WYR,09/05/2018 -"Astro HQ LLC",AHQ,09/05/2018 -"QSC, LLC",QSC,01/18/2019 -"Dimension Engineering LLC",DMN,02/06/2019 -"Shenzhen Dlodlo Technologies Co., Ltd.",DLO,04/29/2019 -"LENOVO BEIJING CO. LTD.",VLM,05/21/2019 -"Cammegh Limited",CRW,06/18/2019 -"Beihai Century Joint Innovation Technology Co.,Ltd",LHC,09/10/2019 -"Findex, Inc.",FDX,10/22/2019 -"Express Luck, Inc.",ELD,10/22/2019 -"LLC SKTB “SKIT”",SKI,10/22/2019 -"WOLF Advanced Technology",WLF,10/22/2019 -"BILD INNOVATIVE TECHNOLOGY LLC",BLD,10/22/2019 -"MIMO Monitors",MMT,10/22/2019 -Icron,ICR,10/22/2019 -"TECNART CO.,LTD.",PIS,10/22/2019 -"Moxa Inc.",MHQ,10/22/2019 -"Disguise Technologies",DSG,10/22/2019 -"Comark LLC",CMK,07/15/2020 -"Megapixel Visual Realty",MPV,07/15/2020 -Skyworth,SKW,07/15/2020 -"Meta View, Inc.",CFR,07/15/2020 -MILCOTS,MLC,07/15/2020 -"NZXT (PNP same EDID)_",NXT,07/15/2020 -"Unicompute Technology Co., Ltd.",UTC,10/19/2020 -"TECHNOGYM S.p.A.",TGW,01/08/2021 -"Clover Electronics",CLR,02/02/2021 -"Kyokko Communication System Co., Ltd.",KTS,02/18/2021 -"Terumo Corporation",TMO,02/02/2021 -"Micro-Star Int'l Co., Ltd.",CND,02/17/2021 -"Newline Interactive Inc.",NWL,12/03/2020 -"CORSAIR MEMORY Inc.",CRM,02/05/2021 -aviica,VAV,06/01/2021 -Monoprice.Inc,DMG,06/04/2021 -"Shenzhen KTC Technology Group",SKG,06/01/2021 -"Truly Semiconductors Ltd.",TLY,06/01/2021 -"Hitevision Group",HHT,03/08/2021 -"DLOGIC Ltd.",DLM,06/10/2021 -"Fun Technology Innovation INC.",FUL,06/10/2021 -"Guangxi Century Innovation Display Electronics Co., Ltd",IOC,06/10/2021 -"ICC Intelligent Platforms GmbH",EMR,06/10/2021 -"New H3C Technology Co., Ltd.",NHC,06/10/2021 -"Seco S.p.A.",SCG,06/10/2021 -"Silent Power Electronics GmbH",LCP,06/10/2021 -"NAFASAE INDIA Pvt. Ltd",NAF,06/18/2021 -"Pico Technology Inc.",PIR,06/18/2021 -"Life is Style Inc.",LIS,06/18/2021 -"Hansung Co., Ltd",HSN,06/18/2021 -"Hubei Century Joint Innovation Technology Co.Ltd",TTR,06/18/2021 -"Zake IP Holdings LLC (3B tech)",VIO,06/18/2021 -"PreSonus Audio Electronics",PAE,06/24/2021 -"C&T Solution Inc.",MXM,07/21/2021 -VARCem,VCE,07/21/2021 -"Nextorage Corporation",NXR,09/21/2021 -"Netvio Ltd.",NVO,09/21/2021 -"Beijing Guochengwantong Information Technology Co., Ltd.",STV,09/21/2021 -"Kopin Corporation",KOP,10/01/2021 -"Anker Innovations Limited",AKR,12/10/2021 -"SAMPO CORPORATION",SPO,12/10/2021 -"Shiftall Inc.",SFL,12/31/2021 -AudioControl,AUD,12/31/2021 -"Schneider Consumer Group",SCA,02/08/2022 -"NOLO CO., LTD.",NVR,04/08/2022 -"Riedel Communications Canada Inc.",RDL,04/08/2022 -"arpara Technology Co., Ltd.",IMX,04/08/2022 -Nreal,MRG,04/08/2022 -"Venetex Corporation",VNX,04/08/2022 -G.VISION,GVS,06/17/2022 -"Galaxy Microsystems Ltd.",GXL,06/17/2022 -"OZO Co.Ltd",OZD,06/17/2022 -"GoUp Co.,Ltd",GUP,06/24/2022 -"Eizo Technologies GmbH",ETG,06/24/2022 -"Steelseries ApS",SSG,09/23/2022 -"Weidmuller Interface GmbH & Co. KG",WMI,09/23/2022 -"Bigscreen, Inc.",BIG,09/23/2022 -"Jiangxi Jinghao Optical Co., Ltd.",OFI,09/23/2022 -"EverPro Technologies Company Limited",EVP,09/30/2022 -"NewCoSemi (Beijing) Technology CO.,Ltd.",NCV,09/30/2022 -"LG Display",LGD,09/30/2022 -"Tianma Microelectronics Ltd.",TMA,10/20/2022 -"Printronix LLC",PTX,10/20/2022 -Colorlight,KLT,10/20/2022 -"Beck GmbH & Co. Elektronik Bauelemente KG",BCK,10/20/2022 -"Shenzhen Soogeen Electronics Co., LTD.",SGN,11/29/2022 -"Emotiva Audio Corp. ",EAC,02/02/2023 -Delem,DLD,04/05/2023 -"UNIONMAN TECHNOLOGY CO., LTD",JLK,04/19/2023 -"Samsung Display Corp.",SDC,04/25/2023 -"KOGAN AUSTRALIA PTY LTD",KGN,05/09/2023 -"Brainlab AG",BRL,05/16/2023 -"Lincoln Technology Solutions",LNC,05/24/2023 -"Norxe AS",NXE,06/07/2023 -"Avocor Technologies USA, Inc",ATU,06/07/2023 -"HONOR Device Co., Ltd.",HNM,06/07/2023 -VITEC,VVI,06/12/2023 -"Dixon Technologies (India) Limited",DXN,07/31/2023 -"China Star Optoelectronics Technology Co., Ltd",CSW,08/02/2023 -RODE,OMG,08/03/2023 -AVARRO,RRO,08/07/2023 -"InfoVision Optoelectronics (Kunshan) Co.,Ltd China",IVO,09/01/2023 -"Moore Threads Virtual Display",MTT,09/11/2023 -"Lumens Digital Optics Inc.",LMS,10/04/2023 -"LUMINO Licht Elektronik GmbH",LLT,11/07/2023 -"Reonel Oy",RNL,01/04/2024 -DemoPad Software Ltd,DEM,01/04/2024 -"TeamViewer Germany GmbH",TMV,01/04/2024 -"Pixio USA",PXO,02/14/2024 -"ELARABY COMPANY FOR ENGINEERING INDUSTRIES",EEI,02/14/2024 -"Maxnerva Technology Services Limited",MNS,02/14/2024 -"Somnium Space Ltd.",SMN,02/29/2024 -"Raspberry PI",RPL,05/07/2024 -"DEIF A/S",DEF,05/10/2024 -"Moka International limited",MOK,05/23/2024 -"Shure Inc.",SHU,06/13/2024 -"Indicates an identity defined by CTS/DID Standards other than EDID",CID,06/28/2024 -"Daten Tecnologia",DTM,06/15/2024 -"LABAU Technology Corp.",LBC,08/05/2024 -"Xiaomi Corporation",XMI,08/05/2024 -"Airdrop Gaming LLC",ADG,09/03/2024 -"Ugreen Group Ltd.",UGR,01/28/2025 -"Barnfind Technologies",BFT,01/28/2025 \ No newline at end of file +"21ST CENTURY ENTERTAINMENT",BUT,2002-04-25 +"2-Tel B.V",TTL,1999-03-20 +"3Com Corporation",TCM,1996-11-29 +"3D Perception",TDP,2002-05-16 +3M,VSD,1998-10-16 +"3NOD Digital Technology Co. Ltd.",NOD,2014-12-11 +"A D S Exports",NGS,1998-07-16 +"A Plus Info Corporation",API,1996-11-29 +"A&R Cambridge Ltd.",ACG,2007-06-13 +"A/Vaux Electronics",AVX,2012-08-29 +"A+V Link",APV,2010-01-27 +"Aashima Technology B.V.",TRU,1998-05-08 +"Aava Mobile Oy",AAM,2013-08-13 +"ABBAHOME INC.",ABA,1999-11-08 +"Abeam Tech Ltd.",MEG,1996-11-29 +"Ably-Tech Corporation",ATC,1996-11-29 +"AboCom System Inc.",ABC,1997-03-28 +"ACC Microelectronics",WTC,1996-11-29 +"Access Works Comm Inc",AWC,1996-11-29 +"Acco UK Ltd.",PKA,2003-05-12 +"Accton Technology Corporation",ACC,1996-11-29 +Acculogic,ACU,1996-11-29 +"AccuScene Corporation Ltd",ASL,2007-06-13 +"Ace CAD Enterprise Company Ltd",ANT,1996-11-29 +"Acer Inc",CHE,1996-11-29 +"Acer Labs",ALI,1996-11-29 +"Acer Netxus Inc",ANX,1996-11-29 +"Acer Technologies",ACR,1996-11-29 +Acksys,ACK,1996-11-29 +"Acnhor Datacomm",ADC,1996-11-29 +Acon,CAL,1996-11-29 +"Acrolink Inc",ALK,1997-03-12 +"Acroloop Motion Control Systems Inc",ACM,1998-03-26 +"ACT Labs Ltd",LAB,1997-09-02 +"Actek Engineering Pty Ltd",ACE,1996-11-29 +"Actiontec Electric Inc",AEI,1996-11-29 +"ActivCard S.A",ACV,1998-05-08 +"Aculab Ltd",ACB,1996-11-29 +"Acutec Ltd.",ALM,1999-11-08 +"AD electronics",GLE,2000-04-19 +"Ad Lib MultiMedia Inc",ADM,1998-04-23 +"Adaptec Inc",ADP,1996-11-29 +"Adax Inc",ADX,1996-11-29 +ADC-Centre,RSH,1999-11-08 +"Add Value Enterpises (Asia) Pte Ltd",AVE,1999-01-10 +"Addi-Data GmbH",ADA,1996-11-29 +"ADI Systems Inc",ADI,1996-11-29 +"ADPM Synthesis sas",DPM,2000-08-10 +"Adrienne Electronics Corporation",AXB,1997-10-07 +Adtek,ADT,1996-11-29 +"Adtek System Science Company Ltd",ADK,1996-11-29 +"ADTI Media, Inc",FLE,2009-09-15 +"Adtran Inc",AND,1996-11-29 +"Advan Int'l Corporation",AGM,1998-05-26 +"Advance Computer Corporation",AVN,2010-06-10 +"Advanced Digital Systems",MSM,1996-11-29 +"Advanced Electronic Designs, Inc.",AED,2004-07-12 +"Advanced Engineering",RJS,1998-06-25 +"Advanced Gravis",GRV,1996-11-29 +"Advanced Integ. Research Inc",AIR,1996-11-29 +"Advanced Logic",ALR,1996-11-29 +"Advanced Micro Devices Inc",ADV,1996-11-29 +"Advanced Micro Peripherals Ltd",EVE,2011-11-18 +"Advanced Optics Electronics, Inc.",AOE,2004-04-20 +"Advanced Peripheral Devices Inc",ADD,1996-11-29 +"Advanced Research Technology",ABV,1997-01-16 +"Advanced Signal Processing Technologies",PSA,1999-09-13 +"Advantech Co., Ltd.",AHC,2007-06-13 +"Aerodata Holdings Ltd",ADH,1997-11-11 +"Aetas Peripheral International",AEP,1999-11-08 +"Aethra Telecomunicazioni S.r.l.",AET,1996-12-13 +"Agentur Chairos",CHS,2001-03-15 +"Agilent Technologies",AGT,2001-10-08 +"Ahead Systems",ASI,1996-11-29 +"AIMS Lab Inc",AIM,1998-03-13 +"Airlib, Inc",AYR,2000-02-21 +"Aironet Wireless Communications, Inc",AWL,1998-08-11 +"Aiwa Company Ltd",AIW,1996-11-29 +"AJA Video Systems, Inc.",AJA,2007-10-11 +"AKAMI Electric Co.,Ltd",AKE,2010-09-03 +"Akebia Ltd",AKB,1996-11-29 +"AKIA Corporation",AKI,1998-12-23 +"AL Systems",ALH,1999-01-20 +"Alacron Inc",ALA,1996-11-29 +"Alana Technologies",ALN,2000-01-13 +Alcatel,AOT,2001-11-06 +"Alcatel Bell",ABE,1996-11-29 +Aldebbaron,ADB,2001-03-15 +"Alenco BV",ALE,2014-05-20 +"ALEXON Co.,Ltd.",ALX,1999-09-13 +"Alfa Inc",AFA,1996-11-29 +"Algolith Inc.",ALO,2005-05-02 +"AlgolTek, Inc.",AGO,2013-10-23 +"Alien Internet Services",AIS,2001-06-21 +"Allen Bradley Company",ABD,1996-11-29 +"Alliance Semiconductor Corporation",ALL,1996-11-29 +"Allied Telesis KK",ATI,1996-11-29 +"Allied Telesyn International (Asia) Pte Ltd",ATA,1997-11-10 +"Allied Telesyn Int'l",ATK,1996-11-29 +"Allion Computer Inc.",ACO,2000-10-23 +"Alpha Data",XAD,2009-10-08 +"Alpha Telecom Inc",ATD,1997-09-26 +"Alpha-Top Corporation",ATP,1996-12-04 +"AlphaView LCD",ALV,2008-11-01 +"ALPS ALPINE CO., LTD.",APE,2013-01-22 +"ALPS ALPINE CO., LTD.",ALP,1996-11-29 +"ALPS ALPINE CO., LTD.",AUI,1996-11-29 +"Alta Research Corporation",ARC,1996-11-29 +"Altec Corporation",ALC,1998-08-04 +"Altec Lansing",ALJ,2000-01-13 +"ALTINEX, INC.",AIX,2001-04-24 +"Altmann Industrieelektronik",AIE,1996-11-29 +"Altos Computer Systems",ACS,1996-11-29 +"Altos India Ltd",AIL,1996-11-29 +"Alvedon Computers Ltd",CNC,1998-11-06 +"Ambient Technologies, Inc.",AMB,1999-05-16 +Altra,ALT,1996-11-29 +"Amdek Corporation",AMD,1996-11-29 +"America OnLine",AOL,1996-11-29 +"American Biometric Company",YOW,1999-05-16 +"American Express",AXP,1999-07-16 +"American Magnetics",AXI,2001-03-15 +"American Megatrends Inc",AMI,1996-11-29 +"American Nuclear Systems Inc",MCA,1997-02-12 +"American Power Conversion",CNB,2001-03-15 +"American Power Conversion",APC,1996-11-29 +"Amimon LTD.",AMN,2007-06-13 +"Amino Technologies PLC and Amino Communications Limited",AMO,2011-12-09 +"AMiT Ltd",AKL,1997-12-02 +"AMP Inc",AMP,1996-11-29 +"Amptron International Inc.",AII,2000-05-24 +"AMT International Industry",AMT,1996-11-29 +"AmTRAN Technology Co., Ltd.",AMR,2013-06-10 +"AMX LLC",AMX,2008-07-06 +Anakron,ANA,1999-11-08 +"Analog & Digital Devices Tel. Inc",ADN,1997-03-14 +"Analog Devices Inc",ADS,1996-11-29 +"Analog Way SAS",ANW,2014-01-22 +"Analogix Semiconductor, Inc",ANL,2005-10-10 +"Anchor Bay Technologies, Inc.",ABT,2006-02-14 +"Anatek Electronics Inc.",AAE,2004-05-25 +"Ancor Communications Inc",ACI,1996-11-29 +Ancot,ANC,1996-11-29 +"Anderson Multimedia Communications (HK) Limited",AML,2003-01-03 +"Andrew Network Production",ANP,1996-11-29 +"Anigma Inc",ANI,1996-11-29 +"Anko Electronic Company Ltd",ANK,1998-03-24 +"Ann Arbor Technologies",AAT,2001-04-24 +"an-najah university",BBB,2001-03-15 +"Anorad Corporation",ANO,2000-01-13 +"ANR Ltd",ANR,1996-11-29 +"Ansel Communication Company",ANS,1996-11-29 +"Antex Electronics Corporation",AEC,1996-11-29 +"AOpen Inc.",AOA,2001-11-06 +"AP Designs Ltd",APX,1997-12-08 +"Apache Micro Peripherals Inc",DNG,1997-11-11 +"Aplicom Oy",APL,2005-05-02 +"Appian Tech Inc",APN,1996-11-29 +"Apple Computer Inc",APP,1996-11-29 +AppliAdata,APD,1996-11-29 +"Applied Creative Technology",ACT,1996-11-29 +"Applied Memory Tech",APM,1996-11-29 +"Apricot Computers",ACL,1996-11-29 +"Aprilia s.p.a.",APR,1999-02-22 +"ArchiTek Corporation",ATJ,2014-01-22 +"Archtek Telecom Corporation",ACH,1997-01-15 +"Arcus Technology Ltd",ATL,1996-11-29 +"AREC Inc.",ARD,2013-07-08 +"Arescom Inc",ARS,1996-11-29 +Argolis,AGL,2001-03-15 +"Argosy Research Inc",ARI,1997-02-24 +"Argus Electronics Co., LTD",ARG,2004-06-04 +"Ariel Corporation",ACA,1996-12-13 +Arima,ARM,2004-04-07 +"Arithmos, Inc.",ADE,1999-07-16 +"Ark Logic Inc",ARK,1996-11-29 +"Arlotto Comnet Inc",ARL,1997-04-29 +"ARMSTEL, Inc.",AMS ,2011-02-25 +"Arnos Insturments & Computer Systems",AIC,1996-11-29 +"ARRIS Group, Inc.",ARR,2015-01-27 +"ART s.r.l.",IMB,2012-01-27 +"Artish Graphics Inc",AGI,1996-11-29 +Arvanics,NPA,2015-03-05 +"Asahi Kasei Microsystems Company Ltd",AKM,1996-11-29 +"Asante Tech Inc",ASN,1996-11-29 +"Ascom Business Systems",HER,1999-01-20 +"Ascom Strategic Technology Unit",ASC,1996-11-29 +"ASEM S.p.A.",ASM,2001-03-15 +"ASEM S.p.A.",AEM,1996-11-29 +"AseV Display Labs",ASE,1998-10-16 +"Ashton Bentley Concepts",ASH,2013-09-20 +"Asia Microelectronic Development Inc",AMA,1997-09-24 +"Ask A/S",ASK,1996-11-29 +"Askey Computer Corporation",DYN,1997-07-22 +"ASP Microelectronics Ltd",ASP,1996-11-29 +"Askey Computer Corporation",AKY,1997-04-02 +"Aspen Tech Inc",ACP,1996-11-29 +"AST Research Inc",AST,1996-11-29 +"Astec Inc",JAC,1996-11-29 +"ASTRA Security Products Ltd",ADL,1997-07-30 +"ASTRO DESIGN, INC.",ATO,2003-06-06 +"Asuscom Network Inc",ASU,1996-11-29 +AT&T,ATT,1996-11-29 +"AT&T Global Info Solutions",GIS,1996-11-29 +"AT&T Microelectronics",HSM,1996-11-29 +"AT&T Microelectronics",TME,1996-11-29 +"AT&T Paradyne",PDN,1996-11-29 +"Atelier Vision Corporation",AVJ,2015-02-24 +"Athena Informatica S.R.L.",ATH,1997-01-29 +"Athena Smartcard Solutions Ltd.",ATN,1999-09-13 +"Athenix Corporation",ATX,1996-11-29 +"ATI Tech Inc",BUJ,1996-11-29 +Atlantis,CFG,1996-11-29 +"ATM Ltd",ATM,1996-11-29 +"Atom Komplex Prylad",AKP,2000-10-23 +"Attachmate Corporation",AMC,1996-11-29 +"Attero Tech, LLC",FWA,2010-04-20 +"Audio Processing Technology Ltd",APT,1997-03-18 +AudioScience,ASX,1996-11-29 +"August Home, Inc.",AUG,2014-06-11 +"Auravision Corporation",AVC,1996-11-29 +"Aureal Semiconductor",AUR,1996-11-29 +"Autologic Inc",APS,1996-11-29 +"automated computer control systems",CLT,1999-09-13 +"Autotime Corporation",AUT,2001-10-08 +"Auvidea GmbH",AUV,2014-04-21 +"Avalue Technology Inc.",AVL,2011-11-18 +"Avance Logic Inc",ALS,1996-11-29 +"Avaya Communication",AVA,2001-03-15 +Avencall,AEN,2012-01-27 +"AVer Information Inc.",AVR,2010-05-07 +"Avid Electronics Corporation",AVD,1996-11-29 +"AVM GmbH",AVM,1996-11-29 +"Avolites Ltd",AAA,2012-02-17 +"Avocent Corporation",AVO,2000-10-23 +"Avtek (Electronics) Pty Ltd",AVT,1996-11-29 +"AWETA BV",ACD,1998-01-20 +Axel,AXL,1996-11-29 +"AXIOMTEK CO., LTD.",AXC,2005-05-02 +"Axonic Labs LLC",AXO,2012-06-21 +"Axtend Technologies Inc",AXT,1997-12-01 +"Axxon Computer Corporation",AXX,1996-11-29 +"AXYZ Automation Services, Inc",AXY,1998-08-11 +"Aydin Displays",AYD,2007-06-13 +"AZ Middelheim - Radiotherapy",AZM,2003-11-14 +"Aztech Systems Ltd",AZT,1996-11-29 +B&Bh,BBH,2003-01-17 +"B.& V. s.r.l.",SMR,1997-03-21 +"B.F. Engineering Corporation",BFE,1996-11-29 +"B.U.G., Inc.",BUG,2011-08-30 +"Bang & Olufsen",BNO,2003-05-16 +"Banksia Tech Pty Ltd",BNK,1996-11-29 +Banyan,BAN,1996-11-29 +BARC,BRC,2000-08-10 +"Barco Display Systems",BDS,1999-09-13 +"Barco GmbH",BCD,2011-03-07 +"Barco Graphics N.V",BGB,1996-11-29 +"Barco, N.V.",BPS,2000-09-12 +"Barco, N.V.",DDS,2000-10-23 +"Baug & Olufsen",BEO,1996-11-29 +"Beaver Computer Corporaton",BCC,1996-11-29 +"Beckhoff Automation",BEC,2002-04-25 +"Beckworth Enterprises Inc",BEI,1997-07-16 +"Beijing Aerospace Golden Card Electronic Engineering Co.,Ltd.",AGC,2001-06-21 +"Beijing AnHeng SecoTech Information Technology Co., Ltd.",AHS,2015-03-24 +"Beijing ANTVR Technology Co., Ltd.",ANV,2015-08-24 +"Beijing Northern Radiantelecom Co.",NRT,1999-03-20 +"Beko Elektronik A.S.",BEK,2005-06-15 +"Beltronic Industrieelektronik GmbH",BEL,2006-09-05 +"Benson Medical Instruments Company",BMI,1996-12-04 +"B&R Industrial Automation GmbH",BUR,1996-11-29 +"Best Buy",INZ,2004-06-04 +"Best Buy",VPR,2002-05-16 +"Best Power",BPU,1996-11-29 +"Biamp Systems Corporation",BIA,2015-05-14 +"BICC Data Networks Ltd",ICC,1996-11-29 +"Big Island Communications",BIC,1997-05-13 +"Billion Electric Company Ltd",BIL,1996-12-11 +"BioLink Technologies",BLN,2000-08-10 +"BioLink Technologies International, Inc.",BIO,2000-05-24 +"BIOMED Lab",BML,1997-05-22 +"Biomedical Systems Laboratory",BSL,1997-10-16 +BIOMEDISYS,BMS,2000-05-24 +"Biometric Access Corporation",BAC,1998-05-19 +"BioTao Ltd",BTO,2012-03-21 +"Bit 3 Computer",BIT,1996-11-29 +"Bit 3 Computer",BTC,1996-11-29 +"Bitfield Oy",BTF,1996-11-29 +"BitHeadz, Inc.",BHZ,2003-09-29 +"Bitworks Inc.",BWK,2003-07-10 +"Blackmagic Design",BMD,2012-09-13 +"Blonder Tongue Labs, Inc.",BDR,2008-09-16 +"Bloomberg L.P.",BLP,2008-09-16 +"Boca Research Inc",ZZZ,1997-02-13 +"Boca Research Inc",BRI,1996-11-29 +"BodySound Technologies, Inc.",BST,2008-03-12 +"Boeckeler Instruments Inc",BII,1996-10-17 +"Booria CAD/CAM systems",BCS,2005-05-11 +BOS,BOS,1997-07-03 +"Bose Corporation",BSE,2006-09-05 +"Boulder Nonlinear Systems",BNS,2008-03-12 +"Braemac Pty Ltd",BRA,2010-11-18 +"Braemar Inc",BRM,1997-10-07 +"Brahler ICS",BDO,1998-06-04 +"Brain Boxes Limited",BBL,2001-10-02 +"Bridge Information Co., Ltd",BRG,1998-08-11 +"BRIGHTSIGN, LLC",BSN,2012-02-28 +"Brilliant Technology",BTE,1996-11-29 +"Broadata Communications Inc.",BCI,2013-11-19 +Broadcom,BCM,2004-04-01 +"BROTHER INDUSTRIES,LTD.",BRO,2000-02-21 +"BTC Korea Co., Ltd",NFC,2002-02-25 +"Budzetron Inc",BGT,1996-11-29 +Bull,BUL,1998-02-03 +"Bull AB",BNE,1998-10-06 +Busicom,BLI,1998-08-11 +"BusTech Inc",BTI,1996-11-29 +BusTek,BUS,1996-11-29 +"Butterfly Communications",FLY,1997-05-05 +"Buxco Electronics",BXE,1996-11-29 +"byd:sign corporation",BYD,2008-04-10 +"C3PO S.L.",XMM,1998-03-03 +"CA & F Elettronica",CAC,1999-05-16 +"Cabletime Ltd",CBT,2010-05-04 +"Cabletron System Inc",CSI,1996-11-29 +Cache,CCI,1996-11-29 +CalComp,CAG,1996-11-29 +CalComp,CDP,1996-11-29 +"Calibre UK Ltd",CUK,2005-09-15 +"California Institute of Technology",CSO,1999-03-20 +"Cambridge Audio",CAM,2008-08-09 +"Cambridge Electronic Design Ltd",CED,1996-11-29 +"Cambridge Research Systems Ltd",CMR,2002-04-25 +"Canon Inc",CNN,1996-11-29 +"Canon Inc.",CAI,2001-11-06 +"Canonical Ltd.",UBU,2013-05-24 +"Canopus Company Ltd",CAN,1996-11-29 +"Capella Microsystems Inc.",CPM,2012-05-09 +"Capetronic USA Inc",CCP,1996-11-29 +"Capstone Visua lProduct Development",DJE,2008-10-09 +"Cardinal Company Ltd",CAR,1996-11-29 +"Cardinal Technical Inc",CRD,1996-11-29 +CardLogix,CLX,2001-03-15 +"Carina System Co., Ltd.",CKJ,2010-09-03 +"Carl Zeiss AG",CZE,2009-06-03 +"CASIO COMPUTER CO.,LTD",CAS,1998-10-06 +"Castles Automation Co., Ltd",CAA,2000-01-13 +"Cavium Networks, Inc",CAV,2011-02-02 +"C-C-C Group Plc",FVX,1998-05-04 +CCL/ITRI,CCL,1997-03-31 +"C-Cube Microsystems",CCC,1996-11-29 +C-DAC,CEP,1996-11-29 +"Cebra Tech A/S",CBR,1996-11-29 +"Cefar Digital Vision",CEF,1997-02-19 +"Centurion Technologies P/L",CEN,2000-10-23 +"Century Corporation",TCE,1996-11-29 +"Cerevo Inc.",CRV,2010-07-13 +Ceronix,CER,2008-09-02 +"Ceton Corporation",TOM,2014-05-08 +"CH Products",CHP,1997-04-24 +"ChangHong Electric Co.,Ltd",CHD,2001-11-30 +"Chase Research PLC",CHA,1996-11-29 +"Cherry GmbH",CHY,1999-05-16 +"Chi Mei Optoelectronics corp.",CMO,2001-03-15 +"CHIC TECHNOLOGY CORP.",CHM,1999-07-16 +"Chicony Electronics Company Ltd",CEC,1996-11-29 +"Chimei Innolux Corporation",CMN,2010-09-02 +"China Hualu Group Co., Ltd.",HLG,2013-05-13 +Chloride-R&D,CHL,1996-11-29 +"Christie Digital Systems Inc",CDG,2001-04-24 +"Chromatec Video Products Ltd",CVP,2013-08-09 +"Chrontel Inc",CHI,1996-11-29 +"Chunghwa Picture Tubes,LTD.",CHT,2001-03-15 +"Chunghwa Telecom Co., Ltd.",CTE,2002-05-16 +"Chunichi Denshi Co.,LTD.",KCD,2010-12-23 +"Chuomusen Co., Ltd.",QQQ,2002-08-07 +"Chyron Corp",CGS,2008-11-13 +Cine-tal,CNE,2007-06-13 +"Cipher Systems Inc",PTG,1996-11-29 +"Ciprico Inc",CIP,1996-11-29 +"Ciprico Inc",CPC,1996-11-29 +"Cirel Systemes",FPX,1996-11-29 +"Cirque Corporation",CRQ,1996-11-29 +"Cirrus Logic Inc",CIR,1996-11-29 +"Cirrus Logic Inc",CLI,1996-11-29 +"Cirtech (UK) Ltd",SNS,1997-08-20 +"CIS Technology Inc",WSC,1996-11-29 +"Cisco Systems Inc",CIS,1996-11-29 +"Citicom Infotech Private Limited",CIL,2000-08-10 +"Citifax Limited",CIT,1997-07-16 +"Citron GmbH",CIN,2005-07-28 +"Clarion Company Ltd",CLA,1996-11-29 +"Clarity Visual Systems",CVS,2000-01-13 +"Classe Audio",CLE,2006-02-16 +"Clevo Company",CLV,1998-01-30 +"Clinton Electronics Corp.",PPM,2003-10-01 +"Clone Computers",CLO,1996-11-29 +"Cloudium Systems Ltd.",CSL,2013-02-14 +"CMC Ltd",CMC,1996-11-29 +"C-Media Electronics",CMI,1996-11-29 +"CNet Technical Inc",JQE,1996-11-29 +"COBY Electronics Co., Ltd",COB,2007-06-13 +"CODAN Pty. Ltd.",COD,2000-10-23 +"Codec Inc.",COI,2001-11-30 +"Codenoll Technical Corporation",CDN,1996-11-29 +"COINT Multimedia Systems",CNT,1999-03-20 +Colin.de,CDE,2005-01-18 +"Colorado MicroDisplay, Inc.",CMD,1999-03-20 +"Colorado Video, Inc.",CVI,2012-08-15 +"COM 1",MVX,1996-11-29 +"Comex Electronics AB",CMX,2004-05-28 +"Comm. Intelligence Corporation",CIC,1996-11-29 +"COMMAT L.t.d.",CLD,2000-08-10 +"Communications Specialies, Inc.",SDH,2005-09-06 +"Communications Supply Corporation (A division of WESCO)",INX,2012-11-07 +"Compal Electronics Inc",CPL,1996-11-29 +"Compaq Computer Company",CPQ,1996-11-29 +"Compound Photonics",CPP,2013-10-01 +CompuAdd,CPD,1996-11-29 +"CompuMaster Srl",CMS,1999-02-22 +"Computer Diagnostic Systems",CDS,2001-03-15 +"Computer Peripherals Inc",CPI,1996-11-29 +"Computer Technology Corporation",CTP,1998-03-26 +"ComputerBoards Inc",CBI,1998-02-03 +"Computerm Corporation",CTM,1996-11-29 +"Computone Products",CTN,1996-11-29 +Comrex,COX,2011-10-18 +"Comtec Systems Co., Ltd.",CTS,2002-04-25 +"Comtime GmbH",CMM,2002-09-23 +"Comtrol Corporation",COM,1996-11-29 +"Concept Development Inc",CDI,1996-11-29 +"Concept Solutions & Engineering",CSE,1996-12-11 +"Concepts Inc",DCI,1996-11-29 +"Conexant Systems",CXT,1999-01-20 +"congatec AG",CGT,2011-06-16 +"Connect Int'l A/S",CNI,1996-11-29 +"Connectware Inc",CWR,1996-11-29 +"CONRAC GmbH",CRC,2004-04-20 +"Consultancy in Advanced Technology",CAT,1997-09-19 +"Consumer Electronics Association",CEA,2006-09-05 +"CONTEC CO.,LTD.",CCJ,2000-08-10 +"Contec Company Ltd",CON,1996-11-29 +"Contemporary Research Corp.",CRH,2015-02-24 +"Control4 Corporation",CTR,2014-05-28 +"Convergent Data Devices",CDD,2004-02-27 +"Convergent Design Inc.",CDV,2006-09-05 +"Core Dynamics Corporation",CDC,1996-11-29 +"Corion Industrial Corporation",ART,1996-11-29 +"Core Technology Inc",COT,2000-04-19 +CoreLogic,CLG,1998-11-27 +"Cornerstone Imaging",CRN,1996-11-29 +"Corollary Inc",COR,1996-12-13 +"Cosmic Engineering Inc.",CSM,2012-04-18 +"CoStar Corporation",COS,1996-11-29 +"CoSystems Inc",CTA,1998-10-24 +"Covia Inc.",CVA,2010-05-11 +cPATH,CPT,1998-03-09 +"CRALTECH ELECTRONICA, S.L.",CRA,2015-03-24 +"Cray Communications",CDK,1996-11-29 +"CRE Technology Corporation",IOA,1997-06-30 +"Creative Labs Inc",CRE,1996-11-29 +"Creative Logic  ",CRL,1997-10-16 +"Creative Technology Ltd",CTL,1996-11-29 +"Creatix Polymedia GmbH",CTX,1996-11-29 +"Crescendo Communication Inc",CRS,1996-11-29 +"Cresta Systems Inc",CSD,1997-08-01 +"Crestron Electronics, Inc.",CEI,2006-05-08 +"Crio Inc.",CRI,1999-09-13 +"Cromack Industries Inc",CII,1997-01-22 +"Crystal Computer",XTL,1996-11-29 +"Crystal Semiconductor",CSC,1996-11-29 +"CrystaLake Multimedia",CLM,1996-11-29 +"CSS Laboratories",CSS,1997-01-02 +"CSTI Inc",CST,1996-11-29 +"CTC Communication Development Company Ltd",CTC,1997-10-21 +"Cubix Corporation",CUB,1996-11-29 +"Curtiss-Wright Controls, Inc.",CWC,2013-04-05 +Cyberlabs,CYL,1998-04-14 +CyberVision,CYB,1997-05-13 +Cyberware,CYW,2000-02-21 +"Cybex Computer Products Corporation",CBX,1999-11-08 +"Cyclades Corporation",CYD,2001-05-07 +"Cylink Corporation",CYC,1996-11-29 +"Cyrix Corporation",CYX,1997-10-21 +"Cyrix Corporation",CRX,1997-03-21 +"Cytechinfo Inc",CYT,1998-03-13 +"Cyviz AS",CYV,2002-04-25 +"D&M Holdings Inc, Professional Business Company",DMP,2006-09-05 +"D.N.S. Corporation",OPI,1996-11-29 +"DA2 Technologies Corporation",DDA,2006-03-13 +"DA2 Technologies Inc",DAW,2005-09-06 +"Daewoo Electronics Company Ltd",DWE,1996-11-29 +"Dai Telecom S.p.A.",TLT,2003-06-04 +"Daintelecom Co., Ltd",DIN,1999-11-08 +"DAIS SET Ltd.",DAI,2000-02-21 +Daktronics,DAK,2004-06-23 +"Dale Computer Corporation",DCC,1996-11-29 +"Dancall Telecom A/S",DCT,1997-08-12 +"Danelec Marine A/S",DAN,2009-12-24 +"Danka Data Devices",DDD,1996-11-29 +"Daou Tech Inc",DAU,1996-11-29 +DAT,HCA,2001-03-15 +"Data Apex Ltd",DAX,1996-11-29 +"Data Display AG",DDI,2002-07-17 +"Data Expert Corporation",DXP,1996-11-29 +"Data Export Corporation",EXP,1996-11-29 +"Data Modul AG",DMO,2013-12-03 +"Data Price Informatica",EBH,2001-05-24 +"Data Race Inc",DRI,1997-07-30 +"Data Ray Corp.",DRC,2001-11-30 +"Data Translation",DTX,1996-11-29 +"Data Video",DVT,2007-02-13 +"Databook Inc",DBK,1996-11-29 +"Datacast LLC",DCD,1997-12-02 +"Datacommunicatie Tron B.V.",TRN,1996-11-29 +"Datacube Inc",DQB,1996-11-29 +"Datadesk Technologies Inc",DDT,1998-11-27 +"Datakey Inc",DKY,1998-04-06 +"Datalogic Corporation",LJX,1996-11-29 +"Datang Telephone Co",DTN,1998-09-23 +"Dataq Instruments Inc",DII,1996-11-29 +"Datasat Digital Entertainment",DDE,2011-11-18 +"Datatronics Technology Inc",DCV,1997-01-02 +"Datel Inc",DAT,1996-11-29 +"Datenerfassungs- und Informationssysteme",MSD,1998-03-16 +"Davicom Semiconductor Inc",DAV,1997-01-15 +"DAVIS AS",DAS,1998-02-03 +"DB Networks Inc",DBN,1997-12-01 +"DBA Hans Wedemeyer",HWC,1999-03-20 +"DCM Data Products",DCM,1996-11-29 +"Dearborn Group Technology",DGT,1997-11-11 +"DECIMATOR DESIGN PTY LTD",DXD,2012-03-06 +"Decros Ltd",DCR,1996-11-29 +"Deep Video Imaging Ltd",MLD,2003-08-14 +"DEI Holdings dba Definitive Technology",DFT,2011-12-09 +"Deico Electronics",DEI,1996-11-29 +"Dell Inc",DLL,2009-03-27 +"Dell Inc.",DEL,2009-12-09 +"Delphi Automotive LLP",DPH,2013-10-15 +"Delta Electronics Inc",DPC,1996-11-29 +"Delta Information Systems, Inc",DDV,2012-01-03 +DELTATEC,DTA,2009-03-13 +"Deltec Corporation",FPS,1996-11-29 +"DENON, Ltd.",DON,2004-04-01 +"Dension Audio Systems",DHD,2013-03-04 +"Densitron Computers Ltd",DEN,1999-09-13 +"Design & Test Technology, Inc.",DTT,2010-09-30 +"Design Technology",LPI,1996-11-29 +"Deterministic Networks Inc.",DNI,2000-04-19 +"Deutsche Telekom Berkom GmbH",BCQ,1997-08-12 +"Deutsche Thomson OHG",DTO,2007-06-14 +"Devolo AG",DVL,2002-05-30 +"Dextera Labs Inc",DXL,2009-12-09 +DFI,DFI,1996-11-29 +"DH Print",DHP,1996-11-29 +Diadem,DIA,1996-11-29 +"Diagsoft Inc",DGS,1996-11-29 +"Dialogue Technology Corporation",DCO,2004-06-16 +"Diamond Computer Systems Inc",DCS,1996-11-29 +"Diamond Lane Comm. Corporation",DLC,1996-11-29 +DiCon,DNV,2004-12-15 +"Dictaphone Corporation",DVD,1998-04-03 +"Diebold Inc.",DBD,2006-09-05 +"Digatron Industrie Elektronik GmbH",DAE,1997-02-24 +"DIGI International",DGI,1996-11-29 +"DigiBoard Inc",DBI,1996-11-29 +"Digicom S.p.A.",DIG,1996-11-29 +"Digicom Systems Inc",DMB,1998-03-13 +"Digicorp European sales S.A.",DGP,1997-05-22 +"Digiital Arts Inc",DGA,2007-06-14 +"Digipronix Control Systems",DXC,1999-07-16 +"Digital Acoustics Corporation",DAC,2000-05-24 +"Digital Audio Labs Inc",DAL,1996-11-29 +"Digital Communications Association",DCA,1996-11-29 +"Digital Discovery",SHR,1997-09-24 +"Schneider Electric Japan Holdings, Ltd.",PRF,2003-01-02 +"Digital Equipment Corporation",DEC,1996-11-29 +"Digital Processing Systems",DPS,1996-11-29 +"Digital Projection Limited",DPL,2002-07-09 +"DIGITAL REFLECTION INC.",DRD,2000-02-21 +"Digital Video System",DVS,1996-11-29 +"DigiTalk Pro AV",DPA,2000-10-23 +"Digital-Logic GmbH",DLG,2003-09-02 +"Digitan Systems Inc",DSI,1996-11-29 +"Digitelec Informatique Park Cadera",DLT,1996-11-29 +"Dimension Technologies, Inc.",DTE,2010-05-03 +"Dimond Multimedia Systems Inc",DMM,1996-11-29 +"Diseda S.A.",DIS,1996-11-29 +"Distributed Management Task Force, Inc. (DMTF)",DMT,2009-03-31 +"Diversified Technology, Inc.",DTI,1996-11-29 +"D-Link Systems Inc",ABO,1996-11-29 +"D-Link Systems Inc",DLK,1996-11-29 +"DNA Enterprises, Inc.",DNA,1998-09-01 +"DO NOT USE - AUO",AUO,2008-09-16 +"DO NOT USE - LPL",LPL,2008-09-16 +"DO NOT USE - PHI",PHI,1996-11-29 +"DO NOT USE - PTW",PTW,2009-09-09 +"DO NOT USE - PVC",PVC,2009-09-09 +"DO NOT USE - RTK",RTK,2009-09-09 +"DO NOT USE - SEG",SEG,2009-09-09 +"DO NOT USE - TNJ",TNJ,2009-09-09 +"DO NOT USE - UND",UND,1996-11-29 +"DO NOT USE - UNE",UNE,1996-11-29 +"DO NOT USE - UNF",UNF,1996-11-29 +"DO NOT USE - WAN",WAN,2009-09-09 +"DO NOT USE - XER",XER,2009-09-09 +"DO NOT USE - XOC",XOC,2009-09-09 +"Doble Engineering Company",DBL,1996-11-29 +DocuPoint,DPI,1996-11-29 +"Dolby Laboratories Inc.",DLB,2010-01-27 +"Dolman Technologies Group Inc",DOL,1997-11-11 +"Domain Technology Inc",DSP,1996-11-29 +"DOME imaging systems",DMS,2000-10-23 +"Dome Imaging Systems",DOM,1996-11-29 +"Dongguan Alllike Electronics Co., Ltd.",AIK,2015-04-11 +"Dosch & Amand GmbH & Company KG",DUA,1997-12-02 +"Dotronic Mikroelektronik GmbH",DOT,2002-06-28 +"dPict Imaging, Inc.",DIM,2008-02-12 +"DpiX, Inc.",DPX,1998-09-23 +DPT,DPT,1996-11-29 +"Dr. Bott KG",DRB,2002-04-25 +"Dr. Neuhous Telekommunikation GmbH",DNT,1996-11-29 +"Dragon Information Technology",DIT,1996-11-29 +"DRS Defense Solutions, LLC",DRS,2011-10-18 +"DS Multimedia Pte Ltd",DSD,2006-02-14 +"DSM Digital Services GmbH",DSM,1996-11-29 +"dSPACE GmbH",DCE,1996-12-16 +"DTC Tech Corporation",DTC,1996-11-29 +"DugoTech Co., LTD",DGK,2007-06-14 +"Dune Microsystems Corporation",DMC,1996-11-29 +"Dycam Inc",DYC,1998-01-08 +"Dymo-CoStar Corporation",DYM,1998-12-28 +"Dynamic Controls Ltd",DCL,2000-05-24 +"Dynax Electronics (HK) Ltd",DTK,1996-11-29 +"Dynax Electronics (HK) Ltd",DYX,1996-11-29 +"e.Digital Corporation",EDC,2000-10-23 +"E.E.P.D. GmbH",EEP,2007-06-14 +"Eagle Technology",EGL,1996-11-29 +"Eastman Kodak Company",KOD,2000-05-24 +"Eastman Kodak Company",EKC,1996-11-29 +"Easytel oy",TWI,1999-07-16 +"EBS Euchner Büro- und Schulsysteme GmbH",EBS,2013-02-05 +"Echo Speech Corporation",ECO,1996-11-29 +"Eclipse Tech Inc",ETI,1996-11-29 +"E-Cmos Tech Corporation",ECM,1996-11-29 +"Eden Sistemas de Computacao S/A",ESC,1996-11-29 +"Edimax Tech. Company Ltd",EDI,1996-11-29 +EDMI,EDM,1998-07-16 +"Edsun Laboratories",ELI,1996-11-29 +"EE Solutions, Inc.",EES,2003-04-16 +"EEH Datalink GmbH",EEH,1997-07-03 +"Efficient Networks",ENI,1996-11-29 +"Egenera, Inc.",EGN,2002-10-08 +"Eicon Technology Corporation",EIC,1996-11-29 +"EIZO GmbH Display Technologies",EGD,2009-02-13 +"Eizo Nanao Corporation",ENC,1998-12-28 +"EKSEN YAZILIM",EKS,2002-04-25 +"ELAD srl",ELA,2002-04-25 +"ELAN MICROELECTRONICS CORPORATION",ETD,2009-11-03 +"ELAN MICROELECTRONICS CORPORATION",TSH,2014-11-14 +"Elbit Systems of America",ESA,2009-06-15 +"ELCON Systemtechnik GmbH",ESG,1999-07-16 +"ELEA CardWare",LXS,1998-06-25 +"Elecom Company Ltd",ECP,1996-11-29 +"Elecom Company Ltd",ELE,1996-11-29 +"Electro Cam Corp.",ECA,2000-08-10 +"Electro Scientific Ind",ELC,1996-11-29 +"Electronic Measurements",MMM,1996-11-29 +"Electronic Trade Solutions Ltd",ETS,2002-08-20 +"Electronic-Design GmbH",EDG,1997-08-12 +"Electrosonic Ltd",ELL,1999-09-13 +"Element Labs, Inc.",ELT,2007-10-11 +"Elgato Systems LLC",EGA,2011-02-08 +"Elitegroup Computer Systems Company Ltd",ECS,1996-11-29 +"Elitegroup Computer Systems Company Ltd",UEG,1996-11-29 +"Elmeg GmbH Kommunikationstechnik",ELG,1996-11-29 +"Elmic Systems Inc",ELM,1996-11-29 +"ELMO COMPANY, LIMITED",EMO,2012-06-26 +"Elo TouchSystems Inc",ELO,1996-11-29 +"Elonex PLC",ELX,1996-11-29 +"El-PUSK Co., Ltd.",LPE,2001-08-14 +"ELSA GmbH",ELS,1996-11-29 +"ELTEC Elektronik AG",EAG,2014-11-25 +"Embedded computing inc ltd",EMB,2002-02-25 +"Embedded Solution Technology",EST,2000-05-24 +"Embrionix Design Inc.",EMD,2013-07-24 +"Emcore Corporation",EMK,2012-05-31 +"Emerging Display Technologies Corp",EDT,2009-08-18 +"EMG Consultants Inc",EMG,1996-11-29 +"EMiNE TECHNOLOGY COMPANY, LTD.",EME,2005-06-16 +Empac,EPC,1996-12-04 +"Emulex Corporation",EMU,1996-11-29 +"Enciris Technologies",ECI,2008-11-01 +"Enciris Technologies",ECT,2008-11-01 +"ENE Technology Inc.",ENE,2001-03-15 +"e-Net Inc",DTL,1997-10-16 +Enhansoft,EHN,2010-11-16 +"ENIDAN Technologies Ltd",END,2000-04-19 +"Ensemble Designs, Inc",ESD,2009-12-09 +"Ensoniq Corporation",ENS,1996-11-29 +"Enterprise Comm. & Computing Inc",ENT,1996-11-29 +"Envision Peripherals, Inc",EPI,1999-02-22 +"Eon Instrumentation, Inc.",EON,2015-01-15 +"EPiCON Inc.",EPN,1998-09-23 +"Epiphan Systems Inc. ",EPH ,2011-03-14 +"Epson Research",EHJ,1996-11-29 +"Equinox Systems Inc",EQX,1996-11-29 +"Equipe Electronics Ltd.",EQP,2005-07-14 +"Ergo Electronics",EGO,1996-11-29 +"Ergo System",ERG,1996-11-29 +"Ericsson Mobile Communications AB",ERI,1997-10-22 +"Ericsson Mobile Networks B.V.",EUT,1998-04-14 +"Ericsson, Inc.",ERN,1998-09-23 +ES&S,ESK,1999-11-08 +eSATURNUS,ESN,2012-02-21 +"Escort Insturments Corporation",ERT,1997-05-02 +"ESS Technology Inc",ESS,1996-11-29 +"ESSential Comm. Corporation",ECC,1996-11-29 +"Esterline Technologies",ESL,2012-01-06 +ScioTeq,ESB,2015-01-15 +"E-Systems Inc",ESY,1996-11-29 +"ET&T Technology Company Ltd",EEE,1998-05-04 +"E-Tech Inc",ETT,1996-11-29 +"eTEK Labs Inc.",ETK,1998-07-16 +"Etherboot Project",ETH,2010-07-09 +"Eugene Chukhlomin Sole Proprietorship, d.b.a.",ECK,2008-05-03 +"Euraplan GmbH",ERP,1996-11-29 +"Evans and Sutherland Computer",EAS,2003-01-28 +Everex,EVX,1996-11-29 +"Everton Technology Company Ltd",ETC,1997-04-10 +"Evertz Microsystems Ltd.",ETL,2007-06-14 +"eviateg GmbH",EVI,2000-02-21 +"Ex Machina Inc",EMI,1996-11-29 +"Exacom SA",YHW,1996-11-29 +"Exatech Computadores & Servicos Ltda",EXT,1998-09-23 +"Excel Company Ltd",ECL,1997-05-27 +"Excession Audio",EXC,1998-11-06 +"EXFO Electro Optical Engineering",XFO,1998-04-29 +"Exide Electronics",EXI,1996-11-29 +"Extended Systems, Inc.",ESI,1999-07-16 +"Exterity Ltd",EXY,2009-02-12 +"Extraordinary Technologies PTY Limited",CRO,2005-04-11 +"Exxact GmbH",EXX,1996-11-29 +"eyefactive Gmbh",EYF,2015-07-07 +"eyevis GmbH",EYE,2011-11-18 +"EzE Technologies",EZE,2005-02-21 +"F.J. Tieman BV",FJT,1998-06-25 +"Fairfield Industries",FFI,1996-11-29 +"Fantalooks Co., Ltd.",FAN,2014-03-12 +"Fanuc LTD",FNC,1997-01-29 +"Farallon Computing",FAR,1996-11-29 +"FARO Technologies",FRO,2012-09-21 +"Faroudja Laboratories",FLI,2004-06-02 +"Fast Multimedia AG",FMA,1996-11-29 +"FastPoint Technologies, Inc.",FTI,2001-06-21 +"Feature Integration Technology Inc.",FIT,2009-08-11 +"Fellowes & Questec",FEL,1996-11-29 +"Fellowes, Inc.",FMI,2001-07-05 +"Fen Systems Ltd.",FEN,2010-05-04 +"Ferranti Int'L",FER,1996-11-29 +"Ferrari Electronic GmbH",TLA,1996-12-04 +FHLP,FHL,1996-11-29 +"Fibernet Research Inc",FRI,1996-11-29 +"Finecom Co., Ltd.",FIN,1998-11-27 +"Fingerprint Cards AB",FPC,2013-06-14 +"First Industrial Computer Inc",PCG,1996-11-29 +"First International Computer Inc",LEO,1997-09-19 +"First International Computer Ltd",FCG,1997-04-10 +"First Virtual Corporation",FVC,1996-11-29 +"Flat Connections Inc",FWR,1996-11-29 +"FlightSafety International",SSD,2000-08-10 +"FLY-IT Simulators",FIS,1997-09-08 +"FocalTech Systems Co., Ltd.",FTS,2013-07-23 +"Focus Enhancements, Inc.",FCS,2002-12-12 +"Fokus Technologies GmbH",FOK,2013-10-22 +"FOR-A Company Limited",FOA,2008-12-06 +"Force Computers",FRC,1996-11-29 +"Ford Microelectronics Inc",FMC,1997-03-11 +"Fore Systems Inc",FSI,1996-11-29 +"Forefront Int'l Ltd",FIL,1996-11-29 +"Formosa Industrial Computing Inc",FIC,1996-11-29 +Formoza-Altair,FMZ,2003-04-25 +"Forth Dimension Displays Ltd",FDD,2015-07-07 +"Forvus Research Inc",FRE,1997-04-24 +"Foss Tecator",FOS,1997-10-22 +"Founder Group Shenzhen Co.",FZC,1999-11-08 +"Fountain Technologies Inc",FTN,1996-11-29 +"Fraunhofer Heinrich-Hertz-Institute",HHI,2012-07-27 +"Freedom Scientific BLV",FRD,2007-06-15 +"FREEMARS Heavy Industries",TCX,2001-03-15 +"Frontline Test Equipment Inc.",FTE,1999-01-20 +"FTG Data Systems",FTG,1996-11-29 +"Fuji Xerox",FXX,1996-11-29 +"FUJIFILM Corporation",FFC,2011-08-22 +"Fujitsu Display Technologies Corp.",FDT,2002-10-23 +"Fujitsu General Limited.",FGL,2000-02-21 +"Fujitsu Ltd",FUJ,1996-11-29 +"Fujitsu Microelect Ltd",FML,1996-11-29 +"Fujitsu Peripherals Ltd",FPE,1997-08-19 +"Fujitsu Siemens Computers GmbH",FUS,2000-01-13 +"Fujitsu Spain",FJS,1996-11-29 +"FCL COMPONENTS LIMITED",FJC,1999-05-16 +"FUJITSU TEN LIMITED",FTL,2011-12-20 +"Funai Electric Co., Ltd.",FNI,2005-01-18 +"Furukawa Electric Company Ltd",FCB,1996-11-29 +"FURUNO ELECTRIC CO., LTD.",FEC,1996-11-29 +"Future Designs, Inc.",FDI,2014-09-29 +"Future Domain",FDC,1996-11-29 +"Future Systems Consulting KK",FSC,1996-11-29 +"Futuretouch Corporation",FTC,1996-11-29 +"FZI Forschungszentrum Informatik",FZI,1997-08-12 +"G&W Instruments GmbH",SPH,2002-02-25 +"G. Diehl ISDN GmbH",GDI,1996-11-29 +"Gadget Labs LLC",GLS,1996-11-29 +"Gage Applied Sciences Inc",GAG,1996-11-29 +"GAI-Tronics, A Hubbell Company",HUB,2009-03-26 +"Galil Motion Control",GAL,1996-11-29 +"Garmin International",GRM,2011-12-09 +"Garnet System Company Ltd",GTM,1996-11-29 +"Gateway 2000",GWY,1996-11-29 +"Gateway Comm. Inc",GCI,1996-11-29 +"Gateworks Corporation",GWK,2013-07-31 +"Gaudi Co., Ltd.",GAU,2003-03-31 +"GCC Technologies Inc",GCC,1997-06-05 +GDS,GDS,2004-06-23 +"GE Fanuc Embedded Systems",GEF,2007-06-14 +"Abaco Systems, Inc.",GEH,2010-09-03 +"Gefen Inc.",GFN,2007-10-11 +"Gem Plus",GEM,1998-02-27 +"GEMINI 2000 Ltd",GMN,2000-10-23 +"General Datacom",GDC,1996-11-29 +"General Dynamics C4 Systems",GED,2013-01-09 +"General Information Systems",GML,2000-01-13 +"General Inst. Corporation",GIC,1996-11-29 +"General Standards Corporation",GSC,1998-07-16 +"General Touch Technology Co., Ltd.",GTT,2002-11-21 +"Genesys ATE Inc",GEN,1996-11-29 +"Genesys Logic",GLM,1999-11-08 +"Gennum Corporation",GND,2006-09-05 +"GEO Sense",GEO,1996-11-29 +"Geotest Marvin Test Systems Inc",GTS,1998-02-24 +"GERMANEERS GmbH",GER,2011-12-20 +"GES Singapore Pte Ltd",GES,2001-03-15 +"Getac Technology Corporation",GET,2010-05-11 +"GFMesstechnik GmbH",GFM,2001-03-15 +"GI Provision Ltd",GIP,2012-02-08 +"Global Data SA",PST,1996-11-29 +"Global Village Communication",GVL,1996-11-29 +"GMK Electronic Design GmbH",GMK,2008-01-18 +"GMM Research Inc",GMM,1996-11-29 +"GMX Inc",GMX,1996-11-29 +"GN Nettest Inc",GNN,1997-07-30 +"GOEPEL electronic GmbH",GOE,2013-06-24 +"Goldmund - Digital Audio SA",GLD,2012-02-06 +"GOLD RAIN ENTERPRISES CORP.",GRE,2003-06-04 +"Goldstar Company Ltd",GSM,1996-11-29 +Goldtouch,GTI,1997-08-06 +"Google Inc.",GGL,2010-05-26 +"GoPro, Inc.",GPR,2015-01-15 +"Granch Ltd",GRH,2002-09-23 +"Grand Junction Networks",GJN,1996-11-29 +"Grandstream Networks, Inc.",GSN,2014-03-03 +"Graphic SystemTechnology",GST,1996-11-29 +"Graphica Computer",GRA,1996-11-29 +"Graphtec Corporation",GTC,1996-11-29 +"Grass Valley Germany GmbH",TGV,2007-06-14 +"Grey Cell Systems Ltd",GCS,1997-04-29 +"Grossenbacher Systeme AG",GSY,2000-04-19 +"G-Tech Corporation",GTK,1996-11-29 +"Guillemont International",GIM,1997-10-29 +"GUNZE Limited",GZE,2005-05-02 +"Gunze Ltd",GNZ,1996-11-29 +"Guntermann & Drunck GmbH",GUD,2003-03-10 +"Guzik Technical Enterprises",GUZ,1996-11-29 +"GVC Corporation",GVC,1996-11-29 +"H.P.R. Electronics GmbH",HPR,2007-08-29 +"Hagiwara Sys-Com Company Ltd",HSC,1996-11-29 +"GW Instruments",GWI,1996-11-29 +"Haider electronics",HAE,2001-07-05 +"Haivision Systems Inc.",HAI,2007-11-15 +Halberthal,HAL,1998-02-10 +"Hall Research",HRI,2012-05-10 +"HAMAMATSU PHOTONICS K.K.",HPK,2006-12-20 +"Hampshire Company, Inc.",HTI,1999-01-20 +"Hanchang System Corporation",HAN,2003-06-21 +"HannStar Display Corp",HSD,2009-08-11 +"HannStar Display Corp",HSP,2009-08-11 +"HardCom Elektronik & Datateknik",HDC,1998-04-14 +"Harman International Industries, Inc",HII,2015-01-09 +"Harris & Jeffries Inc",HJI,1996-11-29 +"Harris Canada Inc",HWA,1998-03-13 +"Harris Corporation",HAR,2011-12-20 +"Harris Semiconductor",HRS,1997-01-02 +"Hauppauge Computer Works Inc",HCW,1996-11-29 +"Hayes Microcomputer Products Inc",HAY,1996-11-29 +"HCL America Inc",HCL,1996-11-29 +"HCL Peripherals",HCM,2001-10-02 +"HD-INFO d.o.o.",HDI,2001-10-08 +"Headplay, Inc.",HPI,2007-04-30 +"Heng Yu Technology (HK) Limited",HYT,2000-10-23 +Hercules,HRC,2001-03-15 +HERCULES,HRT,2001-03-15 +"HETEC Datensysteme GmbH",HET,2004-02-03 +"Hewlett Packard",HWP,2001-03-15 +"Hewlett Packard",HPD,1997-05-02 +"Hewlett-Packard Co.",HPC,2000-08-10 +"Hewlett-Packard Co.",HPQ,2004-07-12 +"Hexium Ltd.",HXM,2008-04-15 +"Hibino Corporation",HIB,2003-07-09 +"Highwater Designs Ltd",HWD,1996-11-29 +"Hikom Co., Ltd.",HIK,2003-10-13 +"Hilevel Technology",HIL,1996-11-29 +"HIRAKAWA HEWTECH CORP.",HHC,2008-05-20 +"Hitachi America Ltd",HIT,1996-11-29 +"Hitachi Consumer Electronics Co., Ltd",HCE,2009-05-15 +"Hitachi Information Technology Co., Ltd.",HIC,2000-04-19 +"Hitachi Ltd",HTC,1996-11-29 +"Hitachi Maxell, Ltd.",MXL,2000-01-13 +"Hitachi Micro Systems Europe Ltd",HEL,1997-07-09 +"Hitex Systementwicklung GmbH",HTX,1998-01-30 +"hmk Daten-System-Technik BmbH",HMK,1997-09-30 +"HOB Electronic GmbH",HOB,1996-11-29 +"Holoeye Photonics AG",HOL,2005-02-02 +"Holografika kft.",HDV,2005-03-31 +"Holtek Microelectronics Inc",HTK,1996-11-29 +"Home Row Inc",INC,1996-11-29 +"HON HAI PRECISON IND.CO.,LTD.",FOX,2010-08-02 +"HONKO MFG. CO., LTD.",HKA,2004-12-01 +"Hope Industrial Systems, Inc.",HIS,2014-01-13 +"Horner Electric Inc",APG,1996-11-29 +"Horsent Technology Co., Ltd.",HST,2015-04-11 +"Hosiden Corporation",HOE,1997-08-05 +"HTBLuVA Mödling",HTL,2014-02-17 +"Hualon Microelectric Corporation",HMC,1996-11-29 +"HUALONG TECHNOLOGY CO., LTD",EBT,2007-06-15 +"Hughes Network Systems",HNS,1996-11-29 +"HUMAX Co., Ltd.",HMX,2006-02-14 +"HYC CO., LTD.",HYO,2006-04-12 +"Hydis Technologies.Co.,LTD",HYD,2010-11-22 +"Hynix Semiconductor",HYV,2008-11-29 +"Hypercope Gmbh Aachen",HYC,1997-12-01 +"Hypertec Pty Ltd",HYR,1996-11-29 +"Hyphen Ltd",HYP,1996-11-29 +"I&T Telecom.",ITT,1999-11-08 +"I/OTech Inc",IOT,1996-11-29 +"IAT Germany GmbH",IAT,1996-11-29 +"IBM Brasil",IBM,1996-11-29 +"IBM Corporation",CDT,1996-11-29 +"IBP Instruments GmbH",IBP,1998-09-23 +"IBR GmbH",IBR,1998-01-16 +"IC Ensemble",ICE,1997-09-19 +"ICA Inc",ICA,2002-05-20 +"ICCC A/S",ICX,1996-11-29 +"ICD Inc",ICD,1997-06-09 +"ICET S.p.A.",ARE,1999-05-16 +"ICP Electronics, Inc./iEi Technology Corp.",ICP,2012-09-07 +ICSL,IUC,1997-08-14 +"Icuiti Corporation",XTD,2007-06-14 +"Icuiti Corporation",IWR,2007-03-06 +"Id3 Semiconductors",ISC,2001-03-15 +"IDE Associates",IDE,1996-11-29 +"IDEO Product Development",IDO,1997-09-30 +"idex displays",DEX,2002-04-25 +"IDEXX Labs",IDX,1996-11-29 +"IDK Corporation",IDK,2003-04-16 +"Idneo Technologies",IDN,2012-07-05 +IDTECH,ITS,2002-06-17 +IEE,IEE,2001-06-21 +"IGM Communi",IGM,1996-11-29 +"IINFRA Co., Ltd",IIN,2003-05-09 +"Iiyama North America",IVM,1996-11-29 +"Ikegami Tsushinki Co. Ltd.",IKE,2014-11-14 +"Ikos Systems Inc",IKS,1996-11-29 +ILC,IND,2004-06-16 +"Image Logic Corporation",ILC,1996-11-29 +"Image Stream Medical",ISM,2010-05-27 +"IMAGENICS Co., Ltd.",IMG,2006-09-05 +"IMAGEQUEST Co., Ltd",IQT,2002-10-08 +Imagraph,IME,1996-12-04 +Imagraph,IMA,1996-11-29 +"ImasDe Canarias S.A.",IMD,1997-07-03 +"IMC Networks",IMC,1996-11-29 +"Immersion Corporation",IMM,1997-07-16 +"IMP Electronics Ltd.",HUM,2004-06-16 +Impinj,IMP,2012-08-14 +"Impossible Production",IMN,2000-08-10 +"In Focus Systems Inc",IFS,1996-11-29 +"In4S Inc",ALD,1997-12-05 +INBINE.CO.LTD,IBI,2001-11-06 +"Indtek Co., Ltd.",INK,2007-03-26 +"IneoQuest Technologies, Inc",IQI,2011-02-18 +"Industrial Products Design, Inc.",IPD,1999-07-16 +"Ines GmbH",INS,1996-11-29 +"Infineon Technologies AG",IFX,2000-04-19 +"Infinite Z",IFZ,2012-01-04 +"Informatik Information Technologies",IIT,2013-08-14 +Informtech,IFT,1996-11-29 +"Infotek Communication Inc",ICI,1996-11-29 +"Infotronic America, Inc.",ITR,2001-06-21 +"Inframetrics Inc",INF,1996-11-29 +"Ingram Macrotron",VSN,2000-08-10 +"Ingram Macrotron Germany",VID,2000-05-24 +"InHand Electronics",IHE,2010-04-20 +"Initio Corporation",INI,1996-11-29 +"Inmax Technology Corporation",IMT,2003-02-12 +"Innolab Pte Ltd",INO,1999-01-20 +"InnoLux Display Corporation",INL,2004-12-15 +"InnoMedia Inc",INM,1996-11-29 +"Innotech Corporation",ILS,2000-10-23 +"Innovate Ltd",ATE,1996-11-29 +"Innovent Systems, Inc.",INN,2000-04-19 +"Innoware Inc",WII,1998-01-30 +"Inovatec S.p.A.",inu,2001-03-15 +"Inside Contactless",ICV,2010-11-04 +"Inside Out Networks",ION,1998-12-28 +"Insignia Solutions Inc",ISG,1996-11-29 +"INSIS Co., LTD.",ISR,2003-02-12 +"Institut f r angewandte Funksystemtechnik GmbH",IAF,1999-03-20 +"Integraph Corporation",ING,1996-11-29 +"Integrated Business Systems",IBC,1996-11-29 +"Integrated Device Technology, Inc.",IDP,2010-01-27 +"Integrated Tech Express Inc",ITE,1996-11-29 +"Integrated Tech Express Inc",SRC,1996-11-29 +"integrated Technology Express Inc",ITX,1997-06-25 +"Integration Associates, Inc.",IAI,2004-03-17 +"Intel Corp",ICO,2000-08-10 +"Intelligent Instrumentation",III,1996-11-29 +"Intelligent Platform Management Interface (IPMI) forum (Intel, HP, NEC, Dell)",IPI,2000-05-24 +"Intelliworxx, Inc.",IWX,1999-05-16 +"Intellix Corp.",SVC,2008-01-18 +"Interaction Systems, Inc",TCH,1999-03-20 +"Interactive Computer Products Inc",PEN,1997-01-15 +"Intercom Inc",ITC,1996-11-29 +"Interdigital Sistemas de Informacao",IDS,1997-10-28 +"Interface Corporation",FBI,1996-11-29 +"Interface Solutions",ISI,1996-11-29 +"Intergate Pty Ltd",IGC,1996-11-29 +"Interlace Engineering Corporation",IEC,1996-11-29 +"Interlink Electronics",IEI,1998-10-16 +"International Datacasting Corporation",IDC,1997-02-25 +"International Display Technology",IDT,2002-05-16 +"International Integrated Systems,Inc.(IISI)",ISY,2000-08-10 +"International Microsystems Inc",IMI,1996-11-29 +"International Power Technologies",IPT,1997-04-11 +"Internet Technology Corporation",ITD,1997-12-05 +"Interphase Corporation",INP,1996-11-29 +"Interphase Corporation",INT,1996-11-29 +"Intersil Corporation",LSD,2012-03-14 +"Intersolve Technologies",IST,1999-03-20 +Inter-Tel,ITL,1997-03-21 +"Intertex Data AB",IXD,1996-11-29 +"Intervoice Inc",IVI,1996-11-29 +"Intevac Photonics Inc.",IVS,2011-02-16 +"Intracom SA",ICM,1998-08-03 +"Intrada-SDD Ltd",SDD,2007-11-21 +"IntreSource Systems Pte Ltd",ISP,1997-08-27 +"Intuitive Surgical, Inc.",SRG,2006-02-16 +"Inventec Corporation",INA,2013-09-13 +"Inventec Electronics (M) Sdn. Bhd.",INE,1998-07-21 +"Inviso, Inc.",INV,2000-10-23 +"I-O Data Device Inc",IOD,1996-11-29 +"i-O Display System",IOS,2001-03-15 +Iomega,IOM,1996-11-29 +"IP Power Technologies GmbH",IPP,2010-12-06 +"IP3 Technology Ltd.",IPQ,2013-11-11 +"IPC Corporation",IPC,1996-11-29 +"IPM Industria Politecnica Meridionale SpA",IPM,1998-09-23 +"IPS, Inc. (Intellectual Property Solutions, Inc.)",IPS,2001-09-05 +"IPWireless, Inc",IPW,2001-03-15 +"ISIC Innoscan Industrial Computers A/S",IIC,2003-07-23 +"Isolation Systems",ISL,1996-11-29 +"ISS Inc",ISS,1996-11-29 +"Itausa Export North America",ITA,1996-11-29 +"Ithaca Peripherals",IPR,1997-07-01 +"ITK Telekommunikation AG",ITK,1996-11-29 +"ITM inc.",ITM,2001-04-24 +"IT-PRO Consulting und Systemhaus GmbH",ITP,2000-10-23 +"Jace Tech Inc",JCE,1996-11-29 +"Jaeik Information & Communication Co., Ltd.",JIC,2000-10-23 +"Jan Strapko - FOTO",XFG,2001-05-07 +"Janich & Klass Computertechnik GmbH",JUK,2002-10-08 +"Janz Automationssysteme AG",JAS,2009-11-03 +"Japan Aviation Electronics Industry, Limited",JAE,2001-03-15 +"Japan Digital Laboratory Co.,Ltd.",JDL,2000-04-19 +"Japan Display Inc.",JDI,2013-04-18 +"Jaton Corporation",JAT,1997-09-24 +"JET POWER TECHNOLOGY CO., LTD.",JET,2001-03-15 +"Jetway Information Co., Ltd",JWY,2003-09-22 +"jetway security micro,inc",JTY,2009-11-11 +"Jiangsu Shinco Electronic Group Co., Ltd",SHI,2004-08-10 +"Jones Futurex Inc",JFX,1996-11-29 +"Jongshine Tech Inc",LTI,1996-11-29 +"Josef Heim KG",HKG,1996-11-29 +"JPC Technology Limited",JPC,2000-10-23 +"JS DigiTech, Inc",JSD,2000-10-23 +"JS Motorsports",JTS,1997-12-05 +Junnila,TPJ,2001-03-15 +"Jupiter Systems",JUP,2006-09-05 +"Jupiter Systems, Inc.",JSI,2007-06-14 +JVC,JVC,2000-10-23 +"JVC KENWOOD Corporation",JKC,2012-03-08 +"JWSpencer & Co.",JWS,1999-07-16 +"Kansai Electric Company Ltd",SGE,1996-12-04 +"Kaohsiung Opto Electronics Americas, Inc.",HIQ,2012-03-14 +"Karn Solutions Ltd.",KSL,2006-05-08 +Karna,KAR,2000-02-21 +"Katron Tech Inc",KTN,1996-11-29 +"Kayser-Threde GmbH",KTG,1996-11-29 +"KDDI Technology Corporation",KDT,2012-05-22 +KDE,KDE,2001-08-14 +"KDS USA",KDS,1996-11-29 +"KEISOKU GIKEN Co.,Ltd.",KGL,2012-04-17 +"Kensington Microware Ltd",KML,1996-11-29 +"Kenwood Corporation",KWD,2008-02-22 +KEPS,EPS,1996-11-29 +"Kesa Corporation",KES,1996-11-29 +"Key Tech Inc",KEY,1996-11-29 +"Key Tronic Corporation",KTK,1996-11-29 +"Keycorp Ltd",KCL,1997-05-20 +KeyView,KVX,2012-08-13 +"Kidboard Inc",KBI,1997-04-24 +"KIMIN Electronics Co., Ltd.",KME,2011-02-15 +"Kinetic Systems Corporation",KSC,1996-11-29 +"King Phoenix Company",KPC,1996-11-29 +"King Tester Corporation",KSX,1998-07-16 +"Kingston Tech Corporation",KTC,1996-11-29 +"Kionix, Inc.",KIO,2013-12-23 +"KiSS Technology A/S",KIS,2005-06-16 +"Klos Technologies, Inc.",PVP,2000-08-10 +"Kobil Systems GmbH",KBL,2001-03-15 +"Kobil Systems GmbH",KOB,2001-03-15 +"Kodiak Tech",KDK,1996-11-29 +"Kofax Image Products",KFX,1996-11-29 +"Kollmorgen Motion Technologies Group",KOL,1996-11-29 +"KOLTER ELECTRONIC",KOE,2001-03-15 +"Komatsu Forest",KFE,2010-04-20 +"Konica corporation",KNC,1997-08-05 +"Konica Technical Inc",KTI,1996-11-29 +"Kontron Electronik",TWE,1996-11-29 +"Kontron Embedded Modules GmbH",KEM,2007-08-29 +"Kontron Europe GmbH",KEU,2014-02-20 +"Korea Data Systems Co., Ltd.",KDM,2003-12-18 +"KOUZIRO Co.,Ltd.",KOU,2012-07-27 +"KOWA Company,LTD.",KOW,2008-03-12 +"Kramer Electronics Ltd. International",KMR,2013-07-10 +"Krell Industries Inc.",KRL,2004-08-03 +"Kroma Telecom",KRM,2010-05-05 +"Kroy LLC",KRY,1998-07-16 +K-Tech,KTE,2003-03-31 +"KUPA China Shenzhen Micro Technology Co., Ltd. Gold Institute",KSG,2014-04-22 +"Kurta Corporation",KUR,1996-11-29 +"Kvaser AB",KVA,1997-01-24 +"KYE Syst Corporation",KYE,1996-11-29 +"Kyocera Corporation",KYC,1996-11-29 +"Kyushu Electronics Systems Inc",KEC,1998-01-12 +"K-Zone International",KZN,2001-06-21 +"K-Zone International co. Ltd.",KZI,2000-08-10 +"L-3 Communications",LLL,2010-05-11 +"La Commande Electronique",LCE,1996-11-29 +"Labcal Technologies",LCT,1999-11-08 +"Labtec Inc",LTC,1997-12-08 +"Labway Corporation",LWC,1996-12-04 +LaCie,LAC,1998-12-28 +"Laguna Systems",LAG,1996-11-29 +"Land Computer Company Ltd",LND,1996-11-29 +"LANETCO International",LNT,2003-05-02 +"Lanier Worldwide",LWW,1996-11-29 +"Lars Haagh ApS",LHA,1997-01-09 +"LASAT Comm. A/S",LAS,1996-11-29 +"Laser Master",LMT,1996-11-29 +"Laserdyne Technologies",LDN,2013-10-16 +"Lasergraphics, Inc.",LGX,2000-02-21 +"Latitude Comm.",LCM,1996-11-29 +"Lava Computer MFG Inc",LAV,1997-04-14 +LCI,LCC,2000-08-10 +"Lectron Company Ltd",LEC,1997-03-27 +"Leda Media Products",LMP,1998-05-11 +"Legerity, Inc",LEG,2005-01-18 +"Leitch Technology International Inc.",LTV,2003-12-09 +Lenovo,LNV,2005-07-14 +"Lenovo Beijing Co. Ltd.",LIN,2012-05-22 +"Lenovo Group Limited",LEN,2005-06-03 +"Lexical Ltd",LEX,1996-11-29 +LEXICON,LCN,2005-03-01 +"Leutron Vision",PRS,1996-11-29 +"Lexmark Int'l Inc",LMI,1996-11-29 +"LG Semicom Company Ltd",LGS,1996-11-29 +LGIC,MAN,2000-02-21 +"LifeSize Communications",LSC,2006-02-14 +"Lighthouse Technologies Limited",LHT,2010-05-04 +"Lightware Visual Engineering",LWR,2009-02-04 +"Lightware, Inc",LTW,1998-10-16 +"Lightwell Company Ltd",LZX,1997-12-02 +"Likom Technology Sdn. Bhd.",LKM,1998-04-23 +"Linear Systems Ltd.",LNR,2007-10-11 +"Link Tech Inc",LNK,1996-11-29 +"Linked IP GmbH",LIP,2010-07-19 +"Lisa Draexlmaier GmbH",FGD,1999-02-22 +"Litelogic Operations Ltd",LOL,2011-12-09 +"Lite-On Communication Inc",LCI,1996-11-29 +"Lithics Silicon Technology",LIT,2001-03-15 +"Litronic Inc",LTN,1998-02-03 +"Locamation B.V.",LOC,2004-01-09 +"Loewe Opta GmbH",LOE,2005-05-02 +"Logic Ltd",LGC,1994-04-02 +"Logical Solutions",LSL,1996-11-29 +"Logicode Technology Inc",LOG,1996-11-29 +"Logitech Inc",LGI,1996-11-29 +"LogiDataTech Electronic GmbH",LDT,2001-03-15 +"Logos Design A/S",SGO,2001-04-24 +"Long Engineering Design Inc",LED,1996-11-29 +"Longshine Electronics Company",LCS,1996-11-29 +"Loughborough Sound Images",LSI,1996-11-29 +"LSI Japan Company Ltd",LSJ,1996-11-29 +"LSI Systems Inc",LSY,1996-11-29 +"LTS Scale LLC",LTS,2007-11-15 +Lubosoft,LBO,2001-04-24 +"Lucent Technologies",LUC,2000-04-19 +"Lucent Technologies",LMG,1997-01-13 +"Lucidity Technology Company Ltd",LTK,1998-05-18 +"Lumagen, Inc.",LUM,2004-08-12 +"Lung Hwa Electronics Company Ltd",LHE,1998-06-12 +Luxeon,LXN,2001-03-15 +"Luxxell Research Inc",LUX,1997-06-09 +"LVI Low Vision International AB",LVI,2011-01-21 +"LXCO Technologies AG",LXC,2012-01-11 +"MAC System Company Ltd",MAC,1997-09-26 +"Mac-Eight Co., LTD.",MEJ,2011-01-19 +"Macraigor Systems Inc",OCD,1998-03-23 +"Macrocad Development Inc.",VHI,2000-04-19 +"Macronix Inc",MXI,1996-11-29 +"Madge Networks",MDG,1996-11-29 +"Maestro Pty Ltd",MAE,1996-12-04 +"MAG InnoVision",MAG,1996-11-29 +"Magic Leap",MLP,2014-11-14 +"Magni Systems Inc",MCP,1996-11-29 +"MagTek Inc.",EKA,2006-02-14 +"Magus Data Tech",MDT,1996-11-29 +"Mainpine Limited",MPN,2007-06-30 +"Mainpine Limited",MUK,1999-09-13 +"Many CNC System Co., Ltd.",PAK,2004-03-12 +"Maple Research Inst. Company Ltd",MPL,1996-11-29 +"MARANTZ JAPAN, INC.",MJI,2000-10-23 +"Marconi Instruments Ltd",MIL,1996-11-29 +"Marconi Simulation & Ty-Coch Way Training",MRC,1996-11-29 +"Marina Communicaitons",MCR,1996-11-29 +"Mark Levinson",MLN,2005-02-28 +"Mark of the Unicorn Inc",MTU,1997-03-21 +"Marseille, Inc.",MNI,2013-02-27 +"Marshall Electronics",MBM,2006-03-13 +"Mars-Tech Corporation",MTC,1996-11-29 +"Maruko & Company Ltd",MRK,1996-11-29 +"MASPRO DENKOH Corp.",MSR,2012-10-25 +"Mass Inc.",MAS,2002-02-25 +"Matelect Ltd.",MEQ,2002-05-30 +Matrox,MTX,1996-11-29 +"Mat's Computers",MCQ,2004-07-22 +"Matsushita Communication Industrial Co., Ltd.",WPA,2001-03-15 +"Panasonic Connect Co.,Ltd.",MAT,2022-04-01 +"MaxCom Technical Inc",MTI,1996-11-29 +"MaxData Computer AG",VOB,2000-02-21 +"MaxData Computer GmbH & Co.KG",MXD,2000-04-19 +"Maxpeed Corporation",MXP,1997-02-19 +"Maxtech Corporation",MXT,1996-11-29 +"MaxVision Corporation",MXV,1999-07-16 +"Maygay Machines, Ltd",DJP,2000-08-10 +"Maynard Electronics",MAY,1996-11-29 +"MAZeT GmbH",MAZ,1998-08-11 +MBC,MBC,1996-11-29 +"McDATA Corporation",MCD,1996-11-29 +"McIntosh Laboratory Inc.",MLI,2008-01-18 +"MCM Industrial Technology GmbH",MIT,2004-10-29 +"MEC Electronics GmbH",CEM,2000-04-19 +"Medar Inc",MDR,1996-12-11 +"Media Technologies Ltd.",MTB,2009-01-05 +"Media Tek Inc.",MKC,2007-06-14 +"Media Vision Inc",MVI,1996-11-29 +"Media4 Inc",MDA,1997-03-20 +"Mediacom Technologies Pte Ltd",OWL,1996-11-29 +"Mediaedge Corporation",MEK,2013-11-19 +"MediaFire Corp.",MFR,1998-12-28 +Mediasonic,FTR,1996-11-29 +"MediaTec GmbH",MTE,1996-12-13 +"Mediatek Corporation",MDK,1997-03-13 +"Mediatrix Peripherals Inc",MPI,1997-04-24 +"Medikro Oy",MRO,1997-09-19 +"Mega System Technologies Inc",MEC,1997-12-29 +"Mega System Technologies, Inc.",MGA,1998-12-28 +"Megasoft Inc",MSK,1996-11-29 +"Megatech R & D Company",MGT,1996-11-29 +"Meld Technology",MEP,2012-08-16 +"MEN Mikroelectronik Nueruberg GmbH",MEN,1997-05-23 +"Mentor Graphics Corporation",MGC,2009-07-30 +MEPCO,RLD,2001-03-15 +MEPhI,PPD,1998-11-27 +"Merging Technologies",MRT,1996-11-29 +"Meridian Audio Ltd",MAL,2009-02-04 +"Messeltronik Dresden GmbH",MED,1996-11-29 +"MET Development Inc",MDV,1996-11-29 +"Meta Watch Ltd",MTA,2013-08-29 +"Metheus Corporation",MET,1996-11-29 +"Metricom Inc",MCM,1996-11-29 +"Metronics Inc",QCH,1996-11-29 +"Mettler Toledo",NET,1996-11-29 +"Metz-Werke GmbH & Co KG",MCE,2005-06-30 +"M-G Technology Ltd",MGL,1997-10-29 +"Micom Communications Inc",MIC,1997-05-05 +"Micomsoft Co., Ltd.",MSX,2008-04-10 +"Micro Computer Systems",MCS,1996-11-29 +"Micro Design Inc",MDI,1998-01-20 +"Micro Display Systems Inc",MDS,1996-11-29 +"Micro Firmware",MFI,1997-12-30 +"Micro Industries",MCC,2003-04-21 +"Micro Solutions, Inc.",BPD,2000-04-19 +"Micro Systemation AB",MSA,1999-11-08 +"Micro Technical Company Ltd",JMT,1996-11-29 +"Microbus PLC",MBD,2002-08-13 +Microcom,MNP,1996-11-29 +"MicroDatec GmbH",MDX,1999-09-13 +"MicroDisplay Corporation",MRD,2007-06-14 +"Microdyne Inc",MDY,1996-12-18 +"MicroField Graphics Inc",MFG,1996-11-29 +Microlab,MPJ,1997-05-23 +Microline,LAF,1999-09-13 +"Micrologica AG",MLG,1998-10-06 +"Micromed Biotecnologia Ltd",MMD,1996-12-11 +"Micromedia AG",MMA,1997-04-24 +"Micron Electronics Inc",MCN,1997-02-20 +"Micronics Computers",MCI,1996-11-29 +micronpc.com,MIP,2000-08-10 +"Micronyx Inc",MYX,1996-11-29 +"Micropix Technologies, Ltd.",MPX,2001-10-08 +"MicroSlate Inc.",MSL,1999-05-16 +Microsoft,PNP,2004-03-05 +Microsoft,MSH,1996-11-29 +Microsoft,PNG,1996-11-29 +MicroSoftWare,WBN,1998-01-14 +Microstep,MSI,1996-11-29 +Microtec,MCT,1996-11-29 +"Micro-Tech Hearing Instruments",MTH,1997-12-15 +"MICROTEK Inc.",MKT,2005-07-14 +"Microtek International Inc.",MTK,2002-02-25 +"MicroTouch Systems Inc",MSY,2000-08-10 +Microvision,MVS,2009-02-13 +"Microvitec PLC",MVD,1996-11-29 +"Microway Inc",MWY,1996-11-29 +"Midori Electronics",MDC,1996-11-29 +"Mikroforum Ring 3",SFT,2004-11-02 +"Milestone EPE",MLS,1998-08-11 +"Millennium Engineering Inc",MLM,1996-11-29 +"Millogic Ltd.",MLL,2014-01-09 +"Millson Custom Solutions Inc.",MCX,2013-10-17 +"Miltope Corporation",VTM,2009-09-23 +"Mimio – A Newell Rubbermaid Company",MIM,2012-07-31 +"MindTech Display Co. Ltd",MTD,2007-06-14 +"MindTribe Product Engineering, Inc.",FTW,2011-02-14 +"Mini Micro Methods Ltd",MNC,1996-11-29 +"Minicom Digital Signage",MIN,2010-08-13 +"MiniMan Inc",MMN,1996-11-29 +"Minnesota Mining and Manufacturing",MMF,2001-03-15 +"Miranda Technologies Inc",MRA,1996-11-29 +Miratel,MRL,1998-10-16 +"Miro Computer Prod.",MIR,1996-11-29 +"miro Displays",MID,1999-03-20 +"Mistral Solutions [P] Ltd.",MSP,1998-09-23 +"Mitec Inc",MII,1996-11-29 +"Mitel Corporation",MTL,1997-08-01 +"Mitron computer Inc",MTR,1996-11-29 +"Mitsubishi Electric Corporation",MEL,1996-11-29 +"Mitsubishi Electric Engineering Co., Ltd.",MEE,2005-10-03 +"Mitsumi Company Ltd",KMC,1996-11-29 +"MJS Designs",MJS,1996-11-29 +"MK Seiko Co., Ltd.",MKS,2013-06-18 +"M-Labs Limited",OHW,2013-11-27 +"MMS Electronics",MMS,1998-02-24 +"Modesto PC Inc",FST,1997-02-27 +MODIS,MDD,1999-11-08 +"Modular Industrial Solutions Inc",MIS,1996-11-29 +"Modular Technology",MOD,1997-06-09 +"Momentum Data Systems",MOM,2008-01-18 +"Monorail Inc",MNL,1997-02-18 +Monydata,MYA,1996-11-29 +"Moreton Bay",MBV,2000-01-13 +"Moses Corporation",MOS,1996-11-29 +"Mosgi Corporation",MSV,1996-11-29 +"Motion Computing Inc.",MCO,2002-05-30 +Motium,MTM,2012-06-19 +motorola,MSU,2001-03-15 +"Motorola Communications Israel",MCL,2002-07-02 +"Motorola Computer Group",MCG,1997-08-14 +"Motorola UDS",MOT,1996-11-29 +"Mouse Systems Corporation",MSC,1996-11-29 +"M-Pact Inc",MPC,1996-11-29 +"mps Software GmbH",MPS,1996-11-29 +"MS Telematica",MST,1997-04-28 +"MSC Vertriebs GmbH",MEX,2012-06-04 +"MSI GmbH",MSG,1999-09-13 +"M-Systems Flash Disk Pioneers",MSF,1997-12-17 +"Mtron Storage Technology Co., Ltd.",MTN,2008-06-17 +"Multi-Dimension Institute",MUD,2000-10-23 +Multimax,MMI,1996-11-29 +"Multi-Tech Systems",MTS,1996-11-29 +"Multiwave Innovation Pte Ltd",MWI,1996-11-29 +"Mutoh America Inc",MAI,1999-09-13 +mware,MWR,2001-04-24 +"Mylex Corporation",MLX,1996-11-29 +"Myriad Solutions Ltd",MYR,1996-11-29 +"Myse Technology",WYS,1996-11-29 +"N*Able Technologies Inc",NBL,1998-04-28 +"NAD Electronics",NAD,2007-06-14 +"Naitoh Densei CO., LTD.",NDK,2006-04-12 +"Najing CEC Panda FPD Technology CO. ltd",NCP,2015-02-24 +"Nakano Engineering Co.,Ltd.",NAK,2009-07-22 +"Nakayo Relecommunications, Inc.",NYC,2000-08-10 +"Nanomach Anstalt",SCS,1996-11-29 +"Nasa Ames Research Center",ADR,1996-11-29 +"National DataComm Corporaiton",NDC,1996-11-29 +"National Display Systems",NDI,2003-08-08 +"National Instruments Corporation",NIC,1996-11-29 +"National Key Lab. on ISN",NBS,1998-07-16 +"National Semiconductor Corporation",NSC,1996-11-29 +"National Semiconductor Japan Ltd",TTB,1997-04-14 +"National Transcomm. Ltd",NTL,1996-11-29 +"Nationz Technologies Inc.",ZIC,2009-03-12 +"Natural Micro System",NMS,1996-11-29 +"NaturalPoint Inc.",NAT,2010-09-03 +"Navatek Engineering Corporation",NVT,1998-03-02 +"Navico, Inc.",NME,2012-11-28 +"Navigation Corporation",NAV,1999-02-22 +"Naxos Tecnologia",NAX,1997-12-12 +"NCR Corporation",DUN,2002-04-25 +"NCR Corporation",NCC,1996-11-29 +"NCR Electronics",NCR,1996-11-29 +"NDF Special Light Products B.V.",NDF,2014-09-18 +"NDS Ltd",DMV,1997-06-25 +"NEC Corporation",NEC,2000-05-24 +"NEC CustomTechnica, Ltd.",NCT,2002-10-23 +"NEC-Mitsubishi Electric Visual Systems Corporation",NMV,2002-02-25 +"NEO TELECOM CO.,LTD.",NEO,1999-11-08 +Neomagic,NMX,1996-11-29 +"NeoTech S.R.L",NTC,1997-11-11 +"Netaccess Inc",NTX,1997-02-07 +"NetComm Ltd",NCL,1996-11-29 +"NetVision Corporation",NVC,1996-11-29 +"Network Alchemy",NAL,1997-09-30 +"Network Designers",NDL,1996-11-29 +"Network General",NGC,1997-08-26 +"Network Info Technology",NIT,1996-11-29 +"Network Peripherals Inc",NPI,1996-11-29 +"Network Security Technology Co",NST,1999-02-22 +"Networth Inc",NTW,1996-11-29 +"NeuroSky, Inc.",NSA,2013-08-28 +"NEUROTEC - EMPRESA DE PESQUISA E DESENVOLVIMENTO EM BIOMEDICINA",NEU,2001-03-15 +"New Tech Int'l Company",NTI,1996-11-29 +"NewCom Inc",NCI,1997-01-09 +"Newisys, Inc.",NWS,2002-10-08 +"Newport Systems Solutions",NSS,1996-11-29 +Nexgen,NXG,1996-11-29 +"Nexgen Mediatech Inc.,",NEX,2003-11-11 +"Nexiq Technologies, Inc.",NXQ,2001-10-08 +"Next Level Communications",NLC,1996-11-29 +"NextCom K.K.",NXC,1996-11-29 +"NingBo Bestwinning Technology CO., Ltd",NBT,2006-09-05 +"NINGBO BOIGLE DIGITAL TECHNOLOGY CO.,LTD",BOI,2009-11-25 +"Nippon Avionics Co.,Ltd",AVI,2000-10-23 +"NIPPONDENCHI CO,.LTD",GSB,2000-05-24 +"NISSEI ELECTRIC CO.,LTD",NSI,2000-01-13 +"Nissei Electric Company",NIS,1996-11-29 +"Nits Technology Inc.",NTS,2006-12-19 +"Nixdorf Company",NCA,1996-11-29 +NNC,NNC,1996-11-29 +"Nokia Data",NDS,1996-11-29 +"Nokia Display Products",NOK,1996-11-29 +"Nokia Mobile Phones",NMP,1996-11-29 +"Norand Corporation",NOR,1997-03-19 +"Norcent Technology, Inc.",NCE,2007-06-20 +"NordicEye AB",NOE,2009-09-23 +"North Invent A/S",NOI,2010-05-04 +"Northgate Computer Systems",NCS,1996-11-29 +"Not Limited Inc",NOT,1998-01-30 +"NovaWeb Technologies Inc",NWP,1998-06-12 +"Novell Inc",NVL,1996-11-29 +"Nspire System Inc.",NSP,2007-02-13 +"N-trig Innovative Technologies, Inc.",NTR,2005-10-03 +"NTT Advanced Technology Corporation",NTT,2004-08-19 +"NU Inc.",NUI,2007-08-29 +"NU Technology, Inc.",NUG,2004-04-16 +"Number Five Software",NFS,1999-02-22 +"Nutech Marketing PTL",KNX,1996-11-29 +"NuVision US, Inc.",NVI,2006-09-05 +"Nuvoton Technology Corporation",NTN,2008-10-09 +Nvidia,NVD,1996-11-29 +N-Vision,JEN,2000-10-23 +"NXP Semiconductors bv.",NXP,2007-06-14 +"NW Computer Engineering",NWC,1997-02-03 +"Oak Tech Inc",OAK,1996-11-29 +"Oasys Technology Company",OAS,1996-11-29 +"OBJIX Multimedia Corporation",OMC,1996-11-29 +"OCTAL S.A.",PCB,1998-02-24 +"Oculus VR, Inc.",OVR,2012-10-19 +Odrac,ODR,2001-06-21 +"Office Depot, Inc.",ATV,2007-06-13 +"OKI Electric Industrial Company Ltd",OKI,1996-11-29 +"Oksori Company Ltd",OQI,1996-11-29 +"Oksori Company Ltd",OSR,1996-11-29 +Olfan,OCN,1996-11-29 +"Olicom A/S",OLC,1996-11-29 +"Olidata S.p.A.",OLD,2006-03-13 +"Olitec S.A.",OLT,1996-11-29 +"Olitec S.A.",OLV,1996-11-29 +Olivetti,OLI,1996-11-29 +"OLYMPUS CORPORATION",OLY,2005-05-02 +OmniTek,OTK,2013-09-19 +Omnitel,OMN,1998-04-28 +"Omron Corporation",OMR,1996-11-29 +"On Systems Inc",ONS,1996-11-29 +"Oneac Corporation",ONE,1998-04-14 +"ONKYO Corporation",ONK,2005-06-16 +"OnLive, Inc",ONL,2010-09-03 +"OOO Technoinvest",TIV,1997-08-05 +"Opcode Inc",OPC,1996-11-29 +"Open Connect Solutions",OCS,1999-09-13 +"OPEN Networks Ltd",ONW,2003-04-25 +"Open Stack, Inc.",OSI,2013-07-22 +"OPPO Digital, Inc.",OPP,2012-06-19 +"OPTi Inc",OPT,1996-11-29 +"Optibase Technologies",OBS,2010-11-01 +"Optical Systems Design Pty Ltd",OSD,2013-06-03 +"Option Industrial Computers",OIC,2001-05-07 +"Option International",OIN,2000-10-23 +"Option International",OIM,1997-01-30 +"OPTI-UPS Corporation",OSP,1997-07-01 +"Optivision Inc",OPV,1996-11-29 +"OPTO22, Inc.",OTT,1998-10-06 +"Optoma Corporation          ",OTM,2010-04-20 +"Optum Engineering Inc.",OEI,2010-08-02 +"Orchid Technology",OTI,1996-11-29 +"ORGA Kartensysteme GmbH",ORG,1998-10-24 +"Orion Communications Co., Ltd.",TOP,2007-04-30 +"ORION ELECTRIC CO., LTD.",ORN,2005-01-19 +"ORION ELECTRIC CO.,LTD",OEC,2000-01-13 +"OSAKA Micro Computer, Inc.",OSA,2003-09-05 +"OSR Open Systems Resources, Inc.",ORI,1999-01-20 +OSRAM,OOS,2002-04-25 +"OUK Company Ltd",OUK,1996-11-29 +outsidetheboxstuff.com,OTB,2010-09-03 +"Oxus Research S.A.",OXU,1996-11-29 +"OZ Corporation",OZC,2012-08-07 +"Pacific Avionics Corporation",PAC,1996-11-29 +"Pacific CommWare Inc",PCW,1996-11-29 +"Pacific Image Electronics Company Ltd",PIE,1997-10-21 +"Packard Bell Electronics",PBL,1996-11-29 +"Packard Bell NEC",PBN,1996-11-29 +"PACSGEAR, Inc.",PGI,2012-08-13 +"Padix Co., Inc.",QFF,1999-09-13 +"Pan Jit International Inc.",PJT,2004-08-03 +Panasonic,MDO,1996-11-29 +"Panasonic Avionics Corporation",PLF,2010-08-13 +"Panasonic Industry Company",MEI,1996-11-29 +"Panelview, Inc.",PNL,2003-08-04 +"Pantel Inc",PTL,1996-11-29 +"PAR Tech Inc.",PTA,2011-01-26 +"Parade Technologies, Ltd.",PRT,2012-04-06 +"Paradigm Advanced Research Centre",PGM,2005-06-16 +"Parallan Comp Inc",PAR,1996-11-29 +"Parallax Graphics",PLX,1996-11-29 +"Parc d'Activite des Bellevues",RCE,1996-11-29 +Parrot,POT,2014-11-25 +"Pathlight Technology Inc",PTH,1996-11-29 +"PC Xperten",PCX,1998-02-24 +PCBANK21,PCK,2007-02-13 +"PCM Systems Corporation",PCM,1997-03-25 +"PC-Tel Inc",PCT,1997-05-02 +"PD Systems International Ltd",PDS,1999-03-20 +"PDTS - Prozessdatentechnik und Systeme",PDT,1998-02-10 +"Pegatron Corporation",PEG,2013-08-27 +"PEI Electronics Inc",PEI,1998-04-06 +"Penta Studiotechnik GmbH",PVM,2010-05-05 +"pentel.co.,ltd",PCL,2002-02-25 +"Peppercon AG",PEP,2006-04-12 +"Perceptive Pixel Inc.",PPX,2010-05-04 +"Perceptive Signal Technologies",PER,1997-05-13 +PerComm,PRC,2001-04-24 +"Performance Concepts Inc.,",PCO,2002-09-24 +"Performance Technologies",IPN,2004-02-24 +"Perle Systems Limited",PSL,1999-02-22 +"Perpetual Technologies, LLC",PON,2000-01-13 +"Peter Antesberger Messtechnik",PAM,1998-04-28 +"Peus-Systems GmbH",PSD,1996-11-29 +"Philips BU Add On Card",PCA,1996-11-29 +"Philips Communication Systems",PHS,1996-11-29 +"Philips Consumer Electronics Company",PHL,1996-11-29 +"Philips Medical Systems Boeblingen GmbH",PHE,2010-04-20 +"Philips Semiconductors",PSC,1996-11-29 +"Phoenix Contact",PXC,2008-02-27 +"Phoenix Technologies, Ltd.",PNX,1999-11-08 +"Phoenixtec Power Company Ltd",PPC,1999-05-16 +"Photonics Systems Inc.",PHO,2002-06-03 +PhotoTelesis,RSC,1998-03-16 +"Phylon Communications",PHY,1996-11-29 +PicPro,PPR,2004-10-18 +"Pijnenburg Beheer N.V.",PHC,2001-04-24 +"Pioneer Computer Inc",PCI,1996-11-29 +"Pioneer Electronic Corporation",PIO,1997-07-16 +"Pitney Bowes",PBV,1999-09-13 +"Pitney Bowes",PBI,1996-11-29 +"Pixel Qi",PQI,2009-06-24 +"Pixel Vision",PVN,1996-11-29 +"PIXELA CORPORATION",PXE,2007-11-21 +"Pixie Tech Inc",PIX,1996-11-29 +"Plain Tree Systems Inc",PTS,1996-11-29 +"Planar Systems, Inc.",PNR,2003-08-11 +"PLUS Vision Corp.",PLV,2001-07-05 +"PMC Consumer Electronics Ltd",PMC,1996-12-11 +"pmns GmbH",SPR,2002-10-08 +"Point Multimedia System",PMM,1997-06-09 +"Polycom Inc.",PLY,2002-06-19 +"PolyComp (PTY) Ltd.",POL,2006-02-14 +"Polycow Productions",COW,2001-03-15 +"Portalis LC",POR,2008-11-01 +"Poso International B.V.",ARO,1997-08-01 +"POTRANS Electrical Corp.",PEC,1999-07-16 +"PowerCom Technology Company Ltd",PCC,1997-09-02 +"Powermatic Data Systems",CPX,1996-11-29 +"Practical Electronic Tools",PET,1999-02-22 +"Practical Peripherals",PPI,1996-11-29 +"Practical Solutions Pte., Ltd.",PSE,1998-10-06 +"Praim S.R.L.",PRD,1996-11-29 +"Primax Electric Ltd",PEL,1996-11-29 +"Prime Systems, Inc.",SYX,2003-10-21 +"Prime view international Co., Ltd",PVI,2009-07-06 +"Princeton Graphic Systems",PGS,1996-11-29 +"Prism, LLC",PIM,2007-07-24 +"Priva Hortimation BV",PRI,1997-10-22 +PRO/AUTOMATION,PRA,1999-07-16 +"Procomp USA Inc",PCP,1996-11-29 +"Prodea Systems Inc.",PSY,2013-02-04 +"Prodrive B.V.",PDV,2005-01-18 +Projecta,PJA,1997-01-29 +"Projectavision Inc",DHT,1998-01-14 +"Projectiondesign AS",PJD,2002-09-23 +"PROLINK Microsystems Corp.",PLM,2002-02-25 +"Pro-Log Corporation",PLC,1996-11-29 +"Promate Electronic Co., Ltd.",PMT,2003-01-13 +Prometheus,PRM,1996-11-29 +"Promise Technology Inc",PTI,1997-01-02 +"Promotion and Display Technology Ltd.",PAD,2001-04-24 +"Promotion and Display Technology Ltd.",TEL,2001-04-24 +"propagamma kommunikation",PGP,2000-04-19 +Prosum,PSM,1996-11-29 +Proteon,PRO,1996-11-29 +"Proview Global Co., Ltd",PVG,2002-10-08 +"Proxim Inc",PXM,1997-09-19 +"Proxima Corporation",PRX,1996-11-29 +"PS Technology Corporation",PTC,1997-01-29 +"Psion Dacom Plc.",PDM,1999-11-08 +"PSI-Perceptive Solutions Inc",PSI,1996-11-29 +"PT Hartono Istana Teknologi",PLT,2010-05-05 +"Pulse-Eight Ltd",PUL,2012-09-12 +"Pure Data Inc",PDR,1996-11-29 +"Purup Prepress AS",PPP,1996-11-29 +"Qingdao Haier Electronics Co., Ltd.",HRE,2006-04-12 +Q-Logic,QLC,1996-11-29 +"Qtronix Corporation",QTR,1996-11-29 +Quadram,DHQ,1996-11-29 +Quadram,QDM,1996-11-29 +"Quadrant Components Inc",QCL,1997-04-03 +"QuakeCom Company Ltd",QCC,1998-03-23 +"Qualcomm Inc",QCP,1999-05-16 +"Quanta Computer Inc",QCI,1996-11-29 +"Quanta Display Inc.",QDS,2002-04-25 +Quantum,QTM,1996-11-29 +"Quantum 3D Inc",QTD,1997-05-23 +"Quantum Data Incorporated",QDI,2001-03-15 +Quartics,QVU,2010-11-04 +"Quatographic AG",QUA,2000-01-13 +"Questech Ltd",QTH,2000-01-13 +"Questra Consulting",QUE,1998-01-30 +"Quick Corporation",QCK,1996-11-29 +"Quickflex, Inc",QFI,1998-08-04 +"Quicknet Technologies Inc",QTI,1996-11-29 +"R Squared",RSQ,1999-11-08 +R.P.T.Intergroups,RPT,1996-11-29 +"Racal Interlan Inc",RII,1996-11-29 +"Racal-Airtech Software Forge Ltd",TSF,1996-11-29 +"Racore Computer Products Inc",RAC,1996-11-29 +"Radicom Research Inc",RRI,1997-12-02 +"Radio Consult SRL",RCN,2002-09-24 +"RADIODATA GmbH",RDN,2012-07-25 +"RadioLAN Inc",RLN,1996-11-29 +"Radiospire Networks, Inc.",RSN,2007-06-14 +"Radisys Corporation",RAD,1996-11-29 +"Radius Inc",RDS,1997-03-07 +"RAFI GmbH & Co. KG",RFI,2015-08-24 +"Rainbow Displays, Inc.",RDI,1998-09-23 +"Rainbow Technologies",RNB,1996-11-29 +"Raintree Systems",RTS,2001-10-02 +"Rainy Orchard",BOB,2000-02-21 +"Rampage Systems Inc",RSI,1996-11-29 +"Rancho Tech Inc",RAN,1996-11-29 +"Rancho Tech Inc",RTI,1996-11-29 +"Rapid Tech Corporation",RSX,1996-11-29 +"Raritan Computer, Inc",RMC,1998-11-27 +"Raritan, Inc.",RAR,2007-06-14 +"RAScom Inc",RAS,1996-11-29 +"RATOC Systems, Inc.",REX,2012-01-06 +"Raylar Design, Inc.",RAY,2000-01-13 +"RC International",RCI,1996-11-29 +"Reach Technology Inc",RCH,1998-02-09 +"Reakin Technolohy Corporation",RKC,2001-03-15 +"Real D",REA,2007-11-15 +"Realtek Semiconductor Company Ltd",RTL,1996-11-29 +"Realtek Semiconductor Corp.",ALG,2002-10-25 +"Realvision Inc",RVI,1996-11-29 +ReCom,REC,1999-05-16 +"Red Wing Corporation",RWC,1998-01-08 +"Redfox Technologies Inc.",RFX,2014-01-14 +"Reflectivity, Inc.",REF,2000-04-19 +"Rehan Electronics Ltd.",REH,2012-02-15 +"Relia Technologies",RTC,1996-11-29 +"Reliance Electric Ind Corporation",REL,1996-11-29 +"Renesas Technology Corp.",REN,2007-06-14 +Rent-A-Tech,RAT,1999-02-22 +"Research Electronics Development Inc",RED,1997-12-02 +"Research Machines",RMP,1996-11-29 +"ResMed Pty Ltd",RES,2000-02-21 +"Resonance Technology, Inc.",RET,2011-02-09 +"Restek Electric Company Ltd",WTS,1996-11-29 +"Reveal Computer Prod",RVL,1996-11-29 +"Revolution Display, Inc.",REV,2014-03-19 +"RGB Spectrum",RGB,2012-11-14 +"RGB Systems, Inc. dba Extron Electronics",EXN,2008-07-06 +"RICOH COMPANY, LTD.",RIC,2010-05-13 +"RightHand Technologies",RHD,2012-05-01 +"Rios Systems Company Ltd",RIO,1996-11-29 +"Ritech Inc",RIT,1998-04-14 +"Rivulet Communications",RIV,2007-07-19 +"Robert Bosch GmbH",BSG,2014-05-15 +"Robert Gray Company",GRY,1998-03-31 +"Robertson Geologging Ltd",RGL,2000-08-10 +"Robust Electronics GmbH",ROB,2008-01-18 +"Rockwell Automation/Intecolor",RAI,1998-03-13 +"Rockwell Collins",RCO,2010-09-10 +"Rockwell Collins / Airshow Systems",ASY,2004-12-02 +"Rockwell Collins, Inc.",COL,2007-06-14 +"Rockwell International",ROK,1996-11-29 +"Rockwell Semiconductor Systems",RSS,1996-11-29 +"Rogen Tech Distribution Inc",MAX,1996-11-29 +"Rohde & Schwarz",ROS,2012-01-20 +"Rohm Co., Ltd.",ROH,2004-06-16 +"Rohm Company Ltd",RHM,1997-05-13 +"Roland Corporation",RJA,1996-11-29 +"RoomPro Technologies",RPI,2010-07-09 +"Roper International Ltd",ROP,1999-05-16 +"Roper Mobile",RMT,2010-07-02 +"Ross Video Ltd",RSV,2012-06-11 +"Royal Information",TRL,1996-11-29 +"Rozsnyó, s.r.o.",RZS,2014-03-24 +"RSI Systems Inc",RVC,1998-04-28 +"RUNCO International",RUN,2004-04-01 +"S&K Electronics",SNK,2000-02-21 +"S3 Inc",TLV,1997-01-07 +"S3 Inc",SIM,1996-11-29 +"S3 Inc",SSS,1996-11-29 +"Saab Aerotech",SAE,2007-06-14 +"Sage Inc",SAI,1997-07-16 +SAGEM,SGM,2003-09-05 +SAIT-Devlonics,SDK,1996-11-29 +"Saitek Ltd",SAK,1999-05-16 +"Salt Internatioinal Corp.",SLT,2006-09-05 +"Samsung Electric Company",SAM,1996-11-29 +"Samsung Electro-Mechanics Company Ltd",SKT,1996-11-29 +"Samsung Electronics America",STN,2000-08-10 +"Samsung Electronics America Inc",KYK,1998-02-24 +"Samsung Electronic Co.",SSE,2000-08-10 +"Samsung Electronics Company Ltd",SEM,1996-11-29 +"Samtron Displays Inc",SDI,1996-11-29 +"SANKEN ELECTRIC CO., LTD",JSK,1999-09-13 +"Sankyo Seiki Mfg.co., Ltd",SSJ,2003-01-28 +"Sanritz Automation Co.,Ltd.",SAA,2002-02-25 +"SANTAK CORP.",STK,1998-11-27 +"Santec Corporation",SOC,2015-01-12 +"Sanyo Electric Co.,Ltd.",SAN,1999-11-08 +"Sanyo Electric Company Ltd",SCD,1996-11-29 +"Sanyo Electric Company Ltd",SIB,1996-11-29 +"Sanyo Electric Company Ltd",TSC,1996-11-29 +"Sanyo Icon",ICN,1996-11-29 +"Sapience Corporation",SPN,1996-11-29 +"SAT (Societe Anonyme)",SDA,1996-11-29 +"SBS Technologies (Canada), Inc. (was Avvida Systems, Inc.)",AVV,2002-12-17 +"SBS-or Industrial Computers GmbH",SBS,1998-12-28 +"Scan Group Ltd",SGI,1996-11-29 +"Scanport, Inc.",SCN,2002-08-05 +"SCD Tech",KFC,2002-10-23 +"Sceptre Tech Inc",SPT,1996-11-29 +Schlumberger,SMB,1999-07-16 +"Schlumberger Cards",SCH,1998-04-28 +"Schlumberger Technology Corporate",SLR,2000-08-10 +"Schneider & Koch",SKD,1996-11-29 +"Schneider Electric S.A.",MGE,1996-11-29 +"Schnick-Schnack-Systems GmbH",SLS,2009-05-06 +"SCI Systems Inc.",REM,2000-08-10 +"SCM Microsystems Inc",SCM,1996-11-29 +"Scriptel Corporation",SCP,2007-06-14 +"SDR Systems",SDR,2001-03-15 +"SDS Technologies",STY,1999-11-08 +"SDX Business Systems Ltd",SDX,1996-11-29 +"Seanix Technology Inc",NIX,2007-04-09 +"Seanix Technology Inc.",SEA,1998-02-24 +Sedlbauer,SAG,1996-11-29 +"SeeColor Corporation",SEE,1996-11-29 +"SeeCubic B.V.",SCB,2012-11-02 +"SeeReal Technologies GmbH",SRT,2005-06-27 +"Seiko Epson Corporation",SEC,1996-11-29 +"Seiko Instruments Information Devices Inc",SID,1996-12-16 +"Seiko Instruments USA Inc",SIU,1996-11-29 +"Seitz & Associates Inc",SEI,1998-01-30 +"Sejin Electron Inc",SJE,1997-08-20 +"SELEX GALILEO",SXG,2012-10-01 +"Semtech Corporation",STH,2001-11-30 +"SendTek Corporation",SET,1999-11-08 +"Senseboard Technologies AB",SBT,2002-09-03 +Sencore,SEN,1997-05-23 +"Sentelic Corporation",STU,2012-07-27 +"SEOS Ltd",SEO,2003-02-20 +"Sentronic International Corp.",SNC,2000-10-23 +"SEP Eletronica Ltda.",SEP,2001-05-07 +"Sequent Computer Systems Inc",SQT,1996-11-29 +"Session Control LLC",SES,2010-09-03 +Setred,SRD,2006-09-05 +"SEVIT Co., Ltd.",SVT,2002-06-25 +SGEG,SVA,2000-02-21 +"Seyeon Tech Company Ltd",SYT,1997-12-02 +"SGS Thomson Microelectronics",STM,1997-11-11 +"Shadow Systems",OYO,1996-11-29 +"Shanghai Bell Telephone Equip Mfg Co",SBC,1998-04-30 +"Shanghai Guowei Science and Technology Co., Ltd.",SGW,2011-01-28 +"SHANGHAI SVA-DAV ELECTRONICS CO., LTD",XQU,2003-07-24 +"Sharedware Ltd",SWL,1998-08-11 +"Shark Multimedia Inc",SMM,1996-11-29 +"SharkTec A/S",DFK,2006-02-14 +"Sharp Corporation",SHP,1996-11-29 +"SHARP TAKAYA ELECTRONIC INDUSTRY CO.,LTD.",SXT,2010-06-24 +"Shenzhen ChuangZhiCheng Technology Co., Ltd.",CZC,2013-10-23 +"Shenzhen Inet Mobile Internet Technology Co., LTD",IXN,2014-11-04 +"Shenzhen MTC Co., Ltd",SZM,2013-08-09 +"Shenzhen Ramos Digital Technology Co., Ltd",RMS,2014-10-29 +"Shenzhen South-Top Computer Co., Ltd.",SSL,2013-12-06 +"Shenzhen three Connaught Information Technology Co., Ltd. (3nod Group)",AZH,2013-09-17 +"Shenzhen Zhuona Technology Co., Ltd.",XYE,2013-10-01 +"Shenzhen ZhuoYi HengTong Computer Technology Limited",HTR,2013-12-13 +"Shenzhen Zowee Technology Co., LTD",ZWE,2015-05-26 +"Sherwood Digital Electronics Corporation",SDE,1996-11-29 +"ShibaSoku Co., Ltd.",SHC,2005-05-26 +"Shin Ho Tech",SHT,1996-11-29 +"Shlumberger Ltd",SLB,1996-11-29 +"Shuttle Tech",SAT,1996-11-29 +"Sichuan Changhong Electric CO, LTD.",CHG,2003-02-26 +"Sichuang Changhong Corporation",CHO,2001-11-30 +Siemens,SIE,1996-11-29 +"Siemens AG",SDT,2006-02-14 +"SIEMENS AG",SIA,2001-03-15 +"Siemens Microdesign GmbH",SNI,1996-11-29 +"Siemens Nixdorf Info Systems",SNP,1996-11-29 +"Sierra Semiconductor Inc",SSC,1996-11-29 +"Sierra Wireless Inc.",SWI,2003-07-10 +"Sigma Designs Inc",SIG,1996-11-29 +"Sigma Designs, Inc.",SGD,2006-02-14 +"Sigmacom Co., Ltd.",SCL,2002-04-25 +"SigmaTel Inc",STL,1997-03-03 +Signet,DXS,2000-10-23 +"SII Ido-Tsushin Inc",STE,1997-04-03 +"Silcom Manufacturing Tech Inc",SMT,1996-11-29 +"Silex technology, Inc.",SXD,2009-03-12 +"Silicom Multimedia Systems Inc",SMS,1996-12-04 +"Silicon Graphics Inc",SGX,1996-11-29 +"Silicon Image, Inc.",SII,2000-01-13 +"Silicon Integrated Systems Corporation",SIS,1996-11-29 +"Silicon Laboratories, Inc",SIL,1998-07-16 +"Silicon Library Inc.",SLH,2008-11-01 +"Silicon Optix Corporation",SOI,2005-07-28 +"Silitek Corporation",SLK,1997-07-16 +"SIM2 Multimedia S.P.A.",SPU,2002-09-05 +"Simple Computing",SMP,1996-11-29 +"Simplex Time Recorder Co.",SPX,2001-03-15 +"Singular Technology Co., Ltd.",SIN,1999-11-08 +"SINOSUN TECHNOLOGY CO., LTD",SNO,2005-06-27 +"Sirius Technologies Pty Ltd",SIR,1998-03-13 +"sisel muhendislik",FUN,2002-04-25 +"SITECSYSTEM CO., LTD.",STS,2005-03-16 +Sitintel,SIT,1996-11-29 +"SKYDATA S.P.A.",SKY,1997-09-19 +"Smart Card Technology",SCT,2000-08-10 +"SMART Modular Technologies",SMA,1997-04-04 +"Smart Silicon Systems Pty Ltd",SPL,2000-08-10 +"Smart Tech Inc",STI,1996-11-29 +"SMART Technologies Inc.",SBI,2007-06-14 +"SMK CORPORATION",SMK,2000-02-21 +"Snell & Wilcox",SNW,2002-04-25 +"SOBO VISION",MVM,2007-06-14 +"Socionext Inc.",SCX,2015-05-14 +"Sodeman Lancom Inc",LAN,1996-11-29 +"SODIFF E&T CO., Ltd.",SDF,2007-06-01 +"Soft & Hardware development Goldammer GmbH",SHG,1996-11-29 +"Softbed - Consulting & Development Ltd",SBD,1997-12-23 +"Software Café",SWC,1996-11-29 +"Software Technologies Group,Inc.",SWT,2008-11-29 +"Solitron Technologies Inc",SOL,1996-11-29 +"Solomon Technology Corporation",SLM,1998-01-16 +SolutionInside,SXL,2001-05-08 +"SOMELEC Z.I. Du Vert Galanta",ONX,1996-11-29 +Sonitronix,HON,2011-02-03 +"Sonix Comm. Ltd",SNX,1996-11-29 +Sony,SNY,1996-11-29 +Sony,SON,1996-11-29 +"Sony Ericsson Mobile Communications Inc.",SER,2004-04-16 +"SORCUS Computer GmbH",SCO,2000-01-13 +"Sorcus Computer GmbH",SOR,1996-11-29 +"SORD Computer Corporation",SCC,1996-11-29 +"Sotec Company Ltd",SOT,1997-05-21 +"South Mountain Technologies, LTD",FRS,2006-02-14 +"SOYO Group, Inc",SOY,2006-12-18 +"SPACE-I Co., Ltd.",SPI,2005-05-11 +"SpaceLabs Medical Inc",SMI,1996-11-29 +"SPEA Software AG",SPE,1996-11-29 +SpeakerCraft,SPK,2010-04-20 +Specialix,SLX,1996-11-29 +"Spectragraphics Corporation",SGC,1996-11-29 +"Spectrum Signal Proecessing Inc",SSP,1996-11-29 +"SR-Systems e.K.",SRS,2012-11-19 +"S-S Technology Inc",SSI,1996-11-29 +"ST Electronics Systems Assembly Pte Ltd",STA,1998-12-28 +"STAC Electronics",STC,1996-11-29 +"Standard Microsystems Corporation",SMC,1996-11-29 +"Star Paging Telecom Tech (Shenzhen) Co. Ltd.",STT,1998-09-23 +"Starflight Electronics",STF,1997-05-23 +"Stargate Technology",SGT,1996-11-29 +StarLeaf,SLF,2010-11-01 +"Starlight Networks Inc",STR,1996-11-29 +"Starwin Inc.",STW,2001-04-24 +Static,SWS,1999-05-16 +"STB Systems Inc",STB,1996-11-29 +"STD Computer Inc",STD,1996-11-29 +"StereoGraphics Corp.",STG,2001-10-02 +ST-Ericsson,STX,2011-12-09 +STMicroelectronics,SMO,2007-06-14 +"Stollmann E+V GmbH",STO,1997-03-27 +"Stores Automated Systems Inc",SAS,1997-03-19 +"Storm Technology",EZP,1996-10-17 +"StreamPlay Ltd",STP,2009-02-04 +"Stryker Communications",SYK,2005-10-10 +"Subspace Comm. Inc",SUB,1996-11-29 +"Sumitomo Metal Industries, Ltd.",SML,1999-09-13 +"Summagraphics Corporation",SUM,1996-11-29 +"Sun Corporation",SCE,1996-11-29 +"Sun Electronics Corporation",SUN,1996-11-29 +"Sun Microsystems",SVI,2003-01-13 +"SUNNY ELEKTRONIK",SNN,2014-11-14 +"SunRiver Data System",SDS,1996-11-29 +"Super Gate Technology Company Ltd",SGL,1997-12-30 +"SuperNet Inc",SNT,1998-04-23 +"Supra Corporation",SUP,1996-11-29 +"Surenam Computer Corporation",SUR,1996-11-29 +"Surf Communication Solutions Ltd",SRF,1998-03-23 +"SVD Computer",SVD,1998-04-14 +SVSI,SVS,2008-08-09 +"SY Electronics Ltd",SYE,2010-09-20 +"Sylvania Computer Products",SYL,1998-06-12 +"Symbios Logic Inc",SLI,1996-11-29 +"Symbol Technologies",ISA,1997-06-02 +"Symicron Computer Communications Ltd.",SYM,1996-11-29 +"Synaptics Inc",SYN,1996-11-29 +"Synopsys Inc",SPS,1996-11-29 +Syntax-Brillian,SXB,2006-05-08 +"SYPRO Co Ltd",SYP,1998-11-27 +"Sysgration Ltd",SYS,1997-04-28 +"Syslogic Datentechnik AG",SLC,1999-01-20 +"Sysmate Company",SME,1997-09-02 +"Sysmate Corporation",SIC,1997-05-05 +Sysmic,SYC,1996-11-29 +"Systec Computer GmbH",SGZ,1997-10-02 +"System Craft",SCI,1996-11-29 +"system elektronik GmbH",SEB,2000-04-19 +"Systeme Lauer GmbH&Co KG",SLA,1999-03-20 +"Systems Enhancement",UPS,1996-11-29 +"SystemSoft Corporation",SST,1996-11-29 +"Systran Corporation",SCR,1996-11-29 +"SYVAX Inc",SYV,1996-11-29 +"T+A elektroakustik GmbH",TUA,2011-01-05 +"Taicom Data Systems Co., Ltd.",TCD,2001-10-08 +"Taicom International Inc",TMR,1996-11-29 +"Taiko Electric Works.LTD",TKC,2001-03-15 +"Taiwan Video & Monitor Corporation",TVM,1996-11-29 +"Takahata Electronics Co.,Ltd.",KTD,2009-07-22 +"Tamura Seisakusyo Ltd",TAM,1997-07-17 +Tandberg,TAA,2003-10-21 +"Tandberg Data Display AS",TDD,1996-11-29 +"Tandem Computer Europe Inc",TDM,1996-11-29 +"Tandon Corporation",TCC,1996-11-29 +"Tandy Electronics",TDY,1996-11-29 +"Taskit Rechnertechnik GmbH",TAS,1997-12-15 +"Tatung Company of America Inc",TCS,1996-11-29 +"Tatung UK Ltd",VIB,1999-07-16 +"Taugagreining hf",NRV,1996-11-29 +"Taxan (Europe) Ltd",TAX,1997-03-13 +"TDK USA Corporation",PMD,1996-11-29 +TDT,TDT,1996-11-29 +"TDVision Systems, Inc.",TDV,2008-01-18 +"TEAC System Corporation",TEA,1996-11-29 +"TEC CORPORATION",CET,1998-07-16 +"TEAC America Inc",TCJ,1996-11-29 +"Tech Source Inc.",TEZ,2013-08-14 +"Techmedia Computer Systems Corporation",TMC,1998-02-10 +"Technical Concepts Ltd",TCL,1996-11-29 +"Technical Illusions Inc.",TIL,2014-02-14 +"TechniSat Digital GmbH",TSD,2005-07-14 +"Technology Nexus Secure Open Systems AB",NXS,1998-05-08 +"Technology Power Enterprises Inc",TPE,1996-11-29 +"TechnoTrend Systemtechnik GmbH",TTS,1996-11-29 +"Tecmar Inc",TEC,1996-11-29 +"Tecnetics (PTY) Ltd",TCN,1996-11-29 +"TECNIMAGEN SA",TNM,2005-05-02 +Tecnovision,TVD,2006-03-13 +"Tectona SoftSolutions (P) Ltd.,",RXT,2004-06-02 +"Teknor Microsystem Inc",TKN,1996-11-29 +"Tekram Technology Company Ltd",TRM,1996-11-29 +"Tektronix Inc",TEK,1999-05-16 +"TEKWorx Limited",TWX,2009-12-24 +"Telecom Technology Centre Co. Ltd.",TCT,1999-07-16 +"Telecommunications Techniques Corporation",TTC,1996-11-29 +"Teleforce.,co,ltd",TLF,2012-11-19 +"Teleliaison Inc",TAT,1997-04-29 +"Telelink AG",TLK,1998-09-01 +"Teleprocessing Systeme GmbH",TPS,1997-01-24 +"Teles AG",TAG,1996-11-29 +"Teleste Educational OY",TLS,1996-11-29 +"TeleVideo Systems",TSI,1996-11-29 +"Telia ProSoft AB",PFT,1999-09-13 +Telindus,TLD,1996-11-29 +"Telxon Corporation",TLX,1996-11-29 +"Tennyson Tech Pty Ltd",TNY,1996-11-29 +Teradici,TDC,2007-10-11 +"TerraTec Electronic GmbH",TER,1997-03-21 +"Texas Insturments",TXN,1996-11-29 +"Texas Microsystem",TMI,1996-11-29 +"Textron Defense System",TXT,1996-11-29 +"The Concept Keyboard Company Ltd",CKC,1997-06-02 +"The Linux Foundation",LNX,2014-04-04 +"The Moving Pixel Company",PXL,2003-11-24 +"The NTI Group",ITN,1996-11-29 +"The OPEN Group",TOG,1999-09-13 +"The Panda Project",PAN,1996-11-29 +"The Phoenix Research Group Inc",PRG,1997-09-19 +"The Software Group Ltd",TSG,1996-11-29 +"Thermotrex Corporation",TMX,1996-11-29 +Thinklogical,TLL,2015-06-01 +"Thomas-Conrad Corporation",TCO,1996-11-29 +"Thomson Consumer Electronics",TCR,1998-08-20 +"Thruput Ltd",TPT,2010-06-16 +"Thundercom Holdings Sdn. Bhd.",THN,1997-03-21 +"Tidewater Association",TWA,1996-11-29 +"Time Management, Inc.",TMM,1999-03-20 +"TimeKeeping Systems, Inc.",TKS,1998-08-31 +"Times (Shanghai) Computer Co., Ltd.",TPD,2013-12-12 +"TIPTEL AG",TIP,1998-02-24 +"Tixi.Com GmbH",TIX,1998-10-16 +"T-Metrics Inc.",TMT,2000-02-21 +"TNC Industrial Company Ltd",TNC,1998-02-27 +"Todos Data System AB",TAB,1997-08-20 +"TOEI Electronics Co., Ltd.",TOE,2001-10-02 +TONNA,TON,2012-03-14 +"Top Victory Electronics ( Fujian ) Company Ltd",TPV,1999-05-16 +"TOPRE CORPORATION",TPK,2009-02-13 +"Topro Technology Inc",TPR,1998-05-08 +"Topson Technology Co., Ltd.",TTA,1998-09-23 +"TORNADO Company",SFM,1997-04-15 +"Torus Systems Ltd",TGS,1996-11-29 +"Torus Systems Ltd",TRS,1996-11-29 +"Toshiba America Info Systems Inc",TAI,1996-11-29 +"Toshiba America Info Systems Inc",TSB,1996-11-29 +"Dynabook Inc.",TOS,1996-11-29 +"Toshiba Corporation",TTP,2015-07-07 +"Toshiba Global Commerce Solutions, Inc.",TGC,2012-06-26 +"Toshiba Matsushita Display Technology Co., Ltd",LCD,2000-05-24 +"TOSHIBA PERSONAL COMPUTER SYSTEM CORPRATION",PCS,2010-06-22 +"TOSHIBA TELI CORPORATION",TLI,2008-01-18 +"Totoku Electric Company Ltd",TTK,1996-11-29 +"Tottori Sanyo Electric",TSE,1996-11-29 +"Tottori SANYO Electric Co., Ltd.",TSL,2001-11-06 +"Touch Panel Systems Corporation",TPC,1997-09-02 +"TouchKo, Inc.",TKO,2006-01-12 +"Touchstone Technology",TOU,2001-05-07 +TouchSystems,TSY,2008-01-18 +"TOWITOKO electronics GmbH",TWK,1998-04-14 +"Transtex SA",CSB,2001-03-15 +"Transtream Inc",TST,1997-04-29 +TRANSVIDEO,TSV,2010-05-04 +Tremetrics,TRE,1997-04-24 +"Tremon Enterprises Company Ltd",RDM,1996-11-29 +"Trenton Terminals Inc",TTI,1996-11-29 +"Trex Enterprises",TRX,2000-02-21 +"Tribe Computer Works Inc",OZO,1996-11-29 +"Tricord Systems",TRI,1996-11-29 +"Tri-Data Systems Inc",TDS,1996-11-29 +"TRIDELITY Display Solutions GmbH",TTY,2010-07-19 +"Trident Microsystem Inc",TRD,1996-11-29 +"Trident Microsystems Ltd",TMS,2002-07-15 +"TriGem Computer Inc",TGI,1996-11-29 +"TriGem Computer,Inc.",TGM,2001-07-05 +"Trigem KinfoComm",TIC,2003-02-26 +"Trioc AB",TRC,2000-01-13 +"Triple S Engineering Inc",TBB,1997-09-26 +"Tritec Electronic AG",TRT,2012-01-11 +"TriTech Microelectronics International",TRA,1997-01-24 +"Triumph Board a.s.",TRB,2013-09-27 +"Trivisio Prototyping GmbH",TRV,2011-11-18 +"Trixel Ltd",TXL,2000-08-10 +"Trtheim Technology",MKV,1997-03-17 +Truevision,TVI,1996-11-29 +"TTE, Inc.",TTE,2005-01-18 +"Tulip Computers Int'l B.V.",TCI,1996-11-29 +"Turbo Communication, Inc",TBC,1998-09-01 +"Turtle Beach System",TBS,1996-11-29 +"Tut Systems",TUT,1997-08-19 +"TV Interactive Corporation",TVR,1996-11-29 +"TV One Ltd",TVO,2008-09-02 +"TV1 GmbH",TVV,2012-02-06 +"TVS Electronics Limited",TVS,2008-05-20 +"Twinhead International Corporation",TWH,1996-11-29 +"Tyan Computer Corporation",TYN,1996-11-29 +"U. S. Electronics Inc.",USE,2013-10-28 +"U.S. Naval Research Lab",NRL,1996-11-29 +"U.S. Navy",TSP,2002-10-17 +"U.S. Digital Corporation",USD,1996-11-29 +"U.S. Robotics Inc",USR,1996-11-29 +"Ubinetics Ltd.",UBL,2002-05-23 +"Ueda Japan Radio Co., Ltd.",UJR,2003-07-09 +"UFO Systems Inc",UFO,1996-11-29 +"Ultima Associates Pte Ltd",UAS,1997-01-02 +"Ultima Electronics Corporation",UEC,1998-09-01 +"Ultra Network Tech",ULT,1996-11-29 +"Umezawa Giken Co.,Ltd",UMG,2008-04-10 +"Ungermann-Bass Inc",UBI,1996-11-29 +Unicate,UNY,1998-07-21 +"Uniden Corporation",UDN,2004-10-18 +"Uniform Industrial Corporation",UIC,1996-11-29 +"Uniform Industry Corp.",UNI,2001-11-06 +UNIGRAF-USA,UFG,2008-10-09 +"Unisys Corporation",UNB,1996-11-29 +"Unisys Corporation",UNC,1996-11-29 +"Unisys Corporation",UNM,1996-11-29 +"Unisys Corporation",UNO,1996-11-29 +"Unisys Corporation",UNS,1996-11-29 +"Unisys Corporation",UNT,1996-11-29 +"Unisys DSD",UNA,1996-11-29 +"Uni-Take Int'l Inc.",WKH,2002-06-17 +"United Microelectr Corporation",UMC,1996-11-29 +Unitop,UNP,2001-11-06 +"Universal Electronics Inc",UEI,1997-08-20 +"Universal Empowering Technologies",UET,1997-09-26 +"Universal Multimedia",UMM,2001-10-08 +"Universal Scientific Industrial Co., Ltd.",USI,2003-11-04 +"University College",JGD,1996-11-29 +"Uniwill Computer Corp.",UWC,2004-04-16 +"Up to Date Tech",UTD,1996-11-29 +UPPI,UPP,1998-04-14 +"Ups Manufactoring s.r.l.",RUP,2001-03-15 +"USC Information Sciences Institute",ASD,1997-04-08 +"Utimaco Safeware AG",USA,1998-05-04 +"Vaddio, LLC",VAD,2012-11-30 +Vadem,VDM,1996-11-29 +"VAIO Corporation",VAI,2014-04-18 +"Valence Computing Corporation",VAL,1996-11-29 +"Valley Board Ltda",VBT,2001-03-15 +"ValleyBoard Ltda.",VLB,1998-04-05 +"Valve Corporation",VLV,2013-03-06 +"VanErum Group",ITI,2013-10-01 +"Varian Australia Pty Ltd",VAR,2000-04-19 +"VATIV Technologies",VTV,2006-04-12 +"VBrick Systems Inc.",VBR,2009-08-19 +VCONEX,VCX,2005-06-15 +"VDC Display Systems",VDC,2009-04-29 +"Vector Informatik GmbH",VEC,1997-09-10 +"Vector Magnetics, LLC",VCM,2006-04-12 +Vektrex,VEK,1996-12-13 +"VeriFone Inc",VFI,1998-05-29 +"Vermont Microsystems",VMI,1996-11-29 +"Vestax Corporation",VTX,2012-02-14 +"Vestel Elektronik Sanayi ve Ticaret A. S.",VES,1997-09-19 +"Via Mons Ltd.",VIM,2012-08-29 +"VIA Tech Inc",VIA,1996-11-29 +"Victor Company of Japan, Limited",VCJ,2009-02-06 +"Victor Data Systems",VDA,2000-05-24 +"Victron B.V.",VIC,1996-11-29 +"Video & Display Oriented Corporation",VDO,1996-11-29 +"Video Computer S.p.A.",URD,1998-02-24 +"Video International Inc.",JWD,2000-02-21 +"Video Products Inc",VPI,2010-05-04 +"VideoLan Technologies",VLT,1997-10-17 +VideoServer,VSI,1997-06-25 +"Videotechnik Breithaupt",VTB,2013-07-23 +"VIDEOTRON CORP.",VTN,2010-05-04 +"Vidisys GmbH & Company",VDS,1996-11-29 +"Viditec, Inc.",VDT,1999-11-08 +"ViewSonic Corporation",VSC,1996-11-29 +"Viewteck Co., Ltd.",VTK,2001-10-08 +"Viking Connectors",VIK,1996-11-29 +"Vinca Corporation",VNC,1996-11-29 +"Vinci Labs",NHT,2006-03-03 +"Vine Micros Limited",VML,2004-06-16 +"Vine Micros Ltd",VIN,2000-04-19 +"Virtual Computer Corporation",VCC,1996-11-29 +"Virtual Resources Corporation",VRC,1996-11-29 +"Vision Quest",VQ@,2009-10-26 +"Vision Systems GmbH",VSP,1996-11-29 +Visioneer,VIS,1996-11-29 +"Visitech AS",VIT,2006-09-05 +"Vislink International Ltd",VLK,2012-08-27 +"VistaCom Inc",VCI,1996-11-29 +"Visual Interface, Inc",VIR,1998-11-27 +"Vivid Technology Pte Ltd",VTL,1996-11-29 +"VIZIO, Inc",VIZ,2012-06-06 +"VLSI Tech Inc",VTI,1996-11-29 +"VMware Inc.,",VMW,2011-10-18 +"Voice Technologies Group Inc",VTG,1997-04-24 +"Vortex Computersysteme GmbH",GDT,1996-11-29 +"VPixx Technologies Inc.",VPX,2013-12-05 +"VRmagic Holding AG",VRM,2013-04-12 +"V-Star Electronics Inc.",VSR,2000-02-21 +"VTech Computers Ltd",VTS,1996-11-29 +"VTel Corporation",VTC,1996-11-29 +"Vutrix (UK) Ltd",VUT,2003-07-22 +"Vweb Corp.",VWB,2004-03-12 +"Wacom Tech",WAC,1996-11-29 +"Wallis Hamilton Industries",JPW,1999-07-16 +"Wanlida Group Co., Ltd.",MLT,2014-04-24 +"Wave Access",WAL,1996-12-13 +"Wave Systems",AWS,1996-11-29 +"Wave Systems Corporation",WVM,1997-12-05 +Wavephore,WAV,1996-11-29 +"Way2Call Communications",SEL,1997-03-20 +"WB Systemtechnik GmbH",WBS,1997-09-08 +W-DEV,WEL ,2010-11-01 +"Wearnes Peripherals International (Pte) Ltd",WPI,1998-03-31 +"Wearnes Thakral Pte",WTK,1996-11-29 +"WebGear Inc",WEB,1998-01-30 +"Westermo Teleindustri AB",WMO,2000-01-13 +"Western Digital",WDC,1996-11-29 +"Westinghouse Digital Electronics",WDE,2003-05-23 +"WEY Design AG",WEY,2004-10-18 +"Whistle Communications",WHI,1998-10-24 +"Wildfire Communications Inc",WLD,1997-02-13 +"WillNet Inc.",WNI,2000-04-19 +"Winbond Electronics Corporation",WEC,1996-11-29 +"Diebold Nixdorf Systems GmbH",WNX,2004-09-20 +"Winmate Communication Inc",WMT,2001-03-15 +"Winnov L.P.",WNV,1997-03-07 +"WiNRADiO Communications",WRC,1997-09-11 +"Wintop Technology Inc",WIN,1996-12-29 +"Wipotec Wiege- und Positioniersysteme GmbH",WWP,2014-04-08 +"WIPRO Information Technology Ltd",WIL,1996-11-29 +"Wipro Infotech",WIP,2004-01-09 +"Wireless And Smart Products Inc.",WSP,1999-03-20 +"Wisecom Inc",WCI,1996-11-29 +"Wistron Corporation",WST,2010-09-03 +"Wolfson Microelectronics Ltd",WML,1997-07-30 +"WolfVision GmbH",WVV,2012-09-18 +"Woodwind Communications Systems Inc",WCS,1996-11-29 +"Wooyoung Image & Information Co.,Ltd.",WYT,2008-01-18 +"WorkStation Tech",WTI,1996-11-29 +"World Wide Video, Inc.",WWV,1998-10-24 +"Woxter Technology Co. Ltd",WXT,2010-09-03 +"X-10 (USA) Inc",XTN,1997-02-24 +"X2E GmbH",XTE,2009-09-23 +"XAC Automation Corp",XAC,1999-02-22 +"XDM Ltd.",XDM,2010-11-22 +"Xedia Corporation",MAD,1996-11-29 +"Xilinx, Inc.",XLX,2007-08-01 +"Xinex Networks Inc",XIN,1997-02-07 +"Xiotech Corporation",XIO,1998-05-29 +"Xircom Inc",XRC,1996-11-29 +"Xitel Pty ltd",XIT,1996-11-29 +"Xirocm Inc",XIR,1996-11-29 +"XN Technologies, Inc.",XNT,2003-07-14 +XOCECO,UHB,1998-11-27 +"XORO ELECTRONICS (CHENGDU) LIMITED",XRO,2005-05-23 +"XS Technologies Inc",XST,1998-01-20 +"Xscreen AS",XSN,2006-02-14 +XSYS,XSY,1998-04-23 +"Yamaha Corporation",YMH,1996-11-29 +"Xycotec Computer GmbH",XYC,2002-09-03 +"Yasuhiko Shirai Melco Inc",BUF,1996-11-29 +"Y-E Data Inc",YED,1996-11-29 +"Yokogawa Electric Corporation",YHQ,1996-11-29 +"Ypoaz Systems Inc",TPZ,1996-11-29 +"Z Microsystems",ZMZ,2005-08-10 +"Z3 Technology",ZTT,2010-12-14 +"Zalman Tech Co., Ltd.",ZMT,2007-05-07 +"Zandar Technologies plc",ZAN,2003-12-03 +"ZeeVee, Inc.",ZAZ,2008-01-18 +"Zebra Technologies International, LLC",ZBR,2003-09-15 +"Zefiro Acoustics",ZAX,1996-11-29 +"ZeitControl cardsystems GmbH",ZCT,1999-01-20 +"ZENIC Inc.",ZEN,2015-04-17 +"Zenith Data Systems",ZDS,1996-11-29 +"Zenith Data Systems",ZGT,1996-11-29 +"Zenith Data Systems",ZSE,1996-11-29 +"Zetinet Inc",ZNI,1996-11-29 +"Zhejiang Tianle Digital Electric Co., Ltd.",TLE,2014-01-17 +"Zhong Shan City Richsound Electronic Industrial Ltd.",RSR,2015-01-27 +"Znyx Adv. Systems",ZNX,1996-11-29 +"Zoom Telephonics Inc",ZTI,1996-11-29 +"Zoran Corporation",ZRN,2005-03-31 +"Zowie Intertainment, Inc",ZOW,1999-02-22 +"ZT Group Int'l Inc.",ZTM,2007-06-14 +"ZTE Corporation",ZTE,2010-09-03 +"Zuniq Data Corporation",SIX,1996-11-29 +"Zydacron Inc",ZYD,1997-04-10 +"ZyDAS Technology Corporation",ZTC,2000-05-24 +"Zypcom Inc",ZYP,1997-03-19 +"Zytex Computers",ZYT,1996-11-29 +"Zytor Communications",HPA,2010-07-02 +"Alpha Electronics Company",AEJ,1996-11-29 +BOE,BOE,2004-12-02 +"Chaplet Systems Inc",FIR,1996-11-29 +"Chenming Mold Ind. Corp.",CMG,2003-11-14 +"coolux GmbH",COO,2010-09-30 +"Data General Corporation",DGC,1996-11-29 +Exabyte,EXA,1996-11-29 +"Herolab GmbH",HRL,1998-03-17 +"Hitachi Computer Products Inc",HCP,1996-11-29 +"Integrated Circuit Systems",ICS,1996-11-29 +Irdata,IRD,2001-04-24 +"Jewell Instruments, LLC",JWL,2001-06-21 +"MultiQ Products AB",MQP,1999-03-20 +"Ncast Corporation",NAC,2006-02-14 +"ODME Inc.",ODM,1998-09-23 +Photomatrix,PMX,1996-11-29 +"Quantum Solutions, Inc.",QSI,2000-01-13 +"Red Hat, Inc.",RHT,2011-02-17 +Zyxel,ZYX,1996-11-29 +"Carrera Computer Inc",JAZ,1994-01-01 +"Chunghwa Picture Tubes, LTD",CGA,1994-01-01 +"eMicro Corporation",EMC,1994-01-01 +"Hisense Electric Co., Ltd.",HEC,1994-01-01 +PanaScope,PNS,1994-01-01 +"SpinCore Technologies, Inc",SPC,1994-01-01 +"Sensics, Inc.",SVR,2015-08-27 +"IAdea Corporation",IAD,2015-09-10 +"Express Industrial, Ltd.",ELU,2015-09-10 +"Hewlett Packard Enterprise",HPE,2015-09-22 +"Klipsch Group, Inc",KGI,2015-09-22 +"Tek Gear",TKG,2015-10-16 +"HangZhou ZMCHIVIN",ZMC,2015-10-16 +"HTC Corportation",HVR,2015-10-16 +"Zebax Technologies",ZBX,2015-10-16 +"Guangzhou Shirui Electronics Co., Ltd.",SWO,2015-10-16 +"Picturall Ltd.",PIC,2015-11-13 +"Guangzhou Teclast Information Technology Limited",SKM,2015-11-18 +"GreenArrays, Inc.",GAC,2015-11-18 +"Thales Avionics",TAV,2015-11-18 +"Explorer Inc.",EXR,2015-11-18 +"Avegant Corporation",AVG,2015-12-02 +"MicroImage Video Systems",MIV,2015-12-08 +"ASUSTek COMPUTER INC",AUS,2015-12-21 +"Synthetel Corporation",STQ,2015-12-21 +"HP Inc.",HPN,2015-12-21 +"MicroTechnica Co.,Ltd.",MTJ,2016-01-04 +"Gechic Corporation",GEC,2016-01-04 +"MPL AG, Elektronik-Unternehmen",MEU,2016-01-15 +"Display Solution AG",DSA,2016-02-03 +"UEFI Forum",PRP,2016-02-03 +"Taitex Corporation",TTX,2016-02-03 +"EchoStar Corporation",ECH,2016-02-26 +"TCL Corporation",TOL,2016-03-30 +"ADDER TECHNOLOGY LTD",ADZ,2016-03-30 +"HKC OVERSEAS LIMITED",HKC,2016-03-30 +"KEYENCE CORPORATION",KYN,2016-03-30 +"TETRADYNE CO., LTD.",TET,2016-04-27 +"Abaco Systems, Inc.",ABS,2016-04-27 +"Meta Company",MVN,2016-05-25 +"Eizo Rugged Solutions",ERS,2016-05-25 +"VersaLogic Corporation",VLC,2016-05-25 +"CYPRESS SEMICONDUCTOR CORPORATION",CYP,2016-05-25 +"MILDEF AB",MDF,2016-06-23 +"FOVE INC",FOV,2016-07-01 +INNES,NES,2016-07-01 +"Hoffmann + Krippner GmbH",HUK,2016-07-01 +"Axell Corporation",AXE,2016-08-03 +UltiMachine,UMT,2016-08-11 +"TPK Holding Co., Ltd",KPT,2016-08-16 +"AAEON Technology Inc.",AAN,2016-09-01 +"Six15 Technologies",TDG,2016-09-14 +"Inlife-Handnet Co., Ltd.",IVR,2017-01-19 +"VR Technology Holdings Limited",DSJ,2017-01-19 +"Pimax Tech. CO., LTD",PVR,2017-02-07 +"Total Vision LTD",TVL,2017-02-07 +"Shanghai Lexiang Technology Limited",DPN,2017-02-07 +"Black Box Corporation",BBX,2017-02-28 +"TRAPEZE GROUP",TRP,2017-02-28 +"Pabian Embedded Systems",PMS,2017-02-28 +"Televic Conference",TCF,2017-02-28 +"Shanghai Chai Ming Huang Info&Tech Co, Ltd",HYL,2017-02-28 +"Techlogix Networx",TLN,2017-02-28 +"G2TOUCH KOREA",GGT,2017-05-25 +"MediCapture, Inc.",MVR,2017-05-25 +"HOYA Corporation PENTAX Lifecare Division",PNT,2017-05-25 +"christmann informationstechnik + medien GmbH & Co. KG",CHR,2017-05-25 +Tencent,TEN,2017-06-20 +"VRstudios, Inc.",VRS,2017-06-22 +"Extreme Engineering Solutions, Inc.",XES,2017-06-22 +NewTek,NTK,2017-06-22 +"BlueBox Video Limited",BBV,2017-06-22 +"Televés, S.A.",TEV,2017-06-22 +"Avatron Software Inc.",AVS,2017-08-23 +"Positivo Tecnologia S.A.",POS,2017-09-01 +"VRgineers, Inc.",VRG,2017-09-07 +"Noritake Itron Corporation",NRI,2017-11-13 +"Matrix Orbital Corporation",MOC,2017-11-13 +"Elegant Invention",EIN,2018-03-29 +"Immersive Audio Technologies France",IMF,2018-03-29 +"Lightspace Technologies",LSP,2018-03-29 +"PixelNext Inc",PXN,2018-03-29 +"VRSHOW Technology Limited",TSW,2018-03-29 +"SONOVE GmbH",SNV,2018-03-29 +"Silex Inside",SXI,2018-03-29 +"Huawei Technologies Co., Inc.",HWV,2018-04-25 +"Varjo Technologies",VRT,2017-11-17 +"Japan E.M.Solutions Co., Ltd.",JEM,2018-05-24 +"QD Laser, Inc.",QDL,2018-05-31 +"VADATECH INC",VAT,2018-07-09 +"Medicaroid Corporation",MCJ,2018-08-20 +"Razer Taiwan Co. Ltd.",RZR,2018-08-20 +"GIGA-BYTE TECHNOLOGY CO., LTD.",GBT,2018-09-05 +"Kontron GmbH",KOM,2018-09-05 +"Convergent Engineering, Inc.",CIE,2018-09-05 +"WyreStorm Technologies LLC",WYR,2018-09-05 +"Astro HQ LLC",AHQ,2018-09-05 +"QSC, LLC",QSC,2019-01-18 +"Dimension Engineering LLC",DMN,2019-02-06 +"Shenzhen Dlodlo Technologies Co., Ltd.",DLO,2019-04-29 +"LENOVO BEIJING CO. LTD.",VLM,2019-05-21 +"Cammegh Limited",CRW,2019-06-18 +"Beihai Century Joint Innovation Technology Co.,Ltd",LHC,2019-09-10 +"Findex, Inc.",FDX,2019-10-22 +"Express Luck, Inc.",ELD,2019-10-22 +"LLC SKTB “SKIT”",SKI,2019-10-22 +"WOLF Advanced Technology",WLF,2019-10-22 +"BILD INNOVATIVE TECHNOLOGY LLC",BLD,2019-10-22 +"MIMO Monitors",MMT,2019-10-22 +Icron,ICR,2019-10-22 +"TECNART CO.,LTD.",PIS,2019-10-22 +"Moxa Inc.",MHQ,2019-10-22 +"Disguise Technologies",DSG,2019-10-22 +"Comark LLC",CMK,2020-07-15 +"Megapixel Visual Realty",MPV,2020-07-15 +Skyworth,SKW,2020-07-15 +"Meta View, Inc.",CFR,2020-07-15 +MILCOTS,MLC,2020-07-15 +"NZXT (PNP same EDID)_",NXT,2020-07-15 +"Unicompute Technology Co., Ltd.",UTC,2020-10-19 +"TECHNOGYM S.p.A.",TGW,2021-01-08 +"Clover Electronics",CLR,2021-02-02 +"Kyokko Communication System Co., Ltd.",KTS,2021-02-18 +"Terumo Corporation",TMO,2021-02-02 +"Micro-Star Int'l Co., Ltd.",CND,2021-02-17 +"Newline Interactive Inc.",NWL,2020-12-03 +"CORSAIR MEMORY Inc.",CRM,2021-02-05 +aviica,VAV,2021-06-01 +Monoprice.Inc,DMG,2021-06-04 +"Shenzhen KTC Technology Group",SKG,2021-06-01 +"Truly Semiconductors Ltd.",TLY,2021-06-01 +"Hitevision Group",HHT,2021-03-08 +"DLOGIC Ltd.",DLM,2021-06-10 +"Fun Technology Innovation INC.",FUL,2021-06-10 +"Guangxi Century Innovation Display Electronics Co., Ltd",IOC,2021-06-10 +"ICC Intelligent Platforms GmbH",EMR,2021-06-10 +"New H3C Technology Co., Ltd.",NHC,2021-06-10 +"Seco S.p.A.",SCG,2021-06-10 +"Silent Power Electronics GmbH",LCP,2021-06-10 +"NAFASAE INDIA Pvt. Ltd",NAF,2021-06-18 +"Pico Technology Inc.",PIR,2021-06-18 +"Life is Style Inc.",LIS,2021-06-18 +"Hansung Co., Ltd",HSN,2021-06-18 +"Hubei Century Joint Innovation Technology Co.Ltd",TTR,2021-06-18 +"Zake IP Holdings LLC (3B tech)",VIO,2021-06-18 +"PreSonus Audio Electronics",PAE,2021-06-24 +"C&T Solution Inc.",MXM,2021-07-21 +VARCem,VCE,2021-07-21 +"Nextorage Corporation",NXR,2021-09-21 +"Netvio Ltd.",NVO,2021-09-21 +"Beijing Guochengwantong Information Technology Co., Ltd.",STV,2021-09-21 +"Kopin Corporation",KOP,2021-10-01 +"Anker Innovations Limited",AKR,2021-12-10 +"SAMPO CORPORATION",SPO,2021-12-10 +"Shiftall Inc.",SFL,2021-12-31 +AudioControl,AUD,2021-12-31 +"Schneider Consumer Group",SCA,2022-02-08 +"NOLO CO., LTD.",NVR,2022-04-08 +"Riedel Communications Canada Inc.",RDL,2022-04-08 +"arpara Technology Co., Ltd.",IMX,2022-04-08 +Nreal,MRG,2022-04-08 +"Venetex Corporation",VNX,2022-04-08 +G.VISION,GVS,2022-06-17 +"Galaxy Microsystems Ltd.",GXL,2022-06-17 +"OZO Co.Ltd",OZD,2022-06-17 +"GoUp Co.,Ltd",GUP,2022-06-24 +"Eizo Technologies GmbH",ETG,2022-06-24 +"Steelseries ApS",SSG,2022-09-23 +"Weidmuller Interface GmbH & Co. KG",WMI,2022-09-23 +"Bigscreen, Inc.",BIG,2022-09-23 +"Jiangxi Jinghao Optical Co., Ltd.",OFI,2022-09-23 +"EverPro Technologies Company Limited",EVP,2022-09-30 +"NewCoSemi (Beijing) Technology CO.,Ltd.",NCV,2022-09-30 +"LG Display",LGD,2022-09-30 +"Tianma Microelectronics Ltd.",TMA,2022-10-20 +"Printronix LLC",PTX,2022-10-20 +Colorlight,KLT,2022-10-20 +"Beck GmbH & Co. Elektronik Bauelemente KG",BCK,2022-10-20 +"Shenzhen Soogeen Electronics Co., LTD.",SGN,2022-11-29 +"Emotiva Audio Corp. ",EAC,2023-02-02 +Delem,DLD,2023-04-05 +"UNIONMAN TECHNOLOGY CO., LTD",JLK,2023-04-19 +"Samsung Display Corp.",SDC,2023-04-25 +"KOGAN AUSTRALIA PTY LTD",KGN,2023-05-09 +"Brainlab AG",BRL,2023-05-16 +"Lincoln Technology Solutions",LNC,2023-05-24 +"Norxe AS",NXE,2023-06-07 +"Avocor Technologies USA, Inc",ATU,2023-06-07 +"HONOR Device Co., Ltd.",HNM,2023-06-07 +VITEC,VVI,2023-06-12 +"Dixon Technologies (India) Limited",DXN,2023-07-31 +"China Star Optoelectronics Technology Co., Ltd",CSW,2023-08-02 +RODE,OMG,2023-08-03 +AVARRO,RRO,2023-08-07 +"InfoVision Optoelectronics (Kunshan) Co.,Ltd China",IVO,2023-09-01 +"Moore Threads Virtual Display",MTT,2023-09-11 +"Lumens Digital Optics Inc.",LMS,2023-10-04 +"LUMINO Licht Elektronik GmbH",LLT,2023-11-07 +"Reonel Oy",RNL,2024-01-04 +DemoPad Software Ltd,DEM,2024-01-04 +"TeamViewer Germany GmbH",TMV,2024-01-04 +"Pixio USA",PXO,2024-02-14 +"ELARABY COMPANY FOR ENGINEERING INDUSTRIES",EEI,2024-02-14 +"Maxnerva Technology Services Limited",MNS,2024-02-14 +"Somnium Space Ltd.",SMN,2024-02-29 +"Raspberry PI",RPL,2024-05-07 +"DEIF A/S",DEF,2024-05-10 +"Moka International limited",MOK,2024-05-23 +"Shure Inc.",SHU,2024-06-13 +"Indicates an identity defined by CTS/DID Standards other than EDID",CID,2024-06-28 +"Daten Tecnologia",DTM,2024-06-15 +"LABAU Technology Corp.",LBC,2024-08-05 +"Xiaomi Corporation",XMI,2024-08-05 +"Airdrop Gaming LLC",ADG,2024-09-03 +"Ugreen Group Ltd.",UGR,2025-01-28 +"Barnfind Technologies",BFT,2025-01-28 \ No newline at end of file diff --git a/hwdb.d/usb.ids b/hwdb.d/usb.ids index 5832b6f9f6604..2980402eeb97a 100644 --- a/hwdb.d/usb.ids +++ b/hwdb.d/usb.ids @@ -9,8 +9,8 @@ # The latest version can be obtained from # http://www.linux-usb.org/usb.ids # -# Version: 2025.12.13 -# Date: 2025-12-13 20:34:01 +# Version: 2026.06.10 +# Date: 2026-06-10 20:34:02 # # Vendors, devices and interfaces. Please keep sorted. @@ -13057,6 +13057,7 @@ 5804 BCM5880 Secure Applications Processor with fingerprint swipe sensor 5832 BCM5880 Secure Applications Processor Smartcard reader 5843 BCM58200 ControlVault 3 (FingerPrint sensor + Contacted SmartCard) + 5865 Fingerprint Reader (Dell Control Vault) 6300 Pirelli Remote NDIS Device 6410 BCM20703A1 Bluetooth 4.1 + LE bd11 BCM4320 802.11bg Wireless Adapter diff --git a/man/90-rearrange-path.py b/man/90-rearrange-path.py index b5b6294754cae..278311f0ab523 100755 --- a/man/90-rearrange-path.py +++ b/man/90-rearrange-path.py @@ -17,6 +17,7 @@ import os import pathlib + def rearrange_bin_sbin(path): """Make sure any pair of …/bin, …/sbin directories is in this order @@ -27,15 +28,16 @@ def rearrange_bin_sbin(path): for i in range(len(items)): if 'sbin' in items[i].parts: ind = items[i].parts.index('sbin') - bin = pathlib.Path(*items[i].parts[:ind], 'bin', *items[i].parts[ind+1:]) - if bin in items[i+1:]: - j = i + 1 + items[i+1:].index(bin) + bin = pathlib.Path(*items[i].parts[:ind], 'bin', *items[i].parts[ind + 1 :]) + if bin in items[i + 1 :]: + j = i + 1 + items[i + 1 :].index(bin) items[i], items[j] = items[j], items[i] return ':'.join(p.as_posix() for p in items) + if __name__ == '__main__': - path = os.environ['PATH'] # This should be always set. - # If it is not, we will just crash, which is OK too. + # This should be always set. If it is not, we will just crash, which is OK too. + path = os.environ['PATH'] new = rearrange_bin_sbin(path) if new != path: - print('PATH={}'.format(new)) + print(f'PATH={new}') 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..bf5d2bf230984 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -102,15 +102,79 @@ - ID + ID - Removes a boot loader entry including the files it refers to. Takes a single boot - loader entry ID string or a glob pattern as argument. Referenced files such as kernel or initrd are - only removed if no other entry refers to them. + Removes a boot loader entry including the files it refers to. Takes an optional boot + loader entry ID string or a glob pattern as argument. Referenced files such as the kernel, initrds, + system extensions (sysexts), configuration extensions (confexts) or credential files are only removed + if no other entry refers to them. + + If no ID argument is specified, the option + must be specified, in which case the boot loader entry with the lowest version is removed (for + robustness reasons the currently booted menu entry is never removed, nor is the last existing boot + loader entry). + + KERNEL + + Creates one or more Type #1 boot loader entries pointing to the specified UKI. Takes + the path to a Unified Kernel Image (UKI) as argument. The UKI is copied into the ESP (or XBOOTLDR + partition if present) below the configured entry token directory, and one or more Boot Loader + Specification Type #1 entries are generated referring to it (one per UKI profile, if multiple + profiles are embedded). + + The title, version, commit number and initial try counter of the generated entries + may be overridden with , , + and . Additional sidecar resources + (system extension images, configuration extension images, credential files) to pass to the UKI at + boot may be specified with . + + If the ESP/XBOOTLDR do not have enough free space for the new boot loader entry and its + referenced resources the oldest existing boot loader entry matching the selected entry token is + removed (along with any resources referenced by it that are no longer referenced by any other + entry). This step is repeated until the new boot loader entry fits. For robustness reasons the + currently booted boot loader entry is never removed, nor is the last existing boot loader + entry. + + By default, the operation refuses to proceed if the resulting ESP/XBOOTLDR free space would + drop below a safety threshold after automatic removal of older entries completes; use + to adjust. + + + + + + + + Like link, but instead of taking the kernel and sidecar resources + as arguments, discovers them automatically. The following directories are searched, in decreasing + order of priority: /etc/systemd/uki/, /run/systemd/uki/, + /var/lib/systemd/uki/ (where + systemd-sysupdate8 + stages downloaded resources), /usr/local/lib/systemd/uki/ and + /usr/lib/systemd/uki/. In each directory the UKI is taken from + kernel.efi, or — if absent — the newest version in the versioned directory + kernel.efi.v/ (see + systemd.v7); the + UKI from the highest-priority directory that has one is used. Sidecar resources are picked up from + the extras.d/ subdirectory of each, matching the suffixes + .sysext.raw, .confext.raw and .cred, each + either as a plain file or as a versioned .v/ directory; resources are combined + across all directories, with the same resource in a higher-priority directory taking precedence. Any + files specified with are linked in addition to the discovered ones. + + This is used by + systemd-sysupdate8 + when it completes an update, so a freshly downloaded kernel and its sidecar resources are linked into + place without specifying any paths. + + + + @@ -406,6 +470,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, @@ -551,6 +624,88 @@ + + + + When used with unlink, selects the oldest installed boot loader + entry matching the boot entry token for removal (rather than passing an explicit entry ID). This is + useful for pruning older installed boot loader entries. Note that the currently booted entry is never + removed, nor is the last remaining one. + + + + + + + + When used with link, controls the minimum amount of free space + (in bytes) that must remain on the target partition (ESP or XBOOTLDR) after the new entry has been + materialized. The operation fails if installing the entry would drop the free space below this + threshold. Accepts the usual size suffixes (K, M, G, …). If empty, the built-in default is + restored. If set to zero no minimum amount of free space is kept. + + + + + + + + When used with link, specifies the title of the generated boot + loader entry (the title field of the Type #1 entry). If not specified, a title is + derived from the UKI's embedded metadata. + + + + + + + + When used with link, specifies the version string of the + generated boot loader entry (the version field of the Type #1 entry). If not + specified, the version is derived from the UKI's embedded metadata. Used by the boot loader to sort + and select entries. + + + + + + + + When used with link, specifies the commit number for the generated + boot loader entry. + + + + + + + + + When used with link, registers an additional sidecar resource + file that shall be passed to the UKI at boot. This may be a system extension image + (*.sysext.raw), configuration extension image + (*.confext.raw), or credential file + (*.cred). The file is copied into the ESP/XBOOTLDR alongside the UKI and the + boot loader will load and pass it to the kernel via initrd. This option may be used multiple times + to register more than one extra resource. If passed an empty argument, all previously specified + extras are cleared. + + + + + + + + When used with link, initializes the boot counting + tries-left counter for the generated entry. If set, the resulting boot entry file + is named according to the boot counting scheme described in the Automatic Boot Assessment documentation, + so that the boot loader decreases the counter on each attempted boot and eventually marks the entry + as bad. If not specified, boot counting is not enabled for the generated entry. + + + + @@ -718,6 +873,32 @@ Boot Loader Entries: + + Files + + + + /etc/systemd/uki/ + /run/systemd/uki/ + /var/lib/systemd/uki/ + /usr/local/lib/systemd/uki/ + /usr/lib/systemd/uki/ + + The link-auto command discovers the UKI to install from these + directories (listed in decreasing order of priority). It uses kernel.efi or the + versioned kernel.efi.v/ directory from the highest-priority directory that + contains one. Additional sidecar resources (system extension images, configuration extension images, + credential files) are picked up from the extras.d/ subdirectory of each, combined + across all directories. /var/lib/systemd/uki/ is the directory where + systemd-sysupdate8 + is supposed to place downloaded kernels, system extensions, configuration extensions and + credentials. + + + + + + See Also diff --git a/man/check-os-release-simple.py b/man/check-os-release-simple.py index ce73c77b14a36..8c64982790237 100755 --- a/man/check-os-release-simple.py +++ b/man/check-os-release-simple.py @@ -2,11 +2,11 @@ # SPDX-License-Identifier: MIT-0 import platform + os_release = platform.freedesktop_os_release() pretty_name = os_release.get('PRETTY_NAME', 'Linux') print(f'Running on {pretty_name!r}') -if 'fedora' in [os_release.get('ID', 'linux'), - *os_release.get('ID_LIKE', '').split()]: +if 'fedora' in [os_release.get('ID', 'linux'), *os_release.get('ID_LIKE', '').split()]: print('Looks like Fedora!') diff --git a/man/check-os-release.py b/man/check-os-release.py index f0a64f349662a..ec71e57a31a54 100755 --- a/man/check-os-release.py +++ b/man/check-os-release.py @@ -5,6 +5,7 @@ import re import sys + def read_os_release(): try: filename = '/etc/os-release' @@ -23,14 +24,13 @@ def read_os_release(): val = ast.literal_eval(val) yield name, val else: - print(f'{filename}:{line_number}: bad line {line!r}', - file=sys.stderr) + print(f'{filename}:{line_number}: bad line {line!r}', file=sys.stderr) + os_release = dict(read_os_release()) pretty_name = os_release.get('PRETTY_NAME', 'Linux') print(f'Running on {pretty_name!r}') -if 'debian' in [os_release.get('ID', 'linux'), - *os_release.get('ID_LIKE', '').split()]: +if 'debian' in [os_release.get('ID', 'linux'), *os_release.get('ID_LIKE', '').split()]: print('Looks like Debian!') diff --git a/man/clonetab.xml b/man/clonetab.xml new file mode 100644 index 0000000000000..651fa339cd163 --- /dev/null +++ b/man/clonetab.xml @@ -0,0 +1,121 @@ + + + + + + + + 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 five 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 or + - may be used as a placeholder. + + + + + At early boot and when the system manager configuration is reloaded, this file is + translated into native systemd units by + systemd-clonesetup-generator8. + + + + Examples + + + Simple clone without options + Clone a source device to a destination, using a separate metadata device: + mydevice /dev/sdb /dev/sdc /dev/sdd + + + + Clone with custom region size + Clone a source device to a destination with a custom region size of 16 sectors: + mydevice /dev/sdb /dev/sdc /dev/sdd region_size=16 + + + + + See Also + + systemd1 + systemd-clonesetup-generator8 + dmsetup8 + + + + diff --git a/man/crypttab.xml b/man/crypttab.xml index d57823f88ad73..38ba4ceafbe80 100644 --- a/man/crypttab.xml +++ b/man/crypttab.xml @@ -232,6 +232,13 @@ activation (e.g. via a file in /run/, generated by a service running before activation), and shall be removed after use. Defaults to off. + Note that this option only applies to a key file explicitly configured in the third field, and + has no effect on key files that are automatically discovered in + /etc/cryptsetup-keys.d/ and /run/cryptsetup-keys.d/. The + latter are considered shared resources that are not owned by an individual volume, and hence are never + erased. To erase an automatically discovered key file, configure its path explicitly in the third + field. + diff --git a/man/custom-entities.ent.in b/man/custom-entities.ent.in index 6c2738d9080e4..4494a1c3ab1af 100644 --- a/man/custom-entities.ent.in +++ b/man/custom-entities.ent.in @@ -18,5 +18,5 @@ - - + + diff --git a/man/environment.d.xml b/man/environment.d.xml index 70dc40c56c900..0b00296d6e5dd 100644 --- a/man/environment.d.xml +++ b/man/environment.d.xml @@ -68,13 +68,33 @@ ${FOO:+ALTERNATE_VALUE} to expand to ALTERNATE_VALUE as long as ${FOO} would have expanded to a non-empty value. + You can escape a literal dollar sign $ by doubling it, as in + $$. + + A backslash character \ followed by a newline acts as a line + continuation (both characters are discarded); in all other cases, the backslash is consumed and + the following character is included literally in the value. This can be used to preserve + leading/trailing whitespace or include literal backslashes. + + You may optionally enclose the right hand side of assignments in pairs of either + 'single quotes' or "double + quotes"; these will be stripped from the value before saving it. Using + either quoting style does not affect variable expansion; however, single quotes disable + backslash escaping entirely, while double quoting removes backslashes that precede double quotes + ", dollar signs $, backslashes \, + backticks `, and newlines, and leaves all other backslashes in place. + + Leading and trailing whitespace characters are stripped from both the left and right hand + sides of the assignment. To preserve such spaces in the value, you can either enclose the entire + value in quotes, or escape the space characters with backslashes. You can also include literal + newlines in the value by enclosing it in quotes. + + Each KEY must be a valid variable name. Empty lines and lines + beginning with the comment characters # or ; are ignored. No other elements of shell syntax are supported. - Each KEY must be a valid variable name. Empty lines - and lines beginning with the comment character # are ignored. - - Example + Examples Setup environment to allow access to a program installed in <filename index="false">/opt/foo</filename> @@ -82,10 +102,39 @@ /etc/environment.d/60-foo.conf: - FOO_DEBUG=force-software-gl,log-verbose - PATH=/opt/foo/bin:$PATH - LD_LIBRARY_PATH=/opt/foo/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} - XDG_DATA_DIRS=/opt/foo/share:${XDG_DATA_DIRS:-/usr/local/share/:/usr/share/} +FOO_DEBUG=force-software-gl,log-verbose +PATH=/opt/foo/bin:$PATH +LD_LIBRARY_PATH=/opt/foo/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} +XDG_DATA_DIRS=/opt/foo/share:${XDG_DATA_DIRS:-/usr/local/share/:/usr/share/} + + + + + Escaping and variable expansion + + /etc/environment.d/70-bar.conf: + + +# A comment +test_var=(variable) +; Another comment +test_comment=value # This is a part of the variable, and not a comment! + +test_none= \ before\ +after "" '' \${test_var} +test_double=" \ before\ +after "" '' \${test_var}" +test_single=' \ before\ +after "" '' \${test_var}' + +# The above example produces the following environment variables, written using +# C-style string escapes: +# +# test_var="(variable)" +# test_comment="value # This is a part of the variable, and not a comment!" +# test_none=" beforeafter \"\" '' (variable)" +# test_double=" \\ beforeafter '' (variable)" +# test_single=" \\ before\\\nafter \"\" \\(variable)" 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/hostname.xml b/man/hostname.xml index 20f00057fdc50..708581c841df9 100644 --- a/man/hostname.xml +++ b/man/hostname.xml @@ -6,7 +6,7 @@ ]> - + hostname systemd @@ -52,6 +52,49 @@ foobar-????-???? will automatically expand to foobar-92a9-061c or similar, depending on the local machine ID. + In addition, the token $ is substituted by a word picked + deterministically from a word list, again derived from the + machine-id5 by + cryptographic hashing. Each $ is positional: the first $ uses the word + list file named 1, the second 2, and so on. This allows + human-friendly names, for example $-$-$-???? might expand to + wildly-happy-octopus-92a9. The word lists are searched for in + /etc/systemd/hostname-wordlist/, /run/systemd/hostname-wordlist/, + /usr/local/lib/systemd/hostname-wordlist/ and + /usr/lib/systemd/hostname-wordlist/ (the first directory providing a given list wins, + lists are not merged); one word per line, with empty lines and lines starting with # + ignored. + + The word for each token is derived deterministically from the machine ID and recomputed on every + boot (the lists are not loaded wholesale: a word is chosen by hashing to a byte offset into the file). + Consequently the word lists must be kept stable: changing a list (adding, removing, or reordering words) + may change the name a machine already has, so installations that rely on persistent hostnames must not + modify the lists after deployment. If a referenced list is missing the + name is treated as invalid and the built-in fallback hostname is used. The combined name space is the + product of the list sizes, so collisions follow the birthday bound; append a few ? + characters for extra entropy when uniqueness across a large fleet matters. + + Note that hostname can be at most 64 characters long. The word lists and the pattern should be + chosen so that the longest possible expansion (the longest words from each list plus any literal and + ? characters and separators) stays within this limit. An expanded name that exceeds + the limit is considered invalid and the built-in fallback hostname is used. + + Because the name only stays stable as long as the pattern and word lists are unchanged, the + preferred way to obtain a stable, generated hostname is to provide the pattern through the + firstboot.hostname credential (see + systemd.system-credentials7) + rather than placing it directly in this file. Concretely, write the pattern to + /etc/credstore/firstboot.hostname (see the description of + LoadCredential= in + systemd.exec5 for the + credential store): when firstboot runs on the live system the credential is resolved once and the + concrete result is persisted here, so the word lists may be updated afterwards without changing the + hostname. A pattern placed directly in /etc/hostname (or provisioned into an offline + image) is instead re-evaluated on every boot, which only really makes sense for systems that keep the + wordlist and the pattern stable. + + + You may use hostnamectl1 to change the value of this file during runtime from the command line. Use diff --git a/man/hostnamectl.xml b/man/hostnamectl.xml index 8ac18349c6718..4c1d3cf245ec4 100644 --- a/man/hostnamectl.xml +++ b/man/hostnamectl.xml @@ -165,6 +165,43 @@ + + + tags [TAG…] + + If no argument is given, print the machine tags currently configured, as a + colon-separated list. If one or more TAG arguments are provided then the + command replaces the configured tags with the specified ones. Each argument may itself be a + colon-separated list of tags, so that the tags may be specified either as multiple arguments or as a + single colon-separated argument, or any combination thereof. Duplicate tags are removed and the + resulting list is sorted before being stored. To remove all tags, invoke the command with a single + empty string argument. + + Alternatively, each argument may be prefixed with + or - + to incrementally update the configured tags rather than replacing them: an argument prefixed with + + adds all of its (colon-separated) tags to the list, one prefixed with + - removes them. For example, hostnamectl tags +foo:bar -quux:ooo + adds the tags foo and bar and removes quux + and ooo. The prefix applies to the whole argument, i.e. to every tag listed within + it. Prefixed and unprefixed arguments may not be combined in a single invocation. + + Machine tags are short labels that may be used to classify and group machines for management + purposes, for example to identify the role a machine plays in a deployment, the fleet or + organizational unit it belongs to, or any other administrator-defined attribute. Each individual tag + must be 1…255 characters long and consist only of ASCII alphanumeric characters, + -, . and =. A tag may optionally be + parameterized with a value, in the form + key=value, in which case the + same key may not be assigned more than one distinct value. The tags are stored in the + TAGS= field of /etc/machine-info; see + machine-info5 for + details. They may also be matched against with the + ConditionMachineTag=/AssertMachineTag= unit settings, see + systemd.unit5. + + + + diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 4da1796a97ca2..8253f0fafd401 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 @@ -78,6 +79,17 @@ + + systemd.restrict_filesystem_access= + + Controls the RestrictFileSystemAccess= execution enforcement policy. For + details, see + systemd-system.conf5. + + + + + systemd.mask= systemd.wants= @@ -710,8 +722,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. @@ -724,6 +736,15 @@ is set in /etc/hostname. Note that this does not bar later runtime changes to the hostname, it simply controls the initial hostname set during early boot. + The name may use the ? and $ wildcard patterns documented + in hostname5, + which are expanded deterministically from the machine ID each time the name is applied. The expanded + name is never persisted, hence it may change if the word lists change. Note that in the initrd the + word lists (and possibly the machine ID) are typically not available; the option is then ignored and + the hostname is instead determined from the other sources, i.e. + /etc/hostname or the system.hostname credential, falling back + to the default hostname if none of those apply. + @@ -782,6 +803,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 +836,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/machine-info.xml b/man/machine-info.xml index 53c9e64ec49f1..6b2d92e7e5a09 100644 --- a/man/machine-info.xml +++ b/man/machine-info.xml @@ -139,6 +139,42 @@ + + TAGS= + + A colon-separated list of tags attached to this machine. Tags are short labels that + may be used to classify and group machines for management purposes, for example to identify the role + a machine plays in a deployment (webserver, database), the + fleet or organizational unit it belongs to, or any other administrator-defined attribute. Example: + TAGS=webserver:frontend:berlin. + + Each individual tag must be 1…255 characters long and may consist only of the ASCII + alphanumeric characters, -, . and =. The + first character may not be -, . or =, and + the last character may not be - or . (unless it takes the + parameterized form, see below). + + A tag may optionally be parameterized with a value, in the form + key=value. The first + = separates the key from the value; any further = characters + are part of the value. The key (the part before the first =) follows the same + restrictions as an unparameterized tag, in particular it may not be empty and may not end in + - or .. The value (the part after the first + =) may be empty and is otherwise unrestricted within the allowed character set. + Example: TAGS=role=webserver:env=production:berlin. The same key may not be + assigned more than one distinct value: role=webserver:role=database is refused + (but a key may coexist with the corresponding unparameterized tag, e.g. + role:role=webserver). + + The configured tags may be matched against with the + ConditionMachineTag= and AssertMachineTag= unit settings, see + systemd.unit5 + for details. They may be queried and changed with the tags command of + hostnamectl1. + + + + HARDWARE_VENDOR= @@ -195,7 +231,8 @@ PRETTY_HOSTNAME="Lennart's Tablet" ICON_NAME=computer-tablet CHASSIS=tablet -DEPLOYMENT=production +DEPLOYMENT=production +TAGS=demo:berlin:role=webserver diff --git a/man/machinectl.xml b/man/machinectl.xml index e64a20bb1d045..10ab225074dfc 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 @@ -332,6 +357,38 @@ + + bind-volume NAME SPEC + + Acquire a storage volume from a + storagectl1 + provider and attach it to the running machine. SPEC is a string of the form + PROVIDER:VOLUME[:CONFIG][:K=V,…], + identical in grammar to the argument of + systemd-vmspawn1. + + The attached volume is identified by the name PROVIDER:VOLUME + and may be detached at runtime via unbind-volume. Currently only supported for + systemd-vmspawn machines that expose an + io.systemd.MachineInstance control socket. + + + + + + unbind-volume NAME STORAGE-NAME + + Detach a storage volume from the running machine. STORAGE-NAME + is the PROVIDER:VOLUME + identifier that was specified at bind-volume time. Volumes that were attached at machine + startup (e.g. via on + systemd-vmspawn1) + cannot be detached and will fail with + io.systemd.MachineInstance.StorageImmutable. + + + + copy-to NAME PATH [PATH] diff --git a/man/networkctl.xml b/man/networkctl.xml index 561eb932386bf..b436797999e04 100644 --- a/man/networkctl.xml +++ b/man/networkctl.xml @@ -287,6 +287,122 @@ + + + dhcp-lease + INTERFACE + CODE:FORMAT + … + + + + Show the DHCP message of the acquired lease (i.e. the received DHCPACK message). Takes an + interface name and optional DHCP option codes. If no option code is specified, both the DHCP + message header and all options are shown. If one or more option codes are specified, only the + specified options are shown. + + When called with the option, the entire DHCP message is shown in + JSON format. In that case, any specified option codes are ignored. + + By default, each option is displayed in a human-readable format, depending on the option. + For example, DNS server addresses are shown as a list of IP addresses (e.g. + 192.0.2.1), and the lease time is shown as a human-readable duration (e.g. + 8h). + + Each option code may optionally be followed by a formatter, separated by a colon (e.g. + 42:address). This can be useful if the default formatting is not appropriate. + + + The following formatters are supported: + + + + + Use the default formatting. + + + + + + Show the raw bytes in hexadecimal, separated by colons. + + + + + + Treat the option as having zero-length data and display yes. + + + + + + Treat the option as a 1-byte boolean and display yes or no. + + + + + + Treat the option as a 1-byte unsigned integer. + + + + + + Treat the option as a 2-byte unsigned integer. + + + + + + Treat the option as a 4-byte time value in seconds and display it as a human-readable duration. + + + + + + Treat the option as a string. + + + + + + Treat the option as one or more IPv4 addresses and display them in human-readable form. + + + + + Produces output similar to: + $ networkctl dhcp-lease eth0 +Header: + KEY VALUE + Hardware Type: ETHER +Hardware Address: 41:42:43:31:32:33 + Client Address: 192.0.2.123 + Server Address: 192.0.2.1 + +Options: +CODE NAME DATA + 1 subnet mask 255.255.255.0 + 3 router 192.0.2.1 + 6 domain name server 192.0.2.1 + 12 hostname test-node + 15 domain name lan + 28 broadcast address 192.0.2.255 + 42 NTP server 192.0.2.11 + 192.0.2.12 + 51 lease time 1d + 53 message type 5 + 54 server identifier 192.0.2.1 + 58 renewal time 11h 5min 38s + 59 rebinding time 20h 5min 38s + 119 domain search hoge.example.com + foo.example.com + + + + + + lldp diff --git a/man/networkd.conf.xml b/man/networkd.conf.xml index f611e244f5aaf..8dd1181508d39 100644 --- a/man/networkd.conf.xml +++ b/man/networkd.conf.xml @@ -393,6 +393,67 @@ DUIDRawData=00:00:ab:11:f9:2a:c2:77:29:f9:5c:00 + + [DHCPRelay] Section Options + The [DHCPRelay] section contains host-wide settings for the DHCP relay agent. + + + + + ServerAddress= + + Specifies the IPv4 address of the upstream DHCP server. This is required for the host to act + as a DHCP relay agent. + + + + + + + OverrideServerIdentifier= + + Takes a boolean value. When enabled, the Server Identifier Override sub-option and the Relay + Agent Flags sub-option in the Relay Agent Information option will be appended to the DHCP message + forwarded to the upstream DHCP server. Defaults to false. + + + + + + + RemoteId= + + Specifies the remote ID, which is typically used by DHCP servers to identify the DHCP relay + agent. Takes a data type and data separated with a colon + (type:value). The type + takes one of uint8, uint16, uint32, + ipv4address, ipv6address, or string. + Special characters in the data string may be escaped using + C-style escapes. + If unset, a UUID generated from the local machine ID will be used. + + + + + + + ExtraOption= + + Specifies an extra sub-option in the Relay Agent Information option, which is appended to + DHCP messages forwarded to the upstream DHCP server. Takes a sub-option code, data type, and data + separated with a colon + (code:type:value). + The code is an integer in 1…254. See RemoteId= in the above for the acceptable + type and data. This setting can be specified multiple times. When an empty string is specified, + all previous assignments are cleared. Defaults to unset, and no extra sub-option will be appended. + + + + + + + + [DHCPServer] Section Options diff --git a/man/notify-selfcontained-example.py b/man/notify-selfcontained-example.py index 6a1e25b99b2ff..1302116221146 100755 --- a/man/notify-selfcontained-example.py +++ b/man/notify-selfcontained-example.py @@ -18,49 +18,56 @@ reloading = False terminating = False + def notify(message): if not message: - raise ValueError("notify() requires a message") + raise ValueError('notify() requires a message') - socket_path = os.environ.get("NOTIFY_SOCKET") + socket_path = os.environ.get('NOTIFY_SOCKET') if not socket_path: return - if socket_path[0] not in ("/", "@"): - raise OSError(errno.EAFNOSUPPORT, "Unsupported socket type") + if socket_path[0] not in ('/', '@'): + raise OSError(errno.EAFNOSUPPORT, 'Unsupported socket type') # Handle abstract socket. - if socket_path[0] == "@": - socket_path = "\0" + socket_path[1:] + if socket_path[0] == '@': + socket_path = '\0' + socket_path[1:] with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM | socket.SOCK_CLOEXEC) as sock: sock.connect(socket_path) sock.sendall(message) + def notify_ready(): - notify(b"READY=1") + notify(b'READY=1') + def notify_reloading(): microsecs = time.clock_gettime_ns(time.CLOCK_MONOTONIC) // 1000 - notify(f"RELOADING=1\nMONOTONIC_USEC={microsecs}".encode()) + notify(f'RELOADING=1\nMONOTONIC_USEC={microsecs}'.encode()) + def notify_stopping(): - notify(b"STOPPING=1") + notify(b'STOPPING=1') + def reload(signum, frame): global reloading reloading = True + def terminate(signum, frame): global terminating terminating = True + def main(): - print("Doing initial setup") + print('Doing initial setup') global reloading, terminating # Set up signal handlers. - print("Setting up signal handlers") + print('Setting up signal handlers') signal.signal(signal.SIGHUP, reload) signal.signal(signal.SIGINT, terminate) signal.signal(signal.SIGTERM, terminate) @@ -68,13 +75,13 @@ def main(): # Do any other setup work here. # Once all setup is done, signal readiness. - print("Done setting up") + print('Done setting up') notify_ready() - print("Starting loop") + print('Starting loop') while not terminating: if reloading: - print("Reloading") + print('Reloading') reloading = False # Support notifying the manager when reloading configuration. @@ -86,19 +93,20 @@ def main(): # Do some reconfiguration work here. - print("Done reloading") + print('Done reloading') notify_ready() # Do the real work here ... - print("Sleeping for five seconds") + print('Sleeping for five seconds') time.sleep(5) - print("Terminating") + print('Terminating') notify_stopping() -if __name__ == "__main__": + +if __name__ == '__main__': sys.stdout.reconfigure(line_buffering=True) - print("Starting app") + print('Starting app') main() - print("Stopped app") + print('Stopped app') diff --git a/man/oomd.conf.xml b/man/oomd.conf.xml index a4be5e1274ff9..1e33f3b3ded43 100644 --- a/man/oomd.conf.xml +++ b/man/oomd.conf.xml @@ -62,12 +62,98 @@ 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. + + OOM Rulesets + + systemd-oomd supports custom rulesets that define conditions and actions for + OOM handling on a per-unit basis. Ruleset files use the .oomrule extension and are + loaded from /etc/systemd/oomd/rules.d/, + /run/systemd/oomd/rules.d/, + /usr/local/lib/systemd/oomd/rules.d/, and + /usr/lib/systemd/oomd/rules.d/. + Units opt into rulesets via the OOMRules= setting in + systemd.resource-control5, + which takes a space-separated list of ruleset names (the file name without the .oomrule + extension). + + Each ruleset file contains a [Rule] section with the following options. At least + one of MemoryPressureAbove= or SwapUsageMax= must be configured; + rulesets with no conditions are ignored. If both are set, the conditions are combined with AND, i.e. the + action is only triggered when both thresholds are exceeded simultaneously. + + + + MemoryPressureAbove= + + Sets the memory pressure threshold above which the rule's action will be triggered. + The memory pressure represents the fraction of time in a 10 second window in which all tasks in the + control group were delayed (PSI full avg10). Takes a value specified in percent + (when suffixed with %), permille () or permyriad + (), between 0% and 100%, inclusive. If unset, this condition is not + evaluated. A value of 100% can never be exceeded and is + therefore rejected with a warning; a value of 0% makes the condition true on any + observed pressure, which is usually not useful. + + + + + + SwapUsageMax= + + Sets the system-wide swap usage threshold above which the rule's action will be + triggered. Takes a value specified in percent (when suffixed with %), + permille () or permyriad (), + between 0% and 100%, inclusive. If unset, this condition is not evaluated. A value of + 100% can never be exceeded and is therefore rejected with + a warning; a value of 0% fires as soon as any swap is in use, which is usually + not useful. + + + + + + Action= + + Specifies the action to take when the rule's conditions are met. Takes one of + kill-all, kill-by-pgscan, or + kill-by-swap. This setting is mandatory; rulesets without + Action= are ignored. + + + kill-all sends SIGKILL to every process + in the unit's cgroup hierarchy, including any descendant cgroups. + + kill-by-pgscan selects and kills the descendant cgroup with + the highest recent page scan (reclaim) rate. + + kill-by-swap selects and kills the descendant cgroup with the + highest swap usage. + + + + + + + LastingSec= + + Sets the duration the conditions must be continuously met before the action is taken. + Takes a time span value, see + systemd.time7 + for details on the permitted syntax. Defaults to 0, i.e. the action is taken + immediately when the conditions are met. + + + + + + + [OOM] Section Options diff --git a/man/org.freedesktop.hostname1.xml b/man/org.freedesktop.hostname1.xml index c70258459c246..67b52e1ed418c 100644 --- a/man/org.freedesktop.hostname1.xml +++ b/man/org.freedesktop.hostname1.xml @@ -56,10 +56,15 @@ node /org/freedesktop/hostname1 { in b interactive); SetLocation(in s location, in b interactive); + SetTags(in as tags); + AddAndRemoveTags(in as add, + in as remove); GetProductUUID(in b interactive, 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 = '...'; @@ -71,6 +76,7 @@ node /org/freedesktop/hostname1 { readonly s Chassis = '...'; readonly s Deployment = '...'; readonly s Location = '...'; + readonly as Tags = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s KernelName = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") @@ -140,12 +146,18 @@ node /org/freedesktop/hostname1 { + + + + + + @@ -164,6 +176,8 @@ node /org/freedesktop/hostname1 { + + @@ -264,6 +278,15 @@ node /org/freedesktop/hostname1 { configure the chassis type if it could not be auto-detected. Set this property to the empty string to reenable the automatic detection of the chassis type from firmware information. + The Tags property exposes a list of machine tags: short + labels that may be used to classify and group machines for management purposes, for example to identify + the role a machine plays in a deployment, the fleet or organizational unit it belongs to, or any other + administrator-defined attribute. Each individual tag is 1…255 characters long and consists only of the + ASCII alphanumeric characters, - and .. The tags are stored as a + colon-separated list in the TAGS= field of /etc/machine-info, see + machine-info5 for + details. If no tags are configured this property is the empty list. + Note that systemd-hostnamed starts only on request and terminates after a short idle period. This effectively means that PropertyChanged messages are not sent out for changes made directly on the files (as in: administrator edits the files with vi). This is @@ -361,8 +384,20 @@ node /org/freedesktop/hostname1 { deployment environment), and Location (physical system location), respectively. + SetTags() sets the machine tags exposed by the Tags + property. It takes a list of strings, each of which must be a valid machine tag (1…255 ASCII + alphanumeric characters, - and .). Passing an empty list removes + the TAGS= field from /etc/machine-info. + + AddAndRemoveTags() incrementally updates the Tags + property instead of replacing it. It takes two lists of machine tags: the tags in add + are added to the current list, and the tags in remove are dropped from it (the + resulting list is (current ∪ add) \ remove, sorted and deduplicated). This is useful + when several independent agents each manage a subset of the tags without racing on a read-modify-write + of the full list. Each tag must be a valid machine tag, as for SetTags(). + PrettyHostname, IconName, Chassis, - Deployment, and Location are stored in + Deployment, Location, and Tags are stored in /etc/machine-info. See machine-info5 for the semantics of those settings. @@ -377,6 +412,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. @@ -389,8 +431,9 @@ node /org/freedesktop/hostname1 { org.freedesktop.hostname1.set-hostname. For SetStaticHostname() and SetPrettyHostname() it is org.freedesktop.hostname1.set-static-hostname. For - SetIconName(), SetChassis(), SetDeployment() - and SetLocation() it is + SetIconName(), SetChassis(), SetDeployment(), + SetLocation(), SetTags() and + AddAndRemoveTags() it is org.freedesktop.hostname1.set-machine-info. @@ -494,6 +537,9 @@ node /org/freedesktop/hostname1 { OperatingSystemImageVersion, HardwareSKU, and HardwareVersion were added in version 258. OperatingSystemFancyName was added in version 260. + GetMachineInfo(), Tags and + SetTags() were added in version 261. + AddAndRemoveTags() was added in version 262. 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..e7e56466d2a51 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -109,6 +109,11 @@ node /org/freedesktop/systemd1 { out o unit_path, out s job_type, out a(uosos) affected_jobs); + EnqueueUnitJobMany(in as units, + in s job_type, + in s job_mode, + in t flags, + out a(uosos) jobs); KillUnit(in s name, in s whom, in i signal); @@ -475,6 +480,10 @@ node /org/freedesktop/systemd1 { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly u DefaultStartLimitBurst = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly t EventLoopRateLimitIntervalUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly u EventLoopRateLimitBurst = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b DefaultIOAccounting = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b DefaultIPAccounting = ...; @@ -552,6 +561,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 +581,12 @@ node /org/freedesktop/systemd1 { readonly s CtrlAltDelBurstAction = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly u SoftRebootsCount = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly u KExecsCount = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly t ReloadCount = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly b DefaultMemoryZSwapWriteback = ...; }; interface org.freedesktop.DBus.Peer { ... }; interface org.freedesktop.DBus.Introspectable { ... }; @@ -575,8 +598,6 @@ node /org/freedesktop/systemd1 { - - @@ -713,6 +734,10 @@ node /org/freedesktop/systemd1 { + + + + @@ -791,6 +816,14 @@ node /org/freedesktop/systemd1 { + + + + + + + + @@ -801,6 +834,8 @@ node /org/freedesktop/systemd1 { + + @@ -839,6 +874,8 @@ node /org/freedesktop/systemd1 { + + @@ -1161,6 +1198,10 @@ node /org/freedesktop/systemd1 { + + + + @@ -1239,6 +1280,14 @@ node /org/freedesktop/systemd1 { + + + + + + + + @@ -1251,6 +1300,12 @@ node /org/freedesktop/systemd1 { + + + + + + @@ -1312,6 +1367,37 @@ node /org/freedesktop/systemd1 { the "Try" flavor is used in which case a service that is not running is not affected by the restart. The "ReloadOrRestart" flavors attempt a reload if the unit supports it and use a restart otherwise. + EnqueueUnitJob() is similar to the various + StartUnit()/StopUnit()/RestartUnit() + methods above, but provides a unified interface that operates on a single unit. The + job_type argument selects the job type and is one of + start, stop, reload, + restart, try-restart, + reload-or-restart or reload-or-try-restart. The + job_mode argument matches the mode argument of + StartUnit(). On reply the method returns the enqueued anchor job's + numeric id and object path, the affected unit's id and object path, the job type + actually queued (after type collapsing, e.g. reload-or-restart turns + into restart or reload), and an array of + additional jobs that were enqueued as part of the same transaction in order to satisfy + dependencies. + + EnqueueUnitJobMany() is similar to + EnqueueUnitJob(), but operates on multiple units at once. All + requested units are enqueued in a single transaction, which is necessary in order to + properly honour After=/Before= ordering between + units passed in the same call regardless of their order in the array. The + job_type argument is applied to each of the listed units, including + the magic values reload-or-restart and + reload-or-try-restart which are resolved per unit (units that + support reloading get a reload job, others get a restart). The flags + argument does not support any flags for now and must be passed as 0; + it is reserved for future extensions. On reply the method returns an array of tuples + describing the anchor job that was enqueued for each requested unit: the numeric job + id, the job object path, the unit id, the unit object path, and the job type that was + actually queued. Note that the order of tuples in the reply is unspecified and may not + correspond to the order of the input array. + EnqueueMarkedJobs() creates reload/restart jobs for units which have been appropriately marked, see Markers property above. This is equivalent to calling TryRestartUnit() or ReloadOrTryRestartUnit() for the marked @@ -1836,6 +1922,15 @@ node /org/freedesktop/systemd1 { SoftRebootsCount encodes how many soft-reboots were successfully completed since the last full boot. Starts at 0. + KExecsCount encodes how many kexec-based live updates were successfully + completed since the last full boot. Starts at 0. The counter is preserved across the + kexec through the Live Update Orchestrator (LUO), and is hence 0 when the kernel does + not provide LUO support. + + ReloadCount encodes the number of successfully completed configuration + reloads of the manager. The counter is reset to 0 on + daemon-reexec and on the initial boot. + Virtualization contains a short ID string describing the virtualization technology the system runs in. On bare-metal hardware this is the empty string. Otherwise, it contains an identifier such as kvm, vmware and so on. For a full list of @@ -2787,6 +2882,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { readonly u RestartSteps = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly t RestartMaxDelayUSec = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly t RestartRandomizedDelayUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t RestartUSecNext = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") @@ -2831,6 +2928,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { readonly u NFileDescriptorStore = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s FileDescriptorStorePreserve = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly as LUOSession = ['...', ...]; readonly s StatusText = '...'; readonly i StatusErrno = ...; readonly s StatusBusError = '...'; @@ -2972,6 +3071,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay StartupAllowedMemoryNodes = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUSetPartition = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b IOAccounting = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t IOWeight = ...; @@ -3046,6 +3147,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s ManagedOOMPreference = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly as OOMRules = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(ss) BPFProgram = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiqq) SocketBindAllow = [...]; @@ -3060,6 +3163,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 = ...; @@ -3505,6 +3616,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -3537,6 +3650,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -3643,6 +3758,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -3715,6 +3832,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -3729,6 +3848,14 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + + + + + + @@ -4127,6 +4254,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -4173,6 +4302,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -4333,6 +4464,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -4407,6 +4540,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + @@ -4421,6 +4556,14 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + + + + + + @@ -5102,6 +5245,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly as Symlinks = ['...', ...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly a(ss) XAttrEntryPoint = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly a(ss) XAttrListen = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly a(ss) XAttrAccept = [...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly i Mark = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly u MaxConnections = ...; @@ -5232,6 +5381,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay StartupAllowedMemoryNodes = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUSetPartition = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b IOAccounting = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t IOWeight = ...; @@ -5306,6 +5457,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s ManagedOOMPreference = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly as OOMRules = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(ss) BPFProgram = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiqq) SocketBindAllow = [...]; @@ -5320,6 +5473,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 = ...; @@ -5919,6 +6080,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -5991,6 +6154,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -6005,6 +6170,14 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + + + + + + + @@ -6449,6 +6622,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + + + + + @@ -6583,6 +6762,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -6657,6 +6838,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + @@ -6671,6 +6854,14 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { + + + + + + + + @@ -7120,6 +7311,17 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { FlushPending specifies whether to flush the socket just before entering the listening state. This setting only applies to sockets with Accept= set to no. + + XAttrEntryPoint, XAttrListen and + XAttrAccept each contain a list of extended attributes (as + NAME/VALUE + pairs) to set on socket inodes. XAttrEntryPoint applies to the file-system inode an + AF_UNIX socket is bound to, XAttrListen applies to the listening + socket, and XAttrAccept applies to connection sockets accepted off the listening + socket. See the XAttrEntryPoint=, XAttrListen= and + XAttrAccept= settings in + systemd.socket5 for + details. @@ -7305,6 +7507,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay StartupAllowedMemoryNodes = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUSetPartition = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b IOAccounting = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t IOWeight = ...; @@ -7379,6 +7583,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s ManagedOOMPreference = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly as OOMRules = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(ss) BPFProgram = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiqq) SocketBindAllow = [...]; @@ -7393,6 +7599,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 = ...; @@ -7916,6 +8130,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -7988,6 +8204,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -8002,6 +8220,14 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + + + + + + @@ -8488,6 +8714,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -8562,6 +8790,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + @@ -8576,6 +8806,14 @@ node /org/freedesktop/systemd1/unit/home_2emount { + + + + + + + + @@ -9343,6 +9581,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay StartupAllowedMemoryNodes = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUSetPartition = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b IOAccounting = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t IOWeight = ...; @@ -9417,6 +9657,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s ManagedOOMPreference = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly as OOMRules = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(ss) BPFProgram = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiqq) SocketBindAllow = [...]; @@ -9431,6 +9673,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 = ...; @@ -9936,6 +10186,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -10008,6 +10260,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -10022,6 +10276,14 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + + + + + + + @@ -10490,6 +10752,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -10564,6 +10828,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + @@ -10578,6 +10844,14 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { + + + + + + + + @@ -11198,6 +11472,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay StartupAllowedMemoryNodes = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUSetPartition = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b IOAccounting = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t IOWeight = ...; @@ -11272,6 +11548,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s ManagedOOMPreference = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly as OOMRules = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(ss) BPFProgram = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiqq) SocketBindAllow = [...]; @@ -11286,6 +11564,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 = ...; @@ -11373,6 +11659,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + @@ -11445,6 +11733,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + @@ -11459,6 +11749,14 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + + + + + + + @@ -11559,6 +11857,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + @@ -11633,6 +11933,8 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + @@ -11647,6 +11949,14 @@ node /org/freedesktop/systemd1/unit/system_2eslice { + + + + + + + + @@ -11770,6 +12080,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly ay StartupAllowedMemoryNodes = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s CPUSetPartition = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b IOAccounting = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t IOWeight = ...; @@ -11844,6 +12156,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly s ManagedOOMPreference = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly as OOMRules = ['...', ...]; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(ss) BPFProgram = [...]; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly a(iiqq) SocketBindAllow = [...]; @@ -11858,6 +12172,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 = ...; @@ -11959,6 +12281,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + @@ -12031,6 +12355,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + @@ -12045,6 +12371,14 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + + + + + + + @@ -12169,6 +12503,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + @@ -12243,6 +12579,8 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + @@ -12257,6 +12595,14 @@ node /org/freedesktop/systemd1/unit/session_2d1_2escope { + + + + + + + + @@ -12469,6 +12815,16 @@ $ 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, + DefaultIOPressureWatch, + ReloadCount, + EventLoopRateLimitIntervalUSec, and + EventLoopRateLimitBurst were added in version 261. + KExecsCount, and + EnqueueUnitJobMany() were added in version 262. Unit Objects @@ -12560,6 +12916,14 @@ $ 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, + IOPressureWatch, + CPUSetPartition, and + OOMRules were added in version 261. + LUOSession was added in version 262. + RestartRandomizedDelayUSec was added in version 262. Socket Unit Objects @@ -12630,6 +12994,15 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ManagedOOMKills were added in 259. BindNetworkInterface MemoryTHP, and RootMStack were added in version 260. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, + IOPressureWatch, + CPUSetPartition, and + OOMRules were added in version 261. + XAttrEntryPoint, + XAttrListen, and + XAttrAccept were added in version 262. Mount Unit Objects @@ -12695,6 +13068,12 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ManagedOOMKills were added in 259. BindNetworkInterface MemoryTHP, and RootMStack were added in version 260. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, + IOPressureWatch, + CPUSetPartition, and + OOMRules were added in version 261. Swap Unit Objects @@ -12758,6 +13137,12 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ ManagedOOMKills were added in 259. BindNetworkInterface, MemoryTHP, and RootMStack were added in version 260. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, + IOPressureWatch, + CPUSetPartition, and + OOMRules were added in version 261. Slice Unit Objects @@ -12791,6 +13176,12 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ OOMKills, and ManagedOOMKills were added in 259. BindNetworkInterface was added in version 260. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, + IOPressureWatch, + CPUSetPartition, and + OOMRules were added in version 261. Scope Unit Objects @@ -12822,6 +13213,12 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ OOMKills, and ManagedOOMKills were added in 259. BindNetworkInterface was added in version 260. + CPUPressureThresholdUSec, + CPUPressureWatch, + IOPressureThresholdUSec, + IOPressureWatch, + CPUSetPartition, and + OOMRules were added in version 261. Job Objects diff --git a/man/org.freedesktop.sysupdate1.xml b/man/org.freedesktop.sysupdate1.xml index 6c0e86df9f29f..fea301167e84a 100644 --- a/man/org.freedesktop.sysupdate1.xml +++ b/man/org.freedesktop.sysupdate1.xml @@ -412,26 +412,35 @@ node /org/freedesktop/sysupdate1/target/host { List(), Describe(), and CheckNew() use the polkit action org.freedesktop.sysupdate1.check. - By default, this action is permitted without administrator authentication. + By default, this action is permitted without administrator authentication. Cancellation of these + methods uses the polkit action org.freedesktop.sysupdate1.cancel-check. + By default, this cancellation action is permitted without administrator authentication. Acquire() and Install() use the polkit action org.freedesktop.sysupdate1.update when no version is specified. By default, this action is permitted without administrator authentication. When a version is specified, org.freedesktop.sysupdate1.update-to-version is - used instead. By default, this alternate action requires administrator authentication. + used instead. By default, this alternate action requires administrator authentication. Cancellation of + these methods uses the polkit actions + org.freedesktop.sysupdate1.cancel-update and + org.freedesktop.sysupdate1.cancel-update-to-version. + By default, these cancellation actions are permitted without administrator authentication. Vacuum() uses the polkit action org.freedesktop.sysupdate1.vacuum. By default, this action requires - administrator authentication. + administrator authentication. Cancellation of this method uses the polkit action + org.freedesktop.sysupdate1.cancel-vacuum. + By default, this cancellation action is permitted without administrator authentication. SetFeatureEnabled() uses the polkit action org.freedesktop.sysupdate1.manage-features. By default, this action - requires administrator authentication. + requires administrator authentication. Cancellation is not supported for this method. GetAppStream(), GetVersion(), ListFeatures(), and DescribeFeature() - are unauthenticated and may be called by anybody. + are unauthenticated and may be called by anybody. Cancellation is not supported for these methods, or + is always allowed without administrator authentication. All methods called on this interface expose additional variables to the polkit rules. class contains the class of the Target being acted upon, and name 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/pam_systemd.xml b/man/pam_systemd.xml index 5482ced067745..bb464556954f0 100644 --- a/man/pam_systemd.xml +++ b/man/pam_systemd.xml @@ -238,6 +238,33 @@ + + inhibit= + + Takes a colon-separated list of inhibitor lock types to acquire for the duration of + the session. The XDG_SESSION_INHIBIT environment variable (see below) takes + precedence. Inhibitor locks prevent the system from performing certain actions, such as sleeping. + The supported lock types are the same as for the + systemd-inhibit1 + command, e.g. shutdown, sleep, idle. + If not set, no inhibitor lock is acquired. The lock is taken in block mode, the requested + actions are fully inhibited as long as the session is active. + + + + + + inhibit-why= + + Takes a short human-readable string describing the reason for the inhibitor lock + acquired via inhibit=. The XDG_SESSION_INHIBIT_WHY + environment variable (see below) takes precedence. Defaults to Active PAM session + if not set. Only has an effect if inhibit= or + XDG_SESSION_INHIBIT is set. + + + + default-capability-bounding-set= default-capability-ambient-set= @@ -414,6 +441,25 @@ + + + $XDG_SESSION_INHIBIT + + The inhibitor lock types to acquire for the duration of the session, as a + colon-separated list. This may be used instead of inhibit= on the module + parameter line. + + + + + + $XDG_SESSION_INHIBIT_WHY + + The reason string for the inhibitor lock. This may be used instead of + inhibit-why= on the module parameter line. + + + If not set, pam_systemd will initialize @@ -528,6 +574,7 @@ session required pam_unix.so logind.conf5 loginctl1 pam_systemd_home8 + systemd-inhibit1 pam.conf5 pam.d5 pam8 diff --git a/man/repart.d.xml b/man/repart.d.xml index 7d3dc4e04b254..8de04357354d5 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, @@ -652,6 +652,39 @@ + + BlockDeviceReplace= + + Takes a path to a mountpoint. Its filesystem's backing device will be replaced + with the newly created partition. This works only for btrfs filesystems. This option is ignored for already existing partitions. + + If after moving the filesystem, an error happens before the writing of the partition table is + finished, there will be an attempt to move the filesystem back to its original device. But if that + attempt also fails, the filesystem might be living on a partition that does not exist in the + partition table and will be lost on reboot. This feature is intended to save initially volatile + mounted filesystems into the new disk with no important data, like a live boot system. They might + need to be recreated after a failure writing the partition table. + + This option is not compatible with . + + + + + + VolumeName= + + If an encrypted partition is created through Encrypt=, and it will + stay activated after systemd-repart is done (using + BlockDeviceReplace=), then the name of the device mapper volume created will use + the name specified by VolumeName=. + + The value must be a valid filename. If not specified, it will default to the value of + VolumeLabel= (which could be derived from Label=) if + valid. + + + + Encrypt= @@ -679,6 +712,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 +899,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. @@ -1103,8 +1176,8 @@ Specifiers Specifiers may be used in the Label=, CopyBlocks=, - CopyFiles=, MakeDirectories=, SplitName= - settings. The following expansions are understood: + CopyFiles=, MakeDirectories=, MakeSymlinks=, + SplitName= settings. The following expansions are understood: Specifiers available diff --git a/man/resolved.conf.xml b/man/resolved.conf.xml index 9adc0143c7c05..2c5358209f07f 100644 --- a/man/resolved.conf.xml +++ b/man/resolved.conf.xml @@ -301,6 +301,32 @@ + + DNSCacheSize= + MulticastDNSCacheSize= + LLMNRCacheSize= + Takes a non-negative integer. Configures the maximum number of DNS resource record + entries that may be stored in the per-scope cache for unicast DNS, Multicast DNS (mDNS), and + Link-Local Multicast Name Resolution (LLMNR) respectively. Each defaults to 4096. The maximum + allowed value is 16777216 (2^24). Setting any of these to 0 effectively disables caching for the + respective protocol. These settings are only effective when Cache= is set to + yes or no-negative. If Cache=no, caching + is fully disabled regardless of these values. + + Note that Multicast DNS relies heavily on caching for request suppression and efficient + operation. It is recommended to keep MulticastDNSCacheSize= at a reasonably high + value even when reducing DNSCacheSize=. + + Note that systemd-resolved automatically flushes all caches on system + memory pressure, thus in most cases manual cache size configuration should not be necessary. + + Note that caching is turned off by default for host-local DNS servers. + See CacheFromLocalhost= for details. + + + + + DNSStubListener= Takes a boolean argument or one of udp and @@ -363,12 +389,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 +465,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..bac836015b8fb 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -2,11 +2,20 @@ # Update with: # ninja -C build update-man-rules manpages = [ -['binfmt.d', '5', [], 'ENABLE_BINFMT'], +['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'], + ''], + ['binfmt.d', '5', [], 'ENABLE_BINFMT'], ['bootctl', '1', [], ''], ['bootup', '7', [], ''], ['busctl', '1', [], ''], ['capsule@.service', '5', [], ''], + ['clonetab', '5', [], ''], ['coredump.conf', '5', ['coredump.conf.d'], 'ENABLE_COREDUMP'], ['coredumpctl', '1', [], 'ENABLE_COREDUMP'], ['crypttab', '5', [], 'HAVE_LIBCRYPTSETUP'], @@ -127,6 +136,7 @@ manpages = [ 'SD_WARNING'], ''], ['sd-device', '3', [], ''], + ['sd-dlopen', '3', [], ''], ['sd-event', '3', [], ''], ['sd-hwdb', '3', [], ''], ['sd-id128', @@ -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,21 @@ manpages = [ 'sd_json_dispatch_variant', 'sd_json_dispatch_variant_noref'], ''], + ['sd_json_parse', + '3', + ['SD_JSON_PARSE_DONATE_FD', + 'SD_JSON_PARSE_MUST_BE_ARRAY', + 'SD_JSON_PARSE_MUST_BE_OBJECT', + 'SD_JSON_PARSE_REOPEN_FD', + 'SD_JSON_PARSE_SEEK0', + 'SD_JSON_PARSE_SENSITIVE', + 'sd_json_parse_continue', + 'sd_json_parse_fd', + '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'], @@ -937,15 +968,29 @@ manpages = [ 'sd_uid_get_sessions', 'sd_uid_is_on_seat'], 'HAVE_PAM'], + ['sd_varlink_connect_address', + '3', + ['sd_varlink_connect_exec', + 'sd_varlink_connect_fd', + 'sd_varlink_connect_fd_pair', + 'sd_varlink_connect_url'], + ''], ['sd_varlink_is_connected', '3', ['sd_varlink_is_idle'], ''], ['sd_varlink_push_fd', '3', ['sd_varlink_push_dup_fd'], ''], ['sd_varlink_send', '3', ['sd_varlink_sendb', 'sd_varlink_sendbo'], ''], + ['sd_varlink_server_listen_address', + '3', + ['sd_varlink_server_listen_auto', + 'sd_varlink_server_listen_fd', + 'sd_varlink_server_listen_name'], + ''], ['sd_varlink_server_new', '3', [], ''], ['sd_varlink_set_description', '3', ['sd_varlink_get_description'], ''], ['sd_varlink_set_relative_timeout', '3', [], ''], ['sd_watchdog_enabled', '3', [], ''], ['shutdown', '8', [], ''], ['smbios-type-11', '7', [], ''], + ['storagectl', '1', ['mount.storage'], ''], ['sysctl.d', '5', [], ''], ['sysext.conf', '5', @@ -980,6 +1025,11 @@ manpages = [ ['systemd-cat', '1', [], ''], ['systemd-cgls', '1', [], ''], ['systemd-cgtop', '1', [], ''], + ['systemd-clonesetup', + '8', + ['systemd-clonesetup@.service'], + ''], + ['systemd-clonesetup-generator', '8', [], ''], ['systemd-coredump', '8', ['systemd-coredump.socket', 'systemd-coredump@.service'], @@ -1025,6 +1075,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', [], ''], @@ -1061,6 +1119,7 @@ manpages = [ ['systemd-loop@.service', '8', [], ''], ['systemd-machine-id-commit.service', '8', [], ''], ['systemd-machine-id-setup', '1', [], ''], + ['systemd-machine-tag@.service', '8', [], 'ENABLE_HOSTNAMED'], ['systemd-machined.service', '8', ['systemd-machined'], 'ENABLE_MACHINED'], ['systemd-makefs@.service', '8', @@ -1108,9 +1167,13 @@ manpages = [ ['systemd-pcrextend', 'systemd-pcrfs-root.service', 'systemd-pcrfs@.service', + 'systemd-pcrlogin@.service', 'systemd-pcrmachine.service', 'systemd-pcrnvdone.service', + 'systemd-pcrosseparator.service', + 'systemd-pcrphase-factory-reset.service', 'systemd-pcrphase-initrd.service', + 'systemd-pcrphase-storage-target-mode.service', 'systemd-pcrphase-sysinit.service', 'systemd-pcrproduct.service'], 'ENABLE_BOOTLOADER HAVE_OPENSSL HAVE_TPM2'], @@ -1135,6 +1198,21 @@ manpages = [ ['systemd-remount-fs.service', '8', ['systemd-remount-fs'], ''], ['systemd-repart', '8', ['systemd-repart.service'], 'ENABLE_REPART'], ['systemd-report', '1', [], ''], + ['systemd-report-files@.service', + '8', + ['systemd-report-files', + 'systemd-report-files.socket'], + ''], + ['systemd-report-sign-plain@.service', + '8', + ['systemd-report-sign-plain', + 'systemd-report-sign-plain.socket'], + 'HAVE_OPENSSL'], + ['systemd-report-sign-tsm@.service', + '8', + ['systemd-report-sign-tsm', + 'systemd-report-sign-tsm.socket'], + ''], ['systemd-resolved.service', '8', ['systemd-resolved'], 'ENABLE_RESOLVE'], ['systemd-rfkill.service', '8', @@ -1151,6 +1229,14 @@ manpages = [ ['systemd-ssh-issue', '1', [], ''], ['systemd-ssh-proxy', '1', [], ''], ['systemd-stdio-bridge', '1', [], ''], + ['systemd-storage-block@.service', + '8', + ['systemd-storage-block', 'systemd-storage-block.socket'], + ''], + ['systemd-storage-fs@.service', + '8', + ['systemd-storage-fs', 'systemd-storage-fs.socket'], + ''], ['systemd-storagetm.service', '8', ['systemd-storagetm'], 'ENABLE_STORAGETM'], ['systemd-stub', '7', @@ -1168,10 +1254,16 @@ 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-sysinstall', + '8', + ['systemd-sysinstall.service'], + 'ENABLE_SYSINSTALL'], ['systemd-system-update-generator', '8', [], ''], ['systemd-system.conf', '5', @@ -1181,6 +1273,8 @@ manpages = [ '8', ['systemd-sysupdate-reboot.service', 'systemd-sysupdate-reboot.timer', + 'systemd-sysupdate-update.service', + 'systemd-sysupdate-update.timer', 'systemd-sysupdate.service', 'systemd-sysupdate.timer'], 'ENABLE_SYSUPDATE'], @@ -1209,6 +1303,10 @@ manpages = [ '8', ['systemd-tpm2-setup', 'systemd-tpm2-setup-early.service'], 'ENABLE_BOOTLOADER'], + ['systemd-tpm2-swtpm.service', + '8', + ['systemd-tpm2-swtpm'], + 'ENABLE_BOOTLOADER HAVE_TPM2'], ['systemd-tty-ask-password-agent', '1', [], ''], ['systemd-udev-settle.service', '8', [], ''], ['systemd-udevd.service', @@ -1260,6 +1358,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/run0.xml b/man/run0.xml index 186383dbbe172..c2e891149f0eb 100644 --- a/man/run0.xml +++ b/man/run0.xml @@ -108,7 +108,9 @@ Make the new .service unit part of the specified slice, instead - of user.slice. + of user.slice. This option conflicts with and + , and implies XDG_SESSION_CLASS=none, + to keep the new process within the specified slice instead of a new session scope. @@ -120,7 +122,9 @@ Make the new .service unit part of the slice the run0 itself has been invoked in. This option may be combined with , in which case the slice specified via is placed - within the slice the run0 command is invoked in. + within the slice the run0 command is invoked in. Also conflicts with + and and implies + XDG_SESSION_CLASS=none, similar to . Example: consider run0 being invoked in the slice foo.slice, and the argument is @@ -167,6 +171,37 @@ + + + + + Revokes temporary polkit authorization for this terminal, if present. + + + + + + + + + + Revokes all temporary polkit authorizations for this user session. + + + + + + + + + + Request authorization from polkit. Can be used to create a temporary authorization + without running a command. + + + + + @@ -326,6 +361,15 @@ + + + + + Equivalent to . + + + + 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-journal.xml b/man/sd-journal.xml index da4e4e1f82340..bcde6779b51f4 100644 --- a/man/sd-journal.xml +++ b/man/sd-journal.xml @@ -94,7 +94,7 @@ sd_journal objects might cause optional shared libraries to be dynamically loaded via dlopen3, - such as decompression libraries (xz, lz4, zstd) or cryptographic libraries (gcrypt). + such as decompression libraries (xz, lz4, zstd). diff --git a/man/sd-varlink.xml b/man/sd-varlink.xml index 576bdf1b7f8d1..bd2b35565234d 100644 --- a/man/sd-varlink.xml +++ b/man/sd-varlink.xml @@ -55,6 +55,84 @@ makes the functionality implemented by sd-varlink available from the command line. + + Recognizing Varlink Socket Inodes + + On kernels that support extended attributes on socket inodes (available since Linux 7.0), + sd-varlink automatically tags the AF_UNIX socket inodes it creates and manages with + the user.varlink extended attribute. The attribute records the role the socket plays + in the Varlink communication, allowing other processes to discover and classify Varlink sockets, for + example via varlinkctl list-sockets (see + varlinkctl1). + + Four distinct roles are defined: + +
+ <varname>user.varlink</varname> extended attribute roles + + + + + + + Attribute value + Meaning + + + + + client + Set on the connecting end of a connection, i.e. a socket created via socket() + connect(). Only for inodes on the anonymous socket file system (sockfs). + + + server + Set on the serving end of a connection, i.e. a socket obtained via accept() on a listening socket. Only for inodes on the anonymous socket file system (sockfs). + + + listen + Set on a listening socket, i.e. a socket created via socket() + listen(). Only for inodes on the anonymous socket file system (sockfs). + + + entrypoint + Set on the entrypoint socket inode, i.e. the socket node bound into the regular file system that clients connect to. Unlike the other three, this attribute lives on a real file-system inode (not on sockfs) and is hence visible to any process that can resolve the socket path. + + + +
+ + Note that for socket activated Varlink services it is recommended to set these extended attributes + via XAttrEntryPoint=, XAttrListen= and + XAttrAccept= settings on the socket units, see + systemd.socket5 for + details. Specifically, for socket units for which Accept=no is set: + + … +[Socket] +SocketMode=0644 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +… + + For socket units which have Accept=yes: + + … +[Socket] +Accept=yes +SocketMode=0644 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server +… + + Note that for Varlink sockets that shall not be world-accessible it is recommended to unset the + relevant "w" bits in the socket access mode, but to keep the relevant "r" bits set. As the access check + done by connect() looks for the "w" bits, this does not make the service + unnecessarily accessible, however it has the benefit that the extended attributes that identify the + socket as Varlink-related may still be read by other users (as that's restricted by the "r" bit). Or in + other words, it's recommended to set SocketMode=0644 for services that shall not be + accessible to arbitrary users, rather than SocketMode=0600. + + @@ -87,6 +165,7 @@ sd-event3 sd-json3 varlinkctl1 + systemd.socket5 sd-bus3 pkg-config1 diff --git a/man/sd_bus_set_address.xml b/man/sd_bus_set_address.xml index 603f153221c80..0057cc6afe8b3 100644 --- a/man/sd_bus_set_address.xml +++ b/man/sd_bus_set_address.xml @@ -74,6 +74,11 @@ One or both of the host= and port= keys must be present, while the rest is optional. family may be either or . + + Note: connections over TCP are made without encryption. Thus, this mode + should only be used in specific situations where integrity and confidentiality of the connection is + not necessary or is ensured through some other means. For local connections, unix: + connections should be used instead. 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_id128_to_string.xml b/man/sd_id128_to_string.xml index 1d6301ec61581..f5b6c9490ff2b 100644 --- a/man/sd_id128_to_string.xml +++ b/man/sd_id128_to_string.xml @@ -72,7 +72,7 @@ sd_id128_to_uuid_string() and SD_ID128_TO_UUID_STRING() are similar to these two functions/macros, but format the 128-bit values as RFC4122 UUIDs, i.e. a series - of 36 lowercase hexadeciaml digits and dashes, terminated by a NUL byte. + of 36 lowercase hexadecimal digits and dashes, terminated by a NUL byte. sd_id128_from_string() implements the reverse operation: it takes a 33 character string with 32 hexadecimal digits (either lowercase or uppercase, terminated by 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..73089853a22b0 --- /dev/null +++ b/man/sd_json_parse.xml @@ -0,0 +1,315 @@ + + + + + + + + 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_fd + SD_JSON_PARSE_SENSITIVE + SD_JSON_PARSE_MUST_BE_OBJECT + SD_JSON_PARSE_MUST_BE_ARRAY + SD_JSON_PARSE_SEEK0 + SD_JSON_PARSE_DONATE_FD + SD_JSON_PARSE_REOPEN_FD + + 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 + + + + int sd_json_parse_fd + const char *path + int fd + 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(). + + sd_json_parse_fd() reads and parses a JSON value from the file referenced by + the file descriptor fd. By default the file descriptor is internally duplicated + and the caller's descriptor is left untouched (the current file offset will be shared with the original + file descriptor however); the JSON text is read starting at the descriptor's current file offset. This + behaviour may be modified via the SD_JSON_PARSE_SEEK0, + SD_JSON_PARSE_DONATE_FD and SD_JSON_PARSE_REOPEN_FD flags, see + below. The path argument is not used to open anything in this case; it is only + recorded as source information in the resulting JSON variant (see above) and may be passed as + NULL. + + 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. + + + + + + SD_JSON_PARSE_SEEK0 + + Before reading, seek the input to its beginning (i.e. file offset 0). This flag has + no effect when parsing from a string. When used together with + SD_JSON_PARSE_REOPEN_FD in sd_json_parse_fd() it is + redundant, since a freshly reopened file descriptor starts at offset 0 anyway. + + + + + + SD_JSON_PARSE_DONATE_FD + + Only has an effect on sd_json_parse_fd(). If set, ownership of + the file descriptor passed in fd is transferred into the call: the descriptor + is consumed and closed before the function returns, including in the error path. If not set (the + default), the caller retains ownership of fd and the function operates on an + internally duplicated descriptor instead. This flag may not be combined with + SD_JSON_PARSE_REOPEN_FD. + + + + + + SD_JSON_PARSE_REOPEN_FD + + Only has an effect on sd_json_parse_fd(). If set, the file + descriptor passed in fd is reopened (read-only) before reading, instead of + being duplicated. This is primarily useful to obtain an independent file offset (positioned at the + beginning of the file) and a clean, read-only access mode, even if the original descriptor was opened + differently (for example with O_PATH). The caller retains ownership of the + original descriptor, which is left untouched. This flag may not be combined with + SD_JSON_PARSE_DONATE_FD. + + + + + + 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. + + sd_json_parse_fd() and the SD_JSON_PARSE_MUST_BE_OBJECT, + SD_JSON_PARSE_MUST_BE_ARRAY, SD_JSON_PARSE_SEEK0, + SD_JSON_PARSE_DONATE_FD and SD_JSON_PARSE_REOPEN_FD flags were + added in version 261. + + + + See Also + + + systemd1 + sd-json3 + sd_json_variant_unref3 + sd_json_variant_get_source3 + sd_json_dispatch3 + + + diff --git a/man/sd_notify.xml b/man/sd_notify.xml index f9bb56b2d4730..52685c404bb9b 100644 --- a/man/sd_notify.xml +++ b/man/sd_notify.xml @@ -428,6 +428,17 @@ For further information on the file descriptor store see the File Descriptor Store overview. + On kernels that support the Live Update Orchestrator, + compatible file descriptors stored this way (such as memfd_create2) + are additionally preserved across kexec-based reboots and handed back to the + service on the other side, provided FileDescriptorStorePreserve=yes is set on + the service (see + systemd.service5). + See the File Descriptor Store + overview for details. + 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/sd_varlink_connect_address.xml b/man/sd_varlink_connect_address.xml new file mode 100644 index 0000000000000..d77a0ae4b7c92 --- /dev/null +++ b/man/sd_varlink_connect_address.xml @@ -0,0 +1,335 @@ + + + + + + + + sd_varlink_connect_address + systemd + + + + sd_varlink_connect_address + 3 + + + + sd_varlink_connect_address + sd_varlink_connect_exec + sd_varlink_connect_url + sd_varlink_connect_fd + sd_varlink_connect_fd_pair + + Create a Varlink connection object and connect it to a service + + + + + #include <systemd/sd-varlink.h> + + struct ucred; /* defined in <sys/socket.h> */ + + + int sd_varlink_connect_address + sd_varlink **ret + const char *address + + + + int sd_varlink_connect_exec + sd_varlink **ret + const char *command + char **argv + + + + int sd_varlink_connect_url + sd_varlink **ret + const char *url + + + + int sd_varlink_connect_fd + sd_varlink **ret + int fd + + + + int sd_varlink_connect_fd_pair + sd_varlink **ret + int input_fd + int output_fd + const struct ucred *override_ucred + + + + + + Description + + These five functions allocate a new client-side Varlink connection object and connect it to a + Varlink service. They differ only in how the service to talk to is specified. On success, a reference to + the new connection object is returned in ret; the caller owns this reference and + must eventually release it with + sd_varlink_unref3, + sd_varlink_close_unref3 + or + sd_varlink_flush_close_unref3. + + All five functions return immediately; none of them blocks waiting for the connection to be fully + established. For socket-based connections the actual connect() may still be in + progress when the function returns (see below); the connection object handles the completion + transparently. + + The returned connection object is not attached to any event loop. There are three ways to drive it: + attach it to an + sd-event3 loop with + sd_varlink_attach_event3; + run its I/O manually via + sd_varlink_process3 and + sd_varlink_wait3; or + simply issue blocking method calls. In particular it is perfectly fine to follow any of these connection + functions directly with a synchronous, blocking call such as + sd_varlink_call3, which + internally drives the connection (including completing a still-pending + connect()) and returns once the reply has been received. In that case no explicit + event loop integration or manual processing is required. + + sd_varlink_connect_address() connects to an AF_UNIX + stream socket whose address is given as a string in address. The address must + begin either with / (to reference a socket in the file system) or with + @ (to reference a socket in the abstract namespace, with the remainder of the string + used as the abstract name). It must be at least two characters long. No other forms are accepted (in + particular, relative paths are refused). Abstract namespace names that embed NUL + bytes cannot be expressed through this interface. If a file system socket path is too long to fit into a + sockaddr_un structure, the connection is established transparently via an + O_PATH indirection, so overlong paths work. + + sd_varlink_connect_exec() forks off a child process and speaks the Varlink + protocol with it over a connected AF_UNIX SOCK_STREAM socket + pair. command is the program to execute; it is looked up in $PATH + in the usual way (i.e. via execvp3). + argv is the argument vector (a NULL-terminated string array) + to pass to the child as its argv[]; if it is NULL or empty, an + argument vector consisting of just command is synthesized. The connected socket is + handed to the child as file descriptor 3 using the + sd_listen_fds3 + protocol, i.e. the child is invoked with $LISTEN_FDS set to + 1, $LISTEN_FDNAMES set to varlink, and the + appropriate $LISTEN_PID (and, where available, $LISTEN_PIDFDID) + variables. The command and argv strings are copied into the + connection object, so the caller may free or modify them once the function returns. + + sd_varlink_connect_url() is a higher-level interface that parses a service + address string with a leading scheme and dispatches to the appropriate transport. Despite the name, these + strings are not Internet URLs in the sense of the relevant RFCs. The following schemes + are understood natively: + + + + unix:PATH + Connects to an AF_UNIX socket, equivalent to passing + PATH to sd_varlink_connect_address(). + PATH must either be an absolute, normalized file system path, or begin with + @ for an abstract namespace socket (for which no path normalization checks are + applied). + + + + exec:PATH + Forks off the executable at PATH, equivalent to passing it + to sd_varlink_connect_exec() with no extra arguments. PATH + must be an absolute, normalized path. + + + + ssh:HOST:PATH + ssh-unix:HOST:PATH + Connects to an AF_UNIX socket at the absolute, normalized path + PATH on the remote SSH host HOST. This relies on + OpenSSH 9.4 or newer on the server side. Abstract namespace sockets are not supported for this + transport. ssh: and ssh-unix: are synonyms. + + + + ssh-exec:HOST:COMMAND + Invokes COMMAND (a command line, split on whitespace with + shell-style quoting and unescaping) on the remote SSH host HOST and speaks + the Varlink protocol over its standard input and output. + + + + If the scheme is none of the above but is otherwise a syntactically valid URL scheme, + sd_varlink_connect_url() looks for a bridge helper binary of that + name in the directory configured via $SYSTEMD_VARLINK_BRIDGES_DIR (see below). If found + and executable, it is invoked like an exec: transport, with the complete, unmodified URL + passed as its sole command line argument. This allows additional transports to be plugged in out of tree. + + For the natively supported schemes, URL parameterization using ;, + ? or # is rejected (these are reserved for possible future use). A + vsock: scheme is not currently supported. + + sd_varlink_connect_fd() turns an already existing, already connected file + descriptor fd into a Varlink connection. The descriptor is used for both reading and + writing. It may refer to a connected stream socket, but also to a pipe or other bidirectional file + descriptor. + + sd_varlink_connect_fd_pair() is like + sd_varlink_connect_fd(), but accepts a separate input_fd (used + for reading from the peer) and output_fd (used for writing to the peer). This is + useful when the two directions are backed by different descriptors, for example a pair of pipes, or the + standard output and standard input of a co-process. The two descriptors may also be identical, which is + exactly what sd_varlink_connect_fd() does internally. If + override_ucred is non-NULL, the peer credentials reported for + the connection (as returned e.g. by + sd_varlink_get_peer_uid3) + are taken from the supplied ucred structure instead of being queried from the + socket via SO_PEERCRED. This is primarily useful when the descriptors are not sockets + (and hence carry no kernel-supplied peer credentials), or when the credentials need to be overridden for + other reasons. If override_ucred is NULL, peer credentials are + determined from the socket as usual. + + sd_varlink_connect_fd() and sd_varlink_connect_fd_pair() take + over ownership of the descriptors passed to them: the descriptors are closed automatically when the + connection object is freed, and the caller must not close them itself. On failure ownership remains with + the caller. + + The connection objects created by sd_varlink_connect_exec(), and by the + exec:, ssh:/ssh-unix:, + ssh-exec: and bridge-helper paths of sd_varlink_connect_url(), + are bound to the lifetime of the spawned child process. When such a connection object is freed, the + associated child process is sent SIGTERM and reaped. The child is also configured to + receive SIGTERM if the calling process dies. + + + + Return Value + + On success, these functions return a non-negative integer and store a pointer to the new connection + object in ret. On failure, they return a negative errno-style error code and leave + ret unchanged. + + + Errors + + Returned errors may indicate the following problems: + + + + -EINVAL + + A required argument is NULL, or the supplied address, command, + or URL is malformed. This includes addresses that do not begin with / or + @, abstract namespace names that do not fit into a + sockaddr_un structure, and — for + sd_varlink_connect_url() — file system paths that are not absolute or not + normalized. + + + + -EBADF + + For sd_varlink_connect_fd() and + sd_varlink_connect_fd_pair(): a supplied file descriptor is + negative. + + + + -EPROTONOSUPPORT + + For sd_varlink_connect_url(): the URL contains no + : separator, uses an unsupported scheme for which no bridge helper binary is + available, or makes use of the reserved ;, ? or + # URL parameterization characters with a natively supported + scheme. + + + + -ENOMEM + + Memory allocation failed. + + + + In addition, these functions may propagate any error returned by the underlying system calls they + use, such as socket2, + connect2, + fork2, + pipe2 + and the various execution helpers. + + + + + + + Environment + + + + $SYSTEMD_SSH + + Used by the ssh:/ssh-unix: and + ssh-exec: transports of sd_varlink_connect_url() to override + the ssh binary to invoke. May be a plain file name (looked up in + $PATH) or an absolute path. + + + + + + $SYSTEMD_VARLINK_BRIDGES_DIR + + Overrides the directory in which + sd_varlink_connect_url() looks up bridge helper binaries for non-native URL + schemes. Defaults to /usr/lib/systemd/varlink-bridges/. + + + + + + + + Files + + + + /usr/lib/systemd/varlink-bridges/ + + Default directory searched for bridge helper binaries when + sd_varlink_connect_url() encounters a URL with a scheme that is not natively + supported. See + sd-varlink3 for + details. + + + + + + History + sd_varlink_connect_address(), + sd_varlink_connect_exec(), sd_varlink_connect_url(), + sd_varlink_connect_fd() and sd_varlink_connect_fd_pair() were + added in version 257. + + + + See Also + + + systemd1 + sd-varlink3 + sd_varlink_attach_event3 + sd_varlink_call3 + sd_varlink_send3 + sd_varlink_is_connected3 + sd_listen_fds3 + varlinkctl1 + + + + diff --git a/man/sd_varlink_server_listen_address.xml b/man/sd_varlink_server_listen_address.xml new file mode 100644 index 0000000000000..55858c1c99666 --- /dev/null +++ b/man/sd_varlink_server_listen_address.xml @@ -0,0 +1,181 @@ + + + + + + + + sd_varlink_server_listen_address + systemd + + + + sd_varlink_server_listen_address + 3 + + + + sd_varlink_server_listen_address + sd_varlink_server_listen_fd + sd_varlink_server_listen_name + sd_varlink_server_listen_auto + + Bind a Varlink server object to listening sockets + + + + + #include <systemd/sd-varlink.h> + + #define SD_VARLINK_SERVER_MODE_MKDIR_0755 … + + + int sd_varlink_server_listen_address + sd_varlink_server* server + const char* address + mode_t mode + + + + int sd_varlink_server_listen_fd + sd_varlink_server* server + int fd + + + + int sd_varlink_server_listen_name + sd_varlink_server* server + const char* name + + + + int sd_varlink_server_listen_auto + sd_varlink_server* server + + + + + + + Description + + These functions add listening sockets to a Varlink server object, as previously allocated with + sd_varlink_server_new3. + Once the server object is attached to an event loop (see + sd_varlink_server_attach_event()) it will accept incoming connections on the + configured sockets and dispatch the registered method calls. + + sd_varlink_server_listen_address() allocates a new + AF_UNIX socket, binds it to the specified address, and starts + listening on it. The address is a file system path of the socket to bind to. (Use + sd_varlink_server_listen_fd() below for sockets in the abstract namespace, in foreign + address families, or that are otherwise already allocated.) + + The mode parameter specifies the file system access mode to apply to the + socket inode, i.e. a bit mask of permission bits as in + chmod2. + Only the lower nine bits (0777) are permitted. The special value + (mode_t) -1 may be specified to pick a suitable default: this is + 0666 in the general case, but 0644 if the server was allocated + with the SD_VARLINK_SERVER_ROOT_ONLY or + SD_VARLINK_SERVER_MYSELF_ONLY flags (the reason being that it is the + w bit that controls + connect2 + access, while leaving the r bit on permits other users to read the socket's extended + attributes, which makes the socket recognizable as a Varlink entrypoint, see + sd-varlink3). + + The constant SD_VARLINK_SERVER_MODE_MKDIR_0755 may be OR-ed into the + mode parameter (this is not supported if the mode is specified as + (mode_t) -1). If specified, and address is an absolute path, + the leading directories of the socket path are automatically created (with access mode + 0755) if they are missing. + + sd_varlink_server_listen_fd() adds an already allocated and listening socket to + the server object, referenced by the file descriptor fd. This is typically used in + conjunction with socket activation, to make use of a listening socket passed in from the service manager, + see + sd_listen_fds3. The + file descriptor is automatically switched to non-blocking mode and the close-on-exec flag is set on + it. + + sd_varlink_server_listen_name() picks up sockets passed in via the socket + activation protocol whose name (as set via $LISTEN_FDNAMES, i.e. via the + FileDescriptorName= setting of the + systemd.socket5 unit) + matches the specified name. For each matching file descriptor that refers to a + listening socket sd_varlink_server_listen_fd() is invoked; file descriptors that + refer to an already accepted connection are added as connections via + sd_varlink_server_add_connection() instead. + + sd_varlink_server_listen_auto() is a convenience wrapper that automatically + determines the sockets to listen on. It first picks up all socket activation file descriptors named + varlink (by calling sd_varlink_server_listen_name() with that + name). In addition, if the $SYSTEMD_VARLINK_LISTEN environment variable is set, it is + used as an additional address to listen on: if set to - the Varlink protocol is spoken + on standard input and output (see sd_varlink_server_add_connection_stdio()), + otherwise the value is passed to sd_varlink_server_listen_address(). This is the + recommended way for a Varlink service to set up its listening sockets, as it transparently supports both + socket activation and direct invocation (for testing and debugging). + + + + Return Value + + On success, sd_varlink_server_listen_address() and + sd_varlink_server_listen_fd() return a non-negative integer. + sd_varlink_server_listen_name() and + sd_varlink_server_listen_auto() return the number of sockets and file descriptors + they added to the server object (which may be zero). On failure, these calls return a negative errno-style + error code. + + + Errors + + Returned errors may indicate the following problems: + + + + -EINVAL + + An argument is invalid. This is also returned by + sd_varlink_server_listen_address() if the mode parameter + contains bits other than the permission bits and + SD_VARLINK_SERVER_MODE_MKDIR_0755. + + + + -EBADF + + The file descriptor passed to sd_varlink_server_listen_fd() is + negative. + + + + + + + + + History + sd_varlink_server_listen_address(), + sd_varlink_server_listen_fd(), and + sd_varlink_server_listen_auto() were added in version 257. + sd_varlink_server_listen_name() was added in version 258. + + + + See Also + + + systemd1 + sd-varlink3 + sd_varlink_server_new3 + sd_listen_fds3 + systemd.socket5 + varlinkctl1 + + + + 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/standard-specifiers.xml b/man/standard-specifiers.xml index 3a2c0b0266a68..f4150531f2d7c 100644 --- a/man/standard-specifiers.xml +++ b/man/standard-specifiers.xml @@ -27,6 +27,11 @@ Operating system build ID The operating system build identifier of the running system, as read from the BUILD_ID= field of /etc/os-release. If not set, resolves to an empty string. See os-release5 for more information. + + %D + Shared data directory + This is either /usr/share/ (for the system manager) or the path $XDG_DATA_HOME resolves to (for user managers). + %H Host name diff --git a/man/storagectl.xml b/man/storagectl.xml new file mode 100644 index 0000000000000..5fddf3ca08db5 --- /dev/null +++ b/man/storagectl.xml @@ -0,0 +1,281 @@ + + + + + + + + storagectl + systemd + + + + storagectl + 1 + + + + storagectl + mount.storage + Enumerate and mount storage volumes provided by storage providers + + + + + storagectl + OPTIONS + COMMAND + NAME + + + + mount + -t + storage + PROVIDER:VOLUME + DIRECTORY + + + + mount + -t + storage.FSTYPE + PROVIDER:VOLUME + DIRECTORY + + + + + Description + + storagectl may be used to inspect storage providers and the storage + volumes they expose. A storage provider is a service implementing the + io.systemd.StorageProvider Varlink + interface, registered as an AF_UNIX socket below the well-known socket directory + /run/systemd/io.systemd.StorageProvider/ (in system mode) or + $XDG_RUNTIME_DIR/systemd/io.systemd.StorageProvider/ (in user mode). The two + storage providers shipped with systemd are + systemd-storage-block@.service8, + which exposes the system's block devices, and + systemd-storage-fs@.service8, + which exposes regular files and directories from a backing file system. + + The tool also provides a mount8 helper + for the file system type storage, which permits mounting storage volumes to arbitrary + places. See "Use as a mount helper" below for details. + + + + Commands + + The following commands are understood: + + + + + volumes GLOB + + List storage volumes provided by all storage providers running on the + system (or, with , in the user runtime). The optional + GLOB argument is a shell-style pattern (see + fnmatch3) + that filters the result by volume name. The output is a table containing the providing + service, the volume name, its type (blk, reg or + dir), whether it is read-only, and — if known — its size and the number + of bytes used. + + This is the default command if none is specified. + + + + + + templates GLOB + + List volume templates supported by the running storage providers. Templates + encapsulate a configuration to use when creating volumes on-the-fly, when they are acquired. Template + support is an optional feature for providers, and only applies to providers that allow creation + of volumes on-the-fly. See the respective provider documentation for details, for example + systemd-storage-fs@.service8. The + optional GLOB argument filters by template name. Storage providers that do + not implement template-based volume creation (such as the block-device provider) do not contribute to + this output. + + + + + + providers + + List the storage providers known to the system. This is determined by scanning the + well-known socket directory for AF_UNIX sockets that look like + io.systemd.StorageProvider endpoints. For each provider it is also reported + whether the socket can currently be connected to. + + + + + + + + Options + + The following options are understood: + + + + + + Operate on system-wide storage providers. Sockets are looked for in + /run/systemd/io.systemd.StorageProvider/. This is the default. + + + + + + + + Operate on per-user storage providers. Sockets are looked for in + $XDG_RUNTIME_DIR/systemd/io.systemd.StorageProvider/. + + + + + + + + + + + + + + + Use as a mount helper + + The tool provides the /sbin/mount.storage alias, implementing the + mount8 + "external helper" interface, allowing storage volumes to be mounted with the regular + mount command. The volume to mount is encoded as the source of the mount, + in the form + PROVIDER:VOLUME, where + PROVIDER is the name of a storage provider (as listed by + storagectl providers) and VOLUME is the volume + name. Two file system type spellings are recognized: + + + + storage + + Acquires a directory volume and bind-mounts its directory tree onto the + target. + + + + + + storage.FSTYPE + + Acquires a regular file or block device volume and mounts it as a file system of type + FSTYPE (for example storage.ext4, + storage.btrfs, …). + + + + + + The standard mount options are forwarded to + mount. In addition, the following storage.-prefixed + options are interpreted by mount.storage itself and stripped from the + forwarded list: + + + + MODE + + Takes one of any (open if it exists, otherwise create — the + default), open (fail if the volume does not yet exist) or new + (fail if the volume already exists). + + + + + + NAME + + The template to use when creating a new volume, if it is missing and the provider + supports on-the-fly creation of volumes. + + + + + + BYTES + + When creating a new volume on-the-fly, the size in bytes to allocate. Accepts the + usual K/M/G/T suffixes + (base 1024). Required when creating a regular file volume. + + + + + + + + + Examples + + + Enumerate available storage providers, volumes and templates + + $ storagectl providers +$ storagectl volumes +$ storagectl volumes '*foo*' +$ storagectl templates + + + + Mount a directory volume from the file system provider + + # mount -t storage fs:myvol /mnt/myvol + + If the volume myvol does not yet exist, it will be created using + the default subvolume template. + + + + Create and mount an ext4 file system from a regular file. + + # mount -t storage.ext4 fs:scratch /mnt/scratch -o loop + + + + Mount a block device volume read-only + + # mount -t storage.ext4 -o ro block:/dev/disk/by-id/usb-foo /mnt/foo + + + + + Exit status + + On success, 0 is returned, a non-zero failure code otherwise. + + + + + + See Also + + systemd1 + systemd-storage-block@.service8 + systemd-storage-fs@.service8 + varlinkctl1 + mount8 + + + + diff --git a/man/systemctl.xml b/man/systemctl.xml index c514c849265bd..8b64b179ea571 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -418,6 +418,13 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err Start (activate) one or more units specified on the command line. + When more than one unit is specified, the units are enqueued in a single job transaction. + This ensures that ordering dependencies (After=/Before=) + configured between the listed units are honored, regardless of the order in which they are given + on the command line. The same applies to stop, restart and + the reload/restart variants below. If a single transaction cannot be put together, separate + transactions will be submitted separately instead as a fallback. + Note that unit glob patterns expand to names of units currently in memory. Units which are not active and are not in a failed state usually are not in memory, and will not be matched by any pattern. In addition, in case of instantiated units, systemd is often unaware of the instance @@ -501,7 +508,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 +528,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. @@ -1988,7 +1995,8 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err The argument is a comma-separated list of unit types such as and . When units are listed with list-units, list-dependencies, show, or status, - only units of the specified types will be shown. By default, units of all types are shown. + only units of the specified types will be shown. By default, units of all types are shown. + Use to reset the filter. As a special case, if one of the arguments is , a list of allowed values will be printed and the program will exit. @@ -2000,9 +2008,10 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err The argument is a comma-separated list of unit LOAD, SUB, or ACTIVE states. When listing - units with list-units, list-dependencies, show - or status, show only those in the specified states. Use - or to show only failed units. + units with list-units, list-dependencies, + show or status, show only those in the specified states. Use + or to show only failed units. Use + to reset the filter. As a special case, if one of the arguments is , a list of allowed values will be printed and the program will exit. @@ -2576,10 +2585,12 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err all as a shortcut for specifying all six resource types. If this option is not specified defaults to the combination of cache, runtime and fdstore, i.e. the three kinds of resources that are generally considered - to be redundant and can be reconstructed on next invocation. Note that the explicit removal of the - fdstore resource type is only useful if the - FileDescriptorStorePreserve= option is enabled, since the file descriptor store - is otherwise cleaned automatically when the unit is stopped. + to be redundant and can be reconstructed on next invocation. The empty argument can be used to + reset to this default. + + Note that the explicit removal of the fdstore resource type is only + useful if the FileDescriptorStorePreserve= option is enabled, since the file + descriptor store is otherwise cleaned automatically when the unit is stopped. @@ -2784,6 +2795,50 @@ 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, separated from the existing + options by a single space. This option may be specified more than once, in which case the + strings are appended in the order given. systemctl kexec will fail if this + option is specified when a kexec kernel is already loaded. + + The kexec kernel command line is assembled in a fixed order, independent of the order in + which the options are specified on the command line: first the command line from the boot loader + entry, then the command line of the currently running kernel (if + is used), and finally the strings specified with + . Afterwards, arguments that are exactly identical and appear + more than once are removed, keeping the last occurrence. Note that only byte-for-byte identical + arguments are deduplicated: for example foo=bar and + foo=quux are both kept, as they are not identical (the latter takes effect, as + the kernel uses the last assignment of a given parameter). + + + + + + + + + + Like , but instead of taking the string to append as an + argument, the command line of the currently running kernel is reused. This is useful to boot the + kexec kernel with the same command line options the running system was booted with, including any + modifications applied at boot time. + As with , the reused command line is added to the command line + taken from the boot loader entry, and systemctl kexec will fail if this option + is specified when a kexec kernel is already loaded. This option may be combined with + ; see above for the order in + which the various command line sources are combined and deduplicated. + + + + + diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index e64f9be57ee7f..4f3057f725d24 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -81,7 +81,7 @@ OPTIONS unit-shell SERVICE - Command + COMMAND systemd-analyze @@ -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-boot.xml b/man/systemd-boot.xml index dab10ed8ef12a..2162c7ffb2bba 100644 --- a/man/systemd-boot.xml +++ b/man/systemd-boot.xml @@ -406,6 +406,66 @@ loader.conf5. + + Companion Files + + For Type #1 boot loader entries (as defined in the UAPI.1 Boot Loader + Specification) systemd-boot will collect additional companion resources + declared via the extra key in the entry, dynamically generate + cpio initrd archives from them, and register those archives via the Linux initrd EFI + protocol so that they are passed to the kernel together with the entry's own initrd. This is supported + for entries referencing a Unified Kernel Image (UKI) via the uki or + uki-url keys. Each extra key references a single regular file + (relative to the root of the file system containing the entry snippet) and the key may be specified + multiple times. Companion resources are recognized by file name suffix: + + + Files with the .cred suffix are packed into a + cpio archive placed in the /.extra/credentials/ directory of + the initrd file hierarchy. This is intended to convey auxiliary, encrypted, authenticated credentials + for use with LoadCredentialEncrypted=. See + systemd.exec5 and + systemd-creds1 for + details on encrypted credentials. The generated cpio archive is measured into TPM + PCR 12 (if a TPM is present). + + Files with the .sysext.raw suffix are packed into a + cpio archive placed in the /.extra/sysext/ directory of the + initrd file hierarchy. This is intended to pass additional entry-specific system extension images to + the initrd. See + systemd-sysext8 for + details on system extension images. The generated cpio archive is measured into TPM + PCR 13 (if a TPM is present). + + Files with the .confext.raw suffix are packed into a + cpio archive placed in the /.extra/confext/ directory of the + initrd file hierarchy. This is intended to pass additional entry-specific configuration extension + images to the initrd. See + systemd-confext8 + for details on configuration extension images. The generated cpio archive is + measured into TPM PCR 12 (if a TPM is present). + + + When the booted kernel is a UKI, the systemd-stub UEFI stub embedded in it will + combine the companion resources injected here with any companion files it itself collects from the UKI's + .extra.d/ drop-in directory and from /loader/credentials/ and + /loader/extensions/, so that all sources are merged uniformly into + /.extra/ in the initrd. See + systemd-stub7 for + details. + + Example Type #1 entry making use of the extra key: + + title My OS +version 1.2.3 +machine-id 6a9857a393724b7a981ebb5b8495b9ea +uki /6a9857a393724b7a981ebb5b8495b9ea/1.2.3/img.efi +extra /6a9857a393724b7a981ebb5b8495b9ea/1.2.3/foo.cred +extra /6a9857a393724b7a981ebb5b8495b9ea/1.2.3/bar.sysext.raw +extra /6a9857a393724b7a981ebb5b8495b9ea/1.2.3/baz.confext.raw + + EFI Variables @@ -562,6 +622,19 @@ + + LoaderPcrSMBIOS + + The PCR register index select SMBIOS structures are measured into — type 1 + (system information, with the volatile "Wake-up Type" field zeroed out), type 2 (baseboard + information) and type 11 (OEM strings). Formatted as decimal ASCII string (e.g. + 1). Set by the boot loader if a measurement was successfully completed, and remains + unset otherwise. (Note that systemd-stub performs the same measurement when booted + directly, bypassing the boot loader.) + + + + LoaderImageIdentifier 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-clonesetup.xml b/man/systemd-clonesetup.xml new file mode 100644 index 0000000000000..f157b8cc28ffb --- /dev/null +++ b/man/systemd-clonesetup.xml @@ -0,0 +1,75 @@ + + + + + + + + systemd-clonesetup + systemd + + + + systemd-clonesetup + 8 + + + + systemd-clonesetup + systemd-clonesetup@.service + Set up and tear down dm-clone devices + + + + + systemd-clonesetup + add + NAME + SOURCE-DEVICE + DEST-DEVICE + META-DEVICE + OPTIONS + + + + systemd-clonesetup + remove + NAME + + + systemd-clonesetup@.service + + + + Description + + systemd-clonesetup is used to set up (with add) and tear down + (with remove) dm-clone devices. It is primarily used via + systemd-clonesetup@.service units generated from + /etc/clonetab by + systemd-clonesetup-generator8. + + + The positional arguments NAME, SOURCE-DEVICE, + DEST-DEVICE, META-DEVICE, and OPTIONS + have the same meaning as the fields in + clonetab5. + + Currently, the supported option in OPTIONS is + region_size=N, where + N is measured in 512-byte sectors. + + + + See Also + + systemd1 + clonetab5 + systemd-clonesetup-generator8 + systemd.generator7 + dmsetup8 + + + + diff --git a/man/systemd-coredump.xml b/man/systemd-coredump.xml index d499600dc15d9..c27981bd89b5f 100644 --- a/man/systemd-coredump.xml +++ b/man/systemd-coredump.xml @@ -201,6 +201,7 @@ COREDUMP_UID=1000 COREDUMP_GID=1000 COREDUMP_SIGNAL_NAME=SIGSEGV COREDUMP_SIGNAL=11 +COREDUMP_CODE=2 COREDUMP_TIMESTAMP=1614342930000000 COREDUMP_COMM=Web Content COREDUMP_EXE=/usr/lib64/firefox/firefox @@ -327,6 +328,19 @@ COREDUMP_FILENAME=/var/lib/systemd/coredump/core.Web….552351.….zst + + COREDUMP_CODE= + + The reason why the signal was sent as a numerical value. The code is defined as + field si_code in the siginfo_t structure and + documented in sigaction2. + + + + + + COREDUMP_CWD= COREDUMP_ROOT= 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-cryptenroll.xml b/man/systemd-cryptenroll.xml index 707a8d25e88bd..9f090ec94b482 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -270,6 +270,16 @@ The following options are understood that may be used to unlock the device in preparation of the enrollment operations: + + + + Use an empty password/passphrase to unlock the volume, instead of reading one from + stdin. This is useful to unlock volumes that are currently protected by an empty password, in order to + enroll a different, stronger unlock mechanism. + + + + @@ -307,6 +317,18 @@ + + + + + Try the unlock mechanisms suitable for headless operation in turn, without requiring + any interactive input. If a TPM2 device is available the volume is unlocked via TPM2, and otherwise + (or if TPM2 unlocking fails) an empty password is attempted. This is primarily useful in automated + provisioning scenarios where a volume is first set up with a TPM2 enrollment or an empty password and + a stronger unlock mechanism shall be enrolled subsequently. + + + @@ -340,6 +362,65 @@ + + Interactive Enrollment + + The following options are understood that drive an interactive enrollment wizard, intended to be + run on first boot (similar in spirit to + systemd-firstboot1): + + + + + + Run an interactive wizard that lets the user enroll a passphrase, a recovery key, or a + FIDO2 security token (one menu entry per suitable token currently plugged in). If credentials of any + of these types are already enrolled, the wizard additionally offers to wipe them. Pressing enter at + the top-level menu leaves the volume unchanged. This option may not be combined with an explicit + enrollment type or with . As with the other commands, the volume to + operate on is the one backing /var/ if none is specified. Honours the + systemd.firstboot= kernel command line option. + + + + + + + + Takes a comma-separated list of slot types, out of password, + recovery, pkcs11, fido2 and + tpm2. Only useful together with : if a slot of any of + the listed types is already enrolled on the volume, the wizard does nothing and exits successfully. + This is useful to hook the wizard into the boot process while ensuring it stays quiet once suitable + credentials have been set up. + + + + + + + + Takes a boolean argument, defaults to on. Only useful together with + : if enabled the interactive wizard draws a coloured bar across the top and + bottom of the terminal to visually frame the prompt. Pass to disable this + decoration, for example when running on a terminal that does not render it well. + + + + + + + + Takes a boolean argument, defaults to off. Only useful together with + : if enabled the kernel and PID 1 are asked to refrain from writing log + messages to the console while the interactive wizard is shown, so that such output does not interfere + with the prompt. The console is unmuted again once the wizard exits. + + + + + + PKCS#11 Enrollment diff --git a/man/systemd-firstboot.xml b/man/systemd-firstboot.xml index 86a85f0bf2855..252ec73feddc3 100644 --- a/man/systemd-firstboot.xml +++ b/man/systemd-firstboot.xml @@ -67,6 +67,8 @@ The system hostname + The machine tags + The kernel command line used when installing kernel images The root user's password and shell @@ -198,6 +200,27 @@ + + + + Set the machine tags to the specified colon-separated list. Machine tags are short + labels that may be used to classify and group machines for management purposes, for example to + identify the role a machine plays in a deployment, the fleet or organizational unit it belongs to, or + any other administrator-defined attribute. Each individual tag must be 1…255 characters long and + consist only of ASCII alphanumeric characters, - and .. This + controls the TAGS= field of + machine-info5. The + tags may later be queried and changed with the tags command of + hostnamectl1, and + matched against with the ConditionMachineTag=/AssertMachineTag= + unit settings, see + systemd.unit5. + + Unlike most other settings, machine tags are not prompted for interactively. + + + + @@ -447,6 +470,29 @@ + + + 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. + + + + + + firstboot.machine-tags + + This credential specifies the machine tags to set during first boot, as a + colon-separated list, equivalent to the switch described above. The + tags are written into the TAGS= field of /etc/machine-info + (if that file is not already present), and only have an effect on first boot. If the list contains + invalid tags it is ignored in its entirety. + + + 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..606dd33068ddb 100644 --- a/man/systemd-fstab-generator.xml +++ b/man/systemd-fstab-generator.xml @@ -105,7 +105,12 @@ Use bind:… to bind mount another directory as operating system root filesystems (added in v258). Expects an absolute path name referencing an existing directory within the initrd's file - hierarchy to boot into. + hierarchy to boot into. Since the resulting root file system is supposed to behave like a regular OS + root, the bind mount is established with the , and + options (i.e. the , and + flags that would otherwise be inherited from the file system the source + directory resides on, such as /run/, are cleared), unless overridden via + rootflags=. Set to off to turn off mounting of a root file system. @@ -214,12 +219,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 +233,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..b8b314ccbd666 --- /dev/null +++ b/man/systemd-imds.xml @@ -0,0 +1,186 @@ + + + + + + + + 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. + + + In addition, when invoked as a Varlink service (i.e. via socket activation through + systemd-imds-metrics.socket), systemd-imds acts as an + io.systemd.Metrics provider for + systemd-report1. It + exposes the detected cloud vendor and the well-known hostname, + region, zone, ipv4-public and + ipv6-public fields as metrics in the io.systemd.InstanceMetadata. + namespace. The data is acquired on demand from + systemd-imdsd@.service8, + benefiting from its local cache. + + + + 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-report1 + 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-machine-tag@.service.xml b/man/systemd-machine-tag@.service.xml new file mode 100644 index 0000000000000..b5bc1d81c77ab --- /dev/null +++ b/man/systemd-machine-tag@.service.xml @@ -0,0 +1,65 @@ + + + + + + + + + systemd-machine-tag@.service + systemd + + + + systemd-machine-tag@.service + 8 + + + + systemd-machine-tag@.service + Add a machine tag to the host + + + + systemd-machine-tag@.service + + + + Description + + systemd-machine-tag@TAG.service is a service + template that adds the machine tag TAG (taken from the instance name) to the + host's list of machine tags. Whenever an instance of this template is pulled in, the corresponding tag is + added; the operation is idempotent, so pulling it in repeatedly has no further effect. + + The primary use of this template is automatic, hardware-driven tagging: the + SYSTEMD_MACHINE_TAG udev property, when set on a device, causes the matching instance + of this template to be pulled in (via SYSTEMD_WANTS=), so that plugging in or + detecting a specific device automatically tags the host. See + systemd.device5 for + details on the SYSTEMD_WANTS= mechanism. + + Machine tags may be inspected and edited with the tags verb of + hostnamectl1, and are + stored in the TAGS= field of + machine-info5. Each + tag must be 1…255 characters long and consist only of ASCII alphanumeric characters, + - and .. + + + + + + See Also + + systemd1 + hostnamectl1 + systemd-hostnamed.service8 + machine-info5 + udev7 + + + + 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-report-files@.service + systemd + + + + systemd-report-files@.service + 8 + + + + systemd-report-files@.service + systemd-report-files.socket + systemd-report-files + Report the contents of files as system report metrics + + + + systemd-report-files@.service + systemd-report-files.socket + /usr/lib/systemd/systemd-report-files + + + + Description + + systemd-report-files@.service is a metrics source for + systemd-report1. It + reports the contents of a configurable set of files as metrics, by implementing the + io.systemd.Metrics Varlink interface on a socket linked into the + /run/systemd/report/ directory. + + The files to report are picked up from report.files/ subdirectories of the + usual configuration directories, i.e. from + /etc/systemd/report.files/, + /run/systemd/report.files/, + /var/lib/systemd/report.files/, + /usr/local/lib/systemd/report.files/ and + /usr/lib/systemd/report.files/. Typically these directories contain symlinks pointing + to the actual files in the file system whose contents shall be reported. Entries with the same name in an + earlier directory override those in a later one. + + Each entry is reported as a metric named + io.systemd.Files.NAME, where + NAME is the name of the entry (which must be a valid metric field name). The + metric value is the verbatim contents of the file. Files that cannot be read (for example because a + symlink is dangling) are skipped, as are files whose contents are not valid UTF-8 text. + + Note that access to this service is unprivileged, but it runs privileged. This means take care when + symlinking files into the various report.files/ directories: this service might + provide read access even if the client does not have the required direct access rights itself. + + + + + + See Also + + systemd1 + systemd-report1 + + + + diff --git a/man/systemd-report-sign-plain@.service.xml b/man/systemd-report-sign-plain@.service.xml new file mode 100644 index 0000000000000..1602f616637d2 --- /dev/null +++ b/man/systemd-report-sign-plain@.service.xml @@ -0,0 +1,66 @@ + + + + + + + + systemd-report-sign-plain@.service + systemd + + + + systemd-report-sign-plain@.service + 8 + + + + systemd-report-sign-plain@.service + systemd-report-sign-plain.socket + systemd-report-sign-plain + Sign system reports with a local software key + + + + systemd-report-sign-plain@.service + systemd-report-sign-plain.socket + /usr/lib/systemd/systemd-report-sign-plain + + + + Description + + systemd-report-sign-plain@.service is a system service that signs system reports + generated by + systemd-report1. It is + a signing backend for the logic of that tool: it implements the + io.systemd.Report.Signer.Sign() Varlink method and is reached via a socket linked into + the /run/systemd/report.sign/ directory, named plain. + + The service is socket-activated (one instance per connection) via + systemd-report-sign-plain.socket, and signs the digest passed to it with a local + software key. Signatures are made with the Ed25519 (EdDSA) algorithm. + + On first use a key pair is generated automatically and stored below + /var/lib/systemd/report.sign.plain/: the private key in + local.private (PKCS#8 PEM, readable only by the root user) and the public key in + local.public (PEM, world readable). + + The public key is also exported as a system report metric. + + + + + + See Also + + systemd1 + systemd-report1 + + + + diff --git a/man/systemd-report-sign-tsm@.service.xml b/man/systemd-report-sign-tsm@.service.xml new file mode 100644 index 0000000000000..61d67b24a9aba --- /dev/null +++ b/man/systemd-report-sign-tsm@.service.xml @@ -0,0 +1,73 @@ + + + + + + + + systemd-report-sign-tsm@.service + systemd + + + + systemd-report-sign-tsm@.service + 8 + + + + systemd-report-sign-tsm@.service + systemd-report-sign-tsm.socket + systemd-report-sign-tsm + Sign system reports with a confidential-computing attestation report + + + + systemd-report-sign-tsm@.service + systemd-report-sign-tsm.socket + /usr/lib/systemd/systemd-report-sign-tsm + + + + Description + + systemd-report-sign-tsm@.service is a system service that signs system reports + generated by + systemd-report1. It is + a signing backend for the logic of that tool: it implements the + io.systemd.Report.Signer.Sign() Varlink method and is reached via a socket linked into + the /run/systemd/report.sign/ directory, named tsm. + + The service is socket-activated (one instance per connection) via + systemd-report-sign-tsm.socket. Rather than signing with a local key, it obtains a + hardware-backed attestation report from the platform's Trusted Security Module (TSM) through the kernel's + configfs-TSM interface at /sys/kernel/config/tsm/report/. The digest passed to it is + embedded into the report as its run-time provided data (inblob), cryptographically + binding the attestation report to the system report being signed. + + The returned signature carries the binary attestation report (the outblob), the + name of the TSM provider that generated it (for example sev_guest for AMD SEV-SNP or + tdx_guest for Intel TDX), and, where the provider supplies them, supplemental + certificate (auxblob) and manifest (manifestblob) data. A verifier + selects the appropriate validation logic based on the provider field. + + This backend is only functional inside a confidential virtual machine whose kernel exposes the + configfs-TSM interface (such as AMD SEV-SNP or Intel TDX guests). On systems where the interface is + unavailable the signing operation is reported as unsupported and the mechanism is skipped. The service + keeps no persistent state. + + + + + + See Also + + systemd1 + systemd-report1 + + + + diff --git a/man/systemd-report.xml b/man/systemd-report.xml index b53a50c2f8681..2c3ff710eeb87 100644 --- a/man/systemd-report.xml +++ b/man/systemd-report.xml @@ -1,6 +1,9 @@ - + + "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [ + +%entities; +]> systemd-report - Generate report of system facts and metrics + Generate report of system metrics @@ -33,7 +36,7 @@ Note: this command is experimental for now. While it is likely to become a regular component of systemd, it might still change in behaviour and interface. - systemd-report requests facts and metrics from the system and writes them to + systemd-report requests metrics from the system and writes them to standard output. @@ -56,18 +59,43 @@ - describe-metrics MATCH + describe MATCH Acquire a list of metric families from all local services providing them, and write them to standard output. This returns primarily static information about metrics, their data types and human readable description, without values. - Match expressions similar to those supported by metrics are supported for - describe-metrics, too. + Match expressions supported by metrics are supported by + describe too. + + generate MATCH + + Acquire a list of metrics and build a JSON report. + + Match expressions supported by metrics are supported here too. + + + + + + upload MATCH + + This command can be used to send the report built by generate + to an external server. Two upload mechanisms are supported. If an http:// or + https:// URL is specified with , an HTTP upload will be + performed to the specified location. Otherwise, any sockets under + /run/systemd/report.upload/ will be used to call + io.systemd.Report.Uploader.Upload(). + + Match expressions supported by metrics are supported here too. + + + + list-sources @@ -101,6 +129,87 @@ + + + + + Upload the collected report to the specified address instead of writing it to + standard output. URL must point to a server accepting POST requests with + a JSON-formatted report body. + + Note: both http:// and https:// URLs are supported, but + connections over plain HTTP are made without encryption. Thus, this mode should + only be used in specific situations where integrity and confidentiality of the report is not required + or is ensured through some other means. Using https:// is recommended. + + + + + + + + Takes a path to a SSL key file in PEM format, used for client certificate + authentication when uploading. Can also be set to -, to disable client certificate + authentication. Defaults to + &CERTIFICATE_ROOT;/private/systemd-report.pem. + + + + + + + + Takes a path to a SSL certificate file in PEM format, used for client certificate + authentication when uploading. Defaults to + &CERTIFICATE_ROOT;/certs/systemd-report.pem. + + + + + + + + Takes a path to a SSL CA certificate file in PEM format used to verify the server + certificate, or the literal string all to disable certificate checking + entirely. Defaults to &CERTIFICATE_ROOT;/ca/trusted.pem. + + + + + + + + Timeout for the network upload operation. Takes a value in seconds (or in other + time units if suffixed with ms, min, h, + etc.); see + systemd.time7 + for details. Defaults to 30 seconds. + + + + + + + + Inject an additional HTTP header into the upload request. May be specified multiple + times to add several headers. Passing an empty string clears any headers added by previous + uses. + + + + + + + + If enabled, the report generated by generate or uploaded by + upload is cryptographically signed. In this mode the report is emitted as a + JSON-SEQ stream: the report object comes first, followed by one or more signature objects that + cover the precise binary representation of the report object. Signatures are acquired by calling + io.systemd.Report.Signer.Sign() on any sockets found under + /run/systemd/report.sign/. Defaults to off. + + + diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml index a5ab48d2fa05c..1d27d2c3c59e7 100644 --- a/man/systemd-resolved.service.xml +++ b/man/systemd-resolved.service.xml @@ -131,6 +131,16 @@ The hostname _localdnsproxy is resolved to the IP address 127.0.0.54, i.e. the address the local DNS proxy (see above) is listening on. + The files matching /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 may be used to define arbitrary + records. See + systemd.rr5 for + details. Support for this may be disabled with ReadStaticRecords=no, see + resolved.conf5. + + The mappings defined in /etc/hosts are resolved to their configured addresses and back, but they will not affect lookups for non-address types (like MX). Support for /etc/hosts may be disabled with ReadEtcHosts=no, @@ -510,6 +520,7 @@ search foobar.com barbar.com systemd1 resolved.conf5 systemd.dns-delegate5 + systemd.rr5 systemd.dnssd5 dnssec-trust-anchors.d5 nss-resolve8 diff --git a/man/systemd-run.xml b/man/systemd-run.xml index d18b80faa8afc..bb90f352657ed 100644 --- a/man/systemd-run.xml +++ b/man/systemd-run.xml @@ -434,6 +434,16 @@ + + + + + Controls the formatting of verbose unit log output, see journalctl1 for possible values. + + + + + diff --git a/man/systemd-socket-proxyd.xml b/man/systemd-socket-proxyd.xml index dde6d888ada58..30f248220ef18 100644 --- a/man/systemd-socket-proxyd.xml +++ b/man/systemd-socket-proxyd.xml @@ -83,6 +83,17 @@ + + + + Uses the PROXY protocol + to communicate with the server. This allows an appropriately configured server to know the real client IP address. + Takes the version of the PROXY protocol + used, and only supports v1 for now. + Default is not to use a PROXY protocol. + + + @@ -176,6 +187,47 @@ server { Enabling the proxy + + + + PROXY protocol example with nginx + systemd-socket-proxyd and nginx using the PROXY protocol + + proxy-to-nginx.socket + + + + proxy-to-nginx.service + + + + nginx.conf + + + + + + Enabling the proxy + @@ -190,6 +242,8 @@ $ curl http://localhost:80/]]> socat1 nginx1 curl1 + PROXY protocol specification + nginx: Accepting the PROXY Protocol diff --git a/man/systemd-ssh-issue.xml b/man/systemd-ssh-issue.xml index 4e887796764e0..f71a50f755d73 100644 --- a/man/systemd-ssh-issue.xml +++ b/man/systemd-ssh-issue.xml @@ -23,15 +23,22 @@ - /usr/lib/systemd/systemd-ssh-issue - /usr/lib/systemd/systemd-ssh-issue + /usr/lib/systemd/systemd-ssh-issue + OPTIONS + make-vsock + + + /usr/lib/systemd/systemd-ssh-issue + OPTIONS + rm-vsock Description - systemd-ssh-issue is a small tool that generates a + systemd-ssh-issue is a small tool that generates (when called with + make-vsock) and removes (when called with rm-vsock) a /run/issue.d/50-ssh-vsock.issue drop-in file in case AF_VSOCK support is available in the kernel and the VM environment. The file contains brief information about how to contact the local system via SSH-over-AF_VSOCK, in particular it reports the @@ -49,21 +56,6 @@ The following options are understood: - - - Generates the issue file. This command has no effect if called on systems lacking - AF_VSOCK support. - - - - - - - Removes the issue file if it exists. - - - - Changes the path to the issue file to write to/remove. If not specified, defaults to diff --git a/man/systemd-storage-block@.service.xml b/man/systemd-storage-block@.service.xml new file mode 100644 index 0000000000000..ee6022af053bb --- /dev/null +++ b/man/systemd-storage-block@.service.xml @@ -0,0 +1,97 @@ + + + + + + + + systemd-storage-block@.service + systemd + + + + systemd-storage-block@.service + 8 + + + + systemd-storage-block@.service + systemd-storage-block.socket + systemd-storage-block + Storage provider exposing local block devices as storage volumes + + + + systemd-storage-block@.service + systemd-storage-block.socket + + + + Description + + systemd-storage-block@.service is a system service that implements the + io.systemd.StorageProvider Varlink + interface, exposing the system's block devices (such as disks, partitions, and device-mapper + nodes) as storage volumes that may be acquired by other programs as file descriptors. + + The service is socket-activated via systemd-storage-block.socket, which + listens on the AF_UNIX socket /run/systemd/io.systemd.StorageProvider/block. The + socket directory /run/systemd/io.systemd.StorageProvider/ is the well-known location + where storage providers register, see + storagectl1 for an + enumeration tool. + + See also + systemd-storage-fs@.service8 + for a complementary implementation that exposes regular files and directories from a backing file + system. + + + + Volumes + + The volumes exposed via the provider are identified by an absolute path (which must begin with + /dev/), i.e. as a kernel block device node such as /dev/sda or + /dev/disk/by-id/…. Volume names that are not normalized or that do not begin with + /dev/ are not accepted. + + + + Options + + The following options are understood: + + + + + + + + + Files + + + + /run/systemd/io.systemd.StorageProvider/block + + AF_UNIX socket the service listens on. This is the canonical location + for the block storage provider, and is enumerated by + storagectl providers. + + + + + + + + See Also + + systemd1 + storagectl1 + systemd-storage-fs@.service8 + + + + diff --git a/man/systemd-storage-fs@.service.xml b/man/systemd-storage-fs@.service.xml new file mode 100644 index 0000000000000..4fe0734398c98 --- /dev/null +++ b/man/systemd-storage-fs@.service.xml @@ -0,0 +1,199 @@ + + + + + + + + systemd-storage-fs@.service + systemd + + + + systemd-storage-fs@.service + 8 + + + + systemd-storage-fs@.service + systemd-storage-fs.socket + systemd-storage-fs + Storage provider exposing regular files and directories as storage volumes + + + + systemd-storage-fs@.service + systemd-storage-fs.socket + + + + Description + + systemd-storage-fs@.service is a service that implements the + io.systemd.StorageProvider Varlink + interface, exposing regular files and directories in /var/lib/storage/*.volume (if + used in system mode) or $XDG_STATE_HOME/storage (when used in user mode) as storage + volumes. Acquired volumes are returned to the caller as file descriptors. Unlike + systemd-storage-block@.service8, + this implementation also supports creating new volumes on demand from a small set of built-in + templates. + + The service is socket-activated via systemd-storage-fs.socket. In system mode + it listens on the AF_UNIX socket /run/systemd/io.systemd.StorageProvider/fs, in user + mode on $XDG_RUNTIME_DIR/systemd/io.systemd.StorageProvider/fs. See + storagectl1 for an + enumeration tool. + + See also + systemd-storage-block@.service8 + for a complementary implementation that exposes local block devices as storage volumes. + + + + Volumes + + Volumes are stored below the storage directory: + + + /var/lib/storage/ when run in system mode. + + $XDG_STATE_HOME/storage/ (typically + ~/.local/state/storage/) when run in user mode. + + + Each volume on disk is stored as a directory entry with a .volume suffix in + the storage directory. Entries which are regular files are exposed as volumes of type + reg; entries which are directories are exposed as volumes of type + dir. Moreover, block device nodes may be symlinked (or bind mounted) into the + directory, which are then exposed as volumes of type blk. + + For directory volumes, the root of the file system passed to clients is placed in a subdirectory + root/ of the NAME.volume directory. The former (and all inodes + below it) must be owned by the foreign UID range, the latter by the host's root. + + When acquiring a volume, symlinks are followed. + + An administrator is permitted to freely manipulate the volume hierarchy directly as long as the + rules described above are followed. In particular, it's permitted to copy, mount or symlink arbitrary + external resources (regardless if directory, regular file or block) into the volume directory, so that + they are exposed as additional volumes. + + + + Templates + + The provider supports creating new volumes automatically when they are acquired. The caller may + select a template that determines configuration details of the volume to create. The + following built-in templates are available: + + + + sparse-file + + Creates a volume backed by a sparsely populated regular file. This is the default + template when creating a regular file volume. (Volume type is reg.) + + + + + + allocated-file + + Creates a volume backed by a fully allocated regular file. (Volume type is + reg.) + + + + + + directory + + Creates a volume backed by a regular directory. (Volume type is + dir.) + + + + + + subvolume + + Creates a btrfs subvolume as backing inode (falling back to a regular directory if + the storage directory is not on btrfs). This is the default template when creating a directory + volume. (Volume type is dir.) + + + + + + + + Options + + The following command-line options are understood: + + + + + + Operate in system mode. Volumes are stored below + /var/lib/storage/. This is the default when invoked from + systemd-storage-fs@.service in the system manager. + + + + + + + + Operate in user mode. Volumes are stored below + $XDG_STATE_HOME/storage/. This is the default when invoked from + systemd-storage-fs@.service in the user manager. + + + + + + + + + + + Files + + + + /var/lib/storage/ + $XDG_STATE_HOME/storage/ + + The storage directory used to back the system mode and user mode service + instances respectively. Each volume is stored as an entry with a + .volume suffix below this directory. + + + + + + /run/systemd/io.systemd.StorageProvider/fs + $XDG_RUNTIME_DIR/systemd/io.systemd.StorageProvider/fs + + AF_UNIX sockets the service listens on, in system and user mode + respectively. These are the canonical locations for the fs storage + provider, and are enumerated by storagectl providers. + + + + + + + + See Also + + systemd1 + storagectl1 + systemd-storage-block@.service8 + + + + diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml index 2b40c1e561071..966a700fe92ba 100644 --- a/man/systemd-stub.xml +++ b/man/systemd-stub.xml @@ -89,10 +89,11 @@ compatible string supplied by the firmware in configuration tables and comparing it with the first compatible string from each of the .dtbauto sections. If the firmware does not provide a DeviceTree, the match is done using the - .hwids section instead. After selecting a .hwids section (see the - description below), the compatible string from that section will be used to perform - the same matching procedure. If a match is found, that .dtbauto section will be - loaded and will override .dtb if present. + .hwids section instead. After selecting the matching entry in the + .hwids section (see the description below), the compatible string + from that entry will be used to perform the same matching procedure. If a match is found, that + .dtbauto section will be loaded and will override .dtb if + present. Zero or more .efifw sections for the firmware image. It works in many ways similar to .dtbauto sections. systemd-stub @@ -103,12 +104,12 @@ matching firmware will be loaded. - Zero or more .hwids sections with hardware IDs of the machines to + An optional .hwids section with hardware IDs of the machines to match DeviceTrees. systemd-stub will use the SMBIOS data to calculate hardware IDs of the machine (as per specification), - and then it will try to find any of them in each of the .hwids sections. The first - matching section will be used. + and then it will try to find any of them in the .hwids section. The first + matching entry will be used. An optional .uname section with the kernel version information, i.e. the output of uname -r for the kernel included in the .linux @@ -130,7 +131,7 @@ In a basic UKI, the sections listed above appear at most once, with the exception of - .dtbauto and .hwids sections. In a multi-profile UKI, + .dtbauto and .efifw sections. In a multi-profile UKI, multiple sets of these sections are present in a single file and form "profiles", one of which can be selected at boot. For this, the PE section .profile is defined to be used as the separator between sets of sections. The @@ -291,6 +292,14 @@ by systemd-creds encrypt -T (see systemd-creds1 for details); in case of the system extension images by using signed Verity images. + + Note that earlier components of the boot process might register additional initrds, and thus + additional "companion" resources such as system extensions, configuration extensions and credentials for + consumption by the kernel and OS eventually booted. For example, + systemd-boot7 does + this for resources configured in UAPI.1 Type #1 extra + lines. systemd-stub will combine any resources provided that way with the companion + file resources it acquires itself. @@ -655,6 +664,31 @@ + + + LoaderPcrSMBIOS + + The PCR register index select SMBIOS structures are measured into — type 1 + (system information, with the volatile "Wake-up Type" field zeroed out), type 2 (baseboard + information) and type 11 (OEM strings). Formatted as decimal ASCII string (e.g. + 1). This variable is set if a measurement was successfully completed, and remains + unset otherwise. systemd-stub performs this measurement only if it has not already + been done in the same boot (e.g. by the boot loader), as indicated by this variable being set + already. + + + + + + LoaderBootSecret + + A non-volatile EFI variable only accessible from the pre-boot environment + (i.e. access from the OS is not permitted) that contains a per-system secret. It is set automatically + by systemd-stub if not present already. A secret derived from the value of this + EFI variable is passed to the OS in /.extra/boot-secret, see below. + + + Note that some of the variables above may also be set by the boot loader. The stub will only set @@ -762,6 +796,33 @@ + + + /.extra/boot-secret + A 32 byte per-system secret which is derived from a 32 byte secret stored in an EFI + variable (LoaderBootSecret, see above), which itself is only accessible to the + pre-boot environment. This may be used for various early-boot cryptographic purposes, and OS file + system access to it is restricted to root. The IMAGE_ID=/ID= + data from the .osrel is hashed into the secret, to ensure that different images + get a distinct secret passed. Moreover, a randomized 32 byte value stored in the ESP in the + /loader/boot-secret-mixin file is hashed in as well, ensuring that distinct disks will + result in different boot secrets. + + Note: this boot secret is ultimately protected only by firmware-enforced access controls on the + EFI variable. This is generally a much weaker protection than TPM-based approaches have, and it is + hence strongly recommended to use the TPM on systems that possess one. The boot secret is primarily + intended to be a lower-security fallback for cases where a TPM is not available. + + Applications should never protect resources directly with this secret, but derive their own + secret from it (by hashing it together with some application ID, in HMAC mode for example), in order + not to accidentally leak the primary boot secret. + + Note that the boot secret is only available if the pre-boot environment had a suitable RNG + source at the current boot or an earlier one. This source can be an initialized on-disk + random seed or the EFI RNG support, or both. + + + Note that all these files are located in the tmpfs file system the kernel sets @@ -789,6 +850,16 @@ + + + io.systemd.boot.loglevel + If set, the value of this string is used as log level. Valid values (from most to + least critical) are emerg, alert, crit, + err, warning, notice, info, + and debug. + + + diff --git a/man/systemd-sysext.xml b/man/systemd-sysext.xml index c9a8f1ed017e1..e9d275fac4960 100644 --- a/man/systemd-sysext.xml +++ b/man/systemd-sysext.xml @@ -20,9 +20,11 @@ systemd-sysext systemd-sysext.service systemd-sysext-initrd.service + systemd-sysext-sysroot.service systemd-confext systemd-confext.service systemd-confext-initrd.service + systemd-confext-sysroot.service Activates System Extension Images @@ -114,27 +116,50 @@ systemd-stub7 with extension images found in the system's EFI System Partition. - During boot OS extension images are activated automatically, if the - systemd-sysext.service is enabled. Note that this service runs only after the - underlying file systems where system extensions may be located have been mounted. This means they are not - suitable for shipping resources that are processed by subsystems running in earliest boot. Specifically, - OS extension images are not suitable for shipping system services or + During boot, system and configuration extension images are activated automatically if the + systemd-sysext.service and systemd-confext.service services are + enabled. Note that these services run only after the underlying file systems where system and configuration + extensions may be located have been mounted. To make it possible to ship resources that are processed by + subsystems running in the earliest boot stages (for example, system services or systemd-sysusers8 - definitions. See the Portable Services page - for a simple mechanism for shipping system services in disk images, in a similar fashion to OS - extensions. Note the different isolation on these two mechanisms: while system extension directly extend - the underlying OS image with additional files that appear in a way very similar to as if they were - shipped in the OS image itself and thus imply no security isolation, portable services imply service - level sandboxing in one way or another. The systemd-sysext.service service is - guaranteed to finish start-up before basic.target is reached; i.e. at the time + definitions), the systemd-sysext-sysroot.service and + systemd-confext-sysroot.service initrd services are provided. Currently, these + services cannot be used to merge system extensions from /sysroot/var/lib/extensions/ + and configuration extensions from /sysroot/var/lib/confexts/ when the + /var/ partition is split off. These extensions are later merged by the + systemd-sysext.service and systemd-confext.service services + during the main OS boot process. + + Also, see the Portable Services + page for a simple mechanism for shipping system services in disk images, in a similar fashion to OS + extensions. Note the differences in isolation between these two mechanisms: while system extensions directly extend + the underlying OS image with additional files that appear as if they were shipped in the OS image itself + and thus imply no security isolation, portable services imply service-level sandboxing in one way or another. + Binaries shipped in system extensions can only link against libraries from the host if the extension is + bound to the version of the host OS (see further below) or otherwise have to use static linking. + Portable services, however, are fully decoupled from the host as they ship their own library dependencies. + + The systemd-sysext.service and systemd-confext.service + services are guaranteed to finish start-up before basic.target is reached; i.e., by the time regular services initialize (those which do not use DefaultDependencies=no), the files - and directories system extensions provide are available in /usr/ and - /opt/ and may be accessed. + and directories provided by system and configuration extensions are available in /usr/, + /opt/, and /etc/ and may be accessed. + + System and configuration extensions can also be used to extend the initrd, and the + systemd-sysext-initrd.service and systemd-confext-initrd.service + initrd services are provided. Note that some limitations apply: resources that are used in the earliest boot + stages of the initrd (e.g. system services) cannot be updated. Note that there is no concept of enabling/disabling installed system extension images: all installed extension images are automatically activated at boot. However, you can place an empty directory named like the extension (no .raw) in /etc/extensions/ to "mask" - an extension with the same name in a system folder with lower precedence. + an extension with the same name in a system folder with lower precedence. It is also possible to disable + automatic merging altogether using the rd.systemd.sysext=, rd.systemd.confext=, + systemd.sysext=, and systemd.confext= kernel command line options. + Note that systemd-sysext-sysroot.service and + systemd-confext-sysroot.service are controlled by the systemd.sysext= + and systemd.confext= options, as these services merge system and configuration + extensions for the main system, not for the initrd. A simple mechanism for version compatibility is enforced: a system extension image must carry a /usr/lib/extension-release.d/extension-release.NAME @@ -148,9 +173,22 @@ but the used architecture identifiers are the same as for ConditionArchitecture= described in systemd.unit5. EXTENSION_RELOAD_MANAGER= can be set to 1 if the extension requires a service manager reload after application - of the extension. Note that for the reasons mentioned earlier, - Portable Services remain - the recommended way to ship system services. + of the extension. EXTENSION_RESTART_UNITS= and + EXTENSION_RELOAD_OR_RESTART_UNITS= can be set to a whitespace-separated list of unit + names (e.g. EXTENSION_RESTART_UNITS="my.service other.service") that + systemd-sysext/systemd-confext should restart resp. reload (or + restart, if the unit does not support reloading) after merging or refreshing the extension, and after + unmerging it. Use EXTENSION_RESTART_UNITS= for units whose binary the extension + replaces (a reload would not pick up the new binary), and EXTENSION_RELOAD_OR_RESTART_UNITS= + for units the extension extends with configuration drop-ins only (so reload-capable services are not + needlessly interrupted). If a unit has disappeared, e.g., on unmerge because it was part of the + extension, it is stopped instead. If the same unit is listed in both fields, + EXTENSION_RESTART_UNITS= takes precedence. Listing any unit in either field implies + EXTENSION_RELOAD_MANAGER=1, as the service manager would otherwise not yet (or no + longer) see the unit files shipped by the extension. Note that these settings do not take effect when + the extension is merged for the system already from the initrd and extensions should still ship a + .wants/ or .upholds/ symlink from the unit's install target to + ensure the unit starts at boot. System extensions should not ship a /usr/lib/os-release file (as that would be merged into the host /usr/ tree, overriding the host OS version data, which is not desirable). @@ -479,7 +517,9 @@ When used with merge, unmerge or refresh, do not reload daemon after executing the changes even if an extension that is applied requires a reload via the - EXTENSION_RELOAD_MANAGER= set to 1. + EXTENSION_RELOAD_MANAGER= set to 1. This also skips processing any units + listed via EXTENSION_RESTART_UNITS= or + EXTENSION_RELOAD_OR_RESTART_UNITS=. diff --git a/man/systemd-sysinstall.xml b/man/systemd-sysinstall.xml new file mode 100644 index 0000000000000..228e7e2bff17f --- /dev/null +++ b/man/systemd-sysinstall.xml @@ -0,0 +1,292 @@ + + + + + + + + systemd-sysinstall + systemd + + + + systemd-sysinstall + 8 + + + + systemd-sysinstall + systemd-sysinstall.service + Simple OS installer + + + + + systemd-sysinstall + OPTIONS + BLOCKDEVICE + + + systemd-sysinstall.service + + + + Description + + systemd-sysinstall is a simple terminal and command line based operating system + installer tool. Its primary use-case is to act as an automatically started interactive interface when + booting from an installer medium (e.g. a USB stick), in order to install an OS onto a target + disk. However, it may also be invoked directly from a shell. It executes the following steps: + + + It prompts the user for the target disk to install the OS on. (Unless the block device + is already specified on the command line.) + + It validates whether the disk is suitable (i.e. large enough, and with enough + free/unpartitioned space) for an OS installation. If it is generally suitable the user is prompted if they + want to erase the disk before installation, or if the OS shall be added to the existing partitions on + the disk (the latter only if enough free/unpartitioned disk space is available). + + It prompts the user whether to register the newly installed OS with the firmware boot option menu. + + It requests confirmation from the user, after showing a summary of the planned OS installation. + + It invokes + systemd-creds1's + encrypt command in order to generate encrypted (TPM locked, if available) system + credential files for a few, very basic system settings of the currently booted system (locale, keymap, + timezone), which it will install on the target disk, parameterizing the invoked kernel. (Or in other + words, it prepares that some settings already in effect on the installer system are propagated securely + onto the new installation.) + + It invokes + systemd-repart8 with + a definitions directory of /usr/lib/repart.sysinstall.d/ (only if populated – if + not will use the default of /usr/lib/repart.d/). This is supposed to set up the + basic OS partition structure on the target disk and copies in basic OS partitions (most importantly the + /usr/ hierarchy). + + It invokes + bootctl1's + link command to install an OS kernel image onto the target disk's ESP/XBOOTLDR, + together with the credential files prepared earlier. + + It invokes + bootctl1's + install command to install the + systemd-boot7 boot + loader onto the target disk's ESP. + + After confirmation, it reboots the system. + + + Note that the prompts/confirmation may be disabled via the command line, enabling fully automatic, + non-interactive installation. See below. + + Note this tool does not interactively query the user for a user to create or a root password to be + set on the target system, under the assumption these questions are better prompted from within the newly + installed system's first boot process, for example via the + systemd-firstboot1 or + systemd-homed-firstboot.service components. Note that if required such settings + may be propagated explicitly via the switch below. + + + + Options + + The following options are understood: + + + + + + + Overrides the directory where systemd-repart shall read its + partition definitions from, in place of the default of + /usr/lib/repart.sysinstall.d/. + + + + + + + + Takes a boolean argument. Controls whether to show the brief welcome text normally + displayed at the beginning of the installation. Defaults to true. + + + + + + + + Takes a boolean argument. Controls whether to show the colored bars at the top and + bottom of the terminal interface. Defaults to true. + + + + + + + + Takes a boolean argument. Controls whether to erase the current contents of the + target disk. If this switch is not used the user is prompted. + + + + + + + + Takes a boolean argument. Controls whether to interactively query the user for + confirmation before initiating the OS installation. Defaults to true. + + + + + + + + Takes a boolean argument. Controls whether to reboot the system after completing the + installation. Defaults to false. + + + + + + + + Takes a boolean argument. Controls whether to register the installed boot loader in + the firmware's boot options database. If not specified the user will be prompted. + + + + + + + + Takes a boolean argument. Controls whether to show a summary of the choices made + before asking for confirmation to proceed with the OS installation. Defaults to true. + + + + + + + + Takes a path to a unified kernel image (UKI). Explicitly selects the kernel image to + install on the target disk. If unspecified the currently booted kernel image is installed on the + target disk. + + + + + + + + Accepts an additional system credential to encrypt (with a key generated on the local + TPM, if available, and the null key otherwise) and place next to the installed kernel image in the + ESP. This may be used to parameterize the installed kernel with arbitrary system credentials. Do not + use this switch for sensitive data (such as passwords), use + instead, see below. May be used multiple times to configure multiple credentials. + + Note that three system credentials are propagated in similar fashion to the target system: + the locale, keymap and timezone. This may be controlled by the relevant + , and + options below. + + See + systemd.system-credentials7 + for a list of well-known system credentials that may be propagated this way. (Note that you may pass + arbitrary additional credentials this way, that can be consumed by any service of your + choice, via the usual system credentials logic.) + + + + + + + + Similar to but reads the credential value from a + file on disk or an AF_UNIX socket in the file system. This is generally + preferable for sensitive data, such as passwords. + + + + + + + + + + These options take boolean parameters. They control whether the indicated system + settings shall be propagated from the currently running system into the new target OS + installation. These options default to true. + + Typically, these three settings are the minimal settings that need to be configured during early + boot of an installer medium in order to make the installer tool accessible to the user. The + systemd-firstboot1 + tool may be used to query the user interactively when the OS install medium is booted for these + properties. By propagating these settings to the target installation via system credentials they do + not need to be queried again on first boot of the new installation. + + + + + + + + Takes a boolean argument. Controls whether to disable kernel and service manager log + output to the console the installer is invoked on temporarily while running, in order to avoid + interleaved output. Defaults to false. + + + + + + + + + + + Exit status + + On success, 0 is returned, and a non-zero failure code otherwise. + + + + Example + + + Invoke the tool for a fully automatic non-interactive OS installation + + systemd-sysinstall \ + /dev/disk/by-id/nvme-Micron_MTFDKBA1T0TFH_214532D0CDA5 \ + --erase=yes \ + --confirm=no \ + --variables=yes \ + --load-credential=ssh.authorized_keys.root:my-ssh-key + + + This installs the OS on the selected disk, erasing any previous contents, without confirmation, + registers it in the firmware, and drops in the SSH key for the root user, read from the + my-ssh-key file in the current directory. + + + + + See Also + + systemd1 + systemd-creds1 + systemd-repart8 + bootctl1 + systemd-firstboot1 + systemd-boot7 + systemd.system-credentials7 + + + + diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml index b7fe53dc9cf38..e33267409bf2f 100644 --- a/man/systemd-system.conf.xml +++ b/man/systemd-system.conf.xml @@ -326,6 +326,34 @@ + + + DefaultCPUPressureWatch= + DefaultCPUPressureThresholdSec= + + Configures the default settings for the per-unit + CPUPressureWatch= and CPUPressureThresholdSec= + settings. See + systemd.resource-control5 + for details. Defaults to auto and 200ms, respectively. This + also sets the CPU pressure monitoring threshold for the service manager itself. + + + + + + DefaultIOPressureWatch= + DefaultIOPressureThresholdSec= + + Configures the default settings for the per-unit + IOPressureWatch= and IOPressureThresholdSec= + settings. See + systemd.resource-control5 + for details. Defaults to auto and 200ms, respectively. This + also sets the IO pressure monitoring threshold for the service manager itself. + + + @@ -504,6 +532,49 @@ + + RestrictFileSystemAccess= + + Takes a boolean argument or the special value exec. Defaults to + no. When enabled, PID 1 loads a BPF LSM program that enforces a deny-default + execution policy: only binaries residing on signed dm-verity block devices (and the initramfs during + early boot) are permitted to execute. Execution from tmpfs, procfs, sysfs, unsigned dm-verity devices, + and anonymous executable memory mappings is denied. + + This setting is intended as one component of an image-based, fully verified system, where the + whole boot chain (firmware, kernel image, kernel command line, initramfs) is measured and attested. + On a general-purpose system without such guarantees it does not provide a meaningful security + boundary on its own: an attacker with sufficient privilege to edit + system.conf, modify the kernel command line, or kexec into an unsigned initrd + can disable or bypass the policy. + + The enforcement hooks block execve() of untrusted binaries + (bprm_check_security), PROT_EXEC memory mappings including + shared libraries (mmap_file), and write-to-execute transitions such as JIT + compilation (file_mprotect). + + Note that execution from overlayfs mounts is blocked even if the underlying layers reside on + signed dm-verity devices, because the BPF program sees the overlay filesystem's anonymous device + number rather than the underlying block device. Multi-device filesystems such as btrfs are similarly + unsupported. + + Note that, without further measures to secure the system, kexec can be used to circumvent this. + + This requires the kernel to be booted with dm_verity.require_signatures=1 + on the kernel command line and with BPF LSM enabled (lsm=...,bpf). If either + prerequisite is not met, PID 1 will refuse to complete startup. + + The value yes is equivalent to exec. Additional + modes may be added in the future. + + This option may also be set via the systemd.restrict_filesystem_access= kernel command + line option, see + kernel-command-line7. + + + + + SystemCallArchitectures= @@ -633,6 +704,38 @@ + + + EventLoopRateLimitIntervalSec= + EventLoopRateLimitBurst= + + Configures the rate limiting applied to the manager's main event loop. If the event + loop iterates more than EventLoopRateLimitBurst= times within + EventLoopRateLimitIntervalSec=, event processing is briefly paused to prevent + excessive CPU usage. EventLoopRateLimitIntervalSec= defaults to 1s. + EventLoopRateLimitBurst= defaults to 50000. These settings can also be set on the + kernel command line via + systemd.event_loop_ratelimit_interval_sec= and + systemd.event_loop_ratelimit_burst=. + + + + + + MinimumUptimeSec= + + Specifies the minimum uptime for the system which has to be reached before a shutdown + is executed. Defaults to 15s. This mechanism is introduced to avoid high frequency reboot loops, when + technical failures trigger an automatic shutdown during the boot process. Each reboot cycle is + delayed to the specified minimum time, giving the user a chance to review screen contents or + otherwise interact with the device before the shutdown proceeds. The delay takes place during the + very last phase of system shutdown, immediately before the reboot() system call + is executed. If the system is already running for longer than the specified time, this setting has no + effect. This logic is also skipped in container environments. Set to zero in order to disable this + logic. + + + @@ -688,6 +791,18 @@ + + + DefaultMemoryZSwapWriteback= + + Takes a boolean argument. Defaults to true if unspecified. This is used as a default + for units which lack an explicit definition for MemoryZSwapWriteback=. + See systemd.resource-control5 + for the details. + + + + diff --git a/man/systemd-sysupdate.xml b/man/systemd-sysupdate.xml index 1dea457e133e5..2855c650c8856 100644 --- a/man/systemd-sysupdate.xml +++ b/man/systemd-sysupdate.xml @@ -18,10 +18,15 @@ systemd-sysupdate - systemd-sysupdate.service - systemd-sysupdate.timer + systemd-sysupdate-update.service + systemd-sysupdate-update.timer systemd-sysupdate-reboot.service systemd-sysupdate-reboot.timer + + + systemd-sysupdate.service + systemd-sysupdate.timer + Automatically Update OS or Other Resources @@ -31,7 +36,7 @@ OPTIONS - systemd-sysupdate.service + systemd-sysupdate-update.service @@ -73,9 +78,9 @@ embedded in the disk images. For the latter, see below. The latter is particularly interesting to update container images or portable service images. - The systemd-sysupdate.service system service will automatically update the + The systemd-sysupdate-update.service system service will automatically update the host OS based on the installed transfer files. It is triggered in regular intervals via - systemd-sysupdate.timer. The systemd-sysupdate-reboot.service + systemd-sysupdate-update.timer. The systemd-sysupdate-reboot.service will automatically reboot the system after a new version is installed. It is triggered via systemd-sysupdate-reboot.timer. The two services are separate from each other as it is typically advisable to download updates regularly while the system is up, but delay reboots until the @@ -204,6 +209,31 @@ + + + + Removes orphaned files that were previously installed by a transfer, but are no + longer owned by any currently defined transfer file. Whenever a resource is installed into the file + system, systemd-sysupdate records the target directory and the matching pattern in + an installation database below /var/lib/systemd/sysupdate/. This command + iterates through these records, determines which files they match, and deletes those that are no + longer covered by any of the patterns of the transfer files currently in place. This is useful to + garbage-collect files that used to be owned by a transfer file that has since been modified, disabled + or removed altogether (for example because a component is no longer being updated). + + By default only the selected component is processed (i.e. the one selected via + , or the default one if none were selected). Use + to process all components known to the installation database in a + single invocation. + + This operation only removes files that were installed into the file system (i.e. resources of + type regular-file, directory and subvolume, + see sysupdate.d5); + it does not touch partition-based resources. + + + + @@ -239,11 +269,26 @@ updated together in a synchronous fashion. Simply define multiple transfer files within the same sysupdate.d/ directory for these cases. - This option may not be combined with . + This option may not be combined with , nor with the + pending and reboot commands or the + switch, which only apply to the booted OS version. + + + + + Instead of operating on a single component, operate on all known components (as well as + the default, component-less installation). This is currently only supported for the + cleanup command; all other commands will fail if this switch is used. + + This option may not be combined with . + + + + @@ -314,11 +359,24 @@ When used in combination with the update commands and a new version is - installed, automatically reboots the system immediately afterwards. + installed, automatically reboots the system immediately afterwards. This switch may not be combined with + , as it only applies to the booted OS version. + + + + Takes a boolean argument. When used in combination with the update + command, automatically performs the equivalent of the cleanup command afterwards, + removing any orphaned files that are no longer covered by the patterns of the currently defined + transfer files. This is useful to garbage-collect files that used to be owned by a transfer file that + has since been modified, disabled or removed. Defaults to off. + + + + diff --git a/man/systemd-tmpfiles.xml b/man/systemd-tmpfiles.xml index 815dcd88d62ff..c48c0653b0dd2 100644 --- a/man/systemd-tmpfiles.xml +++ b/man/systemd-tmpfiles.xml @@ -210,6 +210,14 @@ + + + Treat each positional argument as a separate configuration + line instead of a file name. + + + + Only apply rules with paths that start with diff --git a/man/systemd-tpm2-generator.xml b/man/systemd-tpm2-generator.xml index 2e22b99b5a0fc..b45cf29be8698 100644 --- a/man/systemd-tpm2-generator.xml +++ b/man/systemd-tpm2-generator.xml @@ -45,6 +45,14 @@ for it yet. The latter might be useful in environments where a suitable TPM2 driver for the available hardware is not available. + The kernel command line option (which takes a + boolean argument, defaulting to false) may be used to enable an automatic software TPM fallback in case a + hardware TPM is not detected and + swtpm8 is + available. This pulls in the + systemd-tpm2-swtpm.service8 + service. + systemd-tpm2-generator implements systemd.generator7. @@ -55,6 +63,7 @@ systemd1 systemd.special7 kernel-command-line7 + systemd-tpm2-swtpm.service8 diff --git a/man/systemd-tpm2-swtpm.service.xml b/man/systemd-tpm2-swtpm.service.xml new file mode 100644 index 0000000000000..3111a782c7846 --- /dev/null +++ b/man/systemd-tpm2-swtpm.service.xml @@ -0,0 +1,63 @@ + + + + + + + + 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-vconsole-setup.service.xml b/man/systemd-vconsole-setup.service.xml index 87cb9e4777bb4..e6656eb578545 100644 --- a/man/systemd-vconsole-setup.service.xml +++ b/man/systemd-vconsole-setup.service.xml @@ -95,6 +95,20 @@ + + Firmware-provided keyboard layout + + If the boot loader reports the firmware-configured keyboard layout via the + LoaderKeyboardLayout EFI variable (see the + Boot Loader Interface), + systemd-vconsole-setup uses it as the lowest-priority fallback for the + keymap. The RFC 4646 / BCP 47 language tag reported by the firmware (e.g. de-DE) is + matched against the optional sixth column of /usr/share/systemd/kbd-model-map, + which lists the language tags each virtual-console keymap covers. Credentials, + /etc/vconsole.conf, and kernel command line options all override this + firmware-provided default. + + See Also diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 0b4fef2314a6d..fd73e0077075e 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -104,6 +104,18 @@ + + + + + If specified, the VM is run with a temporary snapshot of its file system that is removed + immediately when the VM terminates. Only works with currently. + + Note that will not work with . + + + + @@ -126,15 +138,38 @@ - - + - If specified, the VM is run with a temporary snapshot of its file system that is removed - immediately when the VM terminates. Only works with currently. + 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. - Note that will not work with . + + - + + + + Controls whether qemu processes discard requests from the VM. + This prevents long running VMs from using more disk space than required. + This is enabled by default. + + + + + + + + + Grows the image file specified by to the specified size + in bytes if it is smaller. Executes no operation if no image file is used or the image file is + already as large or larger than requested. The specified size accepts the usual K, M, G suffixes + (to the base of 1024). Specified values are rounded up to multiples of 4096. + + @@ -154,10 +189,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. @@ -227,36 +264,118 @@ - + - - Set the linux kernel image to use for direct kernel boot. - If a directory type image is used and was omitted, vmspawn will search for boot loader entries - according to the - UAPI.1 Boot Loader Specification assuming - XBOOTLDR to be located at /boot and ESP to be /efi respectively. - If no kernel was installed into the image then the image will fail to boot. + 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. - - + - + - - Set the initrd to use for direct kernel boot. - If the supplied is a - UAPI.1 Boot Loader Specification - Type #2 entry, then this argument is not required. - If no initrd was installed into the image then the image will fail to boot. + 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. - can be specified multiple times and vmspawn will merge them together. + If is specified, auto behaves like + off. - - + + + + + + + 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. + + + + + + + + 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. + + + + + + + + Caveat: This feature is experimental, and is likely to be changed (or removed in + its current form) in a future version of systemd. + + Configures whether to run the guest as a confidential VM. Takes one of + no or sev-snp. Defaults to no. + + sev-snp enables AMD SEV-SNP. This requires KVM on an x86_64 host with + SNP-capable hardware and firmware. must point to a raw SNP-built + OVMF .fd image; the standard pflash + NVRAM split is not supported under + SNP, so the firmware is loaded via QEMU's and Secure Boot is + unavailable. Direct kernel boot via is required so that the kernel, + initrd and command line are hashed into the launch measurement + (kernel-hashes=on); booting the kernel off the disk image via the firmware + would leave it outside the measurement. Credentials passed via + or are bundled into a cpio archive appended to the initrd + (mirroring what systemd-stub does for ESP credentials), so they enter the + launch measurement via kernel-hashes=on; the SMBIOS and fw_cfg channels + normally used to deliver credentials are not used because they are unmeasured and would be + discarded by PID1 in confidential guests. This channel is measured but not confidential with + respect to the host or VMM: the initrd (and thus the credentials it carries) is supplied to QEMU + as plaintext and only its hash enters the launch measurement, which guarantees integrity but does + not keep the credentials secret from the host. This requires the guest to run a sufficiently + recent version of systemd (supporting /.extra/system_credentials/). A vTPM, + if attached via , must be treated as untrusted by the guest. + + + + + + Networking Options + + @@ -283,50 +402,42 @@ + + - - - - 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. - - - + + Execution Options + - + - Controls whether qemu processes discard requests from the VM. - This prevents long running VMs from using more disk space than required. - This is enabled by default. + + Set the linux kernel image to use for direct kernel boot. + If a directory type image is used and was omitted, vmspawn will search for boot loader entries + according to the + UAPI.1 Boot Loader Specification assuming + XBOOTLDR to be located at /boot and ESP to be /efi respectively. + If no kernel was installed into the image then the image will fail to boot. - + + - - - 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. - - - + - - - + + Set the initrd to use for direct kernel boot. + If the supplied is a + UAPI.1 Boot Loader Specification + Type #2 entry, then this argument is not required. + If no initrd was installed into the image then the image will fail to boot. - Grows the image file specified by to the specified size - in bytes if it is smaller. Executes no operation if no image file is used or the image file is - already as large or larger than requested. The specified size accepts the usual K, M, G suffixes - (to the base of 1024). Specified values are rounded up to multiples of 4096. + can be specified multiple times and vmspawn will merge them together. - + + @@ -428,13 +539,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,16 +597,51 @@ - + 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. + + + + Acquire a storage volume from a + storagectl1 + provider and attach it to the virtual machine. PROVIDER is the + provider name (typically block or fs). VOLUME + is the volume name passed to the provider's Acquire() method. + CONFIG selects the guest device type and takes one of + virtio-blk, virtio-scsi, nvme, or + scsi-cd. If empty or omitted, defaults to virtio-blk. + + The trailing comma-separated K=V list passes parameters to + io.systemd.StorageProvider.Acquire(): template=, + create= (one of any, new, open), + read-only= (or ro=; takes a boolean or auto), + size= / create-size= (size for created volumes), + request-as= (one of blk, reg, + dir; dir is rejected by vmspawn). + + Each attached volume is identified by the name PROVIDER:VOLUME. + Volumes attached at startup via this option cannot be detached at runtime via + machinectl unbind-volume; only volumes added at runtime via + machinectl bind-volume are removable. + + The provider is looked up under + /run/systemd/io.systemd.StorageProvider/ for system mode (or + $XDG_RUNTIME_DIR/systemd/io.systemd.StorageProvider/ for user mode), matching + the runtime scope chosen via / . + + + + @@ -584,7 +727,7 @@ - Integration Options + Logging Options @@ -601,6 +744,28 @@ + + + + + + + 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. + + + + + + + + + SSH Options + + @@ -640,15 +805,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 +866,24 @@ 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. + + Under , SMBIOS and fw_cfg are not covered by the SNP launch + measurement and are discarded by PID1 in confidential guests. Credentials are therefore packaged + into a cpio archive containing + .extra/system_credentials/ID.cred entries and + appended to the initrd that QEMU loads, so they enter the launch measurement via + kernel-hashes=on. PID1 imports them from the initramfs at boot. As with the + kernel command line, this is a measured but not a confidential channel: QEMU receives the initrd + (and thus the embedded credentials) as plaintext from the host and only its hash is covered by the + launch measurement, so a modified initrd produces a different launch measurement that a relying + party can detect via remote attestation, but the credentials are not hidden from the host or VMM. + This requires the guest to run a sufficiently recent version of systemd (supporting + /.extra/system_credentials/). + @@ -716,16 +915,16 @@ $ systemd-vmspawn --image=image.raw - Import and run a Fedora &fedora_latest_version; Cloud image using machinectl + Import and run a Fedora &fedora_latest_version; Cloud image using importctl $ curl -L \ - -O https://download.fedoraproject.org/pub/fedora/linux/releases/&fedora_latest_version;/Cloud/x86_64/images/Fedora-Cloud-Base-&fedora_latest_version;-&fedora_cloud_release;.x86_64.raw.xz \ + -O https://download.fedoraproject.org/pub/fedora/linux/releases/&fedora_latest_version;/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-&fedora_latest_version;-&fedora_cloud_release;.x86_64.qcow2 \ -O https://download.fedoraproject.org/pub/fedora/linux/releases/&fedora_latest_version;/Cloud/x86_64/images/Fedora-Cloud-&fedora_latest_version;-&fedora_cloud_release;-x86_64-CHECKSUM \ -O https://fedoraproject.org/fedora.gpg $ gpgv --keyring ./fedora.gpg Fedora-Cloud-&fedora_latest_version;-&fedora_cloud_release;-x86_64-CHECKSUM $ sha256sum -c Fedora-Cloud-&fedora_latest_version;-&fedora_cloud_release;-x86_64-CHECKSUM -# machinectl import-raw Fedora-Cloud-Base-&fedora_latest_version;-&fedora_cloud_release;.x86_64.raw.xz fedora-&fedora_latest_version;-cloud +# importctl import-raw -m Fedora-Cloud-Base-Generic-&fedora_latest_version;-&fedora_cloud_release;.x86_64.qcow2 fedora-&fedora_latest_version;-cloud # systemd-vmspawn -M fedora-&fedora_latest_version;-cloud diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index e7d5e63c963de..0b43bc69603b8 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -1408,12 +1408,16 @@ CapabilityBoundingSet=~CAP_B CAP_C NUMAPolicy= Controls the NUMA memory policy of the executed processes. Takes a policy type, one of: - , , , and - . A list of NUMA nodes that should be associated with the policy must be specified - in NUMAMask=. For more details on each policy please see, + , , , , + , (requires Linux 5.15 or newer) and + (requires Linux 6.9 or newer, weights are configured via + /sys/kernel/mm/mempolicy/weighted_interleave/). A list of NUMA nodes that should be + associated with the policy must be specified in NUMAMask=. For more details on each + policy please see, set_mempolicy2. For overall overview of NUMA support in Linux see, numa7. + If the kernel does not support the requested policy, a warning is logged and the setting is ignored. @@ -1538,6 +1542,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 +1777,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/. + @@ -3282,6 +3298,10 @@ SystemCallErrorNumber=EPERM If the empty string is assigned to this option, the list of files to read is reset, all prior assignments have no effect. + Note that shell variables such as $HOME are not expanded in this path. + Use %-specifiers instead; for example, %h expands to the + user's home directory. + The files listed with this directive will be read shortly before the process is executed (more specifically, after all processes from a previous unit state terminated. This means you can generate these files in one unit state, and read it with this option in the next. The files are read from the file @@ -4686,13 +4706,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..5f5930adf50e6 100644 --- a/man/systemd.link.xml +++ b/man/systemd.link.xml @@ -365,6 +365,20 @@ + + + MachineTag= + + Matches against the tags assigned to the local machine. See + ConditionMachineTag= in + systemd.unit5 + for details. When prefixed with an exclamation mark (!), the result is negated. + If an empty string is assigned, the previously assigned value is cleared. + + + + + @@ -666,7 +680,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 +689,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. @@ -1080,6 +1094,62 @@ + + IRQAffinityPolicy= + + Specifies the IRQ distribution strategy for network interface MSI/MSI-X interrupts. Takes one + of spread or single. When set to spread, + queue IRQs are distributed across CPUs using a topology-aware maximum distance algorithm that + prefers CPUs on different NUMA nodes, then different physical cores, then different hyperthreads. + When set to single, all IRQs are pinned to a single CPU (CPU 0 by default, or + the first CPU in the set specified by IRQAffinity=). When there are more IRQs + than available CPUs, queues wrap around using round-robin assignment. When unset, no affinity + management is performed. Setting this to an empty string explicitly disables affinity management. + + This option only applies to devices with MSI/MSI-X interrupts discoverable via + /sys/class/net/iface/device/msi_irqs/. Virtual + devices (veth, tap) and legacy INTx devices are skipped with a notice logged. + + Note that if irqbalance1 + or similar IRQ management daemons are running, they may override the configured + affinity. Consider disabling such daemons or configuring them to exclude managed interfaces. + + + + + + IRQAffinity= + + Filters the set of CPUs eligible for IRQ placement. Takes a list of CPU indices or ranges + separated by either whitespace or commas (e.g., 0-3, 0,2,4,6, + 0-3,8-11). This option works in conjunction with + IRQAffinityPolicy= and IRQAffinityNUMA= to constrain which + CPUs receive network IRQs. When specified with spread policy, only the listed + CPUs are considered for IRQ distribution. When specified with single policy, + IRQs are pinned to the first CPU in the allowed set instead of CPU 0. Has no effect if + IRQAffinityPolicy= is not set. + + + + + + IRQAffinityNUMA= + + Filters CPUs to those belonging to the specified NUMA node. Takes either + local or an explicit NUMA node number (0, 1, 2, ...). When set to + local, the NUMA node local to the NIC's PCIe slot is used (determined from + /sys/class/net/iface/device/numa_node). If the + device's NUMA node cannot be determined (e.g., non-NUMA system), a warning is logged and IRQ + affinity configuration is skipped. + + When both IRQAffinity= and IRQAffinityNUMA= are + specified, their intersection is used. If the intersection results in an empty set, an error is + logged and no affinity is applied. Has no effect if IRQAffinityPolicy= is not + set. + + + + ReceiveVLANCTAGHardwareAcceleration= diff --git a/man/systemd.mstack.xml b/man/systemd.mstack.xml index b0d6d5f545347..373dc922a2639 100644 --- a/man/systemd.mstack.xml +++ b/man/systemd.mstack.xml @@ -29,7 +29,7 @@ switch or the service manager's setting for services. .mstack/ directories may contain various files and subdirectories, where each will effect one layer of an overlayfs mount, or a bind mount. The name of the - file or subdirectory indicates how it shall used in the mount hierarchy. Specifically, the following + file or subdirectory indicates how it shall be used in the mount hierarchy. Specifically, the following names are defined: @@ -65,9 +65,9 @@ Similar, robind@location.raw creates a read-only bind mount from a DDI. - If a root/ subdirectory it is used as root of the resulting mount - hierarchy, and only the usr/ subtree of the overlayfs mount will be bound to - usr/ in the hierarchy. + If a root/ subdirectory exists, it is used as root of the + resulting mount hierarchy, and only the usr/ subtree of the overlayfs mount will + be bound to usr/ in the hierarchy. Note that each of the entry types above may be a symbolic link pointing to a directory or image @@ -86,7 +86,7 @@ Examples The following .mstack/ consists of two read-only overlayfs layers as DDI, plus one - writable directory one on top. The read-only layers are symlinked: + writable directory on top. The read-only layers are symlinked: foobar.mstack/layer@0.raw../base.raw diff --git a/man/systemd.net-naming-scheme.xml b/man/systemd.net-naming-scheme.xml index 1a024a0c305db..9887aa8701fce 100644 --- a/man/systemd.net-naming-scheme.xml +++ b/man/systemd.net-naming-scheme.xml @@ -170,6 +170,7 @@ ID_NET_NAME_SLOT=prefix[Pdomain]sslot[ffunction][nport_name|ddev_port]uport…[cconfig][iinterface] ID_NET_NAME_SLOT=prefix[Pdomain]sslot[ffunction][nport_name|ddev_port]vslot ID_NET_NAME_SLOT=prefix[Pdomain]sslot[ffunction][nport_name|ddev_port]rslot + ID_NET_NAME_SLOT=prefix[Pdomain]sslot[ffunction][nport_name|ddev_port]Ssfnum This property describes the slot position. Different schemes are used depending on the bus type, as described in the table below. In case of USB, BCMA, and SR-VIO devices, the full @@ -222,6 +223,11 @@ … rslot SR-IOV slot number + + + Ssfnum + Auxiliary sub-function (SF) number + @@ -247,6 +253,20 @@ is linked to the particular representor, with any leading zeros removed. The physical port name and the bus number are ignored. + Auxiliary sub-function (SF) network devices, where the network device's parent is an + auxiliary device exposing a sfnum sysfs attribute (currently mlx5_core SFs), + are named based on the underlying PCI function (the PF, or for VF-SF the PF behind the VF), + with a suffix of S and the user-defined sub-function number from + sfnum. This is analogous to how SR-IOV virtual function devices are named + with a v suffix. + + If the SF's parent PCI function is itself an SR-IOV virtual function (VF-SF), the + name is rooted at the PF and both suffixes are chained, with the v + suffix preceding the S suffix + (e.g. enp193s0f0v0S88). The PF, the VF, and the SF therefore form a + stable, hierarchical sequence regardless of the VF's underlying PCI bus/device/function + numbering. + In some configurations a parent PCI bridge of a given network controller may be associated with a slot. In such case we do not generate this device property to avoid possible naming conflicts. @@ -262,6 +282,7 @@ ID_NET_NAME_PATH=prefix[Pdomain]pbussslot[ffunction][nphys_port_name|ddev_port] ID_NET_NAME_PATH=prefix[Pdomain]pbussslot[ffunction][nphys_port_name|ddev_port]bnumber ID_NET_NAME_PATH=prefix[Pdomain]pbussslot[ffunction][nphys_port_name|ddev_port]uport…[cconfig][iinterface] + ID_NET_NAME_PATH=prefix[Pdomain]pbussslot[ffunction][nphys_port_name|ddev_port]Ssfnum This property describes the device installation location. Different schemes are used depending on the bus type, as described in the table below. For BCMA and USB devices, PCI path @@ -310,6 +331,11 @@ USB port number chain + + Ssfnum + Auxiliary sub-function (SF) number + + @@ -575,6 +601,22 @@ + + + v261 + + Stable names are now generated for auxiliary sub-function (SF) network devices + (such as mlx5_core SFs). The name is built from the parent PCI Physical Function's + path with an Ssfnum suffix, where + sfnum is the user-defined SF number (the value passed to + devlink port add … sfnum N, exposed by the kernel as + the sfnum sysfs attribute on the auxiliary device). This is analogous to the + vN suffix used for SR-IOV virtual functions; for SFs + hosted on SR-IOV VFs (VF-SF), the two suffixes are chained on top of the PF's base name. + + + + Note that latest may be used to denote the latest scheme known (to this diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml index 6a84b7a648cef..66d5334b37b35 100644 --- a/man/systemd.netdev.xml +++ b/man/systemd.netdev.xml @@ -230,6 +230,7 @@ + @@ -2683,7 +2684,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..6b988e0e4744a 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -165,6 +165,7 @@ + @@ -435,6 +436,26 @@ + + DHCPRelay= + + Takes one of upstream or downstream. When specified, + the host acts as a DHCP relay agent. When set to upstream, the interface behaves + as an upstream interface of the DHCP relay agent. When set to downstream, the + interface behaves as a downstream interface of the DHCP relay agent. To make the DHCP relay agent + work, at least one upstream interface and one downstream interface must be configured on the host. + This requires ServerAddress= in the [DHCPRelay] section to be configured in + networkd.conf. If ServerAddress= is not configured, this + setting is ignored. See + networkd.conf5 + for more details about ServerAddress= and other host-wide settings. + Also, further per-interface settings can be configured in the [DHCPRelay] section described below. + Defaults to unset, and the interface is not used for DHCP relay forwarding. + + + + + DHCPServer= @@ -444,10 +465,9 @@ Even if this is enabled, the DHCP server will not be started automatically and wait for the persistent storage being ready to load/save leases in the storage, unless - RelayTarget= or PersistLeases=no/runtime are specified in the - [DHCPServer] section. It will be started after - systemd-networkd-persistent-storage.service is started, which calls - networkctl persistent-storage yes. See + PersistLeases=no/runtime is specified in the [DHCPServer] section. It will be + started after systemd-networkd-persistent-storage.service is started, which + calls networkctl persistent-storage yes. See networkctl1 for more details. @@ -1086,6 +1106,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 +2415,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 +2431,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. @@ -3171,6 +3208,23 @@ MultiPathRoute=2001:db8::1@eth0 + + RouteTable=num + + The table identifier for routes installed by the DHCPv6 client, e.g. unreachable + routes for delegated prefixes. Takes one of the predefined names default, + main, and local, names defined in + RouteTable= in + networkd.conf5, + or a number between 1…4294967295. + + When used in combination with VRF=, the VRF's routing table is + used when this parameter is not specified. + + + + + RapidCommit= @@ -3464,7 +3518,7 @@ MultiPathRoute=2001:db8::1@eth0 generated by using the EUI-64 algorithm. Because the interface identifier is static, if Duplicate Address Detection detects that the computed address is a duplicate (in use by another node on the link), then this mode will fail to provide an address - for that prefix. If an IPv6 address without mode is specified, then + for that prefix. If an IPv6 address without static is specified, then static mode is assumed. @@ -3834,6 +3888,104 @@ Token=prefixstable:2002:da8:1:: + + [DHCPRelay] Section Options + The [DHCPRelay] section contains per-interface settings for the DHCP relay agent. The settings in + this section are only used when DHCPRelay= in the [Network] section is configured. + + + + + + AgentAddress= + + Takes an IPv4 address. The specified address is used as the source IP address of packets + forwarded to the upstream DHCP server. The address may also be used for the Server Identifier + Override sub-option and the Link Selection sub-option in the Relay Agent Information option + appended to DHCP messages forwarded to the upstream DHCP server. This setting applies to both + upstream and downstream interfaces. Defaults to unset. If unset, a statically configured IPv4 + address in the .network file is selected automatically. When multiple static IPv4 addresses are + configured, it is recommended to specify this setting explicitly. + + + + + + + GatewayAddress= + + Takes an IPv4 address. The specified address is set to the giaddr field of + the DHCP message header when the DHCP message is forwarded to the upstream DHCP server. This is + used only when DHCPRelay=downstream, and ignored otherwise. Defaults to unset, + and the same address specified with AgentAddress= will be used. + + + + + + + CircuitId= + + Specifies the circuit ID of the downstream interface. This is set as the Circuit ID + sub-option in the Relay Agent Information option in the forwarded DHCP message. Takes a data type + and data separated with a colon + (type:value). The type + takes one of uint8, uint16, uint32, + ipv4address, ipv6address, or string. + Special characters in the data string may be escaped using + C-style escapes. + This is used only when DHCPRelay=downstream, and ignored otherwise. If unset, + the name of the interface will be used. + + + + + + + VirtualSubnetSelection= + + Specifies a unique identifier used by the DHCP server to select the downstream subnet + independently of giaddr. This value is used as the Virtual Subnet Selection sub-option in the Relay + Agent Information option in the forwarded DHCP message. Takes a value in the same format as + CircuitId=. This is used only when DHCPRelay=downstream, and + ignored otherwise. Defaults to unset, and the sub-option is not set. + + + + + + + ExtraOption= + + Specifies an extra sub-option in the Relay Agent Information option, which is appended to + DHCP messages forwarded to the upstream DHCP server. Takes a sub-option code, data type, and data + separated with a colon + (code:type:value). + The code is an integer in 1…254. See CircuitId= in the above for the acceptable + type and data. This setting can be specified multiple times. When an empty string is specified, + all previous assignments are cleared. This is used only when + DHCPRelay=downstream, and ignored otherwise. Defaults to unset, and no extra + sub-option will be appended. + + + + + + + InterfacePriority= + + Specifies the priority of the upstream interface. Takes an integer value. When the host has + multiple upstream interfaces, the upstream interface with the highest (largest) priority will be + used for forwarding DHCP messages to the upstream DHCP server. When multiple interfaces have the + same priority, which interface is used is unspecified. This is used only when + DHCPRelay=upstream, and ignored otherwise. Defaults to zero. + + + + + + + [DHCPServer] Section Options The [DHCPServer] section contains settings for the DHCP server, if enabled via the @@ -4127,52 +4279,6 @@ ServerAddress=192.168.0.1/24 - - BindToInterface= - - Takes a boolean value. When yes, DHCP server socket will be bound - to its network interface and all socket communication will be restricted to this interface. - Defaults to yes, except if RelayTarget= is used (see below), - in which case it defaults to no. - - - - - - RelayTarget= - - Takes an IPv4 address, which must be in the format described in - inet_pton3. - Turns this DHCP server into a DHCP relay agent. See RFC 1542. - The address is the address of DHCP server or another relay agent to forward DHCP messages to and from. - - - - - - RelayAgentCircuitId= - - Specifies value for Agent Circuit ID suboption of Relay Agent Information option. - Takes a string, which must be in the format string:value, - where value should be replaced with the value of the suboption. - Defaults to unset (means no Agent Circuit ID suboption is generated). - Ignored if RelayTarget= is not specified. - - - - - - RelayAgentRemoteId= - - Specifies value for Agent Remote ID suboption of Relay Agent Information option. - Takes a string, which must be in the format string:value, - where value should be replaced with the value of the suboption. - Defaults to unset (means no Agent Remote ID suboption is generated). - Ignored if RelayTarget= is not specified. - - - - RapidCommit= @@ -4204,9 +4310,6 @@ ServerAddress=192.168.0.1/24 networkd.conf5, which defaults to yes, will be used. - When RelayTarget= is specified, this setting will be ignored and no leases - will be saved, as there will be no bound lease on the server. - @@ -6445,12 +6548,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 +6563,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 +7013,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..4e908f150ecd1 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. + @@ -518,6 +521,28 @@ CPUWeight=20 DisableControllers=cpu / \ + + CPUSetPartition= + + + Sets the partition type for the executed processes. Takes one + of member, root, or isolated. This setting + controls the cpuset.cpus.partition cgroup attribute. + + When set to member, the cpuset operates in normal mode. + root creates a partition root, which can further divide CPUs among child cgroups. + isolated provides full CPU isolation, useful for real-time workloads that + require dedicated CPU resources without interference from other processes. + Defaults to the kernel default, which is member. For more details about this + control group attribute, see + Control Groups v2. + + This setting requires AllowedCPUs= to also be set. + + + + + Process Accounting and Control @@ -1102,7 +1127,7 @@ BindNetworkInterface=vrf-mgmt systemctl daemon-reload can be used to refill the sets. Example: - [Unit] + [Service] NFTSet=cgroup:inet:filter:my_service user:inet:filter:serviceuser Corresponding NFT rules: @@ -1605,6 +1630,35 @@ DeviceAllow=/dev/loop-control + + OOMRules= + + + Takes a space-separated list of OOM ruleset names. The rulesets are defined in + .oomrule files placed in + /etc/systemd/oomd/rules.d/, + /run/systemd/oomd/rules.d/, + /usr/local/lib/systemd/oomd/rules.d/, or + /usr/lib/systemd/oomd/rules.d/. When set, + systemd-oomd.service8 + will monitor this unit's cgroup and evaluate the specified rulesets against it. + Each ruleset defines conditions (such as memory pressure or swap usage thresholds) and an action + to take when those conditions are met. See + oomd.conf5 for + details on the available ruleset options. + + Setting this property will also result in After= and + Wants= dependencies on systemd-oomd.service unless + DefaultDependencies=no. + + Defaults to an empty list, which means no rulesets are applied. Note that each monitored + cgroup incurs a per-interval walk of its descendant cgroup tree, so monitoring very large numbers of + cgroups via OOMRules= may have a measurable performance impact. + + + + + MemoryPressureWatch= @@ -1625,7 +1679,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 +1708,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..fc1545f47d932 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -457,6 +457,15 @@ signal, etc.), the unit will be considered failed (and remaining commands will be skipped). Exit code of 0 or those matching SuccessExitStatus= will continue execution to the next commands. + Note that an ExecCondition= skip is not equivalent to a + unit-level Condition…= or Assert…= check failing. Because + ExecCondition= runs as part of the activation transition, a skip causes the unit to + transition from active to inactive, and consequently + SuccessAction= (see + systemd.unit5) will be + honored. By contrast, Condition…= directives in the [Unit] section + prevent activation entirely and therefore do not trigger SuccessAction=. + The same recommendations about not running long-running processes in ExecStartPre= also applies to ExecCondition=. ExecCondition= will also run the commands in ExecStopPost=, as part of stopping the service, in the case of any non-zero or abnormal @@ -633,6 +642,22 @@ RestartMaxDelaySec=160s + + RestartRandomizedDelaySec= + Delay automatic restarts by a randomly selected, evenly distributed amount of time + between 0 and the specified time value, added on top of the delay otherwise configured via + RestartSec= (and RestartSteps=/RestartMaxDelaySec=, + if used). Takes a value in the same format as RestartSec=. Defaults to 0, indicating + that no randomized delay shall be applied. + + This setting is useful to stretch out the restarts of similarly configured service instances that + fail at the same time, to prevent them from restarting simultaneously and possibly resulting in + resource congestion. It is the restart-side analogue of RandomizedDelaySec= in + systemd.timer5. + + + + TimeoutStartSec= Configures the time to wait for start-up. If a daemon service does not signal @@ -1245,14 +1270,42 @@ RestartMaxDelaySec=160s FileDescriptorStorePreserve= Takes one of no, yes, - restart and controls when to release the service's file descriptor store - (i.e. when to close the contained file descriptors, if any). If set to no the - 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. + restart, on-success and controls when to release the + service's file descriptor store (i.e. when to close the contained file descriptors, if any). If set + to no the 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 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. If on-success + the behaviour is identical to yes, except that the file descriptor store is + discarded if the unit enters the permanent failed state (i.e. once all automated + restart attempts driven by Restart= have been exhausted). The store is preserved + across the transitionary failed states that precede each individual auto-restart attempt. + + When set to yes or on-success, and the service is + itself running under another service manager (e.g. a service of user@.service, + or a payload inside + systemd-nspawn1), + file descriptors pushed into the store are also forwarded one level up via the enveloping manager's + $NOTIFY_SOCKET, tagged with the originating unit id, so that they are preserved + across restarts of the inner manager and handed back to the originating unit when it is started + again. For this to take effect, the enveloping unit must itself enable + FileDescriptorStoreMax= and a non-no/restart + value for FileDescriptorStorePreserve=. + See the File Descriptor Store + overview for details. + + Setting this to yes or on-success also ensures the + file descriptor store is kept loaded across a kexec-based reboot on kernels + supporting the Live Update Orchestrator, + so that compatible file descriptors (such as memfd_create2) + are preserved and handed back to the service on the other side. See the File Descriptor Store overview for + details. Use systemctl clean --what=fdstore … to release the file descriptor store explicitly. @@ -1260,6 +1313,42 @@ RestartMaxDelaySec=160s + + LUOSession= + Takes a whitespace-separated list of names. For each name, when the service is + started the service manager creates a LUO session via the Live Update Orchestrator (LUO, + i.e. /dev/liveupdate) and hands the resulting session file descriptor to the + service through the file descriptor store, using the configured name as its + FDNAME=. The session is hence passed to the service's processes via + sd_listen_fds3, + like any other file descriptor store entry. The service may then preserve additional file + descriptors (such as memfd_create2 + memory file descriptors) in the session, which survive a kexec-based reboot and + can be retrieved again on the other side. + + A session is only handed out if one with the same name is not already present in the service's + file descriptor store. This way a session handed out on first start, or restored across a + kexec, is reused on the next start rather than replaced. The kernel-level session + name is derived deterministically from the unit name and the configured name, so that it remains + stable and unique, and it fits within the length limit imposed by the kernel. + + This setting implies FileDescriptorStoreMax= is set to at least the number + of configured sessions. To ensure the sessions are kept pinned and preserved across a + kexec, combine this with FileDescriptorStorePreserve=yes (see + above). If /dev/liveupdate is not available no session is handed out, and the + service is started normally. This setting may be specified more than once, in which case the lists + are combined. If the empty string is assigned the list is reset and all prior assignments have no + effect. + + For further information on the file descriptor store see the File Descriptor Store overview. + + + + + USBFunctionDescriptors= Configure the location of a file containing @@ -1529,7 +1618,7 @@ ExecStart=/bin/echo ${ONE} ${TWO} ${THREE} ExecStart=/bin/echo $ONE $TWO $THREE This results in /bin/echo being called twice, the first time with arguments - 'one', + one, 'two two' too, , and the second time with arguments one, two two, diff --git a/man/systemd.socket.xml b/man/systemd.socket.xml index 0de281f02eca7..6816b073b3652 100644 --- a/man/systemd.socket.xml +++ b/man/systemd.socket.xml @@ -675,6 +675,40 @@ + + XAttrEntryPoint= + XAttrListen= + XAttrAccept= + Set an extended attribute on the socket, in the form + NAME=VALUE. The extended + attribute's name must be in the user. namespace, i.e. begin with the four + characters user.. Specifiers are expanded in both the name and the value, see + systemd.unit5 for + details on the available specifiers. These settings may be used more than once to set multiple + extended attributes; assigning the empty string resets the list. + + The three variables control where the extended attributes are applied: + XAttrEntryPoint= sets them on the file-system inode an + AF_UNIX socket is bound to (i.e. the socket node visible in the file system at + the specified listening path; this only applies to AF_UNIX sockets), + XAttrListen= sets them on the listening socket, and + XAttrAccept= sets them on the connection sockets accepted off the listening socket + (and is thus only relevant in combination with + Accept=yes). + + This is primarily useful to tag sockets so that they can be discovered and classified by other + tools. For example, Varlink entrypoint sockets are supposed to be tagged with + user.varlink=entrypoint via XAttrEntryPoint=, which makes them + discoverable via varlinkctl list-sockets, see + varlinkctl1. + + These settings require kernel support for extended attributes on socket inodes (available since + Linux 7.0). Extended attributes that cannot be applied are logged at debug level and otherwise + ignored. + + + + SELinuxContextFromNet= Takes a boolean argument. When true, systemd 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..eba06bc9ca26b 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,33 @@ + + 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. The value may use the wildcard + patterns documented in + hostname5 (e.g. + $-$ or foo-????). When the credential is applied on the running + system (the usual case, by systemd-firstboot.service on first boot), the wildcards + are resolved against the machine ID and the resulting concrete name is written to + /etc/hostname. The name is thus persisted once at first boot and + does not change on subsequent boots even if the word lists are later updated or the pattern is changed + (for example to add more ?/$ tokens for additional entropy), which is the recommended + way to obtain a stable, per-machine generated hostname. (When + systemd-firstboot1 + operates on an offline image via /, the target's + machine ID is not yet known, so the pattern is written verbatim and, like a pattern placed directly in + /etc/hostname, re-derived on every boot rather than persisted.) + + + + + firstboot.locale firstboot.locale-messages @@ -77,6 +104,21 @@ + + firstboot.machine-tags + + The machine tags to set, as a colon-separated list (e.g. + webserver:frontend:berlin). Read by + systemd-firstboot1, + and only honoured if /etc/machine-info has not been configured before. Written + into the TAGS= field of that file, see + machine-info5 for + details. + + + + + login.issue @@ -398,9 +440,18 @@ 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. The value may use the wildcard patterns documented + in hostname5 + (? and $), which are expanded deterministically from the + machine ID when the credential is applied. As this is a transient hostname that is re-applied on + every boot, a wildcard value is re-derived each boot (and may change if the word lists change) and + is never persisted; pass an already-resolved name if a stable hostname is required. Note also that + the word-list tokens require the word lists to be present, which is generally not the case in the + initrd so during initrd the default hostname is used and in late boot the resolved one becomes + available. @@ -565,6 +616,16 @@ + + + imds.* + + + Read by systemd-imdsd@.service8. + + + + diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 37022ecc1c3aa..347fc816d090e 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -281,6 +281,16 @@ generally ignored. This includes names that start with a . or end with a .ignore. + When unit aliasing is introduced during reload/reexec (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 migrated to a new stub orphaned unit 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 resources such as running processes, they will now be tracked + under the new orphaned unit. Once all resources are gone (e.g. all processes have exited) the orphaned + unit will be garbage collected automatically. + The unit file format is covered by the Interface Portability and Stability Promise. @@ -1082,6 +1092,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. + @@ -1100,6 +1115,16 @@ allowed. In user mode, only , , and are allowed. Both options default to . + These actions are tied to the unit's state transitions and fire only when the unit actually + transitions out of an active or activating state. As a + consequence, Condition…= and Assert…= directives that fail do + not trigger SuccessAction= or + FailureAction=: they prevent activation in the first place, so no state transition + occurs. By contrast, the ExecCondition= directive in + systemd.service5 + runs as part of activation, so an ExecCondition= skip will + trigger SuccessAction=. + If is set, no action will be triggered. causes a reboot following the normal shutdown procedure (i.e. equivalent to systemctl reboot). causes a forced reboot which will terminate all @@ -1452,6 +1477,38 @@ + + ConditionFraction= + + ConditionFraction= may be used to enable a unit on a stable, + pseudo-random subset of a fleet of machines. It is primarily useful for staged rollouts: the same + unit (or drop-in) is distributed to every machine in a fleet, but only the configured fraction of + them will actually have it enabled. The decision is derived locally from the machine ID (see + machine-id5), so + it requires no central coordination and is stable over time: a given machine always lands on the + same side of the threshold. + + The argument consists of an optional tag followed by a percentage, + separated by whitespace, for example 30% or + myrollout 30%. The percentage may include up to two decimal places (e.g. + 0.5%). The condition is satisfied on approximately the configured percentage of + all machines; 0% matches no machine and 100% matches every + machine. + + The optional tag is an arbitrary string (not containing whitespace) that is mixed into the + derivation, so that independent rollouts select independent subsets of the + fleet. Without it, all untagged ConditionFraction= checks would select the very + same machines (the same machines would always be picked first). Use distinct tags for unrelated + rollouts, and a shared tag to deliberately target the same machines across several units. + + The test may be negated by prepending an exclamation mark, in which case it is satisfied on the + complementary fraction of machines (e.g. !myrollout 30% matches the other ≈70%). + If the machine ID cannot be determined, the condition fails. + + + + + ConditionKernelCommandLine= @@ -1605,6 +1662,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. + @@ -2007,6 +2068,36 @@ + + ConditionMachineTag= + + ConditionMachineTag= may be used to match against the tags + assigned to the local machine. Machine tags are short labels that classify and group machines for + management purposes; they are configured in the TAGS= field of + machine-info5 and + may be queried and changed with the tags command of + hostnamectl1. The + argument is a single tag pattern, which is compared against each of the configured tags using + shell-style globbing (*, ?, []). The + condition is satisfied if at least one of the configured tags matches the pattern. The test may be + negated by prepending an exclamation mark, in which case it is satisfied if none of the configured + tags matches. + + Tags may be parameterized with a value in the form + key=value; the + = and the value are part of the tag and thus part of the string the pattern is + matched against. Hence ConditionMachineTag=role=webserver matches the tag + role=webserver exactly, ConditionMachineTag=role=* matches any + value assigned to the role key, and ConditionMachineTag=role + (without =) does not match role=webserver. + See + machine-info5 + for the precise syntax of machine tags. + + + + + ConditionMemoryPressure= ConditionCPUPressure= @@ -2046,6 +2137,7 @@ AssertArchitecture= AssertVirtualization= AssertHost= + AssertFraction= AssertKernelCommandLine= AssertKernelVersion= AssertVersion= @@ -2074,6 +2166,7 @@ AssertCPUs= AssertCPUFeature= AssertOSRelease= + AssertMachineTag= AssertMemoryPressure= AssertCPUPressure= AssertIOPressure= @@ -2370,11 +2463,7 @@ Credentials directory This is the value of the $CREDENTIALS_DIRECTORY environment variable if available. See section "Credentials" in systemd.exec5 for more information. - - %D - Shared data directory - This is either /usr/share/ (for the system manager) or the path $XDG_DATA_HOME resolves to (for user managers). - + %E Configuration directory root diff --git a/man/systemd.xml b/man/systemd.xml index 06d9102e475f1..557cd76648212 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -994,12 +994,11 @@ emergency rd.emergency - -b - Boot into emergency mode. This is equivalent - to systemd.unit=emergency.target or - rd.systemd.unit=emergency.target, respectively, and - provided for compatibility reasons and to be easier to type. + Boot into emergency mode. Those options are equivalent to + systemd.unit=emergency.target or + rd.systemd.unit=emergency.target, respectively. They were initially + provided for compatibility with SysV, but are retained as convenient shortcuts. @@ -1008,29 +1007,24 @@ rescue rd.rescue single - s - S 1 - Boot into rescue mode. This is equivalent to - systemd.unit=rescue.target or - rd.systemd.unit=rescue.target, respectively, and - provided for compatibility reasons and to be easier to type. + Boot into rescue mode. Those options are equivalent to either + systemd.unit=rescue.target or rd.systemd.unit=rescue.target. + They were initially provided for compatibility with SysV, but are retained as convenient + shortcuts. - 2 3 - 4 5 - Boot into the specified legacy SysV runlevel. - 2, 3, and 4 are equivalent to - systemd.unit=multi-user.target; and 5 is equivalent to - systemd.unit=graphical.target, and provided for compatibility reasons and to be - easier to type. + Shortcuts to boot into specific targets: 3 is equivalent to + systemd.unit=multi-user.target and 5 is equivalent to + systemd.unit=graphical.target. Those options were initially provided for + compatibility with SysV, but are retained as convenient shortcuts. @@ -1061,6 +1055,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/tmpfiles.d.xml b/man/tmpfiles.d.xml index 39fcad850d71e..00c6ecb39959d 100644 --- a/man/tmpfiles.d.xml +++ b/man/tmpfiles.d.xml @@ -76,6 +76,10 @@ a /path-or-glob/to/set/acls - - - - POSIX a+ /path-or-glob/to/append/acls - - - - POSIX ACLs A /path-or-glob/to/set/acls/recursively - - - - POSIX ACLs A+ /path-or-glob/to/append/acls/recursively - - - - POSIX ACLs +k /path-or-glob/to/set/caps - - - - file capabilities +k+ /path-or-glob/to/adjust/caps - - - - file capabilities +K /path-or-glob/to/set/caps/recursively - - - - file capabilities +K+ /path-or-glob/to/adjust/caps/recursively - - - - file capabilities @@ -484,6 +488,37 @@ L /tmp/foobar - - - - /dev/null + + + k + k+ + Set file capabilities, see capabilities7. + Lines of this type accept shell-style globs in place of normal path names. Does not follow + symlinks. + + The syntax follows cap_text_formats7. It + also supports rootuid=INT for the user namespace root + user ID. + + If suffixed with +, current capabilities on the file that are not touched by the expression + will be kept. For example, if all cap_setuid capabilities need to be removed but + others should be kept, one can use k+ with cap_setuid= or + cap_setuid-eip. + + + + + + K + K+ + Same as k and + k+, but recursive. Does not follow + symlinks. + + + @@ -565,8 +600,8 @@ w- /proc/sys/vm/swappiness - - - - 10 -, the default is used: 0755 for directories, 0644 for all other file objects. For z, Z lines, if omitted or when set to -, the file access mode will not be modified. This parameter is ignored for x, - r, R, L, t, and - a lines. + r, R, L, t, + a, and k lines. Optionally, if prefixed with ~, the access mode is masked based on the already set access bits for existing file or directories: if the existing file has all executable bits unset, @@ -707,7 +742,8 @@ d /tmp/foo/bar - - - bmA:1h - suffixed by a newline. For C, specifies the source file or directory. For t and T, determines extended attributes to be set. For a and A, determines ACL attributes to be set. For h and H, - determines the file attributes to set. Ignored for all other lines. + determines the file attributes to set. For k and K, determines + file capabilities to be set. Ignored for all other lines. This field can contain specifiers, see below. @@ -742,6 +778,7 @@ d /tmp/foo/bar - - - bmA:1h - System or user cache directory In mode, this is the same as $XDG_CACHE_HOME, and /var/cache otherwise. + %g User group @@ -776,7 +813,7 @@ d /tmp/foo/bar - - - bmA:1h - %t System or user runtime directory - In mode, this is the same $XDG_RUNTIME_DIR, and /run/ otherwise. + In mode, this is the same as $XDG_RUNTIME_DIR, and /run/ otherwise. diff --git a/man/updatectl.xml b/man/updatectl.xml index c7b0dbd3096c0..a6da35b52f0ee 100644 --- a/man/updatectl.xml +++ b/man/updatectl.xml @@ -119,6 +119,7 @@ + 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..539b0ea33d76d 100644 --- a/man/varlinkctl.xml +++ b/man/varlinkctl.xml @@ -64,7 +64,7 @@ varlinkctl OPTIONS - --exec call + --exec call ADDRESS METHOD @@ -73,6 +73,26 @@ CMDLINE + + varlinkctl + OPTIONS + serve + METHOD + CMDLINE + + + + varlinkctl + OPTIONS + list-registry + + + + varlinkctl + OPTIONS + list-sockets + + varlinkctl OPTIONS @@ -181,6 +201,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 @@ -191,6 +233,23 @@ + + list-sockets + + Shows a list of listening AF_UNIX stream sockets on the local + system that are marked as Varlink entrypoints, along with whether the calling user has write (i.e. + connect) access to each of them. A socket is considered a Varlink entrypoint if its inode carries the + user.varlink extended attribute set to entrypoint. + + Sockets are enumerated via the kernel's sock_diag netlink interface, and + hence this is not restricted to sockets in /run/varlink/registry/ (as + list-registry is), but covers all file-system bound listening Varlink sockets, + regardless of their location. This requires kernel support for extended attributes on socket inodes, + which is available since Linux 7.0. + + + + validate-idl [FILE] @@ -269,6 +328,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 +592,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/vconsole.conf.xml b/man/vconsole.conf.xml index e5e160cf3d55d..20b30d39948f4 100644 --- a/man/vconsole.conf.xml +++ b/man/vconsole.conf.xml @@ -56,6 +56,13 @@ might be checked for configuration of the virtual console as well, however only as fallback. + If the boot loader reports the firmware-configured keyboard layout via the + LoaderKeyboardLayout EFI variable (see the + Boot Loader Interface), + it is used as the lowest-priority fallback for KEYMAP=. + Any setting from credentials, /etc/vconsole.conf, or the kernel + command line overrides it. + /etc/vconsole.conf is usually created and updated using systemd-localed.service8. 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..2a99930058c64 100644 --- a/meson.build +++ b/meson.build @@ -15,7 +15,8 @@ project('systemd', 'c', add_test_setup( 'default', - exclude_suites : ['clang-tidy', 'unused-symbols', 'integration-tests'], + exclude_suites : ['clang-tidy', 'coccinelle', 'unused-symbols', 'integration-tests'], + exe_wrapper : find_program('tools/test-crash-trace.sh'), is_default : true, ) @@ -26,8 +27,8 @@ else project_minor_version = '0' endif -libsystemd_version = '0.43.0' -libudev_version = '1.7.13' +libsystemd_version = '0.44.0' +libudev_version = '1.7.14' conf = configuration_data() conf.set_quoted('PROJECT_URL', 'https://systemd.io/') @@ -37,10 +38,17 @@ conf.set_quoted('PROJECT_VERSION_STR', project_major_version, description: 'Stringified project version (used where a simple string is expected)') conf.set_quoted('PROJECT_VERSION_FULL', meson.project_version(), description : 'Full project version') -relative_source_path = run_command('realpath', - '--relative-to=@0@'.format(meson.project_build_root()), - meson.project_source_root(), - check : true).stdout().strip() +fs = import('fs') + +if meson.version().version_compare('>=1.3.0') + relative_source_path = fs.relative_to(meson.project_source_root(), + meson.project_build_root()) +else + relative_source_path = run_command('realpath', + '--relative-to=@0@'.format(meson.project_build_root()), + meson.project_source_root(), + check : true).stdout().strip() +endif conf.set_quoted('RELATIVE_SOURCE_PATH', relative_source_path) conf.set10('BUILD_MODE_DEVELOPER', get_option('mode') == 'developer', @@ -70,6 +78,9 @@ conf.set10('FUZZ_USE_SIZE_LIMIT', fuzzer_build) # We'll set this to '1' for EFI builds in a different place. conf.set10('SD_BOOT', false) +link_executor_shared = get_option('link-executor-shared') +conf.set10('BUILD_EXECUTOR_SINGLE', link_executor_shared == 'single') + # Create a title-less summary section early, so it ends up first in the output. # More items are added later after they have been detected. summary({ @@ -79,7 +90,6 @@ summary({ ##################################################################### -fs = import('fs') if get_option('split-bin') == 'auto' split_bin = not fs.is_symlink('/usr/sbin') else @@ -291,6 +301,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', libexecdir / '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) @@ -420,10 +431,16 @@ possible_common_cc_flags = [ '-Wno-string-plus-int', # clang '-fdiagnostics-show-option', + '-fexcess-precision=standard', '-fno-common', '-fstack-protector', '-fstack-protector-strong', '-fstrict-flex-arrays=3', + # We don't read errno from any libm call, and the math-errno fallback + # forces a DT_NEEDED on libm via the cold error path even when the hot + # path is a single hardware instruction (sqrtsd, etc.). Drop it so the + # builtins lower to pure hardware instructions. + '-fno-math-errno', '--param=ssp-buffer-size=4', ] @@ -497,6 +514,11 @@ if get_option('mode') == 'developer' possible_cc_flags += '-fno-omit-frame-pointer' endif +# Work around stack alignment issues observed with musl + libucontext on i386. +if get_option('libc') == 'musl' and host_machine.cpu_family() == 'x86' + possible_cc_flags += '-mstackrealign' +endif + add_project_arguments( cc.get_supported_arguments( basic_disabled_warnings, @@ -538,6 +560,36 @@ 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) + +# The "R" (SHF_GNU_RETAIN) section flag is only understood by binutils >= 2.36. +# TODO: drop when support for CentOS 9 is dropped. +if cc.compiles( + '__asm__(".pushsection .note.test, \\"aR\\", %note\\n.popsection\\n");', + name : 'assembler supports the "R" (SHF_GNU_RETAIN) section flag') + conf.set_quoted('_SD_ELF_NOTE_DLOPEN_SECTION_FLAGS', 'aGR') +else + conf.set_quoted('_SD_ELF_NOTE_DLOPEN_SECTION_FLAGS', 'aG') +endif + ##################################################################### # compilation result tests @@ -562,41 +614,6 @@ long_max = cc.compute_int( assert(long_max > 100000) conf.set_quoted('LONG_MAX_STR', f'@long_max@') -foreach ident : [ - ['renameat2', '''#include '''], # since musl-1.2.6 - ['set_mempolicy', '''#include '''], # declared at numaif.h provided by libnuma, which we do not use - ['get_mempolicy', '''#include '''], # declared at numaif.h provided by libnuma, which we do not use - ['epoll_pwait2', '''#include '''], # since glibc-2.35 - ['fsconfig', '''#include '''], # since glibc-2.36 - ['fsmount', '''#include '''], # since glibc-2.36 - ['fsopen', '''#include '''], # since glibc-2.36 - ['mount_setattr', '''#include '''], # since glibc-2.36 - ['move_mount', '''#include '''], # since glibc-2.36 - ['open_tree', '''#include '''], # since glibc-2.36 - ['pidfd_open', '''#include '''], # since glibc-2.36 - ['pidfd_send_signal', '''#include '''], # since glibc-2.36 - ['pidfd_spawn', '''#include '''], # since glibc-2.39 - ['sched_setattr', '''#include '''], # since glibc-2.41 - ['ioprio_get', '''#include '''], # no known header declares ioprio_get - ['ioprio_set', '''#include '''], # no known header declares ioprio_set - ['rt_tgsigqueueinfo', '''#include '''], # no known header declares rt_tgsigqueueinfo - ['open_tree_attr', '''#include '''], # no known header declares open_tree_attr - ['quotactl_fd', '''#include '''], # no known header declares quotactl_fd - ['fchmodat2', '''#include '''], # no known header declares fchmodat2 - ['bpf', '''#include '''], # no known header declares bpf - ['kcmp', '''#include '''], # no known header declares kcmp - ['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 - ['setxattrat', '''#include '''], # no known header declares setxattrat - ['removexattrat', '''#include '''], # no known header declares removexattrat - ['pivot_root', '''#include '''], # no known header declares pivot_root -] - - have = cc.has_function(ident[0], prefix : ident[1], args : '-D_GNU_SOURCE') - conf.set10('HAVE_' + ident[0].to_upper(), have) -endforeach - ##################################################################### awk = find_program('awk') @@ -895,6 +912,7 @@ foreach option : ['adm-gid', 'video-gid', 'wheel-gid', 'systemd-journal-gid', + 'systemd-imds-uid', 'systemd-network-uid', 'systemd-resolve-uid', 'systemd-timesync-uid'] @@ -992,25 +1010,13 @@ endif ##################################################################### -threads = dependency('threads') -librt = cc.find_library('rt') libm = cc.find_library('m') -libdl = cc.find_library('dl') - -# On some distributions that use musl (e.g. Alpine), libintl.h may be provided by gettext rather than musl. -# In that case, we need to explicitly link with libintl.so. -if cc.has_function('dgettext', - prefix : '''#include ''', - args : '-D_GNU_SOURCE') - libintl = [] -else - libintl = cc.find_library('intl') - if not cc.has_function('dgettext', - prefix : '''#include ''', - args : '-D_GNU_SOURCE', - dependencies : libintl) - error('dgettext() not found') - endif + +# Header presence check only — dgettext itself is resolved via dlopen_libintl() at runtime, so we never +# link against libintl. On glibc dgettext lives in libc; on musl gettext-dev provides libintl.h alongside +# libintl.so.8 which we dlopen() if present. +if not cc.has_header('libintl.h') + error('libintl.h not found (install gettext / gettext-dev)') endif # On some architectures, libatomic is required. But on some installations, @@ -1037,6 +1043,14 @@ else endif conf.set10('HAVE_LIBCRYPT', have) +# musl declares the ucontext.h functions but does not implement them; the fiber bootstrap in +# src/libsystemd/sd-future/fiber.c relies on them, so on musl we have to link to libucontext. +if get_option('libc') == 'musl' + libucontext = dependency('libucontext') +else + libucontext = [] +endif + bpf_framework = get_option('bpf-framework') bpf_compiler = get_option('bpf-compiler') libbpf = dependency('libbpf', @@ -1045,86 +1059,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')) @@ -1133,9 +1067,9 @@ conf.set10('HAVE_LIBMOUNT', have) libmount_cflags = libmount.partial_dependency(includes: true, compile_args: true) libfdisk = dependency('fdisk', - version : '>= 2.32', - disabler : true, + version : '>= 2.35', 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. @@ -1227,18 +1161,14 @@ if not libpam.found() # Debian older than bookworm and Ubuntu older than 22.10 do not provide the .pc file. libpam = cc.find_library('pam', required : feature) endif -libpam_misc = dependency('pam_misc', - required : feature.disabled() ? feature : false) -if not libpam_misc.found() - libpam_misc = cc.find_library('pam_misc', required : feature) -endif -conf.set10('HAVE_PAM', libpam.found() and libpam_misc.found()) +conf.set10('HAVE_PAM', libpam.found()) libpam_cflags = libpam.partial_dependency(includes: true, compile_args: true) 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') @@ -1259,21 +1189,10 @@ conf.set10('HAVE_LIBCRYPTSETUP', have) conf.set10('HAVE_LIBCRYPTSETUP_PLUGINS', libcryptsetup_plugins.allowed() and have) -foreach ident : [ - 'crypt_set_keyring_to_link', # 2.7 - 'crypt_token_set_external_path', # 2.7 - ] - - have_ident = have and cc.has_function( - ident, - prefix : '#include ', - dependencies : libcryptsetup) - conf.set10('HAVE_' + ident.to_upper(), have_ident) -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) @@ -1290,29 +1209,24 @@ conf.set10('HAVE_QRENCODE', libqrencode.found()) feature = get_option('gcrypt') libgcrypt = dependency('libgcrypt', - required : feature) -libgpg_error = dependency('gpg-error', - required : feature) - -have = libgcrypt.found() and libgpg_error.found() -if not have - # link to neither of the libs if one is not found - libgcrypt = [] - libgpg_error = [] - libgcrypt_cflags = [] -else + required : feature.disabled() ? feature : false) +conf.set10('HAVE_GCRYPT', libgcrypt.found()) +if libgcrypt.found() libgcrypt_cflags = libgcrypt.partial_dependency(includes: true, compile_args: true) +else + libgcrypt_cflags = [] endif -conf.set10('HAVE_GCRYPT', have) 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', @@ -1345,13 +1259,10 @@ libelf = dependency('libelf', libelf_cflags = libelf.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_ELFUTILS', libdw.found() and libelf.found()) -# New in elfutils 0.192 -conf.set10('HAVE_DWFL_SET_SYSROOT', - libdw.found() and cc.has_function('dwfl_set_sysroot', dependencies : libdw)) - 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 +1272,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')) @@ -1416,16 +1328,6 @@ libarchive = dependency('libarchive', libarchive_cflags = libarchive.partial_dependency(includes: true, compile_args: true) conf.set10('HAVE_LIBARCHIVE', libarchive.found()) -foreach ident : [ - 'archive_entry_gid_is_set', # since 3.7.3 - 'archive_entry_uid_is_set', # since 3.7.3 - 'archive_entry_hardlink_is_set', # since 3.7.5 - ] - - have = libarchive.found() and cc.has_function(ident, dependencies : libarchive) - conf.set10('HAVE_' + ident.to_upper(), have) -endforeach - libxkbcommon = dependency('xkbcommon', version : '>= 0.3.0', required : get_option('xkbcommon')) @@ -1446,11 +1348,15 @@ libgobject = dependency('gobject-2.0', libgio = dependency('gio-2.0', required : get_option('glib')) conf.set10('HAVE_GLIB', libglib.found() and libgobject.found() and libgio.found()) +libglib_cflags = libglib.partial_dependency(includes: true, compile_args: true) +libgobject_cflags = libgobject.partial_dependency(includes: true, compile_args: true) +libgio_cflags = libgio.partial_dependency(includes: true, compile_args: true) libdbus = dependency('dbus-1', version : '>= 1.3.2', required : get_option('dbus')) conf.set10('HAVE_DBUS', libdbus.found()) +libdbus_cflags = libdbus.partial_dependency(includes: true, compile_args: true) dbusdatadir = libdbus.get_variable(pkgconfig: 'datadir', default_value: datadir) / 'dbus-1' @@ -1528,6 +1434,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 @@ -1628,6 +1540,7 @@ foreach tuple : [ ['rfkill'], ['smack'], ['sysext'], + ['sysinstall'], ['sysusers'], ['timedated'], ['timesyncd'], @@ -1699,171 +1612,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', @@ -1896,82 +1644,11 @@ if have and efi_arch == 'x64' and cc.links(''' efi_cpu_family_alt = 'x86' endif -want_ukify = pymod.find_installation('python3', required: get_option('ukify'), modules : ['pefile']).found() +want_ukify = get_option('ukify').allowed() 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 +1656,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 +1700,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, @@ -2053,14 +1732,13 @@ system_includes = [ ), ] -if get_option('libc') == 'musl' - system_include_args = [ - '-isystem', meson.project_build_root() / 'src/include/musl', - '-isystem', meson.project_source_root() / 'src/include/musl', - ] + system_include_args +libc_include_dir = 'src/include' / get_option('libc') +system_include_args = [ + '-isystem', meson.project_build_root() / libc_include_dir, + '-isystem', meson.project_source_root() / libc_include_dir, +] + system_include_args - system_includes += include_directories('src/include/musl', is_system : true) -endif +system_includes += include_directories(libc_include_dir, is_system : true) basic_includes = [ include_directories( @@ -2072,11 +1750,12 @@ basic_includes = [ version_include, ] -libsystemd_includes = [basic_includes, include_directories( +libsystemd_includes = [include_directories( 'src/libsystemd/sd-bus', 'src/libsystemd/sd-common', 'src/libsystemd/sd-device', 'src/libsystemd/sd-event', + 'src/libsystemd/sd-future', 'src/libsystemd/sd-hwdb', 'src/libsystemd/sd-id128', 'src/libsystemd/sd-journal', @@ -2085,9 +1764,15 @@ libsystemd_includes = [basic_includes, include_directories( 'src/libsystemd/sd-network', 'src/libsystemd/sd-path', 'src/libsystemd/sd-resolve', - 'src/libsystemd/sd-varlink')] + 'src/libsystemd/sd-varlink'), basic_includes] -includes = [libsystemd_includes, include_directories('src/shared')] +includes = [ + include_directories( + 'src/shared', + 'src/bpf', + ), + libsystemd_includes, +] subdir('po') subdir('catalog') @@ -2111,9 +1796,7 @@ libsystemd = shared_library( link_with : [libc_wrapper_static, libbasic_static], link_whole : [libsystemd_static], - dependencies : [librt, - threads, - userspace], + dependencies : [libucontext, userspace], link_depends : libsystemd_sym, install : true, install_tag: 'libsystemd', @@ -2132,14 +1815,11 @@ if static_libsystemd != 'false' install_tag: 'libsystemd', install_dir : libdir, pic : static_libsystemd_pic, - dependencies : [libdl, - libgcrypt_cflags, - liblz4_cflags, + dependencies : [liblz4_cflags, libm, - librt, + libucontext, libxz_cflags, libzstd_cflags, - threads, userspace], c_args : libsystemd_c_args + (static_libsystemd_pic ? [] : ['-fno-PIC'])) @@ -2157,8 +1837,7 @@ libudev = shared_library( '-Wl,--version-script=' + libudev_sym_path], link_with : [libsystemd_static], link_whole : libudev_basic, - dependencies : [threads, - userspace], + dependencies : [userspace], link_depends : libudev_sym, install : true, install_tag: 'libudev', @@ -2179,8 +1858,7 @@ if static_libudev != 'false' install_tag: 'libudev', install_dir : libdir, link_depends : libudev_sym, - dependencies : [libmount, - libshared_deps, + dependencies : [libshared_deps, userspace], c_args : static_libudev_pic ? [] : ['-fno-PIC'], pic : static_libudev_pic) @@ -2276,10 +1954,6 @@ nss_template = { libshared_static, libbasic_static, ], - 'dependencies' : [ - librt, - threads, - ], 'install' : true, 'install_tag' : 'nss', 'install_dir' : libdir, @@ -2292,24 +1966,7 @@ pam_template = { libsystemd_static, libshared_static, ], - 'dependencies' : [ - # Note: our PAM modules also call dlopen_libpam() and use - # symbols acquired through that, hence the explicit dep here is - # strictly speaking unnecessary. We put it in place anyway, - # since for the PAM modules we cannot avoid libpam anyway, - # after all they are loaded *by* libpam, and hence there's no - # loss in having explicit deps here, but there's a win: it - # makes the deps more visible. - # - # (In case you wonder why we do dlopen_libpam() from the PAM - # modules in the first place: that's mostly so that all our PAM - # code (regardless if our PAM modules or our PAM consuming - # programs) can use the same helpers, which hence go via - # dlopen_libpam(). - libpam_misc, - libpam, - threads, - ], + 'dependencies' : libpam_cflags, 'install' : true, 'install_tag' : 'pam', 'install_dir' : pamlibdir, @@ -2341,6 +1998,7 @@ subdir('src/bootctl') subdir('src/busctl') subdir('src/cgls') subdir('src/cgtop') +subdir('src/clonesetup') subdir('src/coredump') subdir('src/creds') subdir('src/cryptenroll') @@ -2364,6 +2022,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') @@ -2412,10 +2071,12 @@ subdir('src/socket-activate') subdir('src/socket-proxy') subdir('src/ssh-generator') subdir('src/stdio-bridge') +subdir('src/storage') subdir('src/storagetm') subdir('src/sulogin-shell') subdir('src/sysctl') subdir('src/sysext') +subdir('src/sysinstall') subdir('src/system-update-generator') subdir('src/systemctl') subdir('src/sysupdate') @@ -2487,11 +2148,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 +2177,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 @@ -2641,6 +2309,7 @@ test_dlopen = executables_by_name.get('test-dlopen') nss_targets = [] pam_targets = [] +module_targets = [] foreach dict : modules name = dict.get('name') is_nss = name.startswith('nss_') @@ -2685,6 +2354,8 @@ foreach dict : modules implicit_include_directories : false, ) + module_targets += lib + if is_nss # We cannot use shared_module because it does not support version suffix. # Unfortunately shared_library insists on creating the symlink… @@ -2777,7 +2448,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 ############################################################ @@ -2788,6 +2459,7 @@ subdir('test') ##################################################################### subdir('docs/var-log') +subdir('hostname-wordlist') subdir('hwdb.d') subdir('man') subdir('modprobe.d') @@ -2866,7 +2538,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 +2631,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 +2775,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 +2841,7 @@ foreach tuple : [ ['homed'], ['hostnamed'], ['hwdb'], + ['imds'], ['importd'], ['initrd'], ['kernel-install'], @@ -3157,6 +2865,7 @@ foreach tuple : [ ['resolve'], ['rfkill'], ['sysext'], + ['sysinstall'], ['systemd-analyze', conf.get('ENABLE_ANALYZE') == 1], ['sysupdate'], ['sysupdated'], diff --git a/meson.version b/meson.version index b17ac6a71486d..939c58c9900c4 100644 --- a/meson.version +++ b/meson.version @@ -1 +1 @@ -260~rc1 +262~devel diff --git a/meson_options.txt b/meson_options.txt index c1af7ce237492..701272d13a06d 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -15,8 +15,8 @@ option('split-bin', type : 'combo', choices : ['auto', 'true', 'false'], description : 'sbin is not a symlink to bin') option('link-udev-shared', type : 'boolean', description : 'link systemd-udevd and its helpers to libsystemd-shared.so') -option('link-executor-shared', type : 'boolean', - description : 'link systemd-executor to libsystemd-shared.so and libsystemd-core.so') +option('link-executor-shared', type : 'combo', choices : ['true', 'false', 'single'], + description : 'link systemd-executor to libsystemd-shared.so and libsystemd-core.so, or into systemd') option('link-systemctl-shared', type: 'boolean', description : 'link systemctl against libsystemd-shared.so') option('link-networkd-shared', type: 'boolean', @@ -109,6 +109,8 @@ option('sysupdate', type : 'feature', deprecated : { 'true' : 'enabled', 'false' option('sysupdated', type: 'combo', value : 'auto', choices : ['auto', 'enabled', 'disabled'], description : 'install the systemd-sysupdated service') +option('sysinstall', type : 'boolean', + description : 'install the systemd-sysinstall tool') option('coredump', type : 'boolean', description : 'install the coredump handler') @@ -142,6 +144,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' }, @@ -242,6 +248,8 @@ option('configfiledir', type : 'string', value : '', option('fallback-hostname', type : 'string', value : 'localhost', description : 'the hostname used if none configured') +option('hostname-wordlist', type : 'boolean', value : false, + description : 'install default word lists for $ hostname wildcards') option('extra-net-naming-schemes', type : 'string', description : 'comma-separated list of extra net.naming_scheme= definitions') option('default-net-naming-scheme', type : 'string', value : 'latest', @@ -334,6 +342,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') @@ -377,7 +387,7 @@ option('dns-over-tls', type : 'combo', choices : ['auto', 'gnutls', 'openssl', ' description : 'DNS-over-TLS support') option('dns-servers', type : 'string', description : 'space-separated list of default DNS servers', - value : '1.1.1.1#cloudflare-dns.com 8.8.8.8#dns.google 9.9.9.9#dns.quad9.net 1.0.0.1#cloudflare-dns.com 8.8.4.4#dns.google 149.112.112.112#dns.quad9.net 2606:4700:4700::1111#cloudflare-dns.com 2001:4860:4860::8888#dns.google 2620:fe::fe#dns.quad9.net 2606:4700:4700::1001#cloudflare-dns.com 2001:4860:4860::8844#dns.google 2620:fe::9#dns.quad9.net') + value : '1.1.1.1#one.one.one.one 8.8.8.8#dns.google 9.9.9.9#dns.quad9.net 1.0.0.1#one.one.one.one 8.8.4.4#dns.google 149.112.112.112#dns.quad9.net 2606:4700:4700::1111#one.one.one.one 2001:4860:4860::8888#dns.google 2620:fe::fe#dns.quad9.net 2606:4700:4700::1001#one.one.one.one 2001:4860:4860::8844#dns.google 2620:fe::9#dns.quad9.net') option('ntp-servers', type : 'string', description : 'space-separated list of default NTP servers', value : 'time1.google.com time2.google.com time3.google.com time4.google.com') diff --git a/mkosi.extra/usr/lib/repart.d/root.conf b/mkosi.extra/usr/lib/repart.d/root.conf deleted file mode 100644 index 1aadd2dc7014c..0000000000000 --- a/mkosi.extra/usr/lib/repart.d/root.conf +++ /dev/null @@ -1,2 +0,0 @@ -[Partition] -Type=root diff --git a/mkosi/mkosi.clangd b/mkosi/mkosi.clangd index 7cac6cecbfd0a..c1971cff258af 100755 --- a/mkosi/mkosi.clangd +++ b/mkosi/mkosi.clangd @@ -13,5 +13,5 @@ exec "${SPAWN[@]}" \ clangd \ --compile-commands-dir=build \ --path-mappings="\ -$(pwd)/mkosi.tools/usr/include=/usr/include" \ +$(pwd)/mkosi/mkosi.tools/usr/include=/usr/include" \ "$@" diff --git a/mkosi/mkosi.conf b/mkosi/mkosi.conf index 80c4e59390c95..505dc0bd5d204 100644 --- a/mkosi/mkosi.conf +++ b/mkosi/mkosi.conf @@ -1,7 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later [Config] -MinimumVersion=commit:66d51024b7149f40be4702e84275c936373ace97 +MinimumVersion=commit:f7762b71437227922a367bb89597843c77494ef9 Dependencies= minimal-base minimal-0 @@ -55,12 +55,10 @@ ExtraTrees= %O/minimal-1.root-%a-verity.raw:/usr/share/minimal_1.verity %O/minimal-1.root-%a-verity-sig.raw:/usr/share/minimal_1.verity.sig %O/minimal-base:/usr/share/TEST-13-NSPAWN-container-template - %O/initrd:/exitrd 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 +88,6 @@ Packages= attr bash-completion binutils - coreutils cpio curl diffutils @@ -103,7 +100,7 @@ Packages= gzip jq kbd - kexec-tools + keyutils kmod less lsof @@ -123,6 +120,7 @@ Packages= sed socat strace + swtpm tar tree util-linux @@ -143,7 +141,6 @@ Credentials= tty.virtual.tty1.agetty.autologin=root tty.virtual.tty1.login.noauth=yes RuntimeBuildSources=yes -CPUs=2 TPM=yes VSock=yes KVM=yes diff --git a/mkosi/mkosi.conf.d/arch/mkosi.conf b/mkosi/mkosi.conf.d/arch/mkosi.conf index 9bea621fcaa60..e72b6ee259ada 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 @@ -28,7 +29,7 @@ Packages= iproute iputils knot - libucontext + liburing linux man-db multipath-tools @@ -41,6 +42,7 @@ Packages= pkgconf polkit procps-ng + python-packaging python-pexpect python-psutil qrencode @@ -54,3 +56,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..60b52445d5581 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,7 +43,8 @@ Packages= kernel-core knot libcap-ng-utils - libucontext + libmicrohttpd + liburing man-db nmap-ncat openssh-clients @@ -54,6 +56,7 @@ Packages= policycoreutils polkit procps-ng + python3-packaging python3-pexpect # needed to upgrade and downgrade systemd-ukify in tests python3-zstd @@ -63,8 +66,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..1e516a4220a42 100644 --- a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf +++ b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf @@ -20,13 +20,16 @@ VolatilePackages= systemd-cryptsetup systemd-dev systemd-homed + systemd-imds systemd-journal-remote systemd-oomd systemd-repart + systemd-report systemd-resolved systemd-sysv systemd-tests systemd-timesyncd + systemd-tpm systemd-ukify systemd-userdbd udev @@ -50,6 +53,7 @@ Packages= libcap-ng-utils libdw-dev libdw1 + liburing2 locales login man-db @@ -62,6 +66,7 @@ Packages= polkitd pkgconf procps + python3-packaging python3-pexpect python3-psutil # kernel-bootcfg --add-uri= is just too useful @@ -71,6 +76,8 @@ Packages= softhsm2 squashfs-tools stress-ng + swtpm-tools tgt tpm2-tools tzdata + virtiofsd diff --git a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/debug.conf b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/debug.conf index 78e5e78cef146..5fba94694a039 100644 --- a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/debug.conf +++ b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/debug.conf @@ -18,11 +18,14 @@ VolatilePackages= systemd-cryptsetup-dbgsym systemd-dbgsym systemd-homed-dbgsym + systemd-imds-dbgsym systemd-journal-remote-dbgsym systemd-oomd-dbgsym systemd-repart-dbgsym + systemd-report-dbgsym systemd-resolved-dbgsym systemd-tests-dbgsym systemd-timesyncd-dbgsym + systemd-tpm-dbgsym systemd-userdbd-dbgsym udev-dbgsym diff --git a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/isc-dhcp.conf b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/isc-dhcp.conf index 2977041fd98d5..d22865feaef8c 100644 --- a/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/isc-dhcp.conf +++ b/mkosi/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/isc-dhcp.conf @@ -2,15 +2,15 @@ [TriggerMatch] Distribution=debian -Release=bullseye -Release=bookworm -Release=trixie +Release=|bullseye +Release=|bookworm +Release=|trixie [TriggerMatch] Distribution=ubuntu -Release=jammy -Release=noble -Release=questing +Release=|jammy +Release=|noble +Release=|questing [Content] Packages=isc-dhcp-server 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..dc16d5823d572 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,7 +57,9 @@ Packages= libcap-progs libdw-devel libdw1 + libmicrohttpd12 libtss2-tcti-device0 + liburing2 libz1 man multipath-tools @@ -70,6 +73,7 @@ Packages= policycoreutils pkgconf procps4 + python3-packaging python3-pefile python3-pexpect python3-psutil @@ -83,6 +87,7 @@ Packages= softhsm squashfs stress-ng + system-user-bin tgt timezone tpm2.0-tools @@ -90,5 +95,6 @@ Packages= veritysetup # kernel-bootcfg --add-uri= is just too useful virt-firmware + virtiofsd xz zypper diff --git a/mkosi/mkosi.conf.d/postmarketos/mkosi.conf b/mkosi/mkosi.conf.d/postmarketos/mkosi.conf new file mode 100644 index 0000000000000..14881c5f36839 --- /dev/null +++ b/mkosi/mkosi.conf.d/postmarketos/mkosi.conf @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=postmarketos + +[Distribution] +Release=edge + +[Content] +VolatilePackages= + systemd + systemd-journald + systemd-libs + systemd-logind + systemd-networkd + systemd-resolved + systemd-tests + systemd-timesyncd + systemd-udevd + +Packages= + agetty + bind-tools + bpftool + coreutils + erofs-utils + execline + gnutls-utils + iproute2 + iputils + knot + knot-utils + libcap-ng-utils + libcap-utils + libmicrohttpd + linux-firmware-none + linux-stable + lvm2 + man-db + musl-locales + net-tools + nmap-ncat + openssh-client + openssh-server + openssl + perf + postmarketos-base + procps-ng + psmisc + py3-elftools + py3-pexpect + py3-psutil + py3-pytest + py3-zstd + quota-tools + sfdisk + shadow + softhsm + squashfs-tools + stress-ng + tpm2-tools + uutils-coreutils-groups + vim-common diff --git a/mkosi/mkosi.conf.d/postmarketos/mkosi.conf.d/debug.conf b/mkosi/mkosi.conf.d/postmarketos/mkosi.conf.d/debug.conf new file mode 100644 index 0000000000000..3e8b83cb2ec25 --- /dev/null +++ b/mkosi/mkosi.conf.d/postmarketos/mkosi.conf.d/debug.conf @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Environment=WITH_DEBUG=1 + +[Content] +VolatilePackages=systemd-dbg diff --git a/mkosi/mkosi.conf.d/opensuse/mkosi.conf.d/efi-debug.conf b/mkosi/mkosi.conf.d/postmarketos/mkosi.conf.d/efi.conf similarity index 58% rename from mkosi/mkosi.conf.d/opensuse/mkosi.conf.d/efi-debug.conf rename to mkosi/mkosi.conf.d/postmarketos/mkosi.conf.d/efi.conf index 9ef1833a884de..f2b88eaf1f2b2 100644 --- a/mkosi/mkosi.conf.d/opensuse/mkosi.conf.d/efi-debug.conf +++ b/mkosi/mkosi.conf.d/postmarketos/mkosi.conf.d/efi.conf @@ -1,11 +1,12 @@ # SPDX-License-Identifier: LGPL-2.1-or-later -[Match] -Environment=WITH_DEBUG=1 - [Match] Architecture=uefi [Content] +Packages= + sbsigntool + VolatilePackages= - systemd-boot-debuginfo + systemd-boot + systemd-ukify diff --git a/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/systemd/resolved.conf.d/disable-dnsovertls.conf b/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/systemd/resolved.conf.d/disable-dnsovertls.conf new file mode 100644 index 0000000000000..6bf042b43ecb7 --- /dev/null +++ b/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/systemd/resolved.conf.d/disable-dnsovertls.conf @@ -0,0 +1,8 @@ +# This overrides the following dropin file installed by postmarketos-base-systemd-resolved, +# which disables DNSOverTLS= and breaks integration tests for resolved. + +[Resolve] +# This requires a DNS server that supports it. Disabling it instead of setting +# to "opportunistic", since the latter can be defeated with a downgrade attack and +# that gives a false sense of security. +#DNSOverTLS=no diff --git a/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/systemd/resolved.conf.d/disable-mdns.conf b/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/systemd/resolved.conf.d/disable-mdns.conf new file mode 100644 index 0000000000000..360ca0104aa44 --- /dev/null +++ b/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/systemd/resolved.conf.d/disable-mdns.conf @@ -0,0 +1,7 @@ +# This overrides the following dropin file installed by postmarketos-base-systemd-resolved, +# which disables MulticastDNS= and breaks integration tests for resolved. + +[Resolve] +# CUPS doesn't work with systemd-resolved's mdns: +# https://github.com/OpenPrinting/libcups/issues/81 +#MulticastDNS=no diff --git a/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/sysusers.d/10-testuser.conf b/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/sysusers.d/10-testuser.conf new file mode 100644 index 0000000000000..80de899f3109d --- /dev/null +++ b/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/sysusers.d/10-testuser.conf @@ -0,0 +1,4 @@ +# postmarketOS does not support userdbd, so creating the test user via +# userdbd credentials does not work. We need to create the user entries +# in /etc/passwd and /etc/group using sysusers instead. +u testuser 4711 testuser /home/testuser /bin/bash diff --git a/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/tmpfiles.d/10-testuser.conf b/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/tmpfiles.d/10-testuser.conf new file mode 100644 index 0000000000000..10606b1c75f25 --- /dev/null +++ b/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/tmpfiles.d/10-testuser.conf @@ -0,0 +1,4 @@ +# postmarketOS does not support userdbd, so creating the test user via +# userdbd credentials does not work. We need to create the user's home +# directory using tmpfiles instead. +Q /home/testuser 0700 testuser testuser - - diff --git a/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/udev/udev.conf b/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/udev/udev.conf new file mode 100644 index 0000000000000..403a9cefa907c --- /dev/null +++ b/mkosi/mkosi.conf.d/postmarketos/mkosi.extra/usr/lib/udev/udev.conf @@ -0,0 +1,2 @@ +# This overrides udev.conf installed by postmarketos-base package, +# which sets udev_log=err and breaks TEST-17-UDEV.verify. 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..7cc69182a1145 --- /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..0c985d0bf031d 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 @@ -19,9 +16,16 @@ enable dbus-broker.service enable systemd-networkd.service enable systemd-networkd-wait-online.service -# systemd-resolved is disable by default on CentOS so make sure it is enabled. +# systemd-resolved is disabled by default on CentOS so make sure it is enabled. enable systemd-resolved.service +# systemd-time-wait-sync.service is enabled by default on postmarketOS. +# It pulls in time-sync.target, but the target is never reached because the +# test environment has no Internet connectivity. As a result, timer units +# cannot be started. Disable the service explicitly to allow timer units to +# start in the test environment. +disable systemd-time-wait-sync.service + # systemd-userdbd.socket is disabled by default on OpenSUSE enable systemd-userdbd.socket @@ -49,7 +53,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 @@ -64,3 +68,12 @@ disable multipathd.service # The socket unit is disabled by default in the upstream: # https://github.com/opensvc/multipath-tools/commit/346e9adda4e5f0ccfcbb7a3eccf5f96001d95027 disable multipathd.socket + +# rtkit-daemon.service randomly fails with the following error on postmarketOS: +# rtkit-daemon[54]: pthread_create failed: Resource temporarily unavailable +# The service is not necessary on our test suites, let's disable it. +disable rtkit-daemon.service + +# Disable postmarketOS specific zram swap service. +# Maybe better to replace it with zram-generator? +disable zram_swap.service diff --git a/mkosi/mkosi.extra/usr/lib/systemd/system/systemd-nspawn@.service.d/fdstore.conf b/mkosi/mkosi.extra/usr/lib/systemd/system/systemd-nspawn@.service.d/fdstore.conf new file mode 100644 index 0000000000000..3b023f7832175 --- /dev/null +++ b/mkosi/mkosi.extra/usr/lib/systemd/system/systemd-nspawn@.service.d/fdstore.conf @@ -0,0 +1,3 @@ +[Service] +FileDescriptorStoreMax=16 +FileDescriptorStorePreserve=yes diff --git a/mkosi/mkosi.extra/usr/lib/systemd/system/user@.service.d/fdstore.conf b/mkosi/mkosi.extra/usr/lib/systemd/system/user@.service.d/fdstore.conf new file mode 100644 index 0000000000000..8a0b417e97660 --- /dev/null +++ b/mkosi/mkosi.extra/usr/lib/systemd/system/user@.service.d/fdstore.conf @@ -0,0 +1,5 @@ +# For tests exercising the fd store we need the unit in the rootfs to have these +# settings, or the fdstore content will be dropped in the initrd -> rootfs transition +[Service] +FileDescriptorStoreMax=20 +FileDescriptorStorePreserve=yes diff --git a/mkosi/mkosi.functions b/mkosi/mkosi.functions index c1a4a1dee536a..32fcab70a654e 100644 --- a/mkosi/mkosi.functions +++ b/mkosi/mkosi.functions @@ -1,5 +1,32 @@ # SPDX-License-Identifier: LGPL-2.1-or-later +# This is intended to be included by mkosi.build.chroot, +# and sets the following variables and one common function: +# - $MKOSI_CFLAGS +# - $MKOSI_LDFLAGS +# - $MKOSI_MESON_OPTIONS + +MKOSI_CFLAGS="-O0 -g" +if ((LLVM)); then + # TODO: Remove -fno-sanitize-function when https://github.com/systemd/systemd/issues/29972 is fixed. + MKOSI_CFLAGS="$MKOSI_CFLAGS -shared-libasan -fno-sanitize=function" +fi + +MKOSI_LDFLAGS="" +if ((LLVM)) && [[ -n "$SANITIZERS" ]]; then + MKOSI_LDFLAGS="$MKOSI_LDFLAGS -Wl,-rpath=$(realpath "$(clang --print-runtime-dir)")" +fi + +MKOSI_MESON_OPTIONS="-D mode=developer -D vcs-tag=${VCS_TAG:-true} -D b_sanitize=${SANITIZERS:-none} -Dtime-epoch=1744207869" +if ((WIPE)) && [[ -d "$BUILDDIR/meson-private" ]]; then + MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS --wipe" +fi + +if ((COVERAGE)); then + MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS -D b_coverage=true" + MKOSI_CFLAGS="$MKOSI_CFLAGS -fprofile-dir=/coverage" +fi + make_sysext_unsigned() { if ! ((SYSEXT)); then return diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.build.chroot b/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.build.chroot index e6299ae3bac23..40d8e15a98982 100755 --- a/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.build.chroot +++ b/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.build.chroot @@ -11,25 +11,9 @@ if [[ ! -f "pkg/$PKG_SUBDIR/PKGBUILD" ]]; then exit 1 fi -MKOSI_CFLAGS="-O0 -g -Wp,-U_FORTIFY_SOURCE" -if ((LLVM)); then - # TODO: Remove -fno-sanitize-function when https://github.com/systemd/systemd/issues/29972 is fixed. - MKOSI_CFLAGS="$MKOSI_CFLAGS -shared-libasan -fno-sanitize=function" -fi - -MKOSI_LDFLAGS="" -if ((LLVM)) && [[ -n "$SANITIZERS" ]]; then - MKOSI_LDFLAGS="$MKOSI_LDFLAGS -Wl,-rpath=$(realpath "$(clang --print-runtime-dir)")" -fi +. mkosi/mkosi.functions -MKOSI_MESON_OPTIONS="-D mode=developer -D vcs-tag=${VCS_TAG:-true} -D b_sanitize=${SANITIZERS:-none} -Dtime-epoch=1744207869" -if ((WIPE)) && [[ -d "$BUILDDIR/meson-private" ]]; then - MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS --wipe" -fi -if ((COVERAGE)); then - MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS -D b_coverage=true" - MKOSI_CFLAGS="$MKOSI_CFLAGS -fprofile-dir=/coverage" -fi +MKOSI_CFLAGS="$MKOSI_CFLAGS -Wp,-U_FORTIFY_SOURCE" # Override the default options. We specifically disable "strip", "zipman" and "lto" as they slow down builds # significantly. OPTIONS= cannot be overridden on the makepkg command line so we append to /etc/makepkg.conf diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.conf b/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.conf index 59c7ed6cae9ab..c199288d94bd9 100644 --- a/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.conf +++ b/mkosi/mkosi.images/build/mkosi.conf.d/arch/mkosi.conf @@ -10,4 +10,4 @@ Packages= diffutils erofs-utils git - libucontext + liburing diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.build.chroot b/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.build.chroot index 1665d7eba3f4f..29eb23ac6e549 100755 --- a/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.build.chroot +++ b/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.build.chroot @@ -2,8 +2,6 @@ # SPDX-License-Identifier: LGPL-2.1-or-later set -e -. mkosi/mkosi.functions - if [[ "$1" == "clangd" ]]; then exit 0 fi @@ -13,6 +11,8 @@ if [[ ! -f "pkg/$PKG_SUBDIR/systemd.spec" ]]; then exit 1 fi +. mkosi/mkosi.functions + TS="${SOURCE_DATE_EPOCH:-$(date +%s)}" if [[ "$(rpm --eval "%{lua:print(rpm.vercmp('$(rpm --version | cut -d ' ' -f3)', '4.19.91'))}")" == "-1" ]]; then @@ -38,28 +38,10 @@ COMMON_MACRO_OVERRIDES=( ) # TODO: Drop -U_FORTIFY_SOURCE when we switch to CentOS Stream 10. -MKOSI_CFLAGS="-O0 -g -Wp,-U_FORTIFY_SOURCE" +MKOSI_CFLAGS="$MKOSI_CFLAGS -Wp,-U_FORTIFY_SOURCE" if ((WITH_DEBUG)); then MKOSI_CFLAGS="$MKOSI_CFLAGS -fdebug-prefix-map=../src=/usr/src/debug/systemd" fi -if ((LLVM)); then - # TODO: Remove -fno-sanitize-function when https://github.com/systemd/systemd/issues/29972 is fixed. - MKOSI_CFLAGS="$MKOSI_CFLAGS -shared-libasan -fno-sanitize=function" -fi - -MKOSI_LDFLAGS="" -if ((LLVM)) && [[ -n "$SANITIZERS" ]]; then - MKOSI_LDFLAGS="$MKOSI_LDFLAGS -Wl,-rpath=$(realpath "$(clang --print-runtime-dir)")" -fi - -MKOSI_MESON_OPTIONS="-D mode=developer -D vcs-tag=${VCS_TAG:-true} -D b_sanitize=${SANITIZERS:-none} -Dtime-epoch=1744207869" -if ((WIPE)) && [[ -d "$BUILDDIR/meson-private" ]]; then - MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS --wipe" -fi -if ((COVERAGE)); then - MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS -D b_coverage=true" - MKOSI_CFLAGS="$MKOSI_CFLAGS -fprofile-dir=/coverage" -fi ( shopt -s nullglob diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.conf b/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.conf index 35bc886f40c11..472e6b66927b1 100644 --- a/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.conf +++ b/mkosi/mkosi.images/build/mkosi.conf.d/centos-fedora/mkosi.conf @@ -11,7 +11,7 @@ Packages= gdb git-core libasan + liburing-devel libubsan - libucontext-devel rpm-build which diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot index 02d4c3e5cb9cf..6a26dbc7642af 100755 --- a/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot +++ b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.build.chroot @@ -11,25 +11,7 @@ if [[ ! -d "pkg/$PKG_SUBDIR/debian" ]]; then exit 1 fi -MKOSI_CFLAGS="-O0 -g" -if ((LLVM)); then - # TODO: Remove -fno-sanitize-function when https://github.com/systemd/systemd/issues/29972 is fixed. - MKOSI_CFLAGS="$MKOSI_CFLAGS -shared-libasan -fno-sanitize=function" -fi - -MKOSI_LDFLAGS="" -if ((LLVM)) && [[ -n "$SANITIZERS" ]]; then - MKOSI_LDFLAGS="$MKOSI_LDFLAGS -Wl,-rpath=$(realpath "$(clang --print-runtime-dir)")" -fi - -MKOSI_MESON_OPTIONS="-D mode=developer -D vcs-tag=${VCS_TAG:-true} -D b_sanitize=${SANITIZERS:-none} -Dtime-epoch=1744207869" -if ((WIPE)) && [[ -d "$BUILDDIR/meson-private" ]]; then - MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS --wipe" -fi -if ((COVERAGE)); then - MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS -D b_coverage=true" - MKOSI_CFLAGS="$MKOSI_CFLAGS -fprofile-dir=/coverage" -fi +. mkosi/mkosi.functions # We transplant the debian/ folder from the deb package sources into the upstream sources. mount --mkdir --bind "$SRCDIR/pkg/$PKG_SUBDIR/debian" "$SRCDIR"/debian diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf index b3fd0836597cf..d762bf861a193 100644 --- a/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf +++ b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf @@ -11,5 +11,6 @@ Packages= erofs-utils git-core ?exact-name(libclang-rt-dev) + liburing-dev dpkg-dev mount 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/build/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/linux-tools-generic.conf b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/linux-tools-generic.conf new file mode 100644 index 0000000000000..b777a41490928 --- /dev/null +++ b/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.conf.d/linux-tools-generic.conf @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# bpftool was untangled in resolute, needs linux-tools-generic in older releases + +[TriggerMatch] +Distribution=ubuntu +Release=|jammy +Release=|noble + +[Content] +Packages=linux-tools-generic diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.build.chroot b/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.build.chroot index abc6829b40b0a..14a69456cd198 100755 --- a/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.build.chroot +++ b/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.build.chroot @@ -2,8 +2,6 @@ # SPDX-License-Identifier: LGPL-2.1-or-later set -e -. mkosi/mkosi.functions - if [[ "$1" == "clangd" ]]; then exit 0 fi @@ -13,6 +11,8 @@ if [[ ! -f "pkg/$PKG_SUBDIR${GIT_SUBDIR:+/$GIT_SUBDIR}/systemd.spec" ]]; then exit 1 fi +. mkosi/mkosi.functions + TS="${SOURCE_DATE_EPOCH:-$(date +%s)}" # The openSUSE filelists hardcode the manpage compression extension. This causes rpmbuild errors since we @@ -39,19 +39,12 @@ fi VERSION="$(cat meson.version)" RELEASE="$(date "+%Y%m%d%H%M%S" --date "@$TS")" -MKOSI_CFLAGS="-O0 -g -Wp,-U_FORTIFY_SOURCE" +MKOSI_CFLAGS="$MKOSI_CFLAGS -Wp,-U_FORTIFY_SOURCE" if ((WITH_DEBUG)); then MKOSI_CFLAGS="$MKOSI_CFLAGS -fdebug-prefix-map=../src=/usr/src/debug/systemd" fi -if ((LLVM)); then - # TODO: Remove -fno-sanitize-function when https://github.com/systemd/systemd/issues/29972 is fixed. - MKOSI_CFLAGS="$MKOSI_CFLAGS -shared-libasan -fno-sanitize=function" -fi -MKOSI_LDFLAGS="$(rpm --eval "%{?build_ldflags}")" -if ((LLVM)) && [[ -n "$SANITIZERS" ]]; then - MKOSI_LDFLAGS="$MKOSI_LDFLAGS -Wl,-rpath=$(realpath "$(clang --print-runtime-dir)")" -fi +MKOSI_LDFLAGS="$(rpm --eval "%{?build_ldflags}") $MKOSI_LDFLAGS" # A macro can't have an empty body and currently opensuse does not specify any of its own linker flags so # set LDFLAGS to %{nil} if there are no linker flags. @@ -59,15 +52,6 @@ if [[ -z "${MKOSI_LDFLAGS// }" ]]; then MKOSI_LDFLAGS="%{nil}" fi -MKOSI_MESON_OPTIONS="-D mode=developer -D vcs-tag=${VCS_TAG:-true} -D b_sanitize=${SANITIZERS:-none} -Dtime-epoch=1744207869" -if ((WIPE)) && [[ -d "$BUILDDIR/meson-private" ]]; then - MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS --wipe" -fi -if ((COVERAGE)); then - MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS -D b_coverage=true" - MKOSI_CFLAGS="$MKOSI_CFLAGS -fprofile-dir=/coverage" -fi - # The opensuse spec tars up stuff in test/ and unpacks it in test/integration-tests, which we now use for our # own purposes, so let's get rid of that specific bit of logic in the opensuse spec until they've had a chance # to adapt. @@ -111,6 +95,7 @@ build() { $( ((MESON_VERBOSE)) || echo "--undefine=__meson_verbose") \ --define "meson_extra_configure_options $MKOSI_MESON_OPTIONS $MESON_OPTIONS" \ --define "__os_install_post /usr/lib/rpm/brp-suse %{nil}" \ + --define "ext_man %{nil}" \ --define "__elf_exclude_path ^/usr/lib/systemd/tests/unit-tests/.*$" \ --define "__script_requires %{nil}" \ --define "_find_debuginfo_dwz_opts %{nil}" \ @@ -136,10 +121,13 @@ if ! build; then exit 1 fi - grep -v ".debug" /tmp/unpackaged-files >>"pkg/$PKG_SUBDIR${GIT_SUBDIR:+/$GIT_SUBDIR}/files.systemd" + grep -v "\.debug" /tmp/unpackaged-files >>"pkg/$PKG_SUBDIR${GIT_SUBDIR:+/$GIT_SUBDIR}/files.systemd" build --noprep --nocheck fi +# The %sysusers_generate_pre macro generates these and leaves them behind, clean them up +rm -f systemd-network.pre systemd-resolve.pre + cp "$BUILDDIR"/*.rpm "$PACKAGEDIR" make_sysext_unsigned /var/tmp/BUILD/*/BUILDROOT diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.conf b/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.conf index 70a1b31b64196..184be849a1148 100644 --- a/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.conf +++ b/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.conf @@ -12,7 +12,9 @@ Packages= git-core grep gzip + liburing-devel patterns-base-minimal_base rpm-build sed + sysuser-tools xz diff --git a/mkosi/mkosi.images/build/mkosi.conf.d/postmarketos/mkosi.build.chroot b/mkosi/mkosi.images/build/mkosi.conf.d/postmarketos/mkosi.build.chroot new file mode 100755 index 0000000000000..4ba2af6b6b4dc --- /dev/null +++ b/mkosi/mkosi.images/build/mkosi.conf.d/postmarketos/mkosi.build.chroot @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -e + +if [[ "$1" == "clangd" ]]; then + exit 0 +fi + +if [[ ! -f "pkg/$PKG_SUBDIR${GIT_SUBDIR:+/$GIT_SUBDIR}/APKBUILD" ]]; then + echo "APKBUILD not found at pkg/$PKG_SUBDIR${GIT_SUBDIR:+/$GIT_SUBDIR}/APKBUILD, run mkosi once with -ff to make sure the APKBUILD is cloned" >&2 + exit 1 +fi + +. mkosi/mkosi.functions + +mkdir -p "$HOME/.abuild" +cat >"$HOME/.abuild/abuild.conf" <&2 + exit 1 +fi + +# shellcheck source=/dev/null +CARCH="$DISTRIBUTION_ARCHITECTURE" \ + _systemd_upstream=1 \ + . "$APKBUILD" + +# shellcheck disable=SC2086 +mkosi-install ${makedepends:?} 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/postmarketos.conf b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/postmarketos.conf new file mode 100644 index 0000000000000..8da2e8a19ddc6 --- /dev/null +++ b/mkosi/mkosi.images/minimal-base/mkosi.conf.d/postmarketos.conf @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=postmarketos + +[Content] +Packages= + coreutils + iproute2 + libcap-utils + net-tools + nmap-ncat + +VolatilePackages= + systemd-libs 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..7cc69182a1145 --- /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.images/minimal-base/mkosi.postinst b/mkosi/mkosi.images/minimal-base/mkosi.postinst index 6feaebc19a33f..3c85fe1b72090 100755 --- a/mkosi/mkosi.images/minimal-base/mkosi.postinst +++ b/mkosi/mkosi.images/minimal-base/mkosi.postinst @@ -16,3 +16,14 @@ chmod +x "$BUILDROOT/sbin/init" if [ ! -e "$BUILDROOT/etc/os-release" ]; then ln -s ../usr/lib/os-release "$BUILDROOT/etc/os-release" fi + +# For use in the minimal containers, only needs libsystemd and libc +if [[ -x "$BUILDDIR/test-fdstore" ]]; then + cp "$BUILDDIR/test-fdstore" "$BUILDROOT/usr/bin/test-fdstore" +elif [[ -x "$BUILDROOT/usr/lib/systemd/tests/unit-tests/manual/test-fdstore" ]]; then + cp "$BUILDROOT/usr/lib/systemd/tests/unit-tests/manual/test-fdstore" "$BUILDROOT/usr/bin/test-fdstore" +elif [[ -x /usr/lib/systemd/tests/unit-tests/manual/test-fdstore ]]; then + cp /usr/lib/systemd/tests/unit-tests/manual/test-fdstore "$BUILDROOT/usr/bin/test-fdstore" +else + echo >&2 "WARNING: test-fdstore binary not found, fdstore container tests will be skipped" +fi 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..ab648130601ea 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,10 @@ Distribution=|fedora [Content] PrepareScripts=%D/mkosi/mkosi.conf.d/centos-fedora/systemd.prepare Packages= + coreutils + cryptsetup-libs + 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..17b8638b021f2 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 @@ -19,4 +20,5 @@ VolatilePackages= systemd-container systemd-cryptsetup systemd-repart + systemd-tpm udev 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..b863f7b2d14d9 100644 --- a/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/opensuse.conf @@ -7,7 +7,10 @@ Distribution=opensuse PrepareScripts=%D/mkosi/mkosi.conf.d/opensuse/systemd.prepare Packages= btrfs-progs + coreutils kmod + libcryptsetup12 + policycoreutils tpm2.0-tools VolatilePackages= @@ -17,5 +20,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/postmarketos.conf b/mkosi/mkosi.initrd.conf/mkosi.conf.d/postmarketos.conf new file mode 100644 index 0000000000000..c2369a21cb7e4 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.conf.d/postmarketos.conf @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=postmarketos + +[Content] +Packages= + btrfs-progs + coreutils + device-mapper + e2fsprogs + musl-utils + tpm2-tools + # For TEST-92-TPM2-SWTPM, install following packages when + # https://github.com/systemd/systemd/pull/42760 + # https://gitlab.alpinelinux.org/alpine/aports/-/work_items/18293 + # are resolved. + #gnutls-utils + #kmod + #linux-firmware-none + #linux-stable + +VolatilePackages= + systemd + systemd-journald + systemd-libs + systemd-udevd 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..7cc69182a1145 --- /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-run-initramfs.service b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/initrd-run-initramfs.service new file mode 100644 index 0000000000000..b95397fa23687 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/initrd-run-initramfs.service @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Unit] +Description=Copy initrd contents to /run/initramfs to serve as exitrd +DefaultDependencies=no +AssertPathExists=/etc/initrd-release +After=initrd.target +Before=initrd-cleanup.service initrd-switch-root.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=mkdir -p /run/initramfs +ExecStart=cp -a --one-file-system /. /run/initramfs/ 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.initrd.conf/mkosi.extra/usr/lib/systemd/system/systemd-nspawn@.service.d/fdstore.conf b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/systemd-nspawn@.service.d/fdstore.conf new file mode 100644 index 0000000000000..3b023f7832175 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/systemd-nspawn@.service.d/fdstore.conf @@ -0,0 +1,3 @@ +[Service] +FileDescriptorStoreMax=16 +FileDescriptorStorePreserve=yes diff --git a/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/user@.service.d/fdstore.conf b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/user@.service.d/fdstore.conf new file mode 100644 index 0000000000000..311feddad0640 --- /dev/null +++ b/mkosi/mkosi.initrd.conf/mkosi.extra/usr/lib/systemd/system/user@.service.d/fdstore.conf @@ -0,0 +1,5 @@ +# For tests exercising the FD store we need the unit in the initrd to have these +# settings, or the fdstore content will be dropped in the initrd +[Service] +FileDescriptorStoreMax=20 +FileDescriptorStorePreserve=yes diff --git a/mkosi/mkosi.pkgenv/mkosi.conf.d/centos-fedora.conf b/mkosi/mkosi.pkgenv/mkosi.conf.d/centos-fedora.conf index 5bd63a6ce1f75..50ac21fa72766 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=9cb09470c9c5a437f8e9c1e0e449b87de83733eb 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..7877842ebbd1d 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=0b390d268323a49191a9a3bcc07a46b573c1e464 PKG_SUBDIR=debian diff --git a/mkosi/mkosi.pkgenv/mkosi.conf.d/opensuse.conf b/mkosi/mkosi.pkgenv/mkosi.conf.d/opensuse.conf index 6a2c8868f0547..d711106ff3644 100644 --- a/mkosi/mkosi.pkgenv/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.pkgenv/mkosi.conf.d/opensuse.conf @@ -8,5 +8,5 @@ Environment= GIT_URL=https://github.com/bmwiedemann/openSUSE GIT_SUBDIR=packages/s/systemd GIT_BRANCH=master - GIT_COMMIT=7d9cf5c934705c175766eaa688baa503da84e06a + GIT_COMMIT=462bd9f5eae8d113d0e477455278f64d0284afe8 PKG_SUBDIR=opensuse diff --git a/mkosi/mkosi.pkgenv/mkosi.conf.d/postmarketos.conf b/mkosi/mkosi.pkgenv/mkosi.conf.d/postmarketos.conf new file mode 100644 index 0000000000000..055415eb20f0a --- /dev/null +++ b/mkosi/mkosi.pkgenv/mkosi.conf.d/postmarketos.conf @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=postmarketos + +[Build] +Environment= + GIT_URL=https://gitlab.postmarketos.org/postmarketOS/pmaports + GIT_SUBDIR=extra-repos/systemd/systemd + GIT_BRANCH=main + GIT_COMMIT=bd970948e652e16e8ef8688cc891aa5ab80dbaa3 + PKG_SUBDIR=postmarketos 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..f420a31b633e7 100755 --- a/mkosi/mkosi.sanitizers/mkosi.postinst +++ b/mkosi/mkosi.sanitizers/mkosi.postinst @@ -9,13 +9,17 @@ if [[ ! -f "$BUILDROOT/$LIBSYSTEMD" ]]; then exit 0 fi -# ASAN and syscall filters aren't compatible with each other. -find "$BUILDROOT"/usr "$BUILDROOT"/etc -name '*.service' -type f | while read -r unit; do - if grep -q -e MemoryDeny -e SystemCall "$unit" ; then +# ASAN and syscall filters aren't compatible with each other. Also, drop any memory limits +# as these are quite unpredictable when running under sanitizers. +find "$BUILDROOT"/{etc,usr/lib}/systemd/system/ -name '*.service' -type f | while read -r unit; do + if grep -q -e MemoryDeny -e MemoryMax -e MemoryHigh -e MemorySwapMax -e SystemCall "$unit" ; then mkdir -p "$unit.d" cat > "$unit.d/sanitizer-compat.conf" </dev/null)" == "$GIT_COMMIT" ]]; then exit 0 diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/arch.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/arch.conf index 52889cb0b4357..99592efc01960 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/arch.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/arch.conf @@ -10,7 +10,7 @@ Packages= clang-tools-extra github-cli lcov - libucontext + liburing musl mypy pkgconf diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/centos-fedora.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/centos-fedora.conf index bc998baad6b0c..2715d1494e488 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/centos-fedora.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/centos-fedora.conf @@ -12,5 +12,5 @@ Packages= rpm-build libasan libubsan - libucontext-devel + liburing-devel compiler-rt 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..f3e13c40af363 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,9 @@ Distribution=|ubuntu [Content] PrepareScripts=%D/mkosi/mkosi.images/build/mkosi.conf.d/debian-ubuntu/mkosi.prepare Packages= - clang-tools + clang-tidy + coccinelle lcov + liburing-dev 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..a8b307c92845c 100644 --- a/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/opensuse.conf @@ -7,9 +7,13 @@ Distribution=opensuse PrepareScripts=%D/mkosi/mkosi.images/build/mkosi.conf.d/opensuse/mkosi.prepare Packages= clang-tools + coccinelle gh lcov + libtss2-tcti-device0 + liburing-devel mypy python3-ruff rpm-build ShellCheck + sysuser-tools diff --git a/mkosi/mkosi.tools.conf/mkosi.conf.d/postmarketos.conf b/mkosi/mkosi.tools.conf/mkosi.conf.d/postmarketos.conf new file mode 100644 index 0000000000000..003dfe57b9b05 --- /dev/null +++ b/mkosi/mkosi.tools.conf/mkosi.conf.d/postmarketos.conf @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=postmarketos + +[Content] +PrepareScripts=%D/mkosi/mkosi.images/build/mkosi.conf.d/postmarketos/mkosi.prepare +Packages= + alpine-sdk + postmarketos-base 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..564b23cd99f8b 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -1,11 +1,14 @@ +ar be be@latin bg +bo ca cs da de el +eo es et eu @@ -22,9 +25,14 @@ it ja ka kab +kk +km +kn ko +kw lt nl +nb_NO pa pl pt @@ -35,13 +43,11 @@ si sk sl sr +sr@latin sv tr +ug uk zh_CN zh_TW -kn -ar -km -kw -kk +lo diff --git a/po/POTFILES.in b/po/POTFILES.in index d9c602cf20d8a..16892d895a6ad 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -14,3 +14,4 @@ src/resolve/org.freedesktop.resolve1.policy src/sysupdate/org.freedesktop.sysupdate1.policy src/timedate/org.freedesktop.timedate1.policy src/core/dbus-unit.c +src/shared/libfido2-util.c diff --git a/po/ar.po b/po/ar.po index f6c113f0e32b7..c27c84e1eee64 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-21 11:10+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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -147,7 +147,7 @@ msgstr "أدر مفاتيح توقيع مجلد المنزل" msgid "Authentication is required to manage signing keys for home directories." msgstr "الاستيثاق مطلوب لإدارة مفاتيح التوقيع لوحدات مجلد المنزل." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -156,49 +156,49 @@ msgstr "" "منزل المستخدم %s غير متواجد حالياً، يُرجى توصيل جهاز التخزين اللازم أو نظام " "الملفات الاحتياطي." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "محاولات الولوج متكررة جدًا للمستخدم %s، حاول مرة أخرى لاحقًا." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "كلمة السر: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "كلمة السر غير صحيحة أو غير كافية لاستيثاق المستخدم %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "عذرًا، حاول مرة أخرى: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "مفتاح الاسترداد: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "كلمة السر/مفتاح الاسترداد غير صحيح أو غير كافٍ لاستيثاق المستخدم %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "عذرًا، أعد إدخال مفتاح الاسترداد: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "حاول مرة أخرى باستخدام كلمة السر: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -206,26 +206,26 @@ msgid "" msgstr "" "كلمة السر غير صحيحة أو غير كافية، ورمز الأمان المُعدّ للمستخدم %s لم يُدرج." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "رمز (PIN) لرمز الأمان: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "يُرجى لاستيثاق فعليًا باستخدام رمز الأمان للمستخدم %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "يُرجى تأكيد وجود رمز الأمان للمستخدم %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "يُرجى التحقق من المستخدم على رمز الأمان للمستخدم %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -233,75 +233,75 @@ msgstr "" "رمز PIN لرمز الأمان مُقفل، يُرجى فتحه أولاً. (تلميح: قد يكون الإزالة وإعادة " "الإدخال كافيين.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "رمز (PIN) غير صحيح للمستخدم %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "عذرًا، أعد إدخال رمز PIN لرمز الأمان: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "رمز PIN لرمز الأمان للمستخدم %s غير صحيح (تبقى محاولات قليلة فقط!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "رمز PIN لرمز الأمان للمستخدم %s غير صحيح (تبقى محاولة واحدة فقط!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "مجلد منزل المستخدم %s مُقفل حاليًا، يُرجى فتحه محليًا أولاً." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "عدد محاولات ولوج الفاشلة للمستخدم %s كبير جدًا، يتم الرفض." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "حُظر سجلّ المستخدم، يُمنع الوصول." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "سجلّ المستخدم غير صالح بعد، يُمنع الوصول." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "سجلّ المستخدم لم يعد صالحًا، يُمنع الوصول." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "سجلّ المستخدم غير صالح، يُمنع الوصول." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "عدد الولوج كبير جدًا، حاول مرة أخرى بعد %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "مطلوب تغيير كلمة السر." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "انتهت صلاحية كلمة السر، يتطلب التغيير." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "كلمة السر منتهية الصلاحية، ولا يمكن تغييرها، يتم رفض الولوج." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "ستنتهي صلاحية كلمة السر قريبًا، يُرجى تغييرها." @@ -801,33 +801,52 @@ msgid "" msgstr "الاستيثاق مطلوب لإدارة الأجهزة الافتراضية والحاويات المحلية." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "افحص الأجهزة الافتراضية والحاويات المحلية" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "الاستيثاق مطلوب لفحص الأجهزة الافتراضية والحاويات المحلية." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "أنشئ جهازًا افتراضيًا محليًا أو حاوية" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "الاستيثاق مطلوب لإنشاء جهاز افتراضي محلي أو حاوية." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "سجّل جهازًا افتراضيًا محليًا أو حاوية" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "الاستيثاق مطلوب لتسجيل جهاز افتراضي محلي أو حاوية." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "أدر الأجهزة الافتراضية وصور الحاويات المحلية" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "الاستيثاق مطلوب لإدارة الأجهزة الافتراضية وصور الحاويات المحلية." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "افحص صور الأجهزة الافتراضية والحاويات المحلية" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "الاستيثاق مطلوب لفحص صور الأجهزة الافتراضية والحاويات المحلية." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "عيّن خوادم NTP" @@ -938,8 +957,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" @@ -1082,45 +1103,95 @@ msgstr "صفّر الإحصائيات" msgid "Authentication is required to reset statistics." msgstr "الاستيثاق مطلوب لتصفير الإحصائيات." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "امسح ذاكرة التخزين المؤقت لـ DNS" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "الاستيثاق مطلوب لمسح ذاكرة التخزين المؤقت لـ DNS." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "صفّر ميزات الخادم" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "الاستيثاق مطلوب لتصفير ميزات الخادم." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "تحقق من تحديثات النظام" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "الاستيثاق مطلوب لتحقق من تحديثات النظام." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "ألغِ التحقق من تحديثات النظام" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "الاستيثاق مطلوب لإلغاء التحقق من تحديثات النظام." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "ثبِّت تحديثات النظام" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "الاستيثاق مطلوب لتحديث النظام." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "ألغِ تثبيت تحديثات النظام" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "الاستيثاق مطلوب لإلغاء تثبيت تحديثات النظام." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "ثبِّت إصدار نظام محدد" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "ألغِ تثبيت إصدار نظام معين" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "الاستيثاق مطلوب لإلغاء تحديث النظام إلى إصدار معين (ربما يكون قديمًا)." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" msgstr "نظّف تحديثات النظام القديمة" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." msgstr "الاستيثاق مطلوب لتنظيف تحديثات النظام القديمة." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "ألغِ تنظيف تحديثات النظام القديمة" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "الاستيثاق مطلوب لإلغاء تنظيف تحديثات النظام القديمة." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "أدر الميزات الاختيارية" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "الاستيثاق مطلوب لإدارة الميزات الاختيارية." @@ -1211,3 +1282,27 @@ msgstr "الاستيثاق مطلوب لحذف الملفات والمجلدات msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "الاستيثاق مطلوب لتجميد أو إذابة عمليات وحدة '$(unit)'." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "يُرجى تأكيد الحضور على رمز الأمان لإلغاء القفل." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "يُرجى التحقق من المستخدم عبر رمز الأمان لإلغاء القفل." + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "يُرجى إدخال رمز الأمان (PIN) (عدد المحاولات المتبقية قبل الحظر: %d):" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "يُرجى إدخال رمز الأمان (PIN):" + +#~ msgid "Cleanup old system updates" +#~ msgstr "نظّف تحديثات النظام القديمة" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "الاستيثاق مطلوب لتنظيف تحديثات النظام القديمة." diff --git a/po/be.po b/po/be.po index 1ef0948a26194..397820878d38f 100644 --- a/po/be.po +++ b/po/be.po @@ -4,23 +4,25 @@ # # # Viktar Vaŭčkievič , 2015, 2016. -# Zmicer Turok , 2020, 2021. +# Zmicer Turok , 2020, 2021, 2026. # Maksim Kliazovich , 2023. +# Fedora Weblate user 1008 , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2023-05-13 19:20+0000\n" -"Last-Translator: Maksim Kliazovich \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:59+0000\n" +"Last-Translator: Fedora Weblate user 1008 " +"\n" "Language-Team: Belarusian \n" +"systemd/main/be/>\n" "Language: be\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: Weblate 4.15.2\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -163,156 +165,156 @@ msgstr "" "Для кіравання сістэмнымі службамі і іншымі адзінкамі патрабуецца " "аўтэнтыфікацыя." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "" -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "" -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "" -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "" @@ -892,11 +894,20 @@ msgstr "" "аўтэнтыфікацыя." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 #, fuzzy msgid "Create a local virtual machine or container" msgstr "Кіраванне лакальнымі віртуальнымі машынамі або кантэйнерамі" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 #, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." @@ -904,12 +915,12 @@ msgstr "" "Для кіравання лакальнымі віртуальнымі машынамі і кантэйнерамі патрабуецца " "аўтэнтыфікацыя." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "Кіраванне лакальнымі віртуальнымі машынамі або кантэйнерамі" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." @@ -917,11 +928,11 @@ msgstr "" "Для кіравання лакальнымі віртуальнымі машынамі і кантэйнерамі патрабуецца " "аўтэнтыфікацыя." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Кіраванне вобразамі лакальных віртуальных машын і кантэйнераў" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -929,6 +940,16 @@ msgstr "" "Для кіравання вобразамі лакальных віртуальных машын і кантэйнераў " "патрабуецца аўтэнтыфікацыя." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Вызначыць серверы NTP" @@ -1039,7 +1060,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." +#, fuzzy +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" "Для адпраўкі паведамлення з прымусовым абнаўленнем патрабуецца " "аўтэнтыфікацыя." @@ -1198,49 +1222,98 @@ msgstr "" msgid "Authentication is required to reset statistics." msgstr "Для таго, каб скінуць налады NTP, патрабуецца аўтэнтыфікацыя." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 #, fuzzy msgid "Authentication is required to check for system updates." msgstr "Для наладкі сістэмнага часу патрабуецца аўтэнтыфікацыя." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 #, fuzzy msgid "Authentication is required to install system updates." msgstr "Для наладкі сістэмнага часу патрабуецца аўтэнтыфікацыя." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 #, 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" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy -msgid "Authentication is required to cleanup old system updates." -msgstr "Для наладкі сістэмнага часу патрабуецца аўтэнтыфікацыя." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 #, fuzzy msgid "Authentication is required to manage optional features." msgstr "" @@ -1343,5 +1416,27 @@ msgstr "" "Для спынення альбо ўзнаўлення працэсаў \"$(unit)\" патрабуецца " "аўтэнтыфікацыя." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#, fuzzy +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "Для наладкі сістэмнага часу патрабуецца аўтэнтыфікацыя." + #~ msgid "Authentication is required to kill '$(unit)'." #~ msgstr "Неабходна аўтэнтыфікацыя для забойства '$(unit)'." diff --git a/po/be@latin.po b/po/be@latin.po index 8dbc865a41272..5eab8d5362efd 100644 --- a/po/be@latin.po +++ b/po/be@latin.po @@ -7,17 +7,18 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2016-06-09 19:50+0300\n" -"Last-Translator: Viktar Vaŭčkievič \n" -"Language-Team: \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:04+0000\n" +"Last-Translator: Anonymous \n" +"Language-Team: Belarusian (Latin script) \n" "Language: be@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: Lokalize 2.0\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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -170,156 +171,156 @@ msgstr "" "Nieabchodna aŭtentyfikacyja dlia kiravannia servisami i inšymi sistemnymi " "adzinkami." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "" -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "" -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "" -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "" @@ -921,11 +922,20 @@ msgstr "" "kantejnierami." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 #, fuzzy msgid "Create a local virtual machine or container" msgstr "Kiravać lakaĺnymi virtuaĺnymi mašynami abo kantejnierami" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 #, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." @@ -933,12 +943,12 @@ msgstr "" "Nieabchodna aŭtentyfikacyja dlia kiravannia lakaĺnymi virtuaĺnymi mašynami i " "kantejnierami." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "Kiravać lakaĺnymi virtuaĺnymi mašynami abo kantejnierami" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." @@ -946,11 +956,11 @@ msgstr "" "Nieabchodna aŭtentyfikacyja dlia kiravannia lakaĺnymi virtuaĺnymi mašynami i " "kantejnierami." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Kiravać vobrazami lakaĺnych virtuaĺnych mašyn i kantejnieraŭ" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -958,6 +968,16 @@ msgstr "" "Nieabchodna aŭtentyfikacyja dlia kiravannia vobrazami lakaĺnych virtuaĺnych " "mašyn i kantejnieraŭ." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "" @@ -1079,7 +1099,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" @@ -1243,29 +1265,61 @@ msgstr "" msgid "Authentication is required to reset statistics." msgstr "Nieabchodna aŭtentyfikacyja dlia ŭstaliavannia sistemnaha času." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 #, fuzzy msgid "Authentication is required to check for system updates." msgstr "Nieabchodna aŭtentyfikacyja dlia ŭstaliavannia sistemnaha času." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 #, fuzzy msgid "Authentication is required to install system updates." msgstr "Nieabchodna aŭtentyfikacyja dlia ŭstaliavannia sistemnaha času." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 #, fuzzy msgid "" "Authentication is required to update the system to a specific (possibly old) " @@ -1273,20 +1327,37 @@ msgid "" msgstr "" "Nieabchodna aŭtentyfikacyja dlia ŭstaliavannia sistemnaha časavoha pojasu." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy -msgid "Authentication is required to cleanup old system updates." -msgstr "Nieabchodna aŭtentyfikacyja dlia ŭstaliavannia sistemnaha času." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 #, fuzzy msgid "Authentication is required to manage optional features." msgstr "" @@ -1391,5 +1462,27 @@ msgid "" msgstr "" "Nieabchodna aŭtentyfikacyja dlia anuliavannia pamylkovaha stanu '$(unit)'." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#, fuzzy +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "Nieabchodna aŭtentyfikacyja dlia ŭstaliavannia sistemnaha času." + #~ msgid "Authentication is required to kill '$(unit)'." #~ msgstr "Nieabchodna aŭtentyfikacyja dlia zabojstva '$(unit)'." diff --git a/po/bg.po b/po/bg.po index 0cbfd23c35c4f..15b342affd710 100644 --- a/po/bg.po +++ b/po/bg.po @@ -2,14 +2,14 @@ # # Bulgarian translation of systemd po-file. # Copyright © 2016, 2022, 2025 Alexander Shopov -# Alexander Shopov , 2016, 2022, 2025. +# Alexander Shopov , 2016, 2022, 2025, 2026. # Velislav Ivanov , 2023. msgid "" msgstr "" "Project-Id-Version: systemd main\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-02-11 01:17+0000\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:57+0000\n" "Last-Translator: Alexander Shopov \n" "Language-Team: Bulgarian \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.9.2\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -168,7 +168,7 @@ msgid "Authentication is required to manage signing keys for home directories." msgstr "" "За управление на услугите или другите модули е необходима идентификация." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -178,31 +178,31 @@ msgstr "" "устройство за съхранение на данни и/или монтирайте съответната файлова " "система." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Прекалено много опити за вписване за потребителя „%s“, пробвайте по-късно." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Парола: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Паролата е неправилна или недостатъчна за идентификация на потребител „%s“." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Пробвайте отново: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Ключ за възстановяване: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -211,20 +211,20 @@ msgstr "" "Паролата/Ключът за възстановяване са неправилни или недостатъчни за " "идентификация на потребител „%s“." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Въведето отново ключа за възстановяване: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Пробвайте отново с парола: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -233,28 +233,28 @@ msgstr "" "Паролата е неправилна или недостатъчна, а настроеният жетон за сигурност на " "потребител „%s“ не е поставен." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "ПИН за жетон за сигурност: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" "Идентифицирайте се физически чрез жетона за сигурност на потребител „%s“." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" "Потвърдете физическо присъствие чрез жетона за сигурност на потребител „%s“." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "Идентифицирайте се чрез жетона за сигурност на потребител „%s“." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -262,85 +262,85 @@ msgstr "" "ПИНът на жетона за сигурност на потребител е заключен, отключете го! " "(Понякога изваждането и повторното поставяне е достатъчно за това.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "ПИН-ът за жетона за сигурност на потребител „%s“ е неправилен." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Въведете отново ПИН за жетона за сигурност: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "ПИН-ът за жетона за сигурност на потребител „%s“ е неправилен (остават малко " "пъти!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "ПИН-ът за жетона за сигурност на потребител „%s“ е неправилен (остава един " "път!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "Домашното масто на потребител „%s“ в момента е заключено, отключете го " "локално." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" "Прекалено много неуспешни опити за вписване за потребител „%s“, достъпът е " "отказан." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "Записът на потребителя е блокиран, достъпът е забранен." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "Записът за потребител все още не е валиден, достъпът е забранен." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "Записът за потребител вече е невалиден, достъпът е забранен." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Записът за потребител е невалиден, достъпът е забранен." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Прекалено много опити за вписване, пробвайте отново след %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Необходима е смяна на паролата." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Паролата е изтекла, трябва да се смени." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "Паролата е изтекла, но не може да се променя, вписването е отказано." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Паролата ще изтече скоро, сменете я." @@ -924,22 +924,31 @@ msgstr "" "идентификация." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Създаване на локални виртуални машини и контейнери" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "За създаване на локални виртуални машини и контейнери е необходима " "идентификация." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "Създаване на локални виртуални машини и контейнери" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." @@ -947,11 +956,11 @@ msgstr "" "За създаване на локални виртуални машини и контейнери е необходима " "идентификация." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Управление на изображения на виртуални машини или контейнери" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -959,6 +968,16 @@ msgstr "" "За управление на изображения на виртуални машини или е необходима " "идентификация." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Задаване на сървъри за точно време (NTP)" @@ -1072,7 +1091,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." +#, fuzzy +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "За съобщение за обновяване на DHCP е необходима идентификация." #: src/network/org.freedesktop.network1.policy:154 @@ -1226,27 +1248,59 @@ msgstr "Зануляване на статистиките" msgid "Authentication is required to reset statistics." msgstr "За зануляване на статистиките е необходима идентификация." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Проверка за обновяване на системата" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "За проверка за обновяване на системата е необходима идентификация." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Инсталиране на обновяване на системата" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "За инсталиране на обновяване на системата е необходима идентификация." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Инсталиране на вариант зависещ от версията на системата" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1254,21 +1308,37 @@ msgstr "" "За инсталиране на вариант зависещ от версията на системата е необходима " "идентификация." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" -msgstr "Изтриване на остарелите обновявания на системата" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." msgstr "" -"За изтриване на остарелите обновявания на системата е необходима " -"идентификация." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Управление на незадължителните възможности" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "" "За управление на незадължителните възможности е необходима идентификация." @@ -1370,3 +1440,29 @@ msgid "" msgstr "" "За замразяване/размразяване на процесите на „$(unit)“ е необходима " "идентификация." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Изтриване на остарелите обновявания на системата" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "За изтриване на остарелите обновявания на системата е необходима " +#~ "идентификация." diff --git a/po/bo.po b/po/bo.po new file mode 100644 index 0000000000000..c95a561c3dbbb --- /dev/null +++ b/po/bo.po @@ -0,0 +1,1298 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Tibetan translation for systemd. +# Dongshengyuan , 2026. +msgid "" +msgstr "" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:59+0000\n" +"Last-Translator: Anonymous \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: Weblate 2026.6.1\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:327 +#, 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:332 +#, c-format +msgid "Too frequent login attempts for user %s, try again later." +msgstr "སྤྱོད་མཁན %s ཡི་ནང་འཛུལ་ཚོད་ལྟ་མང་དྲགས་པས་རྗེས་སུ་ཡང་བསྐྱར་ཚོད་ལྟ་བྱེད།" + +#: src/home/pam_systemd_home.c:344 +msgid "Password: " +msgstr "གསང་ཚིག: " + +#: src/home/pam_systemd_home.c:346 +#, c-format +msgid "Password incorrect or not sufficient for authentication of user %s." +msgstr "སྤྱོད་མཁན %s ཡི་གསང་ཚིག་ནོར་འཁྲུལ་ཡིན་པའམ་ར་སྤྲོད་ལ་མི་འདང་།" + +#: src/home/pam_systemd_home.c:347 +msgid "Sorry, try again: " +msgstr "དགོངས་དག ཡང་བསྐྱར་ཚོད་ལྟ: " + +#: src/home/pam_systemd_home.c:369 +msgid "Recovery key: " +msgstr "སླར་གསོ་ལྡེ་མིག: " + +#: src/home/pam_systemd_home.c:371 +#, c-format +msgid "" +"Password/recovery key incorrect or not sufficient for authentication of user " +"%s." +msgstr "སྤྱོད་མཁན %s ཡི་གསང་ཚིག/སླར་གསོ་ལྡེ་མིག་ནོར་འཁྲུལ་ཡིན་པའམ་ར་སྤྲོད་ལ་མི་འདང་།" + +#: src/home/pam_systemd_home.c:372 +msgid "Sorry, reenter recovery key: " +msgstr "དགོངས་དག སླར་གསོ་ལྡེ་མིག་ཡང་བསྐྱར་ནང་འཇུག: " + +#: src/home/pam_systemd_home.c:392 +#, c-format +msgid "Security token of user %s not inserted." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས་མ་བཙུགས་པ།" + +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 +msgid "Try again with password: " +msgstr "གསང་ཚིག་སྤྱད་ནས་ཡང་བསྐྱར་ཚོད་ལྟ: " + +#: src/home/pam_systemd_home.c:395 +#, 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:415 +msgid "Security token PIN: " +msgstr "བདེ་འཇགས་རྟགས PIN: " + +#: src/home/pam_systemd_home.c:432 +#, c-format +msgid "Please authenticate physically on security token of user %s." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས་སྟེང་ལུས་ངོས་ར་སྤྲོད་གནང་རོགས།" + +#: src/home/pam_systemd_home.c:443 +#, c-format +msgid "Please confirm presence on security token of user %s." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས་སྟེང་ཡོད་པ་ངེས་གཏན་གནང་རོགས།" + +#: src/home/pam_systemd_home.c:454 +#, c-format +msgid "Please verify user on security token of user %s." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས་སྟེང་སྤྱོད་མཁན་ཞིབ་བཤེར་གནང་རོགས།" + +#: src/home/pam_systemd_home.c:463 +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:471 +#, c-format +msgid "Security token PIN incorrect for user %s." +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས PIN ནོར་འཁྲུལ།" + +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 +msgid "Sorry, retry security token PIN: " +msgstr "དགོངས་དག བདེ་འཇགས་རྟགས PIN ཡང་བསྐྱར་ཚོད་ལྟ: " + +#: src/home/pam_systemd_home.c:490 +#, c-format +msgid "Security token PIN of user %s incorrect (only a few tries left!)" +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས PIN ནོར་འཁྲུལ། (ཚོད་ལྟ་ཆེས་ཉུང་ཙམ་ལས་མེད!)" + +#: src/home/pam_systemd_home.c:509 +#, c-format +msgid "Security token PIN of user %s incorrect (only one try left!)" +msgstr "སྤྱོད་མཁན %s ཡི་བདེ་འཇགས་རྟགས PIN ནོར་འཁྲུལ། (ཚོད་ལྟ་གཅིག་ལས་མེད!)" + +#: src/home/pam_systemd_home.c:676 +#, c-format +msgid "Home of user %s is currently not active, please log in locally first." +msgstr "སྤྱོད་མཁན %s ཡི་ཁྱིམ་དཀར་ཆག་ད་ལྟ་སྐུལ་སློང་མེད། སྔོན་ལ་ས་གནས་ནས་ནང་འཛུལ་གནང་རོགས།" + +#: src/home/pam_systemd_home.c:678 +#, c-format +msgid "Home of user %s is currently locked, please unlock locally first." +msgstr "སྤྱོད་མཁན %s ཡི་ཁྱིམ་དཀར་ཆག་ད་ལྟ་སྒོ་ལྕགས་བཀག་ཡོད། སྔོན་ལ་ས་གནས་ནས་སྒྲོལ་རོགས།" + +#: src/home/pam_systemd_home.c:712 +#, c-format +msgid "Too many unsuccessful login attempts for user %s, refusing." +msgstr "སྤྱོད་མཁན %s ཡི་ནང་འཛུལ་མ་ལེགས་པའི་ཚོད་ལྟ་མང་དྲགས་པས་ངོས་ལེན་མི་བྱེད།" + +#: src/home/pam_systemd_home.c:1021 +msgid "User record is blocked, prohibiting access." +msgstr "སྤྱོད་མཁན་ཐོ་བཀག་ཟིན་པས་ལྟ་སྤྱོད་བཀག་ཡོད།" + +#: src/home/pam_systemd_home.c:1025 +msgid "User record is not valid yet, prohibiting access." +msgstr "སྤྱོད་མཁན་ཐོ་ད་དུང་ནུས་ལྡན་མིན་པས་ལྟ་སྤྱོད་བཀག་ཡོད།" + +#: src/home/pam_systemd_home.c:1029 +msgid "User record is not valid anymore, prohibiting access." +msgstr "སྤྱོད་མཁན་ཐོ་ད་ནས་ནུས་མེད་པས་ལྟ་སྤྱོད་བཀག་ཡོད།" + +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 +msgid "User record not valid, prohibiting access." +msgstr "སྤྱོད་མཁན་ཐོ་ནུས་མེད་པས་ལྟ་སྤྱོད་བཀག་ཡོད།" + +#: src/home/pam_systemd_home.c:1044 +#, c-format +msgid "Too many logins, try again in %s." +msgstr "ནང་འཛུལ་མང་དྲགས་པས %s རྗེས་སུ་ཡང་བསྐྱར་ཚོད་ལྟ་བྱེད།" + +#: src/home/pam_systemd_home.c:1055 +msgid "Password change required." +msgstr "གསང་ཚིག་བསྒྱུར་དགོས།" + +#: src/home/pam_systemd_home.c:1059 +msgid "Password expired, change required." +msgstr "གསང་ཚིག་དུས་ཡོལ་ཟིན་པས་བསྒྱུར་དགོས།" + +#: src/home/pam_systemd_home.c:1065 +msgid "Password is expired, but can't change, refusing login." +msgstr "གསང་ཚིག་དུས་ཡོལ་ཟིན་ཡང་བསྒྱུར་མི་ཐུབ་པས་ནང་འཛུལ་ཁས་མི་ལེན།" + +#: src/home/pam_systemd_home.c:1069 +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 "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" +msgstr "ས་གནས virtual machine ཡང་ན container གསར་བཟོ" + +#: src/machine/org.freedesktop.machine1.policy:106 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "ས་གནས virtual machine ཡང་ན container གསར་བཟོ་བར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:116 +msgid "Register a local virtual machine or container" +msgstr "ས་གནས virtual machine ཡང་ན container ཐོ་འགོད" + +#: src/machine/org.freedesktop.machine1.policy:117 +msgid "" +"Authentication is required to register a local virtual machine or container." +msgstr "ས་གནས virtual machine ཡང་ན container ཐོ་འགོད་པར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:126 +msgid "Manage local virtual machine and container images" +msgstr "ས་གནས virtual machine དང container མཚོན་རིས་དོ་དམ" + +#: src/machine/org.freedesktop.machine1.policy:127 +msgid "" +"Authentication is required to manage local virtual machine and container " +"images." +msgstr "ས་གནས virtual machine དང container མཚོན་རིས་དོ་དམ་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect 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 "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/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 +msgid "Check for system updates" +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་ཞིབ་བཤེར" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 +msgid "Authentication is required to check for system updates." +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་ཞིབ་བཤེར་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 +msgid "Install system updates" +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་སྒྲིག་འཇུག" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 +msgid "Authentication is required to install system updates." +msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་སྒྲིག་འཇུག་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 +msgid "Install specific system version" +msgstr "དམིགས་བསལ་རྒྱུད་ལམ་པར་གཞི་སྒྲིག་འཇུག" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 +msgid "" +"Authentication is required to update the system to a specific (possibly old) " +"version." +msgstr "རྒྱུད་ལམ་དེ་དམིགས་བསལ་(རྙིང་པ་ཡིན་སྲིད་པའི) པར་གཞིར་གསར་སྒྱུར་བྱེད་པར་ར་སྤྲོད་དགོས།" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 +msgid "Manage optional features" +msgstr "འདེམས་ཆོག་པའི་ལས་འགན་དོ་དམ" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 +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 གི་བརྒྱུད་རིམ་འཁྱགས་བཅུག་གམ་གྲོལ་བར་ར་སྤྲོད་དགོས།" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་རྙིང་པ་གཙང་སེལ" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "རྒྱུད་ལམ་གསར་སྒྱུར་རྙིང་པ་གཙང་སེལ་བྱེད་པར་ར་སྤྲོད་དགོས།" diff --git a/po/ca.po b/po/ca.po index 19beafda1142c..ae3c60fcc2281 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:56+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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -162,7 +162,7 @@ msgstr "" "Es requereix autenticació per gestionar les claus de signatura dels " "directoris d'inici." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -171,32 +171,32 @@ msgstr "" "No hi ha l'àrea d'inici de l'usuari %s, connecteu el dispositiu " "d'emmagatzematge o el sistema de fitxers de còpia de seguretat necessaris." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Intents d'inici de sessió massa freqüents per a l'usuari %s, torneu-ho a " "provar més tard." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Contrasenya: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Contrasenya incorrecta o no suficient per a l'autenticació de l'usuari %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Ho sentim, torneu-ho a provar: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Clau de recuperació: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -205,20 +205,20 @@ msgstr "" "La contrasenya/clau de recuperació és incorrecta o no suficient per a " "l'autenticació de l'usuari %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Torneu a introduir la clau de recuperació: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "No s'ha inserit el token de seguretat de l'usuari %s." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Torneu-ho a provar amb la contrasenya: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -227,28 +227,28 @@ msgstr "" "La contrasenya és incorrecta o no suficient, i no s'ha inserit el token de " "seguretat configurat de l'usuari %s." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "PIN del token de seguretat: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" "Si us plau, autenticeu-vos físicament al token de seguretat de l'usuari %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "Confirmeu la presència al token de seguretat de l'usuari %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" "Si us plau, verifiqueu l'usuari en el token de seguretat de l'usuari %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -256,86 +256,86 @@ msgstr "" "El PIN del token de seguretat està bloquejat, desbloquegeu-lo primer. " "(Pista: L'eliminació i la reinserció poden ser suficients.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "El PIN del token de seguretat és incorrecte per a l'usuari %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Torneu a provar el PIN del token de seguretat: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "El PIN del token de seguretat de l'usuari %s és incorrecte (només queden uns " "quants intents!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "El PIN del token de seguretat de l'usuari %s és incorrecte (només queda un " "intent!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "L'àrea d'inici de l'usuari %s no està activa actualment, primer inicieu la " "sessió localment." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "L'àrea d'inici de l'usuari %s està bloquejada actualment, desbloquegeu-la " "localment primer." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" "Hi ha massa intents d'inici de sessió fallits per a l'usuari %s, rebutjant." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "El registre d'usuari està bloquejat, prohibint l'accés." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "El registre de l'usuari encara no és vàlid, prohibint-ne l'accés." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "El registre d'usuari ja no és vàlid, prohibint-ne l'accés." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Registre d'usuari no vàlid, es prohibeix l'accés." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Massa inicis de sessió, torneu-ho a provar en %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Es requereix un canvi de contrasenya." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "La contrasenya ha caducat, cal canviar-la." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" "La contrasenya ha caducat, però no es pot canviar, rebutjant l'inici de " "sessió." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "La contrasenya caducarà aviat, si us plau, canvieu-la." @@ -916,31 +916,40 @@ msgstr "" "contenidors locals." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Inspeccionar màquines virtuals i contenidors" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "Cal autenticació per inspeccionar màquines virtuals i contenidors." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Crea una màquina virtual o contenidor local" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Es requereix autenticació per crear una màquina virtual o contenidor local." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Registra una màquina virtual local o contenidor" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Es requereix autenticació per registrar una màquina virtual local o " "contenidor." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Gestiona les màquines virtuals i les imatges dels contenidors locals" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -948,6 +957,18 @@ msgstr "" "Es requereix autenticació per gestionar les màquines virtuals i les imatges " "dels contenidors locals." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Inspecciona les màquines virtuals locals i imatges de contenidors" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"Cal autenticació per inspeccionar la màquina virtual local i les imatges del " +"contenidor." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Estableix els servidors d'NTP" @@ -1059,8 +1080,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 +1128,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" @@ -1215,29 +1239,61 @@ msgstr "Restableix les estadístiques" msgid "Authentication is required to reset statistics." msgstr "Es requereix autenticació per restablir les estadístiques." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Comprova si hi ha actualitzacions del sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "" "Es requereix autenticació per comprovar si hi ha actualitzacions del sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Instal·la les actualitzacions del sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "" "Es requereix autenticació per instal·lar les actualitzacions del sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Instal·la una versió específica del sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1245,21 +1301,37 @@ msgstr "" "Es requereix autenticació per instal·lar una versió específica del sistema " "(possiblement antiga)." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" -msgstr "Neteja les actualitzacions antigues del sistema" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." msgstr "" -"Es requereix autenticació per netejar les actualitzacions antigues del " -"sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Gestiona les característiques opcionals" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "" "Es requereix autenticació per gestionar les característiques opcionals." @@ -1359,6 +1431,32 @@ msgstr "" "Es requereix autenticació per congelar o descongelar els processos de la " "unitat '$(unit)'." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Neteja les actualitzacions antigues del sistema" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "Es requereix autenticació per netejar les actualitzacions antigues del " +#~ "sistema." + #~ msgid "" #~ "Authentication is required to halt the system while an application asked " #~ "to inhibit it." diff --git a/po/cs.po b/po/cs.po index ec34fc5307167..76c4e7ba623cb 100644 --- a/po/cs.po +++ b/po/cs.po @@ -2,15 +2,16 @@ # # Czech translation for systemd. # -# Daniel Rusek , 2022, 2023, 2025. -# Pavel Borecki , 2023, 2024, 2025. -# Jan Kalabza , 2025. +# Daniel Rusek , 2022, 2023, 2025, 2026. +# Pavel Borecki , 2023, 2024, 2025, 2026. +# Jan Kalabza , 2025, 2026. +# Honza Hejzl , 2026. 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:00+0000\n" +"Last-Translator: Jan Kalabza \n" "Language-Team: Czech \n" "Language: cs\n" @@ -19,7 +20,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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -157,7 +158,7 @@ msgid "Authentication is required to manage signing keys for home directories." msgstr "" "Pro správu klíčů pro podepisování domovských složek je vyžadováno ověření." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -166,30 +167,30 @@ msgstr "" "Domovská složka uživatele %s v tuto chvíli není dostupná, připojte prosím " "potřebné úložné zařízení nebo záložní souborový systém." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Příliš časté pokusy o přihlášení uživatele %s, zkuste to znovu později." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Heslo: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "Neplatné heslo nebo nedostačující pro ověření se uživatele %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Je nám líto, zkuste znovu: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Obnovovací klíč: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -198,20 +199,20 @@ msgstr "" "Heslo/obnovovací klíč jsou nesprávné nebo nedostatečné pro ověření uživatele " "%s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Je nám líto, zadejte obnovovací klíč znovu: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "Bezpečnostní token uživatele %s není vložen." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Zkuste znovu s heslem: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -220,26 +221,26 @@ msgstr "" "Nesprávné nebo nedostatečné heslo a není vložen nakonfigurovaný bezpečnostní " "token uživatele %s." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "PIN bezpečnostního tokenu: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "Ověřte se prosím fyzicky na bezpečnostním tokenu uživatele %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "Potvrďte prosím přítomnost na bezpečnostním tokenu uživatele %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "Ověřte prosím uživatele na bezpečnostním tokenu uživatele %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -247,86 +248,86 @@ msgstr "" "PIN bezpečnostního tokenu je uzamčen, nejprve jej prosím odemkněte. (Tip: " "Vyjmutí a opětovné vložení může stačit.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "Nesprávný PIN bezpečnostního tokenu pro uživatele %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Je nám líto, zkuste PIN bezpečnostního tokenu znovu: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "PIN bezpečnostního tokenu uživatele %s je nesprávný (zbývá jen několik " "pokusů!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "PIN bezpečnostního tokenu uživatele %s je nesprávný (zbývá pouze jeden " "pokus!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "Domovská složka uživatele %s v tuto chvíli není aktivní – nejprve se " "přihlaste lokálně." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "Domovská složka uživatele %s je v tuto chvíli uzamčená – nejprve lokálně " "odemkněte." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" "Příliš mnoho neúspěšných pokusů o přihlášení uživatele %s, proto odmítnuto." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "Záznam uživatele je blokován, přístup proto zamezen." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "Záznam uživatele ještě není platný, přístup proto zamezen." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "Záznam uživatele již není platný, přístup proto zamezen." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Záznam uživatele neplatný, přístup proto zamezen." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Příliš mnoho přihlášení, zkuste znovu za %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Je vyžadována změna hesla." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Platnost hesla skončila, je zapotřebí ho změnit." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" "Platnost hesla skončila, ale není možné ho změnit – přihlášení proto " "odmítnuto." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Platnost hesla brzy skončí – změňte si ho." @@ -871,32 +872,42 @@ msgstr "" "Pro správu lokálních virtuálních strojů a kontejnerů je vyžadováno ověření." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Prozkoumat místní virtuální stroje a kontejnery" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"K prozkoumání místních virtuálních strojů a kontejnerů je vyžadováno ověření." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Vytvoření místního virtuálního počítače nebo kontejneru" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "K vytvoření místního virtuálního počítače nebo kontejneru je vyžadováno " "ověření." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Zaregistrovat lokální virtuální stroj nebo kontejner" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "K zaregistrování lokálního virtuálního stroje nebo kontejneru je vyžadováno " "ověření." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Spravovat lokální obrazy virtuálních strojů a kontejnerů" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -904,6 +915,18 @@ msgstr "" "Pro správu obrazů lokálních virtuálních strojů a kontejnerů je vyžadováno " "ověření." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Prozkoumat místní virtuální stroje a obrazy kontejnerů" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"K prozkoumání místních virtuálních strojů a obrazů kontejnerů je vyžadováno " +"ověření." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Nastavit NTP servery" @@ -1013,8 +1036,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 +1081,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" @@ -1160,27 +1187,59 @@ msgstr "Obnovit statistiky" msgid "Authentication is required to reset statistics." msgstr "Pro resetování statistik je vyžadováno ověření." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "Vyprázdnit DNS mezipaměti" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "Pro vyprázdnění DNS mezipamětí je nutné se autentizovat." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Resetovat funkce serveru" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "Pro resetování funkcí serveru je vyžadováno ověření." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Kontrola aktualizací systému" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "Pro kontrolu aktualizací systému je vyžadováno ověření." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Zrušit kontrolu aktualizací systému" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "Pro zrušení kontroly aktualizací systému je vyžadováno ověření." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Instalace aktualizací systému" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "Pro instalaci aktualizací systému je vyžadováno ověření." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Zrušit instalaci aktualizací systému" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "Pro zrušení instalace aktualizací systému je vyžadováno ověření." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Instalace konkrétní verze systému" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1188,19 +1247,40 @@ msgstr "" "Pro aktualizaci systému na určitou (případně starší) verzi je vyžadováno " "ověření." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" -msgstr "Vyčištění starých aktualizací systému" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Zrušit instalaci specifické verze systému" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" +"Pro zrušení aktualizace systému na konkrétní (patrně starší) verzi systému " +"je vyžadováno ověření." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "Vyčistit staré aktualizace systému" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "Pro vyčištění starých aktualizací systému je vyžadováno ověření." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." -msgstr "K vyčištění starých aktualizací systému je vyžadováno ověření." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Zrušit vyčištění starých aktualizací systému" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" +"Pro zrušení vyčištění starých aktualizací systému je vyžadováno ověření." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Správa volitelných funkcí" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "Pro správu volitelných funkcí je vyžadováno ověření." @@ -1294,3 +1374,28 @@ msgid "" msgstr "" "Pro zmrazení nebo rozmrazení procesů jednotky „$(unit)” je vyžadováno " "ověření." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, fuzzy, c-format +#| msgid "Please enter security token PIN:" +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "Zadejte PIN bezpečnostního tokenu:" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Zadejte PIN bezpečnostního tokenu:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Vyčištění starých aktualizací systému" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "K vyčištění starých aktualizací systému je vyžadováno ověření." diff --git a/po/da.po b/po/da.po index 9fa704a372e9d..1a0b98c85fe46 100644 --- a/po/da.po +++ b/po/da.po @@ -2,21 +2,21 @@ # # Danish translation for systemd. # Daniel Machon , 2015. -# scootergrisen , 2020, 2021. +# scootergrisen , 2020, 2021, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2021-06-02 16:03+0000\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:00+0000\n" "Last-Translator: scootergrisen \n" "Language-Team: Danish \n" +"main/da/>\n" "Language: da\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 4.6.2\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -164,156 +164,156 @@ msgid "Authentication is required to manage signing keys for home directories." msgstr "" "Der kræves godkendelse for at håndtere systemtjenester og andre enheder." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "" -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "" -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "" -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "" @@ -886,11 +886,20 @@ msgstr "" "containere." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 #, fuzzy msgid "Create a local virtual machine or container" msgstr "Håndter lokale virtuelle maskiner og containere" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 #, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." @@ -898,12 +907,12 @@ msgstr "" "Der kræves godkendelse for at håndtere lokale virtuelle maskiner og " "containere." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "Håndter lokale virtuelle maskiner og containere" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." @@ -911,11 +920,11 @@ msgstr "" "Der kræves godkendelse for at håndtere lokale virtuelle maskiner og " "containere." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Håndter lokal virtuel maskine- og beholderaftryk" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -923,6 +932,16 @@ msgstr "" "Der kræves godkendelse for at håndtere lokal virtuel maskine- og " "beholderaftryk." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Indstil NTP-servere" @@ -1033,7 +1052,10 @@ msgid "DHCP server sends force renew message" msgstr "DHCP-server sender tvunget fornyelsesmeddelelse" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +#, fuzzy +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "Der kræves godkendelse for at indstille en broadcast-besked." #: src/network/org.freedesktop.network1.policy:154 @@ -1191,50 +1213,99 @@ msgstr "" msgid "Authentication is required to reset statistics." msgstr "Der kræves godkendelse for at nulstille NTP-indstillinger." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 #, fuzzy msgid "Authentication is required to check for system updates." msgstr "Der kræves godkendelse for at indstille tiden for systemet." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 #, fuzzy msgid "Authentication is required to install system updates." msgstr "Der kræves godkendelse for at indstille tiden for systemet." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 #, fuzzy msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." msgstr "Der kræves godkendelse for at indstille tidszonen for systemet." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy -msgid "Authentication is required to cleanup old system updates." -msgstr "Der kræves godkendelse for at indstille tiden for systemet." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" # https://www.freedesktop.org/software/systemd/man/sd-login.html -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 #, fuzzy msgid "Authentication is required to manage optional features." msgstr "" @@ -1332,6 +1403,28 @@ msgid "" msgstr "" "Der kræves godkendelse for at nulstille \"fejl\"-tilstanden på '$(unit)'." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#, fuzzy +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "Der kræves godkendelse for at indstille tiden for systemet." + #~ msgid "Authentication is required to kill '$(unit)'." #~ msgstr "Autentificering er nødvendig for at eliminere '$(unit)'." diff --git a/po/de.po b/po/de.po index 6ab12e2c06cd3..c11fa765190bd 100644 --- a/po/de.po +++ b/po/de.po @@ -6,18 +6,19 @@ # Bernd Homuth , 2015. # Fabian Affolter , 2020. # Ettore Atalan , 2021, 2024, 2026. -# Christian Wehrli , 2021. -# Christian Kirbach , 2023. -# Jarne Förster , 2024. -# Weblate Translation Memory , 2024, 2025. -# Anselm Schueler , 2024. -# Marcel Leismann , 2025. +# Christian Wehrli , 2021, 2026. +# Christian Kirbach , 2023, 2026. +# Jarne Förster , 2024, 2026. +# Weblate Translation Memory , 2024, 2025, 2026. +# Anselm Schueler , 2024, 2026. +# Marcel Leismann , 2025, 2026. +# Dark Cronyx , 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:59+0000\n" +"Last-Translator: Anselm Schueler \n" "Language-Team: German \n" "Language: de\n" @@ -25,7 +26,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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -183,7 +184,7 @@ msgstr "" "Legitimierung ist notwendig für die Verwaltung von Signierschlüsseln von " "Benutzerverzeichnissen." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -192,31 +193,31 @@ msgstr "" "Das Benutzerverzeichnis von %s ist nicht verfügbar. Bitte hängen Sie das " "benötigte Speichermedium oder Dateisystem ein." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Zu viele Anmeldeversuche für Benutzer %s, versuchen Sie es später noch " "einmal." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Passwort: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "Falsches Passwort oder unzureichende Authentifizierung für Nutzer %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Entschuldigung, bitte erneut probieren: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Wiederherstellungsschlüssel: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -225,20 +226,20 @@ msgstr "" "Passwort/Wiederherstellungsschlüssel nicht korrekt oder unzureichend um %s " "zu authentifizieren." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Bitte Wiederherstellungsschlüssel erneut eingeben: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "Sicherheitstoken für Benutzer %s nicht eingesteckt." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Bitte noch einmal mit Passwort versuchen: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -247,27 +248,27 @@ msgstr "" "Passwort falsch oder unzureichend und konfigurierter Sicherheitstoken für " "Benutzer %s nicht eingesteckt." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "PIN des Sicherheitstokens: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "Bitte physisch auf Sicherheitstoken für Benutzer %s authentifizieren." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "Bitte Präsenz auf Sicherheitstoken für %s bestätigen." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" "Bitte Benutzer anhand des Sicherheitstokens von Benutzer %s verifizieren." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -275,80 +276,80 @@ msgstr "" "Sicherheitstoken PIN ist gesperrt, bitte entsperren (Hinweis: Entfernen und " "neu einstecken könnte genügen.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "Sicherheitstoken PIN nicht korrekt für %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Entschuldigung, bitte Sicherheitstoken PIN erneut probieren: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "Sicherheitstoken PIN für %s falsch (nur ein paar Versuche übrig!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "Sicherheitstoken PIN für %s falsch (nur noch ein Versuch übrig!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "Persönlicher Ordner von %s ist aktuell nicht aktiv. Bitte zuerst lokal " "anmelden." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "Persönlicher Ordner von %s ist aktuell gesperrt. Bitte zuerst lokal " "entsperren." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "Zu viele erfolglose Anmeldeversuche für %s, lehne ab." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "Benutzerdaten sind blockiert, Zugriff verweigert." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "Benutzerdaten sind noch nicht gültig, Zugriff verweigert." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "Benutzerdaten nicht mehr gültig, Zugriff verweigert." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Benutzerdaten nicht gültig, Zugriff verweigert." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Zu viele Anmeldungen, bitte erneut in %s probieren." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Passwortänderung erforderlich." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Passwort abgelaufen, Änderung erforderlich." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" "Passwort ist abgelaufen, aber ändern nicht möglich, verweigere Anmeldung." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Passwort wird demnächst ablaufen, bitte ändern." @@ -948,32 +949,43 @@ msgstr "" "erforderlich." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Lokale virtuelle Maschinen und Container untersuchen" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"Legitimierung ist zum Untersuchen lokaler virtueller Maschinen und Container " +"notwendig." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Lokale virtuelle Maschinen oder Container erstellen" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Legitimierung ist zum Erstellen einer lokalen virtuellen Maschine oder eines " "Containers erforderlich." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Lokale virtuelle Maschinen oder Container registrieren" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Legitimierung ist für die Registrierung einer lokalen virtuellen Maschine " "oder eines Containers erforderlich." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Lokale virtuelle Maschinen und Containerabbilder verwalten" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -981,6 +993,18 @@ msgstr "" "Legitimierung ist zum Verwalten lokaler virtueller Maschinen und " "Containerabbildern erforderlich." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Abbilder von lokalen virtuellen Maschinen und Containern untersuchen" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"Legitimierung ist zum Untersuchen der Abbilder von lokalen virtuellen " +"Maschinen und Containern notwendig." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "NTP-Server festlegen" @@ -1098,9 +1122,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" @@ -1143,7 +1170,7 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Netzwerkverbindungen verwalten" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." @@ -1257,28 +1284,64 @@ msgstr "Statistik zurücksetzen" msgid "Authentication is required to reset statistics." msgstr "Legitimierung ist zum Zurücksetzen der Statistik erforderlich." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Serverfunktionen zurücksetzen" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "Legitimierung ist zum Zurücksetzen von Serverfunktionen notwendig." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Auf Systemaktualisierungen prüfen" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "Legitimierung ist zum Prüfen auf Systemaktualisierungen erforderlich." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Überprüfung auf Systemaktualisierungen abbrechen" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" +"Legitimierung ist zum Abbrechen der Überprüfung auf Systemaktualisierungen " +"notwendig." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Systemaktualisierungen installieren" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "" "Legitimierung ist zum Installieren von Systemaktualisierungen erforderlich." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Installation von Systemaktualisierungen abbrechen" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" +"Legitimierung ist zum Abbrechen der Installation von Systemaktualisierungen " +"notwendig." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Spezifische Systemversion installieren" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1286,21 +1349,43 @@ msgstr "" "Legitimierung ist zum Aktualisieren des Systems auf eine bestimmte " "(möglicherweise alte) Version erforderlich." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Installation von spezifischer Systemversion abbrechen" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" +"Legitimierung ist zum Abbrechen der Aktualisierung des Systems auf eine " +"spezifische (möglicherweise veraltete) Version notwendig." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" msgstr "Alte Systemaktualisierungen bereinigen" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." msgstr "" -"Legitimierung ist zum Bereinigen alter Systemaktualisierungen erforderlich." +"Legitimierung ist zum Bereinigen alter Systemaktualisierungen notwendig." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Bereinigung von alten Systemaktualisierungen abbrechen" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" +"Legitimierung ist zum Abbrechen der Bereinigung von alten " +"Systemaktualisierungen notwendig." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Optionale Funktionen verwalten" # https://www.freedesktop.org/software/systemd/man/sd-login.html -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "Legitimierung ist zur Verwaltung optionaler Funktionen erforderlich." @@ -1322,7 +1407,7 @@ msgstr "Legitimierung ist zum Festlegen der Systemzeitzone notwendig." #: src/timedate/org.freedesktop.timedate1.policy:43 msgid "Set RTC to local timezone or UTC" -msgstr "Echtzeituhr auf lokale Zeitzone oder UTC setzen" +msgstr "Echtzeituhr auf lokale Zeitzone oder UTC festlegen" #: src/timedate/org.freedesktop.timedate1.policy:44 msgid "" @@ -1402,6 +1487,37 @@ msgstr "" "Authentifizierung ist erforderlich um Prozesse der »$(unit)« Einheit " "einzufrieren oder aufzutauen." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" +"Bitte Anwesenheit auf dem Sicherheitstoken bestätigen, um die Sperre " +"aufzuheben." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" +"Bitte Benutzer auf dem Sicherheitstoken verifizieren, um die Sperre " +"aufzuheben." + +#: src/shared/libfido2-util.c:936 +#, fuzzy, c-format +#| msgid "Please enter security token PIN:" +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "Bitte PIN des Sicherheitstokens eingeben:" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Bitte PIN des Sicherheitstokens eingeben:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Alte Systemaktualisierungen bereinigen" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "Legitimierung ist zum Bereinigen alter Systemaktualisierungen " +#~ "erforderlich." + #~ msgid "Authentication is required to kill '$(unit)'." #~ msgstr "Legitimierung ist zum Eliminieren von »$(unit)« notwendig." diff --git a/po/el.po b/po/el.po index daaabfc354542..900e2e818ff2f 100644 --- a/po/el.po +++ b/po/el.po @@ -3,15 +3,15 @@ # Greek translation for systemd. # Dimitris Spingos , 2014. # Dimitris Spingos (Δημήτρης Σπίγγος) , 2014. -# Dimitrys Meliates , 2024. +# Dimitrys Meliates , 2024, 2026. # Jim Spentzos , 2025, 2026. # Efstathios Iosifidis , 2026. 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:01+0000\n" +"Last-Translator: Jim Spentzos \n" "Language-Team: Greek \n" "Language: el\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.16.1\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -167,7 +167,7 @@ msgstr "" "Απαιτείται ταυτοποίηση για τη διαχείριση κλειδιών υπογραφής για αρχικούς " "καταλόγους." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -176,32 +176,32 @@ msgstr "" "Ο αρχικός κατάλογος του χρήστη %s απουσιάζει, παρακαλούμε συνδέστε την " "απαραίτητη συσκευή αποθήκευσης ή το σύστημα αρχείων υποστήριξης." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Πολύ συχνές προσπάθειες σύνδεσης για τον χρήστη %s, δοκιμάστε ξανά αργότερα." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Κωδικός πρόσβασης: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Ο κωδικός πρόσβασης είναι εσφαλμένος ή ανεπαρκής για την ταυτοποίηση του " "χρήστη %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Συγγνώμη, δοκιμάστε ξανά: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Κλειδί ανάκτησης: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -210,20 +210,20 @@ msgstr "" "Ο κωδικός πρόσβασης ή το κλειδί ανάκτησης είναι εσφαλμένα ή ανεπαρκή για την " "ταυτοποίηση του χρήστη %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Συγγνώμη, εισαγάγετε ξανά το κλειδί ανάκτησης: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Δοκιμάστε ξανά με κωδικό πρόσβασης: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -232,30 +232,30 @@ msgstr "" "Ο κωδικός πρόσβασης είναι εσφαλμένος ή ανεπαρκής, και το ρυθμισμένο " "διακριτικό ασφαλείας του χρήστη %s δεν έχει εισαχθεί." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "PIN διακριτικού ασφαλείας: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" "Παρακαλούμε ταυτοποιηθείτε φυσικά στο διακριτικό ασφαλείας του χρήστη %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" "Παρακαλούμε επιβεβαιώστε την παρουσία σας στο διακριτικό ασφαλείας του " "χρήστη %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" "Παρακαλούμε επαληθεύστε τον χρήστη στο διακριτικό ασφαλείας του χρήστη %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -263,84 +263,85 @@ msgstr "" "Το PIN του διακριτικού ασφαλείας είναι κλειδωμένο, παρακαλούμε ξεκλειδώστε " "το πρώτα. (Συμβουλή: Η αφαίρεση και επανεισαγωγή ίσως αρκεί.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "Το PIN του διακριτικού ασφαλείας είναι εσφαλμένο για τον χρήστη %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Συγγνώμη, προσπαθήστε ξανά το PIN του διακριτικού ασφαλείας: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "Το PIN διακριτικού ασφαλείας του χρήστη %s είναι εσφαλμένο (απέμειναν " "ελάχιστες προσπάθειες!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "Το PIN διακριτικού ασφαλείας του χρήστη %s είναι εσφαλμένο (απέμεινε μόνο " "μία προσπάθεια!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "Ο αρχικός κατάλογος του χρήστη %s είναι κλειδωμένος, παρακαλούμε ξεκλειδώστε " "τοπικά πρώτα." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" "Πάρα πολλές αποτυχημένες προσπάθειες σύνδεσης για τον χρήστη %s, άρνηση." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "Η εγγραφή χρήστη είναι αποκλεισμένη, απαγορεύεται η πρόσβαση." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "Η εγγραφή χρήστη δεν είναι ακόμη έγκυρη, απαγορεύεται η πρόσβαση." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "Η εγγραφή χρήστη δεν είναι πλέον έγκυρη, απαγορεύεται η πρόσβαση." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Η εγγραφή χρήστη δεν είναι έγκυρη, απαγορεύεται η πρόσβαση." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Πάρα πολλές συνδέσεις, δοκιμάστε ξανά σε %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Απαιτείται αλλαγή κωδικού πρόσβασης." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Ο κωδικός πρόσβασης έληξε, απαιτείται αλλαγή." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." -msgstr "Ο κωδικός πρόσβασης έληξε αλλά δεν μπορεί να αλλαχθεί, άρνηση σύνδεσης." +msgstr "" +"Ο κωδικός πρόσβασης έληξε αλλά δεν μπορεί να αλλαχθεί, άρνηση σύνδεσης." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Ο κωδικός πρόσβασης θα λήξει σύντομα, παρακαλούμε αλλάξτε τον." @@ -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" @@ -926,32 +929,41 @@ msgstr "" "εμπορευματοκιβωτίων." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Δημιουργία τοπικής εικονικής μηχανής ή εμπορευματοκιβωτίου" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Απαιτείται ταυτοποίηση για τη δημιουργία τοπικής εικονικής μηχανής ή " "εμπορευματοκιβωτίου." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Καταχώριση τοπικής εικονικής μηχανής ή εμπορευματοκιβωτίου" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Απαιτείται ταυτοποίηση για την καταχώριση τοπικής εικονικής μηχανής ή " "εμπορευματοκιβωτίου." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Διαχείριση ειδώλων τοπικών εικονικών μηχανών και εμπορευματοκιβωτίων" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -959,6 +971,16 @@ msgstr "" "Απαιτείται ταυτοποίηση για τη διαχείριση ειδώλων τοπικών εικονικών μηχανών " "και εμπορευματοκιβωτίων." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Ορισμός διακομιστών NTP" @@ -1005,7 +1027,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,9 +1097,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" @@ -1227,27 +1253,59 @@ msgstr "Επαναφορά στατιστικών" msgid "Authentication is required to reset statistics." msgstr "Απαιτείται ταυτοποίηση για την επαναφορά των στατιστικών." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Έλεγχος για ενημερώσεις συστήματος" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "Απαιτείται ταυτοποίηση για τον έλεγχο ενημερώσεων συστήματος." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Εγκατάσταση ενημερώσεων συστήματος" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "Απαιτείται ταυτοποίηση για την εγκατάσταση ενημερώσεων συστήματος." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Εγκατάσταση συγκεκριμένης έκδοσης συστήματος" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1255,20 +1313,37 @@ msgstr "" "Απαιτείται ταυτοποίηση για την ενημέρωση του συστήματος σε μια συγκεκριμένη " "(πιθανώς παλιά) έκδοση." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" -msgstr "Εκκαθάριση παλαιών ενημερώσεων συστήματος" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" msgstr "" -"Απαιτείται ταυτοποίηση για την εκκαθάριση παλαιών ενημερώσεων συστήματος." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Διαχείριση προαιρετικών χαρακτηριστικών" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "Απαιτείται ταυτοποίηση για τη διαχείριση προαιρετικών χαρακτηριστικών." @@ -1369,6 +1444,31 @@ msgstr "" "Απαιτείται ταυτοποίηση για την παύση (freeze) ή την επαναφορά (thaw) των " "διεργασιών της μονάδας '$(unit)'." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Εκκαθάριση παλαιών ενημερώσεων συστήματος" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "Απαιτείται ταυτοποίηση για την εκκαθάριση παλαιών ενημερώσεων συστήματος." + #, fuzzy #~ msgid "Authentication is required to kill '$(unit)'." #~ msgstr "Απαιτείται πιστοποίηση για να ορίσετε τοπικά όνομα οικοδεσπότη." diff --git a/po/eo.po b/po/eo.po new file mode 100644 index 0000000000000..f0b24c138c608 --- /dev/null +++ b/po/eo.po @@ -0,0 +1,1401 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Esperanto translation for systemd. +# +# Kristjan Schmidt , 2026. +msgid "" +msgstr "" +"Project-Id-Version: systemd\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:05+0000\n" +"Last-Translator: Kristjan Schmidt \n" +"Language-Team: Esperanto \n" +"Language: eo\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 2026.6.1\n" + +#: src/core/org.freedesktop.systemd1.policy.in:22 +msgid "Send passphrase back to system" +msgstr "Resendi pasfrazon al sistemo" + +#: src/core/org.freedesktop.systemd1.policy.in:23 +msgid "" +"Authentication is required to send the entered passphrase back to the system." +msgstr "" +"Aŭtentikigo estas postulata por resendi la enigitan pasfrazon al la sistemo." + +#: src/core/org.freedesktop.systemd1.policy.in:33 +msgid "Manage system services or other units" +msgstr "Administri sistemajn servojn aŭ aliajn unuojn" + +#: src/core/org.freedesktop.systemd1.policy.in:34 +msgid "Authentication is required to manage system services or other units." +msgstr "" +"Aŭtentikigo estas postulata por administri sistemajn servojn aŭ aliajn " +"unuojn." + +#: src/core/org.freedesktop.systemd1.policy.in:43 +msgid "Manage system service or unit files" +msgstr "Administri sistemajn serv-dosierojn aŭ dosierojn de unuoj" + +#: src/core/org.freedesktop.systemd1.policy.in:44 +msgid "Authentication is required to manage system service or unit files." +msgstr "" +"Aŭtentikigo estas postulata por administri sistemajn serv-dosierojn aŭ " +"dosierojn de unuoj." + +#: src/core/org.freedesktop.systemd1.policy.in:54 +msgid "Set or unset system and service manager environment variables" +msgstr "Agordi aŭ forigi medivarojn de la sistemo kaj serva administrilo" + +#: src/core/org.freedesktop.systemd1.policy.in:55 +msgid "" +"Authentication is required to set or unset system and service manager " +"environment variables." +msgstr "" +"Aŭtentikigo estas postulata por agordi aŭ forigi medivarojn de la sistemo " +"kaj serva administrilo." + +#: src/core/org.freedesktop.systemd1.policy.in:64 +msgid "Reload the systemd state" +msgstr "Reŝargi la staton de systemd" + +#: src/core/org.freedesktop.systemd1.policy.in:65 +msgid "Authentication is required to reload the systemd state." +msgstr "Aŭtentikigo estas postulata por reŝargi la staton de systemd." + +#: src/core/org.freedesktop.systemd1.policy.in:74 +msgid "Dump the systemd state without rate limits" +msgstr "Eligi la staton de systemd sen rapidolimoj" + +#: src/core/org.freedesktop.systemd1.policy.in:75 +msgid "" +"Authentication is required to dump the systemd state without rate limits." +msgstr "" +"Aŭtentikigo estas postulata por eligi la staton de systemd sen rapidolimoj." + +#: src/home/org.freedesktop.home1.policy:13 +msgid "Create a home area" +msgstr "Krei hejman areon" + +#: src/home/org.freedesktop.home1.policy:14 +msgid "Authentication is required to create a user's home area." +msgstr "Aŭtentikigo estas postulata por krei la hejman areon de uzanto." + +#: src/home/org.freedesktop.home1.policy:23 +msgid "Remove a home area" +msgstr "Forigi hejman areon" + +#: src/home/org.freedesktop.home1.policy:24 +msgid "Authentication is required to remove a user's home area." +msgstr "Aŭtentikigo estas postulata por forigi la hejman areon de uzanto." + +#: src/home/org.freedesktop.home1.policy:33 +msgid "Check credentials of a home area" +msgstr "Kontroli akreditaĵojn de hejma areo" + +#: src/home/org.freedesktop.home1.policy:34 +msgid "" +"Authentication is required to check credentials against a user's home area." +msgstr "" +"Aŭtentikigo estas postulata por kontroli akreditaĵojn kontraŭ la hejma areo " +"de uzanto." + +#: src/home/org.freedesktop.home1.policy:43 +msgid "Update a home area" +msgstr "Ĝisdatigi hejman areon" + +#: src/home/org.freedesktop.home1.policy:44 +msgid "Authentication is required to update a user's home area." +msgstr "Aŭtentikigo estas postulata por ĝisdatigi la hejman areon de uzanto." + +#: src/home/org.freedesktop.home1.policy:53 +msgid "Update your home area" +msgstr "Ĝisdatigi vian hejman areon" + +#: src/home/org.freedesktop.home1.policy:54 +msgid "Authentication is required to update your home area." +msgstr "Aŭtentikigo estas postulata por ĝisdatigi vian hejman areon." + +#: src/home/org.freedesktop.home1.policy:63 +msgid "Resize a home area" +msgstr "Regrandigi hejman areon" + +#: src/home/org.freedesktop.home1.policy:64 +msgid "Authentication is required to resize a user's home area." +msgstr "Aŭtentikigo estas postulata por regrandigi la hejman areon de uzanto." + +#: src/home/org.freedesktop.home1.policy:73 +msgid "Change password of a home area" +msgstr "Ŝanĝi pasvortojn de hejma areo" + +#: src/home/org.freedesktop.home1.policy:74 +msgid "" +"Authentication is required to change the password of a user's home area." +msgstr "" +"Aŭtentikigo estas postulata por ŝanĝi la pasvortojn de la hejma areo de " +"uzanto." + +#: src/home/org.freedesktop.home1.policy:83 +msgid "Activate a home area" +msgstr "Aktivigi hejman areon" + +#: src/home/org.freedesktop.home1.policy:84 +msgid "Authentication is required to activate a user's home area." +msgstr "Aŭtentikigo estas postulata por aktivigi la hejman areon de uzanto." + +#: src/home/org.freedesktop.home1.policy:93 +msgid "Manage Home Directory Signing Keys" +msgstr "Administri Subskribajn Ŝlosilojn de Hejmdosierujo" + +#: src/home/org.freedesktop.home1.policy:94 +msgid "Authentication is required to manage signing keys for home directories." +msgstr "" +"Aŭtentikigo estas postulata por administri subskribajn ŝlosilojn de " +"hejmdosierujoj." + +#: src/home/pam_systemd_home.c:327 +#, c-format +msgid "" +"Home of user %s is currently absent, please plug in the necessary storage " +"device or backing file system." +msgstr "" +"La hejmo de uzanto %s estas nuntempe forestanta, bonvolu enŝovi la necesan " +"stokadaparaton aŭ subtenan dosiersistemon." + +#: src/home/pam_systemd_home.c:332 +#, c-format +msgid "Too frequent login attempts for user %s, try again later." +msgstr "Tro oftaj ensalutaj provoj por uzanto %s, provu denove poste." + +#: src/home/pam_systemd_home.c:344 +msgid "Password: " +msgstr "Pasvorto: " + +#: src/home/pam_systemd_home.c:346 +#, c-format +msgid "Password incorrect or not sufficient for authentication of user %s." +msgstr "Pasvorto malĝusta aŭ ne sufiĉa por aŭtentikigi uzanton %s." + +#: src/home/pam_systemd_home.c:347 +msgid "Sorry, try again: " +msgstr "Pardonu, provu denove: " + +#: src/home/pam_systemd_home.c:369 +msgid "Recovery key: " +msgstr "Rekupera ŝlosilo: " + +#: src/home/pam_systemd_home.c:371 +#, c-format +msgid "" +"Password/recovery key incorrect or not sufficient for authentication of user " +"%s." +msgstr "" +"Pasvorto/rekupera ŝlosilo malĝusta aŭ ne sufiĉa por aŭtentikigi uzanton %s." + +#: src/home/pam_systemd_home.c:372 +msgid "Sorry, reenter recovery key: " +msgstr "Pardonu, retajpu la rekuperan ŝlosilon: " + +#: src/home/pam_systemd_home.c:392 +#, c-format +msgid "Security token of user %s not inserted." +msgstr "Sekureca ĵetono de uzanto %s ne enŝovita." + +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 +msgid "Try again with password: " +msgstr "Provu denove kun pasvorto: " + +#: src/home/pam_systemd_home.c:395 +#, c-format +msgid "" +"Password incorrect or not sufficient, and configured security token of user " +"%s not inserted." +msgstr "" +"Pasvorto malĝusta aŭ ne sufiĉa, kaj la agordita sekureca ĵetono de uzanto %s " +"ne enŝovita." + +#: src/home/pam_systemd_home.c:415 +msgid "Security token PIN: " +msgstr "PIN de sekureca ĵetono: " + +#: src/home/pam_systemd_home.c:432 +#, c-format +msgid "Please authenticate physically on security token of user %s." +msgstr "Bonvolu fizike aŭtentikiĝi sur la sekureca ĵetono de uzanto %s." + +#: src/home/pam_systemd_home.c:443 +#, c-format +msgid "Please confirm presence on security token of user %s." +msgstr "Bonvolu konfirmi ĉeeston sur la sekureca ĵetono de uzanto %s." + +#: src/home/pam_systemd_home.c:454 +#, c-format +msgid "Please verify user on security token of user %s." +msgstr "Bonvolu konfirmi uzanton sur la sekureca ĵetono de uzanto %s." + +#: src/home/pam_systemd_home.c:463 +msgid "" +"Security token PIN is locked, please unlock it first. (Hint: Removal and re-" +"insertion might suffice.)" +msgstr "" +"La PIN de la sekureca ĵetono estas ŝlosita, bonvolu malŝlosi ĝin unue. " +"(Sugesto: Elpreno kaj reenŝovo povas sufiĉi.)" + +#: src/home/pam_systemd_home.c:471 +#, c-format +msgid "Security token PIN incorrect for user %s." +msgstr "PIN de sekureca ĵetono malĝusta por uzanto %s." + +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 +msgid "Sorry, retry security token PIN: " +msgstr "Pardonu, provu denove la PIN de la sekureca ĵetono: " + +#: src/home/pam_systemd_home.c:490 +#, c-format +msgid "Security token PIN of user %s incorrect (only a few tries left!)" +msgstr "" +"PIN de sekureca ĵetono de uzanto %s malĝusta (nur malmultaj provoj restas!)" + +#: src/home/pam_systemd_home.c:509 +#, c-format +msgid "Security token PIN of user %s incorrect (only one try left!)" +msgstr "PIN de sekureca ĵetono de uzanto %s malĝusta (nur unu provo restas!)" + +#: src/home/pam_systemd_home.c:676 +#, c-format +msgid "Home of user %s is currently not active, please log in locally first." +msgstr "" +"La hejmo de uzanto %s estas nuntempe ne aktiva, bonvolu unue ensaluti loke." + +#: src/home/pam_systemd_home.c:678 +#, c-format +msgid "Home of user %s is currently locked, please unlock locally first." +msgstr "" +"La hejmo de uzanto %s estas nuntempe ŝlosita, bonvolu unue malŝlosi loke." + +#: src/home/pam_systemd_home.c:712 +#, c-format +msgid "Too many unsuccessful login attempts for user %s, refusing." +msgstr "Tro multaj malsukcesaj ensalutaj provoj por uzanto %s, rifuzante." + +#: src/home/pam_systemd_home.c:1021 +msgid "User record is blocked, prohibiting access." +msgstr "Uzanta rekordo estas blokita, malpermesante aliron." + +#: src/home/pam_systemd_home.c:1025 +msgid "User record is not valid yet, prohibiting access." +msgstr "Uzanta rekordo ankoraŭ ne estas valida, malpermesante aliron." + +#: src/home/pam_systemd_home.c:1029 +msgid "User record is not valid anymore, prohibiting access." +msgstr "Uzanta rekordo ne plu estas valida, malpermesante aliron." + +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 +msgid "User record not valid, prohibiting access." +msgstr "Uzanta rekordo ne valida, malpermesante aliron." + +#: src/home/pam_systemd_home.c:1044 +#, c-format +msgid "Too many logins, try again in %s." +msgstr "Tro multaj ensalutoj, provu denove post %s." + +#: src/home/pam_systemd_home.c:1055 +msgid "Password change required." +msgstr "Pasvorta ŝanĝo estas postulata." + +#: src/home/pam_systemd_home.c:1059 +msgid "Password expired, change required." +msgstr "Pasvorto eksvalidiĝis, ŝanĝo estas postulata." + +#: src/home/pam_systemd_home.c:1065 +msgid "Password is expired, but can't change, refusing login." +msgstr "Pasvorto eksvalidiĝis, sed ne eblas ŝanĝi, rifuzante ensalutadon." + +#: src/home/pam_systemd_home.c:1069 +msgid "Password will expire soon, please change." +msgstr "Pasvorto baldaŭ eksvalidiĝos, bonvolu ŝanĝi." + +#: src/hostname/org.freedesktop.hostname1.policy:20 +msgid "Set hostname" +msgstr "Agordi hostnomon" + +#: src/hostname/org.freedesktop.hostname1.policy:21 +msgid "Authentication is required to set the local hostname." +msgstr "Aŭtentikigo estas postulata por agordi la lokan hostnomon." + +#: src/hostname/org.freedesktop.hostname1.policy:30 +msgid "Set static hostname" +msgstr "Agordi statikan hostnomon" + +#: 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 "" +"Aŭtentikigo estas postulata por agordi la statike agorditan lokan hostnomon, " +"same kiel la belan hostnomon." + +#: src/hostname/org.freedesktop.hostname1.policy:41 +msgid "Set machine information" +msgstr "Agordi maŝinajn informojn" + +#: src/hostname/org.freedesktop.hostname1.policy:42 +msgid "Authentication is required to set local machine information." +msgstr "Aŭtentikigo estas postulata por agordi lokajn maŝinajn informojn." + +#: src/hostname/org.freedesktop.hostname1.policy:51 +msgid "Get product UUID" +msgstr "Akiri produkt-UUID" + +#: src/hostname/org.freedesktop.hostname1.policy:52 +msgid "Authentication is required to get product UUID." +msgstr "Aŭtentikigo estas postulata por akiri la produkt-UUID." + +#: src/hostname/org.freedesktop.hostname1.policy:61 +msgid "Get hardware serial number" +msgstr "Akiri aparataran serionan numeron" + +#: src/hostname/org.freedesktop.hostname1.policy:62 +msgid "Authentication is required to get hardware serial number." +msgstr "Aŭtentikigo estas postulata por akiri la aparataran serionan numeron." + +#: src/hostname/org.freedesktop.hostname1.policy:71 +msgid "Get system description" +msgstr "Akiri sistemajn priskribojn" + +#: src/hostname/org.freedesktop.hostname1.policy:72 +msgid "Authentication is required to get system description." +msgstr "Aŭtentikigo estas postulata por akiri la sistemajn priskribojn." + +#: src/import/org.freedesktop.import1.policy:22 +msgid "Import a disk image" +msgstr "Importi diskan bildon" + +#: src/import/org.freedesktop.import1.policy:23 +msgid "Authentication is required to import an image." +msgstr "Aŭtentikigo estas postulata por importi bildon." + +#: src/import/org.freedesktop.import1.policy:32 +msgid "Export a disk image" +msgstr "Eksporti diskan bildon" + +#: src/import/org.freedesktop.import1.policy:33 +msgid "Authentication is required to export disk image." +msgstr "Aŭtentikigo estas postulata por eksporti diskan bildon." + +#: src/import/org.freedesktop.import1.policy:42 +msgid "Download a disk image" +msgstr "Elŝuti diskan bildon" + +#: src/import/org.freedesktop.import1.policy:43 +msgid "Authentication is required to download a disk image." +msgstr "Aŭtentikigo estas postulata por elŝuti diskan bildon." + +#: src/import/org.freedesktop.import1.policy:52 +msgid "Cancel transfer of a disk image" +msgstr "Nuligi translokigon de diska bildo" + +#: src/import/org.freedesktop.import1.policy:53 +msgid "" +"Authentication is required to cancel the ongoing transfer of a disk image." +msgstr "" +"Aŭtentikigo estas postulata por nuligi la daŭrantan translokigon de diska " +"bildo." + +#: src/locale/org.freedesktop.locale1.policy:22 +msgid "Set system locale" +msgstr "Agordi sistemajn lokarojn" + +#: src/locale/org.freedesktop.locale1.policy:23 +msgid "Authentication is required to set the system locale." +msgstr "Aŭtentikigo estas postulata por agordi la sistemajn lokarojn." + +#: src/locale/org.freedesktop.locale1.policy:33 +msgid "Set system keyboard settings" +msgstr "Agordi sistemajn klavarajn agordojn" + +#: src/locale/org.freedesktop.locale1.policy:34 +msgid "Authentication is required to set the system keyboard settings." +msgstr "" +"Aŭtentikigo estas postulata por agordi la sistemajn klavarajn agordojn." + +#: src/login/org.freedesktop.login1.policy:22 +msgid "Allow applications to inhibit system shutdown" +msgstr "Permesi al aplikaĵoj malhelpi sistemmalŝaltadon" + +#: src/login/org.freedesktop.login1.policy:23 +msgid "" +"Authentication is required for an application to inhibit system shutdown." +msgstr "" +"Aŭtentikigo estas postulata por ke aplikaĵo malhelpu sistemmalŝaltadon." + +#: src/login/org.freedesktop.login1.policy:33 +msgid "Allow applications to delay system shutdown" +msgstr "Permesi al aplikaĵoj prokrasti sistemmalŝaltadon" + +#: src/login/org.freedesktop.login1.policy:34 +msgid "Authentication is required for an application to delay system shutdown." +msgstr "" +"Aŭtentikigo estas postulata por ke aplikaĵo prokrastu sistemmalŝaltadon." + +#: src/login/org.freedesktop.login1.policy:44 +msgid "Allow applications to inhibit system sleep" +msgstr "Permesi al aplikaĵoj malhelpi sistemdormon" + +#: src/login/org.freedesktop.login1.policy:45 +msgid "Authentication is required for an application to inhibit system sleep." +msgstr "Aŭtentikigo estas postulata por ke aplikaĵo malhelpu sistemdormon." + +#: src/login/org.freedesktop.login1.policy:55 +msgid "Allow applications to delay system sleep" +msgstr "Permesi al aplikaĵoj prokrasti sistemdormon" + +#: src/login/org.freedesktop.login1.policy:56 +msgid "Authentication is required for an application to delay system sleep." +msgstr "Aŭtentikigo estas postulata por ke aplikaĵo prokrastu sistemdormon." + +#: src/login/org.freedesktop.login1.policy:65 +msgid "Allow applications to inhibit automatic system suspend" +msgstr "Permesi al aplikaĵoj malhelpi aŭtomatan sistemsuspendadon" + +#: src/login/org.freedesktop.login1.policy:66 +msgid "" +"Authentication is required for an application to inhibit automatic system " +"suspend." +msgstr "" +"Aŭtentikigo estas postulata por ke aplikaĵo malhelpu aŭtomatan " +"sistemsuspendadon." + +#: src/login/org.freedesktop.login1.policy:75 +msgid "Allow applications to inhibit system handling of the power key" +msgstr "Permesi al aplikaĵoj malhelpi sistemtraktadon de la ŝaltbutono" + +#: src/login/org.freedesktop.login1.policy:76 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the power key." +msgstr "" +"Aŭtentikigo estas postulata por ke aplikaĵo malhelpu sistemtraktadon de la " +"ŝaltbutono." + +#: src/login/org.freedesktop.login1.policy:86 +msgid "Allow applications to inhibit system handling of the suspend key" +msgstr "Permesi al aplikaĵoj malhelpi sistemtraktadon de la suspenda butono" + +#: src/login/org.freedesktop.login1.policy:87 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the suspend key." +msgstr "" +"Aŭtentikigo estas postulata por ke aplikaĵo malhelpu sistemtraktadon de la " +"suspenda butono." + +#: src/login/org.freedesktop.login1.policy:97 +msgid "Allow applications to inhibit system handling of the hibernate key" +msgstr "Permesi al aplikaĵoj malhelpi sistemtraktadon de la hibernada butono" + +#: src/login/org.freedesktop.login1.policy:98 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the hibernate key." +msgstr "" +"Aŭtentikigo estas postulata por ke aplikaĵo malhelpu sistemtraktadon de la " +"hibernada butono." + +#: src/login/org.freedesktop.login1.policy:107 +msgid "Allow applications to inhibit system handling of the lid switch" +msgstr "Permesi al aplikaĵoj malhelpi sistemtraktadon de la kovrilŝaltilo" + +#: src/login/org.freedesktop.login1.policy:108 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the lid switch." +msgstr "" +"Aŭtentikigo estas postulata por ke aplikaĵo malhelpu sistemtraktadon de la " +"kovrilŝaltilo." + +#: src/login/org.freedesktop.login1.policy:117 +msgid "Allow applications to inhibit system handling of the reboot key" +msgstr "Permesi al aplikaĵoj malhelpi sistemtraktadon de la reekzekutbutono" + +#: src/login/org.freedesktop.login1.policy:118 +msgid "" +"Authentication is required for an application to inhibit system handling of " +"the reboot key." +msgstr "" +"Aŭtentikigo estas postulata por ke aplikaĵo malhelpu sistemtraktadon de la " +"reekzekutbutono." + +#: src/login/org.freedesktop.login1.policy:128 +msgid "Allow non-logged-in user to run programs" +msgstr "Permesi al neensalutinta uzanto ruli programojn" + +#: src/login/org.freedesktop.login1.policy:129 +msgid "Explicit request is required to run programs as a non-logged-in user." +msgstr "" +"Eksplicita peto estas postulata por ruli programojn kiel neensalutinta " +"uzanto." + +#: src/login/org.freedesktop.login1.policy:138 +msgid "Allow non-logged-in users to run programs" +msgstr "Permesi al neensalutintaj uzantoj ruli programojn" + +#: src/login/org.freedesktop.login1.policy:139 +msgid "Authentication is required to run programs as a non-logged-in user." +msgstr "" +"Aŭtentikigo estas postulata por ruli programojn kiel neensalutinta uzanto." + +#: src/login/org.freedesktop.login1.policy:148 +msgid "Allow attaching devices to seats" +msgstr "Permesi alfiksi aparatojn al sidejoj" + +#: src/login/org.freedesktop.login1.policy:149 +msgid "Authentication is required to attach a device to a seat." +msgstr "Aŭtentikigo estas postulata por alfiksi aparaton al sidejon." + +#: src/login/org.freedesktop.login1.policy:159 +msgid "Flush device to seat attachments" +msgstr "Forviŝi alfiksiĝojn de aparatoj al sidejoj" + +#: src/login/org.freedesktop.login1.policy:160 +msgid "Authentication is required to reset how devices are attached to seats." +msgstr "" +"Aŭtentikigo estas postulata por restarigi kiel aparatoj estas alfiksitaj al " +"sidejoj." + +#: src/login/org.freedesktop.login1.policy:169 +msgid "Power off the system" +msgstr "Malŝalti la sistemon" + +#: src/login/org.freedesktop.login1.policy:170 +msgid "Authentication is required to power off the system." +msgstr "Aŭtentikigo estas postulata por malŝalti la sistemon." + +#: src/login/org.freedesktop.login1.policy:180 +msgid "Power off the system while other users are logged in" +msgstr "Malŝalti la sistemon dum aliaj uzantoj estas ensalutintaj" + +#: src/login/org.freedesktop.login1.policy:181 +msgid "" +"Authentication is required to power off the system while other users are " +"logged in." +msgstr "" +"Aŭtentikigo estas postulata por malŝalti la sistemon dum aliaj uzantoj estas " +"ensalutintaj." + +#: src/login/org.freedesktop.login1.policy:191 +msgid "Power off the system while an application is inhibiting this" +msgstr "Malŝalti la sistemon dum aplikaĵo malhelpas ĉi tion" + +#: src/login/org.freedesktop.login1.policy:192 +msgid "" +"Authentication is required to power off the system while an application is " +"inhibiting this." +msgstr "" +"Aŭtentikigo estas postulata por malŝalti la sistemon dum aplikaĵo malhelpas " +"ĉi tion." + +#: src/login/org.freedesktop.login1.policy:202 +msgid "Reboot the system" +msgstr "Reekzekuti la sistemon" + +#: src/login/org.freedesktop.login1.policy:203 +msgid "Authentication is required to reboot the system." +msgstr "Aŭtentikigo estas postulata por reekzekuti la sistemon." + +#: src/login/org.freedesktop.login1.policy:213 +msgid "Reboot the system while other users are logged in" +msgstr "Reekzekuti la sistemon dum aliaj uzantoj estas ensalutintaj" + +#: src/login/org.freedesktop.login1.policy:214 +msgid "" +"Authentication is required to reboot the system while other users are logged " +"in." +msgstr "" +"Aŭtentikigo estas postulata por reekzekuti la sistemon dum aliaj uzantoj " +"estas ensalutintaj." + +#: src/login/org.freedesktop.login1.policy:224 +msgid "Reboot the system while an application is inhibiting this" +msgstr "Reekzekuti la sistemon dum aplikaĵo malhelpas ĉi tion" + +#: src/login/org.freedesktop.login1.policy:225 +msgid "" +"Authentication is required to reboot the system while an application is " +"inhibiting this." +msgstr "" +"Aŭtentikigo estas postulata por reekzekuti la sistemon dum aplikaĵo " +"malhelpas ĉi tion." + +#: src/login/org.freedesktop.login1.policy:235 +msgid "Halt the system" +msgstr "Haltigi la sistemon" + +#: src/login/org.freedesktop.login1.policy:236 +msgid "Authentication is required to halt the system." +msgstr "Aŭtentikigo estas postulata por haltigi la sistemon." + +#: src/login/org.freedesktop.login1.policy:246 +msgid "Halt the system while other users are logged in" +msgstr "Haltigi la sistemon dum aliaj uzantoj estas ensalutintaj" + +#: src/login/org.freedesktop.login1.policy:247 +msgid "" +"Authentication is required to halt the system while other users are logged " +"in." +msgstr "" +"Aŭtentikigo estas postulata por haltigi la sistemon dum aliaj uzantoj estas " +"ensalutintaj." + +#: src/login/org.freedesktop.login1.policy:257 +msgid "Halt the system while an application is inhibiting this" +msgstr "Haltigi la sistemon dum aplikaĵo malhelpas ĉi tion" + +#: src/login/org.freedesktop.login1.policy:258 +msgid "" +"Authentication is required to halt the system while an application is " +"inhibiting this." +msgstr "" +"Aŭtentikigo estas postulata por haltigi la sistemon dum aplikaĵo malhelpas " +"ĉi tion." + +#: src/login/org.freedesktop.login1.policy:268 +msgid "Suspend the system" +msgstr "Suspendi la sistemon" + +#: src/login/org.freedesktop.login1.policy:269 +msgid "Authentication is required to suspend the system." +msgstr "Aŭtentikigo estas postulata por suspendi la sistemon." + +#: src/login/org.freedesktop.login1.policy:278 +msgid "Suspend the system while other users are logged in" +msgstr "Suspendi la sistemon dum aliaj uzantoj estas ensalutintaj" + +#: src/login/org.freedesktop.login1.policy:279 +msgid "" +"Authentication is required to suspend the system while other users are " +"logged in." +msgstr "" +"Aŭtentikigo estas postulata por suspendi la sistemon dum aliaj uzantoj estas " +"ensalutintaj." + +#: src/login/org.freedesktop.login1.policy:289 +msgid "Suspend the system while an application is inhibiting this" +msgstr "Suspendi la sistemon dum aplikaĵo malhelpas ĉi tion" + +#: src/login/org.freedesktop.login1.policy:290 +msgid "" +"Authentication is required to suspend the system while an application is " +"inhibiting this." +msgstr "" +"Aŭtentikigo estas postulata por suspendi la sistemon dum aplikaĵo malhelpas " +"ĉi tion." + +#: src/login/org.freedesktop.login1.policy:300 +msgid "Hibernate the system" +msgstr "Hibernigi la sistemon" + +#: src/login/org.freedesktop.login1.policy:301 +msgid "Authentication is required to hibernate the system." +msgstr "Aŭtentikigo estas postulata por hibernigi la sistemon." + +#: src/login/org.freedesktop.login1.policy:310 +msgid "Hibernate the system while other users are logged in" +msgstr "Hibernigi la sistemon dum aliaj uzantoj estas ensalutintaj" + +#: src/login/org.freedesktop.login1.policy:311 +msgid "" +"Authentication is required to hibernate the system while other users are " +"logged in." +msgstr "" +"Aŭtentikigo estas postulata por hibernigi la sistemon dum aliaj uzantoj " +"estas ensalutintaj." + +#: src/login/org.freedesktop.login1.policy:321 +msgid "Hibernate the system while an application is inhibiting this" +msgstr "Hibernigi la sistemon dum aplikaĵo malhelpas ĉi tion" + +#: src/login/org.freedesktop.login1.policy:322 +msgid "" +"Authentication is required to hibernate the system while an application is " +"inhibiting this." +msgstr "" +"Aŭtentikigo estas postulata por hibernigi la sistemon dum aplikaĵo malhelpas " +"ĉi tion." + +#: src/login/org.freedesktop.login1.policy:332 +msgid "Manage active sessions, users and seats" +msgstr "Administri aktivajn seancojn, uzantojn kaj sidejojn" + +#: src/login/org.freedesktop.login1.policy:333 +msgid "Authentication is required to manage active sessions, users and seats." +msgstr "" +"Aŭtentikigo estas postulata por administri aktivajn seancojn, uzantojn kaj " +"sidejojn." + +#: src/login/org.freedesktop.login1.policy:342 +msgid "Lock or unlock active sessions" +msgstr "Ŝlosi aŭ malŝlosi aktivajn seancojn" + +#: src/login/org.freedesktop.login1.policy:343 +msgid "Authentication is required to lock or unlock active sessions." +msgstr "Aŭtentikigo estas postulata por ŝlosi aŭ malŝlosi aktivajn seancojn." + +#: src/login/org.freedesktop.login1.policy:352 +msgid "Set the reboot \"reason\" in the kernel" +msgstr "Agordi la reekzekutan \"kialon\" en la kerno" + +#: src/login/org.freedesktop.login1.policy:353 +msgid "Authentication is required to set the reboot \"reason\" in the kernel." +msgstr "" +"Aŭtentikigo estas postulata por agordi la reekzekutan \"kialon\" en la kerno." + +#: src/login/org.freedesktop.login1.policy:363 +msgid "Indicate to the firmware to boot to setup interface" +msgstr "Indiki al la firmvaro ekstarti al la agordinterfaco" + +#: src/login/org.freedesktop.login1.policy:364 +msgid "" +"Authentication is required to indicate to the firmware to boot to setup " +"interface." +msgstr "" +"Aŭtentikigo estas postulata por indiki al la firmvaro ekstarti al la " +"agordinterfaco." + +#: src/login/org.freedesktop.login1.policy:374 +msgid "Indicate to the boot loader to boot to the boot loader menu" +msgstr "Indiki al la startŝargilo ekstarti al la startŝargila menuo" + +#: 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 "" +"Aŭtentikigo estas postulata por indiki al la startŝargilo ekstarti al la " +"startŝargila menuo." + +#: src/login/org.freedesktop.login1.policy:385 +msgid "Indicate to the boot loader to boot a specific entry" +msgstr "Indiki al la startŝargilo ekstarti specifan eniron" + +#: 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 "" +"Aŭtentikigo estas postulata por indiki al la startŝargilo ekstarti en " +"specifan startŝargilaeniron." + +#: src/login/org.freedesktop.login1.policy:396 +msgid "Set a wall message" +msgstr "Agordi mur-mesaĝon" + +#: src/login/org.freedesktop.login1.policy:397 +msgid "Authentication is required to set a wall message." +msgstr "Aŭtentikigo estas postulata por agordi mur-mesaĝon." + +#: src/login/org.freedesktop.login1.policy:406 +msgid "Change Session" +msgstr "Ŝanĝi Seanceon" + +#: src/login/org.freedesktop.login1.policy:407 +msgid "Authentication is required to change the virtual terminal." +msgstr "Aŭtentikigo estas postulata por ŝanĝi la virtualan terminalon." + +#: src/machine/org.freedesktop.machine1.policy:22 +msgid "Log into a local container" +msgstr "Ensaluti en lokan ujon" + +#: src/machine/org.freedesktop.machine1.policy:23 +msgid "Authentication is required to log into a local container." +msgstr "Aŭtentikigo estas postulata por ensaluti en lokan ujon." + +#: src/machine/org.freedesktop.machine1.policy:32 +msgid "Log into the local host" +msgstr "Ensaluti en la lokan hoston" + +#: src/machine/org.freedesktop.machine1.policy:33 +msgid "Authentication is required to log into the local host." +msgstr "Aŭtentikigo estas postulata por ensaluti en la lokan hoston." + +#: src/machine/org.freedesktop.machine1.policy:42 +msgid "Acquire a shell in a local container" +msgstr "Akiri ŝelon en loka ujo" + +#: src/machine/org.freedesktop.machine1.policy:43 +msgid "Authentication is required to acquire a shell in a local container." +msgstr "Aŭtentikigo estas postulata por akiri ŝelon en loka ujo." + +#: src/machine/org.freedesktop.machine1.policy:53 +msgid "Acquire a shell on the local host" +msgstr "Akiri ŝelon sur la loka hosto" + +#: src/machine/org.freedesktop.machine1.policy:54 +msgid "Authentication is required to acquire a shell on the local host." +msgstr "Aŭtentikigo estas postulata por akiri ŝelon sur la loka hosto." + +#: src/machine/org.freedesktop.machine1.policy:64 +msgid "Acquire a pseudo TTY in a local container" +msgstr "Akiri pseŭdan TTY en loka ujo" + +#: src/machine/org.freedesktop.machine1.policy:65 +msgid "" +"Authentication is required to acquire a pseudo TTY in a local container." +msgstr "Aŭtentikigo estas postulata por akiri pseŭdan TTY en loka ujo." + +#: src/machine/org.freedesktop.machine1.policy:74 +msgid "Acquire a pseudo TTY on the local host" +msgstr "Akiri pseŭdan TTY sur la loka hosto" + +#: src/machine/org.freedesktop.machine1.policy:75 +msgid "Authentication is required to acquire a pseudo TTY on the local host." +msgstr "Aŭtentikigo estas postulata por akiri pseŭdan TTY sur la loka hosto." + +#: src/machine/org.freedesktop.machine1.policy:84 +msgid "Manage local virtual machines and containers" +msgstr "Administri lokajn virtualajn maŝinojn kaj ujojn" + +#: src/machine/org.freedesktop.machine1.policy:85 +msgid "" +"Authentication is required to manage local virtual machines and containers." +msgstr "" +"Aŭtentikigo estas postulata por administri lokajn virtualajn maŝinojn kaj " +"ujojn." + +#: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Inspekti lokajn virtualajn maŝinojn kaj ujojn" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"Aŭtentikigo estas postulata por inspekti lokajn virtualajn maŝinojn kaj " +"ujojn." + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" +msgstr "Krei lokan virtualan maŝinon aŭ ujon" + +#: src/machine/org.freedesktop.machine1.policy:106 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "Aŭtentikigo estas postulata por krei lokan virtualan maŝinon aŭ ujon." + +#: src/machine/org.freedesktop.machine1.policy:116 +msgid "Register a local virtual machine or container" +msgstr "Registri lokan virtualan maŝinon aŭ ujon" + +#: src/machine/org.freedesktop.machine1.policy:117 +msgid "" +"Authentication is required to register a local virtual machine or container." +msgstr "" +"Aŭtentikigo estas postulata por registri lokan virtualan maŝinon aŭ ujon." + +#: src/machine/org.freedesktop.machine1.policy:126 +msgid "Manage local virtual machine and container images" +msgstr "Administri bildojn de lokaj virtualaj maŝinoj kaj ujoj" + +#: src/machine/org.freedesktop.machine1.policy:127 +msgid "" +"Authentication is required to manage local virtual machine and container " +"images." +msgstr "" +"Aŭtentikigo estas postulata por administri bildojn de lokaj virtualaj " +"maŝinoj kaj ujoj." + +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Inspekti bildojn de lokaj virtualaj maŝinoj kaj ujoj" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"Aŭtentikigo estas postulata por inspekti bildojn de lokaj virtualaj maŝinoj " +"kaj ujoj." + +#: src/network/org.freedesktop.network1.policy:22 +msgid "Set NTP servers" +msgstr "Agordi NTP-servilojn" + +#: src/network/org.freedesktop.network1.policy:23 +msgid "Authentication is required to set NTP servers." +msgstr "Aŭtentikigo estas postulata por agordi NTP-servilojn." + +#: src/network/org.freedesktop.network1.policy:33 +#: src/resolve/org.freedesktop.resolve1.policy:44 +msgid "Set DNS servers" +msgstr "Agordi DNS-servilojn" + +#: src/network/org.freedesktop.network1.policy:34 +#: src/resolve/org.freedesktop.resolve1.policy:45 +msgid "Authentication is required to set DNS servers." +msgstr "Aŭtentikigo estas postulata por agordi DNS-servilojn." + +#: src/network/org.freedesktop.network1.policy:44 +#: src/resolve/org.freedesktop.resolve1.policy:55 +msgid "Set domains" +msgstr "Agordi domenojn" + +#: src/network/org.freedesktop.network1.policy:45 +#: src/resolve/org.freedesktop.resolve1.policy:56 +msgid "Authentication is required to set domains." +msgstr "Aŭtentikigo estas postulata por agordi domenojn." + +#: src/network/org.freedesktop.network1.policy:55 +#: src/resolve/org.freedesktop.resolve1.policy:66 +msgid "Set default route" +msgstr "Agordi defaŭltan vojon" + +#: src/network/org.freedesktop.network1.policy:56 +#: src/resolve/org.freedesktop.resolve1.policy:67 +msgid "Authentication is required to set default route." +msgstr "Aŭtentikigo estas postulata por agordi defaŭltan vojon." + +#: src/network/org.freedesktop.network1.policy:66 +#: src/resolve/org.freedesktop.resolve1.policy:77 +msgid "Enable/disable LLMNR" +msgstr "Aktivigi/malaktivigi 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 "Aŭtentikigo estas postulata por aktivigi aŭ malaktivigi LLMNR." + +#: src/network/org.freedesktop.network1.policy:77 +#: src/resolve/org.freedesktop.resolve1.policy:88 +msgid "Enable/disable multicast DNS" +msgstr "Aktivigi/malaktivigi multjetan 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 "Aŭtentikigo estas postulata por aktivigi aŭ malaktivigi multjetan DNS." + +#: src/network/org.freedesktop.network1.policy:88 +#: src/resolve/org.freedesktop.resolve1.policy:99 +msgid "Enable/disable DNS over TLS" +msgstr "Aktivigi/malaktivigi DNS super 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 "Aŭtentikigo estas postulata por aktivigi aŭ malaktivigi DNS super TLS." + +#: src/network/org.freedesktop.network1.policy:99 +#: src/resolve/org.freedesktop.resolve1.policy:110 +msgid "Enable/disable DNSSEC" +msgstr "Aktivigi/malaktivigi 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 "Aŭtentikigo estas postulata por aktivigi aŭ malaktivigi DNSSEC." + +#: src/network/org.freedesktop.network1.policy:110 +#: src/resolve/org.freedesktop.resolve1.policy:121 +msgid "Set DNSSEC Negative Trust Anchors" +msgstr "Agordi DNSSEC Negativajn Fidankrojn" + +#: 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 "Aŭtentikigo estas postulata por agordi DNSSEC-negativajn fidankrojn." + +#: src/network/org.freedesktop.network1.policy:121 +msgid "Revert NTP settings" +msgstr "Restarigi NTP-agordojn" + +#: src/network/org.freedesktop.network1.policy:122 +msgid "Authentication is required to reset NTP settings." +msgstr "Aŭtentikigo estas postulata por restarigi NTP-agordojn." + +#: src/network/org.freedesktop.network1.policy:132 +msgid "Revert DNS settings" +msgstr "Restarigi DNS-agordojn" + +#: src/network/org.freedesktop.network1.policy:133 +msgid "Authentication is required to reset DNS settings." +msgstr "Aŭtentikigo estas postulata por restarigi DNS-agordojn." + +#: src/network/org.freedesktop.network1.policy:143 +msgid "DHCP server sends force renew message" +msgstr "DHCP-servilo sendas devigan renovigan mesaĝon" + +#: src/network/org.freedesktop.network1.policy:144 +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"Aŭtentikigo estas postulata por sendi devigan renovigan mesaĝon de la DHCP-" +"servilo." + +#: src/network/org.freedesktop.network1.policy:154 +msgid "Renew dynamic addresses" +msgstr "Renovigi dinamikajn adresojn" + +#: src/network/org.freedesktop.network1.policy:155 +msgid "Authentication is required to renew dynamic addresses." +msgstr "Aŭtentikigo estas postulata por renovigi dinamikajn adresojn." + +#: src/network/org.freedesktop.network1.policy:165 +msgid "Reload network settings" +msgstr "Reŝargi retajn agordojn" + +#: src/network/org.freedesktop.network1.policy:166 +msgid "Authentication is required to reload network settings." +msgstr "Aŭtentikigo estas postulata por reŝargi retajn agordojn." + +#: src/network/org.freedesktop.network1.policy:176 +msgid "Reconfigure network interface" +msgstr "Reagordi retinterfacon" + +#: src/network/org.freedesktop.network1.policy:177 +msgid "Authentication is required to reconfigure network interface." +msgstr "Aŭtentikigo estas postulata por reagordi retinterfacon." + +#: src/network/org.freedesktop.network1.policy:187 +msgid "Specify whether persistent storage for systemd-networkd is available" +msgstr "Precizigi ĉu persistenta stokado por systemd-networkd estas disponebla" + +#: src/network/org.freedesktop.network1.policy:188 +msgid "" +"Authentication is required to specify whether persistent storage for systemd-" +"networkd is available." +msgstr "" +"Aŭtentikigo estas postulata por precizigi ĉu persistenta stokado por systemd-" +"networkd estas disponebla." + +#: src/network/org.freedesktop.network1.policy:198 +msgid "Manage network links" +msgstr "Administri retajn ligilojn" + +#: src/network/org.freedesktop.network1.policy:199 +msgid "Authentication is required to manage network links." +msgstr "Aŭtentikigo estas postulata por administri retajn ligilojn." + +#: src/portable/org.freedesktop.portable1.policy:13 +msgid "Inspect a portable service image" +msgstr "Inspekti porteblan servobildon" + +#: src/portable/org.freedesktop.portable1.policy:14 +msgid "Authentication is required to inspect a portable service image." +msgstr "Aŭtentikigo estas postulata por inspekti porteblan servobildon." + +#: src/portable/org.freedesktop.portable1.policy:23 +msgid "Attach or detach a portable service image" +msgstr "Alfiksi aŭ malfiksi porteblan servobildon" + +#: src/portable/org.freedesktop.portable1.policy:24 +msgid "" +"Authentication is required to attach or detach a portable service image." +msgstr "" +"Aŭtentikigo estas postulata por alfiksi aŭ malfiksi porteblan servobildon." + +#: src/portable/org.freedesktop.portable1.policy:34 +msgid "Delete or modify portable service image" +msgstr "Forigi aŭ modifi porteblan servobildon" + +#: src/portable/org.freedesktop.portable1.policy:35 +msgid "" +"Authentication is required to delete or modify a portable service image." +msgstr "" +"Aŭtentikigo estas postulata por forigi aŭ modifi porteblan servobildon." + +#: src/resolve/org.freedesktop.resolve1.policy:22 +msgid "Register a DNS-SD service" +msgstr "Registri DNS-SD-servon" + +#: src/resolve/org.freedesktop.resolve1.policy:23 +msgid "Authentication is required to register a DNS-SD service." +msgstr "Aŭtentikigo estas postulata por registri DNS-SD-servon." + +#: src/resolve/org.freedesktop.resolve1.policy:33 +msgid "Unregister a DNS-SD service" +msgstr "Malregistri DNS-SD-servon" + +#: src/resolve/org.freedesktop.resolve1.policy:34 +msgid "Authentication is required to unregister a DNS-SD service." +msgstr "Aŭtentikigo estas postulata por malregistri DNS-SD-servon." + +#: src/resolve/org.freedesktop.resolve1.policy:132 +msgid "Revert name resolution settings" +msgstr "Restarigi agordojn de nomsolvo" + +#: src/resolve/org.freedesktop.resolve1.policy:133 +msgid "Authentication is required to reset name resolution settings." +msgstr "Aŭtentikigo estas postulata por restarigi agordojn de nomsolvo." + +#: src/resolve/org.freedesktop.resolve1.policy:143 +msgid "Subscribe query results" +msgstr "Aboni demandorezultojn" + +#: src/resolve/org.freedesktop.resolve1.policy:144 +msgid "Authentication is required to subscribe query results." +msgstr "Aŭtentikigo estas postulata por aboni demandorezultojn." + +#: src/resolve/org.freedesktop.resolve1.policy:154 +msgid "Subscribe to DNS configuration" +msgstr "Aboni DNS-konfiguradon" + +#: src/resolve/org.freedesktop.resolve1.policy:155 +msgid "Authentication is required to subscribe to DNS configuration." +msgstr "Aŭtentikigo estas postulata por aboni DNS-konfiguradon." + +#: src/resolve/org.freedesktop.resolve1.policy:165 +msgid "Dump cache" +msgstr "Eligi kaŝmemoron" + +#: src/resolve/org.freedesktop.resolve1.policy:166 +msgid "Authentication is required to dump cache." +msgstr "Aŭtentikigo estas postulata por eligi kaŝmemoron." + +#: src/resolve/org.freedesktop.resolve1.policy:176 +msgid "Dump server state" +msgstr "Eligi servilstaton" + +#: src/resolve/org.freedesktop.resolve1.policy:177 +msgid "Authentication is required to dump server state." +msgstr "Aŭtentikigo estas postulata por eligi servilstaton." + +#: src/resolve/org.freedesktop.resolve1.policy:187 +msgid "Dump statistics" +msgstr "Eligi statistikojn" + +#: src/resolve/org.freedesktop.resolve1.policy:188 +msgid "Authentication is required to dump statistics." +msgstr "Aŭtentikigo estas postulata por eligi statistikojn." + +#: src/resolve/org.freedesktop.resolve1.policy:198 +msgid "Reset statistics" +msgstr "Restarigi statistikojn" + +#: src/resolve/org.freedesktop.resolve1.policy:199 +msgid "Authentication is required to reset statistics." +msgstr "Aŭtentikigo estas postulata por restarigi statistikojn." + +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "Forviŝi DNS-kaŝmemorojn" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "Aŭtentikigo estas postulata por forviŝi DNS-kaŝmemorojn." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Restarigi servilajn funkciojn" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "Aŭtentikigo estas postulata por restarigi servilajn funkciojn." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 +msgid "Check for system updates" +msgstr "Kontroli sistemajn ĝisdatigojn" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 +msgid "Authentication is required to check for system updates." +msgstr "Aŭtentikigo estas postulata por kontroli sistemajn ĝisdatigojn." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Nuligi kontroladon de sistemaj ĝisdatigoj" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" +"Aŭtentikigo estas postulata por nuligi kontroladon de sistemaj ĝisdatigoj." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 +msgid "Install system updates" +msgstr "Instali sistemajn ĝisdatigojn" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 +msgid "Authentication is required to install system updates." +msgstr "Aŭtentikigo estas postulata por instali sistemajn ĝisdatigojn." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Nuligi instaladon de sistemaj ĝisdatigoj" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" +"Aŭtentikigo estas postulata por nuligi instaladon de sistemaj ĝisdatigoj." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 +msgid "Install specific system version" +msgstr "Instali specifan sisteman version" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 +msgid "" +"Authentication is required to update the system to a specific (possibly old) " +"version." +msgstr "" +"Aŭtentikigo estas postulata por ĝisdatigi la sistemon al specifa (eble " +"malnova) versio." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Nuligi instaladon de specifa sistema versio" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" +"Aŭtentikigo estas postulata por nuligi ĝisdatigon de la sistemo al specifa " +"(eble malnova) versio." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "Forigi malnovajn sistemajn ĝisdatigojn" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" +"Aŭtentikigo estas postulata por forigi malnovajn sistemajn ĝisdatigojn." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Nuligi forigon de malnovaj sistemaj ĝisdatigoj" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" +"Aŭtentikigo estas postulata por nuligi forigon de malnovaj sistemaj " +"ĝisdatigoj." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 +msgid "Manage optional features" +msgstr "Administri elekteblajn funkciojn" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 +msgid "Authentication is required to manage optional features." +msgstr "Aŭtentikigo estas postulata por administri elekteblajn funkciojn." + +#: src/timedate/org.freedesktop.timedate1.policy:22 +msgid "Set system time" +msgstr "Agordi sistemtempon" + +#: src/timedate/org.freedesktop.timedate1.policy:23 +msgid "Authentication is required to set the system time." +msgstr "Aŭtentikigo estas postulata por agordi la sistemtempon." + +#: src/timedate/org.freedesktop.timedate1.policy:33 +msgid "Set system timezone" +msgstr "Agordi sisteman horzonon" + +#: src/timedate/org.freedesktop.timedate1.policy:34 +msgid "Authentication is required to set the system timezone." +msgstr "Aŭtentikigo estas postulata por agordi la sisteman horzonon." + +#: src/timedate/org.freedesktop.timedate1.policy:43 +msgid "Set RTC to local timezone or UTC" +msgstr "Agordi RTC al loka horzono aŭ UTC" + +#: src/timedate/org.freedesktop.timedate1.policy:44 +msgid "" +"Authentication is required to control whether the RTC stores the local or " +"UTC time." +msgstr "" +"Aŭtentikigo estas postulata por kontroli ĉu la RTC stokas la lokan aŭ UTC-" +"tempon." + +#: src/timedate/org.freedesktop.timedate1.policy:53 +msgid "Turn network time synchronization on or off" +msgstr "Ŝalti aŭ malŝalti retan temposinkronigon" + +#: src/timedate/org.freedesktop.timedate1.policy:54 +msgid "" +"Authentication is required to control whether network time synchronization " +"shall be enabled." +msgstr "" +"Aŭtentikigo estas postulata por kontroli ĉu reta temposinkronigo estas " +"aktiva." + +#: src/core/dbus-unit.c:372 +msgid "Authentication is required to start '$(unit)'." +msgstr "Aŭtentikigo estas postulata por startigi '$(unit)'." + +#: src/core/dbus-unit.c:373 +msgid "Authentication is required to stop '$(unit)'." +msgstr "Aŭtentikigo estas postulata por haltigi '$(unit)'." + +#: src/core/dbus-unit.c:374 +msgid "Authentication is required to reload '$(unit)'." +msgstr "Aŭtentikigo estas postulata por reŝargi '$(unit)'." + +#: src/core/dbus-unit.c:375 src/core/dbus-unit.c:376 +msgid "Authentication is required to restart '$(unit)'." +msgstr "Aŭtentikigo estas postulata por restartigi '$(unit)'." + +#: src/core/dbus-unit.c:568 +msgid "" +"Authentication is required to send a UNIX signal to the processes of '$" +"(unit)'." +msgstr "" +"Aŭtentikigo estas postulata por sendi UNIKSAN signalon al la procezoj de '$" +"(unit)'." + +#: src/core/dbus-unit.c:621 +msgid "" +"Authentication is required to send a UNIX signal to the processes of " +"subgroup of '$(unit)'." +msgstr "" +"Aŭtentikigo estas postulata por sendi UNIKSAN signalon al la procezoj de " +"subgrupo de '$(unit)'." + +#: src/core/dbus-unit.c:649 +msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." +msgstr "" +"Aŭtentikigo estas postulata por restarigi la \"malsukcesa\" staton de '$" +"(unit)'." + +#: src/core/dbus-unit.c:679 +msgid "Authentication is required to set properties on '$(unit)'." +msgstr "Aŭtentikigo estas postulata por agordi atributojn de '$(unit)'." + +#: src/core/dbus-unit.c:776 +msgid "" +"Authentication is required to delete files and directories associated with '$" +"(unit)'." +msgstr "" +"Aŭtentikigo estas postulata por forigi dosierojn kaj dosierujojn asociitajn " +"kun '$(unit)'." + +#: src/core/dbus-unit.c:813 +msgid "" +"Authentication is required to freeze or thaw the processes of '$(unit)' unit." +msgstr "" +"Aŭtentikigo estas postulata por frostigi aŭ degeli la procezojn de unuo '$" +"(unit)'." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "Bonvolu konfirmi ĉeeston sur la sekureca ĵetono por malŝlosi." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "Bonvolu konfirmi uzanton sur la sekureca ĵetono por malŝlosi." + +#: src/shared/libfido2-util.c:936 +#, fuzzy, c-format +#| msgid "Please enter security token PIN:" +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "Bonvolu enigi la PIN de la sekureca ĵetono:" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Bonvolu enigi la PIN de la sekureca ĵetono:" diff --git a/po/es.po b/po/es.po index 70e0eda54a345..f12aca538be75 100644 --- a/po/es.po +++ b/po/es.po @@ -4,16 +4,16 @@ # Alex Puchades , 2015. # Daniel Mustieles , 2015. # Álex Puchades , 2015. -# Adolfo Jayme Barrientos , 2020, 2021. -# Emilio Herrera , 2021. -# Jose Ortuno , 2025. +# Adolfo Jayme Barrientos , 2020, 2021, 2026. +# Emilio Herrera , 2021, 2026. +# Jose Ortuno , 2025, 2026. # Javier Francisco , 2025. -# "Fco. Javier F. Serrador" , 2025. +# "Fco. Javier F. Serrador" , 2025, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-11-29 03:49+0000\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-25 10:17+0000\n" "Last-Translator: \"Fco. Javier F. Serrador\" \n" "Language-Team: Spanish \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.14.3\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -167,7 +167,7 @@ msgstr "" "Requiere autenticarse para gestionar llaves de firmas para directorios " "iniciales." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -176,32 +176,32 @@ msgstr "" "El directorio principal del usuario %s no existe, por favor conecte el " "dispositivo de almacenamiento necesario o el sistema de archivos de respaldo." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Intentos de inicio de sesión demasiado frecuentes para el usuario %s, " "inténtelo de nuevo más tarde." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Contraseña: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Contraseña incorrecta o insuficiente para la autenticación del usuario %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Lo siento, inténtalo de nuevo: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Llave de recuperación: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -210,20 +210,20 @@ msgstr "" "Contraseña/clave de recuperación incorrecta o insuficiente para la " "autenticación del usuario %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Lo siento, vuelva a introducir la clave de recuperación: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "No se ha insertado el token de seguridad del usuario %s." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Inténtalo de nuevo con la contraseña: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -232,30 +232,30 @@ msgstr "" "Contraseña incorrecta o insuficiente, y token de seguridad configurado del " "usuario %s no insertado." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "PIN del token de seguridad: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" "Por favor, autentifíquese físicamente con el token de seguridad del usuario " "%s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" "Por favor, confirme la presencia en el token de seguridad del usuario %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" "Por favor, verifique el usuario en el token de seguridad del usuario %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -263,85 +263,85 @@ msgstr "" "El PIN de la ficha de seguridad está bloqueado, desbloquéelo primero. " "(Sugerencia: puede bastar con extraerlo y volver a insertarlo)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "PIN de seguridad incorrecto para el usuario %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Lo siento, vuelva a intentar el PIN de seguridad: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "PIN de seguridad del usuario %s incorrecto (¡sólo quedan unos pocos " "intentos!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "PIN de seguridad del usuario %s incorrecto (¡sólo queda un intento!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "La página de inicio del usuario %s no está activa en este momento, por favor " "conéctese localmente primero." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "La carpeta Home del usuario %s está actualmente bloqueada, por favor " "desbloquéela localmente primero." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" "Demasiados intentos de inicio de sesión fallidos para el usuario %s, " "rechazando." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "El registro de usuario está bloqueado, lo que prohíbe el acceso." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "El registro de usuario aún no es válido, lo que prohíbe el acceso." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "El registro de usuario ya no es válido, lo que prohíbe el acceso." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Registro de usuario no válido, prohibiendo el acceso." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Demasiados inicios de sesión, inténtelo de nuevo en %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Es necesario cambiar la contraseña." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Contraseña caducada, cambio necesario." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" "La contraseña ha caducado, pero no se puede cambiar, rechazando el inicio de " "sesión." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "La contraseña caducará pronto, por favor cámbiela." @@ -916,29 +916,40 @@ msgstr "" "contenedores locales." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Inspecciona máquinas virtuales locales y contenedores" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"La autenticación está requerida para inspeccionar máquinas virtuales locales " +"y contenedores." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Crea una maquina virtual local o un contenedor" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "Necesita autenticarse para crear una máquina virtual o un contenedor." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Registrar una máquina virtual local o contenedor" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Requiere autenticación para registrar una máquina virtual local o contenedor." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Administrar imágenes de máquina virtual y de contenedores locales" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -946,6 +957,18 @@ msgstr "" "Necesita autenticarse para administrar las imágenes de máquina virtual y de " "contenedores locales." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Inspecciona máquina virtual local e imágenes de contenedor" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"La autenticación está requerida para inspeccionar máquina virtual local e " +"imágenes de contenedor." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Establecer servidores NTP" @@ -1054,11 +1077,15 @@ msgstr "Necesita autenticarse para restablecer la configuración de DNS." #: src/network/org.freedesktop.network1.policy:143 msgid "DHCP server sends force renew message" -msgstr "El servidor DCHP envía un mensaje de renovación forzada" +msgstr "El servidor DHCP envía un mensaje de renovación forzada" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." -msgstr "Necesita autenticarse para enviar el mensaje de renovación forzada." +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." +msgstr "" +"Necesita autenticarse para enviar un mensaje de renovación forzada desde el " +"servidor DHCP." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1100,11 +1127,11 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Gestionar enlaces de red" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "La autenticación está requerida para gestionar enlaces de red." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" @@ -1210,27 +1237,65 @@ msgstr "Inicializa estadisticas" msgid "Authentication is required to reset statistics." msgstr "Necesita autenticarse para restablecer las estadisticas." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "Purga de cachés de DNS" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "Se requiere autenticación para purgar las cachés de DNS." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Restablece características del servidor" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" +"La autenticación está requerida para restablecimiento de características del " +"servidor." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Busca actualizaciones del sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "Necesita autenticarse para buscar actualizaciones del sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Cancelar comprobante para actualizaciones del sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" +"La autenticación está requerida para cancelar comprobante para " +"actualizaciones del sistema." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Instala actualizaciones del sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "Necesita autenticarse para instalar las actualizaciones del sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Cancela instalación de actualizaciones del sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" +"Se requiere autenticación para cancelar actualizaciones de la instalación " +"del sistema." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Instala la version especifica del sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1238,20 +1303,42 @@ msgstr "" "Necesita autenticarse para actualizar el sistema a una, posiblemente, " "version anterior." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" -msgstr "Limpia actualizaciones anteriores" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Cancela instalación específica de la versión del sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" +"La autenticación está requerida para cancelar actualización del sistema para " +"una versión específica (posiblemente antigua)." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "Purga las actualizaciones anteriores del sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." msgstr "" -"Necesita autenticarse para borrar actualizaciones anteriores del sistema." +"Se requiere autenticación para purgar actualizaciones anteriores del sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Anula purgado de actualizaciones antiguas de sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" +"Se requiere autenticación para cancelar purga de actualizaciones antiguas " +"del sistema." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Gestiona caracteristicas opcionales" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "Necesita autenticarse para administrar las caracteristicas opcionales." @@ -1350,6 +1437,33 @@ msgid "" msgstr "" "Se requiere autenticación para parar o reiniciar los procesos de '$(unit)'." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "Confirme la presencia en el vale de seguridad para desbloquear." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "Verifique al usuario en el vale de seguridad para desbloquear." + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" +"Introduzca el PIN del vale de seguridad (intentos restantes antes del " +"bloqueo: %d):" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Introduzca el PIN del vale de seguridad:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Limpia actualizaciones anteriores" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "Necesita autenticarse para borrar actualizaciones anteriores del sistema." + #~ msgid "Authentication is required to kill '$(unit)'." #~ msgstr "Se requiere autenticación para matar a '$(unit)'." diff --git a/po/et.po b/po/et.po index 4425b680f8050..ec609b91a4986 100644 --- a/po/et.po +++ b/po/et.po @@ -1,13 +1,13 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # H A , 2022, 2025. -# Henri Aunin , 2025. -# Priit Jõerüüt , 2025. +# Henri Aunin , 2025, 2026. +# Priit Jõerüüt , 2025, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-12-17 01:58+0000\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:04+0000\n" "Last-Translator: Priit Jõerüüt " "\n" "Language-Team: Estonian , 2023. +# Asier Sarasua Garmendia , 2023, 2026. 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: 2023-06-03 15:48+0000\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:58+0000\n" "Last-Translator: Asier Sarasua Garmendia \n" "Language-Team: Basque \n" +"main/eu/>\n" "Language: eu\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 4.17\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -158,156 +158,156 @@ msgstr "" "Autentifikazioa behar da sistema-zerbitzuak edo beste unitate batzuk " "kudeatzeko." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "" -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "" -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "" -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "" @@ -814,36 +814,55 @@ msgid "" msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 -msgid "Create a local virtual machine or container" +msgid "Inspect local virtual machines and containers" msgstr "" #: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:106 #, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." msgstr "Autentifikazioa behar da makina-informazio lokala ezartzeko." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "Autentifikazioa behar da makina-informazio lokala ezartzeko." -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." msgstr "Autentifikazioa behar da makina-informazio lokala ezartzeko." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "" @@ -953,8 +972,12 @@ msgid "DHCP server sends force renew message" msgstr "" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +#, fuzzy +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" +"Autentifikazioa behar da sartutako pasaesaldia bueltan sistemara bidaltzeko." #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1106,49 +1129,98 @@ msgstr "" msgid "Authentication is required to reset statistics." msgstr "Autentifikazioa behar da sistemaren eskualde-ezarpenak ezartzeko." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 #, fuzzy msgid "Authentication is required to check for system updates." msgstr "Autentifikazioa behar da sistemaren eskualde-ezarpenak ezartzeko." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 #, fuzzy msgid "Authentication is required to install system updates." msgstr "Autentifikazioa behar da sistemaren eskualde-ezarpenak ezartzeko." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 #, fuzzy msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." msgstr "Autentifikazioa behar da sistemaren teklatuaren ezarpenak ezartzeko." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy -msgid "Authentication is required to cleanup old system updates." -msgstr "Autentifikazioa behar da systemd egoera birkargatzeko." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 #, fuzzy msgid "Authentication is required to manage optional features." msgstr "" @@ -1240,3 +1312,25 @@ msgstr "" msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#, fuzzy +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "Autentifikazioa behar da systemd egoera birkargatzeko." diff --git a/po/fi.po b/po/fi.po index 095027fd88596..af909e1953b48 100644 --- a/po/fi.po +++ b/po/fi.po @@ -1,15 +1,15 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # Finnish translation of systemd. -# Jan Kuparinen , 2021, 2022, 2023. -# Ricky Tigg , 2022, 2024, 2025. -# Jiri Grönroos , 2024. +# Jan Kuparinen , 2021, 2022, 2023, 2026. +# Ricky Tigg , 2022, 2024, 2025, 2026. +# Jiri Grönroos , 2024, 2026. 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:57+0000\n" +"Last-Translator: Jiri Grönroos \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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -155,7 +155,7 @@ msgstr "" "Todennus vaaditaan kotihakemistojen allekirjoitusavaimien hallitsemista " "varten." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -164,31 +164,31 @@ msgstr "" "Käyttäjän %s kotihakemisto ei ole tällä hetkellä saatavissa, liitä " "tarvittava tallennuslaite tai taustatiedostojärjestelmä." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Liian nopeat peräkkäiset kirjautumisyritykset käyttäjälle %s, yritä " "uudelleen myöhemmin." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Salasana: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "Salasana on virheellinen tai ei riitä käyttäjän %s todentamiseen." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Yritä uudelleen: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Palautusavain: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -197,20 +197,20 @@ msgstr "" "Salasana/palautusavain on virheellinen tai se ei riitä käyttäjän %s " "todentamiseen." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Anna palautusavain uudelleen: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "Käyttäjän %s suojaustunnusta ei ole laitettu sisään." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Yritä uudelleen salasanalla: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -219,26 +219,26 @@ msgstr "" "Salasana on virheellinen tai riittämätön, eikä käyttäjän %s määritettyä " "suojaustunnusta ei ole laitettu sisään." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "Turvatunnuksen PIN-koodi: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "Todenna fyysisesti käyttäjän %s suojaustunnuksella." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "Vahvista käyttäjän %s suojaustunnuksen olemassaolo." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "Vahvista käyttäjä käyttäjän %s suojaustunnuksella." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -246,85 +246,85 @@ msgstr "" "Turvatunnuksen PIN-koodi on lukittu, avaa se ensin. (Vihje: Poistaminen ja " "uudelleen asettaminen saattaa riittää.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "Suojaustunnuksen PIN-koodi virheellinen käyttäjälle %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Yritä uudelleen suojaustunnuksen PIN-koodi: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "Käyttäjän %s suojaustunnuksen PIN-koodi on virheellinen (vain muutama yritys " "jäljellä!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "Käyttäjän %s suojaustunnuksen PIN-koodi on virheellinen (vain yksi yritys " "jäljellä!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "Käyttäjän %s kotihakemisto ei ole tällä hetkellä aktiivinen, kirjaudu ensin " "paikallisesti sisään." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "Käyttäjän %s kotihakemisto on tällä hetkellä lukittu, avaa lukitus ensin " "paikallisesti." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" "Liian monta epäonnistunutta kirjautumisyritystä käyttäjälle %s, " "kieltäydytään." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "Käyttäjätietue on estetty, mikä estää pääsyn." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "Käyttäjätietue ei ole vielä voimassa, mikä estää pääsyn." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "Käyttäjätietue ei ole enää voimassa, mikä estää pääsyn." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Käyttäjätietue ei ole voimassa, mikä estää pääsyn." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Liian monta kirjautumista, yritä uudelleen ajassa %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Salasanan vaihto vaaditaan." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Salasana vanhentunut, vaaditaan vaihto." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "Salasana on vanhentunut, mutta ei voi vaihtaa, estetään kirjautuminen." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Salasana vanhenee pian, vaihda se." @@ -876,29 +876,38 @@ msgstr "" "Todennus vaaditaan paikallisten virtuaalikoneiden ja konttien hallintaan." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Luo paikallinen virtuaalikone tai säilö" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "Todennus vaaditaan paikallisen virtuaalikoneen tai säilön luomiseen." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Rekisteröi paikallinen virtuaalikone tai kontti" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Todennus vaaditaan paikallisen virtuaalikoneen tai kontin rekisteröimiseksi." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Hallitse paikallisia virtuaalikoneita ja konttilevykuvia" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -906,6 +915,16 @@ msgstr "" "Todennus vaaditaan paikallisten virtuaalikoneiden ja konttilevykuvien " "hallintaan." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Aseta NTP-palvelimet" @@ -1022,8 +1041,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 +1086,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" @@ -1171,27 +1194,59 @@ msgstr "Nollaa tilastot" msgid "Authentication is required to reset statistics." msgstr "Todennus vaaditaan tilastojen nollaamiseen." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Tarkista, onko järjestelmäpäivityksiä" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "Todennus vaaditaan järjestelmäpäivitysten tarkistamiseen." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Asenna järjestelmäpäivitykset" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "Todennus vaaditaan järjestelmäpäivitysten asentamiseen." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Asenna tietty järjestelmäversio" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1199,19 +1254,37 @@ msgstr "" "Todennus vaaditaan järjestelmän päivittämiseen tiettyyn, mahdollisesti " "vanhaan versioon." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" -msgstr "Puhdista vanhat järjestelmäpäivitykset" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." -msgstr "Todennus vaaditaan vanhojen järjestelmäpäivitysten puhdistamiseen." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Hallitse valinnaisia ominaisuuksia" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "Todennus vaaditaan valinnaisten ominaisuuksien hallintaan." @@ -1309,3 +1382,27 @@ msgid "" msgstr "" "Todennus vaaditaan '$(unit)'-yksikön prosessien jäädyttämiseksi tai " "sulattamiseksi." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Puhdista vanhat järjestelmäpäivitykset" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "Todennus vaaditaan vanhojen järjestelmäpäivitysten puhdistamiseen." diff --git a/po/fr.po b/po/fr.po index f4757167cb665..21cc88571d563 100644 --- a/po/fr.po +++ b/po/fr.po @@ -5,14 +5,14 @@ # # Julien Humbert , 2020, 2021. # Arnaud T. , 2021. -# blutch112 , 2022. +# blutch112 , 2022, 2026. # Léane GRASSER , 2023, 2024, 2025, 2026. -# Weblate Translation Memory , 2024. +# Weblate Translation Memory , 2024, 2026. 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-09 09:04+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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -177,7 +177,7 @@ msgstr "" "Une authentification est requise pour gérer les clés de signature des " "répertoires personnels." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -186,32 +186,32 @@ msgstr "" "L'espace personnel de %s est actuellement absent, veuillez connecter le " "périphérique ou le système de fichiers qui le contient." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Trop de tentatives de connexion à l'utilisateur %s, réessayez plus tard." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Mot de passe : " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Mot de passe incorrect ou insuffisant pour l'authentification de " "l'utilisateur %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Désolé, réessayez : " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Clé de récupération : " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -220,20 +220,20 @@ msgstr "" "Mot de passe ou clé de récupération incorrecte ou insuffisante pour " "l'authentification de l'utilisateur %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Désolé, saisissez à nouveau la clé de récupération : " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "Clé de sécurité de l'utilisateur %s non insérée." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Réessayez avec un mot de passe : " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -242,30 +242,30 @@ msgstr "" "Mot de passe incorrect ou insuffisant, et la clé de sécurité de " "l'utilisateur %s n'est pas insérée." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "PIN de la clé de sécurité : " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" "Veuillez vous authentifier physiquement à l'aide de la clé de sécurité de " "l'utilisateur %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" "Veuillez confirmer votre présence sur la clé de sécurité de l'utilisateur %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" "Veuillez confirmer l'utilisateur sur la clé de sécurité de l'utilisateur %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -273,83 +273,83 @@ msgstr "" "Le PIN de la clé de sécurité est verrouillé, veuillez commencer par le " "déverrouiller. (Astuce : débrancher et rebrancher peut suffire.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "PIN de la clé de sécurité incorrect pour l'utilisateur %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Désolé, saisissez à nouveau le PIN de la clé de sécurité : " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "PIN de la clé de sécurité de l'utilisateur %s incorrect (plus que quelques " "essais restants !)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "PIN de la clé de sécurité de l'utilisateur %s incorrect (plus qu'un essai " "restant !)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "L'espace personnel de l'utilisateur %s est actuellement inactif, veuillez " "vous connecter localement dans un premier temps." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "L'espace personnel de l'utilisateur %s est actuellement verrouillé, veuillez " "commencer par un déverrouillage local." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "Trop d'échecs de connexion pour l'utilisateur %s, refus." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "L'utilisateur est bloqué, accès refusé." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "L'utilisateur n'est pas encore valide, accès refusé." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "L'utilisateur n'est plus valide, accès refusé." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Utilisateur invalide, accès refusé." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Trop de tentatives de connexion, réessayez dans %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Modification du mot de passe obligatoire." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Mot de passe expiré, modification obligatoire." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "Mot de passe expiré et impossible à modifier, connexion refusée." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Le mot de passe va bientôt expirer, veuillez le modifier." @@ -941,32 +941,43 @@ msgstr "" "conteneurs locaux." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Inspecter les machines virtuelles et conteneurs locaux" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"Une authentification est requise pour inspecter les machines virtuelles et " +"conteneurs locaux." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Créer une machine virtuelle locale ou un conteneur local" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Une authentification est requise pour créer une machine virtuelle locale ou " "un conteneur local." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Enregistrer une machine virtuelle locale ou un conteneur local" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Une authentification est requise pour enregistrer une machine virtuelle " "locale ou un conteneur local." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Gérer les images des machines virtuelles et des conteneurs locaux" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -974,6 +985,18 @@ msgstr "" "Une authentification est requise pour gérer les images des machines " "virtuelles et des conteneurs locaux." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Inspecter les images locales de machines virtuelles et de conteneurs" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"Une authentification est requise pour inspecter les images locales de " +"machines virtuelles et de conteneurs." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Définir les serveurs NTP" @@ -1089,10 +1112,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" @@ -1253,29 +1278,67 @@ msgstr "Réinitialiser les statistiques" msgid "Authentication is required to reset statistics." msgstr "Une authentification est requise pour réinitialiser les statistiques." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "Vider les caches DNS" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "Une authentification est requise pour vider les caches DNS." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Réinitialiser les fonctionnalités de serveur" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" +"Une authentification est requise pour réinitialiser les fonctionnalités de " +"serveur." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Rechercher des mises à jour système" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "" "Une authentification est requise pour rechercher des mises à jour système." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Annuler la recherche de mises à jour système" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" +"Une authentification est requise pour annuler la recherche de mises à jour " +"système." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Installer des mises à jour système" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "" "Une authentification est requise pour installer des mises à jour système." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Annuler l'installation de mises à jour système" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" +"Une authentification est requise pour annuler l'installation de mises à jour " +"système." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Installer une version spécifique du système" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1283,21 +1346,43 @@ msgstr "" "Une authentification est requise pour mettre à jour le système vers une " "version spécifique (et potentiellement ancienne)." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Annuler l'installation d'une version spécifique du système" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" +"Une authentification est requise pour annuler l'installation d'une version " +"spécifique (qui peut être ancienne) du système." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" msgstr "Nettoyer les anciennes mises à jour système" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." msgstr "" "Une authentification est requise pour nettoyer les anciennes mises à jour " "système." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Annuler le nettoyage des anciennes mises à jour système" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" +"Une authentification est requise pour annuler le nettoyage des anciennes " +"mises à jour système." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Gérer les fonctionnalités facultatives" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "" "Une authentification est requise pour gérer les fonctionnalités facultatives." @@ -1401,6 +1486,37 @@ msgstr "" "Une authentification est requise pour geler ou dégeler les processus de " "l'unité '$(unit)'." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" +"Veuillez confirmer votre présence sur la clef de sécurité pour le " +"déverrouillage." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" +"Veuillez confirmer l'utilisateur sur la clef de sécurité pour le " +"déverrouillage." + +#: src/shared/libfido2-util.c:936 +#, fuzzy, c-format +#| msgid "Please enter security token PIN:" +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "Veuillez entrer le code PIN de la clef de sécurité:" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Veuillez entrer le code PIN de la clef de sécurité:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Nettoyer les anciennes mises à jour système" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "Une authentification est requise pour nettoyer les anciennes mises à jour " +#~ "système." + #~ msgid "" #~ "Authentication is required to halt the system while an application asked " #~ "to inhibit it." diff --git a/po/gl.po b/po/gl.po index e6fa18890fcd9..e58ef760ea114 100644 --- a/po/gl.po +++ b/po/gl.po @@ -1,21 +1,21 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # Fran Dieguez , 2015. -# Fran Diéguez , 2023. +# Fran Diéguez , 2023, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2023-04-14 18:20+0000\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:01+0000\n" "Last-Translator: Fran Diéguez \n" "Language-Team: Galician \n" +"systemd/main/gl/>\n" "Language: gl\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 4.15.2\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -166,156 +166,156 @@ msgstr "" "Requírese autenticación para xestionar os servizos do sistema ou outras " "unidades." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "" -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "" -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "" -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "" @@ -896,11 +896,20 @@ msgstr "" "locais." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 #, fuzzy msgid "Create a local virtual machine or container" msgstr "Xestionar máquinas virtuais e contenedores locais" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 #, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." @@ -908,12 +917,12 @@ msgstr "" "Requírese autenticación para xestionar máquinas virtuais e contenedores " "locais." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "Xestionar máquinas virtuais e contenedores locais" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." @@ -921,11 +930,11 @@ msgstr "" "Requírese autenticación para xestionar máquinas virtuais e contenedores " "locais." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Xestionar imaxes locais virtuais e contenedores locais" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -933,6 +942,16 @@ msgstr "" "Requírese autenticación para xestionar imaxes de máquinas virtuais e " "contenedores locais." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Estabelecer os servidores NTP" @@ -1045,7 +1064,10 @@ msgid "DHCP server sends force renew message" msgstr "O servidor DHCP envía un mensaxe de renovación forzada" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +#, fuzzy +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "Requírese autenticación para enviar o mensaxe de renovación forzada." #: src/network/org.freedesktop.network1.policy:154 @@ -1203,49 +1225,98 @@ msgstr "" msgid "Authentication is required to reset statistics." msgstr "Requírese autenticación para restabelecer as preferencias de NTP." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 #, fuzzy msgid "Authentication is required to check for system updates." msgstr "Requírese autenticación para estabelecer a hora do sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 #, fuzzy msgid "Authentication is required to install system updates." msgstr "Requírese autenticación para estabelecer a hora do sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 #, fuzzy msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." msgstr "Requírese autenticación para estabelecer o fuso horario do sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy -msgid "Authentication is required to cleanup old system updates." -msgstr "Requírese autenticación para estabelecer a hora do sistema." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 #, fuzzy msgid "Authentication is required to manage optional features." msgstr "" @@ -1347,6 +1418,28 @@ msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "Requírese autenticación para conxelar o proceso da unidade '$(unit)'." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#, fuzzy +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "Requírese autenticación para estabelecer a hora do sistema." + #~ msgid "" #~ "Authentication is required to halt the system while an application asked " #~ "to inhibit it." diff --git a/po/he.po b/po/he.po index 9488a792190ea..f01f39654796e 100644 --- a/po/he.po +++ b/po/he.po @@ -5,8 +5,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-22 23:58+0000\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:00+0000\n" "Last-Translator: Yaron Shahrabani \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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -146,7 +146,7 @@ msgstr "ניהול מפתחות חתימת תיקיית הבית" msgid "Authentication is required to manage signing keys for home directories." msgstr "נדרש אימות כדי לנהל מפתחות חתימה לתיקיות בית." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -155,75 +155,75 @@ msgstr "" "למשתמש %s חסר בית, נא לחבר את התקן האחסון הנחוץ או את מערכת הקבצים שמגבה " "אותו." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "ניסיונות כניסה תכופים מדי למשתמש %s, נא לנסות שוב מאוחר יותר." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "סיסמה: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "הסיסמה שגויה או בלתי הולמת לאימות המשתמש %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "מחילה, נא לנסות שוב: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "מפתח שחזור: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "סיסמה/מפתח שחזור שגויים או לא מספיקים לאימות המשתמש %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "מחילה, נא לספק את מפתח השחזור מחדש: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "נא לנסות שוב עם סיסמה: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "קוד אישי לאסימון אבטחה: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "נא לבצע אימות פיזי באסימון האבטחה למשתמש %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "נא לאשר את נוכחות אסימון האבטחה למשתמש %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "נא לאמת את המשתמש באסימון האבטחה למשתמש %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -231,76 +231,76 @@ msgstr "" "אסימון האבטחה נעול בקוד אישי (PIN), נא לשחרר אותו תחילה. (הסרה: ניתוק וחיבור " "מחדש אמורים להספיק.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "הקוד האישי (PIN) למשתמש %s שגוי." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "מחילה, נא לנסות שוב קוד אסימון אבטחה אישי (PIN): " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "קוד אסימון אבטחה אישי (PIN) למשתמש %s שגוי (נותרו עוד מספר ניסיונות בודדים!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "קוד אסימון אבטחה אישי (PIN) למשתמש %s שגוי (נותר עוד ניסיון אחד!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "הבית של המשתמש %s נעול כרגע, נא לשחרר אותו מקומית תחילה." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "יותר מדי ניסיונות כניסה כושלים למשתמש %s, יסורב מעתה." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "רשומת המשתמש חסומה, הגישה נמנעת." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "רשומת המשתמש לא תקפה עדיין, הגישה נמנעת." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "רשומת המשתמש אינה תקפה עוד, הגישה נמנעת." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "רשומת המשתמש אינה תקפה, הגישה נמנעת." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "יותר מדי ניסיונות כניסה, כדאי לנסות שוב בעוד %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "צריך לשנות את הסיסמה." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "תוקף הסיסמה פג, צריך להחליף." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "תוקף הסיסמה פג, אך אי אפשר להחליף, הכניסה מסורבת." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "תוקף הסיסמה יפוג בקרוב, נא להחליף." @@ -798,36 +798,55 @@ msgstr "" "נדרש אימות כדי לנהל מכונות וירטואליות (VM) ומכולות (container) מקומיות." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "יצירת מכונה וירטואלית או מכולה מקומיות" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "נדרש אימות כדי ליצור מכונות וירטואליות (VM) או מכולות (container) מקומיות." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "רישום מכונה וירטואלית או מכולה מקומיות" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "נדרש אימות כדי לרשום מכונות וירטואליות (VM) או מכולות (container) מקומיות." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "ניהול דמויות של מכונות וירטואליות ומכולות" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "" "נדרש אימות לניהול דמויות של מכונות וירטואליות (VM) ומכולות (container)." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "הגדרת שרתי NTP" @@ -938,8 +957,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 +998,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" @@ -1081,45 +1102,95 @@ msgstr "איפוס סטטיסטיקה" msgid "Authentication is required to reset statistics." msgstr "נדרש אימות כדי לאפס סטטיסטיקה." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "חיפוש עדכוני מערכת" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "נדרש אימות כדי לחפש עדכוני מערכת." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "התקנת עדכוני מערכת" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "נדרש אימות כדי להתקין עדכוני מערכת." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "התקנת גרסת מערכת מסוימת" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up 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:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "ניהול יכולות רשות" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "נדרש אימות כדי לנהל יכולות רשות." @@ -1208,3 +1279,27 @@ msgstr "נדרש אימות כדי למחוק קבצים ותיקיות שמשו msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "נדרש אימות כדי להפשיר או להקפיא את התהליכים של ‚$(unit)’." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "ניקוי עדכוני מערכת ישנים" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "נדרש אימות כדי לנקות עדכוני מערכת ישנים." diff --git a/po/hi.po b/po/hi.po index 8bad4eaf04e54..bdc61401a2b7f 100644 --- a/po/hi.po +++ b/po/hi.po @@ -1,12 +1,12 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # -# Scrambled 777 , 2024. +# Scrambled 777 , 2024, 2026. 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: 2024-06-03 00:35+0000\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:59+0000\n" "Last-Translator: Scrambled 777 \n" "Language-Team: Hindi \n" @@ -15,7 +15,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.5.5\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -149,7 +149,7 @@ msgstr "" msgid "Authentication is required to manage signing keys for home directories." msgstr "सिस्टम सेवाओं या अन्य यूनिटों को प्रबंधित करने के लिए प्रमाणीकरण आवश्यक है।" -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -158,29 +158,29 @@ msgstr "" "उपयोक्ता %s का होम वर्तमान में अनुपस्थित है, कृपया आवश्यक स्टोरेज उपकरण या बैकअप फाइल " "सिस्टम प्लग इन करें।" -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "उपयोक्ता %s के लिए बहुत बार लॉगिन प्रयास, बाद में पुनः प्रयास करें।" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "पासवर्ड: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "पासवर्ड गलत है या उपयोक्ता %s के प्रमाणीकरण के लिए पर्याप्त नहीं है।" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "क्षमा करें, पुनः प्रयास करें: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "पुनर्प्राप्ति कुंजी: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -188,20 +188,20 @@ msgid "" msgstr "" "पासवर्ड/पुनर्प्राप्ति कुंजी गलत है या उपयोक्ता %s के प्रमाणीकरण के लिए पर्याप्त नहीं है।" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "क्षमा करें, पुनर्प्राप्ति कुंजी पुनः दर्ज करें: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "पासवर्ड के साथ पुनः प्रयास करें: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -210,26 +210,26 @@ msgstr "" "पासवर्ड गलत है या पर्याप्त नहीं है, और उपयोक्ता %s का विन्यस्त किया गया सुरक्षा टोकन " "सम्मिलित नहीं किया गया है।" -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "सुरक्षा टोकन PIN: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "कृपया उपयोक्ता %s के सुरक्षा टोकन पर भौतिक रूप से प्रमाणित करें।" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "कृपया उपयोक्ता %s के सुरक्षा टोकन पर उपस्थिति की पुष्टि करें।" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "कृपया उपयोक्ता %s के सुरक्षा टोकन पर उपयोक्ता को सत्यापित करें।" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -237,75 +237,75 @@ msgstr "" "सुरक्षा टोकन PIN बंद है, कृपया पहले खोलें। (संकेत: हटाना और पुनः सम्मिलित करना पर्याप्त हो " "सकता है।)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "क्षमा करें, सुरक्षा टोकन PIN का पुन: प्रयास करें: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, 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 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "उपयोक्ता %s का सुरक्षा टोकन PIN गलत है (केवल एक प्रयास शेष है!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "उपयोक्ता %s का होम फिलहाल बंद है, कृपया पहले स्थानीय स्तर पर खोलें।" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "उपयोक्ता %s के लिए बहुत सारे असफल लॉगिन प्रयास, अस्वीकार कर रहे हैं।" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "उपयोक्ता रिकॉर्ड अवरुद्ध कर दिया गया है, पहुंच पर रोक लगा दी गई है।" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "उपयोक्ता रिकॉर्ड अभी तक मान्य नहीं है, पहुंच प्रतिबंधित है।" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "उपयोक्ता रिकॉर्ड अब मान्य नहीं है, पहुंच पर रोक लगा दी गई है।" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "उपयोक्ता रिकॉर्ड मान्य नहीं है, पहुंच प्रतिबंधित है।" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "बहुत अधिक लॉगिन, %s में पुनः प्रयास करें।" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "पासवर्ड परिवर्तन आवश्यक है।" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "पासवर्ड समाप्त हो गया, परिवर्तन आवश्यक है।" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "पासवर्ड समाप्त हो गया है, लेकिन बदल नहीं सकता, लॉगिन करने से इंकार कर रहा है।" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "पासवर्ड जल्द ही समाप्त हो जाएगा, कृपया बदलें।" @@ -842,37 +842,56 @@ msgid "" msgstr "स्थानीय आभासी मशीनों और कंटेनरों को प्रबंधित करने के लिए प्रमाणीकरण आवश्यक है।" #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 #, fuzzy msgid "Create a local virtual machine or container" msgstr "स्थानीय आभासी मशीन और कंटेनर प्रबंधित करें" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 #, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." msgstr "स्थानीय आभासी मशीनों और कंटेनरों को प्रबंधित करने के लिए प्रमाणीकरण आवश्यक है।" -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "स्थानीय आभासी मशीन और कंटेनर प्रबंधित करें" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." msgstr "स्थानीय आभासी मशीनों और कंटेनरों को प्रबंधित करने के लिए प्रमाणीकरण आवश्यक है।" -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "स्थानीय आभासी मशीन और कंटेनर छवियां प्रबंधित करें" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "स्थानीय आभासी मशीन और कंटेनर छवियों को प्रबंधित करने के लिए प्रमाणीकरण आवश्यक है।" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "NTP सर्वर निर्धारित करें" @@ -982,7 +1001,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." +#, fuzzy +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "बलपूर्वक नवीनीकरण संदेश भेजने के लिए प्रमाणीकरण आवश्यक है।" #: src/network/org.freedesktop.network1.policy:154 @@ -1135,49 +1157,98 @@ msgstr "" msgid "Authentication is required to reset statistics." msgstr "NTP सेटिंग्स को रीसेट करने के लिए प्रमाणीकरण आवश्यक है।" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 #, fuzzy msgid "Authentication is required to check for system updates." msgstr "सिस्टम समय निर्धारित करने के लिए प्रमाणीकरण आवश्यक है।" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 #, fuzzy msgid "Authentication is required to install system updates." msgstr "सिस्टम समय निर्धारित करने के लिए प्रमाणीकरण आवश्यक है।" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 #, 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" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy -msgid "Authentication is required to cleanup old system updates." -msgstr "सिस्टम समय निर्धारित करने के लिए प्रमाणीकरण आवश्यक है।" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 #, fuzzy msgid "Authentication is required to manage optional features." msgstr "सक्रिय सत्रों, उपयोक्ताओं और सीटों को प्रबंधित करने के लिए प्रमाणीकरण आवश्यक है।" @@ -1269,3 +1340,25 @@ msgstr "'$(unit)' से जुड़ी फाइलों और निर् msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "'$(unit)' यूनिट की प्रक्रियाओं को जमाने या पिघलाने के लिए प्रमाणीकरण आवश्यक है।" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#, fuzzy +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "सिस्टम समय निर्धारित करने के लिए प्रमाणीकरण आवश्यक है।" diff --git a/po/hr.po b/po/hr.po index 94138912a068c..733daa3f97a22 100644 --- a/po/hr.po +++ b/po/hr.po @@ -3,14 +3,14 @@ # SOME DESCRIPTIVE TITLE. # This file is distributed under the same license as the PACKAGE package. # gogo , 2016. -# Gogo Gogsi , 2020, 2021, 2022. -# Marin Kresic , 2024. +# Gogo Gogsi , 2020, 2021, 2022, 2026. +# Marin Kresic , 2024, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2024-08-04 03:41+0000\n" -"Last-Translator: Marin Kresic \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:02+0000\n" +"Last-Translator: Gogo Gogsi \n" "Language-Team: Croatian \n" "Language: hr\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.6.2\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -157,82 +157,82 @@ msgstr "" msgid "Authentication is required to manage signing keys for home directories." msgstr "Potrebna je ovjera za upravljanje uslugama sustava ili jedinicama." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "" -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "" -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "" -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -240,82 +240,82 @@ msgstr "" "PIN sigurnosnog tokena je zaključan, molimo prvo ga otključajte. (Napomena: " "Uklanjanje i ponovno umetanje moglo bi biti dovoljno.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "PIN sigurnosnog tokena nije ispravan za korisnika %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 #, fuzzy msgid "Sorry, retry security token PIN: " msgstr "Pokušajte ponovo unijeti PIN sigurnosnog tokena: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "PIN sigurnosnog tokena korisnika %s nije ispravan (preostalo je još nekoliko " "pokušaja!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "PIN sigurnosnog tokena korisnika %s nije ispravan (preostao je još jedan " "pokušaj!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "Početni direktorij korisnika %s trenutno nije aktivan, molimo prvo se " "lokalno prijavite." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "" @@ -881,11 +881,20 @@ msgstr "" "spremnicima." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 #, fuzzy msgid "Create a local virtual machine or container" msgstr "Upravljanje lokalnim vurtualnim strojevima i spremnicima" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 #, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." @@ -893,12 +902,12 @@ msgstr "" "Potrebna je ovjera za upravljanje lokalnim vurtualnim strojevima i " "spremnicima." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "Upravljanje lokalnim vurtualnim strojevima i spremnicima" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." @@ -906,11 +915,11 @@ msgstr "" "Potrebna je ovjera za upravljanje lokalnim vurtualnim strojevima i " "spremnicima." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Upravljanje lokalnim vurtualnim strojevima i spremnicima slika" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -918,6 +927,16 @@ msgstr "" "Potrebna je ovjera za upravljanje lokalnim vurtualnim strojevima i " "spremnicima slika." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Postavi NTP poslužitelje" @@ -1029,7 +1048,10 @@ msgid "DHCP server sends force renew message" msgstr "DHCP poslužitelj šalje poruku prisilne ponovne uspostave" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +#, fuzzy +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "Potrebna je ovjera za slanje poruke prisilne ponovne uspostave." #: src/network/org.freedesktop.network1.policy:154 @@ -1181,49 +1203,98 @@ msgstr "" msgid "Authentication is required to reset statistics." msgstr "Potrebna je ovjera za vraćanje izvornih NTP postavki." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 #, fuzzy msgid "Authentication is required to check for system updates." msgstr "Potrebna je ovjera za postavljanje vremena sustava." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 #, fuzzy msgid "Authentication is required to install system updates." msgstr "Potrebna je ovjera za postavljanje vremena sustava." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 #, fuzzy msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." msgstr "Potrebna je ovjera za postavljanje vremenske zone sustava." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy -msgid "Authentication is required to cleanup old system updates." -msgstr "Potrebna je ovjera za postavljanje vremena sustava." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 #, fuzzy msgid "Authentication is required to manage optional features." msgstr "" @@ -1319,3 +1390,25 @@ msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "" "Potrebna je ovjera za zamrzavanje ili odmrzavanje procesa '$(unit)' jedinice." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#, fuzzy +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "Potrebna je ovjera za postavljanje vremena sustava." diff --git a/po/hu.po b/po/hu.po index 81d4dc6f4abac..4b7ecbc8ce2a0 100644 --- a/po/hu.po +++ b/po/hu.po @@ -5,22 +5,22 @@ # # Gabor Kelemen , 2015, 2016. # Balázs Úr , 2016. -# Balázs Meskó , 2022. -# Balázs Úr , 2023. +# Balázs Meskó , 2022, 2026. +# Balázs Úr , 2023, 2026. 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:04+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 2026.6.1\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,26 +147,22 @@ 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 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -177,31 +171,31 @@ msgstr "" "%s felhasználó saját mappája jelenleg hiányzik, csatlakoztassa a szükséges " "tárolóeszközt vagy a biztonsági mentési fájlrendszert." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Túl gyakori bejelentkezési kísérletek %s felhasználónál, próbálja újra " "később." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Jelszó: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "A jelszó helytelen vagy nem elegendő %s felhasználó hitelesítéséhez." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Elnézést, próbálja újra: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Helyreállítási kulcs: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -210,20 +204,20 @@ msgstr "" "A jelszó vagy a helyreállítási kulcs helytelen vagy nem elegendő %s " "felhasználó hitelesítéséhez." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Elnézést, adja meg újra a helyreállítási kulcsot: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "%s felhasználó biztonsági tokenje nincs behelyezve." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Próbálja újra jelszóval: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -232,26 +226,26 @@ msgstr "" "A jelszó helytelen vagy nem elegendő, és %s felhasználó beállított " "biztonsági tokenje nincs behelyezve." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "Biztonsági token PIN-kódja: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "Hitelesítsen fizikailag %s felhasználó biztonsági tokenjén." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "Erősítse meg a jelenlétet %s felhasználó biztonsági tokenjén." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "Ellenőrizze a felhasználót %s felhasználó biztonsági tokenjén." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -259,85 +253,85 @@ msgstr "" "A biztonsági token PIN-kódja zárolva van, először oldja fel (tipp: az " "eltávolítás és az ismételt behelyezés elegendő lehet)." -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "A biztonsági token PIN-kódja helytelen %s felhasználónál." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Elnézést, próbálja újra a biztonsági token PIN-kódját: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "%s felhasználó biztonsági tokenjének PIN-kódja helytelen (már csak néhány " "próbálkozás maradt!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, 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 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "%s felhasználó saját mappája jelenleg nem aktív, először jelentkezzen be " "helyileg." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "%s felhasználó saját mappája jelenleg zárolva van, először oldja fel " "helyileg." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" "Túl sok sikertelen bejelentkezési kísérlet %s felhasználónál, elutasítás." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "A felhasználói bejegyzés tiltva van, a hozzáférés megtiltása." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "A felhasználói bejegyzés még nem érvényes, a hozzáférés megtiltása." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "A felhasználói bejegyzés már nem érvényes, a hozzáférés megtiltása." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "A felhasználói bejegyzés nem érvényes, a hozzáférés megtiltása." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Túl sok bejelentkezés, próbálja újra %s múlva." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Jelszóváltoztatás szükséges." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "A jelszó lejárt, változtatás szükséges." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" "A jelszó lejárt, de nem lehet megváltoztatni, a bejelentkezés elutasítása." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "A jelszó hamarosan lejár, változtassa meg." @@ -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,32 +906,39 @@ 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" +msgid "Inspect local virtual machines and containers" +msgstr "" #: 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." +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" +msgstr "Helyi virtuális gép vagy konténer létrehozása" #: src/machine/org.freedesktop.machine1.policy:106 -#, fuzzy +msgid "" +"Authentication is required to create a local virtual machine or container." +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:116 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 +#: src/machine/org.freedesktop.machine1.policy:117 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 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Helyi virtuális gépek és konténerek lemezképeinek kezelése" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -955,6 +946,16 @@ msgstr "" "Hitelesítés szükséges a helyi virtuális gépek és konténerek lemezképeinek " "kezeléséhez." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "NTP-kiszolgálók beállítása" @@ -1066,8 +1067,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 +1100,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 +1154,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 +1162,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 +1176,145 @@ 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" +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 -#, fuzzy +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 +msgid "Check for system updates" +msgstr "Rendszerfrissítések keresése" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 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" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 -#, fuzzy +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 +msgid "Install system updates" +msgstr "Rendszerfrissítések telepítése" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 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" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 -#, fuzzy +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 +msgid "Install specific system version" +msgstr "Konkrét rendszerverzió telepítése" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" msgstr "" -#: 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." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 -msgid "Manage optional features" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 -#, fuzzy -msgid "Authentication is required to manage optional features." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" msgstr "" -"Hitelesítés szükséges az aktív munkamenetek, felhasználók és munkaállomások " -"kezeléséhez." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 +msgid "Manage optional features" +msgstr "Választható funkciók kezelése" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 +msgid "Authentication is required to manage optional features." +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 +1381,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)'." @@ -1371,5 +1414,29 @@ msgstr "" "Hitelesítés szükséges a(z) „$(unit)” egység folyamatainak lefagyasztásához " "vagy kiolvasztásához." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Régi rendszerfrissítések eltakarítása" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "Hitelesítés szükséges a régi rendszerfrissítések eltakarításához." + #~ msgid "Authentication is required to kill '$(unit)'." #~ msgstr "Hitelesítés szükséges a következő kilövéséhez: „$(unit)”." diff --git a/po/ia.po b/po/ia.po index 222a9c98308bf..502baae4ecc42 100644 --- a/po/ia.po +++ b/po/ia.po @@ -1,13 +1,13 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the systemd package. -# Emilio Sepulveda , 2025. +# Emilio Sepulveda , 2025, 2026. 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-02-17 18:20+0000\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:05+0000\n" "Last-Translator: Emilio Sepulveda \n" "Language-Team: Interlingua \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.9.2\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -144,156 +144,156 @@ msgstr "" msgid "Authentication is required to manage signing keys for home directories." msgstr "" -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "" -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "" -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "" -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "" @@ -790,33 +790,52 @@ msgid "" msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 -msgid "Create a local virtual machine or container" +msgid "Inspect local virtual machines and containers" msgstr "" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" -"Authentication is required to create a local virtual machine or container." +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" msgstr "" #: src/machine/org.freedesktop.machine1.policy:106 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "" @@ -926,7 +945,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 @@ -1069,45 +1090,95 @@ msgstr "" msgid "Authentication is required to reset statistics." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "" @@ -1193,3 +1264,21 @@ msgstr "" msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" diff --git a/po/id.po b/po/id.po index 7e09c1302eb7a..f0e86036ab1a9 100644 --- a/po/id.po +++ b/po/id.po @@ -1,13 +1,14 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # Indonesian translation for systemd. -# Andika Triwidada , 2014, 2021, 2022, 2024, 2025. +# Andika Triwidada , 2014, 2021, 2022, 2024, 2025, 2026. +# 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-19 17:10+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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -156,7 +157,7 @@ msgstr "" "Otentikasi diperlukan untuk mengelola kunci penandatanganan bagi direktori " "home." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -165,29 +166,29 @@ msgstr "" "Home dari pengguna %s saat ini tidak ada, harap tancapkan perangkat " "penyimpanan yang diperlukan atau sistem berkas pendukung." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "Percobaan log masuk terlalu sering bagi pengguna %s, coba lagi nanti." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Kata Sandi: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "Kata sandi salah atau tidak memadai bagi otentikasi dari pengguna %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Maaf, coba lagi: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Kunci pemulihan: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -196,20 +197,20 @@ msgstr "" "Kata sandi/kunci pemulihan salah atau tidak memadai untuk otentikasi " "pengguna %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Maaf, masukkan lagi kunci pemulihan: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "Token keamanan pengguna %s tidak ditancapkan." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Coba lagi dengan kata sandi: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -218,26 +219,26 @@ msgstr "" "Kata sandi salah atau tidak memadai, dan token keamanan yang terkonfigurasi " "dari pengguna %s tidak ditancapkan." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "PIN token keamanan: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "Harap otentikasikan secara fisik pada token keamanan dari pengguna %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "Harap konfirmasikan kehadiran pada token keamanan pengguna %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "Harap verifikasikan pengguna pada token keamanan dari pengguna %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -245,81 +246,81 @@ msgstr "" "PIN token keamanan terkunci, harap buka dulu kuncinya. (Petunjuk: Mencabut " "dan menancapkan ulang mungkin sudah cukup)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "PIN token keamanan salah bagi pengguna %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Maaf, coba lagi PIN token keamanan: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "PIN token keamanan dari pengguna %s salah (hanya beberapa percobaan tersisa!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "PIN token keamanan dari pengguna %s salah (hanya satu percobaan tersisa!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "Home dari pengguna %s saat ini tidak aktif, harap log masuk secara lokal " "terlebih dahulu." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "Home dari pengguna %s saat ini terkunci, harap buka kunci secara lokal " "terlebih dahulu." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "Terlalu banyak upaya log masuk yang gagal bagi pengguna %s, menolak." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "Rekaman pengguna terblokir, menolak akses." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "Rekaman pengguna belum valid, menolak akses." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "Rekaman pengguna tidak valid lagi, menolak akses." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Rekaman pengguna tidak valid, menolak akses." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Terlalu banyak log masuk, coba lagi dalam %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Perubahan kata sandi diperlukan." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Kata sandi kedaluwarsa, perubahan diperlukan." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "Kata sandi kedaluwarsa, tapi tidak bisa mengubah, menolak log masuk." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Kata sandi akan segera kedaluwarsan, harap diubah." @@ -870,32 +871,42 @@ msgstr "" "Otentikasi diperlukan untuk mengelola mesin virtual lokal dan kontainer." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Memeriksa mesin virtual dan container lokal" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"Autentikasi diperlukan untuk memeriksa mesin virtual dan container lokal." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Membuat suatu mesin virtual lokal atau kontainer" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Otentikasi diperlukan untuk membuat sebuah mesin virtual lokal atau " "kontainer." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Daftarkan suatu mesin virtual lokal atau kontainer" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Otentikasi diperlukan untuk mendaftarkan sebuah mesin virtual lokal atau " "kontainer." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Kelola mesin virtual lokal dan image kontainer" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -903,6 +914,18 @@ msgstr "" "Otentikasi diperlukan untuk mengelola mesin virtual lokal dan image " "kontainer." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Memeriksa image mesin virtual dan container lokal" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"Autentikasi diperlukan untuk memeriksa image mesin virtual dan container " +"lokal." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Atur server NTP" @@ -1014,8 +1037,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 +1082,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" @@ -1163,27 +1190,59 @@ msgstr "Reset statistik" msgid "Authentication is required to reset statistics." msgstr "Otentikasi diperlukan untuk mereset statistik." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "Mengosongkan cache DNS" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "Autentikasi diperlukan untuk mengosongkan cache DNS." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Mengatur ulang fitur server" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "Autentikasi diperlukan untuk mengatur ulang fitur server." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Periksa pembaruan sistem" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "Otentikasi diperlukan untuk memeriksa pembaruan sistem." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Membatalkan pemeriksaan pembaruan sistem" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "Autentikasi diperlukan untuk membatalkan pemeriksaan pembaruan sistem." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Pasang pembaruan sistem" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "Otentikasi diperlukan untuk memasang pembaruan sistem." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Membatalkan instalasi pembaruan sistem" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "Autentikasi diperlukan untuk membatalkan instalasi pembaruan sistem." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Pasang versi sistem spesifik" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1191,19 +1250,40 @@ msgstr "" "Otentikasi diperlukan untuk memperbarui sistem ke versi tertentu (mungkin " "versi lama)." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" -msgstr "Bersihkan pembaruan sistem lama" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Membatalkan instalasi versi sistem tertentu" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." -msgstr "Otentikasi diperlukan untuk membersihkan pembaruan sistem lama." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" +"Autentikasi diperlukan untuk membatalkan pembaruan sistem ke versi tertentu, " +"yang mungkin merupakan versi lama." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "Membersihkan pembaruan sistem lama" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "Autentikasi diperlukan untuk membersihkan pembaruan sistem lama." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Membatalkan pembersihan pembaruan sistem lama" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" +"Autentikasi diperlukan untuk membatalkan pembersihan pembaruan sistem lama." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Kelola fitur opsional" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "Otentikasi diperlukan untuk mengelola fitur opsional." @@ -1303,6 +1383,31 @@ msgstr "" "Otentikasi diperlukan untuk membekukan atau melanjutkan proses dari unit '$" "(unit)'." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "Harap konfirmasikan kehadiran pada token keamanan untuk membuka kunci." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "Harap verifikasi pengguna pada token keamanan untuk membuka kunci." + +#: src/shared/libfido2-util.c:936 +#, fuzzy, c-format +#| msgid "Please enter security token PIN:" +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "Silakan masukkan PIN token keamanan:" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Silakan masukkan PIN token keamanan:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Bersihkan pembaruan sistem lama" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "Otentikasi diperlukan untuk membersihkan pembaruan sistem lama." + #~ msgid "" #~ "Authentication is required to halt the system while an application asked " #~ "to inhibit it." diff --git a/po/it.po b/po/it.po index 45e9763326b4b..cbad9735f4790 100644 --- a/po/it.po +++ b/po/it.po @@ -3,15 +3,17 @@ # Italian translation for systemd package # Traduzione in italiano per il pacchetto systemd # Daniele Medri , 2013-2024. -# Salvatore Cocuzza , 2024. -# Nathan , 2025. +# Salvatore Cocuzza , 2024, 2026. +# Nathan , 2025, 2026. +# Ali Ciloglu , 2026. +# Milo Casagrande , 2026. 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:03+0000\n" +"Last-Translator: Ali Ciloglu \n" "Language-Team: Italian \n" "Language: it\n" @@ -19,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.12.2\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -163,7 +165,7 @@ msgstr "" "È necessaria l'autenticazione per gestire le chiavi di firma delle directory " "home." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -172,31 +174,31 @@ msgstr "" "La home dell'utente %s è al momento inesistente, per piacere collega il " "necessario dispositivo di memorizzazione o il file system necessario." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Tentativi di autenticazione frequenti per l'utente %s, riprova più tardi." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Password: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Password incorretta o non sufficiente per l'autenticazione dell'utente %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Per piacere, prova ancora: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Chiave di ripristino: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -205,20 +207,20 @@ msgstr "" "Password/chiave di ripristino incorretta o non sufficiente per " "l'autenticazione dell'utente %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Per piacere, digita nuovamente la chiave di ripristino: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "Token di sicurezza per l'utente %s non inserito." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Prova ancora con la password: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -227,28 +229,28 @@ msgstr "" "Password non corretta o non sufficiente, e token di sicurezza configurato " "per l'utente %s non inserito." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "PIN del token di sicurezza: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" "Per piacere, autenticate fisicamente il token di sicurezza dell'utente %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" "Per piacere conferma la presenza sul token di sicurezza dell'utente %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "Per piacere verifica l'utente sul token di sicurezza dell'utente %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -257,81 +259,81 @@ msgstr "" "(Suggerimento: la rimozione e il reinserimento potrebbero essere " "sufficienti.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "PIN del token di sicurezza errato per l'utente %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Spiacente, riprova il PIN del token di sicurezza: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "PIN del token di sicurezza dell'utente %s errato (pochi tentativi rimasti!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "PIN del token di sicurezza dell'utente %s errato (un tentativo rimasto!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "La Home dell'utente %s è attualmente non attiva, per piacere accedi prima " "localmente." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "La Home dell'utente %s è attualmente bloccata, per piacere sbloccala prima " "localmente." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "Troppi tentativi d'accesso falliti per l'utente %s, rifiuto." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "Record utente bloccato, accesso negato." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "Record utente non è ancora valido, accesso negato." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "Record utente non più valido, accesso negato." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Record utente non valido, accesso negato." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Troppi tentativi d'accesso, riprova in %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Aggiornamento password richiesto." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "La password è scaduta, aggiornamento richiesto." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "La password è scaduta, ma non può essere cambiata, login rifiutato." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "La password scadrà a breve, per piacere cambiala." @@ -907,32 +909,43 @@ msgstr "" "Autenticazione richiesta per gestire le virtual machine e i container locali." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Ispezionare macchine virtuali e container locali" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"È richiesta l'autenticazione per ispezionare le macchine virtuali e i " +"container locali." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Crea una macchina virtuale o un contenitore locale" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "L'autenticazione è necessaria per creare una macchina virtuale o un " "container locale." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Registrare una macchina virtuale o un container locale" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "È necessaria l'autenticazione per registrare una macchina virtuale o un " "container locale." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Gestisci le immagini locali delle virtual machine e dei container" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -940,6 +953,18 @@ msgstr "" "Autenticazione richiesta per gestire le immagini delle virtual machine e dei " "container locali." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Ispezionare le immagini di macchine virtuali e container locali" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"È richiesta l'autenticazione per ispezionare le immagini di macchine " +"virtuali e container locali." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Configura server NTP" @@ -1052,8 +1077,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 +1123,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" @@ -1209,30 +1238,62 @@ msgstr "Resetta le statistiche" msgid "Authentication is required to reset statistics." msgstr "Per reimpostare le statistiche è necessaria l'autenticazione." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Controlla gli aggiornamenti di sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "" "L'autenticazione è necessaria per verificare la presenza di aggiornamenti di " "sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Installa gli aggiornamenti di sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "" "Per installare gli aggiornamenti di sistema è necessaria l'autenticazione." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Installa la versione specifica del sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1240,20 +1301,37 @@ msgstr "" "L'autenticazione è necessaria per aggiornare il sistema a una versione " "specifica (possibilmente vecchia)." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" -msgstr "Pulisci i vecchi aggiornamenti di sistema" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." msgstr "" -"L'autenticazione è necessaria per pulire i vecchi aggiornamenti di sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Gestisci funzionalità opzionali" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "Per gestire le funzionalità opzionali è necessaria l'autenticazione." @@ -1354,3 +1432,29 @@ msgid "" msgstr "" "Autenticazione richiesta per bloccare/sbloccare il processo dell'unità '$" "(unit)'." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Pulisci i vecchi aggiornamenti di sistema" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "L'autenticazione è necessaria per pulire i vecchi aggiornamenti di " +#~ "sistema." diff --git a/po/ja.po b/po/ja.po index bd58347ec0821..918284d01f448 100644 --- a/po/ja.po +++ b/po/ja.po @@ -2,14 +2,14 @@ # # Japanese translation for systemd. # -# Takuro Onoue , 2021. -# Y T , 2025. +# Takuro Onoue , 2021, 2026. +# Y T , 2025, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-03-17 03:11+0000\n" -"Last-Translator: Y T \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:56+0000\n" +"Last-Translator: Takuro Onoue \n" "Language-Team: Japanese \n" "Language: ja\n" @@ -17,7 +17,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.10.2\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -147,7 +147,7 @@ msgstr "ホームディレクトリの署名キーの管理" msgid "Authentication is required to manage signing keys for home directories." msgstr "ホームディレクトリの署名キーを管理するには認証が必要です。" -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -156,49 +156,49 @@ msgstr "" "ユーザ%sのホーム領域が存在しません。必要なストレージデバイスもしくは基盤ファ" "イルシステムを接続して下さい。" -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "ユーザ%sのログイン試行が頻繁すぎます。後ほどやり直して下さい。" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "パスワード: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "ユーザ%sの認証のためのパスワードが無効です。" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "すみません、パスワードを再入力して下さい: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "復旧キー: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "ユーザ%sの認証のためのパスワードもしくは復旧キーが無効です。" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "すみません、復旧キーを再入力して下さい: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "パスワードを入力して再試行して下さい: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -207,26 +207,26 @@ msgstr "" "ユーザ%sのパスワードが無効、もしくはセキュリティトークンが挿入されていませ" "ん。" -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "セキュリティトークンPIN: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "ユーザ%sのセキュリティトークン上で物理的な認証を行って下さい。" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "ユーザ%sのセキュリティトークンが存在していることを確認して下さい。" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "ユーザ%sのセキュリティトークン上のユーザを確認して下さい。" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -234,85 +234,85 @@ msgstr "" "セキュリティトークンPINがロックされています。アンロックして下さい。(ヒント: " "一旦トークンを外して再挿入してみて下さい。)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "すみません、セキュリティトークンPINを再入力して下さい: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, 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 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "ユーザ%sのセキュリティトークンPINが正しくありません (残り試行可能回数が1回で" "す!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "ユーザ%sのホーム領域がロックされています。ローカル環境にてアンロックして下さ" "い。" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "ユーザ%sのログイン試行の失敗回数が多すぎたため、拒否されました。" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "ユーザレコードがブロックされたため、アクセスが禁止されています。" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "ユーザレコードがまだ有効でないため、アクセスが禁止されています。" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "ユーザレコードがもはや有効でないため、アクセスが禁止されています。" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "ユーザレコードが有効でないため、アクセスが禁止されています。" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "ログイン回数が多すぎます。%s後に試して下さい。" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "パスワードの更新が必要です。" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "パスワードの有効期限が切れたため、パスワードの更新が必要です。" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" "パスワードの有効期限が切れましたが、パスワードの更新できませんでした。ログイ" "ンを拒否します。" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "パスワードの有効期限がまもなく切れます。パスワードを更新して下さい。" @@ -836,28 +836,37 @@ msgid "" msgstr "ローカルに存在する仮想マシンやコンテナを管理するには認証が必要です。" #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "ローカルに存在する仮想マシンやコンテナの精査" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "ローカルに存在する仮想マシンやコンテナを精査するには認証が必要です。" + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "ローカルに存在する仮想マシンやコンテナの作成" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "ローカルに存在する仮想マシンやコンテナを作成するには認証が必要です。" -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "ローカルに存在する仮想マシンやコンテナの登録" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "ローカルに存在する仮想マシンやコンテナを登録するには認証が必要です。" -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "ローカルに存在する仮想マシンやコンテナのイメージ管理" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -865,6 +874,18 @@ msgstr "" "ローカルに存在する仮想マシンやコンテナのイメージを管理するには認証が必要で" "す。" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "ローカルに存在する仮想マシンやコンテナのイメージの精査" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"ローカルに存在する仮想マシンやコンテナのイメージを精査するには認証が必要で" +"す。" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "NTPサーバの設定" @@ -974,7 +995,9 @@ msgid "DHCP server sends force renew message" msgstr "DHCPサーバが強制的にIPアドレスを更新する" #: 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サーバが強制的なIPアドレス更新を行うには認証が必要です。" #: src/network/org.freedesktop.network1.policy:154 @@ -1013,19 +1036,19 @@ 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 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" @@ -1117,46 +1140,96 @@ msgstr "統計情報のリセット" msgid "Authentication is required to reset statistics." msgstr "統計情報をリセットするには認証が必要です。" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "システム更新の確認" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "システム更新の確認をするには認証が必要です。" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "システム更新のインストール" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "システム更新をインストールするには認証が必要です。" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "特定のシステムバージョンをインストール" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up 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:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "任意の機能の管理" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "任意の機能を管理するには認証が必要です。" @@ -1246,3 +1319,27 @@ msgstr "" msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "'$(unit)'のプロセスを凍結もしくは凍結解除するには認証が必要です。" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "過去のシステム更新を削除" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "過去のシステム更新を削除するには認証が必要です。" diff --git a/po/ka.po b/po/ka.po index dc306496b47cc..2bc5df382b648 100644 --- a/po/ka.po +++ b/po/ka.po @@ -4,8 +4,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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-21 11:10+0000\n" "Last-Translator: Temuri Doghonadze \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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -150,7 +150,7 @@ msgstr "" "საწყისის საქაღალდეების ხელმოწერის გასაღებების მართვისთვის საჭიროა " "ავთენტიკაცია." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -159,30 +159,30 @@ msgstr "" "მომხმარებლისთვის %s საწყისი საქაღალდე ჯერჯერობით არ არსებობს. მიაერთეთ " "შესაბამისი საცავის მოწყობილობა ან ფაილური სისტემა." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "მომხმარებლისთვის %s შესვლის მეტისმეტად ხშირი მცდელობა. მოგვიანებით სცადეთ." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "პაროლი: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "მომხმარებლის (%s) ავთენტიკაციისთვის პაროლი არასწორი ან არასაკმარისია." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "თავიდან სცადეთ: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "აღდგენის გასაღები: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -191,20 +191,20 @@ msgstr "" "მომხმარებლის (%s) ავთენტიკაციისთვის პაროლი/აღდგენის გასაღები არასწორი ან " "არასაკმარისია." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "თავიდან შეიყვანეთ აღდგენის გასაღები: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "თავიდა სცადეთ, პაროლით: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -213,27 +213,27 @@ msgstr "" "პაროლი არასწორი ან არასაკმარისია და მომხმარებლისთვის %s უსაფრთხოების კოდი " "ჩასმული არაა." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "უსაფრთხოების კოდის PIN-კოდი: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" "მომხმარებლისთვის %s უსაფრთხოების კოდს ფიზიკური ავთენტიკაცია ესაჭიროება." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "დაადასტურეთ არსებობა უსაფრთხოებით კოდში მომხმარებლისთვის %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "გადაამოწმეთ მომხმარებელი უსაფრთხოების კოდზე მომხმარებლისთვის %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -241,83 +241,83 @@ msgstr "" "უსაფრთხოების კოდის PIN-კოდი დაბლოკილია. ჯერ განბლოკეთ ის. (მინიშნება: წაშლა " "და თავიდან ჩასმა, როგორც წესი, საკმარისია.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "უსაფრთხოების კოდის PIN კოდი მომხმარებლისთვის %s არასწორია." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "უკაცრავად, თავიდან სცადეთ უსაფრთხოების კოდის PIN-კოდი: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, 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 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "მომხმარებლისთვის %s უსაფრთხოების კოდის PIN-კოდი არასწორია (დარჩა მხოლოდ " "ერთი!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "მომხმარებლისთვის %s საწყისი საქაღალდე ამჟამად დაბლოკილია. გთხოვთ, ჯერ " "ლოკალურად განბლოკეთ ის." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "მომხმარებლისთვის %s შესვლის მეტისმეტად ბევრი მცდელობა. უარყოფა." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "მომხმარებლის ჩანაწერი დაბლოკილია. წვდომა აკრძალულია." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "მომხმარებლის წვდომა ჯერ არასწორია. წვდომა აკრძალულია." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "მომხმარებლის ჩანაწერო უკვე სწორი აღარაა. წვდომა აკრძალულია." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "მომხმარებლის ჩანაწერი არასწორია. წვდომის აკრძალვა." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "მეტისმეტად ხშირი შესვლა. სცადეთ თავიდან %s-ის გასვლის შემდეგ." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "პაროლის შეცვლა აუცილებელია." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "პაროლი ვადაგასულია, გთხოვთ, შეცვალეთ." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "პაროლი ვადაგასულია, მაგრამ მას ვერ შეცვლით. შესვლა აკრძალულია." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "პაროლს ვადა მალე გაუვა. გთხოვთ, შეცვალეთ." @@ -872,31 +872,43 @@ msgstr "" "სჭირდება." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" +"ლოკალური ვირტუალური მანქანებისა და კონტეინერების ტექნიკური დათვალიერება" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"ლოკალური ვირტუალური მანქანებისა და კონტეინერების ტექნიკურ დათვალიერებას " +"ავთენტიკაცია სჭირდება." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "ლოკალური ვირტუალური მანქანის ან კონტეინერის შექმნა" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "ლოკალური ვირტუალური მანქანის ან კონტეინერის შექმნას ავთენტიკაცია სჭირდება." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "ლოკალური ვირტუალური მანქანის ან კონტეინერის რეგისტრაცია" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "ლოკალური ვირტუალური მანქანის ან კონტეინერის რეგისტრაციას ავთენტიკაცია " "სჭირდება." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "ლოკალური ვირტუალური მანქანებისა და კონტეინერების იმიჯების მართვა" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -904,6 +916,20 @@ msgstr "" "ლოკალური ვირტუალური მანქანებისა და კონტეინერების იმიჯების მართვას " "ავთენტიკაცია სჭირდება." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" +"ლოკალური ვირტუალური მანქანებისა და კონტეინერების ასლის ფაილების ტექნიკური " +"შემოწმება" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"ლოკალური ვირტუალური მანქანებისა და კონტეინერების ასლის ფაილების ტექნიკური " +"შემოწმებას ავთენტიკაცია სჭირდება." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "NTP სერვერის დაყენება" @@ -1013,8 +1039,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" @@ -1161,27 +1191,59 @@ msgstr "სტატისტიკის ჩამოყრა" msgid "Authentication is required to reset statistics." msgstr "სტატისტიკის ჩამოსაყრელად ავთენტიკაცია აუცილებელია." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "DNS-ის კეშების გაწმენდა" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "DNS-ის კეშების გაწმენდას ავთენტიკაცია სჭირდება." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "სერვერის შესაძლებლობების ჩამოყრა" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "სერვერის შესაძლებლობების ჩამოყრას ავთენტიკაცია სჭირდება." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "სისტემის განახლებების შემოწმება" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "სისტემური განახლებების შესამოწმებლად საჭიროა ავთენტიკაცია." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "სისტემის განახლებების შემოწმების გაუქმება" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "სისტემის განახლებების შემოწმების გაუქმებას ავთენტიკაცია სჭირდება." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "სისტემური განახლებების დაყენება" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "სისტემური განახლებების დასაყენებლად საჭიროა ავთენტიკაცია." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "სისტემის განახლებების დაყენების გაუქმება" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "სისტემის განახლებების დაყენების გაუქმებას ავთენტიკაცია სჭირდება." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "მითითებული სისტემის ვერსიის დაყენება" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1189,19 +1251,40 @@ msgstr "" "მითითებული (ალბათ, ძველი) სისტემის ვერსიამდე განახლებისთვის ავთენტიკაციაა " "საჭირო." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" -msgstr "ძველი სისტემის განახლებების გასუფთავება" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "სპეციფიკური სისტემის ვერსიის დაყენების გაუქმება" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" +"სპეციფიკური სისტემის ვერსიამდე (შესაძლოა, ძველამდე) დაყენების გაუქმებას " +"ავთენტიკაცია სჭირდება." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." -msgstr "ძველი სისტემური განახლებების გასუფთავებას ავთენტიკაცია სჭირდება." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "ძველის სისტემის განახლებების გასუფთავება" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "ძველის სისტემის განახლებების გასუფთავებას ავთენტიკაცია სჭირდება." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "ძველი სისტემის განახლებების გასუფთავების გაუქმება" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" +"ძველი სისტემის განახლებების გასუფთავების გაუქმებას ავთენტიკაცია სჭირდება." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "არასავალდებულო ფუნქციების მართვა" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "არასავალდებულო ფუნქციების მართვას ავთენტიკაცია სჭირდება." @@ -1295,3 +1378,29 @@ msgstr "" msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "'$(unit)'-ის პროცესების გასაყინად საჭიროა ავთენტიკაცია." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "განბლოკვისთვის დაადასტურეთ თქვენი არსებობა უსაფრთხოების ტოკენზე." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "განსაბლოკად გადაამოწმეთ მომხმარებელი უსაფრთხოების ტოკენზე." + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" +"შეიყვანეთ უსაფრთხოების ტოკენის PIN-კოდი (დაბლოკვამდე დარჩენილი მცდელობების " +"რაოდენობა: %d):" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "შეიყვანეთ უსაფრთხოების ტოკენის PIN-კოდი:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "ძველი სისტემის განახლებების გასუფთავება" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "ძველი სისტემური განახლებების გასუფთავებას ავთენტიკაცია სჭირდება." diff --git a/po/kab.po b/po/kab.po index 0b7002d48fe91..9385dd5d9552f 100644 --- a/po/kab.po +++ b/po/kab.po @@ -3,12 +3,14 @@ # Kabyle translation of systemd. # Slimane Selyan Amiri , 2021. # ButterflyOfFire , 2024. -# ButterflyOfFire , 2025. +# ButterflyOfFire , 2025, 2026. +# Massii Aqvayli , 2026. +# Aksel Azwaw , 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" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-16 18:13+0000\n" "Last-Translator: ButterflyOfFire " "\n" "Language-Team: Kabyle 1;\n" -"X-Generator: Weblate 5.12.2\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -27,50 +29,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 +82,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 +117,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,168 +143,184 @@ 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 +#: src/home/pam_systemd_home.c:327 #, c-format 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 +#: src/home/pam_systemd_home.c:332 #, 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 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Awal n uɛeddi: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, 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 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " -msgstr "" +msgstr "Suref-aɣ, ɛreḍ tikkelt nniḍen: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " -msgstr "" +msgstr "Tasarut n tririt: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format 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 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " -msgstr "" +msgstr "Suref-aɣ, sekcem tikelt nniḍen tasarutt n tririt: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Ɛreḍ tikelt nniḍen s wawal n uɛeddi: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " -msgstr "" +msgstr "PIN n tsarut n tɣellist: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, 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 +#: src/home/pam_systemd_home.c:443 #, 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 +#: src/home/pam_systemd_home.c:454 #, 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 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 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 +#: src/home/pam_systemd_home.c:490 #, 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 +#: src/home/pam_systemd_home.c:509 #, 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 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, 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 +#: src/home/pam_systemd_home.c:712 #, 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 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." -msgstr "" +msgstr "Yewḥel usekles n useqdac, yegdel anekcum." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 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 +#: src/home/pam_systemd_home.c:1029 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 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 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 +#: src/home/pam_systemd_home.c:1044 #, 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 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Asnifel n wawal n uɛeddi yettwasra." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." -msgstr "" +msgstr "Awal n uɛeddi yemmut, asnifel yettwasra." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 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 +#: src/home/pam_systemd_home.c:1069 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 +328,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 +346,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 +411,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 +518,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 +526,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." @@ -525,7 +550,7 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:169 msgid "Power off the system" -msgstr "" +msgstr "Sexsi anagraw" #: src/login/org.freedesktop.login1.policy:170 msgid "Authentication is required to power off the system." @@ -561,7 +586,7 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:213 msgid "Reboot the system while other users are logged in" -msgstr "" +msgstr "Ales-as asekker i unagraw xas akken llan iseqdacen-nniḍen ttwaqqnen" #: src/login/org.freedesktop.login1.policy:214 msgid "" @@ -581,7 +606,7 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:235 msgid "Halt the system" -msgstr "Sexsi anagraw" +msgstr "Seḥbes anagraw" #: src/login/org.freedesktop.login1.policy:236 msgid "Authentication is required to halt the system." @@ -589,7 +614,7 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:246 msgid "Halt the system while other users are logged in" -msgstr "" +msgstr "Sexsi anagraw xas akken llan iseqdacen-nniḍen ttwaqqnen" #: src/login/org.freedesktop.login1.policy:247 msgid "" @@ -719,7 +744,7 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:396 msgid "Set a wall message" -msgstr "" +msgstr "Sbadu izen wall" #: src/login/org.freedesktop.login1.policy:397 msgid "Authentication is required to set a wall message." @@ -727,7 +752,7 @@ msgstr "" #: src/login/org.freedesktop.login1.policy:406 msgid "Change Session" -msgstr "" +msgstr "Beddel tiɣimit" #: src/login/org.freedesktop.login1.policy:407 msgid "Authentication is required to change the virtual terminal." @@ -792,36 +817,55 @@ msgid "" msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 -msgid "Create a local virtual machine or container" +msgid "Inspect local virtual machines and containers" msgstr "" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" -"Authentication is required to create a local virtual machine or container." +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" msgstr "" #: src/machine/org.freedesktop.machine1.policy:106 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" -msgstr "" +msgstr "Sbadu iqeddacen NTP" #: src/network/org.freedesktop.network1.policy:23 msgid "Authentication is required to set NTP servers." @@ -830,7 +874,7 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:33 #: src/resolve/org.freedesktop.resolve1.policy:44 msgid "Set DNS servers" -msgstr "" +msgstr "Sbadu iqeddacen DNS" #: src/network/org.freedesktop.network1.policy:34 #: src/resolve/org.freedesktop.resolve1.policy:45 @@ -840,7 +884,7 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:44 #: src/resolve/org.freedesktop.resolve1.policy:55 msgid "Set domains" -msgstr "" +msgstr "Sbadu tiɣula" #: src/network/org.freedesktop.network1.policy:45 #: src/resolve/org.freedesktop.resolve1.policy:56 @@ -860,7 +904,7 @@ msgstr "" #: src/network/org.freedesktop.network1.policy:66 #: src/resolve/org.freedesktop.resolve1.policy:77 msgid "Enable/disable LLMNR" -msgstr "" +msgstr "Sermed/Kkes armad i LLMNR" #: src/network/org.freedesktop.network1.policy:67 #: src/resolve/org.freedesktop.resolve1.policy:78 @@ -880,22 +924,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 +972,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 @@ -1001,7 +1047,7 @@ msgstr "" #: src/resolve/org.freedesktop.resolve1.policy:22 msgid "Register a DNS-SD service" -msgstr "" +msgstr "Sekles yiwet n tanfa n DNS-SD" #: src/resolve/org.freedesktop.resolve1.policy:23 msgid "Authentication is required to register a DNS-SD service." @@ -1041,7 +1087,7 @@ msgstr "" #: src/resolve/org.freedesktop.resolve1.policy:165 msgid "Dump cache" -msgstr "" +msgstr "Awway n ugbur seg tuffart" #: src/resolve/org.freedesktop.resolve1.policy:166 msgid "Authentication is required to dump cache." @@ -1057,7 +1103,7 @@ msgstr "" #: src/resolve/org.freedesktop.resolve1.policy:187 msgid "Dump statistics" -msgstr "" +msgstr "Awway n tedaddanin" #: src/resolve/org.freedesktop.resolve1.policy:188 msgid "Authentication is required to dump statistics." @@ -1065,57 +1111,107 @@ msgstr "" #: src/resolve/org.freedesktop.resolve1.policy:198 msgid "Reset statistics" -msgstr "" +msgstr "Wennez tidaddanin" #: 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" +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 +msgid "Check for system updates" +msgstr "Nadi ɣef ileqman n unagraw" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 -msgid "Install system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 +msgid "Install system updates" +msgstr "Sbedd ileqman n unagraw" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "" #: src/timedate/org.freedesktop.timedate1.policy:22 msgid "Set system time" -msgstr "" +msgstr "Sbadu asrag n unagraw" #: src/timedate/org.freedesktop.timedate1.policy:23 msgid "Authentication is required to set the system time." @@ -1151,11 +1247,11 @@ msgstr "" #: src/core/dbus-unit.c:372 msgid "Authentication is required to start '$(unit)'." -msgstr "" +msgstr "Yettwasra usesteb i usekker n '$(unit)'." #: src/core/dbus-unit.c:373 msgid "Authentication is required to stop '$(unit)'." -msgstr "" +msgstr "Yettwasra usesteb i useḥbes n'$(unit)'." #: src/core/dbus-unit.c:374 msgid "Authentication is required to reload '$(unit)'." @@ -1163,7 +1259,7 @@ msgstr "" #: src/core/dbus-unit.c:375 src/core/dbus-unit.c:376 msgid "Authentication is required to restart '$(unit)'." -msgstr "" +msgstr "Yettwasra usesteb i wallus n asekker n '$(unit)'." #: src/core/dbus-unit.c:568 msgid "" @@ -1195,3 +1291,22 @@ msgstr "" msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, fuzzy, c-format +#| msgid "Please enter security token PIN:" +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "Ttxil, sekcem-d tiddest n tɣellist PIN:" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Ttxil, sekcem-d tiddest n tɣellist PIN:" diff --git a/po/kk.po b/po/kk.po index 7d6e4d3455db4..3968ed72faf43 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:57+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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -152,7 +152,7 @@ msgstr "Үй бумасының қолтаңба кілттерін басқар msgid "Authentication is required to manage signing keys for home directories." msgstr "Үй бумалары үшін қолтаңба кілттерін басқару үшін аутентификация қажет." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -161,31 +161,31 @@ msgstr "" "%s пайдаланушысының үй бумасы қазір жоқ, қажетті сақтау құрылғысын немесе " "негізгі файлдық жүйені қосыңыз." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "%s пайдаланушысы үшін кіру әрекеттері тым жиі, кейінірек қайталап көріңіз." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Пароль: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "%s пайдаланушысының аутентификациясы үшін пароль қате немесе жеткіліксіз." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Кешіріңіз, қайталап көріңіз: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Қалпына келтіру кілті: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -194,20 +194,20 @@ msgstr "" "%s пайдаланушысының аутентификациясы үшін пароль/қалпына келтіру кілті қате " "немесе жеткіліксіз." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Кешіріңіз, қалпына келтіру кілтін қайта енгізіңіз: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Парольмен қайталап көріңіз: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -216,28 +216,28 @@ msgstr "" "Пароль қате немесе жеткіліксіз және %s пайдаланушысының бапталған " "қауіпсіздік токені салынбаған." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "Қауіпсіздік токенінің PIN коды: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" "%s пайдаланушысының қауіпсіздік токенінде физикалық түрде аутентификациядан " "өтіңіз." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "%s пайдаланушысының қауіпсіздік токені болуын растаңыз." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "%s пайдаланушысының қауіпсіздік токенінде пайдаланушыны тексеріңіз." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -245,85 +245,85 @@ msgstr "" "Қауіпсіздік токенінің PIN коды құлыпталған, алдымен оның құлпын ашыңыз. " "(Тұспал: Алып тастау және қайта салу жеткілікті болуы мүмкін.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Кешіріңіз, қауіпсіздік токенінің PIN кодын қайталап көріңіз: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, 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 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "%s пайдаланушысының қауіпсіздік токенінің PIN коды қате (тек бір талап " "қалды!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "%s пайдаланушысының үй бумасы қазір құлыпталған, алдымен жергілікті түрде " "құлпын ашыңыз." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "%s пайдаланушысы үшін сәтсіз кіру әрекеттері тым көп, бас тартылды." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "Пайдаланушы жазбасы блокталған, қол жеткізуге тыйым салынған." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "Пайдаланушы жазбасы әлі жарамсыз, қол жеткізуге тыйым салынған." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "Пайдаланушы жазбасы енді жарамсыз, қол жеткізуге тыйым салынған." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Пайдаланушы жазбасы жарамсыз, қол жеткізуге тыйым салынған." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Кіру әрекеттері тым көп, %s кейін қайталап көріңіз." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Парольді өзгерту қажет." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Парольдің мерзімі өткен, өзгерту қажет." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" "Парольдің мерзімі өткен, бірақ оны өзгерту мүмкін емес, жүйеге кіруден бас " "тартылды." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Парольдің мерзімі жақында өтеді, оны өзгертіңіз." @@ -876,31 +876,40 @@ msgstr "" "аутентификация қажет." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Жергілікті виртуалды машина немесе контейнер жасау" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Жергілікті виртуалды машина немесе контейнер жасау үшін аутентификация қажет." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Жергілікті виртуалды машинаны немесе контейнерді тіркеу" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Жергілікті виртуалды машинаны немесе контейнерді тіркеу үшін аутентификация " "қажет." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Жергілікті виртуалды машина және контейнер бейнелерін басқару" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -908,6 +917,16 @@ msgstr "" "Жергілікті виртуалды машина және контейнер бейнелерін басқару үшін " "аутентификация қажет." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "NTP серверлерін орнату" @@ -1017,8 +1036,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" @@ -1164,46 +1187,96 @@ msgstr "Статистиканы тастау" msgid "Authentication is required to reset statistics." msgstr "Статистиканы тастау үшін аутентификация қажет." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Жүйелік жаңартуларды тексеру" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "Жүйелік жаңартуларды тексеру үшін аутентификация қажет." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Жүйелік жаңартуларды орнату" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "Жүйелік жаңартуларды орнату үшін аутентификация қажет." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Жүйенің белгілі бір нұсқасын орнату" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up 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:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Қосымша мүмкіндіктерді басқару" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "Қосымша мүмкіндіктерді басқару үшін аутентификация қажет." @@ -1300,3 +1373,27 @@ msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "" "'$(unit)' юнитының процестерін қатыру немесе еріту үшін аутентификация қажет." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Ескі жүйелік жаңартуларды тазалау" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "Ескі жүйелік жаңартуларды тазалау үшін аутентификация қажет." diff --git a/po/km.po b/po/km.po index 2091fe42aab23..8102a4235b4b2 100644 --- a/po/km.po +++ b/po/km.po @@ -1,13 +1,13 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the systemd package. -# kanitha chim , 2025. +# kanitha chim , 2025, 2026. 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-09-28 10:07+0000\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:58+0000\n" "Last-Translator: kanitha chim \n" "Language-Team: Khmer (Central) \n" @@ -16,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.13.3\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -148,7 +148,7 @@ msgstr "គ្រប់គ្រង​ Home Directory Signing Keys" msgid "Authentication is required to manage signing keys for home directories." msgstr "Authentication គឺតម្រូវឱ្យគ្រប់គ្រង signing keys សម្រាប់ home directories។" -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -156,49 +156,49 @@ msgid "" msgstr "" "Home របស់អ្នកប្រើប្រាស់ %s បច្ចុប្បន្នគឺអវត្តមាន សូមដោតឧបករណ៍ផ្ទុកចាំបាច់ ឬប្រព័ន្ធឯកសារបម្រុងទុក។" -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "ការព្យាយាមចូល​​ login ញឹកញាប់ពេកសម្រាប់អ្នកប្រើប្រាស់ %s សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ។" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "ពាក្យសម្ងាត់: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "ពាក្យសម្ងាត់មិនត្រឹមត្រូវ ឬមិនគ្រប់គ្រាន់សម្រាប់ការផ្ទៀងផ្ទាត់អ្នកប្រើប្រាស់ %s ។" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "សូមអភ័យទោស សូមព្យាយាមម្តងទៀត៖ " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Recovery key៖ " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "លេខសំងាត់/កូនសោសង្គ្រោះមិនត្រឹមត្រូវ ឬមិនគ្រប់គ្រាន់សម្រាប់ការផ្ទៀងផ្ទាត់អ្នកប្រើប្រាស់ %s ។" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "សូមអភ័យទោស សូមបញ្ចូលសោសង្គ្រោះឡើងវិញ៖ " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "ព្យាយាមម្តងទៀតដោយប្រើពាក្យសម្ងាត់៖ " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -207,26 +207,26 @@ msgstr "" "ពាក្យ​សម្ងាត់​មិន​ត្រឹមត្រូវ ឬ​មិន​គ្រប់គ្រាន់ ហើយ​ configured security token ​របស់​អ្នក​ប្រើ %s មិន​ត្រូវ​" "បាន​បញ្ចូល​ទេ។" -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "កូដ token PIN សុវត្ថិភាព៖ " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "សូមផ្ទៀងផ្ទាត់ physically on security token របស់អ្នកប្រើប្រាស់ %s ។" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "សូមបញ្ជាក់វត្តមាននៅលើនិមិត្តសញ្ញាសុវត្ថិភាពរបស់អ្នកប្រើប្រាស់ %s។" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "សូមផ្ទៀងផ្ទាត់អ្នកប្រើប្រាស់លើសញ្ញាសម្ងាត់សុវត្ថិភាពរបស់អ្នកប្រើប្រាស់ %s ។" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -234,75 +234,75 @@ msgstr "" "កូដ PIN និមិត្តសញ្ញាសុវត្ថិភាពត្រូវបានចាក់សោ សូមដោះសោជាមុនសិន។ (ព័ត៌មានជំនួយ៖ ការដក " "និងការបញ្ចូលឡើងវិញអាចគ្រប់គ្រាន់។ )" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "កូដ PIN និមិត្តសញ្ញាសុវត្ថិភាពមិនត្រឹមត្រូវសម្រាប់អ្នកប្រើប្រាស់ %s ។" -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "សូមអភ័យទោស សូមសាកល្បងកូដ PIN និមិត្តសញ្ញាសុវត្ថិភាពម្តងទៀត៖ " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "កូដ PIN និមិត្តសញ្ញាសុវត្ថិភាពរបស់អ្នកប្រើ %s មិនត្រឹមត្រូវ (នៅសល់តែព្យាយាមពីរបីដងប៉ុណ្ណោះ!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "កូដ PIN និមិត្តសញ្ញាសុវត្ថិភាពរបស់អ្នកប្រើ %s មិនត្រឹមត្រូវ (ព្យាយាមនៅសល់តែមួយប៉ុណ្ណោះ!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "ទំព័រដើមរបស់អ្នកប្រើ %s បច្ចុប្បន្នមិនសកម្មទេ សូម log in locally ជាមុនសិន។" -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "ទំព័រដើមរបស់អ្នកប្រើ %s បច្ចុប្បន្នត្រូវបានចាក់សោ សូមដោះសោនៅក្នុងមូលដ្ឋានជាមុនសិន។" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "ការប៉ុនប៉ង log in មិនជោគជ័យច្រើនពេកសម្រាប់អ្នកប្រើប្រាស់ %s, បដិសេធ។" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "កំណត់ត្រាអ្នកប្រើប្រាស់ត្រូវបានរារាំង, ហាមឃាត់ការចូលប្រើប្រាស់។" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "កំណត់​ត្រា​អ្នក​ប្រើ​មិន​ទាន់​មាន​សុពលភាព​នៅ​ឡើយ​ទេ, ​ហាម​ឃាត់​ការ​ចូល​ប្រើប្រាស់។" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "កំណត់​ត្រា​អ្នក​ប្រើ​មិន​ត្រឹមត្រូវ​ទៀត​ទេ, ​ហាម​ឃាត់​ការ​ចូល​ប្រើ។" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "កំណត់​ត្រា​អ្នក​ប្រើ​មិន​ត្រឹមត្រូវ, ហាម​ឃាត់​ការ​ចូល​ប្រើ។" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "ការ log in ច្រើនពេក សូមព្យាយាមម្តងទៀតក្នុង %s ។" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "ទាមទារការផ្លាស់ប្តូរពាក្យសម្ងាត់។" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "ពាក្យសម្ងាត់ផុតកំណត់ ទាមទារការផ្លាស់ប្តូរ។" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "ពាក្យ​សម្ងាត់​បាន​ផុត​កំណត់ ប៉ុន្តែ​មិន​អាច​ផ្លាស់​ប្តូរ​បាន, ​បដិសេធ​ការ​ log in។" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "ពាក្យសម្ងាត់នឹងផុតកំណត់ក្នុងពេលឆាប់ៗនេះ សូមផ្លាស់ប្តូរ។" @@ -808,33 +808,52 @@ msgid "" msgstr "ការផ្ទៀងផ្ទាត់គឺត្រូវបានទាមទារដើម្បីគ្រប់គ្រងម៉ាស៊ីននិម្មិតក្នុងតំបន់ និង containers។" #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "បង្កើតម៉ាស៊ីននិម្មិត ឬ container" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "ការផ្ទៀងផ្ទាត់គឺតម្រូវឱ្យបង្កើតម៉ាស៊ីននិម្មិតក្នុងស្រុក ឬ container។" -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "ចុះឈ្មោះម៉ាស៊ីននិម្មិតក្នុងស្រុក ឬ container" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "ការផ្ទៀងផ្ទាត់គឺតម្រូវឱ្យចុះឈ្មោះម៉ាស៊ីននិម្មិតក្នុងស្រុក ឬ container។" -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "គ្រប់គ្រងម៉ាស៊ីននិម្មិតក្នុងតំបន់ និង container images" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "ការផ្ទៀងផ្ទាត់គឺទាមទារដើម្បីគ្រប់គ្រងម៉ាស៊ីននិម្មិតក្នុងតំបន់ និង container images។" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "កំណត់ម៉ាស៊ីនមេ NTP" @@ -944,7 +963,10 @@ msgid "DHCP server sends force renew message" msgstr "ម៉ាស៊ីនមេ DHCP ផ្ញើ force renew message" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +#, fuzzy +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "ការផ្ទៀងផ្ទាត់តម្រូវឱ្យផ្ញើ force renew message។" #: src/network/org.freedesktop.network1.policy:154 @@ -1089,45 +1111,95 @@ msgstr "កំណត់ស្ថិតិឡើងវិញ" msgid "Authentication is required to reset statistics." msgstr "ការផ្ទៀងផ្ទាត់គឺតម្រូវឱ្យកំណត់ឡើងវិញនូវស្ថិតិ។" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "ពិនិត្យមើលបច្ចុប្បន្នភាពនៃប្រព័ន្ធ" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "តម្រូវឱ្យមានការផ្ទៀងផ្ទាត់ភាពត្រឹមត្រូវ ដើម្បីពិនិត្យមើលបច្ចុប្បន្នភាពនៃប្រព័ន្ធ។" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "ដំឡើងបច្ចុប្បន្នភាពប្រព័ន្ធ" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "ការផ្ទៀងផ្ទាត់គឺតម្រូវឱ្យដំឡើងបច្ចុប្បន្នភាពប្រព័ន្ធ។" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "ដំឡើងកំណែប្រព័ន្ធជាក់លាក់" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up 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:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "គ្រប់គ្រងមុខងារ optional" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "ការផ្ទៀងផ្ទាត់ត្រូវបានទាមទារដើម្បីគ្រប់គ្រងមុខងារ optional។" @@ -1213,3 +1285,27 @@ msgstr "ការផ្ទៀងផ្ទាត់គឺតម្រូវឱ្ msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "ការផ្ទៀងផ្ទាត់គឺតម្រូវឱ្យបង្កក ឬរលាយដំណើរការនៃឯកតា '$(unit)'។" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "សម្អាតបច្ចុប្បន្នភាពប្រព័ន្ធចាស់" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "ការផ្ទៀងផ្ទាត់គឺតម្រូវឱ្យសម្អាតបច្ចុប្បន្នភាពនៃប្រព័ន្ធចាស់។" diff --git a/po/kn.po b/po/kn.po index 67fa5f5d13e0a..d48b8801dcdfa 100644 --- a/po/kn.po +++ b/po/kn.po @@ -6,14 +6,17 @@ 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: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:58+0000\n" +"Last-Translator: Anonymous \n" +"Language-Team: Kannada \n" "Language: kn\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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -141,156 +144,156 @@ msgstr "" msgid "Authentication is required to manage signing keys for home directories." msgstr "" -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "" -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "" -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "" -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "" @@ -787,33 +790,52 @@ msgid "" msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 -msgid "Create a local virtual machine or container" +msgid "Inspect local virtual machines and containers" msgstr "" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" -"Authentication is required to create a local virtual machine or container." +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" msgstr "" #: src/machine/org.freedesktop.machine1.policy:106 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "" @@ -923,7 +945,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 @@ -1066,45 +1090,95 @@ msgstr "" msgid "Authentication is required to reset statistics." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "" @@ -1190,3 +1264,21 @@ msgstr "" msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" diff --git a/po/ko.po b/po/ko.po index e8bfb0ff87067..27652de168dc1 100644 --- a/po/ko.po +++ b/po/ko.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-27 16:58+0000\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-22 17:44+0000\n" "Last-Translator: 김인수 \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 2026.6.1\n" "X-Poedit-SourceCharset: UTF-8\n" #: src/core/org.freedesktop.systemd1.policy.in:22 @@ -149,7 +149,7 @@ msgstr "홈 디렉토리의 서명 키를 관리합니다" msgid "Authentication is required to manage signing keys for home directories." msgstr "홈 디렉토리를 위해 서명 키를 관리하려면 인증이 필요합니다." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -158,30 +158,30 @@ msgstr "" "사용자 %s의 홈은 현재 비어 있으며, 필요한 저장 장치 또는 파일 시스템 백업에 " "연결하세요." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "사용자 %s를 위해 너무나 많은 로그인 시도가 있었으며, 다음에 다시 시도하세요." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "비밀번호: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "비밀번호가 올바르지 않거나 사용자 %s의 인증에 충분하지 않습니다." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "미안하지만, 다시 시도해 주세요: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "복원 키: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -189,20 +189,20 @@ msgid "" msgstr "" "비밀번호/복구 키가 올바르지 않거나 사용자 %s의 인증에 충분하지 않습니다." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "미안하지만, 복구 키를 재입력하세요: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "비밀번호로 다시 시도하세요: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -211,26 +211,26 @@ msgstr "" "비밀번호가 올바르지 않거나 인증에 충분하지 않으며, 그리고 사용자 %s의 구성된 " "보안 토큰이 삽입되어 있지 않습니다." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "보안 토큰 PIN: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "사용자 %s의 보안 토큰에서 물리적으로 인증해주세요." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "사용자 %s의 보안 토큰에서 존재를 확인해 주세요." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "사용자 %s의 보안 토큰에서 사용자를 확인해 주세요." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -238,76 +238,76 @@ msgstr "" "보안 토큰 핀이 잠겨 있으며, 이를 우선 잠금 해제를 해주세요. (암시: 제거하고 " "다시-삽입으로 충분 할 수 있습니다.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "사용자 %s를 위해 잘못된 보안 토큰 핀." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "미안하지만, 보안 토큰 PIN을 다시 시도하세요: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, 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 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "사용자 %s의 보안 토큰 PIN이 잘못됨 (한 번만 남았습니다!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "사용자 %s의 홈은 현재 잠겨 있으며, 우선 로컬에서 잠금 해제를 해주세요." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "사용자 %s에 대한 너무 많이 실패한 로그인이 있었으며, 거부합니다." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "사용자 레코드가 차단되었으며, 접근을 금지합니다." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "사용자 레크드가 아직 유효하지 않아, 접근을 금지합니다." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "사용자 레코드가 더 이상 유효하지 않아, 접근을 금지합니다." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "사용자 레코드가 유효하지 않아, 접근을 금지합니다." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "너무 많은 로그인, %s에서 다시 시도하세요." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "비밀번호 변경이 필요합니다." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "비밀번호가 만료되었으며, 변경이 필요합니다." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "비밀번호가 만료되었으나, 변경 할 수 없고, 로그인이 거부됩니다." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "비밀번호가 곧 만료됩니다, 변경해 주세요." @@ -830,33 +830,52 @@ msgid "" msgstr "로컬 가상 머신 및 컨테이너를 관리하려면 인증이 필요합니다." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "로컬 가상 장비 및 컨테이너를 검사합니다" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "인증은 로컬 가상 장비 및 컨테이너를 검사하기 위해 필요합니다." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "로컬 가상 장비 및 컨테이너 생성" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "로컬 가상 장비 및 컨테이너를 생성하려면 인증이 필요합니다." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "로컬 가상 장비나 컨테이너를 등록합니다" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "로컬 가상 장비나 컨테이너를 등록하려면 인증이 필요합니다." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "로컬 가상 머신 및 컨테이너 이미지 관리" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "로컬 가상 머신 및 컨테이너 이미지를 관리하려면 인증이 필요합니다." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "로컬 가상 장비 및 컨테이너 이미지를 검사합니다" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "인증은 로컬 가상 장비 및 컨터에이너를 검사하기 위해 필요합니다." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "NTP 서버 설정" @@ -966,8 +985,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" @@ -1111,46 +1132,98 @@ msgstr "통계 재설정" msgid "Authentication is required to reset statistics." msgstr "통계를 재설정하려면 인증이 필요합니다." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "DNS 캐쉬 동기화" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "인증은 DNS 캐쉬 동기화가 필요합니다." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "서버 기능을 초기화합니다" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "인증은 서버 기능을 초기화하는 데 필요합니다." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "시스템 최신화를 위한 점검" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "시스템 최신화를 점검하려면 인증이 필요합니다." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "시스템 최신화를 위해 점검하기를 취소합니다" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "인증은 시스템 최신화를 위해 점검하기를 취소해야 합니다." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "시스템 최신화 설치" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "시스템 최신화로 설치하려면 인증이 필요합니다." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "시스템 최신화 설치하기를 취소합니다" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "인증은 시스템 최신화 설치하기를 취소해야 합니다." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "지정된 시스템 버전 설치" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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:88 +msgid "Cancel installing specific system version" +msgstr "지정된 시스템 버전 설치하기를 취소합니다" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" +"시스템을 지정된 (아마도 오래된) 버전으로 최신화하기를 취소하려면 인증이 필요" +"합니다." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "오래된 시스템 최신화를 정리합니다" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." msgstr "오래된 시스템 최신화를 정리하려면 인증이 필요합니다." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "오래된 최신화 정리하기를 취소합니다" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "오래된 시스템 최신화 정리를 취소하려면 인증이 필요합니다." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "추가 사양을 관리합니다" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "추가 기능을 관리하려면 인증이 필요합니다." @@ -1239,3 +1312,27 @@ msgstr "인증은 '$(unit)'과 관련된 파일과 디렉토리를 삭제하는 msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "'$(unit)'단위의 처리를 동결 또는 해제하려면 인증이 필요합니다." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "잠금 해제하려면 보안 토큰의 존재를 확인하세요." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "잠금 해제하려면 보안 토큰에서 사용자를 확인하세요." + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "보안 토큰 PIN을 입력하세요 (잠금 전 남은 시도 횟수: %d):" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "보안 토큰 PIN을 입력하세요:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "오래된 시스템 최신화 정리" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "오래된 시스템 최신화를 정리하려면 인증이 필요합니다." diff --git a/po/kw.po b/po/kw.po index fb973409da4d6..a1e7c3e7bd066 100644 --- a/po/kw.po +++ b/po/kw.po @@ -6,10 +6,11 @@ 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: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:01+0000\n" +"Last-Translator: Anonymous \n" +"Language-Team: Cornish \n" "Language: kw\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -21,6 +22,7 @@ msgstr "" "1000000 == 100000) ? 2 : ((n % 100 == 3 || n % 100 == 23 || n % 100 == 43 || " "n % 100 == 63 || n % 100 == 83) ? 3 : ((n != 1 && (n % 100 == 1 || n % 100 " "== 21 || n % 100 == 41 || n % 100 == 61 || n % 100 == 81)) ? 4 : 5))));\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -148,156 +150,156 @@ msgstr "" msgid "Authentication is required to manage signing keys for home directories." msgstr "" -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "" -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "" -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "" -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "" @@ -794,33 +796,52 @@ msgid "" msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 -msgid "Create a local virtual machine or container" +msgid "Inspect local virtual machines and containers" msgstr "" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" -"Authentication is required to create a local virtual machine or container." +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" msgstr "" #: src/machine/org.freedesktop.machine1.policy:106 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "" @@ -930,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 @@ -1073,45 +1096,95 @@ msgstr "" msgid "Authentication is required to reset statistics." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "" @@ -1197,3 +1270,21 @@ msgstr "" msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" diff --git a/po/lo.po b/po/lo.po new file mode 100644 index 0000000000000..922b823991faa --- /dev/null +++ b/po/lo.po @@ -0,0 +1,1295 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the systemd package. +# Bone NI , 2026. +msgid "" +msgstr "" +"Project-Id-Version: systemd\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:01+0000\n" +"Last-Translator: Bone NI \n" +"Language-Team: Lao \n" +"Language: lo\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: Weblate 2026.6.1\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:327 +#, 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:332 +#, c-format +msgid "Too frequent login attempts for user %s, try again later." +msgstr "ມີການພະຍາຍາມເຂົ້າສູ່ລະບົບສຳລັບຜູ້ໃຊ້ %s ຫຼາຍເກີນໄປ, ກະລຸນາລອງໃໝ່ພາຍຫຼັງ." + +#: src/home/pam_systemd_home.c:344 +msgid "Password: " +msgstr "ລະຫັດຜ່ານ: " + +#: src/home/pam_systemd_home.c:346 +#, c-format +msgid "Password incorrect or not sufficient for authentication of user %s." +msgstr "ລະຫັດຜ່ານບໍ່ຖືກຕ້ອງ ຫຼື ບໍ່ພຽງພໍສຳລັບການຢືນຢັນຕົວຕົນຂອງຜູ້ໃຊ້ %s." + +#: src/home/pam_systemd_home.c:347 +msgid "Sorry, try again: " +msgstr "ຂໍອະໄພ, ກະລຸນາລອງໃໝ່: " + +#: src/home/pam_systemd_home.c:369 +msgid "Recovery key: " +msgstr "ລະຫັດກູ້ຄືນ: " + +#: src/home/pam_systemd_home.c:371 +#, c-format +msgid "" +"Password/recovery key incorrect or not sufficient for authentication of user " +"%s." +msgstr "ລະຫັດຜ່ານ/ລະຫັດກູ້ຄືນ ບໍ່ຖືກຕ້ອງ ຫຼື ບໍ່ພຽງພໍສຳລັບການຢືນຢັນຕົວຕົນຂອງຜູ້ໃຊ້ %s." + +#: src/home/pam_systemd_home.c:372 +msgid "Sorry, reenter recovery key: " +msgstr "ຂໍອະໄພ, ກະລຸນາປ້ອນລະຫັດກູ້ຄືນໃໝ່: " + +#: src/home/pam_systemd_home.c:392 +#, c-format +msgid "Security token of user %s not inserted." +msgstr "ບໍ່ໄດ້ສຽບໂທເຄັນຄວາມປອດໄພຂອງຜູ້ໃຊ້ %s." + +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 +msgid "Try again with password: " +msgstr "ລອງໃໝ່ດ້ວຍລະຫັດຜ່ານ: " + +#: src/home/pam_systemd_home.c:395 +#, 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:415 +msgid "Security token PIN: " +msgstr "ລະຫັດ PIN ຂອງໂທເຄັນຄວາມປອດໄພ: " + +#: src/home/pam_systemd_home.c:432 +#, c-format +msgid "Please authenticate physically on security token of user %s." +msgstr "ກະລຸນາຢືນຢັນຕົວຕົນດ້ວຍຕົນເອງທີ່ໂທເຄັນຄວາມປອດໄພຂອງຜູ້ໃຊ້ %s." + +#: src/home/pam_systemd_home.c:443 +#, c-format +msgid "Please confirm presence on security token of user %s." +msgstr "ກະລຸນາຢືນຢັນການມີຕົວຕົນຢູ່ທີ່ໂທເຄັນຄວາມປອດໄພຂອງຜູ້ໃຊ້ %s." + +#: src/home/pam_systemd_home.c:454 +#, c-format +msgid "Please verify user on security token of user %s." +msgstr "ກະລຸນາກວດສອບຜູ້ໃຊ້ຢູ່ທີ່ໂທເຄັນຄວາມປອດໄພຂອງຜູ້ໃຊ້ %s." + +#: src/home/pam_systemd_home.c:463 +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:471 +#, c-format +msgid "Security token PIN incorrect for user %s." +msgstr "ລະຫັດ PIN ຂອງໂທເຄັນຄວາມປອດໄພບໍ່ຖືກຕ້ອງສຳລັບຜູ້ໃຊ້ %s." + +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 +msgid "Sorry, retry security token PIN: " +msgstr "ຂໍອະໄພ, ລອງປ້ອນ PIN ຂອງໂທເຄັນຄວາມປອດໄພໃໝ່: " + +#: src/home/pam_systemd_home.c:490 +#, c-format +msgid "Security token PIN of user %s incorrect (only a few tries left!)" +msgstr "ລະຫັດ PIN ຂອງໂທເຄັນຄວາມປອດໄພຂອງຜູ້ໃຊ້ %s ບໍ່ຖືກຕ້ອງ (ເຫຼືອການພະຍາຍາມອີກບໍ່ເທົ່າໃດຄັ້ງ!)" + +#: src/home/pam_systemd_home.c:509 +#, c-format +msgid "Security token PIN of user %s incorrect (only one try left!)" +msgstr "ລະຫັດ PIN ຂອງໂທເຄັນຄວາມປອດໄພຂອງຜູ້ໃຊ້ %s ບໍ່ຖືກຕ້ອງ (ເຫຼືອການພະຍາຍາມອີກພຽງຄັ້ງດຽວ!)" + +#: src/home/pam_systemd_home.c:676 +#, c-format +msgid "Home of user %s is currently not active, please log in locally first." +msgstr "ພື້ນທີ່ໂຮມຂອງຜູ້ໃຊ້ %s ຍັງບໍ່ທັນເປີດໃຊ້ງານ, ກະລຸນາເຂົ້າສູ່ລະບົບໂດຍກົງຢູ່ເຄື່ອງກ່ອນ." + +#: src/home/pam_systemd_home.c:678 +#, c-format +msgid "Home of user %s is currently locked, please unlock locally first." +msgstr "ພື້ນທີ່ໂຮມຂອງຜູ້ໃຊ້ %s ຖືກລັອກຢູ່, ກະລຸນາປົດລັອກໂດຍກົງຢູ່ເຄື່ອງກ່ອນ." + +#: src/home/pam_systemd_home.c:712 +#, c-format +msgid "Too many unsuccessful login attempts for user %s, refusing." +msgstr "ມີການພະຍາຍາມເຂົ້າສູ່ລະບົບບໍ່ສຳເລັດຫຼາຍເກີນໄປສຳລັບຜູ້ໃຊ້ %s, ຂໍປະຕິເສດ." + +#: src/home/pam_systemd_home.c:1021 +msgid "User record is blocked, prohibiting access." +msgstr "ຂໍ້ມູນຜູ້ໃຊ້ຖືກບລັອກ, ຫ້າມເຂົ້າເຖິງ." + +#: src/home/pam_systemd_home.c:1025 +msgid "User record is not valid yet, prohibiting access." +msgstr "ຂໍ້ມູນຜູ້ໃຊ້ຍັງບໍ່ທັນມີຜົນໃຊ້ງານ, ຫ້າມເຂົ້າເຖິງ." + +#: src/home/pam_systemd_home.c:1029 +msgid "User record is not valid anymore, prohibiting access." +msgstr "ຂໍ້ມູນຜູ້ໃຊ້ບໍ່ມີຜົນໃຊ້ງານອີກຕໍ່ໄປ, ຫ້າມເຂົ້າເຖິງ." + +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 +msgid "User record not valid, prohibiting access." +msgstr "ຂໍ້ມູນຜູ້ໃຊ້ບໍ່ຖືກຕ້ອງ, ຫ້າມເຂົ້າເຖິງ." + +#: src/home/pam_systemd_home.c:1044 +#, c-format +msgid "Too many logins, try again in %s." +msgstr "ເຂົ້າສູ່ລະບົບຫຼາຍເກີນໄປ, ລອງໃໝ່ໃນອີກ %s." + +#: src/home/pam_systemd_home.c:1055 +msgid "Password change required." +msgstr "ຈຳເປັນຕ້ອງປ່ຽນລະຫັດຜ່ານ." + +#: src/home/pam_systemd_home.c:1059 +msgid "Password expired, change required." +msgstr "ລະຫັດຜ່ານໝົດອາຍຸ, ຈຳເປັນຕ້ອງປ່ຽນໃໝ່." + +#: src/home/pam_systemd_home.c:1065 +msgid "Password is expired, but can't change, refusing login." +msgstr "ລະຫັດຜ່ານໝົດອາຍຸ ແຕ່ບໍ່ສາມາດປ່ຽນໄດ້, ຂໍປະຕິເສດການເຂົ້າສູ່ລະບົບ." + +#: src/home/pam_systemd_home.c:1069 +msgid "Password will expire soon, please change." +msgstr "ລະຫັດຜ່ານຈະໝົດອາຍຸໃນໄວໆນີ້, ກະລຸນາປ່ຽນໃໝ່." + +#: src/hostname/org.freedesktop.hostname1.policy:20 +msgid "Set hostname" +msgstr "ຕັ້ງຊື່ໂຮສ (hostname)" + +#: 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 "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຕັ້ງຄ່າທ້ອງຖິ່ນຂອງລະບົບ." + +#: 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 "ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນລະງັບການຈັດການປຸ່ມເປີດປິດ (power key) ຂອງລະບົບ" + +#: 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 "ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນລະງັບການຈັດການປຸ່ມພັກເຄື່ອງ (suspend key) ຂອງລະບົບ" + +#: 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 "ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນລະງັບການຈັດການປຸ່ມຈຳສີນ (hibernate key) ຂອງລະບົບ" + +#: 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 "ອະນຸຍາດໃຫ້ແອັບພລິເຄຊັນລະງັບການຈັດການສະວິດຝາປິດ (lid switch) ຂອງລະບົບ" + +#: 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 "ອະນຸຍາດໃຫ້ເຊື່ອມຕໍ່ອຸປະກອນເຂົ້າກັບບ່ອນນັ່ງ (seats)" + +#: src/login/org.freedesktop.login1.policy:149 +msgid "Authentication is required to attach a device to a seat." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອເຊື່ອມຕໍ່ອຸປະກອນເຂົ້າກັບບ່ອນນັ່ງ." + +#: src/login/org.freedesktop.login1.policy:159 +msgid "Flush device to seat attachments" +msgstr "ລ້າງການເຊື່ອມຕໍ່ອຸປະກອນກັບບ່ອນນັ່ງ" + +#: src/login/org.freedesktop.login1.policy:160 +msgid "Authentication is required to reset how devices are attached to seats." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຕັ້ງຄ່າການເຊື່ອມຕໍ່ອຸປະກອນກັບບ່ອນນັ່ງຄືນໃໝ່." + +#: 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 "ຢຸດລະບົບ (Halt)" + +#: 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 "ພັກລະບົບ (Suspend)" + +#: 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 "ຈຳສີນລະບົບ (Hibernate)" + +#: 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 "ຈັດການເຊສຊັນທີ່ເປີດຢູ່, ຜູ້ໃຊ້ ແລະ ບ່ອນນັ່ງ" + +#: src/login/org.freedesktop.login1.policy:333 +msgid "Authentication is required to manage active sessions, users and seats." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຈັດການເຊສຊັນທີ່ເປີດຢູ່, ຜູ້ໃຊ້ ແລະ ບ່ອນນັ່ງ." + +#: 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 message)" + +#: src/login/org.freedesktop.login1.policy:397 +msgid "Authentication is required to set a wall message." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຕັ້ງຂໍ້ຄວາມແຈ້ງເຕືອນລະບົບ." + +#: 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 "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອປ່ຽນເທີມີນໍສະເໝືອນ (virtual terminal)." + +#: 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 "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຂໍໃຊ້ເຊວໃນຄອນເທນເນີພາຍໃນເຄື່ອງ." + +#: src/machine/org.freedesktop.machine1.policy:53 +msgid "Acquire a shell on the local host" +msgstr "ຂໍໃຊ້ເຊວໃນໂຮສພາຍໃນເຄື່ອງ" + +#: src/machine/org.freedesktop.machine1.policy:54 +msgid "Authentication is required to acquire a shell on the local host." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຂໍໃຊ້ເຊວໃນໂຮສພາຍໃນເຄື່ອງ." + +#: 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 "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" +msgstr "ສ້າງເຄື່ອງສະເໝືອນ ຫຼື ຄອນເທນເນີພາຍໃນເຄື່ອງ" + +#: src/machine/org.freedesktop.machine1.policy:106 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອສ້າງເຄື່ອງສະເໝືອນ ຫຼື ຄອນເທນເນີພາຍໃນເຄື່ອງ." + +#: src/machine/org.freedesktop.machine1.policy:116 +msgid "Register a local virtual machine or container" +msgstr "ລົງທະບຽນເຄື່ອງສະເໝືອນ ຫຼື ຄອນເທນເນີພາຍໃນເຄື່ອງ" + +#: src/machine/org.freedesktop.machine1.policy:117 +msgid "" +"Authentication is required to register a local virtual machine or container." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອລົງທະບຽນເຄື່ອງສະເໝືອນ ຫຼື ຄອນເທນເນີພາຍໃນເຄື່ອງ." + +#: src/machine/org.freedesktop.machine1.policy:126 +msgid "Manage local virtual machine and container images" +msgstr "ຈັດການອິເມຈຂອງເຄື່ອງສະເໝືອນ ແລະ ຄອນເທນເນີພາຍໃນເຄື່ອງ" + +#: src/machine/org.freedesktop.machine1.policy:127 +msgid "" +"Authentication is required to manage local virtual machine and container " +"images." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຈັດການອິເມຈຂອງເຄື່ອງສະເໝືອນ ແລະ ຄອນເທນເນີພາຍໃນເຄື່ອງ." + +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect 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 "ຕັ້ງຄ່າເສັ້ນທາງຫຼັກ (default route)" + +#: src/network/org.freedesktop.network1.policy:56 +#: src/resolve/org.freedesktop.resolve1.policy:67 +msgid "Authentication is required to set default route." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຕັ້ງຄ່າເສັ້ນທາງຫຼັກ." + +#: 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." + +#: 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 "ຈັດການການເຊື່ອມຕໍ່ເຄືອຂ່າຍ (network links)" + +#: 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)" + +#: src/portable/org.freedesktop.portable1.policy:14 +msgid "Authentication is required to inspect a portable service image." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອກວດສອບອິເມຈບໍລິການແບບພົກພາ." + +#: src/portable/org.freedesktop.portable1.policy:23 +msgid "Attach or detach a portable service image" +msgstr "ເຊື່ອມຕໍ່ ຫຼື ຕັດການເຊື່ອມຕໍ່ອິເມຈບໍລິການແບບພົກພາ" + +#: src/portable/org.freedesktop.portable1.policy:24 +msgid "" +"Authentication is required to attach or detach a portable service image." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອເຊື່ອມຕໍ່ ຫຼື ຕັດການເຊື່ອມຕໍ່ອິເມຈບໍລິການແບບພົກພາ." + +#: src/portable/org.freedesktop.portable1.policy:34 +msgid "Delete or modify portable service image" +msgstr "ລຶບ ຫຼື ແກ້ໄຂອິເມຈບໍລິການແບບພົກພາ" + +#: src/portable/org.freedesktop.portable1.policy:35 +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 +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 "ຄືນຄ່າການຕັ້ງຄ່າການແປງຊື່ (name resolution)" + +#: 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 "ຕິດຕາມຜົນການສອບຖາມ (query results)" + +#: 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 "ບັນທຶກແຄຊ໌ (Dump cache)" + +#: 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/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 +msgid "Check for system updates" +msgstr "ກວດສອບການອັບເດດລະບົບ" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 +msgid "Authentication is required to check for system updates." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອກວດສອບການອັບເດດລະບົບ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 +msgid "Install system updates" +msgstr "ຕິດຕັ້ງການອັບເດດລະບົບ" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 +msgid "Authentication is required to install system updates." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຕິດຕັ້ງການອັບເດດລະບົບ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 +msgid "Install specific system version" +msgstr "ຕິດຕັ້ງລະບົບເວີຊັນສະເພາະ" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 +msgid "" +"Authentication is required to update the system to a specific (possibly old) " +"version." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອອັບເດດລະບົບເປັນເວີຊັນສະເພາະ (ເຊິ່ງອາດຈະເປັນເວີຊັນເກົ່າ)." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 +msgid "Manage optional features" +msgstr "ຈັດການຄຸນສົມບັດເສີມ" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 +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 "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຕັ້ງສະຖານະ \"ຫຼົ້ມເຫຼວ\" (failed) ຂອງ '$(unit)' ຄືນໃໝ່." + +#: src/core/dbus-unit.c:679 +msgid "Authentication is required to set properties on '$(unit)'." +msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອຕັ້ງຄຸນສົມບັດ (properties) ຂອງ '$(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 "" +"ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອແຊ່ແຂງ (freeze) ຫຼື ປົດແຊ່ແຂງ (thaw) ໂພຣເຊສຂອງໜ່ວຍງານ '$(unit)'." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "ລ້າງການອັບເດດລະບົບເກົ່າ" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "ຕ້ອງມີການຢືນຢັນຕົວຕົນເພື່ອລ້າງການອັບເດດລະບົບເກົ່າ." diff --git a/po/lt.po b/po/lt.po index 49b3e0e026898..37bfb8b7bb6a9 100644 --- a/po/lt.po +++ b/po/lt.po @@ -1,14 +1,14 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # Moo, 2018. #zanata -# mooo , 2023, 2024. -# Justinas Kairys , 2025. +# mooo , 2023, 2024, 2026. +# Justinas Kairys , 2025, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-02-28 08:38+0000\n" -"Last-Translator: Justinas Kairys \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:56+0000\n" +"Last-Translator: mooo \n" "Language-Team: Lithuanian \n" "Language: lt\n" @@ -18,7 +18,7 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n % 10 == 1 && (n % 100 < 11 || n % 100 > " "19)) ? 0 : ((n % 10 >= 2 && n % 10 <= 9 && (n % 100 < 11 || n % 100 > 19)) ? " "1 : 2);\n" -"X-Generator: Weblate 5.10\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -165,7 +165,7 @@ msgstr "" "Norint tvarkyti sistemos tarnybas ar kitus įtaisus, reikia nustatyti " "tapatybę." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -174,32 +174,32 @@ msgstr "" "Naudotojo %s namų aplanko šiuo metu nėra, prašome prijungti reikiamą " "atminties įrenginį arba palaikomą failų sistemą." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Per daug prisijungimo bandymų naudotojui %s, pabandykite dar kartą vėliau." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Slaptažodis: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Slaptažodis yra neteisingas arba nepakankamas naudotojo %s tapatybės " "patvirtinimui." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Atleiskite, bandykite dar kartą: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Atkūrimo raktas: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -208,20 +208,20 @@ msgstr "" "Slaptažodis/atkūrimo raktas yra neteisingas arba nepakankamas naudotojo %s " "tapatybės patvirtinimui." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Atleiskite, įveskite atkūrimo raktą iš naujo: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "Naudotojo %s saugos atpažinimo ženklas yra neįdėtas." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Pabandykite dar kartą su slaptažodžiu: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -230,101 +230,101 @@ msgstr "" "Slaptažodis yra neteisingas arba jis yra nepakankamas, o naudotojo %s " "sukonfigūruotas saugos atpažinimo ženklas yra neįdėtas." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "Saugos atpažinimo ženklo PIN kodas: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "Prašome fiziškai patvirtinti tapatybę su naudotojo %s saugos žėtonu." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Per daug prisijungimų, bandoma dar kartą po %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Reikia pakeisti slaptažodį." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Slaptažodis nebegalioja, reikia pakeisti." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" "Slaptažodis nebegalioja, bet jo pakeisti negalima. Atsisakoma prisijungti." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Greitu metu slaptažodis nustos galioti, prašome jį pakeisti." @@ -906,11 +906,20 @@ msgstr "" "nustatyti tapatybę." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 #, fuzzy msgid "Create a local virtual machine or container" msgstr "Tvarkyti vietines virtualiąsias mašinas ir konteinerius" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 #, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." @@ -918,12 +927,12 @@ msgstr "" "Norint tvarkyti vietines virtualiąsias mašinas ir konteinerius, reikia " "nustatyti tapatybę." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "Tvarkyti vietines virtualiąsias mašinas ir konteinerius" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." @@ -931,11 +940,11 @@ msgstr "" "Norint tvarkyti vietines virtualiąsias mašinas ir konteinerius, reikia " "nustatyti tapatybę." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Tvarkyti vietinę virtualiąją mašiną ir konteinerio atvaizdžius" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -943,6 +952,16 @@ msgstr "" "Norint tvarkyti vietinę virtualiąją mašiną ir konteinerio atvaizdžius, " "reikia nustatyti tapatybę." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Nustatyti NTP serverius" @@ -1059,7 +1078,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 "Norint nustatyti sienos pranešimą, reikia nustatyti tapatybę" #: src/network/org.freedesktop.network1.policy:154 @@ -1220,49 +1241,98 @@ msgstr "" msgid "Authentication is required to reset statistics." msgstr "Norint nustatyti sistemos laiką, reikia nustatyti tapatybę." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 #, fuzzy msgid "Authentication is required to check for system updates." msgstr "Norint nustatyti sistemos laiką, reikia nustatyti tapatybę." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 #, fuzzy msgid "Authentication is required to install system updates." msgstr "Norint nustatyti sistemos laiką, reikia nustatyti tapatybę." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 #, fuzzy msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." msgstr "Norint nustatyti sistemos laiko juostą, reikia nustatyti tapatybę." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy -msgid "Authentication is required to cleanup old system updates." -msgstr "Norint nustatyti sistemos laiką, reikia nustatyti tapatybę." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 #, fuzzy msgid "Authentication is required to manage optional features." msgstr "" @@ -1368,6 +1438,28 @@ msgid "" msgstr "" "Norint siųsti UNIX signalą į \"$(unit)\" procesus, reikia nustatyti tapatybę." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#, fuzzy +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "Norint nustatyti sistemos laiką, reikia nustatyti tapatybę." + #~ msgid "" #~ "Authentication is required to halt the system while an application asked " #~ "to inhibit it." diff --git a/po/meson.build b/po/meson.build index 0a140b40b50cb..a1d3a5edc128c 100644 --- a/po/meson.build +++ b/po/meson.build @@ -3,6 +3,10 @@ want_translations = get_option('translations') if want_translations + # The msgmerge invocation hidden behind i18n.gettext()'s update-po target inserts auto-guessed + # "fuzzy" translations for new strings, which are almost always wrong, use a wrapper to skip it. + meson.override_find_program('msgmerge', msgmerge_no_fuzzy_py) + i18n = import('i18n') i18n.gettext(meson.project_name(), preset : 'glib', diff --git a/po/nb_NO.po b/po/nb_NO.po new file mode 100644 index 0000000000000..60a383eb14dca --- /dev/null +++ b/po/nb_NO.po @@ -0,0 +1,1282 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Norwegian Bokmål translation for systemd package +# arvind froiland , 2026. +msgid "" +msgstr "" +"Project-Id-Version: systemd\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: nb_NO\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" + +#: 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 "" + +#: src/core/org.freedesktop.systemd1.policy.in:65 +msgid "Authentication is required to reload the systemd state." +msgstr "" + +#: src/core/org.freedesktop.systemd1.policy.in:74 +msgid "Dump the systemd state without rate limits" +msgstr "" + +#: src/core/org.freedesktop.systemd1.policy.in:75 +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 "" + +#: 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:327 +#, 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:332 +#, c-format +msgid "Too frequent login attempts for user %s, try again later." +msgstr "" + +#: src/home/pam_systemd_home.c:344 +msgid "Password: " +msgstr "" + +#: src/home/pam_systemd_home.c:346 +#, c-format +msgid "Password incorrect or not sufficient for authentication of user %s." +msgstr "" + +#: src/home/pam_systemd_home.c:347 +msgid "Sorry, try again: " +msgstr "" + +#: src/home/pam_systemd_home.c:369 +msgid "Recovery key: " +msgstr "" + +#: src/home/pam_systemd_home.c:371 +#, c-format +msgid "" +"Password/recovery key incorrect or not sufficient for authentication of user " +"%s." +msgstr "" + +#: src/home/pam_systemd_home.c:372 +msgid "Sorry, reenter recovery key: " +msgstr "" + +#: src/home/pam_systemd_home.c:392 +#, c-format +msgid "Security token of user %s not inserted." +msgstr "" + +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 +msgid "Try again with password: " +msgstr "" + +#: src/home/pam_systemd_home.c:395 +#, c-format +msgid "" +"Password incorrect or not sufficient, and configured security token of user " +"%s not inserted." +msgstr "" + +#: src/home/pam_systemd_home.c:415 +msgid "Security token PIN: " +msgstr "" + +#: src/home/pam_systemd_home.c:432 +#, c-format +msgid "Please authenticate physically on security token of user %s." +msgstr "" + +#: src/home/pam_systemd_home.c:443 +#, c-format +msgid "Please confirm presence on security token of user %s." +msgstr "" + +#: src/home/pam_systemd_home.c:454 +#, c-format +msgid "Please verify user on security token of user %s." +msgstr "" + +#: src/home/pam_systemd_home.c:463 +msgid "" +"Security token PIN is locked, please unlock it first. (Hint: Removal and re-" +"insertion might suffice.)" +msgstr "" + +#: src/home/pam_systemd_home.c:471 +#, c-format +msgid "Security token PIN incorrect for user %s." +msgstr "" + +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 +msgid "Sorry, retry security token PIN: " +msgstr "" + +#: src/home/pam_systemd_home.c:490 +#, c-format +msgid "Security token PIN of user %s incorrect (only a few tries left!)" +msgstr "" + +#: src/home/pam_systemd_home.c:509 +#, c-format +msgid "Security token PIN of user %s incorrect (only one try left!)" +msgstr "" + +#: src/home/pam_systemd_home.c:676 +#, c-format +msgid "Home of user %s is currently not active, please log in locally first." +msgstr "" + +#: src/home/pam_systemd_home.c:678 +#, c-format +msgid "Home of user %s is currently locked, please unlock locally first." +msgstr "" + +#: src/home/pam_systemd_home.c:712 +#, c-format +msgid "Too many unsuccessful login attempts for user %s, refusing." +msgstr "" + +#: src/home/pam_systemd_home.c:1021 +msgid "User record is blocked, prohibiting access." +msgstr "" + +#: src/home/pam_systemd_home.c:1025 +msgid "User record is not valid yet, prohibiting access." +msgstr "" + +#: src/home/pam_systemd_home.c:1029 +msgid "User record is not valid anymore, prohibiting access." +msgstr "" + +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 +msgid "User record not valid, prohibiting access." +msgstr "" + +#: src/home/pam_systemd_home.c:1044 +#, c-format +msgid "Too many logins, try again in %s." +msgstr "" + +#: src/home/pam_systemd_home.c:1055 +msgid "Password change required." +msgstr "" + +#: src/home/pam_systemd_home.c:1059 +msgid "Password expired, change required." +msgstr "" + +#: src/home/pam_systemd_home.c:1065 +msgid "Password is expired, but can't change, refusing login." +msgstr "" + +#: src/home/pam_systemd_home.c:1069 +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 "" + +#: src/hostname/org.freedesktop.hostname1.policy:52 +msgid "Authentication is required to get product UUID." +msgstr "" + +#: 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 "" + +#: src/login/org.freedesktop.login1.policy:149 +msgid "Authentication is required to attach a device to a seat." +msgstr "" + +#: src/login/org.freedesktop.login1.policy:159 +msgid "Flush device to seat attachments" +msgstr "" + +#: src/login/org.freedesktop.login1.policy:160 +msgid "Authentication is required to reset how devices are attached to seats." +msgstr "" + +#: 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 "" + +#: src/login/org.freedesktop.login1.policy:333 +msgid "Authentication is required to manage active sessions, users and seats." +msgstr "" + +#: 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 "" + +#: src/login/org.freedesktop.login1.policy:397 +msgid "Authentication is required to set a wall message." +msgstr "" + +#: 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 "" + +#: src/machine/org.freedesktop.machine1.policy:43 +msgid "Authentication is required to acquire a shell in a local container." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:53 +msgid "Acquire a shell on the local host" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:54 +msgid "Authentication is required to acquire a shell on the local host." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:64 +msgid "Acquire a pseudo TTY in a local container" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:65 +msgid "" +"Authentication is required to acquire a pseudo TTY in a local container." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:74 +msgid "Acquire a pseudo TTY on the local host" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:75 +msgid "Authentication is required to acquire a pseudo TTY on the local host." +msgstr "" + +#: 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 "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:106 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:116 +msgid "Register a local virtual machine or container" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:117 +msgid "" +"Authentication is required to register a local virtual machine or container." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:126 +msgid "Manage local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:127 +msgid "" +"Authentication is required to manage local virtual machine and container " +"images." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + +#: src/network/org.freedesktop.network1.policy:22 +msgid "Set NTP servers" +msgstr "" + +#: src/network/org.freedesktop.network1.policy:23 +msgid "Authentication is required to set NTP servers." +msgstr "" + +#: src/network/org.freedesktop.network1.policy:33 +#: src/resolve/org.freedesktop.resolve1.policy:44 +msgid "Set DNS servers" +msgstr "" + +#: src/network/org.freedesktop.network1.policy:34 +#: src/resolve/org.freedesktop.resolve1.policy:45 +msgid "Authentication is required to set DNS servers." +msgstr "" + +#: 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 "" + +#: src/network/org.freedesktop.network1.policy:56 +#: src/resolve/org.freedesktop.resolve1.policy:67 +msgid "Authentication is required to set default route." +msgstr "" + +#: src/network/org.freedesktop.network1.policy:66 +#: src/resolve/org.freedesktop.resolve1.policy:77 +msgid "Enable/disable LLMNR" +msgstr "" + +#: src/network/org.freedesktop.network1.policy:67 +#: src/resolve/org.freedesktop.resolve1.policy:78 +msgid "Authentication is required to enable or disable LLMNR." +msgstr "" + +#: src/network/org.freedesktop.network1.policy:77 +#: src/resolve/org.freedesktop.resolve1.policy:88 +msgid "Enable/disable multicast DNS" +msgstr "" + +#: 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 "" + +#: src/network/org.freedesktop.network1.policy:88 +#: src/resolve/org.freedesktop.resolve1.policy:99 +msgid "Enable/disable DNS over TLS" +msgstr "" + +#: 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 "" + +#: src/network/org.freedesktop.network1.policy:99 +#: src/resolve/org.freedesktop.resolve1.policy:110 +msgid "Enable/disable DNSSEC" +msgstr "" + +#: src/network/org.freedesktop.network1.policy:100 +#: src/resolve/org.freedesktop.resolve1.policy:111 +msgid "Authentication is required to enable or disable DNSSEC." +msgstr "" + +#: src/network/org.freedesktop.network1.policy:110 +#: src/resolve/org.freedesktop.resolve1.policy:121 +msgid "Set DNSSEC Negative Trust Anchors" +msgstr "" + +#: 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 "" + +#: src/network/org.freedesktop.network1.policy:121 +msgid "Revert NTP settings" +msgstr "" + +#: src/network/org.freedesktop.network1.policy:122 +msgid "Authentication is required to reset NTP settings." +msgstr "" + +#: src/network/org.freedesktop.network1.policy:132 +msgid "Revert DNS settings" +msgstr "" + +#: src/network/org.freedesktop.network1.policy:133 +msgid "Authentication is required to reset DNS settings." +msgstr "" + +#: src/network/org.freedesktop.network1.policy:143 +msgid "DHCP server sends force renew message" +msgstr "" + +#: src/network/org.freedesktop.network1.policy:144 +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 "" + +#: 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 "" + +#: src/network/org.freedesktop.network1.policy:188 +msgid "" +"Authentication is required to specify whether persistent storage for systemd-" +"networkd is available." +msgstr "" + +#: 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 "" + +#: src/portable/org.freedesktop.portable1.policy:14 +msgid "Authentication is required to inspect a portable service image." +msgstr "" + +#: src/portable/org.freedesktop.portable1.policy:23 +msgid "Attach or detach a portable service image" +msgstr "" + +#: src/portable/org.freedesktop.portable1.policy:24 +msgid "" +"Authentication is required to attach or detach a portable service image." +msgstr "" + +#: src/portable/org.freedesktop.portable1.policy:34 +msgid "Delete or modify portable service image" +msgstr "" + +#: src/portable/org.freedesktop.portable1.policy:35 +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 "" + +#: src/resolve/org.freedesktop.resolve1.policy:23 +msgid "Authentication is required to register a DNS-SD service." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:33 +msgid "Unregister a DNS-SD service" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:34 +msgid "Authentication is required to unregister a DNS-SD service." +msgstr "" + +#: 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 "" + +#: src/resolve/org.freedesktop.resolve1.policy:155 +msgid "Authentication is required to subscribe to DNS configuration." +msgstr "" + +#: 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/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 +msgid "Check for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 +msgid "Authentication is required to check for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 +msgid "Install system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 +msgid "Authentication is required to install system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 +msgid "Install specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 +msgid "" +"Authentication is required to update the system to a specific (possibly old) " +"version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 +msgid "Manage optional features" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 +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 "" + +#: src/timedate/org.freedesktop.timedate1.policy:44 +msgid "" +"Authentication is required to control whether the RTC stores the local or " +"UTC time." +msgstr "" + +#: 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 "" + +#: src/core/dbus-unit.c:373 +msgid "Authentication is required to stop '$(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:374 +msgid "Authentication is required to reload '$(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:375 src/core/dbus-unit.c:376 +msgid "Authentication is required to restart '$(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:568 +msgid "" +"Authentication is required to send a UNIX signal to the processes of '$" +"(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:621 +msgid "" +"Authentication is required to send a UNIX signal to the processes of " +"subgroup of '$(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:649 +msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:679 +msgid "Authentication is required to set properties on '$(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:776 +msgid "" +"Authentication is required to delete files and directories associated with '$" +"(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:813 +msgid "" +"Authentication is required to freeze or thaw the processes of '$(unit)' unit." +msgstr "" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" diff --git a/po/nl.po b/po/nl.po index 17b80b2e51e8b..49d0560b3012d 100644 --- a/po/nl.po +++ b/po/nl.po @@ -1,16 +1,17 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # Dutch translation of systemd. -# Pjotr Vertaalt , 2021. +# Pjotr Vertaalt , 2021, 2026. # Richard E. van der Luit , 2022. -# Maarten , 2023. -# Tim Vangehugten , 2025. +# Maarten , 2023, 2026. +# Tim Vangehugten , 2025, 2026. +# "Richard E. van der Luit" , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-05-28 09:31+0000\n" -"Last-Translator: Tim Vangehugten \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:57+0000\n" +"Last-Translator: \"Richard E. van der Luit\" \n" "Language-Team: Dutch \n" "Language: nl\n" @@ -18,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.11.3\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -173,7 +174,7 @@ msgstr "" "Authenticatie is vereist voor het beheren van de systeemdiensten of andere " "eenheden." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -183,33 +184,33 @@ msgstr "" "Gelieve het benodigde opslagapparaat aan te sluiten of het bestandssysteem " "aan te koppelen dat de thuismap bevat." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Gebruiker %s heeft te veel aanmeldpogingen gedaan. Probeer het later nog " "eens." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Wachtwoord: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Wachtwoord is onjuist of niet voldoende voor de authenticatie van gebruiker " "%s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Sorry, probeer het nog eens: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Herstelcode: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -218,20 +219,20 @@ msgstr "" "Het wachtwoord of de herstelcode is onjuist of niet voldoende voor de " "authenticatie van gebruiker %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Sorry, voer de herstelcode nogmaals in: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "Het beveiligingsmiddel van gebruiker %s is niet ingestoken." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Probeer het nog eens met wachtwoord: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -240,31 +241,31 @@ msgstr "" "Het wachtwoord is onjuist of niet voldoende, en het geconfigureerde " "beveiligingsmiddel van gebruiker %s is niet ingestoken." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "PIN van het beveiligingsmiddel: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" "Gelieve in persoon het beveiligingsmiddel van gebruiker %s te authenticeren." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" "Gelieve te bevestigen dat het beveiligingsmiddel van gebruiker %s aanwezig " "is." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" "Gelieve te verifiëren dat het beveiligingsmiddel van gebruiker %s aan die " "gebruiker toebehoort." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -273,95 +274,95 @@ msgstr "" "eerst op te heffen. (Tip: verwijderen en weer insteken zou voldoende kunnen " "zijn.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "De PIN van het beveiligingsmiddel is onjuist voor gebruiker %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Sorry, probeer de PIN van het beveiligingsmiddel nogmaals: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "De PIN van het beveiligingsmiddel is onjuist voor gebruiker %s. U kunt het " "nog maar enkele malen proberen!" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "De PIN van het beveiligingsmiddel is onjuist voor gebruiker %s. U kunt het " "nog maar eenmaal proberen!" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "De thuismap van gebruiker %s is thans niet aangekoppeld. Gelieve u eerst " "plaatselijk aan te melden." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "De thuismap van gebruiker %s is thans vergrendeld. Gelieve de vergrendeling " "eerst op te heffen op het plaatselijke systeem." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" "Er zijn te veel aanmeldpogingen voor gebruiker %s niet succesvol geweest. " "Verdere aanmelding voor die gebruiker wordt geweigerd." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "" "De registratie van de gebruiker wordt geblokkeerd, waardoor zijn toegang tot " "het systeem wordt belet." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" "De registratie van de gebruiker is nog niet geldig, waardoor zijn toegang " "tot het systeem wordt belet." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" "De registratie van de gebruiker is niet meer geldig, waardoor zijn toegang " "tot het systeem wordt belet." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" "De registratie van de gebruiker is niet geldig, waardoor zijn toegang tot " "het systeem wordt belet." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Er zijn te veel aanmeldingen. Probeer het nog eens over %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Wijziging van uw wachtwoord is vereist." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Het wachtwoord is verlopen. Wijziging van het wachtwoord is vereist." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" "Het wachtwoord is verlopen maar kan niet worden gewijzigd. Daarom wordt de " "aanmelding geweigerd." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "" "Het wachtwoord zal spoedig verlopen. Gelieve het wachtwoord te wijzigen." @@ -1003,11 +1004,20 @@ msgstr "" "en containers." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 #, fuzzy msgid "Create a local virtual machine or container" msgstr "Beheer de plaatselijke virtuele machines en de containers" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 #, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." @@ -1015,12 +1025,12 @@ msgstr "" "Authenticatie is vereist voor het beheren van plaatselijke virtuele machines " "en containers." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "Beheer de plaatselijke virtuele machines en de containers" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." @@ -1028,13 +1038,13 @@ msgstr "" "Authenticatie is vereist voor het beheren van plaatselijke virtuele machines " "en containers." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "" "Beheren van de plaatselijke schijfbestanden van virtuele machines en die van " "containers" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -1042,6 +1052,16 @@ msgstr "" "Authenticatie is vereist voor het beheren van de plaatselijke " "schijfbestanden van virtuele machines en die van containers." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Stel het gebruik van NTP-servers in" @@ -1157,7 +1177,10 @@ msgstr "" "Draag de DHCP-server op een FORCERENEW-bericht naar zijn cliënten te zenden" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +#, fuzzy +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" "Authenticatie is vereist voor het verzenden van een FORCERENEW-bericht." @@ -1329,29 +1352,61 @@ msgid "Authentication is required to reset statistics." msgstr "" "Authenticatie is vereist voor het terugzetten van de instellingen voor NTP." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Controleer op systeemupdates" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 #, fuzzy msgid "Authentication is required to check for system updates." msgstr "Authenticatie is vereist voor het instellen van de systeemtijd." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Installeer systeemupdates" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 #, fuzzy msgid "Authentication is required to install system updates." msgstr "Authenticatie is vereist voor het instellen van de systeemtijd." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Installeer specifieke systeem versie" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 #, fuzzy msgid "" "Authentication is required to update the system to a specific (possibly old) " @@ -1360,20 +1415,37 @@ msgstr "" "Authenticatie is vereist voor het instellen van de tijdzone die het systeem " "gebruikt." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" -msgstr "Oude systeemupdates opkuisen" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy -msgid "Authentication is required to cleanup old system updates." -msgstr "Authenticatie is vereist voor het instellen van de systeemtijd." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Beheer optionele features" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 #, fuzzy msgid "Authentication is required to manage optional features." msgstr "" @@ -1481,3 +1553,28 @@ msgid "" msgstr "" "Authenticatie is vereist voor het bevriezen of ontdooien van de processen " "die behoren tot '$(unit)'." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Oude systeemupdates opkuisen" + +#, fuzzy +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "Authenticatie is vereist voor het instellen van de systeemtijd." diff --git a/po/pa.po b/po/pa.po index 44411715deef4..55e2f42147b53 100644 --- a/po/pa.po +++ b/po/pa.po @@ -1,21 +1,21 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # A S Alam , 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:05+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 2026.6.1\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,175 +128,172 @@ 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 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "ਵਰਤੋਂਕਾਰ %s ਲਈ ਬਹੁਤ ਛੇਤੀ ਛੇਤੀ ਲਾਗਇਨ ਕੋਸ਼ਿਸ਼ਾਂ ਕੀਤੀਆਂ, ਬਾਅਦ ਵਿੱਚ ਕੋਸ਼ਿਸ਼ ਕਰਿਓ।" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "ਪਾਸਵਰਡ: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "ਵਰਤੋਂਕਾਰ %s ਦੀ ਪਰਮਾਣਿਕਤਾ ਲਈ ਪਾਸਵਰਡ ਗਲਤ ਹੈ ਜਾਂ ਕਾਫ਼ੀ ਨਹੀਂ ਹੈ।" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "ਅਫ਼ਸੋਸ, ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰਿਓ: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "ਰਿਕਵਰੀ ਕੁੰਜੀ: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "ਵਰਤੋਂਕਾਰ %s ਦੀ ਪਰਮਾਣਿਕਤਾ ਲਈ ਪਾਸਵਰਡ/ਰਿਕਵਰੀ ਕੁੰਜੀ ਗਲਤ ਹੈ ਜਾਂ ਕਾਫ਼ੀ ਨਹੀਂ ਹੈ।" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "ਅਫ਼ਸੋਸ, ਰਿਕਵਰੀ ਕੁੰਜੀ ਫੇਰ ਭਰਿਓ: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "ਪਾਸਵਰਡ ਨਾਲ ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰਿਓ: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "ਸੁਰੱਖਿਆ ਟੋਕਨ ਪਿੰਨ: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "%s ਵਰਤੋਂਕਾਰ ਦਾ ਹੋਮ ਇਸ ਵੇਲੇ ਲਾਕ ਹੈ, ਪਹਿਲਾਂ ਇਸ ਨੂੰ ਲੋਕਲ ਰੂਪ ਵਿੱਚ ਅਣ-ਲਾਕ ਕਰੋ।" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "ਵਰਤੋਂਕਾਰ ਰਿਕਾਰਡ ਉੱਤੇ ਪਾਬੰਦੀ ਲਾਈ ਹੈ, ਪਹੁੰਚ ਤੋਂ ਰੋਕਿਆ ਜਾ ਰਿਹਾ ਹੈ।" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "ਬਹੁਤ ਸਾਰੀਆਂ ਕੋਸ਼ਿਸ ਕੀਤੀਆਂ, %s ਵਿੱਚ ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "ਪਾਸਵਰਡ ਬਦਲਣ ਦੀ ਲੋੜ ਹੈ।" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "ਪਾਸਵਰਡ ਦੀ ਮਿਆਦ ਪੁੱਗੀ ਹੈ, ਇਹ ਬਦਲਣ ਦੀ ਲੋੜ ਹੈ।" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" "ਪਾਸਵਰਡ ਦੀ ਮਿਆਦ ਪੁੱਗੀ, ਪਰ ਬਦਲਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ, ਲਾਗਇਨ ਤੋਂ ਇਨਕਾਰ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ।" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "ਪਾਸਵਰਡ ਦੀ ਮਿਆਦ ਛੇਤੀ ਹੀ ਪੁੱਗ ਜਾਵੇਗੀ, ਇਸ ਨੂੰ ਬਦਲ ਲਵੋ।" @@ -355,40 +349,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 +528,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 +556,17 @@ 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 "" +msgstr "ਜਦੋਂ ਹੋਰ ਵਰਤੋਂਕਾਰ ਲਾਗਇਨ ਕੀਤੇ ਹੋਣ ਤਾਂ ਸਿਸਟਮ ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/login/org.freedesktop.login1.policy:224 msgid "Reboot the system while an application is inhibiting this" @@ -594,7 +584,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" @@ -802,36 +792,55 @@ msgid "" msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 -msgid "Create a local virtual machine or container" +msgid "Inspect local virtual machines and containers" msgstr "" #: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:106 #, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." msgstr "ਲੋਕਲ ਮਸ਼ੀਨ ਜਾਣਕਾਰੀ ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "ਲੋਕਲ ਮਸ਼ੀਨ ਜਾਣਕਾਰੀ ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." msgstr "ਲੋਕਲ ਮਸ਼ੀਨ ਜਾਣਕਾਰੀ ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਿਕਤਾ ਚਾਹੀਦੀ ਹੈ।" -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "" @@ -941,8 +950,11 @@ 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 a force renew message from the DHCP " +"server." +msgstr "ਦਿੱਤਾ ਵਾਕ ਸਿਸਟਮ ਨੂੰ ਵਾਪਸ ਭੇਜਣ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" #: src/network/org.freedesktop.network1.policy:154 msgid "Renew dynamic addresses" @@ -1092,49 +1104,98 @@ msgstr "" msgid "Authentication is required to reset statistics." msgstr "'$(unit)' ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ (restart) ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 #, fuzzy msgid "Authentication is required to check for system updates." msgstr "ਸਿਸਟਮ ਟਾਈਮ ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 #, fuzzy msgid "Authentication is required to install system updates." msgstr "ਸਿਸਟਮ ਟਾਈਮ ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 #, 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" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy -msgid "Authentication is required to cleanup old system updates." -msgstr "ਸਿਸਟਮ ਟਾਈਮ ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 #, fuzzy msgid "Authentication is required to manage optional features." msgstr "ਸਿਸਟਮ ਸੇਵਾਵਾਂ ਜਾਂ ਯੂਨਿਟ ਫ਼ਾਇਲਾਂ ਦਾ ਇੰਤਜ਼ਾਮ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" @@ -1222,3 +1283,25 @@ msgstr "" msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#, fuzzy +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "ਸਿਸਟਮ ਟਾਈਮ ਸੈੱਟ ਕਰਨ ਲਈ ਪਰਮਾਣਕਿਤਾ ਚਾਹੀਦੀ ਹੈ।" diff --git a/po/pl.po b/po/pl.po index b8775fd31f736..27d572af5caa1 100644 --- a/po/pl.po +++ b/po/pl.po @@ -2,13 +2,15 @@ # # Polish translation for systemd. # -# Piotr Drąg , 2023, 2024, 2025. +# Piotr Drąg , 2023, 2024, 2025, 2026. +# 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-21 11:10+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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -171,7 +173,7 @@ msgstr "" "Wymagane jest uwierzytelnienie, aby zarządzać kluczami podpisującymi " "katalogów domowych." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -181,31 +183,31 @@ msgstr "" "wymagane urządzenie do przechowywania danych lub system plików, na którym " "się znajduje." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "Za częste próby logowania użytkownika %s, proszę spróbować później." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Hasło: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Hasło jest niepoprawne lub niewystarczające do uwierzytelnienia użytkownika " "%s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Proszę spróbować ponownie: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Klucz odzyskiwania: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -214,20 +216,20 @@ msgstr "" "Hasło/klucz odzyskiwania jest niepoprawny lub niewystarczający do " "uwierzytelnienia użytkownika %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Proszę ponownie wpisać klucz odzyskiwania: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "Nie włożono tokena zabezpieczeń użytkownika %s." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Proszę spróbować ponownie za pomocą hasła: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -236,27 +238,27 @@ msgstr "" "Hasło jest niepoprawne lub niewystarczające, a skonfigurowany token " "zabezpieczeń użytkownika %s nie jest włożony." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "Kod PIN tokena zabezpieczeń: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "Proszę fizycznie uwierzytelnić na tokenie zabezpieczeń użytkownika %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "Proszę potwierdzić obecność na tokenie zabezpieczeń użytkownika %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" "Proszę zweryfikować użytkownika na tokenie zabezpieczeń użytkownika %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -264,83 +266,83 @@ msgstr "" "Kod PIN tokena zabezpieczeń jest zablokowany, proszę najpierw go odblokować " "(wskazówka: wyjęcie i włożenie ponownie może wystarczyć)." -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "Niepoprawny kod PIN tokena zabezpieczeń dla użytkownika %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Proszę ponownie wpisać kod PIN tokena zabezpieczeń: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "Niepoprawny kod PIN tokena zabezpieczeń użytkownika %s (pozostało tylko " "kilka prób)." -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "Niepoprawny kod PIN tokena zabezpieczeń użytkownika %s (pozostała tylko " "jedna próba)." -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "Katalog domowy użytkownika %s jest teraz nieaktywny, proszę najpierw " "zalogować się lokalnie." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "Katalog domowy użytkownika %s jest teraz zablokowany, proszę najpierw " "odblokować go lokalnie." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "Za dużo nieudanych prób logowania użytkownika %s, odmawianie." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "Wpis użytkownika jest zablokowany, zabranianie dostępu." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "Wpis użytkownika nie jest jeszcze ważny, zabranianie dostępu." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "Wpis użytkownika nie jest już ważny, zabranianie dostępu." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Wpis użytkownika jest nieważny, zabranianie dostępu." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Za dużo logowań, proszę spróbować ponownie za %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Wymagana jest zmiana hasła." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Hasło wygasło, wymagana jest zmiana." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "Hasło wygasło, ale nie można go zmienić, odmawianie logowania." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Hasło niedługo wygaśnie, proszę je zmienić." @@ -906,32 +908,43 @@ msgstr "" "wirtualnymi i kontenerami." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Badanie lokalnych maszyn wirtualnych i kontenerów" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"Wymagane jest uwierzytelnienie, aby zbadać lokalne maszyny wirtualne " +"i kontenery." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Utworzenie lokalnej maszyny wirtualnej lub kontenera" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Wymagane jest uwierzytelnienie, aby utworzyć lokalną maszynę wirtualną lub " "kontener." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Rejestracja lokalnej maszyny wirtualnej lub kontenera" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Wymagane jest uwierzytelnienie, aby zarejestrować lokalną maszynę wirtualną " "lub kontener." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Zarządzanie lokalnymi obrazami maszyn wirtualnych i kontenerów" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -939,6 +952,18 @@ msgstr "" "Wymagane jest uwierzytelnienie, aby zarządzać lokalnymi obrazami maszyn " "wirtualnych i kontenerów." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Badanie obrazów lokalnych maszyn wirtualnych i kontenerów" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"Wymagane jest uwierzytelnienie, aby zbadać obrazy lokalnych maszyn " +"wirtualnych i kontenerów." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Ustawienie serwerów NTP" @@ -1052,9 +1077,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 +1126,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" @@ -1207,27 +1235,63 @@ msgstr "Przywrócenie statystyk" msgid "Authentication is required to reset statistics." msgstr "Wymagane jest uwierzytelnienie, aby przywrócić statystyki." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "Opróżnienie pamięci podręcznej DNS" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "Wymagane jest uwierzytelnienie, aby opróżnić pamięć podręczną DNS." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Przywrócenie funkcji serwera" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "Wymagane jest uwierzytelnienie, aby przywrócić funkcje serwera." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Wyszukanie aktualizacji systemu" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "Wymagane jest uwierzytelnienie, aby wyszukać aktualizacje systemu." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Anulowanie sprawdzania aktualizacji systemu" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" +"Wymagane jest uwierzytelnienie, aby anulować sprawdzanie aktualizacji " +"systemu." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Instalacja aktualizacji systemu" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "Wymagane jest uwierzytelnienie, aby zainstalować aktualizacje systemu." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Anulowanie instalowania aktualizacji systemu" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" +"Wymagane jest uwierzytelnienie, aby anulować instalowanie aktualizacji " +"systemu." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Instalacja konkretnej wersji systemu" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1235,21 +1299,42 @@ msgstr "" "Wymagane jest uwierzytelnienie, aby zaktualizować system do konkretnej " "(możliwie, że poprzedniej) wersji." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" -msgstr "Wyczyszczenie poprzednich aktualizacji systemu" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Anulowanie instalowania określonej wersji systemu" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." msgstr "" -"Wymagane jest uwierzytelnienie, aby wyczyścić poprzednie aktualizacje " -"systemu." +"Wymagane jest uwierzytelnienie, aby anulować aktualizację systemu do " +"określonej (być może starej) wersji." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "Czyszczenie starych aktualizacji systemu" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" +"Wymagane jest uwierzytelnienie, aby wyczyścić stare aktualizacje systemu." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Anulowanie czyszczenia starych aktualizacji systemu" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" +"Wymagane jest uwierzytelnienie, aby anulować czyszczenie starych " +"aktualizacji systemu." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Zarządzanie funkcjami opcjonalnymi" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "Wymagane jest uwierzytelnienie, aby zarządzać funkcjami opcjonalnymi." @@ -1353,6 +1438,34 @@ msgstr "" "Wymagane jest uwierzytelnienie, aby zamrozić lub odmrozić procesy jednostki " "„$(unit)”." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "Proszę potwierdzić obecność tokenem zabezpieczeń, aby odblokować." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "Proszę zweryfikować użytkownika tokenem zabezpieczeń, aby odblokować." + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" +"Proszę wpisać kod PIN tokena zabezpieczeń (pozostałe próby przed " +"zablokowaniem: %d):" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Proszę wpisać kod PIN tokena zabezpieczeń:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Wyczyszczenie poprzednich aktualizacji systemu" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "Wymagane jest uwierzytelnienie, aby wyczyścić poprzednie aktualizacje " +#~ "systemu." + #~ msgid "Press Ctrl+C to cancel all filesystem checks in progress" #~ msgstr "" #~ "Naciśnięcie klawiszy Ctrl+C anuluje wszystkie trwające procesy " diff --git a/po/pt.po b/po/pt.po index 1ed4c425cdcfb..b1183a8c2e9b4 100644 --- a/po/pt.po +++ b/po/pt.po @@ -1,14 +1,14 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # Portuguese translation of systemd. -# Hugo Carvalho , 2021, 2022, 2023. -# Tiago Rocha Cunha , 2024. +# Hugo Carvalho , 2021, 2022, 2023, 2026. +# Tiago Rocha Cunha , 2024, 2026. # Américo Monteiro , 2025, 2026. 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-21 11:10+0000\n" "Last-Translator: Américo Monteiro \n" "Language-Team: Portuguese \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.16.1\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -163,7 +163,7 @@ msgstr "" "É necessária autenticação para gerir assinatura de chaves para directórios " "home." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -172,32 +172,32 @@ msgstr "" "O diretório home do utilizador %s está ausente, ligue o dispositivo de " "armazenamento necessário ou o sistema de ficheiros de apoio." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Tentativas de início de sessão demasiado frequentes para o utilizador %s, " "tente novamente mais tarde." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Palavra-passe: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Palavra-passe incorreta ou insuficiente para a autenticação do utilizador %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Tente novamente: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Chave de recuperação: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -206,20 +206,20 @@ msgstr "" "Palavra-passe/chave de recuperação incorreta ou insuficiente para a " "autenticação do utilizador %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Volte a introduzir a chave de recuperação: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "O código de segurança do utilizador %s não foi inserido." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Tente novamente com a palavra-passe: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -228,27 +228,27 @@ msgstr "" "Palavra-passe incorreta ou insuficiente e token de segurança configurado do " "utilizador %s não inserido." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "PIN do token de segurança: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" "Efetue a autenticação física com o código de segurança do utilizador %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "Confirme a presença no token de segurança do utilizador %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "Verifique o utilizador no código de segurança do utilizador %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -256,87 +256,87 @@ msgstr "" "O PIN do cartão de segurança está bloqueado, desbloqueie-o primeiro. " "(Sugestão: pode ser suficiente remover e voltar a inserir.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "PIN do código de segurança incorreto para o utilizador %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Tente novamente o PIN do token de segurança: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "PIN do token de segurança do utilizador %s incorreto (só faltam algumas " "tentativas!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "PIN do token de segurança do utilizador %s incorreto (só falta uma " "tentativa!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "O diretório home do utilizador %s não está ativo, inicie sessão localmente " "primeiro." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "O diretório home do utilizador %s está atualmente bloqueado, desbloqueie-o " "localmente primeiro." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" "Demasiadas tentativas de início de sessão sem sucesso para o utilizador %s, " "recusando." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "O registo do utilizador está bloqueado, proibindo o acesso." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "O registo do utilizador ainda não é válido, proibindo o acesso." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "O registo do utilizador já não é válido, proibindo o acesso." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "O registo do utilizador não é válido, proibindo o acesso." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Demasiados inícios de sessão, tente novamente em %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "É necessário alterar a palavra-passe." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "A palavra-passe expirou, é necessário alterá-la." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" "A palavra-passe expirou, mas não é possível alterá-la, recusando o início de " "sessão." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "A palavra-passe irá expirar em breve, por favor altere-a." @@ -906,31 +906,42 @@ msgstr "" "É necessária autenticação para gerir máquinas virtuais locais e contentores." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Inspecionar máquinas e contentores virtuais locais" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"É necessária autenticação para inspecionar máquinas e contentores virtuais " +"locais." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Criar máquina virtual local ou contentor" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "É necessária autenticação para criar uma máquina virtual local ou contentor." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Registrar uma máquina virtual local ou contentor" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "É necessária autenticação para registrar uma máquina virtual local ou " "contentor." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Gerir máquinas virtuais locais e imagens contentores" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -938,6 +949,18 @@ msgstr "" "É necessária autenticação para gerir máquinas virtuais locais e imagens " "contentores." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Inspecionar imagens de máquinas e contentores virtuais locais" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"É necessária autenticação para inspecionar imagens de máquinas e contentores " +"virtuais locais." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Definir servidores NTP" @@ -1047,8 +1070,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" @@ -1200,27 +1227,64 @@ msgstr "Reiniciar estatísticas" msgid "Authentication is required to reset statistics." msgstr "É necessária autenticação para reiniciar estatísticas." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "Despejar caches de DNS" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "É requerida autenticação para limpar as caches de DNS." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Reiniciar as funcionalidades do servidor" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" +"É requerida autenticação para reiniciar as funcionalidades do servidor." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Verificar atualizações do sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "É necessária autenticação para verificar atualizações do sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Cancelar a verificação por atualizações do sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" +"É requerida autenticação para cancelar a verificação por atualizações do " +"sistema." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Instalar atualizações do sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "É necessária autenticação para instalar atualizações do sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Cancelar a instalação de atualizações do sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" +"É requerida autenticação para cancelar a instalação de atualizações do " +"sistema." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Instalar versão de sistema específica" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1228,19 +1292,41 @@ msgstr "" "É necessária autenticação para instalar versão de sistema específica " "(possivelmente antiga)." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Cancelar o instalar de versão de sistema específica" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" +"É requerida autenticação para cancelar a atualização do sistema para uma " +"versão específica (possivelmente antiga)." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" msgstr "Limpar atualizações de sistema antigas" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." -msgstr "É necessária autenticação para limpar atualizações de sistema antigas." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "É requerida autenticação para limpar atualizações de sistema antigas." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Cancelar o limpar de atualizações de sistema antigas" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" +"É requerida autenticação para cancelar o limpar de atualizações de sistema " +"antigas." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Gerir funcionalidades opcionais" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "É necessária autenticação para gerir funcionalidades opcionais." @@ -1339,3 +1425,32 @@ msgid "" msgstr "" "É necessária autenticação para congelar ou descongelar os processos da " "unidade '$(unit)'." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" +"Por favor confirme a presença em testemunho de segurança para desbloquear." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" +"Por favor verifique utilizador em testemunho de segurança para desbloquear." + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" +"Por favor insira PIN de testemunho de segurança (tentativas restantes antes " +"do bloqueio: %d):" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Por favor insira PIN de testemunho de segurança:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Limpar atualizações de sistema antigas" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "É necessária autenticação para limpar atualizações de sistema antigas." diff --git a/po/pt_BR.po b/po/pt_BR.po index fdb791df1d9cd..be32899dc0470 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -3,18 +3,19 @@ # Brazilian Portuguese translation for systemd. # Enrico Nicoletto , 2014. # Filipe Brandenburger , 2018. -# Rafael Fontenelle , 2015-2020, 2025. -# Gustavo Costa , 2021. -# Tiago Rocha Cunha , 2024. -# Gabriel Elyas , 2024. -# Fábio Rodrigues Ribeiro , 2024. -# "Geraldo S. Simião Kutz" , 2024. +# Rafael Fontenelle , 2015-2020, 2025, 2026. +# Gustavo Costa , 2021, 2026. +# Tiago Rocha Cunha , 2024, 2026. +# Gabriel Elyas , 2024, 2026. +# Fábio Rodrigues Ribeiro , 2024, 2026. +# "Geraldo S. Simião Kutz" , 2024, 2026. +# 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" -"Last-Translator: Rafael Fontenelle \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:58+0000\n" +"Last-Translator: Fábio Rodrigues Ribeiro \n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -169,7 +170,7 @@ msgstr "" "É necessária autenticação para gerenciar chaves de assinatura de diretório " "pessoal." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -178,30 +179,30 @@ msgstr "" "A home do usuário %s está ausente no momento. Por favor, conecte o " "dispositivo de armazenamento necessário ou sistema de arquivo de recuperação." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Muitas tentativas de login para o usuário %s, tente novamente mais tarde." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Senha: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "Senha incorreta ou insuficiente para autenticação do usuário %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Desculpe, tente novamente: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Chave de recuperação: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -210,20 +211,20 @@ msgstr "" "Senha/chave de recuperação incorreta ou insuficiente para autenticação do " "usuário %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Desculpe, reinsira a chave de recuperação: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "O token de segurança do usuário %s não foi inserido." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Tente a senha novamente: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -232,26 +233,26 @@ msgstr "" "A senha está incorreta ou não é suficiente, e o token de segurança " "configurado do usuário %s não foi inserido." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "PIN do token de segurança: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "Faça a autenticação física no token de segurança do usuário %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "Confirme a presença no token de segurança do usuário %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "Verifique o usuário no token de segurança do usuário %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -259,83 +260,83 @@ msgstr "" "O PIN do token de segurança está bloqueado, desbloqueie-o primeiro. (Dica: a " "remoção e a reinserção podem ser suficientes)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "PIN do token de segurança incorreto para o usuário %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Desculpe, tente novamente o PIN do token de segurança: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "O PIN do token de segurança do usuário %s está incorreto (restam apenas " "algumas tentativas!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "O PIN do token de segurança do usuário %s está incorreto (só resta uma " "tentativa!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "A home do usuário %s não está ativa no momento. Primeiro, faça login " "localmente." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "A home do usuário %s está bloqueada no momento. Desbloqueie localmente " "primeiro." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "Muitas tentativas de login mal sucedidas para o usuário %s, recusando." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "O registro do usuário está bloqueado, proibindo o acesso." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "O registro do usuário ainda não é válido, proibindo o acesso." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "O registro do usuário não é mais válido, proibindo o acesso." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Registro de usuário inválido, proibindo o acesso." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Muitos logins, tente novamente em %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "É necessário alterar a senha." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Senha expirou, é necessário alterá-la." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "A senha expirou, mas não é possível alterá-la, recusando o login." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "A senha expirará em breve, altere-a." @@ -910,31 +911,42 @@ msgstr "" "contêineres." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Inspecionar contêineres e máquinas virtuais locais" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"É necessária autenticação para inspecionar contêineres e máquinas virtuais " +"locais." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Crie uma máquina virtual local ou um contêiner" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "É necessária autenticação para criar máquinas virtuais locais e contêineres." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Registrar uma máquina virtual local ou um contêiner" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "É necessária autenticação para registrar máquinas virtuais locais e " "contêineres." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Gerenciar máquinas virtuais locais e imagens contêineres" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -942,6 +954,18 @@ msgstr "" "É necessária autenticação para gerenciar máquinas virtuais locais e imagens " "contêineres." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Inspecionar imagens de contêiner e máquina virtual local" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"É necessária autenticação para inspecionar imagens de contêiner e máquina " +"virtual local." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Definir servidores NTP" @@ -1051,8 +1075,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 +1122,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" @@ -1205,27 +1233,63 @@ msgstr "Redefinir estatísticas" msgid "Authentication is required to reset statistics." msgstr "A autenticação é necessária para redefinir as estatísticas." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "Limpar caches DNS" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "É necessária autenticação para limpar caches DNS." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Redefinir recursos do servidor" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "É necessária autenticação para redefinir recursos do servidor." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Verifique se há atualizações do sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "É necessária autenticação para verificar por atualizações do sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Cancelar verificação por atualizações do sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" +"É necessária autenticação para o cancelar verificação por atualizações do " +"sistema." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Instalar atualizações do sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "É necessária autenticação para instalar atualizações do sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Cancelar instalação de atualizações do sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" +"É necessária autenticação para cancelar instalação de atualizações do " +"sistema." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Instalar versão específica do sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1233,20 +1297,41 @@ msgstr "" "A autenticação é necessária para atualizar o sistema para uma versão " "específica (possivelmente antiga)." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" -msgstr "Limpar velhas atualizações do sistema" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Cancelar instalação de uma versão específica do sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" +"É necessária autenticação para cancelar instalação de uma versão específica " +"(possivelmente antiga) do sistema." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "Limpar atualizações antigas do sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "É necessária autenticação para limpar atualizações antigas do sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Cancelar limpeza de atualizações antigas do sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." msgstr "" -"A autenticação é necessária para limpar atualizações antigas do sistema." +"É necessária autenticação para cancelar limpeza de atualizações antigas do " +"sistema." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Gerenciar recursos opcionais" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "A autenticação é necessária para gerenciar recursos opcionais." @@ -1345,3 +1430,29 @@ msgid "" msgstr "" "É necessária autenticação para congelar ou descongelar os processos da " "unidade “$(unit)”." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "Confirme a presença do token de segurança para desbloquear." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "Verifique o usuário no token de segurança para desbloquear." + +#: src/shared/libfido2-util.c:936 +#, fuzzy, c-format +#| msgid "Please enter security token PIN:" +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "Insira o PIN do token de segurança:" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Insira o PIN do token de segurança:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Limpar velhas atualizações do sistema" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "A autenticação é necessária para limpar atualizações antigas do sistema." diff --git a/po/ro.po b/po/ro.po index 552dca7e3f239..0d1206bc61e07 100644 --- a/po/ro.po +++ b/po/ro.po @@ -3,22 +3,26 @@ # Romanian translation for systemd. # va511e , 2015. # Daniel Șerbănescu , 2015, 2017. -# Vlad , 2020, 2021. +# Vlad , 2020, 2021, 2026. +# Petru Rebeja , 2026. +# Fedora Weblate user 1831 , 2026. +# Weblate Translation Memory , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2021-01-12 17:36+0000\n" -"Last-Translator: Vlad \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-25 10:17+0000\n" +"Last-Translator: Weblate Translation Memory \n" "Language-Team: Romanian \n" +"systemd/main/ro/>\n" "Language: ro\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==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < " "20)) ? 1 : 2;\n" -"X-Generator: Weblate 4.4\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -75,13 +79,14 @@ msgstr "Autentificarea este necesară pentru a reîncărca starea lui systemd." #: src/core/org.freedesktop.systemd1.policy.in:74 msgid "Dump the systemd state without rate limits" -msgstr "" +msgstr "Descărcați starea systemd fără limite de acces" #: src/core/org.freedesktop.systemd1.policy.in:75 -#, fuzzy msgid "" "Authentication is required to dump the systemd state without rate limits." -msgstr "Autentificarea este necesară pentru a reîncărca starea lui systemd." +msgstr "" +"Autentificarea este necesară pentru a descărca starea systemd fără limite de " +"acces." #: src/home/org.freedesktop.home1.policy:13 msgid "Create a home area" @@ -125,16 +130,12 @@ msgstr "" "utilizator." #: src/home/org.freedesktop.home1.policy:53 -#, fuzzy msgid "Update your home area" -msgstr "Actualizează un spațiu personal" +msgstr "Actualizați-vă spațiu personal" #: src/home/org.freedesktop.home1.policy:54 -#, fuzzy msgid "Authentication is required to update your home area." -msgstr "" -"Autentificarea este necesară pentru a actualiza spațiul personal al unui " -"utilizator." +msgstr "Pentru a-ți actualiza spațiul personal, este necesară autentificarea." #: src/home/org.freedesktop.home1.policy:63 msgid "Resize a home area" @@ -158,20 +159,18 @@ msgstr "" "al unui utilizator." #: src/home/org.freedesktop.home1.policy:83 -#, fuzzy msgid "Activate a home area" -msgstr "Crează un spațiu personal" +msgstr "Activează un spațiu personal" #: src/home/org.freedesktop.home1.policy:84 -#, fuzzy msgid "Authentication is required to activate a user's home area." msgstr "" -"Autentificarea este necesară pentru a crea spațiul personal al unui " -"utilizator." +"Pentru a activa spațiul personal al unui utilizator este necesară " +"autentificarea." #: src/home/org.freedesktop.home1.policy:93 msgid "Manage Home Directory Signing Keys" -msgstr "" +msgstr "Gestionați cheile de semnare pentru directorul personal" #: src/home/org.freedesktop.home1.policy:94 #, fuzzy @@ -180,158 +179,187 @@ msgstr "" "Autentificarea este necesară pentru a gestiona serviciile de sistem sau alte " "module." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " "device or backing file system." msgstr "" +"Directorul personal al utilizatorului %s este absent momentan, vă rugăm să " +"conectați dispozitivul de stocare necesar sau sistemul de fișiere care îl " +"conține." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" +"S-au înregistrat prea multe încercări de autentificare pentru utilizatorul " +"%s. Vă rugăm să încercați din nou mai târziu." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " -msgstr "" +msgstr "Parolă: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" +"Parola este incorectă sau nu este suficientă pentru autentificarea " +"utilizatorului %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " -msgstr "" +msgstr "Scuze, încearcă din nou: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " -msgstr "" +msgstr "Cheia de recuperare: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" +"Parola/cheia de recuperare sunt incorecte sau insuficiente pentru " +"autentificarea utilizatorului %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " -msgstr "" +msgstr "Scuze, reintroduceți cheia de recuperare: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." -msgstr "" +msgstr "Tokenul de securitate al utilizatorului %s nu a fost introdus." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " -msgstr "" +msgstr "Încearcă din nou cu parola: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " "%s not inserted." msgstr "" +"Parola este incorectă sau insuficientă, iar tokenul de securitate configurat " +"al utilizatorului %s nu a fost introdus." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " -msgstr "" +msgstr "PIN-ul tokenului de securitate: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" +"Vă rugăm să vă autentificați fizic folosind tokenul de securitate al " +"utilizatorului %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" +"Vă rugăm să vă confirmați prezența pe tokenul de securitate al " +"utilizatorului %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" +"Vă rugăm să verificați utilizatorul pe cheia de securitate al utilizatorului " +"%s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" msgstr "" +"PIN-ul cheii de securitate este blocat, vă rugăm să îl deblocați mai întâi. " +"(Indiciu: Ar putea fi suficientă doar extragerea și reintroducerea cheii.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." -msgstr "" +msgstr "PIN-ul tokenului de securitate este incorect pentru utilizatorul %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " -msgstr "" +msgstr "Ne pare rău, introduceți din nou PIN-ul tokenului de securitate: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" +"PIN-ul cheii de securitate a utilizatorului %s nu este corect (au mai rămas " +"doar câteva încercări!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" +"PIN-ul cheii de securitate al utilizatorului %s nu este corect (a mai rămas " +"o singură încercare!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" +"Spațiul personal al utilizatorului %s nu este activ momentan, vă rugăm să " +"conectați local mai întâi." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" +"Spațiul personal al utilizatorului %s este blocat momentan, vă rugăm să îl " +"deblocați mai întâi local." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" +"Prea multe încercări de autentificare nereușite pentru utilizatorul %s, " +"refuzat." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." -msgstr "" +msgstr "Utilizatorul este blocat, se refuză accesul." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." -msgstr "" +msgstr "Utilizatorul încă nu este valid, se refuză accesul." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." -msgstr "" +msgstr "Utilizatorul nu mai este valid, se refuză accesul." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." -msgstr "" +msgstr "Utilizatorul nu este valid, se refuză accesul." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." -msgstr "" +msgstr "Prea multe autentificări, vă rugăm să încercați din nou în %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." -msgstr "" +msgstr "Este necesară schimbarea parolei." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." -msgstr "" +msgstr "Parola a expirat, e necesar să o schimbați." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." -msgstr "" +msgstr "Parola a expirat, dar nu poate fi modificată, se refuză conectarea." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." -msgstr "" +msgstr "Parola va expira în curând, vă rugăm să o schimbați." #: src/hostname/org.freedesktop.hostname1.policy:20 msgid "Set hostname" @@ -374,7 +402,7 @@ msgstr "" #: src/hostname/org.freedesktop.hostname1.policy:61 msgid "Get hardware serial number" -msgstr "" +msgstr "Obțineți numărul de serie al componentei hardware" #: src/hostname/org.freedesktop.hostname1.policy:62 #, fuzzy @@ -429,7 +457,7 @@ msgstr "" #: src/import/org.freedesktop.import1.policy:52 msgid "Cancel transfer of a disk image" -msgstr "" +msgstr "Anulați transferul imaginii de disc" #: src/import/org.freedesktop.import1.policy:53 #, fuzzy @@ -624,7 +652,7 @@ msgstr "Oprește sistemul" #: src/login/org.freedesktop.login1.policy:170 msgid "Authentication is required to power off the system." -msgstr "Este necesară autentificarea pentru oprirea sistemului." +msgstr "Trebuie să vă autentificați pentru a opri sistemul." #: src/login/org.freedesktop.login1.policy:180 msgid "Power off the system while other users are logged in" @@ -647,8 +675,8 @@ msgid "" "Authentication is required to power off the system while an application is " "inhibiting this." msgstr "" -"Autentificarea este necesară pentru oprirea sistemului în timp ce o " -"aplicație împiedică oprirea." +"Trebuie să vă autentificați pentru a opri sistemul în timp ce o aplicație " +"împiedică oprirea." #: src/login/org.freedesktop.login1.policy:202 msgid "Reboot the system" @@ -688,7 +716,7 @@ msgstr "Oprește sistemul" #: src/login/org.freedesktop.login1.policy:236 msgid "Authentication is required to halt the system." -msgstr "Este necesară autentificarea pentru oprirea sistemului." +msgstr "Trebuie să vă autentificați pentru a opri sistemul." #: src/login/org.freedesktop.login1.policy:246 msgid "Halt the system while other users are logged in" @@ -711,8 +739,8 @@ msgid "" "Authentication is required to halt the system while an application is " "inhibiting this." msgstr "" -"Autentificarea este necesară pentru oprirea sistemului în timp ce o " -"aplicație împiedică oprirea." +"Trebuie să vă autentificați pentru a opri sistemul în timp ce o aplicație " +"împiedică oprirea." #: src/login/org.freedesktop.login1.policy:268 msgid "Suspend the system" @@ -929,11 +957,22 @@ msgstr "" "containere." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Inspectați mașinile virtuale și containerele locale" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"Autentificarea este necesară pentru a inspecta mașinile virtuale și " +"containerele locale." + +#: src/machine/org.freedesktop.machine1.policy:105 #, fuzzy msgid "Create a local virtual machine or container" msgstr "Gestionează mașini virtuale locale și containere" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 #, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." @@ -941,12 +980,12 @@ msgstr "" "Autentificarea este necesară pentru a gestiona mașini virtuale locale și " "containere." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "Gestionează mașini virtuale locale și containere" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." @@ -954,12 +993,12 @@ msgstr "" "Autentificarea este necesară pentru a gestiona mașini virtuale locale și " "containere." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "" "Gestionează imaginile pentru mașinile virtuale locale și pentru containere" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -967,6 +1006,18 @@ msgstr "" "Autentificarea este necesară pentru a gestiona imagini de mașini virtuale " "locale și de containere." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Inspectați mașinile virtuale și imaginile container locale" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"Autentificarea este necesară pentru a inspecta mașinile virtuale și " +"imaginile container locale." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Setare servere NTP" @@ -1081,7 +1132,10 @@ msgid "DHCP server sends force renew message" msgstr "Serverul DHCP trimite mesaje de reînnoire forțată" #: src/network/org.freedesktop.network1.policy:144 -msgid "Authentication is required to send force renew message." +#, fuzzy +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "" "Autentificarea este necesară pentru a trimite mesaje de reînnoire forțată." @@ -1112,20 +1166,24 @@ msgstr "Autentificarea este necesară pentru a reconfigura interfața de rețea. #: src/network/org.freedesktop.network1.policy:187 msgid "Specify whether persistent storage for systemd-networkd is available" msgstr "" +"Specificați dacă este disponibil un spațiu de stocare persistent pentru " +"systemd-networkd" #: src/network/org.freedesktop.network1.policy:188 msgid "" "Authentication is required to specify whether persistent storage for systemd-" "networkd is available." msgstr "" +"Autentificarea este necesară pentru a specifica dacă este disponibil un " +"spațiu de stocare persistent pentru systemd-networkd." #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" -msgstr "" +msgstr "Gestionați legăturile de rețea" #: src/network/org.freedesktop.network1.policy:199 msgid "Authentication is required to manage network links." -msgstr "" +msgstr "Trebuie să vă autentificați pentru a gestiona legăturile de rețea." #: src/portable/org.freedesktop.portable1.policy:13 msgid "Inspect a portable service image" @@ -1191,7 +1249,7 @@ msgstr "" #: src/resolve/org.freedesktop.resolve1.policy:143 msgid "Subscribe query results" -msgstr "" +msgstr "Abonați-vă la rezultatele interogării" #: src/resolve/org.freedesktop.resolve1.policy:144 #, fuzzy @@ -1200,7 +1258,7 @@ msgstr "Este necesară autentificarea pentru suspendarea sistemului." #: src/resolve/org.freedesktop.resolve1.policy:154 msgid "Subscribe to DNS configuration" -msgstr "" +msgstr "Abonați-vă la configurarea DNS" #: src/resolve/org.freedesktop.resolve1.policy:155 #, fuzzy @@ -1209,7 +1267,7 @@ msgstr "Este necesară autentificarea pentru suspendarea sistemului." #: src/resolve/org.freedesktop.resolve1.policy:165 msgid "Dump cache" -msgstr "" +msgstr "Descărcați cache" #: src/resolve/org.freedesktop.resolve1.policy:166 #, fuzzy @@ -1218,7 +1276,7 @@ msgstr "Autentificarea este necesară pentru a seta domenii." #: src/resolve/org.freedesktop.resolve1.policy:176 msgid "Dump server state" -msgstr "" +msgstr "Descărcați starea serverului" #: src/resolve/org.freedesktop.resolve1.policy:177 #, fuzzy @@ -1227,7 +1285,7 @@ msgstr "Autentificarea este necesară pentru a seta servere NTP." #: src/resolve/org.freedesktop.resolve1.policy:187 msgid "Dump statistics" -msgstr "" +msgstr "Descărcați statisticile" #: src/resolve/org.freedesktop.resolve1.policy:188 #, fuzzy @@ -1236,56 +1294,116 @@ msgstr "Autentificarea este necesară pentru a seta domenii." #: src/resolve/org.freedesktop.resolve1.policy:198 msgid "Reset statistics" -msgstr "" +msgstr "Resetați statisticile" #: src/resolve/org.freedesktop.resolve1.policy:199 #, fuzzy msgid "Authentication is required to reset statistics." msgstr "Autentificarea este necesară pentru a reseta setările NTP." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 -msgid "Check for system updates" +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "Curățați cache-ul DNS" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "Trebuie să vă autentificați ca să curățați cache-ul DNS." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Resetați caracteristicile serverului" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." msgstr "" +"Trebuie să vă autentificați pentru a reseta caracteristicile serverului." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 +msgid "Check for system updates" +msgstr "Verifică dacă sunt disponibile actualizări" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 #, fuzzy msgid "Authentication is required to check for system updates." msgstr "Autentificarea este necesară pentru a seta ora sistemului." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 -msgid "Install system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Anulați verificarea actualizărilor de sistem" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." msgstr "" +"Trebuie să vă autentificați pentru a anula verificarea actualizărilor de " +"sistem." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 +msgid "Install system updates" +msgstr "Instalează actualizările de sistem" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 #, fuzzy msgid "Authentication is required to install system updates." msgstr "Autentificarea este necesară pentru a seta ora sistemului." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 -msgid "Install specific system version" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Anulați instalarea actualizărilor de sistem" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." msgstr "" +"Este necesar să vă autentificați pentru a anula instalarea actualizărilor de " +"sistem." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 +msgid "Install specific system version" +msgstr "Instalează o versiune specifică a sistemului" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 #, fuzzy msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." msgstr "Autentificarea este necesară pentru a seta fusul orar al sistemului." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Anulați instalarea unei versiuni specifice a sistemului" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." msgstr "" +"Trebuie să vă autentificați pentru a anula actualizarea sistemului la o " +"versiune specifică (posibil mai veche)." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy -msgid "Authentication is required to cleanup old system updates." -msgstr "Autentificarea este necesară pentru a seta ora sistemului." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "Ștergeți actualizările de sistem învechite" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 -msgid "Manage optional features" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" +"Trebuie să vă autentificați pentru a șterge actualizările de sistem " +"învechite." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Anulează ștergerea actualizărilor de sistem învechite" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." msgstr "" +"Este necesar să vă autentificați pentru a anula ștergerea actualizărilor de " +"sistem vechi." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 +msgid "Manage optional features" +msgstr "Gestionați funcționalitățile facultative" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 #, fuzzy msgid "Authentication is required to manage optional features." msgstr "" @@ -1390,5 +1508,32 @@ msgstr "" "Autentificarea este necesară pentru a îngheța sau dezgheța procesele " "corespunzătoare lui „$(unit)”." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" +"Vă rugăm să vă confirmați prezența pe tokenul de securitate pentru deblocare." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" +"Vă rugăm să verificați utilizatorul pe tokenul de securitate pentru " +"deblocare." + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" +"Vă rugăm să introduceți codul PIN al tokenului de securitate (au mai rămas " +"%d încercări până la blocare):" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Vă rugăm să introduceți codul PIN al tokenului de securitate:" + +#, fuzzy +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "Autentificarea este necesară pentru a seta ora sistemului." + #~ msgid "Authentication is required to kill '$(unit)'." #~ msgstr "Autentificarea este necesară pentru a omorî „$(unit)”." diff --git a/po/ru.po b/po/ru.po index e13b17dc854f1..5cc1ecca1b238 100644 --- a/po/ru.po +++ b/po/ru.po @@ -5,16 +5,16 @@ # Julia Dronova , 2013. # Sergey Ptashnick <0comffdiz@inbox.ru>, 2013-2018. # Vladimir Yerilov , 2020. -# Alexey Rubtsov , 2021. -# Olga Smirnova , 2022. -# Andrei Stepanov , 2023. +# Alexey Rubtsov , 2021, 2026. +# Olga Smirnova , 2022, 2026. +# Andrei Stepanov , 2023, 2026. # "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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-21 11:10+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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -179,7 +179,7 @@ msgstr "" "Для управления ключами подписи домашних каталогов, необходимо пройти " "аутентификацию." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -188,33 +188,33 @@ msgstr "" "Домашняя папка пользователя %s в настоящее время отсутствует, подключите " "необходимое устройство хранения данных или резервную файловую систему." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Слишком частые попытки войти в систему со стороны пользователя %s, повторите " "попытку позже." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Пароль: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Неверно указан пароль или его недостаточно для аутентификации пользователя " "%s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Извините, повторите попытку: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Ключ восстановления: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -223,20 +223,20 @@ msgstr "" "Пароль/ключ восстановления неверен или его недостаточно для аутентификации " "пользователя %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Извините, ещё раз введите ключ восстановления: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Повторите попытку с паролем: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -245,28 +245,28 @@ msgstr "" "Пароль неверен или его недостаточно, а настроенный токен безопасности " "пользователя %s не вставлен." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "PIN-код токена безопасности: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" "Пожалуйста, пройдите физическую аутентификацию по токену безопасности " "пользователя %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "Пожалуйста, подтвердите наличие токена безопасности пользователя %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "Пожалуйста, подтвердите пользователя на токене безопасности %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -274,87 +274,87 @@ msgstr "" "PIN-код токена безопасности заблокирован. Пожалуйста, сначала снимите его " "блокировку. (Подсказка: иногда достаточно вынуть и снова вставить токен.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "Неверный PIN-код токена безопасности для пользователя %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Извините, повторите попытку введения PIN-кода токена безопасности: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "Неверный PIN-код токена безопасности пользователя %s (осталось всего " "несколько попыток!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "Неверный PIN-код токена безопасности пользователя %s (осталась всего одна " "попытка!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "Домашняя папка пользователя %s в настоящее время заблокирована, сначала " "разблокируйте её локально." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" "Слишком много неудачных попыток входа в систему для пользователя %s. В " "доступе отказано." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "Запись пользователя заблокирована. Доступ запрещён." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "Запись пользователя ещё не вступила в силу. Доступ запрещён." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "Запись пользователя больше не действительна. Доступ запрещён." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Запись пользователя недействительна. Доступ запрещён." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Слишком много попыток войти. Повторите попытку через %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Требуется смена пароля." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Срок действия пароля истёк, требуется его замена." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" "Срок действия пароля истёк, но его невозможно изменить. Вход в систему " "запрещён." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Срок действия пароля скоро истечёт, пожалуйста, измените его." @@ -961,32 +961,42 @@ msgstr "" "аутентификацию." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Проверка локальных виртуальных машин и контейнеров" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"Требуется авторизация для проверки локальных виртуальных машин и контейнеров." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Создание виртуальной машины или контейнера" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Для создания виртуальной машины или контейнера, необходимо пройти " "аутентификацию." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Регистрация локальной виртуальной машины или контейнера" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Для регистрации локальной виртуальной машины или контейнера, необходимо " "пройти аутентификацию." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Управление образами виртуальных машин и контейнеров" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -994,6 +1004,18 @@ msgstr "" "Для управления образами виртуальных машин и контейнеров, необходимо пройти " "аутентификацию." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Проверка образов локальных виртуальных машин и контейнеров" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"Требуется авторизация для проверки образов локальных виртуальных машин и " +"контейнеров." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Задать NTP-серверы" @@ -1109,10 +1131,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 +1152,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 +1180,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" @@ -1274,28 +1294,60 @@ msgstr "Сброс статистики" msgid "Authentication is required to reset statistics." msgstr "Чтобы сбросить статистику, необходимо пройти аутентификацию." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "Очистить кэши DNS" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "Требуется авторизация для очистки кэшей DNS." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Сбросить возможности сервера" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "Требуется авторизация для сброса возможностей сервера." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Проверка наличия обновлений системы" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "" "Чтобы проверить наличие обновлений системы, необходимо пройти аутентификацию." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Отменить проверку системных обновлений" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "Требуется авторизация для отмены проверки системных обновлений." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Установка обновлений системы" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "Чтобы установить обновления системы, необходимо пройти аутентификацию." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Отменить установку системных обновлений" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "Требуется авторизация для отмены установки системных обновлений." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Установка определённой версии системы" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1303,21 +1355,39 @@ msgstr "" "Чтобы обновить систему до определённой (возможно, устаревшей) версии, " "необходимо пройти аутентификацию." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" -msgstr "Удаление устаревших обновлений системы" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Отменить установку определённой версии системы" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." msgstr "" -"Чтобы удалить устаревшие обновления системы, необходимо пройти " -"аутентификацию." +"Требуется авторизация для отмены обновления системы до определённой " +"(возможно, старой) версии." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "Очистить старые системные обновления" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "Требуется авторизация для очистки старых системных обновлений." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Отменить очистку старых системных обновлений" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "Требуется авторизация для отмены очистки старых системных обновлений." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Управление дополнительными функциями" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "" "Для управления дополнительными функциями необходимо пройти аутентификацию." @@ -1421,3 +1491,30 @@ msgid "" msgstr "" "Чтобы отправить сигнал заморозки или разморозки процессам юнита «$(unit)», " "необходимо пройти аутентификацию." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "Подтвердите присутствие на токене безопасности для разблокировки." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "Подтвердите пользователя на токене безопасности для разблокировки." + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" +"Введите PIN-код токена безопасности (осталось попыток до блокировки: %d):" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Введите PIN-код токена безопасности:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Удаление устаревших обновлений системы" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "Чтобы удалить устаревшие обновления системы, необходимо пройти " +#~ "аутентификацию." diff --git a/po/si.po b/po/si.po index f9279183656ac..07c809ceb746f 100644 --- a/po/si.po +++ b/po/si.po @@ -1,21 +1,21 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # Sinhala translation of systemd. -# Hela Basa , 2021. +# Hela Basa , 2021, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2021-08-19 07:04+0000\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:59+0000\n" "Last-Translator: Hela Basa \n" "Language-Team: Sinhala \n" +"main/si/>\n" "Language: si\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 4.7.2\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -143,156 +143,156 @@ msgstr "" msgid "Authentication is required to manage signing keys for home directories." msgstr "" -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "" -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "" -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "" -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "" @@ -790,33 +790,52 @@ msgid "" msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 -msgid "Create a local virtual machine or container" +msgid "Inspect local virtual machines and containers" msgstr "" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" -"Authentication is required to create a local virtual machine or container." +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" msgstr "" #: src/machine/org.freedesktop.machine1.policy:106 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "" @@ -926,7 +945,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 @@ -1069,45 +1090,95 @@ msgstr "" msgid "Authentication is required to reset statistics." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "" @@ -1193,3 +1264,21 @@ msgstr "" msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" diff --git a/po/sk.po b/po/sk.po index 511bbef665f6a..92afdd9f70489 100644 --- a/po/sk.po +++ b/po/sk.po @@ -2,21 +2,21 @@ # # Slovak translation for systemd. # Dušan Kazik , 2017. -# Frantisek Sumsal , 2021. +# 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-22 20:21+0000\n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:02+0000\n" "Last-Translator: Frantisek Sumsal \n" "Language-Team: Slovak \n" +"main/sk/>\n" "Language: sk\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==1) ? 1 : (n>=2 && n<=4) ? 2 : 0;\n" -"X-Generator: Weblate 4.4.2\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -170,156 +170,156 @@ msgstr "" "Vyžaduje sa overenie totožnosti na správu systémových služieb alebo iných " "jednotiek." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "" -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "" -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "" -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "" @@ -869,39 +869,58 @@ msgid "" msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 -msgid "Create a local virtual machine or container" +msgid "Inspect local virtual machines and containers" msgstr "" #: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:106 #, fuzzy msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Vyžaduje sa overenie totožnosti na nastavenie informácií o miestnom počítači." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "" "Vyžaduje sa overenie totožnosti na nastavenie informácií o miestnom počítači." -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Vyžaduje sa overenie totožnosti na nastavenie informácií o miestnom počítači." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "" @@ -1027,7 +1046,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 "Vyžaduje sa overenie totožnosti na uspanie systému." #: src/network/org.freedesktop.network1.policy:154 @@ -1191,29 +1212,61 @@ msgstr "" "Vyžaduje sa overenie totožnosti na nastavenie nastavení systémovej " "klávesnice." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 #, fuzzy msgid "Authentication is required to check for system updates." msgstr "Vyžaduje sa overenie totožnosti na nastavenie miestnych nastavení." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 #, fuzzy msgid "Authentication is required to install system updates." msgstr "Vyžaduje sa overenie totožnosti na nastavenie miestnych nastavení." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 #, fuzzy msgid "" "Authentication is required to update the system to a specific (possibly old) " @@ -1222,21 +1275,37 @@ msgstr "" "Vyžaduje sa overenie totožnosti na nastavenie nastavení systémovej " "klávesnice." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." msgstr "" -"Vyžaduje sa overenie totožnosti na znovu načítanie stavu systému systemd." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 #, fuzzy msgid "Authentication is required to manage optional features." msgstr "" @@ -1333,3 +1402,26 @@ msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "" "Vyžaduje sa overenie totožnosti na znovu načítanie stavu systému systemd." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#, fuzzy +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "Vyžaduje sa overenie totožnosti na znovu načítanie stavu systému systemd." diff --git a/po/sl.po b/po/sl.po index 997a03a792443..e526a45eae9c3 100644 --- a/po/sl.po +++ b/po/sl.po @@ -1,13 +1,13 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # -# Martin Srebotnjak , 2024, 2025. -# Weblate Translation Memory , 2024. +# Martin Srebotnjak , 2024, 2025, 2026. +# Weblate Translation Memory , 2024, 2026. 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-21 11:10+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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -174,7 +174,7 @@ msgstr "" "Preverjanje pristnosti je potrebno za upravljanje podpisnih ključev za " "domače mape." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -183,30 +183,30 @@ msgstr "" "Dom uporabnika %s je trenutno odsoten, priključite potrebno napravo za " "shranjevanje ali rezervni datotečni sistem." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "Prepogosti poskusi prijave uporabnika/ce %s, poskusite znova pozneje." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Geslo: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Geslo ni pravilno ali ne zadostuje za preverjanje pristnosti uporabnika %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Žal poskusite znova: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Obnovitveni ključ: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -215,20 +215,20 @@ msgstr "" "Geslo/obnovitveni ključ je nepravilen ali ne zadostuje za preverjanje " "pristnosti uporabnika/ce %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Žal morate znova vnesti obnovitveni ključ: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "Varnostni žeton uporabnika %s ni vstavljen." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Poskusite znova z geslom: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -237,26 +237,26 @@ msgstr "" "Geslo ni pravilno ali zadostno in prilagojeni varnostni žeton uporabnika %s " "ni vstavljen." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "PIN varnostnega žetona: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "Fizično preverite pristnost na varnostnem žetonu uporabnika %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "Potrdite prisotnost na varnostnem žetonu uporabnika %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "Preverite uporabnika na varnostnem žetonu uporabnika %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -264,78 +264,78 @@ msgstr "" "PIN varnostnega žetona je zaklenjen, najprej ga odklenite. (Namig: " "Odstranitev in vnovična vstavitev lahko zadostujeta.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "PIN varnostnega žetona ni pravilen za uporabnika %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Žal znova poskusite poskusiti s kodo PIN varnostnega žetona: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "PIN varnostnega žetona uporabnika %s je napačen (ostalo je le še nekaj " "poskusov!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "PIN varnostnega žetona uporabnika %s napačen (le en poskus je ostal!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "Dom uporabnika %s trenutno ni aktiven, najprej se prijavite krajevno." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "Dom uporabnika %s je trenutno zaklenjen, najprej odklenite krajevno." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "Preveč neuspešnih poskusov prijave za uporabnika %s, sledi zavrnitev." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "Zapis uporabnika je blokiran, kar prepoveduje dostop." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "Zapis uporabnika še ni veljaven, kar prepoveduje dostop." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "Zapis uporabnika ni več veljaven, kar prepoveduje dostop." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Zapis uporabnika ni veljaven, kar prepoveduje dostop." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Preveč prijav, poskusite znova čez %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Zahtevana je sprememba gesla." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Geslo je poteklo, potrebna je sprememba." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" "Geslo je poteklo, vendar ga ni mogoče spremeniti; prijava bo zavrnjena." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Geslo bo kmalu poteklo, spremenite ga." @@ -916,32 +916,43 @@ msgstr "" "računalnikov in vsebnikov." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Preuči krajevne navidezne računalnike in vsebnike" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"Preverjanje pristnosti je potrebno za preučitev krajevnih navideznih " +"računalnikov in vsebnikov." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Ustvari krajevni navidezni računalnik ali vsebnik" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Preverjanje pristnosti je potrebno za ustvarjanje krajevnih navideznih " "računalnikov in vsebnikov." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Registriraj krajevni navidezni računalnik ali vsebnik" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Preverjanje pristnosti je potrebno za registracijo krajevnih navideznih " "računalnikov in vsebnikov." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Upravljaj posnetke krajevnih navideznih računalnikov in vsebnikov" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -949,6 +960,18 @@ msgstr "" "Preverjanje pristnosti je potrebno za upravljanje posnetkov krajevnih " "navideznih računalnikov in vsebnikov." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Preuči krajevni navidezni računalnik in slike vsebnikov" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"Preverjanje pristnosti je potrebno za preučitev krajevnega navideznega " +"računalnika in slik vsebnikov." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Nastavi strežnike NTP" @@ -1067,10 +1090,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 +1138,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." @@ -1227,30 +1252,66 @@ msgstr "Ponastavi statistiko" msgid "Authentication is required to reset statistics." msgstr "Za ponastavitev statistike je potrebno preverjanje pristnosti." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "Izprazni predpomnilnike DNS" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "Za izpraznitev predpomnilnika DNS je potrebno preverjanje pristnosti." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Ponastavi funkcije strežnika" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "Za ponastavitev funkcij strežnika je zahtevano preverjanje pristnosti." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Preveri obstoj sistemskih posodobitev" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "" "Za preverjanje obstoja sistemskih posodobitev je potrebno preverjanje " "pristnosti." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Prekliči preverjanje obstoja posodobitev sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" +"Za preklic preverjanja obstoja posodobitev sistema je zahtevano preverjanje " +"pristnosti." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Namesti sistemske posodobitve" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "" "Za namestitev sistemskih posodobitev je potrebno preverjanje pristnosti." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Prekliči nameščanje posodobitev sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" +"Za preklic nameščanja posodobitev sistema je zahtevano preverjanje " +"pristnosti." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Namesti določeno različico sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1258,20 +1319,42 @@ msgstr "" "Za posodobitev sistema na določeno (verjetno starejšo) različico je potrebno " "preverjanje pristnosti." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Prekliči nameščanje določene različice sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" +"Za preklic nameščanja določene (verjetno starejše) različice sistema je " +"zahtevano preverjanje pristnosti." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" msgstr "Počisti stare posodobitve sistema" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." msgstr "" -"Za čiščenje starih posodobitev sistema je potrebno preverjanje pristnosti." +"Za čiščenje starih posodobitev sistema je zahtevano preverjanje pristnosti." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Prekliči čiščenje starih posodobitev sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" +"Za preklic čiščenja starih posodobitev sistema je zahtevano preverjanje " +"pristnosti." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Upravljaj dodatne funkcionalnosti" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "" "Preverjanje pristnosti je potrebno za upravljanje dodatnih funkcionalnosti." @@ -1376,6 +1459,31 @@ msgstr "" "Preverjanje pristnosti je potrebno za zamrznitev ali odmrzovanje postopkov " "enote »$(enota)«." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "Potrdite prisotnost za varnostni žeton, da bo možno odklepanje." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "Preverite uporabnika na varnostnem žetonu, da bo možno odklepanje." + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "Vnesite PIN varnostnega žetona (preostali poskusi pred zaklepom: %d):" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Vnesite PIN varnostnega žetona:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Počisti stare posodobitve sistema" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "Za čiščenje starih posodobitev sistema je potrebno preverjanje pristnosti." + #, fuzzy #~ msgid "Inhibit automatic lock of a home area" #~ msgstr "Zaviranje samodejnega zaklepanja domačega območja" diff --git a/po/sr.po b/po/sr.po index 770b0c192d500..0758bdce9bf8a 100644 --- a/po/sr.po +++ b/po/sr.po @@ -1,22 +1,25 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # # Serbian translation of systemd. -# Frantisek Sumsal , 2021. +# Frantisek Sumsal , 2021, 2026. +# Марко Костић , 2026 +# "Марко Костић (Marko Kostić)" , 2026. msgid "" msgstr "" +"Project-Id-Version: \n" "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" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:02+0000\n" "Last-Translator: Frantisek Sumsal \n" "Language-Team: Serbian \n" +"main/sr/>\n" "Language: sr\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: Weblate 4.4.2\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -71,259 +74,274 @@ 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 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" +"Превише чести покушаји пријаве за корисника %s, покушајте поново касније." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " -msgstr "" +msgstr "Лозинка: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" +"Лозинка је неисправна или није довољна за потврђивање идентитета корисника " +"%s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " -msgstr "" +msgstr "Покушајте поново: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " -msgstr "" +msgstr "Кључ за опоравак: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" +"Лозинка/кључ за опоравак је неправилан или није довољан за потврђивање " +"идентитета корисника %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " -msgstr "" +msgstr "Поново унесите кључ за опоравак: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." -msgstr "" +msgstr "Безбедносни жетон корисника %s није уметнут." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " -msgstr "" +msgstr "Покушајте поново помоћу лозинке: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " -msgstr "" +msgstr "ПИН безбедносног жетона: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" +"Молимо вас да се физички аутентификујете на безбедносном жетону корисника %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" +"Молимо вас да потврдите присутност на безбедносном жетону корисника %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." -msgstr "" +msgstr "Молимо вас да потврдите корисника на безбедносном жетону корисника %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." -msgstr "" +msgstr "ПИН безбедносног жетона је нетачан за корисника %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " -msgstr "" +msgstr "Извините, покушајте поново унети ПИН безбедносног жетона: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" +"ПИН безбедносног жетона корисника %s је нетачан (остало је још само неколико " +"покушаја!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" +"ПИН безбедносног жетона корисника %s је нетачан (остао је само један " +"покушај!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" +"Лична фасцикла корисника %s је тренутно закључана, молимо вас да је прво " +"локално откључате." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." -msgstr "" +msgstr "Превише неуспешних покушаја пријаве за корисника %s, одбија се." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." -msgstr "" +msgstr "Запис о кориснику је блокиран, приступ је забрањен." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." -msgstr "" +msgstr "Запис о кориснику још увек није исправан, приступ је забрањен." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." -msgstr "" +msgstr "Запис о кориснику више није исправан, приступ је забрањен." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." -msgstr "" +msgstr "Запис о кориснику није исправан, приступ је забрањен." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." -msgstr "" +msgstr "Превише пријава, покушајте поново за %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." -msgstr "" +msgstr "Потребна је промена лозинке." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." -msgstr "" +msgstr "Лозинка је истекла, потребна је промена." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." -msgstr "" +msgstr "Лозинка је истекла, али не може да се промени, одбија се пријава." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." -msgstr "" +msgstr "Лозинка ће ускоро истећи, молим вас промените је." #: src/hostname/org.freedesktop.hostname1.policy:20 msgid "Set hostname" @@ -357,80 +375,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 +594,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 +788,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 +811,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,36 +920,43 @@ msgstr "" "машинама и контејнерима." #: src/machine/org.freedesktop.machine1.policy:95 -#, fuzzy -msgid "Create a local virtual machine or container" -msgstr "Управљај локалним виртуелним машинама и контејнерима" +msgid "Inspect local virtual machines and containers" +msgstr "Прегледај локалне виртуелне машине и контејнере" #: src/machine/org.freedesktop.machine1.policy:96 -#, fuzzy msgid "" -"Authentication is required to create a local virtual machine or container." +"Authentication is required to inspect local virtual machines and containers." msgstr "" -"Потребно је да се идентификујете да бисте управљали локалним виртуелним " -"машинама и контејнерима." +"За прегледање локалних виртуелних машина и контејнера потребно је " +"потврђивање идентитета." + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" +msgstr "Направи локалну виртуелну машину или контејнер" #: src/machine/org.freedesktop.machine1.policy:106 -#, fuzzy +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "" +"Потребно је потврђивање идентитета за прављење локалне виртуелне машине или " +"контејнера." + +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" -msgstr "Управљај локалним виртуелним машинама и контејнерима" +msgstr "Региструј локалну виртуелну машину или контејнер" -#: src/machine/org.freedesktop.machine1.policy:107 -#, fuzzy +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" -"Потребно је да се идентификујете да бисте управљали локалним виртуелним " -"машинама и контејнерима." +"Потребно је потврђивање идентитета за регистровање локалне виртуелне машине " +"или контејнера." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Управљај локалним виртуелним машинама и сликама контејнера" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -960,346 +964,395 @@ msgstr "" "Потребно је да се идентификујете да бисте управљали локалним виртуелним " "машинама и сликама контејнера." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Прегледај локалне слике виртуелних машина и контејнера" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +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/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "Испразни ДНС оставе" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "За пражњење ДНС остава потребно је потврђивање идентитета." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Врати могућности сервера" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "За враћање могућности сервера потребно је потврђивање идентитета." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" -msgstr "" +msgstr "Провери ажурирања система" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 -#, fuzzy +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за проверу ажурирања система." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 -msgid "Install system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Откажи проверу системских ажурирања" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." msgstr "" +"Потребно је потврђивање идентитета да би се отказала провера ажурирања " +"система." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 +msgid "Install system updates" +msgstr "Инсталирај ажурирања система" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 -#, fuzzy +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +msgstr "Потребно је потврђивање идентитета за инсталирање ажурирања система." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 -msgid "Install specific system version" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Откажи инсталирање ажурирања система" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." msgstr "" +"Потребно је потврђивање идентитета да би се отказало инсталирање ажурирања " +"система." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 +msgid "Install specific system version" +msgstr "Инсталирај одређено издање система" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 -#, fuzzy +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." msgstr "" -"Потребно је да се идентификујете да бисте поставили системску временску зону." +"Потребно је потврђивање идентитета за ажурирање система на одређено (могуће " +"старо) издање." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Откажи инсталирање одређеног издања система" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." msgstr "" +"Потребно је потврђивање идентитета да би се отказало ажурирање система на " +"одређено (могуће старо) издање." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -#, fuzzy -msgid "Authentication is required to cleanup old system updates." -msgstr "Потребно је да се идентификујете да бисте поставили системско време." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "Очисти стара ажурирања система" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 -msgid "Manage optional features" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." msgstr "" +"Потребно је потврђивање идентитета да би се очистила стара ажурирања система." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 -#, fuzzy -msgid "Authentication is required to manage optional features." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Откажи чишћење старих ажурирања система" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." msgstr "" -"Потребно је да се идентификујете да бисте управљали покренутим сесијама, " -"корисницима и седиштима." +"Потребно је потврђивање идентитета да би се отказало чишћење старих " +"ажурирања система." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 +msgid "Manage optional features" +msgstr "Управљај опционим могућностима" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 +msgid "Authentication is required to manage optional features." +msgstr "Потребно је потврђивање идентитета за управљање опционим могућностима." #: src/timedate/org.freedesktop.timedate1.policy:22 msgid "Set system time" @@ -1345,80 +1398,85 @@ msgstr "" #: src/core/dbus-unit.c:372 msgid "Authentication is required to start '$(unit)'." -msgstr "Потребно је да се идентификујете да бисте покренули „$(unit)“." +msgstr "Потребно је потврђивање идентитета да би се покренула '$(unit)'." #: src/core/dbus-unit.c:373 msgid "Authentication is required to stop '$(unit)'." -msgstr "Потребно је да се идентификујете да бисте зауставили „$(unit)“." +msgstr "Потребно је потврђивање идентитета да би се зауставило '$(unit)'." #: src/core/dbus-unit.c:374 msgid "Authentication is required to reload '$(unit)'." -msgstr "Потребно је да се идентификујете да бисте поново учитали „$(unit)“." +msgstr "Потребно је потврђивање идентитета да би се поново учитало '$(unit)'." #: src/core/dbus-unit.c:375 src/core/dbus-unit.c:376 msgid "Authentication is required to restart '$(unit)'." -msgstr "Потребно је да се идентификујете да бисте поново покренули „$(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)“." +"Потребно је потврђивање идентитета да би се послао UNIX сигнал процесима '$" +"(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)“." +"Потребно је потврђивање идентитета да би се послао UNIX сигнал процесима " +"подгрупе '$(unit)'." #: src/core/dbus-unit.c:649 msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." msgstr "" -"Потребно је да се идентификујете да бисте поново поставили „неуспешно“ стање " -"за „$(unit)“." +"Потребно је потврђивање идентитета да би се поништило стање „неуспешно“ за '$" +"(unit)'." #: src/core/dbus-unit.c:679 msgid "Authentication is required to set properties on '$(unit)'." msgstr "" -"Потребно је да се идентификујете да бисте поставили својства за „$(unit)“." +"Потребно је потврђивање идентитета да би се поставила својства за '$(unit)'." #: src/core/dbus-unit.c:776 -#, fuzzy msgid "" "Authentication is required to delete files and directories associated with '$" "(unit)'." msgstr "" -"Потребно је да се идентификујете да бисте поново поставили „неуспешно“ стање " -"за „$(unit)“." +"Потребно је потврђивање идентитета да би се обрисале датотеке и " +"директоријуми повезани са '$(unit)'." #: src/core/dbus-unit.c:813 -#, fuzzy msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "" -"Потребно је да се идентификујете да бисте поново поставили „неуспешно“ стање " -"за „$(unit)“." +"Потребно је потврђивање идентитета да би се замрзнули или одмрзнули процеси " +"јединице '$(unit)'." -#~ msgid "" -#~ "Authentication is required to halt the system while an application asked " -#~ "to inhibit it." -#~ msgstr "" -#~ "Потребно је да се идентификујете да бисте зауставили систем иако програм " -#~ "тражи да се спречи заустављање система." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "Потврдите присуство на жетону безбедности да бисте откључали." -#~ msgid "Authentication is required to kill '$(unit)'." -#~ msgstr "Потребно је да се идентификујете да бисте убили „$(unit)“." +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "Потврдите корисника на безбедносном жетону да бисте откључали." -#~ msgid "Press Ctrl+C to cancel all filesystem checks in progress" -#~ msgstr "" -#~ "Притисните Ctrl+C да бисте прекинули све текуће провере система датотека" +#: src/shared/libfido2-util.c:936 +#, fuzzy, c-format +#| msgid "Please enter security token PIN:" +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "Унесите ПИН безбедносног жетона:" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Унесите ПИН безбедносног жетона:" -#~ 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%% готово)" +#~ msgid "Cleanup old system updates" +#~ msgstr "Очисти стара ажурирања система" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "Потребно је потврђивање идентитета за чишћење старих ажурирања система." diff --git a/po/sr@latin.po b/po/sr@latin.po new file mode 100644 index 0000000000000..31dce26efb205 --- /dev/null +++ b/po/sr@latin.po @@ -0,0 +1,1445 @@ +# 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: \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:57+0000\n" +"Last-Translator: Anonymous \n" +"Language-Team: Serbian (Latin script) \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: Weblate 2026.6.1\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/home/pam_systemd_home.c:327 +#, 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:332 +#, c-format +msgid "Too frequent login attempts for user %s, try again later." +msgstr "" + +#: src/home/pam_systemd_home.c:344 +msgid "Password: " +msgstr "" + +#: src/home/pam_systemd_home.c:346 +#, c-format +msgid "Password incorrect or not sufficient for authentication of user %s." +msgstr "" + +#: src/home/pam_systemd_home.c:347 +msgid "Sorry, try again: " +msgstr "" + +#: src/home/pam_systemd_home.c:369 +msgid "Recovery key: " +msgstr "" + +#: src/home/pam_systemd_home.c:371 +#, c-format +msgid "" +"Password/recovery key incorrect or not sufficient for authentication of user " +"%s." +msgstr "" + +#: src/home/pam_systemd_home.c:372 +msgid "Sorry, reenter recovery key: " +msgstr "" + +#: src/home/pam_systemd_home.c:392 +#, c-format +msgid "Security token of user %s not inserted." +msgstr "" + +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 +msgid "Try again with password: " +msgstr "" + +#: src/home/pam_systemd_home.c:395 +#, c-format +msgid "" +"Password incorrect or not sufficient, and configured security token of user " +"%s not inserted." +msgstr "" + +#: src/home/pam_systemd_home.c:415 +msgid "Security token PIN: " +msgstr "" + +#: src/home/pam_systemd_home.c:432 +#, c-format +msgid "Please authenticate physically on security token of user %s." +msgstr "" + +#: src/home/pam_systemd_home.c:443 +#, c-format +msgid "Please confirm presence on security token of user %s." +msgstr "" + +#: src/home/pam_systemd_home.c:454 +#, c-format +msgid "Please verify user on security token of user %s." +msgstr "" + +#: src/home/pam_systemd_home.c:463 +msgid "" +"Security token PIN is locked, please unlock it first. (Hint: Removal and re-" +"insertion might suffice.)" +msgstr "" + +#: src/home/pam_systemd_home.c:471 +#, c-format +msgid "Security token PIN incorrect for user %s." +msgstr "" + +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 +msgid "Sorry, retry security token PIN: " +msgstr "" + +#: src/home/pam_systemd_home.c:490 +#, c-format +msgid "Security token PIN of user %s incorrect (only a few tries left!)" +msgstr "" + +#: src/home/pam_systemd_home.c:509 +#, c-format +msgid "Security token PIN of user %s incorrect (only one try left!)" +msgstr "" + +#: src/home/pam_systemd_home.c:676 +#, c-format +msgid "Home of user %s is currently not active, please log in locally first." +msgstr "" + +#: src/home/pam_systemd_home.c:678 +#, c-format +msgid "Home of user %s is currently locked, please unlock locally first." +msgstr "" + +#: src/home/pam_systemd_home.c:712 +#, c-format +msgid "Too many unsuccessful login attempts for user %s, refusing." +msgstr "" + +#: src/home/pam_systemd_home.c:1021 +msgid "User record is blocked, prohibiting access." +msgstr "" + +#: src/home/pam_systemd_home.c:1025 +msgid "User record is not valid yet, prohibiting access." +msgstr "" + +#: src/home/pam_systemd_home.c:1029 +msgid "User record is not valid anymore, prohibiting access." +msgstr "" + +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 +msgid "User record not valid, prohibiting access." +msgstr "" + +#: src/home/pam_systemd_home.c:1044 +#, c-format +msgid "Too many logins, try again in %s." +msgstr "" + +#: src/home/pam_systemd_home.c:1055 +msgid "Password change required." +msgstr "" + +#: src/home/pam_systemd_home.c:1059 +msgid "Password expired, change required." +msgstr "" + +#: src/home/pam_systemd_home.c:1065 +msgid "Password is expired, but can't change, refusing login." +msgstr "" + +#: src/home/pam_systemd_home.c:1069 +msgid "Password will expire soon, please change." +msgstr "" + +#: 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 "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" +msgstr "Napravi lokalnu virtuelnu mašinu ili kontejner" + +#: src/machine/org.freedesktop.machine1.policy:106 +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:116 +msgid "Register a local virtual machine or container" +msgstr "Registruj lokalnu virtuelnu mašinu ili kontejner" + +#: src/machine/org.freedesktop.machine1.policy:117 +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:126 +msgid "Manage local virtual machine and container images" +msgstr "Upravljaj lokalnim virtuelnim mašinama i slikama kontejnera" + +#: src/machine/org.freedesktop.machine1.policy:127 +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/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + +#: 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/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 +msgid "Check for system updates" +msgstr "Proveri ažuriranja sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 +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:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 +msgid "Install system updates" +msgstr "Instaliraj ažuriranja sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 +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:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 +msgid "Install specific system version" +msgstr "Instaliraj određeno izdanje sistema" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 +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:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 +msgid "Manage optional features" +msgstr "Upravljaj opcionim mogućnostima" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 +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." + +#: src/core/dbus-unit.c:372 +msgid "Authentication is required to start '$(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:373 +msgid "Authentication is required to stop '$(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:374 +msgid "Authentication is required to reload '$(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:375 src/core/dbus-unit.c:376 +msgid "Authentication is required to restart '$(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:568 +msgid "" +"Authentication is required to send a UNIX signal to the processes of '$" +"(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:621 +msgid "" +"Authentication is required to send a UNIX signal to the processes of " +"subgroup of '$(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:649 +msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:679 +msgid "Authentication is required to set properties on '$(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:776 +msgid "" +"Authentication is required to delete files and directories associated with '$" +"(unit)'." +msgstr "" + +#: src/core/dbus-unit.c:813 +msgid "" +"Authentication is required to freeze or thaw the processes of '$(unit)' unit." +msgstr "" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Očisti stara ažuriranja sistema" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "Potrebno je potvrđivanje identiteta za čišćenje starih ažuriranja sistema." diff --git a/po/sv.po b/po/sv.po index 2b0630fce8012..bd354e6a6d5c2 100644 --- a/po/sv.po +++ b/po/sv.po @@ -4,17 +4,18 @@ # Sebastian Rasmussen , 2015. # Andreas Henriksson , 2016. # Josef Andersson , 2015, 2017. -# Göran Uddeborg , 2020, 2021, 2024. -# Luna Jernberg , 2020, 2023, 2024, 2025. -# Anders Jonsson , 2022, 2024. +# Göran Uddeborg , 2020, 2021, 2024, 2026. +# Luna Jernberg , 2020, 2023, 2024, 2025, 2026. +# Anders Jonsson , 2022, 2024, 2026. # Weblate Translation Memory , 2024. # Daniel Nylander , 2026. +# Luna Jernberg , 2026. 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" -"Last-Translator: Daniel Nylander \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-21 11:10+0000\n" +"Last-Translator: Luna Jernberg \n" "Language-Team: Swedish \n" "Language: sv\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.16.1\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -160,7 +161,7 @@ msgid "Authentication is required to manage signing keys for home directories." msgstr "" "Autentisering krävs för att hantera signeringsnycklar för hemkataloger." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -169,31 +170,31 @@ msgstr "" "Hem för användaren %s är för närvarande inte tillgängligt, vänligen koppla " "in nödvändiga lagringsenheter eller säkerhetskopieringsfilsystem." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "För ofta inloggningsförsök från användare %s, försök igen senare." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Lösenord: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Lösenordet är felaktigt eller inte tillräckligt för autentisering av " "användare %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Tyvärr, försök igen: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Återställningsnyckel: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -202,20 +203,20 @@ msgstr "" "Lösenord/återställningsnyckel är felaktig eller inte tillräcklig för " "autentisering av användare %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Tyvärr, ange återställningsnyckel igen: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "Säkerhetstoken för användare %s har inte kopplats in." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Prova igen med lösenord: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -224,26 +225,26 @@ msgstr "" "Lösenordet är felaktigt eller inte tillräckligt, och konfigurerad " "säkerhetstoken för användare %s har inte kopplats in." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "Säkerhetstoken-PIN: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "Vänligen autentisera fysiskt på säkerhetstoken för användare %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "Vänligen bekräfta närvaro på säkerhetstoken för användare %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "Vänligen verifiera användare på säkerhetstoken för användare %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -251,80 +252,80 @@ msgstr "" "PIN-kod för säkerhetstoken är låst. Lås upp den först. (Tips: Det kan räcka " "med borttagning och återinsättning.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "Felaktig PIN-kod för säkerhetstoken för användare %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Tyvärr, försök igen med PIN-kod för säkerhetstoken: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "Säkerhetstoken-PIN för användare %s är felaktig (bara några försök kvar!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "Säkerhetstoken-PIN för användare %s är felaktig (endast ett försök kvar!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "Hem för användare %s är för närvarande inte aktivt, vänligen logga in lokalt " "först." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "Hem för användare %s är för närvarande låst, vänligen lås upp lokalt först." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "För många misslyckade inloggningsförsök för användare %s, vägrar." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "Användarposten är blockerad, vilket förbjuder åtkomst." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "Användarposten är inte giltig ännu, vilket förbjuder åtkomst." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "Användarposten är inte längre giltig, vilket förbjuder åtkomst." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Användarposten är inte giltig, förbjuder åtkomst." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "För många inloggningsförsök, försök igen om %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Lösenordsbyte krävs." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Lösenordet har löpt ut, byte av lösenord krävs." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "Lösenordet har löpt ut, men kan inte ändras, vägrar inloggning." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Lösenordet upphör snart, vänligen byt." @@ -880,31 +881,42 @@ msgstr "" "Autentisering krävs för att hantera lokala virtuella maskiner och behållare." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Inspektera lokala virtuella maskiner och behållare" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"Autentisering krävs för att inspektera lokala virtuella maskiner och " +"behållare." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Skapa en lokal virtuell maskin eller behållare" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Autentisering krävs för att skapa en lokal virtuell maskin eller behållare." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Registrera en lokal virtuell maskin eller behållare" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Autentisering krävs för att registrera en lokal virtuell maskin eller " "behållare." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Hantera lokala virtuella maskin- och behållaravbildningar" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -912,6 +924,18 @@ msgstr "" "Autentisering krävs för att hantera lokala virtuella maskin- och " "behållaravbildningar." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Inspektera lokala virtuella maskiner och behållaravbilder" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"Autentisering krävs för att inspektera lokala virtuella maskiner och " +"behållaravbilder." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Ange NTP-servrar" @@ -1021,8 +1045,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" @@ -1169,27 +1197,61 @@ msgstr "Återställ statistik" msgid "Authentication is required to reset statistics." msgstr "Autentisering krävs för att återställa statistik." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "Töm DNS-cachar" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "Autentisering krävs för att tömma DNS-cacheminnen." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Återställ serverfunktioner" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "Autentisering krävs för att återställa serverfunktioner." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Kontrollera systemuppgraderingar" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "Autentisering krävs för att kontrollera systemuppgraderingar." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Avbryt sökandet efter systemuppdateringar" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" +"Autentisering krävs för att avbryta sökandet efter systemuppdateringar." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Installera systemuppdateringar" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "Autentisering krävs för att installera systemuppdateringar." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Avbryt installationen av systemuppdateringar" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" +"Autentisering krävs för att avbryta installationen av systemuppdateringar." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Installera en specifik systemversion" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1197,19 +1259,40 @@ msgstr "" "Autentisering krävs för att uppdatera systemet till en specifik (eventuellt " "gammal) version." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" -msgstr "Rensa gamla systemuppdateringar" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Avbryt installationen av en specifik systemversion" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" +"Autentisering krävs för att avbryta uppdateringen av systemet till en " +"specifik (möjligen gammal) version." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." -msgstr "Autentisering krävs för att rensa gamla systemuppdateringar." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "Rensa upp gamla systemuppdateringar" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "Autentisering krävs för att rensa upp gamla systemuppdateringar." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Avbryt rensningen av gamla systemuppdateringar" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" +"Autentisering krävs för att avbryta rensningen av gamla systemuppdateringar." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Hantera valfria funktioner" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "Autentisering krävs för att hantera valfria funktioner." @@ -1311,6 +1394,30 @@ msgid "" msgstr "" "Autentisering krävs för att frysa eller töa processerna i enheten ”$(unit)”." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "Bekräfta närvaro på säkerhetstoken för att låsa upp." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "Verifiera användare med säkerhetstoken för att låsa upp." + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "Ange säkerhetstoken-PIN (återstående försök före utelåsning: %d):" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Ange PIN-koden för säkerhetstoken:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Rensa gamla systemuppdateringar" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "Autentisering krävs för att rensa gamla systemuppdateringar." + #~ msgid "" #~ "Authentication is required to halt the system while an application asked " #~ "to inhibit it." diff --git a/po/systemd.pot b/po/systemd.pot index 825fd49cb0205..842c503858078 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-06-01 11:49+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -143,156 +143,156 @@ msgstr "" msgid "Authentication is required to manage signing keys for home directories." msgstr "" -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "" -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "" -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "" -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "" -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "" -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "" @@ -789,33 +789,52 @@ msgid "" msgstr "" #: src/machine/org.freedesktop.machine1.policy:95 -msgid "Create a local virtual machine or container" +msgid "Inspect local virtual machines and containers" msgstr "" #: src/machine/org.freedesktop.machine1.policy:96 msgid "" -"Authentication is required to create a local virtual machine or container." +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" msgstr "" #: src/machine/org.freedesktop.machine1.policy:106 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "" @@ -925,7 +944,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 @@ -1068,45 +1089,95 @@ msgstr "" msgid "Authentication is required to reset statistics." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "" @@ -1192,3 +1263,21 @@ msgstr "" msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" diff --git a/po/tr.po b/po/tr.po index 4671c06b87414..ba0da01e8b0b8 100644 --- a/po/tr.po +++ b/po/tr.po @@ -6,12 +6,12 @@ # Oğuz Ersen , 2020. # Muhammet Kara , 2015-2020. # Oğuz Ersen , 2022, 2023, 2024, 2025, 2026. -# Emir SARI , 2025. +# Emir SARI , 2025, 2026. 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-21 11:10+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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -169,7 +169,7 @@ msgstr "" "Ana dizinler için olan imzalama anahtarlarını yönetmek için kimlik " "doğrulaması gereklidir." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -178,31 +178,31 @@ msgstr "" "%s kullanıcısının ana dizini şu anda mevcut değil, lütfen gerekli depolama " "aygıtını veya içeren dosya sistemini bağlayın." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "%s kullanıcısı için çok sık oturum açma denemesi, daha sonra tekrar deneyin." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Parola: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Parola yanlış veya %s kullanıcısının kimlik doğrulaması için yeterli değil." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Üzgünüm, tekrar deneyin: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Kurtarma anahtarı: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -211,20 +211,20 @@ msgstr "" "Parola/kurtarma anahtarı yanlış veya %s kullanıcısının kimlik doğrulaması " "için yeterli değil." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Üzgünüm, kurtarma anahtarını yeniden girin: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, c-format msgid "Security token of user %s not inserted." msgstr "%s kullanıcısının güvenlik belirteci girilmedi." -#: src/home/pam_systemd_home.c:396 src/home/pam_systemd_home.c:399 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Parola ile tekrar deneyin: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -233,29 +233,29 @@ msgstr "" "Parola yanlış veya yeterli değil ve %s kullanıcısının yapılandırılan " "güvenlik belirteci girilmedi." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "Güvenlik belirteci PIN kodu: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" "Lütfen %s kullanıcısının güvenlik belirteci ile fiziksel olarak kimlik " "doğrulaması yapın." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "Lütfen %s kullanıcısının güvenlik belirtecinin varlığını doğrulayın." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "" "Lütfen %s kullanıcısının güvenlik belirtecindeki kullanıcıyı doğrulayın." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -263,84 +263,84 @@ msgstr "" "Güvenlik belirteci PIN kodu kilitli, lütfen önce kilidini açın. (İpucu: " "Çıkarma ve yeniden takma yeterli olabilir.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "Güvenlik belirteci PIN kodu %s kullanıcısı için yanlış." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Üzgünüm, güvenlik belirteci PIN kodunu yeniden deneyin: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "%s kullanıcısının güvenlik belirteci PIN kodu yanlış (sadece birkaç deneme " "kaldı!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "%s kullanıcısının güvenlik belirteci PIN kodu yanlış (sadece bir deneme " "kaldı!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, c-format msgid "Home of user %s is currently not active, please log in locally first." msgstr "" "%s kullanıcısının ev dizini şu anda etkin değil, lütfen önce yerel olarak " "oturum açın." -#: src/home/pam_systemd_home.c:681 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "%s kullanıcısının ev dizini şu anda kilitli, lütfen önce yerel olarak " "kilidini açın." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" "%s kullanıcısı için çok fazla başarısız oturum açma denemesi, reddediliyor." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "Kullanıcı kaydı engellendi, erişim engelleniyor." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "Kullanıcı kaydı henüz geçerli değil, erişim engelleniyor." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "Kullanıcı kaydı artık geçerli değil, erişim engelleniyor." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Kullanıcı kaydı geçerli değil, erişim engelleniyor." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Çok fazla oturum açıldı, %s içinde tekrar deneyin." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Parola değişikliği gerekli." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Parolanın süresi doldu, değiştirilmesi gerekiyor." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "Parolanın süresi doldu ama değiştirilemiyor, oturum açma reddediliyor." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Parolanın süresi yakında dolacak, lütfen değiştirin." @@ -917,32 +917,43 @@ msgstr "" "gereklidir." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Yerel sanal makineleri ve kapsayıcıları incele" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"Yerel sanal makineleri ve kapsayıcıları incelemek için kimlik doğrulaması " +"gereklidir." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Yerel sanal makine veya kapsayıcı oluştur" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Yerel sanal makine veya kapsayıcı oluşturmak için kimlik doğrulaması " "gereklidir." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Yerel sanal makine veya kapsayıcı kaydet" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Yerel sanal makine veya kapsayıcı kaydetmek için kimlik doğrulaması " "gereklidir." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Yerel sanal makine ve kapsayıcı kalıplarını yönet" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -950,6 +961,18 @@ msgstr "" "Yerel sanal makineler ve kapsayıcı kalıplarını yönetmek için kimlik " "doğrulaması gereklidir." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Yerel sanal makine ve kapsayıcı kalıplarını incele" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"Yerel sanal makineler ve kapsayıcı kalıplarını incelemek için kimlik " +"doğrulaması gereklidir." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "NTP sunucularını ayarla" @@ -1069,8 +1092,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" @@ -1222,27 +1249,63 @@ msgstr "İstatistikleri sıfırla" msgid "Authentication is required to reset statistics." msgstr "İstatistikleri sıfırlamak için kimlik doğrulaması gereklidir." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "DNS önbelleklerini temizle" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "DNS önbelleklerini temizlemek için kimlik doğrulaması gereklidir." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Sunucu özelliklerini sıfırla" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "Sunucu özelliklerini sıfırlamak için kimlik doğrulaması gereklidir." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Sistem güncellemelerini denetle" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "Sistem güncellemelerini denetlemek için kimlik doğrulaması gereklidir." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Sistem güncellemelerini denetlemeyi iptal et" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" +"Sistem güncellemelerini denetlemeyi iptal etmek için kimlik doğrulaması " +"gereklidir." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Sistem güncellemelerini kur" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "Sistem güncellemelerini kurmak için kimlik doğrulaması gereklidir." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Sistem güncellemelerini kurmayı iptal et" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" +"Sistem güncellemelerini kurmayı iptal etmek için kimlik doğrulaması " +"gereklidir." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Belirli sistem sürümünü kur" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1250,20 +1313,42 @@ msgstr "" "Sistemi belirli (muhtemelen eski) bir sürüme güncellemek için kimlik " "doğrulaması gereklidir." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Belirli bir sistem sürümünü kurmayı iptal et" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" +"Sistemi belirli bir (muhtemelen eski) sürüme güncellemeyi iptal etmek için " +"kimlik doğrulaması gereklidir." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" msgstr "Eski sistem güncellemelerini temizle" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." msgstr "" "Eski sistem güncellemelerini temizlemek için kimlik doğrulaması gereklidir." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Eski sistem güncellemelerini temizlemeyi iptal et" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" +"Eski sistem güncellemelerini temizlemeyi iptal etmek için kimlik doğrulaması " +"gereklidir." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "İsteğe bağlı özellikleri yönet" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "İsteğe bağlı özellikleri yönetmek için kimlik doğrulaması gereklidir." @@ -1365,6 +1450,34 @@ msgstr "" "'$(unit)' biriminin işlemlerini dondurmak veya devam ettirmek için kimlik " "doğrulaması gereklidir." +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "Kilidi açmak için lütfen güvenlik belirtecinin varlığını doğrulayın." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "Kilidi açmak için lütfen güvenlik belirteçinde kullanıcıyı doğrulayın." + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" +"Lütfen güvenlik belirteci kodunu girin (kilitlenmeden önce kalan deneme " +"sayısı: %d):" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Lütfen güvenlik belirteci kodunu girin:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Eski sistem güncellemelerini temizle" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "Eski sistem güncellemelerini temizlemek için kimlik doğrulaması " +#~ "gereklidir." + #~ msgid "" #~ "Authentication is required to halt the system while an application asked " #~ "to inhibit it." diff --git a/po/ug.po b/po/ug.po new file mode 100644 index 0000000000000..d3c8244862f6b --- /dev/null +++ b/po/ug.po @@ -0,0 +1,1428 @@ +# 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:00+0000\n" +"Last-Translator: Anonymous \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: Weblate 2026.6.1\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:327 +#, 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:332 +#, c-format +msgid "Too frequent login attempts for user %s, try again later." +msgstr "" +"%s ئىشلەتكۈچىنىڭ كىرىش سىنىقى بەك كۆپ قېتىم بولدى، كېيىن قايتا سىناپ بېقىڭ." + +#: src/home/pam_systemd_home.c:344 +msgid "Password: " +msgstr "پارول: " + +#: src/home/pam_systemd_home.c:346 +#, c-format +msgid "Password incorrect or not sufficient for authentication of user %s." +msgstr "%s ئىشلەتكۈچىنىڭ پارولى خاتا ياكى دەلىللەش ئۈچۈن يېتەرلىك ئەمەس." + +#: src/home/pam_systemd_home.c:347 +msgid "Sorry, try again: " +msgstr "كەچۈرۈڭ، قايتا سىناڭ: " + +#: src/home/pam_systemd_home.c:369 +msgid "Recovery key: " +msgstr "ئەسلىگە كەلتۈرۈش ئاچقۇچى: " + +#: src/home/pam_systemd_home.c:371 +#, c-format +msgid "" +"Password/recovery key incorrect or not sufficient for authentication of user " +"%s." +msgstr "" +"%s ئىشلەتكۈچىنىڭ پارول/ئەسلىگە كەلتۈرۈش ئاچقۇچى خاتا ياكى دەلىللەش ئۈچۈن " +"يېتەرلىك ئەمەس." + +#: src/home/pam_systemd_home.c:372 +msgid "Sorry, reenter recovery key: " +msgstr "كەچۈرۈڭ، ئەسلىگە كەلتۈرۈش ئاچقۇچىنى قايتا كىرگۈزۈڭ: " + +#: src/home/pam_systemd_home.c:392 +#, c-format +msgid "Security token of user %s not inserted." +msgstr "%s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنى چېتىلمىغان." + +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 +msgid "Try again with password: " +msgstr "پارول بىلەن قايتا سىناڭ: " + +#: src/home/pam_systemd_home.c:395 +#, 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:415 +msgid "Security token PIN: " +msgstr "بىخەتەرلىك توكىنى PIN: " + +#: src/home/pam_systemd_home.c:432 +#, c-format +msgid "Please authenticate physically on security token of user %s." +msgstr "" +"ئىلتىماس، %s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنىدا فىزىكىلىق دەلىللەشنى تاماملاڭ." + +#: src/home/pam_systemd_home.c:443 +#, c-format +msgid "Please confirm presence on security token of user %s." +msgstr "" +"ئىلتىماس، %s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنىدا ھازىر ئىكەنلىكىڭىزنى جەزملەڭ." + +#: src/home/pam_systemd_home.c:454 +#, c-format +msgid "Please verify user on security token of user %s." +msgstr "" +"ئىلتىماس، %s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنىدىكى ئىشلەتكۈچىنى تەكشۈرۈپ " +"دەلىللەڭ." + +#: src/home/pam_systemd_home.c:463 +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:471 +#, c-format +msgid "Security token PIN incorrect for user %s." +msgstr "%s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنى PIN خاتا." + +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 +msgid "Sorry, retry security token PIN: " +msgstr "كەچۈرۈڭ، بىخەتەرلىك توكىنى PIN نى قايتا سىناڭ: " + +#: src/home/pam_systemd_home.c:490 +#, c-format +msgid "Security token PIN of user %s incorrect (only a few tries left!)" +msgstr "" +"%s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنى PIN خاتا (پەقەت بىر قانچەلا سىناق پۇرسىتى " +"قالدى!)" + +#: src/home/pam_systemd_home.c:509 +#, c-format +msgid "Security token PIN of user %s incorrect (only one try left!)" +msgstr "" +"%s ئىشلەتكۈچىنىڭ بىخەتەرلىك توكىنى PIN خاتا (پەقەت بىرلا قېتىملىق سىناق " +"پۇرسىتى قالدى!)" + +#: src/home/pam_systemd_home.c:676 +#, c-format +msgid "Home of user %s is currently not active, please log in locally first." +msgstr "" +"%s ئىشلەتكۈچىنىڭ باش مۇندەرىجىسى ھازىر ئاكتىپ ئەمەس، ئالدى بىلەن يەرلىك " +"كىرىڭ." + +#: src/home/pam_systemd_home.c:678 +#, c-format +msgid "Home of user %s is currently locked, please unlock locally first." +msgstr "" +"%s ئىشلەتكۈچىنىڭ باش مۇندەرىجىسى ھازىر قۇلۇپلانغان، ئالدى بىلەن يەرلىكتە " +"قۇلۇپنى ئېچىڭ." + +#: src/home/pam_systemd_home.c:712 +#, c-format +msgid "Too many unsuccessful login attempts for user %s, refusing." +msgstr "" +"%s ئىشلەتكۈچىنىڭ مۇۋەپپەقىيەتسىز كىرىش سىنىقى بەك كۆپ بولدى، رەت قىلىندى." + +#: src/home/pam_systemd_home.c:1021 +msgid "User record is blocked, prohibiting access." +msgstr "ئىشلەتكۈچى خاتىرىسى توسۇلغان، زىيارەت چەكلەندى." + +#: src/home/pam_systemd_home.c:1025 +msgid "User record is not valid yet, prohibiting access." +msgstr "ئىشلەتكۈچى خاتىرىسى تېخى كۈچكە ئىگە ئەمەس، زىيارەت چەكلەندى." + +#: src/home/pam_systemd_home.c:1029 +msgid "User record is not valid anymore, prohibiting access." +msgstr "ئىشلەتكۈچى خاتىرىسى ئەمدى كۈچكە ئىگە ئەمەس، زىيارەت چەكلەندى." + +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 +msgid "User record not valid, prohibiting access." +msgstr "ئىشلەتكۈچى خاتىرىسى كۈچكە ئىگە ئەمەس، زىيارەت چەكلەندى." + +#: src/home/pam_systemd_home.c:1044 +#, c-format +msgid "Too many logins, try again in %s." +msgstr "كىرىش قېتىملىرى بەك كۆپ، %s دىن كېيىن قايتا سىناڭ." + +#: src/home/pam_systemd_home.c:1055 +msgid "Password change required." +msgstr "پارولنى ئۆزگەرتىش زۆرۈر." + +#: src/home/pam_systemd_home.c:1059 +msgid "Password expired, change required." +msgstr "پارولنىڭ مۇددىتى توشتى، ئۆزگەرتىش زۆرۈر." + +#: src/home/pam_systemd_home.c:1065 +msgid "Password is expired, but can't change, refusing login." +msgstr "" +"پارولنىڭ مۇددىتى توشقان، ئەمما ئۆزگەرتكىلى بولمايدۇ، كىرىش رەت قىلىندى." + +#: src/home/pam_systemd_home.c:1069 +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 "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 +msgid "Create a local virtual machine or container" +msgstr "يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېر قۇر" + +#: src/machine/org.freedesktop.machine1.policy:106 +msgid "" +"Authentication is required to create a local virtual machine or container." +msgstr "" +"يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېر قۇرۇش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:116 +msgid "Register a local virtual machine or container" +msgstr "يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېرنى تىزىملا" + +#: src/machine/org.freedesktop.machine1.policy:117 +msgid "" +"Authentication is required to register a local virtual machine or container." +msgstr "" +"يەرلىك مەۋھۇم ماشىنا ياكى كونتېينېرنى تىزىملاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:126 +msgid "Manage local virtual machine and container images" +msgstr "يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېر ئەكسلىرىنى باشقۇر" + +#: src/machine/org.freedesktop.machine1.policy:127 +msgid "" +"Authentication is required to manage local virtual machine and container " +"images." +msgstr "" +"يەرلىك مەۋھۇم ماشىنا ۋە كونتېينېر ئەكسلىرىنى باشقۇرۇش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." + +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect 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/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 +msgid "Check for system updates" +msgstr "سىستېما يېڭىلانمىلىرىنى تەكشۈر" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 +msgid "Authentication is required to check for system updates." +msgstr "سىستېما يېڭىلانمىلىرىنى تەكشۈرۈش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 +msgid "Install system updates" +msgstr "سىستېما يېڭىلانمىلىرىنى ئورنات" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 +msgid "Authentication is required to install system updates." +msgstr "سىستېما يېڭىلانمىلىرىنى ئورنىتىش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 +msgid "Install specific system version" +msgstr "بەلگىلەنگەن سىستېما نەشرىنى ئورنات" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 +msgid "" +"Authentication is required to update the system to a specific (possibly old) " +"version." +msgstr "" +"سىستېمىنى بەلگىلەنگەن (بەلكىم كونا) نەشرگە يېڭىلاش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 +msgid "Manage optional features" +msgstr "تاللانما ئىقتىدارلارنى باشقۇر" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 +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)' بىرىكىنىڭ جەريانلىرىنى توڭلىتىش ياكى ئېچىش ئۈچۈن دەلىللەش تەلەپ " +"قىلىنىدۇ." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "كونا سىستېما يېڭىلانمىلىرىنى تازىلا" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "كونا سىستېما يېڭىلانمىلىرىنى تازىلاش ئۈچۈن دەلىللەش تەلەپ قىلىنىدۇ." diff --git a/po/uk.po b/po/uk.po index b19efa16cd526..5036f1a0d704a 100644 --- a/po/uk.po +++ b/po/uk.po @@ -5,12 +5,13 @@ # Daniel Korostil , 2014, 2016, 2018. # Yuri Chornoivan , 2019, 2020, 2021, 2022, 2023, 2024, 2026. # Dmytro Markevych , 2024. +# Deleted User , 2026. 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" -"Last-Translator: Yuri Chornoivan \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 14:02+0000\n" +"Last-Translator: Deleted User \n" "Language-Team: Ukrainian \n" "Language: uk\n" @@ -19,7 +20,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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -164,7 +165,7 @@ msgstr "" "Для керування ключами підписування для домашніх каталогів слід пройти " "розпізнавання." -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " @@ -174,33 +175,33 @@ msgstr "" "з'єднайте з комп'ютером відповідний пристрій зберігання даних або резервну " "файлову систему." -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "" "Надто часті спроби увійти до системи з боку користувача %s, повторіть спробу " "пізніше." -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "Пароль: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "" "Неправильно вказано пароль або пароля недостатньо для розпізнавання " "користувача %s." -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "Вибачте, повторіть спробу: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "Ключ відновлення: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " @@ -209,20 +210,20 @@ msgstr "" "Неправильно вказано пароль або пароля недостатньо для розпізнавання " "користувача %s." -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "Вибачте, ще раз введіть ключ відновлення: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "Повторіть спробу з паролем: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, c-format msgid "" "Password incorrect or not sufficient, and configured security token of user " @@ -231,27 +232,27 @@ msgstr "" "Пароль введено неправильно або пароля недостатньо, а налаштований жетон " "безпеки користувача %s не вставлено." -#: src/home/pam_systemd_home.c:418 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "PIN-код жетона безпеки: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "" "Будь ласка, виконайте фізичне розпізнавання на жетоні безпеки користувача %s." -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "Будь ласка, підтвердьте наявність жетона безпеки користувача %s." -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "Будь ласка, перевірте користувача на жетоні безпеки користувача %s." -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 msgid "" "Security token PIN is locked, please unlock it first. (Hint: Removal and re-" "insertion might suffice.)" @@ -259,84 +260,84 @@ msgstr "" "PIN-код жетона захисту заблоковано. Будь ласка, спочатку зніміть його " "блокування. (Підказка: іноді досить вийняти і знову вставити жетон.)" -#: src/home/pam_systemd_home.c:474 +#: src/home/pam_systemd_home.c:471 #, c-format msgid "Security token PIN incorrect for user %s." msgstr "Помилковий PIN-код жетона захисту для користувача %s." -#: src/home/pam_systemd_home.c:475 src/home/pam_systemd_home.c:494 -#: src/home/pam_systemd_home.c:513 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "Вибачте, повторіть введення PIN-коду жетона безпеки: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, c-format msgid "Security token PIN of user %s incorrect (only a few tries left!)" msgstr "" "Помилковий PIN-код жетона безпеки користувача %s (лишилося лише декілька " "спроб!)" -#: src/home/pam_systemd_home.c:512 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "" "Помилковий PIN-код жетона безпеки користувача %s (лишилася одна спроба!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "" "Зараз домашню теку користувача %s заблоковано. Будь ласка, спочатку " "розблокуйте її." -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "" "Забагато неуспішних спроб увійти з боку користувача %s. У доступі відмовлено." -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "Запис користувача заблоковано. Забороняємо доступ." -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "Запис користувача ще не набув чинності, забороняємо доступ." -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "Запис користувача вже втратив чинність, забороняємо доступ." -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "Запис користувача не є чинним, забороняємо доступ." -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "Забагато спроб увійти. Повторіть спробу за %s." -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "Потрібна зміна пароля." -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "Строк дії пароля вичерпано. Потрібна зміна." -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "" "Строк дії пароля вичерпано, але його неможливо змінити. Забороняємо вхід." -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "Строк дії пароля невдовзі буде вичерпано. Будь ласка, змініть його." @@ -911,32 +912,43 @@ msgstr "" "слід пройти розпізнавання." #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "Інспектування локальних віртуальних машин і контейнерів" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" +"Для інспектування локальних віртуальних машин і контейнерів слід пройти " +"розпізнавання." + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "Створення локальної віртуальної машини або контейнера" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "" "Для доступу до створення локальної віртуальної машини або контейнера слід " "пройти розпізнавання." -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "Реєстрація локальної віртуальної машини або контейнера" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "" "Для реєстрації локальної віртуальної машини або контейнера слід пройти " "розпізнавання." -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "Керування локальними образами віртуальних машин і контейнерів" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." @@ -944,6 +956,18 @@ msgstr "" "Для доступу до керування локальними образами віртуальних машин і контейнерів " "слід пройти розпізнавання." +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "Інспектування локальної віртуальної машини і образів контейнерів" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" +"Для інспектування локальної віртуальної машини і образів контейнерів слід " +"пройти розпізнавання." + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "Встановлення серверів NTP" @@ -1055,10 +1079,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" @@ -1210,27 +1236,60 @@ msgstr "Скидання статистики" msgid "Authentication is required to reset statistics." msgstr "Для скидання значень статистики до типових слід пройти розпізнавання." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "Витирання кешів DNS" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "Для витирання кешів DNS слід пройти розпізнавання." + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "Скидання можливостей сервера" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "Для скидання можливостей сервера слід пройти розпізнавання." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "Пошук оновлень системи" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "Для пошуку оновлень системи слід пройти розпізнавання." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "Скасування пошуку оновлень системи" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "Для скасування пошуку оновлень системи слід пройти розпізнавання." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "Встановлення оновлень системи" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "Для встановлення оновлень системи слід пройти розпізнавання." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "Скасування встановлення оновлень системи" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" +"Для скасування встановлення оновлень системи слід пройти розпізнавання." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "Встановлення специфічної версії системи" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 msgid "" "Authentication is required to update the system to a specific (possibly old) " "version." @@ -1238,19 +1297,41 @@ msgstr "" "Для встановлення специфічної (можливо застарілої) версії системи слід пройти " "розпізнавання." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:65 -msgid "Cleanup old system updates" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "Скасування встановлення певної версії системи" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" +"Для скасування оновлення системи до певної (можливо, застарілої) версії слід " +"пройти розпізнавання." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up old system updates" msgstr "Вилучення застарілих оновлень системи" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:66 -msgid "Authentication is required to cleanup old system updates." +#: src/sysupdate/org.freedesktop.sysupdate1.policy:99 +msgid "Authentication is required to clean up old system updates." msgstr "Для вилучення застарілих оновлень системи слід пройти розпізнавання." -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "Скасування вилучення застарілих оновлень системи" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" +"Для скасування вилучення застарілих оновлень системи слід пройти " +"розпізнавання." + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "Керування додатковими функціями" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "Для керування додатковими можливостями слід пройти розпізнавання." @@ -1349,3 +1430,31 @@ msgid "" msgstr "" "Для замороження або розмороження процесів модуля «$(unit)» слід пройти " "розпізнавання." + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "Будь ласка, підтвердьте наявність на жетоні захисту для розблокування." + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" +"Будь ласка, підтвердьте ідентичність користувача на жетоні захисту для " +"розблокування." + +#: src/shared/libfido2-util.c:936 +#, fuzzy, c-format +#| msgid "Please enter security token PIN:" +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "Будь ласка, введіть PIN жетона захисту:" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "Будь ласка, введіть PIN жетона захисту:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "Вилучення застарілих оновлень системи" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "" +#~ "Для вилучення застарілих оновлень системи слід пройти розпізнавання." diff --git a/po/zh_CN.po b/po/zh_CN.po index 70cebffc2fcbb..35ad8424ce74d 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -4,20 +4,22 @@ # Frank Hill , 2014. # Boyuan Yang <073plan@gmail.com>, 2015. # Jeff Bai , 2016. -# Charles Lee , 2020, 2021, 2022, 2023. -# Whired Planck , 2020. -# hanjinpeng , 2024. +# Charles Lee , 2020, 2021, 2022, 2023, 2026. +# Whired Planck , 2020, 2026. +# hanjinpeng , 2024, 2026. # lumingzh , 2024, 2025, 2026. -# z z <3397542367@qq.com>, 2025. -# Hang Li , 2025. -# Jesse Guo , 2025. +# z z <3397542367@qq.com>, 2025, 2026. +# Hang Li , 2025, 2026. +# Jesse Guo , 2025, 2026. # Zongyuan He , 2025. +# Poesty Li , 2026. +# Luke Na , 2026. 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-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-12 09:22+0000\n" +"Last-Translator: Luke Na \n" "Language-Team: Chinese (Simplified) \n" "Language: zh_CN\n" @@ -25,7 +27,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 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -81,23 +83,23 @@ msgstr "无速率限制地转储 systemd 状态需要身份验证。" #: src/home/org.freedesktop.home1.policy:13 msgid "Create a home area" -msgstr "创建家区域" +msgstr "创建主目录区域" #: src/home/org.freedesktop.home1.policy:14 msgid "Authentication is required to create a user's home area." -msgstr "创建用户家区域需要认证。" +msgstr "创建用户的主目录区域需要认证。" #: src/home/org.freedesktop.home1.policy:23 msgid "Remove a home area" -msgstr "移除家区域" +msgstr "移除主目录区域" #: src/home/org.freedesktop.home1.policy:24 msgid "Authentication is required to remove a user's home area." -msgstr "移除用户家区域需要认证。" +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 "" @@ -106,210 +108,211 @@ msgstr "需要身份验证方可将凭证与用户的主目录区域进行比对 #: src/home/org.freedesktop.home1.policy:43 msgid "Update a home area" -msgstr "更新家区域" +msgstr "更新主目录区域" # 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/home/org.freedesktop.home1.policy:44 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 "更新你的主目录区域" # 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/home/org.freedesktop.home1.policy:54 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 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 msgid "" "Authentication is required to change the password of a user's home area." -msgstr "更改用户的家区域密码需要认证。" +msgstr "更改用户的主目录区域密码需要认证。" #: src/home/org.freedesktop.home1.policy:83 msgid "Activate a home area" -msgstr "激活家区域" +msgstr "激活主目录区域" #: src/home/org.freedesktop.home1.policy:84 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 "管理家目录的签名密钥" +msgstr "管理主目录区域的签名密钥" #: src/home/org.freedesktop.home1.policy:94 msgid "Authentication is required to manage signing keys for home directories." -msgstr "管理家目录的签名密钥需要认证。" +msgstr "管理主目录区域的签名密钥需要认证。" -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, c-format msgid "" "Home of user %s is currently absent, please plug in the necessary storage " "device or backing file system." -msgstr "用户 %s 的家目录当前不存在,请插入必要的存储设备或下层文件系统。" +msgstr "" +"用户 %s 的主目录区域当前不可用,请插入必要的存储设备或挂载底层文件系统。" -#: src/home/pam_systemd_home.c:335 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "用户 %s 的登录尝试过于频繁,请稍后重试。" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "密码: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "用户 %s 的密码不正确或不足以完成认证。" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "抱歉,请重试: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "恢复密钥: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "用户 %s 的密码/恢复密钥不正确或不足以完成认证。" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "抱歉,请重新输入恢复密钥: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "请使用密码重试: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "安全令牌 PIN: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "请在用户 %s 的安全令牌上进行物理认证。" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "请确认安全令牌上存在用户 %s。" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." -msgstr "请验证用户 %s 的安全令牌上的用户。" +msgstr "请在用户 %s 的安全令牌上验证用户身份。" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "抱歉,请重试安全令牌 PIN: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, 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 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "用户 %s 的安全令牌 PIN 不正确(仅剩一次重试机会!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "用户 %s 的家目录目前已锁定,请先在本地解锁。" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "用户 %s 有过多不成功的登录尝试,已拒绝登录。" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "用户记录已阻止,禁止访问。" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "用户记录尚未生效,禁止访问。" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "用户记录不再有效,禁止访问。" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "用户记录无效,禁止访问。" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." -msgstr "过多登录请求,请在 %s 后重试。" +msgstr "登录次数过多,请 %s 后重试。" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "必须更改密码。" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "密码已过期,必须更改密码。" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "密码已经过期,但无法修改,拒绝登录。" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "密码将在不久后过期,请及时修改。" @@ -787,20 +790,20 @@ msgstr "在本地主机中获取 shell 需要认证。" #: src/machine/org.freedesktop.machine1.policy:64 msgid "Acquire a pseudo TTY in a local container" -msgstr "在本地容器中获取一个假 TTY" +msgstr "在本地容器中获取一个伪 TTY" #: src/machine/org.freedesktop.machine1.policy:65 msgid "" "Authentication is required to acquire a pseudo TTY in a local container." -msgstr "在本地容器中获取假 TTY 需要认证。" +msgstr "在本地容器中获取伪 TTY 需要认证。" #: src/machine/org.freedesktop.machine1.policy:74 msgid "Acquire a pseudo TTY on the local host" -msgstr "在本地主机中获取一个假 TTY" +msgstr "在本地主机上获取一个伪 TTY" #: src/machine/org.freedesktop.machine1.policy:75 msgid "Authentication is required to acquire a pseudo TTY on the local host." -msgstr "在本地主机中获取假 TTY 需要认证。" +msgstr "在本地主机中获取伪 TTY 需要认证。" #: src/machine/org.freedesktop.machine1.policy:84 msgid "Manage local virtual machines and containers" @@ -812,33 +815,52 @@ msgid "" msgstr "管理本地虚拟机和容器需要认证。" #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "检查本地虚拟机和容器" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "需要认证才能检查本地虚拟机和容器。" + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "创建本地虚拟机或容器" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "创建本地虚拟机或容器需要认证。" -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 msgid "Register a local virtual machine or container" msgstr "注册本地虚拟机或容器" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 msgid "" "Authentication is required to register a local virtual machine or container." msgstr "注册本地虚拟机或容器需要认证。" -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "管理本地虚拟机和容器的镜像" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "管理本地的虚拟机和容器镜像需要认证。" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "检查本地虚拟机和容器镜像" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "需要认证才能检查本地虚拟机和容器镜像。" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "设置 NTP 服务器" @@ -945,11 +967,13 @@ msgstr "重置 DNS 设置需要认证。" #: src/network/org.freedesktop.network1.policy:143 msgid "DHCP server sends force renew message" -msgstr "DHCP 服务器发送强制更新消息" +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,13 +1001,13 @@ msgstr "重新配置网络接口需要认证。" #: src/network/org.freedesktop.network1.policy:187 msgid "Specify whether persistent storage for systemd-networkd is available" -msgstr "指定systemd-networkd的永久存储是否可用" +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的永久存储是否可用需要认证。" +msgstr "指定 systemd-networkd 的永久存储是否可用需要认证。" #: src/network/org.freedesktop.network1.policy:198 msgid "Manage network links" @@ -1056,11 +1080,11 @@ msgstr "订阅查询结果需要认证。" #: src/resolve/org.freedesktop.resolve1.policy:154 msgid "Subscribe to DNS configuration" -msgstr "订阅DNS配置" +msgstr "订阅 DNS 配置" #: src/resolve/org.freedesktop.resolve1.policy:155 msgid "Authentication is required to subscribe to DNS configuration." -msgstr "订阅DNS配置需要认证。" +msgstr "订阅 DNS 配置需要认证。" #: src/resolve/org.freedesktop.resolve1.policy:165 msgid "Dump cache" @@ -1094,45 +1118,95 @@ msgstr "重置统计信息" msgid "Authentication is required to reset statistics." msgstr "重置统计信息需要认证。" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "清除 DNS 缓存" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "需要认证才能清除 DNS 缓存。" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "重置服务器功能" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "需要认证才能重置服务器功能。" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "检查系统更新" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "检查系统更新需要认证。" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "取消检查系统更新" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "需要认证才能取消检查系统更新。" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "安装系统更新" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "安装系统更新需要认证。" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "取消安装系统更新" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "需要认证才能取消安装系统更新。" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "安装指定系统版本" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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" +#: src/sysupdate/org.freedesktop.sysupdate1.policy:88 +msgid "Cancel installing specific system version" +msgstr "取消安装特定系统版本" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "需要认证才能取消将系统更新到特定(可能是旧的)版本。" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up 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:99 +msgid "Authentication is required to clean up old system updates." +msgstr "需要认证才能清理旧的系统更新。" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "取消清理旧的系统更新" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "需要认证才能取消清理旧的系统更新。" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "管理可选功能" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "管理可选功能需要身份验证。" @@ -1218,3 +1292,28 @@ msgstr "删除与 '$(unit)' 关联的文件和目录需要认证。" msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "冻结或解冻 '$(unit)' 单元进程需要认证。" + +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "请在安全令牌上确认在场以解锁。" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "请在安全令牌上验证用户身份以解锁。" + +#: src/shared/libfido2-util.c:936 +#, fuzzy, c-format +#| msgid "Please enter security token PIN:" +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "请输入安全令牌 PIN:" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "请输入安全令牌 PIN:" + +#~ msgid "Cleanup old system updates" +#~ msgstr "清理旧的系统更新" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "清理旧的系统更新需要认证。" diff --git a/po/zh_TW.po b/po/zh_TW.po index 683e08a985ba2..b494772443417 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -3,15 +3,15 @@ # Traditional Chinese translation for systemd. # Jeff Huang , 2015, 2016. # pan93412 , 2019. -# Cheng-Chia Tseng , 2023. +# Cheng-Chia Tseng , 2023, 2026. # hanjinpeng , 2024. -# hsu zangmen , 2025. +# hsu zangmen , 2025, 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-24 18:42+0000\n" -"PO-Revision-Date: 2025-04-09 02:53+0000\n" -"Last-Translator: hsu zangmen \n" +"POT-Creation-Date: 2026-06-01 11:49+0200\n" +"PO-Revision-Date: 2026-06-07 13:59+0000\n" +"Last-Translator: Cheng-Chia Tseng \n" "Language-Team: Chinese (Traditional) \n" "Language: zh_TW\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.10.4\n" +"X-Generator: Weblate 2026.6.1\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -148,156 +148,156 @@ msgstr "" msgid "Authentication is required to manage signing keys for home directories." msgstr "管理系統服務或其他單位需要驗證。" -#: src/home/pam_systemd_home.c:330 +#: src/home/pam_systemd_home.c:327 #, 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 +#: src/home/pam_systemd_home.c:332 #, c-format msgid "Too frequent login attempts for user %s, try again later." msgstr "用戶%s的登入嘗試過於頻繁,請稍後再試。" -#: src/home/pam_systemd_home.c:347 +#: src/home/pam_systemd_home.c:344 msgid "Password: " msgstr "密碼: " -#: src/home/pam_systemd_home.c:349 +#: src/home/pam_systemd_home.c:346 #, c-format msgid "Password incorrect or not sufficient for authentication of user %s." msgstr "用戶 %s 的密碼不正確或不足以進行身份驗證。" -#: src/home/pam_systemd_home.c:350 +#: src/home/pam_systemd_home.c:347 msgid "Sorry, try again: " msgstr "抱歉,請重試: " -#: src/home/pam_systemd_home.c:372 +#: src/home/pam_systemd_home.c:369 msgid "Recovery key: " msgstr "恢復金鑰: " -#: src/home/pam_systemd_home.c:374 +#: src/home/pam_systemd_home.c:371 #, c-format msgid "" "Password/recovery key incorrect or not sufficient for authentication of user " "%s." msgstr "用戶 %s 密碼/恢復金鑰不正確或不足以進行身份驗證。" -#: src/home/pam_systemd_home.c:375 +#: src/home/pam_systemd_home.c:372 msgid "Sorry, reenter recovery key: " msgstr "抱歉,重新輸入恢復金鑰: " -#: src/home/pam_systemd_home.c:395 +#: src/home/pam_systemd_home.c:392 #, 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 +#: src/home/pam_systemd_home.c:393 src/home/pam_systemd_home.c:396 msgid "Try again with password: " msgstr "使用密碼重試: " -#: src/home/pam_systemd_home.c:398 +#: src/home/pam_systemd_home.c:395 #, 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 +#: src/home/pam_systemd_home.c:415 msgid "Security token PIN: " msgstr "安全權杖 PIN: " -#: src/home/pam_systemd_home.c:435 +#: src/home/pam_systemd_home.c:432 #, c-format msgid "Please authenticate physically on security token of user %s." msgstr "請在用戶 %s 的安全權杖上進行物理身份驗證。" -#: src/home/pam_systemd_home.c:446 +#: src/home/pam_systemd_home.c:443 #, c-format msgid "Please confirm presence on security token of user %s." msgstr "請確認用戶 %s 的安全權杖是否存在。" -#: src/home/pam_systemd_home.c:457 +#: src/home/pam_systemd_home.c:454 #, c-format msgid "Please verify user on security token of user %s." msgstr "請在用戶 %s 的安全權杖上驗證用戶。" -#: src/home/pam_systemd_home.c:466 +#: src/home/pam_systemd_home.c:463 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 +#: src/home/pam_systemd_home.c:471 #, 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 +#: src/home/pam_systemd_home.c:472 src/home/pam_systemd_home.c:491 +#: src/home/pam_systemd_home.c:510 msgid "Sorry, retry security token PIN: " msgstr "抱歉,請重試安全權杖 PIN: " -#: src/home/pam_systemd_home.c:493 +#: src/home/pam_systemd_home.c:490 #, 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 +#: src/home/pam_systemd_home.c:509 #, c-format msgid "Security token PIN of user %s incorrect (only one try left!)" msgstr "用戶 %s 的安全權杖 PIN 不正確(只剩下一次嘗試!)" -#: src/home/pam_systemd_home.c:679 +#: src/home/pam_systemd_home.c:676 #, 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 +#: src/home/pam_systemd_home.c:678 #, c-format msgid "Home of user %s is currently locked, please unlock locally first." msgstr "用戶 %s 的家區域當前已鎖定,請先在本地解鎖。" -#: src/home/pam_systemd_home.c:715 +#: src/home/pam_systemd_home.c:712 #, c-format msgid "Too many unsuccessful login attempts for user %s, refusing." msgstr "用戶 %s 的登入嘗試失敗次數過多,正在拒絕。" -#: src/home/pam_systemd_home.c:1012 +#: src/home/pam_systemd_home.c:1021 msgid "User record is blocked, prohibiting access." msgstr "用戶記錄被封锁,禁止訪問。" -#: src/home/pam_systemd_home.c:1016 +#: src/home/pam_systemd_home.c:1025 msgid "User record is not valid yet, prohibiting access." msgstr "用戶記錄尚未生效,禁止訪問。" -#: src/home/pam_systemd_home.c:1020 +#: src/home/pam_systemd_home.c:1029 msgid "User record is not valid anymore, prohibiting access." msgstr "用戶記錄不再有效,禁止訪問。" -#: src/home/pam_systemd_home.c:1025 src/home/pam_systemd_home.c:1074 +#: src/home/pam_systemd_home.c:1034 src/home/pam_systemd_home.c:1083 msgid "User record not valid, prohibiting access." msgstr "用戶記錄無效,禁止訪問。" -#: src/home/pam_systemd_home.c:1035 +#: src/home/pam_systemd_home.c:1044 #, c-format msgid "Too many logins, try again in %s." msgstr "登錄次數太多,請在 %s 後重試。" -#: src/home/pam_systemd_home.c:1046 +#: src/home/pam_systemd_home.c:1055 msgid "Password change required." msgstr "需要更改密碼。" -#: src/home/pam_systemd_home.c:1050 +#: src/home/pam_systemd_home.c:1059 msgid "Password expired, change required." msgstr "密碼已過期,需要更改。" -#: src/home/pam_systemd_home.c:1056 +#: src/home/pam_systemd_home.c:1065 msgid "Password is expired, but can't change, refusing login." msgstr "密碼已過期,但無法更改,拒絕登入。" -#: src/home/pam_systemd_home.c:1060 +#: src/home/pam_systemd_home.c:1069 msgid "Password will expire soon, please change." msgstr "密碼即將過期,請更改。" @@ -794,35 +794,54 @@ msgid "" msgstr "管理本機虛擬機器及容器需要驗證。" #: src/machine/org.freedesktop.machine1.policy:95 +msgid "Inspect local virtual machines and containers" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:96 +msgid "" +"Authentication is required to inspect local virtual machines and containers." +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:105 msgid "Create a local virtual machine or container" msgstr "建立本機虛擬機器或容器" -#: src/machine/org.freedesktop.machine1.policy:96 +#: src/machine/org.freedesktop.machine1.policy:106 msgid "" "Authentication is required to create a local virtual machine or container." msgstr "建立本機虛擬機器或容器需要驗證。" -#: src/machine/org.freedesktop.machine1.policy:106 +#: src/machine/org.freedesktop.machine1.policy:116 #, fuzzy msgid "Register a local virtual machine or container" msgstr "建立本機虛擬機器或容器" -#: src/machine/org.freedesktop.machine1.policy:107 +#: src/machine/org.freedesktop.machine1.policy:117 #, fuzzy msgid "" "Authentication is required to register a local virtual machine or container." msgstr "建立本機虛擬機器或容器需要驗證。" -#: src/machine/org.freedesktop.machine1.policy:116 +#: src/machine/org.freedesktop.machine1.policy:126 msgid "Manage local virtual machine and container images" msgstr "管理本機虛擬機器及容器映像" -#: src/machine/org.freedesktop.machine1.policy:117 +#: src/machine/org.freedesktop.machine1.policy:127 msgid "" "Authentication is required to manage local virtual machine and container " "images." msgstr "管理本機虛擬機器及容器映像需要驗證。" +#: src/machine/org.freedesktop.machine1.policy:137 +msgid "Inspect local virtual machine and container images" +msgstr "" + +#: src/machine/org.freedesktop.machine1.policy:138 +msgid "" +"Authentication is required to inspect local virtual machine and container " +"images." +msgstr "" + #: src/network/org.freedesktop.network1.policy:22 msgid "Set NTP servers" msgstr "設定 NTP 伺服器" @@ -932,7 +951,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." +#, fuzzy +msgid "" +"Authentication is required to send a force renew message from the DHCP " +"server." msgstr "發送強制更新訊息需要身份驗證。" #: src/network/org.freedesktop.network1.policy:154 @@ -1076,45 +1098,95 @@ msgstr "重置統計" msgid "Authentication is required to reset statistics." msgstr "重設統計資料需要驗證。" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:35 +#: src/resolve/org.freedesktop.resolve1.policy:209 +msgid "Flush DNS caches" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:210 +msgid "Authentication is required to flush DNS caches." +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:220 +msgid "Reset server features" +msgstr "" + +#: src/resolve/org.freedesktop.resolve1.policy:221 +msgid "Authentication is required to reset server features." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:38 msgid "Check for system updates" msgstr "檢查系統更新" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:36 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:39 msgid "Authentication is required to check for system updates." msgstr "檢查系統更新需要驗證。" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:45 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:48 +msgid "Cancel checking for system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:49 +msgid "Authentication is required to cancel checking for system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:58 msgid "Install system updates" msgstr "安裝系統更新" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:46 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:59 msgid "Authentication is required to install system updates." msgstr "安裝系統更新需要驗證。" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:55 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:68 +msgid "Cancel installing system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:69 +msgid "Authentication is required to cancel installing system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:78 msgid "Install specific system version" msgstr "安裝特定的系統版本" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:56 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:79 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:88 +msgid "Cancel installing specific system version" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:89 +msgid "" +"Authentication is required to cancel updating the system to a specific " +"(possibly old) version." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:98 +msgid "Clean up 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:99 +msgid "Authentication is required to clean up old system updates." +msgstr "" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:75 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:108 +msgid "Cancel cleaning up old system updates" +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:109 +msgid "Authentication is required to cancel cleanup of old system updates." +msgstr "" + +#: src/sysupdate/org.freedesktop.sysupdate1.policy:118 msgid "Manage optional features" msgstr "管理選配功能" -#: src/sysupdate/org.freedesktop.sysupdate1.policy:76 +#: src/sysupdate/org.freedesktop.sysupdate1.policy:119 msgid "Authentication is required to manage optional features." msgstr "管理選購功能需要驗證。" @@ -1202,6 +1274,30 @@ msgid "" "Authentication is required to freeze or thaw the processes of '$(unit)' unit." msgstr "凍結或解凍「$(unit)」的程序需要身份驗證。" +#: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 +msgid "Please confirm presence on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:516 +msgid "Please verify user on security token to unlock." +msgstr "" + +#: src/shared/libfido2-util.c:936 +#, c-format +msgid "" +"Please enter security token PIN (remaining attempts before lock-out: %d):" +msgstr "" + +#: src/shared/libfido2-util.c:948 +msgid "Please enter security token PIN:" +msgstr "" + +#~ msgid "Cleanup old system updates" +#~ msgstr "清理舊有的系統更新" + +#~ msgid "Authentication is required to cleanup old system updates." +#~ msgstr "清理舊系統更新需要驗證。" + #~ msgid "" #~ "Authentication is required to halt the system while an application asked " #~ "to inhibit it." diff --git a/presets/90-systemd-initrd.preset b/presets/90-systemd-initrd.preset index e966a182f15ad..b7b966daca249 100644 --- a/presets/90-systemd-initrd.preset +++ b/presets/90-systemd-initrd.preset @@ -9,12 +9,14 @@ # Settings for systemd units distributed with systemd itself, specific to initrds. +enable systemd-confext-sysroot.service enable systemd-journald-audit.socket enable systemd-network-generator.service enable systemd-networkd.service enable systemd-networkd-wait-online.service enable systemd-pstore.service enable systemd-resolved.service +enable systemd-sysext-sysroot.service enable systemd-tpm2-clear.service disable console-getty.service diff --git a/presets/90-systemd.preset b/presets/90-systemd.preset index 56f9e9370613e..bce96b7a6d9d9 100644 --- a/presets/90-systemd.preset +++ b/presets/90-systemd.preset @@ -24,6 +24,7 @@ enable systemd-boot-update.service enable systemd-confext.service enable systemd-homed.service enable systemd-homed-activate.service +enable systemd-journalctl-metrics.socket enable systemd-journald-audit.socket enable systemd-mountfsd.socket enable systemd-network-generator.service @@ -31,8 +32,17 @@ enable systemd-networkd.service enable systemd-networkd-wait-online.service enable systemd-nsresourced.socket enable systemd-pstore.service +enable systemd-report-basic.socket +enable systemd-report-cgroup.socket +enable systemd-report-files.socket +enable systemd-report-sign-plain.socket +enable systemd-report-sign-tsm.socket +enable systemd-report.socket enable systemd-resolved.service enable systemd-sysext.service +enable systemd-sysupdate-notify-bootctl.socket +enable systemd-sysupdate-notify-pcrlock.socket +enable systemd-sysupdate-notify-sysext.socket enable systemd-timesyncd.service enable systemd-tpm2-clear.service enable systemd-userdbd.socket @@ -52,7 +62,9 @@ disable proc-sys-fs-binfmt_misc.mount disable syslog.socket disable systemd-boot-check-no-failures.service +disable systemd-confext-sysroot.service disable systemd-journal-gatewayd.* disable systemd-journal-remote.* disable systemd-journal-upload.* +disable systemd-sysext-sysroot.service disable systemd-time-wait-sync.service diff --git a/profile.d/70-systemd-shell-extra.sh b/profile.d/70-systemd-shell-extra.sh index 46dd82d9ce1b3..b47edc3e3625d 100644 --- a/profile.d/70-systemd-shell-extra.sh +++ b/profile.d/70-systemd-shell-extra.sh @@ -12,6 +12,15 @@ # credentials shell.prompt.prefix, shell.prompt.suffix and shell.welcome, and # are propagated into these environment variables by pam_systemd(8). +# This file is "activated" through systemd-tmpfiles which links it into +# /etc/profile.d/. To disable this, remove the +# /etc/profile.d/70-systemd-shell-extra.sh symlink and mask the +# 20-systemd-shell-extra.conf snippet (as root): +# +# test -h /etc/profile.d/70-systemd-shell-extra.sh && \ +# rm -v /etc/profile.d/70-systemd-shell-extra.sh && \ +# ln -s /dev/null /etc/tmpfiles.d/20-systemd-shell-extra.conf + if [ -n "${SHELL_PROMPT_PREFIX-}" ]; then if [ -n "${BASH_VERSION-}" ] && [ "$PS1" = "\\s-\\v\\\$ " ]; then PS1="[\u@\h \W]\\$ " diff --git a/profile.d/80-systemd-osc-context.sh b/profile.d/80-systemd-osc-context.sh index 7d4b9a77f1b39..ea8f456caf2ee 100644 --- a/profile.d/80-systemd-osc-context.sh +++ b/profile.d/80-systemd-osc-context.sh @@ -14,6 +14,15 @@ # specification for the shell prompt. For details see: # https://uapi-group.org/specifications/specs/osc_context/ +# This file is "activated" through systemd-tmpfiles which links it into +# /etc/profile.d/. To disable this, remove the +# /etc/profile.d/80-systemd-osc-context.sh symlink and mask the +# 20-systemd-osc-context.conf snippet (as root): +# +# test -h /etc/profile.d/80-systemd-osc-context.sh && \ +# rm -v /etc/profile.d/80-systemd-osc-context.sh && \ +# ln -s /dev/null /etc/tmpfiles.d/20-systemd-osc-context.conf + # Not bash? [ -n "${BASH_VERSION:-}" ] || return 0 @@ -21,6 +30,10 @@ # Treat missing $TERM same as "dumb". [ "${TERM:-dumb}" = "dumb" ] && return 0 +# We need promptvars, otherwise the prompt strings won't undergo parameter expansion +# and we'd print them literally +shopt -q promptvars || return 0 + __systemd_osc_context_escape() { # Escape according to the OSC 3008 spec. Since this requires shelling out # to 'sed' we'll only do it where it's strictly necessary, and skip it when diff --git a/ruff.toml b/ruff.toml index 6c0ec6ceb84bb..0795d9b9deb46 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,6 +1,19 @@ target-version = "py39" line-length = 109 -lint.select = ["E", "F", "I", "UP"] + +# This file is imported from an external project. +extend-exclude = ["tools/chromiumos/gen_autosuspend_rules.py"] + +[lint] +select = ["E", "F", "I", "Q", "UP"] +# E501: line-too-long +ignore = ["E501"] [format] -quote-style = "single" +# The formatter prefers double quotes for multiline quotes, +# Hence, let's make the formatter not change quotations. +quote-style = "preserve" + +[lint.flake8-quotes] +inline-quotes = "single" +multiline-quotes = "single" diff --git a/rules.d/60-dmi-id.rules b/rules.d/60-dmi-id.rules index 10b1fe000ca18..ecea74ec60d1c 100644 --- a/rules.d/60-dmi-id.rules +++ b/rules.d/60-dmi-id.rules @@ -2,24 +2,28 @@ ACTION=="remove", GOTO="dmi_end" SUBSYSTEM!="dmi", GOTO="dmi_end" +KERNEL!="id", GOTO="dmi_end" ENV{ID_SYS_VENDOR_IS_RUBBISH}!="1", ENV{ID_VENDOR}="$attr{sys_vendor}" ENV{ID_SYSFS_ATTRIBUTE_MODEL}=="", ENV{ID_PRODUCT_NAME_IS_RUBBISH}!="1", ENV{ID_MODEL}="$attr{product_name}" ENV{ID_SYSFS_ATTRIBUTE_MODEL}=="product_name", ENV{ID_MODEL}="$attr{product_name}" ENV{ID_SYSFS_ATTRIBUTE_MODEL}=="product_version", ENV{ID_MODEL}="$attr{product_version}" -# fallback to board information +# Fallback to board information ENV{ID_VENDOR}=="", ENV{ID_VENDOR}="$attr{board_vendor}" ENV{ID_MODEL}=="", ENV{ID_MODEL}="$attr{board_name}" -# stock keeping unit +# Stock keeping unit ENV{ID_PRODUCT_SKU_IS_RUBBISH}!="1", ENV{ID_SKU}="$attr{product_sku}" -# hardware version +# Hardware version ENV{ID_PRODUCT_VERSION_IS_RUBBISH}!="1", ENV{ID_HARDWARE_VERSION}="$attr{product_version}" ENV{ID_HARDWARE_VERSION}=="", ENV{ID_BOARD_VERSION_IS_RUBBISH}!="1", ENV{ID_HARDWARE_VERSION}="$attr{board_version}" -# chassis asset tag +# Chassis asset tag ENV{MODALIAS}!="", ATTR{chassis_asset_tag}!="", IMPORT{builtin}="hwdb '$attr{modalias}cat$attr{chassis_asset_tag}:'" ENV{ID_CHASSIS_ASSET_TAG_IS_RUBBISH}!="1", ENV{ID_CHASSIS_ASSET_TAG}="$attr{chassis_asset_tag}" +# Allow units to be ordered after the DMI device +TAG+="systemd" + LABEL="dmi_end" diff --git a/rules.d/60-tpm2-id.rules b/rules.d/60-tpm2-id.rules new file mode 100644 index 0000000000000..1e08f3b8e5b9d --- /dev/null +++ b/rules.d/60-tpm2-id.rules @@ -0,0 +1,10 @@ +# do not edit this file, it will be overwritten on update + +ACTION=="remove", GOTO="tpm2_id_end" +SUBSYSTEM!="tpmrm", GOTO="tpm2_id_end" +KERNEL!="tpmrm[0-9]*", GOTO="tpm2_id_end" + +IMPORT{program}="tpm2_id identify" +ENV{ID_TPM2_MODALIAS}!="", IMPORT{builtin}="hwdb 'tpm2:$env{ID_TPM2_MODALIAS}'" + +LABEL="tpm2_id_end" diff --git a/rules.d/65-integration.rules b/rules.d/65-integration.rules index 5f26416eb5cb5..5d78c94c387bd 100644 --- a/rules.d/65-integration.rules +++ b/rules.d/65-integration.rules @@ -7,16 +7,21 @@ ACTION=="remove", GOTO="integration_end" ENV{ID_BUS}=="", GOTO="integration_end" # ACPI, platform, PS/2, I2C, RMI, SPI and PCI devices: Internal by default. -ENV{ID_BUS}=="acpi|platform|i8042|i2c|rmi|spi|pci", ENV{ID_INTEGRATION}="internal" +ENV{ID_BUS}=="acpi|platform|i8042|i2c|rmi|spi|pci", ENV{ID_INTEGRATION}="internal", GOTO="libinput_integration_compat" # Bluetooth devices: External by default. -ENV{ID_BUS}=="bluetooth", ENV{ID_INTEGRATION}="external" +ENV{ID_BUS}=="bluetooth", ENV{ID_INTEGRATION}="external", GOTO="libinput_integration_compat" -# USB devices: Internal if it's connected to a fixed port, external to a removable or unknown. -ENV{ID_BUS}=="usb", DRIVERS=="usb", ATTRS{maxchild}=="0", ATTRS{removable}=="fixed", ENV{ID_INTEGRATION}="internal" -ENV{ID_BUS}=="usb", DRIVERS=="usb", ATTRS{maxchild}=="0", ATTRS{removable}=="removable|unknown", ENV{ID_INTEGRATION}="external" +# USB devices: Internal if it's connected to a fixed port, external to a removable and if it's unknown we use the main parent device attribute. +ENV{ID_BUS}!="usb", GOTO="usb_integration_end" +DRIVERS=="usb", ATTRS{maxchild}=="0", ATTRS{removable}=="fixed", ENV{ID_INTEGRATION}="internal", GOTO="libinput_integration_compat" +DRIVERS=="usb", ATTRS{maxchild}=="0", ATTRS{removable}=="removable", ENV{ID_INTEGRATION}="external", GOTO="libinput_integration_compat" +DRIVERS=="usb", ATTRS{devpath}!="0", ATTRS{removable}=="fixed", ENV{ID_INTEGRATION}="internal", GOTO="libinput_integration_compat" +DRIVERS=="usb", ATTRS{devpath}!="0", ATTRS{removable}=="removable|unknown", ENV{ID_INTEGRATION}="external", GOTO="libinput_integration_compat" +LABEL="usb_integration_end" # libinput compatibility, must be loaded before 70-touchpad.rules to allow hwdb quirks to override. +LABEL="libinput_integration_compat" ENV{ID_INPUT_TOUCHPAD}=="1", ENV{ID_INPUT_TOUCHPAD_INTEGRATION}="$env{ID_INTEGRATION}" LABEL="integration_end" diff --git a/rules.d/82-net-auto-link-local.rules b/rules.d/82-net-auto-link-local.rules index 88ac7bc1be05e..88e581c16ca8e 100644 --- a/rules.d/82-net-auto-link-local.rules +++ b/rules.d/82-net-auto-link-local.rules @@ -4,7 +4,7 @@ ACTION=="remove", GOTO="net_link_local_end" SUBSYSTEM!="net", GOTO="net_link_local_end" # Network interfaces for which only Link-Local communication (i.e. IPv4LL, …) -# makes sense, because they almost certainy will point to another host, not an +# makes sense, because they almost certainly will point to another host, not an # internet router. # (Note: matches against VID/PID go into 82-net-auto-link-local.hwdb instead) diff --git a/rules.d/99-systemd.rules.in b/rules.d/99-systemd.rules.in index bebc4d7d09cc3..fb7d6462afba9 100644 --- a/rules.d/99-systemd.rules.in +++ b/rules.d/99-systemd.rules.in @@ -89,4 +89,16 @@ SUBSYSTEM=="module", KERNEL=="configfs", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sy SUBSYSTEM=="tpmrm", KERNEL=="tpmrm[0-9]*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="tpm2.target" SUBSYSTEM=="tpm", KERNEL=="tpm[0-9]*", TAG+="systemd" +# If the kernel cannot parse the GPT partition table on the boot disk (e.g. due to a sector size +# mismatch on a CD-ROM booted via El Torito, or because the optical drive does not support partition +# scanning), trigger a loop device to expose the partitions. Restrict this to optical drives: other +# block devices that lack kernel partition scanning (most notably device-mapper) rely on userspace +# partition managers such as kpartx (see 66-kpartx.rules), and a competing loop device would only cause +# trouble. +SUBSYSTEM=="block", ENV{ID_CDROM}=="1", ENV{ID_PART_GPT_AUTO_ROOT_DISK_NEEDS_LOOP}=="1", \ + ENV{SYSTEMD_WANTS}+="systemd-loop@.service" + +# A device carrying the SYSTEMD_MACHINE_TAG property auto-tags the host with the specified machine tag. +ENV{SYSTEMD_MACHINE_TAG}=="?*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="systemd-machine-tag@$env{SYSTEMD_MACHINE_TAG}.service" + LABEL="systemd_end" diff --git a/rules.d/meson.build b/rules.d/meson.build index 839190658b40a..aa469ef7a7e58 100644 --- a/rules.d/meson.build +++ b/rules.d/meson.build @@ -23,6 +23,7 @@ rules = [ '60-persistent-v4l.rules', '60-sensor.rules', '60-serial.rules', + '60-tpm2-id.rules', '65-integration.rules', '70-camera.rules', '70-joystick.rules', diff --git a/shell-completion/bash/bootctl b/shell-completion/bash/bootctl index 31aaa1caceebd..16aafbe4ce784 100644 --- a/shell-completion/bash/bootctl +++ b/shell-completion/bash/bootctl @@ -35,12 +35,16 @@ _bootctl() { [STANDALONE]='-h --help --version -p --print-esp-path -x --print-boot-path --print-loader-path --print-stub-path -R --print-root-device -RR + --print-efi-architecture --no-pager --graceful -q --quiet --all-architectures --dry-run' [ARG]='--esp-path --boot-path --root --image --image-policy --install-source --variables --random-seed --make-entry-directory --entry-token --json - --efi-boot-option-description --secure-boot-auto-enroll --private-key - --private-key-source --certificate --certificate-source' + --efi-boot-option-description --efi-boot-option-description-with-device + --secure-boot-auto-enroll --private-key + --private-key-source --certificate --certificate-source + --oldest --keep-free --entry-title --entry-version --entry-commit + -X --extra --tries-left' ) if __contains_word "$prev" ${OPTS[ARG]}; then @@ -59,14 +63,14 @@ _bootctl() { --entry-token) comps="machine-id os-id os-image-id auto literal:" ;; - --image|--root) + --image|--root|-X|--extra) compopt -o nospace comps=$( compgen -A file -- "$cur" ) ;; --install-source) comps="image host auto" ;; - --random-seed|--variables|--secure-boot-auto-enroll) + --random-seed|--variables|--secure-boot-auto-enroll|--oldest|--efi-boot-option-description-with-device) comps="yes no" ;; --json) @@ -83,10 +87,10 @@ _bootctl() { fi local -A VERBS=( - [STANDALONE]='help status install update remove is-installed random-seed list set-timeout set-timeout-oneshot cleanup' - [BOOTENTRY]='set-default set-oneshot set-sysfail unlink' + [STANDALONE]='help status install update remove is-installed random-seed list set-timeout set-timeout-oneshot cleanup link-auto' + [BOOTENTRY]='set-default set-oneshot set-sysfail set-preferred unlink' [BOOLEAN]='reboot-to-firmware' - [FILE]='kernel-identify kernel-inspect' + [FILE]='kernel-identify kernel-inspect link' ) for ((i=0; i < COMP_CWORD; i++)); do diff --git a/shell-completion/bash/hostnamectl b/shell-completion/bash/hostnamectl index 0baffeed9c14a..a4ebe2591277e 100644 --- a/shell-completion/bash/hostnamectl +++ b/shell-completion/bash/hostnamectl @@ -67,7 +67,7 @@ _hostnamectl() { local -A VERBS=( [STANDALONE]='status' [ICONS]='icon-name' - [NAME]='hostname deployment location' + [NAME]='hostname deployment location tags' [CHASSIS]='chassis' ) diff --git a/shell-completion/bash/machinectl b/shell-completion/bash/machinectl index 78319d91a419c..d6627e6ba7e63 100644 --- a/shell-completion/bash/machinectl +++ b/shell-completion/bash/machinectl @@ -45,10 +45,10 @@ _machinectl() { local -A VERBS=( [STANDALONE]='list list-images clean pull-tar pull-raw list-transfers cancel-transfer import-fs' - [MACHINES]='status show start stop login shell enable disable poweroff reboot terminate kill + [MACHINES]='status show start stop login shell enable disable poweroff reboot pause resume terminate kill image-status show-image remove export-tar export-raw' [MACHINES_OR_FILES]='edit cat' - [MACHINE_ONLY]='clone rename set-limit' + [MACHINE_ONLY]='clone rename set-limit bind-volume unbind-volume' [READONLY]='read-only' [FILE]='import-tar import-raw' [MACHINES_AND_FILES]='copy-to copy-from bind' diff --git a/shell-completion/bash/meson.build b/shell-completion/bash/meson.build index 178986e17165b..cddf742059d51 100644 --- a/shell-completion/bash/meson.build +++ b/shell-completion/bash/meson.build @@ -36,6 +36,7 @@ foreach item : [ ['portablectl', 'ENABLE_PORTABLED'], ['resolvectl', 'ENABLE_RESOLVE'], ['run0', ''], + ['storagectl', ''], ['systemd-analyze', ''], ['systemd-cat', ''], ['systemd-cgls', ''], @@ -46,12 +47,14 @@ foreach item : [ ['systemd-delta', ''], ['systemd-detect-virt', ''], ['systemd-dissect', 'HAVE_BLKID'], + ['systemd-hwdb', 'ENABLE_HWDB'], ['systemd-id128', ''], ['systemd-nspawn', 'ENABLE_NSPAWN'], ['systemd-path', ''], ['systemd-resolve', 'ENABLE_RESOLVE'], ['systemd-run', ''], ['systemd-sysext', 'ENABLE_SYSEXT'], + ['systemd-sysinstall', 'ENABLE_SYSINSTALL'], ['systemd-vmspawn', 'ENABLE_VMSPAWN'], ['systemd-vpick', ''], ['timedatectl', 'ENABLE_TIMEDATED'], diff --git a/shell-completion/bash/networkctl b/shell-completion/bash/networkctl index c16768079e30e..fe186a49a2e7f 100644 --- a/shell-completion/bash/networkctl +++ b/shell-completion/bash/networkctl @@ -51,7 +51,7 @@ _networkctl() { local -A VERBS=( [STANDALONE]='label reload' - [LINKS]='status list lldp delete renew up down forcerenew reconfigure' + [LINKS]='status dhcp-lease list lldp delete renew up down forcerenew reconfigure' [FILES_OR_LINKS]='edit cat' [FILES]='mask unmask' [BOOL]='persistent-storage' diff --git a/shell-completion/bash/run0 b/shell-completion/bash/run0 index 1bba3b8145ac7..047f8182b2ca2 100644 --- a/shell-completion/bash/run0 +++ b/shell-completion/bash/run0 @@ -38,6 +38,7 @@ _run0() { --setenv --background ) local OPTS="${opts_with_values[*]} -h --help -V --version --no-ask-password --slice-inherit --empower" + OPTS="$OPTS -k --reset-timestamp -K --remove-timestamp -v --validate -n --non-interactive" local i for (( i=1; i <= COMP_CWORD; i++ )); do diff --git a/shell-completion/bash/storagectl b/shell-completion/bash/storagectl new file mode 100644 index 0000000000000..5aefc30ed162d --- /dev/null +++ b/shell-completion/bash/storagectl @@ -0,0 +1,74 @@ +# shellcheck shell=bash +# storagectl(1) completion -*- shell-script -*- +# 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. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see . + +__contains_word () { + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done +} + +_storagectl() { + local i verb comps + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} + + local -A OPTS=( + [STANDALONE]='-h --help --version --no-pager --no-legend --no-ask-password + --system --user' + [ARG]='--json' + ) + + if __contains_word "$prev" ${OPTS[ARG]}; then + case $prev in + --json) + comps=$( storagectl --json=help 2>/dev/null ) + ;; + esac + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + fi + + if [[ "$cur" = -* ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 + fi + + local -A VERBS=( + [STANDALONE]='volumes templates providers help' + ) + + 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 + comps=${VERBS[*]} + elif __contains_word "$verb" ${VERBS[STANDALONE]}; then + comps='' + fi + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 +} + +complete -F _storagectl storagectl diff --git a/shell-completion/bash/systemctl.in b/shell-completion/bash/systemctl.in index b1582252c30cc..319ca134ead7d 100644 --- a/shell-completion/bash/systemctl.in +++ b/shell-completion/bash/systemctl.in @@ -134,10 +134,10 @@ _systemctl () { --help -h --no-ask-password --no-block --legend=no --no-pager --no-reload --no-wall --now --quiet -q --system --user --version --runtime --recursive -r --firmware-setup --show-types --plain --failed --value --fail --dry-run --wait --no-warn --with-dependencies - --show-transaction -T --mkdir --read-only' + --show-transaction -T --mkdir --read-only --kernel-cmdline-reuse' [ARG]='--host -H --kill-whom --property -p -P --signal -s --type -t --state --job-mode --root --preset-mode -n --lines -o --output -M --machine --message --timestamp --check-inhibitors --what - --image --boot-loader-menu --boot-loader-entry --reboot-argument --drop-in' + --image --boot-loader-menu --boot-loader-entry --reboot-argument --kernel-cmdline --drop-in --when' ) if __contains_word "--user" ${COMP_WORDS[*]}; then @@ -202,6 +202,9 @@ _systemctl () { --boot-loader-entry) comps=$(systemctl --boot-loader-entry=help 2>/dev/null) ;; + --when) + comps='show cancel auto' + ;; esac COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) return 0 @@ -234,7 +237,7 @@ _systemctl () { list-timers list-units list-unit-files poweroff reboot rescue show-environment suspend get-default is-system-running preset-all list-automounts list-paths - enqueue-marked-jobs' + enqueue-marked' [FILE]='link switch-root' [TARGETS]='set-default' [MACHINES]='list-machines' diff --git a/shell-completion/bash/systemd-analyze b/shell-completion/bash/systemd-analyze index a863af7affd76..b194c74b63836 100644 --- a/shell-completion/bash/systemd-analyze +++ b/shell-completion/bash/systemd-analyze @@ -77,7 +77,7 @@ _systemd_analyze() { ) local -A VERBS=( - [STANDALONE]='time blame unit-files unit-paths exit-status compare-versions timestamp timespan pcrs nvpcrs srk has-tpm2 smbios11 chid image-policy' + [STANDALONE]='time blame unit-files unit-paths exit-status compare-versions timestamp timespan pcrs nvpcrs srk has-tpm2 identify-tpm2 smbios11 chid image-policy' [CRITICAL_CHAIN]='critical-chain' [DOT]='dot' [DUMP]='dump' diff --git a/shell-completion/bash/systemd-cryptenroll b/shell-completion/bash/systemd-cryptenroll index 6ae9bb3840a15..a24d99780281a 100644 --- a/shell-completion/bash/systemd-cryptenroll +++ b/shell-completion/bash/systemd-cryptenroll @@ -43,10 +43,14 @@ _systemd_cryptenroll() { local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword local -A OPTS=( [STANDALONE]='-h --help --version - --password --recovery-key --list-devices' + --password --recovery-key --list-devices + --unlock-empty --unlock-headless --firstboot' [ARG]='--unlock-key-file --unlock-fido2-device --unlock-tpm2-device + --prompt-suppress + --chrome + --mute-console --pkcs11-token-uri --fido2-credential-algorithm --fido2-device @@ -99,6 +103,12 @@ _systemd_cryptenroll() { --wipe-slot) comps='all empty password recovery pkcs11 fido2 tpm2' ;; + --prompt-suppress) + comps='password recovery pkcs11 fido2 tpm2' + ;; + --chrome|--mute-console) + comps='yes no' + ;; esac COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) return 0 diff --git a/shell-completion/bash/systemd-hwdb b/shell-completion/bash/systemd-hwdb new file mode 100644 index 0000000000000..a401bbf1c7f50 --- /dev/null +++ b/shell-completion/bash/systemd-hwdb @@ -0,0 +1,76 @@ +# shellcheck shell=bash +# systemd-hwdb(8) completion -*- shell-script -*- +# 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. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see . + +__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-sysinstall b/shell-completion/bash/systemd-sysinstall new file mode 100644 index 0000000000000..600e2aa939542 --- /dev/null +++ b/shell-completion/bash/systemd-sysinstall @@ -0,0 +1,90 @@ +# shellcheck shell=bash +# systemd-sysinstall(8) completion -*- shell-script -*- +# 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. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see . + +__contains_word() { + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done +} + +__get_block_devices() { + systemd-repart --list-devices 2>/dev/null +} + +_systemd_sysinstall() { + local comps + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword + local -A OPTS=( + [STANDALONE]='-h --help --version' + [ARG]='--welcome + --chrome + --erase + --confirm + --summary + --reboot + --variables + --mute-console + --copy-locale + --copy-keymap + --copy-timezone + --definitions + --kernel + --set-credential + --load-credential' + ) + + _init_completion || return + + if __contains_word "$prev" ${OPTS[ARG]}; then + case $prev in + --welcome|--chrome|--confirm|--summary|--reboot|--mute-console|--copy-locale|--copy-keymap|--copy-timezone) + comps='yes no' + ;; + --erase|--variables) + comps='yes no auto' + ;; + --definitions) + comps=$(compgen -A directory -- "$cur") + compopt -o filenames + ;; + --kernel|--load-credential) + comps=$(compgen -A file -- "$cur") + compopt -o filenames + ;; + --set-credential) + comps='' + ;; + esac + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 + fi + + if [[ "$cur" = -* ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) + return 0 + fi + + comps=$(__get_block_devices) + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + compopt -o filenames + return 0 +} + +complete -F _systemd_sysinstall systemd-sysinstall diff --git a/shell-completion/bash/systemd-vmspawn b/shell-completion/bash/systemd-vmspawn index b17586de14555..efb4b1059962c 100644 --- a/shell-completion/bash/systemd-vmspawn +++ b/shell-completion/bash/systemd-vmspawn @@ -30,35 +30,47 @@ _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 --bind-volume' [IMAGE_FORMAT]='--image-format' + [IMAGE_DISK_TYPE]='--image-disk-type' + [COCO]='--coco' ) _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[COCO]}; then + comps='no sev-snp' elif __contains_word "$prev" ${OPTS[ARG]}; then comps='' else diff --git a/shell-completion/bash/varlinkctl b/shell-completion/bash/varlinkctl index e6557ffb02036..cfde556840844 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' ) @@ -63,7 +63,7 @@ _varlinkctl() { fi local -A VERBS=( - [STANDALONE]='help list-registry' + [STANDALONE]='help list-registry list-sockets' [CALL]='call' [FILE]='info list-interfaces validate-idl' [ADDRESS_INTERFACES]='list-methods introspect' diff --git a/shell-completion/zsh/_bootctl b/shell-completion/zsh/_bootctl index 447612856197f..46956e55e3beb 100644 --- a/shell-completion/zsh/_bootctl +++ b/shell-completion/zsh/_bootctl @@ -24,10 +24,30 @@ _bootctl_set-oneshot() { _bootctl_comp_ids } +_bootctl_set-sysfail() { + _bootctl_comp_ids +} + +_bootctl_set-preferred() { + _bootctl_comp_ids +} + _bootctl_unlink() { _bootctl_comp_ids } +_bootctl_link() { + _files +} + +_bootctl_kernel-identify() { + _files +} + +_bootctl_kernel-inspect() { + _files +} + _bootctl_reboot-to-firmware() { local -a _completions _completions=( yes no ) @@ -49,10 +69,16 @@ _bootctl_reboot-to-firmware() { "list:List boot loader entries" "set-default:Set the default boot loader entry" "set-oneshot:Set the default boot loader entry only for the next boot" + "set-sysfail:Set boot loader entry used in case of a system failure" + "set-preferred:Set the preferred boot loader entry" "set-timeout:Set the menu timeout" "set-timeout-oneshot:Set the menu timeout for the next boot only" "unlink:Remove boot loader entry" + "link:Add boot loader entry" + "link-auto:Add boot loader entry for kernel and resources staged in /var/lib/systemd/uki/" "cleanup:Remove files in ESP not referenced in any boot entry" + "kernel-identify:Identify kernel image type" + "kernel-inspect:Print details about the kernel image" ) if (( CURRENT == 1 )); then _describe -t commands 'bootctl command' _bootctl_cmds || compadd "$@" @@ -74,10 +100,12 @@ _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]' \ '--dry-run[Dry run (unlink and cleanup)]' \ + '--oldest=[Delete oldest boot menu entry]:options:(yes no)' \ '--root=[Operate under the specified directory]:PATH' \ '--image=[Operate on the specified image]:PATH' \ '--install-source[Where to pick files when using --root=/--image=]:options:(image host auto)' \ diff --git a/shell-completion/zsh/_hostnamectl b/shell-completion/zsh/_hostnamectl index 76473937a0040..30facfb134c39 100644 --- a/shell-completion/zsh/_hostnamectl +++ b/shell-completion/zsh/_hostnamectl @@ -47,6 +47,11 @@ _hostnamectl_location() { fi } +(( $+functions[_hostnamectl_tags] )) || +_hostnamectl_tags() { + _message "machine tag" +} + (( $+functions[_hostnamectl_commands] )) || _hostnamectl_commands() { local -a _hostnamectl_cmds @@ -57,6 +62,7 @@ _hostnamectl_commands() { "chassis:Get/set chassis type for host" "deployment:Get/set deployment environment for host" "location:Get/set location for host" + "tags:Get/set machine tags for host" ) if (( CURRENT == 1 )); then _describe -t commands 'hostnamectl commands' _hostnamectl_cmds || compadd "$@" diff --git a/shell-completion/zsh/_machinectl b/shell-completion/zsh/_machinectl index d5f7aa9680d6c..d61a62ee27681 100644 --- a/shell-completion/zsh/_machinectl +++ b/shell-completion/zsh/_machinectl @@ -38,11 +38,15 @@ "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" "copy-from:Copy files from a container to the host" "bind:Bind mount a path from the host into a container" + "bind-volume:Attach a storage volume to a running machine" + "unbind-volume:Detach a storage volume from a running machine" "list-images:Show available container and VM images" "image-status:Show image details" @@ -77,7 +81,7 @@ start|enable|disable) _machinectl_images ;; - status|show|poweroff|reboot|terminate|kill) + status|show|poweroff|reboot|pause|resume|terminate|kill) _sd_machines ;; login|shell) @@ -113,6 +117,12 @@ else stop=1 fi ;; + bind-volume|unbind-volume) + if (( CURRENT == 2 )); then _sd_machines + elif (( CURRENT == 3 )); then _message "volume spec" + else stop=1 + fi ;; + read-only) if (( CURRENT == 2 )); then _machinectl_images elif (( CURRENT == 3 )); then _values 'read-only flag' 'true' 'false' diff --git a/shell-completion/zsh/_networkctl b/shell-completion/zsh/_networkctl index cf072c0fcbba6..c44b346949834 100644 --- a/shell-completion/zsh/_networkctl +++ b/shell-completion/zsh/_networkctl @@ -7,6 +7,7 @@ _networkctl_cmds=( 'list:List existing links' 'status:Show information about the specified links' + 'dhcp-lease:Show DHCP lease' 'lldp:Show Link Layer Discovery Protocol status' 'label:Show address labels' 'delete:Delete virtual netdevs' @@ -26,7 +27,7 @@ local -a _links cmd="${${_networkctl_cmds[(r)$words[1]:*]%%:*}}" case $cmd in - (list|status|up|down|cat|edit|lldp|delete|renew|forcerenew|reconfigure) + (list|status|dhcp-lease|up|down|cat|edit|lldp|delete|renew|forcerenew|reconfigure) for link in ${(f)"$(_call_program links networkctl list --no-legend)"}; do _links+=($link[(w)2]:$link); done if [[ -n "$_links" ]]; then _describe -t links 'links' _links $( [[ $cmd == (edit|cat) ]] && print -- -P@ ) diff --git a/shell-completion/zsh/_resolvectl b/shell-completion/zsh/_resolvectl index 3d28f1b410e04..dc9a9f1e06dfa 100644 --- a/shell-completion/zsh/_resolvectl +++ b/shell-completion/zsh/_resolvectl @@ -56,7 +56,7 @@ query:"Resolve domain names, IPv4 and IPv6 addresses" reset-server-features:"Flushes all feature level information the resolver has learned about specific servers" reset-statistics:"Resets the statistics counter shown in statistics to zero" - revert:"Revert the per-interfce DNS configuration" + revert:"Revert the per-interface DNS configuration" service:"Resolve DNS-SD and SRV services" show-cache:"Show the current cache contents" show-server-state:"Show servers state" diff --git a/shell-completion/zsh/_run0 b/shell-completion/zsh/_run0 index dc95486bef319..163f20c4157ff 100644 --- a/shell-completion/zsh/_run0 +++ b/shell-completion/zsh/_run0 @@ -37,22 +37,34 @@ _run0_slices() { } local -a args=( - '--no-ask-password[Do not query the user for authentication]' + '(-n --non-interactive --no-ask-password)'{-n,--non-interactive,--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 -/' + '(-k --reset-timestamp)'{-k,--reset-timestamp}'[Revoke temporary authorization for this terminal]' + '(-K --remove-timestamp)'{-K,--remove-timestamp}'[Revoke temporary authorizations for this user session]' + '(-v --validate)'{-v,--validate}'[Request authorization from polkit]' + '(-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/_storagectl b/shell-completion/zsh/_storagectl new file mode 100644 index 0000000000000..b2fdf595a1076 --- /dev/null +++ b/shell-completion/zsh/_storagectl @@ -0,0 +1,35 @@ +#compdef storagectl +# SPDX-License-Identifier: LGPL-2.1-or-later + +(( $+functions[_storagectl_commands] )) || _storagectl_commands() +{ + local -a _storagectl_cmds + _storagectl_cmds=( + "volumes:List storage volumes" + "templates:List storage volume templates" + "providers:List storage providers" + "help:Prints a short help text and exits" + ) + if (( CURRENT == 1 )); then + _describe -t commands 'storagectl command' _storagectl_cmds + else + local curcontext="$curcontext" + cmd="${${_storagectl_cmds[(r)$words[1]:*]%%:*}}" + if (( $+functions[_storagectl_$cmd] )); then + _storagectl_$cmd + else + _message "no more options" + fi + fi +} + +_arguments \ + '(- *)'{-h,--help}'[Prints a short help text and exits.]' \ + '(- *)--version[Prints a short version string and exits.]' \ + '--no-pager[Do not pipe output into a pager]' \ + '--no-legend[Do not show the headers and footers]' \ + '--no-ask-password[Do not query the user for authentication]' \ + '--json=[Show output as JSON]:mode:(pretty short off help)' \ + '--system[Operate in system mode]' \ + '--user[Operate in user mode]' \ + '*::storagectl command:_storagectl_commands' diff --git a/shell-completion/zsh/_systemctl.in b/shell-completion/zsh/_systemctl.in index 828e74c2af15d..39791c0a39d04 100644 --- a/shell-completion/zsh/_systemctl.in +++ b/shell-completion/zsh/_systemctl.in @@ -542,4 +542,6 @@ _arguments -s \ '--plain[When used with list-dependencies, print output as a list]' \ '--failed[Show failed units]' \ '--timestamp=[Change format of printed timestamps]:style:_systemctl_timestamp' \ + '--when=[Schedule halt/power-off/reboot/kexec action after a certain timestamp]:timestamp:(show cancel auto)' \ + '--kernel-cmdline-reuse[Reuse the current kernel command line when loading the kexec kernel]' \ '*::systemctl command:_systemctl_commands' diff --git a/shell-completion/zsh/_systemd-cryptenroll b/shell-completion/zsh/_systemd-cryptenroll new file mode 100644 index 0000000000000..e41b67ffdcc01 --- /dev/null +++ b/shell-completion/zsh/_systemd-cryptenroll @@ -0,0 +1,74 @@ +#compdef systemd-cryptenroll +# SPDX-License-Identifier: LGPL-2.1-or-later + +(( $+functions[_systemd-cryptenroll_devices] )) || +_systemd-cryptenroll_devices() { + local -a devices + devices=( ${(f)"$(_call_program devices systemd-cryptenroll --list-devices 2>/dev/null)"} ) + _describe -t devices 'block device' devices +} + +(( $+functions[_systemd-cryptenroll_fido2_device] )) || +_systemd-cryptenroll_fido2_device() { + _alternative \ + "special:special:($*)" \ + 'devices:FIDO2 device:_files -g "/dev/hidraw*(-c)"' +} + +(( $+functions[_systemd-cryptenroll_tpm2_device] )) || +_systemd-cryptenroll_tpm2_device() { + _alternative \ + "special:special:($*)" \ + 'devices:TPM2 device:_files -g "/dev/tpmrm*(-c)"' +} + +(( $+functions[_systemd-cryptenroll_wipe_slot] )) || +_systemd-cryptenroll_wipe_slot() { + _values -s , 'slot' all empty password recovery pkcs11 fido2 tpm2 +} + +(( $+functions[_systemd-cryptenroll_prompt_suppress] )) || +_systemd-cryptenroll_prompt_suppress() { + _values -s , 'type' password recovery pkcs11 fido2 tpm2 +} + +# Unlock methods are mutually exclusive with each other +local unlock='--unlock-empty --unlock-key-file --unlock-fido2-device --unlock-tpm2-device --unlock-headless' +# Enrollment operations are mutually exclusive with each other +local enroll='--password --recovery-key --pkcs11-token-uri --fido2-device --tpm2-device --tpm2-device-key --firstboot' + +_arguments -s \ + '(- *)'{-h,--help}'[Show this help]' \ + '(- *)--version[Show package version]' \ + '--no-pager[Do not pipe output into a pager]' \ + '(- *)--list-devices[List candidate block devices to operate on]' \ + '--wipe-slot=[Wipe specified slots]:slot:_systemd-cryptenroll_wipe_slot' \ + "($enroll)--firstboot[Interactively enroll a credential (first-boot wizard)]" \ + '--prompt-suppress=[Skip the --firstboot wizard if a slot of any listed type exists]:type:_systemd-cryptenroll_prompt_suppress' \ + '--chrome=[In first-boot mode, do not show colour bar at top and bottom of terminal]:boolean:(yes no)' \ + '--mute-console=[In first-boot mode, tell kernel/PID 1 to not write to the console while running]:boolean:(yes no)' \ + "($unlock)--unlock-empty[Use an empty password to unlock the volume]" \ + "($unlock)--unlock-key-file=[Use a file to unlock the volume]:key file:_files" \ + "($unlock)--unlock-fido2-device=[Use a FIDO2 device to unlock the volume]:FIDO2 device:_systemd-cryptenroll_fido2_device auto" \ + "($unlock)--unlock-tpm2-device=[Use a TPM2 device to unlock the volume]:TPM2 device:_systemd-cryptenroll_tpm2_device auto" \ + "($unlock)--unlock-headless[Try the 'headless' unlock mechanisms in turn]" \ + "($enroll)--password[Enroll a user-supplied password]" \ + "($enroll)--recovery-key[Enroll a recovery key]" \ + "($enroll)--pkcs11-token-uri=[Enroll a PKCS#11 security token or list them]:PKCS#11 token URI:(auto list pkcs11:)" \ + "($enroll)--fido2-device=[Enroll a FIDO2-HMAC security token or list them]:FIDO2 device:_systemd-cryptenroll_fido2_device auto list" \ + '--fido2-salt-file=[Use salt from a file instead of generating one]:salt file:_files' \ + '--fido2-parameters-in-header=[Whether to store FIDO2 parameters in the LUKS2 header]:boolean:(yes no)' \ + '--fido2-credential-algorithm=[Specify COSE algorithm for FIDO2 credential]:algorithm:(es256 rs256 eddsa)' \ + '--fido2-with-client-pin=[Whether to require entering a PIN to unlock the volume]:boolean:(yes no)' \ + '--fido2-with-user-presence=[Whether to require user presence to unlock the volume]:boolean:(yes no)' \ + '--fido2-with-user-verification=[Whether to require user verification to unlock the volume]:boolean:(yes no)' \ + "($enroll)--tpm2-device=[Enroll a TPM2 device or list them]:TPM2 device:_systemd-cryptenroll_tpm2_device auto list" \ + "($enroll)--tpm2-device-key=[Enroll a TPM2 device using its public key]:public key file:_files" \ + '--tpm2-seal-key-handle=[Specify handle of key to use for sealing]:handle:' \ + '--tpm2-pcrs=[Specify TPM2 PCRs to seal against]:PCRs:' \ + '--tpm2-public-key=[Enroll signed TPM2 PCR policy against PEM public key]:public key file:_files' \ + '--tpm2-public-key-pcrs=[Enroll signed TPM2 PCR policy for specified TPM2 PCRs]:PCRs:' \ + '--tpm2-signature=[Validate public key enrollment works with JSON signature file]:signature file:_files' \ + '--tpm2-pcrlock=[Specify pcrlock policy to lock against]:pcrlock file:_files' \ + '--tpm2-with-pin=[Whether to require entering a PIN to unlock the volume]:boolean:(yes no)' \ + '*::block device:_systemd-cryptenroll_devices' 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/_systemd-sysinstall b/shell-completion/zsh/_systemd-sysinstall new file mode 100644 index 0000000000000..039e4bf2a7912 --- /dev/null +++ b/shell-completion/zsh/_systemd-sysinstall @@ -0,0 +1,29 @@ +#compdef systemd-sysinstall +# SPDX-License-Identifier: LGPL-2.1-or-later + +(( $+functions[_systemd-sysinstall_devices] )) || +_systemd-sysinstall_devices() { + local -a _devices + _devices=( ${(f)"$(systemd-repart --list-devices 2>/dev/null)"} ) + _wanted devices expl 'block device' compadd -a _devices +} + +_arguments \ + '(- *)'{-h,--help}'[Show help text]' \ + '(- *)--version[Show package version]' \ + '--welcome=[Show welcome text]:boolean:(yes no)' \ + '--chrome=[Show colored bars at top and bottom of the terminal]:boolean:(yes no)' \ + '--erase=[Erase target disk before installation]:boolean:(yes no auto)' \ + '--confirm=[Query for confirmation before installation]:boolean:(yes no)' \ + '--summary=[Show summary before installation]:boolean:(yes no)' \ + '--reboot=[Reboot system after installation]:boolean:(yes no)' \ + '--variables=[Register installation in firmware variables]:boolean:(yes no auto)' \ + '--mute-console=[Mute kernel/PID 1 console output during installation]:boolean:(yes no)' \ + '--copy-locale=[Copy current locale to target system]:boolean:(yes no)' \ + '--copy-keymap=[Copy current keymap to target system]:boolean:(yes no)' \ + '--copy-timezone=[Copy current timezone to target system]:boolean:(yes no)' \ + '--definitions=[Find partition definitions in directory]:directory:_directories' \ + '--kernel=[Kernel image to install]:kernel image:_files' \ + '--set-credential=[Install a credential with a literal value]: : _message "ID:VALUE"' \ + '--load-credential=[Load credential from a file or AF_UNIX socket]: : _message "ID:PATH"' \ + '*::block device:_systemd-sysinstall_devices' 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..aa44beaeb3a21 100644 --- a/shell-completion/zsh/_varlinkctl +++ b/shell-completion/zsh/_varlinkctl @@ -34,6 +34,7 @@ _regex_words varlink-commands 'varlink command' \ 'introspect:show an interface definition:$varlink_interface' \ 'call:invoke a method:$varlink_call' \ 'validate-idl:validate an interface description:$varlink_idl' \ + 'list-sockets:List listening Varlink entrypoint sockets' \ 'help:show a help message' local -a varlinkcmd=( /$'[^\0]#\0'/ "$reply[@]" ) @@ -65,6 +66,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..edad8bcb3255d 100644 --- a/shell-completion/zsh/meson.build +++ b/shell-completion/zsh/meson.build @@ -30,17 +30,21 @@ foreach item : [ ['_run0', ''], ['_sd_bus_address', ''], ['_sd_hosts_or_user_at_host', ''], - ['_sd_machines', ''], + ['_sd_machines', 'ENABLE_MACHINED'], ['_sd_outputmodes', ''], ['_sd_unit_files', ''], + ['_storagectl', ''], ['_systemd', ''], ['_systemd-analyze', ''], + ['_systemd-cryptenroll', 'HAVE_LIBCRYPTSETUP'], ['_systemd-delta', ''], + ['_systemd-hwdb', 'ENABLE_HWDB'], ['_systemd-id128', ''], ['_systemd-inhibit', 'ENABLE_LOGIND'], ['_systemd-nspawn', ''], ['_systemd-path', ''], ['_systemd-run', ''], + ['_systemd-sysinstall', 'ENABLE_SYSINSTALL'], ['_systemd-tmpfiles', 'ENABLE_TMPFILES'], ['_timedatectl', 'ENABLE_TIMEDATED'], ['_udevadm', ''], diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index 13382b9994377..87242a3b08c7f 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,51 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts) > 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..896c6e54b36a5 100644 --- a/src/analyze/analyze-chid.c +++ b/src/analyze/analyze-chid.c @@ -6,9 +6,9 @@ #include "analyze.h" #include "analyze-chid.h" #include "ansi-color.h" -#include "chid-fundamental.h" +#include "chid.h" #include "device-util.h" -#include "edid-fundamental.h" +#include "edid.h" #include "efi-api.h" #include "errno-util.h" #include "escape.h" @@ -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..5c5177c3bea6a 100644 --- a/src/analyze/analyze-condition.c +++ b/src/analyze/analyze-condition.c @@ -98,7 +98,7 @@ static int verify_conditions(char **lines, RuntimeScope scope, const char *unit, return log_error_errno(r, "Failed to initialize manager: %m"); log_debug("Starting manager..."); - r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, root); + r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, /* named_listen_fds= */ NULL, root); if (r < 0) return r; @@ -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..dde31be0732c4 100644 --- a/src/analyze/analyze-nvpcrs.c +++ b/src/analyze/analyze-nvpcrs.c @@ -16,6 +16,7 @@ static int add_nvpcr_to_table(Tpm2Context **c, Table *t, const char *name) { _cleanup_free_ char *h = NULL; uint32_t nv_index = 0; + uint64_t priority = 0; if (c) { if (!*c) { r = tpm2_context_new_or_warn(/* device= */ NULL, c); @@ -24,7 +25,7 @@ static int add_nvpcr_to_table(Tpm2Context **c, Table *t, const char *name) { } _cleanup_(iovec_done) struct iovec digest = {}; - r = tpm2_nvpcr_read(*c, /* session= */ NULL, name, &digest, &nv_index); + r = tpm2_nvpcr_read(*c, /* session= */ NULL, name, &digest, &nv_index, &priority); if (r < 0) return log_error_errno(r, "Failed to read NvPCR '%s': %m", name); if (r > 0) { /* set? */ @@ -33,7 +34,7 @@ static int add_nvpcr_to_table(Tpm2Context **c, Table *t, const char *name) { return log_oom(); } } else { - r = tpm2_nvpcr_get_index(name, &nv_index); + r = tpm2_nvpcr_get_index(name, &nv_index, &priority); if (r < 0) return log_error_errno(r, "Failed to get NV index of NvPCR '%s': %m", name); } @@ -42,6 +43,7 @@ static int add_nvpcr_to_table(Tpm2Context **c, Table *t, const char *name) { t, TABLE_STRING, name, TABLE_UINT32_HEX_0x, nv_index, + TABLE_UINT64, priority, TABLE_STRING, h); if (r < 0) return table_log_add_error(r); @@ -50,27 +52,28 @@ 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."); - table = table_new("name", "nvindex", "value"); + table = table_new("name", "nvindex", "priority", "value"); if (!table) return log_oom(); (void) table_set_align_percent(table, table_get_cell(table, 0, 1), 100); + (void) table_set_align_percent(table, table_get_cell(table, 0, 2), 100); table_set_ersatz_string(table, TABLE_ERSATZ_DASH); - (void) table_set_sort(table, (size_t) 0); + (void) table_set_sort(table, (size_t) 2, (size_t) 0); if (!have_tpm2) - (void) table_hide_column_from_display(table, (size_t) 2); + (void) table_hide_column_from_display(table, (size_t) 3); if (strv_isempty(strv_skip(argv, 1))) { _cleanup_strv_free_ char **l = NULL; 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..6901956b8d18d 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" @@ -107,6 +108,8 @@ typedef struct SecurityInfo { Set *system_call_filter; mode_t _umask; + + RuntimeScope runtime_scope; } SecurityInfo; struct security_assessor { @@ -181,6 +184,13 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(SecurityInfo*, security_info_free); static bool security_info_runs_privileged(const SecurityInfo *i) { assert(i); + /* For the common --user case the manager runs as a regular user and cannot switch + * UID, so user-scope services never end up privileged. We deliberately do not + * handle root-owned user managers (user@0.service) here: in that narrow case the + * service really is privileged and will be mis-classified. */ + if (i->runtime_scope == RUNTIME_SCOPE_USER) + return false; + if (STRPTR_IN_SET(i->user, "0", "root")) return true; @@ -231,6 +241,12 @@ static int assess_user( } else if (info->user && !STR_IN_SET(info->user, "0", "root", "")) { d = "Service runs under a static non-root user identity"; b = 0; + } else if (info->runtime_scope == RUNTIME_SCOPE_USER) { + /* Same caveat as security_info_runs_privileged(): root-owned user + * managers (user@0.service) will be reported as non-root here, which is + * incorrect but matches the heuristic. */ + d = "Service runs under the calling user's identity"; + b = 0; } else { *ret_badness = 10; *ret_description = NULL; @@ -476,6 +492,12 @@ static int assess_remove_ipc( assert(ret_badness); assert(ret_description); + if (info->runtime_scope == RUNTIME_SCOPE_USER) { + *ret_badness = UINT64_MAX; + return strdup_to(ret_description, + "RemoveIPC= has no effect on per-user services"); + } + if (security_info_runs_privileged(info)) *ret_badness = UINT64_MAX; else @@ -557,6 +579,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 +598,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 +626,7 @@ static int assess_system_call_filter( uint64_t b; int r; - r = dlopen_libseccomp(); + r = DLOPEN_LIBSECCOMP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); if (r < 0) { *ret_badness = UINT64_MAX; *ret_description = NULL; @@ -2290,7 +2313,7 @@ static int property_read_device_allow( return sd_bus_message_exit_container(m); } -static int acquire_security_info(sd_bus *bus, const char *name, SecurityInfo *info, AnalyzeSecurityFlags flags) { +static int acquire_security_info(sd_bus *bus, const char *name, SecurityInfo *info, RuntimeScope scope, AnalyzeSecurityFlags flags) { static const struct bus_properties_map security_map[] = { { "AmbientCapabilities", "t", NULL, offsetof(SecurityInfo, ambient_capabilities) }, @@ -2336,7 +2359,7 @@ static int acquire_security_info(sd_bus *bus, const char *name, SecurityInfo *in { "RootImage", "s", NULL, offsetof(SecurityInfo, root_image) }, { "SupplementaryGroups", "as", NULL, offsetof(SecurityInfo, supplementary_groups) }, { "SystemCallArchitectures", "as", property_read_syscall_archs, 0 }, - { "SystemCallFilter", "(as)", property_read_system_call_filter, 0 }, + { "SystemCallFilter", "(bas)", property_read_system_call_filter, 0 }, { "Type", "s", NULL, offsetof(SecurityInfo, type) }, { "UMask", "u", property_read_umask, 0 }, { "User", "s", NULL, offsetof(SecurityInfo, user) }, @@ -2411,12 +2434,15 @@ static int acquire_security_info(sd_bus *bus, const char *name, SecurityInfo *in info->capability_bounding_set &= ~((UINT64_C(1) << CAP_MKNOD) | (UINT64_C(1) << CAP_SYS_RAWIO)); + info->runtime_scope = scope; + return 0; } static int analyze_security_one(sd_bus *bus, const char *name, Table *overview_table, + RuntimeScope scope, AnalyzeSecurityFlags flags, unsigned threshold, sd_json_variant *policy, @@ -2432,7 +2458,7 @@ static int analyze_security_one(sd_bus *bus, assert(bus); assert(name); - r = acquire_security_info(bus, name, info, flags); + r = acquire_security_info(bus, name, info, scope, flags); if (r == -EMEDIUMTYPE) /* Ignore this one because not loaded or Type is oneshot */ return 0; if (r < 0) @@ -2446,13 +2472,15 @@ static int analyze_security_one(sd_bus *bus, } /* Refactoring SecurityInfo so that it can make use of existing struct variables instead of reading from dbus */ -static int get_security_info(Unit *u, ExecContext *c, CGroupContext *g, SecurityInfo **ret_info) { +static int get_security_info(Unit *u, ExecContext *c, CGroupContext *g, RuntimeScope scope, SecurityInfo **ret_info) { assert(ret_info); _cleanup_(security_info_freep) SecurityInfo *info = security_info_new(); if (!info) return log_oom(); + info->runtime_scope = scope; + if (u) { if (u->id) { info->id = strdup(u->id); @@ -2576,7 +2604,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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) >= 0) { SET_FOREACH(key, c->syscall_archs) { const char *name; @@ -2652,6 +2680,7 @@ static int get_security_info(Unit *u, ExecContext *c, CGroupContext *g, Security } static int offline_security_check(Unit *u, + RuntimeScope scope, unsigned threshold, sd_json_variant *policy, PagerFlags pager_flags, @@ -2667,7 +2696,7 @@ static int offline_security_check(Unit *u, if (DEBUG_LOGGING) unit_dump(u, stdout, "\t"); - r = get_security_info(u, unit_get_exec_context(u), unit_get_cgroup_context(u), &info); + r = get_security_info(u, unit_get_exec_context(u), unit_get_cgroup_context(u), scope, &info); if (r < 0) return r; @@ -2711,7 +2740,7 @@ static int offline_security_checks( log_debug("Starting manager..."); - r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, root); + r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, /* named_listen_fds= */ NULL, root); if (r < 0) return r; @@ -2773,7 +2802,7 @@ static int offline_security_checks( } for (size_t i = 0; i < count; i++) - RET_GATHER(r, offline_security_check(units[i], threshold, policy, pager_flags, json_format_flags)); + RET_GATHER(r, offline_security_check(units[i], scope, threshold, policy, pager_flags, json_format_flags)); return r; } @@ -2853,7 +2882,7 @@ static int analyze_security(sd_bus *bus, flags |= ANALYZE_SECURITY_SHORT|ANALYZE_SECURITY_ONLY_LOADED|ANALYZE_SECURITY_ONLY_LONG_RUNNING; STRV_FOREACH(i, list) { - r = analyze_security_one(bus, *i, overview_table, flags, threshold, policy, pager_flags, json_format_flags); + r = analyze_security_one(bus, *i, overview_table, scope, flags, threshold, policy, pager_flags, json_format_flags); if (r < 0 && ret >= 0) ret = r; } @@ -2886,7 +2915,7 @@ static int analyze_security(sd_bus *bus, } else name = mangled; - r = analyze_security_one(bus, name, overview_table, flags, threshold, policy, pager_flags, json_format_flags); + r = analyze_security_one(bus, name, overview_table, scope, flags, threshold, policy, pager_flags, json_format_flags); if (r < 0 && ret >= 0) ret = r; } @@ -2904,7 +2933,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 +2948,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 +2960,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..54f9acbbff93e 100644 --- a/src/analyze/analyze-time-data.c +++ b/src/analyze/analyze-time-data.c @@ -56,7 +56,7 @@ int acquire_boot_times(sd_bus *bus, bool require_finished, BootTimes **ret) { { "InitRDGeneratorsFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_generators_finish_time) }, { "InitRDUnitsLoadStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_unitsload_start_time) }, { "InitRDUnitsLoadFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_unitsload_finish_time) }, - { "SoftRebootsCount", "t", NULL, offsetof(BootTimes, soft_reboots_count) }, + { "SoftRebootsCount", "u", NULL, offsetof(BootTimes, soft_reboots_count) }, {}, }; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -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; @@ -210,7 +212,7 @@ int pretty_boot_time(sd_bus *bus, char **ret) { return log_oom(); if (timestamp_is_set(t->initrd_time) && !strextend(&text, FORMAT_TIMESPAN(t->userspace_time - t->initrd_time, USEC_PER_MSEC), " (initrd) + ")) return log_oom(); - if (t->soft_reboots_count > 0 && strextendf(&text, "%s (soft reboot #%" PRIu64 ") + ", FORMAT_TIMESPAN(t->userspace_time, USEC_PER_MSEC), t->soft_reboots_count) < 0) + if (t->soft_reboots_count > 0 && strextendf(&text, "%s (soft reboot #%" PRIu32 ") + ", FORMAT_TIMESPAN(t->userspace_time, USEC_PER_MSEC), t->soft_reboots_count) < 0) return log_oom(); if (!strextend(&text, FORMAT_TIMESPAN(t->finish_time - t->userspace_time, USEC_PER_MSEC), " (userspace) ")) @@ -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-data.h b/src/analyze/analyze-time-data.h index b9dcc46d6c3c9..1924409556994 100644 --- a/src/analyze/analyze-time-data.h +++ b/src/analyze/analyze-time-data.h @@ -26,7 +26,7 @@ typedef struct BootTimes { usec_t initrd_unitsload_start_time; usec_t initrd_unitsload_finish_time; /* Not strictly a timestamp, but we are going to show it next to the other timestamps */ - uint64_t soft_reboots_count; + uint32_t soft_reboots_count; /* * If we're analyzing the user instance, all timestamps will be offset by its own start-up timestamp, 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..ad553078d4833 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); @@ -313,7 +315,7 @@ int verify_units( log_debug("Starting manager..."); - r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, root); + r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, /* named_listen_fds= */ NULL, root); if (r < 0) return r; 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..67a9135767503 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -3,7 +3,6 @@ Copyright © 2013 Simon Peeters ***/ -#include #include #include #include @@ -57,11 +56,14 @@ #include "calendarspec.h" #include "dissect-image.h" #include "extract-word.h" +#include "format-table.h" +#include "help-util.h" #include "image-policy.h" #include "log.h" #include "loop-util.h" #include "main-func.h" #include "mount-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -124,6 +126,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 +174,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,549 +197,456 @@ static int verb_transient_settings(int argc, char *argv[], void *userdata) { return 0; } -static int help(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *link = NULL, *dot_link = NULL; +static int help(void) { + static const char *const vgroups[] = { + "Boot Analysis", + "Dependency Analysis", + "Configuration Files and Search Paths", + "Enumerate OS Concepts", + "Expression Evaluation", + "Clock & Time", + "Unit & Service Analysis", + "Executable Analysis", + "TPM Operations", + }; + + Table *vtables[ELEMENTSOF(vgroups)] = {}; + CLEANUP_ELEMENTS(vtables, table_unref_array_clear); + _cleanup_(table_unrefp) Table *options = NULL; int r; pager_open(arg_pager_flags); - r = terminal_urlify_man("systemd-analyze", "1", &link); + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + r = verbs_get_help_table_group(vgroups[i], &vtables[i]); + if (r < 0) + return r; + } + + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; + + assert_cc(ELEMENTSOF(vtables) == 9); + (void) table_sync_column_widths(0, options, vtables[0], vtables[1], vtables[2], + vtables[3], vtables[4], vtables[5], vtables[6], + vtables[7], vtables[8]); + + help_cmdline("[OPTIONS...] COMMAND ..."); + help_abstract("Profile systemd, show unit dependencies, check unit files."); - /* Not using terminal_urlify_man() for this, since we don't want the "man page" text suffix in this case. */ - r = terminal_urlify("man:dot(1)", "dot(1)", &dot_link); + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + help_section(vgroups[i]); + r = table_print_or_warn(vtables[i]); + if (r < 0) + return r; + } + + help_section("Options"); + r = table_print_or_warn(options); if (r < 0) - return log_oom(); + return r; - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sProfile systemd, show unit dependencies, check unit files.%6$s\n" - "\n%3$sBoot Analysis:%4$s\n" - " [time] Print time required to boot the machine\n" - " blame Print list of running units ordered by\n" - " time to init\n" - " critical-chain [UNIT...] Print a tree of the time critical chain\n" - " of units\n" - "\n%3$sDependency Analysis:%4$s\n" - " plot Output SVG graphic showing service\n" - " initialization\n" - " dot [UNIT...] Output dependency graph in %7$s format\n" - " dump [PATTERN...] Output state serialization of service\n" - " manager\n" - "\n%3$sConfiguration Files and Search Paths:%4$s\n" - " cat-config NAME|PATH... Show configuration file and drop-ins\n" - " unit-files List files and symlinks for units\n" - " unit-paths List load directories for units\n" - "\n%3$sEnumerate OS Concepts:%4$s\n" - " exit-status [STATUS...] List exit status definitions\n" - " capability [CAP...] List capability definitions\n" - " syscall-filter [NAME...] List syscalls in seccomp filters\n" - " filesystems [NAME...] List known filesystems\n" - " architectures [NAME...] List known architectures\n" - " smbios11 List strings passed via SMBIOS Type #11\n" - " chid List local CHIDs\n" - " transient-settings TYPE... List transient settings for unit TYPE\n" - "\n%3$sExpression Evaluation:%4$s\n" - " condition CONDITION... Evaluate conditions and asserts\n" - " compare-versions VERSION1 [OP] VERSION2\n" - " Compare two version strings\n" - " image-policy POLICY... Analyze image policy string\n" - "\n%3$sClock & Time:%4$s\n" - " calendar SPEC... Validate repetitive calendar time\n" - " events\n" - " timestamp TIMESTAMP... Validate a timestamp\n" - " timespan SPAN... Validate a time span\n" - "\n%3$sUnit & Service Analysis:%4$s\n" - " verify FILE... Check unit files for correctness\n" - " security [UNIT...] Analyze security of unit\n" - " fdstore SERVICE... Show file descriptor store contents of service\n" - " malloc [D-BUS SERVICE...] Dump malloc stats of a D-Bus service\n" - " unit-gdb SERVICE Attach a debugger to the given running service\n" - " unit-shell SERVICE [Command]\n" - " Run command on the namespace of the service\n" - "\n%3$sExecutable Analysis:%4$s\n" - " inspect-elf FILE... Parse and print ELF package metadata\n" - " 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" - " 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" - "\n%3$sOptions:%4$s\n" - " --recursive-errors=MODE Control which units are verified\n" - " --offline=BOOL Perform a security review on unit file(s)\n" - " --threshold=N Exit with a non-zero status when overall\n" - " exposure level is over threshold value\n" - " --security-policy=PATH Use custom JSON security policy instead\n" - " of built-in one\n" - " --json=pretty|short|off Generate JSON output of the security\n" - " analysis table, or plot's raw time data\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Disable column headers and hints in plot\n" - " with either --table or --json=\n" - " --system Operate on system systemd instance\n" - " --user Operate on user systemd instance\n" - " --global Operate on global user configuration\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --order Show only order in the graph\n" - " --require Show only requirement in the graph\n" - " --from-pattern=GLOB Show only origins in the graph\n" - " --to-pattern=GLOB Show only destinations in the graph\n" - " --fuzz=SECONDS Also print services which finished SECONDS\n" - " earlier than the latest in the branch\n" - " --man[=BOOL] Do [not] check for existence of man pages\n" - " --generators[=BOOL] Do [not] run unit generators\n" - " (requires privileges)\n" - " --instance=NAME Specify fallback instance name for template units\n" - " --iterations=N Show the specified number of iterations\n" - " --base-time=TIMESTAMP Calculate calendar times relative to\n" - " specified time\n" - " --profile=name|PATH Include the specified profile in the\n" - " security review of the unit(s)\n" - " --unit=UNIT Evaluate conditions and asserts of unit\n" - " --table Output plot's raw time data as a table\n" - " --scale-svg=FACTOR Stretch x-axis of plot by FACTOR (default: 1.0)\n" - " --detailed Add more details to SVG plot,\n" - " e.g. show activation timestamps\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -q --quiet Do not emit hints\n" - " --tldr Skip comments and empty lines\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" - " -m --mask Parse parameter as numeric capability mask\n" - " --drm-device=PATH Use this DRM device sysfs path to get EDID\n" - " --debugger=DEBUGGER Use the given debugger\n" - " -A --debugger-arguments=ARGS\n" - " Pass the given arguments to the debugger\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal(), - dot_link); - - /* When updating this list, including descriptions, apply changes to - * shell-completion/bash/systemd-analyze and shell-completion/zsh/_systemd-analyze too. */ + help_man_page_reference("systemd-analyze", "1"); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_ORDER, - ARG_REQUIRE, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_SYSTEM, - ARG_USER, - ARG_GLOBAL, - ARG_DOT_FROM_PATTERN, - ARG_DOT_TO_PATTERN, - ARG_FUZZ, - ARG_NO_PAGER, - ARG_MAN, - ARG_GENERATORS, - ARG_INSTANCE, - ARG_ITERATIONS, - ARG_BASE_TIME, - ARG_RECURSIVE_ERRORS, - ARG_OFFLINE, - ARG_THRESHOLD, - ARG_SECURITY_POLICY, - ARG_JSON, - ARG_PROFILE, - ARG_TABLE, - ARG_NO_LEGEND, - ARG_TLDR, - ARG_SCALE_FACTOR_SVG, - ARG_DETAILED_SVG, - ARG_DRM_DEVICE_PATH, - ARG_DEBUGGER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "quiet", no_argument, NULL, 'q' }, - { "order", no_argument, NULL, ARG_ORDER }, - { "require", no_argument, NULL, ARG_REQUIRE }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "recursive-errors" , required_argument, NULL, ARG_RECURSIVE_ERRORS }, - { "offline", required_argument, NULL, ARG_OFFLINE }, - { "threshold", required_argument, NULL, ARG_THRESHOLD }, - { "security-policy", required_argument, NULL, ARG_SECURITY_POLICY }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "global", no_argument, NULL, ARG_GLOBAL }, - { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN }, - { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN }, - { "fuzz", required_argument, NULL, ARG_FUZZ }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "man", optional_argument, NULL, ARG_MAN }, - { "generators", optional_argument, NULL, ARG_GENERATORS }, - { "instance", required_argument, NULL, ARG_INSTANCE }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "iterations", required_argument, NULL, ARG_ITERATIONS }, - { "base-time", required_argument, NULL, ARG_BASE_TIME }, - { "unit", required_argument, NULL, 'U' }, - { "json", required_argument, NULL, ARG_JSON }, - { "profile", required_argument, NULL, ARG_PROFILE }, - { "table", optional_argument, NULL, ARG_TABLE }, - { "no-legend", optional_argument, NULL, ARG_NO_LEGEND }, - { "tldr", no_argument, NULL, ARG_TLDR }, - { "mask", no_argument, NULL, 'm' }, - { "scale-svg", required_argument, NULL, ARG_SCALE_FACTOR_SVG }, - { "detailed", no_argument, NULL, ARG_DETAILED_SVG }, - { "drm-device", required_argument, NULL, ARG_DRM_DEVICE_PATH }, - { "debugger", required_argument, NULL, ARG_DEBUGGER }, - { "debugger-arguments", required_argument, NULL, 'A' }, - {} - }; - - bool reorder = false; - int r, c, unit_shell = -1; +VERB_COMMON_HELP_HIDDEN(help); + +/* When updating this list, including descriptions, apply changes to + * shell-completion/bash/systemd-analyze and shell-completion/zsh/_systemd-analyze too. */ + +VERB_GROUP("Boot Analysis"); +VERB_SCOPE(, verb_time, "time", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Print time required to boot the machine"); +VERB_SCOPE(, verb_blame, "blame", NULL, VERB_ANY, 1, 0, + "Print list of running units ordered by time to init"); +VERB_SCOPE(, verb_critical_chain, "critical-chain", "[UNIT...]", VERB_ANY, VERB_ANY, 0, + "Print a tree of the time critical chain of units"); + +VERB_GROUP("Dependency Analysis"); +VERB_SCOPE(, verb_plot, "plot", NULL, VERB_ANY, 1, 0, + "Output SVG graphic showing service initialization"); +VERB_SCOPE(, verb_dot, "dot", "[UNIT...]", VERB_ANY, VERB_ANY, 0, + "Output dependency graph in dot(1) format"); +VERB_SCOPE(, verb_dump, "dump", "[PATTERN...]", VERB_ANY, VERB_ANY, 0, + "Output state serialization of service manager"); + +VERB_GROUP("Configuration Files and Search Paths"); +VERB_SCOPE(, verb_cat_config, "cat-config", "NAME|PATH...", 2, VERB_ANY, 0, + "Show configuration file and drop-ins"); +VERB_SCOPE(, verb_unit_files, "unit-files", NULL, VERB_ANY, VERB_ANY, 0, + "List files and symlinks for units"); +VERB_SCOPE(, verb_unit_paths, "unit-paths", NULL, 1, 1, 0, + "List load directories for units"); + +VERB_GROUP("Enumerate OS Concepts"); +VERB_SCOPE(, verb_exit_status, "exit-status", "[STATUS...]", VERB_ANY, VERB_ANY, 0, + "List exit status definitions"); +VERB_SCOPE(, verb_capabilities, "capability", "[CAP...]", VERB_ANY, VERB_ANY, 0, + "List capability definitions"); +VERB_SCOPE(, verb_syscall_filters, "syscall-filter", "[NAME...]", VERB_ANY, VERB_ANY, 0, + "List syscalls in seccomp filters"); +VERB_SCOPE(, verb_filesystems, "filesystems", "[NAME...]", VERB_ANY, VERB_ANY, 0, + "List known filesystems"); +VERB_SCOPE(, verb_architectures, "architectures", "[NAME...]", VERB_ANY, VERB_ANY, 0, + "List known architectures"); +VERB_SCOPE(, verb_smbios11, "smbios11", NULL, VERB_ANY, 1, 0, + "List strings passed via SMBIOS Type #11"); +VERB_SCOPE(, verb_chid, "chid", NULL, VERB_ANY, VERB_ANY, 0, + "List local CHIDs"); +VERB(verb_transient_settings, "transient-settings", "TYPE...", 2, VERB_ANY, 0, + "List transient settings for unit TYPE"); + +VERB_GROUP("Expression Evaluation"); +VERB_SCOPE(, verb_condition, "condition", "CONDITION...", VERB_ANY, VERB_ANY, 0, + "Evaluate conditions and asserts"); +VERB_SCOPE(, verb_compare_versions, "compare-versions", "V1 [OP] V2", 3, 4, 0, + "Compare two version strings"); +VERB_SCOPE(, verb_image_policy, "image-policy", "POLICY...", 2, 2, 0, + "Analyze image policy string"); + +VERB_GROUP("Clock & Time"); +VERB_SCOPE(, verb_calendar, "calendar", "SPEC...", 2, VERB_ANY, 0, + "Validate repetitive calendar time events"); +VERB_SCOPE(, verb_timestamp, "timestamp", "TIMESTAMP...", 2, VERB_ANY, 0, + "Validate a timestamp"); +VERB_SCOPE(, verb_timespan, "timespan", "SPAN...", 2, VERB_ANY, 0, + "Validate a time span"); + +VERB_GROUP("Unit & Service Analysis"); +VERB_SCOPE(, verb_verify, "verify", "FILE...", 2, VERB_ANY, 0, + "Check unit files for correctness"); +VERB_SCOPE(, verb_security, "security", "[UNIT...]", VERB_ANY, VERB_ANY, 0, + "Analyze security of unit"); +VERB_SCOPE(, verb_fdstore, "fdstore", "SERVICE...", 2, VERB_ANY, 0, + "Show file descriptor store contents of service"); +VERB_SCOPE(, verb_malloc, "malloc", "[D-BUS SERVICE...]", VERB_ANY, VERB_ANY, 0, + "Dump malloc stats of a D-Bus service"); +VERB_SCOPE(, verb_unit_gdb, "unit-gdb", "SERVICE", 2, VERB_ANY, 0, + "Attach a debugger to the given running service"); +VERB_SCOPE(, verb_unit_shell, "unit-shell", "SERVICE [COMMAND ...]", 2, VERB_ANY, 0, + "Run command on the namespace of the service"); + +VERB_GROUP("Executable Analysis"); +VERB_SCOPE(, verb_elf_inspection, "inspect-elf", "FILE...", 2, VERB_ANY, 0, + "Parse and print ELF package metadata"); +VERB_SCOPE(, verb_dlopen_metadata, "dlopen-metadata", "FILE", 2, 2, 0, + "Parse and print ELF dlopen metadata"); + +VERB_GROUP("TPM Operations"); +VERB_SCOPE(, verb_has_tpm2, "has-tpm2", NULL, VERB_ANY, 1, 0, + "Report whether TPM2 support is available"); +VERB_SCOPE(, verb_identify_tpm2, "identify-tpm2", NULL, VERB_ANY, 1, 0, + "Show TPM2 vendor information"); +VERB_SCOPE(, verb_pcrs, "pcrs", "[PCR...]", VERB_ANY, VERB_ANY, 0, + "Show TPM2 PCRs and their names"); +VERB_SCOPE(, verb_nvpcrs, "nvpcrs", "[NVPCR...]", VERB_ANY, VERB_ANY, 0, + "Show additional TPM2 PCRs stored in NV indexes"); +VERB_SCOPE(, verb_srk, "srk", "[>FILE]", VERB_ANY, 1, 0, + "Write TPM2 SRK (to FILE)"); + +/* The following are deprecated and not shown in --help. */ +VERB_SCOPE(, verb_log_control, "log-level", NULL, VERB_ANY, 2, 0, /* help= */ NULL); +VERB_SCOPE(, verb_log_control, "log-target", NULL, VERB_ANY, 2, 0, /* help= */ NULL); +VERB_SCOPE(, verb_log_control, "set-log-level", NULL, 2, 2, 0, /* help= */ NULL); +VERB_SCOPE(, verb_log_control, "get-log-level", NULL, VERB_ANY, 1, 0, /* help= */ NULL); +VERB_SCOPE(, verb_log_control, "set-log-target", NULL, 2, 2, 0, /* help= */ NULL); +VERB_SCOPE(, verb_log_control, "get-log-target", NULL, VERB_ANY, 1, 0, /* help= */ NULL); +VERB_SCOPE(, verb_service_watchdogs, "service-watchdogs", NULL, VERB_ANY, 2, 0, /* help= */ NULL); + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); + assert(ret_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; - - for (;;) { - static const char option_string[] = "-hqH:M:U:mA:"; - - c = getopt_long(argc, argv, option_string + reorder, options, NULL); - if (c < 0) - break; + /* For "unit-shell" the switches specified after the service name are part of the commandline + * to execute and are not processed by us. For other verbs, we consume all options as usual. + * To make this work, start with mode==OPTION_PARSER_RETURN_POSITIONAL_ARGS and switch to + * either OPTION_PARSER_STOP_AT_FIRST_NONOPTION or OPTION_PARSER_NORMAL after we've seen + * the verb. */ + OptionParser opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + const char *verb = NULL; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 1: /* getopt_long() returns 1 if "-" was the first character of the option string, and a - * non-option argument was discovered. */ - - assert(!reorder); - - /* We generally are fine with the fact that getopt_long() reorders the command line, and looks - * for switches after the main verb. However, for "unit-shell" we really don't want that, since we - * want that switches specified after the service name are passed to the program to execute, - * and not processed by us. To make this possible, we'll first invoke getopt_long() with - * reordering disabled (i.e. with the "-" prefix in the option string), looking for the first - * non-option parameter. If it's the verb "unit-shell" we remember its position and continue - * processing options. In this case, as soon as we hit the next non-option argument we found - * the service name, and stop further processing. If the first non-option argument is any other - * verb than "unit-shell" we switch to normal reordering mode and continue processing arguments - * normally. */ - - if (unit_shell >= 0) { - optind--; /* don't process this argument, go one step back */ - goto done; - } - if (streq(optarg, "unit-shell")) - /* Remember the position of the "unit_shell" verb, and continue processing normally. */ - unit_shell = optind - 1; - else { - int saved_optind; - - /* Ok, this is some other verb. In this case, turn on reordering again, and continue - * processing normally. */ - reorder = true; - - /* We changed the option string. getopt_long() only looks at it again if we invoke it - * at least once with a reset option index. Hence, let's reset the option index here, - * then invoke getopt_long() again (ignoring what it has to say, after all we most - * likely already processed it), and the bump the option index so that we read the - * intended argument again. */ - saved_optind = optind; - optind = 0; - (void) getopt_long(argc, argv, option_string + reorder, options, NULL); - optind = saved_optind - 1; /* go one step back, process this argument again */ - } + OPTION_POSITIONAL: + verb = opts.arg; + assert(opts.mode == OPTION_PARSER_RETURN_POSITIONAL_ARGS); + if (streq(verb, "unit-shell")) + opts.mode = OPTION_PARSER_STOP_AT_FIRST_NONOPTION; + else + opts.mode = OPTION_PARSER_NORMAL; break; - case 'h': - return help(0, NULL, NULL); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'q': + OPTION('q', "quiet", NULL, "Do not emit hints"): arg_quiet = true; break; - case ARG_RECURSIVE_ERRORS: - if (streq(optarg, "help")) + OPTION_LONG("recursive-errors", "MODE", "Control which units are verified"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(recursive_errors, RecursiveErrors, _RECURSIVE_ERRORS_MAX); - r = recursive_errors_from_string(optarg); + r = recursive_errors_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Unknown mode passed to --recursive-errors='%s'.", optarg); + return log_error_errno(r, "Unknown mode passed to --recursive-errors='%s'.", opts.arg); arg_recursive_errors = r; 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(opts.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(opts.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(opts.arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Operate on system systemd instance"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Operate on user systemd instance"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_GLOBAL: + OPTION_LONG("global", NULL, "Operate on global user configuration"): arg_runtime_scope = RUNTIME_SCOPE_GLOBAL; break; - case ARG_ORDER: + OPTION_LONG("order", NULL, "Show only order in the graph"): arg_dot = DEP_ORDER; break; - case ARG_REQUIRE: + OPTION_LONG("require", NULL, "Show only requirement in the graph"): arg_dot = DEP_REQUIRE; break; - case ARG_DOT_FROM_PATTERN: - if (strv_extend(&arg_dot_from_patterns, optarg) < 0) + OPTION_LONG("from-pattern", "GLOB", "Show only origins in the graph"): + if (strv_extend(&arg_dot_from_patterns, opts.arg) < 0) return log_oom(); - break; - case ARG_DOT_TO_PATTERN: - if (strv_extend(&arg_dot_to_patterns, optarg) < 0) + OPTION_LONG("to-pattern", "GLOB", "Show only destinations in the graph"): + if (strv_extend(&arg_dot_to_patterns, opts.arg) < 0) return log_oom(); - break; - case ARG_FUZZ: - r = parse_sec(optarg, &arg_fuzz); + OPTION_LONG("fuzz", "SECONDS", + "Also print services which finished SECONDS earlier than the latest in the branch"): + r = parse_sec(opts.arg, &arg_fuzz); if (r < 0) return r; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case ARG_MAN: - r = parse_boolean_argument("--man", optarg, &arg_man); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "man", "BOOL", "Whether to check for existence of man pages"): + r = parse_boolean_argument("--man", opts.arg, &arg_man); if (r < 0) return r; break; - case ARG_GENERATORS: - r = parse_boolean_argument("--generators", optarg, &arg_generators); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "generators", "BOOL", + "Whether to run unit generators (which requires privileges)"): + r = parse_boolean_argument("--generators", opts.arg, &arg_generators); if (r < 0) return r; break; - case ARG_INSTANCE: - arg_instance = optarg; + OPTION_LONG("instance", "NAME", "Specify fallback instance name for template units"): + arg_instance = opts.arg; break; - case ARG_OFFLINE: - r = parse_boolean_argument("--offline", optarg, &arg_offline); + OPTION_LONG("offline", "BOOL", "Perform a security review on unit files"): + r = parse_boolean_argument("--offline", opts.arg, &arg_offline); if (r < 0) return r; break; - case ARG_THRESHOLD: - r = safe_atou(optarg, &arg_threshold); + OPTION_LONG("threshold", "N", + "Exit with a non-zero status when overall exposure level is over threshold value"): + r = safe_atou(opts.arg, &arg_threshold); if (r < 0 || arg_threshold > 100) - return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse threshold: %s", optarg); - + return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), + "Failed to parse threshold: %s", opts.arg); break; - case ARG_SECURITY_POLICY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_security_policy); + OPTION_LONG("security-policy", "PATH", + "Use custom JSON security policy instead of built-in one"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_security_policy); if (r < 0) return r; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_ITERATIONS: - r = safe_atou(optarg, &arg_iterations); + OPTION_LONG("iterations", "N", "Show the specified number of iterations"): + r = safe_atou(opts.arg, &arg_iterations); if (r < 0) - return log_error_errno(r, "Failed to parse iterations: %s", optarg); + return log_error_errno(r, "Failed to parse iterations: %s", opts.arg); break; - case ARG_BASE_TIME: - r = parse_timestamp(optarg, &arg_base_time); + OPTION_LONG("base-time", "TIMESTAMP", + "Calculate calendar times relative to specified time"): + r = parse_timestamp(opts.arg, &arg_base_time); if (r < 0) - return log_error_errno(r, "Failed to parse --base-time= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --base-time= parameter: %s", opts.arg); break; - case ARG_PROFILE: - if (isempty(optarg)) + OPTION_LONG("profile", "name|PATH", + "Include the specified profile in the security review of the units"): + if (isempty(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Profile file name is empty"); - if (is_path(optarg)) { - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_profile); + if (is_path(opts.arg)) { + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_profile); if (r < 0) return r; if (!endswith(arg_profile, ".conf")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Profile file name must end with .conf: %s", arg_profile); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Profile file name must end with .conf: %s", arg_profile); } else { - r = free_and_strdup(&arg_profile, optarg); + r = free_and_strdup(&arg_profile, opts.arg); if (r < 0) return log_oom(); } - break; - case 'U': { + OPTION('U', "unit", "UNIT", "Evaluate conditions and asserts of unit"): { _cleanup_free_ char *mangled = NULL; - r = unit_name_mangle(optarg, UNIT_NAME_MANGLE_WARN, &mangled); + r = unit_name_mangle(opts.arg, UNIT_NAME_MANGLE_WARN, &mangled); if (r < 0) - return log_error_errno(r, "Failed to mangle unit name %s: %m", optarg); + return log_error_errno(r, "Failed to mangle unit name %s: %m", opts.arg); free_and_replace(arg_unit, mangled); break; } - case ARG_TABLE: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "table", NULL, + "Output plot's raw time data as a table"): arg_table = true; break; - case ARG_NO_LEGEND: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "no-legend", NULL, + "Disable column headers and hints in plot with either --table or --json="): arg_legend = false; break; - case ARG_TLDR: + OPTION_LONG("tldr", NULL, "Skip comments and empty lines"): arg_cat_flags = CAT_TLDR; break; - case 'm': + OPTION('m', "mask", NULL, "Parse parameter as numeric capability mask"): arg_capability = CAPABILITY_MASK; break; - case ARG_SCALE_FACTOR_SVG: - arg_svg_timescale = strtod(optarg, NULL); + OPTION_LONG("scale-svg", "FACTOR", "Stretch x-axis of plot by FACTOR (default: 1.0)"): + arg_svg_timescale = strtod(opts.arg, NULL); break; - case ARG_DETAILED_SVG: + OPTION_LONG("detailed", NULL, + "Add more details to SVG plot, e.g. show activation timestamps"): arg_detailed_svg = true; break; - case ARG_DRM_DEVICE_PATH: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_drm_device_path); + OPTION_LONG("drm-device", "PATH", "Use this DRM device sysfs path to get EDID"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_drm_device_path); if (r < 0) return r; break; - case ARG_DEBUGGER: - r = free_and_strdup_warn(&arg_debugger, optarg); + OPTION_LONG("debugger", "DEBUGGER", "Use the given debugger"): + r = free_and_strdup_warn(&arg_debugger, opts.arg); if (r < 0) return r; break; - case 'A': { + OPTION('A', "debugger-arguments", "ARGS", "Pass the given arguments to the debugger"): { _cleanup_strv_free_ char **l = NULL; - r = strv_split_full(&l, optarg, WHITESPACE, EXTRACT_UNQUOTE); + r = strv_split_full(&l, opts.arg, WHITESPACE, EXTRACT_UNQUOTE); if (r < 0) - return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", optarg); + return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", opts.arg); strv_free_and_replace(arg_debugger_args, l); break; } - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } - -done: - if (unit_shell >= 0) { - char *t; - - /* We found the "unit-shell" verb while processing the argument list. Since we turned off reordering of the - * argument list initially let's readjust it now, and move the "unit-shell" verb to the back. */ - - optind -= 1; /* place the option index where the "unit-shell" verb will be placed */ - t = argv[unit_shell]; - for (int i = unit_shell; i < optind; i++) - argv[i] = argv[i+1]; - argv[optind] = t; - } + _cleanup_strv_free_ char **args = strv_copy(option_parser_get_args(&opts)); /* args is [arg1, arg2, …] */ + if (!args || strv_prepend(&args, verb) < 0) /* args is now [arg0, arg1, arg2, …] */ + return log_oom(); - if (arg_offline && !streq_ptr(argv[optind], "security")) + if (arg_offline && !streq_ptr(verb, "security")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --offline= is only supported for security right now."); - if (arg_offline && optind >= argc - 1) + if (arg_offline && strv_length(args) < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --offline= requires one or more units to perform a security review."); - if (arg_json_format_flags != SD_JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "dlopen-metadata", "plot", "fdstore", "pcrs", "nvpcrs", "architectures", "capability", "exit-status")) + if (arg_json_format_flags != SD_JSON_FORMAT_OFF && + !STRPTR_IN_SET(verb, "security", "inspect-elf", "dlopen-metadata", "plot", "fdstore", "pcrs", "nvpcrs", "architectures", "capability", "exit-status")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --json= is only supported for security, inspect-elf, dlopen-metadata, plot, fdstore, pcrs, nvpcrs, architectures, capability, exit-status right now."); - if (arg_threshold != 100 && !streq_ptr(argv[optind], "security")) + if (arg_threshold != 100 && !streq_ptr(verb, "security")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --threshold= is only supported for security right now."); - if (arg_runtime_scope == RUNTIME_SCOPE_GLOBAL && !streq_ptr(argv[optind], "unit-paths")) + if (arg_runtime_scope == RUNTIME_SCOPE_GLOBAL && !streq_ptr(verb, "unit-paths")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --global only makes sense with verb unit-paths."); - if (streq_ptr(argv[optind], "cat-config") && arg_runtime_scope == RUNTIME_SCOPE_USER) + if (streq_ptr(verb, "cat-config") && arg_runtime_scope == RUNTIME_SCOPE_USER) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --user is not supported for cat-config right now."); - if (arg_security_policy && !streq_ptr(argv[optind], "security")) + if (arg_security_policy && !streq_ptr(verb, "security")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --security-policy= is only supported for security."); - if ((arg_root || arg_image) && (!STRPTR_IN_SET(argv[optind], "cat-config", "verify", "condition", "inspect-elf", "unit-gdb")) && - (!(streq_ptr(argv[optind], "security") && arg_offline))) + if ((arg_root || arg_image) && + !STRPTR_IN_SET(verb, "cat-config", "verify", "condition", "inspect-elf", "unit-gdb") && + (!(streq_ptr(verb, "security") && arg_offline))) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Options --root= and --image= are only supported for cat-config, verify, condition, unit-gdb, and security when used with --offline= right now."); @@ -743,83 +654,35 @@ static int parse_argv(int argc, char *argv[]) { 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."); - if (arg_unit && !streq_ptr(argv[optind], "condition")) + if (arg_unit && !streq_ptr(verb, "condition")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --unit= is only supported for condition"); - if (streq_ptr(argv[optind], "condition") && !arg_unit && optind >= argc - 1) + if (streq_ptr(verb, "condition") && !arg_unit && strv_length(args) < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments for condition"); - if (streq_ptr(argv[optind], "condition") && arg_unit && optind < argc - 1) + if (streq_ptr(verb, "condition") && arg_unit && strv_length(args) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No conditions can be passed if --unit= is used."); - if (arg_table && !streq_ptr(argv[optind], "plot")) + if (arg_table && !streq_ptr(verb, "plot")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --table is only supported for plot right now."); if (arg_table && sd_json_format_enabled(arg_json_format_flags)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--table and --json= are mutually exclusive."); - if (arg_capability != CAPABILITY_LITERAL && !streq_ptr(argv[optind], "capability")) + if (arg_capability != CAPABILITY_LITERAL && !streq_ptr(verb, "capability")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --mask is only supported for capability."); - if (arg_drm_device_path && !streq_ptr(argv[optind], "chid")) + if (arg_drm_device_path && !streq_ptr(verb, "chid")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --drm-device is only supported for chid right now."); + *ret_args = TAKE_PTR(args); return 1; /* work to do */ } static int run(int argc, char *argv[]) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_freep) char *mounted_dir = NULL; - - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, 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 }, - { "plot", VERB_ANY, 1, 0, verb_plot }, - { "dot", VERB_ANY, VERB_ANY, 0, verb_dot }, - /* ↓ The following seven verbs are deprecated, from here … ↓ */ - { "log-level", VERB_ANY, 2, 0, verb_log_control }, - { "log-target", VERB_ANY, 2, 0, verb_log_control }, - { "set-log-level", 2, 2, 0, verb_log_control }, - { "get-log-level", VERB_ANY, 1, 0, verb_log_control }, - { "set-log-target", 2, 2, 0, verb_log_control }, - { "get-log-target", VERB_ANY, 1, 0, verb_log_control }, - { "service-watchdogs", VERB_ANY, 2, 0, verb_service_watchdogs }, - /* ↑ … until here ↑ */ - { "dump", VERB_ANY, VERB_ANY, 0, verb_dump }, - { "cat-config", 2, VERB_ANY, 0, verb_cat_config }, - { "unit-files", VERB_ANY, VERB_ANY, 0, verb_unit_files }, - { "unit-gdb", 2, VERB_ANY, 0, verb_unit_gdb }, - { "unit-paths", 1, 1, 0, verb_unit_paths }, - { "unit-shell", 2, VERB_ANY, 0, verb_unit_shell }, - { "exit-status", VERB_ANY, VERB_ANY, 0, verb_exit_status }, - { "syscall-filter", VERB_ANY, VERB_ANY, 0, verb_syscall_filters }, - { "capability", VERB_ANY, VERB_ANY, 0, verb_capabilities }, - { "filesystems", VERB_ANY, VERB_ANY, 0, verb_filesystems }, - { "condition", VERB_ANY, VERB_ANY, 0, verb_condition }, - { "compare-versions", 3, 4, 0, verb_compare_versions }, - { "verify", 2, VERB_ANY, 0, verb_verify }, - { "calendar", 2, VERB_ANY, 0, verb_calendar }, - { "timestamp", 2, VERB_ANY, 0, verb_timestamp }, - { "timespan", 2, VERB_ANY, 0, verb_timespan }, - { "security", VERB_ANY, VERB_ANY, 0, verb_security }, - { "inspect-elf", 2, VERB_ANY, 0, verb_elf_inspection }, - { "dlopen-metadata", 2, 2, 0, verb_dlopen_metadata }, - { "malloc", VERB_ANY, VERB_ANY, 0, verb_malloc }, - { "fdstore", 2, VERB_ANY, 0, verb_fdstore }, - { "image-policy", 2, 2, 0, verb_image_policy }, - { "has-tpm2", VERB_ANY, 1, 0, verb_has_tpm2 }, - { "pcrs", VERB_ANY, VERB_ANY, 0, verb_pcrs }, - { "nvpcrs", VERB_ANY, VERB_ANY, 0, verb_nvpcrs }, - { "srk", VERB_ANY, 1, 0, verb_srk }, - { "architectures", VERB_ANY, VERB_ANY, 0, verb_architectures }, - { "smbios11", VERB_ANY, 1, 0, verb_smbios11 }, - { "chid", VERB_ANY, VERB_ANY, 0, verb_chid }, - { "transient-settings", 2, VERB_ANY, 0, verb_transient_settings }, - {} - }; - + _cleanup_strv_free_ char **args = NULL; int r; setlocale(LC_ALL, ""); @@ -827,7 +690,7 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -853,7 +716,7 @@ static int run(int argc, char *argv[]) { return log_oom(); } - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); 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..fd0def44f1f88 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,73 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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 = opts.arg; break; - case ARG_TIMEOUT: - r = parse_sec(optarg, &arg_timeout); + OPTION_LONG("timeout", "SEC", "Timeout in seconds"): + r = parse_sec(opts.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", opts.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 (!opts.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(opts.arg) || streq(opts.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=", opts.arg, NULL); if (r < 0) return r; @@ -165,55 +115,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 = opts.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 = opts.arg; break; - case ARG_KEYNAME: - arg_key_name = optarg; + OPTION_LONG("keyname", "NAME", "Kernel key name for caching passwords"): + arg_key_name = opts.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 = opts.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 +171,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(&opts); + + if (!strv_isempty(args)) { + arg_message = strv_join(args, " "); if (!arg_message) return log_oom(); } else if (FLAGS_SET(arg_flags, ASK_PASSWORD_ECHO)) { @@ -303,6 +250,8 @@ static int vl_method_ask(sd_varlink *link, sd_json_variant *parameters, sd_varli .push_cache = -1, .echo_mode = _ECHO_MODE_INVALID, }; + /* Local copy so that parameters passed are per-invocation */ + AskPasswordFlags ask_flags = arg_flags; int r; assert(link); @@ -318,7 +267,8 @@ static int vl_method_ask(sd_varlink *link, sd_json_variant *parameters, sd_varli /* details= */ NULL, /* good_user= */ FLAGS_SET(arg_flags, ASK_PASSWORD_USER) ? getuid() : UID_INVALID, /* flags= */ 0, - polkit_registry); + polkit_registry, + /* ret_admin= */ NULL); if (r <= 0) return r; @@ -338,21 +288,21 @@ static int vl_method_ask(sd_varlink *link, sd_json_variant *parameters, sd_varli req.until = MIN(usec_add(now(CLOCK_MONOTONIC), p.timeout_usec), p.until_usec); /* If the timeout is set to zero, don't ask agents, just stick to cache */ - SET_FLAG(arg_flags, ASK_PASSWORD_NO_AGENT, req.until == 0); + SET_FLAG(ask_flags, ASK_PASSWORD_NO_AGENT, req.until == 0); if (p.accept_cached >= 0) - SET_FLAG(arg_flags, ASK_PASSWORD_ACCEPT_CACHED, p.accept_cached); + SET_FLAG(ask_flags, ASK_PASSWORD_ACCEPT_CACHED, p.accept_cached); if (p.push_cache >= 0) - SET_FLAG(arg_flags, ASK_PASSWORD_PUSH_CACHE, p.push_cache); + SET_FLAG(ask_flags, ASK_PASSWORD_PUSH_CACHE, p.push_cache); if (p.echo_mode >= 0) { - SET_FLAG(arg_flags, ASK_PASSWORD_ECHO, p.echo_mode == ECHO_ON); - SET_FLAG(arg_flags, ASK_PASSWORD_SILENT, p.echo_mode == ECHO_OFF); + SET_FLAG(ask_flags, ASK_PASSWORD_ECHO, p.echo_mode == ECHO_ON); + SET_FLAG(ask_flags, ASK_PASSWORD_SILENT, p.echo_mode == ECHO_OFF); } _cleanup_strv_free_erase_ char **l = NULL; - r = ask_password_auto(&req, arg_flags, &l); + r = ask_password_auto(&req, ask_flags, &l); if (r == -EUNATCH) return sd_varlink_error(link, "io.systemd.AskPassword.NoPasswordAvailable", NULL); if (r == -ETIME) diff --git a/src/backlight/backlight.c b/src/backlight/backlight.c index 027379809d10f..a84640d45dd87 100644 --- a/src/backlight/backlight.c +++ b/src/backlight/backlight.c @@ -11,10 +11,11 @@ #include "device-util.h" #include "escape.h" #include "fileio.h" +#include "format-table.h" +#include "help-util.h" #include "main-func.h" #include "parse-util.h" #include "percent-util.h" -#include "pretty-print.h" #include "reboot-util.h" #include "string-util.h" #include "strv.h" @@ -23,25 +24,22 @@ #define PCI_CLASS_GRAPHICS_CARD 0x30000 static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL; int r; - r = terminal_urlify_man("systemd-backlight", "8", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; + + help_cmdline("COMMAND [backlight|leds]:DEVICE"); + help_abstract("Save and restore backlight brightness at shutdown and boot."); - printf("%s save [backlight|leds]:DEVICE\n" - "%s load [backlight|leds]:DEVICE\n" - "\n%sSave and restore backlight brightness at shutdown and boot.%s\n\n" - " save Save current brightness\n" - " load Set brightness to be the previously saved value\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + help_man_page_reference("systemd-backlight", "8"); return 0; } @@ -68,13 +66,9 @@ static int has_multiple_graphics_cards(void) { return r; FOREACH_DEVICE(e, dev) { - const char *s; - unsigned long c; + uint32_t c; - if (sd_device_get_sysattr_value(dev, "class", &s) < 0) - continue; - - if (safe_atolu(s, &c) < 0) + if (device_get_sysattr_u32(dev, "class", &c) < 0) continue; if (c != PCI_CLASS_GRAPHICS_CARD) @@ -182,7 +176,7 @@ static int same_device(sd_device *a, sd_device *b) { static int validate_device(sd_device *device) { _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *enumerate = NULL; - const char *v, *sysname; + const char *sysname; sd_device *parent; int r; @@ -207,10 +201,10 @@ static int validate_device(sd_device *device) { if (r > 0) return true; /* We assume LED device is always valid. */ - r = sd_device_get_sysattr_value(device, "type", &v); + r = device_get_sysattr_streq(device, "type", "raw"); if (r < 0) return log_device_debug_errno(device, r, "Failed to read 'type' sysattr: %m"); - if (!streq(v, "raw")) + if (r == 0) return true; r = find_pci_or_platform_parent(device, &parent); @@ -283,7 +277,7 @@ static int validate_device(sd_device *device) { const char *other_sysname = NULL, *other_type = NULL; (void) sd_device_get_sysname(other, &other_sysname); - (void) sd_device_get_sysattr_value(other, "type", &other_type); + (void) device_get_sysattr_safe_string(other, "type", &other_type); log_device_debug(device, "Found another %s backlight device %s on the same PCI, skipping.", strna(other_type), strna(other_sysname)); @@ -297,7 +291,7 @@ static int validate_device(sd_device *device) { const char *other_sysname = NULL, *other_type = NULL; (void) sd_device_get_sysname(other, &other_sysname); - (void) sd_device_get_sysattr_value(other, "type", &other_type); + (void) device_get_sysattr_safe_string(other, "type", &other_type); log_device_debug(device, "Found another %s backlight device %s, which has higher precedence, skipping.", strna(other_type), strna(other_sysname)); @@ -318,7 +312,7 @@ static int read_max_brightness(sd_device *device, unsigned *ret) { r = device_get_sysattr_unsigned(device, "max_brightness", &max_brightness); if (r < 0) - return log_device_warning_errno(device, r, "Failed to read/parse 'max_brightness' attribute: %m"); + return log_device_warning_errno(device, r, "Failed to read 'max_brightness' attribute: %m"); /* If max_brightness is 0, then there is no actual backlight device. This happens on desktops * with Asus mainboards that load the eeepc-wmi module. */ @@ -420,21 +414,16 @@ static int shall_clamp(sd_device *device, unsigned *ret) { } static int read_brightness(sd_device *device, unsigned max_brightness, unsigned *ret_brightness) { - const char *value; unsigned brightness; int r; assert(device); assert(ret_brightness); - r = sd_device_get_sysattr_value(device, "brightness", &value); + r = device_get_sysattr_unsigned(device, "brightness", &brightness); if (r < 0) return log_device_debug_errno(device, r, "Failed to read 'brightness' attribute: %m"); - r = safe_atou(value, &brightness); - if (r < 0) - return log_device_debug_errno(device, r, "Failed to parse 'brightness' attribute: %s", value); - if (brightness > max_brightness) return log_device_debug_errno(device, SYNTHETIC_ERRNO(EINVAL), "brightness=%u is larger than max_brightness=%u", @@ -555,7 +544,9 @@ 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) { +VERB(verb_load, "load", "[backlight|leds]:DEVICE", 2, 2, VERB_ONLINE_ONLY, + "Set brightness to be the previously saved value"); +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 +596,9 @@ static int verb_load(int argc, char *argv[], void *userdata) { return 0; } -static int verb_save(int argc, char *argv[], void *userdata) { +VERB(verb_save, "save", "[backlight|leds]:DEVICE", 2, 2, VERB_ONLINE_ONLY, + "Save current brightness"); +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; @@ -647,12 +640,6 @@ static int verb_save(int argc, char *argv[], void *userdata) { } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "load", 2, 2, VERB_ONLINE_ONLY, verb_load }, - { "save", 2, 2, VERB_ONLINE_ONLY, verb_save }, - {} - }; - log_setup(); if (argv_looks_like_help(argc, argv)) @@ -660,7 +647,7 @@ static int run(int argc, char *argv[]) { umask(0022); - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb(strv_skip(argv, 1), /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); 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..a3e2321b30349 100644 --- a/src/basic/alloc-util.h +++ b/src/basic/alloc-util.h @@ -21,7 +21,7 @@ #define alloca_safe(n) \ ({ \ size_t _nn_ = (n); \ - assert(_nn_ <= ALLOCA_MAX); \ + assert_se(_nn_ <= ALLOCA_MAX); \ alloca(_nn_ == 0 ? 1 : _nn_); \ }) \ @@ -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/architecture.h b/src/basic/architecture.h index ee91ea0d349b8..350a72d087a35 100644 --- a/src/basic/architecture.h +++ b/src/basic/architecture.h @@ -242,4 +242,10 @@ Architecture uname_architecture(void); # error "Please register your architecture here!" #endif +#if defined(__hppa__) || defined(__hppa64__) +# define STACK_GROWS_UP 1 +#else +# define STACK_GROWS_UP 0 +#endif + DECLARE_STRING_TABLE_LOOKUP(architecture, Architecture); diff --git a/src/basic/argv-util.c b/src/basic/argv-util.c index c9d610b3a037d..0602193809cf6 100644 --- a/src/basic/argv-util.c +++ b/src/basic/argv-util.c @@ -29,44 +29,51 @@ void save_argc_argv(int argc, char **argv) { } bool invoked_as(char *argv[], const char *token) { - if (!argv || isempty(argv[0])) - return false; + const char *progname = secure_getenv("SYSTEMD_INVOKED_AS"); + + if (!progname && argv) + progname = argv[0]; - if (isempty(token)) + if (isempty(progname) || isempty(token)) return false; - return strstr(last_path_component(argv[0]), token); + return strstr(last_path_component(progname), token); } bool invoked_by_systemd(void) { + static int cached = -1; int r; + if (cached >= 0) + return cached; + /* If the process is directly executed by PID1 (e.g. ExecStart= or generator), systemd-importd, * or systemd-homed, then $SYSTEMD_EXEC_PID= is set, and read the command line. */ const char *e = getenv("SYSTEMD_EXEC_PID"); if (!e) - return false; + return (cached = false); if (streq(e, "*")) /* For testing. */ - return true; + return (cached = true); pid_t p; r = parse_pid(e, &p); if (r < 0) { /* We know that systemd sets the variable correctly. Something else must have set it. */ log_debug_errno(r, "Failed to parse \"SYSTEMD_EXEC_PID=%s\", ignoring: %m", e); - return false; + return (cached = false); } - return getpid_cached() == p; + cached = getpid_cached() == p; + return cached; } bool argv_looks_like_help(int argc, char **argv) { char **l; /* Scans the command line for indications the user asks for help. This is supposed to be called by - * tools that do not implement getopt() style command line parsing because they are not primarily + * tools that do not implement getopt()-style command line parsing because they are not primarily * user-facing. Detects four ways of asking for help: * * 1. Passing zero arguments @@ -74,6 +81,7 @@ bool argv_looks_like_help(int argc, char **argv) { * 3. Passing --help as any argument * 4. Passing -h as any argument */ + assert(argc == 0 || argv); if (argc <= 1) return true; diff --git a/src/basic/argv-util.h b/src/basic/argv-util.h index 650b00353dc08..02901ebc49f4b 100644 --- a/src/basic/argv-util.h +++ b/src/basic/argv-util.h @@ -9,7 +9,7 @@ extern char **saved_argv; void save_argc_argv(int argc, char **argv); bool invoked_as(char *argv[], const char *token); -bool invoked_by_systemd(void); +bool invoked_by_systemd(void) _const_; bool argv_looks_like_help(int argc, char **argv); int rename_process_full(const char *comm, const char *invocation); diff --git a/src/basic/assert-util.h b/src/basic/assert-util.h index 899a4365601f7..8451b7b29916f 100644 --- a/src/basic/assert-util.h +++ b/src/basic/assert-util.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "assert-fundamental.h" /* IWYU pragma: export */ +#include "../fundamental/assert-util.h" /* IWYU pragma: export */ /* Logging for various assertions */ diff --git a/src/basic/basic-forward.h b/src/basic/basic-forward.h index 1ca9ecfeff43e..f02801a77df2f 100644 --- a/src/basic/basic-forward.h +++ b/src/basic/basic-forward.h @@ -17,7 +17,8 @@ #include "assert-util.h" /* IWYU pragma: export */ #include "cleanup-util.h" /* IWYU pragma: export */ #include "macro.h" /* IWYU pragma: export */ -#include "string-table-fundamental.h" /* IWYU pragma: export */ + +#include "../fundamental/string-table.h" /* IWYU pragma: export */ /* Generic types */ @@ -55,6 +56,7 @@ struct statx; struct termios; struct tm; struct ucred; +struct unix_diag_msg; /* To forward declare FILE and DIR, we have to declare the internal struct names for them. Since these are * used for C++ symbol name mangling, they're effectively part of the ABI and won't actually change. */ @@ -69,6 +71,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); @@ -94,6 +105,7 @@ typedef enum Glyph Glyph; typedef enum ImageClass ImageClass; typedef enum JobMode JobMode; typedef enum RuntimeScope RuntimeScope; +typedef enum StringSafeFlags StringSafeFlags; typedef enum TimestampStyle TimestampStyle; typedef enum UnitActiveState UnitActiveState; typedef enum UnitDependency UnitDependency; @@ -101,16 +113,20 @@ typedef enum UnitNameMangle UnitNameMangle; typedef enum UnitType UnitType; typedef enum WaitFlags WaitFlags; +typedef struct Fiber Fiber; +typedef struct FiberOps FiberOps; typedef struct Hashmap Hashmap; typedef struct HashmapBase HashmapBase; typedef struct IteratedCache IteratedCache; typedef struct Iterator Iterator; +typedef struct LogContext LogContext; typedef struct OrderedHashmap OrderedHashmap; typedef struct OrderedSet OrderedSet; 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; @@ -143,3 +159,7 @@ typedef struct SocketAddress SocketAddress; /* MAX_ERRNO is defined as 4095 in linux/err.h. We use the same value here. */ #define ERRNO_MAX 4095 + +/* This is MAX_ADDR_LEN as defined in linux/netdevice.h, but net/if_arp.h + * defines a macro of the same name with a much lower size. */ +#define HW_ADDR_MAX_SIZE 32 diff --git a/src/basic/btrfs.c b/src/basic/btrfs-util.c similarity index 74% rename from src/basic/btrfs.c rename to src/basic/btrfs-util.c index 7981c670e338f..1c605a9ef2f60 100644 --- a/src/basic/btrfs.c +++ b/src/basic/btrfs-util.c @@ -5,9 +5,10 @@ #include #include "alloc-util.h" -#include "btrfs.h" +#include "btrfs-util.h" #include "errno-util.h" #include "fd-util.h" +#include "fs-util.h" #include "path-util.h" #include "string-util.h" @@ -47,7 +48,7 @@ int btrfs_subvol_make(int dir_fd, const char *path) { _cleanup_close_ int fd = -EBADF; int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(wildcard_fd_is_valid(dir_fd)); assert(!isempty(path)); r = extract_subvolume_name(path, &subvolume); @@ -55,24 +56,16 @@ int btrfs_subvol_make(int dir_fd, const char *path) { return r; r = path_extract_directory(path, &parent); - if (r < 0) { - if (r != -EDESTADDRREQ) /* Propagate error, unless only a filename was specified, which is OK */ - return r; - - dir_fd = fd_reopen_condition(dir_fd, O_CLOEXEC, O_PATH, &fd); /* drop O_PATH if it is set */ - if (dir_fd < 0) - return dir_fd; - } else { - fd = openat(dir_fd, parent, O_DIRECTORY|O_RDONLY|O_CLOEXEC, 0); - if (fd < 0) - return -errno; + if (r < 0 && r != -EDESTADDRREQ) /* Propagate error, unless only a filename was specified, which is OK */ + return r; - dir_fd = fd; - } + fd = xopenat(dir_fd, parent ?: ".", O_DIRECTORY|O_RDONLY|O_CLOEXEC); + if (fd < 0) + return fd; strncpy(args.name, subvolume, sizeof(args.name)-1); - return RET_NERRNO(ioctl(dir_fd, BTRFS_IOC_SUBVOL_CREATE, &args)); + return RET_NERRNO(ioctl(fd, BTRFS_IOC_SUBVOL_CREATE, &args)); } int btrfs_subvol_make_fallback(int dir_fd, const char *path, mode_t mode) { diff --git a/src/basic/btrfs.h b/src/basic/btrfs-util.h similarity index 100% rename from src/basic/btrfs.h rename to src/basic/btrfs-util.h 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/build.c b/src/basic/build.c index 213ccca5f4e66..965d4f5a36f31 100644 --- a/src/basic/build.c +++ b/src/basic/build.c @@ -64,12 +64,6 @@ const char* const systemd_features = /* cryptographic libraries */ -#if HAVE_GCRYPT - " +GCRYPT" -#else - " -GCRYPT" -#endif - #if HAVE_GNUTLS " +GNUTLS" #else diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index f67add133a218..29434dc2ea44d 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -426,6 +426,8 @@ int capability_quintet_enforce(const CapabilityQuintet *q) { bool modified = false; int r; + assert(q); + if (q->ambient != CAP_MASK_UNSET || q->inheritable != CAP_MASK_UNSET || q->permitted != CAP_MASK_UNSET || diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index d613e65c4b820..aac0f035ebafd 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -406,8 +406,9 @@ int cg_kill_recursive( return ret; } -int cg_kill_kernel_sigkill(const char *path) { +int cg_kill_kernel_sigkill(const char *path, uint64_t *ret_n_pids_killed) { _cleanup_free_ char *killfile = NULL; + uint64_t n_pids = UINT64_MAX; int r; /* Kills the cgroup at `path` directly by writing to its cgroup.kill file. This sends SIGKILL to all @@ -422,10 +423,22 @@ int cg_kill_kernel_sigkill(const char *path) { if (r < 0) return r; + if (ret_n_pids_killed) { + /* This is not and cannot be atomic so there is a chance the counter might not be accurate + * if a process starts/stops between this read and the next write, but this is used for + * informational purposes so that's ok. */ + r = cg_get_attribute_as_uint64(path, "pids.current", &n_pids); + if (r < 0) + log_debug_errno(r, "Failed to read pids.current from cgroup '%s', ignoring: %m", path); + } + r = write_string_file(killfile, "1", WRITE_STRING_FILE_DISABLE_BUFFER); if (r < 0) return log_debug_errno(r, "Failed to write to cgroup.kill for cgroup '%s': %m", path); + if (ret_n_pids_killed) + *ret_n_pids_killed = n_pids; + return 0; } @@ -1256,6 +1269,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 +1575,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 +1667,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..2c438167f10eb 100644 --- a/src/basic/cgroup-util.h +++ b/src/basic/cgroup-util.h @@ -142,7 +142,7 @@ int cg_read_subgroup(DIR *d, char **ret); typedef int (*cg_kill_log_func_t)(const PidRef *pid, int sig, void *userdata); int cg_kill(const char *path, int sig, CGroupFlags flags, Set *killed_pids, cg_kill_log_func_t log_kill, void *userdata); -int cg_kill_kernel_sigkill(const char *path); +int cg_kill_kernel_sigkill(const char *path, uint64_t *ret_n_pids_killed); int cg_kill_recursive(const char *path, int sig, CGroupFlags flags, Set *killed_pids, cg_kill_log_func_t log_kill, void *userdata); int cg_split_spec(const char *spec, char **ret_controller, char **ret_path); @@ -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..23a523798eb2b 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,174 @@ #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_openat2(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags) { + /* Open the target of a chase operation via openat2(), translating the relevant ChaseFlags into + * RESOLVE_* and O_* flags and verifying MUST_BE_REGULAR/SOCKET via fstat after the open. Returns + * -EOPNOTSUPP when openat2() is unavailable (older kernels) or blocked by a seccomp filter + * (notably systemd's own filter, which returns ENOSYS to force programs onto the openat() + * fallback path) — the verdict is cached so subsequent calls in the same process skip the syscall + * entirely. */ + + static bool can_openat2 = true; + int r; + + assert(path); + assert(wildcard_fd_is_valid(dir_fd)); + + if (!can_openat2) + return -EOPNOTSUPP; + + /* openat2() can handle everything the regular shortcut handles, plus a real root boundary (via + * RESOLVE_IN_ROOT) and CHASE_PROHIBIT_SYMLINKS (via RESOLVE_NO_SYMLINKS). It cannot model the other + * CHASE_NO_SHORTCUT flags, cannot trigger automounts on O_PATH fds, and RESOLVE_IN_ROOT requires + * the dirfd to be the root. Bail out so the caller falls back to the regular chase loop. */ + if ((chase_flags & (CHASE_NO_SHORTCUT_MASK & ~CHASE_PROHIBIT_SYMLINKS)) != 0) + return -EOPNOTSUPP; + if (FLAGS_SET(chase_flags, CHASE_TRIGGER_AUTOFS)) + return -EOPNOTSUPP; + if (root_fd != XAT_FDROOT && root_fd != dir_fd) + return -EOPNOTSUPP; + + _cleanup_close_ int dir_fd_local = -EBADF; + if (dir_fd == XAT_FDROOT) { + if (path_is_absolute(path)) + dir_fd = AT_FDCWD; + else { + dir_fd_local = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH); + if (dir_fd_local < 0) + return -errno; + dir_fd = dir_fd_local; + } + } + + struct open_how how = { + .flags = O_PATH|O_CLOEXEC, + }; + if (FLAGS_SET(chase_flags, CHASE_NOFOLLOW)) + how.flags |= O_NOFOLLOW; + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY)) + how.flags |= O_DIRECTORY; + if (root_fd != XAT_FDROOT) + how.resolve |= RESOLVE_IN_ROOT; + if (FLAGS_SET(chase_flags, CHASE_PROHIBIT_SYMLINKS)) + how.resolve |= RESOLVE_NO_SYMLINKS; + + _cleanup_close_ int fd = openat2(dir_fd, path, &how, sizeof(how)); + if (fd < 0) { + /* ENOSYS: kernel too old or seccomp filter (systemd's filter returns ENOSYS). + * EPERM: Some seccomp profiles of container runtimes use EPERM rather than ENOSYS. + * But EPERM might also be returned because we can't access some component of the path. So + * we can't cache the result and skip using openat2() if it is blocked with EPERM. Instead + * we fall back to userspace chase() if we get EPERM. + * EAGAIN: with RESOLVE_IN_ROOT the kernel returns this when a ".." component + * (typically from following a symlink like /etc/os-release → ../usr/lib/os-release) + * is processed and the global mount_lock or rename_lock seqcount changed during + * the walk. Any mount activity anywhere in the system bumps mount_lock, so this + * fires reliably while we're still setting up a mount tree. Fall back to the + * regular chase loop, which handles root boundaries without openat2(). Don't + * cache this — the condition is per-call, not a kernel/sandbox capability. */ + if (errno == ENOSYS) + can_openat2 = false; + if (IN_SET(errno, ENOSYS, EPERM, EAGAIN)) + return -EOPNOTSUPP; + return -errno; + } + + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR)) { + r = fd_verify_regular(fd); + if (r < 0) + return r; + } + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_SOCKET)) { + r = fd_verify_socket(fd); + if (r < 0) + return r; + } + + return TAKE_FD(fd); +} -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. */ +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 (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */ + 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; + + return xopenat_full(dir_fd, path, open_flags, xopen_flags, MODE_INVALID); +} + +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) { @@ -81,104 +226,49 @@ static int log_prohibited_symlink(int fd, ChaseFlags flags) { assert(fd >= 0); if (!FLAGS_SET(flags, CHASE_WARN)) - return -EREMCHG; + return -ELOOP; (void) fd_get_path(fd, &n1); - return log_warning_errno(SYNTHETIC_ERRNO(EREMCHG), + return log_warning_errno(SYNTHETIC_ERRNO(ELOOP), "Detected symlink where no symlink is allowed at '%s', refusing.", 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(wildcard_fd_is_valid(dir_fd)); + assert(wildcard_fd_is_valid(root_fd)); + /* 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 +276,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 +306,141 @@ 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 (!ret_path) { + r = chase_openat2(root_fd, dir_fd, path, flags); + if (r >= 0) { + if (ret_fd) + *ret_fd = r; + else + safe_close(r); - r = dir_fd_is_root_or_cwd(dir_fd); - if (r < 0) + return 1; + } + if (r != -EOPNOTSUPP) 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 (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 = chase_xopenat(dir_fd, path, flags, O_PATH|O_CLOEXEC, /* xopen_flags= */ 0); if (r < 0) - return -errno; + return r; - *ret_fd = r; - return 0; - } + if (ret_fd) + *ret_fd = r; + else + safe_close(r); - buffer = strdup(path); - if (!buffer) - return -ENOMEM; + return 1; + } - /* 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 +455,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 +514,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 +535,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 +610,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 +648,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 +690,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 +801,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 +820,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 +843,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 +966,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 +1047,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 +1076,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 +1137,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 +1169,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 +1178,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 +1214,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 +1223,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 +1233,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 +1249,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 +1258,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 +1279,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 +1287,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 +1308,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 +1329,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 +1343,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 +1368,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..93f6a0de96d60 100644 --- a/src/basic/cleanup-util.h +++ b/src/basic/cleanup-util.h @@ -1,7 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "cleanup-fundamental.h" /* IWYU pragma: export */ +#include "../fundamental/assert-util.h" +#include "../fundamental/cleanup-util.h" /* IWYU pragma: export */ typedef void (*free_func_t)(void *p); typedef void* (*mfree_func_t)(void *p); @@ -18,13 +19,13 @@ typedef void* (*mfree_func_t)(void *p); /* This is similar to free_and_replace_full(), but NULL is not assigned to 'b', and its reference counter is * increased. */ -#define unref_and_replace_full(a, b, ref_func, unref_func) \ - ({ \ - typeof(a)* _a = &(a); \ - typeof(b) _b = ref_func(b); \ - unref_func(*_a); \ - *_a = _b; \ - 0; \ +#define unref_and_replace_new_ref(a, b, ref_func, unref_func) \ + ({ \ + typeof(a)* _a = &(a); \ + typeof(b) _b = ref_func(b); \ + unref_func(*_a); \ + *_a = _b; \ + 0; \ }) #define _DEFINE_TRIVIAL_REF_FUNC(type, name, scope) \ @@ -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..b3598053782e7 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,20 +20,46 @@ #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_LZ4 -static void *lz4_dl = NULL; +#if HAVE_XZ +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 DLSYM_PROTOTYPE(LZ4F_compressBegin) = NULL; static DLSYM_PROTOTYPE(LZ4F_compressBound) = NULL; static DLSYM_PROTOTYPE(LZ4F_compressEnd) = NULL; @@ -45,19 +71,17 @@ 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 -static void *zstd_dl = NULL; - static DLSYM_PROTOTYPE(ZSTD_CCtx_setParameter) = NULL; static DLSYM_PROTOTYPE(ZSTD_compress) = NULL; static DLSYM_PROTOTYPE(ZSTD_compressStream2) = NULL; @@ -75,7 +99,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 +113,142 @@ static int zstd_ret_to_errno(size_t ret) { } #endif -#if HAVE_XZ -static void *lzma_dl = NULL; +#if HAVE_ZLIB +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; -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 inline void deflateEnd_wrapper(z_stream *s) { + sym_deflateEnd(s); +} -/* 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 inflateEnd_wrapper(z_stream *s) { + sym_inflateEnd(s); +} +#endif + +#if HAVE_BZIP2 +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_lowercase_table[_COMPRESSION_MAX] = { - [COMPRESSION_NONE] = "none", - [COMPRESSION_XZ] = "xz", - [COMPRESSION_LZ4] = "lz4", - [COMPRESSION_ZSTD] = "zstd", +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_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 +256,37 @@ 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", + static void *lzma_dl = NULL; + + 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 +294,137 @@ 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 + static void *lz4_dl = NULL; + + 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 + static void *zstd_dl = NULL; + + 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 + static void *zlib_dl = NULL; + + 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 + static void *bzip2_dl = NULL; + + 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; + 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); + + s.next_in = (void*) src; + s.avail_in = src_size; - n = sizeof(out) - s.avail_out; + s.next_out = *buffer; + s.avail_out = MIN(allocated, (size_t) UINT_MAX); - k = loop_write(fdt, out, n); - if (k < 0) - return k; + for (;;) { + r = sym_inflate(&s, Z_FINISH); - if (ret == LZMA_STREAM_END) { - if (ret_uncompressed_size) - *ret_uncompressed_size = s.total_in; + if (!IN_SET(r, Z_OK, Z_STREAM_END, Z_BUF_ERROR)) + return -EBADMSG; - 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 (allocated - s.avail_out >= prefix_len + 1) + return memcmp(*buffer, prefix, prefix_len) == 0 && + ((const uint8_t*) *buffer)[prefix_len] == extra; - return 0; - } - } + 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; + + int flags = fcntl(fd, F_GETFL); + if (flags < 0) + return -errno; - if (fstat(fdf, &st) < 0) - return log_debug_errno(errno, "fstat() failed: %m"); + return S_ISREG(st.st_mode) && !FLAGS_SET(flags, O_APPEND); +} - if (file_offset_beyond_memory_size(st.st_size)) - return -EFBIG; +/* 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; - buf = malloc(LZ4_BUFSIZE); - if (!buf) - return -ENOMEM; + assert(fd >= 0); - src = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fdf, 0); - if (src == MAP_FAILED) + pos = lseek(fd, 0, SEEK_CUR); + if (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; + if (ftruncate(fd, pos) < 0) + return -errno; - c = sym_LZ4F_decompress(ctx, buf, &produced, src + total_in, &used, NULL); - if (sym_LZ4F_isError(c)) { - r = -EBADMSG; - goto cleanup; - } + return 0; +} - total_in += used; - total_out += produced; +/* Common helper for decompress_stream_*() wrappers */ - 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; - } +struct decompress_stream_userdata { + int fd; + uint64_t max_bytes; + uint64_t total_out; + bool sparse; +}; - r = loop_write(fdt, buf, produced); - if (r < 0) - goto cleanup; +static int decompress_stream_write_callback(const void *data, size_t size, void *userdata) { + struct decompress_stream_userdata *u = ASSERT_PTR(userdata); + + 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 (in_bytes == 0) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "ZSTD decoder failed: no data read"); +#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 (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); - } +#if HAVE_ZLIB + case COMPRESSION_GZIP: + if (c->encoding) + sym_deflateEnd(&c->gzip); + else + sym_inflateEnd(&c->gzip); + break; +#endif - 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."); +#if HAVE_BZIP2 + case COMPRESSION_BZIP2: + if (c->encoding) + sym_BZ2_bzCompressEnd(&c->bzip2); + else + sym_BZ2_bzDecompressEnd(&c->bzip2); + break; #endif + + default: + break; + } + + return mfree(c); } -int decompress_stream(const char *filename, int fdf, int fdt, uint64_t max_bytes) { +Compression compressor_type(const Compressor *c) { + return c ? c->type : _COMPRESSION_INVALID; +} - 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); +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 - return -EPROTONOSUPPORT; + assert(ret); + + 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..8a934a41b46b7 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(), @@ -160,7 +155,7 @@ static int conf_file_chase_and_verify( struct stat st = {}; int r; - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); assert(original_path); assert(path); assert(name); @@ -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)); @@ -279,7 +274,7 @@ int conf_file_new_at( int r; assert(path); - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); assert(ret); int log_level = conf_files_log_level(flags); @@ -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) @@ -395,7 +390,7 @@ static int files_add( assert(dir); assert(original_dirpath); assert(resolved_dirpath); - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); assert(files); assert(masked); @@ -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); @@ -627,7 +622,7 @@ static int conf_files_list_impl( const ConfFile *inserted = NULL; int r; - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); assert(ret); root = empty_to_root(root); @@ -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, @@ -741,7 +736,7 @@ int conf_files_list_strv_at( _cleanup_free_ char *root = NULL; int r; - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); assert(ret); if (DEBUG_LOGGING) @@ -766,7 +761,7 @@ int conf_files_list_strv_at_full( _cleanup_free_ char *root = NULL; int r; - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); assert(ret_files); assert(ret_n_files); 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/confidential-virt.c b/src/basic/confidential-virt.c index a5996b244bd15..cbedae1a84e8a 100644 --- a/src/basic/confidential-virt.c +++ b/src/basic/confidential-virt.c @@ -8,7 +8,6 @@ #include #include "confidential-virt.h" -#include "confidential-virt-fundamental.h" #include "errno-util.h" /* IWYU pragma: keep */ #include "fd-util.h" #include "fileio.h" /* IWYU pragma: keep */ @@ -20,6 +19,11 @@ #if defined(__x86_64__) static void cpuid(uint32_t *eax, uint32_t *ebx, uint32_t *ecx, uint32_t *edx) { + assert(eax); + assert(ebx); + assert(ecx); + assert(edx); + log_debug("CPUID func %" PRIx32 " %" PRIx32, *eax, *ecx); __cpuid_count(*eax, *ecx, *eax, *ebx, *ecx, *edx); log_debug("CPUID result %" PRIx32 " %" PRIx32 " %" PRIx32 " %" PRIx32, *eax, *ebx, *ecx, *edx); @@ -44,37 +48,32 @@ static uint32_t cpuid_leaf(uint32_t eax, char ret_sig[static 13], bool swapped) #define MSR_DEVICE "/dev/cpu/0/msr" -static uint64_t msr(uint64_t index) { - uint64_t ret; - ssize_t rv; +static int msr(uint64_t index, uint64_t *ret) { _cleanup_close_ int fd = -EBADF; + uint64_t v; + ssize_t n; - fd = open(MSR_DEVICE, O_RDONLY|O_CLOEXEC); - if (fd < 0) { - log_debug_errno(errno, - "Cannot open MSR device %s (index %" PRIu64 "), ignoring: %m", - MSR_DEVICE, - index); - return 0; - } - - rv = pread(fd, &ret, sizeof(ret), index); - if (rv < 0) { - log_debug_errno(errno, - "Cannot read MSR device %s (index %" PRIu64 "), ignoring: %m", - MSR_DEVICE, - index); - return 0; - } else if (rv != sizeof(ret)) { - log_debug("Short read %zd bytes from MSR device %s (index %" PRIu64 "), ignoring", - rv, - MSR_DEVICE, - index); - return 0; - } + assert(ret); - log_debug("MSR %" PRIu64 " result %" PRIu64 "", index, ret); - return ret; + fd = open(MSR_DEVICE, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_debug_errno(errno, + "Cannot open MSR device %s (index %" PRIu64 "): %m", + MSR_DEVICE, index); + + n = pread(fd, &v, sizeof(v), index); + if (n < 0) + return log_debug_errno(errno, + "Cannot read MSR device %s (index %" PRIu64 "): %m", + MSR_DEVICE, index); + if (n != sizeof(v)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Short read %zd bytes from MSR device %s (index %" PRIu64 ")", + n, MSR_DEVICE, index); + + log_debug("MSR %" PRIu64 " result %" PRIu64, index, v); + *ret = v; + return 0; } static bool detect_hyperv_cvm(uint32_t isoltype) { @@ -111,6 +110,7 @@ static bool detect_hyperv_cvm(uint32_t isoltype) { static ConfidentialVirtualization detect_sev(void) { uint32_t eax, ebx, ecx, edx; uint64_t msrval; + int r; eax = CPUID_GET_HIGHEST_FUNCTION; ebx = ecx = edx = 0; @@ -141,7 +141,15 @@ static ConfidentialVirtualization detect_sev(void) { return CONFIDENTIAL_VIRTUALIZATION_NONE; } - msrval = msr(MSR_AMD64_SEV); + r = msr(MSR_AMD64_SEV, &msrval); + if (r < 0) { + /* The CPU advertises SEV support and we're running under a hypervisor, but we couldn't read + * the SEV MSR to determine the exact mode (e.g. /dev/cpu/0/msr is unavailable because the + * msr module isn't loaded). Assume plain SEV. Misreporting a genuine confidential guest as + * non-confidential would wrongly make us trust hypervisor-provided data such as firmware credentials. */ + log_debug_errno(r, "Failed to read SEV MSR, assuming SEV: %m"); + return CONFIDENTIAL_VIRTUALIZATION_SEV; + } /* Test reverse order, since the SEV-SNP bit implies * the SEV-ES bit, which implies the SEV bit */ @@ -156,17 +164,10 @@ static ConfidentialVirtualization detect_sev(void) { } static ConfidentialVirtualization detect_tdx(void) { - uint32_t eax, ebx, ecx, edx; char sig[13] = {}; - eax = CPUID_GET_HIGHEST_FUNCTION; - ebx = ecx = edx = 0; - - cpuid(&eax, &ebx, &ecx, &edx); - - if (eax < CPUID_INTEL_TDX_ENUMERATION) - return CONFIDENTIAL_VIRTUALIZATION_NONE; - + /* Querying an unsupported CPUID leaf is harmless (it returns the highest basic leaf's data rather + * than faulting), so reading this leaf and matching the IntelTDX signature is sufficient. */ cpuid_leaf(CPUID_INTEL_TDX_ENUMERATION, sig, true); if (memcmp(sig, CPUID_SIG_INTEL_TDX, sizeof(sig)) == 0) diff --git a/src/basic/confidential-virt.h b/src/basic/confidential-virt.h index 446080de3595f..5505f997e6f65 100644 --- a/src/basic/confidential-virt.h +++ b/src/basic/confidential-virt.h @@ -3,6 +3,8 @@ #include "basic-forward.h" +#include "../fundamental/confidential-virt.h" /* IWYU pragma: export */ + typedef enum ConfidentialVirtualization { CONFIDENTIAL_VIRTUALIZATION_NONE = 0, diff --git a/src/basic/constants.h b/src/basic/constants.h index 3dbffdee26335..475d8bf79a688 100644 --- a/src/basic/constants.h +++ b/src/basic/constants.h @@ -68,6 +68,8 @@ #define VARLINK_PATH_MACHINED_RESOLVE_HOOK "/run/systemd/resolve.hook/io.systemd.Machine" /* Path where to connect to send varlink prekill events */ #define VARLINK_DIR_OOMD_PREKILL_HOOK "/run/systemd/oomd.prekill.hook/" +/* Directory whose sockets receive io.systemd.SysUpdate.Notify() after a successful sysupdate run */ +#define VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK "/run/systemd/sysupdate/notify/" /* Recommended baseline - see README for details */ #define KERNEL_BASELINE_VERSION "5.14" 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..e7d7b652281ae 100644 --- a/src/basic/devnum-util.c +++ b/src/basic/devnum-util.c @@ -18,6 +18,9 @@ int parse_devnum(const char *s, dev_t *ret) { size_t n; int r; + assert(s); + assert(ret); + n = strspn(s, DIGITS); if (n == 0) return -EINVAL; diff --git a/src/basic/devnum-util.h b/src/basic/devnum-util.h index 8588b434fba79..b5ae0dee57521 100644 --- a/src/basic/devnum-util.h +++ b/src/basic/devnum-util.h @@ -56,3 +56,9 @@ static inline bool devnum_is_zero(dev_t d) { #define DEVNUM_TO_PTR(u) ((void*) (uintptr_t) (u)) #define PTR_TO_DEVNUM(p) ((dev_t) ((uintptr_t) (p))) + +/* Convert a userspace dev_t (as returned by stat()) to the kernel's internal dev_t encoding. stat() returns + * new_encode_dev(s_dev), while various kernel interfaces (e.g. the BPF sb helpers, or unix_diag's + * udiag_vfs_dev) report s_dev directly, which uses MKDEV(major, minor) = (major << 20) | minor. */ +#define STAT_DEV_TO_KERNEL(dev) \ + ((uint32_t) major(dev) << 20 | (uint32_t) minor(dev)) 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..0fd78483f854c 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,46 +33,18 @@ 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)) +/* Resolve a single optional symbol from an already-opened library handle. The pointer variable is expected + * to be named sym_ (same convention as DLSYM_ARG). Only assigns on success, so the pointer keeps its + * pre-existing value if the symbol is not present — useful for fallback initialization. dlerror() is + * cleared first so callers can distinguish "symbol not found" from "symbol's value is NULL" by checking + * dlerror() after; for function symbols (which can never be NULL on success) the _v check below is + * sufficient. */ +#define DLSYM_OPTIONAL(dl, name) \ + ({ \ + (void) dlerror(); \ + typeof(sym_##name) _v = (typeof(sym_##name)) dlsym((dl), #name); \ + if (_v) sym_##name = _v; \ + }) /* 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 diff --git a/src/basic/efivars.h b/src/basic/efivars.h index 22e0eab9a0afa..368fe12a49acc 100644 --- a/src/basic/efivars.h +++ b/src/basic/efivars.h @@ -5,7 +5,7 @@ #include "sd-id128.h" -#include "efivars-fundamental.h" /* IWYU pragma: export */ +#include "../fundamental/efivars.h" /* IWYU pragma: export */ #define EFI_VENDOR_LOADER SD_ID128_MAKE(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f) #define EFI_VENDOR_LOADER_STR SD_ID128_MAKE_UUID_STR(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f) diff --git a/src/basic/env-file.c b/src/basic/env-file.c index 2e15e7eeb7d57..7689c653a28b2 100644 --- a/src/basic/env-file.c +++ b/src/basic/env-file.c @@ -10,7 +10,7 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" -#include "label.h" +#include "label-util.h" #include "log.h" #include "string-util.h" #include "strv.h" @@ -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/env-util.h b/src/basic/env-util.h index 28338a1458e07..4063517660b30 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -3,6 +3,8 @@ #include "basic-forward.h" +#define ENVIRONMENT_ASSIGNMENTS_MAX 16384U + size_t sc_arg_max(void); bool env_name_is_valid(const char *e); diff --git a/src/basic/errno-util.h b/src/basic/errno-util.h index eb2941253dd73..195d13208b460 100644 --- a/src/basic/errno-util.h +++ b/src/basic/errno-util.h @@ -218,10 +218,13 @@ static inline bool ERRNO_IS_NEG_DEVICE_ABSENT_OR_EMPTY(intmax_t r) { } _DEFINE_ABS_WRAPPER(DEVICE_ABSENT_OR_EMPTY); -/* Quite often we want to handle cases where the backing FS doesn't support extended attributes at all and - * where it simply doesn't have the requested xattr the same way */ +/* Quite often we want to handle cases where the backing FS doesn't support extended attributes at all, + * where the path component carrying the xattr is missing, and where it simply doesn't have the requested + * xattr the same way. Note that getxattr(2) does not enumerate -ENOENT in its own error list, but inherits + * it via stat(2) (see ERRORS in getxattr(2)) for the path-component-missing case. */ static inline bool ERRNO_IS_NEG_XATTR_ABSENT(intmax_t r) { return r == -ENODATA || + r == -ENOENT || ERRNO_IS_NEG_NOT_SUPPORTED(r); } _DEFINE_ABS_WRAPPER(XATTR_ABSENT); diff --git a/src/basic/escape.c b/src/basic/escape.c index e1771bf432278..0b8f2755e9f57 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 @@ -378,20 +379,22 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape if (console_width == 0) return strdup(""); - ans = new(char, MIN(strlen(s), console_width) * 4 + 1); - if (!ans) - return NULL; + size_t len_forced_ellipsis = FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS) ? STRLEN("...") : 0; + size_t len_s = strlen(s); - memset(ans, '_', MIN(strlen(s), console_width) * 4); - ans[MIN(strlen(s), console_width) * 4] = 0; + size_t len_body = MIN(console_width, SIZE_MAX - 1); /* We need room for the NUL byte */ + if (len_body > len_forced_ellipsis && len_s <= (len_body - len_forced_ellipsis) / 4) + len_body = len_s * 4 + len_forced_ellipsis; - bool force_ellipsis = FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS); + ans = new(char, len_body + 1); + if (!ans) + return NULL; for (f = s, t = prev = prev2 = ans; ; f++) { char *tmp_t = t; if (!*f) { - if (force_ellipsis) + if (len_forced_ellipsis != 0) break; *t = 0; @@ -401,7 +404,7 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape if ((unsigned char) *f < ' ' || (!FLAGS_SET(flags, XESCAPE_8_BIT) && (unsigned char) *f >= 127) || *f == '\\' || (bad && strchr(bad, *f))) { - if ((size_t) (t - ans) + 4 + 3 * force_ellipsis > console_width) + if ((size_t) (t - ans) + 4 + len_forced_ellipsis > len_body) break; *(t++) = '\\'; @@ -409,7 +412,7 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape *(t++) = hexchar(*f >> 4); *(t++) = hexchar(*f); } else { - if ((size_t) (t - ans) + 1 + 3 * force_ellipsis > console_width) + if ((size_t) (t - ans) + 1 + len_forced_ellipsis > len_body) break; *(t++) = *f; @@ -421,16 +424,16 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, XEscape } /* We can just write where we want, since chars are one-byte */ - size_t c = MIN(console_width, 3u); /* If the console is too narrow, write fewer dots */ + size_t c = MIN(len_body, STRLEN("...")); /* If the console is too narrow, write fewer dots */ size_t off; - if (console_width - c >= (size_t) (t - ans)) + if (len_body - c >= (size_t) (t - ans)) off = (size_t) (t - ans); - else if (console_width - c >= (size_t) (prev - ans)) + else if (len_body - c >= (size_t) (prev - ans)) off = (size_t) (prev - ans); - else if (console_width - c >= (size_t) (prev2 - ans)) + else if (len_body - c >= (size_t) (prev2 - ans)) off = (size_t) (prev2 - ans); else - off = console_width - c; + off = len_body - c; assert(off <= (size_t) (t - ans)); memcpy(ans + off, "...", c); @@ -447,10 +450,11 @@ 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. May be + * reversed with cunescape(). */ assert(s || len == 0); @@ -467,7 +471,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..4ea4be594e10e 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); } @@ -272,3 +301,31 @@ void ether_addr_mark_random(struct ether_addr *addr) { addr->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ addr->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ } + +int hw_addr_ensure_broadcast(struct hw_addr_data *bcast_addr, uint16_t arp_type) { + assert(bcast_addr); + + if (!hw_addr_is_null(bcast_addr)) + return 0; + + switch (arp_type) { + case ARPHRD_ETHER: + *bcast_addr = (struct hw_addr_data) { + .length = ETH_ALEN, + .ether = {{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}, + }; + return 0; + case ARPHRD_INFINIBAND: + *bcast_addr = (struct hw_addr_data) { + .length = INFINIBAND_ALEN, + .infiniband = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, 0x40, 0x1b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, + }, + }; + return 0; + default: + return -EAFNOSUPPORT; + } +} diff --git a/src/basic/ether-addr-util.h b/src/basic/ether-addr-util.h index cf014a95273e8..42b899e7ebc86 100644 --- a/src/basic/ether-addr-util.h +++ b/src/basic/ether-addr-util.h @@ -7,10 +7,6 @@ #include "basic-forward.h" -/* This is MAX_ADDR_LEN as defined in linux/netdevice.h, but net/if_arp.h - * defines a macro of the same name with a much lower size. */ -#define HW_ADDR_MAX_SIZE 32 - struct hw_addr_data { size_t length; union { @@ -57,6 +53,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; @@ -105,3 +102,5 @@ extern const struct hash_ops ether_addr_hash_ops; extern const struct hash_ops ether_addr_hash_ops_free; void ether_addr_mark_random(struct ether_addr *addr); + +int hw_addr_ensure_broadcast(struct hw_addr_data *bcast_addr, uint16_t arp_type); 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..c6e78c59b6954 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; @@ -572,7 +581,7 @@ bool fdname_is_valid(const char *s) { int fd_get_path(int fd, char **ret) { int r; - assert(fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(fd)); if (fd == AT_FDCWD) return safe_getcwd(ret); @@ -772,7 +781,7 @@ int rearrange_stdio(int original_input_fd, int original_output_fd, int original_ } int fd_reopen(int fd, int flags) { - assert(fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(fd)); assert(!FLAGS_SET(flags, O_CREAT)); /* Reopens the specified fd with new flags. This is useful for convert an O_PATH fd into a regular one, or to @@ -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 @@ -1042,7 +1052,7 @@ static bool is_literal_root(const char *p) { } int path_is_root_at(int dir_fd, const char *path) { - assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(dir_fd)); if (dir_fd == XAT_FDROOT && isempty(path)) return true; @@ -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,15 +1079,15 @@ 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; - assert(fd1 >= 0 || IN_SET(fd1, AT_FDCWD, XAT_FDROOT)); - assert(fd2 >= 0 || IN_SET(fd2, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(fd1)); + assert(wildcard_fd_is_valid(fd2)); r = xstatx(fd1, /* path = */ NULL, AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, @@ -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..86bddb6dac447 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -64,6 +64,12 @@ assert_cc(XAT_FDROOT != AT_FDCWD); assert_cc(XAT_FDROOT < -ERRNO_MAX); +/* Checks whether the specified fd is acceptable as a *at() directory fd that supports the two "wildcard" + * values: it's either a regular, valid fd (i.e. >= 0), or one of the special AT_FDCWD/XAT_FDROOT values. */ +static inline bool wildcard_fd_is_valid(int fd) { + return fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT); +} + int close_nointr(int fd); int safe_close(int fd); void safe_close_pair(int p[static 2]); @@ -112,6 +118,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 +187,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/fiber-ops.c b/src/basic/fiber-ops.c new file mode 100644 index 0000000000000..330630f9dc636 --- /dev/null +++ b/src/basic/fiber-ops.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include +#include +#include + +#include "errno-util.h" +#include "fiber-ops.h" + +static thread_local const FiberOps *fiber_ops = NULL; + +bool fiber_ops_is_set(void) { + return fiber_ops != NULL; +} + +void fiber_ops_set(const FiberOps *ops) { + fiber_ops = ops; +} + +int fiber_ops_ppoll(struct pollfd *fds, size_t n_fds, const struct timespec *timeout, const sigset_t *sigmask) { + if (fiber_ops) + return fiber_ops->ppoll(fds, n_fds, timeout, sigmask); + + return RET_NERRNO(ppoll(fds, n_fds, timeout, sigmask)); +} + +ssize_t fiber_ops_read(int fd, void *buf, size_t count) { + if (fiber_ops) + return fiber_ops->read(fd, buf, count); + + ssize_t n = read(fd, buf, count); + return n < 0 ? -errno : n; +} + +ssize_t fiber_ops_write(int fd, const void *buf, size_t count) { + if (fiber_ops) + return fiber_ops->write(fd, buf, count); + + return RET_NERRNO(write(fd, buf, count)); +} + +sd_future* fiber_ops_timeout(uint64_t timeout) { + assert(fiber_ops); + + return fiber_ops->timeout(timeout); +} + +sd_future* fiber_ops_cancel_wait_unref(sd_future *f) { + assert(fiber_ops); + + return fiber_ops->cancel_wait_unref(f); +} diff --git a/src/basic/fiber-ops.h b/src/basic/fiber-ops.h new file mode 100644 index 0000000000000..64bd82353ba16 --- /dev/null +++ b/src/basic/fiber-ops.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "basic-forward.h" + +typedef struct sd_future sd_future; + +/* Hooks installed on a fiber so that functions in src/basic can transparently defer to the suspending + * variants in sd-future when invoked from a running fiber. Populated by sd_fiber_new() with pointers to the + * implementations in fiber-ops.c. */ +typedef struct FiberOps { + int (*ppoll)(struct pollfd *fds, size_t n_fds, const struct timespec *timeout, const sigset_t *sigmask); + ssize_t (*read)(int fd, void *buf, size_t count); + ssize_t (*write)(int fd, const void *buf, size_t count); + sd_future* (*timeout)(uint64_t timeout); + sd_future* (*cancel_wait_unref)(sd_future *f); +} FiberOps; + +bool fiber_ops_is_set(void); +void fiber_ops_set(const FiberOps *fiber_ops); + +int fiber_ops_ppoll(struct pollfd *fds, size_t n_fds, const struct timespec *timeout, const sigset_t *sigmask); +ssize_t fiber_ops_read(int fd, void *buf, size_t count); +ssize_t fiber_ops_write(int fd, const void *buf, size_t count); + +/* Mirror of SD_FIBER_TIMEOUT() for code under src/basic that doesn't include sd-future.h: dispatches + * through FiberOps so the actual sd_fiber_timeout() implementation lives in libsystemd. */ +sd_future* fiber_ops_timeout(uint64_t timeout); +sd_future* fiber_ops_cancel_wait_unref(sd_future *f); +DEFINE_TRIVIAL_CLEANUP_FUNC(sd_future*, fiber_ops_cancel_wait_unref); + +#define FIBER_OPS_TIMEOUT(timeout) _FIBER_OPS_TIMEOUT(UNIQ, (timeout)) +#define _FIBER_OPS_TIMEOUT(uniq, timeout) \ + _unused_ _cleanup_(fiber_ops_cancel_wait_unrefp) sd_future *UNIQ_T(_fot_, uniq) = fiber_ops_timeout(timeout) diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 90436f6ecf820..d7b4a02cbb261 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -7,13 +7,16 @@ #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 "label.h" +#include "io-util.h" +#include "iovec-util.h" +#include "label-util.h" #include "log.h" #include "mkdir.h" #include "nulstr-util.h" @@ -397,6 +400,26 @@ int write_string_filef( return write_string_file(fn, p, flags); } +int write_string_filef_at( + int dir_fd, + const char *fn, + WriteStringFileFlags flags, + const char *format, ...) { + + _cleanup_free_ char *p = NULL; + va_list ap; + int r; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return write_string_file_at(dir_fd, fn, p, flags); +} + int write_base64_file_at( int dir_fd, const char *fn, @@ -417,17 +440,31 @@ int read_one_line_file_at(int dir_fd, const char *filename, char **ret) { _cleanup_fclose_ FILE *f = NULL; int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(wildcard_fd_is_valid(dir_fd)); assert(filename); assert(ret); - r = fopen_unlocked_at(dir_fd, filename, "re", 0, &f); + r = fopen_unlocked_at(dir_fd, filename, "re", /* open_flags= */ 0, &f); if (r < 0) return r; return read_line(f, LONG_LINE_MAX, ret); } +int read_boolean_file_at(int dir_fd, const char *filename) { + _cleanup_free_ char *s = NULL; + int r; + + assert(wildcard_fd_is_valid(dir_fd)); + assert(filename); + + r = read_one_line_file_at(dir_fd, filename, &s); + if (r < 0) + return r; + + return parse_boolean(s); +} + int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *buf = NULL; @@ -1007,13 +1044,19 @@ static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int /* A combination of fopen() with openat() */ - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(wildcard_fd_is_valid(dir_fd)); assert(mode); assert(ret); if (dir_fd == AT_FDCWD && path && open_flags == 0) f = fopen(path, mode); - else { + else if (dir_fd == XAT_FDROOT && path && open_flags == 0) { + _cleanup_free_ char *j = strjoin("/", path); + if (!j) + return -ENOMEM; + + f = fopen(j, mode); + } else { _cleanup_close_ int fd = -EBADF; int mode_flags; @@ -1048,7 +1091,7 @@ static int xfopenat_unix_socket(int dir_fd, const char *path, const char *bind_n FILE *f; int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(wildcard_fd_is_valid(dir_fd)); assert(ret); sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); @@ -1096,7 +1139,7 @@ int xfopenat_full( FILE *f = NULL; /* avoid false maybe-uninitialized warning */ int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(wildcard_fd_is_valid(dir_fd)); assert(mode); assert(ret); @@ -1367,6 +1410,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 +1700,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(wildcard_fd_is_valid(dir_fd)); + + /* 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..d10b04c9078a1 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -56,6 +56,7 @@ static inline int write_string_file(const char *fn, const char *line, WriteStrin return write_string_file_at(AT_FDCWD, fn, line, flags); } int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4); +int write_string_filef_at(int dir_fd, const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(4, 5); int write_base64_file_at(int dir_fd, const char *fn, const struct iovec *data, WriteStringFileFlags flags); @@ -63,6 +64,10 @@ int read_one_line_file_at(int dir_fd, const char *filename, char **ret); static inline int read_one_line_file(const char *filename, char **ret) { return read_one_line_file_at(AT_FDCWD, filename, ret); } +int read_boolean_file_at(int dir_fd, const char *filename); +static inline int read_boolean_file(const char *filename) { + return read_boolean_file_at(AT_FDCWD, filename); +} int read_full_file_full(int dir_fd, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, const char *bind_name, char **ret_contents, size_t *ret_size); static inline int read_full_file_at(int dir_fd, const char *filename, char **ret_contents, size_t *ret_size) { return read_full_file_full(dir_fd, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size); @@ -163,3 +168,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/filesystem-sets.py b/src/basic/filesystem-sets.py index 18daa48681eda..be08664aef65a 100755 --- a/src/basic/filesystem-sets.py +++ b/src/basic/filesystem-sets.py @@ -124,12 +124,12 @@ 'z3fold': ['Z3FOLD_MAGIC'], 'zonefs': ['ZONEFS_MAGIC'], 'zsmalloc': ['ZSMALLOC_MAGIC'], -} +} # fmt: skip # System magics are sometimes not unique, because file systems got new # revisions or got renamed. Let's prefer newer over older here, and thus ignore # the old names. -OBSOLETE_NAMES = { +OBSOLETE_NAMES = { 'cpuset', # magic taken over by cgroupfs 'devtmpfs', # not a file system of its own, but just a "named superblock" of tmpfs 'ext2', # ext4 is the newest revision of ext2 + ext3 @@ -142,115 +142,116 @@ 'nfs', # nfs4 is the newest revision of nfs 'pvfs2', # orangefs is the new name of pvfs2 'smb3', # smb3 is an alias for cifs -} +} # fmt: skip FILESYSTEM_SETS = [ ( - "@basic-api", - "Basic filesystem API", - "cgroup", - "cgroup2", - "devpts", - "devtmpfs", - "mqueue", - "proc", - "sysfs", + '@basic-api', + 'Basic filesystem API', + 'cgroup', + 'cgroup2', + 'devpts', + 'devtmpfs', + 'mqueue', + 'proc', + 'sysfs', ), ( - "@anonymous", - "Anonymous inodes", - "anon_inodefs", - "pipefs", - "sockfs", + '@anonymous', + 'Anonymous inodes', + 'anon_inodefs', + 'pipefs', + 'sockfs', ), ( - "@application", - "Application virtual filesystems", - "autofs", - "fuse", - "overlay", + '@application', + 'Application virtual filesystems', + 'autofs', + 'fuse', + 'overlay', ), ( - "@auxiliary-api", - "Auxiliary filesystem API", - "binfmt_misc", - "configfs", - "efivarfs", - "fusectl", - "hugetlbfs", - "rpc_pipefs", - "securityfs", + '@auxiliary-api', + 'Auxiliary filesystem API', + 'binfmt_misc', + 'configfs', + 'efivarfs', + 'fusectl', + 'hugetlbfs', + 'rpc_pipefs', + 'securityfs', ), ( - "@common-block", - "Common block device filesystems", - "btrfs", - "erofs", - "exfat", - "ext4", - "f2fs", - "iso9660", - "ntfs3", - "squashfs", - "udf", - "vfat", - "xfs", + '@common-block', + 'Common block device filesystems', + 'btrfs', + 'erofs', + 'exfat', + 'ext4', + 'f2fs', + 'iso9660', + 'ntfs3', + 'squashfs', + 'udf', + 'vfat', + 'xfs', ), ( - "@historical-block", - "Historical block device filesystems", - "ext2", - "ext3", - "minix", + '@historical-block', + 'Historical block device filesystems', + 'ext2', + 'ext3', + 'minix', ), ( - "@network", - "Well-known network filesystems", - "afs", - "ceph", - "cifs", - "gfs", - "gfs2", - "ncp", - "ncpfs", - "nfs", - "nfs4", - "ocfs2", - "orangefs", - "pvfs2", - "smb3", - "smbfs", + '@network', + 'Well-known network filesystems', + 'afs', + 'ceph', + 'cifs', + 'gfs', + 'gfs2', + 'ncp', + 'ncpfs', + 'nfs', + 'nfs4', + 'ocfs2', + 'orangefs', + 'pvfs2', + 'smb3', + 'smbfs', ), ( - "@privileged-api", - "Privileged filesystem API", - "bpf", - "debugfs", - "pstore", - "tracefs", + '@privileged-api', + 'Privileged filesystem API', + 'bpf', + 'debugfs', + 'pstore', + 'tracefs', ), ( - "@security", - "Security/MAC API VFS", - "apparmorfs", - "selinuxfs", - "smackfs", + '@security', + 'Security/MAC API VFS', + 'apparmorfs', + 'selinuxfs', + 'smackfs', ), ( - "@temporary", - "Temporary filesystems", - "ramfs", - "tmpfs", + '@temporary', + 'Temporary filesystems', + 'ramfs', + 'tmpfs', ), ( - "@known", - "All known filesystems declared in the kernel", + '@known', + 'All known filesystems declared in the kernel', *NAME_TO_MAGIC.keys(), ), ] + def generate_gperf(): - print("""\ + print('''\ /* SPDX-License-Identifier: LGPL-2.1-or-later */ %{ #if __GNUC__ >= 15 @@ -275,12 +276,13 @@ def generate_gperf(): %omit-struct-type %struct-type %includes -%%""") +%%''') for name, magics in NAME_TO_MAGIC.items(): - print(f"{name + ',':16} {{{', '.join(magics)}}}") + print(f'{name + ",":16} {{{", ".join(magics)}}}') + def generate_fs_type_to_string(): - print("""\ + print('''\ #include #include "filesystems.h" @@ -290,7 +292,7 @@ def generate_fs_type_to_string(): #define PROJECT_FILE __FILE__ const char* fs_type_to_string(statfs_f_type_t magic) { - switch (magic) {""") + switch (magic) {''') for name, magics in NAME_TO_MAGIC.items(): if name in OBSOLETE_NAMES: @@ -299,10 +301,11 @@ def generate_fs_type_to_string(): print(f' case (statfs_f_type_t) {magic}:') print(f' return "{name}";') - print("""\ + print('''\ } return NULL; -}""") +}''') + def generate_fs_in_group(): print('bool fs_in_group(const struct statfs *st, FilesystemGroups fs_group) {') @@ -312,14 +315,14 @@ def generate_fs_in_group(): magics = sorted(set(sum((NAME_TO_MAGIC[fs] for fs in filesystems), start=[]))) enum = 'FILESYSTEM_SET_' + name[1:].upper().replace('-', '_') print(f' case {enum}:') - opts = '\n || '.join(f'F_TYPE_EQUAL(st->f_type, {magic})' - for magic in magics) + opts = '\n || '.join(f'F_TYPE_EQUAL(st->f_type, {magic})' for magic in magics) print(f' return {opts};') print(' default: assert_not_reached();') print(' }') print('}') + def generate_filesystem_sets(): print('const FilesystemSet filesystem_sets[_FILESYSTEM_SET_MAX] = {') @@ -329,37 +332,45 @@ def generate_filesystem_sets(): print(f' [{enum}] = {{') print(f' .name = "{name}",') print(f' .help = "{desc}",') - print(f' .value =') + print(' .value =') for filesystem in filesystems: print(f' "{filesystem}\\0"') print(' },') print('};') + def magic_defines(): cpp = os.environ['CPP'].split() out = subprocess.check_output( [*cpp, '-dM', '-include', 'linux/magic.h', '-'], stdin=subprocess.DEVNULL, - text=True) + text=True, + ) for line in out.splitlines(): _, name, *rest = line.split() - if ('_MAGIC' in name - and rest and rest[0].startswith('0x') - and name not in { + if ( + '_MAGIC' in name + and rest + and rest[0].startswith('0x') + and name + not in { 'STACK_END_MAGIC', 'MTD_INODE_FS_MAGIC', 'FUTEXFS_SUPER_MAGIC', 'CRAMFS_MAGIC_WEND', - }): + } + ): yield name + def check(): kernel_magics = set(magic_defines()) our_magics = set(sum(NAME_TO_MAGIC.values(), start=[])) extra = kernel_magics - our_magics if extra: - sys.exit(f"kernel knows additional filesystem magics: {', '.join(sorted(extra))}") + sys.exit(f'kernel knows additional filesystem magics: {", ".join(sorted(extra))}') + if __name__ == '__main__': for arg in sys.argv[1:]: diff --git a/src/basic/format-util.c b/src/basic/format-util.c index 4c601e553feb4..30ab0fd3b8d5f 100644 --- a/src/basic/format-util.c +++ b/src/basic/format-util.c @@ -25,6 +25,7 @@ char* format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) { const suffix_table *table; size_t n; + assert(buf); assert_cc(ELEMENTSOF(table_iec) == ELEMENTSOF(table_si)); if (t == UINT64_MAX) diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index f790ca4e136b5..2e9a27f2a855a 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -3,17 +3,18 @@ #include #include #include +#include #include #include "alloc-util.h" -#include "btrfs.h" +#include "btrfs-util.h" #include "chattr-util.h" #include "dirent-util.h" #include "errno-util.h" #include "fd-util.h" #include "fs-util.h" #include "hostname-util.h" -#include "label.h" +#include "label-util.h" #include "lock-util.h" #include "log.h" #include "mkdir.h" @@ -1130,15 +1131,67 @@ 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. open_tree() only ever returns O_PATH fds, so this helper is for O_PATH + * acquisition only. */ + + static bool can_open_tree = true; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(path); + assert(FLAGS_SET(open_flags, O_PATH)); + + if (can_open_tree) { + int fd = RET_NERRNO(open_tree(dir_fd, path, + OPEN_TREE_CLOEXEC | + (FLAGS_SET(open_flags, O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0))); + if (fd >= 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(fd); + if (q < 0) { + safe_close(fd); + return q; + } + } + + return fd; + } + if (fd != -EPERM && !ERRNO_IS_NEG_NOT_SUPPORTED(fd)) + return fd; + + 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; int r; - assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(dir_fd)); - /* An inode cannot be both a directory and a regular file at the same time. */ - assert(!(FLAGS_SET(open_flags, O_DIRECTORY) && FLAGS_SET(xopen_flags, XO_REGULAR))); + /* 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) + FLAGS_SET(xopen_flags, XO_SOCKET) <= 1); + /* 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 +1206,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 +1240,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 +1302,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 +1316,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 +1346,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 +1373,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/getopt-defs.h b/src/basic/getopt-defs.h deleted file mode 100644 index 9abef6f156699..0000000000000 --- a/src/basic/getopt-defs.h +++ /dev/null @@ -1,77 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include - -#define SYSTEMD_GETOPT_SHORT_OPTIONS "hDbsz:" - -#define COMMON_GETOPT_ARGS \ - ARG_LOG_LEVEL = 0x100, \ - ARG_LOG_TARGET, \ - ARG_LOG_COLOR, \ - ARG_LOG_LOCATION, \ - ARG_LOG_TIME - -#define SYSTEMD_GETOPT_ARGS \ - ARG_UNIT, \ - ARG_SYSTEM, \ - ARG_USER, \ - ARG_TEST, \ - ARG_NO_PAGER, \ - ARG_VERSION, \ - ARG_DUMP_CONFIGURATION_ITEMS, \ - ARG_DUMP_BUS_PROPERTIES, \ - ARG_BUS_INTROSPECT, \ - ARG_DUMP_CORE, \ - ARG_CRASH_CHVT, \ - ARG_CRASH_SHELL, \ - ARG_CRASH_REBOOT, \ - ARG_CRASH_ACTION, \ - ARG_CONFIRM_SPAWN, \ - ARG_SHOW_STATUS, \ - ARG_DESERIALIZE, \ - ARG_SWITCHED_ROOT, \ - ARG_DEFAULT_STD_OUTPUT, \ - ARG_DEFAULT_STD_ERROR, \ - ARG_MACHINE_ID, \ - ARG_SERVICE_WATCHDOGS - -#define SHUTDOWN_GETOPT_ARGS \ - ARG_EXIT_CODE, \ - ARG_TIMEOUT - -#define COMMON_GETOPT_OPTIONS \ - { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, \ - { "log-target", required_argument, NULL, ARG_LOG_TARGET }, \ - { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, \ - { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, \ - { "log-time", optional_argument, NULL, ARG_LOG_TIME } - -#define SYSTEMD_GETOPT_OPTIONS \ - { "unit", required_argument, NULL, ARG_UNIT }, \ - { "system", no_argument, NULL, ARG_SYSTEM }, \ - { "user", no_argument, NULL, ARG_USER }, \ - { "test", no_argument, NULL, ARG_TEST }, \ - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, \ - { "help", no_argument, NULL, 'h' }, \ - { "version", no_argument, NULL, ARG_VERSION }, \ - { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS }, \ - { "dump-bus-properties", no_argument, NULL, ARG_DUMP_BUS_PROPERTIES }, \ - { "bus-introspect", required_argument, NULL, ARG_BUS_INTROSPECT }, \ - { "dump-core", optional_argument, NULL, ARG_DUMP_CORE }, \ - { "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT }, \ - { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL }, \ - { "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT }, \ - { "crash-action", required_argument, NULL, ARG_CRASH_ACTION }, \ - { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN }, \ - { "show-status", optional_argument, NULL, ARG_SHOW_STATUS }, \ - { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, \ - { "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT }, \ - { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, }, \ - { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, }, \ - { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, \ - { "service-watchdogs", required_argument, NULL, ARG_SERVICE_WATCHDOGS } - -#define SHUTDOWN_GETOPT_OPTIONS \ - { "exit-code", required_argument, NULL, ARG_EXIT_CODE }, \ - { "timeout", required_argument, NULL, ARG_TIMEOUT } 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..a2868e24557bd 100644 --- a/src/basic/hashmap.c +++ b/src/basic/hashmap.c @@ -300,16 +300,20 @@ _destructor_ static void cleanup_pools(void) { #endif static unsigned n_buckets(HashmapBase *h) { + assert(h); return h->has_indirect ? h->indirect.n_buckets : hashmap_type_info[h->type].n_direct_buckets; } static unsigned n_entries(HashmapBase *h) { + assert(h); return h->has_indirect ? h->indirect.n_entries : h->n_direct_entries; } static void n_entries_inc(HashmapBase *h) { + assert(h); + if (h->has_indirect) h->indirect.n_entries++; else @@ -317,6 +321,8 @@ static void n_entries_inc(HashmapBase *h) { } static void n_entries_dec(HashmapBase *h) { + assert(h); + if (h->has_indirect) h->indirect.n_entries--; else @@ -324,11 +330,13 @@ static void n_entries_dec(HashmapBase *h) { } static void* storage_ptr(HashmapBase *h) { + assert(h); return h->has_indirect ? h->indirect.storage : h->direct.storage; } static uint8_t* hash_key(HashmapBase *h) { + assert(h); return h->has_indirect ? h->indirect.hash_key : shared_hash_key; } @@ -337,6 +345,8 @@ static unsigned base_bucket_hash(HashmapBase *h, const void *p) { struct siphash state; uint64_t hash; + assert(h); + siphash24_init(&state, hash_key(h)); h->hash_ops->hash(p, &state); @@ -348,6 +358,8 @@ static unsigned base_bucket_hash(HashmapBase *h, const void *p) { #define bucket_hash(h, p) base_bucket_hash(HASHMAP_BASE(h), p) static void base_set_dirty(HashmapBase *h) { + assert(h); + h->dirty = true; } #define hashmap_set_dirty(h) base_set_dirty(HASHMAP_BASE(h)) @@ -372,6 +384,7 @@ static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) { } static struct hashmap_base_entry* bucket_at(HashmapBase *h, unsigned idx) { + assert(h); return CAST_ALIGN_PTR( struct hashmap_base_entry, (uint8_t *) storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size); @@ -390,6 +403,7 @@ static struct set_entry *set_bucket_at(Set *h, unsigned idx) { } static struct ordered_hashmap_entry* bucket_at_swap(struct swap_entries *swap, unsigned idx) { + assert(swap); return &swap->e[idx - _IDX_SWAP_BEGIN]; } @@ -407,6 +421,7 @@ static struct hashmap_base_entry* bucket_at_virtual(HashmapBase *h, struct swap_ } static dib_raw_t* dib_raw_ptr(HashmapBase *h) { + assert(h); return (dib_raw_t*) ((uint8_t*) storage_ptr(h) + hashmap_type_info[h->type].entry_size * n_buckets(h)); } @@ -456,6 +471,8 @@ static unsigned skip_free_buckets(HashmapBase *h, unsigned idx) { } static void bucket_mark_free(HashmapBase *h, unsigned idx) { + assert(h); + memzero(bucket_at(h, idx), hashmap_type_info[h->type].entry_size); bucket_set_dib(h, idx, DIB_FREE); } @@ -464,6 +481,7 @@ static void bucket_move_entry(HashmapBase *h, struct swap_entries *swap, unsigned from, unsigned to) { struct hashmap_base_entry *e_from, *e_to; + assert(h); assert(from != to); e_from = bucket_at_virtual(h, swap, from); @@ -505,6 +523,9 @@ static unsigned prev_idx(HashmapBase *h, unsigned idx) { } static void* entry_value(HashmapBase *h, struct hashmap_base_entry *e) { + assert(h); + assert(e); + switch (h->type) { case HASHMAP_TYPE_PLAIN: @@ -527,6 +548,7 @@ static void base_remove_entry(HashmapBase *h, unsigned idx) { assert(dibs[idx] != DIB_RAW_FREE); #if ENABLE_DEBUG_HASHMAP + assert(h); h->debug.rem_count++; h->debug.last_rem_idx = idx; #endif @@ -848,6 +870,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 +882,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 +894,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 +906,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; @@ -969,6 +999,7 @@ static bool hashmap_put_robin_hood(HashmapBase *h, unsigned idx, unsigned dib, distance; #if ENABLE_DEBUG_HASHMAP + assert(h); h->debug.put_count++; #endif @@ -1283,6 +1314,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 +1876,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 +2222,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 +2243,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/hostname-util.c b/src/basic/hostname-util.c index 01434d3d641d2..9410dd05d23f9 100644 --- a/src/basic/hostname-util.c +++ b/src/basic/hostname-util.c @@ -18,7 +18,7 @@ char* get_default_hostname_raw(void) { const char *e = secure_getenv("SYSTEMD_DEFAULT_HOSTNAME"); if (e) { - if (hostname_is_valid(e, VALID_HOSTNAME_QUESTION_MARK)) + if (hostname_is_valid(e, VALID_HOSTNAME_QUESTION_MARK|VALID_HOSTNAME_WORD_TOKEN)) return strdup(e); log_debug("Invalid hostname in $SYSTEMD_DEFAULT_HOSTNAME, ignoring: %s", e); @@ -29,7 +29,7 @@ char* get_default_hostname_raw(void) { if (r < 0) log_debug_errno(r, "Failed to parse os-release, ignoring: %m"); else if (f) { - if (hostname_is_valid(f, VALID_HOSTNAME_QUESTION_MARK)) + if (hostname_is_valid(f, VALID_HOSTNAME_QUESTION_MARK|VALID_HOSTNAME_WORD_TOKEN)) return TAKE_PTR(f); log_debug("Invalid hostname in os-release, ignoring: %s", f); @@ -82,7 +82,9 @@ bool hostname_is_valid(const char *s, ValidHostnameFlags flags) { hyphen = true; } else { - if (!valid_ldh_char(*p) && (*p != '?' || !FLAGS_SET(flags, VALID_HOSTNAME_QUESTION_MARK))) + if (!valid_ldh_char(*p) && + (*p != '?' || !FLAGS_SET(flags, VALID_HOSTNAME_QUESTION_MARK)) && + (*p != '$' || !FLAGS_SET(flags, VALID_HOSTNAME_WORD_TOKEN))) return false; dot = false; @@ -124,7 +126,7 @@ char* hostname_cleanup(char *s) { dot = false; hyphen = true; - } else if (valid_ldh_char(*p) || *p == '?') { + } else if (valid_ldh_char(*p) || IN_SET(*p, '?', '$')) { *(d++) = *p; dot = false; hyphen = false; @@ -253,3 +255,123 @@ int machine_spec_valid(const char *s) { return true; } + +bool machine_tag_is_valid(const char *s) { + size_t n = strlen_ptr(s); + if (n <= 0 || n >= 256) + return false; + + /* Don't allow "-" and "." as first char. (This is load-bearing, we want that "+"/"-" can be used as + * prefix for adding/removing tags from the list). */ + if (strchr("-.=", s[0])) + return false; + + /* We allow parameterization of tags, with a "=" as separator */ + const char *eq = strchr(s, '='); + if (eq) { + assert(eq > s); + + /* If there is an '=', then make the same restrictions as for the first char on the last char before it */ + if (strchr("-.", eq[-1])) + return false; + } else { + /* If there's no '=', then make the restriction on the very last character */ + if (strchr("-.", s[n-1])) + return false; + } + + return in_charset(s, ALPHANUMERICAL "-.="); +} + +bool machine_tag_list_is_valid(char **l) { + size_t n = 0; + STRV_FOREACH(i, l) { + n++; + if (n > MACHINE_TAGS_MAX) + return false; + + if (!machine_tag_is_valid(*i)) + return false; + + const char *eq = strchr(*i, '='); + if (!eq) + continue; + + /* Refuse tags with a common part before the '=', that do no also carry the same value. */ + size_t np = eq - *i + 1; + STRV_FOREACH(j, l) { + if (j == i) + break; + + if (streq(*i, *j)) /* Fully identical is OK */ + continue; + + if (strneq(*i, *j, np)) /* Not identical, but same key: refuse */ + return false; + } + } + + return true; +} + +int machine_tags_from_string(const char *s, bool graceful, char ***ret) { + int r; + + assert(ret); + + /* Parses the colon-separated TAGS= machine-info field into a sorted, deduplicated strv. Each tag is + * validated: if 'graceful' is true invalid tags are silently dropped, otherwise an invalid tag makes + * us fail with -EINVAL. The result is NULL if no (valid) tags remain. */ + + if (isempty(s)) { + *ret = NULL; + return 0; + } + + _cleanup_strv_free_ char **l = strv_split(s, ":"); + if (!l) + return -ENOMEM; + + strv_sort_uniq(l); + + if (!graceful) { + if (!machine_tag_list_is_valid(l)) + return -EINVAL; + + *ret = strv_isempty(l) ? NULL : TAKE_PTR(l); + return 0; + } + + size_t n = 0; + _cleanup_strv_free_ char **cleaned = NULL; + STRV_FOREACH(i, l) { + if (!machine_tag_is_valid(*i)) + continue; + + n++; + if (n > MACHINE_TAGS_MAX) + return -E2BIG; + + const char *eq = strchr(*i, '='); + if (eq) { + /* Suppress duplicate assignments */ + bool skip = false; + size_t np = eq - *i + 1; + STRV_FOREACH(j, cleaned) + if (strneq(*i, *j, np)) { + skip = true; + break; + } + + if (skip) + continue; + } + + r = strv_extend(&cleaned, *i); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(cleaned); + return 0; +} diff --git a/src/basic/hostname-util.h b/src/basic/hostname-util.h index 73cd83fbb8d6e..0fdc5b483b71f 100644 --- a/src/basic/hostname-util.h +++ b/src/basic/hostname-util.h @@ -15,6 +15,7 @@ typedef enum ValidHostnameFlags { VALID_HOSTNAME_TRAILING_DOT = 1 << 0, /* Accept trailing dot on multi-label names */ VALID_HOSTNAME_DOT_HOST = 1 << 1, /* Accept ".host" as valid hostname */ VALID_HOSTNAME_QUESTION_MARK = 1 << 2, /* Accept "?" as place holder for hashed machine ID value */ + VALID_HOSTNAME_WORD_TOKEN = 1 << 3, /* Accept "$" as place holder for a word list substitution */ } ValidHostnameFlags; bool hostname_is_valid(const char *s, ValidHostnameFlags flags) _pure_; @@ -47,3 +48,9 @@ int get_pretty_hostname(char **ret); int machine_spec_valid(const char *s); int split_user_at_host(const char *s, char **ret_user, char **ret_host); + +#define MACHINE_TAGS_MAX 1024U + +bool machine_tag_is_valid(const char *s); +bool machine_tag_list_is_valid(char **l); +int machine_tags_from_string(const char *s, bool graceful, char ***ret); diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index a47bdf7149121..bdedfc967ac7f 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -163,6 +163,8 @@ int in_addr_is_localhost_one(int family, const union in_addr_union *u) { } bool in6_addr_is_ipv4_mapped_address(const struct in6_addr *a) { + assert(a); + return a->s6_addr32[0] == 0 && a->s6_addr32[1] == 0 && a->s6_addr32[2] == htobe32(UINT32_C(0x0000ffff)); @@ -559,20 +561,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); @@ -730,6 +730,8 @@ int in4_addr_mask(struct in_addr *addr, unsigned char prefixlen) { int in6_addr_mask(struct in6_addr *addr, unsigned char prefixlen) { unsigned i; + assert(addr); + for (i = 0; i < 16; i++) { uint8_t mask; @@ -843,6 +845,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..d4a95128c850d 100644 --- a/src/basic/io-util.c +++ b/src/basic/io-util.c @@ -1,15 +1,36 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include #include #include +#include /* IWYU pragma: keep */ #include #include #include "errno-util.h" +#include "fiber-ops.h" #include "io-util.h" #include "time-util.h" +uint32_t poll_events_to_epoll(uint32_t events) { + return (events & POLLIN ? EPOLLIN : 0) | + (events & POLLOUT ? EPOLLOUT : 0) | + (events & POLLPRI ? EPOLLPRI : 0) | + (events & POLLERR ? EPOLLERR : 0) | + (events & POLLHUP ? EPOLLHUP : 0) | + (events & POLLRDHUP ? EPOLLRDHUP : 0); +} + +uint32_t epoll_events_to_poll(uint32_t events) { + return (events & EPOLLIN ? POLLIN : 0) | + (events & EPOLLOUT ? POLLOUT : 0) | + (events & EPOLLPRI ? POLLPRI : 0) | + (events & EPOLLERR ? POLLERR : 0) | + (events & EPOLLHUP ? POLLHUP : 0) | + (events & EPOLLRDHUP ? POLLRDHUP : 0); +} + int flush_fd(int fd) { int count = 0; @@ -60,25 +81,44 @@ ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { if (nbytes > (size_t) SSIZE_MAX) return -EINVAL; + /* do_poll == false means "don't wait, return what we have if EAGAIN". If the fd is already + * non-blocking, read() can't block the thread, so the non-fiber path satisfies that semantic + * correctly even from a fiber. Only use the fiber path when the fd is blocking (where read() + * would otherwise block the entire event loop). */ + int flags = 0; + if (fiber_ops_is_set() && !do_poll) { + flags = fcntl(fd, F_GETFL); + if (flags < 0) + return -errno; + } + do { ssize_t k; - k = read(fd, p, nbytes); - if (k < 0) { - if (errno == EINTR) - continue; - - if (errno == EAGAIN && do_poll) { - - /* We knowingly ignore any return value here, - * and expect that any error/EOF is reported - * via read() */ - - (void) fd_wait_for_event(fd, POLLIN, USEC_INFINITY); - continue; + if (fiber_ops_is_set() && (do_poll || !FLAGS_SET(flags, O_NONBLOCK))) { + /* On a fiber the read op suspends on EAGAIN until data is available, so we don't + * need a separate poll step or the do_poll knob. */ + k = fiber_ops_read(fd, p, nbytes); + if (k < 0) + return n > 0 ? n : k; + } else { + k = read(fd, p, nbytes); + if (k < 0) { + if (errno == EINTR) + continue; + + if (errno == EAGAIN && do_poll) { + + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via read() */ + + (void) fd_wait_for_event(fd, POLLIN, USEC_INFINITY); + continue; + } + + return n > 0 ? n : -errno; } - - return n > 0 ? n : -errno; } if (k == 0) @@ -128,6 +168,37 @@ int loop_write_full(int fd, const void *buf, size_t nbytes, usec_t timeout) { p = buf; } + /* timeout == 0 means "don't wait, return -EAGAIN if not ready". If the fd is already + * non-blocking, write() can't block the thread, so the non-fiber path satisfies that + * semantic correctly even from a fiber. Only use the fiber path when the fd is blocking + * (where write() would otherwise block the entire event loop). */ + int flags = 0; + if (fiber_ops_is_set() && timeout == 0) { + flags = fcntl(fd, F_GETFL); + if (flags < 0) + return -errno; + } + + if (fiber_ops_is_set() && !FLAGS_SET(flags, O_NONBLOCK)) { + /* On a fiber the write op suspends on EAGAIN until the fd is writable; honor the + * caller's timeout via a deadline scope. */ + FIBER_OPS_TIMEOUT(timestamp_is_set(timeout) ? timeout : USEC_INFINITY); + + while (nbytes > 0) { + ssize_t k = fiber_ops_write(fd, p, nbytes); + if (k < 0) + return (int) k; + if (_unlikely_(nbytes > 0 && k == 0)) /* Can't really happen */ + return -EIO; + + assert((size_t) k <= nbytes); + p += k; + nbytes -= k; + } + + return 0; + } + /* When timeout is 0 or USEC_INFINITY this is not used. But we initialize it to a sensible value. */ end = timestamp_is_set(timeout) ? usec_add(now(CLOCK_MONOTONIC), timeout) : USEC_INFINITY; @@ -211,11 +282,9 @@ int ppoll_usec_full(struct pollfd *fds, size_t n_fds, usec_t timeout, const sigs if (n_fds == 0 && timeout == 0) return 0; - r = ppoll(fds, n_fds, timeout == USEC_INFINITY ? NULL : TIMESPEC_STORE(timeout), ss); - if (r < 0) - return -errno; - if (r == 0) - return 0; + r = fiber_ops_ppoll(fds, n_fds, timeout == USEC_INFINITY ? NULL : TIMESPEC_STORE(timeout), ss); + if (r <= 0) + return r; for (size_t i = 0, n = r; i < n_fds && n > 0; i++) { if (fds[i].revents == 0) @@ -248,6 +317,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/io-util.h b/src/basic/io-util.h index 918108c023b68..6697f88b54611 100644 --- a/src/basic/io-util.h +++ b/src/basic/io-util.h @@ -3,6 +3,13 @@ #include "basic-forward.h" +/* On most architectures POLL* and EPOLL* share numeric values, but sparc allocates POLLRDHUP at + * 0x800 while EPOLLRDHUP is 0x2000 everywhere. Always go through these helpers when crossing + * between poll() and epoll() event masks. Bits outside the mapped set (EPOLLET, EPOLLONESHOT, + * POLLNVAL, …) are dropped. */ +uint32_t poll_events_to_epoll(uint32_t events); +uint32_t epoll_events_to_poll(uint32_t events); + int flush_fd(int fd); ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll); diff --git a/src/basic/iovec-util.c b/src/basic/iovec-util.c index ec8af57e6424f..0be326a18364e 100644 --- a/src/basic/iovec-util.c +++ b/src/basic/iovec-util.c @@ -16,40 +16,55 @@ const struct iovec iovec_empty = { .iov_len = 0, }; +int iovec_alloc(size_t n, struct iovec *ret) { + assert(ret); + + void *buf = malloc(n ?: 1); + if (!buf) + return -ENOMEM; + + *ret = IOVEC_MAKE(buf, n); + return 0; +} + size_t iovec_total_size(const struct iovec *iovec, size_t n) { size_t sum = 0; 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) { @@ -59,6 +74,15 @@ struct iovec* iovec_make_string(struct iovec *iovec, const char *s) { return iovec; } +void iovec_erase(struct iovec *iovec) { + assert(iovec); + + /* Unlike iovec_done_erase(), which derives the buffer size with MALLOC_SIZEOF_SAFE(), this uses + * iov_len as the buffer size. Hence, it can be used with iovec referring to a static array or a + * buffer allocated on the stack. */ + explicit_bzero_safe(iovec->iov_base, iovec->iov_len); +} + void iovec_done_erase(struct iovec *iovec) { assert(iovec); @@ -125,6 +149,21 @@ struct iovec* iovec_memdup(const struct iovec *source, struct iovec *ret) { return ret; } +int iovec_done_and_memdup(struct iovec *iovec, const struct iovec *source) { + assert(iovec); + + if (iovec_equal(iovec, source)) + return 0; + + struct iovec copy; + if (!iovec_memdup(source, ©)) + return -ENOMEM; + + iovec_done(iovec); + *iovec = copy; + return 1; +} + struct iovec* iovec_append(struct iovec *iovec, const struct iovec *append) { assert(iovec_is_valid(iovec)); diff --git a/src/basic/iovec-util.h b/src/basic/iovec-util.h index 0d1d4a7a94d86..ade2e6be34497 100644 --- a/src/basic/iovec-util.h +++ b/src/basic/iovec-util.h @@ -4,14 +4,17 @@ #include /* IWYU pragma: export */ #include "basic-forward.h" -#include "iovec-util-fundamental.h" /* IWYU pragma: export */ + +#include "../fundamental/iovec-util.h" /* IWYU pragma: export */ extern const struct iovec iovec_nul_byte; /* Points to a single NUL byte */ extern const struct iovec iovec_empty; /* Points to an empty, but valid (i.e. non-NULL) pointer */ +int iovec_alloc(size_t n, struct iovec *ret); + 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 +27,19 @@ 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, \ + } + +#define IOVEC_ALLOCA(n) \ + ({ \ + size_t _n_ = (n); \ + IOVEC_MAKE(alloca_safe(_n_), _n_); \ + }) + +void iovec_erase(struct iovec *iovec); 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,7 +48,11 @@ 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); +int iovec_done_and_memdup(struct iovec *iovec, const struct iovec *source); struct iovec* iovec_append(struct iovec *iovec, const struct iovec *append); diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index b4519f808d5e9..da217170c573c 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -6,10 +6,7 @@ #include "iovec-util.h" #include "iovec-wrapper.h" #include "string-util.h" - -struct iovec_wrapper *iovw_new(void) { - return new0(struct iovec_wrapper, 1); -} +#include "unaligned.h" void iovw_done(struct iovec_wrapper *iovw) { assert(iovw); @@ -27,29 +24,50 @@ void iovw_done_free(struct iovec_wrapper *iovw) { iovw_done(iovw); } -struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw) { +struct iovec_wrapper* iovw_free(struct iovec_wrapper *iovw) { if (!iovw) return NULL; - iovw_done_free(iovw); + iovw_done(iovw); return mfree(iovw); } -struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw) { +struct iovec_wrapper* iovw_free_free(struct iovec_wrapper *iovw) { if (!iovw) return NULL; - iovw_done(iovw); + iovw_done_free(iovw); return mfree(iovw); } -int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) { - assert(iovw); +int iovw_compare(const struct iovec_wrapper *a, const struct iovec_wrapper *b) { + int r; - if (len == 0) + if (a == b) return 0; - assert(data); + if (!a || !b) + return CMP(!!a, !!b); + + /* 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_full(struct iovec_wrapper *iovw, bool accept_zero, void *data, size_t len) { + assert(iovw); + assert(data || len == 0); + + if (len == 0 && !accept_zero) + return 0; if (iovw->count >= IOV_MAX) return -E2BIG; @@ -58,7 +76,146 @@ 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_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec *iov) { + assert(iovw); + + if (!iov) + return 0; + + return iovw_put_full(iovw, accept_zero, iov->iov_base, iov->iov_len); +} + +int iovw_put_iovw_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec_wrapper *source) { + int r; + + 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 (accept_zero) { + if (!GREEDY_REALLOC_APPEND(iovw->iovec, iovw->count, source->iovec, source->count)) + return -ENOMEM; + + return 0; + } + + /* When accept_zero is false, we need to filter zero length iovec in source. */ + size_t original_count = iovw->count; + + FOREACH_ARRAY(iovec, source->iovec, source->count) { + r = iovw_put_iov_full(iovw, accept_zero, iovec); + if (r < 0) + goto rollback; + } + + return 0; + +rollback: + iovw->count = original_count; + return r; +} + +int iovw_consume_full(struct iovec_wrapper *iovw, bool accept_zero, void *data, size_t len) { + /* Move data into iovw or free on error */ + int r; + + r = iovw_put_full(iovw, accept_zero, data, len); + if (r <= 0) + free(data); + + return r; +} + +int iovw_consume_iov_full(struct iovec_wrapper *iovw, bool accept_zero, struct iovec *iov) { + int r; + + assert(iovw); + + if (!iov) + return 0; + + r = iovw_put_iov_full(iovw, accept_zero, 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_full(struct iovec_wrapper *iovw, bool accept_zero, const void *data, size_t len) { + assert(iovw); + assert(data || len == 0); + + if (len == 0) + return iovw_put_full(iovw, accept_zero, /* data= */ NULL, /* len= */ 0); + + void *c = memdup(data, len); + if (!c) + return -ENOMEM; + + return iovw_consume_full(iovw, accept_zero, c, len); +} + +int iovw_extend_iov_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec *iov) { + assert(iovw); + + if (!iov) + return 0; + + return iovw_extend_full(iovw, accept_zero, iov->iov_base, iov->iov_len); +} + +int iovw_extend_iovw_full(struct iovec_wrapper *iovw, bool accept_zero, 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_full(iovw, accept_zero, 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 +275,165 @@ void iovw_rebase(struct iovec_wrapper *iovw, void *old, void *new) { } size_t iovw_size(const struct iovec_wrapper *iovw) { - if (!iovw) + if (iovw_isempty(iovw)) return 0; 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 iovw_concat(const struct iovec_wrapper *iovw, struct iovec *ret) { + assert(iovw); + assert(ret); + + /* Squish a series of iovecs into a single iovec. */ + + size_t len = iovw_size(iovw); + if (len == SIZE_MAX) + return -E2BIG; /* Prevent theoretical overflow */ + + /* 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; + + uint8_t *p = buf; + FOREACH_ARRAY(i, iovw->iovec, iovw->count) + p = mempcpy_safe(p, i->iov_base, i->iov_len); + + *p = 0; + + *ret = IOVEC_MAKE(TAKE_PTR(buf), len); + return 0; +} + +char* iovw_to_cstring(const struct iovec_wrapper *iovw) { + assert(iovw); + + /* 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. */ + + _cleanup_(iovec_done) struct iovec iov = {}; + if (iovw_concat(iovw, &iov) < 0) + return NULL; + + assert(!memchr(iov.iov_base, 0, iov.iov_len)); + return TAKE_PTR(iov.iov_base); +} + +int iovec_split(const struct iovec *iov, size_t length_size, struct iovec_wrapper *ret) { int r; - assert(target); + assert(IN_SET(length_size, 1, 2, 4)); + assert(ret); - /* This duplicates the source and merges it into the target. */ + /* This parses the input iovec as length-prefixed data, and stores the result as iovec_wrapper. + * Note, zero-length entries are silently dropped. */ - if (iovw_isempty(source)) + if (!iovec_is_set(iov)) { + *ret = (struct iovec_wrapper) {}; return 0; + } - original_count = target->count; + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + for (struct iovec i = *iov; iovec_is_set(&i); ) { + if (i.iov_len < length_size) + return -EBADMSG; + + size_t len; + switch (length_size) { + case 1: + len = *(uint8_t*) i.iov_base; + break; + case 2: + len = unaligned_read_be16(i.iov_base); + break; + case 4: + len = unaligned_read_be32(i.iov_base); + break; + default: + assert_not_reached(); + } - FOREACH_ARRAY(iovec, source->iovec, source->count) { - void *dup; + iovec_inc(&i, length_size); - dup = memdup(iovec->iov_base, iovec->iov_len); - if (!dup) { - r = -ENOMEM; - goto rollback; - } + if (len == 0) + continue; - r = iovw_consume(target, dup, iovec->iov_len); + if (i.iov_len < len) + return -EBADMSG; + + r = iovw_extend(&iovw, i.iov_base, len); if (r < 0) - goto rollback; + return r; + + iovec_inc(&i, len); } + *ret = TAKE_STRUCT(iovw); return 0; +} -rollback: - for (size_t i = original_count; i < target->count; i++) - iovec_done(target->iovec + i); +int iovw_merge(const struct iovec_wrapper *iovw, size_t length_size, struct iovec *ret) { + assert(IN_SET(length_size, 1, 2, 4)); + assert(ret); - target->count = original_count; - return r; + /* This is the inverse of iovec_split(), and builds a length-prefixed data from iovec_wrapper. + * Note, zero-length entries are silently dropped. */ + + size_t sz = iovw_size(iovw); + if (sz == 0) { + *ret = (struct iovec) {}; + return 0; + } + if (sz == SIZE_MAX) + return -E2BIG; + + if (size_multiply_overflow(length_size, iovw->count)) + return -E2BIG; + + sz = size_add(sz, iovw->count * length_size); + if (sz == SIZE_MAX) + return -E2BIG; + + _cleanup_free_ uint8_t *buf = new(uint8_t, sz); + if (!buf) + return -ENOMEM; + + uint8_t *p = buf; + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + if (iov->iov_len == 0) + continue; + + switch (length_size) { + case 1: + if (iov->iov_len > UINT8_MAX) + return -ERANGE; + + *p = iov->iov_len; + break; + case 2: + if (iov->iov_len > UINT16_MAX) + return -ERANGE; + + unaligned_write_be16(p, iov->iov_len); + break; + case 4: + if (iov->iov_len > UINT32_MAX) + return -ERANGE; + + unaligned_write_be32(p, iov->iov_len); + break; + default: + assert_not_reached(); + } + p += length_size; + + p = mempcpy(p, iov->iov_base, iov->iov_len); + } + + assert(sz >= (size_t) (p - buf)); + sz = p - buf; + + *ret = IOVEC_MAKE(TAKE_PTR(buf), sz); + return 0; } diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index 94b39feb15250..a4f93f1fdb94b 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -8,25 +8,51 @@ 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; +struct iovec_wrapper* iovw_free(struct iovec_wrapper *iovw); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct iovec_wrapper*, iovw_free); - r = iovw_put(iovw, data, len); - if (r < 0) - free(data); +struct iovec_wrapper* iovw_free_free(struct iovec_wrapper *iovw); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct iovec_wrapper*, iovw_free_free); + +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; +} - return r; +int iovw_put_full(struct iovec_wrapper *iovw, bool accept_zero, void *data, size_t len); +static inline int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) { + return iovw_put_full(iovw, false, data, len); +} +int iovw_put_iov_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec *iov); +static inline int iovw_put_iov(struct iovec_wrapper *iovw, const struct iovec *iov) { + return iovw_put_iov_full(iovw, false, iov); +} +int iovw_put_iovw_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec_wrapper *source); +static inline int iovw_put_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source) { + return iovw_put_iovw_full(iovw, false, source); +} +int iovw_consume_full(struct iovec_wrapper *iovw, bool accept_zero, void *data, size_t len); +static inline int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) { + return iovw_consume_full(iovw, false, data, len); +} +int iovw_consume_iov_full(struct iovec_wrapper *iovw, bool accept_zero, struct iovec *iov); +static inline int iovw_consume_iov(struct iovec_wrapper *iovw, struct iovec *iov) { + return iovw_consume_iov_full(iovw, false, iov); +} +int iovw_extend_full(struct iovec_wrapper *iovw, bool accept_zero, const void *data, size_t len); +static inline int iovw_extend(struct iovec_wrapper *iovw, const void *data, size_t len) { + return iovw_extend_full(iovw, false, data, len); +} +int iovw_extend_iov_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec *iov); +static inline int iovw_extend_iov(struct iovec_wrapper *iovw, const struct iovec *iov) { + return iovw_extend_iov_full(iovw, false, iov); +} +int iovw_extend_iovw_full(struct iovec_wrapper *iovw, bool accept_zero, const struct iovec_wrapper *source); +static inline int iovw_extend_iovw(struct iovec_wrapper *iovw, const struct iovec_wrapper *source) { + return iovw_extend_iovw_full(iovw, false, source); } static inline bool iovw_isempty(const struct iovec_wrapper *iovw) { @@ -46,4 +72,8 @@ 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); + +int iovec_split(const struct iovec *iov, size_t length_size, struct iovec_wrapper *ret); +int iovw_merge(const struct iovec_wrapper *iovw, size_t length_size, struct iovec *ret); diff --git a/src/basic/label.c b/src/basic/label-util.c similarity index 96% rename from src/basic/label.c rename to src/basic/label-util.c index cce3e75c7c1be..a6d616210afaf 100644 --- a/src/basic/label.c +++ b/src/basic/label-util.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "label.h" +#include "label-util.h" static const LabelOps *label_ops = NULL; diff --git a/src/basic/label.h b/src/basic/label-util.h similarity index 100% rename from src/basic/label.h rename to src/basic/label-util.h 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..1f0692e5bd9d1 100644 --- a/src/basic/locale-util.c +++ b/src/basic/locale-util.c @@ -7,7 +7,12 @@ #include #include +#ifndef __GLIBC__ +#include "sd-dlopen.h" +#endif + #include "dirent-util.h" +#include "dlfcn-util.h" #include "env-util.h" #include "fd-util.h" #include "fileio.h" @@ -16,11 +21,34 @@ #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" #include "utf8.h" +#ifdef __GLIBC__ +DLSYM_PROTOTYPE(dgettext) = dgettext; +#else +DLSYM_PROTOTYPE(dgettext) = NULL; +#endif + +int dlopen_libintl(int log_level) { +#ifdef __GLIBC__ + return 1; +#else + static void *libintl_dl = NULL; + + LIBINTL_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); + + return dlopen_many_sym_or_warn( + &libintl_dl, + "libintl.so.8", + log_level, + DLSYM_ARG(dgettext)); +#endif +} + static char* normalize_locale(const char *name) { const char *e; @@ -115,8 +143,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; @@ -229,6 +258,8 @@ int get_locales(char ***ret) { _cleanup_set_free_ Set *locales = NULL; int r; + assert(ret); + locales = set_new(&string_hash_ops_free); if (!locales) return -ENOMEM; diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h index bf5cbcb439220..c2cbf3580a2af 100644 --- a/src/basic/locale-util.h +++ b/src/basic/locale-util.h @@ -1,9 +1,36 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include /* IWYU pragma: export */ #include /* IWYU pragma: export */ +#include "sd-dlopen.h" /* IWYU pragma: export */ + #include "basic-forward.h" +#include "dlfcn-util.h" + +/* format_arg(2) propagates the format-string nature of the second argument to the return value, so that + * printf(_("Hello %s"), name) still gets checked. It survives both DLSYM_PROTOTYPE's typeof() and the + * ternary in _() below — verified on gcc and clang. */ +extern DLSYM_PROTOTYPE(dgettext) __attribute__((format_arg(2))); + +int dlopen_libintl(int log_level); + +#ifdef __GLIBC__ +#define DLOPEN_LIBINTL(log_level, priority) dlopen_libintl(log_level) +#else +#define LIBINTL_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("intl", \ + "Support for message translation via gettext", \ + priority, \ + "libintl.so.8") + +#define DLOPEN_LIBINTL(log_level, priority) \ + ({ \ + LIBINTL_NOTE(priority); \ + dlopen_libintl(log_level); \ + }) +#endif typedef enum LocaleVariable { /* We don't list LC_ALL here on purpose. People should be @@ -31,7 +58,9 @@ int get_locales(char ***ret); bool locale_is_valid(const char *name); int locale_is_installed(const char *name); -#define _(String) dgettext(GETTEXT_PACKAGE, String) +/* Falls back to the untranslated string if dlopen_libintl() hasn't run or has failed, so callers don't have + * to gate every translatable message on a runtime check. */ +#define _(String) (sym_dgettext ? sym_dgettext(GETTEXT_PACKAGE, (String)) : (String)) #define N_(String) String bool is_locale_utf8(void); diff --git a/src/basic/lock-util.c b/src/basic/lock-util.c index bd267eabf99d4..24eee3b9e4c99 100644 --- a/src/basic/lock-util.c +++ b/src/basic/lock-util.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -227,25 +226,6 @@ int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeo if (r < 0) return log_error_errno(r, "Failed to flock block device in child process: %m"); if (r == 0) { - struct sigevent sev = { - .sigev_notify = SIGEV_SIGNAL, - .sigev_signo = SIGALRM, - }; - timer_t id; - - if (timer_create(CLOCK_MONOTONIC, &sev, &id) < 0) { - log_error_errno(errno, "Failed to allocate CLOCK_MONOTONIC timer: %m"); - _exit(EXIT_FAILURE); - } - - struct itimerspec its = {}; - timespec_store(&its.it_value, timeout); - - if (timer_settime(id, /* flags= */ 0, &its, NULL) < 0) { - log_error_errno(errno, "Failed to start CLOCK_MONOTONIC timer: %m"); - _exit(EXIT_FAILURE); - } - if (lock_generic(fd, type, operation) < 0) { log_error_errno(errno, "Unable to get an exclusive lock on the device: %m"); _exit(EXIT_FAILURE); @@ -255,7 +235,7 @@ int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeo } siginfo_t status; - r = pidref_wait_for_terminate(&pidref, &status); + r = pidref_wait_for_terminate_full(&pidref, timeout, &status); if (r < 0) return r; @@ -270,11 +250,6 @@ int lock_generic_with_timeout(int fd, LockType type, int operation, usec_t timeo return 0; case CLD_KILLED: - if (status.si_status == SIGALRM) - return -ETIMEDOUT; - - _fallthrough_; - case CLD_DUMPED: return -EPROTO; diff --git a/src/basic/log-context.c b/src/basic/log-context.c index a05b4b1980e6b..44eb7cd36f923 100644 --- a/src/basic/log-context.c +++ b/src/basic/log-context.c @@ -177,6 +177,14 @@ size_t log_context_num_fields(void) { return _log_context_num_fields; } +void log_context_swap(LogContext **log_context, size_t *num_fields) { + assert(log_context); + assert(num_fields); + + SWAP_TWO(_log_context, *log_context); + SWAP_TWO(_log_context_num_fields, *num_fields); +} + void _reset_log_level(int *saved_log_level) { assert(saved_log_level); diff --git a/src/basic/log-context.h b/src/basic/log-context.h index ca112fa862acf..638d2ed913b19 100644 --- a/src/basic/log-context.h +++ b/src/basic/log-context.h @@ -66,6 +66,8 @@ size_t log_context_num_contexts(void); /* Returns the number of fields in all attached log contexts. */ size_t log_context_num_fields(void); +void log_context_swap(LogContext **log_context, size_t *num_fields); + void _reset_log_level(int *saved_log_level); #define _LOG_CONTEXT_SET_LOG_LEVEL(level, l) \ diff --git a/src/basic/log.c b/src/basic/log.c index b0b4ba5c5050c..29702abd44e40 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -87,6 +87,12 @@ bool _log_message_dummy = false; /* Always false */ } \ } while (false) +void log_prefix_swap(const char **prefix) { + assert(prefix); + + SWAP_TWO(log_prefix, *prefix); +} + static void log_close_console(void) { /* See comment in log_close_journal() */ (void) safe_close_above_stdio(TAKE_FD(console_fd)); @@ -557,7 +563,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 +956,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/log.h b/src/basic/log.h index 46a4339de5565..17c20f8cf0d11 100644 --- a/src/basic/log.h +++ b/src/basic/log.h @@ -273,6 +273,15 @@ int log_emergency_level(void); log_struct_iovec_internal(level, error, PROJECT_FILE, __LINE__, __func__, iovec, n_iovec) #define log_struct_iovec(level, iovec, n_iovec) log_struct_iovec_errno(level, 0, iovec, n_iovec) +/* Like log_struct(), but with log_once() semantics */ +#define log_struct_once(level, ...) \ + ({ \ + if (ONCE) \ + log_struct(level, __VA_ARGS__); \ + else if (LOG_PRI(level) != LOG_DEBUG) \ + log_struct(LOG_DEBUG, __VA_ARGS__); \ + }) + /* This modifies the buffer passed! */ #define log_dump(level, buffer) \ log_dump_internal(level, 0, PROJECT_FILE, __LINE__, __func__, buffer) @@ -380,6 +389,8 @@ int log_syntax_parse_error_internal( void log_setup(void); const char* _log_set_prefix(const char *prefix, bool force); + +void log_prefix_swap(const char **prefix); static inline const char* _log_unset_prefixp(const char **p) { assert(p); _log_set_prefix(*p, true); 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..cb0719f15f220 100644 --- a/src/basic/macro.h +++ b/src/basic/macro.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "macro-fundamental.h" /* IWYU pragma: export */ +#include "../fundamental/macro.h" /* IWYU pragma: export */ #if !defined(HAS_FEATURE_MEMORY_SANITIZER) # if defined(__has_feature) @@ -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/math-util.c b/src/basic/math-util.c new file mode 100644 index 0000000000000..3d95e40016c51 --- /dev/null +++ b/src/basic/math-util.c @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "math-util.h" + +double xexp10i(int n) { + /* Powers of 10 up to 10^22 are exact in IEEE-754 binary64. */ + static const double table[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, + }; + bool negative = n < 0; + + /* Cast before negation so n == INT_MIN doesn't invoke signed-overflow UB. Unsigned negation + * wraps to the magnitude we want. */ + unsigned k = negative ? -(unsigned) n : (unsigned) n; + + /* 10^309 already overflows binary64 to +Inf; anything beyond just stays there. */ + k = MIN(k, 309u); + double r = k < ELEMENTSOF(table) ? table[k] : table[ELEMENTSOF(table) - 1]; + for (unsigned i = ELEMENTSOF(table) - 1; i < k; i++) + r *= 10.0; + + return negative ? 1.0 / r : r; +} diff --git a/src/basic/math-util.h b/src/basic/math-util.h index f3c471b1606ee..9bcd994ae25db 100644 --- a/src/basic/math-util.h +++ b/src/basic/math-util.h @@ -10,5 +10,24 @@ #define iszero_safe(x) (fpclassify(x) == FP_ZERO) /* To avoid x == y and triggering compile warning -Wfloat-equal. This returns false if one of the argument is - * NaN or infinity. One of the argument must be a floating point. */ -#define fp_equal(x, y) iszero_safe((x) - (y)) + * NaN or infinity. One of the argument must be a floating point. + * + * The volatile temporaries force a memory roundtrip, truncating any excess precision (e.g. x87's + * 80-bit register width for double arithmetic) down to the declared type. -fexcess-precision=standard + * doesn't fully cover this on x87 — a function return value carried in ST(0) can still arrive at the + * caller in 80-bit precision (see gcc PR#323), so a value that should compare equal to a + * same-magnitude literal picks up extra mantissa bits and doesn't. The memory store-and-reload is + * the one operation guaranteed to truncate. The temporaries inherit the type of the subtraction + * expression so the macro stays generic over float / double / long double rather than silently + * truncating wider arguments. */ +#define fp_equal(x, y) \ + ({ \ + volatile __typeof__((x) - (y)) _fp_x = (x); \ + volatile __typeof__((x) - (y)) _fp_y = (y); \ + iszero_safe(_fp_x - _fp_y); \ + }) + +/* 10^n. Exact for |n| ≤ 22; otherwise multiplies and may accumulate rounding error. Saturates to + * 0.0 or +Inf outside binary64's exponent range; large |n| is capped internally so untrusted + * inputs can't cause unbounded work. */ +double xexp10i(int n); 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..4caf58585a779 100644 --- a/src/basic/memory-util.h +++ b/src/basic/memory-util.h @@ -4,7 +4,8 @@ #include #include "basic-forward.h" -#include "memory-util-fundamental.h" /* IWYU pragma: export */ + +#include "../fundamental/memory-util.h" /* IWYU pragma: export */ size_t page_size(void) _pure_; #define PAGE_ALIGN(l) ALIGN_TO(l, page_size()) @@ -57,12 +58,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/mempool.c b/src/basic/mempool.c index 3d9189857b155..8ed3b7e551a04 100644 --- a/src/basic/mempool.c +++ b/src/basic/mempool.c @@ -65,6 +65,8 @@ void* mempool_alloc_tile(struct mempool *mp) { void* mempool_alloc0_tile(struct mempool *mp) { void *p; + assert(mp); + p = mempool_alloc_tile(mp); if (p) memzero(p, mp->tile_size); diff --git a/src/basic/meson.build b/src/basic/meson.build index b8ffa80244d07..31fd129871529 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -10,7 +10,7 @@ basic_sources = files( 'arphrd-util.c', 'assert-util.c', 'audit-util.c', - 'btrfs.c', + 'btrfs-util.c', 'build.c', 'build-path.c', 'bus-label.c', @@ -36,12 +36,12 @@ basic_sources = files( 'ether-addr-util.c', 'extract-word.c', 'fd-util.c', + 'fiber-ops.c', 'fileio.c', 'filesystems.c', 'format-ifname.c', 'format-util.c', 'fs-util.c', - 'gcrypt-util.c', 'glob-util.c', 'glyph-util.c', 'gunicode.c', @@ -57,13 +57,14 @@ basic_sources = files( 'iovec-util.c', 'iovec-wrapper.c', 'keyring-util.c', - 'label.c', + 'label-util.c', 'limits-util.c', 'locale-util.c', 'lock-util.c', 'log.c', 'log-context.c', 'login-util.c', + 'math-util.c', 'memfd-util.c', 'memory-util.c', 'mempool.c', @@ -99,6 +100,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,14 +212,11 @@ libbasic_static = static_library( fundamental_sources, include_directories : basic_includes, implicit_include_directories : false, - dependencies : [libdl, - libgcrypt_cflags, + dependencies : [libbzip2_cflags, liblz4_cflags, - libm, - librt, libxz_cflags, + libz_cflags, libzstd_cflags, - threads, userspace], c_args : ['-fvisibility=default'], build_by_default : false) diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c index 837880baa2ec2..fc79763bb95e3 100644 --- a/src/basic/mkdir.c +++ b/src/basic/mkdir.c @@ -3,7 +3,7 @@ #include #include "alloc-util.h" -#include "btrfs.h" +#include "btrfs-util.h" #include "chase.h" #include "errno-util.h" #include "fd-util.h" @@ -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..4db4d8162fd14 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); @@ -236,7 +242,7 @@ struct file_handle* file_handle_dup(const struct file_handle *fh) { int is_mount_point_at(int dir_fd, const char *path, int flags) { int r; - assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(dir_fd)); assert((flags & ~AT_SYMLINK_FOLLOW) == 0); if (path_equal(path, "/")) @@ -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, @@ -299,7 +306,7 @@ static int path_get_mnt_id_at_internal(int dir_fd, const char *path, bool unique struct statx sx; int r; - assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(dir_fd)); assert(ret); r = xstatx(dir_fd, path, @@ -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; @@ -376,6 +385,17 @@ bool fstype_needs_quota(const char *fstype) { "f2fs"); } +bool fstype_has_internal_quota(const char *fstype) { + /* These filesystems have built-in quota support and do not need + * external quotacheck/quotaon services - see the "nothing needed" + * entries in fstype_needs_quota() above. */ + return STR_IN_SET(fstype, + "xfs", + "gfs2", + "ocfs2", + "btrfs"); +} + bool fstype_is_api_vfs(const char *fstype) { assert(fstype); @@ -465,6 +485,22 @@ bool fstype_can_fmask_dmask(const char *fstype) { return streq(fstype, "vfat") || (mount_option_supported(fstype, "fmask", "0177") > 0 && mount_option_supported(fstype, "dmask", "0077") > 0); } +bool fstype_can_ownership(const char *fstype) { + /* File systems which are not known to not support uid/gid ownership. + * For some types, this can be a bit murky. So just exclude the ones that for sure + * don't support with the current implementations in Linux. */ + + return !STR_IN_SET(ASSERT_PTR(fstype), + "adfs", + "exfat", + "fat", + "hfs", + "hpfs", + "msdos", + "ntfs", + "vfat"); +} + bool fstype_can_uid_gid(const char *fstype) { /* All file systems that have a uid=/gid= mount option that fixates the owners of all files and * directories, current and future. Note that this does *not* ask the kernel via @@ -596,6 +632,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/mountpoint-util.h b/src/basic/mountpoint-util.h index 1381f3d8aba0e..dd27f36c2c3e7 100644 --- a/src/basic/mountpoint-util.h +++ b/src/basic/mountpoint-util.h @@ -61,10 +61,12 @@ static inline int path_is_mount_point(const char *path) { bool fstype_is_network(const char *fstype); bool fstype_needs_quota(const char *fstype); +bool fstype_has_internal_quota(const char *fstype); bool fstype_is_api_vfs(const char *fstype); bool fstype_is_blockdev_backed(const char *fstype); bool fstype_is_ro(const char *fsype); bool fstype_can_discard(const char *fstype); +bool fstype_can_ownership(const char *fstype); bool fstype_can_uid_gid(const char *fstype); bool fstype_can_fmask_dmask(const char *fstype); diff --git a/src/basic/namespace-util.c b/src/basic/namespace-util.c index f156bfdf24994..db95ab86275bc 100644 --- a/src/basic/namespace-util.c +++ b/src/basic/namespace-util.c @@ -23,6 +23,7 @@ #include "stat-util.h" #include "stdio-util.h" #include "uid-range.h" +#include "unaligned.h" #include "user-util.h" const struct namespace_info namespace_info[_NAMESPACE_TYPE_MAX + 1] = { @@ -358,7 +359,7 @@ int is_our_namespace(int fd, NamespaceType type) { int r; assert(fd >= 0); - assert(type < _NAMESPACE_TYPE_MAX); + assert(type >= 0 && type < _NAMESPACE_TYPE_MAX); r = fd_is_namespace(fd, type); if (r < 0) @@ -804,16 +805,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 ? pidref->pid : 0); 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 +826,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,16 +849,82 @@ 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); } } +int namespace_open_by_id(uint64_t ns_id) { + int r; + + /* Looks up a namespace by its unique boot-stable identifier and returns an O_PATH fd to it. + * Requires kernel ≥ 6.13. + * + * Returns -ESTALE if the namespace no longer exists, or if the kernel refuses the lookup + * for permission reasons. The latter happens outside the initial user namespace: the + * kernel only permits open_by_handle_at() on nsfs when the caller is in the initial user + * and pid namespaces with CAP_SYS_ADMIN, with a narrow exception for lookups of the + * caller's own user namespace and its ancestors. To avoid conflating "namespace is dead" + * with "kernel refused us", we refuse early with -EPERM when we aren't in the initial + * user/pid namespace or missing CAP_SYS_ADMIN and let the caller skip the check. */ + + if (ns_id == 0) + return -EINVAL; + + r = namespace_is_init(NAMESPACE_USER); + if (r < 0) + return r; + if (r == 0) + return -EPERM; + + r = namespace_is_init(NAMESPACE_PID); + if (r < 0) + return r; + if (r == 0) + return -EPERM; + + r = have_effective_cap(CAP_SYS_ADMIN); + if (r < 0) + return r; + if (r == 0) + return -EPERM; + + /* The natural way to write this would be a compound designated initializer: + * + * union { ... } fh = { + * .file_handle.handle_bytes = sizeof(struct nsfs_file_handle), + * .file_handle.handle_type = FILEID_NSFS, + * }; + * + * but that only zero-initializes the named struct members of struct file_handle. + * struct file_handle ends with a flexible array (`unsigned char f_handle[]`), whose + * storage comes from the overlapping `space[]` member of the union. Bytes in that storage + * are not covered by the partial struct initializer and end up as stack garbage. Zero the + * entire union first, then fill in the fields explicitly. */ + + union { + struct file_handle file_handle; + uint8_t space[offsetof(struct file_handle, f_handle) + sizeof(struct nsfs_file_handle)]; + } fh = {}; + fh.file_handle.handle_bytes = sizeof(struct nsfs_file_handle); + fh.file_handle.handle_type = FILEID_NSFS; + + /* The first 8 bytes of struct nsfs_file_handle (see , uapi since kernel v6.18) + * are __u64 ns_id; the remaining ns_type/ns_inum fields stay zero so the kernel looks up by + * id alone. The kernel made lookup-by-id-only an explicit ABI guarantee in v6.19 via commit + * 04173501a69e ("nstree: allow lookup solely based on inode"). */ + unaligned_write_ne64(fh.file_handle.f_handle, ns_id); + + return RET_NERRNO(open_by_handle_at(FD_NSFS_ROOT, &fh.file_handle, O_PATH|O_CLOEXEC)); +} + int is_idmapping_supported(const char *path) { _cleanup_close_ int mount_fd = -EBADF, userns_fd = -EBADF, dir_fd = -EBADF; int r; diff --git a/src/basic/namespace-util.h b/src/basic/namespace-util.h index cd2ea786927c3..3bfa34371c0d2 100644 --- a/src/basic/namespace-util.h +++ b/src/basic/namespace-util.h @@ -88,6 +88,8 @@ bool userns_supported(void); int userns_get_base_uid(int userns_fd, uid_t *ret_uid, gid_t *ret_gid); +int namespace_open_by_id(uint64_t ns_id); + int process_is_owned_by_uid(const PidRef *pidref, uid_t uid); int is_idmapping_supported(const char *path); 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..f743d2bca8969 100644 --- a/src/basic/os-util.c +++ b/src/basic/os-util.c @@ -3,10 +3,12 @@ #include #include "alloc-util.h" +#include "ansi-color.h" #include "chase.h" #include "dirent-util.h" #include "env-file.h" #include "errno-util.h" +#include "escape.h" #include "fd-util.h" #include "fs-util.h" #include "glyph-util.h" @@ -168,14 +170,14 @@ int open_os_release_at(int rfd, char **ret_path, int *ret_fd) { const char *e; int r; - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); 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; } @@ -225,7 +227,7 @@ int open_extension_release_at( const char *p; int r; - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); assert(!extension || (image_class >= 0 && image_class < _IMAGE_CLASS_MAX)); if (!extension) @@ -238,7 +240,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 +251,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); @@ -370,7 +372,7 @@ static int parse_extension_release_atv( _cleanup_free_ char *p = NULL; int r; - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); r = open_extension_release_at(rfd, image_class, extension, relax_extension_release_check, &p, &fd); if (r < 0) @@ -389,7 +391,7 @@ int parse_extension_release_at_sentinel( va_list ap; int r; - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); va_start(ap, extension); r = parse_extension_release_atv(rfd, image_class, extension, relax_extension_release_check, ap); @@ -425,6 +427,8 @@ int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char _cleanup_strv_free_ char **os_release_pairs = NULL, **os_release_pairs_prefixed = NULL; int r; + assert(ret); + r = load_os_release_pairs(root, &os_release_pairs); if (r < 0) return r; @@ -512,3 +516,46 @@ const char* os_release_pretty_name(const char *pretty_name, const char *name) { return empty_to_null(pretty_name) ?: empty_to_null(name) ?: "Linux"; } + +char *unescape_fancy_name(char **fancy_name) { + assert(fancy_name); + + /* Checks if the fancy name is valid, unescapes if it is, nullifies it if not */ + + _cleanup_free_ char *unescaped_fancy_name = NULL; + + if (isempty(*fancy_name)) + goto clear; + + /* We undo one level of C escapes on this */ + ssize_t n = cunescape(*fancy_name, /* flags= */ 0, &unescaped_fancy_name); + if (n < 0) { + log_debug_errno((int) n, "Failed to unescape FANCY_NAME= string, suppressing: %m"); + goto clear; + } + + if (!utf8_is_valid(unescaped_fancy_name)) { + log_debug("Unescaped FANCY_NAME= string is not valid UTF-8, suppressing."); + goto clear; + } + + free_and_replace(*fancy_name, unescaped_fancy_name); + return *fancy_name; + +clear: + *fancy_name = mfree(*fancy_name); + return NULL; +} + +bool use_fancy_name(const char *fancy_name) { + + /* Decides whether to show the specified fancy name */ + + if (isempty(fancy_name)) + return false; + + if (!colors_enabled()) + return false; + + return emoji_enabled() || ascii_is_valid(fancy_name); +} diff --git a/src/basic/os-util.h b/src/basic/os-util.h index 02d2c9540f2de..336c17ec21aa0 100644 --- a/src/basic/os-util.h +++ b/src/basic/os-util.h @@ -55,3 +55,6 @@ int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char int os_release_support_ended(const char *support_end, bool quiet, usec_t *ret_eol); const char* os_release_pretty_name(const char *pretty_name, const char *name); + +bool use_fancy_name(const char *fancy_name); +char *unescape_fancy_name(char **fancy_name); 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..b41029b157cf9 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; @@ -839,7 +804,7 @@ static int executable_is_good(const char *executable) { if (r < 0) return r; - return !PATH_IN_SET(d, "true" + return !PATH_IN_SET(d, "true", "/bin/true", "/usr/bin/true", "/dev/null"); @@ -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/pidref.c b/src/basic/pidref.c index 10ff9a63b12bc..649a207c1ec45 100644 --- a/src/basic/pidref.c +++ b/src/basic/pidref.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "errno-util.h" #include "fd-util.h" +#include "fiber-ops.h" #include "format-util.h" #include "hash-funcs.h" #include "io-util.h" @@ -466,16 +467,28 @@ int pidref_wait_for_terminate_full(PidRef *pidref, usec_t timeout, siginfo_t *re if (pidref->pid == 1 || pidref_is_self(pidref)) return -ECHILD; - if (timeout != USEC_INFINITY && pidref->fd < 0) + if (pidref->fd < 0 && (timeout != USEC_INFINITY || fiber_ops_is_set())) return -ENOMEDIUM; usec_t ts = timeout == USEC_INFINITY ? USEC_INFINITY : usec_add(now(CLOCK_MONOTONIC), timeout); + /* Poll the pidfd before waitid() if either there's a finite timeout (so we can honor it) or + * we're on a fiber (so fd_wait_for_event() can suspend us instead of blocking the event loop + * inside waitid()). Otherwise let waitid() block directly. The precondition above guarantees + * pidref->fd >= 0 in both cases. */ + bool poll_first = ts != USEC_INFINITY || fiber_ops_is_set(); + for (;;) { - if (ts != USEC_INFINITY) { - usec_t left = usec_sub_unsigned(ts, now(CLOCK_MONOTONIC)); - if (left == 0) - return -ETIMEDOUT; + if (poll_first) { + usec_t left; + + if (ts == USEC_INFINITY) + left = USEC_INFINITY; + else { + left = usec_sub_unsigned(ts, now(CLOCK_MONOTONIC)); + if (left == 0) + return -ETIMEDOUT; + } r = fd_wait_for_event(pidref->fd, POLLIN, left); if (r == 0) @@ -507,6 +520,8 @@ bool pidref_is_automatic(const PidRef *pidref) { } void pidref_hash_func(const PidRef *pidref, struct siphash *state) { + assert(pidref); + siphash24_compress_typesafe(pidref->pid, state); } 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.c b/src/basic/proc-cmdline.c index eb61b0bc554e2..4de3480ff0255 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -5,7 +5,6 @@ #include "alloc-util.h" #include "extract-word.h" #include "fileio.h" -#include "getopt-defs.h" #include "initrd-util.h" #include "log.h" #include "parse-util.h" @@ -15,18 +14,50 @@ #include "strv.h" #include "virt.h" +typedef enum ArgType { + no_argument, + required_argument, + optional_argument, +} ArgType; + int proc_cmdline_filter_pid1_args(char **argv, char ***ret) { - enum { - COMMON_GETOPT_ARGS, - SYSTEMD_GETOPT_ARGS, - SHUTDOWN_GETOPT_ARGS, - }; - static const struct option options[] = { - COMMON_GETOPT_OPTIONS, - SYSTEMD_GETOPT_OPTIONS, - SHUTDOWN_GETOPT_OPTIONS, + const struct { + const char *name; + ArgType has_arg; + } options[] = { + { "log-level", required_argument }, + { "log-target", required_argument }, + { "log-color", optional_argument }, + { "log-location", optional_argument }, + { "log-time", optional_argument }, + { "unit", required_argument }, + { "system", no_argument, }, + { "user", no_argument, }, + { "test", no_argument, }, + { "no-pager", no_argument, }, + { "help", no_argument, }, + { "version", no_argument, }, + { "dump-configuration-items", no_argument, }, + { "dump-bus-properties", no_argument, }, + { "bus-introspect", required_argument }, + { "dump-core", optional_argument }, + { "crash-chvt", required_argument }, + { "crash-vt", required_argument }, + { "crash-shell", optional_argument }, + { "crash-reboot", optional_argument }, + { "crash-action", required_argument }, + { "confirm-spawn", optional_argument }, + { "show-status", optional_argument }, + { "deserialize", required_argument }, + { "switched-root", no_argument, }, + { "default-standard-output", required_argument }, + { "default-standard-error", required_argument }, + { "machine-id", required_argument }, + { "service-watchdogs", required_argument }, + { "exit-code", required_argument }, + { "timeout", required_argument }, }; - static const char *short_options = SYSTEMD_GETOPT_SHORT_OPTIONS; + const char *short_options = "hDbsz:"; _cleanup_strv_free_ char **filtered = NULL; int state, r; @@ -108,12 +139,10 @@ int proc_cmdline_filter_pid1_args(char **argv, char ***ret) { } int proc_cmdline(char **ret) { - const char *e; - assert(ret); /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */ - e = secure_getenv("SYSTEMD_PROC_CMDLINE"); + const char *e = secure_getenv("SYSTEMD_PROC_CMDLINE"); if (e) return strdup_to(ret, e); @@ -124,13 +153,12 @@ int proc_cmdline(char **ret) { } static int proc_cmdline_strv_internal(char ***ret, bool filter_pid1_args) { - const char *e; int r; assert(ret); /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */ - e = secure_getenv("SYSTEMD_PROC_CMDLINE"); + const char *e = secure_getenv("SYSTEMD_PROC_CMDLINE"); if (e) return strv_split_full(ret, e, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE); 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..14e58e21ebd0f 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -674,6 +674,7 @@ int pidref_get_ppid_as_pidref(const PidRef *pidref, PidRef *ret) { pid_t ppid; int r; + POINTER_MAY_BE_NULL(pidref); assert(ret); r = pidref_get_ppid(pidref, &ppid); @@ -1151,6 +1152,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 +1194,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); } @@ -2042,7 +2048,6 @@ int posix_spawn_wrapper( /* Initialization needs to succeed before we can set up a destructor. */ _unused_ _cleanup_(posix_spawnattr_destroyp) posix_spawnattr_t *attr_destructor = &attr; -#if HAVE_PIDFD_SPAWN static bool have_clone_into_cgroup = true; /* kernel 5.7+ */ _cleanup_close_ int cgroup_fd = -EBADF; @@ -2058,12 +2063,13 @@ int posix_spawn_wrapper( return -errno; r = posix_spawnattr_setcgroup_np(&attr, cgroup_fd); - if (r != 0) + if (r == 0) + flags |= POSIX_SPAWN_SETCGROUP; + else if (r != ENOSYS) return -r; - - flags |= POSIX_SPAWN_SETCGROUP; + /* If libc lacks posix_spawnattr_setcgroup_np we silently skip POSIX_SPAWN_SETCGROUP — the + * caller will then need to attach the child to the cgroup themselves. */ } -#endif r = posix_spawnattr_setflags(&attr, flags); if (r != 0) @@ -2072,7 +2078,6 @@ int posix_spawn_wrapper( if (r != 0) return -r; -#if HAVE_PIDFD_SPAWN _cleanup_close_ int pidfd = -EBADF; r = pidfd_spawn(&pidfd, path, NULL, &attr, argv, envp); @@ -2096,15 +2101,18 @@ int posix_spawn_wrapper( r = pidfd_spawn(&pidfd, path, NULL, &attr, argv, envp); } - if (r != 0) + if (r == 0) { + r = pidref_set_pidfd_consume(ret_pidref, TAKE_FD(pidfd)); + if (r < 0) + return r; + + return FLAGS_SET(flags, POSIX_SPAWN_SETCGROUP); + } + if (!ERRNO_IS_NOT_SUPPORTED(r)) return -r; - r = pidref_set_pidfd_consume(ret_pidref, TAKE_FD(pidfd)); - if (r < 0) - return r; + /* pidfd_spawn unavailable (libc or kernel missing) — fall back to plain posix_spawn. */ - return FLAGS_SET(flags, POSIX_SPAWN_SETCGROUP); -#else pid_t pid; r = posix_spawn(&pid, path, NULL, &attr, argv, envp); @@ -2116,7 +2124,6 @@ int posix_spawn_wrapper( return r; return 0; /* We did not use CLONE_INTO_CGROUP so return 0, the caller will have to move the child */ -#endif } int proc_dir_open(DIR **ret) { 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..6e0ce5cd4bbc5 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); } @@ -24,6 +27,35 @@ static bool ignore_dirent(const struct dirent *de, RecurseDirFlags flags) { /* Depending on flag either ignore everything starting with ".", or just "." itself and ".." */ + if ((flags & _RECURSE_DIR_MUST_BE_MASK) != 0) { + RecurseDirFlags f; + + switch (de->d_type) { + + case DT_DIR: + f = RECURSE_DIR_MUST_BE_DIRECTORY; + break; + + case DT_REG: + f = RECURSE_DIR_MUST_BE_REGULAR; + break; + + case DT_LNK: + f = RECURSE_DIR_MUST_BE_SYMLINK; + break; + + case DT_SOCK: + f = RECURSE_DIR_MUST_BE_SOCKET; + break; + + default: + return true; + } + + if (!FLAGS_SET(flags, f)) + return true; + } + return FLAGS_SET(flags, RECURSE_DIR_IGNORE_DOT) ? de->d_name[0] == '.' : dot_or_dot_dot(de->d_name); @@ -36,11 +68,16 @@ int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret) { assert(dir_fd >= 0); + if ((flags & _RECURSE_DIR_MUST_BE_MASK) != 0) /* We need the type to validate it */ + flags |= RECURSE_DIR_ENSURE_TYPE; + /* Returns an array with pointers to "struct dirent" directory entries, optionally sorted. * * 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 +87,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); @@ -80,9 +119,6 @@ int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret) { de->n_entries = 0; struct dirent *entry; FOREACH_DIRENT_IN_BUFFER(entry, de->buffer, de->buffer_size) { - if (ignore_dirent(entry, flags)) - continue; - if (FLAGS_SET(flags, RECURSE_DIR_ENSURE_TYPE)) { r = dirent_ensure_type(dir_fd, entry); if (r == -ENOENT) @@ -92,6 +128,9 @@ int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret) { return r; } + if (ignore_dirent(entry, flags)) + continue; + de->n_entries++; } @@ -110,14 +149,14 @@ int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret) { j = 0; FOREACH_DIRENT_IN_BUFFER(entry, de->buffer, de->buffer_size) { - if (ignore_dirent(entry, flags)) - continue; - /* If d_type == DT_UNKNOWN that means we failed to ensure the type in the earlier loop and * didn't include the dentry in de->n_entries and as such should skip it here as well. */ if (FLAGS_SET(flags, RECURSE_DIR_ENSURE_TYPE) && entry->d_type == DT_UNKNOWN) continue; + if (ignore_dirent(entry, flags)) + continue; + de->entries[j++] = entry; } assert(j == de->n_entries); @@ -159,6 +198,9 @@ int recurse_dir( assert(dir_fd >= 0); assert(func); + /* We cannot descend into dirs if we are supposed to ignore them */ + assert((flags & _RECURSE_DIR_MUST_BE_MASK) == 0 || FLAGS_SET(flags, RECURSE_DIR_MUST_BE_DIRECTORY)); + /* This is a lot like ftw()/nftw(), but a lot more modern, i.e. built around openat()/statx()/O_PATH, * and under the assumption that fds are not as 'expensive' as they used to be. */ diff --git a/src/basic/recurse-dir.h b/src/basic/recurse-dir.h index af33c239f7017..6b6eac3639d9b 100644 --- a/src/basic/recurse-dir.h +++ b/src/basic/recurse-dir.h @@ -7,14 +7,19 @@ typedef enum RecurseDirFlags { /* Interpreted by readdir_all() */ - RECURSE_DIR_SORT = 1 << 0, /* sort file directory entries before processing them */ - RECURSE_DIR_IGNORE_DOT = 1 << 1, /* ignore all dot files ("." and ".." are always ignored) */ - RECURSE_DIR_ENSURE_TYPE = 1 << 2, /* guarantees that 'd_type' field of 'de' is not DT_UNKNOWN */ + RECURSE_DIR_SORT = 1 << 0, /* sort file directory entries before processing them */ + RECURSE_DIR_IGNORE_DOT = 1 << 1, /* ignore all dot files ("." and ".." are always ignored) */ + RECURSE_DIR_ENSURE_TYPE = 1 << 2, /* guarantees that 'd_type' field of 'de' is not DT_UNKNOWN */ + RECURSE_DIR_MUST_BE_DIRECTORY = 1 << 3, /* ignore all entries that aren't directories */ + RECURSE_DIR_MUST_BE_REGULAR = 1 << 4, /* ignore all entries that aren't regular files */ + RECURSE_DIR_MUST_BE_SYMLINK = 1 << 5, /* ignore all entries that aren't symlinks */ + RECURSE_DIR_MUST_BE_SOCKET = 1 << 6, /* ignore all entries that aren't socket */ + _RECURSE_DIR_MUST_BE_MASK = RECURSE_DIR_MUST_BE_DIRECTORY|RECURSE_DIR_MUST_BE_REGULAR|RECURSE_DIR_MUST_BE_SYMLINK|RECURSE_DIR_MUST_BE_SOCKET, /* Interpreted by recurse_dir() */ - RECURSE_DIR_SAME_MOUNT = 1 << 3, /* skips over subdirectories that are submounts */ - RECURSE_DIR_INODE_FD = 1 << 4, /* passes an opened inode fd (O_DIRECTORY fd in case of dirs, O_PATH otherwise) */ - RECURSE_DIR_TOPLEVEL = 1 << 5, /* call RECURSE_DIR_ENTER/RECURSE_DIR_LEAVE once for top-level dir, too, with dir_fd=-1 and NULL dirent */ + RECURSE_DIR_SAME_MOUNT = 1 << 7, /* skips over subdirectories that are submounts */ + RECURSE_DIR_INODE_FD = 1 << 8, /* passes an opened inode fd (O_DIRECTORY fd in case of dirs, O_PATH otherwise) */ + RECURSE_DIR_TOPLEVEL = 1 << 9, /* call RECURSE_DIR_ENTER/RECURSE_DIR_LEAVE once for top-level dir, too, with dir_fd=-1 and NULL dirent */ } RecurseDirFlags; typedef struct DirectoryEntries { 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/sha256.c b/src/basic/sha256.c index 31e535d4d291e..ebefdc7540955 100644 --- a/src/basic/sha256.c +++ b/src/basic/sha256.c @@ -11,6 +11,8 @@ int sha256_fd(int fd, uint64_t max_size, uint8_t ret[static SHA256_DIGEST_SIZE]) struct sha256_ctx ctx; uint64_t total_size = 0; + assert(fd >= 0); + sha256_init_ctx(&ctx); for (;;) { @@ -53,3 +55,12 @@ int parse_sha256(const char *s, uint8_t ret[static SHA256_DIGEST_SIZE]) { bool sha256_is_valid(const char *s) { return s && in_charset(s, HEXDIGITS) && (strlen(s) == SHA256_DIGEST_SIZE * 2); } + +char* sha256_direct_hex(const void *buffer, size_t sz) { + assert(buffer || sz == 0); + + if (sz == SIZE_MAX) + sz = strlen(buffer); + + return hexmem(SHA256_DIRECT(buffer, sz), SHA256_DIGEST_SIZE); +} diff --git a/src/basic/sha256.h b/src/basic/sha256.h index 5016cb42b025a..8d3387ea49086 100644 --- a/src/basic/sha256.h +++ b/src/basic/sha256.h @@ -3,10 +3,13 @@ #pragma once #include "basic-forward.h" -#include "sha256-fundamental.h" /* IWYU pragma: export */ + +#include "../fundamental/sha256.h" /* IWYU pragma: export */ int sha256_fd(int fd, uint64_t max_size, uint8_t ret[static SHA256_DIGEST_SIZE]); int parse_sha256(const char *s, uint8_t ret[static SHA256_DIGEST_SIZE]); bool sha256_is_valid(const char *s) _pure_; + +char* sha256_direct_hex(const void *buffer, size_t sz); diff --git a/src/basic/signal-util.c b/src/basic/signal-util.c index ae7bfa6bd338e..aceafd35e2e14 100644 --- a/src/basic/signal-util.c +++ b/src/basic/signal-util.c @@ -223,6 +223,154 @@ int signal_from_string(const char *s) { return -EINVAL; } +static const char *const sigill_code_table[] = { + [ILL_ILLOPC] = "ILL_ILLOPC", + [ILL_ILLOPN] = "ILL_ILLOPN", + [ILL_ILLADR] = "ILL_ILLADR", + [ILL_ILLTRP] = "ILL_ILLTRP", + [ILL_PRVOPC] = "ILL_PRVOPC", + [ILL_PRVREG] = "ILL_PRVREG", + [ILL_COPROC] = "ILL_COPROC", + [ILL_BADSTK] = "ILL_BADSTK", + [ILL_BADIADDR] = "ILL_BADIADDR", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigill_code, int); + +static const char *const sigfpe_code_table[] = { + [FPE_INTDIV] = "FPE_INTDIV", + [FPE_INTOVF] = "FPE_INTOVF", + [FPE_FLTDIV] = "FPE_FLTDIV", + [FPE_FLTOVF] = "FPE_FLTOVF", + [FPE_FLTUND] = "FPE_FLTUND", + [FPE_FLTRES] = "FPE_FLTRES", + [FPE_FLTINV] = "FPE_FLTINV", + [FPE_FLTSUB] = "FPE_FLTSUB", + [FPE_FLTUNK] = "FPE_FLTUNK", + [FPE_CONDTRAP] = "FPE_CONDTRAP", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigfpe_code, int); + +static const char *const sigsegv_code_table[] = { + [SEGV_MAPERR] = "SEGV_MAPERR", + [SEGV_ACCERR] = "SEGV_ACCERR", + [SEGV_BNDERR] = "SEGV_BNDERR", + [SEGV_PKUERR] = "SEGV_PKUERR", + [SEGV_ACCADI] = "SEGV_ACCADI", + [SEGV_ADIDERR] = "SEGV_ADIDERR", + [SEGV_ADIPERR] = "SEGV_ADIPERR", + [SEGV_MTEAERR] = "SEGV_MTEAERR", + [SEGV_MTESERR] = "SEGV_MTESERR", + [SEGV_CPERR] = "SEGV_CPERR", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigsegv_code, int); + +static const char *const sigbus_code_table[] = { + [BUS_ADRALN] = "BUS_ADRALN", + [BUS_ADRERR] = "BUS_ADRERR", + [BUS_OBJERR] = "BUS_OBJERR", + [BUS_MCEERR_AR] = "BUS_MCEERR_AR", + [BUS_MCEERR_AO] = "BUS_MCEERR_AO", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigbus_code, int); + +static const char *const sigtrap_code_table[] = { + [TRAP_BRKPT] = "TRAP_BRKPT", + [TRAP_TRACE] = "TRAP_TRACE", + [TRAP_BRANCH] = "TRAP_BRANCH", + [TRAP_HWBKPT] = "TRAP_HWBKPT", + [TRAP_UNK] = "TRAP_UNK", + [TRAP_PERF] = "TRAP_PERF", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigtrap_code, int); + +static const char *const sigsys_code_table[] = { + [SYS_SECCOMP] = "SYS_SECCOMP", + [SYS_USER_DISPATCH] = "SYS_USER_DISPATCH", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigsys_code, int); + +/* sigchld_code_table is already defined in src/basic/process-util.c with + * decoded, lower-case values (CLD_EXITED -> "exited"). For consistency with + * all other values decoded here, provide an uppercase variant. */ +static const char *const sigchld_uppercase_code_table[] = { + [CLD_EXITED] = "CLD_EXITED", + [CLD_KILLED] = "CLD_KILLED", + [CLD_DUMPED] = "CLD_DUMPED", + [CLD_TRAPPED] = "CLD_TRAPPED", + [CLD_STOPPED] = "CLD_STOPPED", + [CLD_CONTINUED] = "CLD_CONTINUED", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigchld_uppercase_code, int); + +static const char *const sigpoll_code_table[] = { + [POLL_IN] = "POLL_IN", + [POLL_OUT] = "POLL_OUT", + [POLL_MSG] = "POLL_MSG", + [POLL_ERR] = "POLL_ERR", + [POLL_PRI] = "POLL_PRI", + [POLL_HUP] = "POLL_HUP", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(sigpoll_code, int); + +const char* signal_code_to_string(int signo, int code) { + switch (code) { + /* SI_KERNEL is 0x80 (128). Defining a table just for SI_KERNEL and + * SI_USER would be a waste of .rodata space: special case them instead. */ + case SI_KERNEL: + return "SI_KERNEL"; + case SI_USER: + return "SI_USER"; + /* The following values are all negatives. SI_ASYNCNL is -60. Similarly + * to the special case for SI_KERNEL above, avoid using a table for negative + * values as well. */ + case SI_ASYNCNL: + return "SI_ASYNCNL"; + case SI_DETHREAD: + return "SI_DETHREAD"; + case SI_TKILL: + return "SI_TKILL"; + case SI_SIGIO: + return "SI_SIGIO"; + case SI_ASYNCIO: + return "SI_ASYNCIO"; + case SI_MESGQ: + return "SI_MESGQ"; + case SI_TIMER: + return "SI_TIMER"; + case SI_QUEUE: + return "SI_QUEUE"; + } + + switch (signo) { + case SIGILL: + return sigill_code_to_string(code); + case SIGFPE: + return sigfpe_code_to_string(code); + case SIGSEGV: + return sigsegv_code_to_string(code); + case SIGBUS: + return sigbus_code_to_string(code); + case SIGTRAP: + return sigtrap_code_to_string(code); + case SIGCHLD: + return sigchld_uppercase_code_to_string(code); + case SIGPOLL: + return sigpoll_code_to_string(code); + case SIGSYS: + return sigsys_code_to_string(code); + default: + return NULL; + } +} + void nop_signal_handler(int sig) { /* nothing here */ } diff --git a/src/basic/signal-util.h b/src/basic/signal-util.h index c022da6295051..0c6aa6898d373 100644 --- a/src/basic/signal-util.h +++ b/src/basic/signal-util.h @@ -32,6 +32,7 @@ int sigprocmask_many_internal(int how, sigset_t *ret_old_mask, ...); #define sigprocmask_many(...) sigprocmask_many_internal(__VA_ARGS__, -1) DECLARE_STRING_TABLE_LOOKUP(signal, int); +const char* signal_code_to_string(int signo, int code) _const_; void nop_signal_handler(int sig); diff --git a/src/basic/siphash24.c b/src/basic/siphash24.c index 72d6bbe4f0cb7..b73ede34c8bc4 100644 --- a/src/basic/siphash24.c +++ b/src/basic/siphash24.c @@ -21,6 +21,7 @@ #include +#include "iovec-util.h" #include "siphash24.h" #include "string-util.h" #include "unaligned.h" @@ -156,6 +157,16 @@ void siphash24_compress_string(const char *in, struct siphash *state) { siphash24_compress_safe(in, strlen_ptr(in), state); } +void siphash24_compress_iovec(const struct iovec *iov, struct siphash *state) { + assert(iovec_is_valid(iov)); + assert(state); + + if (!iovec_is_set(iov)) + return; + + siphash24_compress(iov->iov_base, iov->iov_len, state); +} + uint64_t siphash24_finalize(struct siphash *state) { uint64_t b; diff --git a/src/basic/siphash24.h b/src/basic/siphash24.h index d72233beda89b..772e2728b69e0 100644 --- a/src/basic/siphash24.h +++ b/src/basic/siphash24.h @@ -36,6 +36,7 @@ static inline void siphash24_compress_safe(const void *in, size_t inlen, struct } void siphash24_compress_string(const char *in, struct siphash *state); +void siphash24_compress_iovec(const struct iovec *iov, struct siphash *state); uint64_t siphash24_finalize(struct siphash *state); diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 1194eafc1b0cd..13537593cfe13 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -10,6 +13,7 @@ #include #include #include +#include #include #include "alloc-util.h" @@ -18,6 +22,7 @@ #include "fd-util.h" #include "format-ifname.h" #include "format-util.h" +#include "fs-util.h" #include "in-addr-util.h" #include "io-util.h" #include "log.h" @@ -33,6 +38,8 @@ #include "string-util.h" #include "strv.h" #include "sysctl-util.h" +#include "tmpfile-util.h" +#include "xattr-util.h" #if ENABLE_IDN # define IDN_FLAGS NI_IDN @@ -360,6 +367,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 +832,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 +1160,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), @@ -1281,6 +1289,8 @@ size_t sockaddr_un_len(const struct sockaddr_un *sa) { } size_t sockaddr_len(const union sockaddr_union *sa) { + assert(sa); + switch (sa->sa.sa_family) { case AF_INET: return sizeof(struct sockaddr_in); @@ -1558,6 +1568,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) @@ -1615,13 +1627,17 @@ int connect_unix_path(int fd, int dir_fd, const char *path) { _cleanup_close_ int inode_fd = -EBADF; assert(fd >= 0); - assert(dir_fd == AT_FDCWD || dir_fd >= 0); + assert(wildcard_fd_is_valid(dir_fd)); /* Connects to the specified AF_UNIX socket in the file system. Works around the 108 byte size limit * in sockaddr_un, by going via O_PATH if needed. This hence works for any kind of path. */ - if (!path) + if (!path) { + if (dir_fd < 0) + return -EISDIR; + return connect_unix_inode(fd, dir_fd); /* If no path is specified, then dir_fd refers to the socket inode to connect to. */ + } /* Refuse zero length path early, to make sure AF_UNIX stack won't mistake this for an abstract * namespace path, since first char is NUL */ @@ -1636,7 +1652,14 @@ int connect_unix_path(int fd, int dir_fd, const char *path) { * exist. If the path is too long, we also need to take the indirect route, since we can't fit this * into a sockaddr_un directly. */ - inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC); + if (dir_fd == XAT_FDROOT) { + _cleanup_free_ char *j = strjoin("/", path); + if (!j) + return -ENOMEM; + + inode_fd = open(j, O_PATH|O_CLOEXEC); + } else + inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC); if (inode_fd < 0) return -errno; @@ -1713,7 +1736,7 @@ int vsock_parse_cid(const char *s, unsigned *ret) { return -EINVAL; /* Parsed an AF_VSOCK "CID". This is a 32bit entity, and the usual type is "unsigned". We recognize - * the three special CIDs as strings, and otherwise parse the numeric CIDs. */ + * the four special CIDs as strings, and otherwise parse the numeric CIDs. */ if (streq(s, "hypervisor")) *ret = VMADDR_CID_HYPERVISOR; @@ -1721,6 +1744,8 @@ int vsock_parse_cid(const char *s, unsigned *ret) { *ret = VMADDR_CID_LOCAL; else if (streq(s, "host")) *ret = VMADDR_CID_HOST; + else if (STR_IN_SET(s, "any", "-1")) + *ret = VMADDR_CID_ANY; else return safe_atou(s, ret); @@ -1781,30 +1806,6 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { return 0; } -int vsock_get_local_cid(unsigned *ret) { - _cleanup_close_ int vsock_fd = -EBADF; - - vsock_fd = open("/dev/vsock", O_RDONLY|O_CLOEXEC); - if (vsock_fd < 0) - return log_debug_errno(errno, "Failed to open %s: %m", "/dev/vsock"); - - unsigned tmp; - if (ioctl(vsock_fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, &tmp) < 0) - return log_debug_errno(errno, "Failed to query local AF_VSOCK CID: %m"); - log_debug("Local AF_VSOCK CID: %u", tmp); - - /* 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)) - return log_debug_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "IOCTL_VM_SOCKETS_GET_LOCAL_CID returned special value (%u), ignoring.", tmp); - - if (ret) - *ret = tmp; - return 0; -} - int netlink_socket_get_multicast_groups(int fd, size_t *ret_len, uint32_t **ret_groups) { _cleanup_free_ uint32_t *groups = NULL; socklen_t len = 0, old_len; @@ -1870,3 +1871,67 @@ void cmsg_close_all(struct msghdr *mh) { } } } + +int tos_to_priority(uint8_t tos) { + /* Map the IP Precedence (top 3 bits of the TOS field) to Linux internal packet priorities + * (TC_PRIO_*). This exactly mirrors the standard Linux kernel IP precedence-to-priority mapping + * (rt_tos2priority) to ensure consistent behavior when explicitly setting SO_PRIORITY. */ + switch (IPTOS_PREC(tos)) { + case IPTOS_PREC_NETCONTROL: /* 0xc0 (CS7) - Network Control. Used for infrastructure control (e.g., STP, keepalives). */ + case IPTOS_PREC_INTERNETCONTROL: /* 0xe0 (CS6) - Internetwork Control. Used for routing protocols (e.g., OSPF, BGP) and DHCP. */ + return TC_PRIO_CONTROL; + + case IPTOS_PREC_CRITIC_ECP: /* 0xa0 (CS5) - Critical. Used for delay-sensitive traffic like Voice over IP (VoIP). */ + case IPTOS_PREC_FLASHOVERRIDE: /* 0x80 (CS4) - Flash Override. Used for interactive video and multimedia. */ + return TC_PRIO_INTERACTIVE; + + case IPTOS_PREC_FLASH: /* 0x60 (CS3) - Flash. Used for broadcast video and call signaling (e.g., SIP). */ + case IPTOS_PREC_IMMEDIATE: /* 0x40 (CS2) - Immediate. Used for OAM (Operations, Administration, and Management) and transactional data. */ + return TC_PRIO_INTERACTIVE_BULK; + + case IPTOS_PREC_PRIORITY: /* 0x20 (CS1) - Priority. Used for background traffic and bulk data transfers. */ + return TC_PRIO_BULK; + + case IPTOS_PREC_ROUTINE: /* 0x00 (CS0) - Routine. Best effort traffic. */ + default: + return TC_PRIO_BESTEFFORT; + } +} + +int socket_xattr_supported(void) { + int r; + + // FIXME: Drop this check once Linux 7.0 becomes our baseline + + /* Checks if socket inodes may have xattrs on this kernel. This should pass on kernel 7.0, fail on + * older kernels */ + + static int cached = -1; + if (cached >= 0) + return cached; + + const char *t; + r = tmp_dir(&t); + if (r < 0) + return r; + + _cleanup_free_ char *sp = NULL; + r = tempfn_random_child(t, "sockxattrtest", &sp); + if (r < 0) + return r; + + if (mknod(sp, S_IFSOCK | 0600, /* dev= */ 0) < 0) + return -errno; + + _cleanup_(unlink_and_freep) char *sp_destroy = TAKE_PTR(sp); + + /* Old kernels return EPERM. But let's also check for more appropriate error codes, to be friendly to + * seccomp policies */ + r = xsetxattr(AT_FDCWD, sp_destroy, /* at_flags= */ 0, "user.testxxx", "1"); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r) || r == -EPERM) + return (cached = false); + if (r < 0) + return log_debug_errno(r, "Failed to set test xattr on socket inode '%s': %m", sp_destroy); + + return (cached = true); +} diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index 78b948ad461b5..f9d6ddaaad0e5 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include -#include #include #include #include @@ -29,8 +27,8 @@ union sockaddr_union { struct sockaddr_ll ll; struct sockaddr_vm vm; - /* Ensure there is enough space to store Infiniband addresses */ - uint8_t ll_buffer[offsetof(struct sockaddr_ll, sll_addr) + CONST_MAX(ETH_ALEN, INFINIBAND_ALEN)]; + /* Ensure there is enough space to store an arbitrary hardware address, e.g. Infiniband */ + uint8_t ll_buffer[offsetof(struct sockaddr_ll, sll_addr) + HW_ADDR_MAX_SIZE]; /* Ensure there is enough space after the AF_UNIX sun_path for one more NUL byte, just to be sure that the path * component is always followed by at least one NUL byte. */ @@ -263,10 +261,12 @@ int socket_address_equal_unix(const char *a, const char *b); * authoritative. */ #define SOMAXCONN_DELUXE INT_MAX -int vsock_get_local_cid(unsigned *ret); - int netlink_socket_get_multicast_groups(int fd, size_t *ret_len, uint32_t **ret_groups); int socket_get_cookie(int fd, uint64_t *ret); void cmsg_close_all(struct msghdr *mh); + +int tos_to_priority(uint8_t tos); + +int socket_xattr_supported(void); diff --git a/src/basic/sort-util.c b/src/basic/sort-util.c index d848745677d9a..8aca5cac769cd 100644 --- a/src/basic/sort-util.c +++ b/src/basic/sort-util.c @@ -65,9 +65,28 @@ 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); +} + +int cmp_unsigned(const unsigned *a, const unsigned *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/sort-util.h b/src/basic/sort-util.h index 8e7a1991bb692..37c196158eb38 100644 --- a/src/basic/sort-util.h +++ b/src/basic/sort-util.h @@ -44,3 +44,4 @@ void qsort_r_safe(void *base, size_t nmemb, size_t size, comparison_userdata_fn_ int cmp_int(const int *a, const int *b); int cmp_uint16(const uint16_t *a, const uint16_t *b); +int cmp_unsigned(const unsigned *a, const unsigned *b); diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 43f82dd926809..72307fa721782 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -32,7 +32,7 @@ static int verify_stat_at( struct stat st; int r; - assert(fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(fd)); assert(!isempty(path) || !follow); assert(verify_func); @@ -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(wildcard_fd_is_valid(fd)); 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); @@ -377,7 +480,7 @@ static int xfstatfs(int fd, struct statfs *ret) { int xstatfsat(int dir_fd, const char *path, struct statfs *ret) { _cleanup_close_ int fd = -EBADF; - assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(dir_fd)); assert(ret); if (!isempty(path)) { @@ -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)) @@ -630,6 +737,9 @@ bool stat_inode_unmodified(const struct stat *a, const struct stat *b) { * about contents of the file. The purpose here is to detect file contents changes, and nothing * else. */ + assert(a); + assert(b); + return stat_inode_same(a, b) && a->st_mtim.tv_sec == b->st_mtim.tv_sec && a->st_mtim.tv_nsec == b->st_mtim.tv_nsec && @@ -654,40 +764,114 @@ 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) { + assert(ts); return timespec_load(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec }); } nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) { + assert(ts); return timespec_load_nsec(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec }); } void inode_hash_func(const struct stat *q, struct siphash *state) { + assert(q); + 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) { int r; + assert(a); + assert(b); + r = CMP(a->st_dev, b->st_dev); 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) { + assert(q); + + 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; + + assert(a); + assert(b); + + 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 +920,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.c b/src/basic/static-destruct.c index c39a1b381649a..442f5fe7ba4e8 100644 --- a/src/basic/static-destruct.c +++ b/src/basic/static-destruct.c @@ -1,13 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "memory-util.h" #include "static-destruct.h" void static_destruct_impl(const StaticDestructor *start, const StaticDestructor *end) { if (!start) return; - for (const StaticDestructor *d = ALIGN_PTR(start); d < end; d = ALIGN_PTR(d + 1)) + for (const StaticDestructor *d = start; d < end; d++) switch (d->type) { case STATIC_DESTRUCTOR_SIMPLE: d->simple.destroy(d->simple.data); diff --git a/src/basic/static-destruct.h b/src/basic/static-destruct.h index 5772d24240f88..8e71c9b235aec 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. */ \ @@ -38,7 +36,8 @@ typedef struct SimpleCleanup { free_func_t destroy; } SimpleCleanup; -typedef struct StaticDestructor { +/* Note: see the comment on struct Option in options.h for why _alignptr_ is required here. */ +typedef struct _alignptr_ StaticDestructor { StaticDestructorType type; union { SimpleCleanup simple; @@ -46,6 +45,8 @@ typedef struct StaticDestructor { }; } StaticDestructor; +assert_cc(sizeof(StaticDestructor) % sizeof(void*) == 0); + #define STATIC_DESTRUCTOR_REGISTER(variable, func) \ _STATIC_DESTRUCTOR_REGISTER(UNIQ, variable, func) 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/strbuf.c b/src/basic/strbuf.c index c2342e67a22bd..b54dc8fedf777 100644 --- a/src/basic/strbuf.c +++ b/src/basic/strbuf.c @@ -86,7 +86,7 @@ static void bubbleinsert(struct strbuf_node *node, .c = c, .child = node_child, }; - int left = 0, right = node->children_count; + int left = 0, right = ASSERT_PTR(node)->children_count; while (right > left) { int middle = (right + left) / 2 ; diff --git a/src/basic/string-table.c b/src/basic/string-table.c index 069cb40ec1a0d..5cb1e79744e91 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" @@ -10,10 +11,14 @@ const char* string_table_lookup_to_string(const char * const *table, size_t len, if (i < 0 || i >= (ssize_t) len) return NULL; + assert(table); + return table[i]; } ssize_t string_table_lookup_from_string(const char * const *table, size_t len, const char *key) { + assert_return(table, -EINVAL); + if (!key) return -EINVAL; @@ -40,14 +45,17 @@ 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(table); + 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..1dc90d538ad01 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -163,6 +163,8 @@ char* ascii_strupper(char *s) { } char* ascii_strlower_n(char *s, size_t n) { + assert(n <= 0 || s); + if (n <= 0) return s; @@ -174,6 +176,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,8 +281,18 @@ 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. */ + if (length < 2) { + /* Need at least two bytes for an ANSI sequence */ + *ret_where = NULL; + return 0; + } + for (size_t i = length - 2; i > 0; i--) { /* -2 because at least two bytes are needed */ size_t slen = ansi_sequence_length(s + (i - 1), length - (i - 1)); if (slen == 0) @@ -660,6 +675,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,44 +985,55 @@ 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) { assert(s); assert(!isempty(sep)); - assert(ret_first); - assert(ret_second); const char *x = strstr(s, sep); if (!x) return -EINVAL; - _cleanup_free_ char *a = strndup(s, x - s); - if (!a) - return -ENOMEM; + _cleanup_free_ char *a = NULL; + if (ret_first) { + a = strndup(s, x - s); + if (!a) + return -ENOMEM; + } - _cleanup_free_ char *b = strdup(x + strlen(sep)); - if (!b) - return -ENOMEM; + _cleanup_free_ char *b = NULL; + if (ret_second) { + b = strdup(x + strlen(sep)); + if (!b) + return -ENOMEM; + } - *ret_first = TAKE_PTR(a); - *ret_second = TAKE_PTR(b); + if (ret_first) + *ret_first = TAKE_PTR(a); + if (ret_second) + *ret_second = TAKE_PTR(b); return 0; } @@ -1086,25 +1113,50 @@ 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 */ + /* never allow control characters, except for new line */ + if ((*t > 0 && *t < ' ' && *t != '\n') || *t == 0x7f) + return false; + + if (!FLAGS_SET(flags, STRING_ALLOW_NEWLINES) && *t == '\n') 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_DISALLOW_WHITESPACE) && strchr(WHITESPACE, *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; + + if (FLAGS_SET(flags, STRING_FILENAME_PART) && !filename_part_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 +1185,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 +1240,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 +1331,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 +1350,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 +1558,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..68ab38c32113b 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -5,7 +5,8 @@ #include "alloc-util.h" #include "basic-forward.h" -#include "string-util-fundamental.h" /* IWYU pragma: export */ + +#include "../fundamental/string-util.h" /* IWYU pragma: export */ static inline char* strstr_ptr_internal(const char *haystack, const char *needle) { if (!haystack || !needle) @@ -193,7 +194,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 +221,19 @@ 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_NEWLINES = 1 << 2, /* Allow newlines (\n) */ + STRING_ALLOW_BACKSLASHES = 1 << 3, /* Allow backslashes (\) */ + STRING_ALLOW_QUOTES = 1 << 4, /* Allow quotes (" or ') */ + STRING_ALLOW_GLOBS = 1 << 5, /* Allow globs (?, * or [) */ + STRING_FILENAME = 1 << 6, /* Verify the string is valid as regular filename */ + STRING_FILENAME_PART = 1 << 7, /* Verify the string is valid as part of a regular filename */ + STRING_DISALLOW_WHITESPACE = 1 << 8, /* Refuse whitespace (space, tab, newline, …) */ +} 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 +283,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 +324,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..0be282eb598d8 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -383,6 +383,7 @@ int strv_split_newlines_full(char ***ret, const char *s, ExtractFlags flags) { size_t n; int r; + assert(ret); assert(s); /* Special version of strv_split_full() that splits on newlines and @@ -587,6 +588,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 +619,8 @@ int strv_push_pair(char ***l, char *a, char *b) { char **c; size_t n; + assert(l); + if (!a && !b) return 0; @@ -807,16 +813,14 @@ bool strv_is_uniq(char * const *l) { } char** strv_remove(char **l, const char *s) { - char **f, **t; - if (!l) return NULL; assert(s); - /* Drops every occurrence of s in the string list, edits - * in-place. */ + /* Drops every occurrence of s in the string list, edits in-place. */ + char **f, **t; for (f = t = l; *f; f++) if (streq(*f, s)) free(*f); @@ -827,7 +831,21 @@ char** strv_remove(char **l, const char *s) { return l; } +char** strv_remove_strv(char **l, char *const*ll) { + + if (strv_isempty(l)) + return l; + + STRV_FOREACH(i, ll) + strv_remove(l, *i); + + return l; +} + 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 +854,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 +885,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..7bac1b66fa977 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -2,7 +2,8 @@ #pragma once #include "basic-forward.h" -#include "strv-fundamental.h" /* IWYU pragma: export */ + +#include "../fundamental/strv.h" /* IWYU pragma: export */ char* strv_find(char * const *l, const char *name) _pure_; char* strv_find_case(char * const *l, const char *name) _pure_; @@ -84,6 +85,7 @@ int strv_consume_pair(char ***l, char *a, char *b); int strv_consume_prepend(char ***l, char *value); char** strv_remove(char **l, const char *s); +char** strv_remove_strv(char **l, char *const*ll); char** strv_uniq(char **l); bool strv_is_uniq(char * const *l) _pure_; @@ -104,10 +106,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/sysctl-util.c b/src/basic/sysctl-util.c index ae70abe627828..7bec823f4218e 100644 --- a/src/basic/sysctl-util.c +++ b/src/basic/sysctl-util.c @@ -16,6 +16,8 @@ char* sysctl_normalize(char *s) { char *n; + assert(s); + n = strpbrk(s, "/."); /* If the first separator is a slash, the path is 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..cde9f1ec8c091 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; @@ -238,6 +238,8 @@ static CompletionResult pick_completion(const char *string, char *const*completi _cleanup_free_ char *found = NULL; bool partial = false; + assert(ret); + string = strempty(string); STRV_FOREACH(c, completions) { @@ -283,6 +285,7 @@ static void clear_by_backspace(size_t n) { int ask_string_full( char **ret, + const char *prefill, GetCompletionsCallback get_completions, void *userdata, const char *text, ...) { @@ -293,29 +296,58 @@ int ask_string_full( assert(ret); assert(text); + _cleanup_free_ char *string = NULL; + size_t n = 0; + + if (prefill) { + /* Prefill query with explicit data if specified */ + + string = strdup(prefill); + if (!string) + return -ENOMEM; + + n = strlen(string); + + } else if (get_completions) { + /* Otherwise, figure out what string to preselect the query with */ + _cleanup_strv_free_ char **completions = NULL; + r = get_completions("", GET_COMPLETIONS_PRESELECT, &completions, userdata); + if (r < 0) + return r; + + CompletionResult cr = pick_completion(string, completions, &string); + if (cr < 0) + return cr; + + n = strlen_ptr(string); + } + /* Output the prompt */ fputs(ansi_highlight(), stdout); va_start(ap, text); vprintf(text, ap); va_end(ap); fputs(ansi_normal(), stdout); + if (string) + fputs(string, stdout); fflush(stdout); - _cleanup_free_ char *string = NULL; - size_t n = 0; - /* Do interactive logic only if stdin + stdout are connected to the same place. And yes, we could use * STDIN_FILENO and STDOUT_FILENO here, but let's be overly correct for once, after all libc allows * 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); @@ -340,17 +372,15 @@ int ask_string_full( _cleanup_strv_free_ char **completions = NULL; if (get_completions) { - r = get_completions(string, &completions, userdata); + r = get_completions(string, /* flags= */ 0, &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 +399,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 +409,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 +447,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 +455,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 +467,6 @@ int ask_string_full( fflush(stdout); } - if (tcsetattr(fd_input, TCSANOW, &old_termios) < 0) - return -errno; - if (!string) { string = strdup(""); if (!string) @@ -454,12 +476,9 @@ 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 */ + string = mfree(string); r = read_line(stdin, LONG_LINE_MAX, &string); if (r < 0) return r; @@ -510,6 +529,7 @@ int show_menu(char **x, const char *grey_prefix, bool with_numbers) { + assert(x); assert(n_columns > 0); if (n_columns == SIZE_MAX) @@ -858,20 +878,12 @@ 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) { - _cleanup_free_ char *b = NULL; - int r; - /* Read the default VT UTF8 setting from the kernel */ - - r = read_one_line_file("/sys/module/vt/parameters/default_utf8", &b); - if (r < 0) - return r; - - return parse_boolean(b); + return read_boolean_file("/sys/module/vt/parameters/default_utf8"); } static int vt_reset_keyboard(int fd) { @@ -957,7 +969,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 +980,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 +991,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 +1485,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; @@ -1665,6 +1676,8 @@ int openpt_allocate_in_namespace( _cleanup_close_pair_ int pair[2] = EBADF_PAIR; int r; + assert(pidref); + r = pidref_namespace_open(pidref, &pidnsfd, &mntnsfd, /* ret_netns_fd= */ NULL, &usernsfd, &rootfd); if (r < 0) return log_debug_errno(r, "Failed to open namespaces of PID "PID_FMT": %m", pidref->pid); @@ -1726,6 +1739,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 +1887,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 +1899,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 +2013,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 +2037,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 +2054,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 +2071,7 @@ int terminal_get_cursor_position( if (errno == EAGAIN) continue; - r = -errno; - goto finish; + return -errno; } assert((size_t) l <= sizeof(buf)); @@ -2055,7 +2081,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 +2089,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 +2141,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 +2331,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 +2345,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 +2355,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 +2371,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 +2381,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 +2394,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 +2436,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 +2453,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 +2463,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; - if (ret_rows) - *ret_rows = context.row; - if (ret_columns) - *ret_columns = context.column; + assert(input_fd >= 0); + assert(output_fd >= 0); + assert(ret_nonblock_fd); + assert(ret_saved_termios); - r = 0; - goto finish; - } - } - } + /* 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; -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); + r = terminal_verify_same(input_fd, output_fd); + if (r < 0) + return log_debug_errno(r, "Called with distinct input/output fds: %m"); - 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. 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; + + 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 +2566,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 +2606,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 +2614,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 +2690,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 +2769,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 +2783,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 +2807,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 +2815,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..f8cd74f410521 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -88,9 +88,15 @@ int chvt(int vt); int read_one_char(FILE *f, char *ret, usec_t timeout, bool echo, bool *need_nl); int ask_char(char *ret, const char *replies, const char *fmt, ...) _printf_(3, 4); -typedef int (*GetCompletionsCallback)(const char *key, char ***ret_list, void *userdata); -int ask_string_full(char **ret, GetCompletionsCallback get_completions, void *userdata, const char *text, ...) _printf_(4, 5); -#define ask_string(ret, text, ...) ask_string_full(ret, NULL, NULL, text, ##__VA_ARGS__) +typedef enum GetCompletionsFlags { + /* Only return the items subject to preselection: typically you want to suppress meta entries such as + * "list" or alias entries if this flag is set. */ + GET_COMPLETIONS_PRESELECT = 1 << 0, +} GetCompletionsFlags; + +typedef int (*GetCompletionsCallback)(const char *key, GetCompletionsFlags flags, char ***ret_list, void *userdata); +int ask_string_full(char **ret, const char *prefill, GetCompletionsCallback get_completions, void *userdata, const char *text, ...) _printf_(5, 6); +#define ask_string(ret, text, ...) ask_string_full(ret, NULL, NULL, NULL, text, ##__VA_ARGS__) bool any_key_to_proceed(void); int show_menu(char **x, size_t n_columns, size_t column_width, unsigned ellipsize_percentage, const char *grey_prefix, bool with_numbers); @@ -118,6 +124,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 +152,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..eb74de32c2db8 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) { @@ -1684,6 +1697,16 @@ int get_timezone(char **ret) { return strdup_to(ret, e); } +int get_timezone_prefer_env(char **ret) { + assert(ret); + + const char *e = getenv("TZ"); + if (e && e[0] == ':' && timezone_is_valid(e + 1, LOG_DEBUG)) + return strdup_to(ret, e + 1); + + return get_timezone(ret); +} + const char* etc_localtime(void) { static const char *cached = NULL; @@ -1892,3 +1915,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..fdaf11edcbf6c 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); @@ -176,11 +178,29 @@ bool clock_supported(clockid_t clock); usec_t usec_shift_clock(usec_t x, clockid_t from, clockid_t to); int get_timezone(char **ret); +int get_timezone_prefer_env(char **ret); 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..19130dac202fc 100644 --- a/src/basic/tmpfile-util.c +++ b/src/basic/tmpfile-util.c @@ -101,6 +101,8 @@ int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) { _cleanup_close_ int fd = -EBADF; FILE *f; + assert(ret_f); + fd = mkostemp_safe(pattern); if (fd < 0) return fd; @@ -294,7 +296,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..4d2c246dcd14b 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--; } } @@ -386,9 +395,18 @@ int uid_range_partition(UIDRange *range, uid_t size) { if (n_new_entries > range->n_entries && !GREEDY_REALLOC(range->entries, n_new_entries)) return -ENOMEM; - /* Work backwards to avoid overwriting entries we still need to read */ + /* Compact in place: drop entries that contribute zero partitions (nr < size). This forward pass + * reads each entry once and only writes to lower-or-equal indices, so it cannot alias an unread + * source entry. */ + size_t n_src = 0; + for (size_t i = 0; i < range->n_entries; i++) + if (range->entries[i].nr >= size) + range->entries[n_src++] = range->entries[i]; + + /* Pre-compaction guarantees every surviving entry contributes at least one partition slot, so the + * write cursor t stays ahead of the read index. */ size_t t = n_new_entries; - for (size_t i = range->n_entries; i > 0; i--) { + for (size_t i = n_src; i > 0; i--) { UIDRangeEntry *e = range->entries + i - 1; unsigned n_parts = e->nr / size; @@ -608,7 +626,6 @@ int uid_map_search_root(pid_t pid, UIDRangeUsernsMode mode, uid_t *ret) { } uid_t uid_range_base(const UIDRange *range) { - /* Returns the lowest UID in the range (notw that elements are sorted, hence we just need to look at * the first one that is populated. */ diff --git a/src/basic/unaligned.h b/src/basic/unaligned.h index b45fb3c3587d6..f54d7d8edd999 100644 --- a/src/basic/unaligned.h +++ b/src/basic/unaligned.h @@ -4,7 +4,8 @@ #include #include "basic-forward.h" -#include "unaligned-fundamental.h" /* IWYU pragma: export */ + +#include "../fundamental/unaligned.h" /* IWYU pragma: export */ /* BE */ 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..0efc66b91b7e2 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; @@ -714,7 +716,7 @@ int unit_name_mangle_with_suffix( char **ret) { _cleanup_free_ char *s = NULL; - bool mangled, suggest_escape = true, warn = flags & UNIT_NAME_MANGLE_WARN; + bool mangled, suggest_escape = true, warn = FLAGS_SET(flags, UNIT_NAME_MANGLE_WARN); int r; assert(name); @@ -728,12 +730,23 @@ int unit_name_mangle_with_suffix( return -EINVAL; /* Already a fully valid unit name? If so, no mangling is necessary... */ - if (unit_name_is_valid(name, UNIT_NAME_ANY)) + if (unit_name_is_valid(name, UNIT_NAME_ANY)) { + if (FLAGS_SET(flags, UNIT_NAME_MANGLE_STRICT) && !endswith(name, suffix)) { + const char *e = ASSERT_PTR(strrchr(name, '.')); + + return log_full_errno(warn ? LOG_NOTICE : LOG_DEBUG, + SYNTHETIC_ERRNO(EINVAL), + "Unit name \"%s\" has unit type \"%s\", but \"%s\" is expected%s%s.", + name, e + 1, suffix + 1, + operation ? " " : "", strempty(operation)); + } + goto good; + } /* Already a fully valid globbing expression? If so, no mangling is necessary either... */ if (string_is_glob(name) && in_charset(name, VALID_CHARS_GLOB)) { - if (flags & UNIT_NAME_MANGLE_GLOB) + if (FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB)) goto good; log_full(warn ? LOG_NOTICE : LOG_DEBUG, "Glob pattern passed%s%s, but globs are not supported for this.", @@ -742,23 +755,41 @@ int unit_name_mangle_with_suffix( } if (path_is_absolute(name)) { - _cleanup_free_ char *n = NULL; + _cleanup_free_ char *n = NULL, *u = NULL; r = path_simplify_alloc(name, &n); if (r < 0) return r; if (is_device_path(n)) { - r = unit_name_from_path(n, ".device", ret); - if (r >= 0) + r = unit_name_from_path(n, ".device", &u); + if (r >= 0) { + if (FLAGS_SET(flags, UNIT_NAME_MANGLE_STRICT) && !streq(suffix, ".device")) + return log_full_errno(warn ? LOG_NOTICE : LOG_DEBUG, + SYNTHETIC_ERRNO(EINVAL), + "Path \"%s\" resolves to unit type \"device\", but \"%s\" is expected%s%s.", + name, suffix + 1, + operation ? " " : "", strempty(operation)); + + *ret = TAKE_PTR(u); return 1; + } if (r != -EINVAL) return r; } - r = unit_name_from_path(n, ".mount", ret); - if (r >= 0) + r = unit_name_from_path(n, ".mount", &u); + if (r >= 0) { + if (FLAGS_SET(flags, UNIT_NAME_MANGLE_STRICT) && !streq(suffix, ".mount")) + return log_full_errno(warn ? LOG_NOTICE : LOG_DEBUG, + SYNTHETIC_ERRNO(EINVAL), + "Path \"%s\" resolves to unit type \"mount\", but \"%s\" is expected%s%s.", + name, suffix + 1, + operation ? " " : "", strempty(operation)); + + *ret = TAKE_PTR(u); return 1; + } if (r != -EINVAL) return r; } @@ -767,7 +798,7 @@ int unit_name_mangle_with_suffix( if (!s) return -ENOMEM; - mangled = do_escape_mangle(name, flags & UNIT_NAME_MANGLE_GLOB, s); + mangled = do_escape_mangle(name, FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB), s); if (mangled) log_full(warn ? LOG_NOTICE : LOG_DEBUG, "Invalid unit name \"%s\" escaped as \"%s\"%s.", @@ -776,7 +807,7 @@ int unit_name_mangle_with_suffix( /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow * "foo.*" as a valid glob. */ - if ((!(flags & UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0) + if ((!FLAGS_SET(flags, UNIT_NAME_MANGLE_GLOB) || !string_is_glob(s)) && unit_name_to_type(s) < 0) strcat(s, suffix); /* Make sure mangling didn't grow this too large (but don't do this check if globbing is allowed, diff --git a/src/basic/unit-name.h b/src/basic/unit-name.h index e454d0a86dbb5..7484ec6529e31 100644 --- a/src/basic/unit-name.h +++ b/src/basic/unit-name.h @@ -56,8 +56,9 @@ int unit_name_from_path_instance(const char *prefix, const char *path, const cha int unit_name_to_path(const char *name, char **ret); typedef enum UnitNameMangle { - UNIT_NAME_MANGLE_GLOB = 1 << 0, - UNIT_NAME_MANGLE_WARN = 1 << 1, + UNIT_NAME_MANGLE_GLOB = 1 << 0, + UNIT_NAME_MANGLE_WARN = 1 << 1, + UNIT_NAME_MANGLE_STRICT = 1 << 2, /* Refuse if the resolved unit type doesn't match the requested suffix */ } UnitNameMangle; int unit_name_mangle_with_suffix(const char *name, const char *operation, UnitNameMangle flags, const char *suffix, char **ret); diff --git a/src/basic/user-util.c b/src/basic/user-util.c index e434fbec8f985..a665a2d78018c 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) @@ -204,67 +204,97 @@ const char* default_root_shell(const char *root) { return default_root_shell_at(rfd); } -static int synthesize_user_creds( - const char **username, +static int return_user_creds( + const char *username, + uid_t uid, gid_t gid, + const char *home, + const char *shell, + char **ret_username, uid_t *ret_uid, gid_t *ret_gid, - const char **ret_home, - const char **ret_shell, - UserCredsFlags flags) { - - assert(username); - assert(*username); + char **ret_home, + char **ret_shell) { + /* Helper function to help with the strdups and atomic setting of return params. */ - /* We enforce some special rules for uid=0 and uid=65534: in order to avoid NSS lookups for root we hardcode - * their user record data. */ + _cleanup_free_ char *s1 = NULL, *s2 = NULL, *s3 = NULL; + int r; - if (STR_IN_SET(*username, "root", "0")) { - *username = "root"; + if (ret_username) { + r = strdup_to(&s1, username); + if (r < 0) + return r; + } - if (ret_uid) - *ret_uid = 0; - if (ret_gid) - *ret_gid = 0; - if (ret_home) - *ret_home = "/root"; - if (ret_shell) - *ret_shell = default_root_shell(NULL); + if (ret_home) { + r = strdup_to(&s2, home); + if (r < 0) + return r; + } - return 0; + if (ret_shell) { + r = strdup_to(&s3, shell); + if (r < 0) + return r; } - if (STR_IN_SET(*username, NOBODY_USER_NAME, "65534") && - synthesize_nobody()) { - *username = NOBODY_USER_NAME; + if (ret_username) + *ret_username = TAKE_PTR(s1); + if (ret_uid) + *ret_uid = uid; + if (ret_gid) + *ret_gid = gid; + if (ret_home) + *ret_home = TAKE_PTR(s2); + if (ret_shell) + *ret_shell = TAKE_PTR(s3); + return 0; +} - if (ret_uid) - *ret_uid = UID_NOBODY; - if (ret_gid) - *ret_gid = GID_NOBODY; - if (ret_home) - *ret_home = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : "/"; - if (ret_shell) - *ret_shell = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : NOLOGIN; +static int synthesize_user_creds( + const char *username, + UserCredsFlags flags, + char **ret_username, + uid_t *ret_uid, gid_t *ret_gid, + char **ret_home, + char **ret_shell) { + assert(username); - return 0; - } + /* We enforce some special rules for uid=0 and uid=65534: in order to avoid nss lookups for root we + * hardcode their user record data. */ + if (STR_IN_SET(username, "root", "0")) + return return_user_creds("root", 0, 0, + "/root", + ret_shell ? default_root_shell(NULL) : NULL, + ret_username, + ret_uid, ret_gid, + ret_home, + ret_shell); + + if (STR_IN_SET(username, NOBODY_USER_NAME, "65534") && + synthesize_nobody()) + return return_user_creds(NOBODY_USER_NAME, UID_NOBODY, GID_NOBODY, + FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : "/", + FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : NOLOGIN, + ret_username, + ret_uid, ret_gid, + ret_home, + ret_shell); return -ENOMEDIUM; } int get_user_creds( - const char **username, + const char *username, + UserCredsFlags flags, + char **ret_username, uid_t *ret_uid, gid_t *ret_gid, - const char **ret_home, - const char **ret_shell, - UserCredsFlags flags) { + char **ret_home, + char **ret_shell) { - bool patch_username = false; uid_t u = UID_INVALID; - struct passwd *p; + _cleanup_free_ struct passwd *pw = NULL; int r; assert(username); - assert(*username); assert((ret_home || ret_shell) || !(flags & (USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_CLEAN))); if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) || @@ -279,164 +309,134 @@ int get_user_creds( * of the relevant users, but changing the UID/GID mappings for them is something we explicitly don't * support. */ - r = synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags); + r = synthesize_user_creds(username, flags, ret_username, ret_uid, ret_gid, ret_home, ret_shell); if (r >= 0) return 0; if (r != -ENOMEDIUM) /* not a username we can synthesize */ return r; } - if (parse_uid(*username, &u) >= 0) { - errno = 0; - p = getpwuid(u); + if (parse_uid(username, &u) >= 0) { + r = getpwuid_malloc(u, &pw); /* If there are multiple users with the same id, make sure to leave $USER to the configured value * instead of the first occurrence in the database. However if the uid was configured by a numeric uid, * then let's pick the real username from /etc/passwd. */ - if (p) - patch_username = true; - else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !ret_gid && !ret_home && !ret_shell) { + if (r >= 0) + username = pw->pw_name; + else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !ret_gid && !ret_home && !ret_shell) { /* If the specified user is a numeric UID and it isn't in the user database, and the caller * passed USER_CREDS_ALLOW_MISSING and was only interested in the UID, then just return that * and don't complain. */ - + if (ret_username) + *ret_username = NULL; if (ret_uid) *ret_uid = u; - return 0; } - } else { - errno = 0; - p = getpwnam(*username); - } - if (!p) { - /* getpwnam() may fail with ENOENT if /etc/passwd is missing. - * For us that is equivalent to the name not being defined. */ - r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno; + } else + r = getpwnam_malloc(username, &pw); + if (r < 0) { /* If the user requested that we only synthesize as fallback, do so now */ - if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) - if (synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags) >= 0) - return 0; + if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS) && + synthesize_user_creds(username, flags, ret_username, ret_uid, ret_gid, ret_home, ret_shell) >= 0) + return 0; return r; } - if (ret_uid && !uid_is_valid(p->pw_uid)) + if (ret_uid && !uid_is_valid(pw->pw_uid)) return -EBADMSG; - if (ret_gid && !gid_is_valid(p->pw_gid)) + if (ret_gid && !gid_is_valid(pw->pw_gid)) return -EBADMSG; - if (ret_uid) - *ret_uid = p->pw_uid; + /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */ + const char *h = + (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && empty_or_root(pw->pw_dir)) || + (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(pw->pw_dir) || !path_is_absolute(pw->pw_dir))) + ? NULL : pw->pw_dir; - if (ret_gid) - *ret_gid = p->pw_gid; - - if (ret_home) - /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */ - *ret_home = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && empty_or_root(p->pw_dir)) || - (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_dir) || !path_is_absolute(p->pw_dir))) - ? NULL : p->pw_dir; - - if (ret_shell) - *ret_shell = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && shell_is_placeholder(p->pw_shell)) || - (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_shell) || !path_is_absolute(p->pw_shell))) - ? NULL : p->pw_shell; + const char *s = + (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && shell_is_placeholder(pw->pw_shell)) || + (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(pw->pw_shell) || !path_is_absolute(pw->pw_shell))) + ? NULL : pw->pw_shell; - if (patch_username) - *username = p->pw_name; - - return 0; + return return_user_creds(username, pw->pw_uid, pw->pw_gid, h, s, + ret_username, ret_uid, ret_gid, ret_home, ret_shell); } -static int synthesize_group_creds( - const char **groupname, - gid_t *ret_gid) { - +static int synthesize_group_creds(const char *groupname, char **ret_name, gid_t *ret_gid) { assert(groupname); - assert(*groupname); - - if (STR_IN_SET(*groupname, "root", "0")) { - *groupname = "root"; - - if (ret_gid) - *ret_gid = 0; - - return 0; - } - - if (STR_IN_SET(*groupname, NOBODY_GROUP_NAME, "65534") && - synthesize_nobody()) { - *groupname = NOBODY_GROUP_NAME; - - if (ret_gid) - *ret_gid = GID_NOBODY; - return 0; - } + gid_t id; + const char *n; + int r; - return -ENOMEDIUM; + if (STR_IN_SET(groupname, "root", "0")) { + id = 0; + n = "root"; + } else if (STR_IN_SET(groupname, NOBODY_GROUP_NAME, "65534") && + synthesize_nobody()) { + id = GID_NOBODY; + n = NOBODY_GROUP_NAME; + } else + return -ENOMEDIUM; + + r = strdup_to_full(ret_name, n); + if (r < 0) + return r; + if (ret_gid) + *ret_gid = id; + return 0; } -int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags) { - bool patch_groupname = false; - struct group *g; +int get_group_creds(const char *groupname, UserCredsFlags flags, char **ret_name, gid_t *ret_gid) { + _cleanup_free_ struct group *gr = NULL; gid_t id; int r; assert(groupname); - assert(*groupname); if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) { - r = synthesize_group_creds(groupname, ret_gid); + r = synthesize_group_creds(groupname, ret_name, ret_gid); if (r >= 0) return 0; if (r != -ENOMEDIUM) /* not a groupname we can synthesize */ return r; } - if (parse_gid(*groupname, &id) >= 0) { - errno = 0; - g = getgrgid(id); - - if (g) - patch_groupname = true; + if (parse_gid(groupname, &id) >= 0) { + r = getgrgid_malloc(id, &gr); + if (r >= 0) + groupname = gr->gr_name; else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING)) { if (ret_gid) *ret_gid = id; - + if (ret_name) + *ret_name = NULL; return 0; } - } else { - errno = 0; - g = getgrnam(*groupname); - } - - if (!g) { - /* getgrnam() may fail with ENOENT if /etc/group is missing. - * For us that is equivalent to the name not being defined. */ - r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno; - - if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) - if (synthesize_group_creds(groupname, ret_gid) >= 0) - return 0; + } else + r = getgrnam_malloc(groupname, &gr); + if (r < 0) { + if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS) && + synthesize_group_creds(groupname, ret_name, ret_gid) >= 0) + return 0; return r; } - if (ret_gid) { - if (!gid_is_valid(g->gr_gid)) - return -EBADMSG; - - *ret_gid = g->gr_gid; - } - - if (patch_groupname) - *groupname = g->gr_name; + if (ret_gid && !gid_is_valid(gr->gr_gid)) + return -EBADMSG; + r = strdup_to_full(ret_name, groupname); + if (r < 0) + return r; + if (ret_gid) + *ret_gid = gr->gr_gid; return 0; } @@ -521,7 +521,7 @@ int in_group(const char *name) { int r; gid_t gid; - r = get_group_creds(&name, &gid, 0); + r = get_group_creds(name, /* flags= */ 0, /* ret_name= */ NULL, &gid); if (r < 0) return r; @@ -530,6 +530,9 @@ int in_group(const char *name) { int merge_gid_lists(const gid_t *list1, size_t size1, const gid_t *list2, size_t size2, gid_t **ret) { size_t nresult = 0; + + assert(size1 == 0 || list1); + assert(size2 == 0 || list2); assert(ret); if (size2 > INT_MAX - size1) @@ -762,18 +765,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 @@ -1072,7 +1074,7 @@ int is_this_me(const char *username) { /* Checks if the specified username is our current one. Passed string might be a UID or a user name. */ - r = get_user_creds(&username, &uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING); + r = get_user_creds(username, /* flags= */ USER_CREDS_ALLOW_MISSING, NULL, &uid, NULL, NULL, NULL); if (r < 0) return r; @@ -1080,10 +1082,8 @@ int is_this_me(const char *username) { } const char* get_home_root(void) { - const char *e; - /* For debug purposes allow overriding where we look for home dirs */ - e = secure_getenv("SYSTEMD_HOME_ROOT"); + const char *e = secure_getenv("SYSTEMD_HOME_ROOT"); if (e && path_is_absolute(e) && path_is_normalized(e)) return e; @@ -1114,6 +1114,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 +1157,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 +1203,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 +1244,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/user-util.h b/src/basic/user-util.h index 003420dbe3b0d..641bdc06b0999 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -62,8 +62,14 @@ typedef enum UserCredsFlags { USER_CREDS_SUPPRESS_PLACEHOLDER = 1 << 3, /* suppress home and/or shell fields if value is placeholder (root/empty/nologin) */ } UserCredsFlags; -int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags); -int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags); +int get_user_creds( + const char *username, + UserCredsFlags flags, + char **ret_username, + uid_t *ret_uid, gid_t *ret_gid, + char **ret_home, + char **ret_shell); +int get_group_creds(const char *groupname, UserCredsFlags flags, char **ret_name, gid_t *ret_gid); char* uid_to_name(uid_t uid); char* gid_to_name(gid_t gid); @@ -90,6 +96,10 @@ int take_etc_passwd_lock(const char *root); #define UID_NOBODY ((uid_t) 65534U) #define GID_NOBODY ((gid_t) 65534U) +/* Conventional size of a user-namespace UID/GID delegation block (64K). + * Untyped so it can be used in both UID and GID contexts without casts. */ +#define USERNS_RANGE_SIZE 0x10000U + /* If REMOUNT_IDMAPPING_HOST_ROOT is set for remount_idmap() we'll include a mapping here that maps the host * root user accessing the idmapped mount to the this user ID on the backing fs. This is the last valid UID in * the *signed* 32-bit range. You might wonder why precisely use this specific UID for this purpose? Well, we diff --git a/src/basic/utf8.c b/src/basic/utf8.c index 8a0cfc012ec6d..faa3df9d87c3e 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]); @@ -199,7 +200,8 @@ char* utf8_escape_non_printable_full(const char *str, size_t console_width, bool if (console_width == 0) return strdup(""); - p = s = prev_s = malloc(strlen(str) * 4 + 1); + size_t body = strlen(str) * 4; + p = s = prev_s = malloc(body + STRLEN("…") + 1); if (!p) return NULL; @@ -283,6 +285,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; @@ -419,6 +424,7 @@ char* utf16_to_utf8(const char16_t *s, size_t length /* bytes! */) { } size_t utf16_encode_unichar(char16_t *out, char32_t c) { + assert(out); /* Note that this encodes as little-endian. */ @@ -500,6 +506,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 +574,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 +593,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/virt.c b/src/basic/virt.c index 2eea145d9c099..81470c2e83ffa 100644 --- a/src/basic/virt.c +++ b/src/basic/virt.c @@ -909,6 +909,8 @@ static const struct cpuid_table_entry leaf87_edx[] = { }; static bool given_flag_in_set(const char *flag, const struct cpuid_table_entry *set, size_t set_size, uint32_t val) { + assert(set); + for (size_t i = 0; i < set_size; i++) { if ((UINT32_C(1) << set[i].flag_bit) & val && streq(flag, set[i].name)) diff --git a/src/basic/xattr-util.c b/src/basic/xattr-util.c index 68ee83d899ede..417e8650e53ca 100644 --- a/src/basic/xattr-util.c +++ b/src/basic/xattr-util.c @@ -36,8 +36,7 @@ static int normalize_and_maybe_pin_inode( assert(ret_tfd); assert(ret_opath); - if (isempty(*path)) - *path = NULL; /* Normalize "" to NULL */ + *path = empty_to_null(*path); /* Normalize "" to NULL */ if (*fd == AT_FDCWD) { if (!*path) /* Both unspecified? Then operate on current working directory */ @@ -301,6 +300,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 +440,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..13dc8960f2efb 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts) != 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..4e24b35db744b 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,70 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts); + + 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 +191,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 +205,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..43fb72cddb72b 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,76 @@ 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 opts = { argc, argv }; - case 'h': - help(0, NULL, NULL); - return 0; + FOREACH_OPTION_OR_RETURN(c, &opts) + 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, opts.arg); if (r < 0) return log_oom(); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&opts); return 1; } @@ -106,11 +109,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 +319,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 +328,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_DEFAULT_NOARG(verb_status, "status", "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 +447,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 +481,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 +563,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 +580,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(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..9fa42a7ed6620 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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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..7be0880d3b074 --- /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.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..dbe7f0c8ab60c 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -1,25 +1,28 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "bcd.h" -#include "bootspec-fundamental.h" +#include "bootspec.h" #include "console.h" +#include "cpio.h" #include "device-path-util.h" #include "devicetree.h" #include "drivers.h" #include "efi-efivars.h" #include "efi-log.h" #include "efi-string-table.h" -#include "efivars-fundamental.h" +#include "efivars.h" #include "export-vars.h" #include "graphics.h" #include "initrd.h" -#include "iovec-util-fundamental.h" +#include "iovec-util.h" #include "line-edit.h" #include "measure.h" -#include "memory-util-fundamental.h" +#include "measure-smbios.h" +#include "memory-util.h" #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" @@ -27,7 +30,7 @@ #include "secure-boot.h" #include "shim.h" #include "smbios.h" -#include "strv-fundamental.h" +#include "strv.h" #include "sysfail.h" #include "ticks.h" #include "tpm2-pcr.h" @@ -37,6 +40,9 @@ #include "version.h" #include "vmm.h" +/* Safety margin, refuse larger extra files (this is not load bearing, only a safety net for robustness reasons). */ +#define EXTRA_SIZE_MAX (1024U * 1024U * 1536U) + /* Magic string for recognizing our own binaries */ #define SD_MAGIC "#### LoaderInfo: systemd-boot " GIT_VERSION " ####" DECLARE_NOALLOC_SECTION(".sdmagic", SD_MAGIC); @@ -61,6 +67,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 +91,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, @@ -113,6 +125,7 @@ typedef struct BootEntry { char16_t *options; bool options_implied; /* If true, these options are implied if we invoke the PE binary without any parameters (as in: UKI). If false we must specify these options explicitly. */ char16_t **initrd; + char16_t **extras; char16_t key; EFI_STATUS (*call)(const struct BootEntry *entry, EFI_FILE *root_dir, EFI_HANDLE parent_image); int tries_done; @@ -319,9 +332,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; @@ -413,13 +430,15 @@ static void print_status(Config *config, char16_t *loaded_image_path) { printf(" url: %ls\n", entry->url); STRV_FOREACH(initrd, entry->initrd) printf(" initrd: %ls\n", *initrd); + STRV_FOREACH(extra, entry->extras) + printf(" extra: %ls\n", *extra); if (entry->devicetree) printf(" devicetree: %ls\n", entry->devicetree); if (entry->options) 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) { @@ -1036,6 +1055,7 @@ static BootEntry* boot_entry_free(BootEntry *entry) { free(entry->devicetree); free(entry->options); strv_free(entry->initrd); + strv_free(entry->extras); free(entry->directory); free(entry->current_name); free(entry->next_name); @@ -1046,12 +1066,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) @@ -1350,7 +1372,7 @@ static void boot_entry_add_type1( _cleanup_(boot_entry_freep) BootEntry *entry = NULL; char *line; - size_t pos = 0, n_initrd = 0; + size_t pos = 0, n_initrd = 0, n_extras = 0; char *key, *value; EFI_STATUS err; @@ -1479,6 +1501,14 @@ static void boot_entry_add_type1( entry->initrd[n_initrd++] = xstr8_to_path(value); entry->initrd[n_initrd] = NULL; + } else if (streq8(key, "extra")) { + entry->extras = xrealloc( + entry->extras, + n_extras == 0 ? 0 : (n_extras + 1) * sizeof(uint16_t *), + (n_extras + 2) * sizeof(uint16_t *)); + entry->extras[n_extras++] = xstr8_to_path(value); + entry->extras[n_extras] = NULL; + } else if (streq8(key, "options")) { _cleanup_free_ char16_t *new = NULL; @@ -1555,6 +1585,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 +1763,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 +1802,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 +1820,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 +1935,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 +2187,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 +2586,9 @@ static EFI_STATUS initrd_prepare( assert(ret_initrd_pages); assert(ret_initrd_size); - if (entry->type != LOADER_LINUX || !entry->initrd) { + assert(entry->type == LOADER_LINUX); + + if (strv_isempty(entry->initrd)) { *ret_options = NULL; *ret_initrd_pages = (Pages) {}; *ret_initrd_size = 0; @@ -2648,6 +2692,173 @@ static EFI_STATUS initrd_prepare( return EFI_SUCCESS; } +static EFI_STATUS load_extras( + EFI_FILE *root, + const BootEntry *entry, + Pages *ret_initrd_pages, + size_t *ret_initrd_size) { + + EFI_STATUS err; + + assert(root); + assert(entry); + assert(ret_initrd_pages); + assert(ret_initrd_size); + + assert(IN_SET(entry->type, LOADER_UKI, LOADER_UKI_URL)); + + _cleanup_(iovec_done) struct iovec previous_initrd = {}, confext_initrd = {}, sysext_initrd = {}, credential_initrd = {}; + + const struct ExtraResourceInfo { + const char16_t *suffix; + const CpioTarget *target; + struct iovec *iovec; + const char16_t *tpm_description; + } table[] = { + { u".cred", &cpio_target_credentials, &credential_initrd, u"Entry credentials initrd" }, + { u".sysext.raw", &cpio_target_sysext, &sysext_initrd, u"Entry system extension initrd" }, + { u".confext.raw", &cpio_target_confext, &confext_initrd, u"Entry configuration extension initrd" }, + }; + + if (strv_isempty(entry->extras)) + goto nothing; + + uint32_t inode = 1; /* inode counter, so that each item gets a new inode */ + unsigned n = 0; + + STRV_FOREACH(i, entry->extras) { + _cleanup_file_close_ EFI_FILE *handle = NULL; + err = root->Open(root, &handle, *i, EFI_FILE_MODE_READ, /* Attributes= */ 0); + if (err != EFI_SUCCESS) { + log_warning_status(err, "Failed to open extra file '%ls', ignoring: %m", *i); + continue; + } + + _cleanup_free_ EFI_FILE_INFO *info = NULL; + err = get_file_info(handle, &info, /* ret_size= */ NULL); + if (err != EFI_SUCCESS) { + log_warning_status(err, "Failed to get information about file '%ls', ignoring: %m", *i); + continue; + } + + if (FLAGS_SET(info->Attribute, EFI_FILE_DIRECTORY)) { + log_warning("Extra file '%ls' is a directory, ignoring.", *i); + continue; + } + + if (info->FileSize == 0) { + log_warning("Extra file '%ls' is empty, ignoring.", *i); + continue; + } + if (info->FileSize > EXTRA_SIZE_MAX) { + log_warning("Extra file '%ls' is larger than allowed extra file size, ignoring.", *i); + continue; + } + + if (!is_ascii(info->FileName)) { + log_warning("Extra file name '%ls' is not valid ASCII, ignoring.", *i); + continue; + } + if (strlen16(info->FileName) > 255) { /* Max filename size on Linux */ + log_warning("Filename '%ls' too long, ignoring.", *i); + continue; + } + + const struct ExtraResourceInfo *x = NULL; + FOREACH_ELEMENT(j, table) + if (endswith_no_case(info->FileName, j->suffix)) { + x = j; + break; + } + if (!x) { + log_warning("Unrecognized type of extra file '%ls', ignoring.", info->FileName); + continue; + } + + _cleanup_free_ char *content = NULL; + size_t contentsize = 0; /* avoid false maybe-uninitialized warning */ + err = file_handle_read(handle, /* offset= */ 0, info->FileSize, &content, &contentsize); + if (err != EFI_SUCCESS) { + log_warning_status(err, "Failed to read '%ls', ignoring: %m", *i); + continue; + } + + /* 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. Note that we potentially do redundant work here: a prior iteration might already + * have created the prefix for us, but to simplify this we regenerate it anyway. It's very + * little data, and simplifies the implementation here a lot. */ + err = pack_cpio_prefix(x->target, &inode, &x->iovec->iov_base, &x->iovec->iov_len); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to pack cpio prefix '%s': %m", x->target->directory); + + err = pack_cpio_one( + info->FileName, + content, contentsize, + x->target, + &inode, + &x->iovec->iov_base, &x->iovec->iov_len); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to pack cpio file '%ls': %m", info->FileName); + + n++; + } + + if (n == 0) /* Nothing actually loaded */ + goto nothing; + + FOREACH_ELEMENT(x, table) { + if (x->iovec->iov_len <= 0) + continue; + + err = pack_cpio_trailer(&x->iovec->iov_base, &x->iovec->iov_len); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to pack cpio trailer: %m"); + + err = tpm_log_ipl_event( + x->target->tpm_pcr, + POINTER_TO_PHYSICAL_ADDRESS(x->iovec->iov_base), + x->iovec->iov_len, + x->tpm_description, + /* ret_measured= */ NULL); + if (err != EFI_SUCCESS) + return log_error_status( + err, + "Unable to add cpio TPM measurement for PCR %u (%ls): %m", + x->target->tpm_pcr, + x->tpm_description); + } + + /* Be nice: pick up any previously registered initrds and prepend them to what we are generating here */ + err = initrd_read_previous(&previous_initrd); + if (err == EFI_NOT_FOUND) + log_debug_status(err, "No previous initrd installed."); + else if (err != EFI_SUCCESS) + log_warning_status(err, "Failed to read previously registered initrd, ignoring."); + else + log_debug("Successfully loaded previously installed initrd (%zu bytes).", previous_initrd.iov_len); + + err = combine_initrds( + (const struct iovec[]) { + previous_initrd, + credential_initrd, + sysext_initrd, + confext_initrd, + }, + /* n_initrds= */ 4, + ret_initrd_pages, + ret_initrd_size); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to combine previous with extra initrds: %m"); + + return EFI_SUCCESS; + +nothing: + *ret_initrd_pages = (Pages) {}; + *ret_initrd_size = 0; + return EFI_SUCCESS; +} + static EFI_STATUS expand_path( EFI_HANDLE parent_image, EFI_DEVICE_PATH *path, @@ -2694,7 +2905,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; } @@ -2791,15 +3007,11 @@ static EFI_STATUS call_image_start( return log_error_status(err, "Error loading EFI binary %ls: %m", entry->loader); } - _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL; _cleanup_free_ char16_t *options_initrd = NULL; - _cleanup_pages_ Pages initrd_pages = {}; + _cleanup_pages_ Pages initrd_pages = {}; /* Note: please keep order intact: these pages should be released after the initrd handle is released */ + _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL; size_t initrd_size = 0; if (image_root) { - err = initrd_prepare(image_root, entry, &options_initrd, &initrd_pages, &initrd_size); - if (err != EFI_SUCCESS) - return log_error_status(err, "Error preparing initrd: %m"); - /* DTBs are loaded by the kernel before ExitBootServices(), and they can be used to map and * assign arbitrary memory ranges, so skip them when secure boot is enabled as the DTB here * is unverified. */ @@ -2809,9 +3021,35 @@ static EFI_STATUS call_image_start( return log_error_status(err, "Error loading %ls: %m", entry->devicetree); } + switch (entry->type) { + + case LOADER_LINUX: + /* For traditional Linux we follow 'initrd' links, because that's how things worked in the good old days */ + err = initrd_prepare(image_root, entry, &options_initrd, &initrd_pages, &initrd_size); + if (err != EFI_SUCCESS) + return log_error_status(err, "Error preparing initrd: %m"); + + break; + + case LOADER_UKI: + case LOADER_UKI_URL: + /* For modern UKIs we'll not bother with 'initrd', but we'll instead support 'extra' + * for loading credentials, sysexts, and confexts. */ + + err = load_extras(image_root, entry, &initrd_pages, &initrd_size); + if (err != EFI_SUCCESS) + return err; /* load_extras() logs on its own */ + break; + + default: + ; + } + err = initrd_register(&IOVEC_MAKE(PHYSICAL_ADDRESS_TO_POINTER(initrd_pages.addr), initrd_size), &initrd_handle); if (err != EFI_SUCCESS) return log_error_status(err, "Error registering initrd: %m"); + + /* NB: the initrd pages remain in our possession, we will free them if executing the image fails below */ } EFI_LOADED_IMAGE_PROTOCOL *loaded_image; @@ -2863,7 +3101,8 @@ static EFI_STATUS call_image_start( uint32_t compat_address; err = pe_kernel_info(loaded_image->ImageBase, /* ret_entry_point= */ NULL, &compat_address, - /* ret_size_in_memory= */ NULL); + /* ret_size_in_memory= */ NULL, + /* ret_section_alignment= */ NULL); if (err != EFI_SUCCESS) { if (err != EFI_UNSUPPORTED) return log_error_status(err, "Error finding kernel compat entry address: %m"); @@ -3029,6 +3268,8 @@ static void export_loader_variables( EFI_LOADER_FEATURE_TYPE1_UKI | EFI_LOADER_FEATURE_TYPE1_UKI_URL | EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS | + EFI_LOADER_FEATURE_KEYBOARD_LAYOUT | + EFI_LOADER_FEATURE_SMBIOS_MEASURED | 0; assert(loaded_image); @@ -3044,6 +3285,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 +3298,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 +3311,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, @@ -3193,6 +3437,10 @@ static EFI_STATUS run(EFI_HANDLE image) { export_common_variables(loaded_image); export_loader_variables(loaded_image, init_usec); + /* Measure SMBIOS data into PCR 1. This is done early, and suppressed if sd-stub later runs in + * the same boot (and vice versa), via the LoaderPcrSMBIOS EFI variable. */ + measure_smbios(); + (void) load_drivers(image, loaded_image, root_dir); _cleanup_free_ char16_t *loaded_image_path = NULL; @@ -3255,4 +3503,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..23bd60537fa2d 100644 --- a/src/boot/chid.c +++ b/src/boot/chid.c @@ -15,7 +15,6 @@ */ #include "chid.h" -#include "chid-fundamental.h" #include "edid.h" #if SD_BOOT #include "efi-log.h" @@ -99,6 +98,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; @@ -120,12 +121,35 @@ EFI_STATUS chid_match(const void *hwid_buffer, size_t hwid_length, uint32_t matc /* Count devices and check validity */ for (; (n_devices + 1) * sizeof(*devices) < hwid_length;) { + const Device *device = &devices[n_devices]; - if (devices[n_devices].descriptor == DEVICE_DESCRIPTOR_EOL) + if (device->descriptor == DEVICE_DESCRIPTOR_EOL) break; - if (!IN_SET(DEVICE_TYPE_FROM_DESCRIPTOR(devices[n_devices].descriptor), - DEVICE_TYPE_UEFI_FW, DEVICE_TYPE_DEVICETREE)) + + uint32_t device_type = DEVICE_TYPE_FROM_DESCRIPTOR(device->descriptor); + size_t name_off, off; + + if (!IN_SET(device_type, DEVICE_TYPE_UEFI_FW, DEVICE_TYPE_DEVICETREE)) return EFI_UNSUPPORTED; + + if (device_type == DEVICE_TYPE_DEVICETREE) { + off = device->devicetree.compatible_offset; + name_off = device->devicetree.name_offset; + } else if (device_type == DEVICE_TYPE_UEFI_FW) { + off = device->uefi_fw.fwid_offset; + name_off = device->uefi_fw.name_offset; + } else + assert_not_reached(); + + if (off >= hwid_length || name_off >= hwid_length) + return EFI_INVALID_PARAMETER; + + /* check for NULL termination */ + if (off && !memchr((const uint8_t *) hwid_buffer + off, 0, hwid_length - off)) + return EFI_INVALID_PARAMETER; + if (name_off && !memchr((const uint8_t *) hwid_buffer + name_off, 0, hwid_length - name_off)) + return EFI_INVALID_PARAMETER; + n_devices++; } diff --git a/src/boot/chid.h b/src/boot/chid.h index f8299fde88888..8bf4b555a40a9 100644 --- a/src/boot/chid.h +++ b/src/boot/chid.h @@ -1,9 +1,10 @@ /* SPDX-License-Identifier: BSD-3-Clause */ #pragma once -#include "chid-fundamental.h" /* IWYU pragma: export */ #include "efi.h" +#include "../fundamental/chid.h" /* IWYU pragma: export */ + /* A .hwids PE section consists of a series of 'Device' structures. A 'Device' structure binds a CHID to some * resource, for now only Devicetree blobs. Designed to be extensible to other types of resources, should the * need arise. The series of 'Device' structures is followed by some space for strings that can be referenced diff --git a/src/boot/console.c b/src/boot/console.c index 21b36e5a6e5f9..1115eeaae0a2c 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.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..6c1b3caedd178 100644 --- a/src/boot/cpio.c +++ b/src/boot/cpio.c @@ -2,13 +2,14 @@ #include "cpio.h" #include "efi-log.h" -#include "iovec-util-fundamental.h" +#include "iovec-util.h" #include "measure.h" -#include "string-util-fundamental.h" +#include "string-util.h" +#include "tpm2-pcr.h" #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 +53,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 +67,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 +84,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 +121,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 +141,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 +225,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 +242,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 +252,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 +264,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,10 +306,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, - uint32_t tpm_pcr, + const CpioTarget *target, const char16_t *tpm_description, struct iovec *ret_buffer, bool *ret_measured) { @@ -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"); @@ -410,15 +406,14 @@ EFI_STATUS pack_cpio( err = file_read(extra_dir, items[i], 0, 0, &content, &contentsize); if (err != EFI_SUCCESS) { - log_error_status(err, "Failed to read %ls, ignoring: %m", items[i]); + log_warning_status(err, "Failed to read %ls, ignoring: %m", items[i]); continue; } err = pack_cpio_one( items[i], content, contentsize, - target_dir_prefix, - access_mode, + target, &inode, &buffer, &buffer_size); if (err != EFI_SUCCESS) @@ -430,12 +425,16 @@ EFI_STATUS pack_cpio( return log_error_status(err, "Failed to pack cpio trailer: %m"); err = tpm_log_ipl_event( - tpm_pcr, POINTER_TO_PHYSICAL_ADDRESS(buffer), buffer_size, tpm_description, ret_measured); + target->tpm_pcr, + POINTER_TO_PHYSICAL_ADDRESS(buffer), + buffer_size, + tpm_description, + ret_measured); if (err != EFI_SUCCESS) return log_error_status( err, - "Unable to add cpio TPM measurement for PCR %u (%ls), ignoring: %m", - tpm_pcr, + "Unable to add cpio TPM measurement for PCR %u (%ls): %m", + target->tpm_pcr, tpm_description); *ret_buffer = IOVEC_MAKE(TAKE_PTR(buffer), buffer_size); @@ -453,11 +452,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, bool *ret_measured) { @@ -468,22 +464,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) @@ -494,14 +489,78 @@ EFI_STATUS pack_cpio_literal( return log_error_status(err, "Failed to pack cpio trailer: %m"); err = tpm_log_ipl_event( - tpm_pcr, POINTER_TO_PHYSICAL_ADDRESS(buffer), buffer_size, tpm_description, ret_measured); + target->tpm_pcr, + POINTER_TO_PHYSICAL_ADDRESS(buffer), + buffer_size, + tpm_description, + ret_measured); if (err != EFI_SUCCESS) return log_error_status( err, - "Unable to add cpio TPM measurement for PCR %u (%ls), ignoring: %m", - tpm_pcr, + "Unable to add cpio TPM measurement for PCR %u (%ls): %m", + target->tpm_pcr, tpm_description); *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, + .tpm_pcr = TPM2_PCR_KERNEL_CONFIG, +}; + +const CpioTarget cpio_target_global_credentials = { + .directory = ".extra/global_credentials", + .dir_mode = 0500, + .access_mode = 0400, + .tpm_pcr = TPM2_PCR_KERNEL_CONFIG, +}; + +const CpioTarget cpio_target_sysext = { + .directory = ".extra/sysext", + .dir_mode = 0555, + .access_mode = 0444, + .tpm_pcr = TPM2_PCR_SYSEXTS, +}; + +const CpioTarget cpio_target_global_sysext = { + .directory = ".extra/global_sysext", + .dir_mode = 0555, + .access_mode = 0444, + .tpm_pcr = TPM2_PCR_SYSEXTS, +}; + +const CpioTarget cpio_target_confext = { + .directory = ".extra/confext", + .dir_mode = 0555, + .access_mode = 0444, + .tpm_pcr = TPM2_PCR_KERNEL_CONFIG, +}; + +const CpioTarget cpio_target_global_confext = { + .directory = ".extra/global_confext", + .dir_mode = 0555, + .access_mode = 0444, + .tpm_pcr = TPM2_PCR_KERNEL_CONFIG, +}; + +const CpioTarget cpio_target_meta = { + .directory = ".extra", + .dir_mode = 0555, + .access_mode = 0444, + .tpm_pcr = UINT32_MAX, +}; + +const CpioTarget cpio_target_meta_secret = { + .directory = ".extra", + .dir_mode = 0555, + .access_mode = 0400, + .tpm_pcr = UINT32_MAX, +}; diff --git a/src/boot/cpio.h b/src/boot/cpio.h index bb741278fdc24..3aa525779344f 100644 --- a/src/boot/cpio.h +++ b/src/boot/cpio.h @@ -4,15 +4,38 @@ #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 */ + uint32_t tpm_pcr; /* Where to measure this data into */ +} 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, - uint32_t tpm_pcr, + const CpioTarget *target, const char16_t *tpm_description, struct iovec *ret_buffer, bool *ret_measured); @@ -20,11 +43,17 @@ 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/device-path-util.c b/src/boot/device-path-util.c index a40cb5e8a11e9..5243e81e5df18 100644 --- a/src/boot/device-path-util.c +++ b/src/boot/device-path-util.c @@ -2,7 +2,7 @@ #include "device-path-util.h" #include "efi-string.h" -#include "string-util-fundamental.h" +#include "string-util.h" #include "util.h" static const EFI_DEVICE_PATH *device_path_find_end_node(const EFI_DEVICE_PATH *dp) { 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/drivers.c b/src/boot/drivers.c index d58b1e39da74c..215404d4b0398 100644 --- a/src/boot/drivers.c +++ b/src/boot/drivers.c @@ -3,7 +3,7 @@ #include "device-path-util.h" #include "drivers.h" #include "efi-log.h" -#include "string-util-fundamental.h" +#include "string-util.h" #include "util.h" static EFI_STATUS load_one_driver( diff --git a/src/boot/edid.h b/src/boot/edid.h index 74649658d93ff..553684d8d1c77 100644 --- a/src/boot/edid.h +++ b/src/boot/edid.h @@ -1,7 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "edid-fundamental.h" /* IWYU pragma: export */ #include "efi.h" +#include "../fundamental/edid.h" /* IWYU pragma: export */ + EFI_STATUS edid_get_discovered_panel_id(char16_t **ret_panel); diff --git a/src/boot/efi-efivars.c b/src/boot/efi-efivars.c index 0358a071e07d7..c1ae4252c3c34 100644 --- a/src/boot/efi-efivars.c +++ b/src/boot/efi-efivars.c @@ -106,7 +106,7 @@ EFI_STATUS efivar_get_str16(const EFI_GUID *vendor, const char16_t *name, char16 val = xmalloc(size + sizeof(char16_t)); memcpy(val, buf, size); - val[size / sizeof(char16_t) - 1] = 0; /* NUL terminate */ + val[size / sizeof(char16_t)] = 0; /* NUL terminate */ *ret = val; return EFI_SUCCESS; @@ -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..c9e2f0d9295c7 100644 --- a/src/boot/efi-efivars.h +++ b/src/boot/efi-efivars.h @@ -2,7 +2,7 @@ #pragma once #include "efi.h" -#include "efivars-fundamental.h" /* IWYU pragma: export */ +#include "efivars.h" /* IWYU pragma: export */ /* * Allocated random UUID, intended to be shared across tools that implement @@ -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..520f985389c55 100644 --- a/src/boot/efi-log.c +++ b/src/boot/efi-log.c @@ -132,7 +132,8 @@ void log_wait(void) { log_count = 0; } -_used_ intptr_t __stack_chk_guard = (intptr_t) 0x70f6967de78acae3; +// NOLINTNEXTLINE(misc-use-internal-linkage) +_used_ uintptr_t __stack_chk_guard = (uintptr_t) 0x70f6967de78acae3; /* We can only set a random stack canary if this function attribute is available, * otherwise this may create a stack check fail. */ @@ -143,12 +144,14 @@ void __stack_chk_guard_init(void) { (void) rng->GetRNG(rng, NULL, sizeof(__stack_chk_guard), (void *) &__stack_chk_guard); else /* Better than no extra entropy. */ - __stack_chk_guard ^= (intptr_t) __executable_start; + __stack_chk_guard ^= (uintptr_t) __executable_start; } #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-table.h b/src/boot/efi-string-table.h index 9e142aa6ccc13..7b755578b215d 100644 --- a/src/boot/efi-string-table.h +++ b/src/boot/efi-string-table.h @@ -2,7 +2,7 @@ #pragma once #include "efi-string.h" -#include "macro-fundamental.h" +#include "macro.h" #define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ scope const char* name##_to_string(type i) { \ diff --git a/src/boot/efi-string.c b/src/boot/efi-string.c index ee430c1b36a40..306446386271a 100644 --- a/src/boot/efi-string.c +++ b/src/boot/efi-string.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "efi-string.h" -#include "string-util-fundamental.h" +#include "string-util.h" #if SD_BOOT # include "proto/simple-text-io.h" @@ -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/efi.h b/src/boot/efi.h index eddaad1eeeaaa..6f08bacb2db62 100644 --- a/src/boot/efi.h +++ b/src/boot/efi.h @@ -9,11 +9,12 @@ #include /* IWYU pragma: export */ #include /* IWYU pragma: export */ -#include "assert-fundamental.h" /* IWYU pragma: export */ -#include "cleanup-fundamental.h" /* IWYU pragma: export */ -#include "efi-fundamental.h" /* IWYU pragma: export */ -#include "macro-fundamental.h" /* IWYU pragma: export */ -#include "string-table-fundamental.h" /* IWYU pragma: export */ +#include "assert-util.h" /* IWYU pragma: export */ +#include "cleanup-util.h" /* IWYU pragma: export */ +#include "macro.h" /* IWYU pragma: export */ +#include "string-table.h" /* IWYU pragma: export */ + +#include "../fundamental/efi.h" /* IWYU pragma: export */ #if SD_BOOT /* uchar.h/wchar.h are not suitable for freestanding environments. */ @@ -131,6 +132,7 @@ typedef uint64_t EFI_PHYSICAL_ADDRESS; * keep the GUID definitions in line with the UEFI spec. */ #define EFI_GLOBAL_VARIABLE_GUID EFI_GLOBAL_VARIABLE #define EFI_FILE_INFO_GUID EFI_FILE_INFO_ID +#define EFI_FILE_SYSTEM_INFO_GUID EFI_FILE_SYSTEM_INFO_ID #define EFI_CUSTOM_MODE_ENABLE_GUID \ GUID_DEF(0xc076ec0c, 0x7028, 0x4399, 0xa0, 0x72, 0x71, 0xee, 0x5c, 0x44, 0x8b, 0x9f) diff --git a/src/boot/export-vars.c b/src/boot/export-vars.c index 5c037bdd25235..aa49666f57ce4 100644 --- a/src/boot/export-vars.c +++ b/src/boot/export-vars.c @@ -3,6 +3,7 @@ #include "device-path-util.h" #include "efi-efivars.h" #include "export-vars.h" +#include "hii.h" #include "measure.h" #include "part-discovery.h" #include "url-discovery.h" @@ -60,4 +61,13 @@ void export_common_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { s = xasprintf("0x%08x", active_pcr_banks); efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderTpm2ActivePcrBanks", s, 0); } + + /* Report the firmware's currently-active HII keyboard layout (as an RFC 4646 language tag, e.g. + * "de-DE"), so the OS can pick a matching console keymap. Best-effort: many firmwares do not + * implement the HII database protocol or expose no keyboard layout. */ + if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderKeyboardLayout", /* ret_data= */ NULL, /* ret_size= */ NULL) != EFI_SUCCESS) { + _cleanup_free_ char16_t *lang = hii_query_keyboard_layout_language(); + if (lang) + efivar_set_str16(MAKE_GUID_PTR(LOADER), u"LoaderKeyboardLayout", lang, /* flags= */ 0); + } } diff --git a/src/boot/generate-hwids-section.py b/src/boot/generate-hwids-section.py index 621183c20fba0..df929af3b764d 100755 --- a/src/boot/generate-hwids-section.py +++ b/src/boot/generate-hwids-section.py @@ -16,12 +16,13 @@ hwids = ukify.parse_hwid_dir(Path(sys.argv[1])) print( - """/* SPDX-License-Identifier: LGPL-2.1-or-later */ + '''/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include +// NOLINTNEXTLINE(misc-use-internal-linkage) const uint8_t hwids_section_data[] = { - """, + ''', end='', ) @@ -33,7 +34,9 @@ print('') print( - """}; -const size_t hwids_section_len =""", + '''}; + +// NOLINTNEXTLINE(misc-use-internal-linkage) +const size_t hwids_section_len =''', f'{len(hwids)};', ) diff --git a/src/boot/hii.c b/src/boot/hii.c new file mode 100644 index 0000000000000..84bf299c4247b --- /dev/null +++ b/src/boot/hii.c @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "efi-log.h" +#include "hii.h" +#include "proto/hii-database.h" +#include "util.h" + +char16_t *hii_query_keyboard_layout_language(void) { + EFI_HII_DATABASE_PROTOCOL *hii_db = NULL; + EFI_STATUS err; + + err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_HII_DATABASE_PROTOCOL), /* Registration= */ NULL, (void **) &hii_db); + if (err != EFI_SUCCESS) { + log_debug_status(err, "HII database protocol not available, ignoring: %m"); + return NULL; + } + + /* First call sizes the layout. We pass length=0 / buffer=NULL and expect EFI_BUFFER_TOO_SMALL. */ + uint16_t length = 0; + err = hii_db->GetKeyboardLayout(hii_db, /* KeyGuid= */ NULL, &length, /* KeyboardLayout= */ NULL); + if (err != EFI_BUFFER_TOO_SMALL) { + log_debug_status(err, "Initial GetKeyboardLayout did not report required buffer size, ignoring: %m"); + return NULL; + } + if (length < sizeof(EFI_HII_KEYBOARD_LAYOUT)) { + log_debug("Reported keyboard layout size %u is smaller than the header, ignoring.", length); + return NULL; + } + + _cleanup_free_ EFI_HII_KEYBOARD_LAYOUT *layout = xmalloc(length); + err = hii_db->GetKeyboardLayout(hii_db, /* KeyGuid= */ NULL, &length, layout); + if (err != EFI_SUCCESS) { + log_debug_status(err, "Failed to retrieve current keyboard layout, ignoring: %m"); + return NULL; + } + + if (length < sizeof(EFI_HII_KEYBOARD_LAYOUT)) { + log_debug("Reported keyboard layout size %u shrank below the header, ignoring.", length); + return NULL; + } + + if (layout->LayoutLength != length) { + log_debug("Keyboard layout reports inconsistent LayoutLength %u vs %u, ignoring.", + layout->LayoutLength, length); + return NULL; + } + + uint32_t off = layout->LayoutDescriptorStringOffset; + if (off > length || length - off < sizeof(EFI_DESCRIPTION_STRING_BUNDLE)) { + log_debug("Keyboard layout descriptor string offset %u out of bounds (length %u), ignoring.", + off, length); + return NULL; + } + + const EFI_DESCRIPTION_STRING_BUNDLE *bundle = (const EFI_DESCRIPTION_STRING_BUNDLE *) ((const uint8_t *) layout + off); + if (bundle->DescriptionCount == 0) { + log_debug("Keyboard layout has no description strings, ignoring."); + return NULL; + } + + /* Walk Strings[] looking for the U+0020 that terminates the first language tag. */ + size_t max_chars = (length - off - sizeof(EFI_DESCRIPTION_STRING_BUNDLE)) / sizeof(char16_t); + size_t n; + for (n = 0; n < max_chars; n++) + if (bundle->Strings[n] == u' ') + break; + if (n == max_chars) { + log_debug("Keyboard layout language tag is not terminated by a space, ignoring."); + return NULL; + } + if (n == 0) { + log_debug("Keyboard layout language tag is empty, ignoring."); + return NULL; + } + + char16_t *s = xnew(char16_t, n + 1); + memcpy(s, bundle->Strings, n * sizeof(char16_t)); + s[n] = u'\0'; + return s; +} diff --git a/src/boot/hii.h b/src/boot/hii.h new file mode 100644 index 0000000000000..23ebcd303208f --- /dev/null +++ b/src/boot/hii.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +/* Queries the firmware's HII database for the currently-active keyboard layout and returns the RFC 4646 + * language tag (e.g. u"de-DE") embedded in the layout description bundle. Returns NULL if the protocol + * is not provided, the table is malformed, or no language tag is present. */ +char16_t *hii_query_keyboard_layout_language(void); diff --git a/src/boot/initrd.c b/src/boot/initrd.c index d8cbe7deed425..eced9273fe0fa 100644 --- a/src/boot/initrd.c +++ b/src/boot/initrd.c @@ -1,7 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "efi-log.h" #include "initrd.h" -#include "iovec-util-fundamental.h" +#include "iovec-util.h" #include "proto/device-path.h" #include "proto/load-file.h" #include "util.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..fb56d6919d97b 100644 --- a/src/boot/initrd.h +++ b/src/boot/initrd.h @@ -2,6 +2,8 @@ #pragma once #include "efi.h" +#include "iovec-util.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/line-edit.c b/src/boot/line-edit.c index 1e6128e1effaa..e37398d971956 100644 --- a/src/boot/line-edit.c +++ b/src/boot/line-edit.c @@ -2,7 +2,7 @@ #include "console.h" #include "line-edit.h" -#include "string-util-fundamental.h" +#include "string-util.h" #include "util.h" static void cursor_left(size_t *cursor, size_t *first) { diff --git a/src/boot/linux.c b/src/boot/linux.c index b1f38e597d653..bd71ada48358c 100644 --- a/src/boot/linux.c +++ b/src/boot/linux.c @@ -160,14 +160,14 @@ EFI_STATUS linux_exec( const struct iovec *initrd) { size_t kernel_size_in_memory = 0; - uint32_t compat_entry_point, entry_point; + uint32_t compat_entry_point, entry_point, section_alignment; EFI_STATUS err; assert(parent_image); assert(iovec_is_set(kernel)); assert(iovec_is_valid(initrd)); - err = pe_kernel_info(kernel->iov_base, &entry_point, &compat_entry_point, &kernel_size_in_memory); + err = pe_kernel_info(kernel->iov_base, &entry_point, &compat_entry_point, &kernel_size_in_memory, §ion_alignment); #if defined(__i386__) || defined(__x86_64__) if (err == EFI_UNSUPPORTED) /* Kernel is too old to support LINUX_INITRD_MEDIA_GUID, try the deprecated EFI handover @@ -264,9 +264,17 @@ EFI_STATUS linux_exec( if (err != EFI_SUCCESS) return log_error_status(err, "Cannot read sections: %m"); - /* Do we need to ensure under 4gb address on x86? */ - _cleanup_pages_ Pages loaded_kernel_pages = xmalloc_pages( - AllocateAnyPages, EfiLoaderCode, EFI_SIZE_TO_PAGES(kernel_size_in_memory), 0); + /* Honor the PE SectionAlignment (SZ_64K on arm64): if _text is not aligned the kernel's EFI stub + * reallocates and copies the image, which can fail with EFI_OUT_OF_RESOURCES on memory-constrained + * firmware. When alignment <= EFI_PAGE_SIZE (e.g. x86_64) xmalloc_aligned_pages() reduces to a + * plain AllocatePages() with no extra over-allocation. pe_kernel_info() already sanitized a + * non-conforming SectionAlignment to plain page alignment. */ + _cleanup_pages_ Pages loaded_kernel_pages = xmalloc_aligned_pages( + AllocateAnyPages, + EfiLoaderCode, + EFI_SIZE_TO_PAGES(kernel_size_in_memory), + section_alignment, + /* addr= */ 0); uint8_t* loaded_kernel = PHYSICAL_ADDRESS_TO_POINTER(loaded_kernel_pages.addr); FOREACH_ARRAY(h, headers, n_headers) { diff --git a/src/boot/linux.h b/src/boot/linux.h index 681d12c0c1cfa..f7eb834bca7f9 100644 --- a/src/boot/linux.h +++ b/src/boot/linux.h @@ -2,7 +2,7 @@ #pragma once #include "efi.h" -#include "iovec-util-fundamental.h" +#include "iovec-util.h" EFI_STATUS linux_exec( EFI_HANDLE parent, diff --git a/src/boot/linux_x86.c b/src/boot/linux_x86.c index cf9707a6cfd7a..2f3275b0654e6 100644 --- a/src/boot/linux_x86.c +++ b/src/boot/linux_x86.c @@ -12,8 +12,8 @@ #include "efi-log.h" #include "linux.h" -#include "macro-fundamental.h" -#include "memory-util-fundamental.h" +#include "macro.h" +#include "memory-util.h" #include "util.h" #define KERNEL_SECTOR_SIZE 512u @@ -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-smbios.c b/src/boot/measure-smbios.c new file mode 100644 index 0000000000000..2379f4de9e0b8 --- /dev/null +++ b/src/boot/measure-smbios.c @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "efi-efivars.h" +#include "efi-log.h" +#include "measure.h" +#include "measure-smbios.h" +#include "smbios.h" +#include "tpm2-pcr.h" +#include "util.h" + +static void measure_smbios_raw( + const void *p, + size_t size, + uint32_t event_id, + const char16_t *description, + bool *measured) { + + EFI_STATUS err; + bool m = false; + + assert(p); + assert(description); + assert(measured); + + err = tpm_log_tagged_event( + TPM2_PCR_PLATFORM_CONFIG, + POINTER_TO_PHYSICAL_ADDRESS(p), + size, + event_id, + description, + &m); + if (err != EFI_SUCCESS) + log_error_status(err, "Unable to measure SMBIOS structure (%ls), ignoring: %m", description); + + *measured = *measured || m; +} + +static void measure_smbios_type1(const SmbiosHeader *header, size_t size, bool *measured) { + assert(header); + assert(measured); + + /* The wake-up type field varies depending on how the machine was powered on (cold boot, resume + * from sleep, AC restore, …), which would make the measurement non-reproducible. Hence measure a + * copy with that field zeroed out. */ + + assert(size >= sizeof(SmbiosTableType1)); + + _cleanup_free_ SmbiosTableType1 *copy = xmemdup(header, size); + copy->wake_up_type = 0; + + measure_smbios_raw(copy, size, SMBIOS_TYPE1_EVENT_TAG_ID, u"smbios:type1", measured); +} + +static bool measure_smbios_object(const SmbiosHeader *header, size_t size, void *userdata) { + bool *measured = ASSERT_PTR(userdata); + + switch (header->type) { + + case 1: /* System Information */ + measure_smbios_type1(header, size, measured); + break; + + case 2: /* Baseboard Information */ + measure_smbios_raw(header, size, SMBIOS_TYPE2_EVENT_TAG_ID, u"smbios:type2", measured); + break; + + case 11: /* OEM Strings */ + measure_smbios_raw(header, size, SMBIOS_TYPE11_EVENT_TAG_ID, u"smbios:type11", measured); + break; + + default: + break; + } + + return true; /* Keep iterating: there may be more than one matching structure (e.g. type 11). */ +} + +void measure_smbios(void) { + bool measured = false; + + if (!tpm_present()) + return; + + /* If the measurement was already done this boot (e.g. by sd-boot before it chainloaded us), don't + * do it again — re-extending PCR 1 would invalidate the value. */ + if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderPcrSMBIOS", /* ret_data= */ NULL, /* ret_size= */ NULL) == EFI_SUCCESS) + return; + + /* Measure SMBIOS type 1 (system information), type 2 (baseboard information) and type 11 (OEM + * strings) into PCR 1, in a single pass over the SMBIOS table. */ + smbios_foreach(measure_smbios_object, &measured); + + /* If we measured something, tell the OS which PCR we used (and suppress a second pass). */ + if (measured) + (void) efivar_set_uint64_str16(MAKE_GUID_PTR(LOADER), u"LoaderPcrSMBIOS", TPM2_PCR_PLATFORM_CONFIG, /* flags= */ 0); +} diff --git a/src/boot/measure-smbios.h b/src/boot/measure-smbios.h new file mode 100644 index 0000000000000..758b2c45b43bb --- /dev/null +++ b/src/boot/measure-smbios.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* Measures SMBIOS type 1 (system information, with the volatile "Wake-up Type" field masked) and all + * type 11 (OEM strings) structures into PCR 1, and records the PCR index in the transient + * LoaderPcrSMBIOS EFI variable. Called by both sd-boot and sd-stub; the presence of LoaderPcrSMBIOS + * suppresses a redundant second measurement when both run during the same boot. */ +void measure_smbios(void); 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..6da1ae6e2f7e6 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -12,8 +12,8 @@ libefitest = static_library( ), build_by_default : false, include_directories : [ - basic_includes, include_directories('.'), + basic_includes, ], implicit_include_directories : false, dependencies : userspace) @@ -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')) @@ -158,8 +164,8 @@ configure_file( ############################################################ efi_includes = [ - fundamental_include, include_directories('.'), + fundamental_include, version_include, ] @@ -264,6 +270,9 @@ efi_arch_c_args = { 'arm' : ['-mgeneral-regs-only'], # Until -mgeneral-regs-only is supported in LoongArch, use the following option instead: 'loongarch64' : ['-mno-lsx', '-mno-lasx'], + # Assume F/D is usable on RISC-V because they're difficult to disable + 'riscv32' : ['-march=rv32imafdc_zicsr_zifencei'], + 'riscv64' : ['-march=rv64imafdc_zicsr_zifencei'], # Pass -m64/32 explicitly to make building on x32 work. 'x86_64' : ['-m64', '-march=x86-64', '-mno-red-zone', '-mgeneral-regs-only'], 'x86' : ['-m32', '-march=i686', '-mgeneral-regs-only', '-malign-double'], @@ -303,6 +312,7 @@ endif libefi_sources = files( 'chid.c', 'console.c', + 'cpio.c', 'device-path-util.c', 'devicetree.c', 'drivers.c', @@ -313,8 +323,10 @@ libefi_sources = files( 'efi-string.c', 'export-vars.c', 'graphics.c', + 'hii.c', 'initrd.c', 'measure.c', + 'measure-smbios.c', 'part-discovery.c', 'pe.c', 'random-seed.c', @@ -334,7 +346,7 @@ systemd_boot_sources = files( ) stub_sources = files( - 'cpio.c', + 'boot-secret.c', 'linux.c', 'splash.c', 'stub.c', diff --git a/src/boot/part-discovery.c b/src/boot/part-discovery.c index dc1aed0514b1d..ab553a6530bbc 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.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..872cb9e6220e4 100644 --- a/src/boot/pe.c +++ b/src/boot/pe.c @@ -345,13 +345,14 @@ static void pe_locate_sections_internal( } } -static bool looking_for_dtbauto(const char *const section_names[]) { +static bool looking_for_dtbauto_or_efifw(const char *const section_names[]) { assert(section_names); for (size_t i = 0; section_names[i]; i++) - if (pe_section_name_equal(section_names[i], ".dtbauto")) + if (pe_section_name_equal(section_names[i], ".dtbauto") || + pe_section_name_equal(section_names[i], ".efifw")) return true; - return false; + return false; } static void pe_locate_sections( @@ -361,7 +362,7 @@ static void pe_locate_sections( size_t validate_base, PeSectionVector sections[]) { - if (!looking_for_dtbauto(section_names)) + if (!looking_for_dtbauto_or_efifw(section_names)) return pe_locate_sections_internal( section_table, n_section_table, @@ -377,39 +378,64 @@ static void pe_locate_sections( const void *hwids = NULL; const Device *device = NULL; - if (!firmware_devicetree_exists()) { - /* Find HWIDs table and search for the current device */ - static const char *const hwid_section_names[] = { ".hwids", NULL }; - PeSectionVector hwids_section[1] = {}; - - pe_locate_sections_internal( - section_table, - n_section_table, - hwid_section_names, - validate_base, - /* device_table= */ NULL, - /* device= */ NULL, - hwids_section); - - if (PE_SECTION_VECTOR_IS_SET(hwids_section)) { - hwids = (const uint8_t *) SIZE_TO_PTR(validate_base) + hwids_section[0].memory_offset; - - 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"); - hwids = NULL; - } + /* Find HWIDs table and search for the current device */ + static const char *const hwid_section_names[] = { ".hwids", NULL }; + PeSectionVector hwids_section[1] = {}; + + pe_locate_sections_internal( + section_table, + n_section_table, + hwid_section_names, + validate_base, + /* device_table= */ NULL, + /* device= */ NULL, + hwids_section); + + if (PE_SECTION_VECTOR_IS_SET(hwids_section)) { + hwids = (const uint8_t *) SIZE_TO_PTR(validate_base) + hwids_section[0].memory_offset; + EFI_STATUS err; + + if (!firmware_devicetree_exists()) { + err = chid_match(hwids, hwids_section[0].memory_size, DEVICE_TYPE_DEVICETREE, &device); + if (err == EFI_SUCCESS) + pe_locate_sections_internal( + section_table, + n_section_table, + section_names, + validate_base, + hwids, + device, + sections); + else + log_full(err, err == EFI_NOT_FOUND ? LOG_DEBUG : LOG_ERR, + "HWID matching failed, no DT blob will be selected: %m"); } - } + err = chid_match(hwids, hwids_section[0].memory_size, DEVICE_TYPE_UEFI_FW, &device); + if (err == EFI_SUCCESS) + pe_locate_sections_internal( + section_table, + n_section_table, + section_names, + validate_base, + hwids, + device, + sections); + else { + log_full(err, err == EFI_NOT_FOUND ? LOG_DEBUG : LOG_ERR, + "No UEFI FW will be selected: %m"); + hwids = NULL; + device = NULL; + } + } return pe_locate_sections_internal( - section_table, - n_section_table, - section_names, - validate_base, - hwids, - device, - sections); + section_table, + n_section_table, + section_names, + validate_base, + hwids, + device, + sections); } static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const PeFileHeader *pe) { @@ -459,7 +485,12 @@ static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const return 0; } -EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_entry_point, uint32_t *ret_compat_entry_point, size_t *ret_size_in_memory) { +EFI_STATUS pe_kernel_info( + const void *base, + uint32_t *ret_entry_point, + uint32_t *ret_compat_entry_point, + size_t *ret_size_in_memory, + uint32_t *ret_section_alignment) { assert(base); const DosFileHeader *dos = (const DosFileHeader *) base; @@ -474,6 +505,16 @@ EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_entry_point, uint32_t * of the SizeOfImage field in the PE header and return it */ size_t size_in_memory = pe->OptionalHeader.SizeOfImage; + /* Honoring SectionAlignment lets callers place the image so the kernel's EFI stub need not relocate + * it (SZ_64K on arm64). The PE spec requires a power of 2; for a non-conforming value fall back to + * plain page alignment (what we assumed before honoring this field) rather than propagate something + * that would break the allocator's over-alignment math. */ + uint32_t section_alignment = pe->OptionalHeader.SectionAlignment; + if (!ISPOWEROF2(section_alignment)) { + log_warning("PE SectionAlignment %" PRIu32 " is not a power of 2, falling back to page alignment.", section_alignment); + section_alignment = EFI_PAGE_SIZE; + } + /* Support for LINUX_INITRD_MEDIA_GUID was added in kernel stub 1.0. */ if (pe->OptionalHeader.MajorImageVersion < 1) return EFI_UNSUPPORTED; @@ -485,6 +526,8 @@ EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_entry_point, uint32_t *ret_compat_entry_point = 0; if (ret_size_in_memory) *ret_size_in_memory = size_in_memory; + if (ret_section_alignment) + *ret_section_alignment = section_alignment; return EFI_SUCCESS; } @@ -499,6 +542,8 @@ EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_entry_point, uint32_t *ret_compat_entry_point = compat_entry_point; if (ret_size_in_memory) *ret_size_in_memory = size_in_memory; + if (ret_section_alignment) + *ret_section_alignment = section_alignment; return EFI_SUCCESS; } @@ -569,8 +614,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/pe.h b/src/boot/pe.h index 7387f3e2fdff8..5c8dc86fe9389 100644 --- a/src/boot/pe.h +++ b/src/boot/pe.h @@ -57,7 +57,12 @@ EFI_STATUS pe_memory_locate_sections( const char *const section_names[], PeSectionVector sections[]); -EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_entry_point, uint32_t *ret_compat_entry_point, size_t *ret_size_in_memory); +EFI_STATUS pe_kernel_info( + const void *base, + uint32_t *ret_entry_point, + uint32_t *ret_compat_entry_point, + size_t *ret_size_in_memory, + uint32_t *ret_section_alignment); EFI_STATUS pe_kernel_check_no_relocation(const void *base); 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/file-io.h b/src/boot/proto/file-io.h index 001ad48675296..f8dddd94df34c 100644 --- a/src/boot/proto/file-io.h +++ b/src/boot/proto/file-io.h @@ -7,6 +7,8 @@ GUID_DEF(0x0964e5b22, 0x6459, 0x11d2, 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b) #define EFI_FILE_INFO_ID \ GUID_DEF(0x009576e92, 0x6d3f, 0x11d2, 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b) +#define EFI_FILE_SYSTEM_INFO_ID \ + GUID_DEF(0x009576e93, 0x6d3f, 0x11d2, 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b) #define EFI_FILE_MODE_READ 0x0000000000000001U #define EFI_FILE_MODE_WRITE 0x0000000000000002U @@ -31,6 +33,15 @@ typedef struct { char16_t FileName[]; } EFI_FILE_INFO; +typedef struct { + uint64_t Size; + bool ReadOnly; + uint64_t VolumeSize; + uint64_t FreeSpace; + uint32_t BlockSize; + char16_t VolumeLabel[]; +} EFI_FILE_SYSTEM_INFO; + /* Some broken firmware violates the EFI spec by still advancing the readdir * position when returning EFI_BUFFER_TOO_SMALL, effectively skipping over any files when * the buffer was too small. Therefore, we always start with a buffer that should handle FAT32 diff --git a/src/boot/proto/hii-database.h b/src/boot/proto/hii-database.h new file mode 100644 index 0000000000000..7284704f00e23 --- /dev/null +++ b/src/boot/proto/hii-database.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +#define EFI_HII_DATABASE_PROTOCOL_GUID \ + GUID_DEF(0xef9fc172, 0xa1b2, 0x4693, 0xb3, 0x27, 0x6d, 0x32, 0xfc, 0x41, 0x60, 0x42) + +typedef void *EFI_HII_HANDLE; + +typedef struct { + EFI_GUID PackageListGuid; + uint32_t PackageLength; +} EFI_HII_PACKAGE_LIST_HEADER; + +typedef struct _packed_ { + uint32_t LengthAndType; /* Length:24 | Type:8 (little-endian) */ +} EFI_HII_PACKAGE_HEADER; + +typedef size_t EFI_HII_DATABASE_NOTIFY_TYPE; + +typedef EFI_STATUS (EFIAPI *EFI_HII_DATABASE_NOTIFY)( + uint8_t PackageType, + EFI_GUID *PackageGuid, + EFI_HII_PACKAGE_HEADER *Package, + EFI_HII_HANDLE Handle, + EFI_HII_DATABASE_NOTIFY_TYPE NotifyType); + +typedef struct EFI_HII_DATABASE_PROTOCOL EFI_HII_DATABASE_PROTOCOL; + +struct EFI_HII_DATABASE_PROTOCOL { + EFI_STATUS (EFIAPI *NewPackageList)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_PACKAGE_LIST_HEADER *PackageList, + EFI_HANDLE DriverHandle, + EFI_HII_HANDLE *Handle); + + EFI_STATUS (EFIAPI *RemovePackageList)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_HANDLE Handle); + + EFI_STATUS (EFIAPI *UpdatePackageList)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_HANDLE Handle, + EFI_HII_PACKAGE_LIST_HEADER *PackageList); + + EFI_STATUS (EFIAPI *ListPackageLists)( + EFI_HII_DATABASE_PROTOCOL *This, + uint8_t PackageType, + EFI_GUID *PackageGuid, + size_t *HandleBufferLength, + EFI_HII_HANDLE *Handle); + + EFI_STATUS (EFIAPI *ExportPackageLists)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_HANDLE Handle, + size_t *BufferSize, + EFI_HII_PACKAGE_LIST_HEADER *Buffer); + + EFI_STATUS (EFIAPI *RegisterPackageNotify)( + EFI_HII_DATABASE_PROTOCOL *This, + uint8_t PackageType, + EFI_GUID *PackageGuid, + EFI_HII_DATABASE_NOTIFY PackageNotifyFn, + EFI_HII_DATABASE_NOTIFY_TYPE NotifyType, + EFI_HANDLE *NotifyHandle); + + EFI_STATUS (EFIAPI *UnregisterPackageNotify)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HANDLE NotificationHandle); + + EFI_STATUS (EFIAPI *FindKeyboardLayouts)( + EFI_HII_DATABASE_PROTOCOL *This, + uint16_t *KeyGuidBufferLength, + EFI_GUID *KeyGuidBuffer); + + EFI_STATUS (EFIAPI *GetKeyboardLayout)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_GUID *KeyGuid, + uint16_t *KeyboardLayoutLength, + void *KeyboardLayout); + + EFI_STATUS (EFIAPI *SetKeyboardLayout)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_GUID *KeyGuid); + + EFI_STATUS (EFIAPI *GetPackageListHandle)( + EFI_HII_DATABASE_PROTOCOL *This, + EFI_HII_HANDLE PackageListHandle, + EFI_HANDLE *DriverHandle); +}; + +/* EFI_HII_KEYBOARD_LAYOUT and EFI_KEY_DESCRIPTOR are packed: LayoutDescriptorStringOffset follows + * a 16-byte EFI_GUID at offset 2, so it is at offset 18 — *not* a natural 4-byte alignment. */ +typedef struct _packed_ { + uint16_t LayoutLength; + EFI_GUID Guid; + uint32_t LayoutDescriptorStringOffset; + uint8_t DescriptorCount; + /* EFI_KEY_DESCRIPTOR Descriptors[DescriptorCount] follows here, then at + * LayoutDescriptorStringOffset (from the start of this struct) the description-string bundle. */ +} EFI_HII_KEYBOARD_LAYOUT; + +typedef struct _packed_ { + uint32_t Key; + char16_t Unicode; + char16_t ShiftedUnicode; + char16_t AltGrUnicode; + char16_t ShiftedAltGrUnicode; + uint16_t Modifier; + uint16_t AffectedAttribute; +} EFI_KEY_DESCRIPTOR; + +/* The description-string bundle that LayoutDescriptorStringOffset points to. After DescriptionCount, + * each of the DescriptionCount entries is laid out as: + * + * CHAR16 Language[]; // RFC 4646 tag, terminated by the Space below (no NUL) + * CHAR16 Space; // U+0020 + * CHAR16 DescriptionString[]; // NUL-terminated UTF-16 description + * + * Despite what the UEFI spec text says, Language is encoded as UTF-16 (CHAR16) in practice — see EDK2 + * MdeModulePkg/Bus/Usb/UsbKbDxe/KeyBoard.h USB_KEYBOARD_LAYOUT_PACK_BIN. */ +typedef struct _packed_ { + uint16_t DescriptionCount; + char16_t Strings[]; +} EFI_DESCRIPTION_STRING_BUNDLE; 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..d2ed63f0e359e 100644 --- a/src/boot/random-seed.c +++ b/src/boot/random-seed.c @@ -2,24 +2,16 @@ #include "efi-efivars.h" #include "efi-log.h" -#include "memory-util-fundamental.h" +#include "memory-util.h" #include "proto/rng.h" #include "random-seed.h" #include "secure-boot.h" -#include "sha256-fundamental.h" +#include "sha256.h" #include "util.h" #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 @@ -140,6 +132,17 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir) { validate_sha256(); + /* If the volume is read-only we cannot update the random seed, but if we cannot update it, then we + * really don't want to use it since it would be the same on every boot. */ + bool volume_ro; + err = get_volume_ro(root_dir, &volume_ro); + if (err != EFI_SUCCESS) + log_debug_status(err, "Failed to determine if volume is read-only, assuming not: %m"); + else if (volume_ro) { + log_debug("Volume is read-only, not updating random seed."); + return EFI_SUCCESS; + } + /* hash = LABEL || sizeof(input1) || input1 || ... || sizeof(inputN) || inputN */ sha256_init_ctx(&hash); @@ -193,47 +196,71 @@ 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_SUCCESS) { - if (!IN_SET(err, EFI_NOT_FOUND, EFI_WRITE_PROTECTED)) - log_error_status(err, "Failed to open random seed file: %m"); - return err; + if (err == EFI_NOT_FOUND && seeded_by_efi) { + + /* If the file does not exist, but we are reasonably well seeded, create the seed file */ + 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) + return log_full(err, + EFI_STATUS_IS_WRITE_REFUSED(err) ? LOG_DEBUG : LOG_ERR, + "Failed to open random seed file: %m"); + created = true; + + } else if (err != EFI_SUCCESS) + return log_full(err, + err == EFI_NOT_FOUND || EFI_STATUS_IS_WRITE_REFUSED(err) ? LOG_DEBUG : LOG_ERR, + "Failed to open random seed file: %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"); + + /* 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; } - 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) { + 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."); - size = info->FileSize; - if (size < RANDOM_MAX_SIZE_MIN) - return log_error_status(EFI_INVALID_PARAMETER, "Random seed file is too short."); + 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."); + } - if (size > RANDOM_MAX_SIZE_MAX) - return log_error_status(EFI_INVALID_PARAMETER, "Random seed file is too large."); + sha256_process_bytes(&size, sizeof(size), &hash); + sha256_process_bytes(seed, size, &hash); + explicit_bzero_safe(seed, size); - 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."); + err = handle->SetPosition(handle, 0); + if (err != EFI_SUCCESS) + return log_error_status(err, "Failed to seek to beginning of random seed file: %m"); } - 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"); - /* 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 * persistent, the random seed we generate will still be different on every single boot. */ @@ -261,19 +288,23 @@ 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"); wsize = info->FileSize - size; err = handle->Write(handle, &wsize, seed /* All zeros now */); if (err != EFI_SUCCESS) - return log_error_status(err, "Failed to write random seed file: %m"); + return log_full(err, + EFI_STATUS_IS_WRITE_REFUSED(err) ? LOG_DEBUG : LOG_ERR, + "Failed to write random seed file: %m"); if (wsize != info->FileSize - size) return log_error_status(EFI_PROTOCOL_ERROR, "Short write on random seed file."); err = handle->Flush(handle); if (err != EFI_SUCCESS) - return log_error_status(err, "Failed to flush random seed file: %m"); + return log_full(err, + EFI_STATUS_IS_WRITE_REFUSED(err) ? LOG_DEBUG : LOG_ERR, + "Failed to flush 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"); @@ -289,16 +320,21 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir) { * flimsy. So instead we rely on userspace eventually truncating this when it writes a new * seed. For now the best we do is zero it. */ } + /* Update the random seed on disk before we use it */ wsize = size; err = handle->Write(handle, &wsize, random_bytes); if (err != EFI_SUCCESS) - return log_error_status(err, "Failed to write random seed file: %m"); + return log_full(err, + EFI_STATUS_IS_WRITE_REFUSED(err) ? LOG_DEBUG : LOG_ERR, + "Failed to write random seed file: %m"); if (wsize != size) return log_error_status(EFI_PROTOCOL_ERROR, "Short write on random seed file."); err = handle->Flush(handle); if (err != EFI_SUCCESS) - return log_error_status(err, "Failed to flush random seed file: %m"); + return log_full(err, + EFI_STATUS_IS_WRITE_REFUSED(err) ? LOG_DEBUG : LOG_ERR, + "Failed to flush random seed file: %m"); err = BS->AllocatePool(EfiACPIReclaimMemory, offsetof(struct linux_efi_random_seed, seed) + DESIRED_SEED_SIZE, 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/secure-boot.h b/src/boot/secure-boot.h index 03bead27124d8..58cc02a9d277d 100644 --- a/src/boot/secure-boot.h +++ b/src/boot/secure-boot.h @@ -2,7 +2,7 @@ #pragma once #include "efi.h" -#include "efivars-fundamental.h" +#include "efivars.h" typedef enum { ENROLL_OFF, /* no Secure Boot key enrollment whatsoever, even manual entries are not generated */ diff --git a/src/boot/shim.c b/src/boot/shim.c index 97410d6669659..10a0642ac3b9f 100644 --- a/src/boot/shim.c +++ b/src/boot/shim.c @@ -8,7 +8,6 @@ * https://github.com/mjg59/efitools */ -#include "device-path-util.h" #include "efi-efivars.h" #include "secure-boot.h" #include "shim.h" @@ -56,24 +55,7 @@ static bool shim_validate( if (!device_path) return false; - EFI_HANDLE device_handle; - EFI_DEVICE_PATH *file_dp = (EFI_DEVICE_PATH *) device_path; - err = BS->LocateDevicePath( - MAKE_GUID_PTR(EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), &file_dp, &device_handle); - if (err != EFI_SUCCESS) - return false; - - _cleanup_file_close_ EFI_FILE *root = NULL; - err = open_volume(device_handle, &root); - if (err != EFI_SUCCESS) - return false; - - _cleanup_free_ char16_t *dp_str = NULL; - err = device_path_to_str(file_dp, &dp_str); - if (err != EFI_SUCCESS) - return false; - - err = file_read(root, dp_str, 0, 0, &file_buffer_owned, &file_size); + err = load_file_from_simple_filesystem(device_path, &file_buffer_owned, &file_size); if (err != EFI_SUCCESS) return false; @@ -111,12 +93,21 @@ EFI_STATUS shim_load_image( if (have_shim) install_security_override(shim_validate, NULL); + _cleanup_free_ char *source_buffer = NULL; + size_t source_size = 0; + + /* For some AMI firmware, BS->LoadImage() does not read correctly when the file comes the ESP on an + * optical drive. But the simple filesystem protocol does work. So we try to load it. If that does + * not work, we let BS->LoadImage() try instead. + */ + (void) load_file_from_simple_filesystem(device_path, &source_buffer, &source_size); + EFI_STATUS ret = BS->LoadImage( /* BootPolicy= */ boot_policy, parent, (EFI_DEVICE_PATH *) device_path, - /* SourceBuffer= */ NULL, - /* SourceSize= */ 0, + source_buffer, + source_size, ret_image); if (have_shim) uninstall_security_override(); diff --git a/src/boot/smbios.c b/src/boot/smbios.c index 0bbcf30123d18..46e9817acc88d 100644 --- a/src/boot/smbios.c +++ b/src/boot/smbios.c @@ -39,12 +39,6 @@ typedef struct { uint64_t table_address; } _packed_ Smbios3EntryPoint; -typedef struct { - uint8_t type; - uint8_t length; - uint8_t handle[2]; -} _packed_ SmbiosHeader; - typedef struct { SmbiosHeader header; uint8_t vendor; @@ -56,18 +50,6 @@ typedef struct { uint8_t bios_characteristics_ext[2]; } _packed_ SmbiosTableType0; -typedef struct { - SmbiosHeader header; - uint8_t manufacturer; - uint8_t product_name; - uint8_t version; - uint8_t serial_number; - EFI_GUID uuid; - uint8_t wake_up_type; - uint8_t sku_number; - uint8_t family; -} _packed_ SmbiosTableType1; - typedef struct { SmbiosHeader header; uint8_t manufacturer; @@ -103,6 +85,44 @@ static const void* find_smbios_configuration_table(uint64_t *ret_size) { return NULL; } +/* Given 'p' pointing at a structure header with 'size' bytes left in the table from there onwards, + * returns a pointer just past the end of this structure (i.e. the start of the next one), accounting + * for the formatted area and the trailing string set (terminated by a double NUL byte). Returns NULL + * if the structure is malformed or runs past the end of the table. */ +static const uint8_t* smbios_structure_end(const uint8_t *p, uint64_t size) { + assert(p); + + if (size < sizeof(SmbiosHeader)) + return NULL; + + const SmbiosHeader *header = (const SmbiosHeader *) p; + if (size < header->length) + return NULL; + + /* Skip over formatted area. */ + const uint8_t *q = p + header->length; + size -= header->length; + + /* Special case: if there are no strings appended, we'll see two NUL bytes. */ + if (size >= 2 && q[0] == 0 && q[1] == 0) + return q + 2; + + /* Skip over a populated string table. */ + bool first = true; + for (;;) { + const uint8_t *e = memchr(q, 0, size); + if (!e) + return NULL; + + if (!first && e == q) /* Double NUL byte means we've reached the end of the string table. */ + return q + 1; + + size -= e + 1 - q; + q = e + 1; + first = false; + } +} + static const SmbiosHeader* get_smbios_table(uint8_t type, size_t min_size, uint64_t *ret_size_left) { uint64_t size; const uint8_t *p = find_smbios_configuration_table(&size); @@ -132,34 +152,12 @@ static const SmbiosHeader* get_smbios_table(uint8_t type, size_t min_size, uint6 return header; /* Yay! */ } - /* Skip over formatted area. */ - size -= header->length; - p += header->length; - - /* Special case: if there are no strings appended, we'll see two NUL bytes, skip over them */ - if (size >= 2 && p[0] == 0 && p[1] == 0) { - size -= 2; - p += 2; - continue; - } - - /* Skip over a populated string table. */ - bool first = true; - for (;;) { - const uint8_t *e = memchr(p, 0, size); - if (!e) - goto not_found; - - if (!first && e == p) {/* Double NUL byte means we've reached the end of the string table. */ - p++; - size--; - break; - } + const uint8_t *next = smbios_structure_end(p, size); + if (!next) + goto not_found; - size -= e + 1 - p; - p = e + 1; - first = false; - } + size -= next - p; + p = next; } not_found: @@ -169,6 +167,40 @@ static const SmbiosHeader* get_smbios_table(uint8_t type, size_t min_size, uint6 return NULL; } +void smbios_foreach(SmbiosForeachFunc func, void *userdata) { + assert(func); + + uint64_t size; + const uint8_t *p = find_smbios_configuration_table(&size); + if (!p) + return; + + /* Walks the SMBIOS table exactly once, invoking 'func' for every structure. 'func' receives the + * structure's header (which carries its type) and the structure's total length (formatted area + + * trailing string set); returning false stops the iteration early. */ + + for (;;) { + if (size < sizeof(SmbiosHeader)) + return; + + const SmbiosHeader *header = (const SmbiosHeader *) p; + + /* End of table. */ + if (header->type == 127) + return; + + const uint8_t *next = smbios_structure_end(p, size); + if (!next) + return; + + if (!func(header, next - p, userdata)) + return; + + size -= next - p; + p = next; + } +} + bool smbios_in_hypervisor(void) { /* Look up BIOS Information (Type 0). */ const SmbiosTableType0 *type0 = (const SmbiosTableType0 *) get_smbios_table(0, sizeof(SmbiosTableType0), /* ret_size_left= */ NULL); diff --git a/src/boot/smbios.h b/src/boot/smbios.h index 694ef568e6818..226ebf758f129 100644 --- a/src/boot/smbios.h +++ b/src/boot/smbios.h @@ -3,10 +3,35 @@ #include "efi.h" +typedef struct { + uint8_t type; + uint8_t length; + uint8_t handle[2]; +} _packed_ SmbiosHeader; + +typedef struct { + SmbiosHeader header; + uint8_t manufacturer; + uint8_t product_name; + uint8_t version; + uint8_t serial_number; + EFI_GUID uuid; + uint8_t wake_up_type; + uint8_t sku_number; + uint8_t family; +} _packed_ SmbiosTableType1; + bool smbios_in_hypervisor(void); const char* smbios_find_oem_string(const char *name, const char *after); +/* Invoked by smbios_foreach() for each SMBIOS structure. 'header' points at the structure (which + * carries its type), and 'size' is the structure's total length (formatted area + trailing string + * set). Returning false stops the iteration. */ +typedef bool (*SmbiosForeachFunc)(const SmbiosHeader *header, size_t size, void *userdata); + +void smbios_foreach(SmbiosForeachFunc func, void *userdata); + typedef struct RawSmbiosInfo { const char *manufacturer; const char *product_name; diff --git a/src/boot/splash.c b/src/boot/splash.c index 451909eb4ca29..126fff954714b 100644 --- a/src/boot/splash.c +++ b/src/boot/splash.c @@ -4,7 +4,7 @@ #include "logarithm.h" #include "proto/graphics-output.h" #include "splash.h" -#include "unaligned-fundamental.h" +#include "unaligned.h" #include "util.h" struct bmp_file { @@ -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..e69faca00b0bf 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -1,16 +1,21 @@ /* 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 "iovec-util-fundamental.h" +#include "initrd.h" +#include "iovec-util.h" #include "linux.h" #include "measure.h" -#include "memory-util-fundamental.h" +#include "measure-smbios.h" +#include "memory-util.h" #include "part-discovery.h" #include "pe.h" #include "proto/shell-parameters.h" @@ -29,13 +34,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 +47,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 +103,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 */ @@ -156,6 +117,7 @@ static void export_stub_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image, unsig EFI_STUB_FEATURE_MULTI_PROFILE_UKI | /* We grok the "@1" profile command line argument */ EFI_STUB_FEATURE_REPORT_STUB_PARTITION | /* We set StubDevicePartUUID + StubImageIdentifier */ EFI_STUB_FEATURE_REPORT_URL | /* We set StubDeviceURL + LoaderDeviceURL */ + EFI_STUB_FEATURE_SMBIOS_MEASURED | /* We measure SMBIOS data into PCR 1 */ 0; assert(loaded_image); @@ -408,14 +370,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 +508,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, @@ -629,7 +599,7 @@ static EFI_STATUS load_addons( err = pe_memory_locate_sections(loaded_addon->ImageBase, unified_sections, sections); if (err != EFI_SUCCESS) { log_error_status(err, - "Unable to locate embedded .cmdline/.dtb/.dtbauto/.initrd/.ucode sections in %ls, ignoring: %m", + "Unable to locate embedded .cmdline/.dtb/.dtbauto/.efifw/.initrd/.ucode sections in %ls, ignoring: %m", items[i]); continue; } @@ -824,10 +794,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,10 +820,7 @@ static void generate_sidecar_initrds( /* dropin_dir= */ NULL, u".cred", /* exclude_suffix= */ NULL, - ".extra/credentials", - /* dir_mode= */ 0500, - /* access_mode= */ 0400, - /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, + &cpio_target_credentials, u"Credentials initrd", initrds + INITRD_CREDENTIAL, &m) == EFI_SUCCESS) @@ -862,10 +830,7 @@ static void generate_sidecar_initrds( u"\\loader\\credentials", u".cred", /* exclude_suffix= */ NULL, - ".extra/global_credentials", - /* dir_mode= */ 0500, - /* access_mode= */ 0400, - /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, + &cpio_target_global_credentials, u"Global credentials initrd", initrds + INITRD_GLOBAL_CREDENTIAL, &m) == EFI_SUCCESS) @@ -875,10 +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, - /* tpm_pcr= */ TPM2_PCR_SYSEXTS, + &cpio_target_sysext, u"System extension initrd", initrds + INITRD_SYSEXT, &m) == EFI_SUCCESS) @@ -888,10 +850,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, - /* tpm_pcr= */ TPM2_PCR_SYSEXTS, + &cpio_target_global_sysext, u"Global system extension initrd", initrds + INITRD_GLOBAL_SYSEXT, &m) == EFI_SUCCESS) @@ -901,10 +860,7 @@ static void generate_sidecar_initrds( /* dropin_dir= */ NULL, u".confext.raw", /* exclude_suffix= */ NULL, - ".extra/confext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, - /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, + &cpio_target_confext, u"Configuration extension initrd", initrds + INITRD_CONFEXT, &m) == EFI_SUCCESS) @@ -914,10 +870,7 @@ static void generate_sidecar_initrds( u"\\loader\\extensions", u".confext.raw", /* exclude_suffix= */ NULL, - ".extra/global_confext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, - /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, + &cpio_target_global_confext, u"Global configuration extension initrd", initrds + INITRD_GLOBAL_CONFEXT, &m) == EFI_SUCCESS) @@ -967,17 +920,34 @@ 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, /* ret_measured= */ NULL); } } +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_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 +1202,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 +1226,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 +1237,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,16 +1249,24 @@ 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); + /* Measure SMBIOS data into PCR 1, unless sd-boot already did so in the same boot (tracked via + * the LoaderPcrSMBIOS EFI variable). */ + measure_smbios(); + /* First load the base device tree, then fix it up using addons - global first, then per-UKI. */ install_embedded_devicetree(loaded_image, sections, &dt_state); 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 +1274,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 +1310,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..d64ceb999cc71 100644 --- a/src/boot/util.c +++ b/src/boot/util.c @@ -4,10 +4,10 @@ #include "efi-log.h" #include "efi-string.h" -#include "memory-util-fundamental.h" +#include "memory-util.h" #include "proto/device-path.h" #include "proto/simple-text-io.h" -#include "string-util-fundamental.h" +#include "string-util.h" #include "util.h" #include "version.h" @@ -195,6 +195,32 @@ EFI_STATUS file_read( return file_handle_read(handle, offset, size, ret, ret_size); } +EFI_STATUS load_file_from_simple_filesystem(const EFI_DEVICE_PATH *device_path, char **file_buffer, size_t *file_size) { + EFI_STATUS err; + EFI_HANDLE device_handle; + EFI_DEVICE_PATH *file_dp = (EFI_DEVICE_PATH *) device_path; + + assert(device_path); + assert(file_buffer); + assert(file_size); + + err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_SIMPLE_FILE_SYSTEM_PROTOCOL), &file_dp, &device_handle); + if (err != EFI_SUCCESS) + return err; + + _cleanup_file_close_ EFI_FILE *root = NULL; + err = open_volume(device_handle, &root); + if (err != EFI_SUCCESS) + return err; + + _cleanup_free_ char16_t *dp_str = NULL; + err = device_path_to_str(file_dp, &dp_str); + if (err != EFI_SUCCESS) + return err; + + return file_read(root, dp_str, 0, 0, file_buffer, file_size); +} + void set_attribute_safe(size_t attr) { /* Various UEFI implementations suppress color changes from a color to the same color. Often, we want * to force out the color change though, hence change the color here once, and then back. We simply @@ -272,6 +298,28 @@ EFI_STATUS get_file_info(EFI_FILE *handle, EFI_FILE_INFO **ret, size_t *ret_size return EFI_SUCCESS; } +EFI_STATUS get_volume_ro(EFI_FILE *handle, bool *ret) { + size_t size = offsetof(EFI_FILE_SYSTEM_INFO, VolumeLabel) + 256U * sizeof(char16_t); + _cleanup_free_ EFI_FILE_SYSTEM_INFO *fsi = NULL; + EFI_STATUS err; + + assert(handle); + assert(ret); + + fsi = xmalloc(size); + err = handle->GetInfo(handle, MAKE_GUID_PTR(EFI_FILE_SYSTEM_INFO), &size, fsi); + if (err == EFI_BUFFER_TOO_SMALL) { + free(fsi); + fsi = xmalloc(size); /* GetInfo tells us the required size, let's use that now */ + err = handle->GetInfo(handle, MAKE_GUID_PTR(EFI_FILE_SYSTEM_INFO), &size, fsi); + } + if (err != EFI_SUCCESS) + return err; + + *ret = fsi->ReadOnly; + return EFI_SUCCESS; +} + EFI_STATUS readdir( EFI_FILE *handle, EFI_FILE_INFO **buffer, @@ -344,6 +392,7 @@ EFI_STATUS open_directory( EFI_STATUS err; assert(root); + assert(ret); /* Opens a file, and then verifies it is actually a directory */ @@ -375,6 +424,8 @@ __attribute__((noinline)) void notify_debugger(const char *identity, volatile bo asm volatile("pause"); # elif defined(__aarch64__) asm volatile("wfi"); +# elif defined(__riscv) + asm volatile(".insn i 0x0F, 0, x0, x0, 0x010"); # else BS->Stall(5000); # endif diff --git a/src/boot/util.h b/src/boot/util.h index 2c8cc36ea580d..1c0a0412492a6 100644 --- a/src/boot/util.h +++ b/src/boot/util.h @@ -2,7 +2,7 @@ #pragma once #include "efi.h" -#include "memory-util-fundamental.h" +#include "memory-util.h" #if SD_BOOT @@ -138,6 +138,7 @@ char16_t *mangle_stub_cmdline(char16_t *cmdline); EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf); EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, uint64_t offset, size_t size, char **ret, size_t *ret_size); +EFI_STATUS load_file_from_simple_filesystem(const EFI_DEVICE_PATH *device_path, char **file_buffer, size_t *file_size); EFI_STATUS file_handle_read(EFI_FILE *handle, uint64_t offset, size_t size, char **ret, size_t *ret_size); static inline void file_closep(EFI_FILE **handle) { @@ -167,6 +168,7 @@ typedef int (*compare_pointer_func_t)(const void *a, const void *b); void sort_pointer_array(void **array, size_t n_members, compare_pointer_func_t compare); EFI_STATUS get_file_info(EFI_FILE *handle, EFI_FILE_INFO **ret, size_t *ret_size); +EFI_STATUS get_volume_ro(EFI_FILE *handle, bool *ret); EFI_STATUS readdir(EFI_FILE *handle, EFI_FILE_INFO **buffer, size_t *buffer_size); bool is_ascii(const char16_t *f); @@ -262,3 +264,7 @@ char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path); #endif char16_t *url_replace_last_component(const char16_t *url, const char16_t *filename); + +static inline bool EFI_STATUS_IS_WRITE_REFUSED(EFI_STATUS status) { + return IN_SET(status, EFI_WRITE_PROTECTED, EFI_DEVICE_ERROR, EFI_ACCESS_DENIED); +} diff --git a/src/boot/vmm.c b/src/boot/vmm.c index da17e4584aaec..e571a3990de64 100644 --- a/src/boot/vmm.c +++ b/src/boot/vmm.c @@ -4,7 +4,7 @@ # include #endif -#include "confidential-virt-fundamental.h" +#include "confidential-virt.h" #include "device-path-util.h" #include "drivers.h" #include "efi-string.h" @@ -225,14 +225,10 @@ static bool detect_sev(void) { } static bool detect_tdx(void) { - uint32_t eax, ebx, ecx, edx; char sig[13] = {}; - __cpuid(CPUID_GET_HIGHEST_FUNCTION, eax, ebx, ecx, edx); - - if (eax < CPUID_INTEL_TDX_ENUMERATION) - return false; - + /* Querying an unsupported CPUID leaf is harmless (it returns the highest basic leaf's data rather + * than faulting), so reading this leaf and matching the IntelTDX signature is sufficient. */ cpuid_leaf(CPUID_INTEL_TDX_ENUMERATION, sig, true); if (memcmp(sig, CPUID_SIG_INTEL_TDX, sizeof(sig)) == 0) diff --git a/src/bootctl/bootctl-cleanup.c b/src/bootctl/bootctl-cleanup.c index 8cc51dd597ed0..011567d187be6 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) @@ -49,6 +49,7 @@ static int list_remove_orphaned_file( static int cleanup_orphaned_files( const BootConfig *config, + BootEntrySource source, const char *root) { _cleanup_hashmap_free_ Hashmap *known_files = NULL; @@ -65,7 +66,7 @@ static int cleanup_orphaned_files( if (r < 0) return r; - r = boot_config_count_known_files(config, root, &known_files); + r = boot_config_count_known_files(config, source, &known_files); if (r < 0) return log_error_errno(r, "Failed to count files in %s: %m", root); @@ -87,41 +88,39 @@ 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; r = 0; - RET_GATHER(r, cleanup_orphaned_files(&config, arg_esp_path)); + RET_GATHER(r, cleanup_orphaned_files(&config, BOOT_ENTRY_ESP, arg_esp_path)); if (arg_xbootldr_path && xbootldr_devid != esp_devid) - RET_GATHER(r, cleanup_orphaned_files(&config, arg_xbootldr_path)); + RET_GATHER(r, cleanup_orphaned_files(&config, BOOT_ENTRY_XBOOTLDR, arg_xbootldr_path)); 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..f8ba48a485bcd 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,9 +16,10 @@ #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" +#include "efi.h" #include "efivars.h" #include "env-file.h" #include "fd-util.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" @@ -44,6 +45,7 @@ #include "tmpfile-util.h" #include "umask-util.h" #include "utf8.h" +#include "varlink-util.h" typedef enum InstallOperation { INSTALL_NEW, @@ -117,11 +119,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 +165,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 +192,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) @@ -208,60 +212,23 @@ static int install_context_from_cmdline( return log_oom(); } + b.touch_variables = arg_touch_variables; + *ret = TAKE_GENERIC(b, InstallContext, INSTALL_CONTEXT_NULL); 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 +280,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 +397,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) @@ -496,13 +465,13 @@ static int version_check(int fd_from, const char *from, int fd_to, const char *t if (r == -ESRCH) return log_notice_errno(r, "Source file \"%s\" does not carry version information!", from); if (r < 0) - return r; + return log_error_errno(r, "Failed to get file version of '%s': %m", from); r = get_file_version(fd_to, &b); if (r == -ESRCH) return log_info_errno(r, "Skipping \"%s\", it's owned by another boot loader (no version info found).", to); if (r < 0) - return r; + return log_error_errno(r, "Failed to get file version of '%s': %m", to); if (compare_product(a, b) != 0) return log_info_errno(SYNTHETIC_ERRNO(ESRCH), "Skipping \"%s\", it's owned by another boot loader.", to); @@ -554,10 +523,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 +552,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 +609,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 +618,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 +687,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 +709,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) @@ -766,11 +734,57 @@ static int copy_one_file( if (dest_fd < 0 && dest_fd != -ENOENT) return log_error_errno(dest_fd, "Failed to open '%s' under '%s/EFI/systemd' directory: %m", dest_name, j); - /* Note that if this fails we do the second copy anyway, but return this error code, - * so we stash it away in a separate variable. */ - ret = copy_file_with_version_check(source_path, source_fd, dest_path, dest_parent_fd, dest_name, dest_fd, force); - const char *e = startswith(dest_name, "systemd-boot"); + + /* If a primary sd-boot binary already exists and the source is a newer version, copy + * the existing primary to systemd-boot-fallback{arch}.efi before installing the new + * one, so firmware has a fallback to the previous binary. The fallback is left alone + * when its product and version match the currently booted bootloader (from LoaderInfo), + * so a known good binary stays as the fallback. In all other cases, like no fallback yet, + * LoaderInfo is unavailable, or product/version differs from what booted, it is + * overwritten with the current primary. */ + if (e && dest_fd >= 0 && !force) { + r = version_check(source_fd, source_path, dest_fd, dest_path); + if (r < 0) + /* Stash the error and fall through; the BOOT{arch}.EFI updates below still run. */ + ret = r; + else { + _cleanup_free_ char *fallback_name = strjoin("systemd-boot-fallback", e); + if (!fallback_name) + return log_oom(); + + _cleanup_free_ char *fallback_path = path_join(j, "/EFI/systemd", fallback_name); + if (!fallback_path) + return log_oom(); + + /* Leave the fallback alone if it already holds the currently booted product + * and version, so a known good binary stays as the fallback. If there is no + * fallback yet, LoaderInfo is unavailable, or there is a mismatch, then + * overwrite it with the current primary. */ + bool should_rotate = true; + _cleanup_close_ int fallback_fd = xopenat_full(dest_parent_fd, fallback_name, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID); + if (fallback_fd >= 0) { + _cleanup_free_ char *loader_info = NULL, *fallback_version = NULL; + + if (efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderInfo"), &loader_info) >= 0 && + get_file_version(fallback_fd, &fallback_version) >= 0) + should_rotate = compare_product(loader_info, fallback_version) != 0 || + compare_version(loader_info, fallback_version) != 0; + } + + if (should_rotate) { + r = copy_file_with_version_check(dest_path, dest_fd, fallback_path, dest_parent_fd, fallback_name, /* dest_fd= */ -EBADF, /* force= */ true); + if (r < 0) + log_warning_errno(r, "Failed to back up sd-boot binary to fallback path, continuing: %m"); + } + + ret = copy_file_with_version_check(source_path, source_fd, dest_path, dest_parent_fd, dest_name, dest_fd, /* force= */ true); + } + } else + /* Note that if this fails we do the second copy anyway, but return this error code, + * so we stash it away in a separate variable. */ + ret = copy_file_with_version_check(source_path, source_fd, dest_path, dest_parent_fd, dest_name, dest_fd, force); + if (e) { /* Create the EFI default boot loader name (specified for removable devices) */ @@ -781,9 +795,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 +835,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 +894,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 +958,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 +1033,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 +1085,28 @@ 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + 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)); + return log_openssl_errors(LOG_ERR, "Failed to convert X.509 certificate to DER"); - 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 +1140,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 +1149,32 @@ 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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable name to bio"); + if (sym_BIO_write(bio, db16, char16_strsize(db16) - sizeof(char16_t)) < 0) + return log_openssl_errors(LOG_ERR, "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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable GUID to bio"); + if (sym_BIO_write(bio, guid, sizeof(*guid)) < 0) + return log_openssl_errors(LOG_ERR, "Failed to write variable GUID to bio"); - if (BIO_write(bio, &attrs, sizeof(attrs)) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable attributes to bio"); + if (sym_BIO_write(bio, &attrs, sizeof(attrs)) < 0) + return log_openssl_errors(LOG_ERR, "Failed to write variable attributes to bio"); - if (BIO_write(bio, ×tamp, sizeof(timestamp)) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write timestamp to bio"); + if (sym_BIO_write(bio, ×tamp, sizeof(timestamp)) < 0) + return log_openssl_errors(LOG_ERR, "Failed to write timestamp to bio"); - if (BIO_write(bio, siglist, siglistsz) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write signature list to bio"); + if (sym_BIO_write(bio, siglist, siglistsz) < 0) + return log_openssl_errors(LOG_ERR, "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)); + return log_openssl_errors(LOG_ERR, "Failed to calculate PKCS7 signature"); _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)); + return log_openssl_errors(LOG_ERR, "Failed to convert PKCS7 signature to DER"); size_t authsz = offsetof(EFI_VARIABLE_AUTHENTICATION_2, AuthInfo.CertData) + sigsz; _cleanup_free_ EFI_VARIABLE_AUTHENTICATION_2 *auth = malloc(authsz); @@ -1233,6 +1252,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; @@ -1258,7 +1279,7 @@ static int find_slot(sd_id128_t uuid, const char *path, uint16_t *id) { return 0; } -static int insert_into_order(InstallContext *c, uint16_t slot) { +static int insert_into_order(InstallContext *c, uint16_t slot, uint16_t after_slot) { _cleanup_free_ uint16_t *order = NULL; uint16_t *t; int n; @@ -1280,7 +1301,8 @@ static int insert_into_order(InstallContext *c, uint16_t slot) { continue; /* we do not require to be the first one, all is fine */ - if (c->operation != INSTALL_NEW) + /* if after_slot is set, leave existing position alone to preserve user reordering. */ + if (i == 0 || c->operation != INSTALL_NEW || after_slot != UINT16_MAX) return 0; /* move us to the first slot */ @@ -1289,6 +1311,27 @@ static int insert_into_order(InstallContext *c, uint16_t slot) { return efi_set_boot_order(order, n); } + /* slot is not yet in the order, so insert after a specific slot if requested */ + if (after_slot != UINT16_MAX) { + t = reallocarray(order, n + 1, sizeof(uint16_t)); + if (!t) + return -ENOMEM; + order = t; + + for (int i = 0; i < n; i++) { + if (order[i] != after_slot) + continue; + + memmove(order + i + 2, order + i + 1, (n - i - 1) * sizeof(uint16_t)); + order[i + 1] = slot; + return efi_set_boot_order(order, n + 1); + } + + log_warning("Boot entry %04" PRIx16 " not found in BootOrder, appending new entry at the end.", after_slot); + order[n] = slot; + return efi_set_boot_order(order, n + 1); + } + /* extend array */ t = reallocarray(order, n + 1, sizeof(uint16_t)); if (!t) @@ -1377,32 +1420,38 @@ static int pick_efi_boot_option_description(int esp_fd, char **ret) { return 0; } -static int install_variables( +static int install_boot_option( InstallContext *c, - const char *path) { + const char *path, + const char *description, + bool require_existing, + uint16_t after_slot, + uint16_t *ret_slot) { uint16_t slot; int r; assert(c); + assert(path); + assert(description); - 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) + if (r == -ENOENT && require_existing) return 0; - if (r < 0) + if (r < 0 && r != -ENOENT) return log_error_errno(r, "Cannot access \"%s/%s\": %m", j, skip_leading_slash(path)); r = find_slot(c->esp_uuid, path, &slot); @@ -1421,12 +1470,6 @@ static int install_variables( bool existing = r > 0; if (c->operation == INSTALL_NEW || !existing) { - _cleanup_free_ char *description = NULL; - - r = pick_efi_boot_option_description(esp_fd, &description); - if (r < 0) - return r; - r = efi_add_boot_option( slot, description, @@ -1449,7 +1492,14 @@ static int install_variables( description); } - return insert_into_order(c, slot); + r = insert_into_order(c, slot, after_slot); + if (r < 0) + return r; + + if (ret_slot) + *ret_slot = slot; + + return 0; } static int are_we_installed(InstallContext *c) { @@ -1475,14 +1525,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) @@ -1534,7 +1584,7 @@ static int load_secure_boot_auto_enroll( if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); if (r < 0) - return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + return r; } r = openssl_load_private_key( @@ -1560,6 +1610,38 @@ static int load_secure_boot_auto_enroll( } #endif +static int install_variables(InstallContext *c, const char *arch) { + int r; + + assert(c); + + const char *path = strjoina("/EFI/systemd/systemd-boot", arch, ".efi"); + + _cleanup_free_ char *description = NULL; + r = pick_efi_boot_option_description(c->esp_fd, &description); + if (r < 0) + return r; + + uint16_t primary_slot = UINT16_MAX; + r = install_boot_option(c, path, description, /* require_existing= */ true, /* after_slot= */ UINT16_MAX, &primary_slot); + if (r < 0) + return r; + /* If primary registration was skipped (e.g. binary not on ESP), skip the fallback too + * or else it would land at position 0 in BootOrder with no primary ahead of it. */ + if (primary_slot == UINT16_MAX) + return 0; + + const char *fallback_path = strjoina("/EFI/systemd/systemd-boot-fallback", arch, ".efi"); + + _cleanup_free_ char *fallback_description = strjoin("Fallback ", description); + if (!fallback_description) + return log_oom(); + + strshorten(fallback_description, EFI_BOOT_OPTION_DESCRIPTION_MAX); + + return install_boot_option(c, fallback_path, fallback_description, /* require_existing= */ false, /* after_slot= */ primary_slot, /* ret_slot= */ NULL); +} + static int run_install(InstallContext *c) { int r; @@ -1583,9 +1665,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 +1686,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 +1713,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; } @@ -1657,11 +1738,10 @@ static int run_install(InstallContext *c) { return 0; } - char *path = strjoina("/EFI/systemd/systemd-boot", arch, ".efi"); - return install_variables(c, path); + return install_variables(c, arch); } -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 +1770,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 +1780,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) @@ -1726,6 +1806,8 @@ static int remove_boot_efi(InstallContext *c) { return log_oom(); fd = xopenat_full(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_NOFOLLOW, XO_REGULAR, /* mode= */ MODE_INVALID); + if (fd == -ENOENT) + continue; if (fd < 0) return log_error_errno(fd, "Failed to open '%s' for reading: %m", z); @@ -1741,8 +1823,10 @@ static int remove_boot_efi(InstallContext *c) { r = get_file_version(fd, &v); if (r == -ESRCH) continue; /* No version information */ - if (r < 0) - return r; + if (r < 0) { + log_warning_errno(r, "Failed to get file version of '%s', skipping: %m", de->d_name); + continue; + } if (!startswith(v, "systemd-boot ")) continue; @@ -1771,9 +1855,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 +1904,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) { @@ -1834,7 +1920,7 @@ static int remove_binaries(InstallContext *c) { return RET_GATHER(r, remove_boot_efi(c)); } -static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { +static int remove_boot_option(sd_id128_t uuid, const char *path, bool in_order) { uint16_t slot; int r; @@ -1882,8 +1968,19 @@ static int remove_loader_variables(void) { return r; } -int verb_remove(int argc, char *argv[], void *userdata) { - sd_id128_t uuid = SD_ID128_NULL; +static int remove_variables(sd_id128_t uuid) { + int r = 0; + + const char *path = strjoina("/EFI/systemd/systemd-boot", get_efi_arch(), ".efi"); + RET_GATHER(r, remove_boot_option(uuid, path, /* in_order= */ true)); + + const char *fallback_path = strjoina("/EFI/systemd/systemd-boot-fallback", get_efi_arch(), ".efi"); + RET_GATHER(r, remove_boot_option(uuid, fallback_path, /* in_order= */ true)); + + return RET_GATHER(r, remove_loader_variables()); +} + +int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; _cleanup_(install_context_done) InstallContext c = INSTALL_CONTEXT_NULL; @@ -1899,15 +1996,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 +2012,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 */ @@ -1951,12 +2047,10 @@ int verb_remove(int argc, char *argv[], void *userdata) { return r; } - char *path = strjoina("/EFI/systemd/systemd-boot", get_efi_arch(), ".efi"); - RET_GATHER(r, remove_variables(uuid, path, /* in_order= */ true)); - return RET_GATHER(r, remove_loader_variables()); + return remove_variables(c.esp_uuid); } -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; @@ -2028,6 +2122,10 @@ int vl_method_install( if (r != 0) return r; + r = varlink_check_privileged_peer(link); + if (r < 0) + return r; + if (!IN_SET(p.context.operation, INSTALL_NEW, INSTALL_UPDATE)) return sd_varlink_error_invalid_parameter_name(link, "operation"); @@ -2062,11 +2160,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 +2181,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-link.c b/src/bootctl/bootctl-link.c new file mode 100644 index 0000000000000..af0955b64abbc --- /dev/null +++ b/src/bootctl/bootctl-link.c @@ -0,0 +1,1705 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-json.h" +#include "sd-varlink.h" + +#include "boot-entry.h" +#include "bootctl.h" +#include "bootctl-link.h" +#include "bootctl-unlink.h" +#include "bootspec.h" +#include "bootspec-util.h" +#include "chase.h" +#include "copy.h" +#include "dirent-util.h" +#include "efi-loader.h" +#include "env-file.h" +#include "errno-util.h" +#include "fd-util.h" +#include "find-esp.h" +#include "format-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "id128-util.h" +#include "io-util.h" +#include "iovec-util.h" +#include "json-util.h" +#include "kernel-image.h" +#include "log.h" +#include "parse-argument.h" +#include "path-util.h" +#include "recurse-dir.h" +#include "set.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "tmpfile-util.h" +#include "uki.h" +#include "utf8.h" +#include "varlink-util.h" +#include "vpick.h" + +/* Keeps track of an "extra" file to associate with the type 1 entries to generate */ +typedef struct ExtraFile { + /* The source and the temporary file we copy it into */ + int source_fd, temp_fd; + char *filename, *temp_filename; + struct iovec data; /* Alternative to 'source_fd': literal data */ +} ExtraFile; + +#define EXTRA_FILE_NULL \ + (const ExtraFile) { \ + .source_fd = -EBADF, \ + .temp_fd = -EBADF, \ + } + +/* Keeps track of a specific UKI profile we need to generate a type entry for */ +typedef struct Profile { + /* The final and the temporary file for the .conf entry file, while we write it */ + char *entry_filename, *entry_temp_filename; + int entry_temp_fd; +} Profile; + +typedef struct LinkContext { + char *root; + int root_fd; + + sd_id128_t machine_id; + BootEntryTokenType entry_token_type; + char *entry_token; + + char *entry_title; + char *entry_version; + uint64_t entry_commit; + + BootEntrySource dollar_boot_source; + char *dollar_boot_path; + int dollar_boot_fd; + int entry_token_dir_fd; + int loader_entries_dir_fd; + + /* The UKI source and temporary target while we write it. Note that for now we exclusively support + * UKIs, but let's keep things somewhat generic to keep options open for the future. */ + char *kernel_filename, *kernel_temp_filename; + int kernel_fd, kernel_temp_fd; + + ExtraFile *extra; + size_t n_extra; + + Profile *profiles; + size_t n_profiles; + + unsigned tries_left; + + uint64_t keep_free; + + char **linked_ids; +} LinkContext; + +#define LINK_CONTEXT_NULL \ + (LinkContext) { \ + .root_fd = -EBADF, \ + .entry_token_type = _BOOT_ENTRY_TOKEN_TYPE_INVALID, \ + .dollar_boot_fd = -EBADF, \ + .loader_entries_dir_fd = -EBADF, \ + .entry_token_dir_fd = -EBADF, \ + .kernel_fd = -EBADF, \ + .kernel_temp_fd = -EBADF, \ + .tries_left = UINT_MAX, \ + .keep_free = UINT64_MAX, \ + } + +static void extra_file_done(ExtraFile *x) { + assert(x); + + x->source_fd = safe_close(x->source_fd); + x->temp_fd = safe_close(x->temp_fd); + x->filename = mfree(x->filename); + x->temp_filename = mfree(x->temp_filename); + iovec_done(&x->data); +} + +static void profile_done(Profile *p) { + assert(p); + + p->entry_filename = mfree(p->entry_filename); + p->entry_temp_filename = mfree(p->entry_temp_filename); + p->entry_temp_fd = safe_close(p->entry_temp_fd); +} + +static void link_context_unlink_temporary(LinkContext *c) { + assert(c); + + if (c->kernel_temp_filename) { + if (c->entry_token_dir_fd >= 0) + (void) unlinkat(c->entry_token_dir_fd, c->kernel_temp_filename, /* flags= */ 0); + + c->kernel_temp_fd = safe_close(c->kernel_temp_fd); + c->kernel_temp_filename = mfree(c->kernel_temp_filename); + } + + FOREACH_ARRAY(x, c->extra, c->n_extra) { + if (!x->temp_filename) + continue; + + if (c->entry_token_dir_fd >= 0) + (void) unlinkat(c->entry_token_dir_fd, x->temp_filename, /* flags= */ 0); + + x->temp_fd = safe_close(x->temp_fd); + x->temp_filename = mfree(x->temp_filename); + } + + FOREACH_ARRAY(p, c->profiles, c->n_profiles) { + if (!p->entry_temp_filename) + continue; + + if (c->loader_entries_dir_fd >= 0) + (void) unlinkat(c->loader_entries_dir_fd, p->entry_temp_filename, /* flags= */ 0); + + p->entry_temp_fd = safe_close(p->entry_temp_fd); + p->entry_temp_filename = mfree(p->entry_temp_filename); + } +} + +static void link_context_clear_profiles(LinkContext *c) { + assert(c); + + FOREACH_ARRAY(p, c->profiles, c->n_profiles) + profile_done(p); + + c->profiles = mfree(c->profiles); + c->n_profiles = 0; +} + +static void link_context_done(LinkContext *c) { + assert(c); + + link_context_unlink_temporary(c); + + FOREACH_ARRAY(x, c->extra, c->n_extra) + extra_file_done(x); + + c->extra = mfree(c->extra); + c->n_extra = 0; + + link_context_clear_profiles(c); + + c->kernel_filename = mfree(c->kernel_filename); + c->kernel_fd = safe_close(c->kernel_fd); + c->kernel_temp_filename = mfree(c->kernel_temp_filename); + c->kernel_temp_fd = safe_close(c->kernel_temp_fd); + + c->root = mfree(c->root); + c->root_fd = safe_close(c->root_fd); + + c->entry_token = mfree(c->entry_token); + c->entry_title = mfree(c->entry_title); + c->entry_version = mfree(c->entry_version); + + c->dollar_boot_path = mfree(c->dollar_boot_path); + c->dollar_boot_fd = safe_close(c->dollar_boot_fd); + c->entry_token_dir_fd = safe_close(c->entry_token_dir_fd); + c->loader_entries_dir_fd = safe_close(c->loader_entries_dir_fd); + + c->linked_ids = strv_free(c->linked_ids); +} + +static int link_context_acquire_dollar_boot(LinkContext *b) { + int r; + + assert(b); + + r = acquire_xbootldr( + /* unprivileged_mode= */ false, + &b->dollar_boot_fd, + /* ret_uuid= */ NULL, + /* ret_devid= */ NULL); + if (r < 0) + return r; + if (r > 0) { /* XBOOTLDR has been found */ + assert(arg_xbootldr_path); + + if (arg_root) { + const char *e = path_startswith(arg_xbootldr_path, arg_root); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "XBOOTLDR path '%s' not below specified root '%s', refusing.", arg_xbootldr_path, arg_root); + + r = strdup_to(&b->dollar_boot_path, e); + } else + r = strdup_to(&b->dollar_boot_path, arg_xbootldr_path); + if (r < 0) + return log_oom(); + + b->dollar_boot_source = BOOT_ENTRY_XBOOTLDR; + } else { + /* No XBOOTLDR has been found, look for ESP */ + + r = acquire_esp(/* unprivileged_mode= */ false, + /* graceful= */ false, + &b->dollar_boot_fd, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid= */ NULL, + /* ret_devid= */ NULL); + if (r < 0) + return r; + + assert(arg_esp_path); + + if (arg_root) { + const char *e = path_startswith(arg_esp_path, arg_root); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP path '%s' not below specified root '%s', refusing.", arg_esp_path, arg_root); + + r = strdup_to(&b->dollar_boot_path, e); + } else + r = strdup_to(&b->dollar_boot_path, arg_esp_path); + if (r < 0) + return log_oom(); + + b->dollar_boot_source = BOOT_ENTRY_ESP; + } + + return 0; +} + +/* Fills the bits of a LinkContext that both "bootctl link" and "bootctl link-auto" derive from the global + * command line options (everything except the kernel image and extra files themselves). */ +static int link_context_from_cmdline_common(LinkContext *b) { + assert(b); + + b->entry_token_type = arg_entry_token_type; + b->tries_left = arg_tries_left; + b->entry_commit = arg_entry_commit; + b->keep_free = arg_keep_free; + + if (strdup_to(&b->entry_token, arg_entry_token) < 0 || + strdup_to(&b->entry_title, arg_entry_title) < 0 || + strdup_to(&b->entry_version, arg_entry_version) < 0) + return log_oom(); + + if (arg_root) { + b->root_fd = open(arg_root, O_CLOEXEC|O_DIRECTORY|O_PATH); + if (b->root_fd < 0) + return log_error_errno(errno, "Failed to open root directory '%s': %m", arg_root); + + if (strdup_to(&b->root, arg_root) < 0) + return log_oom(); + } else + b->root_fd = XAT_FDROOT; + + return 0; +} + +/* Appends the files passed via --extra= on the command line as extra resources to the link context. */ +static int link_context_add_cmdline_extras(LinkContext *b) { + int r; + + assert(b); + + STRV_FOREACH(x, arg_extras) { + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(*x, &fn); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from path '%s': %m", *x); + if (r == O_DIRECTORY) + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Extra file path '%s' does not refer to regular file.", *x); + + _cleanup_close_ int fd = xopenat_full(AT_FDCWD, *x, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID); + if (fd < 0) + return log_error_errno(fd, "Failed to open '%s': %m", *x); + + if (!GREEDY_REALLOC(b->extra, b->n_extra+1)) + return log_oom(); + + b->extra[b->n_extra++] = (ExtraFile) { + .source_fd = TAKE_FD(fd), + .filename = TAKE_PTR(fn), + .temp_fd = -EBADF, + }; + } + + return 0; +} + +static int validate_kernel(int kernel_fd, const char *filename) { + int r; + + assert(kernel_fd >= 0); + + KernelImageType kit = _KERNEL_IMAGE_TYPE_INVALID; + r = inspect_kernel(kernel_fd, /* filename= */ NULL, &kit); + if (r == -EBADMSG) + return log_error_errno(r, "UKI '%s' is not valid.", filename); + if (r < 0) + return log_error_errno(r, "Failed to determine kernel image type of '%s': %m", filename); + if (kit != KERNEL_IMAGE_TYPE_UKI) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Image '%s' is not a UKI.", filename); + + return 0; +} + +static int link_context_from_cmdline(LinkContext *ret, const char *kernel) { + int r; + + assert(ret); + assert(kernel); + + _cleanup_(link_context_done) LinkContext b = LINK_CONTEXT_NULL; + r = link_context_from_cmdline_common(&b); + if (r < 0) + return r; + + r = path_extract_filename(kernel, &b.kernel_filename); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", kernel); + if (!efi_loader_entry_resource_filename_valid(b.kernel_filename)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Kernel '%s' is not suitable for reference in a boot menu entry.", kernel); + b.kernel_fd = xopenat_full(AT_FDCWD, kernel, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID); + if (b.kernel_fd < 0) + return log_error_errno(b.kernel_fd, "Failed to open kernel path '%s': %m", kernel); + + r = validate_kernel(b.kernel_fd, kernel); + if (r < 0) + return r; + + r = link_context_add_cmdline_extras(&b); + if (r < 0) + return r; + + r = link_context_acquire_dollar_boot(&b); + if (r < 0) + return r; + + *ret = TAKE_GENERIC(b, LinkContext, LINK_CONTEXT_NULL); + return 0; +} + +static int link_context_load_etc_machine_id(LinkContext *c) { + int r; + + assert(c); + + r = id128_get_machine_at(c->root_fd, &c->machine_id); + if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) /* Not set or empty */ + return 0; + if (r < 0) + return log_error_errno(r, "Failed to get machine-id: %m"); + + log_debug("Loaded machine ID %s from '%s/etc/machine-id'.", SD_ID128_TO_STRING(c->machine_id), strempty(c->root)); + return 0; +} + +static int link_context_pick_entry_token(LinkContext *c) { + int r; + + assert(c); + + r = link_context_load_etc_machine_id(c); + if (r < 0) + return r; + + const char *e = secure_getenv("KERNEL_INSTALL_CONF_ROOT"); + r = boot_entry_token_ensure_at( + e ? XAT_FDROOT : c->root_fd, + e, + c->machine_id, + /* machine_id_is_random= */ false, + &c->entry_token_type, + &c->entry_token); + if (r < 0) + return r; + + log_debug("Using entry token: %s", c->entry_token); + return 0; +} + +static int begin_copy_file( + int source_fd, /* Either the source fd is specified, or the 'data' below, not both */ + const struct iovec *data, + const char *filename, + int target_dir_fd, + int *ret_tmpfile_fd, + char **ret_tmpfile_filename) { + + int r; + + assert(filename); + assert(target_dir_fd >= 0); + assert(ret_tmpfile_fd); + assert(ret_tmpfile_filename); + + if (faccessat(target_dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '%s' exists already: %m", filename); + } else { + log_info("'%s' already in place, not copying.", filename); + + *ret_tmpfile_fd = -EBADF; + *ret_tmpfile_filename = NULL; + return 0; + } + + _cleanup_free_ char *t = NULL; + _cleanup_close_ int write_fd = open_tmpfile_linkable_at(target_dir_fd, filename, O_WRONLY|O_CLOEXEC, &t); + if (write_fd < 0) + return log_error_errno(write_fd, "Failed to create '%s': %m", filename); + + CLEANUP_TMPFILE_AT(target_dir_fd, t); + + if (source_fd >= 0) { + 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 into '%s': %m", filename); + + (void) copy_times(source_fd, write_fd, /* flags= */ 0); + } else if (iovec_is_set(data)) { + r = loop_write(write_fd, data->iov_base, data->iov_len); + if (r < 0) + return log_error_errno(r, "Failed to write data into '%s': %m", filename); + } + + (void) fchmod(write_fd, 0644); + + *ret_tmpfile_fd = TAKE_FD(write_fd); + *ret_tmpfile_filename = TAKE_PTR(t); + + return 1; +} + +static int begin_write_entry_file( + LinkContext *c, + unsigned profile_nr, + const char *osrelease_text, + const char *profile_text, + Profile *ret) { + + int r; + + assert(c); + assert(osrelease_text); + assert(ret); + + assert(c->entry_token); + assert(c->kernel_filename); + assert(c->loader_entries_dir_fd >= 0); + + _cleanup_free_ char *good_name = NULL, *good_sort_key = NULL, *os_version_id = NULL, *image_version = NULL; + r = bootspec_extract_osrelease( + osrelease_text, + /* These three fields are used by systemd-stub for showing entries + sorting them */ + &good_name, /* human readable */ + /* ret_good_version= */ NULL, + &good_sort_key, + /* These four fields are the raw fields provided in os-release */ + /* ret_os_id= */ NULL, + &os_version_id, + /* ret_image_id= */ NULL, + &image_version); + if (r < 0) + return log_error_errno(r, "Failed to extract name/version/sort-key from os-release data from unified kernel image, refusing."); + + assert(good_name); /* This one is the only field guaranteed to be defined once the above succeeds */ + + _cleanup_free_ char *profile_id = NULL, *profile_title = NULL; + if (profile_text) { + r = parse_env_data( + profile_text, /* size= */ SIZE_MAX, + ".profile", + "ID", &profile_id, + "TITLE", &profile_title); + if (r < 0) + return log_error_errno(r, "Failed to parse profile data from unified kernel image: %m"); + } + + const char *version = c->entry_version ?: image_version ?: os_version_id; + + _cleanup_free_ char *filename = NULL; + r = boot_entry_make_commit_filename( + c->entry_token, + c->entry_commit, + version, + profile_nr, + c->tries_left, + &filename); + if (r < 0) + return log_error_errno(r, "Failed to generate filename for entry file: %m"); + + if (faccessat(c->loader_entries_dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '%s' exists: %m", filename); + } else + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Boot menu entry '%s' exists already, refusing.", filename); + + log_info("Writing new boot menu entry '%s/loader/entries/%s' for profile %u.", c->dollar_boot_path, filename, profile_nr); + + _cleanup_free_ char *t = NULL; + _cleanup_close_ int write_fd = open_tmpfile_linkable_at(c->loader_entries_dir_fd, filename, O_WRONLY|O_CLOEXEC, &t); + if (write_fd < 0) + return log_error_errno(write_fd, "Failed to create '%s': %m", filename); + + CLEANUP_TMPFILE_AT(c->loader_entries_dir_fd, t); + + _cleanup_free_ char *_title = NULL; + const char *title; + if (profile_title || profile_id) { + _title = strjoin(c->entry_title ?: good_name, " (", profile_title ?: profile_id, ")"); + if (!_title) + return log_oom(); + + title = _title; + } else if (profile_nr > 0) { + _title = asprintf_safe("%s (Profile #%u)", c->entry_title ?: good_name, profile_nr); + if (!_title) + return log_oom(); + + title = _title; + } else + title = c->entry_title ?: good_name; + + /* Do some validation that this will result in a valid type #1 entry before we write this out */ + if (string_has_cc(title, /* ok= */ NULL) || !utf8_is_valid(title)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to generate valid title for new commit: %s", title); + if (string_has_cc(c->kernel_filename, /* ok= */ NULL) || !utf8_is_valid(c->kernel_filename)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UKI filename is not suitable for inclusion in new commit: %s", c->kernel_filename); + + _cleanup_free_ char *text = NULL; + if (asprintf(&text, + "title %s\n" + "uki /%s/%s\n" + "version %" PRIu64 "%s%s\n", + title, + c->entry_token, c->kernel_filename, + c->entry_commit, isempty(version) ? "" : ".", strempty(version)) < 0) + return log_oom(); + + if (good_sort_key && strextendf(&text, "sort-key %s\n", good_sort_key) < 0) + return log_oom(); + + if (profile_nr > 0 && strextendf(&text, "profile %u\n", profile_nr) < 0) + return log_oom(); + + if (!sd_id128_is_null(c->machine_id) && strextendf(&text, "machine-id " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(c->machine_id)) < 0) + return log_oom(); + + FOREACH_ARRAY(x, c->extra, c->n_extra) { + if (string_has_cc(x->filename, /* ok= */ NULL) || !utf8_is_valid(x->filename)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extra filename is not suitable for inclusion in new commit: %s", x->filename); + + if (strextendf(&text, + "extra /%s/%s\n", + c->entry_token, + x->filename) < 0) + return log_oom(); + } + + r = loop_write(write_fd, text, /* nbytes= */ SIZE_MAX); + if (r < 0) + return log_error_errno(r, "Failed to write entry file: %m"); + + *ret = (Profile) { + .entry_filename = TAKE_PTR(filename), + .entry_temp_filename = TAKE_PTR(t), + .entry_temp_fd = TAKE_FD(write_fd), + }; + + return 0; +} + +static int finalize_file( + const char *filename, + int target_dir_fd, + int tmpfile_fd, + const char *tmpfile_filename) { + + int r; + + assert(filename); + assert(target_dir_fd >= 0); + + if (tmpfile_fd < 0) /* If the file already existed, we don't move anything into place. */ + return 0; + + r = link_tmpfile_at(tmpfile_fd, target_dir_fd, tmpfile_filename, filename, LINK_TMPFILE_REPLACE|LINK_TMPFILE_SYNC); + if (r < 0) + return log_error_errno(r, "Failed to move from '%s' into place: %m", filename); + + log_info("Installed '%s' into place.", filename); + return 1; +} + +static int link_context_pick_entry_commit(LinkContext *c) { + int r; + + assert(c); + assert(c->loader_entries_dir_fd >= 0); + assert(c->entry_token); + + /* Already have a commit nr? */ + if (c->entry_commit != 0) + return 0; + + _cleanup_close_ int opened_fd = fd_reopen(c->loader_entries_dir_fd, O_DIRECTORY|O_CLOEXEC); + if (opened_fd < 0) + return log_error_errno(opened_fd, "Failed to reopen loader entries dir: %m"); + + _cleanup_free_ DirectoryEntries *dentries = NULL; + r = readdir_all(opened_fd, RECURSE_DIR_IGNORE_DOT, &dentries); + if (r < 0) + return log_error_errno(r, "Failed to read loader entries directory: %m"); + + uint64_t m = 0; /* largest commit number seen */ + FOREACH_ARRAY(i, dentries->entries, dentries->n_entries) { + const struct dirent *de = *i; + + /* We look for files named -commit_[.][.p].conf */ + + if (!dirent_is_file(de)) + continue; + + if (!efi_loader_entry_name_valid(de->d_name)) + continue; + + _cleanup_free_ char *et = NULL; + uint64_t ec; + r = boot_entry_parse_commit_filename(de->d_name, &et, &ec); + if (r < 0) { + log_debug_errno(r, "Cannot extract entry token/commit number from '%s', ignoring.", de->d_name); + continue; + } + + if (!streq(c->entry_token, et)) + continue; + + log_debug("Found existing commit %" PRIu64 ".", ec); + if (ec > m) + m = ec; + } + + assert(m < UINT64_MAX); + uint64_t next = m + 1; + + if (!entry_commit_valid(next)) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many commits already in place, refusing."); + + log_debug("Picking commit %" PRIu64 " for new commit.", next); + c->entry_commit = next; + return 0; +} + +static int clean_temporary_files(int fd) { + int r; + + assert(fd >= 0); + + /* Before we create any new files let's clear any possible left-overs from a previous run. We look + * specifically for all temporary files whose name starts with .# because that's what we create, via + * open_tmpfile_linkable_at(). + * + * Ideally, this would not be necessary because O_TMPFILE would ensure that files are not + * materialized before they are fully written. However, vfat currently does not support O_TMPFILE, + * hence we need to clean things up manually. */ + + _cleanup_close_ int dfd = fd_reopen(fd, O_CLOEXEC|O_DIRECTORY); + if (dfd < 0) + return log_error_errno(dfd, "Failed to open directory: %m"); + + _cleanup_free_ DirectoryEntries *de = NULL; + r = readdir_all(dfd, RECURSE_DIR_ENSURE_TYPE, &de); + if (r < 0) + return log_error_errno(r, "Failed to enumerate contents of directory: %m"); + + FOREACH_ARRAY(i, de->entries, de->n_entries) { + struct dirent *e = *i; + + if (e->d_type != DT_REG) + continue; + + if (!startswith_no_case(e->d_name, ".#")) + continue; + + if (unlinkat(dfd, e->d_name, /* flags= */ 0) < 0 && errno != ENOENT) + log_warning_errno(errno, "Failed to remove temporary file '%s', ignoring: %m", e->d_name); + } + + return 0; +} + +static int link_context_unlink_oldest(LinkContext *c) { + int r; + + assert(c); + + /* We only load the entries from the partition we want to make space on (!) */ + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; + r = boot_config_load_and_select( + &config, + c->root, + c->dollar_boot_source == BOOT_ENTRY_ESP ? c->dollar_boot_path : NULL, + /* esp_devid= */ 0, + c->dollar_boot_source == BOOT_ENTRY_XBOOTLDR ? c->dollar_boot_path : NULL, + /* xbootldr_devid= */ 0); + if (r < 0) + return r; + + _cleanup_(strv_freep) char **ids = NULL; + r = boot_config_find_oldest_commit( + &config, + c->entry_token, + &ids); + if (r == -ENXIO) + return log_error_errno(r, "No suitable boot menu entry to delete found."); + if (r == -EBUSY) + return log_error_errno(r, "Refusing to remove currently booted boot menu entry."); + if (r < 0) + return log_error_errno(r, "Failed to find suitable oldest boot menu entry: %m"); + + _cleanup_(hashmap_freep) Hashmap *known_files = NULL; + r = boot_config_count_known_files(&config, c->dollar_boot_source, &known_files); + if (r < 0) + return r; + + int ret = 0; + STRV_FOREACH(id, ids) { + const BootEntry *entry = boot_config_find_entry(&config, *id); + if (!entry) + continue; + + RET_GATHER(ret, boot_entry_unlink(entry, c->dollar_boot_path, c->dollar_boot_fd, known_files, /* dry_run= */ false)); + } + + if (ret < 0) + return ret; + + return 1; +} + +static int verify_keep_free(LinkContext *c) { + int r; + + assert(c); + + if (c->keep_free == 0) + return 0; + + uint64_t f; + r = vfs_free_bytes(ASSERT_FD(c->dollar_boot_fd), &f); + if (r < 0) + return log_error_errno(r, "Failed to statvfs() the $BOOT partition: %m"); + + if (f < c->keep_free) + return log_error_errno( + SYNTHETIC_ERRNO(EDQUOT), + "Not installing boot menu entry, free space after installation of %s would be below configured keep free size %s.", + FORMAT_BYTES(f), FORMAT_BYTES(c->keep_free)); + + return 0; +} + +static int run_link_now(LinkContext *c) { + int r; + + assert(c); + assert(c->dollar_boot_fd >= 0); + + _cleanup_free_ char *j = path_join(empty_to_root(c->root), c->dollar_boot_path); + if (!j) + return log_oom(); + + if (c->loader_entries_dir_fd < 0) { + r = chaseat(/* root_fd= */ c->dollar_boot_fd, + /* dir_fd= */ c->dollar_boot_fd, + "loader/entries", + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + /* ret_path= */ NULL, + &c->loader_entries_dir_fd); + if (r < 0) + return log_error_errno(r, "Failed to pin '/loader/entries' directory below '%s': %m", j); + } + + /* Remove any left-overs from an earlier run before we write new stuff */ + (void) clean_temporary_files(c->loader_entries_dir_fd); + + r = link_context_pick_entry_commit(c); + if (r < 0) + return r; + + log_info("Will create commit %" PRIu64 ".", c->entry_commit); + + if (c->entry_token_dir_fd < 0) { + r = chaseat(/* root_fd= */ c->dollar_boot_fd, + /* dir_fd= */ c->dollar_boot_fd, + c->entry_token, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + /* ret_path= */ NULL, + &c->entry_token_dir_fd); + if (r < 0) + return log_error_errno(r, "Failed to pin '/%s' directory below '%s': %m", c->entry_token, j); + } + + /* As above */ + (void) clean_temporary_files(c->entry_token_dir_fd); + + /* Synchronize everything to disk before we verify the disk space, to ensure the counters are + * accurate (some file systems delay accurate counters) */ + (void) syncfs(c->dollar_boot_fd); + + /* Before we start copying things, let's see if there's even a remote chance to get this copied + * in. Note that we do not try to be overly smart here, i.e. we do not try to calculate how much + * extra space we'll need here. Doing that is not trivial since after all the same resources can be + * referenced by multiple entries, which makes copying them multiple times unnecessary. */ + r = verify_keep_free(c); + if (r < 0) + return r; + + for (unsigned p = 0; p < UNIFIED_PROFILES_MAX; p++) { + _cleanup_free_ char *osrelease = NULL, *profile = NULL; + r = pe_find_uki_sections(c->kernel_fd, j, p, &osrelease, &profile, /* ret_cmdline= */ NULL); + if (r < 0) + return r; + if (r == 0) /* this profile does not exist, we are done */ + break; + + if (!GREEDY_REALLOC(c->profiles, c->n_profiles+1)) + return log_oom(); + + r = begin_write_entry_file( + c, + p, + osrelease, + profile, + c->profiles + c->n_profiles); + if (r < 0) + return r; + + c->n_profiles++; + } + + if (c->n_profiles == 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "UKI with no valid profile, refusing."); + + r = begin_copy_file( + c->kernel_fd, + /* data= */ NULL, + c->kernel_filename, + c->entry_token_dir_fd, + &c->kernel_temp_fd, + &c->kernel_temp_filename); + if (r < 0) + return r; + + FOREACH_ARRAY(x, c->extra, c->n_extra) { + r = begin_copy_file( + x->source_fd, + &x->data, + x->filename, + c->entry_token_dir_fd, + &x->temp_fd, + &x->temp_filename); + if (r < 0) + return r; + } + + /* We copied all files into place, but they are not materialized yet. Let's ensure the data hits the + * disk before we proceed */ + (void) syncfs(c->dollar_boot_fd); + + /* Before we materialize things, let's ensure the space to keep free is not taken */ + r = verify_keep_free(c); + if (r < 0) + return r; + + /* We successfully managed to put all resources we need into the $BOOT partition. Now, let's + * "materialize" them by linking them into the file system. Before this point we'd get rid of every + * file we created on error again. But from now on we switch modes: what we manage to move into place + * we leave in place even on error. These are not lost resources after all, the GC logic implemented + * by "bootctl cleanup" will take care of removing things again if necessary. */ + + r = finalize_file( + c->kernel_filename, + c->entry_token_dir_fd, + c->kernel_temp_fd, + c->kernel_temp_filename); + if (r < 0) + return r; + + c->kernel_temp_fd = safe_close(c->kernel_temp_fd); + c->kernel_temp_filename = mfree(c->kernel_temp_filename); + + FOREACH_ARRAY(x, c->extra, c->n_extra) { + r = finalize_file( + x->filename, + c->entry_token_dir_fd, + x->temp_fd, + x->temp_filename); + if (r < 0) + return r; + + x->temp_fd = safe_close(x->temp_fd); + x->temp_filename = mfree(x->temp_filename); + } + + /* Finally, after all our resources are in place, also materialize the menu entry files themselves */ + FOREACH_ARRAY(profile, c->profiles, c->n_profiles) { + r = finalize_file( + profile->entry_filename, + c->loader_entries_dir_fd, + profile->entry_temp_fd, + profile->entry_temp_filename); + if (r < 0) + return r; + + profile->entry_temp_fd = safe_close(profile->entry_temp_fd); + profile->entry_temp_filename = mfree(profile->entry_temp_filename); + + _cleanup_free_ char *stripped = NULL; + r = boot_filename_extract_tries( + profile->entry_filename, + &stripped, + /* ret_tries_left= */ NULL, + /* ret_tries_done= */ NULL); + if (r < 0) + return log_warning_errno(r, "Failed to extract tries counters from id '%s'", profile->entry_filename); + + if (strv_consume(&c->linked_ids, TAKE_PTR(stripped)) < 0) + return log_oom(); + } + + (void) syncfs(c->dollar_boot_fd); + return 0; +} + +static int run_link(LinkContext *c) { + int r; + + assert(c); + assert(c->dollar_boot_path); + assert(c->dollar_boot_fd >= 0); + + if (c->keep_free == UINT64_MAX) + c->keep_free = KEEP_FREE_BYTES_DEFAULT; + + r = link_context_pick_entry_token(c); + if (r < 0) + return r; + + unsigned n_removals = 0; + for (;;) { + r = run_link_now(c); + if (r < 0) { + if (!ERRNO_IS_NEG_DISK_SPACE(r)) + return r; + } else + break; + + log_notice("Attempt to link entry failed due to exhausted disk space, trying to remove oldest boot menu entry."); + + link_context_unlink_temporary(c); + link_context_clear_profiles(c); + + if (link_context_unlink_oldest(c) <= 0) { + log_warning("Attempted to make space on $BOOT, but this failed, attempt to link entry failed."); + return r; /* propagate original error */ + } + + /* Close entry token dir here, quite possible the unlinking above might have removed it too, in case it was empty */ + c->entry_token_dir_fd = safe_close(c->entry_token_dir_fd); + + log_info("Removing oldest boot menu entry succeeded, will retry to create boot loader menu entry."); + n_removals++; + } + + _cleanup_free_ char *j = strv_join(c->linked_ids, "', '"); + if (!j) + return log_oom(); + + if (n_removals > 0) + log_info("Successfully installed boot loader entries '%s', after removing %u old entries.", j, n_removals); + else + log_info("Successfully installed boot loader entries '%s'.", j); + + return 0; +} + +int verb_link(int argc, char *argv[], uintptr_t data, void *userdata) { + int r; + + assert(argc == 2); + + _cleanup_free_ char *x = NULL; + r = parse_path_argument(argv[1], /* suppress_root= */ false, &x); + if (r < 0) + return r; + + _cleanup_(link_context_done) LinkContext c = LINK_CONTEXT_NULL; + r = link_context_from_cmdline(&c, x); + if (r < 0) + return r; + + return run_link(&c); +} + +/* Directories (relative to the operative root) below which "bootctl link-auto" looks for a staged UKI and + * extra resources, in decreasing priority order. /var/lib/systemd/uki/ is where systemd-sysupdate stages + * downloaded resources; the others follow the usual configuration search path precedence, with + * /var/lib/ slotted in just below the volatile locations and above the vendor trees. */ +static const char* const uki_auto_dirs[] = { + "etc/systemd/uki", + "run/systemd/uki", + "var/lib/systemd/uki", + "usr/local/lib/systemd/uki", + "usr/lib/systemd/uki", +}; + +static const PickFilter pick_filter_uki = { + .type_mask = UINT32_C(1) << DT_REG, + .architecture = _ARCHITECTURE_INVALID, + .suffix = ".efi", +}; + +/* The recognized suffixes of extra resources we pick up alongside the UKI. Each may appear as a plain file + * (e.g. foo.sysext.raw) or as a versioned directory (e.g. foo.sysext.raw.v/) resolved via the vpick + * mechanism. */ +static const char* const auto_link_extra_suffixes[] = { + ".sysext.raw", + ".confext.raw", + ".cred", +}; + +static int link_context_add_extra(LinkContext *c, int dir_fd, const char *path, const char *filename) { + assert(c); + assert(dir_fd >= 0); + assert(path); + assert(filename); + + if (!efi_loader_entry_resource_filename_valid(filename)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extra file '%s' is not suitable for reference in a boot menu entry, refusing.", filename); + + _cleanup_close_ int fd = xopenat_full(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY, XO_REGULAR, /* mode= */ MODE_INVALID); + if (fd < 0) + return log_error_errno(fd, "Failed to open extra file '%s': %m", path); + + _cleanup_free_ char *fn = strdup(filename); + if (!fn) + return log_oom(); + + if (!GREEDY_REALLOC(c->extra, c->n_extra + 1)) + return log_oom(); + + c->extra[c->n_extra++] = (ExtraFile) { + .source_fd = TAKE_FD(fd), + .filename = TAKE_PTR(fn), + .temp_fd = -EBADF, + }; + + return 0; +} + +/* Adds the extra resources found in /extras.d/ to the link context. The 'seen' set tracks the + * logical resource names already added by a higher-priority directory, so the same resource appearing in + * multiple directories is taken from the highest-priority one only. */ +static int link_context_add_extras(LinkContext *c, int uki_dir_fd, Set **seen) { + int r; + + assert(c); + assert(uki_dir_fd >= 0); + assert(seen); + + _cleanup_close_ int extras_dir_fd = chase_and_openat( + c->root_fd, + uki_dir_fd, + "extras.d", + /* chase_flags= */ 0, + O_DIRECTORY|O_CLOEXEC, + /* ret_path= */ NULL); + if (extras_dir_fd == -ENOENT) + return 0; + if (extras_dir_fd < 0) + return log_error_errno(extras_dir_fd, "Failed to open extras.d/ directory: %m"); + + _cleanup_free_ DirectoryEntries *des = NULL; + r = readdir_all(extras_dir_fd, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_SORT, &des); + if (r < 0) + return log_error_errno(r, "Failed to enumerate extras.d/ directory: %m"); + + FOREACH_ARRAY(i, des->entries, des->n_entries) { + struct dirent *de = *i; + + if (!IN_SET(de->d_type, DT_DIR, DT_REG)) + continue; + + FOREACH_ELEMENT(suffix, auto_link_extra_suffixes) { + _cleanup_free_ char *dot_v = strjoin(*suffix, ".v"); + if (!dot_v) + return log_oom(); + + bool versioned; + _cleanup_free_ char *key = NULL; + if (endswith(de->d_name, *suffix)) { + if (de->d_type != DT_REG) + continue; + + versioned = false; + key = strdup(de->d_name); + + } else if (endswith(de->d_name, dot_v)) { + if (de->d_type != DT_DIR) + continue; + + /* The dedup key is the directory entry name with any trailing ".v" stripped, so that + * a plain file and a versioned directory of the same resource collapse, and a + * higher-priority directory wins. */ + versioned = true; + key = strndup(de->d_name, strlen(de->d_name) - strlen(".v")); + } else + continue; + if (!key) + return log_oom(); + + if (set_contains(*seen, key)) + break; + + if (versioned) { + /* A versioned directory: resolve the best version via vpick, relative to the + * extras directory fd. We deliberately do not honour boot-counting suffixes + * here. */ + _cleanup_(pick_result_done) PickResult pick = PICK_RESULT_NULL; + const PickFilter filter = { + .type_mask = UINT32_C(1) << DT_REG, + .architecture = _ARCHITECTURE_INVALID, + .suffix = *suffix, + }; + + r = path_pick(/* root_path= */ NULL, + /* root_fd= */ c->root_fd, + /* dir_fd= */ extras_dir_fd, + de->d_name, + &filter, + /* n_filters= */ 1, + /* flags= */ 0, + &pick); + if (r == -ENOENT || r == 0) { + log_debug("No matching entry in versioned directory '%s', skipping.", de->d_name); + break; + } + if (r < 0) + return log_error_errno(r, "Failed to pick version in '%s': %m", de->d_name); + + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(pick.path, &fn); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from '%s': %m", pick.path); + + r = link_context_add_extra(c, extras_dir_fd, pick.path, fn); + } else + /* A plain file. */ + r = link_context_add_extra(c, extras_dir_fd, de->d_name, de->d_name); + if (r < 0) + return r; + + r = set_ensure_consume(seen, &string_hash_ops_free, TAKE_PTR(key)); + if (r < 0) + return log_oom(); + + break; + } + } + + return 0; +} + +/* Looks for a UKI (kernel.efi, or the best version in kernel.efi.v/) in the directory referenced by + * uki_dir_fd and, if found, fills c->kernel_fd and c->kernel_filename. Returns 1 if found, 0 if not, and a + * negative errno on error. We deliberately do not honour boot-counting suffixes here. */ +static int link_context_find_kernel(LinkContext *c, int uki_dir_fd) { + int r; + + assert(c); + assert(uki_dir_fd >= 0); + + _cleanup_free_ char *filename = NULL; + _cleanup_close_ int kernel_fd = xopenat_full( + uki_dir_fd, "kernel.efi", + O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_NOFOLLOW, + XO_REGULAR, + /* mode= */ MODE_INVALID); + if (kernel_fd == -ENOENT) { + _cleanup_(pick_result_done) PickResult pick = PICK_RESULT_NULL; + r = path_pick(/* root_path= */ NULL, + /* root_fd= */ c->root_fd, + /* dir_fd= */ uki_dir_fd, + "kernel.efi.v", + &pick_filter_uki, + /* n_filters= */ 1, + /* flags= */ 0, + &pick); + if (r == -ENOENT || r == 0) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to pick UKI version in 'kernel.efi.v': %m"); + + r = path_extract_filename(pick.path, &filename); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from '%s': %m", pick.path); + + kernel_fd = fd_reopen(pick.fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (kernel_fd < 0) + return log_error_errno(kernel_fd, "Failed to open UKI '%s': %m", pick.path); + + r = fd_verify_regular(kernel_fd); + if (r < 0) + return log_error_errno(r, "UKI '%s' is not a regular file: %m", pick.path); + + } else if (kernel_fd < 0) + return log_error_errno(kernel_fd, "Failed to open 'kernel.efi': %m"); + else { + filename = strdup("kernel.efi"); + if (!filename) + return log_oom(); + } + + if (!efi_loader_entry_resource_filename_valid(filename)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UKI '%s' is not suitable for reference in a boot menu entry.", filename); + + r = validate_kernel(kernel_fd, filename); + if (r < 0) + return r; + + c->kernel_fd = TAKE_FD(kernel_fd); + c->kernel_filename = TAKE_PTR(filename); + return 1; +} + +/* Discovers the staged UKI and extra resources below the uki_auto_dirs[] (relative to the already-pinned + * c->root_fd) and fills c->kernel_fd, c->kernel_filename and c->extra[] accordingly. The directories are + * searched in priority order: the UKI is taken from the first directory that has one, and extra resources + * are combined across all directories with higher-priority directories winning on conflicts. Returns 1 if a + * UKI was found, 0 if there is nothing to link, and a negative errno on error. */ +static int link_context_discover_resources(LinkContext *c) { + _cleanup_set_free_ Set *seen = NULL; + int r; + + assert(c); + + FOREACH_ELEMENT(dir, uki_auto_dirs) { + _cleanup_close_ int uki_dir_fd = chase_and_openat( + /* root_fd= */ c->root_fd, + /* dir_fd= */ c->root_fd, + *dir, + CHASE_MUST_BE_DIRECTORY, + O_RDONLY|O_DIRECTORY|O_CLOEXEC, + /* ret_path= */ NULL); + if (uki_dir_fd == -ENOENT) + continue; + if (uki_dir_fd < 0) + return log_error_errno(uki_dir_fd, "Failed to open '%s': %m", *dir); + + /* Take the UKI from the first (highest-priority) directory that has one. */ + if (c->kernel_fd < 0) { + r = link_context_find_kernel(c, uki_dir_fd); + if (r < 0) + return r; + } + + r = link_context_add_extras(c, uki_dir_fd, &seen); + if (r < 0) + return r; + } + + if (c->kernel_fd < 0) { + log_debug("No UKI found in any of the search directories, nothing to link."); + return 0; + } + + return 1; +} + +int verb_link_auto(int argc, char *argv[], uintptr_t data, void *userdata) { + int r; + + assert(argc == 1); + + _cleanup_(link_context_done) LinkContext c = LINK_CONTEXT_NULL; + r = link_context_from_cmdline_common(&c); + if (r < 0) + return r; + + r = link_context_discover_resources(&c); + if (r < 0) + return r; + if (r == 0) { + log_info("No UKI staged for linking, nothing to do."); + return 0; + } + + /* In addition to the auto-discovered resources, also honour any files passed via --extra=. */ + r = link_context_add_cmdline_extras(&c); + if (r < 0) + return r; + + r = link_context_acquire_dollar_boot(&c); + if (r < 0) + return r; + + return run_link(&c); +} + +static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_boot_entry_token_type, BootEntryTokenType, boot_entry_token_type_from_string); + +typedef struct LinkParameters { + LinkContext context; + unsigned root_fd_index; + unsigned kernel_fd_index; + sd_varlink *link; +} LinkParameters; + +static void link_parameters_done(LinkParameters *p) { + assert(p); + + link_context_done(&p->context); +} + +typedef struct ExtraParameters { + ExtraFile extra_file; + unsigned fd_index; +} ExtraParameters; + +static void extra_parameters_done(ExtraParameters *p) { + assert(p); + + extra_file_done(&p->extra_file); +} + +static int json_dispatch_loader_entry_resource_filename(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + char **n = ASSERT_PTR(userdata); + const char *filename; + int r; + + assert(variant); + + r = json_dispatch_const_filename(name, variant, flags, &filename); + if (r < 0) + return r; + + if (filename && !efi_loader_entry_resource_filename_valid(filename)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid boot entry resource filename.", strna(name)); + + if (free_and_strdup(n, filename) < 0) + return json_log_oom(variant, flags); + + return 0; +} + +static int dispatch_extras(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) { + LinkParameters *c = ASSERT_PTR(userdata); + int r; + + if (!sd_json_variant_is_array(v)) + return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); + + sd_json_variant *i; + JSON_VARIANT_ARRAY_FOREACH(i, v) { + _cleanup_(extra_parameters_done) ExtraParameters xp = { + .extra_file = EXTRA_FILE_NULL, + .fd_index = UINT_MAX, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "filename", SD_JSON_VARIANT_STRING, json_dispatch_loader_entry_resource_filename, offsetof(ExtraParameters, extra_file.filename), SD_JSON_MANDATORY }, + { "fileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(ExtraParameters, fd_index), 0 }, + { "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(ExtraParameters, extra_file.data), 0 }, + {}, + }; + + r = sd_json_dispatch(i, dispatch_table, /* flags= */ 0, &xp); + if (r < 0) + return r; + + if (iovec_is_set(&xp.extra_file.data) == (xp.fd_index != UINT_MAX)) + return sd_varlink_error_invalid_parameter_name(c->link, name); + if (xp.fd_index != UINT_MAX) { + xp.extra_file.source_fd = sd_varlink_peek_dup_fd(c->link, xp.fd_index); + if (xp.extra_file.source_fd < 0) + return log_debug_errno(xp.extra_file.source_fd, "Failed to acquire extra fd from Varlink: %m"); + + r = fd_verify_safe_flags(xp.extra_file.source_fd); + if (r < 0) + return sd_varlink_error_invalid_parameter_name(c->link, name); + + r = fd_verify_regular(xp.extra_file.source_fd); + if (r < 0) + return log_debug_errno(r, "Failed to validate that the extra file is a regular file descriptor: %m"); + } + + if (!GREEDY_REALLOC(c->context.extra, c->context.n_extra+1)) + return log_oom(); + + c->context.extra[c->context.n_extra++] = TAKE_GENERIC(xp.extra_file, ExtraFile, EXTRA_FILE_NULL); + } + + return 0; +} + +/* Resolves the root_fd of the LinkContext from the dispatched Link/LinkAuto parameters, fills in the + * boot entry token type default, and validates the entry title/version/commit fields. Returns 0 on success, + * non-zero (Varlink error or negative errno) otherwise. */ +static int vl_link_prepare(sd_varlink *link, LinkParameters *p) { + int r; + + assert(link); + assert(p); + + if (p->root_fd_index != UINT_MAX) { + p->context.root_fd = sd_varlink_peek_dup_fd(link, p->root_fd_index); + if (p->context.root_fd < 0) + return log_debug_errno(p->context.root_fd, "Failed to acquire root fd from Varlink: %m"); + + r = fd_verify_safe_flags_full(p->context.root_fd, O_DIRECTORY); + if (r < 0) + return sd_varlink_error_invalid_parameter_name(link, "rootFileDescriptor"); + + r = fd_verify_directory(p->context.root_fd); + if (r < 0) + return log_debug_errno(r, "Specified file descriptor does not refer to a directory: %m"); + + if (!p->context.root) { + r = fd_get_path(p->context.root_fd, &p->context.root); + if (r < 0) + return log_debug_errno(r, "Failed to get path of file descriptor: %m"); + + if (empty_or_root(p->context.root)) + p->context.root = mfree(p->context.root); + } + } else if (p->context.root) { + p->context.root_fd = open(p->context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (p->context.root_fd < 0) + return log_debug_errno(errno, "Failed to open '%s': %m", p->context.root); + } else + p->context.root_fd = XAT_FDROOT; + + if (p->context.entry_token_type < 0) + p->context.entry_token_type = BOOT_ENTRY_TOKEN_AUTO; + + if (p->context.entry_title && !efi_loader_entry_title_valid(p->context.entry_title)) + return sd_varlink_error_invalid_parameter_name(link, "entryTitle"); + + if (p->context.entry_version && !version_is_valid_versionspec(p->context.entry_version)) + return sd_varlink_error_invalid_parameter_name(link, "entryVersion"); + + if (p->context.entry_commit != 0 && !entry_commit_valid(p->context.entry_commit)) + return sd_varlink_error_invalid_parameter_name(link, "entryCommit"); + + return 0; +} + +/* Resolves $BOOT, runs the link operation and sends the reply. Expects the kernel and extra files to already + * be set up in the context. If with_ids is true the reply carries the list of created entry IDs (as expected + * by Link()/LinkAuto()), otherwise an empty reply is sent (as expected by + * io.systemd.SysUpdate.Notify.OnCompletedUpdate(), which declares no output fields). */ +static int vl_link_finish(sd_varlink *link, LinkParameters *p, bool with_ids) { + int r; + + assert(link); + assert(p); + + r = find_xbootldr_and_warn_at( + p->context.root_fd, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &p->context.dollar_boot_path, + &p->context.dollar_boot_fd); + if (r < 0) { + if (r != -ENOKEY) + return r; + + /* No XBOOTLDR found, let's look for ESP then. */ + + r = find_esp_and_warn_at( + p->context.root_fd, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &p->context.dollar_boot_path, + &p->context.dollar_boot_fd); + if (r == -ENOKEY) + return sd_varlink_error(link, "io.systemd.BootControl.NoDollarBootFound", NULL); + if (r < 0) + return r; + + p->context.dollar_boot_source = BOOT_ENTRY_ESP; + } else + p->context.dollar_boot_source = BOOT_ENTRY_XBOOTLDR; + + r = run_link(&p->context); + if (r == -EUNATCH) /* no boot entry token is set */ + return sd_varlink_error(link, "io.systemd.BootControl.BootEntryTokenUnavailable", NULL); + if (r < 0) + return r; + + if (with_ids) + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRV("ids", p->context.linked_ids)); + + return sd_varlink_reply(link, NULL); +} + +int vl_method_link( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(link); + + _cleanup_(link_parameters_done) LinkParameters p = { + .context = LINK_CONTEXT_NULL, + .root_fd_index = UINT_MAX, + .kernel_fd_index = UINT_MAX, + .link = link, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "rootFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, root_fd_index), 0 }, + { "rootDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, voffsetof(p, context.root), 0 }, + { "bootEntryTokenType", SD_JSON_VARIANT_STRING, json_dispatch_boot_entry_token_type, voffsetof(p, context.entry_token_type), 0 }, + { "entryTitle", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, voffsetof(p, context.entry_title), 0 }, + { "entryVersion", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, voffsetof(p, context.entry_version), 0 }, + { "entryCommit", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_uint64, voffsetof(p, context.entry_commit), 0 }, + { "kernelFilename", SD_JSON_VARIANT_STRING, json_dispatch_loader_entry_resource_filename, voffsetof(p, context.kernel_filename), SD_JSON_MANDATORY }, + { "kernelFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, kernel_fd_index), SD_JSON_MANDATORY }, + { "extraFiles", SD_JSON_VARIANT_ARRAY, dispatch_extras, 0, 0 }, + { "triesLeft", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, context.tries_left), 0 }, + { "keepFree", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, context.keep_free), 0 }, + {}, + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + r = varlink_check_privileged_peer(link); + if (r < 0) + return r; + + r = vl_link_prepare(link, &p); + if (r != 0) + return r; + + p.context.kernel_fd = sd_varlink_peek_dup_fd(link, p.kernel_fd_index); + if (p.context.kernel_fd < 0) + return log_debug_errno(p.context.kernel_fd, "Failed to acquire kernel fd from Varlink: %m"); + + r = fd_verify_safe_flags(p.context.kernel_fd); + if (r < 0) + return sd_varlink_error_invalid_parameter_name(link, "kernelFileDescriptor"); + r = fd_verify_regular(p.context.kernel_fd); + if (r < 0) + return log_debug_errno(r, "Failed to validate that kernel image file is a regular file descriptor: %m"); + + /* Refuse non-UKIs for now. */ + KernelImageType kit = _KERNEL_IMAGE_TYPE_INVALID; + r = inspect_kernel(p.context.kernel_fd, /* filename= */ NULL, &kit); + if (r == -EBADMSG) + return sd_varlink_error(link, "io.systemd.BootControl.InvalidKernelImage", NULL); + if (r < 0) + return r; + if (kit != KERNEL_IMAGE_TYPE_UKI) + return sd_varlink_error(link, "io.systemd.BootControl.InvalidKernelImage", NULL); + + return vl_link_finish(link, &p, /* with_ids= */ true); +} + +int vl_method_link_auto( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(link); + + _cleanup_(link_parameters_done) LinkParameters p = { + .context = LINK_CONTEXT_NULL, + .root_fd_index = UINT_MAX, + .kernel_fd_index = UINT_MAX, + .link = link, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "rootFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, root_fd_index), 0 }, + { "rootDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, voffsetof(p, context.root), 0 }, + { "bootEntryTokenType", SD_JSON_VARIANT_STRING, json_dispatch_boot_entry_token_type, voffsetof(p, context.entry_token_type), 0 }, + { "entryTitle", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, voffsetof(p, context.entry_title), 0 }, + { "entryVersion", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, voffsetof(p, context.entry_version), 0 }, + { "entryCommit", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_uint64, voffsetof(p, context.entry_commit), 0 }, + { "triesLeft", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, context.tries_left), 0 }, + { "keepFree", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, context.keep_free), 0 }, + {}, + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + r = varlink_check_privileged_peer(link); + if (r < 0) + return r; + + r = vl_link_prepare(link, &p); + if (r != 0) + return r; + + r = link_context_discover_resources(&p.context); + if (r < 0) + return r; + if (r == 0) /* Nothing staged for linking. */ + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRV("ids", STRV_EMPTY)); + + return vl_link_finish(link, &p, /* with_ids= */ true); +} + +int vl_method_on_completed_update( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(link); + + /* Only honour update notifications if they come from root */ + r = varlink_check_privileged_peer(link); + if (r < 0) + return r; + + /* Triggered by systemd-sysupdate after an update completed. We deliberately ignore all parameters + * (we don't even dispatch them) and behave like LinkAuto() with default parameters: discover the + * staged UKI and extra resources and link them in. Contrary to LinkAuto() we reply without any + * parameters, matching the io.systemd.SysUpdate.Notify.OnCompletedUpdate() signature. */ + + _cleanup_(link_parameters_done) LinkParameters p = { + .context = LINK_CONTEXT_NULL, + .root_fd_index = UINT_MAX, + .kernel_fd_index = UINT_MAX, + .link = link, + }; + + r = vl_link_prepare(link, &p); + if (r != 0) + return r; + + r = link_context_discover_resources(&p.context); + if (r < 0) + return r; + if (r == 0) /* Nothing staged for linking. */ + return sd_varlink_reply(link, NULL); + + return vl_link_finish(link, &p, /* with_ids= */ false); +} diff --git a/src/bootctl/bootctl-link.h b/src/bootctl/bootctl-link.h new file mode 100644 index 0000000000000..fb04c31511410 --- /dev/null +++ b/src/bootctl/bootctl-link.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +int verb_link(int argc, char *argv[], uintptr_t data, void *userdata); +int verb_link_auto(int argc, char *argv[], uintptr_t data, void *userdata); + +int vl_method_link(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_link_auto(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_on_completed_update(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..42ee7286a03ae 100644 --- a/src/bootctl/bootctl-reboot-to-firmware.c +++ b/src/bootctl/bootctl-reboot-to-firmware.c @@ -11,8 +11,9 @@ #include "errno-util.h" #include "log.h" #include "parse-util.h" +#include "varlink-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]); @@ -61,6 +62,10 @@ int vl_method_set_reboot_to_firmware(sd_varlink *link, sd_json_variant *paramete if (r != 0) return r; + r = varlink_check_privileged_peer(link); + if (r < 0) + return r; + r = efi_set_reboot_to_firmware(b); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return sd_varlink_error(link, "io.systemd.BootControl.RebootToFirmwareNotSupported", NULL); 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..8ac244474420c 100644 --- a/src/bootctl/bootctl-status.c +++ b/src/bootctl/bootctl-status.c @@ -17,12 +17,12 @@ #include "efivars.h" #include "errno-util.h" #include "fd-util.h" +#include "locale-setup.h" #include "log.h" #include "pager.h" #include "pretty-print.h" #include "string-util.h" #include "tpm2-util.h" -#include "varlink-util.h" static int status_entries( const BootConfig *config, @@ -217,13 +217,15 @@ static int enumerate_binaries( return log_oom(); LOG_SET_PREFIX(filename); - fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC); + fd = RET_NERRNO(openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC)); + if (fd == -ENOENT) + continue; if (fd < 0) - return log_error_errno(errno, "Failed to open file for reading: %m"); + return log_error_errno(fd, "Failed to open file '%s' for reading: %m", de->d_name); r = get_file_version(fd, &v); if (r < 0 && r != -ESRCH) - return r; + return log_error_errno(r, "Failed to get file version of '%s': %m", de->d_name); if (*previous) { /* Let's output the previous entry now, since now we know that there will be * one more, and can draw the tree glyph properly. */ @@ -237,7 +239,7 @@ static int enumerate_binaries( /* Do not output this entry immediately, but store what should be printed in a state * variable, because we only will know the tree glyph to print (branch or final edge) once we * read one more entry */ - if (r == -ESRCH) /* No systemd-owned file but still interesting to print */ + if (r == -ESRCH) /* Not systemd-owned file but still interesting to print */ r = asprintf(previous, "%s%s/%s/%s/%s", ansi_grey(), esp_path, ansi_normal(), path, de->d_name); else /* if (r >= 0) */ @@ -321,12 +323,12 @@ static int efi_get_variable_path_and_warn(const char *variable, char **ret) { static void print_yes_no_line(bool first, bool good, const char *name) { printf("%s%s %s\n", - first ? " Features: " : " ", + first ? " Features: " : " ", COLOR_MARK_BOOL(good), 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 +337,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 +356,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) { @@ -386,25 +390,27 @@ int verb_status(int argc, char *argv[], void *userdata) { uint64_t flag; const char *name; } loader_flags[] = { - { EFI_LOADER_FEATURE_BOOT_COUNTING, "Boot counting" }, - { EFI_LOADER_FEATURE_CONFIG_TIMEOUT, "Menu timeout control" }, - { EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT, "One-shot menu timeout control" }, - { EFI_LOADER_FEATURE_ENTRY_DEFAULT, "Default entry control" }, - { EFI_LOADER_FEATURE_ENTRY_ONESHOT, "One-shot entry control" }, - { EFI_LOADER_FEATURE_XBOOTLDR, "Support for XBOOTLDR partition" }, - { EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, - { EFI_LOADER_FEATURE_LOAD_DRIVER, "Load drop-in drivers" }, - { EFI_LOADER_FEATURE_SORT_KEY, "Support Type #1 sort-key field" }, - { EFI_LOADER_FEATURE_SAVED_ENTRY, "Support @saved pseudo-entry" }, - { EFI_LOADER_FEATURE_DEVICETREE, "Support Type #1 devicetree field" }, - { EFI_LOADER_FEATURE_SECUREBOOT_ENROLL, "Enroll SecureBoot keys" }, - { EFI_LOADER_FEATURE_RETAIN_SHIM, "Retain SHIM protocols" }, - { EFI_LOADER_FEATURE_MENU_DISABLE, "Menu can be disabled" }, - { EFI_LOADER_FEATURE_MULTI_PROFILE_UKI, "Multi-Profile UKIs are supported" }, - { EFI_LOADER_FEATURE_REPORT_URL, "Loader reports network boot URL" }, - { EFI_LOADER_FEATURE_TYPE1_UKI, "Support Type #1 uki field" }, - { EFI_LOADER_FEATURE_TYPE1_UKI_URL, "Support Type #1 uki-url field" }, - { EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS, "Loader reports active TPM2 PCR banks" }, + { EFI_LOADER_FEATURE_BOOT_COUNTING, "Boot counting" }, + { EFI_LOADER_FEATURE_CONFIG_TIMEOUT, "Menu timeout control" }, + { EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT, "One-shot menu timeout control" }, + { EFI_LOADER_FEATURE_ENTRY_DEFAULT, "Default entry control" }, + { EFI_LOADER_FEATURE_ENTRY_ONESHOT, "One-shot entry control" }, + { EFI_LOADER_FEATURE_XBOOTLDR, "Support for XBOOTLDR partition" }, + { EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, + { EFI_LOADER_FEATURE_LOAD_DRIVER, "Load drop-in drivers" }, + { EFI_LOADER_FEATURE_SORT_KEY, "Support Type #1 sort-key field" }, + { EFI_LOADER_FEATURE_SAVED_ENTRY, "Support @saved pseudo-entry" }, + { EFI_LOADER_FEATURE_DEVICETREE, "Support Type #1 devicetree field" }, + { EFI_LOADER_FEATURE_SECUREBOOT_ENROLL, "Enroll SecureBoot keys" }, + { EFI_LOADER_FEATURE_RETAIN_SHIM, "Retain SHIM protocols" }, + { EFI_LOADER_FEATURE_MENU_DISABLE, "Menu can be disabled" }, + { EFI_LOADER_FEATURE_MULTI_PROFILE_UKI, "Multi-Profile UKIs are supported" }, + { EFI_LOADER_FEATURE_REPORT_URL, "Loader reports network boot URL" }, + { EFI_LOADER_FEATURE_TYPE1_UKI, "Support Type #1 uki field" }, + { EFI_LOADER_FEATURE_TYPE1_UKI_URL, "Support Type #1 uki-url field" }, + { EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS, "Loader reports active TPM2 PCR banks" }, + { EFI_LOADER_FEATURE_KEYBOARD_LAYOUT, "Loader reports firmware keyboard layout" }, + { EFI_LOADER_FEATURE_SMBIOS_MEASURED, "Loader measures SMBIOS information" }, }; static const struct { uint64_t flag; @@ -422,10 +428,11 @@ int verb_status(int argc, char *argv[], void *userdata) { { EFI_STUB_FEATURE_CMDLINE_SMBIOS, "Pick up .cmdline from SMBIOS Type 11" }, { EFI_STUB_FEATURE_DEVICETREE_ADDONS, "Pick up .dtb from addons" }, { EFI_STUB_FEATURE_MULTI_PROFILE_UKI, "Stub understands profile selector" }, + { EFI_STUB_FEATURE_SMBIOS_MEASURED, "Stub measures SMBIOS information" }, }; _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL, *stub_path = NULL, *current_entry = NULL, *oneshot_entry = NULL, *preferred_entry = NULL, *default_entry = NULL, *sysfail_entry = NULL, - *sysfail_reason = NULL; + *sysfail_reason = NULL, *keyboard_layout = NULL; uint64_t loader_features = 0, stub_features = 0; int have; @@ -443,6 +450,7 @@ int verb_status(int argc, char *argv[], void *userdata) { (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &default_entry); (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntrySysFail"), &sysfail_entry); (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderSysFailReason"), &sysfail_reason); + (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderKeyboardLayout"), &keyboard_layout); SecureBootMode secure = efi_get_secure_boot_mode(); printf("%sSystem:%s\n", ansi_underline(), ansi_normal()); @@ -477,6 +485,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()); @@ -488,11 +506,23 @@ int verb_status(int argc, char *argv[], void *userdata) { errno = -k; printf(" Boot into FW: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal()); } + + _cleanup_free_ char *lang = NULL; + k = locale_lang_from_efi(&lang, /* flags= */ 0); + if (k > 0) + printf(" Platform Lang: %s\n", lang); + else if (k == 0) + printf(" Platform Lang: n/a\n"); + else { + errno = -k; + printf(" Platform Lang: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal()); + } + printf("\n"); if (loader) { printf("%sCurrent Boot Loader:%s\n", ansi_underline(), ansi_normal()); - printf(" Product: %s%s%s\n", ansi_highlight(), loader, ansi_normal()); + printf(" Product: %s%s%s\n", ansi_highlight(), loader, ansi_normal()); for (size_t i = 0; i < ELEMENTSOF(loader_flags); i++) print_yes_no_line(i == 0, FLAGS_SET(loader_features, loader_flags[i].flag), loader_flags[i].name); @@ -510,38 +540,42 @@ int verb_status(int argc, char *argv[], void *userdata) { SD_ID128_FORMAT_VAL(loader_partition_uuid), SD_ID128_FORMAT_VAL(esp_uuid)); - printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", + printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(loader_partition_uuid)); } else if (loader_path) - printf(" Partition: n/a\n"); + printf(" Partition: n/a\n"); if (loader_path) - printf(" Loader: %s%s%s/%s%s\n", + printf(" Loader: %s%s%s/%s%s\n", glyph(GLYPH_TREE_RIGHT), ansi_grey(), arg_esp_path, ansi_normal(), loader_path); if (loader_url) - printf(" Net Boot URL: %s\n", loader_url); + printf(" Net Boot URL: %s\n", loader_url); + + if (FLAGS_SET(loader_features, EFI_LOADER_FEATURE_KEYBOARD_LAYOUT)) + printf("Keyboard Layout: %s\n", + keyboard_layout ?: "n/a (not reported by firmware)"); if (sysfail_entry) - printf("SysFail Reason: %s\n", sysfail_reason); + printf(" SysFail Reason: %s\n", sysfail_reason); if (current_entry) - printf(" Current Entry: %s\n", current_entry); + printf(" Current Entry: %s\n", current_entry); if (preferred_entry) - printf(" Preferred Entry: %s\n", preferred_entry); + printf("Preferred Entry: %s\n", preferred_entry); if (default_entry) - printf(" Default Entry: %s\n", default_entry); + printf(" Default Entry: %s\n", default_entry); if (oneshot_entry && !streq_ptr(oneshot_entry, default_entry)) - printf(" OneShot Entry: %s\n", oneshot_entry); + printf(" OneShot Entry: %s\n", oneshot_entry); if (sysfail_entry) - printf(" SysFail Entry: %s\n", sysfail_entry); + printf(" SysFail Entry: %s\n", sysfail_entry); printf("\n"); } if (stub) { printf("%sCurrent Stub:%s\n", ansi_underline(), ansi_normal()); - printf(" Product: %s%s%s\n", ansi_highlight(), stub, ansi_normal()); + printf(" Product: %s%s%s\n", ansi_highlight(), stub, ansi_normal()); for (size_t i = 0; i < ELEMENTSOF(stub_flags); i++) print_yes_no_line(i == 0, FLAGS_SET(stub_features, stub_flags[i].flag), stub_flags[i].name); @@ -562,16 +596,16 @@ int verb_status(int argc, char *argv[], void *userdata) { SD_ID128_FORMAT_VAL(esp_uuid), SD_ID128_FORMAT_VAL(xbootldr_uuid)); - printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", + printf(" Partition: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(stub_partition_uuid)); } else if (stub_path) - printf(" Partition: n/a\n"); + printf(" Partition: n/a\n"); if (stub_path) - printf(" Stub: %s%s\n", glyph(GLYPH_TREE_RIGHT), strna(stub_path)); + printf(" Stub: %s%s\n", glyph(GLYPH_TREE_RIGHT), strna(stub_path)); if (stub_url) - printf(" Net Boot URL: %s\n", stub_url); + printf(" Net Boot URL: %s\n", stub_url); printf("\n"); } @@ -606,9 +640,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 +657,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 +669,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 +719,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 +732,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 +740,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..44954d4fa331d 100644 --- a/src/bootctl/bootctl-unlink.c +++ b/src/bootctl/bootctl-unlink.c @@ -3,20 +3,71 @@ #include #include +#include "sd-id128.h" +#include "sd-json.h" +#include "sd-varlink.h" + #include "alloc-util.h" +#include "boot-entry.h" #include "bootctl.h" #include "bootctl-unlink.h" #include "bootspec.h" #include "bootspec-util.h" #include "chase.h" +#include "efi-loader.h" #include "errno-util.h" +#include "fd-util.h" +#include "find-esp.h" #include "hashmap.h" +#include "id128-util.h" +#include "json-util.h" #include "log.h" #include "path-util.h" +#include "stat-util.h" +#include "string-util.h" #include "strv.h" +#include "varlink-util.h" + +typedef struct UnlinkContext { + char *root; + int root_fd; + + sd_id128_t machine_id; + BootEntryTokenType entry_token_type; + char *entry_token; + + char *esp_path; + dev_t esp_devid; + int esp_fd; + + char *xbootldr_path; + dev_t xbootldr_devid; + int xbootldr_fd; +} UnlinkContext; + +#define UNLINK_CONTEXT_NULL \ + (UnlinkContext) { \ + .root_fd = -EBADF, \ + .entry_token_type = _BOOT_ENTRY_TOKEN_TYPE_INVALID, \ + .esp_fd = -EBADF, \ + .xbootldr_fd = -EBADF, \ + } + +static void unlink_context_done(UnlinkContext *c) { + assert(c); + + c->root = mfree(c->root); + c->root_fd = safe_close(c->root_fd); + + c->entry_token = mfree(c->entry_token); + + c->esp_path = mfree(c->esp_path); + c->esp_fd = safe_close(c->esp_fd); + c->xbootldr_path = mfree(c->xbootldr_path); + c->xbootldr_fd = safe_close(c->xbootldr_fd); +} static int ref_file(Hashmap **known_files, const char *fn, int increment) { - char *k = NULL; int n, r; assert(known_files); @@ -26,13 +77,15 @@ static int ref_file(Hashmap **known_files, const char *fn, int increment) { if (!fn) return 0; + char *k = NULL; n = PTR_TO_INT(hashmap_get2(*known_files, fn, (void**)&k)); - n += increment; + if (!INC_SAFE(&n, increment)) + return -EOVERFLOW; assert(n >= 0); if (n == 0) { - (void) hashmap_remove(*known_files, fn); + (void) hashmap_remove(*known_files, k); free(k); } else if (!k) { _cleanup_free_ char *t = NULL; @@ -40,12 +93,14 @@ static int ref_file(Hashmap **known_files, const char *fn, int increment) { t = strdup(fn); if (!t) return -ENOMEM; + r = hashmap_ensure_put(known_files, &path_hash_ops_free, t, INT_TO_PTR(n)); if (r < 0) return r; + TAKE_PTR(t); } else { - r = hashmap_update(*known_files, fn, INT_TO_PTR(n)); + r = hashmap_update(*known_files, k, INT_TO_PTR(n)); if (r < 0) return r; } @@ -53,192 +108,560 @@ static int ref_file(Hashmap **known_files, const char *fn, int increment) { return n; } -int boot_config_count_known_files( - const BootConfig *config, - const char* root, - Hashmap **ret_known_files) { +static int boot_entry_ref_files( + const BootEntry *e, + Hashmap **known_files, + int increment) { - _cleanup_hashmap_free_ Hashmap *known_files = NULL; int r; - assert(config); - assert(ret_known_files); + assert(e); + assert(known_files); + assert(increment != 0); - for (size_t i = 0; i < config->n_entries; i++) { - const BootEntry *e = config->entries + i; + r = ref_file(known_files, e->kernel, increment); + if (r < 0) + return r; - if (!path_equal(e->root, root)) - continue; + r = ref_file(known_files, e->efi, increment); + if (r < 0) + return r; - r = ref_file(&known_files, e->kernel, +1); + r = ref_file(known_files, e->uki, increment); + if (r < 0) + return r; + + STRV_FOREACH(s, e->initrd) { + r = ref_file(known_files, *s, increment); if (r < 0) return r; - r = ref_file(&known_files, e->efi, +1); + } + + r = ref_file(known_files, e->device_tree, increment); + if (r < 0) + return r; + + STRV_FOREACH(s, e->device_tree_overlay) { + r = ref_file(known_files, *s, increment); if (r < 0) return r; - r = ref_file(&known_files, e->uki, +1); + } + + FOREACH_ARRAY(x, e->local_extras.items, e->local_extras.n_items) { + r = ref_file(known_files, x->location, increment); if (r < 0) return r; - STRV_FOREACH(s, e->initrd) { - r = ref_file(&known_files, *s, +1); - if (r < 0) - return r; - } - r = ref_file(&known_files, e->device_tree, +1); + } + + return 0; +} + +int boot_config_count_known_files( + const BootConfig *config, + BootEntrySource source, + Hashmap **ret_known_files) { + + int r; + + assert(config); + assert(ret_known_files); + + _cleanup_hashmap_free_ Hashmap *known_files = NULL; + FOREACH_ARRAY(e, config->entries, config->n_entries) { + + if (e->source != source) + continue; + + r = boot_entry_ref_files(e, &known_files, +1); if (r < 0) return r; - STRV_FOREACH(s, e->device_tree_overlay) { - r = ref_file(&known_files, *s, +1); - if (r < 0) - return r; - } } *ret_known_files = TAKE_PTR(known_files); - return 0; } -static void deref_unlink_file(Hashmap **known_files, const char *fn, const char *root) { - _cleanup_free_ char *path = NULL; +static int unref_unlink_file( + Hashmap **known_files, + const char *root, + int root_fd, + const char *path, + bool dry_run) { + int r; assert(known_files); /* just gracefully ignore this. This way the caller doesn't have to verify whether the bootloader entry is relevant */ - if (!fn || !root) - return; + if (root_fd < 0 || !root || !path) + return 0; - r = ref_file(known_files, fn, -1); + r = ref_file(known_files, path, -1); if (r < 0) - return (void) log_warning_errno(r, "Failed to deref \"%s\", ignoring: %m", fn); + return log_error_errno(r, "Failed to unref '%s': %m", path); if (r > 0) - return; + return 0; - if (arg_dry_run) { - r = chase_and_access(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, F_OK, &path); - if (r < 0) - log_info_errno(r, "Unable to determine whether \"%s\" exists, ignoring: %m", fn); - else - log_info("Would remove \"%s\"", path); - return; + if (dry_run) { + _cleanup_free_ char *resolved = NULL; + r = chase_and_accessat( + /* root_fd= */ root_fd, + /* dir_fd= */ root_fd, + path, + CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS|CHASE_MUST_BE_REGULAR, + F_OK, + &resolved); + if (r < 0) { + log_warning_errno(r, "Unable to determine whether '%s' exists, ignoring: %m", path); + return 0; + } + + log_info("Would remove '%s'", resolved); + return 1; } - r = chase_and_unlink(fn, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, 0, &path); - if (r >= 0) - log_info("Removed \"%s\"", path); - else if (r != -ENOENT) - return (void) log_warning_errno(r, "Failed to remove \"%s\", ignoring: %m", fn); - - _cleanup_free_ char *d = NULL; - if (path_extract_directory(fn, &d) >= 0 && !path_equal(d, "/")) { - r = chase_and_unlink(d, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, AT_REMOVEDIR, NULL); - if (r < 0 && !IN_SET(r, -ENOTEMPTY, -ENOENT)) - log_warning_errno(r, "Failed to remove directory \"%s\", ignoring: %m", d); + _cleanup_free_ char *resolved = NULL; + r = chase_and_unlinkat( + /* root_fd= */ root_fd, + /* dir_fd= */ root_fd, + path, + CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, + /* unlink_flags= */ 0, + &resolved); + if (r == -ENOENT) + log_debug("Resource '%s' is already removed, skipping.", path); + else if (r < 0) { + log_warning_errno(r, "Failed to remove '%s', ignoring: %m", path); + return 0; + } else + log_info("Removed '%s'", resolved); + + _cleanup_free_ char *parent = NULL; + r = path_extract_directory(path, &parent); + if (r < 0) + log_debug_errno(r, "Failed to extract parent directory of '%s', ignoring.", path); + else { + _cleanup_free_ char *resolved_parent = NULL; + r = chase_and_unlinkat( + /* root_fd= */ root_fd, + /* dir_fd= */ root_fd, + parent, + CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, + AT_REMOVEDIR, + &resolved_parent); + if (IN_SET(r, -ENOTEMPTY, -ENOENT)) + log_debug_errno(r, "Failed to remove directory '%s', ignoring: %m", parent); + else if (r < 0) + log_warning_errno(r, "Failed to remove directory '%s', ignoring: %m", parent); + else + log_info("Removed '%s'.", resolved_parent); } + + return 1; } -static int boot_config_find_in(const BootConfig *config, const char *root, const char *id) { +static ssize_t boot_config_find_in( + const BootConfig *config, + BootEntrySource source, + const char *id) { + assert(config); + assert(source >= 0); + assert(source < _BOOT_ENTRY_SOURCE_MAX); - if (!root || !id) + if (!id) return -ENOENT; for (size_t i = 0; i < config->n_entries; i++) - if (path_equal(config->entries[i].root, root) && + if (config->entries[i].source == source && fnmatch(id, config->entries[i].id, FNM_CASEFOLD) == 0) - return i; + return (ssize_t) i; return -ENOENT; } -static int unlink_entry(const BootConfig *config, const char *root, const char *id) { - _cleanup_hashmap_free_ Hashmap *known_files = NULL; - const BootEntry *e = NULL; - int r; - - assert(config); - - r = boot_config_count_known_files(config, root, &known_files); - if (r < 0) - return log_error_errno(r, "Failed to count files in %s: %m", root); - - r = boot_config_find_in(config, root, id); - if (r < 0) - return 0; /* There is nothing to remove. */ +int boot_entry_unlink( + const BootEntry *e, + const char *root, + int root_fd, + Hashmap *known_files, + bool dry_run) { - if (r == config->default_entry) - log_warning("%s is the default boot entry", id); - if (r == config->selected_entry) - log_warning("%s is the selected boot entry", id); + int r; - e = &config->entries[r]; + assert(e); + assert(root_fd >= 0); - deref_unlink_file(&known_files, e->kernel, e->root); - deref_unlink_file(&known_files, e->efi, e->root); - deref_unlink_file(&known_files, e->uki, e->root); + (void) unref_unlink_file(&known_files, root, root_fd, e->kernel, dry_run); + (void) unref_unlink_file(&known_files, root, root_fd, e->efi, dry_run); + (void) unref_unlink_file(&known_files, root, root_fd, e->uki, dry_run); STRV_FOREACH(s, e->initrd) - deref_unlink_file(&known_files, *s, e->root); - deref_unlink_file(&known_files, e->device_tree, e->root); + (void) unref_unlink_file(&known_files, root, root_fd, *s, dry_run); + (void) unref_unlink_file(&known_files, root, root_fd, e->device_tree, dry_run); STRV_FOREACH(s, e->device_tree_overlay) - deref_unlink_file(&known_files, *s, e->root); + (void) unref_unlink_file(&known_files, root, root_fd, *s, dry_run); + FOREACH_ARRAY(x, e->local_extras.items, e->local_extras.n_items) + (void) unref_unlink_file(&known_files, root, root_fd, x->location, dry_run); - if (arg_dry_run) + if (dry_run) log_info("Would remove \"%s\"", e->path); else { - r = chase_and_unlink(e->path, root, CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, 0, NULL); + const char *p = path_startswith(e->path, root); + if (!p) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File '%s' is not inside root '%s', refusing.", e->path, root); + + _cleanup_free_ char *resolved = NULL; + r = chase_and_unlinkat( + /* root_fd= */ root_fd, + /* dir_fd= */ root_fd, + p, + CHASE_PROHIBIT_SYMLINKS|CHASE_TRIGGER_AUTOFS, + /* unlink_flags= */ 0, + &resolved); if (r == -ENOENT) return 0; /* Already removed? */ if (r < 0) return log_error_errno(r, "Failed to remove \"%s\": %m", e->path); - log_info("Removed %s", e->path); + log_info("Removed '%s'.", resolved); } return 0; } -int verb_unlink(int argc, char *argv[], void *userdata) { - dev_t esp_devid = 0, xbootldr_devid = 0; +static int unlink_entry( + const BootConfig *config, + const char *root, + int root_fd, + BootEntrySource source, + char **ids, + bool dry_run) { + + size_t n_removed = 0; + int r; + + assert(config); + + _cleanup_hashmap_free_ Hashmap *known_files = NULL; + r = boot_config_count_known_files(config, source, &known_files); + if (r < 0) + return log_error_errno(r, "Failed to count files in %s: %m", root); + + int ret = 0; + STRV_FOREACH(id, ids) { + log_debug("Unlinking '%s'", *id); + ssize_t idx = boot_config_find_in(config, source, *id); + if (idx < 0) + continue; /* There is nothing to remove. */ + + log_debug("Index %zi", idx); + + if (idx == config->default_entry) + log_warning("%s is the default boot entry", *id); + if (idx == config->selected_entry) + log_warning("%s is the selected boot entry", *id); + + r = boot_entry_unlink(config->entries + idx, root, root_fd, known_files, dry_run); + if (r < 0) + RET_GATHER(ret, r); + else + n_removed++; + } + + if (n_removed == 0) + log_info("No matching entries found or removed."); + + return ret; +} + +static int unlink_context_from_cmdline(UnlinkContext *ret) { int r; + assert(ret); + + _cleanup_(unlink_context_done) UnlinkContext b = UNLINK_CONTEXT_NULL; + b.entry_token_type = arg_entry_token_type; + + if (strdup_to(&b.entry_token, arg_entry_token) < 0) + return log_oom(); + + if (arg_root) { + b.root_fd = open(arg_root, O_CLOEXEC|O_DIRECTORY|O_PATH); + if (b.root_fd < 0) + return log_error_errno(errno, "Failed to open root directory '%s': %m", arg_root); + + if (strdup_to(&b.root, arg_root) < 0) + return log_oom(); + } else + b.root_fd = XAT_FDROOT; + r = acquire_esp(/* unprivileged_mode= */ false, /* graceful= */ false, + &b.esp_fd, /* 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; + &b.esp_devid); + if (r < 0 && r != -ENOKEY) + return r; /* About all other errors acquire_esp() logs on its own */ + if (r > 0) { + if (arg_root) { + const char *e = path_startswith(arg_esp_path, arg_root); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "ESP path '%s' not below specified root '%s', refusing.", arg_esp_path, arg_root); + + r = strdup_to(&b.esp_path, e); + } else + r = strdup_to(&b.esp_path, arg_esp_path); + if (r < 0) + return log_oom(); + } r = acquire_xbootldr( /* unprivileged_mode= */ false, + &b.xbootldr_fd, /* ret_uuid= */ NULL, - &xbootldr_devid); - if (r == -EACCES) - return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); - if (r < 0) + &b.xbootldr_devid); + if (r < 0 && r != -ENOKEY) return r; + if (r > 0) { + if (arg_root) { + const char *e = path_startswith(arg_xbootldr_path, arg_root); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "XBOOTLDR path '%s' not below specified root '%s', refusing.", arg_xbootldr_path, arg_root); + + r = strdup_to(&b.xbootldr_path, e); + } else + r = strdup_to(&b.xbootldr_path, arg_xbootldr_path); + if (r < 0) + return log_oom(); + } + + /* Only if we found neither ESP nor XBOOTLDR let's fail. */ + if (!b.xbootldr_path && !b.esp_path) + return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Neither ESP nor XBOOTLDR found, refusing."); + + *ret = TAKE_GENERIC(b, UnlinkContext, UNLINK_CONTEXT_NULL); + return 0; +} + +static int run_unlink( + UnlinkContext *c, + char **_ids, + bool dry_run) { + + int r; + assert(c); + + _cleanup_free_ char *x = NULL, *y = NULL; + if (c->root && c->esp_path) { + x = path_join(c->root, c->esp_path); + if (!x) + return log_oom(); + } + + if (c->root && c->xbootldr_path) { + y = path_join(c->root, c->xbootldr_path); + if (!y) + return log_oom(); + } _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); + c->root, + x ?: c->esp_path, + c->esp_devid, + y ?: c->xbootldr_path, + c->xbootldr_devid); if (r < 0) return r; + _cleanup_(strv_freep) char **ids = NULL; + if (strv_isempty(_ids)) { + r = id128_get_machine_at(c->root_fd, &c->machine_id); + if (r < 0 && !ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) + return log_error_errno(r, "Failed to get machine-id: %m"); + + const char *e = secure_getenv("KERNEL_INSTALL_CONF_ROOT"); + r = boot_entry_token_ensure_at( + e ? XAT_FDROOT : c->root_fd, + e, + c->machine_id, + /* machine_id_is_random= */ false, + &c->entry_token_type, + &c->entry_token); + if (r < 0) + return r; + + r = boot_config_find_oldest_commit( + &config, + c->entry_token, + &ids); + if (r == -ENXIO) + return log_error_errno(r, "No suitable boot menu entry to delete found."); + if (r == -EBUSY) + return log_error_errno(r, "Refusing to remove currently booted boot menu entry."); + if (r < 0) + return log_error_errno(r, "Failed to find suitable oldest boot menu entry: %m"); + + STRV_FOREACH(id, ids) + log_info("Will unlink '%s'.", *id); + } else { + ids = strv_copy(_ids); + if (!ids) + return log_oom(); + } + + strv_sort_uniq(ids); + r = 0; - RET_GATHER(r, unlink_entry(&config, arg_esp_path, argv[1])); + if (c->esp_path) + RET_GATHER(r, unlink_entry(&config, x ?: c->esp_path, c->esp_fd, BOOT_ENTRY_ESP, ids, dry_run)); - if (arg_xbootldr_path && xbootldr_devid != esp_devid) - RET_GATHER(r, unlink_entry(&config, arg_xbootldr_path, argv[1])); + if (c->xbootldr_path && c->xbootldr_devid != c->esp_devid) + RET_GATHER(r, unlink_entry(&config, y ?: c->xbootldr_path, c->xbootldr_fd, BOOT_ENTRY_XBOOTLDR, ids, dry_run)); return r; } + +int verb_unlink(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + assert(argc < 3); + + if (arg_oldest != isempty(argv[1])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Exactly one of an entry ID or --oldest= must be specified."); + + const char *id = empty_to_null(argv[1]); + + _cleanup_(unlink_context_done) UnlinkContext c = UNLINK_CONTEXT_NULL; + r = unlink_context_from_cmdline(&c); + if (r < 0) + return r; + + return run_unlink(&c, STRV_MAKE(id), arg_dry_run); +} + +static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_boot_entry_token_type, BootEntryTokenType, boot_entry_token_type_from_string); + +typedef struct UnlinkParameters { + UnlinkContext context; + unsigned root_fd_index; + const char *id; + bool oldest; +} UnlinkParameters; + +static void unlink_parameters_done(UnlinkParameters *p) { + assert(p); + + unlink_context_done(&p->context); +} + +int vl_method_unlink( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(link); + + _cleanup_(unlink_parameters_done) UnlinkParameters p = { + .context = UNLINK_CONTEXT_NULL, + .root_fd_index = UINT_MAX, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "rootFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, root_fd_index), 0 }, + { "rootDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, voffsetof(p, context.root), 0 }, + { "bootEntryTokenType", SD_JSON_VARIANT_STRING, json_dispatch_boot_entry_token_type, voffsetof(p, context.entry_token_type), 0 }, + { "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, id), 0 }, + { "oldest", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, voffsetof(p, oldest), 0 }, + {}, + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + /* Only allow oldest *or* id to be set */ + if (p.oldest == !!p.id) + return sd_varlink_error_invalid_parameter_name(link, "id"); + if (p.id && !efi_loader_entry_name_valid(p.id)) + return sd_varlink_error_invalid_parameter_name(link, "id"); + + r = varlink_check_privileged_peer(link); + if (r < 0) + return r; + + if (p.root_fd_index != UINT_MAX) { + p.context.root_fd = sd_varlink_peek_dup_fd(link, p.root_fd_index); + if (p.context.root_fd < 0) + return log_debug_errno(p.context.root_fd, "Failed to acquire root fd from Varlink: %m"); + + r = fd_verify_safe_flags_full(p.context.root_fd, O_DIRECTORY); + if (r < 0) + return sd_varlink_error_invalid_parameter_name(link, "rootFileDescriptor"); + + r = fd_verify_directory(p.context.root_fd); + if (r < 0) + return log_debug_errno(r, "Specified file descriptor does not refer to a directory: %m"); + + if (!p.context.root) { + r = fd_get_path(p.context.root_fd, &p.context.root); + if (r < 0) + return log_debug_errno(r, "Failed to get path of file descriptor: %m"); + + if (empty_or_root(p.context.root)) + p.context.root = mfree(p.context.root); + } + } else if (p.context.root) { + p.context.root_fd = open(p.context.root, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (p.context.root_fd < 0) + return log_debug_errno(errno, "Failed to open '%s': %m", p.context.root); + } else + p.context.root_fd = XAT_FDROOT; + + if (p.context.entry_token_type < 0) + p.context.entry_token_type = BOOT_ENTRY_TOKEN_AUTO; + + r = find_esp_and_warn_at_full( + p.context.root_fd, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &p.context.esp_path, + &p.context.esp_fd, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid= */ NULL, + &p.context.esp_devid); + if (r < 0 && r != -ENOKEY) + return r; + r = find_xbootldr_and_warn_at_full( + p.context.root_fd, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &p.context.xbootldr_path, + &p.context.xbootldr_fd, + /* ret_uuid= */ NULL, + &p.context.xbootldr_devid); + if (r < 0 && r != -ENOKEY) + return r; + + /* Only if we found neither ESP nor XBOOTLDR let's fail. */ + if (!p.context.xbootldr_path && !p.context.esp_path) + return sd_varlink_error(link, "io.systemd.BootControl.NoDollarBootFound", NULL); + + r = run_unlink(&p.context, STRV_MAKE(p.id), /* dry_run= */ false); + if (r == -EUNATCH) /* no boot entry token is set */ + return sd_varlink_error(link, "io.systemd.BootControl.BootEntryTokenUnavailable", NULL); + if (r < 0) + return r; + + return sd_varlink_reply(link, NULL); +} diff --git a/src/bootctl/bootctl-unlink.h b/src/bootctl/bootctl-unlink.h index 5737977ae5ecf..728c775d26e8e 100644 --- a/src/bootctl/bootctl-unlink.h +++ b/src/bootctl/bootctl-unlink.h @@ -3,6 +3,10 @@ #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); +int vl_method_unlink(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); + +int boot_config_count_known_files(const BootConfig *config, BootEntrySource source, Hashmap **ret_known_files); + +int boot_entry_unlink(const BootEntry *e, const char *root, int root_fd, Hashmap *known_files, bool dry_run); diff --git a/src/bootctl/bootctl-util.c b/src/bootctl/bootctl-util.c index 9ed31372bf445..9bd85d64614d7 100644 --- a/src/bootctl/bootctl-util.c +++ b/src/bootctl/bootctl-util.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "alloc-util.h" @@ -12,7 +11,7 @@ #include "errno-util.h" #include "fileio.h" #include "log.h" -#include "stat-util.h" +#include "pe-binary.h" #include "string-util.h" #include "sync-util.h" #include "virt.h" @@ -133,61 +132,60 @@ const char* get_efi_arch(void) { return EFI_MACHINE_TYPE_NAME; } -/* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */ int get_file_version(int fd, char **ret) { - struct stat st; - char *buf; - const char *s, *e; - char *marker = NULL; int r; assert(fd >= 0); assert(ret); - /* Does not reposition file offset (as it uses mmap()) */ + /* Reads the version marker that systemd-boot/systemd-stub and friends store in their ".sdmagic" PE + * section, i.e. a string such as "#### LoaderInfo: systemd-boot 218 ####", and returns the inner + * part, e.g. "systemd-boot 218". Does not reposition the file offset (as it uses pread()). */ - if (fstat(fd, &st) < 0) - return log_error_errno(errno, "Failed to stat EFI binary: %m"); - - r = stat_verify_regular(&st); - if (r < 0) { - log_debug_errno(r, "EFI binary is not a regular file, assuming no version information: %m"); - return -ESRCH; - } + _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 == -EBADMSG) + return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "EFI binary is not a valid PE file, assuming no version information."); + if (r < 0) + return r; - if (st.st_size < 27 || file_offset_beyond_memory_size(st.st_size)) - return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), - "EFI binary size too %s: %"PRIi64, - st.st_size < 27 ? "small" : "large", st.st_size); + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + r = pe_load_sections(fd, dos_header, pe_header, §ions); + if (r == -EBADMSG) + return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "Failed to load PE section table, assuming no version information."); + if (r < 0) + return r; - buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (buf == MAP_FAILED) - return log_error_errno(errno, "Failed to mmap EFI binary: %m"); + _cleanup_free_ char *sdmagic = NULL; + r = pe_read_section_data_by_name( + fd, + pe_header, + sections, + ".sdmagic", + /* max_size= */ 4U*1024U, + (void**) &sdmagic, + /* ret_size= */ NULL); + if (IN_SET(r, -ENXIO, -EBADMSG)) + return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "EFI binary has no .sdmagic section, assuming no version information."); + if (r < 0) + return log_debug_errno(r, "Failed to read .sdmagic section of EFI binary: %m"); - s = mempmem_safe(buf, st.st_size - 8, "#### LoaderInfo: ", 17); - if (!s) { - r = log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "EFI binary has no LoaderInfo marker."); - goto finish; - } + const char *p = startswith(sdmagic, "#### LoaderInfo: "); + if (!p) + return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "EFI binary .sdmagic section lacks LoaderInfo marker."); - e = memmem_safe(s, st.st_size - (s - buf), " ####", 5); - if (!e || e - s < 3) { - r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "EFI binary has malformed LoaderInfo marker."); - goto finish; - } + const char *e = endswith(p, " ####"); + if (!e || e <= p) + return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "EFI binary has malformed LoaderInfo marker."); - marker = strndup(s, e - s); - if (!marker) { - r = log_oom(); - goto finish; - } + char *marker = strndup(p, e - p); + if (!marker) + return log_oom_debug(); log_debug("EFI binary LoaderInfo marker: \"%s\"", marker); - r = 0; - *ret = marker; -finish: - (void) munmap(buf, st.st_size); - return r; + *ret = TAKE_PTR(marker); + return 0; } int settle_entry_token(void) { diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index da4592a7240a8..6e522f01e6a98 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" @@ -10,34 +9,41 @@ #include "bootctl.h" #include "bootctl-cleanup.h" #include "bootctl-install.h" +#include "bootctl-link.h" #include "bootctl-random-seed.h" #include "bootctl-reboot-to-firmware.h" #include "bootctl-set-efivar.h" #include "bootctl-status.h" #include "bootctl-uki.h" #include "bootctl-unlink.h" +#include "bootctl-util.h" +#include "bootspec-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 "parse-util.h" #include "path-util.h" #include "pretty-print.h" #include "string-table.h" #include "string-util.h" #include "strv.h" -#include "utf8.h" #include "varlink-io.systemd.BootControl.h" +#include "varlink-io.systemd.SysUpdate.Notify.h" #include "varlink-util.h" #include "verbs.h" #include "virt.h" @@ -50,6 +56,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; @@ -77,6 +84,13 @@ char *arg_certificate_source = NULL; char *arg_private_key = NULL; KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; char *arg_private_key_source = NULL; +bool arg_oldest = false; +uint64_t arg_keep_free = KEEP_FREE_BYTES_DEFAULT; +char *arg_entry_title = NULL; +char *arg_entry_version = NULL; +uint64_t arg_entry_commit = 0; +char **arg_extras = NULL; +unsigned arg_tries_left = UINT_MAX; STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); @@ -90,6 +104,9 @@ STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); STATIC_DESTRUCTOR_REGISTER(arg_certificate_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); +STATIC_DESTRUCTOR_REGISTER(arg_entry_title, freep); +STATIC_DESTRUCTOR_REGISTER(arg_entry_version, freep); +STATIC_DESTRUCTOR_REGISTER(arg_extras, strv_freep); static const char* const install_source_table[_INSTALL_SOURCE_MAX] = { [INSTALL_SOURCE_IMAGE] = "image", @@ -99,16 +116,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 +134,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 +150,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 +195,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 +235,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 +252,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 +289,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 +299,277 @@ 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", + }; + + Table *verb_tables[ELEMENTSOF(verb_groups)] = {}; + CLEANUP_ELEMENTS(verb_tables, table_unref_array_clear); + Table *option_tables[ELEMENTSOF(option_groups)] = {}; + CLEANUP_ELEMENTS(option_tables, table_unref_array_clear); + + 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", VERB_ANY, 2, 0, + "Remove boot loader entry"); + +VERB_SCOPE(, verb_link, "link", "KERNEL", 2, 2, 0, + "Create boot loader entry for specified kernel"); - int c, r; +VERB_SCOPE_NOARG(, verb_link_auto, "link-auto", + "Create boot loader entry for the kernel and extra resources staged in /var/lib/systemd/uki/"); + +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"); + +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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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; + + 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(); - case ARG_VERSION: + 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, opts.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, opts.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(opts.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(opts.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(opts.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(opts.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", opts.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=", opts.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.", opts.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=", opts.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_COMMON_ENTRY_TOKEN: + r = parse_boot_entry_token_type(opts.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_COMMON_MAKE_ENTRY_DIRECTORY: {} + OPTION_LONG("make-machine-id-directory", "BOOL", /* help= */ NULL): /* Compatibility alias */ + if (streq(opts.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=", opts.arg, NULL); if (r < 0) return r; @@ -548,95 +577,206 @@ 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(opts.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(opts.arg, STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS)) { + _cleanup_free_ char *escaped = cescape(opts.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(opts.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(opts.arg), EFI_BOOT_OPTION_DESCRIPTION_MAX); + r = free_and_strdup_warn(&arg_efi_boot_option_description, opts.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=", opts.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=", opts.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, opts.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(opts.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, opts.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(opts.arg, + &arg_certificate_source, + &arg_certificate_source_type); if (r < 0) return r; break; - case '?': - return -EINVAL; + OPTION_LONG("oldest", "BOOL", + "Delete oldest boot menu entry"): + r = parse_boolean_argument("--oldest=", opts.arg, &arg_oldest); + if (r < 0) + return r; + + break; + + OPTION_LONG("keep-free", "BYTES", + "How much space to keep free on ESP/XBOOTLDR"): + + if (isempty(opts.arg)) + arg_keep_free = KEEP_FREE_BYTES_DEFAULT; + else { + r = parse_size(opts.arg, 1024, &arg_keep_free); + if (r < 0) + return log_error_errno(r, "Failed to parse --keep-free=: %s", opts.arg); + } + + break; + + OPTION_LONG("entry-title", "TITLE", + "Selects the entry title for the new boot menu entry"): + + if (isempty(opts.arg)) { + arg_entry_title = mfree(arg_entry_title); + break; + } + + if (!efi_loader_entry_title_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid boot menu entry title: %s", opts.arg); + + r = free_and_strdup_warn(&arg_entry_title, opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("entry-version", "VERSION", + "Selects the entry version for the new boot menu entry"): + if (isempty(opts.arg)) { + arg_entry_version = mfree(arg_entry_version); + break; + } + + if (!version_is_valid_versionspec(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid boot menu entry version: %s", opts.arg); - default: - assert_not_reached(); + r = free_and_strdup_warn(&arg_entry_version, opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("entry-commit", "NR", + "Selects the entry commit version for the new boot menu entry"): { + if (isempty(opts.arg)) { + arg_entry_commit = 0; + break; + } + + uint64_t n; + r = safe_atou64(opts.arg, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse --entry-commit= parameter: %s", opts.arg); + if (!entry_commit_valid(n)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid entry commit number."); + + arg_entry_commit = n; + break; + } + + OPTION('X', "extra", "PATH", + "Pass extra resource (confext, sysext, credential) to the invoked UKI of the boot menu entry"): { + + if (isempty(opts.arg)) { + arg_extras = strv_free(arg_extras); + break; + } + + _cleanup_free_ char *x = NULL; + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &x); + if (r < 0) + return r; + + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(x, &fn); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from '%s': %m", x); + if (!efi_loader_entry_resource_filename_valid(fn)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extra filename '%s' is not suitable for reference in a boot menu entry.", fn); + + r = strv_consume(&arg_extras, TAKE_PTR(x)); + if (r < 0) + return log_oom(); + + strv_uniq(arg_extras); + break; } - if (!!arg_print_esp_path + !!arg_print_dollar_boot_path + (arg_print_root_device > 0) + arg_print_loader_path + arg_print_stub_path > 1) + OPTION_LONG("tries-left", "NR", + "Set boot menu entries tries-left counter to the specified value"): { + if (isempty(opts.arg)) { + arg_tries_left = UINT_MAX; + break; + } + + unsigned u; + r = safe_atou(opts.arg, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse tries left counter: %s", opts.arg); + if (u >= UINT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Tries left counter too large, refusing: %u", u); + + arg_tries_left = u; + break; + }} + + char **args = option_parser_get_args(&opts); + + 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 +784,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 +807,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; @@ -705,21 +819,30 @@ static int vl_server(void) { r = varlink_server_new( &varlink_server, - SD_VARLINK_SERVER_ROOT_ONLY|SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT, + SD_VARLINK_SERVER_ROOT_ONLY | + SD_VARLINK_SERVER_MYSELF_ONLY | + SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT, /* userdata= */ NULL); 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_BootControl); + r = sd_varlink_server_add_interface_many( + varlink_server, + &vl_interface_io_systemd_BootControl, + &vl_interface_io_systemd_SysUpdate_Notify); if (r < 0) - return log_error_errno(r, "Failed to add Varlink interface: %m"); + return log_error_errno(r, "Failed to add Varlink interfaces: %m"); r = sd_varlink_server_bind_method_many( varlink_server, - "io.systemd.BootControl.ListBootEntries", vl_method_list_boot_entries, - "io.systemd.BootControl.SetRebootToFirmware", vl_method_set_reboot_to_firmware, - "io.systemd.BootControl.GetRebootToFirmware", vl_method_get_reboot_to_firmware, - "io.systemd.BootControl.Install", vl_method_install); + "io.systemd.BootControl.ListBootEntries", vl_method_list_boot_entries, + "io.systemd.BootControl.SetRebootToFirmware", vl_method_set_reboot_to_firmware, + "io.systemd.BootControl.GetRebootToFirmware", vl_method_get_reboot_to_firmware, + "io.systemd.BootControl.Install", vl_method_install, + "io.systemd.BootControl.Link", vl_method_link, + "io.systemd.BootControl.LinkAuto", vl_method_link_auto, + "io.systemd.BootControl.Unlink", vl_method_unlink, + "io.systemd.SysUpdate.Notify.OnCompletedUpdate", vl_method_on_completed_update); if (r < 0) return log_error_errno(r, "Failed to bind Varlink methods: %m"); @@ -737,7 +860,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 +899,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 +926,7 @@ static int run(int argc, char *argv[]) { return log_oom(); } - return bootctl_main(argc, argv); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/bootctl/bootctl.h b/src/bootctl/bootctl.h index d979411c78226..b478ae0ec1d1a 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; @@ -48,6 +51,13 @@ extern char *arg_certificate_source; extern char *arg_private_key; extern KeySourceType arg_private_key_source_type; extern char *arg_private_key_source; +extern bool arg_oldest; +extern uint64_t arg_keep_free; +extern char *arg_entry_title; +extern char *arg_entry_version; +extern uint64_t arg_entry_commit; +extern char **arg_extras; +extern unsigned arg_tries_left; static inline const char* arg_dollar_boot_path(void) { /* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */ @@ -56,11 +66,15 @@ 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 * string, but we limit the length to something reasonable to prevent from the firmware * having to deal with a potentially too long string. */ #define EFI_BOOT_OPTION_DESCRIPTION_MAX ((size_t) 255) + +/* Before we "materialize" a new entry, let's ensure we have this much space free still on the partition, by + * default */ +#define KEEP_FREE_BYTES_DEFAULT (5U * U64_MB) diff --git a/src/bootctl/bootspec-util.c b/src/bootctl/bootspec-util.c index ec3339600bb10..5f9842c9d80a3 100644 --- a/src/bootctl/bootspec-util.c +++ b/src/bootctl/bootspec-util.c @@ -1,15 +1,22 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "bootctl.h" +#include "alloc-util.h" +#include "boot-entry.h" #include "bootspec-util.h" #include "devnum-util.h" #include "efi-loader.h" #include "errno-util.h" #include "log.h" +#include "parse-util.h" +#include "path-util.h" +#include "stdio-util.h" +#include "string-util.h" #include "strv.h" +#include "utf8.h" 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 +32,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 +44,212 @@ 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); +} + +int boot_entry_make_commit_filename( + const char *entry_token, + uint64_t entry_commit, + const char *version, + unsigned profile_nr, + unsigned tries_left, + char **ret) { + + assert(entry_token); + assert(ret); + + /* Generate a new entry filename from the entry token, the commit number, and (optionally) the + * image/OS version, (if non-zero) the profile number, and (unless UINT_MAX) the number of tries + * left. */ + + if (!boot_entry_token_valid(entry_token)) + return -EINVAL; + if (!entry_commit_valid(entry_commit)) + return -EINVAL; + + _cleanup_free_ char *filename = asprintf_safe("%s-commit_%" PRIu64, entry_token, entry_commit); + if (!filename) + return -ENOMEM; + if (version && !strextend(&filename, ".", version)) + return -ENOMEM; + if (profile_nr > 0 && strextendf(&filename, "@%u", profile_nr) < 0) + return -ENOMEM; + if (tries_left != UINT_MAX && strextendf(&filename, "+%u", tries_left) < 0) + return -ENOMEM; + if (!strextend(&filename, ".conf")) + return -ENOMEM; + + if (!filename_is_valid(filename) || string_has_cc(filename, /* ok= */ NULL) || !utf8_is_valid(filename)) + return -EINVAL; + + *ret = TAKE_PTR(filename); + return 0; +} + +int boot_entry_parse_commit_filename( + const char *filename, + char **ret_entry_token, + uint64_t *ret_entry_commit) { + + int r; + + assert(filename); + + if (!filename_is_valid(filename)) + return -EINVAL; + + _cleanup_free_ char *stripped = NULL; + r = boot_filename_extract_tries(filename, &stripped, /* ret_tries_left= */ NULL, /* ret_tries_done= */ NULL); + if (r < 0) + return r; + + const char *a = strrstr_no_case(stripped, "-commit_"); + if (!a) + return -EBADMSG; + + const char *c = endswith_no_case(stripped, ".conf"); + if (!c) + return -EBADMSG; + + assert(a < c); + + _cleanup_free_ char *entry_token = strndup(stripped, a - stripped); + if (!entry_token) + return -ENOMEM; + + if (!boot_entry_token_valid(entry_token)) + return -EBADMSG; + + const char *b = a + STRLEN("-commit_"); + size_t n = strspn(b, DIGITS); + if (n <= 0 || !IN_SET(b[n], '+', '.', '@')) + return -EBADMSG; + + _cleanup_free_ char *entry_commit_string = strndup(b, n); + if (!entry_commit_string) + return -ENOMEM; + + uint64_t entry_commit; + r = safe_atou64_full(entry_commit_string, 10, &entry_commit); + if (r < 0) + return r; + if (!entry_commit_valid(entry_commit)) + return -EBADMSG; + + if (ret_entry_token) + *ret_entry_token = TAKE_PTR(entry_token); + if (ret_entry_commit) + *ret_entry_commit = entry_commit; + + return 0; +} + +int boot_entry_parse_commit( + BootEntry *entry, + char **ret_entry_token, + uint64_t *ret_entry_commit) { + + int r; + + assert(entry); + + if (entry->type != BOOT_ENTRY_TYPE1) + return -EADDRNOTAVAIL; + + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(entry->path, &fn); + if (r < 0) + return r; + + return boot_entry_parse_commit_filename(fn, ret_entry_token, ret_entry_commit); +} + +int boot_config_find_oldest_commit( + BootConfig *config, + const char *entry_token, + char ***ret_ids) { + + int r; + + assert(config); + assert(entry_token); + assert(ret_ids); + + uint64_t commit_oldest = UINT64_MAX, commit_2nd_oldest = UINT64_MAX, commit_blocked = UINT64_MAX; + + /* First, determine which commit is the oldest (that isn't the current one), and hence the candidate + * to be removed */ + FOREACH_ARRAY(b, config->entries, config->n_entries) { + _cleanup_free_ char *et = NULL; + uint64_t ec; + + r = boot_entry_parse_commit(b, &et, &ec); + if (r == -EADDRNOTAVAIL) + continue; + if (r < 0) { + log_debug_errno(r, "Failed to parse entry filename of '%s', ignoring: %m", strna(b->id)); + continue; + } + + if (!streq(et, entry_token)) /* Not ours? */ + continue; + + if (ec < commit_oldest) { + commit_2nd_oldest = commit_oldest; + commit_oldest = ec; + } else if (ec > commit_oldest && ec < commit_2nd_oldest) + commit_2nd_oldest = ec; + + if (boot_config_selected_entry(config) == b) { + assert(commit_blocked == UINT64_MAX); + commit_blocked = ec; + } + } + + uint64_t commit_picked; + if (commit_oldest == UINT64_MAX) + return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), "No matching entry found while determining oldest entry."); + if (commit_oldest != commit_blocked) + commit_picked = commit_oldest; + else { + if (commit_2nd_oldest == UINT64_MAX) + return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Only matching entry found while determining oldest entry is current one, skipping it."); + + assert(commit_2nd_oldest != commit_blocked); + commit_picked = commit_2nd_oldest; + } + + log_debug("Determined commit %" PRIu64 " to be oldest.", commit_picked); + + /* Second loop: actually remove all entries matching this commit (which can be multiple, since UKIs + * have profiles) */ + _cleanup_(strv_freep) char **l = NULL; + FOREACH_ARRAY(b, config->entries, config->n_entries) { + _cleanup_free_ char *et = NULL; + uint64_t ec; + + r = boot_entry_parse_commit(b, &et, &ec); + if (r == -EADDRNOTAVAIL) + continue; + if (r < 0) { + log_debug_errno(r, "Failed to parse entry filename of '%s', ignoring: %m", strna(b->id)); + continue; + } + + if (!streq(et, entry_token)) /* Not ours? */ + continue; + + if (ec != commit_picked) + continue; + + r = strv_extend(&l, b->id); + if (r < 0) + return r; + } + + /* The list cannot be empty, the first loop above and the 2nd loop must have found the same matching + * entries, and if the first loop didn't find any we'd not come this far. */ + assert(!strv_isempty(l)); + *ret_ids = TAKE_PTR(l); + return 0; } diff --git a/src/bootctl/bootspec-util.h b/src/bootctl/bootspec-util.h index a00e002caafdc..0824c8040fb64 100644 --- a/src/bootctl/bootspec-util.h +++ b/src/bootctl/bootspec-util.h @@ -3,4 +3,16 @@ #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); + +static inline bool entry_commit_valid(uint64_t commit) { + return commit > 0 && commit < UINT64_MAX; +} + +int boot_entry_make_commit_filename(const char *entry_token, uint64_t entry_commit, const char *version, unsigned profile_nr, unsigned tries_left, char **ret); + +int boot_entry_parse_commit_filename(const char *filename, char **ret_entry_token, uint64_t *ret_entry_commit); + +int boot_entry_parse_commit(BootEntry *entry, char **ret_entry_token, uint64_t *ret_entry_commit); + +int boot_config_find_oldest_commit(BootConfig *config, const char *entry_token, char ***ret_ids); diff --git a/src/bootctl/meson.build b/src/bootctl/meson.build index 8cfbb7c14acb0..06137bdae00ce 100644 --- a/src/bootctl/meson.build +++ b/src/bootctl/meson.build @@ -3,6 +3,7 @@ bootctl_sources = files( 'bootctl.c', 'bootctl-install.c', + 'bootctl-link.c', 'bootctl-random-seed.c', 'bootctl-reboot-to-firmware.c', 'bootctl-set-efivar.c', @@ -23,6 +24,12 @@ executables += [ ], 'sources' : bootctl_sources, 'link_with' : boot_link_with, - 'dependencies' : [libopenssl], + 'dependencies' : [libopenssl_cflags], + }, + test_template + { + 'sources' : files( + 'test-bootspec-util.c', + 'bootspec-util.c', + ), }, ] diff --git a/src/bootctl/test-bootspec-util.c b/src/bootctl/test-bootspec-util.c new file mode 100644 index 0000000000000..1fa891469f460 --- /dev/null +++ b/src/bootctl/test-bootspec-util.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "bootspec-util.h" +#include "tests.h" + +static void test_one( + const char *entry_token, + uint64_t entry_commit, + const char *version, + unsigned profile_nr, + unsigned tries_left, + const char *expected) { + + _cleanup_free_ char *fn = NULL; + ASSERT_OK(boot_entry_make_commit_filename(entry_token, entry_commit, version, profile_nr, tries_left, &fn)); + ASSERT_STREQ(fn, expected); + + _cleanup_free_ char *token = NULL; + uint64_t commit = 0; + ASSERT_OK(boot_entry_parse_commit_filename(fn, &token, &commit)); + ASSERT_STREQ(token, entry_token); + ASSERT_EQ(commit, entry_commit); +} + +TEST(boot_entry_commit_filename) { + test_one("foo", 1, NULL, 0, UINT_MAX, "foo-commit_1.conf"); + test_one("foo", 42, "1.0", 0, UINT_MAX, "foo-commit_42.1.0.conf"); + test_one("foo", 42, "1.0", 3, UINT_MAX, "foo-commit_42.1.0@3.conf"); + test_one("foo", 42, "1.0", 3, 5, "foo-commit_42.1.0@3+5.conf"); + test_one("foo", 42, NULL, 3, UINT_MAX, "foo-commit_42@3.conf"); + test_one("foo", 42, NULL, 3, 7, "foo-commit_42@3+7.conf"); + test_one("foo", 42, NULL, 0, 9, "foo-commit_42+9.conf"); + test_one("my-token", 123456, "v2", 0, UINT_MAX, "my-token-commit_123456.v2.conf"); + + /* Invalid inputs for make */ + _cleanup_free_ char *fn = NULL; + ASSERT_ERROR(boot_entry_make_commit_filename("foo/bar", 1, NULL, 0, UINT_MAX, &fn), EINVAL); + ASSERT_ERROR(boot_entry_make_commit_filename("foo", 0, NULL, 0, UINT_MAX, &fn), EINVAL); + ASSERT_ERROR(boot_entry_make_commit_filename("foo", UINT64_MAX, NULL, 0, UINT_MAX, &fn), EINVAL); + + /* Invalid inputs for parse */ + _cleanup_free_ char *token = NULL; + uint64_t commit = 0; + ASSERT_ERROR(boot_entry_parse_commit_filename("foo.conf", &token, &commit), EBADMSG); + ASSERT_ERROR(boot_entry_parse_commit_filename("foo-commit_.conf", &token, &commit), EBADMSG); + ASSERT_ERROR(boot_entry_parse_commit_filename("foo-commit_abc.conf", &token, &commit), EBADMSG); + ASSERT_ERROR(boot_entry_parse_commit_filename("foo-commit_0.conf", &token, &commit), EBADMSG); +} + +DEFINE_TEST_MAIN(LOG_INFO); 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..d9450a12b0ada 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-util.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..ae8e7cdf54b43 --- /dev/null +++ b/src/bpf/merge-bpf-compdb.py @@ -0,0 +1,42 @@ +#!/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..ac05eb4b1840f --- /dev/null +++ b/src/bpf/meson.build @@ -0,0 +1,415 @@ +# 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) + +# 'enum lsm_integrity_type' was added together with the bdev_setintegrity LSM +# hook in kernel commit 2deeb6c333e5 (v6.5). The generated vmlinux.h reflects +# the running kernel's BTF; a provided vmlinux.h can be older, so probe. +have_lsm_integrity_type = false +if use_generated_vmlinux_h + have_lsm_integrity_type = true +elif use_provided_vmlinux_h + have_lsm_integrity_type = cc.compiles( + '#include "@0@"\nenum lsm_integrity_type _t;\n'.format(provided_vmlinux_h_path), + name : 'enum lsm_integrity_type in vmlinux.h') +endif +conf.set10('HAVE_LSM_INTEGRITY_TYPE', have_lsm_integrity_type) + +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-fsaccess.bpf.c'), + 'condition' : 'HAVE_LSM_INTEGRITY_TYPE', + 'depends' : vmlinux_h_dependency, + }, + { + '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-util.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/bpf/restrict-fsaccess.bpf.c b/src/bpf/restrict-fsaccess.bpf.c new file mode 100644 index 0000000000000..13e11aff500be --- /dev/null +++ b/src/bpf/restrict-fsaccess.bpf.c @@ -0,0 +1,300 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* 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. + */ + +/* Trusted Execution BPF LSM program. + * + * Enforces that only binaries from signed dm-verity block devices (or the + * initramfs during early boot) can be executed. + * + * Architecture: + * - bdev_setintegrity hook: self-populates a map of trusted devices when + * dm-verity signals signature validity + * - bdev_free_security hook: removes devices from the map on teardown + * - bprm_check_security: blocks execve() from untrusted sources + * - mmap_file: blocks PROT_EXEC mmap from untrusted sources + * - file_mprotect: blocks W->X transitions from untrusted sources + */ + +/* If offsetof() is implemented via __builtin_offset() then it doesn't work on current compilers, since the + * built-ins do not understand CO-RE. Let's undefine any such macros here, to force bpf_helpers.h to define + * its own definitions for this. (In new versions it will do so automatically, but at least in libbpf 1.1.0 + * it does not.) */ +#undef offsetof +#undef container_of + +#include "vmlinux.h" + +#include /* IWYU pragma: keep */ +#include +#include +#include + +#define PROT_EXEC 0x4 +#define VM_EXEC 0x00000004 +#define PTRACE_MODE_ATTACH 0x02 + +/* ---- Maps ---- */ + +struct { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 0); /* placeholder */ + __type(key, __u32); /* dev_t from bdev->bd_dev */ + __type(value, __u8); /* 1 = signature valid */ +} verity_devices SEC(".maps"); + +/* ---- Globals (set by PID1 via skeleton) ---- */ + +/* Device number of the initramfs superblock. PID1 sets this at load time and + * clears it (to 0) after switch_root. A value of 0 means "no initramfs trust + * — the window is closed." */ +volatile __u32 initramfs_s_dev; + +/* ---- Self-protection guard globals (set by PID1 after attach) ---- + * + * While all IDs are 0 (the .bss default), the guard is inactive — no real BPF + * object has ID 0, so no comparisons match. PID1 populates these after + * attaching all programs. */ +volatile __u32 protected_map_id_verity; +volatile __u32 protected_map_id_bss; + +/* Must equal _RESTRICT_FILESYSTEM_ACCESS_LINK_MAX in bpf-restrict-fsaccess.h — update when adding programs */ +#define NUM_PROTECTED_OBJS 9 /* 5 enforcement + 4 guard (bpf, bpf_map, bpf_prog, ptrace) */ +volatile __u32 protected_prog_ids[NUM_PROTECTED_OBJS]; +volatile __u32 protected_link_ids[NUM_PROTECTED_OBJS]; + +/* ---- Integrity tracking hooks ---- */ + +/* Preferred version: reads both value and size for defense-in-depth. + * Requires kernel v6.16+ or the backport of 1271a40eeafa ("bpf: Allow + * access to const void pointer arguments in tracing programs"). + * On older kernels btf_ctx_access() rejects loads from const void * + * arguments because it fails to skip the CONST modifier when checking + * for void pointers. prepare_restrict_fsaccess_bpf() tries this version + * first and falls back to the _compat variant below if loading fails. */ +SEC("lsm/bdev_setintegrity") +int BPF_PROG(restrict_fsaccess_bdev_setintegrity, struct block_device *bdev, + enum lsm_integrity_type type, const void *value, __u64 size) +{ + if (type == LSM_INT_DMVERITY_SIG_VALID) { + __u32 dev = bdev->bd_dev; + __u8 valid = value && size > 0; + bpf_map_update_elem(&verity_devices, &dev, &valid, BPF_ANY); + } + + return 0; +} + +/* Compatibility version for kernels without 1271a40eeafa: does not + * read the const void *value argument (ctx[2]) to avoid the verifier + * rejection. Reads size (ctx[3]) directly from the raw context instead. + * This is safe because dm-verity guarantees value!=NULL iff size>0. */ +#define BDEV_SETINTEGRITY_SIZE_CTX_IDX 3 /* bdev_setintegrity(bdev, type, value, size) */ +SEC("lsm/bdev_setintegrity") +int BPF_PROG(restrict_fsaccess_bdev_setintegrity_compat, struct block_device *bdev, + enum lsm_integrity_type type) +{ + if (type == LSM_INT_DMVERITY_SIG_VALID) { + __u32 dev = bdev->bd_dev; + __u8 valid = ctx[BDEV_SETINTEGRITY_SIZE_CTX_IDX] > 0; + bpf_map_update_elem(&verity_devices, &dev, &valid, BPF_ANY); + } + + return 0; +} + +SEC("lsm/bdev_free_security") +void BPF_PROG(restrict_fsaccess_bdev_free, struct block_device *bdev) +{ + __u32 dev = bdev->bd_dev; + bpf_map_delete_elem(&verity_devices, &dev); +} + +/* ---- Enforcement helpers ---- */ + +/* Check whether a file is from a trusted source. + * Returns 0 (allow) or -EPERM (deny). */ +static __always_inline int check_trusted_file(struct file *file) +{ + __u32 s_dev; + __u8 *sig_valid; + + BPF_CORE_READ_INTO(&s_dev, file, f_inode, i_sb, s_dev); + + /* Check initramfs trust (active only during early boot) */ + if (initramfs_s_dev != 0 && s_dev == initramfs_s_dev) + return 0; + + /* Check verity device map */ + sig_valid = bpf_map_lookup_elem(&verity_devices, &s_dev); + if (sig_valid && *sig_valid) + return 0; + + return -EPERM; +} + +/* ---- Enforcement hooks ---- */ + +SEC("lsm/bprm_check_security") +int BPF_PROG(restrict_fsaccess_bprm_check, struct linux_binprm *bprm) +{ + struct file *file; + + BPF_CORE_READ_INTO(&file, bprm, file); + return check_trusted_file(file); +} + +SEC("lsm/mmap_file") +int BPF_PROG(restrict_fsaccess_mmap_file, struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) +{ + /* Only enforce on executable mappings */ + if (!(prot & PROT_EXEC)) + return 0; + + /* Anonymous executable mapping — no file backing, deny */ + if (!file) + return -EPERM; + + return check_trusted_file(file); +} + +SEC("lsm/file_mprotect") +int BPF_PROG(restrict_fsaccess_file_mprotect, struct vm_area_struct *vma, + unsigned long reqprot, unsigned long prot) +{ + struct file *file; + unsigned long vm_flags; + + /* Only enforce when adding PROT_EXEC */ + if (!(prot & PROT_EXEC)) + return 0; + + /* If VM_EXEC is already set, the mapping is already executable — this + * mprotect isn't granting new executable capability, allow */ + BPF_CORE_READ_INTO(&vm_flags, vma, vm_flags); + if (vm_flags & VM_EXEC) + return 0; + + /* Anonymous executable mapping — no file backing, deny */ + BPF_CORE_READ_INTO(&file, vma, vm_file); + if (!file) + return -EPERM; + + return check_trusted_file(file); +} + +/* ---- PID1 ptrace protection ---- + * + * Blocks PTRACE_MODE_ATTACH access to PID1 from any other process. This + * prevents ptrace(PTRACE_ATTACH), /proc/1/mem, process_vm_readv(), and + * pidfd_getfd() from extracting sensitive state from PID1's address space. + * + * PTRACE_MODE_READ is allowed — monitoring tools and systemctl need + * /proc/1/status, /proc/1/fd/, /proc/1/ns/ *, etc. + * + * PID1 accessing itself is allowed. */ + +SEC("lsm/ptrace_access_check") +int BPF_PROG(restrict_fsaccess_ptrace_guard, struct task_struct *child, + unsigned int mode) +{ + /* We only care about PID 1 and its threads (There are none but still.). */ + if (child->tgid != 1) + return 0; + + /* We only care about dangerous operations. */ + if (!(mode & PTRACE_MODE_ATTACH)) + return 0; + + /* PID1 (any thread) accessing itself is allowed. */ + if ((bpf_get_current_pid_tgid() >> 32) == 1) + return 0; + + return -EPERM; +} + +/* ---- Self-protection guard ---- + * + * Three hooks protect our BPF objects from non-PID1 processes: + * + * lsm/bpf_map — fires inside bpf_map_new_fd(), the chokepoint for ALL + * code paths that produce a map FD (BPF_MAP_GET_FD_BY_ID, + * BPF_OBJ_GET, BPF_MAP_CREATE). Blocks the primary attack: + * obtaining an FD to verity_devices to inject fake trusted + * devices via BPF_MAP_UPDATE_ELEM. + * + * lsm/bpf_prog — fires inside bpf_prog_new_fd(), same chokepoint coverage + * for programs. Defense-in-depth. + * + * lsm/bpf — handles BPF_LINK_GET_FD_BY_ID only. There is no + * security_bpf_link() hook in the kernel, so link + * protection uses the command-level bpf() hook. This is + * sufficient: we don't pin links in production, so + * BPF_OBJ_GET is not an attack vector for links. */ + +SEC("lsm/bpf_map") +int BPF_PROG(restrict_fsaccess_bpf_map_guard, struct bpf_map *map, + unsigned int fmode) +{ + __u32 id; + + if ((bpf_get_current_pid_tgid() >> 32) == 1) + return 0; + + id = map->id; + if (id != 0 && (id == protected_map_id_verity || + id == protected_map_id_bss)) + return -EPERM; + + return 0; +} + +SEC("lsm/bpf_prog") +int BPF_PROG(restrict_fsaccess_bpf_prog_guard, struct bpf_prog *prog) +{ + __u32 id; + + if ((bpf_get_current_pid_tgid() >> 32) == 1) + return 0; + + id = BPF_CORE_READ(prog, aux, id); + if (id == 0) + return 0; + + for (int i = 0; i < NUM_PROTECTED_OBJS; i++) + if (id == protected_prog_ids[i]) + return -EPERM; + + return 0; +} + +SEC("lsm/bpf") +int BPF_PROG(restrict_fsaccess_bpf_guard, int cmd, union bpf_attr *attr, + unsigned int size) +{ + __u32 id; + + if ((bpf_get_current_pid_tgid() >> 32) == 1) + return 0; + + if (cmd != BPF_LINK_GET_FD_BY_ID) + return 0; + + /* link_id/map_id/prog_id share the same offset in the bpf_attr union */ + id = attr->link_id; + if (id == 0) + return 0; + + for (int i = 0; i < NUM_PROTECTED_OBJS; i++) + if (id == protected_link_ids[i]) + return -EPERM; + + return 0; +} + +static const char _license[] SEC("license") = "GPL"; 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..edba5748e86e2 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" @@ -9,6 +8,7 @@ #include "sd-json.h" #include "alloc-util.h" +#include "ansi-color.h" #include "bitfield.h" #include "build.h" #include "bus-dump.h" @@ -26,16 +26,17 @@ #include "fileio.h" #include "format-table.h" #include "glyph-util.h" +#include "help-util.h" #include "log.h" #include "logarithm.h" #include "main-func.h" #include "memstream-util.h" +#include "options.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" -#include "pretty-print.h" #include "runtime-scope.h" #include "set.h" #include "string-util.h" @@ -175,7 +176,8 @@ 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) { +VERB_DEFAULT_NOARG(verb_list, "list", "List bus names"); +static int verb_list(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 +395,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 +539,9 @@ static int tree_one(sd_bus *bus, const char *service) { return r; } -static int tree(int argc, char **argv, void *userdata) { +VERB(verb_tree, "tree", "[SERVICE…]", VERB_ANY, VERB_ANY, 0, + "Show object tree of service"); +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 +1000,9 @@ static int members_flags_to_string(const Member *m, char **ret) { return 0; } -static int introspect(int argc, char **argv, void *userdata) { +VERB(verb_introspect, "introspect", "SERVICE OBJECT [INTERFACE]", 3, 4, 0, + "Introspect an object"); +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 +1194,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 +1383,15 @@ 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) { +VERB(verb_monitor, "monitor", "[SERVICE…]", VERB_ANY, VERB_ANY, 0, + "Show bus traffic"); +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) { +VERB(verb_capture, "capture", "[SERVICE…]", VERB_ANY, VERB_ANY, 0, + "Capture bus traffic as pcap"); +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 +1418,9 @@ static int verb_capture(int argc, char **argv, void *userdata) { return r; } -static int status(int argc, char **argv, void *userdata) { +VERB(verb_status, "status", "[SERVICE]", VERB_ANY, 2, 0, + "Show bus service, process, or bus owner credentials"); +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 +1790,9 @@ static int bus_message_dump(sd_bus_message *m, uint64_t flags) { return 0; } -static int call(int argc, char **argv, void *userdata) { +VERB(verb_call, "call", "SERVICE OBJECT INTERFACE METHOD [SIGNATURE [ARGUMENT…]]", 5, VERB_ANY, 0, + "Call a method"); +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 +1859,9 @@ 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) { +VERB(verb_emit, "emit", "OBJECT INTERFACE SIGNAL [SIGNATURE [ARGUMENT…]]", 4, VERB_ANY, 0, + "Emit a signal"); +static int verb_emit(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 +1906,9 @@ static int emit_signal(int argc, char **argv, void *userdata) { return 0; } -static int get_property(int argc, char **argv, void *userdata) { +VERB(verb_get_property, "get-property", "SERVICE OBJECT INTERFACE PROPERTY…", 5, VERB_ANY, 0, + "Get property value"); +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 +1966,9 @@ 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) { +VERB(verb_wait, "wait", "[SERVICE] OBJECT INTERFACE SIGNAL", 4, 5, 0, + "Wait for a signal"); +static int verb_wait(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 +2016,9 @@ 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) { +VERB(verb_set_property, "set-property", "SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT…", 6, VERB_ANY, 0, + "Set property value"); +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; @@ -2056,213 +2074,131 @@ static int set_property(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("busctl", "1", &link); + pager_open(arg_pager_flags); + + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; - pager_open(arg_pager_flags); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sIntrospect the D-Bus IPC bus.%6$s\n" - "\n%3$sCommands%4$s:\n" - " list List bus names\n" - " status [SERVICE] Show bus service, process or bus owner credentials\n" - " monitor [SERVICE...] Show bus traffic\n" - " capture [SERVICE...] Capture bus traffic as pcap\n" - " tree [SERVICE...] Show object tree of service\n" - " introspect SERVICE OBJECT [INTERFACE]\n" - " call SERVICE OBJECT INTERFACE METHOD [SIGNATURE [ARGUMENT...]]\n" - " Call a method\n" - " emit OBJECT INTERFACE SIGNAL [SIGNATURE [ARGUMENT...]]\n" - " Emit a signal\n" - " wait OBJECT INTERFACE SIGNAL\n" - " Wait for a signal\n" - " get-property SERVICE OBJECT INTERFACE PROPERTY...\n" - " Get property value\n" - " set-property SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT...\n" - " Set property value\n" - " help Show this help\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" - " -l --full Do not ellipsize output\n" - " --system Connect to system bus\n" - " --user Connect to user bus\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --address=ADDRESS Connect to bus specified by address\n" - " --show-machine Show machine ID column in list\n" - " --unique Only show unique names\n" - " --acquired Only show acquired names\n" - " --activatable Only show activatable names\n" - " --match=MATCH Only show matching messages\n" - " --size=SIZE Maximum length of captured packet\n" - " --list Don't show tree, but simple object path list\n" - " -q --quiet Don't show method call reply\n" - " --verbose Show result values in long format\n" - " --json=MODE Output as JSON\n" - " -j Same as --json=pretty on tty, --json=short otherwise\n" - " --xml-interface Dump the XML description in introspect command\n" - " --expect-reply=BOOL Expect a method call reply\n" - " --auto-start=BOOL Auto-start destination service\n" - " --allow-interactive-authorization=BOOL\n" - " Allow interactive authorization for operation\n" - " --timeout=SECS Maximum time to wait for method call completion\n" - " --augment-creds=BOOL Extend credential data with data read from /proc/$PID\n" - " --watch-bind=BOOL Wait for bus AF_UNIX socket to be bound in the file\n" - " system\n" - " --destination=SERVICE Destination service of a signal\n" - " -N --limit-messages=NUMBER\n" - " Stop monitoring after receiving the specified number\n" - " of messages\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + /* Note: column widths are not synced, because the verbs table is very wide. */ - return 0; -} + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Introspect the D-Bus IPC bus."); -static int verb_help(int argc, char **argv, void *userdata) { - return help(); -} + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_SYSTEM, - ARG_USER, - ARG_ADDRESS, - ARG_MATCH, - ARG_SHOW_MACHINE, - ARG_UNIQUE, - ARG_ACQUIRED, - ARG_ACTIVATABLE, - ARG_SIZE, - ARG_LIST, - ARG_VERBOSE, - ARG_XML_INTERFACE, - ARG_EXPECT_REPLY, - ARG_AUTO_START, - ARG_ALLOW_INTERACTIVE_AUTHORIZATION, - ARG_TIMEOUT, - ARG_AUGMENT_CREDS, - ARG_WATCH_BIND, - ARG_JSON, - ARG_DESTINATION, - }; + help_man_page_reference("busctl", "1"); - 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 }, - { "full", no_argument, NULL, 'l' }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "address", required_argument, NULL, ARG_ADDRESS }, - { "show-machine", no_argument, NULL, ARG_SHOW_MACHINE }, - { "unique", no_argument, NULL, ARG_UNIQUE }, - { "acquired", no_argument, NULL, ARG_ACQUIRED }, - { "activatable", no_argument, NULL, ARG_ACTIVATABLE }, - { "match", required_argument, NULL, ARG_MATCH }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "capsule", required_argument, NULL, 'C' }, - { "size", required_argument, NULL, ARG_SIZE }, - { "list", no_argument, NULL, ARG_LIST }, - { "quiet", no_argument, NULL, 'q' }, - { "verbose", no_argument, NULL, ARG_VERBOSE }, - { "xml-interface", no_argument, NULL, ARG_XML_INTERFACE }, - { "expect-reply", required_argument, NULL, ARG_EXPECT_REPLY }, - { "auto-start", required_argument, NULL, ARG_AUTO_START }, - { "allow-interactive-authorization", required_argument, NULL, ARG_ALLOW_INTERACTIVE_AUTHORIZATION }, - { "timeout", required_argument, NULL, ARG_TIMEOUT }, - { "augment-creds", required_argument, NULL, ARG_AUGMENT_CREDS }, - { "watch-bind", required_argument, NULL, ARG_WATCH_BIND }, - { "json", required_argument, NULL, ARG_JSON }, - { "destination", required_argument, NULL, ARG_DESTINATION }, - { "limit-messages", required_argument, NULL, 'N' }, - {}, - }; + return 0; +} - int c, r; +VERB_COMMON_HELP(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, "hH:M:C:J:qjlN:", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case 'l': + OPTION('l', "full", NULL, "Do not ellipsize output"): arg_full = true; break; - case ARG_USER: + OPTION_LONG("system", NULL, "Connect to system bus"): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + OPTION_LONG("user", NULL, "Connect to user bus"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + OPTION_COMMON_HOST: + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = opts.arg; break; - case ARG_ADDRESS: - arg_address = optarg; + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); + if (r < 0) + return r; break; - case ARG_SHOW_MACHINE: + OPTION('C', "capsule", "NAME", "Operate on capsule"): + r = capsule_name_is_valid(opts.arg); + if (r < 0) + return log_error_errno(r, "Unable to validate capsule name '%s': %m", opts.arg); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", opts.arg); + + arg_host = opts.arg; + arg_transport = BUS_TRANSPORT_CAPSULE; + break; + + OPTION_LONG("address", "ADDRESS", "Connect to bus specified by address"): + arg_address = opts.arg; + break; + + OPTION_LONG("show-machine", NULL, "Show machine ID column in list"): arg_show_machine = true; break; - case ARG_UNIQUE: + OPTION_LONG("unique", NULL, "Only show unique names"): arg_unique = true; break; - case ARG_ACQUIRED: + OPTION_LONG("acquired", NULL, "Only show acquired names"): arg_acquired = true; break; - case ARG_ACTIVATABLE: + OPTION_LONG("activatable", NULL, "Only show activatable names"): arg_activatable = true; break; - case ARG_MATCH: - if (strv_extend(&arg_matches, optarg) < 0) + OPTION_LONG("match", "MATCH", "Only show matching messages"): + if (strv_extend(&arg_matches, opts.arg) < 0) return log_oom(); break; - case ARG_SIZE: { + OPTION_LONG("size", "SIZE", "Maximum length of captured packet"): { uint64_t sz; - r = parse_size(optarg, 1024, &sz); + r = parse_size(opts.arg, 1024, &sz); if (r < 0) - return log_error_errno(r, "Failed to parse size '%s': %m", optarg); + return log_error_errno(r, "Failed to parse size '%s': %m", opts.arg); if ((uint64_t) (size_t) sz != sz) return log_error_errno(SYNTHETIC_ERRNO(E2BIG), @@ -2272,167 +2208,123 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_LIST: + OPTION_LONG("list", NULL, "Don't show tree, but simple object path list"): arg_list = true; break; - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); - if (r < 0) - return r; + OPTION('q', "quiet", NULL, "Don't show method call reply"): + arg_quiet = true; break; - case 'C': - r = capsule_name_is_valid(optarg); - if (r < 0) - return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); - - arg_host = optarg; - arg_transport = BUS_TRANSPORT_CAPSULE; + OPTION_LONG("verbose", NULL, "Show result values in long format"): + arg_verbose = true; break; - case 'q': - arg_quiet = true; + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); + if (r <= 0) + return r; break; - case ARG_VERBOSE: - arg_verbose = true; + OPTION_COMMON_LOWERCASE_J: + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; break; - case ARG_XML_INTERFACE: + OPTION_LONG("xml-interface", NULL, "Dump the XML description in introspect command"): arg_xml_interface = true; break; - case ARG_EXPECT_REPLY: - r = parse_boolean_argument("--expect-reply=", optarg, &arg_expect_reply); + OPTION_LONG("expect-reply", "BOOL", "Expect a method call reply"): + r = parse_boolean_argument("--expect-reply=", opts.arg, &arg_expect_reply); if (r < 0) return r; break; - case ARG_AUTO_START: - r = parse_boolean_argument("--auto-start=", optarg, &arg_auto_start); + OPTION_LONG("auto-start", "BOOL", "Auto-start destination service"): + r = parse_boolean_argument("--auto-start=", opts.arg, &arg_auto_start); if (r < 0) return r; break; - case ARG_ALLOW_INTERACTIVE_AUTHORIZATION: - r = parse_boolean_argument("--allow-interactive-authorization=", optarg, + OPTION_LONG("allow-interactive-authorization", "", + "Allow interactive authorization for operation"): + r = parse_boolean_argument("--allow-interactive-authorization=", opts.arg, &arg_allow_interactive_authorization); if (r < 0) return r; break; - case ARG_TIMEOUT: - if (isempty(optarg)) { + OPTION_LONG("timeout", "SECS", "Maximum time to wait for method call completion"): + if (isempty(opts.arg)) { arg_timeout = 0; /* Reset to default */ break; } - r = parse_sec(optarg, &arg_timeout); + r = parse_sec(opts.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", opts.arg); break; - case ARG_AUGMENT_CREDS: - r = parse_boolean_argument("--augment-creds=", optarg, &arg_augment_creds); + OPTION_LONG("augment-creds", "BOOL", + "Extend credential data with data read from /proc/$PID"): + r = parse_boolean_argument("--augment-creds=", opts.arg, &arg_augment_creds); if (r < 0) return r; break; - case ARG_WATCH_BIND: - r = parse_boolean_argument("--watch-bind=", optarg, &arg_watch_bind); + OPTION_LONG("watch-bind", "BOOL", + "Wait for bus AF_UNIX socket to be bound in the file system"): + r = parse_boolean_argument("--watch-bind=", opts.arg, &arg_watch_bind); if (r < 0) return r; 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); - if (r <= 0) - return r; - - break; - - case ARG_DESTINATION: - arg_destination = optarg; + OPTION_LONG("destination", "SERVICE", "Destination service of a signal"): + arg_destination = opts.arg; break; - case 'N': - if (isempty(optarg)) { + OPTION('N', "limit-messages", "NUMBER", + "Stop monitoring after receiving the specified number of messages"): + if (isempty(opts.arg)) { /* Reset to default */ arg_limit_messages = UINT64_MAX; arg_limit_signals = 1; break; } - if (streq(optarg, "infinity")) { + if (streq(opts.arg, "infinity")) { arg_limit_signals = arg_limit_messages = UINT64_MAX; break; } - r = safe_atou64(optarg, &arg_limit_messages); + r = safe_atou64(opts.arg, &arg_limit_messages); if (r < 0) - return log_error_errno(r, "Failed to parse --limit-messages= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --limit-messages= parameter: %s", opts.arg); if (arg_limit_messages == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--limit-messages= parameter cannot be 0"); arg_limit_signals = arg_limit_messages; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_full < 0) arg_full = terminal_is_dumb(); + *remaining_args = option_parser_get_args(&opts); return 1; } -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 }, - {} - }; - - 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 busctl_main(argc, argv); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/cgls/cgls.c b/src/cgls/cgls.c index c224a892e41ed..cdb47ba8bdc57 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,131 @@ 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 opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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, opts.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, opts.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 (opts.arg) { + r = parse_boolean(opts.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", opts.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 (opts.arg) { + r = parse_boolean(opts.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", opts.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 = opts.arg; + break; + + OPTION_POSITIONAL: + if (strv_extend(&arg_names, opts.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(&opts) == 0); + return 1; } diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index 60181caffc122..0c96ad7891a78 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; } @@ -393,23 +391,11 @@ static int process( g->n_tasks_valid = true; } else { - _cleanup_free_ char *p = NULL, *v = NULL; - - r = cg_get_path(path, "pids.current", &p); - if (r < 0) + r = cg_get_attribute_as_uint64(path, "pids.current", &g->n_tasks); + if (r < 0 && r != -ENODATA) return r; - - r = read_one_line_file(p, &v); - if (r < 0 && r != -ENOENT) - return r; - if (r >= 0) { - r = safe_atou64(v, &g->n_tasks); - if (r < 0) - return r; - - if (g->n_tasks > 0) - g->n_tasks_valid = true; - } + if (r >= 0 && g->n_tasks > 0) + g->n_tasks_valid = true; } } else @@ -509,7 +495,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 +678,150 @@ 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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.arg); + if (arg_order < 0) + return log_error_errno(arg_order, + "Invalid argument to --order=: %s", + opts.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 (opts.arg) { + arg_cpu_type = cpu_type_from_string(opts.arg); + if (arg_cpu_type < 0) + return log_error_errno(arg_cpu_type, + "Unknown argument to --cpu=: %s", + opts.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=", opts.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(opts.arg, &arg_delay); + if (r < 0) + return log_error_errno(r, "Failed to parse delay parameter '%s': %m", opts.arg); + if (arg_delay <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid delay parameter '%s'", + opts.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(opts.arg, &arg_iterations); + if (r < 0) + return log_error_errno(r, "Failed to parse iterations parameter '%s': %m", opts.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(opts.arg, &arg_depth); if (r < 0) - return r; - - arg_recursive_unset = !r; + return log_error_errno(r, "Failed to parse depth parameter '%s': %m", opts.arg); break; - case 'M': - arg_machine = optarg; + OPTION_COMMON_MACHINE: + arg_machine = opts.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(&opts); + if (n_args > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments."); + if (n_args == 1) + arg_root = option_parser_get_args(&opts)[0]; return 1; } diff --git a/src/clonesetup/clonesetup-generator.c b/src/clonesetup/clonesetup-generator.c new file mode 100644 index 0000000000000..00ac696c4ac37 --- /dev/null +++ b/src/clonesetup/clonesetup-generator.c @@ -0,0 +1,265 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "dropin.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "generator.h" +#include "log.h" +#include "path-util.h" +#include "specifier.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; + int r; + + assert(clone_name); + assert(source_dev); + assert(dest_dev); + assert(metadata_dev); + + /* Escape clone name for unit specifiers and then ExecStart/ExecStop parsing. */ + _cleanup_free_ char *clone_name_spec_escaped = NULL; + clone_name_spec_escaped = specifier_escape(clone_name); + if (!clone_name_spec_escaped) + return log_oom(); + + /* create clone_dev_path that holds path for new cloned device */ + _cleanup_free_ char *clone_dev_path = NULL, *clone_dev_path_escaped = NULL, + *clone_dev_path_unit_escaped = NULL; + clone_dev_path = path_join("/dev/mapper", clone_name); + if (!clone_dev_path) + return log_oom(); + + clone_dev_path_escaped = specifier_escape(clone_dev_path); + if (!clone_dev_path_escaped) + return log_oom(); + + r = unit_name_path_escape(clone_dev_path, &clone_dev_path_unit_escaped); + if (r < 0) + return log_error_errno(r, "Failed to escape clone device path: %m"); + + _cleanup_free_ char *e = NULL, *unit = NULL; + /* 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 */ + _cleanup_free_ char *source_unit = NULL, *dest_unit = NULL, *metadata_unit = NULL; + 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 unit specifiers and then ExecStart parsing. */ + _cleanup_free_ char *source_spec_escaped = NULL, + *dest_spec_escaped = NULL, *metadata_spec_escaped = NULL, + *options_spec_escaped = NULL; + source_spec_escaped = specifier_escape(source_dev); + if (!source_spec_escaped) + return log_oom(); + + dest_spec_escaped = specifier_escape(dest_dev); + if (!dest_spec_escaped) + return log_oom(); + + metadata_spec_escaped = specifier_escape(metadata_dev); + if (!metadata_spec_escaped) + return log_oom(); + + if (options) { + options_spec_escaped = specifier_escape(options); + if (!options_spec_escaped) + return log_oom(); + } + + r = generator_open_unit_file(arg_dest, /* source = */ NULL, unit, &f); + if (r < 0) + return r; + + /* With DefaultDependencies=no, order after udev so backing /dev nodes are ready in early boot. + * The : exec prefix on ExecStart=/ExecStop= disables $ { } env-var expansion by the manager. */ + fprintf(f, + "[Unit]\n" + "Description=Create dm-clone device %1$s\n" + "Documentation=man:clonetab(5) man:systemd-clonesetup(8) man:systemd-clonesetup-generator(8)\n" + "DefaultDependencies=no\n" + "BindsTo=%2$s %3$s %4$s\n" + "After=%2$s %3$s %4$s systemd-udevd-kernel.socket\n" + "Before=blockdev@%5$s.target clonesetup.target shutdown.target\n" + "Wants=blockdev@%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 '%6$s'\n" + "TimeoutSec=0\n", + clone_dev_path_escaped, + source_unit, dest_unit, metadata_unit, + clone_dev_path_unit_escaped, + clone_name_spec_escaped, source_spec_escaped, dest_spec_escaped, + metadata_spec_escaped, options_spec_escaped ?: "-"); + + 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 */ + _cleanup_free_ char *dmname = NULL; + r = unit_name_from_path(clone_dev_path, ".device", &dmname); + if (r < 0) + return log_error_errno(r, "Failed to generate clone device unit name: %m"); + + 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" + "JobRunningTimeoutSec=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, "clonesetup.target", "requires", unit); + if (r < 0) + return r; + + return 0; +} + +static int validate_dev_path(const char *what, const char *path) { + if (!string_is_safe(path, 0) || !path_is_valid(path) || !path_is_normalized(path) || + !path_is_absolute(path) || !path_startswith(path, "/dev/")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid %s device path '%s'.", what, path); + return 0; +} + +/* Field validation — what each check covers: + * control chars, \, ', whitespace → string_is_safe() on all fields + * / in name → string_is_safe(name, STRING_FILENAME) + * .. in device paths → path_is_normalized() + * non-/dev/ device paths → path_is_absolute() + path_startswith(path, "/dev/") + * % specifier expansion → specifier_escape() applied before writing unit file + * $ { } env-var expansion → : exec prefix on ExecStart=/ExecStop= */ +static int validate_fields(const char *fname, unsigned clone_line, const char *name, + const char *src, const char *dst, const char *meta, const char *options) { + int r; + if (!string_is_safe(name, STRING_FILENAME)) { + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid clone name '%s' in %s:%u, ignoring.", name, fname, clone_line); + } + + r = validate_dev_path("source", src); + if (r < 0) + return r; + r = validate_dev_path("destination", dst); + if (r < 0) + return r; + r = validate_dev_path("metadata", meta); + if (r < 0) + return r; + + if (options && !string_is_safe(options, 0)) { + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid options '%s' in %s:%u, ignoring.", options, fname, clone_line); + } + 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 (r != -ENOENT) + log_error_errno(r, "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; + } + + r = validate_fields(fname, clone_line, name, src, dst, meta, options); + if (r < 0) + 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..812ce9db9cee9 --- /dev/null +++ b/src/clonesetup/clonesetup-ioctl.c @@ -0,0 +1,262 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "sd-device.h" + +#include "clonesetup-ioctl.h" +#include "device-private.h" +#include "errno-util.h" +#include "fd-util.h" +#include "log.h" +#include "memory-util.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 = u64_multiply_safe(size, 512); + if (*ret_size == 0 && size != 0) + return log_error_errno(SYNTHETIC_ERRNO(EOVERFLOW), + "Device size overflow for '%s'", dev_path); + 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; + int r; + + 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; + + if (strlen(name) >= sizeof_field(struct dm_ioctl, name)) + return log_error_errno(SYNTHETIC_ERRNO(ENAMETOOLONG), + "DM device name too long: %s", 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"); + + r = RET_NERRNO(ioctl(fd, cmd, dm)); + if (r < 0) { + if (r == -ENXIO && cmd == DM_DEV_REMOVE) + return log_debug_errno(r, "DM ioctl failed: %m"); + return log_error_errno(r, "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) { + char *params_buf; + size_t params_len, dm_size; + _cleanup_free_ struct dm_ioctl *dm = NULL; + struct dm_target_spec *tgt; + + assert(name); + assert(target_params); + + params_len = strlen(target_params) + 1; + /* ensure that dm_size is always aligned, so it makes the buffer actually match what .next claims */ + dm_size = ALIGN8(sizeof(struct dm_ioctl)) + ALIGN8(sizeof(struct dm_target_spec) + params_len); + dm = malloc0(dm_size); + if (!dm) + return log_oom(); + *dm = (struct dm_ioctl) { + .data_start = ALIGN8(sizeof(struct dm_ioctl)), + .target_count = 1, + }; + + tgt = CAST_ALIGN_PTR(struct dm_target_spec, (uint8_t *) dm + dm->data_start); + *tgt = (struct dm_target_spec) { + .length = size_sectors, + /* Per linux/dm-ioctl.h: next is the byte offset from this dm_target_spec to the next one, + * rounded up to 8-byte alignment. With target_count == 1 next == 0 works, but set it + * correctly to avoid silent breakage if a second target is ever added. */ + .next = ALIGN8(sizeof(struct dm_target_spec) + params_len), + }; + strncpy(tgt->target_type, "clone", sizeof(tgt->target_type)); + + params_buf = (char *) ((uint8_t *) tgt + ALIGN8(sizeof(struct dm_target_spec))); + memcpy(params_buf, target_params, params_len); + + 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). + * + * get_size internally uses sysfs i.e. /sys/block//size which also reports device size in + * 512-byte sectors. Before returning, get_size multiplies the size returned by sysfs to bytes. So we + * divide the received byte size by 512 to get the sector count for the DM table. */ + assert(src_dev_size % 512 == 0); + 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) + goto fail; + + r = dm_clone_activate(name); + if (r < 0) + goto fail; + + log_info("Device %s active.", name); + return 0; + +fail: + (void) dm_clone_remove_device_deferred(name); + return r; +} + +/* Calls dm ioctl to send a message to the device. dm_ioctl is the kernel's generic device mapper envelope — + * every ioctl needs it. dm_target_msg is specific to the "send a message" operation + * */ +int dm_clone_send_message(const char *name, const char *message) { + _cleanup_free_ struct dm_ioctl *dm = NULL; + struct dm_target_msg *msg; + size_t dm_size, msg_len; + + assert(name); + assert(message); + + msg_len = strlen(message) + 1; + /* need to take into account both headers in size calculation */ + dm_size = ALIGN8(sizeof(struct dm_ioctl)) + sizeof(struct dm_target_msg) + msg_len; + dm = malloc0(dm_size); + if (!dm) + return log_oom(); + *dm = (struct dm_ioctl) { + /* with ALIGN8 call below, dm_target_msg starts at ALIGN8(sizeof(struct dm_ioctl)) which is + * already aligned, so the dm_target_msg struct lands correctly */ + .data_start = ALIGN8(sizeof(struct dm_ioctl)), + }; + + msg = CAST_ALIGN_PTR(struct dm_target_msg, (uint8_t *) dm + dm->data_start); + memcpy(msg->message, message, msg_len); + + return dm_ioctl_run(name, DM_TARGET_MSG, dm, dm_size); +} + +/* Calls dm ioctl to remove a device. flags if set can be used + * for deferred remove - e.g. DM_DEFERRED_REMOVE */ +static int dm_clone_remove_device_full(const char *name, uint32_t flags) { + struct dm_ioctl dm = { + .flags = flags, + }; + + assert(name); + + return dm_ioctl_run(name, DM_DEV_REMOVE, &dm, sizeof(dm)); +} + +/* Calls dm ioctl to remove a device. */ +int dm_clone_remove_device(const char *name) { + int r; + + assert(name); + r = dm_clone_remove_device_full(name, 0); + if (r < 0) + return r; + + log_info("Device %s inactive.", name); + return 0; +} + +/* Calls dm ioctl for deferred removal i.e. DM_DEFERRED_REMOVE */ +int dm_clone_remove_device_deferred(const char *name) { + int r; + + assert(name); + r = dm_clone_remove_device_full(name, DM_DEFERRED_REMOVE); + if (r < 0) + return r; + + log_info("Device %s marked for deferred removal.", name); + return 0; +} diff --git a/src/clonesetup/clonesetup-ioctl.h b/src/clonesetup/clonesetup-ioctl.h new file mode 100644 index 0000000000000..ba3762eb3ef1b --- /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); +int dm_clone_remove_device_deferred(const char *name); diff --git a/src/clonesetup/clonesetup.c b/src/clonesetup/clonesetup.c new file mode 100644 index 0000000000000..c920da618bfb8 --- /dev/null +++ b/src/clonesetup/clonesetup.c @@ -0,0 +1,245 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include +#include /* access */ + +#include "alloc-util.h" +#include "build.h" +#include "clonesetup-ioctl.h" +#include "extract-word.h" +#include "format-table.h" +#include "help-util.h" +#include "log.h" +#include "main-func.h" +#include "options.h" +#include "parse-util.h" +#include "path-util.h" /* path_join */ +#include "string-util.h" +#include "strv.h" /* strv_skip */ +#include "verbs.h" + +/* region_size: size of each dm-clone region in 512-byte sectors. + * Must be a power of 2 between 8 (4 KiB) and 2097152 (1 GiB) per dm-clone kernel docs. */ +#define CLONE_REGION_SIZE_DEFAULT (UINT64_C(1) << 3) /* 8 sectors = 4 KiB */ +#define CLONE_REGION_SIZE_MIN (UINT64_C(1) << 3) /* 8 sectors = 4 KiB */ +#define CLONE_REGION_SIZE_MAX (UINT64_C(1) << 21) /* 2097152 sectors = 1 GiB */ + +static int parse_clone_options(const char *options, uint64_t *ret_region_size) { + uint64_t region_size = CLONE_REGION_SIZE_DEFAULT; + + assert(ret_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 - as empty — common placeholders for "no options" */ + if (streq(word, "-")) + continue; + + if ((val = startswith(word, "region_size="))) { + uint64_t r_size; + r = safe_atou64(val, &r_size); + if (r < 0) + log_warning_errno(r, "Failed to parse region_size= value '%s', using default.", val); + else if (!ISPOWEROF2(r_size) || r_size < CLONE_REGION_SIZE_MIN || r_size > CLONE_REGION_SIZE_MAX) + log_warning("region_size=%s must be a power of two between 8 and 2097152, using default.", val); + else + region_size = r_size; + } else { + /* currently only region_size is supported */ + log_warning("Unknown clone option '%s', ignoring.", word); + } + } + *ret_region_size = region_size; + return 0; +} + +/* dm-clone device creation workflow: + * 1. Create the dm-clone device + * 2. Enable background hydration */ +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 r; + + r = dm_clone_create_device(clone_name, source_dev, dest_dev, metadata_dev, region_size); + if (r < 0) + return r; + + r = dm_clone_send_message(clone_name, "enable_hydration"); + if (r < 0) { + (void) dm_clone_remove_device_deferred(clone_name); + return r; + } + return 0; +} + +/* Argument validation — what each check covers: + * /, .., leading ., empty name → filename_is_valid() on name + * control chars, \, ', whitespace → string_is_safe() on device paths + * .. in device paths → path_is_normalized() + * non-/dev/ device paths → path_is_absolute() + path_startswith(path, "/dev/") */ +static int validate_dev_path(const char *what, const char *path) { + if (!string_is_safe(path, 0) || !path_is_valid(path) || !path_is_normalized(path) || + !path_is_absolute(path) || !path_startswith(path, "/dev/")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid %s device path '%s'.", what, path); + return 0; +} + +static int validate_fields(const char *name, const char *src, const char *dst, + const char *meta, const char *options) { + if (!filename_is_valid(name)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid clone name '%s'.", name); + + int r; + r = validate_dev_path("source", src); + if (r < 0) + return r; + r = validate_dev_path("destination", dst); + if (r < 0) + return r; + r = validate_dev_path("metadata", meta); + if (r < 0) + return r; + if (!string_is_safe(options, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid options '%s'.", options); + return 0; +} + +VERB(verb_add, "add", "NAME SOURCE DEST METADATA [OPTIONS]", 5, 6, 0, "Create a dm-clone device"); + +/* Arguments: systemd-clonesetup add NAME SOURCE DEST METADATA [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]); + const char *src = ASSERT_PTR(argv[2]); + const char *dst = ASSERT_PTR(argv[3]); + const char *meta = ASSERT_PTR(argv[4]); + + const char *options = argc == 6 ? argv[5] : ""; + + r = validate_fields(name, src, dst, meta, options); + if (r < 0) + return r; + + log_debug("%s %s %s %s %s opts=%s", __func__, + name, src, dst, meta, options); + return clone_device(name, src, dst, meta, options); +} + +VERB(verb_remove, "remove", "NAME", 2, 2, 0, "Remove a dm-clone device"); + +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 == -ENXIO) { + log_info("Device %s already inactive.", name); + return 0; + } + if (r == -EBUSY) { + r = dm_clone_remove_device_deferred(name); + return r == -ENXIO ? 0 : r; + } + if (r < 0) + return r; + + 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("add NAME SOURCE DEST METADATA [OPTIONS]"); + help_cmdline("remove NAME"); + help_abstract("Add or remove a dm-clone device."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-clonesetup", "8"); + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + } + + 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; + + return dispatch_verb(strv_skip(argv, 1), /* userdata= */ NULL); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/clonesetup/meson.build b/src/clonesetup/meson.build new file mode 100644 index 0000000000000..47af3060d43e1 --- /dev/null +++ b/src/clonesetup/meson.build @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +systemd_clonesetup_sources = files( + 'clonesetup-ioctl.c', + 'clonesetup.c', +) + +executables += [ + libexec_template + { + 'name' : 'systemd-clonesetup', + 'sources' : systemd_clonesetup_sources, + }, + generator_template + { + 'name' : 'systemd-clonesetup-generator', + 'sources' : files('clonesetup-generator.c'), + }, +] diff --git a/src/core/automount.c b/src/core/automount.c index 5fd9c8f4ccbc2..a0529cda5f26d 100644 --- a/src/core/automount.c +++ b/src/core/automount.c @@ -20,7 +20,7 @@ #include "io-util.h" #include "label-util.h" #include "manager.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount.h" #include "mount-util.h" #include "mountpoint-util.h" diff --git a/src/core/bpf-bind-iface.c b/src/core/bpf-bind-iface.c index fdd2e1d8e6318..6d9ecadcec732 100644 --- a/src/core/bpf-bind-iface.c +++ b/src/core/bpf-bind-iface.c @@ -12,9 +12,9 @@ #if BPF_FRAMEWORK /* libbpf, clang, llvm and bpftool compile time dependencies are satisfied */ -#include "bpf-dlopen.h" +#include "bpf-util.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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 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..93f7b800b5b57 100644 --- a/src/core/bpf-restrict-fs.c +++ b/src/core/bpf-restrict-fs.c @@ -15,9 +15,9 @@ #if BPF_FRAMEWORK /* libbpf, clang and llc compile time dependencies are satisfied */ -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" -#include "bpf/restrict-fs/restrict-fs-skel.h" +#include "restrict-fs-skel.h" #define CGROUP_HASH_SIZE_MAX 2048 @@ -30,19 +30,6 @@ static struct restrict_fs_bpf *restrict_fs_bpf_free(struct restrict_fs_bpf *obj) DEFINE_TRIVIAL_CLEANUP_FUNC(struct restrict_fs_bpf *, restrict_fs_bpf_free); -static bool bpf_can_link_lsm_program(struct bpf_program *prog) { - _cleanup_(bpf_link_freep) struct bpf_link *link = NULL; - - assert(prog); - - link = sym_bpf_program__attach_lsm(prog); - - /* If bpf_program__attach_lsm fails the resulting value stores libbpf error code instead of memory - * pointer. That is the case when the helper is called on architectures where BPF trampoline (hence - * BPF_LSM_MAC attach type) is not supported. */ - return bpf_get_error_translated(link) == 0; -} - static int prepare_restrict_fs_bpf(struct restrict_fs_bpf **ret_obj) { _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL; _cleanup_close_ int inner_map_fd = -EBADF; @@ -91,7 +78,7 @@ bool bpf_restrict_fs_supported(bool initialize) { if (!initialize) return false; - if (dlopen_bpf_full(LOG_WARNING) < 0) + if (DLOPEN_BPF(LOG_WARNING, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 0) return (supported = false); r = lsm_supported("bpf"); diff --git a/src/core/bpf-restrict-fsaccess.c b/src/core/bpf-restrict-fsaccess.c new file mode 100644 index 0000000000000..d132c34164df5 --- /dev/null +++ b/src/core/bpf-restrict-fsaccess.c @@ -0,0 +1,564 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "bpf-restrict-fsaccess.h" +#include "devnum-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "initrd-util.h" +#include "log.h" +#include "lsm-util.h" +#include "manager.h" +#include "memory-util.h" +#include "serialize.h" +#include "string-table.h" + +/* DMVERITY_DEVICES_MAX lives in bpf-restrict-fsaccess.h for sharing with tests. */ + +static const char* const restrict_filesystem_access_table[_RESTRICT_FILESYSTEM_ACCESS_MAX] = { + [RESTRICT_FILESYSTEM_ACCESS_NO] = "no", + [RESTRICT_FILESYSTEM_ACCESS_EXEC] = "exec", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(restrict_filesystem_access, RestrictFileSystemAccess, RESTRICT_FILESYSTEM_ACCESS_EXEC); + +const char* const restrict_fsaccess_link_names[_RESTRICT_FILESYSTEM_ACCESS_LINK_MAX] = { + [RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_SETINTEGRITY] = "restrict-fsaccess-bdev-setintegrity-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_FREE] = "restrict-fsaccess-bdev-free-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPRM_CHECK] = "restrict-fsaccess-bprm-check-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_MMAP_FILE] = "restrict-fsaccess-mmap-file-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_FILE_MPROTECT] = "restrict-fsaccess-file-mprotect-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_PTRACE_GUARD] = "restrict-fsaccess-ptrace-guard-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_MAP_GUARD] = "restrict-fsaccess-bpf-map-guard-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_PROG_GUARD] = "restrict-fsaccess-bpf-prog-guard-link", + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_GUARD] = "restrict-fsaccess-bpf-guard-link", +}; + +#if BPF_FRAMEWORK && HAVE_LSM_INTEGRITY_TYPE +#include "bpf-util.h" +#include "bpf-link.h" +#include "restrict-fsaccess-skel.h" + +static struct restrict_fsaccess_bpf *restrict_fsaccess_bpf_free(struct restrict_fsaccess_bpf *obj) { + restrict_fsaccess_bpf__destroy(obj); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct restrict_fsaccess_bpf *, restrict_fsaccess_bpf_free); + +/* Offset of initramfs_s_dev within the BPF program's .bss section, taken from + * the generated skeleton so it matches whichever BPF compiler (clang or gcc) + * emitted the object. clang and gcc do not necessarily place .bss globals in + * source declaration order, so the offset must come from the skeleton rather + * than a hand-maintained mirror struct. restrict_fsaccess_clear_initramfs_trust() + * mmaps the .bss map and clears this field via a single store. */ +#define INITRAMFS_S_DEV_OFF \ + offsetof(typeof_field(struct restrict_fsaccess_bpf, bss[0]), initramfs_s_dev) +/* The single aligned 32-bit store in restrict_fsaccess_clear_initramfs_trust() + * is only atomic if the field is 4-byte aligned. */ +assert_cc(INITRAMFS_S_DEV_OFF % sizeof(uint32_t) == 0); +/* The store is a fixed 4-byte write; pin its width to the skeleton's field + * width so widening initramfs_s_dev fails the build instead of silently + * clearing only the low bytes. */ +assert_cc(sizeof_field(typeof_field(struct restrict_fsaccess_bpf, bss[0]), initramfs_s_dev) == sizeof(uint32_t)); + +/* Build the skeleton links array indexed by the link enum. + * For BDEV_SETINTEGRITY, use whichever variant was loaded (full or compat). + * This compat logic can be removed once the kernel baseline includes + * 1271a40eeafa ("bpf: Allow access to const void pointer arguments"). */ +#define RESTRICT_FSACCESS_LINKS(obj) { \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_SETINTEGRITY] = (obj)->links.restrict_fsaccess_bdev_setintegrity ?: \ + (obj)->links.restrict_fsaccess_bdev_setintegrity_compat, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_FREE] = (obj)->links.restrict_fsaccess_bdev_free, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPRM_CHECK] = (obj)->links.restrict_fsaccess_bprm_check, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_MMAP_FILE] = (obj)->links.restrict_fsaccess_mmap_file, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_FILE_MPROTECT] = (obj)->links.restrict_fsaccess_file_mprotect, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_PTRACE_GUARD] = (obj)->links.restrict_fsaccess_ptrace_guard, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_MAP_GUARD] = (obj)->links.restrict_fsaccess_bpf_map_guard, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_PROG_GUARD] = (obj)->links.restrict_fsaccess_bpf_prog_guard, \ + [RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_GUARD] = (obj)->links.restrict_fsaccess_bpf_guard, \ +} + +bool dm_verity_require_signatures(void) { + int r; + + r = read_boolean_file("/sys/module/dm_verity/parameters/require_signatures"); + if (r < 0) { + if (r != -ENOENT) + log_warning_errno(r, "bpf-restrict-fsaccess: Failed to read dm-verity require_signatures: %m"); + return false; + } + + return r > 0; +} + +static int get_root_s_dev(uint32_t *ret) { + struct stat st; + + assert(ret); + + /* Stat /usr/ rather than / — executable code lives in /usr/ and we push toward + * a writable non-executable /. On systems with a separate /usr partition this + * means / is intentionally not trusted. */ + if (stat("/usr/", &st) < 0) + return log_error_errno(errno, "bpf-restrict-fsaccess: Failed to stat /usr/ filesystem: %m"); + + *ret = STAT_DEV_TO_KERNEL(st.st_dev); + return 0; +} + +int bpf_restrict_fsaccess_prepare(struct restrict_fsaccess_bpf **ret) { + _cleanup_(restrict_fsaccess_bpf_freep) struct restrict_fsaccess_bpf *obj = NULL; + int r; + + assert(ret); + + /* Try the preferred version first — it reads the const void *value + * argument for defense-in-depth. On kernels before v6.16 (missing + * 1271a40eeafa) the verifier rejects loads from const void * context + * arguments, so we fall back to the _compat variant that only reads + * the size argument via raw ctx access. */ + obj = restrict_fsaccess_bpf__open(); + if (!obj) + return log_error_errno(errno, "bpf-restrict-fsaccess: Failed to open BPF object: %m"); + + r = sym_bpf_map__set_max_entries(obj->maps.verity_devices, DMVERITY_DEVICES_MAX); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to size hash table: %m"); + + r = sym_bpf_program__set_autoload(obj->progs.restrict_fsaccess_bdev_setintegrity_compat, false); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to disable compat program: %m"); + + r = restrict_fsaccess_bpf__load(obj); + if (r >= 0) { + log_debug("bpf-restrict-fsaccess: Loaded with full const void * access."); + *ret = TAKE_PTR(obj); + return 0; + } + + log_debug_errno(r, "bpf-restrict-fsaccess: Full version failed to load (%m), trying compat variant."); + obj = restrict_fsaccess_bpf_free(obj); + + obj = restrict_fsaccess_bpf__open(); + if (!obj) + return log_error_errno(errno, "bpf-restrict-fsaccess: Failed to reopen BPF object: %m"); + + r = sym_bpf_map__set_max_entries(obj->maps.verity_devices, DMVERITY_DEVICES_MAX); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to size hash table: %m"); + + r = sym_bpf_program__set_autoload(obj->progs.restrict_fsaccess_bdev_setintegrity, false); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to disable full program: %m"); + + r = restrict_fsaccess_bpf__load(obj); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to load BPF object (compat): %m"); + + log_debug("bpf-restrict-fsaccess: Loaded with compat bdev_setintegrity."); + *ret = TAKE_PTR(obj); + return 0; +} + +bool bpf_restrict_fsaccess_supported(void) { + _cleanup_(restrict_fsaccess_bpf_freep) struct restrict_fsaccess_bpf *obj = NULL; + static int supported = -1; + int r; + + if (supported >= 0) + return supported; + if (DLOPEN_BPF(LOG_WARNING, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 0) + return (supported = false); + + r = lsm_supported("bpf"); + if (r == -ENOPKG) { + log_debug_errno(r, "bpf-restrict-fsaccess: securityfs not mounted, BPF LSM not available."); + return (supported = false); + } + if (r < 0) { + log_warning_errno(r, "bpf-restrict-fsaccess: Can't determine whether the BPF LSM module is used: %m"); + return (supported = false); + } + if (r == 0) { + log_info("bpf-restrict-fsaccess: BPF LSM hook not enabled in the kernel, not supported."); + return (supported = false); + } + + r = bpf_restrict_fsaccess_prepare(&obj); + if (r < 0) + return (supported = false); + + if (!bpf_can_link_lsm_program(obj->progs.restrict_fsaccess_bprm_check)) { + log_warning("bpf-restrict-fsaccess: Failed to link program; assuming BPF LSM is not available."); + return (supported = false); + } + + return (supported = true); +} + +/* Partial deserialization (some FDs but not all) is fatal: continuing + * would leave enforcement incomplete. */ +static int restrict_fsaccess_have_deserialized_fds(Manager *m) { + size_t count = 0; + + assert(m); + + FOREACH_ELEMENT(fd, m->restrict_fsaccess_link_fds) + if (*fd >= 0) + count++; + + if (count == 0) + return 0; + if (count == ELEMENTSOF(m->restrict_fsaccess_link_fds)) + return 1; + + return log_error_errno(SYNTHETIC_ERRNO(EBADFD), + "bpf-restrict-fsaccess: Only %zu of %zu link FDs deserialized, refusing to continue with partial enforcement.", + count, ELEMENTSOF(m->restrict_fsaccess_link_fds)); +} + +/* Close the initramfs trust window after switch_root by clearing initramfs_s_dev + * in the BPF .bss map. The .bss is a BPF_F_MMAPABLE array map — mmap it and do + * a single aligned 4-byte store instead of a full-value read-modify-write via + * bpf_map_update_elem, which would needlessly rewrite the guard globals too. */ +static int restrict_fsaccess_clear_initramfs_trust(int bss_map_fd) { + void *p; + + assert(bss_map_fd >= 0); + + p = mmap(NULL, page_size(), PROT_READ | PROT_WRITE, MAP_SHARED, bss_map_fd, 0); + if (p == MAP_FAILED) + return log_error_errno(errno, "bpf-restrict-fsaccess: Failed to mmap .bss map: %m"); + + /* Single aligned 32-bit store at INITRAMFS_S_DEV_OFF is atomic — BPF + * programs see either the old or new value, no torn reads possible. + * Guard globals are untouched. */ + *(uint32_t *) ((uint8_t *) p + INITRAMFS_S_DEV_OFF) = 0; + + /* munmap failure here is harmless: the clear above already landed in + * the kernel, and the mapping is discarded by exec anyway. */ + if (munmap(p, page_size()) < 0) + log_warning_errno(errno, "bpf-restrict-fsaccess: Failed to munmap .bss map, ignoring: %m"); + + log_info("bpf-restrict-fsaccess: Cleared initramfs trust window after switch_root."); + return 0; +} + +static int bpf_get_map_id(int fd, uint32_t *ret_id) { + struct bpf_map_info info = {}; + uint32_t len = sizeof(info); + int r; + + if (fd < 0) + return -EBADF; + + assert(ret_id); + + r = sym_bpf_obj_get_info_by_fd(fd, &info, &len); + if (r < 0) + return r; + + *ret_id = info.id; + return 0; +} + +static int bpf_get_link_ids(int fd, uint32_t *ret_link_id, uint32_t *ret_prog_id) { + struct bpf_link_info info = {}; + uint32_t len = sizeof(info); + int r; + + if (fd < 0) + return -EBADF; + + r = sym_bpf_obj_get_info_by_fd(fd, &info, &len); + if (r < 0) + return r; + + if (ret_link_id) + *ret_link_id = info.id; + if (ret_prog_id) + *ret_prog_id = info.prog_id; + + return 0; +} + +/* Populate guard globals with kernel-assigned IDs so the guard hooks block + * non-PID1 access to our maps/progs/links via the bpf() syscall. */ +int bpf_restrict_fsaccess_populate_guard(struct restrict_fsaccess_bpf *obj) { + int r; + + assert(obj); + + struct bpf_link *links[] = RESTRICT_FSACCESS_LINKS(obj); + assert_cc(ELEMENTSOF(links) == _RESTRICT_FILESYSTEM_ACCESS_LINK_MAX); + + /* Map IDs */ + r = bpf_get_map_id(sym_bpf_map__fd(obj->maps.verity_devices), &obj->bss->protected_map_id_verity); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to get verity_devices map ID: %m"); + + r = bpf_get_map_id(sym_bpf_map__fd(obj->maps.bss), &obj->bss->protected_map_id_bss); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to get .bss map ID: %m"); + + /* Link and program IDs (each link knows its associated program) */ + FOREACH_ELEMENT(link, links) { + size_t idx = link - links; + + /* BDEV_SETINTEGRITY slot resolves via ?: between full and compat + * variants; assert at least one was attached. */ + if (!*link) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), + "bpf-restrict-fsaccess: %s link missing after attach.", + restrict_fsaccess_link_names[idx]); + + r = bpf_get_link_ids(sym_bpf_link__fd(*link), + &obj->bss->protected_link_ids[idx], + &obj->bss->protected_prog_ids[idx]); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to get link/prog IDs for %s: %m", + restrict_fsaccess_link_names[idx]); + } + + log_info("bpf-restrict-fsaccess: Guard globals populated (verity_map=%u, bss_map=%u)", + (unsigned) obj->bss->protected_map_id_verity, + (unsigned) obj->bss->protected_map_id_bss); + return 0; +} + +/* Validate that deserialized FDs actually reference our LSM BPF links. A + * corrupted serialization file could leave FDs pointing at arbitrary kernel + * objects; a stale FD could point at a BPF link of an entirely different type + * (e.g. kprobe-multi). Verify both link type and attach type so a substituted + * FD that happens to be a BPF link still fails the check. */ +static int restrict_fsaccess_validate_deserialized_fds(Manager *m) { + int r; + + assert(m); + + r = DLOPEN_BPF(LOG_WARNING, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "bpf-restrict-fsaccess: Failed to load libbpf for FD validation, aborting."); + + FOREACH_ELEMENT(fd, m->restrict_fsaccess_link_fds) { + struct bpf_link_info info = {}; + uint32_t len = sizeof(info); + const char *name = restrict_fsaccess_link_names[fd - m->restrict_fsaccess_link_fds]; + + r = sym_bpf_obj_get_info_by_fd(*fd, &info, &len); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "bpf-restrict-fsaccess: Deserialized FD for %s is not a valid BPF object, aborting.", + name); + + if (info.type != BPF_LINK_TYPE_TRACING || info.tracing.attach_type != BPF_LSM_MAC) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "bpf-restrict-fsaccess: Deserialized FD for %s is not an LSM tracing link (type=%u attach=%u), aborting.", + name, info.type, info.tracing.attach_type); + } + + if (m->restrict_fsaccess_bss_map_fd >= 0) { + uint32_t id; + + r = bpf_get_map_id(m->restrict_fsaccess_bss_map_fd, &id); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "bpf-restrict-fsaccess: Deserialized FD for .bss map is not a valid BPF map, aborting."); + } + + return 0; +} + +int bpf_restrict_fsaccess_setup(Manager *m) { + _cleanup_(restrict_fsaccess_bpf_freep) struct restrict_fsaccess_bpf *obj = NULL; + int r; + + assert(m); + + if (!MANAGER_IS_SYSTEM(m) || m->restrict_filesystem_access <= RESTRICT_FILESYSTEM_ACCESS_NO) + return 0; + + r = restrict_fsaccess_have_deserialized_fds(m); + if (r < 0) + return r; + if (r > 0) { + log_info("bpf-restrict-fsaccess: Recovered link FDs from previous exec, programs still attached."); + + r = restrict_fsaccess_validate_deserialized_fds(m); + if (r < 0) + return r; + if (m->switching_root) { + if (m->restrict_fsaccess_bss_map_fd < 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADF), + "bpf-restrict-fsaccess: Cannot clear initramfs trust after switch_root."); + r = restrict_fsaccess_clear_initramfs_trust(m->restrict_fsaccess_bss_map_fd); + if (r < 0) + return r; + } + + return 0; + } + + /* Fresh setup: verify BPF LSM is available */ + if (!bpf_restrict_fsaccess_supported()) + return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "bpf-restrict-fsaccess: BPF LSM is not available."); + + /* Require dm-verity signature enforcement */ + if (!dm_verity_require_signatures()) + return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), + "bpf-restrict-fsaccess: dm-verity require_signatures is not enabled. " + "RestrictFileSystemAccess= requires the kernel to enforce dm-verity signatures. " + "Set dm_verity.require_signatures=1 on the kernel command line."); + + r = bpf_restrict_fsaccess_prepare(&obj); + if (r < 0) + return r; + + /* If we're still in the initramfs, allow execution from it by recording + * its s_dev. After switch_root, PID1 re-execs and in_initrd() returns + * false — initramfs_s_dev stays at 0 (its default), closing the trust + * window. */ + if (in_initrd()) { + uint32_t root_dev; + + r = get_root_s_dev(&root_dev); + if (r < 0) + return r; + + obj->bss->initramfs_s_dev = root_dev; + log_info("bpf-restrict-fsaccess: Initramfs trusted (s_dev=%" PRIu32 ":%" PRIu32 ")", + root_dev >> 20, root_dev & 0xFFFFF); + } + + r = restrict_fsaccess_bpf__attach(obj); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fsaccess: Failed to attach BPF programs: %m"); + + log_info("bpf-restrict-fsaccess: LSM BPF programs attached"); + + /* Now that all programs are attached, populate the guard's globals with + * the kernel-assigned IDs of our maps, programs, and links. From this + * point on, non-PID1 processes cannot obtain FDs to our BPF objects. */ + r = bpf_restrict_fsaccess_populate_guard(obj); + if (r < 0) + return r; + + /* Extract owned FDs from the skeleton. These keep the kernel BPF objects + * alive after the skeleton is destroyed. Destroying the skeleton unmaps + * the .bss page from our address space so no BPF state (guard globals, + * map IDs, initramfs_s_dev) is reachable via /proc/1/mem. */ + struct bpf_link *links[] = RESTRICT_FSACCESS_LINKS(obj); + FOREACH_ELEMENT(link, links) { + size_t idx = link - links; + + if (!*link) { + r = log_error_errno(SYNTHETIC_ERRNO(ENODATA), + "bpf-restrict-fsaccess: %s link missing after attach.", + restrict_fsaccess_link_names[idx]); + goto fail; + } + + m->restrict_fsaccess_link_fds[idx] = fcntl(sym_bpf_link__fd(*link), F_DUPFD_CLOEXEC, 3); + if (m->restrict_fsaccess_link_fds[idx] < 0) { + r = log_error_errno(errno, "bpf-restrict-fsaccess: Failed to dup link FD for %s: %m", + restrict_fsaccess_link_names[idx]); + goto fail; + } + } + + m->restrict_fsaccess_bss_map_fd = fcntl(sym_bpf_map__fd(obj->maps.bss), F_DUPFD_CLOEXEC, 3); + if (m->restrict_fsaccess_bss_map_fd < 0) { + r = log_error_errno(errno, "bpf-restrict-fsaccess: Failed to dup .bss map FD: %m"); + goto fail; + } + + return 0; + +fail: + /* Close partial FDs so we don't leave a half-baked policy attached + * once the skeleton is destroyed by _cleanup_. */ + FOREACH_ELEMENT(fd, m->restrict_fsaccess_link_fds) + *fd = safe_close(*fd); + m->restrict_fsaccess_bss_map_fd = safe_close(m->restrict_fsaccess_bss_map_fd); + return r; +} + +int bpf_restrict_fsaccess_close_initramfs_trust(Manager *m) { + assert(m); + + /* Clear initramfs_s_dev in the BPF .bss map BEFORE switch_root unmounts + * the initramfs. This eliminates the dev_t recycling window: the anonymous + * dev_t is still held by the mounted initramfs superblock, so no other + * filesystem can recycle it yet. Anonymous dev_t recycling is immediate + * and lowest-first, so a stale initramfs_s_dev is a near-certain trust + * bypass — fail closed. */ + if (!in_initrd() || m->restrict_fsaccess_bss_map_fd < 0) + return 0; + + return restrict_fsaccess_clear_initramfs_trust(m->restrict_fsaccess_bss_map_fd); +} + +int bpf_restrict_fsaccess_serialize(Manager *m, FILE *f, FDSet *fds) { + int r; + + assert(m); + assert(f); + assert(fds); + + if (!MANAGER_IS_SYSTEM(m) || m->restrict_filesystem_access <= RESTRICT_FILESYSTEM_ACCESS_NO) + return 0; + + FOREACH_ELEMENT(fd, m->restrict_fsaccess_link_fds) { + r = serialize_fd(f, fds, restrict_fsaccess_link_names[fd - m->restrict_fsaccess_link_fds], *fd); + if (r < 0) + return r; + } + + r = serialize_fd(f, fds, "restrict-fsaccess-bss-map", m->restrict_fsaccess_bss_map_fd); + if (r < 0) + return r; + + return 0; +} + +#else /* ! BPF_FRAMEWORK || ! HAVE_LSM_INTEGRITY_TYPE */ + +bool dm_verity_require_signatures(void) { + return false; +} + +bool bpf_restrict_fsaccess_supported(void) { + return false; +} + +int bpf_restrict_fsaccess_setup(Manager *m) { + if (!MANAGER_IS_SYSTEM(m) || m->restrict_filesystem_access <= RESTRICT_FILESYSTEM_ACCESS_NO) + return 0; + + return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "bpf-restrict-fsaccess: RestrictFileSystemAccess= requested but BPF framework is not compiled in."); +} + +int bpf_restrict_fsaccess_prepare(struct restrict_fsaccess_bpf **ret) { + return -EOPNOTSUPP; +} + +int bpf_restrict_fsaccess_populate_guard(struct restrict_fsaccess_bpf *obj) { + return 0; +} + +int bpf_restrict_fsaccess_close_initramfs_trust(Manager *m) { + return 0; +} + +int bpf_restrict_fsaccess_serialize(Manager *m, FILE *f, FDSet *fds) { + return 0; +} + +#endif diff --git a/src/core/bpf-restrict-fsaccess.h b/src/core/bpf-restrict-fsaccess.h new file mode 100644 index 0000000000000..3203f0fc7e533 --- /dev/null +++ b/src/core/bpf-restrict-fsaccess.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" +#include "macro.h" +#include "shared-forward.h" + +typedef enum RestrictFileSystemAccess { + RESTRICT_FILESYSTEM_ACCESS_NO, + RESTRICT_FILESYSTEM_ACCESS_EXEC, + _RESTRICT_FILESYSTEM_ACCESS_MAX, + _RESTRICT_FILESYSTEM_ACCESS_INVALID = -EINVAL, +} RestrictFileSystemAccess; + +const char* restrict_filesystem_access_to_string(RestrictFileSystemAccess i) _const_; +RestrictFileSystemAccess restrict_filesystem_access_from_string(const char *s) _pure_; + +enum { + RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_SETINTEGRITY, + RESTRICT_FILESYSTEM_ACCESS_LINK_BDEV_FREE, + RESTRICT_FILESYSTEM_ACCESS_LINK_BPRM_CHECK, + RESTRICT_FILESYSTEM_ACCESS_LINK_MMAP_FILE, + RESTRICT_FILESYSTEM_ACCESS_LINK_FILE_MPROTECT, + RESTRICT_FILESYSTEM_ACCESS_LINK_PTRACE_GUARD, + RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_MAP_GUARD, + RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_PROG_GUARD, + RESTRICT_FILESYSTEM_ACCESS_LINK_BPF_GUARD, + _RESTRICT_FILESYSTEM_ACCESS_LINK_MAX, +}; + +/* Maximum number of dm-verity devices tracked in the BPF hash map. */ +#define DMVERITY_DEVICES_MAX (16U*1024U) + +extern const char* const restrict_fsaccess_link_names[_RESTRICT_FILESYSTEM_ACCESS_LINK_MAX]; + +bool dm_verity_require_signatures(void); +bool bpf_restrict_fsaccess_supported(void); +int bpf_restrict_fsaccess_setup(Manager *m); +int bpf_restrict_fsaccess_prepare(struct restrict_fsaccess_bpf **ret); +int bpf_restrict_fsaccess_populate_guard(struct restrict_fsaccess_bpf *obj); + +int bpf_restrict_fsaccess_close_initramfs_trust(Manager *m); +int bpf_restrict_fsaccess_serialize(Manager *m, FILE *f, FDSet *fds); diff --git a/src/core/bpf-restrict-ifaces.c b/src/core/bpf-restrict-ifaces.c index a1bac8301be34..9ca144a334edb 100644 --- a/src/core/bpf-restrict-ifaces.c +++ b/src/core/bpf-restrict-ifaces.c @@ -14,9 +14,9 @@ #if BPF_FRAMEWORK /* libbpf, clang and llc compile time dependencies are satisfied */ -#include "bpf-dlopen.h" +#include "bpf-util.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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 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..3751d62430864 100644 --- a/src/core/bpf-socket-bind.c +++ b/src/core/bpf-socket-bind.c @@ -9,10 +9,10 @@ #if BPF_FRAMEWORK /* libbpf, clang, llvm and bpftool compile time dependencies are satisfied */ -#include "bpf-dlopen.h" +#include "bpf-util.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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 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..543d1ac8e3c43 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -178,6 +178,8 @@ void cgroup_context_init(CGroupContext *c) { .tasks_max = CGROUP_TASKS_MAX_UNSET, + .cpuset_partition = _CPUSET_PARTITION_INVALID, + .moom_swap = MANAGED_OOM_AUTO, .moom_mem_pressure = MANAGED_OOM_AUTO, .moom_preference = MANAGED_OOM_PREFERENCE_NONE, @@ -185,8 +187,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 }, + }, }; } @@ -279,6 +284,8 @@ void cgroup_context_done(CGroupContext *c) { c->delegate_subgroup = mfree(c->delegate_subgroup); + c->moom_rules = strv_free(c->moom_rules); + nft_set_context_clear(&c->nft_set_context); } @@ -313,6 +320,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)) @@ -503,6 +512,7 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { "%sStartupAllowedCPUs: %s\n" "%sAllowedMemoryNodes: %s\n" "%sStartupAllowedMemoryNodes: %s\n" + "%sCPUSetPartition: %s\n" "%sIOWeight: %" PRIu64 "\n" "%sStartupIOWeight: %" PRIu64 "\n" "%sMemoryMin: %" PRIu64 "%s\n" @@ -526,6 +536,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), @@ -539,6 +551,7 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { prefix, strempty(startup_cpuset_cpus), prefix, strempty(cpuset_mems), prefix, strempty(startup_cpuset_mems), + prefix, strna(cpuset_partition_to_string(c->cpuset_partition)), prefix, c->io_weight, prefix, c->startup_io_weight, prefix, c->memory_min, format_cgroup_memory_limit_comparison(u, "MemoryMin", cda, sizeof(cda)), @@ -561,7 +574,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 +587,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", @@ -649,6 +672,9 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { FOREACH_ARRAY(nft_set, c->nft_set_context.sets, c->nft_set_context.n_sets) fprintf(f, "%sNFTSet: %s:%s:%s:%s\n", prefix, nft_set_source_to_string(nft_set->source), nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set); + + STRV_FOREACH(rule, c->moom_rules) + fprintf(f, "%sOOMRules: %s\n", prefix, *rule); } void cgroup_context_dump_socket_bind_item(const CGroupSocketBindItem *item, FILE *f) { @@ -1103,7 +1129,7 @@ static void cgroup_apply_cpu_quota(Unit *u, usec_t quota, usec_t period) { (void) set_attribute_and_warn(u, "cpu.max", buf); } -static void cgroup_apply_cpuset(Unit *u, const CPUSet *cpus, const char *name) { +static void cgroup_apply_cpuset(Unit *u, const char *name, const CPUSet *cpus) { _cleanup_free_ char *buf = NULL; buf = cpu_set_to_range_string(cpus); @@ -1115,6 +1141,53 @@ static void cgroup_apply_cpuset(Unit *u, const CPUSet *cpus, const char *name) { (void) set_attribute_and_warn(u, name, buf); } +static int cgroup_cpuset_partition_invalid(const char *partition) { + _cleanup_free_ char *part_str = NULL, *invalid = NULL; + int r; + + assert(partition); + + /* An invalid line looks like invalid () */ + r = extract_many_words(&partition, /* separators= */ NULL, /* flags= */ 0, &part_str, &invalid); + if (r < 0) + return r; + if (r < 2) + return false; + + return streq_ptr(invalid, "invalid"); +} + +static void cgroup_apply_cpuset_partition(Unit *u, const char *name, const char *partition) { + _cleanup_free_ char *buf = NULL; + CGroupRuntime *crt; + int r; + + assert(u); + assert(name); + assert(partition); + + if (set_attribute_and_warn(u, name, partition) < 0) + return; + + /* We are writing and then reading back, crt is already checked while writing */ + crt = ASSERT_PTR(unit_get_cgroup_runtime(u)); + + r = cg_get_attribute(crt->cgroup_path, name, &buf); + if (r < 0) { + log_unit_full_errno(u, LOG_LEVEL_CGROUP_WRITE(r), r, "Failed to read back '%s' attribute on '%s' as '%.*s': %m", + name, empty_to_root(crt->cgroup_path), (int) strcspn(partition, NEWLINE), partition); + return; + } + + r = cgroup_cpuset_partition_invalid(buf); + if (r < 0) + log_unit_full_errno(u, LOG_LEVEL_CGROUP_WRITE(r), r, "Failed to read back '%s' attribute on '%s' as '%.*s': %m", + name, empty_to_root(crt->cgroup_path), (int) strcspn(partition, NEWLINE), partition); + else if (r) + log_unit_warning(u, "Failed to set '%s' attribute on '%s' to '%.*s': %s", + name, empty_to_root(crt->cgroup_path), (int) strcspn(partition, NEWLINE), partition, buf); +} + static bool cgroup_context_has_io_config(CGroupContext *c) { assert(c); @@ -1276,18 +1349,18 @@ static void unit_modify_nft_set(Unit *u, bool add) { if (!crt || crt->cgroup_id == 0) return; - if (!u->manager->nfnl) { - r = sd_nfnl_socket_open(&u->manager->nfnl); - if (r < 0) - return; - } - CGroupContext *c = ASSERT_PTR(unit_get_cgroup_context(u)); FOREACH_ARRAY(nft_set, c->nft_set_context.sets, c->nft_set_context.n_sets) { if (nft_set->source != NFT_SET_SOURCE_CGROUP) continue; + if (!u->manager->nfnl) { + r = sd_nfnl_socket_open(&u->manager->nfnl); + if (r < 0) + return (void) log_once_errno(LOG_WARNING, r, "Failed to open NETLINK_NETFILTER socket, ignoring: %m"); + } + uint64_t element = crt->cgroup_id; r = nft_set_element_modify_any(u->manager->nfnl, add, nft_set->nfproto, nft_set->table, nft_set->set, &element, sizeof(element)); @@ -1445,8 +1518,11 @@ static void cgroup_context_apply( } if ((apply_mask & CGROUP_MASK_CPUSET) && !is_local_root) { - cgroup_apply_cpuset(u, cgroup_context_allowed_cpus(c, state), "cpuset.cpus"); - cgroup_apply_cpuset(u, cgroup_context_allowed_mems(c, state), "cpuset.mems"); + cgroup_apply_cpuset(u, "cpuset.cpus", cgroup_context_allowed_cpus(c, state)); + cgroup_apply_cpuset(u, "cpuset.mems", cgroup_context_allowed_mems(c, state)); + + if (c->cpuset_partition >= 0) + cgroup_apply_cpuset_partition(u, "cpuset.cpus.partition", cpuset_partition_to_string(c->cpuset_partition)); } /* The 'io' controller attributes are not exported on the host's root cgroup (being a pure cgroup v2 @@ -2105,12 +2181,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 +3055,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 +3071,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 +3257,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 +3635,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 +4047,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; @@ -4553,6 +4641,14 @@ static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy); +static const char* const cpuset_partition_table[_CPUSET_PARTITION_MAX] = { + [CPUSET_PARTITION_MEMBER] = "member", + [CPUSET_PARTITION_ROOT] = "root", + [CPUSET_PARTITION_ISOLATED] = "isolated", +}; + +DEFINE_STRING_TABLE_LOOKUP(cpuset_partition, CPUSetPartition); + static const char* const cgroup_pressure_watch_table[_CGROUP_PRESSURE_WATCH_MAX] = { [CGROUP_PRESSURE_WATCH_NO] = "no", [CGROUP_PRESSURE_WATCH_YES] = "yes", diff --git a/src/core/cgroup.h b/src/core/cgroup.h index 0cd290e92f25d..e3d33ad5e0910 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. @@ -46,6 +47,14 @@ typedef enum FreezerAction { _FREEZER_ACTION_INVALID = -EINVAL, } FreezerAction; +typedef enum CPUSetPartition { + CPUSET_PARTITION_MEMBER, + CPUSET_PARTITION_ROOT, + CPUSET_PARTITION_ISOLATED, + _CPUSET_PARTITION_MAX, + _CPUSET_PARTITION_INVALID = -EINVAL, +} CPUSetPartition; + typedef enum CGroupDevicePermissions { /* We reuse the same bit meanings the kernel's BPF_DEVCG_ACC_xyz definitions use */ CGROUP_DEVICE_MKNOD = 1 << 0, @@ -95,14 +104,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. */ @@ -130,6 +144,7 @@ typedef struct CGroupContext { CPUSet startup_cpuset_cpus; CPUSet cpuset_mems; CPUSet startup_cpuset_mems; + CPUSetPartition cpuset_partition; uint64_t io_weight; uint64_t startup_io_weight; @@ -188,12 +203,10 @@ typedef struct CGroupContext { uint32_t moom_mem_pressure_limit; /* Normalized to 2^32-1 == 100% */ usec_t moom_mem_pressure_duration_usec; ManagedOOMPreference moom_preference; + char **moom_rules; - /* 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 +366,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,11 +481,14 @@ 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); DECLARE_STRING_TABLE_LOOKUP(freezer_action, FreezerAction); +DECLARE_STRING_TABLE_LOOKUP(cpuset_partition, CPUSetPartition); CGroupRuntime* cgroup_runtime_new(void); CGroupRuntime* cgroup_runtime_free(CGroupRuntime *crt); diff --git a/src/core/core-forward.h b/src/core/core-forward.h index 446cf501e43e7..14bcc142a2e0c 100644 --- a/src/core/core-forward.h +++ b/src/core/core-forward.h @@ -52,3 +52,4 @@ typedef struct Unit Unit; typedef struct UnitRef UnitRef; struct restrict_fs_bpf; +struct restrict_fsaccess_bpf; diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index dcb27f80f8c6a..168bdf10c13da 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -26,6 +26,7 @@ BUS_DEFINE_PROPERTY_GET(bus_property_get_tasks_max, "t", CGroupTasksMax, cgroup_ BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_cgroup_pressure_watch, cgroup_pressure_watch, CGroupPressureWatch); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cgroup_device_policy, cgroup_device_policy, CGroupDevicePolicy); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cpuset_partition, cpuset_partition, CPUSetPartition); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_managed_oom_mode, managed_oom_mode, ManagedOOMMode); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_managed_oom_preference, managed_oom_preference, ManagedOOMPreference); @@ -385,6 +386,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = { SD_BUS_PROPERTY("StartupAllowedCPUs", "ay", property_get_cpuset, offsetof(CGroupContext, startup_cpuset_cpus), 0), SD_BUS_PROPERTY("AllowedMemoryNodes", "ay", property_get_cpuset, offsetof(CGroupContext, cpuset_mems), 0), SD_BUS_PROPERTY("StartupAllowedMemoryNodes", "ay", property_get_cpuset, offsetof(CGroupContext, startup_cpuset_mems), 0), + SD_BUS_PROPERTY("CPUSetPartition", "s", property_get_cpuset_partition, offsetof(CGroupContext, cpuset_partition), 0), SD_BUS_PROPERTY("IOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, io_accounting), 0), SD_BUS_PROPERTY("IOWeight", "t", NULL, offsetof(CGroupContext, io_weight), 0), SD_BUS_PROPERTY("StartupIOWeight", "t", NULL, offsetof(CGroupContext, startup_io_weight), 0), @@ -422,13 +424,18 @@ const sd_bus_vtable bus_cgroup_vtable[] = { SD_BUS_PROPERTY("ManagedOOMMemoryPressureLimit", "u", NULL, offsetof(CGroupContext, moom_mem_pressure_limit), 0), SD_BUS_PROPERTY("ManagedOOMMemoryPressureDurationUSec", "t", bus_property_get_usec, offsetof(CGroupContext, moom_mem_pressure_duration_usec), 0), SD_BUS_PROPERTY("ManagedOOMPreference", "s", property_get_managed_oom_preference, offsetof(CGroupContext, moom_preference), 0), + SD_BUS_PROPERTY("OOMRules", "as", NULL, offsetof(CGroupContext, moom_rules), 0), SD_BUS_PROPERTY("BPFProgram", "a(ss)", property_get_bpf_foreign_program, 0, 0), SD_BUS_PROPERTY("SocketBindAllow", "a(iiqq)", property_get_socket_bind, offsetof(CGroupContext, socket_bind_allow), 0), 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 +719,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 +739,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 +1023,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; @@ -1483,6 +1496,35 @@ int bus_cgroup_set_property( return 1; + } else if (streq(name, "CPUSetPartition")) { + const char *partition_str; + CPUSetPartition p; + + r = sd_bus_message_read(message, "s", &partition_str); + if (r < 0) + return r; + + if (isempty(partition_str)) + p = _CPUSET_PARTITION_INVALID; + else { + p = cpuset_partition_from_string(partition_str); + if (p < 0) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, + "Invalid CPUSetPartition value: %s", partition_str); + } + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + c->cpuset_partition = p; + unit_invalidate_cgroup(u, CGROUP_MASK_CPUSET); + + if (p == _CPUSET_PARTITION_INVALID) + unit_write_settingf(u, flags, name, "%s=", name); + else + unit_write_settingf(u, flags, name, "%s=%s", name, partition_str); + } + + return 1; + } else if (streq(name, "DeviceAllow")) { const char *path, *rwm; unsigned n = 0; @@ -1721,13 +1763,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) @@ -1755,6 +1797,38 @@ int bus_cgroup_set_property( return 1; } + + if (streq(name, "OOMRules")) { + _cleanup_strv_free_ char **oom_rules = NULL; + + if (!UNIT_VTABLE(u)->can_set_managed_oom) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Cannot set %s for this unit type", name); + + r = sd_bus_message_read_strv(message, &oom_rules); + if (r < 0) + return r; + + STRV_FOREACH(rule, oom_rules) + if (!string_is_safe(*rule, STRING_FILENAME)) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid rule name: %s", *rule); + + strv_uniq(oom_rules); + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + _cleanup_free_ char *joined = strv_join(oom_rules, " "); + if (!joined) + return -ENOMEM; + + strv_free_and_replace(c->moom_rules, oom_rules); + + unit_write_settingf(u, flags, name, "OOMRules=\nOOMRules=%s", joined); + + (void) manager_varlink_send_managed_oom_update(u); + } + + return 1; + } + if (STR_IN_SET(name, "SocketBindAllow", "SocketBindDeny")) { CGroupSocketBindItem **list; uint16_t nr_ports, port_min; diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 2bd7b1c07eea3..4d86c07a41a43 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -58,7 +58,7 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_private_bpf, private_bpf, Priva static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_protect_home, protect_home, ProtectHome); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_protect_system, protect_system, ProtectSystem); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_personality, personality, unsigned long); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_memory_thp, memory_thp, MemoryTHP); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_memory_thp, exec_memory_thp, ExecMemoryTHP); static BUS_DEFINE_PROPERTY_GET(property_get_ioprio, "i", ExecContext, exec_context_get_effective_ioprio); static BUS_DEFINE_PROPERTY_GET(property_get_mount_apivfs, "b", ExecContext, exec_context_get_effective_mount_apivfs); static BUS_DEFINE_PROPERTY_GET(property_get_bind_log_sockets, "b", ExecContext, exec_context_get_effective_bind_log_sockets); @@ -123,19 +123,20 @@ static int property_get_cpu_affinity( _cleanup_(cpu_set_done) CPUSet s = {}; _cleanup_free_ uint8_t *array = NULL; size_t allocated; + int r; assert(bus); assert(reply); if (c->cpu_affinity_from_numa) { - int r; - r = numa_to_cpu_set(&c->numa_policy, &s); if (r < 0) return r; } - (void) cpu_set_to_dbus(c->cpu_affinity_from_numa ? &s : &c->cpu_set, &array, &allocated); + r = cpu_set_to_dbus(c->cpu_affinity_from_numa ? &s : &c->cpu_set, &array, &allocated); + if (r < 0) + return r; return sd_bus_message_append_array(reply, 'y', array, allocated); } @@ -1002,7 +1003,7 @@ static int property_get_exec_quota(sd_bus *bus, void *userdata, sd_bus_error *reterr_error) { - QuotaLimit *q = ASSERT_PTR(userdata); + ExecQuotaLimit *q = ASSERT_PTR(userdata); assert(bus); assert(reply); @@ -1399,7 +1400,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("BPFDelegatePrograms", "s", property_get_bpf_delegate_programs, offsetof(ExecContext, bpf_delegate_programs), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("BPFDelegateAttachments", "s", property_get_bpf_delegate_attachments, offsetof(ExecContext, bpf_delegate_attachments), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MemoryKSM", "b", bus_property_get_tristate, offsetof(ExecContext, memory_ksm), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("MemoryTHP", "s", property_get_memory_thp, offsetof(ExecContext, memory_thp), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MemoryTHP", "s", property_get_exec_memory_thp, offsetof(ExecContext, memory_thp), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("UserNamespacePath", "s", NULL, offsetof(ExecContext, user_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("NetworkNamespacePath", "s", NULL, offsetof(ExecContext, network_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("IPCNamespacePath", "s", NULL, offsetof(ExecContext, ipc_namespace_path), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1444,7 +1445,7 @@ static int property_get_quota_usage( else assert_not_reached(); - const QuotaLimit *q; + const ExecQuotaLimit *q; q = &c->directories[dt].exec_quota; if (q->quota_enforce || q->quota_accounting) { @@ -1842,7 +1843,7 @@ static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(bpf_delegate_commands, uint64_t, bpf_d static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(bpf_delegate_maps, uint64_t, bpf_delegate_maps_from_string); static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(bpf_delegate_programs, uint64_t, bpf_delegate_programs_from_string); static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(bpf_delegate_attachments, uint64_t, bpf_delegate_attachments_from_string); -static BUS_DEFINE_SET_TRANSIENT_PARSE(memory_thp, MemoryTHP, memory_thp_from_string); +static BUS_DEFINE_SET_TRANSIENT_PARSE(exec_memory_thp, ExecMemoryTHP, exec_memory_thp_from_string); BUS_DEFINE_SET_TRANSIENT_PARSE(exec_preserve_mode, ExecPreserveMode, exec_preserve_mode_from_string); static BUS_DEFINE_SET_TRANSIENT_PARSE_PTR(personality, unsigned long, parse_personality); static BUS_DEFINE_SET_TRANSIENT_TO_STRING_ALLOC(secure_bits, "i", int32_t, int, "%" PRIi32, secure_bits_to_string_alloc_with_check); @@ -2341,7 +2342,7 @@ int bus_exec_context_set_transient_property( return bus_set_transient_tristate(u, name, &c->memory_ksm, message, flags, reterr_error); if (streq(name, "MemoryTHP")) - return bus_set_transient_memory_thp(u, name, &c->memory_thp, message, flags, reterr_error); + return bus_set_transient_exec_memory_thp(u, name, &c->memory_thp, message, flags, reterr_error); if (streq(name, "UtmpIdentifier")) return bus_set_transient_string(u, name, &c->utmp_id, message, flags, reterr_error); @@ -2536,6 +2537,7 @@ int bus_exec_context_set_transient_property( return 1; } else if (STR_IN_SET(name, "SetCredential", "SetCredentialEncrypted")) { + bool encrypted = endswith(name, "Encrypted"); bool isempty = true; r = sd_bus_message_enter_container(message, 'a', "(say)"); @@ -2546,6 +2548,7 @@ int bus_exec_context_set_transient_property( const char *id; const void *p; size_t sz; + const char *err = NULL; r = sd_bus_message_enter_container(message, 'r', "say"); if (r < 0) @@ -2565,34 +2568,13 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; - if (!credential_name_valid(id)) - return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Credential ID is invalid: %s", id); - isempty = false; - if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - bool encrypted = endswith(name, "Encrypted"); - _cleanup_free_ char *a = NULL, *b = NULL; - _cleanup_free_ void *copy = NULL; - - copy = memdup(p, sz); - if (!copy) - return -ENOMEM; - - a = specifier_escape(id); - if (!a) - return -ENOMEM; - - b = cescape_length(p, sz); - if (!b) - return -ENOMEM; - - r = exec_context_put_set_credential(c, id, TAKE_PTR(copy), sz, encrypted); - if (r < 0) - return r; - - (void) unit_write_settingf(u, flags, name, "%s=%s:%s", name, a, b); - } + r = exec_context_apply_set_credential(u, c, id, p, sz, encrypted, flags, &err); + if (r == -EINVAL) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "%s: %s", err, id); + if (r < 0) + return r; } r = sd_bus_message_exit_container(message); @@ -2806,6 +2788,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; @@ -3453,29 +3438,14 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; - if (!strv_env_is_valid(l)) + r = exec_context_apply_environment(u, c, l, flags); + if (r == -E2BIG) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many environment assignments."); + if (r == -EINVAL) return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment block."); - - if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - if (strv_isempty(l)) { - c->environment = strv_free(c->environment); - unit_write_setting(u, flags, name, "Environment="); - } else { - _cleanup_free_ char *joined = NULL; - char **e; - - joined = unit_concat_strv(l, UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_C); - if (!joined) - return -ENOMEM; - - e = strv_env_merge(c->environment, l); - if (!e) - return -ENOMEM; - - strv_free_and_replace(c->environment, e); - unit_write_settingf(u, flags, name, "Environment=%s", joined); - } - } + if (r < 0) + return r; return 1; @@ -3487,6 +3457,9 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; + if (strv_length(l) > ENVIRONMENT_ASSIGNMENTS_MAX) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many environment variable names or assignments."); if (!strv_env_name_or_assignment_is_valid(l)) return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid UnsetEnvironment= list."); @@ -3639,6 +3612,9 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; + if (strv_length(l) > ENVIRONMENT_ASSIGNMENTS_MAX) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many environment variable names."); if (!strv_env_name_is_valid(l)) return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PassEnvironment= block."); @@ -4052,7 +4028,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 +4100,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 +4131,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 +4193,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..2c4d809329f31 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); @@ -845,6 +848,105 @@ static int method_enqueue_unit_job(sd_bus_message *message, void *userdata, sd_b return method_generic_unit_operation(message, userdata, reterr_error, _UNIT_TYPE_INVALID, bus_unit_method_enqueue_job, GENERIC_UNIT_LOAD); } +static int method_enqueue_unit_job_many(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error) { + Manager *m = ASSERT_PTR(userdata); + _cleanup_strv_free_ char **names = NULL; + _cleanup_set_free_ Set *jobs = NULL; + const char *jtype, *smode; + JobType type; + JobMode mode; + uint64_t flags; + bool reload_if_possible; + Job *j; + int r; + + assert(message); + + r = sd_bus_message_read_strv(message, &names); + if (r < 0) + return r; + + if (strv_isempty(names)) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "No units specified."); + + if (strv_length(names) > (unsigned) MANAGER_MAX_NAMES / 2) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many unit names requested."); + + r = sd_bus_message_read(message, "sst", &jtype, &smode, &flags); + if (r < 0) + return r; + + if (flags != 0) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter, must be 0."); + + r = bus_unit_parse_job_type(jtype, &type, &reload_if_possible, reterr_error); + if (r < 0) + return r; + + mode = job_mode_from_string(smode); + if (mode < 0) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Job mode %s invalid", smode); + + r = mac_selinux_access_check(message, job_type_to_access_method(type), reterr_error); + if (r < 0) + return r; + + r = bus_verify_manage_units_async(m, message, reterr_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 */ + + jobs = set_new(NULL); + if (!jobs) + return -ENOMEM; + + r = manager_add_jobs(m, type, names, reload_if_possible, mode, + /* extra_flags= */ 0, /* affected_jobs= */ NULL, reterr_error, jobs); + if (r < 0) + return r; + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(uosos)"); + if (r < 0) + return r; + + SET_FOREACH(j, jobs) { + _cleanup_free_ char *job_path = NULL, *unit_path = NULL; + + r = bus_job_track_sender(j, message); + if (r < 0) + return r; + + bus_job_send_pending_change_signal(j, true); + + job_path = job_dbus_path(j); + if (!job_path) + return -ENOMEM; + + unit_path = unit_dbus_path(j->unit); + if (!unit_path) + return -ENOMEM; + + r = sd_bus_message_append(reply, "(uosos)", + j->id, job_path, + j->unit->id, unit_path, + job_type_to_string(j->type)); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_message_send(reply); +} + static int method_start_unit_replace(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error) { Manager *m = ASSERT_PTR(userdata); const char *old_name; @@ -968,6 +1070,10 @@ static int method_list_units_by_names(sd_bus_message *message, void *userdata, s if (r < 0) return r; + if (strv_length(units) > MAX(hashmap_size(m->units), (unsigned) MANAGER_MAX_NAMES / 2)) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many unit names requested."); + r = sd_bus_message_new_method_return(message, &reply); if (r < 0) return r; @@ -1025,7 +1131,6 @@ static int transient_unit_from_message( Unit **ret_unit, sd_bus_error *reterr_error) { - UnitType t; Unit *u; int r; @@ -1033,27 +1138,7 @@ static int transient_unit_from_message( assert(message); assert(name); - t = unit_name_to_type(name); - if (t < 0) - return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, - "Invalid unit name or type: %s", name); - - if (!unit_vtable[t]->can_transient) - return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, - "Unit type %s does not support transient units.", - unit_type_to_string(t)); - - r = manager_load_unit(m, name, NULL, reterr_error, &u); - if (r < 0) - return r; - - if (!unit_is_pristine(u)) - return sd_bus_error_setf(reterr_error, BUS_ERROR_UNIT_EXISTS, - "Unit %s was already loaded or has a fragment file.", name); - - /* OK, the unit failed to load and is unreferenced, now let's - * fill in the transient data instead */ - r = unit_make_transient(u); + r = manager_setup_transient_unit(m, name, &u, reterr_error); if (r < 0) return r; @@ -1265,9 +1350,13 @@ 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; + if (strv_length(states) > MANAGER_MAX_STATES_PER_CALL) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many states in a single query."); + + if (strv_length(patterns) > MANAGER_MAX_PATTERNS_PER_CALL) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many patterns in a single query."); r = sd_bus_message_new_method_return(message, &reply); if (r < 0) @@ -1281,6 +1370,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; @@ -1448,6 +1541,10 @@ static int dump_impl( assert(message); + if (strv_length(patterns) > MANAGER_MAX_PATTERNS_PER_CALL) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many patterns in a single query."); + /* 'status' access is the bare minimum always needed for this, as the policy might straight out * forbid a client from querying any information from systemd, regardless of any rate limiting. */ r = mac_selinux_access_check(message, "status", reterr_error); @@ -1543,17 +1640,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 +1790,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; @@ -1890,6 +1991,10 @@ static int method_set_environment(sd_bus_message *message, void *userdata, sd_bu r = sd_bus_message_read_strv(message, &plus); if (r < 0) return r; + + if (strv_length(plus) > ENVIRONMENT_ASSIGNMENTS_MAX) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many environment assignments in a single query."); if (!strv_env_is_valid(plus)) return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); @@ -1921,6 +2026,9 @@ static int method_unset_environment(sd_bus_message *message, void *userdata, sd_ if (r < 0) return r; + if (strv_length(minus) > ENVIRONMENT_ASSIGNMENTS_MAX) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many environment variable names in a single query."); if (!strv_env_name_or_assignment_is_valid(minus)) return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment variable names or assignments"); @@ -1957,6 +2065,9 @@ static int method_unset_and_set_environment(sd_bus_message *message, void *userd if (r < 0) return r; + if (strv_length(plus) > ENVIRONMENT_ASSIGNMENTS_MAX || strv_length(minus) > ENVIRONMENT_ASSIGNMENTS_MAX) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many environment variable names or assignments in a single query."); if (!strv_env_name_or_assignment_is_valid(minus)) return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment variable names or assignments"); @@ -2187,6 +2298,14 @@ static int list_unit_files_by_patterns(sd_bus_message *message, void *userdata, /* Anyone can call this method */ + if (strv_length(states) > MANAGER_MAX_STATES_PER_CALL) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many states in a single query."); + + if (strv_length(patterns) > MANAGER_MAX_PATTERNS_PER_CALL) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many patterns in a single query."); + r = mac_selinux_access_check(message, "status", reterr_error); if (r < 0) return r; @@ -2939,6 +3058,8 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_PROPERTY("DefaultStartLimitIntervalSec", "t", bus_property_get_usec, offsetof(Manager, defaults.start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), SD_BUS_PROPERTY("DefaultStartLimitInterval", "t", bus_property_get_usec, offsetof(Manager, defaults.start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), SD_BUS_PROPERTY("DefaultStartLimitBurst", "u", bus_property_get_unsigned, offsetof(Manager, defaults.start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("EventLoopRateLimitIntervalUSec", "t", bus_property_get_usec, offsetof(Manager, event_loop_ratelimit.interval), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("EventLoopRateLimitBurst", "u", bus_property_get_unsigned, offsetof(Manager, event_loop_ratelimit.burst), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultIOAccounting", "b", bus_property_get_bool, offsetof(Manager, defaults.io_accounting), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultIPAccounting", "b", bus_property_get_bool, offsetof(Manager, defaults.ip_accounting), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultMemoryAccounting", "b", bus_property_get_bool, offsetof(Manager, defaults.memory_accounting), SD_BUS_VTABLE_PROPERTY_CONST), @@ -2976,14 +3097,21 @@ 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("KExecsCount", "u", bus_property_get_unsigned, offsetof(Manager, kexecs_count), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ReloadCount", "t", NULL, offsetof(Manager, reload_count), 0), + 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), @@ -3070,6 +3198,11 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_RESULT("u", job_id, "o", job_path, "s", unit_id, "o", unit_path, "s", job_type, "a(uosos)", affected_jobs), method_enqueue_unit_job, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("EnqueueUnitJobMany", + SD_BUS_ARGS("as", units, "s", job_type, "s", job_mode, "t", flags), + SD_BUS_RESULT("a(uosos)", jobs), + method_enqueue_unit_job_many, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("KillUnit", SD_BUS_ARGS("s", name, "s", whom, "i", signal), SD_BUS_NO_RESULT, diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index 9b1b1f7721833..923249cec89aa 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -21,6 +21,7 @@ #include "fd-util.h" #include "glyph-util.h" #include "locale-util.h" +#include "luo-util.h" #include "manager.h" #include "mount-util.h" #include "open-file.h" @@ -357,6 +358,7 @@ const sd_bus_vtable bus_service_vtable[] = { SD_BUS_PROPERTY("RestartUSec", "t", bus_property_get_usec, offsetof(Service, restart_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RestartSteps", "u", bus_property_get_unsigned, offsetof(Service, restart_steps), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RestartMaxDelayUSec", "t", bus_property_get_usec, offsetof(Service, restart_max_delay_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RestartRandomizedDelayUSec", "t", bus_property_get_usec, offsetof(Service, restart_randomized_delay_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RestartUSecNext", "t", property_get_restart_usec_next, 0, 0), SD_BUS_PROPERTY("TimeoutStartUSec", "t", bus_property_get_usec, offsetof(Service, timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Service, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), @@ -380,6 +382,7 @@ const sd_bus_vtable bus_service_vtable[] = { SD_BUS_PROPERTY("FileDescriptorStoreMax", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store_max), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("NFileDescriptorStore", "u", property_get_size_as_uint32, offsetof(Service, n_fd_store), 0), SD_BUS_PROPERTY("FileDescriptorStorePreserve", "s", bus_property_get_exec_preserve_mode, offsetof(Service, fd_store_preserve_mode), 0), + SD_BUS_PROPERTY("LUOSession", "as", NULL, offsetof(Service, luo_sessions), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("StatusText", "s", NULL, offsetof(Service, status_text), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("StatusErrno", "i", bus_property_get_int, offsetof(Service, status_errno), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("StatusBusError", "s", NULL, offsetof(Service, status_bus_error), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), @@ -609,6 +612,9 @@ static int bus_service_set_transient_property( if (streq(name, "RestartMaxDelayUSec")) return bus_set_transient_usec(u, name, &s->restart_max_delay_usec, message, flags, reterr_error); + if (streq(name, "RestartRandomizedDelayUSec")) + return bus_set_transient_usec(u, name, &s->restart_randomized_delay_usec, message, flags, reterr_error); + if (streq(name, "TimeoutStartUSec")) { r = bus_set_transient_usec(u, name, &s->timeout_start_usec, message, flags, reterr_error); if (r >= 0 && !UNIT_WRITE_FLAGS_NOOP(flags)) @@ -648,6 +654,37 @@ static int bus_service_set_transient_property( if (streq(name, "FileDescriptorStorePreserve")) return bus_set_transient_exec_preserve_mode(u, name, &s->fd_store_preserve_mode, message, flags, reterr_error); + if (streq(name, "LUOSession")) { + _cleanup_strv_free_ char **l = NULL; + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + STRV_FOREACH(p, l) + if (!luo_session_name_is_valid(*p)) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, + "Invalid LUO session name: %s", *p); + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + if (strv_isempty(l)) { + s->luo_sessions = strv_free(s->luo_sessions); + unit_write_settingf(u, flags, name, "%s=", name); + } else { + r = strv_extend_strv(&s->luo_sessions, l, /* filter_duplicates= */ true); + if (r < 0) + return r; + + strv_sort_uniq(s->luo_sessions); + + STRV_FOREACH(p, l) + unit_write_settingf(u, flags, name, "%s=%s", name, *p); + } + } + + return 1; + } + if (streq(name, "NotifyAccess")) return bus_set_transient_notify_access(u, name, &s->notify_access, message, flags, reterr_error); diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c index ecb4df4dfda84..26612015f0538 100644 --- a/src/core/dbus-socket.c +++ b/src/core/dbus-socket.c @@ -58,6 +58,33 @@ static int property_get_listen( return sd_bus_message_close_container(reply); } +static int property_get_xattr( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *reterr_error) { + + char ***xattr = ASSERT_PTR(userdata); + int r; + + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "(ss)"); + if (r < 0) + return r; + + STRV_FOREACH_PAIR(name, value, *xattr) { + r = sd_bus_message_append(reply, "(ss)", *name, *value); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + const sd_bus_vtable bus_socket_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("BindIPv6Only", "s", property_get_bind_ipv6_only, offsetof(Socket, bind_ipv6_only), SD_BUS_VTABLE_PROPERTY_CONST), @@ -95,6 +122,9 @@ const sd_bus_vtable bus_socket_vtable[] = { SD_BUS_PROPERTY("RemoveOnStop", "b", bus_property_get_bool, offsetof(Socket, remove_on_stop), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Listen", "a(ss)", property_get_listen, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Symlinks", "as", NULL, offsetof(Socket, symlinks), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("XAttrEntryPoint", "a(ss)", property_get_xattr, offsetof(Socket, xattr_entrypoint), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("XAttrListen", "a(ss)", property_get_xattr, offsetof(Socket, xattr_listen), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("XAttrAccept", "a(ss)", property_get_xattr, offsetof(Socket, xattr_accept), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Mark", "i", bus_property_get_int, offsetof(Socket, mark), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MaxConnections", "u", bus_property_get_unsigned, offsetof(Socket, max_connections), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MaxConnectionsPerSource", "u", bus_property_get_unsigned, offsetof(Socket, max_connections_per_source), SD_BUS_VTABLE_PROPERTY_CONST), @@ -153,6 +183,63 @@ static BUS_DEFINE_SET_TRANSIENT_TO_STRING(socket_protocol, "i", int32_t, int, "% static BUS_DEFINE_SET_TRANSIENT_PARSE(socket_timestamping, SocketTimestamping, socket_timestamping_from_string_harder); static BUS_DEFINE_SET_TRANSIENT_PARSE(socket_defer_trigger, SocketDeferTrigger, socket_defer_trigger_from_string); +static int bus_socket_set_transient_xattr( + Unit *u, + const char *name, + char ***p, + sd_bus_message *message, + UnitWriteFlags flags, + sd_bus_error *reterr_error) { + + _cleanup_strv_free_ char **pairs = NULL; + const char *xname, *xvalue; + bool empty = true; + int r; + + assert(u); + assert(name); + assert(p); + assert(message); + + r = sd_bus_message_enter_container(message, 'a', "(ss)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "(ss)", &xname, &xvalue)) > 0) { + if (!startswith(xname, "user.")) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, + "Extended attribute name does not begin with 'user.': %s", xname); + + r = strv_extend_many(&pairs, xname, xvalue); + if (r < 0) + return -ENOMEM; + + empty = false; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + if (empty) { + *p = strv_free(*p); + unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=", name); + } else { + r = strv_extend_strv(p, pairs, /* filter_duplicates= */ false); + if (r < 0) + return -ENOMEM; + + STRV_FOREACH_PAIR(n, v, pairs) + unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s=%s", name, *n, *v); + } + } + + return 1; +} + static int bus_socket_set_transient_property( Socket *s, const char *name, @@ -335,6 +422,15 @@ static int bus_socket_set_transient_property( &s->exec_command[ci], message, flags, reterr_error); + if (streq(name, "XAttrEntryPoint")) + return bus_socket_set_transient_xattr(u, name, &s->xattr_entrypoint, message, flags, reterr_error); + + if (streq(name, "XAttrListen")) + return bus_socket_set_transient_xattr(u, name, &s->xattr_listen, message, flags, reterr_error); + + if (streq(name, "XAttrAccept")) + return bus_socket_set_transient_xattr(u, name, &s->xattr_accept, message, flags, reterr_error); + if (streq(name, "Symlinks")) { _cleanup_strv_free_ char **l = NULL; diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index 1f9030f3e2e0d..76f64416831ff 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -467,10 +467,44 @@ static int bus_unit_method_reload_or_try_restart(sd_bus_message *message, void * return bus_unit_method_start_generic(message, userdata, JOB_TRY_RESTART, true, reterr_error); } +int bus_unit_parse_job_type( + const char *jtype, + JobType *ret_type, + bool *ret_reload_if_possible, + sd_bus_error *reterr_error) { + + JobType type; + bool reload_if_possible = false; + + assert(jtype); + assert(ret_type); + assert(ret_reload_if_possible); + + /* Parses the job type string as accepted by the EnqueueUnitJob()/EnqueueUnitJobMany() bus methods. The + * two magic "reload-or-…" types are handled manually, the rest generically. The actual + * reload-vs-restart choice is unit-specific and applied per-unit later. */ + if (streq(jtype, "reload-or-restart")) { + type = JOB_RESTART; + reload_if_possible = true; + } else if (streq(jtype, "reload-or-try-restart")) { + type = JOB_TRY_RESTART; + reload_if_possible = true; + } else { + type = job_type_from_string(jtype); + if (type < 0) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Job type %s invalid", jtype); + } + + *ret_type = type; + *ret_reload_if_possible = reload_if_possible; + return 0; +} + int bus_unit_method_enqueue_job(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error) { BusUnitQueueFlags flags = BUS_UNIT_QUEUE_VERBOSE_REPLY; const char *jtype, *smode; Unit *u = ASSERT_PTR(userdata); + bool reload_if_possible; JobType type; JobMode mode; int r; @@ -481,19 +515,11 @@ int bus_unit_method_enqueue_job(sd_bus_message *message, void *userdata, sd_bus_ if (r < 0) return r; - /* Parse the two magic reload types "reload-or-…" manually */ - if (streq(jtype, "reload-or-restart")) { - type = JOB_RESTART; - flags |= BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE; - } else if (streq(jtype, "reload-or-try-restart")) { - type = JOB_TRY_RESTART; + r = bus_unit_parse_job_type(jtype, &type, &reload_if_possible, reterr_error); + if (r < 0) + return r; + if (reload_if_possible) flags |= BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE; - } else { - /* And the rest generically */ - type = job_type_from_string(jtype); - if (type < 0) - return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Job type %s invalid", jtype); - } mode = job_mode_from_string(smode); if (mode < 0) diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h index 778f537672c3a..a31a27dd54629 100644 --- a/src/core/dbus-unit.h +++ b/src/core/dbus-unit.h @@ -15,6 +15,7 @@ void bus_unit_send_removed_signal(Unit *u); int bus_unit_method_start_generic(sd_bus_message *message, Unit *u, JobType job_type, bool reload_if_possible, sd_bus_error *reterr_error); int bus_unit_method_enqueue_job(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error); +int bus_unit_parse_job_type(const char *jtype, JobType *ret_type, bool *ret_reload_if_possible, sd_bus_error *reterr_error); int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error); int bus_unit_method_kill_subgroup(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error); int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *reterr_error); diff --git a/src/core/dbus.c b/src/core/dbus.c index 30d265ec6cef1..659965e4e6c50 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; @@ -1059,7 +1060,7 @@ int bus_fdset_add_all(Manager *m, FDSet *fds) { /* When we are about to reexecute we add all D-Bus fds to the * set to pass over to the newly executed systemd. They won't - * be used there however, except thatt they are closed at the + * be used there however, except that they are closed at the * very end of deserialization, those making it possible for * clients to synchronously wait for systemd to reexec by * simply waiting for disconnection */ diff --git a/src/core/dynamic-user.c b/src/core/dynamic-user.c index 921622d92c0ea..eb4235637e884 100644 --- a/src/core/dynamic-user.c +++ b/src/core/dynamic-user.c @@ -357,8 +357,8 @@ static int dynamic_user_realize( r = dynamic_user_pop(d, &num, &uid_lock_fd); if (r < 0) { - int new_uid_lock_fd; - uid_t new_uid; + int new_uid_lock_fd = -EBADF; /* avoid false maybe-uninitialized warning */ + uid_t new_uid = UID_INVALID; /* avoid false maybe-uninitialized warning */ if (r != -EAGAIN) return r; @@ -465,7 +465,7 @@ static int dynamic_user_realize( int dynamic_user_current(DynamicUser *d, uid_t *ret) { _cleanup_close_ int lock_fd = -EBADF; - uid_t uid; + uid_t uid = UID_INVALID; /* avoid false maybe-uninitialized warning */ int r; assert(d); 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-credential.c b/src/core/exec-credential.c index c1b2fcda85cde..13ecf72f49e5b 100644 --- a/src/core/exec-credential.c +++ b/src/core/exec-credential.c @@ -17,7 +17,7 @@ #include "label-util.h" #include "log.h" #include "manager.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "namespace-util.h" diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index b91a964cdd6ab..2b0ced7b7d51e 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -21,7 +21,7 @@ #include "ask-password-api.h" #include "barrier.h" #include "bitfield.h" -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-restrict-fs.h" #include "btrfs-util.h" #include "capability-util.h" @@ -33,6 +33,7 @@ #include "constants.h" #include "copy.h" #include "coredump-util.h" +#include "crypto-util.h" #include "cryptsetup-util.h" #include "dissect-image.h" #include "dynamic-user.h" @@ -53,7 +54,7 @@ #include "libmount-util.h" #include "manager.h" #include "memfd-util.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "mstack.h" @@ -877,51 +878,6 @@ static int ask_for_confirmation(const ExecContext *context, const ExecParameters return r; } -static int get_fixed_user( - const char *user_or_uid, - bool prefer_nss, - const char **ret_username, - uid_t *ret_uid, - gid_t *ret_gid, - const char **ret_home, - const char **ret_shell) { - - int r; - - assert(user_or_uid); - assert(ret_username); - - r = get_user_creds(&user_or_uid, ret_uid, ret_gid, ret_home, ret_shell, - USER_CREDS_CLEAN|(prefer_nss ? USER_CREDS_PREFER_NSS : 0)); - if (r < 0) - return r; - - /* user_or_uid is normalized by get_user_creds to username */ - *ret_username = user_or_uid; - - return 0; -} - -static int get_fixed_group( - const char *group_or_gid, - const char **ret_groupname, - gid_t *ret_gid) { - - int r; - - assert(group_or_gid); - assert(ret_groupname); - - r = get_group_creds(&group_or_gid, ret_gid, /* flags= */ 0); - if (r < 0) - return r; - - /* group_or_gid is normalized by get_group_creds to groupname */ - *ret_groupname = group_or_gid; - - return 0; -} - static int get_supplementary_groups( const ExecContext *c, const char *user, @@ -989,8 +945,7 @@ static int get_supplementary_groups( if (k >= ngroups_max) return -E2BIG; - const char *g = *i; - r = get_group_creds(&g, l_gids + k, /* flags= */ 0); + r = get_group_creds(*i, /* flags= */ 0, /* ret_name= */ NULL, l_gids + k); if (r < 0) return r; @@ -1086,15 +1041,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 +1314,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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); if (r < 0) - return log_error_errno(r, "PAM support not available: %m"); + return r; r = barrier_create(&barrier); if (r < 0) @@ -1628,7 +1580,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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 0) return true; /* No syscall filter, we are allowed to drop privileges */ @@ -1687,8 +1639,16 @@ static int apply_syscall_filter(const ExecContext *c, const ExecParameters *p) { action = negative_action; } - /* Sending over exec_fd or handoff_timestamp_fd requires write() syscall. */ + /* Sending over exec_fd or handoff_timestamp_fd requires write() syscall. + * + * Note: this mutates c->syscall_filter despite the 'const ExecContext *c' qualifier. + * That is intentional and safe here because apply_syscall_filter() runs only in the + * post-fork child, which holds a private copy of the address space; the hashmap + * change is never visible to the manager process. */ if (p->exec_fd >= 0 || p->handoff_timestamp_fd >= 0) { + if (c->syscall_allow_list) + log_debug("SystemCallFilter= allow-list in effect; adding 'write' syscall required for exec handoff."); + r = seccomp_filter_set_add_by_name(c->syscall_filter, c->syscall_allow_list, "write"); if (r < 0) return r; @@ -1930,7 +1890,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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); if (r < 0) return r; @@ -2034,11 +1994,12 @@ 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) { _cleanup_strv_free_ char **e = NULL; + _cleanup_free_ char *_username = NULL, *_home = NULL, *_shell = NULL; size_t n = 0; pid_t exec_pid; int r; @@ -2103,12 +2064,16 @@ static int build_environment( if (!username && !c->dynamic_user && p->runtime_scope == RUNTIME_SCOPE_SYSTEM) { assert(!c->user); - r = get_fixed_user("root", /* prefer_nss= */ false, &username, NULL, NULL, &home, &shell); + r = get_user_creds("root", USER_CREDS_CLEAN, &_username, NULL, NULL, &_home, &_shell); if (r < 0) { log_debug_errno(r, "Failed to determine credentials for user root: %s", STRERROR_USER(r)); return ERRNO_IS_NEG_BAD_ACCOUNT(r) ? -EINVAL : r; /* Suppress confusing errno */ } + + username = _username; + home = _home; + shell = _shell; } bool set_user_login_env = exec_context_get_set_login_environment(c); @@ -2215,25 +2180,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; } @@ -2752,7 +2730,7 @@ static int create_many_symlinks(const char *root, const char *source, char **sym return 0; } -static int set_exec_storage_quota(int fd, uint32_t proj_id, const QuotaLimit *ql) { +static int set_exec_storage_quota(int fd, uint32_t proj_id, const ExecQuotaLimit *ql) { int r; uint64_t block_limit = 0, inode_limit = 0; @@ -2842,7 +2820,7 @@ static int apply_exec_quotas( const char *target_dir, const char *cgroup_path, ExecDirectoryType type, - const QuotaLimit *ql, + const ExecQuotaLimit *ql, uint32_t *exec_dt_proj_id, /* in/out */ bool *already_enforced) { /* in/out */ @@ -3741,8 +3719,9 @@ static int pin_rootfs( if (context->root_image) { _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; - r = path_pick(/* toplevel_path= */ NULL, - /* toplevel_fd= */ AT_FDCWD, + r = path_pick(/* root_path= */ NULL, + /* root_fd= */ AT_FDCWD, + /* dir_fd= */ AT_FDCWD, context->root_image, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), @@ -3782,8 +3761,9 @@ static int pin_rootfs( if (context->root_directory) { _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; - r = path_pick(/* toplevel_path= */ NULL, - /* toplevel_fd= */ AT_FDCWD, + r = path_pick(/* root_path= */ NULL, + /* root_fd= */ AT_FDCWD, + /* dir_fd= */ AT_FDCWD, context->root_directory, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), @@ -3811,8 +3791,9 @@ static int pin_rootfs( if (context->root_mstack) { _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; - r = path_pick(/* toplevel_path= */ NULL, - /* toplevel_fd= */ AT_FDCWD, + r = path_pick(/* root_path= */ NULL, + /* root_fd= */ AT_FDCWD, + /* dir_fd= */ AT_FDCWD, context->root_mstack, pick_filter_image_mstack, /* n_filters= */ 1, @@ -3855,7 +3836,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 +3868,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 +4123,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) @@ -4152,12 +4145,22 @@ static int apply_root_directory( assert(exit_status); if (params->flags & EXEC_APPLY_CHROOT) - if (!needs_mount_ns && context->root_directory) + if (!needs_mount_ns && context->root_directory) { if (chroot(runtime->ephemeral_copy ?: context->root_directory) < 0) { *exit_status = EXIT_CHROOT; return -errno; } + /* chroot(2) changes only the process root, not cwd. Move cwd into the new root + * immediately so that a subsequent apply_working_directory() failure that is + * silently ignored via working_directory_missing_ok cannot leave cwd pointing + * at a pre-chroot dentry outside the new root. */ + if (chdir("/") < 0) { + *exit_status = EXIT_CHROOT; + return -errno; + } + } + return 0; } @@ -4346,16 +4349,13 @@ static int send_user_lookup( return 0; } -static int acquire_home(const ExecContext *c, const char **home, char **ret_buf) { - int r; - +static int acquire_home(const ExecContext *c, char **home) { assert(c); assert(home); - assert(ret_buf); /* If WorkingDirectory=~ is set, try to acquire a usable home directory. */ - if (*home) /* Already acquired from get_fixed_user()? */ + if (*home) /* Already acquired from get_user_creds()? */ return 0; if (!c->working_directory_home) @@ -4364,12 +4364,7 @@ static int acquire_home(const ExecContext *c, const char **home, char **ret_buf) if (c->dynamic_user || (c->user && is_this_me(c->user) <= 0)) return -EADDRNOTAVAIL; - r = get_home_dir(ret_buf); - if (r < 0) - return r; - - *home = *ret_buf; - return 1; + return get_home_dir(home); } static int compile_suggested_paths(const ExecContext *c, const ExecParameters *p, char ***ret) { @@ -4689,7 +4684,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 +4815,7 @@ static int setup_delegated_namespaces( params, runtime, rootfs, - memory_pressure_path, + pressure_path, needs_sandboxing, uid, gid, @@ -4850,23 +4845,23 @@ static int setup_delegated_namespaces( return 0; } -static int set_memory_thp(MemoryTHP thp) { +static int set_memory_thp(ExecMemoryTHP thp) { int r; switch (thp) { - case MEMORY_THP_INHERIT: + case EXEC_MEMORY_THP_INHERIT: return 0; - case MEMORY_THP_DISABLE: + case EXEC_MEMORY_THP_DISABLE: r = RET_NERRNO(prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0)); break; - case MEMORY_THP_MADVISE: + case EXEC_MEMORY_THP_MADVISE: r = RET_NERRNO(prctl(PR_SET_THP_DISABLE, 1, PR_THP_DISABLE_EXCEPT_ADVISED, 0, 0)); break; - case MEMORY_THP_SYSTEM: + case EXEC_MEMORY_THP_SYSTEM: r = RET_NERRNO(prctl(PR_SET_THP_DISABLE, 0, 0, 0, 0)); break; @@ -5146,6 +5141,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, @@ -5156,9 +5155,9 @@ 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; - const char *pwent_home = NULL, *shell = NULL; + const char *username = NULL; + _cleanup_free_ char *pwent_home = NULL, *shell = NULL, *_own_user = NULL, *_username = NULL; + _cleanup_(free_pressure_paths) char *pressure_path[_PRESSURE_RESOURCE_MAX] = {}; dev_t journal_stream_dev = 0; ino_t journal_stream_ino = 0; bool needs_sandboxing, /* Do we need to set up full sandboxing? (i.e. all namespacing, all MAC stuff, caps, yadda yadda */ @@ -5394,44 +5393,48 @@ int exec_invoke( username = runtime->dynamic_creds->user->name; } else { - const char *u; - if (context->user) - u = context->user; + username = context->user; else if (context->pam_name || FLAGS_SET(command->flags, EXEC_COMMAND_VIA_SHELL)) { /* If PAM is enabled but no user name is explicitly selected, then use our own one. */ - own_user = getusername_malloc(); - if (!own_user) { + username = _own_user = getusername_malloc(); + if (!username) { *exit_status = EXIT_USER; - return log_error_errno(r, "Failed to determine my own user ID: %m"); + return log_oom(); } - u = own_user; - } else - u = NULL; + } - if (u) { + if (username) { /* We can't use nss unconditionally for root without risking deadlocks if some IPC services * will be started by pid1 and are ordered after us. But if SetLoginEnvironment= is * enabled *explicitly* (i.e. no exec_context_get_set_login_environment() here), * or PAM shall be invoked, let's consult NSS even for root, so that the user * gets accurate $SHELL in session(-like) contexts. */ - r = get_fixed_user(u, - /* prefer_nss= */ context->set_login_environment > 0 || context->pam_name, - &username, &uid, &gid, &pwent_home, &shell); + bool prefer_nss = context->set_login_environment > 0 || context->pam_name; + + r = get_user_creds(username, + USER_CREDS_CLEAN|(prefer_nss ? USER_CREDS_PREFER_NSS : 0), + &_username, + &uid, + &gid, + &pwent_home, + &shell); if (r < 0) { *exit_status = EXIT_USER; log_error_errno(r, "Failed to determine credentials for user '%s': %s", - u, STRERROR_USER(r)); + username, STRERROR_USER(r)); return ERRNO_IS_NEG_BAD_ACCOUNT(r) ? -EINVAL : r; /* Suppress confusing errno */ } + + username = _username; } if (context->group) { - r = get_fixed_group(context->group, &groupname, &gid); + r = get_group_creds(context->group, /* flags= */ 0, /* ret_name= */ NULL, &gid); if (r < 0) { *exit_status = EXIT_GROUP; log_error_errno(r, "Failed to determine credentials for group '%s': %s", - u, STRERROR_GROUP(r)); + context->group, STRERROR_GROUP(r)); return ERRNO_IS_NEG_BAD_ACCOUNT(r) ? -EINVAL : r; /* Suppress confusing errno */ } } @@ -5454,7 +5457,7 @@ int exec_invoke( params->user_lookup_fd = safe_close(params->user_lookup_fd); } - r = acquire_home(context, &pwent_home, &home_buffer); + r = acquire_home(context, &pwent_home); if (r < 0) { *exit_status = EXIT_CHDIR; return log_error_errno(r, "Failed to determine $HOME for the invoking user: %m"); @@ -5643,8 +5646,10 @@ int exec_invoke( if (mpol_is_valid(numa_policy_get_type(&context->numa_policy))) { r = apply_numa_policy(&context->numa_policy); - if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + if (r == -ENOSYS) log_debug_errno(r, "NUMA support not available, ignoring."); + else if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + log_warning_errno(r, "NUMA policy not supported by kernel, ignoring."); else if (r < 0) { *exit_status = EXIT_NUMA_POLICY; return log_error_errno(r, "Failed to set NUMA memory policy: %m"); @@ -5684,11 +5689,11 @@ int exec_invoke( r = set_memory_thp(context->memory_thp); if (r == -EOPNOTSUPP) log_debug_errno(r, "Setting MemoryTHP=%s is not supported, ignoring.", - memory_thp_to_string(context->memory_thp)); + exec_memory_thp_to_string(context->memory_thp)); else if (r < 0) { *exit_status = EXIT_MEMORY_THP; return log_error_errno(r, "Failed to set MemoryTHP=%s: %m", - memory_thp_to_string(context->memory_thp)); + exec_memory_thp_to_string(context->memory_thp)); } #if ENABLE_UTMP @@ -5753,36 +5758,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 +5842,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 +6004,12 @@ 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + (void) DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + (void) DLOPEN_LIBMOUNT(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + (void) DLOPEN_LIBSECCOMP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + /* Needed for userspace verity verification fallback */ + (void) DLOPEN_LIBCRYPTO(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); /* 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 */ @@ -6005,8 +6020,10 @@ int exec_invoke( * Users with CAP_SYS_ADMIN can set up user namespaces last because they will be able to * set up all of the other namespaces (i.e. network, mount, UTS) without a user namespace. */ - if (context->user_namespace_path && runtime->shared->userns_storage_socket[0] >= 0) + if (context->user_namespace_path && runtime->shared->userns_storage_socket[0] >= 0) { + *exit_status = EXIT_USER; return log_error_errno(SYNTHETIC_ERRNO(EPERM), "UserNamespacePath= is configured, but user namespace setup not permitted"); + } PrivateUsers pu = exec_context_get_effective_private_users(context, params); if (pu == PRIVATE_USERS_NO) @@ -6047,7 +6064,7 @@ int exec_invoke( runtime, &rootfs, /* delegate= */ false, - memory_pressure_path, + pressure_path, uid, gid, command, @@ -6091,12 +6108,16 @@ int exec_invoke( * case of mount namespaces being less privileged when the mount point list is copied from a * different user namespace). */ if (needs_sandboxing && context->user_namespace_path && runtime->shared && runtime->shared->userns_storage_socket[0] >= 0) { - if (!namespace_type_supported(NAMESPACE_USER)) + if (!namespace_type_supported(NAMESPACE_USER)) { + *exit_status = EXIT_USER; return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "UserNamespacePath= is not supported, refusing."); + } r = setup_shareable_ns(runtime->shared->userns_storage_socket, CLONE_NEWUSER); - if (ERRNO_IS_NEG_PRIVILEGE(r)) - return log_notice_errno(r, "PrivateUsers= is configured, but user namespace setup not permitted, refusing."); + if (ERRNO_IS_NEG_PRIVILEGE(r)) { + *exit_status = EXIT_USER; + return log_error_errno(r, "UserNamespacePath= is configured, but user namespace setup not permitted, refusing."); + } if (r < 0) { *exit_status = EXIT_USER; return log_error_errno(r, "Failed to set up user namespacing: %m"); @@ -6144,7 +6165,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..55a521604255e 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, @@ -120,6 +121,12 @@ static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) { if (r < 0) return r; + if (c->cpuset_partition >= 0) { + r = serialize_item(f, "exec-cgroup-context-cpuset-partition", cpuset_partition_to_string(c->cpuset_partition)); + if (r < 0) + return r; + } + if (c->io_weight != CGROUP_WEIGHT_INVALID) { r = serialize_item_format(f, "exec-cgroup-context-io-weight", "%" PRIu64, c->io_weight); if (r < 0) @@ -278,7 +285,19 @@ 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_strv(f, "exec-cgroup-context-managed-oom-rules", c->moom_rules); + if (r < 0) + return r; + + 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 +305,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; } @@ -492,6 +523,10 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { r = parse_cpu_set(val, &c->startup_cpuset_mems); if (r < 0) return r; + } else if ((val = startswith(l, "exec-cgroup-context-cpuset-partition="))) { + c->cpuset_partition = cpuset_partition_from_string(val); + if (c->cpuset_partition < 0) + return -EINVAL; } else if ((val = startswith(l, "exec-cgroup-context-io-weight="))) { r = safe_atou64(val, &c->io_weight); if (r < 0) @@ -619,16 +654,36 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { r = deserialize_usec(val, &c->moom_mem_pressure_duration_usec); if (r < 0) return r; + } else if ((val = startswith(l, "exec-cgroup-context-managed-oom-rules="))) { + r = deserialize_strv(val, &c->moom_rules); + 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="))) { @@ -1661,7 +1716,7 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) { if (r < 0) return r; - r = serialize_item(f, "exec-context-memory-thp", memory_thp_to_string(c->memory_thp)); + r = serialize_item(f, "exec-context-memory-thp", exec_memory_thp_to_string(c->memory_thp)); if (r < 0) return r; @@ -2606,7 +2661,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { if (r < 0) return r; } else if ((val = startswith(l, "exec-context-memory-thp="))) { - c->memory_thp = memory_thp_from_string(val); + c->memory_thp = exec_memory_thp_from_string(val); if (c->memory_thp < 0) return c->memory_thp; } else if ((val = startswith(l, "exec-context-private-tmp="))) { @@ -3104,12 +3159,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) @@ -3216,7 +3278,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { r = extract_first_word(&val, &options, NULL, EXTRACT_UNQUOTE); if (r < 0) - return -r; + return r; if (isempty(options) || streq(options, "rbind")) rbind = true; @@ -3275,7 +3337,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { r = extract_first_word(&val, &options, NULL, EXTRACT_UNQUOTE); if (r < 0) - return -r; + return r; if (isempty(options) || streq(options, "rbind")) rbind = true; diff --git a/src/core/execute.c b/src/core/execute.c index 80c22623be797..4240e01d0be9c 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -18,11 +18,13 @@ #include "cgroup-setup.h" #include "coredump-util.h" #include "cpu-set-util.h" +#include "creds-util.h" #include "dissect-image.h" #include "dynamic-user.h" #include "env-file.h" #include "env-util.h" #include "escape.h" +#include "exec-credential.h" #include "execute.h" #include "execute-serialize.h" #include "fd-util.h" @@ -55,6 +57,7 @@ #include "serialize.h" #include "set.h" #include "sort-util.h" +#include "specifier.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -113,7 +116,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"); } @@ -476,7 +479,6 @@ int exec_spawn( assert(unit); assert(unit->manager); assert(unit->manager->executor_fd >= 0); - assert(unit->manager->executor_path); assert(command); assert(context); assert(params); @@ -577,7 +579,7 @@ int exec_spawn( /* The executor binary is pinned, to avoid compatibility problems during upgrades. */ r = posix_spawn_wrapper( FORMAT_PROC_FD_PATH(unit->manager->executor_fd), - STRV_MAKE(unit->manager->executor_path, + STRV_MAKE("systemd-executor", "--deserialize", serialization_fd_number, "--log-level", max_log_levels, "--log-target", log_target_to_string(manager_get_executor_log_target(unit->manager))), @@ -696,10 +698,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); @@ -753,6 +755,88 @@ void exec_context_done(ExecContext *c) { c->private_hostname = mfree(c->private_hostname); } +int exec_context_apply_environment( + Unit *u, + ExecContext *c, + char **env, + UnitWriteFlags flags) { + + assert(u); + assert(c); + + if (strv_length(env) > ENVIRONMENT_ASSIGNMENTS_MAX) + return -E2BIG; + if (!strv_env_is_valid(env)) + return -EINVAL; + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + if (strv_isempty(env)) { + c->environment = strv_free(c->environment); + unit_write_setting(u, flags, "Environment", "Environment="); + } else { + _cleanup_free_ char *joined = unit_concat_strv(env, UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_C); + if (!joined) + return -ENOMEM; + + char **e = strv_env_merge(c->environment, env); + if (!e) + return -ENOMEM; + + strv_free_and_replace(c->environment, e); + unit_write_settingf(u, flags, "Environment", "Environment=%s", joined); + } + } + + return 0; +} + +int exec_context_apply_set_credential( + Unit *u, + ExecContext *c, + const char *id, + const void *data, + size_t size, + bool encrypted, + UnitWriteFlags flags, + const char **reterr_message) { + + int r; + + assert(u); + assert(c); + assert(id); + assert(data || size == 0); + + if (!credential_name_valid(id)) { + if (reterr_message) + *reterr_message = "Credential ID is invalid"; + return -EINVAL; + } + + if (UNIT_WRITE_FLAGS_NOOP(flags)) + return 0; + + _cleanup_free_ void *copy = memdup(data, size); + if (!copy) + return -ENOMEM; + + _cleanup_free_ char *escaped_id = specifier_escape(id); + if (!escaped_id) + return -ENOMEM; + + _cleanup_free_ char *escaped_value = cescape_length(data, size); + if (!escaped_value) + return -ENOMEM; + + r = exec_context_put_set_credential(c, id, TAKE_PTR(copy), size, encrypted); + if (r < 0) + return r; + + const char *name = encrypted ? "SetCredentialEncrypted" : "SetCredential"; + unit_write_settingf(u, flags, name, "%s=%s:%s", name, escaped_id, escaped_value); + return 0; +} + int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) { assert(c); @@ -1143,7 +1227,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { prefix, protect_hostname_to_string(c->protect_hostname), c->private_hostname ? ":" : "", strempty(c->private_hostname), prefix, protect_proc_to_string(c->protect_proc), prefix, proc_subset_to_string(c->proc_subset), - prefix, memory_thp_to_string(c->memory_thp), + prefix, exec_memory_thp_to_string(c->memory_thp), prefix, private_bpf_to_string(c->private_bpf)); if (c->private_bpf == PRIVATE_BPF_YES) { @@ -1491,7 +1575,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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) >= 0) { void *id, *val; bool first = true; HASHMAP_FOREACH_KEY(val, id, c->syscall_filter) { @@ -1910,7 +1994,7 @@ char** exec_context_get_syscall_filter(const ExecContext *c) { assert(c); #if HAVE_SECCOMP - if (dlopen_libseccomp() < 0) + if (DLOPEN_LIBSECCOMP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 0) return strv_new(NULL); void *id, *val; @@ -1930,14 +2014,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 +2063,7 @@ char** exec_context_get_syscall_log(const ExecContext *c) { assert(c); #if HAVE_SECCOMP - if (dlopen_libseccomp() < 0) + if (DLOPEN_LIBSECCOMP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 0) return strv_new(NULL); void *id, *val; @@ -2374,6 +2456,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 */ @@ -2664,7 +2748,8 @@ int exec_shared_runtime_deserialize_compat(Unit *u, const char *key, const char int exec_shared_runtime_deserialize_one(Manager *m, const char *value, FDSet *fds) { _cleanup_free_ char *tmp_dir = NULL, *var_tmp_dir = NULL; char *id = NULL; - int r, userns_fdpair[] = {-1, -1}, netns_fdpair[] = {-1, -1}, ipcns_fdpair[] = {-1, -1}; + _cleanup_close_pair_ int userns_fdpair[] = EBADF_PAIR, netns_fdpair[] = EBADF_PAIR, ipcns_fdpair[] = EBADF_PAIR; + int r; const char *p, *v = ASSERT_PTR(value); size_t n; @@ -3097,9 +3182,10 @@ static const char* const exec_utmp_mode_table[_EXEC_UTMP_MODE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(exec_utmp_mode, ExecUtmpMode); static const char* const exec_preserve_mode_table[_EXEC_PRESERVE_MODE_MAX] = { - [EXEC_PRESERVE_NO] = "no", - [EXEC_PRESERVE_YES] = "yes", - [EXEC_PRESERVE_RESTART] = "restart", + [EXEC_PRESERVE_NO] = "no", + [EXEC_PRESERVE_YES] = "yes", + [EXEC_PRESERVE_RESTART] = "restart", + [EXEC_PRESERVE_ON_SUCCESS] = "on-success", }; DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(exec_preserve_mode, ExecPreserveMode, EXEC_PRESERVE_YES); @@ -3146,11 +3232,11 @@ static const char* const exec_keyring_mode_table[_EXEC_KEYRING_MODE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(exec_keyring_mode, ExecKeyringMode); -static const char* const memory_thp_table[_MEMORY_THP_MAX] = { - [MEMORY_THP_INHERIT] = "inherit", - [MEMORY_THP_DISABLE] = "disable", - [MEMORY_THP_MADVISE] = "madvise", - [MEMORY_THP_SYSTEM] = "system", +static const char* const exec_memory_thp_table[_EXEC_MEMORY_THP_MAX] = { + [EXEC_MEMORY_THP_INHERIT] = "inherit", + [EXEC_MEMORY_THP_DISABLE] = "disable", + [EXEC_MEMORY_THP_MADVISE] = "madvise", + [EXEC_MEMORY_THP_SYSTEM] = "system", }; -DEFINE_STRING_TABLE_LOOKUP(memory_thp, MemoryTHP); +DEFINE_STRING_TABLE_LOOKUP(exec_memory_thp, ExecMemoryTHP); diff --git a/src/core/execute.h b/src/core/execute.h index 3d5e92cccf0fe..109dd774fcc5a 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -61,6 +61,7 @@ typedef enum ExecPreserveMode { EXEC_PRESERVE_NO, EXEC_PRESERVE_YES, EXEC_PRESERVE_RESTART, + EXEC_PRESERVE_ON_SUCCESS, _EXEC_PRESERVE_MODE_MAX, _EXEC_PRESERVE_MODE_INVALID = -EINVAL, } ExecPreserveMode; @@ -73,23 +74,23 @@ typedef enum ExecKeyringMode { _EXEC_KEYRING_MODE_INVALID = -EINVAL, } ExecKeyringMode; -typedef enum MemoryTHP { +typedef enum ExecMemoryTHP { /* * Inherit default from process that starts systemd, i.e. do not make * any PR_SET_THP_DISABLE call. */ - MEMORY_THP_INHERIT, - MEMORY_THP_DISABLE, /* Disable THPs completely for the process */ - MEMORY_THP_MADVISE, /* Disable THPs for the process except when madvised */ + EXEC_MEMORY_THP_INHERIT, + EXEC_MEMORY_THP_DISABLE, /* Disable THPs completely for the process */ + EXEC_MEMORY_THP_MADVISE, /* Disable THPs for the process except when madvised */ /* * Use system default THP setting. this can be used when the process that * starts systemd has already disabled THPs via PR_SET_THP_DISABLE, and we * want to restore the system default THP setting at process invocation time. */ - MEMORY_THP_SYSTEM, - _MEMORY_THP_MAX, - _MEMORY_THP_INVALID = -EINVAL, -} MemoryTHP; + EXEC_MEMORY_THP_SYSTEM, + _EXEC_MEMORY_THP_MAX, + _EXEC_MEMORY_THP_INVALID = -EINVAL, +} ExecMemoryTHP; /* Contains start and exit information about an executed command. */ typedef struct ExecStatus { @@ -154,12 +155,12 @@ static inline bool EXEC_DIRECTORY_TYPE_SHALL_CHOWN(ExecDirectoryType t) { return t >= 0 && t < _EXEC_DIRECTORY_TYPE_MAX && t != EXEC_DIRECTORY_CONFIGURATION; } -typedef struct QuotaLimit { +typedef struct ExecQuotaLimit { uint64_t quota_absolute; /* absolute quota in bytes; if UINT64_MAX relative quota configured, see below */ uint32_t quota_scale; /* relative quota to backend size, scaled to 0…UINT32_MAX */ bool quota_enforce; bool quota_accounting; -} QuotaLimit; +} ExecQuotaLimit; typedef struct ExecDirectoryItem { char *path; @@ -172,7 +173,7 @@ typedef struct ExecDirectory { mode_t mode; size_t n_items; ExecDirectoryItem *items; - QuotaLimit exec_quota; + ExecQuotaLimit exec_quota; } ExecDirectory; typedef enum ExecCleanMask { @@ -332,7 +333,7 @@ typedef struct ExecContext { int mount_apivfs; int bind_log_sockets; int memory_ksm; - MemoryTHP memory_thp; + ExecMemoryTHP memory_thp; PrivateTmp private_tmp; /* Those are not independent parameters, but are calculated from */ PrivateTmp private_var_tmp; /* other parameters in unit_patch_contexts(). */ @@ -540,6 +541,9 @@ void exec_context_init(ExecContext *c); void exec_context_done(ExecContext *c); void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix); +int exec_context_apply_environment(Unit *u, ExecContext *c, char **env, UnitWriteFlags flags); +int exec_context_apply_set_credential(Unit *u, ExecContext *c, const char *id, const void *data, size_t size, bool encrypted, UnitWriteFlags flags, const char **reterr_message); + int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix); int exec_context_destroy_mount_ns_dir(Unit *u); @@ -636,7 +640,7 @@ DECLARE_STRING_TABLE_LOOKUP(exec_directory_type_mode, ExecDirectoryType); DECLARE_STRING_TABLE_LOOKUP(exec_resource_type, ExecDirectoryType); -DECLARE_STRING_TABLE_LOOKUP(memory_thp, MemoryTHP); +DECLARE_STRING_TABLE_LOOKUP(exec_memory_thp, ExecMemoryTHP); bool exec_needs_mount_namespace(const ExecContext *context, const ExecParameters *params); bool exec_needs_network_namespace(const ExecContext *context); diff --git a/src/core/executor.c b/src/core/executor.c index 9ca15cf35155f..b227584789be2 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" @@ -14,13 +13,14 @@ #include "exec-invoke.h" #include "execute.h" #include "execute-serialize.h" +#include "executor.h" #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 +32,101 @@ 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_ns("systemd-executor", &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 opts = { argc, argv, .namespace = "systemd-executor" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + + OPTION_NAMESPACE("systemd-executor"): {} + + 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(opts.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", opts.arg); break; - case ARG_LOG_TARGET: - r = log_set_target_from_string(optarg); + OPTION_COMMON_LOG_TARGET: + r = log_set_target_from_string(opts.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", opts.arg); break; - case ARG_LOG_COLOR: - r = log_show_color_from_string(optarg); + OPTION_COMMON_LOG_COLOR: + r = log_show_color_from_string(opts.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", opts.arg); break; - case ARG_LOG_LOCATION: - r = log_show_location_from_string(optarg); + OPTION_COMMON_LOG_LOCATION: + r = log_show_location_from_string(opts.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", opts.arg); break; - case ARG_LOG_TIME: - r = log_show_time_from_string(optarg); + OPTION_COMMON_LOG_TIME: + r = log_show_time_from_string(opts.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", opts.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(opts.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", opts.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 +158,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 +166,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(); @@ -270,7 +229,7 @@ static int run(int argc, char *argv[]) { return exit_status; } -int main(int argc, char *argv[]) { +int run_executor(int argc, char *argv[]) { int r; /* We use safe_fork() for spawning sd-pam helper process, which internally calls rename_process(). @@ -285,3 +244,7 @@ int main(int argc, char *argv[]) { return r < 0 ? EXIT_FAILURE : r; } + +#if !BUILD_EXECUTOR_SINGLE +_alias_(run_executor) main; +#endif diff --git a/src/core/executor.h b/src/core/executor.h new file mode 100644 index 0000000000000..75d2abe0e5458 --- /dev/null +++ b/src/core/executor.h @@ -0,0 +1,3 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +int run_executor(int argc, char *argv[]); diff --git a/src/core/generate-bpf-delegate-configs.py b/src/core/generate-bpf-delegate-configs.py index 200c913b8a826..4e4a54322a480 100755 --- a/src/core/generate-bpf-delegate-configs.py +++ b/src/core/generate-bpf-delegate-configs.py @@ -30,12 +30,12 @@ def print_usage_and_exit() -> None: enumName = '' if output == 'doc': - print("""\ + print('''\ -""") +''') for line in file: line = line.strip() diff --git a/src/core/generator-setup.c b/src/core/generator-setup.c index d63a393d985d1..804eb11f1462b 100644 --- a/src/core/generator-setup.c +++ b/src/core/generator-setup.c @@ -4,7 +4,7 @@ #include "errno-util.h" #include "generator-setup.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "path-lookup.h" #include "rm-rf.h" diff --git a/src/core/import-creds.c b/src/core/import-creds.c index 89f0c557078c2..98675d2fc52e4 100644 --- a/src/core/import-creds.c +++ b/src/core/import-creds.c @@ -17,7 +17,7 @@ #include "initrd-util.h" #include "io-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "parse-util.h" @@ -168,119 +168,158 @@ static int finalize_credentials_dir(const char *dir, const char *envvar) { return 0; } -static int import_credentials_boot(void) { - _cleanup_(import_credentials_context_done) ImportCredentialsContext context = { - .target_dir_fd = -EBADF, - }; +static int import_credentials_from_initrd_path( + ImportCredentialsContext *c, + const char *source_path, + const char *target_dir, + bool with_mount) { + + _cleanup_free_ DirectoryEntries *de = NULL; + _cleanup_close_ int source_dir_fd = -EBADF; int r; - /* systemd-stub will wrap sidecar *.cred files from the UEFI kernel image directory into initrd - * cpios, so that they unpack into /.extra/. We'll pick them up from there and copy them into /run/ - * so that we can access them during the entire runtime (note that the initrd file system is erased - * during the initrd → host transition). Note that these credentials originate from an untrusted - * source (i.e. the ESP typically) and thus need to be authenticated later. We thus put them in a - * directory separate from the usual credentials which are from a trusted source. */ + source_dir_fd = open(source_path, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (source_dir_fd < 0) { + if (errno == ENOENT) { + log_debug("No credentials passed via %s.", source_path); + return 0; + } - if (!in_initrd()) + log_warning_errno(errno, "Failed to open '%s', ignoring: %m", source_path); return 0; + } - FOREACH_STRING(p, - "/.extra/credentials/", /* specific to this boot menu */ - "/.extra/global_credentials/") { /* boot partition wide */ + r = readdir_all(source_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_REGULAR, &de); + if (r < 0) { + log_warning_errno(r, "Failed to read '%s' contents, ignoring: %m", source_path); + return 0; + } - _cleanup_free_ DirectoryEntries *de = NULL; - _cleanup_close_ int source_dir_fd = -EBADF; + FOREACH_ARRAY(i, de->entries, de->n_entries) { + const struct dirent *d = *i; + _cleanup_close_ int cfd = -EBADF, nfd = -EBADF; + _cleanup_free_ char *n = NULL; + const char *e; + struct stat st; - source_dir_fd = open(p, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (source_dir_fd < 0) { - if (errno == ENOENT) { - log_debug("No credentials passed via %s.", p); - continue; - } + e = endswith(d->d_name, ".cred"); + if (!e) + continue; - log_warning_errno(errno, "Failed to open '%s', ignoring: %m", p); + /* drop .cred suffix (which we want in the ESP sidecar dir, but not for our internal + * processing) */ + n = strndup(d->d_name, e - d->d_name); + if (!n) + return log_oom(); + + if (!credential_name_valid(n)) { + log_warning("Credential '%s' has invalid name, ignoring.", d->d_name); continue; } - r = readdir_all(source_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de); - if (r < 0) { - log_warning_errno(r, "Failed to read '%s' contents, ignoring: %m", p); + cfd = openat(source_dir_fd, d->d_name, O_RDONLY|O_CLOEXEC); + if (cfd < 0) { + log_warning_errno(errno, "Failed to open %s, ignoring: %m", d->d_name); continue; } - FOREACH_ARRAY(i, de->entries, de->n_entries) { - const struct dirent *d = *i; - _cleanup_close_ int cfd = -EBADF, nfd = -EBADF; - _cleanup_free_ char *n = NULL; - const char *e; - struct stat st; + if (fstat(cfd, &st) < 0) { + log_warning_errno(errno, "Failed to stat %s, ignoring: %m", d->d_name); + continue; + } - e = endswith(d->d_name, ".cred"); - if (!e) - continue; + r = stat_verify_regular(&st); + if (r < 0) { + log_warning_errno(r, "Credential file %s is not a regular file, ignoring: %m", d->d_name); + continue; + } - /* drop .cred suffix (which we want in the ESP sidecar dir, but not for our internal - * processing) */ - n = strndup(d->d_name, e - d->d_name); - if (!n) - return log_oom(); + if (!credential_size_ok(c, n, st.st_size)) + continue; - if (!credential_name_valid(n)) { - log_warning("Credential '%s' has invalid name, ignoring.", d->d_name); - continue; - } + r = acquire_credential_directory(c, target_dir, with_mount); + if (r < 0) + return r; - cfd = openat(source_dir_fd, d->d_name, O_RDONLY|O_CLOEXEC); - if (cfd < 0) { - log_warning_errno(errno, "Failed to open %s, ignoring: %m", d->d_name); - continue; - } + nfd = open_credential_file_for_write(c->target_dir_fd, target_dir, n); + if (nfd == -EEXIST) + continue; + if (nfd < 0) + return nfd; - if (fstat(cfd, &st) < 0) { - log_warning_errno(errno, "Failed to stat %s, ignoring: %m", d->d_name); - continue; - } + r = copy_bytes(cfd, nfd, st.st_size, 0); + if (r < 0) { + (void) unlinkat(c->target_dir_fd, n, 0); + return log_error_errno(r, "Failed to create credential '%s': %m", n); + } - r = stat_verify_regular(&st); - if (r < 0) { - log_warning_errno(r, "Credential file %s is not a regular file, ignoring: %m", d->d_name); - continue; - } + c->size_sum += st.st_size; + c->n_credentials++; - if (!credential_size_ok(&context, n, st.st_size)) - continue; + log_debug("Successfully copied boot credential '%s'.", n); + } - r = acquire_credential_directory(&context, ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, /* with_mount= */ false); - if (r < 0) - return r; + return 0; +} - nfd = open_credential_file_for_write(context.target_dir_fd, ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, n); - if (nfd == -EEXIST) - continue; - if (nfd < 0) - return nfd; +static int import_credentials_boot(ImportCredentialsContext *system_ctx) { + _cleanup_(import_credentials_context_done) ImportCredentialsContext encrypted_ctx = { + .target_dir_fd = -EBADF, + }; + unsigned n_system_before; + int r; - r = copy_bytes(cfd, nfd, st.st_size, 0); - if (r < 0) { - (void) unlinkat(context.target_dir_fd, n, 0); - return log_error_errno(r, "Failed to create credential '%s': %m", n); - } + assert(system_ctx); + + /* The initrd may contain two flavours of credentials placed under /.extra/, copied + * across the initrd → host transition before the initrd tmpfs is erased: + * + * - /.extra/credentials/ and /.extra/global_credentials/ — placed by systemd-stub + * from the EFI System Partition. Trust model: untrusted, because the ESP can + * be mounted and edited offline. They are routed into the @encrypted bucket + * where consumers must authenticate them before use (via LoadCredentialEncrypted=). + * + * - /.extra/system_credentials/ — placed by host-side producers that take responsibility + * for the trust (e.g. systemd-vmspawn when its cpio is covered by the SEV-SNP launch + * measurement or when the host is the trust root in non-confidential setups). Routed + * into the @system bucket where consumers can use them directly via LoadCredential=. */ - context.size_sum += st.st_size; - context.n_credentials++; + if (!in_initrd()) + return 0; - log_debug("Successfully copied boot credential '%s'.", n); - } + FOREACH_STRING(p, + "/.extra/credentials/", /* specific to this boot menu */ + "/.extra/global_credentials/") { /* boot partition wide */ + r = import_credentials_from_initrd_path( + &encrypted_ctx, p, + ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, + /* with_mount= */ false); + if (r < 0) + return r; } - if (context.n_credentials > 0) { - log_debug("Imported %u credentials from boot loader.", context.n_credentials); + n_system_before = system_ctx->n_credentials; + + r = import_credentials_from_initrd_path( + system_ctx, "/.extra/system_credentials/", + SYSTEM_CREDENTIALS_DIRECTORY, + /* with_mount= */ true); + if (r < 0) + return r; + + if (encrypted_ctx.n_credentials > 0) { + log_debug("Imported %u encrypted credentials from boot loader.", encrypted_ctx.n_credentials); r = finalize_credentials_dir(ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, "ENCRYPTED_CREDENTIALS_DIRECTORY"); if (r < 0) return r; } + if (system_ctx->n_credentials > n_system_before) + log_debug("Imported %u trusted credentials from boot loader.", system_ctx->n_credentials - n_system_before); + + /* The @system credentials directory is shared with import_credentials_trusted(); the caller finalizes. */ + return 0; } @@ -701,28 +740,27 @@ static int import_credentials_initrd(ImportCredentialsContext *c) { return 0; } -static int import_credentials_trusted(void) { - _cleanup_(import_credentials_context_done) ImportCredentialsContext c = { - .target_dir_fd = -EBADF, - }; - int r, ret = 0; +static int import_credentials_trusted(ImportCredentialsContext *c) { + unsigned n_before; + int ret = 0; + + assert(c); /* This is invoked during early boot when no credentials have been imported so far. (Specifically, if * the $CREDENTIALS_DIRECTORY or $ENCRYPTED_CREDENTIALS_DIRECTORY environment variables are not set * yet.) */ - RET_GATHER(ret, import_credentials_qemu(&c)); - RET_GATHER(ret, import_credentials_smbios(&c)); - RET_GATHER(ret, import_credentials_proc_cmdline(&c)); - RET_GATHER(ret, import_credentials_initrd(&c)); + n_before = c->n_credentials; - if (c.n_credentials > 0) { - log_debug("Imported %u credentials from kernel command line/smbios/fw_cfg/initrd.", c.n_credentials); + RET_GATHER(ret, import_credentials_qemu(c)); + RET_GATHER(ret, import_credentials_smbios(c)); + RET_GATHER(ret, import_credentials_proc_cmdline(c)); + RET_GATHER(ret, import_credentials_initrd(c)); - r = finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY, "CREDENTIALS_DIRECTORY"); - if (r < 0) - return r; - } + if (c->n_credentials > n_before) + log_debug("Imported %u credentials from kernel command line/smbios/fw_cfg/initrd.", c->n_credentials - n_before); + + /* The @system credentials directory is shared with import_credentials_boot(); the caller finalizes. */ return ret; } @@ -884,6 +922,9 @@ int import_credentials(void) { RET_GATHER(r, merge_credentials_trusted(received_creds_dir)); } else { + _cleanup_(import_credentials_context_done) ImportCredentialsContext system_ctx = { + .target_dir_fd = -EBADF, + }; bool import; r = proc_cmdline_get_bool("systemd.import_credentials", PROC_CMDLINE_STRIP_RD_PREFIX|PROC_CMDLINE_TRUE_WHEN_MISSING, &import); @@ -894,8 +935,12 @@ int import_credentials(void) { return 0; } - r = import_credentials_boot(); - RET_GATHER(r, import_credentials_trusted()); + /* System credential context is shared so a single CREDENTIALS_TOTAL_SIZE_MAX is enforced. */ + r = import_credentials_boot(&system_ctx); + RET_GATHER(r, import_credentials_trusted(&system_ctx)); + + if (system_ctx.n_credentials > 0) + RET_GATHER(r, finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY, "CREDENTIALS_DIRECTORY")); } report_credentials(); diff --git a/src/core/ipe-setup.c b/src/core/ipe-setup.c index f263117018ef3..7b684e34b72a3 100644 --- a/src/core/ipe-setup.c +++ b/src/core/ipe-setup.c @@ -23,7 +23,7 @@ int ipe_setup(void) { _cleanup_strv_free_ char **policies = NULL; int r; - /* Very quick smoke tests first: this is in the citical, sequential boot path, and in most cases it + /* Very quick smoke tests first: this is in the critical, sequential boot path, and in most cases it * is unlikely this will be configured, so do the fastest existence checks first and immediately * return if there's nothing to do. */ diff --git a/src/core/job.c b/src/core/job.c index 1cac09bd06607..2388d1de95f64 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -2,6 +2,7 @@ #include "sd-bus.h" #include "sd-messages.h" +#include "sd-varlink.h" #include "alloc-util.h" #include "ansi-color.h" @@ -24,6 +25,7 @@ #include "string-util.h" #include "strv.h" #include "unit.h" +#include "varlink-unit.h" #include "virt.h" Job* job_new_raw(Unit *unit) { @@ -41,6 +43,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; @@ -122,6 +126,7 @@ Job* job_free(Job *j) { job_unlink(j); sd_bus_track_unref(j->bus_track); + sd_varlink_unref(j->varlink); strv_free(j->deserialized_clients); activation_details_unref(j->activation_details); @@ -138,17 +143,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) @@ -162,7 +168,7 @@ void job_uninstall(Job *j) { assert(j); assert(j->installed); - job_set_state(j, JOB_WAITING); + job_set_state(j, JOB_FINISHED); pj = j->type == JOB_NOP ? &j->unit->nop_job : &j->unit->job; assert(*pj == j); @@ -170,8 +176,10 @@ void job_uninstall(Job *j) { /* Detach from next 'bigger' objects */ /* daemon-reload should be transparent to job observers */ - if (!MANAGER_IS_RELOADING(j->manager)) + if (!MANAGER_IS_RELOADING(j->manager)) { bus_job_send_removed_signal(j); + varlink_job_send_removed_signal(j); + } *pj = NULL; @@ -510,6 +518,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 +1024,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 +1535,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 +1654,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..45dccdaaaecd4 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; @@ -125,6 +125,9 @@ typedef struct Job { sd_bus_track *bus_track; char **deserialized_clients; + /* If non-NULL, a varlink connection streaming updates. */ + sd_varlink *varlink; + /* If the job had a specific trigger that needs to be advertised (eg: a path unit), store it. */ ActivationDetails *activation_details; @@ -142,6 +145,8 @@ typedef struct Job { bool ref_by_private_bus:1; bool in_gc_queue:1; + + bool varlink_notify_job_changes:1; } Job; Job* job_new(Unit *unit, JobType type); 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..aa95bd4992001 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -196,7 +196,7 @@ {% endif %} {{type}}.ProtectHostname, config_parse_protect_hostname, 0, offsetof({{type}}, exec_context) {{type}}.MemoryKSM, config_parse_tristate, 0, offsetof({{type}}, exec_context.memory_ksm) -{{type}}.MemoryTHP, config_parse_memory_thp, 0, offsetof({{type}}, exec_context.memory_thp) +{{type}}.MemoryTHP, config_parse_exec_memory_thp, 0, offsetof({{type}}, exec_context.memory_thp) {%- endmacro -%} {%- macro KILL_CONTEXT_CONFIG_ITEMS(type) -%} @@ -215,6 +215,7 @@ {{type}}.StartupAllowedCPUs, config_parse_unit_cpu_set, 0, offsetof({{type}}, cgroup_context.startup_cpuset_cpus) {{type}}.AllowedMemoryNodes, config_parse_unit_cpu_set, 0, offsetof({{type}}, cgroup_context.cpuset_mems) {{type}}.StartupAllowedMemoryNodes, config_parse_unit_cpu_set, 0, offsetof({{type}}, cgroup_context.startup_cpuset_mems) +{{type}}.CPUSetPartition, config_parse_cpuset_partition, 0, offsetof({{type}}, cgroup_context.cpuset_partition) {{type}}.CPUAccounting, config_parse_warn_compat, DISABLED_LEGACY, 0 {{type}}.CPUWeight, config_parse_cg_cpu_weight, 0, offsetof({{type}}, cgroup_context.cpu_weight) {{type}}.StartupCPUWeight, config_parse_cg_cpu_weight, 0, offsetof({{type}}, cgroup_context.startup_cpu_weight) @@ -271,13 +272,18 @@ {{type}}.ManagedOOMMemoryPressureLimit, config_parse_managed_oom_mem_pressure_limit, 0, offsetof({{type}}, cgroup_context.moom_mem_pressure_limit) {{type}}.ManagedOOMMemoryPressureDurationSec, config_parse_managed_oom_mem_pressure_duration_sec, 0, offsetof({{type}}, cgroup_context.moom_mem_pressure_duration_usec) {{type}}.ManagedOOMPreference, config_parse_managed_oom_preference, 0, offsetof({{type}}, cgroup_context.moom_preference) +{{type}}.OOMRules, config_parse_managed_oom_rules, 1, offsetof({{type}}, cgroup_context.moom_rules) {{type}}.NetClass, config_parse_warn_compat, DISABLED_LEGACY, 0 {{type}}.BPFProgram, config_parse_bpf_foreign_program, 0, offsetof({{type}}, cgroup_context) {{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) @@ -376,6 +382,7 @@ Unit.ConditionArchitecture, config_parse_unit_condition_string Unit.ConditionFirmware, config_parse_unit_condition_string, CONDITION_FIRMWARE, offsetof(Unit, conditions) Unit.ConditionVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, conditions) Unit.ConditionHost, config_parse_unit_condition_string, CONDITION_HOST, offsetof(Unit, conditions) +Unit.ConditionFraction, config_parse_unit_condition_string, CONDITION_FRACTION, offsetof(Unit, conditions) Unit.ConditionKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, conditions) Unit.ConditionKernelVersion, config_parse_unit_condition_string, CONDITION_VERSION, offsetof(Unit, conditions) Unit.ConditionVersion, config_parse_unit_condition_string, CONDITION_VERSION, offsetof(Unit, conditions) @@ -391,6 +398,7 @@ Unit.ConditionUser, config_parse_unit_condition_string Unit.ConditionGroup, config_parse_unit_condition_string, CONDITION_GROUP, offsetof(Unit, conditions) Unit.ConditionControlGroupController, config_parse_unit_condition_string, CONDITION_CONTROL_GROUP_CONTROLLER, offsetof(Unit, conditions) Unit.ConditionOSRelease, config_parse_unit_condition_string, CONDITION_OS_RELEASE, offsetof(Unit, conditions) +Unit.ConditionMachineTag, config_parse_unit_condition_string, CONDITION_MACHINE_TAG, offsetof(Unit, conditions) Unit.ConditionMemoryPressure, config_parse_unit_condition_string, CONDITION_MEMORY_PRESSURE, offsetof(Unit, conditions) Unit.ConditionCPUPressure, config_parse_unit_condition_string, CONDITION_CPU_PRESSURE, offsetof(Unit, conditions) Unit.ConditionIOPressure, config_parse_unit_condition_string, CONDITION_IO_PRESSURE, offsetof(Unit, conditions) @@ -411,6 +419,7 @@ Unit.AssertFirstBoot, config_parse_unit_condition_string Unit.AssertArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, offsetof(Unit, asserts) Unit.AssertVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, asserts) Unit.AssertHost, config_parse_unit_condition_string, CONDITION_HOST, offsetof(Unit, asserts) +Unit.AssertFraction, config_parse_unit_condition_string, CONDITION_FRACTION, offsetof(Unit, asserts) Unit.AssertKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, asserts) Unit.AssertKernelVersion, config_parse_unit_condition_string, CONDITION_VERSION, offsetof(Unit, asserts) Unit.AssertVersion, config_parse_unit_condition_string, CONDITION_VERSION, offsetof(Unit, asserts) @@ -426,6 +435,7 @@ Unit.AssertUser, config_parse_unit_condition_string Unit.AssertGroup, config_parse_unit_condition_string, CONDITION_GROUP, offsetof(Unit, asserts) Unit.AssertControlGroupController, config_parse_unit_condition_string, CONDITION_CONTROL_GROUP_CONTROLLER, offsetof(Unit, asserts) Unit.AssertOSRelease, config_parse_unit_condition_string, CONDITION_OS_RELEASE, offsetof(Unit, asserts) +Unit.AssertMachineTag, config_parse_unit_condition_string, CONDITION_MACHINE_TAG, offsetof(Unit, asserts) Unit.AssertMemoryPressure, config_parse_unit_condition_string, CONDITION_MEMORY_PRESSURE, offsetof(Unit, asserts) Unit.AssertCPUPressure, config_parse_unit_condition_string, CONDITION_CPU_PRESSURE, offsetof(Unit, asserts) Unit.AssertIOPressure, config_parse_unit_condition_string, CONDITION_IO_PRESSURE, offsetof(Unit, asserts) @@ -443,6 +453,7 @@ Service.ExecStopPost, config_parse_exec, Service.RestartSec, config_parse_sec, 0, offsetof(Service, restart_usec) Service.RestartSteps, config_parse_unsigned, 0, offsetof(Service, restart_steps) Service.RestartMaxDelaySec, config_parse_sec, 0, offsetof(Service, restart_max_delay_usec) +Service.RestartRandomizedDelaySec, config_parse_sec, 0, offsetof(Service, restart_randomized_delay_usec) Service.TimeoutSec, config_parse_service_timeout, 0, 0 Service.TimeoutStartSec, config_parse_service_timeout, 0, 0 Service.TimeoutStopSec, config_parse_sec_fix_0, 0, offsetof(Service, timeout_stop_usec) @@ -474,6 +485,7 @@ Service.NonBlocking, config_parse_bool, Service.BusName, config_parse_bus_name, 0, offsetof(Service, bus_name) Service.FileDescriptorStoreMax, config_parse_unsigned, 0, offsetof(Service, n_fd_store_max) Service.FileDescriptorStorePreserve, config_parse_exec_preserve_mode, 0, offsetof(Service, fd_store_preserve_mode) +Service.LUOSession, config_parse_luo_sessions, 0, offsetof(Service, luo_sessions) Service.NotifyAccess, config_parse_notify_access, 0, offsetof(Service, notify_access) Service.Sockets, config_parse_service_sockets, 0, 0 Service.BusPolicy, config_parse_warn_compat, DISABLED_LEGACY, 0 @@ -549,6 +561,9 @@ Socket.PollLimitIntervalSec, config_parse_sec, Socket.PollLimitBurst, config_parse_unsigned, 0, offsetof(Socket, poll_limit.burst) Socket.DeferTrigger, config_parse_socket_defer_trigger, 0, offsetof(Socket, defer_trigger) Socket.DeferTriggerMaxSec, config_parse_sec_fix_0, 0, offsetof(Socket, defer_trigger_max_usec) +Socket.XAttrEntryPoint, config_parse_xattr, 0, offsetof(Socket, xattr_entrypoint) +Socket.XAttrListen, config_parse_xattr, 0, offsetof(Socket, xattr_listen) +Socket.XAttrAccept, config_parse_xattr, 0, offsetof(Socket, xattr_accept) {% if ENABLE_SMACK %} Socket.SmackLabel, config_parse_unit_string_printf, 0, offsetof(Socket, smack) Socket.SmackLabelIPIn, config_parse_unit_string_printf, 0, offsetof(Socket, smack_ip_in) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index d2bfd20fd43af..ce4517e85bd29 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" @@ -42,6 +41,7 @@ #include "limits-util.h" #include "load-fragment.h" #include "log.h" +#include "luo-util.h" #include "manager.h" #include "mountpoint-util.h" #include "nsflags.h" @@ -57,7 +57,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 +88,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 +109,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; @@ -128,6 +132,7 @@ DEFINE_CONFIG_PARSE(config_parse_socket_protocol, parse_socket_protocol); DEFINE_CONFIG_PARSE(config_parse_exec_secure_bits, secure_bits_from_string); DEFINE_CONFIG_PARSE_ENUM(config_parse_collect_mode, collect_mode, CollectMode); DEFINE_CONFIG_PARSE_ENUM(config_parse_device_policy, cgroup_device_policy, CGroupDevicePolicy); +DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_cpuset_partition, cpuset_partition, CPUSetPartition, _CPUSET_PARTITION_INVALID); DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_keyring_mode, exec_keyring_mode, ExecKeyringMode); DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_proc, protect_proc, ProtectProc); DEFINE_CONFIG_PARSE_ENUM(config_parse_proc_subset, proc_subset, ProcSubset); @@ -150,7 +155,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); @@ -163,7 +168,7 @@ DEFINE_CONFIG_PARSE_PTR(config_parse_bpf_delegate_commands, bpf_delegate_command DEFINE_CONFIG_PARSE_PTR(config_parse_bpf_delegate_maps, bpf_delegate_maps_from_string, uint64_t); DEFINE_CONFIG_PARSE_PTR(config_parse_bpf_delegate_programs, bpf_delegate_programs_from_string, uint64_t); DEFINE_CONFIG_PARSE_PTR(config_parse_bpf_delegate_attachments, bpf_delegate_attachments_from_string, uint64_t); -DEFINE_CONFIG_PARSE_ENUM(config_parse_memory_thp, memory_thp, MemoryTHP); +DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_memory_thp, exec_memory_thp, ExecMemoryTHP); bool contains_instance_specifier_superset(const char *s) { const char *p, *q; @@ -565,6 +570,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 +992,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); @@ -2240,6 +2247,66 @@ int config_parse_socket_service( return 0; } +int config_parse_xattr( + 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) { + + char ***lp = ASSERT_PTR(data); + Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *lp = strv_free(*lp); + return 0; + } + + _cleanup_free_ char *name = NULL, *value = NULL; + r = split_pair(rvalue, "=", &name, &value); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse extended attribute expression, ignoring: %s", rvalue); + return 0; + } + + _cleanup_free_ char *expanded_name = NULL; + r = unit_full_printf(u, name, &expanded_name); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in extended attribute expression, ignoring: %s", name); + return 0; + } + + if (!startswith(expanded_name, "user.")) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Extended attribute name does not begin with 'user.', ignoring: %s", expanded_name); + return 0; + } + + _cleanup_free_ char *expanded_value = NULL; + r = unit_full_printf(u, value, &expanded_value); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in extended attribute expression, ignoring: %s", value); + return 0; + } + + if (strv_push_pair(lp, expanded_name, expanded_value) < 0) + return log_oom(); + + TAKE_PTR(expanded_name); + TAKE_PTR(expanded_value); + + return 0; +} + int config_parse_fdname( const char *unit, const char *filename, @@ -2941,6 +3008,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 +3535,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 @@ -4125,6 +4151,65 @@ int config_parse_managed_oom_mem_pressure_duration_sec( return 0; } +int config_parse_managed_oom_rules( + 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) { + + char ***sv = ASSERT_PTR(data); + UnitType t; + int r; + + assert(rvalue); + + t = unit_name_to_type(unit); + assert(t != _UNIT_TYPE_INVALID); + + if (!unit_vtable[t]->can_set_managed_oom) + return log_syntax(unit, LOG_WARNING, filename, line, 0, "%s= is not supported for this unit type, ignoring.", lvalue); + + if (isempty(rvalue)) { + *sv = strv_free(*sv); + return 0; + } + + /* Tokenize once: validate each rule name (rulesets are loaded from .oomrule files) + * and accumulate into a local strv. Invalid rule names are skipped individually + * with a warning so the rest of the line still applies. */ + _cleanup_strv_free_ char **strv = NULL; + for (const char *p = rvalue;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE); + if (r == 0) + break; + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + if (!string_is_safe(word, STRING_FILENAME)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid rule name in %s=, ignoring: %s", lvalue, word); + continue; + } + + r = strv_consume(&strv, TAKE_PTR(word)); + if (r < 0) + return log_oom(); + } + + r = strv_extend_strv_consume(sv, TAKE_PTR(strv), /* filter_duplicates= */ ltype); + if (r < 0) + return log_oom(); + + return 0; +} + int config_parse_device_allow( const char *unit, const char *filename, @@ -4568,7 +4653,7 @@ int config_parse_exec_quota( void *data, void *userdata) { - QuotaLimit *quota_limit = ASSERT_PTR(data); + ExecQuotaLimit *quota_limit = ASSERT_PTR(data); uint64_t quota_absolute = UINT64_MAX; uint32_t quota_scale = UINT32_MAX; int r; @@ -5224,7 +5309,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 +5459,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; @@ -6702,3 +6787,61 @@ int config_parse_protect_hostname( free_and_replace(c->private_hostname, h); return 1; } + +int config_parse_luo_sessions( + 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) { + + char ***sessions = ASSERT_PTR(data); + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *sessions = strv_free(*sessions); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *word = NULL; + int r; + + r = extract_first_word(&p, &word, /* separators= */ NULL, /* flags= */ 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse LUOSession= value, ignoring: %s", rvalue); + return 0; + } + if (r == 0) { + strv_sort(*sessions); + return 0; + } + + if (!luo_session_name_is_valid(word)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "LUO session name contains invalid characters, ignoring: %s", word); + continue; + } + + if (strv_contains(*sessions, word)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Duplicate LUO session name, ignoring: %s", word); + continue; + } + + r = strv_extend(sessions, word); + if (r < 0) + return log_oom(); + } +} diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 4677564904c52..15dd411cc5a93 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -144,6 +144,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_pid_file); CONFIG_PARSER_PROTOTYPE(config_parse_exit_status); CONFIG_PARSER_PROTOTYPE(config_parse_disable_controllers); CONFIG_PARSER_PROTOTYPE(config_parse_oom_policy); +CONFIG_PARSER_PROTOTYPE(config_parse_managed_oom_rules); CONFIG_PARSER_PROTOTYPE(config_parse_numa_policy); CONFIG_PARSER_PROTOTYPE(config_parse_numa_mask); CONFIG_PARSER_PROTOTYPE(config_parse_ip_filter_bpf_progs); @@ -164,12 +165,15 @@ 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); CONFIG_PARSER_PROTOTYPE(config_parse_bind_network_interface); -CONFIG_PARSER_PROTOTYPE(config_parse_memory_thp); +CONFIG_PARSER_PROTOTYPE(config_parse_exec_memory_thp); +CONFIG_PARSER_PROTOTYPE(config_parse_cpuset_partition); +CONFIG_PARSER_PROTOTYPE(config_parse_luo_sessions); +CONFIG_PARSER_PROTOTYPE(config_parse_xattr); /* gperf prototypes */ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length); diff --git a/src/core/luo.c b/src/core/luo.c new file mode 100644 index 0000000000000..ffb208f3aa895 --- /dev/null +++ b/src/core/luo.c @@ -0,0 +1,340 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "errno-util.h" +#include "fd-util.h" +#include "fdset.h" +#include "json-util.h" +#include "log.h" +#include "luo.h" +#include "luo-util.h" +#include "manager.h" +#include "serialize.h" +#include "service.h" +#include "unit.h" +#include "unit-name.h" + +static int luo_read_mapping(int session_fd, sd_json_variant **ret) { + int r; + + assert(session_fd >= 0); + assert(ret); + + _cleanup_close_ int mapping_fd = luo_session_retrieve_fd(session_fd, LUO_MAPPING_INDEX); + if (mapping_fd < 0) + return log_warning_errno(mapping_fd, "Failed to retrieve LUO mapping fd (fd_index 0): %m"); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + r = sd_json_parse_fd( + "luo-mapping", + mapping_fd, + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_REOPEN_FD, + &v, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL); + if (r < 0) + return log_warning_errno(r, "Failed to parse LUO mapping JSON: %m"); + + *ret = TAKE_PTR(v); + return 0; +} + +static void luo_session_finishp(int *fd) { + assert(fd); + + if (*fd >= 0) + (void) luo_session_finish(*fd); + safe_close(*fd); +} + +int manager_luo_restore_fd_stores(Manager *m) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *mapping = NULL; + _cleanup_close_ int device_fd = -EBADF; + _cleanup_(luo_session_finishp) int session_fd = -EBADF; + const char *unit_id; + sd_json_variant *unit_json; + int r, n_total = 0; + + assert(m); + + if (MANAGER_IS_USER(m)) + return 0; + + device_fd = luo_open_device(); + if (ERRNO_IS_NEG_DEVICE_ABSENT(device_fd)) { + log_debug_errno(device_fd, "No /dev/liveupdate device found, skipping LUO fd store restoration."); + return 0; + } + if (device_fd < 0) + return log_warning_errno(device_fd, "Failed to open /dev/liveupdate: %m"); + + session_fd = luo_retrieve_session(device_fd, LUO_SESSION_NAME); + if (session_fd == -ENOENT) { + log_debug("No LUO session '%s' found, skipping fd store restoration.", LUO_SESSION_NAME); + return 0; + } + if (session_fd < 0) + return log_warning_errno(session_fd, "Failed to retrieve LUO session '%s': %m", LUO_SESSION_NAME); + + log_debug("Found LUO session '%s', restoring fd stores.", LUO_SESSION_NAME); + + r = luo_read_mapping(session_fd, &mapping); + if (r < 0) + return r; + + struct { + uint64_t version; + sd_json_variant *state; + sd_json_variant *units; + } q = {}; + + static const sd_json_dispatch_field mapping_dispatch_table[] = { + { "version", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(q, version), SD_JSON_MANDATORY }, + { "state", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, voffsetof(q, state), 0 }, + { "units", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, voffsetof(q, units), 0 }, + {} + }; + + r = sd_json_dispatch(mapping, mapping_dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG, &q); + if (r < 0) + return r; + + if (q.version > LUO_PROTOCOL_VERSION) { + log_warning("LUO mapping has unsupported version %" PRIu64 ", skipping state restoration.", q.version); + return 0; + } + + struct { + unsigned kexecs_count; + } state_data = {}; + + static const sd_json_dispatch_field state_dispatch_table[] = { + { "kexecsCount", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, voffsetof(state_data, kexecs_count), 0 }, + {} + }; + + r = sd_json_dispatch(q.state, state_dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG, &state_data); + if (r >= 0) + m->kexecs_count = state_data.kexecs_count; + + /* If we found a LUO session then by definition we have just successfully kexec rebooted */ + (void) INC_SAFE(&m->kexecs_count, 1); + + /* Retrieve all fds from the session and dispatch each to the named unit, eagerly loading the + * unit if necessary. */ + JSON_VARIANT_OBJECT_FOREACH(unit_id, unit_json, q.units) { + sd_json_variant *entry, *fds_json; + + if (!unit_name_is_valid(unit_id, UNIT_NAME_ANY)) { + log_warning("Invalid unit name '%s' in LUO mapping, skipping.", unit_id); + continue; + } + + fds_json = sd_json_variant_by_key(unit_json, "fdstore"); + if (!sd_json_variant_is_array(fds_json)) { + log_warning("LUO mapping fdstore for unit '%s' is not a JSON array, skipping.", unit_id); + continue; + } + + JSON_VARIANT_ARRAY_FOREACH(entry, fds_json) { + struct { + const char *type; + const char *name; + uint64_t token; + const char *session_name; + } p = { + .token = UINT64_MAX, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, type), SD_JSON_MANDATORY }, + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + { "token", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, token), 0 }, + { "sessionName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, session_name), 0 }, + {} + }; + + _cleanup_close_ int fd = -EBADF; + + r = sd_json_dispatch(entry, dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG|SD_JSON_WARNING, &p); + if (r < 0) + continue; + + if (streq(p.type, "fd")) { + if (p.token == UINT64_MAX) { + log_warning("LUO mapping for unit '%s' fd '%s': missing 'token' field.", unit_id, p.name); + continue; + } + if (p.token == LUO_MAPPING_INDEX) { + log_warning("LUO mapping for unit '%s' fd '%s': token %" PRIu64 " is reserved for the mapping memfd.", unit_id, p.name, p.token); + continue; + } + + fd = luo_session_retrieve_fd(session_fd, p.token); + if (fd < 0) { + log_warning_errno(fd, "Failed to retrieve LUO fd for unit '%s' name '%s' token %" PRIu64 ": %m", + unit_id, p.name, p.token); + continue; + } + } else if (streq(p.type, "luo_session")) { + if (!p.session_name) { + log_warning("LUO mapping for unit '%s' fd '%s': missing sessionName.", unit_id, p.name); + continue; + } + + fd = luo_retrieve_session(device_fd, p.session_name); + if (fd < 0) { + log_warning_errno(fd, "Failed to retrieve LUO session '%s' for unit '%s' name '%s': %m", + p.session_name, unit_id, p.name); + continue; + } + + log_debug("Retrieved LUO session '%s' for unit fd store '%s'.", p.session_name, p.name); + } else { + log_warning("LUO mapping for unit '%s' fd '%s': unknown type '%s', skipping.", + unit_id, p.name, p.type); + continue; + } + + r = manager_dispatch_external_fd_to_unit(m, unit_id, p.name, /* index= */ 0, TAKE_FD(fd), "LUO"); + if (r > 0) + n_total++; + /* On error fd is already consumed by manager_dispatch_external_fd_to_unit. */ + } + } + + if (n_total > 0) + log_debug("Restored %d fd(s) total from LUO session.", n_total); + + return n_total; +} + +int manager_luo_serialize_fd_stores(Manager *m, FILE **ret_f, FDSet **ret_fds) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *root = NULL, *units = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_fdset_free_ FDSet *fds = NULL; + Unit *u; + int r, n_serialized = 0; + + assert(m); + assert(ret_f); + assert(ret_fds); + + if (MANAGER_IS_USER(m)) { + *ret_f = NULL; + *ret_fds = NULL; + return 0; + } + + fds = fdset_new(); + if (!fds) + return log_oom(); + + /* Build a JSON object: { "version": 1, + * "units": { "unit_id": { "fdstore": [ { "type": "fd", "name": "...", "fd": N }, + * { "type": "luo_session", "name": "...", "fd": N, "sessionName": "..." } ] }, ... } } + * This is passed to systemd-shutdown which will create a LUO session and preserve the fds. */ + HASHMAP_FOREACH(u, m->units) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *entries = NULL; + Service *s; + + if (u->type != UNIT_SERVICE) + continue; + + s = SERVICE(u); + + if (!IN_SET(s->fd_store_preserve_mode, EXEC_PRESERVE_YES, EXEC_PRESERVE_ON_SUCCESS)) + continue; + + if (!s->fd_store) + continue; + + LIST_FOREACH(fd_store, fs, s->fd_store) { + _cleanup_free_ char *session_name = NULL; + int copy; + + /* Check if this fd is itself a LUO session, as those cannot be nested and need + * special handling */ + r = fd_get_luo_session_name(fs->fd, &session_name); + if (r < 0 && r != -EMEDIUMTYPE) { + log_warning_errno(r, "Failed to check if fd '%s' of unit '%s' is a LUO session, skipping: %m", + fs->fdname, u->id); + continue; + } + + /* Ensure nobody tries to hijack our session, as we will create this later before + * kexec */ + if (streq_ptr(session_name, LUO_SESSION_NAME)) { + log_warning("Skipping fd '%s' of unit '%s' for LUO serialization, as the session name '%s' infringes systemd's namespace.", + fs->fdname, u->id, session_name); + continue; + } + + copy = fdset_put_dup(fds, fs->fd); + if (copy < 0) + return log_error_errno(copy, "Failed to duplicate fd for LUO serialization: %m"); + + r = sd_json_variant_append_arraybo( + &entries, + SD_JSON_BUILD_PAIR_STRING("type", session_name ? "luo_session" : "fd"), + SD_JSON_BUILD_PAIR_STRING("name", fs->fdname), + SD_JSON_BUILD_PAIR_INTEGER("fd", copy), + JSON_BUILD_PAIR_STRING_NON_EMPTY("sessionName", session_name)); + if (r < 0) + return log_error_errno(r, "Failed to build JSON for LUO serialization: %m"); + + n_serialized++; + } + + if (!entries) + continue; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *unit_json = NULL; + r = sd_json_buildo( + &unit_json, + SD_JSON_BUILD_PAIR_VARIANT("fdstore", entries)); + if (r < 0) + return log_error_errno(r, "Failed to build LUO unit object: %m"); + + r = sd_json_variant_set_field(&units, u->id, unit_json); + if (r < 0) + return log_error_errno(r, "Failed to add unit to LUO serialization JSON: %m"); + } + + r = sd_json_buildo( + &root, + SD_JSON_BUILD_PAIR_UNSIGNED("version", LUO_PROTOCOL_VERSION), + SD_JSON_BUILD_PAIR("state", + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_UNSIGNED("kexecsCount", m->kexecs_count))), + SD_JSON_BUILD_PAIR_CONDITION(!!units, "units", SD_JSON_BUILD_VARIANT(units))); + if (r < 0) + return log_error_errno(r, "Failed to build LUO serialization JSON: %m"); + + r = open_serialization_file("luo-fd-store", &f); + if (r < 0) + return log_error_errno(r, "Failed to create LUO serialization file: %m"); + + r = sd_json_variant_dump(root, /* flags= */ 0, f, /* prefix= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to dump LUO serialization JSON: %m"); + + r = finish_serialization_file(f); + if (r < 0) + return log_error_errno(r, "Failed to finish LUO serialization file: %m"); + + r = fd_cloexec(fileno(f), false); + if (r < 0) + return log_error_errno(r, "Failed to disable O_CLOEXEC for LUO serialization: %m"); + + r = fdset_cloexec(fds, false); + if (r < 0) + return log_error_errno(r, "Failed to disable O_CLOEXEC for LUO serialization fds: %m"); + + log_info("Serialized %d fd store entries for LUO.", n_serialized); + + *ret_f = TAKE_PTR(f); + *ret_fds = TAKE_PTR(fds); + return n_serialized; +} diff --git a/src/core/luo.h b/src/core/luo.h new file mode 100644 index 0000000000000..314006c76b4db --- /dev/null +++ b/src/core/luo.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int manager_luo_restore_fd_stores(Manager *m); +int manager_luo_serialize_fd_stores(Manager *m, FILE **ret_f, FDSet **ret_fds); diff --git a/src/core/main.c b/src/core/main.c index 3a6284b456bc3..810da526d7855 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -16,12 +15,15 @@ #include "sd-bus.h" #include "sd-daemon.h" +#include "sd-json.h" #include "sd-messages.h" #include "alloc-util.h" +#include "ansi-color.h" #include "apparmor-setup.h" #include "architecture.h" #include "argv-util.h" +#include "bpf-restrict-fsaccess.h" #include "build.h" #include "bus-error.h" #include "capability-util.h" @@ -43,11 +45,17 @@ #include "emergency-action.h" #include "env-util.h" #include "escape.h" +#include "executor.h" +#include "extract-word.h" #include "fd-util.h" #include "fdset.h" #include "fileio.h" +#include "format-table.h" #include "format-util.h" -#include "getopt-defs.h" +#include "glyph-util.h" +#include "hash-funcs.h" +#include "hashmap.h" +#include "help-util.h" #include "hexdecoct.h" #include "hostname-setup.h" #include "id128-util.h" @@ -56,6 +64,7 @@ #include "initrd-util.h" #include "io-util.h" #include "ipe-setup.h" +#include "json-util.h" #include "killall.h" #include "kmod-setup.h" #include "label-util.h" @@ -64,20 +73,23 @@ #include "load-fragment.h" #include "log.h" #include "loopback-setup.h" +#include "luo.h" #include "machine-id-setup.h" #include "main.h" #include "manager.h" #include "manager-dump.h" #include "manager-serialize.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-setup.h" #include "mount-util.h" +#include "options.h" #include "os-util.h" #include "osc-context.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" +#include "pidfd-util.h" #include "pretty-print.h" #include "proc-cmdline.h" #include "process-util.h" @@ -88,6 +100,7 @@ #include "selinux-setup.h" #include "selinux-util.h" #include "serialize.h" +#include "service.h" #include "set.h" #include "signal-util.h" #include "smack-setup.h" @@ -150,9 +163,11 @@ static char **arg_manager_environment; static uint64_t arg_capability_bounding_set; static bool arg_no_new_privs; static int arg_protect_system; +static RestrictFileSystemAccess arg_restrict_filesystem_access; static nsec_t arg_timer_slack_nsec; static Set* arg_syscall_archs; static FILE* arg_serialization; +static FILE* arg_luo_serialization; static sd_id128_t arg_machine_id; static bool arg_machine_id_from_firmware = false; static EmergencyAction arg_cad_burst_action; @@ -163,6 +178,9 @@ 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_event_loop_ratelimit_interval_sec; +static unsigned arg_event_loop_ratelimit_burst; +static usec_t arg_minimum_uptime_usec; /* A copy of the original environment block */ static char **saved_env = NULL; @@ -501,7 +519,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 +572,50 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; } + } else if (proc_cmdline_key_streq(key, "systemd.event_loop_ratelimit_interval_sec")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = parse_sec(value, &arg_event_loop_ratelimit_interval_sec); + if (r < 0) { + log_warning_errno(r, "Failed to parse systemd.event_loop_ratelimit_interval_sec= argument '%s', ignoring: %m", value); + return 0; + } + + } else if (proc_cmdline_key_streq(key, "systemd.event_loop_ratelimit_burst")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = safe_atou(value, &arg_event_loop_ratelimit_burst); + if (r < 0) { + log_warning_errno(r, "Failed to parse systemd.event_loop_ratelimit_burst= argument '%s', ignoring: %m", value); + 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 (proc_cmdline_key_streq(key, "systemd.restrict_filesystem_access")) { + + if (value) { + r = restrict_filesystem_access_from_string(value); + if (r < 0) + log_warning_errno(r, "Failed to parse systemd.restrict_filesystem_access= argument '%s', ignoring: %m", value); + else + arg_restrict_filesystem_access = r; + } else + arg_restrict_filesystem_access = RESTRICT_FILESYSTEM_ACCESS_EXEC; + } else if (streq(key, "quiet") && !value) { if (arg_show_status == _SHOW_STATUS_INVALID) @@ -705,6 +767,29 @@ static int config_parse_protect_system_pid1( return 0; } +static int config_parse_restrict_filesystem_access( + 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) { + + RestrictFileSystemAccess *v = ASSERT_PTR(data); + RestrictFileSystemAccess re; + + re = restrict_filesystem_access_from_string(rvalue); + if (re < 0) + return log_syntax_parse_error(unit, filename, line, re, lvalue, rvalue); + + *v = re; + return 0; +} + static int config_parse_crash_reboot( const char *unit, const char *filename, @@ -735,88 +820,96 @@ 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 }, + { "Manager", "RestrictFileSystemAccess", config_parse_restrict_filesystem_access, 0, &arg_restrict_filesystem_access }, #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", "EventLoopRateLimitIntervalSec", config_parse_sec, 0, &arg_event_loop_ratelimit_interval_sec }, + { "Manager", "EventLoopRateLimitBurst", config_parse_unsigned, 0, &arg_event_loop_ratelimit_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 {} }; @@ -897,6 +990,8 @@ static void set_manager_settings(Manager *m) { * counter on every daemon-reload. */ m->reload_reexec_ratelimit.interval = arg_reload_limit_interval_sec; m->reload_reexec_ratelimit.burst = arg_reload_limit_burst; + m->event_loop_ratelimit.interval = arg_event_loop_ratelimit_interval_sec; + m->event_loop_ratelimit.burst = arg_event_loop_ratelimit_burst; manager_set_watchdog(m, WATCHDOG_RUNTIME, arg_runtime_watchdog); manager_set_watchdog(m, WATCHDOG_REBOOT, arg_reboot_watchdog); @@ -908,252 +1003,275 @@ static void set_manager_settings(Manager *m) { manager_set_show_status(m, arg_show_status, "command line"); m->status_unit_format = arg_status_unit_format; + m->restrict_filesystem_access = arg_restrict_filesystem_access; } -static int parse_argv(int argc, char *argv[]) { - enum { - COMMON_GETOPT_ARGS, - SYSTEMD_GETOPT_ARGS, - }; +static int redirect_telinit(char *argv[], char **args) { + /* Check if we are invoked through the legacy interface, where init would be symlinked as telinit + * and allow users to call 'init 0' and such. If we detect such use, tell the user that this is not + * supported anymore. */ - static const struct option options[] = { - COMMON_GETOPT_OPTIONS, - SYSTEMD_GETOPT_OPTIONS, - {} - }; + if (getpid_cached() == 1 || !invoked_as(argv, "init")) + return 0; + + /* Check if the user specified one of the telinit commands in args. */ + if (!strv_overlap(args, + STRV_MAKE("0", "1", "2", "3", "4", "5", "6", + "s", "S", "q", "Q", "u", "U"))) + return 0; - int c, r; + _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL; + (void) terminal_urlify_man_full("shutdown", "8", /* suffix= */ NULL, &a); + (void) terminal_urlify_man_full("reboot", "8", /* suffix= */ NULL, &b); + (void) terminal_urlify_man_full("systemctl", "1", /* suffix= */ NULL, &c); + + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Program 'systemd' called as 'init' with a legacy telinit command.\n" + "Call %s, %s, or %s instead.", + strnull(a), strnull(b), strnull(c)); +} + +static int parse_argv(int argc, char *argv[]) { bool user_arg_seen = false; + int r; assert(argc >= 1); assert(argv); - if (getpid_cached() == 1) - opterr = 0; + int log_level_shift = getpid_cached() == 1 ? LOG_DEBUG - LOG_ERR : 0; + OptionParser opts = { + argc, argv, + .namespace = "systemd", + .log_level_shift = log_level_shift, + }; - while ((c = getopt_long(argc, argv, SYSTEMD_GETOPT_SHORT_OPTIONS, options, NULL)) >= 0) + /* Note: when new options are added here, also add them to the exclusion list in proc-cmdline.c! */ + FOREACH_OPTION(c, &opts) switch (c) { - case ARG_LOG_LEVEL: - r = log_set_max_level_from_string(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse log level \"%s\": %m", optarg); + OPTION_NAMESPACE("systemd"): {} + OPTION_COMMON_HELP: + arg_action = ACTION_HELP; break; - case ARG_LOG_TARGET: - r = log_set_target_from_string(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse log target \"%s\": %m", optarg); - + OPTION_COMMON_VERSION: + arg_action = ACTION_VERSION; break; - case ARG_LOG_COLOR: - - if (optarg) { - r = log_show_color_from_string(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse log color setting \"%s\": %m", - optarg); - } else - log_show_color(true); - + OPTION_LONG("test", NULL, "Determine initial transaction, dump it and exit"): + arg_action = ACTION_TEST; break; - case ARG_LOG_LOCATION: - if (optarg) { - r = log_show_location_from_string(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse log location setting \"%s\": %m", - optarg); - } else - log_show_location(true); + OPTION_LONG("system", NULL, "Combined with --test: operate in system mode"): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + OPTION_LONG("user", NULL, "Combined with --test: operate in user mode"): + arg_runtime_scope = RUNTIME_SCOPE_USER; + user_arg_seen = true; break; - case ARG_LOG_TIME: + OPTION_LONG("dump-configuration-items", NULL, "Dump understood unit configuration items"): + arg_action = ACTION_DUMP_CONFIGURATION_ITEMS; + break; - if (optarg) { - r = log_show_time_from_string(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse log time setting \"%s\": %m", - optarg); - } else - log_show_time(true); + OPTION_LONG("dump-bus-properties", NULL, "Dump exposed bus properties"): + arg_action = ACTION_DUMP_BUS_PROPERTIES; + break; + OPTION_LONG("bus-introspect", "PATH", "Write XML introspection data"): + arg_bus_introspect = opts.arg; + arg_action = ACTION_BUS_INTROSPECT; break; - case ARG_DEFAULT_STD_OUTPUT: - r = exec_output_from_string(optarg); + OPTION_LONG("unit", "UNIT", "Set default unit"): + r = free_and_strdup(&arg_default_unit, opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse default standard output setting \"%s\": %m", - optarg); - arg_defaults.std_output = r; + return log_error_errno(r, "Failed to set default unit \"%s\": %m", opts.arg); break; - case ARG_DEFAULT_STD_ERROR: - r = exec_output_from_string(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "dump-core", "BOOL", "Dump core on crash"): + r = parse_boolean_argument("--dump-core", opts.arg, &arg_dump_core); if (r < 0) - return log_error_errno(r, "Failed to parse default standard error output setting \"%s\": %m", - optarg); - arg_defaults.std_error = r; + return r; break; - case ARG_UNIT: - r = free_and_strdup(&arg_default_unit, optarg); + OPTION_LONG("crash-chvt", "NR", "Change to specified VT on crash"): {} + OPTION_LONG("crash-vt", "NR", /* help= */ NULL): /* compat option that was previously documented in --help */ + r = parse_crash_chvt(opts.arg, &arg_crash_chvt); if (r < 0) - return log_error_errno(r, "Failed to set default unit \"%s\": %m", optarg); - + return log_error_errno(r, "Failed to parse crash virtual terminal index '%s': %m", + opts.arg); break; - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + OPTION_LONG("crash-action", "ACTION", "Specify what to do on crash"): + r = crash_action_from_string(opts.arg); + if (r < 0) + return log_error_errno(r, "Failed to parse crash action \"%s\": %m", opts.arg); + arg_crash_action = r; break; - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; - user_arg_seen = true; + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "crash-shell", "BOOL", "Run shell on crash"): + r = parse_boolean_argument("--crash-shell", opts.arg, &arg_crash_shell); + if (r < 0) + return r; break; - case ARG_TEST: - arg_action = ACTION_TEST; - break; + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "confirm-spawn", "BOOL", + "Ask for confirmation when spawning processes"): + arg_confirm_spawn = mfree(arg_confirm_spawn); - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + r = parse_confirm_spawn(opts.arg, &arg_confirm_spawn); + if (r < 0) + return log_error_errno(r, "Failed to parse confirm spawn option: \"%s\": %m", + opts.arg); break; - case ARG_VERSION: - arg_action = ACTION_VERSION; + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "show-status", "BOOL", + "Show status updates on the console during boot"): + if (opts.arg) { + r = parse_show_status(opts.arg, &arg_show_status); + if (r < 0) + return log_error_errno(r, "Failed to parse show status boolean: \"%s\": %m", + opts.arg); + } else + arg_show_status = SHOW_STATUS_YES; break; - case ARG_DUMP_CONFIGURATION_ITEMS: - arg_action = ACTION_DUMP_CONFIGURATION_ITEMS; + OPTION_LONG("log-target", "TARGET", + "Set log target (console, journal, kmsg, journal-or-kmsg, null)"): + r = log_set_target_from_string(opts.arg); + if (r < 0) + return log_error_errno(r, "Failed to parse log target \"%s\": %m", opts.arg); break; - case ARG_DUMP_BUS_PROPERTIES: - arg_action = ACTION_DUMP_BUS_PROPERTIES; + OPTION_LONG("log-level", "LEVEL", + "Set log level (debug, info, notice, warning, err, crit, alert, emerg)"): + r = log_set_max_level_from_string(opts.arg); + if (r < 0) + return log_error_errno(r, "Failed to parse log level \"%s\": %m", opts.arg); break; - case ARG_BUS_INTROSPECT: - arg_bus_introspect = optarg; - arg_action = ACTION_BUS_INTROSPECT; + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-color", "BOOL", "Highlight important log messages"): + if (opts.arg) { + r = log_show_color_from_string(opts.arg); + if (r < 0) + return log_error_errno(r, "Failed to parse log color setting \"%s\": %m", + opts.arg); + } else + log_show_color(true); break; - case ARG_DUMP_CORE: - r = parse_boolean_argument("--dump-core", optarg, &arg_dump_core); - if (r < 0) - return r; + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-location", "BOOL", + "Include code location in log messages"): + if (opts.arg) { + r = log_show_location_from_string(opts.arg); + if (r < 0) + return log_error_errno(r, "Failed to parse log location setting \"%s\": %m", + opts.arg); + } else + log_show_location(true); break; - case ARG_CRASH_CHVT: - r = parse_crash_chvt(optarg, &arg_crash_chvt); - if (r < 0) - return log_error_errno(r, "Failed to parse crash virtual terminal index: \"%s\": %m", - optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-time", "BOOL", "Prefix log messages with current time"): + if (opts.arg) { + r = log_show_time_from_string(opts.arg); + if (r < 0) + return log_error_errno(r, "Failed to parse log time setting \"%s\": %m", + opts.arg); + } else + log_show_time(true); break; - case ARG_CRASH_SHELL: - r = parse_boolean_argument("--crash-shell", optarg, &arg_crash_shell); + OPTION_LONG("default-standard-output", "…", "Set default standard output for services"): + r = exec_output_from_string(opts.arg); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse default standard output setting \"%s\": %m", + opts.arg); + arg_defaults.std_output = r; break; - case ARG_CRASH_REBOOT: - r = parse_boolean_argument("--crash-reboot", optarg, NULL); + OPTION_LONG("default-standard-error", "…", "Set default standard error output for services"): + r = exec_output_from_string(opts.arg); if (r < 0) - return r; - arg_crash_action = r > 0 ? CRASH_REBOOT : CRASH_FREEZE; + return log_error_errno(r, "Failed to parse default standard error output setting \"%s\": %m", + opts.arg); + arg_defaults.std_error = r; break; - case ARG_CRASH_ACTION: - r = crash_action_from_string(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse crash action \"%s\": %m", optarg); - arg_crash_action = r; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_CONFIRM_SPAWN: - arg_confirm_spawn = mfree(arg_confirm_spawn); + /* Options not shown in --help. */ - r = parse_confirm_spawn(optarg, &arg_confirm_spawn); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "crash-reboot", "BOOL", /* help= */ NULL): + r = parse_boolean_argument("--crash-reboot", opts.arg, NULL); if (r < 0) - return log_error_errno(r, "Failed to parse confirm spawn option: \"%s\": %m", - optarg); + return r; + arg_crash_action = r > 0 ? CRASH_REBOOT : CRASH_FREEZE; break; - case ARG_SERVICE_WATCHDOGS: - r = parse_boolean_argument("--service-watchdogs=", optarg, &arg_service_watchdogs); + OPTION_LONG("service-watchdogs", "BOOL", /* help= */ NULL): + r = parse_boolean_argument("--service-watchdogs=", opts.arg, &arg_service_watchdogs); if (r < 0) return r; break; - case ARG_SHOW_STATUS: - if (optarg) { - r = parse_show_status(optarg, &arg_show_status); - if (r < 0) - return log_error_errno(r, "Failed to parse show status boolean: \"%s\": %m", - optarg); - } else - arg_show_status = SHOW_STATUS_YES; - break; - - case ARG_DESERIALIZE: { - int fd; - FILE *f; - - fd = parse_fd(optarg); + OPTION_LONG("deserialize", "FD", /* help= */ NULL): { + int fd = parse_fd(opts.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", opts.arg); (void) fd_cloexec(fd, true); - f = 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 ARG_SWITCHED_ROOT: + OPTION_LONG("switched-root", NULL, /* help= */ NULL): arg_switched_root = true; break; - case ARG_MACHINE_ID: - r = id128_from_string_nonzero(optarg, &arg_machine_id); + OPTION_LONG("machine-id", "ID", /* help= */ NULL): + r = id128_from_string_nonzero(opts.arg, &arg_machine_id); if (r < 0) - return log_error_errno(r, "MachineID '%s' is not valid: %m", optarg); + return log_error_errno(r, "MachineID '%s' is not valid: %m", opts.arg); break; - case 'h': - arg_action = ACTION_HELP; - break; - - case 'D': + OPTION_SHORT('D', NULL, /* help= */ NULL): log_set_max_level(LOG_DEBUG); break; - case 'b': - case 's': - case 'z': - /* Just to eat away the sysvinit kernel cmdline args that we'll parse in - * parse_proc_cmdline_item() or ignore, without any getopt() error messages. - */ - case '?': + /* Eat the sysvinit kernel cmdline args that we'll parse in + * parse_proc_cmdline_item() or ignore. */ + OPTION_SHORT('b', NULL, /* help= */ NULL): {} + OPTION_SHORT('s', NULL, /* help= */ NULL): {} + OPTION_SHORT('z', "ARG", /* help= */ NULL): + if (getpid_cached() != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Switch -%c is only supported when running as PID 1.", opts.opt->short_code); + /* In PID1, do nothing and continue with option parsing. */ + break; + OPTION_ERROR: if (getpid_cached() != 1) return -EINVAL; - else - return 0; - - default: - assert_not_reached(); + /* In PID1, silently terminate option parsing. */ + return 0; } - if (optind < argc && getpid_cached() != 1) + r = redirect_telinit(argv, option_parser_get_args(&opts)); + if (r < 0) + return r; + + if (option_parser_get_n_args(&opts) > 0 && getpid_cached() != 1) /* Hmm, when we aren't run as init system let's complain about excess arguments */ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Excess arguments."); @@ -1165,50 +1283,24 @@ static int parse_argv(int argc, char *argv[]) { } static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd", "1", &link); + r = option_parser_get_help_table_ns("systemd", &options); if (r < 0) - return log_oom(); + return r; + + help_cmdline("[OPTIONS...]"); + help_abstract("Starts and monitors system and user services."); + + printf("\nThis program takes no positional arguments.\n"); - printf("%s [OPTIONS...]\n\n" - "%sStarts and monitors system and user services.%s\n\n" - "This program takes no positional arguments.\n\n" - "%sOptions%s:\n" - " -h --help Show this help\n" - " --version Show version\n" - " --test Determine initial transaction, dump it and exit\n" - " --system Combined with --test: operate in system mode\n" - " --user Combined with --test: operate in user mode\n" - " --dump-configuration-items Dump understood unit configuration items\n" - " --dump-bus-properties Dump exposed bus properties\n" - " --bus-introspect=PATH Write XML introspection data\n" - " --unit=UNIT Set default unit\n" - " --dump-core[=BOOL] Dump core on crash\n" - " --crash-vt=NR Change to specified VT on crash\n" - " --crash-action=ACTION Specify what to do on crash\n" - " --crash-shell[=BOOL] Run shell on crash\n" - " --confirm-spawn[=BOOL] Ask for confirmation when spawning processes\n" - " --show-status[=BOOL] Show status updates on the console during boot\n" - " --log-target=TARGET Set log target (console, journal, kmsg,\n" - " journal-or-kmsg, null)\n" - " --log-level=LEVEL Set log level (debug, info, notice, warning,\n" - " err, crit, alert, emerg)\n" - " --log-color[=BOOL] Highlight important log messages\n" - " --log-location[=BOOL] Include code location in log messages\n" - " --log-time[=BOOL] Prefix log messages with current time\n" - " --default-standard-output= Set default standard output for services\n" - " --default-standard-error= Set default standard error output for services\n" - " --no-pager Do not pipe output into a pager\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - ansi_underline(), - ansi_normal(), - link); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("systemd", "1"); return 0; } @@ -1230,6 +1322,16 @@ static int prepare_reexecute( m->n_reloading++; bus_manager_send_reloading(m, true); + /* Only close the initramfs trust window when actually switching root. + * During a plain daemon-reexec in the initrd, PID1 still needs to + * execv() itself from the initramfs — clearing trust here would cause + * the BPF bprm_check_security hook to deny the exec. */ + if (switching_root) { + r = bpf_restrict_fsaccess_close_initramfs_trust(m); + if (r < 0) + return r; + } + r = manager_open_serialization(m, &f); if (r < 0) return log_error_errno(r, "Failed to create serialization file: %m"); @@ -1740,6 +1842,16 @@ 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); + + /* If we have a LUO serialization file, pass the fd to systemd-shutdown so it can + * preserve FD store entries across kexec via the kernel Live Update Orchestrator. */ + if (arg_luo_serialization) { + log_debug("Passing LUO serialization fd to systemd-shutdown."); + (void) strv_extendf(&env_block, "SYSTEMD_LUO_SERIALIZE_FD=%i", fileno(arg_luo_serialization)); + } + (void) write_boot_or_shutdown_osc("shutdown"); execve(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line, env_block); @@ -1917,8 +2029,10 @@ static void update_numa_policy(bool skip_setup) { } r = apply_numa_policy(&arg_numa_policy); - if (r == -EOPNOTSUPP) + if (r == -ENOSYS) log_debug_errno(r, "NUMA support not available, ignoring."); + else if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + log_warning_errno(r, "NUMA policy not supported by kernel, ignoring."); else if (r < 0) log_warning_errno(r, "Failed to set NUMA memory policy, ignoring: %m"); } @@ -2132,8 +2246,8 @@ static int do_reexecute( } /* Try the fallback, if there is any, without any serialization. We pass the original argv[] and - * envp[]. (Well, modulo the ordering changes due to getopt() in argv[], and some cleanups in envp[], - * but let's hope that doesn't matter.) */ + * envp[]. (Well, modulo the ordering changes due to option parsing in argv[], and some cleanups in + * envp[], but let's hope that doesn't matter.) */ arg_serialization = safe_fclose(arg_serialization); fds = fdset_free(fds); @@ -2632,6 +2746,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()) @@ -2812,6 +2928,7 @@ static void reset_arguments(void) { arg_capability_bounding_set = CAP_MASK_ALL; arg_no_new_privs = false; arg_protect_system = -1; + arg_restrict_filesystem_access = RESTRICT_FILESYSTEM_ACCESS_NO; arg_timer_slack_nsec = NSEC_INFINITY; arg_syscall_archs = set_free(arg_syscall_archs); @@ -2830,6 +2947,11 @@ static void reset_arguments(void) { arg_reload_limit_interval_sec = 0; arg_reload_limit_burst = 0; + + arg_event_loop_ratelimit_interval_sec = 1 * USEC_PER_SEC; + arg_event_loop_ratelimit_burst = 50000; + + arg_minimum_uptime_usec = USEC_INFINITY; } static void determine_default_oom_score_adjust(void) { @@ -3002,10 +3124,254 @@ static int initialize_security( return 0; } -static int collect_fds(FDSet **ret_fds, const char **ret_error_message) { +static int parse_listen_fds_env(unsigned *ret_n_fds, char ***ret_names) { + _cleanup_strv_free_ char **names = NULL; + const char *e; + unsigned n_fds; + int r; + + assert(ret_n_fds); + assert(ret_names); + + /* Parse and validate the LISTEN_PID=/LISTEN_PIDFDID=/LISTEN_FDS=/LISTEN_FDNAMES= environment + * variables. */ + + e = secure_getenv("LISTEN_PID"); + if (!e) + return -ENXIO; + + pid_t pid; + r = parse_pid(e, &pid); + if (r < 0) + return log_debug_errno(r, "Failed to parse LISTEN_PID=%s: %m", e); + if (pid != getpid_cached()) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "LISTEN_PID=%s does not match our own PID " PID_FMT ", ignoring.", + e, + getpid_cached()); + + e = secure_getenv("LISTEN_PIDFDID"); + if (e) { + uint64_t own_pidfdid, pidfdid; + + r = safe_atou64(e, &pidfdid); + if (r < 0) + return log_debug_errno(r, "Failed to parse LISTEN_PIDFDID=%s: %m", e); + + if (pidfd_get_inode_id_self_cached(&own_pidfdid) >= 0 && pidfdid != own_pidfdid) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "LISTEN_PIDFDID=%s does not match our own pidfdid %" PRIu64 ", ignoring.", + e, + own_pidfdid); + } + + e = secure_getenv("LISTEN_FDS"); + if (!e) + return -ENXIO; + + r = safe_atou(e, &n_fds); + if (r < 0) + return log_debug_errno(r, "Failed to parse LISTEN_FDS= value '%s': %m", e); + if (n_fds == 0) + return -ENXIO; + if (n_fds > INT_MAX - SD_LISTEN_FDS_START) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid number of fds in LISTEN_FDS= value '%s'", e); + + e = secure_getenv("LISTEN_FDNAMES"); + if (!e) + return -ENXIO; + + r = strv_split_full(&names, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return log_debug_errno(r, "Failed to parse LISTEN_FDNAMES=%s: %m", e); + if (strv_length(names) != (size_t) n_fds) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Mismatch between number of LISTEN_FDS= and LISTEN_FDNAMES= entries: %u vs %zu", + n_fds, strv_length(names)); + + *ret_n_fds = n_fds; + *ret_names = TAKE_PTR(names); + return 0; +} + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + index_to_tag_hash_ops, + uint64_t, uint64_hash_func, uint64_compare_func, + ListenFDsTag, listen_fds_tag_free); + +static int parse_listen_fds_mapping(int mapping_fd, Hashmap **ret_index_to_tag) { + int r; + + assert(mapping_fd >= 0); + assert(ret_index_to_tag); + + /* Parse the JSON mapping memfd that the downstream manager pushed alongside the indexed fds: + * { "unit-name.service": [ { "name": "fdname1", "index": 1 }, ... ], ... } + * Returns a hashmap keyed by stringified index ("1", "2", ...) with ListenFDsTag* values + * carrying the resolved (unit_id, original fdname, upstream index). */ + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *root = NULL; + r = sd_json_parse_fd( + "fdstore-mapping", + mapping_fd, + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_REOPEN_FD, + &root, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL); + if (r < 0) + return log_warning_errno(r, "Failed to parse fdstore-mapping JSON: %m"); + + _cleanup_hashmap_free_ Hashmap *index_to_tag = NULL; + sd_json_variant *fds_json; + const char *unit_id; + JSON_VARIANT_OBJECT_FOREACH(unit_id, fds_json, root) { + sd_json_variant *entry; + + if (!unit_name_is_valid(unit_id, UNIT_NAME_ANY)) { + log_warning("fdstore-mapping has invalid unit name '%s', skipping.", unit_id); + continue; + } + + JSON_VARIANT_ARRAY_FOREACH(entry, fds_json) { + struct { + const char *name; + uint64_t index; + } p = { }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + { "index", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, index), SD_JSON_MANDATORY }, + {} + }; + + r = sd_json_dispatch(entry, dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG|SD_JSON_WARNING, &p); + if (r < 0) + continue; + + if (p.index == 0) { + log_warning("fdstore-mapping entry for unit '%s' name '%s' has zero index, skipping.", unit_id, p.name); + continue; + } + + _cleanup_(listen_fds_tag_freep) ListenFDsTag *t = new(ListenFDsTag, 1); + if (!t) + return log_oom(); + + *t = (ListenFDsTag) { + .index = p.index, + }; + + t->unit_id = strdup(unit_id); + t->fdname = strdup(p.name); + if (!t->unit_id || !t->fdname) + return log_oom(); + + /* Key points into the value struct, so freeing the value frees the key. */ + r = hashmap_ensure_put(&index_to_tag, &index_to_tag_hash_ops, &t->index, t); + if (r < 0) + return log_warning_errno(r, "Failed to insert fdstore-mapping entry into hashmap: %m"); + if (r > 0) + TAKE_PTR(t); + } + } + + *ret_index_to_tag = TAKE_PTR(index_to_tag); + return 0; +} + +static int collect_listen_fds_named(FDSet *fds, Hashmap **ret_named_fds) { + _cleanup_hashmap_free_ Hashmap *named_fds = NULL, *index_to_tag = NULL; + _cleanup_strv_free_ char **names = NULL; + unsigned n_fds; + int r; + + assert(fds); + assert(ret_named_fds); + + /* Pull entries from the LISTEN_FDS=/LISTEN_FDNAMES= protocol out of 'fds' into a hashmap + * keyed by fd. Two flavours of named entries are recognized: + * + * - A single mapping memfd whose fdname matches SERVICE_FDSTORE_MAPPING_FDNAME, which + * contains a JSON map pairing numeric indices to (unit-id, original-fdname). + * - Numeric indices (matching entries in the mapping document) for the actual fds. + * + * The hashmap owns the fds (closed via destructor on cleanup) so any entries the dispatcher + * does not consume are correctly cleaned up. */ + + r = parse_listen_fds_env(&n_fds, &names); + if (r < 0) { + /* Fail gracefully here, just warn and ignore but otherwise proceed on parsing failure */ + if (r != -ENXIO) + log_warning_errno(r, "Failed to parse LISTEN_FDS environment, ignoring: %m"); + *ret_named_fds = NULL; + return 0; + } + + /* First pass: locate and parse the mapping memfd, if any. */ + for (unsigned i = 0; i < n_fds; i++) { + int fd = SD_LISTEN_FDS_START + i; + + if (!streq(names[i], SERVICE_FDSTORE_MAPPING_FDNAME)) + continue; + + if (!fdset_contains(fds, fd)) + continue; + + (void) parse_listen_fds_mapping(fd, &index_to_tag); + + /* The mapping memfd itself is not routed to any unit; close it and remove from fds + * so it doesn't get redistributed */ + assert_se(fdset_remove(fds, fd) == fd); + safe_close(fd); + break; + } + + /* Second pass: route fds whose name matches an entry in the mapping. */ + for (unsigned i = 0; i < n_fds; i++) { + int fd = SD_LISTEN_FDS_START + i; + const char *name = names[i], *suffix; + ListenFDsTag *t; + uint64_t idx; + + if (!fdset_contains(fds, fd)) + continue; + + if (!index_to_tag) + continue; + + suffix = startswith(name, SERVICE_FDSTORE_SUB_FDNAME_PREFIX); + if (!suffix || safe_atou64(suffix, &idx) < 0) + continue; + + /* Steal the matching mapping entry — we transfer ownership of the parsed + * (unit_id, fdname, index) struct into the per-fd hashmap that the manager + * will consume. */ + t = hashmap_remove(index_to_tag, &idx); + if (!t) + continue; + + _cleanup_(listen_fds_tag_freep) ListenFDsTag *t_owned = t; + + r = hashmap_ensure_put(&named_fds, &fd_to_listen_fds_tag_hash_ops, FD_TO_PTR(fd), t_owned); + if (r < 0) + return log_debug_errno(r, "Failed to insert named fd into hashmap: %m"); + if (r == 0) + continue; /* fd already inserted, cannot really happen */ + + TAKE_PTR(t_owned); + + assert_se(fdset_remove(fds, fd) == fd); + } + + *ret_named_fds = TAKE_PTR(named_fds); + return 1; +} + +static int collect_fds(FDSet **ret_fds, Hashmap **ret_named_fds, const char **ret_error_message) { int r; assert(ret_fds); + assert(ret_named_fds); assert(ret_error_message); /* Pick up all fds passed to us. We apply a filter here: we only take the fds that have O_CLOEXEC @@ -3027,6 +3393,8 @@ static int collect_fds(FDSet **ret_fds, const char **ret_error_message) { /* The serialization fd should have O_CLOEXEC turned on already, let's verify that we didn't pick it up here */ assert_se(!arg_serialization || !fdset_contains(*ret_fds, fileno(arg_serialization))); + (void) collect_listen_fds_named(*ret_fds, ret_named_fds); + return 0; } @@ -3071,7 +3439,7 @@ static int save_env(void) { return 0; } -int main(int argc, char *argv[]) { +static int run_systemd(int argc, char *argv[]) { dual_timestamp initrd_timestamp = DUAL_TIMESTAMP_NULL, userspace_timestamp = DUAL_TIMESTAMP_NULL, @@ -3084,6 +3452,7 @@ int main(int argc, char *argv[]) { * for the two that indicate whether * these fields are initialized! */ bool skip_setup, loaded_policy = false, queue_default_job = false, first_boot = false; + _cleanup_hashmap_free_ Hashmap *named_listen_fds = NULL; char *switch_root_dir = NULL, *switch_root_init = NULL; usec_t before_startup, after_startup; static char systemd[] = "systemd"; @@ -3237,15 +3606,19 @@ int main(int argc, char *argv[]) { /* The efivarfs is now mounted, let's lock down the system token. */ lock_down_efi_variables(); + } else { /* Running as user instance */ arg_runtime_scope = RUNTIME_SCOPE_USER; - log_set_always_reopen_console(true); - log_set_target_and_open(LOG_TARGET_AUTO); /* clear the kernel timestamp, because we are not PID 1 */ kernel_timestamp = DUAL_TIMESTAMP_NULL; + if (invoked_by_systemd()) { + log_set_always_reopen_console(true); + log_set_target_and_open(LOG_TARGET_AUTO); + } + r = mac_init(); if (r < 0) { error_message = "Failed to initialize MAC support"; @@ -3257,9 +3630,11 @@ int main(int argc, char *argv[]) { * transitioning from the initrd to the main systemd or suchlike. */ save_rlimits(&saved_rlimit_nofile, &saved_rlimit_memlock); - /* Reset all signal handlers. */ + /* Reset all signal handlers to clean up after reexec. */ (void) reset_all_signal_handlers(); - (void) ignore_signals(SIGNALS_IGNORE); + + if (getpid_cached() == 1 || invoked_by_systemd()) + (void) ignore_signals(SIGNALS_IGNORE); (void) parse_configuration(&saved_rlimit_nofile, &saved_rlimit_memlock); @@ -3323,7 +3698,7 @@ int main(int argc, char *argv[]) { log_close(); /* Remember open file descriptors for later deserialization */ - r = collect_fds(&fds, &error_message); + r = collect_fds(&fds, &named_listen_fds, &error_message); if (r < 0) goto finish; @@ -3354,7 +3729,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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) { error_message = "Failed to load libmount.so"; goto finish; @@ -3398,7 +3773,7 @@ int main(int argc, char *argv[]) { before_startup = now(CLOCK_MONOTONIC); - r = manager_startup(m, arg_serialization, fds, /* root= */ NULL); + r = manager_startup(m, arg_serialization, fds, named_listen_fds, /* root= */ NULL); if (r < 0) { error_message = "Failed to start up manager"; goto finish; @@ -3450,6 +3825,12 @@ int main(int argc, char *argv[]) { if (m) { arg_reboot_watchdog = manager_get_watchdog(m, WATCHDOG_REBOOT); arg_kexec_watchdog = manager_get_watchdog(m, WATCHDOG_KEXEC); + + /* For kexec, serialize fd stores now. Services have stopped and sent + * their FDs to the store, but the manager (and its fd stores) is still alive. */ + if (r == MANAGER_KEXEC) + (void) manager_luo_serialize_fd_stores(m, &arg_luo_serialization, &fds); + m = manager_free(m); } @@ -3467,7 +3848,13 @@ int main(int argc, char *argv[]) { &error_message); /* This only returns if reexecution failed */ arg_serialization = safe_fclose(arg_serialization); - fds = fdset_free(fds); + + /* For kexec, the FDSet and LUO serialization file must survive until become_shutdown() calls + * execve() (CLOEXEC is already cleared on these FDs). For all other paths, free them now. */ + if (r != MANAGER_KEXEC) { + fds = fdset_free(fds); + arg_luo_serialization = safe_fclose(arg_luo_serialization); + } saved_env = strv_free(saved_env); @@ -3533,3 +3920,11 @@ int main(int argc, char *argv[]) { reset_arguments(); return retval; } + +int main(int argc, char *argv[]) { +#if BUILD_EXECUTOR_SINGLE + if (invoked_as(argv, "executor")) + return run_executor(argc, argv); +#endif + return run_systemd(argc, argv); +} diff --git a/src/core/manager-serialize.c b/src/core/manager-serialize.c index 2357b9277c616..72c6aa19a3bdc 100644 --- a/src/core/manager-serialize.c +++ b/src/core/manager-serialize.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-id128.h" + #include "alloc-util.h" +#include "bpf-restrict-fsaccess.h" #include "dbus.h" #include "dynamic-user.h" #include "fd-util.h" @@ -10,13 +13,17 @@ #include "glyph-util.h" #include "hashmap.h" #include "initrd-util.h" +#include "job.h" #include "manager.h" #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" +#include "unit.h" +#include "unit-name.h" #include "unit-serialize.h" #include "user-util.h" #include "varlink.h" @@ -117,6 +124,8 @@ int manager_serialize( (void) serialize_item(f, "previous-objective", manager_objective_to_string(m->objective)); (void) serialize_item_format(f, "soft-reboots-count", "%u", m->soft_reboots_count); + (void) serialize_item_format(f, "kexecs-count", "%u", m->kexecs_count); + (void) serialize_item_format(f, "fd-store-upstream-next-index", "%" PRIu64, m->fd_store_upstream_next_index); for (ManagerTimestamp q = 0; q < _MANAGER_TIMESTAMP_MAX; q++) { _cleanup_free_ char *joined = NULL; @@ -156,6 +165,7 @@ int manager_serialize( (void) serialize_ratelimit(f, "dump-ratelimit", &m->dump_ratelimit); (void) serialize_ratelimit(f, "reload-reexec-ratelimit", &m->reload_reexec_ratelimit); + (void) serialize_ratelimit(f, "event-loop-ratelimit", &m->event_loop_ratelimit); (void) serialize_id128(f, "bus-id", m->bus_id); bus_track_serialize(m->subscribed, f, "subscribed"); @@ -179,6 +189,10 @@ int manager_serialize( if (r < 0) return r; + r = bpf_restrict_fsaccess_serialize(m, f, fds); + if (r < 0) + return r; + (void) fputc('\n', f); HASHMAP_FOREACH_KEY(u, t, m->units) { @@ -197,7 +211,131 @@ 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_synthesize_orphaned_unit( + Manager *m, + const char *original_name, + const char *canonical_name, + FILE *f, + FDSet *fds) { + + _cleanup_(unit_freep) Unit *orphan = NULL; + _cleanup_free_ char *orphaned_name = NULL; + sd_id128_t rnd; + UnitType t; + int r; + + assert(m); + assert(original_name); + assert(canonical_name); + assert(f); + assert(fds); + + t = unit_name_to_type(original_name); + if (t < 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "Cannot synthesize unit for '%s' (overridden by alias to '%s'): invalid unit type. Skipping stale state.", + original_name, canonical_name); + + /* Only transition units that track external resources and can be renamed/know aliases, forget internal ones (eg: timers) */ + if (!unit_vtable[t]->track_orphaned) + return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Cannot synthesize unit for '%s' (overridden by alias to '%s'): unsupported unit type. Skipping stale state.", + original_name, canonical_name); + + /* Use a naming convention with an "orphaned-" prefix to make it clear at a glance that these units + * were synthesized to adopt resources from a now-aliased unit */ + r = sd_id128_randomize(&rnd); + if (r < 0) + return log_warning_errno(r, "Failed to generate random ID for orphan unit: %m"); + + if (asprintf(&orphaned_name, "orphaned-r" SD_ID128_FORMAT_STR ".%s", + SD_ID128_FORMAT_VAL(rnd), unit_type_to_string(t)) < 0) + return log_oom(); + + r = unit_new_for_name(m, unit_vtable[t]->object_size, orphaned_name, &orphan); + if (r < 0) + return log_warning_errno(r, "Failed to allocate orphan unit '%s': %m", orphaned_name); + + /* Force the state as we know the unit file is gone, so it will hang around as long as resources are + * active, and then it will be automatically collected */ + orphan->load_state = UNIT_NOT_FOUND; + /* Ensure the orphan also gets garbage-collected when it ends up in 'failed' state. */ + orphan->collect_mode = COLLECT_INACTIVE_OR_FAILED; + + _cleanup_free_ char *description = strjoin("Orphaned resources adopted from aliased unit ", original_name); + if (!description) + return log_oom(); + + r = unit_deserialize_state(orphan, f, fds); + if (r < 0) + return log_notice_errno(r, "Failed to deserialize state into orphan unit '%s': %m", orphaned_name); + + /* From this point on the serialized stream for this unit has been fully consumed, so avoid failing. */ + + log_warning("Unit file for '%s' was overridden by a symlink to '%s'. Synthesized orphan unit '%s' to retain tracking of the previous unit's processes.", + original_name, canonical_name, orphaned_name); + + /* Override Description= so it's clear what this is for and can be traced back to the original. */ + free_and_replace(orphan->description, description); + + /* Any jobs reinstalled from the deserialized state targeted the original unit. Most of them + * (start, restart, reload, ...) make no sense for the synthesized orphan, especially after a + * service-to-scope conversion, so cancel them. JOB_STOP is the exception: a stop request that was + * already pending on the original unit is exactly what we want to keep around, since it'll cause + * the synthesized unit to just go away immediately. */ + if (orphan->job && orphan->job->type != JOB_STOP) + job_finish_and_invalidate(orphan->job, JOB_CANCELED, /* recursive= */ false, /* already= */ false); + if (orphan->nop_job) + job_finish_and_invalidate(orphan->nop_job, JOB_CANCELED, /* recursive= */ false, /* already= */ false); + + TAKE_PTR(orphan); + 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 +345,77 @@ 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 + * onto the canonical unit would corrupt its live state. Instead, we synthesize a new unloaded + * unit with a unique name and migrate the cgroup/PID/etc. tracking from the stale state + * into it, so that the resources from the previously independent unit remain tracked. + * + * 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 unit (from before + * the symlink existed), but b.service now resolves to a.service. We retain a.service's + * running state, and synthesize an unloaded unit to keep tracking the processes that + * b.service used to own. + * + * Note: Log messages from this code path are checked in TEST-07-PID1.alias-corruption.sh, + * so the test case may need adjustment if they are changed. + */ + r = manager_synthesize_orphaned_unit(m, name, u->id, f, fds); + if (r < 0) /* If we fail to orphan for any reason, then discard the unit */ + r = unit_deserialize_state_skip(f); + return r; + } + 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 +426,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) { @@ -285,6 +482,38 @@ static void manager_deserialize_gid_refs_one(Manager *m, const char *value) { manager_deserialize_uid_refs_one_internal(&m->gid_refs, value); } +static void deserialize_restrict_fsaccess(Manager *m, const char *l, FDSet *fds) { + const char *val; + int fd; + + FOREACH_ELEMENT(name, restrict_fsaccess_link_names) { + val = startswith(l, *name); + if (!val) + continue; + val = startswith(val, "="); + if (!val) + continue; + fd = deserialize_fd(fds, val); + if (fd < 0) { + log_warning_errno(fd, "bpf-restrict-fsaccess: Failed to deserialize FD for %s: %m", *name); + return; + } + close_and_replace(m->restrict_fsaccess_link_fds[name - restrict_fsaccess_link_names], fd); + return; + } + + val = startswith(l, "restrict-fsaccess-bss-map="); + if (!val) + return; + + fd = deserialize_fd(fds, val); + if (fd < 0) { + log_warning_errno(fd, "bpf-restrict-fsaccess: Failed to deserialize FD for .bss map: %m"); + return; + } + close_and_replace(m->restrict_fsaccess_bss_map_fd, fd); +} + int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { int r; @@ -515,10 +744,14 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { else (void) varlink_server_deserialize_one(m->varlink_server, val, fds); - } else if ((val = startswith(l, "dump-ratelimit="))) + } else if (startswith(l, "restrict-fsaccess-")) + deserialize_restrict_fsaccess(m, l, fds); + else if ((val = startswith(l, "dump-ratelimit="))) deserialize_ratelimit(&m->dump_ratelimit, "dump-ratelimit", val); else if ((val = startswith(l, "reload-reexec-ratelimit="))) deserialize_ratelimit(&m->reload_reexec_ratelimit, "reload-reexec-ratelimit", val); + else if ((val = startswith(l, "event-loop-ratelimit="))) + deserialize_ratelimit(&m->event_loop_ratelimit, "event-loop-ratelimit", val); else if ((val = startswith(l, "soft-reboots-count="))) { unsigned n; @@ -526,6 +759,16 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { log_notice("Failed to parse soft reboots counter '%s', ignoring.", val); else m->soft_reboots_count = n; + } else if ((val = startswith(l, "kexecs-count="))) { + unsigned n; + + if (safe_atou(val, &n) < 0) + log_notice("Failed to parse kexecs counter '%s', ignoring.", val); + else + m->kexecs_count = n; + } else if ((val = startswith(l, "fd-store-upstream-next-index="))) { + if (safe_atou64(val, &m->fd_store_upstream_next_index) < 0) + log_notice("Failed to parse fd-store-upstream-next-index '%s', ignoring.", val); } else if ((val = startswith(l, "previous-objective="))) { ManagerObjective objective; diff --git a/src/core/manager.c b/src/core/manager.c index 79fa19d976eb3..015f575ac1b5e 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -21,6 +21,7 @@ #include "audit-fd.h" #include "boot-timestamps.h" #include "bpf-restrict-fs.h" +#include "bpf-restrict-fsaccess.h" #include "build-path.h" #include "bus-common-errors.h" #include "bus-error.h" @@ -56,10 +57,11 @@ #include "libaudit-util.h" #include "locale-setup.h" #include "log.h" +#include "luo.h" #include "manager-dump.h" #include "manager-serialize.h" #include "manager.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "notify-recv.h" #include "parse-util.h" @@ -75,6 +77,7 @@ #include "rm-rf.h" #include "selinux-util.h" #include "serialize.h" +#include "service.h" #include "set.h" #include "signal-util.h" #include "socket-util.h" @@ -93,6 +96,7 @@ #include "umask-util.h" #include "unit-name.h" #include "user-util.h" +#include "varlink-unit.h" #include "varlink.h" #include "virt.h" #include "watchdog.h" @@ -616,9 +620,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 +804,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; @@ -875,6 +895,30 @@ usec_t manager_default_timeout(RuntimeScope scope) { return scope == RUNTIME_SCOPE_SYSTEM ? DEFAULT_TIMEOUT_USEC : DEFAULT_USER_TIMEOUT_USEC; } +static int pin_executor_binary(int *ret_fd) { + _cleanup_free_ char *path = NULL; + + assert(ret_fd); + +#if BUILD_EXECUTOR_SINGLE + int r; + + r = open_and_check_executable("/proc/self/exe", /* root= */ NULL, &path, ret_fd); + if (r < 0) + return log_debug_errno(r, "Failed to pin executor binary %s: %m", "/proc/self/exe"); +#else + int fd; + + fd = pin_callout_binary(SYSTEMD_EXECUTOR_BINARY_PATH, &path); + if (fd < 0) + return log_debug_errno(fd, "Failed to pin executor binary %s: %m", SYSTEMD_EXECUTOR_BINARY_PATH); + *ret_fd = fd; +#endif + + log_debug("Using systemd-executor binary %s.", path); + return 0; +} + int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags, Manager **ret) { _cleanup_(manager_freep) Manager *m = NULL; int r; @@ -924,8 +968,13 @@ int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags, .dump_ratelimit = (const RateLimit) { .interval = 10 * USEC_PER_MINUTE, .burst = 10 }, .executor_fd = -EBADF, + + .restrict_fsaccess_bss_map_fd = -EBADF, }; + FOREACH_ELEMENT(fd, m->restrict_fsaccess_link_fds) + *fd = -EBADF; + unit_defaults_init(&m->defaults, runtime_scope); #if ENABLE_EFI @@ -1001,9 +1050,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)) { @@ -1030,11 +1081,9 @@ int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags, } if (!FLAGS_SET(test_run_flags, MANAGER_TEST_DONT_OPEN_EXECUTOR)) { - m->executor_fd = pin_callout_binary(SYSTEMD_EXECUTOR_BINARY_PATH, &m->executor_path); - if (m->executor_fd < 0) - return log_debug_errno(m->executor_fd, "Failed to pin executor binary: %m"); - - log_debug("Using systemd-executor binary from '%s'.", m->executor_path); + r = pin_executor_binary(&m->executor_fd); + if (r < 0) + return r; } /* Note that we do not set up the notify fd here. We do that after deserialization, @@ -1652,6 +1701,7 @@ static void manager_clear_jobs_and_units(Manager *m) { assert(!m->start_when_upheld_queue); assert(!m->stop_when_bound_queue); assert(!m->release_resources_queue); + assert(!m->stop_notify_queue); assert(hashmap_isempty(m->jobs)); assert(hashmap_isempty(m->units)); @@ -1711,7 +1761,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); @@ -1764,9 +1815,10 @@ Manager* manager_free(Manager *m) { #if BPF_FRAMEWORK bpf_restrict_fs_destroy(m->restrict_fs); #endif + close_many(m->restrict_fsaccess_link_fds, ELEMENTSOF(m->restrict_fsaccess_link_fds)); + safe_close(m->restrict_fsaccess_bss_map_fd); safe_close(m->executor_fd); - free(m->executor_path); return mfree(m); } @@ -1850,6 +1902,93 @@ static void manager_catchup(Manager *m) { } } +ListenFDsTag* listen_fds_tag_free(ListenFDsTag *t) { + if (!t) + return NULL; + + free(t->unit_id); + free(t->fdname); + return mfree(t); +} + +DEFINE_HASH_OPS_FULL( + fd_to_listen_fds_tag_hash_ops, + void, trivial_hash_func, trivial_compare_func, close_fd_ptr, + ListenFDsTag, listen_fds_tag_free); + +int manager_dispatch_external_fd_to_unit( + Manager *m, + const char *unit_id, + const char *fdname, + uint64_t index, + int fd_in, + const char *log_context) { + + _cleanup_close_ int fd = ASSERT_FD(fd_in); + Unit *u = NULL; + int r; + + assert(m); + assert(unit_id); + assert(fdname); + assert(log_context); + + /* Load the unit eagerly: if the unit file exists this brings it into UNIT_LOADED, otherwise it + * lands in UNIT_NOT_FOUND. In both cases we want to attach the fd so it's preserved until the + * unit is fully stopped (or its file appears via daemon-reload). */ + r = manager_load_unit(m, unit_id, /* path= */ NULL, /* e= */ NULL, &u); + if (r < 0) + return log_warning_errno(r, "%s: failed to load unit '%s', closing fd '%s': %m", + log_context, unit_id, fdname); + + if (!UNIT_VTABLE(u)->attach_external_fd_to_fdstore) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: unit '%s' does not support fd restoration, closing fd '%s'.", + log_context, unit_id, fdname); + + r = UNIT_VTABLE(u)->attach_external_fd_to_fdstore(u, TAKE_FD(fd), fdname, index); + if (r < 0) + return log_unit_warning_errno(u, r, "%s: failed to attach fd '%s' to fd store: %m", + log_context, fdname); + + return 1; /* fd consumed */ +} + +static int manager_distribute_listen_fds_named(Manager *m, Hashmap *named_listen_fds) { + assert(m); + + /* Route fds whose LISTEN_FDNAMES name was a numeric index into the matching unit's fd store. + * The hashmap is built and owned by main.c's collect_fds(), keyed by fd, with ListenFDsTag* values + * that already carry the parsed unit-id, original fdname and index (resolved against the + * upstream-pushed fdstore-mapping memfd). We steal entries here so any leftover (skipped) entries + * are still cleaned up by the hashmap's destructor on the caller side. */ + + if (MANAGER_IS_TEST_RUN(m)) + return 0; + + for (;;) { + _cleanup_(listen_fds_tag_freep) ListenFDsTag *t = NULL; + _cleanup_close_ int fd = -EBADF; + void *key; + + t = hashmap_steal_first_key_and_value(named_listen_fds, &key); + if (!t) + break; + + fd = PTR_TO_FD(key); + + if (!t->unit_id || !t->fdname) + continue; + + if (!unit_name_is_valid(t->unit_id, UNIT_NAME_ANY)) + continue; + + (void) manager_dispatch_external_fd_to_unit(m, t->unit_id, t->fdname, t->index, TAKE_FD(fd), "LISTEN_FDS"); + } + + return 0; +} + static void manager_distribute_fds(Manager *m, FDSet *fds) { Unit *u; @@ -1982,6 +2121,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--; @@ -2004,7 +2145,7 @@ static int manager_make_runtime_dir(Manager *m) { return 0; } -int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *root) { +int manager_startup(Manager *m, FILE *serialization, FDSet *fds, Hashmap *named_listen_fds, const char *root) { int r; assert(m); @@ -2073,6 +2214,17 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *roo if (m->previous_objective == MANAGER_SOFT_REBOOT) m->soft_reboots_count++; + /* If a LUO (Live Update Orchestrator) session from a previous kexec is available, restore + * preserved file descriptors into the appropriate service fd stores now, before coldplug. + * Only do this when booting up (i.e., skip on reexec/soft reboot/etc.) */ + if (m->previous_objective < 0) + (void) manager_luo_restore_fd_stores(m); + + /* Pick up fds passed via the LISTEN_FDS=/LISTEN_FDNAMES= protocol that are tagged with a + * unit id ("unit-id|fdname"), and route them into the matching unit's fd store. Untagged + * fds remain in 'fds' and are handed to socket units below as before. */ + (void) manager_distribute_listen_fds_named(m, named_listen_fds); + /* Any fds left? Find some unit which wants them. This is useful to allow container managers to pass * some file descriptors to us pre-initialized. This enables socket-based activation of entire * containers. */ @@ -2112,12 +2264,23 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *roo /* Clean up runtime objects */ manager_vacuum(m); + /* After deserialization, refresh the upstream JSON mapping memfd so the supervisor's + * view of our fd store stays consistent with the indices we just restored. */ + (void) service_propagate_fd_store_mapping_upstream(m); + if (serialization) /* Let's wait for the UnitNew/JobNew messages being sent, before we notify that the * reload is finished */ m->send_reloading_done = true; } + /* Set up RestrictFileSystemAccess= BPF LSM after deserialization (so we can detect deserialized link FDs) + * and before clearing switching_root (so we can close the initramfs trust window). This must + * run after set_manager_settings() has set m->restrict_filesystem_access. */ + r = bpf_restrict_fsaccess_setup(m); + if (r < 0) + return r; + manager_ready(m); manager_set_switching_root(m, false); @@ -2125,6 +2288,128 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *roo return 0; } +int manager_add_jobs( + Manager *m, + JobType type, + char * const *names, + bool reload_if_possible, + JobMode mode, + TransactionAddFlags extra_flags, + Set *affected_jobs, + sd_bus_error *reterr_error, + Set *ret_jobs) { + + _cleanup_(transaction_abort_and_freep) Transaction *tr = NULL; + Job *j; + int r; + + assert(m); + assert(type >= 0 && type < _JOB_TYPE_MAX); + assert(!strv_isempty(names)); + assert(mode >= 0 && mode < _JOB_MODE_MAX); + assert((extra_flags & ~_TRANSACTION_FLAGS_MASK_PUBLIC) == 0); + + if (mode == JOB_ISOLATE && type != JOB_START) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Isolate is only valid for start."); + + if (mode == JOB_TRIGGERING && type != JOB_STOP) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, + "--job-mode=triggering is only valid for stop."); + + if (mode == JOB_RESTART_DEPENDENCIES && type != JOB_START) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, + "--job-mode=restart-dependencies is only valid for start."); + + if (mode == JOB_ISOLATE && strv_length(names) > 1) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_NOT_SUPPORTED, + "Isolating more than one unit is not supported."); + + tr = transaction_new(mode == JOB_REPLACE_IRREVERSIBLY, ++m->last_transaction_id); + if (!tr) + return -ENOMEM; + + LOG_CONTEXT_PUSHF("TRANSACTION_ID=%" PRIu64, tr->id); + + STRV_FOREACH(name, names) { + Unit *u; + JobType t = type; + JobType merged_type; + + r = manager_load_unit(m, *name, NULL, reterr_error, &u); + if (r < 0) + return r; + + if (mode == JOB_ISOLATE && !u->allow_isolate) + return sd_bus_error_setf(reterr_error, BUS_ERROR_NO_ISOLATION, + "Operation refused, unit %s may not be isolated.", u->id); + + /* Per-unit validation and reload-if-possible mangling: units that can reload turn + * JOB_RESTART into JOB_RELOAD_OR_START and JOB_TRY_RESTART into JOB_TRY_RELOAD; others + * keep the original restart type. Also rejects manual start/stop on units that refuse + * it, etc. */ + r = unit_queue_job_check_and_mangle_type(u, &t, reload_if_possible, reterr_error); + if (r < 0) + return r; + + merged_type = job_type_collapse(t, u); + + log_unit_debug(u, "Trying to enqueue job %s/%s/%s", + u->id, job_type_to_string(merged_type), job_mode_to_string(mode)); + + r = transaction_add_job_and_dependencies( + tr, + merged_type, + u, + /* by= */ NULL, + TRANSACTION_MATTERS | + (IN_SET(mode, JOB_IGNORE_DEPENDENCIES, JOB_IGNORE_REQUIREMENTS) ? TRANSACTION_IGNORE_REQUIREMENTS : 0) | + (mode == JOB_IGNORE_DEPENDENCIES ? TRANSACTION_IGNORE_ORDER : 0) | + (mode == JOB_RESTART_DEPENDENCIES ? TRANSACTION_PROPAGATE_START_AS_RESTART : 0) | + extra_flags, + reterr_error); + if (r < 0) + return r; + + if (mode == JOB_TRIGGERING) { + r = transaction_add_triggering_jobs(tr, u); + if (r < 0) + return r; + } + } + + if (mode == JOB_ISOLATE) { + r = transaction_add_isolate_jobs(tr, m); + if (r < 0) + return r; + } + + r = transaction_activate(tr, m, mode, affected_jobs, reterr_error); + if (r < 0) + return r; + + SET_FOREACH(j, tr->anchor_jobs) + log_unit_debug(j->unit, + "Enqueued job %s/%s as %u", + j->unit->id, job_type_to_string(j->type), (unsigned) j->id); + + if (ret_jobs) { + /* The anchor_jobs set would be destroyed anyway, so steal the contents. */ + r = set_move(ret_jobs, tr->anchor_jobs); + if (r < 0) { + /* On failure, still clear anchor_jobs so the cleanup handler doesn't trip the + * empty-set assertion in transaction_free(). */ + set_clear(tr->anchor_jobs); + return r; + } + } else + /* tr->anchor_jobs tracks pointers to jobs that are now installed in the manager; clear + * it so transaction_free() doesn't trip its empty-set assertion. */ + set_clear(tr->anchor_jobs); + + tr = transaction_free(tr); + return 0; +} + int manager_add_job_full( Manager *m, JobType type, @@ -2196,12 +2481,19 @@ int manager_add_job_full( if (r < 0) return r; + Job *anchor = ASSERT_PTR(set_first(tr->anchor_jobs)); + assert(set_size(tr->anchor_jobs) == 1); + log_unit_debug(unit, "Enqueued job %s/%s as %u", unit->id, - job_type_to_string(type), (unsigned) tr->anchor_job->id); + job_type_to_string(type), (unsigned) anchor->id); if (ret) - *ret = tr->anchor_job; + *ret = anchor; + + /* anchor_jobs tracks pointers to jobs that are now installed in the manager; clear it so + * transaction_free() doesn't trip its empty-set assertion. */ + set_clear(tr->anchor_jobs); tr = transaction_free(tr); return 0; @@ -2275,7 +2567,7 @@ int manager_propagate_reload(Manager *m, Unit *unit, JobMode mode, sd_bus_error transaction_add_propagate_reload_jobs( tr, unit, - tr->anchor_job, + set_first(tr->anchor_jobs), mode == JOB_IGNORE_DEPENDENCIES ? TRANSACTION_IGNORE_ORDER : 0); /* Only activate the transaction if it contains jobs other than NOP anchor. @@ -2287,6 +2579,9 @@ int manager_propagate_reload(Manager *m, Unit *unit, JobMode mode, sd_bus_error if (r < 0) return r; + /* tr->anchor_jobs tracks pointers to jobs that are now installed in the manager, clear it */ + set_clear(tr->anchor_jobs); + tr = transaction_free(tr); return 0; } @@ -2501,6 +2796,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", @@ -2613,6 +2910,7 @@ static unsigned manager_dispatch_dbus_queue(Manager *m) { assert(u->in_dbus_queue); bus_unit_send_change_signal(u); + varlink_unit_send_change_signal(u); n++; if (budget != UINT_MAX) @@ -2623,6 +2921,7 @@ static unsigned manager_dispatch_dbus_queue(Manager *m) { assert(j->in_dbus_queue); bus_job_send_change_signal(j); + varlink_job_send_change_signal(j); n++; if (budget != UINT_MAX) @@ -3269,7 +3568,6 @@ static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t use } int manager_loop(Manager *m) { - RateLimit rl = { .interval = 1*USEC_PER_SEC, .burst = 50000 }; int r; assert(m); @@ -3284,7 +3582,7 @@ int manager_loop(Manager *m) { while (m->objective == MANAGER_OK) { - if (!ratelimit_below(&rl)) { + if (!ratelimit_below(&m->event_loop_ratelimit)) { /* Yay, something is going seriously wrong, pause a little */ log_warning("Looping too fast. Throttling execution a little."); sleep(1); @@ -3553,12 +3851,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 +3876,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) @@ -3620,6 +3922,10 @@ int manager_reload(Manager *m) { /* 💀 This is the point of no return, from here on there is no way back. 💀 */ reloading = NULL; + /* Bump before sending the Reloading signal, so any client that reads + * ReloadCount in response to that signal observes the new value. */ + m->reload_count = saturate_add(m->reload_count, 1, UINT64_MAX); + bus_manager_send_reloading(m, true); /* Start by flushing out all jobs and units, all generated units, all runtime environments, all dynamic users @@ -4298,8 +4604,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 +4804,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; @@ -4969,6 +5275,7 @@ static int manager_dispatch_pidref_transport_fd(sd_event_source *source, int fd, if (n != sizeof(child_pid)) { log_warning("Got pidref message of unexpected size %zi (expected %zu), ignoring.", n, sizeof(child_pid)); + cmsg_close_all(&msghdr); return 0; } @@ -4988,6 +5295,8 @@ static int manager_dispatch_pidref_transport_fd(sd_event_source *source, int fd, } } + /* From this point on, the fds are owned by our local variables. Call cmsg_close_all no more. */ + /* Verify and set parent pidref. */ if (!ucred || !pid_is_valid(ucred->pid)) { log_warning("Received pidref message without valid credentials. Ignoring."); @@ -5191,11 +5500,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 +5534,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..a2f0644ef0f4a 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -3,6 +3,7 @@ #include "sd-event.h" +#include "bpf-restrict-fsaccess.h" #include "cgroup.h" #include "common-signal.h" #include "execute.h" @@ -10,13 +11,18 @@ #include "log.h" #include "path-lookup.h" #include "show-status.h" +#include "transaction.h" #include "unit.h" struct libmnt_monitor; -/* Enforce upper limit how many names we allow */ +/* Enforce upper limit on how many names we allow */ #define MANAGER_MAX_NAMES 131072 /* 128K */ +/* Enforce upper limit on the number of patterns/states requested over IPC */ +#define MANAGER_MAX_PATTERNS_PER_CALL 4096U +#define MANAGER_MAX_STATES_PER_CALL 256U + /* On sigrtmin+18, private commands */ enum { MANAGER_SIGNAL_COMMAND_DUMP_JOBS = _COMMON_SIGNAL_COMMAND_PRIVATE_BASE + 0, @@ -147,8 +153,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; @@ -474,23 +481,46 @@ typedef struct Manager { /* Reference to RestrictFileSystems= BPF program */ struct restrict_fs_bpf *restrict_fs; + /* Reference to RestrictFileSystemAccess= BPF LSM program */ + RestrictFileSystemAccess restrict_filesystem_access; + + /* Raw BPF FDs extracted from the skeleton after attach. The kernel + * reference chain (link FD -> bpf_link -> bpf_prog -> bpf_map) keeps + * programs attached and map data alive. The .bss map FD is used for + * targeted writes (clearing initramfs_s_dev after switch_root). */ + int restrict_fsaccess_link_fds[_RESTRICT_FILESYSTEM_ACCESS_LINK_MAX]; + int restrict_fsaccess_bss_map_fd; + /* Allow users to configure a rate limit for Reload()/Reexecute() operations */ RateLimit reload_reexec_ratelimit; /* Dump*() are slow, so always rate limit them to 10 per 10 minutes */ RateLimit dump_ratelimit; - sd_event_source *memory_pressure_event_source; + /* Rate limit for the manager event loop */ + RateLimit event_loop_ratelimit; + + sd_event_source *pressure_event_source[_PRESSURE_RESOURCE_MAX]; /* For NFTSet= */ sd_netlink *nfnl; /* Pin the systemd-executor binary, so that it never changes until re-exec, ensuring we don't have * serialization/deserialization compatibility issues during upgrades. */ - char *executor_path; int executor_fd; unsigned soft_reboots_count; + /* When LUO is enabled we can count consecutive kexec reboots. */ + unsigned kexecs_count; + + /* The number of successfully completed configuration reloads. */ + uint64_t reload_count; + + /* Monotonic counter for fdstore entries propagated to a NOTIFY_SOCKET supervisor. Each propagated + * fd is sent upstream using this index as the FDNAME. The mapping (index -> unit_id + original fdname) + * is pushed alongside as a JSON memfd named "systemd-fdstore-mapping". */ + uint64_t fd_store_upstream_next_index; + /* Original ambient capabilities when we were initialized */ uint64_t saved_ambient_set; } Manager; @@ -520,7 +550,20 @@ int manager_new(RuntimeScope scope, ManagerTestRunFlags test_run_flags, Manager Manager* manager_free(Manager *m); DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); -int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *root); +/* One entry parsed out of the upstream "systemd-fdstore-mapping" memfd. Pairs the numeric index from the + * JSON map to the (unit-id, original fdname) the fd was originally stored as. */ +typedef struct ListenFDsTag { + char *unit_id; + char *fdname; + uint64_t index; +} ListenFDsTag; + +ListenFDsTag* listen_fds_tag_free(ListenFDsTag *t); +DEFINE_TRIVIAL_CLEANUP_FUNC(ListenFDsTag*, listen_fds_tag_free); + +extern const struct hash_ops fd_to_listen_fds_tag_hash_ops; + +int manager_startup(Manager *m, FILE *serialization, FDSet *fds, Hashmap *named_listen_fds, const char *root); Job *manager_get_job(Manager *m, uint32_t id); Unit *manager_get_unit(Manager *m, const char *name); @@ -530,9 +573,21 @@ int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j); bool manager_unit_cache_should_retry_load(Unit *u); int manager_load_unit_prepare(Manager *m, const char *name, const char *path, sd_bus_error *e, Unit **ret); int manager_load_unit(Manager *m, const char *name, const char *path, sd_bus_error *e, Unit **ret); +int manager_dispatch_external_fd_to_unit(Manager *m, const char *unit_id, const char *fdname, uint64_t index, int fd, const char *log_context); int manager_load_startable_unit_or_warn(Manager *m, const char *name, const char *path, Unit **ret); int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u); +int manager_add_jobs( + Manager *m, + JobType type, + char * const *names, + bool reload_if_possible, + JobMode mode, + TransactionAddFlags extra_flags, + Set *affected_jobs, + sd_bus_error *reterr_error, + Set *ret_jobs); + int manager_add_job_full( Manager *m, JobType type, @@ -560,7 +615,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..6e68a59d204db 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -1,15 +1,38 @@ # SPDX-License-Identifier: LGPL-2.1-or-later +systemd_sources = files( + 'apparmor-setup.c', + 'clock-warp.c', + 'crash-handler.c', + 'efi-random.c', + 'ima-setup.c', + 'import-creds.c', + 'ipe-setup.c', + 'kmod-setup.c', + 'main.c', + 'smack-setup.c', +) + +if conf.get('HAVE_SELINUX') == 1 + systemd_sources += files('selinux-setup.c') +endif + +systemd_executor_sources = files( + 'executor.c', + 'exec-invoke.c', +) + libcore_sources = files( 'audit-fd.c', 'automount.c', + 'bpf-bind-iface.c', 'bpf-devices.c', 'bpf-firewall.c', 'bpf-foreign.c', 'bpf-restrict-fs.c', + 'bpf-restrict-fsaccess.c', 'bpf-restrict-ifaces.c', 'bpf-socket-bind.c', - 'bpf-bind-iface.c', 'cgroup.c', 'dbus-automount.c', 'dbus-cgroup.c', @@ -37,11 +60,11 @@ libcore_sources = files( 'execute.c', 'execute-serialize.c', 'generator-setup.c', - 'import-creds.c', 'job.c', 'kill.c', 'load-dropin.c', 'load-fragment.c', + 'luo.c', 'manager-dump.c', 'manager-serialize.c', 'manager.c', @@ -64,26 +87,33 @@ 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-job.c', + 'varlink-kill.c', 'varlink-manager.c', 'varlink-metrics.c', + 'varlink-mount.c', + 'varlink-path.c', + 'varlink-scope.c', + 'varlink-service.c', + 'varlink-socket.c', + 'varlink-swap.c', + 'varlink-timer.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 + +if conf.get('HAVE_LSM_INTEGRITY_TYPE') == 1 + libcore_sources += bpf_programs_by_name['restrict-fsaccess'] endif sources += libcore_sources @@ -129,7 +159,7 @@ generated_sources += [load_fragment_gperf_c, load_fragment_gperf_nulstr_c, bpf_d libcore_sources += [load_fragment_gperf_c, load_fragment_gperf_nulstr_c, bpf_delegate_configs_inc] libcore_build_dir = meson.current_build_dir() libcore_name = 'systemd-core-@0@'.format(shared_lib_tag) -core_includes = [includes, include_directories('.')] +core_includes = [include_directories('.'), includes] libcore_static = static_library( libcore_name, @@ -140,13 +170,10 @@ libcore_static = static_library( dependencies : [libaudit_cflags, libbpf_cflags, libcryptsetup_cflags, - libdl, libm, libmount_cflags, - librt, libseccomp_cflags, libselinux_cflags, - threads, userspace], build_by_default : false) @@ -162,35 +189,38 @@ libcore = shared_library( install : true, install_dir : pkglibdir) -systemd_sources = files( - 'main.c', - 'crash-handler.c', - 'clock-warp.c', - 'kmod-setup.c', - 'apparmor-setup.c', - 'ima-setup.c', - 'ipe-setup.c', - 'selinux-setup.c', - 'smack-setup.c', - 'efi-random.c', -) +core_libs_static = [ + libc_wrapper_static, + libcore_static, + libshared_static, + libbasic_static, + libsystemd_static, +] +core_libs_shared = [ + libcore, + libshared, +] -systemd_executor_sources = files( - 'executor.c', - 'exec-invoke.c', -) +systemd_deps = [ + libapparmor_cflags, + libkmod_cflags, + libmount_cflags, + libseccomp_cflags, + libselinux_cflags, +] -executor_libs = get_option('link-executor-shared') ? \ - [ - libcore, - libshared, - ] : [ - libc_wrapper_static, - libcore_static, - libshared_static, - libbasic_static, - libsystemd_static, +link_executor_shared = get_option('link-executor-shared') + +executor_libs = link_executor_shared == 'true' ? core_libs_shared : core_libs_static + +if link_executor_shared == 'single' + systemd_sources += systemd_executor_sources + systemd_deps += [ + libbpf_cflags, + libcryptsetup_cflags, + libpam_cflags, ] +endif executables += [ libexec_template + { @@ -198,33 +228,10 @@ executables += [ 'dbus' : true, 'public' : true, 'sources' : systemd_sources, - 'link_with' : [ - libcore, - libshared, - ], - 'dependencies' : [ - libapparmor_cflags, - libkmod_cflags, - libmount_cflags, - libseccomp_cflags, - libselinux_cflags, - ], - }, - libexec_template + { - 'name' : 'systemd-executor', - 'public' : true, - 'sources' : systemd_executor_sources, - 'link_with' : executor_libs, - 'dependencies' : [ - libapparmor_cflags, - libbpf_cflags, - libcryptsetup_cflags, - libmount_cflags, - libpam_cflags, - libseccomp_cflags, - libselinux_cflags, - ], + 'link_with' : core_libs_shared, + 'dependencies' : systemd_deps, }, + fuzz_template + { 'sources' : files('fuzz-unit-file.c'), 'link_with' : [ @@ -249,6 +256,31 @@ executables += [ }, ] +if link_executor_shared == 'single' + # Symlink for external callers + install_symlink('systemd-executor', + pointing_to : 'systemd', + install_dir : libexecdir) +else + executables += [ + libexec_template + { + 'name' : 'systemd-executor', + 'public' : true, + 'sources' : systemd_executor_sources, + 'link_with' : executor_libs, + 'dependencies' : [ + libapparmor_cflags, + libbpf_cflags, + libcryptsetup_cflags, + libmount_cflags, + libpam_cflags, + libseccomp_cflags, + libselinux_cflags, + ], + }, + ] +endif + in_files = [['system.conf', pkgconfigfiledir], ['user.conf', pkgconfigfiledir], ['org.freedesktop.systemd1.policy', polkitpolicydir]] diff --git a/src/core/mount.c b/src/core/mount.c index 680e376febfc9..cf2d839347b2e 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -23,7 +23,7 @@ #include "libmount-util.h" #include "log.h" #include "manager.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mount.h" #include "mount-setup.h" @@ -1171,7 +1171,7 @@ static void mount_enter_mounting(Mount *m) { * couldn't support that reasonably: the mounts in /proc/self/mountinfo would not be recognizable to * us anymore. */ fd = chase_and_open_parent(m->where, /* root= */ NULL, CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_TRIGGER_AUTOFS, &fn); - if (fd == -EREMCHG) { + if (fd == -ELOOP) { r = unit_log_noncanonical_mount_path(UNIT(m), m->where); goto fail; } @@ -1554,13 +1554,9 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { /* MOUNT_MOUNTING and MOUNT_UNMOUNTING states need to be patched, see below. */ m->result = f; - if (m->control_command) { + if (m->control_command) exec_status_exit(&m->control_command->exec_status, &m->exec_context, pid, code, status); - m->control_command = NULL; - m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; - } - unit_log_process_exit( u, "Mount process", @@ -1568,6 +1564,9 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { f == MOUNT_SUCCESS, code, status); + m->control_command = NULL; + m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; + /* Note that due to the io event priority logic, we can be sure the new mountinfo is loaded * before we process the SIGCHLD for the mount command. */ @@ -2003,6 +2002,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 +2407,7 @@ static int mount_test_startable(Unit *u) { } static bool mount_supported(void) { - return dlopen_libmount() >= 0; + return DLOPEN_LIBMOUNT(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) >= 0; } static int mount_subsystem_ratelimited(Manager *m) { @@ -2507,6 +2508,8 @@ const UnitVTable mount_vtable = { .can_transient = true, .can_fail = true, .exclude_from_switch_root_serialization = true, + .notify_plymouth = true, + .track_orphaned = true, .init = mount_init, .load = mount_load, @@ -2574,6 +2577,4 @@ const UnitVTable mount_vtable = { }, .test_startable = mount_test_startable, - - .notify_plymouth = true, }; diff --git a/src/core/namespace.c b/src/core/namespace.c index 9727d725eb7df..3fc0a8652db7a 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -31,7 +31,7 @@ #include "log.h" #include "loop-util.h" #include "loopback-setup.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "mstack.h" @@ -564,8 +564,9 @@ static int append_extensions( _cleanup_free_ char *mount_point = NULL; const MountImage *m = mount_images + i; - r = path_pick(/* toplevel_path= */ NULL, - /* toplevel_fd= */ AT_FDCWD, + r = path_pick(/* root_path= */ NULL, + /* root_fd= */ AT_FDCWD, + /* dir_fd= */ AT_FDCWD, m->source, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), @@ -636,8 +637,9 @@ static int append_extensions( if (startswith(e, "+")) e++; - r = path_pick(/* toplevel_path= */ NULL, - /* toplevel_fd= */ AT_FDCWD, + r = path_pick(/* root_path= */ NULL, + /* root_fd= */ AT_FDCWD, + /* dir_fd= */ AT_FDCWD, e, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), @@ -1049,7 +1051,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 +3252,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 +3664,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 +3876,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 +3986,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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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..93e702a481610 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -15,7 +15,7 @@ #include "glob-util.h" #include "inotify-util.h" #include "manager.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "path.h" #include "path-util.h" #include "serialize.h" @@ -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..5e116a6f314ec 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -344,25 +344,21 @@ static int scope_enter_start_chown(Scope *s) { gid_t gid = GID_INVALID; if (!isempty(s->user)) { - const char *user = s->user; - - r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); + r = get_user_creds(s->user, /* flags= */ 0, NULL, &uid, &gid, NULL, NULL); if (r < 0) { log_unit_error_errno(UNIT(s), r, "Failed to resolve user '%s': %s", - user, STRERROR_USER(r)); + s->user, STRERROR_USER(r)); _exit(EXIT_USER); } } if (!isempty(s->group)) { - const char *group = s->group; - - r = get_group_creds(&group, &gid, 0); + r = get_group_creds(s->group, /* flags= */ 0, /* ret_name= */ NULL, &gid); if (r < 0) { log_unit_error_errno(UNIT(s), r, "Failed to resolve group '%s': %s", - group, STRERROR_GROUP(r)); + s->group, STRERROR_GROUP(r)); _exit(EXIT_GROUP); } } @@ -483,6 +479,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; @@ -742,6 +740,7 @@ const UnitVTable scope_vtable = { .can_fail = true, .once_only = true, .can_set_managed_oom = true, + .track_orphaned = true, .init = scope_init, .load = scope_load, diff --git a/src/core/selinux-setup.c b/src/core/selinux-setup.c index 6f78346036d7c..886b00cbf564b 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + 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..9e4af299f7ec9 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -1,11 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include /* IWYU pragma: keep */ +#include #include #include #include #include "sd-bus.h" +#include "sd-id128.h" +#include "sd-json.h" #include "sd-messages.h" #include "alloc-util.h" @@ -13,7 +16,9 @@ #include "bus-common-errors.h" #include "bus-error.h" #include "bus-util.h" +#include "cgroup.h" #include "chase.h" +#include "daemon-util.h" #include "dbus-service.h" #include "dbus-unit.h" #include "devnum-util.h" @@ -29,9 +34,12 @@ #include "fileio.h" #include "format-util.h" #include "glyph-util.h" +#include "id128-util.h" #include "image-policy.h" #include "log.h" +#include "luo-util.h" #include "manager.h" +#include "memfd-util.h" #include "mount-util.h" #include "namespace.h" #include "open-file.h" @@ -60,6 +68,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 +148,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, @@ -388,6 +400,13 @@ usec_t service_restart_usec_next(const Service *s) { (long double) (n_restarts_next - 1) / s->restart_steps)); } +static usec_t service_restart_usec_next_jittered(const Service *s) { + assert(s); + + /* Single helper for the restart timer and the deadline reconstructed at coldplug so they can't drift */ + return usec_add(service_restart_usec_next(s), s->restart_randomized_delay_chosen_usec); +} + static void service_extend_event_source_timeout(Service *s, sd_event_source *source, usec_t extended) { usec_t current; int r; @@ -457,12 +476,27 @@ static void service_override_watchdog_timeout(Service *s, usec_t watchdog_overri log_unit_debug(UNIT(s), "watchdog_override_usec="USEC_FMT, s->watchdog_override_usec); } -static ServiceFDStore* service_fd_store_unlink(ServiceFDStore *fs) { +static ServiceFDStore* service_fd_store_unlink_full(ServiceFDStore *fs, bool propagate_upstream) { if (!fs) return NULL; if (fs->service) { assert(fs->service->n_fd_store > 0); + + /* If we previously propagated this fd to an enveloping service/container manager via + * the FDSTORE=1 protocol on its NOTIFY_SOCKET (only done when persistence is on), + * tell that supervisor to drop it now too, so the upstream fd store stays in sync. + * Only do this for explicit removals (EPOLLHUP/EPOLLERR or app FDSTOREREMOVE), not + * for local cleanup like service shutdown or fdstore-limit truncation: in those + * cases we want the upstream copy to survive so it can be handed back to us later. */ + if (propagate_upstream && fs->index > 0) { + (void) notify_remove_fd_warnf(SERVICE_FDSTORE_SUB_FDNAME_PREFIX "%" PRIu64, fs->index); + fs->index = 0; + /* Refresh the upstream JSON mapping so the supervisor's view stays in sync + * with what fds are actually still around. */ + (void) service_propagate_fd_store_mapping_upstream(UNIT(fs->service)->manager); + } + LIST_REMOVE(fd_store, fs->service->fd_store, fs); fs->service->n_fd_store--; } @@ -474,22 +508,41 @@ static ServiceFDStore* service_fd_store_unlink(ServiceFDStore *fs) { return mfree(fs); } +static ServiceFDStore* service_fd_store_unlink(ServiceFDStore *fs) { + return service_fd_store_unlink_full(fs, /* propagate_upstream= */ false); +} + 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); } +static void service_truncate_fd_store(Service *s) { + assert(s); + + /* Drop fds that exceed the (possibly newly lowered) n_fd_store_max, e.g. after the fragment was + * parsed and FileDescriptorStoreMax= shrunk the configured limit. Newest entries are at the head + * of the list, so drop from the head (newest first). */ + + while (s->n_fd_store > s->n_fd_store_max + strv_length(s->luo_sessions)) { + ServiceFDStore *fs = ASSERT_PTR(s->fd_store); + log_unit_debug(UNIT(s), "Dropping stored fd '%s' to honor FileDescriptorStoreMax=%u.", + strna(fs->fdname), s->n_fd_store_max); + service_fd_store_unlink(fs); + } +} + static void service_release_extra_fds(Service *s) { assert(s); @@ -507,6 +560,15 @@ static void service_release_extra_fds(Service *s) { s->n_extra_fds = 0; } +ServiceExtraFD* service_extra_fd_free(ServiceExtraFD *fd) { + if (!fd) + return NULL; + + safe_close(fd->fd); + free(fd->fdname); + return mfree(fd); +} + static void service_release_stdio_fd(Service *s) { assert(s); @@ -566,24 +628,31 @@ static void service_done(Unit *u) { service_release_extra_fds(s); s->root_directory_fd = asynchronous_close(s->root_directory_fd); + s->luo_sessions = strv_free(s->luo_sessions); + s->mount_request = sd_bus_message_unref(s->mount_request); } 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); + service_fd_store_unlink_full(fs, /* propagate_upstream= */ true); + + if (s->state == SERVICE_DEAD_RESOURCES_PINNED && !SERVICE_FD_STORE_POPULATED(s)) + service_set_state(s, SERVICE_DEAD); + return 0; } -static int service_add_fd_store(Service *s, int fd_in, const char *name, bool do_poll) { +int service_add_fd_store(Service *s, int fd_in, const char *name, bool do_poll, bool propagate_upstream) { _cleanup_(service_fd_store_unlinkp) ServiceFDStore *fs = NULL; _cleanup_(asynchronous_closep) int fd = ASSERT_FD(fd_in); struct stat st; @@ -637,14 +706,43 @@ static int service_add_fd_store(Service *s, int fd_in, const char *name, bool do log_unit_debug(UNIT(s), "Added fd %i (%s) to fd store.", fs->fd, fs->fdname); + /* If fd-store persistence is enabled and we have an enveloping service/container manager (i.e. + * NOTIFY_SOCKET is set), forward the fd to it via sd_notify(FDSTORE=1) tagged with a fresh + * incrementing index, and (re-)push the JSON mapping memfd that pairs the index back to this + * unit and the original fdname. This way fdstore persistence chains all the way up to whichever + * entity is ultimately responsible for surviving across kexec/restart, regardless of fdname + * length or charset constraints. */ + if (propagate_upstream && IN_SET(s->fd_store_preserve_mode, EXEC_PRESERVE_YES, EXEC_PRESERVE_ON_SUCCESS)) { + Manager *m = ASSERT_PTR(UNIT(s)->manager); + char idx_str[STRLEN(SERVICE_FDSTORE_SUB_FDNAME_PREFIX) + DECIMAL_STR_MAX(uint64_t)]; + + assert(m->fd_store_upstream_next_index < UINT64_MAX); + uint64_t idx = ++m->fd_store_upstream_next_index; + + xsprintf(idx_str, SERVICE_FDSTORE_SUB_FDNAME_PREFIX "%" PRIu64, idx); + + r = notify_push_fd(fs->fd, idx_str); + if (r < 0) + log_unit_debug_errno(UNIT(s), r, + "Failed to propagate fd '%s' to upstream supervisor as index %" PRIu64 ", ignoring: %m", + fs->fdname, idx); + else + fs->index = idx; + } + fs->service = s; LIST_PREPEND(fd_store, s->fd_store, TAKE_PTR(fs)); s->n_fd_store++; + if (propagate_upstream && IN_SET(s->fd_store_preserve_mode, EXEC_PRESERVE_YES, EXEC_PRESERVE_ON_SUCCESS)) + /* Refresh the JSON mapping memfd so the supervisor can resolve the new index. Do this + * after LIST_PREPEND so the new entry is visible to the helper. */ + (void) service_propagate_fd_store_mapping_upstream(UNIT(s)->manager); + return 1; /* fd newly stored */ } -static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bool do_poll) { +static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bool do_poll, bool propagate_upstream) { int r; assert(s); @@ -656,7 +754,7 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bo if (fd < 0) break; - r = service_add_fd_store(s, fd, name, do_poll); + r = service_add_fd_store(s, fd, name, do_poll, propagate_upstream); if (r == -EXFULL) return log_unit_warning_errno(UNIT(s), r, "Cannot store more fds than FileDescriptorStoreMax=%u, closing remaining.", @@ -668,6 +766,235 @@ static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name, bo return 0; } +/* Build a deterministic LUO session name from the unit's name and the session name, with stable length. */ +static int service_build_luo_session_name(Service *s, const char *name, char **ret) { + _cleanup_free_ char *full = NULL, *result = NULL; + + assert(s); + assert(name); + assert(ret); + + full = strjoin(UNIT(s)->id, "/", name); + if (!full) + return -ENOMEM; + + /* The kernel embeds the session name in the anon_inode path shown in /proc/self/fd/, i.e. + * "anon_inode:[luo_session] ". On kernels lacking commit 97b67e64affb ("dcache: permit + * dynamic_dname()s up to NAME_MAX") that path must fit dynamic_dname()'s historical 64 byte limit, + * otherwise reading it back will fail. Can be simplified once Ubuntu 26.04 support is dropped. */ + // FIXME: allow longer prefix once Ubuntu 26.04 support is dropped + size_t name_max = 64U - STRLEN("anon_inode:[luo_session] ") - 1U; /* = 38 */ + size_t digest_chars = 24U; /* 96 bit, leaves room for a useful unit id prefix within name_max */ + + assert_cc(64U - STRLEN("anon_inode:[luo_session] ") - 1U < LIVEUPDATE_SESSION_NAME_LENGTH); + assert_cc(24U < SD_ID128_STRING_MAX); + assert_cc(24U + 1U < 64U - STRLEN("anon_inode:[luo_session] ") - 1U); + + char digest[SD_ID128_STRING_MAX]; + sd_id128_to_string(id128_digest(full, SIZE_MAX), digest); + + if (asprintf(&result, "%.*s-%.*s", + (int) (name_max - digest_chars - 1U), UNIT(s)->id, + (int) digest_chars, digest) < 0) + return -ENOMEM; + + *ret = TAKE_PTR(result); + return 0; +} + +static int service_setup_luo_sessions(Service *s) { + int r; + + assert(s); + + /* For each configured LUOSession=, create a fresh LUO session and hand it to the service via the fd + * store (and thus LISTEN_FDS, with the configured name as FDNAME). The kernel-level session name is + * derived deterministically from the unit and the configured name so it stays stable and fits the + * kernel's length limit. */ + + if (strv_isempty(s->luo_sessions)) + return 0; + + if (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) { + log_unit_debug(UNIT(s), "LUOSession= is only supported in the system manager, ignoring."); + return 0; + } + + _cleanup_close_ int device_fd = luo_open_device(); + if (device_fd < 0) { + if (ERRNO_IS_NEG_DEVICE_ABSENT(device_fd)) { + log_unit_debug_errno(UNIT(s), device_fd, "No /dev/liveupdate device found, not handing out LUO sessions."); + return 0; + } + return log_unit_warning_errno(UNIT(s), device_fd, "Failed to open /dev/liveupdate: %m"); + } + + STRV_FOREACH(name, s->luo_sessions) { + _cleanup_free_ char *session_name = NULL; + _cleanup_close_ int session_fd = -EBADF; + bool already_given_out = false; + + LIST_FOREACH(fd_store, fs, s->fd_store) + if (streq_ptr(fs->fdname, *name) && fd_is_luo_session(fs->fd) > 0) { + already_given_out = true; + break; + } + if (already_given_out) + continue; + + r = service_build_luo_session_name(s, *name, &session_name); + if (r < 0) + return log_unit_warning_errno(UNIT(s), r, "Failed to build LUO session name for '%s': %m", *name); + + session_fd = luo_create_session(device_fd, session_name); + if (session_fd < 0) { + log_unit_warning_errno(UNIT(s), session_fd, "Failed to create LUO session '%s', ignoring: %m", session_name); + continue; + } + + r = service_add_fd_store(s, TAKE_FD(session_fd), *name, /* do_poll= */ false, /* propagate_upstream= */ false); + if (r < 0) + return log_unit_warning_errno(UNIT(s), r, "Failed to hand out LUO session '%s': %m", *name); + + log_unit_debug(UNIT(s), "Handed out LUO session '%s' (kernel name '%s').", *name, session_name); + } + + return 0; +} + +int service_propagate_fd_store_mapping_upstream(Manager *m) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *root = NULL; + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *text = NULL; + Unit *u; + int r; + + assert(m); + + /* Build a JSON object listing all fdstore entries that have been propagated upstream: + * + * { + * "unit-name.service": [ + * { "name": "fdname1", "index": 1 }, + * { "name": "fdname2", "index": 2 } + * ], + * ... + * } + * + * Push it as a sealed memfd to the upstream supervisor under a fixed FDNAME so it can resolve + * the per-fd numeric indices back to (unit_id, original fdname) at startup. The mapping is + * regenerated and re-pushed after every add/remove, so the supervisor's view stays in sync. */ + HASHMAP_FOREACH(u, m->units) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *entries = NULL; + Service *s; + + if (u->type != UNIT_SERVICE) + continue; + + s = SERVICE(u); + if (!s->fd_store) + continue; + + LIST_FOREACH(fd_store, fs, s->fd_store) { + if (fs->index == 0) + continue; + + r = sd_json_variant_append_arraybo( + &entries, + SD_JSON_BUILD_PAIR_STRING("name", fs->fdname), + SD_JSON_BUILD_PAIR_UNSIGNED("index", fs->index)); + if (r < 0) + return log_warning_errno(r, "Failed to build fdstore-mapping JSON entry: %m"); + } + + if (!entries) + continue; + + r = sd_json_variant_set_field(&root, u->id, entries); + if (r < 0) + return log_warning_errno(r, "Failed to add unit to fdstore-mapping JSON: %m"); + } + + if (!root) { + /* Nothing to map: tell the supervisor to drop any previously-pushed mapping memfd + * so it doesn't keep stale entries around. Only do this if we have actually pushed + * one in the past (i.e. we ever assigned an upstream index, either in this + * incarnation or in a previous one whose counter we deserialized), otherwise we + * might inadvertently remove a mapping that was just handed back to us via + * LISTEN_FDS during a fresh manager startup. */ + if (m->fd_store_upstream_next_index > 0) + (void) notify_remove_fd_warn(SERVICE_FDSTORE_MAPPING_FDNAME); + return 0; + } + + r = sd_json_variant_format(root, /* flags= */ 0, &text); + if (r < 0) + return log_warning_errno(r, "Failed to format fdstore-mapping JSON: %m"); + + fd = memfd_new_and_seal_string(SERVICE_FDSTORE_MAPPING_FDNAME, text); + if (fd < 0) + return log_warning_errno(fd, "Failed to create fdstore-mapping memfd: %m"); + + r = notify_push_fd(fd, SERVICE_FDSTORE_MAPPING_FDNAME); + if (r < 0) + return log_warning_errno(r, "Failed to propagate fdstore-mapping to upstream supervisor: %m"); + + return 0; +} + +static int service_attach_external_fd_to_fdstore(Unit *u, int fd, const char *fdname, uint64_t index) { + Service *s = ASSERT_PTR(SERVICE(u)); + int r; + + assert(u->type == UNIT_SERVICE); + + /* If the unit file is absent, bump the limit by one and force preserve so the fd is + * accepted and pins the unit until a daemon-reload picks up the unit file or it is + * explicitly stopped. */ + if (u->load_state == UNIT_NOT_FOUND) { + s->fd_store_preserve_mode = EXEC_PRESERVE_YES; + s->n_fd_store_max++; + } + + /* Don't propagate upstream: the fd just came back from upstream, forwarding it would loop. */ + r = service_add_fd_store(s, fd, fdname, /* do_poll= */ true, /* propagate_upstream= */ false); + if (r <= 0 && u->load_state == UNIT_NOT_FOUND) + s->n_fd_store_max--; + if (r < 0) + return log_unit_debug_errno(u, r, "Failed to add LUO fd '%s' to fd store: %m", fdname); + + /* If the fd was previously propagated to an upstream supervisor under a numeric index, + * preserve that index on the freshly-added entry so that future FDSTOREREMOVE messages + * (and the fdstore-mapping memfd we re-push to the supervisor) reference the same index + * the supervisor already knows about. service_add_fd_store() does LIST_PREPEND() on + * success, so the new entry is at the head. Also keep the manager's allocator counter + * past the highest restored index, to avoid collisions with newly allocated indices. */ + if (r > 0 && index > 0 && s->fd_store) { + Manager *m = ASSERT_PTR(u->manager); + + s->fd_store->index = index; + if (index > m->fd_store_upstream_next_index) + m->fd_store_upstream_next_index = index; + } + + /* If the unit is otherwise inactive (typical for LUO/upstream restore), pin its resources so it + * isn't garbage-collected before something explicitly stops it. Only flip the state when both + * runtime and deserialized state agree on DEAD, to avoid clobbering a just-deserialized live + * state (e.g. SERVICE_RUNNING after daemon-reload, where service_coldplug() will set the proper + * state later). */ + if (r > 0 && + s->state == SERVICE_DEAD && + s->deserialized_state == SERVICE_DEAD && + IN_SET(s->fd_store_preserve_mode, EXEC_PRESERVE_YES, EXEC_PRESERVE_ON_SUCCESS)) { + service_set_state(s, SERVICE_DEAD_RESOURCES_PINNED); + s->deserialized_state = SERVICE_DEAD_RESOURCES_PINNED; + } + + if (r > 0) + log_unit_debug(u, "Restored fd '%s'.", fdname); + return r; +} + static void service_remove_fd_store(Service *s, const char *name) { assert(s); assert(name); @@ -677,7 +1004,7 @@ static void service_remove_fd_store(Service *s, const char *name) { continue; log_unit_debug(UNIT(s), "Got explicit request to remove fd %i (%s), closing.", fs->fd, name); - service_fd_store_unlink(fs); + service_fd_store_unlink_full(fs, /* propagate_upstream= */ true); } } @@ -760,6 +1087,11 @@ static int service_verify(Service *s) { s->restart_usec = s->restart_max_delay_usec; } + if (s->restart_randomized_delay_usec == USEC_INFINITY) { + log_unit_warning(UNIT(s), "RestartRandomizedDelaySec= cannot be infinity, ignoring."); + s->restart_randomized_delay_usec = 0; + } + if (s->refresh_on_reload_set && s->refresh_on_reload_flags != _SERVICE_REFRESH_ON_RELOAD_ALL) { if (FLAGS_SET(s->refresh_on_reload_flags, SERVICE_RELOAD_EXTENSIONS)) service_can_reload_extensions(s, /* warn = */ true); @@ -901,6 +1233,10 @@ static int service_add_extras(Service *s) { if (r < 0) return r; + /* Each configured LUOSession= is handed to the service through the fd store, hence make sure the + * store is large enough to hold them all. */ + s->n_fd_store_max += strv_length(s->luo_sessions); + /* If the service needs the notify socket, let's enable it automatically. */ if (s->notify_access == NOTIFY_NONE && (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) || s->watchdog_usec > 0 || s->n_fd_store_max > 0)) @@ -942,6 +1278,11 @@ static int service_load(Unit *u) { if (u->load_state != UNIT_LOADED) return 0; + /* The fragment may have lowered FileDescriptorStoreMax= below the number of fds currently in the + * store (e.g. fds restored from LUO into a synthesized UNIT_NOT_FOUND service that just got a real + * fragment via lazy reload, but which now disables the fd store). */ + service_truncate_fd_store(s); + /* This is a new unit? Then let's add in some extras */ r = service_add_extras(s); if (r < 0) @@ -1059,6 +1400,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { "%sRestartSec: %s\n" "%sRestartSteps: %u\n" "%sRestartMaxDelaySec: %s\n" + "%sRestartRandomizedDelaySec: %s\n" "%sTimeoutStartSec: %s\n" "%sTimeoutStopSec: %s\n" "%sTimeoutStartFailureMode: %s\n" @@ -1066,6 +1408,7 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { prefix, FORMAT_TIMESPAN(s->restart_usec, USEC_PER_SEC), prefix, s->restart_steps, prefix, FORMAT_TIMESPAN(s->restart_max_delay_usec, USEC_PER_SEC), + prefix, FORMAT_TIMESPAN(s->restart_randomized_delay_usec, USEC_PER_SEC), prefix, FORMAT_TIMESPAN(s->timeout_start_usec, USEC_PER_SEC), prefix, FORMAT_TIMESPAN(s->timeout_stop_usec, USEC_PER_SEC), prefix, service_timeout_failure_mode_to_string(s->timeout_start_failure_mode), @@ -1412,7 +1755,8 @@ static usec_t service_coldplug_timeout(Service *s) { return usec_add(UNIT(s)->state_change_timestamp.monotonic, service_timeout_abort_usec(s)); case SERVICE_AUTO_RESTART: - return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, service_restart_usec_next(s)); + return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, + service_restart_usec_next_jittered(s)); case SERVICE_CLEANING: return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->exec_context.timeout_clean_usec); @@ -1427,7 +1771,8 @@ static int service_coldplug(Unit *u) { int r; assert(s); - assert(s->state == SERVICE_DEAD); + /* Ensure we can insert FD store into units at boot */ + assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_DEAD_RESOURCES_PINNED)); if (s->deserialized_state == s->state) return 0; @@ -2126,7 +2471,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) && IN_SET(s->fd_store_preserve_mode, EXEC_PRESERVE_YES, EXEC_PRESERVE_ON_SUCCESS) ? SERVICE_DEAD_RESOURCES_PINNED : SERVICE_DEAD; } static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) { @@ -2184,7 +2529,11 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) if (s->restart_mode != SERVICE_RESTART_MODE_DIRECT) service_set_state(s, restart_state); - restart_usec_next = service_restart_usec_next(s); + /* Do the randomized restart delay once and remember it so that it's stable across daemon-reload */ + s->restart_randomized_delay_chosen_usec = s->restart_randomized_delay_usec > 0 ? + random_u64_range(s->restart_randomized_delay_usec) : 0; + + restart_usec_next = service_restart_usec_next_jittered(s); r = service_arm_timer(s, /* relative= */ true, restart_usec_next); if (r < 0) { @@ -2204,7 +2553,9 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) log_unit_notice(UNIT(s), "Service dead, subsequent restarts will be executed with debug level logging."); } - log_unit_debug(UNIT(s), "Next restart interval calculated as: %s", FORMAT_TIMESPAN(restart_usec_next, 0)); + log_unit_debug(UNIT(s), "Next restart interval calculated as: %s (randomized delay: %s)", + FORMAT_TIMESPAN(restart_usec_next, 0), + FORMAT_TIMESPAN(s->restart_randomized_delay_chosen_usec, 0)); service_set_state(s, SERVICE_AUTO_RESTART); } else { @@ -2230,7 +2581,8 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) unit_destroy_runtime_data(UNIT(s), &s->exec_context, /* destroy_runtime_dir= */ true); /* Also get rid of the fd store, if that's configured. */ - if (s->fd_store_preserve_mode == EXEC_PRESERVE_NO) + if (s->fd_store_preserve_mode == EXEC_PRESERVE_NO || + (s->fd_store_preserve_mode == EXEC_PRESERVE_ON_SUCCESS && s->state == SERVICE_FAILED)) service_release_fd_store(s); /* Get rid of the IPC bits of the user */ @@ -2524,6 +2876,10 @@ static void service_enter_start(Service *s) { service_unwatch_control_pid(s); service_unwatch_main_pid(s); + r = service_setup_luo_sessions(s); + if (r < 0) + goto fail; + r = service_adverse_to_leftover_processes(s); if (r < 0) goto fail; @@ -3165,8 +3521,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; @@ -3407,6 +3765,7 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { (void) serialize_bool(f, "bus-name-good", s->bus_name_good); (void) serialize_item_format(f, "n-restarts", "%u", s->n_restarts); + (void) serialize_usec(f, "restart-randomized-delay-chosen-usec", s->restart_randomized_delay_chosen_usec); (void) serialize_bool(f, "forbid-restart", s->forbid_restart); service_serialize_exec_command(u, f, s->control_command); @@ -3458,7 +3817,8 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { if (!c) return log_oom(); - (void) serialize_item_format(f, "fd-store-fd", "%i \"%s\" %s", copy, c, one_zero(fs->do_poll)); + (void) serialize_item_format(f, "fd-store-fd", "%i \"%s\" %s %" PRIu64, + copy, c, one_zero(fs->do_poll), fs->index); } FOREACH_ARRAY(i, s->extra_fds, s->n_extra_fds) { @@ -3728,12 +4088,13 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, s->socket_fd = deserialize_fd(fds, value); } else if (streq(key, "fd-store-fd")) { - _cleanup_free_ char *fdv = NULL, *fdn = NULL, *fdp = NULL; + _cleanup_free_ char *fdv = NULL, *fdn = NULL, *fdp = NULL, *fdi = NULL; _cleanup_close_ int fd = -EBADF; int do_poll; + uint64_t index = 0; - r = extract_many_words(&value, " ", EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE, &fdv, &fdn, &fdp); - if (r < 2 || r > 3) { + r = extract_many_words(&value, " ", EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE, &fdv, &fdn, &fdp, &fdi); + if (r < 2 || r > 4) { log_unit_debug(u, "Failed to deserialize fd-store-fd, ignoring: %s", value); return 0; } @@ -3742,19 +4103,45 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, if (fd < 0) return 0; - do_poll = r == 3 ? parse_boolean(fdp) : true; + do_poll = r >= 3 ? parse_boolean(fdp) : true; if (do_poll < 0) { log_unit_debug_errno(u, do_poll, "Failed to deserialize fd-store-fd do_poll, ignoring: %s", fdp); return 0; } - r = service_add_fd_store(s, TAKE_FD(fd), fdn, do_poll); + if (r == 4 && safe_atou64(fdi, &index) < 0) { + log_unit_debug(u, "Failed to parse fd-store-fd index '%s', ignoring.", fdi); + index = 0; + } + + /* If the unit file is currently absent (e.g. after switch-root, before the unit file is + * available in the new root), the synthesized service has n_fd_store_max=0 and + * preserve_mode=NO, which would reject the fd. Grow the limit by one per fd so it matches + * exactly what was handed back, and force EXEC_PRESERVE_YES, so the fd survives until + * either a daemon-reload picks up the unit file or the service is explicitly stopped. + * Same logic as in luo_dispatch_fd(). */ + if (u->load_state == UNIT_NOT_FOUND) { + s->fd_store_preserve_mode = EXEC_PRESERVE_YES; + s->n_fd_store_max++; + } + + /* Don't propagate upstream during deserialization: the upstream supervisor (if any) + * already has these fds from when they were originally pushed. */ + r = service_add_fd_store(s, TAKE_FD(fd), fdn, do_poll, /* propagate_upstream= */ false); + if (r <= 0 && u->load_state == UNIT_NOT_FOUND) + /* The fd was not actually stored, roll back the limit bump. */ + s->n_fd_store_max--; if (r < 0) { log_unit_debug_errno(u, r, "Failed to store deserialized fd '%s', ignoring: %m", fdn); return 0; } + /* If preservation is enabled then this fd was previously propagated upstream when it + * was first pushed. Restore the index so future removals can be forwarded upstream + * and the JSON mapping memfd can be regenerated. */ + if (r > 0 && s->fd_store && index > 0) + s->fd_store->index = index; } else if (streq(key, "extra-fd")) { _cleanup_free_ char *fdv = NULL, *fdn = NULL; _cleanup_close_ int fd = -EBADF; @@ -3832,6 +4219,9 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, if (r < 0) log_unit_debug_errno(u, r, "Failed to parse serialized restart counter '%s': %m", value); + } else if (streq(key, "restart-randomized-delay-chosen-usec")) { + (void) deserialize_usec(value, &s->restart_randomized_delay_chosen_usec); + } else if (streq(key, "forbid-restart")) { r = parse_boolean(value); if (r < 0) @@ -3950,8 +4340,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 +5588,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) @@ -5266,7 +5658,7 @@ static void service_notify_message( name = NULL; } - (void) service_add_fd_store_set(s, fds, name, !strv_contains(tags, "FDPOLL=0")); + (void) service_add_fd_store_set(s, fds, name, !strv_contains(tags, "FDPOLL=0"), /* propagate_upstream= */ fdstore_detected()); } /* Notify clients about changed status or main pid */ @@ -5612,7 +6004,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 +6021,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"); @@ -5862,10 +6255,11 @@ static void service_release_resources(Unit *u) { service_release_extra_fds(s); s->root_directory_fd = asynchronous_close(s->root_directory_fd); - if (s->fd_store_preserve_mode != EXEC_PRESERVE_YES) + if (IN_SET(s->fd_store_preserve_mode, EXEC_PRESERVE_NO, EXEC_PRESERVE_RESTART) || + (s->fd_store_preserve_mode == EXEC_PRESERVE_ON_SUCCESS && s->state == SERVICE_FAILED)) 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 +6290,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) { @@ -6134,6 +6528,8 @@ const UnitVTable service_vtable = { .can_delegate = true, .can_fail = true, .can_set_managed_oom = true, + .notify_plymouth = true, + .track_orphaned = true, .init = service_init, .done = service_done, @@ -6161,6 +6557,8 @@ const UnitVTable service_vtable = { .serialize = service_serialize, .deserialize_item = service_deserialize_item, + .attach_external_fd_to_fdstore = service_attach_external_fd_to_fdstore, + .active_state = service_active_state, .sub_state_to_string = service_sub_state_to_string, @@ -6198,8 +6596,6 @@ const UnitVTable service_vtable = { .test_startable = service_test_startable, - .notify_plymouth = true, - .audit_start_message_type = AUDIT_SERVICE_START, .audit_stop_message_type = AUDIT_SERVICE_STOP, }; diff --git a/src/core/service.h b/src/core/service.h index 9750b19ce285f..c028248bba3df 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -8,6 +8,18 @@ #include "pidref.h" #include "unit.h" +/* FDNAME used to push the JSON mapping memfd that pairs upstream-propagated fdstore indices with + * (unit-id, original fdname) tuples. The receiving manager looks for this fdname in LISTEN_FDNAMES + * to find the mapping document. */ +#define SERVICE_FDSTORE_MAPPING_FDNAME "systemd-fdstore-mapping" + +/* Prefix for the upstream FDNAME used when forwarding individual fd-store entries to a parent + * supervisor: the entries are exposed as "sub-fdstore-" so the supervisor's own fd-store + * namespace doesn't collide with names a downstream service manager assigns. The trailing index + * is matched up with an entry in the SERVICE_FDSTORE_MAPPING_FDNAME memfd to recover the original + * (unit, fdname) pair. */ +#define SERVICE_FDSTORE_SUB_FDNAME_PREFIX "sub-fdstore-" + typedef enum ServiceRestart { SERVICE_RESTART_NO, SERVICE_RESTART_ON_SUCCESS, @@ -112,6 +124,10 @@ typedef struct ServiceFDStore { char *fdname; sd_event_source *event_source; bool do_poll; + /* If non-zero, this fd was forwarded to the NOTIFY_SOCKET supervisor via FDSTORE=1, with the + * stringified value of this index as its FDNAME. The originating unit-id and original fdname + * are recorded in a JSON mapping memfd that is also pushed upstream. */ + uint64_t index; LIST_FIELDS(struct ServiceFDStore, fd_store); } ServiceFDStore; @@ -139,6 +155,8 @@ typedef struct Service { unsigned restart_steps; usec_t restart_usec; usec_t restart_max_delay_usec; + usec_t restart_randomized_delay_usec; /* configured upper bound for the randomized restart delay */ + usec_t restart_randomized_delay_chosen_usec; /* the value actually picked for the pending auto-restart */ usec_t timeout_start_usec; usec_t timeout_stop_usec; usec_t timeout_abort_usec; @@ -230,6 +248,8 @@ typedef struct Service { unsigned n_fd_store_max; ExecPreserveMode fd_store_preserve_mode; + char **luo_sessions; /* LUOSession= setting — list of session names to create/manage */ + int stdin_fd; int stdout_fd; int stderr_fd; @@ -279,6 +299,12 @@ extern const UnitVTable service_vtable; int service_set_socket_fd(Service *s, int fd, struct Socket *socket, struct SocketPeer *peer, bool selinux_context_net); void service_release_socket_fd(Service *s); +int service_add_fd_store(Service *s, int fd_in, const char *name, bool do_poll, bool propagate_upstream); + +int service_propagate_fd_store_mapping_upstream(Manager *m); + +ServiceExtraFD* service_extra_fd_free(ServiceExtraFD *fd); + usec_t service_restart_usec_next(const Service *s) _pure_; int service_determine_exec_selinux_label(Service *s, char **ret); 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..a7aa34caeb43f 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -30,7 +30,7 @@ #include "ip-protocol-list.h" #include "log.h" #include "manager.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "namespace-util.h" #include "parse-util.h" #include "path-util.h" @@ -210,6 +210,10 @@ static void socket_done(Unit *u) { strv_free(s->symlinks); + strv_free(s->xattr_entrypoint); + strv_free(s->xattr_listen); + strv_free(s->xattr_accept); + s->user = mfree(s->user); s->group = mfree(s->group); @@ -882,16 +886,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 +908,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 +937,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; } @@ -1520,7 +1514,9 @@ static int socket_address_listen_do( s->directory_mode, s->socket_mode, selinux_label, - s->smack); + s->smack, + s->xattr_entrypoint, + s->xattr_listen); } #define log_address_error_errno(u, address, error, fmt) \ @@ -2036,6 +2032,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) @@ -2054,25 +2051,21 @@ static int socket_chown(Socket *s, PidRef *ret_pid) { /* Child */ if (!isempty(s->user)) { - const char *user = s->user; - - r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); + r = get_user_creds(s->user, /* flags= */ 0, NULL, &uid, &gid, NULL, NULL); if (r < 0) { log_unit_error_errno(UNIT(s), r, "Failed to resolve user '%s': %s", - user, STRERROR_USER(r)); + s->user, STRERROR_USER(r)); _exit(EXIT_USER); } } if (!isempty(s->group)) { - const char *group = s->group; - - r = get_group_creds(&group, &gid, 0); + r = get_group_creds(s->group, /* flags= */ 0, /* ret_name= */ NULL, &gid); if (r < 0) { log_unit_error_errno(UNIT(s), r, "Failed to resolve group '%s': %s", - group, STRERROR_GROUP(r)); + s->group, STRERROR_GROUP(r)); _exit(EXIT_GROUP); } } @@ -3221,6 +3214,7 @@ static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, if (cfd < 0) goto fail; + (void) socket_set_xattrs(cfd, /* path= */ NULL, p->socket->xattr_accept); socket_apply_socket_options(p->socket, p, cfd); } @@ -3535,6 +3529,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; @@ -3733,6 +3729,7 @@ const UnitVTable socket_vtable = { .can_transient = true, .can_trigger = true, .can_fail = true, + .track_orphaned = true, .init = socket_init, .done = socket_done, diff --git a/src/core/socket.h b/src/core/socket.h index b9010bac49f52..d374bec9b70e5 100644 --- a/src/core/socket.h +++ b/src/core/socket.h @@ -126,6 +126,10 @@ typedef struct Socket { char **symlinks; + char **xattr_entrypoint; + char **xattr_listen; + char **xattr_accept; + bool accept; bool remove_on_stop; bool writable; diff --git a/src/core/swap.c b/src/core/swap.c index 5de1dccf42779..88e992bd75040 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; @@ -1580,6 +1616,7 @@ const UnitVTable swap_vtable = { .private_section = "Swap", .can_fail = true, + .track_orphaned = true, .init = swap_init, .load = swap_load, diff --git a/src/core/system.conf.in b/src/core/system.conf.in index 54196e84894df..aa8208ca1d504 100644 --- a/src/core/system.conf.in +++ b/src/core/system.conf.in @@ -37,9 +37,11 @@ #RebootWatchdogSec=10min #KExecWatchdogSec=off #WatchdogDevice= +#MinimumUptimeSec=15s #CapabilityBoundingSet= #NoNewPrivileges=no #ProtectSystem=auto +#RestrictFileSystemAccess=no #SystemCallArchitectures= #TimerSlackNSec= #StatusUnitFormat={{STATUS_UNIT_FORMAT_DEFAULT_STR}} @@ -77,8 +79,15 @@ #DefaultLimitRTTIME= #DefaultMemoryPressureThresholdSec=200ms #DefaultMemoryPressureWatch=auto +#DefaultCPUPressureThresholdSec=200ms +#DefaultCPUPressureWatch=auto +#DefaultIOPressureThresholdSec=200ms +#DefaultIOPressureWatch=auto #DefaultOOMPolicy=stop #DefaultSmackProcessLabel= #DefaultRestrictSUIDSGID= #ReloadLimitIntervalSec= #ReloadLimitBurst= +#EventLoopRateLimitIntervalSec=1s +#EventLoopRateLimitBurst=50000 +#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/transaction.c b/src/core/transaction.c index 90cc8ee41f1bf..9a126b1b60a9f 100644 --- a/src/core/transaction.c +++ b/src/core/transaction.c @@ -20,6 +20,7 @@ static bool job_matters_to_anchor(Job *job); static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies); +static void transaction_find_jobs_that_matter_to_anchor(Transaction *tr, unsigned generation); static void transaction_delete_job(Transaction *tr, Job *j, bool delete_dependencies) { assert(tr); @@ -28,6 +29,7 @@ static void transaction_delete_job(Transaction *tr, Job *j, bool delete_dependen /* Deletes one job from the transaction. */ transaction_unlink_job(tr, j, delete_dependencies); + (void) set_remove(tr->anchor_jobs, j); job_free(j); } @@ -52,7 +54,7 @@ static void transaction_abort(Transaction *tr) { assert(hashmap_isempty(tr->jobs)); } -static void transaction_find_jobs_that_matter_to_anchor(Job *j, unsigned generation) { +static void transaction_find_jobs_that_matter_to_anchor_one(Job *j, unsigned generation) { assert(j); /* A recursive sweep through the graph that marks all units that matter to the anchor job, i.e. are @@ -72,10 +74,22 @@ static void transaction_find_jobs_that_matter_to_anchor(Job *j, unsigned generat if (l->object->generation == generation) continue; - transaction_find_jobs_that_matter_to_anchor(l->object, generation); + transaction_find_jobs_that_matter_to_anchor_one(l->object, generation); } } +static void transaction_find_jobs_that_matter_to_anchor(Transaction *tr, unsigned generation) { + Job *j; + + assert(tr); + + /* All anchor jobs (and reachable jobs) get marked in the same generation. The recursion uses the + * generation only to avoid re-traversing already-visited nodes, so reusing the generation across + * anchors is correct: nodes already marked by a previous anchor are skipped. */ + SET_FOREACH(j, tr->anchor_jobs) + transaction_find_jobs_that_matter_to_anchor_one(j, generation); +} + static void transaction_merge_and_delete_job(Transaction *tr, Job *j, Job *other, JobType t) { JobDependency *last; @@ -249,12 +263,70 @@ static int transaction_ensure_mergeable(Transaction *tr, bool matters_to_anchor, return 0; } +static void transaction_drop_nop(Transaction *tr) { + Job *j; + int r; + + assert(tr); + + /* While a transaction is being built, a NOP job sits in the same per-unit list in tr->jobs (linked + * via transaction_next/transaction_prev) as the regular jobs for that unit; the dedicated 'nop_job' + * slot in Unit is only populated later, at install time. JOB_NOP equals _JOB_TYPE_MAX_MERGING and is + * thus outside the domain of job_type_lookup_merge(), so it cannot be merged with a regular job and + * would trip the assertion there. A unit can end up with both a NOP job (e.g. a try-restart/try-reload + * anchor that collapsed to JOB_NOP because the unit is inactive) and a regular job (typically pulled + * in as a dependency by another job in the same transaction, which is possible since + * EnqueueUnitJobMany() allows more than one anchor). In that case the NOP carries no meaning of its + * own, as the regular job is what determines the fate of the unit, so drop it to keep each unit's + * transaction list mergeable. */ + + HASHMAP_FOREACH(j, tr->jobs) { + Job *nop = NULL, *regular = NULL; + + LIST_FOREACH(transaction, k, j) + if (k->type == JOB_NOP) + nop = k; /* At most one NOP per unit, as jobs are deduplicated by type. */ + else + regular = k; + + /* Only drop the NOP when a regular job for the same unit remains. A lone NOP anchor must be + * kept so that e.g. 'try-restart' on an inactive unit still completes successfully. */ + if (!nop || !regular) + continue; + + log_unit_debug(nop->unit, + "Dropping NOP job for %s, a regular job for the same unit is part of the transaction.", + nop->unit->id); + + /* A NOP job only ever enters the transaction as a client-requested anchor: propagated + * try-restart/try-reload that would collapse to JOB_NOP is skipped before being added. Hand + * the anchor identity over to the surviving regular job (which was pulled in by another job + * and is not an anchor itself) so the unit the client explicitly named still shows up in the + * set reported back by EnqueueUnitJobMany(). */ + if (set_contains(tr->anchor_jobs, nop)) { + r = set_remove_and_put(tr->anchor_jobs, nop, regular); + if (r == -EEXIST) + /* The regular job is already an anchor; just drop the about-to-be-freed NOP + * key so it doesn't linger. */ + (void) set_remove(tr->anchor_jobs, nop); + else + /* nop was present (we just checked) and put cannot fail after a remove. */ + assert(r >= 0); + } + + transaction_delete_job(tr, nop, /* delete_dependencies= */ false); + } +} + static int transaction_merge_jobs(Transaction *tr, sd_bus_error *e) { Job *j; int r; assert(tr); + /* NOP jobs cannot be merged with regular jobs, so drop the ones that would collide first. */ + transaction_drop_nop(tr); + /* First step, try to drop unmergeable jobs for jobs that matter to anchor. */ r = transaction_ensure_mergeable(tr, /* matters_to_anchor= */ true, e); if (r < 0) @@ -275,7 +347,7 @@ static int transaction_merge_jobs(Transaction *tr, sd_bus_error *e) { Job *k; while ((k = j->transaction_next)) { - if (tr->anchor_job == k) { + if (set_contains(tr->anchor_jobs, k)) { transaction_merge_and_delete_job(tr, k, j, t); j = k; } else @@ -306,7 +378,7 @@ static void transaction_drop_redundant(Transaction *tr) { bool keep = false; LIST_FOREACH(transaction, k, j) - if (tr->anchor_job == k || + if (set_contains(tr->anchor_jobs, k) || !job_type_is_redundant(k->type, unit_active_state(k->unit)) || (k->unit->job && job_type_is_conflicting(k->type, k->unit->job->type))) { keep = true; @@ -524,7 +596,7 @@ static void transaction_collect_garbage(Transaction *tr) { again = false; HASHMAP_FOREACH(j, tr->jobs) { - if (tr->anchor_job == j) + if (set_contains(tr->anchor_jobs, j)) continue; if (!j->object_list) { @@ -557,11 +629,20 @@ static int transaction_is_destructive(Transaction *tr, JobMode mode, sd_bus_erro assert(!j->transaction_next); if (j->unit->job && (IN_SET(mode, JOB_FAIL, JOB_LENIENT) || j->unit->job->irreversible) && - job_type_is_conflicting(j->unit->job->type, j->type)) + job_type_is_conflicting(j->unit->job->type, j->type)) { + _cleanup_free_ char *anchors = NULL; + Job *a; + + SET_FOREACH(a, tr->anchor_jobs) + if (strextendf_with_separator(&anchors, ", ", "%s/%s", + a->unit->id, job_type_to_string(a->type)) < 0) + return -ENOMEM; + return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, - "Transaction for %s/%s is destructive (%s has '%s' job queued, but '%s' is included in transaction).", - tr->anchor_job->unit->id, job_type_to_string(tr->anchor_job->type), + "Transaction for %s is destructive (%s has '%s' job queued, but '%s' is included in transaction).", + strna(anchors), j->unit->id, job_type_to_string(j->unit->job->type), job_type_to_string(j->type)); + } } return 0; @@ -680,8 +761,16 @@ static int transaction_apply( installed_job = job_install(j); if (installed_job != j) { /* j has been merged into a previously installed job. */ - if (tr->anchor_job == j) - tr->anchor_job = installed_job; + if (set_contains(tr->anchor_jobs, j)) { + r = set_remove_and_put(tr->anchor_jobs, j, installed_job); + if (r == -EEXIST) + /* installed_job is already an anchor; just drop the + * about-to-be-freed key so it doesn't linger. */ + (void) set_remove(tr->anchor_jobs, j); + else + /* Old key was present and remove -> put cannot fail */ + assert(r >= 0); + } hashmap_remove_value(m->jobs, UINT32_TO_PTR(j->id), j); free_and_replace_full(j, installed_job, job_free); @@ -731,7 +820,7 @@ int transaction_activate( j->generation = 0; /* First step: figure out which jobs matter. */ - transaction_find_jobs_that_matter_to_anchor(tr->anchor_job, generation++); + transaction_find_jobs_that_matter_to_anchor(tr, generation++); /* Second step: Try not to stop any running services if we don't have to. Don't try to reverse * running jobs if we don't have to. */ @@ -1024,9 +1113,10 @@ int transaction_add_job_and_dependencies( if (!job_dependency_new(by, job, FLAGS_SET(flags, TRANSACTION_MATTERS), FLAGS_SET(flags, TRANSACTION_CONFLICTS))) return -ENOMEM; } else { - /* If the job has no parent job, it is the anchor job. */ - assert(!tr->anchor_job); - tr->anchor_job = job; + /* If the job has no parent job, it is an anchor job. */ + r = set_put(tr->anchor_jobs, job); + if (r < 0 && r != -EEXIST) + return r; if (FLAGS_SET(flags, TRANSACTION_REENQUEUE_ANCHOR)) job->refuse_late_merge = true; @@ -1210,6 +1300,7 @@ int transaction_add_isolate_jobs(Transaction *tr, Manager *m) { assert(tr); assert(m); + assert(set_size(tr->anchor_jobs) == 1); HASHMAP_FOREACH_KEY(u, k, m->units) { _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; @@ -1225,7 +1316,9 @@ int transaction_add_isolate_jobs(Transaction *tr, Manager *m) { if (!shall_stop_on_isolate(tr, u)) continue; - r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, tr->anchor_job, TRANSACTION_MATTERS, &e); + /* JOB_ISOLATE only ever has a single anchor (we reject multi-anchor isolate in the + * manager) so picking the first anchor is correct. */ + r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, set_first(tr->anchor_jobs), TRANSACTION_MATTERS, &e); if (r < 0) log_unit_warning_errno(u, r, "Cannot add isolate job, ignoring: %s", bus_error_message(&e, r)); } @@ -1235,11 +1328,17 @@ int transaction_add_isolate_jobs(Transaction *tr, Manager *m) { int transaction_add_triggering_jobs(Transaction *tr, Unit *u) { Unit *trigger; + Job *by; int r; assert(tr); assert(u); + /* The unit's own anchor job is the head of the list in tr->jobs. We pull in JOB_STOP for all + * triggers and link them to that anchor, so they are correctly grouped per anchor when there are + * multiple anchors in the transaction. */ + by = ASSERT_PTR(hashmap_get(tr->jobs, u)); + UNIT_FOREACH_DEPENDENCY_SAFE(trigger, u, UNIT_ATOM_TRIGGERED_BY) { _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; @@ -1251,7 +1350,7 @@ int transaction_add_triggering_jobs(Transaction *tr, Unit *u) { if (hashmap_contains(tr->jobs, trigger)) continue; - r = transaction_add_job_and_dependencies(tr, JOB_STOP, trigger, tr->anchor_job, TRANSACTION_MATTERS, &e); + r = transaction_add_job_and_dependencies(tr, JOB_STOP, trigger, by, TRANSACTION_MATTERS, &e); if (r < 0) log_unit_warning_errno(u, r, "Cannot add triggered by job, ignoring: %s", bus_error_message(&e, r)); } @@ -1270,11 +1369,15 @@ Transaction* transaction_new(bool irreversible, uint64_t id) { *tr = (Transaction) { .jobs = hashmap_new(NULL), + .anchor_jobs = set_new(NULL), .irreversible = irreversible, .id = id, }; - if (!tr->jobs) + if (!tr->jobs || !tr->anchor_jobs) { + hashmap_free(tr->jobs); + set_free(tr->anchor_jobs); return NULL; + } return TAKE_PTR(tr); } @@ -1284,6 +1387,9 @@ Transaction* transaction_free(Transaction *tr) { return NULL; assert(hashmap_isempty(tr->jobs)); + assert(set_isempty(tr->anchor_jobs)); + + set_free(tr->anchor_jobs); hashmap_free(tr->jobs); return mfree(tr); diff --git a/src/core/transaction.h b/src/core/transaction.h index 275dc5984f8ee..ee39b2600a141 100644 --- a/src/core/transaction.h +++ b/src/core/transaction.h @@ -6,7 +6,7 @@ typedef struct Transaction { /* Jobs to be added */ Hashmap *jobs; /* Unit object => Job object list 1:1 */ - Job *anchor_job; /* The job the user asked for */ + Set *anchor_jobs; /* the jobs the user asked for */ bool irreversible; uint64_t id; 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-serialize.c b/src/core/unit-serialize.c index d2aee125ddda6..867472a61923e 100644 --- a/src/core/unit-serialize.c +++ b/src/core/unit-serialize.c @@ -402,6 +402,23 @@ int unit_deserialize_state_skip(FILE *f) { /* End marker */ if (isempty(line)) return 1; + + /* A unit's serialized state may embed one or more "job" subsections (for u->job and + * u->nop_job), each itself terminated by an empty line. We must consume those nested + * sections fully, otherwise we'd stop at the job's end marker and treat the rest of the + * unit's fields as a new top-level entry. */ + if (streq(line, "job")) + for (;;) { + _cleanup_free_ char *job_line = NULL; + + r = read_stripped_line(f, LONG_LINE_MAX, &job_line); + if (r < 0) + return log_error_errno(r, "Failed to read serialization line: %m"); + if (r == 0) + return 0; + if (isempty(job_line)) + break; + } } } diff --git a/src/core/unit.c b/src/core/unit.c index bb3430186cab0..0936e42295896 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -7,6 +7,7 @@ #include "sd-bus.h" #include "sd-id128.h" #include "sd-messages.h" +#include "sd-varlink.h" #include "all-units.h" #include "alloc-util.h" @@ -39,7 +40,7 @@ #include "load-fragment.h" #include "log.h" #include "logarithm.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "manager.h" #include "mount-util.h" #include "mountpoint-util.h" @@ -132,6 +133,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 +181,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 +681,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 +696,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); } @@ -782,6 +792,7 @@ Unit* unit_free(Unit *u) { u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot); u->bus_track = sd_bus_track_unref(u->bus_track); + u->varlink_unit_change = sd_varlink_unref(u->varlink_unit_change); u->deserialized_refs = strv_free(u->deserialized_refs); u->pending_freezer_invocation = sd_bus_message_unref(u->pending_freezer_invocation); @@ -1184,6 +1195,8 @@ int unit_merge(Unit *u, Unit *other) { if (r < 0) return r; + UnitLoadState saved_load_state = other->load_state; + other->load_state = UNIT_MERGED; other->merged_into = u; @@ -1192,7 +1205,7 @@ int unit_merge(Unit *u, Unit *other) { /* If there is still some data attached to the other node, we * don't need it anymore, and can free it. */ - if (other->load_state != UNIT_STUB) + if (saved_load_state != UNIT_STUB) if (UNIT_VTABLE(other)->done) UNIT_VTABLE(other)->done(other); @@ -1615,7 +1628,7 @@ static int unit_add_oomd_dependencies(Unit *u) { if (!c) return 0; - bool wants_oomd = c->moom_swap == MANAGED_OOM_KILL || c->moom_mem_pressure == MANAGED_OOM_KILL; + bool wants_oomd = c->moom_swap == MANAGED_OOM_KILL || c->moom_mem_pressure == MANAGED_OOM_KILL || !strv_isempty(c->moom_rules); if (!wants_oomd) return 0; @@ -2327,7 +2340,7 @@ static void retroactively_stop_dependencies(Unit *u) { void unit_start_on_termination_deps(Unit *u, UnitDependencyAtom atom) { const char *dependency_name = NULL; - JobMode job_mode; + JobMode job_mode = _JOB_MODE_INVALID; unsigned n_jobs = 0; int r; @@ -4147,7 +4160,7 @@ int unit_kill( } if (signo == SIGKILL) { - r = cg_kill_kernel_sigkill(p); + r = cg_kill_kernel_sigkill(p, /* ret_n_pids_killed= */ NULL); if (r >= 0) { killed = true; log_unit_info(u, "Killed unit cgroup '%s' with SIGKILL on client request.", p); @@ -4294,6 +4307,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; @@ -4331,10 +4347,13 @@ static int unit_verify_contexts(const Unit *u) { return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "PrivatePIDs= setting is only supported for service units. Refusing."); if ((ec->user || ec->dynamic_user || ec->group || ec->pam_name) && ec->private_users == PRIVATE_USERS_MANAGED) - return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "PrivateUsers=managed may not be used in combination with User=/DynamicUser=/Group=/PAMName=, refusing."); + return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "PrivateUsers=managed may not be used in combination with User=/DynamicUser=/Group=/PAMName=. Refusing."); if (ec->user_namespace_path && ec->private_users != PRIVATE_USERS_NO) - return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "PrivateUsers= may not be used with custom UserNamespacePath=, refusing."); + return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "PrivateUsers= may not be used with custom UserNamespacePath=. Refusing."); + + if (ec->private_pids != PRIVATE_PIDS_NO && ec->pam_name) + return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "PAM is not supported under PrivatePIDs=. Refusing."); const KillContext *kc = unit_get_kill_context(u); @@ -4881,6 +4900,42 @@ int unit_make_transient(Unit *u) { return 0; } +int manager_setup_transient_unit(Manager *m, const char *name, Unit **ret, sd_bus_error *reterr_error) { + Unit *u; + int r; + + assert(m); + assert(name); + assert(ret); + + UnitType t = unit_name_to_type(name); + if (t < 0) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, + "Invalid unit name or type: %s", name); + + if (!unit_vtable[t]->can_transient) + return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, + "Unit type %s does not support transient units.", + unit_type_to_string(t)); + + r = manager_load_unit(m, name, /* path= */ NULL, reterr_error, &u); + if (r < 0) + return r; + + if (!unit_is_pristine(u)) + return sd_bus_error_setf(reterr_error, BUS_ERROR_UNIT_EXISTS, + "Unit %s was already loaded or has a fragment file.", name); + + /* OK, the unit failed to load and is unreferenced, now let's + * fill in the transient data instead */ + r = unit_make_transient(u); + if (r < 0) + return r; + + *ret = u; + return 0; +} + static bool ignore_leftover_process(const char *comm) { return comm && comm[0] == '('; /* Most likely our own helper process (PAM?), ignore */ } @@ -4977,6 +5032,35 @@ static int unit_kill_context_one( return !is_alien; } +typedef struct PidsMaxRestore { + const char *cgroup_path; + uint64_t max; + Unit *unit; +} PidsMaxRestore; + +static void pids_max_restore(PidsMaxRestore *p) { + int r; + + assert(p); + assert(p->unit); + + if (!p->cgroup_path) + return; + + char value[DECIMAL_STR_MAX(uint64_t) + 1] = "max"; + if (p->max != CGROUP_LIMIT_MAX) + xsprintf(value, "%" PRIu64, p->max); + + r = cg_set_attribute(p->cgroup_path, "pids.max", value); + if (r < 0) + log_unit_warning_errno(p->unit, + r, + "Failed to restore pids.max to %s for control group %s, ignoring: %m", + value, empty_to_root(p->cgroup_path)); + + p->cgroup_path = NULL; +} + int unit_kill_context(Unit *u, KillOperation k) { bool wait_for_exit = false, send_sighup; cg_kill_log_func_t log_func = NULL; @@ -5013,8 +5097,32 @@ int unit_kill_context(Unit *u, KillOperation k) { CGroupRuntime *crt = unit_get_cgroup_runtime(u); if (crt && crt->cgroup_path && (c->kill_mode == KILL_CONTROL_GROUP || (c->kill_mode == KILL_MIXED && k == KILL_KILL))) { + _cleanup_(pids_max_restore) PidsMaxRestore state = { + .unit = u, + }; _cleanup_set_free_ Set *pid_set = NULL; + /* Stop more processes from being spawned when zapping the cgroup. Restore previous state + * before returning. */ + if (sig == SIGKILL) { + r = cg_get_attribute_as_uint64(crt->cgroup_path, "pids.max", &state.max); + if (r < 0) + log_unit_warning_errno(u, + r, + "Failed to read pids.max for control group %s, ignoring: %m", + empty_to_root(crt->cgroup_path)); + else { + r = cg_set_attribute(crt->cgroup_path, "pids.max", "0"); + if (r < 0) + log_unit_warning_errno(u, + r, + "Failed to set pids.max to 0 for control group %s, ignoring: %m", + empty_to_root(crt->cgroup_path)); + else + state.cgroup_path = crt->cgroup_path; + } + } + /* Exclude the main/control pids from being killed via the cgroup */ r = unit_pid_set(u, &pid_set); if (r < 0) @@ -5343,16 +5451,16 @@ static void unit_modify_user_nft_set(Unit *u, bool add, NFTSetSource source, uin if (!c) return; - if (!u->manager->nfnl) { - r = sd_nfnl_socket_open(&u->manager->nfnl); - if (r < 0) - return; - } - FOREACH_ARRAY(nft_set, c->nft_set_context.sets, c->nft_set_context.n_sets) { if (nft_set->source != source) continue; + if (!u->manager->nfnl) { + r = sd_nfnl_socket_open(&u->manager->nfnl); + if (r < 0) + return (void) log_once_errno(LOG_WARNING, r, "Failed to open NETLINK_NETFILTER socket, ignoring: %m"); + } + r = nft_set_element_modify_any(u->manager->nfnl, add, nft_set->nfproto, nft_set->table, nft_set->set, &element, sizeof(element)); if (r < 0) log_warning_errno(r, "Failed to %s NFT set entry: family %s, table %s, set %s, ID %u, ignoring: %m", @@ -5823,12 +5931,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 +5939,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 +5951,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 +5979,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 +5990,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 +6004,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 +6015,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 +6479,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..f96ae279362cc 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" @@ -288,6 +289,10 @@ typedef struct Unit { /* References to this unit from clients */ sd_bus_track *bus_track; + + /* If non-NULL, a varlink connection streaming unit state change notifications */ + sd_varlink *varlink_unit_change; + char **deserialized_refs; /* References to this */ @@ -613,6 +618,13 @@ typedef struct UnitVTable { /* Try to match up fds with what we need for this unit */ void (*distribute_fds)(Unit *u, FDSet *fds); + /* Restore one file descriptor that PID 1 retrieved from a Live Update Orchestrator session into the + * unit's per-instance state (e.g. fd store). Always consumes 'fd', even on failure. If the fd + * was previously propagated to an upstream NOTIFY_SOCKET supervisor under a numeric index, + * 'index' carries that index so it can be re-claimed (avoiding collisions with newly allocated + * indices and keeping FDSTOREREMOVE messages routable). Pass 0 to indicate no preserved index. */ + int (*attach_external_fd_to_fdstore)(Unit *u, int fd, const char *fdname, uint64_t index); + /* Boils down the more complex internal state of this unit to * a simpler one that the engine can understand */ UnitActiveState (*active_state)(Unit *u); @@ -766,6 +778,10 @@ typedef struct UnitVTable { /* If true, we'll notify a surrounding VMM/container manager about this unit becoming available */ bool notify_supervisor; + /* If true, we'll synthesize an 'orphaned' unit if a unit becomes an alias of another unit during a + * reload cycle, but still has resources assigned to it */ + bool track_orphaned; + /* The audit events to generate on start + stop (or 0 if none shall be generated) */ int audit_start_message_type; int audit_stop_message_type; @@ -972,6 +988,7 @@ int unit_write_settingf(Unit *u, UnitWriteFlags flags, const char *name, const c int unit_kill_context(Unit *u, KillOperation k); int unit_make_transient(Unit *u); +int manager_setup_transient_unit(Manager *m, const char *name, Unit **ret, sd_bus_error *reterr_error); int unit_add_mounts_for(Unit *u, const char *path, UnitDependencyMask mask, UnitMountDependencyType type); @@ -1097,6 +1114,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..f9c40458da451 100644 --- a/src/core/user.conf.in +++ b/src/core/user.conf.in @@ -54,7 +54,13 @@ #DefaultLimitRTTIME= #DefaultMemoryPressureThresholdSec=200ms #DefaultMemoryPressureWatch=auto +#DefaultCPUPressureThresholdSec=200ms +#DefaultCPUPressureWatch=auto +#DefaultIOPressureThresholdSec=200ms +#DefaultIOPressureWatch=auto #DefaultSmackProcessLabel= #DefaultRestrictSUIDSGID= #ReloadLimitIntervalSec= -#ReloadLimitBurst +#ReloadLimitBurst= +#EventLoopRateLimitIntervalSec=1s +#EventLoopRateLimitBurst=50000 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..e031f00368bab 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; } @@ -256,6 +258,7 @@ int unit_cgroup_context_build_json(sd_json_variant **ret, const char *name, void JSON_BUILD_PAIR_FINITE_USEC("CPUQuotaPeriodUSec", c->cpu_quota_period_usec), JSON_BUILD_PAIR_CALLBACK_NON_NULL("AllowedCPUs", cpuset_build_json, &c->cpuset_cpus), JSON_BUILD_PAIR_CALLBACK_NON_NULL("StartupAllowedCPUs", cpuset_build_json, &c->startup_cpuset_cpus), + JSON_BUILD_PAIR_ENUM("CPUSetPartition", cpuset_partition_to_string(c->cpuset_partition)), /* Memory Accounting and Control */ SD_JSON_BUILD_PAIR_BOOLEAN("MemoryAccounting", c->memory_accounting), @@ -309,7 +312,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 +321,18 @@ 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_STRV_NON_EMPTY("OOMRules", c->moom_rules), + 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..bdbecd4551bd6 100644 --- a/src/core/varlink-common.c +++ b/src/core/varlink-common.c @@ -1,10 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "sd-bus.h" +#include "sd-varlink.h" #include "bus-common-errors.h" #include "cpu-set-util.h" +#include "execute.h" #include "json-util.h" +#include "pidref.h" +#include "process-util.h" #include "rlimit-util.h" #include "varlink-common.h" #include "varlink-unit.h" @@ -17,6 +21,8 @@ const char* varlink_error_id_from_bus_error(const sd_bus_error *e) { { BUS_ERROR_NO_SUCH_UNIT, VARLINK_ERROR_UNIT_NO_SUCH_UNIT }, { BUS_ERROR_ONLY_BY_DEPENDENCY, VARLINK_ERROR_UNIT_ONLY_BY_DEPENDENCY }, { BUS_ERROR_SHUTTING_DOWN, VARLINK_ERROR_UNIT_DBUS_SHUTTING_DOWN }, + { BUS_ERROR_UNIT_EXISTS, VARLINK_ERROR_UNIT_UNIT_EXISTS }, + { BUS_ERROR_BAD_UNIT_SETTING, VARLINK_ERROR_UNIT_BAD_SETTING }, }; if (!sd_bus_error_is_set(e)) @@ -29,6 +35,13 @@ const char* varlink_error_id_from_bus_error(const sd_bus_error *e) { return NULL; } +int varlink_reply_bus_error(sd_varlink *link, int r, const sd_bus_error *e) { + const char *error_id = varlink_error_id_from_bus_error(e); + if (error_id) + return sd_varlink_error(link, error_id, NULL); + return sd_varlink_error_errno(link, r); +} + int rlimit_build_json(sd_json_variant **ret, const char *name, void *userdata) { const struct rlimit *rl = userdata; struct rlimit buf = {}; @@ -104,3 +117,104 @@ 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))); +} + +int exec_command_list_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + ExecCommand *list = userdata; + int r; + + assert(ret); + + LIST_FOREACH(command, c, list) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL; + + r = exec_command_build_json(&entry, /* name= */ NULL, c); + if (r < 0) + return r; + + r = sd_json_variant_append_array(&v, entry); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +int exec_command_status_build_json(sd_json_variant **ret, const char *name, void *userdata) { + ExecStatus *status = ASSERT_PTR(userdata); + + assert(ret); + + if (!pid_is_valid(status->pid)) { + *ret = NULL; + return 0; + } + + return sd_json_buildo( + ret, + /* TODO: replace with a real PidRef once ExecStatus carries one */ + SD_JSON_BUILD_PAIR("PID", JSON_BUILD_PIDREF(&PIDREF_MAKE_FROM_PID(status->pid))), + JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("StartTimestamp", &status->start_timestamp), + JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("ExitTimestamp", &status->exit_timestamp), + JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("HandoffTimestamp", &status->handoff_timestamp), + SD_JSON_BUILD_PAIR_CONDITION(status->code > 0, "Code", SD_JSON_BUILD_INTEGER(status->code)), + SD_JSON_BUILD_PAIR_CONDITION(status->code > 0, "Status", SD_JSON_BUILD_INTEGER(status->status))); +} + +/* exec_command_status_list_build_json() is the runtime counterpart of exec_command_list_build_json(). + * The two arrays are positionally aligned: index N in the status array corresponds to index N in the + * command array. Commands that have not yet run produce null entries to preserve alignment. */ +int exec_command_status_list_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + ExecCommand *list = userdata; + bool any_ran = false; + int r; + + assert(ret); + + LIST_FOREACH(command, c, list) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL; + + r = exec_command_status_build_json(&entry, /* name= */ NULL, &c->exec_status); + if (r < 0) + return r; + + if (entry) { + any_ran = true; + r = sd_json_variant_append_array(&v, entry); + } else + r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_NULL); + if (r < 0) + return r; + } + + if (!any_ran) { + *ret = NULL; + return 0; + } + + *ret = TAKE_PTR(v); + return 0; +} diff --git a/src/core/varlink-common.h b/src/core/varlink-common.h index 82d1458dd9609..98fe47b48ccaa 100644 --- a/src/core/varlink-common.h +++ b/src/core/varlink-common.h @@ -6,5 +6,9 @@ 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); +int exec_command_list_build_json(sd_json_variant **ret, const char *name, void *userdata); +int exec_command_status_build_json(sd_json_variant **ret, const char *name, void *userdata); +int exec_command_status_list_build_json(sd_json_variant **ret, const char *name, void *userdata); +int varlink_reply_bus_error(sd_varlink *link, int r, const sd_bus_error *e); 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..a80fa9692747a 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) { @@ -312,7 +315,11 @@ static int numa_policy_build_json(sd_json_variant **ret, const char *name, void return 0; } - return sd_json_variant_new_string(ret, mpol_to_string(t)); + _cleanup_free_ char *s = strdup(mpol_to_string(t)); + if (!s) + return -ENOMEM; + + return sd_json_variant_new_string(ret, json_underscorify(s)); } static int numa_mask_build_json(sd_json_variant **ret, const char *name, void *userdata) { @@ -343,13 +350,13 @@ 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) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; ExecDirectory *exec_dir = ASSERT_PTR(userdata); - const QuotaLimit *quota = &exec_dir->exec_quota; + const ExecQuotaLimit *quota = &exec_dir->exec_quota; int r; assert(ret); @@ -470,48 +477,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 +796,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 +825,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 +867,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 +885,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", exec_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 +904,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 +934,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 +949,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 +981,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-job.c b/src/core/varlink-job.c new file mode 100644 index 0000000000000..a0b45fa24ceb1 --- /dev/null +++ b/src/core/varlink-job.c @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink.h" + +#include "bus-polkit.h" +#include "job.h" +#include "json-util.h" +#include "locale-util.h" +#include "manager.h" +#include "selinux-access.h" +#include "strv.h" +#include "unit.h" +#include "varlink-job.h" + +static int activation_details_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_strv_free_ char **pairs = NULL; + Job *j = ASSERT_PTR(userdata); + int r; + + assert(ret); + + r = activation_details_append_pair(j->activation_details, &pairs); + if (r < 0) + return log_debug_errno(r, "Failed to get activation details: %m"); + if (r == 0) { + *ret = NULL; + return 0; + } + + STRV_FOREACH_PAIR(key, value, pairs) { + r = sd_json_variant_set_field_string(&v, *key, *value); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +int job_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Job *j = ASSERT_PTR(userdata); + + /* "Unit" is omitted in StartTransient streaming notifications where the caller already knows the unit. */ + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_INTEGER("Id", j->id), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Unit", j->unit ? j->unit->id : NULL), + JSON_BUILD_PAIR_ENUM("JobType", job_type_to_string(j->type)), + JSON_BUILD_PAIR_ENUM("State", job_state_to_string(j->state)), + JSON_BUILD_PAIR_ENUM_NON_EMPTY("Result", job_result_to_string(j->result)), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ActivationDetails", activation_details_build_json, j)); +} + +static int varlink_error_no_such_job(sd_varlink *link, const char *name) { + return sd_varlink_errorbo( + ASSERT_PTR(link), + VARLINK_ERROR_JOB_NO_SUCH_JOB, + JSON_BUILD_PAIR_STRING_NON_EMPTY("parameter", name)); +} + +static int list_job_one(sd_varlink *link, Job *job) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + assert(link); + assert(job); + + r = job_build_json(&v, /* name= */ NULL, job); + if (r < 0) + return r; + + return sd_varlink_reply(link, v); +} + +static int list_job_one_with_selinux_access_check(sd_varlink *link, Job *job) { + int r; + + assert(link); + assert(job); + assert(job->unit); + + r = mac_selinux_unit_access_check_varlink(job->unit, link, "status"); + if (r < 0) + /* If mac_selinux_unit_access_check_varlink() returned an error, + * it means that SELinux enforce is on. It also does all the logging(). */ + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); + + return list_job_one(link, job); +} + +typedef struct JobLookupParameters { + uint32_t id; + const char *unit; +} JobLookupParameters; + +static int lookup_job_by_parameters( + sd_varlink *link, + Manager *manager, + JobLookupParameters *p, + Job **ret) { + + /* The function can return ret=NULL if no lookup parameters provided */ + Job *job = NULL; + + assert(link); + assert(manager); + assert(p); + assert(ret); + + if (p->id > 0) { + job = manager_get_job(manager, p->id); + if (!job) + return varlink_error_no_such_job(link, "id"); + } + + if (p->unit) { + Unit *u = manager_get_unit(manager, p->unit); + if (!u || !u->job) + return varlink_error_no_such_job(link, "unit"); + if (job && u->job != job) { + log_debug("Job lookup by parameters id=%u unit='%s' resulted in different jobs.", p->id, p->unit); + return varlink_error_no_such_job(link, /* name= */ NULL); + } + + job = u->job; + } + + *ret = job; + return !!job; +} + +int vl_method_list_jobs(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "id", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, offsetof(JobLookupParameters, id), 0 }, + { "unit", SD_JSON_VARIANT_STRING, json_dispatch_const_unit_name, offsetof(JobLookupParameters, unit), 0 }, + {} + }; + + Manager *manager = ASSERT_PTR(userdata); + JobLookupParameters p = {}; + Job *job; + int r; + + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + r = lookup_job_by_parameters(link, manager, &p, &job); + if (r < 0) + return r; + if (r > 0) + return list_job_one_with_selinux_access_check(link, job); + + /* List all jobs */ + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); + + r = sd_varlink_set_sentinel(link, VARLINK_ERROR_JOB_NO_SUCH_JOB); + if (r < 0) + return r; + + HASHMAP_FOREACH(job, manager->jobs) { + r = mac_selinux_unit_access_check_varlink(job->unit, link, "status"); + if (r < 0) + continue; + + r = list_job_one(link, job); + if (r < 0) + return r; + } + + return 0; +} + +int vl_method_cancel_job(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "id", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, 0, SD_JSON_MANDATORY }, + {} + }; + + Manager *manager = ASSERT_PTR(userdata); + uint32_t id = 0; + int r; + + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &id); + if (r != 0) + return r; + + Job *j = manager_get_job(manager, id); + if (!j) + return varlink_error_no_such_job(link, "id"); + + r = mac_selinux_unit_access_check_varlink(j->unit, link, "stop"); + if (r < 0) + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); + + r = varlink_verify_polkit_async( + link, + manager->system_bus, + "org.freedesktop.systemd1.manage-units", + (const char**) STRV_MAKE( + "unit", j->unit ? j->unit->id : NULL, + "verb", "cancel", + "polkit.message", N_("Authentication is required to cancel job for unit '$(unit)'."), + "polkit.gettext_domain", GETTEXT_PACKAGE), + &manager->polkit_registry); + if (r <= 0) + return r; + + job_finish_and_invalidate(j, JOB_CANCELED, /* recursive= */ true, /* already= */ false); + + return sd_varlink_reply(link, NULL); +} + +int vl_method_clear_all_jobs(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + int r; + + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + r = mac_selinux_access_check_varlink(link, "reload"); + if (r < 0) + return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL); + + r = varlink_verify_polkit_async( + link, + manager->system_bus, + "org.freedesktop.systemd1.manage-units", + (const char**) STRV_MAKE( + "verb", "clear-jobs", + "polkit.message", N_("Authentication is required to clear all pending jobs."), + "polkit.gettext_domain", GETTEXT_PACKAGE), + &manager->polkit_registry); + if (r <= 0) + return r; + + manager_clear_jobs(manager); + + return sd_varlink_reply(link, NULL); +} diff --git a/src/core/varlink-job.h b/src/core/varlink-job.h new file mode 100644 index 0000000000000..6393d4318af7b --- /dev/null +++ b/src/core/varlink-job.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +#define VARLINK_ERROR_JOB_NO_SUCH_JOB "io.systemd.Job.NoSuchJob" + +int job_build_json(sd_json_variant **ret, const char *name, void *userdata); + +int vl_method_list_jobs(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_cancel_job(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_clear_all_jobs(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); 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..f63d2a2e8f3db 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)); } @@ -187,7 +193,9 @@ static int manager_runtime_build_json(sd_json_variant **ret, const char *name, v JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("WatchdogLastPingTimestamp", watchdog_get_last_ping_as_dual_timestamp(&watchdog_last_ping)), SD_JSON_BUILD_PAIR_STRING("SystemState", manager_state_to_string(manager_state(m))), SD_JSON_BUILD_PAIR_UNSIGNED("ExitCode", m->return_value), - SD_JSON_BUILD_PAIR_UNSIGNED("SoftRebootsCount", m->soft_reboots_count)); + SD_JSON_BUILD_PAIR_UNSIGNED("SoftRebootsCount", m->soft_reboots_count), + SD_JSON_BUILD_PAIR_UNSIGNED("KExecsCount", m->kexecs_count), + SD_JSON_BUILD_PAIR_UNSIGNED("ReloadCount", m->reload_count)); } int vl_method_describe_manager(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { @@ -201,6 +209,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 +223,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 +262,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 +283,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 +306,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 +345,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; @@ -368,6 +383,7 @@ int vl_method_enqueue_marked_jobs_manager(sd_varlink *link, sd_json_variant *par JOB_FAIL, /* reload_if_possible= */ !BIT_SET(u->markers, UNIT_MARKER_NEEDS_RESTART), &job_id, + /* ret_job= */ NULL, &bus_error); if (ERRNO_IS_NEG_RESOURCE(r)) return r; @@ -398,3 +414,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..0e6bfa961890d 100644 --- a/src/core/varlink-metrics.c +++ b/src/core/varlink-metrics.c @@ -7,17 +7,206 @@ #include "manager.h" #include "metrics.h" #include "service.h" +#include "string-util.h" #include "unit-def.h" #include "unit.h" #include "varlink-metrics.h" +#include "version.h" -static int unit_active_state_build_json(MetricFamilyContext *context, void *userdata) { +static int active_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); Unit *unit; char *key; int r; - assert(context); + assert(mf && mf->name); + assert(vl); + + _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( + mf, + vl, + unit->id, + unit->active_enter_timestamp.realtime, + enter_fields); + if (r < 0) + return r; + + r = metric_build_send_unsigned( + mf, + vl, + unit->id, + unit->active_exit_timestamp.realtime, + exit_fields); + if (r < 0) + return r; + } + + return 0; +} + +static int inactive_exit_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Unit *unit; + char *key; + int r; + + assert(mf && mf->name); + assert(vl); + + HASHMAP_FOREACH_KEY(unit, key, manager->units) { + /* ignore aliases */ + if (key != unit->id) + continue; + + r = metric_build_send_unsigned( + mf, + vl, + unit->id, + unit->inactive_exit_timestamp.realtime, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int version_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + assert(mf && mf->name); + assert(vl); + + return metric_build_send_string( + mf, + vl, + /* object= */ NULL, + GIT_VERSION, + /* fields= */ NULL); +} + +static int boot_timestamp_build_json( + const MetricFamily *mf, + sd_varlink *vl, + const dual_timestamp *t, + bool with_monotonic) { + + int r; + + assert(mf && mf->name); + assert(vl); + assert(t); + + if (timestamp_is_set(t->realtime)) { + r = metric_build_send_unsigned( + mf, /* the .Realtime metric family entry */ + vl, + /* object= */ NULL, + t->realtime, + /* fields= */ NULL); + if (r < 0) + return r; + } + + if (with_monotonic && timestamp_is_set(t->monotonic)) { + assert(endswith(mf[1].name, ".Monotonic")); + r = metric_build_send_unsigned( + mf + 1, /* the .Monotonic sibling is the next entry */ + vl, + /* object= */ NULL, + t->monotonic, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int kernel_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + return boot_timestamp_build_json(mf, vl, &manager->timestamps[MANAGER_TIMESTAMP_KERNEL], /* with_monotonic= */ false); +} + +static int userspace_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + return boot_timestamp_build_json(mf, vl, &manager->timestamps[MANAGER_TIMESTAMP_USERSPACE], /* with_monotonic= */ true); +} + +static int finish_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + return boot_timestamp_build_json(mf, vl, &manager->timestamps[MANAGER_TIMESTAMP_FINISH], /* with_monotonic= */ true); +} + +static int state_change_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Unit *unit; + char *key; + int r; + + assert(mf && mf->name); + assert(vl); + + HASHMAP_FOREACH_KEY(unit, key, manager->units) { + /* ignore aliases */ + if (key != unit->id) + continue; + + r = metric_build_send_unsigned( + mf, + vl, + unit->id, + unit->state_change_timestamp.realtime, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int status_errno_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + int r; + + assert(mf && mf->name); + assert(vl); + + LIST_FOREACH(units_by_type, unit, manager->units_by_type[UNIT_SERVICE]) { + r = metric_build_send_unsigned( + mf, + vl, + unit->id, + (uint64_t) SERVICE(unit)->status_errno, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int unit_active_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Unit *unit; + char *key; + int r; + + assert(mf && mf->name); + assert(vl); HASHMAP_FOREACH_KEY(unit, key, manager->units) { /* ignore aliases */ @@ -25,7 +214,8 @@ static int unit_active_state_build_json(MetricFamilyContext *context, void *user continue; r = metric_build_send_string( - context, + mf, + vl, unit->id, unit_active_state_to_string(unit_active_state(unit)), /* fields= */ NULL); @@ -36,13 +226,14 @@ static int unit_active_state_build_json(MetricFamilyContext *context, void *user return 0; } -static int unit_load_state_build_json(MetricFamilyContext *context, void *userdata) { +static int unit_load_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); Unit *unit; char *key; int r; - assert(context); + assert(mf && mf->name); + assert(vl); HASHMAP_FOREACH_KEY(unit, key, manager->units) { /* ignore aliases */ @@ -50,7 +241,8 @@ static int unit_load_state_build_json(MetricFamilyContext *context, void *userda continue; r = metric_build_send_string( - context, + mf, + vl, unit->id, unit_load_state_to_string(unit->load_state), /* fields= */ NULL); @@ -61,15 +253,20 @@ static int unit_load_state_build_json(MetricFamilyContext *context, void *userda return 0; } -static int nrestarts_build_json(MetricFamilyContext *context, void *userdata) { +static int nrestarts_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); int r; - assert(context); + assert(mf && mf->name); + assert(vl); LIST_FOREACH(units_by_type, unit, manager->units_by_type[UNIT_SERVICE]) { r = metric_build_send_unsigned( - context, unit->id, SERVICE(unit)->n_restarts, /* fields= */ NULL); + mf, + vl, + unit->id, + SERVICE(unit)->n_restarts, + /* fields= */ NULL); if (r < 0) return r; } @@ -77,11 +274,26 @@ static int nrestarts_build_json(MetricFamilyContext *context, void *userdata) { return 0; } -static int units_by_type_total_build_json(MetricFamilyContext *context, void *userdata) { +static int reload_count_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + + assert(mf && mf->name); + assert(vl); + + return metric_build_send_unsigned( + mf, + vl, + /* object= */ NULL, + manager->reload_count, + /* fields= */ NULL); +} + +static int units_by_type_total_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); int r; - assert(context); + assert(mf && mf->name); + assert(vl); for (UnitType type = 0; type < _UNIT_TYPE_MAX; type++) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; @@ -95,7 +307,8 @@ static int units_by_type_total_build_json(MetricFamilyContext *context, void *us return r; r = metric_build_send_unsigned( - context, + mf, + vl, /* object= */ NULL, counter, fields); @@ -106,14 +319,15 @@ static int units_by_type_total_build_json(MetricFamilyContext *context, void *us return 0; } -static int units_by_state_total_build_json(MetricFamilyContext *context, void *userdata) { +static int units_by_state_total_build_json(const MetricFamily *mf, sd_varlink *vl, 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; - assert(context); + assert(mf && mf->name); + assert(vl); /* TODO need a rework probably with state counter */ HASHMAP_FOREACH_KEY(unit, key, manager->units) { @@ -132,7 +346,8 @@ static int units_by_state_total_build_json(MetricFamilyContext *context, void *u return r; r = metric_build_send_unsigned( - context, + mf, + vl, /* object= */ NULL, counters[state], fields); @@ -143,37 +358,220 @@ 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(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + + assert(mf && mf->name); + assert(vl); + + return metric_build_send_unsigned( + mf, + vl, + /* object= */ NULL, + hashmap_size(manager->jobs), + /* fields= */ NULL); +} + +static int system_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + + assert(mf && mf->name); + assert(vl); + + return metric_build_send_string( + mf, + vl, + /* object= */ NULL, + manager_state_to_string(manager_state(manager)), + /* fields= */ NULL); +} + +static int units_by_load_state_total_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + uint64_t counters[_UNIT_LOAD_STATE_MAX] = {}; + Unit *unit; + char *key; + int r; + + assert(mf && mf->name); + assert(vl); + + 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( + mf, + vl, + /* object= */ NULL, + counters[state], + fields); + if (r < 0) + return r; + } + + return 0; +} + +static int units_total_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + uint64_t count = 0; + Unit *unit; + char *key; + + assert(mf && mf->name); + assert(vl); + + HASHMAP_FOREACH_KEY(unit, key, manager->units) { + /* ignore aliases */ + if (key != unit->id) + continue; + + count++; + } + + return metric_build_send_unsigned( + mf, + vl, + /* 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 "FinishTimestamp.Realtime", + .description = "CLOCK_REALTIME microseconds at which userspace finished booting", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = finish_timestamp_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "FinishTimestamp.Monotonic", + .description = "CLOCK_MONOTONIC microseconds at which userspace finished booting", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = NULL, + }, + /* Keep those ↑ in sync with finish_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 "KernelTimestamp.Realtime", + .description = "CLOCK_REALTIME microseconds at which the kernel started (CLOCK_MONOTONIC == 0)", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = kernel_timestamp_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 "ReloadCount", + .description = "Number of successful manager reloads since startup; resets across daemon-reexec", + .type = METRIC_FAMILY_TYPE_COUNTER, + .generate = reload_count_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 "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 "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 "UnitActiveState", - .description = "Per unit metric: active state", - .type = METRIC_FAMILY_TYPE_STRING, - .generate = unit_active_state_build_json, + .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UnitsTotal", + .description = "Total number of units", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = units_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 "UserspaceTimestamp.Realtime", + .description = "CLOCK_REALTIME microseconds at which userspace was reached", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = userspace_timestamp_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 "UserspaceTimestamp.Monotonic", + .description = "CLOCK_MONOTONIC microseconds at which userspace was reached", + .type = METRIC_FAMILY_TYPE_GAUGE, + .generate = NULL, }, + /* Keep those ↑ in sync with userspace_timestamp_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 "Version", + .description = "Version of systemd", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = version_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-path.c b/src/core/varlink-path.c new file mode 100644 index 0000000000000..c6ba45a04d74a --- /dev/null +++ b/src/core/varlink-path.c @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "json-util.h" +#include "path.h" +#include "varlink-path.h" + +static int path_specs_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + PathSpec *specs = userdata; + int r; + + assert(ret); + + LIST_FOREACH(spec, k, specs) { + r = sd_json_variant_append_arraybo( + &v, + JSON_BUILD_PAIR_ENUM("type", path_type_to_string(k->type)), + SD_JSON_BUILD_PAIR_STRING("path", k->path)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +int path_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Path *p = ASSERT_PTR(PATH(userdata)); + Unit *trigger = UNIT_TRIGGER(UNIT(p)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("Paths", path_specs_build_json, p->specs), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Unit", trigger ? trigger->id : NULL), + SD_JSON_BUILD_PAIR_BOOLEAN("MakeDirectory", p->make_directory), + SD_JSON_BUILD_PAIR_UNSIGNED("DirectoryMode", p->directory_mode), + JSON_BUILD_PAIR_RATELIMIT("TriggerLimit", &p->trigger_limit)); +} + +int path_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Path *p = ASSERT_PTR(PATH(userdata)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_ENUM("Result", path_result_to_string(p->result))); +} diff --git a/src/core/varlink-path.h b/src/core/varlink-path.h new file mode 100644 index 0000000000000..a466949e37c98 --- /dev/null +++ b/src/core/varlink-path.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int path_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int path_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata); diff --git a/src/core/varlink-scope.c b/src/core/varlink-scope.c new file mode 100644 index 0000000000000..866d57db57e32 --- /dev/null +++ b/src/core/varlink-scope.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "json-util.h" +#include "scope.h" +#include "varlink-scope.h" + +int scope_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Scope *s = ASSERT_PTR(SCOPE(userdata)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_ENUM("OOMPolicy", oom_policy_to_string(s->oom_policy)), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RuntimeMaxUSec", s->runtime_max_usec), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RuntimeRandomizedExtraUSec", s->runtime_rand_extra_usec), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutStopUSec", s->timeout_stop_usec)); +} + +int scope_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Scope *s = ASSERT_PTR(SCOPE(userdata)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_ENUM("Result", scope_result_to_string(s->result))); +} diff --git a/src/core/varlink-scope.h b/src/core/varlink-scope.h new file mode 100644 index 0000000000000..0e941f873dd0f --- /dev/null +++ b/src/core/varlink-scope.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int scope_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int scope_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata); diff --git a/src/core/varlink-service.c b/src/core/varlink-service.c new file mode 100644 index 0000000000000..a2d244f6e9fbc --- /dev/null +++ b/src/core/varlink-service.c @@ -0,0 +1,187 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "exit-status.h" +#include "json-util.h" +#include "open-file.h" +#include "service.h" +#include "signal-util.h" +#include "strv.h" +#include "user-util.h" +#include "varlink-common.h" +#include "varlink-service.h" + +static int exit_status_set_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *statuses = NULL, *signals = NULL; + ExitStatusSet *set = ASSERT_PTR(userdata); + unsigned n; + int r; + + assert(ret); + + if (exit_status_set_is_empty(set)) { + *ret = NULL; + return 0; + } + + BITMAP_FOREACH(n, &set->status) { + assert(n < 256); + + r = sd_json_variant_append_arrayb(&statuses, SD_JSON_BUILD_UNSIGNED(n)); + if (r < 0) + return r; + } + + BITMAP_FOREACH(n, &set->signal) { + const char *str = signal_to_string(n); + if (!str) + continue; + + r = sd_json_variant_append_arrayb(&signals, SD_JSON_BUILD_STRING(str)); + if (r < 0) + return r; + } + + return sd_json_buildo(ret, + JSON_BUILD_PAIR_VARIANT_NON_NULL("statuses", statuses), + JSON_BUILD_PAIR_VARIANT_NON_NULL("signals", signals)); +} + +static int open_files_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + OpenFile *open_files = userdata; + int r; + + assert(ret); + + LIST_FOREACH(open_files, of, open_files) { + r = sd_json_variant_append_arraybo( + &v, + SD_JSON_BUILD_PAIR_STRING("path", of->path), + SD_JSON_BUILD_PAIR_STRING("fileDescriptorName", of->fdname), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("flags", of->flags)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +static int extra_fd_names_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + Service *s = ASSERT_PTR(userdata); + int r; + + assert(ret); + + FOREACH_ARRAY(i, s->extra_fds, s->n_extra_fds) { + r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_STRING(i->fdname)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +static int refresh_on_reload_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Service *s = ASSERT_PTR(userdata); + _cleanup_strv_free_ char **l = NULL; + int r; + + assert(ret); + + r = service_refresh_on_reload_to_strv(s->refresh_on_reload_flags, &l); + if (r < 0) + return r; + + if (strv_isempty(l)) { + *ret = NULL; + return 0; + } + + return sd_json_variant_new_array_strv(ret, l); +} + +int service_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Service *s = ASSERT_PTR(SERVICE(userdata)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_ENUM("Type", service_type_to_string(s->type)), + JSON_BUILD_PAIR_ENUM("ExitType", service_exit_type_to_string(s->exit_type)), + JSON_BUILD_PAIR_ENUM("Restart", service_restart_to_string(s->restart)), + JSON_BUILD_PAIR_ENUM("RestartMode", service_restart_mode_to_string(s->restart_mode)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("PIDFile", s->pid_file), + JSON_BUILD_PAIR_FINITE_USEC("RestartUSec", s->restart_usec), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("RestartSteps", s->restart_steps), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RestartMaxDelayUSec", s->restart_max_delay_usec), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RestartRandomizedDelayUSec", s->restart_randomized_delay_usec), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutStartUSec", s->timeout_start_usec), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutStopUSec", s->timeout_stop_usec), + JSON_BUILD_PAIR_ENUM("TimeoutStartFailureMode", service_timeout_failure_mode_to_string(s->timeout_start_failure_mode)), + JSON_BUILD_PAIR_ENUM("TimeoutStopFailureMode", service_timeout_failure_mode_to_string(s->timeout_stop_failure_mode)), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RuntimeMaxUSec", s->runtime_max_usec), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RuntimeRandomizedExtraUSec", s->runtime_rand_extra_usec), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("WatchdogUSec", s->watchdog_usec), + SD_JSON_BUILD_PAIR_BOOLEAN("RemainAfterExit", s->remain_after_exit), + SD_JSON_BUILD_PAIR_BOOLEAN("RootDirectoryStartOnly", s->root_directory_start_only), + SD_JSON_BUILD_PAIR_BOOLEAN("GuessMainPID", s->guess_main_pid), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("SuccessExitStatus", exit_status_set_build_json, &s->success_status), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("RestartPreventExitStatus", exit_status_set_build_json, &s->restart_prevent_status), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("RestartForceExitStatus", exit_status_set_build_json, &s->restart_force_status), + JSON_BUILD_PAIR_STRING_NON_EMPTY("BusName", s->bus_name), + JSON_BUILD_PAIR_ENUM("NotifyAccess", notify_access_to_string(service_get_notify_access(s))), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("FileDescriptorStoreMax", s->n_fd_store_max), + JSON_BUILD_PAIR_ENUM("FileDescriptorStorePreserve", exec_preserve_mode_to_string(s->fd_store_preserve_mode)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("USBFunctionDescriptors", s->usb_function_descriptors), + JSON_BUILD_PAIR_STRING_NON_EMPTY("USBFunctionStrings", s->usb_function_strings), + JSON_BUILD_PAIR_ENUM("OOMPolicy", oom_policy_to_string(s->oom_policy)), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("OpenFile", open_files_build_json, s->open_files), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExtraFileDescriptorNames", extra_fd_names_build_json, s), + SD_JSON_BUILD_PAIR_STRING("ReloadSignal", signal_to_string(s->reload_signal)), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("RefreshOnReload", refresh_on_reload_build_json, s), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecCondition", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_CONDITION]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStart", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_START]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStartPre", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_START_PRE]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStartPost", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_START_POST]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecReload", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_RELOAD]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecReloadPost", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_RELOAD_POST]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStop", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_STOP]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStopPost", exec_command_list_build_json, s->exec_command[SERVICE_EXEC_STOP_POST])); +} + +int service_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Unit *u = ASSERT_PTR(userdata); + Service *s = ASSERT_PTR(SERVICE(u)); + + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(&s->main_pid), "MainPID", JSON_BUILD_PIDREF(&s->main_pid)), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(&s->control_pid), "ControlPID", JSON_BUILD_PIDREF(&s->control_pid)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("StatusText", s->status_text), + JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("StatusErrno", s->status_errno), + JSON_BUILD_PAIR_STRING_NON_EMPTY("StatusBusError", s->status_bus_error), + JSON_BUILD_PAIR_STRING_NON_EMPTY("StatusVarlinkError", s->status_varlink_error), + JSON_BUILD_PAIR_ENUM("Result", service_result_to_string(s->result)), + JSON_BUILD_PAIR_ENUM("ReloadResult", service_result_to_string(s->reload_result)), + JSON_BUILD_PAIR_ENUM("CleanResult", service_result_to_string(s->clean_result)), + JSON_BUILD_PAIR_ENUM("LiveMountResult", service_result_to_string(s->live_mount_result)), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("NFileDescriptorStore", s->n_fd_store), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("NRestarts", s->n_restarts), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RestartUSecNext", service_restart_usec_next(s)), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutAbortUSec", service_timeout_abort_usec(s)), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecMain", exec_command_status_build_json, &s->main_exec_status), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecConditionStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_CONDITION]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStartPreStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_START_PRE]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStartStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_START]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStartPostStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_START_POST]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecReloadStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_RELOAD]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecReloadPostStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_RELOAD_POST]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStopStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_STOP]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStopPostStatus", exec_command_status_list_build_json, s->exec_command[SERVICE_EXEC_STOP_POST]), + 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-service.h b/src/core/varlink-service.h new file mode 100644 index 0000000000000..a64ce45803371 --- /dev/null +++ b/src/core/varlink-service.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int service_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int service_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata); diff --git a/src/core/varlink-socket.c b/src/core/varlink-socket.c new file mode 100644 index 0000000000000..91053f9890ffb --- /dev/null +++ b/src/core/varlink-socket.c @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "ip-protocol-list.h" +#include "json-util.h" +#include "socket.h" +#include "user-util.h" +#include "varlink-common.h" +#include "varlink-socket.h" + +static int socket_listen_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + Socket *s = ASSERT_PTR(SOCKET(userdata)); + int r; + + assert(ret); + + LIST_FOREACH(port, p, s->ports) { + _cleanup_free_ char *address = NULL; + + r = socket_port_to_address(p, &address); + if (r < 0) + return log_debug_errno(r, "Failed to call socket_port_to_address(): %m"); + + r = sd_json_variant_append_arraybo( + &v, + SD_JSON_BUILD_PAIR_STRING("type", socket_port_type_to_string(p)), + SD_JSON_BUILD_PAIR_STRING("address", address)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +int socket_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Socket *s = ASSERT_PTR(SOCKET(userdata)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("Listen", socket_listen_build_json, s), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SocketProtocol", ip_protocol_to_name(s->socket_protocol)), + JSON_BUILD_PAIR_ENUM("BindIPv6Only", socket_address_bind_ipv6_only_to_string(s->bind_ipv6_only)), + SD_JSON_BUILD_PAIR_UNSIGNED("Backlog", s->backlog), + JSON_BUILD_PAIR_STRING_NON_EMPTY("BindToDevice", s->bind_to_device), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SocketUser", s->user), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SocketGroup", s->group), + SD_JSON_BUILD_PAIR_UNSIGNED("SocketMode", s->socket_mode), + SD_JSON_BUILD_PAIR_UNSIGNED("DirectoryMode", s->directory_mode), + SD_JSON_BUILD_PAIR_BOOLEAN("Accept", s->accept), + SD_JSON_BUILD_PAIR_BOOLEAN("Writable", s->writable), + SD_JSON_BUILD_PAIR_BOOLEAN("FlushPending", s->flush_pending), + SD_JSON_BUILD_PAIR_UNSIGNED("MaxConnections", s->max_connections), + SD_JSON_BUILD_PAIR_UNSIGNED("MaxConnectionsPerSource", s->max_connections_per_source), + SD_JSON_BUILD_PAIR_BOOLEAN("KeepAlive", s->keep_alive), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("KeepAliveTimeUSec", s->keep_alive_time), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("KeepAliveIntervalUSec", s->keep_alive_interval), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("KeepAliveProbes", s->keep_alive_cnt), + SD_JSON_BUILD_PAIR_BOOLEAN("NoDelay", s->no_delay), + JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("Priority", s->priority), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("DeferAcceptUSec", s->defer_accept), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("ReceiveBuffer", s->receive_buffer), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("SendBuffer", s->send_buffer), + JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("IPTOS", s->ip_tos), + JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("IPTTL", s->ip_ttl), + JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("Mark", s->mark), + SD_JSON_BUILD_PAIR_BOOLEAN("ReusePort", s->reuse_port), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SmackLabel", s->smack), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SmackLabelIPIn", s->smack_ip_in), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SmackLabelIPOut", s->smack_ip_out), + SD_JSON_BUILD_PAIR_BOOLEAN("SELinuxContextFromNet", s->selinux_context_from_net), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("PipeSize", s->pipe_size), + JSON_BUILD_PAIR_INTEGER_NON_ZERO("MessageQueueMaxMessages", s->mq_maxmsg), + JSON_BUILD_PAIR_INTEGER_NON_ZERO("MessageQueueMessageSize", s->mq_msgsize), + SD_JSON_BUILD_PAIR_BOOLEAN("FreeBind", s->free_bind), + SD_JSON_BUILD_PAIR_BOOLEAN("Transparent", s->transparent), + SD_JSON_BUILD_PAIR_BOOLEAN("Broadcast", s->broadcast), + SD_JSON_BUILD_PAIR_BOOLEAN("PassCredentials", s->pass_cred), + SD_JSON_BUILD_PAIR_BOOLEAN("PassPIDFD", s->pass_pidfd), + SD_JSON_BUILD_PAIR_BOOLEAN("PassSecurity", s->pass_sec), + SD_JSON_BUILD_PAIR_BOOLEAN("PassPacketInfo", s->pass_pktinfo), + SD_JSON_BUILD_PAIR_BOOLEAN("AcceptFileDescriptors", s->pass_rights), + JSON_BUILD_PAIR_ENUM("Timestamping", socket_timestamping_to_string(s->timestamping)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("TCPCongestion", s->tcp_congestion), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStartPre", exec_command_list_build_json, s->exec_command[SOCKET_EXEC_START_PRE]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStartPost", exec_command_list_build_json, s->exec_command[SOCKET_EXEC_START_POST]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStopPre", exec_command_list_build_json, s->exec_command[SOCKET_EXEC_STOP_PRE]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecStopPost", exec_command_list_build_json, s->exec_command[SOCKET_EXEC_STOP_POST]), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutUSec", s->timeout_usec), + SD_JSON_BUILD_PAIR_BOOLEAN("RemoveOnStop", s->remove_on_stop), + JSON_BUILD_PAIR_STRV_NON_EMPTY("Symlinks", s->symlinks), + SD_JSON_BUILD_PAIR_STRING("FileDescriptorName", socket_fdname(s)), + JSON_BUILD_PAIR_RATELIMIT("TriggerLimit", &s->trigger_limit), + JSON_BUILD_PAIR_RATELIMIT("PollLimit", &s->poll_limit), + JSON_BUILD_PAIR_ENUM("DeferTrigger", socket_defer_trigger_to_string(s->defer_trigger)), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("DeferTriggerMaxUSec", s->defer_trigger_max_usec), + SD_JSON_BUILD_PAIR_BOOLEAN("PassFileDescriptorsToExec", s->pass_fds_to_exec)); +} + +int socket_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Unit *u = ASSERT_PTR(userdata); + Socket *s = ASSERT_PTR(SOCKET(u)); + + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(&s->control_pid), "ControlPID", JSON_BUILD_PIDREF(&s->control_pid)), + JSON_BUILD_PAIR_ENUM("Result", socket_result_to_string(s->result)), + JSON_BUILD_PAIR_ENUM("CleanResult", socket_result_to_string(s->clean_result)), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("NConnections", s->n_connections), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("NAccepted", s->n_accepted), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("NRefused", s->n_refused), + 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-socket.h b/src/core/varlink-socket.h new file mode 100644 index 0000000000000..911fbed4e94ef --- /dev/null +++ b/src/core/varlink-socket.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int socket_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int socket_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata); diff --git a/src/core/varlink-swap.c b/src/core/varlink-swap.c new file mode 100644 index 0000000000000..055e73a9cda40 --- /dev/null +++ b/src/core/varlink-swap.c @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "json-util.h" +#include "swap.h" +#include "user-util.h" +#include "varlink-common.h" +#include "varlink-swap.h" + +int swap_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Swap *s = ASSERT_PTR(SWAP(userdata)); + + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_STRING("What", s->what), + JSON_BUILD_PAIR_INTEGER_NON_NEGATIVE("Priority", swap_get_priority(s)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Options", swap_get_options(s)), + JSON_BUILD_PAIR_FINITE_USEC("TimeoutUSec", s->timeout_usec), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecActivate", exec_command_build_json, &s->exec_command[SWAP_EXEC_ACTIVATE]), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("ExecDeactivate", exec_command_build_json, &s->exec_command[SWAP_EXEC_DEACTIVATE])); +} + +int swap_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Unit *u = ASSERT_PTR(userdata); + Swap *s = ASSERT_PTR(SWAP(u)); + + return sd_json_buildo( + ASSERT_PTR(ret), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(&s->control_pid), "ControlPID", JSON_BUILD_PIDREF(&s->control_pid)), + JSON_BUILD_PAIR_ENUM("Result", swap_result_to_string(s->result)), + JSON_BUILD_PAIR_ENUM("CleanResult", swap_result_to_string(s->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-swap.h b/src/core/varlink-swap.h new file mode 100644 index 0000000000000..f76aa63fb1582 --- /dev/null +++ b/src/core/varlink-swap.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int swap_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int swap_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata); diff --git a/src/core/varlink-timer.c b/src/core/varlink-timer.c new file mode 100644 index 0000000000000..b2d1c330360b0 --- /dev/null +++ b/src/core/varlink-timer.c @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "calendarspec.h" +#include "json-util.h" +#include "timer.h" +#include "varlink-timer.h" + +static int timers_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + TimerValue *values = userdata; + int r; + + assert(ret); + + LIST_FOREACH(value, value, values) { + _cleanup_free_ char *base_name = NULL; + + base_name = timer_base_to_usec_string(value->base); + if (!base_name) + return -ENOMEM; + + if (value->base == TIMER_CALENDAR) { + _cleanup_free_ char *calendar = NULL; + + r = calendar_spec_to_string(value->calendar_spec, &calendar); + if (r < 0) + return log_debug_errno(r, "Failed to convert calendar spec into string: %m"); + + r = sd_json_variant_append_arraybo( + &v, + JSON_BUILD_PAIR_ENUM("base", base_name), + SD_JSON_BUILD_PAIR_STRING("calendar", calendar)); + } else + r = sd_json_variant_append_arraybo( + &v, + JSON_BUILD_PAIR_ENUM("base", base_name), + SD_JSON_BUILD_PAIR_UNSIGNED("usec", value->value)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +int timer_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Timer *t = ASSERT_PTR(TIMER(userdata)); + Unit *trigger = UNIT_TRIGGER(UNIT(t)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("Timers", timers_build_json, t->values), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Unit", trigger ? trigger->id : NULL), + SD_JSON_BUILD_PAIR_BOOLEAN("OnClockChange", t->on_clock_change), + SD_JSON_BUILD_PAIR_BOOLEAN("OnTimezoneChange", t->on_timezone_change), + JSON_BUILD_PAIR_FINITE_USEC("AccuracyUSec", t->accuracy_usec), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RandomizedDelayUSec", t->random_delay_usec), + JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO("RandomizedOffsetUSec", t->random_offset_usec), + SD_JSON_BUILD_PAIR_BOOLEAN("FixedRandomDelay", t->fixed_random_delay), + SD_JSON_BUILD_PAIR_BOOLEAN("Persistent", t->persistent), + SD_JSON_BUILD_PAIR_BOOLEAN("WakeSystem", t->wake_system), + SD_JSON_BUILD_PAIR_BOOLEAN("RemainAfterElapse", t->remain_after_elapse), + SD_JSON_BUILD_PAIR_BOOLEAN("DeferReactivation", t->defer_reactivation)); +} + +int timer_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Timer *t = ASSERT_PTR(TIMER(userdata)); + + return sd_json_buildo( + ASSERT_PTR(ret), + JSON_BUILD_PAIR_ENUM("Result", timer_result_to_string(t->result)), + JSON_BUILD_PAIR_FINITE_USEC("NextElapseUSecRealtime", t->next_elapse_realtime), + JSON_BUILD_PAIR_FINITE_USEC("NextElapseUSecMonotonic", timer_next_elapse_monotonic(t)), + JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("LastTriggerUSec", &t->last_trigger)); +} diff --git a/src/core/varlink-timer.h b/src/core/varlink-timer.h new file mode 100644 index 0000000000000..e96387a55a9be --- /dev/null +++ b/src/core/varlink-timer.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "core-forward.h" + +int timer_context_build_json(sd_json_variant **ret, const char *name, void *userdata); +int timer_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..a3efed436da42 100644 --- a/src/core/varlink-unit.c +++ b/src/core/varlink-unit.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-bus.h" #include "sd-json.h" #include "bitfield.h" @@ -10,21 +11,39 @@ #include "execute.h" #include "format-util.h" #include "install.h" +#include "iovec-util.h" +#include "job.h" #include "json-util.h" +#include "locale-util.h" #include "manager.h" #include "path-util.h" #include "pidref.h" +#include "process-util.h" #include "selinux-access.h" +#include "service.h" #include "set.h" #include "strv.h" +#include "unit-name.h" #include "unit.h" +#include "user-util.h" +#include "varlink-automount.h" #include "varlink-cgroup.h" +#include "varlink-common.h" #include "varlink-execute.h" +#include "varlink-job.h" +#include "varlink-kill.h" +#include "varlink-mount.h" +#include "varlink-path.h" +#include "varlink-scope.h" +#include "varlink-service.h" +#include "varlink-socket.h" +#include "varlink-swap.h" +#include "varlink-timer.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 +131,18 @@ 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, + [UNIT_PATH] = path_context_build_json, + [UNIT_SCOPE] = scope_context_build_json, + [UNIT_SERVICE] = service_context_build_json, + [UNIT_SOCKET] = socket_context_build_json, + [UNIT_SWAP] = swap_context_build_json, + [UNIT_TIMER] = timer_context_build_json, + }; + return sd_json_buildo( ASSERT_PTR(ret), SD_JSON_BUILD_PAIR_STRING("Type", unit_type_to_string(u->type)), @@ -149,8 +180,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 +189,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 +220,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 +247,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 +302,18 @@ 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, + [UNIT_PATH] = path_runtime_build_json, + [UNIT_SCOPE] = scope_runtime_build_json, + [UNIT_SERVICE] = service_runtime_build_json, + [UNIT_SOCKET] = socket_runtime_build_json, + [UNIT_SWAP] = swap_runtime_build_json, + [UNIT_TIMER] = timer_runtime_build_json, + }; + return sd_json_buildo( ASSERT_PTR(ret), JSON_BUILD_PAIR_STRING_NON_EMPTY("Following", f ? f->id : NULL), @@ -309,7 +343,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 +443,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 +549,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 +558,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; @@ -539,6 +576,7 @@ int varlink_unit_queue_job_one( JobMode mode, bool reload_if_possible, uint32_t *ret_job_id, + Job **ret_job, sd_bus_error *reterr_bus_error) { int r; @@ -559,6 +597,8 @@ int varlink_unit_queue_job_one( if (ret_job_id) *ret_job_id = j->id; + if (ret_job) + *ret_job = j; return 0; } @@ -570,6 +610,965 @@ int varlink_error_no_such_unit(sd_varlink *v, const char *name) { JSON_BUILD_PAIR_STRING_NON_EMPTY("parameter", name)); } +void varlink_unit_send_change_signal(Unit *u) { + assert(u); + + if (!u->varlink_unit_change) + return; + + (void) sd_varlink_notifybo( + u->varlink_unit_change, + SD_JSON_BUILD_PAIR_CALLBACK("runtime", unit_runtime_build_json, u)); +} + +void varlink_job_send_change_signal(Job *j) { + assert(j); + + if (!j->varlink || !j->varlink_notify_job_changes) + return; + + (void) sd_varlink_notifybo( + j->varlink, + SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j)); +} + +void varlink_job_send_removed_signal(Job *j) { + assert(j); + + if (!j->varlink) + return; + + /* Send the final reply, which completes the method call */ + (void) sd_varlink_replybo( + j->varlink, + SD_JSON_BUILD_PAIR_CALLBACK("context", unit_context_build_json, j->unit), + SD_JSON_BUILD_PAIR_CALLBACK("runtime", unit_runtime_build_json, j->unit), + SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j)); + + j->varlink = sd_varlink_unref(j->varlink); + j->unit->varlink_unit_change = sd_varlink_unref(j->unit->varlink_unit_change); +} + +typedef struct TransientExecCommandItem { + const char *path; + char **arguments; +} TransientExecCommandItem; + +static void transient_exec_command_item_done(TransientExecCommandItem *i) { + assert(i); + strv_free(i->arguments); +} + +static JSON_DISPATCH_ENUM_DEFINE(dispatch_service_type, ServiceType, service_type_from_string); +static JSON_DISPATCH_ENUM_DEFINE(dispatch_job_mode, JobMode, job_mode_from_string); + +typedef struct TransientWorkingDirectory { + const char *path; + bool home; + bool missing_ok; +} TransientWorkingDirectory; + +typedef struct TransientSetCredential { + const char *id; + struct iovec value; +} TransientSetCredential; + +typedef struct TransientExecContextParameters { + bool present; + bool working_directory_set; + TransientWorkingDirectory working_directory; + bool environment_set; + char **environment; + + bool set_credentials_set; + TransientSetCredential *set_credentials; + size_t n_set_credentials; + + bool set_credentials_encrypted_set; + TransientSetCredential *set_credentials_encrypted; + size_t n_set_credentials_encrypted; + + const char *user; + const char *group; + char **supplementary_groups; + bool nice_set; + int nice; + bool oom_score_adjust_set; + int oom_score_adjust; + bool umask_set; + uint32_t umask; + /* Tristate bools: -1 = absent, 0/1 = no/yes. */ + int dynamic_user; + int ignore_sigpipe; + int lock_personality; + int memory_deny_write_execute; + int no_new_privileges; + int remove_ipc; + int restrict_realtime; + int restrict_suid_sgid; + int root_ephemeral; +} TransientExecContextParameters; + +static void transient_set_credential_array_free(TransientSetCredential *items, size_t n) { + FOREACH_ARRAY(item, items, n) + iovec_done_erase(&item->value); + free(items); +} + +static void transient_exec_context_parameters_done(TransientExecContextParameters *p) { + assert(p); + strv_free(p->environment); + strv_free(p->supplementary_groups); + transient_set_credential_array_free(p->set_credentials, p->n_set_credentials); + transient_set_credential_array_free(p->set_credentials_encrypted, p->n_set_credentials_encrypted); +} + +typedef struct TransientServiceParameters { + bool present; + ServiceType type; + TransientExecCommandItem *exec_start; + size_t n_exec_start; + int remain_after_exit; +} TransientServiceParameters; + +static void transient_service_parameters_done(TransientServiceParameters *p) { + assert(p); + FOREACH_ARRAY(i, p->exec_start, p->n_exec_start) + transient_exec_command_item_done(i); + free(p->exec_start); +} + +static void transient_service_parameters_init(TransientServiceParameters *p) { + assert(p); + *p = (TransientServiceParameters) { + .type = _SERVICE_TYPE_INVALID, + .remain_after_exit = -1, + }; +} + +static int dispatch_transient_exec_command(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field exec_command_dispatch[] = { + { "path", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(TransientExecCommandItem, path), SD_JSON_MANDATORY }, + { "arguments", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(TransientExecCommandItem, arguments), 0 }, + {} + }; + + TransientServiceParameters *p = ASSERT_PTR(userdata); + size_t n; + int r; + + if (!sd_json_variant_is_array(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Expected JSON array for ExecStart."); + + n = sd_json_variant_elements(variant); + if (n == 0) + return 0; + + p->exec_start = new0(TransientExecCommandItem, n); + if (!p->exec_start) + return -ENOMEM; + p->n_exec_start = n; + + for (size_t i = 0; i < n; i++) { + sd_json_variant *element = sd_json_variant_by_index(variant, i); + + r = sd_json_dispatch(element, exec_command_dispatch, /* flags= */ 0, &p->exec_start[i]); + if (r < 0) + return r; + } + return 0; +} + +typedef struct StartTransientContextParameters { + const char *id; + const char *description; + TransientExecContextParameters exec; + TransientServiceParameters service; + const char *bad_exec_field; /* Set by inner Exec dispatcher to the unknown sub-property name */ + const char *bad_service_field; +} StartTransientContextParameters; + +static void start_transient_context_parameters_done(StartTransientContextParameters *p) { + assert(p); + transient_exec_context_parameters_done(&p->exec); + transient_service_parameters_done(&p->service); +} + +static void transient_exec_context_parameters_init(TransientExecContextParameters *p); + +static void start_transient_context_parameters_init(StartTransientContextParameters *p) { + assert(p); + *p = (StartTransientContextParameters) {}; + transient_exec_context_parameters_init(&p->exec); + transient_service_parameters_init(&p->service); +} + +typedef struct StartTransientParameters { + StartTransientContextParameters context; + JobMode mode; + int notify_job_changes; + int notify_unit_changes; + char *unsupported_property; /* For error reporting on unknown context fields */ +} StartTransientParameters; + +static void start_transient_parameters_done(StartTransientParameters *p) { + assert(p); + start_transient_context_parameters_done(&p->context); + free(p->unsupported_property); +} + +static void start_transient_parameters_init(StartTransientParameters *p) { + assert(p); + *p = (StartTransientParameters) { + .mode = JOB_REPLACE, + .notify_job_changes = -1, + .notify_unit_changes = -1, + }; + start_transient_context_parameters_init(&p->context); +} + +static int dispatch_const_string_empty_as_null(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + const char **s = ASSERT_PTR(userdata); + int r; + + r = sd_json_dispatch_const_string(name, variant, flags, s); + if (r >= 0 && isempty(*s)) + *s = NULL; + return r; +} + +static int dispatch_transient_working_directory(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + /* No equivalent D-Bus properties, so use varlink camelCase */ + static const sd_json_dispatch_field dispatch[] = { + { "path", SD_JSON_VARIANT_STRING, dispatch_const_string_empty_as_null, offsetof(TransientWorkingDirectory, path), 0 }, + { "home", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(TransientWorkingDirectory, home), 0 }, + { "missingOK", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(TransientWorkingDirectory, missing_ok), 0 }, + {} + }; + + TransientExecContextParameters *p = ASSERT_PTR(userdata); + p->working_directory_set = true; + return sd_json_dispatch(variant, dispatch, flags, &p->working_directory); +} + +/* Generate a parse wrapper that flips TransientExecContextParameters._set and delegates the + * actual value parse to the named primitive dispatcher. For fields where the JSON value type alone + * cannot distinguish "absent" from "explicitly set to the default" (int 0, empty strv, etc.). */ +#define DEFINE_TRANSIENT_EXEC_SETTABLE(field, primitive_dispatch) \ + static int dispatch_transient_##field( \ + const char *name, \ + sd_json_variant *variant, \ + sd_json_dispatch_flags_t flags, \ + void *userdata) { \ + TransientExecContextParameters *p = ASSERT_PTR(userdata); \ + p->field##_set = true; \ + return primitive_dispatch(name, variant, flags, &p->field); \ + } + +DEFINE_TRANSIENT_EXEC_SETTABLE(environment, sd_json_dispatch_strv); +DEFINE_TRANSIENT_EXEC_SETTABLE(nice, sd_json_dispatch_int32); +DEFINE_TRANSIENT_EXEC_SETTABLE(oom_score_adjust, sd_json_dispatch_int32); +DEFINE_TRANSIENT_EXEC_SETTABLE(umask, sd_json_dispatch_uint32); + +static int dispatch_transient_set_credential_array( + sd_json_variant *variant, + TransientSetCredential **ret_items, + size_t *ret_n) { + + static const sd_json_dispatch_field item_dispatch[] = { + { "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(TransientSetCredential, id), SD_JSON_MANDATORY }, + { "value", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(TransientSetCredential, value), SD_JSON_MANDATORY }, + {} + }; + + TransientSetCredential *items = NULL; + size_t n = 0; + int r; + + assert(ret_items); + assert(ret_n); + + CLEANUP_ARRAY(items, n, transient_set_credential_array_free); + + size_t n_items = sd_json_variant_elements(variant); + if (n_items == 0) { + *ret_items = NULL; + *ret_n = 0; + return 0; + } + + items = new0(TransientSetCredential, n_items); + if (!items) + return -ENOMEM; + + for (; n < n_items; n++) { + r = sd_json_dispatch(sd_json_variant_by_index(variant, n), item_dispatch, /* flags= */ 0, &items[n]); + if (r < 0) + return r; + } + + *ret_n = n; + *ret_items = TAKE_PTR(items); + return 0; +} + +static int dispatch_transient_set_credential(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + TransientExecContextParameters *p = ASSERT_PTR(userdata); + p->set_credentials_set = true; + return dispatch_transient_set_credential_array(variant, &p->set_credentials, &p->n_set_credentials); +} + +static int dispatch_transient_set_credential_encrypted(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + TransientExecContextParameters *p = ASSERT_PTR(userdata); + p->set_credentials_encrypted_set = true; + return dispatch_transient_set_credential_array(variant, &p->set_credentials_encrypted, &p->n_set_credentials_encrypted); +} + +static int dispatch_transient_exec_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); + +static int dispatch_transient_service(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field service_dispatch[] = { + { "Type", SD_JSON_VARIANT_STRING, dispatch_service_type, offsetof(TransientServiceParameters, type), 0 }, + { "ExecStart", SD_JSON_VARIANT_ARRAY, dispatch_transient_exec_command, 0, 0 }, + { "RemainAfterExit", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(TransientServiceParameters, remain_after_exit), 0 }, + {} + }; + + StartTransientContextParameters *p = ASSERT_PTR(userdata); + p->service.present = true; + return sd_json_dispatch_full(variant, service_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->service, &p->bad_service_field); +} + +static int dispatch_transient_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field context_dispatch[] = { + { "ID", SD_JSON_VARIANT_STRING, json_dispatch_const_unit_name, offsetof(StartTransientContextParameters, id), SD_JSON_MANDATORY }, + { "Description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(StartTransientContextParameters, description), 0 }, + { "Exec", SD_JSON_VARIANT_OBJECT, dispatch_transient_exec_context, 0, 0 }, + { "Service", SD_JSON_VARIANT_OBJECT, dispatch_transient_service, 0, 0 }, + {} + }; + + StartTransientParameters *p = ASSERT_PTR(userdata); + const char *bad_field = NULL; + int r; + + /* Don't propagate the caller's flags (in particular SD_JSON_MANDATORY from the outer 'context' + * field) into the nested dispatch, otherwise every inner field becomes mandatory. */ + r = sd_json_dispatch_full(variant, context_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->context, &bad_field); + if (r == -EADDRNOTAVAIL && !isempty(bad_field)) { + /* A UnitContext field that exists in the schema but is not settable at creation time: stash + * the name so the caller can map this to io.systemd.Unit.PropertyNotSupported. If the + * unknown field lives inside the nested Exec object, compose a dotted name to identify the + * actual sub-property. */ + if (streq(bad_field, "Exec") && !isempty(p->context.bad_exec_field)) + p->unsupported_property = strjoin("Exec.", p->context.bad_exec_field); + else if (streq(bad_field, "Service") && !isempty(p->context.bad_service_field)) + p->unsupported_property = strjoin("Service.", p->context.bad_service_field); + else + p->unsupported_property = strdup(bad_field); + if (!p->unsupported_property) + return -ENOMEM; + } + return r; +} + +static int transient_unit_apply_properties(Unit *u, StartTransientContextParameters *p) { + int r; + + assert(u); + assert(p); + + if (p->description) { + r = unit_set_description(u, p->description); + if (r < 0) + return r; + unit_write_settingf(u, UNIT_RUNTIME|UNIT_ESCAPE_SPECIFIERS, "Description", "Description=%s", p->description); + } + + return 0; +} + +static int transient_apply_set_credentials( + Unit *u, + ExecContext *c, + const TransientSetCredential *items, + size_t n_items, + bool encrypted) { + + int r; + + assert(u); + assert(c); + + FOREACH_ARRAY(item, items, n_items) { + const char *err = NULL; + + r = exec_context_apply_set_credential(u, c, item->id, item->value.iov_base, item->value.iov_len, + encrypted, UNIT_RUNTIME|UNIT_PRIVATE, &err); + if (r == -EINVAL) + return log_debug_errno(r, "%s: %s", err, item->id); + if (r < 0) + return r; + } + + return 0; +} + +static int apply_exec_environment(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + int r; + + assert(p); + + if (!p->environment_set) + return 0; + + r = exec_context_apply_environment(u, c, p->environment, UNIT_RUNTIME|UNIT_PRIVATE); + if (IN_SET(r, -E2BIG, -EINVAL)) + /* Convert E2BIG into EINVAL so the central loop maps it to Exec.Environment. */ + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + r == -E2BIG ? "Too many environment assignments." : "Invalid Environment list."); + return r; +} + +static int apply_exec_nice(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + assert(p); + + if (!p->nice_set) + return 0; + + if (!nice_is_valid(p->nice)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid Nice= value: %i", p->nice); + + c->nice = p->nice; + c->nice_set = true; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "Nice", "Nice=%i", p->nice); + return 0; +} + +static int apply_exec_oom_score_adjust(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + if (!p->oom_score_adjust_set) + return 0; + + if (!oom_score_adjust_is_valid(p->oom_score_adjust)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid OOMScoreAdjust= value: %i", p->oom_score_adjust); + + c->oom_score_adjust = p->oom_score_adjust; + c->oom_score_adjust_set = true; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "OOMScoreAdjust", + "OOMScoreAdjust=%i", p->oom_score_adjust); + return 0; +} + +static int apply_exec_set_credential(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + assert(p); + + if (!p->set_credentials_set) + return 0; + return transient_apply_set_credentials(u, c, p->set_credentials, p->n_set_credentials, /* encrypted= */ false); +} + +static int apply_exec_set_credential_encrypted(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + assert(p); + + if (!p->set_credentials_encrypted_set) + return 0; + return transient_apply_set_credentials(u, c, p->set_credentials_encrypted, p->n_set_credentials_encrypted, /* encrypted= */ true); +} + +static int apply_exec_supplementary_groups(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + _cleanup_free_ char *joined = NULL; + int r; + + assert(p); + + if (!p->supplementary_groups) + return 0; + + STRV_FOREACH(g, p->supplementary_groups) + if (!valid_user_group_name(*g, VALID_USER_ALLOW_NUMERIC|VALID_USER_RELAX)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid supplementary group name: %s", *g); + + r = strv_extend_strv(&c->supplementary_groups, p->supplementary_groups, /* filter_duplicates= */ true); + if (r < 0) + return r; + + joined = strv_join(p->supplementary_groups, " "); + if (!joined) + return -ENOMEM; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "SupplementaryGroups", + "SupplementaryGroups=%s", joined); + return 0; +} + +static int apply_exec_umask(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + if (!p->umask_set) + return 0; + + /* mode_t bits beyond 07777 are reserved/meaningless; reject so a caller passing a stray + * negative or out-of-range int fails clearly instead of having the kernel silently mask it. */ + if (p->umask > 07777) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid UMask= value: %#" PRIo32, p->umask); + + c->umask = (mode_t) p->umask; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "UMask", "UMask=%04" PRIo32, p->umask); + return 0; +} + +static int apply_exec_working_directory(Unit *u, ExecContext *c, TransientExecContextParameters *p) { + _cleanup_free_ char *simplified = NULL; + TransientWorkingDirectory *wd = &p->working_directory; + int r; + + assert(p); + + if (!p->working_directory_set) + return 0; + + if (wd->home && wd->path) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "WorkingDirectory: 'home' and 'path' are mutually exclusive"); + if (!wd->home && !wd->path) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "WorkingDirectory: must specify either 'home' or 'path'"); + + if (!wd->home) { + if (!path_is_absolute(wd->path)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "WorkingDirectory: expects an absolute path"); + r = path_simplify_alloc(wd->path, &simplified); + if (r < 0) + return r; + if (!path_is_normalized(simplified)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "WorkingDirectory: expects a normalized path"); + } + + free_and_replace(c->working_directory, simplified); + c->working_directory_home = wd->home; + c->working_directory_missing_ok = wd->missing_ok; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, "WorkingDirectory", + "WorkingDirectory=%s%s", + c->working_directory_missing_ok ? "-" : "", + c->working_directory_home ? "~" : strempty(c->working_directory)); + return 0; +} + +/* Generate an apply fn for an exec-context string field: copy the parsed value to ExecContext and + * write the corresponding "Name=value" line, skipping if the caller didn't set the field. + * Used where the parsed value is NULL or already validated by the dispatch callback. */ +#define DEFINE_APPLY_EXEC_STRING(field, JsonName) \ + static int apply_exec_##field(Unit *u, ExecContext *c, TransientExecContextParameters *p) { \ + int r; \ + \ + assert(p); \ + \ + if (!p->field) \ + return 0; \ + \ + r = free_and_strdup(&c->field, p->field); \ + if (r < 0) \ + return r; \ + \ + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE|UNIT_ESCAPE_SPECIFIERS, JsonName, \ + JsonName "=%s", p->field); \ + return 0; \ + } + +DEFINE_APPLY_EXEC_STRING(user, "User"); +DEFINE_APPLY_EXEC_STRING(group, "Group"); + +/* Generate an apply fn for an exec-context tristate bool: copy the parsed value to ExecContext and + * write the corresponding "Name=yes/no" line, skipping if the caller didn't set the field. + * Used for the sandbox bool batch where parse and apply are entirely mechanical. */ +#define DEFINE_APPLY_EXEC_TRISTATE_BOOL(field, JsonName) \ + static int apply_exec_##field(Unit *u, ExecContext *c, TransientExecContextParameters *p) { \ + assert(c); \ + assert(p); \ + if (p->field < 0) \ + return 0; \ + c->field = p->field; \ + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, JsonName, \ + JsonName "=%s", yes_no(p->field)); \ + return 0; \ + } + +DEFINE_APPLY_EXEC_TRISTATE_BOOL(dynamic_user, "DynamicUser"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(ignore_sigpipe, "IgnoreSIGPIPE"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(lock_personality, "LockPersonality"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(memory_deny_write_execute, "MemoryDenyWriteExecute"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(no_new_privileges, "NoNewPrivileges"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(remove_ipc, "RemoveIPC"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(restrict_realtime, "RestrictRealtime"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(restrict_suid_sgid, "RestrictSUIDSGID"); +DEFINE_APPLY_EXEC_TRISTATE_BOOL(root_ephemeral, "RootEphemeral"); + +/* Per-property descriptor for fields of the Exec context. + * + * json_name - JSON key inside the Exec object (e.g. "Nice"). + * err_field - Qualified field name used for varlink error replies (e.g. "Exec.Nice"). + * json_type - Expected JSON type for sd_json_dispatch_full(). + * dispatch - Parses the JSON value into TransientExecContextParameters. + * parse_offset - offsetof() into TransientExecContextParameters; 0 if dispatch writes via &p directly. + * json_flags - Per-field flags forwarded to sd_json_dispatch_full(). + * apply - Writes the parsed value to ExecContext. Returns 0 on success (whether or not + * the property was set), <0 on error. -EINVAL is mapped to err_field by the + * central loop. + * tristate - If true, the int at parse_offset is initialized to -1 (absent sentinel) by + * transient_exec_context_parameters_init(). Defaults to false. + */ +typedef struct TransientExecProperty { + const char *json_name; + const char *err_field; + sd_json_variant_type_t json_type; + sd_json_dispatch_callback_t dispatch; + size_t parse_offset; + sd_json_dispatch_flags_t json_flags; + int (*apply)(Unit *u, ExecContext *c, TransientExecContextParameters *p); + bool tristate; +} TransientExecProperty; + +/* Property descriptors for the Exec object. Ordered to match src/shared/varlink-io.systemd.Unit.c so related + * fields (e.g. User/Group) stay close. JSON keys match the D-Bus property names. */ +#define EXEC_PROPERTY(json, type, dispatch_fn, parse_offset, json_flags, apply_fn) \ + { json, "Exec." json, type, dispatch_fn, parse_offset, json_flags, apply_fn } + +/* Tristate-bool property: parsed via sd_json_dispatch_tristate into an int (-1 = absent). + * Sets .tristate=true so transient_exec_context_parameters_init() seeds the int to -1 before + * dispatch. */ +#define EXEC_PROPERTY_TRISTATE_BOOL(json, field) \ + { json, "Exec." json, SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, \ + offsetof(TransientExecContextParameters, field), 0, apply_exec_##field, true } + +/* String property: dispatched into a const char* field and applied via the matching + * DEFINE_APPLY_EXEC_STRING() function. dispatch_fn/json_flags vary per field (e.g. User/Group use + * json_dispatch_const_user_group_name with SD_JSON_RELAX). */ +#define EXEC_PROPERTY_STRING(json, field, dispatch_fn, json_flags) \ + { json, "Exec." json, SD_JSON_VARIANT_STRING, dispatch_fn, \ + offsetof(TransientExecContextParameters, field), json_flags, apply_exec_##field } + +static const TransientExecProperty exec_properties[] = { + EXEC_PROPERTY ("WorkingDirectory", SD_JSON_VARIANT_OBJECT, dispatch_transient_working_directory, 0, 0, apply_exec_working_directory), + EXEC_PROPERTY_TRISTATE_BOOL("RootEphemeral", root_ephemeral), + EXEC_PROPERTY_STRING ("User", user, json_dispatch_const_user_group_name, SD_JSON_RELAX), + EXEC_PROPERTY_STRING ("Group", group, json_dispatch_const_user_group_name, SD_JSON_RELAX), + EXEC_PROPERTY_TRISTATE_BOOL("DynamicUser", dynamic_user), + EXEC_PROPERTY ("SupplementaryGroups", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(TransientExecContextParameters, supplementary_groups), 0, apply_exec_supplementary_groups), + EXEC_PROPERTY_TRISTATE_BOOL("NoNewPrivileges", no_new_privileges), + EXEC_PROPERTY ("UMask", SD_JSON_VARIANT_INTEGER, dispatch_transient_umask, 0, 0, apply_exec_umask), + EXEC_PROPERTY ("OOMScoreAdjust", SD_JSON_VARIANT_INTEGER, dispatch_transient_oom_score_adjust, 0, 0, apply_exec_oom_score_adjust), + EXEC_PROPERTY_TRISTATE_BOOL("IgnoreSIGPIPE", ignore_sigpipe), + EXEC_PROPERTY ("Nice", SD_JSON_VARIANT_INTEGER, dispatch_transient_nice, 0, 0, apply_exec_nice), + EXEC_PROPERTY_TRISTATE_BOOL("LockPersonality", lock_personality), + EXEC_PROPERTY_TRISTATE_BOOL("MemoryDenyWriteExecute", memory_deny_write_execute), + EXEC_PROPERTY_TRISTATE_BOOL("RestrictRealtime", restrict_realtime), + EXEC_PROPERTY_TRISTATE_BOOL("RestrictSUIDSGID", restrict_suid_sgid), + EXEC_PROPERTY_TRISTATE_BOOL("RemoveIPC", remove_ipc), + EXEC_PROPERTY ("Environment", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_transient_environment, 0, 0, apply_exec_environment), + EXEC_PROPERTY ("SetCredential", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential, 0, 0, apply_exec_set_credential), + EXEC_PROPERTY ("SetCredentialEncrypted", SD_JSON_VARIANT_ARRAY, dispatch_transient_set_credential_encrypted, 0, 0, apply_exec_set_credential_encrypted), +}; +#undef EXEC_PROPERTY +#undef EXEC_PROPERTY_TRISTATE_BOOL +#undef EXEC_PROPERTY_STRING + +/* Tristate-bool fields default to -1 (= absent). Default-zeroed slots would look "explicitly false" + * to the apply path. Initialize all tristate fields here so adding a new tristate property only + * requires a new exec_properties[] row, never a fresh designated initializer at the call site. + * The .tristate descriptor flag drives the loop -- mirrors the .cleanup hook used on the Service + * side, and avoids fragile dispatch-function-pointer equality checks. */ +static void transient_exec_context_parameters_init(TransientExecContextParameters *p) { + assert(p); + FOREACH_ELEMENT(prop, exec_properties) + if (prop->tristate) + *(int*) ((uint8_t*) p + prop->parse_offset) = -1; +} + +static int dispatch_transient_exec_context(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + /* Build dispatch table only once, its constant. */ + static sd_json_dispatch_field exec_dispatch[ELEMENTSOF(exec_properties) + 1] = {}; + static bool exec_dispatch_set = false; + + StartTransientContextParameters *p = ASSERT_PTR(userdata); + + if (!exec_dispatch_set) { + FOREACH_ELEMENT(prop, exec_properties) + exec_dispatch[prop - exec_properties] = (sd_json_dispatch_field) { + .name = prop->json_name, + .type = prop->json_type, + .callback = prop->dispatch, + .offset = prop->parse_offset, + .flags = prop->json_flags, + }; + exec_dispatch_set = true; + } + + p->exec.present = true; + return sd_json_dispatch_full(variant, exec_dispatch, /* bad= */ NULL, /* flags= */ 0, &p->exec, &p->bad_exec_field); +} + +static int transient_exec_context_apply_properties(Unit *u, ExecContext *c, TransientExecContextParameters *p, const char **reterr_field) { + int r; + + assert(u); + assert(c); + assert(p); + + FOREACH_ELEMENT(prop, exec_properties) { + r = prop->apply(u, c, p); + if (r < 0) { + if (reterr_field) + *reterr_field = r == -EINVAL ? prop->err_field : NULL; + return r; + } + } + + return 0; +} + +static int transient_service_apply_properties(Service *s, TransientServiceParameters *sp, const char **reterr_field) { + Unit *u = UNIT(ASSERT_PTR(s)); + int r; + + assert(sp); + + if (sp->type >= 0) { + s->type = sp->type; + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "Type", "Type=%s", service_type_to_string(sp->type)); + } + + if (sp->remain_after_exit >= 0) { + s->remain_after_exit = sp->remain_after_exit; + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "RemainAfterExit", "RemainAfterExit=%s", yes_no(sp->remain_after_exit)); + } + + FOREACH_ARRAY(item, sp->exec_start, sp->n_exec_start) { + _cleanup_(exec_command_freep) ExecCommand *c = NULL; + _cleanup_strv_free_ char **argv = NULL; + + if (!filename_or_absolute_path_is_valid(item->path)) { + if (reterr_field) + *reterr_field = "Service.ExecStart"; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ExecStart path: %s", item->path); + } + + if (!strv_isempty(item->arguments)) { + argv = strv_copy(item->arguments); + if (!argv) + return -ENOMEM; + } + + c = new0(ExecCommand, 1); + if (!c) + return -ENOMEM; + + r = path_simplify_alloc(item->path, &c->path); + if (r < 0) + return r; + + /* If no arguments were provided, default argv[0] to the executable path. + * Otherwise the caller is expected to include argv[0] in the arguments array. */ + if (strv_isempty(argv)) { + r = strv_extend(&argv, c->path); + if (r < 0) + return r; + } + + c->argv = TAKE_PTR(argv); + + exec_command_append_list(&s->exec_command[SERVICE_EXEC_START], TAKE_PTR(c)); + } + + /* Write ExecStart= lines to the transient file */ + if (sp->n_exec_start > 0) { + UnitWriteFlags esc_flags = UNIT_ESCAPE_SPECIFIERS|UNIT_ESCAPE_EXEC_SYNTAX_ENV; + + LIST_FOREACH(command, c, s->exec_command[SERVICE_EXEC_START]) { + _cleanup_free_ char *a = NULL; + + a = unit_concat_strv(c->argv, esc_flags); + if (!a) + return -ENOMEM; + + /* streq() instead path_equal() as argv[0] can be arbitrary and may not be a path */ + if (streq(c->path, c->argv[0])) + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "ExecStart", "ExecStart=%s", a); + else { + _cleanup_free_ char *t = NULL; + const char *p; + + p = unit_escape_setting(c->path, esc_flags, &t); + if (!p) + return -ENOMEM; + + unit_write_settingf(u, UNIT_RUNTIME|UNIT_PRIVATE, "ExecStart", "ExecStart=@%s %s", p, a); + } + } + } + + return 0; +} + +int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "context", SD_JSON_VARIANT_OBJECT, dispatch_transient_context, 0, SD_JSON_MANDATORY }, + { "mode", SD_JSON_VARIANT_STRING, dispatch_job_mode, offsetof(StartTransientParameters, mode), 0 }, + { "notifyJobChanges", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(StartTransientParameters, notify_job_changes), 0 }, + { "notifyUnitChanges", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(StartTransientParameters, notify_unit_changes), 0 }, + {} + }; + + _cleanup_(sd_bus_error_free) sd_bus_error bus_error = SD_BUS_ERROR_NULL; + _cleanup_(start_transient_parameters_done) StartTransientParameters p = {}; + Manager *manager = ASSERT_PTR(userdata); + const char *bad_field = NULL; + Unit *u; + int r; + + assert(link); + assert(parameters); + + start_transient_parameters_init(&p); + + r = mac_selinux_access_check_varlink(link, "start"); + if (r < 0) + return r; + + r = sd_json_dispatch_full(parameters, dispatch_table, /* bad= */ NULL, /* flags= */ 0, &p, &bad_field); + if (r < 0) { + /* An unknown field in 'context' maps to PropertyNotSupported (the field is defined in the + * UnitContext schema but cannot be set at creation time). Anything else is a bad parameter. */ + if (streq_ptr(bad_field, "context") && r == -EADDRNOTAVAIL && p.unsupported_property) + return sd_varlink_errorbo( + link, + "io.systemd.Unit.PropertyNotSupported", + SD_JSON_BUILD_PAIR_STRING("property", p.unsupported_property)); + if (bad_field) + return sd_varlink_error_invalid_parameter_name(link, bad_field); + return r; + } + + /* Pre-check unit type early and return targeted varlink error as manager_setup_transient_unit() the + * too generic SD_BUS_ERROR_INVALID_ARGS. */ + UnitType t = unit_name_to_type(p.context.id); + if (t < 0) + return sd_varlink_error_invalid_parameter_name(link, "context"); + if (!unit_vtable[t]->can_transient) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL); + + r = varlink_verify_polkit_async( + link, + manager->system_bus, + "org.freedesktop.systemd1.manage-units", + (const char**) STRV_MAKE( + "unit", p.context.id, + "verb", "start", + "polkit.message", N_("Authentication is required to start transient unit '$(unit)'."), + "polkit.gettext_domain", GETTEXT_PACKAGE), + &manager->polkit_registry); + if (r <= 0) + return r; + + r = manager_setup_transient_unit(manager, p.context.id, &u, &bus_error); + if (r < 0) + return varlink_reply_bus_error(link, r, &bus_error); + + /* Apply unit-level properties from context */ + r = transient_unit_apply_properties(u, &p.context); + if (r == -EINVAL) + return sd_varlink_error_invalid_parameter_name(link, "context"); + if (r < 0) + return sd_varlink_error_errno(link, r); + + /* Apply context.Exec only when an Exec object was actually sent. The tristate bool fields are + * seeded to -1 ("absent") during Exec dispatch; without an Exec object they stay 0, which the + * apply functions would misread as "explicitly false" and write out bogus FooBar=no lines. */ + if (p.context.exec.present) { + ExecContext *c = unit_get_exec_context(u); + if (!c) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL); + + bad_field = NULL; + r = transient_exec_context_apply_properties(u, c, &p.context.exec, &bad_field); + if (r == -EINVAL) + return sd_varlink_error_invalid_parameter_name(link, bad_field ?: "Exec"); + if (r < 0) + return sd_varlink_error_errno(link, r); + } + + /* Apply service-specific properties from context.Service */ + Service *s = SERVICE(u); + if (s) { + bad_field = NULL; + r = transient_service_apply_properties(s, &p.context.service, &bad_field); + if (r == -EINVAL) + return sd_varlink_error_invalid_parameter_name(link, bad_field ?: "Service"); + if (r < 0) + return sd_varlink_error_errno(link, r); + } else if (p.context.service.present) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED, NULL); + + unit_add_to_load_queue(u); + manager_dispatch_load_queue(manager); + + if (u->load_state == UNIT_BAD_SETTING) + return sd_varlink_error_invalid_parameter_name(link, "context"); + if (!UNIT_IS_LOAD_COMPLETE(u->load_state)) + return sd_varlink_error(link, VARLINK_ERROR_UNIT_NO_SUCH_UNIT, NULL); + + Job *j; + r = varlink_unit_queue_job_one( + u, + JOB_START, + p.mode, + /* reload_if_possible= */ false, + /* ret_job_id= */ NULL, + &j, + &bus_error); + if (r < 0) + return varlink_reply_bus_error(link, r, &bus_error); + + bool notify_job = p.notify_job_changes > 0; + bool notify_unit = p.notify_unit_changes > 0; + + /* Non-streaming, or fire-and-forget (no notification flags set): return full unit context + * and runtime, plus the job object so the caller can correlate with later state. */ + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE) || (!notify_job && !notify_unit)) + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_CALLBACK("context", unit_context_build_json, u), + SD_JSON_BUILD_PAIR_CALLBACK("runtime", unit_runtime_build_json, u), + SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j)); + + /* Streaming: always attach to the job for the final reply, and optionally to the unit for state + * change notifications. j->varlink owns the stream lifetime, u->varlink_unit_change is just a flag + * to also send unit state notifications along the way. */ + assert(!j->varlink); + j->varlink = sd_varlink_ref(link); + j->varlink_notify_job_changes = notify_job; + if (notify_unit) { + assert(!u->varlink_unit_change); + u->varlink_unit_change = sd_varlink_ref(link); + } + + /* Send initial job state notification if requested. Unit state change notifications are not sent + * here; they will arrive via varlink_unit_send_change_signal() when the unit actually transitions, + * matching D-Bus PropertiesChanged behavior. */ + if (notify_job) + return sd_varlink_notifybo( + link, + SD_JSON_BUILD_PAIR_CALLBACK("job", job_build_json, j)); + + return 0; +} + typedef struct UnitSetPropertiesParameters { const char *unsupported_property; /* For error reporting */ const char *name; diff --git a/src/core/varlink-unit.h b/src/core/varlink-unit.h index 8f58d7ce10378..9460245ec0a77 100644 --- a/src/core/varlink-unit.h +++ b/src/core/varlink-unit.h @@ -6,6 +6,9 @@ #define VARLINK_ERROR_UNIT_NO_SUCH_UNIT "io.systemd.Unit.NoSuchUnit" #define VARLINK_ERROR_UNIT_ONLY_BY_DEPENDENCY "io.systemd.Unit.OnlyByDependency" #define VARLINK_ERROR_UNIT_DBUS_SHUTTING_DOWN "io.systemd.Unit.DBusShuttingDown" +#define VARLINK_ERROR_UNIT_UNIT_EXISTS "io.systemd.Unit.UnitExists" +#define VARLINK_ERROR_UNIT_TYPE_NOT_SUPPORTED "io.systemd.Unit.UnitTypeNotSupported" +#define VARLINK_ERROR_UNIT_BAD_SETTING "io.systemd.Unit.BadUnitSetting" int vl_method_list_units(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); @@ -15,8 +18,15 @@ int varlink_unit_queue_job_one( JobMode mode, bool reload_if_possible, uint32_t *ret_job_id, + Job **ret_job, sd_bus_error *reterr_bus_error); int vl_method_set_unit_properties(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_start_transient_unit(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); + +void varlink_unit_send_change_signal(Unit *u); +void varlink_job_send_change_signal(Job *j); +void varlink_job_send_removed_signal(Job *j); + int varlink_error_no_such_unit(sd_varlink *v, const char *name); diff --git a/src/core/varlink.c b/src/core/varlink.c index ec4f8abad95ae..d30cb7cab3358 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -4,19 +4,24 @@ #include "constants.h" #include "errno-util.h" +#include "job.h" +#include "json-util.h" #include "manager.h" #include "metrics.h" #include "path-util.h" #include "pidref.h" #include "string-util.h" +#include "strv.h" #include "unit.h" #include "varlink.h" #include "varlink-dynamic-user.h" +#include "varlink-io.systemd.Job.h" #include "varlink-io.systemd.ManagedOOM.h" #include "varlink-io.systemd.Manager.h" #include "varlink-io.systemd.Unit.h" #include "varlink-io.systemd.UserDatabase.h" #include "varlink-io.systemd.service.h" +#include "varlink-job.h" #include "varlink-manager.h" #include "varlink-metrics.h" #include "varlink-serialize.h" @@ -26,10 +31,11 @@ static const char* const managed_oom_mode_properties[] = { "ManagedOOMSwap", "ManagedOOMMemoryPressure", + "OOMRules", }; static int build_managed_oom_json_array_element(Unit *u, const char *property, sd_json_variant **ret_v) { - bool use_limit = false, use_duration = false; + bool use_limit = false, use_duration = false, use_rules = false; CGroupContext *c; const char *mode; @@ -58,15 +64,25 @@ static int build_managed_oom_json_array_element(Unit *u, const char *property, s mode = managed_oom_mode_to_string(c->moom_mem_pressure); use_limit = c->moom_mem_pressure_limit > 0; use_duration = c->moom_mem_pressure_duration_usec != USEC_INFINITY; + } else if (streq(property, "OOMRules")) { + if (strv_isempty(c->moom_rules)) + mode = managed_oom_mode_to_string(MANAGED_OOM_AUTO); + else { + mode = managed_oom_mode_to_string(MANAGED_OOM_KILL); + use_rules = true; + } } else return -EINVAL; + assert(mode); + 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)), - SD_JSON_BUILD_PAIR_CONDITION(use_duration, "duration", SD_JSON_BUILD_UNSIGNED(c->moom_mem_pressure_duration_usec))); + SD_JSON_BUILD_PAIR_CONDITION(use_duration, "duration", SD_JSON_BUILD_UNSIGNED(c->moom_mem_pressure_duration_usec)), + SD_JSON_BUILD_PAIR_CONDITION(use_rules, "rules", SD_JSON_BUILD_STRV(c->moom_rules))); } static int build_managed_oom_cgroups_json(Manager *m, bool allow_empty, sd_json_variant **ret) { @@ -107,7 +123,8 @@ static int build_managed_oom_cgroups_json(Manager *m, bool allow_empty, sd_json_ /* For the initial varlink call we only care about units that enabled (i.e. mode is not * set to "auto") oomd properties. */ if (!(streq(*i, "ManagedOOMSwap") && c->moom_swap == MANAGED_OOM_KILL) && - !(streq(*i, "ManagedOOMMemoryPressure") && c->moom_mem_pressure == MANAGED_OOM_KILL)) + !(streq(*i, "ManagedOOMMemoryPressure") && c->moom_mem_pressure == MANAGED_OOM_KILL) && + !(streq(*i, "OOMRules") && !strv_isempty(c->moom_rules))) continue; r = build_managed_oom_json_array_element(u, *i, &e); @@ -355,6 +372,24 @@ static void vl_disconnect(sd_varlink_server *s, sd_varlink *link, void *userdata if (link == m->managed_oom_varlink) m->managed_oom_varlink = sd_varlink_unref(link); + + /* Drop any job varlink references for the disconnecting client. + * A varlink link can stream at most one job, so stop after the first match. */ + Job *j; + HASHMAP_FOREACH(j, m->jobs) + if (j->varlink == link) { + j->varlink = sd_varlink_unref(j->varlink); + break; + } + + /* Also drop any unit-change varlink reference streaming to this link. + * A varlink link attaches to at most one unit, so stop after the first match. */ + Unit *u; + HASHMAP_FOREACH(u, m->units) + if (u->varlink_unit_change == link) { + u->varlink_unit_change = sd_varlink_unref(u->varlink_unit_change); + break; + } } int manager_setup_varlink_server(Manager *m) { @@ -378,6 +413,7 @@ int manager_setup_varlink_server(Manager *m) { r = sd_varlink_server_add_interface_many( s, + &vl_interface_io_systemd_Job, &vl_interface_io_systemd_Manager, &vl_interface_io_systemd_Unit, &vl_interface_io_systemd_service); @@ -386,12 +422,21 @@ int manager_setup_varlink_server(Manager *m) { r = sd_varlink_server_bind_method_many( s, + "io.systemd.Job.List", vl_method_list_jobs, + "io.systemd.Job.Cancel", vl_method_cancel_job, + "io.systemd.Job.ClearAll", vl_method_clear_all_jobs, "io.systemd.Manager.Describe", vl_method_describe_manager, "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.Unit.StartTransient", vl_method_start_transient_unit, "io.systemd.service.Ping", varlink_method_ping, "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) @@ -413,12 +458,12 @@ int manager_setup_varlink_server(Manager *m) { "io.systemd.ManagedOOM.SubscribeManagedOOMCGroups", vl_method_subscribe_managed_oom_cgroups); if (r < 0) return log_debug_errno(r, "Failed to register varlink methods: %m"); - - r = sd_varlink_server_bind_disconnect(s, vl_disconnect); - if (r < 0) - return log_debug_errno(r, "Failed to register varlink disconnect handler: %m"); } + r = sd_varlink_server_bind_disconnect(s, vl_disconnect); + if (r < 0) + return log_debug_errno(r, "Failed to register varlink disconnect handler: %m"); + r = sd_varlink_server_attach_event(s, m->event, EVENT_PRIORITY_IPC); if (r < 0) return log_debug_errno(r, "Failed to attach varlink connection to event loop: %m"); @@ -571,6 +616,8 @@ void manager_varlink_done(Manager *m) { * installed (vl_disconnect() above) to be called, where we will unref it too. */ sd_varlink_close_unref(TAKE_PTR(m->managed_oom_varlink)); + m->pending_reload_message_vl = sd_varlink_unref(m->pending_reload_message_vl); + m->varlink_server = sd_varlink_server_unref(m->varlink_server); m->managed_oom_varlink = sd_varlink_close_unref(m->managed_oom_varlink); 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..bcf38a03c58f5 100644 --- a/src/coredump/coredump-config.c +++ b/src/coredump/coredump-config.c @@ -2,8 +2,9 @@ #include "conf-parser.h" #include "coredump-config.h" +#include "elf-util.h" #include "format-util.h" -#include "journal-importer.h" +#include "journal-def.h" #include "log.h" #include "string-table.h" #include "string-util.h" @@ -45,12 +46,10 @@ int coredump_parse_config(CoredumpConfig *config) { config->journal_size_max = JOURNAL_SIZE_MAX; } -#if !HAVE_DWFL_SET_SYSROOT - if (config->enter_namespace) { + if (config->enter_namespace && !dlopen_dw_has_dwfl_set_sysroot()) { log_warning("EnterNamespace= is enabled but libdw does not support dwfl_set_sysroot(), disabling."); config->enter_namespace = false; } -#endif log_debug("Selected storage '%s'.", coredump_storage_to_string(config->storage)); log_debug("Selected compression %s.", yes_no(config->compress)); diff --git a/src/coredump/coredump-context.c b/src/coredump/coredump-context.c index 921cfe5de7650..24d273499a3c6 100644 --- a/src/coredump/coredump-context.c +++ b/src/coredump/coredump-context.c @@ -14,6 +14,7 @@ #include "memstream-util.h" #include "namespace-util.h" #include "parse-util.h" +#include "pidfd-util.h" #include "process-util.h" #include "signal-util.h" #include "special.h" @@ -32,10 +33,13 @@ 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=", + [META_CODE] = "COREDUMP_CODE=", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(metadata_field, MetadataField); @@ -49,6 +53,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 +155,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) @@ -172,6 +178,34 @@ static int get_process_container_parent_cmdline(PidRef *pid, char** ret_cmdline) return 1; } +/* The kernel passes si_signo through core_pattern (%s). Starting with v7.1, + * si_code is reported as well via pidfd. */ +static int coredump_context_read_pidfd_info(CoredumpContext *context) { + struct pidfd_info info = { + .mask = PIDFD_INFO_COREDUMP, + }; + int r; + + assert(context); + assert(pidref_is_set(&context->pidref)); + + if (!context->got_pidfd || context->pidref.fd < 0) + return 0; + + r = pidfd_get_info(context->pidref.fd, &info); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return log_debug_errno(r, "PIDFD_INFO_COREDUMP not supported, ignoring: %m"); + if (r < 0) + return log_debug_errno(r, "Failed to get pidfd coredump info, ignoring: %m"); + + if (FLAGS_SET(info.mask, PIDFD_INFO_COREDUMP_CODE)) { + context->code = (int) info.coredump_code; + context->got_code = true; + } + + return 0; +} + int coredump_context_build_iovw(CoredumpContext *context) { char *t; int r; @@ -209,6 +243,10 @@ int coredump_context_build_iovw(CoredumpContext *context) { return log_error_errno(r, "Failed to add COREDUMP_SIGNAL= field: %m"); (void) iovw_put_string_field(&context->iovw, "COREDUMP_SIGNAL_NAME=SIG", signal_to_string(context->signo)); + + /* Emit si_code if we learned it from pidfd_info */ + if (context->got_code) + (void) iovw_put_string_fieldf(&context->iovw, "COREDUMP_CODE=", "%i", context->code); } r = iovw_put_string_fieldf(&context->iovw, "COREDUMP_TIMESTAMP=", USEC_FMT, context->timestamp); @@ -228,6 +266,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 +383,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"); @@ -351,6 +401,8 @@ static int coredump_context_parse_from_procfs(CoredumpContext *context) { if (r < 0) log_warning_errno(r, "Failed to get auxv, ignoring: %m"); + (void) coredump_context_read_pidfd_info(context); + r = pidref_verify(&context->pidref); if (r < 0) return log_error_errno(r, "PIDFD validation failed: %m"); @@ -465,6 +517,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 +532,20 @@ 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_CODE: + /* We must accept both positive and negative values. The former + * are reserved for kernel-generated signals, the latter for signals requested + * from userspace. See /usr/include/bits/siginfo-consts.h. */ + r = safe_atoi(s, &context->code); + if (r < 0) + log_warning_errno(r, "Failed to parse code \"%s\", ignoring: %m", s); + else + context->got_code = true; + return 0; + 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..a2ce16b8531cb 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,8 @@ typedef enum MetadataField { META_EXE, META_UNIT, META_PROC_AUXV, + META_THREAD_NAME, + META_CODE, /* code of signal causing dump (eg: 2 for SEGV_ACCERR). Since v7.1. */ _META_MAX, _META_INVALID = -EINVAL, } MetadataField; @@ -49,16 +52,20 @@ struct CoredumpContext { uid_t uid; /* META_ARGV_UID */ gid_t gid; /* META_ARGV_GID */ int signo; /* META_ARGV_SIGNAL */ + int code; /* META_CODE */ usec_t timestamp; /* META_ARGV_TIMESTAMP */ 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 got_code; /* META_CODE */ bool same_pidns; bool forwarded; int input_fd; diff --git a/src/coredump/coredump-send.c b/src/coredump/coredump-send.c index 0174be69a4056..4817d82611d4b 100644 --- a/src/coredump/coredump-send.c +++ b/src/coredump/coredump-send.c @@ -57,7 +57,7 @@ int coredump_send(CoredumpContext *context) { * what we want to send, and the second one contains * the trailing dots. */ copy[0] = *iovec; - copy[1] = IOVEC_MAKE(((const char[]){'.', '.', '.'}), 3); + copy[1] = IOVEC_MAKE_STRING("..."); mh.msg_iov = copy; mh.msg_iovlen = 2; diff --git a/src/coredump/coredump-submit.c b/src/coredump/coredump-submit.c index 9134697d5b8b7..fe1042ce4afa8 100644 --- a/src/coredump/coredump-submit.c +++ b/src/coredump/coredump-submit.c @@ -33,7 +33,7 @@ #include "journal-send.h" #include "json-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "namespace-util.h" #include "path-util.h" #include "process-util.h" @@ -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; @@ -464,7 +468,9 @@ static int maybe_remove_external_coredump( } static int acquire_pid_mount_tree_fd(const CoredumpConfig *config, CoredumpContext *context) { -#if HAVE_DWFL_SET_SYSROOT + if (!dlopen_dw_has_dwfl_set_sysroot()) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "dwfl_set_sysroot() is not supported."); + _cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF, fd = -EBADF; _cleanup_close_pair_ int pair[2] = EBADF_PAIR; int r; @@ -529,10 +535,6 @@ static int acquire_pid_mount_tree_fd(const CoredumpConfig *config, CoredumpConte context->mount_tree_fd = TAKE_FD(fd); return 0; -#else - /* Don't bother preparing environment if we can't pass it to libdwfl. */ - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "dwfl_set_sysroot() is not supported."); -#endif } static int attach_mount_tree(const CoredumpConfig *config, CoredumpContext *context) { @@ -582,11 +584,9 @@ static int change_uid_gid(const CoredumpContext *context) { gid_t gid = context->gid; if (uid_is_system(uid)) { - const char *user = "systemd-coredump"; - - r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); + r = get_user_creds("systemd-coredump", /* flags= */ 0, NULL, &uid, &gid, NULL, NULL); if (r < 0) { - log_warning_errno(r, "Cannot resolve %s user. Proceeding to dump core as root: %m", user); + log_warning_errno(r, "Cannot resolve systemd-coredump user. Proceeding to dump core as root: %m"); uid = gid = 0; } } diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 64f7ae99a10f0..9b26e23d62930 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -14,6 +13,7 @@ #include "sd-messages.h" #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "bus-error.h" #include "bus-locator.h" @@ -29,7 +29,9 @@ #include "format-util.h" #include "fs-util.h" #include "glob-util.h" +#include "help-util.h" #include "image-policy.h" +#include "io-util.h" #include "journal-internal.h" #include "journal-util.h" #include "json-util.h" @@ -38,12 +40,12 @@ #include "loop-util.h" #include "main-func.h" #include "mount-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" #include "pidref.h" -#include "pretty-print.h" #include "process-util.h" #include "signal-util.h" #include "string-util.h" @@ -175,221 +177,167 @@ static int acquire_journal(sd_journal **ret, char **matches) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { - _cleanup_free_ char *link = NULL; +static int help(void) { + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; - r = terminal_urlify_man("coredumpctl", "1", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sList or retrieve coredumps from the journal.%6$s\n" - "\n%3$sCommands:%4$s\n" - " list [MATCHES...] List available coredumps (default)\n" - " info [MATCHES...] Show detailed information about one or more coredumps\n" - " dump [MATCHES...] Print first matching coredump to stdout\n" - " debug [MATCHES...] Start a debugger for the first matching coredump\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version string\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not print the column headers\n" - " --json=pretty|short|off Generate JSON output\n" - " --debugger=DEBUGGER Use the given debugger\n" - " -A --debugger-arguments=ARGS Pass the given arguments to the debugger\n" - " -n INT Show maximum number of rows\n" - " -1 Show information about most recent entry only\n" - " -S --since=DATE Only print coredumps since the date\n" - " -U --until=DATE Only print coredumps until the date\n" - " -r --reverse Show the newest entries first\n" - " -F --field=FIELD List all values a certain field takes\n" - " -o --output=FILE Write output to FILE\n" - " --file=PATH Use journal file\n" - " -D --directory=DIR Use journal files from directory\n\n" - " -q --quiet Do not show info messages and privilege warning\n" - " --all Look at all journal files instead of local ones\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" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + (void) table_sync_column_widths(0, verbs, options); + + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("List or retrieve coredumps from the journal."); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("coredumpctl", "1"); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - ARG_DEBUGGER, - ARG_FILE, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_ALL, - }; +VERB_COMMON_HELP_HIDDEN(help); - int c, r; - - 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 }, - { "debugger", required_argument, NULL, ARG_DEBUGGER }, - { "debugger-arguments", required_argument, NULL, 'A' }, - { "output", required_argument, NULL, 'o' }, - { "field", required_argument, NULL, 'F' }, - { "file", required_argument, NULL, ARG_FILE }, - { "directory", required_argument, NULL, 'D' }, - { "reverse", no_argument, NULL, 'r' }, - { "since", required_argument, NULL, 'S' }, - { "until", required_argument, NULL, 'U' }, - { "quiet", no_argument, NULL, 'q' }, - { "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 }, - { "all", no_argument, NULL, ARG_ALL }, - {} - }; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "hA:o:F:1D:rS:U:qn:", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - return verb_help(0, NULL, NULL); - case ARG_VERSION: + OPTION_COMMON_HELP: + return help(); + + 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_DEBUGGER: - arg_debugger = optarg; + OPTION_LONG("debugger", "DEBUGGER", "Use the given debugger"): + arg_debugger = opts.arg; break; - case 'A': { + OPTION('A', "debugger-arguments", "…", "Pass the given arguments to the debugger"): { _cleanup_strv_free_ char **l = NULL; - r = strv_split_full(&l, optarg, WHITESPACE, EXTRACT_UNQUOTE); + r = strv_split_full(&l, opts.arg, WHITESPACE, EXTRACT_UNQUOTE); if (r < 0) - return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", optarg); + return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", opts.arg); strv_free_and_replace(arg_debugger_args, l); break; } - case ARG_FILE: - r = glob_extend(&arg_file, optarg, GLOB_NOCHECK); + OPTION_LONG("file", "PATH", "Use journal file"): + r = glob_extend(&arg_file, opts.arg, GLOB_NOCHECK); if (r < 0) return log_error_errno(r, "Failed to add paths: %m"); break; - case 'o': + OPTION('o', "output", "FILE", "Write output to FILE"): if (arg_output) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot set output more than once."); - arg_output = optarg; + arg_output = opts.arg; break; - case 'S': - r = parse_timestamp(optarg, &arg_since); + OPTION('S', "since", "DATE", "Only print coredumps since the date"): + r = parse_timestamp(opts.arg, &arg_since); if (r < 0) - return log_error_errno(r, "Failed to parse timestamp '%s': %m", optarg); + return log_error_errno(r, "Failed to parse timestamp '%s': %m", opts.arg); break; - case 'U': - r = parse_timestamp(optarg, &arg_until); + OPTION('U', "until", "DATE", "Only print coredumps until the date"): + r = parse_timestamp(opts.arg, &arg_until); if (r < 0) - return log_error_errno(r, "Failed to parse timestamp '%s': %m", optarg); + return log_error_errno(r, "Failed to parse timestamp '%s': %m", opts.arg); break; - case 'F': + OPTION('F', "field", "FIELD", "List all values a certain field takes"): if (arg_field) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --field/-F more than once."); - arg_field = optarg; + arg_field = opts.arg; break; - case '1': + OPTION_SHORT('1', NULL, "Show information about most recent entry only"): arg_rows_max = 1; arg_reverse = true; break; - case 'n': { + OPTION_SHORT('n', "INT", "Show at most this many rows"): { unsigned n; - r = safe_atou(optarg, &n); + r = safe_atou(opts.arg, &n); if (r < 0 || n < 1) return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), - "Invalid numeric parameter to -n: %s", optarg); + "Invalid numeric parameter to -n: %s", opts.arg); arg_rows_max = n; break; } - case 'D': - arg_directory = optarg; + OPTION('D', "directory", "DIR", "Use journal files from directory"): + arg_directory = opts.arg; break; - case ARG_ROOT: - r = parse_path_argument(optarg, false, &arg_root); + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(opts.arg, false, &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(opts.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(opts.arg, &arg_image_policy); if (r < 0) return r; break; - case 'r': + OPTION('r', "reverse", NULL, "Show the newest entries first"): arg_reverse = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Do not show info messages and privilege warning"): arg_quiet = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case ARG_ALL: + OPTION_LONG("all", NULL, "Look at all journal files instead of local ones"): arg_all = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_since != USEC_INFINITY && arg_until != USEC_INFINITY && @@ -400,6 +348,7 @@ static int parse_argv(int argc, char *argv[]) { if ((!!arg_directory + !!arg_image + !!arg_root) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root=, --image= or -D/--directory=, the combination of these options is not supported."); + *remaining_args = option_parser_get_args(&opts); return 1; } @@ -411,6 +360,8 @@ static int retrieve(const void *data, size_t ident; char *v; + assert(var); + ident = strlen(name) + 1; /* name + "=" */ if (len < ident) @@ -525,6 +476,8 @@ static int resolve_filename(const char *root, char **p) { char *resolved = NULL; int r; + assert(p); + if (!*p) return 0; @@ -544,14 +497,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 +523,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 +551,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"; @@ -624,227 +579,309 @@ static int print_list(FILE* file, sd_journal *j, Table *t) { return 0; } -static int print_info(FILE *file, sd_journal *j, bool need_space) { - _cleanup_free_ char - *mid = NULL, *pid = NULL, *uid = NULL, *gid = NULL, - *sgnl = NULL, *exe = NULL, *comm = NULL, *cmdline = NULL, - *unit = NULL, *user_unit = NULL, *session = NULL, - *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; +typedef enum CoredumpField { + COREDUMP_FIELD_MID, + COREDUMP_FIELD_PID, + COREDUMP_FIELD_UID, + COREDUMP_FIELD_GID, + COREDUMP_FIELD_SGNL, + COREDUMP_FIELD_CODE, + COREDUMP_FIELD_EXE, + COREDUMP_FIELD_COMM, + COREDUMP_FIELD_CMDLINE, + COREDUMP_FIELD_HOSTNAME, + COREDUMP_FIELD_UNIT, + COREDUMP_FIELD_USER_UNIT, + COREDUMP_FIELD_SESSION, + COREDUMP_FIELD_OWNER_UID, + COREDUMP_FIELD_SLICE, + COREDUMP_FIELD_CGROUP, + COREDUMP_FIELD_TIMESTAMP, + COREDUMP_FIELD_FILENAME, + COREDUMP_FIELD_TRUNCATED, + COREDUMP_FIELD_PKGMETA_NAME, + COREDUMP_FIELD_PKGMETA_VERSION, + COREDUMP_FIELD_PKGMETA_JSON, + COREDUMP_FIELD_TID, + COREDUMP_FIELD_THREAD_NAME, + COREDUMP_FIELD_BOOT_ID, + COREDUMP_FIELD_MACHINE_ID, + COREDUMP_FIELD_MESSAGE, + _COREDUMP_FIELD_MAX, +} CoredumpField; + +static const char* const coredump_field_table[_COREDUMP_FIELD_MAX] = { + [COREDUMP_FIELD_MID] = "MESSAGE_ID", + [COREDUMP_FIELD_PID] = "COREDUMP_PID", + [COREDUMP_FIELD_UID] = "COREDUMP_UID", + [COREDUMP_FIELD_GID] = "COREDUMP_GID", + [COREDUMP_FIELD_SGNL] = "COREDUMP_SIGNAL", + [COREDUMP_FIELD_CODE] = "COREDUMP_CODE", + [COREDUMP_FIELD_EXE] = "COREDUMP_EXE", + [COREDUMP_FIELD_COMM] = "COREDUMP_COMM", + [COREDUMP_FIELD_CMDLINE] = "COREDUMP_CMDLINE", + [COREDUMP_FIELD_HOSTNAME] = "COREDUMP_HOSTNAME", + [COREDUMP_FIELD_UNIT] = "COREDUMP_UNIT", + [COREDUMP_FIELD_USER_UNIT] = "COREDUMP_USER_UNIT", + [COREDUMP_FIELD_SESSION] = "COREDUMP_SESSION", + [COREDUMP_FIELD_OWNER_UID] = "COREDUMP_OWNER_UID", + [COREDUMP_FIELD_SLICE] = "COREDUMP_SLICE", + [COREDUMP_FIELD_CGROUP] = "COREDUMP_CGROUP", + [COREDUMP_FIELD_TIMESTAMP] = "COREDUMP_TIMESTAMP", + [COREDUMP_FIELD_FILENAME] = "COREDUMP_FILENAME", + [COREDUMP_FIELD_TRUNCATED] = "COREDUMP_TRUNCATED", + [COREDUMP_FIELD_PKGMETA_NAME] = "COREDUMP_PACKAGE_NAME", + [COREDUMP_FIELD_PKGMETA_VERSION] = "COREDUMP_PACKAGE_VERSION", + [COREDUMP_FIELD_PKGMETA_JSON] = "COREDUMP_PACKAGE_JSON", + [COREDUMP_FIELD_TID] = "COREDUMP_TID", + [COREDUMP_FIELD_THREAD_NAME] = "COREDUMP_THREAD_NAME", + [COREDUMP_FIELD_BOOT_ID] = "_BOOT_ID", + [COREDUMP_FIELD_MACHINE_ID] = "_MACHINE_ID", + [COREDUMP_FIELD_MESSAGE] = "MESSAGE", +}; + +typedef struct CoredumpFields { + char *fields[_COREDUMP_FIELD_MAX]; + + bool normal_coredump; + const char *storage_state; /* points to a static string, not owned */ + const char *storage_color; /* points to a static string, not owned */ + uint64_t disk_size; + sd_json_variant *package_json; +} CoredumpFields; + +static void coredump_fields_done(CoredumpFields *f) { + assert(f); + + free_many_charp(f->fields, _COREDUMP_FIELD_MAX); + sd_json_variant_unref(f->package_json); +} + +static int coredump_fields_load(sd_journal *j, CoredumpFields *ret) { const void *d; size_t l; - bool normal_coredump; int r; - assert(file); assert(j); + assert(ret); (void) sd_journal_set_data_threshold(j, 0); SD_JOURNAL_FOREACH_DATA(j, d, l) { - RETRIEVE(d, l, "MESSAGE_ID", mid); - RETRIEVE(d, l, "COREDUMP_PID", pid); - RETRIEVE(d, l, "COREDUMP_UID", uid); - RETRIEVE(d, l, "COREDUMP_GID", gid); - RETRIEVE(d, l, "COREDUMP_SIGNAL", sgnl); - RETRIEVE(d, l, "COREDUMP_EXE", exe); - RETRIEVE(d, l, "COREDUMP_COMM", comm); - RETRIEVE(d, l, "COREDUMP_CMDLINE", cmdline); - RETRIEVE(d, l, "COREDUMP_HOSTNAME", hostname); - RETRIEVE(d, l, "COREDUMP_UNIT", unit); - RETRIEVE(d, l, "COREDUMP_USER_UNIT", user_unit); - RETRIEVE(d, l, "COREDUMP_SESSION", session); - RETRIEVE(d, l, "COREDUMP_OWNER_UID", owner_uid); - RETRIEVE(d, l, "COREDUMP_SLICE", slice); - RETRIEVE(d, l, "COREDUMP_CGROUP", cgroup); - 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, "_BOOT_ID", boot_id); - RETRIEVE(d, l, "_MACHINE_ID", machine_id); - RETRIEVE(d, l, "MESSAGE", message); + for (CoredumpField i = 0; i < _COREDUMP_FIELD_MAX; i++) { + int k = retrieve(d, l, coredump_field_table[i], &ret->fields[i]); + if (k < 0) + return k; + if (k > 0) + break; + } } + ret->normal_coredump = streq_ptr(ret->fields[COREDUMP_FIELD_MID], SD_MESSAGE_COREDUMP_STR); + + if (ret->fields[COREDUMP_FIELD_FILENAME]) { + r = resolve_filename(arg_root, &ret->fields[COREDUMP_FIELD_FILENAME]); + if (r < 0) + return r; + + analyze_coredump_file(ret->fields[COREDUMP_FIELD_FILENAME], &ret->storage_state, &ret->storage_color, &ret->disk_size); + + if (STRPTR_IN_SET(ret->storage_state, "present", "journal") && ret->fields[COREDUMP_FIELD_TRUNCATED] && parse_boolean(ret->fields[COREDUMP_FIELD_TRUNCATED]) > 0) + ret->storage_state = "truncated"; + } else if (sd_journal_get_data(j, "COREDUMP", NULL, NULL) >= 0) + ret->storage_state = "journal"; + else + ret->storage_state = "none"; + + if (ret->fields[COREDUMP_FIELD_PKGMETA_JSON]) { + r = sd_json_parse(ret->fields[COREDUMP_FIELD_PKGMETA_JSON], SD_JSON_PARSE_MUST_BE_OBJECT, &ret->package_json, NULL, NULL); + if (r < 0) { + _cleanup_free_ char *esc = cescape(ret->fields[COREDUMP_FIELD_PKGMETA_JSON]); + log_warning_errno(r, "Failed to parse COREDUMP_PACKAGE_JSON \"%s\", ignoring: %m", strnull(esc)); + } + } + + return 0; +} + +static int print_info(FILE *file, sd_journal *j, bool need_space) { + _cleanup_(coredump_fields_done) CoredumpFields f = { + .disk_size = UINT64_MAX, + }; + int r; + + assert(file); + assert(j); + + r = coredump_fields_load(j, &f); + if (r < 0) + return r; + if (need_space) fputs("\n", file); - normal_coredump = streq_ptr(mid, SD_MESSAGE_COREDUMP_STR); - - if (comm) + if (f.fields[COREDUMP_FIELD_COMM]) fprintf(file, " PID: %s%s%s (%s)\n", - ansi_highlight(), strna(pid), ansi_normal(), comm); + ansi_highlight(), strna(f.fields[COREDUMP_FIELD_PID]), ansi_normal(), f.fields[COREDUMP_FIELD_COMM]); else fprintf(file, " PID: %s%s%s\n", - ansi_highlight(), strna(pid), ansi_normal()); + ansi_highlight(), strna(f.fields[COREDUMP_FIELD_PID]), ansi_normal()); - if (uid) { + if (f.fields[COREDUMP_FIELD_TID]) { + if (f.fields[COREDUMP_FIELD_THREAD_NAME]) + fprintf(file, " TID: %s (%s)\n", f.fields[COREDUMP_FIELD_TID], f.fields[COREDUMP_FIELD_THREAD_NAME]); + else + fprintf(file, " TID: %s\n", f.fields[COREDUMP_FIELD_TID]); + } + + if (f.fields[COREDUMP_FIELD_UID]) { uid_t n; - if (parse_uid(uid, &n) >= 0) { + if (parse_uid(f.fields[COREDUMP_FIELD_UID], &n) >= 0) { _cleanup_free_ char *u = NULL; u = uid_to_name(n); fprintf(file, " UID: %s (%s)\n", - uid, u); - } else { + f.fields[COREDUMP_FIELD_UID], u); + } else fprintf(file, " UID: %s\n", - uid); - } + f.fields[COREDUMP_FIELD_UID]); } - if (gid) { + if (f.fields[COREDUMP_FIELD_GID]) { gid_t n; - if (parse_gid(gid, &n) >= 0) { + if (parse_gid(f.fields[COREDUMP_FIELD_GID], &n) >= 0) { _cleanup_free_ char *g = NULL; g = gid_to_name(n); fprintf(file, " GID: %s (%s)\n", - gid, g); - } else { + f.fields[COREDUMP_FIELD_GID], g); + } else fprintf(file, " GID: %s\n", - gid); - } + f.fields[COREDUMP_FIELD_GID]); } - if (sgnl) { + if (f.fields[COREDUMP_FIELD_SGNL]) { int sig; - const char *name = normal_coredump ? "Signal" : "Reason"; + const char *name = f.normal_coredump ? "Signal" : "Reason"; - if (normal_coredump && safe_atoi(sgnl, &sig) >= 0) - fprintf(file, " %s: %s (%s)\n", name, sgnl, signal_to_string(sig)); - else - fprintf(file, " %s: %s\n", name, sgnl); + if (f.normal_coredump && safe_atoi(f.fields[COREDUMP_FIELD_SGNL], &sig) >= 0) { + fprintf(file, " %s: %s (%s)", name, f.fields[COREDUMP_FIELD_SGNL], signal_to_string(sig)); + + if (f.fields[COREDUMP_FIELD_CODE]) { + int n; + const char *s; + + if (safe_atoi(f.fields[COREDUMP_FIELD_CODE], &n) >= 0) + s = signal_code_to_string(sig, n); + else + s = NULL; + + fprintf(file, " si_code: %s", s ?: f.fields[COREDUMP_FIELD_CODE]); + } + + fputc('\n', file); + } else + fprintf(file, " %s: %s\n", name, f.fields[COREDUMP_FIELD_SGNL]); } - if (timestamp) { + if (f.fields[COREDUMP_FIELD_TIMESTAMP]) { usec_t u; - r = safe_atou64(timestamp, &u); + r = safe_atou64(f.fields[COREDUMP_FIELD_TIMESTAMP], &u); if (r >= 0) fprintf(file, " Timestamp: %s (%s)\n", FORMAT_TIMESTAMP(u), FORMAT_TIMESTAMP_RELATIVE(u)); - else - fprintf(file, " Timestamp: %s\n", timestamp); + fprintf(file, " Timestamp: %s\n", f.fields[COREDUMP_FIELD_TIMESTAMP]); } - if (cmdline) - fprintf(file, " Command Line: %s\n", cmdline); - if (exe) - fprintf(file, " Executable: %s%s%s\n", ansi_highlight(), exe, ansi_normal()); - if (cgroup) - fprintf(file, " Control Group: %s\n", cgroup); - if (unit) - fprintf(file, " Unit: %s\n", unit); - if (user_unit) - fprintf(file, " User Unit: %s\n", user_unit); - if (slice) - fprintf(file, " Slice: %s\n", slice); - if (session) - fprintf(file, " Session: %s\n", session); - if (owner_uid) { + if (f.fields[COREDUMP_FIELD_CMDLINE]) + fprintf(file, " Command Line: %s\n", f.fields[COREDUMP_FIELD_CMDLINE]); + if (f.fields[COREDUMP_FIELD_EXE]) + fprintf(file, " Executable: %s%s%s\n", ansi_highlight(), f.fields[COREDUMP_FIELD_EXE], ansi_normal()); + if (f.fields[COREDUMP_FIELD_CGROUP]) + fprintf(file, " Control Group: %s\n", f.fields[COREDUMP_FIELD_CGROUP]); + if (f.fields[COREDUMP_FIELD_UNIT]) + fprintf(file, " Unit: %s\n", f.fields[COREDUMP_FIELD_UNIT]); + if (f.fields[COREDUMP_FIELD_USER_UNIT]) + fprintf(file, " User Unit: %s\n", f.fields[COREDUMP_FIELD_USER_UNIT]); + if (f.fields[COREDUMP_FIELD_SLICE]) + fprintf(file, " Slice: %s\n", f.fields[COREDUMP_FIELD_SLICE]); + if (f.fields[COREDUMP_FIELD_SESSION]) + fprintf(file, " Session: %s\n", f.fields[COREDUMP_FIELD_SESSION]); + if (f.fields[COREDUMP_FIELD_OWNER_UID]) { uid_t n; - if (parse_uid(owner_uid, &n) >= 0) { + if (parse_uid(f.fields[COREDUMP_FIELD_OWNER_UID], &n) >= 0) { _cleanup_free_ char *u = NULL; u = uid_to_name(n); fprintf(file, " Owner UID: %s (%s)\n", - owner_uid, u); - } else { + f.fields[COREDUMP_FIELD_OWNER_UID], u); + } else fprintf(file, " Owner UID: %s\n", - owner_uid); - } + f.fields[COREDUMP_FIELD_OWNER_UID]); } - if (boot_id) - fprintf(file, " Boot ID: %s\n", boot_id); - if (machine_id) - fprintf(file, " Machine ID: %s\n", machine_id); - if (hostname) - fprintf(file, " Hostname: %s\n", hostname); - - if (filename) { - r = resolve_filename(arg_root, &filename); - if (r < 0) - return r; - - const char *state = NULL, *color = NULL; - uint64_t size = UINT64_MAX; - - analyze_coredump_file(filename, &state, &color, &size); - - if (STRPTR_IN_SET(state, "present", "journal") && truncated && parse_boolean(truncated) > 0) - state = "truncated"; - + if (f.fields[COREDUMP_FIELD_BOOT_ID]) + fprintf(file, " Boot ID: %s\n", f.fields[COREDUMP_FIELD_BOOT_ID]); + if (f.fields[COREDUMP_FIELD_MACHINE_ID]) + fprintf(file, " Machine ID: %s\n", f.fields[COREDUMP_FIELD_MACHINE_ID]); + if (f.fields[COREDUMP_FIELD_HOSTNAME]) + fprintf(file, " Hostname: %s\n", f.fields[COREDUMP_FIELD_HOSTNAME]); + + if (f.fields[COREDUMP_FIELD_FILENAME]) { fprintf(file, " Storage: %s%s (%s)%s\n", - strempty(color), - filename, - state, + strempty(f.storage_color), + f.fields[COREDUMP_FIELD_FILENAME], + f.storage_state, ansi_normal()); - if (size != UINT64_MAX) - fprintf(file, " Size on Disk: %s\n", FORMAT_BYTES(size)); - - } else if (coredump) - fprintf(file, " Storage: journal\n"); - else - fprintf(file, " Storage: none\n"); + if (f.disk_size != UINT64_MAX) + fprintf(file, " Size on Disk: %s\n", FORMAT_BYTES(f.disk_size)); + } else + fprintf(file, " Storage: %s\n", f.storage_state); - if (pkgmeta_name && pkgmeta_version) - fprintf(file, " Package: %s/%s\n", pkgmeta_name, pkgmeta_version); + if (f.fields[COREDUMP_FIELD_PKGMETA_NAME] && f.fields[COREDUMP_FIELD_PKGMETA_VERSION]) + fprintf(file, " Package: %s/%s\n", f.fields[COREDUMP_FIELD_PKGMETA_NAME], f.fields[COREDUMP_FIELD_PKGMETA_VERSION]); /* Print out the build-id of the 'main' ELF module, by matching the JSON key * with the 'exe' field. */ - if (exe && pkgmeta_json) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + if (f.fields[COREDUMP_FIELD_EXE] && f.package_json) { + const char *module_name; + sd_json_variant *module_json; - r = sd_json_parse(pkgmeta_json, 0, &v, NULL, NULL); - if (r < 0) { - _cleanup_free_ char *esc = cescape(pkgmeta_json); - log_warning_errno(r, "json_parse on \"%s\" failed, ignoring: %m", strnull(esc)); - } else { - const char *module_name; - sd_json_variant *module_json; + JSON_VARIANT_OBJECT_FOREACH(module_name, module_json, f.package_json) { + sd_json_variant *build_id; - JSON_VARIANT_OBJECT_FOREACH(module_name, module_json, v) { - sd_json_variant *build_id; + /* We only print the build-id for the 'main' ELF module */ + if (!path_equal_filename(module_name, f.fields[COREDUMP_FIELD_EXE])) + continue; - /* We only print the build-id for the 'main' ELF module */ - if (!path_equal_filename(module_name, exe)) - continue; - - build_id = sd_json_variant_by_key(module_json, "buildId"); - if (build_id) - fprintf(file, " build-id: %s\n", sd_json_variant_string(build_id)); + build_id = sd_json_variant_by_key(module_json, "buildId"); + if (build_id) + fprintf(file, " build-id: %s\n", sd_json_variant_string(build_id)); - break; - } + break; } } - if (message) { + if (f.fields[COREDUMP_FIELD_MESSAGE]) { _cleanup_free_ char *m = NULL; - m = strreplace(message, "\n", "\n "); + m = strreplace(f.fields[COREDUMP_FIELD_MESSAGE], "\n", "\n "); - fprintf(file, " Message: %s\n", strstrip(m ?: message)); + fprintf(file, " Message: %s\n", strstrip(m ?: f.fields[COREDUMP_FIELD_MESSAGE])); } return 0; @@ -864,6 +901,91 @@ static int focus(sd_journal *j) { return r; } +static int print_info_json(FILE *file, sd_journal *j) { + _cleanup_(coredump_fields_done) CoredumpFields f = { + .disk_size = UINT64_MAX, + }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + pid_t pid_as_int = 0, tid_as_int = 0; + uid_t uid_as_int = UID_INVALID, owner_uid_as_int = UID_INVALID; + gid_t gid_as_int = GID_INVALID; + int sig_as_int = 0, code_as_int = 0; + bool code_is_valid = false; + usec_t ts = USEC_INFINITY; + int r; + + assert(file); + assert(j); + + r = coredump_fields_load(j, &f); + if (r < 0) + return r; + + if (f.fields[COREDUMP_FIELD_PID]) + (void) parse_pid(f.fields[COREDUMP_FIELD_PID], &pid_as_int); + if (f.fields[COREDUMP_FIELD_TID]) + (void) parse_pid(f.fields[COREDUMP_FIELD_TID], &tid_as_int); + if (f.fields[COREDUMP_FIELD_UID]) + (void) parse_uid(f.fields[COREDUMP_FIELD_UID], &uid_as_int); + if (f.fields[COREDUMP_FIELD_GID]) + (void) parse_gid(f.fields[COREDUMP_FIELD_GID], &gid_as_int); + if (f.fields[COREDUMP_FIELD_OWNER_UID]) + (void) parse_uid(f.fields[COREDUMP_FIELD_OWNER_UID], &owner_uid_as_int); + if (f.normal_coredump && f.fields[COREDUMP_FIELD_SGNL]) + (void) safe_atoi(f.fields[COREDUMP_FIELD_SGNL], &sig_as_int); + if (f.normal_coredump && f.fields[COREDUMP_FIELD_CODE]) + code_is_valid = safe_atoi(f.fields[COREDUMP_FIELD_CODE], &code_as_int) >= 0; + if (f.fields[COREDUMP_FIELD_TIMESTAMP]) + (void) safe_atou64(f.fields[COREDUMP_FIELD_TIMESTAMP], &ts); + + r = sd_json_build(&v, SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_CONDITION(pid_is_valid(pid_as_int), "PID", SD_JSON_BUILD_UNSIGNED(pid_as_int)), + SD_JSON_BUILD_PAIR_CONDITION(!pid_is_valid(pid_as_int) && !!f.fields[COREDUMP_FIELD_PID], "PID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_PID])), + SD_JSON_BUILD_PAIR_CONDITION(pid_is_valid(tid_as_int), "TID", SD_JSON_BUILD_UNSIGNED(tid_as_int)), + SD_JSON_BUILD_PAIR_CONDITION(!pid_is_valid(tid_as_int) && !!f.fields[COREDUMP_FIELD_TID], "TID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_TID])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_THREAD_NAME], "ThreadName", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_THREAD_NAME])), + SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid_as_int), "UID", SD_JSON_BUILD_UNSIGNED(uid_as_int)), + SD_JSON_BUILD_PAIR_CONDITION(!uid_is_valid(uid_as_int) && !!f.fields[COREDUMP_FIELD_UID], "UID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_UID])), + SD_JSON_BUILD_PAIR_CONDITION(gid_is_valid(gid_as_int), "GID", SD_JSON_BUILD_UNSIGNED(gid_as_int)), + SD_JSON_BUILD_PAIR_CONDITION(!gid_is_valid(gid_as_int) && !!f.fields[COREDUMP_FIELD_GID], "GID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_GID])), + SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && sig_as_int > 0, "Signal", SD_JSON_BUILD_INTEGER(sig_as_int)), + SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && sig_as_int > 0 && !!signal_to_string(sig_as_int), "SignalName", SD_JSON_BUILD_STRING(signal_to_string(sig_as_int))), + SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && sig_as_int <= 0 && !!f.fields[COREDUMP_FIELD_SGNL], "Signal", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_SGNL])), + SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && code_is_valid, "SignalCode", SD_JSON_BUILD_INTEGER(code_as_int)), + SD_JSON_BUILD_PAIR_CONDITION(f.normal_coredump && !code_is_valid && !!f.fields[COREDUMP_FIELD_CODE], "SignalCode", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_CODE])), + SD_JSON_BUILD_PAIR_CONDITION(!f.normal_coredump && !!f.fields[COREDUMP_FIELD_SGNL], "Reason", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_SGNL])), + SD_JSON_BUILD_PAIR_CONDITION(ts != USEC_INFINITY, "Timestamp", SD_JSON_BUILD_UNSIGNED(ts)), + SD_JSON_BUILD_PAIR_CONDITION(ts == USEC_INFINITY && !!f.fields[COREDUMP_FIELD_TIMESTAMP], "Timestamp", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_TIMESTAMP])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_EXE], "Executable", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_EXE])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_COMM], "Command", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_COMM])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_CMDLINE], "CommandLine", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_CMDLINE])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_CGROUP], "ControlGroup", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_CGROUP])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_UNIT], "Unit", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_UNIT])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_USER_UNIT], "UserUnit", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_USER_UNIT])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_SLICE], "Slice", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_SLICE])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_SESSION], "Session", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_SESSION])), + SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(owner_uid_as_int), "OwnerUID", SD_JSON_BUILD_UNSIGNED(owner_uid_as_int)), + SD_JSON_BUILD_PAIR_CONDITION(!uid_is_valid(owner_uid_as_int) && !!f.fields[COREDUMP_FIELD_OWNER_UID], "OwnerUID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_OWNER_UID])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_BOOT_ID], "BootID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_BOOT_ID])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_MACHINE_ID], "MachineID", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_MACHINE_ID])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_HOSTNAME], "Hostname", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_HOSTNAME])), + SD_JSON_BUILD_PAIR("Storage", SD_JSON_BUILD_STRING(f.storage_state)), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_FILENAME], "Filename", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_FILENAME])), + SD_JSON_BUILD_PAIR_CONDITION(f.disk_size != UINT64_MAX, "DiskSize", SD_JSON_BUILD_UNSIGNED(f.disk_size)), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_PKGMETA_NAME], "PackageName", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_PKGMETA_NAME])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_PKGMETA_VERSION], "PackageVersion", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_PKGMETA_VERSION])), + SD_JSON_BUILD_PAIR_CONDITION(!!f.package_json, "Package", SD_JSON_BUILD_VARIANT(f.package_json)), + SD_JSON_BUILD_PAIR_CONDITION(!!f.fields[COREDUMP_FIELD_MESSAGE], "Message", SD_JSON_BUILD_STRING(f.fields[COREDUMP_FIELD_MESSAGE])))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON object: %m"); + + r = sd_json_variant_dump(v, arg_json_format_flags, file, NULL); + if (r < 0) + return log_error_errno(r, "Failed to dump JSON object: %m"); + + return 0; +} + static int print_entry( sd_journal *j, size_t n_found, @@ -875,11 +997,17 @@ static int print_entry( return print_list(stdout, j, t); else if (arg_field) return print_field(stdout, j); + else if (sd_json_format_enabled(arg_json_format_flags)) + return print_info_json(stdout, j); else return print_info(stdout, j, n_found > 0); } -static int dump_list(int argc, char **argv, void *userdata) { +VERB(verb_dump_list, "list", "[MATCHES…]", VERB_ANY, VERB_ANY, VERB_DEFAULT, + "List available coredumps"); +VERB(verb_dump_list, "info", "[MATCHES…]", VERB_ANY, VERB_ANY, 0, + "Show detailed information about one or more coredumps"); +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; @@ -907,7 +1035,7 @@ static int dump_list(int argc, char **argv, void *userdata) { (void) table_set_align_percent(t, TABLE_HEADER_CELL(7), 100); table_set_ersatz_string(t, TABLE_ERSATZ_DASH); - } else + } else if (!sd_json_format_enabled(arg_json_format_flags)) pager_open(arg_pager_flags); /* "info" without pattern implies "-1" */ @@ -1080,7 +1208,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 +1219,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 +1230,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 +1251,9 @@ 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) { +VERB(verb_dump_core, "dump", "[MATCHES…]", VERB_ANY, VERB_ANY, 0, + "Print first matching coredump to stdout"); +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 +1289,11 @@ static int dump_core(int argc, char **argv, void *userdata) { return 0; } -static int run_debug(int argc, char **argv, void *userdata) { +VERB(verb_run_debug, "debug", "[MATCHES…]", VERB_ANY, VERB_ANY, 0, + "Start a debugger for the first matching coredump"); +VERB(verb_run_debug, "gdb", "[MATCHES…]", VERB_ANY, VERB_ANY, 0, + /* help= */ NULL); +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, @@ -1356,30 +1483,16 @@ static int check_units_active(void) { return c; } -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 }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_freep) char *mounted_dir = NULL; + char **args = NULL; int r, units_active; setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -1409,7 +1522,7 @@ static int run(int argc, char *argv[]) { return log_oom(); } - r = coredumpctl_main(argc, argv); + r = dispatch_verb(args, NULL); if (units_active > 0) printf("%s-- Notice: %d systemd-coredump@.service %s, output may be incomplete.%s\n", diff --git a/src/coredump/meson.build b/src/coredump/meson.build index 355993d09ef1f..b0753d86fa882 100644 --- a/src/coredump/meson.build +++ b/src/coredump/meson.build @@ -30,7 +30,6 @@ common_dependencies = [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ] executables += [ diff --git a/src/creds/creds.c b/src/creds/creds.c index bcdf3d30384b0..6dc4ea9277d0a 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); if (r < 0) return r; @@ -305,7 +305,8 @@ 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_DEFAULT_NOARG(verb_list, "list", "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 +399,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 +410,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 +466,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 +503,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 +525,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 +535,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 +554,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 +664,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 +748,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 +763,168 @@ 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; + + printf("\nSee the %s for details.\n", link); + return 0; +} - int c, r; +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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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(opts.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(opts.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(opts.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=", opts.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(opts.arg)) { + arg_name = NULL; + arg_name_any = true; + break; } + + if (!credential_name_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", opts.arg); + + arg_name = opts.arg; + arg_name_any = false; break; - case 'p': - arg_pretty = true; + OPTION_LONG("timestamp", "TIME", + "Include specified timestamp in encrypted credential"): + r = parse_timestamp(opts.arg, &arg_timestamp); + if (r < 0) + return log_error_errno(r, "Failed to parse timestamp: %s", opts.arg); + break; + + OPTION_LONG("not-after", "TIME", + "Include specified invalidation time in encrypted credential"): + r = parse_timestamp(opts.arg, &arg_not_after); + if (r < 0) + return log_error_errno(r, "Failed to parse --not-after= timestamp: %s", opts.arg); break; - case ARG_WITH_KEY: - if (streq(optarg, "help")) { + OPTION_LONG("with-key", "KEY", + "Which keys to encrypt with (host, tpm2, host+tpm2, null, auto, auto-initrd)"): + if (streq(opts.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(opts.arg)) arg_with_key = _CRED_AUTO; else { - CredKeyType t = cred_key_type_from_string(optarg); + CredKeyType t = cred_key_type_from_string(opts.arg); if (t < 0) return log_error_errno(t, "Failed to parse key type: %m"); @@ -969,129 +932,96 @@ 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(opts.arg, "list")) return tpm2_list_devices(arg_legend, arg_quiet); - arg_tpm2_device = streq(optarg, "auto") ? NULL : optarg; + arg_tpm2_device = streq(opts.arg, "auto") ? NULL : opts.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(opts.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(opts.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(opts.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(opts.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(opts.arg)) arg_uid = UID_INVALID; - else if (streq(optarg, "self")) + else if (streq(opts.arg, "self")) arg_uid = getuid(); else { - const char *name = optarg; - r = get_user_creds( - &name, + opts.arg, + /* flags= */ 0, + /* ret_username= */ NULL, &arg_uid, /* ret_gid= */ NULL, /* ret_home= */ NULL, - /* ret_shell= */ NULL, - /* flags= */ 0); + /* ret_shell= */ NULL); if (r < 0) return log_error_errno(r, "Failed to resolve user '%s': %s", - optarg, STRERROR_USER(r)); + opts.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 +1050,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(&opts); 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 +1469,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(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/creds/meson.build b/src/creds/meson.build index a6e66495b6059..c18fe2ec8901d 100644 --- a/src/creds/meson.build +++ b/src/creds/meson.build @@ -11,8 +11,7 @@ executables += [ 'sources' : files('creds.c'), 'dependencies' : [ libmount_cflags, - libopenssl, - threads, + libopenssl_cflags, ], }, ] diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c index 822cf26c896a3..b315282df1e85 100644 --- a/src/cryptenroll/cryptenroll-fido2.c +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -3,6 +3,7 @@ #include "alloc-util.h" #include "ask-password-api.h" #include "cryptenroll-fido2.h" +#include "cryptenroll-varlink.h" #include "cryptsetup-fido2.h" #include "cryptsetup-util.h" #include "fido2-util.h" @@ -15,11 +16,9 @@ #include "string-util.h" int load_volume_key_fido2( + const EnrollContext *c, struct crypt_device *cd, - const char *cd_node, - const char *device, - void *ret_vk, - size_t *ret_vks) { + struct iovec *ret_vk) { #if HAVE_LIBFIDO2 _cleanup_(erase_and_freep) void *decrypted_key = NULL; @@ -28,19 +27,19 @@ int load_volume_key_fido2( ssize_t passphrase_size; int r; + assert_se(c); + assert_se(c->node); assert_se(cd); - assert_se(cd_node); assert_se(ret_vk); - assert_se(ret_vks); r = acquire_fido2_key_auto( cd, - cd_node, - cd_node, - device, + c->node, + c->node, + c->unlock_fido2_device, /* until= */ 0, "cryptenroll.fido2-pin", - ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED, + ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED|(c->interactive ? 0 : ASK_PASSWORD_HEADLESS), &decrypted_key, &decrypted_key_size); if (r == -EAGAIN) @@ -54,11 +53,11 @@ 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, - ret_vks, + ret_vk->iov_base, + &ret_vk->iov_len, passphrase, passphrase_size); if (r < 0) @@ -71,13 +70,9 @@ int load_volume_key_fido2( } int enroll_fido2( + const EnrollContext *c, struct crypt_device *cd, - const struct iovec *volume_key, - const char *device, - Fido2EnrollFlags lock_with, - int cred_alg, - const char *salt_file, - bool parameters_in_header) { + const struct iovec *volume_key) { #if HAVE_LIBFIDO2 _cleanup_(iovec_done_erase) struct iovec salt = {}; @@ -88,20 +83,22 @@ int enroll_fido2( size_t cid_size, secret_size; _cleanup_free_ void *cid = NULL; ssize_t base64_encoded_size; + Fido2EnrollFlags lock_with; /* receives the flags actually locked with, see below */ const char *node, *un; int r, keyslot; + assert_se(c); assert_se(cd); assert_se(iovec_is_set(volume_key)); - assert_se(device); + assert_se(c->fido2_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) + if (c->fido2_salt_file) r = fido2_read_salt_file( - salt_file, + c->fido2_salt_file, /* offset= */ UINT64_MAX, /* client= */ "cryptenroll", /* node= */ un, @@ -111,8 +108,14 @@ int enroll_fido2( if (r < 0) return r; + /* If we are operating over a Varlink connection that requested progress reports, let the client + * know a token touch is imminent before we enter the (blocking) FIDO2 operations below. */ + r = enroll_context_notify_state(c, "touch"); + if (r < 0) + return r; + r = fido2_generate_hmac_hash( - device, + c->fido2_device, /* rp_id= */ "io.systemd.cryptsetup", /* rp_name= */ "Encrypted Volume", /* user_id= */ un, strlen(un), /* We pass the user ID and name as the same: the disk's UUID if we have it */ @@ -121,8 +124,10 @@ int enroll_fido2( /* user_icon= */ NULL, /* askpw_icon= */ "drive-harddisk", /* askpw_credential= */ "cryptenroll.fido2-pin", - lock_with, - cred_alg, + c->interactive ? 0 : ASK_PASSWORD_HEADLESS, + c->fido2_pin, + c->fido2_lock_with, + c->fido2_cred_alg != 0 ? c->fido2_cred_alg : COSE_ES256, &salt, &cid, &cid_size, &secret, &secret_size, @@ -140,7 +145,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, @@ -150,7 +155,7 @@ int enroll_fido2( if (keyslot < 0) return log_error_errno(keyslot, "Failed to add new FIDO2 key to %s: %m", node); - if (parameters_in_header) { + if (c->fido2_parameters_in_header) { if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) return log_oom(); diff --git a/src/cryptenroll/cryptenroll-fido2.h b/src/cryptenroll/cryptenroll-fido2.h index 7d0c25c02a957..c5d57f12dbad3 100644 --- a/src/cryptenroll/cryptenroll-fido2.h +++ b/src/cryptenroll/cryptenroll-fido2.h @@ -1,7 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "cryptenroll.h" #include "shared-forward.h" -int load_volume_key_fido2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks); -int enroll_fido2(struct crypt_device *cd, const struct iovec *volume_key, const char *device, Fido2EnrollFlags lock_with, int cred_alg, const char *salt_file, bool parameters_in_header); +int load_volume_key_fido2(const EnrollContext *c, struct crypt_device *cd, struct iovec *ret_vk); +int enroll_fido2(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key); diff --git a/src/cryptenroll/cryptenroll-interactive.c b/src/cryptenroll/cryptenroll-interactive.c new file mode 100644 index 0000000000000..6dc584b18b462 --- /dev/null +++ b/src/cryptenroll/cryptenroll-interactive.c @@ -0,0 +1,243 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "ansi-color.h" +#include "cryptenroll.h" +#include "cryptenroll-interactive.h" +#include "cryptenroll-list.h" +#include "cryptsetup-util.h" +#include "glyph-util.h" +#include "libfido2-util.h" +#include "log.h" +#include "proc-cmdline.h" +#include "prompt-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" + +static int collect_existing_types(struct crypt_device *cd, unsigned *ret_mask) { + _cleanup_free_ EnrolledSlot *slots = NULL; + size_t n_slots; + unsigned mask = 0; + int r; + + assert(cd); + assert(ret_mask); + + r = collect_enrolled_slots(cd, &slots, &n_slots); + if (r < 0) + return r; + + FOREACH_ARRAY(s, slots, n_slots) + if (!s->conflict && s->type >= 0) + mask |= 1U << s->type; + + *ret_mask = mask; + return 0; +} + +static int choose_mechanism(EnrollContext *c) { + int r; + + assert(c); + + /* Top-level menu. Each iteration re-enumerates the FIDO2 tokens, so a token plugged in after the + * menu was first shown appears once the user picks "Rescan". An empty answer leaves the volume + * untouched. */ + + for (;;) { + Fido2DeviceInfo *devices = NULL; + size_t n_devices = 0; + CLEANUP_ARRAY(devices, n_devices, fido2_device_info_free_many); + + /* Best effort: if FIDO2 isn't available we simply offer no token rows. */ + (void) fido2_enumerate_devices(&devices, &n_devices); + + _cleanup_strv_free_ char **menu = strv_new("Enroll a recovery key", "Enroll a passphrase"); + if (!menu) + return log_oom(); + + FOREACH_ARRAY(d, devices, n_devices) { + _cleanup_free_ char *label = NULL; + + label = strjoin("Enroll FIDO2 security token: ", + d->manufacturer ?: "Security Token", + d->product ? " " : "", strempty(d->product), + " (", d->path, ")"); + if (!label) + return log_oom(); + + if (strv_consume(&menu, TAKE_PTR(label)) < 0) + return log_oom(); + } + + if (strv_extend(&menu, "Rescan for FIDO2 security tokens") < 0) + return log_oom(); + + _cleanup_free_ char *choice = NULL; + r = prompt_loop( + "Select enrollment option", + GLYPH_LOCK_AND_KEY, + /* prefill= */ NULL, + menu, + /* accepted= */ menu, /* only accept exact menu entries (or their numbers) */ + /* ellipsize_percentage= */ 60, + /* n_columns= */ 1, + /* column_width= */ SIZE_MAX, /* auto-size to the widest entry */ + /* is_valid= */ NULL, + /* refresh= */ NULL, + /* userdata= */ NULL, + PROMPT_MAY_SKIP|PROMPT_SHOW_MENU|PROMPT_SHOW_MENU_NOW|PROMPT_HIDE_MENU_HINT, + &choice); + if (r < 0) + return r; + if (!choice) { + /* Empty answer: do nothing. */ + log_info("No selection made, leaving volume unchanged."); + return 0; + } + + /* Map the chosen label back to its index. */ + size_t idx = SIZE_MAX; + STRV_FOREACH(m, menu) + if (streq(*m, choice)) { + idx = m - menu; + break; + } + + assert(idx != SIZE_MAX); + + if (idx == 0) + c->enroll_type = ENROLL_RECOVERY; + else if (idx == 1) + c->enroll_type = ENROLL_PASSWORD; + else if (idx + 1 == strv_length(menu)) /* "Rescan" */ + continue; + else { + c->enroll_type = ENROLL_FIDO2; + assert(idx > 1); + assert(idx + 1 < strv_length(menu)); + + if (strdup_to(&c->fido2_device, devices[idx - 2].path) < 0) + return log_oom(); + } + + return 1; + } +} + +static int ask_wipe(EnrollContext *c, unsigned existing_mask) { + static const EnrollType candidates[] = { + ENROLL_PASSWORD, + ENROLL_RECOVERY, + ENROLL_FIDO2, + }; + int r; + + assert(c); + + /* For each already-enrolled type the wizard understands, offer to wipe it alongside the new + * enrollment. */ + + FOREACH_ELEMENT(t, candidates) { + if (!FLAGS_SET(existing_mask, 1U << *t)) + continue; + + _cleanup_free_ char *question = NULL; + question = strjoin("A ", enroll_type_to_string(*t), " slot is already enrolled. Wipe it as part of this enrollment?"); + if (!question) + return log_oom(); + + bool wipe; + r = prompt_loop_yes_no(question, /* def= */ false, &wipe); + if (r < 0) + return r; + + if (wipe) + c->wipe_slots_mask |= 1U << *t; + } + + return 0; +} + +int cryptenroll_run_interactive( + EnrollContext *c, + unsigned prompt_suppress_mask, + bool chrome, + sd_varlink **mute_console_link) { + + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + unsigned existing_mask = 0; + int r; + + assert(c); + assert(c->node); + + /* Honour the systemd.firstboot= kernel command line option, just like systemd-firstboot. */ + bool enabled; + r = proc_cmdline_get_bool("systemd.firstboot", PROC_CMDLINE_TRUE_WHEN_MISSING, &enabled); + if (r < 0) + log_warning_errno(r, "Failed to parse systemd.firstboot= kernel command line option, ignoring: %m"); + else if (!enabled) { + log_debug("systemd.firstboot=no set, skipping interactive enrollment."); + return 0; + } + + /* Open the volume just to inspect its header (no unlocking needed yet). */ + r = prepare_luks(c, &cd, /* ret_volume_key= */ NULL); + if (r < 0) + return r; + + r = collect_existing_types(cd, &existing_mask); + if (r < 0) + return r; + + /* If a credential of a suppressed type is already enrolled, do nothing. This lets the wizard be + * wired into first boot but stay quiet once the system has been set up. */ + if ((existing_mask & prompt_suppress_mask) != 0) { + log_debug("A credential of a suppressed type is already enrolled, skipping interactive setup."); + return 0; + } + + if (existing_mask == 0) { + log_debug("No recognized LUKS slots, we're unlikely able to unlock, skipping interactive setup."); + return 0; + } + + (void) mute_console(mute_console_link); + + /* Draw the installer-style chrome (blue bars at the top and bottom) around the wizard, matching + * systemd-sysinstall. The caller hides it again via a deferred chrome_hide() once enrollment is + * complete. */ + (void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0); + + if (chrome) + (void) chrome_show("Additional Disk Encryption Key Enrollment", /* bottom= */ NULL); + + printf("%s%s%sLet's enroll additional disk encryption mechanisms for recovering access to the system.%s\n\n", + emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "", + ansi_highlight(), ansi_normal()); + + _cleanup_free_ char *s = NULL; + for (EnrollType t = 0; t < _ENROLL_TYPE_MAX; t++) { + if (!FLAGS_SET(existing_mask, 1 << t)) + continue; + + if (!strextend_with_separator(&s, ", ", enroll_type_to_string(t))) + return log_oom(); + } + + printf("Currently enrolled mechanisms: %s\n\n", s); + + r = choose_mechanism(c); + if (r <= 0) + return r; + + r = ask_wipe(c, existing_mask); + if (r < 0) + return r; + + return 1; +} diff --git a/src/cryptenroll/cryptenroll-interactive.h b/src/cryptenroll/cryptenroll-interactive.h new file mode 100644 index 0000000000000..413e0d8b93046 --- /dev/null +++ b/src/cryptenroll/cryptenroll-interactive.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "cryptenroll.h" + +/* Runs the interactive first-boot enrollment wizard, populating *c (c->enroll_type, c->fido2_device, + * c->wipe_slots_mask). On return c->enroll_type is set to the chosen mechanism, or left + * _ENROLL_TYPE_INVALID if the user skipped or the wizard was suppressed. + * + * prompt_suppress_mask is a bitmask of (1U << EnrollType): if a slot of any such type already exists on the + * volume, the wizard does nothing (so it can be hooked into first boot but stay quiet on later boots). */ +int cryptenroll_run_interactive(EnrollContext *c, unsigned prompt_suppress_mask, bool chrome, sd_varlink **mute_console_link); diff --git a/src/cryptenroll/cryptenroll-list.c b/src/cryptenroll/cryptenroll-list.c index 32e8c9cf2a32b..21d768b1a8a1a 100644 --- a/src/cryptenroll/cryptenroll-list.c +++ b/src/cryptenroll/cryptenroll-list.c @@ -11,34 +11,31 @@ #include "log.h" #include "parse-util.h" -struct keyslot_metadata { - int slot; - const char *type; -}; - -int list_enrolled(struct crypt_device *cd) { - _cleanup_free_ struct keyslot_metadata *keyslot_metadata = NULL; - _cleanup_(table_unrefp) Table *t = NULL; - size_t n_keyslot_metadata = 0; +int collect_enrolled_slots(struct crypt_device *cd, EnrolledSlot **ret, size_t *ret_n) { + _cleanup_free_ EnrolledSlot *slots = NULL; + size_t n_slots = 0; int slot_max, r; - TableCell *cell; assert(cd); + assert(ret); + assert(ret_n); - /* First step, find out all currently used slots */ - assert_se((slot_max = crypt_keyslot_max(CRYPT_LUKS2)) > 0); + /* First step, find out all currently used slots. A slot without an associated token is a bare + * passphrase slot, hence default to ENROLL_PASSWORD. */ + 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; - if (!GREEDY_REALLOC(keyslot_metadata, n_keyslot_metadata+1)) + if (!GREEDY_REALLOC(slots, n_slots + 1)) return log_oom(); - keyslot_metadata[n_keyslot_metadata++] = (struct keyslot_metadata) { + slots[n_slots++] = (EnrolledSlot) { .slot = slot, + .type = ENROLL_PASSWORD, }; } @@ -46,7 +43,6 @@ int list_enrolled(struct crypt_device *cd) { * token they are assigned to */ for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - const char *type; sd_json_variant *w, *z; EnrollType et; @@ -64,11 +60,7 @@ int list_enrolled(struct crypt_device *cd) { continue; } - et = luks2_token_type_from_string(sd_json_variant_string(w)); - if (et < 0) - type = "other"; - else - type = enroll_type_to_string(et); + et = luks2_token_type_from_string(sd_json_variant_string(w)); /* _ENROLL_TYPE_INVALID for unrecognized type */ w = sd_json_variant_by_key(v, "keyslots"); if (!w || !sd_json_variant_is_array(w)) { @@ -90,19 +82,40 @@ int list_enrolled(struct crypt_device *cd) { continue; } - for (size_t i = 0; i < n_keyslot_metadata; i++) { - if ((unsigned) keyslot_metadata[i].slot != u) + FOREACH_ARRAY(s, slots, n_slots) { + if ((unsigned) s->slot != u) continue; - if (keyslot_metadata[i].type) /* Slot claimed multiple times? */ - keyslot_metadata[i].type = POINTER_MAX; + if (s->conflict) /* Already marked as claimed multiple times. */ + break; + + if (s->type != ENROLL_PASSWORD) /* Slot already claimed by another token? */ + s->conflict = true; else - keyslot_metadata[i].type = type; + s->type = et; } } } - /* Finally, create a table out of it all */ + *ret = TAKE_PTR(slots); + *ret_n = n_slots; + return 0; +} + +int list_enrolled(struct crypt_device *cd) { + _cleanup_free_ EnrolledSlot *slots = NULL; + _cleanup_(table_unrefp) Table *t = NULL; + size_t n_slots; + int r; + TableCell *cell; + + assert(cd); + + r = collect_enrolled_slots(cd, &slots, &n_slots); + if (r < 0) + return r; + + /* Create a table out of it all */ t = table_new("slot", "type"); if (!t) return log_oom(); @@ -110,12 +123,20 @@ int list_enrolled(struct crypt_device *cd) { assert_se(cell = table_get_cell(t, 0, 0)); (void) table_set_align_percent(t, cell, 100); - for (size_t i = 0; i < n_keyslot_metadata; i++) { + FOREACH_ARRAY(s, slots, n_slots) { + const char *type; + + if (s->conflict) + type = "conflict"; + else if (s->type < 0) + type = "other"; /* token of unrecognized type */ + else + type = enroll_type_to_string(s->type); + r = table_add_many( t, - TABLE_INT, keyslot_metadata[i].slot, - TABLE_STRING, keyslot_metadata[i].type == POINTER_MAX ? "conflict" : - keyslot_metadata[i].type ?: "password"); + TABLE_INT, s->slot, + TABLE_STRING, type); if (r < 0) return table_log_add_error(r); } @@ -125,9 +146,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-list.h b/src/cryptenroll/cryptenroll-list.h index 9097bc42c120b..3366336713984 100644 --- a/src/cryptenroll/cryptenroll-list.h +++ b/src/cryptenroll/cryptenroll-list.h @@ -1,6 +1,19 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "cryptenroll.h" #include "shared-forward.h" +typedef struct EnrolledSlot { + int slot; + /* The token type associated with the slot. ENROLL_PASSWORD means a bare passphrase slot without + * any token. _ENROLL_TYPE_INVALID means either a token of an unrecognized type, or a slot claimed + * by more than one token (see 'conflict'). */ + EnrollType type; + bool conflict; +} EnrolledSlot; + +/* Enumerates the active keyslots and classifies each by token type. Returns a newly allocated array. */ +int collect_enrolled_slots(struct crypt_device *cd, EnrolledSlot **ret, size_t *ret_n); + int list_enrolled(struct crypt_device *cd); diff --git a/src/cryptenroll/cryptenroll-password.c b/src/cryptenroll/cryptenroll-password.c index 26503187133b4..22b64e3229aba 100644 --- a/src/cryptenroll/cryptenroll-password.c +++ b/src/cryptenroll/cryptenroll-password.c @@ -7,49 +7,130 @@ #include "env-util.h" #include "errno-util.h" #include "escape.h" +#include "fileio.h" #include "iovec-util.h" #include "log.h" #include "password-quality-util.h" #include "string-util.h" #include "strv.h" +int load_volume_key_empty( + const EnrollContext *c, + struct crypt_device *cd, + struct iovec *ret_vk) { + + int r; + + assert_se(c); + assert_se(cd); + assert_se(ret_vk); + + r = sym_crypt_volume_key_get( + cd, + CRYPT_ANY_SLOT, + ret_vk->iov_base, + &ret_vk->iov_len, + "", + 0); + if (r < 0) + return log_error_errno(r, "Provided empty password did not work: %m"); + + return r; +} + +int load_volume_key_keyfile( + const EnrollContext *c, + struct crypt_device *cd, + struct iovec *ret_vk) { + + _cleanup_(erase_and_freep) char *password = NULL; + size_t password_len; + int r; + + assert_se(c); + assert_se(cd); + assert_se(ret_vk); + + r = read_full_file_full( + AT_FDCWD, + c->unlock_keyfile, + UINT64_MAX, + 4U * U64_MB, /* safety net */ + READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET|READ_FULL_FILE_FAIL_WHEN_LARGER, + NULL, + &password, + &password_len); + if (r < 0) + return log_error_errno(r, "Reading keyfile %s failed: %m", c->unlock_keyfile); + + r = sym_crypt_volume_key_get( + cd, + CRYPT_ANY_SLOT, + ret_vk->iov_base, + &ret_vk->iov_len, + password, + password_len); + if (r < 0) + return log_error_errno(r, "Unlocking via keyfile failed: %m"); + + return r; +} + int load_volume_key_password( + const EnrollContext *c, struct crypt_device *cd, - const char *cd_node, - void *ret_vk, - size_t *ret_vks) { + struct iovec *ret_vk) { _cleanup_(erase_and_freep) char *envpw = NULL; int r; + assert_se(c); + assert_se(c->node); assert_se(cd); - assert_se(cd_node); assert_se(ret_vk); - assert_se(ret_vks); + + if (c->unlock_password) { + r = sym_crypt_volume_key_get( + cd, + CRYPT_ANY_SLOT, + ret_vk->iov_base, + &ret_vk->iov_len, + c->unlock_password, + strlen(c->unlock_password)); + if (r < 0) + return log_error_errno(r, "Provided unlock password did not work: %m"); + + return r; + } r = getenv_steal_erase("PASSWORD", &envpw); 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, - ret_vks, + ret_vk->iov_base, + &ret_vk->iov_len, envpw, strlen(envpw)); if (r < 0) return log_error_errno(r, "Password from environment variable $PASSWORD did not work: %m"); } else { AskPasswordFlags ask_password_flags = ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED; + + if (!c->interactive) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), + "Password querying disabled via 'headless' option, but no password provided for disk %s.", + c->node); _cleanup_free_ char *question = NULL, *id = NULL, *disk_path = NULL; unsigned i = 5; - question = strjoin("Please enter current passphrase for disk ", cd_node, ":"); + question = strjoin("Please enter current passphrase for disk ", c->node, ":"); if (!question) return log_oom(); - disk_path = cescape(cd_node); + disk_path = cescape(c->node); if (!disk_path) return log_oom(); @@ -81,11 +162,11 @@ 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, - ret_vks, + ret_vk->iov_base, + &ret_vk->iov_len, *p, strlen(*p)); if (r >= 0) @@ -103,6 +184,7 @@ int load_volume_key_password( } int enroll_password( + const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key) { @@ -111,19 +193,31 @@ int enroll_password( const char *node; int r, keyslot; + assert(c); 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) - return log_error_errno(r, "Failed to acquire password from environment: %m"); - if (r == 0) { + if (c->passphrase) { + new_password = memdup_suffix0(c->passphrase, c->passphrase_size); + if (!new_password) + return log_oom(); + } else { + r = getenv_steal_erase("NEWPASSWORD", &new_password); + if (r < 0) + return log_error_errno(r, "Failed to acquire password from environment: %m"); + } + + if (!new_password) { _cleanup_free_ char *disk_path = NULL, *id = NULL; unsigned i = 5; - assert_se(node = crypt_get_device_name(cd)); + if (!c->interactive) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), + "Password querying disabled via 'headless' option, but no new password provided."); + + assert_se(node = sym_crypt_get_device_name(cd)); (void) suggest_passwords(); @@ -196,7 +290,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-password.h b/src/cryptenroll/cryptenroll-password.h index f0c97ced29871..035a1becc5d1a 100644 --- a/src/cryptenroll/cryptenroll-password.h +++ b/src/cryptenroll/cryptenroll-password.h @@ -1,7 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "cryptenroll.h" #include "shared-forward.h" -int load_volume_key_password(struct crypt_device *cd, const char* cd_node, void *ret_vk, size_t *ret_vks); -int enroll_password(struct crypt_device *cd, const struct iovec *volume_key); +int load_volume_key_empty(const EnrollContext *c, struct crypt_device *cd, struct iovec *ret_vk); +int load_volume_key_keyfile(const EnrollContext *c, struct crypt_device *cd, struct iovec *ret_vk); + +int load_volume_key_password(const EnrollContext *c, struct crypt_device *cd, struct iovec *ret_vk); +int enroll_password(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key); diff --git a/src/cryptenroll/cryptenroll-pkcs11.c b/src/cryptenroll/cryptenroll-pkcs11.c index 18ffb2edc3e8b..c46fcca10a125 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); @@ -34,7 +36,7 @@ static int uri_set_private_class(const char *uri, char **ret_uri) { } #endif -int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key,const char *uri) { +int enroll_pkcs11(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key) { #if HAVE_P11KIT && HAVE_OPENSSL _cleanup_(erase_and_freep) void *decrypted_key = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL; @@ -43,28 +45,33 @@ int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key,const size_t decrypted_key_size, saved_key_size; _cleanup_free_ void *saved_key = NULL; _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + Pkcs11RsaPadding rsa_padding = _PKCS11_RSA_PADDING_INVALID; ssize_t base64_encoded_size; const char *node; int r; + assert_se(c); assert_se(cd); assert_se(iovec_is_set(volume_key)); - assert_se(uri); + assert_se(c->pkcs11_token_uri); - assert_se(node = crypt_get_device_name(cd)); + assert_se(node = sym_crypt_get_device_name(cd)); r = pkcs11_acquire_public_key( - uri, + c->pkcs11_token_uri, "volume enrollment operation", "drive-harddisk", "cryptenroll.pkcs11-pin", - /* askpw_flags= */ 0, + c->interactive ? 0 : ASK_PASSWORD_HEADLESS, &pkey, + &rsa_padding, /* ret_pin_used= */ NULL); if (r < 0) return r; - r = pkey_generate_volume_keys(pkey, &decrypted_key, &decrypted_key_size, &saved_key, &saved_key_size); + r = pkey_generate_volume_keys(pkey, + pkcs11_rsa_padding_to_oaep_hash(rsa_padding), + &decrypted_key, &decrypted_key_size, &saved_key, &saved_key_size); if (r < 0) return log_error_errno(r, "Failed to generate volume keys: %m"); @@ -78,7 +85,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, @@ -94,15 +101,17 @@ int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key,const /* Change 'type=cert' or 'type=public' in the provided URI to 'type=private' before storing in a LUKS2 header. This allows users to use output of some PKCS#11 tools directly without modifications. */ - r = uri_set_private_class(uri, &private_uri); + r = uri_set_private_class(c->pkcs11_token_uri, &private_uri); if (r < 0) return r; r = sd_json_buildo(&v, SD_JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-pkcs11")), SD_JSON_BUILD_PAIR("keyslots", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_STRING(keyslot_as_string))), - SD_JSON_BUILD_PAIR_STRING("pkcs11-uri", private_uri ?: uri), - SD_JSON_BUILD_PAIR_BASE64("pkcs11-key", saved_key, saved_key_size)); + SD_JSON_BUILD_PAIR_STRING("pkcs11-uri", private_uri ?: c->pkcs11_token_uri), + SD_JSON_BUILD_PAIR_BASE64("pkcs11-key", saved_key, saved_key_size), + SD_JSON_BUILD_PAIR_CONDITION(rsa_padding > PKCS11_RSA_PADDING_PKCS1V15, + "pkcs11-padding", SD_JSON_BUILD_STRING(pkcs11_rsa_padding_to_string(rsa_padding)))); if (r < 0) return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m"); diff --git a/src/cryptenroll/cryptenroll-pkcs11.h b/src/cryptenroll/cryptenroll-pkcs11.h index b3e6e4584af93..614c6d466cd78 100644 --- a/src/cryptenroll/cryptenroll-pkcs11.h +++ b/src/cryptenroll/cryptenroll-pkcs11.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "cryptenroll.h" #include "shared-forward.h" -int enroll_pkcs11(struct crypt_device *cd, const struct iovec *volume_key, const char *uri); +int enroll_pkcs11(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key); diff --git a/src/cryptenroll/cryptenroll-recovery.c b/src/cryptenroll/cryptenroll-recovery.c index f9a588da26a3e..da7b7a1018dd3 100644 --- a/src/cryptenroll/cryptenroll-recovery.c +++ b/src/cryptenroll/cryptenroll-recovery.c @@ -12,8 +12,10 @@ #include "recovery-key.h" int enroll_recovery( + const EnrollContext *c, struct crypt_device *cd, - const struct iovec *volume_key) { + const struct iovec *volume_key, + char **ret_recovery_key) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; _cleanup_(erase_and_freep) char *password = NULL; @@ -21,10 +23,11 @@ int enroll_recovery( int keyslot, r, q; const char *node; + assert_se(c); 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 +37,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, @@ -44,31 +47,33 @@ int enroll_recovery( if (keyslot < 0) return log_error_errno(keyslot, "Failed to add new recovery key to %s: %m", node); - fflush(stdout); - fprintf(stderr, - "A secret recovery key has been generated for this volume:\n\n" - " %s%s%s", - emoji_enabled() ? glyph(GLYPH_LOCK_AND_KEY) : "", - emoji_enabled() ? " " : "", - ansi_highlight()); - fflush(stderr); + if (c->interactive) { + fflush(stdout); + fprintf(stderr, + "A secret recovery key has been generated for this volume:\n\n" + " %s%s%s", + emoji_enabled() ? glyph(GLYPH_LOCK_AND_KEY) : "", + emoji_enabled() ? " " : "", + ansi_highlight()); + fflush(stderr); - fputs(password, stdout); - fflush(stdout); + fputs(password, stdout); + fflush(stdout); - fputs(ansi_normal(), stderr); - fflush(stderr); + fputs(ansi_normal(), stderr); + fflush(stderr); - fputc('\n', stdout); - fflush(stdout); + fputc('\n', stdout); + fflush(stdout); - fputs("\nPlease save this secret recovery key at a secure location. It may be used to\n" - "regain access to the volume if the other configured access credentials have\n" - "been lost or forgotten. The recovery key may be entered in place of a password\n" - "whenever authentication is requested.\n", stderr); - fflush(stderr); + fputs("\nPlease save this secret recovery key at a secure location. It may be used to\n" + "regain access to the volume if the other configured access credentials have\n" + "been lost or forgotten. The recovery key may be entered in place of a password\n" + "whenever authentication is requested.\n", stderr); + fflush(stderr); - (void) print_qrcode(stderr, "Optionally scan the recovery key for safekeeping", password); + (void) print_qrcode(stderr, "Optionally scan the recovery key for safekeeping", password); + } if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) { r = log_oom(); @@ -89,11 +94,16 @@ int enroll_recovery( goto rollback; } - log_info("New recovery key enrolled as key slot %i.", keyslot); + if (c->interactive) + log_info("New recovery key enrolled as key slot %i.", keyslot); + + if (ret_recovery_key) + *ret_recovery_key = TAKE_PTR(password); + 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-recovery.h b/src/cryptenroll/cryptenroll-recovery.h index 0c76d86108a7b..1655f714453a7 100644 --- a/src/cryptenroll/cryptenroll-recovery.h +++ b/src/cryptenroll/cryptenroll-recovery.h @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "cryptenroll.h" #include "shared-forward.h" -int enroll_recovery(struct crypt_device *cd, const struct iovec *volume_key); +/* When c->interactive is unset, the generated recovery key is returned via ret_recovery_key + * instead of being printed to stdout/stderr + rendered as a QR code. */ +int enroll_recovery(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key, char **ret_recovery_key); diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index 50abca43639b8..eb08abdec6a56 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. */ } } @@ -169,11 +169,9 @@ static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) { #endif int load_volume_key_tpm2( + const EnrollContext *c, struct crypt_device *cd, - const char *cd_node, - const char *device, - void *ret_vk, - size_t *ret_vks) { + struct iovec *ret_vk) { #if HAVE_TPM2 _cleanup_(iovec_done_erase) struct iovec decrypted_key = {}; @@ -181,10 +179,10 @@ int load_volume_key_tpm2( ssize_t passphrase_size; int r; + assert_se(c); + assert_se(c->node); assert_se(cd); - assert_se(cd_node); assert_se(ret_vk); - assert_se(ret_vks); bool found_some = false; int token = 0; /* first token to look at */ @@ -235,8 +233,8 @@ int load_volume_key_tpm2( found_some = true; r = acquire_tpm2_key( - cd_node, - device, + c->node, + c->unlock_tpm2_device, hash_pcr_mask, pcr_bank, &pubkey, @@ -255,7 +253,7 @@ int load_volume_key_tpm2( tpm2_flags, /* until= */ 0, "cryptenroll.tpm2-pin", - /* askpw_flags= */ 0, + c->interactive ? 0 : ASK_PASSWORD_HEADLESS, &decrypted_key); if (IN_SET(r, -EACCES, -ENOLCK)) return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed"); @@ -272,11 +270,11 @@ 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, - ret_vks, + ret_vk->iov_base, + &ret_vk->iov_len, passphrase, passphrase_size); if (r < 0) @@ -288,19 +286,9 @@ int load_volume_key_tpm2( #endif } -int enroll_tpm2(struct crypt_device *cd, +int enroll_tpm2(const EnrollContext *c, + struct crypt_device *cd, const struct iovec *volume_key, - const char *device, - uint32_t seal_key_handle, - const char *device_key, - Tpm2PCRValue *hash_pcr_values, - size_t n_hash_pcr_values, - const char *pcr_pubkey_path, - bool load_pcr_pubkey, - uint32_t pubkey_pcr_mask, - const char *signature_path, - bool use_pin, - const char *pcrlock_path, int *ret_slot_to_wipe) { #if HAVE_TPM2 @@ -314,6 +302,8 @@ int enroll_tpm2(struct crypt_device *cd, int r, keyslot, slot_to_wipe = -1; TPM2Flags flags = 0; uint16_t primary_alg = 0; + /* Mutable copy: cleared on the no-public-key fallback paths below. */ + uint32_t pubkey_pcr_mask = c->tpm2_public_key_pcr_mask; uint8_t binary_salt[SHA256_DIGEST_SIZE] = {}; /* * erase the salt, we'd rather attempt to not have this in a coredump @@ -323,15 +313,16 @@ int enroll_tpm2(struct crypt_device *cd, */ CLEANUP_ERASE(binary_salt); + assert(c); assert(cd); assert(iovec_is_set(volume_key)); - assert(tpm2_pcr_values_valid(hash_pcr_values, n_hash_pcr_values)); - assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask)); + assert(tpm2_pcr_values_valid(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values)); + assert(TPM2_PCR_MASK_VALID(c->tpm2_public_key_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) { + if (c->tpm2_pin) { r = get_pin(&pin_str, &flags); if (r < 0) return r; @@ -354,10 +345,10 @@ int enroll_tpm2(struct crypt_device *cd, } TPM2B_PUBLIC public = {}; - if (pcr_pubkey_path || load_pcr_pubkey) { - r = tpm2_load_pcr_public_key(pcr_pubkey_path, &pubkey.iov_base, &pubkey.iov_len); + if (c->tpm2_public_key || c->tpm2_load_public_key) { + r = tpm2_load_pcr_public_key(c->tpm2_public_key, &pubkey.iov_base, &pubkey.iov_len); if (r < 0) { - if (pcr_pubkey_path || signature_path || r != -ENOENT) + if (c->tpm2_public_key || c->tpm2_signature || r != -ENOENT) return log_error_errno(r, "Failed to read TPM PCR public key: %m"); log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m"); @@ -367,11 +358,11 @@ int enroll_tpm2(struct crypt_device *cd, if (r < 0) return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); - if (signature_path) { + if (c->tpm2_signature) { /* Also try to load the signature JSON object, to verify that our enrollment will work. * This is optional however, skip it if it's not explicitly provided. */ - r = tpm2_load_pcr_signature(signature_path, &signature_json); + r = tpm2_load_pcr_signature(c->tpm2_signature, &signature_json); if (r < 0) return log_error_errno(r, "Failed to read TPM PCR signature: %m"); } @@ -379,15 +370,15 @@ int enroll_tpm2(struct crypt_device *cd, } else pubkey_pcr_mask = 0; - bool any_pcr_value_specified = tpm2_pcr_values_has_any_values(hash_pcr_values, n_hash_pcr_values); + bool any_pcr_value_specified = tpm2_pcr_values_has_any_values(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values); _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy pcrlock_policy = {}; - if (pcrlock_path) { - r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy); + if (c->tpm2_pcrlock) { + r = tpm2_pcrlock_policy_load(c->tpm2_pcrlock, &pcrlock_policy); if (r < 0) return r; if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Couldn't find pcrlock policy %s.", pcrlock_path); + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Couldn't find pcrlock policy %s.", c->tpm2_pcrlock); any_pcr_value_specified = true; flags |= TPM2_FLAGS_USE_PCRLOCK; @@ -395,23 +386,23 @@ int enroll_tpm2(struct crypt_device *cd, _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; TPM2B_PUBLIC device_key_public = {}; - if (device_key) { - r = tpm2_load_public_key_file(device_key, &device_key_public); + if (c->tpm2_device_key) { + r = tpm2_load_public_key_file(c->tpm2_device_key, &device_key_public); if (r < 0) return r; - if (!tpm2_pcr_values_has_all_values(hash_pcr_values, n_hash_pcr_values)) + if (!tpm2_pcr_values_has_all_values(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Must provide all PCR values when using TPM2 device key."); primary_alg = device_key_public.publicArea.type; } else { - r = tpm2_context_new_or_warn(device, &tpm2_context); + r = tpm2_context_new_or_warn(c->tpm2_device, &tpm2_context); if (r < 0) return r; - if (!tpm2_pcr_values_has_all_values(hash_pcr_values, n_hash_pcr_values)) { - r = tpm2_pcr_read_missing_values(tpm2_context, hash_pcr_values, n_hash_pcr_values); + if (!tpm2_pcr_values_has_all_values(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values)) { + r = tpm2_pcr_read_missing_values(tpm2_context, c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values); if (r < 0) return log_error_errno(r, "Could not read pcr values: %m"); } @@ -420,10 +411,10 @@ int enroll_tpm2(struct crypt_device *cd, uint16_t hash_pcr_bank = 0; uint32_t hash_pcr_mask = 0; - if (n_hash_pcr_values > 0) { + if (c->tpm2_n_hash_pcr_values > 0) { size_t hash_count; - r = tpm2_pcr_values_hash_count(hash_pcr_values, n_hash_pcr_values, &hash_count); + r = tpm2_pcr_values_hash_count(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values, &hash_count); if (r < 0) return log_error_errno(r, "Could not get hash count: %m"); @@ -431,12 +422,12 @@ int enroll_tpm2(struct crypt_device *cd, return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR banks selected."); /* If we use a literal PCR value policy, derive the bank to use from the algorithm specified on the hash values */ - hash_pcr_bank = hash_pcr_values[0].hash; - r = tpm2_pcr_values_to_mask(hash_pcr_values, n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask); + hash_pcr_bank = c->tpm2_hash_pcr_values[0].hash; + r = tpm2_pcr_values_to_mask(c->tpm2_hash_pcr_values, c->tpm2_n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask); if (r < 0) return log_error_errno(r, "Could not get hash mask: %m"); - } else if (pubkey_pcr_mask != 0 && !device_key) { + } else if (pubkey_pcr_mask != 0 && !c->tpm2_device_key) { /* If no literal PCR value policy is used, then let's determine the mask to use automatically * from the measurements of the TPM. */ @@ -461,21 +452,21 @@ int enroll_tpm2(struct crypt_device *cd, /* If both PCR public key unlock and pcrlock unlock is selected, then we create the one for PCR public key unlock first. */ r = tpm2_calculate_sealing_policy( - hash_pcr_values, - n_hash_pcr_values, + c->tpm2_hash_pcr_values, + c->tpm2_n_hash_pcr_values, iovec_is_set(&pubkey) ? &public : NULL, - use_pin, - pcrlock_path && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL, + c->tpm2_pin, + c->tpm2_pcrlock && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL, policy_hash + 0); if (r < 0) return r; - if (pcrlock_path && iovec_is_set(&pubkey)) { + if (c->tpm2_pcrlock && iovec_is_set(&pubkey)) { r = tpm2_calculate_sealing_policy( - hash_pcr_values, - n_hash_pcr_values, + c->tpm2_hash_pcr_values, + c->tpm2_n_hash_pcr_values, /* public= */ NULL, /* This one is off now */ - use_pin, + c->tpm2_pin, &pcrlock_policy, /* And this one on instead. */ policy_hash + 1); if (r < 0) @@ -488,7 +479,7 @@ int enroll_tpm2(struct crypt_device *cd, size_t n_blobs = 0; CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free); - if (device_key) { + if (c->tpm2_device_key) { if (n_policy_hash > 1) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Combined signed PCR policies and pcrlock policies cannot be calculated offline, currently."); @@ -500,7 +491,7 @@ int enroll_tpm2(struct crypt_device *cd, n_blobs = 1; r = tpm2_calculate_seal( - seal_key_handle, + c->tpm2_seal_key_handle, &device_key_public, /* attributes= */ NULL, /* secret= */ NULL, @@ -511,7 +502,7 @@ int enroll_tpm2(struct crypt_device *cd, &srk); } else r = tpm2_seal(tpm2_context, - seal_key_handle, + c->tpm2_seal_key_handle, policy_hash, n_policy_hash, pin_str, @@ -534,7 +525,7 @@ int enroll_tpm2(struct crypt_device *cd, log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now."); else if (r < 0) return r; - else if (use_pin) { + else if (c->tpm2_pin) { log_debug("This PCR set is already enrolled, re-enrolling anyway to update PIN."); slot_to_wipe = r; } else { @@ -544,7 +535,7 @@ int enroll_tpm2(struct crypt_device *cd, } /* If possible, verify the sealed data object. */ - if ((!iovec_is_set(&pubkey) || signature_json) && !any_pcr_value_specified && !device_key) { + if ((!iovec_is_set(&pubkey) || signature_json) && !any_pcr_value_specified && !c->tpm2_device_key) { _cleanup_(iovec_done_erase) struct iovec secret2 = {}; log_debug("Unsealing for verification..."); @@ -555,7 +546,7 @@ int enroll_tpm2(struct crypt_device *cd, pubkey_pcr_mask, signature_json, pin_str, - pcrlock_path ? &pcrlock_policy : NULL, + c->tpm2_pcrlock ? &pcrlock_policy : NULL, primary_alg, blobs, n_blobs, @@ -566,7 +557,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 +570,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, @@ -600,9 +591,9 @@ int enroll_tpm2(struct crypt_device *cd, n_blobs, policy_hash_as_iovec, n_policy_hash, - use_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL, + c->tpm2_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL, &srk, - pcrlock_path ? &pcrlock_policy.nv_handle : NULL, + c->tpm2_pcrlock ? &pcrlock_policy.nv_handle : NULL, flags, &v); if (r < 0) diff --git a/src/cryptenroll/cryptenroll-tpm2.h b/src/cryptenroll/cryptenroll-tpm2.h index 48f6c12b7f196..3d57b8f8f38fb 100644 --- a/src/cryptenroll/cryptenroll-tpm2.h +++ b/src/cryptenroll/cryptenroll-tpm2.h @@ -1,7 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "cryptenroll.h" #include "shared-forward.h" -int load_volume_key_tpm2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks); -int enroll_tpm2(struct crypt_device *cd, const struct iovec *volume_key, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcr_values, size_t n_hash_pcr_values, const char *pubkey_path, bool load_pcr_pubkey, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path, int *ret_slot_to_wipe); +int load_volume_key_tpm2(const EnrollContext *c, struct crypt_device *cd, struct iovec *ret_vk); +int enroll_tpm2(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key, int *ret_slot_to_wipe); diff --git a/src/cryptenroll/cryptenroll-varlink.c b/src/cryptenroll/cryptenroll-varlink.c new file mode 100644 index 0000000000000..77ead09673191 --- /dev/null +++ b/src/cryptenroll/cryptenroll-varlink.c @@ -0,0 +1,481 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "bus-polkit.h" +#include "cryptenroll.h" +#include "cryptenroll-list.h" +#include "cryptenroll-varlink.h" +#include "cryptenroll-wipe.h" +#include "cryptsetup-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "iovec-util.h" +#include "json-util.h" +#include "libfido2-util.h" +#include "path-util.h" +#include "string-util.h" +#include "varlink-io.systemd.CryptEnroll.h" +#include "varlink-util.h" + +int enroll_context_notify_state(const EnrollContext *c, const char *state) { + int r; + + assert(c); + assert(state); + + /* Only relevant when invoked over a Varlink connection that asked for progress ('more'). */ + if (!c->link) + return 0; + + r = sd_varlink_notifybo(c->link, SD_JSON_BUILD_PAIR_STRING("state", state)); + if (r < 0) + return r; + + return sd_varlink_flush(c->link); +} + +static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_enroll_type, EnrollType, enroll_type_from_string); + +typedef struct MethodEnrollParameters { + char *node; + EnrollType mechanism; + char *unlock_password; + char *unlock_keyfile; + int64_t unlock_keyfile_fd_idx; + char *unlock_fido2_device; + char *unlock_tpm2_device; + char *password; + char *fido2_device; + char *fido2_pin; + int fido2_with_client_pin; + int fido2_with_user_presence; + int fido2_with_user_verification; + sd_json_variant *wipe_slots; + sd_json_variant *wipe_types; +} MethodEnrollParameters; + +static void method_enroll_parameters_done(MethodEnrollParameters *p) { + assert(p); + + free(p->node); + erase_and_free(p->unlock_password); + free(p->unlock_keyfile); + free(p->unlock_fido2_device); + free(p->unlock_tpm2_device); + erase_and_free(p->password); + free(p->fido2_device); + erase_and_free(p->fido2_pin); + sd_json_variant_unref(p->wipe_slots); + sd_json_variant_unref(p->wipe_types); +} + +static int parse_wipe_slots(sd_json_variant *v, EnrollContext *c) { + sd_json_variant *e; + + assert(c); + + JSON_VARIANT_ARRAY_FOREACH(e, v) { + if (!sd_json_variant_is_unsigned(e)) + return -EINVAL; + + uint64_t u = sd_json_variant_unsigned(e); + if (u > INT_MAX) + return -ERANGE; + + if (!GREEDY_REALLOC(c->wipe_slots, c->n_wipe_slots + 1)) + return -ENOMEM; + + c->wipe_slots[c->n_wipe_slots++] = (int) u; + } + + return 0; +} + +static int parse_wipe_types(sd_json_variant *v, EnrollContext *c) { + sd_json_variant *e; + + assert(c); + + JSON_VARIANT_ARRAY_FOREACH(e, v) { + if (!sd_json_variant_is_string(e)) + return -EINVAL; + + /* Translate the Varlink (underscore) spelling to the internal (dash) one before lookup. */ + _cleanup_free_ char *s = strdup(sd_json_variant_string(e)); + if (!s) + return -ENOMEM; + + EnrollType t = enroll_type_from_string(json_dashify(s)); + if (t < 0) + return -EINVAL; + + c->wipe_slots_mask |= 1U << t; + } + + return 0; +} + +static int varlink_error_for_enroll(sd_varlink *link, int error) { + assert(link); + assert(error < 0); + + /* Translates the errnos the enrollment/unlock helpers return into the interface's named errors, + * falling back to a plain errno error. */ + + switch (error) { + + case -EHOSTDOWN: /* check_for_homed() */ + return sd_varlink_error(link, "io.systemd.CryptEnroll.VolumeUnderForeignManagement", NULL); + + case -ENOPKG: /* credential querying disabled in headless mode but none provided */ + return sd_varlink_error(link, "io.systemd.CryptEnroll.PasswordRequired", NULL); + + case -EPERM: + case -ENOKEY: /* provided password/key did not unlock the volume */ + return sd_varlink_error(link, "io.systemd.CryptEnroll.PasswordIncorrect", NULL); + + case -ENODEV: + case -ENOTUNIQ: /* no (or no unique) FIDO2 device found */ + return sd_varlink_error(link, "io.systemd.CryptEnroll.FidoDeviceNotFound", NULL); + + case -ENOSTR: /* FIDO_ERR_ACTION_TIMEOUT */ + return sd_varlink_error(link, "io.systemd.CryptEnroll.FidoActionTimeout", NULL); + + default: + return sd_varlink_error_errno(link, error); + } +} + +static int vl_method_enroll( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + static const sd_json_dispatch_field dispatch_table[] = { + { "node", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(MethodEnrollParameters, node), SD_JSON_MANDATORY|SD_JSON_STRICT }, + { "mechanism", SD_JSON_VARIANT_STRING, json_dispatch_enroll_type, offsetof(MethodEnrollParameters, mechanism), SD_JSON_MANDATORY }, + { "unlockPassword", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, unlock_password), SD_JSON_NULLABLE }, + { "unlockKeyFile", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(MethodEnrollParameters, unlock_keyfile), SD_JSON_NULLABLE|SD_JSON_STRICT }, + { "unlockKeyFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int64, offsetof(MethodEnrollParameters, unlock_keyfile_fd_idx), SD_JSON_NULLABLE }, + { "unlockFido2Device", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, unlock_fido2_device), SD_JSON_NULLABLE }, + { "unlockTpm2Device", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, unlock_tpm2_device), SD_JSON_NULLABLE }, + { "password", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, password), SD_JSON_NULLABLE }, + { "fido2Device", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, fido2_device), SD_JSON_NULLABLE }, + { "fido2Pin", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(MethodEnrollParameters, fido2_pin), SD_JSON_NULLABLE }, + { "fido2WithClientPin", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodEnrollParameters, fido2_with_client_pin), SD_JSON_NULLABLE }, + { "fido2WithUserPresence", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodEnrollParameters, fido2_with_user_presence), SD_JSON_NULLABLE }, + { "fido2WithUserVerification", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodEnrollParameters, fido2_with_user_verification), SD_JSON_NULLABLE }, + { "wipeSlots", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MethodEnrollParameters, wipe_slots), SD_JSON_NULLABLE }, + { "wipeTypes", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MethodEnrollParameters, wipe_types), SD_JSON_NULLABLE }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + _cleanup_(method_enroll_parameters_done) MethodEnrollParameters p = { + .mechanism = _ENROLL_TYPE_INVALID, + .unlock_keyfile_fd_idx = -1, + .fido2_with_client_pin = -1, + .fido2_with_user_presence = -1, + .fido2_with_user_verification = -1, + }; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(iovec_done_erase) struct iovec vk = {}; + _cleanup_close_ int keyfile_fd = -EBADF; + Hashmap **polkit_registry = ASSERT_PTR(userdata); + int slot, r; + + assert(link); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.mechanism < 0) + return sd_varlink_error_invalid_parameter_name(link, "mechanism"); + + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + "io.systemd.cryptenroll.enroll", + /* details= */ NULL, + polkit_registry); + if (r <= 0) + return r; + + /* Populate the context. This is the Varlink equivalent of enroll_context_from_args(). */ + _cleanup_(enroll_context_done) EnrollContext c = ENROLL_CONTEXT_NULL; + c.interactive = false; + c.enroll_type = p.mechanism; + c.unlock_type = _UNLOCK_TYPE_INVALID; + + if (strdup_to(&c.node, p.node) < 0) + return -ENOMEM; + + if (p.unlock_password) { + if (c.unlock_type >= 0) + return sd_varlink_error_invalid_parameter_name(link, "unlockPassword"); + + c.unlock_password = TAKE_PTR(p.unlock_password); + c.unlock_type = UNLOCK_PASSWORD; + } + + if (p.unlock_keyfile || p.unlock_keyfile_fd_idx >= 0) { + if (c.unlock_type >= 0) + return sd_varlink_error_invalid_parameter_name(link, p.unlock_keyfile ? "unlockKeyFile" : "unlockKeyFileDescriptor"); + + if (p.unlock_keyfile && p.unlock_keyfile_fd_idx >= 0) + return sd_varlink_error_invalid_parameter_name(link, "unlockKeyFileDescriptor"); + + if (p.unlock_keyfile_fd_idx >= 0) { + keyfile_fd = sd_varlink_peek_dup_fd(link, p.unlock_keyfile_fd_idx); + if (keyfile_fd < 0) + return sd_varlink_error_invalid_parameter_name(link, "unlockKeyFileDescriptor"); + + if (asprintf(&c.unlock_keyfile, "/proc/self/fd/%i", keyfile_fd) < 0) + return -ENOMEM; + } else + c.unlock_keyfile = TAKE_PTR(p.unlock_keyfile); + + c.unlock_type = UNLOCK_KEYFILE; + } + + if (p.unlock_fido2_device) { + if (c.unlock_type >= 0) + return sd_varlink_error_invalid_parameter_name(link, "unlockFido2Device"); + + if (!streq(p.unlock_fido2_device, "auto")) { + if (!path_is_normalized(p.unlock_fido2_device) || !path_is_absolute(p.unlock_fido2_device)) + return sd_varlink_error_invalid_parameter_name(link, "unlockFido2Device"); + + if (strdup_to(&c.unlock_fido2_device, p.unlock_fido2_device) < 0) + return -ENOMEM; + } + + c.unlock_type = UNLOCK_FIDO2; + } + + if (p.unlock_tpm2_device) { + if (c.unlock_type >= 0) + return sd_varlink_error_invalid_parameter_name(link, "unlockTpm2Device"); + + if (!streq(p.unlock_tpm2_device, "auto")) { + if (!path_is_normalized(p.unlock_tpm2_device) || !path_is_absolute(p.unlock_tpm2_device)) + return sd_varlink_error_invalid_parameter_name(link, "unlockTpm2Device"); + + if (strdup_to(&c.unlock_tpm2_device, p.unlock_tpm2_device) < 0) + return -ENOMEM; + } + + c.unlock_type = UNLOCK_TPM2; + } + + /* If no unlock method is specified, return a recognizable error. We generate invalid parameter name + * for "unlockPassword", simply because it is the best-known unlock method */ + if (c.unlock_type < 0) + return sd_varlink_error_invalid_parameter_name(link, "unlockPassword"); + + /* Mechanism-specific parameters */ + switch (c.enroll_type) { + + case ENROLL_PASSWORD: + if (p.password) { + c.passphrase = TAKE_PTR(p.password); + c.passphrase_size = strlen(c.passphrase); + } + break; + + case ENROLL_RECOVERY: + break; + + case ENROLL_FIDO2: + /* enroll_fido2() requires a concrete device, so if none was given (NULL), discover one. */ + + if (streq_ptr(p.fido2_device, "auto")) + p.fido2_device = mfree(p.fido2_device); + + if (p.fido2_device) { + if (!path_is_normalized(p.fido2_device) || !path_is_absolute(p.fido2_device)) + return sd_varlink_error_invalid_parameter_name(link, "fido2Device"); + + r = strdup_to(&c.fido2_device, p.fido2_device); + if (r < 0) + return r; + } + + if (!c.fido2_device) { + r = fido2_find_device_auto(&c.fido2_device); + if (r < 0) + return varlink_error_for_enroll(link, r); + } + + if (p.fido2_with_client_pin >= 0) + SET_FLAG(c.fido2_lock_with, FIDO2ENROLL_PIN, p.fido2_with_client_pin); + if (p.fido2_with_user_presence >= 0) + SET_FLAG(c.fido2_lock_with, FIDO2ENROLL_UP, p.fido2_with_user_presence); + if (p.fido2_with_user_verification >= 0) + SET_FLAG(c.fido2_lock_with, FIDO2ENROLL_UV, p.fido2_with_user_verification); + + c.fido2_pin = TAKE_PTR(p.fido2_pin); + break; + + default: + return sd_varlink_error_invalid_parameter_name(link, "mechanism"); + } + + if (p.wipe_slots) { + r = parse_wipe_slots(p.wipe_slots, &c); + if (r == -ENOMEM) + return r; + if (r < 0) + return sd_varlink_error_invalid_parameter_name(link, "wipeSlots"); + } + if (p.wipe_types) { + r = parse_wipe_types(p.wipe_types, &c); + if (r == -ENOMEM) + return r; + if (r < 0) + return sd_varlink_error_invalid_parameter_name(link, "wipeTypes"); + } + + /* If the caller asked for 'more', remember the link so we can send progress (e.g. FIDO2 touch). */ + if (FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + c.link = sd_varlink_ref(link); + + r = DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + + r = prepare_luks(&c, &cd, &vk); + if (r < 0) + return varlink_error_for_enroll(link, r); + + _cleanup_(erase_and_freep) char *recovery_key = NULL; + slot = enroll_now(&c, cd, &vk, &recovery_key); + if (slot < 0) + return varlink_error_for_enroll(link, slot); + + /* Wipe any slots the caller selected, keeping the one we just enrolled. */ + c.wipe_except_slot = slot; + _cleanup_free_ int *wiped_slots = NULL; + size_t n_wiped_slots = 0; + r = wipe_slots(&c, cd, &wiped_slots, &n_wiped_slots); + if (r < 0) + return varlink_error_for_enroll(link, r); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *wiped_slots_json = NULL; + FOREACH_ARRAY(s, wiped_slots, n_wiped_slots) { + r = sd_json_variant_append_arrayb(&wiped_slots_json, SD_JSON_BUILD_INTEGER(*s)); + if (r < 0) + return r; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *rk = NULL; + if (recovery_key) { + r = sd_json_variant_new_string(&rk, recovery_key); + if (r < 0) + return r; + + sd_json_variant_sensitive(rk); + } + + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_INTEGER("slot", slot), + JSON_BUILD_PAIR_VARIANT_NON_NULL("wipedSlots", wiped_slots_json), + JSON_BUILD_PAIR_VARIANT_NON_NULL("recoveryKey", rk)); +} + +static int vl_method_list_slots( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + static const sd_json_dispatch_field dispatch_table[] = { + { "node", SD_JSON_VARIANT_STRING, json_dispatch_const_path, 0, SD_JSON_MANDATORY|SD_JSON_STRICT }, + {} + }; + + _cleanup_(enroll_context_done) EnrollContext c = ENROLL_CONTEXT_NULL; + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + _cleanup_free_ EnrolledSlot *slots = NULL; + const char *node = NULL; + size_t n_slots; + int r; + + assert(link); + + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &node); + if (r != 0) + return r; + + /* NB: No polkit authentication here for now, given this is read access only. */ + + c.interactive = false; + if (strdup_to(&c.node, node) < 0) + return -ENOMEM; + + r = DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + + /* We only need to read the LUKS2 header, no volume key required. */ + r = prepare_luks(&c, &cd, /* ret_volume_key= */ NULL); + if (r < 0) + return varlink_error_for_enroll(link, r); + + r = collect_enrolled_slots(cd, &slots, &n_slots); + if (r < 0) + return r; + + FOREACH_ARRAY(s, slots, n_slots) { + /* enroll_type_to_string() returns NULL for unrecognized types; conflicts are reported + * with no type too. The dash spelling is underscorified for the wire by the build macro. */ + const char *type = s->conflict ? NULL : enroll_type_to_string(s->type); + + r = sd_varlink_notifybo( + link, + SD_JSON_BUILD_PAIR_INTEGER("slot", s->slot), + JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY("type", type)); + if (r < 0) + return r; + } + + return sd_varlink_reply(link, NULL); +} + +int cryptenroll_varlink_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL; + int r; + + r = varlink_server_new( + &varlink_server, + SD_VARLINK_SERVER_ROOT_ONLY|SD_VARLINK_SERVER_MYSELF_ONLY|SD_VARLINK_SERVER_INPUT_SENSITIVE|SD_VARLINK_SERVER_INHERIT_USERDATA|SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT|SD_VARLINK_SERVER_HANDLE_SIGINT|SD_VARLINK_SERVER_HANDLE_SIGTERM, + &polkit_registry); + 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_CryptEnroll); + 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.CryptEnroll.Enroll", vl_method_enroll, + "io.systemd.CryptEnroll.ListSlots", vl_method_list_slots); + 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; +} diff --git a/src/cryptenroll/cryptenroll-varlink.h b/src/cryptenroll/cryptenroll-varlink.h new file mode 100644 index 0000000000000..89efede84ba3d --- /dev/null +++ b/src/cryptenroll/cryptenroll-varlink.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "cryptenroll.h" + +/* Sends a progress 'state' notification over c->link, if (and only if) the enrollment was triggered via a + * Varlink call with the 'more' flag set. A no-op (returning 0) otherwise. */ +int enroll_context_notify_state(const EnrollContext *c, const char *state); + +int cryptenroll_varlink_server(void); diff --git a/src/cryptenroll/cryptenroll-wipe.c b/src/cryptenroll/cryptenroll-wipe.c index 1ae92bf91b8fb..39e8593973af0 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; @@ -296,24 +296,29 @@ static bool slots_remain(struct crypt_device *cd, Set *wipe_slots, Set *keep_slo return false; } -int wipe_slots(struct crypt_device *cd, - const int explicit_slots[], - size_t n_explicit_slots, - WipeScope by_scope, - unsigned by_mask, - int except_slot) { +int wipe_slots(const EnrollContext *c, + struct crypt_device *cd, + int **ret_wiped_slots, + size_t *ret_n_wiped_slots) { _cleanup_set_free_ Set *wipe_slots = NULL, *wipe_tokens = NULL, *keep_slots = NULL; - _cleanup_free_ int *ordered_slots = NULL, *ordered_tokens = NULL; - size_t n_ordered_slots = 0, n_ordered_tokens = 0; + _cleanup_free_ int *ordered_slots = NULL, *ordered_tokens = NULL, *wiped_slots = NULL; + size_t n_ordered_slots = 0, n_ordered_tokens = 0, n_wiped_slots = 0; int r, slot_max, ret; void *e; + assert_se(c); assert_se(cd); + assert_se(!!ret_wiped_slots == !!ret_n_wiped_slots); /* Shortcut if nothing to wipe. */ - if (n_explicit_slots == 0 && by_mask == 0 && by_scope == WIPE_EXPLICIT) + if (c->n_wipe_slots == 0 && c->wipe_slots_mask == 0 && c->wipe_slots_scope == WIPE_EXPLICIT) { + if (ret_wiped_slots) + *ret_wiped_slots = NULL; + if (ret_n_wiped_slots) + *ret_n_wiped_slots = 0; return 0; + } /* So this is a bit more complicated than I'd wish, but we want support three different axis for wiping slots: * @@ -334,23 +339,23 @@ int wipe_slots(struct crypt_device *cd, return log_oom(); /* Let's maintain one set of slots for the slots we definitely want to keep */ - if (except_slot >= 0) - if (set_put(keep_slots, INT_TO_PTR(except_slot)) < 0) + if (c->wipe_except_slot >= 0) + if (set_put(keep_slots, INT_TO_PTR(c->wipe_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++) { - if (explicit_slots[i] >= slot_max) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Slot index %i out of range.", explicit_slots[i]); + for (size_t i = 0; i < c->n_wipe_slots; i++) { + if (c->wipe_slots[i] >= slot_max) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Slot index %i out of range.", c->wipe_slots[i]); - if (set_put(wipe_slots, INT_TO_PTR(explicit_slots[i])) < 0) + if (set_put(wipe_slots, INT_TO_PTR(c->wipe_slots[i])) < 0) return log_oom(); } /* Now, handle the "all" and "empty passphrase" cases. */ - switch (by_scope) { + switch (c->wipe_slots_scope) { case WIPE_EXPLICIT: break; /* Nothing to do here */ @@ -373,7 +378,7 @@ int wipe_slots(struct crypt_device *cd, } /* Then add all slots that match a token type */ - r = find_slots_by_mask(cd, wipe_slots, keep_slots, by_mask); + r = find_slots_by_mask(cd, wipe_slots, keep_slots, c->wipe_slots_mask); if (r < 0) return r; @@ -409,11 +414,22 @@ int wipe_slots(struct crypt_device *cd, typesafe_qsort(ordered_tokens, n_ordered_tokens, cmp_int); if (n_ordered_slots == 0 && n_ordered_tokens == 0) { - log_full(except_slot < 0 ? LOG_NOTICE : LOG_DEBUG, + log_full(c->wipe_except_slot < 0 ? LOG_NOTICE : LOG_DEBUG, "No slots to remove selected."); + if (ret_wiped_slots) + *ret_wiped_slots = NULL; + if (ret_n_wiped_slots) + *ret_n_wiped_slots = 0; return 0; } + /* Remember which slots we actually managed to wipe, so we can report them back to the caller. */ + if (ret_wiped_slots) { + wiped_slots = new(int, n_ordered_slots); + if (!wiped_slots) + return log_oom(); + } + if (DEBUG_LOGGING) { for (size_t i = 0; i < n_ordered_slots; i++) log_debug("Going to wipe slot %i.", ordered_slots[i]); @@ -425,7 +441,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]); @@ -433,12 +449,15 @@ int wipe_slots(struct crypt_device *cd, log_warning_errno(r, "Failed to wipe slot %i, continuing: %m", ordered_slots[i - 1]); if (ret == 0) ret = r; - } else + } else { log_info("Wiped slot %i.", ordered_slots[i - 1]); + if (wiped_slots) + wiped_slots[n_wiped_slots++] = ordered_slots[i - 1]; + } } 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) @@ -446,5 +465,13 @@ int wipe_slots(struct crypt_device *cd, } } + if (ret_wiped_slots) { + /* We wiped from back to front, restore ascending order before handing the list out. */ + typesafe_qsort(wiped_slots, n_wiped_slots, cmp_int); + *ret_wiped_slots = TAKE_PTR(wiped_slots); + } + if (ret_n_wiped_slots) + *ret_n_wiped_slots = n_wiped_slots; + return ret; } diff --git a/src/cryptenroll/cryptenroll-wipe.h b/src/cryptenroll/cryptenroll-wipe.h index aa82f9dbbd342..cb3dd71933133 100644 --- a/src/cryptenroll/cryptenroll-wipe.h +++ b/src/cryptenroll/cryptenroll-wipe.h @@ -1,13 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "cryptenroll.h" #include "shared-forward.h" -typedef enum WipeScope WipeScope; - -int wipe_slots(struct crypt_device *cd, - const int explicit_slots[], - size_t n_explicit_slots, - WipeScope by_scope, - unsigned by_mask, - int except_slot); +/* Wipes the slots selected by c->wipe_slots / c->n_wipe_slots / c->wipe_slots_scope / + * c->wipe_slots_mask, except for c->wipe_except_slot (set to -1 for none). If ret_wiped_slots/ + * ret_n_wiped_slots are non-NULL they receive the (ascendingly sorted) list of slots that were actually + * wiped. */ +int wipe_slots(const EnrollContext *c, struct crypt_device *cd, int **ret_wiped_slots, size_t *ret_n_wiped_slots); diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 2a914a9b49918..67386f31d8b4d 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -1,35 +1,44 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-device.h" +#include "sd-varlink.h" +#include "alloc-util.h" #include "blockdev-list.h" #include "blockdev-util.h" #include "build.h" +#include "cleanup-util.h" #include "cryptenroll.h" #include "cryptenroll-fido2.h" +#include "cryptenroll-interactive.h" #include "cryptenroll-list.h" #include "cryptenroll-password.h" #include "cryptenroll-pkcs11.h" #include "cryptenroll-recovery.h" #include "cryptenroll-tpm2.h" +#include "cryptenroll-varlink.h" #include "cryptenroll-wipe.h" #include "cryptsetup-util.h" #include "extract-word.h" -#include "fileio.h" +#include "format-table.h" +#include "help-util.h" +#include "initrd-util.h" #include "libfido2-util.h" #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" #include "pkcs11-util.h" -#include "pretty-print.h" #include "process-util.h" +#include "prompt-util.h" #include "string-table.h" #include "string-util.h" +#include "terminal-util.h" #include "tpm2-pcr.h" #include "tpm2-util.h" @@ -54,11 +63,15 @@ 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; static unsigned arg_wipe_slots_mask = 0; /* Bitmask of (1U << EnrollType), for wiping all slots of specific types */ +static bool arg_firstboot = false; +static bool arg_chrome = true; +static bool arg_mute_console = false; +static unsigned arg_prompt_suppress_mask = 0; /* Bitmask of (1U << EnrollType): if any such slot exists, --firstboot does nothing */ static Fido2EnrollFlags arg_fido2_lock_with = FIDO2ENROLL_PIN | FIDO2ENROLL_UP; #if HAVE_LIBFIDO2 static int arg_fido2_cred_alg = COSE_ES256; @@ -109,64 +122,132 @@ static const char *const luks2_token_type_table[_ENROLL_TYPE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(luks2_token_type, EnrollType); -static int determine_default_node(void) { +static int enroll_type_mask_from_string(const char *name) { + assert(name); + + /* Maps an enroll type name (command line spelling) to its (1U << EnrollType) bitmask, or returns a + * negative errno if the name is not a known type. Callers merge the returned mask into their own + * accumulator. Shared by the various places that parse type lists into a bitmask. */ + + EnrollType t = enroll_type_from_string(name); + if (t < 0) + return -EINVAL; + + return 1 << t; +} + +void enroll_context_done(EnrollContext *c) { + if (!c) + return; + + c->node = mfree(c->node); + c->unlock_keyfile = mfree(c->unlock_keyfile); + c->unlock_fido2_device = mfree(c->unlock_fido2_device); + c->unlock_tpm2_device = mfree(c->unlock_tpm2_device); + c->unlock_password = erase_and_free(c->unlock_password); + c->passphrase = erase_and_free(c->passphrase); + c->fido2_device = mfree(c->fido2_device); + c->fido2_salt_file = mfree(c->fido2_salt_file); + c->fido2_pin = erase_and_free(c->fido2_pin); + c->pkcs11_token_uri = mfree(c->pkcs11_token_uri); + c->tpm2_device = mfree(c->tpm2_device); + c->tpm2_device_key = mfree(c->tpm2_device_key); + c->tpm2_hash_pcr_values = mfree(c->tpm2_hash_pcr_values); + c->tpm2_public_key = mfree(c->tpm2_public_key); + c->tpm2_signature = mfree(c->tpm2_signature); + c->tpm2_pcrlock = mfree(c->tpm2_pcrlock); + c->wipe_slots = mfree(c->wipe_slots); + c->link = sd_varlink_unref(c->link); +} + +static int resolve_default_node(const char *path, char **ret) { int r; - /* If no device is specified we'll default to the backing device of /var/. - * - * Why /var/ and not just / you ask? - * - * On most systems /var/ is going to be on the root fs, hence the outcome is usually the same. - * - * However, on systems where / and /var/ are separate it makes more sense to default to /var/ because - * that's where the persistent and variable data is placed (i.e. where LUKS should be used) while / - * doesn't really have to be variable and could as well be immutable or ephemeral. Hence /var/ should - * be a better default. - * - * Or to say this differently: it makes sense to support well systems with /var/ being on /. It also - * makes sense to support well systems with them being separate, and /var/ being variable and - * persistent. But any other kind of system appears much less interesting to support, and in that - * case people should just specify the device name explicitly. */ + /* Resolves the path of the underlying LUKS2 block device of the file system at the given mount + * point, i.e. the raw partition behind the dm-crypt mapping that file system sits on. Returns + * -ENXIO if the file system is not backed by a (single) LUKS2 device. Logs only at debug level, so + * the caller can try the next candidate path. */ + + assert(path); + assert(ret); dev_t devno; - r = get_block_device("/var", &devno); + r = get_block_device(path, &devno); if (r < 0) - return log_error_errno(r, "Failed to determine block device backing /var/: %m"); + return log_debug_errno(r, "Failed to determine block device backing %s: %m", path); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(ENXIO), - "File system /var/ is on not backed by a (single) whole block device."); + return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), + "File system %s is not backed by a (single) whole block device.", path); _cleanup_(sd_device_unrefp) sd_device *dev = NULL; r = sd_device_new_from_devnum(&dev, 'b', devno); if (r < 0) - return log_error_errno(r, "Unable to access backing block device for /var/: %m"); + return log_debug_errno(r, "Unable to access backing block device for %s: %m", path); const char *dm_uuid; r = sd_device_get_property_value(dev, "DM_UUID", &dm_uuid); if (r == -ENOENT) - return log_error_errno(r, "Backing block device of /var/ is not a DM device: %m"); + return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), "Backing block device of %s is not a DM device.", path); if (r < 0) - return log_error_errno(r, "Unable to query DM_UUID udev property of backing block device for /var/: %m"); + return log_debug_errno(r, "Unable to query DM_UUID udev property of backing block device for %s: %m", path); if (!startswith(dm_uuid, "CRYPT-LUKS2-")) - return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Block device backing /var/ is not a LUKS2 device."); + return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), "Block device backing %s is not a LUKS2 device.", path); _cleanup_(sd_device_unrefp) sd_device *origin = NULL; r = block_device_get_originating(dev, &origin, /* recursive= */ false); if (r < 0) - return log_error_errno(r, "Failed to get originating device of LUKS2 device backing /var/: %m"); + return log_debug_errno(r, "Failed to get originating device of LUKS2 device backing %s: %m", path); const char *dp; r = sd_device_get_devname(origin, &dp); if (r < 0) - return log_error_errno(r, "Failed to get device path for LUKS2 device backing /var/: %m"); + return log_debug_errno(r, "Failed to get device path for LUKS2 device backing %s: %m", path); - r = free_and_strdup_warn(&arg_node, dp); - if (r < 0) - return r; + return strdup_to(ret, dp); +} - log_info("No device specified, defaulting to '%s'.", arg_node); - return 0; +static int determine_default_node(void) { + int r; + + /* If no device is specified we'll default to the backing device of /var/. + * + * Why /var/ and not just / you ask? + * + * On most systems /var/ is going to be on the root fs, hence the outcome is usually the same. + * + * However, on systems where / and /var/ are separate it makes more sense to default to /var/ because + * that's where the persistent and variable data is placed (i.e. where LUKS should be used) while / + * doesn't really have to be variable and could as well be immutable or ephemeral. Hence /var/ should + * be a better default. + * + * Or to say this differently: it makes sense to support well systems with /var/ being on /. It also + * makes sense to support well systems with them being separate, and /var/ being variable and + * persistent. But any other kind of system appears much less interesting to support, and in that + * case people should just specify the device name explicitly. + * + * When invoked from the initrd the host's file systems are not mounted at their final location yet, + * but below /sysroot/, hence look there instead. */ + + const char *candidates[2]; + size_t n_candidates = 0; + + if (in_initrd()) { + candidates[n_candidates++] = "/sysroot/var"; + candidates[n_candidates++] = "/sysroot"; + } else + candidates[n_candidates++] = "/var"; + + FOREACH_ARRAY(path, candidates, n_candidates) { + r = resolve_default_node(*path, &arg_node); + if (r >= 0) { + log_info("No device specified, defaulting to '%s' (backing %s).", arg_node, *path); + return 0; + } + } + + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), + "Failed to automatically determine a LUKS2 block device to operate on, please specify one explicitly."); } static int parse_wipe_slot(const char *arg) { @@ -189,21 +270,15 @@ static int parse_wipe_slot(const char *arg) { if (r < 0) return log_error_errno(r, "Failed to parse slot list: %s", arg); + int mask; + if (streq(slot, "all")) arg_wipe_slots_scope = WIPE_ALL; else if (streq(slot, "empty")) { if (arg_wipe_slots_scope != WIPE_ALL) /* if "all" was specified before, that wins */ arg_wipe_slots_scope = WIPE_EMPTY_PASSPHRASE; - } else if (streq(slot, "password")) - arg_wipe_slots_mask |= 1U << ENROLL_PASSWORD; - else if (streq(slot, "recovery")) - arg_wipe_slots_mask |= 1U << ENROLL_RECOVERY; - else if (streq(slot, "pkcs11")) - arg_wipe_slots_mask |= 1U << ENROLL_PKCS11; - else if (streq(slot, "fido2")) - arg_wipe_slots_mask |= 1U << ENROLL_FIDO2; - else if (streq(slot, "tpm2")) - arg_wipe_slots_mask |= 1U << ENROLL_TPM2; + } else if ((mask = enroll_type_mask_from_string(slot)) >= 0) + arg_wipe_slots_mask |= mask; else { unsigned n; @@ -221,217 +296,158 @@ static int parse_wipe_slot(const char *arg) { } } +static int parse_prompt_suppress(const char *arg) { + int r; + + assert(arg); + + /* Parses a comma-separated list of the slot types the --firstboot wizard knows how to enroll. If a + * slot of any listed type already exists on the volume, the wizard does nothing. */ + + for (const char *p = arg;;) { + _cleanup_free_ char *type = NULL; + int mask; + + r = extract_first_word(&p, &type, ",", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r == 0) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to parse type list: %s", arg); + + mask = enroll_type_mask_from_string(type); + if (mask < 0) + return log_error_errno(mask, "Unknown slot type: %s", type); + + arg_prompt_suppress_mask |= mask; + } +} + static int help(void) { - _cleanup_free_ char *link = NULL; int r; + static const char* const groups[] = { + NULL, + "Unlocking", + "Simple Enrollment", + "PKCS#11 Enrollment", + "FIDO2 Enrollment", + "TPM2 Enrollment", + }; + + Table *tables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(tables, table_unref_array_clear); + + 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]); + pager_open(arg_pager_flags); - r = terminal_urlify_man("systemd-cryptenroll", "1", &link); - if (r < 0) - return log_oom(); + help_cmdline("[OPTIONS...] [BLOCK-DEVICE]"); + help_abstract("Enroll a security token or authentication credential to a LUKS volume."); + + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + help_section(groups[i] ?: "Options"); - 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", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + help_man_page_reference("systemd-cryptenroll", "1"); 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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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); + 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); + + OPTION_LONG("wipe-slot", "SLOT1,SLOT2,…", + "Wipe specified slots"): + r = parse_wipe_slot(opts.arg); if (r < 0) return r; + break; - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r); + OPTION_LONG("firstboot", NULL, + "Interactively enroll a credential (first-boot wizard)"): + arg_firstboot = true; break; - case ARG_FIDO2_WITH_UP: - r = parse_boolean_argument("--fido2-with-user-presence=", optarg, NULL); + OPTION_LONG("prompt-suppress", "TYPE1,TYPE2,…", + "Skip the --firstboot wizard if a slot of any listed type exists"): + r = parse_prompt_suppress(opts.arg); if (r < 0) return r; - - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r); break; - case ARG_FIDO2_WITH_UV: - r = parse_boolean_argument("--fido2-with-user-verification=", optarg, NULL); + OPTION_LONG("chrome", "BOOL", + "In first-boot mode: if false don't show colour bar at top and bottom of terminal"): + r = parse_boolean_argument("--chrome=", opts.arg, &arg_chrome); 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; + OPTION_LONG("mute-console", "BOOL", + "In first-boot mode, tell kernel/PID 1 to not write to the console while running"): + r = parse_boolean_argument("--mute-console=", opts.arg, &arg_mute_console); + if (r < 0) + return r; break; - case ARG_RECOVERY_KEY: - if (arg_enroll_type >= 0) + OPTION_GROUP("Unlocking"): {} + + OPTION_LONG("unlock-empty", NULL, "Use an empty password to unlock the volume"): + if (arg_unlock_type != UNLOCK_PASSWORD) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Multiple operations specified at once, refusing."); + "Multiple unlock methods specified at once, refusing."); - arg_enroll_type = ENROLL_RECOVERY; + arg_unlock_type = UNLOCK_EMPTY; 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(opts.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 +456,8 @@ static int parse_argv(int argc, char *argv[]) { assert(!arg_unlock_fido2_device); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(opts.arg, "auto")) { + device = strdup(opts.arg); if (!device) return log_oom(); } @@ -451,7 +467,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 +477,8 @@ static int parse_argv(int argc, char *argv[]) { assert(!arg_unlock_tpm2_device); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(opts.arg, "auto")) { + device = strdup(opts.arg); if (!device) return log_oom(); } @@ -471,25 +488,56 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_PKCS11_TOKEN_URI: { + OPTION_LONG("unlock-headless", NULL, "Try the 'headless' unlock mechanisms in turn"): + if (arg_unlock_type != UNLOCK_PASSWORD) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Multiple unlock methods specified at once, refusing."); + + arg_unlock_type = UNLOCK_HEADLESS; + break; + + 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(opts.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(opts.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(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", opts.arg); - uri = strdup(optarg); + uri = strdup(opts.arg); if (!uri) return log_oom(); } @@ -499,24 +547,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(opts.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(opts.arg, "auto")) { + device = strdup(opts.arg); if (!device) return log_oom(); } @@ -526,32 +571,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(opts.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=", opts.arg, &arg_fido2_parameters_in_header); if (r < 0) return r; + break; + OPTION_LONG("fido2-credential-algorithm", "STRING", + "Specify COSE algorithm for FIDO2 credential"): + r = parse_fido2_algorithm(opts.arg, &arg_fido2_cred_alg); + if (r < 0) + return log_error_errno(r, "Failed to parse COSE algorithm: %s", opts.arg); break; - case ARG_FIDO2_PARAMETERS_IN_HEADER: - r = parse_boolean_argument("--fido2-parameters-in-header=", optarg, &arg_fido2_parameters_in_header); + 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=", opts.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=", opts.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=", opts.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(opts.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(opts.arg, "auto")) { + device = strdup(opts.arg); if (!device) return log_oom(); } @@ -561,115 +640,105 @@ 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(opts.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(opts.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", opts.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(opts.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(opts.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(opts.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(opts.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(opts.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(opts.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); + OPTION_LONG("tpm2-with-pin", "BOOL", + "Whether to require entering a PIN to unlock the volume"): + r = parse_boolean_argument("--tpm2-with-pin=", opts.arg, &arg_tpm2_pin); if (r < 0) return r; - break; - - case ARG_WIPE_SLOT: - r = parse_wipe_slot(optarg); - 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) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Too many arguments, refusing."); + if (option_parser_get_n_args(&opts) > 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 (arg_firstboot) { + if (arg_enroll_type >= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--firstboot may not be combined with an explicit enrollment type, refusing."); if (wipe_requested()) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Wiping requested and no block device node specified, refusing."); + "--firstboot may not be combined with --wipe-slot=, refusing."); + } else if (arg_prompt_suppress_mask != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--prompt-suppress= is only useful together with --firstboot, refusing."); + const char *arg = option_parser_get_arg(&opts, 0); + if (arg) + r = parse_path_argument(arg, 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 +791,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; @@ -736,62 +805,26 @@ static int check_for_homed(struct crypt_device *cd) { return 0; } -static int load_volume_key_keyfile( - struct crypt_device *cd, - void *ret_vk, - size_t *ret_vks) { - - _cleanup_(erase_and_freep) char *password = NULL; - size_t password_len; - int r; - - assert_se(cd); - assert_se(ret_vk); - assert_se(ret_vks); - - r = read_full_file_full( - AT_FDCWD, - arg_unlock_keyfile, - UINT64_MAX, - SIZE_MAX, - READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, - NULL, - &password, - &password_len); - if (r < 0) - return log_error_errno(r, "Reading keyfile %s failed: %m", arg_unlock_keyfile); - - r = crypt_volume_key_get( - cd, - CRYPT_ANY_SLOT, - ret_vk, - ret_vks, - password, - password_len); - if (r < 0) - return log_error_errno(r, "Unlocking via keyfile failed: %m"); - - return r; -} - -static int prepare_luks( +int prepare_luks( + const EnrollContext *c, struct crypt_device **ret_cd, struct iovec *ret_volume_key) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; int r; + assert(c); assert(ret_cd); - r = crypt_init(&cd, arg_node); + r = sym_crypt_init(&cd, c->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); + return log_error_errno(r, "Failed to load LUKS2 superblock of %s: %m", c->node); r = check_for_homed(cd); if (r < 0) @@ -802,40 +835,57 @@ 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"); _cleanup_(iovec_done_erase) struct iovec vk = {}; - vk.iov_base = malloc(r); if (!vk.iov_base) return log_oom(); vk.iov_len = (size_t) r; - switch (arg_unlock_type) { + switch (c->unlock_type) { + + case UNLOCK_EMPTY: + r = load_volume_key_empty(c, cd, &vk); + break; case UNLOCK_PASSWORD: - r = load_volume_key_password(cd, arg_node, vk.iov_base, &vk.iov_len); + r = load_volume_key_password(c, cd, &vk); break; case UNLOCK_KEYFILE: - r = load_volume_key_keyfile(cd, vk.iov_base, &vk.iov_len); + r = load_volume_key_keyfile(c, cd, &vk); break; case UNLOCK_FIDO2: - r = load_volume_key_fido2(cd, arg_node, arg_unlock_fido2_device, vk.iov_base, &vk.iov_len); + r = load_volume_key_fido2(c, cd, &vk); break; case UNLOCK_TPM2: - r = load_volume_key_tpm2(cd, arg_node, arg_unlock_tpm2_device, vk.iov_base, &vk.iov_len); + r = load_volume_key_tpm2(c, cd, &vk); + break; + + case UNLOCK_HEADLESS: + if (tpm2_is_mostly_supported()) { + log_info("TPM2 support available, trying unlocking via TPM2…"); + + r = load_volume_key_tpm2(c, cd, &vk); + if (r >= 0) + break; + + log_info("TPM2 unlocking didn't work, trying unlocking via empty password…"); + } else + log_info("TPM2 support not available, trying unlocking via empty password…"); + + r = load_volume_key_empty(c, cd, &vk); break; default: return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown LUKS unlock method"); } - if (r < 0) return r; @@ -845,85 +895,201 @@ static int prepare_luks( return 0; } -static int run(int argc, char *argv[]) { - _cleanup_(crypt_freep) struct crypt_device *cd = NULL; - _cleanup_(iovec_done_erase) struct iovec vk = {}; - int slot, slot_to_wipe, r; +static int enroll_context_from_args(EnrollContext *c) { + assert(c); + + /* Copies the parsed command line parameters from the static arg_* globals into a self-contained + * EnrollContext. The context owns its own copies of all strings/arrays, so it can be torn down + * independently of the arg_* destructors. */ + + *c = ENROLL_CONTEXT_NULL; + + c->enroll_type = arg_enroll_type; + c->unlock_type = arg_unlock_type; + c->fido2_parameters_in_header = arg_fido2_parameters_in_header; + c->fido2_lock_with = arg_fido2_lock_with; + c->fido2_cred_alg = arg_fido2_cred_alg; + c->tpm2_seal_key_handle = arg_tpm2_seal_key_handle; + c->tpm2_pin = arg_tpm2_pin; + c->tpm2_load_public_key = arg_tpm2_load_public_key; + c->tpm2_public_key_pcr_mask = arg_tpm2_public_key_pcr_mask; + c->wipe_slots_scope = arg_wipe_slots_scope; + c->wipe_slots_mask = arg_wipe_slots_mask; + + if (strdup_to(&c->node, arg_node) < 0 || + strdup_to(&c->unlock_keyfile, arg_unlock_keyfile) < 0 || + strdup_to(&c->unlock_fido2_device, arg_unlock_fido2_device) < 0 || + strdup_to(&c->unlock_tpm2_device, arg_unlock_tpm2_device) < 0 || + strdup_to(&c->fido2_device, arg_fido2_device) < 0 || + strdup_to(&c->fido2_salt_file, arg_fido2_salt_file) < 0 || + strdup_to(&c->pkcs11_token_uri, arg_pkcs11_token_uri) < 0 || + strdup_to(&c->tpm2_device, arg_tpm2_device) < 0 || + strdup_to(&c->tpm2_device_key, arg_tpm2_device_key) < 0 || + strdup_to(&c->tpm2_public_key, arg_tpm2_public_key) < 0 || + strdup_to(&c->tpm2_signature, arg_tpm2_signature) < 0 || + strdup_to(&c->tpm2_pcrlock, arg_tpm2_pcrlock) < 0) + return log_oom(); - log_setup(); + if (arg_n_wipe_slots > 0) { + c->wipe_slots = newdup(int, arg_wipe_slots, arg_n_wipe_slots); + if (!c->wipe_slots) + return log_oom(); + c->n_wipe_slots = arg_n_wipe_slots; + } - r = parse_argv(argc, argv); - if (r <= 0) - return r; + if (arg_tpm2_n_hash_pcr_values > 0) { + c->tpm2_hash_pcr_values = newdup(Tpm2PCRValue, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values); + if (!c->tpm2_hash_pcr_values) + return log_oom(); + c->tpm2_n_hash_pcr_values = arg_tpm2_n_hash_pcr_values; + } - /* A delicious drop of snake oil */ - (void) safe_mlockall(MCL_CURRENT|MCL_FUTURE|MCL_ONFAULT); + return 0; +} - cryptsetup_enable_logging(NULL); +int enroll_now( + const EnrollContext *c, + struct crypt_device *cd, + const struct iovec *volume_key, + char **ret_recovery_key) { - 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 - r = prepare_luks(&cd, &vk); - if (r < 0) - return r; + int slot, slot_to_wipe = -1, r; + + assert(c); + assert(cd); + assert(iovec_is_set(volume_key)); - switch (arg_enroll_type) { + switch (c->enroll_type) { case ENROLL_PASSWORD: - slot = enroll_password(cd, &vk); - break; + return enroll_password(c, cd, volume_key); case ENROLL_RECOVERY: - slot = enroll_recovery(cd, &vk); - break; + return enroll_recovery(c, cd, volume_key, ret_recovery_key); case ENROLL_PKCS11: - slot = enroll_pkcs11(cd, &vk, arg_pkcs11_token_uri); - break; + return enroll_pkcs11(c, cd, volume_key); case ENROLL_FIDO2: - slot = enroll_fido2(cd, &vk, arg_fido2_device, arg_fido2_lock_with, arg_fido2_cred_alg, arg_fido2_salt_file, arg_fido2_parameters_in_header); - break; + return enroll_fido2(c, cd, volume_key); case ENROLL_TPM2: - slot = enroll_tpm2(cd, &vk, arg_tpm2_device, arg_tpm2_seal_key_handle, arg_tpm2_device_key, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_load_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin, arg_tpm2_pcrlock, &slot_to_wipe); + slot = enroll_tpm2(c, cd, volume_key, &slot_to_wipe); + if (slot < 0) + return slot; - if (slot >= 0 && slot_to_wipe >= 0) { + if (slot_to_wipe >= 0) { assert(slot != slot_to_wipe); - /* Updating PIN on an existing enrollment */ - r = wipe_slots( - cd, - &slot_to_wipe, - /* n_explicit_slots= */ 1, - WIPE_EXPLICIT, - /* by_mask= */ 0, - /* except_slot= */ -1); + /* Updating the PIN on an existing enrollment: wipe just that one slot. This is an + * internal one-off wipe, unrelated to the user's wipe selection, so use a throwaway + * context referencing a single explicit slot. */ + _cleanup_(enroll_context_done) EnrollContext wipe_ctx = ENROLL_CONTEXT_NULL; + wipe_ctx.wipe_slots = newdup(int, &slot_to_wipe, 1); + if (!wipe_ctx.wipe_slots) + return log_oom(); + + wipe_ctx.n_wipe_slots = 1; + + r = wipe_slots(&wipe_ctx, cd, /* ret_wiped_slots= */ NULL, /* ret_n_wiped_slots= */ NULL); if (r < 0) return r; } - break; - case _ENROLL_TYPE_INVALID: + + return slot; + + default: + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Operation not implemented yet."); + } +} + +static int run(int argc, char *argv[]) { + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + _cleanup_(iovec_done_erase) struct iovec vk = {}; + _cleanup_(enroll_context_done) EnrollContext c = ENROLL_CONTEXT_NULL; + int slot, r; + + log_setup(); + + /* A delicious drop of snake oil */ + (void) safe_mlockall(MCL_CURRENT|MCL_FUTURE|MCL_ONFAULT); + + /* If invoked as a Varlink service, hand off to the Varlink server and don't process the command + * line any further. */ + 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 cryptenroll_varlink_server(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + r = DLOPEN_CRYPTSETUP(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + + r = enroll_context_from_args(&c); + if (r < 0) + return r; + + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *mute_console_link = NULL; + + /* Ensure the interactive chrome (drawn by cryptenroll_run_interactive() in --firstboot mode) is + * always torn down on exit; chrome_hide() is a no-op if no chrome was shown. */ + DEFER_VOID_CALL(chrome_hide); + + if (arg_firstboot) { + assert(c.enroll_type < 0); + + r = cryptenroll_run_interactive( + &c, + arg_prompt_suppress_mask, + arg_chrome, + arg_mute_console ? &mute_console_link : NULL); + if (r <= 0) + return r; + + assert(c.enroll_type >= 0); + + } else if (c.enroll_type < 0) { + /* If we are called without anything to enroll, we just need the LUKS device, not the volume key. */ + r = prepare_luks(&c, &cd, /* ret_volume_key= */ NULL); + if (r < 0) + return r; + /* List enrolled slots if we are called without anything to enroll or wipe */ if (!wipe_requested()) return list_enrolled(cd); /* Only slot wiping selected */ - return wipe_slots(cd, arg_wipe_slots, arg_n_wipe_slots, arg_wipe_slots_scope, arg_wipe_slots_mask, -1); + return wipe_slots(&c, cd, /* ret_wiped_slots= */ NULL, /* ret_n_wiped_slots= */ NULL); + } - default: - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Operation not implemented yet."); + r = prepare_luks(&c, &cd, &vk); + if (r < 0) + goto finish; + + slot = enroll_now(&c, cd, &vk, /* ret_recovery_key= */ NULL); + if (slot < 0) { + r = slot; + goto finish; } - if (slot < 0) - return slot; - /* After we completed enrolling, remove user selected slots */ - r = wipe_slots(cd, arg_wipe_slots, arg_n_wipe_slots, arg_wipe_slots_scope, arg_wipe_slots_mask, slot); + /* After we completed enrolling, remove user selected slots (keeping the one we just added) */ + c.wipe_except_slot = slot; + r = wipe_slots(&c, cd, /* ret_wiped_slots= */ NULL, /* ret_n_wiped_slots= */ NULL); if (r < 0) - return r; + goto finish; - return 0; + r = 0; + +finish: + if (arg_firstboot) + (void) any_key_to_proceed(); + + return r; } DEFINE_MAIN_FUNCTION(run); diff --git a/src/cryptenroll/cryptenroll.h b/src/cryptenroll/cryptenroll.h index 1066ecc18d9aa..5fdb4e713c4f8 100644 --- a/src/cryptenroll/cryptenroll.h +++ b/src/cryptenroll/cryptenroll.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "libfido2-util.h" #include "shared-forward.h" typedef enum EnrollType { @@ -18,6 +19,8 @@ typedef enum UnlockType { UNLOCK_KEYFILE, UNLOCK_FIDO2, UNLOCK_TPM2, + UNLOCK_EMPTY, + UNLOCK_HEADLESS, _UNLOCK_TYPE_MAX, _UNLOCK_TYPE_INVALID = -EINVAL, } UnlockType; @@ -32,3 +35,89 @@ typedef enum WipeScope { DECLARE_STRING_TABLE_LOOKUP(enroll_type, EnrollType); DECLARE_STRING_TABLE_LOOKUP(luks2_token_type, EnrollType); + +/* A single bag of parameters consumed by the enrollment helpers. Populated either from the command line + * (see enroll_context_from_args() in cryptenroll.c), from a Varlink request (see cryptenroll-varlink.c), + * or by the interactive wizard (see cryptenroll-interactive.c). Owns all strings/arrays it points to. */ +typedef struct EnrollContext { + EnrollType enroll_type; + UnlockType unlock_type; + + /* Target device */ + char *node; + + /* Unlock side */ + char *unlock_keyfile; + char *unlock_fido2_device; + char *unlock_tpm2_device; + char *unlock_password; /* used by Varlink; NULL on CLI path */ + + /* New password to enroll (mechanism == password). When NULL the helpers fall back to + * $NEWPASSWORD / askpw. */ + char *passphrase; + size_t passphrase_size; + + /* FIDO2 */ + char *fido2_device; + char *fido2_salt_file; + bool fido2_parameters_in_header; + Fido2EnrollFlags fido2_lock_with; + int fido2_cred_alg; + char *fido2_pin; /* optional pre-supplied token PIN; NULL means prompt (if interactive) */ + + /* PKCS#11 */ + char *pkcs11_token_uri; + + /* TPM2 */ + char *tpm2_device; + uint32_t tpm2_seal_key_handle; + char *tpm2_device_key; + Tpm2PCRValue *tpm2_hash_pcr_values; + size_t tpm2_n_hash_pcr_values; + bool tpm2_pin; + char *tpm2_public_key; + bool tpm2_load_public_key; + uint32_t tpm2_public_key_pcr_mask; + char *tpm2_signature; + char *tpm2_pcrlock; + + /* Wipe selection */ + int *wipe_slots; + size_t n_wipe_slots; + WipeScope wipe_slots_scope; + unsigned wipe_slots_mask; + int wipe_except_slot; /* slot to never wipe (e.g. the one we just enrolled); -1 for none */ + + /* If false, the enrollment helpers must never prompt the user (no askpw, no terminal I/O, + * no log printing of credential material). They use the fields in this context as the + * sole input, and fail with ENOPKG if a required piece of input is missing. CLI and + * interactive callers set this to true; the Varlink dispatcher sets it to false. */ + bool interactive; + + /* Varlink link the request came in on, if the caller asked for 'more'. NULL otherwise. + * Owned (sd_varlink_ref'd) by the context. */ + sd_varlink *link; +} EnrollContext; + +#define ENROLL_CONTEXT_NULL \ + (EnrollContext) { \ + .enroll_type = _ENROLL_TYPE_INVALID, \ + .unlock_type = UNLOCK_PASSWORD, \ + .fido2_parameters_in_header = true, \ + .fido2_lock_with = FIDO2ENROLL_PIN | FIDO2ENROLL_UP, \ + .tpm2_load_public_key = true, \ + .wipe_slots_scope = WIPE_EXPLICIT, \ + .wipe_except_slot = -1, \ + .interactive = true, \ + } + +void enroll_context_done(EnrollContext *c); + +/* Opens & loads the LUKS2 superblock of c->node, refuses homed-managed volumes, and (if ret_volume_key is + * non-NULL) unlocks it according to c->unlock_type, returning the volume key. */ +int prepare_luks(const EnrollContext *c, struct crypt_device **ret_cd, struct iovec *ret_volume_key); + +/* Dispatches to the enroll_*() helper matching c->enroll_type and returns the keyslot the new credential + * was added to. For ENROLL_RECOVERY the generated key is returned via ret_recovery_key (if non-NULL). + * Defined in cryptenroll.c, shared by the command line and Varlink code paths. */ +int enroll_now(const EnrollContext *c, struct crypt_device *cd, const struct iovec *volume_key, char **ret_recovery_key); diff --git a/src/cryptenroll/io.systemd.cryptenroll.policy b/src/cryptenroll/io.systemd.cryptenroll.policy new file mode 100644 index 0000000000000..80efa19957aa4 --- /dev/null +++ b/src/cryptenroll/io.systemd.cryptenroll.policy @@ -0,0 +1,30 @@ + + + + + + + + The systemd Project + https://systemd.io + + + Enroll an unlock mechanism into an encrypted volume + Authentication is required to enroll a new unlock mechanism into an encrypted volume. + + auth_admin + auth_admin + auth_admin_keep + + + diff --git a/src/cryptenroll/meson.build b/src/cryptenroll/meson.build index 488ceea14d1ef..3fbb5bf080bcc 100644 --- a/src/cryptenroll/meson.build +++ b/src/cryptenroll/meson.build @@ -7,11 +7,13 @@ endif systemd_cryptenroll_sources = files( 'cryptenroll.c', 'cryptenroll-fido2.c', + 'cryptenroll-interactive.c', 'cryptenroll-list.c', 'cryptenroll-password.c', 'cryptenroll-pkcs11.c', 'cryptenroll-recovery.c', 'cryptenroll-tpm2.c', + 'cryptenroll-varlink.c', 'cryptenroll-wipe.c', ) @@ -21,10 +23,13 @@ executables += [ 'public' : true, 'sources' : systemd_cryptenroll_sources, 'dependencies' : [ - libcryptsetup, - libdl, - libopenssl, + libcryptsetup_cflags, + libfido2_cflags, + libopenssl_cflags, libp11kit_cflags, ], }, ] + +install_data('io.systemd.cryptenroll.policy', + install_dir : polkitpolicydir) diff --git a/src/cryptsetup/cryptsetup-pkcs11.c b/src/cryptsetup/cryptsetup-pkcs11.c index 238905ae9087e..b8534b15ef3de 100644 --- a/src/cryptsetup/cryptsetup-pkcs11.c +++ b/src/cryptsetup/cryptsetup-pkcs11.c @@ -16,6 +16,7 @@ int decrypt_pkcs11_key( const char *volume_name, const char *friendly_name, const char *pkcs11_uri, + Pkcs11RsaPadding rsa_padding, const char *key_file, /* We either expect key_file and associated parameters to be set (for file keys) … */ size_t key_file_size, uint64_t key_file_offset, @@ -29,6 +30,7 @@ int decrypt_pkcs11_key( .friendly_name = friendly_name, .askpw_flags = askpw_flags, .until = until, + .rsa_padding = rsa_padding, }; int r; @@ -83,6 +85,7 @@ int find_pkcs11_auto_data( char **ret_uri, void **ret_encrypted_key, size_t *ret_encrypted_key_size, + Pkcs11RsaPadding *ret_rsa_padding, int *ret_keyslot) { #if HAVE_P11KIT @@ -90,11 +93,13 @@ int find_pkcs11_auto_data( _cleanup_free_ void *key = NULL; int r, keyslot = -1; size_t key_size = 0; + Pkcs11RsaPadding rsa_padding = PKCS11_RSA_PADDING_PKCS1V15; assert(cd); assert(ret_uri); assert(ret_encrypted_key); assert(ret_encrypted_key_size); + assert(ret_rsa_padding); assert(ret_keyslot); /* Loads PKCS#11 metadata from LUKS2 JSON token headers. */ @@ -148,6 +153,22 @@ int find_pkcs11_auto_data( r = sd_json_variant_unbase64(w, &key, &key_size); if (r < 0) return log_error_errno(r, "Failed to decode base64 encoded key: %m"); + + /* Optional padding-scheme tag. Absent in legacy tokens (which used PKCS#1 v1.5). */ + w = sd_json_variant_by_key(v, "pkcs11-padding"); + if (w) { + Pkcs11RsaPadding p; + + if (!sd_json_variant_is_string(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "PKCS#11 token field 'pkcs11-padding' is not a string."); + p = pkcs11_rsa_padding_from_string(sd_json_variant_string(w)); + if (p < 0) + return log_error_errno(p, + "PKCS#11 token field 'pkcs11-padding' has unsupported value '%s'.", + sd_json_variant_string(w)); + rsa_padding = p; + } } if (!uri) @@ -159,6 +180,7 @@ int find_pkcs11_auto_data( *ret_uri = TAKE_PTR(uri); *ret_encrypted_key = TAKE_PTR(key); *ret_encrypted_key_size = key_size; + *ret_rsa_padding = rsa_padding; *ret_keyslot = keyslot; return 0; #else diff --git a/src/cryptsetup/cryptsetup-pkcs11.h b/src/cryptsetup/cryptsetup-pkcs11.h index 472e5d0e78c27..faccad599e0a7 100644 --- a/src/cryptsetup/cryptsetup-pkcs11.h +++ b/src/cryptsetup/cryptsetup-pkcs11.h @@ -1,12 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "pkcs11-padding.h" #include "shared-forward.h" int decrypt_pkcs11_key( const char *volume_name, const char *friendly_name, const char *pkcs11_uri, + Pkcs11RsaPadding rsa_padding, const char *key_file, size_t key_file_size, uint64_t key_file_offset, @@ -21,4 +23,5 @@ int find_pkcs11_auto_data( char **ret_uri, void **ret_encrypted_key, size_t *ret_encrypted_key_size, + Pkcs11RsaPadding *ret_rsa_padding, int *ret_keyslot); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c index 02ed4dd273c6f..0bfe0c2ec7797 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c @@ -2,6 +2,7 @@ #include #include +#include #include "sd-json.h" @@ -37,8 +38,12 @@ _public_ int cryptsetup_token_open_pin( assert(pin || pin_size == 0); assert(token >= 0); + r = DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + /* This must not fail at this moment (internal error) */ - r = crypt_token_json_get(cd, token, &json); + r = sym_crypt_token_json_get(cd, token, &json); /* Use assert_se() here to avoid emitting warning with -DNDEBUG */ assert_se(token == r); assert(json); @@ -98,6 +103,9 @@ _public_ void cryptsetup_token_dump( assert(json); + if (DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED) < 0) + return; + r = parse_luks2_fido2_data(cd, json, &rp_id, &salt, &salt_size, &cid, &cid_size, &required); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m."); @@ -162,7 +170,11 @@ _public_ int cryptsetup_token_validate( assert(json); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + + 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..63be8a7c64a76 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "sd-json.h" @@ -36,8 +37,12 @@ _public_ int cryptsetup_token_open_pin( assert(pin || pin_size == 0); assert(token >= 0); + r = DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + /* This must not fail at this moment (internal error) */ - r = crypt_token_json_get(cd, token, &json); + r = sym_crypt_token_json_get(cd, token, &json); /* Use assert_se() here to avoid emitting warning with -DNDEBUG */ assert_se(token == r); assert(json); @@ -88,8 +93,12 @@ _public_ void cryptsetup_token_dump( size_t pkcs11_key_size; _cleanup_free_ char *pkcs11_uri = NULL, *key_str = NULL; _cleanup_free_ void *pkcs11_key = NULL; + Pkcs11RsaPadding rsa_padding = PKCS11_RSA_PADDING_PKCS1V15; - r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &pkcs11_key, &pkcs11_key_size); + if (DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED) < 0) + return; + + r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &pkcs11_key, &pkcs11_key_size, &rsa_padding); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m."); @@ -99,6 +108,7 @@ _public_ void cryptsetup_token_dump( crypt_log(cd, "\tpkcs11-uri: %s\n", pkcs11_uri); crypt_log(cd, "\tpkcs11-key: %s\n", key_str); + crypt_log(cd, "\tpkcs11-padding: %s\n", pkcs11_rsa_padding_to_string(rsa_padding)); } /* @@ -115,7 +125,11 @@ _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 = DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + + 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."); @@ -140,5 +154,19 @@ _public_ int cryptsetup_token_validate( if (r < 0) return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m."); + /* Optional 'pkcs11-padding' field: must be a known padding scheme string if present. Older systemd + * versions will just ignore this field entirely and assume PKCS#1 v1.5. */ + w = sd_json_variant_by_key(v, "pkcs11-padding"); + if (w) { + if (!sd_json_variant_is_string(w)) { + crypt_log_debug(cd, "PKCS#11 token field 'pkcs11-padding' is not a string."); + return 1; + } + if (pkcs11_rsa_padding_from_string(sd_json_variant_string(w)) < 0) { + crypt_log_debug(cd, "PKCS#11 token field 'pkcs11-padding' has unsupported value."); + return 1; + } + } + return 0; } diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index 933d18e2fd7a9..70113268b6102 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "alloc-util.h" #include "cryptsetup-token.h" @@ -60,8 +61,12 @@ _public_ int cryptsetup_token_open_pin( assert(ret_password); assert(ret_password_len); + r = DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + /* This must not fail at this moment (internal error) */ - r = crypt_token_json_get(cd, token, &json); + r = sym_crypt_token_json_get(cd, token, &json); assert(token == r); assert(json); @@ -72,7 +77,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 +191,10 @@ _public_ void cryptsetup_token_dump( assert(json); - r = sd_json_parse(json, 0, &v, NULL, NULL); + if (DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED) < 0) + return; + + 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 +283,11 @@ _public_ int cryptsetup_token_validate( assert(json); - r = sd_json_parse(json, 0, &v, NULL, NULL); + r = DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + + 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-util.h b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h index 68b09fc4586db..c6a7d25f398d0 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h @@ -2,22 +2,21 @@ #pragma once -#include - +#include "cryptsetup-util.h" #include "shared-forward.h" /* crypt_dump() internal indentation magic */ #define CRYPT_DUMP_LINE_SEP "\n\t " -#define crypt_log_debug(cd, ...) crypt_logf(cd, CRYPT_LOG_DEBUG, __VA_ARGS__) -#define crypt_log_error(cd, ...) crypt_logf(cd, CRYPT_LOG_ERROR, __VA_ARGS__) -#define crypt_log_verbose(cd, ...) crypt_logf(cd, CRYPT_LOG_VERBOSE, __VA_ARGS__) -#define crypt_log(cd, ...) crypt_logf(cd, CRYPT_LOG_NORMAL, __VA_ARGS__) +#define crypt_log_debug(cd, ...) sym_crypt_logf(cd, CRYPT_LOG_DEBUG, __VA_ARGS__) +#define crypt_log_error(cd, ...) sym_crypt_logf(cd, CRYPT_LOG_ERROR, __VA_ARGS__) +#define crypt_log_verbose(cd, ...) sym_crypt_logf(cd, CRYPT_LOG_VERBOSE, __VA_ARGS__) +#define crypt_log(cd, ...) sym_crypt_logf(cd, CRYPT_LOG_NORMAL, __VA_ARGS__) #define crypt_log_full_errno(cd, e, lvl, ...) ({ \ int _e = ABS(e), _s = errno; \ errno = _e; \ - crypt_logf(cd, lvl, __VA_ARGS__); \ + sym_crypt_logf(cd, lvl, __VA_ARGS__); \ errno = _s; \ -_e; \ }) 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..a55791d7d2264 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c @@ -20,6 +20,7 @@ struct luks2_pkcs11_callback_data { size_t pin_size; void *encrypted_key; size_t encrypted_key_size; + Pkcs11RsaPadding rsa_padding; void *decrypted_key; size_t decrypted_key_size; }; @@ -90,6 +91,7 @@ static int luks2_pkcs11_callback( object, data->encrypted_key, data->encrypted_key_size, + data->rsa_padding, &data->decrypted_key, &data->decrypted_key_size); if (r < 0) @@ -109,6 +111,7 @@ static int acquire_luks2_key_by_pin( size_t pin_size, void *encrypted_key, size_t encrypted_key_size, + Pkcs11RsaPadding rsa_padding, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { @@ -119,6 +122,7 @@ static int acquire_luks2_key_by_pin( .pin_size = pin_size, .encrypted_key = encrypted_key, .encrypted_key_size = encrypted_key_size, + .rsa_padding = rsa_padding, }; assert(pkcs11_uri); @@ -142,6 +146,7 @@ static int acquire_luks2_key_systemd( systemd_pkcs11_plugin_params *params, void *encrypted_key, size_t encrypted_key_size, + Pkcs11RsaPadding rsa_padding, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { @@ -149,7 +154,8 @@ static int acquire_luks2_key_systemd( _cleanup_(pkcs11_crypt_device_callback_data_release) pkcs11_crypt_device_callback_data data = { .encrypted_key = encrypted_key, .encrypted_key_size = encrypted_key_size, - .free_encrypted_key = false + .free_encrypted_key = false, + .rsa_padding = rsa_padding, }; assert(pkcs11_uri); @@ -189,6 +195,7 @@ int acquire_luks2_key( _cleanup_(erase_and_freep) char *base64_encoded = NULL; _cleanup_free_ char *pkcs11_uri = NULL; _cleanup_free_ void *encrypted_key = NULL; + Pkcs11RsaPadding rsa_padding = PKCS11_RSA_PADDING_PKCS1V15; systemd_pkcs11_plugin_params *pkcs11_params = userdata; ssize_t base64_encoded_size; @@ -196,7 +203,7 @@ int acquire_luks2_key( assert(ret_password); assert(ret_password_size); - r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &encrypted_key, &encrypted_key_size); + r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &encrypted_key, &encrypted_key_size, &rsa_padding); if (r < 0) return r; @@ -208,11 +215,13 @@ int acquire_luks2_key( pkcs11_uri, pkcs11_params, encrypted_key, encrypted_key_size, + rsa_padding, &decrypted_key, &decrypted_key_size); else /* default activation that provides single PIN if needed */ r = acquire_luks2_key_by_pin( cd, pkcs11_uri, pin, pin_size, encrypted_key, encrypted_key_size, + rsa_padding, &decrypted_key, &decrypted_key_size); if (r < 0) return r; @@ -232,7 +241,8 @@ int parse_luks2_pkcs11_data( const char *json, char **ret_uri, void **ret_encrypted_key, - size_t *ret_encrypted_key_size) { + size_t *ret_encrypted_key_size, + Pkcs11RsaPadding *ret_rsa_padding) { int r; size_t key_size; @@ -240,13 +250,14 @@ int parse_luks2_pkcs11_data( _cleanup_free_ void *key = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; sd_json_variant *w; + Pkcs11RsaPadding rsa_padding = PKCS11_RSA_PADDING_PKCS1V15; assert(json); assert(ret_uri); 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; @@ -266,9 +277,27 @@ int parse_luks2_pkcs11_data( if (r < 0) return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m."); + /* Optional padding-scheme tag. Absent in legacy tokens (which used PKCS#1 v1.5). */ + w = sd_json_variant_by_key(v, "pkcs11-padding"); + if (w) { + Pkcs11RsaPadding p; + + if (!sd_json_variant_is_string(w)) + return crypt_log_debug_errno(cd, -EINVAL, + "LUKS2 token field 'pkcs11-padding' is not a string."); + p = pkcs11_rsa_padding_from_string(sd_json_variant_string(w)); + if (p < 0) + return crypt_log_debug_errno(cd, p, + "LUKS2 token field 'pkcs11-padding' has unsupported value '%s'.", + sd_json_variant_string(w)); + rsa_padding = p; + } + *ret_uri = TAKE_PTR(uri); *ret_encrypted_key = TAKE_PTR(key); *ret_encrypted_key_size = key_size; + if (ret_rsa_padding) + *ret_rsa_padding = rsa_padding; return 0; } diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h index d233b7ad063da..a4f11b64681f1 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h +++ b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h @@ -2,6 +2,7 @@ #pragma once +#include "pkcs11-util.h" #include "shared-forward.h" int acquire_luks2_key( @@ -18,4 +19,5 @@ int parse_luks2_pkcs11_data( const char *json, char **ret_uri, void **ret_encrypted_key, - size_t *ret_encrypted_key_size); + size_t *ret_encrypted_key_size, + Pkcs11RsaPadding *ret_rsa_padding); diff --git a/src/cryptsetup/cryptsetup-tokens/meson.build b/src/cryptsetup/cryptsetup-tokens/meson.build index 804e18bc67a2e..772c29f50f526 100644 --- a/src/cryptsetup/cryptsetup-tokens/meson.build +++ b/src/cryptsetup/cryptsetup-tokens/meson.build @@ -45,8 +45,8 @@ modules += [ ], 'sources' : cryptsetup_token_systemd_tpm2_sources, 'dependencies' : [ - libcryptsetup, - tpm2, + libcryptsetup_cflags, + tpm2_cflags, ], }, template + { @@ -57,8 +57,8 @@ modules += [ ], 'sources' : cryptsetup_token_systemd_fido2_sources, 'dependencies' : [ - libcryptsetup, - libfido2, + libcryptsetup_cflags, + libfido2_cflags, ], }, template + { @@ -69,7 +69,7 @@ modules += [ ], 'sources' : cryptsetup_token_systemd_pkcs11_sources, 'dependencies' : [ - libcryptsetup, + libcryptsetup_cflags, libp11kit_cflags, ], }, diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index dbd0b14d7bd1b..50f78a2004ce4 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + 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 @@ -603,7 +608,6 @@ static int parse_one_option(const char *option) { log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); } else if ((val = startswith(option, "link-volume-key="))) { -#if HAVE_CRYPT_SET_KEYRING_TO_LINK _cleanup_free_ char *keyring = NULL, *key_type = NULL, *key_description = NULL; const char *sep; @@ -652,9 +656,6 @@ static int parse_one_option(const char *option) { free_and_replace(arg_link_keyring, keyring); free_and_replace(arg_link_key_type, key_type); free_and_replace(arg_link_key_description, key_description); -#else - log_error("Build lacks libcryptsetup support for linking volume keys in user specified kernel keyrings upon device activation, ignoring: %s", option); -#endif } else if ((val = startswith(option, "fixate-volume-key="))) { r = free_and_strdup(&arg_fixate_volume_key, val); if (r < 0) @@ -823,19 +824,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 +1031,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 +1110,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 +1133,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 +1208,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 +1251,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 +1263,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 +1321,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 +1330,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 +1354,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 +1366,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 +1510,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 +1555,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 +1568,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 +1598,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 +1659,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 +1777,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 +1787,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 */ @@ -1814,6 +1815,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_free_ void *discovered_key = NULL; struct iovec discovered_key_data = {}; + Pkcs11RsaPadding rsa_padding = PKCS11_RSA_PADDING_PKCS1V15; int keyslot = arg_key_slot, r; const char *uri = NULL; bool use_libcryptsetup_plugin = use_token_plugins(); @@ -1824,7 +1826,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( if (arg_pkcs11_uri_auto) { if (!use_libcryptsetup_plugin) { - r = find_pkcs11_auto_data(cd, &discovered_uri, &discovered_key, &discovered_key_size, &keyslot); + r = find_pkcs11_auto_data(cd, &discovered_uri, &discovered_key, &discovered_key_size, &rsa_padding, &keyslot); if (IN_SET(r, -ENOTUNIQ, -ENXIO)) return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "Automatic PKCS#11 metadata discovery was not possible because missing or not unique, falling back to traditional unlocking."); @@ -1842,7 +1844,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(); @@ -1860,6 +1862,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( name, friendly, uri, + rsa_padding, key_file, arg_keyfile_size, arg_keyfile_offset, key_data, until, @@ -2036,7 +2039,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(); @@ -2055,7 +2058,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( /* pcrlock_path= */ NULL, /* primary_alg= */ 0, key_file, arg_keyfile_size, arg_keyfile_offset, - key_data, /* n_blobs= */ 1, + key_data, /* n_blobs= */ iovec_is_set(key_data) ? 1 : 0, /* policy_hash= */ NULL, /* we don't know the policy hash */ /* n_policy_hash= */ 0, /* salt= */ NULL, @@ -2382,7 +2385,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 +2424,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 +2433,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 +2454,68 @@ 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 opts = { argc, argv }; - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts); return 1; } @@ -2540,6 +2550,8 @@ static uint32_t determine_flags(void) { static void remove_and_erasep(const char **p) { int r; + assert(p); + if (!*p) return; @@ -2586,7 +2598,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; @@ -2625,25 +2639,28 @@ static int verb_attach(int argc, char *argv[], void *userdata) { /* A delicious drop of snake oil */ (void) safe_mlockall(MCL_CURRENT|MCL_FUTURE|MCL_ONFAULT); + /* Only erase key files explicitly configured on the command line, never the ones we + * auto-discover in /etc/cryptsetup-keys.d/ and /run/cryptsetup-keys.d/: those are shared + * resources not owned by an individual volume. (key_file is NULL when auto-discovery is used.) */ if (key_file && arg_keyfile_erase) destroy_key_file = key_file; /* let's get this baby erased when we leave */ 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 + /* since cryptsetup 2.7.0 (Jan 2024) */ if (arg_link_key_description) { - r = crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring); - if (r < 0) + r = sym_crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring); + if (r == -ENOSYS) + log_warning("Loaded libcryptsetup does not support linking volume keys in user specified kernel keyrings upon device activation, ignoring."); + else 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; @@ -2738,6 +2755,8 @@ static int verb_attach(int argc, char *argv[], void *userdata) { return r; if (r > 0) key_data = &discovered_key_data; + else + try_discover_key = false; } if (token_type < 0 && !key_file && !key_data && !passwords) { @@ -2826,7 +2845,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 +2857,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 +2867,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 +2881,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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb(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..700ad528930b3 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; @@ -278,7 +279,7 @@ static int process_unit_credentials(const char *credentials_dir) { assert(credentials_dir); - r = readdir_all_at(AT_FDCWD, credentials_dir, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des); + r = readdir_all_at(AT_FDCWD, credentials_dir, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_REGULAR, &des); if (r < 0) return log_error_errno(r, "Failed to enumerate credentials from credentials directory '%s': %m", credentials_dir); @@ -286,9 +287,6 @@ static int process_unit_credentials(const char *credentials_dir) { struct dirent *de = *i; const char *unit, *dropin; - if (de->d_type != DT_REG) - continue; - unit = startswith(de->d_name, "systemd.extra-unit."); dropin = startswith(de->d_name, "systemd.unit-dropin."); diff --git a/src/delta/delta.c b/src/delta/delta.c index df28a730a54e6..27dfc105ee7d6 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,44 @@ 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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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", opts.arg, NULL); if (r < 0) return r; arg_diff = r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&opts); return 1; } @@ -577,7 +559,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 +574,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..be39634583f2c 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,77 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts) > 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..e70263a9fd4f5 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -42,6 +41,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 +122,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 +131,17 @@ static int help(void) { if (r < 0) return log_oom(); + r = option_parser_get_help_table_ns("systemd-dissect", &options); + if (r < 0) + return r; + + r = option_parser_get_help_table_full("systemd-dissect", "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 +156,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 +189,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 +223,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 opts = { argc, argv, .namespace = "systemd-dissect" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - return help(); + OPTION_NAMESPACE("systemd-dissect"): {} - 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; - 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': + OPTION('r', "read-only", NULL, "Mount read-only"): 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(opts.arg, "disabled")) flags = 0; - else if (streq(optarg, "loop")) + else if (streq(opts.arg, "loop")) flags = DISSECT_IMAGE_DISCARD_ON_LOOP; - else if (streq(optarg, "all")) + else if (streq(opts.arg, "all")) flags = DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD; - else if (streq(optarg, "crypt")) + else if (streq(opts.arg, "crypt")) flags = DISSECT_IMAGE_DISCARD_ANY; - else if (streq(optarg, "list")) { + else if (streq(opts.arg, "list")) { puts("disabled\n" "all\n" "crypt\n" @@ -467,32 +275,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", opts.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(opts.opt->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(opts.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", opts.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", opts.arg); iovec_done(&arg_verity_settings.root_hash); arg_verity_settings.root_hash = TAKE_STRUCT(roothash); @@ -500,24 +308,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(opts.opt->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(opts.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", opts.arg); } else { - r = read_full_file(optarg, (char**) &sig.iov_base, &sig.iov_len); + r = read_full_file(opts.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", opts.arg); } iovec_done(&arg_verity_settings.root_hash_sig); @@ -526,142 +338,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(opts.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(opts.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", opts.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(opts.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", opts.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(opts.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(opts.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(opts.arg) >= sizeof_field(struct loop_info64, lo_file_name)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Loop device ref string '%s' is too long.", opts.arg); - r = free_and_strdup_warn(&arg_loop_ref, optarg); + r = free_and_strdup_warn(&arg_loop_ref, opts.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(opts.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=", opts.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(opts.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", opts.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=", opts.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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + 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(&opts); + 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 +535,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 +551,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 +594,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 +658,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 +677,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; @@ -879,41 +746,45 @@ static int parse_argv(int argc, char *argv[]) { static int parse_argv_as_mount_helper(int argc, char *argv[]) { const char *options = NULL; bool fake = false; - int c, r; + int r; /* Implements util-linux "external helper" command line interface, as per mount(8) man page. */ - while ((c = getopt(argc, argv, "sfnvN:o:t:")) >= 0) { + OptionParser opts = { argc, argv, .namespace = "mount.ddi" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'f': + OPTION_NAMESPACE("mount.ddi"): {} + + OPTION_SHORT('f', NULL, /* help= */ NULL): fake = true; break; - case 'o': - options = optarg; + OPTION_SHORT('o', "OPTIONS", /* help= */ NULL): + options = opts.arg; break; - case 't': - if (!streq(optarg, "ddi")) - log_debug("Unexpected file system type '%s', ignoring.", optarg); + OPTION_SHORT('t', "FSTYPE", /* help= */ NULL): + if (!streq(opts.arg, "ddi")) + log_debug("Unexpected file system type '%s', ignoring.", opts.arg); break; - case 's': /* sloppy mount options */ - case 'n': /* aka --no-mtab */ - case 'v': /* aka --verbose */ - log_debug("Ignoring option -%c, not implemented.", c); + OPTION_SHORT('s', NULL, /* help= */ NULL): {} /* sloppy mount options */ + OPTION_SHORT('n', NULL, /* help= */ NULL): {} /* aka --no-mtab */ + OPTION_SHORT('v', NULL, /* help= */ NULL): /* aka --verbose */ + log_debug("Ignoring option -%c, not implemented.", opts.opt->short_code); break; - case 'N': /* aka --namespace= */ - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Option -%c is not implemented, refusing.", c); - - case '?': - return -EINVAL; + OPTION_SHORT('N', "NS", /* help= */ NULL): /* aka --namespace= */ + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Option -%c is not implemented, refusing.", + opts.opt->short_code); } - } - if (optind + 2 != argc) + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + + if (n_args != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected an image file path and target directory as only argument."); @@ -942,11 +813,11 @@ static int parse_argv_as_mount_helper(int argc, char *argv[]) { if (fake) return 0; - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_image); + r = parse_path_argument(args[0], /* suppress_root= */ false, &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; @@ -1237,9 +1108,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,8 +2041,13 @@ 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; + /* --attach hands a loop device to the user, who may populate it with a (nested) + * partition table afterwards, so force a real loopback device with partition + * scanning even if the image is currently unpartitioned. */ + if (arg_action == ACTION_ATTACH) + loop_flags |= LOOP_DEVICE_MAY_POPULATE_PARTITION_TABLE; if (arg_in_memory) r = loop_device_make_by_path_memory(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d); @@ -2184,7 +2060,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..09e0338c348fd 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,82 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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\".", opts.arg); - arg_suffix = optarg; + arg_suffix = opts.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(opts.arg, UNIT_NAME_TEMPLATE)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Template name %s is not valid.", optarg); + "Template name %s is not valid.", opts.arg); - arg_template = optarg; + arg_template = opts.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(&opts) == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not enough arguments."); @@ -154,6 +130,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(&opts); return 1; } @@ -162,11 +139,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 +245,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..d3019396c3c1f 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,65 @@ 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 opts = { argc, argv }; - case 'h': + FOREACH_OPTION_OR_RETURN(c, &opts) + 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 +95,12 @@ static int parse_argv(int argc, char *argv[]) { if (r > 0) arg_varlink = true; + *ret_args = option_parser_get_args(&opts); return 1; } -static int verb_status(int argc, char *argv[], void *userdata) { +VERB_DEFAULT_NOARG(verb_status, "status", "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 +121,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 +189,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 +262,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 +353,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(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 38e3adaed6eca..2200235e1d1cc 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" @@ -9,6 +8,7 @@ #include "sd-varlink.h" #include "alloc-util.h" +#include "ansi-color.h" #include "ask-password-api.h" #include "build.h" #include "bus-error.h" @@ -24,8 +24,11 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "glyph-util.h" +#include "help-util.h" +#include "hostname-setup.h" #include "hostname-util.h" #include "image-policy.h" #include "kbd-util.h" @@ -38,13 +41,12 @@ #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" -#include "pretty-print.h" #include "proc-cmdline.h" #include "prompt-util.h" #include "runtime-scope.h" @@ -54,7 +56,7 @@ #include "strv.h" #include "terminal-util.h" #include "time-util.h" -#include "tmpfile-util-label.h" +#include "tmpfile-util.h" #include "user-util.h" #include "vconsole-util.h" @@ -66,6 +68,7 @@ static char *arg_keymap = NULL; static char *arg_timezone = NULL; static char *arg_hostname = NULL; static sd_id128_t arg_machine_id = {}; +static char **arg_machine_tags = NULL; static char *arg_root_password = NULL; static char *arg_root_shell = NULL; static char *arg_kernel_cmdline = NULL; @@ -97,14 +100,16 @@ STATIC_DESTRUCTOR_REGISTER(arg_locale_messages, freep); STATIC_DESTRUCTOR_REGISTER(arg_keymap, freep); STATIC_DESTRUCTOR_REGISTER(arg_timezone, freep); STATIC_DESTRUCTOR_REGISTER(arg_hostname, freep); +STATIC_DESTRUCTOR_REGISTER(arg_machine_tags, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_root_password, erase_and_freep); STATIC_DESTRUCTOR_REGISTER(arg_root_shell, freep); STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); +static bool welcome_done = false; + static void print_welcome(int rfd, sd_varlink **mute_console_link) { - _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL; - static bool done = false; + _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL, *fancy_name = NULL; const char *pn, *ac; int r; @@ -121,7 +126,7 @@ static void print_welcome(int rfd, sd_varlink **mute_console_link) { if (!arg_welcome) return; - if (done) { + if (welcome_done) { putchar('\n'); /* Add some breathing room between multiple prompts */ return; } @@ -133,6 +138,7 @@ static void print_welcome(int rfd, sd_varlink **mute_console_link) { r = parse_os_release_at(rfd, "PRETTY_NAME", &pretty_name, + "FANCY_NAME", &fancy_name, "NAME", &os_name, "ANSI_COLOR", &ansi_color); if (r < 0) @@ -142,7 +148,9 @@ static void print_welcome(int rfd, sd_varlink **mute_console_link) { pn = os_release_pretty_name(pretty_name, os_name); ac = isempty(ansi_color) ? "0" : ansi_color; - if (colors_enabled()) + if (use_fancy_name(unescape_fancy_name(&fancy_name))) + printf(ANSI_HIGHLIGHT "Welcome to " ANSI_NORMAL "%s" ANSI_HIGHLIGHT "!" ANSI_NORMAL "\n", fancy_name); + else if (colors_enabled()) printf(ANSI_HIGHLIGHT "Welcome to " ANSI_NORMAL "\x1B[%sm%s" ANSI_HIGHLIGHT "!" ANSI_NORMAL "\n", ac, pn); else printf("Welcome to %s!\n", pn); @@ -154,7 +162,7 @@ static void print_welcome(int rfd, sd_varlink **mute_console_link) { } printf("Please configure the system!\n\n"); - done = true; + welcome_done = true; } static int should_configure(int dir_fd, const char *filename) { @@ -290,8 +298,12 @@ static int prompt_locale(int rfd, sd_varlink **mute_console_link) { } else { print_welcome(rfd, mute_console_link); + _cleanup_free_ char *prefill = NULL; + (void) locale_lang_from_efi(&prefill, LOCALE_REQUIRE_INSTALLED|LOCALE_SUPPRESS_EN_US); + r = prompt_loop("Please enter the new system locale name or number", GLYPH_WORLD, + prefill, locales, /* accepted= */ NULL, /* ellipsize_percentage= */ 60, @@ -309,6 +321,7 @@ static int prompt_locale(int rfd, sd_varlink **mute_console_link) { r = prompt_loop("Please enter the new system message locale name or number", GLYPH_WORLD, + /* prefill= */ NULL, locales, /* accepted= */ NULL, /* ellipsize_percentage= */ 60, @@ -339,8 +352,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 +425,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; } @@ -447,9 +464,13 @@ static int prompt_keymap(int rfd, sd_varlink **mute_console_link) { print_welcome(rfd, mute_console_link); + _cleanup_free_ char *prefill = NULL; + (void) vconsole_keymap_from_efi(&prefill); + return prompt_loop( "Please enter the new keymap name or number", GLYPH_KEYBOARD, + prefill, kmaps, /* accepted= */ NULL, /* ellipsize_percentage= */ 60, @@ -470,8 +491,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 +561,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; } @@ -562,6 +587,7 @@ static int prompt_timezone(int rfd, sd_varlink **mute_console_link) { return prompt_loop( "Please enter the new timezone name or number", GLYPH_CLOCK, + /* prefill= */ NULL, zones, /* accepted= */ NULL, /* ellipsize_percentage= */ 30, @@ -582,8 +608,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 +673,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|VALID_HOSTNAME_WORD_TOKEN)) + 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; @@ -656,6 +695,7 @@ static int prompt_hostname(int rfd, sd_varlink **mute_console_link) { r = prompt_loop("Please enter the new hostname", GLYPH_LABEL, + /* prefill= */ NULL, /* menu= */ NULL, /* accepted= */ NULL, /* ellipsize_percentage= */ 100, @@ -682,9 +722,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"); @@ -701,7 +739,23 @@ static int process_hostname(int rfd, sd_varlink **mute_console_link) { if (isempty(arg_hostname)) return 0; - r = write_string_file_at(pfd, f, arg_hostname, + /* On running systems we have a machine ID, so resolve any '?'/'$' wildcards now and persist them. + * This "freezes" the name, so later word list updates do not change it. When operating on an offline + * image (--root=/--image=) the target's machine ID is not known yet, so write the template verbatim + * and let it be resolved on each first boot. */ + const char *hostname = arg_hostname; + _cleanup_free_ char *resolved = NULL; + if (!arg_root) { + r = hostname_substitute_wildcards(arg_hostname, &resolved); + if (r < 0) + log_warning_errno(r, "Failed to resolve wildcards in hostname '%s', writing it verbatim: %m", arg_hostname); + else if (!hostname_is_valid(resolved, VALID_HOSTNAME_TRAILING_DOT)) + log_warning("Resolved hostname '%s' is invalid, writing template '%s' verbatim instead.", resolved, arg_hostname); + else + hostname = resolved; + } + + r = write_string_file_at(pfd, f, hostname, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL); if (r < 0) return log_error_errno(r, "Failed to write /etc/hostname: %m"); @@ -717,8 +771,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"); @@ -743,6 +797,71 @@ static int process_machine_id(int rfd) { return 0; } +static int process_machine_tags(int rfd) { + int r; + + assert(rfd >= 0); + + _cleanup_free_ char *f = NULL; + _cleanup_close_ int pfd = chase_and_open_parent_at( + /* root_fd= */ rfd, + /* dir_fd= */ rfd, + "/etc/machine-info", + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + &f); + if (pfd < 0) + return log_error_errno(pfd, "Failed to chase /etc/machine-info parent: %m"); + + r = should_configure(pfd, f); + if (r == 0) + log_debug("Found /etc/machine-info, assuming machine tags have been configured."); + if (r <= 0) + return r; + + if (!arg_machine_tags) { + _cleanup_free_ char *tags = NULL; + r = read_credential("firstboot.machine-tags", (void**) &tags, /* ret_size= */ NULL); + if (r < 0) + log_debug_errno(r, "Failed to read credential firstboot.machine-tags, ignoring: %m"); + else { + _cleanup_strv_free_ char **l = NULL; + r = machine_tags_from_string(tags, /* graceful= */ false, &l); + if (r < 0) + log_warning_errno(r, "Failed to parse machine tags '%s', ignoring credential: %m", tags); + else { + strv_free_and_replace(arg_machine_tags, l); + log_debug("Acquired machine tags list from credentials."); + } + } + } + + /* NB: We do not prompt for machine tags, at least not for now */ + + if (!arg_machine_tags) { + log_debug("Initialization of machine tags was not requested, skipping."); + return 0; + } + + _cleanup_free_ char *j = strv_join(arg_machine_tags, ":"); + if (!j) + return log_oom(); + + _cleanup_free_ char *c = strjoin("TAGS=\"", j, "\"\n"); + if (!c) + return log_oom(); + + r = write_string_file_at( + pfd, + "machine-info", + c, + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL); + if (r < 0) + return log_error_errno(r, "Failed to write /etc/machine-info: %m"); + + log_info("/etc/machine-info written."); + return 0; +} + static int prompt_root_password(int rfd, sd_varlink **mute_console_link) { const char *msg1, *msg2; int r; @@ -827,7 +946,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); @@ -866,6 +985,7 @@ static int prompt_root_shell(int rfd, sd_varlink **mute_console_link) { return prompt_loop( "Please enter the new root shell", GLYPH_SHELL, + /* prefill= */ NULL, /* menu= */ NULL, /* accepted= */ NULL, /* ellipsize_percentage= */ 0, @@ -1031,8 +1151,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 +1268,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 +1301,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) @@ -1218,381 +1338,255 @@ 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); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; + + help_cmdline("[OPTIONS...]"); + help_abstract("Configures basic settings of the system."); + help_section("Options"); - 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", - program_invocation_short_name, - link, - ansi_highlight(), - ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("systemd-firstboot", "1"); 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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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(opts.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(opts.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, opts.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, opts.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(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Keymap %s is not valid.", optarg); + "Keymap %s is not valid.", opts.arg); - r = free_and_strdup(&arg_keymap, optarg); + r = free_and_strdup_warn(&arg_keymap, opts.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(opts.arg, LOG_ERR)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Timezone %s is not valid.", optarg); + "Timezone %s is not valid.", opts.arg); - r = free_and_strdup(&arg_timezone, optarg); + r = free_and_strdup_warn(&arg_timezone, opts.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(opts.arg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK|VALID_HOSTNAME_WORD_TOKEN)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Host name %s is not valid.", opts.arg); + + r = free_and_strdup_warn(&arg_hostname, opts.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(opts.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.", opts.arg); break; - case ARG_ROOT_SHELL: - r = free_and_strdup(&arg_root_shell, optarg); + OPTION_LONG("machine-tags", "TAG[:…]", "Set machine tags"): { + _cleanup_strv_free_ char **tags = NULL; + r = machine_tags_from_string(opts.arg, /* graceful= */ false, &tags); if (r < 0) - return log_oom(); + return log_error_errno(r, "Failed to parse machine tags '%s': %m", opts.arg); + strv_free_and_replace(arg_machine_tags, tags); 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); - - r = free_and_strdup(&arg_hostname, optarg); + OPTION_LONG("root-password", "PASSWORD", "Set root password from plaintext password"): + r = free_and_strdup_warn(&arg_root_password, opts.arg); if (r < 0) - return log_oom(); + return r; - 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-file", "FILE", "Set root password from file"): + arg_root_password = mfree(arg_root_password); + + r = read_one_line_file(opts.arg, &arg_root_password); if (r < 0) - return log_error_errno(r, "Failed to generate randomized machine ID: %m"); + return log_error_errno(r, "Failed to read %s: %m", opts.arg); + arg_root_password_is_hashed = false; break; - case ARG_MACHINE_ID: - r = sd_id128_from_string(optarg, &arg_machine_id); + OPTION_LONG("root-password-hashed", "HASH", "Set root password from hashed password"): + r = free_and_strdup_warn(&arg_root_password, opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse machine id %s.", optarg); + return r; + arg_root_password_is_hashed = true; break; - case ARG_KERNEL_COMMAND_LINE: - r = free_and_strdup(&arg_kernel_cmdline, optarg); + OPTION_LONG("root-shell", "SHELL", "Set root shell"): + r = free_and_strdup_warn(&arg_root_shell, opts.arg); if (r < 0) - return log_oom(); - + return r; 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; + OPTION_LONG("kernel-command-line", "CMDLINE", "Set kernel command line"): + r = free_and_strdup_warn(&arg_kernel_cmdline, opts.arg); + if (r < 0) + 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=", opts.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=", opts.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=", opts.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)) @@ -1663,6 +1657,15 @@ static int reload_vconsole(sd_bus **bus) { return 0; } +static void end_marker(void) { + + if (!welcome_done) + return; + + printf("\n%sExiting first boot settings tool.%s\n\n", ansi_grey(), ansi_normal()); + fflush(stdout); +} + static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; @@ -1728,6 +1731,7 @@ static int run(int argc, char *argv[]) { } LOG_SET_PREFIX(arg_image ?: arg_root); + DEFER_VOID_CALL(end_marker); DEFER_VOID_CALL(chrome_hide); /* We check these conditions here instead of in parse_argv() so that we can take the root directory @@ -1783,6 +1787,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = process_machine_tags(rfd); + if (r < 0) + return r; + return 0; } 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..3a5ce47691e0f 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) @@ -693,7 +704,7 @@ static int add_mount( if (r < 0) { if (r != -EOPNOTSUPP) return r; - } else { + } else if (r > 0) { r = generator_hook_up_quotaon(dest, where, target_unit); if (r < 0) return r; @@ -790,7 +801,7 @@ static int do_daemon_reload(void) { k = bus_call_method(bus, bus_systemd_mgr, "StartUnit", &error, NULL, "ss", unit, "replace"); if (k < 0) { - log_error_errno(k, "Failed to (re)start %s: %s", unit, bus_error_message(&error, r)); + log_error_errno(k, "Failed to (re)start %s: %s", unit, bus_error_message(&error, k)); RET_GATHER(r, k); } } @@ -1229,6 +1240,26 @@ static int add_sysroot_mount(void) { if (!strextend_with_separator(&combined_options, ",", extra_opts)) return log_oom(); + /* A bind mount inherits the mount flags (nosuid, nodev, noexec, …) of the file system the source + * directory is located on. The source typically lives below /run/ (e.g. a freshly unpacked tar image + * in /run/machines/), which is mounted nosuid,nodev, and these flags would then propagate to our root + * file system, breaking suid binaries (e.g. sudo) and device nodes. Since this is supposed to become a + * regular OS root file system, default to dev,suid,exec instead, unless the user explicitly requested + * otherwise. */ + if (bind) { + static const char* const defaults[] = { + "suid", "suid\0" "nosuid\0", + "dev", "dev\0" "nodev\0", + "exec", "exec\0" "noexec\0", + NULL, + }; + + STRV_FOREACH_PAIR(add, test, defaults) + if (!fstab_test_option(combined_options, *test)) + if (!strextend_with_separator(&combined_options, ",", *add)) + return log_oom(); + } + log_debug("Found entry what=%s where=/sysroot type=%s opts=%s", what, strna(fstype), strempty(combined_options)); /* Only honor x-systemd.makefs and .validatefs here, others are not relevant in initrd/not used diff --git a/src/fundamental/assert-fundamental.h b/src/fundamental/assert-util.h similarity index 80% rename from src/fundamental/assert-fundamental.h rename to src/fundamental/assert-util.h index 3168e5699aa93..7f173fefb35f9 100644 --- a/src/fundamental/assert-fundamental.h +++ b/src/fundamental/assert-util.h @@ -5,7 +5,7 @@ # include #endif -#include "macro-fundamental.h" +#include "macro.h" #if SD_BOOT _noreturn_ void efi_assert(const char *expr, const char *file, unsigned line, const char *function); @@ -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/bootspec-fundamental.c b/src/fundamental/bootspec.c similarity index 98% rename from src/fundamental/bootspec-fundamental.c rename to src/fundamental/bootspec.c index 5ca66740294e2..6daf48479deb7 100644 --- a/src/fundamental/bootspec-fundamental.c +++ b/src/fundamental/bootspec.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "bootspec-fundamental.h" +#include "bootspec.h" bool bootspec_pick_name_version_sort_key( const sd_char *os_pretty_name, diff --git a/src/fundamental/bootspec-fundamental.h b/src/fundamental/bootspec.h similarity index 94% rename from src/fundamental/bootspec-fundamental.h rename to src/fundamental/bootspec.h index 19b489ccf55ad..50722d2b34534 100644 --- a/src/fundamental/bootspec-fundamental.h +++ b/src/fundamental/bootspec.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "string-util-fundamental.h" +#include "string-util.h" bool bootspec_pick_name_version_sort_key( const sd_char *os_pretty_name, diff --git a/src/fundamental/chid-fundamental.c b/src/fundamental/chid.c similarity index 98% rename from src/fundamental/chid-fundamental.c rename to src/fundamental/chid.c index 474aab74840fa..28bc4572f06bf 100644 --- a/src/fundamental/chid-fundamental.c +++ b/src/fundamental/chid.c @@ -24,10 +24,10 @@ #define strlen16 char16_strlen #endif -#include "chid-fundamental.h" -#include "macro-fundamental.h" -#include "memory-util-fundamental.h" -#include "sha1-fundamental.h" +#include "chid.h" +#include "macro.h" +#include "memory-util.h" +#include "sha1.h" static void get_chid( const char16_t *const smbios_fields[static _CHID_SMBIOS_FIELDS_MAX], diff --git a/src/fundamental/chid-fundamental.h b/src/fundamental/chid.h similarity index 94% rename from src/fundamental/chid-fundamental.h rename to src/fundamental/chid.h index a0e77dd9b02ce..1d5247bf12bbc 100644 --- a/src/fundamental/chid-fundamental.h +++ b/src/fundamental/chid.h @@ -2,14 +2,12 @@ #pragma once -#if SD_BOOT -# include "efi.h" -#else +#include "efi.h" + +#if !SD_BOOT # include #endif -#include "efi-fundamental.h" - #define CHID_TYPES_MAX 18 /* Any chids starting from EXTRA_CHID_BASE are non-standard and are subject to change and renumeration at any time */ #define EXTRA_CHID_BASE 15 diff --git a/src/fundamental/cleanup-fundamental.h b/src/fundamental/cleanup-util.h similarity index 55% rename from src/fundamental/cleanup-fundamental.h rename to src/fundamental/cleanup-util.h index 86b9851fd54ab..538469f255ac0 100644 --- a/src/fundamental/cleanup-fundamental.h +++ b/src/fundamental/cleanup-util.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "assert-fundamental.h" +#include "assert-util.h" /* A wrapper for 'func' to return void. * Only useful when a void-returning function is required by some API. */ @@ -45,6 +45,36 @@ #define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(type, macro, empty) \ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO_RENAME(type, macro, macro##p, empty) +/* 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); \ + } + +/* Like DEFINE_POINTER_ARRAY_FREE_FUNC() but does not deallocate the array itself, useful for + * arrays with automatic storage duration (e.g. on the stack). */ +#define DEFINE_POINTER_ARRAY_CLEAR_FUNC(type, helper) \ + void helper ## _array_clear(type *array, size_t n) { \ + assert(array || n == 0); \ + FOREACH_ARRAY(item, array, n) \ + *item = helper(*item); \ + } + +/* 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 */ @@ -79,3 +109,33 @@ static inline void array_cleanup(const ArrayCleanup *c) { _f; \ }), \ } + +/* An automatic _cleanup_-like logic for fixed-size arrays where the bound is known via + * ELEMENTSOF(). Unlike CLEANUP_ARRAY() this neither frees the storage nor zeroes it: it just + * invokes func() across the elements when leaving scope. */ +typedef struct ElementsCleanup { + void *array; + size_t n; + free_array_func_t pfunc; +} ElementsCleanup; + +static inline void elements_cleanup(const ElementsCleanup *c) { + assert(c); + + if (c->n == 0) + return; + + assert(c->array); + assert(c->pfunc); + c->pfunc(c->array, c->n); +} + +#define CLEANUP_ELEMENTS(_array, _func) \ + _cleanup_(elements_cleanup) _unused_ const ElementsCleanup CONCATENATE(_cleanup_elements_, UNIQ) = { \ + .array = (_array), \ + .n = ELEMENTSOF(_array), \ + .pfunc = (free_array_func_t) ({ \ + void (*_f)(typeof((_array)[0]) *a, size_t b) = _func; \ + _f; \ + }), \ + } diff --git a/src/fundamental/confidential-virt-fundamental.h b/src/fundamental/confidential-virt.h similarity index 98% rename from src/fundamental/confidential-virt-fundamental.h rename to src/fundamental/confidential-virt.h index d78ad3650c483..fce172e6f397b 100644 --- a/src/fundamental/confidential-virt-fundamental.h +++ b/src/fundamental/confidential-virt.h @@ -4,7 +4,7 @@ #include /* Keep CVM detection logic in this file at feature parity with - * that in src/efi/boot/vmm.c */ + * that in src/boot/vmm.c */ #define CPUID_PROCESSOR_INFO_AND_FEATURE_BITS UINT32_C(0x1) diff --git a/src/fundamental/edid-fundamental.c b/src/fundamental/edid.c similarity index 97% rename from src/fundamental/edid-fundamental.c rename to src/fundamental/edid.c index bfd314e44452b..980a3989b7115 100644 --- a/src/fundamental/edid-fundamental.c +++ b/src/fundamental/edid.c @@ -4,8 +4,8 @@ #include #endif -#include "edid-fundamental.h" -#include "efivars-fundamental.h" +#include "edid.h" +#include "efivars.h" #define EDID_FIXED_HEADER_PATTERN "\x00\xFF\xFF\xFF\xFF\xFF\xFF" assert_cc(sizeof_field(EdidHeader, pattern) == sizeof(EDID_FIXED_HEADER_PATTERN)); diff --git a/src/fundamental/edid-fundamental.h b/src/fundamental/edid.h similarity index 97% rename from src/fundamental/edid-fundamental.h rename to src/fundamental/edid.h index 2a76c4524de2e..6c5930f4c6ac3 100644 --- a/src/fundamental/edid-fundamental.h +++ b/src/fundamental/edid.h @@ -10,7 +10,7 @@ # include #endif -#include "macro-fundamental.h" +#include "macro.h" /* EDID structure, version 1.4 */ typedef struct EdidHeader { diff --git a/src/fundamental/efi-fundamental.h b/src/fundamental/efi.h similarity index 100% rename from src/fundamental/efi-fundamental.h rename to src/fundamental/efi.h diff --git a/src/fundamental/efivars-fundamental.c b/src/fundamental/efivars.c similarity index 98% rename from src/fundamental/efivars-fundamental.c rename to src/fundamental/efivars.c index 611b0b682d9b7..87dc1fff8f4b4 100644 --- a/src/fundamental/efivars-fundamental.c +++ b/src/fundamental/efivars.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "efivars-fundamental.h" +#include "efivars.h" static const sd_char * const table[_SECURE_BOOT_MAX] = { [SECURE_BOOT_UNSUPPORTED] = STR_C("unsupported"), diff --git a/src/fundamental/efivars-fundamental.h b/src/fundamental/efivars.h similarity index 92% rename from src/fundamental/efivars-fundamental.h rename to src/fundamental/efivars.h index 15be52119a0a2..88bc97d75d6c3 100644 --- a/src/fundamental/efivars-fundamental.h +++ b/src/fundamental/efivars.h @@ -6,7 +6,7 @@ #else # include #endif -#include "string-util-fundamental.h" +#include "string-util.h" /* Features of the loader, i.e. systemd-boot */ #define EFI_LOADER_FEATURE_CONFIG_TIMEOUT (UINT64_C(1) << 0) @@ -29,6 +29,8 @@ #define EFI_LOADER_FEATURE_TYPE1_UKI_URL (UINT64_C(1) << 17) #define EFI_LOADER_FEATURE_TPM2_ACTIVE_PCR_BANKS (UINT64_C(1) << 18) #define EFI_LOADER_FEATURE_ENTRY_PREFERRED (UINT64_C(1) << 19) +#define EFI_LOADER_FEATURE_KEYBOARD_LAYOUT (UINT64_C(1) << 20) +#define EFI_LOADER_FEATURE_SMBIOS_MEASURED (UINT64_C(1) << 21) /* Features of the stub, i.e. systemd-stub */ #define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0) @@ -43,6 +45,7 @@ #define EFI_STUB_FEATURE_MULTI_PROFILE_UKI (UINT64_C(1) << 9) #define EFI_STUB_FEATURE_REPORT_STUB_PARTITION (UINT64_C(1) << 10) #define EFI_STUB_FEATURE_REPORT_URL (UINT64_C(1) << 11) +#define EFI_STUB_FEATURE_SMBIOS_MEASURED (UINT64_C(1) << 12) typedef enum SecureBootMode { SECURE_BOOT_UNSUPPORTED, diff --git a/src/fundamental/iovec-util-fundamental.h b/src/fundamental/iovec-util.h similarity index 65% rename from src/fundamental/iovec-util-fundamental.h rename to src/fundamental/iovec-util.h index 707274f86e53f..7d8f65db43d2b 100644 --- a/src/fundamental/iovec-util-fundamental.h +++ b/src/fundamental/iovec-util.h @@ -5,8 +5,8 @@ #include #endif -#include "assert-fundamental.h" /* IWYU pragma: keep */ -#include "macro-fundamental.h" +#include "assert-util.h" /* IWYU pragma: keep */ +#include "macro.h" #if SD_BOOT /* struct iovec is a POSIX userspace construct. Let's introduce it also in EFI mode, it's just so useful */ @@ -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.h similarity index 90% rename from src/fundamental/macro-fundamental.h rename to src/fundamental/macro.h index e8757b1fc37a4..12f2aa81dcd83 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro.h @@ -72,6 +72,7 @@ #define REENABLE_WARNING \ _Pragma("GCC diagnostic pop") +#define _alias_(x) typeof(x) __attribute__((alias(#x))) #define _align_(x) __attribute__((__aligned__(x))) #define _alignas_(x) __attribute__((__aligned__(alignof(x)))) #define _alignptr_ __attribute__((__aligned__(sizeof(void *)))) @@ -88,7 +89,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 +99,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 +172,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); \ @@ -168,12 +180,14 @@ UNIQ_T(A, aq) > UNIQ_T(B, bq) ? UNIQ_T(A, aq) : UNIQ_T(B, bq); \ }) -#ifdef __clang__ -# define ABS(a) __builtin_llabs(a) -#else -# define ABS(a) __builtin_imaxabs(a) -#endif -assert_cc(sizeof(long long) == sizeof(intmax_t)); +#define ABS(a) _Generic((a), \ + float: __builtin_fabsf((float) (a)), \ + double: __builtin_fabs((double) (a)), \ + long double: __builtin_fabsl((long double) (a)), \ + unsigned long long: (a), \ + unsigned long: (a), \ + unsigned int: (a), \ + default: __builtin_llabs((long long) (a))) #define IS_UNSIGNED_INTEGER_TYPE(type) \ (__builtin_types_compatible_p(typeof(type), unsigned char) || \ @@ -216,8 +230,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 +245,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( \ @@ -253,6 +281,13 @@ assert_cc(sizeof(long long) == sizeof(intmax_t)); CONST_ISPOWEROF2(_x); \ })) +/* Returns the largest power of two that divides x (i.e. x's natural alignment in bytes), or 0 if x is 0. */ +#define NATURAL_ALIGNMENT(x) \ + ({ \ + const uint64_t _x = (x); \ + _x == 0 ? UINT64_C(0) : UINT64_C(1) << __builtin_ctzll(_x); \ + }) + #define ADD_SAFE(ret, a, b) (!__builtin_add_overflow(a, b, ret)) #define INC_SAFE(a, b) __INC_SAFE(UNIQ, a, b) #define __INC_SAFE(q, a, b) \ @@ -295,7 +330,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 +347,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 +359,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.c b/src/fundamental/memory-util.c new file mode 100644 index 0000000000000..39d116cc8b65d --- /dev/null +++ b/src/fundamental/memory-util.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "memory-util.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.h similarity index 95% rename from src/fundamental/memory-util-fundamental.h rename to src/fundamental/memory-util.h index c2a99a2039770..55ed19e15ca8e 100644 --- a/src/fundamental/memory-util-fundamental.h +++ b/src/fundamental/memory-util.h @@ -9,9 +9,8 @@ # include #endif -#include "assert-fundamental.h" -#include "cleanup-fundamental.h" -#include "macro-fundamental.h" +#include "assert-util.h" /* IWYU pragma: keep */ +#include "macro.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..93a332ec8a6a1 100644 --- a/src/fundamental/meson.build +++ b/src/fundamental/meson.build @@ -3,14 +3,15 @@ fundamental_include = include_directories('.') fundamental_sources = files( - 'bootspec-fundamental.c', - 'chid-fundamental.c', - 'edid-fundamental.c', - 'efivars-fundamental.c', - 'iovec-util-fundamental.h', - 'sha1-fundamental.c', - 'sha256-fundamental.c', - 'string-util-fundamental.c', + 'bootspec.c', + 'chid.c', + 'edid.c', + 'efivars.c', + 'iovec-util.h', + 'memory-util.c', + 'sha1.c', + 'sha256.c', + 'string-util.c', 'uki.c', ) diff --git a/src/fundamental/sha1-fundamental.c b/src/fundamental/sha1.c similarity index 99% rename from src/fundamental/sha1-fundamental.c rename to src/fundamental/sha1.c index b2af1e98885ed..1d48d6874afd8 100644 --- a/src/fundamental/sha1-fundamental.c +++ b/src/fundamental/sha1.c @@ -82,8 +82,8 @@ modified for use with systemd # include #endif -#include "memory-util-fundamental.h" -#include "sha1-fundamental.h" +#include "memory-util.h" +#include "sha1.h" #define SHA1_DIGEST_SIZE 20 diff --git a/src/fundamental/sha1-fundamental.h b/src/fundamental/sha1.h similarity index 100% rename from src/fundamental/sha1-fundamental.h rename to src/fundamental/sha1.h diff --git a/src/fundamental/sha256-fundamental.c b/src/fundamental/sha256.c similarity index 98% rename from src/fundamental/sha256-fundamental.c rename to src/fundamental/sha256.c index 76951ccfad2e0..ac514eddabbc2 100644 --- a/src/fundamental/sha256-fundamental.c +++ b/src/fundamental/sha256.c @@ -27,10 +27,10 @@ # include #endif -#include "assert-fundamental.h" -#include "memory-util-fundamental.h" -#include "sha256-fundamental.h" -#include "unaligned-fundamental.h" +#include "assert-util.h" +#include "memory-util.h" +#include "sha256.h" +#include "unaligned.h" #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ # define SWAP(n) \ diff --git a/src/fundamental/sha256-fundamental.h b/src/fundamental/sha256.h similarity index 100% rename from src/fundamental/sha256-fundamental.h rename to src/fundamental/sha256.h diff --git a/src/fundamental/string-table-fundamental.h b/src/fundamental/string-table.h similarity index 96% rename from src/fundamental/string-table-fundamental.h rename to src/fundamental/string-table.h index d40251506845b..41c8158689be4 100644 --- a/src/fundamental/string-table-fundamental.h +++ b/src/fundamental/string-table.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "macro-fundamental.h" +#include "macro.h" #define DECLARE_STRING_TABLE_LOOKUP_TO_STRING(name, type) \ const char* name##_to_string(type i) _const_ diff --git a/src/fundamental/string-util-fundamental.c b/src/fundamental/string-util.c similarity index 99% rename from src/fundamental/string-util-fundamental.c rename to src/fundamental/string-util.c index 6877dd439a4bd..bd6b81ea14da9 100644 --- a/src/fundamental/string-util-fundamental.c +++ b/src/fundamental/string-util.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "macro-fundamental.h" -#include "string-util-fundamental.h" +#include "macro.h" +#include "string-util.h" sd_char *startswith_internal(const sd_char *s, const sd_char *prefix) { size_t l; diff --git a/src/fundamental/string-util-fundamental.h b/src/fundamental/string-util.h similarity index 97% rename from src/fundamental/string-util-fundamental.h rename to src/fundamental/string-util.h index e2eb73a4a9dee..c068c1904a876 100644 --- a/src/fundamental/string-util-fundamental.h +++ b/src/fundamental/string-util.h @@ -8,8 +8,8 @@ # include #endif -#include "assert-fundamental.h" /* IWYU pragma: keep */ -#include "macro-fundamental.h" +#include "assert-util.h" /* IWYU pragma: keep */ +#include "macro.h" /* What is interpreted as whitespace? */ #define WHITESPACE " \t\n\r" @@ -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.h similarity index 69% rename from src/fundamental/strv-fundamental.h rename to src/fundamental/strv.h index 3abcdc4b02eea..0ede9c3b796a1 100644 --- a/src/fundamental/strv-fundamental.h +++ b/src/fundamental/strv.h @@ -1,10 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "macro-fundamental.h" +#include "macro.h" +#include "string-util.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/tpm2-pcr.h b/src/fundamental/tpm2-pcr.h index 1d39ba59596f3..72835e886b78d 100644 --- a/src/fundamental/tpm2-pcr.h +++ b/src/fundamental/tpm2-pcr.h @@ -56,3 +56,12 @@ enum { /* The tag used for EV_EVENT_TAG event log records covering the selected UKI profile */ #define UKI_PROFILE_EVENT_TAG_ID UINT32_C(0x13aed6db) + +/* The tag used for EV_EVENT_TAG event log records covering the SMBIOS type 1 (system information) structure */ +#define SMBIOS_TYPE1_EVENT_TAG_ID UINT32_C(0xd5cb7cbc) + +/* The tag used for EV_EVENT_TAG event log records covering the SMBIOS type 2 (baseboard information) structure */ +#define SMBIOS_TYPE2_EVENT_TAG_ID UINT32_C(0xe0d47bc8) + +/* The tag used for EV_EVENT_TAG event log records covering SMBIOS type 11 (OEM strings) structures */ +#define SMBIOS_TYPE11_EVENT_TAG_ID UINT32_C(0xc0b3bd23) 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/fundamental/unaligned-fundamental.h b/src/fundamental/unaligned.h similarity index 100% rename from src/fundamental/unaligned-fundamental.h rename to src/fundamental/unaligned.h diff --git a/src/fuzz/fuzz-bootspec-gen.py b/src/fuzz/fuzz-bootspec-gen.py index a73e59203bbeb..927c5ed529842 100755 --- a/src/fuzz/fuzz-bootspec-gen.py +++ b/src/fuzz/fuzz-bootspec-gen.py @@ -4,14 +4,12 @@ """Generate sample input for fuzz-bootspec""" import json -import os import sys +from pathlib import Path -config = open(sys.argv[1]).read() -loader = [entry for entry in open(sys.argv[2], encoding='utf-16-le').read().split('\0') - if len(entry) > 2] # filter out fluff from bad decoding -entries = [(os.path.basename(name), open(name).read()) - for name in sys.argv[3:]] +config = Path(sys.argv[1]).read_text() +loader = [entry for entry in Path(sys.argv[2]).read_text(encoding='utf-16-le').split('\0') if len(entry) > 2] +entries = [(Path(name).name, Path(name).read_text()) for name in sys.argv[3:]] data = { 'config': config, diff --git a/src/fuzz/fuzz-pe-binary.c b/src/fuzz/fuzz-pe-binary.c new file mode 100644 index 0000000000000..debec376a0031 --- /dev/null +++ b/src/fuzz/fuzz-pe-binary.c @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* Fuzz target for userspace UKI / PE inspection. + * + * pe_load_headers / pe_load_sections / pe_read_section_data_by_name / uki_hash + * are exercised by bootctl, systemd-measure, pcrlock, kernel-install and + * reboot-util against UKIs that an unprivileged actor may have produced. + * + * Expected input: bytes that look like a PE/COFF file (DOS "MZ" header, + * `e_lfanew` pointing to a "PE\0\0" signature, followed by IMAGE_FILE_HEADER, + * IMAGE_OPTIONAL_HEADER and a section table). The harness wraps the bytes in + * a memfd, walks the headers, then attempts to read the known UKI sections + * and finally hashes them via uki_hash() when OpenSSL is available. + */ + +#include + +#include "alloc-util.h" +#include "crypto-util.h" +#include "fd-util.h" +#include "fuzz.h" +#include "memfd-util.h" +#include "pe-binary.h" +#include "tests.h" +#include "uki.h" + +/* Cap section reads so a crafted VirtualSize cannot drive a multi-GiB malloc + * and OOM-kill the fuzzer. Well under the 16 MiB input limit; real code uses + * PE_SECTION_READ_MAX (16 KiB) or smaller. */ +#define FUZZ_SECTION_READ_MAX (1U*1024U*1024U) + +static void fuzz_read_section( + int fd, + const PeHeader *pe_header, + const IMAGE_SECTION_HEADER *sections, + const char *name) { + + _cleanup_free_ void *buf = NULL; + size_t buf_size = 0; + + (void) pe_read_section_data_by_name(fd, pe_header, sections, name, FUZZ_SECTION_READ_MAX, &buf, &buf_size); + DO_NOT_OPTIMIZE(buf); + DO_NOT_OPTIMIZE(buf_size); +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL; + _cleanup_free_ PeHeader *pe_header = NULL; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + + if (outside_size_range(size, 0, 16 * 1024 * 1024)) + return 0; + + fuzz_setup_logging(); + + _cleanup_close_ int fd = ASSERT_OK(memfd_new("fuzz-pe-binary")); + if (size > 0) + ASSERT_OK_EQ_ERRNO(write(fd, data, size), (ssize_t) size); + ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_SET)); + + if (pe_load_headers(fd, &dos_header, &pe_header) < 0) + return 0; + + if (pe_load_sections(fd, dos_header, pe_header, §ions) < 0) + return 0; + + /* Exercise the section-read path for every UKI section. unified_sections[] + * (uki.h) is the canonical list; .sdmagic is written by sd-boot/sd-stub but + * is not part of that table, so read it as an extra step. */ + FOREACH_ARRAY(s, unified_sections, _UNIFIED_SECTION_MAX) + fuzz_read_section(fd, pe_header, sections, *s); + fuzz_read_section(fd, pe_header, sections, ".sdmagic"); + + (void) pe_is_uki(pe_header, sections); + (void) pe_is_addon(pe_header, sections); + (void) pe_is_native(pe_header); + +#if HAVE_OPENSSL + if (dlopen_libcrypto(LOG_DEBUG) >= 0) { + void *hashes[_UNIFIED_SECTION_MAX] = {}; + size_t hash_size = 0; + + /* uki_hash() can return partway through with some hashes already + * allocated; free unconditionally. */ + (void) uki_hash(fd, sym_EVP_sha256(), hashes, &hash_size); + free_many(hashes, _UNIFIED_SECTION_MAX); + } +#endif + + return 0; +} 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..65fc6896f1f77 100644 --- a/src/fuzz/meson.build +++ b/src/fuzz/meson.build @@ -8,8 +8,10 @@ simple_fuzzers += files( 'fuzz-env-file.c', 'fuzz-hostname-setup.c', 'fuzz-json.c', + 'fuzz-pe-binary.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..b92ac8f66a765 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; @@ -871,7 +882,7 @@ static int add_root_mount(void) { if (in_initrd()) { r = generator_write_initrd_root_device_deps(arg_dest_late, bdev); if (r < 0) - return 0; + return r; r = add_root_cryptsetup(); if (r < 0) @@ -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..f5a1b7b969ff0 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_WARNING, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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,68 +129,59 @@ 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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts) != 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_arg(&opts, 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..d2dccd59bda8c 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,58 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts) > 0 && arg_clear) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extraneous arguments specified with --clear, refusing."); + *ret_args = option_parser_get_args(&opts); return 1; } @@ -129,11 +120,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 +139,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 +147,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/home-util.h b/src/home/home-util.h index 0b1781d272866..32dcae30442c6 100644 --- a/src/home/home-util.h +++ b/src/home/home-util.h @@ -35,5 +35,8 @@ int bus_message_append_secret(sd_bus_message *m, UserRecord *secret); * operations permit a *very* long timeout */ #define HOME_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE) +/* Retry to deactivate home directories again and again every 15s until it works */ +#define HOME_RETRY_DEACTIVATE_USEC (15U * USEC_PER_SEC) + const char* home_record_dir(void); const char* home_system_blob_dir(void); diff --git a/src/home/homectl-fido2.c b/src/home/homectl-fido2.c index d351b1db5e113..210c050284d3b 100644 --- a/src/home/homectl-fido2.c +++ b/src/home/homectl-fido2.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" +#include "ask-password-api.h" /* IWYU pragma: keep */ #include "errno-util.h" #include "fido2-util.h" #include "hexdecoct.h" @@ -174,6 +175,8 @@ int identity_add_fido2_parameters( /* user_icon= */ NULL, /* askpw_icon= */ "user-home", /* askpw_credential= */ "home.token-pin", + /* askpw_flags= */ 0, + /* pin= */ NULL, lock_with, cred_alg, &salt, diff --git a/src/home/homectl-pkcs11.c b/src/home/homectl-pkcs11.c index a72aecf135643..69eee3e289fb2 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" @@ -98,6 +98,7 @@ static int add_pkcs11_token_uri(sd_json_variant **v, const char *uri) { static int add_pkcs11_encrypted_key( sd_json_variant **v, const char *uri, + Pkcs11RsaPadding rsa_padding, const void *encrypted_key, size_t encrypted_key_size, const void *decrypted_key, size_t decrypted_key_size) { @@ -126,7 +127,9 @@ static int add_pkcs11_encrypted_key( r = sd_json_buildo(&e, SD_JSON_BUILD_PAIR_STRING("uri", uri), SD_JSON_BUILD_PAIR_BASE64("data", encrypted_key, encrypted_key_size), - SD_JSON_BUILD_PAIR_STRING("hashedPassword", hashed)); + SD_JSON_BUILD_PAIR_STRING("hashedPassword", hashed), + SD_JSON_BUILD_PAIR_CONDITION(rsa_padding > PKCS11_RSA_PADDING_PKCS1V15, + "padding", SD_JSON_BUILD_STRING(pkcs11_rsa_padding_to_string(rsa_padding)))); if (r < 0) return log_error_errno(r, "Failed to build encrypted JSON key object: %m"); @@ -156,6 +159,7 @@ int identity_add_pkcs11_key_data(sd_json_variant **v, const char *uri) { _cleanup_(erase_and_freep) char *pin = NULL; size_t decrypted_key_size, saved_key_size; _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + Pkcs11RsaPadding rsa_padding = _PKCS11_RSA_PADDING_INVALID; int r; assert(v); @@ -167,11 +171,14 @@ int identity_add_pkcs11_key_data(sd_json_variant **v, const char *uri) { "home.token-pin", /* askpw_flags= */ 0, &pkey, + &rsa_padding, &pin); if (r < 0) return r; - r = pkey_generate_volume_keys(pkey, &decrypted_key, &decrypted_key_size, &saved_key, &saved_key_size); + r = pkey_generate_volume_keys(pkey, + pkcs11_rsa_padding_to_oaep_hash(rsa_padding), + &decrypted_key, &decrypted_key_size, &saved_key, &saved_key_size); if (r < 0) return log_error_errno(r, "Failed to generate volume keys: %m"); @@ -184,6 +191,7 @@ int identity_add_pkcs11_key_data(sd_json_variant **v, const char *uri) { r = add_pkcs11_encrypted_key( v, uri, + rsa_padding, saved_key, saved_key_size, decrypted_key, decrypted_key_size); if (r < 0) diff --git a/src/home/homectl-prompts.c b/src/home/homectl-prompts.c new file mode 100644 index 0000000000000..95a6ec3d04f40 --- /dev/null +++ b/src/home/homectl-prompts.c @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "alloc-util.h" +#include "bitfield.h" +#include "chase.h" +#include "glyph-util.h" +#include "group-record.h" +#include "homectl-prompts.h" +#include "log.h" +#include "parse-util.h" +#include "prompt-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "user-util.h" +#include "userdb.h" + +static int acquire_group_list(char ***ret) { + _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; + _cleanup_strv_free_ char **groups = NULL; + UserDBMatch match = USERDB_MATCH_NULL; + int r; + + assert(ret); + + match.disposition_mask = INDEXES_TO_MASK(uint64_t, USER_REGULAR, USER_SYSTEM); + + r = groupdb_all(&match, USERDB_SUPPRESS_SHADOW, &iterator); + if (r == -ENOLINK) + log_debug_errno(r, "No groups found. (Didn't check via Varlink.)"); + else if (r == -ESRCH) + log_debug_errno(r, "No groups found."); + else if (r < 0) + return log_debug_errno(r, "Failed to enumerate groups, ignoring: %m"); + else + for (;;) { + _cleanup_(group_record_unrefp) GroupRecord *gr = NULL; + + r = groupdb_iterator_get(iterator, &match, &gr); + if (r == -ESRCH) + break; + if (r < 0) + return log_debug_errno(r, "Failed to acquire next group: %m"); + + if (group_record_disposition(gr) == USER_REGULAR) { + _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + + /* Filter groups here that belong to a specific user, and are named like them */ + + UserDBMatch user_match = USERDB_MATCH_NULL; + user_match.disposition_mask = INDEX_TO_MASK(uint64_t, USER_REGULAR); + + r = userdb_by_name(gr->group_name, &user_match, USERDB_SUPPRESS_SHADOW, &ur); + if (r < 0 && r != -ESRCH) + return log_debug_errno(r, "Failed to check if matching user exists for group '%s': %m", gr->group_name); + + if (r >= 0 && user_record_gid(ur) == gr->gid) + continue; + } + + r = strv_extend(&groups, gr->group_name); + if (r < 0) + return log_oom(); + } + + strv_sort(groups); + + *ret = TAKE_PTR(groups); + return !!*ret; +} + +static int group_completion_callback(const char *key, GetCompletionsFlags flags, char ***ret_list, void *userdata) { + char ***available = userdata; + int r; + + if (!*available) { + r = acquire_group_list(available); + if (r < 0) + log_debug_errno(r, "Failed to enumerate available groups, ignoring: %m"); + } + + _cleanup_strv_free_ char **l = strv_copy(*available); + if (!l) + return -ENOMEM; + + if (!FLAGS_SET(flags, GET_COMPLETIONS_PRESELECT)) { + r = strv_extend(&l, "list"); + if (r < 0) + return r; + } + + *ret_list = TAKE_PTR(l); + return 0; +} + +int prompt_groups(const char *username, char ***ret_groups) { + int r; + + assert(username); + assert(ret_groups); + + _cleanup_strv_free_ char **available = NULL, **groups = NULL; + for (;;) { + strv_sort_uniq(groups); + + if (!strv_isempty(groups)) { + _cleanup_free_ char *j = strv_join(groups, ", "); + if (!j) + return log_oom(); + + log_info("Currently selected groups: %s", j); + } + + _cleanup_free_ char *s = NULL; + r = ask_string_full( + &s, + /* prefill= */ NULL, + group_completion_callback, + &available, + "%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ", + glyph(GLYPH_LABEL), + username); + if (r < 0) + return log_error_errno(r, "Failed to query user for auxiliary group: %m"); + + if (isempty(s)) + break; + + if (streq(s, "list")) { + if (!available) { + r = acquire_group_list(&available); + if (r < 0) + log_warning_errno(r, "Failed to enumerate available groups, ignoring: %m"); + if (r == 0) + log_notice("Did not find any available groups"); + if (r <= 0) + continue; + } + + r = show_menu(available, + /* n_columns= */ 3, + /* column_width= */ 20, + /* ellipsize_percentage= */ 60, + /* grey_prefix= */ NULL, + /* with_numbers= */ true); + if (r < 0) + return log_error_errno(r, "Failed to show menu: %m"); + + putchar('\n'); + continue; + } + + if (!strv_isempty(available)) { + unsigned u; + r = safe_atou(s, &u); + if (r >= 0) { + if (u <= 0 || u > strv_length(available)) { + log_error("Specified entry number out of range."); + continue; + } + + log_info("Selected '%s'.", available[u-1]); + + r = strv_extend(&groups, available[u-1]); + if (r < 0) + return log_oom(); + + continue; + } + } + + if (!valid_user_group_name(s, /* flags= */ 0)) { + log_notice("Specified group name is not a valid UNIX group name, try again: %s", s); + continue; + } + + r = groupdb_by_name(s, /* match= */ NULL, USERDB_SUPPRESS_SHADOW|USERDB_EXCLUDE_DYNAMIC_USER, /* ret= */ NULL); + if (r == -ESRCH) { + log_notice("Specified auxiliary group does not exist, try again: %s", s); + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to check if specified group '%s' already exists: %m", s); + + log_info("Selected '%s'.", s); + + r = strv_extend(&groups, s); + if (r < 0) + return log_oom(); + } + + *ret_groups = TAKE_PTR(groups); + return 0; +} + +static int shell_is_ok(const char *path, void *userdata) { + int r; + + assert(path); + + if (!valid_shell(path)) { + log_error("String '%s' is not a valid path to a shell, refusing.", path); + return false; + } + + r = chase_and_access(path, /* root= */ NULL, CHASE_MUST_BE_REGULAR, X_OK, /* ret_path= */ NULL); + if (r == -ENOENT) { + log_error_errno(r, "Shell '%s' does not exist, try again.", path); + return false; + } + if (ERRNO_IS_NEG_PRIVILEGE(r)) { + log_error_errno(r, "File '%s' is not executable, try again.", path); + return false; + } + if (r < 0) + return log_error_errno(r, "Failed to check if shell '%s' exists and is executable: %m", path); + + return true; +} + +int prompt_shell(const char *username, char **ret_shell) { + assert(username); + assert(ret_shell); + + _cleanup_free_ char *q = strjoin("Please enter the shell to use for user ", username); + if (!q) + return log_oom(); + + return prompt_loop( + q, + GLYPH_SHELL, + /* prefill= */ NULL, + /* menu= */ NULL, + /* accepted= */ NULL, + /* ellipsize_percentage= */ 0, + /* n_columns= */ 3, + /* column_width= */ 20, + shell_is_ok, + /* refresh= */ NULL, + /* userdata= */ NULL, + PROMPT_MAY_SKIP|PROMPT_SILENT_VALIDATE, + ret_shell); +} diff --git a/src/home/homectl-prompts.h b/src/home/homectl-prompts.h new file mode 100644 index 0000000000000..04d6460058279 --- /dev/null +++ b/src/home/homectl-prompts.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int prompt_groups(const char *username, char ***ret_groups); +int prompt_shell(const char *username, char **ret_shell); diff --git a/src/home/homectl.c b/src/home/homectl.c index 2b92ab6481eae..c0dab67989d35 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" #include "sd-varlink.h" +#include "ansi-color.h" #include "ask-password-api.h" #include "bitfield.h" #include "build.h" @@ -16,8 +16,8 @@ #include "capability-list.h" #include "capability-util.h" #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" @@ -30,16 +30,18 @@ #include "fs-util.h" #include "glyph-util.h" #include "hashmap.h" +#include "help-util.h" #include "hexdecoct.h" #include "home-util.h" #include "homectl-fido2.h" #include "homectl-pkcs11.h" +#include "homectl-prompts.h" #include "homectl-recovery-key.h" #include "json-util.h" #include "libfido2-util.h" #include "locale-util.h" #include "main-func.h" -#include "openssl-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -50,7 +52,6 @@ #include "pkcs11-util.h" #include "plymouth-util.h" #include "polkit-agent.h" -#include "pretty-print.h" #include "proc-cmdline.h" #include "process-util.h" #include "prompt-util.h" @@ -178,7 +179,10 @@ static int acquire_bus(sd_bus **bus) { return 0; } -static int list_homes(int argc, char *argv[], void *userdata) { +VERB_GROUP("Basic User Manipulation Commands"); +VERB(verb_list_homes, "list", /* argspec= */ NULL, VERB_ANY, 1, VERB_DEFAULT, + "List home areas"); +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,85 +631,6 @@ static int acquire_passed_secrets(const char *user_name, UserRecord **ret) { return 0; } -static int activate_home(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r, ret = 0; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - STRV_FOREACH(i, strv_skip(argv, 1)) { - _cleanup_(user_record_unrefp) UserRecord *secret = NULL; - - r = acquire_passed_secrets(*i, &secret); - if (r < 0) - return r; - - for (;;) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = bus_message_new_method_call(bus, &m, bus_mgr, "ActivateHome"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", *i); - if (r < 0) - return bus_log_create_error(r); - - r = bus_message_append_secret(m, secret); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) { - r = handle_generic_user_record_error(*i, secret, &error, r, /* emphasize_current_password= */ false); - if (r < 0) { - if (ret == 0) - ret = r; - - break; - } - } else - break; - } - } - - return ret; -} - -static int deactivate_home(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r, ret = 0; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - STRV_FOREACH(i, strv_skip(argv, 1)) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = bus_message_new_method_call(bus, &m, bus_mgr, "DeactivateHome"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", *i); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) { - log_error_errno(r, "Failed to deactivate user home: %s", bus_error_message(&error, r)); - if (ret == 0) - ret = r; - } - } - - return ret; -} - static void dump_home_record(UserRecord *hr) { int r; @@ -758,7 +683,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 +700,9 @@ static int inspect_home(sd_bus *bus, const char *name) { return 0; } -static int inspect_homes(int argc, char *argv[], void *userdata) { +VERB(verb_inspect_homes, "inspect", "USER…", VERB_ANY, VERB_ANY, 0, + "Inspect a home area"); +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; @@ -799,65 +726,6 @@ static int inspect_homes(int argc, char *argv[], void *userdata) { } } -static int authenticate_home(sd_bus *bus, const char *name) { - _cleanup_(user_record_unrefp) UserRecord *secret = NULL; - int r; - - r = acquire_passed_secrets(name, &secret); - if (r < 0) - return r; - - for (;;) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = bus_message_new_method_call(bus, &m, bus_mgr, "AuthenticateHome"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", name); - if (r < 0) - return bus_log_create_error(r); - - r = bus_message_append_secret(m, secret); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) { - r = handle_generic_user_record_error(name, secret, &error, r, false); - if (r >= 0) - continue; - } - return r; - } -} - -static int authenticate_homes(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - char **args = strv_skip(argv, 1); - if (args) { - STRV_FOREACH(arg, args) - RET_GATHER(r, authenticate_home(bus, *arg)); - - return r; - } else { - _cleanup_free_ char *myself = getusername_malloc(); - if (!myself) - return log_oom(); - - return authenticate_home(bus, myself); - } -} - static int update_last_change(sd_json_variant **v, bool with_password, bool override) { sd_json_variant *c; usec_t n; @@ -1038,8 +906,7 @@ static int apply_identity_changes(sd_json_variant **_v) { if (r < 0) return log_error_errno(r, "Failed to allocate new perMachine array: %m"); - sd_json_variant_unref(per_machine); - per_machine = TAKE_PTR(npm); + json_variant_unref_and_replace(per_machine, npm); } else { _cleanup_(sd_json_variant_unrefp) sd_json_variant *positive = sd_json_variant_ref(arg_identity_extra_this_machine), *negative = sd_json_variant_ref(arg_identity_extra_other_machines); @@ -1122,10 +989,7 @@ static int apply_identity_changes(sd_json_variant **_v) { } } - sd_json_variant_unref(*_v); - *_v = TAKE_PTR(v); - - return 0; + return json_variant_unref_and_replace(*_v, v); } static int add_disposition(sd_json_variant **v) { @@ -1159,7 +1023,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 +1425,9 @@ 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) { +VERB(verb_create_home, "create", "USER", VERB_ANY, 2, 0, + "Create a home area"); +static int verb_create_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; if (argc >= 2) { @@ -1592,268 +1462,101 @@ 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) { - int r, ret = 0; +static int acquire_updated_home_record( + sd_bus *bus, + const char *username, + UserRecord **ret) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - r = acquire_bus(&bus); - if (r < 0) - return r; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; + _cleanup_(user_record_unrefp) UserRecord *hr = NULL; + int r; - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + assert(ret); - STRV_FOREACH(i, strv_skip(argv, 1)) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - r = bus_message_new_method_call(bus, &m, bus_mgr, "AdoptHome"); - if (r < 0) - return bus_log_create_error(r); + if (arg_identity) { + unsigned line = 0, column = 0; + sd_json_variant *un; - r = sd_bus_message_append(m, "st", *i, UINT64_C(0)); + r = sd_json_parse_file( + streq(arg_identity, "-") ? stdin : NULL, + streq(arg_identity, "-") ? "" : arg_identity, + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE, + &json, + &line, + &column); if (r < 0) - return bus_log_create_error(r); + return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column); - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) { - log_error_errno(r, "Failed to adopt home: %s", bus_error_message(&error, r)); - if (ret == 0) - ret = r; + un = sd_json_variant_by_key(json, "userName"); + if (un) { + if (!sd_json_variant_is_string(un) || (username && !streq(sd_json_variant_string(un), username))) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name specified on command line and in JSON record do not match."); + } else { + if (!username) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No username specified."); + + r = sd_json_variant_set_field_string(&arg_identity_extra, "userName", username); + if (r < 0) + return log_error_errno(r, "Failed to set userName field: %m"); } - } - return ret; -} + } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int incomplete; + const char *text; -static int register_home_common(sd_bus *bus, sd_json_variant *v) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *_bus = NULL; - int r; + if (!identity_properties_specified()) + return log_error_errno(SYNTHETIC_ERRNO(EALREADY), "No field to change specified."); - assert(v); + r = bus_call_method(bus, bus_mgr, "GetUserRecordByName", &error, &reply, "s", username); + if (r < 0) + return log_error_errno(r, "Failed to acquire user home record: %s", bus_error_message(&error, r)); - if (!bus) { - r = acquire_bus(&_bus); + r = sd_bus_message_read(reply, "sbo", &text, &incomplete, NULL); if (r < 0) - return r; - bus = _bus; - } + return bus_log_parse_error(r); - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - r = bus_message_new_method_call(bus, &m, bus_mgr, "RegisterHome"); - if (r < 0) - return bus_log_create_error(r); + if (incomplete) + return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Lacking rights to acquire user record including privileged metadata, can't update record."); - _cleanup_free_ char *formatted = NULL; - r = sd_json_variant_format(v, /* flags= */ 0, &formatted); - if (r < 0) - return log_error_errno(r, "Failed to format JSON record: %m"); + 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"); - r = sd_bus_message_append(m, "s", formatted); - if (r < 0) - return bus_log_create_error(r); + reply = sd_bus_message_unref(reply); - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + r = sd_json_variant_filter(&json, STRV_MAKE("binding", "status", "signature", "blobManifest")); + if (r < 0) + return log_error_errno(r, "Failed to strip binding and status from record to update: %m"); + } + + r = apply_identity_changes(&json); if (r < 0) - return log_error_errno(r, "Failed to register home: %s", bus_error_message(&error, r)); + return r; - return 0; -} + STRV_FOREACH(i, arg_pkcs11_token_uri) { + r = identity_add_pkcs11_key_data(&json, *i); + if (r < 0) + return r; + } -static int register_home_one(sd_bus *bus, FILE *f, const char *path) { - int r; + STRV_FOREACH(i, arg_fido2_device) { + r = identity_add_fido2_parameters(&json, *i, arg_fido2_lock_with, arg_fido2_cred_alg); + if (r < 0) + return r; + } - assert(bus); - assert(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); - 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) { - int r; - - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - if (arg_identity) { - if (argc > 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not accepting an arguments if --identity= is specified, refusing."); - - return register_home_one(bus, /* f= */ NULL, arg_identity); - } - - if (argc == 1 || (argc == 2 && streq(argv[1], "-"))) - return register_home_one(bus, /* f= */ stdin, ""); - - r = 0; - STRV_FOREACH(i, strv_skip(argv, 1)) { - if (streq(*i, "-")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing reading from standard input if multiple user records are specified."); - - RET_GATHER(r, register_home_one(bus, /* f= */ NULL, *i)); - } - - return r; -} - -static int verb_unregister_home(int argc, char *argv[], void *userdata) { - int r; - - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - int ret = 0; - STRV_FOREACH(i, strv_skip(argv, 1)) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - r = bus_message_new_method_call(bus, &m, bus_mgr, "UnregisterHome"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", *i); - if (r < 0) - return bus_log_create_error(r); - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, /* ret_reply= */ NULL); - if (r < 0) - RET_GATHER(ret, log_error_errno(r, "Failed to unregister home: %s", bus_error_message(&error, r))); - } - - return ret; -} - -static int remove_home(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r, ret = 0; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - STRV_FOREACH(i, strv_skip(argv, 1)) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = bus_message_new_method_call(bus, &m, bus_mgr, "RemoveHome"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", *i); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) { - log_error_errno(r, "Failed to remove home: %s", bus_error_message(&error, r)); - if (ret == 0) - ret = r; - } - } - - return ret; -} - -static int acquire_updated_home_record( - sd_bus *bus, - const char *username, - UserRecord **ret) { - - _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; - _cleanup_(user_record_unrefp) UserRecord *hr = NULL; - int r; - - assert(ret); - - if (arg_identity) { - unsigned line = 0, column = 0; - sd_json_variant *un; - - r = sd_json_parse_file( - streq(arg_identity, "-") ? stdin : NULL, - streq(arg_identity, "-") ? "" : arg_identity, 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); - - un = sd_json_variant_by_key(json, "userName"); - if (un) { - if (!sd_json_variant_is_string(un) || (username && !streq(sd_json_variant_string(un), username))) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name specified on command line and in JSON record do not match."); - } else { - if (!username) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No username specified."); - - r = sd_json_variant_set_field_string(&arg_identity_extra, "userName", username); - if (r < 0) - return log_error_errno(r, "Failed to set userName field: %m"); - } - - } else { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int incomplete; - const char *text; - - if (!identity_properties_specified()) - return log_error_errno(SYNTHETIC_ERRNO(EALREADY), "No field to change specified."); - - r = bus_call_method(bus, bus_mgr, "GetUserRecordByName", &error, &reply, "s", username); - if (r < 0) - return log_error_errno(r, "Failed to acquire user home record: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "sbo", &text, &incomplete, NULL); - if (r < 0) - return bus_log_parse_error(r); - - 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); - if (r < 0) - return log_error_errno(r, "Failed to parse JSON identity: %m"); - - reply = sd_bus_message_unref(reply); - - r = sd_json_variant_filter(&json, STRV_MAKE("binding", "status", "signature", "blobManifest")); - if (r < 0) - return log_error_errno(r, "Failed to strip binding and status from record to update: %m"); - } - - r = apply_identity_changes(&json); - if (r < 0) - return r; - - STRV_FOREACH(i, arg_pkcs11_token_uri) { - r = identity_add_pkcs11_key_data(&json, *i); - if (r < 0) - return r; - } - - STRV_FOREACH(i, arg_fido2_device) { - r = identity_add_fido2_parameters(&json, *i, arg_fido2_lock_with, arg_fido2_cred_alg); - if (r < 0) - return r; - } - - if (arg_recovery_key) { - r = identity_add_recovery_key(&json); - if (r < 0) - return r; - } + if (arg_recovery_key) { + r = identity_add_recovery_key(&json); + if (r < 0) + return r; + } /* If the user supplied a full record, then add in lastChange, but do not override. Otherwise always * override. */ @@ -1900,7 +1603,9 @@ static int home_record_reset_human_interaction_permission(UserRecord *hr) { return 0; } -static int update_home(int argc, char *argv[], void *userdata) { +VERB(verb_update_home, "update", "USER", VERB_ANY, 2, 0, + "Update a home area"); +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 +1782,9 @@ static int update_home(int argc, char *argv[], void *userdata) { return 0; } -static int passwd_home(int argc, char *argv[], void *userdata) { +VERB(verb_passwd_home, "passwd", "USER", VERB_ANY, 2, 0, + "Change password of a home area"); +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 +1901,9 @@ static int parse_disk_size(const char *t, uint64_t *ret) { return 0; } -static int resize_home(int argc, char *argv[], void *userdata) { +VERB(verb_resize_home, "resize", "USER SIZE", 2, 3, 0, + "Resize a home area"); +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 +1965,9 @@ static int resize_home(int argc, char *argv[], void *userdata) { return 0; } -static int lock_home(int argc, char *argv[], void *userdata) { +VERB(verb_remove_home, "remove", "USER…", 2, VERB_ANY, 0, + "Remove a home area"); +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; @@ -2264,11 +1975,13 @@ static int lock_home(int argc, char *argv[], void *userdata) { if (r < 0) return r; + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + STRV_FOREACH(i, strv_skip(argv, 1)) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - r = bus_message_new_method_call(bus, &m, bus_mgr, "LockHome"); + r = bus_message_new_method_call(bus, &m, bus_mgr, "RemoveHome"); if (r < 0) return bus_log_create_error(r); @@ -2278,7 +1991,7 @@ static int lock_home(int argc, char *argv[], void *userdata) { r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) { - log_error_errno(r, "Failed to lock home: %s", bus_error_message(&error, r)); + log_error_errno(r, "Failed to remove home: %s", bus_error_message(&error, r)); if (ret == 0) ret = r; } @@ -2287,7 +2000,10 @@ static int lock_home(int argc, char *argv[], void *userdata) { return ret; } -static int unlock_home(int argc, char *argv[], void *userdata) { +VERB_GROUP("Advanced User Manipulation Commands"); +VERB(verb_activate_home, "activate", "USER…", 2, VERB_ANY, 0, + "Activate a home area"); +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; @@ -2306,7 +2022,7 @@ static int unlock_home(int argc, char *argv[], 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; - r = bus_message_new_method_call(bus, &m, bus_mgr, "UnlockHome"); + r = bus_message_new_method_call(bus, &m, bus_mgr, "ActivateHome"); if (r < 0) return bus_log_create_error(r); @@ -2320,7 +2036,7 @@ static int unlock_home(int argc, char *argv[], void *userdata) { r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) { - r = handle_generic_user_record_error(argv[1], secret, &error, r, false); + r = handle_generic_user_record_error(*i, secret, &error, r, /* emphasize_current_password= */ false); if (r < 0) { if (ret == 0) ret = r; @@ -2335,7 +2051,84 @@ static int unlock_home(int argc, char *argv[], void *userdata) { return ret; } -static int with_home(int argc, char *argv[], void *userdata) { +VERB(verb_deactivate_home, "deactivate", "USER…", 2, VERB_ANY, 0, + "Deactivate a home area"); +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; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + STRV_FOREACH(i, strv_skip(argv, 1)) { + /* The home directory might still be busy for a brief moment after a preceding operation + * (e.g. a concurrent inspect/deactivate, or a stray reference holding the mount busy at + * unmount time). homed will transition the home into "lingering" state and retry + * deactivation internally after some time, but rather than failing immediately let's just + * retry the bus call here for a while, so callers don't need to deal with this transient + * condition themselves. Use double the time homed waits to avoid racing with it. */ + usec_t end = usec_add(now(CLOCK_MONOTONIC), 2 * HOME_RETRY_DEACTIVATE_USEC); + + for (;;) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = bus_message_new_method_call(bus, &m, bus_mgr, "DeactivateHome"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", *i); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, /* ret_reply= */ NULL); + if (r >= 0) + break; + + if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_BUSY) && + now(CLOCK_MONOTONIC) < end) { + log_info("Home of user %s is currently busy, retrying deactivation.", *i); + (void) usleep_safe(1 * USEC_PER_SEC); + continue; + } + + log_error_errno(r, "Failed to deactivate user home: %s", bus_error_message(&error, r)); + if (ret == 0) + ret = r; + break; + } + } + + return ret; +} + +VERB_NOARG(verb_deactivate_all_homes, "deactivate-all", + "Deactivate all active home areas"); +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; + int r; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + r = bus_message_new_method_call(bus, &m, bus_mgr, "DeactivateAllHomes"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + if (r < 0) + return log_error_errno(r, "Failed to deactivate all homes: %s", bus_error_message(&error, r)); + + return 0; +} + +VERB(verb_with_home, "with", "USER [COMMAND…]", 2, VERB_ANY, 0, + "Run shell or command with access to a home area"); +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,51 +2251,43 @@ static int with_home(int argc, char *argv[], void *userdata) { return ret; } -static int lock_all_homes(int argc, char *argv[], 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; +static int authenticate_home(sd_bus *bus, const char *name) { + _cleanup_(user_record_unrefp) UserRecord *secret = NULL; int r; - r = acquire_bus(&bus); + r = acquire_passed_secrets(name, &secret); if (r < 0) return r; - r = bus_message_new_method_call(bus, &m, bus_mgr, "LockAllHomes"); - if (r < 0) - return bus_log_create_error(r); + for (;;) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) - return log_error_errno(r, "Failed to lock all homes: %s", bus_error_message(&error, r)); + r = bus_message_new_method_call(bus, &m, bus_mgr, "AuthenticateHome"); + if (r < 0) + return bus_log_create_error(r); - return 0; -} + r = sd_bus_message_append(m, "s", name); + if (r < 0) + return bus_log_create_error(r); -static int deactivate_all_homes(int argc, char *argv[], 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; - int r; + r = bus_message_append_secret(m, secret); + if (r < 0) + return bus_log_create_error(r); - r = acquire_bus(&bus); - if (r < 0) + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + if (r < 0) { + r = handle_generic_user_record_error(name, secret, &error, r, false); + if (r >= 0) + continue; + } return r; - - r = bus_message_new_method_call(bus, &m, bus_mgr, "DeactivateAllHomes"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) - return log_error_errno(r, "Failed to deactivate all homes: %s", bus_error_message(&error, r)); - - return 0; + } } -static int rebalance(int argc, char *argv[], 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; +VERB(verb_authenticate_homes, "authenticate", "USER…", VERB_ANY, VERB_ANY, 0, + "Authenticate a home area"); +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; @@ -2510,845 +2295,902 @@ static int rebalance(int argc, char *argv[], void *userdata) { if (r < 0) return r; - r = bus_message_new_method_call(bus, &m, bus_mgr, "Rebalance"); - if (r < 0) - return bus_log_create_error(r); + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_REBALANCE_NOT_NEEDED)) - log_info("No homes needed rebalancing."); - else - return log_error_errno(r, "Failed to rebalance: %s", bus_error_message(&error, r)); - } else - log_info("Completed rebalancing."); + char **args = strv_skip(argv, 1); + if (args) { + STRV_FOREACH(arg, args) + RET_GATHER(r, authenticate_home(bus, *arg)); - return 0; -} + return r; + } else { + _cleanup_free_ char *myself = getusername_malloc(); + if (!myself) + return log_oom(); -static int create_or_register_from_credentials(void) { - int r; + return authenticate_home(bus, myself); + } +} - _cleanup_close_ int fd = open_credentials_dir(); - if (IN_SET(fd, -ENXIO, -ENOENT)) /* Credential env var not set, or dir doesn't exist. */ - return 0; - if (fd < 0) - return log_error_errno(fd, "Failed to open credentials directory: %m"); +VERB_GROUP("User Migration Commands"); +VERB(verb_adopt_home, "adopt", "PATH…", VERB_ANY, VERB_ANY, 0, + "Add an existing home area on this system"); +static int verb_adopt_home(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r, ret = 0; - _cleanup_free_ DirectoryEntries *des = NULL; - r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des); + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + r = acquire_bus(&bus); if (r < 0) - return log_error_errno(r, "Failed to enumerate credentials: %m"); + return r; - int ret = 0, n_processed = 0; - FOREACH_ARRAY(i, des->entries, des->n_entries) { - struct dirent *de = *i; - if (de->d_type != DT_REG) - continue; + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - enum { - OPERATION_CREATE, - OPERATION_REGISTER, - } op; - const char *e; - if ((e = startswith(de->d_name, "home.create."))) - op = OPERATION_CREATE; - else if ((e = startswith(de->d_name, "home.register."))) - op = OPERATION_REGISTER; - else - continue; + STRV_FOREACH(i, strv_skip(argv, 1)) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + r = bus_message_new_method_call(bus, &m, bus_mgr, "AdoptHome"); + if (r < 0) + return bus_log_create_error(r); - if (!valid_user_group_name(e, /* flags= */ 0)) { - log_notice("Skipping over credential with name that is not a suitable user name: %s", de->d_name); - continue; - } + r = sd_bus_message_append(m, "st", *i, UINT64_C(0)); + if (r < 0) + return bus_log_create_error(r); - _cleanup_(sd_json_variant_unrefp) sd_json_variant *identity = NULL; - unsigned line = 0, column = 0; - r = sd_json_parse_file_at( - /* f= */ NULL, - fd, - de->d_name, - /* flags= */ 0, - &identity, - &line, - &column); + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) { - log_warning_errno(r, "[%s:%u:%u] Failed to parse user record in credential, ignoring: %m", de->d_name, line, column); - continue; + log_error_errno(r, "Failed to adopt home: %s", bus_error_message(&error, r)); + if (ret == 0) + ret = r; } + } - sd_json_variant *un = sd_json_variant_by_key(identity, "userName"); - if (un) { - if (!sd_json_variant_is_string(un)) { - log_warning("User record from credential '%s' contains 'userName' field of invalid type, ignoring.", de->d_name); - continue; - } - - if (!streq(sd_json_variant_string(un), e)) { - log_warning("User record from credential '%s' contains 'userName' field (%s) that doesn't match credential name (%s), ignoring.", de->d_name, sd_json_variant_string(un), e); - continue; - } - } else { - r = sd_json_variant_set_field_string(&identity, "userName", e); - if (r < 0) - return log_warning_errno(r, "Failed to set userName field: %m"); - } + return ret; +} - log_notice("Processing user '%s' from credentials.", e); +static int register_home_common(sd_bus *bus, sd_json_variant *v) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *_bus = NULL; + int r; - if (op == OPERATION_CREATE) - r = create_home_common(identity, /* show_enforce_password_policy_hint= */ false); - else - r = register_home_common(/* bus= */ NULL, identity); - if (r >= 0) - n_processed++; + assert(v); - RET_GATHER(ret, r); + if (!bus) { + r = acquire_bus(&_bus); + if (r < 0) + return r; + bus = _bus; } - return ret < 0 ? ret : n_processed; -} - -static int has_regular_user(void) { - _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; - UserDBMatch match = USERDB_MATCH_NULL; - int r; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + r = bus_message_new_method_call(bus, &m, bus_mgr, "RegisterHome"); + if (r < 0) + return bus_log_create_error(r); - match.disposition_mask = INDEX_TO_MASK(uint64_t, USER_REGULAR); + _cleanup_free_ char *formatted = NULL; + r = sd_json_variant_format(v, /* flags= */ 0, &formatted); + if (r < 0) + return log_error_errno(r, "Failed to format JSON record: %m"); - r = userdb_all(&match, USERDB_SUPPRESS_SHADOW, &iterator); + r = sd_bus_message_append(m, "s", formatted); if (r < 0) - return log_error_errno(r, "Failed to create user enumerator: %m"); + return bus_log_create_error(r); - r = userdb_iterator_get(iterator, &match, /* ret= */ NULL); - if (r == -ESRCH) - return false; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) - return log_error_errno(r, "Failed to enumerate users: %m"); + return log_error_errno(r, "Failed to register home: %s", bus_error_message(&error, r)); - return true; + return 0; } -static int acquire_group_list(char ***ret) { - _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; - _cleanup_strv_free_ char **groups = NULL; - UserDBMatch match = USERDB_MATCH_NULL; +static int register_home_one(sd_bus *bus, FILE *f, const char *path) { int r; - assert(ret); + assert(bus); + assert(path); - match.disposition_mask = INDEXES_TO_MASK(uint64_t, USER_REGULAR, USER_SYSTEM); + _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_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); - r = groupdb_all(&match, USERDB_SUPPRESS_SHADOW, &iterator); - if (r == -ENOLINK) - log_debug_errno(r, "No groups found. (Didn't check via Varlink.)"); - else if (r == -ESRCH) - log_debug_errno(r, "No groups found."); - else if (r < 0) - return log_debug_errno(r, "Failed to enumerate groups, ignoring: %m"); - else - for (;;) { - _cleanup_(group_record_unrefp) GroupRecord *gr = NULL; + return register_home_common(bus, v); +} - r = groupdb_iterator_get(iterator, &match, &gr); - if (r == -ESRCH) - break; - if (r < 0) - return log_debug_errno(r, "Failed to acquire next group: %m"); +VERB(verb_register_home, "register", "PATH…", VERB_ANY, VERB_ANY, 0, + "Register a user record locally"); +static int verb_register_home(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; - if (group_record_disposition(gr) == USER_REGULAR) { - _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + r = acquire_bus(&bus); + if (r < 0) + return r; - /* Filter groups here that belong to a specific user, and are named like them */ + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - UserDBMatch user_match = USERDB_MATCH_NULL; - user_match.disposition_mask = INDEX_TO_MASK(uint64_t, USER_REGULAR); + if (arg_identity) { + if (argc > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not accepting an arguments if --identity= is specified, refusing."); - r = userdb_by_name(gr->group_name, &user_match, USERDB_SUPPRESS_SHADOW, &ur); - if (r < 0 && r != -ESRCH) - return log_debug_errno(r, "Failed to check if matching user exists for group '%s': %m", gr->group_name); + return register_home_one(bus, /* f= */ NULL, arg_identity); + } - if (r >= 0 && user_record_gid(ur) == gr->gid) - continue; - } + if (argc == 1 || (argc == 2 && streq(argv[1], "-"))) + return register_home_one(bus, /* f= */ stdin, ""); - r = strv_extend(&groups, gr->group_name); - if (r < 0) - return log_oom(); - } + r = 0; + STRV_FOREACH(i, strv_skip(argv, 1)) { + if (streq(*i, "-")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing reading from standard input if multiple user records are specified."); - strv_sort(groups); + RET_GATHER(r, register_home_one(bus, /* f= */ NULL, *i)); + } - *ret = TAKE_PTR(groups); - return !!*ret; + return r; } -static int group_completion_callback(const char *key, char ***ret_list, void *userdata) { - char ***available = userdata; +VERB(verb_unregister_home, "unregister", "USER…", 2, VERB_ANY, 0, + "Unregister a user record locally"); +static int verb_unregister_home(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; - if (!*available) { - r = acquire_group_list(available); - if (r < 0) - log_debug_errno(r, "Failed to enumerate available groups, ignoring: %m"); - } - - _cleanup_strv_free_ char **l = strv_copy(*available); - if (!l) - return -ENOMEM; - - r = strv_extend(&l, "list"); + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + r = acquire_bus(&bus); if (r < 0) return r; - *ret_list = TAKE_PTR(l); - return 0; -} + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); -static int prompt_groups(const char *username, char ***ret_groups) { - int r; + int ret = 0; + STRV_FOREACH(i, strv_skip(argv, 1)) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + r = bus_message_new_method_call(bus, &m, bus_mgr, "UnregisterHome"); + if (r < 0) + return bus_log_create_error(r); - assert(username); - assert(ret_groups); + r = sd_bus_message_append(m, "s", *i); + if (r < 0) + return bus_log_create_error(r); - if (!arg_prompt_groups) { - *ret_groups = NULL; - return 0; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, /* ret_reply= */ NULL); + if (r < 0) + RET_GATHER(ret, log_error_errno(r, "Failed to unregister home: %s", bus_error_message(&error, r))); } - putchar('\n'); + return ret; +} - _cleanup_strv_free_ char **available = NULL, **groups = NULL; - for (;;) { - strv_sort_uniq(groups); +VERB_GROUP("Signing Keys Commands"); +VERB_NOARG(verb_list_signing_keys, "list-signing-keys", + "List home signing keys"); +static int verb_list_signing_keys(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; - if (!strv_isempty(groups)) { - _cleanup_free_ char *j = strv_join(groups, ", "); - if (!j) - return log_oom(); + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + r = acquire_bus(&bus); + if (r < 0) + return r; - log_info("Currently selected groups: %s", j); - } + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + r = bus_call_method(bus, bus_mgr, "ListSigningKeys", &error, &reply, NULL); + if (r < 0) + return log_error_errno(r, "Failed to list signing keys: %s", bus_error_message(&error, r)); - _cleanup_free_ char *s = NULL; - r = ask_string_full(&s, - group_completion_callback, &available, - "%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ", - glyph(GLYPH_LABEL), username); - if (r < 0) - return log_error_errno(r, "Failed to query user for auxiliary group: %m"); + _cleanup_(table_unrefp) Table *table = table_new("name", "key"); + if (!table) + return log_oom(); + + r = sd_bus_message_enter_container(reply, 'a', "(sst)"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + const char *name, *pem; - if (isempty(s)) + r = sd_bus_message_read(reply, "(sst)", &name, &pem, NULL); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) break; - if (streq(s, "list")) { - if (!available) { - r = acquire_group_list(&available); - if (r < 0) - log_warning_errno(r, "Failed to enumerate available groups, ignoring: %m"); - if (r == 0) - log_notice("Did not find any available groups"); - if (r <= 0) - continue; - } + _cleanup_free_ char *h = NULL; + if (!sd_json_format_enabled(arg_json_format_flags)) { + /* Let's decode the PEM key to DER (so that we lose prefix/suffix), then truncate it + * for display reasons. */ - r = show_menu(available, - /* n_columns= */ 3, - /* column_width= */ 20, - /* ellipsize_percentage= */ 60, - /* grey_prefix= */ NULL, - /* with_numbers= */ true); + r = DLOPEN_LIBCRYPTO(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); if (r < 0) - return log_error_errno(r, "Failed to show menu: %m"); - - putchar('\n'); - continue; - }; + return r; - if (!strv_isempty(available)) { - unsigned u; - r = safe_atou(s, &u); - if (r >= 0) { - if (u <= 0 || u > strv_length(available)) { - log_error("Specified entry number out of range."); - continue; - } + _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"); - log_info("Selected '%s'.", available[u-1]); + _cleanup_free_ void *der = NULL; + 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."); - r = strv_extend(&groups, available[u-1]); - if (r < 0) + ssize_t m = base64mem(der, MIN(n, 64), &h); + if (m < 0) + return log_oom(); + if (n > 64) /* check if we truncated the original version */ + if (!strextend(&h, glyph(GLYPH_ELLIPSIS))) return log_oom(); - - continue; - } - } - - if (!valid_user_group_name(s, /* flags= */ 0)) { - log_notice("Specified group name is not a valid UNIX group name, try again: %s", s); - continue; } - r = groupdb_by_name(s, /* match= */ NULL, USERDB_SUPPRESS_SHADOW|USERDB_EXCLUDE_DYNAMIC_USER, /* ret= */ NULL); - if (r == -ESRCH) { - log_notice("Specified auxiliary group does not exist, try again: %s", s); - continue; - } + r = table_add_many( + table, + TABLE_STRING, name, + TABLE_STRING, h ?: pem); if (r < 0) - return log_error_errno(r, "Failed to check if specified group '%s' already exists: %m", s); + return table_log_add_error(r); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); - log_info("Selected '%s'.", s); + if (!table_isempty(table) || sd_json_format_enabled(arg_json_format_flags)) { + r = table_set_sort(table, (size_t) 0); + if (r < 0) + return table_log_sort_error(r); - r = strv_extend(&groups, s); + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); if (r < 0) - return log_oom(); + return r; + } + + if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) { + if (table_isempty(table)) + printf("No signing keys.\n"); + else + printf("\n%zu signing keys listed.\n", table_get_rows(table) - 1); } - *ret_groups = TAKE_PTR(groups); return 0; } -static int shell_is_ok(const char *path, void *userdata) { +VERB(verb_get_signing_key, "get-signing-key", "[NAME…]", VERB_ANY, VERB_ANY, 0, + "Get a named home signing key"); +static int verb_get_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; - assert(path); - - if (!valid_shell(path)) { - log_error("String '%s' is not a valid path to a shell, refusing.", path); - return false; - } - - r = chase_and_access(path, /* root= */ NULL, CHASE_MUST_BE_REGULAR, X_OK, /* ret_path= */ NULL) >= 0; - if (r == -ENOENT) { - log_error_errno(r, "Shell '%s' does not exist, try again.", path); - return false; - } - if (ERRNO_IS_NEG_PRIVILEGE(r)) { - log_error_errno(r, "File '%s' is not executable, try again.", path); - return false; - } + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + r = acquire_bus(&bus); if (r < 0) - return log_error_errno(r, "Failed to check if shell '%s' exists and is executable: %m", path); - - return true; -} + return r; -static int prompt_shell(const char *username, char **ret_shell) { - assert(username); - assert(ret_shell); + char **keys = argc >= 2 ? strv_skip(argv, 1) : STRV_MAKE("local.public"); + int ret = 0; + STRV_FOREACH(k, keys) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + r = bus_call_method(bus, bus_mgr, "GetSigningKey", &error, &reply, "s", *k); + if (r < 0) { + RET_GATHER(ret, log_error_errno(r, "Failed to get signing key '%s': %s", *k, bus_error_message(&error, r))); + continue; + } - if (!arg_prompt_shell) { - *ret_shell = NULL; - return 0; - } + const char *pem; + r = sd_bus_message_read(reply, "st", &pem, NULL); + if (r < 0) { + RET_GATHER(ret, bus_log_parse_error(r)); + continue; + } - putchar('\n'); + fputs(pem, stdout); + if (!endswith(pem, "\n")) + fputc('\n', stdout); - _cleanup_free_ char *q = strjoin("Please enter the shell to use for user ", username); - if (!q) - return log_oom(); + fflush(stdout); + } - return prompt_loop( - q, - GLYPH_SHELL, - /* menu= */ NULL, - /* accepted= */ NULL, - /* ellipsize_percentage= */ 0, - /* n_columns= */ 3, - /* column_width= */ 20, - shell_is_ok, - /* refresh= */ NULL, - /* userdata= */ NULL, - PROMPT_MAY_SKIP|PROMPT_SILENT_VALIDATE, - ret_shell); + return ret; } -static int username_is_ok(const char *name, void *userdata) { +static int add_signing_key_one(sd_bus *bus, const char *fn, FILE *key) { int r; - assert(name); + assert_se(bus); + assert_se(fn); + assert_se(key); - if (!valid_user_group_name(name, /* flags= */ 0)) { - log_notice("Specified user name is not a valid UNIX user name, try again: %s", name); - return false; - } + _cleanup_free_ char *pem = NULL; + r = read_full_stream(key, &pem, /* ret_size= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to read key '%s': %m", fn); - r = userdb_by_name(name, /* match= */ NULL, USERDB_SUPPRESS_SHADOW, /* ret= */ NULL); - if (r == -ESRCH) - return true; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_call_method(bus, bus_mgr, "AddSigningKey", &error, /* ret_reply= */ NULL, "sst", fn, pem, UINT64_C(0)); if (r < 0) - return log_error_errno(r, "Failed to check if specified user '%s' already exists: %m", name); + return log_error_errno(r, "Failed to add signing key '%s': %s", fn, bus_error_message(&error, r)); - log_notice("Specified user '%s' exists already, try again.", name); - return false; + return 0; } -static int create_interactively(void) { - _cleanup_free_ char *username = NULL; +VERB(verb_add_signing_key, "add-signing-key", "FILE…", VERB_ANY, VERB_ANY, 0, + "Add home signing key"); +static int verb_add_signing_key(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; - if (!arg_prompt_new_user) { - log_debug("Prompting for user creation was not requested."); - return 0; - } + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + r = acquire_bus(&bus); + if (r < 0) + return r; - /* Needs to be called before mute_console or it will garble the screen */ - (void) plymouth_hide_splash(); + int ret = EXIT_SUCCESS; + if (argc < 2 || streq(argv[1], "-")) { + if (!arg_key_name) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Key name must be specified via --key-name= when reading key from standard input, refusing."); - _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *mute_console_link = NULL; - (void) mute_console(&mute_console_link); + RET_GATHER(ret, add_signing_key_one(bus, arg_key_name, stdin)); + } else { + /* Refuse if more han one key is specified in combination with --key-name= */ + if (argc >= 3 && arg_key_name) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--key-name= is not supported if multiple signing keys are specified, refusing."); - (void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0); + STRV_FOREACH(k, strv_skip(argv, 1)) { - if (arg_chrome) - chrome_show("Create a User Account", /* bottom= */ NULL); + if (streq(*k, "-")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing to read from standard input if multiple keys are specified."); - DEFER_VOID_CALL(chrome_hide); + _cleanup_free_ char *fn = NULL; + if (!arg_key_name) { + r = path_extract_filename(*k, &fn); + if (r < 0) { + RET_GATHER(ret, log_error_errno(r, "Failed to extract filename from path '%s': %m", *k)); + continue; + } + } - if (emoji_enabled()) { - fputs(glyph(GLYPH_HOME), stdout); - putchar(' '); + _cleanup_fclose_ FILE *f = fopen(*k, "re"); + if (!f) { + RET_GATHER(ret, log_error_errno(errno, "Failed to open '%s': %m", *k)); + continue; + } + + RET_GATHER(ret, add_signing_key_one(bus, fn ?: arg_key_name, f)); + } } - printf("Please create your user account!\n\n"); - r = prompt_loop("Please enter user name to create", - GLYPH_IDCARD, - /* menu= */ NULL, - /* accepted= */ NULL, - /* ellipsize_percentage= */ 60, - /* n_columns= */ 3, - /* column_width= */ 20, - username_is_ok, - /* refresh= */ NULL, - /* userdata= */ NULL, - PROMPT_MAY_SKIP|PROMPT_SILENT_VALIDATE, - &username); - if (r < 0) - return r; - if (isempty(username)) - return 0; + return ret; +} - r = sd_json_variant_set_field_string(&arg_identity_extra, "userName", username); - if (r < 0) - return log_error_errno(r, "Failed to set userName field: %m"); +static int add_signing_keys_from_credentials(void) { + int r; - /* Let's not insist on a strong password in the firstboot interactive interface. Insisting on this is - * really annoying, as the user cannot just invoke the tool again with "--enforce-password-policy=no" - * because after all the tool is called from the boot process, and not from an interactive - * shell. Moreover, when setting up an initial system we can assume the user owns it, and hence we - * don't need to hard enforce some policy on password strength some organization or OS vendor - * requires. Note that this just disables the *strict* enforcement of the password policy. Even with - * this disabled we'll still tell the user in the UI that the password is too weak and suggest better - * ones, even if we then accept the weak ones if the user insists, by repeating it. */ - r = sd_json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false); - if (r < 0) - return log_error_errno(r, "Failed to set enforcePasswordPolicy field: %m"); + _cleanup_close_ int fd = open_credentials_dir(); + if (IN_SET(fd, -ENXIO, -ENOENT)) /* Credential env var not set, or dir doesn't exist. */ + return 0; + if (fd < 0) + return log_error_errno(fd, "Failed to open credentials directory: %m"); - _cleanup_strv_free_ char **groups = NULL; - r = prompt_groups(username, &groups); + _cleanup_free_ DirectoryEntries *des = NULL; + r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des); if (r < 0) - return r; + return log_error_errno(r, "Failed to enumerate credentials: %m"); - if (!strv_isempty(groups)) { - strv_sort_uniq(groups); + int ret = 0; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + FOREACH_ARRAY(i, des->entries, des->n_entries) { + struct dirent *de = *i; + if (de->d_type != DT_REG) + continue; - r = sd_json_variant_set_field_strv(&arg_identity_extra, "memberOf", groups); - if (r < 0) - return log_error_errno(r, "Failed to set memberOf field: %m"); - } + const char *e = startswith(de->d_name, "home.add-signing-key."); + if (!e) + continue; - _cleanup_free_ char *shell = NULL; - r = prompt_shell(username, &shell); - if (r < 0) - return r; + if (!filename_is_valid(e)) + continue; - if (!isempty(shell)) { - log_info("Selected %s as the shell for user %s", shell, username); + if (!bus) { + r = acquire_bus(&bus); + if (r < 0) + return r; + } - r = sd_json_variant_set_field_string(&arg_identity_extra, "shell", shell); - if (r < 0) - return log_error_errno(r, "Failed to set shell field: %m"); + _cleanup_fclose_ FILE *f = NULL; + r = xfopenat(fd, de->d_name, "re", O_NOFOLLOW, &f); + if (r < 0) { + RET_GATHER(ret, log_error_errno(r, "Failed to open credential '%s': %m", de->d_name)); + continue; + } + + RET_GATHER(ret, add_signing_key_one(bus, e, f)); } - putchar('\n'); + return ret; +} - r = create_home_common(/* input= */ NULL, /* show_enforce_password_policy_hint= */ false); +static int remove_signing_key_one(sd_bus *bus, const char *fn) { + int r; + + assert_se(bus); + assert_se(fn); + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_call_method(bus, bus_mgr, "RemoveSigningKey", &error, /* ret_reply= */ NULL, "st", fn, UINT64_C(0)); if (r < 0) - return r; + return log_error_errno(r, "Failed to remove signing key '%s': %s", fn, bus_error_message(&error, r)); - log_info("Successfully created account '%s'.", username); return 0; } -static int add_signing_keys_from_credentials(void); - -static int verb_firstboot(int argc, char *argv[], void *userdata) { +VERB(verb_remove_signing_key, "remove-signing-key", "NAME…", 2, VERB_ANY, 0, + "Remove home signing key"); +static int verb_remove_signing_key(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 - * tool. */ - - bool enabled; - r = proc_cmdline_get_bool("systemd.firstboot", /* flags= */ 0, &enabled); + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + r = acquire_bus(&bus); if (r < 0) - return log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring: %m"); - if (r > 0 && !enabled) { - log_debug("Found systemd.firstboot=no kernel command line argument, turning off all prompts."); - arg_prompt_new_user = false; - } + return r; - int ret = 0; + r = EXIT_SUCCESS; + STRV_FOREACH(k, strv_skip(argv, 1)) + RET_GATHER(r, remove_signing_key_one(bus, *k)); - RET_GATHER(ret, add_signing_keys_from_credentials()); + return r; +} - r = create_or_register_from_credentials(); - RET_GATHER(ret, r); - bool existing_users = r > 0; +VERB_GROUP("Lock/Unlock Commands"); +VERB(verb_lock_home, "lock", "USER…", 2, VERB_ANY, 0, + "Temporarily lock an active home area"); +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; - r = getenv_bool("SYSTEMD_HOME_FIRSTBOOT_OVERRIDE"); - if (r == 0) - return 0; - if (r < 0) { - if (r != -ENXIO) - log_warning_errno(r, "Failed to parse $SYSTEMD_HOME_FIRSTBOOT_OVERRIDE, ignoring: %m"); + r = acquire_bus(&bus); + if (r < 0) + return r; - if (!existing_users) { - r = has_regular_user(); - if (r < 0) - return r; + STRV_FOREACH(i, strv_skip(argv, 1)) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - existing_users = r > 0; - } - if (existing_users) { - log_info("Regular user already present in user database, skipping interactive user creation."); - return 0; + r = bus_message_new_method_call(bus, &m, bus_mgr, "LockHome"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", *i); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + if (r < 0) { + log_error_errno(r, "Failed to lock home: %s", bus_error_message(&error, r)); + if (ret == 0) + ret = r; } } - RET_GATHER(ret, create_interactively()); return ret; } -#define drop_from_identity(...) _drop_from_identity(STRV_MAKE(__VA_ARGS__)) - -static int _drop_from_identity(char **fields) { - int r; - - /* If we are called to update an identity record and drop some field, let's keep track of what to - * remove from the old record */ - r = strv_extend_strv(&arg_identity_filter, fields, /* filter_duplicates= */ true); - if (r < 0) - return log_oom(); - - /* Let's also drop the field if it was previously set to a new value on the same command line */ - r = sd_json_variant_filter(&arg_identity_extra, fields); - if (r < 0) - return log_error_errno(r, "Failed to filter JSON identity data: %m"); - - r = sd_json_variant_filter(&arg_identity_extra_this_machine, fields); - if (r < 0) - return log_error_errno(r, "Failed to filter JSON identity data: %m"); +VERB(verb_unlock_home, "unlock", "USER…", 2, VERB_ANY, 0, + "Unlock a temporarily locked home area"); +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; - r = sd_json_variant_filter(&arg_identity_extra_privileged, fields); + r = acquire_bus(&bus); if (r < 0) - return log_error_errno(r, "Failed to filter JSON identity data: %m"); - - return 0; -} - -static int parse_ssh_authorized_keys(sd_json_variant **identity, const char *field, const char *arg) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - _cleanup_strv_free_ char **l = NULL, **add = NULL; - int r; - - assert(identity); - assert(field); - - if (isempty(arg)) - return drop_from_identity(field); + return r; - if (arg[0] == '@') { - /* If prefixed with '@', read from a file */ + STRV_FOREACH(i, strv_skip(argv, 1)) { + _cleanup_(user_record_unrefp) UserRecord *secret = NULL; - _cleanup_fclose_ FILE *f = fopen(arg + 1, "re"); - if (!f) - return log_error_errno(errno, "Failed to open '%s': %m", arg + 1); + r = acquire_passed_secrets(*i, &secret); + if (r < 0) + return r; for (;;) { - _cleanup_free_ char *line = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - r = read_line(f, LONG_LINE_MAX, &line); + r = bus_message_new_method_call(bus, &m, bus_mgr, "UnlockHome"); if (r < 0) - return log_error_errno(r, "Failed to read from '%s': %m", arg + 1); - if (r == 0) - break; + return bus_log_create_error(r); - if (isempty(line) || line[0] == '#') - continue; + r = sd_bus_message_append(m, "s", *i); + if (r < 0) + return bus_log_create_error(r); - r = strv_consume(&add, TAKE_PTR(line)); + r = bus_message_append_secret(m, secret); if (r < 0) - return log_oom(); - } - } else { - /* Otherwise, assume it's a literal key. Let's do some superficial checks - * before accepting it though. */ + return bus_log_create_error(r); - if (string_has_cc(arg, NULL)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Authorized key contains control characters, refusing."); - if (arg[0] == '#') - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified key is a comment?"); + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + if (r < 0) { + r = handle_generic_user_record_error(*i, secret, &error, r, false); + if (r < 0) { + if (ret == 0) + ret = r; - add = strv_new(arg); - if (!add) - return log_oom(); + break; + } + } else + break; + } } - v = sd_json_variant_ref(sd_json_variant_by_key(*identity, field)); - if (v) { - r = sd_json_variant_strv(v, &l); - if (r < 0) - return log_error_errno(r, "Failed to parse %s list: %m", field); - } + return ret; +} - r = strv_extend_strv_consume(&l, TAKE_PTR(add), /* filter_duplicates= */ true); - if (r < 0) - return log_oom(); +VERB_NOARG(verb_lock_all_homes, "lock-all", + "Lock all suitable home areas"); +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; + int r; - v = sd_json_variant_unref(v); + r = acquire_bus(&bus); + if (r < 0) + return r; - r = sd_json_variant_new_array_strv(&v, l); + r = bus_message_new_method_call(bus, &m, bus_mgr, "LockAllHomes"); if (r < 0) - return log_oom(); + return bus_log_create_error(r); - r = sd_json_variant_set_field(identity, field, v); + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); + return log_error_errno(r, "Failed to lock all homes: %s", bus_error_message(&error, r)); return 0; } -static int parse_string_field(sd_json_variant **identity, const char *field, const char *arg) { +VERB_GROUP("Other Commands"); +VERB_NOARG(verb_rebalance, "rebalance", + "Rebalance free space between home areas"); +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; int r; - assert(identity); - assert(field); - - if (isempty(arg)) - return drop_from_identity(field); + r = acquire_bus(&bus); + if (r < 0) + return r; - r = sd_json_variant_set_field_string(identity, field, arg); + r = bus_message_new_method_call(bus, &m, bus_mgr, "Rebalance"); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_REBALANCE_NOT_NEEDED)) + log_info("No homes needed rebalancing."); + else + return log_error_errno(r, "Failed to rebalance: %s", bus_error_message(&error, r)); + } else + log_info("Completed rebalancing."); + return 0; } -static int parse_home_directory_field(sd_json_variant **identity, const char *field, const char *arg) { - _cleanup_free_ char *hd = NULL; +static int create_or_register_from_credentials(void) { int r; - assert(identity); - assert(field); - - if (!isempty(arg)) { - r = parse_path_argument(arg, /* suppress_root= */ false, &hd); - if (r < 0) - return r; - - if (!valid_home(hd)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Home directory '%s' not valid.", hd); - } - - return parse_string_field(identity, field, hd); -} + _cleanup_close_ int fd = open_credentials_dir(); + if (IN_SET(fd, -ENXIO, -ENOENT)) /* Credential env var not set, or dir doesn't exist. */ + return 0; + if (fd < 0) + return log_error_errno(fd, "Failed to open credentials directory: %m"); -static int parse_realm_field(sd_json_variant **identity, const char *field, const char *arg) { - int r; + _cleanup_free_ DirectoryEntries *des = NULL; + r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des); + if (r < 0) + return log_error_errno(r, "Failed to enumerate credentials: %m"); - assert(identity); - assert(field); + int ret = 0, n_processed = 0; + FOREACH_ARRAY(i, des->entries, des->n_entries) { + struct dirent *de = *i; + if (de->d_type != DT_REG) + continue; - if (!isempty(arg)) { - r = dns_name_is_valid(arg); - if (r < 0) - return log_error_errno(r, "Failed to determine whether realm '%s' is a valid DNS domain: %m", arg); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Realm '%s' is not a valid DNS domain.", arg); - } + enum { + OPERATION_CREATE, + OPERATION_REGISTER, + } op; + const char *e; + if ((e = startswith(de->d_name, "home.create."))) + op = OPERATION_CREATE; + else if ((e = startswith(de->d_name, "home.register."))) + op = OPERATION_REGISTER; + else + continue; - return parse_string_field(identity, field, arg); -} + if (!valid_user_group_name(e, /* flags= */ 0)) { + log_notice("Skipping over credential with name that is not a suitable user name: %s", de->d_name); + continue; + } -static int parse_path_field(sd_json_variant **identity, const char *field, const char *arg) { - _cleanup_free_ char *v = NULL; - int r; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *identity = NULL; + unsigned line = 0, column = 0; + r = sd_json_parse_file_at( + /* f= */ NULL, + fd, + de->d_name, + /* flags= */ SD_JSON_PARSE_MUST_BE_OBJECT, + &identity, + &line, + &column); + if (r < 0) { + log_warning_errno(r, "[%s:%u:%u] Failed to parse user record in credential, ignoring: %m", de->d_name, line, column); + continue; + } - assert(identity); - assert(field); + sd_json_variant *un = sd_json_variant_by_key(identity, "userName"); + if (un) { + if (!sd_json_variant_is_string(un)) { + log_warning("User record from credential '%s' contains 'userName' field of invalid type, ignoring.", de->d_name); + continue; + } - if (!isempty(arg)) { - r = parse_path_argument(arg, /* suppress_root= */ false, &v); - if (r < 0) - return r; - } + if (!streq(sd_json_variant_string(un), e)) { + log_warning("User record from credential '%s' contains 'userName' field (%s) that doesn't match credential name (%s), ignoring.", de->d_name, sd_json_variant_string(un), e); + continue; + } + } else { + r = sd_json_variant_set_field_string(&identity, "userName", e); + if (r < 0) + return log_warning_errno(r, "Failed to set userName field: %m"); + } - return parse_string_field(identity, field, v); -} + log_notice("Processing user '%s' from credentials.", e); -static int parse_filename_field(sd_json_variant **identity, const char *field, const char *arg) { - assert(identity); - assert(field); + if (op == OPERATION_CREATE) + r = create_home_common(identity, /* show_enforce_password_policy_hint= */ false); + else + r = register_home_common(/* bus= */ NULL, identity); + if (r >= 0) + n_processed++; - if (!isempty(arg) && !filename_is_valid(arg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for %s field not a valid filename: %s", field, arg); + RET_GATHER(ret, r); + } - return parse_string_field(identity, field, arg); + return ret < 0 ? ret : n_processed; } -static int parse_unsigned_field(sd_json_variant **identity, const char *field, const char *arg) { +static int has_regular_user(void) { + _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; + UserDBMatch match = USERDB_MATCH_NULL; int r; - assert(identity); - assert(field); - - if (isempty(arg)) - return drop_from_identity(field); + match.disposition_mask = INDEX_TO_MASK(uint64_t, USER_REGULAR); - unsigned n; - r = safe_atou(arg, &n); + r = userdb_all(&match, USERDB_SUPPRESS_SHADOW, &iterator); if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + return log_error_errno(r, "Failed to create user enumerator: %m"); - r = sd_json_variant_set_field_unsigned(identity, field, n); + r = userdb_iterator_get(iterator, &match, /* ret= */ NULL); + if (r == -ESRCH) + return false; if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - return 0; + return log_error_errno(r, "Failed to enumerate users: %m"); + + return true; } -static int parse_u64_field(sd_json_variant **identity, const char *field, const char *arg) { +static int username_is_ok(const char *name, void *userdata) { int r; - assert(identity); - assert(field); + assert(name); - if (isempty(arg)) - return drop_from_identity(field); + if (!valid_user_group_name(name, /* flags= */ 0)) { + log_notice("Specified user name is not a valid UNIX user name, try again: %s", name); + return false; + } - uint64_t n; - r = safe_atou64(arg, &n); + r = userdb_by_name(name, /* match= */ NULL, USERDB_SUPPRESS_SHADOW, /* ret= */ NULL); + if (r == -ESRCH) + return true; if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + return log_error_errno(r, "Failed to check if specified user '%s' already exists: %m", name); - r = sd_json_variant_set_field_unsigned(identity, field, n); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - return 0; + log_notice("Specified user '%s' exists already, try again.", name); + return false; } -static int parse_size_field(sd_json_variant **identity, const char *field, const char *arg) { +static void end_marker(void) { + printf("\n%sExiting user account creation tool.%s\n\n", ansi_grey(), ansi_normal()); + fflush(stdout); +} + +static int create_interactively(void) { + _cleanup_free_ char *username = NULL; int r; - assert(identity); - assert(field); + if (!arg_prompt_new_user) { + log_debug("Prompting for user creation was not requested."); + return 0; + } - if (isempty(arg)) - return drop_from_identity(field); + /* Needs to be called before mute_console or it will garble the screen */ + (void) plymouth_hide_splash(); - uint64_t n; - r = parse_size(arg, 1024, &n); - if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *mute_console_link = NULL; + if (arg_mute_console) + (void) mute_console(&mute_console_link); - r = sd_json_variant_set_field_unsigned(identity, field, n); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - return 0; -} + (void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0); -static int parse_boolean_field(sd_json_variant **identity, const char *field, const char *arg) { - int r; + if (arg_chrome) + chrome_show("Create a User Account", /* bottom= */ NULL); - assert(identity); - assert(field); + DEFER_VOID_CALL(end_marker); + DEFER_VOID_CALL(chrome_hide); - if (isempty(arg)) - return drop_from_identity(field); + if (emoji_enabled()) { + fputs(glyph(GLYPH_HOME), stdout); + putchar(' '); + } + printf("Please create your user account!\n\n"); - r = parse_boolean(arg); + r = prompt_loop("Please enter user name to create", + GLYPH_IDCARD, + /* prefill= */ NULL, + /* menu= */ NULL, + /* accepted= */ NULL, + /* ellipsize_percentage= */ 60, + /* n_columns= */ 3, + /* column_width= */ 20, + username_is_ok, + /* refresh= */ NULL, + /* userdata= */ NULL, + PROMPT_MAY_SKIP|PROMPT_SILENT_VALIDATE, + &username); if (r < 0) - return log_error_errno(r, "Failed to parse boolean parameter %s: %s", field, arg); + return r; + if (isempty(username)) + return 0; - r = sd_json_variant_set_field_boolean(identity, field, r > 0); + r = sd_json_variant_set_field_string(&arg_identity_extra, "userName", username); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - return 0; -} + return log_error_errno(r, "Failed to set userName field: %m"); -static int parse_mode_field(sd_json_variant **identity, const char *field, const char *arg) { - int r; + /* Let's not insist on a strong password in the firstboot interactive interface. Insisting on this is + * really annoying, as the user cannot just invoke the tool again with "--enforce-password-policy=no" + * because after all the tool is called from the boot process, and not from an interactive + * shell. Moreover, when setting up an initial system we can assume the user owns it, and hence we + * don't need to hard enforce some policy on password strength some organization or OS vendor + * requires. Note that this just disables the *strict* enforcement of the password policy. Even with + * this disabled we'll still tell the user in the UI that the password is too weak and suggest better + * ones, even if we then accept the weak ones if the user insists, by repeating it. */ + r = sd_json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false); + if (r < 0) + return log_error_errno(r, "Failed to set enforcePasswordPolicy field: %m"); - assert(identity); - assert(field); + if (arg_prompt_groups) { + _cleanup_strv_free_ char **groups = NULL; - if (isempty(arg)) - return drop_from_identity(field); + putchar('\n'); - mode_t mode; - r = parse_mode(arg, &mode); - if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Access mode '%s' not valid.", arg); + r = prompt_groups(username, &groups); + if (r < 0) + return r; - r = sd_json_variant_set_field_unsigned(identity, field, mode); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - return 0; -} + if (!strv_isempty(groups)) { + r = sd_json_variant_set_field_strv(&arg_identity_extra, "memberOf", groups); + if (r < 0) + return log_error_errno(r, "Failed to set memberOf field: %m"); + } + } -static int parse_timestamp_field(sd_json_variant **identity, const char *field, const char *arg) { - int r; + if (arg_prompt_shell) { + _cleanup_free_ char *shell = NULL; - assert(identity); - assert(field); + putchar('\n'); - if (isempty(arg)) - return drop_from_identity(field); + r = prompt_shell(username, &shell); + if (r < 0) + return r; - usec_t n; - r = parse_timestamp(arg, &n); - if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + if (!isempty(shell)) { + log_info("Selected %s as the shell for user %s", shell, username); - r = sd_json_variant_set_field_unsigned(identity, field, n); + r = sd_json_variant_set_field_string(&arg_identity_extra, "shell", shell); + if (r < 0) + return log_error_errno(r, "Failed to set shell field: %m"); + } + } + + putchar('\n'); + + r = create_home_common(/* input= */ NULL, /* show_enforce_password_policy_hint= */ false); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); + return r; + + log_info("Successfully created account '%s'.", username); return 0; } -static int parse_time_field(sd_json_variant **identity, const char *field, const char *arg) { +VERB_NOARG(verb_firstboot, "firstboot", + "Run first-boot home area creation wizard"); +static int verb_firstboot(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; - assert(identity); - assert(field); + /* Let's honour the systemd.firstboot kernel command line option, just like the systemd-firstboot + * tool. */ - if (isempty(arg)) - return drop_from_identity(field); + bool enabled; + r = proc_cmdline_get_bool("systemd.firstboot", /* flags= */ 0, &enabled); + if (r < 0) + return log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring: %m"); + if (r > 0 && !enabled) { + log_debug("Found systemd.firstboot=no kernel command line argument, turning off all prompts."); + arg_prompt_new_user = false; + } - usec_t n; - r = parse_sec(arg, &n); + int ret = 0; + + RET_GATHER(ret, add_signing_keys_from_credentials()); + + r = create_or_register_from_credentials(); + RET_GATHER(ret, r); + bool existing_users = r > 0; + + r = getenv_bool("SYSTEMD_HOME_FIRSTBOOT_OVERRIDE"); + if (r == 0) + return 0; + if (r < 0) { + if (r != -ENXIO) + log_warning_errno(r, "Failed to parse $SYSTEMD_HOME_FIRSTBOOT_OVERRIDE, ignoring: %m"); + + if (!existing_users) { + r = has_regular_user(); + if (r < 0) + return r; + + existing_users = r > 0; + } + if (existing_users) { + log_info("Regular user already present in user database, skipping interactive user creation."); + return 0; + } + } + + RET_GATHER(ret, create_interactively()); + return ret; +} + +#define drop_from_identity(...) _drop_from_identity(STRV_MAKE(__VA_ARGS__)) + +static int _drop_from_identity(char **fields) { + int r; + + /* If we are called to update an identity record and drop some field, let's keep track of what to + * remove from the old record */ + r = strv_extend_strv(&arg_identity_filter, fields, /* filter_duplicates= */ true); if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + return log_oom(); - r = sd_json_variant_set_field_unsigned(identity, field, n); + /* Let's also drop the field if it was previously set to a new value on the same command line */ + r = sd_json_variant_filter(&arg_identity_extra, fields); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); + return log_error_errno(r, "Failed to filter JSON identity data: %m"); + + r = sd_json_variant_filter(&arg_identity_extra_this_machine, fields); + if (r < 0) + return log_error_errno(r, "Failed to filter JSON identity data: %m"); + + r = sd_json_variant_filter(&arg_identity_extra_privileged, fields); + if (r < 0) + return log_error_errno(r, "Failed to filter JSON identity data: %m"); + return 0; } -static int parse_uid_field(sd_json_variant **identity, const char *field, const char *arg) { - uid_t uid; +static int parse_ssh_authorized_keys(sd_json_variant **identity, const char *field, const char *arg) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_strv_free_ char **l = NULL, **add = NULL; int r; assert(identity); @@ -3357,26 +3199,70 @@ static int parse_uid_field(sd_json_variant **identity, const char *field, const if (isempty(arg)) return drop_from_identity(field); - r = parse_uid(arg, &uid); + if (arg[0] == '@') { + /* If prefixed with '@', read from a file */ + + _cleanup_fclose_ FILE *f = fopen(arg + 1, "re"); + if (!f) + return log_error_errno(errno, "Failed to open '%s': %m", arg + 1); + + for (;;) { + _cleanup_free_ char *line = NULL; + + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return log_error_errno(r, "Failed to read from '%s': %m", arg + 1); + if (r == 0) + break; + + if (isempty(line) || line[0] == '#') + continue; + + r = strv_consume(&add, TAKE_PTR(line)); + if (r < 0) + return log_oom(); + } + } else { + /* Otherwise, assume it's a literal key. Let's do some superficial checks + * before accepting it though. */ + + if (string_has_cc(arg, NULL)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Authorized key contains control characters, refusing."); + if (arg[0] == '#') + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified key is a comment?"); + + add = strv_new(arg); + if (!add) + return log_oom(); + } + + v = sd_json_variant_ref(sd_json_variant_by_key(*identity, field)); + if (v) { + r = sd_json_variant_strv(v, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse %s list: %m", field); + } + + r = strv_extend_strv_consume(&l, TAKE_PTR(add), /* filter_duplicates= */ true); if (r < 0) - return log_error_errno(r, "Failed to parse UID '%s'.", arg); + return log_oom(); - const char *bad_range = - uid_is_system(uid) ? "in system range" : - uid_is_greeter(uid) ? "in greeter range" : - uid_is_dynamic(uid) ? "in dynamic ragne" : - uid == UID_NOBODY ? "nobody UID" : NULL; - if (bad_range) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID "UID_FMT" is %s, refusing.", uid, bad_range); + v = sd_json_variant_unref(v); - r = sd_json_variant_set_field_unsigned(identity, field, uid); + r = sd_json_variant_new_array_strv(&v, l); + if (r < 0) + return log_oom(); + + r = sd_json_variant_set_field(identity, field, v); if (r < 0) return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; } -static int parse_nice_field(sd_json_variant **identity, const char *field, const char *arg) { - int nc, r; +static int parse_string_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; assert(identity); assert(field); @@ -3384,188 +3270,155 @@ static int parse_nice_field(sd_json_variant **identity, const char *field, const if (isempty(arg)) return drop_from_identity(field); - r = parse_nice(arg, &nc); - if (r < 0) - return log_error_errno(r, "Failed to parse nice level '%s': %m", arg); - - r = sd_json_variant_set_field_integer(identity, field, nc); + r = sd_json_variant_set_field_string(identity, field, arg); if (r < 0) return log_error_errno(r, "Failed to set %s field: %m", field); return 0; } -static int parse_auto_resize_mode_field(sd_json_variant **identity, const char *field, const char *arg) { +static int parse_home_directory_field(sd_json_variant **identity, const char *field, const char *arg) { + _cleanup_free_ char *hd = NULL; int r; assert(identity); assert(field); if (!isempty(arg)) { - r = auto_resize_mode_from_string(arg); + r = parse_path_argument(arg, /* suppress_root= */ false, &hd); if (r < 0) - return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); - arg = auto_resize_mode_to_string(r); + return r; + + if (!valid_home(hd)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Home directory '%s' not valid.", hd); } - return parse_string_field(identity, field, arg); + return parse_string_field(identity, field, hd); } -static int parse_rebalance_weight(sd_json_variant **identity, const char *field, const char *arg) { +static int parse_realm_field(sd_json_variant **identity, const char *field, const char *arg) { int r; assert(identity); assert(field); - if (isempty(arg)) - return drop_from_identity(field); - - uint64_t u; - if (streq(arg, "off")) - u = REBALANCE_WEIGHT_OFF; - else { - r = safe_atou64(arg, &u); + if (!isempty(arg)) { + r = dns_name_is_valid(arg); if (r < 0) - return log_error_errno(r, "Failed to parse rebalance weight parameter: %s", arg); - - if (u < REBALANCE_WEIGHT_MIN || u > REBALANCE_WEIGHT_MAX) - return log_error_errno(SYNTHETIC_ERRNO(ERANGE), - "Rebalancing weight out of valid range %" PRIu64 "%s%" PRIu64 ": %s", - REBALANCE_WEIGHT_MIN, glyph(GLYPH_ELLIPSIS), REBALANCE_WEIGHT_MAX, - arg); + return log_error_errno(r, "Failed to determine whether realm '%s' is a valid DNS domain: %m", arg); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Realm '%s' is not a valid DNS domain.", arg); } - /* Drop from per machine stuff and everywhere */ - r = drop_from_identity(field); - if (r < 0) - return r; - - r = sd_json_variant_set_field_unsigned(identity, field, u); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); - return 0; + return parse_string_field(identity, field, arg); } -static int parse_rlimit_field(sd_json_variant **identity, const char *field, const char *arg) { +static int parse_path_field(sd_json_variant **identity, const char *field, const char *arg) { + _cleanup_free_ char *v = NULL; int r; assert(identity); assert(field); - if (isempty(arg)) { - /* Remove all resource limits */ - - r = drop_from_identity(field); + if (!isempty(arg)) { + r = parse_path_argument(arg, /* suppress_root= */ false, &v); if (r < 0) return r; - - arg_identity_filter_rlimits = strv_free(arg_identity_filter_rlimits); - *identity = sd_json_variant_unref(*identity); - return 0; } - const char *eq = strchr(arg, '='); - if (!eq) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse resource limit assignment: %s", arg); + return parse_string_field(identity, field, v); +} - _cleanup_free_ char *s = strndup(arg, eq - arg); - if (!s) - return log_oom(); +static int parse_filename_field(sd_json_variant **identity, const char *field, const char *arg) { + assert(identity); + assert(field); - int limit = rlimit_from_string_harder(s); - if (limit < 0) - return log_error_errno(limit, "Unknown resource limit type: %s", s); + if (!isempty(arg) && !filename_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for %s field not a valid filename: %s", field, arg); - const char *rlimit_field = strjoina("RLIMIT_", rlimit_to_string(limit)); + return parse_string_field(identity, field, arg); +} - if (isempty(eq + 1)) { - /* Remove only the specific rlimit */ +static int parse_unsigned_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; - r = strv_extend(&arg_identity_filter_rlimits, rlimit_field); - if (r < 0) - return r; - - r = sd_json_variant_filter(identity, STRV_MAKE(rlimit_field)); - if (r < 0) - return log_error_errno(r, "Failed to filter JSON identity data: %m"); - return 0; - } + assert(identity); + assert(field); - _cleanup_(sd_json_variant_unrefp) sd_json_variant *jcur = NULL, *jmax = NULL; - struct rlimit rl; + if (isempty(arg)) + return drop_from_identity(field); - r = rlimit_parse(limit, eq + 1, &rl); + unsigned n; + r = safe_atou(arg, &n); if (r < 0) - return log_error_errno(r, "Failed to parse resource limit value: %s", eq + 1); + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); - r = rl.rlim_cur == RLIM_INFINITY ? sd_json_variant_new_null(&jcur) : sd_json_variant_new_unsigned(&jcur, rl.rlim_cur); + r = sd_json_variant_set_field_unsigned(identity, field, n); if (r < 0) - return log_error_errno(r, "Failed to allocate json variant: %m"); + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} - r = rl.rlim_max == RLIM_INFINITY ? sd_json_variant_new_null(&jmax) : sd_json_variant_new_unsigned(&jmax, rl.rlim_max); +static int parse_u64_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; + + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + uint64_t n; + r = safe_atou64(arg, &n); if (r < 0) - return log_error_errno(r, "Failed to allocate json variant: %m"); + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); - r = sd_json_variant_set_fieldbo(identity, rlimit_field, - SD_JSON_BUILD_PAIR_VARIANT("cur", jcur), - SD_JSON_BUILD_PAIR_VARIANT("max", jmax)); + r = sd_json_variant_set_field_unsigned(identity, field, n); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", rlimit_field); + return log_error_errno(r, "Failed to set %s field: %m", field); return 0; } -static int parse_disk_size_field(sd_json_variant **identity, const char *arg) { +static int parse_size_field(sd_json_variant **identity, const char *field, const char *arg) { int r; assert(identity); + assert(field); - if (isempty(arg)) { - r = drop_from_identity("diskSize", "diskSizeRelative", "rebalanceWeight"); - if (r < 0) - return r; - - arg_disk_size = arg_disk_size_relative = UINT64_MAX; - return 0; - } - - r = parse_permyriad(arg); - if (r < 0) { - r = parse_disk_size(arg, &arg_disk_size); - if (r < 0) - return r; + if (isempty(arg)) + return drop_from_identity(field); - r = drop_from_identity("diskSizeRelative"); - if (r < 0) - return r; + uint64_t n; + r = parse_size(arg, 1024, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); - r = sd_json_variant_set_field_unsigned(identity, "diskSize", arg_disk_size); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", "diskSize"); + r = sd_json_variant_set_field_unsigned(identity, field, n); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} - arg_disk_size_relative = UINT64_MAX; - } else { - /* Normalize to UINT32_MAX == 100% */ - arg_disk_size_relative = UINT32_SCALE_FROM_PERMYRIAD(r); +static int parse_boolean_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; - r = drop_from_identity("diskSize"); - if (r < 0) - return r; + assert(identity); + assert(field); - r = sd_json_variant_set_field_unsigned(identity, "diskSizeRelative", arg_disk_size_relative); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", "diskSizeRelative"); + if (isempty(arg)) + return drop_from_identity(field); - arg_disk_size = UINT64_MAX; - } + r = parse_boolean(arg); + if (r < 0) + return log_error_errno(r, "Failed to parse boolean parameter %s: %s", field, arg); - /* Automatically turn off the rebalance logic if user configured a size explicitly */ - r = sd_json_variant_set_field_unsigned(identity, "rebalanceWeight", REBALANCE_WEIGHT_OFF); + r = sd_json_variant_set_field_boolean(identity, field, r > 0); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", "rebalanceWeight"); + return log_error_errno(r, "Failed to set %s field: %m", field); return 0; } -static int parse_sector_size_field(sd_json_variant **identity, const char *field, const char *arg) { - uint64_t ss; +static int parse_mode_field(sd_json_variant **identity, const char *field, const char *arg) { int r; assert(identity); @@ -3574,17 +3427,18 @@ static int parse_sector_size_field(sd_json_variant **identity, const char *field if (isempty(arg)) return drop_from_identity(field); - r = parse_sector_size(arg, &ss); + mode_t mode; + r = parse_mode(arg, &mode); if (r < 0) - return r; + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Access mode '%s' not valid.", arg); - r = sd_json_variant_set_field_unsigned(identity, field, ss); + r = sd_json_variant_set_field_unsigned(identity, field, mode); if (r < 0) return log_error_errno(r, "Failed to set %s field: %m", field); return 0; } -static int parse_weight_field(sd_json_variant **identity, const char *field, const char *arg) { +static int parse_timestamp_field(sd_json_variant **identity, const char *field, const char *arg) { int r; assert(identity); @@ -3593,24 +3447,18 @@ static int parse_weight_field(sd_json_variant **identity, const char *field, con if (isempty(arg)) return drop_from_identity(field); - uint64_t u; - r = safe_atou64(arg, &u); + usec_t n; + r = parse_timestamp(arg, &n); if (r < 0) return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); - if (!CGROUP_WEIGHT_IS_OK(u)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Weight %" PRIu64 " is out of valid range for field %s.", u, field); - - r = sd_json_variant_set_field_unsigned(identity, field, u); + r = sd_json_variant_set_field_unsigned(identity, field, n); if (r < 0) return log_error_errno(r, "Failed to set %s field: %m", field); return 0; } -static int parse_environment_field(sd_json_variant **identity, const char *field, const char *arg) { - _cleanup_strv_free_ char **l = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *ne = NULL; +static int parse_time_field(sd_json_variant **identity, const char *field, const char *arg) { int r; assert(identity); @@ -3619,69 +3467,81 @@ static int parse_environment_field(sd_json_variant **identity, const char *field if (isempty(arg)) return drop_from_identity(field); - sd_json_variant *e = sd_json_variant_by_key(*identity, field); - if (e) { - r = sd_json_variant_strv(e, &l); - if (r < 0) - return log_error_errno(r, "Failed to parse JSON environment field: %m"); - } + usec_t n; + r = parse_sec(arg, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); - r = strv_env_replace_strdup_passthrough(&l, arg); + r = sd_json_variant_set_field_unsigned(identity, field, n); if (r < 0) - return log_error_errno(r, "Cannot assign environment variable %s: %m", arg); + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} - strv_sort(l); +static int parse_uid_field(sd_json_variant **identity, const char *field, const char *arg) { + uid_t uid; + int r; - r = sd_json_variant_new_array_strv(&ne, l); + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + r = parse_uid(arg, &uid); if (r < 0) - return log_error_errno(r, "Failed to allocate json list: %m"); + return log_error_errno(r, "Failed to parse UID '%s'.", arg); - r = sd_json_variant_set_field(identity, field, ne); + const char *bad_range = + uid_is_system(uid) ? "in system range" : + uid_is_greeter(uid) ? "in greeter range" : + uid_is_dynamic(uid) ? "in dynamic ragne" : + uid == UID_NOBODY ? "nobody UID" : NULL; + if (bad_range) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID "UID_FMT" is %s, refusing.", uid, bad_range); + + r = sd_json_variant_set_field_unsigned(identity, field, uid); if (r < 0) return log_error_errno(r, "Failed to set %s field: %m", field); return 0; } -static int parse_language_field(char ***languages, const char *arg) { - int r; +static int parse_nice_field(sd_json_variant **identity, const char *field, const char *arg) { + int nc, r; - if (isempty(arg)) { - r = drop_from_identity("preferredLanguage", "additionalLanguages"); - if (r < 0) - return r; + assert(identity); + assert(field); - strv_freep(languages); - return 0; - } + if (isempty(arg)) + return drop_from_identity(field); - for (const char *p = arg;;) { - _cleanup_free_ char *word = NULL; + r = parse_nice(arg, &nc); + if (r < 0) + return log_error_errno(r, "Failed to parse nice level '%s': %m", arg); - r = extract_first_word(&p, &word, ",:", /* flags= */ 0); - if (r < 0) - return log_error_errno(r, "Failed to parse locale list: %m"); - if (r == 0) - return 0; + r = sd_json_variant_set_field_integer(identity, field, nc); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} - if (!locale_is_valid(word)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale '%s' is not valid.", word); +static int parse_auto_resize_mode_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; - if (locale_is_installed(word) <= 0) - log_warning("Locale '%s' is not installed, accepting anyway.", word); + assert(identity); + assert(field); - r = strv_consume(languages, TAKE_PTR(word)); + if (!isempty(arg)) { + r = auto_resize_mode_from_string(arg); if (r < 0) - return log_oom(); - - strv_uniq(*languages); + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); + arg = auto_resize_mode_to_string(r); } + + return parse_string_field(identity, field, arg); } -static int parse_group_field( - sd_json_variant *source_identity, - sd_json_variant **identity, - const char *field, - const char *arg) { +static int parse_rebalance_weight(sd_json_variant **identity, const char *field, const char *arg) { int r; assert(identity); @@ -3690,1190 +3550,947 @@ static int parse_group_field( if (isempty(arg)) return drop_from_identity(field); - for (const char *p = arg;;) { - _cleanup_free_ char *word = NULL; - _cleanup_strv_free_ char **list = NULL; - - r = extract_first_word(&p, &word, ",", /* flags= */ 0); + uint64_t u; + if (streq(arg, "off")) + u = REBALANCE_WEIGHT_OFF; + else { + r = safe_atou64(arg, &u); if (r < 0) - return log_error_errno(r, "Failed to parse group list: %m"); - if (r == 0) - return 0; - - if (!valid_user_group_name(word, /* flags= */ 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid group name %s.", word); + return log_error_errno(r, "Failed to parse rebalance weight parameter: %s", arg); - _cleanup_(sd_json_variant_unrefp) sd_json_variant *mo = - sd_json_variant_ref(sd_json_variant_by_key(source_identity, field)); + if (u < REBALANCE_WEIGHT_MIN || u > REBALANCE_WEIGHT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), + "Rebalancing weight out of valid range %" PRIu64 "%s%" PRIu64 ": %s", + REBALANCE_WEIGHT_MIN, glyph(GLYPH_ELLIPSIS), REBALANCE_WEIGHT_MAX, + arg); + } - r = sd_json_variant_strv(mo, &list); - if (r < 0) - return log_error_errno(r, "Failed to parse group list: %m"); + /* Drop from per machine stuff and everywhere */ + r = drop_from_identity(field); + if (r < 0) + return r; - r = strv_extend(&list, word); - if (r < 0) - return log_oom(); + r = sd_json_variant_set_field_unsigned(identity, field, u); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} - strv_sort_uniq(list); +static int parse_rlimit_field(sd_json_variant **identity, const char *field, const char *arg) { + int r; - mo = sd_json_variant_unref(mo); - r = sd_json_variant_new_array_strv(&mo, list); - if (r < 0) - return log_error_errno(r, "Failed to allocate json list: %m"); + assert(identity); + assert(field); - r = sd_json_variant_set_field(identity, field, mo); + if (isempty(arg)) { + /* Remove all resource limits */ + + r = drop_from_identity(field); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); + return r; + + arg_identity_filter_rlimits = strv_free(arg_identity_filter_rlimits); + *identity = sd_json_variant_unref(*identity); + return 0; } -} -static int parse_capability_set_field( - sd_json_variant **identity, - uint64_t *capability_set, - const char *field, - const char *arg) { + const char *eq = strchr(arg, '='); + if (!eq) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse resource limit assignment: %s", arg); - _cleanup_strv_free_ char **l = NULL; - int r; + _cleanup_free_ char *s = strndup(arg, eq - arg); + if (!s) + return log_oom(); - assert(identity); - assert(capability_set); - assert(field); - assert(arg); + int limit = rlimit_from_string_harder(s); + if (limit < 0) + return log_error_errno(limit, "Unknown resource limit type: %s", s); - r = parse_capability_set(arg, CAP_MASK_UNSET, capability_set); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capabilities in capability string '%s'.", arg); + const char *rlimit_field = strjoina("RLIMIT_", rlimit_to_string(limit)); + + if (isempty(eq + 1)) { + /* Remove only the specific rlimit */ + + r = strv_extend(&arg_identity_filter_rlimits, rlimit_field); + if (r < 0) + return r; + + r = sd_json_variant_filter(identity, STRV_MAKE(rlimit_field)); + if (r < 0) + return log_error_errno(r, "Failed to filter JSON identity data: %m"); + return 0; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *jcur = NULL, *jmax = NULL; + struct rlimit rl; + + r = rlimit_parse(limit, eq + 1, &rl); if (r < 0) - return log_error_errno(r, "Failed to parse capability string '%s': %m", arg); + return log_error_errno(r, "Failed to parse resource limit value: %s", eq + 1); - if (*capability_set == CAP_MASK_UNSET) - return drop_from_identity(field); + r = rl.rlim_cur == RLIM_INFINITY ? sd_json_variant_new_null(&jcur) : sd_json_variant_new_unsigned(&jcur, rl.rlim_cur); + if (r < 0) + return log_error_errno(r, "Failed to allocate json variant: %m"); - if (capability_set_to_strv(*capability_set, &l) < 0) - return log_oom(); + r = rl.rlim_max == RLIM_INFINITY ? sd_json_variant_new_null(&jmax) : sd_json_variant_new_unsigned(&jmax, rl.rlim_max); + if (r < 0) + return log_error_errno(r, "Failed to allocate json variant: %m"); - r = sd_json_variant_set_field_strv(identity, field, l); + r = sd_json_variant_set_fieldbo(identity, rlimit_field, + SD_JSON_BUILD_PAIR_VARIANT("cur", jcur), + SD_JSON_BUILD_PAIR_VARIANT("max", jmax)); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); + return log_error_errno(r, "Failed to set %s field: %m", rlimit_field); return 0; } -static int parse_tmpfs_limit_field( - sd_json_variant **identity, - const char *field, - const char *field_scale, - const char *arg) { +static int parse_disk_size_field(sd_json_variant **identity, const char *arg) { int r; assert(identity); - assert(field); - assert(field_scale); - if (isempty(arg)) - return drop_from_identity(field, field_scale); + if (isempty(arg)) { + r = drop_from_identity("diskSize", "diskSizeRelative", "rebalanceWeight"); + if (r < 0) + return r; + + arg_disk_size = arg_disk_size_relative = UINT64_MAX; + return 0; + } r = parse_permyriad(arg); if (r < 0) { - uint64_t u; + r = parse_disk_size(arg, &arg_disk_size); + if (r < 0) + return r; - r = parse_size(arg, 1024, &u); + r = drop_from_identity("diskSizeRelative"); if (r < 0) - return log_error_errno(r, "Failed to parse %s/%s parameter: %s", field, field_scale, arg); + return r; - r = sd_json_variant_set_field_unsigned(identity, field, u); + r = sd_json_variant_set_field_unsigned(identity, "diskSize", arg_disk_size); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field); + return log_error_errno(r, "Failed to set %s field: %m", "diskSize"); - return drop_from_identity(field_scale); + arg_disk_size_relative = UINT64_MAX; + } else { + /* Normalize to UINT32_MAX == 100% */ + arg_disk_size_relative = UINT32_SCALE_FROM_PERMYRIAD(r); + + r = drop_from_identity("diskSize"); + if (r < 0) + return r; + + r = sd_json_variant_set_field_unsigned(identity, "diskSizeRelative", arg_disk_size_relative); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", "diskSizeRelative"); + + arg_disk_size = UINT64_MAX; } - r = sd_json_variant_set_field_unsigned(identity, field_scale, UINT32_SCALE_FROM_PERMYRIAD(r)); + /* Automatically turn off the rebalance logic if user configured a size explicitly */ + r = sd_json_variant_set_field_unsigned(identity, "rebalanceWeight", REBALANCE_WEIGHT_OFF); if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", field_scale); - - return drop_from_identity(field); + return log_error_errno(r, "Failed to set %s field: %m", "rebalanceWeight"); + return 0; } -static int parse_pkcs11_token_uri_field(const char *arg) { +static int parse_sector_size_field(sd_json_variant **identity, const char *field, const char *arg) { + uint64_t ss; int r; - assert(arg); + assert(identity); + assert(field); - if (streq(arg, "list")) - return pkcs11_list_tokens(); + if (isempty(arg)) + return drop_from_identity(field); - /* If --pkcs11-token-uri= is specified we always drop everything old */ - r = drop_from_identity("pkcs11TokenUri", "pkcs11EncryptedKey"); + r = parse_sector_size(arg, &ss); if (r < 0) return r; - if (isempty(arg)) { - arg_pkcs11_token_uri = strv_free(arg_pkcs11_token_uri); - return 1; - } - - if (streq(arg, "auto")) { - char *found; - r = pkcs11_find_token_auto(&found); - if (r < 0) - return r; - - r = strv_consume(&arg_pkcs11_token_uri, found); - } else { - if (!pkcs11_uri_valid(arg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", arg); - - r = strv_extend(&arg_pkcs11_token_uri, arg); - } + r = sd_json_variant_set_field_unsigned(identity, field, ss); if (r < 0) - return r; - - strv_uniq(arg_pkcs11_token_uri); - return 1; + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; } -static int parse_fido2_device_field(const char *arg) { +static int parse_weight_field(sd_json_variant **identity, const char *field, const char *arg) { int r; - assert(arg); + assert(identity); + assert(field); - if (streq(arg, "list")) - return fido2_list_devices(); + if (isempty(arg)) + return drop_from_identity(field); - r = drop_from_identity("fido2HmacCredential", "fido2HmacSalt"); + uint64_t u; + r = safe_atou64(arg, &u); if (r < 0) - return r; - - if (isempty(arg)) { - arg_fido2_device = strv_free(arg_fido2_device); - return 1; - } + return log_error_errno(r, "Failed to parse %s parameter: %s", field, arg); - if (streq(arg, "auto")) { - char *found; - r = fido2_find_device_auto(&found); - if (r < 0) - return r; + if (!CGROUP_WEIGHT_IS_OK(u)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Weight %" PRIu64 " is out of valid range for field %s.", u, field); - r = strv_consume(&arg_fido2_device, found); - } else - r = strv_extend(&arg_fido2_device, arg); + r = sd_json_variant_set_field_unsigned(identity, field, u); if (r < 0) - return r; - - strv_uniq(arg_fido2_device); - return 1; + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; } -static int help(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *link = NULL; +static int parse_environment_field(sd_json_variant **identity, const char *field, const char *arg) { + _cleanup_strv_free_ char **l = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *ne = NULL; int r; - pager_open(arg_pager_flags); + assert(identity); + assert(field); + + if (isempty(arg)) + return drop_from_identity(field); + + sd_json_variant *e = sd_json_variant_by_key(*identity, field); + if (e) { + r = sd_json_variant_strv(e, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse JSON environment field: %m"); + } - r = terminal_urlify_man("homectl", "1", &link); + r = strv_env_replace_strdup_passthrough(&l, arg); if (r < 0) - return log_oom(); + return log_error_errno(r, "Cannot assign environment variable %s: %m", arg); + + strv_sort(l); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%2$sCreate, manipulate or inspect home directories.%3$s\n" - "\n%4$sBasic User Manipulation Commands:%5$s\n" - " list List home areas\n" - " inspect USER… Inspect a home area\n" - " create USER Create a home area\n" - " update USER Update a home area\n" - " passwd USER Change password of a home area\n" - " resize USER SIZE Resize a home area\n" - " remove USER… Remove a home area\n" - "\n%4$sAdvanced User Manipulation Commands:%5$s\n" - " activate USER… Activate a home area\n" - " deactivate USER… Deactivate a home area\n" - " deactivate-all Deactivate all active home areas\n" - " with USER [COMMAND…] Run shell or command with access to a home area\n" - " authenticate USER… Authenticate a home area\n" - "\n%4$sUser Migration Commands:%5$s\n" - " adopt PATH… Add an existing home area on this system\n" - " register PATH… Register a user record locally\n" - " unregister USER… Unregister a user record locally\n" - "\n%4$sSigning Keys Commands:%5$s\n" - " list-signing-keys List home signing keys\n" - " get-signing-key [NAME…] Get a named home signing key\n" - " add-signing-key FILE… Add home signing key\n" - " remove-signing-key NAME… Remove home signing key\n" - "\n%4$sLock/Unlock Commands:%5$s\n" - " lock USER… Temporarily lock an active home area\n" - " unlock USER… Unlock a temporarily locked home area\n" - " lock-all Lock all suitable home areas\n" - "\n%4$sOther Commands:%5$s\n" - " rebalance Rebalance free space between home areas\n" - " firstboot Run first-boot home area creation wizard\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" - " --no-legend Do not show the headers and footers\n" - " --no-ask-password Do not ask for system passwords\n" - " --offline Don't update record embedded in home directory\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --identity=PATH Read JSON identity from file\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" - " --export-format= Strip JSON inspection data (full, stripped,\n" - " minimal)\n" - " -E When specified once equals -j --export-format=\n" - " stripped, when specified twice equals\n" - " -j --export-format=minimal\n" - " --key-name=NAME Key name when adding a signing key\n" - " --seize=no Do not strip existing signatures of user record\n" - " when creating\n" - " --prompt-new-user firstboot: Query user interactively for user\n" - " to create\n" - " --prompt-groups=no In first-boot mode, don't prompt for auxiliary\n" - " group memberships\n" - " --prompt-shell=no In first-boot mode, don't prompt for shells\n" - " --chrome=no In first-boot mode, don't show colour bar at top\n" - " and bottom of terminal\n" - " --mute-console=yes In first-boot mode, tell kernel/PID 1 to not\n" - " write to the console while running\n" - "\n%4$sGeneral User Record Properties:%5$s\n" - " -c --real-name=REALNAME Real name for user\n" - " --realm=REALM Realm to create user in\n" - " --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" - " --icon-name=NAME Icon name for user\n" - " -d --home-dir=PATH Home directory\n" - " -u --uid=UID Numeric UID for user\n" - " -G --member-of=GROUP Add user to group\n" - " --capability-bounding-set=CAPS\n" - " Bounding POSIX capability set\n" - " --capability-ambient-set=CAPS\n" - " Ambient POSIX capability set\n" - " --access-mode=MODE User home directory access mode\n" - " --umask=MODE Umask for user when logging in\n" - " --skel=PATH Skeleton directory to use\n" - " --shell=PATH Shell for account\n" - " --setenv=VARIABLE[=VALUE] Set an environment variable at log-in\n" - " --timezone=TIMEZONE Set a time-zone\n" - " --language=LOCALE Set preferred languages\n" - " --default-area=AREA Select default area\n" - "\n%4$sAuthentication User Record Properties:%5$s\n" - " --ssh-authorized-keys=KEYS\n" - " Specify SSH public keys\n" - " --pkcs11-token-uri=URI URI to PKCS#11 security token containing\n" - " private key and matching X.509 certificate\n" - " --fido2-device=PATH Path to FIDO2 hidraw device with hmac-secret\n" - " extension\n" - " --fido2-with-client-pin=BOOL\n" - " Whether to require entering a PIN to unlock the\n" - " account\n" - " --fido2-with-user-presence=BOOL\n" - " Whether to require user presence to unlock the\n" - " account\n" - " --fido2-with-user-verification=BOOL\n" - " Whether to require user verification to unlock\n" - " the account\n" - " --recovery-key=BOOL Add a recovery key\n" - "\n%4$sBlob Directory User Record Properties:%5$s\n" - " -b --blob=[FILENAME=]PATH\n" - " Path to a replacement blob directory, or replace\n" - " an individual files in the blob directory.\n" - " --avatar=PATH Path to user avatar picture\n" - " --login-background=PATH Path to user login background picture\n" - "\n%4$sAccount Management User Record Properties:%5$s\n" - " --locked=BOOL Set locked account state\n" - " --not-before=TIMESTAMP Do not allow logins before\n" - " --not-after=TIMESTAMP Do not allow logins after\n" - " --rate-limit-interval=SECS\n" - " Login rate-limit interval in seconds\n" - " --rate-limit-burst=NUMBER\n" - " Login rate-limit attempts per interval\n" - "\n%4$sPassword Policy User Record Properties:%5$s\n" - " --password-hint=HINT Set Password hint\n" - " --enforce-password-policy=BOOL\n" - " Control whether to enforce system's password\n" - " policy for this user\n" - " -P Same as --enforce-password-policy=no\n" - " --password-change-now=BOOL\n" - " Require the password to be changed on next login\n" - " --password-change-min=TIME\n" - " Require minimum time between password changes\n" - " --password-change-max=TIME\n" - " Require maximum time between password changes\n" - " --password-change-warn=TIME\n" - " How much time to warn before password expiry\n" - " --password-change-inactive=TIME\n" - " How much time to block password after expiry\n" - "\n%4$sResource Management User Record Properties:%5$s\n" - " --disk-size=BYTES Size to assign the user on disk\n" - " --nice=NICE Nice level for user\n" - " --rlimit=LIMIT=VALUE[:VALUE]\n" - " Set resource limits\n" - " --tasks-max=MAX Set maximum number of per-user tasks\n" - " --memory-high=BYTES Set high memory threshold in bytes\n" - " --memory-max=BYTES Set maximum memory limit\n" - " --cpu-weight=WEIGHT Set CPU weight\n" - " --io-weight=WEIGHT Set IO weight\n" - " --tmp-limit=BYTES|PERCENT Set limit on /tmp/\n" - " --dev-shm-limit=BYTES|PERCENT\n" - " Set limit on /dev/shm/\n" - "\n%4$sStorage User Record Properties:%5$s\n" - " --storage=STORAGE Storage type to use (luks, fscrypt, directory,\n" - " subvolume, cifs)\n" - " --image-path=PATH Path to image file/directory\n" - " --drop-caches=BOOL Whether to automatically drop caches on logout\n" - "\n%4$sLUKS Storage User Record Properties:%5$s\n" - " --fs-type=TYPE File system type to use in case of luks\n" - " storage (btrfs, ext4, xfs)\n" - " --luks-discard=BOOL Whether to use 'discard' feature of file system\n" - " when activated (mounted)\n" - " --luks-offline-discard=BOOL\n" - " Whether to trim file on logout\n" - " --luks-cipher=CIPHER Cipher to use for LUKS encryption\n" - " --luks-cipher-mode=MODE Cipher mode to use for LUKS encryption\n" - " --luks-volume-key-size=BITS\n" - " Volume key size to use for LUKS encryption\n" - " --luks-pbkdf-type=TYPE Password-based Key Derivation Function to use\n" - " --luks-pbkdf-hash-algorithm=ALGORITHM\n" - " PBKDF hash algorithm to use\n" - " --luks-pbkdf-time-cost=SECS\n" - " Time cost for PBKDF in seconds\n" - " --luks-pbkdf-memory-cost=BYTES\n" - " Memory cost for PBKDF in bytes\n" - " --luks-pbkdf-parallel-threads=NUMBER\n" - " Number of parallel threads for PKBDF\n" - " --luks-sector-size=BYTES\n" - " Sector size for LUKS encryption in bytes\n" - " --luks-extra-mount-options=OPTIONS\n" - " LUKS extra mount options\n" - " --auto-resize-mode=MODE Automatically grow/shrink home on login/logout\n" - " --rebalance-weight=WEIGHT Weight while rebalancing\n" - "\n%4$sMounting User Record Properties:%5$s\n" - " --nosuid=BOOL Control the 'nosuid' flag of the home mount\n" - " --nodev=BOOL Control the 'nodev' flag of the home mount\n" - " --noexec=BOOL Control the 'noexec' flag of the home mount\n" - "\n%4$sCIFS User Record Properties:%5$s\n" - " --cifs-domain=DOMAIN CIFS (Windows) domain\n" - " --cifs-user-name=USER CIFS (Windows) user name\n" - " --cifs-service=SERVICE CIFS (Windows) service to mount as home area\n" - " --cifs-extra-mount-options=OPTIONS\n" - " CIFS (Windows) extra mount options\n" - "\n%4$sLogin Behaviour User Record Properties:%5$s\n" - " --stop-delay=SECS How long to leave user services running after\n" - " logout\n" - " --kill-processes=BOOL Whether to kill user processes when sessions\n" - " terminate\n" - " --auto-login=BOOL Try to log this user in automatically\n" - " --session-launcher=LAUNCHER\n" - " Preferred session launcher file\n" - " --session-type=TYPE Preferred session type\n" - "\nSee the %6$s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - ansi_underline(), - ansi_normal(), - link); + r = sd_json_variant_new_array_strv(&ne, l); + if (r < 0) + return log_error_errno(r, "Failed to allocate json list: %m"); + r = sd_json_variant_set_field(identity, field, ne); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); return 0; } -static int parse_argv(int argc, char *argv[]) { - _cleanup_strv_free_ char **arg_languages = NULL; +static int parse_language_field(char ***languages, const char *arg) { + int r; - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_NO_ASK_PASSWORD, - ARG_OFFLINE, - ARG_REALM, - ARG_ALIAS, - ARG_EMAIL_ADDRESS, - ARG_DISK_SIZE, - ARG_ACCESS_MODE, - ARG_STORAGE, - ARG_FS_TYPE, - ARG_IMAGE_PATH, - ARG_UMASK, - ARG_LUKS_DISCARD, - ARG_LUKS_OFFLINE_DISCARD, - ARG_JSON, - ARG_SETENV, - ARG_TIMEZONE, - ARG_LANGUAGE, - ARG_LOCKED, - ARG_SSH_AUTHORIZED_KEYS, - ARG_LOCATION, - ARG_ICON_NAME, - ARG_PASSWORD_HINT, - ARG_NICE, - ARG_RLIMIT, - ARG_NOT_BEFORE, - ARG_NOT_AFTER, - ARG_LUKS_CIPHER, - ARG_LUKS_CIPHER_MODE, - ARG_LUKS_VOLUME_KEY_SIZE, - ARG_NOSUID, - ARG_NODEV, - ARG_NOEXEC, - ARG_CIFS_DOMAIN, - ARG_CIFS_USER_NAME, - ARG_CIFS_SERVICE, - ARG_CIFS_EXTRA_MOUNT_OPTIONS, - ARG_TASKS_MAX, - ARG_MEMORY_HIGH, - ARG_MEMORY_MAX, - ARG_CPU_WEIGHT, - ARG_IO_WEIGHT, - ARG_LUKS_PBKDF_TYPE, - ARG_LUKS_PBKDF_HASH_ALGORITHM, - ARG_LUKS_PBKDF_FORCE_ITERATIONS, - ARG_LUKS_PBKDF_TIME_COST, - ARG_LUKS_PBKDF_MEMORY_COST, - ARG_LUKS_PBKDF_PARALLEL_THREADS, - ARG_LUKS_SECTOR_SIZE, - ARG_RATE_LIMIT_INTERVAL, - ARG_RATE_LIMIT_BURST, - ARG_STOP_DELAY, - ARG_KILL_PROCESSES, - ARG_ENFORCE_PASSWORD_POLICY, - ARG_PASSWORD_CHANGE_NOW, - ARG_PASSWORD_CHANGE_MIN, - ARG_PASSWORD_CHANGE_MAX, - ARG_PASSWORD_CHANGE_WARN, - ARG_PASSWORD_CHANGE_INACTIVE, - ARG_EXPORT_FORMAT, - ARG_AUTO_LOGIN, - ARG_SESSION_LAUNCHER, - ARG_SESSION_TYPE, - ARG_PKCS11_TOKEN_URI, - ARG_FIDO2_DEVICE, - ARG_FIDO2_WITH_PIN, - ARG_FIDO2_WITH_UP, - ARG_FIDO2_WITH_UV, - ARG_RECOVERY_KEY, - ARG_DROP_CACHES, - ARG_LUKS_EXTRA_MOUNT_OPTIONS, - ARG_AUTO_RESIZE_MODE, - ARG_REBALANCE_WEIGHT, - ARG_FIDO2_CRED_ALG, - ARG_CAPABILITY_BOUNDING_SET, - ARG_CAPABILITY_AMBIENT_SET, - ARG_PROMPT_NEW_USER, - ARG_AVATAR, - ARG_LOGIN_BACKGROUND, - ARG_TMP_LIMIT, - ARG_DEV_SHM_LIMIT, - ARG_DEFAULT_AREA, - ARG_KEY_NAME, - ARG_SEIZE, - ARG_MATCH, - ARG_PROMPT_SHELL, - ARG_PROMPT_GROUPS, - ARG_CHROME, - ARG_MUTE_CONSOLE, - }; + assert(languages); - 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 }, - { "offline", no_argument, NULL, ARG_OFFLINE }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "identity", required_argument, NULL, 'I' }, - { "real-name", required_argument, NULL, 'c' }, - { "comment", required_argument, NULL, 'c' }, /* Compat alias to keep thing in sync with useradd(8) */ - { "realm", required_argument, NULL, ARG_REALM }, - { "alias", required_argument, NULL, ARG_ALIAS }, - { "email-address", required_argument, NULL, ARG_EMAIL_ADDRESS }, - { "location", required_argument, NULL, ARG_LOCATION }, - { "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) */ - { "uid", required_argument, NULL, 'u' }, /* Compatible with useradd(8) */ - { "member-of", required_argument, NULL, 'G' }, - { "groups", required_argument, NULL, 'G' }, /* Compat alias to keep thing in sync with useradd(8) */ - { "skel", required_argument, NULL, 'k' }, /* Compatible with useradd(8) */ - { "shell", required_argument, NULL, 's' }, /* Compatible with useradd(8) */ - { "setenv", required_argument, NULL, ARG_SETENV }, - { "timezone", required_argument, NULL, ARG_TIMEZONE }, - { "language", required_argument, NULL, ARG_LANGUAGE }, - { "locked", required_argument, NULL, ARG_LOCKED }, - { "not-before", required_argument, NULL, ARG_NOT_BEFORE }, - { "not-after", required_argument, NULL, ARG_NOT_AFTER }, - { "expiredate", required_argument, NULL, 'e' }, /* Compat alias to keep thing in sync with useradd(8) */ - { "ssh-authorized-keys", required_argument, NULL, ARG_SSH_AUTHORIZED_KEYS }, - { "disk-size", required_argument, NULL, ARG_DISK_SIZE }, - { "access-mode", required_argument, NULL, ARG_ACCESS_MODE }, - { "umask", required_argument, NULL, ARG_UMASK }, - { "nice", required_argument, NULL, ARG_NICE }, - { "rlimit", required_argument, NULL, ARG_RLIMIT }, - { "tasks-max", required_argument, NULL, ARG_TASKS_MAX }, - { "memory-high", required_argument, NULL, ARG_MEMORY_HIGH }, - { "memory-max", required_argument, NULL, ARG_MEMORY_MAX }, - { "cpu-weight", required_argument, NULL, ARG_CPU_WEIGHT }, - { "io-weight", required_argument, NULL, ARG_IO_WEIGHT }, - { "storage", required_argument, NULL, ARG_STORAGE }, - { "image-path", required_argument, NULL, ARG_IMAGE_PATH }, - { "fs-type", required_argument, NULL, ARG_FS_TYPE }, - { "luks-discard", required_argument, NULL, ARG_LUKS_DISCARD }, - { "luks-offline-discard", required_argument, NULL, ARG_LUKS_OFFLINE_DISCARD }, - { "luks-cipher", required_argument, NULL, ARG_LUKS_CIPHER }, - { "luks-cipher-mode", required_argument, NULL, ARG_LUKS_CIPHER_MODE }, - { "luks-volume-key-size", required_argument, NULL, ARG_LUKS_VOLUME_KEY_SIZE }, - { "luks-pbkdf-type", required_argument, NULL, ARG_LUKS_PBKDF_TYPE }, - { "luks-pbkdf-hash-algorithm", required_argument, NULL, ARG_LUKS_PBKDF_HASH_ALGORITHM }, - { "luks-pbkdf-force-iterations", required_argument, NULL, ARG_LUKS_PBKDF_FORCE_ITERATIONS }, - { "luks-pbkdf-time-cost", required_argument, NULL, ARG_LUKS_PBKDF_TIME_COST }, - { "luks-pbkdf-memory-cost", required_argument, NULL, ARG_LUKS_PBKDF_MEMORY_COST }, - { "luks-pbkdf-parallel-threads", required_argument, NULL, ARG_LUKS_PBKDF_PARALLEL_THREADS }, - { "luks-sector-size", required_argument, NULL, ARG_LUKS_SECTOR_SIZE }, - { "nosuid", required_argument, NULL, ARG_NOSUID }, - { "nodev", required_argument, NULL, ARG_NODEV }, - { "noexec", required_argument, NULL, ARG_NOEXEC }, - { "cifs-user-name", required_argument, NULL, ARG_CIFS_USER_NAME }, - { "cifs-domain", required_argument, NULL, ARG_CIFS_DOMAIN }, - { "cifs-service", required_argument, NULL, ARG_CIFS_SERVICE }, - { "cifs-extra-mount-options", required_argument, NULL, ARG_CIFS_EXTRA_MOUNT_OPTIONS }, - { "rate-limit-interval", required_argument, NULL, ARG_RATE_LIMIT_INTERVAL }, - { "rate-limit-burst", required_argument, NULL, ARG_RATE_LIMIT_BURST }, - { "stop-delay", required_argument, NULL, ARG_STOP_DELAY }, - { "kill-processes", required_argument, NULL, ARG_KILL_PROCESSES }, - { "enforce-password-policy", required_argument, NULL, ARG_ENFORCE_PASSWORD_POLICY }, - { "password-change-now", required_argument, NULL, ARG_PASSWORD_CHANGE_NOW }, - { "password-change-min", required_argument, NULL, ARG_PASSWORD_CHANGE_MIN }, - { "password-change-max", required_argument, NULL, ARG_PASSWORD_CHANGE_MAX }, - { "password-change-warn", required_argument, NULL, ARG_PASSWORD_CHANGE_WARN }, - { "password-change-inactive", required_argument, NULL, ARG_PASSWORD_CHANGE_INACTIVE }, - { "auto-login", required_argument, NULL, ARG_AUTO_LOGIN }, - { "session-launcher", required_argument, NULL, ARG_SESSION_LAUNCHER, }, - { "session-type", required_argument, NULL, ARG_SESSION_TYPE, }, - { "json", required_argument, NULL, ARG_JSON }, - { "export-format", required_argument, NULL, ARG_EXPORT_FORMAT }, - { "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-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 }, - { "recovery-key", required_argument, NULL, ARG_RECOVERY_KEY }, - { "drop-caches", required_argument, NULL, ARG_DROP_CACHES }, - { "luks-extra-mount-options", required_argument, NULL, ARG_LUKS_EXTRA_MOUNT_OPTIONS }, - { "auto-resize-mode", required_argument, NULL, ARG_AUTO_RESIZE_MODE }, - { "rebalance-weight", required_argument, NULL, ARG_REBALANCE_WEIGHT }, - { "capability-bounding-set", required_argument, NULL, ARG_CAPABILITY_BOUNDING_SET }, - { "capability-ambient-set", required_argument, NULL, ARG_CAPABILITY_AMBIENT_SET }, - { "prompt-new-user", no_argument, NULL, ARG_PROMPT_NEW_USER }, - { "blob", required_argument, NULL, 'b' }, - { "avatar", required_argument, NULL, ARG_AVATAR }, - { "login-background", required_argument, NULL, ARG_LOGIN_BACKGROUND }, - { "tmp-limit", required_argument, NULL, ARG_TMP_LIMIT }, - { "dev-shm-limit", required_argument, NULL, ARG_DEV_SHM_LIMIT }, - { "default-area", required_argument, NULL, ARG_DEFAULT_AREA }, - { "key-name", required_argument, NULL, ARG_KEY_NAME }, - { "seize", required_argument, NULL, ARG_SEIZE }, - { "match", required_argument, NULL, ARG_MATCH }, - { "prompt-shell", required_argument, NULL, ARG_PROMPT_SHELL }, - { "prompt-groups", required_argument, NULL, ARG_PROMPT_GROUPS }, - { "chrome", required_argument, NULL, ARG_CHROME }, - { "mute-console", required_argument, NULL, ARG_MUTE_CONSOLE }, - {} - }; + if (isempty(arg)) { + r = drop_from_identity("preferredLanguage", "additionalLanguages"); + if (r < 0) + return r; - int r; + strv_freep(languages); + return 0; + } - /* This points to one of arg_identity_extra, arg_identity_extra_this_machine, - * arg_identity_extra_other_machines, in order to redirect changes on the next property being set to - * this part of the identity, instead of the default. */ - sd_json_variant **match_identity = NULL; + for (const char *p = arg;;) { + _cleanup_free_ char *word = NULL; - assert(argc >= 0); - assert(argv); + r = extract_first_word(&p, &word, ",:", /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to parse locale list: %m"); + if (r == 0) + return 0; - /* Eventually we should probably turn this into a proper --dry-run option, but as long as it is not - * hooked up everywhere let's make it an environment variable only. */ - r = getenv_bool("SYSTEMD_HOME_DRY_RUN"); - if (r >= 0) - arg_dry_run = r; - else if (r != -ENXIO) - log_debug_errno(r, "Unable to parse $SYSTEMD_HOME_DRY_RUN, ignoring: %m"); + if (!locale_is_valid(word)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale '%s' is not valid.", word); - for (;;) { - int c; + if (locale_is_installed(word) <= 0) + log_warning("Locale '%s' is not installed, accepting anyway.", word); - c = getopt_long(argc, argv, "hH:M:I:c:d:u:G:k:s:e:b:jPENAT", options, NULL); - if (c < 0) - break; + r = strv_consume(languages, TAKE_PTR(word)); + if (r < 0) + return log_oom(); - switch (c) { + strv_uniq(*languages); + } +} - case 'h': - return help(0, NULL, NULL); +static int parse_group_field( + sd_json_variant **identity, + const char *field, + const char *arg) { + int r; - case ARG_VERSION: - return version(); + assert(identity); + assert(field); - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; + if (isempty(arg)) + return drop_from_identity(field); - case ARG_NO_LEGEND: - arg_legend = false; - break; + for (const char *p = arg;;) { + _cleanup_free_ char *word = NULL; + _cleanup_strv_free_ char **list = NULL; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; + r = extract_first_word(&p, &word, ",", /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to parse group list: %m"); + if (r == 0) + return 0; - case ARG_OFFLINE: - arg_offline = true; - break; + if (!valid_user_group_name(word, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid group name %s.", word); - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *mo = + sd_json_variant_ref(sd_json_variant_by_key(*identity, field)); - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); - if (r < 0) - return r; - break; + r = sd_json_variant_strv(mo, &list); + if (r < 0) + return log_error_errno(r, "Failed to parse group list: %m"); - case 'I': - arg_identity = optarg; - break; + r = strv_extend(&list, word); + if (r < 0) + return log_oom(); - case 'c': - if (!isempty(optarg) && !valid_gecos(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid GECOS field '%s'.", optarg); + strv_sort_uniq(list); - r = parse_string_field(match_identity ?: &arg_identity_extra, "realName", optarg); - if (r < 0) - return r; - break; + mo = sd_json_variant_unref(mo); + r = sd_json_variant_new_array_strv(&mo, list); + if (r < 0) + return log_error_errno(r, "Failed to allocate json list: %m"); - case ARG_ALIAS: - r = parse_group_field(arg_identity_extra, &arg_identity_extra, "aliases", optarg); - if (r < 0) - return r; - break; + r = sd_json_variant_set_field(identity, field, mo); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + } +} - case 'd': - r = parse_home_directory_field(&arg_identity_extra, "homeDirectory", optarg); - if (r < 0) - return r; - break; +static int parse_capability_set_field( + sd_json_variant **identity, + uint64_t *capability_set, + const char *field, + const char *arg) { - case ARG_REALM: - r = parse_realm_field(&arg_identity_extra, "realm", optarg); - if (r < 0) - return r; - break; + _cleanup_strv_free_ char **l = NULL; + int r; - case ARG_EMAIL_ADDRESS: - case ARG_LOCATION: - case ARG_ICON_NAME: - case ARG_CIFS_USER_NAME: - case ARG_CIFS_DOMAIN: - case ARG_CIFS_EXTRA_MOUNT_OPTIONS: - case ARG_LUKS_EXTRA_MOUNT_OPTIONS: - case ARG_SESSION_LAUNCHER: - case ARG_SESSION_TYPE: { - const char *field = - c == ARG_EMAIL_ADDRESS ? "emailAddress" : - c == ARG_LOCATION ? "location" : - c == ARG_ICON_NAME ? "iconName" : - c == ARG_CIFS_USER_NAME ? "cifsUserName" : - c == ARG_CIFS_DOMAIN ? "cifsDomain" : - c == ARG_CIFS_EXTRA_MOUNT_OPTIONS ? "cifsExtraMountOptions" : - c == ARG_LUKS_EXTRA_MOUNT_OPTIONS ? "luksExtraMountOptions" : - c == ARG_SESSION_LAUNCHER ? "preferredSessionLauncher" : - c == ARG_SESSION_TYPE ? "preferredSessionType" : - NULL; - assert(field); + assert(identity); + assert(capability_set); + assert(field); + assert(arg); - r = parse_string_field(match_identity ?: &arg_identity_extra, field, optarg); - if (r < 0) - return r; - break; - } + r = parse_capability_set(arg, CAP_MASK_UNSET, capability_set); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capabilities in capability string '%s'.", arg); + if (r < 0) + return log_error_errno(r, "Failed to parse capability string '%s': %m", arg); - case ARG_CIFS_SERVICE: - if (!isempty(optarg)) { - r = parse_cifs_service(optarg, /* ret_host= */ NULL, /* ret_service= */ NULL, /* ret_path= */ NULL); - if (r < 0) - return log_error_errno(r, "Failed to validate CIFS service name: %s", optarg); - } + if (*capability_set == CAP_MASK_UNSET) + return drop_from_identity(field); - r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsService", optarg); - if (r < 0) - return r; - break; + if (capability_set_to_strv(*capability_set, &l) < 0) + return log_oom(); - case ARG_PASSWORD_HINT: - r = parse_string_field(&arg_identity_extra_privileged, "passwordHint", optarg); - if (r < 0) - return r; + r = sd_json_variant_set_field_strv(identity, field, l); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); + return 0; +} - string_erase(optarg); - break; +static int parse_tmpfs_limit_field( + sd_json_variant **identity, + const char *field, + const char *field_scale, + const char *arg) { + int r; - case ARG_NICE: - r = parse_nice_field(match_identity ?: &arg_identity_extra, "niceLevel", optarg); - if (r < 0) - return r; - break; + assert(identity); + assert(field); + assert(field_scale); - case ARG_RLIMIT: - r = parse_rlimit_field(&arg_identity_extra_rlimits, "resourceLimits", optarg); - if (r < 0) - return r; - break; + if (isempty(arg)) + return drop_from_identity(field, field_scale); - case 'u': - r = parse_uid_field(&arg_identity_extra, "uid", optarg); - if (r < 0) - return r; - break; + r = parse_permyriad(arg); + if (r < 0) { + uint64_t u; - case 'k': - case ARG_IMAGE_PATH: { - const char *field = c == 'k' ? "skeletonDirectory" : "imagePath"; + r = parse_size(arg, 1024, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse %s/%s parameter: %s", field, field_scale, arg); - r = parse_path_field(match_identity ?: &arg_identity_extra_this_machine, field, optarg); - if (r < 0) - return r; - break; - } + r = sd_json_variant_set_field_unsigned(identity, field, u); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field); - case 's': - if (!isempty(optarg) && !valid_shell(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Shell '%s' not valid.", optarg); + return drop_from_identity(field_scale); + } - r = parse_string_field(match_identity ?: &arg_identity_extra, "shell", optarg); - if (r < 0) - return r; - break; + r = sd_json_variant_set_field_unsigned(identity, field_scale, UINT32_SCALE_FROM_PERMYRIAD(r)); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", field_scale); - case ARG_SETENV: - r = parse_environment_field(match_identity ?: &arg_identity_extra, "environment", optarg); - if (r < 0) - return r; - break; + return drop_from_identity(field); +} - case ARG_TIMEZONE: - if (!isempty(optarg) && !timezone_is_valid(optarg, LOG_DEBUG)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Timezone '%s' is not valid.", optarg); +static int parse_pkcs11_token_uri_field(const char *arg) { + int r; - r = parse_string_field(match_identity ?: &arg_identity_extra, "timeZone", optarg); - if (r < 0) - return r; - break; + assert(arg); - case ARG_LANGUAGE: - r = parse_language_field(&arg_languages, optarg); - if (r < 0) - return r; - break; + if (streq(arg, "list")) + return pkcs11_list_tokens(); - case ARG_NOSUID: - case ARG_NODEV: - case ARG_NOEXEC: - case ARG_LOCKED: - case ARG_KILL_PROCESSES: - case ARG_ENFORCE_PASSWORD_POLICY: - case ARG_AUTO_LOGIN: - case ARG_PASSWORD_CHANGE_NOW: { - const char *field = - c == ARG_LOCKED ? "locked" : - c == ARG_NOSUID ? "mountNoSuid" : - c == ARG_NODEV ? "mountNoDevices" : - c == ARG_NOEXEC ? "mountNoExecute" : - c == ARG_KILL_PROCESSES ? "killProcesses" : - c == ARG_ENFORCE_PASSWORD_POLICY ? "enforcePasswordPolicy" : - c == ARG_AUTO_LOGIN ? "autoLogin" : - c == ARG_PASSWORD_CHANGE_NOW ? "passwordChangeNow" : - NULL; - assert(field); + /* If --pkcs11-token-uri= is specified we always drop everything old */ + r = drop_from_identity("pkcs11TokenUri", "pkcs11EncryptedKey"); + if (r < 0) + return r; - r = parse_boolean_field(match_identity ?: &arg_identity_extra, field, optarg); - if (r < 0) - return r; - break; - } + if (isempty(arg)) { + arg_pkcs11_token_uri = strv_free(arg_pkcs11_token_uri); + return 1; + } - case 'P': - r = sd_json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false); - if (r < 0) - return log_error_errno(r, "Failed to set %s field: %m", "enforcePasswordPolicy"); - break; + if (streq(arg, "auto")) { + char *found; + r = pkcs11_find_token_auto(&found); + if (r < 0) + return r; - case ARG_DISK_SIZE: - r = parse_disk_size_field(match_identity ?: &arg_identity_extra_this_machine, optarg); - if (r < 0) - return r; - break; + r = strv_consume(&arg_pkcs11_token_uri, found); + } else { + if (!pkcs11_uri_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", arg); - case ARG_ACCESS_MODE: - r = parse_mode_field(&arg_identity_extra, "accessMode", optarg); - if (r < 0) - return r; - break; + r = strv_extend(&arg_pkcs11_token_uri, arg); + } + if (r < 0) + return r; - case ARG_LUKS_DISCARD: - case ARG_LUKS_OFFLINE_DISCARD: { - const char *field = c == ARG_LUKS_DISCARD ? "luksDiscard" : "luksOfflineDiscard"; + strv_uniq(arg_pkcs11_token_uri); + return 1; +} - r = parse_boolean_field(match_identity ?: &arg_identity_extra, field, optarg); - if (r < 0) - return r; - break; - } +static int parse_fido2_device_field(const char *arg) { + int r; - case ARG_LUKS_VOLUME_KEY_SIZE: - case ARG_LUKS_PBKDF_FORCE_ITERATIONS: - case ARG_LUKS_PBKDF_PARALLEL_THREADS: - case ARG_RATE_LIMIT_BURST: { - const char *field = - c == ARG_LUKS_VOLUME_KEY_SIZE ? "luksVolumeKeySize" : - c == ARG_LUKS_PBKDF_FORCE_ITERATIONS ? "luksPbkdfForceIterations" : - c == ARG_LUKS_PBKDF_PARALLEL_THREADS ? "luksPbkdfParallelThreads" : - c == ARG_RATE_LIMIT_BURST ? "rateLimitBurst" : - NULL; - assert(field); + assert(arg); - r = parse_unsigned_field(match_identity ?: &arg_identity_extra, field, optarg); - if (r < 0) - return r; - break; - } + if (streq(arg, "list")) + return fido2_list_devices(); - case ARG_LUKS_SECTOR_SIZE: - r = parse_sector_size_field(match_identity ?: &arg_identity_extra, "luksSectorSize", optarg); - if (r < 0) - return r; - break; + r = drop_from_identity("fido2HmacCredential", "fido2HmacSalt"); + if (r < 0) + return r; - case ARG_UMASK: - r = parse_mode_field(match_identity ?: &arg_identity_extra, "umask", optarg); + if (isempty(arg)) { + arg_fido2_device = strv_free(arg_fido2_device); + return 1; + } + + if (streq(arg, "auto")) { + char *found; + r = fido2_find_device_auto(&found); + if (r < 0) + return r; + + r = strv_consume(&arg_fido2_device, found); + } else + r = strv_extend(&arg_fido2_device, arg); + if (r < 0) + return r; + + strv_uniq(arg_fido2_device); + return 1; +} + +static int help(void) { + static const char* const vgroups[] = { + "Basic User Manipulation Commands", + "Advanced User Manipulation Commands", + "User Migration Commands", + "Signing Keys Commands", + "Lock/Unlock Commands", + "Other Commands", + }; + + static const char* const ogroups[] = { + NULL, + "General User Record Properties", + "Authentication User Record Properties", + "Blob Directory User Record Properties", + "Account Management User Record Properties", + "Password Policy User Record Properties", + "Resource Management User Record Properties", + "Storage User Record Properties", + "LUKS Storage User Record Properties", + "Mounting User Record Properties", + "CIFS User Record Properties", + "Login Behaviour User Record Properties", + }; + + Table *vtables[ELEMENTSOF(vgroups)] = {}; + CLEANUP_ELEMENTS(vtables, table_unref_array_clear); + Table *otables[ELEMENTSOF(ogroups)] = {}; + CLEANUP_ELEMENTS(otables, table_unref_array_clear); + int r; + + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + r = verbs_get_help_table_group(vgroups[i], &vtables[i]); + if (r < 0) + return r; + } + + for (size_t i = 0; i < ELEMENTSOF(ogroups); i++) { + r = option_parser_get_help_table_group(ogroups[i], &otables[i]); + if (r < 0) + return r; + } + + /* The two groups are not synchronized because the option table is very wide. */ + + assert_cc(ELEMENTSOF(vtables) == 6); + (void) table_sync_column_widths(0, vtables[0], vtables[1], vtables[2], + vtables[3], vtables[4], vtables[5]); + + assert_cc(ELEMENTSOF(otables) == 12); + (void) table_sync_column_widths(0, otables[0], otables[1], otables[2], otables[3], + otables[4], otables[5], otables[6], otables[7], + otables[8], otables[9], otables[10], otables[11]); + + pager_open(arg_pager_flags); + + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Create, manipulate or inspect home directories."); + + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + help_section(vgroups[i]); + r = table_print_or_warn(vtables[i]); + if (r < 0) + return r; + } + + help_section("Options"); + r = table_print_or_warn(otables[0]); + if (r < 0) + return r; + + for (size_t i = 1; i < ELEMENTSOF(ogroups); i++) { + help_section(ogroups[i]); + r = table_print_or_warn(otables[i]); + if (r < 0) + return r; + } + + help_man_page_reference("homectl", "1"); + + return 0; +} + +VERB_COMMON_HELP_HIDDEN(help); + +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + _cleanup_strv_free_ char **arg_languages = NULL; + int r; + + /* This points to one of arg_identity_extra, arg_identity_extra_this_machine, + * arg_identity_extra_other_machines, in order to redirect changes on the next property being set to + * this part of the identity, instead of the default. */ + sd_json_variant **match_identity = NULL; + + assert(argc >= 0); + assert(argv); + assert(remaining_args); + + /* Eventually we should probably turn this into a proper --dry-run option, but as long as it is not + * hooked up everywhere let's make it an environment variable only. */ + r = getenv_bool("SYSTEMD_HOME_DRY_RUN"); + if (r >= 0) + arg_dry_run = r; + else if (r != -ENXIO) + log_debug_errno(r, "Unable to parse $SYSTEMD_HOME_DRY_RUN, ignoring: %m"); + + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_LONG("offline", NULL, "Don't update record embedded in home directory"): + arg_offline = true; + break; + + OPTION_COMMON_HOST: + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = opts.arg; + break; + + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case ARG_SSH_AUTHORIZED_KEYS: - r = parse_ssh_authorized_keys(&arg_identity_extra_privileged, "sshAuthorizedKeys", optarg); + OPTION('I', "identity", "PATH", "Read JSON identity from file"): + arg_identity = opts.arg; + break; + + OPTION_COMMON_JSON: + r = parse_json_argument(opts.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; + + OPTION_LONG("export-format", "FORMAT", + "Strip JSON inspection data (full, stripped, minimal)"): + if (streq(opts.arg, "help")) + return DUMP_STRING_TABLE(export_format, ExportFormat, _EXPORT_FORMAT_MAX); + + arg_export_format = export_format_from_string(opts.arg); + if (arg_export_format < 0) + return log_error_errno(arg_export_format, "Invalid export format: %s", opts.arg); + + break; + + OPTION_SHORT('E', NULL, "Same as -j --export-format=stripped"): {} + OPTION_HELP_VERBATIM("-EE", "Same as -j --export-format=minimal"): + if (arg_export_format == EXPORT_FORMAT_FULL) + arg_export_format = EXPORT_FORMAT_STRIPPED; + else if (arg_export_format == EXPORT_FORMAT_STRIPPED) + arg_export_format = EXPORT_FORMAT_MINIMAL; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specifying -E more than twice is not supported."); + + arg_json_format_flags &= ~SD_JSON_FORMAT_OFF; + if (arg_json_format_flags == 0) + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + break; + + OPTION_LONG("key-name", "NAME", "Key name when adding a signing key"): + if (!isempty(opts.arg) && !filename_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for --key-name= not a valid filename: %s", opts.arg); + + r = free_and_strdup_warn(&arg_key_name, empty_to_null(opts.arg)); if (r < 0) return r; + break; + OPTION_LONG("seize", "BOOL", + "Whether to strip existing signatures of user record when creating"): + r = parse_boolean_argument("--seize=", opts.arg, &arg_seize); + if (r < 0) + return r; break; - case ARG_NOT_BEFORE: - case ARG_NOT_AFTER: - case 'e': { - const char *field = c == ARG_NOT_BEFORE ? "notBeforeUSec" : "notAfterUSec"; + OPTION_LONG("prompt-new-user", NULL, + "firstboot: Query user interactively for user to create"): + arg_prompt_new_user = true; + break; - r = parse_timestamp_field(match_identity ?: &arg_identity_extra, field, optarg); + OPTION_LONG("prompt-groups", "BOOL", + "In first-boot mode, don't prompt for auxiliary group memberships"): + r = parse_boolean_argument("--prompt-groups=", opts.arg, &arg_prompt_groups); if (r < 0) return r; break; - } - case ARG_PASSWORD_CHANGE_MIN: - case ARG_PASSWORD_CHANGE_MAX: - case ARG_PASSWORD_CHANGE_WARN: - case ARG_PASSWORD_CHANGE_INACTIVE: { - const char *field = - c == ARG_PASSWORD_CHANGE_MIN ? "passwordChangeMinUSec" : - c == ARG_PASSWORD_CHANGE_MAX ? "passwordChangeMaxUSec" : - c == ARG_PASSWORD_CHANGE_WARN ? "passwordChangeWarnUSec" : - c == ARG_PASSWORD_CHANGE_INACTIVE ? "passwordChangeInactiveUSec" : - NULL; - assert(field); + OPTION_LONG("prompt-shell", "BOOL", + "In first-boot mode, don't prompt for shells"): + r = parse_boolean_argument("--prompt-shell=", opts.arg, &arg_prompt_shell); + if (r < 0) + return r; + break; - r = parse_time_field(match_identity ?: &arg_identity_extra, field, optarg); + OPTION_LONG("chrome", "BOOL", + "In first-boot mode, don't show colour bar at top and bottom of terminal"): + r = parse_boolean_argument("--chrome=", opts.arg, &arg_chrome); if (r < 0) return r; break; - } - case ARG_STORAGE: - case ARG_FS_TYPE: - case ARG_LUKS_CIPHER: - case ARG_LUKS_CIPHER_MODE: - case ARG_LUKS_PBKDF_TYPE: - case ARG_LUKS_PBKDF_HASH_ALGORITHM: { - const char *field = - c == ARG_STORAGE ? "storage" : - c == ARG_FS_TYPE ? "fileSystemType" : - c == ARG_LUKS_CIPHER ? "luksCipher" : - c == ARG_LUKS_CIPHER_MODE ? "luksCipherMode" : - c == ARG_LUKS_PBKDF_TYPE ? "luksPbkdfType" : - c == ARG_LUKS_PBKDF_HASH_ALGORITHM ? "luksPbkdfHashAlgorithm" : - NULL; - assert(field); + OPTION_LONG("mute-console", "BOOL", + "In first-boot mode, tell kernel/PID 1 to not write to the console while running"): + r = parse_boolean_argument("--mute-console=", opts.arg, &arg_mute_console); + if (r < 0) + return r; + break; + + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + OPTION_COMMON_NO_LEGEND: + arg_legend = false; + break; - sd_json_variant **identity = - match_identity ?: - IN_SET(c, ARG_STORAGE, ARG_FS_TYPE) ? - &arg_identity_extra_this_machine : &arg_identity_extra; + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; + break; - if (!isempty(optarg) && !string_is_safe(optarg)) + OPTION_GROUP("General User Record Properties"): {} + + OPTION('c', "real-name", "REALNAME", "Real name for user"): {} + OPTION_LONG("comment", "REALNAME", /* help= */ NULL): /* Compat alias to keep things in sync with useradd(8) */ + if (!isempty(opts.arg) && !valid_gecos(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for field %s not valid: %s", field, optarg); + "Invalid GECOS field '%s'.", opts.arg); - r = parse_string_field(identity, field, optarg); + r = parse_string_field(match_identity ?: &arg_identity_extra, "realName", opts.arg); if (r < 0) return r; break; - } - case ARG_LUKS_PBKDF_TIME_COST: - case ARG_RATE_LIMIT_INTERVAL: - case ARG_STOP_DELAY: { - const char *field = - c == ARG_LUKS_PBKDF_TIME_COST ? "luksPbkdfTimeCostUSec" : - c == ARG_RATE_LIMIT_INTERVAL ? "rateLimitIntervalUSec" : - c == ARG_STOP_DELAY ? "stopDelayUSec" : - NULL; - assert(field); - - r = parse_time_field(match_identity ?: &arg_identity_extra, field, optarg); + OPTION_LONG("realm", "REALM", "Realm to create user in"): + r = parse_realm_field(&arg_identity_extra, "realm", opts.arg); if (r < 0) return r; break; - } - case 'G': - r = parse_group_field(arg_identity_extra, - match_identity ?: &arg_identity_extra, - "memberOf", optarg); + OPTION_LONG("alias", "ALIAS", "Define alias usernames for this account"): + r = parse_group_field(&arg_identity_extra, "aliases", opts.arg); if (r < 0) return r; break; - case ARG_TASKS_MAX: - r = parse_u64_field(match_identity ?: &arg_identity_extra, "tasksMax", optarg); + OPTION_LONG("email-address", "EMAIL", "Email address for user"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "emailAddress", opts.arg); if (r < 0) return r; break; - case ARG_MEMORY_MAX: - case ARG_MEMORY_HIGH: - case ARG_LUKS_PBKDF_MEMORY_COST: { - const char *field = - c == ARG_MEMORY_MAX ? "memoryMax" : - c == ARG_MEMORY_HIGH ? "memoryHigh" : - c == ARG_LUKS_PBKDF_MEMORY_COST ? "luksPbkdfMemoryCost" : - NULL; - - r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, field, optarg); + OPTION_LONG("location", "LOCATION", "Set location of user on earth"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "location", opts.arg); if (r < 0) return r; break; - } - case ARG_CPU_WEIGHT: - case ARG_IO_WEIGHT: { - const char *field = c == ARG_CPU_WEIGHT ? "cpuWeight" : - c == ARG_IO_WEIGHT ? "ioWeight" : - NULL; + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "birth-date", "DATE", + "Set user birth date (YYYY-MM-DD)"): + if (isempty(opts.arg)) { + r = drop_from_identity("birthDate"); + if (r < 0) + return r; + } else { + r = parse_birth_date(opts.arg, /* ret= */ NULL); + if (r < 0) + return log_error_errno(r, "Invalid birth date (expected YYYY-MM-DD): %s", opts.arg); + + r = parse_string_field(&arg_identity_extra, "birthDate", opts.arg); + if (r < 0) + return r; + } + break; - r = parse_weight_field(match_identity ?: &arg_identity_extra, field, optarg); + OPTION_LONG("icon-name", "NAME", "Icon name for user"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "iconName", opts.arg); if (r < 0) return r; break; - } - case ARG_PKCS11_TOKEN_URI: - r = parse_pkcs11_token_uri_field(optarg); - if (r <= 0) + OPTION('d', "home-dir", "PATH", "Home directory"): /* Compatible with useradd(8) */ + r = parse_home_directory_field(&arg_identity_extra, "homeDirectory", opts.arg); + if (r < 0) return r; break; - case ARG_FIDO2_CRED_ALG: - r = parse_fido2_algorithm(optarg, &arg_fido2_cred_alg); + OPTION('u', "uid", "UID", "Numeric UID for user"): /* Compatible with useradd(8) */ + r = parse_uid_field(&arg_identity_extra, "uid", opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse COSE algorithm: %s", optarg); + return r; break; - case ARG_FIDO2_DEVICE: - r = parse_fido2_device_field(optarg); - if (r <= 0) + OPTION('G', "member-of", "GROUP", "Add user to group"): {} + OPTION_LONG("groups", "GROUP", /* help= */ NULL): /* Compat alias to keep things in sync with useradd(8) */ + r = parse_group_field(match_identity ?: &arg_identity_extra, "memberOf", opts.arg); + if (r < 0) return r; break; - case ARG_FIDO2_WITH_PIN: - r = parse_boolean_argument("--fido2-with-client-pin=", optarg, NULL); + OPTION_LONG("capability-bounding-set", "CAPS", "Bounding POSIX capability set"): + r = parse_capability_set_field(match_identity ?: &arg_identity_extra, + &arg_capability_bounding_set, + "capabilityBoundingSet", opts.arg); if (r < 0) return r; + break; - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r); + OPTION_LONG("capability-ambient-set", "CAPS", "Ambient POSIX capability set"): + r = parse_capability_set_field(match_identity ?: &arg_identity_extra, + &arg_capability_ambient_set, + "capabilityAmbientSet", opts.arg); + if (r < 0) + return r; break; - case ARG_FIDO2_WITH_UP: - r = parse_boolean_argument("--fido2-with-user-presence=", optarg, NULL); + OPTION_LONG("access-mode", "MODE", "User home directory access mode"): + r = parse_mode_field(&arg_identity_extra, "accessMode", opts.arg); if (r < 0) return r; + break; - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r); + OPTION_LONG("umask", "MODE", "Umask for user when logging in"): + r = parse_mode_field(match_identity ?: &arg_identity_extra, "umask", opts.arg); + if (r < 0) + return r; break; - case ARG_FIDO2_WITH_UV: - r = parse_boolean_argument("--fido2-with-user-verification=", optarg, NULL); + OPTION('k', "skel", "PATH", "Skeleton directory to use"): /* Compatible with useradd(8) */ + r = parse_path_field(match_identity ?: &arg_identity_extra_this_machine, "skeletonDirectory", opts.arg); if (r < 0) return r; + break; - SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r); + OPTION('s', "shell", "PATH", "Shell for account"): /* Compatible with useradd(8) */ + if (!isempty(opts.arg) && !valid_shell(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Shell '%s' not valid.", opts.arg); + + r = parse_string_field(match_identity ?: &arg_identity_extra, "shell", opts.arg); + if (r < 0) + return r; break; - case ARG_RECOVERY_KEY: - r = parse_boolean(optarg); + OPTION_LONG("setenv", "VARIABLE[=VALUE]", "Set an environment variable at log-in"): + r = parse_environment_field(match_identity ?: &arg_identity_extra, "environment", opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --recovery-key= argument: %s", optarg); - arg_recovery_key = r; + return r; + break; - r = drop_from_identity("recoveryKey", "recoveryKeyType"); + OPTION_LONG("timezone", "TIMEZONE", "Set a time-zone"): + if (!isempty(opts.arg) && !timezone_is_valid(opts.arg, LOG_DEBUG)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Timezone '%s' is not valid.", opts.arg); + + r = parse_string_field(match_identity ?: &arg_identity_extra, "timeZone", opts.arg); if (r < 0) return r; break; - case ARG_AUTO_RESIZE_MODE: - r = parse_auto_resize_mode_field(match_identity ?: &arg_identity_extra, - "autoResizeMode", optarg); + OPTION_LONG("language", "LOCALE", "Set preferred languages"): + r = parse_language_field(&arg_languages, opts.arg); if (r < 0) return r; break; - case ARG_REBALANCE_WEIGHT: - r = parse_rebalance_weight(match_identity ?: &arg_identity_extra, - "rebalanceWeight", optarg); + OPTION_LONG("default-area", "AREA", "Select default area"): + r = parse_filename_field(match_identity ?: &arg_identity_extra, "defaultArea", opts.arg); if (r < 0) return r; break; - case 'j': - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + OPTION_GROUP("Authentication User Record Properties"): {} + + OPTION_LONG("ssh-authorized-keys", "KEYS", "Specify SSH public keys"): + r = parse_ssh_authorized_keys(&arg_identity_extra_privileged, "sshAuthorizedKeys", opts.arg); + if (r < 0) + return r; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_LONG("pkcs11-token-uri", "URI", + "URI to PKCS#11 security token containing private key and matching X.509 certificate"): + r = parse_pkcs11_token_uri_field(opts.arg); if (r <= 0) return r; - break; - case 'E': - if (arg_export_format == EXPORT_FORMAT_FULL) - arg_export_format = EXPORT_FORMAT_STRIPPED; - else if (arg_export_format == EXPORT_FORMAT_STRIPPED) - arg_export_format = EXPORT_FORMAT_MINIMAL; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specifying -E more than twice is not supported."); - - arg_json_format_flags &= ~SD_JSON_FORMAT_OFF; - if (arg_json_format_flags == 0) - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + OPTION_LONG("fido2-device", "PATH", + "Path to FIDO2 hidraw device with hmac-secret extension"): + r = parse_fido2_device_field(opts.arg); + if (r <= 0) + return r; break; - case ARG_EXPORT_FORMAT: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(export_format, ExportFormat, _EXPORT_FORMAT_MAX); - - arg_export_format = export_format_from_string(optarg); - if (arg_export_format < 0) - return log_error_errno(arg_export_format, "Invalid export format: %s", optarg); + OPTION_LONG("fido2-with-client-pin", "BOOL", + "Whether to require entering a PIN to unlock the account"): + r = parse_boolean_argument("--fido2-with-client-pin=", opts.arg, NULL); + if (r < 0) + return r; + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r); break; - case ARG_DROP_CACHES: - r = parse_boolean_field(match_identity ?: &arg_identity_extra, "dropCaches", optarg); + OPTION_LONG("fido2-with-user-presence", "BOOL", + "Whether to require user presence to unlock the account"): + r = parse_boolean_argument("--fido2-with-user-presence=", opts.arg, NULL); if (r < 0) return r; + + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r); break; - case ARG_CAPABILITY_AMBIENT_SET: - r = parse_capability_set_field(match_identity ?: &arg_identity_extra, - &arg_capability_ambient_set, - "capabilityAmbientSet", optarg); + OPTION_LONG("fido2-with-user-verification", "BOOL", + "Whether to require user verification to unlock the account"): + r = parse_boolean_argument("--fido2-with-user-verification=", opts.arg, NULL); if (r < 0) return r; + + SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r); break; - case ARG_CAPABILITY_BOUNDING_SET: - r = parse_capability_set_field(match_identity ?: &arg_identity_extra, - &arg_capability_bounding_set, - "capabilityBoundingSet", optarg); + OPTION_LONG("recovery-key", "BOOL", "Add a recovery key"): + r = parse_boolean(opts.arg); + if (r < 0) + return log_error_errno(r, "Failed to parse --recovery-key= argument: %s", opts.arg); + arg_recovery_key = r; + + r = drop_from_identity("recoveryKey", "recoveryKeyType"); if (r < 0) return r; break; - case ARG_PROMPT_NEW_USER: - arg_prompt_new_user = true; - break; + OPTION_GROUP("Blob Directory User Record Properties"): {} - case 'b': - case ARG_AVATAR: - case ARG_LOGIN_BACKGROUND: { + OPTION('b', "blob", "[FILENAME=]PATH", + "Path to a replacement blob directory, or replace an individual files in the blob directory"): {} + OPTION_LONG("avatar", "PATH", "Path to user avatar picture"): {} + OPTION_LONG("login-background", "PATH", "Path to user login background picture"): { _cleanup_close_ int fd = -EBADF; _cleanup_free_ char *path = NULL, *filename = NULL; + const char *long_code = opts.opt->long_code; - if (c == 'b') { - char *eq; + if (streq(long_code, "blob")) { + const char *eq; - if (isempty(optarg)) { /* --blob= deletes everything, including existing blob dirs */ + if (isempty(opts.arg)) { /* --blob= deletes everything, including existing blob dirs */ hashmap_clear(arg_blob_files); arg_blob_dir = mfree(arg_blob_dir); arg_blob_clear = true; break; } - eq = strrchr(optarg, '='); + eq = strrchr(opts.arg, '='); if (!eq) { /* --blob=/some/path replaces the blob dir */ - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_blob_dir); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_blob_dir); if (r < 0) - return log_error_errno(r, "Failed to parse path %s: %m", optarg); + return r; break; } /* --blob=filename=/some/path replaces the file "filename" with /some/path */ - filename = strndup(optarg, eq - optarg); + filename = strndup(opts.arg, eq - opts.arg); if (!filename) return log_oom(); if (isempty(filename)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse blob file assignment: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse blob file assignment: %s", opts.arg); if (!suitable_blob_filename(filename)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid blob filename: %s", filename); r = parse_path_argument(eq + 1, /* suppress_root= */ false, &path); if (r < 0) - return log_error_errno(r, "Failed to parse path %s: %m", eq + 1); + return r; } else { - const char *well_known_filename = - c == ARG_AVATAR ? "avatar" : - c == ARG_LOGIN_BACKGROUND ? "login-background" : - NULL; - assert(well_known_filename); - - filename = strdup(well_known_filename); + filename = strdup(long_code); if (!filename) return log_oom(); - r = parse_path_argument(optarg, /* suppress_root= */ false, &path); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &path); if (r < 0) - return log_error_errno(r, "Failed to parse path %s: %m", optarg); + return r; } if (path) { @@ -4895,107 +4512,420 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_TMP_LIMIT: - case ARG_DEV_SHM_LIMIT: { - const char *field = - c == ARG_TMP_LIMIT ? "tmpLimit" : - c == ARG_DEV_SHM_LIMIT ? "devShmLimit" : - NULL; - const char *field_scale = - c == ARG_TMP_LIMIT ? "tmpLimitScale" : - c == ARG_DEV_SHM_LIMIT ? "devShmLimitScale" : - NULL; - - assert(field); - assert(field_scale); + OPTION_GROUP("Account Management User Record Properties"): {} - r = parse_tmpfs_limit_field(match_identity ?: &arg_identity_extra, - field, field_scale, optarg); + OPTION_LONG("locked", "BOOL", "Set locked account state"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "locked", opts.arg); if (r < 0) return r; break; - } - case ARG_DEFAULT_AREA: - r = parse_filename_field(match_identity ?: &arg_identity_extra, "defaultArea", optarg); + OPTION_LONG("not-before", "TIMESTAMP", "Do not allow logins before"): {} + OPTION_LONG("not-after", "TIMESTAMP", "Do not allow logins after"): {} + OPTION_LONG("expiredate", "TIMESTAMP", /* help= */ NULL): /* Compat alias for -e to keep things in sync with useradd(8) */ { + const char *field = streq(opts.opt->long_code, "not-before") ? "notBeforeUSec" : "notAfterUSec"; + + r = parse_timestamp_field(match_identity ?: &arg_identity_extra, field, opts.arg); if (r < 0) return r; break; + } - case ARG_KEY_NAME: - if (!isempty(optarg) && !filename_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Parameter for --key-name= not a valid filename: %s", optarg); - - r = free_and_strdup_warn(&arg_key_name, empty_to_null(optarg)); + OPTION_SHORT('e', "TIMESTAMP", /* help= */ NULL): /* -e alias for --expiredate */ + r = parse_timestamp_field(match_identity ?: &arg_identity_extra, "notAfterUSec", opts.arg); if (r < 0) return r; break; - case ARG_SEIZE: - r = parse_boolean_argument("--seize=", optarg, &arg_seize); + OPTION_LONG("rate-limit-interval", "SECS", "Login rate-limit interval in seconds"): + r = parse_time_field(match_identity ?: &arg_identity_extra, "rateLimitIntervalUSec", opts.arg); if (r < 0) return r; break; - case ARG_MATCH: - if (streq(optarg, "any")) - match_identity = &arg_identity_extra; - else if (streq(optarg, "this")) - match_identity = &arg_identity_extra_this_machine; - else if (streq(optarg, "other")) - match_identity = &arg_identity_extra_other_machines; - else if (streq(optarg, "auto")) - match_identity = NULL; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--machine= argument not understood. Refusing."); + OPTION_LONG("rate-limit-burst", "NUMBER", "Login rate-limit attempts per interval"): + r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "rateLimitBurst", opts.arg); + if (r < 0) + return r; break; - case 'A': - match_identity = &arg_identity_extra; - break; - case 'T': - match_identity = &arg_identity_extra_this_machine; - break; - case 'N': - match_identity = &arg_identity_extra_other_machines; - break; + OPTION_GROUP("Password Policy User Record Properties"): {} - case ARG_PROMPT_SHELL: - r = parse_boolean_argument("--prompt-shell=", optarg, &arg_prompt_shell); + OPTION_LONG("password-hint", "HINT", "Set Password hint"): + r = parse_string_field(&arg_identity_extra_privileged, "passwordHint", opts.arg); if (r < 0) return r; + string_erase((char *) opts.arg); break; - case ARG_PROMPT_GROUPS: - r = parse_boolean_argument("--prompt-groups=", optarg, &arg_prompt_groups); + OPTION_LONG("enforce-password-policy", "BOOL", + "Control whether to enforce system's password policy for this user"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "enforcePasswordPolicy", opts.arg); if (r < 0) return r; - break; - case ARG_CHROME: - r = parse_boolean_argument("--chrome=", optarg, &arg_chrome); + OPTION_SHORT('P', NULL, "Same as --enforce-password-policy=no"): + r = sd_json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false); + if (r < 0) + return log_error_errno(r, "Failed to set %s field: %m", "enforcePasswordPolicy"); + break; + + OPTION_LONG("password-change-now", "BOOL", + "Require the password to be changed on next login"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "passwordChangeNow", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("password-change-min", "TIME", "Require minimum time between password changes"): {} + OPTION_LONG("password-change-max", "TIME", "Require maximum time between password changes"): {} + OPTION_LONG("password-change-warn", "TIME", "How much time to warn before password expiry"): {} + OPTION_LONG("password-change-inactive", "TIME", "How much time to block password after expiry"): { + const char *lc = opts.opt->long_code; + const char *field = + streq(lc, "password-change-min") ? "passwordChangeMinUSec" : + streq(lc, "password-change-max") ? "passwordChangeMaxUSec" : + streq(lc, "password-change-warn") ? "passwordChangeWarnUSec" : + streq(lc, "password-change-inactive") ? "passwordChangeInactiveUSec" : + NULL; + assert(field); + + r = parse_time_field(match_identity ?: &arg_identity_extra, field, opts.arg); + if (r < 0) + return r; + break; + } + + OPTION_GROUP("Resource Management User Record Properties"): {} + + OPTION_LONG("disk-size", "BYTES", "Size to assign the user on disk"): + r = parse_disk_size_field(match_identity ?: &arg_identity_extra_this_machine, opts.arg); if (r < 0) return r; + break; + OPTION_LONG("nice", "NICE", "Nice level for user"): + r = parse_nice_field(match_identity ?: &arg_identity_extra, "niceLevel", opts.arg); + if (r < 0) + return r; break; - case ARG_MUTE_CONSOLE: - r = parse_boolean_argument("--mute-console=", optarg, &arg_mute_console); + OPTION_LONG("rlimit", "LIMIT=VALUE[:VALUE]", "Set resource limits"): + r = parse_rlimit_field(&arg_identity_extra_rlimits, "resourceLimits", opts.arg); if (r < 0) return r; + break; + OPTION_LONG("tasks-max", "MAX", "Set maximum number of per-user tasks"): + r = parse_u64_field(match_identity ?: &arg_identity_extra, "tasksMax", opts.arg); + if (r < 0) + return r; break; - case '?': - return -EINVAL; + OPTION_LONG("memory-high", "BYTES", "Set high memory threshold in bytes"): + r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, "memoryHigh", opts.arg); + if (r < 0) + return r; + break; - default: - assert_not_reached(); + OPTION_LONG("memory-max", "BYTES", "Set maximum memory limit"): + r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, "memoryMax", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("cpu-weight", "WEIGHT", "Set CPU weight"): {} + OPTION_LONG("io-weight", "WEIGHT", "Set IO weight"): { + const char *field = streq(opts.opt->long_code, "cpu-weight") ? "cpuWeight" : "ioWeight"; + + r = parse_weight_field(match_identity ?: &arg_identity_extra, field, opts.arg); + if (r < 0) + return r; + break; + } + + OPTION_LONG("tmp-limit", "BYTES|PERCENT", "Set limit on /tmp/"): {} + OPTION_LONG("dev-shm-limit", "BYTES|PERCENT", "Set limit on /dev/shm/"): { + bool is_tmp = streq(opts.opt->long_code, "tmp-limit"); + const char *field = is_tmp ? "tmpLimit" : "devShmLimit"; + const char *field_scale = is_tmp ? "tmpLimitScale" : "devShmLimitScale"; + + r = parse_tmpfs_limit_field(match_identity ?: &arg_identity_extra, + field, field_scale, opts.arg); + if (r < 0) + return r; + break; + } + + OPTION_GROUP("Storage User Record Properties"): {} + + OPTION_LONG("storage", "STORAGE", + "Storage type to use (luks, fscrypt, directory, subvolume, cifs)"): + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for field %s not valid: %s", "storage", opts.arg); + + r = parse_string_field(match_identity ?: &arg_identity_extra_this_machine, "storage", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("image-path", "PATH", "Path to image file/directory"): + r = parse_path_field(match_identity ?: &arg_identity_extra_this_machine, "imagePath", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("drop-caches", "BOOL", "Whether to automatically drop caches on logout"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "dropCaches", opts.arg); + if (r < 0) + return r; + break; + + OPTION_GROUP("LUKS Storage User Record Properties"): {} + + OPTION_LONG("fs-type", "TYPE", + "File system type to use in case of luks storage (btrfs, ext4, xfs)"): + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for field %s not valid: %s", "fileSystemType", opts.arg); + + r = parse_string_field(match_identity ?: &arg_identity_extra_this_machine, "fileSystemType", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("luks-discard", "BOOL", + "Whether to use 'discard' feature of file system when activated (mounted)"): {} + OPTION_LONG("luks-offline-discard", "BOOL", "Whether to trim file on logout"): { + const char *field = streq(opts.opt->long_code, "luks-discard") ? "luksDiscard" : "luksOfflineDiscard"; + + r = parse_boolean_field(match_identity ?: &arg_identity_extra, field, opts.arg); + if (r < 0) + return r; + break; + } + + OPTION_LONG("luks-cipher", "CIPHER", "Cipher to use for LUKS encryption"): + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for field %s not valid: %s", "luksCipher", opts.arg); + + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksCipher", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("luks-cipher-mode", "MODE", "Cipher mode to use for LUKS encryption"): + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for field %s not valid: %s", "luksCipherMode", opts.arg); + + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksCipherMode", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("luks-volume-key-size", "BITS", "Volume key size to use for LUKS encryption"): + r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "luksVolumeKeySize", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("luks-pbkdf-type", "TYPE", "Password-based Key Derivation Function to use"): + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for field %s not valid: %s", "luksPbkdfType", opts.arg); + + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksPbkdfType", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("luks-pbkdf-hash-algorithm", "ALG", "PBKDF hash algorithm to use"): + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Parameter for field %s not valid: %s", "luksPbkdfHashAlgorithm", opts.arg); + + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksPbkdfHashAlgorithm", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("luks-pbkdf-time-cost", "SECS", "Time cost for PBKDF in seconds"): + r = parse_time_field(match_identity ?: &arg_identity_extra, "luksPbkdfTimeCostUSec", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("luks-pbkdf-memory-cost", "BYTES", "Memory cost for PBKDF in bytes"): + r = parse_size_field(match_identity ?: &arg_identity_extra_this_machine, "luksPbkdfMemoryCost", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("luks-pbkdf-parallel-threads", "N", "Number of parallel threads for PKBDF"): + r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "luksPbkdfParallelThreads", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("luks-sector-size", "BYTES", "Sector size for LUKS encryption in bytes"): + r = parse_sector_size_field(match_identity ?: &arg_identity_extra, "luksSectorSize", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("luks-extra-mount-options", "…", "LUKS extra mount options"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "luksExtraMountOptions", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("luks-pbkdf-force-iterations", "NUMBER", /* help= */ NULL): + r = parse_unsigned_field(match_identity ?: &arg_identity_extra, "luksPbkdfForceIterations", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("auto-resize-mode", "MODE", + "Automatically grow/shrink home on login/logout"): + r = parse_auto_resize_mode_field(match_identity ?: &arg_identity_extra, + "autoResizeMode", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("rebalance-weight", "WEIGHT", "Weight while rebalancing"): + r = parse_rebalance_weight(match_identity ?: &arg_identity_extra, + "rebalanceWeight", opts.arg); + if (r < 0) + return r; + break; + + OPTION_GROUP("Mounting User Record Properties"): {} + + OPTION_LONG("nosuid", "BOOL", "Control the 'nosuid' flag of the home mount"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "mountNoSuid", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("nodev", "BOOL", "Control the 'nodev' flag of the home mount"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "mountNoDevices", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("noexec", "BOOL", "Control the 'noexec' flag of the home mount"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "mountNoExecute", opts.arg); + if (r < 0) + return r; + break; + + OPTION_GROUP("CIFS User Record Properties"): {} + + OPTION_LONG("cifs-domain", "DOMAIN", "CIFS (Windows) domain"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsDomain", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("cifs-user-name", "USER", "CIFS (Windows) user name"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsUserName", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("cifs-service", "SERVICE", + "CIFS (Windows) service to mount as home area"): + if (!isempty(opts.arg)) { + r = parse_cifs_service(opts.arg, /* ret_host= */ NULL, /* ret_service= */ NULL, /* ret_path= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to validate CIFS service name: %s", opts.arg); + } + + r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsService", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("cifs-extra-mount-options", "…", + "CIFS (Windows) extra mount options"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "cifsExtraMountOptions", opts.arg); + if (r < 0) + return r; + break; + + OPTION_GROUP("Login Behaviour User Record Properties"): {} + + OPTION_LONG("stop-delay", "SECS", + "How long to leave user services running after logout"): + r = parse_time_field(match_identity ?: &arg_identity_extra, "stopDelayUSec", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("kill-processes", "BOOL", + "Whether to kill user processes when sessions terminate"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "killProcesses", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("auto-login", "BOOL", "Try to log this user in automatically"): + r = parse_boolean_field(match_identity ?: &arg_identity_extra, "autoLogin", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("session-launcher", "LAUNCHER", "Preferred session launcher file"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "preferredSessionLauncher", opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("session-type", "TYPE", "Preferred session type"): + r = parse_string_field(match_identity ?: &arg_identity_extra, "preferredSessionType", opts.arg); + if (r < 0) + return r; + break; + + /* Hidden options below */ + + OPTION_LONG("fido2-credential-algorithm", "ALG", /* help= */ NULL): + r = parse_fido2_algorithm(opts.arg, &arg_fido2_cred_alg); + if (r < 0) + return log_error_errno(r, "Failed to parse COSE algorithm: %s", opts.arg); + break; + + OPTION_LONG("match", "any|this|other|auto", /* help= */ NULL): + if (streq(opts.arg, "any")) + match_identity = &arg_identity_extra; + else if (streq(opts.arg, "this")) + match_identity = &arg_identity_extra_this_machine; + else if (streq(opts.arg, "other")) + match_identity = &arg_identity_extra_other_machines; + else if (streq(opts.arg, "auto")) + match_identity = NULL; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--machine= argument not understood. Refusing."); + break; + + OPTION_SHORT('A', NULL, /* help= */ NULL): + match_identity = &arg_identity_extra; + break; + + OPTION_SHORT('T', NULL, /* help= */ NULL): + match_identity = &arg_identity_extra_this_machine; + break; + + OPTION_SHORT('N', NULL, /* help= */ NULL): + match_identity = &arg_identity_extra_other_machines; + break; } - } if (!strv_isempty(arg_languages)) { char **additional; @@ -5016,6 +4946,7 @@ static int parse_argv(int argc, char *argv[]) { } } + *remaining_args = option_parser_get_args(&opts); return 1; } @@ -5112,7 +5043,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,300 +5176,8 @@ 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) { - int r; - - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - r = acquire_bus(&bus); - if (r < 0) - return r; - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - r = bus_call_method(bus, bus_mgr, "ListSigningKeys", &error, &reply, NULL); - if (r < 0) - return log_error_errno(r, "Failed to list signing keys: %s", bus_error_message(&error, r)); - - _cleanup_(table_unrefp) Table *table = table_new("name", "key"); - if (!table) - return log_oom(); - - r = sd_bus_message_enter_container(reply, 'a', "(sst)"); - if (r < 0) - return bus_log_parse_error(r); - - for (;;) { - const char *name, *pem; - - r = sd_bus_message_read(reply, "(sst)", &name, &pem, NULL); - if (r < 0) - return bus_log_parse_error(r); - if (r == 0) - break; - - _cleanup_free_ char *h = NULL; - if (!sd_json_format_enabled(arg_json_format_flags)) { - /* Let's decode the PEM key to DER (so that we lose prefix/suffix), then truncate it - * for display reasons. */ - - _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); - if (n < 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to encode key as DER."); - - ssize_t m = base64mem(der, MIN(n, 64), &h); - if (m < 0) - return log_oom(); - if (n > 64) /* check if we truncated the original version */ - if (!strextend(&h, glyph(GLYPH_ELLIPSIS))) - return log_oom(); - } - - r = table_add_many( - table, - TABLE_STRING, name, - TABLE_STRING, h ?: pem); - if (r < 0) - return table_log_add_error(r); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (!table_isempty(table) || sd_json_format_enabled(arg_json_format_flags)) { - r = table_set_sort(table, (size_t) 0); - if (r < 0) - return table_log_sort_error(r); - - r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); - if (r < 0) - return r; - } - - if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) { - if (table_isempty(table)) - printf("No signing keys.\n"); - else - printf("\n%zu signing keys listed.\n", table_get_rows(table) - 1); - } - - return 0; -} - -static int verb_get_signing_key(int argc, char *argv[], void *userdata) { - int r; - - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - r = acquire_bus(&bus); - if (r < 0) - return r; - - char **keys = argc >= 2 ? strv_skip(argv, 1) : STRV_MAKE("local.public"); - int ret = 0; - STRV_FOREACH(k, keys) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - r = bus_call_method(bus, bus_mgr, "GetSigningKey", &error, &reply, "s", *k); - if (r < 0) { - RET_GATHER(ret, log_error_errno(r, "Failed to get signing key '%s': %s", *k, bus_error_message(&error, r))); - continue; - } - - const char *pem; - r = sd_bus_message_read(reply, "st", &pem, NULL); - if (r < 0) { - RET_GATHER(ret, bus_log_parse_error(r)); - continue; - } - - fputs(pem, stdout); - if (!endswith(pem, "\n")) - fputc('\n', stdout); - - fflush(stdout); - } - - return ret; -} - -static int add_signing_key_one(sd_bus *bus, const char *fn, FILE *key) { - int r; - - assert_se(bus); - assert_se(fn); - assert_se(key); - - _cleanup_free_ char *pem = NULL; - r = read_full_stream(key, &pem, /* ret_size= */ NULL); - if (r < 0) - return log_error_errno(r, "Failed to read key '%s': %m", fn); - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = bus_call_method(bus, bus_mgr, "AddSigningKey", &error, /* ret_reply= */ NULL, "sst", fn, pem, UINT64_C(0)); - if (r < 0) - return log_error_errno(r, "Failed to add signing key '%s': %s", fn, bus_error_message(&error, r)); - - return 0; -} - -static int verb_add_signing_key(int argc, char *argv[], void *userdata) { - int r; - - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - r = acquire_bus(&bus); - if (r < 0) - return r; - - int ret = EXIT_SUCCESS; - if (argc < 2 || streq(argv[1], "-")) { - if (!arg_key_name) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Key name must be specified via --key-name= when reading key from standard input, refusing."); - - RET_GATHER(ret, add_signing_key_one(bus, arg_key_name, stdin)); - } else { - /* Refuse if more han one key is specified in combination with --key-name= */ - if (argc >= 3 && arg_key_name) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--key-name= is not supported if multiple signing keys are specified, refusing."); - - STRV_FOREACH(k, strv_skip(argv, 1)) { - - if (streq(*k, "-")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing to read from standard input if multiple keys are specified."); - - _cleanup_free_ char *fn = NULL; - if (!arg_key_name) { - r = path_extract_filename(*k, &fn); - if (r < 0) { - RET_GATHER(ret, log_error_errno(r, "Failed to extract filename from path '%s': %m", *k)); - continue; - } - } - - _cleanup_fclose_ FILE *f = fopen(*k, "re"); - if (!f) { - RET_GATHER(ret, log_error_errno(errno, "Failed to open '%s': %m", *k)); - continue; - } - - RET_GATHER(ret, add_signing_key_one(bus, fn ?: arg_key_name, f)); - } - } - - return ret; -} - -static int add_signing_keys_from_credentials(void) { - int r; - - _cleanup_close_ int fd = open_credentials_dir(); - if (IN_SET(fd, -ENXIO, -ENOENT)) /* Credential env var not set, or dir doesn't exist. */ - return 0; - if (fd < 0) - return log_error_errno(fd, "Failed to open credentials directory: %m"); - - _cleanup_free_ DirectoryEntries *des = NULL; - r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des); - if (r < 0) - return log_error_errno(r, "Failed to enumerate credentials: %m"); - - int ret = 0; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - FOREACH_ARRAY(i, des->entries, des->n_entries) { - struct dirent *de = *i; - if (de->d_type != DT_REG) - continue; - - const char *e = startswith(de->d_name, "home.add-signing-key."); - if (!e) - continue; - - if (!filename_is_valid(e)) - continue; - - if (!bus) { - r = acquire_bus(&bus); - if (r < 0) - return r; - } - - _cleanup_fclose_ FILE *f = NULL; - r = xfopenat(fd, de->d_name, "re", O_NOFOLLOW, &f); - if (r < 0) { - RET_GATHER(ret, log_error_errno(r, "Failed to open credential '%s': %m", de->d_name)); - continue; - } - - RET_GATHER(ret, add_signing_key_one(bus, e, f)); - } - - return ret; -} - -static int remove_signing_key_one(sd_bus *bus, const char *fn) { - int r; - - assert_se(bus); - assert_se(fn); - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = bus_call_method(bus, bus_mgr, "RemoveSigningKey", &error, /* ret_reply= */ NULL, "st", fn, UINT64_C(0)); - if (r < 0) - return log_error_errno(r, "Failed to remove signing key '%s': %s", fn, bus_error_message(&error, r)); - - return 0; -} - -static int verb_remove_signing_key(int argc, char *argv[], void *userdata) { - int r; - - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - r = acquire_bus(&bus); - if (r < 0) - return r; - - r = EXIT_SUCCESS; - STRV_FOREACH(k, strv_skip(argv, 1)) - RET_GATHER(r, remove_signing_key_one(bus, *k)); - - return r; -} - 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 }, - {} - }; - + char **args = NULL; int r; log_setup(); @@ -5550,11 +5189,11 @@ static int run(int argc, char *argv[]) { if (is_fallback_shell(argv[0])) return fallback_shell(argc, argv); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); 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..ec26b63f1dd5a 100644 --- a/src/home/homed-home-bus.c +++ b/src/home/homed-home-bus.c @@ -109,6 +109,7 @@ static int home_verify_polkit_async( good_uid, /* flags= */ 0, &h->manager->polkit_registry, + /* ret_admin= */ NULL, error); } @@ -196,6 +197,7 @@ int bus_home_method_activate( h->uid, /* flags= */ 0, &h->manager->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -801,6 +803,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 +854,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..861106dd0f9f6 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -4,6 +4,7 @@ #include #include "sd-bus.h" +#include "sd-json.h" #include "blockdev-util.h" #include "btrfs-util.h" @@ -48,9 +49,6 @@ #include "user-record-util.h" #include "user-util.h" -/* Retry to deactivate home directories again and again every 15s until it works */ -#define RETRY_DEACTIVATE_USEC (15U * USEC_PER_SEC) - #define HOME_USERS_MAX 500 #define PENDING_OPERATIONS_MAX 100 @@ -513,7 +511,7 @@ static void home_start_retry_deactivate(Home *h) { h->manager->event, &h->retry_deactivate_event_source, CLOCK_MONOTONIC, - RETRY_DEACTIVATE_USEC, + HOME_RETRY_DEACTIVATE_USEC, 1*USEC_PER_MINUTE, home_on_retry_deactivate, h); @@ -570,26 +568,24 @@ static int home_parse_worker_stdout(int _fd, UserRecord **ret) { return 0; } - if (lseek(fd, 0, SEEK_SET) < 0) - return log_error_errno(errno, "Failed to seek to beginning of memfd: %m"); - f = take_fdopen(&fd, "r"); if (!f) return log_error_errno(errno, "Failed to reopen memfd: %m"); if (DEBUG_LOGGING) { - _cleanup_free_ char *text = NULL; + if (fseek(f, 0, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to seek to beginning of memfd: %m"); + _cleanup_free_ char *text = NULL; r = read_full_stream(f, &text, NULL); if (r < 0) return log_error_errno(r, "Failed to read from client: %m"); log_debug("Got from worker: %s", text); - rewind(f); } 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|SD_JSON_PARSE_SEEK0, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column); @@ -1092,9 +1088,8 @@ static void home_unlocking_finish(Home *h, int ret, UserRecord *hr) { log_debug("Unlocking operation of %s completed.", h->user_name); - h->current_operation = operation_result_unref(h->current_operation, r, &error); + h->current_operation = operation_result_unref(h->current_operation, 0, NULL); home_set_state(h, _HOME_STATE_INVALID); - return; } static void home_authenticating_finish(Home *h, int ret, UserRecord *hr) { @@ -2690,7 +2685,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 +3209,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..eb2a8441f2961 100644 --- a/src/home/homed-manager-bus.c +++ b/src/home/homed-manager-bus.c @@ -12,6 +12,8 @@ #include "bus-message-util.h" #include "bus-object.h" #include "bus-polkit.h" +#include "crypto-util.h" +#include "errno-util.h" #include "fileio.h" #include "format-util.h" #include "home-util.h" @@ -22,7 +24,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 +937,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; @@ -983,12 +984,23 @@ static int method_add_signing_key(sd_bus_message *message, void *userdata, sd_bu if (r == 0) return 1; /* Will call us back */ + /* Load libcrypto up front so that its unavailability (e.g. -EOPNOTSUPP) is propagated as the real + * error instead of being misattributed to the user-supplied key below. */ + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; r = openssl_pubkey_from_pem(pem, /* pem_size= */ SIZE_MAX, &pkey); - if (r == -EIO) + if (r < 0) { + /* libcrypto is loaded at this point, so any failure here is a failure to parse or load the + * user-supplied key (the translated OpenSSL errno varies: -EBADMSG, -EINVAL, -EOPNOTSUPP, + * -EIO, …) — treat it as an invalid public key, except resource exhaustion which we + * propagate as-is. */ + if (ERRNO_IS_NEG_RESOURCE(r)) + return r; return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Public key invalid: %s", fn); - if (r < 0) - return r; + } /* Make sure the local key is loaded before can detect conflicts */ r = manager_acquire_key_pair(m); diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c index 85c92192f483d..81b2e83763867 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -3,8 +3,6 @@ #include #include #include -#include -#include #include #include #include @@ -25,6 +23,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" @@ -41,9 +40,9 @@ #include "homed-manager-bus.h" #include "homed-operation.h" #include "homed-varlink.h" +#include "logarithm.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,9 +1336,9 @@ 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"); + return log_openssl_errors(LOG_ERR, "Failed to load private key pair"); log_info("Successfully loaded private key pair."); @@ -1353,21 +1352,21 @@ 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."); + return log_openssl_errors(LOG_ERR, "Failed to allocate Ed25519 key generation context."); - if (EVP_PKEY_keygen_init(ctx) <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize Ed25519 key generation context."); + if (sym_EVP_PKEY_keygen_init(ctx) <= 0) + return log_openssl_errors(LOG_ERR, "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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate Ed25519 key pair"); + if (sym_EVP_PKEY_keygen(ctx, &m->private_key) <= 0) + return log_openssl_errors(LOG_ERR, "Failed to generate Ed25519 key pair"); log_info("Successfully created Ed25519 key pair."); @@ -1378,8 +1377,8 @@ 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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write public key."); + if (sym_PEM_write_PUBKEY(fpublic, m->private_key) <= 0) + return log_openssl_errors(LOG_ERR, "Failed to write public key."); (void) fchmod(fileno(fpublic), 0444); /* Make public key world readable */ @@ -1394,8 +1393,8 @@ 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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write private key pair."); + if (sym_PEM_write_PrivateKey(fprivate, m->private_key, NULL, NULL, 0, NULL, NULL) <= 0) + return log_openssl_errors(LOG_ERR, "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,9 +1495,9 @@ 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); + return log_openssl_errors(LOG_ERR, "Failed to parse public key file %s.", path); r = hashmap_ensure_put(&m->public_keys, &public_key_hash_ops, fn, pkey); if (r < 0) @@ -1537,6 +1537,10 @@ int manager_startup(Manager *m) { assert(m); + r = DLOPEN_LIBCRYPTO(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + 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; @@ -1905,7 +1909,7 @@ static int manager_rebalance_calculate(Manager *m) { /* Keep track of home directory with the least amount of space left: we want to schedule the * next rebalance more quickly if this is low */ if (new_free < min_free) - min_free = h->rebalance_size; + min_free = new_free; if (new_free > UINT64_MAX - h->rebalance_usage) h->rebalance_goal = UINT64_MAX-1; /* maximum size */ @@ -1928,14 +1932,14 @@ 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; } /* Scale next rebalancing interval based on the least amount of space of any of the home - * directories. We pick a time in the range 1min … 15min, scaled by log2(min_free), so that: - * 10M → ~0.7min, 100M → ~2.7min, 1G → ~4.6min, 10G → ~6.5min, 100G ~8.4 */ - m->rebalance_interval_usec = (usec_t) CLAMP((LESS_BY(log2(min_free), 22)*15*USEC_PER_MINUTE)/26, + * directories. We pick a time in the range 1min … 15min, scaled by floor(log2(min_free)), + * so that: 10M → ~0.6min, 100M → ~2.3min, 1G → ~4.0min, 10G → ~6.3min, 100G → ~8.1min */ + m->rebalance_interval_usec = (usec_t) CLAMP((LESS_BY(log2u64(min_free), 22u)*15*USEC_PER_MINUTE)/26, 1 * USEC_PER_MINUTE, 15 * USEC_PER_MINUTE); 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..6f51f49328954 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,7 +8,9 @@ #include #include "alloc-util.h" +#include "crypto-util.h" #include "errno-util.h" +#include "extract-word.h" #include "fd-util.h" #include "format-util.h" #include "hexdecoct.h" @@ -19,13 +19,13 @@ #include "homework-password-cache.h" #include "homework-quota.h" #include "homework.h" +#include "iovec-util.h" #include "keyring-util.h" #include "log.h" #include "memory-util.h" #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,20 +180,41 @@ 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); memcpy(ret_key_descriptor, hashed2, FS_KEY_DESCRIPTOR_SIZE); } -static int fscrypt_slot_try_one( +/* fscrypt slot wrapping + * + * Two on-disk formats are supported. New slots are always written in v2, which improves offline security. + * + * v1 (legacy, read-only): + * : + * KDF: PBKDF2-HMAC-SHA512, 0xFFFF iterations + * Cipher: AES-256-CTR, all-zero IV (relies on per-slot random salt for key uniqueness) + * Integrity: 64-bit truncated double-SHA512 key descriptor comparison only + * + * v2: + * $v2::::: + * KDF: PBKDF2-HMAC-SHA512, FSCRYPT_SLOT_PBKDF2_ITERATIONS iterations (cost stored per slot) + * Cipher: AES-256-GCM with explicit random 96-bit IV and 128-bit authentication tag + */ + +#define FSCRYPT_SLOT_PBKDF2_ITERATIONS UINT32_C(600000) +#define FSCRYPT_SLOT_SALT_SIZE 64u +#define FSCRYPT_SLOT_GCM_IV_SIZE 12u +#define FSCRYPT_SLOT_GCM_TAG_SIZE 16u + +static int fscrypt_slot_try_v1( const char *password, const void *salt, size_t salt_size, const void *encrypted, size_t encrypted_size, const uint8_t match_key_descriptor[static FS_KEY_DESCRIPTOR_SIZE], - void **ret_decrypted, size_t *ret_decrypted_size) { + struct iovec *ret_decrypted) { _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL; _cleanup_(erase_and_freep) void *decrypted = NULL; @@ -211,6 +232,10 @@ static int fscrypt_slot_try_one( assert(encrypted_size > 0); assert(match_key_descriptor); + r = DLOPEN_LIBCRYPTO(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + 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 +251,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); @@ -271,25 +296,228 @@ static int fscrypt_slot_try_one( if (r < 0) return r; + if (ret_decrypted) { + ret_decrypted->iov_base = TAKE_PTR(decrypted); + ret_decrypted->iov_len = decrypted_size; + } + + return 0; +} + +static int fscrypt_slot_try_v2( + const char *password, + uint32_t iterations, + const struct iovec *salt, + const struct iovec *iv, + const struct iovec *encrypted, + const struct iovec *tag, + const uint8_t match_key_descriptor[static FS_KEY_DESCRIPTOR_SIZE], + struct iovec *ret_decrypted) { + + _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL; + _cleanup_(erase_and_freep) void *decrypted = NULL; + uint8_t key_descriptor[FS_KEY_DESCRIPTOR_SIZE]; + int decrypted_size_out1 = 0, decrypted_size_out2 = 0; + uint8_t derived[512 / 8] = {}; + size_t decrypted_size; + const EVP_CIPHER *cc; + int r; + + assert(password); + assert(iterations > 0); + assert(iovec_is_set(salt)); + assert(iovec_is_set(iv)); + assert(iovec_is_set(encrypted)); + assert(iovec_is_set(tag)); + assert(match_key_descriptor); + + r = DLOPEN_LIBCRYPTO(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + if (r < 0) + return r; + + CLEANUP_ERASE(derived); + + if (sym_PKCS5_PBKDF2_HMAC( + password, strlen(password), + salt->iov_base, salt->iov_len, + (int) iterations, sym_EVP_sha512(), + sizeof(derived), derived) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed."); + + context = sym_EVP_CIPHER_CTX_new(); + if (!context) + return log_oom(); + + assert_se(cc = sym_EVP_aes_256_gcm()); + + /* We only use the first 256 bit of the derived key */ + assert(sizeof(derived) >= (size_t) sym_EVP_CIPHER_get_key_length(cc)); + + if (sym_EVP_DecryptInit_ex(context, cc, /* impl= */ NULL, /* key= */ NULL, /* iv= */ NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context."); + + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, (int) iv->iov_len, /* ptr= */ NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set GCM IV length."); + + if (sym_EVP_DecryptInit_ex(context, /* type= */ NULL, /* impl= */ NULL, derived, iv->iov_base) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set decryption key/IV."); + + if (__builtin_add_overflow(encrypted->iov_len, (size_t) sym_EVP_CIPHER_get_block_size(cc), &decrypted_size)) + return log_error_errno(SYNTHETIC_ERRNO(EOVERFLOW), "Decrypted buffer size would overflow."); + + decrypted = malloc(decrypted_size); + if (!decrypted) + return log_oom(); + + if (sym_EVP_DecryptUpdate(context, (uint8_t*) decrypted, &decrypted_size_out1, encrypted->iov_base, encrypted->iov_len) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt volume key."); + + assert((size_t) decrypted_size_out1 <= decrypted_size); + + /* Set the expected GCM tag before finalisation, as an authentication failure here means the wrong + * password (or a tampered slot). */ + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, (int) tag->iov_len, tag->iov_base) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set GCM tag."); + + if (sym_EVP_DecryptFinal_ex(context, (uint8_t*) decrypted + decrypted_size_out1, &decrypted_size_out2) != 1) + return -ENOANO; /* GCM authentication failed: wrong password or tampered slot */ + + assert((size_t) decrypted_size_out1 + (size_t) decrypted_size_out2 <= decrypted_size); + decrypted_size = (size_t) decrypted_size_out1 + (size_t) decrypted_size_out2; + + calculate_key_descriptor(decrypted, decrypted_size, key_descriptor); + + if (memcmp(key_descriptor, match_key_descriptor, FS_KEY_DESCRIPTOR_SIZE) != 0) + /* Authenticated decryption succeeded but the resulting volume key does not match the policy + * descriptor. Treat as a non-match (e.g. leftover slot from a different fscrypt setup). */ + return -ENOANO; + + r = fscrypt_upload_volume_key(key_descriptor, decrypted, decrypted_size, KEY_SPEC_THREAD_KEYRING); + if (r < 0) + return r; + + if (ret_decrypted) { + ret_decrypted->iov_base = TAKE_PTR(decrypted); + ret_decrypted->iov_len = decrypted_size; + } + + return 0; +} + +static int fscrypt_slot_try_one( + const char *password, + const char *xattr_value, size_t xattr_size, + const uint8_t match_key_descriptor[static FS_KEY_DESCRIPTOR_SIZE], + struct iovec *ret_decrypted) { + + _cleanup_free_ void *salt = NULL, *iv = NULL, *encrypted = NULL, *tag = NULL; + size_t salt_size, iv_size, encrypted_size, tag_size; + const char *p, *e; + const void *body; + int r; + + assert(password); + assert(xattr_value); + assert(xattr_size > 0); + assert(match_key_descriptor); + + body = memory_startswith(xattr_value, xattr_size, "$v2:"); + if (!body) { + /* Legacy v1 format: ":" */ + log_debug("fscrypt slot uses legacy v1 format, will upgrade to v2 on next password change."); + + e = memchr(xattr_value, ':', xattr_size); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed legacy fscrypt slot (no separator)."); + + r = unbase64mem_full(xattr_value, e - xattr_value, /* secure= */ false, &salt, &salt_size); + if (r < 0) + return log_error_errno(r, "Failed to decode legacy salt: %m"); + + r = unbase64mem_full(e + 1, xattr_size - (e - xattr_value) - 1, /* secure= */ false, &encrypted, &encrypted_size); + if (r < 0) + return log_error_errno(r, "Failed to decode legacy ciphertext: %m"); + + return fscrypt_slot_try_v1(password, + salt, salt_size, + encrypted, encrypted_size, + match_key_descriptor, + ret_decrypted); + } + + /* v2 format: "$v2:::::". Reject if it has NULs. */ + _cleanup_free_ char *body_str = NULL; + r = make_cstring(body, xattr_size - STRLEN("$v2:"), MAKE_CSTRING_REFUSE_TRAILING_NUL, &body_str); + if (r < 0) + return log_error_errno(r, "Malformed v2 fscrypt slot: %m"); + + _cleanup_free_ char *iter_str = NULL, *salt_b64 = NULL, *iv_b64 = NULL, + *encrypted_b64 = NULL, *tag_b64 = NULL; + uint32_t iterations; + + p = body_str; + r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS | EXTRACT_RETAIN_ESCAPE, + &iter_str, &salt_b64, &iv_b64, &encrypted_b64, &tag_b64); + if (r < 0) + return log_error_errno(r, "Failed to parse v2 fscrypt slot: %m"); + if (r < 5) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed v2 fscrypt slot."); + + if (safe_atou32(iter_str, &iterations) < 0 || iterations == 0 || iterations > INT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid iteration count in v2 fscrypt slot."); + + r = unbase64mem(salt_b64, &salt, &salt_size); + if (r < 0) + return log_error_errno(r, "Failed to decode v2 salt: %m"); + if (salt_size == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid v2 salt size."); + + r = unbase64mem(iv_b64, &iv, &iv_size); + if (r < 0) + return log_error_errno(r, "Failed to decode v2 IV: %m"); + if (iv_size != FSCRYPT_SLOT_GCM_IV_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid v2 IV size."); + + r = unbase64mem(encrypted_b64, &encrypted, &encrypted_size); + if (r < 0) + return log_error_errno(r, "Failed to decode v2 ciphertext: %m"); + if (encrypted_size == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty v2 ciphertext."); + + r = unbase64mem(tag_b64, &tag, &tag_size); + if (r < 0) + return log_error_errno(r, "Failed to decode v2 tag: %m"); + if (tag_size != FSCRYPT_SLOT_GCM_TAG_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid v2 tag size."); + + _cleanup_(iovec_done_erase) struct iovec decrypted = {}; + r = fscrypt_slot_try_v2(password, + iterations, + &IOVEC_MAKE(salt, salt_size), + &IOVEC_MAKE(iv, iv_size), + &IOVEC_MAKE(encrypted, encrypted_size), + &IOVEC_MAKE(tag, tag_size), + match_key_descriptor, + &decrypted); + if (r < 0) + return r; + if (ret_decrypted) - *ret_decrypted = TAKE_PTR(decrypted); - if (ret_decrypted_size) - *ret_decrypted_size = decrypted_size; + *ret_decrypted = TAKE_STRUCT(decrypted); return 0; } static int fscrypt_slot_try_many( char **passwords, - const void *salt, size_t salt_size, - const void *encrypted, size_t encrypted_size, + const char *xattr_value, size_t xattr_size, const uint8_t match_key_descriptor[static FS_KEY_DESCRIPTOR_SIZE], - void **ret_decrypted, size_t *ret_decrypted_size) { + struct iovec *ret_decrypted) { int r; STRV_FOREACH(i, passwords) { - r = fscrypt_slot_try_one(*i, salt, salt_size, encrypted, encrypted_size, match_key_descriptor, ret_decrypted, ret_decrypted_size); + r = fscrypt_slot_try_one(*i, xattr_value, xattr_size, match_key_descriptor, ret_decrypted); if (r != -ENOANO) return r; } @@ -301,8 +529,7 @@ static int fscrypt_setup( const PasswordCache *cache, char **password, HomeSetup *setup, - void **ret_volume_key, - size_t *ret_volume_key_size) { + struct iovec *ret_volume_key) { _cleanup_free_ char *xattr_buf = NULL; int r; @@ -315,10 +542,9 @@ static int fscrypt_setup( return log_error_errno(r, "Failed to retrieve xattr list: %m"); NULSTR_FOREACH(xa, xattr_buf) { - _cleanup_free_ void *salt = NULL, *encrypted = NULL; _cleanup_free_ char *value = NULL; - size_t salt_size, encrypted_size, vsize; - const char *nr, *e; + size_t vsize; + const char *nr; /* Check if this xattr has the format 'trusted.fscrypt_slot' where '' is a 32-bit unsigned integer */ nr = startswith(xa, "trusted.fscrypt_slot"); @@ -332,28 +558,17 @@ static int fscrypt_setup( continue; if (r < 0) return log_error_errno(r, "Failed to read %s xattr: %m", xa); - - e = memchr(value, ':', vsize); - if (!e) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "xattr %s lacks ':' separator.", xa); - - r = unbase64mem_full(value, e - value, /* secure= */ false, &salt, &salt_size); - if (r < 0) - return log_error_errno(r, "Failed to decode salt of %s: %m", xa); - - r = unbase64mem_full(e + 1, vsize - (e - value) - 1, /* secure= */ false, &encrypted, &encrypted_size); - if (r < 0) - return log_error_errno(r, "Failed to decode encrypted key of %s: %m", xa); + if (vsize == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "xattr %s is empty.", xa); r = -ENOANO; char **list; FOREACH_ARGUMENT(list, cache->pkcs11_passwords, cache->fido2_passwords, password) { r = fscrypt_slot_try_many( list, - salt, salt_size, - encrypted, encrypted_size, + value, vsize, setup->fscrypt_key_descriptor, - ret_volume_key, ret_volume_key_size); + ret_volume_key); if (r >= 0) return 0; if (r != -ENOANO) @@ -369,9 +584,8 @@ int home_setup_fscrypt( HomeSetup *setup, const PasswordCache *cache) { - _cleanup_(erase_and_freep) void *volume_key = NULL; + _cleanup_(iovec_done_erase) struct iovec volume_key = {}; struct fscrypt_policy policy = {}; - size_t volume_key_size = 0; const char *ip; int r; @@ -402,8 +616,7 @@ int home_setup_fscrypt( cache, h->password, setup, - &volume_key, - &volume_key_size); + &volume_key); if (r < 0) return r; @@ -427,8 +640,8 @@ int home_setup_fscrypt( r = fscrypt_upload_volume_key( setup->fscrypt_key_descriptor, - volume_key, - volume_key_size, + volume_key.iov_base, + volume_key.iov_len, KEY_SPEC_USER_KEYRING); if (r < 0) _exit(EXIT_FAILURE); @@ -474,68 +687,99 @@ static int fscrypt_slot_set( const char *password, uint32_t nr) { - _cleanup_free_ char *salt_base64 = NULL, *encrypted_base64 = NULL, *joined = NULL; + _cleanup_free_ char *salt_base64 = NULL, *iv_base64 = NULL, + *encrypted_base64 = NULL, *tag_base64 = NULL, + *joined = NULL; char label[STRLEN("trusted.fscrypt_slot") + DECIMAL_STR_MAX(nr) + 1]; _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL; - int r, encrypted_size_out1, encrypted_size_out2; - uint8_t salt[64], derived[512 / 8] = {}; + int r, encrypted_size_out1 = 0, encrypted_size_out2 = 0; + uint8_t salt[FSCRYPT_SLOT_SALT_SIZE], iv[FSCRYPT_SLOT_GCM_IV_SIZE], + tag[FSCRYPT_SLOT_GCM_TAG_SIZE], derived[512 / 8] = {}; _cleanup_free_ void *encrypted = NULL; const EVP_CIPHER *cc; size_t encrypted_size; ssize_t ss; + r = DLOPEN_LIBCRYPTO(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + 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"); + r = crypto_random_bytes(iv, sizeof(iv)); + if (r < 0) + return log_error_errno(r, "Failed to generate IV: %m"); + CLEANUP_ERASE(derived); - if (PKCS5_PBKDF2_HMAC( + if (sym_PKCS5_PBKDF2_HMAC( password, strlen(password), salt, sizeof(salt), - 0xFFFF, EVP_sha512(), + (int) FSCRYPT_SLOT_PBKDF2_ITERATIONS, 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(); + /* AES-256-GCM: authenticated encryption with explicit random IV */ + assert_se(cc = sym_EVP_aes_256_gcm()); - /* We only use the first half of the derived key */ - assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc)); + /* We only use the first 256 bit of the derived key */ + 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, /* impl= */ NULL, /* key= */ NULL, /* iv= */ 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; + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, (int) sizeof(iv), /* ptr= */ NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set GCM IV length."); + + if (sym_EVP_EncryptInit_ex(context, /* type= */ NULL, /* impl= */ NULL, derived, iv) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set encryption key/IV."); + + if (!ADD_SAFE(&encrypted_size, volume_key_size, (size_t) sym_EVP_CIPHER_get_block_size(cc))) + return log_error_errno(SYNTHETIC_ERRNO(EOVERFLOW), "Encrypted buffer size would overflow."); + 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); + assert((size_t) encrypted_size_out1 + (size_t) encrypted_size_out2 <= encrypted_size); encrypted_size = (size_t) encrypted_size_out1 + (size_t) encrypted_size_out2; + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, (int) sizeof(tag), tag) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to retrieve GCM tag."); + ss = base64mem(salt, sizeof(salt), &salt_base64); if (ss < 0) return log_oom(); + ss = base64mem(iv, sizeof(iv), &iv_base64); + if (ss < 0) + return log_oom(); + ss = base64mem(encrypted, encrypted_size, &encrypted_base64); if (ss < 0) return log_oom(); - joined = strjoin(salt_base64, ":", encrypted_base64); - if (!joined) + ss = base64mem(tag, sizeof(tag), &tag_base64); + if (ss < 0) + return log_oom(); + + if (asprintf(&joined, "$v2:%" PRIu32 ":%s:%s:%s:%s", + FSCRYPT_SLOT_PBKDF2_ITERATIONS, + salt_base64, iv_base64, encrypted_base64, tag_base64) < 0) return log_oom(); xsprintf(label, "trusted.fscrypt_slot%" PRIu32, nr); @@ -569,6 +813,10 @@ int home_create_fscrypt( assert(setup); assert(ret_home); + r = DLOPEN_LIBCRYPTO(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + if (r < 0) + return r; + assert_se(ip = user_record_image_path(h)); r = tempfn_random(ip, "homework", &d); @@ -706,9 +954,8 @@ int home_passwd_fscrypt( const PasswordCache *cache, /* the passwords acquired via PKCS#11/FIDO2 security tokens */ char **effective_passwords /* new passwords */) { - _cleanup_(erase_and_freep) void *volume_key = NULL; + _cleanup_(iovec_done_erase) struct iovec volume_key = {}; _cleanup_free_ char *xattr_buf = NULL; - size_t volume_key_size = 0; uint32_t slot = 0; int r; @@ -720,13 +967,12 @@ int home_passwd_fscrypt( cache, h->password, setup, - &volume_key, - &volume_key_size); + &volume_key); if (r < 0) return r; STRV_FOREACH(p, effective_passwords) { - r = fscrypt_slot_set(setup->root_fd, volume_key, volume_key_size, *p, slot); + r = fscrypt_slot_set(setup->root_fd, volume_key.iov_base, volume_key.iov_len, *p, slot); if (r < 0) return r; diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 9f7af06a4e780..4a56551b9cf47 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); if (r < 0) return r; @@ -633,7 +631,7 @@ static int fs_validate( sd_id128_t *ret_found_uuid) { _cleanup_free_ char *fstype = NULL; - sd_id128_t u = SD_ID128_NULL; /* avoid false maybe-unitialized warning */ + sd_id128_t u = SD_ID128_NULL; /* avoid false maybe-uninitialized warning */ int r; assert(dm_node); @@ -686,7 +684,7 @@ static int luks_validate( assert(ret_size); assert(sector_size > 0); - r = dlopen_libblkid(); + r = DLOPEN_LIBBLKID(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + if (r < 0) + return r; + + r = DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + if (r < 0) + return r; + + r = DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); if (r < 0) return r; diff --git a/src/home/homework-pkcs11.c b/src/home/homework-pkcs11.c index 626473d2c9111..44607e4b5f791 100644 --- a/src/home/homework-pkcs11.c +++ b/src/home/homework-pkcs11.c @@ -100,7 +100,7 @@ int pkcs11_callback( if (r < 0) return r; - r = pkcs11_token_decrypt_data(m, session, object, data->encrypted_key->data, data->encrypted_key->size, &decrypted_key, &decrypted_key_size); + r = pkcs11_token_decrypt_data(m, session, object, data->encrypted_key->data, data->encrypted_key->size, data->encrypted_key->padding, &decrypted_key, &decrypted_key_size); if (r < 0) return r; diff --git a/src/home/homework.c b/src/home/homework.c index e796f125fb56e..a5fca5a11884d 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -19,6 +19,7 @@ #include "fileio.h" #include "filesystems.h" #include "format-util.h" +#include "fs-util.h" #include "hashmap.h" #include "home-util.h" #include "homework-fido2.h" @@ -551,27 +552,23 @@ int home_sync_and_statfs(int root_fd, struct statfs *ret) { } static int read_identity_file(int root_fd, sd_json_variant **ret) { - _cleanup_fclose_ FILE *identity_file = NULL; - _cleanup_close_ int identity_fd = -EBADF; int r; assert(root_fd >= 0); assert(ret); - identity_fd = openat(root_fd, ".identity", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK); + _cleanup_close_ int identity_fd = xopenat_full(root_fd, ".identity", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK, XO_REGULAR, MODE_INVALID); if (identity_fd < 0) - return log_error_errno(errno, "Failed to open .identity file in home directory: %m"); - - r = fd_verify_regular(identity_fd); - if (r < 0) - return log_error_errno(r, "Embedded identity file is not a regular file, refusing: %m"); - - identity_file = take_fdopen(&identity_fd, "r"); - if (!identity_file) - return log_oom(); + return log_error_errno(identity_fd, "Failed to open .identity file in home directory: %m"); unsigned line = 0, column = 0; - r = sd_json_parse_file(identity_file, ".identity", SD_JSON_PARSE_SENSITIVE, ret, &line, &column); + r = sd_json_parse_fd( + ".identity", + TAKE_FD(identity_fd), + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_SENSITIVE|SD_JSON_PARSE_DONATE_FD, + ret, + &line, + &column); if (r < 0) return log_error_errno(r, "[.identity:%u:%u] Failed to parse JSON data: %m", line, column); @@ -915,6 +912,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 +1328,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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); if (r < 0) log_info("Not using '%s' storage, since libcryptsetup could not be loaded.", user_storage_to_string(USER_LUKS)); else { @@ -2004,8 +2002,6 @@ static int run(int argc, char *argv[]) { log_setup(); - cryptsetup_enable_logging(NULL); - umask(0022); if (argc < 2 || argc > 3) @@ -2025,7 +2021,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..b724517ae9ce9 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -51,6 +51,10 @@ homectl_sources = files( 'homectl.c', ) +homectl_extract = files( + 'homectl-prompts.c', +) + pam_systemd_home_sources = files( 'home-util.c', 'pam_systemd_home.c', @@ -63,40 +67,35 @@ executables += [ 'dbus' : true, 'sources' : systemd_homed_sources, 'extract' : systemd_homed_extract_sources, - 'dependencies' : [ - libm, - libopenssl, - threads, - ], + 'dependencies' : libopenssl_cflags, }, libexec_template + { '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, ], }, executable_template + { 'name' : 'homectl', 'public' : true, 'sources' : homectl_sources, + 'extract' : homectl_extract, 'objects' : ['systemd-homed'], 'dependencies' : [ - libdl, - libopenssl, + libopenssl_cflags, libp11kit_cflags, - threads, ], }, + test_template + { + 'sources' : files('test-homectl-prompts.c'), + 'objects' : ['homectl'], + 'type' : 'manual', + }, test_template + { 'sources' : files('test-homed-regression-31896.c'), 'type' : 'manual', @@ -108,12 +107,6 @@ modules += [ 'name' : 'pam_systemd_home', 'conditions' : ['HAVE_PAM'], 'sources' : pam_systemd_home_sources, - 'dependencies' : [ - libintl, - libpam_misc, - libpam, - threads, - ], 'version-script' : meson.current_source_dir() / 'pam_systemd_home.sym', }, ] diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index c58a3433760be..abb75b9d92839 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -1,8 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include -#include - #include "sd-bus.h" #include "alloc-util.h" @@ -45,7 +42,7 @@ static int parse_argv( k = parse_boolean(v); if (k < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse suspend= argument, ignoring: %s", v); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse suspend= argument, ignoring: %s", v); else if (flags) SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, k); @@ -57,12 +54,12 @@ static int parse_argv( int k; k = parse_boolean(v); if (k < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", v); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", v); else if (debug) *debug = k; } else - pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); + sym_pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); } return 0; @@ -76,7 +73,7 @@ static int parse_env(pam_handle_t *pamh, AcquireHomeFlags *flags) { * easy to declare the features of a display manager in code rather than configuration, and this is * really a feature of code */ - v = pam_getenv(pamh, "SYSTEMD_HOME_SUSPEND"); + v = sym_pam_getenv(pamh, "SYSTEMD_HOME_SUSPEND"); if (!v) { /* Also check the process env block, so that people can control this via an env var from the * outside of our process. */ @@ -87,7 +84,7 @@ static int parse_env(pam_handle_t *pamh, AcquireHomeFlags *flags) { r = parse_boolean(v); if (r < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse $SYSTEMD_HOME_SUSPEND argument, ignoring: %s", v); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse $SYSTEMD_HOME_SUSPEND argument, ignoring: %s", v); else if (flags) SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, r); @@ -106,7 +103,7 @@ static int acquire_user_record( assert(pamh); if (!username) { - r = pam_get_user(pamh, &username, NULL); + r = sym_pam_get_user(pamh, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get user name: @PAMERR@"); if (isempty(username)) @@ -149,7 +146,7 @@ static int acquire_user_record( return pam_log_oom(pamh); /* Let's use the cache, so that we can share it between the session and the authentication hooks */ - r = pam_get_data(pamh, homed_field, (const void**) &json); + r = sym_pam_get_data(pamh, homed_field, (const void**) &json); if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get PAM user record data: @PAMERR@"); if (r == PAM_SUCCESS && json) { @@ -194,7 +191,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"); @@ -211,13 +208,13 @@ static int acquire_user_record( return pam_syslog_pam_error(pamh, LOG_ERR, PAM_SERVICE_ERR, "Acquired user record does not match user name."); - /* Update the 'username' pointer to point to our own record now. The pam_set_item() call below is + /* Update the 'username' pointer to point to our own record now. The sym_pam_set_item() call below is * going to invalidate the old version after all */ username = ur->user_name; /* We passed all checks. Let's now make sure the rest of the PAM stack continues with the primary, * normalized name of the user record (i.e. not an alias or so). */ - r = pam_set_item(pamh, PAM_USER, ur->user_name); + r = sym_pam_set_item(pamh, PAM_USER, ur->user_name); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to update username PAM item to '%s': @PAMERR@", ur->user_name); @@ -230,7 +227,7 @@ static int acquire_user_record( if (!json_copy) return pam_log_oom(pamh); - r = pam_set_data(pamh, homed_field, json_copy, pam_cleanup_free); + r = sym_pam_set_data(pamh, homed_field, json_copy, pam_cleanup_free); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM user record data '%s': @PAMERR@", homed_field); @@ -246,7 +243,7 @@ static int acquire_user_record( if (!generic_field) return pam_log_oom(pamh); - r = pam_set_data(pamh, generic_field, json_copy, pam_cleanup_free); + r = sym_pam_set_data(pamh, generic_field, json_copy, pam_cleanup_free); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM user record data '%s': @PAMERR@", generic_field); @@ -256,7 +253,7 @@ static int acquire_user_record( /* Let's store the area we parsed out of the name in an env var, so that pam_systemd later can honour it. */ if (area) { - r = pam_misc_setenv(pamh, "XDG_AREA", area, /* readonly= */ 0); + r = pam_putenv_assign(pamh, "XDG_AREA", area); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set environment variable $XDG_AREA to '%s': @PAMERR@", area); @@ -269,7 +266,7 @@ static int acquire_user_record( user_unknown: /* Cache this, so that we don't check again */ - r = pam_set_data(pamh, homed_field, POINTER_MAX, NULL); + r = sym_pam_set_data(pamh, homed_field, POINTER_MAX, NULL); if (r != PAM_SUCCESS) pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM user record data '%s' to invalid, ignoring: @PAMERR@", @@ -289,7 +286,7 @@ static int release_user_record(pam_handle_t *pamh, const char *username) { if (!homed_field) return pam_log_oom(pamh); - r = pam_set_data(pamh, homed_field, NULL, NULL); + r = sym_pam_set_data(pamh, homed_field, NULL, NULL); if (r != PAM_SUCCESS) pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to release PAM user record data '%s': @PAMERR@", homed_field); @@ -298,7 +295,7 @@ static int release_user_record(pam_handle_t *pamh, const char *username) { if (!generic_field) return pam_log_oom(pamh); - k = pam_set_data(pamh, generic_field, NULL, NULL); + k = sym_pam_set_data(pamh, generic_field, NULL, NULL); if (k != PAM_SUCCESS) pam_syslog_pam_error(pamh, LOG_ERR, k, "Failed to release PAM user record data '%s': @PAMERR@", generic_field); @@ -569,7 +566,7 @@ static int acquire_home( * prompt the user for the missing unlock credentials, and then chainload the real shell. */ - r = pam_get_user(pamh, &username, NULL); + r = sym_pam_get_user(pamh, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get user name: @PAMERR@"); if (isempty(username)) @@ -580,7 +577,7 @@ static int acquire_home( if (!fd_field) return pam_log_oom(pamh); - r = pam_get_data(pamh, fd_field, &home_fd_ptr); + r = sym_pam_get_data(pamh, fd_field, &home_fd_ptr); if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to retrieve PAM home reference fd: @PAMERR@"); @@ -618,7 +615,7 @@ static int acquire_home( /* If there's already a cached password, use it. But if not let's authenticate * without anything, maybe some other authentication mechanism systemd-homed * implements (such as PKCS#11) allows us to authenticate without anything else. */ - r = pam_get_item(pamh, PAM_AUTHTOK, (const void**) &cached_password); + r = sym_pam_get_item(pamh, PAM_AUTHTOK, (const void**) &cached_password); if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get cached password: @PAMERR@"); @@ -681,7 +678,7 @@ static int acquire_home( (void) pam_prompt_graceful(pamh, PAM_ERROR_MSG, NULL, _("Home of user %s is currently locked, please unlock locally first."), ur->user_name); if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE)) - pam_syslog(pamh, LOG_ERR, "Failed to prompt for password/prompt."); + sym_pam_syslog(pamh, LOG_ERR, "Failed to prompt for password/prompt."); else pam_debug_syslog(pamh, debug, "Failed to prompt for password/prompt."); @@ -720,12 +717,12 @@ static int acquire_home( /* Later PAM modules may need the auth token, but only during pam_authenticate. */ if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) && !strv_isempty(secret->password)) { - r = pam_set_item(pamh, PAM_AUTHTOK, *secret->password); + r = sym_pam_set_item(pamh, PAM_AUTHTOK, *secret->password); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM auth token: @PAMERR@"); } - r = pam_set_data(pamh, fd_field, FD_TO_PTR(acquired_fd), cleanup_home_fd); + r = sym_pam_set_data(pamh, fd_field, FD_TO_PTR(acquired_fd), cleanup_home_fd); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM bus data: @PAMERR@"); TAKE_FD(acquired_fd); @@ -744,13 +741,13 @@ static int acquire_home( * manager for us (since it would see an inaccessible home directory). Hence set an environment * variable that pam_systemd looks for). */ if (unrestricted) { - r = pam_putenv(pamh, "XDG_SESSION_INCOMPLETE=1"); + r = sym_pam_putenv(pamh, "XDG_SESSION_INCOMPLETE=1"); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_WARNING, r, "Failed to set XDG_SESSION_INCOMPLETE= environment variable: @PAMERR@"); - pam_syslog(pamh, LOG_NOTICE, "Home for user %s acquired in incomplete mode, requires later activation.", ur->user_name); + sym_pam_syslog(pamh, LOG_NOTICE, "Home for user %s acquired in incomplete mode, requires later activation.", ur->user_name); } else - pam_syslog(pamh, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name); + sym_pam_syslog(pamh, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name); return PAM_SUCCESS; } @@ -767,13 +764,13 @@ static int release_home_fd(pam_handle_t *pamh, const char *username) { if (!fd_field) return pam_log_oom(pamh); - r = pam_get_data(pamh, fd_field, &home_fd_ptr); + r = sym_pam_get_data(pamh, fd_field, &home_fd_ptr); if (r == PAM_NO_MODULE_DATA || (r == PAM_SUCCESS && PTR_TO_FD(home_fd_ptr) < 0)) return PAM_NO_MODULE_DATA; if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to retrieve PAM home reference fd: @PAMERR@"); - r = pam_set_data(pamh, fd_field, NULL, NULL); + r = sym_pam_set_data(pamh, fd_field, NULL, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to release PAM home reference fd: @PAMERR@"); @@ -789,10 +786,12 @@ _public_ PAM_EXTERN int pam_sm_authenticate( bool debug = false; int r; - r = dlopen_libpam(); + r = DLOPEN_LIBPAM(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); if (r < 0) return PAM_SERVICE_ERR; + (void) DLOPEN_LIBINTL(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_env(pamh, &flags) < 0) @@ -854,10 +853,12 @@ _public_ PAM_EXTERN int pam_sm_open_session( bool debug = false; int r; - r = dlopen_libpam(); + r = DLOPEN_LIBPAM(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); if (r < 0) return PAM_SERVICE_ERR; + (void) DLOPEN_LIBINTL(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_env(pamh, &flags) < 0) @@ -887,12 +888,12 @@ _public_ PAM_EXTERN int pam_sm_open_session( if (r != PAM_SUCCESS) return r; - r = pam_putenv(pamh, "SYSTEMD_HOME=1"); + r = sym_pam_putenv(pamh, "SYSTEMD_HOME=1"); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM environment variable $SYSTEMD_HOME: @PAMERR@"); - r = pam_putenv(pamh, FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND) ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0"); + r = sym_pam_putenv(pamh, FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND) ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0"); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM environment variable $SYSTEMD_HOME_SUSPEND: @PAMERR@"); @@ -911,6 +912,12 @@ _public_ PAM_EXTERN int pam_sm_close_session( bool debug = false; int r; + r = DLOPEN_LIBPAM(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return PAM_SERVICE_ERR; + + (void) DLOPEN_LIBINTL(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_argv(pamh, @@ -921,7 +928,7 @@ _public_ PAM_EXTERN int pam_sm_close_session( pam_debug_syslog(pamh, debug, "pam-systemd-homed: closing session..."); - r = pam_get_user(pamh, &username, NULL); + r = sym_pam_get_user(pamh, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get user name: @PAMERR@"); if (isempty(username)) @@ -954,7 +961,7 @@ _public_ PAM_EXTERN int pam_sm_close_session( return pam_syslog_pam_error(pamh, LOG_ERR, PAM_SESSION_ERR, "Failed to release user home: %s", bus_error_message(&error, r)); - pam_syslog(pamh, LOG_NOTICE, "Not deactivating home directory of %s, as it is still used.", username); + sym_pam_syslog(pamh, LOG_NOTICE, "Not deactivating home directory of %s, as it is still used.", username); } return PAM_SUCCESS; @@ -972,10 +979,12 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( usec_t t; int r; - r = dlopen_libpam(); + r = DLOPEN_LIBPAM(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); if (r < 0) return PAM_SERVICE_ERR; + (void) DLOPEN_LIBINTL(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_env(pamh, &flags) < 0) @@ -1005,7 +1014,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( switch (r) { case -ESTALE: - pam_syslog(pamh, LOG_WARNING, "User record for '%s' is newer than current system time, assuming incorrect system clock, allowing access.", ur->user_name); + sym_pam_syslog(pamh, LOG_WARNING, "User record for '%s' is newer than current system time, assuming incorrect system clock, allowing access.", ur->user_name); break; case -ENOLCK: @@ -1062,7 +1071,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( case -ESTALE: /* If the system clock is wrong, let's log but continue */ - pam_syslog(pamh, LOG_WARNING, "Couldn't check if password change is required, last change is in the future, system clock likely wrong."); + sym_pam_syslog(pamh, LOG_WARNING, "Couldn't check if password change is required, last change is in the future, system clock likely wrong."); break; case -EROFS: @@ -1091,10 +1100,12 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( bool debug = false; int r; - r = dlopen_libpam(); + r = DLOPEN_LIBPAM(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); if (r < 0) return PAM_SERVICE_ERR; + (void) DLOPEN_LIBINTL(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); /* best-effort: messages won't be translated if this fails */ + pam_log_setup(); if (parse_argv(pamh, @@ -1121,7 +1132,7 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( /* No, it's not cached, then let's ask for the password and its verification, and cache * it. */ - r = pam_get_authtok_noverify(pamh, &new_password, "New password: "); + r = sym_pam_get_authtok_noverify(pamh, &new_password, "New password: "); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get new password: @PAMERR@"); @@ -1130,7 +1141,7 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( return PAM_AUTHTOK_ERR; } - r = pam_get_authtok_verify(pamh, &new_password, "new password: "); /* Lower case, since PAM prefixes 'Repeat' */ + r = sym_pam_get_authtok_verify(pamh, &new_password, "new password: "); /* Lower case, since PAM prefixes 'Repeat' */ if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get password again: @PAMERR@"); diff --git a/src/home/test-homectl-prompts.c b/src/home/test-homectl-prompts.c new file mode 100644 index 0000000000000..59bff32c44387 --- /dev/null +++ b/src/home/test-homectl-prompts.c @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "format-table.h" +#include "help-util.h" +#include "homectl-prompts.h" +#include "main-func.h" +#include "options.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "verbs.h" + +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int r; + + 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); + + help_cmdline("[OPTIONS...] VERB [USERNAME]"); + help_abstract("Exercise homectl prompt functions in isolation."); + + help_section("Verbs"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + return table_print_or_warn(options); +} + +VERB(verb_groups, "groups", "[USER]", VERB_ANY, 2, 0, "Select groups"); +static int verb_groups(int argc, char *argv[], uintptr_t _data, void *userdata) { + assert(argv); + + const char *username = argv[1] ?: "test"; + int r; + + _cleanup_strv_free_ char **t = NULL; + + r = prompt_groups(username, &t); + if (r < 0) + return r; + + _cleanup_free_ char *s = ASSERT_PTR(strv_join(t, ", ")); + log_info("groups: %s → %s", username, s); + return 0; +} + +VERB(verb_shell, "shell", "[USER]", VERB_ANY, 2, 0, "Select shell"); +static int verb_shell(int argc, char *argv[], uintptr_t _data, void *userdata) { + assert(argv); + + const char *username = argv[1] ?: "test"; + int r; + + _cleanup_free_ char *s = NULL; + + r = prompt_shell(username, &s); + if (r < 0) + return r; + + log_info("shell: %s → %s", username, strnull(s)); + return 0; +} + +static int parse_argv(int argc, char **argv, char ***remaining_args) { + assert(argc >= 0); + assert(argv); + assert(remaining_args); + + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + } + + *remaining_args = option_parser_get_args(&opts); + return 1; +} + +static int run(int argc, char **argv) { + int r; + + test_setup_logging(LOG_DEBUG); + + char **args = NULL; + r = parse_argv(argc, argv, &args); + if (r <= 0) + return r; + + return dispatch_verb(args, /* userdata= */ NULL); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/home/user-record-sign.c b/src/home/user-record-sign.c index 7a80ef1e7ab91..c51349d949f10 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,19 @@ 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) - return -EIO; + if (sym_EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL, public_key) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize signature verification"); - 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) { + /* A bad signature is an expected outcome here (counted as n_bad), but it may leave + * entries in the thread-local OpenSSL error queue. Clear them so a later iteration's + * failure — or an unrelated caller on this thread — translates its own error rather + * than this stale one. */ + sym_ERR_clear_error(); 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..b419a85b6842d 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) @@ -375,8 +376,7 @@ int user_record_add_binding( if (r < 0) return r; - sd_json_variant_unref(new_binding_entry); - new_binding_entry = TAKE_PTR(be); + json_variant_unref_and_replace(new_binding_entry, be); } } @@ -786,12 +786,8 @@ int user_record_update_last_changed(UserRecord *h, bool with_password) { } h->last_change_usec = n; - - sd_json_variant_unref(h->json); - h->json = TAKE_PTR(v); - h->mask |= USER_RECORD_REGULAR; - return 0; + return json_variant_unref_and_replace(h->json, v); } int user_record_make_hashed_password(UserRecord *h, char **secret, bool extend) { @@ -1130,12 +1126,8 @@ int user_record_set_password_change_now(UserRecord *h, int b) { SET_FLAG(h->mask, USER_RECORD_PER_MACHINE, !sd_json_variant_is_blank_array(array)); } - sd_json_variant_unref(h->json); - h->json = TAKE_PTR(w); - h->password_change_now = b; - - return 0; + return json_variant_unref_and_replace(h->json, w); } int user_record_merge_secret(UserRecord *h, UserRecord *secret) { @@ -1226,14 +1218,10 @@ int user_record_good_authentication(UserRecord *h) { if (r < 0) return r; - sd_json_variant_unref(h->json); - h->json = TAKE_PTR(v); - h->good_authentication_counter = counter; h->last_good_authentication_usec = usec; - h->mask |= USER_RECORD_STATUS; - return 0; + return json_variant_unref_and_replace(h->json, v); } int user_record_bad_authentication(UserRecord *h) { @@ -1281,14 +1269,10 @@ int user_record_bad_authentication(UserRecord *h) { if (r < 0) return r; - sd_json_variant_unref(h->json); - h->json = TAKE_PTR(v); - h->bad_authentication_counter = counter; h->last_bad_authentication_usec = usec; - h->mask |= USER_RECORD_STATUS; - return 0; + return json_variant_unref_and_replace(h->json, v); } int user_record_ratelimit(UserRecord *h) { @@ -1343,13 +1327,10 @@ int user_record_ratelimit(UserRecord *h) { if (r < 0) return r; - sd_json_variant_unref(h->json); - h->json = TAKE_PTR(v); - h->ratelimit_begin_usec = new_ratelimit_begin_usec; h->ratelimit_count = new_ratelimit_count; - h->mask |= USER_RECORD_STATUS; + json_variant_unref_and_replace(h->json, v); return 1; } diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index ae1af41425703..a6ecad517809a 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 @@ -10,6 +9,7 @@ #include "sd-json.h" #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "bus-common-errors.h" #include "bus-error.h" @@ -19,17 +19,19 @@ #include "bus-util.h" #include "errno-util.h" #include "format-table.h" +#include "help-util.h" #include "hostname-setup.h" #include "hostname-util.h" #include "log.h" #include "main-func.h" +#include "options.h" +#include "os-util.h" #include "parse-argument.h" #include "polkit-agent.h" -#include "pretty-print.h" #include "runtime-scope.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" -#include "utf8.h" #include "verbs.h" static bool arg_ask_password = true; @@ -49,6 +51,7 @@ typedef struct StatusInfo { const char *chassis_asset_tag; const char *deployment; const char *location; + char **tags; const char *kernel_name; const char *kernel_release; const char *os_pretty_name; @@ -196,6 +199,18 @@ static int print_status_info(StatusInfo *i) { return table_log_add_error(r); } + if (!strv_isempty(i->tags)) { + _cleanup_free_ char *j = strv_join(i->tags, ":"); + if (!j) + return log_oom(); + + r = table_add_many(table, + TABLE_FIELD, "Tags", + TABLE_STRING, j); + if (r < 0) + return table_log_add_error(r); + } + if (!sd_id128_is_null(i->machine_id)) { r = table_add_many(table, TABLE_FIELD, "Machine ID", @@ -236,7 +251,7 @@ static int print_status_info(StatusInfo *i) { return table_log_add_error(r); } - if (!isempty(i->os_fancy_name) && (emoji_enabled() || ascii_is_valid(i->os_fancy_name)) && colors_enabled()) { + if (use_fancy_name(i->os_fancy_name)) { r = table_add_many(table, TABLE_FIELD, "Operating System", TABLE_STRING_WITH_ANSI, i->os_fancy_name, @@ -375,11 +390,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) { @@ -415,8 +426,14 @@ static int get_one_name(sd_bus *bus, const char* attr, char **ret) { return 0; } +static void status_info_done(StatusInfo *info) { + assert(info); + + info->tags = strv_free(info->tags); +} + static int show_all_names(sd_bus *bus) { - StatusInfo info = { + _cleanup_(status_info_done) StatusInfo info = { .vsock_cid = VMADDR_CID_ANY, .os_support_end = USEC_INFINITY, .firmware_date = USEC_INFINITY, @@ -431,6 +448,7 @@ static int show_all_names(sd_bus *bus) { { "ChassisAssetTag", "s", NULL, offsetof(StatusInfo, chassis_asset_tag)}, { "Deployment", "s", NULL, offsetof(StatusInfo, deployment) }, { "Location", "s", NULL, offsetof(StatusInfo, location) }, + { "Tags", "as", NULL, offsetof(StatusInfo, tags) }, { "KernelName", "s", NULL, offsetof(StatusInfo, kernel_name) }, { "KernelRelease", "s", NULL, offsetof(StatusInfo, kernel_release) }, { "OperatingSystemPrettyName", "s", NULL, offsetof(StatusInfo, os_pretty_name) }, @@ -549,7 +567,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_DEFAULT_NOARG(verb_show_status, "status", "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 +586,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 +620,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 +706,249 @@ 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; +VERB(verb_get_or_set_tags, "tags", "[TAG …]", VERB_ANY, VERB_ANY, 0, "Get/set machine tags for host"); +static int verb_get_or_set_tags(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; - r = terminal_urlify_man("hostnamectl", "1", &link); - if (r < 0) - return log_oom(); + if (argc == 1) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + + _cleanup_free_ char *j = NULL; + r = bus_get_property(bus, bus_hostname, "Tags", &error, &reply, "as"); + if (r < 0) { + if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY)) + return log_error_errno(r, "Could not get property: %s", bus_error_message(&error, r)); + + /* Old hostnamed didn't know the tags concept, hence such a machine has no tags. */ + } else { + _cleanup_strv_free_ char **l = NULL; + r = sd_bus_message_read_strv(reply, &l); + if (r < 0) + return bus_log_parse_error(r); + + j = strv_join(l, ":"); + if (!j) + return log_oom(); + } + + printf("%s\n", strempty(j)); + return 0; + } + + _cleanup_strv_free_ char **plain = NULL, **add = NULL, **remove = NULL; + bool prefixed = false, unprefixed = false; + for (int i = 1; i < argc; i++) { + const char *e = argv[i]; + char ***dest; + + /* The first character of each argument selects the mode for all (colon-separated) tags in + * that argument: a leading '+' adds them, a leading '-' removes them, and otherwise the + * argument lists tags to set verbatim. Setting and adding/removing may not be mixed. */ + if (e[0] == '+') { + dest = &add; + e++; + prefixed = true; + } else if (e[0] == '-') { + dest = &remove; + e++; + prefixed = true; + } else { + dest = &plain; + unprefixed = true; + } + + if (isempty(e)) { + if (dest == &plain && !*dest) { + *dest = new0(char*, 1); + if (!*dest) + return log_oom(); + } + } else if (strv_split_and_extend(dest, e, ":", /* filter_duplicates= */ true) < 0) + return log_oom(); + } + + if (prefixed && unprefixed) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Plain tags may not be combined with '+'/'-' prefixed tags."); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + if (!prefixed) { + /* No prefixes used: replace the whole tag list with the given tags (possibly empty, which + * clears all tags). */ + strv_sort(plain); + + r = bus_message_new_method_call(bus, &m, bus_hostname, "SetTags"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, plain); + if (r < 0) + return bus_log_create_error(r); + } else { + /* Prefixes used: incrementally add and/or remove tags. */ + strv_sort(add); + strv_sort(remove); + + r = bus_message_new_method_call(bus, &m, bus_hostname, "AddAndRemoveTags"); + if (r < 0) + return bus_log_create_error(r); - 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", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - ansi_underline(), - ansi_normal(), - link); + r = sd_bus_message_append_strv(m, add); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, remove); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_call(bus, m, /* usec= */ 0, &error, /* ret_reply= */ NULL); + if (r < 0) + return log_error_errno(r, "Could not set tags: %s", bus_error_message(&error, r)); return 0; } -static int verb_help(int argc, char **argv, void *userdata) { - return help(); -} +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; + int 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_ASK_PASSWORD, - ARG_TRANSIENT, - ARG_STATIC, - ARG_PRETTY, - ARG_JSON, - }; + 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 }, - { "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 }, - {} - }; + (void) table_sync_column_widths(0, options, verbs); - int c, r; + help_cmdline("[OPTIONS...] COMMAND ..."); + help_abstract("Query or change system hostname."); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("hostnamectl", "1"); + return 0; +} + +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, "hH:M:j", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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 = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.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(opts.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(&opts); 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 +956,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(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index a567221f8ea0b..99de3f23f6f48 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -17,12 +17,13 @@ #include "bus-object.h" #include "bus-polkit.h" #include "bus-util.h" +#include "common-signal.h" #include "constants.h" #include "daemon-util.h" #include "device-private.h" +#include "device-util.h" #include "env-file.h" #include "env-util.h" -#include "escape.h" #include "extract-word.h" #include "fileio.h" #include "hashmap.h" @@ -36,20 +37,18 @@ #include "nulstr-util.h" #include "os-util.h" #include "parse-util.h" -#include "path-util.h" #include "service-util.h" -#include "socket-util.h" #include "stat-util.h" #include "string-util.h" #include "strv.h" #include "time-util.h" -#include "utf8.h" #include "varlink-io.systemd.Hostname.h" #include "varlink-io.systemd.service.h" #include "varlink-util.h" #include "virt.h" +#include "vsock-util.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). */ @@ -58,12 +57,15 @@ typedef enum { PROP_STATIC_HOSTNAME, PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS, - /* Read from /etc/machine-info */ + /* Read from /etc/machine-info (with fallbacks) */ PROP_PRETTY_HOSTNAME, + _PROP_MACHINE_INFO_SETTABLE_FIRST = PROP_PRETTY_HOSTNAME, PROP_ICON_NAME, PROP_CHASSIS, PROP_DEPLOYMENT, PROP_LOCATION, + PROP_TAGS, + _PROP_MACHINE_INFO_SETTABLE_LAST = PROP_TAGS, PROP_HARDWARE_VENDOR, PROP_HARDWARE_MODEL, PROP_HARDWARE_SKU, @@ -77,6 +79,7 @@ typedef enum { PROP_OS_SUPPORT_END, PROP_OS_IMAGE_ID, PROP_OS_IMAGE_VERSION, + _PROP_MAX, _PROP_INVALID = -EINVAL, } HostProperty; @@ -144,13 +147,13 @@ static void context_read_etc_hostname(Context *c) { if (r != -ENOENT) log_warning_errno(r, "Failed to read /etc/hostname, ignoring: %m"); } else { - _cleanup_free_ char *substituted = strdup(c->data[PROP_STATIC_HOSTNAME]); - if (!substituted) - return (void) log_oom(); + _cleanup_free_ char *substituted = NULL; - r = hostname_substitute_wildcards(substituted); + r = hostname_substitute_wildcards(c->data[PROP_STATIC_HOSTNAME], &substituted); if (r < 0) log_warning_errno(r, "Failed to substitute wildcards in /etc/hostname, ignoring: %m"); + else if (!hostname_is_valid(substituted, VALID_HOSTNAME_TRAILING_DOT)) + log_warning("Hostname '%s' in /etc/hostname is invalid after expansion, ignoring.", substituted); else c->data[PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS] = TAKE_PTR(substituted); } @@ -173,6 +176,7 @@ static void context_read_machine_info(Context *c) { PROP_CHASSIS, PROP_DEPLOYMENT, PROP_LOCATION, + PROP_TAGS, PROP_HARDWARE_VENDOR, PROP_HARDWARE_MODEL, PROP_HARDWARE_SKU, @@ -184,6 +188,7 @@ static void context_read_machine_info(Context *c) { "CHASSIS", &c->data[PROP_CHASSIS], "DEPLOYMENT", &c->data[PROP_DEPLOYMENT], "LOCATION", &c->data[PROP_LOCATION], + "TAGS", &c->data[PROP_TAGS], "HARDWARE_VENDOR", &c->data[PROP_HARDWARE_VENDOR], "HARDWARE_MODEL", &c->data[PROP_HARDWARE_MODEL], "HARDWARE_SKU", &c->data[PROP_HARDWARE_SKU], @@ -230,20 +235,7 @@ static void context_read_os_release(Context *c) { if (free_and_strdup(&c->data[PROP_OS_PRETTY_NAME], os_release_pretty_name(os_pretty_name, os_name)) < 0) log_oom(); - if (!isempty(os_fancy_name)) { - _cleanup_free_ char *unescaped = NULL; - - /* We undo one level of C escapes on this */ - ssize_t l = cunescape(os_fancy_name, /* flags= */ 0, &unescaped); - if (l < 0) { - log_warning_errno(l, "Failed to unescape fancy OS name, ignoring: %m"); - os_fancy_name = mfree(os_fancy_name); - } else if (!utf8_is_valid(unescaped)) { - log_warning("Unescaped fancy OS name contains invalid UTF-8, ignoring."); - os_fancy_name = mfree(os_fancy_name); - } else - free_and_replace(os_fancy_name, unescaped); - } + unescape_fancy_name(&os_fancy_name); if (isempty(os_fancy_name)) { free(os_fancy_name); /* free if empty string */ @@ -334,18 +326,6 @@ static int context_acquire_device_tree(Context *c) { return 1; } -static bool string_is_safe_for_dbus(const char *s) { - assert(s); - - /* Do some superficial validation: do not allow CCs and make sure D-Bus won't kick us off the bus - * because we send invalid UTF-8 data */ - - if (string_has_cc(s, /* ok= */ NULL)) - return false; - - return utf8_is_valid(s); -} - static int get_dmi_property(Context *c, const char *key, char **ret) { const char *s; int r; @@ -361,7 +341,7 @@ static int get_dmi_property(Context *c, const char *key, char **ret) { if (r < 0) return r; - if (!string_is_safe_for_dbus(s)) + if (!string_is_safe(s, STRING_ALLOW_EMPTY|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS)) return -ENXIO; return strdup_to(ret, s); @@ -398,6 +378,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 +401,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; @@ -452,12 +436,14 @@ static int get_sysattr(sd_device *device, const char *key, char **ret) { if (!device) return -ENODEV; - r = sd_device_get_sysattr_value(device, key, &s); + r = device_get_sysattr_safe_string(device, key, &s); if (r < 0) - return r; + return log_device_debug_errno(device, r, "Failed to read '%s' attribute: %m", key); - if (!string_is_safe_for_dbus(s)) - return -ENXIO; + if (!string_is_safe(s, STRING_ALLOW_EMPTY|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS)) + return log_device_debug_errno(device, SYNTHETIC_ERRNO(ENXIO), + "'%s' attribute is not safe for exposing through DBus: %s", + key, s); return strdup_to(ret, empty_to_null(s)); } @@ -555,7 +541,7 @@ static int get_firmware_date(Context *c, usec_t *ret) { return 0; } -static const char* valid_chassis(const char *chassis) { +static const char* chassis_is_valid(const char *chassis) { assert(chassis); return nulstr_get( @@ -572,10 +558,29 @@ static const char* valid_chassis(const char *chassis) { chassis); } -static bool valid_deployment(const char *deployment) { +static bool deployment_is_valid(const char *deployment) { assert(deployment); - return in_charset(deployment, VALID_DEPLOYMENT_CHARS); + return !isempty(deployment) && + in_charset(deployment, VALID_DEPLOYMENT_CHARS); +} + +static bool pretty_hostname_is_valid(const char *pretty_hostname) { + assert(pretty_hostname); + + return string_is_safe(pretty_hostname, STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS); +} + +static bool icon_name_is_valid(const char *icon_name) { + assert(icon_name); + + return string_is_safe(icon_name, STRING_FILENAME); +} + +static bool location_is_valid(const char *location) { + assert(location); + + return string_is_safe(location, STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS); } static const char* fallback_chassis_by_virtualization(void) { @@ -713,7 +718,7 @@ static const char* fallback_chassis_by_device_tree(Context *c) { if (!c->device_tree) return NULL; - r = sd_device_get_sysattr_value(c->device_tree, "chassis-type", &type); + r = device_get_sysattr_safe_string(c->device_tree, "chassis-type", &type); if (r < 0) { log_debug_errno(r, "Failed to read device-tree chassis type, ignoring: %m"); return NULL; @@ -724,7 +729,7 @@ static const char* fallback_chassis_by_device_tree(Context *c) { * * https://github.com/devicetree-org/devicetree-specification/blob/master/source/chapter3-devicenodes.rst */ - chassis = valid_chassis(type); + chassis = chassis_is_valid(type); if (!chassis) log_debug("Invalid device-tree chassis type \"%s\", ignoring.", type); return chassis; @@ -822,6 +827,8 @@ static int context_update_kernel_hostname( } static void unset_statp(struct stat **p) { + assert(p); + if (!*p) return; @@ -840,7 +847,7 @@ static int context_write_data_static_hostname(Context *c) { if (isempty(c->data[PROP_STATIC_HOSTNAME])) { if (unlink(etc_hostname()) < 0 && errno != ENOENT) - return -errno; + return log_error_errno(errno, "Failed to remove '%s': %m", etc_hostname()); TAKE_PTR(s); return 0; @@ -848,7 +855,7 @@ static int context_write_data_static_hostname(Context *c) { r = write_string_file(etc_hostname(), c->data[PROP_STATIC_HOSTNAME], WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL); if (r < 0) - return r; + return log_error_errno(r, "Failed to write '%s': %m", etc_hostname()); TAKE_PTR(s); return 0; @@ -857,11 +864,12 @@ static int context_write_data_static_hostname(Context *c) { static int context_write_data_machine_info(Context *c) { _cleanup_(unset_statp) struct stat *s = NULL; static const char * const name[_PROP_MAX] = { - [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME", - [PROP_ICON_NAME] = "ICON_NAME", - [PROP_CHASSIS] = "CHASSIS", - [PROP_DEPLOYMENT] = "DEPLOYMENT", - [PROP_LOCATION] = "LOCATION", + [PROP_PRETTY_HOSTNAME] = "PRETTY_HOSTNAME", + [PROP_ICON_NAME] = "ICON_NAME", + [PROP_CHASSIS] = "CHASSIS", + [PROP_DEPLOYMENT] = "DEPLOYMENT", + [PROP_LOCATION] = "LOCATION", + [PROP_TAGS] = "TAGS", }; _cleanup_strv_free_ char **l = NULL; int r; @@ -874,19 +882,18 @@ static int context_write_data_machine_info(Context *c) { r = load_env_file(NULL, etc_machine_info(), &l); if (r < 0 && r != -ENOENT) - return r; + return log_error_errno(r, "Failed to read '%s': %m", etc_machine_info()); - for (HostProperty p = PROP_PRETTY_HOSTNAME; p <= PROP_LOCATION; p++) { + for (HostProperty p = _PROP_MACHINE_INFO_SETTABLE_FIRST; p <= _PROP_MACHINE_INFO_SETTABLE_LAST; p++) { assert(name[p]); - r = strv_env_assign(&l, name[p], empty_to_null(c->data[p])); - if (r < 0) - return r; + if (strv_env_assign(&l, name[p], empty_to_null(c->data[p])) < 0) + return log_oom(); } if (strv_isempty(l)) { if (unlink(etc_machine_info()) < 0 && errno != ENOENT) - return -errno; + return log_error_errno(errno, "Failed to unlink '%s': %m", etc_machine_info()); TAKE_PTR(s); return 0; @@ -899,7 +906,7 @@ static int context_write_data_machine_info(Context *c) { l, WRITE_ENV_FILE_LABEL); if (r < 0) - return r; + return log_error_errno(r, "Failed to write '%s': %m", etc_machine_info()); TAKE_PTR(s); return 0; @@ -1180,6 +1187,29 @@ static int property_get_machine_info_field( return sd_bus_message_append(reply, "s", *(char**) userdata); } +static int property_get_tags( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Context *c = ASSERT_PTR(userdata); + int r; + + context_read_machine_info(c); + + /* Silently drop any invalid tags that might have been written into the file by hand */ + _cleanup_strv_free_ char **l = NULL; + r = machine_tags_from_string(c->data[PROP_TAGS], /* graceful= */ true, &l); + if (r < 0) + log_warning_errno(r, "Failed to parse machine tags '%s', ignoring: %m", strnull(c->data[PROP_TAGS])); + + return sd_bus_message_append_strv(reply, l); +} + static int property_get_os_release_field( sd_bus *bus, const char *path, @@ -1336,7 +1366,7 @@ static int property_get_vsock_cid( return sd_bus_message_append(reply, "u", (uint32_t) local_cid); } -static int validate_and_substitute_hostname(const char *name, char **ret_substituted, sd_bus_error *error) { +static int validate_and_substitute_hostname(const char *name, char **ret_substituted) { int r; assert(ret_substituted); @@ -1346,21 +1376,31 @@ static int validate_and_substitute_hostname(const char *name, char **ret_substit return 0; } - _cleanup_free_ char *substituted = strdup(name); - if (!substituted) - return log_oom(); + _cleanup_free_ char *substituted = NULL; - r = hostname_substitute_wildcards(substituted); + r = hostname_substitute_wildcards(name, &substituted); if (r < 0) return log_error_errno(r, "Failed to substitute wildcards in hostname: %m"); if (!hostname_is_valid(substituted, 0)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name); + return -EUCLEAN; *ret_substituted = TAKE_PTR(substituted); return 1; } +static int bus_validate_and_substitute_hostname(const char *name, char **ret_substituted, sd_bus_error *error) { + int r; + + assert(ret_substituted); + + r = validate_and_substitute_hostname(name, ret_substituted); + if (r == -EUCLEAN) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name); + + return r; +} + static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error *error) { Context *c = ASSERT_PTR(userdata); const char *name; @@ -1378,7 +1418,7 @@ static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error * * we might want to adjust hostname source information even if the actual hostname is unchanged. */ _cleanup_free_ char *substituted = NULL; - r = validate_and_substitute_hostname(name, &substituted, error); + r = bus_validate_and_substitute_hostname(name, &substituted, error); if (r < 0) return r; @@ -1391,6 +1431,7 @@ static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error * /* good_user= */ UID_INVALID, interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -1429,7 +1470,7 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_ return sd_bus_reply_method_return(m, NULL); _cleanup_free_ char *substituted = NULL; - r = validate_and_substitute_hostname(name, &substituted, error); + r = bus_validate_and_substitute_hostname(name, &substituted, error); if (r < 0) return r; @@ -1440,6 +1481,7 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_ /* good_user= */ UID_INVALID, interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -1454,7 +1496,6 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_ r = context_write_data_static_hostname(c); if (r < 0) { - log_error_errno(r, "Failed to write static hostname: %m"); if (ERRNO_IS_PRIVILEGE(r)) return sd_bus_error_set(error, BUS_ERROR_FILE_IS_PROTECTED, "Not allowed to update /etc/hostname."); if (r == -EROFS) @@ -1463,10 +1504,8 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_ } r = context_update_kernel_hostname(c, /* transient_hostname= */ NULL); - if (r < 0) { - log_error_errno(r, "Failed to set hostname: %m"); + if (r < 0) return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %m"); - } (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", @@ -1498,15 +1537,15 @@ static int set_machine_info(Context *c, sd_bus_message *m, int prop, sd_bus_mess /* The icon name might ultimately be used as file * name, so better be safe than sorry */ - if (prop == PROP_ICON_NAME && !filename_is_valid(name)) + if (prop == PROP_ICON_NAME && !icon_name_is_valid(name)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name); - if (prop == PROP_PRETTY_HOSTNAME && string_has_cc(name, NULL)) + if (prop == PROP_PRETTY_HOSTNAME && !pretty_hostname_is_valid(name)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty hostname '%s'", name); - if (prop == PROP_CHASSIS && !valid_chassis(name)) + if (prop == PROP_CHASSIS && !chassis_is_valid(name)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name); - if (prop == PROP_DEPLOYMENT && !valid_deployment(name)) + if (prop == PROP_DEPLOYMENT && !deployment_is_valid(name)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid deployment '%s'", name); - if (prop == PROP_LOCATION && string_has_cc(name, NULL)) + if (prop == PROP_LOCATION && !location_is_valid(name)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid location '%s'", name); } @@ -1520,6 +1559,7 @@ static int set_machine_info(Context *c, sd_bus_message *m, int prop, sd_bus_mess /* good_user= */ UID_INVALID, interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -1532,7 +1572,6 @@ static int set_machine_info(Context *c, sd_bus_message *m, int prop, sd_bus_mess r = context_write_data_machine_info(c); if (r < 0) { - log_error_errno(r, "Failed to write machine info: %m"); if (ERRNO_IS_PRIVILEGE(r)) return sd_bus_error_set(error, BUS_ERROR_FILE_IS_PROTECTED, "Not allowed to update /etc/machine-info."); if (r == -EROFS) @@ -1578,6 +1617,196 @@ static int method_set_location(sd_bus_message *m, void *userdata, sd_bus_error * return set_machine_info(userdata, m, PROP_LOCATION, method_set_location, error); } +static int context_store_tags(Context *c, char **tags) { + int r; + + assert(c); + + /* Persists the given machine tags (which must already be validated, sorted and deduplicated) to + * /etc/machine-info and emits a PropertiesChanged signal on the Tags property. */ + + if (strv_isempty(tags)) + c->data[PROP_TAGS] = mfree(c->data[PROP_TAGS]); + else { + _cleanup_free_ char *j = strv_join(tags, ":"); + if (!j) + return log_oom(); + + free_and_replace(c->data[PROP_TAGS], j); + } + + r = context_write_data_machine_info(c); + if (r < 0) + return r; + + log_info("Changed tags to '%s'", strempty(c->data[PROP_TAGS])); + + (void) sd_bus_emit_properties_changed( + c->bus, + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + "Tags", + NULL); + + return 0; +} + +static int bus_error_from_tags_write(sd_bus_error *error, int r) { + assert(error); + assert(r < 0); + + log_error_errno(r, "Failed to write machine info: %m"); + if (ERRNO_IS_PRIVILEGE(r)) + return sd_bus_error_set(error, BUS_ERROR_FILE_IS_PROTECTED, "Not allowed to update /etc/machine-info."); + if (r == -EROFS) + return sd_bus_error_set(error, BUS_ERROR_READ_ONLY_FILESYSTEM, "/etc/machine-info is in a read-only filesystem."); + return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %m"); +} + +static int method_set_tags(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(m); + + _cleanup_strv_free_ char **tags = NULL; + r = sd_bus_message_read_strv(m, &tags); + if (r < 0) + return r; + + strv_sort_uniq(tags); + + if (strv_length(tags) > MACHINE_TAGS_MAX) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Too many machine tags specified."); + + _cleanup_free_ char *j = strv_join(tags, ":"); + if (!j) + return log_oom(); + + if (!machine_tag_list_is_valid(tags)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid tags '%s'", j); + + context_read_machine_info(c); + + _cleanup_strv_free_ char **current = NULL; + r = machine_tags_from_string(c->data[PROP_TAGS], /* graceful= */ true, ¤t); + if (r < 0) + return log_error_errno(r, "Failed to parse current machine tags: %m"); + + if (strv_equal(current, tags)) + return sd_bus_reply_method_return(m, NULL); + + r = bus_verify_polkit_async_full( + m, + "org.freedesktop.hostname1.set-machine-info", + /* details= */ NULL, + /* good_user= */ UID_INVALID, + /* flags= */ 0, + &c->polkit_registry, + /* ret_admin= */ NULL, + 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 */ + + r = context_store_tags(c, tags); + if (r < 0) + return bus_error_from_tags_write(error, r); + + return sd_bus_reply_method_return(m, NULL); +} + +static int machine_tags_add_remove(char * const *base, char * const *add, char * const *remove, char ***ret) { + int r; + + assert(ret); + + /* Computes the resulting machine tag list when adding 'add' to and removing 'remove' from the 'base' + * list, i.e. (base ∪ add) \ remove, sorted and deduplicated. Shared by the D-Bus AddAndRemoveTags() + * and Varlink SetTags() implementations. */ + + _cleanup_strv_free_ char **tags = strv_copy(base); + if (!tags) + return -ENOMEM; + + r = strv_extend_strv(&tags, add, /* filter_duplicates= */ true); + if (r < 0) + return r; + + strv_remove_strv(tags, remove); + + strv_sort_uniq(tags); + + if (strv_length(tags) > MACHINE_TAGS_MAX) + return -E2BIG; + + *ret = TAKE_PTR(tags); + return 0; +} + +static int method_add_and_remove_tags(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(m); + + _cleanup_strv_free_ char **add = NULL, **remove = NULL; + r = sd_bus_message_read_strv(m, &add); + if (r < 0) + return r; + r = sd_bus_message_read_strv(m, &remove); + if (r < 0) + return r; + + if (!machine_tag_list_is_valid(add)) { + _cleanup_free_ char *j = strv_join(add, ":"); + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid tags to add '%s'", strna(j)); + } + if (!machine_tag_list_is_valid(remove)) { + _cleanup_free_ char *j = strv_join(remove, ":"); + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid tags to remove '%s'", strna(j)); + } + + context_read_machine_info(c); + + /* Start from the current tags, add the requested ones, then drop the ones to be removed. */ + _cleanup_strv_free_ char **current = NULL; + r = machine_tags_from_string(c->data[PROP_TAGS], /* graceful= */ true, ¤t); + if (r < 0) + return log_error_errno(r, "Failed to parse current machine tags: %m"); + + _cleanup_strv_free_ char **tags = NULL; + r = machine_tags_add_remove(current, add, remove, &tags); + if (r == -E2BIG) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Too many machine tags specified."); + if (r < 0) + return log_oom(); + + if (strv_equal(current, tags)) + return sd_bus_reply_method_return(m, NULL); + + r = bus_verify_polkit_async_full( + m, + "org.freedesktop.hostname1.set-machine-info", + /* details= */ NULL, + /* good_user= */ UID_INVALID, + /* flags= */ 0, + &c->polkit_registry, + /* ret_admin= */ NULL, + 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 */ + + r = context_store_tags(c, tags); + if (r < 0) + return bus_error_from_tags_write(error, r); + + return sd_bus_reply_method_return(m, NULL); +} + static int method_get_product_uuid(sd_bus_message *m, void *userdata, sd_bus_error *error) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; Context *c = ASSERT_PTR(userdata); @@ -1597,6 +1826,7 @@ static int method_get_product_uuid(sd_bus_message *m, void *userdata, sd_bus_err /* good_user= */ UID_INVALID, interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -1652,6 +1882,66 @@ 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 }, + { "TAGS", PROP_TAGS }, + { "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, @@ -1811,6 +2101,7 @@ static const sd_bus_vtable hostname_vtable[] = { SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Deployment", "s", property_get_machine_info_field, offsetof(Context, data[PROP_DEPLOYMENT]), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Location", "s", property_get_machine_info_field, offsetof(Context, data[PROP_LOCATION]), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Tags", "as", property_get_tags, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("KernelName", "s", property_get_uname_field, offsetof(struct utsname, sysname), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("KernelRelease", "s", property_get_uname_field, offsetof(struct utsname, release), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("KernelVersion", "s", property_get_uname_field, offsetof(struct utsname, version), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST), @@ -1868,6 +2159,16 @@ static const sd_bus_vtable hostname_vtable[] = { SD_BUS_NO_RESULT, method_set_location, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("SetTags", + SD_BUS_ARGS("as", tags), + SD_BUS_NO_RESULT, + method_set_tags, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("AddAndRemoveTags", + SD_BUS_ARGS("as", add, "as", remove), + SD_BUS_NO_RESULT, + method_add_and_remove_tags, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("GetProductUUID", SD_BUS_ARGS("b", interactive), SD_BUS_RESULT("ay", uuid), @@ -1883,6 +2184,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 +2206,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"); @@ -1942,7 +2248,8 @@ static int vl_method_describe(sd_varlink *link, sd_json_variant *parameters, sd_ /* details= */ NULL, UID_INVALID, POLKIT_DONT_REPLY, - &c->polkit_registry); + &c->polkit_registry, + /* ret_admin= */ NULL); if (r == 0) return 0; /* No authorization for now, but the async polkit stuff will call us again when it has it */ @@ -1958,6 +2265,333 @@ static int vl_method_describe(sd_varlink *link, sd_json_variant *parameters, sd_ return sd_varlink_reply(link, v); } +static int vl_validate_and_substitute_hostname(sd_varlink *link, const char *name, char **ret_substituted) { + int r; + + assert(link); + assert(ret_substituted); + + r = validate_and_substitute_hostname(name, ret_substituted); + if (r == -EUCLEAN) + return sd_varlink_error_invalid_parameter_name(link, "newValue"); + + return r; +} + +static int vl_method_set_hostname(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "newValue", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0, SD_JSON_NULLABLE }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + Context *c = ASSERT_PTR(userdata); + int r; + + assert(link); + assert(parameters); + + _cleanup_free_ char *value = NULL; + r = sd_varlink_dispatch(link, parameters, dispatch_table, &value); + if (r != 0) + return r; + + const char *name = empty_to_null(value); + + /* We always go through with the procedure below without comparing to the current hostname, because + * we might want to adjust hostname source information even if the actual hostname is unchanged. */ + + _cleanup_free_ char *substituted = NULL; + r = vl_validate_and_substitute_hostname(link, name, &substituted); + if (r < 0) + return r; + + name = substituted; + + r = varlink_verify_polkit_async( + link, + c->bus, + "org.freedesktop.hostname1.set-hostname", + /* details= */ NULL, + &c->polkit_registry); + if (r <= 0) + return r; + + context_read_etc_hostname(c); + + r = context_update_kernel_hostname(c, name); + if (r < 0) + return r; + if (r > 0) + (void) sd_bus_emit_properties_changed(c->bus, + "/org/freedesktop/hostname1", "org.freedesktop.hostname1", + "Hostname", "HostnameSource", NULL); + + return sd_varlink_reply(link, NULL); +} + +static int vl_method_set_static_hostname(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "newValue", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0, SD_JSON_NULLABLE }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + Context *c = ASSERT_PTR(userdata); + int r; + + assert(link); + assert(parameters); + + _cleanup_free_ char *value = NULL; + r = sd_varlink_dispatch(link, parameters, dispatch_table, &value); + if (r != 0) + return r; + + const char *name = empty_to_null(value); + + context_read_etc_hostname(c); + + if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME])) + return sd_varlink_reply(link, NULL); + + _cleanup_free_ char *substituted = NULL; + r = vl_validate_and_substitute_hostname(link, name, &substituted); + if (r < 0) + return r; + + r = varlink_verify_polkit_async( + link, + c->bus, + "org.freedesktop.hostname1.set-static-hostname", + /* details= */ NULL, + &c->polkit_registry); + if (r <= 0) + return r; + + r = free_and_strdup_warn(&c->data[PROP_STATIC_HOSTNAME], name); + if (r < 0) + return r; + + free_and_replace(c->data[PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS], substituted); + + r = context_write_data_static_hostname(c); + if (r < 0) + return r; + + r = context_update_kernel_hostname(c, /* transient_hostname= */ NULL); + if (r < 0) + return r; + + (void) sd_bus_emit_properties_changed(c->bus, + "/org/freedesktop/hostname1", "org.freedesktop.hostname1", + "StaticHostname", "Hostname", "HostnameSource", NULL); + + return sd_varlink_reply(link, NULL); +} + +static int vl_set_machine_info(sd_varlink *link, sd_json_variant *parameters, void *userdata, HostProperty prop) { + static const sd_json_dispatch_field dispatch_table[] = { + { "newValue", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0, SD_JSON_NULLABLE }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + Context *c = ASSERT_PTR(userdata); + int r; + + assert(link); + assert(parameters); + + _cleanup_free_ char *value = NULL; + r = sd_varlink_dispatch(link, parameters, dispatch_table, &value); + if (r != 0) + return r; + + const char *name = empty_to_null(value), + *polkit_action = "org.freedesktop.hostname1.set-machine-info", + *bus_property, *human_name; + + switch (prop) { + + case PROP_PRETTY_HOSTNAME: + if (name && !pretty_hostname_is_valid(name)) + return sd_varlink_error_invalid_parameter_name(link, "newValue"); + bus_property = "PrettyHostname"; + human_name = "pretty hostname"; + /* Since the pretty hostname should always be changed at the same time as the static one, use + * the same policy action for both... */ + polkit_action = "org.freedesktop.hostname1.set-static-hostname"; + break; + + case PROP_ICON_NAME: + /* The icon name might ultimately be used as file name, so better be safe than sorry. */ + if (name && !icon_name_is_valid(name)) + return sd_varlink_error_invalid_parameter_name(link, "newValue"); + bus_property = "IconName"; + human_name = "icon name"; + break; + + case PROP_CHASSIS: + if (name && !chassis_is_valid(name)) + return sd_varlink_error_invalid_parameter_name(link, "newValue"); + bus_property = "Chassis"; + human_name = "chassis"; + break; + + case PROP_DEPLOYMENT: + if (name && !deployment_is_valid(name)) + return sd_varlink_error_invalid_parameter_name(link, "newValue"); + bus_property = "Deployment"; + human_name = "deployment"; + break; + + case PROP_LOCATION: + if (name && !location_is_valid(name)) + return sd_varlink_error_invalid_parameter_name(link, "newValue"); + bus_property = "Location"; + human_name = "location"; + break; + + default: + assert_not_reached(); + } + + context_read_machine_info(c); + + if (streq_ptr(name, c->data[prop])) + return sd_varlink_reply(link, NULL); + + r = varlink_verify_polkit_async( + link, + c->bus, + polkit_action, + /* details= */ NULL, + &c->polkit_registry); + if (r <= 0) + return r; + + r = free_and_strdup_warn(&c->data[prop], name); + if (r < 0) + return r; + + r = context_write_data_machine_info(c); + if (r < 0) + return r; + + log_info("Changed %s to '%s'", human_name, strna(c->data[prop])); + + (void) sd_bus_emit_properties_changed( + c->bus, + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + bus_property, + NULL); + + return sd_varlink_reply(link, NULL); +} + +static int vl_method_set_pretty_hostname(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return vl_set_machine_info(link, parameters, userdata, PROP_PRETTY_HOSTNAME); +} + +static int vl_method_set_icon_name(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return vl_set_machine_info(link, parameters, userdata, PROP_ICON_NAME); +} + +static int vl_method_set_chassis(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return vl_set_machine_info(link, parameters, userdata, PROP_CHASSIS); +} + +static int vl_method_set_deployment(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return vl_set_machine_info(link, parameters, userdata, PROP_DEPLOYMENT); +} + +static int vl_method_set_location(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return vl_set_machine_info(link, parameters, userdata, PROP_LOCATION); +} + +typedef struct SetTagsParameters { + char **set; + char **add; + char **remove; +} SetTagsParameters; + +static void set_tags_parameters_done(SetTagsParameters *p) { + assert(p); + + strv_free(p->set); + strv_free(p->add); + strv_free(p->remove); +} + +static int vl_method_set_tags(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "set", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(SetTagsParameters, set), SD_JSON_NULLABLE }, + { "add", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(SetTagsParameters, add), SD_JSON_NULLABLE }, + { "remove", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(SetTagsParameters, remove), SD_JSON_NULLABLE }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + Context *c = ASSERT_PTR(userdata); + int r; + + assert(link); + assert(parameters); + + _cleanup_(set_tags_parameters_done) SetTagsParameters p = {}; + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + /* Both an absent 'set' field and an explicit null map to a NULL strv after dispatching, but only the + * former should keep the current tags — an explicit (possibly empty) 'set' resets the list. Hence + * check for the field's presence separately. */ + bool reset = sd_json_variant_by_key(parameters, "set"); + + if (reset && !machine_tag_list_is_valid(p.set)) + return sd_varlink_error_invalid_parameter_name(link, "set"); + if (!machine_tag_list_is_valid(p.add)) + return sd_varlink_error_invalid_parameter_name(link, "add"); + if (!machine_tag_list_is_valid(p.remove)) + return sd_varlink_error_invalid_parameter_name(link, "remove"); + + context_read_machine_info(c); + + _cleanup_strv_free_ char **current = NULL; + r = machine_tags_from_string(c->data[PROP_TAGS], /* graceful= */ true, ¤t); + if (r < 0) + return r; + + /* Use either the explicitly specified tag list or the current one as the basis, then apply the + * additions and removals on top. */ + _cleanup_strv_free_ char **tags = NULL; + r = machine_tags_add_remove(reset ? p.set : current, p.add, p.remove, &tags); + if (r == -E2BIG) + return sd_varlink_error_invalid_parameter_name(link, "add"); + if (r < 0) + return r; + + if (strv_equal(tags, current)) + return sd_varlink_reply(link, NULL); + + r = varlink_verify_polkit_async( + link, + c->bus, + "org.freedesktop.hostname1.set-machine-info", + /* details= */ NULL, + &c->polkit_registry); + if (r <= 0) + return r; + + r = context_store_tags(c, tags); + if (r < 0) + return r; + + return sd_varlink_reply(link, NULL); +} + static int connect_varlink(Context *c) { int r; @@ -1981,10 +2615,18 @@ static int connect_varlink(Context *c) { r = sd_varlink_server_bind_method_many( c->varlink_server, - "io.systemd.Hostname.Describe", vl_method_describe, - "io.systemd.service.Ping", varlink_method_ping, - "io.systemd.service.SetLogLevel", varlink_method_set_log_level, - "io.systemd.service.GetEnvironment", varlink_method_get_environment); + "io.systemd.Hostname.Describe", vl_method_describe, + "io.systemd.Hostname.SetHostname", vl_method_set_hostname, + "io.systemd.Hostname.SetStaticHostname", vl_method_set_static_hostname, + "io.systemd.Hostname.SetPrettyHostname", vl_method_set_pretty_hostname, + "io.systemd.Hostname.SetIconName", vl_method_set_icon_name, + "io.systemd.Hostname.SetChassis", vl_method_set_chassis, + "io.systemd.Hostname.SetDeployment", vl_method_set_deployment, + "io.systemd.Hostname.SetLocation", vl_method_set_location, + "io.systemd.Hostname.SetTags", vl_method_set_tags, + "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.SetLogLevel", varlink_method_set_log_level, + "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) return log_error_errno(r, "Failed to bind Varlink method calls: %m"); @@ -2056,6 +2698,14 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m"); + r = sd_event_add_signal(context.event, /* ret= */ NULL, (SIGRTMIN+18)|SD_EVENT_SIGNAL_PROCMASK, sigrtmin18_handler, /* userdata= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to install SIGRTMIN+18 handler: %m"); + + r = sd_event_add_memory_pressure(context.event, /* ret= */ NULL, /* callback= */ NULL, /* userdata= */ NULL); + if (r < 0) + log_debug_errno(r, "Failed to allocate memory pressure event source, ignoring: %m"); + r = connect_bus(&context); if (r < 0) return r; diff --git a/src/hwdb/hwdb.c b/src/hwdb/hwdb.c index 6391b04133c5e..4d2d6fe8c48d9 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,92 @@ 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 opts = { argc, argv }; - while ((c = getopt_long(argc, argv, "sr:h", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) 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 = opts.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(&opts); 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 +125,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return hwdb_main(argc, argv); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/id128/id128.c b/src/id128/id128.c index cdb93ac68fab2..e66acd33e5eed 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,119 @@ 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 opts = { argc, argv }; - case 'h': + FOREACH_OPTION_OR_RETURN(c, &opts) + 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(opts.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(opts.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", opts.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(&opts); 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(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..3f22ca303105d --- /dev/null +++ b/src/imds/imds-generator.c @@ -0,0 +1,200 @@ +/* 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 "initrd-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 int arg_import = -1; /* 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 < 0) + arg_import = in_initrd(); + if (arg_import > 0) { + /* Enable the import of 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"); + } + + /* Enable IMDS metrics in case IMDS is supported */ + r = generator_add_symlink(dest_early, SPECIAL_SOCKETS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imds-metrics.socket"); + if (r < 0) + return log_error_errno(r, "Failed to hook in systemd-imds-metrics.socket: %m"); + + return 0; +} + +DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/imds/imds-tool-metrics.c b/src/imds/imds-tool-metrics.c new file mode 100644 index 0000000000000..27eabe92bc144 --- /dev/null +++ b/src/imds/imds-tool-metrics.c @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" +#include "sd-varlink.h" + +#include "imds-tool.h" +#include "imds-tool-metrics.h" +#include "imds-util.h" +#include "log.h" +#include "metrics.h" +#include "string-util.h" +#include "varlink-io.systemd.Metrics.h" +#include "varlink-util.h" + +#define METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "io.systemd.InstanceMetadata." + +static int metric_vendor_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + sd_varlink *imds = userdata; /* The live systemd-imdsd connection, or NULL if unavailable */ + _cleanup_free_ char *vendor = NULL; + int r; + + assert(mf && mf->name); + assert(vl); + + if (!imds) + return 0; + + r = acquire_imds_vendor(imds, &vendor); + if (r <= 0) + return 0; /* On error or absence simply omit the metric */ + + return metric_build_send_string(mf, vl, /* object= */ NULL, vendor, /* fields= */ NULL); +} + +static int metric_well_known_build_json( + const MetricFamily *mf, + sd_varlink *vl, + void *userdata, + ImdsWellKnown wk) { + + sd_varlink *imds = userdata; + _cleanup_free_ char *value = NULL; + int r; + + assert(mf && mf->name); + assert(vl); + + if (!imds) + return 0; + + r = acquire_imds_key_as_string(imds, wk, /* key= */ NULL, &value); + if (r <= 0 || isempty(value)) + return 0; /* Field not supported/set, or error: omit the metric */ + + return metric_build_send_string(mf, vl, /* object= */ NULL, value, /* fields= */ NULL); +} + +static int metric_hostname_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return metric_well_known_build_json(mf, vl, userdata, IMDS_HOSTNAME); +} + +static int metric_ipv4_public_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return metric_well_known_build_json(mf, vl, userdata, IMDS_IPV4_PUBLIC); +} + +static int metric_ipv6_public_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return metric_well_known_build_json(mf, vl, userdata, IMDS_IPV6_PUBLIC); +} + +static int metric_region_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return metric_well_known_build_json(mf, vl, userdata, IMDS_REGION); +} + +static int metric_zone_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return metric_well_known_build_json(mf, vl, userdata, IMDS_ZONE); +} + +/* Keep metrics ordered alphabetically */ +static const MetricFamily imds_metric_family_table[] = { + { + .name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Hostname", + .description = "Instance hostname reported by IMDS", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = metric_hostname_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "IPv4Public", + .description = "Public IPv4 address reported by IMDS", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = metric_ipv4_public_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "IPv6Public", + .description = "Public IPv6 address reported by IMDS", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = metric_ipv6_public_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Region", + .description = "Cloud region reported by IMDS", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = metric_region_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Vendor", + .description = "Detected cloud vendor", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = metric_vendor_build_json, + }, + { + .name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Zone", + .description = "Cloud availability zone reported by IMDS", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = metric_zone_build_json, + }, + {} +}; + +static int vl_method_metrics_describe(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return metrics_method_describe(imds_metric_family_table, link, parameters, flags, userdata); +} + +static int vl_method_metrics_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + int r; + + /* Acquire a single connection to systemd-imdsd, shared by all metric generators (so they benefit + * from the daemon's token/data cache). If the daemon is unreachable we still serve an empty list + * rather than failing the whole request. */ + _cleanup_(sd_varlink_unrefp) sd_varlink *imds = NULL; + r = connect_imdsd(&imds); + if (r < 0) + log_debug_errno(r, "Failed to connect to systemd-imdsd, serving empty metrics list: %m"); + + return metrics_method_list(imds_metric_family_table, link, parameters, flags, imds); +} + +int imds_metrics_run(void) { + int r; + + /* Invocation as a Varlink metrics provider (io.systemd.Metrics), typically socket-activated via + * /run/systemd/report/io.systemd.InstanceMetadata. */ + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *server = NULL; + r = varlink_server_new(&server, /* flags= */ 0, /* userdata= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = sd_varlink_server_add_interface(server, &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( + server, + "io.systemd.Metrics.List", vl_method_metrics_list, + "io.systemd.Metrics.Describe", vl_method_metrics_describe); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %m"); + + r = sd_varlink_server_loop_auto(server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return 0; +} diff --git a/src/imds/imds-tool-metrics.h b/src/imds/imds-tool-metrics.h new file mode 100644 index 0000000000000..db0e55226e053 --- /dev/null +++ b/src/imds/imds-tool-metrics.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* Runs systemd-imds as an io.systemd.Metrics provider (socket-activated). */ +int imds_metrics_run(void); diff --git a/src/imds/imds-tool.c b/src/imds/imds-tool.c new file mode 100644 index 0000000000000..e46d335cca177 --- /dev/null +++ b/src/imds/imds-tool.c @@ -0,0 +1,918 @@ +/* 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 "help-util.h" +#include "hexdecoct.h" +#include "imds-tool.h" +#include "imds-tool-metrics.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 "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_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] [KEY]"); + help_abstract("IMDS data acquisition."); + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-imds", "1"); + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + 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(opts.arg)) { + arg_well_known = _IMDS_WELL_KNOWN_INVALID; + break; + } + + if (streq(opts.arg, "help")) + return DUMP_STRING_TABLE(imds_well_known, ImdsWellKnown, _IMDS_WELL_KNOWN_MAX); + + ImdsWellKnown wk = imds_well_known_from_string(opts.arg); + if (wk < 0) + return log_error_errno(wk, "Failed to parse --well-known= argument: %s", opts.arg); + + arg_well_known = wk; + break; + } + + OPTION_LONG("refresh", "SEC", "Set minimum freshness time for returned data"): { + if (isempty(opts.arg)) { + arg_refresh_usec_set = false; + break; + } + + usec_t t; + r = parse_sec(opts.arg, &t); + if (r < 0) + return log_error_errno(r, "Failed to parse refresh timeout: %s", opts.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=", opts.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(&opts); + size_t n_args = option_parser_get_n_args(&opts); + + 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|SD_JSON_LOG, &data); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(data); + return 1; +} + +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 log_error_errno(r, "Failed to turn IMDS data into a C string: %m"); + + *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 log_error_errno(r, "Failed to parse IP address '%s': %m", s); + + return 1; +} + +int acquire_imds_vendor(sd_varlink *link, char **ret) { + int r; + + assert(link); + assert(ret); + + 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) { + if (streq(error_id, "io.systemd.InstanceMetadata.NotSupported")) { + *ret = NULL; + return 0; + } + + 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|SD_JSON_LOG, &vendor); + if (r < 0) + return r; + if (isempty(vendor)) { + *ret = NULL; + return 0; + } + + if (strdup_to_full(ret, vendor) < 0) + return log_oom(); + + 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(); + + _cleanup_free_ char *vendor = NULL; + r = acquire_imds_vendor(link, &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, ignoring user data"); + else + log_warning_errno(r, "Failed to parse IMDS userdata JSON, ignoring user data: %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)); +} + +int connect_imdsd(sd_varlink **ret) { + int r; + + assert(ret); + + _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"); + } + + *ret = TAKE_PTR(link); + return 0; +} + +static int run(int argc, char* argv[]) { + int r; + + log_setup(); + + /* When invoked as a Varlink service (socket activation) we act as an io.systemd.Metrics provider + * and run a server loop. The metric generators connect to systemd-imdsd on demand, so we never open + * a one-shot connection here. */ + 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 imds_metrics_run(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + r = connect_imdsd(&link); + if (r < 0) + return r; + + 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-tool.h b/src/imds/imds-tool.h new file mode 100644 index 0000000000000..bfe26015c344a --- /dev/null +++ b/src/imds/imds-tool.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "imds-util.h" +#include "shared-forward.h" + +/* Helpers shared between imds-tool.c and imds-tool-metrics.c for talking to systemd-imdsd. */ + +int connect_imdsd(sd_varlink **ret); +int acquire_imds_key_as_string(sd_varlink *link, ImdsWellKnown wk, const char *key, char **ret); +int acquire_imds_vendor(sd_varlink *link, char **ret); 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..ea079fdbd2747 --- /dev/null +++ b/src/imds/imdsd.c @@ -0,0 +1,3082 @@ +/* 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 "exit-status.h" +#include "fd-util.h" +#include "fileio.h" +#include "format-ifname.h" +#include "format-table.h" +#include "hash-funcs.h" +#include "hashmap.h" +#include "help-util.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 "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) */ + CurlSlot *slot_token; + CurlSlot *slot_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 */ + + c->slot_token = curl_slot_unref(c->slot_token); + c->slot_data = curl_slot_unref(c->slot_data); + + 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 int context_fail_full(Context *c, int r, const char *varlink_error) { + assert(c); + assert(r != 0); + + /* Called whenever the current retrieval fails asynchronously. Returns 0 so callers in + * int-returning paths can `return context_fail_full(...)` directly. */ + + 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); + return 0; +} + +static int context_fail(Context *c, int r) { + return 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 int curl_on_finished(CurlSlot *slot, CURL *curl, CURLcode result, void *userdata) { + Context *c = ASSERT_PTR(userdata); + int r; + + /* Called whenever libcurl did its thing and reports a download being complete or having failed */ + + 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 0; + + 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 0; + } + if (result != CURLE_OK) /* if getting the HTTP status didn't work, propagate a generic error */ + return context_fail(c, SYNTHETIC_ERRNO(ENOTRECOVERABLE)); + + if (slot == c->slot_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 (slot == c->slot_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 0; + + 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(); + + return 0; +} + +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"); + + 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_on_finished() call will then check the HTTP + * status again and act on it. */ + long status; + r = context_acquire_http_status(c, curl_slot_get_easy(c->slot_data), &status); + if (r <= 0) + return 0; /* fail the thing, so that curl_on_finished() can handle this failure or retry request */ + if (status >= 300) /* any status equal or above 300 needs to be handled by curl_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); + + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + r = curl_glue_make(&easy, url); + 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(easy, 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(easy, 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(easy, 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(easy, 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(easy, 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(easy, 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(easy, 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_perform_async(c->glue, easy, curl_on_finished, c, &c->slot_data); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to add CURL request to glue: %m"); + TAKE_PTR(easy); + + 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, curl_slot_get_easy(c->slot_token), &status); + if (r <= 0) + return 0; /* fail the thing, so that curl_on_finished() can handle this failure or retry request */ + if (status >= 300) /* any status equal or above 300 needs to be handled by curl_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; + + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + r = curl_glue_make(&easy, arg_token_url); + 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(easy, 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(easy, 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(easy, 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(easy, 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(easy, 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(easy, 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_perform_async(c->glue, easy, curl_on_finished, c, &c->slot_token); + if (r < 0) + return context_log_errno(c, LOG_ERR, r, "Failed to add CURL request to glue: %m"); + TAKE_PTR(easy); + + 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_INFO); + if (r == -EOPNOTSUPP) + return EXIT_NOTCONFIGURED; + 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_(table_unrefp) Table *options = NULL, *endpoint_options = NULL; + int r; + + 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); + + help_cmdline("[OPTIONS...] KEY"); + help_abstract("Low-level IMDS data acquisition."); + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_section("Manual Endpoint Configuration"); + r = table_print_or_warn(endpoint_options); + if (r < 0) + return r; + + help_man_page_reference("systemd-imdsd@.service", "8"); + 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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('i', "interface", "INTERFACE", "Use the specified interface"): + if (isempty(opts.arg)) { + arg_ifname = mfree(arg_ifname); + break; + } + + if (!ifname_valid_full(opts.arg, IFNAME_VALID_ALTERNATIVE|IFNAME_VALID_NUMERIC)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Interface name '%s' is not valid.", opts.arg); + + r = free_and_strdup_warn(&arg_ifname, opts.arg); + if (r < 0) + return r; + + break; + + OPTION_LONG("refresh", "SEC", "Set token refresh time"): { + if (isempty(opts.arg)) { + arg_refresh_usec = REFRESH_USEC_DEFAULT; + break; + } + + usec_t t; + r = parse_sec(opts.arg, &t); + if (r < 0) + return log_error_errno(r, "Failed to parse refresh timeout: %s", opts.arg); + if (t < REFRESH_USEC_MIN) { + log_info("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(opts.arg)) { + arg_fwmark_set = false; + break; + } + + if (streq(opts.arg, "default")) { + arg_fwmark = FWMARK_DEFAULT; + arg_fwmark_set = true; + break; + } + + r = safe_atou32(opts.arg, &arg_fwmark); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse --fwmark= parameter: %s", opts.arg); + + arg_fwmark_set = true; + break; + + OPTION_LONG("cache", "BOOL", "Enable/disable cache use"): + r = parse_boolean_argument("--cache", opts.arg, &arg_cache); + if (r < 0) + return r; + break; + + OPTION_LONG("wait", "BOOL", "Whether to wait for connectivity"): + r = parse_boolean_argument("--wait", opts.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(opts.arg)) { + arg_well_known = _IMDS_WELL_KNOWN_INVALID; + break; + } + + ImdsWellKnown wk = imds_well_known_from_string(opts.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(opts.arg)) { + arg_vendor = mfree(arg_vendor); + break; + } + + r = free_and_strdup_warn(&arg_vendor, opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("token-url", "URL", "URL for acquiring token"): + if (isempty(opts.arg)) { + arg_token_url = mfree(arg_token_url); + break; + } + + if (!http_url_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL: %s", opts.arg); + + r = free_and_strdup_warn(&arg_token_url, opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("refresh-header-name", "NAME", "Header name for passing refresh time"): + if (isempty(opts.arg)) { + arg_refresh_header_name = mfree(arg_refresh_header_name); + break; + } + + if (!http_header_name_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header name: %s", opts.arg); + + r = free_and_strdup_warn(&arg_refresh_header_name, opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("data-url", "URL", "Base URL for acquiring data"): + if (isempty(opts.arg)) { + arg_data_url = mfree(arg_data_url); + break; + } + + if (!http_url_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL: %s", opts.arg); + + r = free_and_strdup_warn(&arg_data_url, opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("data-url-suffix", "STRING", "Suffix to append to data URL"): + if (isempty(opts.arg)) { + arg_data_url_suffix = mfree(arg_data_url_suffix); + break; + } + + if (!ascii_is_valid(opts.arg) || string_has_cc(opts.arg, /* ok= */ NULL)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URL suffix: %s", opts.arg); + + r = free_and_strdup_warn(&arg_data_url_suffix, opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("token-header-name", "NAME", "Header name for passing token string"): + if (isempty(opts.arg)) { + arg_token_header_name = mfree(arg_token_header_name); + break; + } + + if (!http_header_name_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header name: %s", opts.arg); + + r = free_and_strdup_warn(&arg_token_header_name, opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("extra-header", "NAME: VALUE", "Additional header to pass to data transfer"): + if (isempty(opts.arg)) { + arg_extra_header = strv_free(arg_extra_header); + break; + } + + if (!http_header_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header: %s", opts.arg); + + if (strv_extend(&arg_extra_header, opts.arg) < 0) + return log_oom(); + break; + + OPTION_LONG("address-ipv4", "ADDRESS", "Configure IPv4 address of the IMDS server"): { + if (isempty(opts.arg)) { + arg_address_ipv4 = (struct in_addr) {}; + break; + } + + union in_addr_union u; + r = in_addr_from_string(AF_INET, opts.arg, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse IPv4 address: %s", opts.arg); + arg_address_ipv4 = u.in; + break; + } + + OPTION_LONG("address-ipv6", "ADDRESS", "Configure IPv6 address of the IMDS server"): { + if (isempty(opts.arg)) { + arg_address_ipv6 = (struct in6_addr) {}; + break; + } + + union in_addr_union u; + r = in_addr_from_string(AF_INET6, opts.arg, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse IPv6 address: %s", opts.arg); + arg_address_ipv6 = u.in6; + break; + } + + OPTION_LONG("well-known-key", "NAME:KEY", "Configure the location of well-known keys"): { + if (isempty(opts.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(opts.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(opts.arg, e - opts.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(&opts); + size_t n_args = option_parser_get_n_args(&opts); + + 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 (IN_SET(r, -ENOENT, -ENXIO)) + 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 (IN_SET(r, -ENOENT, -ENXIO)) + 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 && !IN_SET(r, -ENOENT, -ENXIO)) + 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 && !IN_SET(r, -ENOENT, -ENXIO)) + 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 (IN_SET(r, -ENOENT, -ENXIO)) + 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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + 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..a23735d100219 --- /dev/null +++ b/src/imds/meson.build @@ -0,0 +1,31 @@ +# 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', + 'imds-tool-metrics.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.c b/src/import/curl-util.c deleted file mode 100644 index 4747d0993a8c3..0000000000000 --- a/src/import/curl-util.c +++ /dev/null @@ -1,403 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "sd-event.h" - -#include "alloc-util.h" -#include "curl-util.h" -#include "fd-util.h" -#include "hashmap.h" -#include "log.h" -#include "string-util.h" -#include "time-util.h" -#include "version.h" - -static void curl_glue_check_finished(CurlGlue *g) { - int r; - - assert(g); - - /* sd_event_get_exit_code() returns -ENODATA if no exit was scheduled yet */ - r = sd_event_get_exit_code(g->event, /* ret= */ NULL); - if (r >= 0) - return; /* exit scheduled? Then don't process this anymore */ - if (r != -ENODATA) - log_debug_errno(r, "Unexpected error while checking for event loop exit code, ignoring: %m"); - - CURLMsg *msg; - int k = 0; - msg = curl_multi_info_read(g->curl, &k); - if (!msg) - return; - - if (msg->msg == CURLMSG_DONE && g->on_finished) - g->on_finished(g, msg->easy_handle, msg->data.result); - - /* This is a queue, process another item soon, but do so in a later event loop iteration. */ - (void) sd_event_source_set_enabled(g->defer, SD_EVENT_ONESHOT); -} - -static int curl_glue_on_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - CurlGlue *g = ASSERT_PTR(userdata); - int action, k = 0; - - assert(s); - - if (FLAGS_SET(revents, EPOLLIN | EPOLLOUT)) - action = CURL_POLL_INOUT; - else if (revents & EPOLLIN) - action = CURL_POLL_IN; - else if (revents & EPOLLOUT) - action = CURL_POLL_OUT; - else - action = 0; - - if (curl_multi_socket_action(g->curl, fd, action, &k) != CURLM_OK) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to propagate IO event."); - - curl_glue_check_finished(g); - return 0; -} - -static int curl_glue_socket_callback(CURL *curl, curl_socket_t s, int action, void *userdata, void *socketp) { - sd_event_source *io = socketp; - CurlGlue *g = ASSERT_PTR(userdata); - uint32_t events = 0; - int r; - - assert(curl); - - if (action == CURL_POLL_REMOVE) { - if (io) { - sd_event_source_disable_unref(io); - - hashmap_remove(g->ios, FD_TO_PTR(s)); - } - - return 0; - } - - /* Don't configure io event source anymore when the event loop is dead already. */ - if (g->event && sd_event_get_state(g->event) == SD_EVENT_FINISHED) - return 0; - - r = hashmap_ensure_allocated(&g->ios, &trivial_hash_ops); - if (r < 0) { - log_oom(); - return -1; - } - - if (action == CURL_POLL_IN) - events = EPOLLIN; - else if (action == CURL_POLL_OUT) - events = EPOLLOUT; - else if (action == CURL_POLL_INOUT) - events = EPOLLIN|EPOLLOUT; - - if (io) { - if (sd_event_source_set_io_events(io, events) < 0) - return -1; - - if (sd_event_source_set_enabled(io, SD_EVENT_ON) < 0) - return -1; - } else { - 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) - return -1; - - (void) sd_event_source_set_description(io, "curl-io"); - - r = hashmap_put(g->ios, FD_TO_PTR(s), io); - if (r < 0) { - log_oom(); - sd_event_source_unref(io); - return -1; - } - } - - return 0; -} - -static int curl_glue_on_timer(sd_event_source *s, uint64_t usec, void *userdata) { - CurlGlue *g = ASSERT_PTR(userdata); - int k = 0; - - assert(s); - - if (curl_multi_socket_action(g->curl, CURL_SOCKET_TIMEOUT, 0, &k) != CURLM_OK) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to propagate timeout."); - - curl_glue_check_finished(g); - return 0; -} - -static int curl_glue_timer_callback(CURLM *curl, long timeout_ms, void *userdata) { - CurlGlue *g = ASSERT_PTR(userdata); - usec_t usec; - - assert(curl); - - /* Don't configure timer anymore when the event loop is dead already. */ - if (g->timer) { - sd_event *event_loop = sd_event_source_get_event(g->timer); - if (event_loop && sd_event_get_state(event_loop) == SD_EVENT_FINISHED) - return 0; - } - - if (timeout_ms < 0) { - if (sd_event_source_set_enabled(g->timer, SD_EVENT_OFF) < 0) - return -1; - - return 0; - } - - usec = (usec_t) timeout_ms * USEC_PER_MSEC + USEC_PER_MSEC - 1; - - if (g->timer) { - if (sd_event_source_set_time_relative(g->timer, usec) < 0) - return -1; - - if (sd_event_source_set_enabled(g->timer, SD_EVENT_ONESHOT) < 0) - return -1; - } else { - if (sd_event_add_time_relative(g->event, &g->timer, CLOCK_BOOTTIME, usec, 0, curl_glue_on_timer, g) < 0) - return -1; - - (void) sd_event_source_set_description(g->timer, "curl-timer"); - } - - return 0; -} - -static int curl_glue_on_defer(sd_event_source *s, void *userdata) { - CurlGlue *g = ASSERT_PTR(userdata); - - assert(s); - - curl_glue_check_finished(g); - return 0; -} - -CurlGlue *curl_glue_unref(CurlGlue *g) { - sd_event_source *io; - - if (!g) - return NULL; - - if (g->curl) - curl_multi_cleanup(g->curl); - - while ((io = hashmap_steal_first(g->ios))) - sd_event_source_unref(io); - - hashmap_free(g->ios); - - sd_event_source_disable_unref(g->timer); - sd_event_source_disable_unref(g->defer); - sd_event_unref(g->event); - return mfree(g); -} - -int curl_glue_new(CurlGlue **glue, sd_event *event) { - _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; - _cleanup_(curl_multi_cleanupp) CURLM *c = NULL; - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - int r; - - if (event) - e = sd_event_ref(event); - else { - r = sd_event_default(&e); - if (r < 0) - return r; - } - - c = curl_multi_init(); - if (!c) - return -ENOMEM; - - g = new(CurlGlue, 1); - if (!g) - return -ENOMEM; - - *g = (CurlGlue) { - .event = TAKE_PTR(e), - .curl = TAKE_PTR(c), - }; - - if (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) - return -EINVAL; - - if (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) - return -EINVAL; - - r = sd_event_add_defer(g->event, &g->defer, curl_glue_on_defer, g); - if (r < 0) - return r; - - (void) sd_event_source_set_description(g->defer, "curl-defer"); - - *glue = TAKE_PTR(g); - - return 0; -} - -int curl_glue_make(CURL **ret, const char *url, void *userdata) { - _cleanup_(curl_easy_cleanupp) CURL *c = NULL; - const char *useragent; - - assert(ret); - assert(url); - - c = curl_easy_init(); - if (!c) - return -ENOMEM; - - if (DEBUG_LOGGING) - (void) curl_easy_setopt(c, CURLOPT_VERBOSE, 1L); - - if (curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK) - return -EIO; - - if (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) - return -EIO; - - if (curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) - return -EIO; - - if (curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L) != CURLE_OK) - return -EIO; - - if (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) - return -EIO; - -#if LIBCURL_VERSION_NUM >= 0x075500 /* libcurl 7.85.0 */ - if (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) -#endif - return -EIO; - - *ret = TAKE_PTR(c); - return 0; -} - -int curl_glue_add(CurlGlue *g, CURL *c) { - assert(g); - assert(c); - - if (curl_multi_add_handle(g->curl, c) != CURLM_OK) - return -EIO; - - return 0; -} - -void curl_glue_remove_and_free(CurlGlue *g, CURL *c) { - assert(g); - - if (!c) - return; - - if (g->curl) - curl_multi_remove_handle(g->curl, c); - - curl_easy_cleanup(c); -} - -struct curl_slist *curl_slist_new(const char *first, ...) { - struct curl_slist *l; - va_list ap; - - if (!first) - return NULL; - - l = curl_slist_append(NULL, first); - if (!l) - return NULL; - - va_start(ap, first); - - for (;;) { - struct curl_slist *n; - const char *i; - - i = va_arg(ap, const char*); - if (!i) - break; - - n = curl_slist_append(l, i); - if (!n) { - va_end(ap); - curl_slist_free_all(l); - return NULL; - } - - l = n; - } - - va_end(ap); - return l; -} - -int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value) { - const char *p; - char *s; - - p = memory_startswith_no_case(contents, sz, field); - if (!p) - return 0; - - sz -= p - (const char*) contents; - - if (memchr(p, 0, sz)) - return 0; - - /* Skip over preceding whitespace */ - while (sz > 0 && strchr(WHITESPACE, p[0])) { - p++; - sz--; - } - - /* Truncate trailing whitespace */ - while (sz > 0 && strchr(WHITESPACE, p[sz-1])) - sz--; - - s = strndup(p, sz); - if (!s) - return -ENOMEM; - - *value = s; - return 1; -} - -int curl_parse_http_time(const char *t, usec_t *ret) { - assert(t); - assert(ret); - - time_t v = curl_getdate(t, NULL); - if (v == (time_t) -1) - return -EINVAL; - - if ((usec_t) v >= USEC_INFINITY / USEC_PER_SEC) /* check overflow */ - return -ERANGE; - - *ret = (usec_t) v * USEC_PER_SEC; - - return 0; -} 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..1e01da0c94faf 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; @@ -58,7 +59,6 @@ typedef struct TarExport { RateLimit progress_ratelimit; bool eof; - bool tried_splice; } TarExport; TarExport *tar_export_unref(TarExport *e) { @@ -74,7 +74,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,29 +188,8 @@ static int tar_export_process(TarExport *e) { assert(e); - if (!e->tried_splice && e->compress.type == IMPORT_COMPRESS_UNCOMPRESSED) { - - l = splice(e->tar_fd, NULL, e->output_fd, NULL, IMPORT_BUFFER_SIZE, 0); - if (l < 0) { - if (errno == EAGAIN) - return 0; - - e->tried_splice = true; - } else if (l == 0) { - r = tar_export_finish(e); - goto finish; - } else { - e->written_uncompressed += l; - e->written_compressed += l; - - tar_export_report_progress(e); - - return 0; - } - } - 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 +204,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 +261,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 +270,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 +315,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..0b6d27b9ffa8d 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,106 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.arg); + if (arg_compress < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown format: %s", optarg); + "Unknown format: %s", opts.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(opts.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", opts.arg); break; - case ARG_SYSTEM: + OPTION_COMMON_SYSTEM: arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_COMMON_USER: 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(&opts); 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(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/import-common.c b/src/import/import-common.c index 0a5144f94ecd6..c7465147c46b8 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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", @@ -391,13 +392,14 @@ int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags) { assert(path); assert(userns_fd); - r = import_make_foreign_userns(userns_fd); - if (r < 0) - return r; - /* Try the userns dance first, to remove foreign UID range owned trees */ - if (FLAGS_SET(flags, IMPORT_FOREIGN_UID)) + if (FLAGS_SET(flags, IMPORT_FOREIGN_UID)) { + r = import_make_foreign_userns(userns_fd); + if (r < 0) + return r; + (void) remove_tree_foreign(path, *userns_fd); + } r = rm_rf(path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD); if (r < 0) 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..dcb0f8c511a3d 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,13 +10,15 @@ #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" #include "install-file.h" #include "log.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.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,115 @@ 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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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=", opts.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=", opts.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=", opts.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(opts.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", opts.arg); break; - case ARG_SYSTEM: + OPTION_COMMON_SYSTEM: arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_COMMON_USER: arg_runtime_scope = RUNTIME_SCOPE_USER; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_image_root) { @@ -411,31 +385,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(&opts); 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(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..62aaf6bf86833 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -1,26 +1,28 @@ /* 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" #include "io-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #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..25e16f671a786 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,18 +9,18 @@ #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" #include "io-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "path-util.h" #include "pidref.h" #include "pretty-print.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,9 +365,20 @@ 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"); + + /* Try to check the actual exit code from the child process, to make debugging easier */ + if (r == -EPIPE && pidref_is_set(&i->tar_pid)) { + int q = pidref_wait_for_terminate_and_check("tar", &i->tar_pid, WAIT_LOG); + pidref_done(&i->tar_pid); + if (q < 0) + r = q; + else if (q != EXIT_SUCCESS) + r = -EPROTO; + } + goto finish; } diff --git a/src/import/import.c b/src/import/import.c index 5276f26977a3c..18276fa72d243 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,152 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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=", opts.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=", opts.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=", opts.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=", opts.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(opts.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", opts.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", opts.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(opts.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", opts.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", opts.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(opts.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", opts.arg); break; - case ARG_SYSTEM: + OPTION_COMMON_SYSTEM: arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_COMMON_USER: 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 +437,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(&opts); 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 +474,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(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/importctl.c b/src/import/importctl.c index c2ddd08f27624..58d996e8d74ef 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_DEFAULT_NOARG(verb_list_transfers, "list-transfers", "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,178 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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 = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.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(opts.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(opts.arg, "help")) + return DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX); - arg_format = optarg; + r = import_verify_from_string(opts.arg); + if (r < 0) + return log_error_errno(r, "Failed to parse --verify= setting: %s", opts.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(opts.arg, "uncompressed", "xz", "gzip", "bzip2", "zstd")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown format: %s", opts.arg); + arg_format = opts.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(opts.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", opts.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(opts.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", opts.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(&opts); 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 +1249,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 +1260,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(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..f133f276b4be2 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,13 @@ executables += [ 'extract' : files( 'oci-util.c', 'import-common.c', - 'import-compress.c', 'qcow2-util.c', ), - 'dependencies' : [common_deps, threads], }, libexec_template + { 'name' : 'systemd-pull', 'public' : true, 'sources' : files( - 'curl-util.c', 'pull.c', 'pull-common.c', 'pull-job.c', @@ -41,9 +30,7 @@ executables += [ 'pull-tar.c', ), 'objects' : ['systemd-importd'], - 'dependencies' : common_deps + [ - libopenssl, - ], + 'dependencies' : libopenssl_cflags, }, libexec_template + { 'name' : 'systemd-import', @@ -54,7 +41,6 @@ executables += [ 'import-tar.c', ), 'objects' : ['systemd-importd'], - 'dependencies' : common_deps, }, libexec_template + { 'name' : 'systemd-import-fs', @@ -63,7 +49,6 @@ executables += [ 'import-fs.c', ), 'objects' : ['systemd-importd'], - 'dependencies' : common_deps, }, libexec_template + { 'name' : 'systemd-export', @@ -74,33 +59,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..a3c5782e98938 100644 --- a/src/import/oci-util.c +++ b/src/import/oci-util.c @@ -51,13 +51,16 @@ bool oci_image_is_valid(const char *n) { int oci_registry_is_valid(const char *n) { int r; - if (!n) + if (isempty(n)) return false; const char *colon = strchr(n, ':'); if (!colon) return dns_name_is_valid(n); + if (colon == n) /* empty host, e.g. ":5000" */ + return false; + _cleanup_free_ char *s = strndup(n, colon - n); if (!s) return -ENOMEM; @@ -67,7 +70,10 @@ int oci_registry_is_valid(const char *n) { return r; uint16_t port; - return safe_atou16(s, &port) >= 0 && port != 0; + return safe_atou16_full(colon + 1, + 10 | SAFE_ATO_REFUSE_LEADING_WHITESPACE | + SAFE_ATO_REFUSE_PLUS_MINUS | SAFE_ATO_REFUSE_LEADING_ZERO, + &port) >= 0 && port != 0; } bool oci_tag_is_valid(const char *n) { @@ -79,10 +85,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 +159,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 +386,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 +395,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..2d36bf6e4ad02 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" @@ -528,7 +531,6 @@ int pull_verify(ImportVerify verify, PullJob *verity_job) { _cleanup_free_ char *fn = NULL; - VerificationStyle style; PullJob *verify_job; int r; @@ -576,11 +578,6 @@ int pull_verify(ImportVerify verify, return 0; assert(verify_job); - - r = verification_style_from_url(verify_job->url, &style); - if (r < 0) - return log_error_errno(r, "Failed to determine verification style from URL '%s': %m", verify_job->url); - assert(signature_job); assert(signature_job->state == PULL_JOB_DONE); @@ -622,6 +619,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 +673,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..7d83c994ff50a 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" @@ -50,13 +53,13 @@ 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); + curl_slot_unref(j->slot); + 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); @@ -80,11 +83,13 @@ static const char* pull_job_description(PullJob *j) { return j->description ?: j->url; } -static void pull_job_finish(PullJob *j, int ret) { +static int pull_job_finish(PullJob *j, int ret) { assert(j); + /* Returns 0 so callers in int-returning paths can `return pull_job_finish(...)` directly. */ + if (IN_SET(j->state, PULL_JOB_DONE, PULL_JOB_FAILED)) - return; + return 0; if (ret == 0) { j->state = PULL_JOB_DONE; @@ -97,6 +102,8 @@ static void pull_job_finish(PullJob *j, int ret) { if (j->on_finished) j->on_finished(j); + + return 0; } int pull_job_restart(PullJob *j, const char *new_url) { @@ -131,13 +138,12 @@ int pull_job_restart(PullJob *j, const char *new_url) { j->expected_content_length = UINT64_MAX; } - curl_glue_remove_and_free(j->glue, j->curl); - j->curl = NULL; + j->slot = curl_slot_unref(j->slot); - 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; } @@ -157,214 +163,6 @@ static uint64_t pull_job_content_length_effective(PullJob *j) { return j->content_length; } -void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { - PullJob *j = NULL; - char *scheme = NULL; - CURLcode code; - int r; - - if (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); - if (code != CURLE_OK || !scheme) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve URL scheme."); - goto finish; - } - - if (strcaseeq(scheme, "FILE") && result == CURLE_FILE_COULDNT_READ_FILE && j->on_not_found) { - _cleanup_free_ char *new_url = NULL; - - /* This resource wasn't found, but the implementer wants to maybe let us know a new URL, query for it. */ - r = j->on_not_found(j, &new_url); - if (r < 0) - goto finish; - if (r > 0) { /* A new url to use */ - assert(new_url); - - r = pull_job_restart(j, new_url); - if (r < 0) - goto finish; - - return; - } - - /* if this didn't work, handle like any other error below */ - } - - if (result != CURLE_OK) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Transfer failed: %s", curl_easy_strerror(result)); - goto finish; - } - - if (STRCASE_IN_SET(scheme, "HTTP", "HTTPS")) { - long status; - - code = 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)); - goto finish; - } - - if (http_status_etag_exists(status)) { - log_info("Image already downloaded. Skipping download."); - j->etag_exists = true; - r = 0; - goto finish; - } else if (http_status_need_authentication(status)) { - log_info("Access to image requires authentication."); - r = -ENOKEY; - goto finish; - } else if (status >= 300) { - - if (status == 404 && j->on_not_found) { - _cleanup_free_ char *new_url = NULL; - - /* This resource wasn't found, but the implementer wants to maybe let us know a new URL, query for it. */ - r = j->on_not_found(j, &new_url); - if (r < 0) - goto finish; - - if (r > 0) { /* A new url to use */ - assert(new_url); - - r = pull_job_restart(j, new_url); - if (r < 0) - goto finish; - - code = 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)); - goto finish; - } - - if (status == 0) - return; - } - } - - r = log_notice_errno( - status == 404 ? SYNTHETIC_ERRNO(ENOMEDIUM) : SYNTHETIC_ERRNO(EIO), /* Make the most common error recognizable */ - "HTTP request to %s failed with code %li.", j->url, status); - goto finish; - } else if (status < 200) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "HTTP request to %s finished with unexpected code %li.", j->url, status); - goto finish; - } - } - - if (j->state != PULL_JOB_RUNNING) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Premature connection termination."); - goto finish; - } - - uint64_t cl = pull_job_content_length_effective(j); - if (cl != UINT64_MAX && - cl != j->written_compressed) { - r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Download truncated."); - goto finish; - } - - if (j->checksum_ctx) { - unsigned checksum_len; - - iovec_done(&j->checksum); - j->checksum.iov_base = malloc(EVP_MAX_MD_SIZE); - if (!j->checksum.iov_base) { - r = log_oom(); - goto finish; - } - - r = 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; - } - assert(checksum_len <= EVP_MAX_MD_SIZE); - j->checksum.iov_len = checksum_len; - - if (DEBUG_LOGGING) { - _cleanup_free_ char *h = hexmem(j->checksum.iov_base, j->checksum.iov_len); - if (!h) { - r = log_oom(); - goto finish; - } - - log_debug("%s of %s is %s.", 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) { - r = log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Checksum of downloaded resource does not match expected checksum, yikes."); - goto finish; - } - } - - /* Do a couple of finishing disk operations, but only if we are the sole owner of the file (i.e. no - * offset is specified, which indicates we only own the file partially) */ - - if (j->disk_fd >= 0) { - - if (S_ISREG(j->disk_stat.st_mode)) { - - if (j->offset == UINT64_MAX) { - - if (j->written_compressed > 0) { - /* Make sure the file size is right, in case the file was sparse and - * we just moved to the last part. */ - if (ftruncate(j->disk_fd, j->written_uncompressed) < 0) { - r = log_error_errno(errno, "Failed to truncate file: %m"); - goto finish; - } - } - - if (j->etag) - (void) fsetxattr(j->disk_fd, "user.source_etag", j->etag, strlen(j->etag), 0); - if (j->url) - (void) fsetxattr(j->disk_fd, "user.source_url", j->url, strlen(j->url), 0); - - if (j->mtime != 0) { - struct timespec ut; - - timespec_store(&ut, j->mtime); - - if (futimens(j->disk_fd, (struct timespec[]) { ut, ut }) < 0) - log_debug_errno(errno, "Failed to adjust atime/mtime of created image, ignoring: %m"); - - r = fd_setcrtime(j->disk_fd, j->mtime); - if (r < 0) - log_debug_errno(r, "Failed to adjust crtime of created image, ignoring: %m"); - } - } - - if (j->sync) { - r = fsync_full(j->disk_fd); - if (r < 0) { - log_error_errno(r, "Failed to synchronize file to disk: %m"); - goto finish; - } - } - - } else if (S_ISBLK(j->disk_stat.st_mode) && j->sync) { - - if (fsync(j->disk_fd) < 0) { - r = log_error_errno(errno, "Failed to synchronize block device: %m"); - goto finish; - } - } - } - - log_info("Acquired %s for %s.", FORMAT_BYTES(j->written_uncompressed), pull_job_description(j)); - - r = 0; - -finish: - pull_job_finish(j, r); -} - static int pull_job_write_uncompressed(const void *p, size_t sz, void *userdata) { PullJob *j = ASSERT_PTR(userdata); bool too_much = false; @@ -447,13 +245,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 +282,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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + 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."); @@ -497,18 +299,12 @@ static int pull_job_open_disk(PullJob *j) { return 0; } -static int pull_job_detect_compression(PullJob *j) { +static int pull_job_begin_running(PullJob *j) { int r; assert(j); - - r = import_uncompress_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)); + assert(j->state == PULL_JOB_ANALYZING); + assert(j->compress); r = pull_job_open_disk(j); if (r < 0) @@ -526,6 +322,207 @@ static int pull_job_detect_compression(PullJob *j) { return 0; } +static int pull_job_curl_on_finished(CurlSlot *slot, CURL *curl, CURLcode result, void *userdata) { + PullJob *j = ASSERT_PTR(userdata); + char *scheme = NULL; + CURLcode code; + int r; + + if (IN_SET(j->state, PULL_JOB_DONE, PULL_JOB_FAILED)) + return 0; + + code = sym_curl_easy_getinfo(curl, CURLINFO_SCHEME, &scheme); + if (code != CURLE_OK || !scheme) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve URL scheme.")); + + if (strcaseeq(scheme, "FILE") && result == CURLE_FILE_COULDNT_READ_FILE && j->on_not_found) { + _cleanup_free_ char *new_url = NULL; + + /* This resource wasn't found, but the implementer wants to maybe let us know a new URL, query for it. */ + r = j->on_not_found(j, &new_url); + if (r < 0) + return pull_job_finish(j, r); + if (r > 0) { /* A new url to use */ + assert(new_url); + + r = pull_job_restart(j, new_url); + if (r < 0) + return pull_job_finish(j, r); + + return 0; + } + + /* if this didn't work, handle like any other error below */ + } + + if (result != CURLE_OK) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Transfer failed: %s", sym_curl_easy_strerror(result))); + + if (STRCASE_IN_SET(scheme, "HTTP", "HTTPS")) { + long status; + + code = sym_curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status); + if (code != CURLE_OK) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", sym_curl_easy_strerror(code))); + + if (http_status_etag_exists(status)) { + log_info("Image already downloaded. Skipping download."); + j->etag_exists = true; + return pull_job_finish(j, 0); + } else if (http_status_need_authentication(status)) { + log_info("Access to image requires authentication."); + return pull_job_finish(j, -ENOKEY); + } else if (status >= 300) { + + if (status == 404 && j->on_not_found) { + _cleanup_free_ char *new_url = NULL; + + /* This resource wasn't found, but the implementer wants to maybe let us know a new URL, query for it. */ + r = j->on_not_found(j, &new_url); + if (r < 0) + return pull_job_finish(j, r); + + if (r > 0) { /* A new url to use */ + assert(new_url); + + r = pull_job_restart(j, new_url); + if (r < 0) + return pull_job_finish(j, r); + + code = sym_curl_easy_getinfo(curl_slot_get_easy(j->slot), CURLINFO_RESPONSE_CODE, &status); + if (code != CURLE_OK) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve response code: %s", sym_curl_easy_strerror(code))); + + if (status == 0) + return 0; + } + } + + return pull_job_finish(j, log_notice_errno( + status == 404 ? SYNTHETIC_ERRNO(ENOMEDIUM) : SYNTHETIC_ERRNO(EIO), /* Make the most common error recognizable */ + "HTTP request to %s failed with code %li.", j->url, status)); + } else if (status < 200) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "HTTP request to %s finished with unexpected code %li.", j->url, status)); + } + + if (j->state == PULL_JOB_ANALYZING) { + /* When curl finished the download while we were still looking for a compression magic + * header the content isn't compressed but should be written out as is. */ + assert(result == CURLE_OK); + + r = decompressor_force_off(&j->compress); + if (r < 0) + return pull_job_finish(j, r); + + r = pull_job_begin_running(j); + if (r < 0) + return pull_job_finish(j, r); + } + + if (j->state != PULL_JOB_RUNNING) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Premature connection termination.")); + + uint64_t cl = pull_job_content_length_effective(j); + if (cl != UINT64_MAX && + cl != j->written_compressed) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Download truncated.")); + + if (j->checksum_ctx) { + unsigned checksum_len; + + iovec_done(&j->checksum); + j->checksum.iov_base = malloc(EVP_MAX_MD_SIZE); + if (!j->checksum.iov_base) + return pull_job_finish(j, log_oom()); + + r = sym_EVP_DigestFinal_ex(j->checksum_ctx, j->checksum.iov_base, &checksum_len); + if (r == 0) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get checksum.")); + assert(checksum_len <= EVP_MAX_MD_SIZE); + j->checksum.iov_len = checksum_len; + + if (DEBUG_LOGGING) { + _cleanup_free_ char *h = hexmem(j->checksum.iov_base, j->checksum.iov_len); + if (!h) + return pull_job_finish(j, log_oom()); + + 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_equal(&j->checksum, &j->expected_checksum)) + return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Checksum of downloaded resource does not match expected checksum, yikes.")); + } + + /* Do a couple of finishing disk operations, but only if we are the sole owner of the file (i.e. no + * offset is specified, which indicates we only own the file partially) */ + + if (j->disk_fd >= 0) { + + if (S_ISREG(j->disk_stat.st_mode)) { + + if (j->offset == UINT64_MAX) { + + if (j->written_compressed > 0) { + /* Make sure the file size is right, in case the file was sparse and + * we just moved to the last part. */ + if (ftruncate(j->disk_fd, j->written_uncompressed) < 0) + return pull_job_finish(j, log_error_errno(errno, "Failed to truncate file: %m")); + } + + if (j->etag) + (void) fsetxattr(j->disk_fd, "user.source_etag", j->etag, strlen(j->etag), 0); + if (j->url) + (void) fsetxattr(j->disk_fd, "user.source_url", j->url, strlen(j->url), 0); + + if (j->mtime != 0) { + struct timespec ut; + + timespec_store(&ut, j->mtime); + + if (futimens(j->disk_fd, (struct timespec[]) { ut, ut }) < 0) + log_debug_errno(errno, "Failed to adjust atime/mtime of created image, ignoring: %m"); + + r = fd_setcrtime(j->disk_fd, j->mtime); + if (r < 0) + log_debug_errno(r, "Failed to adjust crtime of created image, ignoring: %m"); + } + } + + if (j->sync) { + r = fsync_full(j->disk_fd); + if (r < 0) + return pull_job_finish(j, log_error_errno(r, "Failed to synchronize file to disk: %m")); + } + + } else if (S_ISBLK(j->disk_stat.st_mode) && j->sync) { + + if (fsync(j->disk_fd) < 0) + return pull_job_finish(j, log_error_errno(errno, "Failed to synchronize block device: %m")); + } + } + + log_info("Acquired %s for %s.", FORMAT_BYTES(j->written_uncompressed), pull_job_description(j)); + + return pull_job_finish(j, 0); +} + +static int pull_job_detect_compression(PullJob *j) { + int r; + + assert(j); + + 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", compression_to_string(compressor_type(j->compress))); + + return pull_job_begin_running(j); +} + static size_t pull_job_write_callback(void *contents, size_t size, size_t nmemb, void *userdata) { PullJob *j = ASSERT_PTR(userdata); size_t sz = size * nmemb; @@ -588,9 +585,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(curl_slot_get_easy(j->slot), 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 +777,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; @@ -802,7 +799,8 @@ int pull_job_begin(PullJob *j) { if (j->state != PULL_JOB_INIT) return -EBUSY; - r = curl_glue_make(&j->curl, j->url, j); + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + r = curl_glue_make(&easy, j->url); if (r < 0) return r; @@ -823,34 +821,35 @@ 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(easy, 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(easy, 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(easy, 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(easy, 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(easy, 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(easy, 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(easy, CURLOPT_XFERINFODATA, j) != CURLE_OK) return -EIO; - if (curl_easy_setopt(j->curl, CURLOPT_NOPROGRESS, 0L) != CURLE_OK) + if (sym_curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 0L) != CURLE_OK) return -EIO; - r = curl_glue_add(j->glue, j->curl); + r = curl_glue_perform_async(j->glue, easy, pull_job_curl_on_finished, j, &j->slot); if (r < 0) return r; + TAKE_PTR(easy); j->state = PULL_JOB_ANALYZING; diff --git a/src/import/pull-job.h b/src/import/pull-job.h index ea58b62f3bfa9..00d001680ff20 100644 --- a/src/import/pull-job.h +++ b/src/import/pull-job.h @@ -3,12 +3,10 @@ #include #include +#include #include "shared-forward.h" -#include "import-compress.h" -#include "openssl-util.h" -typedef struct CurlGlue CurlGlue; typedef struct PullJob PullJob; typedef void (*PullJobFinished)(PullJob *job); @@ -47,7 +45,7 @@ typedef struct PullJob { PullJobNotFound on_not_found; CurlGlue *glue; - CURL *curl; + CurlSlot *slot; struct curl_slist *request_header; char *etag; @@ -73,7 +71,7 @@ typedef struct PullJob { usec_t mtime; char *content_type; - ImportCompress compress; + Compressor *compress; unsigned progress_percent; usec_t start_usec; @@ -96,8 +94,6 @@ PullJob* pull_job_unref(PullJob *job); int pull_job_begin(PullJob *j); -void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result); - void pull_job_close_disk_fd(PullJob *j); int pull_job_add_request_header(PullJob *j, const char *hdr); diff --git a/src/import/pull-oci.c b/src/import/pull-oci.c index e9e0a1c6b3419..b6fa71c902965 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" @@ -19,7 +21,7 @@ #include "install-file.h" #include "io-util.h" #include "json-util.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "oci-util.h" #include "ordered-set.h" #include "path-util.h" @@ -29,6 +31,7 @@ #include "pull-oci.h" #include "rm-rf.h" #include "set.h" +#include "sha256.h" #include "signal-util.h" #include "stat-util.h" #include "string-util.h" @@ -192,15 +195,12 @@ int oci_pull_new( .userns_fd = -EBADF, }; - i->glue->on_finished = pull_job_curl_on_finished; - i->glue->userdata = i; - *ret = TAKE_PTR(i); 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 +214,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 +391,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 +489,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 +783,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 +958,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 +1193,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 +1340,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..900be21f59fc3 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,8 +13,9 @@ #include "import-common.h" #include "import-util.h" #include "install-file.h" +#include "iovec-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "pull-common.h" #include "pull-job.h" #include "pull-raw.h" @@ -146,9 +149,6 @@ int raw_pull_new( .offset = UINT64_MAX, }; - p->glue->on_finished = pull_job_curl_on_finished; - p->glue->userdata = p; - *ret = TAKE_PTR(p); return 0; @@ -543,31 +543,11 @@ static void raw_pull_job_on_finished(PullJob *j) { return; if (p->signature_job && p->signature_job->error != 0) { - VerificationStyle style; - PullJob *verify_job; - - /* The signature job failed. Let's see if we actually need it */ - - verify_job = p->checksum_job ?: p->raw_job; /* if the checksum job doesn't exist this must be - * because the main job is the checksum file - * itself */ - - assert(verify_job); + assert(p->checksum_job || p->raw_job); - r = verification_style_from_url(verify_job->url, &style); - if (r < 0) { - log_error_errno(r, "Failed to determine verification style from checksum URL: %m"); - goto finish; - } - - if (style == VERIFICATION_PER_DIRECTORY) { /* A failed signature file download only matters - * in per-directory verification mode, since only - * then the signature is detached, and thus a file - * of its own. */ - r = log_error_errno(p->signature_job->error, - "Failed to retrieve signature file, cannot verify. (Try --verify=no?)"); - goto finish; - } + r = log_error_errno(p->signature_job->error, + "Failed to retrieve signature file, cannot verify. (Try --verify=no?)"); + goto finish; } PullJob *jj; diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index bfc218b6bcbd0..6518c28d72918 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" @@ -14,7 +16,7 @@ #include "fs-util.h" #include "install-file.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "path-util.h" #include "pidref.h" #include "pretty-print.h" @@ -151,9 +153,6 @@ int tar_pull_new( .progress_ratelimit = { 100 * USEC_PER_MSEC, 1 }, }; - p->glue->on_finished = pull_job_curl_on_finished; - p->glue->userdata = p; - *ret = TAKE_PTR(p); return 0; @@ -280,7 +279,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 +442,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 @@ -453,24 +452,11 @@ static void tar_pull_job_on_finished(PullJob *j) { return; if (p->signature_job && p->signature_job->error != 0) { - VerificationStyle style; - assert(p->checksum_job); - r = verification_style_from_url(p->checksum_job->url, &style); - if (r < 0) { - log_error_errno(r, "Failed to determine verification style from checksum URL: %m"); - goto finish; - } - - if (style == VERIFICATION_PER_DIRECTORY) { /* A failed signature file download only matters - * in per-directory verification mode, since only - * then the signature is detached, and thus a file - * of its own. */ - r = log_error_errno(p->signature_job->error, - "Failed to retrieve signature file, cannot verify. (Try --verify=no?)"); - goto finish; - } + r = log_error_errno(p->signature_job->error, + "Failed to retrieve signature file, cannot verify. (Try --verify=no?)"); + goto finish; } pull_job_close_disk_fd(p->tar_job); diff --git a/src/import/pull.c b/src/import/pull.c index e2f816152698f..09294bc6fc573 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,79 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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(opts.arg); if (v < 0) { _cleanup_free_ void *h = NULL; size_t n; @@ -445,17 +397,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(opts.arg, &h, &n); if (r < 0 || n == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid verification setting: %s", optarg); + "Invalid verification setting: %s", opts.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 +416,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=", opts.arg, NULL); if (r < 0) return r; @@ -474,8 +425,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=", opts.arg, NULL); if (r < 0) return r; @@ -486,118 +437,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=", opts.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=", opts.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=", opts.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=", opts.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=", opts.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=", opts.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(opts.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", opts.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", opts.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(opts.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", opts.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", opts.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(opts.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", opts.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(opts.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", opts.arg); SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, r); auto_keep_download = false; break; - case ARG_SYSTEM: + OPTION_COMMON_SYSTEM: arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_COMMON_USER: 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 +576,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(&opts); return 1; } @@ -658,18 +605,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 +613,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(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-oci-util.c b/src/import/test-oci-util.c index 395b622b42804..3fa62eb1b5b4e 100644 --- a/src/import/test-oci-util.c +++ b/src/import/test-oci-util.c @@ -19,4 +19,33 @@ TEST(urlescape) { test_urlescape_one("müffel", "m%c3%bcffel"); } +TEST(oci_registry_is_valid) { + /* plain hostname — valid */ + assert_se(oci_registry_is_valid("localhost") > 0); + assert_se(oci_registry_is_valid("registry.example.com") > 0); + + /* host:port — valid */ + assert_se(oci_registry_is_valid("localhost:5000") > 0); + assert_se(oci_registry_is_valid("registry.example.com:443") > 0); + assert_se(oci_registry_is_valid("registry.io:1") > 0); + assert_se(oci_registry_is_valid("registry.io:65535") > 0); + + /* port 0 — invalid */ + assert_se(oci_registry_is_valid("localhost:0") == 0); + + /* port overflow */ + assert_se(oci_registry_is_valid("localhost:65536") == 0); + + /* non-decimal port forms — rejected */ + assert_se(oci_registry_is_valid("localhost:0x50") == 0); /* hex */ + assert_se(oci_registry_is_valid("localhost:017") == 0); /* leading zero */ + assert_se(oci_registry_is_valid("localhost:+80") == 0); /* plus sign */ + assert_se(oci_registry_is_valid("localhost: 80") == 0); /* leading space */ + + /* invalid hostname */ + assert_se(oci_registry_is_valid(":5000") == 0); + assert_se(oci_registry_is_valid("") == 0); + assert_se(oci_registry_is_valid(NULL) == 0); +} + DEFINE_TEST_MAIN(LOG_DEBUG); 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/glibc/stdio.h b/src/include/glibc/stdio.h new file mode 100644 index 0000000000000..7e45cb3ba28de --- /dev/null +++ b/src/include/glibc/stdio.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* Force glibc's stdio.h to route sscanf/fscanf to the old __isoc99_* siblings (GLIBC_2.7) rather + * than the newer __isoc23_* ones (GLIBC_2.38). The only behavioural difference is "0b" prefix + * support in %i conversions, which we don't use. We include features.h first so the macro is set + * to its normal value, then override it before stdio.h's body evaluates __GLIBC_USE(C23_STRTOL). + * + * The macro was named __GLIBC_USE_C2X_STRTOL on glibc 2.38–2.39 and renamed to the C23 spelling + * in glibc 2.40; clear both so this override works across that range. */ + +#include +#undef __GLIBC_USE_C2X_STRTOL +#define __GLIBC_USE_C2X_STRTOL 0 +#undef __GLIBC_USE_C23_STRTOL +#define __GLIBC_USE_C23_STRTOL 0 + +#include_next /* IWYU pragma: export */ diff --git a/src/include/glibc/stdlib.h b/src/include/glibc/stdlib.h new file mode 100644 index 0000000000000..a3ad2a3b5ab9a --- /dev/null +++ b/src/include/glibc/stdlib.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* Force glibc's stdlib.h to leave strtol/strtoul/strtoll/strtoull as their original GLIBC_2.2.5 + * symbols rather than redirect to __isoc23_* (GLIBC_2.38). The only behavioural difference is + * "0b" prefix support in base 0/2 parsing, which we don't use. + * + * The macro was named __GLIBC_USE_C2X_STRTOL on glibc 2.38–2.39 and renamed to the C23 spelling + * in glibc 2.40; clear both so this override works across that range. */ + +#include +#undef __GLIBC_USE_C2X_STRTOL +#define __GLIBC_USE_C2X_STRTOL 0 +#undef __GLIBC_USE_C23_STRTOL +#define __GLIBC_USE_C23_STRTOL 0 + +#include_next /* IWYU pragma: export */ diff --git a/src/include/musl/getopt.h b/src/include/musl/getopt.h deleted file mode 100644 index 6aed1dfd262ad..0000000000000 --- a/src/include/musl/getopt.h +++ /dev/null @@ -1,29 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -/* getopt() is provided both in getopt.h and unistd.h. Hence, we need to tentatively undefine it. */ -#undef getopt - -#include_next - -/* musl's getopt() always behaves POSIXLY_CORRECT mode, and stops parsing arguments when a non-option string - * found. Let's always use getopt_long(). */ -int getopt_fix(int argc, char * const *argv, const char *optstring); -#define getopt(argc, argv, optstring) getopt_fix(argc, argv, optstring) - -/* musl's getopt_long() behaves something different in handling optional arguments. - * ======== - * $ journalctl _PID=1 _COMM=systemd --since 19:19:01 -n all --follow - * Failed to add match 'all': Invalid argument - * ======== - * Here, we introduce getopt_long_fix() that reorders the passed arguments to make getopt_long() provided by - * musl works as what we expect. */ -int getopt_long_fix( - int argc, - char * const *argv, - const char *optstring, - const struct option *longopts, - int *longindex); - -#define getopt_long(argc, argv, optstring, longopts, longindex) \ - getopt_long_fix(argc, argv, optstring, longopts, longindex) diff --git a/src/include/musl/limits.h b/src/include/musl/limits.h new file mode 100644 index 0000000000000..9620a3b0accce --- /dev/null +++ b/src/include/musl/limits.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include_next /* IWYU pragma: export */ + +#include +#include + +/* musl defines SSIZE_MAX as LONG_MAX, so its type is always long. However, on 32-bit architectures, musl + * defines ssize_t as int. Strictly speaking, this is not a bug in musl. POSIX only requires SSIZE_MAX to + * evaluate to the maximum value representable by ssize_t; it does not require SSIZE_MAX itself to have type + * ssize_t. However, our code assumes that SSIZE_MAX has type ssize_t, as is the case with glibc. Cast the + * value explicitly so that SSIZE_MAX has type ssize_t. */ +static_assert(SSIZE_MAX == LONG_MAX, ""); +#undef SSIZE_MAX +#define SSIZE_MAX ((ssize_t) LONG_MAX) diff --git a/src/include/musl/signal.h b/src/include/musl/signal.h new file mode 100644 index 0000000000000..c69a3b285641f --- /dev/null +++ b/src/include/musl/signal.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include_next /* IWYU pragma: export */ + +#ifndef ILL_BADIADDR +#define ILL_BADIADDR 9 +#endif + +#ifndef FPE_FLTUNK +#define FPE_FLTUNK 14 +#endif + +#ifndef FPE_CONDTRAP +#define FPE_CONDTRAP 15 +#endif + +#ifndef SEGV_ACCADI +#define SEGV_ACCADI 5 +#endif + +#ifndef SEGV_ADIDERR +#define SEGV_ADIDERR 6 +#endif + +#ifndef SEGV_ADIPERR +#define SEGV_ADIPERR 7 +#endif diff --git a/src/include/musl/stdio.h b/src/include/musl/stdio.h index 2ff95e952a4c2..d6a3b54027a3d 100644 --- a/src/include/musl/stdio.h +++ b/src/include/musl/stdio.h @@ -3,15 +3,6 @@ #include_next -#if !HAVE_RENAMEAT2 -# define RENAME_NOREPLACE (1 << 0) -# define RENAME_EXCHANGE (1 << 1) -# define RENAME_WHITEOUT (1 << 2) - -int missing_renameat2(int __oldfd, const char *__old, int __newfd, const char *__new, unsigned __flags); -# define renameat2 missing_renameat2 -#endif - /* When a stream is opened read-only under glibc, fputs() and friends fail with EBADF. However, they * succeed under musl. We rely on the glibc behavior in the code base. The following _check_writable() * functions first check if the passed stream is writable, and refuse to write with EBADF if not. */ diff --git a/src/include/musl/sys/stat.h b/src/include/musl/sys/stat.h index 610dd3e69958a..385e16f3f8757 100644 --- a/src/include/musl/sys/stat.h +++ b/src/include/musl/sys/stat.h @@ -3,64 +3,9 @@ #include_next -#include -#include - /* musl's sys/stat.h does not include linux/stat.h, and unfortunately they conflict with each other. * Hence, some relatively new macros need to be explicitly defined here. */ -/* Before 23ab04a8630225371455d5f4538fd078665bb646, statx.stx_mnt_id is not defined. */ -#ifndef STATX_MNT_ID -static_assert(offsetof(struct statx, __pad1) == offsetof(struct statx, stx_dev_minor) + sizeof(uint32_t), ""); -#define stx_mnt_id __pad1[0] -#endif - -#ifndef STATX_MNT_ID -#define STATX_MNT_ID 0x00001000U -#endif -#ifndef STATX_DIOALIGN -#define STATX_DIOALIGN 0x00002000U -#endif -#ifndef STATX_MNT_ID_UNIQUE -#define STATX_MNT_ID_UNIQUE 0x00004000U -#endif -#ifndef STATX_SUBVOL -#define STATX_SUBVOL 0x00008000U -#endif -#ifndef STATX_WRITE_ATOMIC -#define STATX_WRITE_ATOMIC 0x00010000U -#endif #ifndef STATX_DIO_READ_ALIGN #define STATX_DIO_READ_ALIGN 0x00020000U #endif - -#ifndef STATX_ATTR_COMPRESSED -#define STATX_ATTR_COMPRESSED 0x00000004 -#endif -#ifndef STATX_ATTR_IMMUTABLE -#define STATX_ATTR_IMMUTABLE 0x00000010 -#endif -#ifndef STATX_ATTR_APPEND -#define STATX_ATTR_APPEND 0x00000020 -#endif -#ifndef STATX_ATTR_NODUMP -#define STATX_ATTR_NODUMP 0x00000040 -#endif -#ifndef STATX_ATTR_ENCRYPTED -#define STATX_ATTR_ENCRYPTED 0x00000800 -#endif -#ifndef STATX_ATTR_AUTOMOUNT -#define STATX_ATTR_AUTOMOUNT 0x00001000 -#endif -#ifndef STATX_ATTR_MOUNT_ROOT -#define STATX_ATTR_MOUNT_ROOT 0x00002000 -#endif -#ifndef STATX_ATTR_VERITY -#define STATX_ATTR_VERITY 0x00100000 -#endif -#ifndef STATX_ATTR_DAX -#define STATX_ATTR_DAX 0x00200000 -#endif -#ifndef STATX_ATTR_WRITE_ATOMIC -#define STATX_ATTR_WRITE_ATOMIC 0x00400000 -#endif diff --git a/src/include/musl/unistd.h b/src/include/musl/unistd.h index e58fee356af91..175661442dee4 100644 --- a/src/include/musl/unistd.h +++ b/src/include/musl/unistd.h @@ -1,16 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -/* getopt() is provided both in getopt.h and unistd.h. Hence, we need to tentatively undefine it. */ -#undef getopt - #include_next -/* musl's getopt() always behaves POSIXLY_CORRECT mode, and stops parsing arguments when a non-option string - * found. Let's always use getopt_long(). */ -int getopt_fix(int argc, char * const *argv, const char *optstring); -#define getopt(argc, argv, optstring) getopt_fix(argc, argv, optstring) - int missing_close_range(unsigned first_fd, unsigned end_fd, unsigned flags); #define close_range missing_close_range diff --git a/src/include/override/fcntl.h b/src/include/override/fcntl.h index b41f364534174..bf42009022db0 100644 --- a/src/include/override/fcntl.h +++ b/src/include/override/fcntl.h @@ -2,6 +2,8 @@ #pragma once #include_next /* IWYU pragma: export */ +#include /* IWYU pragma: export */ +#include /* This is defined since glibc-2.41. */ #ifndef F_DUPFD_QUERY @@ -22,3 +24,12 @@ #ifndef AT_HANDLE_MNT_ID_UNIQUE #define AT_HANDLE_MNT_ID_UNIQUE 0x001 /* Return the u64 unique mount ID. */ #endif + +#ifndef FD_NSFS_ROOT +#define FD_NSFS_ROOT -10003 /* Root of the nsfs filesystem */ +#endif + +/* Defined since glibc-2.42. + * Supported since kernel v5.6 (fddb5d430ad9fa91b49b1d34d0202ffe2fa0e179). */ +int openat2_shim(int dfd, const char *filename, const struct open_how *how, size_t usize); +#define openat2 openat2_shim diff --git a/src/include/override/linux/nsfs.h b/src/include/override/linux/nsfs.h index a256df1c6f9fa..163333d362843 100644 --- a/src/include/override/linux/nsfs.h +++ b/src/include/override/linux/nsfs.h @@ -12,3 +12,8 @@ #define PROC_PID_INIT_INO ((ino_t) UINT32_C(0xEFFFFFFC)) #define PROC_CGROUP_INIT_INO ((ino_t) UINT32_C(0xEFFFFFFB)) #define PROC_TIME_INIT_INO ((ino_t) UINT32_C(0xEFFFFFFA)) + +/* From kernel-internal include/linux/exportfs.h, not part of uapi. */ +#ifndef FILEID_NSFS +#define FILEID_NSFS 0xf1 +#endif diff --git a/src/include/override/sched.h b/src/include/override/sched.h index 08f4576eca1e3..3eed39311868e 100644 --- a/src/include/override/sched.h +++ b/src/include/override/sched.h @@ -51,10 +51,8 @@ int __clone2(int (*fn)(void *), void *stack_base, size_t stack_size, int flags, /* Defined since glibc-2.41. * Supported since kernel 3.14 (e6cfc0295c7d51b008999a8b13a44fb43f8685ea). */ -#if !HAVE_SCHED_SETATTR -int missing_sched_setattr(pid_t pid, struct sched_attr *attr, unsigned flags); -# define sched_setattr missing_sched_setattr -#endif +int sched_setattr_shim(pid_t pid, struct sched_attr *attr, unsigned flags); +#define sched_setattr sched_setattr_shim /* f0e1a0643a59bf1f922fa209cec86a170b784f3f (6.12), * defined in sched.h in glibc since glibc-2.41. */ diff --git a/src/include/override/signal.h b/src/include/override/signal.h index 436b49a519993..b6a6a9ce31a9f 100644 --- a/src/include/override/signal.h +++ b/src/include/override/signal.h @@ -3,7 +3,28 @@ #include_next /* IWYU pragma: export */ -#if !HAVE_RT_TGSIGQUEUEINFO -int missing_rt_tgsigqueueinfo(pid_t tgid, pid_t tid, int sig, siginfo_t *info); -# define rt_tgsigqueueinfo missing_rt_tgsigqueueinfo +/* Defined since glibc-2.39. */ +#ifndef SEGV_CPERR +#define SEGV_CPERR 10 #endif + +#ifndef SI_DETHREAD +#define SI_DETHREAD -7 +#endif + +/* Defined since glibc-2.43. */ +#ifndef TRAP_PERF +#define TRAP_PERF 6 +#endif + +/* Defined since glibc-2.33. */ +#ifndef SYS_SECCOMP +#define SYS_SECCOMP 1 +#endif + +#ifndef SYS_USER_DISPATCH +#define SYS_USER_DISPATCH 2 +#endif + +int rt_tgsigqueueinfo_shim(pid_t tgid, pid_t tid, int sig, siginfo_t *info); +#define rt_tgsigqueueinfo rt_tgsigqueueinfo_shim diff --git a/src/include/override/spawn.h b/src/include/override/spawn.h new file mode 100644 index 0000000000000..463825856c511 --- /dev/null +++ b/src/include/override/spawn.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include_next /* IWYU pragma: export */ + +/* pidfd_spawn() and posix_spawnattr_setcgroup_np() were added in glibc 2.39. Redirect to shims that + * return ENOSYS at runtime when the libc symbols aren't available, so callers don't need to worry + * about the libc version. */ +int pidfd_spawn_shim(pid_t *restrict pidfd, const char *restrict path, + const posix_spawn_file_actions_t *restrict file_actions, + const posix_spawnattr_t *restrict attrp, + char *const argv[restrict], char *const envp[restrict]); +#define pidfd_spawn pidfd_spawn_shim + +int posix_spawnattr_setcgroup_np_shim(posix_spawnattr_t *attr, int cgroup); +#define posix_spawnattr_setcgroup_np posix_spawnattr_setcgroup_np_shim + +/* Defined in since glibc 2.39. */ +#ifndef POSIX_SPAWN_SETCGROUP +# define POSIX_SPAWN_SETCGROUP 0x100 +#endif diff --git a/src/include/override/sys/bpf.h b/src/include/override/sys/bpf.h index 56e1466b89006..eaeb89a5a8fb6 100644 --- a/src/include/override/sys/bpf.h +++ b/src/include/override/sys/bpf.h @@ -5,7 +5,5 @@ #include /* Supported since kernel v3.18 (749730ce42a2121e1c88350d69478bff3994b10a). */ -#if !HAVE_BPF -int missing_bpf(int cmd, union bpf_attr *attr, size_t size); -# define bpf missing_bpf -#endif +int bpf_shim(int cmd, union bpf_attr *attr, size_t size); +#define bpf bpf_shim diff --git a/src/include/override/sys/epoll.h b/src/include/override/sys/epoll.h new file mode 100644 index 0000000000000..0ed9c27a4b2e7 --- /dev/null +++ b/src/include/override/sys/epoll.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include_next /* IWYU pragma: export */ + +/* epoll_pwait2() was added to glibc 2.35. Redirect to a shim that sets errno=ENOSYS at runtime when + * the libc symbol isn't available, so callers don't need to worry about the libc version. */ +int epoll_pwait2_shim(int fd, struct epoll_event *events, int maxevents, + const struct timespec *timeout, const sigset_t *sigmask); +#define epoll_pwait2 epoll_pwait2_shim diff --git a/src/include/override/sys/generate-syscall.py b/src/include/override/sys/generate-syscall.py index 6f449f9dc1330..ba44c93eb26da 100755 --- a/src/include/override/sys/generate-syscall.py +++ b/src/include/override/sys/generate-syscall.py @@ -1,34 +1,40 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later -import sys import functools +import sys # We only generate numbers for a dozen or so syscalls SYSCALLS = [ 'fchmodat2', # defined in glibc header since glibc-2.39 + 'kexec_file_load', 'open_tree_attr', 'quotactl_fd', # defined in glibc header since glibc-2.35 'removexattrat', 'setxattrat', -] +] # fmt: skip + def dictify(f): def wrap(*args, **kwargs): return dict(f(*args, **kwargs)) + return functools.update_wrapper(wrap, f) + @dictify def parse_syscall_table(filename): print(f'Reading {filename}…') - for line in open(filename): - items = line.split() - if len(items) >= 2: - yield items[0], int(items[1]) + with open(filename) as f: + for line in f: + items = line.split() + if len(items) >= 2: + yield items[0], int(items[1]) + def parse_syscall_tables(filenames): - return {filename.split('-')[-1][:-4]: parse_syscall_table(filename) - for filename in filenames} + return {filename.split('-')[-1][:-4]: parse_syscall_table(filename) for filename in filenames} + HEADER = '''\ /* SPDX-License-Identifier: LGPL-2.1-or-later @@ -44,7 +50,7 @@ def parse_syscall_tables(filenames): #include_next /* IWYU pragma: export */ -#ifdef ARCH_MIPS +#ifdef __mips__ #include #endif @@ -107,12 +113,15 @@ def parse_syscall_tables(filenames): # else # define systemd_NR_{syscall} {nr_x86_64} # endif +''' + +DEF_TEMPLATE_C = '''\ # elif !defined(missing_arch_template) -%s +# warning "{syscall}() syscall number is unknown for your architecture" # endif ''' -DEF_TEMPLATE_C = '''\ +DEF_TEMPLATE_D = '''\ /* may be an (invalid) negative number due to libseccomp, see PR 13319 */ # if defined __NR_{syscall} && __NR_{syscall} >= 0 @@ -129,23 +138,29 @@ def parse_syscall_tables(filenames): # endif #endif''' -DEF_TEMPLATE = (DEF_TEMPLATE_A + - DEF_TEMPLATE_B % '# warning "{syscall}() syscall number is unknown for your architecture"' + - DEF_TEMPLATE_C) +DEF_TEMPLATE = DEF_TEMPLATE_A + DEF_TEMPLATE_B + DEF_TEMPLATE_C + DEF_TEMPLATE_D -ARCH_CHECK = '''\ +ARCH_CHECK_A = '''\ /* Note: if this code looks strange, this is because it is derived from the same * template as the per-syscall blocks below. */ -''' + '\n'.join(line for line in DEF_TEMPLATE_B.splitlines() - if ' define ' not in line) % '''\ +''' + +ARCH_CHECK_B = '\n'.join(line for line in DEF_TEMPLATE_B.splitlines() if ' define ' not in line) + +ARCH_CHECK_C = '''\ + +# else # warning "Current architecture is missing from the template" -# define missing_arch_template 1''' +# define missing_arch_template 1 +# endif''' + +ARCH_CHECK = ARCH_CHECK_A + ARCH_CHECK_B + ARCH_CHECK_C + def print_syscall_def(syscall, tables, out): - mappings = {f'nr_{arch}':t.get(syscall, -1) - for arch, t in tables.items()} - print(DEF_TEMPLATE.format(syscall=syscall, **mappings), - file=out) + mappings = {f'nr_{arch}': t.get(syscall, -1) for arch, t in tables.items()} + print(DEF_TEMPLATE.format(syscall=syscall, **mappings), file=out) + def print_syscall_defs(syscalls, tables, out): print(HEADER, file=out) @@ -153,12 +168,13 @@ def print_syscall_defs(syscalls, tables, out): for syscall in syscalls: print_syscall_def(syscall, tables, out) + if __name__ == '__main__': output_file = sys.argv[1] arch_files = sys.argv[2:] - out = open(output_file, 'wt') tables = parse_syscall_tables(arch_files) - print_syscall_defs(SYSCALLS, tables, out) + with open(output_file, 'w') as out: + print_syscall_defs(SYSCALLS, tables, out) print(f'Wrote {output_file}') diff --git a/src/include/override/sys/ioprio.h b/src/include/override/sys/ioprio.h index c361eba6e6071..8ea4c8f8b2a86 100644 --- a/src/include/override/sys/ioprio.h +++ b/src/include/override/sys/ioprio.h @@ -3,12 +3,8 @@ #include /* IWYU pragma: export */ -#if !HAVE_IOPRIO_GET -int missing_ioprio_get(int which, int who); -# define ioprio_get missing_ioprio_get -#endif +int ioprio_get_shim(int which, int who); +#define ioprio_get ioprio_get_shim -#if !HAVE_IOPRIO_SET -int missing_ioprio_set(int which, int who, int ioprio); -# define ioprio_set missing_ioprio_set -#endif +int ioprio_set_shim(int which, int who, int ioprio); +#define ioprio_set ioprio_set_shim diff --git a/src/include/override/sys/kcmp.h b/src/include/override/sys/kcmp.h index 4e47825ac5b41..f0df5d402017b 100644 --- a/src/include/override/sys/kcmp.h +++ b/src/include/override/sys/kcmp.h @@ -5,7 +5,5 @@ #include /* Supported since kernel v3.5 (d97b46a64674a267bc41c9e16132ee2a98c3347d). */ -#if !HAVE_KCMP -int missing_kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2); -# define kcmp missing_kcmp -#endif +int kcmp_shim(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2); +#define kcmp kcmp_shim diff --git a/src/include/override/sys/kexec.h b/src/include/override/sys/kexec.h new file mode 100644 index 0000000000000..be31c93520ef0 --- /dev/null +++ b/src/include/override/sys/kexec.h @@ -0,0 +1,15 @@ +/* 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. */ +#ifdef __NR_kexec_file_load +int kexec_file_load_shim(int kernel_fd, int initrd_fd, unsigned long cmdline_len, const char *cmdline, unsigned long flags); +# define kexec_file_load kexec_file_load_shim +# define HAVE_KEXEC_FILE_LOAD_SYSCALL 1 +#else +# define HAVE_KEXEC_FILE_LOAD_SYSCALL 0 +#endif diff --git a/src/include/override/sys/keyctl.h b/src/include/override/sys/keyctl.h index 88c9c40ea78be..65be2fea2deee 100644 --- a/src/include/override/sys/keyctl.h +++ b/src/include/override/sys/keyctl.h @@ -4,17 +4,11 @@ #include /* IWYU pragma: export */ #include -#if !HAVE_KEYCTL -long missing_keyctl(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); -# define keyctl missing_keyctl -#endif +long keyctl_shim(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); +#define keyctl keyctl_shim -#if !HAVE_ADD_KEY -key_serial_t missing_add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid); -# define add_key missing_add_key -#endif +key_serial_t add_key_shim(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid); +#define add_key add_key_shim -#if !HAVE_REQUEST_KEY -key_serial_t missing_request_key(const char *type, const char *description, const char *callout_info, key_serial_t destringid); -# define request_key missing_request_key -#endif +key_serial_t request_key_shim(const char *type, const char *description, const char *callout_info, key_serial_t destringid); +#define request_key request_key_shim diff --git a/src/include/override/sys/mempolicy.h b/src/include/override/sys/mempolicy.h index 9858f2812bef7..1ba6d95958bbe 100644 --- a/src/include/override/sys/mempolicy.h +++ b/src/include/override/sys/mempolicy.h @@ -3,12 +3,8 @@ #include /* IWYU pragma: export */ -#if !HAVE_SET_MEMPOLICY -int missing_set_mempolicy(int mode, const unsigned long *nodemask, unsigned long maxnode); -# define set_mempolicy missing_set_mempolicy -#endif +int set_mempolicy_shim(int mode, const unsigned long *nodemask, unsigned long maxnode); +#define set_mempolicy set_mempolicy_shim -#if !HAVE_GET_MEMPOLICY -int missing_get_mempolicy(int *mode, unsigned long *nodemask, unsigned long maxnode, void *addr, unsigned long flags); -# define get_mempolicy missing_get_mempolicy -#endif +int get_mempolicy_shim(int *mode, unsigned long *nodemask, unsigned long maxnode, void *addr, unsigned long flags); +#define get_mempolicy get_mempolicy_shim diff --git a/src/include/override/sys/mman.h b/src/include/override/sys/mman.h index 30ef92b83538e..9961ea4b21d93 100644 --- a/src/include/override/sys/mman.h +++ b/src/include/override/sys/mman.h @@ -18,3 +18,10 @@ static_assert(MFD_NOEXEC_SEAL == 0x0008U, ""); #else static_assert(MFD_EXEC == 0x0010U, ""); #endif + +/* since Linux 6.13 / glibc-2.42 */ +#ifndef MADV_GUARD_INSTALL +# define MADV_GUARD_INSTALL 102 +#else +static_assert(MADV_GUARD_INSTALL == 102, ""); +#endif diff --git a/src/include/override/sys/mount.h b/src/include/override/sys/mount.h index e6d7ad1b67c74..2fa5c902e5bcf 100644 --- a/src/include/override/sys/mount.h +++ b/src/include/override/sys/mount.h @@ -39,56 +39,36 @@ extern int umount2(const char *__special_file, int __flags); /* Open the filesystem referenced by FS_NAME so it can be configured for mounting. */ /* Defined since glibc-2.36. * Supported since kernel v5.2 (24dcb3d90a1f67fe08c68a004af37df059d74005). */ -#if HAVE_FSOPEN -extern int fsopen(const char *__fs_name, unsigned int __flags); -#else -int missing_fsopen(const char *fsname, unsigned flags); -# define fsopen missing_fsopen -#endif +int fsopen_shim(const char *fsname, unsigned flags); +#define fsopen fsopen_shim /* Create a mount representation for the FD created by fsopen using FLAGS with ATTR_FLAGS describing how the mount is to be performed. */ /* Defined since glibc-2.36. * Supported since kernel v5.2 (93766fbd2696c2c4453dd8e1070977e9cd4e6b6d). */ -#if HAVE_FSMOUNT -extern int fsmount(int __fd, unsigned int __flags, unsigned int __ms_flags); -#else -int missing_fsmount(int fd, unsigned flags, unsigned ms_flags); -# define fsmount missing_fsmount -#endif +int fsmount_shim(int fd, unsigned flags, unsigned ms_flags); +#define fsmount fsmount_shim /* Add the mounted FROM_DFD referenced by FROM_PATHNAME filesystem returned by fsmount in the hierarchy in the place TO_DFD reference by TO_PATHNAME using FLAGS. */ /* Defined since glibc-2.36. * Supported since kernel v5.2 (2db154b3ea8e14b04fee23e3fdfd5e9d17fbc6ae). */ -#if HAVE_MOVE_MOUNT -extern int move_mount(int __from_dfd, const char *__from_pathname, int __to_dfd, const char *__to_pathname, unsigned int flags); -#else -int missing_move_mount(int from_dfd, const char *from_pathname, int to_dfd, const char *to_pathname, unsigned flags); -# define move_mount missing_move_mount -#endif +int move_mount_shim(int from_dfd, const char *from_pathname, int to_dfd, const char *to_pathname, unsigned flags); +#define move_mount move_mount_shim /* Set parameters and trigger CMD action on the FD context. KEY, VALUE, and AUX are used depending ng of the CMD. */ /* Defined since glibc-2.36. * Supported since kernel v5.2 (ecdab150fddb42fe6a739335257949220033b782). */ -#if HAVE_FSCONFIG -extern int fsconfig(int __fd, unsigned int __cmd, const char *__key, const void *__value, int __aux); -#else -int missing_fsconfig(int fd, unsigned cmd, const char *key, const void *value, int aux); -# define fsconfig missing_fsconfig -#endif +int fsconfig_shim(int fd, unsigned cmd, const char *key, const void *value, int aux); +#define fsconfig fsconfig_shim /* Open the mount point FILENAME in directory DFD using FLAGS. */ /* Defined since glibc-2.36. * Supported since kernel v5.2 (a07b20004793d8926f78d63eb5980559f7813404). */ -#if HAVE_OPEN_TREE -extern int open_tree(int __dfd, const char *__filename, unsigned int __flags); -#else -int missing_open_tree(int dfd, const char *filename, unsigned flags); -# define open_tree missing_open_tree -#endif +int open_tree_shim(int dfd, const char *filename, unsigned flags); +#define open_tree open_tree_shim /* Change the mount properties of the mount or an entire mount tree. If PATH is a relative pathname, then it is interpreted relative to the @@ -97,18 +77,10 @@ int missing_open_tree(int dfd, const char *filename, unsigned flags); working directory of the calling process. */ /* Defined since glibc-2.36. * Supported since kernel v5.12 (2a1867219c7b27f928e2545782b86daaf9ad50bd). */ -#if HAVE_MOUNT_SETATTR -extern int mount_setattr(int __dfd, const char *__path, unsigned int __flags, struct mount_attr *__uattr, size_t __usize); -#else -int missing_mount_setattr(int dfd, const char *path, unsigned flags, struct mount_attr *attr, size_t size); -# define mount_setattr missing_mount_setattr -#endif +int mount_setattr_shim(int dfd, const char *path, unsigned flags, struct mount_attr *attr, size_t size); +#define mount_setattr mount_setattr_shim /* Not defined in glibc yet as of glibc-2.41. * Supported since kernel v6.15 (c4a16820d90199409c9bf01c4f794e1e9e8d8fd8). */ -#if HAVE_OPEN_TREE_ATTR -extern int open_tree_attr(int __dfd, const char *__filename, unsigned int __flags, struct mount_attr *__uattr, size_t __usize); -#else -int missing_open_tree_attr(int dfd, const char *filename, unsigned int flags, struct mount_attr *attr, size_t size); -# define open_tree_attr missing_open_tree_attr -#endif +int open_tree_attr_shim(int dfd, const char *filename, unsigned int flags, struct mount_attr *attr, size_t size); +#define open_tree_attr open_tree_attr_shim diff --git a/src/include/override/sys/param.h b/src/include/override/sys/param.h index 88cc47edf4fe0..2a4410f526d19 100644 --- a/src/include/override/sys/param.h +++ b/src/include/override/sys/param.h @@ -3,6 +3,6 @@ /* sys/param.h from glibc unconditionally overrides the MIN() and MAX() macros which interferes with our own * MAX() macro. It also includes a bunch of other headers transitively so we don't want to include - * sys/param.h in macro-fundamental.h unconditionally. We'd like to make including this file an error but + * sys/param.h in macro.h unconditionally. We'd like to make including this file an error but * unfortunately includes it. However, doesn't actually make use of anything from * sys/param.h, so we override it with an empty file so it can't mess with our macros. */ diff --git a/src/include/override/sys/pidfd.h b/src/include/override/sys/pidfd.h index 7f136eb62eeb7..28fb75698ed38 100644 --- a/src/include/override/sys/pidfd.h +++ b/src/include/override/sys/pidfd.h @@ -1,28 +1,19 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -/* since glibc-2.36 */ -#if HAVE_PIDFD_OPEN -#include_next /* IWYU pragma: export */ -#endif - #include #include #include /* Defined since glibc-2.36. * Supported since kernel v5.3 (7615d9e1780e26e0178c93c55b73309a5dc093d7). */ -#if !HAVE_PIDFD_OPEN -int missing_pidfd_open(pid_t pid, unsigned flags); -# define pidfd_open missing_pidfd_open -#endif +int pidfd_open_shim(pid_t pid, unsigned flags); +#define pidfd_open pidfd_open_shim /* Defined since glibc-2.36. * Supported since kernel v5.1 (3eb39f47934f9d5a3027fe00d906a45fe3a15fad). */ -#if !HAVE_PIDFD_SEND_SIGNAL -int missing_pidfd_send_signal(int fd, int sig, siginfo_t *info, unsigned flags); -# define pidfd_send_signal missing_pidfd_send_signal -#endif +int pidfd_send_signal_shim(int fd, int sig, siginfo_t *info, unsigned flags); +#define pidfd_send_signal pidfd_send_signal_shim /* since glibc-2.41 */ #ifndef PIDFS_IOCTL_MAGIC @@ -49,8 +40,14 @@ int missing_pidfd_send_signal(int fd, int sig, siginfo_t *info, unsigned flags); #define PIDFD_INFO_CGROUPID (1UL << 2) /* Always returned if available, even if not requested */ #define PIDFD_INFO_EXIT (1UL << 3) /* Only returned if requested. */ #define PIDFD_INFO_COREDUMP (1UL << 4) /* Only returned if requested. */ +#define PIDFD_INFO_SUPPORTED_MASK (1UL << 5) /* Want/got supported mask flags */ +#define PIDFD_INFO_COREDUMP_SIGNAL (1UL << 6) /* Always returned if PIDFD_INFO_COREDUMP is requested. */ +#define PIDFD_INFO_COREDUMP_CODE (1UL << 7) /* Always returned if PIDFD_INFO_COREDUMP is requested. */ #define PIDFD_INFO_SIZE_VER0 64 /* sizeof first published struct */ +#define PIDFD_INFO_SIZE_VER1 72 /* sizeof second published struct */ +#define PIDFD_INFO_SIZE_VER2 80 /* sizeof third published struct */ +#define PIDFD_INFO_SIZE_VER3 88 /* sizeof fourth published struct */ /* * Values for @coredump_mask in pidfd_info. @@ -105,8 +102,13 @@ struct pidfd_info { __u32 fsuid; __u32 fsgid; __s32 exit_code; /* since kernel v6.15 (7477d7dce48a996ae4e4f0b5f7bd82de7ec9131b) */ - __u32 coredump_mask; /* since kernel v6.16 (1d8db6fd698de1f73b1a7d72aea578fdd18d9a87) */ - __u32 __spare1; + struct { /* coredump info */ + __u32 coredump_mask; /* since kernel v6.16 (1d8db6fd698de1f73b1a7d72aea578fdd18d9a87) */ + __u32 coredump_signal; /* since kernel v6.19 (036375522be8425874e9e0f907c7127e315c7a52) */ + __u32 coredump_code; /* since kernel v7.1 (701f7f4fbabbf4989ba6fbf033b160dd943221d5) */ + __u32 coredump_pad; /* since kernel v7.1 (701f7f4fbabbf4989ba6fbf033b160dd943221d5) */ + }; + __u64 supported_mask; /* Mask flags that this kernel supports */ }; #define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info) diff --git a/src/include/override/sys/quota.h b/src/include/override/sys/quota.h index fcf6be988bb24..95aa3674460e1 100644 --- a/src/include/override/sys/quota.h +++ b/src/include/override/sys/quota.h @@ -4,7 +4,5 @@ #include_next /* IWYU pragma: export */ /* Supported since kernel v5.14 (64c2c2c62f92339b176ea24403d8db16db36f9e6). */ -#if !HAVE_QUOTACTL_FD -int missing_quotactl_fd(int fd, int cmd, int id, void *addr); -# define quotactl_fd missing_quotactl_fd -#endif +int quotactl_fd_shim(int fd, int cmd, int id, void *addr); +#define quotactl_fd quotactl_fd_shim diff --git a/src/include/override/sys/stat.h b/src/include/override/sys/stat.h index 5cef9f852ee0b..bfd9b54e7f90e 100644 --- a/src/include/override/sys/stat.h +++ b/src/include/override/sys/stat.h @@ -4,7 +4,5 @@ #include_next /* IWYU pragma: export */ /* Supported since kernel v6.6 (78252deb023cf0879256fcfbafe37022c390762b). */ -#if !HAVE_FCHMODAT2 -int missing_fchmodat2(int dirfd, const char *path, mode_t mode, int flags); -# define fchmodat2 missing_fchmodat2 -#endif +int fchmodat2_shim(int dirfd, const char *path, mode_t mode, int flags); +#define fchmodat2 fchmodat2_shim diff --git a/src/include/override/sys/syscall-list.txt b/src/include/override/sys/syscall-list.txt index 4e8260eca058e..baab9835443c1 100644 --- a/src/include/override/sys/syscall-list.txt +++ b/src/include/override/sys/syscall-list.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir rseq +rseq_slice_yield rt_sigaction rt_sigpending rt_sigprocmask diff --git a/src/include/override/sys/syscall.h b/src/include/override/sys/syscall.h index da2f780bed39c..da555e79080c4 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 @@ -49,7 +49,7 @@ # if defined(__ILP32__) # else # endif -# elif !defined(missing_arch_template) +# else # warning "Current architecture is missing from the template" # define missing_arch_template 1 # endif @@ -124,6 +124,76 @@ static_assert(__NR_fchmodat2 == systemd_NR_fchmodat2, ""); # endif #endif +#ifndef __IGNORE_kexec_file_load +# if defined(__aarch64__) +# define systemd_NR_kexec_file_load 294 +# elif defined(__alpha__) +# define systemd_NR_kexec_file_load -1 +# elif defined(__arc__) || defined(__tilegx__) +# define systemd_NR_kexec_file_load 294 +# elif defined(__arm__) +# define systemd_NR_kexec_file_load 401 +# elif defined(__i386__) +# define systemd_NR_kexec_file_load -1 +# elif defined(__ia64__) +# define systemd_NR_kexec_file_load -1 +# elif defined(__loongarch_lp64) +# define systemd_NR_kexec_file_load 294 +# elif defined(__m68k__) +# define systemd_NR_kexec_file_load -1 +# elif defined(_MIPS_SIM) +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define systemd_NR_kexec_file_load -1 +# elif _MIPS_SIM == _MIPS_SIM_NABI32 +# define systemd_NR_kexec_file_load -1 +# elif _MIPS_SIM == _MIPS_SIM_ABI64 +# define systemd_NR_kexec_file_load -1 +# else +# error "Unknown MIPS ABI" +# endif +# elif defined(__hppa__) +# define systemd_NR_kexec_file_load 355 +# elif defined(__powerpc__) +# define systemd_NR_kexec_file_load 382 +# elif defined(__riscv) +# if __riscv_xlen == 32 +# define systemd_NR_kexec_file_load 294 +# elif __riscv_xlen == 64 +# define systemd_NR_kexec_file_load 294 +# else +# error "Unknown RISC-V ABI" +# endif +# elif defined(__s390__) +# define systemd_NR_kexec_file_load 381 +# elif defined(__sh__) +# define systemd_NR_kexec_file_load -1 +# elif defined(__sparc__) +# define systemd_NR_kexec_file_load -1 +# elif defined(__x86_64__) +# if defined(__ILP32__) +# define systemd_NR_kexec_file_load (320 | /* __X32_SYSCALL_BIT */ 0x40000000) +# else +# define systemd_NR_kexec_file_load 320 +# endif +# elif !defined(missing_arch_template) +# warning "kexec_file_load() syscall number is unknown for your architecture" +# endif + +/* may be an (invalid) negative number due to libseccomp, see PR 13319 */ +# if defined __NR_kexec_file_load && __NR_kexec_file_load >= 0 +# if defined systemd_NR_kexec_file_load +static_assert(__NR_kexec_file_load == systemd_NR_kexec_file_load, ""); +# endif +# else +# if defined __NR_kexec_file_load +# undef __NR_kexec_file_load +# endif +# if defined systemd_NR_kexec_file_load && systemd_NR_kexec_file_load >= 0 +# define __NR_kexec_file_load systemd_NR_kexec_file_load +# endif +# endif +#endif + #ifndef __IGNORE_open_tree_attr # if defined(__aarch64__) # define systemd_NR_open_tree_attr 467 diff --git a/src/include/override/sys/syscalls-alpha.txt b/src/include/override/sys/syscalls-alpha.txt index 5bd33d275c729..e6de840d4eaa9 100644 --- a/src/include/override/sys/syscalls-alpha.txt +++ b/src/include/override/sys/syscalls-alpha.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 137 rseq 527 +rseq_slice_yield 581 rt_sigaction 352 rt_sigpending 354 rt_sigprocmask 353 diff --git a/src/include/override/sys/syscalls-arc.txt b/src/include/override/sys/syscalls-arc.txt index f42160becb8f9..76834685fbe72 100644 --- a/src/include/override/sys/syscalls-arc.txt +++ b/src/include/override/sys/syscalls-arc.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir rseq 293 +rseq_slice_yield 471 rt_sigaction 134 rt_sigpending 136 rt_sigprocmask 135 diff --git a/src/include/override/sys/syscalls-arm.txt b/src/include/override/sys/syscalls-arm.txt index 02776873f6e2c..a1998492872ef 100644 --- a/src/include/override/sys/syscalls-arm.txt +++ b/src/include/override/sys/syscalls-arm.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 398 +rseq_slice_yield 471 rt_sigaction 174 rt_sigpending 176 rt_sigprocmask 175 diff --git a/src/include/override/sys/syscalls-arm64.txt b/src/include/override/sys/syscalls-arm64.txt index a29c0745baf50..725c51f181d87 100644 --- a/src/include/override/sys/syscalls-arm64.txt +++ b/src/include/override/sys/syscalls-arm64.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir rseq 293 +rseq_slice_yield 471 rt_sigaction 134 rt_sigpending 136 rt_sigprocmask 135 diff --git a/src/include/override/sys/syscalls-i386.txt b/src/include/override/sys/syscalls-i386.txt index ec79c704b135e..0b5ead1519eeb 100644 --- a/src/include/override/sys/syscalls-i386.txt +++ b/src/include/override/sys/syscalls-i386.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 386 +rseq_slice_yield 471 rt_sigaction 174 rt_sigpending 176 rt_sigprocmask 175 diff --git a/src/include/override/sys/syscalls-loongarch64.txt b/src/include/override/sys/syscalls-loongarch64.txt index 78b4eb8dcc896..f694728f6d234 100644 --- a/src/include/override/sys/syscalls-loongarch64.txt +++ b/src/include/override/sys/syscalls-loongarch64.txt @@ -213,7 +213,7 @@ map_shadow_stack 453 mbind 235 membarrier 283 memfd_create 279 -memfd_secret +memfd_secret 447 memory_ordering migrate_pages 238 mincore 232 @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir rseq 293 +rseq_slice_yield 471 rt_sigaction 134 rt_sigpending 136 rt_sigprocmask 135 diff --git a/src/include/override/sys/syscalls-m68k.txt b/src/include/override/sys/syscalls-m68k.txt index db78f19ae852c..d577d8fe5ea2c 100644 --- a/src/include/override/sys/syscalls-m68k.txt +++ b/src/include/override/sys/syscalls-m68k.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 384 +rseq_slice_yield 471 rt_sigaction 174 rt_sigpending 176 rt_sigprocmask 175 diff --git a/src/include/override/sys/syscalls-mips64.txt b/src/include/override/sys/syscalls-mips64.txt index d85885c96197c..ab9aecbc0e5ae 100644 --- a/src/include/override/sys/syscalls-mips64.txt +++ b/src/include/override/sys/syscalls-mips64.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 5082 rseq 5327 +rseq_slice_yield 5471 rt_sigaction 5013 rt_sigpending 5125 rt_sigprocmask 5014 diff --git a/src/include/override/sys/syscalls-mips64n32.txt b/src/include/override/sys/syscalls-mips64n32.txt index 1dbbf702dd437..08a983c24deb6 100644 --- a/src/include/override/sys/syscalls-mips64n32.txt +++ b/src/include/override/sys/syscalls-mips64n32.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 6082 rseq 6331 +rseq_slice_yield 6471 rt_sigaction 6013 rt_sigpending 6125 rt_sigprocmask 6014 diff --git a/src/include/override/sys/syscalls-mipso32.txt b/src/include/override/sys/syscalls-mipso32.txt index 5d27caabe2918..587183e352638 100644 --- a/src/include/override/sys/syscalls-mipso32.txt +++ b/src/include/override/sys/syscalls-mipso32.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 4040 rseq 4367 +rseq_slice_yield 4471 rt_sigaction 4194 rt_sigpending 4196 rt_sigprocmask 4195 diff --git a/src/include/override/sys/syscalls-parisc.txt b/src/include/override/sys/syscalls-parisc.txt index cf45fc19763b5..793f65070d35c 100644 --- a/src/include/override/sys/syscalls-parisc.txt +++ b/src/include/override/sys/syscalls-parisc.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 354 +rseq_slice_yield 471 rt_sigaction 174 rt_sigpending 176 rt_sigprocmask 175 diff --git a/src/include/override/sys/syscalls-powerpc.txt b/src/include/override/sys/syscalls-powerpc.txt index 533003ac062d5..02f9869efbdcf 100644 --- a/src/include/override/sys/syscalls-powerpc.txt +++ b/src/include/override/sys/syscalls-powerpc.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 387 +rseq_slice_yield 471 rt_sigaction 173 rt_sigpending 175 rt_sigprocmask 174 diff --git a/src/include/override/sys/syscalls-powerpc64.txt b/src/include/override/sys/syscalls-powerpc64.txt index 4f164e93df600..0da8e5c954775 100644 --- a/src/include/override/sys/syscalls-powerpc64.txt +++ b/src/include/override/sys/syscalls-powerpc64.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 387 +rseq_slice_yield 471 rt_sigaction 173 rt_sigpending 175 rt_sigprocmask 174 diff --git a/src/include/override/sys/syscalls-riscv32.txt b/src/include/override/sys/syscalls-riscv32.txt index 4f28fdd757ab4..b88d32c3618f9 100644 --- a/src/include/override/sys/syscalls-riscv32.txt +++ b/src/include/override/sys/syscalls-riscv32.txt @@ -359,6 +359,7 @@ riscv_flush_icache 259 riscv_hwprobe 258 rmdir rseq 293 +rseq_slice_yield 471 rt_sigaction 134 rt_sigpending 136 rt_sigprocmask 135 diff --git a/src/include/override/sys/syscalls-riscv64.txt b/src/include/override/sys/syscalls-riscv64.txt index cf82f4ff95e02..ff03aa304e551 100644 --- a/src/include/override/sys/syscalls-riscv64.txt +++ b/src/include/override/sys/syscalls-riscv64.txt @@ -359,6 +359,7 @@ riscv_flush_icache 259 riscv_hwprobe 258 rmdir rseq 293 +rseq_slice_yield 471 rt_sigaction 134 rt_sigpending 136 rt_sigprocmask 135 diff --git a/src/include/override/sys/syscalls-s390x.txt b/src/include/override/sys/syscalls-s390x.txt index ea838b1093748..017d7863b2370 100644 --- a/src/include/override/sys/syscalls-s390x.txt +++ b/src/include/override/sys/syscalls-s390x.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 383 +rseq_slice_yield 471 rt_sigaction 174 rt_sigpending 176 rt_sigprocmask 175 diff --git a/src/include/override/sys/syscalls-sh.txt b/src/include/override/sys/syscalls-sh.txt index 1da85abd4d7ea..a8473a39221d6 100644 --- a/src/include/override/sys/syscalls-sh.txt +++ b/src/include/override/sys/syscalls-sh.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 40 rseq 387 +rseq_slice_yield 471 rt_sigaction 174 rt_sigpending 176 rt_sigprocmask 175 diff --git a/src/include/override/sys/syscalls-sparc.txt b/src/include/override/sys/syscalls-sparc.txt index 23e1eb5d6186c..be5d76b2e30a7 100644 --- a/src/include/override/sys/syscalls-sparc.txt +++ b/src/include/override/sys/syscalls-sparc.txt @@ -39,7 +39,7 @@ clock_nanosleep_time64 407 clock_settime 256 clock_settime64 404 clone 217 -clone3 +clone3 435 close 6 close_range 436 connect 98 @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 137 rseq 365 +rseq_slice_yield 471 rt_sigaction 102 rt_sigpending 104 rt_sigprocmask 103 diff --git a/src/include/override/sys/syscalls-x86_64.txt b/src/include/override/sys/syscalls-x86_64.txt index f961ab78dd124..4ae31535c96cf 100644 --- a/src/include/override/sys/syscalls-x86_64.txt +++ b/src/include/override/sys/syscalls-x86_64.txt @@ -359,6 +359,7 @@ riscv_flush_icache riscv_hwprobe rmdir 84 rseq 334 +rseq_slice_yield 471 rt_sigaction 13 rt_sigpending 127 rt_sigprocmask 14 diff --git a/src/include/override/sys/xattr.h b/src/include/override/sys/xattr.h index db31d0ba6f3e4..8823548d89d03 100644 --- a/src/include/override/sys/xattr.h +++ b/src/include/override/sys/xattr.h @@ -8,13 +8,9 @@ #include_next /* IWYU pragma: export */ /* Supported since kernel v6.13 (6140be90ec70c39fa844741ca3cc807dd0866394). */ -#if !HAVE_SETXATTRAT -int missing_setxattrat(int fd, const char *path, int at_flags, const char *name, const struct xattr_args *args, size_t size); -# define setxattrat missing_setxattrat -#endif +int setxattrat_shim(int fd, const char *path, int at_flags, const char *name, const struct xattr_args *args, size_t size); +#define setxattrat setxattrat_shim /* Supported since kernel v6.13 (6140be90ec70c39fa844741ca3cc807dd0866394). */ -#if !HAVE_REMOVEXATTRAT -int missing_removexattrat(int fd, const char *path, int at_flags, const char *name); -# define removexattrat missing_removexattrat -#endif +int removexattrat_shim(int fd, const char *path, int at_flags, const char *name); +#define removexattrat removexattrat_shim diff --git a/src/include/override/unistd.h b/src/include/override/unistd.h index bd558694eebdd..7dd56aa06f6a1 100644 --- a/src/include/override/unistd.h +++ b/src/include/override/unistd.h @@ -3,7 +3,5 @@ #include_next /* IWYU pragma: export */ -#if !HAVE_PIVOT_ROOT -int missing_pivot_root(const char *new_root, const char *put_old); -# define pivot_root missing_pivot_root -#endif +int pivot_root_shim(const char *new_root, const char *put_old); +#define pivot_root pivot_root_shim diff --git a/src/include/uapi/linux/bpf.h b/src/include/uapi/linux/bpf.h index 14c80da5df87f..2ae9d05cb4e15 100644 --- a/src/include/uapi/linux/bpf.h +++ b/src/include/uapi/linux/bpf.h @@ -4645,7 +4645,9 @@ union bpf_attr { * Description * Discard reserved ring buffer sample, pointed to by *data*. * If **BPF_RB_NO_WAKEUP** is specified in *flags*, no notification - * of new data availability is sent. + * of new data availability is sent. Discarded records remain in + * the ring buffer until consumed by user space, so a later submit + * using adaptive wakeup might not wake up the consumer. * If **BPF_RB_FORCE_WAKEUP** is specified in *flags*, notification * of new data availability is sent unconditionally. * If **0** is specified in *flags*, an adaptive notification diff --git a/src/include/uapi/linux/btrfs_tree.h b/src/include/uapi/linux/btrfs_tree.h index 0507f9309523b..e7b75f9206ecd 100644 --- a/src/include/uapi/linux/btrfs_tree.h +++ b/src/include/uapi/linux/btrfs_tree.h @@ -1241,7 +1241,8 @@ struct btrfs_free_space_info { __le32 flags; } __attribute__ ((__packed__)); -#define BTRFS_FREE_SPACE_USING_BITMAPS (1ULL << 0) +#define BTRFS_FREE_SPACE_USING_BITMAPS (1UL << 0) +#define BTRFS_FREE_SPACE_FLAGS_MASK (BTRFS_FREE_SPACE_USING_BITMAPS) #define BTRFS_QGROUP_LEVEL_SHIFT 48 static __inline__ __u16 btrfs_qgroup_level(__u64 qgroupid) diff --git a/src/include/uapi/linux/const.h b/src/include/uapi/linux/const.h index 95ede23342040..c6a9d0c9835ca 100644 --- a/src/include/uapi/linux/const.h +++ b/src/include/uapi/linux/const.h @@ -50,4 +50,22 @@ #define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +/* + * Divide positive or negative dividend by positive or negative divisor + * and round to closest integer. Result is undefined for negative + * divisors if the dividend variable type is unsigned and for negative + * dividends if the divisor variable type is unsigned. + */ +#define __KERNEL_DIV_ROUND_CLOSEST(x, divisor) \ +({ \ + __typeof__(x) __x = x; \ + __typeof__(divisor) __d = divisor; \ + \ + (((__typeof__(x))-1) > 0 || \ + ((__typeof__(divisor))-1) > 0 || \ + (((__x) > 0) == ((__d) > 0))) ? \ + (((__x) + ((__d) / 2)) / (__d)) : \ + (((__x) - ((__d) / 2)) / (__d)); \ +}) + #endif /* _LINUX_CONST_H */ diff --git a/src/include/uapi/linux/ethtool.h b/src/include/uapi/linux/ethtool.h index 5cfcf2b858ee7..fc439ea218f86 100644 --- a/src/include/uapi/linux/ethtool.h +++ b/src/include/uapi/linux/ethtool.h @@ -225,7 +225,7 @@ enum tunable_id { ETHTOOL_ID_UNSPEC, ETHTOOL_RX_COPYBREAK, ETHTOOL_TX_COPYBREAK, - ETHTOOL_PFC_PREVENTION_TOUT, /* timeout in msecs */ + ETHTOOL_PFC_PREVENTION_TOUT, /* both pause and pfc, see man ethtool */ ETHTOOL_TX_COPYBREAK_BUF_SIZE, /* * Add your fresh new tunable attribute above and remember to update diff --git a/src/include/uapi/linux/fs.h b/src/include/uapi/linux/fs.h index 19d04b50e6625..6e9a44969f229 100644 --- a/src/include/uapi/linux/fs.h +++ b/src/include/uapi/linux/fs.h @@ -653,4 +653,16 @@ struct procmap_query { __u64 build_id_addr; /* in */ }; +/* + * Shutdown the filesystem. + */ +#define FS_IOC_SHUTDOWN _IOR('X', 125, __u32) + +/* + * Flags for FS_IOC_SHUTDOWN + */ +#define FS_SHUTDOWN_FLAGS_DEFAULT 0x0 +#define FS_SHUTDOWN_FLAGS_LOGFLUSH 0x1 /* flush log but not data*/ +#define FS_SHUTDOWN_FLAGS_NOLOGFLUSH 0x2 /* don't flush log nor data */ + #endif /* _LINUX_FS_H */ diff --git a/src/include/uapi/linux/if_link.h b/src/include/uapi/linux/if_link.h index 2037afbc4b330..a8c64be26f2ca 100644 --- a/src/include/uapi/linux/if_link.h +++ b/src/include/uapi/linux/if_link.h @@ -742,6 +742,11 @@ enum in6_addr_gen_mode { * @IFLA_BR_FDB_MAX_LEARNED * Set the number of max dynamically learned FDB entries for the current * bridge. + * + * @IFLA_BR_STP_MODE + * Set the STP mode for the bridge, which controls how the bridge + * selects between userspace and kernel STP. The valid values are + * documented below in the ``BR_STP_MODE_*`` constants. */ enum { IFLA_BR_UNSPEC, @@ -794,11 +799,45 @@ enum { IFLA_BR_MCAST_QUERIER_STATE, IFLA_BR_FDB_N_LEARNED, IFLA_BR_FDB_MAX_LEARNED, + IFLA_BR_STP_MODE, __IFLA_BR_MAX, }; #define IFLA_BR_MAX (__IFLA_BR_MAX - 1) +/** + * DOC: Bridge STP mode values + * + * @BR_STP_MODE_AUTO + * Default. The kernel invokes the ``/sbin/bridge-stp`` helper to hand + * the bridge to a userspace STP daemon (e.g. mstpd). Only attempted in + * the initial network namespace; in other namespaces this falls back to + * kernel STP. + * + * @BR_STP_MODE_USER + * Directly enable userspace STP (``BR_USER_STP``) without invoking the + * ``/sbin/bridge-stp`` helper. Works in any network namespace. + * Userspace is responsible for ensuring an STP daemon manages the + * bridge. + * + * @BR_STP_MODE_KERNEL + * Directly enable kernel STP (``BR_KERNEL_STP``) without invoking the + * helper. + * + * The mode controls how the bridge selects between userspace and kernel + * STP when STP is enabled via ``IFLA_BR_STP_STATE``. It can only be + * changed while STP is disabled (``IFLA_BR_STP_STATE`` == 0), returns + * ``-EBUSY`` otherwise. The default value is ``BR_STP_MODE_AUTO``. + */ +enum br_stp_mode { + BR_STP_MODE_AUTO, + BR_STP_MODE_USER, + BR_STP_MODE_KERNEL, + __BR_STP_MODE_MAX +}; + +#define BR_STP_MODE_MAX (__BR_STP_MODE_MAX - 1) + struct ifla_bridge_id { __u8 prio[2]; __u8 addr[6]; /* ETH_ALEN */ @@ -1294,6 +1333,11 @@ enum netkit_mode { NETKIT_L3, }; +enum netkit_pairing { + NETKIT_DEVICE_PAIR, + NETKIT_DEVICE_SINGLE, +}; + /* NETKIT_SCRUB_NONE leaves clearing skb->{mark,priority} up to * the BPF program if attached. This also means the latter can * consume the two fields if they were populated earlier. @@ -1318,6 +1362,7 @@ enum { IFLA_NETKIT_PEER_SCRUB, IFLA_NETKIT_HEADROOM, IFLA_NETKIT_TAILROOM, + IFLA_NETKIT_PAIRING, __IFLA_NETKIT_MAX, }; #define IFLA_NETKIT_MAX (__IFLA_NETKIT_MAX - 1) @@ -1566,6 +1611,8 @@ enum { IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE, IFLA_BOND_SLAVE_PRIO, IFLA_BOND_SLAVE_ACTOR_PORT_PRIO, + IFLA_BOND_SLAVE_AD_CHURN_ACTOR_STATE, + IFLA_BOND_SLAVE_AD_CHURN_PARTNER_STATE, __IFLA_BOND_SLAVE_MAX, }; diff --git a/src/include/uapi/linux/input-event-codes.h b/src/include/uapi/linux/input-event-codes.h index 32bc46b55f611..593fb257ce54b 100644 --- a/src/include/uapi/linux/input-event-codes.h +++ b/src/include/uapi/linux/input-event-codes.h @@ -643,6 +643,10 @@ #define KEY_EPRIVACY_SCREEN_ON 0x252 #define KEY_EPRIVACY_SCREEN_OFF 0x253 +#define KEY_ACTION_ON_SELECTION 0x254 /* AL Action on Selection (HUTRR119) */ +#define KEY_CONTEXTUAL_INSERT 0x255 /* AL Contextual Insertion (HUTRR119) */ +#define KEY_CONTEXTUAL_QUERY 0x256 /* AL Contextual Query (HUTRR119) */ + #define KEY_KBDINPUTASSIST_PREV 0x260 #define KEY_KBDINPUTASSIST_NEXT 0x261 #define KEY_KBDINPUTASSIST_PREVGROUP 0x262 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/include/uapi/linux/liveupdate.h b/src/include/uapi/linux/liveupdate.h new file mode 100644 index 0000000000000..bef962adbf3ef --- /dev/null +++ b/src/include/uapi/linux/liveupdate.h @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +/* + * Userspace interface for /dev/liveupdate + * Live Update Orchestrator + * + * Copyright (c) 2025, Google LLC. + * Pasha Tatashin + */ + +#ifndef _LIVEUPDATE_H +#define _LIVEUPDATE_H + +#include +#include + +/** + * DOC: General ioctl format + * + * The ioctl interface follows a general format to allow for extensibility. Each + * ioctl is passed in a structure pointer as the argument providing the size of + * the structure in the first u32. The kernel checks that any structure space + * beyond what it understands is 0. This allows userspace to use the backward + * compatible portion while consistently using the newer, larger, structures. + * + * ioctls use a standard meaning for common errnos: + * + * - ENOTTY: The IOCTL number itself is not supported at all + * - E2BIG: The IOCTL number is supported, but the provided structure has + * non-zero in a part the kernel does not understand. + * - EOPNOTSUPP: The IOCTL number is supported, and the structure is + * understood, however a known field has a value the kernel does not + * understand or support. + * - EINVAL: Everything about the IOCTL was understood, but a field is not + * correct. + * - ENOENT: A provided token does not exist. + * - ENOMEM: Out of memory. + * - EOVERFLOW: Mathematics overflowed. + * + * As well as additional errnos, within specific ioctls. + */ + +/* The ioctl type, documented in ioctl-number.rst */ +#define LIVEUPDATE_IOCTL_TYPE 0xBA + +/* The maximum length of session name including null termination */ +#define LIVEUPDATE_SESSION_NAME_LENGTH 64 + +/* The /dev/liveupdate ioctl commands */ +enum { + LIVEUPDATE_CMD_BASE = 0x00, + LIVEUPDATE_CMD_CREATE_SESSION = LIVEUPDATE_CMD_BASE, + LIVEUPDATE_CMD_RETRIEVE_SESSION = 0x01, +}; + +/* ioctl commands for session file descriptors */ +enum { + LIVEUPDATE_CMD_SESSION_BASE = 0x40, + LIVEUPDATE_CMD_SESSION_PRESERVE_FD = LIVEUPDATE_CMD_SESSION_BASE, + LIVEUPDATE_CMD_SESSION_RETRIEVE_FD = 0x41, + LIVEUPDATE_CMD_SESSION_FINISH = 0x42, +}; + +/** + * struct liveupdate_ioctl_create_session - ioctl(LIVEUPDATE_IOCTL_CREATE_SESSION) + * @size: Input; sizeof(struct liveupdate_ioctl_create_session) + * @fd: Output; The new file descriptor for the created session. + * @name: Input; A null-terminated string for the session name, max + * length %LIVEUPDATE_SESSION_NAME_LENGTH including termination + * character. + * + * Creates a new live update session for managing preserved resources. + * This ioctl can only be called on the main /dev/liveupdate device. + * + * Return: 0 on success, negative error code on failure. + */ +struct liveupdate_ioctl_create_session { + __u32 size; + __s32 fd; + __u8 name[LIVEUPDATE_SESSION_NAME_LENGTH]; +}; + +#define LIVEUPDATE_IOCTL_CREATE_SESSION \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_CREATE_SESSION) + +/** + * struct liveupdate_ioctl_retrieve_session - ioctl(LIVEUPDATE_IOCTL_RETRIEVE_SESSION) + * @size: Input; sizeof(struct liveupdate_ioctl_retrieve_session) + * @fd: Output; The new file descriptor for the retrieved session. + * @name: Input; A null-terminated string identifying the session to retrieve. + * The name must exactly match the name used when the session was + * created in the previous kernel. + * + * Retrieves a handle (a new file descriptor) for a preserved session by its + * name. This is the primary mechanism for a userspace agent to regain control + * of its preserved resources after a live update. + * + * The userspace application provides the null-terminated `name` of a session + * it created before the live update. If a preserved session with a matching + * name is found, the kernel instantiates it and returns a new file descriptor + * in the `fd` field. This new session FD can then be used for all file-specific + * operations, such as restoring individual file descriptors with + * LIVEUPDATE_SESSION_RETRIEVE_FD. + * + * It is the responsibility of the userspace application to know the names of + * the sessions it needs to retrieve. If no session with the given name is + * found, the ioctl will fail with -ENOENT. + * + * This ioctl can only be called on the main /dev/liveupdate device when the + * system is in the LIVEUPDATE_STATE_UPDATED state. + */ +struct liveupdate_ioctl_retrieve_session { + __u32 size; + __s32 fd; + __u8 name[LIVEUPDATE_SESSION_NAME_LENGTH]; +}; + +#define LIVEUPDATE_IOCTL_RETRIEVE_SESSION \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_RETRIEVE_SESSION) + +/* Session specific IOCTLs */ + +/** + * struct liveupdate_session_preserve_fd - ioctl(LIVEUPDATE_SESSION_PRESERVE_FD) + * @size: Input; sizeof(struct liveupdate_session_preserve_fd) + * @fd: Input; The user-space file descriptor to be preserved. + * @token: Input; An opaque, unique token for preserved resource. + * + * Holds parameters for preserving a file descriptor. + * + * User sets the @fd field identifying the file descriptor to preserve + * (e.g., memfd, kvm, iommufd, VFIO). The kernel validates if this FD type + * and its dependencies are supported for preservation. If validation passes, + * the kernel marks the FD internally and *initiates the process* of preparing + * its state for saving. The actual snapshotting of the state typically occurs + * during the subsequent %LIVEUPDATE_IOCTL_PREPARE execution phase, though + * some finalization might occur during freeze. + * On successful validation and initiation, the kernel uses the @token + * field with an opaque identifier representing the resource being preserved. + * This token confirms the FD is targeted for preservation and is required for + * the subsequent %LIVEUPDATE_SESSION_RETRIEVE_FD call after the live update. + * + * Return: 0 on success (validation passed, preservation initiated), negative + * error code on failure (e.g., unsupported FD type, dependency issue, + * validation failed). + */ +struct liveupdate_session_preserve_fd { + __u32 size; + __s32 fd; + __aligned_u64 token; +}; + +#define LIVEUPDATE_SESSION_PRESERVE_FD \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_PRESERVE_FD) + +/** + * struct liveupdate_session_retrieve_fd - ioctl(LIVEUPDATE_SESSION_RETRIEVE_FD) + * @size: Input; sizeof(struct liveupdate_session_retrieve_fd) + * @fd: Output; The new file descriptor representing the fully restored + * kernel resource. + * @token: Input; An opaque, token that was used to preserve the resource. + * + * Retrieve a previously preserved file descriptor. + * + * User sets the @token field to the value obtained from a successful + * %LIVEUPDATE_IOCTL_FD_PRESERVE call before the live update. On success, + * the kernel restores the state (saved during the PREPARE/FREEZE phases) + * associated with the token and populates the @fd field with a new file + * descriptor referencing the restored resource in the current (new) kernel. + * This operation must be performed *before* signaling completion via + * %LIVEUPDATE_IOCTL_FINISH. + * + * Return: 0 on success, negative error code on failure (e.g., invalid token). + */ +struct liveupdate_session_retrieve_fd { + __u32 size; + __s32 fd; + __aligned_u64 token; +}; + +#define LIVEUPDATE_SESSION_RETRIEVE_FD \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_RETRIEVE_FD) + +/** + * struct liveupdate_session_finish - ioctl(LIVEUPDATE_SESSION_FINISH) + * @size: Input; sizeof(struct liveupdate_session_finish) + * @reserved: Input; Must be zero. Reserved for future use. + * + * Signals the completion of the restoration process for a retrieved session. + * This is the final operation that should be performed on a session file + * descriptor after a live update. + * + * This ioctl must be called once all required file descriptors for the session + * have been successfully retrieved (using %LIVEUPDATE_SESSION_RETRIEVE_FD) and + * are fully restored from the userspace and kernel perspective. + * + * Upon success, the kernel releases its ownership of the preserved resources + * associated with this session. This allows internal resources to be freed, + * typically by decrementing reference counts on the underlying preserved + * objects. + * + * If this operation fails, the resources remain preserved in memory. Userspace + * may attempt to call finish again. The resources will otherwise be reset + * during the next live update cycle. + * + * Return: 0 on success, negative error code on failure. + */ +struct liveupdate_session_finish { + __u32 size; + __u32 reserved; +}; + +#define LIVEUPDATE_SESSION_FINISH \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_FINISH) + +#endif /* _LIVEUPDATE_H */ diff --git a/src/include/uapi/linux/mount.h b/src/include/uapi/linux/mount.h index 96f2d20e78d64..719b46f6d5102 100644 --- a/src/include/uapi/linux/mount.h +++ b/src/include/uapi/linux/mount.h @@ -110,6 +110,7 @@ enum fsconfig_command { * fsmount() flags. */ #define FSMOUNT_CLOEXEC 0x00000001 +#define FSMOUNT_NAMESPACE 0x00000002 /* Create the mount in a new mount namespace */ /* * Mount attributes. diff --git a/src/include/uapi/linux/netfilter/nf_tables.h b/src/include/uapi/linux/netfilter/nf_tables.h index 45c71f7d21c25..1b915e32c9990 100644 --- a/src/include/uapi/linux/netfilter/nf_tables.h +++ b/src/include/uapi/linux/netfilter/nf_tables.h @@ -46,6 +46,7 @@ enum nft_registers { }; #define NFT_REG_MAX (__NFT_REG_MAX - 1) + #define NFT_REG_SIZE 16 #define NFT_REG32_SIZE 4 #define NFT_REG32_COUNT (NFT_REG32_15 - NFT_REG32_00 + 1) @@ -884,7 +885,7 @@ enum nft_exthdr_flags { * @NFT_EXTHDR_OP_TCPOPT: match against tcp options * @NFT_EXTHDR_OP_IPV4: match against ipv4 options * @NFT_EXTHDR_OP_SCTP: match against sctp chunks - * @NFT_EXTHDR_OP_DCCP: match against dccp otions + * @NFT_EXTHDR_OP_DCCP: match against dccp options */ enum nft_exthdr_op { NFT_EXTHDR_OP_IPV6, diff --git a/src/include/uapi/linux/nl80211.h b/src/include/uapi/linux/nl80211.h index b63f718509060..3d55bf4be36fe 100644 --- a/src/include/uapi/linux/nl80211.h +++ b/src/include/uapi/linux/nl80211.h @@ -906,8 +906,9 @@ * @NL80211_CMD_UNEXPECTED_FRAME: Used by an application controlling an AP * (or GO) interface (i.e. hostapd) to ask for unexpected frames to * implement sending deauth to stations that send unexpected class 3 - * frames. Also used as the event sent by the kernel when such a frame - * is received. + * frames. For NAN_DATA interfaces, this is used to report frames from + * unknown peers (A2 not assigned to any active NDP). + * Also used as the event sent by the kernel when such a frame is received. * For the event, the %NL80211_ATTR_MAC attribute carries the TA and * other attributes like the interface index are present. * If used as the command it must have an interface index and you can @@ -1361,6 +1362,59 @@ * user space that the NAN new cluster has been joined. The cluster ID is * indicated by %NL80211_ATTR_MAC. * + * @NL80211_CMD_INCUMBENT_SIGNAL_DETECT: Once any incumbent signal is detected + * on the operating channel in 6 GHz band, userspace is notified with the + * signal interference bitmap using + * %NL80211_ATTR_INCUMBENT_SIGNAL_INTERFERENCE_BITMAP. The current channel + * definition is also sent. + * + * @NL80211_CMD_NAN_SET_LOCAL_SCHED: Set the local NAN schedule. NAN must be + * operational (%NL80211_CMD_START_NAN was executed). Must contain + * %NL80211_ATTR_NAN_TIME_SLOTS and %NL80211_ATTR_NAN_AVAIL_BLOB, but + * %NL80211_ATTR_NAN_CHANNEL is optional (for example in case of a channel + * removal, that channel won't be provided). + * If %NL80211_ATTR_NAN_SCHED_DEFERRED is set, the command is a request + * from the device to perform an announced schedule update. See + * %NL80211_ATTR_NAN_SCHED_DEFERRED for more details. + * If not set, the schedule should be applied immediately. + * @NL80211_CMD_NAN_SCHED_UPDATE_DONE: Event sent to user space to notify that + * a deferred local NAN schedule update (requested with + * %NL80211_CMD_NAN_SET_LOCAL_SCHED and %NL80211_ATTR_NAN_SCHED_DEFERRED) + * has been completed. The presence of %NL80211_ATTR_NAN_SCHED_UPDATE_SUCCESS + * indicates that the update was successful. + * @NL80211_CMD_NAN_SET_PEER_SCHED: Set the peer NAN schedule. NAN + * must be operational (%NL80211_CMD_START_NAN was executed). + * Required attributes: %NL80211_ATTR_MAC (peer NMI address) and + * %NL80211_ATTR_NAN_COMMITTED_DW. + * Optionally, the full schedule can be provided by including all of: + * %NL80211_ATTR_NAN_SEQ_ID, %NL80211_ATTR_NAN_CHANNEL (one or more), and + * %NL80211_ATTR_NAN_PEER_MAPS (see &enum nl80211_nan_peer_map_attrs). + * If any of these three optional attributes is provided, all three must + * be provided. + * Each peer channel must be compatible with at least one local channel + * set by %NL80211_CMD_SET_LOCAL_NAN_SCHED. Different maps must not + * contain compatible channels. + * For single-radio devices (n_radio <= 1), different maps must not + * schedule the same time slot, as the device cannot operate on multiple + * channels simultaneously. + * When updating an existing peer schedule, the full new schedule must be + * provided - partial updates are not supported. The new schedule will + * completely replace the previous one. + * The peer schedule is automatically removed when the NMI station is + * removed. + * @NL80211_CMD_NAN_ULW_UPDATE: Notification from the driver to user space + * with the updated ULW blob of the device. User space can use this blob + * to attach to frames sent to peers. This notification contains + * %NL80211_ATTR_NAN_ULW with the ULW blob. + * @NL80211_CMD_NAN_CHANNEL_EVAC: Notification to indicate that a NAN + * channel has been evacuated due to resource conflicts with other + * interfaces. This can happen when another interface sharing the channel + * resource with NAN needs to move to a different channel (e.g., channel + * switch or link switch on a BSS interface). + * The notification contains %NL80211_ATTR_NAN_CHANNEL attribute + * identifying the evacuated channel. + * User space may reconfigure the local schedule in response to this + * notification. * @NL80211_CMD_MAX: highest used command number * @__NL80211_CMD_AFTER_LAST: internal use */ @@ -1624,6 +1678,18 @@ enum nl80211_commands { NL80211_CMD_NAN_NEXT_DW_NOTIFICATION, NL80211_CMD_NAN_CLUSTER_JOINED, + NL80211_CMD_INCUMBENT_SIGNAL_DETECT, + + NL80211_CMD_NAN_SET_LOCAL_SCHED, + + NL80211_CMD_NAN_SCHED_UPDATE_DONE, + + NL80211_CMD_NAN_SET_PEER_SCHED, + + NL80211_CMD_NAN_ULW_UPDATE, + + NL80211_CMD_NAN_CHANNEL_EVAC, + /* add new commands above here */ /* used to define NL80211_CMD_MAX below */ @@ -2651,7 +2717,8 @@ enum nl80211_commands { * a flow is assigned on each round of the DRR scheduler. * @NL80211_ATTR_HE_CAPABILITY: HE Capability information element (from * association request when used with NL80211_CMD_NEW_STATION). Can be set - * only if %NL80211_STA_FLAG_WME is set. + * only if %NL80211_STA_FLAG_WME is set (except for NAN, which uses WME + * anyway). * * @NL80211_ATTR_FTM_RESPONDER: nested attribute which user-space can include * in %NL80211_CMD_START_AP or %NL80211_CMD_SET_BEACON for fine timing @@ -2983,6 +3050,95 @@ enum nl80211_commands { * @NL80211_ATTR_DISABLE_UHR: Force UHR capable interfaces to disable * this feature during association. This is a flag attribute. * Currently only supported in mac80211 drivers. + * @NL80211_ATTR_NAN_CHANNEL: This is a nested attribute. There can be multiple + * attributes of this type, each one represents a channel definition and + * consists of top-level attributes like %NL80211_ATTR_WIPHY_FREQ. + * When used with %NL80211_CMD_NAN_SET_LOCAL_SCHED, it specifies + * the channel definitions on which the radio needs to operate during + * specific time slots. All of the channel definitions should be mutually + * incompatible. With this command, %NL80211_ATTR_NAN_CHANNEL_ENTRY and + * %NL80211_ATTR_NAN_RX_NSS are mandatory. + * When used with %NL80211_CMD_NAN_SET_PEER_SCHED, it configures the + * peer NAN channels. In that case, the channel definitions can be + * compatible to each other, or even identical just with different RX NSS. + * With this command, %NL80211_ATTR_NAN_CHANNEL_ENTRY and + * %NL80211_ATTR_NAN_RX_NSS are mandatory. + * The number of channels should fit the current configuration of channels + * and the possible interface combinations. + * If an existing NAN channel is changed but the chandef isn't, the + * channel entry must also remain unchanged. + * When used with %NL80211_CMD_NAN_CHANNEL_EVAC, this identifies the + * channels that were evacuated. + * @NL80211_ATTR_NAN_CHANNEL_ENTRY: a byte array of 6 bytes. contains the + * Channel Entry as defined in Wi-Fi Aware (TM) 4.0 specification Table + * 100 (Channel Entry format for the NAN Availability attribute). + * @NL80211_ATTR_NAN_RX_NSS: (u8) RX NSS used for a NAN channel. This is + * used with %NL80211_ATTR_NAN_CHANNEL when configuring NAN channels with + * %NL80211_CMD_NAN_SET_LOCAL_SCHED or %NL80211_CMD_NAN_SET_PEER_SCHED. + * @NL80211_ATTR_NAN_TIME_SLOTS: an array of u8 values and 32 cells. each value + * maps a time slot to the chandef on which the radio should operate on in + * that time. %NL80211_NAN_SCHED_NOT_AVAIL_SLOT indicates unscheduled. + * The chandef is represented using its index, where the index is the + * sequential number of the %NL80211_ATTR_NAN_CHANNEL attribute within all + * the attributes of this type. + * Each slots spans over 16TUs, hence the entire schedule spans over + * 512TUs. Other slot durations and periods are currently not supported. + * @NL80211_ATTR_NAN_AVAIL_BLOB: (Binary) The NAN Availability attribute blob, + * including the attribute header, as defined in Wi-Fi Aware (TM) 4.0 + * specification Table 93 (NAN Availability attribute format). Required with + * %NL80211_CMD_NAN_SET_LOCAL_SCHED to provide the raw NAN Availability + * attribute. Used by the device to publish Schedule Update NAFs. + * @NL80211_ATTR_NAN_SCHED_DEFERRED: Flag attribute used with + * %NL80211_CMD_NAN_SET_LOCAL_SCHED. When present, the command is a + * request from the device to perform an announced schedule update. This + * means that it needs to send the updated NAN availability to the peers, + * and do the actual switch on the right time (i.e. at the end of the slot + * after the slot in which the updated NAN Availability was sent). Since + * the slots management is done in the device, the update to the peers + * needs to be sent by the device, so it knows the actual switch time. + * If the flag is not set, the schedule should be applied immediately. + * When this flag is set, the total number of NAN channels from both the + * old and new schedules must not exceed the allowed number of local NAN + * channels, because with deferred scheduling the old channels cannot be + * removed before adding the new ones to free up space. + * @NL80211_ATTR_NAN_SCHED_UPDATE_SUCCESS: flag attribute used with + * %NL80211_CMD_NAN_SCHED_UPDATE_DONE to indicate that the deferred + * schedule update completed successfully. If this flag is not present, + * the update failed. + * @NL80211_ATTR_NAN_NMI_MAC: The address of the NMI station to which this NDI + * station belongs. Used with %NL80211_CMD_NEW_STATION when adding an NDI + * station. + * @NL80211_ATTR_NAN_ULW: (Binary) The initial ULW(s) as published by the + * peer, as defined in the Wi-Fi Aware (TM) 4.0 specification Table 109 + * (Unaligned Schedule attribute format). Used to configure the device + * with the initial ULW(s) of a peer, before the device starts tracking it. + * @NL80211_ATTR_NAN_COMMITTED_DW: (u16) The committed DW as published by the + * peer, as defined in the Wi-Fi Aware (TM) 4.0 specification Table 80 + * (Committed DW Information field format). + * @NL80211_ATTR_NAN_SEQ_ID: (u8) The sequence ID of the peer schedule that + * %NL80211_CMD_NAN_SET_PEER_SCHED defines. The device follows the + * sequence ID in the frames to identify newer schedules. Once a schedule + * with a higher sequence ID is received, the device may stop communicating + * with that peer until a new peer schedule with a matching sequence ID is + * received. + * @NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME: (u16) The maximum channel switch + * time, in microseconds. + * @NL80211_ATTR_NAN_PEER_MAPS: Nested array of peer schedule maps. + * Used with %NL80211_CMD_NAN_SET_PEER_SCHED. Contains up to 2 entries, + * each containing nested attributes from &enum nl80211_nan_peer_map_attrs. + * + * @NL80211_ATTR_INCUMBENT_SIGNAL_INTERFERENCE_BITMAP: u32 attribute specifying + * the signal interference bitmap detected on the operating bandwidth for + * %NL80211_CMD_INCUMBENT_SIGNAL_DETECT. Each bit represents a 20 MHz + * segment, lowest bit corresponds to the lowest 20 MHz segment, in the + * operating bandwidth where the interference is detected. Punctured + * sub-channels are included in the bitmap structure; however, since + * interference detection is not performed on these sub-channels, their + * corresponding bits are consistently set to zero. + * + * @NL80211_ATTR_UHR_OPERATION: Full UHR Operation element, as it appears in + * association response etc., since it's abridged in the beacon. Used + * for START_AP etc. * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined @@ -3557,6 +3713,26 @@ enum nl80211_attrs { NL80211_ATTR_UHR_CAPABILITY, NL80211_ATTR_DISABLE_UHR, + NL80211_ATTR_INCUMBENT_SIGNAL_INTERFERENCE_BITMAP, + + NL80211_ATTR_UHR_OPERATION, + + NL80211_ATTR_NAN_CHANNEL, + NL80211_ATTR_NAN_CHANNEL_ENTRY, + NL80211_ATTR_NAN_TIME_SLOTS, + NL80211_ATTR_NAN_RX_NSS, + NL80211_ATTR_NAN_AVAIL_BLOB, + NL80211_ATTR_NAN_SCHED_DEFERRED, + NL80211_ATTR_NAN_SCHED_UPDATE_SUCCESS, + + NL80211_ATTR_NAN_NMI_MAC, + + NL80211_ATTR_NAN_ULW, + NL80211_ATTR_NAN_COMMITTED_DW, + NL80211_ATTR_NAN_SEQ_ID, + NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME, + NL80211_ATTR_NAN_PEER_MAPS, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -3650,6 +3826,9 @@ enum nl80211_attrs { * @NL80211_IFTYPE_OCB: Outside Context of a BSS * This mode corresponds to the MIB variable dot11OCBActivated=true * @NL80211_IFTYPE_NAN: NAN device interface type (not a netdev) + * @NL80211_IFTYPE_NAN_DATA: NAN data interface type (netdev); NAN data + * interfaces can only be brought up (IFF_UP) when a NAN interface + * already exists and NAN has been started (using %NL80211_CMD_START_NAN). * @NL80211_IFTYPE_MAX: highest interface type number currently defined * @NUM_NL80211_IFTYPES: number of defined interface types * @@ -3671,6 +3850,7 @@ enum nl80211_iftype { NL80211_IFTYPE_P2P_DEVICE, NL80211_IFTYPE_OCB, NL80211_IFTYPE_NAN, + NL80211_IFTYPE_NAN_DATA, /* keep last */ NUM_NL80211_IFTYPES, @@ -4359,6 +4539,46 @@ enum nl80211_band_attr { #define NL80211_BAND_ATTR_HT_CAPA NL80211_BAND_ATTR_HT_CAPA +/** + * enum nl80211_nan_phy_cap_attr - NAN PHY capabilities attributes + * @__NL80211_NAN_PHY_CAP_ATTR_INVALID: attribute number 0 is reserved + * @NL80211_NAN_PHY_CAP_ATTR_HT_MCS_SET: 16-byte attribute containing HT MCS set + * @NL80211_NAN_PHY_CAP_ATTR_HT_CAPA: HT capabilities (u16) + * @NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_FACTOR: HT A-MPDU factor (u8) + * @NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_DENSITY: HT A-MPDU density (u8) + * @NL80211_NAN_PHY_CAP_ATTR_VHT_MCS_SET: 8-byte attribute containing VHT MCS set + * @NL80211_NAN_PHY_CAP_ATTR_VHT_CAPA: VHT capabilities (u32) + * @NL80211_NAN_PHY_CAP_ATTR_HE_MAC: HE MAC capabilities + * @NL80211_NAN_PHY_CAP_ATTR_HE_PHY: HE PHY capabilities + * @NL80211_NAN_PHY_CAP_ATTR_HE_MCS_SET: HE supported NSS/MCS combinations + * @NL80211_NAN_PHY_CAP_ATTR_HE_PPE: HE PPE thresholds + * @NL80211_NAN_PHY_CAP_ATTR_MAX: highest NAN PHY cap attribute number + * @__NL80211_NAN_PHY_CAP_ATTR_AFTER_LAST: internal use + */ +enum nl80211_nan_phy_cap_attr { + __NL80211_NAN_PHY_CAP_ATTR_INVALID, + + /* HT capabilities */ + NL80211_NAN_PHY_CAP_ATTR_HT_MCS_SET, + NL80211_NAN_PHY_CAP_ATTR_HT_CAPA, + NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_FACTOR, + NL80211_NAN_PHY_CAP_ATTR_HT_AMPDU_DENSITY, + + /* VHT capabilities */ + NL80211_NAN_PHY_CAP_ATTR_VHT_MCS_SET, + NL80211_NAN_PHY_CAP_ATTR_VHT_CAPA, + + /* HE capabilities */ + NL80211_NAN_PHY_CAP_ATTR_HE_MAC, + NL80211_NAN_PHY_CAP_ATTR_HE_PHY, + NL80211_NAN_PHY_CAP_ATTR_HE_MCS_SET, + NL80211_NAN_PHY_CAP_ATTR_HE_PPE, + + /* keep last */ + __NL80211_NAN_PHY_CAP_ATTR_AFTER_LAST, + NL80211_NAN_PHY_CAP_ATTR_MAX = __NL80211_NAN_PHY_CAP_ATTR_AFTER_LAST - 1 +}; + /** * enum nl80211_wmm_rule - regulatory wmm rule * @@ -4480,6 +4700,10 @@ enum nl80211_wmm_rule { * as a non-primary subchannel. Only applicable to S1G channels. * @NL80211_FREQUENCY_ATTR_NO_UHR: UHR operation is not allowed on this channel * in current regulatory domain. + * @NL80211_FREQUENCY_ATTR_CAC_START_TIME: Channel Availability Check (CAC) + * start time (CLOCK_BOOTTIME, nanoseconds). Only present when CAC is + * currently in progress on this channel. + * @NL80211_FREQUENCY_ATTR_PAD: attribute used for padding for 64-bit alignment * @NL80211_FREQUENCY_ATTR_MAX: highest frequency attribute number * currently defined * @__NL80211_FREQUENCY_ATTR_AFTER_LAST: internal use @@ -4530,6 +4754,8 @@ enum nl80211_frequency_attr { NL80211_FREQUENCY_ATTR_NO_16MHZ, NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY, NL80211_FREQUENCY_ATTR_NO_UHR, + NL80211_FREQUENCY_ATTR_CAC_START_TIME, + NL80211_FREQUENCY_ATTR_PAD, /* keep last */ __NL80211_FREQUENCY_ATTR_AFTER_LAST, @@ -5466,6 +5692,8 @@ enum nl80211_bss_status { * @NL80211_AUTHTYPE_FILS_SK_PFS: Fast Initial Link Setup shared key with PFS * @NL80211_AUTHTYPE_FILS_PK: Fast Initial Link Setup public key * @NL80211_AUTHTYPE_EPPKE: Enhanced Privacy Protection Key Exchange + * @NL80211_AUTHTYPE_IEEE8021X: IEEE 802.1X authentication utilizing + * Authentication frames * @__NL80211_AUTHTYPE_NUM: internal * @NL80211_AUTHTYPE_MAX: maximum valid auth algorithm * @NL80211_AUTHTYPE_AUTOMATIC: determine automatically (if necessary by @@ -5482,6 +5710,7 @@ enum nl80211_auth_type { NL80211_AUTHTYPE_FILS_SK_PFS, NL80211_AUTHTYPE_FILS_PK, NL80211_AUTHTYPE_EPPKE, + NL80211_AUTHTYPE_IEEE8021X, /* keep last */ __NL80211_AUTHTYPE_NUM, @@ -6795,6 +7024,11 @@ enum nl80211_feature_flags { * frames in both non‑AP STA and AP mode as specified in * "IEEE P802.11bi/D3.0, 12.16.6". * + * @NL80211_EXT_FEATURE_IEEE8021X_AUTH: Driver supports IEEE 802.1X + * authentication utilizing Authentication frames with user space SME + * (NL80211_CMD_AUTHENTICATE) in non-AP STA mode, as specified in + * "IEEE P802.11bi/D4.0, 12.16.5". + * * @NUM_NL80211_EXT_FEATURES: number of extended features. * @MAX_NL80211_EXT_FEATURES: highest extended feature index. */ @@ -6873,6 +7107,7 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_BEACON_RATE_EHT, NL80211_EXT_FEATURE_EPPKE, NL80211_EXT_FEATURE_ASSOC_FRAME_ENCRYPTION, + NL80211_EXT_FEATURE_IEEE8021X_AUTH, /* add new features before the definition below */ NUM_NL80211_EXT_FEATURES, @@ -8517,6 +8752,8 @@ enum nl80211_s1g_short_beacon_attrs { * @NL80211_NAN_CAPA_CAPABILITIES: u8 attribute containing the * capabilities of the device as defined in Wi-Fi Aware (TM) * specification Table 79 (Capabilities field). + * @NL80211_NAN_CAPA_PHY: nested attribute containing band-agnostic + * capabilities for NAN data path. See &enum nl80211_nan_phy_cap_attr. * @__NL80211_NAN_CAPABILITIES_LAST: Internal * @NL80211_NAN_CAPABILITIES_MAX: Highest NAN capability attribute. */ @@ -8529,9 +8766,38 @@ enum nl80211_nan_capabilities { NL80211_NAN_CAPA_NUM_ANTENNAS, NL80211_NAN_CAPA_MAX_CHANNEL_SWITCH_TIME, NL80211_NAN_CAPA_CAPABILITIES, + NL80211_NAN_CAPA_PHY, /* keep last */ __NL80211_NAN_CAPABILITIES_LAST, NL80211_NAN_CAPABILITIES_MAX = __NL80211_NAN_CAPABILITIES_LAST - 1, }; +/** + * enum nl80211_nan_peer_map_attrs - NAN peer schedule map attributes + * + * Nested attributes used within %NL80211_ATTR_NAN_PEER_MAPS to define + * individual peer schedule maps. + * + * @__NL80211_NAN_PEER_MAP_ATTR_INVALID: Invalid + * @NL80211_NAN_PEER_MAP_ATTR_MAP_ID: (u8) The map ID for this schedule map. + * @NL80211_NAN_PEER_MAP_ATTR_TIME_SLOTS: An array of u8 values with 32 cells. + * Each value maps a time slot to a channel index within the schedule's + * channel list (%NL80211_ATTR_NAN_CHANNEL attributes). + * %NL80211_NAN_SCHED_NOT_AVAIL_SLOT indicates unscheduled. + * @__NL80211_NAN_PEER_MAP_ATTR_LAST: Internal + * @NL80211_NAN_PEER_MAP_ATTR_MAX: Highest peer map attribute + */ +enum nl80211_nan_peer_map_attrs { + __NL80211_NAN_PEER_MAP_ATTR_INVALID, + + NL80211_NAN_PEER_MAP_ATTR_MAP_ID, + NL80211_NAN_PEER_MAP_ATTR_TIME_SLOTS, + + /* keep last */ + __NL80211_NAN_PEER_MAP_ATTR_LAST, + NL80211_NAN_PEER_MAP_ATTR_MAX = __NL80211_NAN_PEER_MAP_ATTR_LAST - 1, +}; + +#define NL80211_NAN_SCHED_NOT_AVAIL_SLOT 0xff + #endif /* __LINUX_NL80211_H */ diff --git a/src/include/uapi/linux/nsfs.h b/src/include/uapi/linux/nsfs.h new file mode 100644 index 0000000000000..96bd591feb678 --- /dev/null +++ b/src/include/uapi/linux/nsfs.h @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_NSFS_H +#define __LINUX_NSFS_H + +#include +#include + +#define NSIO 0xb7 + +/* Returns a file descriptor that refers to an owning user namespace */ +#define NS_GET_USERNS _IO(NSIO, 0x1) +/* Returns a file descriptor that refers to a parent namespace */ +#define NS_GET_PARENT _IO(NSIO, 0x2) +/* Returns the type of namespace (CLONE_NEW* value) referred to by + file descriptor */ +#define NS_GET_NSTYPE _IO(NSIO, 0x3) +/* Get owner UID (in the caller's user namespace) for a user namespace */ +#define NS_GET_OWNER_UID _IO(NSIO, 0x4) +/* Translate pid from target pid namespace into the caller's pid namespace. */ +#define NS_GET_PID_FROM_PIDNS _IOR(NSIO, 0x6, int) +/* Return thread-group leader id of pid in the callers pid namespace. */ +#define NS_GET_TGID_FROM_PIDNS _IOR(NSIO, 0x7, int) +/* Translate pid from caller's pid namespace into a target pid namespace. */ +#define NS_GET_PID_IN_PIDNS _IOR(NSIO, 0x8, int) +/* Return thread-group leader id of pid in the target pid namespace. */ +#define NS_GET_TGID_IN_PIDNS _IOR(NSIO, 0x9, int) + +struct mnt_ns_info { + __u32 size; + __u32 nr_mounts; + __u64 mnt_ns_id; +}; + +#define MNT_NS_INFO_SIZE_VER0 16 /* size of first published struct */ + +/* Get information about namespace. */ +#define NS_MNT_GET_INFO _IOR(NSIO, 10, struct mnt_ns_info) +/* Get next namespace. */ +#define NS_MNT_GET_NEXT _IOR(NSIO, 11, struct mnt_ns_info) +/* Get previous namespace. */ +#define NS_MNT_GET_PREV _IOR(NSIO, 12, struct mnt_ns_info) + +/* Retrieve namespace identifiers. */ +#define NS_GET_MNTNS_ID _IOR(NSIO, 5, __u64) +#define NS_GET_ID _IOR(NSIO, 13, __u64) + +enum init_ns_ino { + IPC_NS_INIT_INO = 0xEFFFFFFFU, + UTS_NS_INIT_INO = 0xEFFFFFFEU, + USER_NS_INIT_INO = 0xEFFFFFFDU, + PID_NS_INIT_INO = 0xEFFFFFFCU, + CGROUP_NS_INIT_INO = 0xEFFFFFFBU, + TIME_NS_INIT_INO = 0xEFFFFFFAU, + NET_NS_INIT_INO = 0xEFFFFFF9U, + MNT_NS_INIT_INO = 0xEFFFFFF8U, +}; + +struct nsfs_file_handle { + __u64 ns_id; + __u32 ns_type; + __u32 ns_inum; +}; + +#define NSFS_FILE_HANDLE_SIZE_VER0 16 /* sizeof first published struct */ +#define NSFS_FILE_HANDLE_SIZE_LATEST sizeof(struct nsfs_file_handle) /* sizeof latest published struct */ + +enum init_ns_id { + IPC_NS_INIT_ID = 1ULL, + UTS_NS_INIT_ID = 2ULL, + USER_NS_INIT_ID = 3ULL, + PID_NS_INIT_ID = 4ULL, + CGROUP_NS_INIT_ID = 5ULL, + TIME_NS_INIT_ID = 6ULL, + NET_NS_INIT_ID = 7ULL, + MNT_NS_INIT_ID = 8ULL, +}; + +enum ns_type { + TIME_NS = (1ULL << 7), /* CLONE_NEWTIME */ + MNT_NS = (1ULL << 17), /* CLONE_NEWNS */ + CGROUP_NS = (1ULL << 25), /* CLONE_NEWCGROUP */ + UTS_NS = (1ULL << 26), /* CLONE_NEWUTS */ + IPC_NS = (1ULL << 27), /* CLONE_NEWIPC */ + USER_NS = (1ULL << 28), /* CLONE_NEWUSER */ + PID_NS = (1ULL << 29), /* CLONE_NEWPID */ + NET_NS = (1ULL << 30), /* CLONE_NEWNET */ +}; + +/** + * struct ns_id_req - namespace ID request structure + * @size: size of this structure + * @spare: reserved for future use + * @filter: filter mask + * @ns_id: last namespace id + * @user_ns_id: owning user namespace ID + * + * Structure for passing namespace ID and miscellaneous parameters to + * statns(2) and listns(2). + * + * For statns(2) @param represents the request mask. + * For listns(2) @param represents the last listed mount id (or zero). + */ +struct ns_id_req { + __u32 size; + __u32 spare; + __u64 ns_id; + struct /* listns */ { + __u32 ns_type; + __u32 spare2; + __u64 user_ns_id; + }; +}; + +/* + * Special @user_ns_id value that can be passed to listns() + */ +#define LISTNS_CURRENT_USER 0xffffffffffffffff /* Caller's userns */ + +/* List of all ns_id_req versions. */ +#define NS_ID_REQ_SIZE_VER0 32 /* sizeof first published struct */ + +#endif /* __LINUX_NSFS_H */ diff --git a/src/include/uapi/linux/openat2.h b/src/include/uapi/linux/openat2.h new file mode 100644 index 0000000000000..4759c471676cc --- /dev/null +++ b/src/include/uapi/linux/openat2.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _LINUX_OPENAT2_H +#define _LINUX_OPENAT2_H + +#include + +/* + * Arguments for how openat2(2) should open the target path. If only @flags and + * @mode are non-zero, then openat2(2) operates very similarly to openat(2). + * + * However, unlike openat(2), unknown or invalid bits in @flags result in + * -EINVAL rather than being silently ignored. @mode must be zero unless one of + * {O_CREAT, O_TMPFILE} are set. + * + * @flags: O_* flags. + * @mode: O_CREAT/O_TMPFILE file mode. + * @resolve: RESOLVE_* flags. + */ +struct open_how { + __u64 flags; + __u64 mode; + __u64 resolve; +}; + +/* how->resolve flags for openat2(2). */ +#define RESOLVE_NO_XDEV 0x01 /* Block mount-point crossings + (includes bind-mounts). */ +#define RESOLVE_NO_MAGICLINKS 0x02 /* Block traversal through procfs-style + "magic-links". */ +#define RESOLVE_NO_SYMLINKS 0x04 /* Block traversal through all symlinks + (implies OEXT_NO_MAGICLINKS) */ +#define RESOLVE_BENEATH 0x08 /* Block "lexical" trickery like + "..", symlinks, and absolute + paths which escape the dirfd. */ +#define RESOLVE_IN_ROOT 0x10 /* Make all jumps to "/" and ".." + be scoped inside the dirfd + (similar to chroot(2)). */ +#define RESOLVE_CACHED 0x20 /* Only complete if resolution can be + completed through cached lookup. May + return -EAGAIN if that's not + possible. */ + +#endif /* _LINUX_OPENAT2_H */ diff --git a/src/include/uapi/linux/prctl.h b/src/include/uapi/linux/prctl.h index 55b0446fff9d9..b6ec6f6937195 100644 --- a/src/include/uapi/linux/prctl.h +++ b/src/include/uapi/linux/prctl.h @@ -397,30 +397,23 @@ struct prctl_mm_map { # define PR_RSEQ_SLICE_EXT_ENABLE 0x01 /* - * Get the current indirect branch tracking configuration for the current - * thread, this will be the value configured via PR_SET_INDIR_BR_LP_STATUS. + * Get or set the control flow integrity (CFI) configuration for the + * current thread. + * + * Some per-thread control flow integrity settings are not yet + * controlled through this prctl(); see for example + * PR_{GET,SET,LOCK}_SHADOW_STACK_STATUS */ -#define PR_GET_INDIR_BR_LP_STATUS 80 - +#define PR_GET_CFI 80 +#define PR_SET_CFI 81 /* - * Set the indirect branch tracking configuration. PR_INDIR_BR_LP_ENABLE will - * enable cpu feature for user thread, to track all indirect branches and ensure - * they land on arch defined landing pad instruction. - * x86 - If enabled, an indirect branch must land on an ENDBRANCH instruction. - * arch64 - If enabled, an indirect branch must land on a BTI instruction. - * riscv - If enabled, an indirect branch must land on an lpad instruction. - * PR_INDIR_BR_LP_DISABLE will disable feature for user thread and indirect - * branches will no more be tracked by cpu to land on arch defined landing pad - * instruction. - */ -#define PR_SET_INDIR_BR_LP_STATUS 81 -# define PR_INDIR_BR_LP_ENABLE (1UL << 0) - -/* - * Prevent further changes to the specified indirect branch tracking - * configuration. All bits may be locked via this call, including - * undefined bits. + * Forward-edge CFI variants (excluding ARM64 BTI, which has its own + * prctl()s). */ -#define PR_LOCK_INDIR_BR_LP_STATUS 82 +#define PR_CFI_BRANCH_LANDING_PADS 0 +/* Return and control values for PR_{GET,SET}_CFI */ +# define PR_CFI_ENABLE _BITUL(0) +# define PR_CFI_DISABLE _BITUL(1) +# define PR_CFI_LOCK _BITUL(2) #endif /* _LINUX_PRCTL_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-generator.c b/src/integritysetup/integritysetup-generator.c index ab01873cdd19e..ce7a8ce1789ec 100644 --- a/src/integritysetup/integritysetup-generator.c +++ b/src/integritysetup/integritysetup-generator.c @@ -80,7 +80,11 @@ static int create_disk( "SourcePath=%s\n" "DefaultDependencies=no\n" "IgnoreOnIsolate=true\n" - "After=integritysetup-pre.target systemd-udevd-kernel.socket\n" + /* The systemd-udevd.service ordering is mostly about shutdown, not startup: on stop the dm + * device is detached via an ioctl carrying a udev cookie that blocks in dm_udev_wait() until + * udev releases it (95-dm-notify.rules). Stopping before udevd keeps it around to service the + * cookie; otherwise during soft-reboot/shutdown udevd may exit first and the detach hangs. */ + "After=integritysetup-pre.target systemd-udevd-kernel.socket systemd-udevd.service\n" "Before=blockdev@dev-mapper-%%i.target\n" "Wants=blockdev@dev-mapper-%%i.target\n" "Conflicts=umount.target\n" diff --git a/src/integritysetup/integritysetup.c b/src/integritysetup/integritysetup.c index 5eed1b5665d5e..619c3b10db50d 100644 --- a/src/integritysetup/integritysetup.c +++ b/src/integritysetup/integritysetup.c @@ -7,12 +7,15 @@ #include "argv-util.h" #include "cryptsetup-util.h" #include "fileio.h" +#include "format-table.h" +#include "help-util.h" #include "integrity-util.h" #include "log.h" #include "main-func.h" #include "path-util.h" -#include "pretty-print.h" +#include "string-table.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" #include "verbs.h" @@ -20,27 +23,42 @@ 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; + _cleanup_(table_unrefp) Table *verbs = NULL; int r; - r = terminal_urlify_man("systemd-integritysetup@.service", "8", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; + + help_cmdline("COMMAND ..."); + help_abstract("Attach or detach an integrity protected block device."); - printf("%s attach VOLUME DEVICE [HMAC_KEY_FILE|-] [OPTIONS]\n" - "%s detach VOLUME\n\n" - "Attach or detach an integrity protected block device.\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - program_invocation_short_name, - link); + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + help_man_page_reference("systemd-integritysetup@.service", "8"); return 0; } @@ -72,24 +90,16 @@ 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) { +VERB(verb_attach, "attach", "VOLUME DEVICE [HMAC_KEY_FILE|-] [OPTIONS]", 3, 5, 0, + "Attach an integrity protected block device"); +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 +131,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 +154,21 @@ 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) { +VERB(verb_detach, "detach", "VOLUME", 2, 2, 0, + "Detach an integrity protected block device"); +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 +179,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 +189,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,22 +197,20 @@ 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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; umask(0022); - static const Verb verbs[] = { - { "attach", 3, 5, 0, verb_attach }, - { "detach", 2, 2, 0, verb_detach }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb(strv_skip(argv, 1), /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); 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..bb1f901d9c867 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; @@ -181,9 +184,12 @@ static ssize_t request_reader_entries( m->n_entries <= 0) return MHD_CONTENT_READER_END_OF_STREAM; - if (m->n_skip < 0) + if (m->n_skip < 0) { + /* request_parse_range_skip_and_n_entries() rejects INT64_MIN, so the negation + * below cannot overflow. */ + assert(m->n_skip >= -INT64_MAX); r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1); - else if (m->n_skip > 0) { + } else if (m->n_skip > 0) { r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1); if (r < 0) { log_error_errno(r, "Failed to skip journal entries: %m"); @@ -191,7 +197,7 @@ static ssize_t request_reader_entries( } /* We skipped beyond the end, make sure entries between the cursor and n_skip offset * from it are not returned. */ - if (r < m->n_skip + 1) { + if ((uint64_t) r < (uint64_t) m->n_skip + 1) { m->n_skip -= r; if (!m->follow) @@ -309,7 +315,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; @@ -345,6 +351,11 @@ static int request_parse_range_skip_and_n_entries( r = safe_atoi64(t, &m->n_skip); if (r < 0) return r; + + /* The consumer negates n_skip, and negating INT64_MIN in signed arithmetic is undefined + * behaviour, so reject it up front. */ + if (m->n_skip == INT64_MIN) + return -ERANGE; } p = (colon2 ?: colon) + 1; @@ -457,7 +468,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 +575,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 +625,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 +753,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 +776,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 +808,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 +824,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 +908,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 +1035,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 +1099,51 @@ 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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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, opts.arg, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_CONNECT_SOCKET, NULL, &arg_cert_pem, NULL); @@ -1180,13 +1152,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, opts.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, opts.arg, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_CONNECT_SOCKET, NULL, &arg_trust_pem, NULL); @@ -1199,38 +1185,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, opts.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, opts.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(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program does not take arguments."); @@ -1286,6 +1266,10 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + r = DLOPEN_MICROHTTPD(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + journal_browse_prepare(); assert_se(sigaction(SIGTERM, &sigterm, NULL) >= 0); @@ -1319,11 +1303,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..44b169c591934 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,18 +11,22 @@ #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" +#include "journal-authenticate.h" #include "journal-compression-util.h" #include "journal-remote.h" #include "journal-remote-write.h" #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 +109,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 +212,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; @@ -219,21 +222,23 @@ static int build_accept_encoding(char **ret) { return 0; } -static int request_meta(void **connection_cls, int fd, char *hostname) { +static int request_meta(void **connection_cls, int fd, char *_hostname) { int r; assert(connection_cls); + /* This takes ownership of the hostname in all cases, including on failure. */ + _cleanup_free_ char *hostname = TAKE_PTR(_hostname); + if (*connection_cls) return 0; /* already assigned. */ Writer *writer; r = journal_remote_get_writer(journal_remote_server_global, hostname, &writer); if (r < 0) - return log_warning_errno(r, "Failed to get writer for source %s: %m", - hostname); + return log_warning_errno(r, "Failed to get writer for source %s: %m", hostname); - _cleanup_(source_freep) RemoteSource *source = source_new(fd, true, hostname, writer); + _cleanup_(source_freep) RemoteSource *source = source_new(fd, true, TAKE_PTR(hostname), writer); if (!source) return log_oom(); @@ -286,7 +291,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 +364,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 +385,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 +399,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 +423,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, @@ -443,13 +448,12 @@ static mhd_result request_handler( assert(hostname); - r = request_meta(connection_cls, fd, hostname); + r = request_meta(connection_cls, fd, TAKE_PTR(hostname)); if (r == -ENOMEM) return respond_oom(connection); else if (r < 0) return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "%m"); - hostname = NULL; return MHD_YES; } @@ -462,6 +466,12 @@ static int setup_microhttpd_server(RemoteServer *s, const char *trust) { #if HAVE_MICROHTTPD + int r; + + r = DLOPEN_MICROHTTPD(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + 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 +491,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 +534,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 +622,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 +843,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 +869,119 @@ 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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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, opts.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, opts.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, opts.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(opts.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, opts.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(opts.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, opts.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, opts.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, opts.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, opts.arg); if (r < 0) return r; #else @@ -994,33 +989,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(opts.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(opts.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", opts.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", opts.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", opts.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 = opts.arg;;) { _cleanup_free_ char *word = NULL; r = extract_first_word(&p, &word, ",", 0); @@ -1037,14 +1034,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(opts.arg, 1024, &arg_max_use); + if (r < 0) + return log_error_errno(r, "Failed to parse --max-use= value: %s", opts.arg); + break; + + OPTION_LONG("keep-free", "BYTES", "Minimum disk space to keep free"): + r = parse_size(opts.arg, 1024, &arg_keep_free); + if (r < 0) + return log_error_errno(r, "Failed to parse --keep-free= value: %s", opts.arg); + break; + + OPTION_LONG("max-file-size", "BYTES", "Maximum size of individual journal files"): + r = parse_size(opts.arg, 1024, &arg_max_size); + if (r < 0) + return log_error_errno(r, "Failed to parse --max-file-size= value: %s", opts.arg); + break; - default: - assert_not_reached(); + OPTION_LONG("max-files", "N", "Maximum number of journal files to keep"): + r = safe_atou64(opts.arg, &arg_n_max_files); + if (r < 0) + return log_error_errno(r, "Failed to parse --max-files= value: %s", opts.arg); + break; } - arg_files = strv_copy(strv_skip(argv, optind)); + arg_files = strv_copy(option_parser_get_args(&opts)); if (!arg_files) return log_oom(); @@ -1103,6 +1118,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, @@ -1150,6 +1167,8 @@ static int run(int argc, char **argv) { log_setup(); + journal_auth_init(); + r = parse_config(); if (r < 0) return r; diff --git a/src/journal-remote/journal-remote-parse.c b/src/journal-remote/journal-remote-parse.c index 4794393d93165..00f14c765aae1 100644 --- a/src/journal-remote/journal-remote-parse.c +++ b/src/journal-remote/journal-remote-parse.c @@ -24,7 +24,8 @@ RemoteSource* source_free(RemoteSource *source) { /** * Initialize zero-filled source with given values. On success, takes - * ownership of fd, name, and writer, otherwise does not touch them. + * ownership of fd and writer, otherwise does not touch them. Always takes + * ownership of name, even on failure. */ RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer) { RemoteSource *source; @@ -35,8 +36,10 @@ RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer) { assert(fd >= 0); source = new0(RemoteSource, 1); - if (!source) + if (!source) { + free(name); return NULL; + } source->importer = JOURNAL_IMPORTER_MAKE(fd); source->importer.passive_fd = passive_fd; diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c index 47d3881a5d754..12886f512fd9b 100644 --- a/src/journal-remote/journal-remote.c +++ b/src/journal-remote/journal-remote.c @@ -162,11 +162,12 @@ static int dispatch_raw_connection_event(sd_event_source *event, void *userdata); static int get_source_for_fd(RemoteServer *s, - int fd, char *name, RemoteSource **source) { + int fd, char *_name, RemoteSource **source) { + _cleanup_free_ char *name = TAKE_PTR(_name); Writer *writer; int r; - /* This takes ownership of name, but only on success. */ + /* This takes ownership of the name in all cases, including on failure. */ assert(s); assert(fd >= 0); @@ -177,11 +178,10 @@ static int get_source_for_fd(RemoteServer *s, r = journal_remote_get_writer(s, name, &writer); if (r < 0) - return log_warning_errno(r, "Failed to get writer for source %s: %m", - name); + return log_warning_errno(r, "Failed to get writer for source %s: %m", name); if (!s->sources[fd]) { - s->sources[fd] = source_new(fd, false, name, writer); + s->sources[fd] = source_new(fd, false, TAKE_PTR(name), writer); if (!s->sources[fd]) { writer_unref(writer); return log_oom(); @@ -211,29 +211,29 @@ static int remove_source(RemoteServer *s, int fd) { return 0; } -int journal_remote_add_source(RemoteServer *s, int fd, char *name, bool own_name) { +int journal_remote_add_source(RemoteServer *s, int fd, char *_name, bool own_name) { + _cleanup_free_ char *name = NULL; RemoteSource *source = NULL; int r; - /* This takes ownership of name, even on failure, if own_name is true. */ + /* This takes ownership of _name, even on failure, if own_name is true. */ assert(s); assert(fd >= 0); - assert(name); + assert(_name); - if (!own_name) { - name = strdup(name); + if (own_name) + name = TAKE_PTR(_name); + else { + name = strdup(_name); if (!name) return log_oom(); } - r = get_source_for_fd(s, fd, name, &source); - if (r < 0) { - log_error_errno(r, "Failed to create source for fd:%d (%s): %m", - fd, name); - free(name); - return r; - } + /* get_source_for_fd() takes ownership of the name in all cases, so it must not be touched below. */ + r = get_source_for_fd(s, fd, TAKE_PTR(name), &source); + if (r < 0) + return log_error_errno(r, "Failed to create source for fd:%d: %m", fd); r = sd_event_add_io(s->event, &source->event, fd, EPOLLIN|EPOLLRDHUP|EPOLLPRI, @@ -246,7 +246,7 @@ int journal_remote_add_source(RemoteServer *s, int fd, char *name, bool own_name if (r == 0) r = sd_event_source_set_enabled(source->buffer_event, SD_EVENT_OFF); } else if (r == -EPERM) { - log_debug("Falling back to sd_event_add_defer for fd:%d (%s)", fd, name); + log_debug("Falling back to sd_event_add_defer for fd:%d (%s)", fd, source->importer.name); r = sd_event_add_defer(s->event, &source->event, dispatch_blocking_source_event, source); if (r == 0) @@ -258,7 +258,7 @@ int journal_remote_add_source(RemoteServer *s, int fd, char *name, bool own_name goto error; } - r = sd_event_source_set_description(source->event, name); + r = sd_event_source_set_description(source->event, source->importer.name); if (r < 0) { log_error_errno(r, "Failed to set source name for fd:%d: %m", fd); goto error; 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..2eb47d21300d2 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include @@ -11,15 +10,18 @@ #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" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "glob-util.h" #include "hashmap.h" +#include "help-util.h" #include "journal-header-util.h" #include "journal-upload.h" #include "journal-util.h" @@ -27,9 +29,9 @@ #include "logs-show.h" #include "main-func.h" #include "mkdir.h" +#include "options.h" #include "parse-argument.h" #include "parse-helpers.h" -#include "pretty-print.h" #include "process-util.h" #include "string-util.h" #include "strv.h" @@ -81,20 +83,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 +182,6 @@ int start_upload(Uploader *u, size_t nmemb, void *userdata), void *data) { - CURLcode code; - assert(u); assert(input_callback); @@ -203,26 +189,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 +230,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 +242,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 +315,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 +354,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 +486,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 +515,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 +530,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 +546,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 +576,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 +597,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 +625,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), @@ -696,194 +684,145 @@ 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-upload.service", "8", &link); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; + + help_cmdline("-u URL {FILE|-}..."); + help_abstract("Upload journal events to a remote server."); - printf("%s -u URL {FILE|-}...\n\n" - "Upload journal events to a remote server.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -u --url=URL Upload to this address (default port " - STRINGIFY(DEFAULT_PORT) ")\n" - " --key=FILENAME Specify key in PEM format (default:\n" - " \"" PRIV_KEY_FILE "\")\n" - " --cert=FILENAME Specify certificate in PEM format (default:\n" - " \"" CERT_FILE "\")\n" - " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n" - " \"" TRUST_FILE "\")\n" - " --system Use the system journal\n" - " --user Use the user journal for the current user\n" - " -m --merge Use all available journals\n" - " -M --machine=CONTAINER Operate on local container\n" - " --namespace=NAMESPACE Use journal files from namespace\n" - " -D --directory=PATH Use journal files from directory\n" - " --file=PATH Use this journal file\n" - " --cursor=CURSOR Start at the specified cursor\n" - " --after-cursor=CURSOR Start after the specified cursor\n" - " --follow[=BOOL] Do [not] wait for input\n" - " --save-state[=FILE] Save uploaded cursors (default \n" - " " STATE_FILE ")\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("systemd-journal-upload.service", "8"); 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_FILE, - ARG_CURSOR, - ARG_AFTER_CURSOR, - ARG_FOLLOW, - ARG_SAVE_STATE, - ARG_NAMESPACE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "url", required_argument, NULL, 'u' }, - { "key", required_argument, NULL, ARG_KEY }, - { "cert", required_argument, NULL, ARG_CERT }, - { "trust", required_argument, NULL, ARG_TRUST }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "merge", no_argument, NULL, 'm' }, - { "machine", required_argument, NULL, 'M' }, - { "namespace", required_argument, NULL, ARG_NAMESPACE }, - { "directory", required_argument, NULL, 'D' }, - { "file", required_argument, NULL, ARG_FILE }, - { "cursor", required_argument, NULL, ARG_CURSOR }, - { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR }, - { "follow", optional_argument, NULL, ARG_FOLLOW }, - { "save-state", optional_argument, NULL, ARG_SAVE_STATE }, - {} - }; - - int c, r; +static int parse_argv(int argc, char *argv[], char ***ret_args) { + int r; assert(argc >= 0); assert(argv); + assert(ret_args); + + OptionParser opts = { argc, argv }; - while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'u': - r = free_and_strdup_warn(&arg_url, optarg); + OPTION('u', "url", "URL", + "Upload to this address (default port " STRINGIFY(DEFAULT_PORT) ")"): + r = free_and_strdup_warn(&arg_url, opts.arg); if (r < 0) return r; break; - case ARG_KEY: - r = free_and_strdup_warn(&arg_key, optarg); + OPTION_LONG("key", "FILENAME", + "Specify key in PEM format (default: \"" PRIV_KEY_FILE "\")"): + r = free_and_strdup_warn(&arg_key, opts.arg); if (r < 0) return r; break; - case ARG_CERT: - r = free_and_strdup_warn(&arg_cert, optarg); + OPTION_LONG("cert", "FILENAME", + "Specify certificate in PEM format (default: \"" CERT_FILE "\")"): + r = free_and_strdup_warn(&arg_cert, opts.arg); if (r < 0) return r; break; - case ARG_TRUST: - r = free_and_strdup_warn(&arg_trust, optarg); + OPTION_LONG("trust", "FILENAME|all", + "Specify CA certificate or disable checking (default: \"" TRUST_FILE "\")"): + r = free_and_strdup_warn(&arg_trust, opts.arg); if (r < 0) return r; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Use the system journal"): arg_journal_type |= SD_JOURNAL_SYSTEM; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Use the user journal for the current user"): arg_journal_type |= SD_JOURNAL_CURRENT_USER; break; - case 'm': + OPTION('m', "merge", NULL, "Use all available journals"): arg_merge = true; break; - case 'M': - r = free_and_strdup_warn(&arg_machine, optarg); + OPTION_COMMON_MACHINE: + r = free_and_strdup_warn(&arg_machine, opts.arg); if (r < 0) return r; break; - case ARG_NAMESPACE: - if (streq(optarg, "*")) { + OPTION_LONG("namespace", "NAMESPACE", "Use journal files from namespace"): + if (streq(opts.arg, "*")) { arg_namespace_flags = SD_JOURNAL_ALL_NAMESPACES; arg_namespace = mfree(arg_namespace); r = 0; - } else if (startswith(optarg, "+")) { + } else if (startswith(opts.arg, "+")) { arg_namespace_flags = SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE; - r = free_and_strdup_warn(&arg_namespace, optarg + 1); - } else if (isempty(optarg)) { + r = free_and_strdup_warn(&arg_namespace, opts.arg + 1); + } else if (isempty(opts.arg)) { arg_namespace_flags = 0; arg_namespace = mfree(arg_namespace); r = 0; } else { arg_namespace_flags = 0; - r = free_and_strdup_warn(&arg_namespace, optarg); + r = free_and_strdup_warn(&arg_namespace, opts.arg); } if (r < 0) return r; break; - case 'D': - r = free_and_strdup_warn(&arg_directory, optarg); + OPTION('D', "directory", "PATH", "Use journal files from this directory"): + r = free_and_strdup_warn(&arg_directory, opts.arg); if (r < 0) return r; break; - case ARG_FILE: - r = glob_extend(&arg_file, optarg, GLOB_NOCHECK); + OPTION_LONG("file", "PATH", "Use this journal file"): + r = glob_extend(&arg_file, opts.arg, GLOB_NOCHECK); if (r < 0) return log_error_errno(r, "Failed to add paths: %m"); break; - case ARG_CURSOR: - case ARG_AFTER_CURSOR: - r = free_and_strdup_warn(&arg_cursor, optarg); + OPTION_LONG_DATA("after-cursor", "CURSOR", /* data= */ true, + "Start after the specified cursor"): {} + OPTION_LONG_DATA("cursor", "CURSOR", /* data= */ false, + "Start at the specified cursor"): + r = free_and_strdup_warn(&arg_cursor, opts.arg); if (r < 0) return r; - arg_after_cursor = c == ARG_AFTER_CURSOR; + arg_after_cursor = opts.opt->data; break; - case ARG_FOLLOW: - r = parse_boolean_argument("--follow", optarg, NULL); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "follow", "BOOL", + "Whether to wait for input"): + r = parse_boolean_argument("--follow", opts.arg, NULL); if (r < 0) return r; arg_follow = r; break; - case ARG_SAVE_STATE: - r = free_and_strdup_warn(&arg_save_state, optarg ?: STATE_FILE); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "save-state", "FILE", + "Save uploaded cursors (default " STATE_FILE ")"): + r = free_and_strdup_warn(&arg_save_state, opts.arg ?: STATE_FILE); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_url) @@ -894,10 +833,12 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Options --key= and --cert= must be used together."); - if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) + char **args = option_parser_get_args(&opts); + if (!strv_isempty(args) && (arg_directory || arg_file || arg_machine || arg_journal_type)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Input arguments make no sense with journal input."); + *ret_args = args; return 1; } @@ -924,6 +865,7 @@ static int open_journal(sd_journal **j) { static int run(int argc, char **argv) { _cleanup_(destroy_uploader) Uploader u = {}; _unused_ _cleanup_(notify_on_cleanup) const char *notify_message = NULL; + char **args = NULL; bool use_journal; int r; @@ -933,10 +875,14 @@ static int run(int argc, char **argv) { if (r < 0) return r; - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; + r = DLOPEN_CURL(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + r = compression_configs_mangle(&arg_compression); if (r < 0) return r; @@ -956,7 +902,7 @@ static int run(int argc, char **argv) { log_debug("%s running as pid "PID_FMT, program_invocation_short_name, getpid_cached()); - use_journal = optind >= argc; + use_journal = strv_isempty(args); if (use_journal) { sd_journal *j; r = open_journal(&j); @@ -974,7 +920,7 @@ static int run(int argc, char **argv) { "STATUS=Processing input...", NOTIFY_STOPPING_MESSAGE); - for (;;) { + for (size_t i = 0;;) { r = sd_event_get_state(u.event); if (r < 0) return r; @@ -987,11 +933,11 @@ static int run(int argc, char **argv) { r = check_journal_input(&u); } else if (u.input < 0 && !use_journal) { - if (optind >= argc) + if (!args[i]) return 0; - log_debug("Using %s as input.", argv[optind]); - r = open_file_for_upload(&u, argv[optind++]); + log_debug("Using %s as input.", args[i]); + r = open_file_for_upload(&u, args[i++]); } if (r < 0) return r; diff --git a/src/journal-remote/log-generator.py b/src/journal-remote/log-generator.py index 2843afb4c93c7..1e2c0e5c3e96b 100755 --- a/src/journal-remote/log-generator.py +++ b/src/journal-remote/log-generator.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later -import sys import argparse +import sys PARSER = argparse.ArgumentParser() PARSER.add_argument('n', type=int) @@ -12,7 +12,7 @@ PARSER.add_argument('--data-type', choices={'random', 'simple'}) OPTIONS = PARSER.parse_args() -template = """\ +template = '''\ __CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m={m:x};t=4fd05c __REALTIME_TIMESTAMP={realtime_ts} __MONOTONIC_TIMESTAMP={monotonic_ts} @@ -30,7 +30,7 @@ _PID=25721 _SOURCE_REALTIME_TIMESTAMP={source_realtime_ts} DATA={data} -""" +''' priority = 3 facility = 6 @@ -51,14 +51,16 @@ data = '{:0{}}'.format(counter, OPTIONS.data_size) counter += 1 - entry = template.format(m=0x198603b12d7 + i, - realtime_ts=1404101101501873 + i, - monotonic_ts=1753961140951 + i, - source_realtime_ts=1404101101483516 + i, - priority=priority, - facility=facility, - message=message, - data=data) + entry = template.format( + m=0x198603B12D7 + i, + realtime_ts=1404101101501873 + i, + monotonic_ts=1753961140951 + i, + source_realtime_ts=1404101101483516 + i, + priority=priority, + facility=facility, + message=message, + data=data, + ) bytes += len(entry) @@ -69,4 +71,4 @@ if OPTIONS.dots: print(file=sys.stderr) -print('Wrote {} bytes'.format(bytes), file=sys.stderr) +print(f'Wrote {bytes} bytes', file=sys.stderr) diff --git a/src/journal-remote/meson.build b/src/journal-remote/meson.build index 51261d000d9e4..22ac8703b55d4 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,11 +21,10 @@ systemd_journal_upload_extract_sources = files( ) common_deps = [ - libgnutls, + libgnutls_cflags, liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ] executables += [ @@ -40,8 +36,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 +47,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 +59,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 +69,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..e380e08b1c20c 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,34 @@ 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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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(&opts) > 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..b2b1689ff26d5 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,80 @@ 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 opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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(opts.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(opts.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=", opts.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(opts.arg); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&opts); return 1; } @@ -135,7 +113,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 +136,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 +150,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-authenticate.c b/src/journal/journalctl-authenticate.c index c31c31ef6125c..73c1579b88fed 100644 --- a/src/journal/journalctl-authenticate.c +++ b/src/journal/journalctl-authenticate.c @@ -7,13 +7,15 @@ #include "alloc-util.h" #include "ansi-color.h" #include "chattr-util.h" +#include "crypto-util.h" #include "errno-util.h" #include "fd-util.h" #include "fs-util.h" -#include "fsprg.h" +#include "fsprg-openssl.h" #include "hostname-setup.h" #include "hostname-util.h" #include "io-util.h" +#include "iovec-util.h" #include "journal-def.h" #include "journalctl.h" #include "journalctl-authenticate.h" @@ -28,10 +30,9 @@ #include "time-util.h" #include "tmpfile-util.h" -#if HAVE_GCRYPT +#if HAVE_OPENSSL static int format_key( - const void *seed, - size_t seed_size, + const struct iovec *seed, uint64_t start, uint64_t interval, char **ret) { @@ -39,18 +40,17 @@ static int format_key( _cleanup_(memstream_done) MemStream m = {}; FILE *f; - assert(seed); - assert(seed_size > 0); + assert(iovec_is_set(seed)); assert(ret); f = memstream_init(&m); if (!f) return -ENOMEM; - for (size_t i = 0; i < seed_size; i++) { + for (size_t i = 0; i < seed->iov_len; i++) { if (i > 0 && i % 3 == 0) fputc('-', f); - fprintf(f, "%02x", ((uint8_t*) seed)[i]); + fprintf(f, "%02x", ((uint8_t*) seed->iov_base)[i]); } fprintf(f, "/%"PRIx64"-%"PRIx64, start, interval); @@ -60,18 +60,20 @@ static int format_key( #endif int action_setup_keys(void) { -#if HAVE_GCRYPT +#if HAVE_OPENSSL _cleanup_(unlink_and_freep) char *tmpfile = NULL; _cleanup_close_ int fd = -EBADF; _cleanup_free_ char *path = NULL; - size_t mpk_size, seed_size, state_size; - uint8_t *mpk, *seed, *state; sd_id128_t machine, boot; uint64_t n; int r; assert(arg_action == ACTION_SETUP_KEYS); + r = DLOPEN_LIBCRYPTO(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + if (r < 0) + return r; + r = is_dir("/var/log/journal/", /* follow= */ false); if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), @@ -100,30 +102,19 @@ int action_setup_keys(void) { return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Sealing key file %s exists already. Use --force to recreate.", path); - mpk_size = FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR); - mpk = alloca_safe(mpk_size); - - seed_size = FSPRG_RECOMMENDED_SEEDLEN; - seed = alloca_safe(seed_size); - - state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR); - state = alloca_safe(state_size); + _cleanup_(iovec_erase) struct iovec + seed = IOVEC_ALLOCA(FSPRG_RECOMMENDED_SEEDLEN), + state = IOVEC_ALLOCA(fsprg_state_size(FSPRG_RECOMMENDED_SECPAR)); if (!arg_quiet) log_info("Generating seed..."); - r = crypto_random_bytes(seed, seed_size); + r = crypto_random_bytes(seed.iov_base, seed.iov_len); if (r < 0) return log_error_errno(r, "Failed to acquire random seed: %m"); - if (!arg_quiet) - log_info("Generating key pair..."); - r = FSPRG_GenMK(NULL, mpk, seed, seed_size, FSPRG_RECOMMENDED_SECPAR); - if (r < 0) - return log_error_errno(r, "Failed to generate key pair: %m"); - if (!arg_quiet) log_info("Generating sealing key..."); - r = FSPRG_GenState0(state, mpk, seed, seed_size); + r = fsprg_generate_state(FSPRG_RECOMMENDED_SECPAR, /* epoch= */ 0, &seed, &state); if (r < 0) return log_error_errno(r, "Failed to generate sealing key: %m"); @@ -141,21 +132,21 @@ int action_setup_keys(void) { r, "Failed to set file attributes on a temporary file for '%s', ignoring: %m", path); struct FSSHeader h = { - .signature = { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' }, + .signature = FSS_HEADER_SIGNATURE, .machine_id = machine, .boot_id = boot, .header_size = htole64(sizeof(h)), .start_usec = htole64(n * arg_interval), .interval_usec = htole64(arg_interval), .fsprg_secpar = htole16(FSPRG_RECOMMENDED_SECPAR), - .fsprg_state_size = htole64(state_size), + .fsprg_state_size = htole64(state.iov_len), }; r = loop_write(fd, &h, sizeof(h)); if (r < 0) return log_error_errno(r, "Failed to write header: %m"); - r = loop_write(fd, state, state_size); + r = loop_write(fd, state.iov_base, state.iov_len); if (r < 0) return log_error_errno(r, "Failed to write state: %m"); @@ -165,8 +156,8 @@ int action_setup_keys(void) { tmpfile = mfree(tmpfile); - _cleanup_free_ char *key = NULL; - r = format_key(seed, seed_size, n, arg_interval, &key); + _cleanup_(erase_and_freep) char *key = NULL; + r = format_key(&seed, n, arg_interval, &key); if (r < 0) return r; @@ -235,7 +226,7 @@ int action_setup_keys(void) { fputs(ansi_normal(), stderr); #if HAVE_QRENCODE - _cleanup_free_ char *url = NULL; + _cleanup_(erase_and_freep) char *url = NULL; url = strjoin("fss://", key, "?machine=", SD_ID128_TO_STRING(machine), hn ? ";hostname=" : "", hn); if (!url) return log_oom(); 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-metrics.c b/src/journal/journalctl-metrics.c new file mode 100644 index 0000000000000..bcba2ef00e3fc --- /dev/null +++ b/src/journal/journalctl-metrics.c @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-journal.h" +#include "sd-json.h" +#include "sd-varlink.h" + +#include "journalctl.h" +#include "journalctl-filter.h" +#include "journalctl-metrics.h" +#include "log.h" +#include "logs-show.h" +#include "metrics.h" +#include "output-mode.h" + +/* Fallback cap so we never stream unbounded entries when --lines= is "all" or unset. */ +#define N_RECENT_HIGH_PRIORITY 10 + +/* Set a maximum scan factor to avoid unbound iterating through the journal */ +#define RECENT_HIGH_PRIORITY_SCAN_FACTOR 50 + +static int recent_high_priority_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + int r; + + assert(mf && mf->name); + assert(link); + + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_SYSTEM | SD_JOURNAL_ASSUME_IMMUTABLE); + if (r < 0) + return log_debug_errno(r, "Failed to open journal, ignoring: %m"); + + /* Get filters (--priority=, units, matches, ...) from the command-line. */ + r = add_filters(j, /* matches= */ NULL); + if (r < 0) + return log_debug_errno(r, "Failed to add journal filters: %m"); + + r = sd_journal_seek_tail(j); + if (r < 0) + return log_debug_errno(r, "Failed to seek to journal tail: %m"); + + /* The --lines=+N argument does not make much sense for a metrics provider so we ignore it here. */ + if (arg_lines_oldest) + log_warning("--lines=+N is not supported when serving the metrics interface, ignoring."); + + uint64_t max_lines = arg_lines_needs_seek_end() ? (uint64_t) arg_lines : N_RECENT_HIGH_PRIORITY; + uint64_t max_scan = max_lines * RECENT_HIGH_PRIORITY_SCAN_FACTOR; + + for (uint64_t found = 0, scanned = 0; found < max_lines && scanned < max_scan; scanned++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL; + + r = sd_journal_previous(j); + if (r < 0) + return log_debug_errno(r, "Failed to iterate to previous journal entry: %m"); + if (r == 0) + break; + + r = journal_entry_to_json(j, OUTPUT_SHOW_ALL | OUTPUT_SKIP_UNPRINTABLE, /* output_fields= */ NULL, &entry); + if (r < 0) { + log_debug_errno(r, "Failed to convert journal entry to JSON, skipping entry: %m"); + continue; + } + if (r == 0) + continue; + + const char *ident = sd_json_variant_string(sd_json_variant_by_key(entry, "SYSLOG_IDENTIFIER")); + + r = metric_build_send_object(mf, link, /* object= */ ident, entry, /* fields= */ NULL); + if (r < 0) + return log_debug_errno(r, "Failed to send journal metric: %m"); + + found++; + } + + return 0; +} + +static const MetricFamily journal_metric_family_table[] = { + { + .name = METRIC_IO_SYSTEMD_JOURNAL_PREFIX "HighPriorityMessage", + .description = "The most recent high-priority journal messages", + .type = METRIC_FAMILY_TYPE_OBJECT, + .generate = recent_high_priority_generate, + }, + {} +}; + +int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return metrics_method_describe(journal_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) { + return metrics_method_list(journal_metric_family_table, link, parameters, flags, userdata); +} diff --git a/src/journal/journalctl-metrics.h b/src/journal/journalctl-metrics.h new file mode 100644 index 0000000000000..8250b43855e42 --- /dev/null +++ b/src/journal/journalctl-metrics.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +#define METRIC_IO_SYSTEMD_JOURNAL_PREFIX "io.systemd.Journal." + +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/journal/journalctl-misc.c b/src/journal/journalctl-misc.c index 78d3cae91ac33..000c1c6a2b02f 100644 --- a/src/journal/journalctl-misc.c +++ b/src/journal/journalctl-misc.c @@ -4,10 +4,12 @@ #include "alloc-util.h" #include "dirent-util.h" +#include "errno-util.h" #include "fd-util.h" #include "format-table.h" #include "format-util.h" #include "hashmap.h" +#include "journal-authenticate.h" #include "journal-internal.h" #include "journal-verify.h" #include "journalctl.h" @@ -35,10 +37,12 @@ int action_print_header(void) { int action_verify(void) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; - int r; + int r, ret = 0; assert(arg_action == ACTION_VERIFY); + journal_auth_init(); + r = acquire_journal(&j); if (r < 0) return r; @@ -47,43 +51,40 @@ int action_verify(void) { JournalFile *f; ORDERED_HASHMAP_FOREACH(f, j->files) { - int k; usec_t first = 0, validated = 0, last = 0; -#if HAVE_GCRYPT - if (!arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) - log_notice("Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path); -#endif - - k = journal_file_verify(f, arg_verify_key, &first, &validated, &last, /* show_progress= */ !arg_quiet); - if (k == -EINVAL) + r = journal_file_verify(f, arg_verify_key, &first, &validated, &last, /* show_progress= */ !arg_quiet); + if (r == -EKEYREJECTED) /* If the key was invalid give up right-away. */ - return k; - if (k < 0) - r = log_warning_errno(k, "FAIL: %s (%m)", f->path); - else { - char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX]; - log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "PASS: %s", f->path); - - if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) { - if (validated > 0) { - log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, - "=> Validated from %s to %s, final %s entries not sealed.", - format_timestamp_maybe_utc(a, sizeof(a), first), - format_timestamp_maybe_utc(b, sizeof(b), validated), - FORMAT_TIMESPAN(last > validated ? last - validated : 0, 0)); - } else if (last > 0) - log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, - "=> No sealing yet, %s of entries not sealed.", - FORMAT_TIMESPAN(last - first, 0)); - else - log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, - "=> No sealing yet, no entries in file."); - } + return r; + if (r == -ENOKEY) + log_notice_errno(r, "Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path); + if (r < 0) { + RET_GATHER(ret, log_warning_errno(r, "FAIL: %s: %m", f->path)); + continue; + } + + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "PASS: %s", f->path); + + if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) { + if (validated > 0) { + char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX]; + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, + "=> Validated from %s to %s, final %s entries not sealed.", + format_timestamp_maybe_utc(a, sizeof(a), first), + format_timestamp_maybe_utc(b, sizeof(b), validated), + FORMAT_TIMESPAN(usec_sub_unsigned(last, validated), 0)); + } else if (last > 0) + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, + "=> No sealing yet, %s of entries not sealed.", + FORMAT_TIMESPAN(usec_sub_unsigned(last, first), 0)); + else + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, + "=> No sealing yet, no entries in file."); } } - return r; + return ret; } int action_disk_usage(void) { diff --git a/src/journal/journalctl-show.c b/src/journal/journalctl-show.c index e582845d15614..4d174f242b23d 100644 --- a/src/journal/journalctl-show.c +++ b/src/journal/journalctl-show.c @@ -258,7 +258,18 @@ static int show(Context *c) { return log_error_errno(r, "Failed to get MESSAGE field: %m"); } - assert_se(message = startswith(message, "MESSAGE=")); + message = startswith(message, "MESSAGE="); + if (!message) { + /* The data object doesn't carry the expected field prefix, e.g. because + * the journal file is corrupted. Skip over it instead of aborting. */ + log_notice("MESSAGE field does not start with \"MESSAGE=\", skipping."); + + if (!arg_reverse) + c->until_safe = false; + + c->need_seek = true; + continue; + } r = pattern_matches_and_log(arg_compiled_pattern, message, len - strlen("MESSAGE="), highlight); 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/journalctl-varlink.c b/src/journal/journalctl-varlink.c index c278bc3365724..a38c320a4f868 100644 --- a/src/journal/journalctl-varlink.c +++ b/src/journal/journalctl-varlink.c @@ -65,7 +65,7 @@ int action_relinquish_var(void) { _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *link = NULL; int r; - assert(arg_action == ACTION_RELINQUISH_VAR); + assert(IN_SET(arg_action, ACTION_RELINQUISH_VAR, ACTION_SMART_RELINQUISH_VAR)); if (arg_machine || arg_namespace) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index b25827961f22c..3ee4da9013b41 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-journal.h" @@ -9,12 +8,15 @@ #include "build.h" #include "dissect-image.h" #include "extract-word.h" +#include "format-table.h" #include "glob-util.h" +#include "help-util.h" #include "id128-print.h" #include "image-policy.h" #include "journalctl.h" #include "journalctl-authenticate.h" #include "journalctl-catalog.h" +#include "journalctl-metrics.h" #include "journalctl-misc.h" #include "journalctl-show.h" #include "journalctl-varlink.h" @@ -24,12 +26,12 @@ #include "main-func.h" #include "mount-util.h" #include "mountpoint-util.h" +#include "options.h" #include "output-mode.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "pcre2-util.h" -#include "pretty-print.h" #include "runtime-scope.h" #include "set.h" #include "static-destruct.h" @@ -39,6 +41,7 @@ #include "syslog-util.h" #include "time-util.h" #include "varlink-io.systemd.JournalAccess.h" +#include "varlink-io.systemd.Metrics.h" #include "varlink-util.h" #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE) @@ -78,7 +81,7 @@ bool arg_file_stdin = false; int arg_priorities = 0; Set *arg_facilities = NULL; char *arg_verify_key = NULL; -#if HAVE_GCRYPT +#if HAVE_OPENSSL usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC; bool arg_force = false; #endif @@ -213,6 +216,43 @@ static int parse_lines(const char *arg, bool graceful) { return 0; } +static int parse_priorities(const char *arg) { + assert(arg); + + const char *dots = strstr(arg, ".."); + if (dots) { + /* a range */ + _cleanup_free_ char *a = strndup(arg, dots - arg); + if (!a) + return log_oom(); + + int from = log_level_from_string(a), + to = log_level_from_string(dots + 2); + + if (from < 0 || to < 0) + return log_error_errno(from < 0 ? from : to, + "Failed to parse log level range %s", arg); + + arg_priorities = 0; + if (from < to) + for (int i = from; i <= to; i++) + arg_priorities |= 1 << i; + else + for (int i = to; i <= from; i++) + arg_priorities |= 1 << i; + } else { + int p = log_level_from_string(arg); + if (p < 0) + return log_error_errno(p, "Unknown log level %s", arg); + + arg_priorities = 0; + for (int i = 0; i <= p; i++) + arg_priorities |= 1 << i; + } + + return 0; +} + static int help_facilities(void) { if (!arg_quiet) puts("Available facilities:"); @@ -229,106 +269,42 @@ static int help_facilities(void) { } static int help(void) { - _cleanup_free_ char *link = NULL; + static const char *const groups[] = { + "Source Options", + "Filtering Options", + "Output Control Options", + "Pager Control Options", + "Forward Secure Sealing (FSS) Options", + "Commands", + }; + + Table *tables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(tables, table_unref_array_clear); int r; pager_open(arg_pager_flags); - r = terminal_urlify_man("journalctl", "1", &link); - if (r < 0) - return log_oom(); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + r = option_parser_get_help_table_full("journalctl", groups[i], &tables[i]); + if (r < 0) + return r; + } + + assert_cc(ELEMENTSOF(tables) == 6); + (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], + tables[3], tables[4], tables[5]); - printf("%1$s [OPTIONS...] [MATCHES...]\n\n" - "%5$sQuery the journal.%6$s\n\n" - "%3$sSource Options:%4$s\n" - " --system Show the system journal\n" - " --user Show the user journal for the current user\n" - " -M --machine=CONTAINER Operate on local container\n" - " -m --merge Show entries from all available journals\n" - " -D --directory=PATH Show journal files from directory\n" - " -i --file=PATH Show journal file\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" - " --namespace=NAMESPACE Show journal data from specified journal namespace\n" - "\n%3$sFiltering Options:%4$s\n" - " -S --since=DATE Show entries not older than the specified date\n" - " -U --until=DATE Show entries not newer than the specified date\n" - " -c --cursor=CURSOR Show entries starting at the specified cursor\n" - " --after-cursor=CURSOR Show entries after the specified cursor\n" - " --cursor-file=FILE Show entries after cursor in FILE and update FILE\n" - " -b --boot[=ID] Show current boot or the specified boot\n" - " -u --unit=UNIT Show logs from the specified unit\n" - " --user-unit=UNIT Show logs from the specified user unit\n" - " --invocation=ID Show logs from the matching invocation ID\n" - " -I Show logs from the latest invocation of unit\n" - " -t --identifier=STRING Show entries with the specified syslog identifier\n" - " -T --exclude-identifier=STRING\n" - " Hide entries with the specified syslog identifier\n" - " -p --priority=RANGE Show entries within the specified priority range\n" - " --facility=FACILITY... Show entries with the specified facilities\n" - " -g --grep=PATTERN Show entries with MESSAGE matching PATTERN\n" - " --case-sensitive[=BOOL] Force case sensitive or insensitive matching\n" - " -k --dmesg Show kernel message log from the current boot\n" - "\n%3$sOutput Control Options:%4$s\n" - " -o --output=STRING Change journal output mode (short, short-precise,\n" - " short-iso, short-iso-precise, short-full,\n" - " short-monotonic, short-unix, verbose, export,\n" - " json, json-pretty, json-sse, json-seq, cat,\n" - " with-unit)\n" - " --output-fields=LIST Select fields to print in verbose/export/json modes\n" - " -n --lines[=[+]INTEGER] Number of journal entries to show\n" - " -r --reverse Show the newest entries first\n" - " --show-cursor Print the cursor after all the entries\n" - " --utc Express time in Coordinated Universal Time (UTC)\n" - " -x --catalog Add message explanations where available\n" - " -W --no-hostname Suppress output of hostname field\n" - " --no-full Ellipsize fields\n" - " -a --all Show all fields, including long and unprintable\n" - " -f --follow Follow the journal\n" - " --no-tail Show all lines, even in follow mode\n" - " --truncate-newline Truncate entries by first newline character\n" - " -q --quiet Do not show info messages and privilege warning\n" - " --synchronize-on-exit=BOOL\n" - " Wait for Journal synchronization before exiting\n" - "\n%3$sPager Control Options:%4$s\n" - " --no-pager Do not pipe output into a pager\n" - " -e --pager-end Immediately jump to the end in the pager\n" - "\n%3$sForward Secure Sealing (FSS) Options:%4$s\n" - " --interval=TIME Time interval for changing the FSS sealing key\n" - " --verify-key=KEY Specify FSS verification key\n" - " --force Override of the FSS key pair with --setup-keys\n" - "\n%3$sCommands:%4$s\n" - " -h --help Show this help text\n" - " --version Show package version\n" - " -N --fields List all field names currently used\n" - " -F --field=FIELD List all values that a specified field takes\n" - " --list-boots Show terse information about recorded boots\n" - " --list-invocations Show invocation IDs of specified unit\n" - " --list-namespaces Show list of journal namespaces\n" - " --disk-usage Show total disk usage of all journal files\n" - " --vacuum-size=BYTES Reduce disk usage below specified size\n" - " --vacuum-files=INT Leave only the specified number of journal files\n" - " --vacuum-time=TIME Remove journal files older than specified time\n" - " --verify Verify journal file consistency\n" - " --sync Synchronize unwritten journal messages to disk\n" - " --relinquish-var Stop logging to disk, log to temporary file system\n" - " --smart-relinquish-var Similar, but NOP if log directory is on root mount\n" - " --flush Flush all journal data from /run into /var\n" - " --rotate Request immediate rotation of the journal files\n" - " --header Show journal header information\n" - " --list-catalog Show all message IDs in the catalog\n" - " --dump-catalog Show entries in the message catalog\n" - " --update-catalog Update the message catalog database\n" - " --setup-keys Generate a new FSS key pair\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS…] [MATCHES…]"); + help_abstract("Query the journal."); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + help_section(groups[i]); + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + + help_man_page_reference("journalctl", "1"); return 0; } @@ -340,13 +316,23 @@ static int vl_server(void) { 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_JournalAccess); + /* Serve both interfaces regardless of which socket activated us: both activating sockets share the + * same access controls (systemd-journal group), so anyone reaching either is already entitled to + * both. */ + r = sd_varlink_server_add_interface_many( + varlink_server, + &vl_interface_io_systemd_JournalAccess, + &vl_interface_io_systemd_Metrics); if (r < 0) - return log_error_errno(r, "Failed to add Varlink interface: %m"); + return log_error_errno(r, "Failed to add Varlink interfaces: %m"); - r = sd_varlink_server_bind_method(varlink_server, "io.systemd.JournalAccess.GetEntries", vl_method_get_entries); + r = sd_varlink_server_bind_method_many( + varlink_server, + "io.systemd.JournalAccess.GetEntries", vl_method_get_entries, + "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 method: %m"); + return log_error_errno(r, "Failed to bind Varlink methods: %m"); r = sd_varlink_server_loop_auto(varlink_server); if (r < 0) @@ -355,134 +341,12 @@ static int vl_server(void) { return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_FULL, - ARG_NO_TAIL, - ARG_NEW_ID128, - ARG_THIS_BOOT, - ARG_LIST_BOOTS, - ARG_LIST_INVOCATIONS, - ARG_USER, - ARG_SYSTEM, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_HEADER, - ARG_FACILITY, - ARG_SETUP_KEYS, - ARG_INTERVAL, - ARG_VERIFY, - ARG_VERIFY_KEY, - ARG_DISK_USAGE, - ARG_AFTER_CURSOR, - ARG_CURSOR_FILE, - ARG_SHOW_CURSOR, - ARG_USER_UNIT, - ARG_INVOCATION, - ARG_LIST_CATALOG, - ARG_DUMP_CATALOG, - ARG_UPDATE_CATALOG, - ARG_FORCE, - ARG_CASE_SENSITIVE, - ARG_UTC, - ARG_SYNC, - ARG_FLUSH, - ARG_RELINQUISH_VAR, - ARG_SMART_RELINQUISH_VAR, - ARG_ROTATE, - ARG_TRUNCATE_NEWLINE, - ARG_VACUUM_SIZE, - ARG_VACUUM_FILES, - ARG_VACUUM_TIME, - ARG_OUTPUT_FIELDS, - ARG_NAMESPACE, - ARG_LIST_NAMESPACES, - ARG_SYNCHRONIZE_ON_EXIT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "pager-end", no_argument, NULL, 'e' }, - { "follow", no_argument, NULL, 'f' }, - { "force", no_argument, NULL, ARG_FORCE }, - { "output", required_argument, NULL, 'o' }, - { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, 'l' }, - { "no-full", no_argument, NULL, ARG_NO_FULL }, - { "lines", optional_argument, NULL, 'n' }, - { "truncate-newline", no_argument, NULL, ARG_TRUNCATE_NEWLINE }, - { "no-tail", no_argument, NULL, ARG_NO_TAIL }, - { "new-id128", no_argument, NULL, ARG_NEW_ID128 }, /* deprecated */ - { "quiet", no_argument, NULL, 'q' }, - { "merge", no_argument, NULL, 'm' }, - { "this-boot", no_argument, NULL, ARG_THIS_BOOT }, /* deprecated */ - { "boot", optional_argument, NULL, 'b' }, - { "list-boots", no_argument, NULL, ARG_LIST_BOOTS }, - { "list-invocations", no_argument, NULL, ARG_LIST_INVOCATIONS }, - { "dmesg", no_argument, NULL, 'k' }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "directory", required_argument, NULL, 'D' }, - { "file", required_argument, NULL, 'i' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "header", no_argument, NULL, ARG_HEADER }, - { "identifier", required_argument, NULL, 't' }, - { "exclude-identifier", required_argument, NULL, 'T' }, - { "priority", required_argument, NULL, 'p' }, - { "facility", required_argument, NULL, ARG_FACILITY }, - { "grep", required_argument, NULL, 'g' }, - { "case-sensitive", optional_argument, NULL, ARG_CASE_SENSITIVE }, - { "setup-keys", no_argument, NULL, ARG_SETUP_KEYS }, - { "interval", required_argument, NULL, ARG_INTERVAL }, - { "verify", no_argument, NULL, ARG_VERIFY }, - { "verify-key", required_argument, NULL, ARG_VERIFY_KEY }, - { "disk-usage", no_argument, NULL, ARG_DISK_USAGE }, - { "cursor", required_argument, NULL, 'c' }, - { "cursor-file", required_argument, NULL, ARG_CURSOR_FILE }, - { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR }, - { "show-cursor", no_argument, NULL, ARG_SHOW_CURSOR }, - { "since", required_argument, NULL, 'S' }, - { "until", required_argument, NULL, 'U' }, - { "unit", required_argument, NULL, 'u' }, - { "user-unit", required_argument, NULL, ARG_USER_UNIT }, - { "invocation", required_argument, NULL, ARG_INVOCATION }, - { "field", required_argument, NULL, 'F' }, - { "fields", no_argument, NULL, 'N' }, - { "catalog", no_argument, NULL, 'x' }, - { "list-catalog", no_argument, NULL, ARG_LIST_CATALOG }, - { "dump-catalog", no_argument, NULL, ARG_DUMP_CATALOG }, - { "update-catalog", no_argument, NULL, ARG_UPDATE_CATALOG }, - { "reverse", no_argument, NULL, 'r' }, - { "machine", required_argument, NULL, 'M' }, - { "utc", no_argument, NULL, ARG_UTC }, - { "flush", no_argument, NULL, ARG_FLUSH }, - { "relinquish-var", no_argument, NULL, ARG_RELINQUISH_VAR }, - { "smart-relinquish-var", no_argument, NULL, ARG_SMART_RELINQUISH_VAR }, - { "sync", no_argument, NULL, ARG_SYNC }, - { "rotate", no_argument, NULL, ARG_ROTATE }, - { "vacuum-size", required_argument, NULL, ARG_VACUUM_SIZE }, - { "vacuum-files", required_argument, NULL, ARG_VACUUM_FILES }, - { "vacuum-time", required_argument, NULL, ARG_VACUUM_TIME }, - { "no-hostname", no_argument, NULL, 'W' }, - { "output-fields", required_argument, NULL, ARG_OUTPUT_FIELDS }, - { "namespace", required_argument, NULL, ARG_NAMESPACE }, - { "list-namespaces", no_argument, NULL, ARG_LIST_NAMESPACES }, - { "synchronize-on-exit", required_argument, NULL, ARG_SYNCHRONIZE_ON_EXIT }, - {} - }; - - int c, r; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); if (r < 0) @@ -490,387 +354,263 @@ static int parse_argv(int argc, char *argv[]) { if (r > 0) { arg_varlink = true; - static const struct option varlink_options[] = { - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - while ((c = getopt_long(argc, argv, "", varlink_options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "journalctl-varlink" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case ARG_SYSTEM: + OPTION_NAMESPACE("journalctl-varlink"): {} + + OPTION_COMMON_SYSTEM: arg_varlink_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_USER: + OPTION_COMMON_USER: arg_varlink_runtime_scope = RUNTIME_SCOPE_USER; break; - case '?': - return -EINVAL; + OPTION('p', "priority", "RANGE", "Show entries within the specified priority range"): + r = parse_priorities(opts.arg); + if (r < 0) + return r; + break; - default: - assert_not_reached(); - } + OPTION_FULL(OPTION_OPTIONAL_ARG, 'n', "lines", "[+]INTEGER", + "Number of journal entries to show"): { + const char *p = opts.arg ?: option_parser_peek_next_arg(&opts); + + r = parse_lines(p, /* graceful= */ !opts.arg); + if (r < 0) + return r; + if (r > 0 && !opts.arg) + (void) option_parser_consume_next_arg(&opts); + + break; + }} if (arg_varlink_runtime_scope < 0) return log_error_errno(arg_varlink_runtime_scope, "Cannot run in Varlink mode with no runtime scope specified."); + if (option_parser_get_n_args(&opts) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No positional arguments expected in Varlink mode."); + + /* We return early, skipping the tristate resolution the regular path does below; resolve it + * here so add_filters() doesn't trip over the unresolved -1. */ + arg_boot = false; + + *remaining_args = NULL; return 1; } - while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:T:u:INF:xrM:i:W", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "journalctl" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - return help(); - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; - - case 'e': - arg_pager_flags |= PAGER_JUMP_TO_END; - break; - - case 'f': - arg_follow = true; - break; - - case 'o': - 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); - - if (IN_SET(arg_output, OUTPUT_EXPORT, OUTPUT_JSON, OUTPUT_JSON_PRETTY, OUTPUT_JSON_SSE, OUTPUT_JSON_SEQ, OUTPUT_CAT)) - arg_quiet = true; - - if (OUTPUT_MODE_IS_JSON(arg_output)) - arg_json_format_flags = output_mode_to_json_format_flags(arg_output) | SD_JSON_FORMAT_COLOR_AUTO; - else - arg_json_format_flags = SD_JSON_FORMAT_OFF; - - break; + OPTION_NAMESPACE("journalctl"): {} - case 'l': - arg_full = true; - break; + OPTION_GROUP("Source Options"): {} - case ARG_NO_FULL: - arg_full = false; + OPTION_LONG("system", NULL, "Show the system journal"): + arg_journal_type |= SD_JOURNAL_SYSTEM; break; - case 'a': - arg_all = true; + OPTION_LONG("user", NULL, "Show the user journal for the current user"): + arg_journal_type |= SD_JOURNAL_CURRENT_USER; break; - case 'n': - r = parse_lines(optarg ?: argv[optind], !optarg); + OPTION_COMMON_MACHINE: + r = free_and_strdup_warn(&arg_machine, opts.arg); if (r < 0) return r; - if (r > 0 && !optarg) - optind++; - break; - case ARG_NO_TAIL: - arg_no_tail = true; - break; - - case ARG_TRUNCATE_NEWLINE: - arg_truncate_newline = true; - break; - - case ARG_NEW_ID128: - arg_action = ACTION_NEW_ID128; - break; - - case 'q': - arg_quiet = true; - break; - - case 'm': + OPTION('m', "merge", NULL, "Show entries from all available journals"): arg_merge = true; break; - case ARG_THIS_BOOT: - arg_boot = true; - arg_boot_id = SD_ID128_NULL; - arg_boot_offset = 0; + OPTION('D', "directory", "PATH", "Show journal files from directory"): + r = free_and_strdup_warn(&arg_directory, opts.arg); + if (r < 0) + return r; break; - case 'b': - arg_boot = true; - arg_boot_id = SD_ID128_NULL; - arg_boot_offset = 0; - - if (optarg) { - r = parse_id_descriptor(optarg, &arg_boot_id, &arg_boot_offset); + OPTION('i', "file", "PATH", "Show journal file"): + if (streq(opts.arg, "-")) + /* An undocumented feature: we can read journal files from STDIN. We don't document + * this though, since after all we only support this for mmap-able, seekable files, and + * not for example pipes which are probably the primary use case for reading things from + * STDIN. To avoid confusion we hence don't document this feature. */ + arg_file_stdin = true; + else { + r = glob_extend(&arg_file, opts.arg, GLOB_NOCHECK); if (r < 0) - return log_error_errno(r, "Failed to parse boot descriptor '%s'", optarg); - - arg_boot = r; - - } else if (optind < argc) { - /* Hmm, no argument? Maybe the next word on the command line is supposed to be the - * argument? Let's see if there is one and is parsable as a boot descriptor... */ - r = parse_id_descriptor(argv[optind], &arg_boot_id, &arg_boot_offset); - if (r >= 0) { - arg_boot = r; - optind++; - } + return log_error_errno(r, "Failed to add paths: %m"); } break; - case ARG_LIST_BOOTS: - arg_action = ACTION_LIST_BOOTS; - break; - - case ARG_LIST_INVOCATIONS: - arg_action = ACTION_LIST_INVOCATIONS; - break; - - case 'k': - arg_dmesg = true; - break; - - case ARG_SYSTEM: - arg_journal_type |= SD_JOURNAL_SYSTEM; + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(opts.arg, /* suppress_root= */ true, &arg_root); + if (r < 0) + return r; break; - case ARG_USER: - arg_journal_type |= SD_JOURNAL_CURRENT_USER; + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_image); + if (r < 0) + return r; break; - case 'M': - r = free_and_strdup_warn(&arg_machine, optarg); + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(opts.arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_NAMESPACE: - if (streq(optarg, "*")) { + OPTION_LONG("namespace", "NAMESPACE", + "Show journal data from specified journal namespace"): + if (streq(opts.arg, "*")) { arg_namespace_flags = SD_JOURNAL_ALL_NAMESPACES; arg_namespace = mfree(arg_namespace); - } else if (startswith(optarg, "+")) { + } else if (startswith(opts.arg, "+")) { arg_namespace_flags = SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE; - r = free_and_strdup_warn(&arg_namespace, optarg + 1); + r = free_and_strdup_warn(&arg_namespace, opts.arg + 1); if (r < 0) return r; - } else if (isempty(optarg)) { + } else if (isempty(opts.arg)) { arg_namespace_flags = 0; arg_namespace = mfree(arg_namespace); } else { arg_namespace_flags = 0; - r = free_and_strdup_warn(&arg_namespace, optarg); + r = free_and_strdup_warn(&arg_namespace, opts.arg); if (r < 0) return r; } break; - case ARG_LIST_NAMESPACES: - arg_action = ACTION_LIST_NAMESPACES; - break; + OPTION_GROUP("Filtering Options"): {} - case 'D': - r = free_and_strdup_warn(&arg_directory, optarg); + OPTION('S', "since", "DATE", "Show entries not older than the specified date"): + r = parse_timestamp(opts.arg, &arg_since); if (r < 0) - return r; - break; - - case 'i': - if (streq(optarg, "-")) - /* An undocumented feature: we can read journal files from STDIN. We don't document - * this though, since after all we only support this for mmap-able, seekable files, and - * not for example pipes which are probably the primary use case for reading things from - * STDIN. To avoid confusion we hence don't document this feature. */ - arg_file_stdin = true; - else { - r = glob_extend(&arg_file, optarg, GLOB_NOCHECK); - if (r < 0) - return log_error_errno(r, "Failed to add paths: %m"); - } - break; - - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); - if (r < 0) - return r; + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse timestamp: %s", opts.arg); + arg_since_set = true; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION('U', "until", "DATE", "Show entries not newer than the specified date"): + r = parse_timestamp(opts.arg, &arg_until); if (r < 0) - return r; + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse timestamp: %s", opts.arg); + arg_until_set = true; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION('c', "cursor", "CURSOR", "Show entries starting at the specified cursor"): + r = free_and_strdup_warn(&arg_cursor, opts.arg); if (r < 0) return r; break; - case 'c': - r = free_and_strdup_warn(&arg_cursor, optarg); + OPTION_LONG("after-cursor", "CURSOR", "Show entries after the specified cursor"): + r = free_and_strdup_warn(&arg_after_cursor, opts.arg); if (r < 0) return r; break; - case ARG_CURSOR_FILE: - r = free_and_strdup_warn(&arg_cursor_file, optarg); + OPTION_LONG("cursor-file", "FILE", "Show entries after cursor in FILE and update FILE"): + r = free_and_strdup_warn(&arg_cursor_file, opts.arg); if (r < 0) return r; break; - case ARG_AFTER_CURSOR: - r = free_and_strdup_warn(&arg_after_cursor, optarg); - if (r < 0) - return r; + OPTION_LONG("this-boot", NULL, /* help= */ NULL): + arg_boot = true; + arg_boot_id = SD_ID128_NULL; + arg_boot_offset = 0; break; - case ARG_SHOW_CURSOR: - arg_show_cursor = true; - break; + OPTION_FULL(OPTION_OPTIONAL_ARG, 'b', "boot", "ID", + "Show current boot or the specified boot"): + arg_boot = true; + arg_boot_id = SD_ID128_NULL; + arg_boot_offset = 0; - case ARG_HEADER: - arg_action = ACTION_PRINT_HEADER; - break; + if (opts.arg) { + r = parse_id_descriptor(opts.arg, &arg_boot_id, &arg_boot_offset); + if (r < 0) + return log_error_errno(r, "Failed to parse boot descriptor '%s'", opts.arg); - case ARG_VERIFY: - arg_action = ACTION_VERIFY; - break; + arg_boot = r; - case ARG_DISK_USAGE: - arg_action = ACTION_DISK_USAGE; + } else { + /* Hmm, no argument? Maybe the next word on the command line is supposed to + * be the argument? Let's see if there is one and is parsable as a boot + * descriptor… */ + char *peek = option_parser_peek_next_arg(&opts); + if (peek) { + r = parse_id_descriptor(peek, &arg_boot_id, &arg_boot_offset); + if (r >= 0) { + arg_boot = r; + (void) option_parser_consume_next_arg(&opts); + } + } + } break; - case ARG_VACUUM_SIZE: - r = parse_size(optarg, 1024, &arg_vacuum_size); + OPTION('u', "unit", "UNIT", "Show logs from the specified unit"): + r = strv_extend(&arg_system_units, opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse vacuum size: %s", optarg); - - arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; + return log_oom(); break; - case ARG_VACUUM_FILES: - r = safe_atou64(optarg, &arg_vacuum_n_files); + OPTION_LONG("user-unit", "UNIT", "Show logs from the specified user unit"): + r = strv_extend(&arg_user_units, opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse vacuum files: %s", optarg); - - arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; + return log_oom(); break; - case ARG_VACUUM_TIME: - r = parse_sec(optarg, &arg_vacuum_time); + OPTION_LONG("invocation", "ID", "Show logs from the matching invocation ID"): + r = parse_id_descriptor(opts.arg, &arg_invocation_id, &arg_invocation_offset); if (r < 0) - return log_error_errno(r, "Failed to parse vacuum time: %s", optarg); - - arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; - break; - -#if HAVE_GCRYPT - case ARG_FORCE: - arg_force = true; + return log_error_errno(r, "Failed to parse invocation descriptor: %s", opts.arg); + arg_invocation = r; break; - case ARG_SETUP_KEYS: - arg_action = ACTION_SETUP_KEYS; + OPTION_SHORT('I', NULL, "Show logs from the latest invocation of unit"): + /* Equivalent to --invocation=0 */ + arg_invocation = true; + arg_invocation_id = SD_ID128_NULL; + arg_invocation_offset = 0; break; - case ARG_VERIFY_KEY: - erase_and_free(arg_verify_key); - arg_verify_key = strdup(optarg); - if (!arg_verify_key) + OPTION('t', "identifier", "ID", "Show entries with the specified syslog identifier"): + r = strv_extend(&arg_syslog_identifier, opts.arg); + if (r < 0) return log_oom(); - - /* Use memset not explicit_bzero() or similar so this doesn't look confusing - * in ps or htop output. */ - memset(optarg, 'x', strlen(optarg)); - - arg_action = ACTION_VERIFY; - arg_merge = false; break; - case ARG_INTERVAL: - r = parse_sec(optarg, &arg_interval); - if (r < 0 || arg_interval <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse sealing key change interval: %s", optarg); + OPTION('T', "exclude-identifier", "ID", + "Hide entries with the specified syslog identifier"): + r = strv_extend(&arg_exclude_identifier, opts.arg); + if (r < 0) + return log_oom(); break; -#else - case ARG_SETUP_KEYS: - case ARG_VERIFY_KEY: - case ARG_INTERVAL: - case ARG_FORCE: - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Compiled without forward-secure sealing support."); -#endif - - case 'p': { - const char *dots; - - dots = strstr(optarg, ".."); - if (dots) { - _cleanup_free_ char *a = NULL; - int from, to, i; - - /* a range */ - a = strndup(optarg, dots - optarg); - if (!a) - return log_oom(); - - from = log_level_from_string(a); - to = log_level_from_string(dots + 2); - - if (from < 0 || to < 0) - return log_error_errno(from < 0 ? from : to, - "Failed to parse log level range %s", optarg); - - arg_priorities = 0; - - if (from < to) { - for (i = from; i <= to; i++) - arg_priorities |= 1 << i; - } else { - for (i = to; i <= from; i++) - arg_priorities |= 1 << i; - } - - } else { - int p, i; - - p = log_level_from_string(optarg); - if (p < 0) - return log_error_errno(p, "Unknown log level %s", optarg); - - arg_priorities = 0; - - for (i = 0; i <= p; i++) - arg_priorities |= 1 << i; - } + OPTION('p', "priority", "RANGE", "Show entries within the specified priority range"): + r = parse_priorities(opts.arg); + if (r < 0) + return r; break; - } - - case ARG_FACILITY: { - const char *p; - for (p = optarg;;) { + OPTION_LONG("facility", "FACILITY…", "Show entries with the specified facilities"): + for (const char *p = opts.arg;;) { _cleanup_free_ char *fac = NULL; int num; r = extract_first_word(&p, &fac, ",", 0); if (r < 0) - return log_error_errno(r, "Failed to parse facilities: %s", optarg); + return log_error_errno(r, "Failed to parse facilities: %s", opts.arg); if (r == 0) break; @@ -888,187 +628,303 @@ static int parse_argv(int argc, char *argv[]) { } break; - } - case 'g': - r = free_and_strdup_warn(&arg_pattern, optarg); + OPTION('g', "grep", "PATTERN", "Show entries with MESSAGE matching PATTERN"): + r = free_and_strdup_warn(&arg_pattern, opts.arg); if (r < 0) return r; break; - case ARG_CASE_SENSITIVE: - if (optarg) { - r = parse_boolean(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "case-sensitive", "BOOL", + "Force case sensitive or insensitive matching"): + if (opts.arg) { + r = parse_boolean(opts.arg); if (r < 0) - return log_error_errno(r, "Bad --case-sensitive= argument \"%s\": %m", optarg); + return log_error_errno(r, "Bad --case-sensitive= argument \"%s\": %m", opts.arg); arg_case = r ? PATTERN_COMPILE_CASE_SENSITIVE : PATTERN_COMPILE_CASE_INSENSITIVE; } else arg_case = PATTERN_COMPILE_CASE_SENSITIVE; break; - case 'S': - r = parse_timestamp(optarg, &arg_since); - if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse timestamp: %s", optarg); - arg_since_set = true; + OPTION('k', "dmesg", NULL, "Show kernel message log from the current boot"): + arg_dmesg = true; break; - case 'U': - r = parse_timestamp(optarg, &arg_until); - if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse timestamp: %s", optarg); - arg_until_set = true; + OPTION_GROUP("Output Control Options"): {} + + OPTION('o', "output", "STRING", + "Change journal output mode (short, short-precise, short-iso, short-iso-precise, " + "short-full, short-monotonic, short-unix, verbose, export, json, json-pretty, " + "json-sse, json-seq, cat, with-unit)"): + if (streq(opts.arg, "help")) + return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); + + arg_output = output_mode_from_string(opts.arg); + if (arg_output < 0) + return log_error_errno(arg_output, "Unknown output format '%s'.", opts.arg); + + if (IN_SET(arg_output, OUTPUT_EXPORT, OUTPUT_JSON, OUTPUT_JSON_PRETTY, OUTPUT_JSON_SSE, OUTPUT_JSON_SEQ, OUTPUT_CAT)) + arg_quiet = true; + + if (OUTPUT_MODE_IS_JSON(arg_output)) + arg_json_format_flags = output_mode_to_json_format_flags(arg_output) | SD_JSON_FORMAT_COLOR_AUTO; + else + arg_json_format_flags = SD_JSON_FORMAT_OFF; + break; - case 't': - r = strv_extend(&arg_syslog_identifier, optarg); - if (r < 0) + OPTION_LONG("output-fields", "LIST", "Select fields to print in verbose/export/json modes"): { + _cleanup_strv_free_ char **v = NULL; + + v = strv_split(opts.arg, ","); + if (!v) return log_oom(); - break; - case 'T': - r = strv_extend(&arg_exclude_identifier, optarg); + r = set_put_strdupv(&arg_output_fields, v); if (r < 0) return log_oom(); + break; + } - case 'u': - r = strv_extend(&arg_system_units, optarg); + OPTION_FULL(OPTION_OPTIONAL_ARG, 'n', "lines", "[+]INTEGER", + "Number of journal entries to show"): { + const char *p = opts.arg ?: option_parser_peek_next_arg(&opts); + + r = parse_lines(p, /* graceful= */ !opts.arg); if (r < 0) - return log_oom(); + return r; + if (r > 0 && !opts.arg) + (void) option_parser_consume_next_arg(&opts); + break; + } - case ARG_USER_UNIT: - r = strv_extend(&arg_user_units, optarg); - if (r < 0) - return log_oom(); + OPTION('r', "reverse", NULL, "Show the newest entries first"): + arg_reverse = true; break; - case ARG_INVOCATION: - r = parse_id_descriptor(optarg, &arg_invocation_id, &arg_invocation_offset); - if (r < 0) - return log_error_errno(r, "Failed to parse invocation descriptor: %s", optarg); - arg_invocation = r; + OPTION_LONG("show-cursor", NULL, "Print the cursor after all the entries"): + arg_show_cursor = true; break; - case 'I': - /* Equivalent to --invocation=0 */ - arg_invocation = true; - arg_invocation_id = SD_ID128_NULL; - arg_invocation_offset = 0; + OPTION_LONG("utc", NULL, "Express time in Coordinated Universal Time (UTC)"): + arg_utc = true; break; - case 'F': - arg_action = ACTION_LIST_FIELDS; - r = free_and_strdup_warn(&arg_field, optarg); + OPTION('x', "catalog", NULL, "Add message explanations where available"): + arg_catalog = true; + break; + + OPTION('W', "no-hostname", NULL, "Suppress output of hostname field"): + arg_no_hostname = true; + break; + + OPTION('l', "full", NULL, /* help= */ NULL): + arg_full = true; + break; + + OPTION_LONG("no-full", NULL, "Ellipsize fields"): + arg_full = false; + break; + + OPTION('a', "all", NULL, "Show all fields, including long and unprintable"): + arg_all = true; + break; + + OPTION('f', "follow", NULL, "Follow the journal"): + arg_follow = true; + break; + + OPTION_LONG("no-tail", NULL, "Show all lines, even in follow mode"): + arg_no_tail = true; + break; + + OPTION_LONG("truncate-newline", NULL, "Truncate entries by first newline character"): + arg_truncate_newline = true; + break; + + OPTION('q', "quiet", NULL, "Do not show info messages and privilege warning"): + arg_quiet = true; + break; + + OPTION_LONG("synchronize-on-exit", "BOOL", + "Wait for Journal synchronization before exiting"): + r = parse_boolean_argument("--synchronize-on-exit", opts.arg, &arg_synchronize_on_exit); if (r < 0) return r; break; - case 'N': - arg_action = ACTION_LIST_FIELD_NAMES; + OPTION_GROUP("Pager Control Options"): {} + + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case 'W': - arg_no_hostname = true; + OPTION('e', "pager-end", NULL, "Immediately jump to the end in the pager"): + arg_pager_flags |= PAGER_JUMP_TO_END; break; - case 'x': - arg_catalog = true; + OPTION_GROUP("Forward Secure Sealing (FSS) Options"): {} + + OPTION_LONG("interval", "TIME", "Time interval for changing the FSS sealing key"): +#if HAVE_OPENSSL + r = parse_sec(opts.arg, &arg_interval); + if (r < 0 || arg_interval <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse sealing key change interval: %s", opts.arg); break; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Compiled without forward-secure sealing support."); +#endif - case ARG_LIST_CATALOG: - arg_action = ACTION_LIST_CATALOG; + OPTION_LONG("verify-key", "KEY", "Specify FSS verification key"): +#if HAVE_OPENSSL + erase_and_free(arg_verify_key); + arg_verify_key = strdup(opts.arg); + if (!arg_verify_key) + return log_oom(); + + /* Use memset not explicit_bzero() or similar so this doesn't look confusing + * in ps or htop output. We need to cast away the const to do this. */ + memset((char*) opts.arg, 'x', strlen(opts.arg)); + + arg_action = ACTION_VERIFY; + arg_merge = false; break; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Compiled without forward-secure sealing support."); +#endif - case ARG_DUMP_CATALOG: - arg_action = ACTION_DUMP_CATALOG; + OPTION_LONG("force", NULL, "Override of the FSS key pair with --setup-keys"): +#if HAVE_OPENSSL + arg_force = true; break; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Compiled without forward-secure sealing support."); +#endif - case ARG_UPDATE_CATALOG: - arg_action = ACTION_UPDATE_CATALOG; + OPTION_GROUP("Commands"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('N', "fields", NULL, "List all field names currently used"): + arg_action = ACTION_LIST_FIELD_NAMES; break; - case 'r': - arg_reverse = true; + OPTION('F', "field", "FIELD", "List all values that a specified field takes"): + arg_action = ACTION_LIST_FIELDS; + r = free_and_strdup_warn(&arg_field, opts.arg); + if (r < 0) + return r; break; - case ARG_UTC: - arg_utc = true; + OPTION_LONG("list-boots", NULL, "Show terse information about recorded boots"): + arg_action = ACTION_LIST_BOOTS; break; - case ARG_FLUSH: - arg_action = ACTION_FLUSH; + OPTION_LONG("list-invocations", NULL, "Show invocation IDs of specified unit"): + arg_action = ACTION_LIST_INVOCATIONS; break; - case ARG_SMART_RELINQUISH_VAR: { - int root_mnt_id, log_mnt_id; + OPTION_LONG("list-namespaces", NULL, "Show list of journal namespaces"): + arg_action = ACTION_LIST_NAMESPACES; + break; - /* Try to be smart about relinquishing access to /var/log/journal/ during shutdown: - * if it's on the same mount as the root file system there's no point in - * relinquishing access and we can leave journald write to it until the very last - * moment. */ + OPTION_LONG("disk-usage", NULL, "Show total disk usage of all journal files"): + arg_action = ACTION_DISK_USAGE; + break; - r = path_get_mnt_id("/", &root_mnt_id); + OPTION_LONG("vacuum-size", "BYTES", "Reduce disk usage below specified size"): + r = parse_size(opts.arg, 1024, &arg_vacuum_size); if (r < 0) - log_debug_errno(r, "Failed to get root mount ID, ignoring: %m"); - else { - r = path_get_mnt_id("/var/log/journal/", &log_mnt_id); - if (r < 0) - log_debug_errno(r, "Failed to get journal directory mount ID, ignoring: %m"); - else if (root_mnt_id == log_mnt_id) { - log_debug("/var/log/journal/ is on root file system, not relinquishing access to /var."); - return 0; - } else - log_debug("/var/log/journal/ is not on the root file system, relinquishing access to it."); - } + return log_error_errno(r, "Failed to parse vacuum size: %s", opts.arg); - _fallthrough_; - } + arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; + break; - case ARG_RELINQUISH_VAR: - arg_action = ACTION_RELINQUISH_VAR; + OPTION_LONG("vacuum-files", "INT", "Leave only the specified number of journal files"): + r = safe_atou64(opts.arg, &arg_vacuum_n_files); + if (r < 0) + return log_error_errno(r, "Failed to parse vacuum files: %s", opts.arg); + + arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; - case ARG_ROTATE: - arg_action = arg_action == ACTION_VACUUM ? ACTION_ROTATE_AND_VACUUM : ACTION_ROTATE; + OPTION_LONG("vacuum-time", "TIME", "Remove journal files older than specified time"): + r = parse_sec(opts.arg, &arg_vacuum_time); + if (r < 0) + return log_error_errno(r, "Failed to parse vacuum time: %s", opts.arg); + + arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; - case ARG_SYNC: + OPTION_LONG("verify", NULL, "Verify journal file consistency"): + arg_action = ACTION_VERIFY; + break; + + OPTION_LONG("sync", NULL, "Synchronize unwritten journal messages to disk"): arg_action = ACTION_SYNC; break; - case ARG_OUTPUT_FIELDS: { - _cleanup_strv_free_ char **v = NULL; + OPTION_LONG("relinquish-var", NULL, "Stop logging to disk, log to temporary file system"): + arg_action = ACTION_RELINQUISH_VAR; + break; - v = strv_split(optarg, ","); - if (!v) - return log_oom(); + OPTION_LONG("smart-relinquish-var", NULL, + "Similar, but NOP if log directory is on root mount"): + arg_action = ACTION_SMART_RELINQUISH_VAR; + break; - r = set_put_strdupv(&arg_output_fields, v); - if (r < 0) - return log_oom(); + OPTION_LONG("flush", NULL, "Flush all journal data from /run into /var"): + arg_action = ACTION_FLUSH; + break; + OPTION_LONG("rotate", NULL, "Request immediate rotation of the journal files"): + arg_action = arg_action == ACTION_VACUUM ? ACTION_ROTATE_AND_VACUUM : ACTION_ROTATE; break; - } - case ARG_SYNCHRONIZE_ON_EXIT: - r = parse_boolean_argument("--synchronize-on-exit", optarg, &arg_synchronize_on_exit); - if (r < 0) - return r; + OPTION_LONG("header", NULL, "Show journal header information"): + arg_action = ACTION_PRINT_HEADER; + break; + OPTION_LONG("list-catalog", NULL, "Show all message IDs in the catalog"): + arg_action = ACTION_LIST_CATALOG; break; - case '?': - return -EINVAL; + OPTION_LONG("dump-catalog", NULL, "Show entries in the message catalog"): + arg_action = ACTION_DUMP_CATALOG; + break; + + OPTION_LONG("update-catalog", NULL, "Update the message catalog database"): + arg_action = ACTION_UPDATE_CATALOG; + break; - default: - assert_not_reached(); + OPTION_LONG("setup-keys", NULL, "Generate a new FSS key pair"): +#if HAVE_OPENSSL + arg_action = ACTION_SETUP_KEYS; + break; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Compiled without forward-secure sealing support."); +#endif + + OPTION_LONG("new-id128", NULL, /* help= */ NULL): + arg_action = ACTION_NEW_ID128; + break; } + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + if (arg_no_tail) arg_lines = ARG_LINES_ALL; @@ -1109,10 +965,10 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--lines=+N is unsupported when --reverse or --follow is specified."); - if (!IN_SET(arg_action, ACTION_SHOW, ACTION_DUMP_CATALOG, ACTION_LIST_CATALOG) && optind < argc) + if (!IN_SET(arg_action, ACTION_SHOW, ACTION_DUMP_CATALOG, ACTION_LIST_CATALOG) && n_args > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extraneous arguments starting with '%s'", - argv[optind]); + args[0]); if ((arg_boot || arg_action == ACTION_LIST_BOOTS) && arg_merge) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -1146,6 +1002,11 @@ static int parse_argv(int argc, char *argv[]) { if (!arg_follow) arg_journal_additional_open_flags = SD_JOURNAL_ASSUME_IMMUTABLE; + args = strv_copy(args); + if (!args) + return log_oom(); + + *remaining_args = args; return 1; } @@ -1158,17 +1019,13 @@ static int run(int argc, char *argv[]) { setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; if (arg_varlink) return vl_server(); - r = strv_copy_unless_empty(strv_skip(argv, optind), &args); - if (r < 0) - return log_oom(); - if (arg_image) { assert(!arg_root); @@ -1237,6 +1094,31 @@ static int run(int argc, char *argv[]) { case ACTION_FLUSH: return action_flush_to_var(); + case ACTION_SMART_RELINQUISH_VAR: { + int root_mnt_id, log_mnt_id; + + /* Try to be smart about relinquishing access to /var/log/journal/ during shutdown: + * if it's on the same mount as the root file system there's no point in + * relinquishing access and we can leave journald write to it until the very last + * moment. */ + + r = path_get_mnt_id("/", &root_mnt_id); + if (r < 0) + log_debug_errno(r, "Failed to get root mount ID, ignoring: %m"); + else { + r = path_get_mnt_id("/var/log/journal/", &log_mnt_id); + if (r < 0) + log_debug_errno(r, "Failed to get journal directory mount ID, ignoring: %m"); + else if (root_mnt_id == log_mnt_id) { + log_debug("/var/log/journal/ is on root file system, not relinquishing access to /var."); + return 0; + } else + log_debug("/var/log/journal/ is not on the root file system, relinquishing access to it."); + } + + _fallthrough_; + } + case ACTION_RELINQUISH_VAR: return action_relinquish_var(); diff --git a/src/journal/journalctl.h b/src/journal/journalctl.h index 090e3382496ca..504e5e6dd1115 100644 --- a/src/journal/journalctl.h +++ b/src/journal/journalctl.h @@ -21,6 +21,7 @@ typedef enum JournalctlAction { ACTION_LIST_NAMESPACES, ACTION_FLUSH, ACTION_RELINQUISH_VAR, + ACTION_SMART_RELINQUISH_VAR, ACTION_SYNC, ACTION_ROTATE, ACTION_VACUUM, @@ -56,7 +57,7 @@ extern bool arg_file_stdin; extern int arg_priorities; extern Set *arg_facilities; extern char *arg_verify_key; -#if HAVE_GCRYPT +#if HAVE_OPENSSL extern usec_t arg_interval; extern bool arg_force; #endif 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-console.c b/src/journal/journald-console.c index 0f376f9e5a45c..0cd2215bd21ad 100644 --- a/src/journal/journald-console.c +++ b/src/journal/journald-console.c @@ -12,7 +12,6 @@ #include "journald-console.h" #include "journald-manager.h" #include "log.h" -#include "parse-util.h" #include "process-util.h" #include "stdio-util.h" #include "terminal-util.h" @@ -22,13 +21,8 @@ static bool prefix_timestamp(void) { static int cached_printk_time = -1; - if (_unlikely_(cached_printk_time < 0)) { - _cleanup_free_ char *p = NULL; - - cached_printk_time = - read_one_line_file("/sys/module/printk/parameters/time", &p) >= 0 - && parse_boolean(p) > 0; - } + if (_unlikely_(cached_printk_time < 0)) + cached_printk_time = read_boolean_file("/sys/module/printk/parameters/time") > 0; return cached_printk_time; } diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c index 1eb142887d749..3040a132d6e13 100644 --- a/src/journal/journald-context.c +++ b/src/journal/journald-context.c @@ -443,10 +443,13 @@ static int client_context_read_extra_fields( if (v < 2) return -EBADMSG; - n = sizeof(uint64_t) + v; - if (left < n) + /* left >= sizeof(uint64_t) here, so the subtraction is safe and we avoid + * overflowing sizeof(uint64_t) + v when v is close to UINT64_MAX. */ + if (v > left - sizeof(uint64_t)) return -EBADMSG; + n = sizeof(uint64_t) + v; + field = q + sizeof(uint64_t); eq = memchr(field, '=', v); diff --git a/src/journal/journald-manager.c b/src/journal/journald-manager.c index 8d95280fc61c7..68f95ea96782e 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) @@ -693,7 +697,10 @@ static int manager_archive_offline_user_journals(Manager *m) { TAKE_FD(fd); /* Donated to journal_file_open() */ - journal_file_write_final_tag(f); + r = journal_file_auth_append_tag(f); + if (r < 0) + log_debug_errno(r, "Failed to append tag when closing journal, ignoring: %m"); + r = journal_file_archive(f, NULL); if (r < 0) log_debug_errno(r, "Failed to archive journal file '%s', ignoring: %m", full); @@ -2535,18 +2542,16 @@ int manager_init(Manager *m) { } void manager_maybe_append_tags(Manager *m) { -#if HAVE_GCRYPT - JournalFile *f; - usec_t n; + assert(m); - n = now(CLOCK_REALTIME); + usec_t n = now(CLOCK_REALTIME); if (m->system_journal) - journal_file_maybe_append_tag(m->system_journal, n); + journal_file_auth_append_tag_maybe(m->system_journal, n); + JournalFile *f; ORDERED_HASHMAP_FOREACH(f, m->user_journals) - journal_file_maybe_append_tag(f, n); -#endif + journal_file_auth_append_tag_maybe(f, n); } Manager* manager_free(Manager *m) { 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/journald.c b/src/journal/journald.c index a06752d32e578..91f02178f5012 100644 --- a/src/journal/journald.c +++ b/src/journal/journald.c @@ -52,6 +52,8 @@ static int run(int argc, char *argv[]) { sigbus_install(); + journal_auth_init(); + r = manager_new(&m); if (r < 0) return log_oom(); @@ -110,14 +112,12 @@ static int run(int argc, char *argv[]) { } else t = USEC_INFINITY; -#if HAVE_GCRYPT if (m->system_journal) { usec_t u; - if (journal_file_next_evolve_usec(m->system_journal, &u)) + if (journal_file_auth_next_evolve_usec(m->system_journal, &u) >= 0) t = MIN(t, usec_sub_unsigned(u, n)); } -#endif r = sd_event_run(m->event, t); if (r < 0) diff --git a/src/journal/meson.build b/src/journal/meson.build index 5f64304219447..e39381f03f633 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( @@ -37,6 +34,7 @@ journalctl_sources = files( 'journalctl-authenticate.c', 'journalctl-catalog.c', 'journalctl-filter.c', + 'journalctl-metrics.c', 'journalctl-misc.c', 'journalctl-show.c', 'journalctl-util.c', @@ -58,7 +56,10 @@ journal_test_template = test_template + { } journal_fuzz_template = fuzz_template + { - 'objects' : ['systemd-journald'], + 'objects' : [ + 'fuzz-journald-audit', + 'systemd-journald', + ], 'dependencies' : libselinux_cflags, } @@ -72,7 +73,6 @@ executables += [ libselinux_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, libexec_template + { @@ -80,14 +80,12 @@ executables += [ 'public' : true, 'conditions' : ['HAVE_QRENCODE'], 'sources' : files('bsod.c'), - 'dependencies' : libqrencode, }, executable_template + { 'name' : 'systemd-cat', 'public' : true, 'sources' : files('cat.c'), 'objects' : ['systemd-journald'], - 'dependencies' : [threads], }, executable_template + { 'name' : 'journalctl', @@ -95,11 +93,10 @@ executables += [ 'sources' : journalctl_sources, 'link_with' : journalctl_link_with, 'dependencies' : [ - libdl, liblz4_cflags, + libopenssl_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, journal_test_template + { @@ -124,7 +121,6 @@ executables += [ liblz4_cflags, libselinux_cflags, libxz_cflags, - threads, ], }, journal_test_template + { @@ -133,8 +129,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..132ae803dfdf9 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -1,17 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include +#include "ansi-color.h" #include "argv-util.h" #include "boot-entry.h" #include "bootspec.h" #include "build.h" #include "chase.h" #include "conf-files.h" -#include "dirent-util.h" #include "dissect-image.h" #include "env-file.h" #include "env-util.h" @@ -22,6 +21,7 @@ #include "find-esp.h" #include "format-table.h" #include "fs-util.h" +#include "help-util.h" #include "id128-util.h" #include "image-policy.h" #include "kernel-config.h" @@ -29,9 +29,9 @@ #include "loop-util.h" #include "main-func.h" #include "mount-util.h" +#include "options.h" #include "parse-argument.h" #include "path-util.h" -#include "pretty-print.h" #include "recurse-dir.h" #include "rm-rf.h" #include "stat-util.h" @@ -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(wildcard_fd_is_valid(source->rfd)); _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,9 @@ static int do_add( return context_execute(c); } -static int verb_add(int argc, char *argv[], void *userdata) { +VERB(verb_add, "add", "[[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]", 1, VERB_ANY, 0, + "Add a kernel and initrd images to the boot partition"); +static int verb_add(int argc, char *argv[], uintptr_t _data, void *userdata) { const char *version, *kernel; char **initrds; int r; @@ -1214,7 +1221,9 @@ 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) { +VERB_NOARG(verb_add_all, "add-all", + "Add all kernels found in /usr/lib/modules/"); +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,26 +1241,16 @@ 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)); _cleanup_free_ DirectoryEntries *de = NULL; - r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de); + r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_DIRECTORY, &de); if (r < 0) return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m"); FOREACH_ARRAY(d, de->entries, de->n_entries) { - r = dirent_ensure_type(fd, *d); - if (r < 0) { - if (r != -ENOENT) /* don't log if just gone by now */ - log_debug_errno(r, "Failed to check if '%s/usr/lib/modules/%s' is a directory, ignoring: %m", strempty(arg_root), (*d)->d_name); - continue; - } - - if ((*d)->d_type != DT_DIR) - continue; - _cleanup_free_ char *fn = path_join((*d)->d_name, "vmlinuz"); if (!fn) return log_oom(); @@ -1294,17 +1293,19 @@ static int verb_add_all(int argc, char *argv[], void *userdata) { return ret; } -static int run_as_installkernel(int argc, char *argv[]) { +static int run_as_installkernel(char **args) { /* kernel's install.sh invokes us as * /sbin/installkernel * We ignore the last two arguments. */ - if (optind + 2 > argc) + if (strv_length(args) < 2) 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", args[0], args[1]), /* data= */ 0, /* userdata= */ NULL); } -static int verb_remove(int argc, char *argv[], void *userdata) { +VERB(verb_remove, "remove", "KERNEL-VERSION", 2, VERB_ANY, 0, + "Remove a kernel from the boot partition"); +static int verb_remove(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; assert(argc >= 2); @@ -1340,7 +1341,9 @@ static int verb_remove(int argc, char *argv[], void *userdata) { return context_execute(&c); } -static int verb_inspect(int argc, char *argv[], void *userdata) { +VERB(verb_inspect, "inspect", "[[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]", 1, VERB_ANY, VERB_DEFAULT, + "Print details about the installation"); +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 +1456,9 @@ 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) { +VERB_NOARG(verb_list, "list", + "List installed kernels"); +static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_close_ int fd = -EBADF; int r; @@ -1462,12 +1467,12 @@ 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)); _cleanup_free_ DirectoryEntries *de = NULL; - r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de); + r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_DIRECTORY, &de); if (r < 0) return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m"); @@ -1485,16 +1490,6 @@ static int verb_list(int argc, char *argv[], void *userdata) { if (!j) return log_oom(); - r = dirent_ensure_type(fd, *d); - if (r < 0) { - if (r != -ENOENT) /* don't log if just gone by now */ - log_debug_errno(r, "Failed to check if '%s/%s' is a directory, ignoring: %m", strempty(arg_root), j); - continue; - } - - if ((*d)->d_type != DT_DIR) - continue; - _cleanup_free_ char *fn = path_join((*d)->d_name, "vmlinuz"); if (!fn) return log_oom(); @@ -1521,128 +1516,87 @@ static int verb_list(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("kernel-install", "8", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + /* Note: column widths are not synced, because the verbs table is very wide. */ - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sAdd and remove kernel and initrd images to and from the boot partition.%6$s\n" - "\n%3$sUsage:%4$s\n" - " kernel-install [OPTIONS...] add [[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]\n" - " kernel-install [OPTIONS...] add-all\n" - " kernel-install [OPTIONS...] remove KERNEL-VERSION\n" - " kernel-install [OPTIONS...] inspect [[[KERNEL-VERSION] KERNEL-IMAGE]\n" - " [INITRD ...]]\n" - " kernel-install [OPTIONS...] list\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -v --verbose Increase verbosity\n" - " --esp-path=PATH Path to the EFI System Partition (ESP)\n" - " --boot-path=PATH Path to the $BOOT partition\n" - " --make-entry-directory=yes|no|auto\n" - " Create $BOOT/ENTRY-TOKEN/ directory\n" - " --entry-type=type1|type2|all\n" - " Operate only on the specified bootloader\n" - " entry type\n" - " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n" - " Entry token to be used for this installation\n" - " --no-pager Do not pipe inspect output into a pager\n" - " --json=pretty|short|off Generate JSON output\n" - " --no-legend Do not show the headers and footers\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" - "\n" + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Add and remove kernel and initrd images to and from the boot partition."); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\n" "This program may also be invoked as 'installkernel':\n" - " installkernel [OPTIONS...] VERSION VMLINUZ [MAP] [INSTALLATION-DIR]\n" - "(The optional arguments are passed by kernel build system, but ignored.)\n" - "\n" - "See the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + " installkernel [OPTIONS...] VERSION VMLINUZ [MAP] [INSTALLATION-DIR]\n" + "(The optional arguments are passed by kernel build system, but ignored.)\n"); + + help_man_page_reference("kernel-install", "8"); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_LEGEND, - ARG_ESP_PATH, - ARG_BOOT_PATH, - ARG_MAKE_ENTRY_DIRECTORY, - ARG_ENTRY_TOKEN, - ARG_NO_PAGER, - ARG_JSON, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_BOOT_ENTRY_TYPE, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "verbose", no_argument, NULL, 'v' }, - { "esp-path", required_argument, NULL, ARG_ESP_PATH }, - { "boot-path", required_argument, NULL, ARG_BOOT_PATH }, - { "make-entry-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY }, - { "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "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 }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "entry-type", required_argument, NULL, ARG_BOOT_ENTRY_TYPE }, - {} - }; - int t, r; +VERB_COMMON_HELP(help); +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); + + OptionParser opts = { argc, argv }; + int r; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { - while ((t = getopt_long(argc, argv, "hv", options, NULL)) >= 0) - switch (t) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case 'v': + OPTION('v', "verbose", NULL, "Increase verbosity"): log_set_max_level(LOG_DEBUG); arg_verbose = true; break; - case ARG_ESP_PATH: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_esp_path); + OPTION_LONG("esp-path", "PATH", "Path to the EFI System Partition (ESP)"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_esp_path); if (r < 0) - return log_oom(); + return r; break; - case ARG_BOOT_PATH: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_xbootldr_path); + OPTION_LONG("boot-path", "PATH", "Path to the $BOOT partition"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_xbootldr_path); if (r < 0) - return log_oom(); + return r; break; - case ARG_MAKE_ENTRY_DIRECTORY: - if (streq(optarg, "auto")) + OPTION_COMMON_MAKE_ENTRY_DIRECTORY: + if (streq(opts.arg, "auto")) arg_make_entry_directory = -1; else { - r = parse_boolean_argument("--make-entry-directory=", optarg, NULL); + r = parse_boolean_argument("--make-entry-directory=", opts.arg, NULL); if (r < 0) return r; @@ -1650,85 +1604,70 @@ static int parse_argv(int argc, char *argv[]) { } break; - case ARG_ENTRY_TOKEN: - r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token); + OPTION_COMMON_ENTRY_TOKEN: + r = parse_boot_entry_token_type(opts.arg, &arg_entry_token_type, &arg_entry_token); if (r < 0) return r; break; - case ARG_NO_PAGER: + OPTION_LONG("entry-type", "TYPE", + "Operate only on the specified bootloader entry type (type1, type2, all)"): { + if (isempty(opts.arg) || streq(opts.arg, "all")) { + arg_boot_entry_type = _BOOT_ENTRY_TYPE_INVALID; + break; + } + + BootEntryType e = boot_entry_type_from_string(opts.arg); + if (e < 0) + return log_error_errno(e, "Invalid entry type: %s", opts.arg); + arg_boot_entry_type = e; + break; + } + + 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(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root); + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(opts.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(opts.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(opts.arg, &arg_image_policy); if (r < 0) return r; break; - - case ARG_BOOT_ENTRY_TYPE: { - if (isempty(optarg) || streq(optarg, "all")) { - arg_boot_entry_type = _BOOT_ENTRY_TYPE_INVALID; - break; - } - - BootEntryType e = boot_entry_type_from_string(optarg); - if (e < 0) - return log_error_errno(e, "Invalid entry type: %s", optarg); - arg_boot_entry_type = e; - break; - } - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } 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."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Please specify either --root= or --image=, the combination of both is not supported."); + *remaining_args = option_parser_get_args(&opts); return 1; } -static int kernel_install_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "add", 1, VERB_ANY, 0, verb_add }, - { "add-all", 1, 1, 0, verb_add_all }, - { "remove", 2, VERB_ANY, 0, verb_remove }, - { "inspect", 1, VERB_ANY, VERB_DEFAULT, verb_inspect }, - { "list", 1, 1, 0, verb_list }, - {} - }; - - return dispatch_verb(argc, argv, verbs, /* userdata= */ 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; @@ -1757,9 +1696,9 @@ static int run(int argc, char* argv[]) { } if (invoked_as(argv, "installkernel")) - return run_as_installkernel(argc, argv); + return run_as_installkernel(args); - return kernel_install_main(argc, argv); + return dispatch_verb(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); 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,133 @@ 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); + + OptionParser opts = { argc, argv }; + int r; - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) 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, opts.arg); if (r < 0) return r; - break; - case ARG_PRIVATE_KEY_SOURCE: + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - optarg, + opts.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, opts.arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - optarg, + opts.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(opts.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(opts.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 = opts.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(opts.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(&opts); 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; @@ -224,7 +193,7 @@ static int verb_validate(int argc, char *argv[], void *userdata) { if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); if (r < 0) - return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + return r; } r = openssl_load_private_key( @@ -248,7 +217,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 +233,10 @@ static int verb_extract_public(int argc, char *argv[], void *userdata) { return r; } + r = DLOPEN_LIBCRYPTO(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + r = openssl_load_x509_certificate( arg_certificate_source_type, arg_certificate_source, @@ -269,10 +245,10 @@ 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), + return log_openssl_errors( + LOG_ERR, "Failed to extract public key from certificate %s.", arg_certificate); @@ -283,7 +259,7 @@ static int verb_extract_public(int argc, char *argv[], void *userdata) { if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); if (r < 0) - return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + return r; } r = openssl_load_private_key( @@ -309,13 +285,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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write public key to stdout"); + if (sym_PEM_write_PUBKEY(stdout, public_key) == 0) + return log_openssl_errors(LOG_ERR, "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 +314,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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write certificate to stdout."); + if (sym_PEM_write_X509(stdout, certificate) == 0) + return log_openssl_errors(LOG_ERR, "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 +373,16 @@ 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)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Error creating new PKCS7 content field"); + if (!sym_PKCS7_content_new(pkcs7, NID_pkcs7_data)) + return log_openssl_errors(LOG_ERR, "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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to set PKCS#7 detached attribute: %s", - ERR_error_string(ERR_get_error(), NULL)); + if (sym_PKCS7_set_detached(pkcs7, true) == 0) + return log_openssl_errors(LOG_ERR, "Failed to set PKCS#7 detached attribute"); /* 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 +390,8 @@ 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)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write PKCS#7 file: %s", - ERR_error_string(ERR_get_error(), NULL)); + if (!sym_i2d_PKCS7_fp(output, pkcs7)) + return log_openssl_errors(LOG_ERR, "Failed to write PKCS#7 file"); r = flink_tmpfile(output, tmp, arg_output, LINK_TMPFILE_REPLACE|LINK_TMPFILE_SYNC); if (r < 0) @@ -426,24 +403,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(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/bpf.c b/src/libc/bpf.c index 4a7498e502aa7..d87dcb1b0c1cf 100644 --- a/src/libc/bpf.c +++ b/src/libc/bpf.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_BPF -int missing_bpf(int cmd, union bpf_attr *attr, size_t size) { - return syscall(__NR_bpf, cmd, attr, size); -} -#endif +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(bpf, int, + int, cmd, + union bpf_attr *, attr, + size_t, size) diff --git a/src/libc/epoll.c b/src/libc/epoll.c new file mode 100644 index 0000000000000..a7d4075f37a35 --- /dev/null +++ b/src/libc/epoll.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "libc-shim.h" + +/* On 32-bit architectures built with _TIME_BITS=64, glibc renames epoll_pwait2() to + * __epoll_pwait2_time64 via __asm__() in so the linker picks the variant whose + * struct timespec ABI matches our 64-bit time_t. dlsym() can't see that header-level rename, so + * we have to spell out the right name here, otherwise we'd silently dispatch a 64-bit timespec to + * the legacy 32-bit-time_t entry point. */ +#ifdef __USE_TIME_BITS64 +DEFINE_LIBC_ERRNO_SHIM_NAMED(epoll_pwait2, "__epoll_pwait2_time64", int, + int, fd, + struct epoll_event *, events, + int, maxevents, + const struct timespec *, timeout, + const sigset_t *, sigmask) +#else +DEFINE_LIBC_ERRNO_SHIM(epoll_pwait2, int, + int, fd, + struct epoll_event *, events, + int, maxevents, + const struct timespec *, timeout, + const sigset_t *, sigmask) +#endif diff --git a/src/libc/fcntl.c b/src/libc/fcntl.c new file mode 100644 index 0000000000000..8fd78ca897e5e --- /dev/null +++ b/src/libc/fcntl.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(openat2, int, + int, dfd, + const char *, filename, + const struct open_how *, how, + size_t, usize) diff --git a/src/libc/ioprio.c b/src/libc/ioprio.c index 5bb640457dc1f..053693139c3b5 100644 --- a/src/libc/ioprio.c +++ b/src/libc/ioprio.c @@ -1,17 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_IOPRIO_GET -int missing_ioprio_get(int which, int who) { - return syscall(__NR_ioprio_get, which, who); -} -#endif +#include "libc-shim.h" -#if !HAVE_IOPRIO_SET -int missing_ioprio_set(int which, int who, int ioprio) { - return syscall(__NR_ioprio_set, which, who, ioprio); -} -#endif +DEFINE_SYSCALL_SHIM(ioprio_get, int, + int, which, + int, who) + +DEFINE_SYSCALL_SHIM(ioprio_set, int, + int, which, + int, who, + int, ioprio) diff --git a/src/libc/kcmp.c b/src/libc/kcmp.c index aafff999ef44a..0f73f8500dd62 100644 --- a/src/libc/kcmp.c +++ b/src/libc/kcmp.c @@ -1,11 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_KCMP -int missing_kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) { - return syscall(__NR_kcmp, pid1, pid2, type, idx1, idx2); -} -#endif +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(kcmp, int, + pid_t, pid1, + pid_t, pid2, + int, type, + unsigned long, idx1, + unsigned long, idx2) diff --git a/src/libc/kexec.c b/src/libc/kexec.c new file mode 100644 index 0000000000000..051253d17bde8 --- /dev/null +++ b/src/libc/kexec.c @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "libc-shim.h" + +#ifdef __NR_kexec_file_load +DEFINE_SYSCALL_SHIM(kexec_file_load, int, + int, kernel_fd, + int, initrd_fd, + unsigned long, cmdline_len, + const char *, cmdline, + unsigned long, flags) +#endif diff --git a/src/libc/keyctl.c b/src/libc/keyctl.c index af4bca87f513e..c86518454835b 100644 --- a/src/libc/keyctl.c +++ b/src/libc/keyctl.c @@ -1,23 +1,25 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_KEYCTL -long missing_keyctl(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) { - return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5); -} -#endif +#include "libc-shim.h" -#if !HAVE_ADD_KEY -key_serial_t missing_add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) { - return syscall(__NR_add_key, type, description, payload, plen, ringid); -} -#endif +DEFINE_SYSCALL_SHIM(keyctl, long, + int, cmd, + unsigned long, arg2, + unsigned long, arg3, + unsigned long, arg4, + unsigned long, arg5) -#if !HAVE_REQUEST_KEY -key_serial_t missing_request_key(const char *type, const char *description, const char *callout_info, key_serial_t destringid) { - return syscall(__NR_request_key, type, description, callout_info, destringid); -} -#endif +DEFINE_SYSCALL_SHIM(add_key, key_serial_t, + const char *, type, + const char *, description, + const void *, payload, + size_t, plen, + key_serial_t, ringid) + +DEFINE_SYSCALL_SHIM(request_key, key_serial_t, + const char *, type, + const char *, description, + const char *, callout_info, + key_serial_t, destringid) diff --git a/src/libc/libc-shim.h b/src/libc/libc-shim.h new file mode 100644 index 0000000000000..4377b713f0ab6 --- /dev/null +++ b/src/libc/libc-shim.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include +#include +#include + +/* Each parameter is passed as a flat (type, name) pair: type1, name1, type2, name2, ... The + * _SHIM_DECL/_SHIM_NAME macros consume two args at a time and emit either "type name" pairs (for + * the function declarator) or just the names (for forwarding). _SHIM_PAIRS counts the number of + * pairs by indexing into a table that increments every two positions. */ +#define _SHIM_CAT(a, b) _SHIM_CAT_(a, b) +#define _SHIM_CAT_(a, b) a##b + +#define _SHIM_NTH(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N +#define _SHIM_PAIRS(...) _SHIM_NTH(__VA_ARGS__, 8, 8, 7, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0) + +#define _SHIM_DECL_1(t, n) t n +#define _SHIM_DECL_2(t, n, ...) t n, _SHIM_DECL_1(__VA_ARGS__) +#define _SHIM_DECL_3(t, n, ...) t n, _SHIM_DECL_2(__VA_ARGS__) +#define _SHIM_DECL_4(t, n, ...) t n, _SHIM_DECL_3(__VA_ARGS__) +#define _SHIM_DECL_5(t, n, ...) t n, _SHIM_DECL_4(__VA_ARGS__) +#define _SHIM_DECL_6(t, n, ...) t n, _SHIM_DECL_5(__VA_ARGS__) +#define _SHIM_DECL_7(t, n, ...) t n, _SHIM_DECL_6(__VA_ARGS__) +#define _SHIM_DECL_8(t, n, ...) t n, _SHIM_DECL_7(__VA_ARGS__) +#define _SHIM_DECL(...) _SHIM_CAT(_SHIM_DECL_, _SHIM_PAIRS(__VA_ARGS__))(__VA_ARGS__) + +#define _SHIM_NAME_1(t, n) n +#define _SHIM_NAME_2(t, n, ...) n, _SHIM_NAME_1(__VA_ARGS__) +#define _SHIM_NAME_3(t, n, ...) n, _SHIM_NAME_2(__VA_ARGS__) +#define _SHIM_NAME_4(t, n, ...) n, _SHIM_NAME_3(__VA_ARGS__) +#define _SHIM_NAME_5(t, n, ...) n, _SHIM_NAME_4(__VA_ARGS__) +#define _SHIM_NAME_6(t, n, ...) n, _SHIM_NAME_5(__VA_ARGS__) +#define _SHIM_NAME_7(t, n, ...) n, _SHIM_NAME_6(__VA_ARGS__) +#define _SHIM_NAME_8(t, n, ...) n, _SHIM_NAME_7(__VA_ARGS__) +#define _SHIM_NAME(...) _SHIM_CAT(_SHIM_NAME_, _SHIM_PAIRS(__VA_ARGS__))(__VA_ARGS__) + +/* The shim resolves the libc symbol via dlsym(RTLD_DEFAULT) at DSO-load time using a constructor + * and caches the result in a file-scope static. Constructors run single-threaded before main() and + * before any signal handler can fire, so the cached pointer needs no atomics: subsequent reads from + * any thread observe the value stored during init. Resolving eagerly also keeps dlsym() out of + * contexts where it is not async-signal-safe (signal handlers, between fork() and exec()). + * + * The asm barrier after dlsym() is load-bearing: without it, when LTO determines the cache store + * is dead (because no caller of func##_shim survives DCE) the compiler is free to tail-call + * dlsym() (jmp dlsym@plt). Under glibc, dlsym reads __builtin_return_address(0) to find its + * caller's link map; with a tail call that read lands inside ld.so's call_init(), the resulting + * link map has no l_scope, and _dl_lookup_symbol_x SIGSEGVs. Filed upstream in glibc by + * https://sourceware.org/bugzilla/show_bug.cgi?id=34156. The barrier keeps us working on + * unpatched libc. + * + * Each reference to `func` in the macro body is positioned as an operand of `#` or `##` so the + * override headers (e.g. "#define openat2 openat2_shim") don't rewrite the token before pasting or + * stringification. For the same reason the resolution logic isn't extracted into a helper macro — + * passing `func` to a nested macro would expand it as a regular argument and re-trigger the + * override. + * + * Defines a wrapper that calls the libc symbol if available at runtime, or falls back to the + * corresponding direct syscall otherwise. Each parameter is passed as type, name pairs (flat). */ +#define DEFINE_SYSCALL_SHIM(func, ret, ...) \ + static typeof(&func##_shim) func##_shim_cache; \ + __attribute__((constructor)) static void func##_shim_init(void) { \ + void *p = dlsym(RTLD_DEFAULT, #func); \ + __asm__ volatile("" ::: "memory"); \ + func##_shim_cache = (typeof(&func##_shim)) p; \ + } \ + ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \ + if (func##_shim_cache) \ + return func##_shim_cache(_SHIM_NAME(__VA_ARGS__)); \ + return syscall(__NR_##func, _SHIM_NAME(__VA_ARGS__)); \ + } + +/* Like DEFINE_SYSCALL_SHIM but for libc helpers that have no corresponding syscall and report errors + * by returning the positive errno value directly (posix_spawn-family convention). If the libc symbol + * is missing at runtime, ENOSYS is returned. */ +#define DEFINE_LIBC_SHIM(func, ret, ...) \ + static typeof(&func##_shim) func##_shim_cache; \ + __attribute__((constructor)) static void func##_shim_init(void) { \ + void *p = dlsym(RTLD_DEFAULT, #func); \ + __asm__ volatile("" ::: "memory"); \ + func##_shim_cache = (typeof(&func##_shim)) p; \ + } \ + ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \ + if (func##_shim_cache) \ + return func##_shim_cache(_SHIM_NAME(__VA_ARGS__)); \ + return ENOSYS; \ + } + +/* Like DEFINE_LIBC_SHIM but for libc helpers that report errors via errno + -1 return value. If the + * libc symbol is missing at runtime, errno is set to ENOSYS and -1 is returned. */ +#define DEFINE_LIBC_ERRNO_SHIM(func, ret, ...) \ + static typeof(&func##_shim) func##_shim_cache; \ + __attribute__((constructor)) static void func##_shim_init(void) { \ + void *p = dlsym(RTLD_DEFAULT, #func); \ + __asm__ volatile("" ::: "memory"); \ + func##_shim_cache = (typeof(&func##_shim)) p; \ + } \ + ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \ + if (func##_shim_cache) \ + return func##_shim_cache(_SHIM_NAME(__VA_ARGS__)); \ + errno = ENOSYS; \ + return -1; \ + } + +/* Like DEFINE_LIBC_ERRNO_SHIM but with an explicit string for the libc symbol name to dlsym. This is + * needed for functions whose libc symbol is renamed via __asm__() in the header (e.g. when glibc + * redirects time-related calls to their __*_time64 aliases on 32-bit systems built with + * _TIME_BITS=64) since dlsym() doesn't see those header-level renames, so the caller has to spell out + * the actual ABI symbol name that matches the struct layout the compiler picked. We can't forward to + * DEFINE_LIBC_ERRNO_SHIM since passing `func` as a regular argument would let the override-header + * #define rewrite the token (e.g. `epoll_pwait2` to `epoll_pwait2_shim`) before the inner macro + * could paste it, so we duplicate the body and keep every `func` reference behind `#` or `##`. */ +#define DEFINE_LIBC_ERRNO_SHIM_NAMED(func, sym_name, ret, ...) \ + static typeof(&func##_shim) func##_shim_cache; \ + __attribute__((constructor)) static void func##_shim_init(void) { \ + void *p = dlsym(RTLD_DEFAULT, sym_name); \ + __asm__ volatile("" ::: "memory"); \ + func##_shim_cache = (typeof(&func##_shim)) p; \ + } \ + ret func##_shim(_SHIM_DECL(__VA_ARGS__)) { \ + if (func##_shim_cache) \ + return func##_shim_cache(_SHIM_NAME(__VA_ARGS__)); \ + errno = ENOSYS; \ + return -1; \ + } diff --git a/src/libc/mempolicy.c b/src/libc/mempolicy.c index 77b0e1ac010f9..686aba2b59024 100644 --- a/src/libc/mempolicy.c +++ b/src/libc/mempolicy.c @@ -1,17 +1,17 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_SET_MEMPOLICY -int missing_set_mempolicy(int mode, const unsigned long *nodemask, unsigned long maxnode) { - return syscall(__NR_set_mempolicy, mode, nodemask, maxnode); -} -#endif +#include "libc-shim.h" -#if !HAVE_GET_MEMPOLICY -int missing_get_mempolicy(int *mode, unsigned long *nodemask, unsigned long maxnode, void *addr, unsigned long flags) { - return syscall(__NR_get_mempolicy, mode, nodemask, maxnode, addr, flags); -} -#endif +DEFINE_SYSCALL_SHIM(set_mempolicy, int, + int, mode, + const unsigned long *, nodemask, + unsigned long, maxnode) + +DEFINE_SYSCALL_SHIM(get_mempolicy, int, + int *, mode, + unsigned long *, nodemask, + unsigned long, maxnode, + void *, addr, + unsigned long, flags) diff --git a/src/libc/meson.build b/src/libc/meson.build index 306512ffd7002..9b86168536e43 100644 --- a/src/libc/meson.build +++ b/src/libc/meson.build @@ -2,8 +2,11 @@ libc_wrapper_sources = files( 'bpf.c', + 'epoll.c', + 'fcntl.c', 'ioprio.c', 'kcmp.c', + 'kexec.c', 'keyctl.c', 'mempolicy.c', 'mount.c', @@ -11,6 +14,7 @@ libc_wrapper_sources = files( 'quota.c', 'sched.c', 'signal.c', + 'spawn.c', 'stat.c', 'unistd.c', 'xattr.c', diff --git a/src/libc/mount.c b/src/libc/mount.c index 1d324ef386614..3e626e87acd2b 100644 --- a/src/libc/mount.c +++ b/src/libc/mount.c @@ -1,47 +1,47 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include - -#if !HAVE_FSOPEN -int missing_fsopen(const char *fsname, unsigned flags) { - return syscall(__NR_fsopen, fsname, flags); -} -#endif - -#if !HAVE_FSMOUNT -int missing_fsmount(int fd, unsigned flags, unsigned ms_flags) { - return syscall(__NR_fsmount, fd, flags, ms_flags); -} -#endif - -#if !HAVE_MOVE_MOUNT -int missing_move_mount(int from_dfd, const char *from_pathname, int to_dfd, const char *to_pathname, unsigned flags) { - return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags); -} -#endif - -#if !HAVE_FSCONFIG -int missing_fsconfig(int fd, unsigned cmd, const char *key, const void *value, int aux) { - return syscall(__NR_fsconfig, fd, cmd, key, value, aux); -} -#endif - -#if !HAVE_OPEN_TREE -int missing_open_tree(int dfd, const char *filename, unsigned flags) { - return syscall(__NR_open_tree, dfd, filename, flags); -} -#endif - -#if !HAVE_MOUNT_SETATTR -int missing_mount_setattr(int dfd, const char *path, unsigned flags, struct mount_attr *attr, size_t size) { - return syscall(__NR_mount_setattr, dfd, path, flags, attr, size); -} -#endif - -#if !HAVE_OPEN_TREE_ATTR -int missing_open_tree_attr(int dfd, const char *filename, unsigned int flags, struct mount_attr *attr, size_t size) { - return syscall(__NR_open_tree_attr, dfd, filename, flags, attr, size); -} -#endif + +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(fsopen, int, + const char *, fsname, + unsigned, flags) + +DEFINE_SYSCALL_SHIM(fsmount, int, + int, fd, + unsigned, flags, + unsigned, ms_flags) + +DEFINE_SYSCALL_SHIM(move_mount, int, + int, from_dfd, + const char *, from_pathname, + int, to_dfd, + const char *, to_pathname, + unsigned, flags) + +DEFINE_SYSCALL_SHIM(fsconfig, int, + int, fd, + unsigned, cmd, + const char *, key, + const void *, value, + int, aux) + +DEFINE_SYSCALL_SHIM(open_tree, int, + int, dfd, + const char *, filename, + unsigned, flags) + +DEFINE_SYSCALL_SHIM(mount_setattr, int, + int, dfd, + const char *, path, + unsigned, flags, + struct mount_attr *, attr, + size_t, size) + +DEFINE_SYSCALL_SHIM(open_tree_attr, int, + int, dfd, + const char *, filename, + unsigned int, flags, + struct mount_attr *, attr, + size_t, size) diff --git a/src/libc/musl/getopt.c b/src/libc/musl/getopt.c deleted file mode 100644 index 15c3afa49bdbb..0000000000000 --- a/src/libc/musl/getopt.c +++ /dev/null @@ -1,100 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include -#include - -static int first_non_opt = 0, last_non_opt = 0; -static bool non_opt_found = false, dash_dash = false; - -static void shift(char * const *argv, int start, int end) { - char **av = (char**) argv; - char *saved = av[end]; - - for (int i = end; i > start; i--) - av[i] = av[i - 1]; - - av[start] = saved; -} - -static void exchange(int argc, char * const *argv) { - /* input: - * - * first_non_opt last_non_opt optind - * | | | - * v v v - * aaaaa bbbbb ccccc --prev-opt prev-opt-arg ddddd --next-opt - * - * output: - * first_non_opt last_non_opt optind - * | | | - * v v v - * --prev-opt prev-opt-arg aaaaa bbbbb ccccc ddddd --next-opt - */ - - /* First, move previous arguments. */ - int c = optind - 1 - last_non_opt; - if (c > 0) { - for (int i = 0; i < c; i++) - shift(argv, first_non_opt, optind - 1); - first_non_opt += c; - last_non_opt += c; - } - - /* Then, skip entries that do not start with '-'. */ - while (optind < argc && (argv[optind][0] != '-' || argv[optind][1] == '\0')) { - if (!non_opt_found) { - first_non_opt = optind; - non_opt_found = true; - } - last_non_opt = optind; - optind++; - } -} - -int getopt_long_fix( - int argc, - char * const *argv, - const char *optstring, - const struct option *longopts, - int *longindex) { - - int r; - - if (optind == 0 || first_non_opt == 0 || last_non_opt == 0) { - /* initialize musl's internal variables. */ - (void) (getopt_long)(/* argc= */ -1, /* argv= */ NULL, /* optstring= */ NULL, /* longopts= */ NULL, /* longindex= */ NULL); - first_non_opt = last_non_opt = 1; - non_opt_found = dash_dash = false; - } - - if (first_non_opt >= argc || last_non_opt >= argc || optind > argc || dash_dash) - return -1; - - /* Do not shuffle arguments when optstring starts with '+' or '-'. */ - if (!optstring || optstring[0] == '+' || optstring[0] == '-') - return (getopt_long)(argc, argv, optstring, longopts, longindex); - - exchange(argc, argv); - - if (optind < argc && strcmp(argv[optind], "--") == 0) { - if (first_non_opt < optind) - shift(argv, first_non_opt, optind); - first_non_opt++; - optind++; - dash_dash = true; - if (non_opt_found) - optind = first_non_opt; - return -1; - } - - r = (getopt_long)(argc, argv, optstring, longopts, longindex); - if (r < 0 && non_opt_found) - optind = first_non_opt; - - return r; -} - -int getopt_fix(int argc, char * const *argv, const char *optstring) { - return getopt_long_fix(argc, argv, optstring, /* longopts= */ NULL, /* longindex= */ NULL); -} diff --git a/src/libc/musl/meson.build b/src/libc/musl/meson.build index acda973b8d9b2..a06216f063445 100644 --- a/src/libc/musl/meson.build +++ b/src/libc/musl/meson.build @@ -6,7 +6,6 @@ endif libc_wrapper_sources += files( 'crypt.c', - 'getopt.c', 'printf.c', 'stdio.c', 'stdlib.c', diff --git a/src/libc/musl/stdio.c b/src/libc/musl/stdio.c index 6d02aef5f65da..cfe35b09f9908 100644 --- a/src/libc/musl/stdio.c +++ b/src/libc/musl/stdio.c @@ -3,14 +3,6 @@ #include #include #include -#include -#include - -#if !HAVE_RENAMEAT2 -int missing_renameat2(int __oldfd, const char *__old, int __newfd, const char *__new, unsigned __flags) { - return syscall(__NR_renameat2, __oldfd, __old, __newfd, __new, __flags); -} -#endif #define DEFINE_PUT(func) \ int func##_check_writable(int c, FILE *stream) { \ diff --git a/src/libc/pidfd.c b/src/libc/pidfd.c index a779e3459c674..8343cf7ff9fa9 100644 --- a/src/libc/pidfd.c +++ b/src/libc/pidfd.c @@ -1,17 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_PIDFD_OPEN -int missing_pidfd_open(pid_t pid, unsigned flags) { - return syscall(__NR_pidfd_open, pid, flags); -} -#endif +#include "libc-shim.h" -#if !HAVE_PIDFD_SEND_SIGNAL -int missing_pidfd_send_signal(int fd, int sig, siginfo_t *info, unsigned flags) { - return syscall(__NR_pidfd_send_signal, fd, sig, info, flags); -} -#endif +DEFINE_SYSCALL_SHIM(pidfd_open, int, + pid_t, pid, + unsigned, flags) + +DEFINE_SYSCALL_SHIM(pidfd_send_signal, int, + int, fd, + int, sig, + siginfo_t *, info, + unsigned, flags) diff --git a/src/libc/quota.c b/src/libc/quota.c index 19695df9b3c32..08a7e5b8ae2a1 100644 --- a/src/libc/quota.c +++ b/src/libc/quota.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_QUOTACTL_FD -int missing_quotactl_fd(int fd, int cmd, int id, void *addr) { - return syscall(__NR_quotactl_fd, fd, cmd, id, addr); -} -#endif +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(quotactl_fd, int, + int, fd, + int, cmd, + int, id, + void *, addr) diff --git a/src/libc/sched.c b/src/libc/sched.c index cf1752651b39b..24de9731beb61 100644 --- a/src/libc/sched.c +++ b/src/libc/sched.c @@ -1,11 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_SCHED_SETATTR -int missing_sched_setattr(pid_t pid, struct sched_attr *attr, unsigned flags) { - return syscall(__NR_sched_setattr, pid, attr, flags); -} -#endif +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(sched_setattr, int, + pid_t, pid, + struct sched_attr *, attr, + unsigned, flags) diff --git a/src/libc/signal.c b/src/libc/signal.c index 1908331b1a4f2..9370491821f82 100644 --- a/src/libc/signal.c +++ b/src/libc/signal.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_RT_TGSIGQUEUEINFO -int missing_rt_tgsigqueueinfo(pid_t tgid, pid_t tid, int sig, siginfo_t *info) { - return syscall(__NR_rt_tgsigqueueinfo, tgid, tid, sig, info); -} -#endif +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(rt_tgsigqueueinfo, int, + pid_t, tgid, + pid_t, tid, + int, sig, + siginfo_t *, info) diff --git a/src/libc/spawn.c b/src/libc/spawn.c new file mode 100644 index 0000000000000..6e2f9cf0429e2 --- /dev/null +++ b/src/libc/spawn.c @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "libc-shim.h" + +DEFINE_LIBC_SHIM(pidfd_spawn, int, + pid_t *restrict, pidfd, + const char *restrict, path, + const posix_spawn_file_actions_t *restrict, file_actions, + const posix_spawnattr_t *restrict, attrp, + char *const *restrict, argv, + char *const *restrict, envp) + +DEFINE_LIBC_SHIM(posix_spawnattr_setcgroup_np, int, + posix_spawnattr_t *, attr, + int, cgroup) diff --git a/src/libc/stat.c b/src/libc/stat.c index b283005f40b55..ffc00f13c647d 100644 --- a/src/libc/stat.c +++ b/src/libc/stat.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include -#include -#if !HAVE_FCHMODAT2 -int missing_fchmodat2(int dirfd, const char *path, mode_t mode, int flags) { - return syscall(__NR_fchmodat2, dirfd, path, mode, flags); -} -#endif +#include "libc-shim.h" + +DEFINE_SYSCALL_SHIM(fchmodat2, int, + int, dirfd, + const char *, path, + mode_t, mode, + int, flags) diff --git a/src/libc/unistd.c b/src/libc/unistd.c index 81728631c6b43..22fb704907e13 100644 --- a/src/libc/unistd.c +++ b/src/libc/unistd.c @@ -1,10 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include -#include +#include "libc-shim.h" -#if !HAVE_PIVOT_ROOT -int missing_pivot_root(const char *new_root, const char *put_old) { - return syscall(__NR_pivot_root, new_root, put_old); -} -#endif +DEFINE_SYSCALL_SHIM(pivot_root, int, + const char *, new_root, + const char *, put_old) diff --git a/src/libc/xattr.c b/src/libc/xattr.c index 68fa97ab281d0..668faa766c9da 100644 --- a/src/libc/xattr.c +++ b/src/libc/xattr.c @@ -1,17 +1,19 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include -#include -#if !HAVE_SETXATTRAT -int missing_setxattrat(int fd, const char *path, int at_flags, const char *name, const struct xattr_args *args, size_t size) { - return syscall(__NR_setxattrat, fd, path, at_flags, name, args, size); -} -#endif +#include "libc-shim.h" -#if !HAVE_REMOVEXATTRAT -int missing_removexattrat(int fd, const char *path, int at_flags, const char *name) { - return syscall(__NR_removexattrat, fd, path, at_flags, name); -} -#endif +DEFINE_SYSCALL_SHIM(setxattrat, int, + int, fd, + const char *, path, + int, at_flags, + const char *, name, + const struct xattr_args *, args, + size_t, size) + +DEFINE_SYSCALL_SHIM(removexattrat, int, + int, fd, + const char *, path, + int, at_flags, + const char *, name) diff --git a/src/libsystemd-network/dhcp-client-id-internal.h b/src/libsystemd-network/dhcp-client-id-internal.h index 8efc007a3d4a8..47cc3cf804012 100644 --- a/src/libsystemd-network/dhcp-client-id-internal.h +++ b/src/libsystemd-network/dhcp-client-id-internal.h @@ -1,13 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include - #include "sd-dhcp-client-id.h" #include "dhcp-duid-internal.h" -#include "sd-forward.h" -#include "sparse-endian.h" +#include "dhcp-forward.h" +#include "ether-addr-util.h" /* RFC 2132 section 9.14: its minimum length is 2. * Note, its maximum is not mentioend in the RFC. Hence, 255. */ diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h index f75ca71b4350c..53f4287868379 100644 --- a/src/libsystemd-network/dhcp-client-internal.h +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -3,26 +3,71 @@ #include "sd-dhcp-client.h" -#include "sd-forward.h" +#include "dhcp-client-id-internal.h" +#include "dhcp-forward.h" +#include "dhcp-protocol.h" +#include "ether-addr-util.h" +#include "iovec-wrapper.h" #include "network-common.h" -typedef enum DHCPState { - DHCP_STATE_STOPPED, - DHCP_STATE_INIT, - DHCP_STATE_SELECTING, - DHCP_STATE_INIT_REBOOT, - DHCP_STATE_REBOOTING, - DHCP_STATE_REQUESTING, - DHCP_STATE_BOUND, - DHCP_STATE_RENEWING, - DHCP_STATE_REBINDING, - _DHCP_STATE_MAX, - _DHCP_STATE_INVALID = -EINVAL, -} DHCPState; +struct sd_dhcp_client { + unsigned n_ref; -DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_state, DHCPState); + int socket_fd; /* socket fd set externally, used by unit tests */ -typedef struct sd_dhcp_client sd_dhcp_client; + 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; + 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; + struct iovec_wrapper 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; + TLV *extra_options; + TLV *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; + uint8_t ip_service_type; + int socket_priority; + bool ipv6_acquired; + bool bootp; + bool send_release; +}; int dhcp_client_set_state_callback( sd_dhcp_client *client, @@ -30,6 +75,21 @@ int dhcp_client_set_state_callback( void *userdata); int dhcp_client_get_state(sd_dhcp_client *client); +int dhcp_client_set_extra_options(sd_dhcp_client *client, TLV *options); +int dhcp_client_set_vendor_options(sd_dhcp_client *client, TLV *options); +int dhcp_client_set_user_class(sd_dhcp_client *client, const struct iovec_wrapper *user_class); + +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..f4f3b52a6598d --- /dev/null +++ b/src/libsystemd-network/dhcp-client-send.c @@ -0,0 +1,708 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-event.h" + +#include "dhcp-client-internal.h" +#include "dhcp-client-send.h" +#include "dhcp-lease-internal.h" /* IWYU pragma: keep */ +#include "dhcp-message.h" +#include "errno-util.h" +#include "fd-util.h" +#include "ip-util.h" +#include "socket-util.h" +#include "unaligned.h" + +/* The minimal DHCP packet size without IP header: + * UDP header (8 bytes) + DHCP header (without options) */ +#define DHCP_MINIMUM_UDP_SIZE (sizeof(struct udphdr) + sizeof(struct DHCPMessageHeader)) + +static int client_set_bpf(sd_dhcp_client *client, int fd) { + assert(client); + assert(fd >= 0); + + size_t hlen = 0; + uint32_t mac_hi = 0; + uint16_t mac_lo = 0; + if (client->arp_type != ARPHRD_INFINIBAND) + hlen = client->hw_addr.length; + if (hlen == ETH_ALEN) { + mac_hi = unaligned_read_be32(client->hw_addr.bytes); + mac_lo = unaligned_read_be16(client->hw_addr.bytes + 4); + } + + struct sock_filter filter[] = { + /* 1. Basic packet length check. + * Check against the minimum possible length. + * Note, BPF_MSH extracts the lower 4 bits (IHL) and multiplies by 4 to get the byte length, + * it needs to be combined with BPF_LDX. */ + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), /* X <- IP header length in bytes */ + BPF_STMT(BPF_MISC + BPF_TXA, 0), /* A <- X */ + BPF_STMT(BPF_ALU + BPF_ADD + BPF_K, DHCP_MINIMUM_UDP_SIZE), /* A += UDP header + DHCP header */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A (minimal DHCP packet size) */ + BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_X, 0, 1, 0), /* packet length >= minimal DHCP packet size ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 2. Protocol check (Fixed offset in IPv4 header) */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct iphdr, protocol)), /* A <- IP protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), /* IP protocol == UDP ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 3. IP Fragmentation checks. + * When an IP packet is larger than the MTU, it is fragmented into smaller pieces. The UDP + * header is ONLY present in the very first fragment. Since BPF filters are stateless and + * cannot reassemble fragments, we must explicitly drop any packet that is part of a + * fragmented sequence to avoid parsing raw payload data as if it were a UDP/DHCP header. */ + + /* 3a. Check the 'More Fragments' (MF) bit. + * If the bit is set, it means there are more fragments following this one. Hence, the packet + * must be dropped. */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct iphdr, frag_off)), + BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x20), /* A <- A & 0x20 (More Fragments bit) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? (No more fragments) */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore packet if MF == 1 */ + + /* 3b. Check the 'Fragment Offset' field. + * This indicates the position of this specific fragment relative to the beginning of the + * original, unfragmented packet. If the offset is greater than 0, it means this is a + * subsequent fragment (e.g., the 2nd or later piece), hence it must be dropped. */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct iphdr, frag_off)), + BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x1fff), /* A <- A & 0x1fff (Fragment offset) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? (This is the first fragment) */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore packet if Offset != 0 */ + + /* 4. Variable Offset Processing (Support for IP Options) + * Load the IP header length again. It will be used BPF_IND below. */ + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), /* X <- IP header length in bytes */ + + /* 4a. Check UDP destination port using indirect load (X + offset) */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct udphdr, dest)), /* A <- (UDP destination port) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, client->port, 1, 0), /* UDP destination port == 68 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 4b. Check DHCP operation code (op) using indirect load (X + UDP header len + op offset) */ + BPF_STMT(BPF_LD + BPF_B + BPF_IND, sizeof(struct udphdr) + offsetof(DHCPMessageHeader, op)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 4c. Check hardware type using indirect load (X + UDP header len + htype offset) */ + BPF_STMT(BPF_LD + BPF_B + BPF_IND, sizeof(struct udphdr) + offsetof(DHCPMessageHeader, htype)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, client->arp_type, 1, 0), /* htype == client->arp_type ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 4d. Check message xid using indirect load (X + UDP header len + xid offset) */ + BPF_STMT(BPF_LD + BPF_W + BPF_IND, sizeof(struct udphdr) + offsetof(DHCPMessageHeader, xid)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, client->xid, 1, 0), /* xid == client->xid ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 4e. Check hardware address length using indirect load (X + UDP header len + hlen offset) */ + BPF_STMT(BPF_LD + BPF_B + BPF_IND, sizeof(struct udphdr) + offsetof(DHCPMessageHeader, hlen)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, hlen, 1, 0), /* hlen == expected hlen ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 4f. Check hardware address when the hardware address length is 6 (ETH_ALEN) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_ALEN, 0, 6), /* hlen == ETH_ALEN ? */ + BPF_STMT(BPF_LD + BPF_W + BPF_IND, sizeof(struct udphdr) + offsetof(DHCPMessageHeader, chaddr)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, mac_hi, 1, 0), /* first 4 bytes of chaddr == mac_hi ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_IND, sizeof(struct udphdr) + offsetof(DHCPMessageHeader, chaddr) + 4), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, mac_lo, 1, 0), /* next 2 bytes of chaddr == mac_lo ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* 4g. Check DHCP magic cookie using indirect load (X + UDP header len + magic cookie offset) */ + BPF_STMT(BPF_LD + BPF_W + BPF_IND, sizeof(struct udphdr) + offsetof(DHCPMessageHeader, magic)), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_MAGIC_COOKIE, 1, 0), /* cookie == DHCP magic cookie ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* All checks passed, accept the entire packet. */ + BPF_STMT(BPF_RET + BPF_K, UINT32_MAX), /* accept */ + }; + + struct sock_fprog fprog = { + .len = ELEMENTSOF(filter), + .filter = filter + }; + + return RET_NERRNO(setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog))); +} + +static int client_open_raw_socket(sd_dhcp_client *client) { + int r; + + assert(client); + + _cleanup_close_ int fd = RET_NERRNO(socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + if (fd < 0) + return fd; + + /* While bind() with sockaddr_ll is strictly sufficient for AF_PACKET, we also set SO_BINDTOIFINDEX + * to initialize the kernel's sk_bound_dev_if state. This ensures compatibility with cgroup/eBPF + * filters and maintains consistency. */ + r = socket_bind_to_ifindex(fd, client->ifindex); + if (r < 0) + return r; + + r = client_set_bpf(client, fd); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_TIMESTAMP, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, client->socket_priority); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_PACKET, PACKET_AUXDATA, true); + if (r < 0) + return r; + + union sockaddr_union sa = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_IP), + .ll.sll_ifindex = client->ifindex, + }; + + if (bind(fd, &sa.sa, sockaddr_ll_len(&sa.ll)) < 0) + return -errno; + + return TAKE_FD(fd); +} + +static int client_open_udp_socket(sd_dhcp_client *client) { + int r; + + assert(client); + assert(client->lease); + + _cleanup_close_ int fd = RET_NERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + if (fd < 0) + return fd; + + r = socket_bind_to_ifindex(fd, client->ifindex); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_TIMESTAMP, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, client->socket_priority); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_TOS, client->ip_service_type); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_FREEBIND, true); + if (r < 0) + return r; + + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(client->port), + .in.sin_addr.s_addr = client->lease->address, + }; + + if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) + return -errno; + + return TAKE_FD(fd); +} + +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)); + + /* When a socket fd is given externally, unconditionally use it. */ + if (client->socket_fd >= 0) + return client->socket_fd; + + 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); + + /* When the socket fd is given externally, the fd is used for both UDP and RAW packet operations. + * Hence, first we need to disable the previous event source, otherwise sd_event_add_io() will fail + * with -EEXIST. */ + if (fd == client->socket_fd) + client->receive_message = sd_event_source_disable_unref(client->receive_message); + + _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; + + /* When the socket fd is given externally, do not close it while we are running. The IO event source + * is freed when not necessary, hence the lifetime of the socket fd should not be tied to the one of + * the event source in that case. */ + if (fd != client->socket_fd) { + 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; +} + +static int client_send_raw( + sd_dhcp_client *client, + sd_dhcp_message *message, + bool expect_reply) { + + _cleanup_close_ int fd_close = -EBADF; + int r, fd; + + assert(client); + assert(message); + + fd = client_get_socket(client, AF_PACKET); + if (fd < 0) { + fd = fd_close = client_open_raw_socket(client); + if (fd < 0) + return fd; + } + + r = dhcp_message_send_raw( + message, + fd, + client->ifindex, + INADDR_ANY, + client->port, + &client->bcast_addr, + INADDR_BROADCAST, + client->server_port, + client->ip_service_type); + 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 && fd != client->socket_fd) + 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; +} + +static int client_send_udp( + sd_dhcp_client *client, + sd_dhcp_message *message, + bool expect_reply) { + + _cleanup_close_ int fd_close = -EBADF; + int r, fd; + + assert(client); + assert(message); + + if (!client->lease || client->lease->address == 0) + return -EADDRNOTAVAIL; + + fd = client_get_socket(client, AF_INET); + if (fd < 0) { + fd = fd_close = client_open_udp_socket(client); + if (fd < 0) + return fd; + } + + r = dhcp_message_send_udp( + message, + fd, + INADDR_ANY, + client->lease->server_address, + client->server_port); + 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 && fd != client->socket_fd) + 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; +} + +static int client_new_message(sd_dhcp_client *client, uint8_t type, sd_dhcp_message **ret) { + int r; + + assert(client); + assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE)); + assert(ret); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_new(&message); + if (r < 0) + return r; + + r = dhcp_message_init_header( + message, + BOOTREQUEST, + client->xid, + client->arp_type, + &client->hw_addr); + if (r < 0) + return r; + + /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers refuse to issue a DHCP lease + * if 'secs' is set to zero. */ + usec_t time_now; + r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now); + if (r < 0) + return r; + assert(time_now >= client->start_time); + + /* Seconds between sending first and last DISCOVER must always be strictly positive to deal with + * broken servers. */ + message->header.secs = usec_to_be16_sec(usec_sub_unsigned(time_now, client->start_time) ?: 1 * USEC_PER_SEC); + + /* RFC2131 section 4.1 + * A client that cannot receive unicast IP datagrams until its protocol software has been configured + * with an IP address SHOULD set the BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or + * DHCPREQUEST messages that client sends. The BROADCAST bit will provide a hint to the DHCP server + * and BOOTP relay agent to broadcast any messages to the client on the client's subnet. + * + * Note: some interfaces needs this to be enabled, but some networks need this to be disabled as + * broadcasts are filtered, so this needs to be configurable. */ + dhcp_message_set_broadcast_flag(message, client->request_broadcast || client->arp_type != ARPHRD_ETHER); + + /* We append no vendor options on BOOTP mode. */ + if (client->bootp) { + *ret = TAKE_PTR(message); + return 0; + } + + /* DHCP Message Type (53): Mandatory. */ + r = dhcp_message_append_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, type); + if (r < 0) + return r; + + /* Server Identifier (54): mandatory in DHCPREQUEST on REQUESTING state. It is also mandatory when + * DHCPRELEASE and DHCPDECLINE. */ + if ((type == DHCP_REQUEST && client->state == DHCP_STATE_REQUESTING) || + IN_SET(type, DHCP_RELEASE, DHCP_DECLINE)) { + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + ASSERT_PTR(client->lease)->server_address); + if (r < 0) + return r; + } + + /* Client Identifier (61): Not mandatory, but some DHCP servers will reject messages without client + * identifier option. Hence, we always set it. */ + r = dhcp_message_append_option_client_id(message, &client->client_id); + if (r < 0) + return r; + + /* Requested IP Address option (50) or ciaddr + * + * See RFC2131 section 4.3.2 (note that there is a typo in the RFC, SELECTING should be REQUESTING). */ + be32_t addr = INADDR_ANY; + switch (type) { + case DHCP_DISCOVER: + /* the client may suggest values for the network address and lease time in the DHCPDISCOVER + * message. The client may include the ’requested IP address’ option to suggest that a + * particular IP address be assigned, and may include the ’IP address lease time’ option to + * suggest the lease time it would like. + * + * RFC7844 section 3: + * SHOULD NOT contain any other option (when running on anonymize mode). */ + if (!client->anonymize) + addr = client->last_addr; + break; + + case DHCP_REQUEST: + switch (client->state) { + + case DHCP_STATE_REQUESTING: + /* ’ciaddr’ MUST be zero, ’requested IP address’ MUST be filled in with the + * yiaddr value from the chosen DHCPOFFER. */ + addr = ASSERT_PTR(client->lease)->address; + break; + + case DHCP_STATE_REBOOTING: + /* ’requested IP address’ option MUST be filled in with client’s notion of its + * previously assigned address. ’ciaddr’ MUST be zero. */ + addr = client->last_addr; + break; + + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + /* ’requested IP address’ option MUST NOT be filled in, ’ciaddr’ MUST be filled + * in with client’s IP address. */ + message->header.ciaddr = ASSERT_PTR(client->lease)->address; + break; + + default: + assert_not_reached(); + } + break; + + case DHCP_RELEASE: + /* The acquired address must be set in ciaddr. */ + message->header.ciaddr = ASSERT_PTR(client->lease)->address; + break; + + case DHCP_DECLINE: + /* The acquired address must be set in Requested IP Address option. */ + addr = ASSERT_PTR(client->lease)->address; + break; + + default: + assert_not_reached(); + } + + if (addr != INADDR_ANY) { + r = dhcp_message_append_option_be32(message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr); + if (r < 0) + return r; + } + + /* DHCPRELEASE and DHCPDECLINE MUST NOT contain any other options. */ + if (IN_SET(type, DHCP_RELEASE, DHCP_DECLINE)) { + *ret = TAKE_PTR(message); + return 0; + } + + /* Parameter Request List (55) + * + * RFC2131 section 3.5: + * in its initial DHCPDISCOVER or DHCPREQUEST message, a client may provide the server with a list of + * specific parameters the client is interested in. If the client includes a list of parameters in a + * DHCPDISCOVER message, it MUST include that list in any subsequent DHCPREQUEST messages. + * + * RFC7844 section 3: + * MAY contain the Parameter Request List option. + * + * RFC7844 section 3.6: + * The client intending to protect its privacy SHOULD only request a minimal number of options in the + * PRL and SHOULD also randomly shuffle the ordering of option codes in the PRL. If this random + * ordering cannot be implemented, the client MAY order the option codes in the PRL by option code + * number (lowest to highest). + * + * NOTE: using PRL options that Windows 10 RFC7844 implementation uses. */ + if (client->anonymize) { + static const uint8_t default_req_opts_anonymize[] = { + SD_DHCP_OPTION_SUBNET_MASK, /* 1 */ + SD_DHCP_OPTION_ROUTER, /* 3 */ + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, /* 6 */ + SD_DHCP_OPTION_DOMAIN_NAME, /* 15 */ + SD_DHCP_OPTION_ROUTER_DISCOVERY, /* 31 */ + SD_DHCP_OPTION_STATIC_ROUTE, /* 33 */ + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, /* 43 */ + SD_DHCP_OPTION_NETBIOS_NAME_SERVER, /* 44 */ + SD_DHCP_OPTION_NETBIOS_NODE_TYPE, /* 46 */ + SD_DHCP_OPTION_NETBIOS_SCOPE, /* 47 */ + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, /* 121 */ + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, /* 249 */ + SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, /* 252 */ + }; + + r = dhcp_message_append_option( + message, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, + ELEMENTSOF(default_req_opts_anonymize), + default_req_opts_anonymize); + if (r < 0) + return r; + + /* RFC7844 section 3: + * SHOULD NOT contain any other option (when running on anonymize mode). */ + *ret = TAKE_PTR(message); + return 0; + } + + /* When not on anonymized mode, use the default + user requested options. */ + r = dhcp_message_append_option_parameter_request_list(message, client->req_opts); + if (r < 0) + return r; + + /* Maximum Message Size (57) + * + * RFC2131 section 3.5: + * The client SHOULD include the ’maximum DHCP message size’ option to let the server know how + * large the server may make its DHCP messages. + * + * Note (from ConnMan): Some DHCP servers will send bigger DHCP packets than the defined default size + * unless the Maximum Message Size option is explicitly set. + * + * RFC3442 "Requirements to Avoid Sizing Constraints": + * Because a full routing table can be quite large, the standard 576 octet maximum size for a DHCP + * message may be too short to contain some legitimate Classless Static Route options. Because of + * this, clients implementing the Classless Static Route option SHOULD send a Maximum DHCP Message + * Size [4] option if the DHCP client's TCP/IP stack is capable of receiving larger IP datagrams. + * In this case, the client SHOULD set the value of this option to at least the MTU of the interface + * that the client is configuring. The client MAY set the value of this option higher, up to the size + * of the largest UDP packet it is prepared to accept. (Note that the value specified in the Maximum + * DHCP Message Size option is the total maximum packet size, including IP and UDP headers.) */ + r = dhcp_message_append_option_u16( + message, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, + CLAMP(client->mtu, (uint32_t) IPV4_MIN_REASSEMBLY_SIZE, (uint32_t) UINT16_MAX)); + if (r < 0) + return r; + + /* Hostname (12) or FQDN (81) + * + * Note, it is unclear from RFC 2131 if client should send hostname in DHCPDISCOVER but dhclient does + * and so we do as well. */ + r = dhcp_message_append_option_hostname( + message, + DHCP_FQDN_FLAG_S, /* Request server to perform A RR DNS updates */ + /* is_client= */ true, + client->hostname); + if (r < 0) + return r; + + /* Vendor Specific (43) */ + r = dhcp_message_append_option_sub_tlv( + message, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, + client->vendor_options); + if (r < 0) + return r; + + /* Vendor Class Identifier (60) */ + r = dhcp_message_append_option_string( + message, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, + client->vendor_class_identifier); + if (r < 0) + return r; + + /* User Class (77) */ + r = dhcp_message_append_option_length_prefixed_data( + message, + SD_DHCP_OPTION_USER_CLASS, + /* length_size= */ 1, + &client->user_class); + if (r < 0) + return r; + + /* Rapid Commit (80): only for DHCPDISCOVER */ + if (client->rapid_commit && type == DHCP_DISCOVER) { + r = dhcp_message_append_option_flag(message, SD_DHCP_OPTION_RAPID_COMMIT); + if (r < 0) + return r; + } + + /* MUD URL (161) */ + r = dhcp_message_append_option_string(message, SD_DHCP_OPTION_MUD_URL, client->mudurl); + if (r < 0) + return r; + + r = dhcp_message_append_option_tlv(message, client->extra_options); + if (r < 0) + return r; + + *ret = TAKE_PTR(message); + return 0; +} + +int dhcp_client_send_message(sd_dhcp_client *client, uint8_t type) { + int r; + + assert(client); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = client_new_message(client, type, &message); + if (r < 0) + return r; + + switch (type) { + case DHCP_DISCOVER: + r = client_send_raw(client, message, /* expect_reply= */ true); + break; + case DHCP_REQUEST: + if (client->state == DHCP_STATE_RENEWING) + r = client_send_udp(client, message, /* expect_reply= */ true); + else + r = client_send_raw(client, message, /* expect_reply= */ true); + break; + case DHCP_RELEASE: + r = client_send_udp(client, message, /* expect_reply= */ false); + break; + case DHCP_DECLINE: + r = client_send_raw(client, message, /* expect_reply= */ false); + break; + default: + r = -EINVAL; + } + if (r < 0) + return r; + + if (client->bootp) + log_dhcp_client(client, "BOOTREQUEST"); + else if (type == DHCP_REQUEST) + log_dhcp_client(client, "%s (%s)", + dhcp_message_type_to_string(type), + dhcp_state_to_string(client->state)); + else + log_dhcp_client(client, "%s", dhcp_message_type_to_string(type)); + 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..758b987727829 --- /dev/null +++ b/src/libsystemd-network/dhcp-client-send.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "dhcp-forward.h" + +int dhcp_client_send_message(sd_dhcp_client *client, uint8_t type); diff --git a/src/libsystemd-network/dhcp-duid-internal.h b/src/libsystemd-network/dhcp-duid-internal.h index eca98892791d2..bcb46c4f355ad 100644 --- a/src/libsystemd-network/dhcp-duid-internal.h +++ b/src/libsystemd-network/dhcp-duid-internal.h @@ -4,9 +4,8 @@ #include "sd-dhcp-duid.h" #include "sd-id128.h" +#include "dhcp-forward.h" #include "ether-addr-util.h" -#include "sd-forward.h" -#include "sparse-endian.h" #define SYSTEMD_PEN 43793 diff --git a/src/libsystemd-network/dhcp-forward.h b/src/libsystemd-network/dhcp-forward.h new file mode 100644 index 0000000000000..f83c58a3af9f2 --- /dev/null +++ b/src/libsystemd-network/dhcp-forward.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" +#include "sparse-endian.h" + +struct in_pktinfo; +struct iphdr; +struct udphdr; + +typedef struct sd_dhcp_client sd_dhcp_client; +typedef struct sd_dhcp_client_id sd_dhcp_client_id; +typedef struct sd_dhcp_duid sd_dhcp_duid; +typedef struct sd_dhcp_lease sd_dhcp_lease; +typedef struct sd_dhcp_message sd_dhcp_message; +typedef struct sd_dhcp_relay sd_dhcp_relay; +typedef struct sd_dhcp_relay_interface sd_dhcp_relay_interface; +typedef struct sd_dhcp_route sd_dhcp_route; +typedef struct sd_dhcp_server sd_dhcp_server; +typedef struct sd_dhcp_server_lease sd_dhcp_server_lease; + +typedef struct DHCPMessageHeader DHCPMessageHeader; +typedef struct DHCPRequest DHCPRequest; +typedef struct DHCPServerData DHCPServerData; + +typedef struct TLV TLV; diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index 744580fc48a88..12711a5846ad3 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -5,30 +5,19 @@ Copyright © 2013 Intel Corporation. All rights reserved. ***/ -#include "sd-dhcp-lease.h" - -#include "dhcp-client-id-internal.h" -#include "dhcp-option.h" -#include "sd-forward.h" -#include "list.h" - -struct sd_dhcp_route { - struct in_addr dst_addr; - struct in_addr gw_addr; - unsigned char dst_prefixlen; -}; +#include -struct sd_dhcp_raw_option { - LIST_FIELDS(struct sd_dhcp_raw_option, options); +#include "sd-dhcp-lease.h" - uint8_t tag; - uint8_t length; - void *data; -}; +#include "dhcp-forward.h" +#include "dhcp-message.h" +#include "time-util.h" struct sd_dhcp_lease { unsigned n_ref; + sd_dhcp_message *message; + /* each 0 if unset */ usec_t t1; usec_t t2; @@ -39,19 +28,12 @@ struct sd_dhcp_lease { /* each 0 if unset */ be32_t address; be32_t server_address; - be32_t next_server; - - bool have_subnet_mask; be32_t subnet_mask; - - bool have_broadcast; be32_t broadcast; struct in_addr *router; size_t router_size; - bool rapid_commit; - DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX]; sd_dns_resolver *dnr; @@ -66,16 +48,9 @@ struct sd_dhcp_lease { char *domainname; char **search_domains; - char *hostname; /* SD_DHCP_OPTION_HOST_NAME (12) */ - char *fqdn; /* SD_DHCP_OPTION_FQDN (81) */ - char *root_path; + char *hostname; char *captive_portal; - sd_dhcp_client_id client_id; - - void *vendor_specific; - size_t vendor_specific_len; - char *timezone; uint8_t sixrd_ipv4masklen; @@ -83,19 +58,10 @@ struct sd_dhcp_lease { struct in6_addr sixrd_prefix; struct in_addr *sixrd_br_addresses; size_t sixrd_n_br_addresses; - - LIST_HEAD(struct sd_dhcp_raw_option, private_options); }; int dhcp_lease_new(sd_dhcp_lease **ret); -int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata); -int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains); -int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len); - void dhcp_lease_set_timestamp(sd_dhcp_lease *lease, const triple_timestamp *timestamp); -int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease); -int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id *client_id); -#define dhcp_lease_unref_and_replace(a, b) \ - unref_and_replace_full(a, b, sd_dhcp_lease_ref, sd_dhcp_lease_unref) +int dhcp_client_parse_message(sd_dhcp_client *client, const struct iovec *iov, sd_dhcp_lease **ret); diff --git a/src/libsystemd-network/dhcp-message-dump.c b/src/libsystemd-network/dhcp-message-dump.c new file mode 100644 index 0000000000000..0314522231108 --- /dev/null +++ b/src/libsystemd-network/dhcp-message-dump.c @@ -0,0 +1,1180 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "ansi-color.h" +#include "arphrd-util.h" +#include "dhcp-message.h" +#include "dhcp-message-dump.h" +#include "dhcp-route.h" +#include "dns-resolver-internal.h" +#include "escape.h" +#include "ether-addr-util.h" +#include "format-table.h" +#include "hashmap.h" +#include "hexdecoct.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "parse-util.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" + +static int iovec_to_hex(const struct iovec *iov, char **ret) { + assert(iov); + assert(ret); + + _cleanup_free_ char *str = new(char, iov->iov_len * 3); + if (!str) + return log_oom(); + + char *p = str; + FOREACH_ARRAY(v, ((uint8_t*) iov->iov_base), iov->iov_len) { + if (p != str) + *p++ = ':'; + *p++ = hexchar(*v >> 4); + *p++ = hexchar(*v & 0x0f); + } + *p = '\0'; + + *ret = TAKE_PTR(str); + return 0; +} + +static int iovw_to_strv(const struct iovec_wrapper *iovw, char ***ret) { + int r; + + assert(iovw); + assert(ret); + + _cleanup_strv_free_ char **strv = NULL; + size_t n_strv = 0; + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + _cleanup_free_ char *escaped = cescape_length(iov->iov_base, iov->iov_len); + if (!escaped) + return log_oom(); + + r = strv_consume_with_size(&strv, &n_strv, TAKE_PTR(escaped)); + if (r < 0) + return log_oom(); + } + + *ret = TAKE_PTR(strv); + return 0; +} + +static void table_apply_flags(Table *table, DumpDHCPMessageFlag flags) { + assert(table); + + table_set_header(table, FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND)); + if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_FULL)) + table_set_width(table, 0); +} + +static int dump_dhcp_option_vendor_specific_information(sd_dhcp_message *m, DumpDHCPMessageFlag flags) { + int r; + + assert(m); + + _cleanup_(table_unrefp) Table *table = table_new("code", "data"); + if (!table) + return log_oom(); + + table_apply_flags(table, flags); + + (void) table_set_sort(table, (size_t) 0); + + TableCell *cell = table_get_cell(table, 0, 0); + if (!cell) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell."); + + (void) table_set_align_percent(table, cell, 100); + (void) table_set_ellipsize_percent(table, cell, 100); + + _cleanup_(tlv_unrefp) TLV *tlv = NULL; + r = dhcp_message_get_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, TLV_DHCP4_SUBOPTION, &tlv); + if (r < 0) + return log_error_errno(r, "Failed to read DHCP option %i: %m", SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION); + + void *tagp; + struct iovec_wrapper *iovw; + HASHMAP_FOREACH_KEY(iovw, tagp, tlv->entries) + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + _cleanup_free_ char *str = NULL; + r = iovec_to_hex(iov, &str); + if (r < 0) + return r; + + r = table_add_many( + table, + TABLE_UINT32, PTR_TO_UINT32(tagp), + TABLE_STRING, str); + if (r < 0) + return table_log_add_error(r); + } + + putchar('\n'); + if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND)) + printf("%s%sVendor-Specific Information:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal()); + + return table_print_or_warn(table); +} + +static int dump_dhcp_option_vendor_identifying_vendor_class(sd_dhcp_message *m, DumpDHCPMessageFlag flags) { + int r; + + assert(m); + + _cleanup_(table_unrefp) Table *table = table_new("enterprise-number", "data"); + if (!table) + return log_oom(); + + table_apply_flags(table, flags); + + (void) table_set_sort(table, (size_t) 0); + + TableCell *cell = table_get_cell(table, 0, 0); + if (!cell) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell."); + + (void) table_set_align_percent(table, cell, 100); + (void) table_set_ellipsize_percent(table, cell, 100); + + _cleanup_(tlv_unrefp) TLV *tlv = NULL; + r = dhcp_message_get_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS, TLV_DHCP4_VENDOR_IDENTIFYING_OPTION, &tlv); + if (r < 0) + return log_error_errno(r, "Failed to read DHCP option %i: %m", SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS); + + void *key, *value; + HASHMAP_FOREACH_KEY(value, key, tlv->entries) { + uint32_t enterprise_number = PTR_TO_UINT32(key); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = tlv_get_alloc(tlv, enterprise_number, &iov); + if (r < 0) + return log_error_errno(r, "Failed to read vendor class of enterprise number %"PRIu32": %m", enterprise_number); + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + r = iovec_split(&iov, /* length_size= */ 1, &iovw); + if (r < 0) + return log_error_errno(r, "Failed to parse vendor class of enterprise number %"PRIu32": %m", enterprise_number); + + _cleanup_strv_free_ char **strv = NULL; + r = iovw_to_strv(&iovw, &strv); + if (r < 0) + return r; + + r = table_add_many( + table, + TABLE_UINT32, enterprise_number, + TABLE_STRV, strv); + if (r < 0) + return table_log_add_error(r); + } + + putchar('\n'); + if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND)) + printf("%s%sVendor-Identifying Vendor Class:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal()); + + return table_print_or_warn(table); +} + +static int dump_dhcp_option_vendor_identifying_vendor_specific_information(sd_dhcp_message *m, DumpDHCPMessageFlag flags) { + int r; + + assert(m); + + _cleanup_(table_unrefp) Table *table = table_new("enterprise-number", "code", "data"); + if (!table) + return log_oom(); + + table_apply_flags(table, flags); + + (void) table_set_sort(table, (size_t) 0, (size_t) 1); + + for (unsigned i = 0; i <= 1; i++) { + TableCell *cell = table_get_cell(table, 0, i); + if (!cell) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell."); + + (void) table_set_align_percent(table, cell, 100); + (void) table_set_ellipsize_percent(table, cell, 100); + } + + _cleanup_(tlv_unrefp) TLV *tlv = NULL; + r = dhcp_message_get_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION, TLV_DHCP4_VENDOR_IDENTIFYING_OPTION, &tlv); + if (r < 0) + return log_error_errno(r, "Failed to read DHCP option %i: %m", SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION); + + void *key, *value; + HASHMAP_FOREACH_KEY(value, key, tlv->entries) { + uint32_t enterprise_number = PTR_TO_UINT32(key); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = tlv_get_alloc(tlv, enterprise_number, &iov); + if (r < 0) + return log_error_errno(r, "Failed to read vendor specific information of enterprise number %"PRIu32": %m", enterprise_number); + + _cleanup_(tlv_done) TLV sub_tlv = TLV_INIT(TLV_DHCP4_SUBOPTION); + r = tlv_parse(&sub_tlv, &iov); + if (r < 0) + return log_error_errno(r, "Failed to parse vendor specific information of enterprise number %"PRIu32": %m", enterprise_number); + + void *tagp; + struct iovec_wrapper *iovw; + HASHMAP_FOREACH_KEY(iovw, tagp, sub_tlv.entries) { + uint32_t code = PTR_TO_UINT32(tagp); + + FOREACH_ARRAY(i, iovw->iovec, iovw->count) { + _cleanup_free_ char *str = NULL; + r = iovec_to_hex(i, &str); + if (r < 0) + return r; + + r = table_add_many( + table, + TABLE_UINT32, enterprise_number, + TABLE_UINT32, code, + TABLE_STRING, str); + if (r < 0) + return table_log_add_error(r); + } + } + } + + putchar('\n'); + if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND)) + printf("%s%sVendor-Identifying Vendor-Specific Information:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal()); + + return table_print_or_warn(table); +} + +typedef enum DHCPOptionType { + DHCP_OPTION_TYPE_AUTO, + DHCP_OPTION_TYPE_HEX, + DHCP_OPTION_TYPE_FLAG, + DHCP_OPTION_TYPE_BOOL, + DHCP_OPTION_TYPE_UINT8, + DHCP_OPTION_TYPE_UINT16, + DHCP_OPTION_TYPE_TIME, + DHCP_OPTION_TYPE_STRING, + DHCP_OPTION_TYPE_ADDRESS, + _DHCP_OPTION_TYPE_AUTO_MAX, + DHCP_OPTION_TYPE_SIP = _DHCP_OPTION_TYPE_AUTO_MAX, + DHCP_OPTION_TYPE_FQDN, + DHCP_OPTION_TYPE_ROUTE, + DHCP_OPTION_TYPE_LENGTH_PREFIXED_DATA, + DHCP_OPTION_TYPE_SEARCH_DOMAINS, + DHCP_OPTION_TYPE_DNR, + DHCP_OPTION_TYPE_6RD, + DHCP_OPTION_TYPE_TBD, + _DHCP_OPTION_TYPE_MAX, + _DHCP_OPTION_TYPE_INVALID = -EINVAL, +} DHCPOptionType; + +static const char * const dhcp_option_type_table[_DHCP_OPTION_TYPE_AUTO_MAX] = { + [DHCP_OPTION_TYPE_AUTO] = "auto", + [DHCP_OPTION_TYPE_HEX] = "hex", + [DHCP_OPTION_TYPE_FLAG] = "flag", + [DHCP_OPTION_TYPE_BOOL] = "bool", + [DHCP_OPTION_TYPE_UINT8] = "uint8", + [DHCP_OPTION_TYPE_UINT16] = "uint16", + [DHCP_OPTION_TYPE_TIME] = "time", + [DHCP_OPTION_TYPE_STRING] = "string", + [DHCP_OPTION_TYPE_ADDRESS] = "address", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(dhcp_option_type, DHCPOptionType); + +static DHCPOptionType dhcp_option_type_from_code(uint8_t code) { + switch (code) { + case SD_DHCP_OPTION_PAD: + return -EINVAL; + case SD_DHCP_OPTION_SUBNET_MASK: + case SD_DHCP_OPTION_ROUTER: + case SD_DHCP_OPTION_TIME_SERVER: + case SD_DHCP_OPTION_NAME_SERVER: + case SD_DHCP_OPTION_DOMAIN_NAME_SERVER: + case SD_DHCP_OPTION_LOG_SERVER: + case SD_DHCP_OPTION_QUOTES_SERVER: + case SD_DHCP_OPTION_LPR_SERVER: + case SD_DHCP_OPTION_IMPRESS_SERVER: + case SD_DHCP_OPTION_RLP_SERVER: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_HOST_NAME: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_BOOT_FILE_SIZE: + return DHCP_OPTION_TYPE_UINT16; + case SD_DHCP_OPTION_MERIT_DUMP_FILE: + case SD_DHCP_OPTION_DOMAIN_NAME: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_SWAP_SERVER: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_ROOT_PATH: + case SD_DHCP_OPTION_EXTENSION_FILE: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_FORWARD: + case SD_DHCP_OPTION_SOURCE_ROUTE: + return DHCP_OPTION_TYPE_BOOL; + case SD_DHCP_OPTION_MAX_DATAGRAM_ASSEMBLY: + return DHCP_OPTION_TYPE_UINT16; + case SD_DHCP_OPTION_DEFAULT_IP_TTL: + return DHCP_OPTION_TYPE_UINT8; + case SD_DHCP_OPTION_MTU_TIMEOUT: + return DHCP_OPTION_TYPE_TIME; + case SD_DHCP_OPTION_MTU_INTERFACE: + return DHCP_OPTION_TYPE_UINT16; + case SD_DHCP_OPTION_MTU_SUBNET: + return DHCP_OPTION_TYPE_BOOL; + case SD_DHCP_OPTION_BROADCAST: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_MASK_DISCOVERY: + case SD_DHCP_OPTION_MASK_SUPPLIER: + case SD_DHCP_OPTION_ROUTER_DISCOVERY: + return DHCP_OPTION_TYPE_BOOL; + case SD_DHCP_OPTION_ROUTER_REQUEST: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_STATIC_ROUTE: + return DHCP_OPTION_TYPE_ROUTE; + case SD_DHCP_OPTION_TRAILERS: + return DHCP_OPTION_TYPE_BOOL; + case SD_DHCP_OPTION_ARP_TIMEOUT: + return DHCP_OPTION_TYPE_TIME; + case SD_DHCP_OPTION_ETHERNET: + return DHCP_OPTION_TYPE_BOOL; + case SD_DHCP_OPTION_DEFAULT_TCP_TTL: + return DHCP_OPTION_TYPE_UINT8; + case SD_DHCP_OPTION_KEEPALIVE_TIME: + return DHCP_OPTION_TYPE_TIME; + case SD_DHCP_OPTION_KEEPALIVE_DATA: + return DHCP_OPTION_TYPE_BOOL; + case SD_DHCP_OPTION_NIS_DOMAIN: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_NIS_SERVER: + case SD_DHCP_OPTION_NTP_SERVER: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION: + return DHCP_OPTION_TYPE_TBD; + case SD_DHCP_OPTION_NETBIOS_NAME_SERVER: + case SD_DHCP_OPTION_NETBIOS_DIST_SERVER: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_NETBIOS_NODE_TYPE: + return DHCP_OPTION_TYPE_UINT8; + case SD_DHCP_OPTION_NETBIOS_SCOPE: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_X_WINDOW_FONT: + case SD_DHCP_OPTION_X_WINDOW_MANAGER: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: + return DHCP_OPTION_TYPE_TIME; + case SD_DHCP_OPTION_OVERLOAD: + case SD_DHCP_OPTION_MESSAGE_TYPE: + return DHCP_OPTION_TYPE_UINT8; + case SD_DHCP_OPTION_SERVER_IDENTIFIER: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_ERROR_MESSAGE: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE: + return DHCP_OPTION_TYPE_UINT16; + case SD_DHCP_OPTION_RENEWAL_TIME: + case SD_DHCP_OPTION_REBINDING_TIME: + return DHCP_OPTION_TYPE_TIME; + case SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_NETWARE_IP_DOMAIN: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_NIS_DOMAIN_NAME: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_NIS_SERVER_ADDR: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_BOOT_SERVER_NAME: + case SD_DHCP_OPTION_BOOT_FILENAME: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_HOME_AGENT_ADDRESS: + case SD_DHCP_OPTION_SMTP_SERVER: + case SD_DHCP_OPTION_POP3_SERVER: + case SD_DHCP_OPTION_NNTP_SERVER: + case SD_DHCP_OPTION_WWW_SERVER: + case SD_DHCP_OPTION_FINGER_SERVER: + case SD_DHCP_OPTION_IRC_SERVER: + case SD_DHCP_OPTION_STREETTALK_SERVER: + case SD_DHCP_OPTION_STDA_SERVER: + return DHCP_OPTION_TYPE_ADDRESS; + case SD_DHCP_OPTION_USER_CLASS: + return DHCP_OPTION_TYPE_LENGTH_PREFIXED_DATA; + case SD_DHCP_OPTION_RAPID_COMMIT: + return DHCP_OPTION_TYPE_FLAG; + case SD_DHCP_OPTION_FQDN: + return DHCP_OPTION_TYPE_FQDN; + case SD_DHCP_OPTION_POSIX_TIMEZONE: + case SD_DHCP_OPTION_TZDB_TIMEZONE: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_IPV6_ONLY_PREFERRED: + return DHCP_OPTION_TYPE_TIME; + case SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_DOMAIN_SEARCH: + return DHCP_OPTION_TYPE_SEARCH_DOMAINS; + case SD_DHCP_OPTION_SIP_SERVER: + return DHCP_OPTION_TYPE_SIP; + case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: + return DHCP_OPTION_TYPE_ROUTE; + case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS: + case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION: + return DHCP_OPTION_TYPE_TBD; + case SD_DHCP_OPTION_MUD_URL: + return DHCP_OPTION_TYPE_STRING; + case SD_DHCP_OPTION_V4_DNR: + return DHCP_OPTION_TYPE_DNR; + case SD_DHCP_OPTION_6RD: + return DHCP_OPTION_TYPE_6RD; + case SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE: + return DHCP_OPTION_TYPE_ROUTE; + case SD_DHCP_OPTION_END: + return -EINVAL; + default: + return DHCP_OPTION_TYPE_HEX; + } +} + +static int dump_dhcp_option_hex(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return log_error_errno(r, "Failed to read DHCP option %u: %m", code); + + _cleanup_free_ char *str = NULL; + r = iovec_to_hex(&iov, &str); + if (r < 0) + return r; + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRING, str); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_flag(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + r = dhcp_message_get_option_flag(message, code); + if (r < 0) { + if (fallback) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + return log_error_errno(r, "Failed to dump DHCP option %u as flag: %m", code); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_BOOLEAN, true); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_bool(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + uint8_t u; + r = dhcp_message_get_option_u8(message, code, &u); + if (r < 0) { + if (fallback) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + return log_error_errno(r, "Failed to read option %u as boolean: %m", code); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_BOOLEAN, !!u); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_uint8(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + uint8_t u; + r = dhcp_message_get_option_u8(message, code, &u); + if (r < 0) { + if (fallback) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + return log_error_errno(r, "Failed to read option %u as uint8: %m", code); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_UINT8, u); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_uint16(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + uint16_t u; + r = dhcp_message_get_option_u16(message, code, &u); + if (r < 0) { + if (fallback) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + return log_error_errno(r, "Failed to read option %u as uint16: %m", code); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_UINT16, u); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_time(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + usec_t usec; + r = dhcp_message_get_option_sec(message, code, /* max_as_infinity= */ true, &usec); + if (r < 0) { + if (fallback) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + return log_error_errno(r, "Failed to read option %u as time: %m", code); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_TIMESPAN, usec); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_string(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + _cleanup_free_ char *str = NULL; + r = dhcp_message_get_option_string(message, code, &str); + if (r < 0) { + if (fallback) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + return log_error_errno(r, "Failed to read option %u as string: %m", code); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRING, str); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_address(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + _cleanup_free_ struct in_addr *addrs = NULL; + size_t n_addrs; + r = dhcp_message_get_option_addresses(message, code, &n_addrs, &addrs); + if (r < 0) { + if (fallback) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + return log_error_errno(r, "Failed to read option %u as address: %m", code); + } + + _cleanup_strv_free_ char **strv = NULL; + size_t n_strv = 0; + FOREACH_ARRAY(a, addrs, n_addrs) { + r = strv_extend_with_size(&strv, &n_strv, IN4_ADDR_TO_STRING(a)); + if (r < 0) + return log_oom(); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRV, strv); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_sip(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + assert(code == SD_DHCP_OPTION_SIP_SERVER); + + _cleanup_strv_free_ char **strv = NULL; + if (dhcp_message_get_option_domains(message, code, &strv) < 0) + return dump_dhcp_option_address(table, message, code, /* fallback= */ true); + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRV, strv); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_fqdn(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + assert(code == SD_DHCP_OPTION_FQDN); + + _cleanup_free_ char *fqdn = NULL; + uint8_t flags; + if (dhcp_message_get_option_fqdn(message, &flags, &fqdn) < 0) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code)); + if (r < 0) + return table_log_add_error(r); + + r = table_add_cell_stringf(table, /* ret_cell= */ NULL, "flags: 0x%x, fqdn: %s", flags, fqdn); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_route(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + _cleanup_free_ sd_dhcp_route *routes = NULL; + size_t n_routes; + if (dhcp_message_get_option_routes(message, code, &n_routes, &routes) < 0) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + + _cleanup_strv_free_ char **strv = NULL; + size_t n_strv = 0; + FOREACH_ARRAY(route, routes, n_routes) { + r = strv_extendf_with_size(&strv, &n_strv, "%s via %s", + IN4_ADDR_PREFIX_TO_STRING(&route->dst_addr, route->dst_prefixlen), + IN4_ADDR_TO_STRING(&route->gw_addr)); + if (r < 0) + return log_oom(); + } + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRV, strv); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_length_prefixed_data(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + if (dhcp_message_get_option_length_prefixed_data(message, code, /* length_size= */ 1, &iovw) < 0) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + + _cleanup_strv_free_ char **strv = NULL; + r = iovw_to_strv(&iovw, &strv); + if (r < 0) + return r; + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRV, strv); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_tbd(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + + if (!dhcp_message_has_option(message, code)) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "The DHCP message does not have option %u.", code); + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRING, "See below."); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_search_domains(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + assert(code == SD_DHCP_OPTION_DOMAIN_SEARCH); + + _cleanup_strv_free_ char **strv = NULL; + if (dhcp_message_get_option_domains(message, code, &strv) < 0) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRV, strv); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_dnr(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + assert(code == SD_DHCP_OPTION_V4_DNR); + + sd_dns_resolver *resolvers = NULL; + size_t n_resolvers = 0; + CLEANUP_ARRAY(resolvers, n_resolvers, dns_resolver_free_array); + + if (dhcp_message_get_option_dnr(message, &n_resolvers, &resolvers) < 0) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + + _cleanup_strv_free_ char **strv = NULL; + r = dns_resolvers_to_dot_strv(resolvers, n_resolvers, &strv); + if (r < 0) + return log_oom(); + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code), + TABLE_STRV, strv); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_6rd(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback) { + int r; + + assert(table); + assert(message); + assert(code == SD_DHCP_OPTION_6RD); + + uint8_t ipv4masklen, prefixlen; + struct in6_addr prefix; + size_t n_br_addresses; + _cleanup_free_ struct in_addr *br_addresses = NULL; + if (dhcp_message_get_option_6rd(message, &ipv4masklen, &prefixlen, &prefix, &n_br_addresses, &br_addresses) < 0) + return dump_dhcp_option_hex(table, message, code, /* fallback= */ false); + + _cleanup_free_ char *str = asprintf_safe("ipv4masklen: %u, prefix: %s, br_addresses: ", + ipv4masklen, IN6_ADDR_PREFIX_TO_STRING(&prefix, prefixlen)); + if (!str) + return log_oom(); + + assert(n_br_addresses > 0); + _cleanup_free_ char *br_addresses_str = NULL; + FOREACH_ARRAY(a, br_addresses, n_br_addresses) + if (!strextend_with_separator(&br_addresses_str, ", ", IN4_ADDR_TO_STRING(a))) + return log_oom(); + + r = table_add_many( + table, + TABLE_UINT8, code, + TABLE_STRING, dhcp_option_code_to_string(code)); + if (r < 0) + return table_log_add_error(r); + + r = table_add_cell_stringf(table, /* ret_cell= */ NULL, "%s%s", str, br_addresses_str); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_option_one(Table *table, sd_dhcp_message *message, uint8_t code, DHCPOptionType type) { + assert(table); + assert(message); + assert(!IN_SET(code, SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END)); + + typedef int (*dump_dhcp_option_t)(Table *table, sd_dhcp_message *message, uint8_t code, bool fallback); + + static const dump_dhcp_option_t functions[_DHCP_OPTION_TYPE_MAX] = { + [DHCP_OPTION_TYPE_HEX] = dump_dhcp_option_hex, + [DHCP_OPTION_TYPE_FLAG] = dump_dhcp_option_flag, + [DHCP_OPTION_TYPE_BOOL] = dump_dhcp_option_bool, + [DHCP_OPTION_TYPE_UINT8] = dump_dhcp_option_uint8, + [DHCP_OPTION_TYPE_UINT16] = dump_dhcp_option_uint16, + [DHCP_OPTION_TYPE_TIME] = dump_dhcp_option_time, + [DHCP_OPTION_TYPE_STRING] = dump_dhcp_option_string, + [DHCP_OPTION_TYPE_ADDRESS] = dump_dhcp_option_address, + [DHCP_OPTION_TYPE_SIP] = dump_dhcp_option_sip, + [DHCP_OPTION_TYPE_FQDN] = dump_dhcp_option_fqdn, + [DHCP_OPTION_TYPE_ROUTE] = dump_dhcp_option_route, + [DHCP_OPTION_TYPE_LENGTH_PREFIXED_DATA] = dump_dhcp_option_length_prefixed_data, + [DHCP_OPTION_TYPE_SEARCH_DOMAINS] = dump_dhcp_option_search_domains, + [DHCP_OPTION_TYPE_DNR] = dump_dhcp_option_dnr, + [DHCP_OPTION_TYPE_6RD] = dump_dhcp_option_6rd, + [DHCP_OPTION_TYPE_TBD] = dump_dhcp_option_tbd, + }; + + bool fallback = false; + if (type == DHCP_OPTION_TYPE_AUTO) { + type = dhcp_option_type_from_code(code); + if (type < 0) + return type; + fallback = true; + } + + assert(functions[type]); + return functions[type](table, message, code, fallback); +} + +static int parse_arg(const char *arg, uint8_t *ret_code, DHCPOptionType *ret_type) { + _cleanup_free_ char *buf = NULL; + const char *code_str, *type_str; + int r; + + assert(arg); + + const char *colon = strchr(arg, ':'); + if (colon) { + buf = strndup(arg, colon - arg); + if (!buf) + return log_oom(); + + code_str = buf; + type_str = colon + 1; + } else { + code_str = arg; + type_str = NULL; + } + + uint8_t code; + r = safe_atou8(code_str, &code); + if (r < 0) + return log_error_errno(r, "Failed to parse option code number '%s': %m", code_str); + + if (IN_SET(code, SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid option code number: %u", code); + + DHCPOptionType type = DHCP_OPTION_TYPE_AUTO; + if (type_str) { + type = dhcp_option_type_from_string(type_str); + if (type < 0) + return log_error_errno(type, "Failed to parse option type '%s': %m", type_str); + } + + if (ret_code) + *ret_code = code; + if (ret_type) + *ret_type = type; + return 0; +} + +static int dump_dhcp_options(sd_dhcp_message *message, char * const *args, DumpDHCPMessageFlag flags) { + int r; + + assert(message); + + _cleanup_(table_unrefp) Table *table = table_new("code", "name", "data"); + if (!table) + return log_oom(); + + table_apply_flags(table, flags); + + (void) table_set_sort(table, (size_t) 0); + + TableCell *cell = table_get_cell(table, 0, 0); + if (!cell) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell."); + + (void) table_set_align_percent(table, cell, 100); + (void) table_set_ellipsize_percent(table, cell, 100); + + bool + has_vendor_specific_information = false, + has_vendor_identifying_vendor_class = false, + has_vendor_identifying_vendor_specific_information = false; + + if (strv_isempty(args)) { + void *tagp; + struct iovec_wrapper *iovw; + HASHMAP_FOREACH_KEY(iovw, tagp, message->options.entries) { + uint32_t tag = PTR_TO_UINT32(tagp); + assert(tag > 0); + assert(tag < UINT8_MAX); + + r = dump_dhcp_option_one(table, message, tag, DHCP_OPTION_TYPE_AUTO); + if (r < 0) + return r; + + switch (tag) { + case SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION: + has_vendor_specific_information = true; + break; + case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS: + has_vendor_identifying_vendor_class = true; + break; + case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION: + has_vendor_identifying_vendor_specific_information = true; + break; + } + } + } else + STRV_FOREACH(arg, args) { + uint8_t code = SD_DHCP_OPTION_PAD; /* avoid false maybe-uninitialized warning */ + DHCPOptionType type = DHCP_OPTION_TYPE_AUTO; /* avoid false maybe-uninitialized warning */ + r = parse_arg(*arg, &code, &type); + if (r < 0) + return r; + assert(!IN_SET(code, SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END)); + + r = dump_dhcp_option_one(table, message, code, type); + if (r < 0) + return r; + + if (type == DHCP_OPTION_TYPE_AUTO) + switch (code) { + case SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION: + has_vendor_specific_information = true; + break; + case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS: + has_vendor_identifying_vendor_class = true; + break; + case SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION: + has_vendor_identifying_vendor_specific_information = true; + break; + } + } + + if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND)) + printf("%s%sOptions:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal()); + + r = table_print_or_warn(table); + if (r < 0) + return r; + + if (has_vendor_specific_information) { + r = dump_dhcp_option_vendor_specific_information(message, flags); + if (r < 0) + return r; + } + + if (has_vendor_identifying_vendor_class) { + r = dump_dhcp_option_vendor_identifying_vendor_class(message, flags); + if (r < 0) + return r; + } + + if (has_vendor_identifying_vendor_specific_information) { + r = dump_dhcp_option_vendor_identifying_vendor_specific_information(message, flags); + if (r < 0) + return r; + } + + return 0; +} + +static int dump_buffer(Table *table, const char *field, uint8_t *buf, size_t len) { + int r; + + assert(table); + assert(field); + assert(buf); + assert(len > 0); + + uint8_t *nul = memchr(buf, 0, len); + if (nul) + len = nul - buf; + + if (len == 0) + return 0; + + _cleanup_free_ char *str = NULL; + r = make_cstring(buf, len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &str); + if (r < 0) + return log_error_errno(r, "Failed to parse buffer for field '%s': %m", field); + + if (isempty(str)) + return 0; + + r = table_add_many( + table, + TABLE_FIELD, field, + TABLE_STRING, str); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static int dump_dhcp_header(sd_dhcp_message *message, DumpDHCPMessageFlag flags) { + int r; + + assert(message); + + _cleanup_(table_unrefp) Table *table = table_new_vertical(); + if (!table) + return log_oom(); + + table_apply_flags(table, flags); + + TableCell *cell = table_get_cell(table, 0, 0); + if (!cell) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get table cell."); + + (void) table_set_align_percent(table, cell, 100); + (void) table_set_ellipsize_percent(table, cell, 100); + + struct hw_addr_data hw_addr; + r = dhcp_message_get_hw_addr(message, &hw_addr); + if (r < 0) + return log_error_errno(r, "Failed to get hardware address from DHCP message: %m"); + + struct in_addr yiaddr = { .s_addr = message->header.yiaddr }; + + r = table_add_many( + table, + TABLE_FIELD, "Hardware Type", + TABLE_STRING, arphrd_to_name(message->header.htype), + TABLE_FIELD, "Hardware Address", + TABLE_STRING, HW_ADDR_TO_STR(&hw_addr), + TABLE_FIELD, "Client Address", + TABLE_IN_ADDR, &yiaddr); + if (r < 0) + return table_log_add_error(r); + + if (message->header.siaddr != INADDR_ANY) { + struct in_addr siaddr = { .s_addr = message->header.siaddr }; + + r = table_add_many( + table, + TABLE_FIELD, "Server Address", + TABLE_IN_ADDR, &siaddr); + if (r < 0) + return table_log_add_error(r); + } + + if (message->header.giaddr != INADDR_ANY) { + struct in_addr giaddr = { .s_addr = message->header.giaddr }; + + r = table_add_many( + table, + TABLE_FIELD, "Relay Agent Address", + TABLE_IN_ADDR, &giaddr); + if (r < 0) + return table_log_add_error(r); + } + + uint8_t overload = DHCP_OVERLOAD_NONE; + (void) dhcp_message_get_option_u8(message, SD_DHCP_OPTION_OVERLOAD, &overload); + + if (!FLAGS_SET(overload, DHCP_OVERLOAD_SNAME)) { + r = dump_buffer(table, "Server Host Name", message->header.sname, sizeof(message->header.sname)); + if (r < 0) + return r; + } + + if (!FLAGS_SET(overload, DHCP_OVERLOAD_FILE)) { + r = dump_buffer(table, "Boot File Name", message->header.file, sizeof(message->header.file)); + if (r < 0) + return r; + } + + if (FLAGS_SET(flags, DUMP_DHCP_MESSAGE_LEGEND)) + printf("%s%sHeader:%s\n", ansi_highlight(), ansi_add_underline(), ansi_normal()); + + return table_print_or_warn(table); +} + +int dump_dhcp_message(sd_dhcp_message *m, char * const *args, DumpDHCPMessageFlag flags) { + int r; + + assert(m); + + if (strv_isempty(args)) { + r = dump_dhcp_header(m, flags); + if (r < 0) + return r; + + putchar('\n'); + } + + return dump_dhcp_options(m, args, flags); +} diff --git a/src/libsystemd-network/dhcp-message-dump.h b/src/libsystemd-network/dhcp-message-dump.h new file mode 100644 index 0000000000000..34eb261f1bbbb --- /dev/null +++ b/src/libsystemd-network/dhcp-message-dump.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "dhcp-forward.h" + +typedef enum { + DUMP_DHCP_MESSAGE_LEGEND = 1 << 0, + DUMP_DHCP_MESSAGE_FULL = 1 << 1, +} DumpDHCPMessageFlag; + +int dump_dhcp_message(sd_dhcp_message *message, char * const *args, DumpDHCPMessageFlag flags); diff --git a/src/libsystemd-network/dhcp-message.c b/src/libsystemd-network/dhcp-message.c new file mode 100644 index 0000000000000..732a52c08b5b5 --- /dev/null +++ b/src/libsystemd-network/dhcp-message.c @@ -0,0 +1,1794 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "dhcp-client-id-internal.h" +#include "dhcp-message.h" +#include "dhcp-route.h" +#include "dns-def.h" +#include "dns-domain.h" +#include "dns-resolver-internal.h" +#include "errno-util.h" +#include "ether-addr-util.h" +#include "hostname-util.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "ip-util.h" +#include "json-util.h" +#include "network-common.h" +#include "set.h" +#include "socket-util.h" +#include "sort-util.h" +#include "string-util.h" +#include "unaligned.h" + +static sd_dhcp_message* dhcp_message_free(sd_dhcp_message *message) { + if (!message) + return NULL; + + tlv_done(&message->options); + return mfree(message); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_message, sd_dhcp_message, dhcp_message_free); + +int dhcp_message_new(sd_dhcp_message **ret) { + assert(ret); + + sd_dhcp_message *message = new(sd_dhcp_message, 1); + if (!message) + return -ENOMEM; + + *message = (sd_dhcp_message) { + .n_ref = 1, + .options = TLV_INIT(TLV_DHCP4), + }; + + *ret = TAKE_PTR(message); + return 0; +} + +int dhcp_message_init_header( + sd_dhcp_message *message, + uint8_t op, + uint32_t xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr) { + + assert(message); + assert(IN_SET(op, BOOTREQUEST, BOOTREPLY)); + + /* RFC 2131 section 4.1.1: + * The client MUST include its hardware address in the ’chaddr’ field, if necessary for delivery of + * DHCP reply messages. + * + * RFC 4390 section 2.1: + * A DHCP client, when working over an IPoIB interface, MUST follow the following rules: + * "htype" (hardware address type) MUST be 32 [ARPPARAM]. + * "hlen" (hardware address length) MUST be 0. + * "chaddr" (client hardware address) field MUST be zeroed. + * + * Note, the maximum hardware address length (HW_ADDR_MAX_SIZE) is 32, but the size of the chaddr + * field is 16. + * + * Also, ARP type is 2 bytes, but the htype field is 1 byte. */ + + if (arp_type == ARPHRD_INFINIBAND) + hw_addr = NULL; + + if (hw_addr && hw_addr->length > sizeof_field(DHCPMessageHeader, chaddr)) + return -EINVAL; + + message->header = (DHCPMessageHeader) { + .op = op, + .htype = arp_type <= UINT8_MAX ? arp_type : 0, + .hlen = hw_addr ? hw_addr->length : 0, + .xid = htobe32(xid), + .magic = htobe32(DHCP_MAGIC_COOKIE), + }; + + if (hw_addr) + memcpy_safe(message->header.chaddr, hw_addr->bytes, hw_addr->length); + return 0; +} + +void dhcp_message_set_broadcast_flag(sd_dhcp_message *message, bool b) { + assert(message); + + SET_FLAG(message->header.flags, htobe16(0x8000), b); +} + +bool dhcp_message_has_broadcast_flag(sd_dhcp_message *message) { + assert(message); + + return FLAGS_SET(message->header.flags, htobe16(0x8000)); +} + +int dhcp_message_get_hw_addr(sd_dhcp_message *message, struct hw_addr_data *ret) { + assert(message); + assert(ret); + + if (message->header.hlen > sizeof_field(DHCPMessageHeader, chaddr)) + return -EBADMSG; + + ret->length = message->header.hlen; + memcpy_safe(ret->bytes, message->header.chaddr, message->header.hlen); + return 0; +} + +bool dhcp_message_has_option(sd_dhcp_message *message, uint8_t code) { + assert(message); + return tlv_contains(&message->options, code); +} + +void dhcp_message_remove_option(sd_dhcp_message *message, uint8_t code) { + assert(message); + tlv_remove(&message->options, code); +} + +int dhcp_message_append_option(sd_dhcp_message *message, uint8_t code, size_t length, const void *data) { + assert(message); + return tlv_append(&message->options, code, length, data); +} + +int dhcp_message_append_option_tlv(sd_dhcp_message *message, const TLV *tlv) { + assert(message); + return tlv_append_tlv(&message->options, tlv); +} + +int dhcp_message_append_option_flag(sd_dhcp_message *message, uint8_t code) { + assert(message); + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + return dhcp_message_append_option(message, code, /* length= */ 0, /* data= */ NULL); +} + +int dhcp_message_append_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t data) { + assert(message); + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + return dhcp_message_append_option(message, code, sizeof(uint8_t), &data); +} + +int dhcp_message_append_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t data) { + assert(message); + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + be16_t b = htobe16(data); + return dhcp_message_append_option(message, code, sizeof(be16_t), &b); +} + +int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32_t data) { + assert(message); + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + return dhcp_message_append_option(message, code, sizeof(be32_t), &data); +} + +int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_t usec) { + assert(message); + return dhcp_message_append_option_be32(message, code, usec_to_be32_sec(usec)); +} + +int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, const struct in_addr *addr) { + assert(message); + assert(addr); + return dhcp_message_append_option_be32(message, code, addr->s_addr); +} + +int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, size_t n_addr, const struct in_addr *addr) { + assert(message); + assert(n_addr == 0 || addr); + + if (n_addr == 0) + return 0; + + if (size_multiply_overflow(sizeof(struct in_addr), n_addr)) + return -ENOBUFS; + + if (code == SD_DHCP_OPTION_SIP_SERVER) { + if (dhcp_message_has_option(message, SD_DHCP_OPTION_SIP_SERVER)) + return -EEXIST; + + size_t len = size_add(1, sizeof(struct in_addr) * n_addr); + if (len == SIZE_MAX) + return -ENOBUFS; + + _cleanup_free_ uint8_t *buf = new(uint8_t, len); + if (!buf) + return -ENOMEM; + + buf[0] = 1; /* 'enc' field, 0: domains, 1: addresses */ + memcpy(buf + 1, addr, sizeof(struct in_addr) * n_addr); + + return dhcp_message_append_option(message, code, len, buf); + } + + return dhcp_message_append_option(message, code, sizeof(struct in_addr) * n_addr, addr); +} + +int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, const char *data) { + assert(message); + + if (isempty(data)) + return 0; + + if (!string_is_safe(data, STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS)) + return -EINVAL; + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + return dhcp_message_append_option(message, code, strlen(data), data); +} + +static int dhcp_message_append_option_static_routes(sd_dhcp_message *message, size_t n_routes, const sd_dhcp_route *routes) { + int r; + + assert(message); + assert(routes || n_routes == 0); + + if (n_routes == 0) + return 0; + + if (size_multiply_overflow(2 * sizeof(struct in_addr), n_routes)) + return -ENOBUFS; + + _cleanup_free_ struct in_addr *buf = new(struct in_addr, 2 * n_routes); + if (!buf) + return -ENOMEM; + + size_t count = 0; + FOREACH_ARRAY(route, routes, n_routes) { + uint8_t prefixlen; + r = in4_addr_default_prefixlen(&route->dst_addr, &prefixlen); + if (r < 0) + return r; + + if (prefixlen != route->dst_prefixlen) + return -EINVAL; + + struct in_addr dst = route->dst_addr; + (void) in4_addr_mask(&dst, prefixlen); + + buf[count++] = dst; + buf[count++] = route->gw_addr; + } + + assert(count == 2 * n_routes); + + return dhcp_message_append_option_addresses(message, SD_DHCP_OPTION_STATIC_ROUTE, 2 * n_routes, buf); +} + +static int dhcp_message_append_option_classless_static_routes(sd_dhcp_message *message, uint8_t code, size_t n_routes, const sd_dhcp_route *routes) { + assert(message); + assert(routes || n_routes == 0); + assert(IN_SET(code, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE)); + + if (n_routes == 0) + return 0; + + if (size_multiply_overflow(1 + 2 * sizeof(struct in_addr), n_routes)) + return -ENOBUFS; + + _cleanup_free_ uint8_t *buf = new(uint8_t, (1 + 2 * sizeof(struct in_addr)) * n_routes); + if (!buf) + return -ENOMEM; + + uint8_t *p = buf; + FOREACH_ARRAY(route, routes, n_routes) { + if (route->dst_prefixlen > sizeof(struct in_addr) * 8) + return -EINVAL; + + *p++ = route->dst_prefixlen; + struct in_addr dst = route->dst_addr; + (void) in4_addr_mask(&dst, route->dst_prefixlen); + p = mempcpy(p, &dst, DIV_ROUND_UP(route->dst_prefixlen, 8)); + p = mempcpy(p, &route->gw_addr, sizeof(struct in_addr)); + } + + return dhcp_message_append_option(message, code, p - buf, buf); +} + +int dhcp_message_append_option_routes(sd_dhcp_message *message, uint8_t code, size_t n_routes, const sd_dhcp_route *routes) { + switch (code) { + case SD_DHCP_OPTION_STATIC_ROUTE: + return dhcp_message_append_option_static_routes(message, n_routes, routes); + case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: + case SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE: + return dhcp_message_append_option_classless_static_routes(message, code, n_routes, routes); + default: + return -EINVAL; + } +} + +int dhcp_message_append_option_6rd( + sd_dhcp_message *message, + uint8_t ipv4masklen, + uint8_t prefixlen, + const struct in6_addr *prefix, + size_t n_br_addresses, + const struct in_addr *br_addresses) { + + assert(message); + assert(prefix); + assert(n_br_addresses == 0 || br_addresses); + + /* See RFC 5969 Section 7.1.1 and dhcp_message_get_option_6rd() below. */ + + if (dhcp_message_has_option(message, SD_DHCP_OPTION_6RD)) + return -EEXIST; + + if (ipv4masklen > 32) + return -EINVAL; + + if (32 - ipv4masklen + prefixlen > 128) + return -EINVAL; + + if (n_br_addresses == 0) + return -EINVAL; + + if (size_multiply_overflow(sizeof(struct in_addr), n_br_addresses)) + return -ENOBUFS; + + size_t buflen = size_add(2 + sizeof(struct in6_addr), sizeof(struct in_addr) * n_br_addresses); + if (buflen == SIZE_MAX) + return -ENOBUFS; + + _cleanup_free_ uint8_t *buf = new(uint8_t, buflen); + if (!buf) + return -ENOMEM; + + uint8_t *p = buf; + *p++ = ipv4masklen; + *p++ = prefixlen; + + struct in6_addr masked = *prefix; + (void) in6_addr_mask(&masked, prefixlen); + p = mempcpy(p, &masked, sizeof(struct in6_addr)); + + memcpy(p, br_addresses, n_br_addresses * sizeof(struct in_addr)); + + return dhcp_message_append_option(message, SD_DHCP_OPTION_6RD, buflen, buf); +} + +int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id) { + assert(message); + assert(id); + + if (!sd_dhcp_client_id_is_set(id)) + return -EINVAL; + + if (dhcp_message_has_option(message, SD_DHCP_OPTION_CLIENT_IDENTIFIER)) + return -EEXIST; + + return dhcp_message_append_option(message, SD_DHCP_OPTION_CLIENT_IDENTIFIER, id->size, id->raw); +} + +static int cmp_uint8(const uint8_t *a, const uint8_t *b) { + assert(a); + assert(b); + + return CMP(*a, *b); +} + +int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message, Set *prl) { + assert(message); + + size_t len = set_size(prl); + if (len == 0) + return 0; + + _cleanup_free_ uint8_t *buf = new(uint8_t, len); + if (!buf) + return -ENOMEM; + + uint8_t *p = buf; + void *q; + SET_FOREACH(q, prl) + *p++ = PTR_TO_UINT8(q); + + /* Sort the options to make the message reproducible. */ + typesafe_qsort(buf, len, cmp_uint8); + + return dhcp_message_append_option(message, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, len, buf); +} + +static int dhcp_message_append_option_fqdn(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *fqdn) { + int r; + + assert(message); + assert(fqdn); + + /* FIXME: Allow long fqdn, as now we support long option. */ + uint8_t buf[3 + DHCP_MAX_FQDN_LENGTH]; + + /* RFC 4702 section 2.1 + * The "E" bit indicates the encoding of the Domain Name field. 1 indicates canonical wire format, + * without compression. This encoding SHOULD be used by clients and MUST be supported by servers. + * A server MUST use the same encoding as that used by the client. A server that does not support + * the deprecated ASCII encoding MUST ignore Client FQDN options that use that encoding. + * + * Here, we unconditionally set the 'E' flag. Hence, sd_dhcp_server must ignore the option if a + * client does not set the 'E' flag in the request. */ + buf[0] = flags | DHCP_FQDN_FLAG_E; + + /* RFC 4702 section 2.2 + * The two 1-octet RCODE1 and RCODE2 fields are deprecated. A client SHOULD set these to 0 when + * sending the option and SHOULD ignore them on receipt. A server SHOULD set these to 255 when + * sending the option and MUST ignore them on receipt. */ + buf[1] = is_client ? 0 : 255; + buf[2] = is_client ? 0 : 255; + + r = dns_name_to_wire_format(fqdn, buf + 3, sizeof(buf) - 3, false); + if (r <= 0) + return r; + + return dhcp_message_append_option(message, SD_DHCP_OPTION_FQDN, 3 + r, buf); +} + +int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *hostname) { + assert(message); + + /* Hostname (12) or FQDN (81) + * + * RFC 4702 section 3.1 + * clients that send the Client FQDN option in their messages MUST NOT also send the Host Name option. */ + + if (isempty(hostname)) + return 0; + + if (dhcp_message_has_option(message, SD_DHCP_OPTION_HOST_NAME)) + return -EEXIST; + + if (dhcp_message_has_option(message, SD_DHCP_OPTION_FQDN)) + return -EEXIST; + + if (dns_name_is_single_label(hostname)) + return dhcp_message_append_option_string(message, SD_DHCP_OPTION_HOST_NAME, hostname); + + return dhcp_message_append_option_fqdn(message, flags, is_client, hostname); +} + +int dhcp_message_append_option_sub_tlv(sd_dhcp_message *message, uint8_t code, const TLV *tlv) { + int r; + + assert(message); + + if (tlv_isempty(tlv)) + return 0; + + if (dhcp_message_has_option(message, code)) + return -EEXIST; + + _cleanup_(iovec_done) struct iovec iov = {}; + r = tlv_build(tlv, &iov); + if (r < 0) + return r; + + return dhcp_message_append_option(message, code, iov.iov_len, iov.iov_base); +} + +int dhcp_message_append_option_length_prefixed_data( + sd_dhcp_message *message, + uint8_t code, + size_t length_size, + const struct iovec_wrapper *iovw) { + + int r; + + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = iovw_merge(iovw, length_size, &iov); + if (r < 0) + return r; + + if (!iovec_is_set(&iov)) + return 0; + + return dhcp_message_append_option(message, code, iov.iov_len, iov.iov_base); +} + +int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret) { + int r; + + assert(message); + + struct iovec iov; + r = tlv_get_full(&message->options, code, length, ret ? &iov : NULL); + if (r < 0) + return r; + + if (ret) + memcpy_safe(ret, iov.iov_base, iov.iov_len); + return 0; +} + +int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret) { + assert(message); + return tlv_get_alloc(&message->options, code, ret); +} + +int dhcp_message_get_option_flag(sd_dhcp_message *message, uint8_t code) { + assert(message); + return dhcp_message_get_option(message, code, /* length= */ 0, /* ret= */ NULL); +} + +int dhcp_message_get_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t *ret) { + assert(message); + return dhcp_message_get_option(message, code, sizeof(uint8_t), ret); +} + +int dhcp_message_get_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t *ret) { + be16_t b; + int r; + + assert(message); + + r = dhcp_message_get_option(message, code, sizeof(be16_t), ret ? &b : NULL); + if (r < 0) + return r; + + if (ret) + *ret = be16toh(b); + return 0; +} + +int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t *ret) { + assert(message); + return dhcp_message_get_option(message, code, sizeof(be32_t), ret); +} + +int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max_as_infinity, usec_t *ret) { + int r; + + assert(message); + + be32_t t; + r = dhcp_message_get_option_be32(message, code, &t); + if (r < 0) + return r; + + if (ret) + *ret = be32_sec_to_usec(t, max_as_infinity); + return 0; +} + +int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, struct in_addr *ret) { + assert(message); + return dhcp_message_get_option_be32(message, code, ret ? &ret->s_addr : NULL); +} + +int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, size_t *ret_n_addr, struct in_addr **ret_addr) { + int r; + + assert(message); + assert(ret_n_addr || !ret_addr); + + _cleanup_(iovec_done) struct iovec iov_free = {}; + r = dhcp_message_get_option_alloc(message, code, &iov_free); + if (r < 0) + return r; + + struct iovec iov = iov_free; + if (code == SD_DHCP_OPTION_SIP_SERVER) { + if (!iovec_is_set(&iov)) + return -EBADMSG; + + if (*(uint8_t*) iov.iov_base != 1) /* 'enc' field, 0: domains, 1: addresses */ + return -ENODATA; + + iovec_inc(&iov, 1); + } + + if (iov.iov_len % sizeof(struct in_addr) != 0) + return -EBADMSG; + + size_t n = iov.iov_len / sizeof(struct in_addr); + if (n == 0) + return -ENODATA; + + if (ret_addr) { + if (code == SD_DHCP_OPTION_SIP_SERVER) { + struct in_addr *addr = newdup(struct in_addr, iov.iov_base, n); + if (!addr) + return -ENOMEM; + *ret_addr = addr; + } else { + *ret_addr = iov.iov_base; + TAKE_STRUCT(iov_free); + } + } + if (ret_n_addr) + *ret_n_addr = n; + return 0; +} + +int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char **ret) { + int r; + + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + if (!iovec_is_set(&iov)) + return -ENODATA; + + /* Allow NUL at the end for buggy DHCP servers, but refuse intermediate NUL. */ + if (memchr(iov.iov_base, 0, iov.iov_len - 1)) + return -EBADMSG; + + /* Note, dhcp_message_get_option_alloc() -> tlv_get_alloc() allocates an extra byte to make + * iov.iov_base can be handled as a NUL-terminated string. Hence, we can directly pass it to + * isempty() and string_is_safe(). */ + + if (isempty(iov.iov_base)) + return -ENODATA; + + if (!string_is_safe(iov.iov_base, STRING_ALLOW_BACKSLASHES | STRING_ALLOW_QUOTES | STRING_ALLOW_GLOBS)) + return -EBADMSG; + + if (ret) + *ret = TAKE_PTR(iov.iov_base); + return 0; +} + +static int dhcp_message_get_option_static_routes(sd_dhcp_message *message, size_t *ret_n_routes, sd_dhcp_route **ret_routes) { + int r; + + assert(message); + + size_t n; + _cleanup_free_ struct in_addr *addrs = NULL; + r = dhcp_message_get_option_addresses(message, SD_DHCP_OPTION_STATIC_ROUTE, &n, &addrs); + if (r < 0) + return r; + + if (n % 2 != 0) + return -EBADMSG; + + _cleanup_free_ sd_dhcp_route *routes = NULL; + size_t n_routes = 0; + + for (size_t i = 0; i < n; i += 2) { + struct in_addr dst = addrs[i]; + + uint8_t prefixlen; + if (in4_addr_default_prefixlen(&dst, &prefixlen) < 0) + continue; + + (void) in4_addr_mask(&dst, prefixlen); + + /* RFC 2132 section 5.8: + * The default route (0.0.0.0) is an illegal destination for a static route.*/ + if (in4_addr_is_null(&dst)) + continue; + + if (!ret_routes) { + n_routes++; + continue; + } + + if (!GREEDY_REALLOC(routes, n_routes + 1)) + return -ENOMEM; + + routes[n_routes++] = (struct sd_dhcp_route) { + .dst_addr = dst, + .gw_addr = addrs[i + 1], + .dst_prefixlen = prefixlen, + }; + } + + if (n_routes == 0) + return -ENODATA; + + if (ret_routes) + *ret_routes = TAKE_PTR(routes); + if (ret_n_routes) + *ret_n_routes = n_routes; + return 0; +} + +static int dhcp_message_get_option_classless_static_routes(sd_dhcp_message *message, uint8_t code, size_t *ret_n_routes, sd_dhcp_route **ret_routes) { + int r; + + assert(message); + assert(IN_SET(code, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE)); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + _cleanup_free_ sd_dhcp_route *routes = NULL; + size_t n_routes = 0; + + for (struct iovec i = iov; iovec_is_set(&i);) { + uint8_t prefixlen = *(uint8_t*) i.iov_base; + iovec_inc(&i, 1); + + if (prefixlen > 32) + return -EBADMSG; + + size_t n = DIV_ROUND_UP(prefixlen, 8); + if (n > i.iov_len) + return -EBADMSG; + + struct in_addr dst = {}; + memcpy_safe(&dst, i.iov_base, n); + (void) in4_addr_mask(&dst, prefixlen); + iovec_inc(&i, n); + + if (i.iov_len < sizeof(struct in_addr)) + return -EBADMSG; + + struct in_addr gw; + memcpy(&gw, i.iov_base, sizeof(struct in_addr)); + iovec_inc(&i, sizeof(struct in_addr)); + + if (!ret_routes) { + n_routes++; + continue; + } + + if (!GREEDY_REALLOC(routes, n_routes + 1)) + return -ENOMEM; + + routes[n_routes++] = (struct sd_dhcp_route) { + .dst_addr = dst, + .gw_addr = gw, + .dst_prefixlen = prefixlen, + }; + } + + if (n_routes == 0) + return -ENODATA; + + if (ret_routes) + *ret_routes = TAKE_PTR(routes); + if (ret_n_routes) + *ret_n_routes = n_routes; + return 0; +} + +int dhcp_message_get_option_routes(sd_dhcp_message *message, uint8_t code, size_t *ret_n_routes, sd_dhcp_route **ret_routes) { + switch (code) { + case SD_DHCP_OPTION_STATIC_ROUTE: + return dhcp_message_get_option_static_routes(message, ret_n_routes, ret_routes); + case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: + case SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE: + return dhcp_message_get_option_classless_static_routes(message, code, ret_n_routes, ret_routes); + default: + return -EINVAL; + } +} + +int dhcp_message_get_option_6rd( + sd_dhcp_message *message, + uint8_t *ret_ipv4masklen, + uint8_t *ret_prefixlen, + struct in6_addr *ret_prefix, + size_t *ret_n_br_addresses, + struct in_addr **ret_br_addresses) { + + int r; + + assert(message); + assert(ret_n_br_addresses || !ret_br_addresses); + + /* See RFC 5969 Section 7.1.1 */ + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, SD_DHCP_OPTION_6RD, &iov); + if (r < 0) + return r; + + /* option-length: The length of the DHCP option in octets (22 octets with one BR IPv4 address). */ + if (iov.iov_len < 2 + sizeof(struct in6_addr) + sizeof(struct in_addr) || + (iov.iov_len - 2 - sizeof(struct in6_addr)) % sizeof(struct in_addr) != 0) + return -EBADMSG; + + size_t n_br_addresses = (iov.iov_len - 2 - sizeof(struct in6_addr)) / sizeof(struct in_addr); + assert(n_br_addresses > 0); /* We have already checked that in the above. */ + + const uint8_t *p = iov.iov_base; + + /* IPv4MaskLen: The number of high-order bits that are identical across all CE IPv4 addresses + * within a given 6rd domain. This may be any value between 0 and 32. Any value + * greater than 32 is invalid. */ + uint8_t ipv4masklen = *p++; + if (ipv4masklen > 32) + return -EBADMSG; + + /* 6rdPrefixLen: The IPv6 prefix length of the SP's 6rd IPv6 prefix in number of bits. For the + * purpose of bounds checking by DHCP option processing, the sum of + * (32 - IPv4MaskLen) + 6rdPrefixLen MUST be less than or equal to 128. */ + uint8_t prefixlen = *p++; + if (32 - ipv4masklen + prefixlen > 128) + return -EBADMSG; + + /* 6rdPrefix: The service provider's 6rd IPv6 prefix represented as a 16-octet IPv6 address. + * The bits in the prefix after the 6rdPrefixlen number of bits are reserved and + * MUST be initialized to zero by the sender and ignored by the receiver. */ + struct in6_addr prefix; + memcpy(&prefix, p, sizeof(struct in6_addr)); + (void) in6_addr_mask(&prefix, prefixlen); + p += sizeof(struct in6_addr); + + /* 6rdBRIPv4Address: One or more IPv4 addresses of the 6rd Border Relays for a given 6rd domain. */ + if (ret_br_addresses) { + struct in_addr *br_addresses = newdup(struct in_addr, p, n_br_addresses); + if (!br_addresses) + return -ENOMEM; + + *ret_br_addresses = br_addresses; + } + + if (ret_ipv4masklen) + *ret_ipv4masklen = ipv4masklen; + if (ret_prefixlen) + *ret_prefixlen = prefixlen; + if (ret_prefix) + *ret_prefix = prefix; + if (ret_n_br_addresses) + *ret_n_br_addresses = n_br_addresses; + return 0; +} + +int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret) { + int r; + + assert(message); + assert(ret); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, SD_DHCP_OPTION_CLIENT_IDENTIFIER, &iov); + if (r < 0) + return r; + + return sd_dhcp_client_id_set_raw(ret, iov.iov_base, iov.iov_len); +} + +int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set **ret) { + int r; + + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, &iov); + if (r < 0) + return r; + + if (!iovec_is_set(&iov)) + return -ENODATA; + + if (!ret) + return 0; + + _cleanup_set_free_ Set *prl = NULL; + for (struct iovec i = iov; iovec_is_set(&i); iovec_inc(&i, 1)) { + r = set_ensure_put(&prl, /* hash_ops= */ NULL, UINT8_TO_PTR(*(uint8_t*) i.iov_base)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(prl); + return 0; +} + +static int normalize_dns_name(const char *name, char **ret) { + int r; + + assert(name); + + _cleanup_free_ char *normalized = NULL; + r = dns_name_normalize(name, /* flags= */ 0, &normalized); + if (r < 0) + return r; + + if (is_localhost(normalized)) + return -EINVAL; + + if (dns_name_is_root(normalized)) + return -EINVAL; + + if (ret) + *ret = TAKE_PTR(normalized); + return 0; +} + +int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, char **ret_fqdn) { + int r; + + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, SD_DHCP_OPTION_FQDN, &iov); + if (r < 0) + return r; + + if (iov.iov_len <= 3) + return -EBADMSG; + + uint8_t flags = *(uint8_t*) iov.iov_base; + if (!FLAGS_SET(flags, DHCP_FQDN_FLAG_E)) + return -EOPNOTSUPP; + + struct iovec i; + iovec_shift(&iov, 3, &i); + + _cleanup_free_ char *name = NULL; + const uint8_t *p = i.iov_base; + r = dns_name_from_wire_format(&p, &i.iov_len, &name); + if (r < 0) + return r; + if (i.iov_len > 0) /* trailing garbage? */ + return -EBADMSG; + + if (isempty(name)) + return -ENODATA; + + if (!string_is_safe(name, /* flags= */ 0)) + return -EBADMSG; + + _cleanup_free_ char *normalized = NULL; + r = normalize_dns_name(name, &normalized); + if (r < 0) + return r; + + if (ret_flags) + *ret_flags = flags; + if (ret_fqdn) + *ret_fqdn = TAKE_PTR(normalized); + return 0; +} + +int dhcp_message_get_option_dns_name(sd_dhcp_message *message, uint8_t code, char **ret) { + int r; + + assert(message); + + /* Mainly for Host Name or Domain Name options. */ + + _cleanup_free_ char *name = NULL; + r = dhcp_message_get_option_string(message, code, &name); + if (r < 0) + return r; + + _cleanup_free_ char *normalized = NULL; + r = normalize_dns_name(name, &normalized); + if (r < 0) + return r; + + if (ret) + *ret = TAKE_PTR(normalized); + return 0; +} + +int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret) { + int r; + + assert(message); + + /* FQDN option always takes precedence. */ + r = dhcp_message_get_option_fqdn(message, /* ret_flags= */ NULL, ret); + if (ERRNO_IS_NEG_RESOURCE(r)) + return r; + if (r >= 0) + return 0; + + /* Then, fall back to Host Name option. */ + return dhcp_message_get_option_dns_name(message, SD_DHCP_OPTION_HOST_NAME, ret); +} + +int dhcp_message_get_option_domains(sd_dhcp_message *message, uint8_t code, char ***ret) { + int r; + + assert(message); + + /* This is mostly for SD_DHCP_OPTION_DOMAIN_SEARCH and SD_DHCP_OPTION_SIP_SERVER. */ + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + const uint8_t *buf = iov.iov_base; + size_t len = iov.iov_len; + + if (code == SD_DHCP_OPTION_SIP_SERVER) { + if (len == 0) + return -EBADMSG; + + if (buf[0] != 0) /* 'enc' field, 0: domains, 1: addresses */ + return -ENODATA; + + len--; + buf++; + } + + _cleanup_strv_free_ char **names = NULL; + size_t n_names = 0; + + _cleanup_free_ char *name = NULL; + size_t n = 0; + + for (size_t pos = 0, jump_barrier = 0, next_chunk = 0; pos < len;) { + uint8_t c = buf[pos++]; + + if (c == 0) { + /* End of name */ + + if (!string_is_safe(name, /* flags= */ 0)) + return -EBADMSG; + + _cleanup_free_ char *normalized = NULL; + r = normalize_dns_name(name, &normalized); + if (r < 0) + return r; + + r = strv_consume_with_size(&names, &n_names, TAKE_PTR(normalized)); + if (r < 0) + return r; + + if (next_chunk != 0) + pos = next_chunk; + + next_chunk = 0; + jump_barrier = pos; + + name = mfree(name); + n = 0; + + } else if (c <= 63) { + /* Literal label */ + + const char *label = (const char*) (buf + pos); + pos += c; + + if (pos >= len) + return -EBADMSG; + + if (!GREEDY_REALLOC(name, n + 1 + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + if (n != 0) + name[n++] = '.'; + + r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + + } else if (FLAGS_SET(c, 0xc0)) { + /* Pointer */ + + if (pos >= len) /* pointer is 2 bytes, hence we need to read at least one more byte. */ + return -EBADMSG; + + /* Save the current location so we don't end up re-parsing what's parsed so far. */ + if (next_chunk == 0) + next_chunk = pos + 1; + + pos = ((size_t) (c & ~0xc0) << 8) | ((size_t) buf[pos]); + + /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ + if (pos >= jump_barrier) + return -EBADMSG; + + jump_barrier = pos; + + } else + return -EBADMSG; + } + + if (!isempty(name)) /* trailing garbage?? Should not happen, but for safety. */ + return -EBADMSG; + + if (strv_isempty(names)) + return -EBADMSG; + + if (ret) + *ret = TAKE_PTR(names); + return 0; +} + +int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret) { + int r; + + assert(message); + assert(!FLAGS_SET(flags, TLV_TEMPORARY)); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + _cleanup_(tlv_unrefp) TLV *tlv = tlv_new(flags); + if (!tlv) + return -ENOMEM; + + r = tlv_parse(tlv, &iov); + if (r < 0) + return r; + + if (tlv_isempty(tlv)) + return -ENODATA; + + if (ret) + *ret = TAKE_PTR(tlv); + return 0; +} + +int dhcp_message_get_option_length_prefixed_data( + sd_dhcp_message *message, + uint8_t code, + size_t length_size, + struct iovec_wrapper *ret) { + + int r; + + assert(message); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(message, code, &iov); + if (r < 0) + return r; + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + r = iovec_split(&iov, length_size, &iovw); + if (r < 0) + return r; + + if (iovw_isempty(&iovw)) + return -ENODATA; + + if (ret) + *ret = TAKE_STRUCT(iovw); + return 0; +} + +static int parse_dnr_one(const struct iovec *iov, sd_dns_resolver *ret) { + int r; + + assert(iovec_is_set(iov)); + assert(ret); + + _cleanup_(sd_dns_resolver_done) sd_dns_resolver resolver = {}; + struct iovec i = *iov; + + /* service priority */ + if (i.iov_len < sizeof(be16_t)) + return -EBADMSG; + + resolver.priority = unaligned_read_be16(i.iov_base); + iovec_inc(&i, sizeof(be16_t)); + + /* RFC 9460 section 2.4.1: + * When SvcPriority is 0, the SVCB record is in AliasMode. + * + * We do not support the alias mode. But the entry itself is not invalid. */ + if (resolver.priority == 0) { + *ret = (sd_dns_resolver) {}; + return 0; + } + + /* authentication domain name */ + if (!iovec_is_set(&i)) + return -EBADMSG; + + size_t name_len = *(uint8_t*) i.iov_base; + iovec_inc(&i, 1); + if (i.iov_len < name_len) + return -EBADMSG; + + const uint8_t *name_buf = i.iov_base; + iovec_inc(&i, name_len); + + r = dns_name_from_wire_format(&name_buf, &name_len, &resolver.auth_name); + if (r < 0) + return r; + if (r == 0 || name_len != 0) + return -EBADMSG; + + r = dns_name_is_valid_ldh(resolver.auth_name); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + if (dns_name_is_root(resolver.auth_name)) + return -EBADMSG; + + /* RFC9463 section 3.1.6: In ADN-only mode, server omits everything after the ADN. + * + * We don't support these, but they are not invalid. */ + if (!iovec_is_set(&i)) { + *ret = (sd_dns_resolver) {}; + return 0; + } + + /* IPv4 addresses */ + size_t n = *(uint8_t*) i.iov_base; + iovec_inc(&i, 1); + + if (n % sizeof(struct in_addr) != 0) + return -EBADMSG; + + n /= sizeof(struct in_addr); + + /* RFC9463 section 3.1.8: option MUST include at least one valid IP addr */ + if (n == 0) + return -EBADMSG; + + resolver.family = AF_INET; + resolver.n_addrs = n; + resolver.addrs = new(union in_addr_union, n); + if (!resolver.addrs) + return -ENOMEM; + + for (size_t j = 0; j < n; j++) { + if (i.iov_len < sizeof(struct in_addr)) + return -EBADMSG; + + struct in_addr a; + memcpy(&a, i.iov_base, sizeof(struct in_addr)); + iovec_inc(&i, sizeof(struct in_addr)); + + /* RFC9463 section 5.2: client MUST discard multicast and host loopback addresses */ + if (in4_addr_is_multicast(&a) || in4_addr_is_localhost(&a)) + return -EBADMSG; + + resolver.addrs[j] = (union in_addr_union) { .in = a }; + } + + /* service params */ + r = dnr_parse_svc_params(i.iov_base, i.iov_len, &resolver); + if (r < 0) + return r; + if (r == 0) { + /* We can't use this record, but it is not invalid. */ + *ret = (sd_dns_resolver) {}; + return 0; + } + + *ret = TAKE_STRUCT(resolver); + return 1; +} + +int dhcp_message_get_option_dnr(sd_dhcp_message *message, size_t *ret_n_resolvers, sd_dns_resolver **ret_resolvers) { + int r; + + assert(message); + assert(ret_n_resolvers || !ret_resolvers); + + /* See RFC 9463 section 5.1 */ + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + r = dhcp_message_get_option_length_prefixed_data(message, SD_DHCP_OPTION_V4_DNR, /* length_size= */ 2, &iovw); + if (r < 0) + return r; + + sd_dns_resolver *resolvers = NULL; + size_t n_resolvers = 0; + CLEANUP_ARRAY(resolvers, n_resolvers, dns_resolver_free_array); + FOREACH_ARRAY(i, iovw.iovec, iovw.count) { + _cleanup_(sd_dns_resolver_done) sd_dns_resolver dnr = {}; + r = parse_dnr_one(i, &dnr); + if (r < 0) + return r; + if (r == 0) + continue; + + if (!ret_resolvers) { + n_resolvers++; + continue; + } + + if (!GREEDY_REALLOC(resolvers, n_resolvers + 1)) + return -ENOMEM; + + resolvers[n_resolvers++] = TAKE_STRUCT(dnr); + } + + if (n_resolvers == 0) /* no supported resolver */ + return -ENODATA; + + if (ret_resolvers) { + /* Sort the resolvers with their priorities. */ + typesafe_qsort(resolvers, n_resolvers, dns_resolver_prio_compare); + *ret_resolvers = TAKE_PTR(resolvers); + } + if (ret_n_resolvers) + *ret_n_resolvers = n_resolvers; + + return 0; +} + +static int dhcp_message_verify_header( + const struct iovec *iov, + uint8_t op, + const uint32_t *xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr) { + + assert(iov); + assert(iovec_is_valid(iov)); + assert(IN_SET(op, 0, BOOTREQUEST, BOOTREPLY)); /* when 0, both BOOTREQUEST and BOOTREPLY are accepted */ + + POINTER_MAY_BE_NULL(xid); + POINTER_MAY_BE_NULL(hw_addr); + + if (iov->iov_len < sizeof(DHCPMessageHeader)) + return -EBADMSG; + + /* DHCP travels over UDP, so anything larger than the maximum UDP payload cannot be a valid + * message and would also be impossible to rebuild as a UDP packet. */ + if (iov->iov_len > UDP_PAYLOAD_MAX_SIZE) + return -EBADMSG; + + const DHCPMessageHeader *header = iov->iov_base; + + if (!IN_SET(header->op, BOOTREQUEST, BOOTREPLY)) + return -EBADMSG; + if (op != 0 && header->op != op) + return -EBADMSG; + + if (xid && be32toh(header->xid) != *xid) + return -EBADMSG; + + if (arp_type <= UINT8_MAX && header->htype != arp_type) + return -EBADMSG; + + if (header->hlen > sizeof_field(DHCPMessageHeader, chaddr)) + return -EBADMSG; + + if (hw_addr && memcmp_nn(header->chaddr, header->hlen, hw_addr->bytes, hw_addr->length) != 0) + return -EBADMSG; + + return 0; +} + +int dhcp_message_parse( + const struct iovec *iov, + uint8_t op, + const uint32_t *xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr, + sd_dhcp_message **ret) { + + int r; + + assert(iov); + assert(ret); + + r = dhcp_message_verify_header(iov, op, xid, arp_type, hw_addr); + if (r < 0) + return r; + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_new(&message); + if (r < 0) + return r; + + memcpy(&message->header, iov->iov_base, sizeof(DHCPMessageHeader)); + + if (be32toh(message->header.magic) != DHCP_MAGIC_COOKIE) { + /* Should be BOOTP, and no options. */ + *ret = TAKE_PTR(message); + return 0; + } + + /* In the BOOTP protocol (RFC 951), the vendor field (magic + options) is 64 bytes, but here we do + * not check the length, and support all DHCP options even if we are running as BOOTP client. */ + + r = tlv_parse(&message->options, &IOVEC_SHIFT(iov, sizeof(DHCPMessageHeader))); + if (r < 0) + return r; + + /* Parse SD_DHCP_OPTION_OVERLOAD (52) to determine if we should parse sname and/or file. */ + uint8_t overload = DHCP_OVERLOAD_NONE; + (void) dhcp_message_get_option_u8(message, SD_DHCP_OPTION_OVERLOAD, &overload); + + if (FLAGS_SET(overload, DHCP_OVERLOAD_FILE)) { + r = tlv_parse(&message->options, &IOVEC_MAKE(message->header.file, sizeof(message->header.file))); + if (r < 0) + return r; + + /* The content of the overloaded field has been merged into options. Clear it so that the + * field is not re-parsed (which would duplicate the options) and not re-emitted verbatim + * by dhcp_message_build(), ensuring parse/build is idempotent. */ + memzero(message->header.file, sizeof(message->header.file)); + } + + if (FLAGS_SET(overload, DHCP_OVERLOAD_SNAME)) { + r = tlv_parse(&message->options, &IOVEC_MAKE(message->header.sname, sizeof(message->header.sname))); + if (r < 0) + return r; + + memzero(message->header.sname, sizeof(message->header.sname)); + } + + *ret = TAKE_PTR(message); + return 0; +} + +size_t dhcp_message_payload_size(sd_dhcp_message *message) { + assert(message); + + return MAX(size_add(sizeof(DHCPMessageHeader), tlv_size(&message->options)), BOOTP_MESSAGE_SIZE); +} + +size_t dhcp_message_packet_size(sd_dhcp_message *message) { + assert(message); + + return size_add(sizeof(struct iphdr) + sizeof(struct udphdr), dhcp_message_payload_size(message)); +} + +int dhcp_message_build(sd_dhcp_message *message, struct iovec_wrapper *ret) { + int r; + + assert(message); + assert(ret); + + size_t size = size_add(sizeof(DHCPMessageHeader), tlv_size(&message->options)); + if (size > UDP_PAYLOAD_MAX_SIZE) + return -E2BIG; + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + r = iovw_extend(&iovw, &message->header, sizeof(DHCPMessageHeader)); + if (r < 0) + return r; + + struct iovec options; + r = tlv_build(&message->options, &options); + if (r < 0) + return r; + + r = iovw_consume_iov(&iovw, &options); + if (r < 0) + return r; + + /* For compatibility with other implementations and network appliances, the message size should be at + * least 300 bytes, which is the size of BOOTP message. */ + size_t padding_size = LESS_BY(BOOTP_MESSAGE_SIZE, size); + if (padding_size > 0) { + uint8_t *padding = new0(uint8_t, padding_size); + if (!padding) + return -ENOMEM; + + r = iovw_consume(&iovw, padding, padding_size); + if (r < 0) + return r; + } + + *ret = TAKE_STRUCT(iovw); + return 0; +} + +int dhcp_message_build_json(sd_dhcp_message *message, sd_json_variant **ret) { + int r; + + assert(message); + assert(message->header.hlen <= sizeof(message->header.chaddr)); + assert(ret); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR_UNSIGNED("op", message->header.op), + SD_JSON_BUILD_PAIR_UNSIGNED("htype", message->header.htype), + SD_JSON_BUILD_PAIR_UNSIGNED("hops", message->header.hops), + SD_JSON_BUILD_PAIR_UNSIGNED("xid", be32toh(message->header.xid)), + SD_JSON_BUILD_PAIR_UNSIGNED("secs", be16toh(message->header.secs)), + SD_JSON_BUILD_PAIR_UNSIGNED("flags", be16toh(message->header.flags)), + JSON_BUILD_PAIR_HEX_NON_EMPTY("ciaddr", &message->header.ciaddr, sizeof(message->header.ciaddr)), + JSON_BUILD_PAIR_HEX_NON_EMPTY("yiaddr", &message->header.yiaddr, sizeof(message->header.yiaddr)), + JSON_BUILD_PAIR_HEX_NON_EMPTY("siaddr", &message->header.siaddr, sizeof(message->header.siaddr)), + JSON_BUILD_PAIR_HEX_NON_EMPTY("giaddr", &message->header.giaddr, sizeof(message->header.giaddr)), + JSON_BUILD_PAIR_HEX_NON_EMPTY("chaddr", message->header.chaddr, message->header.hlen)); + if (r < 0) + return r; + + uint8_t overload = DHCP_OVERLOAD_NONE; + (void) dhcp_message_get_option_u8(message, SD_DHCP_OPTION_OVERLOAD, &overload); + + if (!FLAGS_SET(overload, DHCP_OVERLOAD_SNAME) && !eqzero(message->header.sname)) { + r = sd_json_variant_merge_objectbo( + &v, + JSON_BUILD_PAIR_HEX_NON_EMPTY("sname", message->header.sname, sizeof(message->header.sname))); + if (r < 0) + return r; + } + + if (!FLAGS_SET(overload, DHCP_OVERLOAD_FILE) && !eqzero(message->header.file)) { + r = sd_json_variant_merge_objectbo( + &v, + JSON_BUILD_PAIR_HEX_NON_EMPTY("file", message->header.file, sizeof(message->header.file))); + if (r < 0) + return r; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; + r = tlv_build_json(&message->options, &w); + if (r < 0) + return r; + + r = sd_json_variant_merge_objectbo( + &v, + JSON_BUILD_PAIR_VARIANT_NON_NULL("options", w)); + if (r < 0) + return r; + + *ret = TAKE_PTR(v); + return 0; +} + +typedef struct MessageParam { + uint8_t op; + uint8_t htype; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + struct iovec ciaddr; + struct iovec yiaddr; + struct iovec siaddr; + struct iovec giaddr; + struct iovec chaddr; + struct iovec sname; + struct iovec file; + TLV *options; +} MessageParam; + +static void message_param_done(MessageParam *p) { + assert(p); + + iovec_done(&p->ciaddr); + iovec_done(&p->yiaddr); + iovec_done(&p->siaddr); + iovec_done(&p->giaddr); + iovec_done(&p->chaddr); + iovec_done(&p->sname); + iovec_done(&p->file); + tlv_unref(p->options); +} + +static int dispatch_options(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) { + TLV **options = ASSERT_PTR(userdata); + int r; + + if (*options) + return -EINVAL; /* multiple options field? */ + + _cleanup_(tlv_unrefp) TLV *tlv = tlv_new(TLV_DHCP4); + if (!tlv) + return -ENOMEM; + + r = tlv_parse_json(tlv, v); + if (r < 0) + return r; + + *options = TAKE_PTR(tlv); + return 0; +} + +int dhcp_message_parse_json(sd_json_variant *v, sd_dhcp_message **ret) { + static const sd_json_dispatch_field dispatch_table[] = { + { "op", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(MessageParam, op), SD_JSON_MANDATORY }, + { "htype", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(MessageParam, htype), 0 }, + { "hops", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(MessageParam, hops), 0 }, + { "xid", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(MessageParam, xid), 0 }, + { "secs", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16, offsetof(MessageParam, secs), 0 }, + { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16, offsetof(MessageParam, flags), 0 }, + { "ciaddr", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MessageParam, ciaddr), 0 }, + { "yiaddr", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MessageParam, yiaddr), 0 }, + { "siaddr", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MessageParam, siaddr), 0 }, + { "giaddr", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MessageParam, giaddr), 0 }, + { "chaddr", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MessageParam, chaddr), 0 }, + { "sname", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MessageParam, sname), 0 }, + { "file", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(MessageParam, file), 0 }, + { "options", SD_JSON_VARIANT_ARRAY, dispatch_options, offsetof(MessageParam, options), 0 }, + {}, + }; + + int r; + + assert(v); + assert(ret); + + _cleanup_(message_param_done) MessageParam p = {}; + r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return r; + + if (!IN_SET(p.op, BOOTREQUEST, BOOTREPLY)) + return -EINVAL; + if (!iovec_is_valid(&p.ciaddr) || !IN_SET(p.ciaddr.iov_len, 0, sizeof_field(sd_dhcp_message, header.ciaddr))) + return -EINVAL; + if (!iovec_is_valid(&p.yiaddr) || !IN_SET(p.yiaddr.iov_len, 0, sizeof_field(sd_dhcp_message, header.yiaddr))) + return -EINVAL; + if (!iovec_is_valid(&p.siaddr) || !IN_SET(p.siaddr.iov_len, 0, sizeof_field(sd_dhcp_message, header.siaddr))) + return -EINVAL; + if (!iovec_is_valid(&p.giaddr) || !IN_SET(p.giaddr.iov_len, 0, sizeof_field(sd_dhcp_message, header.giaddr))) + return -EINVAL; + if (!iovec_is_valid(&p.chaddr) || p.chaddr.iov_len > sizeof_field(sd_dhcp_message, header.chaddr)) + return -EINVAL; + if (!iovec_is_valid(&p.sname) || p.sname.iov_len > sizeof_field(sd_dhcp_message, header.sname)) + return -EINVAL; + if (!iovec_is_valid(&p.file) || p.file.iov_len > sizeof_field(sd_dhcp_message, header.file)) + return -EINVAL; + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_new(&message); + if (r < 0) + return r; + + message->header = (DHCPMessageHeader) { + .op = p.op, + .htype = p.htype, + .hlen = p.chaddr.iov_len, + .hops = p.hops, + .xid = htobe32(p.xid), + .secs = htobe16(p.secs), + .flags = htobe16(p.flags), + .magic = htobe32(DHCP_MAGIC_COOKIE), + }; + + memcpy_safe(&message->header.ciaddr, p.ciaddr.iov_base, p.ciaddr.iov_len); + memcpy_safe(&message->header.yiaddr, p.yiaddr.iov_base, p.yiaddr.iov_len); + memcpy_safe(&message->header.siaddr, p.siaddr.iov_base, p.siaddr.iov_len); + memcpy_safe(&message->header.giaddr, p.giaddr.iov_base, p.giaddr.iov_len); + memcpy_safe(message->header.chaddr, p.chaddr.iov_base, p.chaddr.iov_len); + memcpy_safe(message->header.sname, p.sname.iov_base, p.sname.iov_len); + memcpy_safe(message->header.file, p.file.iov_base, p.file.iov_len); + if (p.options) { + message->options = TAKE_STRUCT(*p.options); + p.options = mfree(p.options); + } + + *ret = TAKE_PTR(message); + return 0; +} + +int dhcp_message_send_udp( + sd_dhcp_message *message, + int fd, + be32_t src_addr, + be32_t dst_addr, + uint16_t dst_port) { + + int r; + + assert(message); + assert(fd >= 0); + + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + r = dhcp_message_build(message, &payload); + if (r < 0) + return r; + + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(dst_port), + .in.sin_addr.s_addr = dst_addr, + }; + + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; + + struct msghdr msg = { + .msg_name = &sa, + .msg_namelen = sizeof(sa.in), + .msg_iov = payload.iovec, + .msg_iovlen = payload.count, + }; + + if (src_addr != INADDR_ANY) { + msg.msg_control = &control; + msg.msg_controllen = sizeof(control); + + struct cmsghdr *cmsg = ASSERT_PTR(CMSG_FIRSTHDR(&msg)); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + + struct in_pktinfo *pktinfo = ASSERT_PTR(CMSG_TYPED_DATA(cmsg, struct in_pktinfo)); + pktinfo->ipi_spec_dst.s_addr = src_addr; + } + + if (sendmsg(fd, &msg, MSG_NOSIGNAL) < 0) + return -errno; + + return 0; +} + +int dhcp_message_send_raw( + sd_dhcp_message *message, + int fd, + int ifindex, + be32_t src_addr, + uint16_t src_port, + const struct hw_addr_data *dst_hw_addr, + be32_t dst_addr, + uint16_t dst_port, + int ip_service_type) { + + int r; + + assert(message); + assert(fd >= 0); + assert(ifindex > 0); + assert(dst_hw_addr); + + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + r = dhcp_message_build(message, &payload); + if (r < 0) + return r; + + struct iphdr ip; + struct udphdr udp; + r = udp_packet_build( + src_addr, + src_port, + dst_addr, + dst_port, + ip_service_type, + &payload, + &ip, + &udp); + if (r < 0) + return r; + + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + r = iovw_put(&iovw, &ip, sizeof(struct iphdr)); + if (r < 0) + return r; + + r = iovw_put(&iovw, &udp, sizeof(struct udphdr)); + if (r < 0) + return r; + + r = iovw_put_iovw(&iovw, &payload); + if (r < 0) + return r; + + union sockaddr_union sa = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_IP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = dst_hw_addr->length, + }; + + memcpy_safe(sa.ll.sll_addr, dst_hw_addr->bytes, dst_hw_addr->length); + + struct msghdr mh = { + .msg_name = &sa.sa, + .msg_namelen = sockaddr_ll_len(&sa.ll), + .msg_iov = iovw.iovec, + .msg_iovlen = iovw.count, + }; + + if (sendmsg(fd, &mh, MSG_NOSIGNAL) < 0) + return -errno; + + return 0; +} diff --git a/src/libsystemd-network/dhcp-message.h b/src/libsystemd-network/dhcp-message.h new file mode 100644 index 0000000000000..2e3bf7dbac7a3 --- /dev/null +++ b/src/libsystemd-network/dhcp-message.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "dhcp-forward.h" +#include "dhcp-protocol.h" +#include "tlv-util.h" + +typedef struct DHCPServerData { + struct in_addr *addr; + size_t size; +} DHCPServerData; + +struct sd_dhcp_message { + unsigned n_ref; + + DHCPMessageHeader header; + TLV options; +}; + +typedef struct sd_dhcp_message sd_dhcp_message; + +sd_dhcp_message* sd_dhcp_message_ref(sd_dhcp_message *p); +sd_dhcp_message* sd_dhcp_message_unref(sd_dhcp_message *p); +DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_message*, sd_dhcp_message_unref); + +int dhcp_message_new(sd_dhcp_message **ret); + +int dhcp_message_init_header( + sd_dhcp_message *message, + uint8_t op, + uint32_t xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr); + +void dhcp_message_set_broadcast_flag(sd_dhcp_message *message, bool b); +bool dhcp_message_has_broadcast_flag(sd_dhcp_message *message); +int dhcp_message_get_hw_addr(sd_dhcp_message *message, struct hw_addr_data *ret); + +bool dhcp_message_has_option(sd_dhcp_message *message, uint8_t code); +void dhcp_message_remove_option(sd_dhcp_message *message, uint8_t code); + +int dhcp_message_append_option(sd_dhcp_message *message, uint8_t code, size_t length, const void *data); +int dhcp_message_append_option_tlv(sd_dhcp_message *message, const TLV *tlv); +int dhcp_message_append_option_flag(sd_dhcp_message *message, uint8_t code); +int dhcp_message_append_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t data); +int dhcp_message_append_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t data); +int dhcp_message_append_option_be32(sd_dhcp_message *message, uint8_t code, be32_t data); +int dhcp_message_append_option_sec(sd_dhcp_message *message, uint8_t code, usec_t usec); +int dhcp_message_append_option_address(sd_dhcp_message *message, uint8_t code, const struct in_addr *addr); +int dhcp_message_append_option_addresses(sd_dhcp_message *message, uint8_t code, size_t n_addr, const struct in_addr *addr); +int dhcp_message_append_option_string(sd_dhcp_message *message, uint8_t code, const char *data); +int dhcp_message_append_option_routes(sd_dhcp_message *message, uint8_t code, size_t n_routes, const sd_dhcp_route *routes); +int dhcp_message_append_option_6rd( + sd_dhcp_message *message, + uint8_t ipv4masklen, + uint8_t prefixlen, + const struct in6_addr *prefix, + size_t n_br_addresses, + const struct in_addr *br_addresses); +int dhcp_message_append_option_client_id(sd_dhcp_message *message, const sd_dhcp_client_id *id); +int dhcp_message_append_option_parameter_request_list(sd_dhcp_message *message, Set *prl); +int dhcp_message_append_option_hostname(sd_dhcp_message *message, uint8_t flags, bool is_client, const char *hostname); +int dhcp_message_append_option_sub_tlv(sd_dhcp_message *message, uint8_t code, const TLV *tlv); +int dhcp_message_append_option_length_prefixed_data(sd_dhcp_message *message, uint8_t code, size_t length_size, const struct iovec_wrapper *iovw); + +int dhcp_message_get_option(sd_dhcp_message *message, uint8_t code, size_t length, void *ret); +int dhcp_message_get_option_alloc(sd_dhcp_message *message, uint8_t code, struct iovec *ret); +int dhcp_message_get_option_flag(sd_dhcp_message *message, uint8_t code); +int dhcp_message_get_option_u8(sd_dhcp_message *message, uint8_t code, uint8_t *ret); +int dhcp_message_get_option_u16(sd_dhcp_message *message, uint8_t code, uint16_t *ret); +int dhcp_message_get_option_be32(sd_dhcp_message *message, uint8_t code, be32_t *ret); +int dhcp_message_get_option_sec(sd_dhcp_message *message, uint8_t code, bool max_as_infinity, usec_t *ret); +int dhcp_message_get_option_address(sd_dhcp_message *message, uint8_t code, struct in_addr *ret); +int dhcp_message_get_option_addresses(sd_dhcp_message *message, uint8_t code, size_t *ret_n_addr, struct in_addr **ret_addr); +int dhcp_message_get_option_string(sd_dhcp_message *message, uint8_t code, char **ret); +int dhcp_message_get_option_routes(sd_dhcp_message *message, uint8_t code, size_t *ret_n_routes, sd_dhcp_route **ret_routes); +int dhcp_message_get_option_6rd( + sd_dhcp_message *message, + uint8_t *ret_ipv4masklen, + uint8_t *ret_prefixlen, + struct in6_addr *ret_prefix, + size_t *ret_n_br_addresses, + struct in_addr **ret_br_addresses); +int dhcp_message_get_option_client_id(sd_dhcp_message *message, sd_dhcp_client_id *ret); +int dhcp_message_get_option_parameter_request_list(sd_dhcp_message *message, Set **ret); +int dhcp_message_get_option_fqdn(sd_dhcp_message *message, uint8_t *ret_flags, char **ret_fqdn); +int dhcp_message_get_option_dns_name(sd_dhcp_message *message, uint8_t code, char **ret); +int dhcp_message_get_option_hostname(sd_dhcp_message *message, char **ret); +int dhcp_message_get_option_domains(sd_dhcp_message *message, uint8_t code, char ***ret); +int dhcp_message_get_option_sub_tlv(sd_dhcp_message *message, uint8_t code, TLVFlag flags, TLV **ret); +int dhcp_message_get_option_length_prefixed_data(sd_dhcp_message *message, uint8_t code, size_t length_size, struct iovec_wrapper *ret); +int dhcp_message_get_option_dnr(sd_dhcp_message *message, size_t *ret_n_resolvers, sd_dns_resolver **ret_resolvers); + +int dhcp_message_parse( + const struct iovec *iov, + uint8_t op, + const uint32_t *xid, + uint16_t arp_type, + const struct hw_addr_data *hw_addr, + sd_dhcp_message **ret); + +size_t dhcp_message_payload_size(sd_dhcp_message *message); +size_t dhcp_message_packet_size(sd_dhcp_message *message); + +int dhcp_message_build(sd_dhcp_message *message, struct iovec_wrapper *ret); + +int dhcp_message_build_json(sd_dhcp_message *message, sd_json_variant **ret); +int dhcp_message_parse_json(sd_json_variant *v, sd_dhcp_message **ret); + +int dhcp_message_send_udp( + sd_dhcp_message *message, + int fd, + be32_t src_addr, + be32_t dst_addr, + uint16_t dst_port); +int dhcp_message_send_raw( + sd_dhcp_message *message, + int fd, + int ifindex, + be32_t src_addr, + uint16_t src_port, + const struct hw_addr_data *dst_hw_addr, + be32_t dst_addr, + uint16_t dst_port, + int ip_service_type); diff --git a/src/libsystemd-network/dhcp-network.c b/src/libsystemd-network/dhcp-network.c deleted file mode 100644 index 24b8c12011f73..0000000000000 --- a/src/libsystemd-network/dhcp-network.c +++ /dev/null @@ -1,286 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/*** - Copyright © 2013 Intel Corporation. All rights reserved. -***/ - -#include -#include -#include -#include -#include -#include - -#include "dhcp-network.h" -#include "dhcp-protocol.h" -#include "ether-addr-util.h" -#include "fd-util.h" -#include "socket-util.h" -#include "unaligned.h" - -static int _bind_raw_socket( - int ifindex, - union sockaddr_union *link, - uint32_t xid, - const struct hw_addr_data *hw_addr, - const struct hw_addr_data *bcast_addr, - uint16_t arp_type, - uint16_t port, - bool so_priority_set, - int so_priority) { - - assert(ifindex > 0); - assert(link); - assert(hw_addr); - assert(bcast_addr); - assert(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND)); - - switch (arp_type) { - case ARPHRD_ETHER: - assert(hw_addr->length == ETH_ALEN); - assert(bcast_addr->length == ETH_ALEN); - break; - case ARPHRD_INFINIBAND: - assert(hw_addr->length == 0); - assert(bcast_addr->length == INFINIBAND_ALEN); - break; - default: - assert_not_reached(); - } - - struct sock_filter filter[] = { - BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ - BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(DHCPPacket), 1, 0), /* packet >= DHCPPacket ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.protocol)), /* A <- IP protocol */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), /* IP protocol == UDP ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags */ - BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x20), /* A <- A & 0x20 (More Fragments bit) */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags + Fragment offset */ - BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x1fff), /* A <- A & 0x1fff (Fragment offset) */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, udp.dest)), /* A <- UDP destination port */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, port, 1, 0), /* UDP destination port == DHCP client port ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.op)), /* A <- DHCP op */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.htype)), /* A <- DHCP header type */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arp_type, 1, 0), /* header type == arp_type ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.xid)), /* A <- client identifier */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, xid, 1, 0), /* client identifier == xid ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.hlen)), /* A <- MAC address length */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (uint8_t) hw_addr->length, 1, 0), /* address length == hw_addr->length ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - - /* We only support MAC address length to be either 0 or 6 (ETH_ALEN). Optionally - * compare chaddr for ETH_ALEN bytes. */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_ALEN, 0, 8), /* A (the MAC address length) == ETH_ALEN ? */ - BPF_STMT(BPF_LDX + BPF_IMM, unaligned_read_be32(hw_addr->bytes)), /* X <- 4 bytes of client's MAC */ - BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr)), /* A <- 4 bytes of MAC from dhcp.chaddr */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), /* A == X ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LDX + BPF_IMM, unaligned_read_be16(hw_addr->bytes + 4)), /* X <- remainder of client's MAC */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr) + 4), /* A <- remainder of MAC from dhcp.chaddr */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), /* A == X ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - - BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.magic)), /* A <- DHCP magic cookie */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_MAGIC_COOKIE, 1, 0), /* cookie == DHCP magic cookie ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_RET + BPF_K, UINT32_MAX), /* accept */ - }; - struct sock_fprog fprog = { - .len = ELEMENTSOF(filter), - .filter = filter - }; - _cleanup_close_ int s = -EBADF; - int r; - - s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (s < 0) - return -errno; - - r = setsockopt_int(s, SOL_PACKET, PACKET_AUXDATA, true); - if (r < 0) - return r; - - r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); - if (r < 0) - return -errno; - - r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true); - if (r < 0) - return r; - - if (so_priority_set) { - r = setsockopt_int(s, SOL_SOCKET, SO_PRIORITY, so_priority); - if (r < 0) - return r; - } - - link->ll = (struct sockaddr_ll) { - .sll_family = AF_PACKET, - .sll_protocol = htobe16(ETH_P_IP), - .sll_ifindex = ifindex, - .sll_hatype = htobe16(arp_type), - .sll_halen = bcast_addr->length, - }; - /* We may overflow link->ll. link->ll_buffer ensures we have enough space. */ - memcpy(link->ll.sll_addr, bcast_addr->bytes, bcast_addr->length); - - r = bind(s, &link->sa, sockaddr_ll_len(&link->ll)); - if (r < 0) - return -errno; - - return TAKE_FD(s); -} - -int dhcp_network_bind_raw_socket( - int ifindex, - union sockaddr_union *link, - uint32_t xid, - const struct hw_addr_data *hw_addr, - const struct hw_addr_data *bcast_addr, - uint16_t arp_type, - uint16_t port, - bool so_priority_set, - int so_priority) { - - static struct hw_addr_data default_eth_bcast = { - .length = ETH_ALEN, - .ether = {{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}, - }, default_ib_bcast = { - .length = INFINIBAND_ALEN, - .infiniband = { - 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, 0x40, 0x1b, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff - }, - }; - - assert(ifindex > 0); - assert(link); - assert(hw_addr); - - switch (arp_type) { - case ARPHRD_ETHER: - return _bind_raw_socket(ifindex, link, xid, - hw_addr, - (bcast_addr && !hw_addr_is_null(bcast_addr)) ? bcast_addr : &default_eth_bcast, - arp_type, port, so_priority_set, so_priority); - - case ARPHRD_INFINIBAND: - return _bind_raw_socket(ifindex, link, xid, - &HW_ADDR_NULL, - (bcast_addr && !hw_addr_is_null(bcast_addr)) ? bcast_addr : &default_ib_bcast, - arp_type, port, so_priority_set, so_priority); - default: - return -EINVAL; - } -} - -int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) { - union sockaddr_union src = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(port), - .in.sin_addr.s_addr = address, - }; - _cleanup_close_ int s = -EBADF; - int r; - - s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (s < 0) - return -errno; - - if (ip_service_type >= 0) - r = setsockopt_int(s, IPPROTO_IP, IP_TOS, ip_service_type); - else - r = setsockopt_int(s, IPPROTO_IP, IP_TOS, IPTOS_CLASS_CS6); - if (r < 0) - return r; - - r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true); - if (r < 0) - return r; - - r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true); - if (r < 0) - return r; - - if (ifindex > 0) { - r = socket_bind_to_ifindex(s, ifindex); - if (r < 0) - return r; - } - - if (port == DHCP_PORT_SERVER) { - r = setsockopt_int(s, SOL_SOCKET, SO_BROADCAST, true); - if (r < 0) - return r; - if (address == INADDR_ANY) { - /* IP_PKTINFO filter should not be applied when packets are - allowed to enter/leave through the interface other than - DHCP server sits on(BindToInterface option). */ - r = setsockopt_int(s, IPPROTO_IP, IP_PKTINFO, true); - if (r < 0) - return r; - } - } else { - r = setsockopt_int(s, IPPROTO_IP, IP_FREEBIND, true); - if (r < 0) - return r; - } - - if (bind(s, &src.sa, sizeof(src.in)) < 0) - return -errno; - - return TAKE_FD(s); -} - -int dhcp_network_send_raw_socket( - int s, - const union sockaddr_union *link, - const void *packet, - size_t len) { - - /* Do not add assert(s >= 0) here, as this is called in fuzz-dhcp-server, and in that case this - * function should fail with negative errno. */ - - assert(link); - assert(packet); - assert(len > 0); - - if (sendto(s, packet, len, 0, &link->sa, sockaddr_ll_len(&link->ll)) < 0) - return -errno; - - return 0; -} - -int dhcp_network_send_udp_socket( - int s, - be32_t address, - uint16_t port, - const void *packet, - size_t len) { - - union sockaddr_union dest = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(port), - .in.sin_addr.s_addr = address, - }; - - assert(s >= 0); - assert(packet); - assert(len > 0); - - if (sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in)) < 0) - return -errno; - - return 0; -} diff --git a/src/libsystemd-network/dhcp-network.h b/src/libsystemd-network/dhcp-network.h deleted file mode 100644 index 3cc66dad9da91..0000000000000 --- a/src/libsystemd-network/dhcp-network.h +++ /dev/null @@ -1,32 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "sd-forward.h" -#include "sparse-endian.h" - -int dhcp_network_bind_raw_socket( - int ifindex, - union sockaddr_union *link, - uint32_t xid, - const struct hw_addr_data *hw_addr, - const struct hw_addr_data *bcast_addr, - uint16_t arp_type, - uint16_t port, - bool so_priority_set, - int so_priority); -int dhcp_network_bind_udp_socket( - int ifindex, - be32_t address, - uint16_t port, - int ip_service_type); -int dhcp_network_send_raw_socket( - int s, - const union sockaddr_union *link, - const void *packet, - size_t len); -int dhcp_network_send_udp_socket( - int s, - be32_t address, - uint16_t port, - const void *packet, - size_t len); diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c deleted file mode 100644 index a840b61462157..0000000000000 --- a/src/libsystemd-network/dhcp-option.c +++ /dev/null @@ -1,489 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/*** - Copyright © 2013 Intel Corporation. All rights reserved. -***/ - -#include - -#include "alloc-util.h" -#include "dhcp-option.h" -#include "dhcp-server-internal.h" -#include "dns-domain.h" -#include "hostname-util.h" -#include "memory-util.h" -#include "ordered-set.h" -#include "string-util.h" -#include "strv.h" -#include "utf8.h" - -/* Append type-length value structure to the options buffer */ -static int dhcp_option_append_tlv(uint8_t options[], size_t size, size_t *offset, uint8_t code, size_t optlen, const void *optval) { - assert(options); - assert(size > 0); - assert(offset); - assert(optlen <= UINT8_MAX); - assert(*offset < size); - - if (*offset + 2 + optlen > size) - return -ENOBUFS; - - options[*offset] = code; - options[*offset + 1] = optlen; - - memcpy_safe(&options[*offset + 2], optval, optlen); - *offset += 2 + optlen; - return 0; -} - -static int option_append(uint8_t options[], size_t size, size_t *offset, - uint8_t code, size_t optlen, const void *optval) { - assert(options); - assert(size > 0); - assert(offset); - - int r; - - if (code != SD_DHCP_OPTION_END) - /* always make sure there is space for an END option */ - size--; - - switch (code) { - - case SD_DHCP_OPTION_PAD: - case SD_DHCP_OPTION_END: - if (*offset + 1 > size) - return -ENOBUFS; - - options[*offset] = code; - *offset += 1; - break; - - case SD_DHCP_OPTION_USER_CLASS: { - size_t total = 0; - - if (strv_isempty((char **) optval)) - return -EINVAL; - - STRV_FOREACH(s, (const char* const*) optval) { - size_t len = strlen(*s); - - if (len > 255 || len == 0) - return -EINVAL; - - total += 1 + len; - } - - if (*offset + 2 + total > size) - return -ENOBUFS; - - options[*offset] = code; - options[*offset + 1] = total; - *offset += 2; - - STRV_FOREACH(s, (const char* const*) optval) { - size_t len = strlen(*s); - - options[*offset] = len; - memcpy(&options[*offset + 1], *s, len); - *offset += 1 + len; - } - - break; - } - case SD_DHCP_OPTION_SIP_SERVER: - if (*offset + 3 + optlen > size) - return -ENOBUFS; - - options[*offset] = code; - options[*offset + 1] = optlen + 1; - options[*offset + 2] = 1; - - memcpy_safe(&options[*offset + 3], optval, optlen); - *offset += 3 + optlen; - - break; - case SD_DHCP_OPTION_VENDOR_SPECIFIC: { - OrderedSet *s = (OrderedSet *) optval; - struct sd_dhcp_option *p; - size_t l = 0; - - ORDERED_SET_FOREACH(p, s) - l += p->length + 2; - - if (*offset + l + 2 > size) - return -ENOBUFS; - - options[*offset] = code; - options[*offset + 1] = l; - *offset += 2; - - ORDERED_SET_FOREACH(p, s) { - r = dhcp_option_append_tlv(options, size, offset, p->option, p->length, p->data); - if (r < 0) - return r; - } - break; - } - case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: { - sd_dhcp_server *server = (sd_dhcp_server *) optval; - size_t current_offset = *offset + 2; - - if (server->agent_circuit_id) { - r = dhcp_option_append_tlv(options, size, ¤t_offset, SD_DHCP_RELAY_AGENT_CIRCUIT_ID, - strlen(server->agent_circuit_id), server->agent_circuit_id); - if (r < 0) - return r; - } - if (server->agent_remote_id) { - r = dhcp_option_append_tlv(options, size, ¤t_offset, SD_DHCP_RELAY_AGENT_REMOTE_ID, - strlen(server->agent_remote_id), server->agent_remote_id); - if (r < 0) - return r; - } - - options[*offset] = code; - options[*offset + 1] = current_offset - *offset - 2; - assert(current_offset - *offset - 2 <= UINT8_MAX); - *offset = current_offset; - break; - } - default: - return dhcp_option_append_tlv(options, size, offset, code, optlen, optval); - } - return 0; -} - -static int option_length(uint8_t *options, size_t length, size_t offset) { - assert(options); - assert(offset < length); - - if (IN_SET(options[offset], SD_DHCP_OPTION_PAD, SD_DHCP_OPTION_END)) - return 1; - if (length < offset + 2) - return -ENOBUFS; - - /* validating that buffer is long enough */ - if (length < offset + 2 + options[offset + 1]) - return -ENOBUFS; - - return options[offset + 1] + 2; -} - -int dhcp_option_find_option(uint8_t *options, size_t length, uint8_t code, size_t *ret_offset) { - int r; - - assert(options); - assert(ret_offset); - - for (size_t offset = 0; offset < length; offset += r) { - r = option_length(options, length, offset); - if (r < 0) - return r; - - if (code == options[offset]) { - *ret_offset = offset; - return r; - } - } - return -ENOENT; -} - -int dhcp_option_remove_option(uint8_t *options, size_t length, uint8_t option_code) { - int r; - size_t offset; - - assert(options); - - r = dhcp_option_find_option(options, length, option_code, &offset); - if (r < 0) - return r; - - memmove(options + offset, options + offset + r, length - offset - r); - return length - r; -} - -int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, - uint8_t overload, - uint8_t code, size_t optlen, const void *optval) { - const bool use_file = overload & DHCP_OVERLOAD_FILE; - const bool use_sname = overload & DHCP_OVERLOAD_SNAME; - int r; - - assert(message); - assert(offset); - - /* If *offset is in range [0, size), we are writing to ->options, - * if *offset is in range [size, size + sizeof(message->file)) and use_file, we are writing to ->file, - * if *offset is in range [size + use_file*sizeof(message->file), size + use_file*sizeof(message->file) + sizeof(message->sname)) - * and use_sname, we are writing to ->sname. - */ - - if (*offset < size) { - /* still space in the options array */ - r = option_append(message->options, size, offset, code, optlen, optval); - if (r >= 0) - return 0; - else if (r == -ENOBUFS && (use_file || use_sname)) { - /* did not fit, but we have more buffers to try - close the options array and move the offset to its end */ - r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - *offset = size; - } else - return r; - } - - if (use_file) { - size_t file_offset = *offset - size; - - if (file_offset < sizeof(message->file)) { - /* still space in the 'file' array */ - r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval); - if (r >= 0) { - *offset = size + file_offset; - return 0; - } else if (r == -ENOBUFS && use_sname) { - /* did not fit, but we have more buffers to try - close the file array and move the offset to its end */ - r = option_append(message->file, sizeof(message->file), &file_offset, SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - *offset = size + sizeof(message->file); - } else - return r; - } - } - - if (use_sname) { - size_t sname_offset = *offset - size - use_file*sizeof(message->file); - - if (sname_offset < sizeof(message->sname)) { - /* still space in the 'sname' array */ - r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval); - if (r >= 0) { - *offset = size + use_file*sizeof(message->file) + sname_offset; - return 0; - } else - /* no space, or other error, give up */ - return r; - } - } - - return -ENOBUFS; -} - -static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload, - uint8_t *message_type, char **error_message, dhcp_option_callback_t cb, - void *userdata) { - uint8_t code, len; - const uint8_t *option; - size_t offset = 0; - int r; - - while (offset < buflen) { - code = options[offset++]; - - switch (code) { - case SD_DHCP_OPTION_PAD: - continue; - - case SD_DHCP_OPTION_END: - return 0; - } - - if (buflen < offset + 1) - return -ENOBUFS; - - len = options[offset++]; - - if (buflen < offset + len) - return -EINVAL; - - option = &options[offset]; - - switch (code) { - case SD_DHCP_OPTION_MESSAGE_TYPE: - if (len != 1) - return -EINVAL; - - if (message_type) - *message_type = *option; - - break; - - case SD_DHCP_OPTION_ERROR_MESSAGE: - if (len == 0) - return -EINVAL; - - if (error_message) { - _cleanup_free_ char *string = NULL; - - r = make_cstring((const char*) option, len, MAKE_CSTRING_ALLOW_TRAILING_NUL, &string); - if (r < 0) - return r; - - if (!ascii_is_valid(string)) - return -EINVAL; - - free_and_replace(*error_message, string); - } - - break; - case SD_DHCP_OPTION_OVERLOAD: - if (len != 1) - return -EINVAL; - - if (overload) - *overload = *option; - - break; - - default: - if (cb) - cb(code, len, option, userdata); - } - - offset += len; - } - - if (offset < buflen) - return -EINVAL; - - return 0; -} - -int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **ret_error_message) { - _cleanup_free_ char *error_message = NULL; - uint8_t overload = 0; - uint8_t message_type = 0; - int r; - - if (!message) - return -EINVAL; - - if (len < sizeof(DHCPMessage)) - return -EINVAL; - - len -= sizeof(DHCPMessage); - - r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata); - if (r < 0) - return r; - - if (overload & DHCP_OVERLOAD_FILE) { - r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata); - if (r < 0) - return r; - } - - if (overload & DHCP_OVERLOAD_SNAME) { - r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata); - if (r < 0) - return r; - } - - if (message_type == 0) - return -ENOMSG; - - if (ret_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE)) - *ret_error_message = TAKE_PTR(error_message); - - return message_type; -} - -int dhcp_option_parse_string(const uint8_t *option, size_t len, char **ret) { - _cleanup_free_ char *string = NULL; - int r; - - assert(option); - assert(ret); - - if (len <= 0) { - *ret = NULL; - return 0; - } - - /* One trailing NUL byte is OK, we don't mind. See: - * https://github.com/systemd/systemd/issues/1337 */ - r = make_cstring((const char *) option, len, MAKE_CSTRING_ALLOW_TRAILING_NUL, &string); - if (r < 0) - return r; - - if (!string_is_safe(string) || !utf8_is_valid(string)) - return -EINVAL; - - *ret = TAKE_PTR(string); - return 0; -} - -int dhcp_option_parse_hostname(const uint8_t *option, size_t len, char **ret) { - _cleanup_free_ char *hostname = NULL; - int r; - - assert(option); - assert(ret); - - r = dhcp_option_parse_string(option, len, &hostname); - if (r < 0) - return r; - - if (!hostname) { - *ret = NULL; - return 0; - } - - if (!hostname_is_valid(hostname, 0)) - return -EINVAL; - - r = dns_name_is_valid(hostname); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - *ret = TAKE_PTR(hostname); - return 0; -} - -static sd_dhcp_option* dhcp_option_free(sd_dhcp_option *i) { - if (!i) - return NULL; - - free(i->data); - return mfree(i); -} - -int sd_dhcp_option_new(uint8_t option, const void *data, size_t length, sd_dhcp_option **ret) { - assert_return(ret, -EINVAL); - assert_return(length == 0 || data, -EINVAL); - - _cleanup_free_ void *q = memdup(data, length); - if (!q) - return -ENOMEM; - - sd_dhcp_option *p = new(sd_dhcp_option, 1); - if (!p) - return -ENOMEM; - - *p = (sd_dhcp_option) { - .n_ref = 1, - .option = option, - .length = length, - .data = TAKE_PTR(q), - }; - - *ret = TAKE_PTR(p); - return 0; -} - -DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_option, sd_dhcp_option, dhcp_option_free); -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( - dhcp_option_hash_ops, - void, - trivial_hash_func, - trivial_compare_func, - sd_dhcp_option, - sd_dhcp_option_unref); diff --git a/src/libsystemd-network/dhcp-option.h b/src/libsystemd-network/dhcp-option.h deleted file mode 100644 index 5ca1cafe388ae..0000000000000 --- a/src/libsystemd-network/dhcp-option.h +++ /dev/null @@ -1,46 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "sd-dhcp-option.h" /* IWYU pragma: export */ - -#include "dhcp-protocol.h" -#include "sd-forward.h" -#include "hash-funcs.h" - -struct sd_dhcp_option { - unsigned n_ref; - - uint8_t option; - void *data; - size_t length; -}; - -extern const struct hash_ops dhcp_option_hash_ops; - -typedef struct DHCPServerData { - struct in_addr *addr; - size_t size; -} DHCPServerData; - -int dhcp_option_append( - DHCPMessage *message, - size_t size, - size_t *offset, - uint8_t overload, - uint8_t code, - size_t optlen, - const void *optval); -int dhcp_option_find_option(uint8_t *options, size_t length, uint8_t wanted_code, size_t *ret_offset); -int dhcp_option_remove_option(uint8_t *options, size_t length, uint8_t option_code); - -typedef int (*dhcp_option_callback_t)(uint8_t code, uint8_t len, const void *option, void *userdata); - -int dhcp_option_parse( - DHCPMessage *message, - size_t len, - dhcp_option_callback_t cb, - void *userdata, - char **ret_error_message); - -int dhcp_option_parse_string(const uint8_t *option, size_t len, char **ret); -int dhcp_option_parse_hostname(const uint8_t *option, size_t len, char **ret); diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c deleted file mode 100644 index 90eae88379ad5..0000000000000 --- a/src/libsystemd-network/dhcp-packet.c +++ /dev/null @@ -1,211 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/*** - Copyright © 2013 Intel Corporation. All rights reserved. -***/ - -#include -#include - -#include "dhcp-option.h" -#include "dhcp-packet.h" -#include "log.h" -#include "memory-util.h" - -#define DHCP_CLIENT_MIN_OPTIONS_SIZE 312 - -int bootp_message_init( - DHCPMessage *message, - uint8_t op, - uint32_t xid, - uint16_t arp_type, - uint8_t hlen, - const uint8_t *chaddr) { - - assert(message); - assert(IN_SET(op, BOOTREQUEST, BOOTREPLY)); - assert(chaddr || hlen == 0); - - message->op = op; - message->htype = arp_type; - - /* RFC2131 section 4.1.1: - The client MUST include its hardware address in the ’chaddr’ field, if - necessary for delivery of DHCP reply messages. - - RFC 4390 section 2.1: - A DHCP client, when working over an IPoIB interface, MUST follow the - following rules: - "htype" (hardware address type) MUST be 32 [ARPPARAM]. - "hlen" (hardware address length) MUST be 0. - "chaddr" (client hardware address) field MUST be zeroed. - */ - message->hlen = arp_type == ARPHRD_INFINIBAND ? 0 : hlen; - memcpy_safe(message->chaddr, chaddr, message->hlen); - - message->xid = htobe32(xid); - message->magic = htobe32(DHCP_MAGIC_COOKIE); - - return 0; -} - -int dhcp_message_init( - DHCPMessage *message, - uint8_t op, - uint32_t xid, - uint16_t arp_type, - uint8_t hlen, - const uint8_t *chaddr, - uint8_t type, - size_t optlen, - size_t *ret_optoffset) { - - size_t offset = 0; - int r; - - assert(message); - assert(chaddr || hlen == 0); - assert(ret_optoffset); - - r = bootp_message_init(message, op, xid, arp_type, hlen, chaddr); - if (r < 0) - return r; - - r = dhcp_option_append(message, optlen, &offset, 0, - SD_DHCP_OPTION_MESSAGE_TYPE, 1, &type); - if (r < 0) - return r; - - *ret_optoffset = offset; - 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); - - packet->ip.check = packet->udp.len; - packet->udp.check = dhcp_packet_checksum(&packet->ip.ttl, len - 8); - - packet->ip.ttl = IPDEFTTL; - packet->ip.check = 0; - packet->ip.check = dhcp_packet_checksum((uint8_t*)&packet->ip, DHCP_IP_SIZE); -} - -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; -} diff --git a/src/libsystemd-network/dhcp-packet.h b/src/libsystemd-network/dhcp-packet.h deleted file mode 100644 index 967bd5d89df71..0000000000000 --- a/src/libsystemd-network/dhcp-packet.h +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "dhcp-protocol.h" -#include "sd-forward.h" - -int bootp_message_init( - DHCPMessage *message, - uint8_t op, - uint32_t xid, - uint16_t arp_type, - uint8_t hlen, - const uint8_t *chaddr); - -int dhcp_message_init( - DHCPMessage *message, - uint8_t op, - uint32_t xid, - uint16_t arp_type, - uint8_t hlen, - const uint8_t *chaddr, - uint8_t type, - size_t optlen, - size_t *ret_optoffset); - -uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len); - -void dhcp_packet_append_ip_headers( - DHCPPacket *packet, - be32_t source_addr, - uint16_t source, - be32_t destination_addr, - uint16_t destination, - uint16_t len, - int ip_service_type); - -int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum, uint16_t port); diff --git a/src/libsystemd-network/dhcp-protocol.c b/src/libsystemd-network/dhcp-protocol.c new file mode 100644 index 0000000000000..3a42087766b0d --- /dev/null +++ b/src/libsystemd-network/dhcp-protocol.c @@ -0,0 +1,204 @@ +/* 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_INFORMATION] = "vendor-specific information", + [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_IDENTIFYING_VENDOR_CLASS] = "vendor-identifying vendor class", + [SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION] = "vendor-identifying 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); + +static const char* const dhcp_state_table[_DHCP_STATE_MAX] = { + [DHCP_STATE_STOPPED] = "stopped", + [DHCP_STATE_INIT] = "initialization", + [DHCP_STATE_SELECTING] = "selecting", + [DHCP_STATE_INIT_REBOOT] = "init-reboot", + [DHCP_STATE_REBOOTING] = "rebooting", + [DHCP_STATE_REQUESTING] = "requesting", + [DHCP_STATE_BOUND] = "bound", + [DHCP_STATE_RENEWING] = "renewing", + [DHCP_STATE_REBINDING] = "rebinding", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_state, DHCPState); diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index 425f730894d2b..c366cf2202a98 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -5,70 +5,56 @@ Copyright © 2013 Intel Corporation. All rights reserved. ***/ -#include -#include +#include "sd-dhcp-protocol.h" /* IWYU pragma: export */ -#include "sd-dhcp-protocol.h" - -#include "sd-forward.h" -#include "sparse-endian.h" +#include "dhcp-forward.h" #include "time-util.h" /* RFC 8925 - IPv6-Only Preferred Option for DHCPv4 3.4. * MIN_V6ONLY_WAIT: The lower boundary for V6ONLY_WAIT. Value: 300 seconds */ #define MIN_V6ONLY_WAIT_USEC (300U * USEC_PER_SEC) -#define DHCP_MESSAGE_HEADER_DEFINITION \ - uint8_t op; \ - uint8_t htype; \ - uint8_t hlen; \ - uint8_t hops; \ - be32_t xid; \ - be16_t secs; \ - be16_t flags; \ - be32_t ciaddr; \ - be32_t yiaddr; \ - be32_t siaddr; \ - be32_t giaddr; \ - uint8_t chaddr[16]; \ - uint8_t sname[64]; \ - uint8_t file[128]; \ +struct DHCPMessageHeader { + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + be32_t xid; + be16_t secs; + be16_t flags; + be32_t ciaddr; + be32_t yiaddr; + be32_t siaddr; + be32_t giaddr; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; be32_t magic; - -struct DHCPMessage { - DHCP_MESSAGE_HEADER_DEFINITION; - uint8_t options[]; -} _packed_; - -typedef struct DHCPMessage DHCPMessage; - -struct DHCPPacket { - struct iphdr ip; - struct udphdr udp; - DHCPMessage dhcp; } _packed_; -typedef struct DHCPPacket DHCPPacket; +typedef struct DHCPMessageHeader DHCPMessageHeader; -#define DHCP_IP_SIZE (int32_t)(sizeof(struct iphdr)) -#define DHCP_IP_UDP_SIZE (int32_t)(sizeof(struct udphdr) + DHCP_IP_SIZE) -#define DHCP_HEADER_SIZE (int32_t)(sizeof(DHCPMessage)) -#define DHCP_MIN_MESSAGE_SIZE 576 /* the minimum internet hosts must be able to receive, see RFC 2132 Section 9.10 */ -#define DHCP_MIN_OPTIONS_SIZE (DHCP_MIN_MESSAGE_SIZE - DHCP_HEADER_SIZE) -#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 +64,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 +94,26 @@ enum { DHCP_FQDN_FLAG_E = (1 << 2), DHCP_FQDN_FLAG_N = (1 << 3), }; + +/* For SD_DHCP_RELAY_AGENT_FLAGS sub-option. */ +enum { + DHCP_RELAY_AGENT_FLAG_UNICAST = 1 << 0, +}; + +DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_option_code, int); + +typedef enum DHCPState { + DHCP_STATE_STOPPED, + DHCP_STATE_INIT, + DHCP_STATE_SELECTING, + DHCP_STATE_INIT_REBOOT, + DHCP_STATE_REBOOTING, + DHCP_STATE_REQUESTING, + DHCP_STATE_BOUND, + DHCP_STATE_RENEWING, + DHCP_STATE_REBINDING, + _DHCP_STATE_MAX, + _DHCP_STATE_INVALID = -EINVAL, +} DHCPState; + +DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_state, DHCPState); diff --git a/src/libsystemd-network/dhcp-relay-downstream.c b/src/libsystemd-network/dhcp-relay-downstream.c new file mode 100644 index 0000000000000..5c3311d979aa0 --- /dev/null +++ b/src/libsystemd-network/dhcp-relay-downstream.c @@ -0,0 +1,502 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "dhcp-message.h" +#include "dhcp-relay-internal.h" +#include "errno-util.h" +#include "ether-addr-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "siphash24.h" +#include "socket-util.h" + +int sd_dhcp_relay_downstream_set_circuit_id(sd_dhcp_relay_interface *interface, const struct iovec *iov) { + assert_return(interface, -EINVAL); + assert_return(!interface->upstream, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + return iovec_done_and_memdup(&interface->circuit_id, iov); +} + +int sd_dhcp_relay_downstream_set_virtual_subnet_selection(sd_dhcp_relay_interface *interface, const struct iovec *iov) { + assert_return(interface, -EINVAL); + assert_return(!interface->upstream, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + return iovec_done_and_memdup(&interface->vss, iov); +} + +int downstream_set_extra_options(sd_dhcp_relay_interface *interface, TLV *options) { + assert(interface); + assert(!interface->upstream); + assert(!sd_dhcp_relay_interface_is_running(interface)); + + return unref_and_replace_new_ref(interface->extra_options, options, tlv_ref, tlv_unref); +} + +int sd_dhcp_relay_downstream_set_gateway_address(sd_dhcp_relay_interface *interface, const struct in_addr *address) { + assert_return(interface, -EINVAL); + assert_return(!interface->upstream, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + if (address) + interface->gateway_address = *address; + else + interface->gateway_address = (struct in_addr) {}; + + return 0; +} + +static void downstream_hash_func(const sd_dhcp_relay_interface *interface, struct siphash *state) { + int b; + + assert(interface); + assert(!interface->upstream); + assert(state); + + siphash24_compress_typesafe(interface->gateway_address, state); + + b = iovec_is_set(&interface->circuit_id); + siphash24_compress_typesafe(b, state); + if (b) + siphash24_compress_iovec(&interface->circuit_id, state); + + b = iovec_is_set(&interface->vss); + siphash24_compress_typesafe(b, state); + if (b) + siphash24_compress_iovec(&interface->vss, state); +} + +static int downstream_compare_func(const sd_dhcp_relay_interface *a, const sd_dhcp_relay_interface *b) { + int r; + + assert(a); + assert(!a->upstream); + assert(b); + assert(!b->upstream); + + r = CMP(a->gateway_address.s_addr, b->gateway_address.s_addr); + if (r != 0) + return r; + + r = iovec_memcmp(&a->circuit_id, &b->circuit_id); + if (r != 0) + return r; + + return iovec_memcmp(&a->vss, &b->vss); +} + +DEFINE_PRIVATE_HASH_OPS( + downstream_hash_ops, + sd_dhcp_relay_interface, + downstream_hash_func, + downstream_compare_func); + +int downstream_register(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(interface->relay); + assert(!interface->upstream); + assert(in4_addr_is_set(&interface->address)); + assert(in4_addr_is_set(&interface->gateway_address)); + assert(!sd_dhcp_relay_interface_is_running(interface)); + + /* Do not use a Set; otherwise, we cannot deduplicate entries. */ + return hashmap_ensure_put(&interface->relay->downstream_interfaces, &downstream_hash_ops, interface, interface); +} + +void downstream_unregister(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(interface->relay); + assert(!interface->upstream); + + hashmap_remove_value(interface->relay->downstream_interfaces, interface, interface); +} + +void downstream_done(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(!interface->upstream); + + downstream_unregister(interface); + iovec_done(&interface->circuit_id); + iovec_done(&interface->vss); + interface->extra_options = tlv_unref(interface->extra_options); +} + +int downstream_get(sd_dhcp_relay *relay, sd_dhcp_message *message, sd_dhcp_relay_interface **ret) { + int r; + + assert(relay); + assert(message); + + /* RFC 3046 section 2.2: + * DHCP servers claiming to support the Relay Agent Information option SHALL echo the entire contents + * of the Relay Agent Information option in all replies. + * + * So, first try to find the suitable downstream interface by the gateway address and circuit ID in + * the reply message. */ + sd_dhcp_relay_interface key = { + .gateway_address.s_addr = message->header.giaddr, + }; + + _cleanup_(tlv_unrefp) TLV *agent_info = NULL; + r = dhcp_message_get_option_sub_tlv( + message, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, + TLV_DHCP4_SUBOPTION, + &agent_info); + if (r < 0 && r != -ENODATA) + return r; + + if (agent_info) { + r = tlv_get(agent_info, SD_DHCP_RELAY_AGENT_CIRCUIT_ID, &key.circuit_id); + if (r < 0 && r != -ENODATA) + return r; + + r = tlv_get(agent_info, SD_DHCP_RELAY_AGENT_VIRTUAL_SUBNET_SELECTION, &key.vss); + if (r < 0 && r != -ENODATA) + return r; + } + + sd_dhcp_relay_interface *interface = hashmap_get(relay->downstream_interfaces, &key); + if (!interface) { + /* Some DHCP servers may not understand the Relay Agent Information option and may not echo + * it back. To support this case, we fall back to finding a suitable downstream interface + * using only the gateway address. Note that if the downstream network uses VRF or the Link + * Selection sub-option, multiple interfaces may share the same gateway address. In such + * cases, we cannot reliably determine the correct downstream interface, so we must drop the + * packet. */ + sd_dhcp_relay_interface *i; + HASHMAP_FOREACH(i, relay->downstream_interfaces) { + if (i->gateway_address.s_addr != message->header.giaddr) + continue; + + if (interface) + /* multiple interfaces have the same gateway address?? */ + return -ENOTUNIQ; + + interface = i; + } + } + if (!interface) + return -ENODEV; + + assert(!interface->upstream); + assert(interface->io_event_source); + + if (ret) + *ret = interface; + return 0; +} + +static int downstream_append_relay_agent_information( + sd_dhcp_relay_interface *interface, + sd_dhcp_message *message, + const struct in_pktinfo *pktinfo) { + + int r; + + assert(interface); + assert(interface->relay); + assert(!interface->upstream); + assert(message); + + _cleanup_(tlv_done) TLV tlv = TLV_INIT(TLV_DHCP4_SUBOPTION); + + /* First, set per-interface options. */ + if (iovec_is_set(&interface->circuit_id)) { + r = tlv_append_iov(&tlv, SD_DHCP_RELAY_AGENT_CIRCUIT_ID, &interface->circuit_id); + if (r < 0) + return r; + } + + if (iovec_is_set(&interface->vss)) { + r = tlv_append_iov(&tlv, SD_DHCP_RELAY_AGENT_VIRTUAL_SUBNET_SELECTION, &interface->vss); + if (r < 0) + return r; + } + + if (!in4_addr_equal(&interface->address, &interface->gateway_address)) { + /* RFC 3527 section 3 + * The link-selection sub-option is used by any DHCP relay agent that desires to specify a + * subnet/link for a DHCP client request that it is relaying but needs the subnet/link + * specification to be different from the IP address the DHCP server should use when + * communicating with the relay agent. */ + r = tlv_append(&tlv, SD_DHCP_RELAY_AGENT_LINK_SELECTION, sizeof(struct in_addr), &interface->address); + if (r < 0) + return r; + } + + r = tlv_append_tlv(&tlv, interface->extra_options); + if (r < 0) + return r; + + /* Then, set agent-wide options. */ + if (iovec_is_set(&interface->relay->remote_id)) { + r = tlv_append_iov(&tlv, SD_DHCP_RELAY_AGENT_REMOTE_ID, &interface->relay->remote_id); + if (r < 0) + return r; + } + + if (interface->relay->server_identifier_override) { + /* RFC 5107 section 1: + * This DHCP relay agent suboption, Server Identifier Override, allows the relay agent to + * tell the DHCP server what value to place into the Server Identifier option. Using this, + * the relay agent can force a host in RENEWING state to send DHCPREQUEST messages to the + * relay agent instead of directly to the server. */ + r = tlv_append(&tlv, SD_DHCP_RELAY_AGENT_SERVER_IDENTIFIER_OVERRIDE, sizeof(struct in_addr), &interface->address); + if (r < 0) + return r; + + /* RFC 5107 section 4: + * DHCP relay agents implementing this suboption SHOULD also implement and use the DHCPv4 + * Relay Agent Flags Suboption in order to specify whether the DHCP relay agent received the + * original message as a broadcast or unicast. */ + uint8_t flags = 0; + SET_FLAG(flags, DHCP_RELAY_AGENT_FLAG_UNICAST, + pktinfo && + pktinfo->ipi_addr.s_addr != INADDR_BROADCAST && + pktinfo->ipi_addr.s_addr != interface->subnet_broadcast.s_addr); + r = tlv_append(&tlv, SD_DHCP_RELAY_AGENT_FLAGS, sizeof(uint8_t), &flags); + if (r < 0) + return r; + } + + r = tlv_append_tlv(&tlv, interface->relay->extra_options); + if (r < 0) + return r; + + if (tlv_isempty(&tlv)) + return 0; + + return dhcp_message_append_option_sub_tlv(message, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, &tlv); +} + +int downstream_process_message( + sd_dhcp_relay_interface *interface, + const struct iovec *iov, + const struct in_pktinfo *pktinfo) { + + int r; + + assert(interface); + assert(interface->relay); + assert(!interface->upstream); + assert(in4_addr_is_set(&interface->address)); + assert(in4_addr_is_set(&interface->gateway_address)); + assert(iov); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_parse( + iov, + BOOTREQUEST, + /* xid= */ NULL, + ARPHRD_NONE, + /* hw_addr= */ NULL, + &message); + if (r < 0) + return r; + + /* RFC 1542 section 4.1.1: + * The relay agent MUST silently discard BOOTREQUEST messages whose 'hops' field exceeds the value 16. */ + if (message->header.hops >= 16) + return 0; + message->header.hops++; + + /* RFC 3046 section 2.1.1: + * Relay agents configured to add a Relay Agent option which receive a client DHCP packet with a + * nonzero giaddr SHALL discard the packet if the giaddr spoofs a giaddr address implemented by the + * local agent itself. */ + if (message->header.giaddr != INADDR_ANY) { + sd_dhcp_relay_interface *i; + HASHMAP_FOREACH(i, interface->relay->interfaces) { + if (i->upstream) + continue; + if (message->header.giaddr == i->address.s_addr || + message->header.giaddr == i->gateway_address.s_addr) + return -EBADMSG; + } + } + + /* RFC 1542 section 4.1.1: + * If the relay agent does decide to relay the request, it MUST examine the 'giaddr' ("gateway" IP + * address) field. If this field is zero, the relay agent MUST fill this field with the IP address of + * the interface on which the request was received. (snip) If the 'giaddr' field contains some + * non-zero value, the 'giaddr' field MUST NOT be modified. + * + * RFC 3046 section 2.1.1: + * the relay agent SHALL forward any received DHCP packet with a valid non-zero giaddr WITHOUT adding + * any relay agent options. Per RFC 2131, it shall also NOT modify the giaddr value. + * + * Therefore, we set giaddr and the Relay Agent Information option here only when the giaddr in the + * received message is zero. */ + if (message->header.giaddr == INADDR_ANY) { + message->header.giaddr = interface->gateway_address.s_addr; + + /* RFC 3046 section 2.1: + * Relay agents receiving a DHCP packet from an untrusted circuit with giaddr set to zero + * (indicating that they are the first-hop router) but with a Relay Agent Information option + * already present in the packet SHALL discard the packet and increment an error count. */ + if (dhcp_message_has_option(message, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION)) + return -EBADMSG; + + r = downstream_append_relay_agent_information(interface, message, pktinfo); + if (r < 0) + return r; + } + + log_dhcp_relay_interface(interface, "Received BOOTREQUEST (0x%"PRIx32")", be32toh(message->header.xid)); + + sd_dhcp_relay_interface *upstream; + r = upstream_get(interface->relay, &upstream); + if (r < 0) + return r; + + return upstream_send_message(upstream, message); +} + +static int downstream_acquire_raw_socket(sd_dhcp_relay_interface *interface) { + int r; + + assert(interface); + assert(!interface->upstream); + + if (interface->socket_fd >= 0) + /* When a socket fd is given externally, unconditionally use it. */ + return interface->socket_fd; + + if (interface->raw_socket_fd >= 0) + /* Already opened. */ + return interface->raw_socket_fd; + + /* This is a send-only socket, hence it is opened with protocol=0, and do not call bind(). + * The interface binding will be done on send. */ + _cleanup_close_ int fd = RET_NERRNO(socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, /* protocol= */ 0)); + if (fd < 0) + return fd; + + r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, tos_to_priority(interface->ip_service_type)); + if (r < 0) + return r; + + return interface->raw_socket_fd = TAKE_FD(fd); +} + +static int downstream_send_l2_unicast( + sd_dhcp_relay_interface *interface, + sd_dhcp_message *message, + const struct hw_addr_data *hw_addr) { + + int r; + + assert(interface); + assert(!interface->upstream); + assert(message); + assert(message->header.yiaddr != INADDR_ANY); + assert(!hw_addr_is_null(hw_addr)); + + int fd = downstream_acquire_raw_socket(interface); + if (fd < 0) + return fd; + + r = dhcp_message_send_raw( + message, + fd, + interface->ifindex, + interface->address.s_addr, + interface->port, + hw_addr, + message->header.yiaddr, + DHCP_PORT_CLIENT, + interface->ip_service_type); + if (r < 0) + return r; + + log_dhcp_relay_interface(interface, "Forwarded BOOTREPLY (0x%"PRIx32") to %s (L2 unicast).", + be32toh(message->header.xid), + IN4_ADDR_TO_STRING(&(struct in_addr) { .s_addr = message->header.yiaddr })); + return 0; +} + +static int downstream_send_udp( + sd_dhcp_relay_interface *interface, + sd_dhcp_message *message, + be32_t address) { + + int r; + + assert(interface); + assert(!interface->upstream); + assert(message); + assert(address != INADDR_ANY); + + int fd = sd_event_source_get_io_fd(interface->io_event_source); + if (fd < 0) + return fd; + + r = dhcp_message_send_udp( + message, + fd, + interface->address.s_addr, + address, + DHCP_PORT_CLIENT); + if (r < 0) + return r; + + log_dhcp_relay_interface(interface, "Forwarded BOOTREPLY (0x%"PRIx32") to %s (UDP).", + be32toh(message->header.xid), + IN4_ADDR_TO_STRING(&(struct in_addr) { .s_addr = address })); + return 0; +} + +int downstream_send_message(sd_dhcp_relay_interface *interface, sd_dhcp_message *message) { + int r; + + assert(interface); + assert(!interface->upstream); + assert(message); + assert(message->header.op == BOOTREPLY); + + /* See RFC 2131 Section 4.1 + * + * (Note, we are a relay agent, hence conditions for giaddr in the statements are ignored.) */ + + uint8_t type; + r = dhcp_message_get_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, &type); + if (r < 0) + return r; + + /* the server broadcasts any DHCPNAK messages to 0xffffffff. */ + if (type == DHCP_NAK) + return downstream_send_udp(interface, message, INADDR_BROADCAST); + + /* If (...) the ’ciaddr’ field is nonzero, then the server unicasts DHCPOFFER and DHCPACK messages + * to the address in ’ciaddr’. */ + if (message->header.ciaddr != INADDR_ANY) + return downstream_send_udp(interface, message, message->header.ciaddr); + + /* If (...) ’ciaddr’ is zero, and the broadcast bit is set, then the server broadcasts DHCPOFFER + * and DHCPACK messages to 0xffffffff. + * + * (Note, even the broadcast flag is unset, we may not know the client hardware address, e.g. + * InfiniBand. In that case, we cannot unicast in the below, so need to broadcast. Also, for other + * message types we do not support, also broadcast if 'yiaddr' is zero.) */ + struct hw_addr_data hw_addr = {}; + if (!dhcp_message_has_broadcast_flag(message) && + message->header.yiaddr != INADDR_ANY) { + r = dhcp_message_get_hw_addr(message, &hw_addr); + if (r < 0) + return r; + } + + if (hw_addr_is_null(&hw_addr)) + return downstream_send_udp(interface, message, INADDR_BROADCAST); + + /* If the broadcast bit is not set (...) and ’ciaddr’ is zero, then the server unicasts DHCPOFFER + * and DHCPACK messages to the client’s hardware address and ’yiaddr’ address. */ + return downstream_send_l2_unicast(interface, message, &hw_addr); +} diff --git a/src/libsystemd-network/dhcp-relay-interface.c b/src/libsystemd-network/dhcp-relay-interface.c new file mode 100644 index 0000000000000..218f56fc04c74 --- /dev/null +++ b/src/libsystemd-network/dhcp-relay-interface.c @@ -0,0 +1,334 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "alloc-util.h" +#include "dhcp-protocol.h" +#include "dhcp-relay-internal.h" +#include "errno-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "socket-util.h" +#include "string-util.h" + +static sd_dhcp_relay_interface* dhcp_relay_interface_free(sd_dhcp_relay_interface *interface) { + if (!interface) + return NULL; + + assert(interface->relay); + + sd_event_source_disable_unref(interface->io_event_source); + safe_close(interface->socket_fd); + safe_close(interface->raw_socket_fd); + + if (interface->upstream) + upstream_done(interface); + else + downstream_done(interface); + + hashmap_remove_value(interface->relay->interfaces, INT_TO_PTR(interface->ifindex), interface); + sd_dhcp_relay_unref(interface->relay); + + free(interface->ifname); + return mfree(interface); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_relay_interface, sd_dhcp_relay_interface, dhcp_relay_interface_free); + +int sd_dhcp_relay_add_interface(sd_dhcp_relay *relay, int ifindex, int is_upstream, sd_dhcp_relay_interface **ret) { + int r; + + assert_return(relay, -EINVAL); + assert_return(ifindex > 0 || (ifindex == DHCP_RELAY_IFINDEX_UNBOUND && !!is_upstream), -EINVAL); + assert_return(ret, -EINVAL); + + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *interface = new(sd_dhcp_relay_interface, 1); + if (!interface) + return -ENOMEM; + + /* RFC 1542 section 5.4: + * The server SHOULD next check the 'giaddr' field. If this field is non-zero, the server SHOULD send + * the BOOTREPLY as an IP unicast to the IP address identified in the 'giaddr' field. The UDP + * destination port MUST be set to BOOTPS (67). + * + * Hence, the relay agent needs to use DHCP_PORT_SERVER (67) for both source and destination port. */ + *interface = (sd_dhcp_relay_interface) { + .n_ref = 1, + .relay = sd_dhcp_relay_ref(relay), + .upstream = !!is_upstream, + .ifindex = ifindex, + .port = DHCP_PORT_SERVER, + .socket_fd = -EBADF, + .raw_socket_fd = -EBADF, + .ip_service_type = IPTOS_CLASS_CS6, /* Defaults to CS6 (Internetwork Control). */ + }; + + r = hashmap_ensure_put(&relay->interfaces, NULL, INT_TO_PTR(interface->ifindex), interface); + if (r < 0) + return r; + + *ret = TAKE_PTR(interface); + return 0; +} + +int sd_dhcp_relay_interface_set_ifname(sd_dhcp_relay_interface *interface, const char *ifname) { + assert_return(interface, -EINVAL); + + return free_and_strdup(&interface->ifname, ifname); +} + +int sd_dhcp_relay_interface_get_ifname(sd_dhcp_relay_interface *interface, const char **ret) { + int r; + + assert_return(interface, -EINVAL); + + r = get_ifname(interface->ifindex, &interface->ifname); + if (r < 0) + return r; + + if (ret) + *ret = interface->ifname; + + return 0; +} + +int sd_dhcp_relay_interface_set_address(sd_dhcp_relay_interface *interface, const struct in_addr *address, uint8_t prefixlen) { + assert_return(interface, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + assert_return(prefixlen <= sizeof(struct in_addr) * 8, -EINVAL); + + if (address) + interface->address = *address; + else + interface->address = (struct in_addr) {}; + + if (in4_addr_is_set(&interface->address)) { + interface->address_prefixlen = prefixlen; + + struct in_addr netmask; + in4_addr_prefixlen_to_netmask(&netmask, prefixlen); + interface->subnet_broadcast.s_addr = (interface->address.s_addr & netmask.s_addr) | ~netmask.s_addr; + } else { + interface->address_prefixlen = 0; + interface->subnet_broadcast = (struct in_addr) {}; + } + + return 0; +} + +int sd_dhcp_relay_interface_get_address(sd_dhcp_relay_interface *interface, struct in_addr *ret_address, uint8_t *ret_prefixlen) { + assert_return(interface, -EINVAL); + + if (ret_address) + *ret_address = interface->address; + if (ret_prefixlen) + *ret_prefixlen = interface->address_prefixlen; + + return in4_addr_is_set(&interface->address); +} + +int sd_dhcp_relay_interface_set_port(sd_dhcp_relay_interface *interface, uint16_t port) { + assert_return(interface, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + interface->port = port; + return 0; +} + +int sd_dhcp_relay_interface_set_ip_service_type(sd_dhcp_relay_interface *interface, uint8_t type) { + assert_return(interface, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + interface->ip_service_type = type; + return 0; +} + +int sd_dhcp_relay_interface_is_running(sd_dhcp_relay_interface *interface) { + return interface && sd_event_source_get_enabled(interface->io_event_source, /* ret= */ NULL) > 0; +} + +static int interface_open_socket(sd_dhcp_relay_interface *interface) { + int r; + + assert(interface); + + _cleanup_close_ int fd = RET_NERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + if (fd < 0) + return fd; + + if (interface->ifindex > 0) { + r = socket_bind_to_ifindex(fd, interface->ifindex); + if (r < 0) + return r; + } + + r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_BROADCAST, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, tos_to_priority(interface->ip_service_type)); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_TOS, interface->ip_service_type); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_PKTINFO, true); + if (r < 0) + return r; + + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(interface->port), + .in.sin_addr.s_addr = INADDR_ANY, + }; + + if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) + return -errno; + + return TAKE_FD(fd); +} + +static int interface_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_relay_interface *interface = ASSERT_PTR(userdata); + int r; + + assert(fd >= 0); + + ssize_t buflen = next_datagram_size_fd(fd); + if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen)) + return 0; + if (buflen < 0) { + log_dhcp_relay_interface_errno( + interface, buflen, + "Failed to determine datagram size to read, ignoring: %m"); + return 0; + } + + _cleanup_free_ void *buf = malloc0(buflen); + if (!buf) + return log_oom_debug(); + + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + + ssize_t len = recvmsg_safe(fd, &msg, MSG_DONTWAIT); + if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len)) + return 0; + if (len < 0) { + log_dhcp_relay_interface_errno( + interface, len, + "Could not receive message, ignoring: %m"); + return 0; + } + + if (interface->upstream) + r = upstream_process_message( + interface, + &IOVEC_MAKE(buf, len), + CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo)); + else + r = downstream_process_message( + interface, + &IOVEC_MAKE(buf, len), + CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo)); + if (r < 0) + log_dhcp_relay_interface_errno( + interface, r, + "Could not process message, ignoring: %m"); + + return 0; +} + +int sd_dhcp_relay_interface_start(sd_dhcp_relay_interface *interface) { + int r; + + assert_return(interface, -EINVAL); + assert_return(interface->relay, -ESTALE); + assert_return(interface->relay->event, -EINVAL); + + if (in4_addr_is_null(&interface->relay->server_address) || + (!interface->upstream && in4_addr_is_null(&interface->address)) || + (!interface->upstream && in4_addr_is_null(&interface->gateway_address))) + return -EADDRNOTAVAIL; + + if (sd_event_source_get_enabled(interface->io_event_source, /* ret= */ NULL) > 0) + return 0; /* Already started. */ + + _cleanup_close_ int fd_close = -EBADF; + int fd; + if (interface->socket_fd >= 0) + /* When a socket fd is given externally, unconditionally use it and do not close the socket + * even if we fail to set up the event source. */ + fd = interface->socket_fd; + else { + /* Otherwise, open a new socket. */ + fd = fd_close = interface_open_socket(interface); + if (fd < 0) + return fd; + } + + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + r = sd_event_add_io(interface->relay->event, &s, fd, EPOLLIN, + interface_receive_message, interface); + if (r < 0) + return r; + + r = sd_event_source_set_priority(s, interface->relay->event_priority); + if (r < 0) + return r; + + const char *name, *description; + if (sd_dhcp_relay_interface_get_ifname(interface, &name) >= 0) + description = strjoina("dhcp-relay-interface-io-event-source-", name); + else + description = "dhcp-relay-interface-io-event-source"; + (void) sd_event_source_set_description(s, description); + + if (fd_close >= 0) { + r = sd_event_source_set_io_fd_own(s, true); + if (r < 0) + return r; + TAKE_FD(fd_close); + } + + /* This may potentially fail, in which case the event source should be discarded. */ + if (interface->upstream) + r = upstream_register(interface); + else + r = downstream_register(interface); + if (r < 0) + return r; + + sd_event_source_disable_unref(interface->io_event_source); + interface->io_event_source = TAKE_PTR(s); + return 0; +} + +int sd_dhcp_relay_interface_stop(sd_dhcp_relay_interface *interface) { + if (!interface) + return 0; + + interface->raw_socket_fd = safe_close(interface->raw_socket_fd); + interface->io_event_source = sd_event_source_disable_unref(interface->io_event_source); + + if (interface->upstream) + upstream_unregister(interface); + else + downstream_unregister(interface); + return 0; +} diff --git a/src/libsystemd-network/dhcp-relay-internal.h b/src/libsystemd-network/dhcp-relay-internal.h new file mode 100644 index 0000000000000..baa6ecab06cae --- /dev/null +++ b/src/libsystemd-network/dhcp-relay-internal.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +#include "sd-dhcp-relay.h" + +#include "dhcp-forward.h" +#include "network-common.h" + +#define DHCP_RELAY_IFINDEX_UNBOUND (-100) + +struct sd_dhcp_relay { + unsigned n_ref; + + sd_event *event; + int event_priority; + + Hashmap *interfaces; /* All interfaces by their ifindex. */ + Prioq *upstream_interfaces; /* Active upstream interfaces by their priorities. */ + Hashmap *downstream_interfaces; /* Active downstream interfaces by their gateway address, circuit ID, and VSS. */ + + struct in_addr server_address; + uint16_t server_port; + + /* Global Relay Agent Information option (82) */ + struct iovec remote_id; /* Agent Remote ID Sub-option (2) */ + bool server_identifier_override; /* Relay Agent Flags (10) and Server Identifier Override Sub-option (11) */ + TLV *extra_options; +}; + +struct sd_dhcp_relay_interface { + unsigned n_ref; + + sd_dhcp_relay *relay; + bool upstream; + + int ifindex; + char *ifname; + + /* The address used for: + * - the source IP of forwarded packets (both downstream and upstream), + * - the Server Identifier Override Sub-option (when sd_dhcp_relay.server_identifier_override is true), + * - the Link Selection Sub-option (when address != gateway_address). + * Typically, this is an address of the interface itself, but we can specify an address of another + * interface (e.g., for IP unnumbered setups). */ + struct in_addr address; + uint8_t address_prefixlen; + /* subnet-directed broadcast address, e.g. 192.0.2.255 when address is 192.0.2.1/24. */ + struct in_addr subnet_broadcast; + uint16_t port; + + uint8_t ip_service_type; /* a.k.a. TOS */ + int socket_fd; /* socket fd set externally, used by unit tests */ + int raw_socket_fd; /* send-only raw socket fd, used on sending L2 unicast message. */ + sd_event_source *io_event_source; + + /* Mutually exclusive fields depending on the 'upstream' boolean */ + union { + /* Upstream specific */ + struct { + int priority; + unsigned priority_idx; + }; + + /* Downstream specific */ + struct { + /* The address set in the giaddr field of the DHCP message header. Typically, it is + * the same as 'address' above, but we can specify a different address, and it does + * not need to be an address assigned to the interface. */ + struct in_addr gateway_address; + + /* Per-interface Relay Agent Information option (82) */ + struct iovec circuit_id; /* Agent Circuit ID Sub-option (1) */ + struct iovec vss; /* DHCPv4 Virtual Subnet Selection Sub-Option (151) */ + TLV *extra_options; + }; + }; +}; + +int dhcp_relay_set_extra_options(sd_dhcp_relay *relay, TLV *options); + +int downstream_set_extra_options(sd_dhcp_relay_interface *interface, TLV *options); +int downstream_register(sd_dhcp_relay_interface *interface); +void downstream_unregister(sd_dhcp_relay_interface *interface); +void downstream_done(sd_dhcp_relay_interface *interface); +int downstream_get(sd_dhcp_relay *relay, sd_dhcp_message *message, sd_dhcp_relay_interface **ret); +int downstream_process_message( + sd_dhcp_relay_interface *interface, + const struct iovec *iov, + const struct in_pktinfo *pktinfo); +int downstream_send_message(sd_dhcp_relay_interface *interface, sd_dhcp_message *message); + +int upstream_register(sd_dhcp_relay_interface *interface); +void upstream_unregister(sd_dhcp_relay_interface *interface); +void upstream_done(sd_dhcp_relay_interface *interface); +int upstream_get(sd_dhcp_relay *relay, sd_dhcp_relay_interface **ret); +int upstream_process_message( + sd_dhcp_relay_interface *interface, + const struct iovec *iov, + const struct in_pktinfo *pktinfo); +int upstream_send_message(sd_dhcp_relay_interface *interface, sd_dhcp_message *message); + +#define log_dhcp_relay_interface_errno(interface, error, fmt, ...) \ + log_interface_prefix_full_errno( \ + "DHCPv4 relay: ", \ + sd_dhcp_relay_interface, interface, \ + error, fmt, ##__VA_ARGS__) +#define log_dhcp_relay_interface(interface, fmt, ...) \ + log_interface_prefix_full_errno_zerook( \ + "DHCPv4 relay: ", \ + sd_dhcp_relay_interface, interface, \ + 0, fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/dhcp-relay-upstream.c b/src/libsystemd-network/dhcp-relay-upstream.c new file mode 100644 index 0000000000000..fa3dbc02573e4 --- /dev/null +++ b/src/libsystemd-network/dhcp-relay-upstream.c @@ -0,0 +1,149 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "dhcp-message.h" +#include "dhcp-relay-internal.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "prioq.h" + +int sd_dhcp_relay_upstream_set_priority(sd_dhcp_relay_interface *interface, int priority) { + assert_return(interface, -EINVAL); + assert_return(interface->upstream, -EINVAL); + assert_return(!sd_dhcp_relay_interface_is_running(interface), -EBUSY); + + interface->priority = priority; + return 0; +} + +static int upstream_compare_func(const sd_dhcp_relay_interface *a, const sd_dhcp_relay_interface *b) { + assert(a); + assert(a->upstream); + assert(b); + assert(b->upstream); + + /* Higher priority first */ + return CMP(b->priority, a->priority); +} + +int upstream_register(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(interface->relay); + assert(interface->upstream); + assert(!sd_dhcp_relay_interface_is_running(interface)); + + interface->priority_idx = PRIOQ_IDX_NULL; + return prioq_ensure_put(&interface->relay->upstream_interfaces, upstream_compare_func, interface, &interface->priority_idx); +} + +void upstream_unregister(sd_dhcp_relay_interface *interface) { + assert(interface); + assert(interface->relay); + assert(interface->upstream); + + (void) prioq_remove(interface->relay->upstream_interfaces, interface, &interface->priority_idx); +} + +void upstream_done(sd_dhcp_relay_interface *interface) { + upstream_unregister(interface); +} + +int upstream_get(sd_dhcp_relay *relay, sd_dhcp_relay_interface **ret) { + sd_dhcp_relay_interface *interface = prioq_peek(relay->upstream_interfaces); + if (!interface) + return -ENETDOWN; + + assert(interface->upstream); + assert(interface->io_event_source); + + if (ret) + *ret = interface; + return 0; +} + +int upstream_process_message( + sd_dhcp_relay_interface *interface, + const struct iovec *iov, + const struct in_pktinfo *pktinfo) { + + int r; + + assert(interface); + assert(interface->relay); + assert(interface->upstream); + assert(iov); + + if (pktinfo && pktinfo->ipi_ifindex > 0 && + interface->ifindex < 0 && + hashmap_contains(interface->relay->interfaces, INT_TO_PTR(pktinfo->ipi_ifindex))) + return 0; /* This message is not for the catch-all interface. */ + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_parse( + iov, + BOOTREPLY, + /* xid= */ NULL, + ARPHRD_NONE, + /* hw_addr= */ NULL, + &message); + if (r < 0) + return r; + + if (message->header.giaddr == INADDR_ANY) + return 0; /* Not a relay message, so it is probably not for us. */ + + if (pktinfo && pktinfo->ipi_addr.s_addr != message->header.giaddr) + return -EBADMSG; + + log_dhcp_relay_interface(interface, "Received BOOTREPLY (0x%"PRIx32")", be32toh(message->header.xid)); + + sd_dhcp_relay_interface *downstream; + r = downstream_get(interface->relay, message, &downstream); + if (r < 0) + return r; + + /* RFC 3046 abstract: + * The DHCP Server echoes the option back verbatim to the relay agent in server-to-client + * replies, and the relay agent strips the option before forwarding the reply to the client. + * + * RFC 3046 section 2.1: + * The Relay Agent Information option echoed by a server MUST be removed by either the relay + * agent or the trusted downstream network element which added it when forwarding a + * server-to-client response back to the client. + * + * Here, we do not check the contents of the option, and unconditionally remove it. */ + dhcp_message_remove_option(message, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION); + + return downstream_send_message(downstream, message); +} + +int upstream_send_message(sd_dhcp_relay_interface *interface, sd_dhcp_message *message) { + int r; + + assert(interface); + assert(interface->upstream); + assert(message); + assert(message->header.op == BOOTREQUEST); + assert(message->header.giaddr != INADDR_ANY); + + int fd = sd_event_source_get_io_fd(interface->io_event_source); + if (fd < 0) + return fd; + + r = dhcp_message_send_udp( + message, + fd, + interface->address.s_addr, + interface->relay->server_address.s_addr, + interface->relay->server_port); + if (r < 0) + return r; + + log_dhcp_relay_interface(interface, "Forwarded BOOTREQUEST (0x%"PRIx32") to %s (UDP).", + be32toh(message->header.xid), + IN4_ADDR_TO_STRING(&interface->relay->server_address)); + return 0; +} diff --git a/src/libsystemd-network/dhcp-route.c b/src/libsystemd-network/dhcp-route.c new file mode 100644 index 0000000000000..6a7f9d7da9ad1 --- /dev/null +++ b/src/libsystemd-network/dhcp-route.c @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dhcp-lease.h" + +#include "dhcp-route.h" + +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *ret) { + assert_return(route, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = route->dst_addr; + return 0; +} + +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *ret) { + assert_return(route, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = route->dst_prefixlen; + return 0; +} + +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *ret) { + assert_return(route, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = route->gw_addr; + return 0; +} diff --git a/src/libsystemd-network/dhcp-route.h b/src/libsystemd-network/dhcp-route.h new file mode 100644 index 0000000000000..9d2b0b4a00346 --- /dev/null +++ b/src/libsystemd-network/dhcp-route.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "dhcp-forward.h" /* IWYU pragma: export */ + +struct sd_dhcp_route { + struct in_addr dst_addr; + struct in_addr gw_addr; + uint8_t dst_prefixlen; +}; diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index 8b7c856d218af..f2d85b6e6c0ee 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -5,40 +5,26 @@ Copyright © 2013 Intel Corporation. All rights reserved. ***/ -#include "sd-dhcp-lease.h" +#include + #include "sd-dhcp-server.h" -#include "dhcp-client-id-internal.h" -#include "dhcp-option.h" -#include "sd-forward.h" +#include "dhcp-forward.h" +#include "dhcp-message.h" #include "network-common.h" -#include "sparse-endian.h" - -typedef enum DHCPRawOption { - DHCP_RAW_OPTION_DATA_UINT8, - DHCP_RAW_OPTION_DATA_UINT16, - DHCP_RAW_OPTION_DATA_UINT32, - DHCP_RAW_OPTION_DATA_STRING, - DHCP_RAW_OPTION_DATA_IPV4ADDRESS, - DHCP_RAW_OPTION_DATA_IPV6ADDRESS, - _DHCP_RAW_OPTION_DATA_MAX, - _DHCP_RAW_OPTION_DATA_INVALID, -} DHCPRawOption; typedef struct sd_dhcp_server { unsigned n_ref; sd_event *event; int event_priority; - sd_event_source *receive_message; - sd_event_source *receive_broadcast; - int fd; - int fd_raw; - int fd_broadcast; + sd_event_source *io_event_source; + uint8_t ip_service_type; + int socket_fd; /* socket fd set externally, used by unit tests */ + int raw_socket_fd; /* send-only raw socket fd, used on sending L2 unicast message. */ int ifindex; char *ifname; - bool bind_to_interface; be32_t address; be32_t netmask; be32_t subnet; @@ -53,8 +39,8 @@ typedef struct sd_dhcp_server { char *boot_server_name; char *boot_filename; - OrderedSet *extra_options; - OrderedSet *vendor_options; + TLV *extra_options; + TLV *vendor_options; bool emit_router; struct in_addr router_address; @@ -72,38 +58,16 @@ typedef struct sd_dhcp_server { sd_dhcp_server_callback_t callback; void *callback_userdata; - struct in_addr relay_target; - - char *agent_circuit_id; - char *agent_remote_id; - int lease_dir_fd; char *lease_file; } sd_dhcp_server; -typedef struct DHCPRequest { - /* received message */ - DHCPMessage *message; - - /* options */ - sd_dhcp_client_id client_id; - size_t max_optlen; - be32_t server_id; - be32_t requested_ip; - usec_t lifetime; - const uint8_t *agent_info_option; - char *hostname; - const uint8_t *parameter_request_list; - size_t parameter_request_list_len; - bool rapid_commit; - triple_timestamp timestamp; -} DHCPRequest; - -int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, - size_t length, const triple_timestamp *timestamp); -int dhcp_server_send_packet(sd_dhcp_server *server, - DHCPRequest *req, DHCPPacket *packet, - int type, size_t optoffset); +int dhcp_server_set_extra_options(sd_dhcp_server *server, TLV *options); +int dhcp_server_set_vendor_options(sd_dhcp_server *server, TLV *options); + +void dhcp_server_on_lease_change(sd_dhcp_server *server); +bool dhcp_server_address_is_in_pool(sd_dhcp_server *server, be32_t address); +bool dhcp_server_address_available(sd_dhcp_server *server, be32_t address); #define log_dhcp_server_errno(server, error, fmt, ...) \ log_interface_prefix_full_errno( \ diff --git a/src/libsystemd-network/dhcp-server-lease-internal.h b/src/libsystemd-network/dhcp-server-lease-internal.h index aeb099ff81878..76b92c6339f8a 100644 --- a/src/libsystemd-network/dhcp-server-lease-internal.h +++ b/src/libsystemd-network/dhcp-server-lease-internal.h @@ -4,8 +4,8 @@ #include "sd-dhcp-server-lease.h" #include "dhcp-client-id-internal.h" -#include "dhcp-server-internal.h" -#include "sd-forward.h" +#include "dhcp-forward.h" +#include "ether-addr-util.h" typedef struct sd_dhcp_server_lease { unsigned n_ref; @@ -15,10 +15,9 @@ typedef struct sd_dhcp_server_lease { sd_dhcp_client_id client_id; uint8_t htype; /* e.g. ARPHRD_ETHER */ - uint8_t hlen; /* e.g. ETH_ALEN */ + struct hw_addr_data hw_addr; be32_t address; be32_t gateway; - uint8_t chaddr[16]; usec_t expiration; char *hostname; } sd_dhcp_server_lease; @@ -27,7 +26,7 @@ extern const struct hash_ops dhcp_server_lease_hash_ops; int dhcp_server_put_lease(sd_dhcp_server *server, sd_dhcp_server_lease *lease, bool is_static); -int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *req, usec_t expiration); +int dhcp_server_set_lease(sd_dhcp_server *server, DHCPRequest *req); int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server); sd_dhcp_server_lease* dhcp_server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req); diff --git a/src/libsystemd-network/dhcp-server-request.c b/src/libsystemd-network/dhcp-server-request.c new file mode 100644 index 0000000000000..116a64fad1bdf --- /dev/null +++ b/src/libsystemd-network/dhcp-server-request.c @@ -0,0 +1,671 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "alloc-util.h" +#include "dhcp-message.h" +#include "dhcp-server-internal.h" +#include "dhcp-server-lease-internal.h" +#include "dhcp-server-request.h" +#include "dhcp-server-send.h" +#include "errno-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "iovec-util.h" +#include "ip-util.h" +#include "set.h" +#include "siphash24.h" +#include "socket-util.h" +#include "string-util.h" + +static DHCPRequest* dhcp_request_free(DHCPRequest *req) { + if (!req) + return NULL; + + sd_dhcp_message_unref(req->message); + set_free(req->parameter_request_list); + return mfree(req); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free); + +static void dhcp_request_set_timestamp(DHCPRequest *req, const triple_timestamp *timestamp) { + assert(req); + + if (timestamp && triple_timestamp_is_set(timestamp)) + req->timestamp = *timestamp; + else + triple_timestamp_now(&req->timestamp); +} + +int dhcp_request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_t *ret) { + assert(req); + assert(TRIPLE_TIMESTAMP_HAS_CLOCK(clock)); + assert(clock_supported(clock)); + assert(ret); + + if (req->lifetime <= 0) + return -ENODATA; + + if (!triple_timestamp_is_set(&req->timestamp)) + return -ENODATA; + + *ret = usec_add(triple_timestamp_by_clock(&req->timestamp, clock), req->lifetime); + return 0; +} + +static int dhcp_request_set_client_id(DHCPRequest *req) { + assert(req); + assert(req->message); + + /* Genuine client ID from Client Identifier option. The option may not be set. */ + (void) dhcp_message_get_option_client_id(req->message, &req->client_id); + + /* Fake client ID generated from the DHCP header. + * The client ID type 0 and 255 are special. So do not use if htype is 0 or 255. + * Note, Some hardware type (e.g. Infiniband) may not set chaddr field. */ + if (!IN_SET(req->message->header.htype, 0, UINT8_MAX)) + (void) sd_dhcp_client_id_set( + &req->client_id_by_header, + req->message->header.htype, + req->message->header.chaddr, + req->message->header.hlen); + + /* If Client Identifier option is unspecified, use the generated one. */ + if (!sd_dhcp_client_id_is_set(&req->client_id)) + req->client_id = req->client_id_by_header; + + /* We manage bound leases by client ID. Hence, at least one of them is necessary. */ + if (!sd_dhcp_client_id_is_set(&req->client_id)) + return -EBADMSG; + + return 0; +} + +static int dhcp_request_set_server_identifier(DHCPRequest *req) { + int r; + + assert(req); + assert(req->message); + + bool mandatory = IN_SET(req->type, DHCP_RELEASE, DHCP_DECLINE); + + be32_t a; + r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_SERVER_IDENTIFIER, &a); + if (r < 0) + return mandatory ? r : 0; + + req->server_address = a; + return 0; +} + +static int dhcp_request_set_maximum_message_size(DHCPRequest *req) { + int r; + + assert(req); + assert(req->message); + + uint16_t sz; + r = dhcp_message_get_option_u16(req->message, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, &sz); + if (r < 0) + return r; + + /* RFC 2132 section 9.10: + * The minimum legal value is 576 octets. */ + if (sz < IPV4_MIN_REASSEMBLY_SIZE) + return -EBADMSG; + + req->max_message_size = sz; + return 0; +} + +static int dhcp_request_set_lifetime(DHCPRequest *req, sd_dhcp_server *server) { + assert(req); + assert(req->message); + assert(server); + + (void) dhcp_message_get_option_sec( + req->message, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, + /* max_as_infinity= */ true, + &req->lifetime); + + /* If unset (or zero is specified...), use the default lease time. */ + if (req->lifetime <= 0) + req->lifetime = MAX(30 * USEC_PER_SEC, server->default_lease_time); + + /* If the requested lifetime is too long, then cap it with the maximum lease time. */ + if (server->max_lease_time > 0 && req->lifetime > server->max_lease_time) + req->lifetime = server->max_lease_time; + + return 0; +} + +static int dhcp_server_parse_message(sd_dhcp_server *server, const struct iovec *iov, DHCPRequest **ret) { + int r; + + assert(server); + assert(iov); + assert(ret); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_parse( + iov, + BOOTREQUEST, + /* xid= */ NULL, + ARPHRD_NONE, + /* hw_addr= */ NULL, + &message); + if (r < 0) + return r; + + /* A DHCP relay agent is running on the interface with the same address?? + * Should be malicious message. */ + if (message->header.giaddr == server->address) + return -EBADMSG; + + _cleanup_(dhcp_request_freep) DHCPRequest *req = new(DHCPRequest, 1); + if (!req) + return -ENOMEM; + + *req = (DHCPRequest) { + .message = sd_dhcp_message_ref(message), + + /* RFC 2132 section 9.10: + * The minimum legal value is 576 octets. */ + .max_message_size = IPV4_MIN_REASSEMBLY_SIZE, + }; + + /* client hardware address + * Note, hlen and chaddr may not be set for non-ethernet interface. + * See RFC2131 section 4.1. */ + r = dhcp_message_get_hw_addr(message, &req->hw_addr); + if (r < 0) + return r; + + /* Message Type: mandatory */ + r = dhcp_message_get_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, &req->type); + if (r < 0) + return r; + + /* Client Identifier: Mandatory. If not set, fall back to use chaddr. */ + r = dhcp_request_set_client_id(req); + if (r < 0) + return r; + + /* Server Identifier */ + r = dhcp_request_set_server_identifier(req); + if (r < 0) + return r; + + /* Maximum Message Size: optional */ + (void) dhcp_request_set_maximum_message_size(req); + + /* Lifetime: optional */ + (void) dhcp_request_set_lifetime(req, server); + + /* Parameter Request List: optional */ + (void) dhcp_message_get_option_parameter_request_list(message, &req->parameter_request_list); + + *ret = TAKE_PTR(req); + return 0; +} + +static int dhcp_server_ack(sd_dhcp_server *server, DHCPRequest *req) { + int r; + + assert(server); + assert(req); + assert(req->address != INADDR_ANY); + + r = dhcp_server_set_lease(server, req); + if (r < 0) + return r; + + r = dhcp_server_send_reply(server, req, DHCP_ACK); + if (r < 0) + return r; + + dhcp_server_on_lease_change(server); + return r; +} + +static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req) { + assert(server); + assert(req); + + sd_dhcp_server_lease + *existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id), + *static_lease = dhcp_server_get_static_lease(server, req); + + log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->header.xid)); + + /* for now pick a random free address from the pool */ + if (static_lease) { + sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(static_lease->address)); + if (l && l != existing_lease) + /* The address is already assigned to another host. Refusing. */ + return -EADDRINUSE; + + /* Found a matching static lease. */ + req->static_lease = static_lease; + req->address = static_lease->address; + + } else if (existing_lease && dhcp_server_address_is_in_pool(server, existing_lease->address)) + /* If we previously assigned an address to the host, then reuse it. */ + req->address = existing_lease->address; + + else { + struct siphash state; + uint64_t hash; + + /* Even with no persistence of leases, we try to offer the same client the same IP address. + * We do this by using the hash of the client ID as the offset into the pool of leases when + * finding the next free one. */ + +#define HASH_KEY SD_ID128_MAKE(0d,1d,fe,bd,f1,24,bd,b3,47,f1,dd,6e,73,21,93,30) + + siphash24_init(&state, HASH_KEY.bytes); + client_id_hash_func(&req->client_id, &state); + hash = htole64(siphash24_finalize(&state)); + + for (unsigned i = 0; i < server->pool_size; i++) { + be32_t a = server->subnet | htobe32(server->pool_offset + (hash + i) % server->pool_size); + if (dhcp_server_address_available(server, a)) { + req->address = a; + break; + } + } + } + + if (req->address == INADDR_ANY) + return -EADDRNOTAVAIL; /* no free addresses left */ + + if (server->rapid_commit && + dhcp_message_get_option_flag(req->message, SD_DHCP_OPTION_RAPID_COMMIT) >= 0) + return dhcp_server_ack(server, req); + + return dhcp_server_send_reply(server, req, DHCP_OFFER); +} + +static int server_guess_client_state(sd_dhcp_server *server, DHCPRequest *req) { + int r; + + assert(server); + assert(req); + assert(req->message); + + /* This guesses the client is in the renewing or rebinding state. + * + * If giaddr is zero, then there is no relay agent between the client and us. In that case, a + * DHCPREQUEST sent to a unicast destination address implies the client is in RENEWING state, while a + * broadcast destination address implies REBINDING state. Note, if packet destination information is + * unavailable, we conservatively assume REBINDING. + * + * If giaddr is non-zero, then there is a relay agent. The relay forwards packets to us using unicast + * transport, so the outer IPv4 destination address no longer reflects whether the original client + * message was broadcast or unicast. If the message contains the Relay Agent Information option with + * Relay Agent Flags suboption, then we can know if the original message is unicast or broadcast. If + * the message does not have the option, then we assume the client is in the rebinding state. */ + + if (req->message->header.giaddr == INADDR_ANY) { + if (!req->pktinfo) + return DHCP_STATE_REBINDING; + + /* 255.255.255.255 ? */ + if (req->pktinfo->ipi_addr.s_addr == INADDR_BROADCAST) + return DHCP_STATE_REBINDING; + + /* subnet-directed broadcast, e.g., 192.0.2.0/24 -> 192.0.2.255 ? */ + if (req->pktinfo->ipi_addr.s_addr == (server->subnet | ~server->netmask)) + return DHCP_STATE_REBINDING; + + /* unicast, hence renewing. */ + return DHCP_STATE_RENEWING; + } + + _cleanup_(tlv_unrefp) TLV *agent_info = NULL; + r = dhcp_message_get_option_sub_tlv( + req->message, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, + TLV_DHCP4_SUBOPTION, + &agent_info); + if (r == -ENODATA) + return DHCP_STATE_REBINDING; + if (r < 0) + return r; + + struct iovec iov = {}; + r = tlv_get_full(agent_info, SD_DHCP_RELAY_AGENT_FLAGS, sizeof(uint8_t), &iov); + if (r == -ENODATA) + return DHCP_STATE_REBINDING; + if (r < 0) + return r; + + assert(iov.iov_len == sizeof(uint8_t)); + uint8_t flags = *(const uint8_t*) iov.iov_base; + return FLAGS_SET(flags, DHCP_RELAY_AGENT_FLAG_UNICAST) ? DHCP_STATE_RENEWING : DHCP_STATE_REBINDING; +} + +static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) { + int r; + + assert(server); + assert(req); + + sd_dhcp_server_lease + *existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id), + *static_lease = dhcp_server_get_static_lease(server, req); + + DHCPState state; + be32_t address; + + /* see RFC 2131, section 4.3.2 */ + if (req->server_address != INADDR_ANY) { + state = DHCP_STATE_SELECTING; + + if (req->server_address != server->address) + return 0; /* The message is not for us. Let's silently ignore the packet. */ + + if (req->message->header.ciaddr != INADDR_ANY) /* this MUST be zero */ + return -EBADMSG; + + /* this must be filled in with the yiaddr from the chosen OFFER */ + r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &address); + if (r < 0) + return r; + + if (address == INADDR_ANY) + return -EBADMSG; + + } else if (req->message->header.ciaddr != INADDR_ANY) { + r = server_guess_client_state(server, req); + if (r < 0) + return r; + state = r; + + /* this must NOT be filled */ + if (dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, /* ret= */ NULL) >= 0) + return -EBADMSG; + + address = req->message->header.ciaddr; + + } else { + state = DHCP_STATE_INIT_REBOOT; + + r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &address); + if (r < 0) + return r; + + if (address == INADDR_ANY) + return -EBADMSG; + } + + log_dhcp_server(server, "REQUEST (%s) (0x%x)", dhcp_state_to_string(state), be32toh(req->message->header.xid)); + + /* In the below, when we found some inconsistency with the bound lease or static lease, we send + * DHCPNAK if the client is in the selecting, renewing, or init-reboot state. Otherwise, i.e., + * if in the rebinding state, we silently ignore the message. This is because, the network may have + * multiple DHCP servers, and the address may be managed by another server, and the request may be + * for that server. + * + * On selecting: + * We have already verified the message has matching server identifier, hence we have responsibility + * to manage the request, hence we should aggressively reply DHCPNAK. + * + * On renewing: + * The message is sent directly to us using unicast transport, so replying with DHCPNAK is unlikely + * to interfere with another server. + * + * On rebinding: + * The message is sent using broadcast delivery, hence if the network has multiple DHCP servers, then + * another server may reply DHCPACK. We should not disturb their process. Hence, silently ignore the + * message. + * + * On init-reboot: + * Even though the message is broadcast, for faster rebooting, we should aggressively reply DHCPNAK. + * Even if this is unnecessarily aggressive, the client will soon enter the usual DISCOVER -> REQUEST + * cycle, so that should not cause any big issues. */ + bool send_nak = IN_SET(state, DHCP_STATE_SELECTING, DHCP_STATE_RENEWING, DHCP_STATE_INIT_REBOOT); + + /* Check if the requested address is already assigned to another host. + * - if 'l' is NULL, then the address is not assigned to any host. + * - if 'l' is non-NULL, and equivalent to 'existing_lease', then the address is assigned to the host. + * - if 'l' is non-NULL, but different from 'existing_lease', then the address is already assigned to + * another host. In this case, we explicitly know that the address should not be used by the host. */ + sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(address)); + if (l && l != existing_lease) + return send_nak ? dhcp_server_send_reply(server, req, DHCP_NAK) : 0; + + /* Check if the request is consistent with the static lease. */ + if (static_lease) { + /* Found a static lease for the client ID. In this case, the server is explicitly configured + * to manage the host. Hence, send NAK when the request is invalid. */ + + if (static_lease->address != address) + /* The client requested an address which is different from the static lease. Refusing. */ + return send_nak ? dhcp_server_send_reply(server, req, DHCP_NAK) : 0; + + req->static_lease = static_lease; + req->address = address; + + return dhcp_server_ack(server, req); + } + + if (dhcp_server_address_is_in_pool(server, address)) { + /* The requested address is in the pool. In the above, we have checked the address is free or + * already assigned to the host. Hence, ACK. */ + req->address = address; + + return dhcp_server_ack(server, req); + } + + /* No static lease is configured for the host, and the requested address is not in our pool. Refusing. */ + if (send_nak) + return dhcp_server_send_reply(server, req, DHCP_NAK); + + return 0; +} + +static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req) { + assert(server); + assert(req); + + if (req->server_address != server->address) + return 0; /* The message is not for us. Let's silently ignore the packet. */ + + /* TODO: make sure we don't offer this address again for a while. */ + + _cleanup_free_ char *e = NULL; + (void) dhcp_message_get_option_string(req->message, SD_DHCP_OPTION_ERROR_MESSAGE, &e); + log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->header.xid), strna(e)); + return 0; +} + +static int dhcp_server_process_release(sd_dhcp_server *server, DHCPRequest *req) { + assert(server); + assert(req); + + if (req->server_address != server->address) + return 0; /* The message is not for us. Let's silently ignore the packet. */ + + sd_dhcp_server_lease *existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id); + if (!existing_lease) + return 0; + + if (existing_lease->address != req->message->header.ciaddr) + return -EBADMSG; + + sd_dhcp_server_lease_unref(existing_lease); + dhcp_server_on_lease_change(server); + + log_dhcp_server(server, "RELEASE (0x%x)", be32toh(req->message->header.xid)); + return 0; +} + +int dhcp_server_process_message(sd_dhcp_server *server, const struct iovec *iov, struct msghdr *mh) { + int r; + + assert(server); + assert(iov); + + _cleanup_(dhcp_request_freep) DHCPRequest *req = NULL; + r = dhcp_server_parse_message(server, iov, &req); + if (r < 0) + return r; + + dhcp_request_set_timestamp(req, mh ? TRIPLE_TIMESTAMP_FROM_CMSG(mh) : NULL); + req->pktinfo = mh ? CMSG_FIND_DATA(mh, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo) : NULL; + + r = dhcp_server_cleanup_expired_leases(server); + if (r < 0) + return r; + + switch (req->type) { + case DHCP_DISCOVER: + return dhcp_server_process_discover(server, req); + case DHCP_REQUEST: + return dhcp_server_process_request(server, req); + case DHCP_DECLINE: + return dhcp_server_process_decline(server, req); + case DHCP_RELEASE: + return dhcp_server_process_release(server, req); + default: + return 0; /* Unsupported DHCP message type? Ignore the message silently. */ + } +} + +static int server_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_server *server = ASSERT_PTR(userdata); + int r; + + ssize_t buflen = next_datagram_size_fd(fd); + if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen)) + return 0; + if (buflen < 0) { + log_dhcp_server_errno(server, buflen, "Failed to determine datagram size to read, ignoring: %m"); + return 0; + } + + _cleanup_free_ void *buf = malloc0(buflen); + if (!buf) + return -ENOMEM; + + /* This needs to be initialized with zero. See #20741. + * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */ + CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL + + CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; + struct iovec iov = IOVEC_MAKE(buf, buflen); + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + + ssize_t len = recvmsg_safe(fd, &msg, 0); + if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len)) + return 0; + if (len < 0) { + log_dhcp_server_errno(server, len, "Could not receive message, ignoring: %m"); + return 0; + } + + r = dhcp_server_process_message(server, &IOVEC_MAKE(buf, len), &msg); + if (r < 0) + log_dhcp_server_errno(server, r, "Couldn't process incoming message, ignoring: %m"); + + return 0; +} + +static int server_open_socket(sd_dhcp_server *server) { + int r; + + assert(server); + + _cleanup_close_ int fd = RET_NERRNO(socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + if (fd < 0) + return fd; + + r = socket_bind_to_ifindex(fd, server->ifindex); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_TIMESTAMP, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_BROADCAST, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, tos_to_priority(server->ip_service_type)); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_TOS, server->ip_service_type); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_PKTINFO, true); + if (r < 0) + return r; + + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(DHCP_PORT_SERVER), + .in.sin_addr.s_addr = htobe32(INADDR_ANY), + }; + + if (bind(fd, &sa.sa, sizeof(sa.in)) < 0) + return -errno; + + return TAKE_FD(fd); +} + +int dhcp_server_setup_io_event_source(sd_dhcp_server *server) { + int r; + + assert(server); + assert(server->event); + + _cleanup_close_ int fd_close = -EBADF; + int fd; + if (server->socket_fd >= 0) + /* When a socket fd is given externally, unconditionally use it and do not close the socket + * even if we fail to set up the event source. */ + fd = server->socket_fd; + else { + fd = fd_close = server_open_socket(server); + if (fd < 0) + return fd; + } + + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + r = sd_event_add_io(server->event, &s, fd, EPOLLIN, server_receive_message, server); + if (r < 0) + return r; + + r = sd_event_source_set_priority(s, server->event_priority); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s, "dhcp-server-io"); + + if (fd_close >= 0) { + r = sd_event_source_set_io_fd_own(s, true); + if (r < 0) + return r; + TAKE_FD(fd_close); + } + + sd_event_source_disable_unref(server->io_event_source); + server->io_event_source = TAKE_PTR(s); + return 0; +} diff --git a/src/libsystemd-network/dhcp-server-request.h b/src/libsystemd-network/dhcp-server-request.h new file mode 100644 index 0000000000000..ad77a0250ca9b --- /dev/null +++ b/src/libsystemd-network/dhcp-server-request.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "dhcp-client-id-internal.h" +#include "dhcp-forward.h" +#include "ether-addr-util.h" +#include "time-util.h" + +typedef struct DHCPRequest { + /* received message */ + sd_dhcp_message *message; + /* sender hardware address, may not be set for non-ethernet interface */ + struct hw_addr_data hw_addr; + triple_timestamp timestamp; + struct in_pktinfo *pktinfo; + + /* options */ + uint8_t type; + sd_dhcp_client_id client_id; + sd_dhcp_client_id client_id_by_header; + size_t max_message_size; /* maximum message size, including IP and UDP headers */ + be32_t server_address; + usec_t lifetime; + Set *parameter_request_list; + + /* acquired data */ + sd_dhcp_server_lease *static_lease; + be32_t address; +} DHCPRequest; + +int dhcp_request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_t *ret); + +int dhcp_server_process_message(sd_dhcp_server *server, const struct iovec *iov, struct msghdr *mh); +int dhcp_server_setup_io_event_source(sd_dhcp_server *server); diff --git a/src/libsystemd-network/dhcp-server-send.c b/src/libsystemd-network/dhcp-server-send.c new file mode 100644 index 0000000000000..6e00bafd098a8 --- /dev/null +++ b/src/libsystemd-network/dhcp-server-send.c @@ -0,0 +1,453 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-event.h" + +#include "dhcp-server-internal.h" +#include "dhcp-server-lease-internal.h" +#include "dhcp-server-request.h" +#include "dhcp-server-send.h" +#include "errno-util.h" +#include "fd-util.h" +#include "in-addr-util.h" +#include "random-util.h" +#include "set.h" +#include "socket-util.h" + +static int server_acquire_raw_socket(sd_dhcp_server *server) { + int r; + + assert(server); + + if (server->socket_fd >= 0) + /* When a socket fd is given externally, unconditionally use it and do not close the socket. */ + return server->socket_fd; + + if (server->raw_socket_fd >= 0) + /* Already opened. */ + return server->raw_socket_fd; + + /* This is a send-only socket, hence it is opened with protocol=0, and do not call bind(). + * The interface binding will be done on send. */ + _cleanup_close_ int fd = RET_NERRNO(socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + if (fd < 0) + return fd; + + r = setsockopt_int(fd, SOL_SOCKET, SO_PRIORITY, tos_to_priority(server->ip_service_type)); + if (r < 0) + return r; + + return server->raw_socket_fd = TAKE_FD(fd); +} + +static int dhcp_server_send_unicast_raw( + sd_dhcp_server *server, + const struct hw_addr_data *hw_addr, + sd_dhcp_message *message) { + + assert(server); + assert(server->ifindex > 0); + assert(server->address != 0); + assert(hw_addr); + assert(message); + + int fd = server_acquire_raw_socket(server); + if (fd < 0) + return fd; + + return dhcp_message_send_raw( + message, + fd, + server->ifindex, + server->address, + DHCP_PORT_SERVER, + hw_addr, + message->header.yiaddr, + DHCP_PORT_CLIENT, + server->ip_service_type); +} + +static int dhcp_server_send_udp( + sd_dhcp_server *server, + be32_t address, + uint16_t port, + sd_dhcp_message *message) { + + assert(server); + assert(message); + + int fd = sd_event_source_get_io_fd(server->io_event_source); + if (fd < 0) + return fd; + + return dhcp_message_send_udp( + message, + fd, + server->address, + address, + port); +} + +static int dhcp_server_send_message( + sd_dhcp_server *server, + uint8_t type, + sd_dhcp_message *message) { + + int r; + + assert(server); + assert(message); + + /* RFC 2131 Section 4.1 */ + + /* If the ’giaddr’ field in a DHCP message from a client is non-zero, the server sends any + * return messages to the ’DHCP server’ port on the BOOTP relay agent whose address appears + * in ’giaddr’. */ + if (message->header.giaddr != INADDR_ANY) + return dhcp_server_send_udp( + server, + message->header.giaddr, + DHCP_PORT_SERVER, + message); + + /* when ’giaddr’ is zero, the server broadcasts any DHCPNAK messages to 0xffffffff. */ + if (type == DHCP_NAK) + return dhcp_server_send_udp( + server, + INADDR_BROADCAST, + DHCP_PORT_CLIENT, + message); + + /* If the ’giaddr’ field is zero and the ’ciaddr’ field is nonzero, then the server unicasts + * DHCPOFFER and DHCPACK messages to the address in ’ciaddr’. */ + if (message->header.ciaddr != INADDR_ANY) + return dhcp_server_send_udp( + server, + message->header.ciaddr, + DHCP_PORT_CLIENT, + message); + + /* If ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is set, then the server + * broadcasts DHCPOFFER and DHCPACK messages to 0xffffffff. + * + * (Note, even the broadcast flag is unset, we may not know the client hardware address, e.g. + * InfiniBand. In that case, we cannot unicast in the below, so need to broadcast. Also, broadcast + * the message if 'yiaddr' is zero.) */ + struct hw_addr_data hw_addr = {}; + if (!dhcp_message_has_broadcast_flag(message) && + message->header.yiaddr != INADDR_ANY) { + r = dhcp_message_get_hw_addr(message, &hw_addr); + if (r < 0) + return r; + } + + if (hw_addr_is_null(&hw_addr)) + return dhcp_server_send_udp( + server, + INADDR_BROADCAST, + DHCP_PORT_CLIENT, + message); + + /* If the broadcast bit is not set and ’giaddr’ is zero and ’ciaddr’ is zero, then the server + * unicasts DHCPOFFER and DHCPACK messages to the client’s hardware address and ’yiaddr’ address. */ + return dhcp_server_send_unicast_raw( + server, + &hw_addr, + message); +} + +static int dhcp_server_new_reply( + sd_dhcp_server *server, + DHCPRequest *req, + uint8_t type, + sd_dhcp_message **ret) { + + int r; + + assert(server); + assert(req); + assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK)); + assert(ret); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_new(&message); + if (r < 0) + return r; + + r = dhcp_message_init_header( + message, + BOOTREPLY, + be32toh(req->message->header.xid), + req->message->header.htype, + &req->hw_addr); + if (r < 0) + return r; + + message->header.giaddr = req->message->header.giaddr; + + /* RFC 2131 Section 4.3.2 + * + * If ’giaddr’ is set in the DHCPREQUEST message, the client is on a different subnet. The server + * MUST set the broadcast bit in the DHCPNAK, so that the relay agent will broadcast the DHCPNAK to + * the client, because the client may not have a correct network address or subnet mask, and the + * client may not be answering ARP requests. */ + dhcp_message_set_broadcast_flag( + message, + dhcp_message_has_broadcast_flag(req->message) || + req->message->header.giaddr != INADDR_ANY || + type == DHCP_NAK); + + /* DHCP Message Type (53): Mandatory. */ + r = dhcp_message_append_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, type); + if (r < 0) + return r; + + /* Server Identifier */ + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + server->address); + if (r < 0) + return r; + + if (type == DHCP_NAK) { + *ret = TAKE_PTR(message); + return 0; + } + + assert(req->address != INADDR_ANY); + message->header.yiaddr = req->address; + message->header.siaddr = server->boot_server_address.s_addr; + + if (type == DHCP_ACK) + message->header.ciaddr = req->message->header.ciaddr; + + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, + usec_to_be32_sec(req->lifetime)); + if (r < 0) + return r; + + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_SUBNET_MASK, + server->netmask); + if (r < 0) + return r; + + if (server->emit_router) { + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_ROUTER, + in4_addr_is_set(&server->router_address) ? + server->router_address.s_addr : + server->address); + if (r < 0) + return r; + } + + if (server->boot_server_name) { + r = dhcp_message_append_option_string( + message, + SD_DHCP_OPTION_BOOT_SERVER_NAME, + server->boot_server_name); + if (r < 0) + return r; + } + + if (server->boot_filename) { + r = dhcp_message_append_option_string( + message, + SD_DHCP_OPTION_BOOT_FILENAME, + server->boot_filename); + if (r < 0) + return r; + } + + static const uint8_t option_map[_SD_DHCP_LEASE_SERVER_TYPE_MAX] = { + [SD_DHCP_LEASE_DNS] = SD_DHCP_OPTION_DOMAIN_NAME_SERVER, + [SD_DHCP_LEASE_NTP] = SD_DHCP_OPTION_NTP_SERVER, + [SD_DHCP_LEASE_SIP] = SD_DHCP_OPTION_SIP_SERVER, + [SD_DHCP_LEASE_POP3] = SD_DHCP_OPTION_POP3_SERVER, + [SD_DHCP_LEASE_SMTP] = SD_DHCP_OPTION_SMTP_SERVER, + [SD_DHCP_LEASE_LPR] = SD_DHCP_OPTION_LPR_SERVER, + }; + + for (sd_dhcp_lease_server_type_t k = 0; k < _SD_DHCP_LEASE_SERVER_TYPE_MAX; k++) { + if (server->servers[k].size <= 0) + continue; + + r = dhcp_message_append_option_addresses( + message, + option_map[k], + server->servers[k].size, + server->servers[k].addr); + if (r < 0) + return r; + } + + if (server->timezone) { + r = dhcp_message_append_option_string( + message, + SD_DHCP_OPTION_TZDB_TIMEZONE, + server->timezone); + if (r < 0) + return r; + } + + if (server->domain_name) { + r = dhcp_message_append_option_string( + message, + SD_DHCP_OPTION_DOMAIN_NAME, + server->domain_name); + if (r < 0) + return r; + } + + /* RFC 8925 section 3.3. DHCPv4 Server Behavior + * The server MUST NOT include the IPv6-Only Preferred option in the DHCPOFFER or DHCPACK message if + * the option was not present in the Parameter Request List sent by the client. */ + if (set_contains(req->parameter_request_list, UINT_TO_PTR(SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)) && + server->ipv6_only_preferred_usec > 0) { + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, + usec_to_be32_sec(server->ipv6_only_preferred_usec)); + if (r < 0) + return r; + } + + r = dhcp_message_append_option_sub_tlv( + message, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, + server->vendor_options); + if (r < 0) + return r; + + if (req->static_lease) { + /* Hostname (12) or FQDN (81) + * Flags: S=0 (will not update RR), O=1 (are overriding client), N=1 (will not update DNS) */ + r = dhcp_message_append_option_hostname( + message, + DHCP_FQDN_FLAG_O | DHCP_FQDN_FLAG_N, + /* is_client= */ false, + req->static_lease->hostname); + if (r < 0) + return r; + } + + _cleanup_(tlv_unrefp) TLV *agent_info = NULL; + r = dhcp_message_get_option_sub_tlv( + req->message, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, + TLV_DHCP4_SUBOPTION, + &agent_info); + if (r < 0 && r != -ENODATA) + log_dhcp_server_errno(server, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_RELAY_AGENT_INFORMATION)); + + if (agent_info) { + r = dhcp_message_append_option_sub_tlv( + message, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, + agent_info); + if (r < 0) + return r; + } + + if (type == DHCP_ACK && req->type == DHCP_DISCOVER) { + assert(server->rapid_commit); + r = dhcp_message_append_option_flag(message, SD_DHCP_OPTION_RAPID_COMMIT); + if (r < 0) + return r; + } + + r = dhcp_message_append_option_tlv(message, server->extra_options); + if (r < 0) + return r; + + *ret = TAKE_PTR(message); + return 0; +} + +int dhcp_server_send_reply( + sd_dhcp_server *server, + DHCPRequest *req, + uint8_t type) { + + int r; + + assert(server); + assert(req); + assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_server_new_reply(server, req, type, &message); + if (r < 0) + return r; + + if (dhcp_message_packet_size(message) > req->max_message_size) + return -E2BIG; + + r = dhcp_server_send_message(server, type, message); + if (r < 0) + return r; + + log_dhcp_server(server, "%s (0x%x)", dhcp_message_type_to_string(type), be32toh(message->header.xid)); + return type; /* Return the sent message type. To make the test easier. */ +} + +static int dhcp_server_send_forcerenew( + sd_dhcp_server *server, + sd_dhcp_server_lease *lease) { + + int r; + + assert(server); + assert(lease); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_new(&message); + if (r < 0) + return r; + + r = dhcp_message_init_header( + message, + BOOTREPLY, + random_u32(), + lease->htype, + &lease->hw_addr); + if (r < 0) + return r; + + /* DHCP Message Type (53): Mandatory. */ + r = dhcp_message_append_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_FORCERENEW); + if (r < 0) + return r; + + /* Server Identifier */ + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + server->address); + if (r < 0) + return r; + + r = dhcp_server_send_udp(server, lease->address, DHCP_PORT_CLIENT, message); + if (r < 0) + return r; + + log_dhcp_server(server, "%s (0x%x)", dhcp_message_type_to_string(DHCP_FORCERENEW), be32toh(message->header.xid)); + return 0; +} + +int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { + sd_dhcp_server_lease *lease; + int r = 0; + + assert_return(server, -EINVAL); + + HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) + RET_GATHER(r, dhcp_server_send_forcerenew(server, lease)); + return r; +} diff --git a/src/libsystemd-network/dhcp-server-send.h b/src/libsystemd-network/dhcp-server-send.h new file mode 100644 index 0000000000000..92f63bcc869fd --- /dev/null +++ b/src/libsystemd-network/dhcp-server-send.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "dhcp-forward.h" + +int dhcp_server_send_reply( + sd_dhcp_server *server, + DHCPRequest *req, + uint8_t type); diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 751aed78a7f02..1c6b29b8cb19d 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; @@ -670,6 +672,13 @@ static int dhcp6_option_parse_ia_pdprefix(sd_dhcp6_client *client, DHCP6IA *ia, FORMAT_TIMESPAN(lt_pref, USEC_PER_SEC), FORMAT_TIMESPAN(lt_valid, USEC_PER_SEC)); + /* RFC 8415 defines the prefix length as 1…128; reject the out-of-range values the sender-side + * counterpart option_append_pd_prefix() also refuses. */ + if (a->iapdprefix.prefixlen == 0 || a->iapdprefix.prefixlen > 128) + return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), + "Received a PD prefix with invalid prefix length %u, ignoring.", + a->iapdprefix.prefixlen); + if (len > sizeof(struct iapdprefix)) { r = dhcp6_option_parse_ia_options(client, data + sizeof(struct iapdprefix), len - sizeof(struct iapdprefix)); if (r < 0) 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..baa5a8723e607 100644 --- a/src/libsystemd-network/fuzz-dhcp-client.c +++ b/src/libsystemd-network/fuzz-dhcp-client.c @@ -1,95 +1,82 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include -#include "dhcp-network.h" -#include "fuzz.h" -#include "network-internal.h" -#include "sd-dhcp-client.c" -#include "tmpfile-util.h" - -int dhcp_network_bind_raw_socket( - int ifindex, - union sockaddr_union *link, - uint32_t id, - const struct hw_addr_data *hw_addr, - const struct hw_addr_data *bcast_addr, - uint16_t arp_type, - uint16_t port, - 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; -} - -int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const void *packet, size_t len) { - return len; -} - -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; +#include "sd-event.h" +#include "sd-json.h" - return fd; -} - -int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, const void *packet, size_t len) { - return len; -} +#include "dhcp-client-internal.h" /* IWYU pragma: keep */ +#include "dhcp-lease-internal.h" +#include "dhcp-message.h" +#include "fd-util.h" +#include "fuzz.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "tests.h" 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 struct hw_addr_data hw_addr = { + .length = ETH_ALEN, + .ether = {{ 'A', 'B', 'C', '1', '2', '3' }}, + }, bcast_addr = { + .length = ETH_ALEN, + .ether = {{ 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); + + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + ASSERT_OK(sd_dhcp_client_new(&client)); + ASSERT_NOT_NULL(client); - assert_se(sd_event_new(&e) >= 0); + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); + client->socket_fd = TAKE_FD(socket_fd[0]); - 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, hw_addr.bytes, bcast_addr.bytes, hw_addr.length, 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); - - r = dhcp_lease_load(&lease, lease_file); - assert_se(r >= 0); - -end: - assert_se(sd_dhcp_client_stop(client) >= 0); - + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + if (dhcp_client_parse_message(client, &IOVEC_MAKE(data, size), &lease) >= 0) { + /* Build json variant and parse it. */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + ASSERT_OK(dhcp_message_build_json(lease->message, &v)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_parse_json(v, &m)); + + /* Build UDP payload and parse it. Note, currently we do not use file and sname overload on + * build. Hence, dhcp_message_build() may fail with -E2BIG if the input uses file and/or + * sname overload. */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + if (ASSERT_OK_OR(dhcp_message_build(lease->message, &iovw), -E2BIG) >= 0) { + _cleanup_(iovec_done) struct iovec iov = {}; + ASSERT_OK(iovw_concat(&iovw, &iov)); + + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease2 = NULL; + ASSERT_OK(dhcp_client_parse_message(client, &iov, &lease2)); + + /* Build UDP payload again, and compare with the previous one. */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; + ASSERT_OK(dhcp_message_build(lease2->message, &iovw2)); + + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + } + } + + ASSERT_OK(sd_dhcp_client_stop(client)); return 0; } diff --git a/src/libsystemd-network/fuzz-dhcp-relay.c b/src/libsystemd-network/fuzz-dhcp-relay.c new file mode 100644 index 0000000000000..2c828b9548b8e --- /dev/null +++ b/src/libsystemd-network/fuzz-dhcp-relay.c @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "dhcp-relay-internal.h" +#include "fd-util.h" +#include "fuzz.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "tests.h" + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + union in_addr_union a; + + fuzz_setup_logging(); + + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + + _cleanup_(sd_dhcp_relay_unrefp) sd_dhcp_relay *relay = NULL; + ASSERT_OK(sd_dhcp_relay_new(&relay)); + ASSERT_OK(sd_dhcp_relay_attach_event(relay, e, SD_EVENT_PRIORITY_NORMAL)); + ASSERT_OK(in_addr_from_string(AF_INET, "198.51.100.1", &a)); + ASSERT_OK(sd_dhcp_relay_set_server_address(relay, &a.in)); + ASSERT_OK(sd_dhcp_relay_set_remote_id(relay, &IOVEC_MAKE_STRING("test-remote-id"))); + ASSERT_OK(sd_dhcp_relay_set_server_identifier_override(relay, true)); + + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *upstream = NULL; + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4242, /* is_upstream= */ true, &upstream)); + ASSERT_OK(sd_dhcp_relay_interface_set_ifname(upstream, "test-upstream")); + ASSERT_OK(in_addr_from_string(AF_INET, "198.51.100.2", &a)); + ASSERT_OK(sd_dhcp_relay_interface_set_address(upstream, &a.in, 24)); + + _cleanup_close_pair_ int upstream_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, upstream_fd)); + upstream->socket_fd = TAKE_FD(upstream_fd[0]); + ASSERT_OK(sd_dhcp_relay_interface_start(upstream)); + + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *downstream = NULL; + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4343, /* is_upstream= */ false, &downstream)); + ASSERT_OK(sd_dhcp_relay_interface_set_ifname(downstream, "test-downstream")); + ASSERT_OK(in_addr_from_string(AF_INET, "192.0.2.1", &a)); + ASSERT_OK(sd_dhcp_relay_interface_set_address(downstream, &a.in, 24)); + + ASSERT_OK(in_addr_from_string(AF_INET, "203.0.113.1", &a)); + ASSERT_OK(sd_dhcp_relay_downstream_set_gateway_address(downstream, &a.in)); + ASSERT_OK(sd_dhcp_relay_downstream_set_circuit_id(downstream, &IOVEC_MAKE_STRING("test-circuit-id"))); + ASSERT_OK(sd_dhcp_relay_downstream_set_virtual_subnet_selection(downstream, &IOVEC_MAKE_STRING("test-virtual-net"))); + + _cleanup_close_pair_ int downstream_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, downstream_fd)); + downstream->socket_fd = TAKE_FD(downstream_fd[0]); + ASSERT_OK(sd_dhcp_relay_interface_start(downstream)); + + (void) downstream_process_message(downstream, &IOVEC_MAKE(data, size), /* pktinfo= */ NULL); + (void) upstream_process_message(upstream, &IOVEC_MAKE(data, size), /* pktinfo= */ NULL); + + return 0; +} diff --git a/src/libsystemd-network/fuzz-dhcp-server-relay.c b/src/libsystemd-network/fuzz-dhcp-server-relay.c deleted file mode 100644 index b2c881c06773a..0000000000000 --- a/src/libsystemd-network/fuzz-dhcp-server-relay.c +++ /dev/null @@ -1,45 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include - -#include "fuzz.h" -#include "sd-dhcp-server.c" - -ssize_t sendto(int __fd, const void *__buf, size_t __n, int flags, const struct sockaddr *__addr, socklen_t __addr_len) { - return __n; -} - -ssize_t sendmsg(int __fd, const struct msghdr *__message, int flags) { - return 0; -} - -int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - struct in_addr address = {.s_addr = htobe32(UINT32_C(10) << 24 | UINT32_C(1))}; - union in_addr_union relay_address; - _cleanup_free_ uint8_t *message = NULL; - - if (size < sizeof(DHCPMessage)) - return 0; - - fuzz_setup_logging(); - - assert_se(sd_dhcp_server_new(&server, 1) >= 0); - assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0); - assert_se(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0) >= 0); - assert_se(in_addr_from_string(AF_INET, "192.168.5.1", &relay_address) >= 0); - assert_se(sd_dhcp_server_set_relay_target(server, &relay_address.in) >= 0); - assert_se(sd_dhcp_server_set_bind_to_interface(server, false) >= 0); - assert_se(sd_dhcp_server_set_relay_agent_information(server, "string:sample_circuit_id", "string:sample_remote_id") >= 0); - - size_t buflen = size; - buflen += relay_agent_information_length(server->agent_circuit_id, server->agent_remote_id) + 2; - assert_se(message = malloc(buflen)); - memcpy(message, data, size); - - server->fd = open("/dev/null", O_RDWR|O_CLOEXEC|O_NOCTTY); - assert_se(server->fd >= 0); - - (void) dhcp_server_relay_message(server, (DHCPMessage *) message, size - sizeof(DHCPMessage), buflen); - return 0; -} diff --git a/src/libsystemd-network/fuzz-dhcp-server.c b/src/libsystemd-network/fuzz-dhcp-server.c index bcd8af629ed3d..408f1590a4f33 100644 --- a/src/libsystemd-network/fuzz-dhcp-server.c +++ b/src/libsystemd-network/fuzz-dhcp-server.c @@ -1,22 +1,21 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include +#include "sd-event.h" + +#include "alloc-util.h" +#include "dhcp-server-internal.h" +#include "dhcp-server-lease-internal.h" +#include "dhcp-server-request.h" +#include "fd-util.h" #include "fuzz.h" +#include "hashmap.h" +#include "iovec-util.h" #include "rm-rf.h" -#include "sd-dhcp-server.c" +#include "tests.h" #include "tmpfile-util.h" -/* stub out network so that the server doesn't send */ -ssize_t sendto(int __fd, const void *__buf, size_t __n, int flags, const struct sockaddr *__addr, socklen_t __addr_len) { - return __n; -} - -ssize_t sendmsg(int __fd, const struct msghdr *__message, int flags) { - return 0; -} - static int add_lease(sd_dhcp_server *server, const struct in_addr *server_address, uint8_t i) { _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL; int r; @@ -30,10 +29,10 @@ static int add_lease(sd_dhcp_server *server, const struct in_addr *server_addres *lease = (sd_dhcp_server_lease) { .n_ref = 1, .address = htobe32(UINT32_C(10) << 24 | i), - .chaddr = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, + .hw_addr.length = ETH_ALEN, + .hw_addr.bytes = { 3, 3, 3, 3, 3, 3, }, .expiration = usec_add(now(CLOCK_BOOTTIME), USEC_PER_DAY), .gateway = server_address->s_addr, - .hlen = ETH_ALEN, .htype = ARPHRD_ETHER, .client_id.size = 2, @@ -64,43 +63,41 @@ static int add_static_lease(sd_dhcp_server *server, uint8_t i) { } int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; - _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; struct in_addr address = { .s_addr = htobe32(UINT32_C(10) << 24 | UINT32_C(1))}; - _cleanup_free_ uint8_t *duped = NULL; - _cleanup_close_ int dir_fd = -EBADF; - - if (size < sizeof(DHCPMessage)) - return 0; fuzz_setup_logging(); - assert_se(duped = memdup(data, size)); + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + _cleanup_close_ int dir_fd = ASSERT_OK(mkdtemp_open(NULL, 0, &tmpdir)); + + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); - dir_fd = mkdtemp_open(NULL, 0, &tmpdir); - assert_se(dir_fd >= 0); + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_new(&event)); - assert_se(sd_dhcp_server_new(&server, 1) >= 0); - assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0); - assert_se(sd_dhcp_server_set_lease_file(server, dir_fd, "leases") >= 0); - server->fd = open("/dev/null", O_RDWR|O_CLOEXEC|O_NOCTTY); - assert_se(server->fd >= 0); - assert_se(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0) >= 0); + _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; + ASSERT_OK(sd_dhcp_server_new(&server, 1)); + ASSERT_OK(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL)); + server->socket_fd = TAKE_FD(socket_fd[0]); + ASSERT_OK(sd_dhcp_server_set_lease_file(server, dir_fd, "leases")); + ASSERT_OK(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0)); /* add leases to the pool to expose additional code paths */ - assert_se(add_lease(server, &address, 2) >= 0); - assert_se(add_lease(server, &address, 3) >= 0); + ASSERT_OK(add_lease(server, &address, 2)); + ASSERT_OK(add_lease(server, &address, 3)); /* add static leases */ - assert_se(add_static_lease(server, 3) >= 0); - assert_se(add_static_lease(server, 4) >= 0); + ASSERT_OK(add_static_lease(server, 3)); + ASSERT_OK(add_static_lease(server, 4)); - (void) dhcp_server_handle_message(server, (DHCPMessage*) duped, size, NULL); + ASSERT_OK(sd_dhcp_server_start(server)); + (void) dhcp_server_process_message(server, &IOVEC_MAKE(data, size), /* mh= */ NULL); - assert_se(dhcp_server_save_leases(server) >= 0); + ASSERT_OK(dhcp_server_save_leases(server)); server->bound_leases_by_address = hashmap_free(server->bound_leases_by_address); server->bound_leases_by_client_id = hashmap_free(server->bound_leases_by_client_id); - assert_se(dhcp_server_load_leases(server) >= 0); + ASSERT_OK(dhcp_server_load_leases(server)); return 0; } diff --git a/src/libsystemd-network/icmp6-packet.c b/src/libsystemd-network/icmp6-packet.c index b4e8e7f85147e..23e6582b67a01 100644 --- a/src/libsystemd-network/icmp6-packet.c +++ b/src/libsystemd-network/icmp6-packet.c @@ -112,7 +112,7 @@ int icmp6_packet_receive(int fd, ICMP6Packet **ret) { if (!p) return -ENOMEM; - r = icmp6_receive(fd, p->raw_packet, p->raw_size, &p->sender_address, &p->timestamp); + r = icmp6_receive(fd, p->raw_packet, p->raw_size, &p->sender_address, &p->ifindex, &p->timestamp); if (r == -EADDRNOTAVAIL) return log_debug_errno(r, "ICMPv6: Received a packet from neither link-local nor null address."); if (r == -EMULTIHOP) diff --git a/src/libsystemd-network/icmp6-packet.h b/src/libsystemd-network/icmp6-packet.h index 929fff1f23c91..1b6f15727575c 100644 --- a/src/libsystemd-network/icmp6-packet.h +++ b/src/libsystemd-network/icmp6-packet.h @@ -8,6 +8,7 @@ typedef struct ICMP6Packet { unsigned n_ref; + int ifindex; struct in6_addr sender_address; struct triple_timestamp timestamp; diff --git a/src/libsystemd-network/icmp6-test-util.c b/src/libsystemd-network/icmp6-test-util.c index 38b3537ebe0a2..33315c4384113 100644 --- a/src/libsystemd-network/icmp6-test-util.c +++ b/src/libsystemd-network/icmp6-test-util.c @@ -9,6 +9,7 @@ #include "time-util.h" int test_fd[2] = EBADF_PAIR; +int test_ifindex = 42; static struct in6_addr dummy_link_local = { .s6_addr = { @@ -18,6 +19,8 @@ static struct in6_addr dummy_link_local = { }; int icmp6_bind(int ifindex, bool is_router) { + test_ifindex = ifindex; + if (!is_router && socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0) return -errno; @@ -33,6 +36,7 @@ int icmp6_receive( void *buffer, size_t size, struct in6_addr *ret_sender, + int *ret_ifindex, triple_timestamp *ret_timestamp) { assert_se(read(fd, buffer, size) == (ssize_t) size); @@ -43,5 +47,8 @@ int icmp6_receive( if (ret_sender) *ret_sender = dummy_link_local; + if (ret_ifindex) + *ret_ifindex = test_ifindex; + return 0; } diff --git a/src/libsystemd-network/icmp6-test-util.h b/src/libsystemd-network/icmp6-test-util.h index 124cab555218a..8e9f17eb952fb 100644 --- a/src/libsystemd-network/icmp6-test-util.h +++ b/src/libsystemd-network/icmp6-test-util.h @@ -2,3 +2,4 @@ #pragma once extern int test_fd[2]; +extern int test_ifindex; diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c index 4347f8b723bf0..6df78bd2eed76 100644 --- a/src/libsystemd-network/icmp6-util.c +++ b/src/libsystemd-network/icmp6-util.c @@ -72,6 +72,10 @@ int icmp6_bind(int ifindex, bool is_router) { if (r < 0) return r; + r = socket_set_recvpktinfo(s, AF_INET6, true); + if (r < 0) + return r; + r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true); if (r < 0) return r; @@ -108,11 +112,13 @@ int icmp6_receive( void *buffer, size_t size, struct in6_addr *ret_sender, + int *ret_ifindex, triple_timestamp *ret_timestamp) { /* This needs to be initialized with zero. See #20741. * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */ CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int)) + /* ttl */ + CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE_TIMEVAL) control = {}; struct iovec iov = { buffer, size }; union sockaddr_union sa = {}; @@ -145,6 +151,10 @@ int icmp6_receive( if (hops && *hops != 255) return -EMULTIHOP; + struct in6_pktinfo *pktinfo = CMSG_FIND_DATA(&msg, IPPROTO_IPV6, IPV6_PKTINFO, struct in6_pktinfo); + if (ret_ifindex) + *ret_ifindex = pktinfo ? pktinfo->ipi6_ifindex : 0; + if (ret_timestamp) triple_timestamp_from_cmsg(ret_timestamp, &msg); if (ret_sender) diff --git a/src/libsystemd-network/icmp6-util.h b/src/libsystemd-network/icmp6-util.h index a1183724b289c..d23e0fe0eb3cb 100644 --- a/src/libsystemd-network/icmp6-util.h +++ b/src/libsystemd-network/icmp6-util.h @@ -28,4 +28,5 @@ int icmp6_receive( void *buffer, size_t size, struct in6_addr *ret_sender, + int *ret_ifindex, triple_timestamp *ret_timestamp); 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/lldp-network.c b/src/libsystemd-network/lldp-network.c index 53dd50c606887..f8185a28029bb 100644 --- a/src/libsystemd-network/lldp-network.c +++ b/src/libsystemd-network/lldp-network.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "fd-util.h" #include "lldp-network.h" diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index e0012abe0bcf3..7340bed03855f 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -2,14 +2,22 @@ libsystemd_network_sources = files( 'arp-util.c', - 'dhcp-network.c', - 'dhcp-option.c', - 'dhcp-packet.c', + 'dhcp-client-send.c', + 'dhcp-message.c', + 'dhcp-message-dump.c', + 'dhcp-protocol.c', + 'dhcp-relay-downstream.c', + 'dhcp-relay-interface.c', + 'dhcp-relay-upstream.c', + 'dhcp-route.c', + 'dhcp-server-request.c', + 'dhcp-server-send.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', @@ -19,6 +27,7 @@ libsystemd_network_sources = files( 'sd-dhcp-client.c', 'sd-dhcp-duid.c', 'sd-dhcp-lease.c', + 'sd-dhcp-relay.c', 'sd-dhcp-server-lease.c', 'sd-dhcp-server.c', 'sd-dhcp6-client.c', @@ -34,6 +43,7 @@ libsystemd_network_sources = files( 'sd-ndisc-router.c', 'sd-ndisc-router-solicit.c', 'sd-radv.c', + 'tlv-util.c', ) sources += libsystemd_network_sources @@ -46,7 +56,7 @@ libsystemd_network = static_library( dependencies : userspace, build_by_default : false) -libsystemd_network_includes = [includes, include_directories('.')] +libsystemd_network_includes = [include_directories('.'), includes] ############################################################ @@ -74,7 +84,16 @@ executables += [ 'sources' : files('test-dhcp-client.c'), }, network_test_template + { - 'sources' : files('test-dhcp-option.c'), + 'sources' : files('test-dhcp-client-id.c'), + }, + network_test_template + { + 'sources' : files('test-dhcp-duid.c'), + }, + network_test_template + { + 'sources' : files('test-dhcp-message.c'), + }, + network_test_template + { + 'sources' : files('test-dhcp-relay.c'), }, network_test_template + { 'sources' : files('test-dhcp-server.c'), @@ -82,6 +101,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', @@ -105,19 +127,19 @@ executables += [ 'type' : 'manual', }, network_test_template + { - 'sources' : files('test-sd-dhcp-lease.c'), + 'sources' : files('test-tlv-util.c'), }, network_fuzz_template + { 'sources' : files('fuzz-dhcp-client.c'), }, network_fuzz_template + { - 'sources' : files('fuzz-dhcp6-client.c'), + 'sources' : files('fuzz-dhcp-relay.c'), }, network_fuzz_template + { - 'sources' : files('fuzz-dhcp-server.c'), + 'sources' : files('fuzz-dhcp6-client.c'), }, network_fuzz_template + { - 'sources' : files('fuzz-dhcp-server-relay.c'), + 'sources' : files('fuzz-dhcp-server.c'), }, network_fuzz_template + { 'sources' : files('fuzz-lldp-rx.c'), 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..e32b145370ee2 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -1,16 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include +#include "sd-dhcp-lease.h" + #include "alloc-util.h" -#include "dhcp-lease-internal.h" #include "dns-resolver-internal.h" #include "extract-word.h" -#include "hexdecoct.h" #include "in-addr-util.h" #include "network-internal.h" -#include "parse-util.h" #include "string-util.h" #include "strv.h" @@ -169,7 +167,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 (;;) { @@ -214,112 +212,3 @@ int deserialize_dnr(sd_dns_resolver **ret, const char *string) { *ret = TAKE_PTR(dnr); return n; } - -void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) { - assert(f); - assert(key); - assert(routes); - assert(size); - - fprintf(f, "%s=", key); - - for (size_t i = 0; i < size; i++) { - struct in_addr dest, gw; - uint8_t length; - - assert_se(sd_dhcp_route_get_destination(routes[i], &dest) >= 0); - assert_se(sd_dhcp_route_get_gateway(routes[i], &gw) >= 0); - assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &length) >= 0); - - fprintf(f, "%s,%s%s", - IN4_ADDR_PREFIX_TO_STRING(&dest, length), - IN4_ADDR_TO_STRING(&gw), - i < size - 1 ? " ": ""); - } - - fputs("\n", f); -} - -int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, const char *string) { - _cleanup_free_ struct sd_dhcp_route *routes = NULL; - size_t size = 0; - - assert(ret); - assert(ret_size); - assert(string); - - /* WORD FORMAT: dst_ip/dst_prefixlen,gw_ip */ - for (;;) { - _cleanup_free_ char *word = NULL; - char *tok, *tok_end; - unsigned n; - int r; - - r = extract_first_word(&string, &word, NULL, 0); - if (r < 0) - return r; - if (r == 0) - break; - - struct sd_dhcp_route route = {}; - - tok = word; - - /* get the subnet */ - tok_end = strchr(tok, '/'); - if (!tok_end) - continue; - *tok_end = '\0'; - - r = inet_aton(tok, &route.dst_addr); - if (r == 0) - continue; - - tok = tok_end + 1; - - /* get the prefixlen */ - tok_end = strchr(tok, ','); - if (!tok_end) - continue; - - *tok_end = '\0'; - - r = safe_atou(tok, &n); - if (r < 0 || n > 32) - continue; - - route.dst_prefixlen = (uint8_t) n; - tok = tok_end + 1; - - /* get the gateway */ - r = inet_aton(tok, &route.gw_addr); - if (r == 0) - continue; - - if (!GREEDY_REALLOC(routes, size + 1)) - return -ENOMEM; - - routes[size++] = route; - } - - *ret_size = size; - *ret = TAKE_PTR(routes); - - return 0; -} - -int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size) { - _cleanup_free_ char *hex_buf = NULL; - - assert(f); - assert(key); - assert(data); - - hex_buf = hexmem(data, size); - if (!hex_buf) - return -ENOMEM; - - fprintf(f, "%s=%s\n", key, hex_buf); - - return 0; -} diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h index 658247e350182..773f42c42de36 100644 --- a/src/libsystemd-network/network-internal.h +++ b/src/libsystemd-network/network-internal.h @@ -16,16 +16,3 @@ int deserialize_in6_addrs(struct in6_addr **ret, const char *string); int serialize_dnr(FILE *f, const sd_dns_resolver *dnr, size_t n_dnr, bool *with_leading_space); int deserialize_dnr(sd_dns_resolver **ret, const char *string); - -/* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */ -struct sd_dhcp_route; -struct sd_dhcp_lease; - -void serialize_dhcp_routes(FILE *f, const char *key, struct sd_dhcp_route **routes, size_t size); -int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, const char *string); - -/* It is not necessary to add deserialize_dhcp_option(). Use unhexmem() instead. */ -int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size); - -int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file); -int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 8c373146f6185..1594796887780 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -7,33 +7,26 @@ #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 "iovec-wrapper.h" +#include "ip-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" -#include "strv.h" #include "time-util.h" +#include "tlv-util.h" #include "web-util.h" #define MAX_MAC_ADDR_LEN CONST_MAX(INFINIBAND_ALEN, ETH_ALEN) @@ -41,71 +34,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, @@ -114,42 +47,6 @@ static const uint8_t default_req_opts[] = { SD_DHCP_OPTION_DOMAIN_NAME_SERVER, }; -/* RFC7844 section 3: - MAY contain the Parameter Request List option. - RFC7844 section 3.6: - The client intending to protect its privacy SHOULD only request a - minimal number of options in the PRL and SHOULD also randomly shuffle - the ordering of option codes in the PRL. If this random ordering - cannot be implemented, the client MAY order the option codes in the - PRL by option code number (lowest to highest). -*/ -/* NOTE: using PRL options that Windows 10 RFC7844 implementation uses */ -static const uint8_t default_req_opts_anonymize[] = { - SD_DHCP_OPTION_SUBNET_MASK, /* 1 */ - SD_DHCP_OPTION_ROUTER, /* 3 */ - SD_DHCP_OPTION_DOMAIN_NAME_SERVER, /* 6 */ - SD_DHCP_OPTION_DOMAIN_NAME, /* 15 */ - SD_DHCP_OPTION_ROUTER_DISCOVERY, /* 31 */ - SD_DHCP_OPTION_STATIC_ROUTE, /* 33 */ - SD_DHCP_OPTION_VENDOR_SPECIFIC, /* 43 */ - SD_DHCP_OPTION_NETBIOS_NAME_SERVER, /* 44 */ - SD_DHCP_OPTION_NETBIOS_NODE_TYPE, /* 46 */ - SD_DHCP_OPTION_NETBIOS_SCOPE, /* 47 */ - SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, /* 121 */ - SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, /* 249 */ - 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); @@ -179,6 +76,14 @@ int sd_dhcp_client_set_callback( return 0; } +int sd_dhcp_client_set_anonymize(sd_dhcp_client *client, int b) { + assert_return(client, -EINVAL); + assert_return(!sd_dhcp_client_is_running(client), -EBUSY); + + client->anonymize = !!b; + return 0; +} + int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast) { assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); @@ -208,12 +113,6 @@ int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) { return set_ensure_put(&client->req_opts, NULL, UINT8_TO_PTR(option)); } -static int client_request_contains(sd_dhcp_client *client, uint8_t option) { - assert(client); - - return set_contains(client->req_opts, UINT8_TO_PTR(option)); -} - int sd_dhcp_client_set_request_address( sd_dhcp_client *client, const struct in_addr *last_addr) { @@ -272,44 +171,55 @@ int sd_dhcp_client_set_mac( assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - assert_return(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND, ARPHRD_RAWIP, ARPHRD_NONE), -EINVAL); - static const uint8_t default_eth_bcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, - default_eth_hwaddr[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uint8_t default_eth_hwaddr[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; switch (arp_type) { + case ARPHRD_ETHER: + assert_return(addr_len == ETH_ALEN, -EINVAL); + assert_return(hw_addr, -EINVAL); + break; + + case ARPHRD_INFINIBAND: + assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); + assert_return(hw_addr, -EINVAL); + break; + case ARPHRD_RAWIP: case ARPHRD_NONE: - /* Linux cellular modem drivers (e.g. qmi_wwan) present a - * network interface of type ARPHRD_RAWIP(519) or - * ARPHRD_NONE(65534) when in point-to-point mode, but these - * are not valid DHCP hardware-type values. + /* Linux cellular modem drivers (e.g. qmi_wwan) present a network interface of type + * ARPHRD_RAWIP(519) or ARPHRD_NONE(65534) when in point-to-point mode, but these are not + * valid DHCP hardware-type values. * - * Apparently, it's best to just pretend that these are ethernet - * devices. Other approaches have been tried, but resulted in - * incompatibilities with some server software. See - * https://lore.kernel.org/netdev/cover.1228948072.git.inaky@linux.intel.com/ - */ + * Apparently, it's best to just pretend that these are ethernet devices. Other approaches + * have been tried, but resulted in incompatibilities with some server software. See + * https://lore.kernel.org/netdev/cover.1228948072.git.inaky@linux.intel.com/ */ arp_type = ARPHRD_ETHER; if (addr_len == 0) { - assert_cc(sizeof(default_eth_hwaddr) == ETH_ALEN); - assert_cc(sizeof(default_eth_bcast) == ETH_ALEN); - hw_addr = default_eth_hwaddr; - bcast_addr = default_eth_bcast; + /* If the specified hardware address length is 0, always use the default ones. */ addr_len = ETH_ALEN; + hw_addr = default_eth_hwaddr; + bcast_addr = NULL; + } else if (addr_len == ETH_ALEN) { + /* If the specified hardware address length is ETH_ALEN, use the default ones when + * unspecified. */ + if (!hw_addr) + hw_addr = default_eth_hwaddr; + } else { + /* Otherwise, user must specify valid addresses. */ + assert_return(hw_addr, -EINVAL); + assert_return(bcast_addr, -EINVAL); } break; - } - assert_return(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND), -EINVAL); - assert_return(hw_addr, -EINVAL); - assert_return(addr_len == (arp_type == ARPHRD_ETHER ? ETH_ALEN : INFINIBAND_ALEN), -EINVAL); + default: + return -EINVAL; + } client->arp_type = arp_type; hw_addr_set(&client->hw_addr, hw_addr, addr_len); hw_addr_set(&client->bcast_addr, bcast_addr, bcast_addr ? addr_len : 0); - - return 0; + return hw_addr_ensure_broadcast(&client->bcast_addr, arp_type); } int sd_dhcp_client_get_client_id(sd_dhcp_client *client, const sd_dhcp_client_id **ret) { @@ -509,28 +419,30 @@ int sd_dhcp_client_set_mud_url( return free_and_strdup(&client->mudurl, mudurl); } -int sd_dhcp_client_set_user_class( - sd_dhcp_client *client, - char * const *user_class) { - - char **s = NULL; +int dhcp_client_set_user_class(sd_dhcp_client *client, const struct iovec_wrapper *user_class) { + int r; assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - assert_return(!strv_isempty(user_class), -EINVAL); - STRV_FOREACH(p, user_class) { - size_t n = strlen(*p); + if (iovw_isempty(user_class)) { + iovw_done_free(&client->user_class); + return 0; + } - if (n > 255 || n == 0) + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + FOREACH_ARRAY(iovec, user_class->iovec, user_class->count) { + if (iovec->iov_len == 0 || iovec->iov_len > UINT8_MAX) return -EINVAL; - } - s = strv_copy(user_class); - if (!s) - return -ENOMEM; + r = iovw_extend_iov(&iovw, iovec); + if (r < 0) + return r; + } - return strv_free_and_replace(client->user_class, s); + iovw_done_free(&client->user_class); + client->user_class = TAKE_STRUCT(iovw); + return 0; } int sd_dhcp_client_set_client_port( @@ -559,13 +471,14 @@ int sd_dhcp_client_set_port( int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) { assert_return(client, -EINVAL); - assert_return(mtu >= DHCP_MIN_PACKET_SIZE, -ERANGE); /* MTU may be changed by the acquired lease. Hence, we cannot require that the client is stopped here. * Please do not add assertion for !sd_dhcp_client_is_running(client) here. */ - client->mtu = mtu; + if (mtu < IPV4_MIN_MTU) + return -ERANGE; + client->mtu = mtu; return 0; } @@ -578,39 +491,18 @@ int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempt return 0; } -int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v) { - int r; - - assert_return(client, -EINVAL); - assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - assert_return(v, -EINVAL); - - r = ordered_hashmap_ensure_put(&client->extra_options, &dhcp_option_hash_ops, UINT_TO_PTR(v->option), v); - if (r < 0) - return r; +int dhcp_client_set_extra_options(sd_dhcp_client *client, TLV *options) { + assert(client); + assert(!sd_dhcp_client_is_running(client)); - sd_dhcp_option_ref(v); - return 0; + return unref_and_replace_new_ref(client->extra_options, options, tlv_ref, tlv_unref); } -int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v) { - int r; - - assert_return(client, -EINVAL); - assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - assert_return(v, -EINVAL); - - r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp_option_hash_ops); - if (r < 0) - return -ENOMEM; - - r = ordered_hashmap_put(client->vendor_options, v, v); - if (r < 0) - return r; - - sd_dhcp_option_ref(v); +int dhcp_client_set_vendor_options(sd_dhcp_client *client, TLV *options) { + assert(client); + assert(!sd_dhcp_client_is_running(client)); - return 1; + return unref_and_replace_new_ref(client->vendor_options, options, tlv_ref, tlv_unref); } int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) { @@ -625,7 +517,7 @@ int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) { return 0; } -int sd_dhcp_client_set_service_type(sd_dhcp_client *client, int type) { +int sd_dhcp_client_set_ip_service_type(sd_dhcp_client *client, uint8_t type) { assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); @@ -638,7 +530,6 @@ int sd_dhcp_client_set_socket_priority(sd_dhcp_client *client, int socket_priori assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - client->socket_priority_set = true; client->socket_priority = socket_priority; return 0; @@ -683,6 +574,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 +617,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); +} + +static void client_initialize(sd_dhcp_client *client) { + assert(client); - client->discover_attempt = 0; - client->request_attempt = 0; + 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) { @@ -761,12 +674,6 @@ static usec_t client_compute_request_timeout(uint64_t attempt) { return usec_sub_signed(timeout, RFC2131_RANDOM_FUZZ); } -/* RFC2131 section 4.4.5: - * T1 defaults to (0.5 * duration_of_lease). - * T2 defaults to (0.875 * duration_of_lease). */ -#define T1_DEFAULT(lifetime) ((lifetime) / 2) -#define T2_DEFAULT(lifetime) (((lifetime) * 7) / 8) - /* RFC2131 section 4.4.5: * the client SHOULD wait one-half of the remaining time until T2 (in RENEWING state) * and one-half of the remaining lease time (in REBINDING state), down to a minimum @@ -777,1001 +684,248 @@ static usec_t client_compute_reacquisition_timeout(usec_t now_usec, usec_t expir return MAX(usec_sub_unsigned(expire, now_usec) / 2, 60 * USEC_PER_SEC); } -static int cmp_uint8(const uint8_t *a, const uint8_t *b) { - return CMP(*a, *b); -} - -static int client_message_init( - sd_dhcp_client *client, - uint8_t type, - DHCPPacket **ret_packet, - size_t *ret_optlen, - size_t *ret_optoffset) { +static int client_timeout_resend( + sd_event_source *s, + uint64_t usec, + void *userdata) { - _cleanup_free_ DHCPPacket *packet = NULL; - size_t optlen, optoffset, size; - usec_t time_now; - uint16_t secs; + sd_dhcp_client *client = ASSERT_PTR(userdata); + DHCP_CLIENT_DONT_DESTROY(client); + usec_t time_now, next_timeout; int r; - assert(client); - assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE, DHCP_DECLINE)); - assert(ret_packet); - assert(ret_optlen); - assert(ret_optoffset); - - optlen = DHCP_MIN_OPTIONS_SIZE; - size = sizeof(DHCPPacket) + optlen; - - packet = malloc0(size); - if (!packet) - return -ENOMEM; - if (client->bootp) { - /* BOOTP supports options, but only DHCP_OPTION_END is used. The rest of the 64-byte buffer - * is set to zero, per RFC1542. Allow for this by initialaizing optoffset to 0. */ - optoffset = 0; - r = bootp_message_init( - &packet->dhcp, BOOTREQUEST, client->xid, client->arp_type, - client->hw_addr.length, client->hw_addr.bytes); - } else - r = dhcp_message_init( - &packet->dhcp, BOOTREQUEST, client->xid, client->arp_type, - client->hw_addr.length, client->hw_addr.bytes, - type, optlen, &optoffset); - if (r < 0) - return r; + assert(s); + assert(client->event); - /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers - refuse to issue an DHCP lease if 'secs' is set to zero */ r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now); if (r < 0) - return r; - assert(time_now >= client->start_time); - - /* seconds between sending first and last DISCOVER - * must always be strictly positive to deal with broken servers */ - secs = ((time_now - client->start_time) / USEC_PER_SEC) ?: 1; - packet->dhcp.secs = htobe16(secs); - - /* RFC2131 section 4.1 - A client that cannot receive unicast IP datagrams until its protocol - software has been configured with an IP address SHOULD set the - BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or - DHCPREQUEST messages that client sends. The BROADCAST bit will - provide a hint to the DHCP server and BOOTP relay agent to broadcast - any messages to the client on the client's subnet. - - Note: some interfaces needs this to be enabled, but some networks - needs this to be disabled as broadcasts are filteretd, so this - needs to be configurable */ - if (client->request_broadcast || client->arp_type != ARPHRD_ETHER) - packet->dhcp.flags = htobe16(0x8000); - - if (client->bootp) { - *ret_optlen = optlen; - *ret_optoffset = optoffset; - *ret_packet = TAKE_PTR(packet); - return 0; - } + goto error; - /* Some DHCP servers will refuse to issue an DHCP lease if the Client - Identifier option is not set */ - r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_CLIENT_IDENTIFIER, - client->client_id.size, - client->client_id.raw); - if (r < 0) - return r; + switch (client->state) { - /* RFC2131 section 3.5: - in its initial DHCPDISCOVER or DHCPREQUEST message, a - client may provide the server with a list of specific - parameters the client is interested in. If the client - includes a list of parameters in a DHCPDISCOVER message, - it MUST include that list in any subsequent DHCPREQUEST - messages. - */ - - /* RFC7844 section 3: - 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) { - _cleanup_free_ uint8_t *opts = NULL; - size_t n_opts, i = 0; - void *val; - - n_opts = set_size(client->req_opts); - opts = new(uint8_t, n_opts); - if (!opts) - return -ENOMEM; - - SET_FOREACH(val, client->req_opts) - opts[i++] = PTR_TO_UINT8(val); - assert(i == n_opts); - - /* For anonymizing the request, let's sort the options. */ - typesafe_qsort(opts, n_opts, cmp_uint8); - - r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, - n_opts, opts); - if (r < 0) - return r; - } + case DHCP_STATE_RENEWING: + next_timeout = client_compute_reacquisition_timeout(time_now, client->t2_time); + break; - /* RFC2131 section 3.5: - The client SHOULD include the ’maximum DHCP message size’ option to - let the server know how large the server may make its DHCP messages. - - Note (from ConnMan): Some DHCP servers will send bigger DHCP packets - than the defined default size unless the Maximum Message Size option - is explicitly set - - RFC3442 "Requirements to Avoid Sizing Constraints": - Because a full routing table can be quite large, the standard 576 - octet maximum size for a DHCP message may be too short to contain - some legitimate Classless Static Route options. Because of this, - clients implementing the Classless Static Route option SHOULD send a - Maximum DHCP Message Size [4] option if the DHCP client's TCP/IP - stack is capable of receiving larger IP datagrams. In this case, the - client SHOULD set the value of this option to at least the MTU of the - interface that the client is configuring. The client MAY set the - value of this option higher, up to the size of the largest UDP packet - it is prepared to accept. (Note that the value specified in the - Maximum DHCP Message Size option is the total maximum packet size, - including IP and UDP headers.) - */ - /* RFC7844 section 3: - SHOULD NOT contain any other option. */ - if (!client->anonymize && IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST)) { - be16_t max_size = htobe16(MIN(client->mtu - DHCP_IP_UDP_SIZE, (uint32_t) UINT16_MAX)); - r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, - 2, &max_size); - if (r < 0) - return r; - } + case DHCP_STATE_REBINDING: + next_timeout = client_compute_reacquisition_timeout(time_now, client->expire_time); + break; - *ret_optlen = optlen; - *ret_optoffset = optoffset; - *ret_packet = TAKE_PTR(packet); + case DHCP_STATE_INIT: + client_set_state(client, DHCP_STATE_SELECTING); + _fallthrough_; - return 0; -} + case DHCP_STATE_SELECTING: + if (client->discover_attempt >= client->max_discover_attempts) { + r = -ETIMEDOUT; + goto error; + } -static int client_append_fqdn_option( - DHCPMessage *message, - size_t optlen, - size_t *optoffset, - const char *fqdn) { + client->discover_attempt++; + next_timeout = client_compute_request_timeout(client->discover_attempt); + break; - uint8_t buffer[3 + DHCP_MAX_FQDN_LENGTH]; - int r; + case DHCP_STATE_INIT_REBOOT: + client_set_state(client, DHCP_STATE_REBOOTING); + _fallthrough_; - buffer[0] = DHCP_FQDN_FLAG_S | /* Request server to perform A RR DNS updates */ - DHCP_FQDN_FLAG_E; /* Canonical wire format */ - buffer[1] = 0; /* RCODE1 (deprecated) */ - buffer[2] = 0; /* RCODE2 (deprecated) */ + 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. */ - r = dns_name_to_wire_format(fqdn, buffer + 3, sizeof(buffer) - 3, false); - if (r > 0) - r = dhcp_option_append(message, optlen, optoffset, 0, - SD_DHCP_OPTION_FQDN, 3 + r, buffer); + if (client->request_attempt >= MAX_REQUEST_ATTEMPTS_ON_REBOOTING) + goto restart; - return r; -} + client->request_attempt++; + next_timeout = client_compute_request_timeout(client->request_attempt) / 4; + break; -static int dhcp_client_send_raw( - sd_dhcp_client *client, - DHCPPacket *packet, - size_t len) { + case DHCP_STATE_REQUESTING: + if (client->request_attempt >= MAX_REQUEST_ATTEMPTS) + goto restart; - dhcp_packet_append_ip_headers(packet, INADDR_ANY, client->port, - INADDR_BROADCAST, client->server_port, len, client->ip_service_type); + client->request_attempt++; + next_timeout = client_compute_request_timeout(client->request_attempt); + break; - return dhcp_network_send_raw_socket(client->fd, &client->link, - packet, len); -} + default: + assert_not_reached(); + } -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; + r = event_reset_time_relative( + client->event, &client->timeout_resend, + CLOCK_BOOTTIME, next_timeout, 10 * USEC_PER_MSEC, + client_timeout_resend, client, + client->event_priority, "dhcp4-resend-timer", true); + if (r < 0) + goto error; - assert(client); + switch (client->state) { + case DHCP_STATE_SELECTING: + r = dhcp_client_send_message(client, DHCP_DISCOVER); + if (r < 0 && client->discover_attempt >= client->max_discover_attempts) + goto error; - if (client->hostname) { - /* According to RFC 4702 "clients that send the Client FQDN option in - their messages MUST NOT also send the Host Name option". Just send - one of the two depending on the hostname type. - */ - if (dns_name_is_single_label(client->hostname)) { - /* it is unclear from RFC 2131 if client should send hostname in - DHCPDISCOVER but dhclient does and so we do as well - */ - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_HOST_NAME, - strlen(client->hostname), client->hostname); - } else - r = client_append_fqdn_option(&packet->dhcp, optlen, optoffset, - client->hostname); - if (r < 0) - return r; - } + if (client->discover_attempt >= TRANSIENT_FAILURE_ATTEMPTS) + client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE); + break; - if (client->vendor_class_identifier) { - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, - strlen(client->vendor_class_identifier), - client->vendor_class_identifier); - if (r < 0) - return r; - } + case DHCP_STATE_REBOOTING: + r = dhcp_client_send_message(client, DHCP_REQUEST); + if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS_ON_REBOOTING) + goto restart; + break; - if (client->mudurl) { - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_MUD_URL, - strlen(client->mudurl), - client->mudurl); - if (r < 0) - return r; - } + case DHCP_STATE_REQUESTING: + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + r = dhcp_client_send_message(client, DHCP_REQUEST); + if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS) + goto restart; + break; - 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); - if (r < 0) - return r; + default: + assert_not_reached(); } - ORDERED_HASHMAP_FOREACH(j, client->extra_options) { - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - j->option, j->length, j->data); - if (r < 0) - return r; - } + return 0; - if (!ordered_hashmap_isempty(client->vendor_options)) { - r = dhcp_option_append( - &packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_VENDOR_SPECIFIC, - ordered_hashmap_size(client->vendor_options), client->vendor_options); - if (r < 0) - return r; - } +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 */ + 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 + errors into the event loop handler */ return 0; } -static int client_send_dhcp_discover(sd_dhcp_client *client) { - _cleanup_free_ DHCPPacket *discover = NULL; - size_t optoffset, optlen; - int r; - +static int client_initialize_time_events(sd_dhcp_client *client) { assert(client); - assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING)); + assert(client->event); - r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset); - if (r < 0) - return r; + 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); +} - /* the client may suggest values for the network address - and lease time in the DHCPDISCOVER message. The client may include - the ’requested IP address’ option to suggest that a particular IP - address be assigned, and may include the ’IP address lease time’ - option to suggest the lease time it would like. - */ - /* RFC7844 section 3: - SHOULD NOT contain any other option. */ - if (!client->anonymize && client->last_addr != INADDR_ANY) { - r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, - 4, &client->last_addr); - if (r < 0) - return r; - } +static int client_start_delayed(sd_dhcp_client *client) { + assert(client); + DHCP_CLIENT_DONT_DESTROY(client); - if (client->rapid_commit) { - r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_RAPID_COMMIT, 0, NULL); - if (r < 0) - return r; - } + client_disable_event_sources(client); + client->lease = sd_dhcp_lease_unref(client->lease); - r = client_append_common_discover_request_options(client, discover, &optoffset, optlen); - if (r < 0) - return r; + client->xid = random_u32(); + client->start_time = now(CLOCK_BOOTTIME); - r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; + if (client->state != DHCP_STATE_INIT_REBOOT) + client_set_state(client, DHCP_STATE_INIT); - r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset); - if (r < 0) - return r; + return client_initialize_time_events(client); +} - log_dhcp_client(client, "DISCOVER"); +static int client_start(sd_dhcp_client *client) { + assert(client); - return 0; + client->start_delay = 0; + return client_start_delayed(client); } -static int client_send_bootp_discover(sd_dhcp_client *client) { - _cleanup_free_ DHCPPacket *discover = NULL; - size_t optoffset, optlen; - int r; - +static int client_restart(sd_dhcp_client *client) { assert(client); - assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING)); + DHCP_CLIENT_DONT_DESTROY(client); - r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset); - if (r < 0) - return r; + /* This is called when we receive a DHCPNAK or could not receive any replies. */ - r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; + /* 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); - /* RFC1542 section 3.5: - * if the client has no information to communicate to the server, the octet immediately following the - * magic cookie SHOULD be set to the "End" tag (255) and the remaining octets of the 'vend' field - * SHOULD be set to zero. - * - * Use this RFC, along with the fact that some BOOTP servers require a 64-byte vend field, to suggest - * that we always zero and send 64 bytes in the options field. The first four bites are the "magic" - * field, so this only needs to add 60 bytes. */ - if (optoffset < 60 && optlen >= 60) { - memzero(&discover->dhcp.options[optoffset], optlen - optoffset); - optoffset = 60; + if (client->state == DHCP_STATE_STOPPED) + return 0; /* The notify callback stopped the client. */ } - r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset); - if (r < 0) - return r; + /* 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); - log_dhcp_client(client, "DISCOVER"); - return 0; + /* 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); + + log_dhcp_client(client, "REBOOT in %s", FORMAT_TIMESPAN(client->start_delay, USEC_PER_SEC)); + return client_start_delayed(client); } -static int client_send_request(sd_dhcp_client *client) { - _cleanup_free_ DHCPPacket *request = NULL; - size_t optoffset, optlen; +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; - assert(client); - assert(!client->bootp); + log_dhcp_client(client, "EXPIRED"); - r = client_message_init(client, DHCP_REQUEST, &request, &optlen, &optoffset); - if (r < 0) - return r; - - switch (client->state) { - /* See RFC2131 section 4.3.2 (note that there is a typo in the RFC, - SELECTING should be REQUESTING) - */ - - case DHCP_STATE_REQUESTING: - /* Client inserts the address of the selected server in ’server - identifier’, ’ciaddr’ MUST be zero, ’requested IP address’ MUST be - filled in with the yiaddr value from the chosen DHCPOFFER. - */ - - r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_SERVER_IDENTIFIER, - 4, &client->lease->server_address); - if (r < 0) - return r; - - r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, - 4, &client->lease->address); - if (r < 0) - return r; - - break; - - case DHCP_STATE_INIT_REBOOT: - /* ’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. - */ - r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, - 4, &client->last_addr); - if (r < 0) - return r; - break; - - case DHCP_STATE_RENEWING: - /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ - option MUST NOT be filled in, ’ciaddr’ MUST be filled in with - client’s IP address. - */ - - case DHCP_STATE_REBINDING: - /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ - option MUST NOT be filled in, ’ciaddr’ MUST be filled in with - client’s IP address. - - 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; - } - - r = client_append_common_discover_request_options(client, request, &optoffset, optlen); - if (r < 0) - return r; - - r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - 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); - else - r = dhcp_client_send_raw(client, request, sizeof(DHCPPacket) + optoffset); - if (r < 0) - return r; - - switch (client->state) { - - case DHCP_STATE_REQUESTING: - log_dhcp_client(client, "REQUEST (requesting)"); - break; - - case DHCP_STATE_INIT_REBOOT: - log_dhcp_client(client, "REQUEST (init-reboot)"); - break; - - case DHCP_STATE_RENEWING: - log_dhcp_client(client, "REQUEST (renewing)"); - break; - - case DHCP_STATE_REBINDING: - log_dhcp_client(client, "REQUEST (rebinding)"); - break; - - default: - log_dhcp_client(client, "REQUEST (invalid)"); - } - - return 0; -} - -static int client_start(sd_dhcp_client *client); - -static int client_timeout_resend( - sd_event_source *s, - uint64_t usec, - void *userdata) { - - sd_dhcp_client *client = ASSERT_PTR(userdata); - DHCP_CLIENT_DONT_DESTROY(client); - usec_t time_now, next_timeout; - int r; - - assert(s); - assert(client->event); - - r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now); - if (r < 0) - goto error; - - switch (client->state) { - - case DHCP_STATE_RENEWING: - next_timeout = client_compute_reacquisition_timeout(time_now, client->t2_time); - break; - - case DHCP_STATE_REBINDING: - 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: - case DHCP_STATE_SELECTING: - if (client->discover_attempt >= client->max_discover_attempts) - goto error; - - client->discover_attempt++; - next_timeout = client_compute_request_timeout(client->discover_attempt); - break; - case DHCP_STATE_REQUESTING: - case DHCP_STATE_BOUND: - if (client->request_attempt >= client->max_request_attempts) - goto error; - - 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(); - } - - r = event_reset_time_relative( - client->event, &client->timeout_resend, - CLOCK_BOOTTIME, next_timeout, 10 * USEC_PER_MSEC, - client_timeout_resend, client, - client->event_priority, "dhcp4-resend-timer", true); - if (r < 0) - 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); - if (r < 0 && client->discover_attempt >= client->max_discover_attempts) - goto error; - 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: - break; - - case DHCP_STATE_STOPPED: - default: - r = -EINVAL; - goto error; - } - - if (client->discover_attempt >= TRANSIENT_FAILURE_ATTEMPTS) - client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE); - - return 0; - -error: - /* 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); - return 0; - } - client_stop(client, r); - - /* Errors were dealt with when stopping the client, don't spill - errors into the event loop handler */ - return 0; -} - -static int client_initialize_io_events( - sd_dhcp_client *client, - sd_event_io_handler_t io_callback) { - - int r; - - 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; -} - -static int client_initialize_time_events(sd_dhcp_client *client) { - usec_t usec = 0; - int r; - - assert(client); - assert(client->event); - - (void) event_source_disable(client->timeout_ipv6_only_mode); + client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); - if (client->start_delay > 0) { - assert_se(sd_event_now(client->event, CLOCK_BOOTTIME, &usec) >= 0); - usec = usec_add(usec, client->start_delay); - } + if (client->state == DHCP_STATE_STOPPED) + return 0; /* The notify callback stopped the client. */ - r = event_reset_time(client->event, &client->timeout_resend, - CLOCK_BOOTTIME, - usec, 0, - client_timeout_resend, client, - client->event_priority, "dhcp4-resend-timer", true); + r = client_start(client); if (r < 0) client_stop(client, r); return 0; } -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); - - return 0; -} - -static int client_start_delayed(sd_dhcp_client *client) { - int r; - - 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); - - client->xid = random_u32(); - - 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; - } - client->fd = r; - - client->start_time = now(CLOCK_BOOTTIME); - - if (client->state == DHCP_STATE_STOPPED) - client->state = DHCP_STATE_INIT; - - return client_initialize_events(client, client_receive_message_raw); -} - -static int client_start(sd_dhcp_client *client) { - client->start_delay = 0; - 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); - - 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); - } - - return 0; -} - 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; - 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; - - return client_initialize_time_events(client); -} - -static int dhcp_option_parse_and_verify( - sd_dhcp_client *client, - DHCPMessage *message, - size_t len, - sd_dhcp_lease *lease) { - - _cleanup_free_ char *error_message = NULL; - int r; - - assert(client); - assert(message); - assert(lease); - - r = dhcp_option_parse(message, len, dhcp_lease_parse_options, lease, &error_message); - if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to parse DHCP options, ignoring: %m"); - - switch (client->state) { - case DHCP_STATE_SELECTING: - if (r == DHCP_ACK) { - if (!client->rapid_commit) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received unexpected ACK, ignoring."); - if (!lease->rapid_commit) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received rapid ACK without Rapid Commit option, ignoring."); - } else if (r == DHCP_OFFER) { - if (lease->rapid_commit) { - /* Some RFC incompliant servers provides an OFFER with a rapid commit option. - * See https://github.com/systemd/systemd/issues/29904. - * Let's support such servers gracefully. */ - log_dhcp_client(client, "received OFFER with Rapid Commit option, ignoring."); - lease->rapid_commit = false; - } - if (lease->lifetime == 0 && client->fallback_lease_lifetime > 0) - lease->lifetime = client->fallback_lease_lifetime; - } else - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received unexpected message, ignoring."); - - break; - - case DHCP_STATE_REBOOTING: - case DHCP_STATE_REQUESTING: - case DHCP_STATE_RENEWING: - case DHCP_STATE_REBINDING: - if (r == DHCP_NAK) { - if (client->lease && client->lease->server_address != lease->server_address) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "NAK from unexpected server, ignoring: %s", - strna(error_message)); - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EADDRNOTAVAIL), - "NAK: %s", strna(error_message)); - } - if (r != DHCP_ACK) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received message was not an ACK, ignoring."); - break; - - default: - assert_not_reached(); - } - - lease->next_server = message->siaddr; - lease->address = message->yiaddr; - - if (lease->address == 0 || - lease->server_address == 0 || - lease->lifetime == 0) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received lease lacks address, server address or lease lifetime, ignoring."); - - return 0; -} - -static int bootp_option_parse_and_verify( - sd_dhcp_client *client, - DHCPMessage *message, - size_t len, - sd_dhcp_lease *lease) { - - int r; - - assert(client); - assert(message); - assert(lease); - - r = dhcp_option_parse(message, len, dhcp_lease_parse_options, lease, /* ret_error_message= */ NULL); - if (r == -ENOMSG) - r = DHCP_ACK; /* BOOTP messages don't have a DHCP message type option */ - else if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to parse BOOTP options, ignoring: %m"); - else - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), "Received unexpected message, ignoring."); - - log_dhcp_client(client, "BOOTP identified, using infinite lease. BOOTP siaddr=(%#x), DHCP Server Identifier=(%#x)", - message->siaddr, lease->server_address); - - lease->lifetime = USEC_INFINITY; - lease->address = message->yiaddr; - if (lease->server_address == 0) - lease->server_address = message->siaddr; - - /* BOOTP protocol does not have any OFFER and REQUEST process. Hence, it is mostly equivalent to - * Rapid Commit process in DHCP. */ - lease->rapid_commit = true; - - if (lease->address == 0) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), "received lease lacks address, ignoring."); - - return 0; -} - -static int client_parse_message( - sd_dhcp_client *client, - DHCPMessage *message, - size_t len, - sd_dhcp_lease **ret) { - - _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; - int r; - - assert(client); - assert(message); - assert(ret); - - r = dhcp_lease_new(&lease); - if (r < 0) - return r; - - if (sd_dhcp_client_id_is_set(&client->client_id)) { - r = dhcp_lease_set_client_id(lease, &client->client_id); - if (r < 0) - return r; - } - - if (client->bootp) - r = bootp_option_parse_and_verify(client, message, len, lease); - else - r = dhcp_option_parse_and_verify(client, message, len, lease); - if (r < 0) - return r; - - r = dhcp_lease_set_default_subnet_mask(lease); - if (r < 0) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG), - "received lease lacks subnet mask, and a fallback one cannot be generated, ignoring."); - - /* RFC 8925 section 3.2 - * If the client did not include the IPv6-Only Preferred option code in the Parameter Request List in - * the DHCPDISCOVER or DHCPREQUEST message, it MUST ignore the IPv6-Only Preferred option in any - * messages received from the server. */ - if (lease->ipv6_only_preferred_usec > 0 && - !client_request_contains(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)) { - log_dhcp_client(client, "Received message with unrequested IPv6-only preferred option, ignoring the option."); - lease->ipv6_only_preferred_usec = 0; - } - - *ret = TAKE_PTR(lease); - return 0; -} - -static int client_handle_offer_or_rapid_ack(sd_dhcp_client *client, DHCPMessage *message, size_t len, const triple_timestamp *timestamp) { - _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; - int r; - - assert(client); - assert(message); - - r = client_parse_message(client, message, len, &lease); - if (r < 0) - return r; - - dhcp_lease_set_timestamp(lease, timestamp); - - dhcp_lease_unref_and_replace(client->lease, lease); - - if (client->lease->rapid_commit) { - log_dhcp_client(client, "ACK"); - return SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; - } - - if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0) - return -ENOMSG; - - log_dhcp_client(client, "OFFER"); - 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); + client_set_state(client, DHCP_STATE_RENEWING); - return 0; + return client_timeout_resend(s, usec, userdata); } 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 +935,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) { @@ -1829,32 +967,6 @@ static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) { return true; } -static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *message, size_t len, const triple_timestamp *timestamp) { - _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; - int r; - - assert(client); - assert(message); - - r = client_parse_message(client, message, len, &lease); - if (r < 0) - return r; - - dhcp_lease_set_timestamp(lease, timestamp); - - if (!client->lease) - r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; - else if (lease_equal(client->lease, lease)) - r = SD_DHCP_CLIENT_EVENT_RENEW; - else - r = SD_DHCP_CLIENT_EVENT_IP_CHANGE; - - dhcp_lease_unref_and_replace(client->lease, lease); - - log_dhcp_client(client, "ACK"); - return r; -} - static int client_set_lease_timeouts(sd_dhcp_client *client) { usec_t time_now; int r; @@ -1878,22 +990,6 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { if (r < 0) return r; - /* verify that 0 < t2 < lifetime */ - if (client->lease->t2 == 0 || client->lease->t2 >= client->lease->lifetime) - client->lease->t2 = T2_DEFAULT(client->lease->lifetime); - /* verify that 0 < t1 < lifetime */ - if (client->lease->t1 == 0 || client->lease->t1 >= client->lease->t2) - client->lease->t1 = T1_DEFAULT(client->lease->lifetime); - /* now, if t1 >= t2, t1 *must* be T1_DEFAULT, since the previous check - * could not evaluate to false if t1 >= t2; so setting t2 to T2_DEFAULT - * guarantees t1 < t2. */ - if (client->lease->t1 >= client->lease->t2) - client->lease->t2 = T2_DEFAULT(client->lease->lifetime); - - assert(client->lease->t1 > 0); - assert(client->lease->t1 < client->lease->t2); - assert(client->lease->t2 < client->lease->lifetime); - r = sd_dhcp_lease_get_lifetime_timestamp(client->lease, CLOCK_BOOTTIME, &client->expire_time); if (r < 0) return r; @@ -1962,249 +1058,94 @@ 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, sd_dhcp_lease *lease) { int r; assert(client); + assert(lease); - if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING)) + int notify_event; + switch (client->state) { + case DHCP_STATE_SELECTING: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_REBOOTING: notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; + break; + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + assert(client->lease); + if (lease_equal(client->lease, lease)) + notify_event = SD_DHCP_CLIENT_EVENT_RENEW; + else + notify_event = SD_DHCP_CLIENT_EVENT_IP_CHANGE; + break; + default: + assert_not_reached(); + } + + unref_and_replace_new_ref(client->lease, lease, sd_dhcp_lease_ref, sd_dhcp_lease_unref); + + 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); +static int client_handle_message(sd_dhcp_client *client, const struct iovec *iov, const triple_timestamp *timestamp) { 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); + assert(iov); - client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); - - r = client_initialize(client); - if (r < 0) + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + r = dhcp_client_parse_message(client, iov, &lease); + if (ERRNO_IS_NEG_RESOURCE(r)) 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; -} - -static int client_verify_message_header(sd_dhcp_client *client, DHCPMessage *message, size_t len) { - const uint8_t *expected_chaddr = NULL; - uint8_t expected_hlen = 0; - - assert(client); - assert(message); - - if (len < sizeof(DHCPMessage)) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), - "Too small to be a DHCP message, ignoring."); - - if (be32toh(message->magic) != DHCP_MAGIC_COOKIE) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), - "Not a DHCP message, ignoring."); - - if (message->op != BOOTREPLY) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), - "Not a BOOTREPLY message, ignoring."); - - if (message->htype != client->arp_type) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), - "Packet type does not match client type, ignoring."); - - if (client->arp_type == ARPHRD_ETHER) { - expected_hlen = ETH_ALEN; - expected_chaddr = client->hw_addr.bytes; - } - - if (message->hlen != expected_hlen) - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), - "Received packet hlen (%u) does not match expected (%u), ignoring.", - message->hlen, expected_hlen); - - if (memcmp_safe(message->chaddr, expected_chaddr, expected_hlen)) - 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 */ - return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), - "Received xid (%u) does not match expected (%u), ignoring.", - be32toh(message->xid), client->xid); - - return 0; -} + return 0; /* Ignore all parse errors. */ -static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, size_t len, const triple_timestamp *timestamp) { - DHCP_CLIENT_DONT_DESTROY(client); - int r; - - assert(client); - assert(message); - assert(timestamp); - - if (client_verify_message_header(client, message, len) < 0) - return 0; - - switch (client->state) { - case DHCP_STATE_SELECTING: - - r = client_handle_offer_or_rapid_ack(client, message, len, timestamp); - if (ERRNO_IS_NEG_RESOURCE(r)) - return r; - if (r == -EADDRNOTAVAIL) - /* got a rapid NAK, let's restart the client */ - return client_restart(client); - if (r < 0) - return 0; /* invalid message, let's ignore it */ + switch (r) { - if (client->lease->rapid_commit) - /* got a successful rapid commit */ - return client_enter_bound(client, r); + case DHCP_OFFER: + dhcp_lease_set_timestamp(lease, timestamp); + unref_and_replace_new_ref(client->lease, lease, sd_dhcp_lease_ref, sd_dhcp_lease_unref); + if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0) + return 0; /* networkd refused the server, ignoring the message. */ + if (client->state == DHCP_STATE_STOPPED) + return 0; /* The notify callback stopped the client. */ return client_enter_requesting(client); - case DHCP_STATE_REBOOTING: - case DHCP_STATE_REQUESTING: - case DHCP_STATE_RENEWING: - case DHCP_STATE_REBINDING: - - r = client_handle_ack(client, message, len, timestamp); - if (ERRNO_IS_NEG_RESOURCE(r)) - return r; - if (r == -EADDRNOTAVAIL) - /* got a NAK, let's restart the client */ - return client_restart(client); - if (r < 0) - return 0; /* invalid message, let's ignore it */ - - 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); + case DHCP_ACK: + dhcp_lease_set_timestamp(lease, timestamp); + return client_enter_bound(client, lease); - case DHCP_STATE_INIT: - case DHCP_STATE_INIT_REBOOT: - log_dhcp_client(client, "Unexpectedly receive message without sending any requests, ignoring."); - return 0; + case DHCP_NAK: + return client_restart(client); default: assert_not_reached(); } - - return 0; } -static int client_receive_message_udp( - sd_event_source *s, - int fd, - uint32_t revents, - void *userdata) { - - sd_dhcp_client *client = ASSERT_PTR(userdata); - _cleanup_free_ DHCPMessage *message = NULL; - ssize_t len, buflen; - /* This needs to be initialized with zero. See #20741. - * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */ - CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL) control = {}; - struct iovec iov; - struct msghdr msg = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - }; +static int client_receive_message(sd_dhcp_client *client, int fd, bool raw) { int r; - assert(s); + assert(client); + assert(fd >= 0); - buflen = next_datagram_size_fd(fd); + ssize_t buflen = next_datagram_size_fd(fd); if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen)) return 0; if (buflen < 0) { @@ -2212,101 +1153,63 @@ static int client_receive_message_udp( return 0; } - message = malloc0(buflen); - if (!message) + _cleanup_free_ void *buf = malloc0(buflen); + if (!buf) return -ENOMEM; - iov = IOVEC_MAKE(message, buflen); - - len = recvmsg_safe(fd, &msg, MSG_DONTWAIT); - if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len)) - return 0; - if (len < 0) { - log_dhcp_client_errno(client, len, "Could not receive message from UDP socket, ignoring: %m"); - return 0; - } - - log_dhcp_client(client, "Received message from UDP socket, processing."); - r = client_handle_message(client, message, len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); - if (r < 0) - client_stop(client, r); - - return 0; -} - -static int client_receive_message_raw( - sd_event_source *s, - int fd, - uint32_t revents, - void *userdata) { - - sd_dhcp_client *client = ASSERT_PTR(userdata); - _cleanup_free_ DHCPPacket *packet = NULL; /* This needs to be initialized with zero. See #20741. * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */ CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL + CMSG_SPACE(sizeof(struct tpacket_auxdata))) control = {}; - struct iovec iov = {}; struct msghdr msg = { - .msg_iov = &iov, + .msg_iov = &IOVEC_MAKE(buf, buflen), .msg_iovlen = 1, .msg_control = &control, .msg_controllen = sizeof(control), }; - bool checksum = true; - ssize_t buflen, len; - int r; - - assert(s); - - buflen = next_datagram_size_fd(fd); - if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen)) - return 0; - if (buflen < 0) { - log_dhcp_client_errno(client, buflen, "Failed to determine datagram size to read, ignoring: %m"); - return 0; - } - - packet = malloc0(buflen); - if (!packet) - return -ENOMEM; - iov = IOVEC_MAKE(packet, buflen); - - len = recvmsg_safe(fd, &msg, 0); + ssize_t len = recvmsg_safe(fd, &msg, MSG_DONTWAIT); if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len)) return 0; if (len < 0) { - log_dhcp_client_errno(client, len, "Could not receive message from raw socket, ignoring: %m"); + log_dhcp_client_errno(client, len, + "Could not receive message from %s socket, ignoring: %m", + raw ? "RAW" : "UDP"); return 0; } - struct tpacket_auxdata *aux = CMSG_FIND_DATA(&msg, SOL_PACKET, PACKET_AUXDATA, struct tpacket_auxdata); - if (aux) - checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY); - - if (dhcp_packet_verify_headers(packet, len, checksum, client->port) < 0) - return 0; + struct iovec payload = IOVEC_MAKE(buf, len); + if (raw) { + struct tpacket_auxdata *aux = CMSG_FIND_DATA(&msg, SOL_PACKET, PACKET_AUXDATA, struct tpacket_auxdata); + bool checksum = !aux || !(aux->tp_status & TP_STATUS_CSUMNOTREADY); - len -= DHCP_IP_UDP_SIZE; + if (udp_packet_verify(&payload, client->port, checksum, &payload) < 0) + return 0; + } - log_dhcp_client(client, "Received message from RAW socket, processing."); - r = client_handle_message(client, &packet->dhcp, len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); + log_dhcp_client(client, "Received message from %s socket, processing.", raw ? "RAW" : "UDP"); + r = client_handle_message(client, &payload, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); if (r < 0) client_stop(client, r); return 0; } +int client_receive_message_udp(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + return client_receive_message(userdata, fd, /* raw= */ false); +} + +int client_receive_message_raw(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + return client_receive_message(userdata, fd, /* raw= */ true); +} + 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 +1224,9 @@ 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); + assert_return(!hw_addr_is_null(&client->bcast_addr), -EINVAL); /* If no client identifier exists, construct an RFC 4361-compliant one */ if (!sd_dhcp_client_id_is_set(&client->client_id)) { @@ -2356,125 +1255,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; - int r; - - assert(client); - - if (!client->send_release) - return 0; /* disabled */ - - if (!client->lease || client->bootp) - return 0; /* there is nothing to be released */ - - r = client_message_init(client, DHCP_RELEASE, &release, &optlen, &optoffset); - if (r < 0) - return r; - - /* Fill up release IP and MAC */ - 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) - return r; - - log_dhcp_client(client, "RELEASE"); - return 0; -} - 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 */ + if (!sd_dhcp_client_is_running(client) || !client->lease || client->bootp) + return 0; /* there is nothing to decline */ - r = client_message_init(client, DHCP_DECLINE, &release, &optlen, &optoffset); + r = dhcp_client_send_message(client, DHCP_DECLINE); 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; + /* This function is mostly called when the acquired address conflicts with another host. + * Restarting the daemon to acquire another address. */ + return client_restart(client); +} - r = dhcp_network_send_udp_socket(client->fd, - client->lease->server_address, - client->server_port, - &release->dhcp, - sizeof(DHCPMessage) + optoffset); - if (r < 0) - return r; +static int client_send_release(sd_dhcp_client *client) { + assert(client); - log_dhcp_client(client, "DECLINE"); + if (!client->send_release) + return 0; - /* This function is mostly called when the acquired address conflicts with another host. - * Restarting the daemon to acquire another address. */ - r = client_restart(client); - if (r < 0) - return r; + if (!sd_dhcp_client_is_running(client) || !client->lease || client->bootp) + return 0; /* there is nothing to release */ - return 1; /* sent and restarted. */ + return dhcp_client_send_message(client, DHCP_RELEASE); } 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"); + (void) client_send_release(client); 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) { @@ -2489,7 +1351,7 @@ int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t else { r = sd_event_default(&client->event); if (r < 0) - return 0; + return r; } client->event_priority = priority; @@ -2506,7 +1368,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; @@ -2515,10 +1377,10 @@ sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client) { int sd_dhcp_client_attach_device(sd_dhcp_client *client, sd_device *dev) { assert_return(client, -EINVAL); - return device_unref_and_replace(client->dev, dev); + return device_unref_and_replace_new_ref(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 +1388,12 @@ 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); + safe_close(client->socket_fd); + + 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); @@ -2539,18 +1403,16 @@ static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) { free(client->hostname); free(client->vendor_class_identifier); free(client->mudurl); - client->user_class = strv_free(client->user_class); - ordered_hashmap_free(client->extra_options); - ordered_hashmap_free(client->vendor_options); + iovw_done_free(&client->user_class); + tlv_unref(client->extra_options); + tlv_unref(client->vendor_options); free(client->ifname); return mfree(client); } DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_client, sd_dhcp_client, dhcp_client_free); -int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) { - const uint8_t *opts; - size_t n_opts; +int sd_dhcp_client_new(sd_dhcp_client **ret) { int r; assert_return(ret, -EINVAL); @@ -2561,28 +1423,18 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) { *client = (sd_dhcp_client) { .n_ref = 1, + .socket_fd = -EBADF, .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, + .ip_service_type = IPTOS_CLASS_CS6, /* Defaults to CS6 (Internetwork Control). */ + .socket_priority = tos_to_priority(IPTOS_CLASS_CS6), }; - /* NOTE: this could be moved to a function. */ - if (anonymize) { - n_opts = ELEMENTSOF(default_req_opts_anonymize); - opts = default_req_opts_anonymize; - } else { - n_opts = ELEMENTSOF(default_req_opts); - opts = default_req_opts; - } - for (size_t i = 0; i < n_opts; i++) { - r = sd_dhcp_client_set_request_option(client, opts[i]); + FOREACH_ELEMENT(opt, default_req_opts) { + r = sd_dhcp_client_set_request_option(client, *opt); if (r < 0) return r; } @@ -2591,17 +1443,3 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) { return 0; } - -static const char* const dhcp_state_table[_DHCP_STATE_MAX] = { - [DHCP_STATE_STOPPED] = "stopped", - [DHCP_STATE_INIT] = "initialization", - [DHCP_STATE_SELECTING] = "selecting", - [DHCP_STATE_INIT_REBOOT] = "init-reboot", - [DHCP_STATE_REBOOTING] = "rebooting", - [DHCP_STATE_REQUESTING] = "requesting", - [DHCP_STATE_BOUND] = "bound", - [DHCP_STATE_RENEWING] = "renewing", - [DHCP_STATE_REBINDING] = "rebinding", -}; - -DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_state, DHCPState); diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index efded5df01025..2799275d3052f 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -3,34 +3,21 @@ Copyright © 2013 Intel Corporation. All rights reserved. ***/ -#include #include #include "sd-dhcp-lease.h" #include "alloc-util.h" +#include "dhcp-client-internal.h" #include "dhcp-lease-internal.h" -#include "dhcp-option.h" -#include "dns-def.h" -#include "dns-domain.h" +#include "dhcp-route.h" #include "dns-resolver-internal.h" -#include "env-file.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hexdecoct.h" -#include "hostname-util.h" #include "in-addr-util.h" -#include "network-common.h" -#include "network-internal.h" -#include "parse-util.h" -#include "sort-util.h" -#include "stdio-util.h" +#include "ip-util.h" +#include "set.h" #include "string-util.h" #include "strv.h" #include "time-util.h" -#include "tmpfile-util.h" -#include "unaligned.h" void dhcp_lease_set_timestamp(sd_dhcp_lease *lease, const triple_timestamp *timestamp) { assert(lease); @@ -45,67 +32,67 @@ int sd_dhcp_lease_get_timestamp(sd_dhcp_lease *lease, clockid_t clock, uint64_t assert_return(lease, -EINVAL); assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP); assert_return(clock_supported(clock), -EOPNOTSUPP); - assert_return(ret, -EINVAL); if (!triple_timestamp_is_set(&lease->timestamp)) return -ENODATA; - *ret = triple_timestamp_by_clock(&lease->timestamp, clock); + if (ret) + *ret = triple_timestamp_by_clock(&lease->timestamp, clock); return 0; } -int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr) { +int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *ret) { assert_return(lease, -EINVAL); - assert_return(addr, -EINVAL); - if (lease->address == 0) + if (lease->address == INADDR_ANY) return -ENODATA; - addr->s_addr = lease->address; + if (ret) + ret->s_addr = lease->address; return 0; } -int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr) { +int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *ret) { assert_return(lease, -EINVAL); - assert_return(addr, -EINVAL); - if (!lease->have_broadcast) + if (lease->broadcast == INADDR_ANY) return -ENODATA; - addr->s_addr = lease->broadcast; + if (ret) + ret->s_addr = lease->broadcast; return 0; } int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint64_t *ret) { assert_return(lease, -EINVAL); - assert_return(ret, -EINVAL); if (lease->lifetime <= 0) return -ENODATA; - *ret = lease->lifetime; + if (ret) + *ret = lease->lifetime; return 0; } int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint64_t *ret) { assert_return(lease, -EINVAL); - assert_return(ret, -EINVAL); if (lease->t1 <= 0) return -ENODATA; - *ret = lease->t1; + if (ret) + *ret = lease->t1; return 0; } int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint64_t *ret) { assert_return(lease, -EINVAL); - assert_return(ret, -EINVAL); if (lease->t2 <= 0) return -ENODATA; - *ret = lease->t2; + if (ret) + *ret = lease->t2; return 0; } @@ -118,7 +105,7 @@ int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint64_t *ret) { usec_t t, timestamp; \ int r; \ \ - assert_return(ret, -EINVAL); \ + assert_return(lease, -EINVAL); \ \ r = sd_dhcp_lease_get_##name(lease, &t); \ if (r < 0) \ @@ -128,7 +115,8 @@ int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint64_t *ret) { if (r < 0) \ return r; \ \ - *ret = usec_add(t, timestamp); \ + if (ret) \ + *ret = usec_add(t, timestamp); \ return 0; \ } @@ -136,21 +124,21 @@ DEFINE_GET_TIMESTAMP(lifetime); DEFINE_GET_TIMESTAMP(t1); DEFINE_GET_TIMESTAMP(t2); -int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu) { +int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *ret) { assert_return(lease, -EINVAL); - assert_return(mtu, -EINVAL); if (lease->mtu <= 0) return -ENODATA; - *mtu = lease->mtu; + if (ret) + *ret = lease->mtu; return 0; } int sd_dhcp_lease_get_servers( sd_dhcp_lease *lease, sd_dhcp_lease_server_type_t what, - const struct in_addr **addr) { + const struct in_addr **ret) { assert_return(lease, -EINVAL); assert_return(what >= 0, -EINVAL); @@ -159,110 +147,85 @@ int sd_dhcp_lease_get_servers( if (lease->servers[what].size <= 0) return -ENODATA; - if (addr) - *addr = lease->servers[what].addr; + if (ret) + *ret = lease->servers[what].addr; return (int) lease->servers[what].size; } -int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr) { - return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_DNS, addr); +int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **ret) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_DNS, ret); } -int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr) { - return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_NTP, addr); +int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **ret) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_NTP, ret); } -int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr) { - return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SIP, addr); -} -int sd_dhcp_lease_get_pop3(sd_dhcp_lease *lease, const struct in_addr **addr) { - return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_POP3, addr); -} -int sd_dhcp_lease_get_smtp(sd_dhcp_lease *lease, const struct in_addr **addr) { - return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SMTP, addr); -} -int sd_dhcp_lease_get_lpr(sd_dhcp_lease *lease, const struct in_addr **addr) { - return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_LPR, addr); +int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **ret) { + return sd_dhcp_lease_get_servers(lease, SD_DHCP_LEASE_SIP, ret); } -int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname) { +int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **ret) { assert_return(lease, -EINVAL); - assert_return(domainname, -EINVAL); if (!lease->domainname) return -ENODATA; - *domainname = lease->domainname; - return 0; -} - -int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname) { - assert_return(lease, -EINVAL); - assert_return(hostname, -EINVAL); - - /* FQDN option (81) always takes precedence. */ - - if (lease->fqdn) - *hostname = lease->fqdn; - else if (lease->hostname) - *hostname = lease->hostname; - else - return -ENODATA; - + if (ret) + *ret = lease->domainname; return 0; } -int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path) { +int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **ret) { assert_return(lease, -EINVAL); - assert_return(root_path, -EINVAL); - if (!lease->root_path) + if (!lease->hostname) return -ENODATA; - *root_path = lease->root_path; + if (ret) + *ret = lease->hostname; return 0; } int sd_dhcp_lease_get_captive_portal(sd_dhcp_lease *lease, const char **ret) { assert_return(lease, -EINVAL); - assert_return(ret, -EINVAL); if (!lease->captive_portal) return -ENODATA; - *ret = lease->captive_portal; + if (ret) + *ret = lease->captive_portal; return 0; } -int sd_dhcp_lease_get_dnr(sd_dhcp_lease *lease, sd_dns_resolver **ret_resolvers) { +int sd_dhcp_lease_get_dnr(sd_dhcp_lease *lease, sd_dns_resolver **ret) { assert_return(lease, -EINVAL); - assert_return(ret_resolvers, -EINVAL); if (!lease->dnr) return -ENODATA; - *ret_resolvers = lease->dnr; + if (ret) + *ret = lease->dnr; return lease->n_dnr; } -int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr) { +int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **ret) { assert_return(lease, -EINVAL); - assert_return(addr, -EINVAL); if (lease->router_size <= 0) return -ENODATA; - *addr = lease->router; + if (ret) + *ret = lease->router; return (int) lease->router_size; } -int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr) { +int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *ret) { assert_return(lease, -EINVAL); - assert_return(addr, -EINVAL); - if (!lease->have_subnet_mask) + if (lease->subnet_mask == INADDR_ANY) return -ENODATA; - addr->s_addr = lease->subnet_mask; + if (ret) + ret->s_addr = lease->subnet_mask; return 0; } @@ -294,25 +257,14 @@ int sd_dhcp_lease_get_prefix(sd_dhcp_lease *lease, struct in_addr *ret_prefix, u return 0; } -int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr) { - assert_return(lease, -EINVAL); - assert_return(addr, -EINVAL); - - if (lease->server_address == 0) - return -ENODATA; - - addr->s_addr = lease->server_address; - return 0; -} - -int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr) { +int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *ret) { assert_return(lease, -EINVAL); - assert_return(addr, -EINVAL); - if (lease->next_server == 0) + if (lease->server_address == INADDR_ANY) return -ENODATA; - addr->s_addr = lease->next_server; + if (ret) + ret->s_addr = lease->server_address; return 0; } @@ -354,19 +306,16 @@ int sd_dhcp_lease_get_classless_routes(sd_dhcp_lease *lease, sd_dhcp_route ***re return dhcp_lease_get_routes(lease->classless_routes, lease->n_classless_routes, ret); } -int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains) { - size_t r; - +int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***ret) { assert_return(lease, -EINVAL); - assert_return(domains, -EINVAL); - r = strv_length(lease->search_domains); - if (r > 0) { - *domains = lease->search_domains; - return (int) r; - } + size_t n = strv_length(lease->search_domains); + if (n == 0) + return -ENODATA; - return -ENODATA; + if (ret) + *ret = lease->search_domains; + return (int) n; } int sd_dhcp_lease_get_6rd( @@ -400,44 +349,23 @@ int sd_dhcp_lease_has_6rd(sd_dhcp_lease *lease) { return lease && lease->sixrd_n_br_addresses > 0; } -int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len) { - assert_return(lease, -EINVAL); - assert_return(data, -EINVAL); - assert_return(data_len, -EINVAL); - - if (lease->vendor_specific_len <= 0) - return -ENODATA; - - *data = lease->vendor_specific; - *data_len = lease->vendor_specific_len; - return 0; -} - -static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { - struct sd_dhcp_raw_option *option; - +static sd_dhcp_lease* dhcp_lease_free(sd_dhcp_lease *lease) { assert(lease); - while ((option = LIST_POP(options, lease->private_options))) { - free(option->data); - free(option); - } + sd_dhcp_message_unref(lease->message); - free(lease->root_path); free(lease->router); free(lease->timezone); free(lease->hostname); - free(lease->fqdn); free(lease->domainname); free(lease->captive_portal); 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); strv_free(lease->search_domains); free(lease->sixrd_br_addresses); return mfree(lease); @@ -445,1391 +373,459 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_lease, sd_dhcp_lease, dhcp_lease_free); -static int lease_parse_be32_seconds(const uint8_t *option, size_t len, bool max_as_infinity, usec_t *ret) { - assert(option); - assert(ret); - - if (len != 4) - return -EINVAL; - - *ret = unaligned_be32_sec_to_usec(option, max_as_infinity); - return 0; -} +int dhcp_lease_new(sd_dhcp_lease **ret) { + sd_dhcp_lease *lease; -static int lease_parse_u16(const uint8_t *option, size_t len, uint16_t *ret, uint16_t min) { - assert(option); assert(ret); - if (len != 2) - return -EINVAL; + lease = new0(sd_dhcp_lease, 1); + if (!lease) + return -ENOMEM; - *ret = unaligned_read_be16((be16_t*) option); - if (*ret < min) - *ret = min; + lease->n_ref = 1; + *ret = lease; return 0; } -static int lease_parse_be32(const uint8_t *option, size_t len, be32_t *ret) { - assert(option); - assert(ret); +int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **ret) { + assert_return(lease, -EINVAL); - if (len != 4) - return -EINVAL; + if (!lease->timezone) + return -ENODATA; - memcpy(ret, option, 4); + if (ret) + *ret = lease->timezone; return 0; } -static int lease_parse_domain(const uint8_t *option, size_t len, char **domain) { - _cleanup_free_ char *name = NULL, *normalized = NULL; +static int dhcp_lease_new_from_message(sd_dhcp_client *client, sd_dhcp_message *message, sd_dhcp_lease **ret) { int r; - assert(option); - assert(domain); - - r = dhcp_option_parse_string(option, len, &name); - if (r < 0) - return r; - if (!name) { - *domain = mfree(*domain); - return 0; - } + assert(client); + assert(message); + assert(ret); - r = dns_name_normalize(name, 0, &normalized); + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + r = dhcp_lease_new(&lease); if (r < 0) return r; - if (is_localhost(normalized)) - return -EINVAL; - - if (dns_name_is_root(normalized)) - return -EINVAL; - - return free_and_replace(*domain, normalized); -} - -static int lease_parse_fqdn(const uint8_t *option, size_t len, char **fqdn) { - _cleanup_free_ char *name = NULL, *normalized = NULL; - int r; - - assert(option); - assert(fqdn); - - /* RFC 4702 Section 2 - * - * Byte 0: Flags (S: server should perform A RR updates, O: override existing A RR, - * E: encoding (0=ASCII, 1=Wire format), N: no server updates) - * Byte 1: RCODE1 (ignored on receipt) - * Byte 2: RCODE2 (ignored on receipt) - * Bytes 3+: Domain Name */ - - if (len <= 3) + /* acquired address: mandatory */ + if (message->header.yiaddr == INADDR_ANY) return -EBADMSG; - - size_t data_len = len - 3; - const uint8_t *data = option + 3; - - /* In practice, many servers send DNS wire format regardless of the E flag, so ignore and try wire - * format first, then fall back to ASCII if that fails. */ - r = dns_name_from_wire_format(&data, &data_len, &name); - if (r < 0) { - if (FLAGS_SET(option[0], DHCP_FQDN_FLAG_E)) - return -EBADMSG; - - /* Wire format failed, try ASCII format */ - r = dhcp_option_parse_string(option + 3, len - 3, &name); + lease->address = message->header.yiaddr; + + /* subnet mask: mandatory */ + if (dhcp_message_get_option_be32(message, SD_DHCP_OPTION_SUBNET_MASK, &lease->subnet_mask) < 0) { + /* fall back to the default subnet masks based on address class */ + struct in_addr mask; + r = in4_addr_default_subnet_mask( + &(struct in_addr) { + .s_addr = message->header.yiaddr, + }, + &mask); if (r < 0) return r; - } - - if (!name) { - *fqdn = mfree(*fqdn); - return 0; - } - - r = dns_name_normalize(name, 0, &normalized); - if (r < 0) - return r; - - if (is_localhost(normalized)) - return -EINVAL; - - if (dns_name_is_root(normalized)) - return -EINVAL; - - return free_and_replace(*fqdn, normalized); -} - -static int lease_parse_captive_portal(const uint8_t *option, size_t len, char **uri) { - _cleanup_free_ char *s = NULL; - int r; - - assert(option); - assert(uri); - r = dhcp_option_parse_string(option, len, &s); - if (r < 0) - return r; - if (s && !in_charset(s, URI_VALID)) - return -EINVAL; - - return free_and_replace(*uri, s); -} - -static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_addr **addresses, size_t *n_addresses) { - assert(option || len == 0); - assert(addresses); - assert(n_addresses); - - if (len <= 0) { - *n_addresses = 0; - *addresses = mfree(*addresses); - return 0; + lease->subnet_mask = mask.s_addr; } - if (len % 4 != 0) - return -EINVAL; - - size_t n = len / 4; - struct in_addr *a = newdup(struct in_addr, option, n); - if (!a) - return -ENOMEM; - - *n_addresses = n; - return free_and_replace(*addresses, a); -} - -static int lease_parse_sip_server(const uint8_t *option, size_t len, struct in_addr **sips, size_t *n_sips) { - assert(option || len == 0); - assert(sips); - assert(n_sips); - - if (len <= 0) - return -EINVAL; - - /* The SIP record is like the other, regular server records, but prefixed with a single "encoding" - * byte that is either 0 or 1. We only support it to be 1 for now. Let's drop it and parse it like - * the other fields */ + /* DHCP server address: mandatory */ + r = dhcp_message_get_option_be32(message, SD_DHCP_OPTION_SERVER_IDENTIFIER, &lease->server_address); + if (r < 0) { + if (!client->bootp) + return log_dhcp_client_errno(client, r, "Failed to read %s option: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_SERVER_IDENTIFIER)); - if (option[0] != 1) { /* We only support IP address encoding for now */ - *sips = mfree(*sips); - *n_sips = 0; - return 0; + /* BOOTP typically does not use Server Identifier option, but uses the siaddr field. */ + lease->server_address = message->header.siaddr; } - return lease_parse_in_addrs(option + 1, len - 1, sips, n_sips); -} - -static int lease_parse_dns_name(const uint8_t *optval, size_t optlen, char **ret) { - _cleanup_free_ char *name = NULL; - int r; - - assert(optval); - assert(ret); - - r = dns_name_from_wire_format(&optval, &optlen, &name); - if (r < 0) - return r; - if (r == 0 || optlen != 0) - return -EBADMSG; + /* lifetime: mandatory */ + if (client->bootp) + lease->lifetime = USEC_INFINITY; /* BOOTP does not support lifetime. */ + else { + r = dhcp_message_get_option_sec(message, SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, /* max_as_infinity= */ true, &lease->lifetime); + if (r < 0 || lease->lifetime == 0) { + if (client->fallback_lease_lifetime == 0) { + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to read %s option: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME)); - *ret = TAKE_PTR(name); - return r; -} + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), + "The %s option set to 0 second.", + dhcp_option_code_to_string(SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME)); + } -static int lease_parse_dnr(const uint8_t *option, size_t len, sd_dns_resolver **dnr, size_t *n_dnr) { - int r; - sd_dns_resolver *res_list = NULL; - size_t n_resolvers = 0; - CLEANUP_ARRAY(res_list, n_resolvers, dns_resolver_done_many); - - assert(option || len == 0); - assert(dnr); - assert(n_dnr); - - _cleanup_(sd_dns_resolver_done) sd_dns_resolver res = {}; - - size_t offset = 0; - while (offset < len) { - /* Instance Data length */ - if (offset + 2 > len) - return -EBADMSG; - size_t ilen = unaligned_read_be16(option + offset); - if (offset + ilen + 2 > len) - return -EBADMSG; - offset += 2; - size_t iend = offset + ilen; - - /* priority */ - if (offset + 2 > len) - return -EBADMSG; - res.priority = unaligned_read_be16(option + offset); - offset += 2; - - /* Authenticated Domain Name */ - if (offset + 1 > len) - return -EBADMSG; - ilen = option[offset++]; - if (offset + ilen > iend) - return -EBADMSG; - - r = lease_parse_dns_name(option + offset, ilen, &res.auth_name); - if (r < 0) - return r; - r = dns_name_is_valid_ldh(res.auth_name); - if (r < 0) - return r; - if (!r) - return -EBADMSG; - if (dns_name_is_root(res.auth_name)) - return -EBADMSG; - offset += ilen; - - /* RFC9463 § 3.1.6: In ADN-only mode, server omits everything after the ADN. - * We don't support these, but they are not invalid. */ - if (offset == iend) { - log_debug("Received ADN-only DNRv4 option, ignoring."); - sd_dns_resolver_done(&res); - continue; + lease->lifetime = client->fallback_lease_lifetime; } - /* IPv4 addrs */ - if (offset + 1 > len) - return -EBADMSG; - ilen = option[offset++]; - if (offset + ilen > iend) - return -EBADMSG; - - size_t n_addrs; - _cleanup_free_ struct in_addr *addrs = NULL; - r = lease_parse_in_addrs(option + offset, ilen, &addrs, &n_addrs); - if (r < 0) - return r; - offset += ilen; - - /* RFC9463 § 3.1.8: option MUST include at least one valid IP addr */ - if (!n_addrs) - return -EBADMSG; - - res.addrs = new(union in_addr_union, n_addrs); - if (!res.addrs) - return -ENOMEM; - for (size_t i = 0; i < n_addrs; i++) { - union in_addr_union addr = {.in = addrs[i]}; - /* RFC9463 § 5.2 client MUST discard multicast and host loopback addresses */ - if (in_addr_is_multicast(AF_INET, &addr) || - in_addr_is_localhost(AF_INET, &addr)) - return -EBADMSG; - res.addrs[i] = addr; + /* There is nothing mentioned about the valid range of the lifetime in RFC, but if it is too + * short, then the network connection easily become unstable. Let's bump to 30 seconds in + * that case. + * TODO: filter short lifetime in selecting state. */ + if (lease->lifetime <= 30 * USEC_PER_SEC) { + log_dhcp_client(client, "The %s option is too short (%s), bumping lease lifetime to 30 seconds.", + dhcp_option_code_to_string(SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME), + FORMAT_TIMESPAN(lease->lifetime, USEC_PER_SEC)); + lease->lifetime = 30 * USEC_PER_SEC; } - res.n_addrs = n_addrs; - res.family = AF_INET; - /* service params */ - r = dnr_parse_svc_params(option + offset, iend-offset, &res); - if (r < 0) - return r; - if (r == 0) { - /* We can't use this record, but it was not invalid. */ - log_debug("Received DNRv4 option with unsupported SvcParams, ignoring."); - sd_dns_resolver_done(&res); - continue; + if (lease->lifetime != USEC_INFINITY) { + /* T2 */ + r = dhcp_message_get_option_sec(message, SD_DHCP_OPTION_REBINDING_TIME, /* max_as_infinity= */ true, &lease->t2); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to read %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_REBINDING_TIME)); + + /* verify that 0 < t2 < lifetime */ + if (lease->t2 <= 0 || lease->t2 >= lease->lifetime) + /* RFC2131 section 4.4.5: T2 defaults to (0.875 * duration_of_lease). */ + lease->t2 = lease->lifetime * 7 / 8; + + /* T1 */ + r = dhcp_message_get_option_sec(message, SD_DHCP_OPTION_RENEWAL_TIME, /* max_as_infinity= */ true, &lease->t1); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to read %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_RENEWAL_TIME)); + + /* verify that 0 < t1 < t2 */ + if (lease->t1 <= 0 || lease->t1 >= lease->t2) + /* RFC2131 section 4.4.5: T1 defaults to (0.5 * duration_of_lease). */ + lease->t1 = lease->lifetime / 2; + + /* For the case when T2 is too small compared with lifetime. */ + if (lease->t1 >= lease->t2) + /* RFC2131 section 4.4.5: T2 defaults to (0.875 * duration_of_lease). */ + lease->t2 = lease->lifetime * 7 / 8; + + assert(lease->t1 > 0); + assert(lease->t1 < lease->t2); + assert(lease->t2 < lease->lifetime); } - offset = iend; - - /* Append the latest resolver */ - if (!GREEDY_REALLOC0(res_list, n_resolvers+1)) - return -ENOMEM; - - res_list[n_resolvers++] = TAKE_STRUCT(res); } - typesafe_qsort(res_list, n_resolvers, dns_resolver_prio_compare); - - dns_resolver_done_many(*dnr, *n_dnr); - *dnr = TAKE_PTR(res_list); - *n_dnr = n_resolvers; - - return n_resolvers; -} - -static int lease_parse_static_routes(sd_dhcp_lease *lease, const uint8_t *option, size_t len) { - int r; - - assert(lease); - assert(option || len <= 0); - - if (len % 8 != 0) - return -EINVAL; - - while (len >= 8) { - struct in_addr dst, gw; - uint8_t prefixlen; - - assert_se(lease_parse_be32(option, 4, &dst.s_addr) >= 0); - option += 4; - - assert_se(lease_parse_be32(option, 4, &gw.s_addr) >= 0); - option += 4; - - len -= 8; - - r = in4_addr_default_prefixlen(&dst, &prefixlen); - if (r < 0) { - log_debug("sd-dhcp-lease: cannot determine class of received static route, ignoring."); - continue; - } - - (void) in4_addr_mask(&dst, prefixlen); + r = dhcp_message_get_option_be32(message, SD_DHCP_OPTION_BROADCAST, &lease->broadcast); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_BROADCAST)); + + r = dhcp_message_get_option_addresses( + message, + SD_DHCP_OPTION_ROUTER, + &lease->router_size, + &lease->router); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_ROUTER)); + + r = dhcp_message_get_option_addresses( + message, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, + &lease->servers[SD_DHCP_LEASE_DNS].size, + &lease->servers[SD_DHCP_LEASE_DNS].addr); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_DOMAIN_NAME_SERVER)); + + r = dhcp_message_get_option_addresses( + message, + SD_DHCP_OPTION_NTP_SERVER, + &lease->servers[SD_DHCP_LEASE_NTP].size, + &lease->servers[SD_DHCP_LEASE_NTP].addr); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_NTP_SERVER)); + + r = dhcp_message_get_option_addresses( + message, + SD_DHCP_OPTION_SIP_SERVER, + &lease->servers[SD_DHCP_LEASE_SIP].size, + &lease->servers[SD_DHCP_LEASE_SIP].addr); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_SIP_SERVER)); + + r = dhcp_message_get_option_routes( + message, + SD_DHCP_OPTION_STATIC_ROUTE, + &lease->n_static_routes, + &lease->static_routes); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_STATIC_ROUTE)); + + r = dhcp_message_get_option_routes( + message, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, + &lease->n_classless_routes, + &lease->classless_routes); + if (r < 0) { + if (r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE)); - if (!GREEDY_REALLOC(lease->static_routes, lease->n_static_routes + 1)) - return -ENOMEM; + r = dhcp_message_get_option_routes( + message, + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, + &lease->n_classless_routes, + &lease->classless_routes); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE)); + } + + r = dhcp_message_get_option_6rd( + message, + &lease->sixrd_ipv4masklen, + &lease->sixrd_prefixlen, + &lease->sixrd_prefix, + &lease->sixrd_n_br_addresses, + &lease->sixrd_br_addresses); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_6RD)); + + r = dhcp_message_get_option_dns_name(message, SD_DHCP_OPTION_DOMAIN_NAME, &lease->domainname); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_DOMAIN_NAME)); + + r = dhcp_message_get_option_hostname(message, &lease->hostname); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s and/or %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_FQDN), + dhcp_option_code_to_string(SD_DHCP_OPTION_HOST_NAME)); + + r = dhcp_message_get_option_domains(message, SD_DHCP_OPTION_DOMAIN_SEARCH, &lease->search_domains); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_DOMAIN_SEARCH)); + + r = dhcp_message_get_option_dnr(message, &lease->n_dnr, &lease->dnr); + if (r < 0 && r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_V4_DNR)); + + _cleanup_free_ char *captive_portal = NULL; + r = dhcp_message_get_option_string(message, SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL, &captive_portal); + if (r >= 0) { + if (!in_charset(captive_portal, URI_VALID)) + log_dhcp_client(client, "Received invalid %s, ignoring: %s", + dhcp_option_code_to_string(SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL), + captive_portal); + else + lease->captive_portal = TAKE_PTR(captive_portal); + } else if (r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL)); - lease->static_routes[lease->n_static_routes++] = (struct sd_dhcp_route) { - .dst_addr = dst, - .gw_addr = gw, - .dst_prefixlen = prefixlen, - }; - } + _cleanup_free_ char *tz = NULL; + r = dhcp_message_get_option_string(message, SD_DHCP_OPTION_TZDB_TIMEZONE, &tz); + if (r >= 0) { + if (!timezone_is_valid(tz, LOG_DEBUG)) + log_dhcp_client(client, "Received invalid %s, ignoring: %s", + dhcp_option_code_to_string(SD_DHCP_OPTION_TZDB_TIMEZONE), tz); + else + lease->timezone = TAKE_PTR(tz); + } else if (r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_TZDB_TIMEZONE)); + uint16_t mtu; + r = dhcp_message_get_option_u16(message, SD_DHCP_OPTION_MTU_INTERFACE, &mtu); + if (r >= 0) { + /* RFC 2132 section 5.1 permits MTU values down to 68 bytes, which corresponds to the minimum + * IPv4 datagram size defined in RFC 791. + * + * Such a small MTU is not generally usable for normal IP communication. RFC 791 and RFC 1122 + * require hosts to be able to reassemble datagrams of at least 576 bytes, which is treated + * as the minimum safe size for IPv4 interoperability. + * + * Ignore MTU values smaller than 576 bytes. */ + if (mtu < IPV4_MIN_REASSEMBLY_SIZE) + log_dhcp_client(client, "Received too small %s, ignoring: %u", + dhcp_option_code_to_string(SD_DHCP_OPTION_MTU_INTERFACE), mtu); + else + lease->mtu = mtu; + } else if (r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_MTU_INTERFACE)); + + /* RFC 8925 section 3.2 + * If the client did not include the IPv6-Only Preferred option code in the Parameter Request List in + * the DHCPDISCOVER or DHCPREQUEST message, it MUST ignore the IPv6-Only Preferred option in any + * messages received from the server. */ + if (!client->anonymize && + set_contains(client->req_opts, UINT_TO_PTR(SD_DHCP_OPTION_IPV6_ONLY_PREFERRED))) { + usec_t t; + r = dhcp_message_get_option_sec( + message, + SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, + /* max_as_infinity= */ false, + &t); + if (r >= 0) { + /* RFC 8925 section 3.4 + * MIN_V6ONLY_WAIT: The lower boundary for V6ONLY_WAIT. */ + if (t < MIN_V6ONLY_WAIT_USEC && !network_test_mode_enabled()) + lease->ipv6_only_preferred_usec = MIN_V6ONLY_WAIT_USEC; + else + lease->ipv6_only_preferred_usec = t; + } else if (r != -ENODATA) + log_dhcp_client_errno(client, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + } + + lease->message = sd_dhcp_message_ref(message); + *ret = TAKE_PTR(lease); return 0; } -/* parses RFC3442 Classless Static Route Option */ -static int lease_parse_classless_routes(sd_dhcp_lease *lease, const uint8_t *option, size_t len) { - assert(lease); - assert(option || len <= 0); - - /* option format: (subnet-mask-width significant-subnet-octets gateway-ip) */ - - while (len > 0) { - uint8_t prefixlen, dst_octets; - struct in_addr dst = {}, gw; - - prefixlen = *option; - option++; - len--; - - dst_octets = DIV_ROUND_UP(prefixlen, 8); - - /* can't have more than 4 octets in IPv4 */ - if (dst_octets > 4 || len < dst_octets) - return -EINVAL; - - memcpy(&dst, option, dst_octets); - option += dst_octets; - len -= dst_octets; - - if (len < 4) - return -EINVAL; - - assert_se(lease_parse_be32(option, 4, &gw.s_addr) >= 0); - option += 4; - len -= 4; - - if (!GREEDY_REALLOC(lease->classless_routes, lease->n_classless_routes + 1)) - return -ENOMEM; - - lease->classless_routes[lease->n_classless_routes++] = (struct sd_dhcp_route) { - .dst_addr = dst, - .gw_addr = gw, - .dst_prefixlen = prefixlen, - }; - } +static int client_parse_bootreply(sd_dhcp_client *client, sd_dhcp_message *message, sd_dhcp_lease **ret) { + int r; - return 0; -} + assert(client); + assert(message); + assert(ret); -static int lease_parse_6rd(sd_dhcp_lease *lease, const uint8_t *option, size_t len) { - uint8_t ipv4masklen, prefixlen; - struct in6_addr prefix; - _cleanup_free_ struct in_addr *br_addresses = NULL; - size_t n_br_addresses; + if (client->state != DHCP_STATE_SELECTING) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received unexpected BOOTREPLY."); - assert(lease); - assert(option); - - /* See RFC 5969 Section 7.1.1 */ - - if (lease->sixrd_n_br_addresses > 0) - /* Multiple 6rd option?? */ - return -EINVAL; - - /* option-length: The length of the DHCP option in octets (22 octets with one BR IPv4 address). */ - if (len < 2 + sizeof(struct in6_addr) + sizeof(struct in_addr) || - (len - 2 - sizeof(struct in6_addr)) % sizeof(struct in_addr) != 0) - return -EINVAL; - - /* IPv4MaskLen: The number of high-order bits that are identical across all CE IPv4 addresses - * within a given 6rd domain. This may be any value between 0 and 32. Any value - * greater than 32 is invalid. */ - ipv4masklen = option[0]; - if (ipv4masklen > 32) - return -EINVAL; - - /* 6rdPrefixLen: The IPv6 prefix length of the SP's 6rd IPv6 prefix in number of bits. For the - * purpose of bounds checking by DHCP option processing, the sum of - * (32 - IPv4MaskLen) + 6rdPrefixLen MUST be less than or equal to 128. */ - prefixlen = option[1]; - if (32 - ipv4masklen + prefixlen > 128) - return -EINVAL; - - /* 6rdPrefix: The service provider's 6rd IPv6 prefix represented as a 16-octet IPv6 address. - * The bits in the prefix after the 6rdPrefixlen number of bits are reserved and - * MUST be initialized to zero by the sender and ignored by the receiver. */ - memcpy(&prefix, option + 2, sizeof(struct in6_addr)); - (void) in6_addr_mask(&prefix, prefixlen); - - /* 6rdBRIPv4Address: One or more IPv4 addresses of the 6rd Border Relays for a given 6rd domain. */ - n_br_addresses = (len - 2 - sizeof(struct in6_addr)) / sizeof(struct in_addr); - br_addresses = newdup(struct in_addr, option + 2 + sizeof(struct in6_addr), n_br_addresses); - if (!br_addresses) - return -ENOMEM; + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + r = dhcp_lease_new_from_message(client, message, &lease); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to create BOOTP lease: %m"); - lease->sixrd_ipv4masklen = ipv4masklen; - lease->sixrd_prefixlen = prefixlen; - lease->sixrd_prefix = prefix; - lease->sixrd_br_addresses = TAKE_PTR(br_addresses); - lease->sixrd_n_br_addresses = n_br_addresses; + log_dhcp_client(client, "Received BOOTREPLY from %s", IN4_ADDR_TO_STRING(&(struct in_addr) { .s_addr = lease->server_address })); - return 0; + *ret = TAKE_PTR(lease); + return DHCP_ACK; } -int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata) { - sd_dhcp_lease *lease = ASSERT_PTR(userdata); +static int client_parse_ack(sd_dhcp_client *client, sd_dhcp_message *message, sd_dhcp_lease **ret) { int r; - switch (code) { - - case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: - r = lease_parse_be32_seconds(option, len, /* max_as_infinity= */ true, &lease->lifetime); - if (r < 0) - log_debug_errno(r, "Failed to parse lease time, ignoring: %m"); - - break; - - case SD_DHCP_OPTION_SERVER_IDENTIFIER: - r = lease_parse_be32(option, len, &lease->server_address); - if (r < 0) - log_debug_errno(r, "Failed to parse server identifier, ignoring: %m"); - - break; - - case SD_DHCP_OPTION_SUBNET_MASK: - r = lease_parse_be32(option, len, &lease->subnet_mask); - if (r < 0) - log_debug_errno(r, "Failed to parse subnet mask, ignoring: %m"); - else - lease->have_subnet_mask = true; - break; - - case SD_DHCP_OPTION_BROADCAST: - r = lease_parse_be32(option, len, &lease->broadcast); - if (r < 0) - log_debug_errno(r, "Failed to parse broadcast address, ignoring: %m"); - else - lease->have_broadcast = true; - break; - - case SD_DHCP_OPTION_ROUTER: - r = lease_parse_in_addrs(option, len, &lease->router, &lease->router_size); - if (r < 0) - log_debug_errno(r, "Failed to parse router addresses, ignoring: %m"); - break; - - case SD_DHCP_OPTION_RAPID_COMMIT: - if (len > 0) - log_debug("Invalid DHCP Rapid Commit option, ignoring."); - lease->rapid_commit = true; - break; - - case SD_DHCP_OPTION_DOMAIN_NAME_SERVER: - r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_DNS].addr, &lease->servers[SD_DHCP_LEASE_DNS].size); - if (r < 0) - log_debug_errno(r, "Failed to parse DNS server, ignoring: %m"); - break; - - case SD_DHCP_OPTION_NTP_SERVER: - r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_NTP].addr, &lease->servers[SD_DHCP_LEASE_NTP].size); - if (r < 0) - log_debug_errno(r, "Failed to parse NTP server, ignoring: %m"); - break; + assert(client); + assert(message); + assert(ret); - case SD_DHCP_OPTION_SIP_SERVER: - r = lease_parse_sip_server(option, len, &lease->servers[SD_DHCP_LEASE_SIP].addr, &lease->servers[SD_DHCP_LEASE_SIP].size); - if (r < 0) - log_debug_errno(r, "Failed to parse SIP server, ignoring: %m"); - break; + switch (client->state) { + case DHCP_STATE_SELECTING: + if (!client->rapid_commit) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received unexpected DHCPACK."); - case SD_DHCP_OPTION_POP3_SERVER: - r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_POP3].addr, &lease->servers[SD_DHCP_LEASE_POP3].size); + r = dhcp_message_get_option_flag(message, SD_DHCP_OPTION_RAPID_COMMIT); if (r < 0) - log_debug_errno(r, "Failed to parse POP3 server, ignoring: %m"); - break; + return log_dhcp_client_errno(client, r, "Failed to get Rapid Commit option: %m"); - case SD_DHCP_OPTION_SMTP_SERVER: - r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_SMTP].addr, &lease->servers[SD_DHCP_LEASE_SMTP].size); - if (r < 0) - log_debug_errno(r, "Failed to parse SMTP server, ignoring: %m"); break; - - case SD_DHCP_OPTION_LPR_SERVER: - r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_LPR].addr, &lease->servers[SD_DHCP_LEASE_LPR].size); - if (r < 0) - log_debug_errno(r, "Failed to parse LPR server, ignoring: %m"); + case DHCP_STATE_REBOOTING: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: break; + default: + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received unexpected DHCPACK."); + } - case SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL: - r = lease_parse_captive_portal(option, len, &lease->captive_portal); - if (r < 0) - log_debug_errno(r, "Failed to parse captive portal, ignoring: %m"); - break; + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + r = dhcp_lease_new_from_message(client, message, &lease); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to create DHCP lease: %m"); - case SD_DHCP_OPTION_STATIC_ROUTE: - r = lease_parse_static_routes(lease, option, len); - if (r < 0) - log_debug_errno(r, "Failed to parse static routes, ignoring: %m"); - break; + log_dhcp_client(client, "Received DHCPACK from %s", IN4_ADDR_TO_STRING(&(struct in_addr) { .s_addr = lease->server_address })); - case SD_DHCP_OPTION_MTU_INTERFACE: - r = lease_parse_u16(option, len, &lease->mtu, 68); - if (r < 0) - log_debug_errno(r, "Failed to parse MTU, ignoring: %m"); - if (lease->mtu < DHCP_MIN_PACKET_SIZE) { - log_debug("MTU value of %" PRIu16 " too small. Using default MTU value of %d instead.", lease->mtu, DHCP_MIN_PACKET_SIZE); - lease->mtu = DHCP_MIN_PACKET_SIZE; - } + *ret = TAKE_PTR(lease); + return DHCP_ACK; +} - break; +static int client_parse_offer(sd_dhcp_client *client, sd_dhcp_message *message, sd_dhcp_lease **ret) { + int r; - case SD_DHCP_OPTION_DOMAIN_NAME: - r = lease_parse_domain(option, len, &lease->domainname); - if (r < 0) { - log_debug_errno(r, "Failed to parse domain name, ignoring: %m"); - return 0; - } + assert(client); + assert(message); + assert(ret); - break; + if (client->state != DHCP_STATE_SELECTING) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received unexpected DHCPOFFER."); - case SD_DHCP_OPTION_DOMAIN_SEARCH: - r = dhcp_lease_parse_search_domains(option, len, &lease->search_domains); - if (r < 0) - log_debug_errno(r, "Failed to parse Domain Search List, ignoring: %m"); - break; + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + r = dhcp_lease_new_from_message(client, message, &lease); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to create DHCP lease: %m"); - case SD_DHCP_OPTION_HOST_NAME: - r = lease_parse_domain(option, len, &lease->hostname); - if (r < 0) { - log_debug_errno(r, "Failed to parse hostname, ignoring: %m"); - return 0; - } + log_dhcp_client(client, "Received DHCPOFFER from %s", IN4_ADDR_TO_STRING(&(struct in_addr) { .s_addr = lease->server_address })); - break; + *ret = TAKE_PTR(lease); + return DHCP_OFFER; +} - case SD_DHCP_OPTION_FQDN: - r = lease_parse_fqdn(option, len, &lease->fqdn); - if (r < 0) { - log_debug_errno(r, "Failed to parse FQDN, ignoring: %m"); - return 0; - } +static int client_parse_nak(sd_dhcp_client *client, sd_dhcp_message *message, sd_dhcp_lease **ret) { + int r; - break; + assert(client); + assert(message); + assert(ret); - case SD_DHCP_OPTION_ROOT_PATH: { - _cleanup_free_ char *p = NULL; + /* DHCPNAK is a valid reply when we sent DHCPREQUEST. When we receive it after sending + * DHCPDISCOVER (or even we sent nothing), we should ignore the message. */ + if (!IN_SET(client->state, DHCP_STATE_REBOOTING, DHCP_STATE_REQUESTING, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING)) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received unexpected DHCPNAK."); - r = dhcp_option_parse_string(option, len, &p); - if (r < 0) - log_debug_errno(r, "Failed to parse root path, ignoring: %m"); + /* Always ignore DHCPNAK without Server Identifier option. */ + struct in_addr a; + r = dhcp_message_get_option_address(message, SD_DHCP_OPTION_SERVER_IDENTIFIER, &a); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to read Server Identifier option in DHCPNAK: %m"); - free_and_replace(lease->root_path, p); - break; - } - case SD_DHCP_OPTION_RENEWAL_TIME: - r = lease_parse_be32_seconds(option, len, /* max_as_infinity= */ true, &lease->t1); - if (r < 0) - log_debug_errno(r, "Failed to parse T1 time, ignoring: %m"); - break; + if (client->lease && client->lease->server_address != a.s_addr) + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), + "Received DHCPNAK from unexpected server (%s).", + IN4_ADDR_TO_STRING(&a)); - case SD_DHCP_OPTION_REBINDING_TIME: - r = lease_parse_be32_seconds(option, len, /* max_as_infinity= */ true, &lease->t2); - if (r < 0) - log_debug_errno(r, "Failed to parse T2 time, ignoring: %m"); - break; + _cleanup_free_ char *e = NULL; + (void) dhcp_message_get_option_string(message, SD_DHCP_OPTION_ERROR_MESSAGE, &e); + log_dhcp_client(client, "Received DHCPNAK: %s", strna(e)); - case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: - r = lease_parse_classless_routes(lease, option, len); - if (r < 0) - log_debug_errno(r, "Failed to parse classless routes, ignoring: %m"); - break; + *ret = NULL; + return DHCP_NAK; +} - case SD_DHCP_OPTION_TZDB_TIMEZONE: { - _cleanup_free_ char *tz = NULL; +int dhcp_client_parse_message(sd_dhcp_client *client, const struct iovec *iov, sd_dhcp_lease **ret) { + int r; - r = dhcp_option_parse_string(option, len, &tz); - if (r < 0) { - log_debug_errno(r, "Failed to parse timezone option, ignoring: %m"); - return 0; - } + assert(client); + assert(iov); + assert(ret); - if (!timezone_is_valid(tz, LOG_DEBUG)) { - log_debug("Timezone is not valid, ignoring."); - return 0; - } + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_parse( + iov, + BOOTREPLY, + &client->xid, + client->arp_type, + &client->hw_addr, + &message); + if (r < 0) + return r; - free_and_replace(lease->timezone, tz); + if (client->bootp) + return client_parse_bootreply(client, message, ret); - break; + uint8_t type; + r = dhcp_message_get_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, &type); + if (r < 0) + return log_dhcp_client_errno(client, r, "Failed to read Message Type option: %m"); + + switch (type) { + case DHCP_OFFER: + return client_parse_offer(client, message, ret); + case DHCP_ACK: + return client_parse_ack(client, message, ret); + case DHCP_NAK: + return client_parse_nak(client, message, ret); + default: + return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(EBADMSG), "Received message with unexpected type (%u).", type); } - - case SD_DHCP_OPTION_V4_DNR: - r = lease_parse_dnr(option, len, &lease->dnr, &lease->n_dnr); - if (r < 0) { - log_debug_errno(r, "Failed to parse network-designated resolvers, ignoring: %m"); - return 0; - } - - break; - - case SD_DHCP_OPTION_VENDOR_SPECIFIC: - - if (len <= 0) - lease->vendor_specific = mfree(lease->vendor_specific); - else { - void *p; - - p = memdup(option, len); - if (!p) - return -ENOMEM; - - free_and_replace(lease->vendor_specific, p); - } - - lease->vendor_specific_len = len; - break; - - case SD_DHCP_OPTION_6RD: - r = lease_parse_6rd(lease, option, len); - if (r < 0) - log_debug_errno(r, "Failed to parse 6rd option, ignoring: %m"); - break; - - case SD_DHCP_OPTION_IPV6_ONLY_PREFERRED: - r = lease_parse_be32_seconds(option, len, /* max_as_infinity= */ false, &lease->ipv6_only_preferred_usec); - if (r < 0) - log_debug_errno(r, "Failed to parse IPv6 only preferred option, ignoring: %m"); - - else if (lease->ipv6_only_preferred_usec < MIN_V6ONLY_WAIT_USEC && - !network_test_mode_enabled()) - lease->ipv6_only_preferred_usec = MIN_V6ONLY_WAIT_USEC; - break; - - case SD_DHCP_OPTION_PRIVATE_BASE ... SD_DHCP_OPTION_PRIVATE_LAST: - r = dhcp_lease_insert_private_option(lease, code, option, len); - if (r < 0) - return r; - - break; - - default: - log_debug("Ignoring DHCP option %"PRIu8" while parsing.", code); - } - - return 0; -} - -/* Parses compressed domain names. */ -int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains) { - _cleanup_strv_free_ char **names = NULL; - size_t pos = 0, cnt = 0; - int r; - - assert(domains); - assert(option || len == 0); - - if (len == 0) - return -EBADMSG; - - while (pos < len) { - _cleanup_free_ char *name = NULL; - size_t n = 0; - size_t jump_barrier = pos, next_chunk = 0; - bool first = true; - - for (;;) { - uint8_t c; - c = option[pos++]; - - if (c == 0) { - /* End of name */ - break; - } else if (c <= 63) { - const char *label; - - /* Literal label */ - label = (const char*) (option + pos); - pos += c; - if (pos >= len) - return -EBADMSG; - - if (!GREEDY_REALLOC(name, n + !first + DNS_LABEL_ESCAPED_MAX)) - return -ENOMEM; - - if (first) - first = false; - else - name[n++] = '.'; - - r = dns_label_escape(label, c, name + n, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - n += r; - } else if (FLAGS_SET(c, 0xc0)) { - /* Pointer */ - - uint8_t d; - uint16_t ptr; - - if (pos >= len) - return -EBADMSG; - - d = option[pos++]; - ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; - - /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ - if (ptr >= jump_barrier) - return -EBADMSG; - jump_barrier = ptr; - - /* Save current location so we don't end up re-parsing what's parsed so far. */ - if (next_chunk == 0) - next_chunk = pos; - - pos = ptr; - } else - return -EBADMSG; - } - - if (!GREEDY_REALLOC(name, n + 1)) - return -ENOMEM; - name[n] = 0; - - r = strv_extend(&names, name); - if (r < 0) - return r; - - cnt++; - - if (next_chunk != 0) - pos = next_chunk; - } - - strv_free_and_replace(*domains, names); - - return cnt; -} - -int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len) { - struct sd_dhcp_raw_option *option, *before = NULL; - - assert(lease); - - LIST_FOREACH(options, cur, lease->private_options) { - if (tag < cur->tag) { - before = cur; - break; - } - if (tag == cur->tag) { - log_debug("Ignoring duplicate option, tagged %i.", tag); - return 0; - } - } - - option = new(struct sd_dhcp_raw_option, 1); - if (!option) - return -ENOMEM; - - option->tag = tag; - option->length = len; - option->data = memdup(data, len); - if (!option->data) { - free(option); - return -ENOMEM; - } - - LIST_INSERT_BEFORE(options, lease->private_options, before, option); - return 0; -} - -int dhcp_lease_new(sd_dhcp_lease **ret) { - sd_dhcp_lease *lease; - - lease = new0(sd_dhcp_lease, 1); - if (!lease) - return -ENOMEM; - - lease->n_ref = 1; - - *ret = lease; - return 0; -} - -int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { - _cleanup_(unlink_and_freep) char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - struct in_addr address; - const struct in_addr *addresses; - const void *data; - size_t data_len; - const char *string; - uint16_t mtu; - _cleanup_free_ sd_dhcp_route **routes = NULL; - char **search_domains; - usec_t t; - int r; - - assert(lease); - assert(lease_file); - - r = fopen_temporary(lease_file, &f, &temp_path); - if (r < 0) - return r; - - (void) fchmod(fileno(f), 0644); - - fprintf(f, - "# This is private data. Do not parse.\n"); - - r = sd_dhcp_lease_get_address(lease, &address); - if (r >= 0) - fprintf(f, "ADDRESS=%s\n", IN4_ADDR_TO_STRING(&address)); - - r = sd_dhcp_lease_get_netmask(lease, &address); - if (r >= 0) - fprintf(f, "NETMASK=%s\n", IN4_ADDR_TO_STRING(&address)); - - r = sd_dhcp_lease_get_router(lease, &addresses); - if (r > 0) { - fputs("ROUTER=", f); - serialize_in_addrs(f, addresses, r, NULL, NULL); - fputc('\n', f); - } - - r = sd_dhcp_lease_get_server_identifier(lease, &address); - if (r >= 0) - fprintf(f, "SERVER_ADDRESS=%s\n", IN4_ADDR_TO_STRING(&address)); - - r = sd_dhcp_lease_get_next_server(lease, &address); - if (r >= 0) - fprintf(f, "NEXT_SERVER=%s\n", IN4_ADDR_TO_STRING(&address)); - - r = sd_dhcp_lease_get_broadcast(lease, &address); - if (r >= 0) - fprintf(f, "BROADCAST=%s\n", IN4_ADDR_TO_STRING(&address)); - - r = sd_dhcp_lease_get_mtu(lease, &mtu); - if (r >= 0) - fprintf(f, "MTU=%" PRIu16 "\n", mtu); - - r = sd_dhcp_lease_get_t1(lease, &t); - if (r >= 0) - fprintf(f, "T1=%s\n", FORMAT_TIMESPAN(t, USEC_PER_SEC)); - - r = sd_dhcp_lease_get_t2(lease, &t); - if (r >= 0) - fprintf(f, "T2=%s\n", FORMAT_TIMESPAN(t, USEC_PER_SEC)); - - r = sd_dhcp_lease_get_lifetime(lease, &t); - if (r >= 0) - fprintf(f, "LIFETIME=%s\n", FORMAT_TIMESPAN(t, USEC_PER_SEC)); - - r = sd_dhcp_lease_get_dns(lease, &addresses); - if (r > 0) { - fputs("DNS=", f); - serialize_in_addrs(f, addresses, r, NULL, NULL); - fputc('\n', f); - } - - sd_dns_resolver *resolvers; - r = sd_dhcp_lease_get_dnr(lease, &resolvers); - if (r > 0) { - fputs("DNR=", f); - serialize_dnr(f, resolvers, r, NULL); - fputc('\n', f); - } - - r = sd_dhcp_lease_get_ntp(lease, &addresses); - if (r > 0) { - fputs("NTP=", f); - serialize_in_addrs(f, addresses, r, NULL, NULL); - fputc('\n', f); - } - - r = sd_dhcp_lease_get_sip(lease, &addresses); - if (r > 0) { - fputs("SIP=", f); - serialize_in_addrs(f, addresses, r, NULL, NULL); - fputc('\n', f); - } - - r = sd_dhcp_lease_get_domainname(lease, &string); - if (r >= 0) - fprintf(f, "DOMAINNAME=%s\n", string); - - r = sd_dhcp_lease_get_search_domains(lease, &search_domains); - if (r > 0) { - fputs("DOMAIN_SEARCH_LIST=", f); - fputstrv(f, search_domains, NULL, NULL); - fputc('\n', f); - } - - r = sd_dhcp_lease_get_hostname(lease, &string); - if (r >= 0) - fprintf(f, "HOSTNAME=%s\n", string); - - r = sd_dhcp_lease_get_root_path(lease, &string); - if (r >= 0) - fprintf(f, "ROOT_PATH=%s\n", string); - - r = sd_dhcp_lease_get_static_routes(lease, &routes); - if (r > 0) - serialize_dhcp_routes(f, "STATIC_ROUTES", routes, r); - - routes = mfree(routes); - r = sd_dhcp_lease_get_classless_routes(lease, &routes); - if (r > 0) - serialize_dhcp_routes(f, "CLASSLESS_ROUTES", routes, r); - - r = sd_dhcp_lease_get_timezone(lease, &string); - if (r >= 0) - fprintf(f, "TIMEZONE=%s\n", string); - - if (sd_dhcp_client_id_is_set(&lease->client_id)) { - _cleanup_free_ char *client_id_hex = NULL; - - client_id_hex = hexmem(lease->client_id.raw, lease->client_id.size); - if (!client_id_hex) - return -ENOMEM; - fprintf(f, "CLIENTID=%s\n", client_id_hex); - } - - r = sd_dhcp_lease_get_vendor_specific(lease, &data, &data_len); - if (r >= 0) { - _cleanup_free_ char *option_hex = NULL; - - option_hex = hexmem(data, data_len); - if (!option_hex) - return -ENOMEM; - fprintf(f, "VENDOR_SPECIFIC=%s\n", option_hex); - } - - LIST_FOREACH(options, option, lease->private_options) { - char key[STRLEN("OPTION_000")+1]; - - xsprintf(key, "OPTION_%" PRIu8, option->tag); - r = serialize_dhcp_option(f, key, option->data, option->length); - if (r < 0) - return r; - } - - r = fflush_and_check(f); - if (r < 0) - return r; - - r = conservative_rename(temp_path, lease_file); - if (r < 0) - return r; - - temp_path = mfree(temp_path); - - return 0; -} - -static char **private_options_free(char **options) { - if (!options) - return NULL; - - free_many_charp(options, SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1); - - return mfree(options); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(char**, private_options_free); - -int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { - _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; - _cleanup_free_ char - *address = NULL, - *router = NULL, - *netmask = NULL, - *server_address = NULL, - *next_server = NULL, - *broadcast = NULL, - *dns = NULL, - *dnr = NULL, - *ntp = NULL, - *sip = NULL, - *pop3 = NULL, - *smtp = NULL, - *lpr = NULL, - *mtu = NULL, - *static_routes = NULL, - *classless_routes = NULL, - *domains = NULL, - *client_id_hex = NULL, - *vendor_specific_hex = NULL, - *lifetime = NULL, - *t1 = NULL, - *t2 = NULL; - _cleanup_(private_options_freep) char **options = NULL; - - int r, i; - - assert(lease_file); - assert(ret); - - r = dhcp_lease_new(&lease); - if (r < 0) - return r; - - options = new0(char*, SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1); - if (!options) - return -ENOMEM; - - r = parse_env_file(NULL, lease_file, - "ADDRESS", &address, - "ROUTER", &router, - "NETMASK", &netmask, - "SERVER_ADDRESS", &server_address, - "NEXT_SERVER", &next_server, - "BROADCAST", &broadcast, - "DNS", &dns, - "DNR", &dnr, - "NTP", &ntp, - "SIP", &sip, - "POP3", &pop3, - "SMTP", &smtp, - "LPR", &lpr, - "MTU", &mtu, - "DOMAINNAME", &lease->domainname, - "HOSTNAME", &lease->hostname, - "DOMAIN_SEARCH_LIST", &domains, - "ROOT_PATH", &lease->root_path, - "STATIC_ROUTES", &static_routes, - "CLASSLESS_ROUTES", &classless_routes, - "CLIENTID", &client_id_hex, - "TIMEZONE", &lease->timezone, - "VENDOR_SPECIFIC", &vendor_specific_hex, - "LIFETIME", &lifetime, - "T1", &t1, - "T2", &t2, - "OPTION_224", &options[0], - "OPTION_225", &options[1], - "OPTION_226", &options[2], - "OPTION_227", &options[3], - "OPTION_228", &options[4], - "OPTION_229", &options[5], - "OPTION_230", &options[6], - "OPTION_231", &options[7], - "OPTION_232", &options[8], - "OPTION_233", &options[9], - "OPTION_234", &options[10], - "OPTION_235", &options[11], - "OPTION_236", &options[12], - "OPTION_237", &options[13], - "OPTION_238", &options[14], - "OPTION_239", &options[15], - "OPTION_240", &options[16], - "OPTION_241", &options[17], - "OPTION_242", &options[18], - "OPTION_243", &options[19], - "OPTION_244", &options[20], - "OPTION_245", &options[21], - "OPTION_246", &options[22], - "OPTION_247", &options[23], - "OPTION_248", &options[24], - "OPTION_249", &options[25], - "OPTION_250", &options[26], - "OPTION_251", &options[27], - "OPTION_252", &options[28], - "OPTION_253", &options[29], - "OPTION_254", &options[30]); - if (r < 0) - return r; - - if (address) { - r = inet_pton(AF_INET, address, &lease->address); - if (r <= 0) - log_debug("Failed to parse address %s, ignoring.", address); - } - - if (router) { - r = deserialize_in_addrs(&lease->router, router); - if (r < 0) - log_debug_errno(r, "Failed to deserialize router addresses %s, ignoring: %m", router); - else - lease->router_size = r; - } - - if (netmask) { - r = inet_pton(AF_INET, netmask, &lease->subnet_mask); - if (r <= 0) - log_debug("Failed to parse netmask %s, ignoring.", netmask); - else - lease->have_subnet_mask = true; - } - - if (server_address) { - r = inet_pton(AF_INET, server_address, &lease->server_address); - if (r <= 0) - log_debug("Failed to parse server address %s, ignoring.", server_address); - } - - if (next_server) { - r = inet_pton(AF_INET, next_server, &lease->next_server); - if (r <= 0) - log_debug("Failed to parse next server %s, ignoring.", next_server); - } - - if (broadcast) { - r = inet_pton(AF_INET, broadcast, &lease->broadcast); - if (r <= 0) - log_debug("Failed to parse broadcast address %s, ignoring.", broadcast); - else - lease->have_broadcast = true; - } - - if (dns) { - r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_DNS].addr, dns); - if (r < 0) - log_debug_errno(r, "Failed to deserialize DNS servers %s, ignoring: %m", dns); - else - lease->servers[SD_DHCP_LEASE_DNS].size = r; - } - - if (dnr) { - r = deserialize_dnr(&lease->dnr, dnr); - if (r < 0) - log_debug_errno(r, "Failed to deserialize DNR servers %s, ignoring: %m", dnr); - else - lease->n_dnr = r; - } - - if (ntp) { - r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_NTP].addr, ntp); - if (r < 0) - log_debug_errno(r, "Failed to deserialize NTP servers %s, ignoring: %m", ntp); - else - lease->servers[SD_DHCP_LEASE_NTP].size = r; - } - - if (sip) { - r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SIP].addr, sip); - if (r < 0) - log_debug_errno(r, "Failed to deserialize SIP servers %s, ignoring: %m", sip); - else - lease->servers[SD_DHCP_LEASE_SIP].size = r; - } - - if (pop3) { - r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_POP3].addr, pop3); - if (r < 0) - log_debug_errno(r, "Failed to deserialize POP3 server %s, ignoring: %m", pop3); - else - lease->servers[SD_DHCP_LEASE_POP3].size = r; - } - - if (smtp) { - r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_SMTP].addr, smtp); - if (r < 0) - log_debug_errno(r, "Failed to deserialize SMTP server %s, ignoring: %m", smtp); - else - lease->servers[SD_DHCP_LEASE_SMTP].size = r; - } - - if (lpr) { - r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_LPR].addr, lpr); - if (r < 0) - log_debug_errno(r, "Failed to deserialize LPR server %s, ignoring: %m", lpr); - else - lease->servers[SD_DHCP_LEASE_LPR].size = r; - } - - if (mtu) { - r = safe_atou16(mtu, &lease->mtu); - if (r < 0) - log_debug_errno(r, "Failed to parse MTU %s, ignoring: %m", mtu); - } - - if (domains) { - _cleanup_strv_free_ char **a = NULL; - a = strv_split(domains, " "); - if (!a) - return -ENOMEM; - - if (!strv_isempty(a)) - lease->search_domains = TAKE_PTR(a); - } - - if (static_routes) { - r = deserialize_dhcp_routes( - &lease->static_routes, - &lease->n_static_routes, - static_routes); - if (r < 0) - log_debug_errno(r, "Failed to parse DHCP static routes %s, ignoring: %m", static_routes); - } - - if (classless_routes) { - r = deserialize_dhcp_routes( - &lease->classless_routes, - &lease->n_classless_routes, - classless_routes); - if (r < 0) - log_debug_errno(r, "Failed to parse DHCP classless routes %s, ignoring: %m", classless_routes); - } - - if (lifetime) { - r = parse_sec(lifetime, &lease->lifetime); - if (r < 0) - log_debug_errno(r, "Failed to parse lifetime %s, ignoring: %m", lifetime); - } - - if (t1) { - r = parse_sec(t1, &lease->t1); - if (r < 0) - log_debug_errno(r, "Failed to parse T1 %s, ignoring: %m", t1); - } - - if (t2) { - r = parse_sec(t2, &lease->t2); - if (r < 0) - log_debug_errno(r, "Failed to parse T2 %s, ignoring: %m", t2); - } - - if (client_id_hex) { - _cleanup_free_ void *data = NULL; - size_t data_size; - - r = unhexmem(client_id_hex, &data, &data_size); - if (r < 0) - log_debug_errno(r, "Failed to parse client ID %s, ignoring: %m", client_id_hex); - - r = sd_dhcp_client_id_set_raw(&lease->client_id, data, data_size); - if (r < 0) - log_debug_errno(r, "Failed to assign client ID, ignoring: %m"); - } - - if (vendor_specific_hex) { - r = unhexmem(vendor_specific_hex, &lease->vendor_specific, &lease->vendor_specific_len); - if (r < 0) - log_debug_errno(r, "Failed to parse vendor specific data %s, ignoring: %m", vendor_specific_hex); - } - - for (i = 0; i <= SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE; i++) { - _cleanup_free_ void *data = NULL; - size_t len; - - if (!options[i]) - continue; - - r = unhexmem(options[i], &data, &len); - if (r < 0) { - log_debug_errno(r, "Failed to parse private DHCP option %s, ignoring: %m", options[i]); - continue; - } - - r = dhcp_lease_insert_private_option(lease, SD_DHCP_OPTION_PRIVATE_BASE + i, data, len); - if (r < 0) - return r; - } - - *ret = TAKE_PTR(lease); - - return 0; -} - -int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) { - struct in_addr address, mask; - int r; - - assert(lease); - - if (lease->have_subnet_mask) - return 0; - - if (lease->address == 0) - return -ENODATA; - - address.s_addr = lease->address; - - /* fall back to the default subnet masks based on address class */ - r = in4_addr_default_subnet_mask(&address, &mask); - if (r < 0) - return r; - - lease->subnet_mask = mask.s_addr; - lease->have_subnet_mask = true; - - return 0; -} - -int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id **ret) { - assert_return(lease, -EINVAL); - assert_return(ret, -EINVAL); - - if (!sd_dhcp_client_id_is_set(&lease->client_id)) - return -ENODATA; - - *ret = &lease->client_id; - - return 0; -} - -int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id *client_id) { - assert_return(lease, -EINVAL); - - if (!sd_dhcp_client_id_is_set(client_id)) - return sd_dhcp_client_id_clear(&lease->client_id); - - lease->client_id = *client_id; - - return 0; -} - -int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **ret) { - assert_return(lease, -EINVAL); - assert_return(ret, -EINVAL); - - if (!lease->timezone) - return -ENODATA; - - *ret = lease->timezone; - return 0; -} - -int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination) { - assert_return(route, -EINVAL); - assert_return(destination, -EINVAL); - - *destination = route->dst_addr; - return 0; -} - -int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length) { - assert_return(route, -EINVAL); - assert_return(length, -EINVAL); - - *length = route->dst_prefixlen; - return 0; -} - -int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway) { - assert_return(route, -EINVAL); - assert_return(gateway, -EINVAL); - - *gateway = route->gw_addr; - return 0; } diff --git a/src/libsystemd-network/sd-dhcp-relay.c b/src/libsystemd-network/sd-dhcp-relay.c new file mode 100644 index 0000000000000..9188fd5958afe --- /dev/null +++ b/src/libsystemd-network/sd-dhcp-relay.c @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-event.h" + +#include "alloc-util.h" +#include "dhcp-protocol.h" +#include "dhcp-relay-internal.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "prioq.h" +#include "tlv-util.h" + +static sd_dhcp_relay* dhcp_relay_free(sd_dhcp_relay *relay) { + if (!relay) + return NULL; + + assert(hashmap_isempty(relay->interfaces)); + hashmap_free(relay->interfaces); + assert(hashmap_isempty(relay->downstream_interfaces)); + hashmap_free(relay->downstream_interfaces); + assert(prioq_isempty(relay->upstream_interfaces)); + prioq_free(relay->upstream_interfaces); + + sd_event_unref(relay->event); + + iovec_done(&relay->remote_id); + tlv_unref(relay->extra_options); + return mfree(relay); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_relay, sd_dhcp_relay, dhcp_relay_free); + +int sd_dhcp_relay_new(sd_dhcp_relay **ret) { + assert_return(ret, -EINVAL); + + sd_dhcp_relay *relay = new(sd_dhcp_relay, 1); + if (!relay) + return -ENOMEM; + + *relay = (sd_dhcp_relay) { + .n_ref = 1, + .server_port = DHCP_PORT_SERVER, + }; + + *ret = TAKE_PTR(relay); + return 0; +} + +int sd_dhcp_relay_is_running(sd_dhcp_relay *relay) { + if (!relay) + return false; + + return + !prioq_isempty(relay->upstream_interfaces) || + !hashmap_isempty(relay->downstream_interfaces); +} + +int sd_dhcp_relay_attach_event(sd_dhcp_relay *relay, sd_event *event, int64_t priority) { + int r; + + assert_return(relay, -EINVAL); + assert_return(!relay->event, -EBUSY); + + if (event) + relay->event = sd_event_ref(event); + else { + r = sd_event_default(&relay->event); + if (r < 0) + return r; + } + + relay->event_priority = priority; + return 0; +} + +int sd_dhcp_relay_detach_event(sd_dhcp_relay *relay) { + assert_return(relay, -EINVAL); + assert_return(!sd_dhcp_relay_is_running(relay), -EBUSY); + + relay->event = sd_event_unref(relay->event); + return 0; +} + +sd_event* sd_dhcp_relay_get_event(sd_dhcp_relay *relay) { + assert_return(relay, NULL); + + return relay->event; +} + +int sd_dhcp_relay_set_server_address(sd_dhcp_relay *relay, const struct in_addr *address) { + assert_return(relay, -EINVAL); + assert_return(!sd_dhcp_relay_is_running(relay), -EBUSY); + + if (address) + relay->server_address = *address; + else + relay->server_address = (struct in_addr) {}; + + return 0; +} + +int sd_dhcp_relay_get_server_address(sd_dhcp_relay *relay, struct in_addr *ret) { + assert_return(relay, -EINVAL); + + if (ret) + *ret = relay->server_address; + + return in4_addr_is_set(&relay->server_address); +} + +int sd_dhcp_relay_set_server_port(sd_dhcp_relay *relay, uint16_t port) { + assert_return(relay, -EINVAL); + assert_return(!sd_dhcp_relay_is_running(relay), -EBUSY); + + relay->server_port = port; + return 0; +} + +int sd_dhcp_relay_set_remote_id(sd_dhcp_relay *relay, const struct iovec *iov) { + assert_return(relay, -EINVAL); + + return iovec_done_and_memdup(&relay->remote_id, iov); +} + +int sd_dhcp_relay_set_server_identifier_override(sd_dhcp_relay *relay, int b) { + assert_return(relay, -EINVAL); + + relay->server_identifier_override = !!b; + return 0; +} + +int dhcp_relay_set_extra_options(sd_dhcp_relay *relay, TLV *options) { + assert(relay); + + return unref_and_replace_new_ref(relay->extra_options, options, tlv_ref, tlv_unref); +} diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c index 5c24de4084fb8..f170056046eda 100644 --- a/src/libsystemd-network/sd-dhcp-server-lease.c +++ b/src/libsystemd-network/sd-dhcp-server-lease.c @@ -6,7 +6,9 @@ #include "sd-event.h" #include "alloc-util.h" +#include "dhcp-server-internal.h" #include "dhcp-server-lease-internal.h" +#include "dhcp-server-request.h" #include "dns-domain.h" #include "errno-util.h" #include "fd-util.h" @@ -16,7 +18,6 @@ #include "iovec-util.h" #include "json-util.h" #include "mkdir.h" -#include "string-util.h" #include "tmpfile-util.h" static sd_dhcp_server_lease* dhcp_server_lease_free(sd_dhcp_server_lease *lease) { @@ -65,60 +66,60 @@ int dhcp_server_put_lease(sd_dhcp_server *server, sd_dhcp_server_lease *lease, b return 0; } -int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *req, usec_t expiration) { - _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL; +int dhcp_server_set_lease(sd_dhcp_server *server, DHCPRequest *req) { int r; assert(server); - assert(address != 0); assert(req); - assert(expiration != 0); + assert(req->message); + assert(req->address != INADDR_ANY); + + usec_t expiration; + r = dhcp_request_get_lifetime_timestamp(req, CLOCK_BOOTTIME, &expiration); + if (r < 0) + return r; - /* If a lease for the host already exists, update it. */ - lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id); + _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = + hashmap_get(server->bound_leases_by_client_id, &req->client_id); if (lease) { - if (lease->address != address) { + /* If a lease for the host already exists, update it. */ + if (lease->address != req->address) { hashmap_remove_value(server->bound_leases_by_address, UINT32_TO_PTR(lease->address), lease); - lease->address = address; + lease->address = req->address; r = hashmap_ensure_put(&server->bound_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease); if (r < 0) return r; } + lease->htype = req->message->header.htype; + lease->hw_addr = req->hw_addr; + lease->gateway = req->message->header.giaddr; lease->expiration = expiration; - - TAKE_PTR(lease); - return 0; - } - - /* Otherwise, add a new lease. */ - - lease = new(sd_dhcp_server_lease, 1); - if (!lease) - return -ENOMEM; - - *lease = (sd_dhcp_server_lease) { - .n_ref = 1, - .address = address, - .client_id = req->client_id, - .htype = req->message->htype, - .hlen = req->message->hlen, - .gateway = req->message->giaddr, - .expiration = expiration, - }; - - memcpy(lease->chaddr, req->message->chaddr, req->message->hlen); - - if (req->hostname) { - lease->hostname = strdup(req->hostname); - if (!lease->hostname) + } else { + /* Otherwise, add a new lease. */ + lease = new(sd_dhcp_server_lease, 1); + if (!lease) return -ENOMEM; + + *lease = (sd_dhcp_server_lease) { + .n_ref = 1, + .client_id = req->client_id, + .htype = req->message->header.htype, + .hw_addr = req->hw_addr, + .address = req->address, + .gateway = req->message->header.giaddr, + .expiration = expiration, + }; + + r = dhcp_server_put_lease(server, lease, /* is_static= */ false); + if (r < 0) + return r; } - r = dhcp_server_put_lease(server, lease, /* is_static= */ false); - if (r < 0) - return r; + char *hostname = NULL; + (void) dhcp_message_get_option_hostname(req->message, &hostname); + free_and_replace(lease->hostname, hostname); TAKE_PTR(lease); return 0; @@ -146,27 +147,17 @@ int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server) { sd_dhcp_server_lease* dhcp_server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req) { sd_dhcp_server_lease *static_lease; - sd_dhcp_client_id client_id; assert(server); assert(req); static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id); - if (static_lease) - goto verify; - - /* when no lease is found based on the client id fall back to chaddr */ - if (!client_id_data_size_is_valid(req->message->hlen)) - return NULL; - - if (sd_dhcp_client_id_set(&client_id, /* type= */ 1, req->message->chaddr, req->message->hlen) < 0) - return NULL; - - static_lease = hashmap_get(server->static_leases_by_client_id, &client_id); + if (!static_lease && sd_dhcp_client_id_is_set(&req->client_id_by_header)) + /* when no lease is found, fall back to use the fake client ID generated from the header. */ + static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id_by_header); if (!static_lease) return NULL; -verify: /* Check if the address is in the same subnet. */ if ((static_lease->address & server->netmask) != server->subnet) return NULL; @@ -237,7 +228,7 @@ int sd_dhcp_server_set_static_lease( return 0; } -static int dhcp_server_lease_append_json(sd_dhcp_server_lease *lease, sd_json_variant **ret) { +static int dhcp_server_lease_build_json(sd_dhcp_server_lease *lease, sd_json_variant **ret) { assert(lease); assert(ret); @@ -247,8 +238,8 @@ static int dhcp_server_lease_append_json(sd_dhcp_server_lease *lease, sd_json_va JSON_BUILD_PAIR_IN4_ADDR_WITH_STRING_NON_NULL("Address", &(struct in_addr) { .s_addr = lease->address }), JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", lease->hostname), SD_JSON_BUILD_PAIR_UNSIGNED("HardwareAddressType", lease->htype), - SD_JSON_BUILD_PAIR_UNSIGNED("HardwareAddressLength", lease->hlen), - SD_JSON_BUILD_PAIR_BYTE_ARRAY("HardwareAddress", lease->chaddr, sizeof(lease->chaddr))); + SD_JSON_BUILD_PAIR_UNSIGNED("HardwareAddressLength", lease->hw_addr.length), + SD_JSON_BUILD_PAIR_BYTE_ARRAY("HardwareAddress", lease->hw_addr.bytes, lease->hw_addr.length)); } int dhcp_server_bound_leases_append_json(sd_dhcp_server *server, sd_json_variant **v) { @@ -271,7 +262,7 @@ int dhcp_server_bound_leases_append_json(sd_dhcp_server *server, sd_json_variant HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; - r = dhcp_server_lease_append_json(lease, &w); + r = dhcp_server_lease_build_json(lease, &w); if (r < 0) return r; @@ -303,7 +294,7 @@ int dhcp_server_static_leases_append_json(sd_dhcp_server *server, sd_json_varian HASHMAP_FOREACH(lease, server->static_leases_by_client_id) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; - r = dhcp_server_lease_append_json(lease, &w); + r = dhcp_server_lease_build_json(lease, &w); if (r < 0) return r; @@ -376,70 +367,67 @@ int dhcp_server_save_leases(sd_dhcp_server *server) { return 0; } -static int json_dispatch_chaddr(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { - uint8_t* address = ASSERT_PTR(userdata); - _cleanup_(iovec_done) struct iovec iov = {}; - int r; - - r = json_dispatch_byte_array_iovec(name, variant, flags, &iov); - if (r < 0) - return r; +typedef struct LeaseParam { + sd_dhcp_client_id client_id; + uint8_t htype; + uint8_t hlen; + struct iovec hw_addr; + struct in_addr address; + usec_t exp_b; + usec_t exp_r; + char *hostname; +} LeaseParam; - if (iov.iov_len != 16) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name)); +static void lease_param_done(LeaseParam *p) { + assert(p); - memcpy(address, iov.iov_base, iov.iov_len); - return 0; + iovec_done(&p->hw_addr); + free(p->hostname); } static int json_dispatch_dhcp_lease(sd_dhcp_server *server, sd_json_variant *v, bool use_boottime) { - static const sd_json_dispatch_field dispatch_table_boottime[] = { - { "ClientId", SD_JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(sd_dhcp_server_lease, client_id), SD_JSON_MANDATORY }, - { "Address", SD_JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(sd_dhcp_server_lease, address), SD_JSON_MANDATORY }, - { "Hostname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(sd_dhcp_server_lease, hostname), 0 }, - { "HardwareAddressType", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, htype), 0 }, - { "HardwareAddressLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, hlen), 0 }, - { "HardwareAddress", SD_JSON_VARIANT_ARRAY, json_dispatch_chaddr, offsetof(sd_dhcp_server_lease, chaddr), 0 }, - { "ExpirationUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(sd_dhcp_server_lease, expiration), SD_JSON_MANDATORY }, - { "ExpirationRealtimeUSec", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, SD_JSON_MANDATORY }, - {} - }, dispatch_table_realtime[] = { - { "ClientId", SD_JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(sd_dhcp_server_lease, client_id), SD_JSON_MANDATORY }, - { "Address", SD_JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(sd_dhcp_server_lease, address), SD_JSON_MANDATORY }, - { "Hostname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(sd_dhcp_server_lease, hostname), 0 }, - { "HardwareAddressType", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, htype), 0 }, - { "HardwareAddressLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(sd_dhcp_server_lease, hlen), 0 }, - { "HardwareAddress", SD_JSON_VARIANT_ARRAY, json_dispatch_chaddr, offsetof(sd_dhcp_server_lease, chaddr), 0 }, - { "ExpirationUSec", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, SD_JSON_MANDATORY }, - { "ExpirationRealtimeUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(sd_dhcp_server_lease, expiration), SD_JSON_MANDATORY }, + static const sd_json_dispatch_field dispatch_table[] = { + { "ClientId", SD_JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(LeaseParam, client_id), SD_JSON_MANDATORY }, + { "HardwareAddressType", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(LeaseParam, htype), 0 }, + { "HardwareAddressLength", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint8, offsetof(LeaseParam, hlen), 0 }, + { "HardwareAddress", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(LeaseParam, hw_addr), 0 }, + { "Address", SD_JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(LeaseParam, address), SD_JSON_MANDATORY }, + { "AddressString", SD_JSON_VARIANT_STRING, NULL, 0, 0 }, + { "ExpirationUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LeaseParam, exp_b), SD_JSON_MANDATORY }, + { "ExpirationRealtimeUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LeaseParam, exp_r), SD_JSON_MANDATORY }, + { "Hostname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(LeaseParam, hostname), 0 }, {} }; - _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL; - usec_t now_b; int r; assert(server); assert(v); - lease = new(sd_dhcp_server_lease, 1); - if (!lease) - return -ENOMEM; - - *lease = (sd_dhcp_server_lease) { - .n_ref = 1, - }; - - r = sd_json_dispatch(v, use_boottime ? dispatch_table_boottime : dispatch_table_realtime, SD_JSON_ALLOW_EXTENSIONS, lease); + _cleanup_(lease_param_done) LeaseParam p = {}; + r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) return r; + if (p.hlen > HW_ADDR_MAX_SIZE) + return -EINVAL; + + if (p.hlen > p.hw_addr.iov_len) + return -EINVAL; + + if (!in4_addr_is_set(&p.address)) + return -EINVAL; + + if (!sd_dhcp_client_id_is_set(&p.client_id)) + return -EINVAL; + + usec_t now_b; r = sd_event_now(server->event, CLOCK_BOOTTIME, &now_b); if (r < 0) return r; if (use_boottime) { - if (lease->expiration < now_b) + if (p.exp_b < now_b) return 0; /* already expired */ } else { usec_t now_r; @@ -448,12 +436,29 @@ static int json_dispatch_dhcp_lease(sd_dhcp_server *server, sd_json_variant *v, if (r < 0) return r; - if (lease->expiration < now_r) + if (p.exp_r < now_r) return 0; /* already expired */ - lease->expiration = map_clock_usec_raw(lease->expiration, now_r, now_b); + p.exp_b = map_clock_usec_raw(p.exp_r, now_r, now_b); } + _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = new(sd_dhcp_server_lease, 1); + if (!lease) + return -ENOMEM; + + *lease = (sd_dhcp_server_lease) { + .n_ref = 1, + + .client_id = p.client_id, + .htype = p.htype, + .hw_addr.length = p.hlen, + .address = p.address.s_addr, + .expiration = p.exp_b, + .hostname = TAKE_PTR(p.hostname), + }; + + memcpy_safe(lease->hw_addr.bytes, p.hw_addr.iov_base, p.hlen); + r = dhcp_server_put_lease(server, lease, /* is_static= */ false); if (r == -EEXIST) return 0; @@ -490,7 +495,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..7bd363ad4bcd0 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -3,34 +3,28 @@ Copyright © 2013 Intel Corporation. All rights reserved. ***/ +#include + #include "sd-dhcp-server.h" #include "sd-event.h" -#include "sd-id128.h" #include "alloc-util.h" -#include "dhcp-network.h" -#include "dhcp-option.h" -#include "dhcp-packet.h" #include "dhcp-server-internal.h" #include "dhcp-server-lease-internal.h" +#include "dhcp-server-request.h" #include "dns-domain.h" -#include "errno-util.h" #include "fd-util.h" +#include "hashmap.h" #include "in-addr-util.h" -#include "iovec-util.h" -#include "memory-util.h" #include "network-common.h" -#include "ordered-set.h" #include "path-util.h" -#include "siphash24.h" #include "socket-util.h" #include "string-util.h" -#include "unaligned.h" #define DHCP_DEFAULT_LEASE_TIME_USEC USEC_PER_HOUR #define DHCP_MAX_LEASE_TIME_USEC (USEC_PER_HOUR*12) -static void server_on_lease_change(sd_dhcp_server *server) { +void dhcp_server_on_lease_change(sd_dhcp_server *server) { int r; assert(server); @@ -106,24 +100,17 @@ int sd_dhcp_server_configure_pool( } int sd_dhcp_server_is_running(sd_dhcp_server *server) { - if (!server) - return false; - - return !!server->receive_message; -} - -int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server) { - assert_return(server, -EINVAL); - - return in4_addr_is_set(&server->relay_target); + return server && sd_event_source_get_enabled(server->io_event_source, /* ret= */ NULL) > 0; } -static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) { +static sd_dhcp_server* dhcp_server_free(sd_dhcp_server *server) { assert(server); sd_dhcp_server_stop(server); sd_event_unref(server->event); + safe_close(server->socket_fd); + safe_close(server->raw_socket_fd); free(server->boot_server_name); free(server->boot_filename); @@ -138,11 +125,8 @@ static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) { server->static_leases_by_address = hashmap_free(server->static_leases_by_address); server->static_leases_by_client_id = hashmap_free(server->static_leases_by_client_id); - ordered_set_free(server->extra_options); - ordered_set_free(server->vendor_options); - - free(server->agent_circuit_id); - free(server->agent_remote_id); + tlv_unref(server->extra_options); + tlv_unref(server->vendor_options); safe_close(server->lease_dir_fd); free(server->lease_file); @@ -165,13 +149,12 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) { *server = (sd_dhcp_server) { .n_ref = 1, - .fd_raw = -EBADF, - .fd = -EBADF, - .fd_broadcast = -EBADF, + .ip_service_type = IPTOS_CLASS_CS6, + .socket_fd = -EBADF, + .raw_socket_fd = -EBADF, .address = htobe32(INADDR_ANY), .netmask = htobe32(INADDR_ANY), .ifindex = ifindex, - .bind_to_interface = true, .default_lease_time = DHCP_DEFAULT_LEASE_TIME_USEC, .max_lease_time = DHCP_MAX_LEASE_TIME_USEC, .rapid_commit = true, @@ -235,12 +218,20 @@ int sd_dhcp_server_detach_event(sd_dhcp_server *server) { return 0; } -sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) { +sd_event* sd_dhcp_server_get_event(sd_dhcp_server *server) { assert_return(server, NULL); return server->event; } +int sd_dhcp_server_set_ip_service_type(sd_dhcp_server *server, uint8_t type) { + assert_return(server, -EINVAL); + assert_return(!sd_dhcp_server_is_running(server), -EBUSY); + + server->ip_service_type = type; + return 0; +} + int sd_dhcp_server_set_boot_server_address(sd_dhcp_server *server, const struct in_addr *address) { assert_return(server, -EINVAL); @@ -271,26 +262,22 @@ 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); } int sd_dhcp_server_stop(sd_dhcp_server *server) { - bool running; - if (!server) return 0; - running = sd_dhcp_server_is_running(server); + bool running = sd_dhcp_server_is_running(server); - server->receive_message = sd_event_source_disable_unref(server->receive_message); - server->receive_broadcast = sd_event_source_disable_unref(server->receive_broadcast); - - server->fd_raw = safe_close(server->fd_raw); - server->fd = safe_close(server->fd); - server->fd_broadcast = safe_close(server->fd_broadcast); + server->raw_socket_fd = safe_close(server->raw_socket_fd); + server->io_event_source = sd_event_source_disable_unref(server->io_event_source); if (running) log_dhcp_server(server, "STOPPED"); @@ -298,661 +285,7 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) { return 0; } -static bool dhcp_request_contains(DHCPRequest *req, uint8_t option) { - assert(req); - - if (!req->parameter_request_list) - return false; - - return memchr(req->parameter_request_list, option, req->parameter_request_list_len); -} - -static int dhcp_server_send_unicast_raw( - sd_dhcp_server *server, - uint8_t hlen, - const uint8_t *chaddr, - DHCPPacket *packet, - size_t len) { - - union sockaddr_union link = { - .ll.sll_family = AF_PACKET, - .ll.sll_protocol = htobe16(ETH_P_IP), - .ll.sll_ifindex = server->ifindex, - .ll.sll_halen = hlen, - }; - - assert(server); - assert(server->ifindex > 0); - assert(server->address != 0); - assert(hlen > 0); - assert(chaddr); - assert(packet); - assert(len > sizeof(DHCPPacket)); - - memcpy(link.ll.sll_addr, chaddr, hlen); - - 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); - - return dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len); -} - -static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination, - uint16_t destination_port, - DHCPMessage *message, size_t len) { - union sockaddr_union dest = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(destination_port), - .in.sin_addr.s_addr = destination, - }; - struct iovec iov = { - .iov_base = message, - .iov_len = len, - }; - CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; - struct msghdr msg = { - .msg_name = &dest, - .msg_namelen = sizeof(dest.in), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - struct cmsghdr *cmsg; - struct in_pktinfo *pktinfo; - - assert(server); - assert(server->fd >= 0); - assert(message); - assert(len >= sizeof(DHCPMessage)); - - if (server->bind_to_interface) { - msg.msg_control = &control; - msg.msg_controllen = sizeof(control); - - cmsg = CMSG_FIRSTHDR(&msg); - assert(cmsg); - - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_PKTINFO; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); - - /* we attach source interface and address info to the message - rather than binding the socket. This will be mostly useful - when we gain support for arbitrary number of server addresses - */ - pktinfo = CMSG_TYPED_DATA(cmsg, struct in_pktinfo); - assert(pktinfo); - - pktinfo->ipi_ifindex = server->ifindex; - pktinfo->ipi_spec_dst.s_addr = server->address; - } - - if (sendmsg(server->fd, &msg, 0) < 0) - return -errno; - - return 0; -} - -static bool requested_broadcast(DHCPMessage *message) { - assert(message); - return message->flags & htobe16(0x8000); -} - -static int dhcp_server_send( - sd_dhcp_server *server, - uint8_t hlen, - const uint8_t *chaddr, - be32_t destination, - uint16_t destination_port, - DHCPPacket *packet, - size_t optoffset, - bool l2_broadcast) { - - if (destination != INADDR_ANY) - return dhcp_server_send_udp(server, destination, - destination_port, &packet->dhcp, - sizeof(DHCPMessage) + optoffset); - else if (l2_broadcast) - return dhcp_server_send_udp(server, INADDR_BROADCAST, - destination_port, &packet->dhcp, - sizeof(DHCPMessage) + optoffset); - else - /* we cannot send UDP packet to specific MAC address when the - address is not yet configured, so must fall back to raw - packets */ - return dhcp_server_send_unicast_raw(server, hlen, chaddr, packet, - sizeof(DHCPPacket) + optoffset); -} - -int dhcp_server_send_packet(sd_dhcp_server *server, - DHCPRequest *req, DHCPPacket *packet, - int type, size_t optoffset) { - be32_t destination = INADDR_ANY; - uint16_t destination_port = DHCP_PORT_CLIENT; - int r; - - assert(server); - assert(req); - assert(req->max_optlen > 0); - assert(req->message); - assert(optoffset <= req->max_optlen); - assert(packet); - - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, - SD_DHCP_OPTION_SERVER_IDENTIFIER, - 4, &server->address); - if (r < 0) - return r; - - if (req->agent_info_option) { - size_t opt_full_length = *(req->agent_info_option + 1) + 2; - /* there must be space left for SD_DHCP_OPTION_END */ - if (optoffset + opt_full_length < req->max_optlen) { - memcpy(packet->dhcp.options + optoffset, req->agent_info_option, opt_full_length); - optoffset += opt_full_length; - } - } - - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, - SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - /* RFC 2131 Section 4.1 - - If the ’giaddr’ field in a DHCP message from a client is non-zero, - the server sends any return messages to the ’DHCP server’ port on the - BOOTP relay agent whose address appears in ’giaddr’. If the ’giaddr’ - field is zero and the ’ciaddr’ field is nonzero, then the server - unicasts DHCPOFFER and DHCPACK messages to the address in ’ciaddr’. - If ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is - set, then the server broadcasts DHCPOFFER and DHCPACK messages to - 0xffffffff. If the broadcast bit is not set and ’giaddr’ is zero and - ’ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK - messages to the client’s hardware address and ’yiaddr’ address. In - all cases, when ’giaddr’ is zero, the server broadcasts any DHCPNAK - messages to 0xffffffff. - - Section 4.3.2 - - If ’giaddr’ is set in the DHCPREQUEST message, the client is on a - different subnet. The server MUST set the broadcast bit in the - DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the - client, because the client may not have a correct network address - or subnet mask, and the client may not be answering ARP requests. - */ - if (req->message->giaddr != 0) { - destination = req->message->giaddr; - destination_port = DHCP_PORT_SERVER; - if (type == DHCP_NAK) - packet->dhcp.flags = htobe16(0x8000); - } else if (req->message->ciaddr != 0 && type != DHCP_NAK) - destination = req->message->ciaddr; - - bool l2_broadcast = requested_broadcast(req->message) || type == DHCP_NAK; - return dhcp_server_send(server, req->message->hlen, req->message->chaddr, - destination, destination_port, packet, optoffset, l2_broadcast); -} - -static int server_message_init( - sd_dhcp_server *server, - DHCPPacket **ret, - uint8_t type, - size_t *ret_optoffset, - DHCPRequest *req) { - - _cleanup_free_ DHCPPacket *packet = NULL; - size_t optoffset = 0; - int r; - - assert(server); - assert(ret); - assert(ret_optoffset); - assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK)); - assert(req); - - packet = malloc0(sizeof(DHCPPacket) + req->max_optlen); - if (!packet) - return -ENOMEM; - - r = dhcp_message_init(&packet->dhcp, BOOTREPLY, - be32toh(req->message->xid), - req->message->htype, req->message->hlen, req->message->chaddr, - type, req->max_optlen, &optoffset); - if (r < 0) - return r; - - packet->dhcp.flags = req->message->flags; - packet->dhcp.giaddr = req->message->giaddr; - - *ret_optoffset = optoffset; - *ret = TAKE_PTR(packet); - - return 0; -} - -static int dhcp_server_append_static_hostname( - sd_dhcp_server *server, - DHCPPacket *packet, - size_t *offset, - DHCPRequest *req) { - - sd_dhcp_server_lease *static_lease; - int r; - - assert(server); - assert(packet); - assert(offset); - assert(req); - - static_lease = dhcp_server_get_static_lease(server, req); - if (!static_lease || !static_lease->hostname) - return 0; - - if (dns_name_is_single_label(static_lease->hostname)) - /* Option 12 */ - return dhcp_option_append( - &packet->dhcp, - req->max_optlen, - offset, - /* overload= */ 0, - SD_DHCP_OPTION_HOST_NAME, - strlen(static_lease->hostname), - static_lease->hostname); - - - /* Option 81 */ - uint8_t buffer[DHCP_MAX_FQDN_LENGTH + 3]; - - /* Flags: S=0 (will not update RR), O=1 (are overriding client), - * E=1 (using DNS wire format), N=1 (will not update DNS) */ - buffer[0] = DHCP_FQDN_FLAG_O | DHCP_FQDN_FLAG_E | DHCP_FQDN_FLAG_N; - - /* RFC 4702: A server SHOULD set these to 255 when sending the option and MUST ignore them on - * receipt. */ - buffer[1] = 255; - buffer[2] = 255; - - r = dns_name_to_wire_format(static_lease->hostname, buffer + 3, sizeof(buffer) - 3, false); - if (r < 0) - return log_dhcp_server_errno(server, r, "Failed to encode FQDN for static lease: %m"); - if (r > DHCP_MAX_FQDN_LENGTH) - return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EINVAL), "FQDN for static lease too long"); - - return dhcp_option_append( - &packet->dhcp, - req->max_optlen, - offset, - /* overload= */ 0, - SD_DHCP_OPTION_FQDN, - 3 + r, - buffer); -} - -static int server_send_offer_or_ack( - sd_dhcp_server *server, - DHCPRequest *req, - be32_t address, - uint8_t type) { - - static const uint8_t option_map[_SD_DHCP_LEASE_SERVER_TYPE_MAX] = { - [SD_DHCP_LEASE_DNS] = SD_DHCP_OPTION_DOMAIN_NAME_SERVER, - [SD_DHCP_LEASE_NTP] = SD_DHCP_OPTION_NTP_SERVER, - [SD_DHCP_LEASE_SIP] = SD_DHCP_OPTION_SIP_SERVER, - [SD_DHCP_LEASE_POP3] = SD_DHCP_OPTION_POP3_SERVER, - [SD_DHCP_LEASE_SMTP] = SD_DHCP_OPTION_SMTP_SERVER, - [SD_DHCP_LEASE_LPR] = SD_DHCP_OPTION_LPR_SERVER, - }; - - _cleanup_free_ DHCPPacket *packet = NULL; - sd_dhcp_option *j; - be32_t lease_time; - size_t offset; - int r; - - assert(server); - assert(req); - assert(IN_SET(type, DHCP_OFFER, DHCP_ACK)); - - r = server_message_init(server, &packet, type, &offset, req); - if (r < 0) - return r; - - packet->dhcp.yiaddr = address; - packet->dhcp.siaddr = server->boot_server_address.s_addr; - - lease_time = usec_to_be32_sec(req->lifetime); - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, - &lease_time); - if (r < 0) - return r; - - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); - if (r < 0) - return r; - - if (server->emit_router) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_ROUTER, 4, - in4_addr_is_set(&server->router_address) ? - &server->router_address.s_addr : - &server->address); - if (r < 0) - return r; - } - - if (server->boot_server_name) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_BOOT_SERVER_NAME, - strlen(server->boot_server_name), server->boot_server_name); - if (r < 0) - return r; - } - - if (server->boot_filename) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_BOOT_FILENAME, - strlen(server->boot_filename), server->boot_filename); - if (r < 0) - return r; - } - - for (sd_dhcp_lease_server_type_t k = 0; k < _SD_DHCP_LEASE_SERVER_TYPE_MAX; k++) { - if (server->servers[k].size <= 0) - continue; - - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - option_map[k], - sizeof(struct in_addr) * server->servers[k].size, - server->servers[k].addr); - if (r < 0) - return r; - } - - if (server->timezone) { - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_TZDB_TIMEZONE, - strlen(server->timezone), server->timezone); - if (r < 0) - return r; - } - - if (server->domain_name) { - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_DOMAIN_NAME, - strlen(server->domain_name), server->domain_name); - if (r < 0) - return r; - } - - /* RFC 8925 section 3.3. DHCPv4 Server Behavior - * The server MUST NOT include the IPv6-Only Preferred option in the DHCPOFFER or DHCPACK message if - * the option was not present in the Parameter Request List sent by the client. */ - if (dhcp_request_contains(req, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED) && - server->ipv6_only_preferred_usec > 0) { - be32_t sec = usec_to_be32_sec(server->ipv6_only_preferred_usec); - - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, - sizeof(sec), &sec); - if (r < 0) - return r; - } - - ORDERED_SET_FOREACH(j, server->extra_options) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - j->option, j->length, j->data); - if (r < 0) - return r; - } - - if (!ordered_set_isempty(server->vendor_options)) { - 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); - if (r < 0) - return r; - } - - if (server->rapid_commit && req->rapid_commit && type == DHCP_ACK) { - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_RAPID_COMMIT, - 0, NULL); - if (r < 0) - return r; - } - - r = dhcp_server_append_static_hostname(server, packet, &offset, req); - if (r < 0) - return r; - - return dhcp_server_send_packet(server, req, packet, type, offset); -} - -static int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req) { - _cleanup_free_ DHCPPacket *packet = NULL; - size_t offset; - int r; - - /* When a request is refused, RFC 2131, section 4.3.2 mentioned we should send NAK when the - * client is in INITREBOOT. If the client is in other state, there is nothing mentioned in the - * RFC whether we should send NAK or not. Hence, let's silently ignore the request. */ - - if (!init_reboot) - return 0; - - r = server_message_init(server, &packet, DHCP_NAK, &offset, req); - if (r < 0) - return log_dhcp_server_errno(server, r, "Failed to create NAK message: %m"); - - r = dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset); - if (r < 0) - return log_dhcp_server_errno(server, r, "Could not send NAK message: %m"); - - log_dhcp_server(server, "NAK (0x%x)", be32toh(req->message->xid)); - return DHCP_NAK; -} - -static int server_send_forcerenew( - sd_dhcp_server *server, - be32_t address, - be32_t gateway, - uint8_t htype, - uint8_t hlen, - const uint8_t *chaddr) { - - _cleanup_free_ DHCPPacket *packet = NULL; - size_t optoffset = 0; - int r; - - assert(server); - assert(address != INADDR_ANY); - assert(chaddr); - - packet = malloc0(sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE); - if (!packet) - return -ENOMEM; - - r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0, - htype, hlen, chaddr, DHCP_FORCERENEW, - DHCP_MIN_OPTIONS_SIZE, &optoffset); - if (r < 0) - return r; - - r = dhcp_option_append(&packet->dhcp, DHCP_MIN_OPTIONS_SIZE, - &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - return dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); -} - -static int parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) { - DHCPRequest *req = ASSERT_PTR(userdata); - int r; - - switch (code) { - case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: - if (len == 4) - req->lifetime = unaligned_be32_sec_to_usec(option, /* max_as_infinity= */ true); - - break; - case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS: - if (len == 4) - memcpy(&req->requested_ip, option, sizeof(be32_t)); - - break; - case SD_DHCP_OPTION_SERVER_IDENTIFIER: - if (len == 4) - memcpy(&req->server_id, option, sizeof(be32_t)); - - break; - case SD_DHCP_OPTION_CLIENT_IDENTIFIER: - if (client_id_size_is_valid(len)) - (void) sd_dhcp_client_id_set_raw(&req->client_id, option, len); - - break; - case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE: - - if (len == 2 && unaligned_read_be16(option) >= sizeof(DHCPPacket)) - req->max_optlen = unaligned_read_be16(option) - sizeof(DHCPPacket); - - break; - case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: - req->agent_info_option = (uint8_t*)option - 2; - - break; - case SD_DHCP_OPTION_HOST_NAME: { - _cleanup_free_ char *p = NULL; - - r = dhcp_option_parse_hostname(option, len, &p); - if (r < 0) - log_debug_errno(r, "Failed to parse hostname, ignoring: %m"); - else - free_and_replace(req->hostname, p); - break; - } - case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST: - req->parameter_request_list = option; - req->parameter_request_list_len = len; - break; - - case SD_DHCP_OPTION_RAPID_COMMIT: - req->rapid_commit = true; - break; - } - - return 0; -} - -static DHCPRequest* dhcp_request_free(DHCPRequest *req) { - if (!req) - return NULL; - - free(req->hostname); - return mfree(req); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free); - -static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) { - int r; - - assert(req); - assert(message); - - req->message = message; - - if (message->hlen > sizeof(message->chaddr)) - return -EBADMSG; - - /* set client id based on MAC address if client did not send an explicit one */ - if (!sd_dhcp_client_id_is_set(&req->client_id)) { - if (!client_id_data_size_is_valid(message->hlen)) - return -EBADMSG; - - r = sd_dhcp_client_id_set(&req->client_id, /* type= */ 1, message->chaddr, message->hlen); - if (r < 0) - return r; - } - - if (message->hlen == 0 || memeqzero(message->chaddr, message->hlen)) { - uint8_t type; - const void *data; - size_t size; - - /* See RFC2131 section 4.1.1. - * hlen and chaddr may not be set for non-ethernet interface. - * Let's try to retrieve it from the client ID. */ - - if (!sd_dhcp_client_id_is_set(&req->client_id)) - return -EBADMSG; - - r = sd_dhcp_client_id_get(&req->client_id, &type, &data, &size); - if (r < 0) - return r; - - if (type != 1) - return -EBADMSG; - - if (size > sizeof(message->chaddr)) - return -EBADMSG; - - memcpy(message->chaddr, data, size); - message->hlen = size; - } - - if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE) - req->max_optlen = DHCP_MIN_OPTIONS_SIZE; - - if (req->lifetime <= 0) - req->lifetime = MAX(USEC_PER_SEC, server->default_lease_time); - - if (server->max_lease_time > 0 && req->lifetime > server->max_lease_time) - req->lifetime = server->max_lease_time; - - return 0; -} - -static void request_set_timestamp(DHCPRequest *req, const triple_timestamp *timestamp) { - assert(req); - - if (timestamp && triple_timestamp_is_set(timestamp)) - req->timestamp = *timestamp; - else - triple_timestamp_now(&req->timestamp); -} - -static int request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_t *ret) { - assert(req); - assert(TRIPLE_TIMESTAMP_HAS_CLOCK(clock)); - assert(clock_supported(clock)); - assert(ret); - - if (req->lifetime <= 0) - return -ENODATA; - - if (!triple_timestamp_is_set(&req->timestamp)) - return -ENODATA; - - *ret = usec_add(triple_timestamp_by_clock(&req->timestamp, clock), req->lifetime); - return 0; -} - -static bool address_is_in_pool(sd_dhcp_server *server, be32_t address) { +bool dhcp_server_address_is_in_pool(sd_dhcp_server *server, be32_t address) { assert(server); if (server->pool_size == 0) @@ -971,111 +304,7 @@ static bool address_is_in_pool(sd_dhcp_server *server, be32_t address) { return true; } -static int append_agent_information_option(sd_dhcp_server *server, DHCPMessage *message, size_t opt_length, size_t size) { - int r; - size_t offset; - - assert(server); - assert(message); - - r = dhcp_option_find_option(message->options, opt_length, SD_DHCP_OPTION_END, &offset); - if (r < 0) - return r; - - r = dhcp_option_append(message, size, &offset, 0, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, 0, server); - if (r < 0) - return r; - - r = dhcp_option_append(message, size, &offset, 0, SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - return offset; -} - -static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *message, size_t opt_length, size_t buflen) { - _cleanup_free_ DHCPPacket *packet = NULL; - int r; - - assert(server); - assert(message); - assert(sd_dhcp_server_is_in_relay_mode(server)); - - if (message->hlen == 0 || message->hlen > sizeof(message->chaddr) || memeqzero(message->chaddr, message->hlen)) - return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EBADMSG), - "(relay agent) received message without/invalid hardware address, discarding."); - - if (message->op == BOOTREQUEST) { - log_dhcp_server(server, "(relay agent) BOOTREQUEST (0x%x)", be32toh(message->xid)); - if (message->hops >= 16) - return -ETIME; - message->hops++; - - /* https://tools.ietf.org/html/rfc1542#section-4.1.1 */ - if (message->giaddr == 0) - message->giaddr = server->address; - - if (server->agent_circuit_id || server->agent_remote_id) { - r = append_agent_information_option(server, message, opt_length, buflen - sizeof(DHCPMessage)); - if (r < 0) - return log_dhcp_server_errno(server, r, "could not append relay option: %m"); - opt_length = r; - } - - return dhcp_server_send_udp(server, server->relay_target.s_addr, DHCP_PORT_SERVER, message, sizeof(DHCPMessage) + opt_length); - } else if (message->op == BOOTREPLY) { - log_dhcp_server(server, "(relay agent) BOOTREPLY (0x%x)", be32toh(message->xid)); - if (message->giaddr != server->address) - return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EBADMSG), - "(relay agent) BOOTREPLY giaddr mismatch, discarding"); - - int message_type = dhcp_option_parse(message, sizeof(DHCPMessage) + opt_length, NULL, NULL, NULL); - if (message_type < 0) - return message_type; - - packet = malloc0(sizeof(DHCPPacket) + opt_length); - if (!packet) - return -ENOMEM; - memcpy(&packet->dhcp, message, sizeof(DHCPMessage) + opt_length); - - r = dhcp_option_remove_option(packet->dhcp.options, opt_length, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION); - if (r > 0) - opt_length = r; - - bool l2_broadcast = requested_broadcast(message) || message_type == DHCP_NAK; - const be32_t destination = message_type == DHCP_NAK ? INADDR_ANY : message->ciaddr; - return dhcp_server_send(server, message->hlen, message->chaddr, destination, DHCP_PORT_CLIENT, packet, opt_length, l2_broadcast); - } - return -EBADMSG; -} - -static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, be32_t address) { - usec_t expiration; - int r; - - assert(server); - assert(req); - assert(address != 0); - - r = request_get_lifetime_timestamp(req, CLOCK_BOOTTIME, &expiration); - if (r < 0) - return r; - - r = dhcp_server_set_lease(server, address, req, expiration); - if (r < 0) - return log_dhcp_server_errno(server, r, "Failed to create new lease: %m"); - - r = server_send_offer_or_ack(server, req, address, DHCP_ACK); - if (r < 0) - return log_dhcp_server_errno(server, r, "Could not send ACK: %m"); - - log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->xid)); - - server_on_lease_change(server); - - return DHCP_ACK; -} - -static bool address_available(sd_dhcp_server *server, be32_t address) { +bool dhcp_server_address_available(sd_dhcp_server *server, be32_t address) { assert(server); if (hashmap_contains(server->bound_leases_by_address, UINT32_TO_PTR(address)) || @@ -1086,280 +315,6 @@ static bool address_available(sd_dhcp_server *server, be32_t address) { return true; } -#define HASH_KEY SD_ID128_MAKE(0d,1d,fe,bd,f1,24,bd,b3,47,f1,dd,6e,73,21,93,30) - -int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, const triple_timestamp *timestamp) { - _cleanup_(dhcp_request_freep) DHCPRequest *req = NULL; - _cleanup_free_ char *error_message = NULL; - sd_dhcp_server_lease *existing_lease, *static_lease; - int type, r; - - assert(server); - assert(message); - - if (message->op != BOOTREQUEST) - return 0; - - req = new0(DHCPRequest, 1); - if (!req) - return -ENOMEM; - - type = dhcp_option_parse(message, length, parse_request, req, &error_message); - if (type < 0) - return type; - - r = ensure_sane_request(server, req, message); - if (r < 0) - return r; - - request_set_timestamp(req, timestamp); - - r = dhcp_server_cleanup_expired_leases(server); - if (r < 0) - return r; - - existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id); - static_lease = dhcp_server_get_static_lease(server, req); - - switch (type) { - - case DHCP_DISCOVER: { - be32_t address = INADDR_ANY; - - log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->xid)); - - if (server->pool_size == 0) - /* no pool allocated */ - return 0; - - /* for now pick a random free address from the pool */ - if (static_lease) { - sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(static_lease->address)); - if (l && l != existing_lease) - /* The address is already assigned to another host. Refusing. */ - return 0; - - /* Found a matching static lease. */ - address = static_lease->address; - - } else if (existing_lease && address_is_in_pool(server, existing_lease->address)) - - /* If we previously assigned an address to the host, then reuse it. */ - address = existing_lease->address; - - else { - struct siphash state; - uint64_t hash; - - /* even with no persistence of leases, we try to offer the same client - the same IP address. we do this by using the hash of the client id - as the offset into the pool of leases when finding the next free one */ - - siphash24_init(&state, HASH_KEY.bytes); - client_id_hash_func(&req->client_id, &state); - hash = htole64(siphash24_finalize(&state)); - - for (unsigned i = 0; i < server->pool_size; i++) { - be32_t tmp_address; - - tmp_address = server->subnet | htobe32(server->pool_offset + (hash + i) % server->pool_size); - if (address_available(server, tmp_address)) { - address = tmp_address; - break; - } - } - } - - if (address == INADDR_ANY) - /* no free addresses left */ - return 0; - - if (server->rapid_commit && req->rapid_commit) - return server_ack_request(server, req, address); - - r = server_send_offer_or_ack(server, req, address, DHCP_OFFER); - if (r < 0) - /* this only fails on critical errors */ - return log_dhcp_server_errno(server, r, "Could not send offer: %m"); - - log_dhcp_server(server, "OFFER (0x%x)", be32toh(req->message->xid)); - return DHCP_OFFER; - } - case DHCP_DECLINE: - log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->xid), strna(error_message)); - - /* TODO: make sure we don't offer this address again */ - - return 1; - - case DHCP_REQUEST: { - be32_t address; - bool init_reboot = false; - - /* see RFC 2131, section 4.3.2 */ - - if (req->server_id != 0) { - log_dhcp_server(server, "REQUEST (selecting) (0x%x)", - be32toh(req->message->xid)); - - /* SELECTING */ - if (req->server_id != server->address) - /* client did not pick us */ - return 0; - - if (req->message->ciaddr != 0) - /* this MUST be zero */ - return 0; - - if (req->requested_ip == 0) - /* this must be filled in with the yiaddr - from the chosen OFFER */ - return 0; - - address = req->requested_ip; - } else if (req->requested_ip != 0) { - log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)", - be32toh(req->message->xid)); - - /* INIT-REBOOT */ - if (req->message->ciaddr != 0) - /* this MUST be zero */ - return 0; - - /* TODO: check more carefully if IP is correct */ - address = req->requested_ip; - init_reboot = true; - } else { - log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)", - be32toh(req->message->xid)); - - /* REBINDING / RENEWING */ - if (req->message->ciaddr == 0) - /* this MUST be filled in with clients IP address */ - return 0; - - address = req->message->ciaddr; - } - - /* Silently ignore Rapid Commit option in REQUEST message. */ - req->rapid_commit = false; - - if (static_lease) { - if (static_lease->address != address) - /* The client requested an address which is different from the static lease. Refusing. */ - return server_send_nak_or_ignore(server, init_reboot, req); - - sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(address)); - if (l && l != existing_lease) - /* The requested address is already assigned to another host. Refusing. */ - return server_send_nak_or_ignore(server, init_reboot, req); - - /* Found a static lease for the client ID. */ - return server_ack_request(server, req, address); - } - - if (address_is_in_pool(server, address)) - /* The requested address is in the pool. */ - return server_ack_request(server, req, address); - - /* Refuse otherwise. */ - return server_send_nak_or_ignore(server, init_reboot, req); - } - - case DHCP_RELEASE: { - log_dhcp_server(server, "RELEASE (0x%x)", - be32toh(req->message->xid)); - - if (!existing_lease) - return 0; - - if (existing_lease->address != req->message->ciaddr) - return 0; - - sd_dhcp_server_lease_unref(existing_lease); - - server_on_lease_change(server); - - return 0; - }} - - return 0; -} - -static size_t relay_agent_information_length(const char* agent_circuit_id, const char* agent_remote_id) { - size_t sum = 0; - if (agent_circuit_id) - sum += 2 + strlen(agent_circuit_id); - if (agent_remote_id) - sum += 2 + strlen(agent_remote_id); - return sum; -} - -static int server_receive_message(sd_event_source *s, int fd, - uint32_t revents, void *userdata) { - _cleanup_free_ DHCPMessage *message = NULL; - /* This needs to be initialized with zero. See #20741. - * The issue is fixed on glibc-2.35 (8fba672472ae0055387e9315fc2eddfa6775ca79). */ - CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL + - CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; - sd_dhcp_server *server = ASSERT_PTR(userdata); - struct iovec iov = {}; - struct msghdr msg = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - ssize_t datagram_size, len; - int r; - - datagram_size = next_datagram_size_fd(fd); - if (ERRNO_IS_NEG_TRANSIENT(datagram_size) || ERRNO_IS_NEG_DISCONNECT(datagram_size)) - return 0; - if (datagram_size < 0) { - log_dhcp_server_errno(server, datagram_size, "Failed to determine datagram size to read, ignoring: %m"); - return 0; - } - - size_t buflen = datagram_size; - if (sd_dhcp_server_is_in_relay_mode(server)) - /* Preallocate the additional size for DHCP Relay Agent Information Option if needed */ - buflen += relay_agent_information_length(server->agent_circuit_id, server->agent_remote_id) + 2; - - message = malloc0(buflen); - if (!message) - return -ENOMEM; - - iov = IOVEC_MAKE(message, datagram_size); - - len = recvmsg_safe(fd, &msg, 0); - if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len)) - return 0; - if (len < 0) { - log_dhcp_server_errno(server, len, "Could not receive message, ignoring: %m"); - return 0; - } - - if ((size_t) len < sizeof(DHCPMessage)) - return 0; - - /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */ - struct in_pktinfo *info = CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo); - if (info && info->ipi_ifindex != server->ifindex) - return 0; - - if (sd_dhcp_server_is_in_relay_mode(server)) { - r = dhcp_server_relay_message(server, message, len - sizeof(DHCPMessage), buflen); - if (r < 0) - log_dhcp_server_errno(server, r, "Couldn't relay message, ignoring: %m"); - } else { - r = dhcp_server_handle_message(server, message, (size_t) len, TRIPLE_TIMESTAMP_FROM_CMSG(&msg)); - if (r < 0) - log_dhcp_server_errno(server, r, "Couldn't process incoming message, ignoring: %m"); - } - return 0; -} - static void dhcp_server_update_lease_servers(sd_dhcp_server *server) { assert(server); assert(server->address != 0); @@ -1377,100 +332,23 @@ int sd_dhcp_server_start(sd_dhcp_server *server) { assert_return(server, -EINVAL); assert_return(server->event, -EINVAL); + assert_return(server->address != INADDR_ANY, -EUNATCH); if (sd_dhcp_server_is_running(server)) return 0; - assert_return(!server->receive_message, -EBUSY); - assert_return(server->fd_raw < 0, -EBUSY); - assert_return(server->fd < 0, -EBUSY); - assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH); - dhcp_server_update_lease_servers(server); - r = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (r < 0) { - r = -errno; - goto on_error; - } - server->fd_raw = r; - - if (server->bind_to_interface) - r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_ANY, DHCP_PORT_SERVER, -1); - else - r = dhcp_network_bind_udp_socket(0, server->address, DHCP_PORT_SERVER, -1); - if (r < 0) - goto on_error; - server->fd = r; - - r = sd_event_add_io(server->event, &server->receive_message, - server->fd, EPOLLIN, - server_receive_message, server); + r = dhcp_server_setup_io_event_source(server); if (r < 0) - goto on_error; - - r = sd_event_source_set_priority(server->receive_message, - server->event_priority); - if (r < 0) - goto on_error; - - if (!server->bind_to_interface) { - r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_BROADCAST, DHCP_PORT_SERVER, -1); - if (r < 0) - goto on_error; - - server->fd_broadcast = r; - - r = sd_event_add_io(server->event, &server->receive_broadcast, - server->fd_broadcast, EPOLLIN, - server_receive_message, server); - if (r < 0) - goto on_error; - - r = sd_event_source_set_priority(server->receive_broadcast, - server->event_priority); - if (r < 0) - goto on_error; - } + return r; r = dhcp_server_load_leases(server); if (r < 0) log_dhcp_server_errno(server, r, "Failed to load lease file %s, ignoring: %m", strna(server->lease_file)); log_dhcp_server(server, "STARTED"); - return 0; - -on_error: - sd_dhcp_server_stop(server); - return r; -} - -int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { - sd_dhcp_server_lease *lease; - int r = 0; - - assert_return(server, -EINVAL); - - log_dhcp_server(server, "FORCERENEW"); - - HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) - RET_GATHER(r, - server_send_forcerenew(server, lease->address, lease->gateway, - lease->htype, lease->hlen, lease->chaddr)); - return r; -} - -int sd_dhcp_server_set_bind_to_interface(sd_dhcp_server *server, int enabled) { - assert_return(server, -EINVAL); - assert_return(!sd_dhcp_server_is_running(server), -EBUSY); - - if (!!enabled == server->bind_to_interface) - return 0; - - server->bind_to_interface = enabled; - - return 1; } int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *tz) { @@ -1601,33 +479,14 @@ int sd_dhcp_server_set_router(sd_dhcp_server *server, const struct in_addr *rout return 0; } -int sd_dhcp_server_add_option(sd_dhcp_server *server, sd_dhcp_option *v) { - int r; - - assert_return(server, -EINVAL); - assert_return(v, -EINVAL); - - r = ordered_set_ensure_put(&server->extra_options, &dhcp_option_hash_ops, v); - if (r < 0) - return r; - - sd_dhcp_option_ref(v); - return 0; +int dhcp_server_set_extra_options(sd_dhcp_server *server, TLV *options) { + assert(server); + return unref_and_replace_new_ref(server->extra_options, options, tlv_ref, tlv_unref); } -int sd_dhcp_server_add_vendor_option(sd_dhcp_server *server, sd_dhcp_option *v) { - int r; - - assert_return(server, -EINVAL); - assert_return(v, -EINVAL); - - r = ordered_set_ensure_put(&server->vendor_options, &dhcp_option_hash_ops, v); - if (r < 0) - return r; - - sd_dhcp_option_ref(v); - - return 1; +int dhcp_server_set_vendor_options(sd_dhcp_server *server, TLV *options) { + assert(server); + return unref_and_replace_new_ref(server->vendor_options, options, tlv_ref, tlv_unref); } int sd_dhcp_server_set_callback(sd_dhcp_server *server, sd_dhcp_server_callback_t cb, void *userdata) { @@ -1639,45 +498,6 @@ int sd_dhcp_server_set_callback(sd_dhcp_server *server, sd_dhcp_server_callback_ return 0; } -int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr *address) { - assert_return(server, -EINVAL); - assert_return(!sd_dhcp_server_is_running(server), -EBUSY); - - if (memcmp(address, &server->relay_target, sizeof(struct in_addr)) == 0) - return 0; - - server->relay_target = *address; - return 1; -} - -int sd_dhcp_server_set_relay_agent_information( - sd_dhcp_server *server, - const char *agent_circuit_id, - const char *agent_remote_id) { - _cleanup_free_ char *circuit_id_dup = NULL, *remote_id_dup = NULL; - - assert_return(server, -EINVAL); - - if (relay_agent_information_length(agent_circuit_id, agent_remote_id) > UINT8_MAX) - return -ENOBUFS; - - if (agent_circuit_id) { - circuit_id_dup = strdup(agent_circuit_id); - if (!circuit_id_dup) - return -ENOMEM; - } - - if (agent_remote_id) { - remote_id_dup = strdup(agent_remote_id); - if (!remote_id_dup) - return -ENOMEM; - } - - free_and_replace(server->agent_circuit_id, circuit_id_dup); - free_and_replace(server->agent_remote_id, remote_id_dup); - return 0; -} - int sd_dhcp_server_set_lease_file(sd_dhcp_server *server, int dir_fd, const char *path) { int r; diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 448a2e7557ee9..b78d1c29ecd29 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -182,12 +182,12 @@ int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option * } r = ordered_set_ensure_put(&client->vendor_options, &dhcp6_option_hash_ops, v); - if (r < 0) + if (r <= 0) return r; sd_dhcp6_option_ref(v); - return 1; + return r; } static int client_ensure_duid(sd_dhcp6_client *client) { @@ -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)); } @@ -538,11 +541,11 @@ int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v) { assert_return(v, -EINVAL); r = ordered_hashmap_ensure_put(&client->extra_options, &dhcp6_option_hash_ops, UINT_TO_PTR(v->option), v); - if (r < 0) + if (r <= 0) return r; sd_dhcp6_option_ref(v); - return 0; + return r; } static void client_set_state(sd_dhcp6_client *client, DHCP6State state) { @@ -1504,7 +1507,7 @@ int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64 else { r = sd_event_default(&client->event); if (r < 0) - return 0; + return r; } client->event_priority = priority; @@ -1530,7 +1533,7 @@ sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) { int sd_dhcp6_client_attach_device(sd_dhcp6_client *client, sd_device *dev) { assert_return(client, -EINVAL); - return device_unref_and_replace(client->dev, dev); + return device_unref_and_replace_new_ref(client->dev, dev); } static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) { 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-rx.c b/src/libsystemd-network/sd-lldp-rx.c index 288ed2ee96919..30c616448b622 100644 --- a/src/libsystemd-network/sd-lldp-rx.c +++ b/src/libsystemd-network/sd-lldp-rx.c @@ -199,6 +199,9 @@ static int lldp_rx_receive_datagram(sd_event_source *s, int fd, uint32_t revents sd_lldp_rx *lldp_rx = ASSERT_PTR(userdata); struct timespec ts; + /* Keep ref in case the callback drops the last reference, so we can use it below */ + _unused_ _cleanup_(sd_lldp_rx_unrefp) sd_lldp_rx *ref = sd_lldp_rx_ref(lldp_rx); + assert(fd >= 0); space = next_datagram_size_fd(fd); @@ -421,6 +424,9 @@ static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) { sd_lldp_rx *lldp_rx = userdata; int r; + /* Keep ref in case the callback drops the last reference, so we can use it below */ + _unused_ _cleanup_(sd_lldp_rx_unrefp) sd_lldp_rx *ref = sd_lldp_rx_ref(lldp_rx); + r = lldp_rx_make_space(lldp_rx, 0); if (r < 0) { log_lldp_rx_errno(lldp_rx, r, "Failed to make space, ignoring: %m"); 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/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 30c0de70047e2..51e12d45287a7 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -137,7 +137,7 @@ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) { else { r = sd_event_default(&nd->event); if (r < 0) - return 0; + return r; } nd->event_priority = priority; @@ -348,6 +348,12 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda return 0; } + if (packet->ifindex != nd->ifindex) { + log_ndisc(nd, "Received an ICMPv6 packet on interface %i, expected %i, ignoring.", + packet->ifindex, nd->ifindex); + return 0; + } + /* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states * that hosts MUST discard messages with the null source address. */ if (in6_addr_is_null(&packet->sender_address)) { diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c index b9c59d01643af..df5fcc6c02ebe 100644 --- a/src/libsystemd-network/sd-radv.c +++ b/src/libsystemd-network/sd-radv.c @@ -58,7 +58,7 @@ int sd_radv_attach_event(sd_radv *ra, sd_event *event, int64_t priority) { else { r = sd_event_default(&ra->event); if (r < 0) - return 0; + return r; } ra->event_priority = priority; @@ -226,6 +226,12 @@ static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdat return 0; } + if (packet->ifindex != ra->ifindex) { + log_radv(ra, "Received an ICMPv6 packet on interface %i, expected %i, ignoring.", + packet->ifindex, ra->ifindex); + return 0; + } + (void) radv_process_packet(ra, packet); return 0; } @@ -254,9 +260,13 @@ int sd_radv_send(sd_radv *ra) { r = radv_send_router(ra, NULL); if (r < 0) - return log_radv_errno(ra, r, "Unable to send Router Advertisement: %m"); - - ra->ra_sent++; + /* Do not treat transient send failures (e.g. ENOBUFS while a bond is still selecting an + * aggregator after a carrier flap, or ENETDOWN/EADDRNOTAVAIL during a short link bounce) as + * fatal: log and reschedule so we try again instead of stopping the RA engine for good. + * Solicited RAs already behave this way, see radv_process_packet(). */ + log_radv_errno(ra, r, "Unable to send Router Advertisement, will retry later: %m"); + else + ra->ra_sent++; /* RFC 4861, Section 6.2.4, sending initial Router Advertisements */ if (ra->ra_sent <= RADV_MAX_INITIAL_RTR_ADVERTISEMENTS) @@ -283,8 +293,12 @@ int sd_radv_send(sd_radv *ra) { assert(min_timeout <= max_timeout * 3 / 4); timeout = min_timeout + random_u64_range(max_timeout - min_timeout); - log_radv(ra, "Sent unsolicited Router Advertisement. Next advertisement will be in %s.", - FORMAT_TIMESPAN(timeout, USEC_PER_SEC)); + if (r >= 0) + log_radv(ra, "Sent unsolicited Router Advertisement. Next advertisement will be in %s.", + FORMAT_TIMESPAN(timeout, USEC_PER_SEC)); + else + log_radv(ra, "Next Router Advertisement attempt in %s.", + FORMAT_TIMESPAN(timeout, USEC_PER_SEC)); return event_reset_time( ra->event, &ra->timeout_event_source, diff --git a/src/libsystemd-network/test-dhcp-client-id.c b/src/libsystemd-network/test-dhcp-client-id.c new file mode 100644 index 0000000000000..60cb682c88bb0 --- /dev/null +++ b/src/libsystemd-network/test-dhcp-client-id.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dhcp-client-id-internal.h" +#include "hashmap.h" +#include "siphash24.h" +#include "tests.h" + +static uint64_t client_id_hash_helper(sd_dhcp_client_id *id, uint8_t key[HASH_KEY_SIZE]) { + struct siphash state; + + siphash24_init(&state, key); + client_id_hash_func(id, &state); + + return htole64(siphash24_finalize(&state)); +} + +TEST(client_id_hash) { + sd_dhcp_client_id a = { + .size = 4, + }, b = { + .size = 4, + }; + uint8_t hash_key[HASH_KEY_SIZE] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + }; + + log_debug("/* %s */", __func__); + + memcpy(a.raw, "abcd", 4); + memcpy(b.raw, "abcd", 4); + + ASSERT_EQ(client_id_compare_func(&a, &b), 0); + ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); + a.size = 3; + ASSERT_NE(client_id_compare_func(&a, &b), 0); + a.size = 4; + ASSERT_EQ(client_id_compare_func(&a, &b), 0); + ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); + + b.size = 3; + ASSERT_NE(client_id_compare_func(&a, &b), 0); + b.size = 4; + ASSERT_EQ(client_id_compare_func(&a, &b), 0); + ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); + + memcpy(b.raw, "abce", 4); + ASSERT_NE(client_id_compare_func(&a, &b), 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 35cfcec6aca04..9eeb102b64a23 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -4,768 +4,1374 @@ ***/ #include -#include #include #include #include -#if HAVE_VALGRIND_VALGRIND_H -# include -#endif #include "sd-dhcp-client.h" #include "sd-dhcp-lease.h" #include "sd-event.h" -#include "alloc-util.h" -#include "dhcp-duid-internal.h" -#include "dhcp-network.h" -#include "dhcp-option.h" -#include "dhcp-packet.h" +#include "dhcp-client-internal.h" +#include "dhcp-message.h" #include "ether-addr-util.h" #include "fd-util.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "ip-util.h" #include "log.h" +#include "set.h" +#include "socket-util.h" +#include "strv.h" #include "tests.h" -static struct hw_addr_data hw_addr = { +static const struct hw_addr_data hw_addr = { .length = ETH_ALEN, .ether = {{ 'A', 'B', 'C', '1', '2', '3' }}, }, bcast_addr = { .length = ETH_ALEN, .ether = {{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }}, }; -typedef int (*test_callback_recv_t)(size_t size, DHCPMessage *dhcp); -struct bootp_addr_data { - uint8_t *offer_buf; - size_t offer_len; - int netmask_offset; - int ip_offset; +static const uint16_t server_port = 1067; +static const uint16_t client_port = 1068; +static const int ip_service_type = IPTOS_CLASS_CS3; + +static const union in_addr_union prefix = { + .bytes = { 198, 51, 100, 0 }, +}, server_address = { + .bytes = { 198, 51, 100, 1 }, +}, client_address = { + .bytes = { 198, 51, 100, 100 }, +}, broadcast = { + .bytes = { 198, 51, 100, 255 }, +}, netmask = { + .bytes = { 255, 255, 255, 0 }, }; -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 const usec_t lifetime = USEC_PER_DAY; -static void test_request_basic(sd_event *e) { - int r; +static const sd_dhcp_client_id client_id_generic = { + .size = 10, + .id.type = 0, + .id.data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, }, +}; - sd_dhcp_client *client; +/* options sent by client */ +static const char * const *user_class_strv = STRV_MAKE_CONST("user-class-hoge", "user-class-foo"); +static const char * const *vendor_specific_strv = STRV_MAKE_CONST("vendor-specific-hoge", "vendor-specific-foo"); +static const char *vendor_class = "vendor-class"; +static const char *mud_url = "https://mud-url.example.com"; +static const char *hostname = "hogehoge.example.com"; +static const uint32_t mtu = 3000; +static const char *extra_option_163 = "private_option_163"; +static const char *extra_option_164 = "private_option_164"; - if (verbose) - log_info("* %s", __func__); +static void setup(sd_event_io_handler_t io_handler, sd_dhcp_client_callback_t client_handler, sd_dhcp_client **ret) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_NOT_NULL(e); - /* Initialize client without Anonymize settings. */ - r = sd_dhcp_client_new(&client, false); + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); - assert_se(r >= 0); - assert_se(client); + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + ASSERT_OK(sd_dhcp_client_new(&client)); - r = sd_dhcp_client_attach_event(client, e, 0); - assert_se(r >= 0); + client->socket_fd = TAKE_FD(socket_fd[0]); - 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_OK(sd_dhcp_client_attach_event(client, e, SD_EVENT_PRIORITY_NORMAL)); + 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, client_handler, e)); + ASSERT_OK(sd_dhcp_client_set_port(client, server_port)); + ASSERT_OK(sd_dhcp_client_set_client_port(client, client_port)); + ASSERT_OK(sd_dhcp_client_set_ip_service_type(client, ip_service_type)); + + /* options */ + for (uint8_t i = 178; i <= 207; i++) /* These are currently unassigned. See sd-dhcp-protocol.h. */ + ASSERT_OK(sd_dhcp_client_set_request_option(client, i)); + + ASSERT_OK(sd_dhcp_client_set_client_id(client, client_id_generic.id.type, client_id_generic.id.data, client_id_generic.size - 1)); + ASSERT_OK(sd_dhcp_client_set_mtu(client, mtu)); + ASSERT_OK(sd_dhcp_client_set_mud_url(client, mud_url)); + ASSERT_OK(sd_dhcp_client_set_hostname(client, hostname)); + ASSERT_OK(sd_dhcp_client_set_vendor_class_identifier(client, vendor_class)); + + _cleanup_(tlv_unrefp) TLV *vendor_specific = + ASSERT_NOT_NULL(tlv_new(TLV_DHCP4_SUBOPTION)); + uint8_t c = 0; + STRV_FOREACH(s, vendor_specific_strv) + ASSERT_OK(tlv_append(vendor_specific, ++c, strlen(*s), *s)); + ASSERT_OK(dhcp_client_set_vendor_options(client, vendor_specific)); + + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + STRV_FOREACH(s, user_class_strv) + ASSERT_OK(iovw_put(&iovw, (void*) *s, strlen(*s))); + ASSERT_OK(dhcp_client_set_user_class(client, &iovw)); + + _cleanup_(tlv_unrefp) TLV *extra_options = ASSERT_NOT_NULL(tlv_new(TLV_DHCP4)); + ASSERT_OK(tlv_append(extra_options, 163, strlen(extra_option_163), extra_option_163)); + ASSERT_OK(tlv_append(extra_options, 164, strlen(extra_option_164), extra_option_164)); + ASSERT_OK(dhcp_client_set_extra_options(client, extra_options)); + + /* IO event source for the fake server side */ + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + ASSERT_OK(sd_event_add_io(e, &s, socket_fd[1], EPOLLIN, io_handler, client)); + ASSERT_OK(sd_event_source_set_priority(s, SD_EVENT_PRIORITY_IMPORTANT)); + ASSERT_OK(sd_event_source_set_description(s, "fake-server-io-event-source")); + ASSERT_OK(sd_event_source_set_io_fd_own(s, true)); + TAKE_FD(socket_fd[1]); + ASSERT_OK(sd_event_source_set_floating(s, true)); + + *ret = TAKE_PTR(client); +} - assert_se(sd_dhcp_client_set_ifindex(client, 15) == 0); - 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); +static void receive_message(int fd, bool raw, bool check_xid, sd_dhcp_client *client, sd_dhcp_message **ret) { + ssize_t buflen = ASSERT_OK_POSITIVE(next_datagram_size_fd(fd)); + _cleanup_free_ void *buf = ASSERT_NOT_NULL(malloc0(buflen)); - 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); + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + }; + ssize_t len = ASSERT_OK_ERRNO(recvmsg_safe(fd, &msg, MSG_DONTWAIT)); + + struct iovec payload = IOVEC_MAKE(buf, len); + if (raw) + ASSERT_OK(udp_packet_verify( + &payload, + client->server_port, + /* checksum= */ true, + &payload)); + + ASSERT_OK(dhcp_message_parse( + &payload, + BOOTREQUEST, + check_xid ? &client->xid : NULL, + ARPHRD_ETHER, + &hw_addr, + ret)); +} - 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); +static void iovw_send(int fd, struct iovec_wrapper *iovw) { + struct msghdr mh = { + .msg_iov = iovw->iovec, + .msg_iovlen = iovw->count, + }; + ASSERT_OK_ERRNO(sendmsg(fd, &mh, MSG_NOSIGNAL)); +} - 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); +static void send_message(int fd, bool raw, sd_dhcp_client *client, sd_dhcp_message *m) { + _cleanup_(iovw_done_free) struct iovec_wrapper payload = {}; + ASSERT_OK(dhcp_message_build(m, &payload)); - /* 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); + if (!raw) { + iovw_send(fd, &payload); + return; + } - sd_dhcp_client_unref(client); + struct iphdr ip; + struct udphdr udp; + ASSERT_OK(udp_packet_build( + server_address.in.s_addr, + client->server_port, + m->header.yiaddr, + client->port, + client->ip_service_type, + &payload, + &ip, + &udp)); + + _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)); + iovw_send(fd, &iovw); } -static void test_request_anonymize(sd_event *e) { - int r; +static void create_reply(sd_dhcp_client *client, sd_dhcp_message *request, uint8_t type, sd_dhcp_message **ret) { + assert(ret); + + struct hw_addr_data hw; + ASSERT_OK(dhcp_message_get_hw_addr(request, &hw)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + ASSERT_OK(dhcp_message_init_header( + m, + BOOTREPLY, + be32toh(request->header.xid), + request->header.htype, + &hw)); + + if (client->bootp) { + m->header.yiaddr = client_address.in.s_addr; + m->header.siaddr = server_address.in.s_addr; + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_SUBNET_MASK, &netmask.in)); + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_BROADCAST, &broadcast.in)); + + *ret = TAKE_PTR(m); + return; + } + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, type)); + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_SERVER_IDENTIFIER, &server_address.in)); + + switch (type) { + case DHCP_OFFER: + case DHCP_ACK: + m->header.yiaddr = client_address.in.s_addr; + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_SUBNET_MASK, &netmask.in)); + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_BROADCAST, &broadcast.in)); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, usec_to_be32_sec(lifetime))); + /* The following two options are intentionally set with spurious values, to test the adjusting logic. */ + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REBINDING_TIME, usec_to_be32_sec(lifetime + USEC_PER_SEC))); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_RENEWAL_TIME, usec_to_be32_sec(lifetime - USEC_PER_SEC))); + break; + + case DHCP_NAK: + ASSERT_OK(dhcp_message_append_option_string(m, SD_DHCP_OPTION_ERROR_MESSAGE, "test error message")); + break; + + default: + ; + } - sd_dhcp_client *client; + *ret = TAKE_PTR(m); +} - if (verbose) - log_info("* %s", __func__); +static void verify_header(sd_dhcp_message *m) { + ASSERT_EQ(m->header.op, BOOTREQUEST); + ASSERT_EQ(m->header.htype, ARPHRD_ETHER); + ASSERT_EQ(memcmp_nn(m->header.chaddr, m->header.hlen, hw_addr.bytes, hw_addr.length), 0); +} - /* Initialize client with Anonymize settings. */ - r = sd_dhcp_client_new(&client, true); +static void verify_basic_options(sd_dhcp_message *m, uint8_t type) { + uint8_t t; + ASSERT_OK(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, &t)); + ASSERT_EQ(t, type); - assert_se(r >= 0); - assert_se(client); + sd_dhcp_client_id id; + ASSERT_OK(dhcp_message_get_option_client_id(m, &id)); + ASSERT_EQ(client_id_compare_func(&id, &client_id_generic), 0); +} - r = sd_dhcp_client_attach_event(client, e, 0); - assert_se(r >= 0); +static void verify_request(sd_dhcp_message *m, uint8_t type) { + verify_header(m); + verify_basic_options(m, type); + + _cleanup_set_free_ Set *prl = NULL; + ASSERT_OK(dhcp_message_get_option_parameter_request_list(m, &prl)); + + for (uint8_t i = 178; i <= 207; i++) + ASSERT_TRUE(set_contains(prl, UINT_TO_PTR(i))); + + uint16_t sz; + ASSERT_OK(dhcp_message_get_option_u16(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, &sz)); + ASSERT_EQ(sz, mtu); + + _cleanup_free_ char *str = NULL; + ASSERT_OK(dhcp_message_get_option_string(m, SD_DHCP_OPTION_MUD_URL, &str)); + ASSERT_STREQ(str, mud_url); + + str = mfree(str); + ASSERT_OK(dhcp_message_get_option_hostname(m, &str)); + ASSERT_STREQ(str, hostname); + + str = mfree(str); + ASSERT_OK(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, &str)); + ASSERT_STREQ(str, vendor_class); + + _cleanup_(tlv_unrefp) TLV *vendor_specific = NULL; + ASSERT_OK(dhcp_message_get_option_sub_tlv( + m, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, + TLV_DHCP4_SUBOPTION, + &vendor_specific)); + ASSERT_EQ(hashmap_size(vendor_specific->entries), strv_length((char**) vendor_specific_strv)); + uint8_t c = 0; + STRV_FOREACH(s, vendor_specific_strv) { + struct iovec v; + ASSERT_OK(tlv_get(vendor_specific, ++c, &v)); + ASSERT_EQ(memcmp_nn(v.iov_base, v.iov_len, *s, strlen(*s)), 0); + } - assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_NETBIOS_NAME_SERVER) == 0); - /* 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); + _cleanup_(iovw_done) struct iovec_wrapper iovw = {}; + STRV_FOREACH(s, user_class_strv) + ASSERT_OK(iovw_put(&iovw, (void*) *s, strlen(*s))); + _cleanup_(iovw_done_free) struct iovec_wrapper user_class = {}; + ASSERT_OK(dhcp_message_get_option_length_prefixed_data(m, SD_DHCP_OPTION_USER_CLASS, /* length_size= */ 1, &user_class)); + ASSERT_TRUE(iovw_equal(&user_class, &iovw)); - /* 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); + str = mfree(str); + ASSERT_OK(dhcp_message_get_option_string(m, 163, &str)); + ASSERT_STREQ(str, extra_option_163); - sd_dhcp_client_unref(client); + str = mfree(str); + ASSERT_OK(dhcp_message_get_option_string(m, 164, &str)); + ASSERT_STREQ(str, extra_option_164); } -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 - }; +static void verify_anonymized_request(sd_dhcp_message *m, uint8_t type) { + verify_header(m); + verify_basic_options(m, type); + + _cleanup_set_free_ Set *prl = NULL; + ASSERT_OK(dhcp_message_get_option_parameter_request_list(m, &prl)); + + for (uint8_t i = 178; i <= 207; i++) + ASSERT_FALSE(set_contains(prl, UINT_TO_PTR(i))); + + uint8_t code; + FOREACH_ARGUMENT(code, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, + SD_DHCP_OPTION_MUD_URL, + SD_DHCP_OPTION_HOST_NAME, + SD_DHCP_OPTION_FQDN, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, + SD_DHCP_OPTION_USER_CLASS, + 163, + 164) + ASSERT_FALSE(dhcp_message_has_option(m, code)); +} - if (verbose) - log_info("* %s", __func__); +static void verify_request_server_address(sd_dhcp_message *m) { + struct in_addr a; + ASSERT_OK(dhcp_message_get_option_address(m, SD_DHCP_OPTION_SERVER_IDENTIFIER, &a)); + ASSERT_TRUE(in4_addr_equal(&a, &server_address.in)); +} - assert_se(dhcp_packet_checksum((uint8_t*)&buf, 20) == be16toh(0x78ae)); +static void verify_request_client_address(sd_dhcp_message *m, bool header) { + if (header) + ASSERT_EQ(m->header.ciaddr, client_address.in.s_addr); + else { + struct in_addr a; + ASSERT_OK(dhcp_message_get_option_address(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &a)); + ASSERT_TRUE(in4_addr_equal(&a, &client_address.in)); + } } -static void test_dhcp_identifier_set_iaid(void) { - uint32_t iaid_legacy; - be32_t iaid; +static void verify_reply(sd_dhcp_client *client, DHCPState state) { + ASSERT_EQ(client->state, state); - 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); + sd_dhcp_lease *lease; + ASSERT_OK(sd_dhcp_client_get_lease(client, &lease)); - /* 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)); -#if __BYTE_ORDER == __LITTLE_ENDIAN - assert_se(iaid == iaid_legacy); -#else - assert_se(iaid == bswap_32(iaid_legacy)); -#endif + struct in_addr a; + ASSERT_OK(sd_dhcp_lease_get_address(lease, &a)); + ASSERT_TRUE(in4_addr_equal(&a, &client_address.in)); + + ASSERT_OK(sd_dhcp_lease_get_server_identifier(lease, &a)); + ASSERT_TRUE(in4_addr_equal(&a, &server_address.in)); + + ASSERT_OK(sd_dhcp_lease_get_broadcast(lease, &a)); + ASSERT_TRUE(in4_addr_equal(&a, &broadcast.in)); + + ASSERT_OK(sd_dhcp_lease_get_netmask(lease, &a)); + ASSERT_TRUE(in4_addr_equal(&a, &netmask.in)); + + uint8_t prefixlen; + ASSERT_OK(sd_dhcp_lease_get_prefix(lease, &a, &prefixlen)); + ASSERT_TRUE(in4_addr_equal(&a, &prefix.in)); + ASSERT_EQ(prefixlen, 24u); + + if (client->bootp) { + usec_t t; + ASSERT_OK(sd_dhcp_lease_get_lifetime(lease, &t)); + ASSERT_EQ(t, USEC_INFINITY); + } else { + usec_t t; + ASSERT_OK(sd_dhcp_lease_get_lifetime(lease, &t)); + ASSERT_EQ(t, lifetime); + ASSERT_OK(sd_dhcp_lease_get_t1(lease, &t)); + ASSERT_EQ(t, lifetime / 2); + ASSERT_OK(sd_dhcp_lease_get_t2(lease, &t)); + ASSERT_EQ(t, lifetime * 7 / 8); + } } -static int check_options(uint8_t code, uint8_t len, const void *option, void *userdata) { - switch (code) { - case SD_DHCP_OPTION_CLIENT_IDENTIFIER: { - sd_dhcp_duid duid; - uint32_t iaid; +static int basic_io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u", __func__, count); + + switch (count) { + case 1: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + verify_request(request, DHCP_DISCOVER); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_OFFER, &reply); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 2: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + /* REQUEST (selecting) */ + verify_request(request, DHCP_REQUEST); + verify_request_server_address(request); + verify_request_client_address(request, /* header= */ false); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 3: { + /* In this stage, client is already restarted and a new xid is picked. */ + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ false, client, &request); + + verify_header(request); + verify_basic_options(request, DHCP_DECLINE); + verify_request_server_address(request); + verify_request_client_address(request, /* header= */ false); + break; + } + case 4: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + verify_request(request, DHCP_DISCOVER); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_OFFER, &reply); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 5: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + /* REQUEST (selecting) */ + verify_request(request, DHCP_REQUEST); + verify_request_server_address(request); + verify_request_client_address(request, /* header= */ false); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 6: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ false, /* check_xid= */ true, client, &request); + + /* REQUEST (renewing) */ + verify_request(request, DHCP_REQUEST); + verify_request_client_address(request, /* header= */ true); - assert_se(sd_dhcp_duid_set_en(&duid) >= 0); - assert_se(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy_unstable_byteorder= */ true, &iaid) >= 0); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); - assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid.size); - assert_se(len == 19); - assert_se(((uint8_t*) option)[0] == 0xff); + send_message(fd, /* raw= */ false, client, reply); + break; + } + case 7: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ false, /* check_xid= */ true, client, &request); + + /* REQUEST (renewing) */ + verify_request(request, DHCP_REQUEST); + verify_request_client_address(request, /* header= */ true); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + + send_message(fd, /* raw= */ false, client, reply); + break; + } + case 8: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + /* REQUEST (rebinding) */ + verify_request(request, DHCP_REQUEST); + verify_request_client_address(request, /* header= */ true); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); - assert_se(memcmp((uint8_t*) option + 1, &iaid, sizeof(iaid)) == 0); - assert_se(memcmp((uint8_t*) option + 5, &duid.duid, duid.size) == 0); + send_message(fd, /* raw= */ true, client, reply); break; } + case 9: { + /* In this stage, client is already stopped and the xid has been cleared. */ + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ false, /* check_xid= */ false, client, &request); + verify_header(request); + verify_basic_options(request, DHCP_RELEASE); + verify_request_server_address(request); + verify_request_client_address(request, /* header= */ true); + + ASSERT_OK(sd_event_exit(sd_dhcp_client_get_event(client), 0)); + break; + } default: - ; + assert_not_reached(); } return 0; } -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; +static int restart_now_defer_handler(sd_event_source *s, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + + ASSERT_OK(sd_event_source_set_time_relative(client->timeout_resend, /* usec= */ 0)); + return 0; +} + +static int basic_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); - assert_se(s >= 0); - assert_se(packet); + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + break; + case 2: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + /* decline the bound lease, and restart the cycle. */ + ASSERT_OK(sd_dhcp_client_send_decline(client)); + break; + case 3: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_EXPIRED); + verify_reply(client, DHCP_STATE_BOUND); + /* on decline, the client will be restarted with a delay. Let's boost the restart timer. */ + ASSERT_OK(sd_event_add_defer(e, /* ret= */ NULL, restart_now_defer_handler, client)); + break; + case 4: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + break; + case 5: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + /* renew the lease manually */ + ASSERT_OK(sd_dhcp_client_send_renew(client)); + break; + case 6: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_RENEW); + verify_reply(client, DHCP_STATE_BOUND); + /* renew the lease by timer, triggering the corresponding timer event source now. */ + ASSERT_OK(sd_event_source_set_time_relative(client->timeout_t1, /* usec= */ 0)); + break; + case 7: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_RENEW); + verify_reply(client, DHCP_STATE_BOUND); + /* rebind the lease, triggering the corresponding timer event source now. */ + ASSERT_OK(sd_event_source_set_time_relative(client->timeout_t2, /* usec= */ 0)); + break; + case 8: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_RENEW); + verify_reply(client, DHCP_STATE_BOUND); + /* release and stop. */ + ASSERT_OK(sd_dhcp_client_stop(client)); + break; + case 9: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_STOP); + verify_reply(client, DHCP_STATE_BOUND); + break; + default: + assert_not_reached(); + } - size = sizeof(DHCPPacket); - assert_se(len > size); + return 0; +} - discover = memdup(packet, len); +TEST(basic) { + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + setup(basic_io_handler, basic_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_send_release(client, true)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); +} - 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)); +static int anonymize_io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + static unsigned count = 0; - ip_check = discover->ip.check; + count++; + log_debug("%s: count=%u", __func__, count); - discover->ip.ttl = 0; - discover->ip.check = discover->udp.len; + switch (count) { + case 1: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); - udp_check = ~dhcp_packet_checksum(&discover->ip.ttl, len - 8); - assert_se(udp_check == 0xffff); + verify_anonymized_request(request, DHCP_DISCOVER); - discover->ip.ttl = IPDEFTTL; - discover->ip.check = ip_check; + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_OFFER, &reply); - ip_check = ~dhcp_packet_checksum((uint8_t*)&discover->ip, sizeof(discover->ip)); - assert_se(ip_check == 0xffff); + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 2: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); - assert_se(discover->dhcp.xid); - assert_se(memcmp(discover->dhcp.chaddr, hw_addr.bytes, hw_addr.length) == 0); + /* REQUEST (selecting) */ + verify_anonymized_request(request, DHCP_REQUEST); + verify_request_server_address(request); - size = len - sizeof(struct iphdr) - sizeof(struct udphdr); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); - assert_se(callback_recv); - callback_recv(size, &discover->dhcp); + send_message(fd, /* raw= */ true, client, reply); + break; + } + default: + assert_not_reached(); + } - return 575; + return 0; } -int dhcp_network_bind_raw_socket( - int ifindex, - union sockaddr_union *link, - uint32_t id, - const struct hw_addr_data *_hw_addr, - const struct hw_addr_data *_bcast_addr, - uint16_t arp_type, - uint16_t port, - bool so_priority_set, - int so_priority) { +static int anonymize_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + break; + case 2: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } - if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0) - return -errno; + return 0; +} - return test_fd[0]; +TEST(anonymize) { + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + setup(anonymize_io_handler, anonymize_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_anonymize(client, true)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); } -int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) { - int fd; +static int rapid_commit_io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u", __func__, count); + + switch (count) { + case 1: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + verify_request(request, DHCP_DISCOVER); + ASSERT_TRUE(dhcp_message_has_option(request, SD_DHCP_OPTION_RAPID_COMMIT)); - fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + ASSERT_OK(dhcp_message_append_option_flag(reply, SD_DHCP_OPTION_RAPID_COMMIT)); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + default: + assert_not_reached(); + } - return fd; + return 0; } -int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, const void *packet, size_t len) { +static int rapid_commit_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + return 0; } -static int test_discover_message_verify(size_t size, struct DHCPMessage *dhcp) { - int res; +TEST(rapid_commit) { + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + setup(rapid_commit_io_handler, rapid_commit_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_rapid_commit(client, true)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); +} + +static int init_reboot_io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u", __func__, count); + + switch (count) { + case 1: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); - res = dhcp_option_parse(dhcp, size, check_options, NULL, NULL); - assert_se(res == DHCP_DISCOVER); + verify_request(request, DHCP_REQUEST); + verify_request_client_address(request, /* header= */ false); - if (verbose) - log_info(" recv DHCP Discover 0x%08x", be32toh(dhcp->xid)); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + default: + assert_not_reached(); + } return 0; } -static void test_discover_message(sd_event *e) { - sd_dhcp_client *client; - int res, r; - - if (verbose) - log_info("* %s", __func__); - - 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); - - 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_se(sd_dhcp_client_set_request_option(client, 248) >= 0); - - 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); - - test_fd[1] = safe_close(test_fd[1]); - - callback_recv = NULL; -} - -static uint8_t test_addr_acq_offer[] = { - 0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00, - 0x80, 0x11, 0xb3, 0x84, 0xc0, 0xa8, 0x02, 0x01, - 0xc0, 0xa8, 0x02, 0xbf, 0x00, 0x43, 0x00, 0x44, - 0x01, 0x34, 0x00, 0x00, 0x02, 0x01, 0x06, 0x00, - 0x6f, 0x95, 0x2f, 0x30, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x02, 0xbf, - 0xc0, 0xa8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x02, 0x36, - 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x33, 0x04, 0x00, - 0x00, 0x02, 0x58, 0x01, 0x04, 0xff, 0xff, 0xff, - 0x00, 0x2a, 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x0f, - 0x09, 0x6c, 0x61, 0x62, 0x2e, 0x69, 0x6e, 0x74, - 0x72, 0x61, 0x03, 0x04, 0xc0, 0xa8, 0x02, 0x01, - 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; +static int init_reboot_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; -static uint8_t test_addr_acq_ack[] = { - 0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00, - 0x80, 0x11, 0xb3, 0x84, 0xc0, 0xa8, 0x02, 0x01, - 0xc0, 0xa8, 0x02, 0xbf, 0x00, 0x43, 0x00, 0x44, - 0x01, 0x34, 0x00, 0x00, 0x02, 0x01, 0x06, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x02, 0xbf, - 0xc0, 0xa8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36, - 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x33, 0x04, 0x00, - 0x00, 0x02, 0x58, 0x01, 0x04, 0xff, 0xff, 0xff, - 0x00, 0x2a, 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x0f, - 0x09, 0x6c, 0x61, 0x62, 0x2e, 0x69, 0x6e, 0x74, - 0x72, 0x61, 0x03, 0x04, 0xc0, 0xa8, 0x02, 0x01, - 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); -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; + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } - assert_se(client); - assert_se(IN_SET(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE, SD_DHCP_CLIENT_EVENT_SELECTING)); + return 0; +} + +TEST(init_reboot) { + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + setup(init_reboot_io_handler, init_reboot_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_request_address(client, &client_address.in)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); +} - assert_se(sd_dhcp_client_get_lease(client, &lease) >= 0); - assert_se(lease); +static int bootp_io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + static unsigned count = 0; - 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); + count++; + log_debug("%s: count=%u", __func__, count); - 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); + switch (count) { + case 1: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); - 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); + verify_header(request); + ASSERT_TRUE(tlv_isempty(&request->options)); - if (verbose) - log_info(" DHCP address acquired"); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); - sd_event_exit(e, 0); + send_message(fd, /* raw= */ true, client, reply); + break; + } + default: + assert_not_reached(); + } return 0; } -static int test_addr_acq_recv_request(size_t size, DHCPMessage *request) { - uint16_t udp_check = 0; - uint8_t *msg_bytes = (uint8_t *)request; - int res; +static int bootp_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; - res = dhcp_option_parse(request, size, check_options, NULL, NULL); - assert_se(res == DHCP_REQUEST); - assert_se(xid == request->xid); + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); - assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END); + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } - if (verbose) - log_info(" recv DHCP Request 0x%08x", be32toh(xid)); + return 0; +} - memcpy(&test_addr_acq_ack[26], &udp_check, sizeof(udp_check)); - memcpy(&test_addr_acq_ack[32], &xid, sizeof(xid)); - memcpy(&test_addr_acq_ack[56], hw_addr.bytes, hw_addr.length); +TEST(bootp) { + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + setup(bootp_io_handler, bootp_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_bootp(client, true)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); +} + +static int ipv6_only_io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u", __func__, count); + + /* This is used multiple times. */ + switch (count) { + case 1: /* test case: before discover */ + case 2: /* test case: after offer */ + case 3: /* test case: before request */ + case 4: /* test case: before ack */ + case 6: { /* test case: after ack */ + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + verify_request(request, DHCP_DISCOVER); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_OFFER, &reply); + + ASSERT_OK(dhcp_message_append_option_be32(reply, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, usec_to_be32_sec(10 * USEC_PER_SEC))); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 5: /* test case: before ack */ + case 7: { /* test case: after ack */ + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + /* REQUEST (selecting) */ + verify_request(request, DHCP_REQUEST); + verify_request_server_address(request); + verify_request_client_address(request, /* header= */ false); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + + ASSERT_OK(dhcp_message_append_option_be32(reply, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, usec_to_be32_sec(10 * USEC_PER_SEC))); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 8: { /* test case: init-reboot */ + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + /* REQUEST (init-reboot) */ + verify_request(request, DHCP_REQUEST); + verify_request_client_address(request, /* header= */ false); - callback_recv = NULL; + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); - res = write(test_fd[1], test_addr_acq_ack, - sizeof(test_addr_acq_ack)); - assert_se(res == sizeof(test_addr_acq_ack)); + ASSERT_OK(dhcp_message_append_option_be32(reply, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, usec_to_be32_sec(10 * USEC_PER_SEC))); - if (verbose) - log_info(" send DHCP Ack"); + send_message(fd, /* raw= */ true, client, reply); + break; + } + case 9: { /* test case: rapid commit */ + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + verify_request(request, DHCP_DISCOVER); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + ASSERT_OK(dhcp_message_append_option_flag(reply, SD_DHCP_OPTION_RAPID_COMMIT)); + + ASSERT_OK(dhcp_message_append_option_be32(reply, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, usec_to_be32_sec(10 * USEC_PER_SEC))); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + default: + assert_not_reached(); + } return 0; -}; +} + +static int ipv6_only_before_discover_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + break; + case 2: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_STOP); + verify_reply(client, DHCP_STATE_REQUESTING); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +static int ipv6_only_after_offer_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; -static int test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) { - uint16_t udp_check = 0; - uint8_t *msg_bytes = (uint8_t *)discover; - int res; + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + ASSERT_OK(sd_dhcp_client_set_ipv6_connectivity(client, true)); + break; + case 2: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_STOP); + verify_reply(client, DHCP_STATE_REQUESTING); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } - res = dhcp_option_parse(discover, size, check_options, NULL, NULL); - assert_se(res == DHCP_DISCOVER); + return 0; +} - assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END); +static int ipv6_only_before_request_defer_handler(sd_event_source *s, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); - xid = discover->xid; + ASSERT_EQ(client->state, DHCP_STATE_REQUESTING); + ASSERT_EQ(client->request_attempt, 0u); - if (verbose) - log_info(" recv DHCP Discover 0x%08x", be32toh(xid)); + ASSERT_OK(sd_dhcp_client_set_ipv6_connectivity(client, true)); + ASSERT_EQ(client->state, DHCP_STATE_STOPPED); - memcpy(&test_addr_acq_offer[26], &udp_check, sizeof(udp_check)); - memcpy(&test_addr_acq_offer[32], &xid, sizeof(xid)); - memcpy(&test_addr_acq_offer[56], hw_addr.bytes, hw_addr.length); + ASSERT_OK(sd_event_source_set_enabled(s, SD_EVENT_OFF)); + return 0; +} - callback_recv = test_addr_acq_recv_request; +static int ipv6_only_before_request_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; - res = write(test_fd[1], test_addr_acq_offer, - sizeof(test_addr_acq_offer)); - assert_se(res == sizeof(test_addr_acq_offer)); + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); - if (verbose) - log_info(" sent DHCP Offer"); + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + ASSERT_OK(sd_event_add_defer(e, /* ret= */ NULL, ipv6_only_before_request_defer_handler, client)); + break; + case 2: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_STOP); + verify_reply(client, DHCP_STATE_REQUESTING); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } return 0; } -static void test_addr_acq(sd_event *e) { - sd_dhcp_client *client; - int res, r; - - if (verbose) - log_info("* %s", __func__); - - 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); - - 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_se(sd_dhcp_client_set_callback(client, test_addr_acq_acquired, e) >= 0); - - callback_recv = test_addr_acq_recv_discover; - - assert_se(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); - - assert_se(sd_dhcp_client_set_callback(client, NULL, NULL) >= 0); - assert_se(sd_dhcp_client_stop(client) >= 0); - 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, - 0xff, 0xff, 0xff, 0xff, 0x00, 0x43, 0x00, 0x44, - 0x01, 0x2c, 0x2b, 0x91, 0x02, 0x01, 0x06, 0x00, - 0x69, 0xd3, 0x79, 0x11, 0x17, 0x00, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0a, 0x46, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x50, 0x2d, 0xf4, 0x1f, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x63, 0x82, 0x53, 0x63, 0x01, 0x04, 0xff, 0x00, - 0x00, 0x00, 0x36, 0x04, 0x0a, 0x00, 0x00, 0x02, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -}; +static int ipv6_only_before_ack_post_handler(sd_event_source *s, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); -static uint8_t test_addr_bootp_reply_bootpd[] = { - 0x45, 0x00, 0x01, 0x48, 0xbe, 0xad, 0x40, 0x00, - 0x40, 0x11, 0x73, 0x43, 0xc0, 0xa8, 0x43, 0x31, - 0xc0, 0xa8, 0x43, 0x32, 0x00, 0x43, 0x00, 0x44, - 0x01, 0x34, 0x08, 0xfa, 0x02, 0x01, 0x06, 0x00, - 0x82, 0x57, 0xda, 0xf1, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x43, 0x32, - 0xc0, 0xa8, 0x43, 0x31, 0x00, 0x00, 0x00, 0x00, - 0xc2, 0x3e, 0xa5, 0x53, 0x57, 0x72, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x64, 0x65, 0x62, 0x69, 0x61, 0x6e, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x63, 0x82, 0x53, 0x63, 0x01, 0x04, 0xff, 0xff, - 0xff, 0xf0, 0x03, 0x04, 0xc0, 0xa8, 0x43, 0x31, - 0x06, 0x04, 0x0a, 0x00, 0x01, 0x01, 0x0c, 0x15, - 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x64, - 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2d, 0x74, - 0x72, 0x69, 0x78, 0x69, 0x65, 0xff, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; + if (sd_dhcp_client_is_waiting_for_ipv6_connectivity(client)) + /* Boost the time for sending DHCPREQUEST */ + ASSERT_OK(sd_event_source_set_time_relative(client->timeout_resend, /* usec= */ 0)); -static struct bootp_addr_data bootp_addr_data[] = { - { - .offer_buf = test_addr_bootp_reply, - .offer_len = sizeof(test_addr_bootp_reply), - .netmask_offset = 270, - .ip_offset = 44, - }, - { - .offer_buf = test_addr_bootp_reply_bootpd, - .offer_len = sizeof(test_addr_bootp_reply_bootpd), - .netmask_offset = 270, - .ip_offset = 44, - }, -}; + else if (client->state == DHCP_STATE_REQUESTING) { + ASSERT_EQ(client->request_attempt, 1u); -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; + /* Set IPv6 connectivity after a DHCPREQUEST sent. */ + ASSERT_OK(sd_dhcp_client_set_ipv6_connectivity(client, true)); + /* Still running */ + ASSERT_EQ(client->state, DHCP_STATE_REQUESTING); - ASSERT_NOT_NULL(client); - assert_se(IN_SET(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE, SD_DHCP_CLIENT_EVENT_SELECTING)); + } else if (client->state == DHCP_STATE_BOUND) + ASSERT_OK(sd_dhcp_client_stop(client)); - ASSERT_OK(sd_dhcp_client_get_lease(client, &lease)); - ASSERT_NOT_NULL(lease); + return 0; +} - 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); +static int ipv6_only_before_ack_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); - 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); + static unsigned count = 0; - if (verbose) - log_info(" BOOTP address acquired"); + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); - sd_event_exit(e, 0); + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + ASSERT_OK(sd_event_add_post(e, /* ret= */ NULL, ipv6_only_before_ack_post_handler, client)); + break; + case 2: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + break; + case 3: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_STOP); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } return 0; } -static int test_bootp_recv_request(size_t size, DHCPMessage *request) { - uint16_t udp_check = 0; - size_t res; +static int ipv6_only_after_ack_defer_handler(sd_event_source *s, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); - xid = request->xid; + ASSERT_EQ(client->state, DHCP_STATE_REQUESTING); + /* Boost the time for sending DHCPREQUEST */ + ASSERT_OK(sd_event_source_set_time_relative(client->timeout_resend, /* usec= */ 0)); + return 0; +} - if (verbose) - log_info(" recv BOOTP Request 0x%08x", be32toh(xid)); +static int ipv6_only_after_ack_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; - callback_recv = NULL; + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); - memcpy(&bootp_test_context->offer_buf[26], &udp_check, sizeof(udp_check)); - memcpy(&bootp_test_context->offer_buf[32], &xid, sizeof(xid)); - memcpy(&bootp_test_context->offer_buf[56], hw_addr.bytes, hw_addr.length); + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + ASSERT_OK(sd_event_add_defer(e, /* ret= */ NULL, ipv6_only_after_ack_defer_handler, client)); + break; + case 2: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_dhcp_client_set_ipv6_connectivity(client, true)); + /* still running */ + ASSERT_EQ(client->state, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } - res = write(test_fd[1], bootp_test_context->offer_buf, - bootp_test_context->offer_len); - ASSERT_EQ(res, bootp_test_context->offer_len); + return 0; +} + +static int ipv6_only_init_reboot_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; - if (verbose) - log_info(" sent BOOTP Reply"); + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } return 0; -}; +} + +static int ipv6_only_rapid_commit_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; -static void test_acquire_bootp(sd_event *e) { - sd_dhcp_client *client = NULL; - int res; + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); - if (verbose) - log_info("* %s", __func__); + switch (count) { + case 1: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } - ASSERT_OK(sd_dhcp_client_new(&client, false)); - ASSERT_NOT_NULL(client); + return 0; +} - ASSERT_OK(sd_dhcp_client_attach_event(client, e, 0)); +TEST(ipv6_only) { + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; - ASSERT_OK(sd_dhcp_client_set_bootp(client, true)); + /* case 1: IPv6 connectivity is acquired before starting the client. */ + setup(ipv6_only_io_handler, ipv6_only_before_discover_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + ASSERT_OK(sd_dhcp_client_set_ipv6_connectivity(client, true)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); - 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)); + client = sd_dhcp_client_unref(client); - ASSERT_OK(sd_dhcp_client_set_callback(client, test_bootp_acquired, e)); + /* case 2: IPv6 connectivity is acquired after DHCPOFFER received. */ + setup(ipv6_only_io_handler, ipv6_only_after_offer_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); - callback_recv = test_bootp_recv_request; + client = sd_dhcp_client_unref(client); + + /* case 3: IPv6 connectivity is acquired before sending DHCPREQUEST. */ + setup(ipv6_only_io_handler, ipv6_only_before_request_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); - ASSERT_OK(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, - 30 * USEC_PER_SEC, 0, - NULL, INT_TO_PTR(-ETIMEDOUT))); + client = sd_dhcp_client_unref(client); - res = sd_dhcp_client_start(client); - assert_se(IN_SET(res, 0, -EINPROGRESS)); + /* case 4: IPv6 connectivity is acquired before DHCPACK received. */ + setup(ipv6_only_io_handler, ipv6_only_before_ack_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); + + client = sd_dhcp_client_unref(client); - ASSERT_OK(sd_event_loop(e)); + /* case 5: IPv6 connectivity is acquired after DHCPACK received. */ + setup(ipv6_only_io_handler, ipv6_only_after_ack_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); - 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); - test_fd[1] = safe_close(test_fd[1]); + /* case 6: IPv6 connectivity is acquired on reboot. */ + setup(ipv6_only_io_handler, ipv6_only_init_reboot_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + ASSERT_OK(sd_dhcp_client_set_request_address(client, &client_address.in)); + ASSERT_OK(sd_dhcp_client_set_ipv6_connectivity(client, true)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); - callback_recv = NULL; - xid = 0; + client = sd_dhcp_client_unref(client); + + /* case 7: IPv6 connectivity is acquired on rapid commit. */ + setup(ipv6_only_io_handler, ipv6_only_rapid_commit_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_rapid_commit(client, true)); + ASSERT_OK(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)); + ASSERT_OK(sd_dhcp_client_set_ipv6_connectivity(client, true)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); } -int main(int argc, char *argv[]) { - _cleanup_(sd_event_unrefp) sd_event *e; +static int discover_attempt_io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_client *client = ASSERT_PTR(userdata); + static unsigned count = 0; - assert_se(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", 1) >= 0); + count++; + log_debug("%s: count=%u", __func__, count); - test_setup_logging(LOG_DEBUG); + switch (count) { + case 1 ... 5: /* test case: no reply */ + case 6 ... 9: /* test case: reset on bound (1st cycle) */ + case 12 ... 15: { /* test case: reset on bound (2nd cycle) */ + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); - assert_se(sd_event_new(&e) >= 0); + verify_request(request, DHCP_DISCOVER); + + /* Boost the retransmit timer. */ + ASSERT_OK(sd_event_source_set_time_relative(client->timeout_resend, /* usec= */ 0)); + break; + } + case 10: + case 16: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); - test_request_basic(e); - test_request_anonymize(e); - test_checksum(); - test_dhcp_identifier_set_iaid(); + verify_request(request, DHCP_DISCOVER); - test_discover_message(e); - test_addr_acq(e); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_OFFER, &reply); - FOREACH_ELEMENT(i, bootp_addr_data) { - sd_event_unref(e); - ASSERT_OK(sd_event_new(&e)); - bootp_test_context = i; - test_acquire_bootp(e); + send_message(fd, /* raw= */ true, client, reply); + break; } + case 11: + case 17: { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *request = NULL; + receive_message(fd, /* raw= */ true, /* check_xid= */ true, client, &request); + + /* REQUEST (selecting) */ + verify_request(request, DHCP_REQUEST); + verify_request_server_address(request); + verify_request_client_address(request, /* header= */ false); -#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 + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *reply = NULL; + create_reply(client, request, DHCP_ACK, &reply); + + send_message(fd, /* raw= */ true, client, reply); + break; + } + default: + assert_not_reached(); + } return 0; } + +static int discover_attempt_no_reply_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1 ... 3: /* The event triggered after sending the 3rd, 4th, 5th DISCOVER message. */ + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE); + ASSERT_EQ(client->state, DHCP_STATE_SELECTING); + ASSERT_EQ(client->discover_attempt, count + 2u); + break; + case 4: + ASSERT_EQ(event, -ETIMEDOUT); + ASSERT_EQ(client->state, DHCP_STATE_SELECTING); + ASSERT_EQ(client->discover_attempt, 5u); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +static int discover_attempt_reset_on_bound_client_handler(sd_dhcp_client *client, int event, void *userdata) { + sd_event *e = ASSERT_PTR(userdata); + static unsigned count = 0; + + count++; + log_debug("%s: count=%u, event=%i", __func__, count, event); + + switch (count) { + case 1 ... 3: /* The event triggered after sending the 3rd, 4th, 5th DISCOVER message. */ + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE); + ASSERT_EQ(client->state, DHCP_STATE_SELECTING); + ASSERT_EQ(client->discover_attempt, count + 2u); + break; + case 4: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + ASSERT_EQ(client->discover_attempt, 5u); + break; + case 5: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_EQ(client->discover_attempt, 0u); + /* expire the lease now. */ + ASSERT_OK(sd_event_source_set_time_relative(client->timeout_expire, 0)); + break; + case 6: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_EXPIRED); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_EQ(client->discover_attempt, 0u); + break; + case 7 ... 9: /* The event triggered after sending the 3rd, 4th, 5th DISCOVER message. */ + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE); + ASSERT_EQ(client->state, DHCP_STATE_SELECTING); + ASSERT_EQ(client->discover_attempt, count - 4u); + break; + case 10: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_SELECTING); + verify_reply(client, DHCP_STATE_SELECTING); + ASSERT_EQ(client->discover_attempt, 5u); + break; + case 11: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_EQ(client->discover_attempt, 0u); + ASSERT_OK(sd_dhcp_client_stop(client)); + break; + case 12: + ASSERT_EQ(event, SD_DHCP_CLIENT_EVENT_STOP); + verify_reply(client, DHCP_STATE_BOUND); + ASSERT_EQ(client->discover_attempt, 0u); + ASSERT_OK(sd_event_exit(e, 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +TEST(discover_attempt) { + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + + /* case 1: no reply */ + setup(discover_attempt_io_handler, discover_attempt_no_reply_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_max_attempts(client, 5)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); + + client = sd_dhcp_client_unref(client); + + /* case 2: attempt is reset on bound */ + setup(discover_attempt_io_handler, discover_attempt_reset_on_bound_client_handler, &client); + ASSERT_OK(sd_dhcp_client_set_max_attempts(client, 5)); + ASSERT_OK(sd_dhcp_client_start(client)); + ASSERT_OK(sd_event_loop(sd_dhcp_client_get_event(client))); +} + +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-duid.c b/src/libsystemd-network/test-dhcp-duid.c new file mode 100644 index 0000000000000..363295782d0b5 --- /dev/null +++ b/src/libsystemd-network/test-dhcp-duid.c @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dhcp-duid-internal.h" +#include "ether-addr-util.h" +#include "tests.h" + +TEST(dhcp_identifier_set_iaid) { + uint32_t iaid_legacy; + be32_t iaid; + + static struct hw_addr_data hw_addr = { + .length = ETH_ALEN, + .ether = {{ 'A', 'B', 'C', '1', '2', '3' }}, + }; + + ASSERT_OK(dhcp_identifier_set_iaid(/* dev= */ NULL, &hw_addr, /* legacy_unstable_byteorder= */ true, &iaid_legacy)); + ASSERT_OK(dhcp_identifier_set_iaid(/* dev= */ NULL, &hw_addr, /* legacy_unstable_byteorder= */ false, &iaid)); + + /* 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_EQ(iaid, iaid_legacy); +#else + ASSERT_EQ(iaid, bswap_32(iaid_legacy)); +#endif +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd-network/test-dhcp-message.c b/src/libsystemd-network/test-dhcp-message.c new file mode 100644 index 0000000000000..9975a4820a8f9 --- /dev/null +++ b/src/libsystemd-network/test-dhcp-message.c @@ -0,0 +1,944 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-json.h" + +#include "alloc-util.h" +#include "dhcp-client-id-internal.h" +#include "dhcp-message.h" +#include "dhcp-message-dump.h" +#include "dhcp-route.h" +#include "dns-packet.h" +#include "dns-resolver-internal.h" +#include "ether-addr-util.h" +#include "fd-util.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "ip-util.h" +#include "random-util.h" +#include "set.h" +#include "socket-util.h" +#include "strv.h" +#include "tests.h" + +static void verify_header(sd_dhcp_message *m, uint32_t xid, const struct hw_addr_data *hw_addr) { + ASSERT_EQ(be32toh(m->header.xid), xid); + + ASSERT_FALSE(dhcp_message_has_broadcast_flag(m)); + dhcp_message_set_broadcast_flag(m, true); + ASSERT_TRUE(dhcp_message_has_broadcast_flag(m)); + dhcp_message_set_broadcast_flag(m, false); + ASSERT_FALSE(dhcp_message_has_broadcast_flag(m)); + + struct hw_addr_data a; + ASSERT_OK(dhcp_message_get_hw_addr(m, &a)); + ASSERT_TRUE(hw_addr_equal(&a, hw_addr)); +} + +static void verify_flag(sd_dhcp_message *m) { + ASSERT_TRUE(dhcp_message_has_option(m, SD_DHCP_OPTION_RAPID_COMMIT)); + ASSERT_OK(dhcp_message_get_option_flag(m, SD_DHCP_OPTION_RAPID_COMMIT)); + ASSERT_ERROR(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_RAPID_COMMIT, NULL), ENODATA); /* size mismatch */ +} + +static void verify_u8(sd_dhcp_message *m, uint8_t expected) { + uint8_t u; + ASSERT_OK(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, &u)); + ASSERT_EQ(u, expected); +} + +static void verify_u16(sd_dhcp_message *m, uint16_t expected) { + uint16_t u; + ASSERT_OK(dhcp_message_get_option_u16(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, &u)); + ASSERT_EQ(u, expected); +} + +static void verify_sec(sd_dhcp_message *m, usec_t expected) { + usec_t t; + ASSERT_OK(dhcp_message_get_option_sec(m, SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, /* max_as_infinity= */ false, &t)); + ASSERT_EQ(t, expected); + ASSERT_OK(dhcp_message_get_option_sec(m, SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, /* max_as_infinity= */ true, &t)); + ASSERT_EQ(t, expected); + ASSERT_OK(dhcp_message_get_option_sec(m, SD_DHCP_OPTION_RENEWAL_TIME, /* max_as_infinity= */ false, &t)); + ASSERT_EQ(t, UINT32_MAX * USEC_PER_SEC); + ASSERT_OK(dhcp_message_get_option_sec(m, SD_DHCP_OPTION_RENEWAL_TIME, /* max_as_infinity= */ true, &t)); + ASSERT_EQ(t, USEC_INFINITY); +} + +static void verify_address(sd_dhcp_message *m, const struct in_addr *expected) { + struct in_addr a; + ASSERT_OK(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &a.s_addr)); + ASSERT_EQ(a.s_addr, expected->s_addr); + ASSERT_OK(dhcp_message_get_option_address(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &a)); + ASSERT_EQ(a.s_addr, expected->s_addr); +} + +static void verify_addresses( + sd_dhcp_message *m, + size_t n_ntp, const struct in_addr *ntp, + size_t n_sip, const struct in_addr *sip) { + + struct in_addr a; + ASSERT_OK(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_NTP_SERVER, &a.s_addr)); + ASSERT_EQ(a.s_addr, ntp->s_addr); + ASSERT_OK(dhcp_message_get_option_address(m, SD_DHCP_OPTION_NTP_SERVER, &a)); + ASSERT_EQ(a.s_addr, ntp->s_addr); + + size_t n; + _cleanup_free_ struct in_addr *addrs = NULL; + ASSERT_OK(dhcp_message_get_option_addresses(m, SD_DHCP_OPTION_NTP_SERVER, &n, &addrs)); + ASSERT_EQ(n, n_ntp); + ASSERT_EQ(memcmp(addrs, ntp, sizeof(struct in_addr) * n), 0); + + ASSERT_ERROR(dhcp_message_get_option_be32(m, SD_DHCP_OPTION_SIP_SERVER, NULL), ENODATA); + ASSERT_ERROR(dhcp_message_get_option_address(m, SD_DHCP_OPTION_SIP_SERVER, NULL), ENODATA); + + addrs = mfree(addrs); + ASSERT_OK(dhcp_message_get_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, &n, &addrs)); + ASSERT_EQ(n, n_sip); + ASSERT_EQ(memcmp(addrs, sip, sizeof(struct in_addr) * n), 0); +} + +static void verify_string(sd_dhcp_message *m, const char *expected) { + _cleanup_free_ char *s = NULL; + ASSERT_OK(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, &s)); + ASSERT_STREQ(s, expected); +} + +static void verify_multiple_strings(sd_dhcp_message *m, char * const *expected) { + _cleanup_free_ char *s = NULL; + ASSERT_OK(dhcp_message_get_option_string(m, SD_DHCP_OPTION_ROOT_PATH, &s)); + _cleanup_free_ char *joined = ASSERT_NOT_NULL(strv_join(expected, /* separator= */ "")); + ASSERT_STREQ(s, joined); +} + +static void verify_routes(sd_dhcp_message *m, size_t n_expected, const sd_dhcp_route *expected) { + uint8_t code; + FOREACH_ARGUMENT(code, + SD_DHCP_OPTION_STATIC_ROUTE, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE) { + + _cleanup_free_ sd_dhcp_route *routes = NULL; + size_t n; + ASSERT_OK(dhcp_message_get_option_routes(m, code, &n, &routes)); + ASSERT_EQ(n, n_expected); + for (size_t i = 0; i < n; i++) { + ASSERT_EQ(routes[i].dst_addr.s_addr, expected[i].dst_addr.s_addr); + ASSERT_EQ(routes[i].gw_addr.s_addr, expected[i].gw_addr.s_addr); + ASSERT_EQ(routes[i].dst_prefixlen, expected[i].dst_prefixlen); + } + } +} + +static void verify_6rd( + sd_dhcp_message *m, + uint8_t expected_ipv4masklen, + uint8_t expected_prefixlen, + const struct in6_addr *expected_prefix, + size_t expected_n_br_addresses, + const struct in_addr *expected_br_addresses) { + + uint8_t ipv4masklen, prefixlen; + struct in6_addr prefix; + size_t n_br_addresses; + _cleanup_free_ struct in_addr *br_addresses = NULL; + + ASSERT_OK(dhcp_message_get_option_6rd(m, NULL, NULL, NULL, NULL, NULL)); + ASSERT_OK(dhcp_message_get_option_6rd(m, &ipv4masklen, &prefixlen, &prefix, &n_br_addresses, &br_addresses)); + ASSERT_EQ(ipv4masklen, expected_ipv4masklen); + ASSERT_EQ(prefixlen, expected_prefixlen); + ASSERT_TRUE(in6_addr_equal(&prefix, expected_prefix)); + ASSERT_EQ(n_br_addresses, expected_n_br_addresses); + ASSERT_EQ(memcmp(br_addresses, expected_br_addresses, sizeof(struct in_addr) * n_br_addresses), 0); +} + +static void verify_client_id(sd_dhcp_message *m, const sd_dhcp_client_id *expected) { + sd_dhcp_client_id id = {}; + ASSERT_OK(dhcp_message_get_option_client_id(m, &id)); + ASSERT_EQ(client_id_compare_func(&id, expected), 0); +} + +static void verify_prl(sd_dhcp_message *m, Set *expected) { + _cleanup_set_free_ Set *set = NULL; + ASSERT_OK(dhcp_message_get_option_parameter_request_list(m, &set)); + ASSERT_TRUE(set_equal(set, expected)); +} + +static void verify_hostname(sd_dhcp_message *m, const char *expected) { + _cleanup_free_ char *s = NULL; + ASSERT_OK(dhcp_message_get_option_hostname(m, &s)); + ASSERT_STREQ(s, expected); +} + +static void verify_sub_tlv(sd_dhcp_message *m, TLV *expected) { + _cleanup_(tlv_unrefp) TLV *tlv = NULL; + ASSERT_OK(dhcp_message_get_option_sub_tlv( + m, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, + TLV_DHCP4_SUBOPTION, + &tlv)); + + _cleanup_(iovec_done) struct iovec iov = {}, iov_expected = {}; + ASSERT_OK(tlv_build(tlv, &iov)); + ASSERT_OK(tlv_build(expected, &iov_expected)); + ASSERT_TRUE(iovec_equal(&iov, &iov_expected)); +} + +static void verify_length_prefixed_data(sd_dhcp_message *m, const struct iovec_wrapper *expected) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + ASSERT_OK(dhcp_message_get_option_length_prefixed_data(m, SD_DHCP_OPTION_USER_CLASS, 1, &iovw)); + ASSERT_TRUE(iovw_equal(&iovw, expected)); +} + +static void verify_send_udp(sd_dhcp_message *message, uint32_t xid, const struct hw_addr_data *hw_addr) { + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); + + ASSERT_OK(dhcp_message_send_udp( + message, + socket_fd[0], + /* src_addr= */ htobe32(0xC0000201), /* 192.0.2.1 */ + /* dst_addr= */ htobe32(0xC0000202), /* 192.0.2.2 */ + /* dst_port= */ DHCP_PORT_CLIENT)); + + ssize_t buflen = ASSERT_OK_POSITIVE(next_datagram_size_fd(socket_fd[1])); + _cleanup_free_ void *buf = ASSERT_NOT_NULL(malloc0(buflen)); + + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + }; + ssize_t len = ASSERT_OK_ERRNO(recvmsg_safe(socket_fd[1], &msg, MSG_DONTWAIT)); + ASSERT_EQ((size_t) len, dhcp_message_payload_size(message)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_parse( + &IOVEC_MAKE(buf, len), + BOOTREQUEST, + &xid, + ARPHRD_ETHER, + hw_addr, + &m)); + + /* Verify the received message. */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}, iovw2 = {}; + ASSERT_OK(dhcp_message_build(message, &iovw)); + ASSERT_OK(dhcp_message_build(m, &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); +} + +static void verify_send_raw(sd_dhcp_message *message, uint32_t xid, const struct hw_addr_data *hw_addr) { + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); + + ASSERT_OK(dhcp_message_send_raw( + message, + socket_fd[0], + /* ifindex= */ 42, + /* src_addr= */ htobe32(0xC0000201), /* 192.0.2.1 */ + /* src_port= */ DHCP_PORT_SERVER, + /* dst_hw_addr= */ &(struct hw_addr_data) { + .length = 6, + .ether = {{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }}, + }, + /* dst_addr= */ htobe32(0xC0000202), /* 192.0.2.2 */ + /* dst_port= */ DHCP_PORT_CLIENT, + /* ip_service_type= */ IPTOS_CLASS_CS6)); + + ssize_t buflen = ASSERT_OK_POSITIVE(next_datagram_size_fd(socket_fd[1])); + _cleanup_free_ void *buf = ASSERT_NOT_NULL(malloc0(buflen)); + + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + }; + ssize_t len = ASSERT_OK_ERRNO(recvmsg_safe(socket_fd[1], &msg, MSG_DONTWAIT)); + ASSERT_EQ((size_t) len, dhcp_message_packet_size(message)); + + struct iovec payload; + ASSERT_OK(udp_packet_verify( + &IOVEC_MAKE(buf, len), + DHCP_PORT_CLIENT, + /* checksum= */ true, + &payload)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_parse( + &payload, + BOOTREQUEST, + &xid, + ARPHRD_ETHER, + hw_addr, + &m)); + + /* Verify the received message. */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}, iovw2 = {}; + ASSERT_OK(dhcp_message_build(message, &iovw)); + ASSERT_OK(dhcp_message_build(m, &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); +} + +TEST(dhcp_message) { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + + ASSERT_OK(dhcp_message_new(&m)); + ASSERT_NOT_NULL(m); + + uint32_t xid = random_u32(); + + struct hw_addr_data hw_addr = { + .length = ETH_ALEN, + .ether = {{ 'A', 'B', 'C', '1', '2', '3' }}, + }; + + usec_t lease_time = USEC_PER_DAY; + + /* 192.0.2.42 */ + struct in_addr addr = { .s_addr = htobe32(0xC000022a) }; + + /* 192.0.2.1 - 4 */ + struct in_addr ntp[4] = { + { .s_addr = htobe32(0xC0000201) }, + { .s_addr = htobe32(0xC0000202) }, + { .s_addr = htobe32(0xC0000203) }, + { .s_addr = htobe32(0xC0000204) }, + }; + + /* 192.0.2.17 - 20 */ + struct in_addr sip[4] = { + { .s_addr = htobe32(0xC0000211) }, + { .s_addr = htobe32(0xC0000212) }, + { .s_addr = htobe32(0xC0000213) }, + { .s_addr = htobe32(0xC0000214) }, + }; + + struct sd_dhcp_route routes[3] = { + { /* class A: 10.0.0.0/8 -> 192.0.2.33 */ + .dst_addr = { .s_addr = htobe32(0x0A000000) }, + .gw_addr = { .s_addr = htobe32(0xC0000221) }, + .dst_prefixlen = 8, + }, + { /* class B: 172.16.0.0/16 -> 192.0.2.34 */ + .dst_addr = { .s_addr = htobe32(0xAC100000) }, + .gw_addr = { .s_addr = htobe32(0xC0000222) }, + .dst_prefixlen = 16, + }, + { /* class C: 192.168.0.0/24 -> 192.0.2.35 */ + .dst_addr = { .s_addr = htobe32(0xC0A80000) }, + .gw_addr = { .s_addr = htobe32(0xC0000223) }, + .dst_prefixlen = 24, + }, + }; + + uint8_t sixrd_ipv4masklen = 24; + uint8_t sixrd_prefixlen = 64; + struct in6_addr sixrd_prefix = { + .s6_addr = { 0x20, 0x01, 0x0d, 0xb8, }, + }; + struct in_addr sixrd_br_addresses[3] = { + { .s_addr = htobe32(0xC0000231) }, + { .s_addr = htobe32(0xC0000232) }, + { .s_addr = htobe32(0xC0000233) }, + }; + + sd_dhcp_client_id id = { + .raw = { 1, 3, 3, 3, 3, 3, 3, }, + .size = 7, + }; + + _cleanup_set_free_ Set *prl = NULL; + for (uint8_t i = SD_DHCP_OPTION_PRIVATE_BASE; i <= SD_DHCP_OPTION_PRIVATE_LAST; i++) + ASSERT_OK(set_ensure_put(&prl, /* hash_ops= */ NULL, UINT_TO_PTR(i))); + + const char *hostname = "test-node.example.com"; + const char *vendor_class = "hogehoge"; + char **root_path = STRV_MAKE("/path/to/root", "/hogehoge/foofoo"); + + _cleanup_(iovw_done_free) struct iovec_wrapper user_class = {}, user_class_1 = {}, user_class_2 = {}; + FOREACH_STRING(s, "hoge", "foo", "bar") { + ASSERT_OK(iovw_extend(&user_class, s, strlen(s))); + ASSERT_OK(iovw_extend(&user_class_1, s, strlen(s))); + } + FOREACH_STRING(s, "aaa", "bbb", "ccc") { + ASSERT_OK(iovw_extend(&user_class, s, strlen(s))); + ASSERT_OK(iovw_extend(&user_class_2, s, strlen(s))); + } + + _cleanup_(tlv_done) TLV vendor = TLV_INIT(TLV_DHCP4_SUBOPTION); + for (unsigned i = 0; i < 3; i++) { + uint8_t buf[255]; + memset(buf, 42 + i, sizeof(buf)); + ASSERT_OK(tlv_append(&vendor, i + 1, 255, buf)); + } + + ASSERT_OK(dhcp_message_init_header( + m, + BOOTREQUEST, + xid, + ARPHRD_ETHER, + &hw_addr)); + + /* header */ + verify_header(m, xid, &hw_addr); + + ASSERT_ERROR(dhcp_message_append_option(m, SD_DHCP_OPTION_PAD, 0, NULL), EINVAL); + ASSERT_ERROR(dhcp_message_append_option(m, SD_DHCP_OPTION_END, 0, NULL), EINVAL); + + /* multiple strings */ + STRV_FOREACH(s, root_path) + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_ROOT_PATH, strlen(*s), *s)); + verify_multiple_strings(m, root_path); + + /* flag */ + ASSERT_ERROR(dhcp_message_get_option_flag(m, SD_DHCP_OPTION_RAPID_COMMIT), ENODATA); + ASSERT_OK(dhcp_message_append_option_flag(m, SD_DHCP_OPTION_RAPID_COMMIT)); + ASSERT_ERROR(dhcp_message_append_option_flag(m, SD_DHCP_OPTION_RAPID_COMMIT), EEXIST); + verify_flag(m); + + /* u8 */ + ASSERT_ERROR(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, NULL), ENODATA); + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_DISCOVER)); + ASSERT_ERROR(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_REQUEST), EEXIST); + verify_u8(m, DHCP_DISCOVER); + + /* u16 */ + ASSERT_OK(dhcp_message_append_option_u16(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, 512)); + ASSERT_ERROR(dhcp_message_append_option_u16(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, 1024), EEXIST); + ASSERT_ERROR(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, 32), EEXIST); + verify_u16(m, 512); + + /* sec */ + ASSERT_OK(dhcp_message_append_option_sec(m, SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, lease_time)); + ASSERT_OK(dhcp_message_append_option_sec(m, SD_DHCP_OPTION_RENEWAL_TIME, USEC_INFINITY)); + verify_sec(m, lease_time); + + /* address */ + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr.s_addr)); + ASSERT_ERROR(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, addr.s_addr), EEXIST); + ASSERT_ERROR(dhcp_message_append_option_address(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &addr), EEXIST); + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &addr)); + verify_address(m, &addr); + + /* multiple addresses */ + ASSERT_OK(dhcp_message_append_option_address(m, SD_DHCP_OPTION_NTP_SERVER, &ntp[0])); + ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_NTP_SERVER, ELEMENTSOF(ntp) - 1, ntp + 1)); + ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, ELEMENTSOF(sip), sip)); + ASSERT_ERROR(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, ELEMENTSOF(sip), sip), EEXIST); + ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_SIP_SERVER, 0, NULL)); + verify_addresses(m, ELEMENTSOF(ntp), ntp, ELEMENTSOF(sip), sip); + + /* string */ + ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), ENODATA); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, 0, NULL)); + ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), ENODATA); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, 1, "\0")); + ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), ENODATA); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, 9, "hoge\0hoge")); + ASSERT_ERROR(dhcp_message_get_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, NULL), EBADMSG); + ASSERT_ERROR(dhcp_message_append_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, vendor_class), EEXIST); + dhcp_message_remove_option(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER); + ASSERT_OK(dhcp_message_append_option_string(m, SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, vendor_class)); + verify_string(m, vendor_class); + + /* routes */ + ASSERT_OK(dhcp_message_append_option_routes(m, SD_DHCP_OPTION_STATIC_ROUTE, ELEMENTSOF(routes), routes)); + ASSERT_OK(dhcp_message_append_option_routes(m, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, ELEMENTSOF(routes), routes)); + ASSERT_OK(dhcp_message_append_option_routes(m, SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, ELEMENTSOF(routes), routes)); + verify_routes(m, ELEMENTSOF(routes), routes); + + /* 6rd */ + ASSERT_ERROR(dhcp_message_append_option_6rd(m, 33, sixrd_prefixlen, &sixrd_prefix, 1, sixrd_br_addresses), EINVAL); + ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, 127, &sixrd_prefix, 1, sixrd_br_addresses), EINVAL); + ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, 0, sixrd_br_addresses), EINVAL); + ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, SIZE_MAX, sixrd_br_addresses), ENOBUFS); + ASSERT_OK(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, 1, sixrd_br_addresses)); + ASSERT_ERROR(dhcp_message_append_option_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, 1, sixrd_br_addresses), EEXIST); + ASSERT_OK(dhcp_message_append_option_addresses(m, SD_DHCP_OPTION_6RD, ELEMENTSOF(sixrd_br_addresses) - 1, sixrd_br_addresses + 1)); + verify_6rd(m, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, ELEMENTSOF(sixrd_br_addresses), sixrd_br_addresses); + + /* client ID */ + ASSERT_OK(dhcp_message_append_option_client_id(m, &id)); + verify_client_id(m, &id); + + /* parameter request list */ + ASSERT_OK(dhcp_message_append_option_parameter_request_list(m, prl)); + ASSERT_OK(dhcp_message_append_option_parameter_request_list(m, prl)); + verify_prl(m, prl); + + /* hostname */ + ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, "hogehoge")); + ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST); + dhcp_message_remove_option(m, SD_DHCP_OPTION_FQDN); + ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST); + dhcp_message_remove_option(m, SD_DHCP_OPTION_HOST_NAME); + ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, "hogehoge.example.com")); + ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST); + dhcp_message_remove_option(m, SD_DHCP_OPTION_HOST_NAME); + ASSERT_ERROR(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname), EEXIST); + dhcp_message_remove_option(m, SD_DHCP_OPTION_FQDN); + ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, hostname)); + verify_hostname(m, hostname); + + /* vendor specific */ + ASSERT_OK(dhcp_message_append_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, &vendor)); + ASSERT_ERROR(dhcp_message_append_option_sub_tlv(m, SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, &vendor), EEXIST); + verify_sub_tlv(m, &vendor); + + /* user class */ + ASSERT_OK(dhcp_message_append_option_length_prefixed_data(m, SD_DHCP_OPTION_USER_CLASS, 1, &user_class_1)); + ASSERT_OK(dhcp_message_append_option_length_prefixed_data(m, SD_DHCP_OPTION_USER_CLASS, 1, &user_class_2)); + verify_length_prefixed_data(m, &user_class); + + /* build and parse */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + ASSERT_OK(dhcp_message_build(m, &iovw)); + + _cleanup_(iovec_done) struct iovec joined = {}; + ASSERT_OK(iovw_concat(&iovw, &joined)); + ASSERT_EQ(joined.iov_len, dhcp_message_payload_size(m)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m2 = NULL; + ASSERT_OK(dhcp_message_parse( + &joined, + BOOTREQUEST, + &xid, + ARPHRD_ETHER, + &hw_addr, + &m2)); + + ASSERT_EQ(memcmp(&m2->header, &m->header, sizeof(m->header)), 0); + + /* verify parsed message */ + verify_header(m2, xid, &hw_addr); + verify_multiple_strings(m2, root_path); + verify_flag(m2); + verify_u8(m2, DHCP_DISCOVER); + verify_u16(m2, 512); + verify_sec(m2, lease_time); + verify_address(m2, &addr); + verify_addresses(m2, ELEMENTSOF(ntp), ntp, ELEMENTSOF(sip), sip); + verify_string(m2, vendor_class); + verify_routes(m2, ELEMENTSOF(routes), routes); + verify_6rd(m2, sixrd_ipv4masklen, sixrd_prefixlen, &sixrd_prefix, ELEMENTSOF(sixrd_br_addresses), sixrd_br_addresses); + verify_client_id(m2, &id); + verify_prl(m2, prl); + verify_hostname(m2, hostname); + verify_sub_tlv(m2, &vendor); + verify_length_prefixed_data(m2, &user_class); + + /* build again, and verify the packet */ + _cleanup_(iovw_done_free) struct iovec_wrapper iovw2 = {}; + ASSERT_OK(dhcp_message_build(m2, &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + /* json */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + ASSERT_OK(dhcp_message_build_json(m, &v)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m3 = NULL; + ASSERT_OK(dhcp_message_parse_json(v, &m3)); + + _cleanup_(iovw_done_free) struct iovec_wrapper iovw3 = {}; + ASSERT_OK(dhcp_message_build(m3, &iovw3)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw3)); + + /* send */ + verify_send_udp(m, xid, &hw_addr); + verify_send_raw(m, xid, &hw_addr); + + /* dump */ + ASSERT_OK(dump_dhcp_message(m, /* args= */ NULL, DUMP_DHCP_MESSAGE_LEGEND | DUMP_DHCP_MESSAGE_FULL)); +} + +static void test_domains_one(size_t len, const uint8_t *data, char * const *expected) { + _cleanup_strv_free_ char **strv = NULL; + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len, data)); + ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, &strv)); + ASSERT_TRUE(strv_equal(strv, expected)); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH); + strv = strv_free(strv); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len / 2, data)); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len - len / 2, data + len / 2)); + ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, &strv)); + ASSERT_TRUE(strv_equal(strv, expected)); + + strv = strv_free(strv); + + _cleanup_free_ uint8_t *sip = new(uint8_t, len + 1); + sip[0] = 0; + memcpy(sip + 1, data, len); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1, sip)); + ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, &strv)); + ASSERT_TRUE(strv_equal(strv, expected)); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_SIP_SERVER); + strv = strv_free(strv); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, (len + 1) / 2, sip)); + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1 - (len + 1) / 2, sip + (len + 1) / 2)); + ASSERT_OK(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, &strv)); + ASSERT_TRUE(strv_equal(strv, expected)); + + ASSERT_OK(dump_dhcp_message(m, STRV_MAKE("119", "120"), DUMP_DHCP_MESSAGE_LEGEND | DUMP_DHCP_MESSAGE_FULL)); +} + +static void test_domains_fail(size_t len, const uint8_t *data) { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH, len, data)); + ASSERT_ERROR(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_DOMAIN_SEARCH, /* ret= */ NULL), EBADMSG); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_DOMAIN_SEARCH); + + _cleanup_free_ uint8_t *sip = new(uint8_t, len + 1); + sip[0] = 0; + memcpy(sip + 1, data, len); + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_SIP_SERVER, len + 1, sip)); + ASSERT_ERROR(dhcp_message_get_option_domains(m, SD_DHCP_OPTION_SIP_SERVER, /* ret= */ NULL), EBADMSG); +} + +TEST(domains) { + /* simple */ + static uint8_t simple[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + 4, 'h', 'o', 'g', 'e', + 3, 'f', 'o', 'o', + 0, + }; + test_domains_one(ELEMENTSOF(simple), simple, + STRV_MAKE("example.com", "hoge.foo")); + + /* compressed */ + static uint8_t compressed[] = { + 4, 'h', 'o', 'g', 'e', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + 3, 'f', 'o', 'o', + 0xc0, 5, + 3, 'b', 'a', 'r', + 0xc0, 18, + 3, 'b', 'a', 'z', + 0xc0, 0, + }; + test_domains_one(ELEMENTSOF(compressed), compressed, + STRV_MAKE("hoge.example.com", "foo.example.com", "bar.foo.example.com", "baz.hoge.example.com")); + + /* invalid pointer */ + static uint8_t invalid[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + 3, 'f', 'o', 'o', + 0xc0, 0xff, + }; + test_domains_fail(ELEMENTSOF(invalid), invalid); + + /* forward pointer */ + static uint8_t forward[] = { + 4, 'h', 'o', 'g', 'e', + 0xc0, 11, + 3, 'f', 'o', 'o', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + }; + test_domains_fail(ELEMENTSOF(forward), forward); + + /* infinite loop */ + static uint8_t loop1[] = { + 0xc0, 0x00, + }; + test_domains_fail(ELEMENTSOF(loop1), loop1); + + static uint8_t loop2[] = { + 0xc0, 0x02, + 0xc0, 0x00, + }; + test_domains_fail(ELEMENTSOF(loop2), loop2); + + static uint8_t loop3[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0xc0, 0x00, + }; + test_domains_fail(ELEMENTSOF(loop3), loop3); + + /* unterminated */ + static uint8_t unterminated[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + }; + test_domains_fail(ELEMENTSOF(unterminated), unterminated); + + /* truncated label */ + static uint8_t truncated[] = { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', + }; + test_domains_fail(ELEMENTSOF(truncated), truncated); +} + +TEST(dnr) { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + + sd_dns_resolver *resolvers = NULL; + size_t n_resolvers = 0; + CLEANUP_ARRAY(resolvers, n_resolvers, dns_resolver_free_array); + + static uint8_t data[] = { + /* Instance 1 */ + /* length */ + 0, 78, + /* priority */ + 0, 1, + /* authentication domain name */ + 22, + 8, 'r', 'e', 's', 'o', 'l', 'v', 'e', 'r', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + /* addresses */ + 8, + 192, 0, 2, 1, + 192, 0, 2, 2, + /* service parameters */ + /* ALPN */ + 0, DNS_SVC_PARAM_KEY_ALPN, + 0, 14, + 2, 'h', '2', + 2, 'h', '3', + 3, 'd', 'o', 't', + 3, 'd', 'o', 'q', + /* port */ + 0, DNS_SVC_PARAM_KEY_PORT, + 0, 2, + 0, 42, + /* DoH path*/ + 0, DNS_SVC_PARAM_KEY_DOHPATH, + 0, 16, + '/', 'd', 'n', 's', '-', 'q', 'u', 'e', 'r', 'y', '{', '?', 'd', 'n', 's', '}', + + /* Instance 2 */ + /* length */ + 0, 44, + /* priority */ + 0, 2, + /* authentication domain name */ + 22, + 8, 'h', 'o', 'g', 'e', 'h', 'o', 'g', 'e', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + /* addresses */ + 4, + 192, 0, 2, 3, + /* service parameters */ + /* ALPN */ + 0, DNS_SVC_PARAM_KEY_ALPN, + 0, 4, + 3, 'd', 'o', 't', + /* port */ + 0, DNS_SVC_PARAM_KEY_PORT, + 0, 2, + 0, 33, + + /* Instance 3 (no address, ignored) */ + /* length */ + 0, 20, + /* priority */ + 0, 3, + /* authentication domain name */ + 17, + 3, 'f', 'o', 'o', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + + /* Instance 4 (unknown alpn, ignored) */ + /* length */ + 0, 37, + /* priority */ + 0, 4, + /* authentication domain name */ + 20, + 6, 'b', 'a', 'r', 'b', 'a', 'z', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + /* addresses */ + 4, + 192, 0, 2, 4, + /* service parameters */ + /* ALPN */ + 0, DNS_SVC_PARAM_KEY_ALPN, + 0, 5, + 4, 'h', 'o', 'g', 'e', + }; + + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(data), data)); + ASSERT_OK(dhcp_message_get_option_dnr(m, &n_resolvers, &resolvers)); + ASSERT_EQ(n_resolvers, 2u); + + ASSERT_EQ(resolvers[0].priority, 1u); + ASSERT_STREQ(resolvers[0].auth_name, "resolver.example.com"); + ASSERT_EQ(resolvers[0].family, AF_INET); + ASSERT_EQ(resolvers[0].n_addrs, 2u); + ASSERT_STREQ(IN_ADDR_TO_STRING(resolvers[0].family, &resolvers[0].addrs[0]), "192.0.2.1"); + ASSERT_STREQ(IN_ADDR_TO_STRING(resolvers[0].family, &resolvers[0].addrs[1]), "192.0.2.2"); + ASSERT_EQ(resolvers[0].transports, SD_DNS_ALPN_HTTP_2_TLS | SD_DNS_ALPN_HTTP_3 | SD_DNS_ALPN_DOT | SD_DNS_ALPN_DOQ); + ASSERT_EQ(resolvers[0].port, 42u); + ASSERT_STREQ(resolvers[0].dohpath, "/dns-query{?dns}"); + + ASSERT_EQ(resolvers[1].priority, 2u); + ASSERT_STREQ(resolvers[1].auth_name, "hogehoge.example.com"); + ASSERT_EQ(resolvers[1].family, AF_INET); + ASSERT_EQ(resolvers[1].n_addrs, 1u); + ASSERT_STREQ(IN_ADDR_TO_STRING(resolvers[1].family, &resolvers[1].addrs[0]), "192.0.2.3"); + ASSERT_EQ(resolvers[1].transports, SD_DNS_ALPN_DOT); + ASSERT_EQ(resolvers[1].port, 33u); + ASSERT_NULL(resolvers[1].dohpath); + + ASSERT_OK(dump_dhcp_message(m, STRV_MAKE("162"), DUMP_DHCP_MESSAGE_LEGEND | DUMP_DHCP_MESSAGE_FULL)); + + /* missing DoH path */ + static uint8_t invalid[] = { + /* length */ + 0, 35, + /* priority */ + 0, 5, + /* authentication domain name */ + 20, + 6, 'b', 'a', 'r', 'b', 'a', 'z', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + /* addresses */ + 4, + 192, 0, 2, 5, + /* service parameters */ + /* ALPN */ + 0, DNS_SVC_PARAM_KEY_ALPN, + 0, 3, + 2, 'h', '2', + }; + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(invalid), invalid)); + ASSERT_ERROR(dhcp_message_get_option_dnr(m, NULL, NULL), EBADMSG); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_V4_DNR); + + /* missing ALPN */ + static uint8_t invalid2[] = { + /* length */ + 0, 28, + /* priority */ + 0, 6, + /* authentication domain name */ + 20, + 6, 'b', 'a', 'r', 'b', 'a', 'z', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', 'm', + 0, + /* addresses */ + 4, + 192, 0, 2, 6, + }; + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(invalid2), invalid2)); + ASSERT_ERROR(dhcp_message_get_option_dnr(m, NULL, NULL), EBADMSG); + + dhcp_message_remove_option(m, SD_DHCP_OPTION_V4_DNR); + + /* truncated domain name */ + static uint8_t invalid3[] = { + /* length */ + 0, 34, + /* priority */ + 0, 7, + /* authentication domain name */ + 18, + 6, 'b', 'a', 'r', 'b', 'a', 'z', + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'c', 'o', + /* addresses */ + 4, + 192, 0, 2, 7, + /* service parameters */ + /* ALPN */ + 0, DNS_SVC_PARAM_KEY_ALPN, + 0, 4, + 3, 'd', 'o', 't', + }; + ASSERT_OK(dhcp_message_append_option(m, SD_DHCP_OPTION_V4_DNR, ELEMENTSOF(invalid3), invalid3)); + ASSERT_ERROR(dhcp_message_get_option_dnr(m, NULL, NULL), EMSGSIZE); +} + +TEST(dump_vendor) { + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + + /* Vendor Specific Information (43) -- TLV */ + static uint8_t option_43[] = { + 1, 6, 'm', 'o', 'd', 'e', 'm', '1', + 2, 6, 's', 't', 'a', 't', 'u', 's', + 3, 6, 1, 2, 3, 4, 5, 6, + }; + + /* Vendor Class Identifier (60) -- typically string */ + static uint8_t option_60[] = { + 'n', 'e', 't', 'w', 'o', 'r', 'k', 'd', + }; + + /* User Class (77) -- length-prefixed data, typically strings */ + static uint8_t option_77[] = { + 6, 'l', 'a', 'p', 't', 'o', 'p', + 4, 'c', 'o', 'r', 'p', + }; + + /* Vendor-Identifying Vendor Class -- length-prefixed data tagged with enterprise number */ + static uint8_t option_124[] = { + 0x00, 0x00, 0x00, 0x09, /* enterprise number: 9 */ + 13, + 5, 'c', 'i', 's', 'c', 'o', + 6, 'f', 'o', 'o', 'b', 'a', 'r', + 0x00, 0x00, 0x11, 0x8b, /* enterprise number: 4491 */ + 21, + 9, 'd', 'o', 'c', 's', 'i', 's', '3', '.', '0', + 10, 'e', 'R', 'o', 'u', 't', 'e', 'r', '1', '.', '0', + }; + + /* Vendor-Identifying Vendor-Specific Information (125) -- sub TLVs with enterprise number */ + static uint8_t option_125[] = { + 0x00, 0x00, 0x00, 0x09, /* enterprise number: 9 */ + 12, + 1, 4, 'C', 'i', 's', 'c', + 2, 4, 'I', 'O', 'S', 'X', + 0x00, 0x00, 0x11, 0x8b, /* enterprise number: 4491 */ + 16, + 1, 6, 'm', 'o', 'd', 'e', 'm', '1', + 2, 6, 's', 't', 'a', 't', 'u', 's', + }; + + ASSERT_OK(dhcp_message_append_option(m, 43, ELEMENTSOF(option_43), option_43)); + ASSERT_OK(dhcp_message_append_option(m, 60, ELEMENTSOF(option_60), option_60)); + ASSERT_OK(dhcp_message_append_option(m, 77, ELEMENTSOF(option_77), option_77)); + ASSERT_OK(dhcp_message_append_option(m, 124, ELEMENTSOF(option_124), option_124)); + ASSERT_OK(dhcp_message_append_option(m, 125, ELEMENTSOF(option_125), option_125)); + + ASSERT_OK(dump_dhcp_message(m, STRV_MAKE("43", "60", "77", "124", "125"), + DUMP_DHCP_MESSAGE_LEGEND | DUMP_DHCP_MESSAGE_FULL)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd-network/test-dhcp-option.c b/src/libsystemd-network/test-dhcp-option.c deleted file mode 100644 index 05572e0b21a28..0000000000000 --- a/src/libsystemd-network/test-dhcp-option.c +++ /dev/null @@ -1,381 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include -#include - -#include "alloc-util.h" -#include "dhcp-option.h" -#include "dhcp-packet.h" -#include "memory-util.h" -#include "tests.h" - -struct option_desc { - uint8_t sname[64]; - int snamelen; - uint8_t file[128]; - int filelen; - uint8_t options[128]; - int len; - bool success; - int filepos; - int snamepos; - int pos; -}; - -static bool verbose = false; - -static struct option_desc option_tests[] = { - { {}, 0, {}, 0, { 42, 5, 65, 66, 67, 68, 69 }, 7, false, }, - { {}, 0, {}, 0, { 42, 5, 65, 66, 67, 68, 69, 0, 0, - SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 12, true, }, - { {}, 0, {}, 0, { 8, 255, 70, 71, 72 }, 5, false, }, - { {}, 0, {}, 0, { 0x35, 0x01, 0x05, 0x36, 0x04, 0x01, 0x00, 0xa8, - 0xc0, 0x33, 0x04, 0x00, 0x01, 0x51, 0x80, 0x01, - 0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0xc0, - 0xa8, 0x00, 0x01, 0x06, 0x04, 0xc0, 0xa8, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, - 40, true, }, - { {}, 0, {}, 0, { SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_OFFER, - 42, 3, 0, 0, 0 }, 8, true, }, - { {}, 0, {}, 0, { 42, 2, 1, 2, 44 }, 5, false, }, - - { {}, 0, - { 222, 3, 1, 2, 3, SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_NAK }, 8, - { SD_DHCP_OPTION_OVERLOAD, 1, DHCP_OVERLOAD_FILE }, 3, true, }, - - { { 1, 4, 1, 2, 3, 4, SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 9, - { 222, 3, 1, 2, 3 }, 5, - { SD_DHCP_OPTION_OVERLOAD, 1, - DHCP_OVERLOAD_FILE|DHCP_OVERLOAD_SNAME }, 3, true, }, -}; - -static const char *dhcp_type(int type) { - switch (type) { - case DHCP_DISCOVER: - return "DHCPDISCOVER"; - case DHCP_OFFER: - return "DHCPOFFER"; - case DHCP_REQUEST: - return "DHCPREQUEST"; - case DHCP_DECLINE: - return "DHCPDECLINE"; - case DHCP_ACK: - return "DHCPACK"; - case DHCP_NAK: - return "DHCPNAK"; - case DHCP_RELEASE: - return "DHCPRELEASE"; - default: - return "unknown"; - } -} - -static void test_invalid_buffer_length(void) { - DHCPMessage message; - - assert_se(dhcp_option_parse(&message, 0, NULL, NULL, NULL) == -EINVAL); - assert_se(dhcp_option_parse(&message, sizeof(DHCPMessage) - 1, NULL, NULL, NULL) == -EINVAL); -} - -static void test_message_init(void) { - _cleanup_free_ DHCPMessage *message = NULL; - size_t optlen = 4, optoffset; - size_t len = sizeof(DHCPMessage) + optlen; - uint8_t *magic; - - message = malloc0(len); - - assert_se(dhcp_message_init(message, BOOTREQUEST, 0x12345678, - ARPHRD_ETHER, ETH_ALEN, (uint8_t[16]){}, DHCP_DISCOVER, - optlen, &optoffset) >= 0); - - assert_se(message->xid == htobe32(0x12345678)); - assert_se(message->op == BOOTREQUEST); - - magic = (uint8_t*)&message->magic; - - assert_se(magic[0] == 99); - assert_se(magic[1] == 130); - assert_se(magic[2] == 83); - assert_se(magic[3] == 99); - - assert_se(dhcp_option_parse(message, len, NULL, NULL, NULL) >= 0); -} - -static DHCPMessage *create_message(uint8_t *options, uint16_t optlen, - uint8_t *file, uint8_t filelen, - uint8_t *sname, uint8_t snamelen) { - DHCPMessage *message; - size_t len = sizeof(DHCPMessage) + optlen; - - message = malloc0(len); - assert_se(message); - - memcpy_safe(&message->options, options, optlen); - memcpy_safe(&message->file, file, filelen); - memcpy_safe(&message->sname, sname, snamelen); - - return message; -} - -static void test_ignore_opts(uint8_t *descoption, int *descpos, int *desclen) { - assert_se(*descpos >= 0); - - while (*descpos < *desclen) { - switch (descoption[*descpos]) { - case SD_DHCP_OPTION_PAD: - *descpos += 1; - break; - - case SD_DHCP_OPTION_MESSAGE_TYPE: - case SD_DHCP_OPTION_OVERLOAD: - *descpos += 3; - break; - - default: - return; - } - } -} - -static int test_options_cb(uint8_t code, uint8_t len, const void *option, void *userdata) { - struct option_desc *desc = userdata; - uint8_t *descoption = NULL; - int *desclen = NULL, *descpos = NULL; - uint8_t optcode = 0; - uint8_t optlen = 0; - - assert_se((!desc && !code && !len) || desc); - - if (!desc) - return -EINVAL; - - assert_se(code != SD_DHCP_OPTION_PAD); - assert_se(code != SD_DHCP_OPTION_END); - assert_se(code != SD_DHCP_OPTION_MESSAGE_TYPE); - assert_se(code != SD_DHCP_OPTION_OVERLOAD); - - while (desc->pos >= 0 || desc->filepos >= 0 || desc->snamepos >= 0) { - - if (desc->pos >= 0) { - descoption = &desc->options[0]; - desclen = &desc->len; - descpos = &desc->pos; - } else if (desc->filepos >= 0) { - descoption = &desc->file[0]; - desclen = &desc->filelen; - descpos = &desc->filepos; - } else if (desc->snamepos >= 0) { - descoption = &desc->sname[0]; - desclen = &desc->snamelen; - descpos = &desc->snamepos; - } - - assert_se(descoption && desclen && descpos); - - if (*desclen) - test_ignore_opts(descoption, descpos, desclen); - - if (*descpos < *desclen) - break; - - if (*descpos == *desclen) - *descpos = -1; - } - - assert_se(descpos); - assert_se(*descpos != -1); - - optcode = descoption[*descpos]; - optlen = descoption[*descpos + 1]; - - if (verbose) - printf("DHCP code %2d(%2d) len %2d(%2d) ", code, optcode, - len, optlen); - - assert_se(code == optcode); - assert_se(len == optlen); - - for (unsigned i = 0; i < len; i++) { - if (verbose) - printf("0x%02x(0x%02x) ", - ((uint8_t*) option)[i], - descoption[*descpos + 2 + i]); - - assert_se(((uint8_t*) option)[i] == descoption[*descpos + 2 + i]); - } - - if (verbose) - printf("\n"); - - *descpos += optlen + 2; - - test_ignore_opts(descoption, descpos, desclen); - - if (desc->pos != -1 && desc->pos == desc->len) - desc->pos = -1; - - if (desc->filepos != -1 && desc->filepos == desc->filelen) - desc->filepos = -1; - - if (desc->snamepos != -1 && desc->snamepos == desc->snamelen) - desc->snamepos = -1; - - return 0; -} - -static void test_options(struct option_desc *desc) { - uint8_t *options = NULL; - uint8_t *file = NULL; - uint8_t *sname = NULL; - int optlen = 0; - int filelen = 0; - int snamelen = 0; - int buflen = 0; - _cleanup_free_ DHCPMessage *message = NULL; - int res; - - if (desc) { - file = &desc->file[0]; - filelen = desc->filelen; - if (!filelen) - desc->filepos = -1; - - sname = &desc->sname[0]; - snamelen = desc->snamelen; - if (!snamelen) - desc->snamepos = -1; - - options = &desc->options[0]; - optlen = desc->len; - desc->pos = 0; - } - message = create_message(options, optlen, file, filelen, - sname, snamelen); - - buflen = sizeof(DHCPMessage) + optlen; - - if (!desc) { - assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, NULL, NULL)) == -ENOMSG); - } else if (desc->success) { - assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, desc, NULL)) >= 0); - assert_se(desc->pos == -1 && desc->filepos == -1 && desc->snamepos == -1); - } else - assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, desc, NULL)) < 0); - - if (verbose) - printf("DHCP type %s\n", dhcp_type(res)); -} - -static void test_option_removal(struct option_desc *desc) { - _cleanup_free_ DHCPMessage *message = create_message(&desc->options[0], desc->len, NULL, 0, NULL, 0); - - assert_se(dhcp_option_parse(message, sizeof(DHCPMessage) + desc->len, NULL, NULL, NULL) >= 0); - assert_se((desc->len = dhcp_option_remove_option(message->options, desc->len, SD_DHCP_OPTION_MESSAGE_TYPE)) >= 0); - assert_se(dhcp_option_parse(message, sizeof(DHCPMessage) + desc->len, NULL, NULL, NULL) < 0); -} - -static uint8_t the_options[64] = { - 'A', 'B', 'C', 'D', - 160, 2, 0x11, 0x12, - 0, - 31, 8, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - 0, - 55, 3, 0x51, 0x52, 0x53, - 17, 7, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, - 255 -}; - -static void test_option_set(void) { - _cleanup_free_ DHCPMessage *result = NULL; - size_t offset = 0, len, pos; - - result = malloc0(sizeof(DHCPMessage) + 11); - assert_se(result); - - result->options[0] = 'A'; - result->options[1] = 'B'; - result->options[2] = 'C'; - result->options[3] = 'D'; - - assert_se(dhcp_option_append(result, 0, &offset, 0, SD_DHCP_OPTION_PAD, - 0, NULL) == -ENOBUFS); - assert_se(offset == 0); - - offset = 4; - assert_se(dhcp_option_append(result, 5, &offset, 0, SD_DHCP_OPTION_PAD, - 0, NULL) == -ENOBUFS); - assert_se(offset == 4); - assert_se(dhcp_option_append(result, 6, &offset, 0, SD_DHCP_OPTION_PAD, - 0, NULL) >= 0); - assert_se(offset == 5); - - offset = pos = 4; - len = 11; - while (pos < len && the_options[pos] != SD_DHCP_OPTION_END) { - assert_se(dhcp_option_append(result, len, &offset, DHCP_OVERLOAD_SNAME, - the_options[pos], - the_options[pos + 1], - &the_options[pos + 2]) >= 0); - - if (the_options[pos] == SD_DHCP_OPTION_PAD) - pos++; - else - pos += 2 + the_options[pos + 1]; - - if (pos < len) - assert_se(offset == pos); - } - - for (unsigned i = 0; i < 9; i++) { - if (verbose) - printf("%2u: 0x%02x(0x%02x) (options)\n", i, result->options[i], - the_options[i]); - assert_se(result->options[i] == the_options[i]); - } - - if (verbose) - printf("%2d: 0x%02x(0x%02x) (options)\n", 9, result->options[9], - (unsigned) SD_DHCP_OPTION_END); - - assert_se(result->options[9] == SD_DHCP_OPTION_END); - - if (verbose) - printf("%2d: 0x%02x(0x%02x) (options)\n", 10, result->options[10], - (unsigned) SD_DHCP_OPTION_PAD); - - assert_se(result->options[10] == SD_DHCP_OPTION_PAD); - - for (unsigned i = 0; i < pos - 8; i++) { - if (verbose) - printf("%2u: 0x%02x(0x%02x) (sname)\n", i, result->sname[i], - the_options[i + 9]); - assert_se(result->sname[i] == the_options[i + 9]); - } - - if (verbose) - printf ("\n"); -} - -int main(int argc, char *argv[]) { - test_setup_logging(LOG_DEBUG); - - test_invalid_buffer_length(); - test_message_init(); - - test_options(NULL); - - FOREACH_ELEMENT(desc, option_tests) - test_options(desc); - - test_option_set(); - - FOREACH_ELEMENT(desc, option_tests) { - if (!desc->success || desc->snamelen > 0 || desc->filelen > 0) - continue; - test_option_removal(desc); - } - - return 0; -} diff --git a/src/libsystemd-network/test-dhcp-relay.c b/src/libsystemd-network/test-dhcp-relay.c new file mode 100644 index 0000000000000..2a7131e59b5a2 --- /dev/null +++ b/src/libsystemd-network/test-dhcp-relay.c @@ -0,0 +1,383 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-event.h" + +#include "dhcp-message.h" +#include "dhcp-relay-internal.h" +#include "ether-addr-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "ip-util.h" +#include "socket-util.h" +#include "tests.h" + +static uint32_t xid = 12345; + +static const struct hw_addr_data hw_addr = { + .length = ETH_ALEN, + .ether = {{ 'A', 'B', 'C', '1', '2', '3' }}, +}; + +static unsigned fake_server_message_count = 0; +static unsigned fake_client_message_count = 0; + +TEST(sd_dhcp_relay_ref_unref) { + _cleanup_(sd_dhcp_relay_unrefp) sd_dhcp_relay *relay = NULL; + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *upstream = NULL, *downstream = NULL; + + ASSERT_OK(sd_dhcp_relay_new(&relay)); + ASSERT_NOT_NULL(relay); + + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4242, /* is_upstream= */ true, &upstream)); + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4343, /* is_upstream= */ false, &downstream)); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4343)), downstream); + + /* Each interface holds a reference to the sd_dhcp_relay object, so we can safely drop our reference. */ + relay = sd_dhcp_relay_unref(relay); + ASSERT_PTR_EQ(hashmap_get(upstream->relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_PTR_EQ(hashmap_get(downstream->relay->interfaces, INT_TO_PTR(4343)), downstream); + + /* Still upstream interface has the reference. */ + downstream = sd_dhcp_relay_interface_unref(downstream); + ASSERT_PTR_EQ(hashmap_get(upstream->relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_FALSE(hashmap_contains(upstream->relay->interfaces, INT_TO_PTR(4343))); + + /* Everything should be freed with this. */ + upstream = sd_dhcp_relay_interface_unref(upstream); + + /* Let's check the inverse order. */ + ASSERT_OK(sd_dhcp_relay_new(&relay)); + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4242, /* is_upstream= */ true, &upstream)); + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4343, /* is_upstream= */ false, &downstream)); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4343)), downstream); + + downstream = sd_dhcp_relay_interface_unref(downstream); + ASSERT_PTR_EQ(hashmap_get(relay->interfaces, INT_TO_PTR(4242)), upstream); + ASSERT_FALSE(hashmap_contains(relay->interfaces, INT_TO_PTR(4343))); + + upstream = sd_dhcp_relay_interface_unref(upstream); + ASSERT_FALSE(hashmap_contains(relay->interfaces, INT_TO_PTR(4242))); + ASSERT_FALSE(hashmap_contains(relay->interfaces, INT_TO_PTR(4343))); +} + +static void send_message(int fd, sd_dhcp_message *m) { + ASSERT_OK(dhcp_message_send_udp( + m, + fd, + /* src_addr= */ INADDR_ANY, + /* dst_addr= */ INADDR_ANY, + /* dst_port= */ 0)); +} + +static int fake_server_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_dhcp_relay *relay = ASSERT_PTR(userdata); + + fake_server_message_count++; + log_debug("%s: count=%u", __func__, fake_server_message_count); + + ssize_t buflen = ASSERT_OK_POSITIVE(next_datagram_size_fd(fd)); + _cleanup_free_ void *buf = ASSERT_NOT_NULL(malloc0(buflen)); + + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + }; + ssize_t len = ASSERT_OK_ERRNO(recvmsg_safe(fd, &msg, MSG_DONTWAIT)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_parse( + &IOVEC_MAKE(buf, len), + BOOTREQUEST, + &xid, + ARPHRD_ETHER, + &hw_addr, + &m)); + + ASSERT_EQ(m->header.hops, 1u); + + sd_dhcp_relay_interface *downstream; + ASSERT_OK(downstream_get(relay, m, &downstream)); + ASSERT_FALSE(downstream->upstream); + + ASSERT_EQ(m->header.giaddr, downstream->gateway_address.s_addr); + + _cleanup_(tlv_unrefp) TLV *agent_info = NULL; + ASSERT_OK(dhcp_message_get_option_sub_tlv( + m, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, + TLV_DHCP4_SUBOPTION, + &agent_info)); + + void *key, *value; + HASHMAP_FOREACH_KEY(value, key, agent_info->entries) { + uint32_t tag = PTR_TO_UINT32(key); + _cleanup_(iovec_done) struct iovec iov = {}; + ASSERT_OK(tlv_get_alloc(agent_info, tag, &iov)); + + switch (tag) { + case SD_DHCP_RELAY_AGENT_CIRCUIT_ID: + ASSERT_TRUE(iovec_equal(&iov, &downstream->circuit_id)); + break; + case SD_DHCP_RELAY_AGENT_REMOTE_ID: + ASSERT_TRUE(iovec_equal(&iov, &relay->remote_id)); + break; + case SD_DHCP_RELAY_AGENT_LINK_SELECTION: + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE(&downstream->address, sizeof(struct in_addr)))); + break; + case SD_DHCP_RELAY_AGENT_FLAGS: { + ASSERT_TRUE(relay->server_identifier_override); + ASSERT_EQ(iov.iov_len, 1u); + uint8_t flags = *(uint8_t*) iov.iov_base; + /* In the unit test, we cannot detect if the message is broadcast or unicast because + * AF_UNIX is used; therefore, the unicast flag is not set. */ + ASSERT_FALSE(FLAGS_SET(flags, DHCP_RELAY_AGENT_FLAG_UNICAST)); + break; + } + case SD_DHCP_RELAY_AGENT_SERVER_IDENTIFIER_OVERRIDE: + ASSERT_TRUE(relay->server_identifier_override); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE(&downstream->address, sizeof(struct in_addr)))); + break; + case SD_DHCP_RELAY_AGENT_VIRTUAL_SUBNET_SELECTION: + ASSERT_TRUE(iovec_equal(&iov, &downstream->vss)); + break; + default: + assert_not_reached(); + } + } + + uint8_t t; + ASSERT_OK(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, &t)); + + switch (fake_server_message_count) { + case 1: + ASSERT_EQ(t, DHCP_DISCOVER); + break; + case 2: + ASSERT_EQ(t, DHCP_REQUEST); + break; + case 3: + ASSERT_EQ(t, DHCP_RELEASE); + + if (fake_client_message_count == 3) + ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +static void fake_client_verify(int fd, uint8_t type, bool raw) { + ssize_t buflen = ASSERT_OK_POSITIVE(next_datagram_size_fd(fd)); + _cleanup_free_ void *buf = ASSERT_NOT_NULL(malloc0(buflen)); + + struct msghdr msg = { + .msg_iov = &IOVEC_MAKE(buf, buflen), + .msg_iovlen = 1, + }; + ssize_t len = ASSERT_OK_ERRNO(recvmsg_safe(fd, &msg, MSG_DONTWAIT)); + + struct iovec payload = IOVEC_MAKE(buf, len); + if (raw) + ASSERT_OK(udp_packet_verify(&payload, DHCP_PORT_CLIENT, /* checksum= */ true, &payload)); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_parse( + &payload, + BOOTREPLY, + &xid, + ARPHRD_ETHER, + &hw_addr, + &m)); + + ASSERT_EQ(m->header.hops, 0u); + ASSERT_FALSE(dhcp_message_has_option(m, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION)); + + uint8_t t; + ASSERT_OK(dhcp_message_get_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, &t)); + ASSERT_EQ(t, type); +} + +static int fake_client_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + fake_client_message_count++; + log_debug("%s: count=%u", __func__, fake_client_message_count); + + switch (fake_client_message_count) { + case 1: + fake_client_verify(fd, DHCP_OFFER, /* raw= */ true); + break; + case 2: + fake_client_verify(fd, DHCP_ACK, /* raw= */ false); + break; + case 3: + fake_client_verify(fd, DHCP_NAK, /* raw= */ false); + + if (fake_server_message_count == 3) + ASSERT_OK(sd_event_exit(sd_event_source_get_event(s), 0)); + break; + default: + assert_not_reached(); + } + + return 0; +} + +TEST(forwarding) { + union in_addr_union a; + + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + + _cleanup_(sd_dhcp_relay_unrefp) sd_dhcp_relay *relay = NULL; + ASSERT_OK(sd_dhcp_relay_new(&relay)); + ASSERT_OK(sd_dhcp_relay_attach_event(relay, e, SD_EVENT_PRIORITY_NORMAL)); + ASSERT_OK(in_addr_from_string(AF_INET, "198.51.100.1", &a)); + ASSERT_OK(sd_dhcp_relay_set_server_address(relay, &a.in)); + ASSERT_OK(sd_dhcp_relay_set_remote_id(relay, &IOVEC_MAKE_STRING("test-remote-id"))); + ASSERT_OK(sd_dhcp_relay_set_server_identifier_override(relay, true)); + + /* Setting up an upstream interface. */ + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *upstream = NULL; + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4242, /* is_upstream= */ true, &upstream)); + ASSERT_OK(sd_dhcp_relay_interface_set_ifname(upstream, "test-upstream")); + ASSERT_OK_ZERO(sd_dhcp_relay_interface_get_address(upstream, /* ret_address= */ NULL, /* ret_prefixlen= */ NULL)); + ASSERT_OK(in_addr_from_string(AF_INET, "198.51.100.2", &a)); + ASSERT_OK(sd_dhcp_relay_interface_set_address(upstream, &a.in, 24)); + ASSERT_OK_POSITIVE(sd_dhcp_relay_interface_get_address(upstream, /* ret_address= */ NULL, /* ret_prefixlen= */ NULL)); + + _cleanup_close_pair_ int upstream_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, upstream_fd)); + upstream->socket_fd = TAKE_FD(upstream_fd[0]); + ASSERT_OK(sd_dhcp_relay_interface_start(upstream)); + + /* IO event source for the server side. */ + _cleanup_(sd_event_source_unrefp) sd_event_source *fake_server = NULL; + ASSERT_OK(sd_event_add_io(e, &fake_server, upstream_fd[1], EPOLLIN, fake_server_handler, relay)); + ASSERT_OK(sd_event_source_set_priority(fake_server, SD_EVENT_PRIORITY_IMPORTANT)); + ASSERT_OK(sd_event_source_set_description(fake_server, "fake-server-io-event-source")); + + /* Setting up a downstream interface. */ + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *downstream = NULL; + ASSERT_OK(sd_dhcp_relay_add_interface(relay, 4343, /* is_upstream= */ false, &downstream)); + ASSERT_OK(sd_dhcp_relay_interface_set_ifname(downstream, "test-downstream")); + ASSERT_OK(in_addr_from_string(AF_INET, "192.0.2.1", &a)); + ASSERT_OK(sd_dhcp_relay_interface_set_address(downstream, &a.in, 24)); + + ASSERT_OK(in_addr_from_string(AF_INET, "203.0.113.1", &a)); + ASSERT_OK(sd_dhcp_relay_downstream_set_gateway_address(downstream, &a.in)); + ASSERT_OK(sd_dhcp_relay_downstream_set_circuit_id(downstream, &IOVEC_MAKE_STRING("test-circuit-id"))); + ASSERT_OK(sd_dhcp_relay_downstream_set_virtual_subnet_selection(downstream, &IOVEC_MAKE_STRING("test-virtual-net"))); + + _cleanup_close_pair_ int downstream_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, downstream_fd)); + downstream->socket_fd = TAKE_FD(downstream_fd[0]); + ASSERT_OK(sd_dhcp_relay_interface_start(downstream)); + + /* IO event source for the client side. */ + _cleanup_(sd_event_source_unrefp) sd_event_source *fake_client = NULL; + ASSERT_OK(sd_event_add_io(e, &fake_client, downstream_fd[1], EPOLLIN, fake_client_handler, relay)); + ASSERT_OK(sd_event_source_set_priority(fake_client, SD_EVENT_PRIORITY_NORMAL)); + ASSERT_OK(sd_event_source_set_description(fake_client, "fake-client-io-event-source")); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + ASSERT_OK(dhcp_message_init_header( + m, + BOOTREQUEST, + xid, + ARPHRD_ETHER, + &hw_addr)); + + /* Test: downstream -> upstream */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_DISCOVER)); + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_REQUEST)); + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + /* Invalid message (unexpected BOOTP operation). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 100)); + m->header.op = BOOTREPLY; + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.op = BOOTREQUEST; + + /* Invalid message (too large hops). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 101)); + m->header.hops = 16; + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.hops = 0; + + /* Invalid message (invalid giaddr). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 102)); + m->header.giaddr = downstream->address.s_addr; + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.giaddr = INADDR_ANY; + + /* Invalid message (unexpected relay agent information). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 103)); + ASSERT_OK(dhcp_message_append_option_flag(m, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION)); + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + dhcp_message_remove_option(m, SD_DHCP_OPTION_RELAY_AGENT_INFORMATION); + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_RELEASE)); + send_message(downstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + /* Test: upstream -> downstream */ + m->header.op = BOOTREPLY; + m->header.giaddr = downstream->gateway_address.s_addr; + m->header.yiaddr = 0x12345678; + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_OFFER)); + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_ACK)); + m->header.ciaddr = 0x12345678; + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.ciaddr = 0; + + /* Invalid message (unexpected BOOTP operation). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 200)); + m->header.op = BOOTREQUEST; + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.op = BOOTREPLY; + + /* Invalid message (NULL giaddr). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 201)); + m->header.giaddr = INADDR_ANY; + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.giaddr = downstream->gateway_address.s_addr; + + /* Invalid message (unexpected giaddr). */ + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, 202)); + m->header.giaddr = 1234567; + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + m->header.giaddr = downstream->gateway_address.s_addr; + + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_NAK)); + send_message(upstream_fd[1], m); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + + ASSERT_OK(sd_event_loop(e)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index 7789037011e7d..d25c31219e441 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -9,48 +9,34 @@ #include "sd-event.h" #include "dhcp-server-internal.h" -#include "hashmap.h" -#include "siphash24.h" +#include "dhcp-server-request.h" +#include "fd-util.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" #include "tests.h" -static void test_pool(struct in_addr *address, unsigned size, int ret) { - _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - - ASSERT_OK(sd_dhcp_server_new(&server, 1)); - - if (ret >= 0) - ASSERT_RETURN_IS_CRITICAL(true, ASSERT_OK_EQ(sd_dhcp_server_configure_pool(server, address, 8, 0, size), ret)); - else - ASSERT_RETURN_IS_CRITICAL(false, ASSERT_ERROR(sd_dhcp_server_configure_pool(server, address, 8, 0, size), -ret)); -} - -static int test_basic(bool bind_to_interface) { - _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; +TEST(basic) { struct in_addr address_lo = { .s_addr = htobe32(INADDR_LOOPBACK), }; struct in_addr address_any = { .s_addr = htobe32(INADDR_ANY), }; - int r; - - log_debug("/* %s(bind_to_interface=%s) */", __func__, yes_no(bind_to_interface)); + _cleanup_(sd_event_unrefp) sd_event *event = NULL; ASSERT_OK(sd_event_new(&event)); - /* attach to loopback interface */ - ASSERT_OK(sd_dhcp_server_new(&server, 1)); + _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; + ASSERT_OK(sd_dhcp_server_new(&server, 4242)); ASSERT_NOT_NULL(server); - server->bind_to_interface = bind_to_interface; - ASSERT_OK(sd_dhcp_server_attach_event(server, event, 0)); - ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, event, 0), EBUSY)); - ASSERT_TRUE(sd_dhcp_server_get_event(server) == event); /* ASSERT_EQ() doesn't work here. */ + ASSERT_OK(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL)); + ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL), EBUSY)); + ASSERT_PTR_EQ(sd_dhcp_server_get_event(server), event); ASSERT_OK(sd_dhcp_server_detach_event(server)); ASSERT_NULL(sd_dhcp_server_get_event(server)); - ASSERT_OK(sd_dhcp_server_attach_event(server, NULL, 0)); - ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, NULL, 0), EBUSY)); + ASSERT_OK(sd_dhcp_server_attach_event(server, NULL, SD_EVENT_PRIORITY_NORMAL)); + ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_attach_event(server, NULL, SD_EVENT_PRIORITY_NORMAL), EBUSY)); ASSERT_TRUE(sd_dhcp_server_ref(server) == server); ASSERT_NULL(sd_dhcp_server_unref(server)); @@ -61,71 +47,47 @@ static int test_basic(bool bind_to_interface) { ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_configure_pool(server, &address_lo, 38, 0, 0), ERANGE)); ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0)); ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0)); + ASSERT_RETURN_EXPECTED(ASSERT_ERROR(sd_dhcp_server_configure_pool(server, &address_any, 8, 0, 1), EINVAL)); + ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 1)); - test_pool(&address_any, 1, -EINVAL); - test_pool(&address_lo, 1, 0); + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); - 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) - return r; - ASSERT_OK(r); + server->socket_fd = TAKE_FD(socket_fd[0]); + ASSERT_OK(sd_dhcp_server_start(server)); ASSERT_OK(sd_dhcp_server_start(server)); ASSERT_OK(sd_dhcp_server_stop(server)); ASSERT_OK(sd_dhcp_server_stop(server)); ASSERT_OK(sd_dhcp_server_start(server)); +} - return 0; +static int process_one(sd_dhcp_server *server, sd_dhcp_message *message) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + ASSERT_OK(dhcp_message_build(message, &iovw)); + + _cleanup_(iovec_done) struct iovec iov = {}; + ASSERT_OK(iovw_concat(&iovw, &iov)); + return dhcp_server_process_message(server, &iov, /* mh= */ NULL); } -static void test_message_handler(void) { - _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - struct { - struct { - DHCP_MESSAGE_HEADER_DEFINITION; - } _packed_ message; - struct { - uint8_t code; - uint8_t length; - uint8_t type; - } _packed_ option_type; - struct { - uint8_t code; - uint8_t length; - be32_t address; - } _packed_ option_requested_ip; - struct { - uint8_t code; - uint8_t length; - be32_t address; - } _packed_ option_server_id; - struct { - uint8_t code; - uint8_t length; - uint8_t id[7]; - } _packed_ option_client_id; - struct { - uint8_t code; - uint8_t length; - uint8_t hostname[6]; - } _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' }, - .option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE, - .option_type.length = 1, - .option_type.type = DHCP_DISCOVER, - .option_hostname.code = SD_DHCP_OPTION_HOST_NAME, - .option_hostname.length = 6, - .option_hostname.hostname = { 'T', 'E', 'S', 'T', 'H', 'N' }, - .end = SD_DHCP_OPTION_END, +TEST(dhcp_server_process_message) { + static const struct hw_addr_data hw_addr = { + .length = ETH_ALEN, + .ether = {{ 'A', 'B', 'C', 'D', 'E', 'F' }}, }; + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *m = NULL; + ASSERT_OK(dhcp_message_new(&m)); + ASSERT_OK(dhcp_message_init_header( + m, + BOOTREQUEST, + 0x12345678, + ARPHRD_ETHER, + &hw_addr)); + + ASSERT_OK(dhcp_message_append_option_hostname(m, /* flags= */ 0, /* is_client= */ false, "TESTHN")); + struct in_addr address_lo = { .s_addr = htobe32(INADDR_LOOPBACK), }; @@ -133,11 +95,15 @@ static void test_message_handler(void) { .s_addr = htobe32(INADDR_LOOPBACK + 42), }; static uint8_t static_lease_client_id[7] = {0x01, 'A', 'B', 'C', 'D', 'E', 'G' }; - int r; - log_debug("/* %s */", __func__); + _cleanup_close_pair_ int socket_fd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, socket_fd)); + + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_new(&event)); - ASSERT_OK(sd_dhcp_server_new(&server, 1)); + _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; + ASSERT_OK(sd_dhcp_server_new(&server, 4242)); ASSERT_OK(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0)); ASSERT_OK(sd_dhcp_server_set_static_lease( server, @@ -145,78 +111,78 @@ static void test_message_handler(void) { static_lease_client_id, ELEMENTSOF(static_lease_client_id), /* hostname= */ NULL)); - ASSERT_OK(sd_dhcp_server_attach_event(server, NULL, 0)); + ASSERT_OK(sd_dhcp_server_attach_event(server, event, SD_EVENT_PRIORITY_NORMAL)); + server->socket_fd = TAKE_FD(socket_fd[0]); ASSERT_OK(sd_dhcp_server_start(server)); - r = dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL); - if (r == -ENETDOWN) - return (void) log_tests_skipped("Network is not available"); - ASSERT_OK_EQ(r, DHCP_OFFER); - - test.end = 0; - /* TODO, shouldn't this fail? */ - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); - test.end = SD_DHCP_OPTION_END; - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); - - test.option_type.code = 0; - test.option_type.length = 0; - test.option_type.type = 0; - ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), ENOMSG); - test.option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE; - test.option_type.length = 1; - 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; - ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); - test.message.op = BOOTREQUEST; - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); - - test.message.htype = 0; - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); - test.message.htype = ARPHRD_ETHER; - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); - - test.message.hlen = 0; - ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), EBADMSG); - test.message.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; - ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); - test.option_requested_ip.code = SD_DHCP_OPTION_REQUESTED_IP_ADDRESS; - test.option_requested_ip.length = 4; - test.option_requested_ip.address = htobe32(0x12345678); - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_NAK); - test.option_server_id.code = SD_DHCP_OPTION_SERVER_IDENTIFIER; - test.option_server_id.length = 4; - test.option_server_id.address = htobe32(INADDR_LOOPBACK); - test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3); - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); - - test.option_server_id.address = htobe32(0x12345678); - test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3); - ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); - test.option_server_id.address = htobe32(INADDR_LOOPBACK); - test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 4); - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); - test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3); - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); - - test.option_client_id.code = SD_DHCP_OPTION_CLIENT_IDENTIFIER; - test.option_client_id.length = 7; - test.option_client_id.id[0] = 0x01; - test.option_client_id.id[1] = 'A'; - test.option_client_id.id[2] = 'B'; - test.option_client_id.id[3] = 'C'; - test.option_client_id.id[4] = 'D'; - test.option_client_id.id[5] = 'E'; - test.option_client_id.id[6] = 'F'; - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); - - test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 30); - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_DISCOVER)); + ASSERT_OK_EQ(process_one(server, m), DHCP_OFFER); + + /* Missing Message Type option */ + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + ASSERT_ERROR(process_one(server, m), ENODATA); + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_DISCOVER)); + + /* Invalid op */ + m->header.op = 0; + ASSERT_ERROR(process_one(server, m), EBADMSG); + m->header.op = BOOTREQUEST; + + /* Invalid htype */ + m->header.htype = 0; + ASSERT_ERROR(process_one(server, m), EBADMSG); + m->header.htype = ARPHRD_ETHER; + + /* Invalid hlen */ + m->header.hlen = 0; + ASSERT_ERROR(process_one(server, m), EBADMSG); + m->header.hlen = ETHER_ADDR_LEN; + + /* DHCPREQUEST (init-reboot) without Requested IP Address option */ + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_REQUEST)); + ASSERT_ERROR(process_one(server, m), ENODATA); + + /* DHCPREQUEST (init-reboot) with an invalid Requested IP Address option */ + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, htobe32(0x12345678))); + ASSERT_OK_EQ(process_one(server, m), DHCP_NAK); + + /* DHCPREQUEST (selecting) with Requested IP address */ + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_SERVER_IDENTIFIER, htobe32(INADDR_LOOPBACK))); + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, htobe32(INADDR_LOOPBACK + 3))); + ASSERT_OK_EQ(process_one(server, m), DHCP_ACK); + + /* DHCPREQUEST (selecting) with unmatching server address (silently ignored). */ + dhcp_message_remove_option(m, SD_DHCP_OPTION_SERVER_IDENTIFIER); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_SERVER_IDENTIFIER, htobe32(0x12345678))); + ASSERT_OK_ZERO(process_one(server, m)); + dhcp_message_remove_option(m, SD_DHCP_OPTION_SERVER_IDENTIFIER); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_SERVER_IDENTIFIER, htobe32(INADDR_LOOPBACK))); + + /* DHCPREQUEST (selecting) with another address */ + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, htobe32(INADDR_LOOPBACK + 4))); + ASSERT_OK_EQ(process_one(server, m), DHCP_ACK); + + /* Request the previous address again. */ + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, htobe32(INADDR_LOOPBACK + 3))); + ASSERT_OK_EQ(process_one(server, m), DHCP_ACK); + + /* With client ID */ + struct sd_dhcp_client_id client_id = { + .size = 7, + .id.type = 1, + .id.eth.haddr = { 'A', 'B', 'C', 'D', 'E', 'F' }, + }; + ASSERT_OK(dhcp_message_append_option_client_id(m, &client_id)); + ASSERT_OK_EQ(process_one(server, m), DHCP_ACK); + + /* Request a different address with client ID */ + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, htobe32(INADDR_LOOPBACK + 30))); + ASSERT_OK_EQ(process_one(server, m), DHCP_ACK); /* add the static lease for the client ID */ ASSERT_OK(sd_dhcp_server_stop(server)); @@ -229,26 +195,35 @@ static void test_message_handler(void) { ASSERT_OK(sd_dhcp_server_start(server)); /* discover */ - test.option_type.type = DHCP_DISCOVER; - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_DISCOVER)); + ASSERT_OK_EQ(process_one(server, m), DHCP_OFFER); /* request neither bound nor static address */ - test.option_type.type = DHCP_REQUEST; - test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 29); - ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_REQUEST)); + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, htobe32(INADDR_LOOPBACK + 29))); + ASSERT_OK_EQ(process_one(server, m), DHCP_NAK); /* request the currently assigned address */ - test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 30); - ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, htobe32(INADDR_LOOPBACK + 30))); + ASSERT_OK_EQ(process_one(server, m), DHCP_NAK); /* request the new static address */ - test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 31); - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, htobe32(INADDR_LOOPBACK + 31))); + ASSERT_OK_EQ(process_one(server, m), DHCP_ACK); /* release the bound static lease */ - test.message.ciaddr = htobe32(INADDR_LOOPBACK + 31); - test.option_type.type = DHCP_RELEASE; - ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); + m->header.ciaddr = htobe32(INADDR_LOOPBACK + 31); + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_RELEASE)); + ASSERT_OK_ZERO(process_one(server, m)); + m->header.ciaddr = 0; + dhcp_message_remove_option(m, SD_DHCP_OPTION_MESSAGE_TYPE); + ASSERT_OK(dhcp_message_append_option_u8(m, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_REQUEST)); /* drop the static lease for the client ID */ ASSERT_OK(sd_dhcp_server_stop(server)); @@ -261,81 +236,40 @@ static void test_message_handler(void) { ASSERT_OK(sd_dhcp_server_start(server)); /* request a new non-static address */ - test.message.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); + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, htobe32(INADDR_LOOPBACK + 29))); + ASSERT_OK_EQ(process_one(server, m), DHCP_ACK); /* request address reserved for static lease (unmatching client ID) */ - test.option_client_id.id[6] = 'H'; - test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 42); - ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); + client_id.id.eth.haddr[5] = 'H'; + dhcp_message_remove_option(m, SD_DHCP_OPTION_CLIENT_IDENTIFIER); + ASSERT_OK(dhcp_message_append_option_client_id(m, &client_id)); + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, htobe32(INADDR_LOOPBACK + 42))); + ASSERT_OK_EQ(process_one(server, m), DHCP_NAK); /* request unmatching address */ - test.option_client_id.id[6] = 'G'; - test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 41); - ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); + client_id.id.eth.haddr[5] = 'G'; + dhcp_message_remove_option(m, SD_DHCP_OPTION_CLIENT_IDENTIFIER); + ASSERT_OK(dhcp_message_append_option_client_id(m, &client_id)); + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, htobe32(INADDR_LOOPBACK + 41))); + ASSERT_OK_EQ(process_one(server, m), DHCP_NAK); /* request matching address */ - test.option_client_id.id[6] = 'G'; - test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 42); - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, htobe32(INADDR_LOOPBACK + 42))); + ASSERT_OK_EQ(process_one(server, m), DHCP_ACK); /* try again */ - test.option_client_id.id[6] = 'G'; - test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 42); - ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_ACK); + dhcp_message_remove_option(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS); + ASSERT_OK(dhcp_message_append_option_be32(m, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, htobe32(INADDR_LOOPBACK + 42))); + ASSERT_OK_EQ(process_one(server, m), DHCP_ACK); } -static uint64_t client_id_hash_helper(sd_dhcp_client_id *id, uint8_t key[HASH_KEY_SIZE]) { - struct siphash state; - - siphash24_init(&state, key); - client_id_hash_func(id, &state); - - return htole64(siphash24_finalize(&state)); -} - -static void test_client_id_hash(void) { - sd_dhcp_client_id a = { - .size = 4, - }, b = { - .size = 4, - }; - uint8_t hash_key[HASH_KEY_SIZE] = { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', - }; - - log_debug("/* %s */", __func__); - - memcpy(a.raw, "abcd", 4); - memcpy(b.raw, "abcd", 4); - - ASSERT_EQ(client_id_compare_func(&a, &b), 0); - ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); - a.size = 3; - ASSERT_NE(client_id_compare_func(&a, &b), 0); - a.size = 4; - ASSERT_EQ(client_id_compare_func(&a, &b), 0); - ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); - - b.size = 3; - ASSERT_NE(client_id_compare_func(&a, &b), 0); - b.size = 4; - ASSERT_EQ(client_id_compare_func(&a, &b), 0); - ASSERT_EQ(client_id_hash_helper(&a, hash_key), client_id_hash_helper(&b, hash_key)); - - memcpy(b.raw, "abce", 4); - ASSERT_NE(client_id_compare_func(&a, &b), 0); -} - -static void test_static_lease(void) { +TEST(sd_dhcp_server_set_static_lease) { _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - - log_debug("/* %s */", __func__); - - ASSERT_OK(sd_dhcp_server_new(&server, 1)); + ASSERT_OK(sd_dhcp_server_new(&server, 4242)); ASSERT_OK(sd_dhcp_server_set_static_lease( server, @@ -411,12 +345,9 @@ static void test_static_lease(void) { /* hostname= */ NULL)); } -static void test_domain_name(void) { +TEST(sd_dhcp_server_set_domain_name) { _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; - - log_debug("/* %s */", __func__); - - ASSERT_OK(sd_dhcp_server_new(&server, 1)); + ASSERT_OK(sd_dhcp_server_new(&server, 4242)); /* Test setting domain name */ ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "example.com")); @@ -449,24 +380,4 @@ static void test_domain_name(void) { ASSERT_OK_POSITIVE(sd_dhcp_server_set_domain_name(server, "local")); } -int main(int argc, char *argv[]) { - int r; - - test_setup_logging(LOG_DEBUG); - - test_client_id_hash(); - test_static_lease(); - test_domain_name(); - - r = test_basic(true); - if (r < 0) - return log_tests_skipped_errno(r, "cannot start dhcp server(bound to interface)"); - - r = test_basic(false); - if (r < 0) - return log_tests_skipped_errno(r, "cannot start dhcp server(non-bound to interface)"); - - test_message_handler(); - - return 0; -} +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index f2b6ce8598be3..08dda7ce18720 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -324,6 +324,17 @@ TEST(option_status) { /* PD prefix status option */ 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, }; + static const uint8_t option6[] = { + /* IA PD */ + 0x00, 0x19, 0x00, 0x29, 0x1a, 0x1d, 0x1a, 0x1d, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, + /* IA PD Prefix, with an invalid prefix length of 0 */ + 0x00, 0x1a, 0x00, 0x19, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x00, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + }; _cleanup_(dhcp6_ia_freep) DHCP6IA *ia = NULL; DHCP6Option *option; be32_t iaid; @@ -379,6 +390,14 @@ TEST(option_status) { assert_se(ia); assert_se(ia->addresses); ia = dhcp6_ia_free(ia); + + /* An IA_PD whose only prefix carries an invalid (zero) prefix length must be refused, leaving no + * valid prefix behind. */ + option = (DHCP6Option*) option6; + assert_se(sizeof(option6) == sizeof(DHCP6Option) + be16toh(option->len)); + r = dhcp6_option_parse_ia(NULL, iaid, be16toh(option->code), be16toh(option->len), option->data, &ia); + assert_se(r == -ENODATA); + assert_se(!ia); } TEST(client_parse_message_issue_22099) { 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..75a40871a527e 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; @@ -415,6 +417,44 @@ static void test_receive_oui_vlanid_packet(sd_event *e) { ASSERT_OK(stop_lldp_rx(lldp_rx)); } +static int reentrant_unref_calls; + +static void lldp_rx_reentrant_unref_handler(sd_lldp_rx *lldp_rx, sd_lldp_rx_event_t event, sd_lldp_neighbor *n, void *userdata) { + /* Drop the last external reference from inside the callback. The event-source callback holds a + * transient self-ref, so lldp_rx must survive until the dispatch stack unwinds. */ + reentrant_unref_calls++; + sd_lldp_rx_unref(lldp_rx); +} + +static void test_receive_reentrant_unref(sd_event *e) { + /* Regression: a user callback dropping the last lldp_rx reference used to free the object while + * inner library frames still dereferenced it (use-after-free). The event-source callbacks now take + * a transient self-ref, so teardown is deferred until the dispatch stack unwinds. */ + static const uint8_t frame[] = { + /* Ethernet header */ + 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC */ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */ + 0x88, 0xcc, /* Ethertype */ + /* LLDP mandatory TLVs */ + 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ + 0x03, 0x04, 0x05, + 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port: interface name, "1/3" */ + 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds */ + 0x00, 0x00 /* End Of LLDPDU */ + }; + sd_lldp_rx *lldp_rx; + + reentrant_unref_calls = 0; + assert_se(start_lldp_rx(&lldp_rx, e, lldp_rx_reentrant_unref_handler, NULL) == 0); + + assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame)); + sd_event_run(e, 0); + assert_se(reentrant_unref_calls == 1); + + /* lldp_rx was freed by the callback's unref once the dispatch stack unwound; do not touch it. */ + safe_close(test_fd[1]); +} + int main(int argc, char *argv[]) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; @@ -427,6 +467,7 @@ int main(int argc, char *argv[]) { test_receive_oui_packet(e); test_multiple_neighbors_sorted(e); test_receive_oui_vlanid_packet(e); + test_receive_reentrant_unref(e); return 0; } diff --git a/src/libsystemd-network/test-ndisc-ra.c b/src/libsystemd-network/test-ndisc-ra.c index 84dc73e84fa5f..2ad42b5285f70 100644 --- a/src/libsystemd-network/test-ndisc-ra.c +++ b/src/libsystemd-network/test-ndisc-ra.c @@ -12,6 +12,7 @@ #include "sd-radv.h" #include "alloc-util.h" +#include "fd-util.h" #include "icmp6-test-util.h" #include "in-addr-util.h" #include "radv-internal.h" @@ -245,6 +246,38 @@ static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdat return 0; } +TEST(ra_ifindex_mismatch) { + static const struct nd_router_solicit rs = { + .nd_rs_type = ND_ROUTER_SOLICIT, + }; + + _cleanup_free_ uint8_t *buf = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_radv_unrefp) sd_radv *ra = NULL; + ssize_t buflen; + + assert_se(socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) >= 0); + + assert_se(sd_event_new(&e) >= 0); + assert_se(sd_radv_new(&ra) >= 0); + assert_se(sd_radv_attach_event(ra, e, 0) >= 0); + assert_se(sd_radv_set_ifindex(ra, 42) >= 0); + assert_se(sd_radv_start(ra) >= 0); + + assert_se(sd_event_run(e, UINT64_MAX) >= 0); + assert_se((buflen = next_datagram_size_fd(test_fd[0])) >= 0); + assert_se(buf = new(uint8_t, buflen)); + assert_se(read(test_fd[0], buf, buflen) == buflen); + + test_ifindex = 43; + assert_se(write(test_fd[0], &rs, sizeof(rs)) == sizeof(rs)); + assert_se(sd_event_run(e, UINT64_MAX) >= 0); + assert_se(next_datagram_size_fd(test_fd[0]) == -EAGAIN); + + assert_se(sd_radv_stop(ra) >= 0); + test_fd[0] = safe_close(test_fd[0]); +} + TEST(ra) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; _cleanup_(sd_event_source_unrefp) sd_event_source *recv_router_advertisement = NULL; diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index 51adc4e685316..9c1e6f03a4c7f 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -227,6 +227,18 @@ static void test_callback_ra(sd_ndisc *nd, sd_ndisc_event_t event, void *message sd_event_exit(e, 0); } +static void test_callback_count( + sd_ndisc *nd, + sd_ndisc_event_t event, + void *message, + void *userdata) { + + unsigned *count = ASSERT_PTR(userdata); + + if (event == SD_NDISC_EVENT_ROUTER) + (*count)++; +} + static int on_recv_rs(sd_event_source *s, int fd, uint32_t revents, void *userdata) { _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; assert_se(icmp6_packet_receive(fd, &packet) >= 0); @@ -234,6 +246,27 @@ static int on_recv_rs(sd_event_source *s, int fd, uint32_t revents, void *userda return send_ra(0); } +TEST(rs_ifindex_mismatch) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; + unsigned count = 0; + + assert_se(sd_event_new(&e) >= 0); + assert_se(sd_ndisc_new(&nd) >= 0); + assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0); + assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); + assert_se(sd_ndisc_set_callback(nd, test_callback_count, &count) >= 0); + assert_se(sd_ndisc_start(nd) >= 0); + + test_ifindex = 43; + send_ra(0); + assert_se(sd_event_run(e, UINT64_MAX) >= 0); + assert_se(count == 0); + + assert_se(sd_ndisc_stop(nd) >= 0); + test_fd[1] = safe_close(test_fd[1]); +} + TEST(rs) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; diff --git a/src/libsystemd-network/test-ndisc-send.c b/src/libsystemd-network/test-ndisc-send.c index 247cc0ec16cfb..87b8abefd58f0 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,157 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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, opts.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(opts.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, opts.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.", opts.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(opts.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(opts.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(opts.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(opts.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(opts.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(opts.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(opts.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(opts.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(opts.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(opts.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(opts.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, opts.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, opts.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(opts.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(opts.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(opts.arg, &p, &len); if (r < 0) return log_error_errno(r, "Failed to parse redirected header: %m"); @@ -272,20 +232,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(opts.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-network/test-sd-dhcp-lease.c b/src/libsystemd-network/test-sd-dhcp-lease.c deleted file mode 100644 index 32f9a6b9a3465..0000000000000 --- a/src/libsystemd-network/test-sd-dhcp-lease.c +++ /dev/null @@ -1,82 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "dhcp-lease-internal.h" -#include "strv.h" -#include "tests.h" - -/* According to RFC1035 section 4.1.4, a domain name in a message can be either: - * - a sequence of labels ending in a zero octet - * - a pointer - * - a sequence of labels ending with a pointer - */ -TEST(dhcp_lease_parse_search_domains_basic) { - int r; - _cleanup_strv_free_ char **domains = NULL; - static const uint8_t optionbuf[] = { - 0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00, - 0x04, 'A', 'B', 'C', 'D', 0x03, 'E', 'F', 'G', 0x00, - }; - - r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains); - assert_se(r == 2); - assert_se(streq(domains[0], "FOO.BAR")); - assert_se(streq(domains[1], "ABCD.EFG")); -} - -TEST(dhcp_lease_parse_search_domains_ptr) { - int r; - _cleanup_strv_free_ char **domains = NULL; - static const uint8_t optionbuf[] = { - 0x03, 'F', 'O', 'O', 0x00, 0xC0, 0x00, - }; - - r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains); - assert_se(r == 2); - assert_se(streq(domains[0], "FOO")); - assert_se(streq(domains[1], "FOO")); -} - -TEST(dhcp_lease_parse_search_domains_labels_and_ptr) { - int r; - _cleanup_strv_free_ char **domains = NULL; - static const uint8_t optionbuf[] = { - 0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00, - 0x03, 'A', 'B', 'C', 0xC0, 0x04, - }; - - r = dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains); - assert_se(r == 2); - assert_se(streq(domains[0], "FOO.BAR")); - assert_se(streq(domains[1], "ABC.BAR")); -} - -/* Tests for exceptions. */ - -TEST(dhcp_lease_parse_search_domains_no_data) { - _cleanup_strv_free_ char **domains = NULL; - static const uint8_t optionbuf[3] = {0, 0, 0}; - - assert_se(dhcp_lease_parse_search_domains(NULL, 0, &domains) == -EBADMSG); - assert_se(dhcp_lease_parse_search_domains(optionbuf, 0, &domains) == -EBADMSG); -} - -TEST(dhcp_lease_parse_search_domains_loops) { - _cleanup_strv_free_ char **domains = NULL; - static const uint8_t optionbuf[] = { - 0x03, 'F', 'O', 'O', 0x00, 0x03, 'B', 'A', 'R', 0xC0, 0x06, - }; - - assert_se(dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf), &domains) == -EBADMSG); -} - -TEST(dhcp_lease_parse_search_domains_wrong_len) { - _cleanup_strv_free_ char **domains = NULL; - static const uint8_t optionbuf[] = { - 0x03, 'F', 'O', 'O', 0x03, 'B', 'A', 'R', 0x00, - 0x04, 'A', 'B', 'C', 'D', 0x03, 'E', 'F', 'G', 0x00, - }; - - assert_se(dhcp_lease_parse_search_domains(optionbuf, sizeof(optionbuf) - 5, &domains) == -EBADMSG); -} - -DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/libsystemd-network/test-tlv-util.c b/src/libsystemd-network/test-tlv-util.c new file mode 100644 index 0000000000000..4747afa98dfe5 --- /dev/null +++ b/src/libsystemd-network/test-tlv-util.c @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dhcp-protocol.h" + +#include "hashmap.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "random-util.h" +#include "tests.h" +#include "tlv-util.h" + +TEST(tlv_constant) { + ASSERT_EQ(TLV_TAG_PAD, (uint32_t) SD_DHCP_OPTION_PAD); + ASSERT_EQ(TLV_TAG_END, (uint32_t) SD_DHCP_OPTION_END); +} + +TEST(tlv) { + _cleanup_(tlv_done) TLV tlv = TLV_INIT(TLV_DHCP4); + + _cleanup_(iovec_done) struct iovec data0 = {}, data1 = {}, data2a = {}, data2b = {}, data3 = {}, data4 = {}; + ASSERT_OK(random_bytes_allocate_iovec(0, &data0)); + ASSERT_OK(random_bytes_allocate_iovec(111, &data1)); + ASSERT_OK(random_bytes_allocate_iovec(123, &data2a)); + ASSERT_OK(random_bytes_allocate_iovec(321, &data2b)); + ASSERT_OK(random_bytes_allocate_iovec(333, &data3)); + ASSERT_OK(random_bytes_allocate_iovec(444, &data4)); + + /* tlv_append() */ + ASSERT_OK(tlv_append(&tlv, 10, data0.iov_len, data0.iov_base)); + ASSERT_OK(tlv_append(&tlv, 11, data1.iov_len, data1.iov_base)); + ASSERT_OK(tlv_append(&tlv, 22, data2a.iov_len, data2a.iov_base)); + ASSERT_OK(tlv_append(&tlv, 22, data2b.iov_len, data2b.iov_base)); + ASSERT_OK(tlv_append(&tlv, 33, data3.iov_len, data3.iov_base)); + ASSERT_OK(tlv_append(&tlv, 44, data4.iov_len, data4.iov_base)); + ASSERT_ERROR(tlv_append(&tlv, 0x00, data4.iov_len, data4.iov_base), EINVAL); + ASSERT_ERROR(tlv_append(&tlv, 0xFF, data4.iov_len, data4.iov_base), EINVAL); + ASSERT_EQ(hashmap_size(tlv.entries), 5u); + + /* tlv_remove() */ + tlv_remove(&tlv, 44); + ASSERT_EQ(hashmap_size(tlv.entries), 4u); + tlv_remove(&tlv, 55); + ASSERT_EQ(hashmap_size(tlv.entries), 4u); + + /* tlv_append_tlv() */ + _cleanup_(tlv_done) TLV tlv_copy = TLV_INIT(TLV_DHCP4); + ASSERT_ERROR(tlv_append_tlv(&tlv_copy, &tlv_copy), EINVAL); + ASSERT_OK(tlv_append_tlv(&tlv_copy, NULL)); + ASSERT_OK(tlv_append_tlv(&tlv_copy, &tlv)); + ASSERT_EQ(hashmap_size(tlv_copy.entries), hashmap_size(tlv.entries)); + + /* tlv_isempty() */ + ASSERT_TRUE(tlv_isempty(NULL)); + ASSERT_TRUE(tlv_isempty(&TLV_INIT(TLV_DHCP4))); + ASSERT_FALSE(tlv_isempty(&tlv)); + + /* tlv_contains() */ + ASSERT_TRUE(tlv_contains(&tlv, 10)); + ASSERT_TRUE(tlv_contains(&tlv, 11)); + ASSERT_TRUE(tlv_contains(&tlv, 22)); + ASSERT_TRUE(tlv_contains(&tlv, 33)); + ASSERT_FALSE(tlv_contains(&tlv, 44)); + + /* tlv_get_all() */ + struct iovec_wrapper *iovw; + + iovw = ASSERT_NOT_NULL(tlv_get_all(&tlv, 10)); + ASSERT_EQ(iovw->count, 1u); + ASSERT_TRUE(iovec_equal(&iovw->iovec[0], &data0)); + + iovw = ASSERT_NOT_NULL(tlv_get_all(&tlv, 11)); + ASSERT_EQ(iovw->count, 1u); + ASSERT_TRUE(iovec_equal(&iovw->iovec[0], &data1)); + + iovw = ASSERT_NOT_NULL(tlv_get_all(&tlv, 22)); + ASSERT_EQ(iovw->count, 3u); + ASSERT_TRUE(iovec_equal(&iovw->iovec[0], &data2a)); + ASSERT_TRUE(iovec_equal(&iovw->iovec[1], &IOVEC_MAKE(data2b.iov_base, UINT8_MAX))); + ASSERT_TRUE(iovec_equal(&iovw->iovec[2], &IOVEC_SHIFT(&data2b, UINT8_MAX))); + + iovw = ASSERT_NOT_NULL(tlv_get_all(&tlv, 33)); + ASSERT_EQ(iovw->count, 2u); + ASSERT_TRUE(iovec_equal(&iovw->iovec[0], &IOVEC_MAKE(data3.iov_base, UINT8_MAX))); + ASSERT_TRUE(iovec_equal(&iovw->iovec[1], &IOVEC_SHIFT(&data3, UINT8_MAX))); + + ASSERT_NULL(tlv_get_all(&tlv, 44)); + + /* tlv_get_full() */ + struct iovec iov; + + ASSERT_OK(tlv_get(&tlv, 10, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &data0)); + ASSERT_OK(tlv_get_full(&tlv, 10, data0.iov_len, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &data0)); + ASSERT_ERROR(tlv_get_full(&tlv, 10, 123, &iov), ENODATA); + + ASSERT_OK(tlv_get(&tlv, 11, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &data1)); + ASSERT_OK(tlv_get_full(&tlv, 11, data1.iov_len, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &data1)); + ASSERT_ERROR(tlv_get_full(&tlv, 11, 123, &iov), ENODATA); + + ASSERT_OK(tlv_get(&tlv, 22, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &data2a)); + ASSERT_OK(tlv_get_full(&tlv, 22, data2a.iov_len, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &data2a)); + ASSERT_ERROR(tlv_get_full(&tlv, 22, data2b.iov_len, &iov), ENODATA); + ASSERT_OK(tlv_get_full(&tlv, 22, UINT8_MAX, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE(data2b.iov_base, UINT8_MAX))); + ASSERT_OK(tlv_get_full(&tlv, 22, data2b.iov_len - UINT8_MAX, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_SHIFT(&data2b, UINT8_MAX))); + + ASSERT_OK(tlv_get(&tlv, 33, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE(data3.iov_base, UINT8_MAX))); + ASSERT_ERROR(tlv_get_full(&tlv, 33, data3.iov_len, &iov), ENODATA); + ASSERT_OK(tlv_get_full(&tlv, 33, UINT8_MAX, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE(data3.iov_base, UINT8_MAX))); + ASSERT_OK(tlv_get_full(&tlv, 33, data3.iov_len - UINT8_MAX, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_SHIFT(&data3, UINT8_MAX))); + + ASSERT_ERROR(tlv_get(&tlv, 44, NULL), ENODATA); + + /* tlv_get_alloc() */ + _cleanup_(iovec_done) struct iovec v = {}; + + ASSERT_OK(tlv_get_alloc(&tlv, 10, &v)); + ASSERT_TRUE(iovec_equal(&v, &data0)); + iovec_done(&v); + + ASSERT_OK(tlv_get_alloc(&tlv, 11, &v)); + ASSERT_TRUE(iovec_equal(&v, &data1)); + iovec_done(&v); + + ASSERT_OK(tlv_get_alloc(&tlv, 22, &v)); + ASSERT_EQ(v.iov_len, data2a.iov_len + data2b.iov_len); + ASSERT_EQ(memcmp(v.iov_base, data2a.iov_base, data2a.iov_len), 0); + ASSERT_EQ(memcmp((uint8_t*) v.iov_base + data2a.iov_len, data2b.iov_base, data2b.iov_len), 0); + iovec_done(&v); + + ASSERT_OK(tlv_get_alloc(&tlv, 33, &v)); + ASSERT_TRUE(iovec_equal(&v, &data3)); + iovec_done(&v); + + ASSERT_ERROR(tlv_get_alloc(&tlv, 44, NULL), ENODATA); + + /* tlv_size() */ + size_t sz = tlv_size(&tlv); + /* The tlv contains the 7 entries with a 2-byte header: + * tag 10: 1 entry, tag 11: 1 entry, tag 22: 3 entries, tag 33: 2 entries = 7 entries total. */ + ASSERT_EQ(sz, 7 * 2 + data0.iov_len + data1.iov_len + data2a.iov_len + data2b.iov_len + data3.iov_len + 1); + + /* tlv_build() */ + ASSERT_OK(tlv_build(&tlv, &v)); + ASSERT_EQ(v.iov_len, sz); + uint8_t *p = v.iov_base; + ASSERT_EQ(*p++, 10u); + ASSERT_EQ(*p++, data0.iov_len); + + ASSERT_EQ(*p++, 11u); + ASSERT_EQ(*p++, data1.iov_len); + ASSERT_EQ(memcmp(p, data1.iov_base, data1.iov_len), 0); + p += data1.iov_len; + + ASSERT_EQ(*p++, 22u); + ASSERT_EQ(*p++, data2a.iov_len); + ASSERT_EQ(memcmp(p, data2a.iov_base, data2a.iov_len), 0); + p += data2a.iov_len; + + ASSERT_EQ(*p++, 22u); + ASSERT_EQ(*p++, UINT8_MAX); + ASSERT_EQ(memcmp(p, data2b.iov_base, UINT8_MAX), 0); + p += UINT8_MAX; + + ASSERT_EQ(*p++, 22u); + ASSERT_EQ(*p++, data2b.iov_len - UINT8_MAX); + ASSERT_EQ(memcmp(p, (uint8_t*) data2b.iov_base + UINT8_MAX, data2b.iov_len - UINT8_MAX), 0); + p += data2b.iov_len - UINT8_MAX; + + ASSERT_EQ(*p++, 33u); + ASSERT_EQ(*p++, UINT8_MAX); + ASSERT_EQ(memcmp(p, data3.iov_base, UINT8_MAX), 0); + p += UINT8_MAX; + + ASSERT_EQ(*p++, 33u); + ASSERT_EQ(*p++, data3.iov_len - UINT8_MAX); + ASSERT_EQ(memcmp(p, (uint8_t*) data3.iov_base + UINT8_MAX, data3.iov_len - UINT8_MAX), 0); + p += data3.iov_len - UINT8_MAX; + + ASSERT_EQ(*p, 255u); + + /* tlv_new() and tlv_parse() */ + _cleanup_(tlv_unrefp) TLV *tlv2 = ASSERT_NOT_NULL(tlv_new(TLV_DHCP4 | TLV_TEMPORARY)); + ASSERT_OK(tlv_parse(tlv2, &v)); + ASSERT_EQ(hashmap_size(tlv.entries), hashmap_size(tlv2->entries)); + void *tagp; + HASHMAP_FOREACH_KEY(iovw, tagp, tlv.entries) { + struct iovec_wrapper *iovw2 = ASSERT_PTR(hashmap_get(tlv2->entries, tagp)); + ASSERT_TRUE(iovw_equal(iovw, iovw2)); + } + + /* tlv_build() again, and check the reproducibility. */ + _cleanup_(iovec_done) struct iovec v2 = {}; + ASSERT_OK(tlv_build(tlv2, &v2)); + ASSERT_TRUE(iovec_equal(&v, &v2)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd-network/tlv-util.c b/src/libsystemd-network/tlv-util.c new file mode 100644 index 0000000000000..488b6c5bd1e42 --- /dev/null +++ b/src/libsystemd-network/tlv-util.c @@ -0,0 +1,574 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "hashmap.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "json-util.h" +#include "tlv-util.h" +#include "unaligned.h" + +#define TLV_MAX_ENTRIES 4096u + +TLVFlag tlv_flags_verify(TLVFlag flags) { + assert(IN_SET(flags & _TLV_TAG_MASK, TLV_TAG_U8, TLV_TAG_U16, TLV_TAG_U32)); + assert(IN_SET(flags & _TLV_LENGTH_MASK, TLV_LENGTH_U8, TLV_LENGTH_U16, TLV_LENGTH_U32)); + + /* TLV_PAD and TLV_END are for DHCPv4 options, hence here we assume TLV_TAG_U8 is set. */ + assert(!FLAGS_SET(flags, TLV_PAD) || FLAGS_SET(flags, TLV_TAG_U8)); + assert(!FLAGS_SET(flags, TLV_END) || FLAGS_SET(flags, TLV_TAG_U8)); + + /* When we requested to append the END tag, then we should understand the END tag on parse. */ + assert(!FLAGS_SET(flags, TLV_APPEND_END) || FLAGS_SET(flags, TLV_END)); + + return flags; +} + +void tlv_done(TLV *tlv) { + assert(tlv); + + tlv->entries = hashmap_free(tlv->entries); + tlv->n_entries = 0; +} + +static TLV* tlv_free(TLV *tlv) { + if (!tlv) + return NULL; + + tlv_done(tlv); + return mfree(tlv); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(TLV, tlv, tlv_free); + +TLV* tlv_new(TLVFlag flags) { + TLV *tlv = new(TLV, 1); + if (!tlv) + return NULL; + + *tlv = TLV_INIT(flags); + return tlv; +} + +bool tlv_isempty(const TLV *tlv) { + return !tlv || hashmap_isempty(tlv->entries); +} + +struct iovec_wrapper* tlv_get_all(const TLV *tlv, uint32_t tag) { + assert(tlv); + return hashmap_get(tlv->entries, UINT32_TO_PTR(tag)); +} + +int tlv_get_full(const TLV *tlv, uint32_t tag, size_t length, struct iovec *ret) { + assert(tlv); + + /* Do not free the result iovec, the data is still owned by TLV (or the original input data when + * TLV_TEMPORARY is set). */ + + struct iovec_wrapper *iovw = tlv_get_all(tlv, tag); + if (iovw_isempty(iovw)) + return -ENODATA; + + /* When multiple entries exist, use the first one matching the length. */ + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + if (length != SIZE_MAX && iov->iov_len != length) + continue; + + if (ret) + *ret = *iov; + return 0; + } + + return -ENODATA; +} + +int tlv_get_alloc(const TLV *tlv, uint32_t tag, struct iovec *ret) { + assert(tlv); + + /* Free the result iovec. */ + + struct iovec_wrapper *iovw = tlv_get_all(tlv, tag); + if (iovw_isempty(iovw)) + return -ENODATA; + + if (!ret) + return 0; + + if (FLAGS_SET(tlv->flags, TLV_MERGE)) + return iovw_concat(iovw, ret); + + /* When TLV_MERGE is unset, provides the first entry. */ + if (!iovec_memdup(&iovw->iovec[0], ret)) + return -ENOMEM; + + return 0; +} + +void tlv_remove(TLV *tlv, uint32_t tag) { + assert(tlv); + + struct iovec_wrapper *iovw = hashmap_remove(tlv->entries, UINT32_TO_PTR(tag)); + if (!iovw) + return; + + assert(tlv->n_entries >= iovw->count); + tlv->n_entries -= iovw->count; + + if (FLAGS_SET(tlv->flags, TLV_TEMPORARY)) + iovw_free(iovw); + else + iovw_free_free(iovw); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + tlv_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + struct iovec_wrapper, + iovw_free); + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + tlv_hash_ops_free, + void, + trivial_hash_func, + trivial_compare_func, + struct iovec_wrapper, + iovw_free_free); + +static int tlv_append_impl(TLV *tlv, uint32_t tag, size_t length, const void *data) { + int r; + + assert(tlv); + assert(length == 0 || data); + + if (tlv->n_entries >= TLV_MAX_ENTRIES) + return -E2BIG; + + if (FLAGS_SET(tlv->flags, TLV_TEMPORARY)) { + struct iovec_wrapper *e = tlv_get_all(tlv, tag); + if (e) { + r = iovw_put_full(e, /* accept_zero= */ true, (void*) data, length); + if (r < 0) + return r; + } else { + _cleanup_(iovw_freep) struct iovec_wrapper *v = new0(struct iovec_wrapper, 1); + if (!v) + return -ENOMEM; + + r = iovw_put_full(v, /* accept_zero= */ true, (void*) data, length); + if (r < 0) + return r; + + r = hashmap_ensure_put(&tlv->entries, &tlv_hash_ops, UINT32_TO_PTR(tag), v); + if (r < 0) + return r; + + TAKE_PTR(v); + } + } else { + struct iovec_wrapper *e = tlv_get_all(tlv, tag); + if (e) { + r = iovw_extend_full(e, /* accept_zero= */ true, data, length); + if (r < 0) + return r; + } else { + _cleanup_(iovw_free_freep) struct iovec_wrapper *v = new0(struct iovec_wrapper, 1); + if (!v) + return -ENOMEM; + + r = iovw_extend_full(v, /* accept_zero= */ true, data, length); + if (r < 0) + return r; + + r = hashmap_ensure_put(&tlv->entries, &tlv_hash_ops_free, UINT32_TO_PTR(tag), v); + if (r < 0) + return r; + + TAKE_PTR(v); + } + } + + tlv->n_entries++; + return 0; +} + +int tlv_append(TLV *tlv, uint32_t tag, size_t length, const void *data) { + int r; + + assert(tlv); + assert(length == 0 || data); + + switch (tlv->flags & _TLV_TAG_MASK) { + case TLV_TAG_U8: + if (tag > UINT8_MAX) + return -EINVAL; + break; + case TLV_TAG_U16: + if (tag > UINT16_MAX) + return -EINVAL; + break; + case TLV_TAG_U32: + break; + default: + assert_not_reached(); + } + + if ((FLAGS_SET(tlv->flags, TLV_PAD) && tag == TLV_TAG_PAD) || + (FLAGS_SET(tlv->flags, TLV_END) && tag == TLV_TAG_END)) + return -EINVAL; + + size_t max_length; + switch (tlv->flags & _TLV_LENGTH_MASK) { + case TLV_LENGTH_U8: + max_length = UINT8_MAX; + break; + case TLV_LENGTH_U16: + max_length = UINT16_MAX; + break; + case TLV_LENGTH_U32: + max_length = UINT32_MAX; + break; + default: + assert_not_reached(); + } + + if (FLAGS_SET(tlv->flags, TLV_MERGE)) { + /* If TLV_MERGE is set and the length is larger than the allowed maximum, then split the data + * and store them in multiple entries. + * + * Note, if tlv_append_impl() fails below, we do not rollback the entries, hence the caller + * of this function needs to discard the entire data in that case. */ + const uint8_t *p = data; + while (length > max_length) { + r = tlv_append_impl(tlv, tag, max_length, p); + if (r < 0) + return r; + + p += max_length; + length -= max_length; + } + + return tlv_append_impl(tlv, tag, length, p); + } + + /* Otherwise, refuse too long data. */ + if (length > max_length) + return -EINVAL; + + return tlv_append_impl(tlv, tag, length, data); +} + +int tlv_append_iov(TLV *tlv, uint32_t tag, const struct iovec *iov) { + assert(tlv); + assert(iovec_is_valid(iov)); + + return tlv_append(tlv, tag, iov ? iov->iov_len : 0, iov ? iov->iov_base : NULL); +} + +int tlv_append_tlv(TLV *tlv, const TLV *source) { + int r; + + assert(tlv); + + /* Note, this does not rollback entries on failure, hence the caller of this function needs to + * discard the entire data in that case. */ + + if (!source) + return 0; + + if (source == tlv) + return -EINVAL; + + void *tagp; + struct iovec_wrapper *iovw; + HASHMAP_FOREACH_KEY(iovw, tagp, source->entries) { + uint32_t tag = PTR_TO_UINT32(tagp); + + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + r = tlv_append(tlv, tag, iov->iov_len, iov->iov_base); + if (r < 0) + return r; + } + } + + return 0; +} + +int tlv_parse(TLV *tlv, const struct iovec *iov) { + int r; + + assert(tlv); + assert(iovec_is_valid(iov)); + + /* Note, this does not rollback entries on failure, hence the caller of this function needs to + * discard the entire data in that case. */ + + if (!iovec_is_set(iov)) + return 0; + + for (struct iovec i = *iov; iovec_is_set(&i); ) { + uint32_t tag; + switch (tlv->flags & _TLV_TAG_MASK) { + case TLV_TAG_U8: + if (i.iov_len < sizeof(uint8_t)) + return -EBADMSG; + tag = *(uint8_t*) i.iov_base; + iovec_inc(&i, sizeof(uint8_t)); + break; + case TLV_TAG_U16: + if (i.iov_len < sizeof(uint16_t)) + return -EBADMSG; + tag = unaligned_read_be16(i.iov_base); + iovec_inc(&i, sizeof(uint16_t)); + break; + case TLV_TAG_U32: + if (i.iov_len < sizeof(uint32_t)) + return -EBADMSG; + tag = unaligned_read_be32(i.iov_base); + iovec_inc(&i, sizeof(uint32_t)); + break; + default: + assert_not_reached(); + } + + if (FLAGS_SET(tlv->flags, TLV_PAD) && tag == TLV_TAG_PAD) + continue; + if (FLAGS_SET(tlv->flags, TLV_END) && tag == TLV_TAG_END) + break; + + size_t len; + switch (tlv->flags & _TLV_LENGTH_MASK) { + case TLV_LENGTH_U8: + if (i.iov_len < sizeof(uint8_t)) + return -EBADMSG; + len = *(uint8_t*) i.iov_base; + iovec_inc(&i, sizeof(uint8_t)); + break; + case TLV_LENGTH_U16: + if (i.iov_len < sizeof(uint16_t)) + return -EBADMSG; + len = unaligned_read_be16(i.iov_base); + iovec_inc(&i, sizeof(uint16_t)); + break; + case TLV_LENGTH_U32: + if (i.iov_len < sizeof(uint32_t)) + return -EBADMSG; + len = unaligned_read_be32(i.iov_base); + iovec_inc(&i, sizeof(uint32_t)); + break; + default: + assert_not_reached(); + } + + if (i.iov_len < len) + return -EBADMSG; + + r = tlv_append_impl(tlv, tag, len, i.iov_base); + if (r < 0) + return r; + + iovec_inc(&i, len); + } + + return 0; +} + +size_t tlv_size(const TLV *tlv) { + assert(tlv); + + size_t header_sz; + switch (tlv->flags & _TLV_TAG_MASK) { + case TLV_TAG_U8: + header_sz = sizeof(uint8_t); + break; + case TLV_TAG_U16: + header_sz = sizeof(uint16_t); + break; + case TLV_TAG_U32: + header_sz = sizeof(uint32_t); + break; + default: + assert_not_reached(); + } + + switch (tlv->flags & _TLV_LENGTH_MASK) { + case TLV_LENGTH_U8: + header_sz += sizeof(uint8_t); + break; + case TLV_LENGTH_U16: + header_sz += sizeof(uint16_t); + break; + case TLV_LENGTH_U32: + header_sz += sizeof(uint32_t); + break; + default: + assert_not_reached(); + } + + size_t sz = FLAGS_SET(tlv->flags, TLV_APPEND_END); + + struct iovec_wrapper *iovw; + HASHMAP_FOREACH(iovw, tlv->entries) { + if (size_multiply_overflow(header_sz, iovw->count)) + return SIZE_MAX; + + sz = size_add(sz, size_add(header_sz * iovw->count, iovw_size(iovw))); + } + + return sz; +} + +int tlv_build(const TLV *tlv, struct iovec *ret) { + int r; + + assert(tlv); + assert(ret); + + size_t sz = tlv_size(tlv); + if (sz == SIZE_MAX) + return -ENOBUFS; + + _cleanup_free_ uint8_t *buf = new(uint8_t, sz); + if (!buf) + return -ENOMEM; + + /* Sort by tags, for reproducibility. */ + _cleanup_free_ void **sorted = NULL; + size_t n; + r = hashmap_dump_keys_sorted(tlv->entries, &sorted, &n); + if (r < 0) + return r; + + uint8_t *p = buf; + FOREACH_ARRAY(tagp, sorted, n) { + uint32_t tag = PTR_TO_UINT32(*tagp); + struct iovec_wrapper *iovw = ASSERT_PTR(tlv_get_all(tlv, tag)); + + if ((FLAGS_SET(tlv->flags, TLV_PAD) && tag == TLV_TAG_PAD) || + (FLAGS_SET(tlv->flags, TLV_END) && tag == TLV_TAG_END)) + return -EINVAL; + + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + switch (tlv->flags & _TLV_TAG_MASK) { + case TLV_TAG_U8: + if (tag > UINT8_MAX) + return -EINVAL; + *p++ = tag; + break; + case TLV_TAG_U16: + if (tag > UINT16_MAX) + return -EINVAL; + unaligned_write_be16(p, tag); + p += sizeof(uint16_t); + break; + case TLV_TAG_U32: + unaligned_write_be32(p, tag); + p += sizeof(uint32_t); + break; + default: + assert_not_reached(); + } + + switch (tlv->flags & _TLV_LENGTH_MASK) { + case TLV_LENGTH_U8: + if (iov->iov_len > UINT8_MAX) + return -EINVAL; + *p++ = iov->iov_len; + break; + case TLV_LENGTH_U16: + if (iov->iov_len > UINT16_MAX) + return -EINVAL; + unaligned_write_be16(p, iov->iov_len); + p += sizeof(uint16_t); + break; + case TLV_LENGTH_U32: + if (iov->iov_len > UINT32_MAX) + return -EINVAL; + unaligned_write_be32(p, iov->iov_len); + p += sizeof(uint32_t); + break; + default: + assert_not_reached(); + } + + p = mempcpy_safe(p, iov->iov_base, iov->iov_len); + } + } + + if (FLAGS_SET(tlv->flags, TLV_APPEND_END)) + *p++ = TLV_TAG_END; + + assert(sz == (size_t) (p - buf)); + + *ret = IOVEC_MAKE(TAKE_PTR(buf), sz); + return 0; +} + +int tlv_build_json(const TLV *tlv, sd_json_variant **ret) { + int r; + + assert(tlv); + assert(ret); + + /* Sort by tags, for reproducibility. */ + _cleanup_free_ void **sorted = NULL; + size_t n; + r = hashmap_dump_keys_sorted(tlv->entries, &sorted, &n); + if (r < 0) + return r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + FOREACH_ARRAY(tagp, sorted, n) { + uint32_t tag = PTR_TO_UINT32(*tagp); + struct iovec_wrapper *iovw = ASSERT_PTR(tlv_get_all(tlv, tag)); + + FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { + r = sd_json_variant_append_arraybo( + &v, + SD_JSON_BUILD_PAIR_UNSIGNED("tag", tag), + JSON_BUILD_PAIR_IOVEC_HEX("data", iov)); + if (r < 0) + return r; + } + } + + *ret = TAKE_PTR(v); + return 0; +} + +typedef struct TLVParam { + uint32_t tag; + struct iovec data; +} TLVParam; + +static void tlv_param_done(TLVParam *p) { + iovec_done(&p->data); +} + +int tlv_parse_json(TLV *tlv, sd_json_variant *v) { + static const sd_json_dispatch_field dispatch_table[] = { + { "tag", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(TLVParam, tag), SD_JSON_MANDATORY }, + { "data", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(TLVParam, data), SD_JSON_MANDATORY }, + {}, + }; + + int r; + + assert(tlv); + assert(v); + + sd_json_variant *e; + JSON_VARIANT_ARRAY_FOREACH(e, v) { + _cleanup_(tlv_param_done) TLVParam p = {}; + r = sd_json_dispatch(e, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return r; + + r = tlv_append(tlv, p.tag, p.data.iov_len, p.data.iov_base); + if (r < 0) + return r; + } + + return 0; +} diff --git a/src/libsystemd-network/tlv-util.h b/src/libsystemd-network/tlv-util.h new file mode 100644 index 0000000000000..1bdcadccb805a --- /dev/null +++ b/src/libsystemd-network/tlv-util.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +#define TLV_TAG_PAD UINT32_C(0) +#define TLV_TAG_END UINT32_C(0xFF) + +typedef enum TLVFlag { + TLV_TAG_U8 = 1 << 0, + TLV_TAG_U16 = 1 << 1, + TLV_TAG_U32 = 1 << 2, + _TLV_TAG_MASK = TLV_TAG_U8 | TLV_TAG_U16 | TLV_TAG_U32, + TLV_LENGTH_U8 = 1 << 3, + TLV_LENGTH_U16 = 1 << 4, + TLV_LENGTH_U32 = 1 << 5, + _TLV_LENGTH_MASK = TLV_LENGTH_U8 | TLV_LENGTH_U16 | TLV_LENGTH_U32, + TLV_PAD = 1 << 6, /* If set, tag == 0 is a pad, and does not have the length field. */ + TLV_END = 1 << 7, /* If set, tag == 0xFF is a sign of the end of the sequence. */ + TLV_APPEND_END = 1 << 8, /* If set, append the END tag at the end of the sequence on build. */ + TLV_MERGE = 1 << 9, /* If set, tlv_get_alloc() merges them, and tlv_append() split long data. */ + TLV_TEMPORARY = 1 << 10, /* If set, tlv_append() and tlv_parse() do not copy the data. */ + + /* DHCPv4 options. */ + TLV_DHCP4 = TLV_TAG_U8 | TLV_LENGTH_U8 | TLV_PAD | TLV_END | TLV_APPEND_END | TLV_MERGE, + /* Used for DHCPv4 sub-options, e.g. + * DHCPv4 Vendor Specific Information sub-option (43), + * DHCPv4 Relay Agent Information sub-option (82), or + * DHCPv4 Vendor-Identifying Vendor Specific Information sub-sub-option (125). + * Note that the PAD is not mentioned in RFC, but some implementations use it, hence let's gracefully + * handle it. Also note that the END tag is prohibited in most options, but we also gracefully handle + * it on parse, but of course do not append it on build. */ + TLV_DHCP4_SUBOPTION + = TLV_TAG_U8 | TLV_LENGTH_U8 | TLV_PAD | TLV_END, + /* DHCPv4 Vendor-Identifying Vendor Class sub-option (124) and + * DHCPv4 Vendor-Identifying Vendor Specific Information sub-option (125). + * The tag is called 'enterprise-number', and in uint32. */ + TLV_DHCP4_VENDOR_IDENTIFYING_OPTION + = TLV_TAG_U32 | TLV_LENGTH_U8 | TLV_MERGE, +} TLVFlag; + +typedef struct TLV { + unsigned n_ref; + TLVFlag flags; + unsigned n_entries; + Hashmap *entries; +} TLV; + +#define TLV_INIT(f) \ + (TLV) { \ + .n_ref = 1, \ + .flags = tlv_flags_verify(f), \ + } + +TLVFlag tlv_flags_verify(TLVFlag flags); + +void tlv_done(TLV *tlv); +TLV* tlv_ref(TLV *p); +TLV* tlv_unref(TLV *p); +DEFINE_TRIVIAL_CLEANUP_FUNC(TLV*, tlv_unref); +TLV* tlv_new(TLVFlag flags); + +bool tlv_isempty(const TLV *tlv); + +struct iovec_wrapper* tlv_get_all(const TLV *tlv, uint32_t tag); +static inline bool tlv_contains(const TLV *tlv, uint32_t tag) { + return tlv_get_all(tlv, tag); +} +int tlv_get_full(const TLV *tlv, uint32_t tag, size_t length, struct iovec *ret); +static inline int tlv_get(const TLV *tlv, uint32_t tag, struct iovec *ret) { + return tlv_get_full(tlv, tag, SIZE_MAX, ret); +} +int tlv_get_alloc(const TLV *tlv, uint32_t tag, struct iovec *ret); + +void tlv_remove(TLV *tlv, uint32_t tag); +int tlv_append(TLV *tlv, uint32_t tag, size_t length, const void *data); +int tlv_append_iov(TLV *tlv, uint32_t tag, const struct iovec *iov); +int tlv_append_tlv(TLV *tlv, const TLV *source); + +int tlv_parse(TLV *tlv, const struct iovec *iov); +size_t tlv_size(const TLV *tlv); +int tlv_build(const TLV *tlv, struct iovec *ret); + +int tlv_build_json(const TLV *tlv, sd_json_variant **ret); +int tlv_parse_json(TLV *tlv, sd_json_variant *v); diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index aa270a483a40e..6afc11d91b374 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -1090,3 +1090,17 @@ LIBSYSTEMD_260 { global: sd_session_get_extra_device_access; } LIBSYSTEMD_259; + +LIBSYSTEMD_261 { +global: + 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_json_parse_fd; + sd_varlink_call_and_upgrade; + sd_varlink_reply_and_upgrade; + sd_varlink_set_sentinel; +} LIBSYSTEMD_260; diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index 976f0e998766c..d1797722a42da 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -3,8 +3,7 @@ sd_journal_sources = files( 'sd-journal/audit-type.c', 'sd-journal/catalog.c', - 'sd-journal/fsprg.c', - 'sd-journal/journal-authenticate.c', + 'sd-journal/journal-authenticate-internal.c', 'sd-journal/journal-file.c', 'sd-journal/journal-send.c', 'sd-journal/journal-vacuum.c', @@ -33,6 +32,7 @@ sd_daemon_sources = files('sd-daemon/sd-daemon.c') ############################################################ sd_event_sources = files( + 'sd-event/event-future.c', 'sd-event/event-util.c', 'sd-event/sd-event.c', ) @@ -48,6 +48,7 @@ sd_bus_sources = files( 'sd-bus/bus-dump.c', 'sd-bus/bus-dump-json.c', 'sd-bus/bus-error.c', + 'sd-bus/bus-future.c', 'sd-bus/bus-internal.c', 'sd-bus/bus-introspect.c', 'sd-bus/bus-kernel.c', @@ -75,11 +76,20 @@ sd_device_sources = files( ############################################################ +sd_future_sources = files( + 'sd-future/fiber-io.c', + 'sd-future/fiber.c', + 'sd-future/sd-future.c', +) + +############################################################ + 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', ) @@ -134,8 +144,9 @@ libsystemd_sources = files( 'sd-resolve/sd-resolve.c', ) + sd_journal_sources + sd_id128_sources + sd_daemon_sources \ + sd_event_sources + sd_bus_sources + sd_device_sources \ - + sd_login_sources + sd_json_sources + sd_varlink_sources \ - + sd_path_sources + sd_netlink_sources + sd_network_sources + + sd_future_sources + sd_login_sources + sd_json_sources \ + + sd_varlink_sources + sd_path_sources + sd_netlink_sources \ + + sd_network_sources sources += libsystemd_sources @@ -149,9 +160,8 @@ libsystemd_static = static_library( c_args : libsystemd_c_args, link_with : [libc_wrapper_static, libbasic_static], - dependencies : [threads, - libm, - librt, + dependencies : [libm, + libucontext, userspace], build_by_default : false) @@ -173,97 +183,75 @@ libsystemd_pc = custom_target( ############################################################ -simple_tests += files( - 'sd-journal/test-audit-type.c', - 'sd-journal/test-catalog.c', - 'sd-journal/test-journal-file.c', - 'sd-journal/test-journal-init.c', - 'sd-journal/test-journal-match.c', - 'sd-journal/test-journal-send.c', - 'sd-journal/test-mmap-cache.c', -) - -libsystemd_tests += [ - { - 'sources' : files('sd-journal/test-journal-enum.c'), - 'timeout' : 360, - }, - { - 'sources' : files('sd-event/test-event.c'), - 'timeout' : 120, - } -] - -############################################################ - simple_tests += files( 'sd-bus/test-bus-creds.c', + 'sd-bus/test-bus-fiber.c', 'sd-bus/test-bus-introspect.c', 'sd-bus/test-bus-match.c', 'sd-bus/test-bus-vtable.c', 'sd-device/test-device-util.c', 'sd-device/test-sd-device-monitor.c', + 'sd-event/test-event-future.c', + 'sd-future/test-fiber.c', + 'sd-future/test-fiber-io.c', + 'sd-future/test-fiber-ops.c', + 'sd-hwdb/test-sd-hwdb.c', + 'sd-id128/test-id128.c', + 'sd-journal/test-audit-type.c', + 'sd-journal/test-catalog.c', + 'sd-journal/test-journal-file.c', 'sd-journal/test-journal-flush.c', + 'sd-journal/test-journal-init.c', 'sd-journal/test-journal-interleaving.c', + 'sd-journal/test-journal-match.c', + 'sd-journal/test-journal-send.c', 'sd-journal/test-journal-stream.c', 'sd-journal/test-journal.c', + 'sd-journal/test-mmap-cache.c', 'sd-login/test-login.c', 'sd-login/test-sd-login.c', 'sd-netlink/test-netlink.c', + 'sd-path/test-sd-path.c', ) libsystemd_tests += [ - { - 'sources' : files('sd-device/test-sd-device.c'), - 'dependencies' : [ threads, libmount_cflags ], - }, { 'sources' : files('sd-bus/test-bus-address.c'), - 'dependencies' : threads }, { 'sources' : files('sd-bus/test-bus-benchmark.c'), - 'dependencies' : threads, 'type' : 'manual', }, { 'sources' : files('sd-bus/test-bus-chat.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-cleanup.c'), - 'dependencies' : [threads, libseccomp_cflags], + 'dependencies' : libseccomp_cflags, }, { 'sources' : files('sd-bus/test-bus-marshal.c'), 'dependencies' : [ - libdbus, - libgio, - libglib, - libgobject, - libm, - threads, + libdbus_cflags, + libgio_cflags, + libglib_cflags, + libgobject_cflags, ], }, { 'sources' : files('sd-bus/test-bus-objects.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-peersockaddr.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-queue-ref-cycle.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-server.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-signature.c'), - 'dependencies' : threads, }, { 'sources' : files('sd-bus/test-bus-track.c'), @@ -271,9 +259,20 @@ libsystemd_tests += [ }, { 'sources' : files('sd-bus/test-bus-watch-bind.c'), - 'dependencies' : threads, 'timeout' : 120, }, + { + 'sources' : files('sd-device/test-sd-device.c'), + 'dependencies' : libmount_cflags, + }, + { + 'sources' : files('sd-event/test-event.c'), + 'timeout' : 120, + }, + { + 'sources' : files('sd-journal/test-journal-enum.c'), + 'timeout' : 360, + }, { 'sources' : files('sd-journal/test-journal-append.c'), 'type' : 'manual', @@ -286,11 +285,20 @@ libsystemd_tests += [ 'sources' : files('sd-journal/test-journal-verify.c'), 'timeout' : 90, }, + { + 'sources' : files('sd-json/test-json.c'), + 'dependencies' : libm, + }, { 'sources' : files('sd-resolve/test-resolve.c'), - 'dependencies' : threads, 'timeout' : 120, }, + { + 'sources' : files('sd-varlink/test-varlink.c'), + }, + { + 'sources' : files('sd-varlink/test-varlink-idl.c'), + }, ] if cxx_cmd != '' @@ -302,4 +310,5 @@ endif simple_fuzzers += files( 'sd-bus/fuzz-bus-match.c', 'sd-bus/fuzz-bus-message.c', + 'sd-hwdb/fuzz-hwdb.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..ae3e8061946b5 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) { @@ -72,6 +72,7 @@ static int json_transform_dict_array(sd_bus_message *m, sd_json_variant **ret) { CLEANUP_ARRAY(elements, n_elements, sd_json_variant_unref_many); for (;;) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *key = NULL; const char *contents; char type; @@ -94,11 +95,35 @@ static int json_transform_dict_array(sd_bus_message *m, sd_json_variant **ret) { if (r < 0) return r; - r = json_transform_one(m, elements + n_elements); + r = json_transform_one(m, &key); if (r < 0) return r; - n_elements++; + /* JSON only supports string keys in objects, but D-Bus specification is a bit more lenient + * and allows dict entries to have any basic type as key. Let's stringify allowed non-string + * keys so that we can represent them as JSON objects. */ + if (!sd_json_variant_is_string(key)) { + _cleanup_free_ char *s = NULL; + + if (!IN_SET(sd_json_variant_type(key), + SD_JSON_VARIANT_BOOLEAN, + SD_JSON_VARIANT_INTEGER, + SD_JSON_VARIANT_REAL, + SD_JSON_VARIANT_UNSIGNED)) + return -EINVAL; + + r = sd_json_variant_format(key, /* flags= */ 0, &s); + if (r < 0) + return r; + + key = sd_json_variant_unref(key); + + r = sd_json_variant_new_string(&key, s); + if (r < 0) + return r; + } + + elements[n_elements++] = TAKE_PTR(key); r = json_transform_one(m, elements + n_elements); if (r < 0) @@ -287,8 +312,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 +350,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 +366,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..bee843e33806d 100644 --- a/src/libsystemd/sd-bus/bus-error.c +++ b/src/libsystemd/sd-bus/bus-error.c @@ -83,22 +83,17 @@ static int bus_error_name_to_errno(const char *name) { } } - const sd_bus_error_map *elf_map = ALIGN_PTR(__start_SYSTEMD_BUS_ERROR_MAP); - while (elf_map < __stop_SYSTEMD_BUS_ERROR_MAP) { + assert_cc(sizeof(sd_bus_error_map) % sizeof(void*) == 0); + + for (const sd_bus_error_map *m = __start_SYSTEMD_BUS_ERROR_MAP; m < __stop_SYSTEMD_BUS_ERROR_MAP; m++) { /* For magic ELF error maps, the end marker might appear in the middle of things, since - * multiple maps might appear in the same section. Hence, let's skip over it, but realign - * the pointer to the next 8 byte boundary, which is the selected alignment for the arrays. */ - if (elf_map->code == BUS_ERROR_MAP_END_MARKER) { - elf_map = ALIGN_PTR(elf_map + 1); - continue; - } + * multiple maps might appear in the same section. Skip over it. */ - if (streq(elf_map->name, name)) { - assert(elf_map->code > 0); - return elf_map->code; + if (m->code != BUS_ERROR_MAP_END_MARKER && + streq(m->name, name)) { + assert(m->code > 0); + return m->code; } - - elf_map++; } return EIO; @@ -165,6 +160,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-future.c b/src/libsystemd/sd-bus/bus-future.c new file mode 100644 index 0000000000000..d7037b3f15bed --- /dev/null +++ b/src/libsystemd/sd-bus/bus-future.c @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-bus.h" +#include "sd-future.h" + +#include "alloc-util.h" +#include "bus-future.h" +#include "bus-internal.h" +#include "bus-message.h" + +typedef struct BusFuture { + sd_bus_slot *slot; + sd_bus_message *reply; +} BusFuture; + +static void* bus_future_alloc(void) { + return new0(BusFuture, 1); +} + +static void bus_future_free(sd_future *f) { + BusFuture *bf = ASSERT_PTR(sd_future_get_private(f)); + sd_bus_slot_unref(bf->slot); + sd_bus_message_unref(bf->reply); + free(bf); +} + +static int bus_future_cancel(sd_future *f) { + BusFuture *bf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + + bf->slot = sd_bus_slot_unref(bf->slot); + return sd_future_resolve(f, -ECANCELED); +} + +static const sd_future_ops bus_future_ops = { + .size = sizeof(sd_future_ops), + .alloc = bus_future_alloc, + .free = bus_future_free, + .cancel = bus_future_cancel, +}; + +static int bus_future_handler(sd_bus_message *m, void *userdata, sd_bus_error *reterr_error) { + sd_future *f = ASSERT_PTR(userdata); + BusFuture *bf = ASSERT_PTR(sd_future_get_private(f)); + int r; + + /* Resolve with 0 on a success reply and -errno (derived from the D-Bus error name) on a + * method error reply, so a caller awaiting the future learns about call failures from the + * resolution value alone. The reply itself is always stashed in bf->reply so + * future_get_bus_reply() can hand back the detailed sd_bus_error (name + message) on + * top of the bare errno. Cancellation surfaces as -ECANCELED via bus_future_cancel(), + * with bf->reply left NULL — callers can distinguish "got an error reply" from "no reply + * will arrive" by whether future_get_bus_reply() can produce a message. */ + bf->slot = sd_bus_slot_unref(bf->slot); + bf->reply = sd_bus_message_ref(m); + + r = sd_bus_message_is_method_error(m, NULL) ? -sd_bus_message_get_errno(m) : 0; + return sd_future_resolve(f, r); +} + +int bus_call_future(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_future **ret) { + int r; + + assert(bus); + assert(m); + assert(ret); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_future_new(&bus_future_ops, &f); + if (r < 0) + return r; + + BusFuture *bf = sd_future_get_private(f); + + r = sd_bus_call_async(bus, &bf->slot, m, bus_future_handler, f, usec); + if (r < 0) + return r; + + *ret = TAKE_PTR(f); + return 0; +} + +int future_get_bus_reply(sd_future *f, sd_bus_error *reterr_error, sd_bus_message **ret_reply) { + BusFuture *bf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + sd_bus_message *reply = ASSERT_PTR(bf->reply); + + assert(sd_future_get_ops(f) == &bus_future_ops); + assert(sd_future_state(f) == SD_FUTURE_RESOLVED); + + if (sd_bus_message_is_method_error(reply, NULL)) { + if (reterr_error) + return sd_bus_error_copy(reterr_error, sd_bus_message_get_error(reply)); + return -sd_bus_message_get_errno(reply); + } + + if (reply->n_fds > 0 && !sd_bus_message_get_bus(reply)->accept_fd) + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INCONSISTENT_MESSAGE, + "Reply message contained file descriptors which I couldn't accept. Sorry."); + + if (reterr_error) + *reterr_error = SD_BUS_ERROR_NULL; + if (ret_reply) + *ret_reply = sd_bus_message_ref(reply); + + return 1; +} + +int bus_call_suspend( + sd_bus *bus, + sd_bus_message *m, + uint64_t usec, + sd_bus_error *reterr_error, + sd_bus_message **ret_reply) { + + int r; + + assert(bus); + assert(m); + assert(sd_fiber_is_running()); + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *f = NULL; + r = bus_call_future(bus, m, usec, &f); + if (r < 0) + return sd_bus_error_set_errno(reterr_error, r); + + r = sd_fiber_suspend(); + + /* If the future isn't resolved, the suspend was interrupted before a reply arrived (fiber + * cancelled, fiber-wide SD_FIBER_TIMEOUT scope expired, …). There's no reply to extract, + * so surface the resume error directly. When the future is resolved, future_get_bus_reply() + * recovers either the reply or the detailed sd_bus_error from the error reply. */ + if (sd_future_state(f) != SD_FUTURE_RESOLVED) + return sd_bus_error_set_errno(reterr_error, r); + + return future_get_bus_reply(f, reterr_error, ret_reply); +} diff --git a/src/libsystemd/sd-bus/bus-future.h b/src/libsystemd/sd-bus/bus-future.h new file mode 100644 index 0000000000000..ec9bd80b1598a --- /dev/null +++ b/src/libsystemd/sd-bus/bus-future.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +int bus_call_future(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_future **ret); +int future_get_bus_reply(sd_future *f, sd_bus_error *reterr_error, sd_bus_message **ret_reply); + +int bus_call_suspend( + sd_bus *bus, + sd_bus_message *m, + uint64_t usec, + sd_bus_error *reterr_error, + sd_bus_message **ret_reply); 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-internal.h b/src/libsystemd/sd-bus/bus-internal.h index 19a3b67d12f6a..3a52f738d6bd7 100644 --- a/src/libsystemd/sd-bus/bus-internal.h +++ b/src/libsystemd/sd-bus/bus-internal.h @@ -17,6 +17,13 @@ #define DEFAULT_SYSTEM_BUS_ADDRESS "unix:path=/run/dbus/system_bus_socket" #define DEFAULT_USER_BUS_ADDRESS_FMT "unix:path=%s/bus" +/* Private vtable flag: dispatch the method handler on its own fiber, so it can use suspending + * primitives (sd_bus_call() on a fiber, sd_fiber_sleep(), loop_read_suspend(), ...) without + * blocking the event loop for other connections or method calls. Kept out of the public + * sd-bus-vtable.h so the fiber runtime stays an implementation detail of systemd. The bit value is + * reserved in sd-bus-vtable.h to make sure it never collides with a future public flag. */ +#define SD_BUS_VTABLE_METHOD_FIBER (UINT64_C(1) << 10) + typedef struct BusReplyCallback { sd_bus_message_handler_t callback; usec_t timeout_usec; /* this is a relative timeout until we reach the BUS_HELLO state, and an absolute one right after */ @@ -222,6 +229,12 @@ typedef struct sd_bus { Set *vtable_methods; Set *vtable_properties; + /* Futures for outstanding SD_BUS_VTABLE_METHOD_FIBER dispatches. Entries are added as the + * dispatcher spawns each fiber and removed when the fiber resolves. On bus_enter_closing() + * we cancel everything in here and then wait in process_closing() until the set drains, + * before tearing down the rest of the bus. */ + Set *fiber_futures; + union sockaddr_union sockaddr; socklen_t sockaddr_size; 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..a719253fa73e6 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); @@ -3898,7 +3904,8 @@ static int message_skip_fields( sd_bus_message *m, size_t *ri, uint32_t array_size, - const char **signature) { + const char **signature, + unsigned depth) { size_t original_index; int r; @@ -3907,6 +3914,9 @@ static int message_skip_fields( assert(ri); assert(signature); + if (depth >= BUS_CONTAINER_DEPTH) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Maximum container nesting depth reached, refusing."); + original_index = *ri; for (;;) { @@ -3987,7 +3997,7 @@ static int message_skip_fields( if (r < 0) return r; - r = message_skip_fields(m, ri, nas, (const char**) &s); + r = message_skip_fields(m, ri, nas, (const char**) &s, depth + 1); if (r < 0) return r; } @@ -4001,7 +4011,7 @@ static int message_skip_fields( if (r < 0) return r; - r = message_skip_fields(m, ri, UINT32_MAX, &s); + r = message_skip_fields(m, ri, UINT32_MAX, &s, depth + 1); if (r < 0) return r; @@ -4019,7 +4029,7 @@ static int message_skip_fields( strncpy(sig, *signature + 1, l); sig[l] = '\0'; - r = message_skip_fields(m, ri, UINT32_MAX, (const char**) &s); + r = message_skip_fields(m, ri, UINT32_MAX, (const char**) &s, depth + 1); if (r < 0) return r; } @@ -4192,7 +4202,7 @@ static int message_parse_fields(sd_bus_message *m, bool got_ctrunc) { break; default: - r = message_skip_fields(m, &ri, UINT32_MAX, &signature); + r = message_skip_fields(m, &ri, UINT32_MAX, &signature, 0); } if (r < 0) return r; @@ -4325,6 +4335,7 @@ int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz) { _public_ int sd_bus_message_read_strv_extend(sd_bus_message *m, char ***l) { char type; const char *contents, *s; + size_t n; int r; assert(m); @@ -4341,9 +4352,10 @@ _public_ int sd_bus_message_read_strv_extend(sd_bus_message *m, char ***l) { if (r <= 0) return r; + n = strv_length(*l); /* sd_bus_message_read_basic() does content validation for us. */ while ((r = sd_bus_message_read_basic(m, *contents, &s)) > 0) { - r = strv_extend(l, s); + r = strv_extend_with_size(l, &n, s); if (r < 0) return r; } 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..795cc68837655 100644 --- a/src/libsystemd/sd-bus/bus-objects.c +++ b/src/libsystemd/sd-bus/bus-objects.c @@ -3,6 +3,7 @@ #include #include "sd-bus.h" +#include "sd-future.h" #include "alloc-util.h" #include "bus-internal.h" @@ -337,6 +338,67 @@ static int check_access(sd_bus *bus, sd_bus_message *m, BusVTableMember *c, sd_b return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_ACCESS_DENIED, "Access to %s.%s() not permitted.", c->interface, c->member); } +typedef struct BusFiberData { + sd_bus *bus; + sd_bus_message *message; + sd_bus_slot *slot; + sd_bus_message_handler_t handler; + void *userdata; +} BusFiberData; + +static BusFiberData* bus_fiber_data_free(BusFiberData *d) { + if (!d) + return NULL; + + sd_bus_slot_unref(d->slot); + sd_bus_message_unref(d->message); + sd_bus_unref(d->bus); + return mfree(d); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(BusFiberData*, bus_fiber_data_free); + +static void bus_fiber_data_destroy(void *userdata) { + bus_fiber_data_free(userdata); +} + +static void bus_fiber_future_unref(void *p) { + sd_future_unref(p); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + bus_fiber_future_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + bus_fiber_future_unref); + +static int bus_fiber_resolved(sd_future *f) { + sd_bus *bus = ASSERT_PTR(sd_future_get_userdata(f)); + + assert_se(set_remove(bus->fiber_futures, f) == f); + sd_future_unref(f); + return 0; +} + +static int bus_fiber_entry(void *userdata) { + BusFiberData *d = ASSERT_PTR(userdata); + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + /* Note: unlike the synchronous dispatch path, we deliberately do NOT set + * bus->current_slot/handler/userdata around the callback. Those fields track the slot of the + * message currently being dispatched inline and must be NULL at each entry into + * bus_process_internal(). Because a fiber handler can yield and let the event loop dispatch + * other messages before it resumes, leaving current_slot non-NULL across yields would trip + * that invariant. Fiber handlers receive their slot's userdata via the handler argument, so + * sd_bus_get_current_slot()/handler()/userdata() simply aren't meaningful inside them — the + * handler should use the message/userdata parameters directly instead. */ + r = d->handler(d->message, d->userdata, &error); + + return bus_maybe_reply_error(d->message, r, &error); +} + static int method_callbacks_run( sd_bus *bus, sd_bus_message *m, @@ -407,6 +469,53 @@ static int method_callbacks_run( slot = container_of(c->parent, sd_bus_slot, node_vtable); + if (FLAGS_SET(c->vtable->flags, SD_BUS_VTABLE_METHOD_FIBER)) { + /* A fiber-dispatched method requires an event loop to spawn the fiber on. + * By the time a method call actually arrives the bus is running, so the + * event loop should already be attached — if not, the caller set up the bus + * wrong and there's no meaningful recovery. */ + assert(bus->event); + + _cleanup_(bus_fiber_data_freep) BusFiberData *d = new(BusFiberData, 1); + if (!d) + return -ENOMEM; + + *d = (BusFiberData) { + .bus = sd_bus_ref(bus), + .message = sd_bus_message_ref(m), + .slot = sd_bus_slot_ref(slot), + .handler = c->vtable->x.method.handler, + .userdata = u, + }; + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_fiber_new(bus->event, c->member, bus_fiber_entry, d, bus_fiber_data_destroy, &f); + if (r < 0) + return bus_maybe_reply_error(m, r, NULL); + + /* The fiber now owns d via bus_fiber_data_destroy. Drop our cleanup before any + * further fallible calls, so a later failure unwinding f doesn't double-free d. */ + TAKE_PTR(d); + + r = set_ensure_put(&bus->fiber_futures, &bus_fiber_future_hash_ops, f); + if (r < 0) + return bus_maybe_reply_error(m, r, NULL); + assert(r > 0); + + /* Track the future on the bus so shutdown can cancel it and wait for it. */ + r = sd_future_set_callback(f, bus_fiber_resolved, bus); + if (r < 0) { + /* TAKE_PTR(f) hasn't run yet, so our cleanup attribute still owns the + * ref; set_remove() returns the raw pointer without firing the hash_ops + * destructor, and the cleanup will unref f on return. */ + assert_se(set_remove(bus->fiber_futures, f) == f); + return bus_maybe_reply_error(m, r, NULL); + } + + TAKE_PTR(f); + return 1; + } + bus->current_slot = sd_bus_slot_ref(slot); bus->current_handler = c->vtable->x.method.handler; bus->current_userdata = u; @@ -1704,6 +1813,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 +2514,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 +2729,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..e44c439fad862 100644 --- a/src/libsystemd/sd-bus/sd-bus.c +++ b/src/libsystemd/sd-bus/sd-bus.c @@ -10,12 +10,14 @@ #include "sd-bus.h" #include "sd-event.h" +#include "sd-future.h" #include "af-list.h" #include "alloc-util.h" #include "bus-container.h" #include "bus-control.h" #include "bus-error.h" +#include "bus-future.h" #include "bus-internal.h" #include "bus-kernel.h" #include "bus-label.h" @@ -222,6 +224,12 @@ static sd_bus* bus_free(sd_bus *b) { ordered_hashmap_free(b->reply_callbacks); prioq_free(b->reply_callbacks_prioq); + /* Outstanding fiber handlers pin the bus via their BusFiberData ref, so by the time refcount + * reaches zero and bus_free() runs, every fiber has already resolved and removed itself from + * this set. */ + assert(set_isempty(b->fiber_futures)); + set_free(b->fiber_futures); + assert(b->match_callbacks.type == BUS_MATCH_ROOT); bus_match_free(&b->match_callbacks); @@ -1480,7 +1488,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 +1504,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); } @@ -1809,6 +1817,9 @@ _public_ sd_bus* sd_bus_flush_close_unref(sd_bus *bus) { } void bus_enter_closing(sd_bus *bus, int exit_code) { + sd_future *f; + int r; + assert(bus); if (!IN_SET(bus->state, BUS_WATCH_BIND, BUS_OPENING, BUS_AUTHENTICATING, BUS_HELLO, BUS_RUNNING)) @@ -1816,6 +1827,19 @@ void bus_enter_closing(sd_bus *bus, int exit_code) { bus_set_state(bus, BUS_CLOSING); bus->exit_code = exit_code; + + /* Cancel all outstanding fiber-dispatched method handlers. Most cancellations are scheduled + * asynchronously (fibers resolve with -ECANCELED the next time they run), but a fiber still + * in FIBER_STATE_INITIAL resolves synchronously, which fires bus_fiber_resolved() and + * removes f from this set mid-iteration. That's safe because SET_FOREACH permits removal of + * exactly the current entry — see the assertion in hashmap_iterate_entry(). Either way this + * doesn't block here: process_closing() waits for the fiber_futures set to drain before it + * continues tearing down the rest of the bus. */ + SET_FOREACH(f, bus->fiber_futures) { + r = sd_future_cancel(f); + if (r < 0) + log_debug_errno(r, "Failed to cancel outstanding fiber method handler, ignoring: %m"); + } } /* Define manually so we can add the PID check */ @@ -2021,6 +2045,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) @@ -2387,23 +2412,30 @@ _public_ int sd_bus_call( sd_bus_error *reterr_error, sd_bus_message **ret_reply) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); usec_t timeout; uint64_t cookie; size_t i; int r; - bus_assert_return(m, -EINVAL, reterr_error); - bus_assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL, reterr_error); - bus_assert_return(!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED), -EINVAL, reterr_error); + bus_assert_return(_m, -EINVAL, reterr_error); + bus_assert_return(_m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL, reterr_error); + bus_assert_return(!(_m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED), -EINVAL, reterr_error); bus_assert_return(!bus_error_is_dirty(reterr_error), -EINVAL, reterr_error); if (bus) assert_return(bus = bus_resolve(bus), -ENOPKG); else - assert_return(bus = m->bus, -ENOTCONN); + assert_return(bus = _m->bus, -ENOTCONN); bus_assert_return(!bus_origin_changed(bus), -ECHILD, reterr_error); + /* If the current fiber and the bus share their event loop, we can use sd_bus_call_suspend() + * instead which does an async method call. This allows multiple invocations of sd_bus_call() to + * happen across multiple fibers at once. */ + if (sd_fiber_is_running() && bus->event == sd_fiber_get_event()) + return bus_call_suspend(bus, _m, usec, reterr_error, ret_reply); + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); + if (!BUS_IS_OPEN(bus->state)) { r = -ENOTCONN; goto fail; @@ -3176,7 +3208,14 @@ static int process_closing(sd_bus *bus, sd_bus_message **ret) { assert(bus); assert(bus->state == BUS_CLOSING); - /* First, fail all outstanding method calls */ + /* Wait for any still-running fiber method handlers to finish unwinding their cancellation + * before tearing down the rest of the bus. bus_enter_closing() scheduled the cancel; each + * fiber resolves asynchronously and bus_fiber_resolved() removes it from the set. Returning + * 1 here keeps the bus in CLOSING state so the event loop drives the fibers to completion. */ + if (!set_isempty(bus->fiber_futures)) + return 1; + + /* Then, fail all outstanding method calls */ c = ordered_hashmap_first(bus->reply_callbacks); if (c) return process_closing_reply_callback(bus, c); 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..d6f4860c41511 100644 --- a/src/libsystemd/sd-bus/test-bus-chat.c +++ b/src/libsystemd/sd-bus/test-bus-chat.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include "sd-bus.h" +#include "sd-future.h" #include "alloc-util.h" #include "bus-error.h" @@ -102,7 +102,8 @@ static int server_init(sd_bus **ret) { return 0; } -static int server(sd_bus *bus) { +static int server(void *userdata) { + sd_bus *bus = ASSERT_PTR(userdata); bool client1_gone = false, client2_gone = false; int r; @@ -178,7 +179,9 @@ static int server(sd_bus *bus) { client2_gone = true; } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "Slow")) { - sleep(1); + r = sd_fiber_sleep(1 * USEC_PER_SEC); + if (r < 0) + return r; r = sd_bus_reply_method_return(m, NULL); if (r < 0) @@ -194,10 +197,10 @@ static int server(sd_bus *bus) { log_info("Received fd=%d", fd); - if (write(fd, &x, 1) < 0) { - r = log_error_errno(errno, "Failed to write to fd: %m"); + ssize_t n = sd_fiber_write(fd, &x, 1); + if (n < 0) { safe_close(fd); - return r; + return log_error_errno(n, "Failed to write to fd: %m"); } r = sd_bus_reply_method_return(m, NULL); @@ -217,7 +220,7 @@ static int server(sd_bus *bus) { return 0; } -static void* client1(void *p) { +static int client1(void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -277,9 +280,9 @@ static void* client1(void *p) { goto finish; } - errno = 0; - if (read(pp[0], &x, 1) <= 0) { - log_error("Failed to read from pipe: %s", STRERROR_OR_EOF(errno)); + ssize_t n = sd_fiber_read(pp[0], &x, 1); + if (n <= 0) { + log_error("Failed to read from pipe: %s", STRERROR_OR_EOF(n)); goto finish; } @@ -303,7 +306,7 @@ static void* client1(void *p) { } - return INT_TO_PTR(r); + return r; } static int quit_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { @@ -315,7 +318,7 @@ static int quit_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_er return 1; } -static void* client2(void *p) { +static int client2(void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -494,7 +497,7 @@ static void* client2(void *p) { (void) sd_bus_send(bus, q, NULL); } - return INT_TO_PTR(r); + return r; } static ino_t get_inode(int fd) { @@ -567,11 +570,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; @@ -620,9 +629,9 @@ TEST(ctrunc) { } TEST(chat) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f_server = NULL, *f_client1 = NULL, *f_client2 = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - pthread_t c1, c2; - void *p; int r; test_setup_logging(LOG_INFO); @@ -633,16 +642,18 @@ TEST(chat) { log_info("Initialized..."); - ASSERT_OK(-pthread_create(&c1, NULL, client1, NULL)); - ASSERT_OK(-pthread_create(&c2, NULL, client2, NULL)); + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + ASSERT_OK(sd_fiber_new(e, "client-1", client1, NULL, /* destroy= */ NULL, &f_client1)); + ASSERT_OK(sd_fiber_new(e, "client-2", client2, NULL, /* destroy= */ NULL, &f_client2)); + ASSERT_OK(sd_fiber_new(e, "server", server, bus, /* destroy= */ NULL, &f_server)); - r = server(bus); + ASSERT_OK(sd_event_loop(e)); - ASSERT_OK(-pthread_join(c1, &p)); - ASSERT_OK(PTR_TO_INT(p)); - ASSERT_OK(-pthread_join(c2, &p)); - ASSERT_OK(PTR_TO_INT(p)); - ASSERT_OK(r); + ASSERT_OK(sd_future_result(f_client1)); + ASSERT_OK(sd_future_result(f_client2)); + ASSERT_OK(sd_future_result(f_server)); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/libsystemd/sd-bus/test-bus-error.c b/src/libsystemd/sd-bus/test-bus-error.c index db41141811f84..a91a014a38ca9 100644 --- a/src/libsystemd/sd-bus/test-bus-error.c +++ b/src/libsystemd/sd-bus/test-bus-error.c @@ -128,19 +128,12 @@ extern const sd_bus_error_map __start_SYSTEMD_BUS_ERROR_MAP[]; extern const sd_bus_error_map __stop_SYSTEMD_BUS_ERROR_MAP[]; static int dump_mapping_table(void) { - const sd_bus_error_map *m; - printf("----- errno mappings ------\n"); - m = ALIGN_PTR(__start_SYSTEMD_BUS_ERROR_MAP); - while (m < __stop_SYSTEMD_BUS_ERROR_MAP) { - - if (m->code == BUS_ERROR_MAP_END_MARKER) { - m = ALIGN_PTR(m + 1); - continue; - } + for (const sd_bus_error_map *m = __start_SYSTEMD_BUS_ERROR_MAP; m < __stop_SYSTEMD_BUS_ERROR_MAP; m++) { + assert((uintptr_t) m % sizeof(void*) == 0); - printf("%s -> %i/%s\n", strna(m->name), m->code, ERRNO_NAME(m->code)); - m++; + if (m->code != BUS_ERROR_MAP_END_MARKER) + printf("%s -> %i/%s\n", strna(m->name), m->code, ERRNO_NAME(m->code)); } printf("---------------------------\n"); @@ -154,13 +147,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-fiber.c b/src/libsystemd/sd-bus/test-bus-fiber.c new file mode 100644 index 0000000000000..2cadf1dc17591 --- /dev/null +++ b/src/libsystemd/sd-bus/test-bus-fiber.c @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-bus.h" +#include "sd-event.h" +#include "sd-future.h" + +#include "bus-internal.h" +#include "tests.h" +#include "time-util.h" + +typedef struct Context { + /* Counters for the concurrency check: every Concurrent invocation bumps in_flight on entry + * and drops it on exit, and tracks the maximum observed concurrency. If fiber dispatch + * works, two overlapping client calls must both be inside the handler at the same time, + * giving a max of at least 2. */ + int in_flight; + int max_in_flight; + sd_future *waiter; +} Context; + +static int method_concurrent(sd_bus_message *m, void *userdata, sd_bus_error *reterr_error) { + Context *c = ASSERT_PTR(userdata); + + ASSERT_OK_POSITIVE(sd_fiber_is_running()); + + c->in_flight++; + if (c->in_flight > c->max_in_flight) + c->max_in_flight = c->in_flight; + + /* Synchronize on the peer instead of sleeping: the first handler to enter stashes its + * fiber and suspends; the second resumes it and then yields, so the first runs to + * completion (sending its reply) before the second proceeds. Both are therefore in + * flight at the same time regardless of how long any individual dispatch takes, and + * the reply order matches the request order. */ + if (c->in_flight < 2) { + assert(!c->waiter); + c->waiter = sd_fiber_get_current(); + ASSERT_OK(sd_fiber_suspend()); + } else { + ASSERT_OK(sd_fiber_resume(TAKE_PTR(c->waiter), 0)); + ASSERT_OK(sd_fiber_yield()); + } + + c->in_flight--; + + return sd_bus_reply_method_return(m, NULL); +} + +static int method_fail_errno(sd_bus_message *m, void *userdata, sd_bus_error *reterr_error) { + ASSERT_OK_POSITIVE(sd_fiber_is_running()); + + /* Yielding first exercises the deferred-error path in the fiber entry: the handler returns + * a negative errno after suspending, and bus_maybe_reply_error() must still turn that into + * a matching sd_bus error reply. */ + ASSERT_OK(sd_fiber_sleep(1 * USEC_PER_MSEC)); + + return -EACCES; +} + +static int method_fail_error(sd_bus_message *m, void *userdata, sd_bus_error *reterr_error) { + ASSERT_OK_POSITIVE(sd_fiber_is_running()); + + ASSERT_OK(sd_fiber_sleep(1 * USEC_PER_MSEC)); + + return sd_bus_error_set(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "bad arguments from fiber"); +} + +static const sd_bus_vtable vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("Concurrent", NULL, NULL, method_concurrent, + SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_METHOD_FIBER), + SD_BUS_METHOD("FailErrno", NULL, NULL, method_fail_errno, + SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_METHOD_FIBER), + SD_BUS_METHOD("FailError", NULL, NULL, method_fail_error, + SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_METHOD_FIBER), + SD_BUS_VTABLE_END, +}; + +typedef struct Setup { + int fds[2]; + Context *c; +} Setup; + +static int attach_pair(Setup *s, sd_bus **ret_server, sd_bus **ret_client) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *server = NULL, *client = NULL; + sd_id128_t id; + + assert(ret_server); + assert(ret_client); + + ASSERT_OK(sd_id128_randomize(&id)); + ASSERT_OK(sd_bus_new(&server)); + ASSERT_OK(sd_bus_set_description(server, "server")); + ASSERT_OK(sd_bus_set_fd(server, s->fds[0], s->fds[0])); + ASSERT_OK(sd_bus_set_server(server, true, id)); + ASSERT_OK(sd_bus_attach_event(server, sd_fiber_get_event(), 0)); + ASSERT_OK(sd_bus_add_object_vtable(server, NULL, "/test", "test.Fiber", vtable, s->c)); + ASSERT_OK(sd_bus_start(server)); + + ASSERT_OK(sd_bus_new(&client)); + ASSERT_OK(sd_bus_set_description(client, "client")); + ASSERT_OK(sd_bus_set_fd(client, s->fds[1], s->fds[1])); + ASSERT_OK(sd_bus_attach_event(client, sd_fiber_get_event(), 0)); + ASSERT_OK(sd_bus_start(client)); + + *ret_server = TAKE_PTR(server); + *ret_client = TAKE_PTR(client); + return 0; +} + +static int call_concurrent_fiber(void *userdata) { + sd_bus *client = ASSERT_PTR(userdata); + + /* A plain suspending sd_bus_call() — on a fiber this goes through sd_bus_call_suspend() + * which multiplexes onto the single client connection, so multiple caller fibers can have + * calls in flight at the same time. */ + return sd_bus_call_method(client, NULL, "/test", "test.Fiber", "Concurrent", + NULL, NULL, NULL); +} + +static int concurrency_fiber(void *userdata) { + Setup *s = ASSERT_PTR(userdata); + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *server = NULL, *client = NULL; + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *f_a = NULL, *f_b = NULL; + + ASSERT_OK(attach_pair(s, &server, &client)); + + /* Two concurrent calls on the shared client bus. Each lands in method_concurrent which + * blocks on the peer; if fiber dispatch works the second is entered while the first is + * suspended, so max_in_flight on the context reaches 2. */ + ASSERT_OK(sd_fiber_new(sd_fiber_get_event(), "call-a", call_concurrent_fiber, client, + /* destroy= */ NULL, &f_a)); + ASSERT_OK(sd_fiber_new(sd_fiber_get_event(), "call-b", call_concurrent_fiber, client, + /* destroy= */ NULL, &f_b)); + + ASSERT_OK(sd_fiber_await(f_a)); + ASSERT_OK(sd_fiber_await(f_b)); + + ASSERT_OK(sd_future_result(f_a)); + ASSERT_OK(sd_future_result(f_b)); + return 0; +} + +TEST(fiber_method_concurrency) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + Context c = {}; + Setup s = { .c = &c }; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM, 0, s.fds)); + + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + ASSERT_OK(sd_fiber_new(e, "concurrency", concurrency_fiber, &s, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(f)); + ASSERT_GE(c.max_in_flight, 2); +} + +static int errors_fiber(void *userdata) { + Setup *s = ASSERT_PTR(userdata); + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *server = NULL, *client = NULL; + + ASSERT_OK(attach_pair(s, &server, &client)); + + /* A fiber handler that returns a negative errno gets turned into a matching sd_bus error + * reply (bus_maybe_reply_error → sd_bus_reply_method_errno). */ + _cleanup_(sd_bus_error_free) sd_bus_error e1 = SD_BUS_ERROR_NULL; + ASSERT_ERROR(sd_bus_call_method(client, NULL, "/test", "test.Fiber", "FailErrno", + &e1, NULL, NULL), + EACCES); + ASSERT_TRUE(sd_bus_error_has_name(&e1, SD_BUS_ERROR_ACCESS_DENIED)); + + /* A fiber handler that populates sd_bus_error directly propagates both name and message. */ + _cleanup_(sd_bus_error_free) sd_bus_error e2 = SD_BUS_ERROR_NULL; + ASSERT_FAIL(sd_bus_call_method(client, NULL, "/test", "test.Fiber", "FailError", + &e2, NULL, NULL)); + ASSERT_TRUE(sd_bus_error_has_name(&e2, SD_BUS_ERROR_INVALID_ARGS)); + ASSERT_STREQ(e2.message, "bad arguments from fiber"); + + return 0; +} + +TEST(fiber_method_errors) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + Context c = {}; + Setup s = { .c = &c }; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM, 0, s.fds)); + + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + ASSERT_OK(sd_fiber_new(e, "errors", errors_fiber, &s, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(f)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c index e26ca43344713..05074fd92b2f6 100644 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ b/src/libsystemd/sd-bus/test-bus-marshal.c @@ -1,10 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - /* We make an exception here to our usual "include system headers first" rule because we need one of these * macros to disable a warning triggered by the glib headers. */ -#include "macro-fundamental.h" +#include "macro.h" #if HAVE_GLIB DISABLE_WARNING_FORMAT_NONLITERAL @@ -23,6 +21,7 @@ REENABLE_WARNING #include "bus-label.h" #include "bus-message.h" #include "bus-util.h" +#include "dlfcn-util.h" #include "escape.h" #include "fd-util.h" #include "log.h" @@ -31,6 +30,42 @@ REENABLE_WARNING #include "stat-util.h" #include "tests.h" +#if HAVE_GLIB +static DLSYM_PROTOTYPE(g_dbus_message_new_from_blob) = NULL; +static DLSYM_PROTOTYPE(g_dbus_message_print) = NULL; +static DLSYM_PROTOTYPE(g_free) = NULL; +static DLSYM_PROTOTYPE(g_object_unref) = NULL; + +static int dlopen_glib(void) { + static void *glib_dl = NULL; + + return dlopen_many_sym_or_warn( + &glib_dl, "libgio-2.0.so.0", LOG_DEBUG, + DLSYM_ARG(g_dbus_message_new_from_blob), + DLSYM_ARG(g_dbus_message_print), + DLSYM_ARG(g_free), + DLSYM_ARG(g_object_unref)); +} +#endif + +#if HAVE_DBUS +static DLSYM_PROTOTYPE(dbus_error_init) = NULL; +static DLSYM_PROTOTYPE(dbus_error_free) = NULL; +static DLSYM_PROTOTYPE(dbus_message_demarshal) = NULL; +static DLSYM_PROTOTYPE(dbus_message_unref) = NULL; + +static int dlopen_libdbus(void) { + static void *libdbus_dl = NULL; + + return dlopen_many_sym_or_warn( + &libdbus_dl, "libdbus-1.so.3", LOG_DEBUG, + DLSYM_ARG(dbus_error_init), + DLSYM_ARG(dbus_error_free), + DLSYM_ARG(dbus_message_demarshal), + DLSYM_ARG(dbus_message_unref)); +} +#endif + static void test_bus_path_encode_unique(void) { _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL; @@ -216,6 +251,58 @@ static void test_bus_fds_truncated(void) { log_info("All fd truncation tests passed"); } +static void test_bus_nested_variant_depth_limit(void) { + /* Craft a raw D-Bus message with an unknown header field whose value is a variant + * containing a variant containing a variant... nested beyond BUS_CONTAINER_DEPTH. + * Without the depth limit in message_skip_fields(), this causes unbounded recursion + * and stack overflow. With the fix, it should be rejected with -EBADMSG. */ + + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + const unsigned depth = BUS_CONTAINER_DEPTH + 1; /* one past the limit */ + + /* Each nesting level in the fields area is: 1 byte sig_len + 1 byte 'v' + 1 byte NUL = 3 bytes. + * The innermost level has sig_len=1, 'u', NUL, then 4 bytes for the uint32 value. + * The field header is: 1 byte field_code + 1 byte sig_len + 1 byte 'v' + 1 byte NUL = 4 bytes. */ + size_t fields_size = 4 + (depth * 3) + 4; /* field header + nested variant sigs + uint32 */ + size_t padded_fields = ALIGN8(fields_size); + size_t total = sizeof(BusMessageHeader) + padded_fields; + + _cleanup_free_ void *buf = ASSERT_PTR(malloc0(total)); + + BusMessageHeader *h = buf; + *h = (BusMessageHeader) { + .endian = BUS_NATIVE_ENDIAN, + .type = SD_BUS_MESSAGE_METHOD_CALL, + .version = 1, + .serial = 1, + .fields_size = (uint32_t) fields_size, + }; + + uint8_t *p = (uint8_t *) buf + sizeof(BusMessageHeader); + + /* Unknown field code (triggers default: in message_parse_fields) */ + *p++ = 0xFF; + /* Field signature: variant */ + *p++ = 1; /* sig length */ + *p++ = 'v'; + *p++ = '\0'; + + /* Nested variant signatures: each level declares its content is another variant */ + for (unsigned i = 0; i < depth; i++) { + *p++ = 1; /* sig length */ + *p++ = 'v'; + *p++ = '\0'; + } + + /* Innermost value: a uint32 */ + memset(p, 0, 4); + + ASSERT_OK(sd_bus_new(&bus)); + + ASSERT_ERROR(bus_message_from_malloc(bus, buf, total, NULL, 0, false, NULL, &m), EBADMSG); +} + static void test_bus_label_escape(void) { test_bus_label_escape_one("foo123bar", "foo123bar"); test_bus_label_escape_one("foo.bar", "foo_2ebar"); @@ -322,39 +409,32 @@ int main(int argc, char *argv[]) { log_info("message size = %zu, contents =\n%s", sz, h); #if HAVE_GLIB - /* Work-around for asan bug. See c8d980a3e962aba2ea3a4cedf75fa94890a6d746. */ -#if !HAS_FEATURE_ADDRESS_SANITIZER - { + if (dlopen_glib() >= 0) { GDBusMessage *g; char *p; -#if !defined(GLIB_VERSION_2_36) - g_type_init(); -#endif - - g = g_dbus_message_new_from_blob(buffer, sz, 0, NULL); - p = g_dbus_message_print(g, 0); + g = sym_g_dbus_message_new_from_blob(buffer, sz, 0, NULL); + p = sym_g_dbus_message_print(g, 0); log_info("%s", p); - g_free(p); - g_object_unref(g); + sym_g_free(p); + sym_g_object_unref(g); } #endif -#endif #if HAVE_DBUS - { + if (dlopen_libdbus() >= 0) { DBusMessage *w; DBusError error; - dbus_error_init(&error); + sym_dbus_error_init(&error); - w = dbus_message_demarshal(buffer, sz, &error); + w = sym_dbus_message_demarshal(buffer, sz, &error); if (!w) log_error("%s", error.message); else - dbus_message_unref(w); + sym_dbus_message_unref(w); - dbus_error_free(&error); + sym_dbus_error_free(&error); } #endif @@ -451,7 +531,7 @@ int main(int argc, char *argv[]) { assert_se(r > 0); assert_se(streq(x, "foo")); assert_se(u64 == 815ULL); - assert_se(fabs(dbl - 47.0) < 0.1); + assert_se(ABS(dbl - 47.0) < 0.1); assert_se(streq(y, "/")); r = sd_bus_message_peek_type(m, NULL, NULL); @@ -533,6 +613,7 @@ int main(int argc, char *argv[]) { test_bus_path_encode_unique(); test_bus_path_encode_many(); test_bus_fds_truncated(); + test_bus_nested_variant_depth_limit(); return 0; } diff --git a/src/libsystemd/sd-bus/test-bus-objects.c b/src/libsystemd/sd-bus/test-bus-objects.c index 62050b103db23..ac33086a6f374 100644 --- a/src/libsystemd/sd-bus/test-bus-objects.c +++ b/src/libsystemd/sd-bus/test-bus-objects.c @@ -1,8 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" +#include "sd-future.h" #include "alloc-util.h" #include "bus-internal.h" @@ -177,6 +176,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 +185,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 +196,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; @@ -207,9 +210,9 @@ static int enumerator3_callback(sd_bus *bus, const char *path, void *userdata, c return 1; } -static void* server(void *p) { - struct context *c = p; - sd_bus *bus = NULL; +static int server(void *userdata) { + struct context *c = ASSERT_PTR(userdata); + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; sd_id128_t id; int r; @@ -238,36 +241,25 @@ static void* server(void *p) { log_error("Loop!"); r = sd_bus_process(bus, NULL); - if (r < 0) { - log_error_errno(r, "Failed to process requests: %m"); - goto fail; - } + if (r < 0) + return log_error_errno(r, "Failed to process requests: %m"); if (r == 0) { r = sd_bus_wait(bus, UINT64_MAX); - if (r < 0) { - log_error_errno(r, "Failed to wait: %m"); - goto fail; - } + if (r < 0) + return log_error_errno(r, "Failed to wait: %m"); continue; } } - r = 0; - -fail: - if (bus) { - sd_bus_flush(bus); - sd_bus_unref(bus); - } - - return INT_TO_PTR(r); + return 0; } -static int client(struct context *c) { +static int client(void *p) { + struct context *c = ASSERT_PTR(p); _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_strv_free_ char **lines = NULL; const char *s; @@ -571,16 +563,13 @@ static int client(struct context *c) { ASSERT_OK(sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Exit", &error, NULL, NULL)); - sd_bus_flush(bus); - return 0; } int main(int argc, char *argv[]) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f_server = NULL, *f_client = NULL; struct context c = {}; - pthread_t s; - void *p; - int r, q; test_setup_logging(LOG_DEBUG); @@ -589,21 +578,16 @@ int main(int argc, char *argv[]) { ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM, 0, c.fds)); - r = pthread_create(&s, NULL, server, &c); - if (r != 0) - return -r; - - r = client(&c); + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); - q = pthread_join(s, &p); - if (q != 0) - return -q; + ASSERT_OK(sd_fiber_new(e, "server", server, &c, /* destroy= */ NULL, &f_server)); + ASSERT_OK(sd_fiber_new(e, "client", client, &c, /* destroy= */ NULL, &f_client)); - if (r < 0) - return r; + ASSERT_OK(sd_event_loop(e)); - if (PTR_TO_INT(p) < 0) - return PTR_TO_INT(p); + ASSERT_OK(sd_future_result(f_server)); + ASSERT_OK(sd_future_result(f_client)); free(c.something); free(c.automatic_string_property); diff --git a/src/libsystemd/sd-bus/test-bus-peersockaddr.c b/src/libsystemd/sd-bus/test-bus-peersockaddr.c index 2cac35dde4033..bee76c9b10ca7 100644 --- a/src/libsystemd/sd-bus/test-bus-peersockaddr.c +++ b/src/libsystemd/sd-bus/test-bus-peersockaddr.c @@ -1,9 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" +#include "sd-future.h" #include "bus-dump.h" #include "fd-util.h" @@ -38,9 +38,9 @@ static bool gid_list_same(const gid_t *a, size_t n, const gid_t *b, size_t m) { gid_list_contained(b, m, a, n); } -static void* server(void *p) { +static int server(void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_close_ int listen_fd = PTR_TO_INT(p), fd = -EBADF; + _cleanup_close_ int listen_fd = PTR_TO_INT(userdata), fd = -EBADF; _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; _cleanup_free_ char *our_comm = NULL; sd_id128_t id; @@ -48,7 +48,7 @@ static void* server(void *p) { ASSERT_OK(sd_id128_randomize(&id)); - ASSERT_OK_ERRNO(fd = accept4(listen_fd, NULL, NULL, SOCK_CLOEXEC|SOCK_NONBLOCK)); + ASSERT_OK(fd = sd_fiber_accept(listen_fd, NULL, NULL, SOCK_CLOEXEC|SOCK_NONBLOCK)); ASSERT_OK(sd_bus_new(&bus)); ASSERT_OK(sd_bus_set_fd(bus, fd, fd)); @@ -114,17 +114,18 @@ static void* server(void *p) { } } - return NULL; + return 0; } -static void* client(void *p) { +static int client(void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; const char *z; ASSERT_OK(sd_bus_new(&bus)); ASSERT_OK(sd_bus_set_description(bus, "wuffwuff")); - ASSERT_OK(sd_bus_set_address(bus, p)); + ASSERT_OK(sd_bus_set_address(bus, userdata)); + ASSERT_OK(sd_bus_attach_event(bus, sd_fiber_get_event(), 0)); ASSERT_OK(sd_bus_start(bus)); ASSERT_OK(sd_bus_call_method(bus, "foo.foo", "/foo", "foo.foo", "Foo", NULL, &reply, "s", "foo")); @@ -132,17 +133,18 @@ static void* client(void *p) { ASSERT_OK(sd_bus_message_read(reply, "s", &z)); ASSERT_STREQ(z, "bar"); - return NULL; + return 0; } TEST(description) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f_server = NULL, *f_client = NULL; _cleanup_free_ char *a = NULL; _cleanup_close_ int fd = -EBADF; union sockaddr_union sa = { .un.sun_family = AF_UNIX, }; socklen_t salen; - pthread_t s, c; ASSERT_OK_ERRNO(fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0)); ASSERT_OK_ERRNO(bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path))); /* force auto-bind */ @@ -155,13 +157,18 @@ TEST(description) { ASSERT_OK(asprintf(&a, "unix:abstract=%s", sa.un.sun_path + 1)); - ASSERT_OK(-pthread_create(&s, NULL, server, INT_TO_PTR(fd))); + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + ASSERT_OK(sd_fiber_new(e, "server", server, INT_TO_PTR(fd), /* destroy= */ NULL, &f_server)); TAKE_FD(fd); - ASSERT_OK(-pthread_create(&c, NULL, client, a)); + ASSERT_OK(sd_fiber_new(e, "client", client, a, /* destroy= */ NULL, &f_client)); + + ASSERT_OK(sd_event_loop(e)); - ASSERT_OK(-pthread_join(s, NULL)); - ASSERT_OK(-pthread_join(c, NULL)); + ASSERT_OK(sd_future_result(f_server)); + ASSERT_OK(sd_future_result(f_client)); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/libsystemd/sd-bus/test-bus-server.c b/src/libsystemd/sd-bus/test-bus-server.c index 989d2bf10dcaa..1edcec858f2ac 100644 --- a/src/libsystemd/sd-bus/test-bus-server.c +++ b/src/libsystemd/sd-bus/test-bus-server.c @@ -1,10 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-bus.h" +#include "sd-event.h" +#include "sd-future.h" +#include "errno-util.h" #include "log.h" #include "memory-util.h" #include "string-util.h" @@ -20,7 +22,8 @@ struct context { bool server_anonymous_auth; }; -static int _server(struct context *c) { +static int server(void *userdata) { + struct context *c = ASSERT_PTR(userdata); _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; sd_id128_t id; bool quit = false; @@ -29,6 +32,7 @@ static int _server(struct context *c) { ASSERT_OK(sd_id128_randomize(&id)); ASSERT_OK(sd_bus_new(&bus)); + ASSERT_OK(sd_bus_set_description(bus, "server")); ASSERT_OK(sd_bus_set_fd(bus, c->fds[0], c->fds[0])); ASSERT_OK(sd_bus_set_server(bus, 1, id)); ASSERT_OK(sd_bus_set_anonymous(bus, c->server_anonymous_auth)); @@ -74,17 +78,16 @@ static int _server(struct context *c) { return 0; } -static void* server(void *p) { - return INT_TO_PTR(_server(p)); -} - -static int client(struct context *c) { +static int client(void *userdata) { + struct context *c = ASSERT_PTR(userdata); _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; ASSERT_OK(sd_bus_new(&bus)); + ASSERT_OK(sd_bus_set_description(bus, "client")); ASSERT_OK(sd_bus_set_fd(bus, c->fds[1], c->fds[1])); + ASSERT_OK(sd_bus_attach_event(bus, sd_fiber_get_event(), 0)); ASSERT_OK(sd_bus_negotiate_fds(bus, c->client_negotiate_unix_fds)); ASSERT_OK(sd_bus_set_anonymous(bus, c->client_anonymous_auth)); ASSERT_OK(sd_bus_start(bus)); @@ -103,10 +106,10 @@ static int client(struct context *c) { static int test_one(bool client_negotiate_unix_fds, bool server_negotiate_unix_fds, bool client_anonymous_auth, bool server_anonymous_auth) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f_server = NULL, *f_client = NULL; struct context c; - pthread_t s; - void *p; - int r, q; + int r = 0; zero(c); @@ -117,23 +120,18 @@ static int test_one(bool client_negotiate_unix_fds, bool server_negotiate_unix_f c.client_anonymous_auth = client_anonymous_auth; c.server_anonymous_auth = server_anonymous_auth; - r = pthread_create(&s, NULL, server, &c); - if (r != 0) - return -r; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); - r = client(&c); + ASSERT_OK(sd_fiber_new(e, "server", server, &c, /* destroy= */ NULL, &f_server)); + ASSERT_OK(sd_fiber_new(e, "client", client, &c, /* destroy= */ NULL, &f_client)); - q = pthread_join(s, &p); - if (q != 0) - return -q; + ASSERT_OK(sd_event_loop(e)); - if (r < 0) - return r; + RET_GATHER(r, sd_future_result(f_client)); + RET_GATHER(r, sd_future_result(f_server)); - if (PTR_TO_INT(p) < 0) - return PTR_TO_INT(p); - - return 0; + return r; } int main(int argc, char *argv[]) { @@ -145,7 +143,7 @@ int main(int argc, char *argv[]) { ASSERT_OK(test_one(false, false, false, false)); ASSERT_OK(test_one(true, true, true, true)); ASSERT_OK(test_one(true, true, false, true)); - ASSERT_ERROR(test_one(true, true, true, false), EPERM); + ASSERT_ERROR(test_one(true, true, true, false), EACCES); return EXIT_SUCCESS; } 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-bus/test-bus-watch-bind.c b/src/libsystemd/sd-bus/test-bus-watch-bind.c index 1bf4ee7017119..6561633b8a823 100644 --- a/src/libsystemd/sd-bus/test-bus-watch-bind.c +++ b/src/libsystemd/sd-bus/test-bus-watch-bind.c @@ -1,10 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include -#include - #include "sd-bus.h" #include "sd-event.h" +#include "sd-future.h" #include "sd-id128.h" #include "alloc-util.h" @@ -44,33 +42,33 @@ static const sd_bus_vtable vtable[] = { SD_BUS_VTABLE_END, }; -static void* thread_server(void *p) { +static int server(void *userdata) { _cleanup_free_ char *suffixed = NULL, *suffixed_basename = NULL, *suffixed2 = NULL, *d = NULL; _cleanup_close_ int fd = -EBADF; union sockaddr_union u; - const char *path = p; + const char *path = ASSERT_PTR(userdata); int r; log_debug("Initializing server"); /* Let's play some games, by slowly creating the socket directory, and renaming it in the middle */ - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); ASSERT_OK(mkdir_parents(path, 0755)); - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); ASSERT_OK(path_extract_directory(path, &d)); ASSERT_OK(asprintf(&suffixed, "%s.%" PRIx64, d, random_u64())); ASSERT_OK_ERRNO(rename(d, suffixed)); - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); ASSERT_OK(asprintf(&suffixed2, "%s.%" PRIx64, d, random_u64())); ASSERT_OK_ERRNO(symlink(suffixed2, d)); - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); ASSERT_OK(path_extract_filename(suffixed, &suffixed_basename)); ASSERT_OK_ERRNO(symlink(suffixed_basename, suffixed2)); - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); socklen_t sa_len; r = sockaddr_un_set_path(&u.un, path); @@ -81,13 +79,13 @@ static void* thread_server(void *p) { ASSERT_OK_ERRNO(fd); ASSERT_OK_ERRNO(bind(fd, &u.sa, sa_len)); - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); ASSERT_OK_ERRNO(listen(fd, SOMAXCONN_DELUXE)); - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); ASSERT_OK(touch(path)); - usleep_safe(100 * USEC_PER_MSEC); + ASSERT_OK(sd_fiber_sleep(100 * USEC_PER_MSEC)); log_debug("Initialized server"); @@ -101,8 +99,7 @@ static void* thread_server(void *p) { ASSERT_OK(sd_event_new(&event)); - bus_fd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); - ASSERT_OK_ERRNO(bus_fd); + ASSERT_OK(bus_fd = sd_fiber_accept(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC)); log_debug("Accepted server connection"); @@ -129,13 +126,13 @@ static void* thread_server(void *p) { log_debug("Server done"); - return NULL; + return 0; } -static void* thread_client1(void *p) { +static int client1(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; - const char *path = p, *t; + const char *path = ASSERT_PTR(userdata), *t; log_debug("Initializing client1"); @@ -151,59 +148,65 @@ static void* thread_client1(void *p) { log_debug("Client1 done"); - return NULL; -} - -static int client2_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - ASSERT_OK_ZERO(sd_bus_message_is_method_error(m, NULL)); - ASSERT_OK(sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), 0)); return 0; } -static void* thread_client2(void *p) { +static int client2(void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - const char *path = p, *t; + const char *path = ASSERT_PTR(userdata), *t; log_debug("Initializing client2"); - ASSERT_OK(sd_event_new(&event)); ASSERT_OK(sd_bus_new(&bus)); ASSERT_OK(sd_bus_set_description(bus, "client2")); t = strjoina("unix:path=", path); ASSERT_OK(sd_bus_set_address(bus, t)); ASSERT_OK(sd_bus_set_watch_bind(bus, true)); - ASSERT_OK(sd_bus_attach_event(bus, event, 0)); + ASSERT_OK(sd_bus_attach_event(bus, sd_fiber_get_event(), 0)); ASSERT_OK(sd_bus_start(bus)); - ASSERT_OK(sd_bus_call_method_async(bus, NULL, "foo.bar", "/foo", "foo.TestInterface", "Foobar", client2_callback, NULL, NULL)); - - ASSERT_OK(sd_event_loop(event)); + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + ASSERT_OK(sd_bus_call_method(bus, "foo.bar", "/foo", "foo.TestInterface", "Foobar", NULL, &m, NULL)); + ASSERT_OK_ZERO(sd_bus_message_is_method_error(m, NULL)); log_debug("Client2 done"); - return NULL; + return 0; } -static void request_exit(const char *path) { +typedef struct RequestExitArgs { + const char *path; + sd_future *client1; + sd_future *client2; +} RequestExitArgs; + +static int request_exit(void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + RequestExitArgs *args = ASSERT_PTR(userdata); const char *t; + /* Wait for all client fibers to complete before requesting exit */ + ASSERT_OK(sd_fiber_await(args->client1)); + ASSERT_OK(sd_fiber_await(args->client2)); + ASSERT_OK(sd_bus_new(&bus)); - t = strjoina("unix:path=", path); + t = strjoina("unix:path=", args->path); ASSERT_OK(sd_bus_set_address(bus, t)); ASSERT_OK(sd_bus_set_watch_bind(bus, true)); ASSERT_OK(sd_bus_set_description(bus, "request-exit")); ASSERT_OK(sd_bus_start(bus)); ASSERT_OK(sd_bus_call_method(bus, "foo.bar", "/foo", "foo.TestInterface", "Exit", NULL, NULL, NULL)); + + return 0; } int main(int argc, char *argv[]) { _cleanup_(rm_rf_physical_and_freep) char *d = NULL; - pthread_t server, client1, client2; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f_server = NULL, *f_client1 = NULL, *f_client2 = NULL, *f_exit = NULL; char *path; test_setup_logging(LOG_DEBUG); @@ -214,16 +217,27 @@ int main(int argc, char *argv[]) { path = strjoina(d, "/this/is/a/socket"); - ASSERT_OK(-pthread_create(&server, NULL, thread_server, path)); - ASSERT_OK(-pthread_create(&client1, NULL, thread_client1, path)); - ASSERT_OK(-pthread_create(&client2, NULL, thread_client2, path)); + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); - ASSERT_OK(-pthread_join(client1, NULL)); - ASSERT_OK(-pthread_join(client2, NULL)); + ASSERT_OK(sd_fiber_new(e, "server", server, path, /* destroy= */ NULL, &f_server)); - request_exit(path); + ASSERT_OK(sd_fiber_new(e, "client-1", client1, path, /* destroy= */ NULL, &f_client1)); + ASSERT_OK(sd_fiber_new(e, "client-2", client2, path, /* destroy= */ NULL, &f_client2)); - ASSERT_OK(-pthread_join(server, NULL)); + RequestExitArgs args = { + .path = path, + .client1 = f_client1, + .client2 = f_client2, + }; + ASSERT_OK(sd_fiber_new(e, "request-exit", request_exit, &args, /* destroy= */ NULL, &f_exit)); - return 0; + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(f_client1)); + ASSERT_OK(sd_future_result(f_client2)); + ASSERT_OK(sd_future_result(f_exit)); + ASSERT_OK(sd_future_result(f_server)); + + return EXIT_SUCCESS; } diff --git a/src/libsystemd/sd-common/sd-forward.h b/src/libsystemd/sd-common/sd-forward.h index 8abe655209dec..aca0ed32095f7 100644 --- a/src/libsystemd/sd-common/sd-forward.h +++ b/src/libsystemd/sd-common/sd-forward.h @@ -76,11 +76,7 @@ typedef enum sd_lldp_multicast_mode_t sd_lldp_multicast_mode_t; typedef enum sd_ndisc_event_t sd_ndisc_event_t; typedef struct sd_ipv4ll sd_ipv4ll; -typedef struct sd_dhcp_client sd_dhcp_client; -typedef struct sd_dhcp_lease sd_dhcp_lease; -typedef struct sd_dhcp_route sd_dhcp_route; typedef struct sd_dns_resolver sd_dns_resolver; -typedef struct sd_dhcp_server sd_dhcp_server; typedef struct sd_ndisc sd_ndisc; typedef struct sd_radv sd_radv; typedef struct sd_dhcp6_client sd_dhcp6_client; @@ -127,3 +123,9 @@ typedef struct sd_resolve sd_resolve; typedef struct sd_resolve_query sd_resolve_query; typedef struct sd_hwdb sd_hwdb; + +typedef struct sd_future sd_future; + +typedef int (*sd_future_func_t)(sd_future *f); +typedef int (*sd_fiber_func_t)(void *userdata); +typedef _sd_destroy_t sd_fiber_destroy_t; diff --git a/src/libsystemd/sd-daemon/sd-daemon.c b/src/libsystemd/sd-daemon/sd-daemon.c index 2ab50287b4ffa..50bdcb1a23816 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. */ @@ -635,8 +637,8 @@ static int pid_notify_with_fds_internal( } if (DEBUG_LOGGING) { - _cleanup_free_ char *escaped = xescape_full(state, "\"", /* console_width = */ SIZE_MAX, XESCAPE_8_BIT); - log_debug("Notify message sent to '%s': \"%s\"", e, escaped ?: state); + _cleanup_free_ char *escaped = shell_escape(state, "\""); + log_debug("Notify message sent to '%s': \"%s\"", e, strnull(escaped)); } return 1; diff --git a/src/libsystemd/sd-device/device-enumerator.c b/src/libsystemd/sd-device/device-enumerator.c index b3fe85a976167..6c1e79edd588c 100644 --- a/src/libsystemd/sd-device/device-enumerator.c +++ b/src/libsystemd/sd-device/device-enumerator.c @@ -9,6 +9,7 @@ #include "device-filter.h" #include "device-util.h" #include "dirent-util.h" +#include "errno-util.h" #include "fd-util.h" #include "log.h" #include "path-util.h" @@ -82,18 +83,13 @@ _public_ int sd_device_enumerator_new(sd_device_enumerator **ret) { return 0; } -static void device_unref_many(sd_device **devices, size_t n) { - assert(devices || n == 0); - - for (size_t i = 0; i < n; i++) - sd_device_unref(devices[i]); -} +static DEFINE_POINTER_ARRAY_CLEAR_FUNC(sd_device*, sd_device_unref); static void device_enumerator_unref_devices(sd_device_enumerator *enumerator) { assert(enumerator); hashmap_clear(enumerator->devices_by_syspath); - device_unref_many(enumerator->devices, enumerator->n_devices); + sd_device_unref_array_clear(enumerator->devices, enumerator->n_devices); enumerator->devices = mfree(enumerator->devices); enumerator->n_devices = 0; } @@ -461,7 +457,7 @@ static int enumerator_sort_devices(sd_device_enumerator *enumerator) { typesafe_qsort(devices + n_sorted, n - n_sorted, device_compare); - device_unref_many(enumerator->devices, enumerator->n_devices); + sd_device_unref_array_clear(enumerator->devices, enumerator->n_devices); enumerator->n_devices = n; free_and_replace(enumerator->devices, devices); @@ -470,7 +466,7 @@ static int enumerator_sort_devices(sd_device_enumerator *enumerator) { return 0; failed: - device_unref_many(devices, n); + sd_device_unref_array_clear(devices, n); free(devices); return r; } @@ -746,7 +742,7 @@ static int enumerator_scan_dir_and_add_devices( k = sd_device_new_from_syspath(&device, syspath); if (k < 0) { - if (k != -ENODEV) + if (!ERRNO_IS_NEG_DEVICE_ABSENT(k)) /* this is necessarily racey, so ignore missing devices */ r = k; @@ -841,7 +837,7 @@ static int enumerator_scan_devices_tag(sd_device_enumerator *enumerator, const c k = sd_device_new_from_device_id(&device, de->d_name); if (k < 0) { - if (k != -ENODEV) + if (!ERRNO_IS_NEG_DEVICE_ABSENT(k)) /* this is necessarily racy, so ignore missing devices */ r = k; @@ -887,7 +883,7 @@ static int parent_add_child(sd_device_enumerator *enumerator, const char *path, int r; r = sd_device_new_from_syspath(&device, path); - if (r == -ENODEV) + if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) /* this is necessarily racy, so ignore missing devices */ return 0; else if (r < 0) diff --git a/src/libsystemd/sd-device/device-internal.h b/src/libsystemd/sd-device/device-internal.h index 16a5792350283..f207a1618f7dc 100644 --- a/src/libsystemd/sd-device/device-internal.h +++ b/src/libsystemd/sd-device/device-internal.h @@ -6,6 +6,7 @@ #include "sd-forward.h" #include "iterator.h" +#define OLDEST_UDEV_DATABASE_VERSION 1 #define LATEST_UDEV_DATABASE_VERSION 1 struct sd_device { @@ -51,7 +52,7 @@ struct sd_device { /* The database version indicates the supported features by the udev database. * This is saved and parsed in V field. * - * 0: None of the following features are supported (systemd version <= 246). + * 0: None of the following features are supported (systemd version <= 246), unsupported since v261. * 1: The current tags (Q) and the database version (V) features are implemented (>= 247). */ unsigned database_version; 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/device-private.c b/src/libsystemd/sd-device/device-private.c index 054c3545d006d..48dce7768949e 100644 --- a/src/libsystemd/sd-device/device-private.c +++ b/src/libsystemd/sd-device/device-private.c @@ -912,7 +912,7 @@ int device_update_db(sd_device *device) { fprintf(f, "Q:%s\n", ct); /* Current tag */ /* Always write the latest database version here, instead of the value stored in - * device->database_version, as which may be 0. */ + * device->database_version. */ fputs("V:" STRINGIFY(LATEST_UDEV_DATABASE_VERSION) "\n", f); } diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h index f2f349f2acac9..0773ec909861d 100644 --- a/src/libsystemd/sd-device/device-private.h +++ b/src/libsystemd/sd-device/device-private.h @@ -16,13 +16,26 @@ int device_get_property_bool(sd_device *device, const char *key); int device_get_property_int(sd_device *device, const char *key, int *ret); int device_get_property_uint(sd_device *device, const char *key, unsigned *ret); int device_get_ifname(sd_device *device, const char **ret); -int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value); -int device_get_sysattr_unsigned_full(sd_device *device, const char *sysattr, unsigned base, unsigned *ret_value); -static inline int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value) { - return device_get_sysattr_unsigned_full(device, sysattr, 0, ret_value); +int device_get_sysattr_streq(sd_device *device, const char *sysattr, const char *expected); +int device_get_sysattr_safe_string(sd_device *device, const char *sysattr, const char **ret); +int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret); +int device_get_sysattr_unsigned_full(sd_device *device, const char *sysattr, unsigned base, unsigned *ret); +static inline int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret) { + return device_get_sysattr_unsigned_full(device, sysattr, /* base= */ 0, ret); } -int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value); -int device_get_sysattr_u64(sd_device *device, const char *sysattr, uint64_t *ret_value); +int device_get_sysattr_u8_full(sd_device *device, const char *sysattr, unsigned base, uint8_t *ret); +static inline int device_get_sysattr_u8(sd_device *device, const char *sysattr, uint8_t *ret) { + return device_get_sysattr_u8_full(device, sysattr, /* base= */ 0, ret); +} +int device_get_sysattr_u16_full(sd_device *device, const char *sysattr, unsigned base, uint16_t *ret); +static inline int device_get_sysattr_u16(sd_device *device, const char *sysattr, uint16_t *ret) { + return device_get_sysattr_u16_full(device, sysattr, /* base= */ 0, ret); +} +int device_get_sysattr_u32_full(sd_device *device, const char *sysattr, unsigned base, uint32_t *ret); +static inline int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret) { + return device_get_sysattr_u32_full(device, sysattr, /* base= */ 0, ret); +} +int device_get_sysattr_u64(sd_device *device, const char *sysattr, uint64_t *ret); int device_get_sysattr_bool(sd_device *device, const char *sysattr); int device_get_devlink_priority(sd_device *device, int *ret); int device_get_devnode_mode(sd_device *device, mode_t *ret); diff --git a/src/libsystemd/sd-device/device-util.h b/src/libsystemd/sd-device/device-util.h index 3bbe321f61649..e4350a679b690 100644 --- a/src/libsystemd/sd-device/device-util.h +++ b/src/libsystemd/sd-device/device-util.h @@ -6,8 +6,8 @@ #include "sd-forward.h" #include "log.h" -#define device_unref_and_replace(a, b) \ - unref_and_replace_full(a, b, sd_device_ref, sd_device_unref) +#define device_unref_and_replace_new_ref(a, b) \ + unref_and_replace_new_ref(a, b, sd_device_ref, sd_device_unref) #define FOREACH_DEVICE_PROPERTY(device, key, value) \ for (const char *value, *key = sd_device_get_property_first(device, &value); \ diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index 526f183b1e9ec..07c3dd60d2f8b 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -15,6 +15,7 @@ #include "dirent-util.h" #include "env-util.h" #include "errno-util.h" +#include "escape.h" #include "extract-word.h" #include "fd-util.h" #include "fileio.h" @@ -46,6 +47,7 @@ int device_new_aux(sd_device **ret) { .devuid = UID_INVALID, .devgid = GID_INVALID, .action = _SD_DEVICE_ACTION_INVALID, + .database_version = OLDEST_UDEV_DATABASE_VERSION, }; *ret = device; @@ -81,18 +83,47 @@ 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 */ + return string_is_safe(value, + STRING_ALLOW_BACKSLASHES | + STRING_ALLOW_QUOTES | + STRING_ALLOW_GLOBS); +} + 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)) { + if (DEBUG_LOGGING) { + _cleanup_free_ char *escaped_key = cescape(key), + *escaped_value = cescape(strempty(value)); + log_device_debug(device, "sd-device: Refusing invalid property: %s=%s", + strnull(escaped_key), strnull(escaped_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; @@ -806,7 +837,7 @@ int device_read_uevent_file(sd_device *device) { device->uevent_loaded = true; const char *uevent; - r = sd_device_get_sysattr_value(device, "uevent", &uevent); + r = device_get_sysattr_safe_string(device, "uevent", &uevent); if (ERRNO_IS_NEG_PRIVILEGE(r) || ERRNO_IS_NEG_DEVICE_ABSENT(r)) /* The uevent files may be write-only, the device may be already removed, or the device * may not have the uevent file. */ @@ -857,7 +888,7 @@ int device_read_uevent_file(sd_device *device) { return 0; } -_public_ int sd_device_get_ifindex(sd_device *device, int *ifindex) { +_public_ int sd_device_get_ifindex(sd_device *device, int *ret) { int r; assert_return(device, -EINVAL); @@ -869,8 +900,8 @@ _public_ int sd_device_get_ifindex(sd_device *device, int *ifindex) { if (device->ifindex <= 0) return -ENOENT; - if (ifindex) - *ifindex = device->ifindex; + if (ret) + *ret = device->ifindex; return 0; } @@ -1227,7 +1258,7 @@ _public_ int sd_device_get_subsystem(sd_device *device, const char **ret) { if (!device->subsystem_set) { const char *subsystem; - r = sd_device_get_sysattr_value(device, "subsystem", &subsystem); + r = device_get_sysattr_safe_string(device, "subsystem", &subsystem); if (r < 0 && r != -ENOENT) return log_device_debug_errno(device, r, "sd-device: Failed to read subsystem for %s: %m", @@ -1317,7 +1348,7 @@ _public_ int sd_device_get_parent_with_subsystem_devtype(sd_device *device, cons } } -_public_ int sd_device_get_devnum(sd_device *device, dev_t *devnum) { +_public_ int sd_device_get_devnum(sd_device *device, dev_t *ret) { int r; assert_return(device, -EINVAL); @@ -1329,8 +1360,8 @@ _public_ int sd_device_get_devnum(sd_device *device, dev_t *devnum) { if (major(device->devnum) <= 0) return -ENOENT; - if (devnum) - *devnum = device->devnum; + if (ret) + *ret = device->devnum; return 0; } @@ -1367,7 +1398,7 @@ _public_ int sd_device_get_driver(sd_device *device, const char **ret) { if (!device->driver_set) { const char *driver = NULL; - r = sd_device_get_sysattr_value(device, "driver", &driver); + r = device_get_sysattr_safe_string(device, "driver", &driver); if (r < 0 && r != -ENOENT) return log_device_debug_errno(device, r, "sd-device: Failed to read driver: %m"); @@ -1628,7 +1659,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 +1678,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) { @@ -1948,26 +1974,11 @@ _public_ const char* sd_device_get_tag_next(sd_device *device) { return v; } -static bool device_database_supports_current_tags(sd_device *device) { - assert(device); - - (void) device_read_db(device); - - /* The current tags (saved in Q field) feature is implemented in database version 1. - * If the database version is 0, then the tags (NOT current tags, saved in G field) are not - * sticky. Thus, we can safely bypass the operations for the current tags (Q) to tags (G). */ - - return device->database_version >= 1; -} - _public_ const char* sd_device_get_current_tag_first(sd_device *device) { void *v; assert_return(device, NULL); - if (!device_database_supports_current_tags(device)) - return sd_device_get_tag_first(device); - (void) device_read_db(device); device->current_tags_iterator_generation = device->tags_generation; @@ -1982,9 +1993,6 @@ _public_ const char* sd_device_get_current_tag_next(sd_device *device) { assert_return(device, NULL); - if (!device_database_supports_current_tags(device)) - return sd_device_get_tag_next(device); - (void) device_read_db(device); if (device->current_tags_iterator_generation != device->tags_generation) @@ -2255,15 +2263,12 @@ _public_ int sd_device_has_current_tag(sd_device *device, const char *tag) { assert_return(device, -EINVAL); assert_return(tag, -EINVAL); - if (!device_database_supports_current_tags(device)) - return sd_device_has_tag(device, tag); - (void) device_read_db(device); return set_contains(device->current_tags, tag); } -_public_ int sd_device_get_property_value(sd_device *device, const char *key, const char **ret_value) { +_public_ int sd_device_get_property_value(sd_device *device, const char *key, const char **ret) { const char *value; int r; @@ -2278,8 +2283,8 @@ _public_ int sd_device_get_property_value(sd_device *device, const char *key, co if (!value) return -ENOENT; - if (ret_value) - *ret_value = value; + if (ret) + *ret = value; return 0; } @@ -2610,49 +2615,24 @@ _public_ int sd_device_get_sysattr_value_with_size(sd_device *device, const char return device_get_cached_sysattr_value(device, sysattr, ret_value, ret_size); } -_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **ret_value) { - return sd_device_get_sysattr_value_with_size(device, sysattr, ret_value, NULL); +_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **ret) { + return sd_device_get_sysattr_value_with_size(device, sysattr, ret, NULL); } -int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value) { +int device_get_sysattr_streq(sd_device *device, const char *sysattr, const char *expected) { const char *value; int r; - r = sd_device_get_sysattr_value(device, sysattr, &value); - if (r < 0) - return r; - - int v; - r = safe_atoi(value, &v); - if (r < 0) - return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr); - - if (ret_value) - *ret_value = v; - /* We return "true" if the value is positive. */ - return v > 0; -} - -int device_get_sysattr_unsigned_full(sd_device *device, const char *sysattr, unsigned base, unsigned *ret_value) { - const char *value; - int r; + assert(expected); r = sd_device_get_sysattr_value(device, sysattr, &value); if (r < 0) return r; - unsigned v; - r = safe_atou_full(value, base, &v); - if (r < 0) - return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr); - - if (ret_value) - *ret_value = v; - /* We return "true" if the value is positive. */ - return v > 0; + return streq(value, expected); } -int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value) { +int device_get_sysattr_safe_string(sd_device *device, const char *sysattr, const char **ret) { const char *value; int r; @@ -2660,35 +2640,72 @@ int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret if (r < 0) return r; - uint32_t v; - r = safe_atou32(value, &v); - if (r < 0) - return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr); + if (!string_is_safe(value, + STRING_ALLOW_EMPTY | + STRING_ALLOW_NEWLINES | + STRING_ALLOW_BACKSLASHES | + STRING_ALLOW_QUOTES | + STRING_ALLOW_GLOBS)) { + if (DEBUG_LOGGING) { + _cleanup_free_ char *escaped = cescape(value); + log_device_debug(device, "sd-device: '%s' sysattr contains invalid characters, refusing: %s", + sysattr, strnull(escaped)); + } + return -ENXIO; + } - if (ret_value) - *ret_value = v; - /* We return "true" if the value is positive. */ - return v > 0; -} + if (ret) + *ret = value; -int device_get_sysattr_u64(sd_device *device, const char *sysattr, uint64_t *ret_value) { - const char *value; - int r; + return 0; +} - r = sd_device_get_sysattr_value(device, sysattr, &value); - if (r < 0) - return r; +#define DEFINE_DEVICE_GET_SYSATTR_PARSE(name, type, parser) \ + int device_get_sysattr_##name(sd_device *device, const char *sysattr, type *ret) { \ + const char *value; \ + int r; \ + \ + r = sd_device_get_sysattr_value(device, sysattr, &value); \ + if (r < 0) \ + return r; \ + \ + type v; \ + r = parser(value, &v); \ + if (r < 0) \ + return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr); \ + \ + if (ret) \ + *ret = v; \ + /* We return "true" if the value is positive. */ \ + return v > 0; \ + } - uint64_t v; - r = safe_atou64(value, &v); - if (r < 0) - return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr); +#define DEFINE_DEVICE_GET_SYSATTR_PARSE_BASE(name, type, parser) \ + int device_get_sysattr_##name##_full(sd_device *device, const char *sysattr, unsigned base, type *ret) { \ + const char *value; \ + int r; \ + \ + r = sd_device_get_sysattr_value(device, sysattr, &value); \ + if (r < 0) \ + return r; \ + \ + type v; \ + r = parser(value, base, &v); \ + if (r < 0) \ + return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr); \ + \ + if (ret) \ + *ret = v; \ + /* We return "true" if the value is positive. */ \ + return v > 0; \ + } - if (ret_value) - *ret_value = v; - /* We return "true" if the value is positive. */ - return v > 0; -} +DEFINE_DEVICE_GET_SYSATTR_PARSE(int, int, safe_atoi); +DEFINE_DEVICE_GET_SYSATTR_PARSE_BASE(unsigned, unsigned, safe_atou_full); +DEFINE_DEVICE_GET_SYSATTR_PARSE_BASE(u8, uint8_t, safe_atou8_full); +DEFINE_DEVICE_GET_SYSATTR_PARSE_BASE(u16, uint16_t, safe_atou16_full); +DEFINE_DEVICE_GET_SYSATTR_PARSE_BASE(u32, uint32_t, safe_atou32_full); +DEFINE_DEVICE_GET_SYSATTR_PARSE(u64, uint64_t, safe_atou64); int device_get_sysattr_bool(sd_device *device, const char *sysattr) { const char *value; 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..c53f0b0e88d6e 100644 --- a/src/libsystemd/sd-device/test-sd-device.c +++ b/src/libsystemd/sd-device/test-sd-device.c @@ -305,6 +305,48 @@ static void test_sd_device_one(sd_device *d) { ASSERT_OK(r = device_get_sysattr_unsigned(d, "nsid", &x)); ASSERT_EQ(x > 0, r > 0); } + + const char *uevent; + if (sd_device_get_sysattr_value(d, "uevent", &uevent) >= 0) { + const char *uevent_safe; + ASSERT_OK(device_get_sysattr_safe_string(d, "uevent", &uevent_safe)); + ASSERT_STREQ(uevent, uevent_safe); + } + + if (sd_device_get_ifindex(d, &ifindex) >= 0) { + int i; + ASSERT_OK_POSITIVE(device_get_sysattr_int(d, "ifindex", &i)); + ASSERT_EQ(i, ifindex); + + unsigned u; + ASSERT_OK_POSITIVE(device_get_sysattr_unsigned(d, "ifindex", &u)); + ASSERT_EQ(u, (unsigned) ifindex); + + uint64_t u64; + ASSERT_OK_POSITIVE(device_get_sysattr_u64(d, "ifindex", &u64)); + ASSERT_EQ(u64, (uint64_t) ifindex); + + uint32_t u32; + ASSERT_OK_POSITIVE(device_get_sysattr_u32(d, "ifindex", &u32)); + ASSERT_EQ(u32, (uint32_t) ifindex); + + if (ifindex <= UINT16_MAX) { + uint16_t u16; + ASSERT_OK_POSITIVE(device_get_sysattr_u16(d, "ifindex", &u16)); + ASSERT_EQ(u16, (uint16_t) ifindex); + } + + if (ifindex <= UINT8_MAX) { + uint8_t u8; + ASSERT_OK_POSITIVE(device_get_sysattr_u8(d, "ifindex", &u8)); + ASSERT_EQ(u8, (uint8_t) ifindex); + } + + const char *s; + ASSERT_OK(sd_device_get_sysattr_value(d, "ifindex", &s)); + ASSERT_OK_POSITIVE(device_get_sysattr_streq(d, "ifindex", s)); + ASSERT_OK_ZERO(device_get_sysattr_streq(d, "ifindex", "hoge")); + } } static void exclude_problematic_devices(sd_device_enumerator *e) { @@ -351,6 +393,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 +886,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-future.c b/src/libsystemd/sd-event/event-future.c new file mode 100644 index 0000000000000..4e0a0a87fc204 --- /dev/null +++ b/src/libsystemd/sd-event/event-future.c @@ -0,0 +1,308 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-event.h" +#include "sd-future.h" + +#include "alloc-util.h" +#include "errno-util.h" +#include "event-future.h" +#include "event-util.h" +#include "fd-util.h" + +typedef struct IoFuture { + sd_event_source *source; +} IoFuture; + +static void* io_future_alloc(void) { + return new0(IoFuture, 1); +} + +static void io_future_free(sd_future *f) { + IoFuture *iof = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + sd_event_source_unref(iof->source); + free(iof); +} + +static int io_future_cancel(sd_future *f) { + IoFuture *iof = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + int r = 0; + + RET_GATHER(r, sd_event_source_set_enabled(iof->source, SD_EVENT_OFF)); + RET_GATHER(r, sd_future_resolve(f, -ECANCELED)); + return r; +} + +static int io_future_set_priority(sd_future *f, int64_t priority) { + IoFuture *iof = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + return sd_event_source_set_priority(iof->source, priority); +} + +static const sd_future_ops io_future_ops = { + .size = sizeof(sd_future_ops), + .alloc = io_future_alloc, + .free = io_future_free, + .cancel = io_future_cancel, + .set_priority = io_future_set_priority, +}; + +static int io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_future *f = ASSERT_PTR(userdata); + + /* Resolve with the revents mask on success (matching io_uring poll_add's CQE convention) so + * callers can read it directly off the future result. EPOLLERR is the one exception: surface + * the actual socket error via SO_ERROR so callers like sd_fiber_connect() can return -errno + * directly without re-querying. */ + if (FLAGS_SET(revents, EPOLLERR)) { + int error = 0; + socklen_t len = sizeof(error); + + int r = RET_NERRNO(getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len)); + if (r == -ENOTSOCK) + return sd_future_resolve(f, (int) revents); + if (r >= 0 && error != 0) + return sd_future_resolve(f, -error); + if (r >= 0) + /* EPOLLERR was reported but SO_ERROR returned no pending error (e.g. + * already consumed elsewhere). Surface the revents mask so the caller + * still sees the error condition rather than mistaking it for success. */ + return sd_future_resolve(f, (int) revents); + /* On any other getsockopt() error fall through and resolve the future with that + * error so the waiting fiber wakes up rather than hanging forever. */ + return sd_future_resolve(f, r); + } + + return sd_future_resolve(f, (int) revents); +} + +int future_new_io(sd_event *e, int fd, uint32_t events, sd_future **ret) { + int r; + + assert(e); + assert(fd >= 0); + assert(ret); + + if (IN_SET(sd_event_get_state(e), SD_EVENT_EXITING, SD_EVENT_FINISHED)) + return -ECANCELED; + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_future_new(&io_future_ops, &f); + if (r < 0) + return r; + + IoFuture *iof = sd_future_get_private(f); + + /* Duplicate fd to avoid EEXIST from epoll when adding the same fd multiple times */ + _cleanup_close_ int fd_copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (fd_copy < 0) + return -errno; + + r = sd_event_add_io(e, &iof->source, fd_copy, events, io_handler, f); + if (r < 0) + return r; + + r = sd_event_source_set_io_fd_own(iof->source, true); + if (r < 0) + return r; + + TAKE_FD(fd_copy); + + r = sd_event_source_set_enabled(iof->source, SD_EVENT_ONESHOT); + if (r < 0) + return r; + + if (sd_fiber_is_running()) { + int64_t priority; + + r = sd_fiber_get_priority(&priority); + if (r < 0) + return r; + + r = sd_event_source_set_priority(iof->source, priority); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(f); + return 0; +} + +typedef struct TimeFuture { + sd_event_source *source; + + /* Result the future resolves with on natural expiry (vs. cancellation). 0 for normal sleep, + * non-zero (e.g. -ETIMEDOUT) lets a fiber waiting on this future resume with that error. */ + int result; +} TimeFuture; + +static void* time_future_alloc(void) { + return new0(TimeFuture, 1); +} + +static void time_future_free(sd_future *f) { + TimeFuture *tf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + sd_event_source_unref(tf->source); + free(tf); +} + +static int time_future_cancel(sd_future *f) { + TimeFuture *tf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + int r; + + r = sd_event_source_set_enabled(tf->source, SD_EVENT_OFF); + RET_GATHER(r, sd_future_resolve(f, -ECANCELED)); + return r; +} + +static int time_future_set_priority(sd_future *f, int64_t priority) { + TimeFuture *tf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + return sd_event_source_set_priority(tf->source, priority); +} + +static const sd_future_ops time_future_ops = { + .size = sizeof(sd_future_ops), + .alloc = time_future_alloc, + .free = time_future_free, + .cancel = time_future_cancel, + .set_priority = time_future_set_priority, +}; + +static int time_handler(sd_event_source *s, usec_t usec, void *userdata) { + sd_future *f = ASSERT_PTR(userdata); + TimeFuture *tf = ASSERT_PTR(sd_future_get_private(f)); + + return sd_future_resolve(f, tf->result); +} + +typedef int (*event_add_time_func)( + sd_event *e, + sd_event_source **ret, + clockid_t clock, + uint64_t usec, + uint64_t accuracy, + sd_event_time_handler_t callback, + void *userdata); + +static int future_new_time_internal( + event_add_time_func add_time, + sd_event *e, + clockid_t clock, + uint64_t usec, + uint64_t accuracy, + int result, + sd_future **ret) { + + int r; + + assert(add_time); + assert(e); + assert(ret); + + if (IN_SET(sd_event_get_state(e), SD_EVENT_EXITING, SD_EVENT_FINISHED)) + return -ECANCELED; + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_future_new(&time_future_ops, &f); + if (r < 0) + return r; + + TimeFuture *tf = sd_future_get_private(f); + tf->result = result; + + r = add_time(e, &tf->source, clock, usec, accuracy, time_handler, f); + if (r < 0) + return r; + + if (sd_fiber_is_running()) { + int64_t priority; + + r = sd_fiber_get_priority(&priority); + if (r < 0) + return r; + + r = sd_event_source_set_priority(tf->source, priority); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(f); + return 0; +} + +int future_new_time(sd_event *e, clockid_t clock, uint64_t usec, uint64_t accuracy, int result, sd_future **ret) { + return future_new_time_internal(sd_event_add_time, e, clock, usec, accuracy, result, ret); +} + +int future_new_time_relative(sd_event *e, clockid_t clock, uint64_t usec, uint64_t accuracy, int result, sd_future **ret) { + return future_new_time_internal(sd_event_add_time_relative, e, clock, usec, accuracy, result, ret); +} + +int event_run_suspend(sd_event *e, uint64_t timeout) { + sd_event *outer = sd_fiber_get_event(); + int r; + + assert(e); + assert(sd_fiber_is_running()); + assert(outer); + assert(e != outer); + + /* Make sure that none of the preparation callbacks ends up freeing the event source under our feet */ + PROTECT_EVENT(e); + + r = sd_event_prepare(e); + if (r < 0) + return r; + if (r == 0) { + r = sd_event_wait(e, 0); + if (r < 0) + return r; + } + if (r > 0) { + r = sd_event_dispatch(e); + if (r < 0) + return r; + + return 1; + } + + if (timeout == 0) + return 0; + + int fd = sd_event_get_fd(e); + if (fd < 0) + return fd; + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *io = NULL; + r = future_new_io(outer, fd, EPOLLIN, &io); + if (r < 0) + return r; + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *timer = NULL; + if (timeout != USEC_INFINITY) { + r = future_new_time_relative( + outer, + CLOCK_MONOTONIC, + timeout, + /* accuracy= */ 1, + /* result= */ 0, + &timer); + if (r < 0) + return r; + } + + r = sd_fiber_suspend(); + if (r < 0) + return r; + + r = sd_event_prepare(e); + if (r == 0) + r = sd_event_wait(e, 0); + if (r > 0) { + r = sd_event_dispatch(e); + if (r < 0) + return r; + + return 1; + } + + return r; +} diff --git a/src/libsystemd/sd-event/event-future.h b/src/libsystemd/sd-event/event-future.h new file mode 100644 index 0000000000000..83d5939d6b02d --- /dev/null +++ b/src/libsystemd/sd-event/event-future.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +int future_new_io(sd_event *e, int fd, uint32_t events, sd_future **ret); +int future_new_time(sd_event *e, clockid_t clock, uint64_t usec, uint64_t accuracy, int result, sd_future **ret); +int future_new_time_relative(sd_event *e, clockid_t clock, uint64_t usec, uint64_t accuracy, int result, sd_future **ret); + +int event_run_suspend(sd_event *e, uint64_t timeout); 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/event-util.h b/src/libsystemd/sd-event/event-util.h index dc3b3ed70ff12..ce213b9c9e4d9 100644 --- a/src/libsystemd/sd-event/event-util.h +++ b/src/libsystemd/sd-event/event-util.h @@ -5,6 +5,9 @@ #include "sd-forward.h" +#define PROTECT_EVENT(e) \ + _unused_ _cleanup_(sd_event_unrefp) sd_event *_ref = sd_event_ref(e); + extern const struct hash_ops event_source_hash_ops; int event_reset_time( diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index b78cfe86fa40e..4935ae1b4aac2 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -10,12 +10,15 @@ #include "sd-daemon.h" #include "sd-event.h" +#include "sd-future.h" #include "sd-id128.h" #include "sd-messages.h" #include "alloc-util.h" #include "errno-util.h" +#include "event-future.h" #include "event-source.h" +#include "event-util.h" #include "fd-util.h" #include "format-util.h" #include "glyph-util.h" @@ -76,6 +79,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 +104,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 +151,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; @@ -470,9 +477,6 @@ _public_ sd_event* sd_event_unref(sd_event *e) { return event_free(e); } -#define PROTECT_EVENT(e) \ - _unused_ _cleanup_(sd_event_unrefp) sd_event *_ref = sd_event_ref(e); - _public_ sd_event_source* sd_event_source_disable_unref(sd_event_source *s) { int r; @@ -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; @@ -4544,19 +4629,9 @@ static int epoll_wait_usec( int maxevents, usec_t timeout) { - int msec; - /* A wrapper that uses epoll_pwait2() if available, and falls back to epoll_wait() if not. */ - -#if HAVE_EPOLL_PWAIT2 static bool epoll_pwait2_absent = false; - int r; - - /* epoll_pwait2() was added to Linux 5.11 (2021-02-14) and to glibc in 2.35 (2022-02-03). In contrast - * to other syscalls we don't bother with our own fallback syscall wrappers on old libcs, since this - * is not that obvious to implement given the libc and kernel definitions differ in the last - * argument. Moreover, the only reason to use it is the more accurate timeouts (which is not a - * biggie), let's hence rely on glibc's definitions, and fallback to epoll_pwait() when that's - * missing. */ + int r, msec; + /* A wrapper that uses epoll_pwait2() if available, and falls back to epoll_wait() if not. */ if (!epoll_pwait2_absent && timeout != USEC_INFINITY) { r = epoll_pwait2(fd, @@ -4572,7 +4647,6 @@ static int epoll_wait_usec( epoll_pwait2_absent = true; } -#endif if (timeout == USEC_INFINITY) msec = -1; @@ -4665,7 +4739,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: @@ -4856,6 +4932,13 @@ _public_ int sd_event_run(sd_event *e, uint64_t timeout) { assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + /* When running on a fiber, delegate to the suspending implementation. Note that the + * profile_delays accounting below is intentionally skipped on that path: the suspending variant + * drives the event loop via sd_event_prepare()/sd_event_wait()/sd_event_dispatch() itself, which + * are the same primitives profile_delays tracks when called directly. */ + if (sd_fiber_is_running()) + return event_run_suspend(e, timeout); + if (e->profile_delays && e->last_run_usec != 0) { usec_t this_run; unsigned l; @@ -5074,6 +5157,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 +5386,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 +5415,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 +5465,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 +5487,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-event/test-event-future.c b/src/libsystemd/sd-event/test-event-future.c new file mode 100644 index 0000000000000..754daf0df2fc4 --- /dev/null +++ b/src/libsystemd/sd-event/test-event-future.c @@ -0,0 +1,358 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-event.h" +#include "sd-future.h" + +#include "fd-util.h" +#include "tests.h" +#include "time-util.h" + +static int timer_callback(sd_event_source *s, uint64_t usec, void *userdata) { + int *count = ASSERT_PTR(userdata); + int r; + + (*count)++; + + r = sd_event_source_set_time_relative(s, 5 * USEC_PER_MSEC); + if (r < 0) + return r; + + if (sd_fiber_is_running() && *count >= 3) + return sd_event_exit(sd_event_source_get_event(s), 0); + + return 0; +} + +static int event_run_fiber_func(void *userdata) { + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *inner_timer = NULL; + int r; + + /* Create inner event loop from within the fiber */ + r = sd_event_new(&inner); + if (r < 0) + return r; + + /* Add a timer to the inner event loop that fires every 5ms */ + r = sd_event_add_time_relative(inner, &inner_timer, CLOCK_MONOTONIC, + 5 * USEC_PER_MSEC, 0, timer_callback, + userdata); + if (r < 0) + return r; + + r = sd_event_source_set_enabled(inner_timer, SD_EVENT_ON); + if (r < 0) + return r; + + return sd_event_loop(inner); +} + +TEST(sd_event_loop_fiber) { + /* Create outer event loop for the fiber scheduler */ + _cleanup_(sd_event_unrefp) sd_event *outer = NULL; + ASSERT_OK(sd_event_new(&outer)); + ASSERT_OK(sd_event_set_exit_on_idle(outer, true)); + + /* Add a timer to the outer event loop that fires every 5ms */ + _cleanup_(sd_event_source_unrefp) sd_event_source *outer_timer = NULL; + int outer_timer_count = 0; + ASSERT_OK(sd_event_add_time_relative(outer, &outer_timer, CLOCK_MONOTONIC, + 5 * USEC_PER_MSEC, 0, timer_callback, + &outer_timer_count)); + + /* Create a fiber that will create and run the inner event loop */ + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int inner_timer_count = 0; + ASSERT_OK(sd_fiber_new(outer, "event-runner", event_run_fiber_func, &inner_timer_count, /* destroy= */ NULL, &f)); + + /* Run the outer event loop */ + ASSERT_OK(sd_event_loop(outer)); + + /* Fiber should have completed successfully */ + ASSERT_OK(sd_future_result(f)); + + /* Both timers should have fired at least once */ + ASSERT_EQ(inner_timer_count, 3); + ASSERT_GT(outer_timer_count, 0); +} + +static int event_run_fiber_timeout_func(void *userdata) { + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + int r; + + /* Create inner event loop from within the fiber */ + r = sd_event_new(&inner); + if (r < 0) + return r; + + /* Run with a short timeout - should timeout since there are no events */ + return sd_event_run(inner, 10 * USEC_PER_MSEC); +} + +TEST(sd_event_run_fiber_timeout) { + /* Create outer event loop for the fiber scheduler */ + _cleanup_(sd_event_unrefp) sd_event *outer = NULL; + ASSERT_OK(sd_event_new(&outer)); + ASSERT_OK(sd_event_set_exit_on_idle(outer, true)); + + /* Create a fiber that will run sd_event_run() with timeout */ + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(outer, "event-timeout", event_run_fiber_timeout_func, NULL, /* destroy= */ NULL, &f)); + + /* Run the outer event loop */ + ASSERT_OK(sd_event_loop(outer)); + + /* Fiber should have completed successfully (timeout returns 0) */ + ASSERT_OK_ZERO(sd_future_result(f)); +} + +/* Test: sd_event_run() with zero timeout returns immediately */ +static int sd_event_run_zero_timeout_fiber(void *userdata) { + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + int r; + + r = sd_event_new(&inner); + if (r < 0) + return r; + + /* With zero timeout on an empty event loop, should return 0 immediately */ + r = sd_event_run(inner, 0); + if (r != 0) + return r < 0 ? r : -EIO; + + return 0; +} + +TEST(sd_event_run_zero_timeout) { + _cleanup_(sd_event_unrefp) sd_event *outer = NULL; + ASSERT_OK(sd_event_new(&outer)); + ASSERT_OK(sd_event_set_exit_on_idle(outer, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(outer, "run-suspend-zero", sd_event_run_zero_timeout_fiber, NULL, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(outer)); + ASSERT_OK_ZERO(sd_future_result(f)); +} + +/* Test: sd_event_run() dispatches immediately pending IO */ +static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + int *counter = ASSERT_PTR(userdata); + char buf[64]; + + (*counter)++; + + /* Drain the fd */ + (void) read(fd, buf, sizeof(buf)); + + return sd_event_exit(sd_event_source_get_event(s), 0); +} + +static int sd_event_run_immediate_fiber(void *userdata) { + int *pipefd = ASSERT_PTR(userdata); + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *source = NULL; + int counter = 0, r; + + r = sd_event_new(&inner); + if (r < 0) + return r; + + /* Add IO source watching the read end of the pipe */ + r = sd_event_add_io(inner, &source, pipefd[0], EPOLLIN, io_callback, &counter); + if (r < 0) + return r; + + /* Data is already available on the pipe (written before fiber started), so + * sd_event_run() should dispatch immediately without suspending */ + r = sd_event_run(inner, USEC_INFINITY); + if (r < 0) + return r; + + /* The IO callback should have fired */ + if (counter != 1) + return -EIO; + + return 0; +} + +TEST(sd_event_run_immediate) { + _cleanup_(sd_event_unrefp) sd_event *outer = NULL; + ASSERT_OK(sd_event_new(&outer)); + ASSERT_OK(sd_event_set_exit_on_idle(outer, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + /* Write data before starting the fiber so it's immediately available */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "X", 1), 1); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(outer, "run-suspend-immediate", sd_event_run_immediate_fiber, pipefd, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(outer)); + ASSERT_OK_ZERO(sd_future_result(f)); +} + +/* Test: sd_event_run() with IO arriving during suspension */ +static int sd_event_run_io_fiber(void *userdata) { + int *pipefd = ASSERT_PTR(userdata); + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *source = NULL; + int counter = 0, r; + + r = sd_event_new(&inner); + if (r < 0) + return r; + + r = sd_event_add_io(inner, &source, pipefd[0], EPOLLIN, io_callback, &counter); + if (r < 0) + return r; + + /* No data available yet, so this will suspend the fiber until IO arrives */ + r = sd_event_run(inner, USEC_INFINITY); + if (r < 0) + return r; + + if (counter != 1) + return -EIO; + + return 0; +} + +TEST(sd_event_run_io) { + _cleanup_(sd_event_unrefp) sd_event *outer = NULL; + ASSERT_OK(sd_event_new(&outer)); + ASSERT_OK(sd_event_set_exit_on_idle(outer, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(outer, "run-suspend-io", sd_event_run_io_fiber, pipefd, /* destroy= */ NULL, &f)); + + /* First iteration: fiber runs, adds IO source, suspends because no data */ + ASSERT_OK_POSITIVE(sd_event_run(outer, 0)); + + /* Write data to the pipe to wake the inner event loop */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "Y", 1), 1); + + /* Complete: fiber resumes, dispatches IO, finishes */ + ASSERT_OK(sd_event_loop(outer)); + ASSERT_OK_ZERO(sd_future_result(f)); +} + +/* Test: event_run called in a loop keeps event loop state consistent. + * This is a regression test for a bug where error paths after sd_event_prepare() + * could leave the inner event loop stuck in SD_EVENT_ARMED state. */ +static int sd_event_run_loop_fiber(void *userdata) { + int *pipefd = ASSERT_PTR(userdata); + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *source = NULL; + int counter = 0, r; + + r = sd_event_new(&inner); + if (r < 0) + return r; + + r = sd_event_add_io(inner, &source, pipefd[0], EPOLLIN, io_callback, &counter); + if (r < 0) + return r; + + /* Call sd_event_run() multiple times with short timeouts. + * Each call should leave the inner event loop in a clean state for the next call. */ + for (int i = 0; i < 5; i++) { + r = sd_event_run(inner, 10 * USEC_PER_MSEC); + if (r < 0) + return r; + if (r > 0) + break; + } + + /* After multiple timeouts, the event loop should still be usable. + * Write data and do one more run to verify. */ + if (counter == 0) { + /* Data wasn't written yet, do a final run with longer timeout */ + r = sd_event_run(inner, USEC_INFINITY); + if (r < 0) + return r; + } + + if (counter != 1) + return -EIO; + + return 0; +} + +TEST(sd_event_run_loop) { + _cleanup_(sd_event_unrefp) sd_event *outer = NULL; + ASSERT_OK(sd_event_new(&outer)); + ASSERT_OK(sd_event_set_exit_on_idle(outer, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(outer, "run-suspend-loop", sd_event_run_loop_fiber, pipefd, /* destroy= */ NULL, &f)); + + /* Let the fiber run through a few timeout iterations */ + for (int i = 0; i < 10; i++) + ASSERT_OK(sd_event_run(outer, 50 * USEC_PER_MSEC)); + + /* Write data to unblock the fiber */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "Z", 1), 1); + + ASSERT_OK(sd_event_loop(outer)); + ASSERT_OK_ZERO(sd_future_result(f)); +} + +/* Test: sd_event_run() with an inner timer that fires during suspension */ +static int inner_timer_handler(sd_event_source *s, uint64_t usec, void *userdata) { + int *counter = ASSERT_PTR(userdata); + (*counter)++; + return sd_event_exit(sd_event_source_get_event(s), 0); +} + +static int sd_event_run_timer_fiber(void *userdata) { + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *source = NULL; + int counter = 0, r; + + r = sd_event_new(&inner); + if (r < 0) + return r; + + /* Add a timer that fires after 10ms */ + r = sd_event_add_time_relative(inner, &source, CLOCK_MONOTONIC, + 10 * USEC_PER_MSEC, 0, inner_timer_handler, + &counter); + if (r < 0) + return r; + + /* Should suspend, then resume when the timer fires */ + r = sd_event_run(inner, USEC_INFINITY); + if (r < 0) + return r; + + if (counter != 1) + return -EIO; + + return 0; +} + +TEST(sd_event_run_timer) { + _cleanup_(sd_event_unrefp) sd_event *outer = NULL; + ASSERT_OK(sd_event_new(&outer)); + ASSERT_OK(sd_event_set_exit_on_idle(outer, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(outer, "run-suspend-timer", sd_event_run_timer_fiber, NULL, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(outer)); + ASSERT_OK_ZERO(sd_future_result(f)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd/sd-future/fiber-io.c b/src/libsystemd/sd-future/fiber-io.c new file mode 100644 index 0000000000000..9fe0acfccd5e8 --- /dev/null +++ b/src/libsystemd/sd-future/fiber-io.c @@ -0,0 +1,471 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include /* IWYU pragma: keep */ +#include +#include +#include +#include + +#include "sd-event.h" +#include "sd-future.h" + +#include "alloc-util.h" +#include "errno-util.h" +#include "event-future.h" +#include "fd-util.h" +#include "io-util.h" +#include "time-util.h" + +typedef ssize_t (*FiberIOFunc)(int fd, void *args); + +static ssize_t fiber_io_operation( + int fd, + uint32_t events, + FiberIOFunc func, + void *args) { + _cleanup_(nonblock_resetp) int reset_fd = -EBADF; + int r; + + assert(fd >= 0); + assert(func); + + if (!sd_fiber_is_running()) + return func(fd, args); + + sd_event *e = sd_fiber_get_event(); + assert(e); + + r = fd_nonblock(fd, true); + if (r < 0) + return r; + if (r > 0) + reset_fd = fd; + + ssize_t n = func(fd, args); + if (n >= 0 || !ERRNO_IS_NEG_TRANSIENT(n)) + return n; + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *io = NULL; + r = future_new_io(e, fd, events, &io); + if (r < 0) + return r; + + r = sd_fiber_suspend(); + if (r < 0) + return r; + + return func(fd, args); +} + +typedef struct ReadArgs { + void *buf; + size_t count; +} ReadArgs; + +static ssize_t read_callback(int fd, void *args) { + ReadArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = read(fd, a->buf, a->count); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_read(int fd, void *buf, size_t count) { + assert_return(fd >= 0, -EBADF); + assert_return(buf || count == 0, -EINVAL); + + return fiber_io_operation(fd, EPOLLIN, read_callback, &(ReadArgs) { + .buf = buf, + .count = count, + }); +} + +typedef struct WriteArgs { + const void *buf; + size_t count; +} WriteArgs; + +static ssize_t write_callback(int fd, void *args) { + WriteArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = write(fd, a->buf, a->count); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_write(int fd, const void *buf, size_t count) { + assert_return(fd >= 0, -EBADF); + assert_return(buf || count == 0, -EINVAL); + + return fiber_io_operation(fd, EPOLLOUT, write_callback, &(WriteArgs) { + .buf = buf, + .count = count, + }); +} + +typedef struct ReadvArgs { + const struct iovec *iov; + int iovcnt; +} ReadvArgs; + +static ssize_t readv_callback(int fd, void *args) { + ReadvArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = readv(fd, a->iov, a->iovcnt); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_readv(int fd, const struct iovec *iov, int iovcnt) { + assert_return(fd >= 0, -EBADF); + assert_return(iov || iovcnt == 0, -EINVAL); + + return fiber_io_operation(fd, EPOLLIN, readv_callback, &(ReadvArgs) { + .iov = iov, + .iovcnt = iovcnt, + }); +} + +typedef struct WritevArgs { + const struct iovec *iov; + int iovcnt; +} WritevArgs; + +static ssize_t writev_callback(int fd, void *args) { + WritevArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = writev(fd, a->iov, a->iovcnt); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_writev(int fd, const struct iovec *iov, int iovcnt) { + assert_return(fd >= 0, -EBADF); + assert_return(iov || iovcnt == 0, -EINVAL); + + return fiber_io_operation(fd, EPOLLOUT, writev_callback, &(WritevArgs) { + .iov = iov, + .iovcnt = iovcnt, + }); +} + +typedef struct RecvArgs { + void *buf; + size_t len; + int flags; +} RecvArgs; + +static ssize_t recv_callback(int fd, void *args) { + RecvArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = recv(fd, a->buf, a->len, a->flags); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_recv(int sockfd, void *buf, size_t len, int flags) { + assert_return(sockfd >= 0, -EBADF); + assert_return(buf || len == 0, -EINVAL); + + return fiber_io_operation(sockfd, EPOLLIN, recv_callback, &(RecvArgs) { + .buf = buf, + .len = len, + .flags = flags, + }); +} + +typedef struct SendArgs { + const void *buf; + size_t len; + int flags; +} SendArgs; + +static ssize_t send_callback(int fd, void *args) { + SendArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = send(fd, a->buf, a->len, a->flags); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_send(int sockfd, const void *buf, size_t len, int flags) { + assert_return(sockfd >= 0, -EBADF); + assert_return(buf || len == 0, -EINVAL); + + return fiber_io_operation(sockfd, EPOLLOUT, send_callback, &(SendArgs) { + .buf = buf, + .len = len, + .flags = flags, + }); +} + +int sd_fiber_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { + _cleanup_(nonblock_resetp) int reset_fd = -EBADF; + int r; + + assert_return(sockfd >= 0, -EBADF); + assert_return(addr, -EINVAL); + + if (!sd_fiber_is_running()) + return RET_NERRNO(connect(sockfd, addr, addrlen)); + + sd_event *e = sd_fiber_get_event(); + assert(e); + + r = fd_nonblock(sockfd, true); + if (r < 0) + return r; + if (r > 0) + reset_fd = sockfd; + + r = RET_NERRNO(connect(sockfd, addr, addrlen)); + if (r != -EINPROGRESS) + return r; + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *io = NULL; + r = future_new_io(e, sockfd, EPOLLOUT, &io); + if (r < 0) + return r; + + /* future_new_io resolves with the revents mask on success; translate any positive value + * (e.g. POLLOUT) back to the connect(2) success status. */ + r = sd_fiber_suspend(); + return r > 0 ? 0 : r; +} + +typedef struct RecvmsgArgs { + struct msghdr *msg; + int flags; +} RecvmsgArgs; + +static ssize_t recvmsg_callback(int fd, void *args) { + RecvmsgArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = recvmsg(fd, a->msg, a->flags); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_recvmsg(int sockfd, struct msghdr *msg, int flags) { + assert_return(sockfd >= 0, -EBADF); + assert_return(msg, -EINVAL); + + return fiber_io_operation(sockfd, EPOLLIN, recvmsg_callback, &(RecvmsgArgs) { + .msg = msg, + .flags = flags, + }); +} + +typedef struct SendmsgArgs { + const struct msghdr *msg; + int flags; +} SendmsgArgs; + +static ssize_t sendmsg_callback(int fd, void *args) { + SendmsgArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = sendmsg(fd, a->msg, a->flags); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_sendmsg(int sockfd, const struct msghdr *msg, int flags) { + assert_return(sockfd >= 0, -EBADF); + assert_return(msg, -EINVAL); + + return fiber_io_operation(sockfd, EPOLLOUT, sendmsg_callback, &(SendmsgArgs) { + .msg = msg, + .flags = flags, + }); +} + +static ssize_t recvfrom_callback(int fd, void *args) { + RecvmsgArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = recvmsg(fd, a->msg, a->flags); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) { + ssize_t n; + + assert_return(sockfd >= 0, -EBADF); + assert_return(buf || len == 0, -EINVAL); + assert_return(!src_addr || addrlen, -EINVAL); + + /* io_uring has no direct recvfrom prep helper, so emulate via recvmsg with a single-iovec + * msghdr. The kernel updates msg_namelen in place; we copy it back to *addrlen below. */ + struct iovec iov = { .iov_base = buf, .iov_len = len }; + struct msghdr msg = { + .msg_name = src_addr, + .msg_namelen = src_addr ? *addrlen : 0, + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + n = fiber_io_operation(sockfd, EPOLLIN, recvfrom_callback, &(RecvmsgArgs) { + .msg = &msg, + .flags = flags, + }); + if (n < 0) + return n; + + if (addrlen) + *addrlen = msg.msg_namelen; + + return n; +} + +static ssize_t sendto_callback(int fd, void *args) { + SendmsgArgs *a = ASSERT_PTR(args); + ssize_t n; + + n = sendmsg(fd, a->msg, a->flags); + return n >= 0 ? n : -errno; +} + +ssize_t sd_fiber_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { + assert_return(sockfd >= 0, -EBADF); + assert_return(buf || len == 0, -EINVAL); + + struct iovec iov = { .iov_base = (void *) buf, .iov_len = len }; + struct msghdr msg = { + .msg_name = (void *) dest_addr, + .msg_namelen = dest_addr ? addrlen : 0, + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + return fiber_io_operation(sockfd, EPOLLOUT, sendto_callback, &(SendmsgArgs) { + .msg = &msg, + .flags = flags, + }); +} + +typedef struct AcceptArgs { + struct sockaddr *addr; + socklen_t *addrlen; + int flags; +} AcceptArgs; + +static ssize_t accept_callback(int fd, void *args) { + AcceptArgs *a = ASSERT_PTR(args); + + return RET_NERRNO(accept4(fd, a->addr, a->addrlen, a->flags)); +} + +int sd_fiber_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) { + assert_return(sockfd >= 0, -EBADF); + + return fiber_io_operation(sockfd, EPOLLIN, accept_callback, &(AcceptArgs) { + .addr = addr, + .addrlen = addrlen, + .flags = flags, + }); +} + +int sd_fiber_ppoll(struct pollfd *fds, size_t n_fds, const struct timespec *timeout, const sigset_t *sigmask) { + int r; + + assert_return(fds || n_fds == 0, -EINVAL); + + if (!sd_fiber_is_running()) + return RET_NERRNO(ppoll(fds, n_fds, timeout, sigmask)); + + /* When on a fiber signals are handled via sd-event hence we should never mess around with the + * signal mask when running on a fiber. */ + assert_return(!sigmask, -EOPNOTSUPP); + + sd_event *e = sd_fiber_get_event(); + assert(e); + + /* No fds to wait on and no timeout means there's nothing that could ever wake the fiber up, + * since unlike raw ppoll() we cannot use signal delivery as a wakeup. Signals received while + * the fiber is suspended are handled by sd-event via signalfd, in which case the signal handler + * is expected to cancel the fiber via sd_future_cancel() if a wakeup is desired. */ + if (n_fds == 0 && !timeout) + return -EINVAL; + + bool zero_timeout = timeout && timeout->tv_sec == 0 && timeout->tv_nsec == 0; + + /* Try polling with zero timeout first to see if any are immediately ready. */ + r = RET_NERRNO(ppoll(fds, n_fds, &(const struct timespec) {}, /* sigmask= */ NULL)); + if (zero_timeout || r != 0) /* Either error or some fds are ready */ + return r; + + sd_future **futures = NULL; + CLEANUP_ARRAY(futures, n_fds, sd_future_cancel_wait_unref_array); + + futures = new0(sd_future*, n_fds); + if (!futures) + return -ENOMEM; + + /* Set up I/O event sources for all valid fds. POLL* and EPOLL* share their bit values (see + * EPOLL_POLL_COMMON_MASK in io-util.h), so we can pass the user-supplied event mask through + * to either backend without translation. */ + size_t n_io_futures = 0; + for (size_t i = 0; i < n_fds; i++) { + if (fds[i].fd < 0) + continue; + + uint32_t events = poll_events_to_epoll(fds[i].events); + if (events == 0) + continue; + + r = future_new_io(e, fds[i].fd, events, &futures[i]); + if (r < 0) + return r; + + n_io_futures++; + } + + /* A timeout that overflows usec_t saturates to USEC_INFINITY in timespec_load(); treat that + * like "no timeout" (matches sd_fiber_sleep(USEC_INFINITY)) rather than letting + * sd_event_add_time_relative() reject it with -EOVERFLOW — standard ppoll() would just + * wait a very long time. */ + usec_t usec = timeout ? timespec_load(timeout) : USEC_INFINITY; + + /* If every fd was skipped (negative or empty event mask) and we'd have no timer, there's + * nothing that could ever wake the fiber up — same situation as n_fds == 0 && !timeout, + * just not detectable upfront. Refuse rather than suspend forever. */ + if (n_io_futures == 0 && usec == USEC_INFINITY) + return -EINVAL; + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *timer = NULL; + if (usec != USEC_INFINITY) { + r = future_new_time_relative( + e, + CLOCK_MONOTONIC, + usec, + /* accuracy= */ 1, + /* result= */ 0, + &timer); + if (r < 0) + return r; + } + + r = sd_fiber_suspend(); + if (r < 0 && r != -ETIME) + return r; + + /* Always sweep fds with a non-blocking ppoll(): the timer and an fd readiness can resolve in + * the same event-loop tick (or the fd can become ready between the timer firing and us being + * scheduled), and ppoll() semantics give events precedence over the timeout in that case. */ + int n = RET_NERRNO(ppoll(fds, n_fds, &(const struct timespec) {}, /* sigmask= */ NULL)); + if (n != 0) + return n; + + /* No fds ready: distinguish our own timer from an external -ETIME. */ + if (timer && sd_future_state(timer) == SD_FUTURE_RESOLVED) + return 0; + + /* An IO future resolved with a revents mask (r > 0) but the readiness was already consumed + * by the time we swept — report 0 rather than leaking the bitmask as a (bogus) ppoll fd + * count to the caller. */ + if (r > 0) + return 0; + + return r; +} diff --git a/src/libsystemd/sd-future/fiber.c b/src/libsystemd/sd-future/fiber.c new file mode 100644 index 0000000000000..64dee8411df22 --- /dev/null +++ b/src/libsystemd/sd-future/fiber.c @@ -0,0 +1,827 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_VALGRIND_VALGRIND_H +#include +#endif + +#include "sd-event.h" +#include "sd-future.h" + +#include "alloc-util.h" +#include "architecture.h" +#include "errno-util.h" +#include "event-future.h" +#include "fiber-ops.h" +#include "log-context.h" +#include "log.h" +#include "memory-util.h" +#include "pthread-util.h" +#include "time-util.h" + +#if HAS_FEATURE_ADDRESS_SANITIZER +#include +#endif + +/* glibc's _FORTIFY_SOURCE wraps siglongjmp() with __longjmp_chk, which asserts that the target SP is below + * the current SP. That assumption is incompatible with fiber switching, where the target SP lives on a + * separately-mmap'd stack and can be at any address relative to the caller. The fortify redirect happens + * in 's declaration of siglongjmp; we sidestep it by declaring our own alias that links + * directly to the unchecked "siglongjmp" symbol. musl doesn't fortify setjmp.h, so the alias is a plain + * synonym there. */ +_noreturn_ extern void siglongjmp_unchecked(sigjmp_buf env, int val) __asm__("siglongjmp"); + +static thread_local Fiber *current_fiber = NULL; + +typedef enum FiberState { + FIBER_STATE_INITIAL, + FIBER_STATE_READY, + FIBER_STATE_SUSPENDED, + FIBER_STATE_CANCELLED, + FIBER_STATE_COMPLETED, + _FIBER_STATE_MAX, + _FIBER_STATE_INVALID = -EINVAL, +} FiberState; + +typedef struct Fiber { + struct iovec stack; + sigjmp_buf context; /* Where to jump to when entering or resuming the fiber. */ + sigjmp_buf resume_context; /* Where to jump back to when the fiber yields or completes. */ + + /* Caller's stack range, recorded by fiber_run() on each entry so the fiber's siglongjmp back + * out (in fiber_swap() or the trampoline's terminate path) can hand AddressSanitizer the + * destination stack info. With ucontext this comes for free via uc_link/uc_stack; sigjmp_buf + * is opaque and doesn't carry it. */ + struct iovec resume_stack; + + FiberState state; + int result; /* Either resume error code or final return value */ + + sd_future *floating; /* Self-ref held while the fiber is floating; dropped on resolve. */ + + sd_event *event; + sd_event_source *defer_event_source; + sd_event_source *exit_event_source; + + char *name; + int64_t priority; + sd_fiber_func_t func; + void *userdata; + sd_fiber_destroy_t destroy; + + /* Storage for the swap performed in fiber_run(): while the fiber is suspended these hold the + * fiber's own log state; while it is running they hold the caller's log state. The active state + * always lives in the thread-locals in log.c / log-context.c. */ + LIST_HEAD(LogContext, log_context); + size_t log_context_num_fields; + const char *log_prefix; + +#if HAVE_VALGRIND_VALGRIND_H + unsigned stack_id; +#endif +} Fiber; + +static Fiber* fiber_get_current(void) { + return current_fiber; +} + +static void fiber_set_current(Fiber *f) { + current_fiber = f; +} + +static int fiber_allocate_stack(size_t size, void **ret) { + void *stack = NULL; + int r; + + /* The effective stack size is one page less than the given size, because we have to use + * one page as the guard page for the stack. */ + + assert(size > 0 && size % page_size() == 0); + assert(ret); + + stack = mmap(/* addr= */ NULL, size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, + /* fd= */ -EBADF, /* offset= */ 0); + if (stack == MAP_FAILED) + return -errno; + + /* Place the guard page where stack overflow will hit it: the high end on architectures + * where the stack grows up (PA-RISC), the low end everywhere else. fiber_stack_usable() + * mirrors this with the inverse offset. */ + void *guard = STACK_GROWS_UP ? (uint8_t*) stack + size - page_size() : stack; + + /* Prefer MADV_GUARD_INSTALL (Linux 6.13+): unlike mprotect(PROT_NONE) it doesn't split + * the VMA, so guard installation skips the mmap-lock contention and per-guard VMA cost. + * Fall back to mprotect on older kernels, which return EINVAL for unknown advice. + * FIXME: delete when baseline above 6.13. */ + r = RET_NERRNO(madvise(guard, page_size(), MADV_GUARD_INSTALL)); + if (r == -EINVAL) + r = RET_NERRNO(mprotect(guard, page_size(), PROT_NONE)); + if (r < 0) { + (void) munmap(stack, size); + return r; + } + + *ret = TAKE_PTR(stack); + return 0; +} + +/* Usable stack range of a fiber: the full mmap region minus the guard page. Single source of + * truth for the layout assumed by fiber_allocate_stack(); every consumer (ucontext ss_sp, + * ASAN handoff iovecs, Valgrind stack registration) goes through here. + * + * iov_base is the lowest usable byte regardless of growth direction — that matches POSIX's + * definition of stack_t.ss_sp, so libc's makecontext() handles the direction for us. Only the + * guard page placement (and hence iov_base's offset within the mapping) varies. */ +static struct iovec fiber_stack_usable(const struct iovec *stack) { + assert(stack); + assert(stack->iov_len > page_size()); + return (struct iovec) { + .iov_base = STACK_GROWS_UP ? stack->iov_base : (uint8_t*) stack->iov_base + page_size(), + .iov_len = stack->iov_len - page_size(), + }; +} + +static inline void start_switch_stack(void **fake_stack_save, const struct iovec *dest) { +#if HAS_FEATURE_ADDRESS_SANITIZER + __sanitizer_start_switch_fiber(fake_stack_save, + dest ? dest->iov_base : NULL, + dest ? dest->iov_len : 0); +#endif +} + +static inline void finish_switch_stack(void *fake_stack_save) { +#if HAS_FEATURE_ADDRESS_SANITIZER + __sanitizer_finish_switch_fiber(fake_stack_save, NULL, NULL); +#endif +} + +/* Refresh f->resume_stack from whoever is currently the running fiber, so the next siglongjmp() out + * of f (in the trampoline or fiber_swap()) can hand the right destination stack to ASAN. Must be + * called before fiber_set_current(f) — relies on fiber_get_current() returning the caller. */ +static void fiber_set_resume_stack(Fiber *f, Fiber *resume) { + assert(f); + + if (resume) + f->resume_stack = fiber_stack_usable(&resume->stack); + else + f->resume_stack = (struct iovec) {}; +} + +_noreturn_ static void fiber_entry_point(void) { + Fiber *f = ASSERT_PTR(fiber_get_current()); + void *fake_stack_save = NULL; + + assert(f->func); + assert(IN_SET(f->state, FIBER_STATE_INITIAL, FIBER_STATE_READY, FIBER_STATE_CANCELLED)); + + finish_switch_stack(NULL); + + /* Capture our resumable point on the fiber's stack, then bounce back to whoever last set + * f->resume_context. On bootstrap that's fiber_bootstrap(); on every subsequent yield it's + * the most recent fiber_run(). sigsetjmp(buf, 0) skips the signal-mask save: switching is + * thread-shared with respect to signal masks. */ + if (sigsetjmp(f->context, /* savemask= */ 0) == 0) { + start_switch_stack(&fake_stack_save, &f->resume_stack); + siglongjmp_unchecked(f->resume_context, /* val= */ 1); + } + + /* Re-entered for real via fiber_run()'s siglongjmp(f->context). */ + finish_switch_stack(fake_stack_save); + + /* Block scope so the cleanups attached to LOG_SET_PREFIX / LOG_CONTEXT_PUSH_KEY_VALUE fire + * before the siglongjmp below — siglongjmp skips _cleanup_ attributes, so we have to make + * sure the scope ends via a normal control-flow path first. */ + { + LOG_SET_PREFIX(f->name); + LOG_CONTEXT_PUSH_KEY_VALUE("FIBER=", f->name); + + f->result = f->state == FIBER_STATE_CANCELLED ? -ECANCELED : f->func(f->userdata); + f->state = FIBER_STATE_COMPLETED; + } + + /* Pass NULL fake_stack_save to discard the fiber's fake stack since the fiber is done. */ + start_switch_stack(NULL, &f->resume_stack); + + /* Bounce back to whichever fiber_run() call most recently entered us. resume_context is + * per-fiber so nested fiber_run() — e.g. a bus method dispatched as a fiber handler while + * sd_event_loop() itself runs in a fiber — is safe. */ + siglongjmp_unchecked(f->resume_context, 1); + assert_not_reached(); +} + +static int fiber_init(Fiber *f) { + ucontext_t old_uc, uc; + void *fake_stack_save = NULL; + + assert(f); + + if (getcontext(&uc) < 0) + return -errno; + + struct iovec fiber_stack = fiber_stack_usable(&f->stack); + + uc.uc_link = NULL; /* Unused: trampoline siglongjmps out instead of returning. */ + uc.uc_stack.ss_sp = fiber_stack.iov_base; + uc.uc_stack.ss_size = fiber_stack.iov_len; + uc.uc_stack.ss_flags = 0; + + Fiber *prev = fiber_get_current(); + fiber_set_current(f); + + makecontext(&uc, fiber_entry_point, /* argc= */ 0); + + fiber_set_resume_stack(f, prev); + if (sigsetjmp(f->resume_context, /* savemask= */ 0) == 0) { + start_switch_stack(&fake_stack_save, &fiber_stack); + if (swapcontext(&old_uc, &uc) < 0) { + finish_switch_stack(fake_stack_save); + fiber_set_current(prev); + return -errno; + } + assert_not_reached(); /* Trampoline siglongjmps back; swapcontext doesn't return. */ + } + + finish_switch_stack(fake_stack_save); + + fiber_set_current(prev); + return 0; +} + +/* Swap the thread-local log prefix and log context with the values stashed in f. While the fiber is + * suspended, f holds the fiber's own log state; while it's running, f holds the caller's log state. The + * swap is its own inverse, so the same call drives both directions. */ +static void fiber_swap_log_state(Fiber *f) { + assert(f); + log_prefix_swap(&f->log_prefix); + log_context_swap(&f->log_context, &f->log_context_num_fields); +} + +static void reset_current_fiber(void) { + /* Restore the caller's log state stashed in the running fiber (if any) before clearing + * current_fiber. Without this, the child of a fork() that happened mid-fiber would inherit the + * fiber's log prefix / context list in its thread-locals even though no fiber is running. */ + Fiber *f = fiber_get_current(); + if (f) { + fiber_swap_log_state(f); + fiber_ops_set(NULL); + } + fiber_set_current(NULL); +} + +static sd_event_source* fiber_current_event_source(Fiber *f) { + assert(f); + assert(f->state != FIBER_STATE_COMPLETED); + assert(f->event); + + return sd_event_get_state(f->event) == SD_EVENT_EXITING ? f->exit_event_source : f->defer_event_source; +} + +static int atfork_ret; + +static void install_atfork(void) { + /* __register_atfork() either returns 0 or -ENOMEM, in its glibc implementation. Since it's + * only half-documented (glibc doesn't document it but LSB does — though only superficially) + * we'll check for errors only in the most generic fashion possible. */ + atfork_ret = pthread_atfork(/* prepare= */ NULL, /* parent= */ NULL, reset_current_fiber); + if (atfork_ret != 0) + log_debug_errno(atfork_ret, "pthread_atfork() failed: %m"); +} + +static void fiber_resolve(sd_future *f) { + Fiber *fiber = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + + fiber->defer_event_source = sd_event_source_disable_unref(fiber->defer_event_source); + fiber->exit_event_source = sd_event_source_disable_unref(fiber->exit_event_source); + /* The floating self-ref (if any) is potentially the last ref keeping the fiber alive — moving it + * into a local _cleanup_ slot ensures sd_future_resolve() runs callbacks and waiters while f is + * still valid; the local's cleanup drops the ref afterwards, at which point no further f->... + * access can happen. */ + _unused_ _cleanup_(sd_future_unrefp) sd_future *floating = TAKE_PTR(fiber->floating); + sd_future_resolve(f, fiber->result); +} + +static const FiberOps fiber_ops = { + .ppoll = sd_fiber_ppoll, + .read = sd_fiber_read, + .write = sd_fiber_write, + .timeout = sd_fiber_timeout, + .cancel_wait_unref = sd_future_cancel_wait_unref, +}; + +static void fiber_enter(Fiber *fiber, Fiber *prev, void **fake_stack_save) { + fiber_set_current(fiber); + fiber_swap_log_state(fiber); + if (!prev) + fiber_ops_set(&fiber_ops); + + struct iovec fiber_stack = fiber_stack_usable(&fiber->stack); + start_switch_stack(fake_stack_save, &fiber_stack); + fiber_set_resume_stack(fiber, prev); +} + +static void fiber_leave(Fiber *fiber, Fiber *prev, void *fake_stack_save) { + finish_switch_stack(fake_stack_save); + if (!prev) + fiber_ops_set(NULL); + fiber_swap_log_state(fiber); + fiber_set_current(prev); +} + +static int fiber_run(sd_future *f) { + Fiber *fiber = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + int r; + + if (fiber->state == FIBER_STATE_COMPLETED) + return -ESTALE; + + assert(IN_SET(fiber->state, FIBER_STATE_INITIAL, FIBER_STATE_READY, FIBER_STATE_CANCELLED)); + + static pthread_once_t atfork_once = PTHREAD_ONCE_INIT; + r = pthread_once(&atfork_once, install_atfork); + if (r != 0) + return -r; + if (atfork_ret != 0) + return -atfork_ret; + + LOG_SET_PREFIX(fiber->name); + LOG_CONTEXT_PUSH_KEY_VALUE("FIBER=", fiber->name); + + log_debug("Scheduling fiber"); + + /* Save the previously-current fiber (if any) so we can restore it when this fiber yields or + * completes. This matters when fiber_run() is invoked from within another fiber (e.g. an + * sd-event dispatch that happens to be running inside a fiber context itself): the + * LOG_SET_PREFIX/LOG_CONTEXT_PUSH above attached to whichever fiber was current at that moment, + * and their scope-level cleanup must see the same fiber_get_current() when it runs to detach + * them from the correct list. */ + Fiber *prev = fiber_get_current(); + void *fake_stack_save = NULL; + fiber_enter(fiber, prev, &fake_stack_save); + + /* This is where we start executing the fiber. Once it yields, we continue here as if nothing + * happened. resume_context captures this point; the fiber siglongjmps back to it. */ + if (sigsetjmp(fiber->resume_context, 0) == 0) + siglongjmp_unchecked(fiber->context, 1); + + fiber_leave(fiber, prev, fake_stack_save); + + switch (fiber->state) { + + case FIBER_STATE_COMPLETED: + if (fiber->result < 0 && fiber->result != -ECANCELED) + log_debug_errno(fiber->result, "Fiber failed with error: %m"); + else + log_debug("Fiber finished executing"); + + fiber_resolve(f); + break; + + case FIBER_STATE_CANCELLED: + case FIBER_STATE_READY: + log_debug("Fiber yielded execution"); + + r = sd_event_source_set_enabled(fiber_current_event_source(fiber), SD_EVENT_ONESHOT); + if (r < 0) + return r; + break; + + case FIBER_STATE_SUSPENDED: + log_debug("Fiber suspended execution"); + /* Fiber is waiting for something - don't re-queue it */ + break; + + default: + assert_not_reached(); + } + + return 0; +} + +static int fiber_cancel(sd_future *f) { + Fiber *fiber = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + int r; + + assert(fiber != fiber_get_current()); + + if (IN_SET(fiber->state, FIBER_STATE_COMPLETED, FIBER_STATE_CANCELLED)) + return 0; + + if (fiber->state == FIBER_STATE_INITIAL) { + /* The fiber's stack was allocated but never entered, so there are no scope-level cleanups + * waiting to run. Skip the dispatch round-trip that would just have fiber_entry_point() + * fall straight through with -ECANCELED, and settle the future right here — mirroring the + * FIBER_STATE_COMPLETED branch of fiber_run(). */ + fiber->result = -ECANCELED; + fiber->state = FIBER_STATE_COMPLETED; + fiber_resolve(f); + return 1; + } + + /* Once we cancel a fiber, we want to immediately resume it with -ECANCELED. */ + r = sd_event_source_set_enabled(fiber_current_event_source(fiber), SD_EVENT_ONESHOT); + if (r < 0) + return r; + + fiber->state = FIBER_STATE_CANCELLED; + + return 1; +} + +static int fiber_on_defer(sd_event_source *s, void *userdata) { + sd_future *f = ASSERT_PTR(userdata); + return fiber_run(f); +} + +static int fiber_on_exit(sd_event_source *s, void *userdata) { + sd_future *f = ASSERT_PTR(userdata); + Fiber *fiber = ASSERT_PTR(sd_future_get_private(f)); + int r; + + /* The fiber may already have completed via the regular defer path before sd_event_exit() + * fires the exit source; in that case there's nothing left to drive and we'd otherwise + * trip fiber_run()'s -ESTALE return, which sd_event would log spuriously and disable the + * source for. */ + if (fiber->state == FIBER_STATE_COMPLETED) + return 0; + + /* If fiber_cancel() returned 1 the fiber was just marked cancelled and its deferred/exit event + * source was re-armed; we let the event loop dispatch that source on the next iteration so it goes + * through the normal fiber_on_defer/fiber_on_exit path rather than running it recursively here. */ + r = fiber_cancel(f); + if (r != 0) + return r; + + return fiber_run(f); +} + +static void* fiber_alloc(void) { + return new0(Fiber, 1); +} + +static void fiber_free(sd_future *f) { + Fiber *fiber = ASSERT_PTR(sd_future_get_private(f)); + + /* To make sure all memory is deallocated, the fiber has to have completed by the time we free it to + * make sure its stack has finished unwinding (which will invoke the registered cleanup functions). + * As this function may get called when not running on a fiber ourselves, we can't guarantee here + * that we can run the fiber to completion ourselves, so we insist that this happens before we get + * here. To ensure fibers are cleaned up before exiting the event loop, exit handlers are added for + * fibers created outside of existing fibers. For fibers created within running fibers, unwinding the + * outer fiber should take care of cleaning up any created child fibers (for example using + * sd_future_cancel_wait_unref()). + * + * FIBER_STATE_INITIAL is also accepted: the stack was allocated but never entered, so there are no + * registered cleanups to run. This covers the partial-construction failure path in sd_fiber_new() + * as well as fibers that are unrefed before the event loop ever dispatches them. */ + assert(IN_SET(fiber->state, FIBER_STATE_INITIAL, FIBER_STATE_COMPLETED)); + + if (fiber->destroy) + fiber->destroy(fiber->userdata); + +#if HAVE_VALGRIND_VALGRIND_H + if (fiber->stack.iov_base) + VALGRIND_STACK_DEREGISTER(fiber->stack_id); +#endif + + if (fiber->stack.iov_base) + (void) munmap(fiber->stack.iov_base, fiber->stack.iov_len); + + sd_event_source_disable_unref(fiber->defer_event_source); + sd_event_source_disable_unref(fiber->exit_event_source); + sd_event_unref(fiber->event); + + free(fiber->name); + free(fiber); +} + +sd_future* sd_fiber_get_current(void) { + Fiber *f = fiber_get_current(); + if (!f) + return NULL; + + return sd_event_source_get_userdata(fiber_current_event_source(f)); +} + +int sd_fiber_is_running(void) { + return !!fiber_get_current(); +} + +sd_event* sd_fiber_get_event(void) { + Fiber *f = fiber_get_current(); + assert_return(f, NULL); + return f->event; +} + +int sd_fiber_get_priority(int64_t *ret) { + Fiber *f = fiber_get_current(); + + assert_return(ret, -EINVAL); + assert_return(f, -ESRCH); + + *ret = f->priority; + return 0; +} + +static int fiber_swap(FiberState state) { + Fiber *f = ASSERT_PTR(fiber_get_current()); + + f->state = state; + + void *fake_stack_save = NULL; + + if (sigsetjmp(f->context, 0) == 0) { + start_switch_stack(&fake_stack_save, &f->resume_stack); + siglongjmp_unchecked(f->resume_context, 1); + } + + finish_switch_stack(fake_stack_save); + + /* When we get here, we've been resumed. */ + + if (f->state == FIBER_STATE_CANCELLED) + return -ECANCELED; + + /* sd_fiber_resume() stashes the resumer's value (an async wakeup error from a deadline + * timer, an io_uring CQE result, etc.) into f->result for us to surface here. Consume it + * unconditionally so it doesn't pollute subsequent suspends or the fiber's eventual return + * value — both negative errors and positive payloads (byte counts, accepted fds, revents + * masks) are valid resume values. */ + return TAKE_GENERIC(f->result, int, 0); +} + +int sd_fiber_yield(void) { + assert_return(fiber_get_current(), -ESRCH); + return fiber_swap(FIBER_STATE_READY); +} + +int sd_fiber_suspend(void) { + assert_return(fiber_get_current(), -ESRCH); + return fiber_swap(FIBER_STATE_SUSPENDED); +} + +static int fiber_set_priority(sd_future *f, int64_t priority) { + Fiber *fiber = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + int r = 0; + + if (fiber->defer_event_source) + RET_GATHER(r, sd_event_source_set_priority(fiber->defer_event_source, priority)); + + if (fiber->exit_event_source) + RET_GATHER(r, sd_event_source_set_priority(fiber->exit_event_source, priority)); + + if (r >= 0) + fiber->priority = priority; + + return r; +} + +static const sd_future_ops fiber_future_ops; + +int sd_fiber_resume(sd_future *f, int result) { + assert_return(f, -EINVAL); + assert_return(sd_future_get_ops(f) == &fiber_future_ops, -EINVAL); + + Fiber *fiber = ASSERT_PTR(sd_future_get_private(f)); + + if (fiber->state != FIBER_STATE_SUSPENDED) + return 0; + + /* Stash the result so fiber_swap() returns it from sd_fiber_suspend(). */ + fiber->result = result; + fiber->state = FIBER_STATE_READY; + return sd_event_source_set_enabled(fiber_current_event_source(fiber), SD_EVENT_ONESHOT); +} + +/* The fiber_future ops pass the Fiber pointer through as the future's private state. The fiber resolves + * its own future once it finishes running, so fiber_cancel() intentionally does not resolve. */ +static const sd_future_ops fiber_future_ops = { + .size = sizeof(sd_future_ops), + .alloc = fiber_alloc, + .free = fiber_free, + .cancel = fiber_cancel, + .set_priority = fiber_set_priority, +}; + +int sd_fiber_new(sd_event *e, const char *name, sd_fiber_func_t func, void *userdata, sd_fiber_destroy_t destroy, sd_future **ret) { + int r; + + assert_return(e, -EINVAL); + assert_return(name, -EINVAL); + assert_return(func, -EINVAL); + + if (IN_SET(sd_event_get_state(e), SD_EVENT_EXITING, SD_EVENT_FINISHED)) + return -ECANCELED; + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_future_new(&fiber_future_ops, &f); + if (r < 0) + return r; + + Fiber *fiber = ASSERT_PTR(sd_future_get_private(f)); + + struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY }; + if (getrlimit(RLIMIT_STACK, &rl) < 0) + log_debug_errno(errno, "Reading RLIMIT_STACK failed, ignoring: %m"); + if (rl.rlim_cur == RLIM_INFINITY) + rl.rlim_cur = 8U * U64_MB; /* Same as the default thread stack size */ + + /* Reserve room for the guard page so the usable region stays above PTHREAD_STACK_MIN, which + * is what libc/pthread routines (e.g. TLS setup on musl) assume. */ + size_t stack_len = ROUND_UP(rl.rlim_cur, page_size()); + if (stack_len < (size_t) PTHREAD_STACK_MIN + page_size()) + stack_len = ROUND_UP((size_t) PTHREAD_STACK_MIN + page_size(), page_size()); + + *fiber = (Fiber) { + .stack.iov_len = stack_len, + .state = FIBER_STATE_INITIAL, + .name = strdup(name), + .func = func, + .userdata = userdata, + .event = sd_event_ref(e), + }; + if (!fiber->name) + return -ENOMEM; + + r = fiber_allocate_stack(fiber->stack.iov_len, &fiber->stack.iov_base); + if (r < 0) + return r; + +#if HAVE_VALGRIND_VALGRIND_H + /* Register the usable stack range (above the guard page) before fiber_bootstrap() so the + * trampoline's first sigsetjmp doesn't trip Valgrind's stack-tracking heuristics. */ + struct iovec usable = fiber_stack_usable(&fiber->stack); + fiber->stack_id = VALGRIND_STACK_REGISTER( + usable.iov_base, + (uint8_t*) usable.iov_base + usable.iov_len); +#endif + + r = fiber_init(fiber); + if (r < 0) + return r; + + /* Execution of the fiber is driven by two event sources, one deferred, one exit. The exit event + * source kicks in when sd_event_exit() is called, as from that point onwards only exit event + * sources will be dispatched. */ + + r = sd_event_add_defer(e, &fiber->defer_event_source, fiber_on_defer, f); + if (r < 0) + return r; + + r = sd_event_source_set_description(fiber->defer_event_source, fiber->name); + if (r < 0) + return r; + + r = sd_event_add_exit(e, &fiber->exit_event_source, fiber_on_exit, f); + if (r < 0) + return r; + + r = sd_event_source_set_description(fiber->exit_event_source, fiber->name); + if (r < 0) + return r; + + /* If we're on a fiber, we'll rely on the parent fiber to cancel this fiber if the event loop is + * exiting. Otherwise, we'll trigger cancellation of this fiber via the exit event source. Why cancel + * via the exit event source? We can only run the fiber while the event loop is active, so we need to + * make sure all fibers finish running before the event loop is finished, which an exit event source + * allows us to do. */ + r = sd_event_source_set_enabled(fiber->exit_event_source, sd_fiber_is_running() ? SD_EVENT_OFF : SD_EVENT_ONESHOT); + if (r < 0) + return r; + + /* Stays in FIBER_STATE_INITIAL until the event loop first dispatches it via fiber_run(). */ + + if (ret) + *ret = TAKE_PTR(f); + else { + /* Fire-and-forget: the fiber is guaranteed to resolve (via completion, cancellation, or + * the event loop exit handler), so making the future floating cleans it up. */ + r = sd_fiber_set_floating(f, true); + if (r < 0) + return r; + } + + /* We only take ownership of the given userdata pointer on success so assign the destroy callback + * at the very end so we don't clean up the userdata pointer on failure. */ + fiber->destroy = destroy; + + return 0; +} + +int sd_fiber_set_floating(sd_future *f, int b) { + assert_return(f, -EINVAL); + assert_return(sd_future_get_ops(f) == &fiber_future_ops, -EINVAL); + + Fiber *fiber = ASSERT_PTR(sd_future_get_private(f)); + + if (!!fiber->floating == !!b) + return 0; + + /* The floating self-ref keeps the future alive until the fiber resolves; fiber_run() drops it + * in the COMPLETED branch. Only valid for fiber futures because fibers uniquely guarantee + * resolution (via completion, cancellation, or the event loop exit handler). */ + if (b) + fiber->floating = sd_future_ref(f); + else + fiber->floating = sd_future_unref(fiber->floating); + + return 0; +} + +int sd_fiber_get_floating(sd_future *f) { + assert_return(f, -EINVAL); + assert_return(sd_future_get_ops(f) == &fiber_future_ops, -EINVAL); + + Fiber *fiber = ASSERT_PTR(sd_future_get_private(f)); + return !!fiber->floating; +} + +int sd_fiber_sleep(uint64_t usec) { + Fiber *f = fiber_get_current(); + int r; + + if (!f) + return usleep_safe(usec); + + if (usec == 0) + return sd_fiber_yield(); + + /* Match usleep_safe(USEC_INFINITY): suspend indefinitely. Passing USEC_INFINITY to + * sd_event_add_time_relative() would overflow into -EOVERFLOW. */ + if (usec == USEC_INFINITY) + return sd_fiber_suspend(); + + assert(f->event); + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *timer = NULL; + r = future_new_time_relative( + f->event, + CLOCK_MONOTONIC, + usec, + /* accuracy= */ 1, + /* result= */ 0, + &timer); + if (r < 0) + return r; + + return sd_fiber_suspend(); +} + +int sd_fiber_await(sd_future *target) { + sd_future *f = sd_fiber_get_current(); + int r; + + assert_return(f, -ESRCH); + assert_return(target, -EINVAL); + assert_return(target != f, -EDEADLK); + + Fiber *fiber = ASSERT_PTR(sd_future_get_private(f)); + + if (sd_future_state(target) == SD_FUTURE_RESOLVED) + return sd_future_result(target); + + /* Note that we do allow waiting for other fibers when the event loop is exiting, since waiting for + * other fibers does not require adding new event sources to the event loop. */ + if (sd_event_get_state(fiber->event) == SD_EVENT_FINISHED) + return -ECANCELED; + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *wait = NULL; + r = sd_future_new_wait(target, &wait); + if (r < 0) + return r; + + return sd_fiber_suspend(); +} + +sd_future* sd_fiber_timeout(uint64_t timeout) { + Fiber *fiber = fiber_get_current(); + int r; + + assert_return(fiber, NULL); + + if (timeout == USEC_INFINITY) + return NULL; + + sd_future *timer; + r = future_new_time_relative( + fiber->event, + CLOCK_MONOTONIC, + timeout, + /* accuracy= */ 1, + /* result= */ -ETIME, + &timer); + if (r < 0) + return NULL; /* On allocation failure no timer is armed and the scope becomes a no-op. + * Errors here are rare; if the caller cares they can compare to NULL. */ + + return timer; +} diff --git a/src/libsystemd/sd-future/sd-future.c b/src/libsystemd/sd-future/sd-future.c new file mode 100644 index 0000000000000..ba3a1c52d2fa6 --- /dev/null +++ b/src/libsystemd/sd-future/sd-future.c @@ -0,0 +1,263 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-future.h" + +#include "alloc-util.h" +#include "errno-util.h" +#include "log.h" +#include "macro.h" +#include "set.h" + +struct sd_future { + unsigned n_ref; + + int state; + int result; + + Set *waiters; + + sd_future_func_t callback; + void *userdata; + + const sd_future_ops *ops; + + /* Opaque per-future state owned by the future implementation (the code that called + * sd_future_new()). The ops vtable above receives this pointer in its callbacks, and + * external code can fetch it via sd_future_get_private(). */ + void *private; +}; + +static int fiber_resume_trampoline(sd_future *f) { + /* The future's result is what the fiber should resume with. Impls choose the value at + * resolution time — e.g. a deadline timer resolves with -ETIME, a wait future resolves + * with the target's result, a normal IO/sleep future resolves with 0 on success. */ + return sd_fiber_resume(sd_future_get_userdata(f), sd_future_result(f)); +} + +int sd_future_resolve(sd_future *f, int result) { + int r = 0; + + assert_return(f, -EINVAL); + + if (f->state != SD_FUTURE_PENDING) + return 0; + + /* Hold a self-ref across callback/waiter dispatch: callbacks (e.g. bus_fiber_resolved() + * dropping the tracking-set's ref) may legitimately release what would otherwise be the + * last reference, and we still access f->waiters below. The cleanup unrefs at scope exit, + * which is when freeing is safe again. */ + _unused_ _cleanup_(sd_future_unrefp) sd_future *self = sd_future_ref(f); + + f->state = SD_FUTURE_RESOLVED; + f->result = result; + + if (f->callback) + RET_GATHER(r, f->callback(f)); + + /* We'd like the set to not be modified while iterating over it, hence take ownership over it in + * a local variable. Otherwise code invoked via sd_future_resolve() could try to modify the set while + * we're iterating over it (for example wait_future_free()). */ + Set *waiters = TAKE_PTR(f->waiters); + sd_future *w; + SET_FOREACH(w, waiters) + RET_GATHER(r, sd_future_resolve(w, result)); + + set_free(waiters); + + return r; +} + +static sd_future* sd_future_free(sd_future *f) { + if (!f) + return NULL; + + if (f->state == SD_FUTURE_PENDING) + sd_future_resolve(f, -ECANCELED); + + set_free(f->waiters); + + if (f->ops->free) + f->ops->free(f); + + return mfree(f); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_future, sd_future, sd_future_free); +DEFINE_POINTER_ARRAY_CLEAR_FUNC(sd_future*, sd_future_unref); +DEFINE_POINTER_ARRAY_FREE_FUNC(sd_future*, sd_future_unref); + +sd_future* sd_future_cancel_wait_unref(sd_future *f) { + int r; + + if (!f) + return NULL; + + /* We have to be able to suspend until the fiber we're waiting for finishes, and that's only + * possible if we're running on a fiber ourselves. */ + if (!sd_fiber_is_running()) + return sd_future_unref(f); + + r = sd_future_cancel(f); + if (r < 0) + log_debug_errno(r, "Failed to cancel future, ignoring: %m"); + + if (f->state == SD_FUTURE_PENDING) { + /* Fast path: when f's resolve callback already targets the current fiber (the default for + * futures created on this fiber), we can suspend directly and let the existing trampoline + * wake us up — no need to allocate a wait future just to learn about the resolution. + * Otherwise fall back to sd_fiber_await() which sets up an explicit waiter. */ + if (f->callback == fiber_resume_trampoline && f->userdata == sd_fiber_get_current()) + r = sd_fiber_suspend(); + else + r = sd_fiber_await(f); + if (r < 0 && r != -ECANCELED) + log_debug_errno(r, "Failed to wait for future to finish, ignoring: %m"); + } + + return sd_future_unref(f); +} + +DEFINE_POINTER_ARRAY_CLEAR_FUNC(sd_future*, sd_future_cancel_wait_unref); +DEFINE_POINTER_ARRAY_FREE_FUNC(sd_future*, sd_future_cancel_wait_unref); + +int sd_future_new(const sd_future_ops *ops, sd_future **ret) { + assert_return(ops, -EINVAL); + assert_return(ops->size >= endoffsetof_field(sd_future_ops, set_priority), -EINVAL); + assert_return(ops->alloc, -EINVAL); + assert_return(ops->free, -EINVAL); + assert_return(ret, -EINVAL); + + sd_future *f = new(sd_future, 1); + if (!f) + return -ENOMEM; + + *f = (sd_future) { + .n_ref = 1, + .state = SD_FUTURE_PENDING, + .ops = ops, + }; + + f->private = ops->alloc(); + if (!f->private) { + free(f); + return -ENOMEM; + } + + /* If we're being created on a fiber, default the callback to resuming that fiber on resolve — + * this is almost always what you want, and it saves the usual set_callback boilerplate before + * sd_fiber_suspend(). Callers that want different behavior can override with + * sd_future_set_callback(). */ + sd_future *fiber = sd_fiber_get_current(); + if (fiber) + (void) sd_future_set_callback(f, fiber_resume_trampoline, fiber); + + *ret = f; + return 0; +} + +int sd_future_state(sd_future *f) { + assert_return(f, -EINVAL); + return f->state; +} + +int sd_future_result(sd_future *f) { + assert_return(f, -EINVAL); + assert_return(f->state == SD_FUTURE_RESOLVED, -EBUSY); + return f->result; +} + +void* sd_future_get_userdata(sd_future *f) { + assert_return(f, NULL); + return f->userdata; +} + +void* sd_future_get_private(sd_future *f) { + assert_return(f, NULL); + return f->private; +} + +const sd_future_ops* sd_future_get_ops(sd_future *f) { + assert_return(f, NULL); + return f->ops; +} + +int sd_future_set_callback(sd_future *f, sd_future_func_t callback, void *userdata) { + assert_return(f, -EINVAL); + + f->callback = callback; + f->userdata = userdata; + return 0; +} + +int sd_future_set_priority(sd_future *f, int64_t priority) { + assert_return(f, -EINVAL); + assert_return(f->state == SD_FUTURE_PENDING, -ESTALE); + assert_return(f->ops->set_priority, -EOPNOTSUPP); + + return f->ops->set_priority(f, priority); +} + +int sd_future_cancel(sd_future *f) { + assert_return(f, -EINVAL); + assert_return(f->ops->cancel, -EOPNOTSUPP); + + if (f->state == SD_FUTURE_RESOLVED) + return 0; + + return f->ops->cancel(f); +} + +typedef struct WaitFuture { + sd_future *target; +} WaitFuture; + +static void* wait_future_alloc(void) { + return new0(WaitFuture, 1); +} + +static void wait_future_free(sd_future *f) { + WaitFuture *wf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + + set_remove(wf->target->waiters, f); + sd_future_unref(wf->target); + free(wf); +} + +static int wait_future_cancel(sd_future *f) { + WaitFuture *wf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + + set_remove(wf->target->waiters, f); + return sd_future_resolve(f, -ECANCELED); +} + +static const sd_future_ops wait_future_ops = { + .size = sizeof(sd_future_ops), + .alloc = wait_future_alloc, + .free = wait_future_free, + .cancel = wait_future_cancel, +}; + +int sd_future_new_wait(sd_future *target, sd_future **ret) { + int r; + + assert_return(target, -EINVAL); + assert_return(ret, -EINVAL); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_future_new(&wait_future_ops, &f); + if (r < 0) + return r; + + WaitFuture *wf = sd_future_get_private(f); + wf->target = sd_future_ref(target); + + if (target->state == SD_FUTURE_RESOLVED) + r = sd_future_resolve(f, target->result); + else + r = set_ensure_put(&target->waiters, &trivial_hash_ops, f); + if (r < 0) + return r; + + *ret = TAKE_PTR(f); + return 0; +} diff --git a/src/libsystemd/sd-future/test-fiber-io.c b/src/libsystemd/sd-future/test-fiber-io.c new file mode 100644 index 0000000000000..aef38950b6332 --- /dev/null +++ b/src/libsystemd/sd-future/test-fiber-io.c @@ -0,0 +1,1388 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include + +#include "sd-event.h" +#include "sd-future.h" + +#include "fd-util.h" +#include "tests.h" +#include "time-util.h" + +/* Test: Basic pipe I/O with sd-event */ + +typedef struct PipeIOContext { + int *pipefd; + int order; +} PipeIOContext; + +static int pipe_read_fiber(void *userdata) { + PipeIOContext *ctx = ASSERT_PTR(userdata); + char buf[64]; + ssize_t n; + + n = sd_fiber_read(ctx->pipefd[0], buf, sizeof(buf)); + if (n < 0) + return (int) n; + + /* Verify we read "hello" */ + if (n != 5 || memcmp(buf, "hello", 5) != 0) + return -EIO; + + return (int) n; +} + +TEST(fiber_io_basic) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + PipeIOContext ctx = { .pipefd = pipefd }; + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "pipe-read", pipe_read_fiber, &ctx, NULL, &f)); + + /* Write data to the pipe */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "hello", 5), 5); + + /* Run the scheduler - should process the I/O */ + ASSERT_OK(sd_event_loop(e)); + + /* Verify fiber read the data */ + ASSERT_OK_EQ(sd_future_result(f), 5); +} + +static int pipe_read_order_fiber(void *userdata) { + PipeIOContext *ctx = ASSERT_PTR(userdata); + char buf[64]; + ssize_t n; + + /* Record that the read fiber started before attempting the blocking read */ + ASSERT_EQ(ctx->order, 0); + ctx->order = 1; + + n = sd_fiber_read(ctx->pipefd[0], buf, sizeof(buf)); + if (n < 0) + return (int) n; + + /* After resuming, verify the write fiber ran while we were suspended */ + ASSERT_EQ(ctx->order, 2); + + /* Verify we read "hello" */ + if (n != 5 || memcmp(buf, "hello", 5) != 0) + return -EIO; + + return (int) n; +} + +static int pipe_write_order_fiber(void *userdata) { + PipeIOContext *ctx = ASSERT_PTR(userdata); + + /* Verify the read fiber already ran and suspended before we started */ + ASSERT_EQ(ctx->order, 1); + ctx->order = 2; + + return sd_fiber_write(ctx->pipefd[1], "hello", STRLEN("hello")); +} + +TEST(fiber_io_read_write) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + PipeIOContext ctx = { .pipefd = pipefd }; + + /* Higher priority for the read fiber, which will run first and then suspend because no data is + * available. The write fiber will run second, write data to the pipe, causing the read fiber to get + * resumed. */ + _cleanup_(sd_future_unrefp) sd_future *fr = NULL, *fw = NULL; + ASSERT_OK(sd_fiber_new(e, "pipe-read", pipe_read_order_fiber, &ctx, NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 0)); + ASSERT_OK(sd_fiber_new(e, "pipe-write", pipe_write_order_fiber, &ctx, NULL, &fw)); + ASSERT_OK(sd_future_set_priority(fw, 1)); + + /* Run the scheduler - should process the I/O */ + ASSERT_OK(sd_event_loop(e)); + + /* Verify both fibers completed and the full read->suspend->write->resume sequence occurred */ + ASSERT_OK_EQ(sd_future_result(fr), 5); + ASSERT_OK_EQ(sd_future_result(fw), 5); +} + +/* Test: Multiple concurrent reads */ +static int concurrent_read_fiber(void *userdata) { + int *args = userdata; + int fd = args[0]; + int expected = args[1]; + char buf[64]; + ssize_t n; + + n = sd_fiber_read(fd, buf, sizeof buf); + if (n < 0) + return (int) n; + + if (n != 1 || buf[0] != (char) expected) + return -EIO; + + return 0; +} + +TEST(fiber_io_concurrent) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[3] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + + /* Create 3 pipes and 3 fibers */ + int pipes[3][2]; + int args[3][2]; + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + ASSERT_OK_ERRNO(pipe2(pipes[i], O_CLOEXEC | O_NONBLOCK)); + args[i][0] = pipes[i][0]; + args[i][1] = 'A' + i; + ASSERT_OK(sd_fiber_new(e, "concurrent-read", concurrent_read_fiber, args[i], NULL, &fibers[i])); + } + + /* Write data in reverse order */ + ASSERT_EQ(write(pipes[2][1], "C", 1), 1); + ASSERT_EQ(write(pipes[1][1], "B", 1), 1); + ASSERT_EQ(write(pipes[0][1], "A", 1), 1); + + /* Run until all complete */ + ASSERT_OK(sd_event_loop(e)); + + /* All should complete successfully */ + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + ASSERT_OK(sd_future_result(fibers[i])); + safe_close_pair(pipes[i]); + } +} + +/* Test: Cancel fiber during I/O */ +static int blocking_read_fiber(void *userdata) { + int fd = PTR_TO_INT(userdata); + char buf[64]; + ssize_t n; + + n = sd_fiber_read(fd, buf, sizeof(buf)); + return (int) n; +} + +TEST(fiber_io_cancel) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "blocking-read", blocking_read_fiber, INT_TO_PTR(pipefd[0]), NULL, &f)); + + /* Run once - fiber will suspend on read */ + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + + /* Fiber should be suspended now - add explicit check via state tracking */ + + /* Cancel the fiber */ + ASSERT_OK(sd_future_cancel(f)); + + /* Run to completion */ + ASSERT_OK(sd_event_loop(e)); + + /* Should be cancelled */ + ASSERT_ERROR(sd_future_result(f), ECANCELED); +} + +TEST(fiber_io_fallback) { + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); /* Note: blocking pipe */ + + char buf[STRLEN("fallback")] = {}; + ASSERT_OK_EQ(sd_fiber_write(pipefd[1], "fallback", sizeof(buf)), (ssize_t) sizeof(buf)); + ASSERT_OK_EQ(sd_fiber_read(pipefd[0], buf, sizeof(buf)), (ssize_t) sizeof(buf)); +} + +static int pipe_readv_order_fiber(void *userdata) { + PipeIOContext *ctx = ASSERT_PTR(userdata); + char buf1[5], buf2[5]; + struct iovec iov[] = { + { .iov_base = buf1, .iov_len = sizeof(buf1) }, + { .iov_base = buf2, .iov_len = sizeof(buf2) }, + }; + ssize_t n; + + /* Record that the read fiber started before attempting the blocking read */ + ASSERT_EQ(ctx->order, 0); + ctx->order = 1; + + /* This will initially block since no data is available */ + n = sd_fiber_readv(ctx->pipefd[0], iov, ELEMENTSOF(iov)); + if (n < 0) + return (int) n; + + /* After resuming, verify the write fiber ran while we were suspended */ + ASSERT_EQ(ctx->order, 2); + + if (n != 10 || memcmp(buf1, "fiber", 5) != 0 || memcmp(buf2, "readv", 5) != 0) + return -EIO; + + return (int) n; +} + +static int pipe_writev_order_fiber(void *userdata) { + PipeIOContext *ctx = ASSERT_PTR(userdata); + const char *part1 = "fiber"; + const char *part2 = "readv"; + struct iovec iov[] = { + { .iov_base = (void*) part1, .iov_len = 5 }, + { .iov_base = (void*) part2, .iov_len = 5 }, + }; + + /* Verify the read fiber already ran and suspended before we started */ + ASSERT_EQ(ctx->order, 1); + ctx->order = 2; + + return sd_fiber_writev(ctx->pipefd[1], iov, ELEMENTSOF(iov)); +} + +TEST(fiber_io_readv_writev) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + PipeIOContext ctx = { .pipefd = pipefd }; + + /* Higher priority for the read fiber, which will run first and then suspend because no data is + * available. The write fiber will run second, write data to the pipe, causing the read fiber to get + * resumed. */ + _cleanup_(sd_future_unrefp) sd_future *fr = NULL, *fw = NULL; + ASSERT_OK(sd_fiber_new(e, "pipe-readv", pipe_readv_order_fiber, &ctx, NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 0)); + ASSERT_OK(sd_fiber_new(e, "pipe-writev", pipe_writev_order_fiber, &ctx, NULL, &fw)); + ASSERT_OK(sd_future_set_priority(fw, 1)); + + /* Run the scheduler - should process the I/O */ + ASSERT_OK(sd_event_loop(e)); + + /* Verify both fibers completed and the full read->suspend->write->resume sequence occurred */ + ASSERT_OK_EQ(sd_future_result(fr), 10); + ASSERT_OK_EQ(sd_future_result(fw), 10); +} + +static int concurrent_readv_fiber(void *userdata) { + int *args = userdata; + int fd = args[0]; + int expected1 = args[1]; + int expected2 = args[2]; + char buf1[1], buf2[1]; + struct iovec iov[] = { + { .iov_base = buf1, .iov_len = sizeof(buf1) }, + { .iov_base = buf2, .iov_len = sizeof(buf2) }, + }; + ssize_t n; + + n = sd_fiber_readv(fd, iov, ELEMENTSOF(iov)); + if (n < 0) + return (int) n; + + if (n != 2 || buf1[0] != (char) expected1 || buf2[0] != (char) expected2) + return -EIO; + + return 0; +} + +TEST(fiber_io_readv_concurrent) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[3] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + + /* Create 3 pipes and 3 fibers */ + int pipes[3][2]; + int args[3][3]; + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + ASSERT_OK_ERRNO(pipe2(pipes[i], O_CLOEXEC | O_NONBLOCK)); + args[i][0] = pipes[i][0]; + args[i][1] = 'A' + i; + args[i][2] = 'a' + i; + ASSERT_OK(sd_fiber_new(e, "concurrent-readv", concurrent_readv_fiber, args[i], NULL, &fibers[i])); + } + + /* Write data in reverse order */ + ASSERT_EQ(write(pipes[2][1], "Cc", 2), 2); + ASSERT_EQ(write(pipes[1][1], "Bb", 2), 2); + ASSERT_EQ(write(pipes[0][1], "Aa", 2), 2); + + /* Run until all complete */ + ASSERT_OK(sd_event_loop(e)); + + /* All should complete successfully */ + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + ASSERT_OK(sd_future_result(fibers[i])); + safe_close_pair(pipes[i]); + } +} + +typedef struct SocketIOContext { + int *sockfd; + int order; +} SocketIOContext; + +static int socket_send_order_fiber(void *userdata) { + SocketIOContext *ctx = ASSERT_PTR(userdata); + + /* Verify the recv fiber already ran and suspended before we started */ + ASSERT_EQ(ctx->order, 1); + ctx->order = 2; + + return sd_fiber_send(ctx->sockfd[0], "socket", STRLEN("socket"), 0); +} + +static int socket_recv_order_fiber(void *userdata) { + SocketIOContext *ctx = ASSERT_PTR(userdata); + char buf[64]; + ssize_t n; + + /* Record that the recv fiber started before attempting the blocking recv */ + ASSERT_EQ(ctx->order, 0); + ctx->order = 1; + + n = sd_fiber_recv(ctx->sockfd[1], buf, sizeof(buf), 0); + if (n < 0) + return (int) n; + + /* After resuming, verify the send fiber ran while we were suspended */ + ASSERT_EQ(ctx->order, 2); + + /* Verify we received "socket" */ + if (n != 6 || memcmp(buf, "socket", 6) != 0) + return -EIO; + + return (int) n; +} + +TEST(fiber_io_recv_send) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int sockfd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, sockfd)); + + SocketIOContext ctx = { .sockfd = sockfd }; + + /* Higher priority for the recv fiber, which will run first and suspend */ + _cleanup_(sd_future_unrefp) sd_future *fs = NULL, *fr = NULL; + ASSERT_OK(sd_fiber_new(e, "socket-recv", socket_recv_order_fiber, &ctx, NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 0)); + ASSERT_OK(sd_fiber_new(e, "socket-send", socket_send_order_fiber, &ctx, NULL, &fs)); + ASSERT_OK(sd_future_set_priority(fs, 1)); + + ASSERT_OK(sd_event_loop(e)); + + /* Verify both fibers completed and the full recv->suspend->send->resume sequence occurred */ + ASSERT_OK_EQ(sd_future_result(fr), 6); + ASSERT_OK_EQ(sd_future_result(fs), 6); +} + +static int socket_recv_peek_fiber(void *userdata) { + int sockfd = PTR_TO_INT(userdata); + char buf1[64], buf2[64]; + ssize_t n1, n2; + + /* First peek at the data */ + n1 = sd_fiber_recv(sockfd, buf1, sizeof(buf1), MSG_PEEK); + if (n1 < 0) + return (int) n1; + + /* Then actually read it */ + n2 = sd_fiber_recv(sockfd, buf2, sizeof(buf2), 0); + if (n2 < 0) + return (int) n2; + + /* Both should have read the same data */ + if (n1 != n2 || memcmp(buf1, buf2, n1) != 0) + return -EIO; + + if (n1 != 4 || memcmp(buf1, "peek", 4) != 0) + return -EIO; + + return 0; +} + +TEST(fiber_io_recv_peek) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int sockfd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, sockfd)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "socket-recv-peek", socket_recv_peek_fiber, INT_TO_PTR(sockfd[1]), NULL, &f)); + + /* Write data to the socket */ + ASSERT_OK_EQ_ERRNO(write(sockfd[0], "peek", 4), 4); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +static int socket_connect_fiber(void *userdata) { + struct sockaddr_un *addr = userdata; + _cleanup_close_ int sockfd = -EBADF; + + sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (sockfd < 0) + return -errno; + + return sd_fiber_connect(sockfd, (struct sockaddr*) addr, sizeof(*addr)); +} + +TEST(fiber_io_connect) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create listening socket with abstract namespace */ + _cleanup_close_ int listen_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + ASSERT_OK(listen_fd); + + /* Use abstract socket (starts with null byte) */ + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + }; + addr.sun_path[0] = '\0'; + snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, "test-fiber-connect-%d", getpid()); + + ASSERT_OK(bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr))); + ASSERT_OK(listen(listen_fd, 1)); + + /* Create fiber to connect */ + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "socket-connect", socket_connect_fiber, &addr, NULL, &f)); + + /* Run the event loop - connection should complete */ + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +static int socket_sendmsg_fiber(void *userdata) { + int sockfd = PTR_TO_INT(userdata); + struct iovec iov = { + .iov_base = (void*) "message", + .iov_len = STRLEN("message"), + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + return sd_fiber_sendmsg(sockfd, &msg, 0); +} + +static int socket_recvmsg_fiber(void *userdata) { + int sockfd = PTR_TO_INT(userdata); + char buf[64]; + struct iovec iov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + }; + ssize_t n; + + n = sd_fiber_recvmsg(sockfd, &msg, 0); + if (n < 0) + return (int) n; + + if (n != 7 || memcmp(buf, "message", 7) != 0) + return -EIO; + + return (int) n; +} + +TEST(fiber_io_recvmsg_sendmsg) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int sockfd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, sockfd)); + + _cleanup_(sd_future_unrefp) sd_future *fs = NULL, *fr = NULL; + ASSERT_OK(sd_fiber_new(e, "socket-recvmsg", socket_recvmsg_fiber, INT_TO_PTR(sockfd[1]), NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 1)); + ASSERT_OK(sd_fiber_new(e, "socket-sendmsg", socket_sendmsg_fiber, INT_TO_PTR(sockfd[0]), NULL, &fs)); + ASSERT_OK(sd_future_set_priority(fs, 0)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK_EQ(sd_future_result(fr), 7); + ASSERT_OK_EQ(sd_future_result(fs), 7); +} + +static int socket_sendto_fiber(void *userdata) { + int sockfd = PTR_TO_INT(userdata); + + /* For socketpair dgram sockets, we can use NULL address since they're connected */ + return sd_fiber_sendto(sockfd, "datagram", STRLEN("datagram"), 0, NULL, 0); +} + +static int socket_recvfrom_fiber(void *userdata) { + int sockfd = PTR_TO_INT(userdata); + char buf[64]; + struct sockaddr_un addr; + socklen_t addr_len = sizeof(addr); + ssize_t n; + + n = sd_fiber_recvfrom(sockfd, buf, sizeof(buf), 0, + (struct sockaddr*) &addr, &addr_len); + if (n < 0) + return (int) n; + + if (n != 8 || memcmp(buf, "datagram", 8) != 0) + return -EIO; + + return (int) n; +} + +TEST(fiber_io_recvfrom_sendto) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int sockfd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, sockfd)); + + _cleanup_(sd_future_unrefp) sd_future *fs = NULL, *fr = NULL; + ASSERT_OK(sd_fiber_new(e, "socket-recvfrom", socket_recvfrom_fiber, INT_TO_PTR(sockfd[1]), NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 1)); + ASSERT_OK(sd_fiber_new(e, "socket-sendto", socket_sendto_fiber, INT_TO_PTR(sockfd[0]), NULL, &fs)); + ASSERT_OK(sd_future_set_priority(fs, 0)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK_EQ(sd_future_result(fr), 8); + ASSERT_OK_EQ(sd_future_result(fs), 8); +} + +static int socket_sendmsg_fd_fiber(void *userdata) { + int *args = userdata; + int sockfd = args[0]; + int fd_to_send = args[1]; + struct iovec iov = { + .iov_base = (void*) "X", + .iov_len = 1, + }; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &fd_to_send, sizeof(int)); + + return sd_fiber_sendmsg(sockfd, &msg, 0); +} + +static int socket_recvmsg_fd_fiber(void *userdata) { + int sockfd = PTR_TO_INT(userdata); + char buf[1]; + struct iovec iov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + int received_fd; + ssize_t n; + + n = sd_fiber_recvmsg(sockfd, &msg, 0); + if (n < 0) + return (int) n; + + if (n != 1 || buf[0] != 'X') + return -EIO; + + /* Extract the file descriptor */ + cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg || cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) + return -EIO; + + memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int)); + + /* Verify we can use the fd */ + if (fcntl(received_fd, F_GETFD) < 0) + return -errno; + + close(received_fd); + return 0; +} + +TEST(fiber_io_sendmsg_recvmsg_fd) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int sockfd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, sockfd)); + + /* Create a test file descriptor to send */ + _cleanup_close_ int test_fd = open("/dev/null", O_RDONLY | O_CLOEXEC); + ASSERT_OK_ERRNO(test_fd); + + _cleanup_(sd_future_unrefp) sd_future *fs = NULL, *fr = NULL; + int args[2] = { sockfd[0], test_fd }; + ASSERT_OK(sd_fiber_new(e, "socket-recvmsg-fd", socket_recvmsg_fd_fiber, INT_TO_PTR(sockfd[1]), NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 1)); + ASSERT_OK(sd_fiber_new(e, "socket-sendmsg-fd", socket_sendmsg_fd_fiber, args, NULL, &fs)); + ASSERT_OK(sd_future_set_priority(fs, 0)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(fr)); + ASSERT_OK_EQ(sd_future_result(fs), 1); +} + +TEST(fiber_io_socket_fallback) { + _cleanup_close_pair_ int sockfd[2] = EBADF_PAIR; + char buf[STRLEN("fallback")] = {}; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sockfd)); + + /* Test send/recv without fiber context */ + ASSERT_OK_EQ(sd_fiber_send(sockfd[0], "fallback", sizeof(buf), 0), (ssize_t) sizeof(buf)); + ASSERT_OK_EQ(sd_fiber_recv(sockfd[1], buf, sizeof(buf), 0), (ssize_t) sizeof(buf)); + + /* Test sendto/recvfrom without fiber context */ + ASSERT_OK_EQ(sd_fiber_sendto(sockfd[0], "fallback", sizeof(buf), 0, NULL, 0), (ssize_t) sizeof(buf)); + ASSERT_OK_EQ(sd_fiber_recvfrom(sockfd[1], buf, sizeof(buf), 0, NULL, NULL), (ssize_t) sizeof(buf)); +} + +static int blocking_recv_fiber(void *userdata) { + int sockfd = PTR_TO_INT(userdata); + char buf[64]; + + return sd_fiber_recv(sockfd, buf, sizeof(buf), 0); +} + +TEST(fiber_io_socket_cancel) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int sockfd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, sockfd)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "blocking-recv", blocking_recv_fiber, INT_TO_PTR(sockfd[0]), NULL, &f)); + + /* Run once - fiber will suspend on recv */ + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + + /* Cancel the fiber */ + ASSERT_OK(sd_future_cancel(f)); + + /* Run to completion */ + ASSERT_OK(sd_event_loop(e)); + + /* Should be cancelled */ + ASSERT_ERROR(sd_future_result(f), ECANCELED); +} + +/* Test: Basic accept operation */ +static int accept_fiber(void *userdata) { + int listen_fd = PTR_TO_INT(userdata); + struct sockaddr_un addr; + socklen_t addr_len = sizeof(addr); + int client_fd; + + client_fd = sd_fiber_accept(listen_fd, (struct sockaddr*) &addr, &addr_len, SOCK_CLOEXEC); + if (client_fd < 0) + return client_fd; + + close(client_fd); + return 0; +} + +TEST(fiber_io_accept_basic) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create listening socket with abstract namespace */ + _cleanup_close_ int listen_fd = -EBADF; + ASSERT_OK_ERRNO(listen_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + }; + addr.sun_path[0] = '\0'; + snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, "test-fiber-accept-%d", getpid()); + + ASSERT_OK_ERRNO(bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr))); + ASSERT_OK_ERRNO(listen(listen_fd, 1)); + + /* Create fiber to accept connection */ + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "accept", accept_fiber, INT_TO_PTR(listen_fd), NULL, &f)); + + /* Connect from outside fiber context */ + _cleanup_close_ int connect_fd = -EBADF; + ASSERT_OK(connect_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + ASSERT_OK(connect(connect_fd, (struct sockaddr*) &addr, sizeof(addr))); + + /* Run the event loop - accept should complete */ + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: Multiple sequential accepts */ +static int accept_multiple_fiber(void *userdata) { + int listen_fd = PTR_TO_INT(userdata); + struct sockaddr_un addr; + socklen_t addr_len; + int count = 0; + + for (int i = 0; i < 3; i++) { + _cleanup_close_ int client_fd = -EBADF; + + addr_len = sizeof(addr); + client_fd = sd_fiber_accept(listen_fd, (struct sockaddr*) &addr, &addr_len, SOCK_CLOEXEC); + if (client_fd < 0) + return client_fd; + + count++; + } + + return count; +} + +TEST(fiber_io_accept_multiple) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create listening socket */ + _cleanup_close_ int listen_fd = -EBADF; + ASSERT_OK(listen_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + }; + addr.sun_path[0] = '\0'; + snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, "test-fiber-accept-multi-%d", getpid()); + + ASSERT_OK(bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr))); + ASSERT_OK(listen(listen_fd, 5)); + + /* Create fiber to accept multiple connections */ + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "accept-multiple", accept_multiple_fiber, INT_TO_PTR(listen_fd), NULL, &f)); + + /* Connect multiple times */ + int connect_fds[3] = { -EBADF, -EBADF, -EBADF }; + for (size_t i = 0; i < 3; i++) { + connect_fds[i] = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + ASSERT_OK(connect_fds[i]); + ASSERT_OK(connect(connect_fds[i], (struct sockaddr*) &addr, sizeof(addr))); + } + + /* Run the event loop */ + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK_EQ(sd_future_result(f), 3); + + /* Clean up connection fds */ + for (size_t i = 0; i < 3; i++) + safe_close(connect_fds[i]); +} + +/* Test: Accept and exchange data */ +static int accept_and_read_fiber(void *userdata) { + int listen_fd = PTR_TO_INT(userdata); + _cleanup_close_ int client_fd = -EBADF; + char buf[64]; + ssize_t n; + + client_fd = sd_fiber_accept(listen_fd, NULL, NULL, SOCK_CLOEXEC); + if (client_fd < 0) + return client_fd; + + n = sd_fiber_read(client_fd, buf, sizeof(buf)); + if (n < 0) + return (int) n; + + if (n != 5 || memcmp(buf, "hello", 5) != 0) + return -EIO; + + return 0; +} + +TEST(fiber_io_accept_and_read) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create listening socket */ + _cleanup_close_ int listen_fd = -EBADF; + ASSERT_OK(listen_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + }; + addr.sun_path[0] = '\0'; + snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, "test-fiber-accept-read-%d", getpid()); + + ASSERT_OK(bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr))); + ASSERT_OK(listen(listen_fd, 1)); + + /* Create fiber to accept and read */ + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "accept-and-read", accept_and_read_fiber, INT_TO_PTR(listen_fd), NULL, &f)); + + /* Connect and send data */ + _cleanup_close_ int connect_fd = -EBADF; + ASSERT_OK(connect_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)); + ASSERT_OK(connect(connect_fd, (struct sockaddr*) &addr, sizeof(addr))); + ASSERT_OK_EQ_ERRNO(write(connect_fd, "hello", 5), 5); + + /* Run the event loop */ + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: poll with single fd ready immediately */ +static int poll_immediate_fiber(void *userdata) { + int *pipefd = userdata; + struct pollfd fds[] = { + { .fd = pipefd[0], .events = POLLIN }, + }; + int r; + + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), NULL, NULL); + if (r < 0) + return r; + + /* Should have one fd ready */ + if (r != 1) + return -EIO; + + if (!(fds[0].revents & POLLIN)) + return -EIO; + + return 0; +} + +TEST(fiber_poll_immediate) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + /* Write data before creating fiber */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "X", 1), 1); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-immediate", poll_immediate_fiber, pipefd, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: poll with fd that becomes ready after suspension */ +static int poll_fiber(void *userdata) { + int *pipefd = userdata; + struct pollfd fds[] = { + { .fd = pipefd[0], .events = POLLIN }, + }; + int r; + + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), NULL, NULL); + if (r < 0) + return r; + + if (r != 1 || !(fds[0].revents & POLLIN)) + return -EIO; + + /* Read the data */ + char buf[1]; + if (read(pipefd[0], buf, 1) != 1 || buf[0] != 'Y') + return -EIO; + + return 0; +} + +TEST(fiber_poll) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-suspend", poll_fiber, pipefd, NULL, &f)); + + /* Run once - fiber will suspend on poll */ + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + + /* Write data to wake it up */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "Y", 1), 1); + + /* Complete execution */ + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: poll with multiple fds */ +static int poll_multiple_fiber(void *userdata) { + int (*pipes)[2] = userdata; + struct pollfd fds[] = { + { .fd = pipes[0][0], .events = POLLIN }, + { .fd = pipes[1][0], .events = POLLIN }, + { .fd = pipes[2][0], .events = POLLIN }, + }; + int r; + + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), NULL, NULL); + if (r < 0) + return r; + + /* Should have all three ready */ + if (r != 3) + return -EIO; + + for (size_t i = 0; i < 3; i++) { + if (!(fds[i].revents & POLLIN)) + return -EIO; + + char buf[1]; + if (read(fds[i].fd, buf, 1) != 1 || buf[0] != (char) ('A' + i)) + return -EIO; + } + + return 0; +} + +TEST(fiber_poll_multiple) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create three pipes */ + int pipes[3][2]; + for (size_t i = 0; i < 3; i++) + ASSERT_OK_ERRNO(pipe2(pipes[i], O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-multiple", poll_multiple_fiber, pipes, NULL, &f)); + + /* Run once - fiber will suspend waiting for data */ + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + + /* Write to all three pipes in different order */ + ASSERT_OK_EQ_ERRNO(write(pipes[2][1], "C", 1), 1); + ASSERT_OK_EQ_ERRNO(write(pipes[0][1], "A", 1), 1); + ASSERT_OK_EQ_ERRNO(write(pipes[1][1], "B", 1), 1); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); + + for (size_t i = 0; i < 3; i++) + safe_close_pair(pipes[i]); +} + +/* Test: poll with POLLOUT (write readiness) */ +static int poll_pollout_fiber(void *userdata) { + int *pipefd = userdata; + struct pollfd fds[] = { + { .fd = pipefd[1], .events = POLLOUT }, + }; + int r; + + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), NULL, NULL); + if (r < 0) + return r; + + if (r != 1 || !(fds[0].revents & POLLOUT)) + return -EIO; + + /* Pipe should be writable */ + if (write(pipefd[1], "Z", 1) != 1) + return -errno; + + return 0; +} + +TEST(fiber_poll_pollout) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-pollout", poll_pollout_fiber, pipefd, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); + + /* Verify data was written */ + char buf[1]; + ASSERT_OK_EQ_ERRNO(read(pipefd[0], buf, 1), 1); + ASSERT_EQ(buf[0], 'Z'); +} + +/* Test: poll with timeout that expires */ +static int poll_timeout_fiber(void *userdata) { + int *pipefd = userdata; + struct pollfd fds[] = { + { .fd = pipefd[0], .events = POLLIN }, + }; + int r; + + /* Poll with 100ms timeout - no data will arrive */ + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), &(struct timespec) { .tv_nsec = 100 * NSEC_PER_MSEC }, NULL); + if (r < 0) + return r; + + /* Should timeout with no fds ready */ + if (r != 0) + return -EIO; + + return 0; +} + +TEST(fiber_poll_timeout) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-timeout", poll_timeout_fiber, pipefd, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: poll with zero timeout (should not block) */ +static int poll_zero_timeout_fiber(void *userdata) { + int *pipefd = userdata; + struct pollfd fds[] = { + { .fd = pipefd[0], .events = POLLIN }, + }; + int r; + + /* Poll with zero timeout - should return immediately */ + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), &(struct timespec) {}, NULL); + if (r < 0) + return r; + + /* No data available, so should return 0 */ + if (r != 0) + return -EIO; + + /* Now write data */ + if (write(pipefd[1], "Q", 1) != 1) + return -errno; + + /* Poll again with zero timeout - should see data */ + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), NULL, NULL); + if (r < 0) + return r; + + if (r != 1 || !(fds[0].revents & POLLIN)) + return -EIO; + + return 0; +} + +TEST(fiber_poll_zero_timeout) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-zero-timeout", poll_zero_timeout_fiber, pipefd, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: poll with zero fds and zero timeout (should return immediately) */ +static int poll_zero_fds_fiber(void *userdata) { + return sd_fiber_ppoll(NULL, 0, &(struct timespec) {}, NULL); +} + +TEST(fiber_poll_zero_fds) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-zero-fds", poll_zero_fds_fiber, NULL, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK_EQ(sd_future_result(f), 0); +} + +/* Test: poll with zero fds and no timeout has no possible wakeup, must reject with -EINVAL */ +static int poll_zero_fds_no_timeout_fiber(void *userdata) { + return sd_fiber_ppoll(NULL, 0, NULL, NULL); +} + +TEST(fiber_poll_zero_fds_no_timeout) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-zero-fds-no-timeout", poll_zero_fds_no_timeout_fiber, NULL, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_ERROR(sd_future_result(f), EINVAL); +} + +/* Test: poll with negative fd (should be ignored) */ +static int poll_negative_fd_fiber(void *userdata) { + int *pipefd = userdata; + struct pollfd fds[] = { + { .fd = -1, .events = POLLIN }, + { .fd = pipefd[0], .events = POLLIN }, + }; + int r; + + r = sd_fiber_ppoll(fds, ELEMENTSOF(fds), NULL, NULL); + if (r < 0) + return r; + + /* Only the second fd should be ready */ + if (r != 1 || !(fds[1].revents & POLLIN)) + return -EIO; + + /* First fd should have no events */ + if (fds[0].revents != 0) + return -EIO; + + return 0; +} + +TEST(fiber_poll_negative_fd) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + /* Write data before creating fiber */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "N", 1), 1); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "poll-negative-fd", poll_negative_fd_fiber, pipefd, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: Multiple fibers waiting on the same fd */ +typedef struct SharedFdArgs { + int pipefd; + int *counter; +} SharedFdArgs; + +static int shared_fd_read_fiber(void *userdata) { + SharedFdArgs *args = ASSERT_PTR(userdata); + char buf[1]; + ssize_t n; + + n = sd_fiber_read(args->pipefd, buf, sizeof(buf)); + if (n < 0) + return (int) n; + + if (n != 1) + return -EIO; + + /* Increment counter to track successful reads */ + (*args->counter)++; + + return 0; +} + +TEST(fiber_io_same_fd_multiple_fibers) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + /* Create 3 fibers all waiting on the same pipe read end */ + sd_future *fibers[3] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + SharedFdArgs args[3]; + int counter = 0; + + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + args[i].pipefd = pipefd[0]; + args[i].counter = &counter; + ASSERT_OK(sd_fiber_new(e, "shared-fd-read", shared_fd_read_fiber, &args[i], NULL, &fibers[i])); + } + + /* All fibers should suspend waiting for data */ + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + + /* Write 3 bytes - each byte will wake one fiber */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "ABC", 3), 3); + + /* Run until all fibers complete */ + ASSERT_OK(sd_event_loop(e)); + + /* All should complete successfully and each should have read one byte */ + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_OK(sd_future_result(fibers[i])); + + ASSERT_EQ(counter, 3); +} + +static int blocking_fd_preserve_fiber(void *userdata) { + int *pipefd = ASSERT_PTR(userdata); + char buf[8] = {}; + ssize_t n; + + /* The pipe has data pre-filled, so this should succeed immediately on the fast path. + * This exercises the fd blocking state restore: fiber_io_operation() temporarily sets the fd + * to nonblocking, and must restore it to blocking on the success path. */ + n = sd_fiber_read(pipefd[0], buf, sizeof(buf)); + if (n < 0) + return (int) n; + + if ((size_t) n != sizeof(buf) || memcmp(buf, "blocking", sizeof(buf)) != 0) + return -EIO; + + return 0; +} + +TEST(fiber_io_blocking_fd_preserved) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create a blocking pipe (no O_NONBLOCK) */ + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); + + /* Pre-fill the pipe so the read will succeed immediately */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "blocking", 8), 8); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "blocking-fd-preserve", blocking_fd_preserve_fiber, pipefd, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); + + /* Verify the read end is still in blocking mode after the fiber completed */ + ASSERT_OK_ZERO(fd_nonblock(pipefd[0], false)); +} + +static int socket_connect_blocking_fiber(void *userdata) { + struct sockaddr_un *addr = userdata; + _cleanup_close_ int sockfd = -EBADF; + + /* Use a blocking socket (no SOCK_NONBLOCK). sd_fiber_connect() should temporarily set it + * to nonblocking, handle the EINPROGRESS path with getsockopt(SO_ERROR), and restore + * the blocking state. */ + sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (sockfd < 0) + return -errno; + + int r = sd_fiber_connect(sockfd, (struct sockaddr*) addr, sizeof(*addr)); + if (r < 0) + return r; + + /* Verify the socket is back in blocking mode */ + r = fd_nonblock(sockfd, false); + if (r < 0) + return r; + if (r > 0) + return -EBUSY; /* fd was nonblocking, but should have been restored to blocking */ + + return 0; +} + +TEST(fiber_io_connect_blocking) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create listening socket */ + _cleanup_close_ int listen_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + ASSERT_OK(listen_fd); + + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + }; + addr.sun_path[0] = '\0'; + snprintf(addr.sun_path + 1, sizeof(addr.sun_path) - 1, "test-fiber-connect-blocking-%d", getpid()); + + ASSERT_OK(bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr))); + ASSERT_OK(listen(listen_fd, 1)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "connect-blocking", socket_connect_blocking_fiber, &addr, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd/sd-future/test-fiber-ops.c b/src/libsystemd/sd-future/test-fiber-ops.c new file mode 100644 index 0000000000000..35a99527100bb --- /dev/null +++ b/src/libsystemd/sd-future/test-fiber-ops.c @@ -0,0 +1,572 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "sd-event.h" +#include "sd-future.h" + +#include "alloc-util.h" +#include "cleanup-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "pidref.h" +#include "process-util.h" +#include "tests.h" +#include "time-util.h" + +/* Test: wait_for_terminate basic functionality */ +static int wait_simple_fiber(void *userdata) { + _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; + siginfo_t si; + int r; + + /* Fork a child that exits immediately */ + r = pidref_safe_fork("(test-child)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &pidref); + if (r < 0) + return r; + + if (r == 0) + _exit(42); + + /* Parent - wait for child */ + r = pidref_wait_for_terminate(&pidref, &si); + if (r < 0) + return r; + + pidref_done(&pidref); + + /* Verify child exited with status 42 */ + if (si.si_code != CLD_EXITED || si.si_status != 42) + return -EIO; + + return 0; +} + +TEST(wait_for_terminate_fiber_basic) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "wait-simple", wait_simple_fiber, NULL, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +/* Test: wait_for_terminate with multiple children */ +static int wait_multiple_fiber(void *userdata) { + PidRef pidrefs[3] = { PIDREF_NULL, PIDREF_NULL, PIDREF_NULL }; + siginfo_t si; + int r; + + /* Fork three children with different exit codes */ + for (size_t i = 0; i < 3; i++) { + r = pidref_safe_fork("(test-child)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &pidrefs[i]); + if (r < 0) + goto cleanup; + + if (r == 0) + /* Child process */ + _exit(10 + i); + } + + /* Wait for all three in order */ + for (size_t i = 0; i < 3; i++) { + r = pidref_wait_for_terminate(&pidrefs[i], &si); + if (r < 0) + goto cleanup; + + pidref_done(&pidrefs[i]); + + if (si.si_code != CLD_EXITED || si.si_status != (int) (10 + i)) { + r = -EIO; + goto cleanup; + } + } + + return 0; + +cleanup: + for (size_t i = 0; i < 3; i++) + pidref_done(&pidrefs[i]); + + return r; +} + +TEST(wait_for_terminate_fiber_multiple) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "wait-multiple", wait_multiple_fiber, NULL, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); +} + +static int concurrent_wait_fiber(void *userdata) { + _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; + siginfo_t si; + int r; + + r = pidref_safe_fork("(test-child)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &pidref); + if (r < 0) + return r; + + if (r == 0) + /* Child exits with specified status */ + _exit(PTR_TO_INT(userdata)); + + r = pidref_wait_for_terminate(&pidref, &si); + if (r < 0) + return r; + + pidref_done(&pidref); + + if (si.si_code != CLD_EXITED || si.si_status != PTR_TO_INT(userdata)) + return -EIO; + + return 0; +} + +TEST(wait_for_terminate_fiber_concurrent) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[3] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + + /* Create 3 fibers, each waiting for a different child */ + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_OK(sd_fiber_new(e, "concurrent-wait", concurrent_wait_fiber, INT_TO_PTR(20 + i), /* destroy= */ NULL, &fibers[i])); + + ASSERT_OK(sd_event_loop(e)); + + /* All fibers should complete successfully */ + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_OK(sd_future_result(fibers[i])); +} + +typedef struct LoopIOContext { + int *pipefd; + const char *data; + size_t len; + int order; +} LoopIOContext; + +static int loop_read_suspend_fiber(void *userdata) { + LoopIOContext *ctx = ASSERT_PTR(userdata); + char buf[64]; + + ASSERT_EQ(ctx->order, 0); + ctx->order = 1; + + ssize_t n = loop_read(ctx->pipefd[0], buf, sizeof(buf), /* do_poll= */ true); + + /* While we were suspended, the writer fiber should have run. */ + ASSERT_EQ(ctx->order, 2); + + if (n < 0) + return (int) n; + if ((size_t) n != ctx->len || memcmp(buf, ctx->data, ctx->len) != 0) + return -EIO; + + return (int) n; +} + +static int loop_write_suspend_fiber(void *userdata) { + LoopIOContext *ctx = ASSERT_PTR(userdata); + + ASSERT_EQ(ctx->order, 1); + ctx->order = 2; + + int r = loop_write(ctx->pipefd[1], ctx->data, ctx->len); + if (r < 0) + return r; + + /* Close the write end so the reader sees EOF after reading the data. */ + ctx->pipefd[1] = safe_close(ctx->pipefd[1]); + return 0; +} + +/* Test: two fibers cooperatively pass a small payload through a blocking pipe using the suspending + * loop helpers. Exercises the non-blocking flip, event-loop yielding, and the blocking-mode restore. */ +TEST(loop_read_write_suspend) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); + + static const char payload[] = "loop-suspend"; + LoopIOContext ctx = { + .pipefd = pipefd, + .data = payload, + .len = sizeof(payload) - 1, + }; + + _cleanup_(sd_future_unrefp) sd_future *fr = NULL, *fw = NULL; + ASSERT_OK(sd_fiber_new(e, "loop-read", loop_read_suspend_fiber, &ctx, /* destroy= */ NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 0)); + ASSERT_OK(sd_fiber_new(e, "loop-write", loop_write_suspend_fiber, &ctx, /* destroy= */ NULL, &fw)); + ASSERT_OK(sd_future_set_priority(fw, 1)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK_EQ(sd_future_result(fr), (int) ctx.len); + ASSERT_OK_ZERO(sd_future_result(fw)); + + /* The read fd started out blocking and loop_read() must have restored it before returning. */ + ASSERT_OK_ZERO(fcntl(pipefd[0], F_GETFL) & O_NONBLOCK); +} + +static int loop_read_exact_short_fiber(void *userdata) { + int fd = PTR_TO_INT(userdata); + char buf[16]; + + /* Requesting more bytes than the peer writes should return -EIO once EOF is hit. */ + return loop_read_exact(fd, buf, sizeof(buf), /* do_poll= */ true); +} + +/* Test: loop_read_exact() returns -EIO when the peer closes early. */ +TEST(loop_read_exact_short) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "loop-read-exact", loop_read_exact_short_fiber, + INT_TO_PTR(pipefd[0]), /* destroy= */ NULL, &f)); + + /* Write a few bytes and close the write end — less than the fiber asked for. */ + ASSERT_OK_EQ_ERRNO(write(pipefd[1], "abc", 3), (ssize_t) 3); + pipefd[1] = safe_close(pipefd[1]); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_ERROR(sd_future_result(f), EIO); +} + +typedef struct LoopWriteTimeoutContext { + int fd; + int result; +} LoopWriteTimeoutContext; + +static int loop_write_timeout_fiber(void *userdata) { + LoopWriteTimeoutContext *ctx = ASSERT_PTR(userdata); + + /* Try to write much more than the pipe buffer can hold with a short timeout. The write will + * succeed partially and then hit -ETIME after exhausting the timeout while blocked. */ + static const char big_buf[128 * 1024] = { 0 }; + ctx->result = loop_write_full(ctx->fd, big_buf, sizeof(big_buf), 100 * USEC_PER_MSEC); + return 0; +} + +/* Test: loop_write_full() returns -ETIME when the peer never drains. */ +TEST(loop_write_full_timeout) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); + + /* Shrink the pipe buffer to its minimum (one page) so the 128K write below is guaranteed to block + * regardless of the architecture's page size. The default pipe buffer is 16 pages, which on + * 64K-page architectures (e.g. ppc64le) is 1 MiB — enough to absorb the entire write without ever + * blocking, defeating the purpose of the timeout. */ + ASSERT_OK_ERRNO(fcntl(pipefd[1], F_SETPIPE_SZ, 1)); + + LoopWriteTimeoutContext ctx = { .fd = pipefd[1], .result = 0 }; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "loop-write-timeout", loop_write_timeout_fiber, &ctx, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK_ZERO(sd_future_result(f)); + ASSERT_ERROR(ctx.result, ETIME); +} + +typedef struct PpollDispatchContext { + int *pipefd; + int order; +} PpollDispatchContext; + +static int ppoll_dispatch_read_fiber(void *userdata) { + PpollDispatchContext *ctx = ASSERT_PTR(userdata); + struct pollfd pfd = { + .fd = ctx->pipefd[0], + .events = POLLIN, + }; + + ASSERT_EQ(ctx->order, 0); + ctx->order = 1; + + /* Direct ppoll_usec() call from a fiber must dispatch through sd_fiber_poll(), suspending the + * fiber instead of blocking the entire thread. If dispatch fails, the writer fiber never gets a + * chance to run and the test deadlocks. */ + int r = ppoll_usec(&pfd, 1, USEC_INFINITY); + if (r < 0) + return r; + + ASSERT_EQ(ctx->order, 2); + + if (r != 1 || !FLAGS_SET(pfd.revents, POLLIN)) + return -EIO; + + return 0; +} + +static int ppoll_dispatch_write_fiber(void *userdata) { + PpollDispatchContext *ctx = ASSERT_PTR(userdata); + + ASSERT_EQ(ctx->order, 1); + ctx->order = 2; + + if (write(ctx->pipefd[1], "x", 1) < 0) + return -errno; + + return 0; +} + +/* Test: ppoll_usec() called from a fiber dispatches through the FiberOps hook to sd_fiber_poll(), + * yielding to the event loop instead of blocking. */ +TEST(ppoll_usec_dispatch) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + PpollDispatchContext ctx = { .pipefd = pipefd }; + + _cleanup_(sd_future_unrefp) sd_future *fr = NULL, *fw = NULL; + ASSERT_OK(sd_fiber_new(e, "ppoll-read", ppoll_dispatch_read_fiber, &ctx, /* destroy= */ NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 0)); + ASSERT_OK(sd_fiber_new(e, "ppoll-write", ppoll_dispatch_write_fiber, &ctx, /* destroy= */ NULL, &fw)); + ASSERT_OK(sd_future_set_priority(fw, 1)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(fr)); + ASSERT_OK(sd_future_result(fw)); +} + +static int loop_write_zero_timeout_nonblock_fiber(void *userdata) { + int fd = PTR_TO_INT(userdata); + + /* Fill the pipe so the next write would block. The fd is non-blocking, so on a fiber + * loop_write_full(timeout=0) must take the non-fiber path and return -EAGAIN immediately + * rather than suspending. */ + static const char big_buf[128 * 1024] = { 0 }; + return loop_write_full(fd, big_buf, sizeof(big_buf), /* timeout= */ 0); +} + +/* Test: timeout == 0 on a non-blocking fd from a fiber preserves the "don't wait" semantic and + * returns -EAGAIN when the pipe buffer is full, instead of suspending the fiber. */ +TEST(loop_write_zero_timeout_nonblock) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + ASSERT_OK_ERRNO(fcntl(pipefd[1], F_SETPIPE_SZ, 1)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "loop-write-zt-nb", loop_write_zero_timeout_nonblock_fiber, + INT_TO_PTR(pipefd[1]), /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_ERROR(sd_future_result(f), EAGAIN); +} + +typedef struct LoopWriteZeroBlockingContext { + int *pipefd; + size_t total; + int order; +} LoopWriteZeroBlockingContext; + +static int loop_write_zero_blocking_writer_fiber(void *userdata) { + LoopWriteZeroBlockingContext *ctx = ASSERT_PTR(userdata); + + ASSERT_EQ(ctx->order, 0); + ctx->order = 1; + + /* timeout == 0 on a *blocking* fd from a fiber: the fast EAGAIN return isn't possible, so + * loop_write_full() takes the fiber path. The reader fiber drains the pipe, letting our + * write complete via fiber suspension/resume. */ + _cleanup_free_ char *big_buf = malloc0(ctx->total); + ASSERT_NOT_NULL(big_buf); + int r = loop_write_full(ctx->pipefd[1], big_buf, ctx->total, /* timeout= */ 0); + + ASSERT_EQ(ctx->order, 2); + return r; +} + +static int loop_write_zero_blocking_reader_fiber(void *userdata) { + LoopWriteZeroBlockingContext *ctx = ASSERT_PTR(userdata); + + ASSERT_EQ(ctx->order, 1); + ctx->order = 2; + + _cleanup_free_ char *buf = malloc(ctx->total); + ASSERT_NOT_NULL(buf); + ssize_t n = loop_read(ctx->pipefd[0], buf, ctx->total, /* do_poll= */ true); + return (int) n; +} + +/* Test: timeout == 0 on a blocking fd from a fiber takes the fiber path (suspends until the peer + * drains) instead of blocking the entire thread. */ +TEST(loop_write_zero_timeout_blocking) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); + ASSERT_OK_ERRNO(fcntl(pipefd[1], F_SETPIPE_SZ, 1)); + + /* F_SETPIPE_SZ rounds up to the kernel's pipe minimum (typically a page); query the actual + * size and write more than that, so the write must wait on the reader regardless of page size. */ + int pipe_sz = fcntl(pipefd[1], F_GETPIPE_SZ); + ASSERT_OK_ERRNO(pipe_sz); + + LoopWriteZeroBlockingContext ctx = { .pipefd = pipefd, .total = (size_t) pipe_sz * 2 }; + + _cleanup_(sd_future_unrefp) sd_future *fw = NULL, *fr = NULL; + ASSERT_OK(sd_fiber_new(e, "loop-write-zt-blk", loop_write_zero_blocking_writer_fiber, + &ctx, /* destroy= */ NULL, &fw)); + ASSERT_OK(sd_future_set_priority(fw, 0)); + ASSERT_OK(sd_fiber_new(e, "loop-read-zt-blk", loop_write_zero_blocking_reader_fiber, + &ctx, /* destroy= */ NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 1)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(fw)); + ASSERT_OK_EQ(sd_future_result(fr), (int) ctx.total); +} + +static int loop_read_no_poll_nonblock_fiber(void *userdata) { + int fd = PTR_TO_INT(userdata); + char buf[64]; + + /* Empty non-blocking pipe + do_poll=false: on a fiber loop_read() must take the non-fiber + * path and return -EAGAIN immediately rather than suspending. */ + return (int) loop_read(fd, buf, sizeof(buf), /* do_poll= */ false); +} + +/* Test: do_poll == false on a non-blocking fd from a fiber preserves the "don't wait" semantic + * and returns -EAGAIN when no data is available, instead of suspending the fiber. */ +TEST(loop_read_no_poll_nonblock) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "loop-read-np-nb", loop_read_no_poll_nonblock_fiber, + INT_TO_PTR(pipefd[0]), /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_ERROR(sd_future_result(f), EAGAIN); +} + +typedef struct LoopReadNoPollBlockingContext { + int *pipefd; + const char *data; + size_t len; + int order; +} LoopReadNoPollBlockingContext; + +static int loop_read_no_poll_blocking_reader_fiber(void *userdata) { + LoopReadNoPollBlockingContext *ctx = ASSERT_PTR(userdata); + char buf[64]; + + ASSERT_EQ(ctx->order, 0); + ctx->order = 1; + + /* do_poll == false on a *blocking* fd from a fiber: the fast EAGAIN return isn't possible, + * so loop_read() takes the fiber path and suspends until the writer fiber feeds data. */ + ssize_t n = loop_read(ctx->pipefd[0], buf, sizeof(buf), /* do_poll= */ false); + + ASSERT_EQ(ctx->order, 2); + + if (n < 0) + return (int) n; + if ((size_t) n != ctx->len || memcmp(buf, ctx->data, ctx->len) != 0) + return -EIO; + + return (int) n; +} + +static int loop_read_no_poll_blocking_writer_fiber(void *userdata) { + LoopReadNoPollBlockingContext *ctx = ASSERT_PTR(userdata); + + ASSERT_EQ(ctx->order, 1); + ctx->order = 2; + + int r = loop_write(ctx->pipefd[1], ctx->data, ctx->len); + if (r < 0) + return r; + + ctx->pipefd[1] = safe_close(ctx->pipefd[1]); + return 0; +} + +/* Test: do_poll == false on a blocking fd from a fiber takes the fiber path (suspends until the + * peer feeds data) instead of blocking the entire thread. */ +TEST(loop_read_no_poll_blocking) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); + + static const char payload[] = "no-poll"; + LoopReadNoPollBlockingContext ctx = { + .pipefd = pipefd, + .data = payload, + .len = sizeof(payload) - 1, + }; + + _cleanup_(sd_future_unrefp) sd_future *fr = NULL, *fw = NULL; + ASSERT_OK(sd_fiber_new(e, "loop-read-np-blk", loop_read_no_poll_blocking_reader_fiber, + &ctx, /* destroy= */ NULL, &fr)); + ASSERT_OK(sd_future_set_priority(fr, 0)); + ASSERT_OK(sd_fiber_new(e, "loop-write-np-blk", loop_read_no_poll_blocking_writer_fiber, + &ctx, /* destroy= */ NULL, &fw)); + ASSERT_OK(sd_future_set_priority(fw, 1)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK_EQ(sd_future_result(fr), (int) ctx.len); + ASSERT_OK_ZERO(sd_future_result(fw)); +} + +/* Test: loop_*() helpers transparently fall back to blocking I/O when called outside any + * fiber context. */ +TEST(loop_read_write_fallback) { + _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipefd, O_CLOEXEC)); + + ASSERT_OK(loop_write(pipefd[1], "fallback", STRLEN("fallback"))); + + char buf[16]; + ssize_t n = loop_read(pipefd[0], buf, STRLEN("fallback"), /* do_poll= */ true); + ASSERT_OK_EQ(n, (ssize_t) STRLEN("fallback")); + ASSERT_EQ(memcmp(buf, "fallback", STRLEN("fallback")), 0); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd/sd-future/test-fiber.c b/src/libsystemd/sd-future/test-fiber.c new file mode 100644 index 0000000000000..2760b919a0045 --- /dev/null +++ b/src/libsystemd/sd-future/test-fiber.c @@ -0,0 +1,1171 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#if HAVE_VALGRIND_VALGRIND_H +# include +#endif + +#include "sd-event.h" +#include "sd-future.h" + +#include "architecture.h" +#include "log-context.h" +#include "memory-util.h" +#include "pidref.h" +#include "process-util.h" +#include "tests.h" +#include "time-util.h" + +static int simple_fiber(void *userdata) { + int *value = ASSERT_PTR(userdata); + return *value; +} + +TEST(fiber_simple) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int value = 5; + ASSERT_OK(sd_fiber_new(e, "simple", simple_fiber, &value, NULL, &f)); + ASSERT_OK(sd_event_loop(e)); + ASSERT_EQ(sd_future_result(f), 5); +} + +/* Fiber that yields once */ +static int yielding_fiber(void *userdata) { + int *counter = userdata; + (*counter)++; + + sd_fiber_yield(); + + (*counter)++; + return 0; +} + +/* Test: Single fiber that yields */ +TEST(fiber_single_yield) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "yielding", yielding_fiber, &counter, NULL, &f)); + + /* First iteration: fiber runs until first yield */ + ASSERT_EQ(counter, 0); + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_EQ(counter, 1); + + /* Second iteration: fiber runs from yield to completion */ + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_EQ(counter, 2); + + /* No more fibers to run */ + ASSERT_OK_ZERO(sd_event_loop(e)); +} + +static int counting_fiber(void *userdata) { + int counter = 0; + + for (int i = 0; i < 5; i++) { + counter++; + sd_fiber_yield(); + } + + return counter; +} + +/* Test: Multiple fibers yielding cooperatively */ +TEST(fiber_multiple_yield) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[5] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + _cleanup_free_ char *name = NULL; + ASSERT_OK(asprintf(&name, "counting-%zu", i)); + ASSERT_OK(sd_fiber_new(e, name, counting_fiber, NULL, NULL, &fibers[i])); + } + + ASSERT_OK(sd_event_loop(e)); + + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_OK_EQ(sd_future_result(fibers[i]), 5); +} + +static int priority_fiber(void *userdata) { + int *counter = ASSERT_PTR(userdata); + + (*counter)++; + sd_fiber_yield(); + + return *counter; +} + +/* Test: Priority-based scheduling */ +TEST(fiber_priority_ascending) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[5] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + int counter = 0; + + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + _cleanup_free_ char *name = NULL; + ASSERT_OK(asprintf(&name, "priority-%zu", i)); + ASSERT_OK(sd_fiber_new(e, name, priority_fiber, &counter, NULL, &fibers[i])); + ASSERT_OK(sd_future_set_priority(fibers[i], i)); + } + + ASSERT_OK(sd_event_loop(e)); + + /* The fibers have ascending priorities, so we the first one to run to completion, + * followed by the second one, etc. */ + + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_EQ(sd_future_result(fibers[i]), (int) i + 1); +} + +TEST(fiber_priority_identical) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[5] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + int counter = 0; + + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) { + _cleanup_free_ char *name = NULL; + ASSERT_OK(asprintf(&name, "priority-%zu", i)); + ASSERT_OK(sd_fiber_new(e, name, priority_fiber, &counter, NULL, &fibers[i])); + } + + ASSERT_OK(sd_event_loop(e)); + + /* The fibers have the same priorities, so we expect all of them to run once first, and then they'll + * all run again another time, so they should all return the same value. */ + + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_EQ(sd_future_result(fibers[i]), (int) 5); +} + +static int error_fiber(void *userdata) { + return -ENOENT; +} + +TEST(fiber_error_return) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "error", error_fiber, NULL, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_EQ(sd_future_result(f), -ENOENT); +} + +static int cancel_fiber(void *userdata) { + return sd_fiber_yield(); +} + +TEST(fiber_cancel_basic) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int value = 42; + ASSERT_OK(sd_fiber_new(e, "cancel", cancel_fiber, &value, NULL, &f)); + + ASSERT_OK(sd_future_cancel(f)); + ASSERT_OK(sd_event_loop(e)); + ASSERT_ERROR(sd_future_result(f), ECANCELED); +} + +static int fiber_that_yields(void *userdata) { + int *yield_count = userdata; + int r; + + for (int i = 0; i < 5; i++) { + (*yield_count)++; + r = sd_fiber_yield(); + if (r < 0) + return r; /* Propagate cancellation error */ + } + + return 0; +} + +/* Test: fiber_yield() returns error when fiber is cancelled externally */ +TEST(fiber_cancel_propagation_via_yield) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int yield_count = 0; + ASSERT_OK(sd_fiber_new(e, "yielding", fiber_that_yields, &yield_count, NULL, &f)); + + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_EQ(yield_count, 1); + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_EQ(yield_count, 2); + + ASSERT_OK(sd_future_cancel(f)); + + ASSERT_OK(sd_event_loop(e)); + + /* sd_fiber should have been cancelled */ + ASSERT_ERROR(sd_future_result(f), ECANCELED); + ASSERT_EQ(yield_count, 2); +} + +/* Test: Cancel a fiber that has already completed */ +TEST(fiber_cancel_completed) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int value = 42; + ASSERT_OK(sd_fiber_new(e, "simple", simple_fiber, &value, NULL, &f)); + + /* Run the fiber to completion */ + ASSERT_OK(sd_event_loop(e)); + + /* Canceling a completed fiber should be a no-op */ + ASSERT_OK(sd_future_cancel(f)); + ASSERT_EQ(sd_future_result(f), 42); +} + +static int multiple_yield_fiber(void *userdata) { + int *counter = userdata; + int r; + + for (int i = 0; i < 3; i++) { + (*counter)++; + r = sd_fiber_yield(); + if (r < 0) + return r; + } + + return 0; +} + +/* Test: Cancel one fiber among multiple */ +TEST(fiber_cancel_one_of_many) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[3] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + int counters[3] = {0, 0, 0}; + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_OK(sd_fiber_new(e, "multiple-yield", multiple_yield_fiber, &counters[i], NULL, &fibers[i])); + + /* Run one iteration - all fibers yield after incrementing once */ + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_EQ(counters[0], 1); + ASSERT_EQ(counters[1], 1); + ASSERT_EQ(counters[2], 1); + + /* Cancel the second fiber */ + ASSERT_OK(sd_future_cancel(fibers[1])); + + /* Run to completion */ + ASSERT_OK(sd_event_loop(e)); + + /* First and third fibers should complete normally */ + ASSERT_EQ(counters[0], 3); + ASSERT_EQ(counters[2], 3); + ASSERT_EQ(sd_future_result(fibers[0]), 0); + ASSERT_EQ(sd_future_result(fibers[2]), 0); + + /* Second fiber should be canceled with counter at 1 */ + ASSERT_EQ(counters[1], 1); + ASSERT_EQ(sd_future_result(fibers[1]), -ECANCELED); +} + +/* Test: sd_fiber_await() - wait for a fiber to complete */ +static int slow_fiber(void *userdata) { + int *counter = userdata; + + for (int i = 0; i < 3; i++) { + (*counter)++; + sd_fiber_yield(); + } + + return 42; +} + +static int waiting_fiber(void *userdata) { + sd_future *target = userdata; + int r; + + r = sd_fiber_await(target); + if (r < 0) + return r; + + r = sd_future_result(target); + return r == 42 ? 0 : -EIO; +} + +TEST(fiber_wait_for_basic) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + /* Create target fiber with lower priority (runs second) */ + _cleanup_(sd_future_unrefp) sd_future *target = NULL, *waiter = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "slow", slow_fiber, &counter, NULL, &target)); + ASSERT_OK(sd_future_set_priority(target, 1)); + + /* Create waiter fiber with higher priority (runs first) */ + ASSERT_OK(sd_fiber_new(e, "waiting", waiting_fiber, target, NULL, &waiter)); + ASSERT_OK(sd_future_set_priority(waiter, 0)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(waiter)); + ASSERT_OK_EQ(sd_future_result(target), 42); + ASSERT_EQ(counter, 3); +} + +/* Test: wait for already completed fiber */ +static int wait_for_completed_fiber(void *userdata) { + sd_future *target = userdata; + int r; + + r = sd_fiber_await(target); + if (r < 0) + return r; + + return sd_future_result(target); +} + +TEST(fiber_wait_for_completed) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *target = NULL, *waiter = NULL; + int value = 100; + + /* Create target fiber with higher priority (runs first) */ + ASSERT_OK(sd_fiber_new(e, "simple", simple_fiber, &value, NULL, &target)); + ASSERT_OK(sd_future_set_priority(target, 0)); + /* Create waiter fiber with lower priority (runs second, after target completes) */ + ASSERT_OK(sd_fiber_new(e, "wait-for-completed", wait_for_completed_fiber, target, NULL, &waiter)); + ASSERT_OK(sd_future_set_priority(waiter, 1)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK_EQ(sd_future_result(waiter), 100); + ASSERT_OK_EQ(sd_future_result(target), 100); +} + +/* Test: awaiting an already-resolved future returns the future's result directly */ +static int await_resolved_fiber(void *userdata) { + sd_future *target = userdata; + + ASSERT_EQ((int) sd_future_state(target), (int) SD_FUTURE_RESOLVED); + ASSERT_OK_EQ(sd_fiber_await(target), 77); + return 0; +} + +TEST(fiber_await_resolved_returns_result) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *target = NULL, *waiter = NULL; + int value = 77; + + /* Higher-priority target runs to completion before the waiter starts. */ + ASSERT_OK(sd_fiber_new(e, "target", simple_fiber, &value, NULL, &target)); + ASSERT_OK(sd_future_set_priority(target, 0)); + ASSERT_OK(sd_fiber_new(e, "await-resolved", await_resolved_fiber, target, NULL, &waiter)); + ASSERT_OK(sd_future_set_priority(waiter, 1)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(waiter)); + ASSERT_OK_EQ(sd_future_result(target), 77); +} + +/* Test: wait for cancelled fiber */ +static int wait_for_cancelled_fiber(void *userdata) { + sd_future *target = userdata; + int r; + + r = sd_fiber_await(target); + if (r < 0) + return r; + + return sd_future_result(target); +} + +TEST(fiber_wait_for_cancelled) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *target = NULL, *waiter = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "yielding", fiber_that_yields, &counter, NULL, &target)); + ASSERT_OK(sd_fiber_new(e, "wait-for-cancelled", wait_for_cancelled_fiber, target, NULL, &waiter)); + + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + ASSERT_OK_POSITIVE(sd_event_run(e, 0)); + + ASSERT_OK(sd_future_cancel(target)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_ERROR(sd_future_result(waiter), ECANCELED); + ASSERT_ERROR(sd_future_result(target), ECANCELED); +} + +/* Test: multiple fibers waiting for the same target */ +static int multi_waiter_fiber(void *userdata) { + sd_future *target = userdata; + int r; + + r = sd_fiber_await(target); + if (r < 0) + return r; + + return sd_future_result(target); +} + +TEST(fiber_wait_for_multiple_waiters) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *target = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "slow", slow_fiber, &counter, NULL, &target)); + + sd_future *waiters[3] = {}; + CLEANUP_ELEMENTS(waiters, sd_future_unref_array_clear); + for (size_t i = 0; i < ELEMENTSOF(waiters); i++) + ASSERT_OK(sd_fiber_new(e, "multi-waiter", multi_waiter_fiber, target, NULL, &waiters[i])); + + ASSERT_OK(sd_event_loop(e)); + + for (size_t i = 0; i < ELEMENTSOF(waiters); i++) + ASSERT_OK_EQ(sd_future_result(waiters[i]), 42); + + ASSERT_OK_EQ(sd_future_result(target), 42); + ASSERT_EQ(counter, 3); +} + +/* Test: chain of waiting fibers */ +static int chain_waiter_fiber(void *userdata) { + sd_future *target = userdata; + int r; + + r = sd_fiber_await(target); + if (r < 0) + return r; + + r = sd_future_result(target); + return r + 1; +} + +TEST(fiber_wait_for_chain) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *fibers[5] = {}; + CLEANUP_ELEMENTS(fibers, sd_future_unref_array_clear); + int value = 10; + + ASSERT_OK(sd_fiber_new(e, "simple", simple_fiber, &value, NULL, &fibers[0])); + + /* Each subsequent fiber waits for the previous and adds 1 */ + for (size_t i = 1; i < ELEMENTSOF(fibers); i++) + ASSERT_OK(sd_fiber_new(e, "chain-waiter", chain_waiter_fiber, fibers[i - 1], NULL, &fibers[i])); + + ASSERT_OK(sd_event_loop(e)); + + /* Check results: 10, 11, 12, 13, 14 */ + for (size_t i = 0; i < ELEMENTSOF(fibers); i++) + ASSERT_OK_EQ(sd_future_result(fibers[i]), 10 + (int) i); +} + +static int nested_run_inner_fiber(void *userdata) { + int *counter = ASSERT_PTR(userdata); + + (*counter)++; + int r = sd_fiber_yield(); + if (r < 0) + return r; + (*counter)++; + + return 0; +} + +static int nested_run_outer_fiber(void *userdata) { + int *counter = ASSERT_PTR(userdata); + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + _cleanup_(sd_future_unrefp) sd_future *nested = NULL; + int r; + + /* Yield once before the nested loop: this forces the outer fiber to later resume through its own + * siglongjmp back to its resume_context after the inner fiber_run() has executed, which is + * exactly the path that breaks when the resume context is stored thread-globally instead of + * per-fiber. */ + r = sd_fiber_yield(); + if (r < 0) + return r; + + r = sd_event_new(&inner); + if (r < 0) + return r; + + r = sd_event_set_exit_on_idle(inner, true); + if (r < 0) + return r; + + /* Spawn a fiber on the inner event loop. Driving it via sd_event_loop(inner) causes fiber_run() to + * be invoked while we are already executing inside fiber_run() for the outer fiber. */ + r = sd_fiber_new(inner, "inner", nested_run_inner_fiber, counter, NULL, &nested); + if (r < 0) + return r; + + r = sd_event_loop(inner); + if (r < 0) + return r; + + r = sd_future_result(nested); + if (r < 0) + return r; + + /* Yield again after the inner loop has returned. If the outer fiber's resume context was clobbered + * by the nested fiber_run(), the siglongjmp underneath this yield would jump into an already + * unwound stack frame. */ + return sd_fiber_yield(); +} + +TEST(fiber_nested_run) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *outer = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "outer", nested_run_outer_fiber, &counter, NULL, &outer)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(outer)); + + /* The inner fiber incremented the counter once before yielding and once after resuming. */ + ASSERT_EQ(counter, 2); +} + +static int nested_current_check_inner_fiber(void *userdata) { + sd_future **slots = ASSERT_PTR(userdata); + + slots[1] = sd_fiber_get_current(); + int r = sd_fiber_yield(); + if (r < 0) + return r; + /* After resuming, the current fiber must still be us, not the outer fiber that was current when + * fiber_run() re-entered. */ + if (sd_fiber_get_current() != slots[1]) + return -EBADF; + + return 0; +} + +static int nested_current_check_outer_fiber(void *userdata) { + sd_future **slots = ASSERT_PTR(userdata); + _cleanup_(sd_event_unrefp) sd_event *inner = NULL; + _cleanup_(sd_future_unrefp) sd_future *nested = NULL; + int r; + + slots[0] = sd_fiber_get_current(); + + r = sd_event_new(&inner); + if (r < 0) + return r; + + r = sd_event_set_exit_on_idle(inner, true); + if (r < 0) + return r; + + r = sd_fiber_new(inner, "inner", nested_current_check_inner_fiber, slots, NULL, &nested); + if (r < 0) + return r; + + r = sd_event_loop(inner); + if (r < 0) + return r; + + r = sd_future_result(nested); + if (r < 0) + return r; + + /* After the nested fiber_run() has returned, the current fiber must have been restored to the + * outer fiber rather than left as NULL or pointing at the (now freed) inner fiber. */ + if (sd_fiber_get_current() != slots[0]) + return -EBADF; + + return 0; +} + +TEST(fiber_nested_run_current_restored) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *slots[2] = {}; + _cleanup_(sd_future_unrefp) sd_future *outer = NULL; + ASSERT_OK(sd_fiber_new(e, "outer", nested_current_check_outer_fiber, slots, NULL, &outer)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(outer)); + + ASSERT_NOT_NULL(slots[0]); + ASSERT_NOT_NULL(slots[1]); + ASSERT_TRUE(slots[0] != slots[1]); +} + +static int nested_cancellation_fiber(void *userdata) { + int *counter = ASSERT_PTR(userdata); + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *nested = NULL; + int r; + + if (*counter >= 5) + return sd_fiber_sleep(10 * USEC_PER_SEC); + + (*counter)++; + + _cleanup_free_ char *name = NULL; + if (asprintf(&name, "nested-cancellation-%i", *counter) < 0) + return -ENOMEM; + + /* Create a nested fiber within this fiber */ + r = sd_fiber_new(sd_fiber_get_event(), name, nested_cancellation_fiber, counter, NULL, &nested); + if (r < 0) + return r; + + /* Wait for the nested fiber to complete */ + r = sd_fiber_await(nested); + if (r < 0) + return r; + + /* If we got here without cancellation, verify the nested fiber completed */ + return sd_future_result(nested); +} + +static int exit_loop_fiber(void *userdata) { + /* Just exit the event loop, causing the outer fiber to be cancelled */ + return sd_event_exit(sd_fiber_get_event(), 0); +} + +TEST(fiber_nested_cancellation) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + + int counter = 0; + + /* Create outer fiber with higher priority (runs first) */ + _cleanup_(sd_future_unrefp) sd_future *outer = NULL; + ASSERT_OK(sd_fiber_new(e, "outer", nested_cancellation_fiber, &counter, NULL, &outer)); + + /* Create exit fiber with lower priority (runs after all nested fibers have suspended) */ + _cleanup_(sd_future_unrefp) sd_future *exit_fiber = NULL; + ASSERT_OK(sd_fiber_new(e, "exit-loop", exit_loop_fiber, NULL, NULL, &exit_fiber)); + ASSERT_OK(sd_future_set_priority(exit_fiber, 1)); + + /* Run the event loop - the exit fiber should cause it to exit, + * which should cancel the outer fiber, which should cancel the nested fiber, and so forth. */ + ASSERT_OK(sd_event_loop(e)); + + /* The exit fiber should have completed successfully */ + ASSERT_OK(sd_future_result(exit_fiber)); + + /* The outer fiber should have been cancelled */ + ASSERT_ERROR(sd_future_result(outer), ECANCELED); + + /* The nested fiber was created and incremented counter once before being cancelled */ + ASSERT_GT(counter, 0); +} + +static int nested_fiber_cleanup_nested_fiber(void *userdata) { + int *counter = ASSERT_PTR(userdata); + int r; + + r = sd_fiber_sleep(10 * USEC_PER_SEC); + if (r == -ECANCELED) + (*counter)++; + else if (r < 0) + return r; + + return 0; +} + +static int nested_fiber_cleanup_fiber(void *userdata) { + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *nested = NULL; + int r; + + /* Create a nested fiber within this fiber. */ + r = sd_fiber_new(sd_fiber_get_event(), "nested", nested_fiber_cleanup_nested_fiber, userdata, NULL, &nested); + if (r < 0) + return r; + + /* Yield and then exit, the nested fiber should be cancelled. */ + return sd_fiber_yield(); +} + +TEST(nested_fiber_cleanup) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *outer = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "outer", nested_fiber_cleanup_fiber, &counter, NULL, &outer)); + + ASSERT_OK(sd_event_loop(e)); + + /* The outer fiber should have finished normally */ + ASSERT_OK(sd_future_result(outer)); + + /* The nested fiber was created and incremented its counter once when it was cancelled. */ + ASSERT_GT(counter, 0); +} + +static int priority_check_fiber(void *userdata) { + int64_t *ret = ASSERT_PTR(userdata); + + /* Verify that sd_fiber_get_priority() returns the value set via sd_future_set_priority() */ + ASSERT_OK(sd_fiber_get_priority(ret)); + + /* Exercise sd_fiber_sleep() which internally creates a time future. This verifies that the priority + * is correctly propagated to the time event source (via f->time.source, not f->io.source). */ + return sd_fiber_sleep(1); +} + +TEST(fiber_priority_get) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + int64_t got_priority = 0; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "priority-check", priority_check_fiber, &got_priority, NULL, &f)); + ASSERT_OK(sd_future_set_priority(f, 10)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(f)); + + /* Verify priority was stored and retrievable */ + ASSERT_EQ(got_priority, 10); +} + +static int floating_fiber(void *userdata) { + int *counter = ASSERT_PTR(userdata); + + (*counter)++; + int r = sd_fiber_yield(); + if (r < 0) + return r; + (*counter)++; + + return 0; +} + +TEST(fiber_floating) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "floating", floating_fiber, &counter, NULL, &f)); + + ASSERT_OK_ZERO(sd_fiber_get_floating(f)); + ASSERT_OK(sd_fiber_set_floating(f, true)); + ASSERT_OK_POSITIVE(sd_fiber_get_floating(f)); + + /* Drop our handle: the floating ref keeps the future alive until the fiber resolves, after + * which the self-unref frees it. If this didn't work we'd either leak (visible under ASan) or + * trip fiber_free()'s "state == COMPLETED" assertion. */ + f = sd_future_unref(f); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_EQ(counter, 2); +} + +static int drop_extra_ref(sd_future *f) { + /* Drop an extra ref the test installed before the callback fires. After this returns, the + * floating self-ref is the only thing keeping the future alive — exercising the path where + * the floating unref in fiber_run() is the last unref. */ + sd_future_unref(f); + return 0; +} + +TEST(fiber_floating_callback_drops_ref) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sd_future *f = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "floating-cb", floating_fiber, &counter, NULL, &f)); + + ASSERT_OK(sd_fiber_set_floating(f, true)); + + /* Bump the ref for the callback to drop, then install the callback. */ + sd_future_ref(f); + ASSERT_OK(sd_future_set_callback(f, drop_extra_ref, NULL)); + + /* Drop our handle. Refs remaining: floating self-ref + the extra ref the callback will drop. */ + f = sd_future_unref(f); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_EQ(counter, 2); +} + +TEST(fiber_floating_toggle) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + int counter = 0; + ASSERT_OK(sd_fiber_new(e, "floating-toggle", floating_fiber, &counter, NULL, &f)); + + /* Toggling floating on and off again should leave the refcount unchanged: set_floating(true) + * takes a ref and set_floating(false) drops it. If the accounting were off, the subsequent + * event loop would either free the future while the fiber still runs (fiber_free assertion) + * or leak it. */ + ASSERT_OK(sd_fiber_set_floating(f, true)); + ASSERT_OK(sd_fiber_set_floating(f, false)); + ASSERT_OK_ZERO(sd_fiber_get_floating(f)); + + /* Setting floating to the same value twice should be a no-op. */ + ASSERT_OK(sd_fiber_set_floating(f, false)); + ASSERT_OK(sd_fiber_set_floating(f, true)); + ASSERT_OK(sd_fiber_set_floating(f, true)); + + /* Drop our handle; the still-floating ref drives cleanup. */ + f = sd_future_unref(f); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_EQ(counter, 2); +} + +/* Test: SD_FIBER_TIMEOUT scope expires while the fiber is suspended with no other wakeup source. */ +static int timeout_suspend_fiber(void *userdata) { + SD_FIBER_TIMEOUT(50 * USEC_PER_MSEC); + + /* Plain suspend with no other future to wake us — only the deadline timer can resume. */ + return sd_fiber_suspend(); +} + +TEST(fiber_timeout_suspend_expires) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "timeout-suspend", timeout_suspend_fiber, NULL, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_ERROR(sd_future_result(f), ETIME); +} + +/* Test: SD_FIBER_TIMEOUT scope around a sleep that finishes before the deadline expires; the + * cleanup must cancel the timer cleanly without leaving a stale wakeup. */ +static int timeout_in_time_fiber(void *userdata) { + SD_FIBER_TIMEOUT(1 * USEC_PER_SEC); + return sd_fiber_sleep(10 * USEC_PER_MSEC); +} + +TEST(fiber_timeout_sleep_in_time) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "in-time", timeout_in_time_fiber, NULL, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK_ZERO(sd_future_result(f)); +} + +/* Test: SD_FIBER_TIMEOUT(USEC_INFINITY) is a no-op — no timer is created and the fiber completes + * normally. */ +static int timeout_infinite_fiber(void *userdata) { + SD_FIBER_TIMEOUT(USEC_INFINITY); + return sd_fiber_sleep(10 * USEC_PER_MSEC); +} + +TEST(fiber_timeout_infinite_no_op) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "infinite", timeout_infinite_fiber, NULL, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK_ZERO(sd_future_result(f)); +} + +/* Test: SD_FIBER_WITH_TIMEOUT block form returns -ETIME from the suspend inside it. */ +static int with_timeout_block_fiber(void *userdata) { + int r = 0; + SD_FIBER_WITH_TIMEOUT(50 * USEC_PER_MSEC) + r = sd_fiber_suspend(); + return r; +} + +TEST(fiber_with_timeout_block) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "with-timeout", with_timeout_block_fiber, NULL, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_ERROR(sd_future_result(f), ETIME); +} + +/* Test: nested SD_FIBER_TIMEOUT — inner scope's timer fires first; once we're back in just the + * outer scope, suspending again must time out via the still-armed outer timer. */ +static int nested_timeout_fiber(void *userdata) { + int *fired = ASSERT_PTR(userdata); + + SD_FIBER_TIMEOUT(50 * USEC_PER_MSEC); /* outer */ + + SD_FIBER_WITH_TIMEOUT(20 * USEC_PER_MSEC) { /* inner — expires first */ + int r = sd_fiber_suspend(); + if (r != -ETIME) + return -ENOTRECOVERABLE; + (*fired)++; + } + + /* Inner scope is gone, but the outer timer is still armed (it only used ~20ms of its + * 100ms budget). Suspending again must eventually wake us with -ETIME. */ + int r = sd_fiber_suspend(); + if (r != -ETIME) + return -ENOTRECOVERABLE; + (*fired)++; + + return 0; +} + +TEST(fiber_timeout_nested) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + int fired = 0; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "nested-timeout", nested_timeout_fiber, &fired, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK_ZERO(sd_future_result(f)); + ASSERT_EQ(fired, 2); +} + +/* Test: signal mask is per-thread, not per-fiber. Changes one fiber makes via pthread_sigmask + * must be visible to other fibers on the same thread, both while the modifying fiber is + * suspended and after it resumes. The fiber switch (sigsetjmp/siglongjmp with savesigs=0) + * deliberately doesn't save or restore the mask. */ +static int sigmask_peer_fiber(void *userdata) { + sigset_t set, current; + + /* The waiter blocked SIGUSR1 before await'ing us; the per-thread mask should still + * have it blocked here. */ + ASSERT_OK_ZERO(-pthread_sigmask(SIG_SETMASK, NULL, ¤t)); + ASSERT_TRUE(sigismember(¤t, SIGUSR1)); + + ASSERT_OK(sigemptyset(&set)); + ASSERT_OK(sigaddset(&set, SIGUSR1)); + ASSERT_OK_ZERO(-pthread_sigmask(SIG_UNBLOCK, &set, NULL)); + + return 0; +} + +static int sigmask_waiter_fiber(void *userdata) { + sd_future *peer = ASSERT_PTR(userdata); + sigset_t set, current; + + ASSERT_OK(sigemptyset(&set)); + ASSERT_OK(sigaddset(&set, SIGUSR1)); + ASSERT_OK_ZERO(-pthread_sigmask(SIG_BLOCK, &set, NULL)); + + ASSERT_OK_ZERO(-pthread_sigmask(SIG_SETMASK, NULL, ¤t)); + ASSERT_TRUE(sigismember(¤t, SIGUSR1)); + + int r = sd_fiber_await(peer); + if (r < 0) + return r; + + /* The peer unblocked SIGUSR1 while we were suspended. The change is per-thread, so + * we must observe it here. */ + ASSERT_OK_ZERO(-pthread_sigmask(SIG_SETMASK, NULL, ¤t)); + ASSERT_FALSE(sigismember(¤t, SIGUSR1)); + + return 0; +} + +TEST(fiber_signal_mask_is_per_thread) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + sigset_t saved; + ASSERT_OK_ZERO(-pthread_sigmask(SIG_SETMASK, NULL, &saved)); + + _cleanup_(sd_future_unrefp) sd_future *waiter = NULL, *peer = NULL; + ASSERT_OK(sd_fiber_new(e, "sigmask-peer", sigmask_peer_fiber, NULL, NULL, &peer)); + ASSERT_OK(sd_future_set_priority(peer, 1)); + ASSERT_OK(sd_fiber_new(e, "sigmask-waiter", sigmask_waiter_fiber, peer, NULL, &waiter)); + ASSERT_OK(sd_future_set_priority(waiter, 0)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(waiter)); + ASSERT_OK(sd_future_result(peer)); + + ASSERT_OK_ZERO(-pthread_sigmask(SIG_SETMASK, &saved, NULL)); +} + +/* Test: log context is per-fiber. fiber_run() swaps the thread-local log context (and prefix) with + * a per-fiber stash on entry and exit, so fields pushed by one fiber must not leak into another + * fiber that runs while the first is suspended, and must be restored when the first resumes. */ +static int log_context_peer_fiber(void *userdata) { + size_t *peer_observed = ASSERT_PTR(userdata); + + /* The waiter pushed a field before await'ing us. If log context were shared across fibers, + * we would observe it here. Record what we see and let the caller verify. */ + *peer_observed = log_context_num_fields(); + + return 0; +} + +static int log_context_waiter_fiber(void *userdata) { + sd_future *peer = ASSERT_PTR(userdata); + + size_t before_push = log_context_num_fields(); + + LOG_CONTEXT_PUSH("WAITER=here"); + size_t after_push = log_context_num_fields(); + if (after_push != before_push + 1) + return -EBADF; + + int r = sd_fiber_await(peer); + if (r < 0) + return r; + + /* Our pushed field must be visible again after the peer ran and resumed us. */ + if (log_context_num_fields() != after_push) + return -EBADF; + + return 0; +} + +TEST(fiber_log_context_per_fiber) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + size_t baseline = log_context_num_fields(); + + size_t peer_observed = 0; + _cleanup_(sd_future_unrefp) sd_future *waiter = NULL, *peer = NULL; + ASSERT_OK(sd_fiber_new(e, "log-peer", log_context_peer_fiber, &peer_observed, NULL, &peer)); + ASSERT_OK(sd_future_set_priority(peer, 1)); + ASSERT_OK(sd_fiber_new(e, "log-waiter", log_context_waiter_fiber, peer, NULL, &waiter)); + ASSERT_OK(sd_future_set_priority(waiter, 0)); + + ASSERT_OK(sd_event_loop(e)); + ASSERT_OK(sd_future_result(waiter)); + ASSERT_OK(sd_future_result(peer)); + + /* Inside the peer, only the peer's own FIBER= field (pushed by fiber_run) should have been + * active — the waiter's WAITER= push must have been swapped out. */ + ASSERT_EQ(peer_observed, baseline + 1); + + /* The thread-local log context should be exactly as it was before the test ran. */ + ASSERT_EQ(log_context_num_fields(), baseline); +} + +static int stack_overflow_fiber(void *userdata) { + volatile char anchor; + size_t pagesz = page_size(); + + /* Walk one page at a time away from the fiber's current SP toward the guard page, + * writing one byte per page until the kernel raises a fatal signal. On downward + * stacks we walk to lower addresses (guard at the base); on upward stacks like + * hppa we walk to higher addresses (guard at the top of the mapping). The 64 MiB + * ceiling is purely a safety net so the test fails loudly instead of looping if + * the guard isn't there. */ + for (size_t i = 1; i < (64U * U64_MB) / pagesz; i++) { + uintptr_t off = i * pagesz; + volatile char *p = (volatile char *) (STACK_GROWS_UP + ? (uintptr_t) &anchor + off + : (uintptr_t) &anchor - off); + *p = 0; + } + return 0; +} + +TEST(fiber_stack_guard) { +#if HAS_FEATURE_ADDRESS_SANITIZER + (void) log_tests_skipped("ASan intercepts deliberate stack OOB writes"); + return; +#endif +#if HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) { + (void) log_tests_skipped("Valgrind intercepts deliberate stack OOB writes"); + return; + } +#endif + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r = pidref_safe_fork("(stack-overflow)", FORK_RESET_SIGNALS|FORK_LOG, &pidref); + ASSERT_OK(r); + + if (r == 0) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + ASSERT_OK(sd_fiber_new(e, "overflow", stack_overflow_fiber, NULL, NULL, &f)); + (void) sd_event_loop(e); + _exit(EXIT_SUCCESS); /* unreachable if the guard fires */ + } + + siginfo_t si; + ASSERT_OK(pidref_wait_for_terminate(&pidref, &si)); + ASSERT_TRUE(IN_SET(si.si_code, CLD_KILLED, CLD_DUMPED)); + ASSERT_TRUE(IN_SET(si.si_status, SIGSEGV, SIGBUS)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd/sd-hwdb/fuzz-hwdb.c b/src/libsystemd/sd-hwdb/fuzz-hwdb.c new file mode 100644 index 0000000000000..711d4713c83e5 --- /dev/null +++ b/src/libsystemd/sd-hwdb/fuzz-hwdb.c @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* Fuzz target for the sd-hwdb binary trie parser. + * + * sd-hwdb consumes an mmap'd hwdb.bin produced by `systemd-hwdb update`. The + * file is walked via offset-chasing into struct trie_node_f / trie_child_entry_f + * / trie_value_entry_f records. A malformed file can drive the walker outside + * the mapped region; the validators in trie_node_from_off / trie_string don't + * guard every dereference site. + * + * Expected input: bytes that look like an `hwdb.bin` file (header magic + * "KSLPHHRH" + trie). The harness writes the fuzzer bytes to a temp file, + * mmaps it via sd_hwdb_new_from_path, then exercises sd_hwdb_get (which + * drives trie_search_f / trie_fnmatch_f) and sd_hwdb_seek + sd_hwdb_enumerate + * (capped to bound infinite loops via crafted offset cycles). + */ + +#include + +#include "sd-hwdb.h" + +#include "fd-util.h" +#include "fileio.h" +#include "fuzz.h" +#include "tests.h" +#include "tmpfile-util.h" + +#define MAX_ENUMERATE 1024 + +static const char * const modaliases[] = { + "pci:v00008086d00000A04", + "usb:v1234p5678", + "acpi:PNP0C0D:", + "dmi:bvnLENOVO:bvrR0KET20W:bd02/01/2020:svnLENOVO:pnThinkPadX1:", + "*", +}; + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_(unlink_tempfilep) char filename[] = "/tmp/fuzz-hwdb.XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + + if (outside_size_range(size, 0, 4 * 1024 * 1024)) + return 0; + + fuzz_setup_logging(); + + ASSERT_OK(fmkostemp_safe(filename, "r+", &f)); + if (size != 0) + ASSERT_OK_EQ_ERRNO((ssize_t) fwrite(data, size, 1, f), (ssize_t) 1); + ASSERT_OK(fflush_and_check(f)); + + if (sd_hwdb_new_from_path(filename, &hwdb) < 0) + return 0; + + FOREACH_ELEMENT(modalias, modaliases) { + const char *key, *value; + size_t n = 0; + + (void) sd_hwdb_get(hwdb, *modalias, "ID_VENDOR", &value); + + if (sd_hwdb_seek(hwdb, *modalias) < 0) + continue; + while (sd_hwdb_enumerate(hwdb, &key, &value) > 0) { + DO_NOT_OPTIMIZE(key); + DO_NOT_OPTIMIZE(value); + if (++n >= MAX_ENUMERATE) + break; + } + } + + return 0; +} diff --git a/src/libsystemd/sd-hwdb/sd-hwdb.c b/src/libsystemd/sd-hwdb/sd-hwdb.c index 1197c4f954c80..938d02f3b79af 100644 --- a/src/libsystemd/sd-hwdb/sd-hwdb.c +++ b/src/libsystemd/sd-hwdb/sd-hwdb.c @@ -3,6 +3,7 @@ Copyright © 2008 Alan Jenkins ***/ +#include #include #include #include @@ -79,12 +80,74 @@ static const struct trie_value_entry_f *trie_node_value(sd_hwdb *hwdb, const str return (const struct trie_value_entry_f *)base; } -static const struct trie_node_f *trie_node_from_off(sd_hwdb *hwdb, le64_t off) { - return (const struct trie_node_f *)(hwdb->map + le64toh(off)); +static const void* hwdb_at(sd_hwdb *hwdb, uint64_t off, uint64_t size) { + assert(hwdb); + + uint64_t file_size = hwdb->st.st_size; + + /* off == file_size is rejected too: a read at EOF is always OOB, and this keeps the + * boundary unambiguous even for a size == 0 caller (which would otherwise get a + * one-past-the-end pointer). */ + if (off >= file_size) + return NULL; + if (file_size - off < size) + return NULL; + + return (const uint8_t*) hwdb->map + off; +} + +static const struct trie_node_f* trie_node_from_off(sd_hwdb *hwdb, le64_t off) { + assert(hwdb); + assert(hwdb->head); + + uint64_t offset = le64toh(off); + uint64_t node_size = le64toh(hwdb->head->node_size); + uint64_t child_entry_size = le64toh(hwdb->head->child_entry_size); + uint64_t value_entry_size = le64toh(hwdb->head->value_entry_size); + + if (node_size < sizeof(struct trie_node_f)) + return NULL; + if (child_entry_size < sizeof(struct trie_child_entry_f)) + return NULL; + if (value_entry_size < sizeof(struct trie_value_entry_f)) + return NULL; + + const struct trie_node_f *node = hwdb_at(hwdb, offset, node_size); + if (!node) + return NULL; + + uint64_t children_bytes, values_bytes, total; + if (!MUL_SAFE(&children_bytes, (uint64_t) node->children_count, child_entry_size)) + return NULL; + if (!MUL_SAFE(&values_bytes, le64toh(node->values_count), value_entry_size)) + return NULL; + if (!ADD_SAFE(&total, node_size, children_bytes)) + return NULL; + if (!INC_SAFE(&total, values_bytes)) + return NULL; + + if (!hwdb_at(hwdb, offset, total)) + return NULL; + + return node; } -static const char *trie_string(sd_hwdb *hwdb, le64_t off) { - return hwdb->map + le64toh(off); +static const char* trie_string(sd_hwdb *hwdb, le64_t off) { + assert(hwdb); + + uint64_t file_size = hwdb->st.st_size; + uint64_t offset = le64toh(off); + + const char *p = hwdb_at(hwdb, offset, /* size= */ 1); + if (!p) + return NULL; + + /* Clamp to SIZE_MAX so memchr()'s size_t arg cannot truncate on 32-bit. */ + size_t avail = (size_t) MIN(file_size - offset, (uint64_t) SIZE_MAX); + if (!memchr(p, '\0', avail)) + return NULL; + + return p; } static int trie_children_cmp_f(const void *v1, const void *v2) { @@ -102,7 +165,9 @@ static const struct trie_node_f *node_lookup_f(sd_hwdb *hwdb, const struct trie_ child = bsearch(&search, (const char *)node + le64toh(hwdb->head->node_size), node->children_count, le64toh(hwdb->head->child_entry_size), trie_children_cmp_f); if (child) + /* Treat corrupt child offsets like lookup misses. */ return trie_node_from_off(hwdb, child->child_off); + return NULL; } @@ -113,6 +178,8 @@ static int hwdb_add_property(sd_hwdb *hwdb, const struct trie_value_entry_f *ent assert(hwdb); key = trie_string(hwdb, entry->key_off); + if (!key) + return -EBADMSG; /* * Silently ignore all properties which do not start with a @@ -173,33 +240,64 @@ static int hwdb_add_property(sd_hwdb *hwdb, const struct trie_value_entry_f *ent return 0; } +/* Cap recursion depth so a corrupt hwdb.bin whose children offsets form a + * cycle (or just a deep linear chain) cannot exhaust the stack. Real-world + * hwdb files do not approach this; the deepest legitimate trie key is well + * under a kilobyte. */ +#define HWDB_RECURSION_MAX 2048U + static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t p, - struct linebuf *buf, const char *search) { + struct linebuf *buf, const char *search, unsigned depth) { size_t len; size_t i; const char *prefix; int err; + assert(hwdb); + assert(node); + + if (depth >= HWDB_RECURSION_MAX) + return -EBADMSG; + prefix = trie_string(hwdb, node->prefix_off); - len = strlen(prefix + p); - linebuf_add(buf, prefix + p, len); + if (!prefix) + return -EBADMSG; + + len = strlen(prefix); + if (p > len) + return -EBADMSG; + len -= p; + + if (!linebuf_add(buf, prefix + p, len)) + return -EINVAL; for (i = 0; i < node->children_count; i++) { const struct trie_child_entry_f *child = trie_node_child(hwdb, node, i); + const struct trie_node_f *child_node; linebuf_add_char(buf, child->c); - err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search); + child_node = trie_node_from_off(hwdb, child->child_off); + if (!child_node) + return -EBADMSG; + + err = trie_fnmatch_f(hwdb, child_node, 0, buf, search, depth + 1); if (err < 0) return err; linebuf_rem_char(buf); } - if (le64toh(node->values_count) && fnmatch(linebuf_get(buf), search, 0) == 0) - for (i = 0; i < le64toh(node->values_count); i++) { - err = hwdb_add_property(hwdb, trie_node_value(hwdb, node, i)); - if (err < 0) - return err; - } + if (le64toh(node->values_count) != 0) { + const char *line = linebuf_get(buf); + if (!line) + return -EBADMSG; + + if (fnmatch(line, search, 0) == 0) + for (i = 0; i < le64toh(node->values_count); i++) { + err = hwdb_add_property(hwdb, trie_node_value(hwdb, node, i)); + if (err < 0) + return err; + } + } linebuf_rem(buf, len); return 0; @@ -214,16 +312,24 @@ static int trie_search_f(sd_hwdb *hwdb, const char *search) { linebuf_init(&buf); node = trie_node_from_off(hwdb, hwdb->head->nodes_root_off); + if (!node) + return -EBADMSG; + while (node) { const struct trie_node_f *child; size_t p = 0; if (node->prefix_off) { + const char *prefix; char c; - for (; (c = trie_string(hwdb, node->prefix_off)[p]); p++) { + prefix = trie_string(hwdb, node->prefix_off); + if (!prefix) + return -EBADMSG; + + for (; (c = prefix[p]); p++) { if (IN_SET(c, '*', '?', '[')) - return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p); + return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p, 0); if (c != search[i + p]) return 0; } @@ -233,7 +339,7 @@ static int trie_search_f(sd_hwdb *hwdb, const char *search) { child = node_lookup_f(hwdb, node, '*'); if (child) { linebuf_add_char(&buf, '*'); - err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); + err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i, 0); if (err < 0) return err; linebuf_rem_char(&buf); @@ -242,7 +348,7 @@ static int trie_search_f(sd_hwdb *hwdb, const char *search) { child = node_lookup_f(hwdb, node, '?'); if (child) { linebuf_add_char(&buf, '?'); - err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); + err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i, 0); if (err < 0) return err; linebuf_rem_char(&buf); @@ -251,7 +357,7 @@ static int trie_search_f(sd_hwdb *hwdb, const char *search) { child = node_lookup_f(hwdb, node, '['); if (child) { linebuf_add_char(&buf, '['); - err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); + err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i, 0); if (err < 0) return err; linebuf_rem_char(&buf); @@ -370,14 +476,15 @@ static int properties_prepare(sd_hwdb *hwdb, const char *modalias) { return trie_search_f(hwdb, modalias); } -_public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **_value) { +_public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **ret) { const struct trie_value_entry_f *entry; + const char *value; int r; assert_return(hwdb, -EINVAL); assert_return(hwdb->f, -EINVAL); assert_return(modalias, -EINVAL); - assert_return(_value, -EINVAL); + assert_return(ret, -EINVAL); r = properties_prepare(hwdb, modalias); if (r < 0) @@ -387,7 +494,11 @@ _public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, c if (!entry) return -ENOENT; - *_value = trie_string(hwdb, entry->value_off); + value = trie_string(hwdb, entry->value_off); + if (!value) + return -EBADMSG; + + *ret = value; return 0; } @@ -409,13 +520,14 @@ _public_ int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias) { return 0; } -_public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value) { +_public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **ret_key, const char **ret_value) { const struct trie_value_entry_f *entry; + const char *v; const void *k; assert_return(hwdb, -EINVAL); - assert_return(key, -EINVAL); - assert_return(value, -EINVAL); + assert_return(ret_key, -EINVAL); + assert_return(ret_value, -EINVAL); if (hwdb->properties_modified) return -EAGAIN; @@ -423,8 +535,12 @@ _public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **val if (!ordered_hashmap_iterate(hwdb->properties, &hwdb->properties_iterator, (void **)&entry, &k)) return 0; - *key = k; - *value = trie_string(hwdb, entry->value_off); + v = trie_string(hwdb, entry->value_off); + if (!v) + return -EBADMSG; + + *ret_key = k; + *ret_value = v; return 1; } diff --git a/src/libsystemd/sd-hwdb/test-sd-hwdb.c b/src/libsystemd/sd-hwdb/test-sd-hwdb.c new file mode 100644 index 0000000000000..1c4223d7d78c5 --- /dev/null +++ b/src/libsystemd/sd-hwdb/test-sd-hwdb.c @@ -0,0 +1,371 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-hwdb.h" + +#include "errno-util.h" +#include "fd-util.h" +#include "hwdb-internal.h" +#include "nulstr-util.h" +#include "path-util.h" +#include "rm-rf.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST(failed_enumerate) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + const char *key, *value; + + assert_se(sd_hwdb_new(&hwdb) == 0); + + assert_se(sd_hwdb_seek(hwdb, "no-such-modalias-should-exist") == 0); + + assert_se(sd_hwdb_enumerate(hwdb, &key, &value) == 0); + ASSERT_RETURN_EXPECTED_SE(sd_hwdb_enumerate(hwdb, &key, NULL) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_hwdb_enumerate(hwdb, NULL, &value) == -EINVAL); +} + +#define DELL_MODALIAS \ + "evdev:atkbd:dmi:bvnXXX:bvrYYY:bdZZZ:svnDellXXX:pnYYY:" + +TEST(basic_enumerate) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + const char *key, *value; + size_t len1 = 0, len2 = 0; + int r; + + assert_se(sd_hwdb_new(&hwdb) == 0); + + assert_se(sd_hwdb_seek(hwdb, DELL_MODALIAS) == 0); + + for (;;) { + r = sd_hwdb_enumerate(hwdb, &key, &value); + assert_se(IN_SET(r, 0, 1)); + if (r == 0) + break; + assert_se(key); + assert_se(value); + log_debug("A: \"%s\" → \"%s\"", key, value); + len1 += strlen(key) + strlen(value); + } + + SD_HWDB_FOREACH_PROPERTY(hwdb, DELL_MODALIAS, key, value) { + log_debug("B: \"%s\" → \"%s\"", key, value); + len2 += strlen(key) + strlen(value); + } + + assert_se(len1 == len2); +} + +TEST(sd_hwdb_new_from_path) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + int r; + + ASSERT_RETURN_EXPECTED_SE(sd_hwdb_new_from_path(NULL, &hwdb) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_hwdb_new_from_path("", &hwdb) == -EINVAL); + assert_se(sd_hwdb_new_from_path("/path/that/should/not/exist", &hwdb) < 0); + + NULSTR_FOREACH(hwdb_bin_path, HWDB_BIN_PATHS) { + r = sd_hwdb_new_from_path(hwdb_bin_path, &hwdb); + if (r >= 0) + break; + } + + assert_se(r >= 0); +} + +static sd_hwdb* hwdb_new_from_blob(const void *data, size_t size) { + _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + _cleanup_close_ int fd = -EBADF; + + ASSERT_OK(mkdtemp_malloc(/* template= */ NULL, &tmp)); + _cleanup_free_ char *path = ASSERT_NOT_NULL(path_join(tmp, "hwdb.bin")); + + fd = ASSERT_OK_ERRNO(open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_TRUNC, 0644)); + ASSERT_OK_EQ_ERRNO(write(fd, data, size), (ssize_t) size); + fd = safe_close(fd); + + ASSERT_OK(sd_hwdb_new_from_path(path, &hwdb)); + + return TAKE_PTR(hwdb); +} + +TEST(sd_hwdb_seek_rejects_invalid_fnmatch_child_node) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + + struct invalid_hwdb { + struct trie_header_f header; + struct trie_node_f root_node; + struct trie_child_entry_f child1; + struct trie_node_f mid_node; + struct trie_child_entry_f child2; + struct trie_node_f wildcard_node; + struct trie_child_entry_f child3; + char strings[STRLEN("usb:") + 1]; + } _packed_ data = { + .header = { + .signature = HWDB_SIG, + .tool_version = htole64(PROJECT_VERSION), + .file_size = htole64(sizeof(struct invalid_hwdb)), + .header_size = htole64(sizeof(struct trie_header_f)), + .node_size = htole64(sizeof(struct trie_node_f)), + .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), + .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), + .nodes_root_off = htole64(offsetof(struct invalid_hwdb, root_node)), + .nodes_len = htole64(offsetof(struct invalid_hwdb, strings) - offsetof(struct invalid_hwdb, root_node)), + .strings_len = htole64(STRLEN("usb:") + 1), + }, + .root_node = { + .prefix_off = htole64(offsetof(struct invalid_hwdb, strings)), + .children_count = 1, + }, + .child1 = { + .c = 'v', + .child_off = htole64(offsetof(struct invalid_hwdb, mid_node)), + }, + .mid_node = { + .prefix_off = htole64(offsetof(struct invalid_hwdb, strings) + STRLEN("usb:")), + .children_count = 1, + }, + .child2 = { + .c = '*', + .child_off = htole64(offsetof(struct invalid_hwdb, wildcard_node)), + }, + .wildcard_node = { + .prefix_off = htole64(offsetof(struct invalid_hwdb, strings) + STRLEN("usb:")), + .children_count = 1, + }, + .child3 = { + .c = 'x', + .child_off = htole64(0xDEAD0000), + }, + .strings = "usb:", + }; + + hwdb = ASSERT_NOT_NULL(hwdb_new_from_blob(&data, sizeof(data))); + + ASSERT_ERROR(sd_hwdb_seek(hwdb, "usb:vx"), EBADMSG); +} + +TEST(sd_hwdb_seek_rejects_invalid_fnmatch_prefix) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + + struct invalid_hwdb { + struct trie_header_f header; + struct trie_node_f root_node; + struct trie_child_entry_f child1; + struct trie_node_f mid_node; + struct trie_child_entry_f child2; + struct trie_node_f crash_node; + char strings[STRLEN("usb:") + 1]; + } _packed_ data = { + .header = { + .signature = HWDB_SIG, + .tool_version = htole64(PROJECT_VERSION), + .file_size = htole64(sizeof(struct invalid_hwdb)), + .header_size = htole64(sizeof(struct trie_header_f)), + .node_size = htole64(sizeof(struct trie_node_f)), + .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), + .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), + .nodes_root_off = htole64(offsetof(struct invalid_hwdb, root_node)), + .nodes_len = htole64(offsetof(struct invalid_hwdb, strings) - offsetof(struct invalid_hwdb, root_node)), + .strings_len = htole64(STRLEN("usb:") + 1), + }, + .root_node = { + .prefix_off = htole64(offsetof(struct invalid_hwdb, strings)), + .children_count = 1, + }, + .child1 = { + .c = 'v', + .child_off = htole64(offsetof(struct invalid_hwdb, mid_node)), + }, + .mid_node = { + .prefix_off = htole64(offsetof(struct invalid_hwdb, strings) + STRLEN("usb:")), + .children_count = 1, + }, + .child2 = { + .c = '*', + .child_off = htole64(offsetof(struct invalid_hwdb, crash_node)), + }, + .crash_node = { + .prefix_off = htole64(0xDEAD0000), + }, + .strings = "usb:", + }; + + hwdb = ASSERT_NOT_NULL(hwdb_new_from_blob(&data, sizeof(data))); + + ASSERT_ERROR(sd_hwdb_seek(hwdb, "usb:v*"), EBADMSG); +} + +TEST(sd_hwdb_seek_rejects_invalid_search_prefix) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + + struct invalid_hwdb { + struct trie_header_f header; + struct trie_node_f root_node; + char strings[1]; + } _packed_ data = { + .header = { + .signature = HWDB_SIG, + .tool_version = htole64(PROJECT_VERSION), + .file_size = htole64(sizeof(struct invalid_hwdb)), + .header_size = htole64(sizeof(struct trie_header_f)), + .node_size = htole64(sizeof(struct trie_node_f)), + .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), + .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), + .nodes_root_off = htole64(offsetof(struct invalid_hwdb, root_node)), + .nodes_len = htole64(sizeof(struct trie_node_f)), + .strings_len = htole64(1), + }, + .root_node = { + .prefix_off = htole64(0xDEAD0000), + }, + }; + + hwdb = ASSERT_NOT_NULL(hwdb_new_from_blob(&data, sizeof(data))); + + ASSERT_ERROR(sd_hwdb_seek(hwdb, "usb:v1234"), EBADMSG); +} + +TEST(sd_hwdb_seek_rejects_truncated_children_array) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + + struct invalid_hwdb { + struct trie_header_f header; + struct trie_node_f root_node; + } _packed_ data = { + .header = { + .signature = HWDB_SIG, + .tool_version = htole64(PROJECT_VERSION), + .file_size = htole64(sizeof(struct invalid_hwdb)), + .header_size = htole64(sizeof(struct trie_header_f)), + .node_size = htole64(sizeof(struct trie_node_f)), + .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), + .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), + .nodes_root_off = htole64(offsetof(struct invalid_hwdb, root_node)), + .nodes_len = htole64(sizeof(struct trie_node_f)), + }, + .root_node = { + .children_count = 1, + }, + }; + + hwdb = ASSERT_NOT_NULL(hwdb_new_from_blob(&data, sizeof(data))); + + ASSERT_ERROR(sd_hwdb_seek(hwdb, "x"), EBADMSG); +} + +TEST(sd_hwdb_seek_rejects_invalid_property_key) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + + struct invalid_hwdb { + struct trie_header_f header; + struct trie_node_f root_node; + struct trie_value_entry_f value; + char strings[STRLEN("usb") + 1 + STRLEN("value") + 1]; + } _packed_ data = { + .header = { + .signature = HWDB_SIG, + .tool_version = htole64(PROJECT_VERSION), + .file_size = htole64(sizeof(struct invalid_hwdb)), + .header_size = htole64(sizeof(struct trie_header_f)), + .node_size = htole64(sizeof(struct trie_node_f)), + .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), + .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), + .nodes_root_off = htole64(offsetof(struct invalid_hwdb, root_node)), + .nodes_len = htole64(offsetof(struct invalid_hwdb, strings) - offsetof(struct invalid_hwdb, root_node)), + .strings_len = htole64(STRLEN("usb") + 1 + STRLEN("value") + 1), + }, + .root_node = { + .prefix_off = htole64(offsetof(struct invalid_hwdb, strings)), + .values_count = htole64(1), + }, + .value = { + .key_off = htole64(0xDEAD0000), + .value_off = htole64(offsetof(struct invalid_hwdb, strings) + STRLEN("usb") + 1), + }, + .strings = "usb\0value", + }; + + hwdb = ASSERT_NOT_NULL(hwdb_new_from_blob(&data, sizeof(data))); + + ASSERT_ERROR(sd_hwdb_seek(hwdb, "usb"), EBADMSG); +} + +TEST(sd_hwdb_seek_rejects_cyclic_trie) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + + /* Cyclic trie: the wildcard_node's only child points back to + * itself, which would drive trie_fnmatch_f() into infinite + * recursion (stack-overflow CVE-class bug found by CIFuzz). + * The recursion-depth cap must turn this into a clean -EBADMSG. */ + struct invalid_hwdb { + struct trie_header_f header; + struct trie_node_f root_node; + struct trie_child_entry_f child1; + struct trie_node_f mid_node; + struct trie_child_entry_f child2; + struct trie_node_f wildcard_node; + struct trie_child_entry_f wildcard_self_child; + char strings[STRLEN("usb:") + 1]; + } _packed_ data = { + .header = { + .signature = HWDB_SIG, + .tool_version = htole64(PROJECT_VERSION), + .file_size = htole64(sizeof(struct invalid_hwdb)), + .header_size = htole64(sizeof(struct trie_header_f)), + .node_size = htole64(sizeof(struct trie_node_f)), + .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), + .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), + .nodes_root_off = htole64(offsetof(struct invalid_hwdb, root_node)), + .nodes_len = htole64(offsetof(struct invalid_hwdb, strings) - offsetof(struct invalid_hwdb, root_node)), + .strings_len = htole64(STRLEN("usb:") + 1), + }, + .root_node = { + .prefix_off = htole64(offsetof(struct invalid_hwdb, strings)), + .children_count = 1, + }, + .child1 = { + .c = 'v', + .child_off = htole64(offsetof(struct invalid_hwdb, mid_node)), + }, + .mid_node = { + .prefix_off = htole64(offsetof(struct invalid_hwdb, strings) + STRLEN("usb:")), + .children_count = 1, + }, + .child2 = { + .c = '*', + .child_off = htole64(offsetof(struct invalid_hwdb, wildcard_node)), + }, + .wildcard_node = { + .prefix_off = htole64(offsetof(struct invalid_hwdb, strings) + STRLEN("usb:")), + .children_count = 1, + }, + .wildcard_self_child = { + .c = 'a', + .child_off = htole64(offsetof(struct invalid_hwdb, wildcard_node)), + }, + .strings = "usb:", + }; + + hwdb = ASSERT_NOT_NULL(hwdb_new_from_blob(&data, sizeof(data))); + + ASSERT_ERROR(sd_hwdb_seek(hwdb, "usb:v*"), EBADMSG); +} + +static int intro(void) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + int r; + + r = sd_hwdb_new(&hwdb); + if (r == -ENOENT || ERRNO_IS_PRIVILEGE(r)) + return log_tests_skipped_errno(r, "cannot open hwdb"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/libsystemd/sd-id128/id128-util.c b/src/libsystemd/sd-id128/id128-util.c index 9d406a45d1316..b961be0659d97 100644 --- a/src/libsystemd/sd-id128/id128-util.c +++ b/src/libsystemd/sd-id128/id128-util.c @@ -142,7 +142,7 @@ int id128_read_fd(int fd, Id128Flag f, sd_id128_t *ret) { int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret) { _cleanup_close_ int fd = -EBADF; - assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(dir_fd)); assert(path); fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY); @@ -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..ba3e47dd8d5c1 100644 --- a/src/libsystemd/sd-id128/sd-id128.c +++ b/src/libsystemd/sd-id128/sd-id128.c @@ -138,7 +138,7 @@ _public_ int sd_id128_get_machine(sd_id128_t *ret) { int id128_get_machine_at(int rfd, sd_id128_t *ret) { int r; - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); r = dir_fd_is_root_or_cwd(rfd); if (r < 0) @@ -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/test/test-id128.c b/src/libsystemd/sd-id128/test-id128.c similarity index 100% rename from src/test/test-id128.c rename to src/libsystemd/sd-id128/test-id128.c diff --git a/src/libsystemd/sd-journal/catalog.c b/src/libsystemd/sd-journal/catalog.c index 9ddbc11089a65..433288b4abbaf 100644 --- a/src/libsystemd/sd-journal/catalog.c +++ b/src/libsystemd/sd-journal/catalog.c @@ -28,30 +28,12 @@ #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 }; -#define CATALOG_SIGNATURE { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' } - -typedef struct CatalogHeader { - uint8_t signature[8]; /* "RHHHKSLP" */ - le32_t compatible_flags; - le32_t incompatible_flags; - le64_t header_size; - le64_t n_items; - le64_t catalog_item_size; -} CatalogHeader; - -typedef struct CatalogItem { - sd_id128_t id; - char language[32]; /* One byte is used for termination, so the maximum allowed - * length of the string is actually 31 bytes. */ - le64_t offset; -} CatalogItem; - static void catalog_hash_func(const CatalogItem *i, struct siphash *state) { assert(i); assert(state); @@ -451,7 +433,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, @@ -514,11 +496,12 @@ int catalog_update(const char *database, const char *root, const char* const *di return 0; } -static int open_mmap(const char *database, int *ret_fd, struct stat *ret_st, void **ret_map) { +static int open_mmap(const char *database, int *ret_fd, struct stat *ret_st, void **ret_map, uint64_t *ret_strings_offset) { assert(database); assert(ret_fd); assert(ret_st); assert(ret_map); + assert(ret_strings_offset); _cleanup_close_ int fd = open(database, O_RDONLY|O_CLOEXEC); if (fd < 0) @@ -536,12 +519,15 @@ static int open_mmap(const char *database, int *ret_fd, struct stat *ret_st, voi return -errno; const CatalogHeader *h = p; + uint64_t total; if (memcmp(h->signature, (const uint8_t[]) CATALOG_SIGNATURE, sizeof(h->signature)) != 0 || le64toh(h->header_size) < sizeof(CatalogHeader) || le64toh(h->catalog_item_size) < sizeof(CatalogItem) || h->incompatible_flags != 0 || le64toh(h->n_items) <= 0 || - st.st_size < (off_t) (le64toh(h->header_size) + le64toh(h->catalog_item_size) * le64toh(h->n_items))) { + !MUL_SAFE(&total, le64toh(h->catalog_item_size), le64toh(h->n_items)) || + !INC_SAFE(&total, le64toh(h->header_size)) || + (uint64_t) st.st_size < total) { munmap(p, st.st_size); return -EBADMSG; } @@ -549,10 +535,11 @@ static int open_mmap(const char *database, int *ret_fd, struct stat *ret_st, voi *ret_fd = TAKE_FD(fd); *ret_st = st; *ret_map = p; + *ret_strings_offset = total; /* start of the string store, already validated to fit in the file */ return 0; } -static const char* find_id(const void *p, sd_id128_t id) { +static const char* find_id(const void *p, uint64_t file_size, uint64_t strings_offset, sd_id128_t id) { CatalogItem key = { .id = id }; const CatalogItem *f = NULL; const CatalogHeader *h = ASSERT_PTR(p); @@ -602,10 +589,20 @@ static const char* find_id(const void *p, sd_id128_t id) { if (!f) return NULL; - return (const char*) p + - le64toh(h->header_size) + - le64toh(h->n_items) * le64toh(h->catalog_item_size) + - le64toh(f->offset); + /* f->offset is attacker-controlled in a hostile database; make sure the string start, plus a + * terminating NUL, stay inside the mapping before handing the pointer out for strlen()/strdup(). + * strings_offset (the start of the string store) was already bounded against the file in + * open_mmap(), so only f->offset needs to be added and checked here. */ + uint64_t off = strings_offset; + if (!INC_SAFE(&off, le64toh(f->offset)) || + off >= file_size) + return NULL; + + const char *s = (const char*) p + off; + if (!memchr(s, 0, file_size - off)) + return NULL; + + return s; } int catalog_get(const char *database, sd_id128_t id, char **ret_text) { @@ -617,11 +614,12 @@ int catalog_get(const char *database, sd_id128_t id, char **ret_text) { _cleanup_close_ int fd = -EBADF; struct stat st; void *p; - r = open_mmap(database, &fd, &st, &p); + uint64_t strings_offset; + r = open_mmap(database, &fd, &st, &p, &strings_offset); if (r < 0) return r; - const char *s = find_id(p, id); + const char *s = find_id(p, st.st_size, strings_offset, id); if (!s) r = -ENOENT; else @@ -678,7 +676,8 @@ int catalog_list(FILE *f, const char *database, bool oneline) { _cleanup_close_ int fd = -EBADF; struct stat st; void *p; - r = open_mmap(database, &fd, &st, &p); + uint64_t strings_offset; + r = open_mmap(database, &fd, &st, &p, &strings_offset); if (r < 0) return r; @@ -693,7 +692,12 @@ int catalog_list(FILE *f, const char *database, bool oneline) { if (last_id_set && sd_id128_equal(last_id, items[n].id)) continue; - assert_se(s = find_id(p, items[n].id)); + s = find_id(p, st.st_size, strings_offset, items[n].id); + if (!s) { + log_debug("Skipping catalog item " SD_ID128_FORMAT_STR " with out-of-bounds string offset.", + SD_ID128_FORMAT_VAL(items[n].id)); + continue; + } dump_catalog_entry(f, items[n].id, s, oneline); diff --git a/src/libsystemd/sd-journal/catalog.h b/src/libsystemd/sd-journal/catalog.h index 41dab41fbbd8d..1e5b3b5477d01 100644 --- a/src/libsystemd/sd-journal/catalog.h +++ b/src/libsystemd/sd-journal/catalog.h @@ -1,7 +1,28 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-id128.h" + #include "sd-forward.h" +#include "sparse-endian.h" + +#define CATALOG_SIGNATURE { 'R', 'H', 'H', 'H', 'K', 'S', 'L', 'P' } + +typedef struct CatalogHeader { + uint8_t signature[8]; /* "RHHHKSLP" */ + le32_t compatible_flags; + le32_t incompatible_flags; + le64_t header_size; + le64_t n_items; + le64_t catalog_item_size; +} CatalogHeader; + +typedef struct CatalogItem { + sd_id128_t id; + char language[32]; /* One byte is used for termination, so the maximum allowed + * length of the string is actually 31 bytes. */ + le64_t offset; +} CatalogItem; int catalog_import_file(OrderedHashmap **h, int fd, const char *path); int catalog_update(const char *database, const char *root, const char* const *dirs); diff --git a/src/libsystemd/sd-journal/journal-authenticate-internal.c b/src/libsystemd/sd-journal/journal-authenticate-internal.c new file mode 100644 index 0000000000000..acb4c221bc44b --- /dev/null +++ b/src/libsystemd/sd-journal/journal-authenticate-internal.c @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "journal-authenticate-internal.h" +#include "journal-file.h" + +static const JournalAuthOps *auth_ops = NULL; + +void journal_auth_set_ops(const JournalAuthOps *ops) { + assert(ops); + assert(!auth_ops || auth_ops == ops); + + auth_ops = ops; +} + +bool journal_auth_supported(void) { + return !!auth_ops; +} + +void journal_file_auth_done(JournalFile *f) { + assert(f); + + if (!auth_ops) + return; + + assert(auth_ops->free); + f->auth_context = auth_ops->free(f->auth_context); +} + +int journal_file_auth_load(JournalFile *f) { + assert(f); + + if (!auth_ops) + return -EOPNOTSUPP; + + if (f->auth_context) + return -EBUSY; + + assert(auth_ops->load); + return auth_ops->load(&f->auth_context); +} + +int journal_file_auth_load_key(JournalFile *f, const char *key) { + assert(f); + + if (!auth_ops) + return -EOPNOTSUPP; + + if (f->auth_context) + return -EBUSY; + + assert(auth_ops->load_key); + return auth_ops->load_key(&f->auth_context, key); +} + +int journal_file_auth_epoch_to_realtime_usec(JournalFile *f, uint64_t epoch, usec_t *ret_start, usec_t *ret_end) { + assert(f); + + if (!JOURNAL_HEADER_SEALED(f->header)) + return -EOPNOTSUPP; + + assert(auth_ops); + assert(auth_ops->epoch_to_realtime_usec); + return auth_ops->epoch_to_realtime_usec(f->auth_context, epoch, ret_start, ret_end); +} + +int journal_file_auth_next_evolve_usec(JournalFile *f, usec_t *ret) { + assert(f); + + if (!JOURNAL_HEADER_SEALED(f->header)) + return -EOPNOTSUPP; + + assert(auth_ops); + assert(auth_ops->next_evolve_usec); + return auth_ops->next_evolve_usec(f->auth_context, ret); +} + +int journal_file_auth_seek(JournalFile *f, uint64_t goal) { + assert(f); + + if (!JOURNAL_HEADER_SEALED(f->header)) + return 0; + + assert(auth_ops); + assert(auth_ops->seek); + return auth_ops->seek(f->auth_context, goal); +} + +int journal_file_auth_start(JournalFile *f) { + assert(f); + + if (!JOURNAL_HEADER_SEALED(f->header)) + return 0; + + assert(auth_ops); + assert(auth_ops->start); + return auth_ops->start(f->auth_context); +} + +int journal_file_auth_end(JournalFile *f, uint8_t ret[static TAG_LENGTH]) { + assert(f); + + if (!JOURNAL_HEADER_SEALED(f->header)) + return -EOPNOTSUPP; + + assert(auth_ops); + assert(auth_ops->end); + return auth_ops->end(f->auth_context, ret); +} + +int journal_file_auth_put_header(JournalFile *f) { + assert(f); + + if (!JOURNAL_HEADER_SEALED(f->header)) + return 0; + + assert(auth_ops); + assert(auth_ops->put_header); + return auth_ops->put_header(f->auth_context, f); +} + +int journal_file_auth_put_object(JournalFile *f, ObjectType type, Object *o, uint64_t p) { + assert(f); + + if (!JOURNAL_HEADER_SEALED(f->header)) + return 0; + + assert(auth_ops); + assert(auth_ops->put_object); + return auth_ops->put_object(f->auth_context, f, type, o, p); +} + +int journal_file_auth_append_tag(JournalFile *f) { + assert(f); + + if (!JOURNAL_HEADER_SEALED(f->header)) + return 0; + + if (!journal_file_writable(f)) + return 0; + + assert(auth_ops); + assert(auth_ops->append_tag); + return auth_ops->append_tag(f->auth_context, f); +} + +int journal_file_auth_append_tag_first(JournalFile *f) { + assert(f); + + if (!JOURNAL_HEADER_SEALED(f->header)) + return 0; + + if (!journal_file_writable(f)) + return 0; + + assert(auth_ops); + assert(auth_ops->append_tag_first); + return auth_ops->append_tag_first(f->auth_context, f); +} + +int journal_file_auth_append_tag_maybe(JournalFile *f, usec_t realtime) { + assert(f); + + if (!JOURNAL_HEADER_SEALED(f->header)) + return 0; + + if (!journal_file_writable(f)) + return 0; + + assert(auth_ops); + assert(auth_ops->append_tag_maybe); + return auth_ops->append_tag_maybe(f->auth_context, f, realtime); +} diff --git a/src/libsystemd/sd-journal/journal-authenticate-internal.h b/src/libsystemd/sd-journal/journal-authenticate-internal.h new file mode 100644 index 0000000000000..5d5d94be2028e --- /dev/null +++ b/src/libsystemd/sd-journal/journal-authenticate-internal.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "journal-def.h" +#include "sd-forward.h" + +typedef struct JournalAuthOps { + JournalAuthContext* (*free)(JournalAuthContext *c); + int (*load)(JournalAuthContext **ret); + int (*load_key)(JournalAuthContext **ret, const char *key); + int (*epoch_to_realtime_usec)(const JournalAuthContext *c, uint64_t epoch, usec_t *ret_start, usec_t *ret_end); + int (*next_evolve_usec)(const JournalAuthContext *c, usec_t *ret); + int (*seek)(JournalAuthContext *c, uint64_t goal); + int (*start)(JournalAuthContext *c); + int (*end)(JournalAuthContext *c, uint8_t ret[static TAG_LENGTH]); + int (*put_header)(JournalAuthContext *c, JournalFile *f); + int (*put_object)(JournalAuthContext *c, JournalFile *f, ObjectType type, Object *o, uint64_t p); + int (*append_tag)(JournalAuthContext *c, JournalFile *f); + int (*append_tag_first)(JournalAuthContext *c, JournalFile *f); + int (*append_tag_maybe)(JournalAuthContext *c, JournalFile *f, usec_t realtime); +} JournalAuthOps; + +void journal_auth_set_ops(const JournalAuthOps *ops); +bool journal_auth_supported(void); + +void journal_file_auth_done(JournalFile *f); +int journal_file_auth_load(JournalFile *f); +int journal_file_auth_load_key(JournalFile *f, const char *key); +int journal_file_auth_epoch_to_realtime_usec(JournalFile *f, uint64_t epoch, usec_t *ret_start, usec_t *ret_end); +int journal_file_auth_next_evolve_usec(JournalFile *f, usec_t *ret); +int journal_file_auth_seek(JournalFile *f, uint64_t goal); +int journal_file_auth_start(JournalFile *f); +int journal_file_auth_end(JournalFile *f, uint8_t ret[static TAG_LENGTH]); +int journal_file_auth_put_header(JournalFile *f); +int journal_file_auth_put_object(JournalFile *f, ObjectType type, Object *o, uint64_t p); +int journal_file_auth_append_tag(JournalFile *f); +int journal_file_auth_append_tag_first(JournalFile *f); +int journal_file_auth_append_tag_maybe(JournalFile *f, usec_t realtime); diff --git a/src/libsystemd/sd-journal/journal-authenticate.c b/src/libsystemd/sd-journal/journal-authenticate.c deleted file mode 100644 index 60a60f26f598b..0000000000000 --- a/src/libsystemd/sd-journal/journal-authenticate.c +++ /dev/null @@ -1,563 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fsprg.h" -#include "gcrypt-util.h" -#include "hexdecoct.h" -#include "journal-authenticate.h" -#include "journal-def.h" -#include "journal-file.h" -#include "log.h" -#include "memory-util.h" -#include "string-util.h" -#include "time-util.h" - -static void* fssheader_free(FSSHeader *p) { - /* mmap() returns MAP_FAILED on error and sets the errno */ - if (!p || p == MAP_FAILED) - return NULL; - - assert_se(munmap(p, PAGE_ALIGN(sizeof(FSSHeader))) >= 0); - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(FSSHeader*, fssheader_free); - -#if HAVE_GCRYPT -static uint64_t journal_file_tag_seqnum(JournalFile *f) { - uint64_t r; - - assert(f); - - r = le64toh(f->header->n_tags) + 1; - f->header->n_tags = htole64(r); - - return r; -} -#endif - -int journal_file_append_tag(JournalFile *f) { -#if HAVE_GCRYPT - Object *o; - uint64_t p; - int r; - - assert(f); - - if (!JOURNAL_HEADER_SEALED(f->header)) - return 0; - - if (!f->hmac_running) { - r = journal_file_hmac_start(f); - if (r < 0) - return r; - } - - assert(f->hmac); - - r = journal_file_append_object(f, OBJECT_TAG, sizeof(struct TagObject), &o, &p); - if (r < 0) - return r; - - o->tag.seqnum = htole64(journal_file_tag_seqnum(f)); - o->tag.epoch = htole64(FSPRG_GetEpoch(f->fsprg_state)); - - log_debug("Writing tag %"PRIu64" for epoch %"PRIu64"", - le64toh(o->tag.seqnum), - FSPRG_GetEpoch(f->fsprg_state)); - - /* Add the tag object itself, so that we can protect its - * header. This will exclude the actual hash value in it */ - r = journal_file_hmac_put_object(f, OBJECT_TAG, o, p); - if (r < 0) - return r; - - /* Get the HMAC tag and store it in the object */ - memcpy(o->tag.tag, sym_gcry_md_read(f->hmac, 0), TAG_LENGTH); - f->hmac_running = false; - - return 0; -#else - return -EOPNOTSUPP; -#endif -} - -int journal_file_hmac_start(JournalFile *f) { -#if HAVE_GCRYPT - uint8_t key[256 / 8]; /* Let's pass 256 bit from FSPRG to HMAC */ - gcry_error_t err; - int r; - - assert(f); - - if (!JOURNAL_HEADER_SEALED(f->header)) - return 0; - - if (f->hmac_running) - return 0; - - /* Prepare HMAC for next cycle */ - sym_gcry_md_reset(f->hmac); - - r = FSPRG_GetKey(f->fsprg_state, key, sizeof(key), 0); - if (r < 0) - return r; - - err = sym_gcry_md_setkey(f->hmac, key, sizeof(key)); - if (gcry_err_code(err) != GPG_ERR_NO_ERROR) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "sym_gcry_md_setkey() failed with error code: %s", - sym_gcry_strerror(err)); - - f->hmac_running = true; - - return 0; -#else - return -EOPNOTSUPP; -#endif -} - -static int journal_file_get_epoch(JournalFile *f, uint64_t realtime, uint64_t *epoch) { - uint64_t t; - - assert(f); - assert(epoch); - assert(JOURNAL_HEADER_SEALED(f->header)); - - if (f->fss_start_usec == 0 || f->fss_interval_usec == 0) - return -EOPNOTSUPP; - - if (realtime < f->fss_start_usec) - return -ESTALE; - - t = realtime - f->fss_start_usec; - t = t / f->fss_interval_usec; - - *epoch = t; - - return 0; -} - -static int journal_file_fsprg_need_evolve(JournalFile *f, uint64_t realtime) { - uint64_t goal, epoch; - int r; - - assert(f); - - if (!JOURNAL_HEADER_SEALED(f->header)) - return 0; - - r = journal_file_get_epoch(f, realtime, &goal); - if (r < 0) - return r; - - epoch = FSPRG_GetEpoch(f->fsprg_state); - if (epoch > goal) - return -ESTALE; - - return epoch != goal; -} - -int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime) { - uint64_t goal, epoch; - int r; - - assert(f); - - if (!JOURNAL_HEADER_SEALED(f->header)) - return 0; - - r = journal_file_get_epoch(f, realtime, &goal); - if (r < 0) - return r; - - epoch = FSPRG_GetEpoch(f->fsprg_state); - if (epoch < goal) - log_debug("Evolving FSPRG key from epoch %"PRIu64" to %"PRIu64".", epoch, goal); - - for (;;) { - if (epoch > goal) - return -ESTALE; - if (epoch == goal) - return 0; - - r = FSPRG_Evolve(f->fsprg_state); - if (r < 0) - return r; - - epoch = FSPRG_GetEpoch(f->fsprg_state); - if (epoch < goal) { - r = journal_file_append_tag(f); - if (r < 0) - return r; - } - } -} - -int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) { - void *msk; - uint64_t epoch; - int r; - - assert(f); - - if (!JOURNAL_HEADER_SEALED(f->header)) - return 0; - - assert(f->fsprg_seed); - - if (f->fsprg_state) { - /* Cheaper... */ - - epoch = FSPRG_GetEpoch(f->fsprg_state); - if (goal == epoch) - return 0; - - if (goal == epoch + 1) - return FSPRG_Evolve(f->fsprg_state); - } else { - f->fsprg_state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR); - f->fsprg_state = malloc(f->fsprg_state_size); - if (!f->fsprg_state) - return -ENOMEM; - } - - log_debug("Seeking FSPRG key to %"PRIu64".", goal); - - msk = alloca_safe(FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR)); - - r = FSPRG_GenMK(msk, NULL, f->fsprg_seed, f->fsprg_seed_size, FSPRG_RECOMMENDED_SECPAR); - if (r < 0) - return r; - - return FSPRG_Seek(f->fsprg_state, goal, msk, f->fsprg_seed, f->fsprg_seed_size); -} - -int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime) { - int r; - - assert(f); - - if (!JOURNAL_HEADER_SEALED(f->header)) - return 0; - - if (realtime <= 0) - realtime = now(CLOCK_REALTIME); - - r = journal_file_fsprg_need_evolve(f, realtime); - if (r <= 0) - return 0; - - r = journal_file_append_tag(f); - if (r < 0) - return r; - - r = journal_file_fsprg_evolve(f, realtime); - if (r < 0) - return r; - - return 0; -} - -int journal_file_hmac_put_object(JournalFile *f, ObjectType type, Object *o, uint64_t p) { -#if HAVE_GCRYPT - int r; - - assert(f); - - if (!JOURNAL_HEADER_SEALED(f->header)) - return 0; - - r = journal_file_hmac_start(f); - if (r < 0) - return r; - - if (!o) { - r = journal_file_move_to_object(f, type, p, &o); - if (r < 0) - return r; - } else if (type > OBJECT_UNUSED && o->object.type != type) - return -EBADMSG; - - sym_gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload)); - - switch (o->object.type) { - - case OBJECT_DATA: - /* All but hash and payload are mutable */ - sym_gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash)); - sym_gcry_md_write(f->hmac, journal_file_data_payload_field(f, o), le64toh(o->object.size) - journal_file_data_payload_offset(f)); - break; - - case OBJECT_FIELD: - /* Same here */ - sym_gcry_md_write(f->hmac, &o->field.hash, sizeof(o->field.hash)); - sym_gcry_md_write(f->hmac, o->field.payload, le64toh(o->object.size) - offsetof(Object, field.payload)); - break; - - case OBJECT_ENTRY: - /* All */ - sym_gcry_md_write(f->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(Object, entry.seqnum)); - break; - - case OBJECT_FIELD_HASH_TABLE: - case OBJECT_DATA_HASH_TABLE: - case OBJECT_ENTRY_ARRAY: - /* Nothing: everything is mutable */ - break; - - case OBJECT_TAG: - /* All but the tag itself */ - sym_gcry_md_write(f->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum)); - sym_gcry_md_write(f->hmac, &o->tag.epoch, sizeof(o->tag.epoch)); - break; - default: - return -EINVAL; - } - - return 0; -#else - return -EOPNOTSUPP; -#endif -} - -int journal_file_hmac_put_header(JournalFile *f) { -#if HAVE_GCRYPT - int r; - - assert(f); - - if (!JOURNAL_HEADER_SEALED(f->header)) - return 0; - - r = journal_file_hmac_start(f); - if (r < 0) - return r; - - /* All but state+reserved, boot_id, arena_size, - * tail_object_offset, n_objects, n_entries, - * tail_entry_seqnum, head_entry_seqnum, entry_array_offset, - * head_entry_realtime, tail_entry_realtime, - * tail_entry_monotonic, n_data, n_fields, n_tags, - * n_entry_arrays. */ - - sym_gcry_md_write(f->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature)); - sym_gcry_md_write(f->hmac, &f->header->file_id, offsetof(Header, tail_entry_boot_id) - offsetof(Header, file_id)); - sym_gcry_md_write(f->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id)); - sym_gcry_md_write(f->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset)); - - return 0; -#else - return -EOPNOTSUPP; -#endif -} - -int journal_file_fss_load(JournalFile *f) { - _cleanup_close_ int fd = -EBADF; - _cleanup_free_ char *path = NULL; - _cleanup_(fssheader_freep) FSSHeader *header = NULL; - struct stat st; - sd_id128_t machine; - int r; - - assert(f); - - /* This function is used to determine whether sealing should be enabled in the journal header so we - * can't check the header to check if sealing is enabled here. */ - - r = sd_id128_get_machine(&machine); - if (r < 0) - return r; - - if (asprintf(&path, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss", - SD_ID128_FORMAT_VAL(machine)) < 0) - return -ENOMEM; - - fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY, 0600); - if (fd < 0) { - if (errno != ENOENT) - log_error_errno(errno, "Failed to open %s: %m", path); - - return -errno; - } - - if (fstat(fd, &st) < 0) - return -errno; - - if (st.st_size < (off_t) sizeof(FSSHeader)) - return -ENODATA; - - header = mmap(NULL, PAGE_ALIGN(sizeof(FSSHeader)), PROT_READ, MAP_SHARED, fd, 0); - if (header == MAP_FAILED) - return -errno; - - if (memcmp(header->signature, FSS_HEADER_SIGNATURE, 8) != 0) - return -EBADMSG; - - if (header->incompatible_flags != 0) - return -EPROTONOSUPPORT; - - if (le64toh(header->header_size) < sizeof(FSSHeader)) - return -EBADMSG; - - if (le64toh(header->fsprg_state_size) != FSPRG_stateinbytes(le16toh(header->fsprg_secpar))) - return -EBADMSG; - - f->fss_file_size = le64toh(header->header_size) + le64toh(header->fsprg_state_size); - if ((uint64_t) st.st_size < f->fss_file_size) - return -ENODATA; - - if (!sd_id128_equal(machine, header->machine_id)) - return -EHOSTDOWN; - - if (le64toh(header->start_usec) <= 0 || le64toh(header->interval_usec) <= 0) - return -EBADMSG; - - size_t sz = PAGE_ALIGN(f->fss_file_size); - assert(sz < SIZE_MAX); - f->fss_file = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); - if (f->fss_file == MAP_FAILED) { - f->fss_file = NULL; - return -errno; - } - - f->fss_start_usec = le64toh(f->fss_file->start_usec); - f->fss_interval_usec = le64toh(f->fss_file->interval_usec); - - f->fsprg_state = (uint8_t*) f->fss_file + le64toh(f->fss_file->header_size); - f->fsprg_state_size = le64toh(f->fss_file->fsprg_state_size); - - return 0; -} - -int journal_file_hmac_setup(JournalFile *f) { -#if HAVE_GCRYPT - gcry_error_t e; - int r; - - if (!JOURNAL_HEADER_SEALED(f->header)) - return 0; - - r = initialize_libgcrypt(true); - if (r < 0) - return r; - - e = sym_gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC); - if (e != 0) - return -EOPNOTSUPP; - - return 0; -#else - return -EOPNOTSUPP; -#endif -} - -int journal_file_append_first_tag(JournalFile *f) { - uint64_t p; - int r; - - if (!JOURNAL_HEADER_SEALED(f->header)) - return 0; - - log_debug("Calculating first tag..."); - - r = journal_file_hmac_put_header(f); - if (r < 0) - return r; - - p = le64toh(f->header->field_hash_table_offset); - if (p < offsetof(Object, hash_table.items)) - return -EINVAL; - p -= offsetof(Object, hash_table.items); - - r = journal_file_hmac_put_object(f, OBJECT_FIELD_HASH_TABLE, NULL, p); - if (r < 0) - return r; - - p = le64toh(f->header->data_hash_table_offset); - if (p < offsetof(Object, hash_table.items)) - return -EINVAL; - p -= offsetof(Object, hash_table.items); - - r = journal_file_hmac_put_object(f, OBJECT_DATA_HASH_TABLE, NULL, p); - if (r < 0) - return r; - - r = journal_file_append_tag(f); - if (r < 0) - return r; - - return 0; -} - -int journal_file_parse_verification_key(JournalFile *f, const char *key) { - _cleanup_free_ uint8_t *seed = NULL; - size_t seed_size; - const char *k; - unsigned long long start, interval; - int r; - - assert(f); - assert(key); - - seed_size = FSPRG_RECOMMENDED_SEEDLEN; - seed = malloc(seed_size); - if (!seed) - return -ENOMEM; - - k = key; - for (size_t c = 0; c < seed_size; c++) { - int x, y; - - k = skip_leading_chars(k, "-"); - - x = unhexchar(*k); - if (x < 0) - return -EINVAL; - k++; - - y = unhexchar(*k); - if (y < 0) - return -EINVAL; - k++; - - seed[c] = (uint8_t) (x * 16 + y); - } - - if (*k != '/') - return -EINVAL; - k++; - - r = sscanf(k, "%llx-%llx", &start, &interval); - if (r != 2) - return -EINVAL; - - f->fsprg_seed = TAKE_PTR(seed); - f->fsprg_seed_size = seed_size; - - f->fss_start_usec = start * interval; - f->fss_interval_usec = interval; - - return 0; -} - -bool journal_file_next_evolve_usec(JournalFile *f, usec_t *u) { - uint64_t epoch; - - assert(f); - assert(u); - - if (!JOURNAL_HEADER_SEALED(f->header)) - return false; - - epoch = FSPRG_GetEpoch(f->fsprg_state); - - *u = (usec_t) (f->fss_start_usec + f->fss_interval_usec * epoch + f->fss_interval_usec); - - return true; -} diff --git a/src/libsystemd/sd-journal/journal-authenticate.h b/src/libsystemd/sd-journal/journal-authenticate.h deleted file mode 100644 index 89897fc0a7a12..0000000000000 --- a/src/libsystemd/sd-journal/journal-authenticate.h +++ /dev/null @@ -1,22 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "sd-forward.h" -#include "journal-file.h" - -int journal_file_append_tag(JournalFile *f); -int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime); -int journal_file_append_first_tag(JournalFile *f); - -int journal_file_hmac_setup(JournalFile *f); -int journal_file_hmac_start(JournalFile *f); -int journal_file_hmac_put_header(JournalFile *f); -int journal_file_hmac_put_object(JournalFile *f, ObjectType type, Object *o, uint64_t p); - -int journal_file_fss_load(JournalFile *f); -int journal_file_parse_verification_key(JournalFile *f, const char *key); - -int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime); -int journal_file_fsprg_seek(JournalFile *f, uint64_t goal); - -bool journal_file_next_evolve_usec(JournalFile *f, usec_t *u); diff --git a/src/libsystemd/sd-journal/journal-def.h b/src/libsystemd/sd-journal/journal-def.h index f20c9a357a195..a48554cee1d71 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: * @@ -28,6 +43,9 @@ typedef struct HashItem HashItem; typedef struct FSSHeader FSSHeader; +typedef struct JournalFile JournalFile; +typedef struct JournalAuthContext JournalAuthContext; + /* Object types */ typedef enum ObjectType { OBJECT_UNUSED, /* also serves as "any type" or "additional category" */ @@ -192,7 +210,8 @@ enum { HEADER_COMPATIBLE_TAIL_ENTRY_BOOT_ID | HEADER_COMPATIBLE_SEALED_CONTINUOUS, - HEADER_COMPATIBLE_SUPPORTED = (HAVE_GCRYPT ? HEADER_COMPATIBLE_SEALED | HEADER_COMPATIBLE_SEALED_CONTINUOUS : 0) | + HEADER_COMPATIBLE_SUPPORTED = HEADER_COMPATIBLE_SEALED | + HEADER_COMPATIBLE_SEALED_CONTINUOUS | HEADER_COMPATIBLE_TAIL_ENTRY_BOOT_ID, }; @@ -246,7 +265,7 @@ assert_cc(sizeof(struct Header) == sizeof(struct Header__packed)); assert_cc(sizeof(struct Header) == 272); #define FSS_HEADER_SIGNATURE \ - ((const char[]) { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' }) + { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' } struct FSSHeader { uint8_t signature[8]; /* "KSHHRHLP" */ diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index de5d7075e0cca..41e06086d209e 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -19,10 +19,9 @@ #include "fd-util.h" #include "format-util.h" #include "fs-util.h" -#include "gcrypt-util.h" #include "hashmap.h" #include "id128-util.h" -#include "journal-authenticate.h" +#include "journal-authenticate-internal.h" #include "journal-def.h" #include "journal-file.h" #include "journal-internal.h" @@ -307,17 +306,7 @@ JournalFile* journal_file_close(JournalFile *f) { free(f->compress_buffer); #endif - if (f->fss_file) { - size_t sz = PAGE_ALIGN(f->fss_file_size); - assert(sz < SIZE_MAX); - munmap(f->fss_file, sz); - } else - free(f->fsprg_state); - - free(f->fsprg_seed); - - if (f->hmac) - sym_gcry_md_close(f->hmac); + journal_file_auth_done(f); return mfree(f); } @@ -370,7 +359,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; @@ -403,16 +392,13 @@ static int journal_file_init_header( JournalFileFlags file_flags, JournalFile *template) { - bool seal = false; ssize_t k; int r; assert(f); -#if HAVE_GCRYPT /* Try to load the FSPRG state, and if we can't, then just don't do sealing */ - seal = FLAGS_SET(file_flags, JOURNAL_SEAL) && journal_file_fss_load(f) >= 0; -#endif + bool seal = FLAGS_SET(file_flags, JOURNAL_SEAL) && journal_file_auth_load(f) >= 0; Header h = { .header_size = htole64(ALIGN64(sizeof(h))), @@ -471,14 +457,19 @@ static int journal_file_refresh_header(JournalFile *f) { } static bool warn_wrong_flags(const JournalFile *f, bool compatible) { - const uint32_t any = compatible ? HEADER_COMPATIBLE_ANY : HEADER_INCOMPATIBLE_ANY, - supported = compatible ? HEADER_COMPATIBLE_SUPPORTED : HEADER_INCOMPATIBLE_SUPPORTED; + const uint32_t any = compatible ? HEADER_COMPATIBLE_ANY : HEADER_INCOMPATIBLE_ANY; + uint32_t supported = compatible ? HEADER_COMPATIBLE_SUPPORTED : HEADER_INCOMPATIBLE_SUPPORTED; const char *type = compatible ? "compatible" : "incompatible"; uint32_t flags; assert(f); assert(f->header); + /* When sealing is not supported, refuse to write to an already sealed journal file, but still allow + * reading sealed journal files. */ + if (compatible && journal_file_writable(f) && !journal_auth_supported()) + supported &= ~(HEADER_COMPATIBLE_SEALED | HEADER_COMPATIBLE_SEALED_CONTINUOUS); + flags = le32toh(compatible ? f->header->compatible_flags : f->header->incompatible_flags); if (flags & ~supported) { @@ -590,10 +581,39 @@ static int journal_file_verify_header(JournalFile *f) { arena_size = le64toh(READ_NOW(f->header->arena_size)); - if (UINT64_MAX - header_size < arena_size || header_size + arena_size > (uint64_t) f->last_stat.st_size) + if (UINT64_MAX - header_size < arena_size) return -ENODATA; + uint64_t file_size = (uint64_t) f->last_stat.st_size; + + /* Probably an unclean shutdown where the header was written, but the arena data was not. On write we + * should ask the caller to rotate, but on read, we can still work it out with bounds checks. */ + bool truncated = false; + if (header_size + arena_size > file_size) { + if (journal_file_writable(f)) + return -ENODATA; + + /* This shouldn't happen given file_size is page aligned via fallocate(), but just in case + * things are _really_ messed up... */ + uint64_t available = ALIGN_DOWN_U64(file_size, sizeof(uint64_t)); + if (header_size > available || available - header_size < offsetof(ObjectHeader, payload)) + return -ENODATA; + + log_debug("Journal file %s claims a %" PRIu64 " byte arena but is only %" PRIu64 + " bytes on disk, clamping for recovery.", + f->path, + arena_size, + file_size); + arena_size = available - header_size; + truncated = true; + } + uint64_t tail_object_offset = le64toh(f->header->tail_object_offset); + if (truncated) + /* The tail may be in the lost region, so cap it at the last possible object header start. */ + tail_object_offset = MIN( + tail_object_offset, + header_size + arena_size - offsetof(ObjectHeader, payload)); if (!offset_is_valid(tail_object_offset, header_size, UINT64_MAX)) return -ENODATA; if (header_size + arena_size < tail_object_offset) @@ -615,7 +635,7 @@ static int journal_file_verify_header(JournalFile *f) { if (!offset_is_valid(entry_array_offset, header_size, tail_object_offset)) return -ENODATA; - if (JOURNAL_HEADER_CONTAINS(f->header, tail_entry_array_offset)) { + if (!truncated && JOURNAL_HEADER_CONTAINS(f->header, tail_entry_array_offset)) { uint32_t offset = le32toh(f->header->tail_entry_array_offset); uint32_t n = le32toh(f->header->tail_entry_array_n_entries); @@ -632,7 +652,7 @@ static int journal_file_verify_header(JournalFile *f) { return -ENODATA; } - if (JOURNAL_HEADER_CONTAINS(f->header, tail_entry_offset)) { + if (!truncated && JOURNAL_HEADER_CONTAINS(f->header, tail_entry_offset)) { uint64_t offset = le64toh(f->header->tail_entry_offset); if (!offset_is_valid(offset, header_size, tail_object_offset)) @@ -664,7 +684,7 @@ static int journal_file_verify_header(JournalFile *f) { /* Verify number of objects */ uint64_t n_objects = le64toh(f->header->n_objects); - if (n_objects > arena_size / offsetof(ObjectHeader, payload)) + if (!truncated && n_objects > arena_size / offsetof(ObjectHeader, payload)) return -ENODATA; uint64_t n_entries = le64toh(f->header->n_entries); @@ -1301,7 +1321,7 @@ static int journal_file_setup_data_hash_table(JournalFile *f) { if (r < 0) return r; - memzero(o->hash_table.items, s); + memzero(o->hash_table.items, s * sizeof(HashItem)); f->header->data_hash_table_offset = htole64(p + offsetof(Object, hash_table.items)); f->header->data_hash_table_size = htole64(s * sizeof(HashItem)); @@ -1517,6 +1537,18 @@ static int get_next_hash_offset( return 0; } +static bool chain_tail_lost(JournalFile *f, int r, uint64_t offset, ObjectType type) { + assert(f); + + /* Only accept truncation when reading. On writing it's not possible to know how to safely proceed. */ + if (journal_file_writable(f) || !IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) + return false; + + log_debug_errno(r, "Failed to read %s at offset %" PRIu64 " of %s, treating it as the end of the chain: %m", + journal_object_type_to_string(type), offset, f->path); + return true; +} + int journal_file_find_field_object_with_hash( JournalFile *f, const void *field, @@ -1554,6 +1586,8 @@ int journal_file_find_field_object_with_hash( Object *o; r = journal_file_move_to_object(f, OBJECT_FIELD, p, &o); + if (chain_tail_lost(f, r, p, OBJECT_FIELD)) + break; if (r < 0) return r; @@ -1655,6 +1689,8 @@ int journal_file_find_data_object_with_hash( size_t rsize; r = journal_file_move_to_object(f, OBJECT_DATA, p, &o); + if (chain_tail_lost(f, r, p, OBJECT_DATA)) + break; if (r < 0) return r; @@ -1787,11 +1823,9 @@ static int journal_file_append_field( /* The linking might have altered the window, so let's only pass the offset to hmac which will * move to the object again if needed. */ -#if HAVE_GCRYPT - r = journal_file_hmac_put_object(f, OBJECT_FIELD, NULL, p); + r = journal_file_auth_put_object(f, OBJECT_FIELD, NULL, p); if (r < 0) return r; -#endif if (ret_object) { r = journal_file_move_to_object(f, OBJECT_FIELD, p, ret_object); @@ -1902,11 +1936,9 @@ static int journal_file_append_data( if (r < 0) return r; -#if HAVE_GCRYPT - r = journal_file_hmac_put_object(f, OBJECT_DATA, o, p); + r = journal_file_auth_put_object(f, OBJECT_DATA, o, p); if (r < 0) return r; -#endif /* Create field object ... */ r = journal_file_append_field(f, data, (uint8_t*) eq - (uint8_t*) data, &fo, NULL); @@ -1915,7 +1947,7 @@ static int journal_file_append_data( /* ... and link it in. */ o->data.next_field_offset = fo->field.head_data_offset; - fo->field.head_data_offset = le64toh(p); + fo->field.head_data_offset = htole64(p); if (ret_object) *ret_object = o; @@ -1965,9 +1997,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 +2143,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); @@ -2146,11 +2184,9 @@ static int link_entry_into_array( if (r < 0) return r; -#if HAVE_GCRYPT - r = journal_file_hmac_put_object(f, OBJECT_ENTRY_ARRAY, o, q); + r = journal_file_auth_put_object(f, OBJECT_ENTRY_ARRAY, o, q); if (r < 0) return r; -#endif write_entry_array_item(f, o, i, p); @@ -2392,11 +2428,9 @@ static int journal_file_append_entry_internal( for (size_t i = 0; i < n_items; i++) write_entry_item(f, o, i, &items[i]); -#if HAVE_GCRYPT - r = journal_file_hmac_put_object(f, OBJECT_ENTRY, o, np); + r = journal_file_auth_put_object(f, OBJECT_ENTRY, o, np); if (r < 0) return r; -#endif r = journal_file_link_entry(f, o, np, items, n_items); if (r < 0) @@ -2581,11 +2615,9 @@ int journal_file_append_entry( else machine_id = &_machine_id; -#if HAVE_GCRYPT - r = journal_file_maybe_append_tag(f, ts->realtime); + r = journal_file_auth_append_tag_maybe(f, ts->realtime); if (r < 0) return r; -#endif if (n_iovec < ALLOCA_MAX / sizeof(EntryItem) / 2) items = newa(EntryItem, n_iovec); @@ -3064,6 +3096,10 @@ static int generic_array_bisect( uint64_t left, right, k, m, m_original; r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array); + if (chain_tail_lost(f, r, a, OBJECT_ENTRY_ARRAY)) { + r = TEST_GOTO_PREVIOUS; + goto previous; + } if (r < 0) return r; @@ -4058,6 +4094,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; } @@ -4226,13 +4264,11 @@ int journal_file_open( goto fail; } -#if HAVE_GCRYPT if (!newly_created && journal_file_writable(f) && JOURNAL_HEADER_SEALED(f->header)) { - r = journal_file_fss_load(f); + r = journal_file_auth_load(f); if (r < 0) goto fail; } -#endif if (journal_file_writable(f)) { if (metrics) { @@ -4246,12 +4282,6 @@ int journal_file_open( goto fail; } -#if HAVE_GCRYPT - r = journal_file_hmac_setup(f); - if (r < 0) - goto fail; -#endif - if (newly_created) { r = journal_file_setup_field_hash_table(f); if (r < 0) @@ -4261,11 +4291,9 @@ int journal_file_open( if (r < 0) goto fail; -#if HAVE_GCRYPT - r = journal_file_append_first_tag(f); + r = journal_file_auth_append_tag_first(f); if (r < 0) goto fail; -#endif } if (mmap_cache_fd_got_sigbus(f->cache_fd)) { @@ -4568,10 +4596,17 @@ int journal_file_get_cutoff_realtime_usec(JournalFile *f, usec_t *ret_from, usec } if (ret_to) { - if (f->header->tail_entry_realtime == 0) + Object *o; + int r; + + /* The header may be stale on unclean shutdown, so don't trust it. */ + r = journal_file_next_entry(f, 0, DIRECTION_UP, &o, NULL); + if (r < 0) + return r; + if (r == 0) return -ENOENT; - *ret_to = le64toh(f->header->tail_entry_realtime); + *ret_to = le64toh(o->entry.realtime); } return 1; diff --git a/src/libsystemd/sd-journal/journal-file.h b/src/libsystemd/sd-journal/journal-file.h index b3a620200ae81..8499fa2b662f9 100644 --- a/src/libsystemd/sd-journal/journal-file.h +++ b/src/libsystemd/sd-journal/journal-file.h @@ -6,7 +6,6 @@ #include "compress.h" #include "sd-forward.h" -#include "gcrypt-util.h" #include "journal-def.h" #include "mmap-cache.h" #include "sparse-endian.h" @@ -98,20 +97,7 @@ typedef struct JournalFile { void *compress_buffer; #endif - gcry_md_hd_t hmac; - bool hmac_running; - - FSSHeader *fss_file; - size_t fss_file_size; - - uint64_t fss_start_usec; - uint64_t fss_interval_usec; - - void *fsprg_state; - size_t fsprg_state_size; - - void *fsprg_seed; - size_t fsprg_seed_size; + JournalAuthContext *auth_context; /* When we insert this file into the per-boot priority queue 'newest_by_boot_id' in sd_journal, then by these keys */ sd_id128_t newest_boot_id; 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..dd3b6f32d49e9 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -9,8 +9,7 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" -#include "gcrypt-util.h" -#include "journal-authenticate.h" +#include "journal-authenticate-internal.h" #include "journal-def.h" #include "journal-file.h" #include "journal-verify.h" @@ -23,6 +22,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 +125,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)); @@ -812,12 +813,14 @@ static int verify_hash_table( int journal_file_verify( JournalFile *f, const char *key, - usec_t *first_contained, usec_t *last_validated, usec_t *last_contained, + usec_t *ret_first_contained, + usec_t *ret_last_validated, + usec_t *ret_last_contained, bool show_progress) { + int r; Object *o; - uint64_t p = 0, last_epoch = 0, last_tag_realtime = 0; - + uint64_t p = 0, last_tag = 0, last_epoch = 0, last_tag_realtime = 0, last_tag_realtime_end = 0; uint64_t entry_seqnum = 0, entry_monotonic = 0, entry_realtime = 0; usec_t min_entry_realtime = USEC_INFINITY, max_entry_realtime = 0; sd_id128_t entry_boot_id = {}; /* Unnecessary initialization to appease gcc */ @@ -832,23 +835,20 @@ int journal_file_verify( const char *tmp_dir = NULL; MMapCache *m; -#if HAVE_GCRYPT - uint64_t last_tag = 0; -#endif assert(f); if (key) { -#if HAVE_GCRYPT - r = journal_file_parse_verification_key(f, key); - if (r < 0) { - log_error("Failed to parse seed."); - return r; - } -#else - return -EOPNOTSUPP; -#endif - } else if (JOURNAL_HEADER_SEALED(f->header)) - return -ENOKEY; + r = journal_file_auth_load_key(f, key); + if (r < 0) + return log_error_errno(r, "Failed to load verification key: %m"); + } else if (JOURNAL_HEADER_SEALED(f->header)) { + /* For a sealed journal file, request the verification key when journal sealing is supported. + * Otherwise, log that seal verification is skipped. */ + if (journal_auth_supported()) + return -ENOKEY; + else + log_notice("Journal file is sealed, but journal sealing support is disabled. Skipping seal verification."); + } r = var_tmp_dir(&tmp_dir); if (r < 0) { @@ -1153,19 +1153,20 @@ int journal_file_verify( } } -#if HAVE_GCRYPT - if (JOURNAL_HEADER_SEALED(f->header)) { + if (JOURNAL_HEADER_SEALED(f->header) && journal_auth_supported()) { uint64_t q, rt, rt_end; debug(p, "Checking tag %"PRIu64"...", le64toh(o->tag.seqnum)); - rt = f->fss_start_usec + le64toh(o->tag.epoch) * f->fss_interval_usec; - rt_end = usec_add(rt, f->fss_interval_usec); + r = journal_file_auth_epoch_to_realtime_usec(f, le64toh(o->tag.epoch), &rt, &rt_end); + if (r < 0) + goto fail; + if (entry_realtime_set && entry_realtime >= rt_end) { error(p, "tag/entry realtime timestamp out of synchronization (%"PRIu64" >= %"PRIu64")", entry_realtime, - rt + f->fss_interval_usec); + rt_end); r = -EBADMSG; goto fail; } @@ -1190,16 +1191,16 @@ int journal_file_verify( /* OK, now we know the epoch. So let's now set * it, and calculate the HMAC for everything * since the last tag. */ - r = journal_file_fsprg_seek(f, le64toh(o->tag.epoch)); + r = journal_file_auth_seek(f, le64toh(o->tag.epoch)); if (r < 0) goto fail; - r = journal_file_hmac_start(f); + r = journal_file_auth_start(f); if (r < 0) goto fail; if (last_tag == 0) { - r = journal_file_hmac_put_header(f); + r = journal_file_auth_put_header(f); if (r < 0) goto fail; @@ -1212,7 +1213,7 @@ int journal_file_verify( if (r < 0) goto fail; - r = journal_file_hmac_put_object(f, OBJECT_UNUSED, o, q); + r = journal_file_auth_put_object(f, OBJECT_UNUSED, o, q); if (r < 0) goto fail; @@ -1224,19 +1225,24 @@ int journal_file_verify( if (r < 0) goto fail; - if (memcmp(o->tag.tag, sym_gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) { + uint8_t tag[TAG_LENGTH]; + CLEANUP_ERASE(tag); + + r = journal_file_auth_end(f, tag); + if (r < 0) + goto fail; + + if (memcmp(o->tag.tag, tag, TAG_LENGTH) != 0) { error(p, "Tag failed verification"); r = -EBADMSG; goto fail; } - f->hmac_running = false; last_tag_realtime = rt; + last_tag_realtime_end = rt_end; } last_tag = p + ALIGN64(le64toh(o->object.size)); -#endif - last_epoch = le64toh(o->tag.epoch); n_tags++; @@ -1402,14 +1408,12 @@ int journal_file_verify( mmap_cache_fd_free(cache_entry_fd); mmap_cache_fd_free(cache_entry_array_fd); - if (first_contained) - *first_contained = le64toh(f->header->head_entry_realtime); -#if HAVE_GCRYPT - if (last_validated) - *last_validated = last_tag_realtime + f->fss_interval_usec; -#endif - if (last_contained) - *last_contained = le64toh(f->header->tail_entry_realtime); + if (ret_first_contained) + *ret_first_contained = le64toh(f->header->head_entry_realtime); + if (ret_last_validated) + *ret_last_validated = last_tag_realtime_end; + if (ret_last_contained) + *ret_last_contained = le64toh(f->header->tail_entry_realtime); return 0; diff --git a/src/libsystemd/sd-journal/journal-verify.h b/src/libsystemd/sd-journal/journal-verify.h index 579033069b56c..46d323df83d8e 100644 --- a/src/libsystemd/sd-journal/journal-verify.h +++ b/src/libsystemd/sd-journal/journal-verify.h @@ -3,4 +3,10 @@ #include "journal-file.h" -int journal_file_verify(JournalFile *f, const char *key, usec_t *first_contained, usec_t *last_validated, usec_t *last_contained, bool show_progress); +int journal_file_verify( + JournalFile *f, + const char *key, + usec_t *ret_first_contained, + usec_t *ret_last_validated, + usec_t *ret_last_contained, + bool show_progress); diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c index befa1945176f3..01bca7e82a19e 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; } @@ -3164,7 +3162,7 @@ _public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) { if (r < 0) return r; - /* Server might have done some vacuuming while we weren't watching. Get rid of the deleted + /* journald might have done some vacuuming while we weren't watching. Get rid of the deleted * files now so they don't stay around indefinitely. */ ORDERED_HASHMAP_FOREACH(f, j->files) { r = journal_file_fstat(f); diff --git a/src/libsystemd/sd-journal/test-catalog.c b/src/libsystemd/sd-journal/test-catalog.c index 51e113b3fc61b..c4b63319d3d6b 100644 --- a/src/libsystemd/sd-journal/test-catalog.c +++ b/src/libsystemd/sd-journal/test-catalog.c @@ -10,8 +10,10 @@ #include "fd-util.h" #include "hashmap.h" #include "log.h" +#include "memstream-util.h" #include "tests.h" #include "tmpfile-util.h" +#include "unaligned.h" static char** catalog_dirs = NULL; static const char *no_catalog_dirs[] = { @@ -27,11 +29,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 +41,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 +69,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 +104,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 +135,116 @@ 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_OK_ZERO(catalog_file_lang("systemd..catalog", &lang2)); + ASSERT_NULL(lang2); + + ASSERT_EQ(catalog_file_lang("systemd.fr.catalog", &lang2), 1); + ASSERT_STREQ(lang2, "fr"); - assert_se(catalog_file_lang("systemd..catalog", &lang2) == 0); - assert_se(lang2 == NULL); + ASSERT_OK_ZERO(catalog_file_lang("systemd.fr.catalog.gz", &lang3)); + ASSERT_NULL(lang3); - assert_se(catalog_file_lang("systemd.fr.catalog", &lang2) == 1); - assert_se(streq(lang2, "fr")); + ASSERT_OK_ZERO(catalog_file_lang("systemd.01234567890123456789012345678901.catalog", &lang3)); + ASSERT_NULL(lang3); - assert_se(catalog_file_lang("systemd.fr.catalog.gz", &lang3) == 0); - assert_se(lang3 == NULL); + ASSERT_EQ(catalog_file_lang("systemd.0123456789012345678901234567890.catalog", &lang3), 1); + ASSERT_STREQ(lang3, "0123456789012345678901234567890"); - assert_se(catalog_file_lang("systemd.01234567890123456789012345678901.catalog", &lang3) == 0); - assert_se(lang3 == NULL); + ASSERT_OK_ZERO(catalog_file_lang("/x/y/systemd.catalog", &lang4)); + ASSERT_NULL(lang4); + + ASSERT_EQ(catalog_file_lang("/x/y/systemd.ru_RU.catalog", &lang4), 1); + ASSERT_STREQ(lang4, "ru_RU"); +} - assert_se(catalog_file_lang("systemd.0123456789012345678901234567890.catalog", &lang3) == 1); - assert_se(streq(lang3, "0123456789012345678901234567890")); +static void test_catalog_oob_offset_one(uint64_t item_offset, size_t strings_size) { + /* Builds a hostile single-item catalog database and verifies the reader rejects it instead of + * chasing the item's string offset out of the mapping. The blob is laid out from the real struct + * offsets so it keeps matching open_mmap() if CatalogHeader/CatalogItem ever change. */ + _cleanup_(unlink_tempfilep) char db[] = "/tmp/test-catalog.XXXXXX"; + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *text = NULL; + _cleanup_free_ uint8_t *blob = NULL; + size_t blob_size = sizeof(CatalogHeader) + sizeof(CatalogItem) + strings_size; + + ASSERT_NOT_NULL(blob = new0(uint8_t, blob_size)); + uint8_t *item = blob + sizeof(CatalogHeader); + + memcpy(blob + offsetof(CatalogHeader, signature), + (const uint8_t[]) CATALOG_SIGNATURE, sizeof_field(CatalogHeader, signature)); + unaligned_write_le64(blob + offsetof(CatalogHeader, header_size), sizeof(CatalogHeader)); + unaligned_write_le64(blob + offsetof(CatalogHeader, n_items), 1); + unaligned_write_le64(blob + offsetof(CatalogHeader, catalog_item_size), sizeof(CatalogItem)); + + memset(item + offsetof(CatalogItem, id), 0x42, sizeof_field(CatalogItem, id)); + /* item language left zero so the C-locale lookup matches */ + unaligned_write_le64(item + offsetof(CatalogItem, offset), item_offset); + + /* Any trailing string store is filled with non-NUL bytes, so an in-bounds offset still has no + * terminator before EOF. */ + memset(blob + sizeof(CatalogHeader) + sizeof(CatalogItem), 0x41, strings_size); + + ASSERT_OK(fd = mkostemp_safe(db)); + ASSERT_OK_EQ_ERRNO(write(fd, blob, blob_size), (ssize_t) blob_size); + + sd_id128_t id; + memset(&id, 0x42, sizeof(id)); + + ASSERT_ERROR(catalog_get(db, id, &text), ENOENT); + ASSERT_NULL(text); + + /* Listing the same database must walk every item without dereferencing the bad offset, and the + * corrupt item must be skipped rather than emitted. Capture the output and assert its id is absent + * so a regressed guard is caught here and not only under a sanitizer. */ + bool oneline; + FOREACH_ARGUMENT(oneline, true, false) { + _cleanup_(memstream_done) MemStream m = {}; + _cleanup_free_ char *out = NULL; + FILE *f; + + ASSERT_NOT_NULL(f = memstream_init(&m)); + ASSERT_OK(catalog_list(f, db, oneline)); + ASSERT_OK(memstream_finalize(&m, &out, NULL)); + ASSERT_NULL(strstr(out, SD_ID128_TO_STRING(id))); + } +} - assert_se(catalog_file_lang("/x/y/systemd.catalog", &lang4) == 0); - assert_se(lang4 == NULL); +static void test_catalog_oob_offset(void) { + /* Offset lands far past EOF: rejected by the bounds check. */ + test_catalog_oob_offset_one(/* item_offset= */ UINT64_C(0x4000000000), /* strings_size= */ 0); - assert_se(catalog_file_lang("/x/y/systemd.ru_RU.catalog", &lang4) == 1); - assert_se(streq(lang4, "ru_RU")); + /* Offset is in bounds but its string runs to EOF with no terminator: rejected by memchr(). */ + test_catalog_oob_offset_one(/* item_offset= */ 0, /* strings_size= */ 16); } 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,28 +254,28 @@ 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(); + test_catalog_oob_offset(); + test_catalog_import_invalid(); test_catalog_import_badid(); test_catalog_import_one(); 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..c71240660dcd7 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,80 @@ 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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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..df5605f1bcbd9 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)); @@ -564,7 +566,7 @@ static void test_sequence_numbers_one(void) { if (sd_id128_get_machine(NULL) >= 0) { two = journal_file_offline_close(two); - /* restart server */ + /* emulate a system restart */ seqnum = 0; ASSERT_OK(journal_file_open(-EBADF, "two.journal", O_RDWR, JOURNAL_COMPRESS, 0, @@ -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..6a1401764bd3a 100644 --- a/src/libsystemd/sd-journal/test-journal-verify.c +++ b/src/libsystemd/sd-journal/test-journal-verify.c @@ -9,6 +9,7 @@ #include "chattr-util.h" #include "fd-util.h" #include "iovec-util.h" +#include "journal-authenticate.h" #include "journal-file-util.h" #include "journal-verify.h" #include "log.h" @@ -22,19 +23,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 +41,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 +73,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 +81,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 +97,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 +105,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 +116,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 +133,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 +146,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 +166,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; } @@ -180,6 +175,8 @@ int main(int argc, char *argv[]) { const char *verification_key = NULL; int max_iterations = 512; + journal_auth_init(); + if (argc > 1) { /* Don't limit the number of iterations when the verification key * is provided on the command line, we want to do that only in CIs */ @@ -187,25 +184,23 @@ 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 - /* If we're running without any arguments and we're compiled with gcrypt + /* If we're running without any arguments and journal sealing support is enabled, * check the journal verification stuff with a valid key as well */ - if (argc <= 1) { + if (argc <= 1 && journal_auth_supported()) { 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 return 0; } diff --git a/src/libsystemd/sd-journal/test-journal.c b/src/libsystemd/sd-journal/test-journal.c index fb0a02d9bfbbf..645636d0d03b6 100644 --- a/src/libsystemd/sd-journal/test-journal.c +++ b/src/libsystemd/sd-journal/test-journal.c @@ -11,14 +11,15 @@ #include "journal-vacuum.h" #include "log.h" #include "rm-rf.h" +#include "stdio-util.h" #include "tests.h" #include "time-util.h" 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 +37,69 @@ 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)); + + journal_file_auth_append_tag(f); -#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,37 +185,33 @@ 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)); + + journal_file_auth_append_tag(f); -#if HAVE_GCRYPT - journal_file_append_tag(f); -#endif journal_file_dump(f); /* We have to partially reimplement some of the dump logic, because the normal next_entry does the * 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 +226,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,30 +239,348 @@ 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 +typedef struct EntryArrayCut { + uint64_t offset; + uint64_t last_surviving_seqnum; +} EntryArrayCut; + +static EntryArrayCut find_entry_array_cut(JournalFile *f, uint64_t first) { + uint64_t a, n = 0, last_surviving_seqnum = 0; + Object *o, *entry; + + for (a = first; a > 0;) { + ASSERT_OK_ZERO(journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &o)); + n++; + + uint64_t next = le64toh(o->entry_array.next_entry_array_offset); + if (next == 0) + break; + + uint64_t k = journal_file_entry_array_n_items(f, o); + ASSERT_GT(k, UINT64_C(0)); + + ASSERT_OK_ZERO(journal_file_move_to_object( + f, + OBJECT_ENTRY, + journal_file_entry_array_item(f, o, k - 1), + &entry)); + last_surviving_seqnum = le64toh(entry->entry.seqnum); + a = next; + } + + /* We need at least two arrays, so that lopping off the final one still leaves a readable prefix. */ + ASSERT_GE(n, UINT64_C(2)); + ASSERT_GT(last_surviving_seqnum, UINT64_C(0)); + + return (EntryArrayCut) { + .offset = a, + .last_surviving_seqnum = last_surviving_seqnum, + }; +} + +static void test_recover_truncated_linear_one(bool zeroed_tail) { + _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; + dual_timestamp ts; + JournalFile *f; + Object *o; + EntryArrayCut cut; + uint64_t p, file_size, c; + usec_t from, to, last_realtime = 0; + char t[] = "/var/tmp/journal-XXXXXX"; + + /* When a journal's header records more arena than reached disk, make sure reads recover the on disk + * prefix. */ + + ASSERT_NOT_NULL(m = mmap_cache_new()); + mkdtemp_chdir_chattr(t); + + ASSERT_OK_ZERO(journal_file_open( + -EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, + /* metrics= */ NULL, m, /* template= */ NULL, &f)); + dual_timestamp_now(&ts); + + for (unsigned i = 0; i < 200; i++) { + struct iovec iovec = IOVEC_MAKE_STRING("LINE=x"); + ts.realtime = i + 1; + ASSERT_OK_ZERO(journal_file_append_entry( + f, &ts, /* boot_id= */ NULL, &iovec, 1, + /* seqnum= */ NULL, /* seqnum_id= */ NULL, + /* ret_object= */ NULL, /* ret_offset= */ NULL)); + } + + cut = find_entry_array_cut(f, le64toh(f->header->entry_array_offset)); + file_size = (uint64_t) f->last_stat.st_size; + ASSERT_GT(file_size, cut.offset); + (void) journal_file_offline_close(f); + + /* Turn the last entry to crowfood. */ + ASSERT_OK_ERRNO(truncate("test.journal", (int64_t) cut.offset)); + if (zeroed_tail) + ASSERT_OK_ERRNO(truncate("test.journal", (int64_t) file_size)); + + ASSERT_OK_ZERO(journal_file_open( + -EBADF, "test.journal", O_RDONLY, JOURNAL_COMPRESS, 0666, UINT64_MAX, + /* metrics= */ NULL, m, /* template= */ NULL, &f)); + + c = 0; + for (p = 0; journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) > 0;) { + c++; + ASSERT_EQ(le64toh(o->entry.seqnum), c); + last_realtime = le64toh(o->entry.realtime); + } + ASSERT_EQ(c, cut.last_surviving_seqnum); + ASSERT_LT(c, UINT64_C(200)); /* We did not recover the tail that was cut. */ + + ASSERT_EQ(journal_file_get_cutoff_realtime_usec(f, &from, &to), 1); + ASSERT_EQ(from, UINT64_C(1)); + ASSERT_EQ(to, last_realtime); + + /* Direct access to the zeroed region must fail. */ + if (zeroed_tail) + ASSERT_ERROR(journal_file_move_to_object(f, OBJECT_UNUSED, cut.offset, &o), EBADMSG); + + (void) journal_file_close(f); + + /* A file with arena past EOF can't be written to safely. However, if the tail was allocated but + * zeroed, the header is self consistent, so writable opens are accepted (and are handled by + * STATE_ONLINE instead). */ + if (zeroed_tail) { + struct iovec iovec = IOVEC_MAKE_STRING("LINE=new"); + + ASSERT_OK_ZERO(journal_file_open( + -EBADF, "test.journal", O_RDWR, JOURNAL_COMPRESS, 0666, UINT64_MAX, + /* metrics= */ NULL, m, /* template= */ NULL, &f)); + + /* The header is consistent, but when we write we should find the real tail and fail. */ + ASSERT_ERROR(journal_file_append_entry( + f, &ts, /* boot_id= */ NULL, &iovec, 1, + /* seqnum= */ NULL, /* seqnum_id= */ NULL, + /* ret_object= */ NULL, /* ret_offset= */ NULL), EBADMSG); + + (void) journal_file_offline_close(f); + } else + ASSERT_ERROR(journal_file_open( + -EBADF, "test.journal", O_RDWR, JOURNAL_COMPRESS, 0666, UINT64_MAX, + /* metrics= */ NULL, m, /* template= */ NULL, &f), ENODATA); + + if (arg_keep) + log_info("Not removing %s", t); + else + ASSERT_OK(rm_rf(t, REMOVE_ROOT | REMOVE_PHYSICAL)); +} + +TEST(recover_truncated_linear) { + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); + test_recover_truncated_linear_one(/* zeroed_tail= */ false); + test_recover_truncated_linear_one(/* zeroed_tail= */ true); + + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); + test_recover_truncated_linear_one(/* zeroed_tail= */ false); + test_recover_truncated_linear_one(/* zeroed_tail= */ true); +} + +static void test_recover_truncated_indexed_one(bool zeroed_tail) { + _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; + dual_timestamp ts; + JournalFile *f; + Object *o, *d; + EntryArrayCut cut; + uint64_t file_size; + static const char field[] = "FOO=bar"; + char t[] = "/var/tmp/journal-XXXXXX"; + + /* The same vague idea as above with the truncation, but this time it's the bisection case since the + * per-data entry array is missing. */ + + ASSERT_NOT_NULL(m = mmap_cache_new()); + mkdtemp_chdir_chattr(t); + + ASSERT_OK_ZERO(journal_file_open( + -EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, + /* metrics= */ NULL, m, /* template= */ NULL, &f)); + dual_timestamp_now(&ts); + + for (unsigned i = 0; i < 200; i++) { + struct iovec iovec = IOVEC_MAKE_STRING(field); + ASSERT_OK_ZERO(journal_file_append_entry( + f, &ts, /* boot_id= */ NULL, &iovec, 1, + /* seqnum= */ NULL, /* seqnum_id= */ NULL, + /* ret_object= */ NULL, /* ret_offset= */ NULL)); + } + + ASSERT_EQ(journal_file_find_data_object(f, field, strlen(field), &d, /* ret_offset= */ NULL), 1); + cut = find_entry_array_cut(f, le64toh(d->data.entry_array_offset)); + file_size = (uint64_t) f->last_stat.st_size; + ASSERT_GT(file_size, cut.offset); + (void) journal_file_offline_close(f); + + /* Remove the final per-data array object. The data object's n_entries now overcounts the per-data + * arrays on disk */ + ASSERT_OK_ERRNO(truncate("test.journal", (int64_t) cut.offset)); + if (zeroed_tail) + ASSERT_OK_ERRNO(truncate("test.journal", (int64_t) file_size)); + + ASSERT_OK_ZERO(journal_file_open( + -EBADF, "test.journal", O_RDONLY, JOURNAL_COMPRESS, 0666, UINT64_MAX, + /* metrics= */ NULL, m, /* template= */ NULL, &f)); + ASSERT_EQ(journal_file_find_data_object(f, field, strlen(field), &d, /* ret_offset= */ NULL), 1); + + /* Seeking up for the largest possible seqnum walks into the missing tail array, and must fall back + * to the last entry that actually survived rather than failing the query. */ + ASSERT_EQ(journal_file_move_to_entry_by_seqnum_for_data( + f, d, UINT64_MAX, DIRECTION_UP, &o, /* ret_offset= */ NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), cut.last_surviving_seqnum); + ASSERT_LT(le64toh(o->entry.seqnum), UINT64_C(200)); + + /* Seeking down past everything that survived also walks into the missing array, and must report no + * match rather than propagating the read error to the caller. */ + ASSERT_OK_ZERO(journal_file_move_to_entry_by_seqnum_for_data( + f, d, UINT64_MAX, DIRECTION_DOWN, &o, /* ret_offset= */ NULL)); + + /* A seqnum that only existed in the lost region must degrade to no match, not a false hit (on an + * intact file this seqnum would have matched). */ + ASSERT_OK_ZERO(journal_file_move_to_entry_by_seqnum_for_data( + f, d, cut.last_surviving_seqnum + 1, DIRECTION_DOWN, &o, /* ret_offset= */ NULL)); + + /* The head of the chain is intact, so a downward seek from the start still finds the first entry. */ + ASSERT_EQ(journal_file_move_to_entry_by_seqnum_for_data( + f, d, 0, DIRECTION_DOWN, &o, /* ret_offset= */ NULL), 1); + ASSERT_EQ(le64toh(o->entry.seqnum), UINT64_C(1)); + + (void) journal_file_close(f); + + if (arg_keep) + log_info("Not removing %s", t); + else + ASSERT_OK(rm_rf(t, REMOVE_ROOT | REMOVE_PHYSICAL)); +} + +TEST(recover_truncated_indexed) { + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1)); + test_recover_truncated_indexed_one(/* zeroed_tail= */ false); + test_recover_truncated_indexed_one(/* zeroed_tail= */ true); + + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1)); + test_recover_truncated_indexed_one(/* zeroed_tail= */ false); + test_recover_truncated_indexed_one(/* zeroed_tail= */ true); +} + +static void test_recover_truncated_hash_chain_one(bool field, bool zeroed_tail) { + _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; + dual_timestamp ts; + JournalFile *f; + uint64_t lost_offset, file_size, buckets, bucket; + char lost_key[64], lost_value[64], t[] = "/var/tmp/journal-XXXXXX"; + + /* A lookup must tolerate a hash bucket whose tail node was lost to truncation, returning the + * surviving prefix instead of failing. The lost value is chosen to share a bucket with the surviving + * one, so it is chained behind the surviving head rather than heading its own bucket. */ + + const char *survives_value = field ? "FIELD0=x" : "FOO=survives"; + const char *survives_key = field ? "FIELD0" : "FOO=survives"; + + ASSERT_NOT_NULL(m = mmap_cache_new()); + mkdtemp_chdir_chattr(t); + + ASSERT_OK_ZERO(journal_file_open( + -EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, + /* metrics= */ NULL, m, /* template= */ NULL, &f)); + dual_timestamp_now(&ts); + + /* journal_file_hash_data() is keyed per-file, so compute the colliding value against the open file. */ + buckets = le64toh(field ? f->header->field_hash_table_size : f->header->data_hash_table_size) / sizeof(HashItem); + bucket = journal_file_hash_data(f, survives_key, strlen(survives_key)) % buckets; + for (uint64_t i = 1;; i++) { + ASSERT_LT(i, UINT64_C(1000000)); + if (field) + xsprintf(lost_key, "FIELD%" PRIu64, i); + else + xsprintf(lost_key, "FOO=%" PRIu64, i); + if (journal_file_hash_data(f, lost_key, strlen(lost_key)) % buckets == bucket) + break; + } + if (field) + xsprintf(lost_value, "%s=x", lost_key); + else + strcpy(lost_value, lost_key); + + const char *v; + FOREACH_ARGUMENT(v, survives_value, lost_value) { + struct iovec iovec = IOVEC_MAKE_STRING(v); + ASSERT_OK_ZERO(journal_file_append_entry( + f, &ts, /* boot_id= */ NULL, &iovec, 1, + /* seqnum= */ NULL, /* seqnum_id= */ NULL, + /* ret_object= */ NULL, /* ret_offset= */ NULL)); + } + + ASSERT_EQ(field ? + journal_file_find_field_object(f, lost_key, strlen(lost_key), NULL, &lost_offset) : + journal_file_find_data_object(f, lost_key, strlen(lost_key), NULL, &lost_offset), 1); + file_size = (uint64_t) f->last_stat.st_size; + ASSERT_GT(file_size, lost_offset); + (void) journal_file_offline_close(f); + + /* Lose the second object's body, but keep the bucket pointer that still references it. */ + ASSERT_OK_ERRNO(truncate("test.journal", (int64_t) lost_offset)); + if (zeroed_tail) + ASSERT_OK_ERRNO(truncate("test.journal", (int64_t) file_size)); + + ASSERT_OK_ZERO(journal_file_open( + -EBADF, "test.journal", O_RDONLY, JOURNAL_COMPRESS, 0666, UINT64_MAX, + /* metrics= */ NULL, m, /* template= */ NULL, &f)); + + ASSERT_EQ(field ? + journal_file_find_field_object(f, survives_key, strlen(survives_key), NULL, NULL) : + journal_file_find_data_object(f, survives_key, strlen(survives_key), NULL, NULL), 1); + ASSERT_OK_ZERO(field ? + journal_file_find_field_object(f, lost_key, strlen(lost_key), NULL, NULL) : + journal_file_find_data_object(f, lost_key, strlen(lost_key), NULL, NULL)); + + (void) journal_file_close(f); + + if (arg_keep) + log_info("Not removing %s", t); + else + ASSERT_OK(rm_rf(t, REMOVE_ROOT | REMOVE_PHYSICAL)); +} + +TEST(recover_truncated_hash_chain) { + const char *compact; + + FOREACH_ARGUMENT(compact, "0", "1") { + ASSERT_OK_ERRNO(setenv("SYSTEMD_JOURNAL_COMPACT", compact, 1)); + + test_recover_truncated_hash_chain_one(/* field= */ false, /* zeroed_tail= */ false); + test_recover_truncated_hash_chain_one(/* field= */ false, /* zeroed_tail= */ true); + test_recover_truncated_hash_chain_one(/* field= */ true, /* zeroed_tail= */ false); + test_recover_truncated_hash_chain_one(/* field= */ true, /* zeroed_tail= */ true); + } +} + static int intro(void) { arg_keep = saved_argc > 1; @@ -276,6 +588,8 @@ static int intro(void) { if (access("/etc/machine-id", F_OK) != 0) return log_tests_skipped("/etc/machine-id not found"); + journal_auth_init(); + return EXIT_SUCCESS; } 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..88473b81929a7 --- /dev/null +++ b/src/libsystemd/sd-json/json-stream.c @@ -0,0 +1,1396 @@ +/* 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 = RET_NERRNO(recvmsg(s->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC)); + if (n >= 0 && FLAGS_SET(mh.msg_flags, MSG_TRUNC)) { + cmsg_close_all(&mh); + return -EXFULL; + } + if (n >= 0 && FLAGS_SET(mh.msg_flags, MSG_CTRUNC)) { + /* SCM_RIGHTS got truncated — typically because an LSM (e.g. SELinux) + * denied the fd transfer. Drop the partial fds and continue with the + * data bytes: the request handler will surface a clean error to the + * peer when it tries to peek the missing fd, instead of us tearing + * the connection down silently and leaving the caller waiting. */ + json_stream_log(s, "SCM_RIGHTS truncated on inbound message, dropping received file descriptors."); + cmsg_close_all(&mh); + mh.msg_controllen = 0; + } + } 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..193ee714d0ac5 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,17 +190,119 @@ 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; +} + +int json_dispatch_in_addr_data(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + struct in_addr_data *ret = ASSERT_PTR(userdata), data = { .family = AF_UNSPEC, .address = IN_ADDR_NULL }; + int r; + + assert(variant); + + if (sd_json_variant_is_null(variant)) { + *ret = data; + return 0; + } + + /* We support both a more human readable string based encoding and an array based encoding */ + if (sd_json_variant_is_string(variant)) { + r = in_addr_from_string_auto(sd_json_variant_string(variant), &data.family, &data.address); + if (r < 0) + return json_log(variant, flags, r, + "JSON field '%s' is not a valid IPv4 or IPv6 address string: %s", strna(name), sd_json_variant_string(variant)); + *ret = data; + return 0; + } + + if (!sd_json_variant_is_array(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); + + switch (sd_json_variant_elements(variant)) { + case sizeof(struct in_addr): + data.family = AF_INET; + break; + case sizeof(struct in6_addr): + data.family = AF_INET6; + break; + default: + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name)); + } + + size_t k = 0; + sd_json_variant *i; + JSON_VARIANT_ARRAY_FOREACH(i, variant) { + if (!sd_json_variant_is_integer(i)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an integer.", k, strna(name)); + + int64_t b = sd_json_variant_integer(i); + if (b < 0 || b > 0xff) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), + "Element %zu of JSON field '%s' is out of range 0%s255.", + k, strna(name), glyph(GLYPH_ELLIPSIS)); + + data.address.bytes[k++] = (uint8_t) b; + } + assert(k == FAMILY_ADDRESS_SIZE_SAFE(data.family)); + + *ret = data; + return 0; +} + int json_dispatch_const_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { const char **p = ASSERT_PTR(userdata), *path; @@ -243,6 +346,7 @@ int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispa int json_dispatch_strv_path(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { _cleanup_strv_free_ char **n = NULL; char ***l = ASSERT_PTR(userdata); + size_t s = 0; int r; assert(variant); @@ -262,7 +366,7 @@ int json_dispatch_strv_path(const char *name, sd_json_variant *variant, sd_json_ if (r < 0) return r; - r = strv_extend(&n, a); + r = strv_extend_with_size(&n, &s, a); if (r < 0) return json_log_oom(variant, flags); } @@ -604,6 +708,9 @@ int json_dispatch_strv_environment(const char *name, sd_json_variant *variant, s if (!sd_json_variant_is_array(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); + if (sd_json_variant_elements(variant) > ENVIRONMENT_ASSIGNMENTS_MAX) + return json_log(variant, flags, SYNTHETIC_ERRNO(E2BIG), "Too many environment variable assignments."); + sd_json_variant *i; JSON_VARIANT_ARRAY_FOREACH(i, variant) { const char *e; @@ -744,6 +851,27 @@ int json_dispatch_access_mode(const char *name, sd_json_variant *variant, sd_jso return 0; } +int json_dispatch_job_id(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + uint32_t *id = ASSERT_PTR(userdata); + uint32_t k; + int r; + + if (sd_json_variant_is_null(variant)) { + *id = 0; + return 0; + } + + r = sd_json_dispatch_uint32(name, variant, flags, &k); + if (r < 0) + return r; + + if (k == 0) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid job ID.", strna(name)); + + *id = k; + return 0; +} + int json_variant_compare(sd_json_variant *a, sd_json_variant *b) { int r; @@ -845,3 +973,27 @@ int json_variant_compare(sd_json_variant *a, sd_json_variant *b) { return CMP(sd_json_variant_type(a), sd_json_variant_type(b)); } + +int json_dispatch_address_family(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + int *family = ASSERT_PTR(userdata), r; + + assert(variant); + + if (sd_json_variant_is_negative(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), + "JSON field '%s' for an address family cannot be negative.", strna(name)); + + int k = AF_UNSPEC; + if (!sd_json_variant_is_null(variant)) { + r = sd_json_dispatch_int(name, variant, flags, &k); + if (r < 0) + return r; + } + + if (!IN_SET(k, AF_INET, AF_INET6) && !(FLAGS_SET(flags, SD_JSON_RELAX) && k == AF_UNSPEC)) + return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), + "JSON field '%s' out of range for an address family.", strna(name)); + + *family = k; + return 0; +} diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index 847725a41e292..2032566fdd0c3 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -9,14 +9,17 @@ #include "sd-forward.h" #include "string-util.h" /* IWYU pragma: keep */ -#define JSON_VARIANT_REPLACE(v, q) \ - do { \ - typeof(v)* _v = &(v); \ - typeof(q) _q = (q); \ +#define JSON_VARIANT_REPLACE(v, q) \ + do { \ + typeof(v)* _v = &(v); \ + typeof(q) _q = (q); \ sd_json_variant_unref(*_v); \ - *_v = _q; \ + *_v = _q; \ } while(false) +#define json_variant_unref_and_replace(a, b) \ + free_and_replace_full(a, b, sd_json_variant_unref) + static inline int json_variant_set_field_non_null(sd_json_variant **v, const char *field, sd_json_variant *value) { return value && !sd_json_variant_is_null(value) ? sd_json_variant_set_field(v, field, value) : 0; } @@ -115,6 +118,8 @@ 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_in_addr_data(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); @@ -128,6 +133,8 @@ int json_dispatch_ifindex(const char *name, sd_json_variant *variant, sd_json_di int json_dispatch_log_level(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_strv_environment(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_access_mode(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); +int json_dispatch_job_id(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); +int json_dispatch_address_family(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); static inline int json_variant_unbase64_iovec(sd_json_variant *v, struct iovec *ret) { return sd_json_variant_unbase64(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL); @@ -168,7 +175,9 @@ enum { _JSON_BUILD_PAIR_UNSIGNED_NON_ZERO, _JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL, _JSON_BUILD_PAIR_FINITE_USEC, + _JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO, _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, @@ -217,7 +226,9 @@ enum { #define JSON_BUILD_PAIR_UNSIGNED_NON_ZERO(name, u) _JSON_BUILD_PAIR_UNSIGNED_NON_ZERO, (const char*) { name }, (uint64_t) { u } #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_FINITE_USEC_NON_ZERO(name, u) _JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO, (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,12 +269,16 @@ 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_ENUM_NON_EMPTY(name, s) JSON_BUILD_PAIR_STRING_NON_EMPTY_UNDERSCORIFY(name, 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) \ SD_JSON_BUILD_PAIR_CONDITION(condition, name, SD_JSON_BUILD_UNSIGNED(value)) #define JSON_BUILD_PAIR_CONDITION_BOOLEAN(condition, name, value) \ SD_JSON_BUILD_PAIR_CONDITION(condition, name, SD_JSON_BUILD_BOOLEAN(value)) +#define JSON_BUILD_PAIR_CONDITION_STRING(condition, name, value) \ + SD_JSON_BUILD_PAIR_CONDITION(condition, name, SD_JSON_BUILD_STRING(value)) #define JSON_BUILD_PAIR_CONDITION_STRV(condition, name, value) \ SD_JSON_BUILD_PAIR_CONDITION(condition, name, SD_JSON_BUILD_STRV(value)) diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 647b56555a7bb..531429b51f32d 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -12,6 +12,7 @@ #include "errno-util.h" #include "escape.h" #include "ether-addr-util.h" +#include "fd-util.h" #include "fileio.h" #include "float.h" #include "hexdecoct.h" @@ -1003,7 +1004,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 +1941,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 +1950,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; @@ -1972,7 +1975,7 @@ _public_ int sd_json_variant_filter(sd_json_variant **v, char **to_remove) { return r; json_variant_propagate_sensitive(*v, w); - JSON_VARIANT_REPLACE(*v, TAKE_PTR(w)); + json_variant_unref_and_replace(*v, w); return (int) n; } @@ -2041,7 +2044,7 @@ _public_ int sd_json_variant_set_field(sd_json_variant **v, const char *field, s return r; json_variant_propagate_sensitive(*v, w); - JSON_VARIANT_REPLACE(*v, TAKE_PTR(w)); + json_variant_unref_and_replace(*v, w); return 1; } @@ -2181,7 +2184,7 @@ _public_ int sd_json_variant_merge_object(sd_json_variant **v, sd_json_variant * json_variant_propagate_sensitive(*v, w); json_variant_propagate_sensitive(m, w); - JSON_VARIANT_REPLACE(*v, TAKE_PTR(w)); + json_variant_unref_and_replace(*v, w); return 1; } @@ -2260,9 +2263,7 @@ _public_ int sd_json_variant_append_array(sd_json_variant **v, sd_json_variant * } json_variant_propagate_sensitive(*v, nv); - JSON_VARIANT_REPLACE(*v, TAKE_PTR(nv)); - - return 0; + return json_variant_unref_and_replace(*v, nv); } _public_ int sd_json_variant_append_arrayb(sd_json_variant **v, ...) { @@ -2509,7 +2510,7 @@ static int json_variant_set_source(sd_json_variant **v, JsonSource *source, unsi w->line = line; w->column = column; - JSON_VARIANT_REPLACE(*v, w); + json_variant_unref_and_replace(*v, w); return 1; } @@ -2761,21 +2762,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,19 +2789,23 @@ 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; if (is_real) { - ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * exp10((exponent_negative ? -1.0 : 1.0) * exponent); + /* Clamp before casting to int — a JSON input with an absurdly large exponent could + * otherwise trigger undefined behaviour in the double→int conversion. xexp10i() + * itself saturates anything beyond ~10^308, so clamping at INT_MAX is harmless. */ + int e = exponent > (double) INT_MAX ? INT_MAX : (int) exponent; + ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * xexp10i(exponent_negative ? -e : e); return JSON_TOKEN_REAL; } else if (negative) { ret->integer = i; @@ -2904,7 +2909,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 +3044,6 @@ static int json_parse_internal( int r; assert_return(input, -EINVAL); - assert_return(ret, -EINVAL); p = *input; @@ -3111,12 +3115,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 +3182,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 +3242,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 +3270,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 +3296,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 +3322,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 +3348,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 +3374,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 +3420,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; @@ -3451,9 +3507,12 @@ _public_ int sd_json_parse_file_at( _cleanup_free_ char *text = NULL; int r; - if (f) + if (f) { + if (FLAGS_SET(flags, SD_JSON_PARSE_SEEK0) && fseek(f, /* offset= */ 0, SEEK_SET) < 0) + return -errno; + r = read_full_stream(f, &text, NULL); - else + } else r = read_full_file_full(dir_fd, path, UINT64_MAX, SIZE_MAX, 0, NULL, &text, NULL); if (r < 0) return r; @@ -3472,6 +3531,44 @@ _public_ int sd_json_parse_file( return sd_json_parse_file_at(f, AT_FDCWD, path, flags, ret, reterr_line, reterr_column); } +_public_ int sd_json_parse_fd( + const char *path, + int fd, + sd_json_parse_flags_t flags, + sd_json_variant **ret, + unsigned *reterr_line, + unsigned *reterr_column) { + + int r; + + assert_return(fd >= 0, -EBADF); + + _cleanup_close_ int our_fd = -EBADF; + if (FLAGS_SET(flags, SD_JSON_PARSE_REOPEN_FD)) { + assert_return(!FLAGS_SET(flags, SD_JSON_PARSE_DONATE_FD), -EINVAL); + + our_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC); + if (our_fd < 0) + return our_fd; + + /* If we reopened the thing the file offset will be at the beginning anyway */ + flags &= ~SD_JSON_PARSE_SEEK0; + } else if (FLAGS_SET(flags, SD_JSON_PARSE_DONATE_FD)) + our_fd = fd; + else { + our_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (our_fd < 0) + return -errno; + } + + _cleanup_fclose_ FILE *f = NULL; + r = take_fdopen_unlocked(&our_fd, "r", &f); + if (r < 0) + return r; + + return sd_json_parse_file(f, path, flags, ret, reterr_line, reterr_column); +} + char *json_underscorify(char *p) { if (!p) return NULL; @@ -3542,7 +3639,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 +4279,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 @@ -4492,6 +4589,7 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { break; } + case _JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO: case _JSON_BUILD_PAIR_FINITE_USEC: { const char *n; usec_t u; @@ -4504,7 +4602,9 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { n = va_arg(ap, const char *); u = va_arg(ap, usec_t); - if (u != USEC_INFINITY && current->n_suppress == 0) { + if (u != USEC_INFINITY && + (command != _JSON_BUILD_PAIR_FINITE_USEC_NON_ZERO || u > 0) && + current->n_suppress == 0) { r = sd_json_variant_new_string(&add, n); if (r < 0) goto finish; @@ -4520,7 +4620,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 +4637,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 +4915,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 +4945,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; } @@ -5231,10 +5341,8 @@ _public_ int sd_json_dispatch_full( done++; } else { - if (flags & SD_JSON_ALLOW_EXTENSIONS) { - json_log(value, flags|SD_JSON_DEBUG, 0, "Unrecognized object field '%s', assuming extension.", sd_json_variant_string(key)); + if (flags & SD_JSON_ALLOW_EXTENSIONS) continue; - } json_log(value, flags, 0, "Unexpected object field '%s'.", sd_json_variant_string(key)); if (flags & SD_JSON_PERMISSIVE) @@ -5251,7 +5359,7 @@ _public_ int sd_json_dispatch_full( for (const sd_json_dispatch_field *p = table; p && p->name; p++) { sd_json_dispatch_flags_t merged_flags = p->flags | flags; - if ((merged_flags & SD_JSON_MANDATORY) && !found[p-table]) { + if ((p->flags & SD_JSON_MANDATORY) && !found[p-table]) { json_log(v, merged_flags, 0, "Missing object field '%s'.", p->name); if ((merged_flags & SD_JSON_PERMISSIVE)) @@ -5589,7 +5697,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); @@ -5600,6 +5708,7 @@ _public_ int sd_json_dispatch_strv(const char *name, sd_json_variant *variant, s _cleanup_strv_free_ char **l = NULL; char ***s = userdata; sd_json_variant *e; + size_t n = 0; int r; assert_return(variant, -EINVAL); @@ -5612,7 +5721,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,10 +5739,10 @@ _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)); + r = strv_extend_with_size(&l, &n, sd_json_variant_string(e)); if (r < 0) return json_log(e, flags, r, "Failed to append array element: %m"); } @@ -5793,7 +5902,7 @@ _public_ int sd_json_variant_sort(sd_json_variant **v) { if (!n->sorted) /* Check if this worked. This will fail if there are multiple identical keys used. */ return -ENOTUNIQ; - JSON_VARIANT_REPLACE(*v, TAKE_PTR(n)); + json_variant_unref_and_replace(*v, n); return 1; } @@ -5848,7 +5957,7 @@ _public_ int sd_json_variant_normalize(sd_json_variant **v) { goto finish; } - JSON_VARIANT_REPLACE(*v, TAKE_PTR(n)); + json_variant_unref_and_replace(*v, n); r = 1; diff --git a/src/test/test-json.c b/src/libsystemd/sd-json/test-json.c similarity index 67% rename from src/test/test-json.c rename to src/libsystemd/sd-json/test-json.c index 679152fd955a8..6992d095c666f 100644 --- a/src/test/test-json.c +++ b/src/libsystemd/sd-json/test-json.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include #include @@ -12,6 +13,8 @@ #include "fd-util.h" #include "format-util.h" #include "fileio.h" +#include "in-addr-util.h" +#include "io-util.h" #include "iovec-util.h" #include "json-internal.h" #include "json-util.h" @@ -59,8 +62,8 @@ static void test_tokenizer_one(const char *data, ...) { d = va_arg(ap, double); - assert_se(fabs(d - v.real) < 1e-10 || - fabs((d - v.real) / v.real) < 1e-10); + assert_se(ABS(d - v.real) < 1e-10 || + ABS((d - v.real) / v.real) < 1e-10); } else if (t == JSON_TOKEN_INTEGER) { int64_t i; @@ -241,7 +244,7 @@ static void test_2(sd_json_variant *v) { /* has thisisaverylongproperty */ p = sd_json_variant_by_key(v, "thisisaverylongproperty"); - assert_se(p && sd_json_variant_type(p) == SD_JSON_VARIANT_REAL && fabs(sd_json_variant_real(p) - 1.27) < 0.001); + assert_se(p && sd_json_variant_type(p) == SD_JSON_VARIANT_REAL && ABS(sd_json_variant_real(p) - 1.27) < 0.001); } static void test_zeroes(sd_json_variant *v) { @@ -517,6 +520,68 @@ TEST(source) { printf("--- pretty end ---\n"); } +TEST(parse_fd) { + static const char data[] = "{ \"foo\" : \"bar\", \"baz\" : 4711 }"; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_close_ int fd = -EBADF; + + ASSERT_OK(fd = open_tmpfile_unlinkable(NULL, O_RDWR)); + ASSERT_OK(loop_write(fd, data, strlen(data))); + + /* By default the fd is internally duplicated, the caller's fd stays open and the JSON text is + * read starting at the current file offset. */ + ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_SET)); + ASSERT_OK(sd_json_parse_fd("tmpfile", fd, /* flags= */ 0, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_OK(fd_validate(fd)); /* still open, we only got a duplicate */ + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(v, "foo")), "bar"); + ASSERT_EQ(sd_json_variant_unsigned(sd_json_variant_by_key(v, "baz")), UINT64_C(4711)); + v = sd_json_variant_unref(v); + + /* Without SD_JSON_PARSE_SEEK0 and with the offset left at EOF there is nothing to read. */ + ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_END)); + ASSERT_ERROR(sd_json_parse_fd("tmpfile", fd, /* flags= */ 0, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL), ENODATA); + ASSERT_NULL(v); + ASSERT_OK(fd_validate(fd)); + + /* SD_JSON_PARSE_SEEK0 rewinds to the beginning first, so the stale offset no longer matters. */ + ASSERT_OK(sd_json_parse_fd("tmpfile", fd, SD_JSON_PARSE_SEEK0, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_OK(fd_validate(fd)); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(v, "foo")), "bar"); + v = sd_json_variant_unref(v); + + /* SD_JSON_PARSE_REOPEN_FD reopens the fd internally (starting at offset 0), the caller's fd and + * its offset are left untouched. */ + ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_END)); + ASSERT_OK(sd_json_parse_fd("tmpfile", fd, SD_JSON_PARSE_REOPEN_FD, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_OK(fd_validate(fd)); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(v, "foo")), "bar"); + v = sd_json_variant_unref(v); + + /* SD_JSON_PARSE_REOPEN_FD and SD_JSON_PARSE_DONATE_FD are mutually exclusive. */ + ASSERT_RETURN_EXPECTED_SE(sd_json_parse_fd("tmpfile", fd, SD_JSON_PARSE_REOPEN_FD|SD_JSON_PARSE_DONATE_FD, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL) == -EINVAL); + ASSERT_OK(fd_validate(fd)); /* not consumed on the -EINVAL path */ + + /* SD_JSON_PARSE_DONATE_FD passes ownership into the call: the fd is consumed and closed even on + * success. */ + ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_SET)); + ASSERT_OK(sd_json_parse_fd("tmpfile", fd, SD_JSON_PARSE_DONATE_FD, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL)); + ASSERT_ERROR(fd_validate(fd), EBADF); + TAKE_FD(fd); /* already closed by the call, don't double-close */ + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(v, "foo")), "bar"); + v = sd_json_variant_unref(v); + + /* SD_JSON_PARSE_DONATE_FD also consumes the fd when parsing fails. */ + _cleanup_close_ int fd2 = -EBADF; + ASSERT_OK(fd2 = open_tmpfile_unlinkable(NULL, O_RDWR)); + ASSERT_OK(loop_write(fd2, "kookoo", strlen("kookoo"))); + ASSERT_OK_ERRNO(lseek(fd2, 0, SEEK_SET)); + ASSERT_ERROR(sd_json_parse_fd("tmpfile", fd2, SD_JSON_PARSE_DONATE_FD, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL), EINVAL); + ASSERT_ERROR(fd_validate(fd2), EBADF); + TAKE_FD(fd2); + ASSERT_NULL(v); +} + TEST(depth) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; int r; @@ -547,14 +612,56 @@ TEST(depth) { assert_se(r >= 0); - sd_json_variant_unref(v); - v = TAKE_PTR(w); + json_variant_unref_and_replace(v, w); } sd_json_variant_dump(v, 0, stdout, NULL); 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,13 +753,15 @@ 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(fabs(1.0 - (DBL_MIN / sd_json_variant_real(sd_json_variant_by_index(v, 0)))) <= delta); - assert_se(fabs(1.0 - (DBL_MAX / sd_json_variant_real(sd_json_variant_by_index(v, 1)))) <= delta); + assert_se(!iszero_safe(sd_json_variant_real(sd_json_variant_by_index(v, 0)))); + assert_se(ABS(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(ABS(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 */ assert_se(sd_json_variant_is_null(sd_json_variant_by_index(v, 4))); /* -inf is not supported by json → null */ assert_se(sd_json_variant_is_null(sd_json_variant_by_index(v, 5)) || - fabs(1.0 - (HUGE_VAL / sd_json_variant_real(sd_json_variant_by_index(v, 5)))) <= delta); /* HUGE_VAL might be +inf, but might also be something else */ + ABS(1.0 - (HUGE_VAL / sd_json_variant_real(sd_json_variant_by_index(v, 5)))) <= delta); /* HUGE_VAL might be +inf, but might also be something else */ assert_se(sd_json_variant_is_real(sd_json_variant_by_index(v, 6)) && sd_json_variant_is_integer(sd_json_variant_by_index(v, 6)) && sd_json_variant_integer(sd_json_variant_by_index(v, 6)) == 0); @@ -664,11 +773,12 @@ 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(fabs(1.0 - (DBL_MIN / 2 / sd_json_variant_real(sd_json_variant_by_index(v, 9)))) <= delta); + assert_se(!iszero_safe(sd_json_variant_real(sd_json_variant_by_index(v, 9)))); + assert_se(ABS(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))); assert_se(!iszero_safe(sd_json_variant_real(sd_json_variant_by_index(v, 10)))); - assert_se(fabs(1.0 - (-DBL_MIN / 2 / sd_json_variant_real(sd_json_variant_by_index(v, 10)))) <= delta); + assert_se(ABS(1.0 - (-DBL_MIN / 2 / sd_json_variant_real(sd_json_variant_by_index(v, 10)))) <= delta); } TEST(float) { @@ -1034,8 +1144,8 @@ TEST(json_dispatch_double) { /* flags= */ 0, &data) >= 0); - assert_se(fabs(data.x1 - 0.5) < 0.01); - assert_se(fabs(data.x2 + 0.5) < 0.01); + assert_se(ABS(data.x1 - 0.5) < 0.01); + assert_se(ABS(data.x2 + 0.5) < 0.01); assert_se(isinf(data.x3)); assert_se(data.x3 > 0); assert_se(isinf(data.x4)); @@ -1159,8 +1269,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); } @@ -1523,6 +1633,69 @@ TEST(access_mode) { &mm), ERANGE); } +TEST(job_id) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + ASSERT_OK(sd_json_parse("{\"a\": 1, \"b\": 4294967295, \"c\": null}", + /* flags= */ 0, + &v, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL)); + + struct { + uint32_t a, b, c; + } data = { 99, 99, 99 }; + + ASSERT_OK(sd_json_dispatch( + v, + (const sd_json_dispatch_field[]) { + { "a", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, voffsetof(data, a), 0 }, + { "b", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, voffsetof(data, b), 0 }, + { "c", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, voffsetof(data, c), 0 }, + {}, + }, + /* flags= */ 0, + &data)); + + ASSERT_EQ(data.a, UINT32_C(1)); + ASSERT_EQ(data.b, UINT32_MAX); + ASSERT_EQ(data.c, UINT32_C(0)); + + /* Zero is not a valid job ID */ + sd_json_variant_unrefp(&v); + ASSERT_OK(sd_json_parse("{\"a\": 0}", + /* flags= */ 0, + &v, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL)); + + ASSERT_ERROR(sd_json_dispatch( + v, + (const sd_json_dispatch_field[]) { + { "a", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, voffsetof(data, a), 0 }, + {}, + }, + /* flags= */ 0, + &data), EINVAL); + + /* Negative values are not valid */ + sd_json_variant_unrefp(&v); + ASSERT_OK(sd_json_parse("{\"a\": -1}", + /* flags= */ 0, + &v, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL)); + + ASSERT_ERROR(sd_json_dispatch( + v, + (const sd_json_dispatch_field[]) { + { "a", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_job_id, voffsetof(data, a), 0 }, + {}, + }, + /* flags= */ 0, + &data), EINVAL); +} + static void test_json_variant_compare_one(const char *a, const char *b, int expected) { int r; @@ -1588,4 +1761,435 @@ 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); +} + +TEST(json_dispatch_in_addr_data) { + _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_data addr; + struct in_addr_data null_addr; + } data = {}; + + ASSERT_OK(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, offsetof(typeof(data), addr) }, + { "null_addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, offsetof(typeof(data), null_addr) }, + {}, + }, + /* flags= */ 0, + &data)); + + ASSERT_EQ(be32toh(data.addr.address.in.s_addr), 0xC0A80101U); + ASSERT_EQ(data.null_addr.address.in.s_addr, 0U); + ASSERT_EQ(data.addr.family, AF_INET); + ASSERT_EQ(data.null_addr.family, AF_UNSPEC); + + struct in_addr_data 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_data, 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_data, 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_data, 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_data, 0 }, + {}, + }, + /* flags= */ 0, + &data)); + ASSERT_EQ(be32toh(data.addr.address.in.s_addr), 0xC0A80101U); + ASSERT_EQ(data.addr.family, AF_INET); + + /* 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_data, 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_data, 0 }, + {}, + }, + /* flags= */ 0, + &dummy), EINVAL); + + /* ::1 */ + j = sd_json_variant_unref(j); + 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)))); + + zero(data); + ASSERT_OK(sd_json_dispatch(j, + (const sd_json_dispatch_field[]) { + { "addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, offsetof(typeof(data), addr) }, + { "null_addr", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, offsetof(typeof(data), null_addr) }, + {}, + }, + /* flags= */ 0, + &data)); + + ASSERT_EQ(data.addr.address.in6.s6_addr[15], 1); + for (size_t i = 0; i < 15; i++) + ASSERT_EQ(data.addr.address.in6.s6_addr[i], 0); + for (size_t i = 0; i < 16; i++) + ASSERT_EQ(data.null_addr.address.in6.s6_addr[i], 0); + ASSERT_EQ(data.addr.family, AF_INET6); + ASSERT_EQ(data.null_addr.family, AF_UNSPEC); + + /* 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_in_addr_data, 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_in_addr_data, 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_in_addr_data, 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_in_addr_data, 0 }, + {}, + }, + /* flags= */ 0, + &data)); + + ASSERT_EQ(data.addr.address.in6.s6_addr[15], 1); + for (size_t i = 0; i < 15; i++) + ASSERT_EQ(data.addr.address.in6.s6_addr[i], 0); + ASSERT_EQ(data.addr.family, AF_INET6); +} + DEFINE_TEST_MAIN(LOG_DEBUG); 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/netlink-slot.c b/src/libsystemd/sd-netlink/netlink-slot.c index f1e270de6acb5..f48ea05defe16 100644 --- a/src/libsystemd/sd-netlink/netlink-slot.c +++ b/src/libsystemd/sd-netlink/netlink-slot.c @@ -61,7 +61,7 @@ void netlink_slot_disconnect(sd_netlink_slot *slot, bool unref) { switch (slot->type) { case NETLINK_REPLY_CALLBACK: - (void) hashmap_remove(nl->reply_callbacks, &slot->reply_callback.serial); + (void) hashmap_remove(nl->reply_callbacks, UINT32_TO_PTR(slot->reply_callback.serial)); if (slot->reply_callback.timeout != USEC_INFINITY) prioq_remove(nl->reply_callbacks_prioq, &slot->reply_callback, &slot->reply_callback.prioq_idx); diff --git a/src/libsystemd/sd-netlink/netlink-sock-diag.c b/src/libsystemd/sd-netlink/netlink-sock-diag.c index 177be0c2bc846..c76cef4632f61 100644 --- a/src/libsystemd/sd-netlink/netlink-sock-diag.c +++ b/src/libsystemd/sd-netlink/netlink-sock-diag.c @@ -40,3 +40,51 @@ int sd_sock_diag_message_new_unix( *ret = TAKE_PTR(m); return 0; } + +int sd_sock_diag_message_new_unix_dump( + sd_netlink *sdnl, + sd_netlink_message **ret, + uint32_t states, + uint32_t show) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert_return(sdnl, -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(sdnl, &m, SOCK_DIAG_BY_FAMILY, NLM_F_REQUEST | NLM_F_DUMP); + if (r < 0) + return r; + + /* Unlike sd_sock_diag_message_new_unix() this requests a dump of all AF_UNIX sockets matching the + * specified state mask, rather than looking up a single socket by inode/cookie. The kernel's dump + * handler ignores udiag_ino/udiag_cookie, hence we leave them zeroed. */ + + *(struct unix_diag_req*) NLMSG_DATA(m->hdr) = (struct unix_diag_req) { + .sdiag_family = AF_UNIX, + .udiag_states = states, + .udiag_show = show, + }; + + *ret = TAKE_PTR(m); + return 0; +} + +int sd_sock_diag_message_get_unix(sd_netlink_message *m, struct unix_diag_msg *ret) { + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(ret, -EINVAL); + + if (m->hdr->nlmsg_type != SOCK_DIAG_BY_FAMILY) + return -EINVAL; + + if (NLMSG_PAYLOAD(m->hdr, 0) < sizeof(struct unix_diag_msg)) + return -EBADMSG; + + /* Reads out the fixed-size unix_diag_msg header that precedes the attributes in a reply. There's no + * sd-netlink attribute for this leading family header, hence we read it directly. */ + + *ret = *(struct unix_diag_msg*) NLMSG_DATA(m->hdr); + return 0; +} diff --git a/src/libsystemd/sd-netlink/netlink-sock-diag.h b/src/libsystemd/sd-netlink/netlink-sock-diag.h index e12e55a285935..7253193f107d8 100644 --- a/src/libsystemd/sd-netlink/netlink-sock-diag.h +++ b/src/libsystemd/sd-netlink/netlink-sock-diag.h @@ -7,3 +7,5 @@ int sd_sock_diag_socket_open(sd_netlink **ret); int sd_sock_diag_message_new_unix(sd_netlink *sdnl, sd_netlink_message **ret, ino_t inode, uint64_t cookie, uint32_t show); +int sd_sock_diag_message_new_unix_dump(sd_netlink *sdnl, sd_netlink_message **ret, uint32_t states, uint32_t show); +int sd_sock_diag_message_get_unix(sd_netlink_message *m, struct unix_diag_msg *ret); diff --git a/src/libsystemd/sd-netlink/netlink-types-sdnl.c b/src/libsystemd/sd-netlink/netlink-types-sdnl.c index 6b72efb86bc08..6e08825513b5b 100644 --- a/src/libsystemd/sd-netlink/netlink-types-sdnl.c +++ b/src/libsystemd/sd-netlink/netlink-types-sdnl.c @@ -18,6 +18,8 @@ static const NLAPolicy sdnl_req_policies[] = { DEFINE_POLICY_SET(sdnl_req); static const NLAPolicy unix_diag_msg_policies[] = { + [UNIX_DIAG_NAME] = BUILD_POLICY(STRING), /* Note: not NUL-terminated on the wire, read via sd_netlink_message_read_data() */ + [UNIX_DIAG_VFS] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct unix_diag_vfs)), [UNIX_DIAG_RQLEN] = BUILD_POLICY_WITH_SIZE(BINARY, sizeof(struct unix_diag_rqlen)), }; DEFINE_POLICY_SET(unix_diag_msg); 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/test/test-sd-path.c b/src/libsystemd/sd-path/test-sd-path.c similarity index 100% rename from src/test/test-sd-path.c rename to src/libsystemd/sd-path/test-sd-path.c diff --git a/src/libsystemd/sd-varlink/sd-varlink-idl.c b/src/libsystemd/sd-varlink/sd-varlink-idl.c index a70cbe529ce29..55f9b8b1d0f4e 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 upstream, 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..048dbb474725a 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -6,6 +6,7 @@ #include "sd-daemon.h" #include "sd-event.h" +#include "sd-future.h" #include "sd-varlink.h" #include "alloc-util.h" @@ -18,8 +19,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" @@ -39,15 +38,14 @@ #include "varlink-internal.h" #include "varlink-io.systemd.h" #include "varlink-org.varlink.service.h" +#include "varlink-util.h" +#include "xattr-util.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,38 +73,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 int varlink_reply_terminator(sd_varlink *v); static void varlink_set_state(sd_varlink *v, VarlinkState state) { assert(v); @@ -124,8 +92,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 +135,55 @@ static int varlink_new(sd_varlink **ret) { *v = (sd_varlink) { .n_ref = 1, - .input_fd = -EBADF, - .output_fd = -EBADF, - .state = _VARLINK_STATE_INVALID, + .exec_pidref = PIDREF_NULL, + }; - .ucred = UCRED_INVALID, + r = json_stream_init( + &v->stream, + &(JsonStreamParams) { + .phase = varlink_phase, + .dispatch = varlink_dispatch, + .userdata = v, + }); + if (r < 0) + return r; - .peer_pidfd = -EBADF, + json_stream_set_timeout(&v->stream, VARLINK_DEFAULT_TIMEOUT_USEC); - .timestamp = USEC_INFINITY, - .timeout = VARLINK_DEFAULT_TIMEOUT_USEC, + *ret = TAKE_PTR(v); + return 0; +} - .allow_fd_passing_input = -1, +static int mark_varlink_socket(int fd, const char *path, const char *role) { + int r; - .af = -1, + assert(wildcard_fd_is_valid(fd)); + assert(role); - .exec_pidref = PIDREF_NULL, - }; + /* We define four roles: + * + * 1. "client" → this socket was created via socket()+connect() [sockfs] + * 2. "server" → this socket was created via accept() [sockfs] + * 3. "listen" → this socket was created via socket()+listen() [sockfs] + * 4. "entrypoint" → this is the entrypoint socket inode [not sockfs] + */ + + r = socket_xattr_supported(); /* Let's check for the feature directly, to make use of the cache */ + if (r < 0) + return r; + if (r == 0) + return -EOPNOTSUPP; + + r = xsetxattr(fd, path, AT_EMPTY_PATH, "user.varlink", role); + if (r < 0) + return log_debug_errno(r, "Failed to set 'user.varlink' to '%s': %m", role); - *ret = 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,48 +193,35 @@ _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"); + r = json_stream_connect_address(&v->stream, address); + if (r < 0) + return r; - v->output_fd = v->input_fd = fd_move_above_stdio(v->input_fd); - v->af = AF_UNIX; + (void) mark_varlink_socket(v->stream.input_fd, /* path= */ NULL, "client"); - 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); + varlink_set_state(v, VARLINK_IDLE_CLIENT); - /* This is a file system path, and too long to fit into sockaddr_un. Let's connect via O_PATH - * to this socket. */ + *ret = TAKE_PTR(v); + return 0; +} - r = connect_unix_path(v->input_fd, AT_FDCWD, address); - } else - r = RET_NERRNO(connect(v->input_fd, &sockaddr.sa, r)); +static int varlink_socketpair(int *ret_client_fd, int *ret_server_fd) { + assert(ret_client_fd); + assert(ret_server_fd); - 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(). */ - } + _cleanup_close_pair_ int pair[2] = EBADF_PAIR; + if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0) + return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m"); - varlink_set_state(v, VARLINK_IDLE_CLIENT); + (void) mark_varlink_socket(pair[0], /* path= */ NULL, "client"); + (void) mark_varlink_socket(pair[1], /* path= */ NULL, "server"); - *ret = TAKE_PTR(v); + *ret_client_fd = TAKE_FD(pair[0]); + *ret_server_fd = TAKE_FD(pair[1]); return 0; } _public_ int sd_varlink_connect_exec(sd_varlink **ret, const char *_command, char **_argv) { - _cleanup_close_pair_ int pair[2] = EBADF_PAIR; _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; _cleanup_free_ char *command = NULL; _cleanup_strv_free_ char **argv = NULL; @@ -235,17 +245,19 @@ _public_ int sd_varlink_connect_exec(sd_varlink **ret, const char *_command, cha log_debug("Forking off Varlink child process '%s'.", command); - if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0) - return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m"); + _cleanup_close_ int client_fd = -EBADF, server_fd = -EBADF; + r = varlink_socketpair(&client_fd, &server_fd); + if (r < 0) + return r; - r = fd_nonblock(pair[1], false); + r = fd_nonblock(server_fd, false); if (r < 0) return log_debug_errno(r, "Failed to disable O_NONBLOCK for varlink socket: %m"); r = pidref_safe_fork_full( "(sd-vlexec)", /* stdio_fds= */ NULL, - /* except_fds= */ (int[]) { pair[1] }, + /* except_fds= */ (int[]) { server_fd }, /* n_except_fds= */ 1, FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_PACK_FDS|FORK_CLOEXEC_OFF|FORK_REOPEN_LOG|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE, &pidref); @@ -284,15 +296,18 @@ _public_ int sd_varlink_connect_exec(sd_varlink **ret, const char *_command, cha _exit(EXIT_FAILURE); } - pair[1] = safe_close(pair[1]); + server_fd = safe_close(server_fd); sd_varlink *v; r = varlink_new(&v); 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(client_fd); + 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); @@ -312,7 +327,6 @@ static int ssh_path(const char **ret) { } static int varlink_connect_ssh_unix(sd_varlink **ret, const char *where) { - _cleanup_close_pair_ int pair[2] = EBADF_PAIR; _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; int r; @@ -348,12 +362,14 @@ static int varlink_connect_ssh_unix(sd_varlink **ret, const char *where) { log_debug("Forking off SSH child process '%s -W %s %s'.", ssh, p, h); - if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0) - return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m"); + _cleanup_close_ int client_fd = -EBADF, server_fd = -EBADF; + r = varlink_socketpair(&client_fd, &server_fd); + if (r < 0) + return r; r = pidref_safe_fork_full( "(sd-vlssh)", - /* stdio_fds= */ (int[]) { pair[1], pair[1], STDERR_FILENO }, + /* stdio_fds= */ (int[]) { server_fd, server_fd, STDERR_FILENO }, /* except_fds= */ NULL, /* n_except_fds= */ 0, FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO, @@ -368,15 +384,18 @@ static int varlink_connect_ssh_unix(sd_varlink **ret, const char *where) { _exit(EXIT_FAILURE); } - pair[1] = safe_close(pair[1]); + server_fd = safe_close(server_fd); sd_varlink *v; r = varlink_new(&v); 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(client_fd); + 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 +486,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 +595,25 @@ _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; - } + (void) mark_varlink_socket(v->stream.input_fd, /* path= */ NULL, "client"); + + if (override_ucred) + json_stream_set_peer_ucred(&v->stream, override_ucred); varlink_set_state(v, VARLINK_IDLE_CLIENT); @@ -614,7 +624,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 +632,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 +641,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 +656,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 +682,6 @@ static sd_varlink* varlink_destroy(sd_varlink *v) { varlink_clear(v); - free(v->description); return mfree(v); } @@ -709,358 +690,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) + if (!json_stream_should_disconnect(&v->stream)) return 0; - /* Still something to write and we can write? Stay around */ - if (v->output_buffer_size > 0 && !v->write_disconnected) - 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; } @@ -1121,10 +825,7 @@ static int varlink_sanitize_incoming_parameters(sd_json_variant **v) { r = sd_json_variant_new_object(&empty, NULL, 0); if (r < 0) return r; - /* sd_json_variant_unref() is a NOP if *v is NULL */ - sd_json_variant_unref(*v); - *v = TAKE_PTR(empty); - return 0; + return json_variant_unref_and_replace(*v, empty); } /* Ensure we have an object */ @@ -1135,9 +836,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 +862,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 +904,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,148 +1009,176 @@ 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; +static int varlink_dispatch_sentinel(sd_varlink *v) { + int 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; + assert(v->sentinel); - /* 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; + if (v->previous) { + r = json_stream_enqueue_full(&v->stream, v->previous, v->previous_fds, v->n_previous_fds); + if (r >= 0) { + v->previous = sd_json_variant_unref(v->previous); + v->previous_fds = mfree(v->previous_fds); + v->n_previous_fds = 0; + /* Mirror sd_varlink_reply()'s post-enqueue state machine: PENDING_* means we're + * outside the dispatch stack frame (e.g. called from varlink_fiber_entry after + * the fiber returned), so we go straight to IDLE_SERVER ourselves. PROCESSING_* + * means we're inside varlink_dispatch_method(), which will transition us. */ + if (IN_SET(v->state, VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE)) { + varlink_clear_current(v); + varlink_set_state(v, VARLINK_IDLE_SERVER); + } else + varlink_set_state(v, VARLINK_PROCESSED_METHOD); + } - n = new(char, new_size); - if (!n) - return -ENOMEM; + return r; + } - memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, sz + 1); + char *sentinel = TAKE_PTR(v->sentinel); - free_and_replace(v->output_buffer, n); - v->output_buffer_size = new_size; - v->output_buffer_index = 0; + /* Propagate the sentinel to the client if one was configured and no replies were enqueued by + * the callback. */ + if (sentinel == POINTER_MAX) + /* Synthetic empty terminator. Skip IDL validation since the empty parameters wouldn't + * satisfy any mandatory output fields the method declares. */ + r = varlink_reply_terminator(v); + 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 (IN_SET(v->state, VARLINK_PROCESSED_METHOD, VARLINK_IDLE_SERVER)) + r = 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 */ + if (sentinel != POINTER_MAX) + free(sentinel); - return 0; + return r; } -static int varlink_format_queue(sd_varlink *v) { - int r; +typedef struct VarlinkFiberData { + sd_varlink *link; + sd_json_variant *parameters; + sd_varlink_method_flags_t flags; + void *userdata; + sd_varlink_method_t callback; +} VarlinkFiberData; - assert(v); +static VarlinkFiberData* varlink_fiber_data_free(VarlinkFiberData *d) { + if (!d) + return NULL; - /* 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 */ + sd_json_variant_unref(d->parameters); + sd_varlink_unref(d->link); + return mfree(d); +} - while (v->output_queue) { - assert(v->n_output_queue > 0); +DEFINE_TRIVIAL_CLEANUP_FUNC(VarlinkFiberData*, varlink_fiber_data_free); - if (v->n_output_fds > 0) /* unwritten fds? if we'd add more we'd corrupt the fd message boundaries, hence wait */ - return 0; +static void varlink_fiber_data_destroy(void *userdata) { + varlink_fiber_data_free(userdata); +} - VarlinkJsonQueueItem *q = v->output_queue; - _cleanup_free_ int *array = NULL; +static int varlink_fiber_entry(void *userdata) { + VarlinkFiberData *d = ASSERT_PTR(userdata); + sd_varlink *v = d->link; + int r; - if (q->n_fds > 0) { - array = newdup(int, q->fds, q->n_fds); - if (!array) - return -ENOMEM; - } + r = d->callback(v, d->parameters, d->flags, d->userdata); + + /* The fiber runs after varlink_dispatch_method() has already transitioned the state from + * VARLINK_PROCESSING_METHOD{,_MORE} to VARLINK_PENDING_METHOD{,_MORE}, so that's what we match + * here to decide whether the call still needs a reply. Any other state (e.g. IDLE_SERVER after + * the callback replied, or DISCONNECTED after sd_varlink_close()) means no fixup is needed. */ + if (!IN_SET(v->state, VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE)) + return r; + + if (r < 0) { + varlink_log_errno(v, r, "Fiber returned error: %m"); - r = varlink_format_json(v, q->data); + /* Propagate error to the client if the method call remains unanswered. */ + r = sd_varlink_error_errno(v, r); + } else if (v->sentinel) { + r = varlink_dispatch_sentinel(v); if (r < 0) - return r; + varlink_log_errno(v, r, "Failed to process sentinel: %m"); + } else if (v->n_ref <= 2) { + /* Bare minimum refs (server + fiber data) means the connection wasn't stashed + * to reply later, so the fiber was supposed to reply itself but didn't. */ + r = varlink_log_errno(v, SYNTHETIC_ERRNO(EPROTO), + "Fiber returned without enqueuing a reply or stashing connection, failing."); + goto fail; + } else + r = 0; - /* 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; + /* If we didn't manage to enqueue a response, then fail the connection completely. */ + if (r < 0 && IN_SET(v->state, VARLINK_PENDING_METHOD, VARLINK_PENDING_METHOD_MORE)) + goto fail; - LIST_REMOVE(queue, v->output_queue, q); - if (!v->output_queue) - v->output_queue_tail = NULL; - v->n_output_queue--; + return r; - varlink_json_queue_item_free(q); - } +fail: + varlink_set_state(v, VARLINK_PROCESSING_FAILURE); + varlink_dispatch_local_error(v, SD_VARLINK_ERROR_PROTOCOL); + sd_varlink_close(v); - return 0; + return r; } -static int varlink_enqueue_item(sd_varlink *v, VarlinkJsonQueueItem *q) { - assert(v); - assert(q); +static int varlink_dispatch_fiber(sd_varlink *v, const char *method, sd_varlink_method_t callback, sd_json_variant *parameters, sd_varlink_method_flags_t flags) { + int r; - if (v->n_output_queue >= VARLINK_QUEUE_MAX) - return -ENOBUFS; + assert(v); + assert(v->server); + assert(method); + assert(callback); - LIST_INSERT_AFTER(queue, v->output_queue, v->output_queue_tail, q); - v->output_queue_tail = q; - v->n_output_queue++; - return 0; -} + if (!v->server->event) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EDEADLK), + "Cannot dispatch fiber method without event loop."); -static int varlink_enqueue_json(sd_varlink *v, sd_json_variant *m) { - VarlinkJsonQueueItem *q; + _cleanup_(varlink_fiber_data_freep) VarlinkFiberData *d = new(VarlinkFiberData, 1); + if (!d) + return log_oom_debug(); - assert(v); - assert(m); + *d = (VarlinkFiberData) { + .link = sd_varlink_ref(v), + .parameters = sd_json_variant_ref(parameters), + .flags = flags, + .userdata = v->userdata, + .callback = callback, + }; - /* 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); + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_fiber_new(v->server->event, method, varlink_fiber_entry, d, varlink_fiber_data_destroy, &f); + if (r < 0) + return r; - if (v->n_output_queue >= VARLINK_QUEUE_MAX) - return -ENOBUFS; + TAKE_PTR(d); /* The fiber owns the data now. */ - /* 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; + /* Run the fiber at a higher priority than the connection's quit event source, so that on event + * loop exit the fiber's exit source (which cancels it and drives its cleanup) fires before + * varlink's quit_callback closes the connection. This lets a fiber handler reply with an error + * or flush its sentinel on a still-open connection during graceful shutdown. */ + int64_t priority; + r = sd_event_source_get_priority(v->quit_event_source, &priority); + if (r < 0) + return r; - v->n_pushed_fds = 0; /* fds now belong to the queue entry */ + r = sd_future_set_priority(f, priority > INT64_MIN ? priority - 1 : priority); + if (r < 0) + return r; - /* We already checked the precondition ourselves so this call cannot fail. */ - assert_se(varlink_enqueue_item(v, q) >= 0); + /* Hand the future's lifetime over to the event loop: it'll auto-unref on resolve. */ + r = sd_fiber_set_floating(f, true); + if (r < 0) + return r; return 0; } @@ -1494,7 +1222,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 +1233,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 +1242,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,8 +1270,23 @@ 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 */ + bool is_fiber = false; callback = hashmap_get(v->server->methods, method); + if (!callback) { + callback = hashmap_get(v->server->fiber_methods, method); + if (callback) + is_fiber = true; + } if (!callback) { if (streq(method, "org.varlink.service.GetInfo")) callback = generic_method_get_info; @@ -1551,11 +1305,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 @@ -1580,7 +1338,13 @@ static int varlink_dispatch_method(sd_varlink *v) { } if (!invalid) { - r = callback(v, parameters, flags, v->userdata); + if (is_fiber) + /* Spawn a fiber to run the callback. The VarlinkFiberData takes a ref on the + * connection (bumping n_ref above 2), so the post-callback logic below treats + * this as a deferred reply and moves state to PENDING_METHOD. */ + r = varlink_dispatch_fiber(v, method, callback, parameters, flags); + else + r = callback(v, parameters, flags, v->userdata); if (VARLINK_STATE_WANTS_REPLY(v->state)) { if (r < 0) { varlink_log_errno(v, r, "Callback for '%s' returned error: %m", method); @@ -1589,25 +1353,7 @@ static int varlink_dispatch_method(sd_varlink *v) { * if the method call remains unanswered. */ r = sd_varlink_error_errno(v, r); } else if (v->sentinel) { - if (v->previous) { - r = varlink_enqueue_item(v, v->previous); - if (r >= 0) { - TAKE_PTR(v->previous); - varlink_set_state(v, VARLINK_PROCESSED_METHOD); - } - } else { - char *sentinel = TAKE_PTR(v->sentinel); - - /* Propagate the sentinel to the client if one was configured - * and no replies were enqueued by the callback. */ - if (sentinel == POINTER_MAX) - r = sd_varlink_reply(v, NULL); - else - r = sd_varlink_error(v, sentinel, NULL); - - if (sentinel != POINTER_MAX) - free(sentinel); - } + r = varlink_dispatch_sentinel(v); if (r < 0) varlink_log_errno(v, r, "Failed to process sentinel for method '%s': %m", method); } else { @@ -1820,85 +1566,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 +1593,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 +1650,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 +1675,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 +1766,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 +1813,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 +1864,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,19 +1892,13 @@ _public_ int sd_varlink_observeb(sd_varlink *v, const char *method, ...) { return sd_varlink_observe(v, method, parameters); } -_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; +/* 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_return(v, -EINVAL); - assert_return(method, -EINVAL); + assert(v); + assert(request); if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); @@ -2282,20 +1911,13 @@ _public_ int sd_varlink_call_full( * that we can assign a new reply shortly. */ varlink_clear_current(v); - 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_enqueue_json(v, m); + 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++; - v->timestamp = now(CLOCK_MONOTONIC); + json_stream_mark_activity(&v->stream); while (v->state == VARLINK_CALLING) { r = sd_varlink_process(v); @@ -2311,29 +1933,9 @@ _public_ int sd_varlink_call_full( switch (v->state) { - case VARLINK_CALLED: { + 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"); - - /* 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; - } + return 0; case VARLINK_PENDING_DISCONNECT: case VARLINK_DISCONNECTED: @@ -2347,6 +1949,52 @@ _public_ int sd_varlink_call_full( } } +_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, @@ -2357,6 +2005,133 @@ _public_ int sd_varlink_call( 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, + 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); + 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), + SD_JSON_BUILD_PAIR_BOOLEAN("upgrade", true)); + if (r < 0) + return varlink_log_errno(v, r, "Failed to build json message: %m"); + + 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; + } + + /* ensure we did not consume any data from the upgraded protocol */ + assert(!json_stream_has_buffered_input(&v->stream)); + + sd_json_variant *e = sd_json_variant_by_key(v->current, "error"), + *p = sd_json_variant_by_key(v->current, "parameters"); + + /* 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); + + varlink_set_state(v, VARLINK_IDLE_CLIENT); + goto finish; + } + + /* 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; + } + + varlink_set_state(v, VARLINK_DISCONNECTED); + assert(v->n_pending == 1); + v->n_pending--; + + if (ret_parameters) + *ret_parameters = p; + if (ret_error_id) + *ret_error_id = NULL; + + return 1; + +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( sd_varlink *v, const char *method, @@ -2444,13 +2219,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) { @@ -2576,10 +2351,10 @@ _public_ int sd_varlink_collectb( return sd_varlink_collect_full(v, method, parameters, ret_parameters, ret_error_id, NULL); } -_public_ int sd_varlink_reply(sd_varlink *v, sd_json_variant *parameters) { +static int varlink_reply_internal(sd_varlink *v, sd_json_variant *parameters, bool skip_validation) { int r; - assert_return(v, -EINVAL); + assert(v); if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); @@ -2591,8 +2366,9 @@ _public_ int sd_varlink_reply(sd_varlink *v, sd_json_variant *parameters) { bool more = IN_SET(v->state, VARLINK_PROCESSING_METHOD_MORE, VARLINK_PENDING_METHOD_MORE); - /* Validate parameters BEFORE sanitization */ - if (v->current_method) { + /* Validate parameters BEFORE sanitization. Validation should be skipped for the synthetic + * empty terminator. */ + if (!skip_validation && v->current_method) { const char *bad_field = NULL; r = varlink_idl_validate_method_reply(v->current_method, parameters, more && v->sentinel ? SD_VARLINK_REPLY_CONTINUES : 0, &bad_field); @@ -2612,24 +2388,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"); @@ -2647,6 +2427,16 @@ _public_ int sd_varlink_reply(sd_varlink *v, sd_json_variant *parameters) { return 1; } +static int varlink_reply_terminator(sd_varlink *v) { + return varlink_reply_internal(v, /* parameters= */ NULL, /* skip_validation= */ true); +} + +_public_ int sd_varlink_reply(sd_varlink *v, sd_json_variant *parameters) { + assert_return(v, -EINVAL); + + return varlink_reply_internal(v, parameters, /* skip_validation= */ false); +} + _public_ int sd_varlink_replyb(sd_varlink *v, ...) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *parameters = NULL; va_list ap; @@ -2664,6 +2454,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 +2553,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 +2597,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 +2737,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 +2809,64 @@ _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. The PENDING states + * apply to fiber callbacks, which run after varlink_dispatch_method() has already transitioned + * the state from PROCESSING to PENDING. */ + assert_return(IN_SET(v->state, + VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE, + VARLINK_PENDING_METHOD, VARLINK_PENDING_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 +2879,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 +2897,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 +2912,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"); - } + assert_return(!json_stream_get_event(&v->stream), -EBUSY); - r = sd_event_add_time(v->event, &v->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, v); + r = json_stream_attach_event(&v->stream, e, priority); if (r < 0) - goto fail; - - r = sd_event_source_set_priority(v->time_event_source, 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 +2930,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 +2952,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 +2979,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 +3011,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 +3034,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 +3087,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) @@ -3471,7 +3123,11 @@ static sd_varlink_server* varlink_server_destroy(sd_varlink_server *s) { while ((m = hashmap_steal_first_key(s->methods))) free(m); + while ((m = hashmap_steal_first_key(s->fiber_methods))) + free(m); + hashmap_free(s->methods); + hashmap_free(s->fiber_methods); hashmap_free(s->interfaces); hashmap_free(s->symbols); hashmap_free(s->by_uid); @@ -3563,8 +3219,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 +3236,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 +3294,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,13 +3322,19 @@ _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; } } + (void) mark_varlink_socket(input_fd, /* path= */ NULL, "server"); + if (ret) *ret = v; @@ -3682,6 +3349,7 @@ VarlinkServerSocket* varlink_server_socket_free(VarlinkServerSocket *ss) { if (!ss) return NULL; + sd_event_source_disable_unref(ss->event_source); free(ss->address); return mfree(ss); } @@ -3778,10 +3446,19 @@ _public_ int sd_varlink_server_listen_fd(sd_varlink_server *s, int fd) { if (r < 0) return r; + (void) mark_varlink_socket(ss->fd, /* path= */ NULL, "listen"); + LIST_PREPEND(sockets, s->sockets, TAKE_PTR(ss)); return 0; } +static mode_t default_listen_mode(sd_varlink_server_flags_t flags) { + /* NB: we use 0644 rather than 0600 here, because it's the "w" flag that controls connect() + * privileges, but leaving the "r" flag on allows others to read our xattrs, which is good because it + * makes our sockets recognizable as varlink, even if not connectible. */ + return (flags & (SD_VARLINK_SERVER_ROOT_ONLY|SD_VARLINK_SERVER_MYSELF_ONLY)) != 0 ? 0644 : 0666; +} + _public_ int sd_varlink_server_listen_address(sd_varlink_server *s, const char *address, mode_t m) { _cleanup_(varlink_server_socket_freep) VarlinkServerSocket *ss = NULL; union sockaddr_union sockaddr; @@ -3791,6 +3468,12 @@ _public_ int sd_varlink_server_listen_address(sd_varlink_server *s, const char * assert_return(s, -EINVAL); assert_return(address, -EINVAL); + + /* NB: we resolve m being MODE_INVALID before checking SD_VARLINK_SERVER_MODE_MKDIR_0755, since that + * flag is not defined for MODE_INVALID (if we'd check we'd see it always set...) */ + if (m == MODE_INVALID) + m = default_listen_mode(s->flags); + assert_return((m & ~(0777|SD_VARLINK_SERVER_MODE_MKDIR_0755)) == 0, -EINVAL); /* Validate that the definition of our flag doesn't collide with the official mode_t bits. Thankfully @@ -3815,6 +3498,8 @@ _public_ int sd_varlink_server_listen_address(sd_varlink_server *s, const char * fd = fd_move_above_stdio(fd); + (void) mark_varlink_socket(fd, /* path= */ NULL, "listen"); + /* See the comment in sd_varlink_server_listen_fd() */ if (FLAGS_SET(s->flags, SD_VARLINK_SERVER_FD_PASSING_INPUT_STRICT)) (void) setsockopt_int(fd, SOL_SOCKET, SO_PASSRIGHTS, FLAGS_SET(s->flags, SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT)); @@ -3826,6 +3511,9 @@ _public_ int sd_varlink_server_listen_address(sd_varlink_server *s, const char * if (r < 0) return r; + if (path_is_absolute(address)) + (void) mark_varlink_socket(AT_FDCWD, address, "entrypoint"); + if (listen(fd, SOMAXCONN_DELUXE) < 0) return -errno; @@ -3961,7 +3649,7 @@ _public_ int sd_varlink_server_listen_auto(sd_varlink_server *s) { if (streq(e, "-")) r = sd_varlink_server_add_connection_stdio(s, /* ret= */ NULL); else - r = sd_varlink_server_listen_address(s, e, FLAGS_SET(s->flags, SD_VARLINK_SERVER_ROOT_ONLY) ? 0600 : 0666); + r = sd_varlink_server_listen_address(s, e, default_listen_mode(s->flags)); if (r < 0) return r; @@ -4153,23 +3841,32 @@ static bool varlink_symbol_in_interface(const char *method, const char *interfac return !strchr(p+1, '.'); } -_public_ int sd_varlink_server_bind_method(sd_varlink_server *s, const char *method, sd_varlink_method_t callback) { +static int varlink_server_bind_internal(sd_varlink_server *s, Hashmap **methods, const char *method, sd_varlink_method_t callback) { _cleanup_free_ char *m = NULL; int r; - assert_return(s, -EINVAL); - assert_return(method, -EINVAL); - assert_return(callback, -EINVAL); + assert(s); + assert(methods); + assert(method); + assert(callback); if (varlink_symbol_in_interface(method, "org.varlink.service") || varlink_symbol_in_interface(method, "io.systemd")) return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EEXIST), "Cannot bind server to '%s'.", method); + /* Refuse to register the same method in both the regular and fiber method maps: the dispatcher + * always consults methods first and would silently ignore a shadowed fiber_methods entry (or vice + * versa), hiding the misconfiguration. */ + Hashmap *other = methods == &s->methods ? s->fiber_methods : s->methods; + if (hashmap_contains(other, method)) + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EEXIST), + "Method '%s' is already bound in the other method map.", method); + m = strdup(method); if (!m) return log_oom_debug(); - r = hashmap_ensure_put(&s->methods, &string_hash_ops, m, callback); + r = hashmap_ensure_put(methods, &string_hash_ops, m, callback); if (r == -ENOMEM) return log_oom_debug(); if (r < 0) @@ -4180,13 +3877,12 @@ _public_ int sd_varlink_server_bind_method(sd_varlink_server *s, const char *met return 0; } -_public_ int sd_varlink_server_bind_method_many_internal(sd_varlink_server *s, ...) { - va_list ap; +static int varlink_server_bind_many_internal(sd_varlink_server *s, Hashmap **methods, va_list ap) { int r = 0; - assert_return(s, -EINVAL); + assert(s); + assert(methods); - va_start(ap, s); for (;;) { sd_varlink_method_t callback; const char *method; @@ -4197,10 +3893,51 @@ _public_ int sd_varlink_server_bind_method_many_internal(sd_varlink_server *s, . callback = va_arg(ap, sd_varlink_method_t); - r = sd_varlink_server_bind_method(s, method, callback); + r = varlink_server_bind_internal(s, methods, method, callback); if (r < 0) break; } + + return r; +} + +_public_ int sd_varlink_server_bind_method(sd_varlink_server *s, const char *method, sd_varlink_method_t callback) { + assert_return(s, -EINVAL); + assert_return(method, -EINVAL); + assert_return(callback, -EINVAL); + + return varlink_server_bind_internal(s, &s->methods, method, callback); +} + +_public_ int sd_varlink_server_bind_method_many_internal(sd_varlink_server *s, ...) { + va_list ap; + int r; + + assert_return(s, -EINVAL); + + va_start(ap, s); + r = varlink_server_bind_many_internal(s, &s->methods, ap); + va_end(ap); + + return r; +} + +int varlink_server_bind_fiber(sd_varlink_server *s, const char *method, sd_varlink_method_t callback) { + assert_return(s, -EINVAL); + assert_return(method, -EINVAL); + assert_return(callback, -EINVAL); + + return varlink_server_bind_internal(s, &s->fiber_methods, method, callback); +} + +int varlink_server_bind_fiber_many_internal(sd_varlink_server *s, ...) { + va_list ap; + int r; + + assert_return(s, -EINVAL); + + va_start(ap, s); + r = varlink_server_bind_many_internal(s, &s->fiber_methods, ap); va_end(ap); return r; @@ -4393,6 +4130,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/test/test-varlink-idl.c b/src/libsystemd/sd-varlink/test-varlink-idl.c similarity index 84% rename from src/test/test-varlink-idl.c rename to src/libsystemd/sd-varlink/test-varlink-idl.c index c6aca36677745..ae4a6d21b80f7 100644 --- a/src/test/test-varlink-idl.c +++ b/src/libsystemd/sd-varlink/test-varlink-idl.c @@ -11,26 +11,31 @@ #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" #include "varlink-io.systemd.BootControl.h" #include "varlink-io.systemd.Credentials.h" +#include "varlink-io.systemd.CryptEnroll.h" #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.Job.h" #include "varlink-io.systemd.Journal.h" #include "varlink-io.systemd.JournalAccess.h" #include "varlink-io.systemd.Login.h" #include "varlink-io.systemd.Machine.h" #include "varlink-io.systemd.MachineImage.h" +#include "varlink-io.systemd.MachineInstance.h" #include "varlink-io.systemd.ManagedOOM.h" #include "varlink-io.systemd.Manager.h" +#include "varlink-io.systemd.Metrics.h" #include "varlink-io.systemd.MountFileSystem.h" #include "varlink-io.systemd.MuteConsole.h" #include "varlink-io.systemd.NamespaceResource.h" @@ -39,12 +44,20 @@ #include "varlink-io.systemd.PCRExtend.h" #include "varlink-io.systemd.PCRLock.h" #include "varlink-io.systemd.Repart.h" +#include "varlink-io.systemd.Report.h" +#include "varlink-io.systemd.Report.Signer.h" +#include "varlink-io.systemd.Report.Uploader.h" #include "varlink-io.systemd.Resolve.h" #include "varlink-io.systemd.Resolve.Hook.h" #include "varlink-io.systemd.Resolve.Monitor.h" +#include "varlink-io.systemd.Shutdown.h" +#include "varlink-io.systemd.StorageProvider.h" +#include "varlink-io.systemd.SysUpdate.h" +#include "varlink-io.systemd.SysUpdate.Notify.h" #include "varlink-io.systemd.Udev.h" #include "varlink-io.systemd.Unit.h" #include "varlink-io.systemd.UserDatabase.h" +#include "varlink-io.systemd.VirtualMachineInstance.h" #include "varlink-io.systemd.oom.h" #include "varlink-io.systemd.oom.Prekill.h" #include "varlink-io.systemd.service.h" @@ -187,16 +200,21 @@ TEST(parse_format) { &vl_interface_io_systemd_AskPassword, &vl_interface_io_systemd_BootControl, &vl_interface_io_systemd_Credentials, + &vl_interface_io_systemd_CryptEnroll, &vl_interface_io_systemd_FactoryReset, &vl_interface_io_systemd_Hostname, &vl_interface_io_systemd_Import, + &vl_interface_io_systemd_InstanceMetadata, + &vl_interface_io_systemd_Job, &vl_interface_io_systemd_Journal, &vl_interface_io_systemd_JournalAccess, &vl_interface_io_systemd_Login, &vl_interface_io_systemd_Machine, &vl_interface_io_systemd_MachineImage, + &vl_interface_io_systemd_MachineInstance, &vl_interface_io_systemd_ManagedOOM, &vl_interface_io_systemd_Manager, + &vl_interface_io_systemd_Metrics, &vl_interface_io_systemd_MountFileSystem, &vl_interface_io_systemd_MuteConsole, &vl_interface_io_systemd_NamespaceResource, @@ -205,12 +223,20 @@ TEST(parse_format) { &vl_interface_io_systemd_PCRExtend, &vl_interface_io_systemd_PCRLock, &vl_interface_io_systemd_Repart, + &vl_interface_io_systemd_Report, + &vl_interface_io_systemd_Report_Signer, + &vl_interface_io_systemd_Report_Uploader, &vl_interface_io_systemd_Resolve, &vl_interface_io_systemd_Resolve_Hook, &vl_interface_io_systemd_Resolve_Monitor, + &vl_interface_io_systemd_Shutdown, + &vl_interface_io_systemd_StorageProvider, + &vl_interface_io_systemd_SysUpdate, + &vl_interface_io_systemd_SysUpdate_Notify, &vl_interface_io_systemd_Udev, &vl_interface_io_systemd_Unit, &vl_interface_io_systemd_UserDatabase, + &vl_interface_io_systemd_VirtualMachineInstance, &vl_interface_io_systemd_oom, &vl_interface_io_systemd_oom_Prekill, &vl_interface_io_systemd_service, @@ -334,8 +360,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 +505,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 +559,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/libsystemd/sd-varlink/test-varlink.c similarity index 50% rename from src/test/test-varlink.c rename to src/libsystemd/sd-varlink/test-varlink.c index bf1390fba1dc4..a2a32b7f78d47 100644 --- a/src/test/test-varlink.c +++ b/src/libsystemd/sd-varlink/test-varlink.c @@ -2,18 +2,24 @@ #include #include -#include +#include #include +#include #include #include "sd-event.h" +#include "sd-future.h" #include "sd-json.h" #include "sd-varlink.h" +#include "dirent-util.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 +51,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 +81,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 +90,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 +131,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; @@ -210,7 +216,12 @@ static void flood_test(const char *address) { /* Block the main event loop while we flood */ ASSERT_OK_EQ_ERRNO(write(block_write_fd, &x, sizeof(x)), (ssize_t) sizeof(x)); - ASSERT_OK(sd_event_default(&e)); + /* Create a fresh event loop for the flood test — we can't reuse the default event because the + * main test (and the fiber we're running in) is already running it, and sd_event_loop() asserts + * the event is in the INITIAL state. Exit-on-idle so the nested loop terminates once the + * overload reply has been received and all other work is quiesced. */ + ASSERT_OK(sd_event_new(&e)); + ASSERT_OK(sd_event_set_exit_on_idle(e, true)); /* Flood the server with connections */ ASSERT_NOT_NULL(connections = new0(sd_varlink*, OVERLOAD_CONNECTIONS)); @@ -222,7 +233,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 */ @@ -245,7 +256,7 @@ static void flood_test(const char *address) { connections[k] = sd_varlink_unref(connections[k]); } -static void *thread(void *arg) { +static int client_fiber(void *arg) { _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *c = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *i = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *wrong = NULL; @@ -253,17 +264,17 @@ 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")); + ASSERT_OK(sd_varlink_set_description(c, "fiber-client")); ASSERT_OK(sd_varlink_set_allow_fd_passing_input(c, true)); 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 +303,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 +313,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))))); @@ -315,7 +326,7 @@ static void *thread(void *arg) { ASSERT_OK(sd_varlink_send(c, "io.test.Done", NULL)); - return NULL; + return 0; } static int block_fd_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { @@ -342,8 +353,8 @@ TEST(chat) { _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; _cleanup_close_pair_ int block_fds[2] = EBADF_PAIR; - pthread_t t; const char *sp; ASSERT_OK(mkdtemp_malloc("/tmp/varlink-test-XXXXXX", &tmpdir)); @@ -371,8 +382,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")); @@ -382,11 +393,11 @@ TEST(chat) { ASSERT_OK(sd_varlink_attach_event(c, e, 0)); - ASSERT_OK(-pthread_create(&t, NULL, thread, (void*) sp)); + ASSERT_OK(sd_fiber_new(e, "client", client_fiber, (void*) sp, /* destroy= */ NULL, &f)); ASSERT_OK(sd_event_loop(e)); - ASSERT_OK(-pthread_join(t, NULL)); + ASSERT_OK(sd_future_result(f)); } static int method_invalid(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { @@ -441,9 +452,81 @@ TEST(invalid_parameter) { ASSERT_OK(sd_event_loop(e)); } +typedef struct MandatoryTypeWithOptionalField { + const char *mandatory; + const char *optional; +} MandatoryTypeWithOptionalField; + +static int dispatch_mandatory_type_with_optional_field(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + MandatoryTypeWithOptionalField *m = ASSERT_PTR(userdata); + static const sd_json_dispatch_field dispatch[] = { + { "mandatory", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(*m, mandatory), SD_JSON_MANDATORY }, + { "optional", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(*m, optional), 0 }, + {} + }; + + return sd_json_dispatch(variant, dispatch, flags, m); +} + +static int method_mandatory_type_with_optional_field(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + MandatoryTypeWithOptionalField m = { NULL, NULL }; + int r; + + sd_json_dispatch_field table[] = { + { "mandatoryField", SD_JSON_VARIANT_OBJECT, dispatch_mandatory_type_with_optional_field, 0, SD_JSON_MANDATORY }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, table, &m); + if (r != 0) + return r; + + _cleanup_free_ char *sum = strjoin(m.mandatory, "+", m.optional); + if (!sum) + return log_oom(); + + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("result", sum)); +} + +static int reply_valid_hi(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + ASSERT_NULL(error_id); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(parameters, "result")), "hi+"); + ASSERT_OK(sd_event_exit(sd_varlink_get_event(link), EXIT_SUCCESS)); + return 0; +} + +TEST(mandatory_type_with_optional_field) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_default(&e)); + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + ASSERT_OK(sd_varlink_server_new(&s, 0)); + + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + + ASSERT_OK(sd_varlink_server_bind_method(s, "foo.mytest.MandatoryTypeWithOptionalField", method_mandatory_type_with_optional_field)); + + int connfd[2]; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, connfd)); + ASSERT_OK(sd_varlink_server_add_connection(s, connfd[0], /* ret= */ NULL)); + + _cleanup_(sd_varlink_unrefp) sd_varlink *c = NULL; + ASSERT_OK(sd_varlink_connect_fd(&c, connfd[1])); + + ASSERT_OK(sd_varlink_attach_event(c, e, 0)); + + ASSERT_OK(sd_varlink_bind_reply(c, reply_valid_hi)); + + ASSERT_OK(sd_varlink_invokebo(c, "foo.mytest.MandatoryTypeWithOptionalField", + SD_JSON_BUILD_PAIR_OBJECT("mandatoryField", + SD_JSON_BUILD_PAIR_STRING("mandatory", "hi")))); + + ASSERT_OK(sd_event_loop(e)); +} + 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 +565,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 +605,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 +644,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; } @@ -612,6 +695,173 @@ TEST(sentinel_oneway) { ASSERT_OK(sd_event_loop(e)); } +static int method_fiber_sentinel_error(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + /* Set an error sentinel from a fiber callback and return without sending a reply. The sentinel + * error should still be propagated by the fiber's post-callback logic, even though the varlink + * state has already been transitioned to VARLINK_PENDING_METHOD by the time the fiber runs. */ + ASSERT_OK(sd_varlink_set_sentinel(link, "io.test.SentinelError")); + return 0; +} + +TEST(fiber_sentinel_error) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_default(&e)); + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + ASSERT_OK(sd_varlink_server_new(&s, 0)); + + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + + ASSERT_OK(varlink_server_bind_fiber(s, "io.test.FiberSentinelError", method_fiber_sentinel_error)); + + int connfd[2]; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, connfd)); + ASSERT_OK(sd_varlink_server_add_connection(s, connfd[0], /* ret= */ NULL)); + + _cleanup_(sd_varlink_unrefp) sd_varlink *c = NULL; + ASSERT_OK(sd_varlink_connect_fd(&c, connfd[1])); + + ASSERT_OK(sd_varlink_attach_event(c, e, 0)); + + ASSERT_OK(sd_varlink_bind_reply(c, reply_sentinel_error)); + + ASSERT_OK(sd_varlink_invoke(c, "io.test.FiberSentinelError", /* parameters= */ NULL)); + + ASSERT_OK(sd_event_loop(e)); +} + +static int method_fiber_errno(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + /* Return a negative errno without sending a reply. The fiber's post-callback logic should + * convert this into a SD_VARLINK_ERROR_SYSTEM reply. */ + return -ENOSYS; +} + +static int reply_fiber_errno(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + ASSERT_STREQ(error_id, SD_VARLINK_ERROR_SYSTEM); + ASSERT_EQ(sd_json_variant_integer(sd_json_variant_by_key(parameters, "errno")), ENOSYS); + ASSERT_OK(sd_event_exit(sd_varlink_get_event(link), EXIT_SUCCESS)); + return 0; +} + +TEST(fiber_errno) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_default(&e)); + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + ASSERT_OK(sd_varlink_server_new(&s, 0)); + + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + + ASSERT_OK(varlink_server_bind_fiber(s, "io.test.FiberErrno", method_fiber_errno)); + + int connfd[2]; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, connfd)); + ASSERT_OK(sd_varlink_server_add_connection(s, connfd[0], /* ret= */ NULL)); + + _cleanup_(sd_varlink_unrefp) sd_varlink *c = NULL; + ASSERT_OK(sd_varlink_connect_fd(&c, connfd[1])); + + ASSERT_OK(sd_varlink_attach_event(c, e, 0)); + + ASSERT_OK(sd_varlink_bind_reply(c, reply_fiber_errno)); + + ASSERT_OK(sd_varlink_invoke(c, "io.test.FiberErrno", /* parameters= */ NULL)); + + ASSERT_OK(sd_event_loop(e)); +} + +static int method_fiber_no_reply(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + /* Return success without replying and without stashing a ref. The fiber's post-callback + * logic should detect this and fail the connection. */ + return 0; +} + +static int reply_fiber_no_reply(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + ASSERT_STREQ(error_id, SD_VARLINK_ERROR_DISCONNECTED); + ASSERT_OK(sd_event_exit(sd_varlink_get_event(link), EXIT_SUCCESS)); + return 0; +} + +TEST(fiber_no_reply) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_default(&e)); + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + ASSERT_OK(sd_varlink_server_new(&s, 0)); + + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + + ASSERT_OK(varlink_server_bind_fiber(s, "io.test.FiberNoReply", method_fiber_no_reply)); + + int connfd[2]; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, connfd)); + ASSERT_OK(sd_varlink_server_add_connection(s, connfd[0], /* ret= */ NULL)); + + _cleanup_(sd_varlink_unrefp) sd_varlink *c = NULL; + ASSERT_OK(sd_varlink_connect_fd(&c, connfd[1])); + + ASSERT_OK(sd_varlink_attach_event(c, e, 0)); + + ASSERT_OK(sd_varlink_bind_reply(c, reply_fiber_no_reply)); + + ASSERT_OK(sd_varlink_invoke(c, "io.test.FiberNoReply", /* parameters= */ NULL)); + + ASSERT_OK(sd_event_loop(e)); +} + +static int fiber_stashed_deferred_reply(sd_event_source *s, void *userdata) { + _cleanup_(sd_varlink_unrefp) sd_varlink *link = ASSERT_PTR(userdata); + + sd_event_source_disable_unref(s); + ASSERT_OK(sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("result", "stashed"))); + return 0; +} + +static int method_fiber_stash(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + /* Stash a ref on the connection so n_ref > 2 when the fiber returns, and reply later from a + * deferred event source. The fiber's post-callback logic should see the extra ref and treat + * this as a valid deferred-reply case instead of failing the connection. */ + sd_event_source *source; + + ASSERT_OK(sd_event_add_defer(sd_varlink_get_event(link), &source, fiber_stashed_deferred_reply, sd_varlink_ref(link))); + ASSERT_OK(sd_event_source_set_enabled(source, SD_EVENT_ONESHOT)); + return 0; +} + +static int reply_fiber_stash(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + ASSERT_NULL(error_id); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(parameters, "result")), "stashed"); + ASSERT_OK(sd_event_exit(sd_varlink_get_event(link), EXIT_SUCCESS)); + return 0; +} + +TEST(fiber_stash) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_default(&e)); + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + ASSERT_OK(sd_varlink_server_new(&s, 0)); + + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + + ASSERT_OK(varlink_server_bind_fiber(s, "io.test.FiberStash", method_fiber_stash)); + + int connfd[2]; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, connfd)); + ASSERT_OK(sd_varlink_server_add_connection(s, connfd[0], /* ret= */ NULL)); + + _cleanup_(sd_varlink_unrefp) sd_varlink *c = NULL; + ASSERT_OK(sd_varlink_connect_fd(&c, connfd[1])); + + ASSERT_OK(sd_varlink_attach_event(c, e, 0)); + + ASSERT_OK(sd_varlink_bind_reply(c, reply_fiber_stash)); + + ASSERT_OK(sd_varlink_invoke(c, "io.test.FiberStash", /* parameters= */ NULL)); + + ASSERT_OK(sd_event_loop(e)); +} + static int method_with_fd_sentinel(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF; @@ -619,7 +869,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 +975,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 +1002,385 @@ 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 the client shuts down its write side (giving us a clean + * EOF), reverse what we got, and write it back. Use suspending I/O so other fibers (the client) + * can make progress while we're waiting on the socket. */ + 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); + + return sd_varlink_reply(link, /* parameters= */ NULL); +} + +static int upgrade_client_fiber(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); + + ASSERT_OK(sd_event_exit(sd_fiber_get_event(), EXIT_SUCCESS)); + return 0; +} + +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; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + 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")); + /* The method does raw I/O on the upgraded socket — bind it as a fiber method so it can + * suspend on loop_read()/loop_write() and the client fiber can make progress concurrently. */ + ASSERT_OK(varlink_server_bind_fiber(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(sd_fiber_new(e, "upgrade-client", upgrade_client_fiber, (void*) sp, /* destroy= */ NULL, &f)); + + /* Run the event loop. Exits on idle once the client fiber completes and all server connections + * have been torn down. */ + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(f)); +} + +static int upgrade_pipelining_client_fiber(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"); + + ASSERT_OK(sd_event_exit(sd_fiber_get_event(), EXIT_SUCCESS)); + return 0; +} + +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; + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + 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-pipelining-server")); + /* method_upgrade does raw I/O on the upgraded socket, so bind as a fiber method. */ + ASSERT_OK(varlink_server_bind_fiber(s, "io.test.Upgrade", method_upgrade)); + ASSERT_OK(sd_varlink_server_listen_address(s, sp, 0600)); + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + + ASSERT_OK(sd_fiber_new(e, "upgrade-pipelining-client", upgrade_pipelining_client_fiber, (void*) sp, /* destroy= */ NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(f)); +} + +typedef struct ExecDirServer { + sd_varlink_server *server; + const char *name; +} 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 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; +} + +typedef struct ExecDirClientArgs { + const char *tmpdir; + size_t n_servers; + size_t *reply_count; +} ExecDirClientArgs; + +static int execute_dir_client_fiber(void *arg) { + ExecDirClientArgs *a = ASSERT_PTR(arg); + + ASSERT_OK_EQ(varlink_execute_directory( + a->tmpdir, + "io.test.ExecDirPing", + /* parameters= */ NULL, + /* more= */ false, + /* timeout_usec= */ USEC_INFINITY, + execute_dir_reply, + a->reply_count), (ssize_t) a->n_servers); + ASSERT_EQ(*a->reply_count, a->n_servers); + + /* Calling the helper against a non-existent directory must fail. */ + _cleanup_free_ char *nope = NULL; + ASSERT_OK(asprintf(&nope, "%s/does-not-exist", a->tmpdir)); + ASSERT_FAIL(varlink_execute_directory( + nope, + "io.test.ExecDirPing", + /* parameters= */ NULL, + /* more= */ false, + /* timeout_usec= */ USEC_INFINITY, + execute_dir_reply, + a->reply_count)); + + /* An empty directory must simply return 0 and not invoke the reply callback. */ + _cleanup_free_ char *empty = ASSERT_PTR(path_join(a->tmpdir, "empty")); + ASSERT_OK_ERRNO(mkdir(empty, 0755)); + + size_t count_before = *a->reply_count; + ASSERT_OK_ZERO(varlink_execute_directory( + empty, + "io.test.ExecDirPing", + /* parameters= */ NULL, + /* more= */ false, + /* timeout_usec= */ USEC_INFINITY, + execute_dir_reply, + a->reply_count)); + ASSERT_EQ(*a->reply_count, count_before); + + ASSERT_OK(sd_event_exit(sd_fiber_get_event(), EXIT_SUCCESS)); + return 0; +} + +TEST(execute_directory) { + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_future_unrefp) sd_future *f = 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)); + + ASSERT_OK(sd_event_new(&e)); + + 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(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_listen_address(eds->server, j, 0600)); + ASSERT_OK(sd_varlink_server_attach_event(eds->server, e, 0)); + } + + ExecDirClientArgs args = { + .tmpdir = tmpdir, + .n_servers = ELEMENTSOF(names), + .reply_count = &reply_count, + }; + ASSERT_OK(sd_fiber_new(e, "execute-dir-client", execute_dir_client_fiber, &args, NULL, &f)); + + ASSERT_OK(sd_event_loop(e)); + + ASSERT_OK(sd_future_result(f)); + + FOREACH_ELEMENT(eds, servers) + eds->server = sd_varlink_server_unref(eds->server); +} + +#define CTRUNC_N_FDS 64U + +static int method_ctrunc(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + /* Peek the first fd the client supposedly sent. With SCM_RIGHTS truncated by the kernel + * because the receiver hit RLIMIT_NOFILE, sd_varlink_peek_fd() returns -ENXIO once we + * walk past the last successfully installed fd. We're forcing the missing-fd case here, + * so we expect to fail and let varlink translate -ENXIO into io.systemd.System for the + * peer. */ + int fd = sd_varlink_peek_fd(link, CTRUNC_N_FDS - 1); + if (fd < 0) + return fd; + + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_INTEGER("ok", 1)); +} + +static int reply_ctrunc(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + /* We expect a clean system error back rather than a hanging connection: the server + * dropped the truncated fds and our handler surfaced -ENXIO, which varlink wraps as + * io.systemd.System. */ + ASSERT_STREQ(error_id, SD_VARLINK_ERROR_SYSTEM); + ASSERT_ERROR(sd_varlink_error_to_errno(error_id, parameters), ENXIO); + + ASSERT_OK(sd_event_exit(sd_varlink_get_event(link), EXIT_SUCCESS)); + return 0; +} + +TEST(ctrunc) { + int r; + + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_default(&e)); + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + ASSERT_OK(sd_varlink_server_new(&s, SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT|SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT)); + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + ASSERT_OK(sd_varlink_server_bind_method(s, "io.test.CTrunc", method_ctrunc)); + + int connfd[2]; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, connfd)); + ASSERT_OK(sd_varlink_server_add_connection(s, connfd[0], /* ret= */ NULL)); + + _cleanup_(sd_varlink_unrefp) sd_varlink *c = NULL; + ASSERT_OK(sd_varlink_connect_fd(&c, connfd[1])); + ASSERT_OK(sd_varlink_set_allow_fd_passing_input(c, true)); + ASSERT_OK(sd_varlink_set_allow_fd_passing_output(c, true)); + ASSERT_OK(sd_varlink_attach_event(c, e, 0)); + ASSERT_OK(sd_varlink_bind_reply(c, reply_ctrunc)); + + /* Open a batch of memfds we'll attach to the call. We push duplicates so the originals + * keep occupying fd table slots after the stream sends and closes the dup'd copies, + * making the receiver hit RLIMIT_NOFILE when it tries to install the incoming fds. */ + int originals[CTRUNC_N_FDS]; + for (size_t i = 0; i < CTRUNC_N_FDS; i++) + originals[i] = -EBADF; + CLEANUP_ELEMENTS(originals, close_many_unset); + + for (size_t i = 0; i < CTRUNC_N_FDS; i++) { + originals[i] = ASSERT_OK(memfd_new_and_seal_string("ctrunc", "x")); + ASSERT_OK_EQ(sd_varlink_push_dup_fd(c, originals[i]), (int) i); + } + + /* Constrain RLIMIT_NOFILE so the server can't install every received fd. The kernel + * will then drop the remaining fds from the SCM_RIGHTS message and set MSG_CTRUNC, + * which is precisely what an LSM denial (or a real fd-table-full peer) looks like to + * the receive side. Pick the new limit slightly above our current open-fd count so + * the kernel can install only a handful of received fds before failing the rest. */ + struct rlimit orig_rl; + ASSERT_OK_ERRNO(getrlimit(RLIMIT_NOFILE, &orig_rl)); + + size_t n_open = 0; + _cleanup_closedir_ DIR *d = ASSERT_NOT_NULL(opendir("/proc/self/fd")); + FOREACH_DIRENT_ALL(de, d, break) + if (!dot_or_dot_dot(de->d_name)) + n_open++; + + /* n_open currently includes the CTRUNC_N_FDS dup'd fds that the stream will close once + * the message has been sent. After the send, we'll be back down to n_open - CTRUNC_N_FDS + * fds. Set the limit just slightly above that, so the kernel can install only a handful + * of the CTRUNC_N_FDS incoming fds before failing the rest with MSG_CTRUNC. */ + ASSERT_GT(n_open, CTRUNC_N_FDS); + struct rlimit new_rl = { + .rlim_cur = n_open - CTRUNC_N_FDS + 8, + .rlim_max = orig_rl.rlim_max, + }; + ASSERT_OK_ERRNO(setrlimit(RLIMIT_NOFILE, &new_rl)); + + r = sd_varlink_invoke(c, "io.test.CTrunc", /* parameters= */ NULL); + if (r >= 0) + r = sd_event_loop(e); + + ASSERT_OK_ERRNO(setrlimit(RLIMIT_NOFILE, &orig_rl)); + ASSERT_OK(r); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd/sd-varlink/varlink-internal.h b/src/libsystemd/sd-varlink/varlink-internal.h index 39b15e12a1fee..beec5be42c709 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,27 @@ 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; - - 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; + sd_varlink_server *server; - int *output_fds; /* file descriptors associated with the data in output_buffer (for fd passing) */ - size_t n_output_fds; + /* 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; - /* 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; + unsigned n_pending; - /* 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; + /* 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; sd_varlink_reply_t reply_callback; @@ -157,37 +100,16 @@ 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 af; /* address family if socket; AF_UNSPEC if not socket; negative if not known */ + int *pushed_fds; + size_t n_pushed_fds; - usec_t timestamp; - usec_t timeout; + sd_json_variant *previous; + int *previous_fds; + size_t n_previous_fds; + char *sentinel; 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; @@ -213,7 +135,8 @@ typedef struct sd_varlink_server { LIST_HEAD(VarlinkServerSocket, sockets); - Hashmap *methods; /* Fully qualified symbol name of a method → VarlinkMethod */ + Hashmap *methods; /* Fully qualified symbol name of a method → sd_varlink_method_t */ + Hashmap *fiber_methods; /* Fully qualified symbol name of a fiber method → sd_varlink_method_t */ Hashmap *interfaces; /* Fully qualified interface name → VarlinkInterface* */ Hashmap *symbols; /* Fully qualified symbol name of method/error → VarlinkSymbol* */ sd_varlink_connect_t connect_callback; @@ -222,8 +145,12 @@ typedef struct sd_varlink_server { sd_event *event; int64_t event_priority; - unsigned n_connections; Hashmap *by_uid; /* UID_TO_PTR(uid) → UINT_TO_PTR(n_connections) */ + unsigned n_connections; + unsigned connections_max; + unsigned connections_per_uid_max; + + bool exit_on_idle; void *userdata; @@ -232,11 +159,6 @@ typedef struct sd_varlink_server { char *product; char *version; char *url; - - unsigned connections_max; - unsigned connections_per_uid_max; - - bool exit_on_idle; } sd_varlink_server; #define varlink_log_errno(v, error, fmt, ...) \ @@ -252,7 +174,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-org.varlink.service.c b/src/libsystemd/sd-varlink/varlink-org.varlink.service.c index d91670899af3a..57acf052e522a 100644 --- a/src/libsystemd/sd-varlink/varlink-org.varlink.service.c +++ b/src/libsystemd/sd-varlink/varlink-org.varlink.service.c @@ -34,7 +34,7 @@ static SD_VARLINK_DEFINE_ERROR( static SD_VARLINK_DEFINE_ERROR( MethodNotImplemented, - SD_VARLINK_FIELD_COMMENT("Name of method that was called but is not implemented."), + SD_VARLINK_FIELD_COMMENT("Name of method that was called but is not implemented"), SD_VARLINK_DEFINE_FIELD(method, SD_VARLINK_STRING, 0)); static SD_VARLINK_DEFINE_ERROR( @@ -59,7 +59,7 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_error_InterfaceNotFound, SD_VARLINK_SYMBOL_COMMENT("Error returned if an unknown method is called on an known interface"), &vl_error_MethodNotFound, - SD_VARLINK_SYMBOL_COMMENT("Error returned if an method is called that is known but not implemented"), + SD_VARLINK_SYMBOL_COMMENT("Error returned if a method is called that is known but not implemented"), &vl_error_MethodNotImplemented, SD_VARLINK_SYMBOL_COMMENT("Error returned if a method is called with an invalid parameter"), &vl_error_InvalidParameter, diff --git a/src/libsystemd/sd-varlink/varlink-util.c b/src/libsystemd/sd-varlink/varlink-util.c index 916ac2ba996fe..d454fc7f48255 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,168 @@ 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_MUST_BE_SOCKET|RECURSE_DIR_MUST_BE_SYMLINK, &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; + + 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..d5765ca2c72f1 100644 --- a/src/libsystemd/sd-varlink/varlink-util.h +++ b/src/libsystemd/sd-varlink/varlink-util.h @@ -19,6 +19,10 @@ int varlink_many_notifyb(Set *s, ...); int varlink_many_reply(Set *s, sd_json_variant *parameters); int varlink_many_error(Set *s, const char *error_id, sd_json_variant *parameters); +int varlink_server_bind_fiber(sd_varlink_server *s, const char *method, sd_varlink_method_t callback); +int varlink_server_bind_fiber_many_internal(sd_varlink_server *s, ...); +#define varlink_server_bind_fiber_many(s, ...) varlink_server_bind_fiber_many_internal(s, __VA_ARGS__, NULL) + int varlink_set_info_systemd(sd_varlink_server *server); int varlink_server_new( @@ -28,6 +32,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/libudev.pc.in b/src/libudev/libudev.pc.in index 6541bcb1ab6b8..72d46ffc50530 100644 --- a/src/libudev/libudev.pc.in +++ b/src/libudev/libudev.pc.in @@ -16,5 +16,4 @@ Name: libudev Description: Library to access udev device information Version: {{PROJECT_VERSION}} Libs: -L${libdir} -ludev -Libs.private: -lrt -pthread Cflags: -I${includedir} diff --git a/src/libudev/meson.build b/src/libudev/meson.build index eb9dda79ae2fc..854628aed42ce 100644 --- a/src/libudev/meson.build +++ b/src/libudev/meson.build @@ -15,7 +15,7 @@ sources += libudev_sources ############################################################ -libudev_includes = [includes, include_directories('.')] +libudev_includes = [include_directories('.'), includes] libudev_dir_path = meson.current_source_dir() diff --git a/src/libudev/test-libudev.c b/src/libudev/test-libudev.c index 63c24031240e6..06feb1ffbc61a 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,56 @@ 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 opts = { argc, argv }; - while ((c = getopt_long(argc, argv, "p:s:dhVm", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) 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 = opts.arg; break; - case 's': - *subsystem = optarg; + OPTION('s', "subsystem", "SUBSYSTEM", "Subsystem to enumerate"): + *subsystem = opts.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/kbd-model-map b/src/locale/kbd-model-map index 612f6d749a76f..819e19c61704f 100644 --- a/src/locale/kbd-model-map +++ b/src/locale/kbd-model-map @@ -1,73 +1,77 @@ # Originally generated from system-config-keyboard's model list. -# consolelayout xlayout xmodel xvariant xoptions -sg ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp -nl nl pc105 - terminate:ctrl_alt_bksp -mk-utf mk,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -trq tr pc105 - terminate:ctrl_alt_bksp -uk gb pc105 - terminate:ctrl_alt_bksp -is-latin1 is pc105 - terminate:ctrl_alt_bksp -de de pc105 - terminate:ctrl_alt_bksp -la-latin1 latam pc105 - terminate:ctrl_alt_bksp -us us pc105+inet - terminate:ctrl_alt_bksp -ko kr pc105 - terminate:ctrl_alt_bksp -ro-std ro pc105 std terminate:ctrl_alt_bksp -de-latin1 de pc105 - terminate:ctrl_alt_bksp -slovene si pc105 - terminate:ctrl_alt_bksp -hu hu pc105 - terminate:ctrl_alt_bksp -jp106 jp jp106 - terminate:ctrl_alt_bksp -croat hr pc105 - terminate:ctrl_alt_bksp -it2 it pc105 - terminate:ctrl_alt_bksp -hu101 hu pc105 qwerty terminate:ctrl_alt_bksp -sr-latin rs pc105 latin terminate:ctrl_alt_bksp -fi fi pc105 - terminate:ctrl_alt_bksp -fr_CH ch pc105 fr terminate:ctrl_alt_bksp -dk-latin1 dk pc105 - terminate:ctrl_alt_bksp -fr fr pc105 - terminate:ctrl_alt_bksp -it it pc105 - terminate:ctrl_alt_bksp -ua-utf ua,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -fr-latin1 fr pc105 - terminate:ctrl_alt_bksp -sg-latin1 ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp -be-latin1 be pc105 - terminate:ctrl_alt_bksp -dk dk pc105 - terminate:ctrl_alt_bksp -fr-pc fr pc105 - terminate:ctrl_alt_bksp -bg_pho-utf8 bg,us pc105 ,phonetic terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -it-ibm it pc105 - terminate:ctrl_alt_bksp -cz-us-qwertz cz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -cz-qwerty cz,us pc105 qwerty, terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -br-abnt2 br abnt2 - terminate:ctrl_alt_bksp -ro ro pc105 - terminate:ctrl_alt_bksp -us-acentos us pc105 intl terminate:ctrl_alt_bksp -pt-latin1 pt pc105 - terminate:ctrl_alt_bksp -ro-std-cedilla ro pc105 std_cedilla terminate:ctrl_alt_bksp -tj_alt-UTF8 tj pc105 - terminate:ctrl_alt_bksp -de-latin1-nodeadkeys de pc105 nodeadkeys terminate:ctrl_alt_bksp -no no pc105 - terminate:ctrl_alt_bksp -bg_bds-utf8 bg,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -dvorak us pc105 dvorak terminate:ctrl_alt_bksp -dvorak us pc105 dvorak-alt-intl terminate:ctrl_alt_bksp -ru ru,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -cz-lat2 cz pc105 qwerty terminate:ctrl_alt_bksp -pl2 pl pc105 - terminate:ctrl_alt_bksp -es es pc105 - terminate:ctrl_alt_bksp -ro-cedilla ro pc105 cedilla terminate:ctrl_alt_bksp -ie ie pc105 - terminate:ctrl_alt_bksp -et ee pc105 - terminate:ctrl_alt_bksp -sk-qwerty sk pc105 qwerty terminate:ctrl_alt_bksp -sk-qwertz sk pc105 - terminate:ctrl_alt_bksp -fr-latin9 fr pc105 latin9 terminate:ctrl_alt_bksp -fr_CH-latin1 ch pc105 fr terminate:ctrl_alt_bksp -cf ca pc105 - terminate:ctrl_alt_bksp -sv-latin1 se pc105 - terminate:ctrl_alt_bksp -sr-cy rs pc105 - terminate:ctrl_alt_bksp -gr gr,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -by by,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -il il pc105 - terminate:ctrl_alt_bksp -kazakh kz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll -lt.baltic lt pc105 - terminate:ctrl_alt_bksp -lt.l4 lt pc105 - terminate:ctrl_alt_bksp -lt lt pc105 - terminate:ctrl_alt_bksp -khmer kh,us pc105 - terminate:ctrl_alt_bksp -es-dvorak es microsoftpro dvorak terminate:ctrl_alt_bksp -lv lv pc105 apostrophe terminate:ctrl_alt_bksp -lv-tilde lv pc105 tilde terminate:ctrl_alt_bksp -ge ge,us pc105 - terminate:ctrl_alt_bksp +# The sixth column is an optional comma-separated list of RFC 4646 / BCP 47 +# language tags the row matches; used to map a firmware-reported keyboard +# layout to a vconsole keymap. Use "-" or omit when no tags apply. +# consolelayout xlayout xmodel xvariant xoptions bcp47 +sg ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp de-CH +nl nl pc105 - terminate:ctrl_alt_bksp nl-NL,nl +mk-utf mk,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll mk-MK,mk +trq tr pc105 - terminate:ctrl_alt_bksp tr-TR,tr +uk gb pc105 - terminate:ctrl_alt_bksp en-GB +is-latin1 is pc105 - terminate:ctrl_alt_bksp is-IS,is +de de pc105 - terminate:ctrl_alt_bksp de-DE,de-AT,de +la-latin1 latam pc105 - terminate:ctrl_alt_bksp es-419,es-MX,es-AR,es-CO,es-CL,es-PE,es-VE +us us pc105+inet - terminate:ctrl_alt_bksp en-US,en +ko kr pc105 - terminate:ctrl_alt_bksp ko-KR,ko +ro-std ro pc105 std terminate:ctrl_alt_bksp - +de-latin1 de pc105 - terminate:ctrl_alt_bksp - +slovene si pc105 - terminate:ctrl_alt_bksp sl-SI,sl +hu hu pc105 - terminate:ctrl_alt_bksp hu-HU,hu +jp106 jp jp106 - terminate:ctrl_alt_bksp ja-JP,ja +croat hr pc105 - terminate:ctrl_alt_bksp hr-HR,hr +it2 it pc105 - terminate:ctrl_alt_bksp - +hu101 hu pc105 qwerty terminate:ctrl_alt_bksp - +sr-latin rs pc105 latin terminate:ctrl_alt_bksp sr-Latn-RS,sr-Latn +fi fi pc105 - terminate:ctrl_alt_bksp fi-FI,fi +fr_CH ch pc105 fr terminate:ctrl_alt_bksp fr-CH +dk-latin1 dk pc105 - terminate:ctrl_alt_bksp da-DK,da +fr fr pc105 - terminate:ctrl_alt_bksp fr-FR,fr +it it pc105 - terminate:ctrl_alt_bksp it-IT,it-CH,it +ua-utf ua,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll uk-UA,uk +fr-latin1 fr pc105 - terminate:ctrl_alt_bksp - +sg-latin1 ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp - +be-latin1 be pc105 - terminate:ctrl_alt_bksp fr-BE,nl-BE +dk dk pc105 - terminate:ctrl_alt_bksp - +fr-pc fr pc105 - terminate:ctrl_alt_bksp - +bg_pho-utf8 bg,us pc105 ,phonetic terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll bg-BG,bg +it-ibm it pc105 - terminate:ctrl_alt_bksp - +cz-us-qwertz cz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll cs-CZ,cs +cz-qwerty cz,us pc105 qwerty, terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll - +br-abnt2 br abnt2 - terminate:ctrl_alt_bksp pt-BR +ro ro pc105 - terminate:ctrl_alt_bksp ro-RO,ro +us-acentos us pc105 intl terminate:ctrl_alt_bksp - +pt-latin1 pt pc105 - terminate:ctrl_alt_bksp pt-PT,pt +ro-std-cedilla ro pc105 std_cedilla terminate:ctrl_alt_bksp - +tj_alt-UTF8 tj pc105 - terminate:ctrl_alt_bksp tg-TJ,tg +de-latin1-nodeadkeys de pc105 nodeadkeys terminate:ctrl_alt_bksp - +no no pc105 - terminate:ctrl_alt_bksp nb-NO,nn-NO,no +bg_bds-utf8 bg,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll - +dvorak us pc105 dvorak terminate:ctrl_alt_bksp - +dvorak us pc105 dvorak-alt-intl terminate:ctrl_alt_bksp - +ru ru,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll ru-RU,ru +cz-lat2 cz pc105 qwerty terminate:ctrl_alt_bksp - +pl2 pl pc105 - terminate:ctrl_alt_bksp pl-PL,pl +es es pc105 - terminate:ctrl_alt_bksp es-ES,es +ro-cedilla ro pc105 cedilla terminate:ctrl_alt_bksp - +ie ie pc105 - terminate:ctrl_alt_bksp en-IE,ga-IE,ga +et ee pc105 - terminate:ctrl_alt_bksp et-EE,et +sk-qwerty sk pc105 qwerty terminate:ctrl_alt_bksp - +sk-qwertz sk pc105 - terminate:ctrl_alt_bksp sk-SK,sk +fr-latin9 fr pc105 latin9 terminate:ctrl_alt_bksp - +fr_CH-latin1 ch pc105 fr terminate:ctrl_alt_bksp - +cf ca pc105 - terminate:ctrl_alt_bksp fr-CA +sv-latin1 se pc105 - terminate:ctrl_alt_bksp sv-SE,sv +sr-cy rs pc105 - terminate:ctrl_alt_bksp sr-Cyrl-RS,sr-Cyrl,sr-RS,sr +gr gr,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll el-GR,el +by by,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll be-BY,be +il il pc105 - terminate:ctrl_alt_bksp he-IL,he +kazakh kz,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll kk-KZ,kk +lt.baltic lt pc105 - terminate:ctrl_alt_bksp - +lt.l4 lt pc105 - terminate:ctrl_alt_bksp - +lt lt pc105 - terminate:ctrl_alt_bksp lt-LT,lt +khmer kh,us pc105 - terminate:ctrl_alt_bksp km-KH,km +es-dvorak es microsoftpro dvorak terminate:ctrl_alt_bksp - +lv lv pc105 apostrophe terminate:ctrl_alt_bksp lv-LV,lv +lv-tilde lv pc105 tilde terminate:ctrl_alt_bksp - +ge ge,us pc105 - terminate:ctrl_alt_bksp ka-GE,ka +ara ara,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scroll ar-SA,ar diff --git a/src/locale/localectl.c b/src/locale/localectl.c index 63703007ad527..ff7afa6c841dd 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -1,9 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" +#include "ansi-color.h" #include "alloc-util.h" #include "build.h" #include "bus-error.h" @@ -13,15 +12,16 @@ #include "fd-util.h" #include "fileio.h" #include "format-table.h" +#include "help-util.h" #include "kbd-util.h" #include "locale-setup.h" #include "main-func.h" #include "memory-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "path-util.h" #include "polkit-agent.h" -#include "pretty-print.h" #include "runtime-scope.h" #include "string-util.h" #include "strv.h" @@ -144,14 +144,11 @@ 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) { +VERB_DEFAULT_NOARG(verb_show_status, "status", "Show current locale settings"); +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 +180,9 @@ 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) { +VERB(verb_set_locale, "set-locale", "LOCALE...", 2, VERB_ANY, 0, + "Set system locale"); +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 +210,9 @@ static int set_locale(int argc, char **argv, void *userdata) { return 0; } -static int list_locales(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_list_locales, "list-locales", + "Show known locales"); +static int verb_list_locales(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_strv_free_ char **l = NULL; int r; @@ -225,7 +226,9 @@ static int list_locales(int argc, char **argv, void *userdata) { return 0; } -static int set_vconsole_keymap(int argc, char **argv, void *userdata) { +VERB(verb_set_vconsole_keymap, "set-keymap", "MAP [MAP]", 2, 3, 0, + "Set console and X11 keyboard mappings"); +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 +252,9 @@ static int set_vconsole_keymap(int argc, char **argv, void *userdata) { return 0; } -static int list_vconsole_keymaps(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_list_vconsole_keymaps, "list-keymaps", + "Show known virtual console keyboard mappings"); +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 +269,9 @@ static int list_vconsole_keymaps(int argc, char **argv, void *userdata) { return 0; } -static int set_x11_keymap(int argc, char **argv, void *userdata) { +VERB(verb_set_x11_keymap, "set-x11-keymap", "LAYOUT [MODEL [VARIANT [OPTIONS]]]", 2, 5, 0, + "Set X11 and console keyboard mappings"); +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 +306,15 @@ static const char* xkb_directory(void) { return cached; } -static int list_x11_keymaps(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_list_x11_keymaps, "list-x11-keymap-models", + "Show known X11 keyboard mapping models"); +VERB_NOARG(verb_list_x11_keymaps, "list-x11-keymap-layouts", + "Show known X11 keyboard mapping layouts"); +VERB(verb_list_x11_keymaps, "list-x11-keymap-variants", "[LAYOUT]", VERB_ANY, 2, 0, + "Show known X11 keyboard mapping variants"); +VERB_NOARG(verb_list_x11_keymaps, "list-x11-keymap-options", + "Show known X11 keyboard mapping options"); +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 { @@ -406,143 +421,88 @@ static int list_x11_keymaps(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("localectl", "1", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; - printf("%s [OPTIONS...] COMMAND ...\n\n" - "%sQuery or change system locale and keyboard settings.%s\n" - "\nCommands:\n" - " status Show current locale settings\n" - " set-locale LOCALE... Set system locale\n" - " list-locales Show known locales\n" - " set-keymap MAP [MAP] Set console and X11 keyboard mappings\n" - " list-keymaps Show known virtual console keyboard mappings\n" - " set-x11-keymap LAYOUT [MODEL [VARIANT [OPTIONS]]]\n" - " Set X11 and console keyboard mappings\n" - " list-x11-keymap-models Show known X11 keyboard mapping models\n" - " list-x11-keymap-layouts Show known X11 keyboard mapping layouts\n" - " list-x11-keymap-variants [LAYOUT]\n" - " Show known X11 keyboard mapping variants\n" - " list-x11-keymap-options Show known X11 keyboard mapping options\n" - "\nOptions:\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -l --full Do not ellipsize output\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" - " --no-convert Don't convert keyboard mappings\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; - return 0; -} + (void) table_sync_column_widths(0, verbs, options); -static int verb_help(int argc, char **argv, void *userdata) { - return help(); -} + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Query or change system locale and keyboard settings."); -static int parse_argv(int argc, char *argv[]) { + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_CONVERT, - ARG_NO_ASK_PASSWORD - }; + help_section("Options"); + 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 }, - { "full", no_argument, NULL, 'l' }, - { "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 }, - { "no-convert", no_argument, NULL, ARG_NO_CONVERT }, - {} - }; + help_man_page_reference("localectl", "1"); + + return 0; +} - int r, c; +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, "hlH:M:", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'l': + OPTION('l', "full", NULL, "Do not ellipsize output"): arg_full = true; break; - case ARG_NO_CONVERT: - arg_convert = false; - break; - - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; 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 = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION_LONG("no-convert", NULL, "Don't convert keyboard mappings"): + arg_convert = false; + break; } + *remaining_args = option_parser_get_args(&opts); return 1; } -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. */ - {} - }; - - 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; @@ -550,7 +510,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; @@ -558,7 +519,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM); - return localectl_main(bus, argc, argv); + return dispatch_verb(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/locale/localed-util.c b/src/locale/localed-util.c index 3fe039a053061..ab2abd7daeae3 100644 --- a/src/locale/localed-util.c +++ b/src/locale/localed-util.c @@ -17,7 +17,7 @@ #include "kbd-util.h" #include "localed-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "stat-util.h" #include "string-util.h" #include "strv.h" @@ -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; @@ -412,7 +412,7 @@ static bool locale_encoding_is_utf8_or_unspecified(const char *locale) { } static int locale_gen_locale_supported(const char *locale_entry) { - /* Returns an error valus <= 0 if the locale-gen entry is invalid or unsupported, + /* Returns an error value <= 0 if the locale-gen entry is invalid or unsupported, * 1 in case the locale entry is valid, and -EOPNOTSUPP specifically in case * the distributor has not provided us with a SUPPORTED file to check * locale for validity. */ diff --git a/src/locale/localed.c b/src/locale/localed.c index 041ba29cd8a39..4296db6f2f9b4 100644 --- a/src/locale/localed.c +++ b/src/locale/localed.c @@ -280,6 +280,7 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er /* good_user= */ UID_INVALID, interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -384,6 +385,7 @@ static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_erro /* good_user= */ UID_INVALID, interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -503,6 +505,7 @@ static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_err /* good_user= */ UID_INVALID, interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; diff --git a/src/locale/meson.build b/src/locale/meson.build index 1b97628abbc42..2f99bb8d8072a 100644 --- a/src/locale/meson.build +++ b/src/locale/meson.build @@ -18,7 +18,6 @@ localectl_sources = files('localectl.c') # specify where the headers are. if conf.get('HAVE_XKBCOMMON') == 1 libxkbcommon_deps = [ - libdl, libxkbcommon_cflags, ] else diff --git a/src/locale/xkbcommon-util.c b/src/locale/xkbcommon-util.c index 2334587e88ccd..181d14dd17cc8 100644 --- a/src/locale/xkbcommon-util.c +++ b/src/locale/xkbcommon-util.c @@ -1,26 +1,29 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dlopen.h" + #include "dlfcn-util.h" #include "log.h" #include "string-util.h" #include "xkbcommon-util.h" #if HAVE_XKBCOMMON -static void *xkbcommon_dl = NULL; - DLSYM_PROTOTYPE(xkb_context_new) = NULL; DLSYM_PROTOTYPE(xkb_context_unref) = NULL; 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) { + static void *xkbcommon_dl = NULL; + + 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..78c784c30fad8 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,99 @@ 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 opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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 = opts.arg; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION_LONG("who", "STRING", + "A descriptive string who is inhibiting"): + arg_who = opts.arg; break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION_LONG("why", "STRING", + "A descriptive string why is being inhibited"): + arg_why = opts.arg; break; - case ARG_NO_LEGEND: - arg_legend = false; + OPTION_LONG("mode", "MODE", "One of block, block-weak, or delay"): + arg_mode = opts.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(&opts); - 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 +272,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 +298,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 +315,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 +331,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..19ee4c5b75b71 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -8,6 +7,7 @@ #include "sd-journal.h" #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "bus-error.h" #include "bus-locator.h" @@ -19,14 +19,15 @@ #include "cgroup-util.h" #include "format-table.h" #include "format-util.h" +#include "help-util.h" #include "log.h" #include "logs-show.h" #include "main-func.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "polkit-agent.h" -#include "pretty-print.h" #include "process-util.h" #include "runtime-scope.h" #include "string-table.h" @@ -266,7 +267,10 @@ 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) { +VERB_GROUP("Session Commands"); + +VERB_DEFAULT_NOARG(verb_list_sessions, "list-sessions", "List sessions"); +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,126 +312,6 @@ 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 const struct bus_properties_map property_map[] = { - { "Linger", "b", NULL, offsetof(UserStatusInfo, linger) }, - { "State", "s", NULL, offsetof(UserStatusInfo, state) }, - {}, - }; - - _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; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - assert(argv); - - r = bus_call_method(bus, bus_login_mgr, "ListUsers", &error, &reply, NULL); - if (r < 0) - return log_error_errno(r, "Failed to list users: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, 'a', "(uso)"); - if (r < 0) - return bus_log_parse_error(r); - - table = table_new("uid", "user", "linger", "state"); - if (!table) - return log_oom(); - - (void) table_set_align_percent(table, TABLE_HEADER_CELL(0), 100); - table_set_ersatz_string(table, TABLE_ERSATZ_DASH); - - for (;;) { - _cleanup_(sd_bus_error_free) sd_bus_error error_property = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_property = NULL; - _cleanup_(user_status_info_done) UserStatusInfo info = {}; - const char *user, *object; - uint32_t uid; - - r = sd_bus_message_read(reply, "(uso)", &uid, &user, &object); - if (r < 0) - return bus_log_parse_error(r); - if (r == 0) - break; - - r = bus_map_all_properties(bus, - "org.freedesktop.login1", - object, - property_map, - BUS_MAP_BOOLEAN_AS_BOOL, - &error_property, - &reply_property, - &info); - if (r < 0) { - log_full_errno(sd_bus_error_has_name(&error_property, SD_BUS_ERROR_UNKNOWN_OBJECT) ? LOG_DEBUG : LOG_WARNING, - r, - "Failed to get properties of user %s, ignoring: %s", - user, bus_error_message(&error_property, r)); - continue; - } - - r = table_add_many(table, - TABLE_UID, (uid_t) uid, - TABLE_STRING, user, - TABLE_BOOLEAN, info.linger, - TABLE_STRING, info.state); - if (r < 0) - return table_log_add_error(r); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - return list_table_print(table, "users"); -} - -static int list_seats(int argc, char *argv[], 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; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - assert(argv); - - r = bus_call_method(bus, bus_login_mgr, "ListSeats", &error, &reply, NULL); - if (r < 0) - return log_error_errno(r, "Failed to list seats: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, 'a', "(so)"); - if (r < 0) - return bus_log_parse_error(r); - - table = table_new("seat"); - if (!table) - return log_oom(); - - table_set_ersatz_string(table, TABLE_ERSATZ_DASH); - - for (;;) { - const char *seat; - - r = sd_bus_message_read(reply, "(so)", &seat, NULL); - if (r < 0) - return bus_log_parse_error(r); - if (r == 0) - break; - - r = table_add_cell(table, NULL, TABLE_STRING, seat); - if (r < 0) - return table_log_add_error(r); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - return list_table_print(table, "seats"); -} - static int show_unit_cgroup( sd_bus *bus, const char *unit, @@ -716,9 +600,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 +705,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 +780,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 +922,11 @@ static int get_bus_path_by_id( return strdup_to(ret, path); } -static int show_session(int argc, char *argv[], void *userdata) { +VERB(verb_show_session, "session-status", "[ID…]", VERB_ANY, VERB_ANY, 0, + "Show session status"); +VERB(verb_show_session, "show-session", "[ID…]", VERB_ANY, VERB_ANY, 0, + "Show properties of sessions or the manager"); +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,104 +972,13 @@ static int show_session(int argc, char *argv[], void *userdata) { return 0; } -static int show_user(int argc, char *argv[], void *userdata) { - sd_bus *bus = ASSERT_PTR(userdata); - bool properties; - int r; - - assert(argv); - - properties = !strstr(argv[0], "status"); - - pager_open(arg_pager_flags); - - if (argc <= 1) { - /* If no argument is specified inspect the manager itself */ - if (properties) - return show_properties(bus, "/org/freedesktop/login1"); - - return print_user_status_info(bus, "/org/freedesktop/login1/user/self"); - } - - for (int i = 1, first = true; i < argc; i++, first = false) { - _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 *path; - uid_t uid; - - r = get_user_creds((const char**) (argv+i), &uid, NULL, NULL, NULL, 0); - if (r < 0) - return log_error_errno(r, "Failed to look up user %s: %m", argv[i]); - - r = bus_call_method(bus, bus_login_mgr, "GetUser", &error, &reply, "u", (uint32_t) uid); - if (r < 0) - return log_error_errno(r, "Failed to get user: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - if (!first) - putchar('\n'); - - if (properties) - r = show_properties(bus, path); - else - r = print_user_status_info(bus, path); - if (r < 0) - return r; - } - - return 0; -} - -static int show_seat(int argc, char *argv[], void *userdata) { - sd_bus *bus = ASSERT_PTR(userdata); - bool properties; - int r; - - assert(argv); - - properties = !strstr(argv[0], "status"); - - pager_open(arg_pager_flags); - - if (argc <= 1) { - _cleanup_free_ char *path = NULL; - - /* If no argument is specified inspect the manager itself */ - if (properties) - return show_properties(bus, "/org/freedesktop/login1"); - - r = get_bus_path_by_id(bus, "seat", "GetSeat", "auto", &path); - if (r < 0) - return r; - - return print_seat_status_info(bus, path); - } - - for (int i = 1, first = true; i < argc; i++, first = false) { - _cleanup_free_ char *path = NULL; - - r = get_bus_path_by_id(bus, "seat", "GetSeat", argv[i], &path); - if (r < 0) - return r; - - if (!first) - putchar('\n'); - - if (properties) - r = show_properties(bus, path); - else - r = print_seat_status_info(bus, path); - if (r < 0) - return r; - } - - return 0; -} - -static int activate(int argc, char *argv[], void *userdata) { +VERB(verb_activate, "activate", "[ID]", VERB_ANY, 2, 0, + "Activate a session"); +VERB(verb_activate, "lock-session", "[ID…]", VERB_ANY, VERB_ANY, 0, + "Screen lock one or more sessions"); +VERB(verb_activate, "unlock-session", "[ID…]", VERB_ANY, VERB_ANY, 0, + "Screen unlock one or more sessions"); +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 +1021,36 @@ static int activate(int argc, char *argv[], void *userdata) { return 0; } -static int kill_session(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_lock_sessions, "lock-sessions", "Screen lock all current sessions"); +VERB_NOARG(verb_lock_sessions, "unlock-sessions", "Screen unlock all current sessions"); +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; + + assert(argv); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + r = bus_call_method( + bus, + bus_login_mgr, + streq(argv[0], "lock-sessions") ? "LockSessions" : "UnlockSessions", + &error, NULL, + NULL); + if (r < 0) + return log_error_errno(r, "Could not lock sessions: %s", bus_error_message(&error, r)); + + return 0; +} + +/* The implementation is above, but we put this here to preserve the logical order in --help. */ +VERB(verb_activate, "terminate-session", "ID…", 2, VERB_ANY, 0, + "Terminate one or more sessions"); + +VERB(verb_kill_session, "kill-session", "ID…", 2, VERB_ANY, 0, + "Send signal to processes of a session"); +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,20 +1076,158 @@ static int kill_session(int argc, char *argv[], void *userdata) { return 0; } -static int enable_linger(int argc, char *argv[], void *userdata) { +VERB_GROUP("User Commands"); + +VERB_NOARG(verb_list_users, "list-users", "List users"); +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) }, + { "State", "s", NULL, offsetof(UserStatusInfo, state) }, + {}, + }; + _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; sd_bus *bus = ASSERT_PTR(userdata); - char* short_argv[3]; - bool b; int r; assert(argv); - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - b = streq(argv[0], "enable-linger"); + r = bus_call_method(bus, bus_login_mgr, "ListUsers", &error, &reply, NULL); + if (r < 0) + return log_error_errno(r, "Failed to list users: %s", bus_error_message(&error, r)); - if (argc < 2) { + r = sd_bus_message_enter_container(reply, 'a', "(uso)"); + if (r < 0) + return bus_log_parse_error(r); + + table = table_new("uid", "user", "linger", "state"); + if (!table) + return log_oom(); + + (void) table_set_align_percent(table, TABLE_HEADER_CELL(0), 100); + table_set_ersatz_string(table, TABLE_ERSATZ_DASH); + + for (;;) { + _cleanup_(sd_bus_error_free) sd_bus_error error_property = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_property = NULL; + _cleanup_(user_status_info_done) UserStatusInfo info = {}; + const char *user, *object; + uint32_t uid; + + r = sd_bus_message_read(reply, "(uso)", &uid, &user, &object); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + r = bus_map_all_properties(bus, + "org.freedesktop.login1", + object, + property_map, + BUS_MAP_BOOLEAN_AS_BOOL, + &error_property, + &reply_property, + &info); + if (r < 0) { + log_full_errno(sd_bus_error_has_name(&error_property, SD_BUS_ERROR_UNKNOWN_OBJECT) ? LOG_DEBUG : LOG_WARNING, + r, + "Failed to get properties of user %s, ignoring: %s", + user, bus_error_message(&error_property, r)); + continue; + } + + r = table_add_many(table, + TABLE_UID, (uid_t) uid, + TABLE_STRING, user, + TABLE_BOOLEAN, info.linger, + TABLE_STRING, info.state); + if (r < 0) + return table_log_add_error(r); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return list_table_print(table, "users"); +} + +VERB(verb_show_user, "user-status", "[USER…]", VERB_ANY, VERB_ANY, 0, + "Show user status"); +VERB(verb_show_user, "show-user", "[USER…]", VERB_ANY, VERB_ANY, 0, + "Show properties of users or the manager"); +static int verb_show_user(int argc, char *argv[], uintptr_t _data, void *userdata) { + sd_bus *bus = ASSERT_PTR(userdata); + bool properties; + int r; + + assert(argv); + + properties = !strstr(argv[0], "status"); + + pager_open(arg_pager_flags); + + if (argc <= 1) { + /* If no argument is specified inspect the manager itself */ + if (properties) + return show_properties(bus, "/org/freedesktop/login1"); + + return print_user_status_info(bus, "/org/freedesktop/login1/user/self"); + } + + for (int i = 1, first = true; i < argc; i++, first = false) { + _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 *path; + uid_t uid; + + r = get_user_creds(argv[i], /* flags= */ 0, NULL, &uid, NULL, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to look up user %s: %m", argv[i]); + + r = bus_call_method(bus, bus_login_mgr, "GetUser", &error, &reply, "u", (uint32_t) uid); + if (r < 0) + return log_error_errno(r, "Failed to get user: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + if (!first) + putchar('\n'); + + if (properties) + r = show_properties(bus, path); + else + r = print_user_status_info(bus, path); + if (r < 0) + return r; + } + + return 0; +} + +VERB(verb_enable_linger, "enable-linger", "[USER…]", VERB_ANY, VERB_ANY, 0, + "Enable linger state of one or more users"); +VERB(verb_enable_linger, "disable-linger", "[USER…]", VERB_ANY, VERB_ANY, 0, + "Disable linger state of one or more users"); +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]; + bool b; + int r; + + assert(argv); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + b = streq(argv[0], "enable-linger"); + + if (argc < 2) { /* No argument? Let's use an empty user name, * then logind will use our user. */ @@ -1280,7 +1244,7 @@ static int enable_linger(int argc, char *argv[], void *userdata) { if (isempty(argv[i])) uid = UID_INVALID; else { - r = get_user_creds((const char**) (argv+i), &uid, NULL, NULL, NULL, 0); + r = get_user_creds(argv[i], /* flags= */ 0, NULL, &uid, NULL, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to look up user %s: %m", argv[i]); } @@ -1298,7 +1262,9 @@ static int enable_linger(int argc, char *argv[], void *userdata) { return 0; } -static int terminate_user(int argc, char *argv[], void *userdata) { +VERB(verb_terminate_user, "terminate-user", "USER…", 2, VERB_ANY, 0, + "Terminate all sessions of one or more users"); +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; @@ -1313,9 +1279,7 @@ static int terminate_user(int argc, char *argv[], void *userdata) { if (isempty(argv[i])) uid = getuid(); else { - const char *u = argv[i]; - - r = get_user_creds(&u, &uid, NULL, NULL, NULL, 0); + r = get_user_creds(argv[i], /* flags= */ 0, NULL, &uid, NULL, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to look up user %s: %m", argv[i]); } @@ -1328,7 +1292,9 @@ static int terminate_user(int argc, char *argv[], void *userdata) { return 0; } -static int kill_user(int argc, char *argv[], void *userdata) { +VERB(verb_kill_user, "kill-user", "USER…", 2, VERB_ANY, 0, + "Send signal to processes of a user"); +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; @@ -1346,9 +1312,7 @@ static int kill_user(int argc, char *argv[], void *userdata) { if (isempty(argv[i])) uid = getuid(); else { - const char *u = argv[i]; - - r = get_user_creds(&u, &uid, NULL, NULL, NULL, 0); + r = get_user_creds(argv[i], /* flags= */ 0, NULL, &uid, NULL, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to look up user %s: %m", argv[i]); } @@ -1366,47 +1330,106 @@ static int kill_user(int argc, char *argv[], void *userdata) { return 0; } -static int attach(int argc, char *argv[], void *userdata) { +VERB_GROUP("Seat Commands"); + +VERB_NOARG(verb_list_seats, "list-seats", "List seats"); +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; sd_bus *bus = ASSERT_PTR(userdata); int r; assert(argv); - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + r = bus_call_method(bus, bus_login_mgr, "ListSeats", &error, &reply, NULL); + if (r < 0) + return log_error_errno(r, "Failed to list seats: %s", bus_error_message(&error, r)); - for (int i = 2; i < argc; i++) { + r = sd_bus_message_enter_container(reply, 'a', "(so)"); + if (r < 0) + return bus_log_parse_error(r); - r = bus_call_method( - bus, - bus_login_mgr, - "AttachDevice", - &error, NULL, - "ssb", argv[1], argv[i], true); + table = table_new("seat"); + if (!table) + return log_oom(); + + table_set_ersatz_string(table, TABLE_ERSATZ_DASH); + + for (;;) { + const char *seat; + + r = sd_bus_message_read(reply, "(so)", &seat, NULL); if (r < 0) - return log_error_errno(r, "Could not attach device: %s", bus_error_message(&error, r)); + return bus_log_parse_error(r); + if (r == 0) + break; + + r = table_add_cell(table, NULL, TABLE_STRING, seat); + if (r < 0) + return table_log_add_error(r); } - return 0; + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return list_table_print(table, "seats"); } -static int flush_devices(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; +VERB(verb_show_seat, "seat-status", "[NAME…]", VERB_ANY, VERB_ANY, 0, + "Show seat status"); +VERB(verb_show_seat, "show-seat", "[NAME…]", VERB_ANY, VERB_ANY, 0, + "Show properties of seats or the manager"); +static int verb_show_seat(int argc, char *argv[], uintptr_t _data, void *userdata) { sd_bus *bus = ASSERT_PTR(userdata); + bool properties; int r; assert(argv); - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + properties = !strstr(argv[0], "status"); - r = bus_call_method(bus, bus_login_mgr, "FlushDevices", &error, NULL, "b", true); - if (r < 0) - return log_error_errno(r, "Could not flush devices: %s", bus_error_message(&error, r)); + pager_open(arg_pager_flags); + + if (argc <= 1) { + _cleanup_free_ char *path = NULL; + + /* If no argument is specified inspect the manager itself */ + if (properties) + return show_properties(bus, "/org/freedesktop/login1"); + + r = get_bus_path_by_id(bus, "seat", "GetSeat", "auto", &path); + if (r < 0) + return r; + + return print_seat_status_info(bus, path); + } + + for (int i = 1, first = true; i < argc; i++, first = false) { + _cleanup_free_ char *path = NULL; + + r = get_bus_path_by_id(bus, "seat", "GetSeat", argv[i], &path); + if (r < 0) + return r; + + if (!first) + putchar('\n'); + + if (properties) + r = show_properties(bus, path); + else + r = print_seat_status_info(bus, path); + if (r < 0) + return r; + } return 0; } -static int lock_sessions(int argc, char *argv[], void *userdata) { +VERB(verb_attach, "attach", "NAME DEVICE…", 3, VERB_ANY, 0, + "Attach one or more devices to a seat"); +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; @@ -1415,19 +1438,41 @@ static int lock_sessions(int argc, char *argv[], void *userdata) { (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - r = bus_call_method( + for (int i = 2; i < argc; i++) { + + r = bus_call_method( bus, bus_login_mgr, - streq(argv[0], "lock-sessions") ? "LockSessions" : "UnlockSessions", + "AttachDevice", &error, NULL, - NULL); + "ssb", argv[1], argv[i], true); + if (r < 0) + return log_error_errno(r, "Could not attach device: %s", bus_error_message(&error, r)); + } + + return 0; +} + +VERB_NOARG(verb_flush_devices, "flush-devices", "Flush all device associations"); +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; + + assert(argv); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + r = bus_call_method(bus, bus_login_mgr, "FlushDevices", &error, NULL, "b", true); if (r < 0) - return log_error_errno(r, "Could not lock sessions: %s", bus_error_message(&error, r)); + return log_error_errno(r, "Could not flush devices: %s", bus_error_message(&error, r)); return 0; } -static int terminate_seat(int argc, char *argv[], void *userdata) { +VERB(verb_terminate_seat, "terminate-seat", "NAME…", 2, VERB_ANY, 0, + "Terminate all sessions on one or more seats"); +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,176 +1491,128 @@ static int terminate_seat(int argc, char *argv[], void *userdata) { return 0; } -static int help(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *link = NULL; +static int help(void) { + static const char *const groups[] = { + "Session Commands", + "User Commands", + "Seat Commands", + }; + + Table *vtables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(vtables, table_unref_array_clear); + _cleanup_(table_unrefp) Table *options = NULL; int r; pager_open(arg_pager_flags); - r = terminal_urlify_man("loginctl", "1", &link); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + r = verbs_get_help_table_group(groups[i], &vtables[i]); + if (r < 0) + return r; + } + + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; + + assert_cc(ELEMENTSOF(vtables) == 3); + (void) table_sync_column_widths(0, vtables[0], vtables[1], vtables[2], options); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sSend control commands to or query the login manager.%6$s\n" - "\n%3$sSession Commands:%4$s\n" - " list-sessions List sessions\n" - " session-status [ID...] Show session status\n" - " show-session [ID...] Show properties of sessions or the manager\n" - " activate [ID] Activate a session\n" - " lock-session [ID...] Screen lock one or more sessions\n" - " unlock-session [ID...] Screen unlock one or more sessions\n" - " lock-sessions Screen lock all current sessions\n" - " unlock-sessions Screen unlock all current sessions\n" - " terminate-session ID... Terminate one or more sessions\n" - " kill-session ID... Send signal to processes of a session\n" - "\n%3$sUser Commands:%4$s\n" - " list-users List users\n" - " user-status [USER...] Show user status\n" - " show-user [USER...] Show properties of users or the manager\n" - " enable-linger [USER...] Enable linger state of one or more users\n" - " disable-linger [USER...] Disable linger state of one or more users\n" - " terminate-user USER... Terminate all sessions of one or more users\n" - " kill-user USER... Send signal to processes of a user\n" - "\n%3$sSeat Commands:%4$s\n" - " list-seats List seats\n" - " seat-status [NAME...] Show seat status\n" - " show-seat [NAME...] Show properties of seats or the manager\n" - " attach NAME DEVICE... Attach one or more devices to a seat\n" - " flush-devices Flush all device associations\n" - " terminate-seat NAME... Terminate all sessions on one or more seats\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 Don't prompt for password\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " -p --property=NAME Show only properties by this name\n" - " -P NAME Equivalent to --value --property=NAME\n" - " -a --all Show all properties, including empty ones\n" - " --value When showing properties, only print the value\n" - " -l --full Do not ellipsize output\n" - " --kill-whom=WHOM Whom to send signal to\n" - " -s --signal=SIGNAL Which signal to send\n" - " -n --lines=INTEGER Number of journal entries to show\n" - " --json=MODE Generate JSON output for list-sessions/users/seats\n" - " (takes one of pretty, short, or off)\n" - " -j Same as --json=pretty on tty, --json=short otherwise\n" - " -o --output=MODE Change journal output mode (short, short-precise,\n" - " short-iso, short-iso-precise, short-full,\n" - " short-monotonic, short-unix, short-delta,\n" - " json, json-pretty, json-sse, json-seq, cat,\n" - " verbose, export, with-unit)\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Send control commands to or query the login manager."); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + help_section(groups[i]); + r = table_print_or_warn(vtables[i]); + if (r < 0) + return r; + } + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("loginctl", "1"); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_VALUE, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - ARG_KILL_WHOM, - ARG_NO_ASK_PASSWORD, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "property", required_argument, NULL, 'p' }, - { "all", no_argument, NULL, 'a' }, - { "value", no_argument, NULL, ARG_VALUE }, - { "full", no_argument, NULL, 'l' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "json", required_argument, NULL, ARG_JSON }, - { "kill-whom", required_argument, NULL, ARG_KILL_WHOM }, - { "signal", required_argument, NULL, 's' }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "lines", required_argument, NULL, 'n' }, - { "output", required_argument, NULL, 'o' }, - {} - }; +VERB_COMMON_HELP_HIDDEN(help); - int c, r; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "hp:P:als:H:M:n:o:j", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - return help(0, NULL, NULL); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case 'P': - SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); - _fallthrough_; + OPTION_COMMON_HOST: + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = opts.arg; + break; - case 'p': { - r = strv_extend(&arg_property, optarg); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); + if (r < 0) + return r; + break; + + OPTION('p', "property", "NAME", "Show only properties by this name"): {} + OPTION_SHORT('P', "NAME", "Equivalent to --value --property=NAME"): + r = strv_extend(&arg_property, opts.arg); if (r < 0) return log_oom(); - /* If the user asked for a particular - * property, show it to them, even if it is + /* If the user asked for a particular property, show it to them, even if it is * empty. */ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); + + if (opts.opt->short_code == 'P') + 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 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 'l': + OPTION('l', "full", NULL, "Do not ellipsize output"): arg_full = true; break; - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse lines '%s'", optarg); + OPTION_LONG("kill-whom", "WHOM", "Whom to send signal to"): + arg_kill_whom = opts.arg; break; - case 'o': - 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 '%s'.", optarg); - + OPTION('s', "signal", "SIGNAL", "Which signal to send"): + r = parse_signal_argument(opts.arg, &arg_signal); + if (r <= 0) + return r; break; - case 'j': - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; - arg_legend = false; + OPTION('n', "lines", "INTEGER", "Number of journal entries to show"): + if (safe_atou(opts.arg, &arg_lines) < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse lines '%s'", opts.arg); break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; @@ -1624,89 +1621,50 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; - - case ARG_NO_LEGEND: + OPTION_COMMON_LOWERCASE_J: + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; arg_legend = false; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; + OPTION('o', "output", "MODE", + "Change journal output mode (short, short-precise, short-iso, " + "short-iso-precise, short-full, short-monotonic, short-unix, short-delta, " + "json, json-pretty, json-sse, json-seq, cat, verbose, export, with-unit)"): + if (streq(opts.arg, "help")) + return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); - case ARG_KILL_WHOM: - arg_kill_whom = optarg; - break; + arg_output = output_mode_from_string(opts.arg); + if (arg_output < 0) + return log_error_errno(arg_output, "Unknown output '%s'.", opts.arg); - case 's': - r = parse_signal_argument(optarg, &arg_signal); - if (r <= 0) - return r; break; - 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_LEGEND: + arg_legend = false; break; - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; + break; } + *remaining_args = option_parser_get_args(&opts); return 1; } -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 }, - {} - }; - - 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; @@ -1718,7 +1676,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - return loginctl_main(argc, argv, bus); + return dispatch_verb(args, bus); } DEFINE_MAIN_FUNCTION(run); 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-core.c b/src/login/logind-core.c index 33f895f0041e9..29ca81eec9673 100644 --- a/src/login/logind-core.c +++ b/src/login/logind-core.c @@ -13,6 +13,7 @@ #include "bus-locator.h" #include "cgroup-util.h" #include "conf-parser.h" +#include "device-private.h" #include "device-util.h" #include "efi-loader.h" #include "errno-util.h" @@ -446,7 +447,7 @@ int manager_get_user_by_pid(Manager *m, pid_t pid, User **ret) { return !!u; } -int manager_get_idle_hint(Manager *m, dual_timestamp *t) { +bool manager_get_idle_hint(Manager *m, dual_timestamp *ret_timestamp) { Session *s; bool idle_hint; dual_timestamp ts; @@ -457,19 +458,16 @@ int manager_get_idle_hint(Manager *m, dual_timestamp *t) { * unreasonable large idle periods starting with the Unix epoch. */ ts = m->init_ts; - idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, t, /* flags= */ 0, UID_INVALID, NULL); + idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, /* since= */ NULL, /* flags= */ 0, UID_INVALID, NULL); HASHMAP_FOREACH(s, m->sessions) { dual_timestamp k; - int ih; + bool ih; if (!SESSION_CLASS_CAN_IDLE(s->class)) continue; ih = session_get_idle_hint(s, &k); - if (ih < 0) - return ih; - if (!ih) { if (!idle_hint) { if (k.monotonic < ts.monotonic) @@ -485,8 +483,8 @@ int manager_get_idle_hint(Manager *m, dual_timestamp *t) { } } - if (t) - *t = ts; + if (ret_timestamp) + *ret_timestamp = ts; return idle_hint; } @@ -676,21 +674,17 @@ static int manager_count_external_displays(Manager *m) { continue; /* Ignore ports that are not enabled */ - const char *enabled; - r = sd_device_get_sysattr_value(d, "enabled", &enabled); - if (r == -ENOENT) + r = device_get_sysattr_streq(d, "enabled", "enabled"); + if (IN_SET(r, 0, -ENOENT)) continue; if (r < 0) return r; - if (!streq(enabled, "enabled")) - continue; /* We count any connector which is not explicitly "disconnected" as connected. */ - const char *status = NULL; - r = sd_device_get_sysattr_value(d, "status", &status); + r = device_get_sysattr_streq(d, "status", "disconnected"); if (r < 0 && r != -ENOENT) return r; - if (!streq_ptr(status, "disconnected")) + if (r <= 0) n++; } diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index b50e69809fea0..7472cf8ec41c4 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -45,10 +45,11 @@ #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" -#include "mkdir-label.h" +#include "mkdir.h" #include "os-util.h" #include "parse-util.h" #include "path-util.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) @@ -281,7 +282,7 @@ static int property_get_idle_hint( assert(bus); assert(reply); - return sd_bus_message_append(reply, "b", manager_get_idle_hint(m, NULL) > 0); + return sd_bus_message_append(reply, "b", manager_get_idle_hint(m, /* ret_timestamp= */ NULL)); } static int property_get_idle_since_hint( @@ -294,7 +295,7 @@ static int property_get_idle_since_hint( sd_bus_error *error) { Manager *m = ASSERT_PTR(userdata); - dual_timestamp t = DUAL_TIMESTAMP_NULL; + dual_timestamp t; assert(bus); assert(reply); @@ -700,10 +701,7 @@ static int method_list_sessions_ex(sd_bus_message *message, void *userdata, sd_b if (!path) return -ENOMEM; - r = session_get_idle_hint(s, &idle_ts); - if (r < 0) - return r; - idle = r > 0; + idle = session_get_idle_hint(s, &idle_ts); r = sd_bus_message_append(reply, "(sussussbto)", s->id, @@ -1154,7 +1152,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); } @@ -1647,6 +1645,7 @@ static int method_set_user_linger(sd_bus_message *message, void *userdata, sd_bu /* good_user= */ UID_INVALID, interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &m->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -1823,6 +1822,7 @@ static int method_attach_device(sd_bus_message *message, void *userdata, sd_bus_ /* good_user= */ UID_INVALID, interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &m->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -1853,6 +1853,7 @@ static int method_flush_devices(sd_bus_message *message, void *userdata, sd_bus_ /* good_user= */ UID_INVALID, interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &m->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -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), @@ -4430,7 +4321,7 @@ int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *err /* If the user is stopping, we're tracking stop jobs here. So don't send reply. */ if (!user->stopping) { char **user_job; - FOREACH_ARGUMENT(user_job, &user->runtime_dir_job, &user->service_manager_job) + FOREACH_ARGUMENT(user_job, &user->runtime_dir_job, &user->service_manager_job, &user->measure_job) if (streq_ptr(path, *user_job)) { *user_job = mfree(*user_job); diff --git a/src/login/logind-inhibit.c b/src/login/logind-inhibit.c index b78d39d02d777..99ec0a3c99ed5 100644 --- a/src/login/logind-inhibit.c +++ b/src/login/logind-inhibit.c @@ -17,12 +17,13 @@ #include "fs-util.h" #include "hashmap.h" #include "io-util.h" +#include "json-util.h" #include "log.h" #include "logind-session.h" #include "logind.h" #include "logind-dbus.h" #include "logind-inhibit.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "parse-util.h" #include "path-util.h" #include "string-table.h" @@ -528,6 +529,67 @@ int inhibit_what_from_string(const char *s) { } } +static int inhibit_what_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + InhibitWhat *what = ASSERT_PTR(userdata); + int r; + + assert(ret); + assert(inhibit_what_is_valid(*what)); + + for (unsigned bit = 0; (1U << bit) < (unsigned) _INHIBIT_WHAT_MAX; bit++) { + InhibitWhat w = 1U << bit; + + if (!FLAGS_SET(*what, w)) + continue; + + r = sd_json_variant_append_arrayb(&v, JSON_BUILD_STRING_UNDERSCORIFY(inhibit_what_to_string(w))); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +static int inhibitor_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Inhibitor *i = ASSERT_PTR(userdata); + + assert(ret); + assert(i->mode >= 0 && i->mode < _INHIBIT_MODE_MAX); + assert(inhibit_what_is_valid(i->what)); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("Id", i->id), + SD_JSON_BUILD_PAIR_CALLBACK("What", inhibit_what_build_json, &i->what), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Who", i->who), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Why", i->why), + JSON_BUILD_PAIR_ENUM("Mode", inhibit_mode_to_string(i->mode)), + SD_JSON_BUILD_PAIR_UNSIGNED("UID", i->uid)); +} + +static int inhibitor_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Inhibitor *i = ASSERT_PTR(userdata); + + assert(ret); + + return sd_json_buildo( + ret, + JSON_BUILD_PAIR_PIDREF_NON_NULL("PID", &i->pid), + JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("Since", &i->since)); +} + +int inhibitor_build_json(Inhibitor *i, sd_json_variant **ret) { + assert(i); + assert(ret); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_CALLBACK("context", inhibitor_context_build_json, i), + SD_JSON_BUILD_PAIR_CALLBACK("runtime", inhibitor_runtime_build_json, i)); +} + static const char* const inhibit_mode_table[_INHIBIT_MODE_MAX] = { [INHIBIT_BLOCK] = "block", [INHIBIT_BLOCK_WEAK] = "block-weak", diff --git a/src/login/logind-inhibit.h b/src/login/logind-inhibit.h index 1cb4ed460541e..c606582df2149 100644 --- a/src/login/logind-inhibit.h +++ b/src/login/logind-inhibit.h @@ -80,6 +80,8 @@ bool manager_is_inhibited( uid_t uid_to_ignore, Inhibitor **ret_offending); +int inhibitor_build_json(Inhibitor *i, sd_json_variant **ret); + static inline bool inhibit_what_is_valid(InhibitWhat w) { return w > 0 && w < _INHIBIT_WHAT_MAX; } diff --git a/src/login/logind-seat-dbus.c b/src/login/logind-seat-dbus.c index a07a0e49ee5a0..3e236e8bb79d3 100644 --- a/src/login/logind-seat-dbus.c +++ b/src/login/logind-seat-dbus.c @@ -100,7 +100,7 @@ static int property_get_idle_hint( assert(bus); assert(reply); - return sd_bus_message_append(reply, "b", seat_get_idle_hint(s, NULL) > 0); + return sd_bus_message_append(reply, "b", seat_get_idle_hint(s, /* ret_timestamp= */ NULL)); } static int property_get_idle_since_hint( @@ -115,14 +115,11 @@ static int property_get_idle_since_hint( Seat *s = ASSERT_PTR(userdata); dual_timestamp t; uint64_t u; - int r; assert(bus); assert(reply); - r = seat_get_idle_hint(s, &t); - if (r < 0) - return r; + seat_get_idle_hint(s, &t); u = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic; diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c index 77f006a479d29..1fe1eb41f45f3 100644 --- a/src/login/logind-seat.c +++ b/src/login/logind-seat.c @@ -16,6 +16,7 @@ #include "fs-util.h" #include "hashmap.h" #include "id128-util.h" +#include "json-util.h" #include "log.h" #include "logind.h" #include "logind-device.h" @@ -25,7 +26,7 @@ #include "logind-session-dbus.h" #include "logind-session-device.h" #include "logind-user.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "path-util.h" #include "set.h" #include "stat-util.h" @@ -773,7 +774,7 @@ bool seat_can_graphical(Seat *s) { return seat_has_master_device(s); } -int seat_get_idle_hint(Seat *s, dual_timestamp *t) { +bool seat_get_idle_hint(Seat *s, dual_timestamp *ret_timestamp) { bool idle_hint = true; dual_timestamp ts = DUAL_TIMESTAMP_NULL; @@ -781,12 +782,9 @@ int seat_get_idle_hint(Seat *s, dual_timestamp *t) { LIST_FOREACH(sessions_by_seat, session, s->sessions) { dual_timestamp k; - int ih; + bool ih; ih = session_get_idle_hint(session, &k); - if (ih < 0) - return ih; - if (!ih) { if (!idle_hint) { if (k.monotonic > ts.monotonic) @@ -796,14 +794,13 @@ int seat_get_idle_hint(Seat *s, dual_timestamp *t) { ts = k; } } else if (idle_hint) { - if (k.monotonic > ts.monotonic) ts = k; } } - if (t) - *t = ts; + if (ret_timestamp) + *ret_timestamp = ts; return idle_hint; } @@ -830,6 +827,61 @@ void seat_add_to_gc_queue(Seat *s) { s->in_gc_queue = true; } +static int seat_sessions_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Seat *s = ASSERT_PTR(userdata); + int r; + + assert(ret); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + LIST_FOREACH(sessions_by_seat, session, s->sessions) { + r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_STRING(session->id)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +static int seat_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Seat *s = ASSERT_PTR(userdata); + + assert(ret); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("Id", s->id)); +} + +static int seat_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Seat *s = ASSERT_PTR(userdata); + + assert(ret); + + dual_timestamp idle_ts; + bool idle = seat_get_idle_hint(s, &idle_ts); + + return sd_json_buildo( + ret, + JSON_BUILD_PAIR_STRING_NON_EMPTY("ActiveSession", s->active ? s->active->id : NULL), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("Sessions", seat_sessions_build_json, s), + SD_JSON_BUILD_PAIR_BOOLEAN("CanTTY", seat_can_tty(s)), + SD_JSON_BUILD_PAIR_BOOLEAN("CanGraphical", seat_can_graphical(s)), + SD_JSON_BUILD_PAIR_BOOLEAN("IdleHint", idle), + SD_JSON_BUILD_PAIR_CONDITION(idle, "IdleSinceHint", JSON_BUILD_DUAL_TIMESTAMP(&idle_ts))); +} + +int seat_build_json(Seat *s, sd_json_variant **ret) { + assert(s); + assert(ret); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_CALLBACK("context", seat_context_build_json, s), + SD_JSON_BUILD_PAIR_CALLBACK("runtime", seat_runtime_build_json, s)); +} + static bool seat_name_valid_char(char c) { return ascii_isalpha(c) || diff --git a/src/login/logind-seat.h b/src/login/logind-seat.h index 323cbe546e232..3f6d2aaf3c930 100644 --- a/src/login/logind-seat.h +++ b/src/login/logind-seat.h @@ -55,7 +55,7 @@ bool seat_can_tty(Seat *s); bool seat_has_master_device(Seat *s); bool seat_can_graphical(Seat *s); -int seat_get_idle_hint(Seat *s, dual_timestamp *t); +bool seat_get_idle_hint(Seat *s, dual_timestamp *ret_timestamp); int seat_start(Seat *s); int seat_stop(Seat *s, bool force); @@ -64,6 +64,8 @@ int seat_stop_sessions(Seat *s, bool force); bool seat_may_gc(Seat *s, bool drop_not_started); void seat_add_to_gc_queue(Seat *s); +int seat_build_json(Seat *s, sd_json_variant **ret); + bool seat_name_is_valid(const char *name); bool seat_is_self(const char *name); bool seat_is_auto(const char *name); diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c index 4da9bb2425579..2c8207f054e10 100644 --- a/src/login/logind-session-dbus.c +++ b/src/login/logind-session-dbus.c @@ -117,7 +117,7 @@ static int property_get_idle_hint( assert(bus); assert(reply); - return sd_bus_message_append(reply, "b", session_get_idle_hint(s, NULL) > 0); + return sd_bus_message_append(reply, "b", session_get_idle_hint(s, /* ret_timestamp= */ NULL)); } static int property_get_can_idle( @@ -164,16 +164,13 @@ static int property_get_idle_since_hint( sd_bus_error *error) { Session *s = ASSERT_PTR(userdata); - dual_timestamp t = DUAL_TIMESTAMP_NULL; + dual_timestamp t; uint64_t u; - int r; assert(bus); assert(reply); - r = session_get_idle_hint(s, &t); - if (r < 0) - return r; + session_get_idle_hint(s, &t); u = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic; @@ -210,6 +207,7 @@ int bus_session_method_terminate(sd_bus_message *message, void *userdata, sd_bus s->user->user_record->uid, /* flags= */ 0, &s->manager->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -255,6 +253,7 @@ int bus_session_method_lock(sd_bus_message *message, void *userdata, sd_bus_erro s->user->user_record->uid, /* flags= */ 0, &s->manager->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -365,6 +364,7 @@ int bus_session_method_kill(sd_bus_message *message, void *userdata, sd_bus_erro s->user->user_record->uid, /* flags= */ 0, &s->manager->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; 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-session.c b/src/login/logind-session.c index 72cad136650c2..e62a5768da693 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -28,6 +28,7 @@ #include "format-util.h" #include "fs-util.h" #include "hashmap.h" +#include "json-util.h" #include "login-util.h" #include "logind.h" #include "logind-dbus.h" @@ -39,7 +40,7 @@ #include "logind-user.h" #include "logind-user-dbus.h" #include "logind-varlink.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "parse-util.h" #include "path-util.h" #include "process-util.h" @@ -1153,19 +1154,23 @@ static int get_process_ctty_atime(pid_t pid, usec_t *atime) { return get_tty_atime(p, atime); } -int session_get_idle_hint(Session *s, dual_timestamp *t) { +bool session_get_idle_hint(Session *s, dual_timestamp *ret_timestamp) { usec_t atime = 0, dtime = 0; int r; assert(s); - if (!SESSION_CLASS_CAN_IDLE(s->class)) + if (!SESSION_CLASS_CAN_IDLE(s->class)) { + if (ret_timestamp) + *ret_timestamp = DUAL_TIMESTAMP_NULL; + return false; + } /* Graphical sessions have an explicit idle hint */ if (SESSION_TYPE_IS_GRAPHICAL(s->type)) { - if (t) - *t = s->idle_hint_timestamp; + if (ret_timestamp) + *ret_timestamp = s->idle_hint_timestamp; return s->idle_hint; } @@ -1187,14 +1192,14 @@ int session_get_idle_hint(Session *s, dual_timestamp *t) { } } - if (t) - *t = DUAL_TIMESTAMP_NULL; + if (ret_timestamp) + *ret_timestamp = DUAL_TIMESTAMP_NULL; return false; found_atime: - if (t) - dual_timestamp_from_realtime(t, atime); + if (ret_timestamp) + dual_timestamp_from_realtime(ret_timestamp, atime); if (s->manager->idle_action_usec > 0 && s->manager->stop_idle_session_usec != USEC_INFINITY) dtime = MIN(s->manager->idle_action_usec, s->manager->stop_idle_session_usec); @@ -1680,6 +1685,65 @@ bool session_is_auto(const char *name) { return streq_ptr(name, "auto"); } +static int session_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Session *s = ASSERT_PTR(userdata); + + assert(ret); + assert(s->user); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_UNSIGNED("UID", s->user->user_record->uid), + JSON_BUILD_PAIR_PIDREF_NON_NULL("PID", &s->leader), + JSON_BUILD_PAIR_ENUM("Type", session_type_to_string(s->type)), + JSON_BUILD_PAIR_ENUM("Class", session_class_to_string(s->class)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Service", s->service), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Desktop", s->desktop), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Seat", s->seat ? s->seat->id : NULL), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("VTNr", s->vtnr), + JSON_BUILD_PAIR_STRING_NON_EMPTY("TTY", s->tty), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Display", s->display), + SD_JSON_BUILD_PAIR_BOOLEAN("Remote", s->remote), + JSON_BUILD_PAIR_STRING_NON_EMPTY("RemoteHost", s->remote_host), + JSON_BUILD_PAIR_STRING_NON_EMPTY("RemoteUser", s->remote_user), + JSON_BUILD_PAIR_STRV_NON_EMPTY("ExtraDeviceAccess", s->extra_device_access)); +} + +static int session_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + Session *s = ASSERT_PTR(userdata); + + assert(ret); + + dual_timestamp idle_ts; + bool idle = session_get_idle_hint(s, &idle_ts); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_STRING("Id", s->id), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Scope", s->scope), + SD_JSON_BUILD_PAIR_CONDITION(audit_session_is_valid(s->audit_id), + "Audit", SD_JSON_BUILD_UNSIGNED(s->audit_id)), + JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("Timestamp", &s->timestamp), + JSON_BUILD_PAIR_ENUM("State", session_state_to_string(session_get_state(s))), + SD_JSON_BUILD_PAIR_BOOLEAN("Active", session_is_active(s)), + SD_JSON_BUILD_PAIR_BOOLEAN("IdleHint", idle), + SD_JSON_BUILD_PAIR_CONDITION(idle, "IdleSinceHint", JSON_BUILD_DUAL_TIMESTAMP(&idle_ts)), + SD_JSON_BUILD_PAIR_BOOLEAN("LockedHint", s->locked_hint), + SD_JSON_BUILD_PAIR_BOOLEAN("CanIdle", SESSION_CLASS_CAN_IDLE(s->class)), + SD_JSON_BUILD_PAIR_BOOLEAN("CanLock", SESSION_CLASS_CAN_LOCK(s->class))); +} + +int session_build_json(Session *s, sd_json_variant **ret) { + assert(s); + assert(s->user); + assert(ret); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_CALLBACK("context", session_context_build_json, s), + SD_JSON_BUILD_PAIR_CALLBACK("runtime", session_runtime_build_json, s)); +} + static const char* const session_state_table[_SESSION_STATE_MAX] = { [SESSION_OPENING] = "opening", [SESSION_ONLINE] = "online", diff --git a/src/login/logind-session.h b/src/login/logind-session.h index f51eed24fa0f1..b0749134af620 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -179,7 +179,7 @@ bool session_may_gc(Session *s, bool drop_not_started); void session_add_to_gc_queue(Session *s); int session_activate(Session *s); bool session_is_active(Session *s); -int session_get_idle_hint(Session *s, dual_timestamp *t); +bool session_get_idle_hint(Session *s, dual_timestamp *ret_timestamp); int session_set_idle_hint(Session *s, bool b); int session_get_locked_hint(Session *s); int session_set_locked_hint(Session *s, bool b); @@ -219,3 +219,5 @@ int session_send_create_reply(Session *s, const sd_bus_error *error); bool session_is_self(const char *name); bool session_is_auto(const char *name); + +int session_build_json(Session *s, sd_json_variant **ret); diff --git a/src/login/logind-shutdown.c b/src/login/logind-shutdown.c new file mode 100644 index 0000000000000..a0a30951cc147 --- /dev/null +++ b/src/login/logind-shutdown.c @@ -0,0 +1,256 @@ +/* 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, + /* ret_admin= */ NULL, + 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, + /* ret_admin= */ NULL); + + 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, + /* ret_admin= */ NULL, + 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, + /* ret_admin= */ NULL); + + 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, + /* ret_admin= */ NULL, + 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, + /* ret_admin= */ NULL); + + 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-user-dbus.c b/src/login/logind-user-dbus.c index b67e21e052369..a6cab9ee653e1 100644 --- a/src/login/logind-user-dbus.c +++ b/src/login/logind-user-dbus.c @@ -144,7 +144,7 @@ static int property_get_idle_hint( assert(bus); assert(reply); - return sd_bus_message_append(reply, "b", user_get_idle_hint(u, NULL) > 0); + return sd_bus_message_append(reply, "b", user_get_idle_hint(u, /* ret_timestamp= */ NULL)); } static int property_get_idle_since_hint( @@ -163,7 +163,7 @@ static int property_get_idle_since_hint( assert(bus); assert(reply); - (void) user_get_idle_hint(u, &t); + user_get_idle_hint(u, &t); k = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic; return sd_bus_message_append(reply, "t", k); @@ -202,6 +202,7 @@ int bus_user_method_terminate(sd_bus_message *message, void *userdata, sd_bus_er u->user_record->uid, /* flags= */ 0, &u->manager->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -229,6 +230,7 @@ int bus_user_method_kill(sd_bus_message *message, void *userdata, sd_bus_error * u->user_record->uid, /* flags= */ 0, &u->manager->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 49d51952e98e7..9bac65cc81508 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -10,6 +10,7 @@ #include "bus-locator.h" #include "bus-util.h" #include "clean-ipc.h" +#include "efi-loader.h" #include "env-file.h" #include "errno-util.h" #include "escape.h" @@ -17,6 +18,7 @@ #include "format-util.h" #include "fs-util.h" #include "hashmap.h" +#include "json-util.h" #include "limits-util.h" #include "logind-session.h" #include "logind.h" @@ -24,7 +26,7 @@ #include "logind-seat.h" #include "logind-user.h" #include "logind-user-dbus.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "parse-util.h" #include "percent-util.h" #include "serialize.h" @@ -84,6 +86,10 @@ int user_new(Manager *m, UserRecord *ur, User **ret) { if (r < 0) return r; + r = unit_name_build("systemd-pcrlogin", lu, ".service", &u->measure_unit); + if (r < 0) + return r; + r = hashmap_put(m->users, UID_TO_PTR(ur->uid), u); if (r < 0) return r; @@ -100,6 +106,10 @@ int user_new(Manager *m, UserRecord *ur, User **ret) { if (r < 0) return r; + r = hashmap_put(m->user_units, u->measure_unit, u); + if (r < 0) + return r; + *ret = TAKE_PTR(u); return 0; } @@ -118,15 +128,21 @@ User *user_free(User *u) { if (u->service_manager_unit) { (void) hashmap_remove_value(u->manager->user_units, u->service_manager_unit, u); - free(u->service_manager_job); free(u->service_manager_unit); } + free(u->service_manager_job); if (u->runtime_dir_unit) { (void) hashmap_remove_value(u->manager->user_units, u->runtime_dir_unit, u); - free(u->runtime_dir_job); free(u->runtime_dir_unit); } + free(u->runtime_dir_job); + + if (u->measure_unit) { + (void) hashmap_remove_value(u->manager->user_units, u->measure_unit, u); + free(u->measure_unit); + } + free(u->measure_job); if (u->slice) { (void) hashmap_remove_value(u->manager->user_units, u->slice, u); @@ -177,6 +193,7 @@ static int user_save_internal(User *u) { env_file_fputs_assignment(f, "RUNTIME=", u->runtime_path); env_file_fputs_assignment(f, "RUNTIME_DIR_JOB=", u->runtime_dir_job); env_file_fputs_assignment(f, "SERVICE_JOB=", u->service_manager_job); + env_file_fputs_assignment(f, "MEASURE_JOB=", u->measure_job); if (u->display) env_file_fputs_assignment(f, "DISPLAY=", u->display->id); @@ -303,6 +320,7 @@ int user_load(User *u) { r = parse_env_file(NULL, u->state_file, "RUNTIME_DIR_JOB", &u->runtime_dir_job, "SERVICE_JOB", &u->service_manager_job, + "MEASURE_JOB", &u->measure_job, "STOPPING", &stopping, "REALTIME", &realtime, "MONOTONIC", &monotonic, @@ -358,6 +376,39 @@ static int user_start_runtime_dir(User *u) { return 0; } +static int user_start_measure(User *u) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(u); + assert(!u->stopping); + assert(u->manager); + assert(u->measure_unit); + + /* Measures the user's record into the 'login' TPM2 NvPCR on the first login of this boot. This is + * strictly best-effort: a measurement failure must never block the login (simply because NvPCRs might + * not be available). Unlike the runtime-dir and service-manager units, this one is started but never + * stopped (see user_stop_service()): it is a Type=oneshot/RemainAfterExit unit living in + * system.slice that stays active until reboot, so a later re-login's StartUnit is a no-op and every + * user is measured exactly once per boot. + * + * Only bother if we're actually running in measured-OS mode. The unit itself also carries + * ConditionSecurity=measured-os, but checking here avoids queuing a no-op job on every first login + * on systems that don't measure the OS */ + if (efi_measured_os(LOG_DEBUG) <= 0) + return 0; + + u->measure_job = mfree(u->measure_job); + + r = manager_start_unit(u->manager, u->measure_unit, &error, &u->measure_job); + if (r < 0) + return log_full_errno(sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED) ? LOG_DEBUG : LOG_WARNING, + r, "Failed to start user measurement service '%s', ignoring: %s", + u->measure_unit, bus_error_message(&error, r)); + + return 0; +} + static bool user_wants_service_manager(const User *u) { assert(u); @@ -512,6 +563,14 @@ int user_start(User *u) { /* Set slice parameters */ (void) user_update_slice(u); + /* Measure the user record into the 'login' NvPCR. We do this *before* starting the runtime dir + * (and, further below, the service manager): manager_start_unit() is synchronous, so by the + * time those units are enqueued the measurement's start job already exists, and their + * After=systemd-pcrlogin@%i.service ordering then guarantees the measurement completes before + * the user's session becomes available. Best-effort and never stopped again, so each user is + * measured exactly once per boot. */ + (void) user_start_measure(u); + (void) user_start_runtime_dir(u); } @@ -621,7 +680,7 @@ int user_finalize(User *u) { return r; } -int user_get_idle_hint(User *u, dual_timestamp *t) { +bool user_get_idle_hint(User *u, dual_timestamp *ret_timestamp) { bool idle_hint = true; dual_timestamp ts = DUAL_TIMESTAMP_NULL; @@ -629,15 +688,12 @@ int user_get_idle_hint(User *u, dual_timestamp *t) { LIST_FOREACH(sessions_by_user, s, u->sessions) { dual_timestamp k; - int ih; + bool ih; if (!SESSION_CLASS_CAN_IDLE(s->class)) continue; ih = session_get_idle_hint(s, &k); - if (ih < 0) - return ih; - if (!ih) { if (!idle_hint) { if (k.monotonic < ts.monotonic) @@ -647,14 +703,13 @@ int user_get_idle_hint(User *u, dual_timestamp *t) { ts = k; } } else if (idle_hint) { - if (k.monotonic > ts.monotonic) ts = k; } } - if (t) - *t = ts; + if (ret_timestamp) + *ret_timestamp = ts; return idle_hint; } @@ -763,8 +818,14 @@ bool user_may_gc(User *u, bool drop_not_started) { /* Is this a user that shall stay around forever ("linger")? Before we say "no" to GC'ing for lingering users, let's check * if any of the three units that we maintain for this user is still around. If none of them is, - * there's no need to keep this user around even if lingering is enabled. */ - if (user_check_linger_file(u) > 0 && user_unit_active(u)) + * there's no need to keep this user around even if lingering is enabled. + * + * Exception: at startup-time GC (drop_not_started=false) the per-user units have not yet been + * started by manager_startup(), so requiring user_unit_active() here would unconditionally GC + * every lingering user before we get a chance to user_start() them. That breaks after a + * soft-reboot, where /run/systemd/users is preserved and feeds lingering users into the GC + * queue (cold boot is unaffected because /run is empty). See #41789. */ + if (user_check_linger_file(u) > 0 && (!drop_not_started || user_unit_active(u))) return false; /* Check if our job is still pending */ @@ -959,6 +1020,77 @@ void user_update_last_session_timer(User *u) { FORMAT_TIMESPAN(user_stop_delay, USEC_PER_MSEC)); } +static int user_sessions_build_json(sd_json_variant **ret, const char *name, void *userdata) { + User *u = ASSERT_PTR(userdata); + int r; + + assert(ret); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + LIST_FOREACH(sessions_by_user, session, u->sessions) { + r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_STRING(session->id)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +static int user_context_build_json(sd_json_variant **ret, const char *name, void *userdata) { + User *u = ASSERT_PTR(userdata); + + assert(ret); + assert(u->user_record); + + int linger = user_check_linger_file(u); + if (linger == -ENOMEM) + return linger; + if (linger < 0) + log_warning_errno(linger, + "Failed to check linger file for user '%s', assuming disabled: %m", + u->user_record->user_name); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_UNSIGNED("UID", u->user_record->uid), + SD_JSON_BUILD_PAIR_UNSIGNED("GID", u->user_record->gid), + SD_JSON_BUILD_PAIR_STRING("Name", u->user_record->user_name), + SD_JSON_BUILD_PAIR_BOOLEAN("Linger", linger > 0), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Service", u->service_manager_unit), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Slice", u->slice), + JSON_BUILD_PAIR_STRING_NON_EMPTY("RuntimePath", u->runtime_path)); +} + +static int user_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) { + User *u = ASSERT_PTR(userdata); + + assert(ret); + + dual_timestamp idle_ts; + bool idle = user_get_idle_hint(u, &idle_ts); + + return sd_json_buildo( + ret, + JSON_BUILD_PAIR_STRING_NON_EMPTY("Display", u->display ? u->display->id : NULL), + JSON_BUILD_PAIR_ENUM("State", user_state_to_string(user_get_state(u))), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("Sessions", user_sessions_build_json, u), + JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("Timestamp", &u->timestamp), + SD_JSON_BUILD_PAIR_BOOLEAN("IdleHint", idle), + SD_JSON_BUILD_PAIR_CONDITION(idle, "IdleSinceHint", JSON_BUILD_DUAL_TIMESTAMP(&idle_ts))); +} + +int user_build_json(User *u, sd_json_variant **ret) { + assert(u); + assert(u->user_record); + assert(ret); + + return sd_json_buildo( + ret, + SD_JSON_BUILD_PAIR_CALLBACK("context", user_context_build_json, u), + SD_JSON_BUILD_PAIR_CALLBACK("runtime", user_runtime_build_json, u)); +} + static const char* const user_state_table[_USER_STATE_MAX] = { [USER_OFFLINE] = "offline", [USER_OPENING] = "opening", diff --git a/src/login/logind-user.h b/src/login/logind-user.h index b38af08744b23..3119e7ed21ebd 100644 --- a/src/login/logind-user.h +++ b/src/login/logind-user.h @@ -43,6 +43,13 @@ typedef struct User { char *service_manager_unit; char *service_manager_job; + /* systemd-pcrlogin@UID.service — measures the user record into the 'login' NvPCR on first login. + * Unlike the units above, logind starts this but never stops it: it must run exactly once per boot, + * so it lives in system.slice, stays active until reboot, and is intentionally absent from + * user_stop_service(). */ + char *measure_unit; + char *measure_job; + Session *display; dual_timestamp timestamp; /* When this User object was 'started' the first time */ @@ -75,7 +82,7 @@ void user_add_to_gc_queue(User *u); int user_stop(User *u, bool force); int user_finalize(User *u); UserState user_get_state(User *u); -int user_get_idle_hint(User *u, dual_timestamp *t); +bool user_get_idle_hint(User *u, dual_timestamp *ret_timestamp); int user_save(User *u); int user_load(User *u); int user_kill(User *u, int signo); @@ -83,6 +90,8 @@ int user_check_linger_file(const User *u); void user_elect_display(User *u); void user_update_last_session_timer(User *u); +int user_build_json(User *u, sd_json_variant **ret); + DECLARE_STRING_TABLE_LOOKUP(user_state, UserState); DECLARE_STRING_TABLE_LOOKUP(user_gc_mode, UserGCMode); diff --git a/src/login/logind-varlink.c b/src/login/logind-varlink.c index a1fdac01c907b..382f8fea221f3 100644 --- a/src/login/logind-varlink.c +++ b/src/login/logind-varlink.c @@ -4,15 +4,20 @@ #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-inhibit.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 +25,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" @@ -36,7 +42,8 @@ static int manager_varlink_get_session_by_peer( assert(ret); /* Determines the session of the peer. If the peer is not part of a session, but consult_display is - * true, then will return the display session of the peer's owning user */ + * true, then will return the display session of the peer's owning user. Returns 0 with *ret set to + * NULL if no session could be determined; the caller decides which error to report to the client. */ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; r = varlink_get_peer_pidref(link, &pidref); @@ -65,35 +72,114 @@ static int manager_varlink_get_session_by_peer( } else session = hashmap_get(m->sessions, name); + *ret = session; + return 0; +} + +static int lookup_session_by_name(Manager *m, sd_varlink *link, const char *name, Session **ret) { + int r; + + assert(m); + assert(link); + assert(name); + assert(ret); + + if (session_is_self(name) || session_is_auto(name)) { + Session *peer; + r = manager_varlink_get_session_by_peer(m, link, /* consult_display= */ session_is_auto(name), &peer); + if (r < 0) + return r; + if (!peer) + return -ESRCH; + + *ret = peer; + return 0; + } + + if (!session_id_valid(name)) + return -EINVAL; + + Session *session = hashmap_get(m->sessions, name); if (!session) - return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL); + return -ESRCH; *ret = session; return 0; } -static int manager_varlink_get_session_by_name( +static int lookup_session_by_pidref(Manager *m, const PidRef *pidref, Session **ret) { + int r; + + assert(m); + assert(pidref); + assert(ret); + + Session *session; + r = manager_get_session_by_pidref(m, pidref, &session); + if (r < 0) + return log_debug_errno(r, "Failed to look up session by PID " PID_FMT ": %m", pidref->pid); + if (!session) + return -ESRCH; + + *ret = session; + return 0; +} + +static int manager_varlink_get_session_by_name_or_pidref( Manager *m, sd_varlink *link, const char *name, + const PidRef *pidref, Session **ret) { + Session *by_name = NULL, *by_pid = NULL; + int r; + assert(m); assert(link); assert(ret); - /* Resolves a session name to a session object. Supports resolving the special names "self" and "auto". */ + /* Resolves a session by name and/or PID. Supports the special names "self" and "auto" for the name + * argument. If both name and pidref are unset, resolves to the caller's session. If both name and + * pidref are set they must refer to the same session, otherwise -ESRCH is returned. Returns -ESRCH + * on "not found". Caller is expected to turn that into a varlink error, typically via + * sd_varlink_set_sentinel(). Returns negative errno on other failures. */ - if (session_is_self(name)) - return manager_varlink_get_session_by_peer(m, link, /* consult_display= */ false, ret); - if (session_is_auto(name)) - return manager_varlink_get_session_by_peer(m, link, /* consult_display= */ true, ret); + if (name) { + r = lookup_session_by_name(m, link, name, &by_name); + if (r == -EINVAL) + return sd_varlink_error_invalid_parameter_name(link, "Id"); + if (r < 0) + return r; + } - Session *session = hashmap_get(m->sessions, name); - if (!session) - return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL); + if (pidref && pidref_is_set(pidref)) { + r = lookup_session_by_pidref(m, pidref, &by_pid); + if (r == -EINVAL) + return sd_varlink_error_invalid_parameter_name(link, "PID"); + if (r < 0) + return r; + } - *ret = session; + if (by_name && by_pid && by_name != by_pid) + return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), + "Search by session id '%s' and PID " PID_FMT " resulted in two different sessions", + name, pidref->pid); + + if (by_name || by_pid) { + *ret = by_name ?: by_pid; + return 0; + } + + /* Neither filter set — fall back to caller's session. */ + Session *by_peer; + r = manager_varlink_get_session_by_peer(m, link, /* consult_display= */ true, &by_peer); + if (r < 0) + return r; + if (!by_peer) + return -ESRCH; + + *ret = by_peer; return 0; } @@ -124,9 +210,9 @@ int session_send_create_reply_varlink(Session *s, const sd_bus_error *error) { SD_JSON_BUILD_PAIR_STRING("RuntimePath", s->user->runtime_path), 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_UNSIGNED_NON_ZERO("VTNr", s->vtnr), + 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); @@ -299,16 +385,356 @@ static int vl_method_create_session(sd_varlink *link, sd_json_variant *parameter return r; } +static int emit_session_reply(sd_varlink *link, Session *session) { + assert(link); + assert(session); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r = session_build_json(session, &v); + if (r < 0) + return r; + + return sd_varlink_reply(link, v); +} + +typedef struct ListSessionsParameters { + const char *id; + PidRef pidref; +} ListSessionsParameters; + +static void list_sessions_parameters_done(ListSessionsParameters *p) { + assert(p); + pidref_done(&p->pidref); +} + +static int vl_method_list_sessions(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + int r; + + static const sd_json_dispatch_field dispatch_table[] = { + { "Id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(ListSessionsParameters, id), 0 }, + { "PID", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_pidref, offsetof(ListSessionsParameters, pidref), 0 }, + {} + }; + + _cleanup_(list_sessions_parameters_done) ListSessionsParameters p = { + .pidref = PIDREF_NULL, + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + /* Unique-key path: Id and/or PID provided. Single reply or NoSuchSession. */ + if (p.id || pidref_is_set(&p.pidref)) { + r = sd_varlink_set_sentinel(link, "io.systemd.Login.NoSuchSession"); + if (r < 0) + return r; + + Session *session; + r = manager_varlink_get_session_by_name_or_pidref(m, link, p.id, &p.pidref, &session); + if (r == -ESRCH) + return 0; /* triggers NoSuchSession sentinel */ + if (r < 0) + return r; + + return emit_session_reply(link, session); + } + + /* Streaming path: no filter. Full list, requires 'more' flag. */ + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, /* parameters= */ NULL); + + /* Empty hashmap is a valid empty stream, not "not found" — see vl_method_list_inhibitors. */ + r = sd_varlink_set_sentinel(link, /* error_id= */ NULL); + if (r < 0) + return r; + + Session *session; + HASHMAP_FOREACH(session, m->sessions) { + r = emit_session_reply(link, session); + if (r < 0) + return r; + } + + return 0; +} + +static int manager_varlink_get_user_by_uid_or_pidref( + Manager *m, + uid_t uid, + const PidRef *pidref, + User **ret) { + + int r; + + assert(m); + assert(ret); + + /* Resolves a user by UID and/or PID. At least one filter must be set. If both are set they must + * reference the same user, otherwise -ESRCH is returned. Returns -ESRCH on "not found". Returns + * negative errno on other failures. */ + + User *by_uid = NULL; + if (uid_is_valid(uid)) { + by_uid = hashmap_get(m->users, UID_TO_PTR(uid)); + if (!by_uid) + return -ESRCH; + } + + User *by_pid = NULL; + if (pidref && pidref_is_set(pidref)) { + uid_t pid_uid; + r = cg_pidref_get_owner_uid(pidref, &pid_uid); + if (r < 0) { + if (!IN_SET(r, -ENXIO, -ENOENT)) + return log_debug_errno(r, "Failed to acquire owning UID of PID " PID_FMT ": %m", pidref->pid); + /* -ENXIO: PID not in a user-N.slice cgroup (e.g. PID 1). + * -ENOENT: cgroup gone (process terminated). + * Translate to -ESRCH so the caller maps to the NoSuchUser sentinel. */ + return -ESRCH; + } + + by_pid = hashmap_get(m->users, UID_TO_PTR(pid_uid)); + if (!by_pid) + return -ESRCH; + } + + if (by_uid && by_pid && by_uid != by_pid) + return log_debug_errno(SYNTHETIC_ERRNO(ESRCH), + "Search by UID " UID_FMT " and PID " PID_FMT " resulted in two different users", + uid, pidref->pid); + + assert(by_uid || by_pid); + + *ret = by_uid ?: by_pid; + return 0; +} + +static int emit_user_reply(sd_varlink *link, User *user) { + assert(link); + assert(user); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r = user_build_json(user, &v); + if (r < 0) + return r; + + return sd_varlink_reply(link, v); +} + +typedef struct ListUsersParameters { + uid_t uid; + PidRef pidref; +} ListUsersParameters; + +static void list_users_parameters_done(ListUsersParameters *p) { + assert(p); + pidref_done(&p->pidref); +} + +static int vl_method_list_users(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + int r; + + static const sd_json_dispatch_field dispatch_table[] = { + { "UID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(ListUsersParameters, uid), 0 }, + { "PID", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_pidref, offsetof(ListUsersParameters, pidref), 0 }, + {} + }; + + _cleanup_(list_users_parameters_done) ListUsersParameters p = { + .uid = UID_INVALID, + .pidref = PIDREF_NULL, + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + /* Unique-key path: UID and/or PID provided. Single reply or NoSuchUser. */ + if (uid_is_valid(p.uid) || pidref_is_set(&p.pidref)) { + r = sd_varlink_set_sentinel(link, "io.systemd.Login.NoSuchUser"); + if (r < 0) + return r; + + User *user; + r = manager_varlink_get_user_by_uid_or_pidref(m, p.uid, &p.pidref, &user); + if (r == -ESRCH) + return 0; /* triggers NoSuchUser sentinel */ + if (r < 0) + return r; + + return emit_user_reply(link, user); + } + + /* Streaming path: no filter. Full list, requires 'more' flag. */ + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, /* parameters= */ NULL); + + /* Empty hashmap is a valid empty stream, not "not found" — see vl_method_list_inhibitors. */ + r = sd_varlink_set_sentinel(link, /* error_id= */ NULL); + if (r < 0) + return r; + + User *user; + HASHMAP_FOREACH(user, m->users) { + r = emit_user_reply(link, user); + if (r < 0) + return r; + } + + return 0; +} + +static int manager_varlink_get_seat_by_name( + Manager *m, + sd_varlink *link, + const char *name, + Seat **ret) { + + int r; + + assert(m); + assert(link); + assert(name); + assert(ret); + + /* Resolves a seat name to a seat object. Supports the special names "self" and "auto" — these + * resolve to the seat of the caller's session. Returns -EINVAL on invalid seat name. Returns + * -ESRCH on "not found". Caller is expected to turn that into a varlink error. */ + + if (seat_is_self(name) || seat_is_auto(name)) { + Session *session; + r = manager_varlink_get_session_by_peer(m, link, /* consult_display= */ seat_is_auto(name), &session); + if (r < 0) + return r; + if (!session || !session->seat) + return -ESRCH; + + *ret = session->seat; + return 0; + } + + if (!seat_name_is_valid(name)) + return -EINVAL; + + Seat *seat = hashmap_get(m->seats, name); + if (!seat) + return -ESRCH; + + *ret = seat; + return 0; +} + +static int emit_seat_reply(sd_varlink *link, Seat *seat) { + assert(link); + assert(seat); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r = seat_build_json(seat, &v); + if (r < 0) + return r; + + return sd_varlink_reply(link, v); +} + +static int vl_method_list_seats(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + int r; + + const char *id = NULL; + + static const sd_json_dispatch_field dispatch_table[] = { + { "Id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &id); + if (r != 0) + return r; + + /* Unique-key path: Id provided. Single reply or NoSuchSeat. */ + if (id) { + r = sd_varlink_set_sentinel(link, "io.systemd.Login.NoSuchSeat"); + if (r < 0) + return r; + + Seat *seat; + r = manager_varlink_get_seat_by_name(m, link, id, &seat); + if (r == -EINVAL) + return sd_varlink_error_invalid_parameter_name(link, "Id"); + if (r == -ESRCH) + return 0; /* triggers NoSuchSeat sentinel */ + if (r < 0) + return r; + + return emit_seat_reply(link, seat); + } + + /* Streaming path: no filter. Full list, requires 'more' flag. */ + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, /* parameters= */ NULL); + + /* Empty hashmap is a valid empty stream, not "not found" — see vl_method_list_inhibitors. */ + r = sd_varlink_set_sentinel(link, /* error_id= */ NULL); + if (r < 0) + return r; + + Seat *seat; + HASHMAP_FOREACH(seat, m->seats) { + r = emit_seat_reply(link, seat); + if (r < 0) + return r; + } + + return 0; +} + +static int vl_method_list_inhibitors(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + int r; + + /* IDL uses SD_VARLINK_REQUIRES_MORE, so the framework rejects non-more calls before this handler. */ + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + /* Required for multi-reply streaming: sd_varlink_reply() only stashes the previous reply (for + * the 'continues' flag machinery) when v->sentinel is set. Pass NULL to send an empty reply + * (not an error) when the inhibitor hashmap is empty — this method has no point-lookup, so an + * empty list is a valid result, not a "not found" failure. */ + r = sd_varlink_set_sentinel(link, /* error_id= */ NULL); + if (r < 0) + return r; + + Inhibitor *inhibitor; + HASHMAP_FOREACH(inhibitor, m->inhibitors) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + r = inhibitor_build_json(inhibitor, &v); + if (r < 0) + return r; + + r = sd_varlink_reply(link, v); + if (r < 0) + return r; + } + + return 0; +} + static int vl_method_release_session(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { Manager *m = ASSERT_PTR(userdata); int r; struct { const char *id; - } p; + } 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 }, {} }; @@ -317,7 +743,9 @@ static int vl_method_release_session(sd_varlink *link, sd_json_variant *paramete return r; Session *session; - r = manager_varlink_get_session_by_name(m, link, p.id, &session); + r = manager_varlink_get_session_by_name_or_pidref(m, link, p.id, /* pidref= */ NULL, &session); + if (r == -ESRCH) + return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL); if (r < 0) return r; @@ -325,6 +753,8 @@ static int vl_method_release_session(sd_varlink *link, sd_json_variant *paramete r = manager_varlink_get_session_by_peer(m, link, /* consult_display= */ false, &peer_session); if (r < 0) return r; + if (!peer_session) + return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL); if (session != peer_session) return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, /* parameters= */ NULL); @@ -336,6 +766,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 +883,24 @@ 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.Login.ListSessions", vl_method_list_sessions, + "io.systemd.Login.ListUsers", vl_method_list_users, + "io.systemd.Login.ListSeats", vl_method_list_seats, + "io.systemd.Login.ListInhibitors", vl_method_list_inhibitors, + "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/logind.c b/src/login/logind.c index 2b7984051e197..973506a8922a1 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -35,7 +35,7 @@ #include "logind-utmp.h" #include "logind-varlink.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "parse-util.h" #include "process-util.h" #include "service-util.h" @@ -1116,6 +1116,7 @@ static int manager_dispatch_idle_action(sd_event_source *s, uint64_t t, void *us Manager *m = ASSERT_PTR(userdata); struct dual_timestamp since; usec_t n, elapse; + bool idle; int r; if (m->idle_action == HANDLE_IGNORE || @@ -1124,8 +1125,8 @@ static int manager_dispatch_idle_action(sd_event_source *s, uint64_t t, void *us n = now(CLOCK_MONOTONIC); - r = manager_get_idle_hint(m, &since); - if (r <= 0) { + idle = manager_get_idle_hint(m, &since); + if (!idle) { /* Not idle. Let's check if after a timeout it might be idle then. */ elapse = n + m->idle_action_usec; m->was_idle = false; diff --git a/src/login/logind.h b/src/login/logind.h index 4e3c15de52124..d96bb753dde13 100644 --- a/src/login/logind.h +++ b/src/login/logind.h @@ -159,7 +159,7 @@ int manager_spawn_autovt(Manager *m, unsigned vtnr); bool manager_shall_kill(Manager *m, const char *user); -int manager_get_idle_hint(Manager *m, dual_timestamp *t); +bool manager_get_idle_hint(Manager *m, dual_timestamp *ret_timestamp); int manager_get_user_by_pid(Manager *m, pid_t pid, User **ret); int manager_get_session_by_pidref(Manager *m, const PidRef *pid, Session **ret); diff --git a/src/login/meson.build b/src/login/meson.build index d6654ff5ced50..44325ccd7c02f 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', @@ -47,9 +48,6 @@ executables += [ 'dbus' : true, 'sources' : systemd_logind_sources, 'extract' : systemd_logind_extract_sources, - 'dependencies' : [ - threads, - ], }, executable_template + { 'name' : 'loginctl', @@ -59,7 +57,6 @@ executables += [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, executable_template + { @@ -78,7 +75,6 @@ executables += [ test_template + { 'sources' : files('test-login-tables.c'), 'objects' : ['systemd-logind'], - 'dependencies' : threads, }, test_template + { 'sources' : files('test-session-properties.c'), diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index cf8fe30ebeac1..06d19607b2f9c 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -88,7 +87,7 @@ static int parse_caps( c = capability_from_name(s); if (c < 0) { - pam_syslog(pamh, LOG_WARNING, "Unknown capability, ignoring: %s", s); + sym_pam_syslog(pamh, LOG_WARNING, "Unknown capability, ignoring: %s", s); continue; } @@ -118,6 +117,8 @@ static int parse_argv( const char **type, const char **desktop, const char **area, + const char **inhibit_what, + const char **inhibit_why, bool *debug, uint64_t *default_capability_bounding_set, uint64_t *default_capability_ambient_set) { @@ -146,9 +147,15 @@ static int parse_argv( } else if ((p = startswith(argv[i], "area="))) { if (!isempty(p) && !filename_is_valid(p)) - pam_syslog(pamh, LOG_WARNING, "Area name specified among PAM module parameters is not valid, ignoring: %s", p); + sym_pam_syslog(pamh, LOG_WARNING, "Area name specified among PAM module parameters is not valid, ignoring: %s", p); else if (area) *area = p; + } else if ((p = startswith(argv[i], "inhibit="))) { + if (inhibit_what) + *inhibit_what = p; + } else if ((p = startswith(argv[i], "inhibit-why="))) { + if (inhibit_why) + *inhibit_why = p; } else if (streq(argv[i], "debug")) { if (debug) @@ -157,22 +164,22 @@ static int parse_argv( } else if ((p = startswith(argv[i], "debug="))) { r = parse_boolean(p); if (r < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", p); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", p); else if (debug) *debug = r; } else if ((p = startswith(argv[i], "default-capability-bounding-set="))) { r = parse_caps(pamh, p, default_capability_bounding_set); if (r < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse default-capability-bounding-set= argument, ignoring: %s", p); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse default-capability-bounding-set= argument, ignoring: %s", p); } else if ((p = startswith(argv[i], "default-capability-ambient-set="))) { r = parse_caps(pamh, p, default_capability_ambient_set); if (r < 0) - pam_syslog(pamh, LOG_WARNING, "Failed to parse default-capability-ambient-set= argument, ignoring: %s", p); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse default-capability-ambient-set= argument, ignoring: %s", p); } else - pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); + sym_pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); } return 0; @@ -184,7 +191,7 @@ static int acquire_user_record(pam_handle_t *pamh, UserRecord **ret_record) { assert(pamh); const char *username = NULL; - r = pam_get_user(pamh, &username, NULL); + r = sym_pam_get_user(pamh, &username, NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get user name: @PAMERR@"); if (isempty(username)) @@ -198,14 +205,14 @@ static int acquire_user_record(pam_handle_t *pamh, UserRecord **ret_record) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL; const char *json = NULL; - r = pam_get_data(pamh, field, (const void**) &json); + r = sym_pam_get_data(pamh, field, (const void**) &json); if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get PAM user record data: @PAMERR@"); if (r == PAM_SUCCESS && json) { _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"); @@ -240,7 +247,7 @@ static int acquire_user_record(pam_handle_t *pamh, UserRecord **ret_record) { return pam_syslog_errno(pamh, LOG_ERR, r, "Failed to format user JSON: %m"); /* And cache it for everyone else */ - r = pam_set_data(pamh, field, formatted, pam_cleanup_free); + r = sym_pam_set_data(pamh, field, formatted, pam_cleanup_free); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM user record data '%s': @PAMERR@", field); @@ -279,7 +286,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); @@ -382,7 +389,7 @@ static int append_session_memory_max(pam_handle_t *pamh, sd_bus_message *m, cons uint64_t val; r = parse_size(limit, 1024, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.memory_max, ignoring: %s", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.memory_max, ignoring: %s", limit); return 0; } @@ -402,7 +409,7 @@ static int append_session_runtime_max_sec(pam_handle_t *pamh, sd_bus_message *m, usec_t val; r = parse_sec(limit, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit); return 0; } @@ -422,7 +429,7 @@ static int append_session_tasks_max(pam_handle_t *pamh, sd_bus_message *m, const uint64_t val; r = safe_atou64(limit, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.tasks_max, ignoring: %s", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.tasks_max, ignoring: %s", limit); return 0; } @@ -441,7 +448,7 @@ static int append_session_cpu_weight(pam_handle_t *pamh, sd_bus_message *m, cons uint64_t val; r = cg_cpu_weight_parse(limit, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.cpu_weight, ignoring: %s", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.cpu_weight, ignoring: %s", limit); return 0; } @@ -460,7 +467,7 @@ static int append_session_io_weight(pam_handle_t *pamh, sd_bus_message *m, const uint64_t val; r = cg_weight_parse(limit, &val); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.io_weight, ignoring: %s", limit); + sym_pam_syslog(pamh, LOG_WARNING, "Failed to parse systemd.io_weight, ignoring: %s", limit); return 0; } @@ -479,7 +486,7 @@ static const char* getenv_harder(pam_handle_t *pamh, const char *key, const char * PAM services don't have to be reworked to set systemd-specific properties, but these properties * can still be set from the unit file Environment= block. */ - v = pam_getenv(pamh, key); + v = sym_pam_getenv(pamh, key); if (!isempty(v)) return v; @@ -507,9 +514,9 @@ static bool getenv_harder_bool(pam_handle_t *pamh, const char *key, bool fallbac r = parse_boolean(v); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, - "Failed to parse environment variable value '%s' of '%s', falling back to using '%s'.", - v, key, true_false(fallback)); + sym_pam_syslog(pamh, LOG_WARNING, + "Failed to parse environment variable value '%s' of '%s', falling back to using '%s'.", + v, key, true_false(fallback)); return fallback; } @@ -529,9 +536,9 @@ static uint32_t getenv_harder_uint32(pam_handle_t *pamh, const char *key, uint32 uint32_t u; r = safe_atou32(v, &u); if (r < 0) { - pam_syslog(pamh, LOG_WARNING, - "Failed to parse environment variable value '%s' of '%s' as unsigned integer, falling back to using %" PRIu32 ".", - v, key, fallback); + sym_pam_syslog(pamh, LOG_WARNING, + "Failed to parse environment variable value '%s' of '%s' as unsigned integer, falling back to using %" PRIu32 ".", + v, key, fallback); return fallback; } @@ -548,14 +555,14 @@ static int update_environment(pam_handle_t *pamh, const char *key, const char *v * about errors. */ if (isempty(value)) { - /* Unset the variable if set. Note that pam_putenv() would log nastily behind our back if we + /* Unset the variable if set. Note that sym_pam_putenv() would log nastily behind our back if we * call it without the variable actually being set. Hence we check explicitly if it's set * before. */ - if (!pam_getenv(pamh, key)) + if (!sym_pam_getenv(pamh, key)) return PAM_SUCCESS; - r = pam_putenv(pamh, key); + r = sym_pam_putenv(pamh, key); if (!IN_SET(r, PAM_SUCCESS, PAM_BAD_ITEM)) return pam_syslog_pam_error(pamh, LOG_WARNING, r, "Failed to unset %s environment variable: @PAMERR@", key); @@ -563,7 +570,7 @@ static int update_environment(pam_handle_t *pamh, const char *key, const char *v return PAM_SUCCESS; } - r = pam_misc_setenv(pamh, key, value, /* readonly= */ false); + r = pam_putenv_assign(pamh, key, value); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set environment variable %s: @PAMERR@", key); @@ -588,7 +595,7 @@ static int propagate_credential_to_environment(pam_handle_t *pamh, bool debug, c return PAM_SUCCESS; } - r = pam_misc_setenv(pamh, varname, value, 0); + r = pam_putenv_assign(pamh, varname, value); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set environment variable %s: @PAMERR@", varname); @@ -610,7 +617,7 @@ static bool validate_runtime_directory(pam_handle_t *pamh, const char *path, uid * otherwise we might end up setting $XDG_RUNTIME_DIR to some directory owned by the wrong user. */ if (!path_is_absolute(path)) { - pam_syslog(pamh, LOG_ERR, "Provided runtime directory '%s' is not absolute.", path); + sym_pam_syslog(pamh, LOG_ERR, "Provided runtime directory '%s' is not absolute.", path); goto fail; } @@ -620,19 +627,19 @@ static bool validate_runtime_directory(pam_handle_t *pamh, const char *path, uid } if (!S_ISDIR(st.st_mode)) { - pam_syslog(pamh, LOG_ERR, "Runtime directory '%s' is not actually a directory.", path); + sym_pam_syslog(pamh, LOG_ERR, "Runtime directory '%s' is not actually a directory.", path); goto fail; } if (st.st_uid != uid) { - pam_syslog(pamh, LOG_ERR, "Runtime directory '%s' is not owned by UID " UID_FMT ", as it should.", path, uid); + sym_pam_syslog(pamh, LOG_ERR, "Runtime directory '%s' is not owned by UID " UID_FMT ", as it should.", path, uid); goto fail; } return true; fail: - pam_syslog(pamh, LOG_WARNING, "Not setting $XDG_RUNTIME_DIR, as the directory is not in order."); + sym_pam_syslog(pamh, LOG_WARNING, "Not setting $XDG_RUNTIME_DIR, as the directory is not in order."); return false; } @@ -642,7 +649,7 @@ static int pam_putenv_and_log(pam_handle_t *pamh, const char *e, bool debug) { assert(pamh); assert(e); - r = pam_putenv(pamh, e); + r = sym_pam_putenv(pamh, e); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM environment variable %s: @PAMERR@", e); @@ -992,7 +999,7 @@ static void session_context_mangle( c->area = ur->default_area; if (!isempty(c->area) && !filename_is_valid(c->area)) { - pam_syslog(pamh, LOG_WARNING, "Specified area '%s' is not a valid filename, ignoring area request.", c->area); + sym_pam_syslog(pamh, LOG_WARNING, "Specified area '%s' is not a valid filename, ignoring area request.", c->area); c->area = NULL; } @@ -1048,7 +1055,7 @@ static void session_context_mangle( if (streq(c->class, "user")) c->class = "user-incomplete"; else - pam_syslog(pamh, LOG_WARNING, "PAM session of class '%s' is incomplete, which is not supported, ignoring.", c->class); + sym_pam_syslog(pamh, LOG_WARNING, "PAM session of class '%s' is incomplete, which is not supported, ignoring.", c->class); } c->remote = !isempty(c->remote_host) && !is_localhost(c->remote_host); @@ -1144,11 +1151,11 @@ 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)), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("VTNr", c->vtnr), JSON_BUILD_PAIR_STRING_NON_EMPTY("TTY", c->tty), JSON_BUILD_PAIR_STRING_NON_EMPTY("Display", c->display), SD_JSON_BUILD_PAIR_BOOLEAN("Remote", c->remote), @@ -1253,8 +1260,7 @@ static int register_session( return PAM_SUCCESS; } - pam_syslog(pamh, LOG_ERR, - "Failed to create session: %s", bus_error_message(&error, r)); + sym_pam_syslog(pamh, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r)); return PAM_SESSION_ERR; } @@ -1282,7 +1288,7 @@ static int register_session( if (fd < 0) return pam_syslog_errno(pamh, LOG_ERR, errno, "Failed to dup session fd: %m"); - r = pam_set_data(pamh, "systemd.session-fd", FD_TO_PTR(fd), NULL); + r = sym_pam_set_data(pamh, "systemd.session-fd", FD_TO_PTR(fd), NULL); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to install session fd: @PAMERR@"); TAKE_FD(fd); @@ -1580,7 +1586,7 @@ static int setup_environment( pam_syslog_errno(pamh, LOG_WARNING, r, "Path '%s' of requested user area '%s' is not accessible, reverting to regular home directory: %m", j, area); /* Also tell the user directly at login, but a bit more vague */ - pam_info(pamh, "Path '%s' of requested user area '%s' is not accessible, reverting to regular home directory.", j, area); + sym_pam_info(pamh, "Path '%s' of requested user area '%s' is not accessible, reverting to regular home directory.", j, area); area = NULL; } else { /* Validate that the target is definitely owned by user */ @@ -1589,10 +1595,10 @@ static int setup_environment( return pam_syslog_errno(pamh, LOG_ERR, errno, "Unable to fstat() target area directory '%s': %m", ha); if (st.st_uid != ur->uid) { - pam_syslog(pamh, LOG_ERR, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area); + sym_pam_syslog(pamh, LOG_ERR, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area); /* Also tell the user directly at login. */ - pam_info(pamh, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area); + sym_pam_info(pamh, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area); area = NULL; } else { /* All good, now make a copy of the area string, since we quite likely are @@ -1631,13 +1637,13 @@ static int open_osc_context(pam_handle_t *pamh, const char *session_type, UserRe if (!streq_ptr(session_type, "tty")) return PAM_SUCCESS; - const char *e = pam_getenv(pamh, "TERM"); + const char *e = sym_pam_getenv(pamh, "TERM"); if (!e) e = getenv("TERM"); if (streq_ptr(e, "dumb")) return PAM_SUCCESS; - /* NB: we output directly to stdout, instead of going via pam_info() or so, because that's too + /* NB: we output directly to stdout, instead of going via sym_pam_info() or so, because that's too * high-level for us, as it suffixes the output with a newline, expecting a full blown text message * as prompt string, not just an ANSI sequence. Note that PAM's conv_misc() actually goes to stdout * anyway, hence let's do so here too, but only after careful validation. */ @@ -1657,7 +1663,7 @@ static int open_osc_context(pam_handle_t *pamh, const char *session_type, UserRe sd_id128_t osc_id; r = osc_context_open_session( ur->user_name, - pam_getenv(pamh, "XDG_SESSION_ID"), + sym_pam_getenv(pamh, "XDG_SESSION_ID"), &osc, &osc_id); if (r < 0) @@ -1672,7 +1678,7 @@ static int open_osc_context(pam_handle_t *pamh, const char *session_type, UserRe if (!osc_id_copy) return pam_log_oom(pamh); - r = pam_set_data(pamh, "systemd.osc-context-id", osc_id_copy, pam_cleanup_free); + r = sym_pam_set_data(pamh, "systemd.osc-context-id", osc_id_copy, pam_cleanup_free); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM OSC sequence ID data: @PAMERR@"); @@ -1680,7 +1686,7 @@ static int open_osc_context(pam_handle_t *pamh, const char *session_type, UserRe TAKE_PTR(osc_id_copy); if (tty_opath_fd >= 0) { - r = pam_set_data(pamh, "systemd.osc-context-fd", FD_TO_PTR(tty_opath_fd), pam_cleanup_close); + r = sym_pam_set_data(pamh, "systemd.osc-context-fd", FD_TO_PTR(tty_opath_fd), pam_cleanup_close); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM OSC sequence fd data: @PAMERR@"); @@ -1698,7 +1704,7 @@ static int close_osc_context(pam_handle_t *pamh, bool debug) { const void *p; int tty_opath_fd = -EBADF; - r = pam_get_data(pamh, "systemd.osc-context-fd", &p); + r = sym_pam_get_data(pamh, "systemd.osc-context-fd", &p); if (r == PAM_SUCCESS) tty_opath_fd = PTR_TO_FD(p); else if (r != PAM_NO_MODULE_DATA) @@ -1707,7 +1713,7 @@ static int close_osc_context(pam_handle_t *pamh, bool debug) { return PAM_SUCCESS; const sd_id128_t *osc_id = NULL; - r = pam_get_data(pamh, "systemd.osc-context-id", (const void**) &osc_id); + r = sym_pam_get_data(pamh, "systemd.osc-context-id", (const void**) &osc_id); if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to get PAM OSC context id data: @PAMERR@"); if (!osc_id) @@ -1721,7 +1727,7 @@ static int close_osc_context(pam_handle_t *pamh, bool debug) { } /* /bin/login calls us with fds 0, 1, 2 closed, which is just weird. Let's step outside of that - * range, just in case pam_syslog() or so logs to stderr */ + * range, just in case sym_pam_syslog() or so logs to stderr */ fd = fd_move_above_stdio(fd); /* Safety check, let's verify this is a valid TTY we just opened */ @@ -1741,6 +1747,57 @@ static int close_osc_context(pam_handle_t *pamh, bool debug) { return PAM_SUCCESS; } +static int acquire_inhibit_lock( + pam_handle_t *pamh, + const char *inhibit_what, + const char *inhibit_why, + bool debug) { + + _cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL; + _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 *reply = NULL; + const char *service = NULL; + int r; + + assert(pamh); + assert(inhibit_what); + + r = pam_acquire_bus_connection(pamh, "pam-systemd", debug, &bus, &d); + if (r != PAM_SUCCESS) + return r; + + (void) sym_pam_get_item(pamh, PAM_SERVICE, (const void **) &service); + + r = bus_call_method(bus, bus_login_mgr, "Inhibit", &error, &reply, "ssss", + inhibit_what, + service ?: "pam_systemd", + inhibit_why ?: "Active PAM session", + "block"); + if (r < 0) { + sym_pam_syslog(pamh, LOG_WARNING, "Failed to acquire inhibit lock: %s", bus_error_message(&error, r)); + return PAM_SESSION_ERR; + } + + int raw_fd; + r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_UNIX_FD, &raw_fd); + if (r < 0) + return pam_bus_log_parse_error(pamh, r); + + _cleanup_close_ int fd = fcntl(raw_fd, F_DUPFD_CLOEXEC, 3); + if (fd < 0) + return pam_syslog_errno(pamh, LOG_ERR, errno, "Failed to dup inhibit lock fd: %m"); + + r = sym_pam_set_data(pamh, "systemd.inhibit-fd", FD_TO_PTR(fd), pam_cleanup_close); + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(pamh, LOG_ERR, r, + "Failed to store inhibit lock fd: @PAMERR@"); + TAKE_FD(fd); + + pam_debug_syslog(pamh, debug, "Acquired inhibit lock for '%s'.", inhibit_what); + return PAM_SUCCESS; +} + _public_ PAM_EXTERN int pam_sm_open_session( pam_handle_t *pamh, int flags, @@ -1750,14 +1807,14 @@ _public_ PAM_EXTERN int pam_sm_open_session( assert(pamh); - r = dlopen_libpam(); + r = DLOPEN_LIBPAM(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); if (r < 0) return PAM_SERVICE_ERR; pam_log_setup(); uint64_t default_capability_bounding_set = CAP_MASK_UNSET, default_capability_ambient_set = CAP_MASK_UNSET; - const char *class_pam = NULL, *type_pam = NULL, *desktop_pam = NULL, *area_pam = NULL; + const char *class_pam = NULL, *type_pam = NULL, *desktop_pam = NULL, *area_pam = NULL, *inhibit_what = NULL, *inhibit_why = NULL; bool debug = false; if (parse_argv(pamh, argc, argv, @@ -1765,6 +1822,8 @@ _public_ PAM_EXTERN int pam_sm_open_session( &type_pam, &desktop_pam, &area_pam, + &inhibit_what, + &inhibit_why, &debug, &default_capability_bounding_set, &default_capability_ambient_set) < 0) @@ -1795,6 +1854,8 @@ _public_ PAM_EXTERN int pam_sm_open_session( c.desktop = getenv_harder(pamh, "XDG_SESSION_DESKTOP", desktop_pam); c.area = getenv_harder(pamh, "XDG_AREA", area_pam); c.incomplete = getenv_harder_bool(pamh, "XDG_SESSION_INCOMPLETE", false); + inhibit_what = getenv_harder(pamh, "XDG_SESSION_INHIBIT", inhibit_what); + inhibit_why = getenv_harder(pamh, "XDG_SESSION_INHIBIT_WHY", inhibit_why); const char *extra_device_access = getenv_harder(pamh, "XDG_SESSION_EXTRA_DEVICE_ACCESS", NULL); if (extra_device_access) { @@ -1820,6 +1881,11 @@ _public_ PAM_EXTERN int pam_sm_open_session( if (r != PAM_SUCCESS) return r; + if (inhibit_what) + /* Failure to acquire the inhibit lock is not fatal, session setup should proceed + * regardless. */ + (void) acquire_inhibit_lock(pamh, inhibit_what, inhibit_why, debug); + r = import_shell_credentials(pamh, debug); if (r != PAM_SUCCESS) return r; @@ -1849,6 +1915,10 @@ _public_ PAM_EXTERN int pam_sm_close_session( assert(pamh); + r = DLOPEN_LIBPAM(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return PAM_SERVICE_ERR; + pam_log_setup(); if (parse_argv(pamh, @@ -1857,6 +1927,8 @@ _public_ PAM_EXTERN int pam_sm_close_session( /* type= */ NULL, /* desktop= */ NULL, /* area= */ NULL, + /* inhibit_what= */ NULL, + /* inhibit_why= */ NULL, &debug, /* default_capability_bounding_set= */ NULL, /* default_capability_ambient_set= */ NULL) < 0) @@ -1866,7 +1938,9 @@ _public_ PAM_EXTERN int pam_sm_close_session( (void) close_osc_context(pamh, debug); - id = pam_getenv(pamh, "XDG_SESSION_ID"); + (void) sym_pam_set_data(pamh, "systemd.inhibit-fd", NULL, NULL); + + id = sym_pam_getenv(pamh, "XDG_SESSION_ID"); if (id) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; bool done = false; diff --git a/src/login/pam_systemd_loadkey.c b/src/login/pam_systemd_loadkey.c index 3e0df8c86b31b..c93dedf5ea88d 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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); if (r < 0) return PAM_SERVICE_ERR; @@ -41,7 +41,7 @@ _public_ PAM_EXTERN int pam_sm_authenticate( else if (streq(argv[i], "debug")) debug = true; else - pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); + sym_pam_syslog(pamh, LOG_WARNING, "Unknown parameter '%s', ignoring.", argv[i]); } pam_debug_syslog(pamh, debug, "pam-systemd-loadkey: initializing..."); @@ -81,7 +81,7 @@ _public_ PAM_EXTERN int pam_sm_authenticate( } else if (passwords_len > 1) pam_debug_syslog(pamh, debug, "Multiple passwords found in the key. Using the last one."); - r = pam_set_item(pamh, PAM_AUTHTOK, passwords[passwords_len - 1]); + r = sym_pam_set_item(pamh, PAM_AUTHTOK, passwords[passwords_len - 1]); if (r != PAM_SUCCESS) return pam_syslog_pam_error(pamh, LOG_ERR, r, "Failed to set PAM auth token: @PAMERR@"); diff --git a/src/login/sysfs-show.c b/src/login/sysfs-show.c index 202041832c46d..bdbd8a64e4413 100644 --- a/src/login/sysfs-show.c +++ b/src/login/sysfs-show.c @@ -4,6 +4,7 @@ #include "alloc-util.h" #include "device-enumerator-private.h" +#include "device-private.h" #include "device-util.h" #include "glyph-util.h" #include "path-util.h" @@ -61,8 +62,8 @@ static int show_sysfs_one( is_master = sd_device_has_current_tag(dev_list[*i_dev], "master-of-seat") > 0; - if (sd_device_get_sysattr_value(dev_list[*i_dev], "name", &name) < 0) - (void) sd_device_get_sysattr_value(dev_list[*i_dev], "id", &name); + if (device_get_sysattr_safe_string(dev_list[*i_dev], "name", &name) < 0) + (void) device_get_sysattr_safe_string(dev_list[*i_dev], "id", &name); /* Look if there's more coming after this */ for (lookahead = *i_dev + 1; lookahead < n_dev; lookahead++) { diff --git a/src/login/systemd-user.in b/src/login/systemd-user.in index ce22744501735..70ca709f51762 100644 --- a/src/login/systemd-user.in +++ b/src/login/systemd-user.in @@ -8,6 +8,8 @@ {% endif %} account required pam_unix.so no_pass_expiry +auth required pam_deny.so + {% if HAVE_SELINUX %} session required pam_selinux.so close session required pam_selinux.so nottys open diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c index 6cf157ffada3f..bc522bcbca283 100644 --- a/src/login/user-runtime-dir.c +++ b/src/login/user-runtime-dir.c @@ -16,7 +16,7 @@ #include "limits-util.h" #include "log.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "path-util.h" @@ -183,7 +183,7 @@ static int do_umount(const char *user) { /* The user may be already removed. So, first try to parse the string by parse_uid(), * and if it fails, fall back to get_user_creds(). */ if (parse_uid(user, &uid) < 0) { - r = get_user_creds(&user, &uid, NULL, NULL, NULL, 0); + r = get_user_creds(user, /* flags= */ 0, NULL, &uid, NULL, NULL, NULL); if (r < 0) return log_error_errno(r, r == -ESRCH ? "No such user \"%s\"" : diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index 70774dcdd1b85..2363427b5f54e 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,94 @@ 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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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(opts.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(opts.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(&opts) > 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..cf1d3d6bdbc30 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); @@ -69,6 +70,7 @@ int bus_machine_method_unregister(sd_bus_message *message, void *userdata, sd_bu m->uid, /* flags= */ 0, &m->manager->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -103,6 +105,7 @@ int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus m->uid, /* flags= */ 0, &m->manager->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -155,6 +158,7 @@ int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_erro m->uid, /* flags= */ 0, &m->manager->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -246,6 +250,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."); @@ -279,6 +302,7 @@ int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_ m->uid, /* flags= */ 0, &m->manager->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -324,6 +348,7 @@ int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bu m->uid, /* flags= */ 0, &m->manager->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -366,21 +391,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, @@ -411,6 +440,10 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu r = sd_bus_message_read_strv(message, &env); if (r < 0) return r; + + if (strv_length(env) > ENVIRONMENT_ASSIGNMENTS_MAX) + return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED, + "Too many environment assignments in a single query."); if (!strv_env_is_valid(env)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); @@ -434,6 +467,7 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu m->uid, /* flags= */ 0, &m->manager->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -565,13 +599,13 @@ int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_erro copy_flags |= COPY_REPLACE; } - if (!path_is_absolute(src)) - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute."); + if (!path_is_absolute(src) || !path_is_normalized(src)) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and normalized."); if (isempty(dest)) dest = src; - else if (!path_is_absolute(dest)) - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute."); + else if (!path_is_absolute(dest) || !path_is_normalized(dest)) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and normalized."); if (manager->runtime_scope != RUNTIME_SCOPE_USER) { const char *details[] = { diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index a499dbc3be2d3..a73f81b6f7225 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -126,6 +126,7 @@ static int machine_cid(const char *name, sd_json_variant *variant, sd_json_dispa int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { Manager *manager = ASSERT_PTR(userdata); _cleanup_(machine_freep) Machine *machine = NULL; + bool sender_is_admin = false; int r; static const sd_json_dispatch_field dispatch_table[] = { @@ -142,6 +143,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,14 +157,20 @@ 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( + r = varlink_verify_polkit_async_full( link, manager->system_bus, machine->allocate_unit ? "org.freedesktop.machine1.create-machine" : "org.freedesktop.machine1.register-machine", (const char**) STRV_MAKE("name", machine->name, "class", machine_class_to_string(machine->class)), - &manager->polkit_registry); + /* good_user= */ UID_INVALID, + /* flags= */ 0, + &manager->polkit_registry, + &sender_is_admin); if (r <= 0) return r; } @@ -189,8 +197,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 && !sender_is_admin) { r = process_is_owned_by_uid(&machine->leader, machine->uid); if (r < 0) return r; @@ -317,7 +327,8 @@ int vl_method_unregister_internal(sd_varlink *link, sd_json_variant *parameters, "verb", "unregister"), machine->uid, /* flags= */ 0, - &manager->polkit_registry); + &manager->polkit_registry, + /* ret_admin= */ NULL); if (r <= 0) return r; } @@ -343,7 +354,8 @@ int vl_method_terminate_internal(sd_varlink *link, sd_json_variant *parameters, "verb", "terminate"), machine->uid, /* flags= */ 0, - &manager->polkit_registry); + &manager->polkit_registry, + /* ret_admin= */ NULL); if (r <= 0) return r; } @@ -355,10 +367,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 +385,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 +394,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 +412,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( @@ -415,7 +423,8 @@ int vl_method_kill(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met "verb", "kill"), machine->uid, /* flags= */ 0, - &manager->polkit_registry); + &manager->polkit_registry, + /* ret_admin= */ NULL); if (r <= 0) return r; } @@ -552,6 +561,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); @@ -562,7 +590,8 @@ int vl_method_open(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met (const char**) polkit_details, machine->uid, /* flags= */ 0, - &manager->polkit_registry); + &manager->polkit_registry, + /* ret_admin= */ NULL); if (r <= 0) return r; } @@ -638,7 +667,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,11 +828,11 @@ 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), - { "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 }, - { "mkdir", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MachineMountParameters, mkdir), 0 }, + VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineMountParameters), + { "source", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineMountParameters, src), SD_JSON_MANDATORY|SD_JSON_STRICT }, + { "destination", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineMountParameters, dest), SD_JSON_STRICT }, + { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MachineMountParameters, read_only), 0 }, + { "mkdir", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MachineMountParameters, mkdir), 0 }, VARLINK_DISPATCH_POLKIT_FIELD, {} }; @@ -823,7 +852,7 @@ int vl_method_bind_mount(sd_varlink *link, sd_json_variant *parameters, sd_varli if (r != 0) return r; - /* There is no need for extra validation since json_dispatch_const_path() does path_is_valid() and path_is_absolute(). */ + /* There is no need for extra validation since json_dispatch_const_path() with SD_JSON_STRICT does path_is_normalized() and path_is_absolute(). */ const char *dest = p.dest ?: p.src; Machine *machine; @@ -910,9 +939,9 @@ static int copy_done(Operation *operation, int ret, sd_bus_error *error) { int vl_method_copy_internal(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata, bool copy_from) { static const sd_json_dispatch_field dispatch_table[] = { VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineCopyParameters), - { "source", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineCopyParameters, src), SD_JSON_MANDATORY }, - { "destination", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineCopyParameters, dest), 0 }, - { "replace", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MachineCopyParameters, replace), 0 }, + { "source", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineCopyParameters, src), SD_JSON_MANDATORY|SD_JSON_STRICT }, + { "destination", SD_JSON_VARIANT_STRING, json_dispatch_const_path, offsetof(MachineCopyParameters, dest), SD_JSON_STRICT }, + { "replace", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MachineCopyParameters, replace), 0 }, VARLINK_DISPATCH_POLKIT_FIELD, {} }; @@ -933,7 +962,7 @@ int vl_method_copy_internal(sd_varlink *link, sd_json_variant *parameters, sd_va if (r != 0) return r; - /* There is no need for extra validation since json_dispatch_const_path() does path_is_valid() and path_is_absolute(). */ + /* There is no need for extra validation since json_dispatch_const_path() with SD_JSON_STRICT does path_is_normalized() and path_is_absolute(). */ const char *dest = p.dest ?: p.src; const char *container_path = copy_from ? p.src : dest; const char *host_path = copy_from ? dest : p.src; diff --git a/src/machine/machine.c b/src/machine/machine.c index da26bdf7a0711..56d93ae560b1c 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -26,7 +26,7 @@ #include "machine-dbus.h" #include "machined-resolve-hook.h" #include "machined.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "namespace-util.h" #include "operation.h" #include "parse-util.h" @@ -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..b0efb6ec029cf 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -9,6 +8,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" @@ -28,23 +28,29 @@ #include "cgroup-util.h" #include "edit-util.h" #include "env-util.h" +#include "fd-util.h" #include "format-ifname.h" #include "format-table.h" #include "format-util.h" +#include "help-util.h" #include "hostname-util.h" #include "import-util.h" #include "in-addr-util.h" +#include "json-util.h" #include "label-util.h" #include "log.h" #include "logs-show.h" #include "machine-dbus.h" +#include "machine-util.h" #include "main-func.h" #include "nulstr-util.h" +#include "options.h" #include "osc-context.h" #include "output-mode.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" @@ -53,6 +59,7 @@ #include "ptyfwd.h" #include "runtime-scope.h" #include "stdio-util.h" +#include "storage-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -193,6 +200,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 +270,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 +285,10 @@ static int show_table(Table *table, const char *word) { return 0; } -static int list_machines(int argc, char *argv[], void *userdata) { +VERB_GROUP("Machine Commands"); + +VERB_DEFAULT_NOARG(verb_list_machines, "list", "List running VMs and containers"); +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,66 +365,6 @@ 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) { - - _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; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - pager_open(arg_pager_flags); - - r = bus_call_method(bus, bus_machine_mgr, "ListImages", &error, &reply, NULL); - if (r < 0) - return log_error_errno(r, "Could not get images: %s", bus_error_message(&error, r)); - - table = table_new("name", "type", "ro", "usage", "created", "modified"); - if (!table) - return log_oom(); - - if (arg_full) - table_set_width(table, 0); - - (void) table_set_align_percent(table, TABLE_HEADER_CELL(3), 100); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)"); - if (r < 0) - return bus_log_parse_error(r); - - for (;;) { - uint64_t crtime, mtime, size; - const char *name, *type; - int ro_int; - - r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &ro_int, &crtime, &mtime, &size, NULL); - if (r < 0) - return bus_log_parse_error(r); - if (r == 0) - break; - - if (name[0] == '.' && !arg_all) - continue; - - r = table_add_many(table, - TABLE_STRING, name, - TABLE_STRING, type, - TABLE_BOOLEAN, ro_int, - TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL, - TABLE_SIZE, size, - TABLE_TIMESTAMP, crtime, - TABLE_TIMESTAMP, mtime); - if (r < 0) - return table_log_add_error(r); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - return show_table(table, "images"); -} - static int show_unit_cgroup( sd_bus *bus, const char *unit, @@ -676,7 +627,6 @@ static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_ } static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) { - static const struct bus_properties_map map[] = { { "Name", "s", NULL, offsetof(MachineStatusInfo, name) }, { "Class", "s", NULL, offsetof(MachineStatusInfo, class) }, @@ -745,7 +695,11 @@ 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) { +VERB(verb_show_machine, "status", "NAME…", 2, VERB_ANY, 0, + "Show VM/container details"); +VERB(verb_show_machine, "show", "[NAME…]", VERB_ANY, VERB_ANY, 0, + "Show properties of one or more VMs/containers"); +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); @@ -785,354 +739,628 @@ static int show_machine(int argc, char *argv[], void *userdata) { return r; } -static int print_image_hostname(sd_bus *bus, const char *name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *hn; +static int make_service_name(const char *name, char **ret) { int r; - r = bus_call_method(bus, bus_machine_mgr, "GetImageHostname", NULL, &reply, "s", name); - if (r < 0) - return r; + assert(name); + assert(ret); + assert(arg_runner >= 0 && arg_runner < _RUNNER_MAX); - r = sd_bus_message_read(reply, "s", &hn); - if (r < 0) - return r; + if (!hostname_is_valid(name, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid machine name %s.", name); - if (!isempty(hn)) - printf("\tHostname: %s\n", hn); + r = unit_name_build(machine_runner_unit_prefix_table[arg_runner], name, ".service", ret); + if (r < 0) + return log_error_errno(r, "Failed to build unit name: %m"); return 0; } -static int print_image_machine_id(sd_bus *bus, const char *name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - sd_id128_t id; +static int image_exists(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; - r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineID", NULL, &reply, "s", name); - if (r < 0) - return r; + assert(bus); + assert(name); - r = bus_message_read_id128(reply, &id); - if (r < 0) - return r; + r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, NULL, "s", name); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_IMAGE)) + return 0; - if (!sd_id128_is_null(id)) - printf(" Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id)); + return log_error_errno(r, "Failed to check whether image %s exists: %s", name, bus_error_message(&error, r)); + } - return 0; + return 1; } -static int print_image_machine_info(sd_bus *bus, const char *name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; +VERB(verb_start_machine, "start", "NAME…", 2, VERB_ANY, 0, + "Start container as a service"); +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); int r; - r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineInfo", NULL, &reply, "s", name); - if (r < 0) - return r; + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + ask_password_agent_open_if_enabled(arg_transport, arg_ask_password); - r = sd_bus_message_enter_container(reply, 'a', "{ss}"); + r = bus_wait_for_jobs_new(bus, &w); if (r < 0) - return r; + return log_error_errno(r, "Could not watch jobs: %m"); - for (;;) { - const char *p, *q; + for (int i = 1; i < argc; i++) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *unit = NULL; + const char *object; - r = sd_bus_message_read(reply, "{ss}", &p, &q); + r = make_service_name(argv[i], &unit); + if (r < 0) + return r; + + r = image_exists(bus, argv[i]); if (r < 0) return r; if (r == 0) - break; + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), + "Machine image '%s' does not exist.", + argv[i]); - if (streq(p, "DEPLOYMENT")) - printf(" Deployment: %s\n", q); + r = bus_call_method( + bus, + bus_systemd_mgr, + "StartUnit", + &error, + &reply, + "ss", unit, "fail"); + if (r < 0) + return log_error_errno(r, "Failed to start unit: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_add(w, object); + if (r < 0) + return log_oom(); } - r = sd_bus_message_exit_container(reply); + r = bus_wait_for_jobs(w, arg_quiet, NULL); if (r < 0) return r; return 0; } -typedef struct ImageStatusInfo { - const char *name; - const char *path; - const char *type; - bool read_only; - usec_t crtime; - usec_t mtime; - uint64_t usage; - uint64_t limit; - uint64_t usage_exclusive; - uint64_t limit_exclusive; -} ImageStatusInfo; - -static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { - assert(bus); - assert(i); +static int parse_machine_uid(const char *spec, const char **machine, char **uid) { + /* + * Whatever is specified in the spec takes priority over global arguments. + */ + char *_uid = NULL; + const char *_machine = NULL; - if (i->name) { - fputs(i->name, stdout); - putchar('\n'); - } + assert(uid); + assert(machine); - if (i->type) - printf("\t Type: %s\n", i->type); + if (spec) { + const char *at; - if (i->path) - printf("\t Path: %s\n", i->path); + at = strchr(spec, '@'); + if (at) { + if (at == spec) + /* Do the same as ssh and refuse "@host". */ + return -EINVAL; - (void) print_image_hostname(bus, i->name); - (void) print_image_machine_id(bus, i->name); - (void) print_image_machine_info(bus, i->name); + _machine = at + 1; + _uid = strndup(spec, at - spec); + if (!_uid) + return -ENOMEM; + } else + _machine = spec; + }; - print_os_release(bus, "GetImageOSRelease", i->name, "\t OS: "); + if (arg_uid && !_uid) { + _uid = strdup(arg_uid); + if (!_uid) + return -ENOMEM; + } - printf("\t RO: %s%s%s\n", - i->read_only ? ansi_highlight_red() : "", - i->read_only ? "read-only" : "writable", - i->read_only ? ansi_normal() : ""); + *uid = _uid; + *machine = isempty(_machine) ? ".host" : _machine; + return 0; +} - if (timestamp_is_set(i->crtime)) - printf("\t Created: %s; %s\n", - FORMAT_TIMESTAMP(i->crtime), FORMAT_TIMESTAMP_RELATIVE(i->crtime)); +static int process_forward(sd_event *event, sd_bus_slot *machine_removed_slot, int master, PTYForwardFlags flags, const char *name) { + int r; - if (timestamp_is_set(i->mtime)) - printf("\tModified: %s; %s\n", - FORMAT_TIMESTAMP(i->mtime), FORMAT_TIMESTAMP_RELATIVE(i->mtime)); + assert(event); + assert(machine_removed_slot); + assert(master >= 0); + assert(name); - if (i->usage != UINT64_MAX) { - if (i->usage_exclusive != i->usage && i->usage_exclusive != UINT64_MAX) - printf("\t Usage: %s (exclusive: %s)\n", - FORMAT_BYTES(i->usage), FORMAT_BYTES(i->usage_exclusive)); + if (!arg_quiet) { + if (streq(name, ".host")) + log_info("Connected to the local host. Press ^] three times within 1s to exit session."); else - printf("\t Usage: %s\n", FORMAT_BYTES(i->usage)); + log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name); } - if (i->limit != UINT64_MAX) { - if (i->limit_exclusive != i->limit && i->limit_exclusive != UINT64_MAX) - printf("\t Limit: %s (exclusive: %s)\n", - FORMAT_BYTES(i->limit), FORMAT_BYTES(i->limit_exclusive)); - else - printf("\t Limit: %s\n", FORMAT_BYTES(i->limit)); + _cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL; + if (!terminal_is_dumb()) { + r = osc_context_open_container(name, /* ret_seq= */ NULL, &osc_context_id); + if (r < 0) + return r; } -} -static int show_image_info(sd_bus *bus, const char *path, bool *new_line) { + r = sd_event_set_signal_exit(event, true); + if (r < 0) + return log_error_errno(r, "Failed to enable SIGINT/SITERM handling: %m"); - static const struct bus_properties_map map[] = { - { "Name", "s", NULL, offsetof(ImageStatusInfo, name) }, - { "Path", "s", NULL, offsetof(ImageStatusInfo, path) }, - { "Type", "s", NULL, offsetof(ImageStatusInfo, type) }, - { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) }, - { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) }, - { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) }, - { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) }, - { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) }, - { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) }, - { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) }, - {} - }; - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - ImageStatusInfo info = {}; - int r; + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + r = pty_forward_new(event, master, flags, &forward); + if (r < 0) + return log_error_errno(r, "Failed to create PTY forwarder: %m"); - assert(bus); - assert(path); - assert(new_line); + /* No userdata should not set previously. */ + assert_se(!sd_bus_slot_set_userdata(machine_removed_slot, forward)); - r = bus_map_all_properties(bus, - "org.freedesktop.machine1", - path, - map, - BUS_MAP_BOOLEAN_AS_BOOL, - &error, - &m, - &info); + r = sd_event_loop(event); if (r < 0) - return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r)); + return log_error_errno(r, "Failed to run event loop: %m"); - if (*new_line) - printf("\n"); - *new_line = true; + bool machine_died = + FLAGS_SET(flags, PTY_FORWARD_IGNORE_VHANGUP) && + !pty_forward_vhangup_honored(forward); - print_image_status_info(bus, &info); + if (!arg_quiet) { + if (machine_died) + log_info("Machine %s terminated.", name); + else if (streq(name, ".host")) + log_info("Connection to the local host terminated."); + else + log_info("Connection to machine %s terminated.", name); + } - return r; + return 0; } -typedef struct PoolStatusInfo { - const char *path; - uint64_t usage; - uint64_t limit; -} PoolStatusInfo; +static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + PTYForward *forward = ASSERT_PTR(userdata); + int r; -static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) { - if (i->path) - printf("\t Path: %s\n", i->path); + assert(m); - if (i->usage != UINT64_MAX) - printf("\t Usage: %s\n", FORMAT_BYTES(i->usage)); + /* Tell the forwarder to exit on the next vhangup(), so that we still flush out what might be queued + * and exit then. */ - if (i->limit != UINT64_MAX) - printf("\t Limit: %s\n", FORMAT_BYTES(i->limit)); -} + r = pty_forward_honor_vhangup(forward); + if (r < 0) { + /* On error, quit immediately. */ + log_error_errno(r, "Failed to make PTY forwarder honor vhangup(): %m"); + (void) sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE); + } -static int show_pool_info(sd_bus *bus) { + return 0; +} - static const struct bus_properties_map map[] = { - { "PoolPath", "s", NULL, offsetof(PoolStatusInfo, path) }, - { "PoolUsage", "t", NULL, offsetof(PoolStatusInfo, usage) }, - { "PoolLimit", "t", NULL, offsetof(PoolStatusInfo, limit) }, - {} - }; +VERB(verb_login_machine, "login", "[NAME]", VERB_ANY, 2, 0, + "Get a login prompt in a container or on the local host"); +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; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int master = -1, r; + sd_bus *bus = ASSERT_PTR(userdata); + const char *match, *machine; - PoolStatusInfo info = { - .usage = UINT64_MAX, - .limit = UINT64_MAX, - }; + if (!strv_isempty(arg_setenv) || arg_uid) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--setenv= and --uid= are not supported for 'login'. Use 'shell' instead."); - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; + if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Login only supported on local machines."); - assert(bus); + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - r = bus_map_all_properties(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - map, - 0, - &error, - &m, - &info); + r = sd_event_default(&event); if (r < 0) - return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r)); - - print_pool_status_info(bus, &info); + return log_error_errno(r, "Failed to get event loop: %m"); - return 0; -} + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); -static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) { - int r; + machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1]; - assert(bus); - assert(path); - assert(new_line); + match = strjoina("type='signal'," + "sender='org.freedesktop.machine1'," + "path='/org/freedesktop/machine1',", + "interface='org.freedesktop.machine1.Manager'," + "member='MachineRemoved'," + "arg0='", machine, "'"); - if (*new_line) - printf("\n"); + r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to request machine removal match: %m"); - *new_line = true; + r = bus_call_method(bus, bus_machine_mgr, "OpenMachineLogin", &error, &reply, "s", machine); + if (r < 0) + return log_error_errno(r, "Failed to get login PTY: %s", bus_error_message(&error, r)); - r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, NULL, arg_property, arg_print_flags, NULL); + r = sd_bus_message_read(reply, "hs", &master, NULL); if (r < 0) - log_error_errno(r, "Could not get properties: %m"); + return bus_log_parse_error(r); - return r; + return process_forward(event, slot, master, PTY_FORWARD_IGNORE_VHANGUP, machine); } -static int show_image(int argc, char *argv[], void *userdata) { +VERB(verb_shell_machine, "shell", "[[USER@]NAME [COMMAND…]]", VERB_ANY, VERB_ANY, 0, + "Invoke a shell (or other command) in a container or on the local host"); +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; - bool properties, new_line = false; + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int master = -1, r; sd_bus *bus = ASSERT_PTR(userdata); - int r = 0; - - properties = !strstr(argv[0], "status"); + const char *match, *machine, *path; + _cleanup_free_ char *uid = NULL; - pager_open(arg_pager_flags); + if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Shell only supported on local machines."); - if (argc <= 1) { + if (terminal_is_dumb()) { + /* Set TERM=dumb if we are running on a dumb terminal or with a pipe. + * Otherwise, we will get unwanted OSC sequences. */ + if (!strv_find_prefix(arg_setenv, "TERM=")) + if (strv_extend(&arg_setenv, "TERM=dumb") < 0) + return log_oom(); + } else + /* Pass $TERM & Co. to shell session, if not explicitly specified. */ + FOREACH_STRING(v, "TERM=", "COLORTERM=", "NO_COLOR=") { + if (strv_find_prefix(arg_setenv, v)) + continue; - /* If no argument is specified, inspect the manager - * itself */ + const char *t = strv_find_prefix(environ, v); + if (!t) + continue; - if (properties) - r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line); - else - r = show_pool_info(bus); - if (r < 0) - return r; - } + if (strv_extend(&arg_setenv, t) < 0) + return log_oom(); + } - for (int i = 1; i < argc; i++) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *path = NULL; + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, &reply, "s", argv[i]); - if (r < 0) - return log_error_errno(r, "Could not get path to image: %s", bus_error_message(&error, r)); + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); - if (properties) - r = show_image_properties(bus, path, &new_line); - else - r = show_image_info(bus, path, &new_line); - } + r = parse_machine_uid(argc >= 2 ? argv[1] : NULL, &machine, &uid); + if (r < 0) + return log_error_errno(r, "Failed to parse machine specification: %m"); - return r; -} + match = strjoina("type='signal'," + "sender='org.freedesktop.machine1'," + "path='/org/freedesktop/machine1',", + "interface='org.freedesktop.machine1.Manager'," + "member='MachineRemoved'," + "arg0='", machine, "'"); -static int kill_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = ASSERT_PTR(userdata); - int r; + r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to request machine removal match: %m"); - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "OpenMachineShell"); + if (r < 0) + return bus_log_create_error(r); - if (!arg_kill_whom) - arg_kill_whom = "all"; + path = argc < 3 || isempty(argv[2]) ? NULL : argv[2]; - for (int i = 1; i < argc; i++) { - r = bus_call_method( - bus, - bus_machine_mgr, - "KillMachine", - &error, - NULL, - "ssi", argv[i], arg_kill_whom, arg_signal); - if (r < 0) - return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r)); - } + r = sd_bus_message_append(m, "sss", machine, uid, path); + if (r < 0) + return bus_log_create_error(r); - return 0; + r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, arg_setenv); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to get shell PTY: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "hs", &master, NULL); + if (r < 0) + return bus_log_parse_error(r); + + return process_forward(event, slot, master, /* flags= */ 0, machine); } -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_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata); + +VERB(verb_enable_machine, "enable", "NAME…", 2, VERB_ANY, 0, + "Enable automatic container start at boot"); +VERB(verb_enable_machine, "disable", "NAME…", 2, VERB_ANY, 0, + "Disable automatic container start at boot"); +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; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + bool enable; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + enable = streq(argv[0], "enable"); + method = enable ? "EnableUnitFiles" : "DisableUnitFiles"; + + r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + if (enable) { + r = sd_bus_message_append(m, "s", "machines.target"); + if (r < 0) + return bus_log_create_error(r); + } + + for (int i = 1; i < argc; i++) { + _cleanup_free_ char *unit = NULL; + + r = make_service_name(argv[i], &unit); + if (r < 0) + return r; + + r = image_exists(bus, argv[i]); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), + "Machine image '%s' does not exist.", + argv[i]); + + r = sd_bus_message_append(m, "s", unit); + if (r < 0) + return bus_log_create_error(r); + } - arg_kill_whom = "leader"; - arg_signal = SIGINT; /* sysvinit + systemd */ + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + if (enable) + r = sd_bus_message_append(m, "bb", false, false); + else + r = sd_bus_message_append(m, "b", false); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to enable or disable unit: %s", bus_error_message(&error, r)); + + if (enable) { + r = sd_bus_message_read(reply, "b", NULL); + if (r < 0) + return bus_log_parse_error(r); + } + + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet); + if (r < 0) + return r; + + r = bus_service_manager_reload(bus); + if (r < 0) + return r; + + if (arg_now) { + _cleanup_strv_free_ char **new_args = NULL; + + new_args = strv_new(enable ? "start" : "poweroff"); + if (!new_args) + return log_oom(); + + r = strv_extend_strv(&new_args, argv + 1, /* filter_duplicates= */ false); + if (r < 0) + return log_oom(); + + if (enable) + return verb_start_machine(strv_length(new_args), new_args, data, userdata); + + return verb_poweroff_machine(strv_length(new_args), new_args, data, userdata); + } - return kill_machine(argc, argv, userdata); + return 0; } -static int poweroff_machine(int argc, char *argv[], void *userdata) { - arg_kill_whom = "leader"; - arg_signal = SIGRTMIN+4; /* only systemd */ +/* 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 */ - return kill_machine(argc, argv, userdata); + char *a = strdup(sd_json_variant_string(addr)); + if (!a) + return log_oom(); + + *ret = a; + 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_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"); + + 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; +} + +VERB(verb_poweroff_machine, "poweroff", "NAME…", 2, VERB_ANY, 0, + "Power off one or more machines"); +VERB(verb_poweroff_machine, "stop", "NAME…", 2, VERB_ANY, 0, + /* help= */ NULL); /* Convenience alias */ +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); + + 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; +} + +VERB(verb_reboot_machine, "reboot", "NAME…", 2, VERB_ANY, 0, + "Reboot one or more machines"); +VERB(verb_reboot_machine, "restart", "NAME…", 2, VERB_ANY, 0, + /* help= */ NULL); /* Convenience alias */ +static int verb_reboot_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++) { + 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 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; +} + +VERB(verb_pause, "pause", "NAME…", 2, VERB_ANY, 0, + "Pause one or more machines"); +static int verb_pause(int argc, char *argv[], uintptr_t _data, void *userdata) { + return verb_vm_control(argc, argv, "io.systemd.MachineInstance.Pause"); +} + +VERB(verb_resume, "resume", "NAME…", 2, VERB_ANY, 0, + "Resume one or more paused machines"); +static int verb_resume(int argc, char *argv[], uintptr_t _data, void *userdata) { + return verb_vm_control(argc, argv, "io.systemd.MachineInstance.Resume"); +} + +VERB(verb_terminate_machine, "terminate", "NAME…", 2, VERB_ANY, 0, + "Terminate one or more machines"); +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,14 +1369,45 @@ static int terminate_machine(int argc, char *argv[], void *userdata) { return 0; } -static const char *select_copy_method(bool copy_from, bool force) { +VERB(verb_kill_machine, "kill", "NAME…", 2, VERB_ANY, 0, + "Send signal to processes of a machine"); +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; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + if (!arg_kill_whom) + arg_kill_whom = "all"; + + for (int i = 1; i < argc; i++) { + r = bus_call_method( + bus, + bus_machine_mgr, + "KillMachine", + &error, + NULL, + "ssi", argv[i], arg_kill_whom, arg_signal); + if (r < 0) + return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static const char* select_copy_method(bool copy_from, bool force) { if (force) return copy_from ? "CopyFromMachineWithFlags" : "CopyToMachineWithFlags"; else return copy_from ? "CopyFromMachine" : "CopyToMachine"; } -static int copy_files(int argc, char *argv[], void *userdata) { +VERB(verb_copy_files, "copy-to", "NAME PATH [PATH]", 3, 4, 0, + "Copy files from the host to a container"); +VERB(verb_copy_files, "copy-from", "NAME PATH [PATH]", 3, 4, 0, + "Copy files from a container to the host"); +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 +1462,9 @@ static int copy_files(int argc, char *argv[], void *userdata) { return 0; } -static int bind_mount(int argc, char *argv[], void *userdata) { +VERB(verb_bind_mount, "bind", "NAME PATH [PATH]", 3, 4, 0, + "Bind mount a path from the host into a container"); +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; @@ -1228,251 +1489,364 @@ static int bind_mount(int argc, char *argv[], void *userdata) { return 0; } -static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - PTYForward *forward = ASSERT_PTR(userdata); - int r; +VERB_GROUP("Image Commands"); - assert(m); +VERB(verb_list_images, "list-images", /* argspec= */ NULL, VERB_ANY, 1, 0, + "Show available container and VM 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 *table = NULL; + sd_bus *bus = ASSERT_PTR(userdata); + int r; - /* Tell the forwarder to exit on the next vhangup(), so that we still flush out what might be queued - * and exit then. */ + pager_open(arg_pager_flags); - r = pty_forward_honor_vhangup(forward); - if (r < 0) { - /* On error, quit immediately. */ - log_error_errno(r, "Failed to make PTY forwarder honor vhangup(): %m"); - (void) sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE); - } + r = bus_call_method(bus, bus_machine_mgr, "ListImages", &error, &reply, NULL); + if (r < 0) + return log_error_errno(r, "Could not get images: %s", bus_error_message(&error, r)); - return 0; -} + table = table_new("name", "type", "ro", "usage", "created", "modified"); + if (!table) + return log_oom(); -static int process_forward(sd_event *event, sd_bus_slot *machine_removed_slot, int master, PTYForwardFlags flags, const char *name) { - int r; + if (arg_full) + table_set_width(table, 0); - assert(event); - assert(machine_removed_slot); - assert(master >= 0); - assert(name); + (void) table_set_align_percent(table, TABLE_HEADER_CELL(3), 100); - if (!arg_quiet) { - if (streq(name, ".host")) - log_info("Connected to the local host. Press ^] three times within 1s to exit session."); - else - log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name); - } + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)"); + if (r < 0) + return bus_log_parse_error(r); - _cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL; - if (!terminal_is_dumb()) { - r = osc_context_open_container(name, /* ret_seq= */ NULL, &osc_context_id); + for (;;) { + uint64_t crtime, mtime, size; + const char *name, *type; + int ro_int; + + r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &ro_int, &crtime, &mtime, &size, NULL); if (r < 0) - return r; + return bus_log_parse_error(r); + if (r == 0) + break; + + if (name[0] == '.' && !arg_all) + continue; + + r = table_add_many(table, + TABLE_STRING, name, + TABLE_STRING, type, + TABLE_BOOLEAN, ro_int, + TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL, + TABLE_SIZE, size, + TABLE_TIMESTAMP, crtime, + TABLE_TIMESTAMP, mtime); + if (r < 0) + return table_log_add_error(r); } - r = sd_event_set_signal_exit(event, true); + r = sd_bus_message_exit_container(reply); if (r < 0) - return log_error_errno(r, "Failed to enable SIGINT/SITERM handling: %m"); + return bus_log_parse_error(r); - _cleanup_(pty_forward_freep) PTYForward *forward = NULL; - r = pty_forward_new(event, master, flags, &forward); + return show_table(table, "images"); +} + +static int print_image_hostname(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *hn; + int r; + + r = bus_call_method(bus, bus_machine_mgr, "GetImageHostname", NULL, &reply, "s", name); if (r < 0) - return log_error_errno(r, "Failed to create PTY forwarder: %m"); + return r; - /* No userdata should not set previously. */ - assert_se(!sd_bus_slot_set_userdata(machine_removed_slot, forward)); + r = sd_bus_message_read(reply, "s", &hn); + if (r < 0) + return r; - r = sd_event_loop(event); + if (!isempty(hn)) + printf("\tHostname: %s\n", hn); + + return 0; +} + +static int print_image_machine_id(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + sd_id128_t id; + int r; + + r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineID", NULL, &reply, "s", name); if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); + return r; - bool machine_died = - FLAGS_SET(flags, PTY_FORWARD_IGNORE_VHANGUP) && - !pty_forward_vhangup_honored(forward); + r = bus_message_read_id128(reply, &id); + if (r < 0) + return r; - if (!arg_quiet) { - if (machine_died) - log_info("Machine %s terminated.", name); - else if (streq(name, ".host")) - log_info("Connection to the local host terminated."); - else - log_info("Connection to machine %s terminated.", name); - } + if (!sd_id128_is_null(id)) + printf(" Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id)); return 0; } -static int parse_machine_uid(const char *spec, const char **machine, char **uid) { - /* - * Whatever is specified in the spec takes priority over global arguments. - */ - char *_uid = NULL; - const char *_machine = NULL; +static int print_image_machine_info(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; - if (spec) { - const char *at; + r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineInfo", NULL, &reply, "s", name); + if (r < 0) + return r; - at = strchr(spec, '@'); - if (at) { - if (at == spec) - /* Do the same as ssh and refuse "@host". */ - return -EINVAL; + r = sd_bus_message_enter_container(reply, 'a', "{ss}"); + if (r < 0) + return r; - _machine = at + 1; - _uid = strndup(spec, at - spec); - if (!_uid) - return -ENOMEM; - } else - _machine = spec; - }; + for (;;) { + const char *p, *q; - if (arg_uid && !_uid) { - _uid = strdup(arg_uid); - if (!_uid) - return -ENOMEM; + r = sd_bus_message_read(reply, "{ss}", &p, &q); + if (r < 0) + return r; + if (r == 0) + break; + + if (streq(p, "DEPLOYMENT")) + printf(" Deployment: %s\n", q); } - *uid = _uid; - *machine = isempty(_machine) ? ".host" : _machine; + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + return 0; } -static int login_machine(int argc, char *argv[], 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; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - int master = -1, r; - sd_bus *bus = ASSERT_PTR(userdata); - const char *match, *machine; +typedef struct ImageStatusInfo { + const char *name; + const char *path; + const char *type; + bool read_only; + usec_t crtime; + usec_t mtime; + uint64_t usage; + uint64_t limit; + uint64_t usage_exclusive; + uint64_t limit_exclusive; +} ImageStatusInfo; - if (!strv_isempty(arg_setenv) || arg_uid) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--setenv= and --uid= are not supported for 'login'. Use 'shell' instead."); +static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { + assert(bus); + assert(i); - if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Login only supported on local machines."); + if (i->name) { + fputs(i->name, stdout); + putchar('\n'); + } - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + if (i->type) + printf("\t Type: %s\n", i->type); - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); + if (i->path) + printf("\t Path: %s\n", i->path); - 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) print_image_hostname(bus, i->name); + (void) print_image_machine_id(bus, i->name); + (void) print_image_machine_info(bus, i->name); - machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1]; + print_os_release(bus, "GetImageOSRelease", i->name, "\t OS: "); - match = strjoina("type='signal'," - "sender='org.freedesktop.machine1'," - "path='/org/freedesktop/machine1',", - "interface='org.freedesktop.machine1.Manager'," - "member='MachineRemoved'," - "arg0='", machine, "'"); + printf("\t RO: %s%s%s\n", + i->read_only ? ansi_highlight_red() : "", + i->read_only ? "read-only" : "writable", + i->read_only ? ansi_normal() : ""); - r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to request machine removal match: %m"); + if (timestamp_is_set(i->crtime)) + printf("\t Created: %s; %s\n", + FORMAT_TIMESTAMP(i->crtime), FORMAT_TIMESTAMP_RELATIVE(i->crtime)); - r = bus_call_method(bus, bus_machine_mgr, "OpenMachineLogin", &error, &reply, "s", machine); - if (r < 0) - return log_error_errno(r, "Failed to get login PTY: %s", bus_error_message(&error, r)); + if (timestamp_is_set(i->mtime)) + printf("\tModified: %s; %s\n", + FORMAT_TIMESTAMP(i->mtime), FORMAT_TIMESTAMP_RELATIVE(i->mtime)); - r = sd_bus_message_read(reply, "hs", &master, NULL); - if (r < 0) - return bus_log_parse_error(r); + if (i->usage != UINT64_MAX) { + if (i->usage_exclusive != i->usage && i->usage_exclusive != UINT64_MAX) + printf("\t Usage: %s (exclusive: %s)\n", + FORMAT_BYTES(i->usage), FORMAT_BYTES(i->usage_exclusive)); + else + printf("\t Usage: %s\n", FORMAT_BYTES(i->usage)); + } - return process_forward(event, slot, master, PTY_FORWARD_IGNORE_VHANGUP, machine); + if (i->limit != UINT64_MAX) { + if (i->limit_exclusive != i->limit && i->limit_exclusive != UINT64_MAX) + printf("\t Limit: %s (exclusive: %s)\n", + FORMAT_BYTES(i->limit), FORMAT_BYTES(i->limit_exclusive)); + else + printf("\t Limit: %s\n", FORMAT_BYTES(i->limit)); + } } -static int shell_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; +static int show_image_info(sd_bus *bus, const char *path, bool *new_line) { + static const struct bus_properties_map map[] = { + { "Name", "s", NULL, offsetof(ImageStatusInfo, name) }, + { "Path", "s", NULL, offsetof(ImageStatusInfo, path) }, + { "Type", "s", NULL, offsetof(ImageStatusInfo, type) }, + { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) }, + { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) }, + { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) }, + { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) }, + { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) }, + { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) }, + { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) }, + {} + }; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - int master = -1, r; - sd_bus *bus = ASSERT_PTR(userdata); - const char *match, *machine, *path; - _cleanup_free_ char *uid = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + ImageStatusInfo info = {}; + int r; - if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Shell only supported on local machines."); + assert(bus); + assert(path); + assert(new_line); - if (terminal_is_dumb()) { - /* Set TERM=dumb if we are running on a dumb terminal or with a pipe. - * Otherwise, we will get unwanted OSC sequences. */ - if (!strv_find_prefix(arg_setenv, "TERM=")) - if (strv_extend(&arg_setenv, "TERM=dumb") < 0) - return log_oom(); - } else - /* Pass $TERM & Co. to shell session, if not explicitly specified. */ - FOREACH_STRING(v, "TERM=", "COLORTERM=", "NO_COLOR=") { - if (strv_find_prefix(arg_setenv, v)) - continue; + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + path, + map, + BUS_MAP_BOOLEAN_AS_BOOL, + &error, + &m, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r)); - const char *t = strv_find_prefix(environ, v); - if (!t) - continue; + if (*new_line) + printf("\n"); + *new_line = true; - if (strv_extend(&arg_setenv, t) < 0) - return log_oom(); - } + print_image_status_info(bus, &info); - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + return r; +} - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); +typedef struct PoolStatusInfo { + const char *path; + uint64_t usage; + uint64_t limit; +} PoolStatusInfo; - r = sd_bus_attach_event(bus, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); +static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) { + if (i->path) + printf("\t Path: %s\n", i->path); - r = parse_machine_uid(argc >= 2 ? argv[1] : NULL, &machine, &uid); - if (r < 0) - return log_error_errno(r, "Failed to parse machine specification: %m"); + if (i->usage != UINT64_MAX) + printf("\t Usage: %s\n", FORMAT_BYTES(i->usage)); - match = strjoina("type='signal'," - "sender='org.freedesktop.machine1'," - "path='/org/freedesktop/machine1',", - "interface='org.freedesktop.machine1.Manager'," - "member='MachineRemoved'," - "arg0='", machine, "'"); + if (i->limit != UINT64_MAX) + printf("\t Limit: %s\n", FORMAT_BYTES(i->limit)); +} - r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL); +static int show_pool_info(sd_bus *bus) { + static const struct bus_properties_map map[] = { + { "PoolPath", "s", NULL, offsetof(PoolStatusInfo, path) }, + { "PoolUsage", "t", NULL, offsetof(PoolStatusInfo, usage) }, + { "PoolLimit", "t", NULL, offsetof(PoolStatusInfo, limit) }, + {} + }; + + PoolStatusInfo info = { + .usage = UINT64_MAX, + .limit = UINT64_MAX, + }; + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(bus); + + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + map, + 0, + &error, + &m, + &info); if (r < 0) - return log_error_errno(r, "Failed to request machine removal match: %m"); + return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r)); - r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "OpenMachineShell"); + print_pool_status_info(bus, &info); + + return 0; +} + +static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) { + int r; + + assert(bus); + assert(path); + assert(new_line); + + if (*new_line) + printf("\n"); + + *new_line = true; + + r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, NULL, arg_property, arg_print_flags, NULL); if (r < 0) - return bus_log_create_error(r); + log_error_errno(r, "Could not get properties: %m"); - path = argc < 3 || isempty(argv[2]) ? NULL : argv[2]; + return r; +} + +VERB(verb_show_image, "image-status", "[NAME…]", VERB_ANY, VERB_ANY, 0, + "Show image details"); +VERB(verb_show_image, "show-image", "[NAME…]", VERB_ANY, VERB_ANY, 0, + "Show properties of image"); +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); + int r = 0; + + properties = !strstr(argv[0], "status"); + + pager_open(arg_pager_flags); + + if (argc <= 1) { + + /* If no argument is specified, inspect the manager + * itself */ - r = sd_bus_message_append(m, "sss", machine, uid, path); - if (r < 0) - return bus_log_create_error(r); + if (properties) + r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line); + else + r = show_pool_info(bus); + if (r < 0) + return r; + } - r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2); - if (r < 0) - return bus_log_create_error(r); + for (int i = 1; i < argc; i++) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *path = NULL; - r = sd_bus_message_append_strv(m, arg_setenv); - if (r < 0) - return bus_log_create_error(r); + r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, &reply, "s", argv[i]); + if (r < 0) + return log_error_errno(r, "Could not get path to image: %s", bus_error_message(&error, r)); - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "Failed to get shell PTY: %s", bus_error_message(&error, r)); + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); - r = sd_bus_message_read(reply, "hs", &master, NULL); - if (r < 0) - return bus_log_parse_error(r); + if (properties) + r = show_image_properties(bus, path, &new_line); + else + r = show_image_info(bus, path, &new_line); + } - return process_forward(event, slot, master, /* flags= */ 0, machine); + return r; } static int normalize_nspawn_filename(const char *name, char **ret_file) { @@ -1515,7 +1889,9 @@ static int get_settings_path(const char *name, char **ret_path) { return -ENOENT; } -static int edit_settings(int argc, char *argv[], void *userdata) { +VERB(verb_edit_settings, "edit", "NAME|FILE…", 2, VERB_ANY, 0, + "Edit settings of one or more VMs/containers"); +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 +1961,9 @@ 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) { +VERB(verb_cat_settings, "cat", "NAME|FILE…", 2, VERB_ANY, 0, + "Show settings of one or more VMs/containers"); +static int verb_cat_settings(int argc, char *argv[], uintptr_t _data, void *userdata) { int r = 0; if (arg_transport != BUS_TRANSPORT_LOCAL) @@ -1636,288 +2014,110 @@ static int cat_settings(int argc, char *argv[], void *userdata) { return r; } -static int remove_image(int argc, char *argv[], 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++) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "RemoveImage"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", argv[i]); - if (r < 0) - return bus_log_create_error(r); - - /* This is a slow operation, hence turn off any method call timeouts */ - r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); - if (r < 0) - return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r)); - } - - return 0; -} - -static int rename_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - r = bus_call_method( - bus, - bus_machine_mgr, - "RenameImage", - &error, - NULL, - "ss", argv[1], argv[2]); - if (r < 0) - return log_error_errno(r, "Could not rename image: %s", bus_error_message(&error, r)); - - return 0; -} - -static int clone_image(int argc, char *argv[], void *userdata) { +VERB(verb_clone_image, "clone", "NAME NAME", 3, 3, 0, + "Clone an image"); +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); int r; - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "CloneImage"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "ssb", argv[1], argv[2], arg_read_only); - if (r < 0) - return bus_log_create_error(r); - - /* This is a slow operation, hence turn off any method call timeouts */ - r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); - if (r < 0) - return log_error_errno(r, "Could not clone image: %s", bus_error_message(&error, r)); - - return 0; -} - -static int read_only_image(int argc, char *argv[], 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; - - if (argc > 2) { - b = parse_boolean(argv[2]); - if (b < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse boolean argument: %s", - argv[2]); - } - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - r = bus_call_method(bus, bus_machine_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b); - if (r < 0) - return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r)); - - return 0; -} - -static int image_exists(sd_bus *bus, const char *name) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(name); - - r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, NULL, "s", name); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_IMAGE)) - return 0; - - return log_error_errno(r, "Failed to check whether image %s exists: %s", name, bus_error_message(&error, r)); - } - - return 1; -} - -static int make_service_name(const char *name, char **ret) { - int r; - - assert(name); - assert(ret); - assert(arg_runner >= 0 && arg_runner < _RUNNER_MAX); - - if (!hostname_is_valid(name, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid machine name %s.", name); - - r = unit_name_build(machine_runner_unit_prefix_table[arg_runner], name, ".service", ret); - if (r < 0) - return log_error_errno(r, "Failed to build unit name: %m"); - - return 0; -} - -static int start_machine(int argc, char *argv[], 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); - int r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - ask_password_agent_open_if_enabled(arg_transport, arg_ask_password); - - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_error_errno(r, "Could not watch jobs: %m"); - - for (int i = 1; i < argc; i++) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *unit = NULL; - const char *object; - - r = make_service_name(argv[i], &unit); - if (r < 0) - return r; - - r = image_exists(bus, argv[i]); - if (r < 0) - return r; - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(ENXIO), - "Machine image '%s' does not exist.", - argv[i]); - - r = bus_call_method( - bus, - bus_systemd_mgr, - "StartUnit", - &error, - &reply, - "ss", unit, "fail"); - if (r < 0) - return log_error_errno(r, "Failed to start unit: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "o", &object); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_add(w, object); - if (r < 0) - return log_oom(); - } - - r = bus_wait_for_jobs(w, arg_quiet, NULL); - if (r < 0) - return r; - - return 0; -} - -static int enable_machine(int argc, char *argv[], 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; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - bool enable; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - enable = streq(argv[0], "enable"); - method = enable ? "EnableUnitFiles" : "DisableUnitFiles"; - - r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return bus_log_create_error(r); - - if (enable) { - r = sd_bus_message_append(m, "s", "machines.target"); - if (r < 0) - return bus_log_create_error(r); - } - - for (int i = 1; i < argc; i++) { - _cleanup_free_ char *unit = NULL; - - r = make_service_name(argv[i], &unit); - if (r < 0) - return r; - - r = image_exists(bus, argv[i]); - if (r < 0) - return r; - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(ENXIO), - "Machine image '%s' does not exist.", - argv[i]); - - r = sd_bus_message_append(m, "s", unit); - if (r < 0) - return bus_log_create_error(r); - } + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - r = sd_bus_message_close_container(m); + r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "CloneImage"); if (r < 0) return bus_log_create_error(r); - if (enable) - r = sd_bus_message_append(m, "bb", false, false); - else - r = sd_bus_message_append(m, "b", false); + r = sd_bus_message_append(m, "ssb", argv[1], argv[2], arg_read_only); if (r < 0) return bus_log_create_error(r); - r = sd_bus_call(bus, m, 0, &error, &reply); + /* This is a slow operation, hence turn off any method call timeouts */ + r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); if (r < 0) - return log_error_errno(r, "Failed to enable or disable unit: %s", bus_error_message(&error, r)); + return log_error_errno(r, "Could not clone image: %s", bus_error_message(&error, r)); - if (enable) { - r = sd_bus_message_read(reply, "b", NULL); - if (r < 0) - return bus_log_parse_error(r); - } + return 0; +} - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet); +VERB(verb_rename_image, "rename", "NAME NAME", 3, 3, 0, + "Rename an image"); +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; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + r = bus_call_method( + bus, + bus_machine_mgr, + "RenameImage", + &error, + NULL, + "ss", argv[1], argv[2]); if (r < 0) - return r; + return log_error_errno(r, "Could not rename image: %s", bus_error_message(&error, r)); - r = bus_service_manager_reload(bus); + return 0; +} + +VERB(verb_read_only_image, "read-only", "NAME [BOOL]", 2, 3, 0, + "Mark or unmark image read-only"); +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; + + if (argc > 2) { + b = parse_boolean(argv[2]); + if (b < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse boolean argument: %s", + argv[2]); + } + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + r = bus_call_method(bus, bus_machine_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b); if (r < 0) - return r; + return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r)); - if (arg_now) { - _cleanup_strv_free_ char **new_args = NULL; + return 0; +} - new_args = strv_new(enable ? "start" : "poweroff"); - if (!new_args) - return log_oom(); +VERB(verb_remove_image, "remove", "NAME…", 2, VERB_ANY, 0, + "Remove an image"); +static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) { + sd_bus *bus = ASSERT_PTR(userdata); + int r; - r = strv_extend_strv(&new_args, argv + 1, /* filter_duplicates= */ false); + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + for (int i = 1; i < argc; i++) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "RemoveImage"); if (r < 0) - return log_oom(); + return bus_log_create_error(r); - if (enable) - return start_machine(strv_length(new_args), new_args, userdata); + r = sd_bus_message_append(m, "s", argv[i]); + if (r < 0) + return bus_log_create_error(r); - return poweroff_machine(strv_length(new_args), new_args, userdata); + /* This is a slow operation, hence turn off any method call timeouts */ + r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); + if (r < 0) + return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r)); } return 0; } -static int set_limit(int argc, char *argv[], void *userdata) { +VERB(verb_set_limit, "set-limit", "[NAME] BYTES", 2, 3, 0, + "Set image or pool size limit (disk quota)"); +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 +2147,9 @@ static int set_limit(int argc, char *argv[], void *userdata) { return 0; } -static int clean_images(int argc, char *argv[], void *userdata) { +VERB(verb_clean_images, "clean", /* argspec= */ NULL, VERB_ANY, 1, 0, + "Remove hidden (or all) images"); +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; @@ -2001,11 +2203,130 @@ static int clean_images(int argc, char *argv[], void *userdata) { return 0; } -static int chainload_importctl(int argc, char *argv[]) { +VERB(verb_bind_volume, "bind-volume", "MACHINE PROVIDER:VOLUME[:…]", 3, 3, 0, + "Attach a volume to a running machine"); +static int verb_bind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "bind-volume is only supported on the local transport."); + + _cleanup_(bind_volume_freep) BindVolume *bv = NULL; + r = bind_volume_parse(argv[2], &bv); + if (r < 0) + return log_error_errno(r, "Failed to parse bind-volume argument '%s': %m", argv[2]); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + /* Locate and connect to the target machine before acquiring storage, so a missing + * machine doesn't trigger 'create=new' side effects on the StorageProvider. */ + _cleanup_free_ char *address = NULL; + r = machine_get_control_address(argv[1], &address); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]); + 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 %s: %m", address); + + r = sd_varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable fd passing on varlink connection: %m"); + + _cleanup_(storage_acquire_reply_done) StorageAcquireReply reply = STORAGE_ACQUIRE_REPLY_INIT; + _cleanup_free_ char *acquire_error_id = NULL; + r = storage_acquire_volume(arg_runtime_scope, bv, arg_ask_password, &acquire_error_id, &reply); + if (r < 0) { + if (acquire_error_id) + return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %s", + bv->provider, bv->volume, acquire_error_id); + return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %m", + bv->provider, bv->volume); + } + + int fd_index = sd_varlink_push_fd(vl, reply.fd); + if (fd_index < 0) + return log_error_errno(fd_index, "Failed to push storage fd onto varlink connection: %m"); + TAKE_FD(reply.fd); + + _cleanup_free_ char *name = strjoin(bv->provider, ":", bv->volume); + if (!name) + return log_oom(); + + sd_json_variant *vl_reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.MachineInstance.AddStorage", + &vl_reply, &error_id, + SD_JSON_BUILD_PAIR_INTEGER("fileDescriptorIndex", fd_index), + SD_JSON_BUILD_PAIR_STRING("name", name), + JSON_BUILD_PAIR_STRING_NON_EMPTY("config", bv->config)); + if (r < 0) + return log_error_errno(r, "Failed to call io.systemd.MachineInstance.AddStorage: %m"); + if (error_id) + return log_error_errno(sd_varlink_error_to_errno(error_id, vl_reply), + "AddStorage failed for '%s': %s", name, error_id); + + return 0; +} + +VERB(verb_unbind_volume, "unbind-volume", "MACHINE PROVIDER:VOLUME", 3, 3, 0, + "Detach a volume from a running machine"); +static int verb_unbind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "unbind-volume is only supported on the local transport."); + + r = machine_storage_name_split(argv[2], /* ret_provider= */ NULL, /* ret_volume= */ NULL); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid unbind-volume name '%s', expected ':'.", argv[2]); + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + _cleanup_free_ char *address = NULL; + r = machine_get_control_address(argv[1], &address); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]); + 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 %s: %m", address); + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.MachineInstance.RemoveStorage", + &reply, &error_id, + SD_JSON_BUILD_PAIR_STRING("name", argv[2])); + if (r < 0) + return log_error_errno(r, "Failed to call io.systemd.MachineInstance.RemoveStorage: %m"); + if (error_id) + return log_error_errno(sd_varlink_error_to_errno(error_id, reply), + "RemoveStorage failed for '%s': %s", argv[2], error_id); + + return 0; +} + +static int chainload_importctl(char **args) { int r; if (!arg_quiet) - log_notice("The 'machinectl %1$s' command has been replaced by 'importctl -m %1$s'. Redirecting invocation.", argv[optind]); + log_notice("The 'machinectl %1$s' command has been replaced by 'importctl -m %1$s'. " + "Redirecting invocation.", args[0]); _cleanup_strv_free_ char **c = strv_new("importctl", "--class=machine"); @@ -2036,7 +2357,7 @@ static int chainload_importctl(int argc, char *argv[]) { if (strv_extend_many(&c, "--format", arg_format) < 0) return log_oom(); - if (strv_extend_strv(&c, argv + optind, /* filter_duplicates= */ false) < 0) + if (strv_extend_strv(&c, args, /* filter_duplicates= */ false) < 0) return log_oom(); if (DEBUG_LOGGING) { @@ -2048,456 +2369,286 @@ 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) { - _cleanup_free_ char *link = NULL; +static int help(void) { + static const char* const vgroups[] = { + "Machine Commands", + "Image Commands", + }; + + Table *vtables[ELEMENTSOF(vgroups)] = {}; + CLEANUP_ELEMENTS(vtables, table_unref_array_clear); + _cleanup_(table_unrefp) Table *options = NULL; int r; - pager_open(arg_pager_flags); + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + r = verbs_get_help_table_group(vgroups[i], &vtables[i]); + if (r < 0) + return r; + } - r = terminal_urlify_man("machinectl", "1", &link); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; + + assert_cc(ELEMENTSOF(vtables) == 2); + (void) table_sync_column_widths(0, options, vtables[0], vtables[1]); - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sSend control commands to or query the virtual machine and container%6$s\n" - "%5$sregistration manager.%6$s\n" - "\n%3$sMachine Commands:%4$s\n" - " list List running VMs and containers\n" - " status NAME... Show VM/container details\n" - " show [NAME...] Show properties of one or more VMs/containers\n" - " start NAME... Start container as a service\n" - " login [NAME] Get a login prompt in a container or on the\n" - " local host\n" - " shell [[USER@]NAME [COMMAND...]]\n" - " Invoke a shell (or other command) in a container\n" - " 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" - " 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" - "\n%3$sImage Commands:%4$s\n" - " list-images Show available container and VM images\n" - " image-status [NAME...] Show image details\n" - " show-image [NAME...] Show properties of image\n" - " edit NAME|FILE... Edit settings of one or more VMs/containers\n" - " cat NAME|FILE... Show settings of one or more VMs/containers\n" - " clone NAME NAME Clone an image\n" - " rename NAME NAME Rename an image\n" - " read-only NAME [BOOL] Mark or unmark image read-only\n" - " remove NAME... Remove an image\n" - " set-limit [NAME] BYTES Set image or pool size limit (disk quota)\n" - " clean Remove hidden (or all) 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" - " -p --property=NAME Show only properties by this name\n" - " --value When showing properties, only print the value\n" - " -P NAME Equivalent to --value --property=NAME\n" - " -q --quiet Suppress output\n" - " -a --all Show all properties, including empty ones\n" - " -l --full Do not ellipsize output\n" - " --kill-whom=WHOM Whom to send signal to\n" - " -s --signal=SIGNAL Which signal to send\n" - " --uid=USER Specify user ID to invoke shell as\n" - " -E --setenv=VAR[=VALUE] Add an environment variable for shell\n" - " --read-only Create read-only bind mount or clone\n" - " --mkdir Create directory before bind mounting, if missing\n" - " -n --lines=INTEGER Number of journal entries to show\n" - " --max-addresses=INTEGER Number of internet addresses to show at most\n" - " -o --output=STRING Change journal output mode (short, short-precise,\n" - " short-iso, short-iso-precise, short-full,\n" - " short-monotonic, short-unix, short-delta,\n" - " json, json-pretty, json-sse, json-seq, cat,\n" - " verbose, export, with-unit)\n" - " --force Replace target file when copying, if necessary\n" - " --now Start or power off container after enabling or\n" - " disabling it\n" - " --runner=RUNNER Select between nspawn and vmspawn as the runner\n" - " -V Short for --runner=vmspawn\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), + pager_open(arg_pager_flags); + + help_cmdline("[OPTIONS…] COMMAND …"); + + printf("\n%1$s%2$sSend control commands to or query the virtual machine and container%3$s\n" + "%1$s%2$sregistration manager.%3$s\n", ansi_highlight(), + ansi_add_italics(), ansi_normal()); - return 0; -} + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + help_section(vgroups[i]); + r = table_print_or_warn(vtables[i]); + if (r < 0) + return r; + } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_VALUE, - ARG_KILL_WHOM, - ARG_READ_ONLY, - ARG_MKDIR, - ARG_NO_ASK_PASSWORD, - ARG_VERIFY, - ARG_RUNNER, - ARG_NOW, - ARG_FORCE, - ARG_FORMAT, - ARG_UID, - ARG_MAX_ADDRESSES, - ARG_SYSTEM, - ARG_USER, - }; + help_section("Options"); + 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 }, - { "property", required_argument, NULL, 'p' }, - { "value", no_argument, NULL, ARG_VALUE }, - { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, 'l' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "kill-whom", required_argument, NULL, ARG_KILL_WHOM }, - { "signal", required_argument, NULL, 's' }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "mkdir", no_argument, NULL, ARG_MKDIR }, - { "quiet", no_argument, NULL, 'q' }, - { "lines", required_argument, NULL, 'n' }, - { "output", required_argument, NULL, 'o' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "verify", required_argument, NULL, ARG_VERIFY }, - { "runner", required_argument, NULL, ARG_RUNNER }, - { "now", no_argument, NULL, ARG_NOW }, - { "force", no_argument, NULL, ARG_FORCE }, - { "format", required_argument, NULL, ARG_FORMAT }, - { "uid", required_argument, NULL, ARG_UID }, - { "setenv", required_argument, NULL, 'E' }, - { "max-addresses", required_argument, NULL, ARG_MAX_ADDRESSES }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - {} - }; + help_man_page_reference("machinectl", "1"); + + return 0; +} - bool reorder = false; - int c, r, shell = -1; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); + + /* For "shell" we don't want option reordering; options specified after the command should be passed + * to the program to execute, and not processed by us. For other verbs, we consume all options as + * usual. To make this work, start with OPTION_PARSER_RETURN_POSITIONAL_ARGS and switch to either + * OPTION_PARSER_STOP_AT_FIRST_NONOPTION or OPTION_PARSER_NORMAL after we've seen the verb and one + * more argument after that. */ + OptionParser opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + _cleanup_strv_free_ char **args = NULL; + 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; - - for (;;) { - static const char option_string[] = "-hp:P:als:H:M:qn:o:E:V"; - - c = getopt_long(argc, argv, option_string + reorder, options, NULL); - if (c < 0) - break; - + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 1: /* getopt_long() returns 1 if "-" was the first character of the option string, and a - * non-option argument was discovered. */ - - assert(!reorder); - - /* We generally are fine with the fact that getopt_long() reorders the command line, and looks - * for switches after the main verb. However, for "shell" we really don't want that, since we - * want that switches specified after the machine name are passed to the program to execute, - * and not processed by us. To make this possible, we'll first invoke getopt_long() with - * reordering disabled (i.e. with the "-" prefix in the option string), looking for the first - * non-option parameter. If it's the verb "shell" we remember its position and continue - * processing options. In this case, as soon as we hit the next non-option argument we found - * the machine name, and stop further processing. If the first non-option argument is any other - * verb than "shell" we switch to normal reordering mode and continue processing arguments - * normally. */ - - if (shell >= 0) { - /* If we already found the "shell" verb on the command line, and now found the next - * non-option argument, then this is the machine name and we should stop processing - * further arguments. */ - optind--; /* don't process this argument, go one step back */ - goto done; - } - if (streq(optarg, "shell")) - /* Remember the position of the "shell" verb, and continue processing normally. */ - shell = optind - 1; - else { - int saved_optind; - - /* OK, this is some other verb. In this case, turn on reordering again, and continue - * processing normally. */ - reorder = true; - - /* We changed the option string. getopt_long() only looks at it again if we invoke it - * at least once with a reset option index. Hence, let's reset the option index here, - * then invoke getopt_long() again (ignoring what it has to say, after all we most - * likely already processed it), and the bump the option index so that we read the - * intended argument again. */ - saved_optind = optind; - optind = 0; - (void) getopt_long(argc, argv, option_string + reorder, options, NULL); - optind = saved_optind - 1; /* go one step back, process this argument again */ - } - - break; - - case 'h': - return help(0, NULL, NULL); + OPTION_POSITIONAL: + assert(opts.mode == OPTION_PARSER_RETURN_POSITIONAL_ARGS); - case ARG_VERSION: - return version(); + if (!args && !streq(opts.arg, "shell")) + opts.mode = OPTION_PARSER_NORMAL; + else if (args) + opts.mode = OPTION_PARSER_STOP_AT_FIRST_NONOPTION; - case 'p': - case 'P': - r = strv_extend(&arg_property, optarg); + r = strv_extend(&args, opts.arg); if (r < 0) return log_oom(); + break; - /* 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); + OPTION_COMMON_HELP: + return help(); - if (c == 'p') - break; - _fallthrough_; + OPTION_COMMON_VERSION: + return version(); - case ARG_VALUE: - SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); + OPTION_COMMON_HOST: + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = opts.arg; break; - case 'a': - SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); - arg_all = true; + OPTION_COMMON_MACHINE: + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = opts.arg; break; - case 'l': - arg_full = true; + OPTION_LONG("system", NULL, "Connect to system machine manager"): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse lines '%s'", optarg); + OPTION_LONG("user", NULL, "Connect to user machine manager"): + arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case 'o': - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); + OPTION_LONG("value", NULL, "When showing properties, only print the value"): + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); + break; - r = output_mode_from_string(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, opts.arg); if (r < 0) - return log_error_errno(r, "Unknown output '%s'.", optarg); - arg_output = r; + 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 (opts.opt->short_code == 'P') + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); - if (OUTPUT_MODE_IS_JSON(arg_output)) - arg_legend = false; break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION('a', "all", NULL, "Show all properties, including empty ones"): + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); + arg_all = true; break; - case ARG_NO_LEGEND: - arg_legend = false; + OPTION('l', "full", NULL, "Do not ellipsize output"): + arg_full = true; break; - case ARG_KILL_WHOM: - arg_kill_whom = optarg; + OPTION_LONG("kill-whom", "WHOM", "Whom to send signal to"): + arg_kill_whom = opts.arg; break; - case 's': - r = parse_signal_argument(optarg, &arg_signal); + OPTION('s', "signal", "SIGNAL", "Which signal to send"): + r = parse_signal_argument(opts.arg, &arg_signal); if (r <= 0) return r; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + OPTION_LONG("uid", "USER", "Specify user ID to invoke shell as"): + arg_uid = opts.arg; break; - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; + OPTION('E', "setenv", "VAR[=VALUE]", "Add an environment variable for shell"): + r = strv_env_replace_strdup_passthrough(&arg_setenv, opts.arg); + if (r < 0) + return log_error_errno(r, "Cannot assign environment variable %s: %m", opts.arg); break; - case ARG_READ_ONLY: + OPTION_LONG("read-only", NULL, "Create read-only bind mount or clone"): arg_read_only = true; break; - case ARG_MKDIR: + OPTION_LONG("mkdir", NULL, "Create directory before bind mounting, if missing"): arg_mkdir = true; break; - case 'q': - arg_quiet = true; + OPTION('n', "lines", "INTEGER", "Number of journal entries to show"): + if (safe_atou(opts.arg, &arg_lines) < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse lines '%s'", opts.arg); 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_LONG("max-addresses", "INTEGER", + "Number of internet addresses to show at most"): + if (streq(opts.arg, "all")) + arg_max_addresses = UINT_MAX; + else if (safe_atou(opts.arg, &arg_max_addresses) < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid number of addresses: %s", opts.arg); break; - case 'V': - arg_runner = RUNNER_VMSPAWN; - break; + OPTION('o', "output", "STRING", + "Change journal output mode (short, short-precise, short-iso, " + "short-iso-precise, short-full, short-monotonic, short-unix, short-delta, " + "json, json-pretty, json-sse, json-seq, cat, verbose, export, with-unit)"): + if (streq(opts.arg, "help")) + return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); - case ARG_RUNNER: - r = machine_runner_from_string(optarg); + r = output_mode_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --runner= setting: %s", optarg); + return log_error_errno(r, "Unknown output '%s'.", opts.arg); + arg_output = r; - arg_runner = r; + if (OUTPUT_MODE_IS_JSON(arg_output)) + arg_legend = false; break; - case ARG_NOW: - arg_now = true; + OPTION_LONG("force", NULL, "Replace target file when copying, if necessary"): + arg_force = true; break; - case ARG_FORCE: - arg_force = true; + OPTION_LONG("now", NULL, + "Start or power off container after enabling or disabling it"): + arg_now = true; 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("runner", "RUNNER", + "Select between nspawn and vmspawn as the runner"): + r = machine_runner_from_string(opts.arg); + if (r < 0) + return log_error_errno(r, "Failed to parse --runner= setting: %s", opts.arg); - arg_format = optarg; + arg_runner = r; break; - case ARG_UID: - arg_uid = optarg; + OPTION_SHORT('V', NULL, "Short for --runner=vmspawn"): + arg_runner = RUNNER_VMSPAWN; break; - case 'E': - r = strv_env_replace_strdup_passthrough(&arg_setenv, optarg); + /* Hidden options below */ + + OPTION_LONG("verify", "MODE", /* help= */ NULL): + if (streq(opts.arg, "help")) + return DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX); + + r = import_verify_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); + return log_error_errno(r, "Failed to parse --verify= setting: %s", opts.arg); + arg_verify = r; break; - case ARG_MAX_ADDRESSES: - if (streq(optarg, "all")) - arg_max_addresses = UINT_MAX; - else if (safe_atou(optarg, &arg_max_addresses) < 0) + OPTION_LONG("format", "FORMAT", /* help= */ NULL): + if (!STR_IN_SET(opts.arg, "uncompressed", "xz", "gzip", "bzip2", "zstd")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid number of addresses: %s", optarg); + "Unknown format: %s", opts.arg); + + arg_format = opts.arg; break; - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + OPTION_COMMON_NO_LEGEND: + arg_legend = false; break; - case '?': - return -EINVAL; + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; + break; - default: - assert_not_reached(); + OPTION('q', "quiet", NULL, "Suppress output"): + arg_quiet = true; + break; } - } - -done: - if (shell >= 0) { - char *t; - - /* We found the "shell" verb while processing the argument list. Since we turned off reordering of the - * argument list initially let's readjust it now, and move the "shell" verb to the back. */ - optind -= 1; /* place the option index where the "shell" verb will be placed */ - - t = argv[shell]; - for (int i = shell; i < optind; i++) - argv[i] = argv[i+1]; - argv[optind] = t; - } + /* We gathered some positional args in 'args' ourselves. Append the remaining ones. */ + if (strv_extend_strv(&args, option_parser_get_args(&opts), /* filter_duplicates= */ false) < 0) + return log_oom(); + *ret_args = TAKE_PTR(args); return 1; } -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 }, - {} - }; - - return dispatch_verb(argc, argv, verbs, bus); -} - static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_strv_free_ 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; journal_browse_prepare(); - if (STRPTR_IN_SET(argv[optind], - "import-tar", "import-raw", "import-fs", - "export-tar", "export-raw", - "pull-tar", "pull-raw", - "list-transfers", "cancel-transfer")) - return chainload_importctl(argc, argv); + if (args && STR_IN_SET(args[0], + "import-tar", "import-raw", "import-fs", + "export-tar", "export-raw", + "pull-tar", "pull-raw", + "list-transfers", "cancel-transfer")) + return chainload_importctl(args); r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, &bus); if (r < 0) @@ -2505,7 +2656,7 @@ static int run(int argc, char *argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - return machinectl_main(argc, argv, bus); + return dispatch_verb(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 0130fbf77988f..3591bbca2e565 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) @@ -275,12 +279,33 @@ static int machine_add_from_params( /* Ensure an unprivileged user cannot claim any process they don't control as their own machine */ switch (manager->runtime_scope) { - case RUNTIME_SCOPE_SYSTEM: - /* In system mode root may register anything */ - if (uid == 0) + case RUNTIME_SCOPE_SYSTEM: { + const char *details[] = { + "name", name, + "class", machine_class_to_string(c), + NULL + }; + bool sender_is_admin = false; + + r = bus_verify_polkit_async_full( + message, + polkit_action, + details, + /* good_user= */ UID_INVALID, + /* flags= */ 0, + &manager->polkit_registry, + &sender_is_admin, + error); + if (r < 0) + return r; + if (r == 0) + return 0; /* Will call us back */ + + /* In system mode root/admin may register anything */ + if (uid == 0 || sender_is_admin) break; - /* And non-root may only register things if they own the userns */ + /* And non-root/admin may only register things if they own the userns */ r = process_is_owned_by_uid(leader_pidref, uid); if (r < 0) return r; @@ -288,7 +313,8 @@ static int machine_add_from_params( break; /* Nothing else may */ - return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "Only root may register machines for other users"); + return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "Only privileged users may register machines for other users"); + } case RUNTIME_SCOPE_USER: /* In user mode the user owning our instance may register anything. */ @@ -302,25 +328,6 @@ static int machine_add_from_params( assert_not_reached(); } - if (manager->runtime_scope != RUNTIME_SCOPE_USER) { - const char *details[] = { - "name", name, - "class", machine_class_to_string(c), - NULL - }; - - r = bus_verify_polkit_async( - message, - polkit_action, - details, - &manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 0; /* Will call us back */ - } - r = manager_add_machine(manager, name, &m); if (r < 0) return r; @@ -434,7 +441,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 +471,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 +616,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 +1226,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 +1458,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..954e26069214e 100644 --- a/src/machine/machined.c +++ b/src/machine/machined.c @@ -23,17 +23,18 @@ #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" +#include "mkdir.h" #include "operation.h" #include "path-lookup.h" #include "service-util.h" #include "set.h" #include "signal-util.h" -#include "socket-util.h" #include "special.h" #include "string-util.h" +#include "vsock-util.h" static Manager* manager_unref(Manager *m); DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref); 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/meson.build b/src/machine/meson.build index 13756cb8a1ba2..fc16e9f5c5f32 100644 --- a/src/machine/meson.build +++ b/src/machine/meson.build @@ -36,13 +36,11 @@ executables += [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, test_template + { 'sources' : files('test-machine-tables.c'), 'objects' : ['systemd-machined'], - 'dependencies': threads, }, ] 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..70971c11adb74 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,92 @@ 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 opts = { argc, argv }; + int r; - while ((c = getopt_long(argc, argv, "hjc", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + + implementation = sym_EVP_get_digestbyname(opts.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.", opts.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(opts.arg, "list")) + return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); + + if (!streq(opts.arg, "auto")) { + device = strdup(opts.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, opts.arg); if (r < 0) return r; break; - case ARG_PRIVATE_KEY_SOURCE: + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - optarg, + opts.arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) @@ -271,81 +224,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(opts.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, opts.arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - optarg, + opts.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(opts.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(opts.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(opts.opt->data < _UNIFIED_SECTION_MAX); + + r = parse_path_argument(opts.arg, /* suppress_root= */ false, arg_sections + opts.opt->data); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_public_key && arg_certificate) @@ -387,9 +346,159 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); log_debug("Measuring boot phases: %s", j); + + *ret_args = option_parser_get_args(&opts); 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_DEFAULT_NOARG(verb_status, "status", "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 +530,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 +547,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 +630,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 +649,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 +669,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 +679,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 +720,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 +728,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 +745,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 +758,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 +817,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 +930,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 */ @@ -850,7 +959,7 @@ static int build_policy_digest(bool sign) { if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); if (r < 0) - return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + return r; } r = openssl_load_private_key( @@ -878,11 +987,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 +1027,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 +1044,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 +1100,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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + + return dispatch_verb(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..eb58b70c7b06c 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 @@ -12,13 +11,16 @@ #include "build.h" #include "conf-files.h" #include "constants.h" +#include "cpu-set-util.h" #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" @@ -313,13 +315,13 @@ static unsigned determine_num_worker_threads(unsigned n_modules) { if (n_threads == UINT_MAX) { /* By default, use a number of worker threads equal the number of online CPUs, * but clamp it to avoid a probing storm on machines with many CPUs. */ - long ncpus = sysconf(_SC_NPROCESSORS_ONLN); - if (ncpus < 0) { - log_warning_errno(errno, "Failed to get number of online CPUs, ignoring: %m"); - ncpus = 1; - } - - n_threads = CLAMP((unsigned)ncpus, 1U, 16U); + unsigned n_cpus; + r = cpus_online(&n_cpus); + if (r < 0) { + log_warning_errno(r, "Failed to get number of online CPUs, ignoring: %m"); + n_threads = 1; + } else + n_threads = CLAMP(n_cpus, 1U, 16U); } /* There's no reason to spawn more threads than the modules that need to be loaded */ @@ -331,53 +333,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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts); return 1; } @@ -395,7 +390,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 +403,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 +413,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..c35769e56a7f5 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -1,10 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" #include "sd-device.h" +#include "ansi-color.h" #include "argv-util.h" #include "build.h" #include "bus-error.h" @@ -13,6 +12,7 @@ #include "bus-util.h" #include "bus-wait-for-jobs.h" #include "chase.h" +#include "device-private.h" #include "device-util.h" #include "errno-util.h" #include "escape.h" @@ -20,15 +20,16 @@ #include "format-table.h" #include "format-util.h" #include "fstab-util.h" +#include "help-util.h" #include "libmount-util.h" #include "main-func.h" #include "mountpoint-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" #include "polkit-agent.h" -#include "pretty-print.h" #include "process-util.h" #include "runtime-scope.h" #include "stat-util.h" @@ -40,7 +41,7 @@ #include "unit-name.h" #include "user-util.h" -enum { +static enum { ACTION_DEFAULT, ACTION_MOUNT, ACTION_AUTOMOUNT, @@ -108,295 +109,217 @@ static int parse_where(const char *input, char **ret_where) { return 0; } -static int help(void) { - _cleanup_free_ char *link = NULL; +static int help(char *argv[]) { + _cleanup_(table_unrefp) Table *options_common = NULL, *options_mount = NULL; int r; - r = terminal_urlify_man("systemd-mount", "1", &link); + r = option_parser_get_help_table(&options_common); if (r < 0) - return log_oom(); + return r; - printf("systemd-mount [OPTIONS...] WHAT [WHERE]\n" - "systemd-mount [OPTIONS...] --tmpfs [NAME] WHERE\n" - "systemd-mount [OPTIONS...] --list\n" - "%1$s [OPTIONS...] %7$sWHAT|WHERE...\n" - "\n%5$sEstablish a mount or auto-mount point transiently.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-block Do not wait until operation finished\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers\n" - " -l --full Do not ellipsize output\n" - " --no-ask-password Do not prompt for password\n" - " -q --quiet Suppress information messages during runtime\n" - " --json=pretty|short|off Generate JSON output\n" - " --user Run as user unit\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --discover Discover mount device metadata\n" - " -t --type=TYPE File system type\n" - " -o --options=OPTIONS Mount options\n" - " --owner=USER Add uid= and gid= options for USER\n" - " --fsck=no Don't run file system check before mount\n" - " --description=TEXT Description for unit\n" - " -p --property=NAME=VALUE Set mount unit property\n" - " --automount=BOOL Create an automount point\n" - " -A Same as --automount=yes\n" - " --timeout-idle-sec=SEC Specify automount idle timeout\n" - " --automount-property=NAME=VALUE\n" - " Set automount unit property\n" - " --bind-device Bind automount unit to device\n" - " --list List mountable block devices\n" - " -u --umount Unmount mount points\n" - " -G --collect Unload unit after it stopped, even when failed\n" - " -T --tmpfs Create a new tmpfs on the mount point\n" - " --canonicalize=BOOL Controls whether to canonicalize path before\n" - " operation\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal(), - streq(program_invocation_short_name, "systemd-umount") ? "" : "--umount "); + if (invoked_as(argv, "systemd-umount")) { + help_cmdline("[OPTIONS…] WHAT|WHERE…"); + help_abstract("Unmount one or more mount points."); + } else { + help_cmdline("[OPTIONS…] WHAT [WHERE]"); + help_cmdline("[OPTIONS…] --tmpfs [NAME] WHERE"); + help_cmdline("[OPTIONS…] --list"); + help_cmdline("[OPTIONS…] --umount WHAT|WHERE…"); + help_abstract("Establish a mount or auto-mount point."); - return 0; -} + r = option_parser_get_help_table_group("Mount options", &options_mount); + if (r < 0) + return r; -static int parse_argv(int argc, char *argv[]) { + (void) table_sync_column_widths(0, options_common, options_mount); + } - enum { - ARG_VERSION = 0x100, - ARG_NO_BLOCK, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_NO_ASK_PASSWORD, - ARG_USER, - ARG_SYSTEM, - ARG_DISCOVER, - ARG_MOUNT_TYPE, - ARG_MOUNT_OPTIONS, - ARG_OWNER, - ARG_FSCK, - ARG_DESCRIPTION, - ARG_TIMEOUT_IDLE, - ARG_AUTOMOUNT, - ARG_AUTOMOUNT_PROPERTY, - ARG_BIND_DEVICE, - ARG_LIST, - ARG_JSON, - ARG_CANONICALIZE, - }; + help_section("Options"); + r = table_print_or_warn(options_common); + if (r < 0) + return r; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "full", no_argument, NULL, 'l' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "quiet", no_argument, NULL, 'q' }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "discover", no_argument, NULL, ARG_DISCOVER }, - { "type", required_argument, NULL, 't' }, - { "options", required_argument, NULL, 'o' }, - { "owner", required_argument, NULL, ARG_OWNER }, - { "fsck", required_argument, NULL, ARG_FSCK }, - { "description", required_argument, NULL, ARG_DESCRIPTION }, - { "property", required_argument, NULL, 'p' }, - { "automount", required_argument, NULL, ARG_AUTOMOUNT }, - { "timeout-idle-sec", required_argument, NULL, ARG_TIMEOUT_IDLE }, - { "automount-property", required_argument, NULL, ARG_AUTOMOUNT_PROPERTY }, - { "bind-device", no_argument, NULL, ARG_BIND_DEVICE }, - { "list", no_argument, NULL, ARG_LIST }, - { "umount", no_argument, NULL, 'u' }, - { "unmount", no_argument, NULL, 'u' }, /* Compat spelling */ - { "collect", no_argument, NULL, 'G' }, - { "tmpfs", no_argument, NULL, 'T' }, - { "json", required_argument, NULL, ARG_JSON }, - { "canonicalize", required_argument, NULL, ARG_CANONICALIZE }, - {}, - }; + if (options_mount) { + r = table_print_or_warn(options_mount); + if (r < 0) + return r; + } - int r, c; + help_man_page_reference("systemd-mount", "1"); + return 0; +} + +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); if (invoked_as(argv, "systemd-umount")) arg_action = ACTION_UMOUNT; - while ((c = getopt_long(argc, argv, "hqH:M:t:o:p:AuGlT", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - return help(); + OPTION_COMMON_HELP: + return help(argv); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_BLOCK: - arg_no_block = true; + OPTION_LONG("user", NULL, "Run as user unit"): + arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION_LONG("system", NULL, /* help= */ NULL): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_NO_LEGEND: - arg_legend = false; + OPTION_COMMON_HOST: + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = opts.arg; break; - case 'l': - arg_full = true; + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); + if (r < 0) + return r; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION_LONG("canonicalize", "BOOL", + "Whether to canonicalize path before operation"): + r = parse_boolean_argument("--canonicalize=", opts.arg, &arg_canonicalize); + if (r < 0) + return r; break; - case 'q': - arg_quiet = true; + OPTION_LONG("no-block", NULL, "Do not wait until operation finished"): + arg_no_block = true; break; - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + OPTION_COMMON_NO_LEGEND: + arg_legend = false; break; - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + OPTION('l', "full", NULL, "Do not ellipsize output"): + arg_full = true; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); - if (r < 0) + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + OPTION('q', "quiet", NULL, "Suppress informational messages during runtime"): + arg_quiet = true; + break; + + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); + if (r <= 0) return r; break; - case ARG_DISCOVER: + OPTION_GROUP("Mount options"): {} + + OPTION_LONG("discover", NULL, "Discover mount device metadata"): arg_discover = true; break; - case 't': - r = free_and_strdup_warn(&arg_mount_type, optarg); + OPTION('t', "type", "TYPE", "File system type"): + r = free_and_strdup_warn(&arg_mount_type, opts.arg); if (r < 0) return r; break; - case 'o': - r = free_and_strdup_warn(&arg_mount_options, optarg); + OPTION('o', "options", "OPTIONS", "Mount options"): + r = free_and_strdup_warn(&arg_mount_options, opts.arg); if (r < 0) return r; break; - case ARG_OWNER: { - const char *user = optarg; - - r = get_user_creds(&user, &arg_uid, &arg_gid, NULL, NULL, 0); + OPTION_LONG("owner", "USER", "Add uid= and gid= options for USER"): + r = get_user_creds(opts.arg, /* flags= */ 0, NULL, &arg_uid, &arg_gid, NULL, NULL); if (r < 0) return log_error_errno(r, r == -EBADMSG ? "UID or GID of user %s are invalid." : "Cannot use \"%s\" as owner: %m", - optarg); + opts.arg); break; - } - case ARG_FSCK: - r = parse_boolean_argument("--fsck=", optarg, &arg_fsck); + OPTION_LONG("fsck", "BOOL", "Run a file system check before mount"): + r = parse_boolean_argument("--fsck=", opts.arg, &arg_fsck); if (r < 0) return r; break; - case ARG_DESCRIPTION: - r = free_and_strdup_warn(&arg_description, optarg); + OPTION_LONG("description", "TEXT", "Description for unit"): + r = free_and_strdup_warn(&arg_description, opts.arg); if (r < 0) return r; break; - case 'p': - if (strv_extend(&arg_property, optarg) < 0) + OPTION('p', "property", "NAME=VALUE", "Set mount unit property"): + if (strv_extend(&arg_property, opts.arg) < 0) return log_oom(); - break; - case 'A': + OPTION_SHORT('A', NULL, "Same as --automount=yes"): arg_action = ACTION_AUTOMOUNT; break; - case ARG_AUTOMOUNT: - r = parse_boolean_argument("--automount=", optarg, NULL); + OPTION_LONG("automount", "BOOL", "Create an automount point"): + r = parse_boolean_argument("--automount=", opts.arg, NULL); if (r < 0) return r; arg_action = r ? ACTION_AUTOMOUNT : ACTION_MOUNT; break; - case ARG_TIMEOUT_IDLE: - r = parse_sec(optarg, &arg_timeout_idle); + OPTION_LONG("timeout-idle-sec", "SEC", "Specify automount idle timeout"): + r = parse_sec(opts.arg, &arg_timeout_idle); if (r < 0) - return log_error_errno(r, "Failed to parse timeout: %s", optarg); + return log_error_errno(r, "Failed to parse timeout: %s", opts.arg); + arg_timeout_idle_set = true; break; - case ARG_AUTOMOUNT_PROPERTY: - if (strv_extend(&arg_automount_property, optarg) < 0) + OPTION_LONG("automount-property", "NAME=VALUE", "Set automount unit property"): + if (strv_extend(&arg_automount_property, opts.arg) < 0) return log_oom(); - break; - case ARG_BIND_DEVICE: + OPTION_LONG("bind-device", NULL, "Bind automount unit to device"): arg_bind_device = true; break; - case ARG_LIST: + OPTION_LONG("list", NULL, "List mountable block devices"): arg_action = ACTION_LIST; break; - case 'u': + OPTION('u', "umount", NULL, "Unmount mount points"): {} + OPTION_LONG("unmount", NULL, /* help= */ NULL): /* compat spelling */ arg_action = ACTION_UMOUNT; break; - case 'G': + OPTION('G', "collect", NULL, "Unload unit after it stopped, even when failed"): arg_aggressive_gc = true; break; - case 'T': + OPTION('T', "tmpfs", NULL, "Create a new tmpfs on the mount point"): arg_tmpfs = true; break; - - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; - - break; - - case ARG_CANONICALIZE: - r = parse_boolean_argument("--canonicalize=", optarg, &arg_canonicalize); - if (r < 0) - return r; - - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + if (arg_runtime_scope == RUNTIME_SCOPE_USER) { arg_ask_password = false; @@ -406,7 +329,7 @@ static int parse_argv(int argc, char *argv[]) { } if (arg_action == ACTION_LIST) { - if (optind < argc) + if (n_args > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments."); @@ -414,22 +337,22 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Listing devices only supported locally."); } else if (arg_action == ACTION_UMOUNT) { - if (optind >= argc) + if (n_args == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument required."); if (arg_transport != BUS_TRANSPORT_LOCAL || !arg_canonicalize) - for (int i = optind; i < argc; i++) - if (!path_is_absolute(argv[i])) + STRV_FOREACH(a, args) + if (!path_is_absolute(*a)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path must be absolute when operating remotely or when canonicalization is turned off: %s", - argv[i]); + *a); } else { - if (optind >= argc) + if (n_args == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument required."); - if (argc > optind+2) + if (n_args > 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "More than two arguments are not allowed."); @@ -438,16 +361,16 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--discover cannot be used in conjunction with --tmpfs."); - if (argc <= optind+1) { + if (n_args == 1) { arg_mount_what = strdup("tmpfs"); if (!arg_mount_what) return log_oom(); - r = parse_where(argv[optind], &arg_mount_where); + r = parse_where(args[0], &arg_mount_where); if (r < 0) return r; } else { - arg_mount_what = strdup(argv[optind]); + arg_mount_what = strdup(args[0]); if (!arg_mount_what) return log_oom(); } @@ -462,12 +385,12 @@ static int parse_argv(int argc, char *argv[]) { arg_mount_type); } else { if (arg_mount_type && !fstype_is_blockdev_backed(arg_mount_type)) { - arg_mount_what = strdup(argv[optind]); + arg_mount_what = strdup(args[0]); if (!arg_mount_what) return log_oom(); } else { _cleanup_free_ char *u = NULL; - const char *p = argv[optind]; + const char *p = args[0]; if (arg_canonicalize) { u = fstab_node_to_udev_node(p); @@ -493,8 +416,8 @@ static int parse_argv(int argc, char *argv[]) { } } - if (argc > optind+1) { - r = parse_where(argv[optind+1], &arg_mount_where); + if (n_args >= 2) { + r = parse_where(args[1], &arg_mount_where); if (r < 0) return r; } else if (!arg_tmpfs) @@ -523,6 +446,7 @@ static int parse_argv(int argc, char *argv[]) { } } + *remaining_args = args; return 1; } @@ -914,7 +838,7 @@ static int find_loop_device(const char *backing_file, sd_device **ret) { FOREACH_DEVICE(e, dev) { const char *s; - r = sd_device_get_sysattr_value(dev, "loop/backing_file", &s); + r = device_get_sysattr_safe_string(dev, "loop/backing_file", &s); if (r < 0) { log_device_debug_errno(dev, r, "Failed to read \"loop/backing_file\" sysattr, ignoring: %m"); continue; @@ -1076,18 +1000,18 @@ static int umount_loop(sd_bus *bus, const char *backing_file) { return umount_by_device(bus, dev); } -static int action_umount(sd_bus *bus, int argc, char **argv) { +static int action_umount(sd_bus *bus, char **args) { int r, ret = 0; assert(bus); - assert(argv); - assert(argc > optind); + assert(args); + assert(!strv_isempty(args)); if (arg_transport != BUS_TRANSPORT_LOCAL || !arg_canonicalize) { - for (int i = optind; i < argc; i++) { + STRV_FOREACH(arg, args) { _cleanup_free_ char *p = NULL; - r = path_simplify_alloc(argv[i], &p); + r = path_simplify_alloc(*arg, &p); if (r < 0) return r; @@ -1096,10 +1020,10 @@ static int action_umount(sd_bus *bus, int argc, char **argv) { return ret; } - for (int i = optind; i < argc; i++) { + STRV_FOREACH(arg, args) { _cleanup_free_ char *u = NULL, *p = NULL; - u = fstab_node_to_udev_node(argv[i]); + u = fstab_node_to_udev_node(*arg); if (!u) return log_oom(); @@ -1112,7 +1036,7 @@ static int action_umount(sd_bus *bus, int argc, char **argv) { struct stat st; if (fstat(fd, &st) < 0) - return log_error_errno(errno, "Can't stat '%s' (from %s): %m", p, argv[i]); + return log_error_errno(errno, "Can't stat '%s' (from %s): %m", p, *arg); r = is_mount_point_at(fd, /* path= */ NULL, /* flags= */ 0); fd = safe_close(fd); /* before continuing make sure the dir is not keeping anything busy */ @@ -1134,7 +1058,7 @@ static int action_umount(sd_bus *bus, int argc, char **argv) { else r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown file type for unmounting: %s (from %s)", - p, argv[i]); + p, *arg); RET_GATHER(ret, r); } } @@ -1301,7 +1225,6 @@ static int acquire_description(sd_device *d) { } static int acquire_removable(sd_device *d) { - const char *v; int r; assert(d); @@ -1311,8 +1234,13 @@ static int acquire_removable(sd_device *d) { return 0; for (;;) { - if (sd_device_get_sysattr_value(d, "removable", &v) >= 0) + r = device_get_sysattr_bool(d, "removable"); + if (r == 0) + return 0; /* not a removable device */ + if (r > 0) break; + if (r != -ENOENT) + return log_device_debug_errno(d, r, "Failed to read 'removable' sysattr: %m"); r = sd_device_get_parent(d, &d); if (r == -ENODEV) @@ -1321,15 +1249,10 @@ 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) - return 0; - log_debug("Discovered removable device."); if (arg_action == ACTION_DEFAULT) { @@ -1414,9 +1337,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) @@ -1553,11 +1476,12 @@ static int list_devices(void) { static int run(int argc, char* argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -1571,7 +1495,7 @@ static int run(int argc, char* argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); if (arg_action == ACTION_UMOUNT) - return action_umount(bus, argc, argv); + return action_umount(bus, args); if ((!arg_mount_type || fstype_is_blockdev_backed(arg_mount_type)) && !path_is_normalized(arg_mount_what)) 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..4cd4e4abdd244 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); @@ -508,7 +502,8 @@ static int vl_method_mount_image( polkit_details, /* good_user= */ UID_INVALID, polkit_flags, - polkit_registry); + polkit_registry, + /* ret_admin= */ NULL); if (r <= 0) return r; @@ -521,7 +516,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 +527,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 | @@ -547,7 +542,7 @@ static int vl_method_mount_image( /* Let's see if we have acquired the privilege to mount untrusted images already */ bool polkit_have_untrusted_action = - varlink_has_polkit_action(link, polkit_untrusted_action, polkit_details, polkit_registry); + varlink_has_polkit_action(link, polkit_untrusted_action, polkit_details, polkit_registry, /* ret_admin= */ NULL); for (;;) { use_policy = image_policy_free(use_policy); @@ -600,7 +595,8 @@ static int vl_method_mount_image( polkit_details, /* good_user= */ UID_INVALID, /* flags= */ 0, /* NB: the image cannot be authenticated, hence unless PK is around to allow this anyway, fail! */ - polkit_registry); + polkit_registry, + /* ret_admin= */ NULL); if (r <= 0 && !ERRNO_IS_NEG_PRIVILEGE(r)) return r; if (r > 0) { @@ -678,7 +674,8 @@ static int vl_method_mount_image( polkit_details, /* good_user= */ UID_INVALID, /* flags= */ 0, /* NB: the image cannot be authenticated, hence unless PK is around to allow this anyway, fail! */ - polkit_registry); + polkit_registry, + /* ret_admin= */ NULL); if (r <= 0 && !ERRNO_IS_NEG_PRIVILEGE(r)) return r; if (r > 0) { @@ -754,7 +751,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 +863,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 +931,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 +946,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 @@ -1168,7 +1170,8 @@ static int vl_method_mount_directory( polkit_details, /* good_user= */ UID_INVALID, trusted_directory ? polkit_flags : 0, - polkit_registry); + polkit_registry, + /* ret_admin= */ NULL); if (r <= 0) return r; @@ -1363,7 +1366,7 @@ static int vl_method_make_directory( struct stat parent_stat; if (fstat(parent_fd, &parent_stat) < 0) - return r; + return log_debug_errno(errno, "Failed to fstat parent directory fd: %m"); r = stat_verify_directory(&parent_stat); if (r < 0) @@ -1401,7 +1404,8 @@ static int vl_method_make_directory( polkit_details, /* good_user= */ UID_INVALID, polkit_flags, - polkit_registry); + polkit_registry, + /* ret_admin= */ NULL); if (r <= 0) return r; diff --git a/src/mstack/mstack-tool.c b/src/mstack/mstack-tool.c index 2e8946ab72a7e..244e7dc682dcd 100644 --- a/src/mstack/mstack-tool.c +++ b/src/mstack/mstack-tool.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "argv-util.h" @@ -11,13 +10,14 @@ #include "extract-word.h" #include "fd-util.h" #include "format-table.h" +#include "help-util.h" #include "image-policy.h" #include "main-func.h" #include "mount-util.h" #include "mountpoint-util.h" #include "mstack.h" +#include "options.h" #include "parse-argument.h" -#include "pretty-print.h" #include "string-util.h" static enum { @@ -41,191 +41,155 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); 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; - r = terminal_urlify_man("systemd-mstack", "1", &link); + r = option_parser_get_help_table_ns("systemd-mstack", &options); if (r < 0) - return log_oom(); + return r; - printf("%1$s [OPTIONS...] WHAT\n" - "%1$s [OPTIONS...] --mount WHAT WHERE\n" - "%1$s [OPTIONS...] --umount WHERE\n" - "\n%5$sInspect or apply mount stack.%6$s\n\n" - "%3$sOptions:%4$s\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not print the column headers\n" - " --json=pretty|short|off Generate JSON output\n" - " -r --read-only Mount read-only\n" - " --mkdir Make mount directory before mounting, if missing\n" - " --rmdir Remove mount directory after unmounting\n" - " --image-policy=POLICY\n" - " Specify image dissection policy\n" - " --image-filter=FILTER\n" - " Specify image dissection filter\n" - "\n%3$sCommands:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -m --mount Mount the mstack 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" - "\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_full("systemd-mstack", "Commands", &commands); + if (r < 0) + return r; + (void) table_sync_column_widths(0, options, commands); + + help_cmdline("[OPTIONS...] WHAT"); + help_cmdline("[OPTIONS...] --mount WHAT WHERE"); + help_cmdline("[OPTIONS...] --umount WHERE"); + help_abstract("Inspect or apply mount stack."); + + help_section("Commands"); + r = table_print_or_warn(commands); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-mstack", "1"); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - ARG_MKDIR, - ARG_RMDIR, - ARG_IMAGE_POLICY, - ARG_IMAGE_FILTER, - }; - - 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' }, - { "json", required_argument, NULL, ARG_JSON }, - { "read-only", no_argument, NULL, 'r' }, - { "mkdir", no_argument, NULL, ARG_MKDIR }, - { "rmdir", no_argument, NULL, ARG_RMDIR }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "image-filter", required_argument, NULL, ARG_IMAGE_FILTER }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hmMuUr", options, NULL)) >= 0) { + OptionParser opts = { argc, argv, .namespace = "systemd-mstack" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - return help(); - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; + OPTION_NAMESPACE("systemd-mstack"): {} - case ARG_NO_LEGEND: - arg_legend = false; + OPTION('r', "read-only", NULL, "Mount read-only"): + arg_mstack_flags |= MSTACK_RDONLY; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; - + OPTION_LONG("mkdir", NULL, "Make mount directory before mounting, if missing"): + arg_mstack_flags |= MSTACK_MKDIR; break; - case 'r': - arg_mstack_flags |= MSTACK_RDONLY; + OPTION_LONG("rmdir", NULL, "Remove mount directory after unmounting"): + arg_rmdir = 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(opts.arg, &arg_image_policy); if (r < 0) return r; 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(opts.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", opts.arg); image_filter_free(arg_image_filter); arg_image_filter = TAKE_PTR(f); break; } - case ARG_MKDIR: - arg_mstack_flags |= MSTACK_MKDIR; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_RMDIR: - arg_rmdir = true; + OPTION_COMMON_NO_LEGEND: + arg_legend = false; break; - case 'm': + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); + if (r <= 0) + return r; + break; + + OPTION_GROUP("Commands"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('m', "mount", NULL, "Mount the mstack to the specified directory"): arg_action = ACTION_MOUNT; break; - case 'M': - /* Shortcut combination of --mkdir + --mount */ + OPTION_SHORT('M', NULL, "Shortcut for --mount --mkdir"): arg_action = ACTION_MOUNT; arg_mstack_flags |= MSTACK_MKDIR; break; - case 'u': + OPTION('u', "umount", NULL, "Unmount the image from the specified directory"): arg_action = ACTION_UMOUNT; break; - case 'U': - /* Shortcut combination of --rmdir + --umount */ + OPTION_SHORT('U', NULL, "Shortcut for --umount --rmdir"): arg_action = ACTION_UMOUNT; arg_rmdir = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } + + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); switch (arg_action) { case ACTION_INSPECT: - if (optind + 1 != argc) + if (n_args != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected one argument."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_what); + r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_what); if (r < 0) return r; break; case ACTION_MOUNT: - if (optind + 2 != argc) + if (n_args != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected two arguments."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_what); + r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_what); if (r < 0) return r; - r = parse_path_argument(argv[optind+1], /* suppress_root= */ false, &arg_where); + r = parse_path_argument(args[1], /* suppress_root= */ false, &arg_where); if (r < 0) return r; break; case ACTION_UMOUNT: - if (optind + 1 != argc) + if (n_args != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected one argument."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_where); + r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_where); if (r < 0) return r; @@ -239,47 +203,49 @@ static int parse_argv(int argc, char *argv[]) { } static int parse_argv_as_mount_helper(int argc, char *argv[]) { - const char *options = NULL; + const char *mount_options = NULL; bool fake = false; - int c, r; + int r; /* Implements util-linux "external helper" command line interface, as per mount(8) man page. */ - while ((c = getopt(argc, argv, "sfnvN:o:t:")) >= 0) { + OptionParser opts = { argc, argv, .namespace = "mount.mstack" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'f': + OPTION_NAMESPACE("mount.mstack"): {} + + OPTION_SHORT('f', NULL, NULL): fake = true; break; - case 'o': - options = optarg; + OPTION_SHORT('o', "OPTIONS", NULL): + mount_options = opts.arg; break; - case 't': - if (!streq(optarg, "mstack")) - log_debug("Unexpected file system type '%s', ignoring.", optarg); + OPTION_SHORT('t', "TYPE", NULL): + if (!streq(opts.arg, "mstack")) + log_debug("Unexpected file system type '%s', ignoring.", opts.arg); break; - case 's': /* sloppy mount options */ - case 'n': /* aka --no-mtab */ - case 'v': /* aka --verbose */ - log_debug("Ignoring option -%c, not implemented.", c); + OPTION_SHORT('s', NULL, NULL): {} /* sloppy mount options, fall-through */ + OPTION_SHORT('n', NULL, NULL): {} /* aka --no-mtab, fall-through */ + OPTION_SHORT('v', NULL, NULL): /* aka --verbose */ + log_debug("Ignoring option -%c, not implemented.", opts.opt->short_code); break; - case 'N': /* aka --namespace= */ - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Option -%c is not implemented, refusing.", c); - - case '?': - return -EINVAL; + OPTION_SHORT('N', "NAMESPACE", NULL): /* aka --namespace= */ + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Option -%c is not implemented, refusing.", opts.opt->short_code); } - } - if (optind + 2 != argc) + char **args = option_parser_get_args(&opts); + if (option_parser_get_n_args(&opts) != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected an image file path and target directory as only argument."); + "Expected an image file path and target directory as arguments."); - for (const char *p = options;;) { + for (const char *p = mount_options;;) { _cleanup_free_ char *word = NULL; r = extract_first_word(&p, &word, ",", EXTRACT_KEEP_QUOTE); @@ -300,11 +266,11 @@ static int parse_argv_as_mount_helper(int argc, char *argv[]) { if (fake) return 0; - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_what); + r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_what); if (r < 0) return r; - r = parse_path_argument(argv[optind+1], /* suppress_root= */ false, &arg_where); + r = parse_path_argument(args[1], /* suppress_root= */ false, &arg_where); if (r < 0) return r; diff --git a/src/mute-console/mute-console.c b/src/mute-console/mute-console.c index b18e79d622c2e..b64c66e14f07f 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,59 @@ 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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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=", opts.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=", opts.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) @@ -327,8 +308,9 @@ static int vl_server(void) { r = varlink_server_new( &varlink_server, - SD_VARLINK_SERVER_ROOT_ONLY| - SD_VARLINK_SERVER_HANDLE_SIGINT| + SD_VARLINK_SERVER_ROOT_ONLY | + SD_VARLINK_SERVER_MYSELF_ONLY | + SD_VARLINK_SERVER_HANDLE_SIGINT | SD_VARLINK_SERVER_HANDLE_SIGTERM, /* userdata= */ NULL); 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..df9ce9265dbbb 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,46 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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 = opts.arg; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&opts); return 1; } @@ -205,20 +201,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 +230,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..110af9511c11b 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' ) @@ -45,6 +43,7 @@ systemd_networkd_extract_sources = files( 'networkd-conf.c', 'networkd-dhcp-common.c', 'networkd-dhcp-prefix-delegation.c', + 'networkd-dhcp-relay.c', 'networkd-dhcp-server-bus.c', 'networkd-dhcp-server-static-lease.c', 'networkd-dhcp-server.c', @@ -130,9 +129,11 @@ networkctl_sources = files( 'networkctl-address-label.c', 'networkctl-config-file.c', 'networkctl-description.c', + 'networkctl-dhcp-lease.c', 'networkctl-dump-util.c', 'networkctl-journal.c', 'networkctl-link-info.c', + 'networkctl-link-info-json.c', 'networkctl-list.c', 'networkctl-lldp.c', 'networkctl-misc.c', @@ -144,10 +145,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', @@ -173,7 +170,7 @@ else libshared_static] endif -network_includes = [libsystemd_network_includes, include_directories(['.', 'netdev', 'tc'])] +network_includes = [include_directories(['.', 'netdev', 'tc']), libsystemd_network_includes] network_test_template = test_template + { 'conditions' : ['ENABLE_NETWORKD'], @@ -192,7 +189,6 @@ network_fuzz_template = fuzz_template + { libsystemd_network, ], 'objects' : ['systemd-networkd'], - 'dependencies' : threads, 'include_directories' : network_includes, } @@ -208,7 +204,9 @@ executables += [ libsystemd_network, networkd_link_with, ], - 'dependencies' : threads, + 'bpf_programs': [ + 'sysctl-monitor', + ] }, libexec_template + { 'name' : 'systemd-networkd-wait-online', @@ -241,11 +239,9 @@ executables += [ }, network_test_template + { 'sources' : files('test-network-tables.c'), - 'dependencies' : threads, }, network_test_template + { 'sources' : files('test-network.c'), - 'dependencies' : threads, }, network_test_template + { 'sources' : files('test-networkd-address.c'), @@ -258,6 +254,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/bareudp.c b/src/network/netdev/bareudp.c index 9dd70296bc6c6..2183e11661c77 100644 --- a/src/network/netdev/bareudp.c +++ b/src/network/netdev/bareudp.c @@ -49,7 +49,7 @@ static int netdev_bare_udp_verify(NetDev *netdev, const char *filename) { if (u->dest_port == 0) return log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), - "%s: BareUDP DesinationPort= is not set. Ignoring.", filename); + "%s: BareUDP DestinationPort= is not set. Ignoring.", filename); if (u->iftype == _BARE_UDP_PROTOCOL_INVALID) return log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), 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..70af349108d92 100644 --- a/src/network/netdev/netdev-gperf.gperf +++ b/src/network/netdev/netdev-gperf.gperf @@ -52,6 +52,7 @@ Match.Version, config_parse_net_condition, Match.Credential, config_parse_net_condition, CONDITION_CREDENTIAL, offsetof(NetDev, conditions) Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(NetDev, conditions) Match.Firmware, config_parse_net_condition, CONDITION_FIRMWARE, offsetof(NetDev, conditions) +Match.MachineTag, config_parse_net_condition, CONDITION_MACHINE_TAG, offsetof(NetDev, conditions) NetDev.Description, config_parse_string, 0, offsetof(NetDev, description) NetDev.Name, config_parse_ifname, 0, offsetof(NetDev, ifname) NetDev.Kind, config_parse_netdev_kind, 0, offsetof(NetDev, kind) @@ -249,7 +250,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..f69c509023ab9 100644 --- a/src/network/networkctl-config-file.c +++ b/src/network/networkctl-config-file.c @@ -2,24 +2,19 @@ #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" #include "extract-word.h" #include "fd-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "netlink-util.h" #include "network-util.h" #include "networkctl.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-dhcp-lease.c b/src/network/networkctl-dhcp-lease.c new file mode 100644 index 0000000000000..465bff0979926 --- /dev/null +++ b/src/network/networkctl-dhcp-lease.c @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" +#include "sd-netlink.h" +#include "sd-varlink.h" + +#include "dhcp-message-dump.h" +#include "log.h" +#include "networkctl.h" +#include "networkctl-dhcp-lease.h" +#include "networkctl-link-info.h" +#include "networkctl-util.h" +#include "strv.h" + +int verb_dhcp_lease(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + /* networkctl dhcp-lease INTERFACE [CODE[:FORMAT] ...] */ + assert(argc >= 2); + + pager_open(arg_pager_flags); + + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL; + r = varlink_connect_networkd(&vl); + if (r < 0) + return r; + + const char *ifname = argv[1]; + + _cleanup_(link_info_array_freep) LinkInfo *link = NULL; + r = acquire_link_info(vl, rtnl, STRV_MAKE(ifname), &link); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; /* already logged in acquire_link_info(). */ + if (r > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Interface name '%s' matches multiple interfaces.", ifname); + + if (!link->dhcp_message) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), + "Interface '%s' does not have DHCPv4 lease.", link->name); + + if (sd_json_format_enabled(arg_json_format_flags)) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + r = dhcp_message_build_json(link->dhcp_message, &v); + if (r < 0) + return log_error_errno(r, "Failed to build JSON variant from DHCP message: %m"); + + r = sd_json_variant_dump(v, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to dump JSON variant: %m"); + + return 0; + } + + DumpDHCPMessageFlag flags = 0; + SET_FLAG(flags, DUMP_DHCP_MESSAGE_LEGEND, arg_legend); + SET_FLAG(flags, DUMP_DHCP_MESSAGE_FULL, arg_full); + + return dump_dhcp_message(link->dhcp_message, strv_skip(argv, 2), flags); +} diff --git a/src/network/networkctl-dhcp-lease.h b/src/network/networkctl-dhcp-lease.h new file mode 100644 index 0000000000000..f068df3b5ce58 --- /dev/null +++ b/src/network/networkctl-dhcp-lease.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +int verb_dhcp_lease(int argc, char *argv[], uintptr_t _data, void *userdata); diff --git a/src/network/networkctl-dump-util.c b/src/network/networkctl-dump-util.c index 20d5e8f43cc00..e6fbc45d9a923 100644 --- a/src/network/networkctl-dump-util.c +++ b/src/network/networkctl-dump-util.c @@ -1,10 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-dhcp-lease.h" #include "sd-hwdb.h" #include "sd-netlink.h" #include "alloc-util.h" +#include "dhcp-protocol.h" #include "ether-addr-util.h" #include "format-ifname.h" #include "format-table.h" @@ -210,13 +210,13 @@ int dump_gateways(sd_netlink *rtnl, sd_hwdb *hwdb, Table *table, int ifindex) { int dump_addresses( sd_netlink *rtnl, - sd_dhcp_lease *lease, + sd_dhcp_message *message, Table *table, int ifindex) { _cleanup_free_ struct local_address *local_addrs = NULL; _cleanup_strv_free_ char **buf = NULL; - struct in_addr dhcp4_address = {}; + struct in_addr dhcp4_address = {}, server_address = {}; int r, n; assert(rtnl); @@ -226,15 +226,15 @@ int dump_addresses( if (n <= 0) return n; - if (lease) - (void) sd_dhcp_lease_get_address(lease, &dhcp4_address); + if (message) { + dhcp4_address.s_addr = message->header.yiaddr; + if (dhcp_message_get_option_address(message, SD_DHCP_OPTION_SERVER_IDENTIFIER, &server_address) < 0) + /* The message should be BOOTP, let's fallback to the siaddr field. */ + server_address.s_addr = message->header.siaddr; + } FOREACH_ARRAY(local, local_addrs, n) { - struct in_addr server_address; - bool dhcp4 = false; - - if (local->family == AF_INET && in4_addr_equal(&local->address.in, &dhcp4_address)) - dhcp4 = sd_dhcp_lease_get_server_identifier(lease, &server_address) >= 0; + bool dhcp4 = local->family == AF_INET && in4_addr_equal(&local->address.in, &dhcp4_address); r = strv_extendf(&buf, "%s%s%s%s%s%s", IN_ADDR_TO_STRING(local->family, &local->address), diff --git a/src/network/networkctl-dump-util.h b/src/network/networkctl-dump-util.h index cfd0dd3de0122..a4c02f0390625 100644 --- a/src/network/networkctl-dump-util.h +++ b/src/network/networkctl-dump-util.h @@ -3,7 +3,9 @@ #include "shared-forward.h" +#include "dhcp-message.h" + int dump_list(Table *table, const char *key, char * const *l); int ieee_oui(sd_hwdb *hwdb, const struct ether_addr *mac, char **ret); int dump_gateways(sd_netlink *rtnl, sd_hwdb *hwdb, Table *table, int ifindex); -int dump_addresses(sd_netlink *rtnl, sd_dhcp_lease *lease, Table *table, int ifindex); +int dump_addresses(sd_netlink *rtnl, sd_dhcp_message *message, Table *table, int ifindex); diff --git a/src/network/networkctl-link-info-json.c b/src/network/networkctl-link-info-json.c new file mode 100644 index 0000000000000..b187432b805ed --- /dev/null +++ b/src/network/networkctl-link-info-json.c @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dhcp-client-id.h" +#include "sd-json.h" + +#include "iovec-util.h" +#include "json-util.h" +#include "networkctl-link-info.h" +#include "networkctl-link-info-json.h" +#include "networkctl-util.h" + +static int acquire_link_bitrates(LinkInfo *link) { + int r; + + assert(link); + + 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 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 }, + {} + }; + + 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; +} + +static int acquire_link_dhcp_client(LinkInfo *link) { + int r; + + assert(link); + + sd_json_variant *v; + r = json_variant_find_object(link->description, STRV_MAKE("Interface", "DHCPv4Client", "ClientIdentifier"), &v); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + _cleanup_(iovec_done) struct iovec iov = {}; + r = json_dispatch_byte_array_iovec("ClientIdentifier", v, /* flags= */ 0, &iov); + if (r < 0) + return r; + + return sd_dhcp_client_id_set_raw(&link->dhcp_client_id, iov.iov_base, iov.iov_len); +} + +static int acquire_link_dhcp_message(LinkInfo *link) { + int r; + + assert(link); + + sd_json_variant *v; + r = json_variant_find_object(link->description, STRV_MAKE("Interface", "DHCPv4Client", "Lease", "Message"), &v); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + return dhcp_message_parse_json(v, &link->dhcp_message); +} + +int link_info_parse_description(LinkInfo *link, sd_varlink *vl) { + int r; + + assert(link); + + if (!vl) + return 0; + + r = acquire_link_description(vl, link->ifindex, &link->description); + if (r < 0) + return r; + + (void) acquire_link_bitrates(link); + (void) acquire_link_dhcp_client(link); + (void) acquire_link_dhcp_message(link); + + return 0; +} diff --git a/src/network/networkctl-link-info-json.h b/src/network/networkctl-link-info-json.h new file mode 100644 index 0000000000000..2b15e5b01b68c --- /dev/null +++ b/src/network/networkctl-link-info-json.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +typedef struct LinkInfo LinkInfo; + +int link_info_parse_description(LinkInfo *link, sd_varlink *vl); diff --git a/src/network/networkctl-link-info.c b/src/network/networkctl-link-info.c index 8f072f834ac70..789f2a00eedd4 100644 --- a/src/network/networkctl-link-info.c +++ b/src/network/networkctl-link-info.c @@ -2,19 +2,16 @@ #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" #include "netlink-util.h" #include "networkctl-link-info.h" -#include "networkctl-util.h" +#include "networkctl-link-info-json.h" #include "sort-util.h" #include "stdio-util.h" #include "string-util.h" @@ -28,6 +25,8 @@ 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); + sd_dhcp_message_unref(array[i].dhcp_message); free(array[i].netdev_kind); free(array[i].ssid); free(array[i].qdisc); @@ -280,36 +279,6 @@ 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; - 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); - 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); - - link->has_bitrates = link->tx_bitrate != UINT64_MAX && link->rx_bitrate != UINT64_MAX; - - return 0; -} - static void acquire_ether_link_info(int *fd, LinkInfo *link) { assert(fd); assert(link); @@ -356,7 +325,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 +371,8 @@ 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]); + (void) link_info_parse_description(&links[c], vl); + c++; } @@ -421,10 +392,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..ae1bf71687510 100644 --- a/src/network/networkctl-link-info.h +++ b/src/network/networkctl-link-info.h @@ -5,6 +5,8 @@ #include #include +#include "dhcp-client-id-internal.h" +#include "dhcp-message.h" #include "ether-addr-util.h" #include "ethtool-util.h" #include "shared-forward.h" @@ -35,6 +37,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; @@ -57,6 +60,10 @@ typedef struct LinkInfo { uint64_t tx_bitrate; uint64_t rx_bitrate; + /* DHCPv4 */ + sd_dhcp_message *dhcp_message; + sd_dhcp_client_id dhcp_client_id; + /* bridge info */ uint32_t forward_delay; uint32_t hello_time; @@ -133,4 +140,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..5083898ec3cec 100644 --- a/src/network/networkctl-status-link.c +++ b/src/network/networkctl-status-link.c @@ -1,9 +1,8 @@ /* 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" +#include "sd-dhcp-protocol.h" #include "sd-hwdb.h" #include "sd-netlink.h" #include "sd-network.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,10 +18,11 @@ #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" #include "networkctl.h" #include "networkctl-description.h" #include "networkctl-dump-util.h" @@ -40,87 +38,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 +228,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, @@ -272,11 +240,9 @@ static int link_status_one( const char *driver = NULL, *path = NULL, *vendor = NULL, *model = NULL, *link = NULL, *on_color_operational, *off_color_operational, *on_color_setup, *off_color_setup, *on_color_online; _cleanup_free_ int *carrier_bound_to = NULL, *carrier_bound_by = NULL; - _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; _cleanup_(table_unrefp) Table *table = NULL; int r; - assert(bus); assert(rtnl); assert(vl); assert(info); @@ -325,11 +291,6 @@ static int link_status_one( if (r == -ENOMEM) return log_oom(); - char lease_file[STRLEN("/run/systemd/netif/leases/") + DECIMAL_STR_MAX(int)]; - xsprintf(lease_file, "/run/systemd/netif/leases/%i", info->ifindex); - - (void) dhcp_lease_load(&lease, lease_file); - r = format_config_files(&network_dropins, network); if (r < 0) return r; @@ -609,7 +570,7 @@ static int link_status_one( } else if (streq_ptr(info->netdev_kind, "vlan") && info->vlan_id > 0) { r = table_add_many(table, - TABLE_FIELD, "VLan Id", + TABLE_FIELD, "VLAN ID", TABLE_UINT16, info->vlan_id); if (r < 0) return table_log_add_error(r); @@ -811,7 +772,7 @@ static int link_status_one( return r; } - r = dump_addresses(rtnl, lease, table, info->ifindex); + r = dump_addresses(rtnl, info->dhcp_message, table, info->ifindex); if (r < 0) return r; @@ -869,31 +830,29 @@ static int link_status_one( return table_log_add_error(r); } - if (lease) { - const sd_dhcp_client_id *client_id; - const char *tz; + if (info->dhcp_message) { + _cleanup_free_ char *tz = NULL; - r = sd_dhcp_lease_get_timezone(lease, &tz); - if (r >= 0) { + if (dhcp_message_get_option_string(info->dhcp_message, SD_DHCP_OPTION_TZDB_TIMEZONE, &tz) >= 0 + && timezone_is_valid(tz, LOG_DEBUG)) { r = table_add_many(table, TABLE_FIELD, "Time Zone", TABLE_STRING, tz); if (r < 0) return table_log_add_error(r); } + } + + if (sd_dhcp_client_id_is_set(&info->dhcp_client_id)) { + _cleanup_free_ char *id = NULL; - r = sd_dhcp_lease_get_client_id(lease, &client_id); + r = sd_dhcp_client_id_to_string(&info->dhcp_client_id, &id); if (r >= 0) { - _cleanup_free_ char *id = NULL; - - r = sd_dhcp_client_id_to_string(client_id, &id); - if (r >= 0) { - r = table_add_many(table, - TABLE_FIELD, "DHCPv4 Client ID", - TABLE_STRING, id); - if (r < 0) - return table_log_add_error(r); - } + r = table_add_many(table, + TABLE_FIELD, "DHCPv4 Client ID", + TABLE_STRING, id); + if (r < 0) + return table_log_add_error(r); } } @@ -919,7 +878,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 +891,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 +909,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 +924,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 +939,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..35de930423a45 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); @@ -91,7 +94,7 @@ int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) { if (r < 0) return table_log_add_error(r); - r = dump_addresses(rtnl, NULL, table, 0); + r = dump_addresses(rtnl, /* message= */ NULL, table, /* ifindex= */ 0); if (r < 0) return r; @@ -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..bf74f10a4a48e 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -1,25 +1,26 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-json.h" #include "alloc-util.h" #include "build.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" #include "logs-show.h" #include "main-func.h" #include "networkctl.h" #include "networkctl-address-label.h" #include "networkctl-config-file.h" +#include "networkctl-dhcp-lease.h" #include "networkctl-list.h" #include "networkctl-lldp.h" #include "networkctl-misc.h" #include "networkctl-status-link.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" -#include "pretty-print.h" #include "string-util.h" #include "verbs.h" @@ -38,144 +39,148 @@ bool arg_ask_password = true; STATIC_DESTRUCTOR_REGISTER(arg_drop_in, freep); +VERB_SCOPE(, verb_list_links, "list", "[PATTERN...]", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, + "List links"); +VERB_SCOPE(, verb_link_status, "status", "[PATTERN...]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Show link status"); +VERB_SCOPE(, verb_dhcp_lease, "dhcp-lease", "INTERFACE [CODE[:FORMAT]...]", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Show DHCP lease"); +VERB_SCOPE(, verb_link_lldp_status, "lldp", "[PATTERN...]", VERB_ANY, VERB_ANY, 0, + "Show LLDP neighbors"); +VERB_SCOPE(, verb_list_address_labels, "label", NULL, 1, 1, 0, + "Show current address label entries in the kernel"); +VERB_SCOPE(, verb_link_delete, "delete", "DEVICES...", 2, VERB_ANY, 0, + "Delete virtual netdevs"); +VERB_SCOPE(, verb_link_varlink_simple_method, "up", "DEVICES...", 2, VERB_ANY, 0, + "Bring devices up"); +VERB_SCOPE(, verb_link_varlink_simple_method, "down", "DEVICES...", 2, VERB_ANY, 0, + "Bring devices down"); +VERB_SCOPE(, verb_link_varlink_simple_method, "renew", "DEVICES...", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Renew dynamic configurations"); +VERB_SCOPE(, verb_link_varlink_simple_method, "forcerenew", "DEVICES...", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Trigger DHCP reconfiguration of all connected clients"); +VERB_SCOPE(, verb_link_varlink_simple_method, "reconfigure", "DEVICES...", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Reconfigure interfaces"); +VERB_SCOPE(, verb_reload, "reload", NULL, 1, 1, VERB_ONLINE_ONLY, + "Reload .network and .netdev files"); +VERB_SCOPE(, verb_edit, "edit", "FILES|DEVICES...", 2, VERB_ANY, 0, + "Edit network configuration files"); +VERB_SCOPE(, verb_cat, "cat", "[FILES|DEVICES...]", 1, VERB_ANY, 0, + "Show network configuration files"); +VERB_SCOPE(, verb_mask, "mask", "FILES...", 2, VERB_ANY, 0, + "Mask network configuration files"); +VERB_SCOPE(, verb_unmask, "unmask", "FILES...", 2, VERB_ANY, 0, + "Unmask network configuration files"); +VERB_SCOPE(, verb_persistent_storage, "persistent-storage", "BOOL", 2, 2, 0, + "Notify systemd-networkd if persistent storage is ready"); + static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; - r = terminal_urlify_man("networkctl", "1", &link); + 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); + + help_cmdline("[OPTIONS...] COMMAND"); + help_abstract("Query and control the networking subsystem."); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); if (r < 0) - return log_oom(); - - printf("%s [OPTIONS...] COMMAND\n\n" - "%sQuery and control the networking subsystem.%s\n" - "\nCommands:\n" - " list [PATTERN...] List links\n" - " status [PATTERN...] Show link status\n" - " lldp [PATTERN...] Show LLDP neighbors\n" - " label Show current address label entries in the kernel\n" - " delete DEVICES... Delete virtual netdevs\n" - " up DEVICES... Bring devices up\n" - " down DEVICES... Bring devices down\n" - " renew DEVICES... Renew dynamic configurations\n" - " forcerenew DEVICES... Trigger DHCP reconfiguration of all connected clients\n" - " reconfigure DEVICES... Reconfigure interfaces\n" - " reload Reload .network and .netdev files\n" - " edit FILES|DEVICES... Edit network configuration files\n" - " cat [FILES|DEVICES...] Show network configuration files\n" - " mask FILES... Mask network configuration files\n" - " unmask FILES... Unmask network configuration files\n" - " persistent-storage BOOL\n" - " Notify systemd-networkd if persistent storage is ready\n" - "\nOptions:\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 prompt for password\n" - " -a --all Show status for all links\n" - " -s --stats Show detailed link statistics\n" - " -l --full Do not ellipsize output\n" - " -n --lines=INTEGER Number of journal entries to show\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " --no-reload Do not reload systemd-networkd or systemd-udevd\n" - " after editing network config\n" - " --drop-in=NAME Edit specified drop-in instead of main config file\n" - " --runtime Edit runtime config files\n" - " --stdin Read new contents of edited file from stdin\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + return r; + help_man_page_reference("networkctl", "1"); 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_JSON, - ARG_NO_RELOAD, - ARG_DROP_IN, - ARG_RUNTIME, - ARG_STDIN, - }; - - 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 }, - { "all", no_argument, NULL, 'a' }, - { "stats", no_argument, NULL, 's' }, - { "full", no_argument, NULL, 'l' }, - { "lines", required_argument, NULL, 'n' }, - { "json", required_argument, NULL, ARG_JSON }, - { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, - { "drop-in", required_argument, NULL, ARG_DROP_IN }, - { "runtime", no_argument, NULL, ARG_RUNTIME }, - { "stdin", no_argument, NULL, ARG_STDIN }, - {} - }; - - int c, r; +VERB_COMMON_HELP_HIDDEN(help); + +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "hasln:", options, NULL)) >= 0) { + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case ARG_NO_RELOAD: - arg_no_reload = true; + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION('a', "all", NULL, "Show status for all links"): + arg_all = true; break; - case ARG_RUNTIME: - arg_runtime = true; + OPTION('s', "stats", NULL, "Show detailed link statistics"): + arg_stats = true; break; - case ARG_STDIN: - arg_stdin = true; + OPTION('l', "full", NULL, "Do not ellipsize output"): + arg_full = true; + break; + + OPTION('n', "lines", "INTEGER", "Number of journal entries to show"): + r = safe_atou(opts.arg, &arg_lines); + if (r < 0) + return log_error_errno(r, "Failed to parse --lines value '%s': %m", opts.arg); break; - case ARG_DROP_IN: - if (isempty(optarg)) + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); + if (r <= 0) + return r; + break; + + OPTION_LONG("no-reload", NULL, + "Do not reload systemd-networkd or systemd-udevd after editing network config"): + arg_no_reload = true; + break; + + OPTION_LONG("drop-in", "NAME", + "Edit specified drop-in instead of main config file"): + if (isempty(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty drop-in file name."); - if (!endswith(optarg, ".conf")) { + if (!endswith(opts.arg, ".conf")) { char *conf; - conf = strjoin(optarg, ".conf"); + conf = strjoin(opts.arg, ".conf"); if (!conf) return log_oom(); free_and_replace(arg_drop_in, conf); } else { - r = free_and_strdup(&arg_drop_in, optarg); + r = free_and_strdup(&arg_drop_in, opts.arg); if (r < 0) return log_oom(); } @@ -186,77 +191,32 @@ static int parse_argv(int argc, char *argv[]) { break; - case 'a': - arg_all = true; - break; - - case 's': - arg_stats = true; - break; - - case 'l': - arg_full = true; - break; - - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse lines '%s'", optarg); + OPTION_LONG("runtime", NULL, "Edit runtime config files"): + arg_runtime = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; + OPTION_LONG("stdin", NULL, "Read new contents of edited file from stdin"): + arg_stdin = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } + *remaining_args = option_parser_get_args(&opts); return 1; } -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 }, - {} - }; - - 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; journal_browse_prepare(); - return networkctl_main(argc, argv); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 5bb4cddbdac82..582069cf39745 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -19,6 +19,7 @@ #include "networkd-address.h" #include "networkd-address-pool.h" #include "networkd-dhcp-prefix-delegation.h" +#include "networkd-dhcp-relay.h" #include "networkd-dhcp-server.h" #include "networkd-ipv4acd.h" #include "networkd-link.h" @@ -162,7 +163,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 +174,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; @@ -243,6 +246,9 @@ static Address* address_detach_impl(Address *address) { if (address->network->dhcp_server_address == address) address->network->dhcp_server_address = NULL; + if (address->network->dhcp_relay_agent_address == address) + address->network->dhcp_relay_agent_address = NULL; + address->network = NULL; return address; } @@ -805,8 +811,10 @@ static int address_update(Address *address) { link->ipv6ll_address = address->in_addr.in6; r = link_ipv6ll_gained(link); - if (r < 0) + if (r < 0) { + link->ipv6ll_address = (const struct in6_addr) {}; return r; + } } if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) @@ -884,13 +892,47 @@ static int address_drop(Address *in, bool removed_by_us) { address_del_netlabel(address); - /* FIXME: if the IPv6LL address is dropped, stop DHCPv6, NDISC, RADV. */ if (address->family == AF_INET6 && - in6_addr_equal(&address->in_addr.in6, &link->ipv6ll_address)) + in6_addr_equal(&address->in_addr.in6, &link->ipv6ll_address)) { link->ipv6ll_address = (const struct in6_addr) {}; + Address *a; + bool has_replacement; + + /* If another ready IPv6LL address exists on this link, use it instead. */ + SET_FOREACH(a, link->addresses) { + if (a == address) + continue; + if (a->family != AF_INET6) + continue; + if (!in6_addr_is_link_local(&a->in_addr.in6)) + continue; + if (!address_is_ready(a)) + continue; + link->ipv6ll_address = a->in_addr.in6; + break; + } + + has_replacement = in6_addr_is_set(&link->ipv6ll_address); + + /* Stop engines bound to the dropped IPv6LL source address. Do not return early on error. + * address_detach() and link_update_operstate() must run to keep link state consistent. */ + r = link_ipv6ll_lost(link, &address->in_addr.in6, has_replacement); + if (r < 0) + log_link_warning_errno(link, r, "Failed to stop IPv6 services after IPv6LL loss, ignoring: %m"); + + /* If another IPv6LL address is available, restart engines with it. */ + if (has_replacement) { + r = link_ipv6ll_gained(link); + if (r < 0) + log_link_warning_errno(link, r, "Failed to restart IPv6 services with alternate IPv6LL address, ignoring: %m"); + } + } + ipv4acd_detach(link, address); + (void) link_dhcp_relay_address_dropped(link, address); + address_detach(address); if (!removed_by_us) { @@ -2476,9 +2518,13 @@ int network_drop_invalid_addresses(Network *network) { assert(r > 0); } + /* Detach duplicated entries now. */ + duplicated_addresses = set_free(duplicated_addresses); + r = network_adjust_dhcp_server(network, &addresses); if (r < 0) return r; + network_adjust_dhcp_relay(network); return 0; } diff --git a/src/network/networkd-bridge-fdb.c b/src/network/networkd-bridge-fdb.c index 6a635a880185a..ff9356a444e65 100644 --- a/src/network/networkd-bridge-fdb.c +++ b/src/network/networkd-bridge-fdb.c @@ -139,7 +139,7 @@ static int bridge_fdb_configure_message(const BridgeFDB *fdb, Link *link, sd_net if (r < 0) return r; - /* VLAN Id is optional. We'll add VLAN Id only if it's specified. */ + /* VLAN ID is optional. We'll add VLAN ID only if it's specified. */ if (fdb->vlan_id > 0) { r = sd_netlink_message_append_u16(req, NDA_VLAN, fdb->vlan_id); if (r < 0) diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c index bce16284f84e9..ba3e2f5a43132 100644 --- a/src/network/networkd-conf.c +++ b/src/network/networkd-conf.c @@ -22,6 +22,7 @@ int manager_parse_config_file(Manager *m) { "DHCPv4\0" "DHCPv6\0" "DHCPServer\0" + "DHCPRelay\0" "DHCP\0", config_item_perf_lookup, networkd_gperf_lookup, CONFIG_PARSE_WARN, diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index 619c2f6377093..3368145981f9f 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -11,18 +11,20 @@ #include "alloc-util.h" #include "bus-error.h" #include "bus-locator.h" -#include "dhcp-option.h" #include "dhcp6-option.h" #include "escape.h" #include "extract-word.h" #include "hexdecoct.h" #include "in-addr-prefix-util.h" +#include "iovec-util.h" #include "networkd-dhcp-common.h" #include "networkd-dhcp-prefix-delegation.h" +#include "networkd-ipv6ll.h" #include "networkd-link.h" #include "networkd-manager.h" #include "networkd-network.h" #include "networkd-route-util.h" +#include "networkd-util.h" #include "parse-util.h" #include "set.h" #include "socket-util.h" @@ -59,6 +61,15 @@ uint32_t link_get_ndisc_route_table(Link *link) { return link_get_vrf_table(link); } +uint32_t link_get_dhcp6_route_table(Link *link) { + assert(link); + assert(link->network); + + if (link->network->dhcp6_route_table_set) + return link->network->dhcp6_route_table; + return link_get_vrf_table(link); +} + bool link_dhcp_enabled(Link *link, int family) { assert(link); assert(IN_SET(family, AF_INET, AF_INET6)); @@ -100,6 +111,7 @@ void network_adjust_dhcp(Network *network) { } if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6) && + !network_has_static_ipv6ll_address(network) && FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV6)) { log_warning("%s: DHCPv6 client is enabled but IPv6 link-local addressing is disabled. " "Disabling DHCPv6 client.", network->filename); @@ -276,6 +288,7 @@ int link_get_captive_portal(Link *link, const char **ret) { int r; assert(link); + assert(ret); if (!link->network) { *ret = NULL; @@ -562,7 +575,7 @@ int config_parse_dhcp_or_ra_route_table( assert(filename); assert(lvalue); - assert(IN_SET(ltype, AF_INET, AF_INET6)); + assert(IN_SET(ltype, NETWORK_CONFIG_SOURCE_DHCP4, NETWORK_CONFIG_SOURCE_DHCP6, NETWORK_CONFIG_SOURCE_NDISC)); assert(rvalue); r = manager_get_route_table_from_string(network->manager, rvalue, &rt); @@ -573,11 +586,15 @@ int config_parse_dhcp_or_ra_route_table( } switch (ltype) { - case AF_INET: + case NETWORK_CONFIG_SOURCE_DHCP4: network->dhcp_route_table = rt; network->dhcp_route_table_set = true; break; - case AF_INET6: + case NETWORK_CONFIG_SOURCE_DHCP6: + network->dhcp6_route_table = rt; + network->dhcp6_route_table_set = true; + break; + case NETWORK_CONFIG_SOURCE_NDISC: network->ndisc_route_table = rt; network->ndisc_route_table_set = true; break; @@ -635,7 +652,7 @@ int config_parse_iaid( return 0; } -int config_parse_dhcp_user_or_vendor_class( +int config_parse_dhcp4_user_class( const char *unit, const char *filename, unsigned line, @@ -647,46 +664,76 @@ int config_parse_dhcp_user_or_vendor_class( void *data, void *userdata) { - char ***l = ASSERT_PTR(data); + struct iovec_wrapper *iovw = ASSERT_PTR(data); int r; assert(lvalue); assert(rvalue); - assert(IN_SET(ltype, AF_INET, AF_INET6)); if (isempty(rvalue)) { - *l = strv_free(*l); + iovw_done_free(iovw); return 0; } for (const char *p = rvalue;;) { _cleanup_free_ char *w = NULL; - size_t len; r = extract_first_word(&p, &w, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to split user classes option, ignoring: %s", rvalue); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + if (r == 0) return 0; + + size_t len = strlen(w); + if (len > UINT8_MAX || len == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "The length of the %s entry '%s' is not in the range 1…255, ignoring.", lvalue, w); + continue; } + + r = iovw_consume(iovw, TAKE_PTR(w), len); + if (r < 0) + return log_oom(); + } +} + +int config_parse_dhcp6_user_or_vendor_class( + 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) { + + char ***l = ASSERT_PTR(data); + int r; + + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *l = strv_free(*l); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL; + + r = extract_first_word(&p, &w, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); if (r == 0) return 0; - len = strlen(w); - if (ltype == AF_INET) { - if (len > UINT8_MAX || len == 0) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "%s length is not in the range 1…255, ignoring.", w); - continue; - } - } else { - if (len > UINT16_MAX || len == 0) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "%s length is not in the range 1…65535, ignoring.", w); - continue; - } + size_t len = strlen(w); + if (len > UINT16_MAX || len == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "The length of the %s entry '%s' is not in the range 1…65535, ignoring.", lvalue, w); + continue; } r = strv_consume(l, TAKE_PTR(w)); @@ -695,7 +742,7 @@ int config_parse_dhcp_user_or_vendor_class( } } -int config_parse_dhcp_send_option( +int config_parse_dhcp6_send_option( const char *unit, const char *filename, unsigned line, @@ -707,17 +754,15 @@ int config_parse_dhcp_send_option( void *data, void *userdata) { - _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *opt4 = NULL; _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *opt6 = NULL; - _unused_ _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *old4 = NULL; _unused_ _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *old6 = NULL; uint32_t uint32_data, enterprise_identifier = 0; _cleanup_free_ char *word = NULL, *q = NULL; - OrderedHashmap **options = ASSERT_PTR(data); + OrderedHashmap **dhcp6_options = ASSERT_PTR(data); uint16_t u16, uint16_data; union in_addr_union addr; DHCPOptionDataType type; - uint8_t u8, uint8_data; + uint8_t uint8_data; const void *udata; const char *p; ssize_t sz; @@ -728,12 +773,12 @@ int config_parse_dhcp_send_option( assert(rvalue); if (isempty(rvalue)) { - *options = ordered_hashmap_free(*options); + *dhcp6_options = ordered_hashmap_free(*dhcp6_options); return 0; } p = rvalue; - if (ltype == AF_INET6 && streq(lvalue, "SendVendorOption")) { + if (streq(lvalue, "SendVendorOption")) { r = extract_first_word(&p, &word, ":", 0); if (r == -ENOMEM) return log_oom(); @@ -761,30 +806,16 @@ int config_parse_dhcp_send_option( return 0; } - if (ltype == AF_INET6) { - r = safe_atou16(word, &u16); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid DHCP option, ignoring assignment: %s", rvalue); - return 0; - } - if (u16 < 1 || u16 >= UINT16_MAX) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid DHCP option, valid range is 1-65535, ignoring assignment: %s", rvalue); - return 0; - } - } else { - r = safe_atou8(word, &u8); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid DHCP option, ignoring assignment: %s", rvalue); - return 0; - } - if (u8 < 1 || u8 >= UINT8_MAX) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid DHCP option, valid range is 1-254, ignoring assignment: %s", rvalue); - return 0; - } + r = safe_atou16(word, &u16); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid DHCP option, ignoring assignment: %s", rvalue); + return 0; + } + if (u16 < 1 || u16 >= UINT16_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid DHCP option, valid range is 1-65535, ignoring assignment: %s", rvalue); + return 0; } word = mfree(word); @@ -886,49 +917,193 @@ int config_parse_dhcp_send_option( return -EINVAL; } - if (ltype == AF_INET6) { - r = sd_dhcp6_option_new(u16, udata, sz, enterprise_identifier, &opt6); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); - return 0; - } + r = sd_dhcp6_option_new(u16, udata, sz, enterprise_identifier, &opt6); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); + return 0; + } + + r = ordered_hashmap_ensure_allocated(dhcp6_options, &dhcp6_option_hash_ops); + if (r < 0) + return log_oom(); + + /* Overwrite existing option */ + old6 = ordered_hashmap_get(*dhcp6_options, UINT_TO_PTR(u16)); + r = ordered_hashmap_replace(*dhcp6_options, UINT_TO_PTR(u16), opt6); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); + return 0; + } + TAKE_PTR(opt6); + + return 0; +} + +int config_parse_dhcp_option( + 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) { + + struct iovec *iov = ASSERT_PTR(data); + bool check_length = ltype; + int r; + + if (isempty(rvalue)) { + iovec_done(iov); + return 0; + } - r = ordered_hashmap_ensure_allocated(options, &dhcp6_option_hash_ops); + _cleanup_free_ char *word = NULL; + const char *p = rvalue; + r = extract_first_word(&p, &word, ":", 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0 || isempty(p)) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + DHCPOptionDataType type = dhcp_option_data_type_from_string(word); + if (type < 0) + return log_syntax_parse_error(unit, filename, line, type, lvalue, rvalue); + + switch (type) { + case DHCP_OPTION_DATA_UINT8:{ + uint8_t u; + + r = safe_atou8(p, &u); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&u, sizeof(u))); if (r < 0) return log_oom(); - /* Overwrite existing option */ - old6 = ordered_hashmap_get(*options, UINT_TO_PTR(u16)); - r = ordered_hashmap_replace(*options, UINT_TO_PTR(u16), opt6); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); - return 0; - } - TAKE_PTR(opt6); - } else { - r = sd_dhcp_option_new(u8, udata, sz, &opt4); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); - return 0; - } + return 1; + } + case DHCP_OPTION_DATA_UINT16:{ + uint16_t u; + + r = safe_atou16(p, &u); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); - r = ordered_hashmap_ensure_allocated(options, &dhcp_option_hash_ops); + u = htobe16(u); + r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&u, sizeof(u))); if (r < 0) return log_oom(); - /* Overwrite existing option */ - old4 = ordered_hashmap_get(*options, UINT_TO_PTR(u8)); - r = ordered_hashmap_replace(*options, UINT_TO_PTR(u8), opt4); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); - return 0; - } - TAKE_PTR(opt4); + return 1; + } + case DHCP_OPTION_DATA_UINT32: { + uint32_t u; + + r = safe_atou32(p, &u); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + u = htobe32(u); + r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&u, sizeof(u))); + if (r < 0) + return log_oom(); + + return 1; + } + case DHCP_OPTION_DATA_IPV4ADDRESS: { + union in_addr_union a; + + r = in_addr_from_string(AF_INET, p, &a); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&a.in, sizeof(a.in))); + if (r < 0) + return log_oom(); + + return 1; + } + case DHCP_OPTION_DATA_IPV6ADDRESS: { + union in_addr_union a; + + r = in_addr_from_string(AF_INET6, p, &a); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + r = iovec_done_and_memdup(iov, &IOVEC_MAKE(&a.in6, sizeof(a.in6))); + if (r < 0) + return log_oom(); + + return 1; + } + case DHCP_OPTION_DATA_STRING: { + _cleanup_free_ char *s = NULL; + ssize_t sz = cunescape(p, UNESCAPE_ACCEPT_NUL, &s); + if (sz < 0) + return log_syntax_parse_error(unit, filename, line, sz, lvalue, rvalue); + if (check_length && sz > UINT8_MAX) + return log_syntax_parse_error(unit, filename, line, 0, lvalue, rvalue); + + iovec_done(iov); + *iov = IOVEC_MAKE(TAKE_PTR(s), sz); + return 1; + } + default: + return -EINVAL; + } +} + +int config_parse_dhcp_option_tlv( + 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) { + + TLV *options = ASSERT_PTR(data); + int r; + + if (isempty(rvalue)) { + tlv_done(options); + return 0; } + + _cleanup_free_ char *word = NULL; + const char *p = rvalue; + r = extract_first_word(&p, &word, ":", 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0 || isempty(p)) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + uint8_t code; + r = safe_atou8(word, &code); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + if (code < 1 || code >= UINT8_MAX) + return log_syntax_parse_error(unit, filename, line, 0, lvalue, rvalue); + + _cleanup_(iovec_done) struct iovec iov = {}; + r = config_parse_dhcp_option(unit, filename, line, section, section_line, lvalue, ltype, p, &iov, userdata); + if (r <= 0) + return r; + + r = tlv_append_iov(options, code, &iov); + if (r < 0) + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store '%s=%s', ignoring assignment: %m", lvalue, rvalue); + return 0; } diff --git a/src/network/networkd-dhcp-common.h b/src/network/networkd-dhcp-common.h index 9eb9331923b9f..b6385492c38a3 100644 --- a/src/network/networkd-dhcp-common.h +++ b/src/network/networkd-dhcp-common.h @@ -41,6 +41,7 @@ typedef struct DUID { uint32_t link_get_dhcp4_route_table(Link *link); uint32_t link_get_ndisc_route_table(Link *link); +uint32_t link_get_dhcp6_route_table(Link *link); bool link_dhcp_enabled(Link *link, int family); static inline bool link_dhcp4_enabled(Link *link) { @@ -81,8 +82,11 @@ CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_route_metric); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_send_hostname); CONFIG_PARSER_PROTOTYPE(config_parse_iaid); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_or_ra_route_table); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_or_vendor_class); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_send_option); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp4_user_class); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_user_or_vendor_class); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_send_option); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_option); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_option_tlv); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_request_options); CONFIG_PARSER_PROTOTYPE(config_parse_duid_type); CONFIG_PARSER_PROTOTYPE(config_parse_manager_duid_type); diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index f53d7d1c5aada..85c52334726be 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -14,6 +14,7 @@ #include "in-addr-prefix-util.h" #include "networkd-address.h" #include "networkd-address-generation.h" +#include "networkd-dhcp-common.h" #include "networkd-dhcp-prefix-delegation.h" #include "networkd-dhcp6.h" #include "networkd-link.h" @@ -557,6 +558,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 */ @@ -850,6 +852,8 @@ static int dhcp_request_unreachable_route( route->protocol = RTPROT_DHCP; route->priority = IP6_RT_PRIO_USER; route->lifetime_usec = lifetime_usec; + if (source == NETWORK_CONFIG_SOURCE_DHCP6 && !route->table_set) + route->table = link_get_dhcp6_route_table(link); r = route_adjust_nexthops(route, link); if (r < 0) diff --git a/src/network/networkd-dhcp-relay.c b/src/network/networkd-dhcp-relay.c new file mode 100644 index 0000000000000..7811d9d585757 --- /dev/null +++ b/src/network/networkd-dhcp-relay.c @@ -0,0 +1,380 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" +#include "sd-id128.h" + +#include "conf-parser.h" +#include "dhcp-relay-internal.h" +#include "hashmap.h" +#include "iovec-util.h" +#include "networkd-address.h" +#include "networkd-dhcp-relay.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "networkd-queue.h" +#include "string-table.h" +#include "string-util.h" /* IWYU pragma: keep */ + +#define DHCP_RELAY_APP_REMOTE_ID SD_ID128_MAKE(85,bb,eb,d2,b8,56,47,0b,b0,86,4c,f3,d3,9b,c1,b5) + +void network_adjust_dhcp_relay(Network *network) { + assert(network); + assert(network->manager); + + if (network->dhcp_relay_interface_mode < 0) + return; + + if (network->bond) { + log_warning("%s: DHCPRelay= is enabled for bond slave. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } + + if (network->dhcp_server) { + log_warning("%s: DHCPRelay= cannot be enabled when DHCPServer= is enabled. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } + + if (FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV4)) { + log_warning("%s: DHCPRelay= cannot be enabled when DHCPv4 client is enabled. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } + + if (in4_addr_is_null(&network->manager->dhcp_relay_server_address)) { + log_warning("%s: DHCPRelay= is enabled, but [DHCPRelay] ServerAddress= in networkd.conf is not configured. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } + + Address *a; + ORDERED_HASHMAP_FOREACH(a, network->addresses_by_section) { + assert(!section_is_invalid(a->section)); + + if (a->family != AF_INET) + continue; + + if (in4_addr_is_set(&a->in_addr_peer.in)) + continue; + + if (in4_addr_is_set(&network->dhcp_relay_agent_address_in_addr)) { + if (!in4_addr_equal(&a->in_addr.in, &network->dhcp_relay_agent_address_in_addr)) + continue; + + } else { + if (in4_addr_is_localhost(&a->in_addr.in)) + continue; + + if (in4_addr_is_link_local(&a->in_addr.in)) + continue; + + if (a->scope != RT_SCOPE_UNIVERSE) + continue; + } + + network->dhcp_relay_agent_address = a; + break; + } + + if (!network->dhcp_relay_agent_address) { + if (in4_addr_is_set(&network->dhcp_relay_agent_address_in_addr)) + log_warning("%s: Configured AgentAddress=%s not found among static addresses. Disabling DHCP relay agent.", + network->filename, IN4_ADDR_TO_STRING(&network->dhcp_relay_agent_address_in_addr)); + else + log_warning("%s: DHCPRelay= is enabled, but no suitable static address configured. Disabling DHCP relay agent.", + network->filename); + network->dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID; + return; + } +} + +static int manager_configure_dhcp_relay(Manager *manager) { + int r; + + assert(manager); + assert(manager->event); + + if (manager->dhcp_relay) + return 0; + + if (in4_addr_is_null(&manager->dhcp_relay_server_address)) + return -EADDRNOTAVAIL; + + _cleanup_(sd_dhcp_relay_unrefp) sd_dhcp_relay *relay = NULL; + r = sd_dhcp_relay_new(&relay); + if (r < 0) + return r; + + r = sd_dhcp_relay_attach_event(relay, manager->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return r; + + r = sd_dhcp_relay_set_server_address(relay, &manager->dhcp_relay_server_address); + if (r < 0) + return r; + + if (iovec_is_set(&manager->dhcp_relay_remote_id)) { + r = sd_dhcp_relay_set_remote_id(relay, &manager->dhcp_relay_remote_id); + if (r < 0) + return r; + } else { + sd_id128_t id; + r = sd_id128_get_machine_app_specific(DHCP_RELAY_APP_REMOTE_ID, &id); + if (r < 0) + return r; + + r = sd_dhcp_relay_set_remote_id(relay, &IOVEC_MAKE_STRING(SD_ID128_TO_STRING(id))); + if (r < 0) + return r; + } + + r = sd_dhcp_relay_set_server_identifier_override(relay, manager->dhcp_relay_override_server_id); + if (r < 0) + return r; + + r = dhcp_relay_set_extra_options(relay, &manager->dhcp_relay_extra_options); + if (r < 0) + return r; + + manager->dhcp_relay = TAKE_PTR(relay); + return 0; +} + +static int link_configure_dhcp_relay(Link *link) { + int r; + + assert(link); + assert(link->manager); + assert(link->network); + assert(!link->dhcp_relay_interface); + assert(link->network->dhcp_relay_agent_address); + assert(link->network->dhcp_relay_interface_mode >= 0 && link->network->dhcp_relay_interface_mode < _DHCP_RELAY_INTERFACE_MAX); + + r = manager_configure_dhcp_relay(link->manager); + if (r < 0) + return r; + + bool upstream = link->network->dhcp_relay_interface_mode == DHCP_RELAY_INTERFACE_UPSTREAM; + + _cleanup_(sd_dhcp_relay_interface_unrefp) sd_dhcp_relay_interface *interface = NULL; + r = sd_dhcp_relay_add_interface(link->manager->dhcp_relay, link->ifindex, upstream, &interface); + if (r < 0) + return r; + + r = sd_dhcp_relay_interface_set_ifname(interface, link->ifname); + if (r < 0) + return r; + + r = sd_dhcp_relay_interface_set_address( + interface, + &link->network->dhcp_relay_agent_address->in_addr.in, + link->network->dhcp_relay_agent_address->prefixlen); + if (r < 0) + return r; + + if (upstream) { + r = sd_dhcp_relay_upstream_set_priority(interface, link->network->dhcp_relay_interface_priority); + if (r < 0) + return r; + } else { + if (in4_addr_is_set(&link->network->dhcp_relay_gateway_address)) + r = sd_dhcp_relay_downstream_set_gateway_address(interface, &link->network->dhcp_relay_gateway_address); + else + r = sd_dhcp_relay_downstream_set_gateway_address(interface, &link->network->dhcp_relay_agent_address->in_addr.in); + if (r < 0) + return r; + + if (iovec_is_set(&link->network->dhcp_relay_circuit_id)) + r = sd_dhcp_relay_downstream_set_circuit_id(interface, &link->network->dhcp_relay_circuit_id); + else + r = sd_dhcp_relay_downstream_set_circuit_id(interface, &IOVEC_MAKE_STRING(link->ifname)); + if (r < 0) + return r; + + r = sd_dhcp_relay_downstream_set_virtual_subnet_selection(interface, &link->network->dhcp_relay_vss); + if (r < 0) + return r; + + r = downstream_set_extra_options(interface, &link->network->dhcp_relay_extra_options); + if (r < 0) + return r; + } + + link->dhcp_relay_interface = TAKE_PTR(interface); + + if (link->network->dhcp_relay_interface_mode == DHCP_RELAY_INTERFACE_COMPAT && + !link->dhcp_relay_interface_compat) { + r = sd_dhcp_relay_add_interface( + link->manager->dhcp_relay, + DHCP_RELAY_IFINDEX_UNBOUND, + /* is_upstream= */ true, + &link->dhcp_relay_interface_compat); + if (r < 0) + return r; + } + + return 0; +} + +static bool dhcp_relay_is_ready_to_configure(Link *link) { + assert(link); + assert(link->network); + + if (!link_is_ready_to_configure(link, /* allow_unmanaged= */ false)) + return false; + + if (!link_has_carrier(link)) + return false; + + if (!link->static_addresses_configured) + return false; + + Address *a; + if (address_get(link, link->network->dhcp_relay_agent_address, &a) < 0) + return false; + + if (!address_is_ready(a)) + return false; + + return true; +} + +static int dhcp_relay_process_request(Request *req, Link *link, void *userdata) { + int r; + + assert(link); + + if (!dhcp_relay_is_ready_to_configure(link)) + return 0; + + r = link_configure_dhcp_relay(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to configure DHCP relay agent: %m"); + + log_link_debug(link, "DHCP relay agent is configured."); + + r = link_start_dhcp_relay(link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to start DHCP relay agent: %m"); + + return 1; +} + +int link_request_dhcp_relay(Link *link) { + int r; + + assert(link); + assert(link->manager); + + if (link->manager->state != MANAGER_RUNNING) + return 0; + + if (!link->network) + return 0; + + if (link->network->dhcp_relay_interface_mode < 0) + return 0; + + if (link->dhcp_relay_interface) + return 0; + + r = link_queue_request(link, REQUEST_TYPE_DHCP_RELAY, dhcp_relay_process_request, NULL); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to request configuring of the DHCP relay agent: %m"); + + log_link_debug(link, "Requested configuring of the DHCP relay agent."); + return 0; +} + +int link_start_dhcp_relay(Link *link) { + int r; + + assert(link); + assert(link->manager); + + if (!link->dhcp_relay_interface) + return 0; /* Not configured yet. */ + + if (!link_has_carrier(link)) + return 0; + + r = sd_dhcp_relay_interface_start(link->dhcp_relay_interface); + if (r < 0) + return r; + + if (link->dhcp_relay_interface_compat) { + r = sd_dhcp_relay_interface_start(link->dhcp_relay_interface_compat); + if (r < 0) + return r; + } + + log_link_debug(link, "Relaying DHCPv4 messages."); + return 0; +} + +int link_dhcp_relay_address_dropped(Link *link, const Address *address) { + int r; + + assert(link); + assert(link->manager); + assert(address); + + /* This is called when an address is removed from the interface. */ + + if (link->manager->state != MANAGER_RUNNING) + return 0; + + if (!link->network) + return 0; + + if (!link->dhcp_relay_interface) + return 0; + + if (address->family != AF_INET) + return 0; + + struct in_addr a; + uint8_t prefixlen; + r = sd_dhcp_relay_interface_get_address(link->dhcp_relay_interface, &a, &prefixlen); + if (r <= 0) + return r; + + if (!in4_addr_equal(&address->in_addr.in, &a)) + return 0; + + if (address->prefixlen != prefixlen) + return 0; + + r = sd_dhcp_relay_interface_stop(link->dhcp_relay_interface); + if (r < 0) + return r; + + link->dhcp_relay_interface = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface); + + if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + return 0; + + /* The address may be reconfigured later. Let's reconfigure DHCP relay interface when the address comes back. */ + return link_request_dhcp_relay(link); +} + +static const char * const dhcp_relay_interface_mode_table[_DHCP_RELAY_INTERFACE_MAX] = { + [DHCP_RELAY_INTERFACE_UPSTREAM] = "upstream", + [DHCP_RELAY_INTERFACE_DOWNSTREAM] = "downstream", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(dhcp_relay_interface_mode, DHCPRelayInterfaceMode); + +DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT( + config_parse_dhcp_relay_interface_mode, + dhcp_relay_interface_mode, + DHCPRelayInterfaceMode, + _DHCP_RELAY_INTERFACE_INVALID); diff --git a/src/network/networkd-dhcp-relay.h b/src/network/networkd-dhcp-relay.h new file mode 100644 index 0000000000000..2a676931bd4a7 --- /dev/null +++ b/src/network/networkd-dhcp-relay.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "networkd-forward.h" + +typedef enum DHCPRelayInterfaceMode { + DHCP_RELAY_INTERFACE_UPSTREAM, + DHCP_RELAY_INTERFACE_DOWNSTREAM, + DHCP_RELAY_INTERFACE_COMPAT, + _DHCP_RELAY_INTERFACE_MAX, + _DHCP_RELAY_INTERFACE_INVALID = -EINVAL, +} DHCPRelayInterfaceMode; + +void network_adjust_dhcp_relay(Network *network); + +int link_request_dhcp_relay(Link *link); +int link_start_dhcp_relay(Link *link); +int link_dhcp_relay_address_dropped(Link *link, const Address *address); + +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_relay_interface_mode); diff --git a/src/network/networkd-dhcp-server-bus.c b/src/network/networkd-dhcp-server-bus.c index db2ee37f34be6..40679c288bfb2 100644 --- a/src/network/networkd-dhcp-server-bus.c +++ b/src/network/networkd-dhcp-server-bus.c @@ -4,6 +4,7 @@ #include "alloc-util.h" #include "bus-object.h" +#include "dhcp-server-internal.h" #include "dhcp-server-lease-internal.h" #include "hashmap.h" #include "networkd-dhcp-server-bus.h" @@ -30,9 +31,6 @@ static int property_get_leases( if (!s) return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Link %s has no DHCP server.", l->ifname); - if (sd_dhcp_server_is_in_relay_mode(s)) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Link %s has DHCP relay agent active.", l->ifname); - r = sd_bus_message_open_container(reply, 'a', "(uayayayayt)"); if (r < 0) return r; @@ -58,7 +56,7 @@ static int property_get_leases( if (r < 0) return r; - r = sd_bus_message_append_array(reply, 'y', &lease->chaddr, sizeof(lease->chaddr)); + r = sd_bus_message_append_array(reply, 'y', &lease->hw_addr.bytes, lease->hw_addr.length); if (r < 0) return r; @@ -74,6 +72,40 @@ 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); + + assert(reply); + + uint32_t v = l->dhcp_server ? l->dhcp_server->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); + + assert(reply); + + uint32_t v = l->dhcp_server ? l->dhcp_server->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 +136,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..2ee5a70abc513 100644 --- a/src/network/networkd-dhcp-server.c +++ b/src/network/networkd-dhcp-server.c @@ -7,6 +7,7 @@ #include "conf-parser.h" #include "dhcp-protocol.h" +#include "dhcp-server-internal.h" #include "dhcp-server-lease-internal.h" #include "dns-domain.h" #include "errno-util.h" @@ -15,6 +16,7 @@ #include "fileio.h" #include "hashmap.h" #include "hostname-setup.h" +#include "iovec-util.h" #include "network-common.h" #include "networkd-address.h" #include "networkd-dhcp-server.h" @@ -65,6 +67,31 @@ int network_adjust_dhcp_server(Network *network, Set **addresses) { return 0; } + if (in4_addr_is_set(&network->dhcp_relay_target_address)) { + if (!in4_addr_is_set(&network->manager->dhcp_relay_server_address)) { + /* [DHCPServer] RelayTarget= in .network file is replaced with + * [DHCPRelay] ServerAddress= in networkd.conf. */ + network->manager->dhcp_relay_server_address = network->dhcp_relay_target_address; + + /* [DHCPServer] RelayAgentRemoteId= in .network file is replaced with + * [DHCPRelay] RemoteId= in networkd.conf. */ + if (!iovec_is_set(&network->manager->dhcp_relay_remote_id)) { + iovec_done(&network->manager->dhcp_relay_remote_id); + network->manager->dhcp_relay_remote_id = TAKE_STRUCT(network->dhcp_relay_remote_id); + } + } + + /* Copy [DHCPServer] ServerAddress= to [DHCPRelay] AgentAddress= if unspecified. */ + if (!in4_addr_is_set(&network->dhcp_relay_agent_address_in_addr)) + network->dhcp_relay_agent_address_in_addr = network->dhcp_server_address_in_addr; + + /* Assume this interface acts as a downstream interface of the DHCP relay agent. Also, + * configure a catch-all upstream socket. */ + network->dhcp_relay_interface_mode = DHCP_RELAY_INTERFACE_COMPAT; + network->dhcp_server = false; + return 0; + } + assert(network->dhcp_server_address_prefixlen <= 32); if (network->dhcp_server_address_prefixlen == 0) { @@ -159,9 +186,6 @@ static DHCPServerPersistLeases link_get_dhcp_server_persist_leases(Link *link) { assert(link->manager); assert(link->network); - if (in4_addr_is_set(&link->network->dhcp_server_relay_target)) - return DHCP_SERVER_PERSIST_LEASES_NO; /* On relay mode. Nothing saved in the persistent storage. */ - if (link->network->dhcp_server_persist_leases >= 0) return link->network->dhcp_server_persist_leases; @@ -326,6 +350,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 +480,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; @@ -563,11 +591,9 @@ static int dhcp_server_set_domain(Link *link) { static int dhcp4_server_configure(Link *link) { bool acquired_uplink = false; - sd_dhcp_option *p; DHCPStaticLease *static_lease; Link *uplink = NULL; Address *address; - bool bind_to_interface; int r; assert(link); @@ -677,19 +703,6 @@ static int dhcp4_server_configure(Link *link) { return log_link_error_errno(link, r, "Failed to set router address for DHCP server: %m"); } - r = sd_dhcp_server_set_relay_target(link->dhcp_server, &link->network->dhcp_server_relay_target); - if (r < 0) - return log_link_error_errno(link, r, "Failed to set relay target for DHCP server: %m"); - - bind_to_interface = sd_dhcp_server_is_in_relay_mode(link->dhcp_server) ? false : link->network->dhcp_server_bind_to_interface; - r = sd_dhcp_server_set_bind_to_interface(link->dhcp_server, bind_to_interface); - if (r < 0) - return log_link_error_errno(link, r, "Failed to set interface binding for DHCP server: %m"); - - r = sd_dhcp_server_set_relay_agent_information(link->dhcp_server, link->network->dhcp_server_relay_agent_circuit_id, link->network->dhcp_server_relay_agent_remote_id); - if (r < 0) - return log_link_error_errno(link, r, "Failed to set agent circuit/remote id for DHCP server: %m"); - if (link->network->dhcp_server_emit_timezone) { _cleanup_free_ char *buffer = NULL; const char *tz = NULL; @@ -717,21 +730,13 @@ static int dhcp4_server_configure(Link *link) { else if (r < 0) return log_link_error_errno(link, r, "Failed to set domain name for DHCP server: %m"); - ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_options) { - r = sd_dhcp_server_add_option(link->dhcp_server, p); - if (r == -EEXIST) - continue; - if (r < 0) - return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m"); - } + r = dhcp_server_set_extra_options(link->dhcp_server, &link->network->dhcp_server_extra_options); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set DHCPv4 extra options: %m"); - ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_vendor_options) { - r = sd_dhcp_server_add_vendor_option(link->dhcp_server, p); - if (r == -EEXIST) - continue; - if (r < 0) - return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m"); - } + r = dhcp_server_set_vendor_options(link->dhcp_server, &link->network->dhcp_server_vendor_options); + if (r < 0) + return log_link_error_errno(link, r, "Failed to set DHCPv4 vendor options: %m"); HASHMAP_FOREACH(static_lease, link->network->dhcp_static_leases_by_section) { r = sd_dhcp_server_set_static_lease( @@ -819,38 +824,6 @@ int link_request_dhcp_server(Link *link) { return 0; } -int config_parse_dhcp_server_relay_agent_suboption( - 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) { - - char **suboption_value = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - *suboption_value = mfree(*suboption_value); - return 0; - } - - const char *p = startswith(rvalue, "string:"); - if (!p) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Failed to parse %s=%s'. Invalid format, ignoring.", lvalue, rvalue); - return 0; - } - return free_and_strdup(suboption_value, empty_to_null(p)); -} - int config_parse_dhcp_server_emit( const char *unit, const char *filename, diff --git a/src/network/networkd-dhcp-server.h b/src/network/networkd-dhcp-server.h index e46ad3a8579af..f60ca2d3971ae 100644 --- a/src/network/networkd-dhcp-server.h +++ b/src/network/networkd-dhcp-server.h @@ -18,7 +18,6 @@ int link_request_dhcp_server(Link *link); int link_start_dhcp4_server(Link *link); void manager_toggle_dhcp4_server_state(Manager *manager, bool start); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_relay_agent_suboption); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_emit); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_address); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_ipv6_only_preferred); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index f274a0c4d94a0..468c3eb920b9a 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include #include @@ -1477,7 +1478,6 @@ static bool link_dhcp4_ipv6_only_mode(Link *link) { } static int dhcp4_configure(Link *link) { - sd_dhcp_option *send_option; void *request_options; int r; @@ -1487,10 +1487,14 @@ static int dhcp4_configure(Link *link) { if (link->dhcp_client) return log_link_debug_errno(link, SYNTHETIC_ERRNO(EBUSY), "DHCPv4 client is already configured."); - r = sd_dhcp_client_new(&link->dhcp_client, link->network->dhcp_anonymize); + r = sd_dhcp_client_new(&link->dhcp_client); if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to allocate DHCPv4 client: %m"); + r = sd_dhcp_client_set_anonymize(link->dhcp_client, link->network->dhcp_anonymize); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to anonymize requests: %m"); + r = sd_dhcp_client_set_bootp(link->dhcp_client, link->network->dhcp_use_bootp); if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to %s BOOTP: %m", @@ -1619,21 +1623,13 @@ static int dhcp4_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for '%u': %m", option); } - ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp_client_send_options) { - r = sd_dhcp_client_add_option(link->dhcp_client, send_option); - if (r == -EEXIST) - continue; - if (r < 0) - return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set send option: %m"); - } + r = dhcp_client_set_extra_options(link->dhcp_client, &link->network->dhcp_extra_options); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set extra options: %m"); - ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp_client_send_vendor_options) { - r = sd_dhcp_client_add_vendor_option(link->dhcp_client, send_option); - if (r == -EEXIST) - continue; - if (r < 0) - return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set send option: %m"); - } + r = dhcp_client_set_vendor_options(link->dhcp_client, &link->network->dhcp_vendor_options); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set vendor options: %m"); r = dhcp4_set_hostname(link); if (r < 0) @@ -1652,11 +1648,9 @@ static int dhcp4_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set MUD URL: %m"); } - if (link->network->dhcp_user_class) { - r = sd_dhcp_client_set_user_class(link->dhcp_client, link->network->dhcp_user_class); - if (r < 0) - return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set user class: %m"); - } + r = dhcp_client_set_user_class(link->dhcp_client, &link->network->dhcp_user_class); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set user class: %m"); } if (link->network->dhcp_client_port > 0) { @@ -1677,12 +1671,13 @@ static int dhcp4_configure(Link *link) { } if (link->network->dhcp_ip_service_type >= 0) { - r = sd_dhcp_client_set_service_type(link->dhcp_client, link->network->dhcp_ip_service_type); + assert(link->network->dhcp_ip_service_type <= UINT8_MAX); + r = sd_dhcp_client_set_ip_service_type(link->dhcp_client, link->network->dhcp_ip_service_type); if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set IP service type: %m"); } - if (link->network->dhcp_socket_priority_set) { + if (link->network->dhcp_socket_priority >= 0) { r = sd_dhcp_client_set_socket_priority(link->dhcp_client, link->network->dhcp_socket_priority); if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set socket priority: %m"); @@ -1733,6 +1728,8 @@ int dhcp4_update_mac(Link *link) { } int dhcp4_update_ipv6_connectivity(Link *link) { + int r; + assert(link); if (!link->network) @@ -1744,16 +1741,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 +1806,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); @@ -2004,14 +2007,13 @@ int config_parse_dhcp_socket_priority( void *data, void *userdata) { - Network *network = ASSERT_PTR(data); - int a, r; + int r, a, *priority = ASSERT_PTR(data); assert(lvalue); assert(rvalue); if (isempty(rvalue)) { - network->dhcp_socket_priority_set = false; + *priority = -1; return 0; } @@ -2021,10 +2023,13 @@ int config_parse_dhcp_socket_priority( "Failed to parse socket priority, ignoring: %s", rvalue); return 0; } + if (a < 0 || a > TC_PRIO_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid socket priority, must be in 0…%i, ignoring: %i", TC_PRIO_MAX, a); + return 0; + } - network->dhcp_socket_priority_set = true; - network->dhcp_socket_priority = a; - + *priority = a; return 0; } diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 26ab7ceb52944..799caeb15b312 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(); @@ -360,7 +364,7 @@ static int dhcp6_lease_information_acquired(sd_dhcp6_client *client, Link *link) if (r < 0) return log_link_error_errno(link, r, "Failed to get DHCPv6 lease: %m"); - unref_and_replace_full(link->dhcp6_lease, lease, sd_dhcp6_lease_ref, sd_dhcp6_lease_unref); + unref_and_replace_new_ref(link->dhcp6_lease, lease, sd_dhcp6_lease_ref, sd_dhcp6_lease_unref); link_dirty(link); return 0; diff --git a/src/network/networkd-forward.h b/src/network/networkd-forward.h index 95bd46801e320..40e6380b91563 100644 --- a/src/network/networkd-forward.h +++ b/src/network/networkd-forward.h @@ -4,6 +4,7 @@ /* IWYU pragma: always_keep */ #include "conf-parser-forward.h" /* IWYU pragma: export */ +#include "dhcp-forward.h" /* IWYU pragma: export */ #include "shared-forward.h" /* IWYU pragma: export */ typedef enum NetDevLocalAddressType NetDevLocalAddressType; diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf index 3684a696ff89e..242da72c27ee8 100644 --- a/src/network/networkd-gperf.gperf +++ b/src/network/networkd-gperf.gperf @@ -45,6 +45,10 @@ DHCPv6.UseDomains, config_parse_use_domains, DHCPv6.DUIDType, config_parse_duid_type, 0, offsetof(Manager, dhcp6_duid) DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, dhcp6_duid) DHCPServer.PersistLeases, config_parse_dhcp_server_persist_leases, 0, offsetof(Manager, dhcp_server_persist_leases) +DHCPRelay.ServerAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Manager, dhcp_relay_server_address) +DHCPRelay.OverrideServerIdentifier, config_parse_bool, 0, offsetof(Manager, dhcp_relay_override_server_id) +DHCPRelay.RemoteId, config_parse_dhcp_option, /* check_length= */ true, offsetof(Manager, dhcp_relay_remote_id) +DHCPRelay.ExtraOption, config_parse_dhcp_option_tlv, 0, offsetof(Manager, dhcp_relay_extra_options) /* Deprecated */ DHCP.DUIDType, config_parse_manager_duid_type, 0, 0 DHCP.DUIDRawData, config_parse_manager_duid_rawdata, 0, 0 diff --git a/src/network/networkd-ipv6ll.c b/src/network/networkd-ipv6ll.c index 546229a2e2929..654fbdb6a8ca4 100644 --- a/src/network/networkd-ipv6ll.c +++ b/src/network/networkd-ipv6ll.c @@ -42,6 +42,22 @@ bool link_ipv6ll_enabled(Link *link) { return link->network->link_local & ADDRESS_FAMILY_IPV6; } +bool network_has_static_ipv6ll_address(const Network *network) { + assert(network); + + Address *a; + ORDERED_HASHMAP_FOREACH(a, network->addresses_by_section) { + if (a->family != AF_INET6) + continue; + if (in6_addr_is_set(&a->in_addr_peer.in6)) + continue; + if (in6_addr_is_link_local(&a->in_addr.in6)) + return true; + } + + return false; +} + bool link_ipv6ll_enabled_harder(Link *link) { assert(link); @@ -54,17 +70,7 @@ bool link_ipv6ll_enabled_harder(Link *link) { if (!link->network) return false; - Address *a; - ORDERED_HASHMAP_FOREACH(a, link->network->addresses_by_section) { - if (a->family != AF_INET6) - continue; - if (in6_addr_is_set(&a->in_addr_peer.in6)) - continue; - if (in6_addr_is_link_local(&a->in_addr.in6)) - return true; - } - - return false; + return network_has_static_ipv6ll_address(link->network); } IPv6LinkLocalAddressGenMode link_get_ipv6ll_addrgen_mode(Link *link) { diff --git a/src/network/networkd-ipv6ll.h b/src/network/networkd-ipv6ll.h index 62ce9d957ebd0..73fa51fbef1ce 100644 --- a/src/network/networkd-ipv6ll.h +++ b/src/network/networkd-ipv6ll.h @@ -16,6 +16,7 @@ typedef enum IPv6LinkLocalAddressGenMode { bool link_ipv6ll_enabled(Link *link); bool link_ipv6ll_enabled_harder(Link *link); +bool network_has_static_ipv6ll_address(const Network *network); IPv6LinkLocalAddressGenMode link_get_ipv6ll_addrgen_mode(Link *link); int ipv6ll_addrgen_mode_fill_message(sd_netlink_message *message, IPv6LinkLocalAddressGenMode mode); diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index de1a8829ee94b..137653bd66754 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -6,11 +6,13 @@ #include "sd-dhcp-client.h" #include "sd-dhcp6-client.h" -#include "dhcp-lease-internal.h" +#include "dhcp-lease-internal.h" /* IWYU pragma: keep */ +#include "dhcp-server-internal.h" #include "dhcp-server-lease-internal.h" #include "dhcp6-lease-internal.h" #include "extract-word.h" #include "in-addr-util.h" +#include "iovec-util.h" #include "ip-protocol-list.h" #include "json-util.h" #include "netif-util.h" @@ -26,6 +28,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" @@ -1351,12 +1354,18 @@ static int dhcp_client_lease_append_json(Link *link, sd_json_variant **v) { if (r < 0 && r != -ENODATA) return r; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *m = NULL; + r = dhcp_message_build_json(link->dhcp_lease->message, &m); + if (r < 0) + return r; + r = sd_json_buildo( &w, JSON_BUILD_PAIR_FINITE_USEC("LeaseTimestampUSec", lease_timestamp_usec), JSON_BUILD_PAIR_FINITE_USEC("Timeout1USec", t1), JSON_BUILD_PAIR_FINITE_USEC("Timeout2USec", t2), - JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", hostname)); + JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", hostname), + JSON_BUILD_PAIR_VARIANT_NON_NULL("Message", m)); if (r < 0) return r; @@ -1418,36 +1427,39 @@ static int dhcp_client_private_options_append_json(Link *link, sd_json_variant * if (!link->dhcp_lease) return 0; - LIST_FOREACH(options, option, link->dhcp_lease->private_options) { + for (uint8_t i = SD_DHCP_OPTION_PRIVATE_BASE; i <= SD_DHCP_OPTION_PRIVATE_LAST; i++) { + _cleanup_(iovec_done) struct iovec iov = {}; + r = dhcp_message_get_option_alloc(link->dhcp_lease->message, i, &iov); + if (r == -ENODATA) + continue; + if (r < 0) + return r; 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)); + SD_JSON_BUILD_PAIR_UNSIGNED("Option", i), + SD_JSON_BUILD_PAIR_HEX("PrivateOptionData", iov.iov_base, iov.iov_len)); 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 +1560,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 +1651,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..454db15fdfea0 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); } @@ -107,7 +88,7 @@ int bus_link_method_set_ntp_servers(sd_bus_message *message, void *userdata, sd_ r = bus_verify_polkit_async( message, "org.freedesktop.network1.set-ntp-servers", - /* details= */ NULL, + (const char**) STRV_MAKE("interface", l->ifname), &l->manager->polkit_registry, error); if (r < 0) @@ -143,7 +124,7 @@ static int bus_link_method_set_dns_servers_internal(sd_bus_message *message, voi r = bus_verify_polkit_async( message, "org.freedesktop.network1.set-dns-servers", - /* details= */ NULL, + (const char**) STRV_MAKE("interface", l->ifname), &l->manager->polkit_registry, error); if (r < 0) @@ -244,7 +225,7 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_ r = bus_verify_polkit_async( message, "org.freedesktop.network1.set-domains", - /* details= */ NULL, + (const char**) STRV_MAKE("interface", l->ifname), &l->manager->polkit_registry, error); if (r < 0) @@ -281,7 +262,7 @@ int bus_link_method_set_default_route(sd_bus_message *message, void *userdata, s r = bus_verify_polkit_async( message, "org.freedesktop.network1.set-default-route", - /* details= */ NULL, + (const char**) STRV_MAKE("interface", l->ifname), &l->manager->polkit_registry, error); if (r < 0) @@ -327,7 +308,7 @@ int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_er r = bus_verify_polkit_async( message, "org.freedesktop.network1.set-llmnr", - /* details= */ NULL, + (const char**) STRV_MAKE("interface", l->ifname), &l->manager->polkit_registry, error); if (r < 0) @@ -373,7 +354,7 @@ int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_err r = bus_verify_polkit_async( message, "org.freedesktop.network1.set-mdns", - /* details= */ NULL, + (const char**) STRV_MAKE("interface", l->ifname), &l->manager->polkit_registry, error); if (r < 0) @@ -419,7 +400,7 @@ int bus_link_method_set_dns_over_tls(sd_bus_message *message, void *userdata, sd r = bus_verify_polkit_async( message, "org.freedesktop.network1.set-dns-over-tls", - /* details= */ NULL, + (const char**) STRV_MAKE("interface", l->ifname), &l->manager->polkit_registry, error); if (r < 0) @@ -465,7 +446,7 @@ int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_e r = bus_verify_polkit_async( message, "org.freedesktop.network1.set-dnssec", - /* details= */ NULL, + (const char**) STRV_MAKE("interface", l->ifname), &l->manager->polkit_registry, error); if (r < 0) @@ -523,7 +504,7 @@ int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, v r = bus_verify_polkit_async( message, "org.freedesktop.network1.set-dnssec-negative-trust-anchors", - /* details= */ NULL, + (const char**) STRV_MAKE("interface", l->ifname), &l->manager->polkit_registry, error); if (r < 0) @@ -553,7 +534,7 @@ int bus_link_method_revert_ntp(sd_bus_message *message, void *userdata, sd_bus_e r = bus_verify_polkit_async( message, "org.freedesktop.network1.revert-ntp", - /* details= */ NULL, + (const char**) STRV_MAKE("interface", l->ifname), &l->manager->polkit_registry, error); if (r < 0) return r; @@ -582,7 +563,7 @@ int bus_link_method_revert_dns(sd_bus_message *message, void *userdata, sd_bus_e r = bus_verify_polkit_async( message, "org.freedesktop.network1.revert-dns", - /* details= */ NULL, + (const char**) STRV_MAKE("interface", l->ifname), &l->manager->polkit_registry, error); if (r < 0) @@ -611,7 +592,7 @@ int bus_link_method_force_renew(sd_bus_message *message, void *userdata, sd_bus_ r = bus_verify_polkit_async( message, "org.freedesktop.network1.forcerenew", - /* details= */ NULL, + (const char**) STRV_MAKE("interface", l->ifname), &l->manager->polkit_registry, error); if (r < 0) @@ -640,7 +621,7 @@ int bus_link_method_renew(sd_bus_message *message, void *userdata, sd_bus_error r = bus_verify_polkit_async( message, "org.freedesktop.network1.renew", - /* details= */ NULL, + (const char**) STRV_MAKE("interface", l->ifname), &l->manager->polkit_registry, error); if (r < 0) @@ -664,7 +645,7 @@ int bus_link_method_reconfigure(sd_bus_message *message, void *userdata, sd_bus_ r = bus_verify_polkit_async( message, "org.freedesktop.network1.reconfigure", - /* details= */ NULL, + (const char**) STRV_MAKE("interface", l->ifname), &l->manager->polkit_registry, error); if (r < 0) @@ -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. */ @@ -874,8 +855,7 @@ int link_object_find(sd_bus *bus, const char *path, const char *interface, void if (r < 0) return 0; - if (streq(interface, "org.freedesktop.network1.DHCPServer") && - (!link->dhcp_server || sd_dhcp_server_is_in_relay_mode(link->dhcp_server))) + if (streq(interface, "org.freedesktop.network1.DHCPServer") && !link->dhcp_server) return 0; if (streq(interface, "org.freedesktop.network1.DHCPv4Client") && !link->dhcp_client) diff --git a/src/network/networkd-link-varlink.c b/src/network/networkd-link-varlink.c index ad7386dabff05..02d25a757e0b6 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; @@ -80,7 +104,9 @@ static int vl_method_link_up_or_down(sd_varlink *vlink, sd_json_variant *paramet vlink, manager->bus, "org.freedesktop.network1.manage-links", - /* details= */ NULL, + (const char**) STRV_MAKE( + "interface", link->ifname, + "verb", up ? "up" : "down"), &manager->polkit_registry); if (r <= 0) return r; @@ -100,3 +126,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", + (const char**) STRV_MAKE("interface", link->ifname), + &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", + (const char**) STRV_MAKE("interface", link->ifname), + &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", + (const char**) STRV_MAKE("interface", link->ifname), + &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..f505031e1e1b7 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -10,6 +10,7 @@ #include "sd-bus.h" #include "sd-dhcp-client.h" +#include "sd-dhcp-relay.h" #include "sd-dhcp-server.h" #include "sd-dhcp6-client.h" #include "sd-dhcp6-lease.h" @@ -18,16 +19,17 @@ #include "sd-ndisc.h" #include "sd-netlink.h" #include "sd-radv.h" +#include "sd-varlink.h" #include "alloc-util.h" #include "arphrd-util.h" #include "bitfield.h" +#include "device-private.h" #include "device-util.h" #include "dns-domain.h" #include "errno-util.h" #include "ethtool-util.h" #include "event-util.h" -#include "format-ifname.h" #include "fs-util.h" #include "glyph-util.h" #include "logarithm.h" @@ -39,6 +41,7 @@ #include "networkd-bridge-mdb.h" #include "networkd-bridge-vlan.h" #include "networkd-dhcp-prefix-delegation.h" +#include "networkd-dhcp-relay.h" #include "networkd-dhcp-server.h" #include "networkd-dhcp4.h" #include "networkd-dhcp6.h" @@ -65,7 +68,6 @@ #include "networkd-wifi.h" #include "networkd-wwan-bus.h" #include "ordered-set.h" -#include "parse-util.h" #include "set.h" #include "socket-util.h" #include "string-table.h" @@ -239,6 +241,8 @@ static void link_free_engines(Link *link) { if (!link) return; + link->dhcp_relay_interface = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface); + link->dhcp_relay_interface_compat = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface_compat); link->dhcp_server = sd_dhcp_server_unref(link->dhcp_server); link->dhcp_client = sd_dhcp_client_unref(link->dhcp_client); @@ -287,7 +291,6 @@ static Link* link_free(Link *link) { free(link->previous_ssid); free(link->driver); - unlink_and_free(link->lease_file); unlink_and_free(link->state_file); sd_device_unref(link->dev); @@ -424,6 +427,14 @@ int link_stop_engines(Link *link, bool may_keep_dynamic) { ndisc_flush(link); } + r = sd_dhcp_relay_interface_stop(link->dhcp_relay_interface); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCP relay agent: %m")); + + r = sd_dhcp_relay_interface_stop(link->dhcp_relay_interface_compat); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCP relay agent (compat): %m")); + r = sd_dhcp_server_stop(link->dhcp_server); if (r < 0) RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv4 server: %m")); @@ -739,6 +750,10 @@ static int link_acquire_dynamic_ipv4_conf(Link *link) { log_link_debug(link, "Acquiring IPv4 link-local address."); } + r = link_start_dhcp_relay(link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not start DHCP relay agent: %m"); + r = link_start_dhcp4_server(link); if (r < 0) return log_link_warning_errno(link, r, "Could not start DHCP server: %m"); @@ -813,9 +828,48 @@ int link_ipv6ll_gained(Link *link) { return 0; } +int link_ipv6ll_lost(Link *link, const struct in6_addr *dropped_ipv6ll, bool has_replacement) { + int ret = 0, r; + + assert(link); + assert(dropped_ipv6ll); + + if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + return 0; + + log_link_info(link, "Lost IPv6LL address %s%s.", + IN6_ADDR_TO_STRING(dropped_ipv6ll), + has_replacement ? ", switching to alternate IPv6LL source" : ""); + + r = sd_dhcp6_client_stop(link->dhcp6_client); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv6 client: %m")); + + /* DHCPv6 must be restarted to switch the client's source address, while NDisc and + * RADV can switch to the replacement IPv6LL in link_ipv6ll_gained() without flushing + * learned state. Keep link->ndisc_configured as-is in this path. */ + if (has_replacement) + return ret; + + r = ndisc_stop(link); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv6 Router Discovery: %m")); + link->ndisc_configured = false; + ndisc_flush(link); + + r = sd_radv_stop(link->radv); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv6 Router Advertisement: %m")); + + r = link_request_stacked_netdevs(link, NETDEV_LOCAL_ADDRESS_IPV6LL); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not reconfigure stacked netdevs after IPv6LL loss: %m")); + + return ret; +} + int link_handle_bound_to_list(Link *link) { bool required_up = false; - bool link_is_up = false; Link *l; assert(link); @@ -826,18 +880,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 +916,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); @@ -1098,14 +1150,14 @@ static Link *link_drop(Link *link) { link_clean(link); STRV_FOREACH(n, link->alternative_names) - hashmap_remove(link->manager->links_by_name, *n); - hashmap_remove(link->manager->links_by_name, link->ifname); + hashmap_remove_value(link->manager->links_by_name, *n, link); + hashmap_remove_value(link->manager->links_by_name, link->ifname, link); /* bonding master and its slaves have the same hardware address. */ hashmap_remove_value(link->manager->links_by_hw_addr, &link->hw_addr, link); /* The following must be called at last. */ - assert_se(hashmap_remove(link->manager->links_by_index, INT_TO_PTR(link->ifindex)) == link); + hashmap_remove_value(link->manager->links_by_index, INT_TO_PTR(link->ifindex), link); if (notify) manager_notify_hook_filters(link->manager); @@ -1159,6 +1211,8 @@ static int link_drop_dynamic_config(Link *link, Network *network) { RET_GATHER(r, link_drop_dhcp4_config(link, network)); RET_GATHER(r, link_drop_dhcp6_config(link, network)); RET_GATHER(r, link_drop_dhcp_pd_config(link, network)); + link->dhcp_relay_interface = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface); + link->dhcp_relay_interface_compat = sd_dhcp_relay_interface_unref(link->dhcp_relay_interface_compat); link->dhcp_server = sd_dhcp_server_unref(link->dhcp_server); link->lldp_rx = sd_lldp_rx_unref(link->lldp_rx); /* TODO: keep the received neighbors. */ link->lldp_tx = sd_lldp_tx_unref(link->lldp_tx); @@ -1277,6 +1331,10 @@ static int link_configure(Link *link) { if (r < 0) return r; + r = link_request_dhcp_relay(link); + if (r < 0) + return r; + r = link_request_dhcp_server(link); if (r < 0) return r; @@ -1337,13 +1395,9 @@ static int link_get_network(Link *link, Network **ret) { continue; if (network->match.ifname && link->dev) { - uint8_t name_assign_type = NET_NAME_UNKNOWN; - const char *attr; - - if (sd_device_get_sysattr_value(link->dev, "name_assign_type", &attr) >= 0) - (void) safe_atou8(attr, &name_assign_type); - - warn = name_assign_type == NET_NAME_ENUM; + uint8_t name_assign_type; + if (device_get_sysattr_u8(link->dev, "name_assign_type", &name_assign_type) >= 0) + warn = name_assign_type == NET_NAME_ENUM; } log_link_full(link, warn ? LOG_WARNING : LOG_DEBUG, @@ -1513,6 +1567,7 @@ typedef struct LinkReconfigurationData { Link *link; LinkReconfigurationFlag flags; sd_bus_message *message; + sd_varlink *varlink; unsigned *counter; } LinkReconfigurationData; @@ -1522,6 +1577,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 +1588,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 +1634,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 +1642,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 +1661,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, }; @@ -1670,7 +1734,7 @@ static int link_initialized(Link *link, sd_device *device) { /* Always replace with the new sd_device object. As the sysname (and possibly other properties * or sysattrs) may be outdated. */ - device_unref_and_replace(link->dev, device); + device_unref_and_replace_new_ref(link->dev, device); r = link_managed_by_us(link); if (r <= 0) @@ -2012,7 +2076,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 +2211,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 +2295,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 +2305,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; @@ -2524,8 +2593,21 @@ static int link_update_alternative_names(Link *link, sd_netlink_message *message if (strv_equal(altnames, link->alternative_names)) return 0; + /* See the comment in link_update_name(). If one of the new alternative names is already in use by + * another interface, the alternative-name information in this message is likely stale. Ignore the + * entire IFLA_ALT_IFNAME attribute in that case. */ + STRV_FOREACH(n, altnames) { + Link *existing; + if (link_get_by_name(link->manager, *n, &existing) >= 0 && existing != link) { + log_link_debug(link, + "Alternative interface name update detected, but the new name '%s' is already in use by another interface (ifindex=%i), ignoring the update as it may be stale.", + *n, existing->ifindex); + return 0; + } + } + STRV_FOREACH(n, link->alternative_names) - hashmap_remove(link->manager->links_by_name, *n); + hashmap_remove_value(link->manager->links_by_name, *n, link); strv_free_and_replace(link->alternative_names, altnames); @@ -2539,7 +2621,6 @@ static int link_update_alternative_names(Link *link, sd_netlink_message *message } static int link_update_name(Link *link, sd_netlink_message *message) { - char ifname_from_index[IF_NAMESIZE]; const char *ifname; int r; @@ -2556,20 +2637,50 @@ static int link_update_name(Link *link, sd_netlink_message *message) { if (streq(ifname, link->ifname)) return 0; - r = format_ifname(link->ifindex, ifname_from_index); - if (r < 0) - return log_link_debug_errno(link, r, "Could not get interface name for index %i.", link->ifindex); - - if (!streq(ifname, ifname_from_index)) { - log_link_debug(link, "New interface name '%s' received from the kernel does not correspond " - "with the name currently configured on the actual interface '%s'. Ignoring.", - ifname, ifname_from_index); + /* Check if the new interface name is already used by another interface. If so, the rename is likely + * stale. Consider the following race: + * + * 1. networkd enables rtnl matches in manager_connect_rtnl(). + * 2. The kernel sends an RTM_NEWLINK notification for an interface (say, ifindex=2, ifname="eth0"), + * and the notification message (A) is queued in networkd's sd-netlink object. + * 3. The interface is renamed (say, "eth0" -> "enp0"), e.g. by udevd. The kernel sends another + * RTM_NEWLINK notification for the rename, and the notification message (B) is also queued in + * networkd's sd-netlink object. + * 4. The kernel detects another new interface (say, ifindex=3). Since the name "eth0" is now unused, + * the interface is named "eth0". + * 5. networkd enumerates links and creates Link objects for: + * - ifindex=2, ifname="enp0" + * - ifindex=3, ifname="eth0" + * 6. After enumeration, when processing message (A), networkd becomes confused and thinks that the + * interface with ifindex=2 was renamed from "enp0" to "eth0". However, it fails to update the + * Manager.links_by_name hashmap because "eth0" is already used by the interface with ifindex=3. + * 7. When processing message (B), networkd thinks that the interface with ifindex=2 has been renamed + * again from "eth0" to "enp0", and renames the Link object back to "enp0". + * + * When this happens, we get something like the following: + * + * systemd-networkd[5164]: enp0: Interface name change detected, renamed to eth0. + * systemd-networkd[5164]: eth0: Failed to manage link by its new name: File exists + * systemd-networkd[5164]: Could not process link message: File exists + * systemd-networkd[5164]: eth0: Failed + * systemd-networkd[5164]: eth0: State changed: initialized -> failed + * systemd-networkd[5164]: eth0: Interface name change detected, renamed to enp0. + * + * See also #20203. + * + * To avoid the race, ignore the rename. A subsequent RTM_NEWLINK message should eventually provide + * the current interface name, e.g. message (B) above. */ + Link *existing; + if (link_get_by_name(link->manager, ifname, &existing) >= 0 && existing != link) { + log_link_debug(link, + "Interface name change detected, but the new interface name '%s' is already in use by another interface (ifindex=%i), ignoring the rename as it may be stale.", + ifname, existing->ifindex); return 0; } log_link_info(link, "Interface name change detected, renamed to %s.", ifname); - hashmap_remove(link->manager->links_by_name, link->ifname); + hashmap_remove_value(link->manager->links_by_name, link->ifname, link); r = free_and_strdup(&link->ifname, ifname); if (r < 0) @@ -2597,6 +2708,12 @@ static int link_update_name(Link *link, sd_netlink_message *message) { return log_link_debug_errno(link, r, "Failed to update interface name in NDisc: %m"); } + if (link->dhcp_relay_interface) { + r = sd_dhcp_relay_interface_set_ifname(link->dhcp_relay_interface, link->ifname); + if (r < 0) + return log_link_debug_errno(link, r, "Failed to update interface name in DHCP relay interface: %m"); + } + if (link->dhcp_server) { r = sd_dhcp_server_set_ifname(link->dhcp_server, link->ifname); if (r < 0) @@ -2700,7 +2817,7 @@ static Link *link_drop_or_unref(Link *link) { DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_drop_or_unref); static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { - _cleanup_free_ char *ifname = NULL, *kind = NULL, *state_file = NULL, *lease_file = NULL; + _cleanup_free_ char *ifname = NULL, *kind = NULL, *state_file = NULL; _cleanup_(link_drop_or_unrefp) Link *link = NULL; unsigned short iftype; int r, ifindex; @@ -2738,9 +2855,6 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { /* Do not update state files when running in test mode. */ if (asprintf(&state_file, "/run/systemd/netif/links/%d", ifindex) < 0) return log_oom_debug(); - - if (asprintf(&lease_file, "/run/systemd/netif/leases/%d", ifindex) < 0) - return log_oom_debug(); } link = new(Link, 1); @@ -2762,7 +2876,6 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { .ipv6ll_address_gen_mode = _IPV6_LINK_LOCAL_ADDRESS_GEN_MODE_INVALID, .state_file = TAKE_PTR(state_file), - .lease_file = TAKE_PTR(lease_file), .n_dns = UINT_MAX, .dns_default_route = -1, diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index c5b9421bc0b2b..4c69ce7d027dc 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -117,7 +117,6 @@ typedef struct Link { sd_dhcp_client *dhcp_client; sd_dhcp_lease *dhcp_lease; - char *lease_file; unsigned dhcp4_messages; bool dhcp4_configured; char *dhcp4_6rd_tunnel_name; @@ -144,6 +143,8 @@ typedef struct Link { bool bridge_vlan_set:1; bool bearer_configured:1; + sd_dhcp_relay_interface *dhcp_relay_interface; + sd_dhcp_relay_interface *dhcp_relay_interface_compat; sd_dhcp_server *dhcp_server; sd_ndisc *ndisc; @@ -229,10 +230,12 @@ 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); int link_ipv6ll_gained(Link *link); +int link_ipv6ll_lost(Link *link, const struct in6_addr *dropped_ipv6ll, bool has_replacement); bool link_has_ipv6_connectivity(Link *link); int link_stop_engines(Link *link, bool may_keep_dynamic); @@ -242,9 +245,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..63ba0c166fa83 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -5,6 +5,7 @@ #include #include "sd-bus.h" +#include "sd-dhcp-relay.h" #include "sd-event.h" #include "sd-netlink.h" #include "sd-resolve.h" @@ -25,6 +26,7 @@ #include "errno-util.h" #include "fd-util.h" #include "initrd-util.h" +#include "iovec-util.h" #include "mount-util.h" #include "netlink-internal.h" #include "netlink-util.h" @@ -533,7 +535,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 +679,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; @@ -699,6 +703,7 @@ int manager_new(Manager **ret, bool test_mode) { .dhcp_duid.type = DUID_TYPE_EN, .dhcp6_duid.type = DUID_TYPE_EN, .duid_product_uuid.type = DUID_TYPE_UUID, + .dhcp_relay_extra_options = TLV_INIT(TLV_DHCP4_SUBOPTION), .dhcp_server_persist_leases = DHCP_SERVER_PERSIST_LEASES_YES, .serialization_fd = -EBADF, .ip_forwarding = { -1, -1, }, @@ -758,6 +763,10 @@ Manager* manager_free(Manager *m) { sd_netlink_unref(m->nfnl); sd_resolve_unref(m->resolve); + iovec_done(&m->dhcp_relay_remote_id); + tlv_done(&m->dhcp_relay_extra_options); + sd_dhcp_relay_unref(m->dhcp_relay); + m->routes = set_free(m->routes); m->nexthops_by_id = hashmap_free(m->nexthops_by_id); @@ -1257,11 +1266,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 +1289,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..2e1b6430907ee 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -3,6 +3,7 @@ #include "networkd-forward.h" #include "networkd-network.h" +#include "tlv-util.h" typedef enum ManagerState { MANAGER_RUNNING, @@ -74,6 +75,13 @@ typedef struct Manager { bool has_product_uuid; bool product_uuid_requested; + /* DHCP relay agent */ + sd_dhcp_relay *dhcp_relay; + struct in_addr dhcp_relay_server_address; + bool dhcp_relay_override_server_id; + struct iovec dhcp_relay_remote_id; + TLV dhcp_relay_extra_options; + char* dynamic_hostname; char* dynamic_timezone; @@ -151,7 +159,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..08bb2bfd1225a 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -17,6 +17,7 @@ #include "networkd-address.h" #include "networkd-address-generation.h" #include "networkd-dhcp6.h" +#include "networkd-ipv6ll.h" #include "networkd-link.h" #include "networkd-manager.h" #include "networkd-ndisc.h" @@ -91,7 +92,8 @@ bool link_ndisc_enabled(Link *link) { void network_adjust_ndisc(Network *network) { assert(network); - if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) { + if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6) && + !network_has_static_ipv6ll_address(network)) { if (network->ndisc > 0) log_warning("%s: IPv6AcceptRA= is enabled but IPv6 link-local addressing is disabled or not supported. " "Disabling IPv6AcceptRA=.", network->filename); @@ -1909,6 +1911,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(); @@ -2239,7 +2243,7 @@ static int sd_dns_resolver_copy(const sd_dns_resolver *a, sd_dns_resolver *b) { /* addrs, n_addrs */ c.addrs = newdup(union in_addr_union, a->addrs, a->n_addrs); if (!c.addrs) - return r; + return -ENOMEM; c.n_addrs = a->n_addrs; /* dohpath */ diff --git a/src/network/networkd-neighbor.c b/src/network/networkd-neighbor.c index e4a44666b4810..6e2c9f476af3f 100644 --- a/src/network/networkd-neighbor.c +++ b/src/network/networkd-neighbor.c @@ -268,7 +268,7 @@ static void neighbor_forget(Link *link, Neighbor *neighbor, const char *msg) { return; neighbor_enter_removed(neighbor); - log_neighbor_debug(neighbor, "Forgetting", link); + log_neighbor_debug(neighbor, msg, link); neighbor_detach(neighbor); } diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 10566b7a4ed85..0b598f131d9a5 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -94,6 +94,7 @@ Match.Version, config_parse_net_condition, Match.Credential, config_parse_net_condition, CONDITION_CREDENTIAL, offsetof(Network, conditions) Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, conditions) Match.Firmware, config_parse_net_condition, CONDITION_FIRMWARE, offsetof(Network, conditions) +Match.MachineTag, config_parse_net_condition, CONDITION_MACHINE_TAG, offsetof(Network, conditions) Link.MACAddress, config_parse_hw_addr, 0, offsetof(Network, hw_addr) Link.MTUBytes, config_parse_mtu, AF_UNSPEC, offsetof(Network, mtu) Link.Group, config_parse_link_group, 0, 0 @@ -132,6 +133,7 @@ Network.VLAN, config_parse_stacked_netdev, Network.VXLAN, config_parse_stacked_netdev, NETDEV_KIND_VXLAN, offsetof(Network, stacked_netdev_names) Network.Xfrm, config_parse_stacked_netdev, NETDEV_KIND_XFRM, offsetof(Network, stacked_netdev_names) Network.DHCP, config_parse_dhcp, 0, offsetof(Network, dhcp) +Network.DHCPRelay, config_parse_dhcp_relay_interface_mode, 0, offsetof(Network, dhcp_relay_interface_mode) Network.DHCPServer, config_parse_bool, 0, offsetof(Network, dhcp_server) Network.LinkLocalAddressing, config_parse_link_local_address_family, 0, offsetof(Network, link_local) Network.IPv6LinkLocalAddressGenerationMode, config_parse_ipv6_link_local_address_gen_mode, 0, offsetof(Network, ipv6ll_address_gen_mode) @@ -168,6 +170,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) @@ -277,12 +280,12 @@ DHCPv4.RequestBroadcast, config_parse_tristate, DHCPv4.VendorClassIdentifier, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_vendor_class_identifier) DHCPv4.MUDURL, config_parse_mud_url, 0, offsetof(Network, dhcp_mudurl) DHCPv4.MaxAttempts, config_parse_dhcp_max_attempts, 0, 0 -DHCPv4.UserClass, config_parse_dhcp_user_or_vendor_class, AF_INET, offsetof(Network, dhcp_user_class) +DHCPv4.UserClass, config_parse_dhcp4_user_class, 0, offsetof(Network, dhcp_user_class) DHCPv4.IAID, config_parse_iaid, AF_INET, 0 DHCPv4.DUIDType, config_parse_network_duid_type, 0, 0 DHCPv4.DUIDRawData, config_parse_network_duid_rawdata, 0, 0 DHCPv4.RouteMetric, config_parse_dhcp_route_metric, AF_INET, 0 -DHCPv4.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET, 0 +DHCPv4.RouteTable, config_parse_dhcp_or_ra_route_table, NETWORK_CONFIG_SOURCE_DHCP4, 0 DHCPv4.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) DHCPv4.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port) DHCPv4.ServerPort, config_parse_uint16, 0, offsetof(Network, dhcp_port) @@ -291,9 +294,9 @@ DHCPv4.SendDecline, config_parse_bool, DHCPv4.DenyList, config_parse_in_addr_prefixes, AF_INET, offsetof(Network, dhcp_deny_listed_ip) DHCPv4.AllowList, config_parse_in_addr_prefixes, AF_INET, offsetof(Network, dhcp_allow_listed_ip) DHCPv4.IPServiceType, config_parse_dhcp_ip_service_type, 0, offsetof(Network, dhcp_ip_service_type) -DHCPv4.SocketPriority, config_parse_dhcp_socket_priority, 0, 0 -DHCPv4.SendOption, config_parse_dhcp_send_option, AF_INET, offsetof(Network, dhcp_client_send_options) -DHCPv4.SendVendorOption, config_parse_dhcp_send_option, 0, offsetof(Network, dhcp_client_send_vendor_options) +DHCPv4.SocketPriority, config_parse_dhcp_socket_priority, 0, offsetof(Network, dhcp_socket_priority) +DHCPv4.SendOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_extra_options) +DHCPv4.SendVendorOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_vendor_options) DHCPv4.RouteMTUBytes, config_parse_mtu, AF_INET, offsetof(Network, dhcp_route_mtu) DHCPv4.InitialCongestionWindow, config_parse_tcp_window, 0, offsetof(Network, dhcp_initial_congestion_window) DHCPv4.InitialAdvertisedReceiveWindow, config_parse_tcp_window, 0, offsetof(Network, dhcp_advertised_receive_window) @@ -317,13 +320,13 @@ DHCPv6.MUDURL, config_parse_mud_url, DHCPv6.SendHostname, config_parse_dhcp_send_hostname, AF_INET6, 0 DHCPv6.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp6_hostname) DHCPv6.RequestOptions, config_parse_dhcp_request_options, AF_INET6, 0 -DHCPv6.UserClass, config_parse_dhcp_user_or_vendor_class, AF_INET6, offsetof(Network, dhcp6_user_class) -DHCPv6.VendorClass, config_parse_dhcp_user_or_vendor_class, AF_INET6, offsetof(Network, dhcp6_vendor_class) -DHCPv6.SendVendorOption, config_parse_dhcp_send_option, AF_INET6, offsetof(Network, dhcp6_client_send_vendor_options) +DHCPv6.UserClass, config_parse_dhcp6_user_or_vendor_class, 0, offsetof(Network, dhcp6_user_class) +DHCPv6.VendorClass, config_parse_dhcp6_user_or_vendor_class, 0, offsetof(Network, dhcp6_vendor_class) +DHCPv6.SendVendorOption, config_parse_dhcp6_send_option, 0, offsetof(Network, dhcp6_client_send_vendor_options) DHCPv6.PrefixDelegationHint, config_parse_dhcp6_pd_prefix_hint, 0, 0 DHCPv6.UnassignedSubnetPolicy, config_parse_dhcp_pd_prefix_route_type, 0, offsetof(Network, dhcp6_pd_prefix_route_type) DHCPv6.WithoutRA, config_parse_dhcp6_client_start_mode, 0, offsetof(Network, dhcp6_client_start_mode) -DHCPv6.SendOption, config_parse_dhcp_send_option, AF_INET6, offsetof(Network, dhcp6_client_send_options) +DHCPv6.SendOption, config_parse_dhcp6_send_option, 0, offsetof(Network, dhcp6_client_send_options) DHCPv6.IAID, config_parse_iaid, AF_INET6, 0 DHCPv6.DUIDType, config_parse_duid_type, 0, offsetof(Network, dhcp6_duid) DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, dhcp6_duid) @@ -331,6 +334,7 @@ DHCPv6.RapidCommit, config_parse_bool, DHCPv6.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp6_netlabel) DHCPv6.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp6_send_release) DHCPv6.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, dhcp6_nft_set_context) +DHCPv6.RouteTable, config_parse_dhcp_or_ra_route_table, NETWORK_CONFIG_SOURCE_DHCP6, 0 IPv6AcceptRA.UseRedirect, config_parse_bool, 0, offsetof(Network, ndisc_use_redirect) IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ndisc_use_gateway) IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_route_prefix) @@ -345,7 +349,7 @@ IPv6AcceptRA.UseHopLimit, config_parse_bool, IPv6AcceptRA.UseReachableTime, config_parse_bool, 0, offsetof(Network, ndisc_use_reachable_time) IPv6AcceptRA.UseRetransmissionTime, config_parse_bool, 0, offsetof(Network, ndisc_use_retransmission_time) IPv6AcceptRA.DHCPv6Client, config_parse_ndisc_start_dhcp6_client, 0, offsetof(Network, ndisc_start_dhcp6_client) -IPv6AcceptRA.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET6, 0 +IPv6AcceptRA.RouteTable, config_parse_dhcp_or_ra_route_table, NETWORK_CONFIG_SOURCE_NDISC, 0 IPv6AcceptRA.RouteMetric, config_parse_ndisc_route_metric, 0, 0 IPv6AcceptRA.QuickAck, config_parse_bool, 0, offsetof(Network, ndisc_quickack) IPv6AcceptRA.UseCaptivePortal, config_parse_bool, 0, offsetof(Network, ndisc_use_captive_portal) @@ -358,11 +362,17 @@ IPv6AcceptRA.RouteDenyList, config_parse_in_addr_prefixes, IPv6AcceptRA.Token, config_parse_address_generation_type, 0, offsetof(Network, ndisc_tokens) IPv6AcceptRA.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, ndisc_netlabel) IPv6AcceptRA.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, ndisc_nft_set_context) +DHCPRelay.AgentAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_relay_agent_address_in_addr) +DHCPRelay.GatewayAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_relay_gateway_address) +DHCPRelay.CircuitId, config_parse_dhcp_option, /* check_length= */ true, offsetof(Network, dhcp_relay_circuit_id) +DHCPRelay.VirtualSubnetSelection, config_parse_dhcp_option, /* check_length= */ true, offsetof(Network, dhcp_relay_vss) +DHCPRelay.ExtraOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_relay_extra_options) +DHCPRelay.InterfacePriority, config_parse_int, 0, offsetof(Network, dhcp_relay_interface_priority) DHCPServer.ServerAddress, config_parse_dhcp_server_address, 0, 0 DHCPServer.UplinkInterface, config_parse_uplink, 0, 0 -DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_relay_target) -DHCPServer.RelayAgentCircuitId, config_parse_dhcp_server_relay_agent_suboption, 0, offsetof(Network, dhcp_server_relay_agent_circuit_id) -DHCPServer.RelayAgentRemoteId, config_parse_dhcp_server_relay_agent_suboption, 0, offsetof(Network, dhcp_server_relay_agent_remote_id) +DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_relay_target_address) /* deprecated */ +DHCPServer.RelayAgentCircuitId, config_parse_dhcp_option, /* check_length= */ true, offsetof(Network, dhcp_relay_circuit_id) /* deprecated */ +DHCPServer.RelayAgentRemoteId, config_parse_dhcp_option, /* check_length= */ true, offsetof(Network, dhcp_relay_remote_id) /* deprecated */ DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec) DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec) DHCPServer.IPv6OnlyPreferredSec, config_parse_dhcp_server_ipv6_only_preferred, 0, offsetof(Network, dhcp_server_ipv6_only_preferred_usec) @@ -386,9 +396,9 @@ DHCPServer.EmitDomain, config_parse_bool, DHCPServer.Domain, config_parse_dns_name, 0, offsetof(Network, dhcp_server_domain) DHCPServer.PoolOffset, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_offset) DHCPServer.PoolSize, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_size) -DHCPServer.SendVendorOption, config_parse_dhcp_send_option, 0, offsetof(Network, dhcp_server_send_vendor_options) -DHCPServer.SendOption, config_parse_dhcp_send_option, 0, offsetof(Network, dhcp_server_send_options) -DHCPServer.BindToInterface, config_parse_bool, 0, offsetof(Network, dhcp_server_bind_to_interface) +DHCPServer.SendOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_server_extra_options) +DHCPServer.SendVendorOption, config_parse_dhcp_option_tlv, 0, offsetof(Network, dhcp_server_vendor_options) +DHCPServer.BindToInterface, config_parse_warn_compat, DISABLED_LEGACY, 0 DHCPServer.BootServerAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_boot_server_address) DHCPServer.BootServerName, config_parse_dns_name, 0, offsetof(Network, dhcp_server_boot_server_name) DHCPServer.BootFilename, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, dhcp_server_boot_filename) @@ -493,9 +503,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 @@ -656,12 +673,12 @@ DHCP.Hostname, config_parse_hostname, DHCP.RequestBroadcast, config_parse_tristate, 0, offsetof(Network, dhcp_broadcast) DHCP.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical) DHCP.VendorClassIdentifier, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_vendor_class_identifier) -DHCP.UserClass, config_parse_dhcp_user_or_vendor_class, AF_INET, offsetof(Network, dhcp_user_class) +DHCP.UserClass, config_parse_dhcp4_user_class, 0, offsetof(Network, dhcp_user_class) DHCP.IAID, config_parse_iaid, AF_INET, 0 DHCP.DUIDType, config_parse_network_duid_type, 0, 0 DHCP.DUIDRawData, config_parse_network_duid_rawdata, 0, 0 DHCP.RouteMetric, config_parse_dhcp_route_metric, AF_UNSPEC, 0 -DHCP.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET, 0 +DHCP.RouteTable, config_parse_dhcp_or_ra_route_table, NETWORK_CONFIG_SOURCE_DHCP4, 0 DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port) DHCP.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_use_rapid_commit) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 8141a45432e45..1e3fd33bcaaf8 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -7,6 +7,7 @@ #include "conf-files.h" #include "conf-parser.h" #include "in-addr-util.h" +#include "iovec-util.h" #include "net-condition.h" #include "netdev/macvlan.h" #include "netif-sriov.h" @@ -397,11 +398,14 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp_use_gateway = -1, .dhcp_send_hostname = true, .dhcp_send_release = true, + .dhcp_extra_options = TLV_INIT(TLV_DHCP4), + .dhcp_vendor_options = TLV_INIT(TLV_DHCP4_SUBOPTION), .dhcp_route_metric = DHCP_ROUTE_METRIC, .dhcp_use_rapid_commit = -1, .dhcp_client_identifier = _DHCP_CLIENT_ID_INVALID, .dhcp_route_table = RT_TABLE_MAIN, .dhcp_ip_service_type = -1, + .dhcp_socket_priority = -1, .dhcp_broadcast = -1, .dhcp_ipv6_only_mode = -1, .dhcp_6rd_prefix_route_type = RTN_UNREACHABLE, @@ -421,6 +425,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp6_client_start_mode = _DHCP6_CLIENT_START_MODE_INVALID, .dhcp6_send_release = true, .dhcp6_pd_prefix_route_type = RTN_UNREACHABLE, + .dhcp6_route_table = RT_TABLE_MAIN, .dhcp_pd = -1, .dhcp_pd_announce = true, @@ -429,13 +434,17 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp_pd_subnet_id = -1, .dhcp_pd_route_metric = DHCP6PD_ROUTE_METRIC, - .dhcp_server_bind_to_interface = true, + .dhcp_relay_interface_mode = _DHCP_RELAY_INTERFACE_INVALID, + .dhcp_relay_extra_options = TLV_INIT(TLV_DHCP4_SUBOPTION), + .dhcp_server_emit[SD_DHCP_LEASE_DNS].emit = true, .dhcp_server_emit[SD_DHCP_LEASE_NTP].emit = true, .dhcp_server_emit[SD_DHCP_LEASE_SIP].emit = true, .dhcp_server_emit_router = true, .dhcp_server_emit_timezone = true, .dhcp_server_rapid_commit = true, + .dhcp_server_extra_options = TLV_INIT(TLV_DHCP4), + .dhcp_server_vendor_options = TLV_INIT(TLV_DHCP4_SUBOPTION), .dhcp_server_persist_leases = _DHCP_SERVER_PERSIST_LEASES_INVALID, .router_lifetime_usec = RADV_DEFAULT_ROUTER_LIFETIME_USEC, @@ -479,6 +488,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 +523,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, }; @@ -537,10 +550,10 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi "DHCPv6\0" "DHCPv6PrefixDelegation\0" /* compat */ "DHCPPrefixDelegation\0" + "DHCPRelay\0" "DHCPServer\0" "DHCPServerStaticLease\0" "IPv6AcceptRA\0" - "IPv6NDPProxyAddress\0" "Bridge\0" "BridgeFDB\0" "BridgeMDB\0" @@ -553,7 +566,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" @@ -757,9 +770,13 @@ static Network *network_free(Network *network) { ordered_set_free(network->route_domains); set_free(network->dnssec_negative_trust_anchors); + /* DHCP relay agent */ + iovec_done(&network->dhcp_relay_remote_id); + iovec_done(&network->dhcp_relay_circuit_id); + iovec_done(&network->dhcp_relay_vss); + tlv_done(&network->dhcp_relay_extra_options); + /* DHCP server */ - free(network->dhcp_server_relay_agent_circuit_id); - free(network->dhcp_server_relay_agent_remote_id); free(network->dhcp_server_boot_server_name); free(network->dhcp_server_boot_filename); free(network->dhcp_server_timezone); @@ -767,8 +784,8 @@ static Network *network_free(Network *network) { free(network->dhcp_server_uplink_name); for (sd_dhcp_lease_server_type_t t = 0; t < _SD_DHCP_LEASE_SERVER_TYPE_MAX; t++) free(network->dhcp_server_emit[t].addresses); - ordered_hashmap_free(network->dhcp_server_send_options); - ordered_hashmap_free(network->dhcp_server_send_vendor_options); + tlv_done(&network->dhcp_server_extra_options); + tlv_done(&network->dhcp_server_vendor_options); free(network->dhcp_server_local_lease_domain); /* DHCP client */ @@ -778,10 +795,10 @@ static Network *network_free(Network *network) { free(network->dhcp_label); set_free(network->dhcp_deny_listed_ip); set_free(network->dhcp_allow_listed_ip); - strv_free(network->dhcp_user_class); + iovw_done_free(&network->dhcp_user_class); set_free(network->dhcp_request_options); - ordered_hashmap_free(network->dhcp_client_send_options); - ordered_hashmap_free(network->dhcp_client_send_vendor_options); + tlv_done(&network->dhcp_extra_options); + tlv_done(&network->dhcp_vendor_options); free(network->dhcp_netlabel); nft_set_context_clear(&network->dhcp_nft_set_context); @@ -851,7 +868,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..893b3022bb4b1 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -7,10 +7,12 @@ #include "bridge.h" #include "firewall-util.h" #include "ipoib.h" +#include "iovec-wrapper.h" #include "net-condition.h" #include "network-util.h" #include "networkd-bridge-vlan.h" #include "networkd-dhcp-common.h" +#include "networkd-dhcp-relay.h" #include "networkd-dhcp-server.h" #include "networkd-dhcp4.h" #include "networkd-dhcp6.h" @@ -21,7 +23,9 @@ #include "networkd-ndisc.h" #include "networkd-radv.h" #include "networkd-sysctl.h" +#include "networkd-wwan-bus.h" #include "resolve-util.h" +#include "tlv-util.h" typedef enum KeepConfiguration { KEEP_CONFIGURATION_NO = 0, @@ -122,7 +126,7 @@ typedef struct Network { bool dhcp_iaid_set; char *dhcp_vendor_class_identifier; char *dhcp_mudurl; - char **dhcp_user_class; + struct iovec_wrapper dhcp_user_class; char *dhcp_hostname; char *dhcp_label; uint64_t dhcp_max_attempts; @@ -137,7 +141,6 @@ typedef struct Network { int dhcp_critical; int dhcp_ip_service_type; int dhcp_socket_priority; - bool dhcp_socket_priority_set; bool dhcp_anonymize; bool dhcp_send_hostname; bool dhcp_send_hostname_set; @@ -167,8 +170,8 @@ typedef struct Network { Set *dhcp_deny_listed_ip; Set *dhcp_allow_listed_ip; Set *dhcp_request_options; - OrderedHashmap *dhcp_client_send_options; - OrderedHashmap *dhcp_client_send_vendor_options; + TLV dhcp_extra_options; + TLV dhcp_vendor_options; char *dhcp_netlabel; NFTSetContext dhcp_nft_set_context; @@ -203,18 +206,28 @@ typedef struct Network { char *dhcp6_netlabel; bool dhcp6_send_release; NFTSetContext dhcp6_nft_set_context; + uint32_t dhcp6_route_table; + bool dhcp6_route_table_set; + + /* DHCP Relay Agent Support */ + DHCPRelayInterfaceMode dhcp_relay_interface_mode; + Address *dhcp_relay_agent_address; + struct in_addr dhcp_relay_target_address; /* for deprecated DHCPServer.RelayTarget= */ + struct in_addr dhcp_relay_agent_address_in_addr; + struct in_addr dhcp_relay_gateway_address; + struct iovec dhcp_relay_remote_id; /* for deprecated DHCPServer.RelayAgentRemoteId= */ + struct iovec dhcp_relay_circuit_id; + struct iovec dhcp_relay_vss; + TLV dhcp_relay_extra_options; + int dhcp_relay_interface_priority; /* DHCP Server Support */ bool dhcp_server; - bool dhcp_server_bind_to_interface; unsigned char dhcp_server_address_prefixlen; struct in_addr dhcp_server_address_in_addr; const Address *dhcp_server_address; int dhcp_server_uplink_index; char *dhcp_server_uplink_name; - struct in_addr dhcp_server_relay_target; - char *dhcp_server_relay_agent_circuit_id; - char *dhcp_server_relay_agent_remote_id; NetworkDHCPServerEmitAddress dhcp_server_emit[_SD_DHCP_LEASE_SERVER_TYPE_MAX]; bool dhcp_server_emit_router; struct in_addr dhcp_server_router; @@ -225,8 +238,8 @@ typedef struct Network { usec_t dhcp_server_default_lease_time_usec, dhcp_server_max_lease_time_usec; uint32_t dhcp_server_pool_offset; uint32_t dhcp_server_pool_size; - OrderedHashmap *dhcp_server_send_options; - OrderedHashmap *dhcp_server_send_vendor_options; + TLV dhcp_server_extra_options; + TLV dhcp_server_vendor_options; struct in_addr dhcp_server_boot_server_address; char *dhcp_server_boot_server_name; char *dhcp_server_boot_filename; @@ -331,6 +344,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 +430,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-queue.c b/src/network/networkd-queue.c index 0ab372b4b21e7..ea846327b0db9 100644 --- a/src/network/networkd-queue.c +++ b/src/network/networkd-queue.c @@ -365,6 +365,7 @@ static const char *const request_type_table[_REQUEST_TYPE_MAX] = { [REQUEST_TYPE_ADDRESS_LABEL] = "address label", [REQUEST_TYPE_BRIDGE_FDB] = "bridge FDB", [REQUEST_TYPE_BRIDGE_MDB] = "bridge MDB", + [REQUEST_TYPE_DHCP_RELAY] = "DHCP relay agent", [REQUEST_TYPE_DHCP_SERVER] = "DHCP server", [REQUEST_TYPE_DHCP4_CLIENT] = "DHCPv4 client", [REQUEST_TYPE_DHCP6_CLIENT] = "DHCPv6 client", diff --git a/src/network/networkd-queue.h b/src/network/networkd-queue.h index d656d7aa7aa8a..70c8b2ca9eeda 100644 --- a/src/network/networkd-queue.h +++ b/src/network/networkd-queue.h @@ -13,6 +13,7 @@ typedef enum RequestType { REQUEST_TYPE_ADDRESS_LABEL, REQUEST_TYPE_BRIDGE_FDB, REQUEST_TYPE_BRIDGE_MDB, + REQUEST_TYPE_DHCP_RELAY, REQUEST_TYPE_DHCP_SERVER, REQUEST_TYPE_DHCP4_CLIENT, REQUEST_TYPE_DHCP6_CLIENT, diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index 0ef292bdf22f7..1aafc0dd6d429 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -14,6 +14,7 @@ #include "networkd-address.h" #include "networkd-address-generation.h" #include "networkd-dhcp-prefix-delegation.h" +#include "networkd-ipv6ll.h" #include "networkd-link.h" #include "networkd-manager.h" #include "networkd-network.h" @@ -423,6 +424,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); @@ -695,6 +697,12 @@ int radv_start(Link *link) { if (in6_addr_is_null(&link->ipv6ll_address)) return 0; + /* Update the source IPv6LL before the running check so replacement IPv6LL handover can + * rebind RADV without requiring stop/start. */ + r = sd_radv_set_link_local_address(link->radv, &link->ipv6ll_address); + if (r < 0) + return r; + if (sd_radv_is_running(link->radv)) return 0; @@ -704,10 +712,6 @@ int radv_start(Link *link) { return log_link_debug_errno(link, r, "Failed to request DHCP delegated subnet prefix: %m"); } - r = sd_radv_set_link_local_address(link->radv, &link->ipv6ll_address); - if (r < 0) - return r; - log_link_debug(link, "Starting IPv6 Router Advertisements"); return sd_radv_start(link->radv); } @@ -814,7 +818,8 @@ void network_adjust_radv(Network *network) { /* For backward compatibility. */ network->dhcp_pd = FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_DHCP6); - if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) { + if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6) && + !network_has_static_ipv6ll_address(network)) { if (network->router_prefix_delegation != RADV_PREFIX_DELEGATION_NONE) log_warning("%s: IPv6PrefixDelegation= is enabled but IPv6 link-local addressing is disabled. " "Disabling IPv6PrefixDelegation=.", network->filename); 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..ac2dbbfe38a32 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; @@ -562,7 +564,7 @@ static void routing_policy_rule_forget(Manager *manager, RoutingPolicyRule *rule return; routing_policy_rule_enter_removed(rule); - log_routing_policy_rule_debug(rule, "Forgetting", NULL, manager); + log_routing_policy_rule_debug(rule, msg, NULL, manager); routing_policy_rule_detach(rule); } @@ -1490,7 +1492,7 @@ static int config_parse_routing_policy_rule_uid_range( assert(rvalue); - if (get_user_creds(&rvalue, &p->start, NULL, NULL, NULL, 0) >= 0) { + if (get_user_creds(rvalue, /* flags= */ 0, NULL, &p->start, NULL, NULL, NULL) >= 0) { p->end = p->start; return 1; } diff --git a/src/network/networkd-serialize.c b/src/network/networkd-serialize.c index 31f885d87a316..224fcc93a005c 100644 --- a/src/network/networkd-serialize.c +++ b/src/network/networkd-serialize.c @@ -5,7 +5,6 @@ #include "daemon-util.h" #include "errno-util.h" #include "fd-util.h" -#include "fileio.h" #include "hashmap.h" #include "iovec-util.h" #include "json-util.h" @@ -103,28 +102,6 @@ int manager_set_serialization_fd(Manager *manager, int fd, const char *name) { static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_network_config_source, NetworkConfigSource, network_config_source_from_string); -static int json_dispatch_address_family(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { - int r, *i = ASSERT_PTR(userdata); - int64_t i64; - - assert_return(variant, -EINVAL); - - if (FLAGS_SET(flags, SD_JSON_RELAX) && sd_json_variant_is_null(variant)) { - *i = AF_UNSPEC; - return 0; - } - - r = sd_json_dispatch_int64(name, variant, flags, &i64); - if (r < 0) - return r; - - if (!IN_SET(i64, AF_INET, AF_INET6) && !(FLAGS_SET(flags, SD_JSON_RELAX) && i64 == AF_UNSPEC)) - return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds for an address family.", strna(name)); - - *i = (int) i64; - return 0; -} - typedef struct AddressParam { int family; struct iovec address; @@ -442,16 +419,12 @@ int manager_deserialize(Manager *manager) { log_debug("Deserializing..."); - _cleanup_fclose_ FILE *f = take_fdopen(&fd, "r"); - if (!f) - return log_debug_errno(errno, "Failed to fdopen() serialization file descriptor: %m"); - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; unsigned err_line = 0, err_column = 0; - r = sd_json_parse_file( - f, + r = sd_json_parse_fd( /* path= */ NULL, - /* flags= */ 0, + TAKE_FD(fd), + SD_JSON_PARSE_DONATE_FD, &v, &err_line, &err_column); 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-sriov.c b/src/network/networkd-sriov.c index f5591f5672db7..466442597916f 100644 --- a/src/network/networkd-sriov.c +++ b/src/network/networkd-sriov.c @@ -3,6 +3,7 @@ #include "sd-netlink.h" +#include "device-private.h" #include "device-util.h" #include "errno-util.h" #include "hashmap.h" @@ -282,7 +283,7 @@ int link_set_sr_iov_ifindices(Link *link) { /* This may return -EINVAL or -ENODEV, instead of -ENOENT, if the device has been removed or is being * removed. Let's ignore the error codes here. */ - r = sd_device_get_sysattr_value(link->dev, "dev_port", &dev_port); + r = device_get_sysattr_safe_string(link->dev, "dev_port", &dev_port); if (ERRNO_IS_NEG_DEVICE_ABSENT(r) || r == -EINVAL) return 0; if (r < 0) diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index b3b85f18f5c2e..3c505f39e6c95 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) @@ -959,15 +959,6 @@ static int link_save(Link *link) { print_link_hashmap(f, "CARRIER_BOUND_TO=", link->bound_to_links); print_link_hashmap(f, "CARRIER_BOUND_BY=", link->bound_by_links); - if (link->dhcp_lease) { - r = dhcp_lease_save(link->dhcp_lease, link->lease_file); - if (r < 0) - return r; - - fprintf(f, "DHCP_LEASE=%s\n", link->lease_file); - } else - (void) unlink(link->lease_file); - r = link_serialize_dhcp6_client(link, f); if (r < 0) return r; diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index 914fbccd09bf9..ab4c03609a2bf 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -5,7 +5,7 @@ #include "sd-messages.h" #include "af-list.h" -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "conf-parser.h" #include "alloc-util.h" #include "cgroup-util.h" @@ -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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return log_debug_errno(r, "sysctl monitor disabled, as BPF support is not available."); if (r < 0) @@ -545,11 +545,11 @@ int link_set_ipv6_mtu(Link *link, int log_level) { if (mtu == 0) return 0; - if (mtu > link->max_mtu) { + if (mtu > link->mtu) { log_link_full(link, log_level, "Reducing requested IPv6 MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", - mtu, link->max_mtu); - mtu = link->max_mtu; + mtu, link->mtu); + mtu = link->mtu; } r = sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "mtu", mtu, manager_get_sysctl_shadow(link->manager)); @@ -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..a52b74b0b0310 100644 --- a/src/network/networkd-varlink-metrics.c +++ b/src/network/networkd-varlink-metrics.c @@ -1,23 +1,33 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + +#include "sd-json.h" #include "sd-varlink.h" +#include "af-list.h" +#include "alloc-util.h" #include "argv-util.h" #include "errno-util.h" #include "fd-util.h" #include "hashmap.h" +#include "in-addr-util.h" #include "metrics.h" #include "network-util.h" +#include "networkd-address.h" #include "networkd-link.h" #include "networkd-manager.h" +#include "networkd-route-util.h" #include "networkd-varlink-metrics.h" +#include "set.h" #define METRIC_IO_SYSTEMD_NETWORK_PREFIX "io.systemd.Network." typedef const char* (*link_metric_extractor_t)(const Link *link); static int link_metric_build_json( - MetricFamilyContext *context, + const MetricFamily *mf, + sd_varlink *vl, link_metric_extractor_t extractor, void *userdata) { @@ -25,11 +35,12 @@ static int link_metric_build_json( Link *link; int r; - assert(context); + assert(mf && mf->name); + assert(vl); assert(extractor); HASHMAP_FOREACH(link, manager->links_by_index) { - r = metric_build_send_string(context, link->ifname, extractor(link), /* fields= */ NULL); + r = metric_build_send_string(mf, vl, link->ifname, extractor(link), /* fields= */ NULL); if (r < 0) return r; } @@ -61,46 +72,152 @@ static const char* link_get_oper_state(const Link *l) { return link_operstate_to_string(ASSERT_PTR(l)->operstate); } -static int link_address_state_build_json(MetricFamilyContext *ctx, void *userdata) { - return link_metric_build_json(ctx, link_get_address_state, userdata); +static int link_addresses_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Link *link; + int r; + + assert(mf && mf->name); + assert(vl); + + HASHMAP_FOREACH(link, manager->links_by_index) { + Address *a; + + SET_FOREACH(a, link->addresses) { + if (!address_is_ready(a)) + continue; + + /* Remove localhost address (127.0.0.1 and ::1) */ + if (link->flags & IFF_LOOPBACK && in_addr_is_localhost_one(a->family, &a->in_addr) > 0) + continue; + + _cleanup_free_ char *scope = NULL; + r = route_scope_to_string_alloc(a->scope, &scope); + 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("family", af_to_ipv4_ipv6(a->family)), + SD_JSON_BUILD_PAIR_STRING("scope", scope)); + if (r < 0) + return r; + + r = metric_build_send_string( + mf, + vl, + link->ifname, + IN_ADDR_PREFIX_TO_STRING(a->family, &a->in_addr, a->prefixlen), + fields); + if (r < 0) + return r; + } + } + + return 0; +} + +static int link_address_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return link_metric_build_json(mf, vl, link_get_address_state, userdata); } -static int link_admin_state_build_json(MetricFamilyContext *ctx, void *userdata) { - return link_metric_build_json(ctx, link_get_admin_state, userdata); +static int link_admin_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return link_metric_build_json(mf, vl, link_get_admin_state, userdata); } -static int link_carrier_state_build_json(MetricFamilyContext *ctx, void *userdata) { - return link_metric_build_json(ctx, link_get_carrier_state, userdata); +static int link_carrier_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return link_metric_build_json(mf, vl, link_get_carrier_state, userdata); } -static int link_ipv4_address_state_build_json(MetricFamilyContext *ctx, void *userdata) { - return link_metric_build_json(ctx, link_get_ipv4_address_state, userdata); +static int link_ipv4_address_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return link_metric_build_json(mf, vl, link_get_ipv4_address_state, userdata); } -static int link_ipv6_address_state_build_json(MetricFamilyContext *ctx, void *userdata) { - return link_metric_build_json(ctx, link_get_ipv6_address_state, userdata); +static int link_ipv6_address_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return link_metric_build_json(mf, vl, link_get_ipv6_address_state, userdata); } -static int link_oper_state_build_json(MetricFamilyContext *ctx, void *userdata) { - return link_metric_build_json(ctx, link_get_oper_state, userdata); +static int link_oper_state_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + return link_metric_build_json(mf, vl, link_get_oper_state, userdata); } -static int managed_interfaces_build_json(MetricFamilyContext *context, void *userdata) { +static int managed_interfaces_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { Manager *manager = ASSERT_PTR(userdata); Link *link; uint64_t count = 0; - assert(context); + assert(mf && mf->name); + assert(vl); HASHMAP_FOREACH(link, manager->links_by_index) if (link->network) count++; - return metric_build_send_unsigned(context, /* object= */ NULL, count, /* fields= */ NULL); + return metric_build_send_unsigned(mf, vl, /* object= */ NULL, count, /* fields= */ NULL); +} + +static int required_for_online_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + Link *link; + int r; + + assert(mf && mf->name); + assert(vl); + + HASHMAP_FOREACH(link, manager->links_by_index) { + if (!link->network) + continue; + + if (link->network->required_for_online == 0) { + r = metric_build_send_string( + mf, + vl, + 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( + mf, + vl, + 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( + mf, + vl, + link->ifname, + value, + /* fields= */ NULL); + } + } + if (r < 0) + return r; + } + + return 0; } /* Keep metrics ordered alphabetically */ static const MetricFamily network_metric_family_table[] = { + { + .name = METRIC_IO_SYSTEMD_NETWORK_PREFIX "Address", + .description = "Per interface metric: configured IP address in CIDR notation", + .type = METRIC_FAMILY_TYPE_STRING, + .generate = link_addresses_build_json, + }, { .name = METRIC_IO_SYSTEMD_NETWORK_PREFIX "AddressState", .description = "Per interface metric: address state", @@ -143,6 +260,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-wiphy.c b/src/network/networkd-wiphy.c index 1dde69a43b44d..f715f244d6b29 100644 --- a/src/network/networkd-wiphy.c +++ b/src/network/networkd-wiphy.c @@ -469,7 +469,7 @@ int manager_udev_process_wiphy(Manager *m, sd_device *device, sd_device_action_t return 0; } - return device_unref_and_replace(w->dev, action == SD_DEVICE_REMOVE ? NULL : device); + return device_unref_and_replace_new_ref(w->dev, action == SD_DEVICE_REMOVE ? NULL : device); } int manager_udev_process_rfkill(Manager *m, sd_device *device, sd_device_action_t action) { @@ -501,5 +501,5 @@ int manager_udev_process_rfkill(Manager *m, sd_device *device, sd_device_action_ return 0; } - return device_unref_and_replace(w->rfkill, action == SD_DEVICE_REMOVE ? NULL : device); + return device_unref_and_replace_new_ref(w->rfkill, action == SD_DEVICE_REMOVE ? NULL : device); } diff --git a/src/network/networkd-wwan-bus.c b/src/network/networkd-wwan-bus.c index d87cdd3441185..98c6a28c2697b 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. */ @@ -949,12 +882,12 @@ static int modem_properties_changed_signal( sd_bus_error *ret_error) { static const struct bus_properties_map map[] = { - { "Bearers", "a{sv}", modem_map_bearers, 0, }, + { "Bearers", "ao", modem_map_bearers, 0, }, { "State", "i", NULL, offsetof(Modem, state) }, { "StateFailedReason", "u", NULL, offsetof(Modem, state_fail_reason) }, { "Manufacturer", "s", NULL, offsetof(Modem, manufacturer) }, { "Model", "s", NULL, offsetof(Modem, model) }, - { "Ports", "a{su}", modem_map_ports, 0, }, + { "Ports", "a(su)", modem_map_ports, 0, }, {} }; Modem *modem = ASSERT_PTR(userdata); @@ -1036,7 +969,7 @@ static int modem_add(Manager *m, const char *path, sd_bus_message *message, sd_b { "StateFailedReason", "u", NULL, offsetof(Modem, state_fail_reason) }, { "Manufacturer", "s", NULL, offsetof(Modem, manufacturer) }, { "Model", "s", NULL, offsetof(Modem, model) }, - { "Ports", "a{su}", modem_map_ports, 0, }, + { "Ports", "a(su)", modem_map_ports, 0, }, {} }; Modem *modem; @@ -1249,7 +1182,7 @@ int manager_match_mm_signals(Manager *manager) { /* install_callback= */ NULL, manager); if (r < 0) - return log_error_errno(r, "Failed to request signal for IntefaceAdded"); + return log_error_errno(r, "Failed to request signal for InterfaceAdded"); r = sd_bus_match_signal_async( manager->bus, @@ -1262,7 +1195,7 @@ int manager_match_mm_signals(Manager *manager) { /* install_callback= */ NULL, manager); if (r < 0) - return log_error_errno(r, "Failed to request signal for IntefaceRemoved"); + return log_error_errno(r, "Failed to request signal for InterfaceRemoved"); /* N.B. We need "path_namespace" for bearers, not "path", */ r = sd_bus_add_match_async( 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/networkd.c b/src/network/networkd.c index 3e0666610bfe9..230339ba0c7f3 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -10,7 +10,7 @@ #include "capability-util.h" #include "daemon-util.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "networkd-conf.h" #include "networkd-manager.h" #include "networkd-manager-bus.h" @@ -42,13 +42,12 @@ static int run(int argc, char *argv[]) { /* 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 and we can't create our runtime directory. */ if (geteuid() == 0) { - const char *user = "systemd-network"; uid_t uid; gid_t gid; - r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); + r = get_user_creds("systemd-network", /* flags= */ 0, NULL, &uid, &gid, NULL, NULL); if (r < 0) - return log_error_errno(r, "Cannot resolve user name %s: %m", user); + return log_error_errno(r, "Cannot resolve user name %s: %m", "systemd-network"); /* Create runtime directory. This is not necessary when networkd is * started with "RuntimeDirectory=systemd/netif", or after diff --git a/src/network/networkd.conf b/src/network/networkd.conf index a5a9418bbb194..2c6d71f480ce5 100644 --- a/src/network/networkd.conf +++ b/src/network/networkd.conf @@ -46,5 +46,11 @@ #DUIDRawData= #UseDomains= +[DHCPRelay] +#ServerAddress= +#OverrideServerIdentifier=no +#RemoteId= +#ExtraOption= + [DHCPServer] #PersistLeases=yes 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..7ddb18828af11 --- /dev/null +++ b/src/network/test-modem-manager-mock.c @@ -0,0 +1,479 @@ +/* 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 "sd-bus.h" +#include "sd-daemon.h" +#include "sd-event.h" + +#include "alloc-util.h" +#include "build.h" +#include "format-table.h" +#include "help-util.h" +#include "log.h" +#include "main-func.h" +#include "options.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 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...]"); + help_abstract("Mock ModemManager D-Bus service for testing."); + + help_section("Options"); + return table_print_or_warn(options); +} + +static int parse_argv(int argc, char *argv[]) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_LONG("ifname", "NAME", "Interface name"): + if (free_and_strdup(&arg_ifname, opts.arg) < 0) + return log_oom(); + break; + + OPTION_LONG("ipv4-address", "ADDR", "IPv4 address"): + if (free_and_strdup(&arg_ipv4_address, opts.arg) < 0) + return log_oom(); + break; + + OPTION_LONG("ipv4-gateway", "ADDR", "IPv4 gateway"): + if (free_and_strdup(&arg_ipv4_gateway, opts.arg) < 0) + return log_oom(); + break; + + OPTION_LONG("ipv4-prefix", "LEN", "IPv4 prefix length"): + r = safe_atou32(opts.arg, &arg_ipv4_prefix); + if (r < 0) + return log_error_errno(r, "Failed to parse IPv4 prefix length: %m"); + break; + + OPTION_LONG("ipv6-address", "ADDR", "IPv6 address"): + if (free_and_strdup(&arg_ipv6_address, opts.arg) < 0) + return log_oom(); + break; + + OPTION_LONG("ipv6-gateway", "ADDR", "IPv6 gateway"): + if (free_and_strdup(&arg_ipv6_gateway, opts.arg) < 0) + return log_oom(); + break; + + OPTION_LONG("ipv6-prefix", "LEN", "IPv6 prefix length"): + r = safe_atou32(opts.arg, &arg_ipv6_prefix); + if (r < 0) + return log_error_errno(r, "Failed to parse IPv6 prefix length: %m"); + break; + } + + 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..ee0e5d13b5700 100644 --- a/src/network/test-network.c +++ b/src/network/test-network.c @@ -1,9 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" -#include "dhcp-lease-internal.h" #include "hashmap.h" #include "hostname-setup.h" #include "network-internal.h" @@ -11,6 +8,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; @@ -41,51 +39,6 @@ TEST(deserialize_in_addr) { ASSERT_TRUE(in6_addr_equal(&f.in6, &addresses6[2])); } -TEST(deserialize_dhcp_routes) { - _cleanup_free_ struct sd_dhcp_route *routes = NULL; - size_t size; - - ASSERT_OK(deserialize_dhcp_routes(&routes, &size, "")); - ASSERT_EQ(size, 0U); - ASSERT_NULL(routes); - - ASSERT_OK(deserialize_dhcp_routes(&routes, &size, "192.168.0.0/16,192.168.0.1 10.1.2.0/24,10.1.2.1 0.0.0.0/0,10.0.1.1")); - ASSERT_EQ(size, 3U); - ASSERT_NOT_NULL(routes); - - ASSERT_EQ(routes[0].dst_addr.s_addr, inet_addr("192.168.0.0")); - ASSERT_EQ(routes[0].gw_addr.s_addr, inet_addr("192.168.0.1")); - ASSERT_EQ(routes[0].dst_prefixlen, 16U); - - ASSERT_EQ(routes[1].dst_addr.s_addr, inet_addr("10.1.2.0")); - ASSERT_EQ(routes[1].gw_addr.s_addr, inet_addr("10.1.2.1")); - ASSERT_EQ(routes[1].dst_prefixlen, 24U); - - ASSERT_EQ(routes[2].dst_addr.s_addr, inet_addr("0.0.0.0")); - ASSERT_EQ(routes[2].gw_addr.s_addr, inet_addr("10.0.1.1")); - ASSERT_EQ(routes[2].dst_prefixlen, 0U); - - routes = mfree(routes); - - ASSERT_OK(deserialize_dhcp_routes(&routes, &size, "192.168.0.0/16,192.168.0.1 10.1.2.0#24,10.1.2.1 0.0.0.0/0,10.0.1.1")); - ASSERT_EQ(size, 2U); - ASSERT_NOT_NULL(routes); - - ASSERT_EQ(routes[0].dst_addr.s_addr, inet_addr("192.168.0.0")); - ASSERT_EQ(routes[0].gw_addr.s_addr, inet_addr("192.168.0.1")); - ASSERT_EQ(routes[0].dst_prefixlen, 16U); - - ASSERT_EQ(routes[1].dst_addr.s_addr, inet_addr("0.0.0.0")); - ASSERT_EQ(routes[1].gw_addr.s_addr, inet_addr("10.0.1.1")); - ASSERT_EQ(routes[1].dst_prefixlen, 0U); - - routes = mfree(routes); - - ASSERT_OK(deserialize_dhcp_routes(&routes, &size, "192.168.0.0/55,192.168.0.1 10.1.2.0#24,10.1.2.1 0.0.0.0/0,10.0.1.X")); - ASSERT_EQ(size, 0U); - ASSERT_NULL(routes); -} - static void test_route_tables_one(Manager *manager, const char *name, uint32_t number) { _cleanup_free_ char *str = NULL, *expected = NULL, *num_str = NULL; uint32_t t; @@ -128,7 +81,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 +103,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/network/wait-online/wait-online-manager.c b/src/network/wait-online/wait-online-manager.c index b5ca38cbe2403..70e2b45f00d20 100644 --- a/src/network/wait-online/wait-online-manager.c +++ b/src/network/wait-online/wait-online-manager.c @@ -68,7 +68,7 @@ static const LinkOperationalStateRange* get_state_range(Manager *m, Link *l, con if (operational_state_range_is_valid(range)) return range; - /* l->requred_operstate should be always valid. */ + /* l->required_operstate should be always valid. */ assert_not_reached(); } diff --git a/src/network/wait-online/wait-online.c b/src/network/wait-online/wait-online.c index b1d0b9cde212d..e566f89d02e57 100644 --- a/src/network/wait-online/wait-online.c +++ b/src/network/wait-online/wait-online.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-event.h" @@ -8,11 +7,13 @@ #include "alloc-util.h" #include "build.h" #include "daemon-util.h" +#include "format-table.h" #include "hashmap.h" +#include "help-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" -#include "pretty-print.h" #include "socket-util.h" #include "strv.h" #include "time-util.h" @@ -31,32 +32,22 @@ STATIC_DESTRUCTOR_REGISTER(arg_interfaces, hashmap_freep); STATIC_DESTRUCTOR_REGISTER(arg_ignore, strv_freep); static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd-networkd-wait-online.service", "8", &link); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; - printf("%s [OPTIONS...]\n\n" - "Block until network is configured.\n\n" - " -h --help Show this help\n" - " --version Print version string\n" - " -q --quiet Do not show status information\n" - " -i --interface=INTERFACE[:MIN_OPERSTATE[:MAX_OPERSTATE]]\n" - " Block until at least these interfaces have appeared\n" - " --ignore=INTERFACE Don't take these interfaces into account\n" - " -o --operational-state=MIN_OPERSTATE[:MAX_OPERSTATE]\n" - " Required operational state\n" - " -4 --ipv4 Requires at least one IPv4 address\n" - " -6 --ipv6 Requires at least one IPv6 address\n" - " --any Wait until at least one of the interfaces is online\n" - " --timeout=SECS Maximum time to wait for network connectivity\n" - " --dns Requires at least one DNS server to be accessible\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + help_cmdline("[OPTIONS...]"); + help_abstract("Block until network is configured."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-networkd-wait-online.service", "8"); return 0; } @@ -78,7 +69,7 @@ static int parse_interface_with_operstate_range(const char *str) { if (r < 0) return log_error_errno(r, "Invalid operational state range: %s", p + 1); - ifname = strndup(optarg, p - optarg); + ifname = strndup(str, p - str); } else { *range = LINK_OPERSTATE_RANGE_INVALID; ifname = strdup(str); @@ -105,97 +96,70 @@ static int parse_interface_with_operstate_range(const char *str) { } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_IGNORE, - ARG_ANY, - ARG_TIMEOUT, - ARG_DNS, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "quiet", no_argument, NULL, 'q' }, - { "interface", required_argument, NULL, 'i' }, - { "ignore", required_argument, NULL, ARG_IGNORE }, - { "operational-state", required_argument, NULL, 'o' }, - { "ipv4", no_argument, NULL, '4' }, - { "ipv6", no_argument, NULL, '6' }, - { "any", no_argument, NULL, ARG_ANY }, - { "timeout", required_argument, NULL, ARG_TIMEOUT }, - { "dns", optional_argument, NULL, ARG_DNS }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hi:qo:46", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - help(); - return 0; + OPTION_COMMON_HELP: + return help(); - case 'q': + OPTION_COMMON_VERSION: + return version(); + + OPTION('q', "quiet", NULL, "Do not show status information"): arg_quiet = true; break; - case ARG_VERSION: - return version(); - - case 'i': - r = parse_interface_with_operstate_range(optarg); + OPTION('i', "interface", "IFNAME[:MIN[:MAX]]", + "Block until at least these interfaces have appeared, " + "in the operational state between MIN and MAX"): + r = parse_interface_with_operstate_range(opts.arg); if (r < 0) return r; break; - case ARG_IGNORE: - if (strv_extend(&arg_ignore, optarg) < 0) + OPTION_LONG("ignore", "IFNAME", "Don't take these interfaces into account"): + if (strv_extend(&arg_ignore, opts.arg) < 0) return log_oom(); - break; - case 'o': - r = parse_operational_state_range(optarg, &arg_required_operstate); + OPTION('o', "operational-state", "MIN[:MAX]", + "Require operational state between MIN and MAX"): + r = parse_operational_state_range(opts.arg, &arg_required_operstate); if (r < 0) - return log_error_errno(r, "Invalid operational state range '%s'", optarg); + return log_error_errno(r, "Invalid operational state range '%s'", opts.arg); break; - case '4': + OPTION('4', "ipv4", NULL, "Require at least one IPv4 address"): arg_required_family |= ADDRESS_FAMILY_IPV4; break; - case '6': + OPTION('6', "ipv6", NULL, "Require at least one IPv6 address"): arg_required_family |= ADDRESS_FAMILY_IPV6; break; - case ARG_ANY: + OPTION_LONG("any", NULL, "Wait until at least one of the interfaces is online"): arg_any = true; break; - case ARG_TIMEOUT: - r = parse_sec(optarg, &arg_timeout); + OPTION_LONG("timeout", "SECS", "Maximum time to wait for network connectivity"): + r = parse_sec(opts.arg, &arg_timeout); if (r < 0) return r; break; - case ARG_DNS: - r = parse_boolean_argument("--dns", optarg, &arg_requires_dns); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "dns", "BOOL", + "Require at least one DNS server to be accessible"): + r = parse_boolean_argument("--dns", opts.arg, &arg_requires_dns); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } return 1; diff --git a/src/notify/notify.c b/src/notify/notify.c index 6a39147f99e1c..667a7a64477a5 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,86 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.arg) || streq(opts.arg, "auto")) r = pidref_parent_if_applicable(&arg_pid); - else if (streq(optarg, "parent")) + else if (streq(opts.arg, "parent")) r = pidref_set_parent(&arg_pid); - else if (streq(optarg, "self")) + else if (streq(opts.arg, "self")) r = pidref_set_self(&arg_pid); else - r = pidref_set_pidstr(&arg_pid, optarg); + r = pidref_set_pidstr(&arg_pid, opts.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", opts.arg); break; - case ARG_STATUS: - arg_status = optarg; + OPTION_LONG("uid", "USER", "Set user to send from"): + r = get_user_creds(opts.arg, /* flags= */ 0, NULL, &arg_uid, &arg_gid, NULL, NULL); + if (r == -ESRCH) /* If the user doesn't exist, then accept it anyway as numeric */ + r = parse_uid(opts.arg, &arg_uid); + if (r < 0) + return log_error_errno(r, "Can't resolve user %s: %m", opts.arg); break; - case ARG_BOOTED: - arg_action = ACTION_BOOTED; + OPTION_LONG("status", "TEXT", "Set status text"): + arg_status = opts.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(opts.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", opts.arg); if (!passed) { /* Take possession of all passed fds */ @@ -310,33 +257,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(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File descriptor name invalid: %s", opts.arg); - if (free_and_strdup(&arg_fdname, optarg) < 0) + if (free_and_strdup(&arg_fdname, opts.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(&opts); + switch (arg_action) { case ACTION_NOTIFY: { @@ -348,22 +290,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 +318,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 +330,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 +346,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 +522,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 +599,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 +643,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/meson.build b/src/nspawn/meson.build index 815b74cb1572d..95bc461cc5a08 100644 --- a/src/nspawn/meson.build +++ b/src/nspawn/meson.build @@ -43,8 +43,8 @@ executables += [ 'sources' : nspawn_sources, 'extract' : nspawn_extract_sources, 'include_directories' : [ + include_directories('.'), executable_template['include_directories'], - include_directories('.') ], 'dependencies' : [ libmount_cflags, 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..3b57a75070b82 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -14,7 +14,7 @@ #include "format-util.h" #include "fs-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "namespace-util.h" @@ -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..ad506cfaf4b68 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. */ @@ -498,7 +499,7 @@ static int netns_child_begin(int netns_fd, int *ret_original_netns_fd) { if (r < 0) return log_error_errno(r, "Failed to mount sysfs on /sys/: %m"); - /* udev_avaliable() might be called previously and the result may be cached. + /* udev_available() might be called previously and the result may be cached. * Now, we (re-)mount sysfs. Hence, we need to reset the cache. */ reset_cached_udev_availability(); diff --git a/src/nspawn/nspawn-oci.c b/src/nspawn/nspawn-oci.c index 29091bd82c8f5..0f206bd7067c6 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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..2645bd21e419f 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); @@ -694,6 +697,11 @@ int config_parse_private_users( settings->userns_mode = USER_NAMESPACE_FIXED; settings->uid_shift = 0; settings->uid_range = UINT32_C(0x10000); + } else if (streq(rvalue, "managed")) { + /* managed: User namespace on, and acquire it from systemd-nsresourced */ + settings->userns_mode = USER_NAMESPACE_MANAGED; + settings->uid_shift = UID_INVALID; + settings->uid_range = UINT32_C(0x10000); } else { const char *range, *shift; uid_t sh, rn; @@ -717,12 +725,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 +1062,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..bbd5f411d413c 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,56 @@ 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", + }; + + Table* tables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(tables, table_unref_array_clear); + + 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 +460,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 +596,524 @@ 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 opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + + FOREACH_OPTION_OR_RETURN(c, &opts) { 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(opts.arg); + if (r < 0) { + if (streq(opts.arg, "trusted")) { + mask_all_settings = false; + mask_no_settings = false; + arg_settings_trusted = true; + + } else if (streq(opts.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", opts.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(opts.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(opts.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(opts.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(opts.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(opts.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(opts.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 (!opts.arg) + arg_volatile_mode = VOLATILE_YES; + else if (streq(opts.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(opts.arg); + if (m < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse --volatile= argument: %s", opts.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(opts.arg, &k.iov_base, &k.iov_len); if (r < 0) - return log_oom(); + return log_error_errno(r, "Failed to parse root hash: %s", opts.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", opts.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(opts.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", opts.arg); - case ARG_NETWORK_INTERFACE: - r = interface_pair_parse(&arg_network_interfaces, optarg); - if (r < 0) - return r; + } else { + r = read_full_file(opts.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", opts.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(opts.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, opts.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", opts.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; - - arg_settings_mask |= SETTING_NETWORK; - break; + OPTION_GROUP("Execution"): {} - 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(opts.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.", opts.arg); - arg_settings_mask |= SETTING_MACHINE_ID; - break; + r = path_simplify_alloc(opts.arg, &wd); + if (r < 0) + return log_error_errno(r, "Failed to simplify path %s: %m", opts.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, opts.arg); if (r < 0) - return r; + return log_error_errno(r, "Cannot assign environment variable %s: %m", opts.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, opts.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(opts.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(opts.arg); + if (arg_kill_signal < 0) + return log_error_errno(arg_kill_signal, "Cannot parse signal: %s", opts.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=", opts.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=", opts.arg, &arg_suppress_sync); if (r < 0) return r; - - arg_settings_mask |= SETTING_NO_NEW_PRIVILEGES; - break; - - case 'j': - arg_link_journal = LINK_GUEST; - arg_link_journal_try = true; - arg_settings_mask |= SETTING_LINK_JOURNAL; + arg_settings_mask |= SETTING_SUPPRESS_SYNC; break; - 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); + OPTION_GROUP("System Identity"): {} - 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(opts.arg) && !hostname_is_valid(opts.arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid machine name: %s", opts.arg); + r = free_and_strdup_warn(&arg_machine, opts.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(opts.arg) && !hostname_is_valid(opts.arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid hostname: %s", opts.arg); + r = free_and_strdup_warn(&arg_hostname, opts.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(opts.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", opts.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(opts.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, opts.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=", opts.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; - - 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; - } + OPTION_GROUP("User Namespacing"): {} - 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(opts.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(opts.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(opts.arg); + if (arg_userns_ownership < 0) + return log_error_errno(arg_userns_ownership, "Cannot parse --private-users-ownership= value: %s", opts.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(opts.arg, &arg_delegate_container_ranges); if (r < 0) - return r; - + return log_error_errno(r, "Failed to parse --private-users-delegate= parameter: %s", opts.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, opts.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, opts.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, opts.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, opts.arg); + if (r < 0) + return log_error_errno(r, "Failed to parse --network-veth-extra= parameter: %s", opts.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(opts.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", opts.arg); + r = free_and_strdup(&arg_network_bridge, opts.arg); if (r < 0) - return log_error_errno(r, "Failed to simplify path %s: %m", optarg); + return log_oom(); + arg_network_veth = true; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; - if (!path_is_normalized(wd)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory path is not normalized: %s", wd); + OPTION_LONG("network-zone", "NAME", + "Similar, but attach the new interface to an automatically managed bridge interface"): { + _cleanup_free_ char *j = NULL; - if (path_below_api_vfs(wd)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory is below API VFS, refusing: %s", wd); + j = strjoin("vz-", opts.arg); + if (!j) + return log_oom(); - free_and_replace(arg_chdir, wd); - arg_settings_mask |= SETTING_WORKING_DIRECTORY; + if (!ifname_valid(j)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Network zone name not valid: %s", j); + + free_and_replace(arg_network_zone, j); + arg_network_veth = true; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; break; } - case ARG_PIVOT_ROOT: - r = pivot_root_parse(&arg_pivot_root_new, &arg_pivot_root_old, optarg); + OPTION_LONG("network-namespace-path", "PATH", + "Set network namespace to the one represented by the specified kernel namespace file node"): + r = parse_path_argument(opts.arg, false, &arg_network_namespace_path); if (r < 0) - return log_error_errno(r, "Failed to parse --pivot-root= argument %s: %m", optarg); - - arg_settings_mask |= SETTING_PIVOT_ROOT; + return r; + arg_settings_mask |= SETTING_NETWORK; break; - case ARG_NOTIFY_READY: - r = parse_boolean_argument("--notify-ready=", optarg, &arg_notify_ready); + OPTION('p', "port", "[PROTOCOL:]HOSTPORT[:CONTAINERPORT]", + "Expose a container IP port on the host"): + r = expose_port_parse(&arg_expose_ports, opts.arg); + if (r == -EEXIST) + return log_error_errno(r, "Duplicate port specification: %s", opts.arg); if (r < 0) - return r; - - arg_settings_mask |= SETTING_NOTIFY_READY; + return log_error_errno(r, "Failed to parse host port %s: %m", opts.arg); + arg_settings_mask |= SETTING_EXPOSE_PORTS; break; - case ARG_ROOT_HASH: { - _cleanup_(iovec_done) struct iovec k = {}; + OPTION_GROUP("Security"): {} - r = unhexmem(optarg, &k.iov_base, &k.iov_len); - 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); + 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(opts.arg, &m); + if (r <= 0) + return r; - iovec_done(&arg_verity_settings.root_hash); - arg_verity_settings.root_hash = TAKE_STRUCT(k); + if (streq(opts.opt->long_code, "capability")) + plus |= m; + else + minus |= m; + arg_settings_mask |= SETTING_CAPABILITY; break; } - case ARG_ROOT_HASH_SIG: { - _cleanup_(iovec_done) struct iovec p = {}; - char *value; - - 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); - - } 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); - } - - 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(opts.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=", opts.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 = opts.arg[0] == '~'; + items = negative ? opts.arg + 1 : opts.arg; for (;;) { _cleanup_free_ char *word = NULL; @@ -1413,25 +1133,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(opts.arg, &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", opts.arg); + + 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 = opts.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 = opts.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(opts.arg, "help")) return DUMP_STRING_TABLE(rlimit, int, _RLIMIT_MAX); - eq = strchr(optarg, '='); + eq = strchr(opts.arg, '='); if (!eq) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--rlimit= expects an '=' assignment."); - name = strndup(optarg, eq - optarg); + name = strndup(opts.arg, eq - opts.arg); if (!name) return log_oom(); @@ -1453,162 +1192,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(opts.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", opts.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(opts.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", opts.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(opts.arg); + if (arg_personality == PERSONALITY_INVALID) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown or unsupported personality '%s'.", opts.arg); + arg_settings_mask |= SETTING_PERSONALITY; + break; + + OPTION_GROUP("Integration"): {} + + OPTION_LONG("resolv-conf", "MODE", "Select mode of /etc/resolv.conf initialization"): + if (streq(opts.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(opts.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", opts.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(opts.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(opts.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", opts.arg); arg_settings_mask |= SETTING_TIMEZONE; break; - case ARG_CONSOLE: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(console_mode, ConsoleMode, _CONSOLE_MODE_MAX); - - 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); - - arg_settings_mask |= SETTING_CONSOLE_MODE; + OPTION_LONG("link-journal", "MODE", + "Link up guest journal, one of no, auto, guest, host, try-guest, try-host"): + r = parse_link_journal(opts.arg, &arg_link_journal, &arg_link_journal_try); + if (r < 0) + return log_error_errno(r, "Failed to parse link journal mode %s", opts.arg); + arg_settings_mask |= SETTING_LINK_JOURNAL; + break; + + 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; + + OPTION_LONG("forward-journal", "FILE|DIR", "Forward the container's journal to the host"): + r = parse_path_argument(opts.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(opts.arg, 1024, &arg_forward_journal_max_use); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-max-use= value: %s", opts.arg); 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(opts.arg, 1024, &arg_forward_journal_keep_free); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-keep-free= value: %s", opts.arg); 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(opts.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", opts.arg); 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(opts.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", opts.arg); + 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, opts.arg, + streq(opts.opt->long_code, "bind-ro")); + if (r < 0) + return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", opts.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, opts.arg); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse --inaccessible= argument %s: %m", opts.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, opts.arg); + if (r < 0) + return log_error_errno(r, "Failed to parse --tmpfs= argument %s: %m", opts.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, opts.arg, + streq(opts.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", opts.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(opts.arg, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", opts.arg); + if (strv_extend(&arg_bind_user, opts.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(opts.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", opts.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(opts.arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", opts.arg); + if (strv_extend(&arg_bind_user_groups, opts.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(opts.arg, "help")) + return DUMP_STRING_TABLE(console_mode, ConsoleMode, _CONSOLE_MODE_MAX); + + arg_console_mode = console_mode_from_string(opts.arg); + if (arg_console_mode < 0) + return log_error_errno(arg_console_mode, "Unknown console mode: %s", opts.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(opts.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, opts.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, opts.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 (opts.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 opts.arg does not look + * like an option, interpret it as a user name. */ + const char *t = option_parser_peek_next_arg(&opts); + if (t && t[0] != '-') { + opts.arg = option_parser_consume_next_arg(&opts); + log_warning("--user NAME is deprecated, use --uid=NAME instead."); + } + } + + if (opts.arg) { + r = free_and_strdup(&arg_user, opts.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(&opts); + 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 +1460,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 +1513,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 +1521,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 +1532,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."); } @@ -1775,6 +1639,43 @@ static int verify_arguments(void) { return 0; } +static int split_boot_parameters(void) { + _cleanup_strv_free_ char **kept = NULL; + int r; + + /* When the kernel hands the command line to PID 1, any KEY=VALUE assignment whose KEY does not + * contain a '.' is exported as an environment variable (with '-' replaced by '_'), rather than + * passed as an argument. Mimic the same split here so users can pass kernel-cmdline-style + * arguments after the container path and get the behavior they'd get on a real boot. */ + + if (arg_start_mode != START_BOOT) + return 0; + + STRV_FOREACH(p, arg_parameters) { + _cleanup_free_ char *key = NULL, *value = NULL; + + if (split_pair(*p, "=", &key, &value) >= 0 && !strchr(key, '.')) { + string_replace_char(key, '-', '_'); + + if (env_name_is_valid(key) && env_value_is_valid(value)) { + r = strv_env_assign(&arg_setenv, key, value); + if (r < 0) + return log_error_errno(r, "Cannot assign environment variable: %m"); + + arg_settings_mask |= SETTING_ENVIRONMENT; + continue; + } + } + + r = strv_extend(&kept, *p); + if (r < 0) + return log_oom(); + } + + strv_free_and_replace(arg_parameters, kept); + return 0; +} + static int verify_network_interfaces_initialized(void) { int r; r = test_network_interfaces_initialized(arg_network_interfaces); @@ -2123,35 +2024,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); + + /* 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. */ - 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"); + 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 +2132,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 +2454,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 +2910,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 +3112,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 +3553,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; } @@ -3852,16 +3782,21 @@ static int setup_notify_child(const void *directory) { if (r < 0) log_debug_errno(r, "Failed to enable SO_PASSPIDFD, ignoring: %m"); - r = setsockopt_int(fd, SOL_SOCKET, SO_PASSRIGHTS, false); - if (r < 0) - log_debug_errno(r, "Failed to turn off SO_PASSRIGHTS, ignoring: %m"); + /* Only allow the container payload to pass file descriptors to us if we ourselves are + * supervised by a service manager that enabled the FD store. */ + if (!fdstore_detected()) { + r = setsockopt_int(fd, SOL_SOCKET, SO_PASSRIGHTS, false); + if (r < 0) + log_debug_errno(r, "Failed to turn off SO_PASSRIGHTS, ignoring: %m"); + } 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 +3805,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 +4310,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; @@ -4506,6 +4449,13 @@ static int outer_child( if (notify_fd < 0) return notify_fd; + /* Join the external network namespace first, while we are still in the parent's + * user namespace and have CAP_SYS_ADMIN there. Once we clone with CLONE_NEWUSER, + * the child will be in a new user namespace, lacking the capabilities in the + * parent user namespace required to join its network namespace. */ + if (arg_network_namespace_path && setns(netns_fd, CLONE_NEWNET) < 0) + return log_error_errno(errno, "Failed to join network namespace: %m"); + pid_t pid = raw_clone(SIGCHLD|CLONE_NEWNS| arg_clone_ns_flags | (IN_SET(arg_userns_mode, USER_NAMESPACE_FIXED, USER_NAMESPACE_PICK) ? CLONE_NEWUSER : 0) | @@ -4521,9 +4471,6 @@ static int outer_child( /* The inner child has all namespaces that are requested, so that we all are owned by the * user if user namespaces are turned on. */ - if (arg_network_namespace_path && setns(netns_fd, CLONE_NEWNET) < 0) - return log_error_errno(errno, "Failed to join network namespace: %m"); - if (arg_userns_mode == USER_NAMESPACE_MANAGED) { /* In managed usernamespace operation, sysfs + procfs are special, we'll have to * mount them inside the inner namespaces, but before we switch root. Hence do so @@ -4674,6 +4621,76 @@ static int setup_uid_map( return 0; } +static int forward_fd_store(char **tags, FDSet *fds) { + int r; + + /* Forward fd-store related messages to our own service manager, so that file descriptors stored + * by the inner payload propagate up the chain and are preserved across restarts. Skip entirely + * if we have no upstream supervisor (no NOTIFY_SOCKET) or no fd store available (no FDSTORE). + * + * Forwarded entries are namespaced with a "payload-" prefix on their FDNAME so that they + * cannot collide with fd-store entries that nspawn itself might want to push to its own + * upstream supervisor (the container payload and nspawn share a single upstream fdstore + * namespace, since there's only one init system per container). */ + if (!getenv("NOTIFY_SOCKET") || !fdstore_detected()) + return 0; + + if (strv_contains(tags, "FDSTOREREMOVE=1")) { + const char *fdname = strv_find_startswith(tags, "FDNAME="); + if (!fdname) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "Got FDSTOREREMOVE=1 from container payload without FDNAME=, ignoring."); + if (!fdname_is_valid(fdname)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "Got FDSTOREREMOVE=1 from container payload with invalid FDNAME='%s', ignoring.", + fdname); + + r = sd_notifyf(/* unset_environment= */ false, + "FDSTOREREMOVE=1\nFDNAME=payload-%s", fdname); + if (r < 0) + return log_warning_errno(r, "Failed to forward FDSTOREREMOVE upstream, ignoring: %m"); + } else if (strv_contains(tags, "FDSTORE=1")) { + if (fdset_isempty(fds)) { + log_debug("Got FDSTORE=1 from container payload without any attached file descriptors, ignoring."); + return 0; + } + + _cleanup_free_ int *fds_array = NULL; + int n; + + n = fdset_to_array(fds, &fds_array); + if (n < 0) + return log_warning_errno(n, "Failed to convert fdset to array, ignoring FDSTORE forward: %m"); + + const char *fdname = strv_find_startswith(tags, "FDNAME="); + bool fdpoll_off = strv_contains(tags, "FDPOLL=0"); + _cleanup_free_ char *msg = NULL; + unsigned n_fds = (unsigned) n; + + if (fdname && !fdname_is_valid(fdname)) { + log_warning("Got FDSTORE=1 from container payload with invalid FDNAME='%s', ignoring name.", fdname); + fdname = NULL; + } + + if (asprintf(&msg, "FDSTORE=1\nFDNAME=payload-%s%s%s", + fdname ?: "stored", + fdpoll_off ? "\nFDPOLL=" : "", + fdpoll_off ? "0" : "") < 0) + return log_oom(); + + r = sd_pid_notify_with_fds( + 0, + /* unset_environment= */ false, + msg, + fds_array, + n_fds); + if (r < 0) + return log_warning_errno(r, "Failed to forward FDSTORE upstream, ignoring: %m"); + } + + return 0; +} + static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { PidRef *inner_child_pid = ASSERT_PTR(userdata); int r; @@ -4682,7 +4699,8 @@ static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t r _cleanup_(pidref_done) PidRef sender_pid = PIDREF_NULL; _cleanup_strv_free_ char **tags = NULL; - r = notify_recv_strv(fd, &tags, /* ret_ucred= */ NULL, &sender_pid); + _cleanup_(fdset_freep) FDSet *fds = NULL; + r = notify_recv_with_fds_strv(fd, &tags, /* ret_ucred= */ NULL, &sender_pid, &fds); if (r == -EAGAIN) return 0; if (r < 0) @@ -4717,12 +4735,16 @@ static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t r (void) sd_notifyf(/* unset_environment= */ false, "STATUS=Container running."); } + (void) forward_fd_store(tags, fds); + return 0; } 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 +4805,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,13 +4980,23 @@ 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) { - free_and_replace(arg_bind_user_shell, settings->bind_user_shell); - arg_bind_user_shell_copy = settings->bind_user_shell_copy; + + if (!arg_settings_trusted) + log_warning("Ignoring bind user shell setting, file %s is not trusted.", path); + else { + free_and_replace(arg_bind_user_shell, settings->bind_user_shell); + arg_bind_user_shell_copy = settings->bind_user_shell_copy; + } } if ((arg_settings_mask & SETTING_NOTIFY_READY) == 0 && @@ -5091,6 +5128,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 +5177,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 +5271,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 +5310,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 +5533,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 +5609,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 +5624,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 +5692,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 +5925,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 +6076,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 +6104,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 +6133,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 +6146,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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + (void) DLOPEN_LIBSECCOMP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + (void) DLOPEN_LIBSELINUX(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); r = cg_has_legacy(); if (r < 0) @@ -6151,6 +6183,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. */ @@ -6161,6 +6199,10 @@ static int run(int argc, char *argv[]) { if (r < 0) goto finish; + r = split_boot_parameters(); + if (r < 0) + goto finish; + r = resolve_network_interface_names(arg_network_interfaces); if (r < 0) goto finish; @@ -6212,7 +6254,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 +6303,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 +6324,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 +6455,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 +6483,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 +6512,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 +6629,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 +6691,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 +6786,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..1654e1766b1eb 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,24 @@ 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..7d5f27bbda89d 100644 --- a/src/nsresourced/nsresourced-manager.c +++ b/src/nsresourced/nsresourced-manager.c @@ -5,20 +5,22 @@ #include "sd-daemon.h" -#include "bpf-dlopen.h" +#include "bpf-util.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" #include "env-util.h" +#include "errno-util.h" #include "event-util.h" #include "fd-util.h" #include "format-util.h" #include "fs-util.h" #include "log.h" #include "mkdir.h" +#include "namespace-util.h" #include "nsresourced-manager.h" #include "parse-util.h" #include "pidfd-util.h" @@ -33,7 +35,6 @@ #include "time-util.h" #include "umask-util.h" #include "unaligned.h" -#include "user-util.h" #include "userns-registry.h" #include "userns-restrict.h" @@ -89,6 +90,8 @@ int manager_new(Manager **ret) { _cleanup_(manager_freep) Manager *m = NULL; int r; + assert(ret); + m = new(Manager, 1); if (!m) return -ENOMEM; @@ -310,82 +313,61 @@ static int start_workers(Manager *m, bool explicit_request) { return 0; } -static void manager_release_userns_bpf(Manager *m, uint64_t inode) { -#if HAVE_VMLINUX_H - int r; - +static struct userns_restrict_bpf *manager_bpf(Manager *m) { assert(m); - if (inode == 0) - return; - - assert(m->userns_restrict_bpf); - - r = userns_restrict_reset_by_inode(m->userns_restrict_bpf, inode); - if (r < 0) - return (void) log_warning_errno(r, "Failed to remove namespace inode from BPF map, ignoring: %m"); +#if HAVE_VMLINUX_H + return m->userns_restrict_bpf; +#else + return NULL; #endif } -static void manager_release_userns_fds(Manager *m, uint64_t inode) { - int r; - +/* Releases the resources tied to a user namespace described by info. The caller must hold the + * registry lock if there is any chance of a concurrent writer (i.e. workers — true once the listen + * socket is open; not true during manager_startup() before that point). */ +static void manager_release_userns_by_info(Manager *m, UserNamespaceInfo *info) { assert(m); - assert(inode != 0); + assert(info); + assert(info->userns_inode != 0); + + /* Before tearing anything down, confirm by namespace id that the namespace we're releasing is + * actually dead. The kernel may have recycled this inode for a freshly created live namespace + * (e.g. between a BPF death event firing and us getting here); proceeding in that case would + * clobber the new namespace's BPF allowlist, fdstore fd and registry entry. */ + if (info->userns_id != 0) { + _cleanup_close_ int probe_fd = namespace_open_by_id(info->userns_id); + if (probe_fd >= 0) { + log_warning("Refusing to release user namespace %" PRIu64 " (id %" PRIu64 "): the namespace is still alive.", + info->userns_inode, info->userns_id); + return; + } + if (probe_fd != -ESTALE && + !ERRNO_IS_NEG_PRIVILEGE(probe_fd) && + !ERRNO_IS_NEG_NOT_SUPPORTED(probe_fd)) + log_warning_errno(probe_fd, + "Failed to probe liveness of user namespace %" PRIu64 " (id %" PRIu64 "), proceeding with release: %m", + info->userns_inode, info->userns_id); + } - r = sd_notifyf(/* unset_environment= */ false, - "FDSTOREREMOVE=1\n" - "FDNAME=userns-%" PRIu64 "\n", inode); - if (r < 0) - log_warning_errno(r, "Failed to send fd store removal message, ignoring: %m"); + userns_registry_release_by_info(manager_bpf(m), m->registry_fd, info); } static void manager_release_userns_by_inode(Manager *m, uint64_t inode) { _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; - _cleanup_close_ int lock_fd = -EBADF; int r; assert(m); assert(inode != 0); - lock_fd = userns_registry_lock(m->registry_fd); - if (lock_fd < 0) - return (void) log_error_errno(lock_fd, "Failed to lock registry: %m"); - r = userns_registry_load_by_userns_inode(m->registry_fd, inode, &userns_info); - if (r < 0) - log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, - "Failed to find userns for inode %" PRIu64 ", ignoring: %m", inode); - - if (DEBUG_LOGGING) { - if (userns_info && uid_is_valid(userns_info->start_uid)) - log_debug("Removing user namespace mapping %" PRIu64 " for UID " UID_FMT ".", inode, userns_info->start_uid); - else - log_debug("Removing user namespace mapping %" PRIu64 ".", inode); - } - - /* Remove the BPF rules */ - manager_release_userns_bpf(m, inode); - - /* Remove the resources from the fdstore */ - manager_release_userns_fds(m, inode); - - /* And finally remove the resources file from disk */ - if (userns_info) { - /* Remove the cgroups of this userns */ - r = userns_info_remove_cgroups(userns_info); - if (r < 0) - log_warning_errno(r, "Failed to remove cgroups of user namespace, ignoring: %m"); + if (r >= 0) + return manager_release_userns_by_info(m, userns_info); - /* Remove the netifs of this userns */ - r = userns_info_remove_netifs(userns_info); - if (r < 0) - log_warning_errno(r, "Failed to remove netifs of user namespace, ignoring: %m"); - - r = userns_registry_remove(m->registry_fd, userns_info); - if (r < 0) - log_warning_errno(r, "Failed to remove user namespace '%s', ignoring.", userns_info->name); - } + /* No registry entry to consult — fall through to inode-only cleanup of kernel resources. */ + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to load registry entry for user namespace %" PRIu64 ", proceeding with inode-only cleanup: %m", inode); + userns_registry_release_by_userns_inode(manager_bpf(m), m->registry_fd, inode); } static int manager_scan_registry(Manager *m, Set **registry_inodes) { @@ -537,6 +519,13 @@ static int ringbuf_event(void *userdata, void *data, size_t size) { if ((size % sizeof(unsigned)) != 0) /* Not multiples of "unsigned"? */ return -EIO; + /* Workers are active alongside us once we're processing BPF events, so we have to serialize + * registry mutations against them. The startup-time release callers run before any worker + * exists and skip the lock. */ + _cleanup_close_ int lock_fd = userns_registry_lock(m->registry_fd); + if (lock_fd < 0) + return log_error_errno(lock_fd, "Failed to lock registry: %m"); + n = size / sizeof(unsigned); for (size_t i = 0; i < n; i++) { const void *d; @@ -646,6 +635,33 @@ int manager_startup(Manager *m) { manager_release_userns_by_inode(m, inode); } + /* Look for registry entries whose user namespace has died without us getting a BPF + * notification — e.g. because the BPF ring buffer overflowed, the kprobe is missing, or + * something else dropped the fd store entry without going through our cleanup path. Each + * registry entry stores the kernel's unique namespace identifier; ask the kernel to open + * the namespace by that identifier and release the entry if the lookup fails. Entries + * written by older versions don't carry the identifier, and old kernels (or running + * outside the initial user namespace) don't support lookup by it — in those cases we leave + * the entry alone. */ + + SET_FOREACH(p, registry_inodes) { + uint64_t inode = PTR_TO_UINT32(p); + + r = userns_registry_reap_if_dead(manager_bpf(m), m->registry_fd, inode); + if (r < 0) { + log_debug_errno(r, "Failed to probe liveness of user namespace %" PRIu64 ", ignoring: %m", inode); + continue; + } + if (r == USERNS_REAP_UNSUPPORTED) { + /* Can't look namespaces up by id at all here (old kernel, or not in the + * initial user namespace) — no entry is probeable, so stop rather than + * continuing to probe (and log) once per entry. */ + log_debug("Cannot detect stale registry entries, skipping the rest."); + break; + } + /* USERNS_REAP_RELEASED, _ALIVE, or _INDETERMINATE — nothing more to do for this entry. */ + } + r = manager_make_listen_socket(m); if (r < 0) return r; diff --git a/src/nsresourced/nsresourcework.c b/src/nsresourced/nsresourcework.c index 3b2450529c3ac..b8d68cbb427e1 100644 --- a/src/nsresourced/nsresourcework.c +++ b/src/nsresourced/nsresourcework.c @@ -374,66 +374,160 @@ static int vl_method_get_memberships(sd_varlink *link, sd_json_variant *paramete return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); } -static int uid_is_available(int registry_dir_fd, uid_t candidate, int parent_userns_fd) { +static int registry_range_is_available( + struct userns_restrict_bpf *bpf, + int registry_dir_fd, + uid_t candidate, + bool gid) { + + _cleanup_(userns_info_freep) UserNamespaceInfo *owner = NULL; int r; assert(registry_dir_fd >= 0); - log_debug("Checking if UID " UID_FMT " is available.", candidate); + /* Checks whether the (UID or GID) range starting at candidate is free to allocate as a fresh + * transient range. If an existing registry entry claims it but its owning namespace has since + * died — without a BPF death event reaping it yet — we reclaim it on the spot and treat the range + * as available. Unlike delegations, transient registrations have no ancestor chain, so a single + * reap suffices. */ - uint64_t parent_userns_inode = 0; - struct stat parent_st; - if (fstat(parent_userns_fd, &parent_st) < 0) - return log_debug_errno(errno, "Failed to fstat parent user namespace: %m"); - parent_userns_inode = parent_st.st_ino; + r = gid ? userns_registry_gid_exists(registry_dir_fd, (gid_t) candidate) + : userns_registry_uid_exists(registry_dir_fd, candidate); + if (r < 0) + return r; + if (r == 0) + return true; /* No registry entry for this range → available. */ - r = userns_registry_uid_exists(registry_dir_fd, candidate); + r = gid ? userns_registry_load_by_start_gid(registry_dir_fd, (gid_t) candidate, &owner) + : userns_registry_load_by_start_uid(registry_dir_fd, candidate, &owner); + if (r == -ENOENT) + return true; /* Raced away between the existence check and the load → available. */ if (r < 0) return r; - if (r > 0) - return false; - r = userns_registry_gid_exists(registry_dir_fd, (gid_t) candidate); + r = userns_registry_reap_if_dead(bpf, registry_dir_fd, owner->userns_inode); if (r < 0) return r; - if (r > 0) - return false; + if (r == USERNS_REAP_RELEASED) + return true; /* Owner was dead and reaped → range is now free. */ + + /* Owner is alive or its liveness couldn't be determined → we can't reclaim it, so the range + * is taken. */ + log_debug("%s" UID_FMT " unavailable: already registered in userns registry.", + gid ? "GID " : "UID ", candidate); + return false; +} - /* Also check delegation files. If parent_userns_inode is set and matches the delegation's userns - * inode, the UID is available because the parent owns that delegation. */ - r = userns_registry_delegation_uid_exists(registry_dir_fd, candidate); +static int delegation_range_is_available( + struct userns_restrict_bpf *bpf, + int registry_dir_fd, + uid_t candidate, + uint64_t parent_userns_inode, + bool gid) { + + int r; + + assert(registry_dir_fd >= 0); + + /* Checks whether the (UID or GID) range starting at candidate is free to allocate as far as + * the delegation registry is concerned. A delegated range is available only if it's owned by + * the requesting parent user namespace (which may then sub-allocate within it). If it's owned + * by another namespace that has since died — without a BPF death event reaping it yet — we reclaim + * it on the spot, which restores the range to its ancestor (possibly the parent), and + * re-evaluate. This walks a chain of dead ancestors until it reaches a live owner, the + * requesting parent, or a fully freed range. */ + + r = gid ? userns_registry_delegation_gid_exists(registry_dir_fd, (gid_t) candidate) + : userns_registry_delegation_uid_exists(registry_dir_fd, candidate); if (r < 0) return r; - if (r > 0) { + if (r == 0) + return true; /* No delegation for this range → available. */ + + for (;;) { _cleanup_(delegated_userns_info_done) DelegatedUserNamespaceInfo delegation = DELEGATED_USER_NAMESPACE_INFO_NULL; - r = userns_registry_load_delegation_by_uid(registry_dir_fd, candidate, &delegation); + + r = gid ? userns_registry_load_delegation_by_gid(registry_dir_fd, (gid_t) candidate, &delegation) + : userns_registry_load_delegation_by_uid(registry_dir_fd, candidate, &delegation); + if (r == -ENOENT) + return true; /* Delegation removed while reaping (no ancestor left) → fully free. */ if (r < 0) return r; - if (delegation.userns_inode != parent_userns_inode) - return false; + if (delegation.userns_inode == parent_userns_inode) { + /* The parent userns owns this delegation, so the range is available for nested + * allocation. */ + log_debug("%s" UID_FMT " is delegated by parent userns inode %" PRIu64 ", available for nested allocation.", + gid ? "GID " : "UID ", candidate, parent_userns_inode); + return true; + } - /* The parent userns owns this delegation, so the UID is available for nested allocation */ - log_debug("UID " UID_FMT " is delegated by parent userns inode %" PRIu64 ", available for nested allocation.", - candidate, parent_userns_inode); + /* Owned by some other namespace. If that namespace is dead, reclaim it (restoring the + * range to its ancestor) and loop to re-evaluate; otherwise the range is genuinely + * taken. */ + r = userns_registry_reap_if_dead(bpf, registry_dir_fd, delegation.userns_inode); + if (r < 0) + return r; + if (r == USERNS_REAP_RELEASED) + continue; /* Reaped a dead owner; re-evaluate the now-restored delegation. */ + + /* Owner is alive, or its liveness couldn't be determined (no recorded id, or a dead + * ancestor that an earlier reap popped into ownership but left no registry entry to + * drive a further restore). We can't reclaim the range here — and mustn't loop, since + * re-reaping would not make progress — so treat it as taken. */ + log_debug("%s" UID_FMT " unavailable: delegated to userns inode %" PRIu64 " that can't be reclaimed (parent is %" PRIu64 ").", + gid ? "GID " : "UID ", candidate, delegation.userns_inode, parent_userns_inode); + return false; } +} + +static int uid_is_available( + struct userns_restrict_bpf *bpf, + int registry_dir_fd, + uid_t candidate, + int parent_userns_fd) { + + int r; - r = userns_registry_delegation_gid_exists(registry_dir_fd, (gid_t) candidate); + assert(registry_dir_fd >= 0); + + log_debug("Checking if UID " UID_FMT " is available.", candidate); + + uint64_t parent_userns_inode = 0; + struct stat parent_st; + if (fstat(parent_userns_fd, &parent_st) < 0) + return log_debug_errno(errno, "Failed to fstat parent user namespace: %m"); + parent_userns_inode = parent_st.st_ino; + + /* Check whether an existing transient registration claims this range; a dead owner is reclaimed + * inline so its range doesn't stay blocked. The UID and GID are checked separately as either + * can exist independently. */ + r = registry_range_is_available(bpf, registry_dir_fd, candidate, /* gid= */ false); if (r < 0) return r; - if (r > 0) { - _cleanup_(delegated_userns_info_done) DelegatedUserNamespaceInfo delegation = DELEGATED_USER_NAMESPACE_INFO_NULL; - r = userns_registry_load_delegation_by_gid(registry_dir_fd, candidate, &delegation); - if (r < 0) - return r; + if (r == 0) + return false; - if (delegation.userns_inode != parent_userns_inode) - return false; + r = registry_range_is_available(bpf, registry_dir_fd, candidate, /* gid= */ true); + if (r < 0) + return r; + if (r == 0) + return false; - /* The parent userns owns this delegation, so the UID is available for nested allocation */ - log_debug("UID " UID_FMT " is delegated by parent userns inode %" PRIu64 ", available for nested allocation.", - candidate, parent_userns_inode); - } + /* Also check delegation files. A delegated range is only available for nested allocation when + * the requesting parent owns it; dead owners are reclaimed inline so their ranges don't stay + * blocked. We check the UID and GID delegations separately as either can exist independently. */ + r = delegation_range_is_available(bpf, registry_dir_fd, candidate, parent_userns_inode, /* gid= */ false); + if (r < 0) + return r; + if (r == 0) + return false; + + r = delegation_range_is_available(bpf, registry_dir_fd, candidate, parent_userns_inode, /* gid= */ true); + if (r < 0) + return r; + if (r == 0) + return false; r = is_our_namespace(parent_userns_fd, NAMESPACE_USER); if (r < 0) @@ -447,14 +541,18 @@ static int uid_is_available(int registry_dir_fd, uid_t candidate, int parent_use * nsresourced user namespace, but not in the nspawn user namespace). */ r = userdb_by_uid(candidate, /* match= */ NULL, USERDB_AVOID_MULTIPLEXER, /* ret= */ NULL); - if (r >= 0) + if (r >= 0) { + log_debug("UID " UID_FMT " unavailable: known to userdb.", candidate); return false; + } if (r != -ESRCH) return r; r = groupdb_by_gid(candidate, /* match= */ NULL, USERDB_AVOID_MULTIPLEXER, /* ret= */ NULL); - if (r >= 0) + if (r >= 0) { + log_debug("UID " UID_FMT " unavailable: GID known to groupdb.", candidate); return false; + } if (r != -ESRCH) return r; } @@ -501,7 +599,43 @@ static int name_is_available( return true; } +static int inode_slot_is_available( + int registry_dir_fd, + int userns_fd, + struct userns_restrict_bpf *bpf, + uint64_t inode) { + + _cleanup_(userns_info_freep) UserNamespaceInfo *existing = NULL; + int r; + + assert(registry_dir_fd >= 0); + assert(userns_fd >= 0); + assert(inode != 0); + + /* Returns true if the registry has no entry for this inode (after cleaning up any stale + * leftover from a previously-registered namespace whose inode was recycled by the kernel), + * false if a live registration already occupies the slot, negative on error. */ + + r = userns_registry_load_by_userns_inode(registry_dir_fd, inode, &existing); + if (r == -ENOENT) + return true; + if (r < 0) + return log_debug_errno(r, "Failed to load existing registry entry: %m"); + + r = userns_info_verify_fd(userns_fd, existing); + if (r >= 0) + return false; + if (r != -ESTALE) + return log_debug_errno(r, "Failed to verify user namespace identity: %m"); + + log_debug("Inode %" PRIu64 " was reused by the kernel; cleaning up stale registry entry for namespace id %" PRIu64 ".", + inode, existing->userns_id); + userns_registry_release_by_info(bpf, registry_dir_fd, existing); + return true; +} + static int allocate_one( + struct userns_restrict_bpf *bpf, int registry_dir_fd, const char *name, uint32_t size, @@ -574,7 +708,7 @@ static int allocate_one( /* We only check the base UID for each range. Pass the parent userns inode so that * allocating from a delegated range owned by the parent is allowed. */ - r = uid_is_available(registry_dir_fd, candidate, parent_userns_fd); + r = uid_is_available(bpf, registry_dir_fd, candidate, parent_userns_fd); if (r < 0) return log_debug_errno(r, "Can't determine if UID range " UID_FMT " is available: %m", candidate); if (r > 0) @@ -604,6 +738,7 @@ static int allocate_now( int registry_dir_fd, int userns_fd, int parent_userns_fd, + struct userns_restrict_bpf *bpf, UserNamespaceInfo *info, int *ret_lock_fd) { @@ -626,6 +761,12 @@ static int allocate_now( if (r < 0) return log_debug_errno(r, "Failed to read userns UID range: %m"); + log_debug("Parent user namespace exposes %zu UID range(s) inside:", uid_range_entries(candidates)); + if (DEBUG_LOGGING) + FOREACH_ARRAY(e, candidates->entries, candidates->n_entries) + log_debug(" " UID_FMT "…" UID_FMT " (size " UID_FMT ")", + e->start, e->start + e->nr - 1, e->nr); + _cleanup_close_ int lock_fd = -EBADF; lock_fd = userns_registry_lock(registry_dir_fd); if (lock_fd < 0) @@ -638,10 +779,10 @@ static int allocate_now( if (r >= USERNS_PER_UID) return log_debug_errno(SYNTHETIC_ERRNO(EUSERS), "User already registered %i user namespaces, refusing.", r); - r = userns_registry_inode_exists(registry_dir_fd, info->userns_inode); + r = inode_slot_is_available(registry_dir_fd, userns_fd, bpf, info->userns_inode); if (r < 0) return r; - if (r > 0) + if (r == 0) return -EDEADLK; r = name_is_available(registry_dir_fd, info->name); @@ -654,6 +795,7 @@ static int allocate_now( * allocate a transient range. */ if (!uid_is_valid(info->start_uid) && !gid_is_valid(info->start_gid)) { r = allocate_one( + bpf, registry_dir_fd, info->name, info->size, parent_userns_fd, @@ -672,6 +814,7 @@ static int allocate_now( FOREACH_ARRAY(delegate, info->delegates, info->n_delegates) { r = allocate_one( + bpf, registry_dir_fd, /* name= */ NULL, delegate->size, @@ -1251,6 +1394,10 @@ static int vl_method_allocate_user_range(sd_varlink *link, sd_json_variant *para if (parent_userns_fd < 0) return log_debug_errno(errno, "Failed to get parent user namespace: %m"); + struct stat parent_st = {}; + if (DEBUG_LOGGING && fstat(parent_userns_fd, &parent_st) < 0) + return log_debug_errno(errno, "Failed to fstat() parent user namespace fd: %m"); + r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) return r; @@ -1259,6 +1406,15 @@ static int vl_method_allocate_user_range(sd_varlink *link, sd_json_variant *para if (r < 0) return r; + log_debug("Processing AllocateUserRange request: name='%s' type=%s peer=" UID_FMT ":" GID_FMT + " userns=" INO_FMT " parent_userns=" INO_FMT + " size=%" PRIu32 " delegates=%" PRIu32 " mapForeign=%s", + userns_name, + p.type == ALLOCATE_USER_RANGE_SELF ? "self" : "managed", + peer_uid, peer_gid, + userns_st.st_ino, parent_st.st_ino, + p.size, p.delegate_container_ranges, yes_no(p.map_foreign)); + const char *polkit_details[] = { "name", userns_name, NULL, @@ -1271,7 +1427,8 @@ static int vl_method_allocate_user_range(sd_varlink *link, sd_json_variant *para polkit_details, /* good_user= */ UID_INVALID, POLKIT_DEFAULT_ALLOW, /* If no polkit is installed, allow unpriv userns namespace allocation */ - &c->polkit_registry); + &c->polkit_registry, + /* ret_admin= */ NULL); if (r <= 0) return r; @@ -1295,6 +1452,8 @@ static int vl_method_allocate_user_range(sd_varlink *link, sd_json_variant *para userns_info->owner = peer_uid; userns_info->userns_inode = userns_st.st_ino; + if (ioctl(userns_fd, NS_GET_ID, &userns_info->userns_id) < 0) + log_debug_errno(errno, "Failed to query userns ID, ignoring: %m"); userns_info->size = p.size; userns_info->target_uid = p.target; userns_info->target_gid = (gid_t) p.target; @@ -1340,7 +1499,7 @@ static int vl_method_allocate_user_range(sd_varlink *link, sd_json_variant *para userns_info->n_delegates = p.delegate_container_ranges; } - r = allocate_now(registry_dir_fd, userns_fd, parent_userns_fd, userns_info, &lock_fd); + r = allocate_now(registry_dir_fd, userns_fd, parent_userns_fd, c->bpf, userns_info, &lock_fd); if (r == -EHOSTDOWN) /* The needed UID range is not delegated to us */ return sd_varlink_error(link, "io.systemd.NamespaceResource.DynamicRangeUnavailable", NULL); if (r == -EBUSY) /* All used up */ @@ -1534,7 +1693,8 @@ static int vl_method_register_user_namespace(sd_varlink *link, sd_json_variant * polkit_details, /* good_user= */ UID_INVALID, POLKIT_DEFAULT_ALLOW, /* If no polkit is installed, allow unpriv userns namespace registration */ - &c->polkit_registry); + &c->polkit_registry, + /* ret_admin= */ NULL); if (r <= 0) return r; @@ -1553,10 +1713,10 @@ static int vl_method_register_user_namespace(sd_varlink *link, sd_json_variant * if (lock_fd < 0) return log_debug_errno(lock_fd, "Failed to open nsresource registry lock file: %m"); - r = userns_registry_inode_exists(registry_dir_fd, userns_st.st_ino); + r = inode_slot_is_available(registry_dir_fd, userns_fd, c->bpf, userns_st.st_ino); if (r < 0) return r; - if (r > 0) + if (r == 0) return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceExists", NULL); r = name_is_available(registry_dir_fd, userns_name); @@ -1575,6 +1735,8 @@ static int vl_method_register_user_namespace(sd_varlink *link, sd_json_variant * userns_info->owner = peer_uid; userns_info->userns_inode = userns_st.st_ino; + if (ioctl(userns_fd, NS_GET_ID, &userns_info->userns_id) < 0) + log_debug_errno(errno, "Failed to query userns ID, ignoring: %m"); r = userns_registry_store(registry_dir_fd, userns_info); if (r < 0) @@ -1679,7 +1841,8 @@ static int vl_method_add_mount_to_user_namespace(sd_varlink *link, sd_json_varia /* details= */ NULL, /* good_user= */ UID_INVALID, POLKIT_DEFAULT_ALLOW, /* If no polkit is installed, allow delegation of mounts to registered userns */ - &c->polkit_registry); + &c->polkit_registry, + /* ret_admin= */ NULL); if (r <= 0) return r; @@ -1702,6 +1865,12 @@ static int vl_method_add_mount_to_user_namespace(sd_varlink *link, sd_json_varia if (r < 0) return r; + r = userns_info_verify_fd(userns_fd, userns_info); + if (r == -ESTALE) + return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL); + if (r < 0) + return log_debug_errno(r, "Failed to verify user namespace identity: %m"); + if (!c->bpf) { r = userns_restrict_install(/* pin= */ true, &c->bpf); if (r < 0) @@ -1832,7 +2001,8 @@ static int vl_method_add_cgroup_to_user_namespace(sd_varlink *link, sd_json_vari /* details= */ NULL, /* good_user= */ UID_INVALID, POLKIT_DEFAULT_ALLOW, /* If no polkit is installed, allow delegation of cgroups to registered userns */ - &c->polkit_registry); + &c->polkit_registry, + /* ret_admin= */ NULL); if (r <= 0) return r; @@ -1854,6 +2024,12 @@ static int vl_method_add_cgroup_to_user_namespace(sd_varlink *link, sd_json_vari if (r < 0) return r; + r = userns_info_verify_fd(userns_fd, userns_info); + if (r == -ESTALE) + return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL); + if (r < 0) + return log_debug_errno(r, "Failed to verify user namespace identity: %m"); + /* The user namespace must have a user assigned */ if (userns_info->size == 0) return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceWithoutUserRange", NULL); @@ -1925,7 +2101,7 @@ static void hash_ether_addr(UserNamespaceInfo *userns_info, const char *ifname, siphash24_compress_byte(0, &state); /* separator */ siphash24_compress_string(strempty(ifname), &state); siphash24_compress_byte(0, &state); /* separator */ - n = htole64(n); /* add the 'index' to the mix in an endianess-independent fashion */ + n = htole64(n); /* add the 'index' to the mix in an endianness-independent fashion */ siphash24_compress_typesafe(n, &state); h = htole64(siphash24_finalize(&state)); @@ -2225,7 +2401,8 @@ static int vl_method_add_netif_to_user_namespace(sd_varlink *link, sd_json_varia polkit_details, /* good_user= */ UID_INVALID, POLKIT_DEFAULT_ALLOW, /* If no polkit is installed, allow delegation of network interfaces to registered userns */ - &c->polkit_registry); + &c->polkit_registry, + /* ret_admin= */ NULL); if (r <= 0) return r; @@ -2247,6 +2424,12 @@ static int vl_method_add_netif_to_user_namespace(sd_varlink *link, sd_json_varia if (r < 0) return r; + r = userns_info_verify_fd(userns_fd, userns_info); + if (r == -ESTALE) + return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL); + if (r < 0) + return log_debug_errno(r, "Failed to verify user namespace identity: %m"); + if (strv_length(userns_info->netifs) > USER_NAMESPACE_NETIFS_DELEGATE_MAX) return sd_varlink_error(link, "io.systemd.NamespaceResource.TooManyNetworkInterfaces", NULL); diff --git a/src/nsresourced/userns-registry.c b/src/nsresourced/userns-registry.c index 371a35086f424..0da6a6a7c692b 100644 --- a/src/nsresourced/userns-registry.c +++ b/src/nsresourced/userns-registry.c @@ -1,8 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include +#include #include +#include "sd-daemon.h" #include "sd-json.h" #include "sd-netlink.h" @@ -14,6 +17,7 @@ #include "format-util.h" #include "fs-util.h" #include "json-util.h" +#include "namespace-util.h" #include "path-util.h" #include "recurse-dir.h" #include "rm-rf.h" @@ -23,6 +27,7 @@ #include "uid-classification.h" #include "user-util.h" #include "userns-registry.h" +#include "userns-restrict.h" int userns_registry_open_fd(void) { int fd; @@ -65,14 +70,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 +95,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 +152,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 +195,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; @@ -248,6 +244,7 @@ static int userns_registry_load(int dir_fd, const char *fn, UserNamespaceInfo ** { "owner", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, owner), SD_JSON_MANDATORY }, { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(UserNamespaceInfo, name), SD_JSON_MANDATORY }, { "userns", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint64, offsetof(UserNamespaceInfo, userns_inode), SD_JSON_MANDATORY }, + { "usernsId", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint64, offsetof(UserNamespaceInfo, userns_id), 0 }, { "size", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint32, offsetof(UserNamespaceInfo, size), 0 }, { "start", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, start_uid), 0 }, { "target", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, target_uid), 0 }, @@ -376,23 +373,6 @@ int userns_registry_name_exists(int dir_fd, const char *name) { return true; } -int userns_registry_inode_exists(int dir_fd, uint64_t inode) { - _cleanup_free_ char *fn = NULL; - - assert(dir_fd >= 0); - - if (inode <= 0) - return -EINVAL; - - if (asprintf(&fn, "i%" PRIu64 ".userns", inode) < 0) - return -ENOMEM; - - if (faccessat(dir_fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0) - return errno == ENOENT ? false : -errno; - - return true; -} - int userns_registry_load_by_start_uid(int dir_fd, uid_t start, UserNamespaceInfo **ret) { _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; _cleanup_close_ int registry_fd = -EBADF; @@ -492,6 +472,139 @@ int userns_registry_load_by_userns_inode(int dir_fd, uint64_t inode, UserNamespa return 0; } +static void release_userns_inode_resources(struct userns_restrict_bpf *bpf, uint64_t inode) { + int r; + + assert(inode != 0); + + if (bpf) { + r = userns_restrict_reset_by_inode(bpf, inode); + if (r < 0) + log_warning_errno(r, "Failed to remove namespace inode from BPF map, ignoring: %m"); + } + + r = sd_notifyf(/* unset_environment= */ false, + "FDSTOREREMOVE=1\n" + "FDNAME=userns-%" PRIu64 "\n", inode); + if (r < 0) + log_warning_errno(r, "Failed to send fd store removal message, ignoring: %m"); +} + +void userns_registry_release_by_info(struct userns_restrict_bpf *bpf, int dir_fd, UserNamespaceInfo *info) { + int r; + + assert(dir_fd >= 0); + assert(info); + assert(info->userns_inode != 0); + + if (DEBUG_LOGGING) { + if (uid_is_valid(info->start_uid)) + log_debug("Removing user namespace mapping %" PRIu64 " for UID " UID_FMT ".", info->userns_inode, info->start_uid); + else + log_debug("Removing user namespace mapping %" PRIu64 ".", info->userns_inode); + } + + release_userns_inode_resources(bpf, info->userns_inode); + + r = userns_info_remove_cgroups(info); + if (r < 0) + log_warning_errno(r, "Failed to remove cgroups of user namespace, ignoring: %m"); + + r = userns_info_remove_netifs(info); + if (r < 0) + log_warning_errno(r, "Failed to remove netifs of user namespace, ignoring: %m"); + + r = userns_registry_remove(dir_fd, info); + if (r < 0) + log_warning_errno(r, "Failed to remove user namespace '%s', ignoring.", info->name); +} + +void userns_registry_release_by_userns_inode(struct userns_restrict_bpf *bpf, int dir_fd, uint64_t inode) { + _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; + int r; + + assert(dir_fd >= 0); + assert(inode != 0); + + r = userns_registry_load_by_userns_inode(dir_fd, inode, &userns_info); + if (r >= 0) + return userns_registry_release_by_info(bpf, dir_fd, userns_info); + + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to find userns for inode %" PRIu64 ", ignoring: %m", inode); + log_debug("Removing user namespace mapping %" PRIu64 ".", inode); + + /* No registry entry — still clean up the inode-keyed kernel resources (BPF map allowlist and + * fdstore fd), which can outlive a missing registry record. */ + release_userns_inode_resources(bpf, inode); +} + +int userns_registry_reap_if_dead(struct userns_restrict_bpf *bpf, int dir_fd, uint64_t inode) { + _cleanup_(userns_info_freep) UserNamespaceInfo *info = NULL; + int r; + + assert(dir_fd >= 0); + assert(inode != 0); + + /* This is the shared engine behind both the runtime allocation hot path and the startup + * registry sweep: it reclaims ranges blocked by a dead namespace that no BPF death event + * cleaned up, without waiting for the manager to restart. */ + + r = userns_registry_load_by_userns_inode(dir_fd, inode, &info); + if (r == -ENOENT) + /* No entry is registered for this inode any more (already gone) — e.g. a dead ancestor + * still referenced by a delegation chain. Nothing to reap. */ + return USERNS_REAP_INDETERMINATE; + if (r < 0) + return r; + + if (info->userns_id == 0) + return USERNS_REAP_INDETERMINATE; /* Entry predates ns id tracking, can't probe authoritatively. */ + + _cleanup_close_ int probe_fd = namespace_open_by_id(info->userns_id); + if (probe_fd >= 0) + return USERNS_REAP_ALIVE; /* Still alive (or the inode was recycled for a live namespace). */ + if (ERRNO_IS_NEG_PRIVILEGE(probe_fd) || ERRNO_IS_NEG_NOT_SUPPORTED(probe_fd) || probe_fd == -EINVAL) + /* EPERM/EACCES (not in the initial user namespace, missing CAP_SYS_ADMIN), or + * ENOTSUP/ENOSYS/EINVAL (kernel new enough for NS_GET_ID but too old for + * open_by_handle_at() lookup by id on nsfs) — we can't confirm death for this or any + * other entry. The userns_id == 0 case is already handled above, so -EINVAL here means + * the lookup is unsupported rather than a bad id. */ + return USERNS_REAP_UNSUPPORTED; + if (probe_fd != -ESTALE) + return probe_fd; /* Unexpected probe error. */ + + log_debug("User namespace %" PRIu64 " (id %" PRIu64 ") refers to a dead namespace, reclaiming its resources.", + inode, info->userns_id); + + userns_registry_release_by_info(bpf, dir_fd, info); + return USERNS_REAP_RELEASED; +} + +int userns_info_verify_fd(int userns_fd, const UserNamespaceInfo *info) { + uint64_t live_id; + + assert(userns_fd >= 0); + assert(info); + + /* Verifies that userns_fd refers to the same user namespace described by info, distinguishing a + * live namespace from a different one that happens to have inherited the same inode after the + * original was destroyed. Returns 0 on match (also when the check cannot be performed because + * the stored or live id is unavailable on older kernels), -ESTALE on mismatch, or another + * negative errno on unexpected failure. */ + + if (info->userns_id == 0) + return 0; + + if (ioctl(userns_fd, NS_GET_ID, &live_id) < 0) { + if (ERRNO_IS_IOCTL_NOT_SUPPORTED(errno)) + return 0; + return -errno; + } + + return live_id == info->userns_id ? 0 : -ESTALE; +} + int userns_registry_load_by_name(int dir_fd, const char *name, UserNamespaceInfo **ret) { _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; _cleanup_close_ int registry_fd = -EBADF; @@ -574,6 +687,7 @@ int userns_registry_store(int dir_fd, UserNamespaceInfo *info) { SD_JSON_BUILD_PAIR_UNSIGNED("owner", info->owner), SD_JSON_BUILD_PAIR_STRING("name", info->name), SD_JSON_BUILD_PAIR_UNSIGNED("userns", info->userns_inode), + SD_JSON_BUILD_PAIR_CONDITION(info->userns_id != 0, "usernsId", SD_JSON_BUILD_UNSIGNED(info->userns_id)), SD_JSON_BUILD_PAIR_CONDITION(info->size > 0, "size", SD_JSON_BUILD_UNSIGNED(info->size)), SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->start_uid), "start", SD_JSON_BUILD_UNSIGNED(info->start_uid)), SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->target_uid), "target", SD_JSON_BUILD_UNSIGNED(info->target_uid)), @@ -847,9 +961,11 @@ int userns_registry_remove(int dir_fd, UserNamespaceInfo *info) { continue; } - _cleanup_free_ char *delegate_uid_fn = NULL; + _cleanup_free_ char *delegate_uid_fn = NULL, *delegate_gid_fn = NULL; if (asprintf(&delegate_uid_fn, "u" UID_FMT ".delegate", delegate->start_uid) < 0) return log_oom_debug(); + if (asprintf(&delegate_gid_fn, "g" GID_FMT ".delegate", delegate->start_gid) < 0) + return log_oom_debug(); if (existing.n_ancestor_userns > 0) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *delegate_def = NULL, *ancestor_array = NULL; @@ -885,10 +1001,18 @@ int userns_registry_remove(int dir_fd, UserNamespaceInfo *info) { return log_debug_errno(r, "Failed to format delegation JSON object: %m"); r = write_string_file_at(dir_fd, delegate_uid_fn, delegate_buf, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); - if (r < 0) + if (r < 0) { RET_GATHER(ret, log_debug_errno(r, "Failed to write restored delegation data to '%s' in registry: %m", delegate_uid_fn)); + continue; + } + + /* The atomic write above replaced the UID file with a new inode, so the + * hardlink to the GID file is now broken. Re-create it to keep the two in + * sync. */ + r = linkat_replace(dir_fd, delegate_uid_fn, dir_fd, delegate_gid_fn); + if (r < 0) + RET_GATHER(ret, log_debug_errno(r, "Failed to re-link '%s' to '%s' in registry: %m", delegate_uid_fn, delegate_gid_fn)); - /* GID link already points to the UID file, no need to update it */ continue; } @@ -900,10 +1024,6 @@ int userns_registry_remove(int dir_fd, UserNamespaceInfo *info) { if (r < 0) RET_GATHER(ret, log_debug_errno(r, "Failed to remove %s: %m", delegate_uid_fn)); - _cleanup_free_ char *delegate_gid_fn = NULL; - if (asprintf(&delegate_gid_fn, "g" GID_FMT ".delegate", delegate->start_gid) < 0) - return log_oom_debug(); - r = RET_NERRNO(unlinkat(dir_fd, delegate_gid_fn, 0)); if (r < 0) RET_GATHER(ret, log_debug_errno(r, "Failed to remove %s: %m", delegate_gid_fn)); @@ -1099,7 +1219,7 @@ int userns_registry_per_uid(int dir_fd, uid_t owner) { _cleanup_free_ DirectoryEntries *de = NULL; - r = readdir_all_at(dir_fd, uid_fn, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &de); + r = readdir_all_at(dir_fd, uid_fn, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_REGULAR, &de); if (r == -ENOENT) return 0; if (r < 0) @@ -1108,9 +1228,6 @@ int userns_registry_per_uid(int dir_fd, uid_t owner) { FOREACH_ARRAY(i, de->entries, de->n_entries) { struct dirent *e = *i; - if (e->d_type != DT_REG) - continue; - if (!startswith(e->d_name, "i") || !endswith(e->d_name, ".userns")) continue; diff --git a/src/nsresourced/userns-registry.h b/src/nsresourced/userns-registry.h index f08b238861ae4..ba537e82944eb 100644 --- a/src/nsresourced/userns-registry.h +++ b/src/nsresourced/userns-registry.h @@ -3,6 +3,8 @@ #include "shared-forward.h" +struct userns_restrict_bpf; + #define USER_NAMESPACE_CGROUPS_DELEGATE_MAX 16U #define USER_NAMESPACE_NETIFS_DELEGATE_MAX 16U #define USER_NAMESPACE_DELEGATIONS_MAX 16U @@ -24,12 +26,12 @@ 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; char *name; uint64_t userns_inode; + uint64_t userns_id; /* Unique namespace identifier from NS_GET_ID, 0 if unavailable */ uint32_t size; uid_t start_uid; uid_t target_uid; @@ -64,10 +66,39 @@ int userns_registry_load_by_start_gid(int dir_fd, gid_t start, UserNamespaceInfo int userns_registry_load_by_userns_inode(int dir_fd, uint64_t inode, UserNamespaceInfo **ret); int userns_registry_load_by_name(int dir_fd, const char *name, UserNamespaceInfo **ret); +int userns_info_verify_fd(int userns_fd, const UserNamespaceInfo *info); + +/* Releases all resources tied to a user namespace: removes BPF allowlist entries (if a bpf handle is + * given), drops the corresponding fd from systemd's fdstore, removes cgroups and netifs recorded for + * it, and unlinks the registry entry. The caller must already hold the registry lock (e.g. via + * userns_registry_lock()). The _by_inode variant loads the registry entry; prefer the _by_info + * variant where the caller already has it. */ +void userns_registry_release_by_info(struct userns_restrict_bpf *bpf, int dir_fd, UserNamespaceInfo *info); +void userns_registry_release_by_userns_inode(struct userns_restrict_bpf *bpf, int dir_fd, uint64_t inode); + +typedef enum UserNamespaceReapStatus { + USERNS_REAP_RELEASED, /* Confirmed dead via its kernel id — registry entry released. */ + USERNS_REAP_ALIVE, /* Still alive — left untouched. */ + USERNS_REAP_INDETERMINATE, /* Liveness couldn't be determined for this entry — it predates id + tracking, or no entry is registered for the inode any more (e.g. a + dead ancestor still referenced by a delegation chain). */ + USERNS_REAP_UNSUPPORTED, /* Namespaces can't be looked up by id in this environment at all (old + kernel, or not in the initial user namespace). Applies to every + entry, so callers sweeping many of them can stop probing. */ + _USERNS_REAP_MAX, + _USERNS_REAP_INVALID = -EINVAL, +} UserNamespaceReapStatus; + +/* Probes the registered user namespace with the given inode for liveness via its recorded kernel + * namespace id and, if it is authoritatively dead, releases its registry entry (restoring any ranges + * it received via delegation to their ancestors). Returns a non-negative UserNamespaceReapStatus + * describing what happened, or a negative errno on genuine failure. The caller must hold the registry + * lock (or otherwise be free of concurrent writers). */ +int userns_registry_reap_if_dead(struct userns_restrict_bpf *bpf, int dir_fd, uint64_t inode); + int userns_registry_store(int dir_fd, UserNamespaceInfo *info); int userns_registry_remove(int dir_fd, UserNamespaceInfo *info); -int userns_registry_inode_exists(int dir_fd, uint64_t inode); int userns_registry_name_exists(int dir_fd, const char *name); int userns_registry_uid_exists(int dir_fd, uid_t start); int userns_registry_gid_exists(int dir_fd, gid_t start); diff --git a/src/nsresourced/userns-restrict.c b/src/nsresourced/userns-restrict.c index c0d7f8a82daec..1b82c797f09ee 100644 --- a/src/nsresourced/userns-restrict.c +++ b/src/nsresourced/userns-restrict.c @@ -3,10 +3,10 @@ #include #if HAVE_VMLINUX_H -#include "bpf/userns-restrict/userns-restrict-skel.h" +#include "userns-restrict-skel.h" #endif -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "fd-util.h" #include "log.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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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); } } @@ -244,7 +244,7 @@ int userns_restrict_put_by_inode( if (n_try == 0) return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), - "Stillcan't create inode entry in BPF map after 10 tries."); + "Still cannot create inode entry in BPF map after 10 tries."); r = sym_bpf_map_lookup_elem(outer_map_fd, &ino, &innermap_id); if (r >= 0) { 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/meson.build b/src/nss-resolve/meson.build index 1fae4266364f0..63332913c72a2 100644 --- a/src/nss-resolve/meson.build +++ b/src/nss-resolve/meson.build @@ -6,7 +6,7 @@ modules += [ 'conditions' : ['ENABLE_NSS_RESOLVE'], 'sources' : files('nss-resolve.c'), 'version-script' : meson.current_source_dir() / 'nss-resolve.sym', - 'include_directories' : includes + - include_directories('../resolve'), + 'include_directories' : [include_directories('../resolve'), + includes], }, ] diff --git a/src/nss-resolve/nss-resolve.c b/src/nss-resolve/nss-resolve.c index ea60727e906d9..55e4216f53b8e 100644 --- a/src/nss-resolve/nss-resolve.c +++ b/src/nss-resolve/nss-resolve.c @@ -14,6 +14,7 @@ #include "json-util.h" #include "netlink-util.h" #include "nss-util.h" +#include "resolve-varlink-util.h" #include "resolved-def.h" #include "signal-util.h" #include "string-util.h" @@ -50,6 +51,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; @@ -62,107 +65,6 @@ static int connect_to_resolved(sd_varlink **ret) { return 0; } -static uint32_t ifindex_to_scopeid(int family, const void *a, int ifindex) { - struct in6_addr in6; - - if (family != AF_INET6 || ifindex == 0) - return 0; - - /* Some apps can't deal with the scope ID attached to non-link-local addresses. Hence, let's suppress that. */ - - assert(sizeof(in6) == FAMILY_ADDRESS_SIZE(AF_INET6)); - memcpy(&in6, a, sizeof(struct in6_addr)); - - return in6_addr_is_link_local(&in6) ? ifindex : 0; -} - -static int json_dispatch_family(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { - int *family = ASSERT_PTR(userdata); - int64_t t; - - assert(variant); - - if (!sd_json_variant_is_integer(variant)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name)); - - t = sd_json_variant_integer(variant); - if (t < 0 || t > INT_MAX) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid family.", strna(name)); - - *family = (int) t; - return 0; -} - -typedef struct ResolveHostnameReply { - sd_json_variant *addresses; - char *name; - uint64_t flags; -} ResolveHostnameReply; - -static void resolve_hostname_reply_destroy(ResolveHostnameReply *p) { - assert(p); - - sd_json_variant_unref(p->addresses); - free(p->name); -} - -static const sd_json_dispatch_field resolve_hostname_reply_dispatch_table[] = { - { "addresses", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(ResolveHostnameReply, addresses), SD_JSON_MANDATORY }, - { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolveHostnameReply, name), 0 }, - { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ResolveHostnameReply, flags), 0 }, - {} -}; - -typedef struct AddressParameters { - int ifindex; - int family; - union in_addr_union address; - size_t address_size; -} AddressParameters; - -static int json_dispatch_address(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { - AddressParameters *p = ASSERT_PTR(userdata); - union in_addr_union buf = {}; - sd_json_variant *i; - size_t n, k = 0; - - assert(variant); - - if (!sd_json_variant_is_array(variant)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); - - n = sd_json_variant_elements(variant); - if (!IN_SET(n, 4, 16)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name)); - - JSON_VARIANT_ARRAY_FOREACH(i, variant) { - int64_t b; - - if (!sd_json_variant_is_integer(i)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an integer.", k, strna(name)); - - b = sd_json_variant_integer(i); - if (b < 0 || b > 0xff) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), - "Element %zu of JSON field '%s' is out of range 0%s255.", - k, strna(name), glyph(GLYPH_ELLIPSIS)); - - buf.bytes[k++] = (uint8_t) b; - } - - p->address = buf; - p->address_size = k; - - return 0; -} - -static const sd_json_dispatch_field address_parameters_dispatch_table[] = { - { "ifindex", SD_JSON_VARIANT_INTEGER, json_dispatch_ifindex, offsetof(AddressParameters, ifindex), 0 }, - { "family", SD_JSON_VARIANT_INTEGER, json_dispatch_family, offsetof(AddressParameters, family), SD_JSON_MANDATORY }, - { "address", SD_JSON_VARIANT_ARRAY, json_dispatch_address, 0, SD_JSON_MANDATORY }, - {} -}; - static uint64_t query_flag( const char *name, const int value, @@ -206,6 +108,19 @@ static int query_ifindex(void) { return ifindex; } +static uint32_t ifindex_to_scopeid(ResolvedAddress *addr) { + assert(addr); + + if (addr->family != AF_INET6 || addr->ifindex == 0) + return 0; + + /* Some apps can't deal with the scope ID attached to non-link-local addresses. Hence, let's suppress that. */ + if (in6_addr_is_link_local(&addr->in_addr.address.in6)) + return addr->ifindex; + + return 0; +} + enum nss_status _nss_resolve_gethostbyname4_r( const char *name, struct gaih_addrtuple **pat, @@ -215,8 +130,8 @@ enum nss_status _nss_resolve_gethostbyname4_r( _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *cparams = NULL; - _cleanup_(resolve_hostname_reply_destroy) ResolveHostnameReply p = {}; - sd_json_variant *rparams, *entry; + _cleanup_(resolve_hostname_reply_done) ResolveHostnameReply p = {}; + sd_json_variant *rparams; int r; PROTECT_ERRNO; @@ -259,31 +174,23 @@ enum nss_status _nss_resolve_gethostbyname4_r( goto not_found; } - r = sd_json_dispatch(rparams, resolve_hostname_reply_dispatch_table, nss_json_dispatch_flags, &p); + r = dispatch_resolve_hostname_reply(NULL, rparams, nss_json_dispatch_flags, &p); if (r < 0) goto fail; - if (sd_json_variant_is_blank_object(p.addresses)) + if (p.n_addresses == 0) goto not_found; size_t n_addresses = 0; - JSON_VARIANT_ARRAY_FOREACH(entry, p.addresses) { - AddressParameters q = {}; - - r = sd_json_dispatch(entry, address_parameters_dispatch_table, nss_json_dispatch_flags, &q); - if (r < 0) - goto fail; - - if (!IN_SET(q.family, AF_INET, AF_INET6)) + FOREACH_ARRAY(entry, p.addresses, p.n_addresses) { + if (!IN_SET(entry->family, AF_INET, AF_INET6)) continue; - if (q.address_size != FAMILY_ADDRESS_SIZE(q.family)) { - r = -EINVAL; - goto fail; - } - n_addresses++; } + if (n_addresses == 0) + goto not_found; + const char *canonical = p.name ?: name; size_t l = strlen(canonical); size_t idx, ms = ALIGN(l+1) + ALIGN(sizeof(struct gaih_addrtuple)) * n_addresses; @@ -304,22 +211,16 @@ enum nss_status _nss_resolve_gethostbyname4_r( struct gaih_addrtuple *r_tuple = NULL, *r_tuple_first = (struct gaih_addrtuple*) (buffer + idx); - JSON_VARIANT_ARRAY_FOREACH(entry, p.addresses) { - AddressParameters q = {}; - - r = sd_json_dispatch(entry, address_parameters_dispatch_table, nss_json_dispatch_flags, &q); - if (r < 0) - goto fail; - - if (!IN_SET(q.family, AF_INET, AF_INET6)) + FOREACH_ARRAY(entry, p.addresses, p.n_addresses) { + if (!IN_SET(entry->family, AF_INET, AF_INET6)) continue; r_tuple = (struct gaih_addrtuple*) (buffer + idx); r_tuple->next = (struct gaih_addrtuple*) ((char*) r_tuple + ALIGN(sizeof(struct gaih_addrtuple))); r_tuple->name = r_name; - r_tuple->family = q.family; - r_tuple->scopeid = ifindex_to_scopeid(q.family, &q.address, q.ifindex); - memcpy(r_tuple->addr, &q.address, q.address_size); + r_tuple->family = entry->family; + r_tuple->scopeid = ifindex_to_scopeid(entry); + memcpy(r_tuple->addr, entry->in_addr.address.bytes, FAMILY_ADDRESS_SIZE_SAFE(entry->family)); idx += ALIGN(sizeof(struct gaih_addrtuple)); } @@ -376,8 +277,8 @@ enum nss_status _nss_resolve_gethostbyname3_r( _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *cparams = NULL; - _cleanup_(resolve_hostname_reply_destroy) ResolveHostnameReply p = {}; - sd_json_variant *rparams, *entry; + _cleanup_(resolve_hostname_reply_done) ResolveHostnameReply p = {}; + sd_json_variant *rparams; int r; PROTECT_ERRNO; @@ -424,31 +325,23 @@ enum nss_status _nss_resolve_gethostbyname3_r( goto not_found; } - r = sd_json_dispatch(rparams, resolve_hostname_reply_dispatch_table, nss_json_dispatch_flags, &p); + r = dispatch_resolve_hostname_reply(NULL, rparams, nss_json_dispatch_flags, &p); if (r < 0) goto fail; - if (sd_json_variant_is_blank_object(p.addresses)) - goto not_found; + if (p.n_addresses == 0) + goto no_data; size_t n_addresses = 0; - JSON_VARIANT_ARRAY_FOREACH(entry, p.addresses) { - AddressParameters q = {}; - - r = sd_json_dispatch(entry, address_parameters_dispatch_table, nss_json_dispatch_flags, &q); - if (r < 0) - goto fail; - - if (q.family != af) + FOREACH_ARRAY(entry, p.addresses, p.n_addresses) { + if (entry->family != af) continue; - if (q.address_size != FAMILY_ADDRESS_SIZE(q.family)) { - r = -EINVAL; - goto fail; - } - n_addresses++; } + if (n_addresses == 0) + goto no_data; + const char *canonical = p.name ?: name; size_t alen = FAMILY_ADDRESS_SIZE(af); @@ -477,22 +370,16 @@ enum nss_status _nss_resolve_gethostbyname3_r( char *r_addr = buffer + idx; size_t i = 0; - JSON_VARIANT_ARRAY_FOREACH(entry, p.addresses) { - AddressParameters q = {}; - - r = sd_json_dispatch(entry, address_parameters_dispatch_table, nss_json_dispatch_flags, &q); - if (r < 0) - goto fail; - - if (q.family != af) + FOREACH_ARRAY(entry, p.addresses, p.n_addresses) { + if (entry->family != af) continue; - if (q.address_size != alen) { + if (FAMILY_ADDRESS_SIZE_SAFE(entry->family) != alen) { r = -EINVAL; goto fail; } - memcpy(r_addr + i*ALIGN(alen), &q.address, alen); + memcpy(r_addr + i*ALIGN(alen), entry->in_addr.address.bytes, alen); i++; } @@ -549,40 +436,6 @@ enum nss_status _nss_resolve_gethostbyname3_r( return NSS_STATUS_TRYAGAIN; } -typedef struct ResolveAddressReply { - sd_json_variant *names; - uint64_t flags; -} ResolveAddressReply; - -static void resolve_address_reply_destroy(ResolveAddressReply *p) { - assert(p); - - sd_json_variant_unref(p->names); -} - -static const sd_json_dispatch_field resolve_address_reply_dispatch_table[] = { - { "names", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(ResolveAddressReply, names), SD_JSON_MANDATORY }, - { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ResolveAddressReply, flags), 0 }, - {} -}; - -typedef struct NameParameters { - int ifindex; - char *name; -} NameParameters; - -static void name_parameters_destroy(NameParameters *p) { - assert(p); - - free(p->name); -} - -static const sd_json_dispatch_field name_parameters_dispatch_table[] = { - { "ifindex", SD_JSON_VARIANT_INTEGER, json_dispatch_ifindex, offsetof(NameParameters, ifindex), 0 }, - { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(NameParameters, name), SD_JSON_MANDATORY }, - {} -}; - enum nss_status _nss_resolve_gethostbyaddr2_r( const void* addr, socklen_t len, int af, @@ -593,8 +446,8 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *cparams = NULL; - _cleanup_(resolve_address_reply_destroy) ResolveAddressReply p = {}; - sd_json_variant *rparams, *entry; + _cleanup_(resolve_address_reply_done) ResolveAddressReply p = {}; + sd_json_variant *rparams; int r; PROTECT_ERRNO; @@ -643,25 +496,18 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( goto not_found; } - r = sd_json_dispatch(rparams, resolve_address_reply_dispatch_table, nss_json_dispatch_flags, &p); + r = dispatch_resolve_address_reply(NULL, rparams, nss_json_dispatch_flags, &p); if (r < 0) goto fail; - if (sd_json_variant_is_blank_object(p.names)) + if (p.n_names == 0) goto not_found; size_t ms = 0, idx; - JSON_VARIANT_ARRAY_FOREACH(entry, p.names) { - _cleanup_(name_parameters_destroy) NameParameters q = {}; - - r = sd_json_dispatch(entry, name_parameters_dispatch_table, nss_json_dispatch_flags, &q); - if (r < 0) - goto fail; + FOREACH_ARRAY(entry, p.names, p.n_names) + ms += ALIGN(strlen(entry->name) + 1); - ms += ALIGN(strlen(q.name) + 1); - } - - size_t n_names = sd_json_variant_elements(p.names); + size_t n_names = p.n_names; ms += ALIGN(len) + /* the address */ 2 * sizeof(char*) + /* pointer to the address, plus trailing NULL */ n_names * sizeof(char*); /* pointers to aliases, plus trailing NULL */ @@ -692,16 +538,10 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( char *r_name = buffer + idx; size_t i = 0; - JSON_VARIANT_ARRAY_FOREACH(entry, p.names) { - _cleanup_(name_parameters_destroy) NameParameters q = {}; - - r = sd_json_dispatch(entry, name_parameters_dispatch_table, nss_json_dispatch_flags, &q); - if (r < 0) - goto fail; - - size_t l = strlen(q.name); + FOREACH_ARRAY(entry, p.names, p.n_names) { + size_t l = strlen(entry->name); char *z = buffer + idx; - memcpy(z, q.name, l + 1); + memcpy(z, entry->name, l + 1); if (i > 0) ((char**) r_aliases)[i - 1] = z; diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c index 6ed97f31a68f9..2af9b95e4cc1e 100644 --- a/src/nss-systemd/nss-systemd.c +++ b/src/nss-systemd/nss-systemd.c @@ -4,7 +4,6 @@ #include #include #include -#include #include "alloc-util.h" #include "env-util.h" @@ -149,6 +148,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 +191,7 @@ static enum nss_status copy_synthesized_spwd( assert(dest); assert(src); + assert(errnop); assert(src->sp_namp); assert(src->sp_pwdp); @@ -223,6 +224,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 +261,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); @@ -1066,28 +1069,72 @@ enum nss_status _nss_systemd_initgroups_dyn( return any ? NSS_STATUS_SUCCESS : NSS_STATUS_NOTFOUND; } -static thread_local unsigned _blocked = 0; +/* Note that we intentionally use POSIX thread-specific data instead of a plain thread_local variable. + * A thread_local in this lazily-loaded DSO uses a dynamic TLS model by default and may require + * a dynamic TLS allocation. If that allocation fails, glibc calls _exit() from the dynamic linker, + * making the failure unrecoverable. Using pthread_key_t avoids ELF TLS entirely and lets any such + * failure propagate as a normal error instead of terminating the process. */ +static pthread_once_t nss_blocked_key_once = PTHREAD_ONCE_INIT; +static pthread_key_t nss_blocked_key; +static int nss_blocked_key_error; + +static void nss_blocked_key_init(void) { + /* NULL destructor: the per-thread value is a plain integer counter encoded as void*, + * not a heap allocation, so nothing needs to be freed at thread exit. + * No pthread_key_delete: this library is linked with -z nodelete and always opened with + * RTLD_NODELETE, so it is never unloaded and the key exists for the process lifetime. */ + nss_blocked_key_error = pthread_key_create(&nss_blocked_key, NULL); +} + +static int nss_blocked_key_ensure(void) { + int r; + + r = pthread_once(&nss_blocked_key_once, nss_blocked_key_init); + if (r != 0) + return -r; + + if (nss_blocked_key_error != 0) + return -nss_blocked_key_error; + + return 0; +} _public_ int _nss_systemd_block(bool b) { + int r; + uintptr_t blocked; + + r = nss_blocked_key_ensure(); + if (r < 0) + return r; + + blocked = (uintptr_t) pthread_getspecific(nss_blocked_key); /* This blocks recursively: it's blocked for as many times this function is called with `true` until * it is called an equal time with `false`. */ if (b) { - if (_blocked >= UINT_MAX) + if (blocked >= UINTPTR_MAX) return -EOVERFLOW; - _blocked++; + blocked++; } else { - if (_blocked <= 0) + if (blocked == 0) return -EOVERFLOW; - _blocked--; + blocked--; } + r = pthread_setspecific(nss_blocked_key, (void*) blocked); + /* Ignore failure on the unblock path: callers may assert on it. */ + if (r != 0 && b) + return -r; + return b; /* Return what is passed in, i.e. the new state from the PoV of the caller */ } _public_ bool _nss_systemd_is_blocked(void) { - return _blocked > 0; + if (nss_blocked_key_ensure() < 0) + return false; + + return (uintptr_t) pthread_getspecific(nss_blocked_key) > 0; } 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..8b5051ebd09c2 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,45 @@ 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_DEFAULT_NOARG(verb_dump_state, "dump", "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 +85,42 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts); 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(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/oom/oomd-conf.c b/src/oom/oomd-conf.c index f0091e27561c7..bd3d0003b07e5 100644 --- a/src/oom/oomd-conf.c +++ b/src/oom/oomd-conf.c @@ -1,11 +1,18 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "conf-files.h" #include "conf-parser.h" +#include "hashmap.h" #include "log.h" #include "oomd-conf.h" #include "oomd-manager.h" #include "parse-util.h" +#include "path-util.h" +#include "percent-util.h" +#include "stat-util.h" +#include "string-table.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" static int config_parse_duration( @@ -66,7 +73,143 @@ void manager_set_defaults(Manager *m) { log_warning_errno(r, "Failed to set default for default_mem_pressure_limit, ignoring: %m"); } +/* OOMD_ACTION_NONE is intentionally omitted — it's the "unset" sentinel. Rulesets with + * .action == OOMD_ACTION_NONE are rejected at load time, so oomd_action_to_string() must + * only be called on rulesets that have already passed ruleset_load_one's validation + * (otherwise it returns NULL). */ +static const char* const oomd_action_table[] = { + [OOMD_ACTION_KILL_ALL] = "kill-all", + [OOMD_ACTION_KILL_BY_PGSCAN] = "kill-by-pgscan", + [OOMD_ACTION_KILL_BY_SWAP] = "kill-by-swap", +}; + +DEFINE_STRING_TABLE_LOOKUP(oomd_action, OomdAction); +static DEFINE_CONFIG_PARSE_ENUM(config_parse_oomd_action, oomd_action, OomdAction); + +void oomd_ruleset_free(OomdRuleset *ruleset) { + if (!ruleset) + return; + hashmap_free(ruleset->start_times); + free(ruleset->name); + free(ruleset); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OomdRuleset*, oomd_ruleset_free, NULL); +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(oomd_ruleset_hash_ops, char, string_hash_func, string_compare_func, OomdRuleset, oomd_ruleset_free); + +static int ruleset_load_one(Manager *m, const char *filename) { + _cleanup_free_ char *name = NULL; + _cleanup_(oomd_ruleset_freep) OomdRuleset *ruleset = NULL; + _cleanup_fclose_ FILE *f = NULL; + struct stat st; + int r; + + assert(m); + assert(filename); + + /* Pin the file via an fd so the empty-file check and the parse operate on the same + * inode (avoids TOCTOU between null_or_empty_path() and a subsequent open()). */ + f = fopen(filename, "re"); + if (!f) + return log_warning_errno(errno, "Failed to open '%s': %m", filename); + + if (fstat(fileno(f), &st) < 0) + return log_warning_errno(errno, "Failed to stat '%s': %m", filename); + + if (null_or_empty(&st)) { + log_debug("Skipping empty file: %s", filename); + return 0; + } + + r = path_extract_filename(filename, &name); + if (r < 0) + return log_error_errno(r, "Failed to extract file name of '%s': %m", filename); + + char *e = ASSERT_PTR(endswith(name, ".oomrule")); + *e = 0; + + /* Apply the same validation the DBus setter and the config parser use, so that any + * ruleset we accept here is actually referenceable via OOMRules= from a unit. */ + if (!string_is_safe(name, STRING_FILENAME)) { + log_warning("Invalid ruleset name '%s' derived from '%s', ignoring.", name, filename); + return 0; + } + + ruleset = new(OomdRuleset, 1); + if (!ruleset) + return log_oom(); + + *ruleset = (OomdRuleset) { + .name = TAKE_PTR(name), + .memory_pressure_above = -1, + .swap_above = -1, + }; + + const ConfigTableItem items[] = { + { "Rule", "MemoryPressureAbove", config_parse_permyriad, 0, &ruleset->memory_pressure_above }, + { "Rule", "SwapUsageMax", config_parse_permyriad, 0, &ruleset->swap_above }, + { "Rule", "Action", config_parse_oomd_action, 0, &ruleset->action }, + { "Rule", "LastingSec", config_parse_sec, 0, &ruleset->lasting_usec }, + {} + }; + + r = config_parse( + /* unit= */ NULL, + filename, + f, + "Rule\0", + config_item_table_lookup, + items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL, + /* ret_stat= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse ruleset file '%s': %m", filename); + + if (ruleset->memory_pressure_above < 0 && ruleset->swap_above < 0) { + log_warning("Ruleset '%s' has no conditions configured (MemoryPressureAbove= or SwapUsageMax=), ignoring.", ruleset->name); + return 0; + } + + if (ruleset->action == OOMD_ACTION_NONE) { + log_warning("Ruleset '%s' has no Action= configured, ignoring.", ruleset->name); + return 0; + } + + if (ruleset->lasting_usec == USEC_INFINITY) { + log_warning("Ruleset '%s' has LastingSec=infinity which can never be satisfied, ignoring.", ruleset->name); + return 0; + } + + /* A threshold at the maximum can never be exceeded, so the condition would never fire. + * Report the normalized percent value so the warning matches regardless of whether the + * user wrote '100%', '1000‰' or '10000‱'. */ + if (ruleset->memory_pressure_above == 10000) { + log_warning("Ruleset '%s' has MemoryPressureAbove=" PERMYRIAD_AS_PERCENT_FORMAT_STR " (the maximum) which can never be exceeded, ignoring.", + ruleset->name, PERMYRIAD_AS_PERCENT_FORMAT_VAL(ruleset->memory_pressure_above)); + return 0; + } + + if (ruleset->swap_above == 10000) { + log_warning("Ruleset '%s' has SwapUsageMax=" PERMYRIAD_AS_PERCENT_FORMAT_STR " (the maximum) which can never be exceeded, ignoring.", + ruleset->name, PERMYRIAD_AS_PERCENT_FORMAT_VAL(ruleset->swap_above)); + return 0; + } + + /* Duplicates cannot occur here: conf_files_list_strv deduplicates filenames across + * directories, and hashmap_clear is called before loading. The value destructor in + * oomd_ruleset_hash_ops handles cleanup during hashmap_clear/hashmap_free. */ + r = hashmap_ensure_replace(&m->rulesets, &oomd_ruleset_hash_ops, ruleset->name, ruleset); + if (r < 0) + return log_error_errno(r, "Failed to register ruleset '%s': %m", ruleset->name); + + TAKE_PTR(ruleset); + + return 0; +} + void manager_parse_config_file(Manager *m) { + _cleanup_strv_free_ char **files = NULL; int r; assert(m); @@ -88,4 +231,37 @@ void manager_parse_config_file(Manager *m) { /* userdata= */ m); if (r >= 0) log_debug("Config file successfully parsed."); + + r = conf_files_list_strv(&files, ".oomrule", /* root= */ NULL, CONF_FILES_WARN, RULESET_DIRS); + if (r < 0) { + /* On enumeration failure, keep the previously loaded rulesets rather than clearing them — + * a transient I/O error shouldn't cause in-flight units to silently lose their OOM policy. */ + log_error_errno(r, "Failed to enumerate ruleset files, keeping previously loaded rulesets: %m"); + return; + } + + /* Clear all rulesets and re-parse. This intentionally resets any accumulated + * start_times (LastingSec timers), since the ruleset definitions may have changed. */ + hashmap_clear(m->rulesets); + + STRV_FOREACH(f, files) + (void) ruleset_load_one(m, *f); + + if (DEBUG_LOGGING) { + char *name; + OomdRuleset *ruleset; + HASHMAP_FOREACH_KEY(ruleset, name, m->rulesets) { + log_debug("Registered ruleset: %s", name); + if (ruleset->memory_pressure_above >= 0) + log_debug(" MemoryPressureAbove=" PERMYRIAD_AS_PERCENT_FORMAT_STR, PERMYRIAD_AS_PERCENT_FORMAT_VAL(ruleset->memory_pressure_above)); + else + log_debug(" MemoryPressureAbove=unset"); + if (ruleset->swap_above >= 0) + log_debug(" SwapUsageMax=" PERMYRIAD_AS_PERCENT_FORMAT_STR, PERMYRIAD_AS_PERCENT_FORMAT_VAL(ruleset->swap_above)); + else + log_debug(" SwapUsageMax=unset"); + log_debug(" Action=%s", oomd_action_to_string(ruleset->action)); + log_debug(" LastingSec=%s", FORMAT_TIMESPAN(ruleset->lasting_usec, USEC_PER_SEC)); + } + } } diff --git a/src/oom/oomd-conf.h b/src/oom/oomd-conf.h index 429b976b91be2..8f715e81c6fe0 100644 --- a/src/oom/oomd-conf.h +++ b/src/oom/oomd-conf.h @@ -1,8 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "string-table.h" /* IWYU pragma: keep */ + typedef struct Manager Manager; +typedef struct OomdRuleset OomdRuleset; +typedef enum OomdAction OomdAction; + +void oomd_ruleset_free(OomdRuleset *ruleset); void manager_set_defaults(Manager *m); void manager_parse_config_file(Manager *m); + +DECLARE_STRING_TABLE_LOOKUP(oomd_action, OomdAction); diff --git a/src/oom/oomd-manager.c b/src/oom/oomd-manager.c index b2142dd43cf33..2fafa6904d72f 100644 --- a/src/oom/oomd-manager.c +++ b/src/oom/oomd-manager.c @@ -24,6 +24,7 @@ #include "percent-util.h" #include "set.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" #include "varlink-io.systemd.oom.h" #include "varlink-io.systemd.service.h" @@ -35,12 +36,14 @@ typedef struct ManagedOOMMessage { char *property; uint32_t limit; usec_t duration; + char **rules; } ManagedOOMMessage; static void managed_oom_message_destroy(ManagedOOMMessage *message) { assert(message); free(message->path); free(message->property); + strv_free(message->rules); } static JSON_DISPATCH_ENUM_DEFINE(dispatch_managed_oom_mode, ManagedOOMMode, managed_oom_mode_from_string); @@ -55,6 +58,7 @@ static int process_managed_oom_message(Manager *m, uid_t uid, sd_json_variant *p { "property", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ManagedOOMMessage, property), SD_JSON_MANDATORY }, { "limit", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(ManagedOOMMessage, limit), 0 }, { "duration", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ManagedOOMMessage, duration), 0 }, + { "rules", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_strv, offsetof(ManagedOOMMessage, rules), 0 }, {}, }; @@ -84,6 +88,11 @@ static int process_managed_oom_message(Manager *m, uid_t uid, sd_json_variant *p if (r < 0) continue; + if (!path_is_normalized(empty_to_root(message.path))) { + log_debug("Received non-normalized cgroup path '%s', ignoring.", message.path); + continue; + } + if (uid != 0) { uid_t cg_uid; @@ -101,11 +110,31 @@ static int process_managed_oom_message(Manager *m, uid_t uid, sd_json_variant *p "(" UID_FMT " != " UID_FMT ")", uid, cg_uid); } - monitor_hm = streq(message.property, "ManagedOOMSwap") ? - m->monitored_swap_cgroup_contexts : m->monitored_mem_pressure_cgroup_contexts; + if (streq(message.property, "ManagedOOMSwap")) + monitor_hm = m->monitored_swap_cgroup_contexts; + else if (streq(message.property, "OOMRules")) + monitor_hm = m->monitored_rules_cgroup_contexts; + else if (streq(message.property, "ManagedOOMMemoryPressure")) + monitor_hm = m->monitored_mem_pressure_cgroup_contexts; + else { + log_debug("Unknown property '%s', ignoring.", message.property); + continue; + } if (message.mode == MANAGED_OOM_AUTO) { (void) oomd_cgroup_context_unref(hashmap_remove(monitor_hm, empty_to_root(message.path))); + + /* Clean up start_times entries for this cgroup across all rulesets + * to prevent stale timers from causing premature action triggers + * if the cgroup re-subscribes later. */ + if (streq(message.property, "OOMRules")) { + OomdRuleset *ruleset; + HASHMAP_FOREACH(ruleset, m->rulesets) { + _cleanup_free_ char *key = NULL; + free(hashmap_remove2(ruleset->start_times, empty_to_root(message.path), (void **) &key)); + } + } + continue; } @@ -124,6 +153,57 @@ static int process_managed_oom_message(Manager *m, uid_t uid, sd_json_variant *p else duration = m->default_mem_pressure_duration_usec; + /* For OOMRules, only insert/update if rules are actually provided */ + if (streq(message.property, "OOMRules")) { + if (strv_isempty(message.rules)) + continue; + + /* Avoid re-reading memory.pressure/pgscan/etc. on every OOMRules update for a + * cgroup we already track — fetch the existing context first and only acquire + * a fresh one if the cgroup is new. */ + ctx = hashmap_get(monitor_hm, empty_to_root(message.path)); + if (!ctx) { + r = oomd_insert_cgroup_context(NULL, monitor_hm, message.path); + if (r == -ENOMEM) + return r; + if (r < 0) { + log_debug_errno(r, "Failed to insert message, ignoring: %m"); + continue; + } + ctx = hashmap_get(monitor_hm, empty_to_root(message.path)); + } + + if (ctx) { + /* For each rule being dropped from this cgroup's subscription, + * remove its start_times entry so the timer doesn't linger. */ + STRV_FOREACH(old_rule, ctx->rules) { + if (strv_contains(message.rules, *old_rule)) + continue; + OomdRuleset *dropped = hashmap_get(m->rulesets, *old_rule); + if (!dropped) + continue; + _cleanup_free_ char *key = NULL; + free(hashmap_remove2(dropped->start_times, empty_to_root(message.path), (void**) &key)); + } + + strv_free_and_replace(ctx->rules, message.rules); + + /* Defensively deduplicate: the DBus setter and config parser both + * dedupe, but another varlink client could in principle send + * duplicates, which would cause redundant per-interval evaluation. */ + strv_uniq(ctx->rules); + + /* Warn about any referenced rules that don't exist. Done here + * (once per subscription change) rather than per-interval to avoid + * log spam when a unit references a missing ruleset. */ + STRV_FOREACH(new_rule, ctx->rules) + if (!hashmap_contains(m->rulesets, *new_rule)) + log_warning("Cgroup %s references undefined ruleset '%s', it will be ignored.", + ctx->path, *new_rule); + } + continue; + } + r = oomd_insert_cgroup_context(NULL, monitor_hm, message.path); if (r == -ENOMEM) return r; @@ -145,6 +225,12 @@ static int process_managed_oom_message(Manager *m, uid_t uid, sd_json_variant *p if (r < 0) return log_error_errno(r, "Failed to toggle enabled state of swap context source: %m"); + /* Toggle wake-ups for "OOMRules" if entries are present. */ + r = sd_event_source_set_enabled(m->rules_context_event_source, + hashmap_isempty(m->monitored_rules_cgroup_contexts) ? SD_EVENT_OFF : SD_EVENT_ON); + if (r < 0) + return log_error_errno(r, "Failed to toggle enabled state of rules context source: %m"); + return 0; } @@ -408,7 +494,7 @@ static int monitor_swap_contexts_handler(sd_event_source *s, uint64_t usec, void log_debug_errno(r, "Failed to get monitored swap cgroup candidates, ignoring: %m"); threshold = m->system_context.swap_total * THRESHOLD_SWAP_USED_PERCENT / 100; - r = oomd_select_by_swap_usage(candidates, threshold, &selected); + r = oomd_select_by_swap_usage(candidates, /* prefix= */ NULL, threshold, &selected); if (r < 0) return log_error_errno(r, "Failed to select any cgroups based on swap: %m"); if (r == 0) { @@ -422,7 +508,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 +516,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 +524,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); } @@ -583,6 +670,398 @@ static int monitor_memory_pressure_contexts_handler(sd_event_source *s, uint64_t return 0; } +static int ruleset_execute_action( + Manager *m, + OomdCGroupContext *ctx, + OomdRuleset *ruleset, + const char *rule_name, + usec_t usec_now) { + + _cleanup_free_ char *reason = NULL; + int r; + + assert(m); + assert(ctx); + assert(ruleset); + assert(rule_name); + + if (ruleset->lasting_usec > 0) + log_notice("Rule '%s' conditions met for cgroup %s (lasting %s), taking action %s", + rule_name, + ctx->path, + FORMAT_TIMESPAN(ruleset->lasting_usec, USEC_PER_SEC), + oomd_action_to_string(ruleset->action)); + else + log_notice("Rule '%s' conditions met for cgroup %s, taking action %s", + rule_name, + ctx->path, + oomd_action_to_string(ruleset->action)); + + reason = strjoin("rule ", rule_name); + if (!reason) + return log_oom(); + + if (ruleset->action == OOMD_ACTION_KILL_ALL) { + r = oomd_cgroup_kill_mark(m, ctx, reason); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_notice_errno(r, "Failed to kill all processes in %s: %m", ctx->path); + return 0; + } + } else if (ruleset->action == OOMD_ACTION_KILL_BY_PGSCAN) { + OomdCGroupContext *selected = NULL; + + /* Check if there was reclaim activity in the given interval. If there isn't any reclaim + * pressure, killing won't help — well-behaved processes faulting in recently resident + * pages will keep pressure high even after the offending cgroup is killed. */ + if (usec_sub_unsigned(usec_now, ctx->last_had_mem_reclaim) > RECLAIM_DURATION_USEC) { + log_debug("No reclaim activity for %s, skipping pgscan-based action", ctx->path); + return 0; + } + + r = oomd_select_by_pgscan_rate(m->monitored_rules_cgroup_contexts_candidates, ctx->path, &selected); + if (r < 0) { + log_notice_errno(r, "Failed to select cgroup by pgscan rate for %s: %m", ctx->path); + return 0; + } + if (r == 0) { + log_debug("No cgroup candidates found for pgscan-based action for %s", ctx->path); + return 0; + } + + r = oomd_cgroup_kill_mark(m, selected, reason); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_notice_errno(r, "Failed to kill processes in %s: %m", selected->path); + return 0; + } + } else if (ruleset->action == OOMD_ACTION_KILL_BY_SWAP) { + OomdCGroupContext *selected = NULL; + uint64_t threshold; + + if (m->system_context.swap_total == 0) { + if (!ruleset->warned_no_swap) { + log_warning("Rule '%s' uses kill-by-swap action but no swap is configured, skipping (further occurrences suppressed)", rule_name); + ruleset->warned_no_swap = true; + } + return 0; + } + + /* Swap came back — clear the latch so re-disabling swap warns again. */ + ruleset->warned_no_swap = false; + + threshold = m->system_context.swap_total * THRESHOLD_SWAP_USED_PERCENT / 100; + r = oomd_select_by_swap_usage(m->monitored_rules_cgroup_contexts_candidates, ctx->path, threshold, &selected); + if (r < 0) { + log_notice_errno(r, "Failed to select cgroup by swap usage for %s: %m", ctx->path); + return 0; + } + if (r == 0) { + log_debug("No cgroup candidates found for swap-based action for %s", ctx->path); + return 0; + } + + r = oomd_cgroup_kill_mark(m, selected, reason); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_notice_errno(r, "Failed to kill processes in %s: %m", selected->path); + return 0; + } + } else + assert_not_reached(); + + return 1; +} + +static int ruleset_check_conditions( + Manager *m, + OomdCGroupContext *ctx, + OomdRuleset *ruleset, + const char *rule_name, + usec_t usec_now) { + + int r; + + assert(m); + assert(ctx); + assert(ruleset); + assert(rule_name); + + /* Check memory pressure condition. + * memory_pressure_above is in permyriad (0-10000, i.e. 6050 = 60.50%). + * store_loadavg_fixed_point takes integer and decimal parts of a percentage, + * so divide/modulo by 100 to split permyriad into percent + centipercent. */ + if (ruleset->memory_pressure_above >= 0) { + loadavg_t threshold; + r = store_loadavg_fixed_point(ruleset->memory_pressure_above / 100, + ruleset->memory_pressure_above % 100, + &threshold); + if (r < 0) + return log_debug_errno(r, "Failed to convert pressure threshold for rule '%s': %m", rule_name); + + if (ctx->memory_pressure.avg10 <= threshold) + goto reset; + } + + /* swap_above means take action when swap usage is above threshold. + * oomd_swap_free_below returns true when swap free is below threshold, + * so if swap_above is X%, check if swap free is below (100-X)%. + * When no swap is configured, the condition cannot be meaningfully evaluated. */ + if (ruleset->swap_above >= 0) { + if (m->system_context.swap_total == 0 || + !oomd_swap_free_below(&m->system_context, 10000 - ruleset->swap_above)) + goto reset; + } + + /* All conditions met, check if LastingSec requirement is satisfied */ + usec_t *start_time = hashmap_get(ruleset->start_times, ctx->path); + if (!start_time) { + /* First time seeing this condition - record the start time */ + _cleanup_free_ usec_t *new_start_time = new(usec_t, 1); + if (!new_start_time) + return log_oom(); + + *new_start_time = usec_now; + + _cleanup_free_ char *path_copy = strdup(ctx->path); + if (!path_copy) + return log_oom(); + + r = hashmap_ensure_put(&ruleset->start_times, &string_hash_ops_free_free, path_copy, new_start_time); + if (r < 0) + return log_error_errno(r, "Failed to record start time for rule '%s' on %s: %m", + rule_name, ctx->path); + TAKE_PTR(path_copy); + TAKE_PTR(new_start_time); + + /* If lasting_usec is 0, take action immediately */ + if (ruleset->lasting_usec == 0) + return true; + + log_debug("Rule '%s' conditions met for cgroup %s, waiting for %s", + rule_name, ctx->path, + FORMAT_TIMESPAN(ruleset->lasting_usec, USEC_PER_SEC)); + return false; + } + + /* Check if the condition has been true for long enough */ + usec_t duration = usec_sub_unsigned(usec_now, *start_time); + if (duration >= ruleset->lasting_usec) + return true; + + log_debug("Rule '%s' conditions met for cgroup %s for %s (need %s)", + rule_name, ctx->path, + FORMAT_TIMESPAN(duration, USEC_PER_SEC), + FORMAT_TIMESPAN(ruleset->lasting_usec, USEC_PER_SEC)); + return false; + +reset: + /* Conditions no longer met — remove start time if it exists. */ + { + _cleanup_free_ char *old_key = NULL; + _cleanup_free_ usec_t *old_start_time = + hashmap_remove2(ruleset->start_times, ctx->path, (void**) &old_key); + if (old_start_time) + log_debug("Rule '%s' conditions no longer met for cgroup %s, resetting timer", + rule_name, ctx->path); + } + return false; +} + +/* After a reload, some cgroups may reference rulesets that no longer exist (or didn't exist yet + * when the cgroup subscribed). Warn once per (cgroup, rule) pair so the operator sees the mismatch, + * without spamming the per-interval evaluation loop. */ +static void warn_missing_rulesets(Manager *m) { + OomdCGroupContext *ctx; + + assert(m); + + HASHMAP_FOREACH(ctx, m->monitored_rules_cgroup_contexts) + STRV_FOREACH(rule, ctx->rules) + if (!hashmap_contains(m->rulesets, *rule)) + log_warning("Cgroup %s references undefined ruleset '%s', it will be ignored.", + ctx->path, *rule); +} + +/* Remove start_times entries for cgroups that are no longer in monitored_rules_cgroup_contexts. + * Cgroups can vanish silently (unit stops, cgroup destroyed) without an explicit unsubscribe + * message, so we periodically reconcile to prevent unbounded growth of start_times. */ +static int prune_stale_ruleset_start_times(Manager *m) { + OomdRuleset *ruleset; + int r; + + assert(m); + + HASHMAP_FOREACH(ruleset, m->rulesets) { + _cleanup_strv_free_ char **to_remove = NULL; + const char *path; + void *v; + + HASHMAP_FOREACH_KEY(v, path, ruleset->start_times) + if (!hashmap_contains(m->monitored_rules_cgroup_contexts, path)) { + r = strv_extend(&to_remove, path); + if (r < 0) + return log_oom(); + } + + STRV_FOREACH(p, to_remove) { + _cleanup_free_ char *key = NULL; + free(hashmap_remove2(ruleset->start_times, *p, (void**) &key)); + } + } + + return 0; +} + +static int process_rules_cgroup_context(Manager *m, OomdCGroupContext *ctx, usec_t usec_now) { + int r; + + assert(m); + assert(ctx); + + if (strv_isempty(ctx->rules)) + return 0; + + STRV_FOREACH(rule_name, ctx->rules) { + OomdRuleset *ruleset = hashmap_get(m->rulesets, *rule_name); + if (!ruleset) + /* Silently skip: already warned once when the subscription was attached or when + * rulesets were loaded. Repeating here would fire every interval. */ + continue; + + r = ruleset_check_conditions(m, ctx, ruleset, *rule_name, usec_now); + if (r < 0) + continue; + if (r == 0) + continue; + + r = ruleset_execute_action(m, ctx, ruleset, *rule_name, usec_now); + if (r < 0) + return r; + + /* Only remove start time if the action actually killed something, so that + * LastingSec must be satisfied again before re-triggering. If the action + * failed to kill, keep the timer running to retry on the next interval. */ + if (r > 0) { + _cleanup_free_ char *action_key = NULL; + free(hashmap_remove2(ruleset->start_times, ctx->path, (void **) &action_key)); + + /* Global (not per-cgroup/per-ruleset) post-action delay: after any + * successful ruleset kill we suppress *all* subsequent rule evaluations + * until POST_ACTION_DELAY_USEC elapses. This is intentional — pressure + * and swap metrics need time to reflect the effect of a kill before we + * act again, otherwise a single overload could cascade into multiple + * unrelated kills across sibling cgroups within the same interval. */ + m->rules_post_action_delay_start = usec_now; + return 0; + } + } + + return 0; +} + +static int monitor_rules_contexts_handler(sd_event_source *s, uint64_t usec, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + bool in_post_action_delay = false; + usec_t usec_now; + int r; + + assert(s); + + /* Reset timer */ + r = sd_event_now(sd_event_source_get_event(s), CLOCK_MONOTONIC, &usec_now); + if (r < 0) + return log_error_errno(r, "Failed to reset event timer: %m"); + + r = sd_event_source_set_time_relative(s, RULESETS_INTERVAL_USEC); + if (r < 0) + return log_error_errno(r, "Failed to set relative time for timer: %m"); + + /* Reconnect if our connection dropped */ + if (!m->varlink_client) { + r = acquire_managed_oom_connect(m); + if (r < 0) + return log_error_errno(r, "Failed to acquire varlink connection: %m"); + } + + /* Return early if no rules are set */ + if (hashmap_isempty(m->monitored_rules_cgroup_contexts)) + return 0; + + /* Determine whether we're still inside the post-action delay window before doing any + * heavy lifting, so we can short-circuit the expensive descendant walk below. */ + if (m->rules_post_action_delay_start > 0) { + if (usec_add(m->rules_post_action_delay_start, POST_ACTION_DELAY_USEC) > usec_now) + in_post_action_delay = true; + else + m->rules_post_action_delay_start = 0; + } + + /* Always keep the subscribed (parent) cgroup contexts fresh so pgscan rate differentials + * stay accurate across intervals, even during the post-action delay. Only suppress the + * kill action itself. + * + * Note: update_monitored_cgroup_contexts() rebuilds the hashmap by calling + * oomd_insert_cgroup_context(), which also carries over the per-cgroup 'rules' strv + * from the old context. We rely on that implicit rule propagation here — the + * rules attached to each cgroup context persist across refreshes. */ + r = update_monitored_cgroup_contexts(&m->monitored_rules_cgroup_contexts); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + log_debug_errno(r, "Failed to update monitored rules cgroup contexts, ignoring: %m"); + + /* The candidate refresh is the expensive part — it recursively walks descendants of every + * monitored cgroup. Since candidates are only consumed by kill-by-pgscan / kill-by-swap + * (both suppressed during the delay), skip the walk while we're not going to act. */ + if (!in_post_action_delay) { + r = update_monitored_cgroup_contexts_candidates( + m->monitored_rules_cgroup_contexts, &m->monitored_rules_cgroup_contexts_candidates); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + log_debug_errno(r, "Failed to update monitored rules cgroup candidates, ignoring: %m"); + } + + r = prune_stale_ruleset_start_times(m); + if (r < 0) + return r; + + if (in_post_action_delay) + return 0; + + /* Only read /proc/meminfo if at least one ruleset actually needs swap info — either as + * a SwapUsageMax= condition or as a kill-by-swap action (which uses swap_total to + * compute the per-cgroup selection threshold). */ + OomdRuleset *ruleset; + HASHMAP_FOREACH(ruleset, m->rulesets) + if (ruleset->swap_above >= 0 || ruleset->action == OOMD_ACTION_KILL_BY_SWAP) { + r = oomd_system_context_acquire("/proc/meminfo", &m->system_context); + if (r < 0) + return log_error_errno(r, "Failed to acquire system context: %m"); + break; + } + + OomdCGroupContext *ctx; + HASHMAP_FOREACH(ctx, m->monitored_rules_cgroup_contexts) { + r = process_rules_cgroup_context(m, ctx, usec_now); + if (r < 0) + return r; + + /* process_rules_cgroup_context() sets rules_post_action_delay_start when it queues + * a kill. Honor the delay *within the same tick* too: otherwise a single overload + * could cascade into kills across unrelated sibling cgroups before pressure metrics + * have a chance to reflect the first kill. */ + if (m->rules_post_action_delay_start > 0) + break; + } + + return 0; +} + static int monitor_swap_contexts(Manager *m) { _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; int r; @@ -633,6 +1112,31 @@ static int monitor_memory_pressure_contexts(Manager *m) { return 0; } +static int monitor_rules_contexts(Manager *m) { + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + int r; + + assert(m); + assert(m->event); + + r = sd_event_add_time(m->event, &s, CLOCK_MONOTONIC, 0, 0, monitor_rules_contexts_handler, m); + if (r < 0) + return r; + + r = sd_event_source_set_exit_on_failure(s, true); + if (r < 0) + return r; + + r = sd_event_source_set_enabled(s, SD_EVENT_OFF); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s, "oomd-rules-timer"); + + m->rules_context_event_source = TAKE_PTR(s); + return 0; +} + Manager* manager_free(Manager *m) { assert(m); @@ -640,6 +1144,7 @@ Manager* manager_free(Manager *m) { sd_varlink_close_unref(m->varlink_client); sd_event_source_unref(m->swap_context_event_source); sd_event_source_unref(m->mem_pressure_context_event_source); + sd_event_source_unref(m->rules_context_event_source); sd_event_unref(m->event); hashmap_free(m->polkit_registry); @@ -648,9 +1153,13 @@ Manager* manager_free(Manager *m) { hashmap_free(m->monitored_swap_cgroup_contexts); hashmap_free(m->monitored_mem_pressure_cgroup_contexts); hashmap_free(m->monitored_mem_pressure_cgroup_contexts_candidates); + hashmap_free(m->monitored_rules_cgroup_contexts); + hashmap_free(m->monitored_rules_cgroup_contexts_candidates); set_free(m->kill_states); + hashmap_free(m->rulesets); + return mfree(m); } @@ -661,6 +1170,7 @@ static int manager_dispatch_reload_signal(sd_event_source *s, const struct signa manager_set_defaults(m); manager_parse_config_file(m); + warn_missing_rulesets(m); (void) sd_notify(/* unset_environment= */ false, NOTIFY_READY_MESSAGE); return 0; @@ -705,6 +1215,14 @@ int manager_new(Manager **ret) { if (!m->monitored_mem_pressure_cgroup_contexts_candidates) return -ENOMEM; + m->monitored_rules_cgroup_contexts = hashmap_new(&oomd_cgroup_ctx_hash_ops); + if (!m->monitored_rules_cgroup_contexts) + return -ENOMEM; + + m->monitored_rules_cgroup_contexts_candidates = hashmap_new(&oomd_cgroup_ctx_hash_ops); + if (!m->monitored_rules_cgroup_contexts_candidates) + return -ENOMEM; + *ret = TAKE_PTR(m); return 0; } @@ -814,6 +1332,10 @@ int manager_start( if (r < 0) return r; + r = monitor_rules_contexts(m); + if (r < 0) + return r; + return 0; } diff --git a/src/oom/oomd-manager.h b/src/oom/oomd-manager.h index 8b9476232fb59..cc588461b0860 100644 --- a/src/oom/oomd-manager.h +++ b/src/oom/oomd-manager.h @@ -2,15 +2,22 @@ #pragma once #include "conf-parser-forward.h" +#include "constants.h" #include "shared-forward.h" #include "oomd-conf.h" #include "oomd-util.h" +#define RULESET_DIRS ((const char* const*) CONF_PATHS_STRV("systemd/oomd/rules.d")) + /* Polling interval for monitoring stats */ #define SWAP_INTERVAL_USEC 150000 /* 0.15 seconds */ /* Pressure counters are lagging (~2 seconds) compared to swap so polling too frequently just wastes CPU */ #define MEM_PRESSURE_INTERVAL_USEC (1 * USEC_PER_SEC) +/* Rules evaluate both pressure and swap metrics; align on the slower-moving metric + * (pressure counters lag ~2s), so polling faster than 1s just wastes CPU. */ +#define RULESETS_INTERVAL_USEC MEM_PRESSURE_INTERVAL_USEC + /* Take action if 10s of memory pressure > 60 for more than 30s. We use the "full" value from PSI so this is the * percentage of time all tasks were delayed (i.e. unproductive). * Generally 60 or higher might be acceptable for something like system.slice with no memory.high set; processes in @@ -25,6 +32,25 @@ #define RECLAIM_DURATION_USEC (30 * USEC_PER_SEC) #define POST_ACTION_DELAY_USEC (15 * USEC_PER_SEC) +typedef enum OomdAction { + OOMD_ACTION_NONE, + OOMD_ACTION_KILL_ALL, + OOMD_ACTION_KILL_BY_PGSCAN, + OOMD_ACTION_KILL_BY_SWAP, + _OOMD_ACTION_MAX, + _OOMD_ACTION_INVALID = -EINVAL, +} OomdAction; + +typedef struct OomdRuleset { + char *name; + int memory_pressure_above; /* permyriad (0-10000), or -1 for unset */ + int swap_above; /* permyriad (0-10000), or -1 for unset */ + OomdAction action; + usec_t lasting_usec; + Hashmap *start_times; /* key: cgroup path (char*) -> value: heap-allocated timestamp (usec_t*) */ + bool warned_no_swap; /* latched once we've warned that kill-by-swap is misconfigured */ +} OomdRuleset; + typedef struct Manager { sd_bus *bus; sd_event *event; @@ -41,13 +67,17 @@ typedef struct Manager { Hashmap *monitored_swap_cgroup_contexts; Hashmap *monitored_mem_pressure_cgroup_contexts; Hashmap *monitored_mem_pressure_cgroup_contexts_candidates; + Hashmap *monitored_rules_cgroup_contexts; + Hashmap *monitored_rules_cgroup_contexts_candidates; OomdSystemContext system_context; usec_t mem_pressure_post_action_delay_start; + usec_t rules_post_action_delay_start; sd_event_source *swap_context_event_source; sd_event_source *mem_pressure_context_event_source; + sd_event_source *rules_context_event_source; /* This varlink object is used to manage the subscription from systemd-oomd to PID1 which it uses to * listen for changes in ManagedOOM settings (oomd client - systemd server). */ @@ -58,6 +88,7 @@ typedef struct Manager { usec_t prekill_timeout; Set *kill_states; /* currently ongoing OomdKillState operations */ + Hashmap *rulesets; } Manager; Manager* manager_free(Manager *m); diff --git a/src/oom/oomd-util.c b/src/oom/oomd-util.c index 55e17df46f08c..d488277c47b5a 100644 --- a/src/oom/oomd-util.c +++ b/src/oom/oomd-util.c @@ -14,6 +14,7 @@ #include "parse-util.h" #include "path-util.h" #include "pidref.h" +#include "process-util.h" #include "procfs-util.h" #include "sd-bus.h" #include "set.h" @@ -21,13 +22,14 @@ #include "sort-util.h" #include "stdio-util.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" #include "varlink-util.h" typedef struct OomdKillState { Manager *manager; OomdCGroupContext *ctx; - const char *reason; + char *reason; /* This holds sd_varlink references */ Set *links; } OomdKillState; @@ -80,6 +82,7 @@ static OomdCGroupContext *oomd_cgroup_context_free(OomdCGroupContext *ctx) { return NULL; free(ctx->path); + strv_free(ctx->rules); return mfree(ctx); } @@ -247,24 +250,46 @@ int oomd_sort_cgroup_contexts(Hashmap *h, oomd_compare_t compare_func, const cha } int oomd_cgroup_kill(Manager *m, OomdCGroupContext *ctx, bool recurse, const char *reason) { - _cleanup_set_free_ Set *pids_killed = NULL; + _cleanup_free_ char *cg_path_self = NULL; + uint64_t n_pids_killed = UINT64_MAX; int r; assert(ctx); assert(!m || reason); - pids_killed = set_new(NULL); - if (!pids_killed) - return -ENOMEM; + /* Just for safety, in case group.kill is used */ + r = cg_pid_get_path(getpid_cached(), &cg_path_self); + if (r < 0) + log_debug_errno(r, "Failed to get cgroup path for self, ignoring: %m"); + else if (streq(cg_path_self, ctx->path)) { + log_debug("Skipping own cgroup '%s'", ctx->path); + return 0; + } r = increment_oomd_xattr(ctx->path, "user.oomd_ooms", 1); if (r < 0) log_debug_errno(r, "Failed to set user.oomd_ooms before kill: %m"); - if (recurse) - r = cg_kill_recursive(ctx->path, SIGKILL, CGROUP_IGNORE_SELF, pids_killed, log_kill, NULL); - else - r = cg_kill(ctx->path, SIGKILL, CGROUP_IGNORE_SELF, pids_killed, log_kill, NULL); + if (recurse) { + r = cg_kill_kernel_sigkill(ctx->path, &n_pids_killed); + if (r == -EOPNOTSUPP) { + _cleanup_set_free_ Set *pids_killed = set_new(/* hash_ops= */ NULL); + if (!pids_killed) + return -ENOMEM; + + r = cg_kill_recursive(ctx->path, SIGKILL, CGROUP_IGNORE_SELF, pids_killed, log_kill, /* userdata= */ NULL); + if (r >= 0) + n_pids_killed = set_size(pids_killed); + } + } else { + _cleanup_set_free_ Set *pids_killed = set_new(/* hash_ops= */ NULL); + if (!pids_killed) + return -ENOMEM; + + r = cg_kill(ctx->path, SIGKILL, CGROUP_IGNORE_SELF, pids_killed, log_kill, /* userdata= */ NULL); + if (r >= 0) + n_pids_killed = set_size(pids_killed); + } /* The cgroup could have been cleaned up after we have sent SIGKILL to all of the processes, but before * we could do one last iteration of cgroup.procs to check. Or the service unit could have exited and @@ -275,12 +300,13 @@ int oomd_cgroup_kill(Manager *m, OomdCGroupContext *ctx, bool recurse, const cha else if (r < 0) return r; - if (set_isempty(pids_killed)) + if (n_pids_killed == 0) log_debug("Nothing killed when attempting to kill %s", ctx->path); - - r = increment_oomd_xattr(ctx->path, "user.oomd_kill", set_size(pids_killed)); - if (r < 0) - log_debug_errno(r, "Failed to set user.oomd_kill on kill: %m"); + else if (n_pids_killed != UINT64_MAX) { + r = increment_oomd_xattr(ctx->path, "user.oomd_kill", n_pids_killed); + if (r < 0) + log_debug_errno(r, "Failed to set user.oomd_kill on kill, ignoring: %m"); + } /* send dbus signal */ if (m) @@ -292,7 +318,7 @@ int oomd_cgroup_kill(Manager *m, OomdCGroupContext *ctx, bool recurse, const cha ctx->path, reason); - return !set_isempty(pids_killed); + return n_pids_killed > 0 && n_pids_killed != UINT64_MAX; } static void oomd_kill_state_free(OomdKillState *ks) { @@ -305,6 +331,7 @@ static void oomd_kill_state_free(OomdKillState *ks) { set_remove(ks->manager->kill_states, ks); oomd_cgroup_context_unref(ks->ctx); + free(ks->reason); free(ks); } @@ -485,6 +512,10 @@ int oomd_cgroup_kill_mark(Manager *m, OomdCGroupContext *ctx, const char *reason return 0; } + _cleanup_free_ char *reason_copy = strdup(reason); + if (!reason_copy) + return log_oom_debug(); + _cleanup_(oomd_kill_state_removep) OomdKillState *ks = new(OomdKillState, 1); if (!ks) return log_oom_debug(); @@ -492,7 +523,7 @@ int oomd_cgroup_kill_mark(Manager *m, OomdCGroupContext *ctx, const char *reason *ks = (OomdKillState) { .manager = m, .ctx = oomd_cgroup_context_ref(ctx), - .reason = reason, + .reason = TAKE_PTR(reason_copy), }; r = set_ensure_put(&m->kill_states, &oomd_kill_state_hash_ops, ks); @@ -503,6 +534,7 @@ int oomd_cgroup_kill_mark(Manager *m, OomdCGroupContext *ctx, const char *reason * cleanup path would remove by cgroup path key and could interfere with the existing queued * kill state. */ oomd_cgroup_context_unref(ks->ctx); + free(ks->reason); ks = mfree(ks); return 0; } @@ -585,14 +617,14 @@ int oomd_select_by_pgscan_rate(Hashmap *h, const char *prefix, OomdCGroupContext return ret; } -int oomd_select_by_swap_usage(Hashmap *h, uint64_t threshold_usage, OomdCGroupContext **ret_selected) { +int oomd_select_by_swap_usage(Hashmap *h, const char *prefix, uint64_t threshold_usage, OomdCGroupContext **ret_selected) { _cleanup_free_ OomdCGroupContext **sorted = NULL; int r, n, ret = 0; assert(h); assert(ret_selected); - n = oomd_sort_cgroup_contexts(h, compare_swap_usage, NULL, &sorted); + n = oomd_sort_cgroup_contexts(h, compare_swap_usage, prefix, &sorted); if (n < 0) return n; @@ -624,7 +656,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 +710,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); @@ -790,6 +818,9 @@ int oomd_insert_cgroup_context(Hashmap *old_h, Hashmap *new_h, const char *path) curr_ctx->mem_pressure_limit_hit_start = old_ctx->mem_pressure_limit_hit_start; curr_ctx->mem_pressure_duration_usec = old_ctx->mem_pressure_duration_usec; curr_ctx->last_had_mem_reclaim = old_ctx->last_had_mem_reclaim; + curr_ctx->rules = strv_copy(old_ctx->rules); + if (old_ctx->rules && !curr_ctx->rules) + return -ENOMEM; } if (oomd_pgscan_rate(curr_ctx) > 0) @@ -821,6 +852,9 @@ void oomd_update_cgroup_contexts_between_hashmaps(Hashmap *old_h, Hashmap *curr_ ctx->mem_pressure_limit_hit_start = old_ctx->mem_pressure_limit_hit_start; ctx->mem_pressure_duration_usec = old_ctx->mem_pressure_duration_usec; ctx->last_had_mem_reclaim = old_ctx->last_had_mem_reclaim; + /* Note: rules are intentionally not copied here. This function is only used on + * candidate hashmaps (populated by recursively_get_cgroup_context for descendant + * cgroups), which never carry rules. */ if (oomd_pgscan_rate(ctx) > 0) ctx->last_had_mem_reclaim = now(CLOCK_MONOTONIC); diff --git a/src/oom/oomd-util.h b/src/oom/oomd-util.h index d4e1a9207bd50..a76454f812393 100644 --- a/src/oom/oomd-util.h +++ b/src/oom/oomd-util.h @@ -40,6 +40,7 @@ struct OomdCGroupContext { usec_t mem_pressure_limit_hit_start; usec_t last_had_mem_reclaim; usec_t mem_pressure_duration_usec; + char **rules; }; struct OomdSystemContext { @@ -132,7 +133,7 @@ int oomd_cgroup_kill_mark(Manager *m, OomdCGroupContext *ctx, const char *reason * everything in `h` is a candidate. * Returns the killed cgroup in ret_selected. */ int oomd_select_by_pgscan_rate(Hashmap *h, const char *prefix, OomdCGroupContext **ret_selected); -int oomd_select_by_swap_usage(Hashmap *h, uint64_t threshold_usage, OomdCGroupContext **ret_selected); +int oomd_select_by_swap_usage(Hashmap *h, const char *prefix, uint64_t threshold_usage, OomdCGroupContext **ret_selected); int oomd_cgroup_context_acquire(const char *path, OomdCGroupContext **ret); int oomd_system_context_acquire(const char *proc_meminfo_path, OomdSystemContext *ret); diff --git a/src/oom/oomd.c b/src/oom/oomd.c index b1d3efb8f5733..62eecfc065c65 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,59 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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, + opts.arg, BUS_IMPLEMENTATIONS(&manager_object, &log_control_object)); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); diff --git a/src/oom/test-oomd-util.c b/src/oom/test-oomd-util.c index 7332f532a8aaa..14a821ebebecb 100644 --- a/src/oom/test-oomd-util.c +++ b/src/oom/test-oomd-util.c @@ -291,7 +291,7 @@ TEST(oomd_cgroup_context_acquire_and_insert) { ASSERT_EQ(ctx->memory_low, 0u); ASSERT_EQ(ctx->swap_usage, 0u); ASSERT_EQ(ctx->last_pgscan, 0u); - ASSERT_EQ(ctx->pgscan, 0u); + ASSERT_LT(ctx->pgscan, 10u); ASSERT_NULL(ctx = oomd_cgroup_context_unref(ctx)); ASSERT_OK(oomd_cgroup_context_acquire("", &ctx)); @@ -309,7 +309,6 @@ TEST(oomd_cgroup_context_acquire_and_insert) { c1->mem_pressure_limit = 6789; c1->mem_pressure_limit_hit_start = 42; c1->mem_pressure_duration_usec = 1234; - c1->last_had_mem_reclaim = 888; ASSERT_NOT_NULL(h2 = hashmap_new(&oomd_cgroup_ctx_hash_ops)); ASSERT_OK(oomd_insert_cgroup_context(h1, h2, cgroup)); ASSERT_NOT_NULL(c1 = hashmap_get(h1, cgroup)); @@ -319,7 +318,6 @@ TEST(oomd_cgroup_context_acquire_and_insert) { ASSERT_EQ(c2->mem_pressure_limit, 6789u); ASSERT_EQ(c2->mem_pressure_limit_hit_start, 42u); ASSERT_EQ(c2->mem_pressure_duration_usec, 1234u); - ASSERT_EQ(c2->last_had_mem_reclaim, 888u); /* assumes the live pgscan is less than UINT64_MAX */ } TEST(oomd_update_cgroup_contexts_between_hashmaps) { diff --git a/src/path/path-tool.c b/src/path/path-tool.c index 62eade3b05dfa..29696501d03a0 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,56 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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 = opts.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(&opts); return 1; } @@ -246,15 +233,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..067cc92ff3916 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -1,25 +1,28 @@ /* 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 "help-util.h" #include "json-util.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "pcrextend-util.h" -#include "pretty-print.h" #include "string-table.h" #include "string-util.h" #include "strv.h" #include "tpm2-pcr.h" #include "tpm2-util.h" +#include "user-record.h" +#include "userdb.h" #include "varlink-io.systemd.PCRExtend.h" #include "varlink-util.h" @@ -29,7 +32,8 @@ 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 UserRecord *arg_login = NULL; +static uint32_t arg_pcr_mask = 0; static char *arg_nvpcr_name = NULL; static bool arg_varlink = false; static bool arg_early = false; @@ -39,130 +43,97 @@ STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep); STATIC_DESTRUCTOR_REGISTER(arg_nvpcr_name, freep); +STATIC_DESTRUCTOR_REGISTER(arg_login, user_record_unrefp); #define EXTENSION_STRING_SAFE_LIMIT 1024 -static int help(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *link = NULL; +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd-pcrextend", "8", &link); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + 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", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS...] WORD"); + help_cmdline("[OPTIONS...] --file-system=PATH"); + help_cmdline("[OPTIONS...] --machine-id"); + help_cmdline("[OPTIONS...] --product-id"); + help_cmdline("[OPTIONS...] --login=UID|USER"); + help_abstract("Extend a TPM2 PCR with boot phase, machine ID, file system ID or user record."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-pcrextend", "8"); 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, - }; - - 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 }, - {} - }; - - int c, r; - +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 opts = { argc, argv }; + int r; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + + implementation = sym_EVP_get_digestbyname(opts.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.", opts.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(opts.arg)) { + arg_pcr_mask = 0; + break; + } + + r = tpm2_pcr_index_from_string(opts.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", opts.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(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NvPCR name is not valid: %s", opts.arg); - r = free_and_strdup_warn(&arg_nvpcr_name, optarg); + r = free_and_strdup_warn(&arg_nvpcr_name, opts.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(opts.arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(opts.arg, "auto")) { + device = strdup(opts.arg); if (!device) return log_oom(); } @@ -171,49 +142,60 @@ 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(opts.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("login", "UID|USER", + "Measure a user's record into NvPCR 'login'"): { + _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + + r = userdb_by_name(opts.arg, /* match= */ NULL, USERDB_PARSE_NUMERIC|USERDB_SUPPRESS_SHADOW, &ur); + if (r < 0) + return log_error_errno(r, "Failed to look up user '%s': %m", opts.arg); + + user_record_unref(arg_login); + arg_login = TAKE_PTR(ur); + break; + } + + 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(opts.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(opts.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", opts.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_file_system + arg_machine_id + arg_product_id + !!arg_login > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system=, --machine-id, --product-id, --login= 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 +203,25 @@ 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; - - r = free_and_strdup_warn(&arg_nvpcr_name, arg_product_id ? "hardware" : NULL); + 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 || arg_login) ? 0 : /* → NvPCR */ + INDEX_TO_MASK(uint32_t, TPM2_PCR_KERNEL_BOOT); /* → PCR 11 */ + + r = free_and_strdup_warn(&arg_nvpcr_name, + arg_product_id ? "hardware" : + arg_login ? "login" : + NULL); if (r < 0) return r; } + *ret_args = option_parser_get_args(&opts); 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 +230,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 +242,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); @@ -274,8 +261,50 @@ static int escape_and_truncate_data(const void *data, size_t size, char **ret) { return 0; } +static int tpm2_context_new_for_measurement(Tpm2Context **ret) { + int r; + + assert(ret); + + /* Wrapper around tpm2_context_new_or_warn() that translates a missing TPM device from -ENOENT to + * -EOPNOTSUPP, for two reasons: + * + * - It disambiguates -ENOENT on the NvPCR path. There -ENOENT also means "no such NvPCR definition" + * (from tpm2_nvpcr_extend_bytes() → nvpcr_data_load()), which vl_method_extend() maps to the + * io.systemd.PCRExtend.NoSuchNvPCR error. If "no TPM device" stayed -ENOENT it would be + * misreported as a missing NvPCR definition. So we keep each errno single-meaning: -ENOENT = "no + * such NvPCR definition", -EOPNOTSUPP = "TPM cannot be used for this measurement" (also what + * tpm2_context_new_or_warn() already returns for missing crypto / an OpenSSL-less build, and what + * extend_pcr_now() returns when no PCR bank is enabled). + * + * - It lets the --graceful skip in run() match a single errno (-EOPNOTSUPP). + * + * We deliberately translate *only* -ENOENT here, not every "TPM unusable" errno: + * + * - Genuine absence (no TPM hardware, no tpm2 libraries) never reaches this point under --graceful: + * run() bails out earlier at the tpm2_is_mostly_supported() guard, which requires the kernel + * driver + tpm subsystem + libtss2 esys/rc/mu. So broadening the set buys nothing for the common + * "no TPM" case. + * + * - -ENOPKG (TCTI driver libtss2-tcti-device.so.0 not loadable) can therefore only happen when the + * rest of the stack *is* present — i.e. a half-installed tpm2-tss, a misconfiguration. + * + * - -ENOTRECOVERABLE (TCTI/Esys init or TPM startup failed) means a TPM is present but + * malfunctioning. + * + * Both of the latter are real faults we want to surface and fail on, not silently skip: + * --graceful's contract is "no TPM2 device is found", i.e. absence, not breakage. -EINVAL (bad + * device string), -ENOMEM, etc. likewise stay hard errors. */ + + r = tpm2_context_new_or_warn(arg_tpm2_device, ret); + if (r == -ENOENT) + return -EOPNOTSUPP; + + return r; +} + static int extend_pcr_now( - unsigned pcr, + uint32_t pcr_mask, const void *data, size_t size, Tpm2UserspaceEventType event) { @@ -283,15 +312,17 @@ static int extend_pcr_now( _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; int r; - r = tpm2_context_new_or_warn(arg_tpm2_device, &c); + assert(pcr_mask != 0); + + r = tpm2_context_new_for_measurement(&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? */ - return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Found a TPM2 without enabled PCR banks. Can't operate."); + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Found a TPM2 without enabled PCR banks. Can't operate."); _cleanup_free_ char *joined_banks = NULL; joined_banks = strv_join(arg_banks, ", "); @@ -302,18 +333,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,7 +360,9 @@ static int extend_nvpcr_now( _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; int r; - r = tpm2_context_new_or_warn(arg_tpm2_device, &c); + assert(name); + + r = tpm2_context_new_for_measurement(&c); if (r < 0) return r; @@ -428,12 +463,15 @@ static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_va else return sd_varlink_error_invalid_parameter_name(link, "text"); + if (!iovec_is_set(extend_iovec)) + return sd_varlink_error_invalid_parameter_name(link, p.text ? "text" : "data"); + if (p.nvpcr) { r = extend_nvpcr_now(p.nvpcr, extend_iovec->iov_base, extend_iovec->iov_len, p.event_type); - if (r == -ENOENT) + if (IN_SET(r, -ENOENT, -ENODEV)) 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; @@ -444,7 +482,9 @@ static int vl_server(void) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; int r; - r = varlink_server_new(&varlink_server, SD_VARLINK_SERVER_ROOT_ONLY, /* userdata= */ NULL); + r = varlink_server_new(&varlink_server, + SD_VARLINK_SERVER_ROOT_ONLY | SD_VARLINK_SERVER_MYSELF_ONLY, + /* userdata= */ NULL); if (r < 0) return log_error_errno(r, "Failed to allocate Varlink server: %m"); @@ -470,15 +510,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 +532,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 +543,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); @@ -508,11 +551,22 @@ static int run(int argc, char *argv[]) { return r; event = TPM2_EVENT_PRODUCT_ID; + + } else if (arg_login) { + + if (n_args != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument."); + + r = pcrextend_login_word(arg_login, &word); + if (r < 0) + return r; + + event = TPM2_EVENT_LOGIN; } 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 +583,32 @@ 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); + /* Both extend paths report "TPM cannot be used for this measurement" (no PCR bank, missing crypto, + * no TPM device — see tpm2_context_new_for_measurement()) as -EOPNOTSUPP. Under --graceful we skip + * those rather than fail and block boot. Genuine faults keep their own errno and are never + * suppressed. */ + if (arg_graceful && r == -EOPNOTSUPP) { + log_notice_errno(r, "TPM2 cannot be used for measurement (no usable PCR bank, missing device, or missing crypto support), skipping gracefully."); + return EXIT_SUCCESS; + } if (r < 0) return r; diff --git a/src/pcrlock/meson.build b/src/pcrlock/meson.build index 6533ef3ab8f17..dbe816402deca 100644 --- a/src/pcrlock/meson.build +++ b/src/pcrlock/meson.build @@ -12,10 +12,10 @@ executables += [ 'pcrlock-firmware.c', ), 'dependencies' : [ - libm, - libopenssl, + libopenssl_cflags, tpm2, ], + 'public' : true, }, ] @@ -27,6 +27,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..8b4ebceeb65b7 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 @@ -10,6 +8,7 @@ #include "sd-varlink.h" #include "alloc-util.h" +#include "ansi-color.h" #include "ask-password-api.h" #include "bitfield.h" #include "blockdev-util.h" @@ -19,6 +18,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" @@ -30,14 +30,17 @@ #include "format-table.h" #include "format-util.h" #include "fs-util.h" +#include "glyph-util.h" #include "gpt.h" +#include "help-util.h" #include "hexdecoct.h" #include "initrd-util.h" #include "json-util.h" #include "label-util.h" #include "list.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" +#include "options.h" #include "ordered-set.h" #include "parse-argument.h" #include "parse-util.h" @@ -45,7 +48,6 @@ #include "pcrextend-util.h" #include "pcrlock-firmware.h" #include "pe-binary.h" -#include "pretty-print.h" #include "proc-cmdline.h" #include "recovery-key.h" #include "sort-util.h" @@ -59,6 +61,7 @@ #include "unit-name.h" #include "utf8.h" #include "varlink-io.systemd.PCRLock.h" +#include "varlink-io.systemd.SysUpdate.Notify.h" #include "varlink-util.h" #include "verbs.h" @@ -543,7 +546,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 +630,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; } @@ -744,6 +747,23 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { break; } + /* SMBIOS structures measured by sd-boot/sd-stub. The tagged event payload is just a + * constant identifying string ("smbios:typeN"), hence don't show it. */ + case SMBIOS_TYPE1_EVENT_TAG_ID: + if (!strextend_with_separator(&rec->description, ", ", "systemd: SMBIOS system information (type 1)")) + return log_oom(); + break; + + case SMBIOS_TYPE2_EVENT_TAG_ID: + if (!strextend_with_separator(&rec->description, ", ", "systemd: SMBIOS baseboard information (type 2)")) + return log_oom(); + break; + + case SMBIOS_TYPE11_EVENT_TAG_ID: + if (!strextend_with_separator(&rec->description, ", ", "systemd: SMBIOS OEM strings (type 11)")) + return log_oom(); + break; + default: { _cleanup_free_ char *s = NULL; @@ -817,7 +837,9 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { goto invalid; } - if (left < offsetof(packed_EFI_DEVICE_PATH, path) || left < dp->length) { + if (left < offsetof(packed_EFI_DEVICE_PATH, path) || + dp->length < offsetof(packed_EFI_DEVICE_PATH, path) || + left < dp->length) { log_warning("Device path element too short, ignoring."); goto invalid; } @@ -899,6 +921,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 +975,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 +1368,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 +1376,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 +1401,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 +1503,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 +1513,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 +1563,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 +1571,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 +1597,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 +1744,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; } @@ -1938,7 +1969,7 @@ static int event_log_match_component_variant( return r; if (assign) { - /* Take ownership (Note we allow multiple components and variants to take owneship of the same record!) */ + /* Take ownership (Note we allow multiple components and variants to take ownership of the same record!) */ if (!GREEDY_REALLOC(el->records[i]->mapped, el->records[i]->n_mapped+1)) return log_oom(); @@ -2447,6 +2478,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 +2522,9 @@ static int event_log_load_and_process(EventLog **ret) { return 0; } -static int verb_show_log(int argc, char *argv[], void *userdata) { +VERB_DEFAULT_NOARG(verb_show_log, "log", + "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 +2637,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 +2674,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 +2810,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 +2819,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 +2843,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 +2863,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 +2885,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 +2895,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 +2920,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 +2953,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 +2996,2258 @@ 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."); - - /* Check for duplicates */ - if (records[found]) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate '%s' record, refusing.", rec->description); + 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 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); + if (result->hash[i].size <= 0) /* already invalidated */ + continue; - records[found] = rec; - } - - /* 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); + b = event_log_record_find_bank(rec, tpm2_hash_algorithms[i]); + if (!b) { + /* Can't calculate, hence invalidate */ + result->hash[i] = (TPM2B_DIGEST) {}; + continue; + } - /* At this point we know that all required variables have been measured, in the right order. */ - return 0; -} + md_ctx = sym_EVP_MD_CTX_new(); + if (!md_ctx) + return log_oom(); -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"); + } + + sd_json_variant_dump(j, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL); + return 0; } - vdata_size = (uint8_t*) qq - (uint8_t*) vdata; + for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { + Tpm2PCRPredictionResult *result; + if (!BIT_SET(context->pcrs, pcr)) + continue; - r = make_pcrlock_record(TPM2_PCR_BOOT_LOADER_CONFIG /* =5 */, vdata, vdata_size, &record); - if (r < 0) - return r; + if (ordered_set_isempty(context->results[pcr])) { + printf("No results for PCR %u (%s).\n", pcr, tpm2_pcr_index_to_string(pcr)); + continue; + } - r = sd_json_variant_new_array(&array, &record, 1); - if (r < 0) - return log_error_errno(r, "Failed to append to JSON array: %m"); + printf("%sResults for PCR %u (%s):%s\n", ansi_underline(), pcr, tpm2_pcr_index_to_string(pcr), ansi_normal()); - return write_pcrlock(array, PCRLOCK_GPT_PATH); -} + ORDERED_SET_FOREACH(result, context->results[pcr]) { -static int verb_unlock_gpt(int argc, char *argv[], void *userdata) { - return unlink_pcrlock(PCRLOCK_GPT_PATH); -} + _cleanup_free_ char *aa = NULL, *h = NULL; + const char *a; -static bool event_log_record_is_separator(const EventLogRecord *rec) { - assert(rec); + TPM2B_DIGEST *hash = tpm2_pcr_prediction_result_get_hash(result, alg); + if (!hash) + continue; - /* Recognizes EV_SEPARATOR events */ + a = ASSERT_PTR(tpm2_hash_alg_to_string(alg)); + aa = strdup(a); + if (!aa) + return log_oom(); - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - return false; + ascii_strlower(aa); - if (rec->firmware_event_type != EV_SEPARATOR) - return false; + h = hexmem(hash->buffer, hash->size); + if (!h) + return log_oom(); - return rec->event_payload_valid == EVENT_PAYLOAD_VALID_YES; /* Insist the record is consistent */ + printf(" %s%-6s:%s %s\n", ansi_grey(), aa, ansi_normal(), h); + } + } + + return 0; } -static int event_log_record_is_action_calling_efi_app(const EventLogRecord *rec) { - _cleanup_free_ char *d = NULL; - int r; +static int tpm2_pcr_prediction_run( + EventLog *el, + Tpm2PCRPrediction *context) { - assert(rec); + int r; - /* Recognizes the special EV_EFI_ACTION that is issues when the firmware passes control to the boot loader. */ + assert(el); + assert(context); - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - return false; + for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { + _cleanup_free_ Tpm2PCRPredictionResult *result = NULL; - if (rec->pcr != TPM2_PCR_BOOT_LOADER_CODE) - return false; + if (!BIT_SET(context->pcrs, pcr)) + continue; - if (rec->firmware_event_type != EV_EFI_ACTION) - return false; + result = new0(Tpm2PCRPredictionResult, 1); + if (!result) + return log_oom(); - if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) /* Insist the record is consistent */ - return false; + 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); - r = make_cstring(rec->firmware_payload, rec->firmware_payload_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, &d); - if (r < 0) - return r; + r = event_log_predict_pcrs( + el, + context, + result, + /* component_index= */ 0, + pcr, + /* path= */ NULL); + if (r < 0) + return r; + } - 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; +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; - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_SSE)) - return; + r = event_log_load_and_process(&el); + if (r < 0) + return r; - log_notice("Enabling JSON_SEQ mode, since writing two .pcrlock files to single output."); - arg_json_format_flags |= SD_JSON_FORMAT_SSE; -} + count = event_log_calculate_component_combinations(el); + if (count < 0) + return count; -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; + log_info("%zi combinations of components.", count); - enable_json_sse(); + r = event_log_reduce_to_safe_pcrs(el, &context.pcrs); + 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 */ + r = tpm2_pcr_prediction_run(el, &context); + if (r < 0) + return r; - separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; /* → 4 */ + return event_log_show_predictions(&context, el->primary_algorithm); +} - 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 */ +static int remove_policy_file(const char *path) { + assert(path); - separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CONFIG; /* → 5 */ + if (unlink(path) < 0) { + if (errno == ENOENT) + return 0; - default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH; - default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH; + return log_error_errno(errno, "Failed to remove policy file '%s': %m", path); } - el = event_log_new(); - if (!el) - return log_oom(); + log_info("Removed policy file '%s'.", path); + return 1; +} - r = event_log_add_algorithms_from_environment(el); - if (r < 0) - return r; +static int determine_boot_policy_file(char **ret_path, char **ret_credential_name) { + int r; - r = event_log_load(el); + _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! */ + } - r = event_log_read_pcrs(el); + sd_id128_t machine_id; + r = sd_id128_get_machine(&machine_id); if (r < 0) - return r; + return log_error_errno(r, "Failed to read machine ID: %m"); - r = event_log_calculate_pcrs(el); + 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; - r = event_log_validate_record_hashes(el); - if (r < 0) - return r; + _cleanup_free_ char *fn = strjoin("pcrlock.", arg_entry_token, ".cred"); + if (!fn) + return log_oom(); - /* 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. */ + 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 = event_log_pcr_mask_checks_out(el, always_mask|separator_mask); - if (r < 0) - return r; + _cleanup_free_ char *joined = NULL; + if (ret_path) { + joined = path_join(path, fn); + if (!joined) + return log_oom(); + } - // FIXME: before doing this, validate ahead-of-time that EV_SEPARATOR records exist for all entries, - // and exactly once + _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) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; - EventLogRecord *rec = *rr; - uint32_t bit = UINT32_C(1) << rec->pcr; + 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_FIRMWARE(rec)) - continue; + if (ret_path) + *ret_path = TAKE_PTR(joined); - if (!FLAGS_SET(always_mask, bit) && - !(FLAGS_SET(separator_mask, bit) && !FLAGS_SET(separator_seen_mask|action_seen_mask, bit))) - continue; + if (ret_credential_name) + *ret_credential_name = TAKE_PTR(cn); - /* 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 1; /* found! */ +} - /* 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 write_boot_policy_file(const char *json_text) { + _cleanup_free_ char *boot_policy_file = NULL, *credential_name = NULL; + int r; - 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"); - } + assert(json_text); - 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"); + 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; } - r = write_pcrlock(array_early, default_pcrlock_early_path); + _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 r; + return log_error_errno(r, "Failed to encode policy as credential: %m"); - return write_pcrlock(array_late, default_pcrlock_late_path); + 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); + + log_info("Written new boot policy to '%s'.", boot_policy_file); + 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 make_policy(bool force, RecoveryPinMode recovery_pin_mode) { 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; - } + /* 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 = unlink_pcrlock(default_pcrlock_early_path); + usec_t start_usec = now(CLOCK_MONOTONIC); + + _cleanup_(event_log_freep) EventLog *el = NULL; + r = event_log_load_and_process(&el); if (r < 0) return r; - if (arg_pcrlock_path) /* if the path is specified don't delete the same thing twice */ - return 0; - - r = unlink_pcrlock(default_pcrlock_late_path); + _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; - return 0; -} + 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."); -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; + usec_t predict_start_usec = now(CLOCK_MONOTONIC); - r = pcrextend_machine_id_word(&word); + r = tpm2_pcr_prediction_run(el, &new_prediction); if (r < 0) return r; - r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record); - 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 = sd_json_variant_new_array(&array, &record, 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); if (r < 0) - return log_error_errno(r, "Failed to create record array: %m"); + return r; - return write_pcrlock(array, PCRLOCK_MACHINE_ID_PATH); -} + if (DEBUG_LOGGING) + (void) sd_json_variant_dump(new_prediction_json, SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO, stderr, NULL); -static int verb_unlock_machine_id(int argc, char *argv[], void *userdata) { - return unlink_pcrlock(PCRLOCK_MACHINE_ID_PATH); -} + /* 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."); -static int pcrlock_file_system_path(const char *normalized_path, char **ret) { - _cleanup_free_ char *s = NULL; + arg_policy_path = strdup(arg_pcrlock_path); + if (!arg_policy_path) + return log_oom(); + } - assert(normalized_path); + _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy old_policy = {}; + r = tpm2_pcrlock_policy_load(arg_policy_path, &old_policy); + if (r < 0) + return 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; + bool have_old_policy = r > 0; - assert(normalized_path[0] == '/'); - assert(normalized_path[1] != '/'); + /* 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); - escaped = unit_name_escape(normalized_path + 1); - if (!escaped) - return log_oom(); + 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); - s = strjoin(PCRLOCK_FILE_SYSTEM_PATH_PREFIX, escaped, ".pcrlock"); + 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 */ + } } - if (!s) - return log_oom(); - *ret = TAKE_PTR(s); - return 0; -} + _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; + r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); + if (r < 0) + return r; -static int verb_lock_file_system(int argc, char *argv[], void *userdata) { - const char* paths[3] = {}; - int r; + 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."); - if (argc > 1) - paths[0] = argv[1]; - else { - dev_t a, b; - paths[0] = "/"; + _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL; - r = get_block_device("/", &a); + 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 log_error_errno(r, "Failed to get device of root file system: %m"); + return log_error_errno(r, "Failed to install SRK: %m"); + } - r = get_block_device("/var", &b); - if (r < 0) - return log_error_errno(r, "Failed to get device of /var/ file system: %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 log_error_errno(r, "Failed to allocate encryption session: %m"); - /* if backing device is distinct, then measure /var/ too */ - if (a != b) - paths[1] = "/var"; + /* 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; - enable_json_sse(); - } + AskPasswordRequest req = { + .tty_fd = -EBADF, + .message = "Recovery PIN", + .id = "pcrlock-recovery-pin", + .credential = "pcrlock.recovery-pin", + .until = USEC_INFINITY, + .hup_fd = -EBADF, + }; - 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; + r = ask_password_auto( + &req, + /* flags= */ 0, + &l); + if (r < 0) + return log_error_errno(r, "Failed to query for recovery PIN: %m"); - r = pcrextend_file_system_word(*p, &word, &normalized_path); - if (r < 0) - return r; + if (strv_length(l) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single PIN only."); - r = pcrlock_file_system_path(normalized_path, &pcrlock_file); - if (r < 0) - return r; + pin = TAKE_PTR(l[0]); + l = mfree(l); + } - r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record); + } else if (!have_old_policy) { + r = make_recovery_key(&pin); if (r < 0) - return r; + return log_error_errno(r, "Failed to generate a randomized recovery PIN: %m"); - 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_file); - if (r < 0) - return r; - } - - return 0; -} - -static int verb_unlock_file_system(int argc, char *argv[], void *userdata) { - const char* paths[3] = {}; - int r; - - if (argc > 1) - paths[0] = argv[1]; - else { - paths[0] = "/"; - paths[1] = "/var"; + 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()); } - STRV_FOREACH(p, paths) { - _cleanup_free_ char *normalized_path = NULL, *pcrlock_file = NULL; + _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; + TPM2_HANDLE nv_index = 0; - 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 = 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; - r = pcrlock_file_system_path(normalized_path, &pcrlock_file); - if (r < 0) - return r; + TPM2B_AUTH auth = {}; + CLEANUP_ERASE(auth); - r = unlink_pcrlock(pcrlock_file); + if (pin) { + r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth); if (r < 0) - return r; - } - - 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; - - // 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 - - 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]); - } - - 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; + return log_error_errno(r, "Failed to hash PIN: %m"); + } else { + assert(iovec_is_set(&pin_public)); + assert(iovec_is_set(&pin_private)); - if (!BIT_SET(arg_pcr_mask, i)) - continue; + log_debug("Retrieving PIN from sealed data."); - 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; + usec_t pin_start_usec = now(CLOCK_MONOTONIC); - assert_se(a = tpm2_hash_alg_to_string(*pa)); - assert_se(md = EVP_get_digestbyname(a)); + _cleanup_(iovec_done_erase) struct iovec secret = {}; + for (unsigned attempt = 0;; attempt++) { + _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; - r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &hash, &hash_size); + r = tpm2_make_policy_session( + tc, + srk_handle, + encryption_session, + &policy_session); if (r < 0) - return log_error_errno(r, "Failed to hash PE binary: %m"); + return log_error_errno(r, "Failed to allocate policy session: %m"); - 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))); + r = tpm2_policy_super_pcr( + tc, + policy_session, + &old_policy.prediction, + old_policy.algorithm); if (r < 0) - return log_error_errno(r, "Failed to build JSON digest object: %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"); - } + 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"); + return log_error_errno(r, "Failed to allocate policy session: %m"); - for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *section_digests = NULL, *record = NULL; + 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"); - if (!unified_section_measure(section)) - continue; + log_debug("Calculating new PCR policy to write..."); + TPM2B_DIGEST new_super_pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); - for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { - const char *a; - void *hash; + usec_t pcr_policy_start_usec = now(CLOCK_MONOTONIC); - hash = section_hashes[i * _UNIFIED_SECTION_MAX + section]; - if (!hash) - continue; + 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"); - assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); + log_info("Calculated new PCR policy in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pcr_policy_start_usec), 1)); - 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_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"); - if (!section_digests) - continue; + log_info("Updated NV index in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), nv_index_start_usec), 1)); - /* 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); + 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); + assert(rec); - FOREACH_ARRAY(rr, variant->records, variant->n_records) { - EventLogRecord *rec = *rr; + /* Recognizes the special EV_EFI_ACTION that is issues when the firmware passes control to the boot loader. */ - if (!EVENT_LOG_RECORD_IS_PCR(rec)) - continue; + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; - if (rec->pcr != pcr) - continue; + if (rec->pcr != TPM2_PCR_BOOT_LOADER_CODE) + return false; - 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 (rec->firmware_event_type != EV_EFI_ACTION) + return false; - if (result->hash[i].size <= 0) /* already invalidated */ - continue; + if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) /* Insist the record is consistent */ + 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; - } + r = make_cstring(rec->firmware_payload, rec->firmware_payload_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, &d); + if (r < 0) + return r; - md_ctx = EVP_MD_CTX_new(); - if (!md_ctx) - return log_oom(); + return streq(d, "Calling EFI Application from Boot Option"); +} - const EVP_MD *md = ASSERT_PTR(evp_from_tpm2_alg(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 */ - int sz = EVP_MD_size(md); - assert(sz > 0); - assert((size_t) sz <= sizeof_field(TPM2B_DIGEST, buffer)); + if (!arg_pcrlock_path && arg_pcrlock_auto) + return; - assert(sz == tpm2_hash_alg_to_size(tpm2_hash_algorithms[i])); + if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_SSE)) + return; - assert(result->hash[i].size == (size_t) sz); - assert(b->hash.size == (size_t) sz); + log_notice("Enabling JSON_SEQ mode, since writing two .pcrlock files to single output."); + arg_json_format_flags |= SD_JSON_FORMAT_SSE; +} - if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize message digest."); +static bool event_log_record_is_separator(const EventLogRecord *rec) { + assert(rec); - if (EVP_DigestUpdate(md_ctx, result->hash[i].buffer, sz) != 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash bank value."); + /* Recognizes EV_SEPARATOR events */ - if (EVP_DigestUpdate(md_ctx, b->hash.buffer, sz) != 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data value."); + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; - 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."); + if (rec->firmware_event_type != EV_SEPARATOR) + return false; - assert(l == (unsigned) sz); - } + return rec->event_payload_valid == EVENT_PAYLOAD_VALID_YES; /* Insist the record is consistent */ +} - /* 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; - } - } +VERB_GROUP("Protections"); - return 0; -} +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; -static int event_log_predict_pcrs( - EventLog *el, - Tpm2PCRPrediction *context, - Tpm2PCRPredictionResult *parent_result, - size_t component_index, - uint32_t pcr, - const char *path) { + enable_json_sse(); - EventLogComponent *component; - int count = 0, 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 */ - assert(el); - assert(context); - assert(parent_result); + separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; /* → 4 */ - /* 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; + 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 */ - return 1; - } + separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CONFIG; /* → 5 */ - component = ASSERT_PTR(el->components[component_index]); + default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH; + default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH; + } - FOREACH_ARRAY(ii, component->variants, component->n_variants) { - _cleanup_free_ Tpm2PCRPredictionResult *result = NULL; - EventLogComponentVariant *variant = *ii; - _cleanup_free_ char *subpath = NULL; + el = event_log_new(); + if (!el) + return log_oom(); - /* Operate on a copy of the result */ + r = event_log_add_algorithms_from_environment(el); + if (r < 0) + return r; - if (path) - subpath = strjoin(path, ":", component->id); - else - subpath = strdup(component->id); - if (!subpath) - return log_oom(); + r = event_log_load(el); + if (r < 0) + return r; - if (!streq(component->id, variant->id)) - if (!strextend(&subpath, "@", variant->id)) - return log_oom(); + r = event_log_read_pcrs(el); + if (r < 0) + return r; - result = newdup(Tpm2PCRPredictionResult, parent_result, 1); - if (!result) - return log_oom(); + r = event_log_calculate_pcrs(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_validate_record_hashes(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; + /* 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. */ - count += r; - } + r = event_log_pcr_mask_checks_out(el, always_mask|separator_mask); + if (r < 0) + return r; - return count; -} + // FIXME: before doing this, validate ahead-of-time that EV_SEPARATOR records exist for all entries, + // and exactly once -static ssize_t event_log_calculate_component_combinations(EventLog *el) { - ssize_t count = 1; - assert(el); + 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; - FOREACH_ARRAY(cc, el->components, el->n_components) { - EventLogComponent *c = *cc; + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + continue; - assert(c->n_variants > 0); + if (!FLAGS_SET(always_mask, bit) && + !(FLAGS_SET(separator_mask, bit) && !FLAGS_SET(separator_seen_mask|action_seen_mask, bit))) + continue; - /* 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; - - for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL; - - r = tpm2_pcr_prediction_to_json( - context, - tpm2_hash_algorithms[i], - &aj); - if (r < 0) - return r; - - 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; + + return streq(found_name, name); } -static int remove_policy_file(const char *path) { - assert(path); +static bool event_log_record_is_secureboot_authority(EventLogRecord *rec) { + assert(rec); - if (unlink(path) < 0) { - if (errno == ENOENT) - return 0; + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; - return log_error_errno(errno, "Failed to remove policy file '%s': %m", path); - } + if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY) + return false; - log_info("Removed policy file '%s'.", path); - return 1; + if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) + return false; + + return rec->firmware_event_type == EV_EFI_VARIABLE_AUTHORITY; } -static int determine_boot_policy_file(char **ret_path, char **ret_credential_name) { - int r; - - _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! */ - } - - 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 = 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); + /* Before we base anything on the event log records, let's check that the event log state checks + * out. */ - /* 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."); + r = event_log_pcr_mask_checks_out(el, UINT32_C(1) << TPM2_PCR_SECURE_BOOT_POLICY); + if (r < 0) + return r; - arg_policy_path = strdup(arg_pcrlock_path); - if (!arg_policy_path) - return log_oom(); - } + r = event_log_validate_record_hashes(el); + 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_ensure_secureboot_consistency(el); if (r < 0) return r; - bool have_old_policy = r > 0; + FOREACH_ARRAY(rr, el->records, el->n_records) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; + EventLogRecord *rec = *rr; - /* 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 (!event_log_record_is_secureboot_authority(rec)) + continue; - 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); + log_debug("Locking down authority '%s'.", strna(rec->description)); - 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 */ + 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"); } - } - _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL; - r = tpm2_context_new_or_warn(/* device= */ NULL, &tc); - if (r < 0) - return r; + 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"); + } - 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."); + return write_pcrlock(array, PCRLOCK_SECUREBOOT_AUTHORITY_PATH); +} - _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL; +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); +} - r = tpm2_deserialize( - tc, - &srk_blob, - &srk_handle); +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]; + +static void section_hashes_array_done(SectionHashArray *array) { + assert(array); + + for (size_t i = 0; i < _UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS; i++) + free((*array)[i]); +} + +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; - log_debug("Trying again (attempt %u), as PCR values changed during unlock attempt.", attempt+1); - } + if (arg_pcr_mask != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PCR not configurable for UKI lock down."); - if (secret.iov_len > sizeof_field(TPM2B_AUTH, buffer)) - return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Decrypted PIN too large."); + 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]); + } - auth = (TPM2B_AUTH) { - .size = secret.iov_len, - }; + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + _cleanup_free_ void *peh = NULL; + const EVP_MD *md; + const char *a; - memcpy_safe(auth.buffer, secret.iov_base, secret.iov_len); + assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); + assert_se(md = sym_EVP_get_digestbyname(a)); - log_info("Retrieved PIN from TPM2 in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_start_usec), 1)); + r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &peh, hash_sizes + i); + if (r < 0) + return log_error_errno(r, "Failed to hash PE binary: %m"); + + 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 build JSON digest object: %m"); + + 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 UKI hash PE binary: %m"); } - /* 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); + 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 r; + return log_error_errno(r, "Failed to append record object: %m"); - TPM2B_NV_PUBLIC nv_public = {}; - usec_t nv_index_start_usec = now(CLOCK_MONOTONIC); + for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *section_digests = NULL, *record = NULL; - if (!iovec_is_set(&nv_blob)) { - _cleanup_(Esys_Freep) TPM2B_NAME *pin_name = NULL; - r = tpm2_get_name( - tc, - pin_handle, - &pin_name); + if (!unified_section_measure(section)) + continue; + + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + const char *a; + void *hash; + + 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 log_error_errno(r, "Failed to get name of PIN from TPM2: %m"); + return r; - 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_array(&array, record); if (r < 0) - return log_error_errno(r, "Failed to calculate PolicySigned policy: %m"); + return log_error_errno(r, "Failed to append JSON record array: %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); + /* 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 allocate NV index: %m"); + return log_error_errno(r, "Failed to append record object: %m"); } - _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 allocate policy session: %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"); + return write_pcrlock(array, NULL); +} - log_debug("Calculating new PCR policy to write..."); - TPM2B_DIGEST new_super_pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); +VERB_NOARG(verb_unlock_simple, "unlock-uki", + "Remove .pcrlock file for UKI PE binary"); - usec_t pcr_policy_start_usec = now(CLOCK_MONOTONIC); +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 = tpm2_calculate_policy_super_pcr( - &new_prediction, - el->primary_algorithm, - &new_super_pcr_policy_digest); + 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(); + } - 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); + 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; + + 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; + r = make_pcrlock_record_from_stream(arg_pcr_mask, f ?: stdin, &records); + if (r < 0) + return r; + + return write_pcrlock(records, NULL); } -static int verb_is_supported(int argc, char *argv[], void *userdata) { +static int help(void) { + _cleanup_(table_unrefp) Table *commands = NULL, *protections = NULL, *options = NULL; int r; - Tpm2Support s; - r = test_tpm2_support_pcrlock(&s); + r = verbs_get_help_table(&commands); 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()); - } + r = verbs_get_help_table_group("Protections", &protections); + if (r < 0) + return r; - assert_cc((TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK) <= 255); /* make sure this is safe to use as process exit status */ + r = option_parser_get_help_table(&options); + if (r < 0) + return r; - return ~s & (TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK); -} + (void) table_sync_column_widths(0, commands, protections, options); -static int help(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *link = NULL; - int r; + help_cmdline("[OPTIONS...] COMMAND ..."); + help_abstract("Manage a TPM2 PCR lock."); - r = terminal_urlify_man("systemd-pcrlock", "8", &link); + help_section("Commands"); + r = table_print_or_warn(commands); if (r < 0) - return log_oom(); + return r; - 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", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + help_section("Protections"); + r = table_print_or_warn(protections); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("systemd-pcrlock", "8"); 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 opts = { argc, argv }; + bool auto_location = true; + int r; - while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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(opts.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", opts.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(opts.arg)) arg_nv_index = 0; else { uint32_t u; - r = safe_atou32_full(optarg, 16, &u); + r = safe_atou32_full(opts.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", opts.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 +5257,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(opts.arg, /* suppress_root= */ false, &p); if (r < 0) return r; @@ -5243,21 +5272,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(opts.arg)) { arg_location_start = mfree(arg_location_start); arg_location_end = mfree(arg_location_end); break; } - e = strchr(optarg, ':'); + e = strchr(opts.arg, ':'); if (e) { - start = strndup(optarg, e - optarg); + start = strndup(opts.arg, e - opts.arg); if (!start) return log_oom(); @@ -5265,11 +5295,11 @@ static int parse_argv(int argc, char *argv[]) { if (!end) return log_oom(); } else { - start = strdup(optarg); + start = strdup(opts.arg); if (!start) return log_oom(); - end = strdup(optarg); + end = strdup(opts.arg); if (!end) return log_oom(); } @@ -5284,17 +5314,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(opts.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", opts.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(opts.arg)) arg_pcrlock_path = mfree(arg_pcrlock_path); else { - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_pcrlock_path); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_pcrlock_path); if (r < 0) return r; } @@ -5302,36 +5334,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(opts.arg)) arg_policy_path = mfree(arg_policy_path); else { - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_policy_path); + r = parse_path_argument(opts.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(opts.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 +5385,10 @@ static int parse_argv(int argc, char *argv[]) { arg_pager_flags |= PAGER_DISABLE; } + *ret_args = option_parser_get_args(&opts); 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; @@ -5418,8 +5409,7 @@ static int vl_method_read_event_log(sd_varlink *link, sd_json_variant *parameter if (r < 0) 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; @@ -5480,6 +5470,27 @@ static int vl_method_remove_policy(sd_varlink *link, sd_json_variant *parameters return sd_varlink_reply(link, NULL); } +static int vl_method_on_completed_update(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + int r; + + assert(link); + + /* Triggered by systemd-sysupdate after an update completed. We deliberately ignore all parameters + * (we don't even dispatch them) and simply recompute the PCR policy unconditionally, since the set + * of measured components might have changed. */ + + /* Only honour update notifications if they come from root */ + r = varlink_check_privileged_peer(link); + if (r < 0) + return r; + + r = make_policy(/* force= */ false, /* recovery_pin_mode= */ RECOVERY_PIN_HIDE); + if (r < 0) + return r; + + return sd_varlink_reply(link, NULL); +} + static int run(int argc, char *argv[]) { int r; @@ -5489,28 +5500,39 @@ 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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + if (arg_varlink) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; /* Invocation as Varlink service */ - r = varlink_server_new(&varlink_server, SD_VARLINK_SERVER_ROOT_ONLY, NULL); + r = varlink_server_new(&varlink_server, + SD_VARLINK_SERVER_ROOT_ONLY | SD_VARLINK_SERVER_MYSELF_ONLY, + /* userdata= */ NULL); 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_PCRLock); + r = sd_varlink_server_add_interface_many( + varlink_server, + &vl_interface_io_systemd_PCRLock, + &vl_interface_io_systemd_SysUpdate_Notify); if (r < 0) - return log_error_errno(r, "Failed to add Varlink interface: %m"); + return log_error_errno(r, "Failed to add Varlink interfaces: %m"); r = sd_varlink_server_bind_method_many( varlink_server, - "io.systemd.PCRLock.ReadEventLog", vl_method_read_event_log, - "io.systemd.PCRLock.MakePolicy", vl_method_make_policy, - "io.systemd.PCRLock.RemovePolicy", vl_method_remove_policy); + "io.systemd.PCRLock.ReadEventLog", vl_method_read_event_log, + "io.systemd.PCRLock.MakePolicy", vl_method_make_policy, + "io.systemd.PCRLock.RemovePolicy", vl_method_remove_policy, + "io.systemd.SysUpdate.Notify.OnCompletedUpdate", vl_method_on_completed_update); if (r < 0) return log_error_errno(r, "Failed to bind Varlink methods: %m"); @@ -5521,7 +5543,7 @@ static int run(int argc, char *argv[]) { return EXIT_SUCCESS; } - return pcrlock_main(argc, argv); + return dispatch_verb(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/meson.build b/src/portable/meson.build index 3029ad4177784..0bd5d1c3ce262 100644 --- a/src/portable/meson.build +++ b/src/portable/meson.build @@ -32,7 +32,6 @@ executables += [ libcryptsetup_cflags, libmount_cflags, libselinux_cflags, - threads, ], }, executable_template + { @@ -40,7 +39,6 @@ executables += [ 'public' : true, 'sources' : files('portablectl.c'), 'link_with' : portabled_link_with, - 'dependencies' : threads, }, ] diff --git a/src/portable/portable.c b/src/portable/portable.c index c86a6c27228e3..9d510118be284 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + (void) DLOPEN_LIBMOUNT(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); r = mkdtemp_malloc("/tmp/inspect-XXXXXX", &tmpdir); if (r < 0) @@ -816,8 +821,9 @@ static int extract_image_and_extensions( /* If we get a path, then check if it can be resolved with vpick. We need this as we might just * get a simple image name, which would make vpick error out. */ if (path_is_absolute(name_or_path)) { - r = path_pick(/* toplevel_path= */ NULL, - /* toplevel_fd= */ AT_FDCWD, + r = path_pick(/* root_path= */ NULL, + /* root_fd= */ AT_FDCWD, + /* dir_fd= */ AT_FDCWD, name_or_path, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), @@ -855,8 +861,9 @@ static int extract_image_and_extensions( const char *path = *p; if (path_is_absolute(*p)) { - r = path_pick(/* toplevel_path= */ NULL, - /* toplevel_fd= */ AT_FDCWD, + r = path_pick(/* root_path= */ NULL, + /* root_fd= */ AT_FDCWD, + /* dir_fd= */ AT_FDCWD, *p, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), @@ -1266,19 +1273,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 +1384,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 +1503,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); @@ -2192,8 +2203,9 @@ static bool marker_matches_images(const char *marker, const char *name_or_path, if (r < 0) return log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", image); - r = path_pick(/* toplevel_path= */ NULL, - /* toplevel_fd= */ AT_FDCWD, + r = path_pick(/* root_path= */ NULL, + /* root_fd= */ AT_FDCWD, + /* dir_fd= */ AT_FDCWD, *image_name_or_path, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), @@ -2422,7 +2434,7 @@ int portable_detach( portable_changes_add_with_prefix(changes, n_changes, PORTABLE_UNLINK, where, md, NULL); } - /* Now, also drop any image symlink or copy, for images outside of the sarch path */ + /* Now, also drop any image symlink or copy, for images outside of the search path */ SET_FOREACH(item, markers) { _cleanup_free_ char *target = NULL; 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..831b454372275 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -1,10 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "bus-error.h" #include "bus-locator.h" @@ -13,12 +12,13 @@ #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 "glyph-util.h" +#include "help-util.h" #include "install.h" #include "main-func.h" +#include "options.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" @@ -26,7 +26,6 @@ #include "path-util.h" #include "polkit-agent.h" #include "portable.h" -#include "pretty-print.h" #include "string-util.h" #include "strv.h" #include "verbs.h" @@ -59,6 +58,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 +133,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; @@ -153,9 +156,10 @@ static int extract_prefix(const char *path, char **ret) { if (!name) return -ENOMEM; - /* 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 "-.")) + /* 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, ALPHANUMERICAL "-.")) return -EINVAL; if (!filename_is_valid(name)) @@ -169,9 +173,9 @@ static int determine_matches(const char *image, char **l, bool allow_any, char * _cleanup_strv_free_ char **k = NULL; int r; - /* Determine the matches to apply. If the list is empty we derive the match from the image name. If the list - * contains exactly the "-" we return a wildcard list (which is the empty list), but only if this is expressly - * permitted. */ + /* Determine the matches to apply. If the list is empty we derive the match from the image name. If + * the list contains exactly the "-" we return a wildcard list (which is the empty list), but only if + * this is expressly permitted. */ if (strv_isempty(l)) { char *prefix; @@ -237,6 +241,8 @@ static int acquire_bus(sd_bus **bus) { static int maybe_reload(sd_bus **bus) { int r; + assert(bus); + if (!arg_reload) return 0; @@ -247,6 +253,82 @@ static int maybe_reload(sd_bus **bus) { return bus_service_manager_reload(*bus); } +VERB_DEFAULT_NOARG(verb_list_images, "list", + "List available portable service images (default)"); +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; + _cleanup_(table_unrefp) Table *table = NULL; + int r; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + r = bus_call_method(bus, bus_portable_mgr, "ListImages", &error, &reply, NULL); + if (r < 0) + return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r)); + + table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state"); + if (!table) + return log_oom(); + + r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + const char *name, *type, *state; + uint64_t crtime, mtime, usage; + int ro_int; + + r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + r = table_add_many(table, + TABLE_STRING, name, + TABLE_STRING, type, + TABLE_BOOLEAN, ro_int, + TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL, + TABLE_TIMESTAMP, crtime, + TABLE_TIMESTAMP, mtime, + TABLE_SIZE, usage, + TABLE_STRING, state, + TABLE_SET_COLOR, !streq(state, "detached") ? ansi_highlight_green() : NULL); + if (r < 0) + return table_log_add_error(r); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (!table_isempty(table)) { + r = table_set_sort(table, (size_t) 0); + if (r < 0) + return table_log_sort_error(r); + + table_set_header(table, arg_legend); + + r = table_print_or_warn(table); + if (r < 0) + return r; + } + + if (arg_legend) { + if (table_isempty(table)) + printf("No images.\n"); + else + printf("\n%zu images listed.\n", table_get_rows(table) - 1); + } + + return 0; +} + static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd_bus_message **reply) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -288,7 +370,9 @@ 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) { +VERB(verb_inspect_image, "inspect", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0, + "Show details of specified portable service image"); +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 +416,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 +477,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); @@ -600,22 +678,18 @@ static int maybe_enable_disable(sd_bus *bus, const char *path, bool enable) { return 0; } -static int maybe_start_stop_restart(sd_bus *bus, const char *path, const char *method, BusWaitForJobs *wait) { +static int maybe_start_stop_restart(sd_bus *bus, const char *name, const char *method, BusWaitForJobs *wait) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *name = NULL; const char *job = NULL; int r; + assert(name); assert(STR_IN_SET(method, "StartUnit", "StopUnit", "RestartUnit")); if (!arg_now) return 0; - r = path_extract_filename(path, &name); - if (r < 0) - return log_error_errno(r, "Failed to extract file name from '%s': %m", path); - r = bus_call_method( bus, bus_systemd_mgr, @@ -626,7 +700,7 @@ static int maybe_start_stop_restart(sd_bus *bus, const char *path, const char *m if (r < 0) return log_error_errno(r, "Failed to call %s on the portable service %s: %s", method, - path, + name, bus_error_message(&error, r)); r = sd_bus_message_read(reply, "o", &job); @@ -646,8 +720,92 @@ static int maybe_start_stop_restart(sd_bus *bus, const char *path, const char *m return 0; } +static int maybe_start_stop_restart_units( + sd_bus *bus, + char * const *names, + const char *job_type, + BusWaitForJobs *wait) { + + _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; + int r; + + assert(bus); + assert(STR_IN_SET(job_type, "start", "stop", "restart")); + + if (!arg_now || strv_isempty(names)) + return 0; + + method = streq(job_type, "start") ? "StartUnit" : + streq(job_type, "stop") ? "StopUnit" : + "RestartUnit"; + + /* Prefer the new EnqueueUnitJobMany() method which submits all units in a single transaction. + * Falls back to per-unit calls on older managers (UnknownMethod) or when the new method rejects + * something about the request (InvalidArgs). */ + r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "EnqueueUnitJobMany"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, (char**) names); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "sst", job_type, "replace", UINT64_C(0)); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r >= 0) { + r = sd_bus_message_enter_container(reply, 'a', "(uosos)"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + const char *path, *unit_id; + uint32_t id; + + r = sd_bus_message_read(reply, "(uosos)", &id, &path, &unit_id, NULL, NULL); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + if (!arg_quiet) + log_info("Queued %s to call %s on portable service %s.", path, method, unit_id); + + if (wait) { + r = bus_wait_for_jobs_add(wait, path); + if (r < 0) + return log_error_errno(r, "Failed to watch %s job to call %s on %s: %m", + path, method, unit_id); + } + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + } + + if (!sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_METHOD, SD_BUS_ERROR_INVALID_ARGS)) + return log_error_errno(r, "Failed to enqueue jobs for portable services: %s", + bus_error_message(&error, r)); + + log_debug_errno(r, "EnqueueUnitJobMany() not supported (%s), falling back to per-unit calls.", + bus_error_message(&error, r)); + + STRV_FOREACH(name, names) + (void) maybe_start_stop_restart(bus, *name, method, wait); + + return 0; +} + static int maybe_enable_start(sd_bus *bus, sd_bus_message *reply) { _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL; + _cleanup_strv_free_ char **start_names = NULL; int r; if (!arg_enable && !arg_now) @@ -677,7 +835,15 @@ static int maybe_enable_start(sd_bus *bus, sd_bus_message *reply) { if (STR_IN_SET(type, "symlink", "copy") && is_portable_managed(path)) { (void) maybe_enable_disable(bus, path, true); - (void) maybe_start_stop_restart(bus, path, "StartUnit", wait); + + _cleanup_free_ char *name = NULL; + r = path_extract_filename(path, &name); + if (r < 0) + return log_error_errno(r, "Failed to extract file name from '%s': %m", path); + + r = strv_consume(&start_names, TAKE_PTR(name)); + if (r < 0) + return log_oom(); } } @@ -685,6 +851,8 @@ static int maybe_enable_start(sd_bus *bus, sd_bus_message *reply) { if (r < 0) return r; + (void) maybe_start_stop_restart_units(bus, start_names, "start", wait); + if (!arg_no_block) { r = bus_wait_for_jobs(wait, arg_quiet, NULL); if (r < 0) @@ -696,6 +864,7 @@ static int maybe_enable_start(sd_bus *bus, sd_bus_message *reply) { static int maybe_stop_enable_restart(sd_bus *bus, sd_bus_message *reply) { _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL; + _cleanup_strv_free_ char **stop_names = NULL, **restart_names = NULL; int r; if (!arg_enable && !arg_now) @@ -726,14 +895,25 @@ static int maybe_stop_enable_restart(sd_bus *bus, sd_bus_message *reply) { if (r == 0) break; - if (streq(type, "unlink") && is_portable_managed(path)) - (void) maybe_start_stop_restart(bus, path, "StopUnit", wait); + if (streq(type, "unlink") && is_portable_managed(path) && arg_now) { + _cleanup_free_ char *name = NULL; + + r = path_extract_filename(path, &name); + if (r < 0) + return log_error_errno(r, "Failed to extract file name from '%s': %m", path); + + r = strv_consume(&stop_names, TAKE_PTR(name)); + if (r < 0) + return log_oom(); + } } r = sd_bus_message_exit_container(reply); if (r < 0) return r; + (void) maybe_start_stop_restart_units(bus, stop_names, "stop", wait); + /* Then we get a list of units that were either added or changed, so that we can * enable them and/or restart them if the user asked us to. */ r = sd_bus_message_enter_container(reply, 'a', "(sss)"); @@ -751,7 +931,15 @@ static int maybe_stop_enable_restart(sd_bus *bus, sd_bus_message *reply) { if (STR_IN_SET(type, "symlink", "copy") && is_portable_managed(path)) { (void) maybe_enable_disable(bus, path, true); - (void) maybe_start_stop_restart(bus, path, "RestartUnit", wait); + + _cleanup_free_ char *name = NULL; + r = path_extract_filename(path, &name); + if (r < 0) + return log_error_errno(r, "Failed to extract file name from '%s': %m", path); + + r = strv_consume(&restart_names, TAKE_PTR(name)); + if (r < 0) + return log_oom(); } } @@ -759,6 +947,8 @@ static int maybe_stop_enable_restart(sd_bus *bus, sd_bus_message *reply) { if (r < 0) return r; + (void) maybe_start_stop_restart_units(bus, restart_names, "restart", wait); + if (!arg_no_block) { r = bus_wait_for_jobs(wait, arg_quiet, NULL); if (r < 0) @@ -862,9 +1052,6 @@ static int maybe_stop_disable_clean(sd_bus *bus, char *image, char *argv[]) { if (r < 0) return bus_log_parse_error(r); - (void) maybe_start_stop_restart(bus, name, "StopUnit", wait); - (void) maybe_enable_disable(bus, name, false); - r = strv_extend(&units, name); if (r < 0) return log_oom(); @@ -874,6 +1061,12 @@ static int maybe_stop_disable_clean(sd_bus *bus, char *image, char *argv[]) { if (r < 0) return bus_log_parse_error(r); + (void) maybe_start_stop_restart_units(bus, units, "stop", wait); + + /* Disable after stopping to match the idiomatic stop-then-disable lifecycle order. */ + STRV_FOREACH(name, units) + (void) maybe_enable_disable(bus, *name, false); + /* Stopping must always block or the detach will fail if the unit is still running */ r = bus_wait_for_jobs(wait, arg_quiet, NULL); if (r < 0) @@ -958,15 +1151,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) { +VERB(verb_attach_image, "attach", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0, + "Attach the specified portable service image"); +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) { - 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) { +VERB(verb_detach_image, "detach", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0, + "Detach the specified portable service image"); +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,81 +1213,93 @@ static int detach_image(int argc, char *argv[], void *userdata) { return 0; } -static int list_images(int argc, char *argv[], void *userdata) { +VERB(verb_reattach_image, "reattach", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0, + "Reattach the specified portable service image"); +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"); +} + +VERB(verb_is_image_attached, "is-attached", "NAME|PATH", 2, 2, 0, + "Query if portable service image is attached"); +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_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(table_unrefp) Table *table = NULL; + _cleanup_free_ char *image = NULL; + const char *state, *method; int r; - r = acquire_bus(&bus); + r = determine_image(argv[1], true, &image); if (r < 0) return r; - r = bus_call_method(bus, bus_portable_mgr, "ListImages", &error, &reply, NULL); + r = acquire_bus(&bus); if (r < 0) - return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r)); + return r; - table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state"); - if (!table) - return log_oom(); + method = strv_isempty(arg_extension_images) ? "GetImageState" : "GetImageStateWithExtensions"; - r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)"); + r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method); if (r < 0) - return bus_log_parse_error(r); + return bus_log_create_error(r); - for (;;) { - const char *name, *type, *state; - uint64_t crtime, mtime, usage; - int ro_int; + r = sd_bus_message_append(m, "s", image); + if (r < 0) + return bus_log_create_error(r); - r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL); - if (r < 0) - return bus_log_parse_error(r); - if (r == 0) - break; + r = attach_extensions_to_message(m, method, arg_extension_images); + if (r < 0) + return r; - r = table_add_many(table, - TABLE_STRING, name, - TABLE_STRING, type, - TABLE_BOOLEAN, ro_int, - TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL, - TABLE_TIMESTAMP, crtime, - TABLE_TIMESTAMP, mtime, - TABLE_SIZE, usage, - TABLE_STRING, state, - TABLE_SET_COLOR, !streq(state, "detached") ? ansi_highlight_green() : NULL); + if (!strv_isempty(arg_extension_images)) { + r = sd_bus_message_append(m, "t", UINT64_C(0)); if (r < 0) - return table_log_add_error(r); + return bus_log_create_error(r); } - r = sd_bus_message_exit_container(reply); + r = sd_bus_call(bus, m, 0, &error, &reply); if (r < 0) - return bus_log_parse_error(r); + return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r)); - if (!table_isempty(table)) { - r = table_set_sort(table, (size_t) 0); - if (r < 0) - return table_log_sort_error(r); + r = sd_bus_message_read(reply, "s", &state); + if (r < 0) + return r; - table_set_header(table, arg_legend); + if (!arg_quiet) + puts(state); - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); - } + return streq(state, "detached"); +} - if (arg_legend) { - if (table_isempty(table)) - printf("No images.\n"); - else - printf("\n%zu images listed.\n", table_get_rows(table) - 1); +VERB(verb_read_only_image, "read-only", "NAME|PATH [BOOL]", 2, 3, 0, + "Mark or unmark portable service image read-only"); +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; + + if (argc > 2) { + b = parse_boolean(argv[2]); + if (b < 0) + return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]); } + r = acquire_bus(&bus); + if (r < 0) + return r; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + r = bus_call_method(bus, bus_portable_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b); + if (r < 0) + return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r)); + return 0; } -static int remove_image(int argc, char *argv[], void *userdata) { +VERB(verb_remove_image, "remove", "NAME|PATH…", 2, VERB_ANY, 0, + "Remove a portable service image"); +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,31 +1330,9 @@ static int remove_image(int argc, char *argv[], void *userdata) { return 0; } -static int read_only_image(int argc, char *argv[], 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; - - if (argc > 2) { - b = parse_boolean(argv[2]); - if (b < 0) - return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]); - } - - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - r = bus_call_method(bus, bus_portable_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b); - if (r < 0) - return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r)); - - return 0; -} - -static int set_limit(int argc, char *argv[], void *userdata) { +VERB(verb_set_limit, "set-limit", "[NAME|PATH] LIMIT", 2, 3, 0, + "Set image or pool size limit (disk quota)"); +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,56 +1365,6 @@ static int set_limit(int argc, char *argv[], void *userdata) { return 0; } -static int is_image_attached(int argc, char *argv[], 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; - _cleanup_free_ char *image = NULL; - const char *state, *method; - int r; - - r = determine_image(argv[1], true, &image); - if (r < 0) - return r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - method = strv_isempty(arg_extension_images) ? "GetImageState" : "GetImageStateWithExtensions"; - - r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", image); - if (r < 0) - return bus_log_create_error(r); - - r = attach_extensions_to_message(m, method, arg_extension_images); - if (r < 0) - return r; - - if (!strv_isempty(arg_extension_images)) { - r = sd_bus_message_append(m, "t", UINT64_C(0)); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "s", &state); - if (r < 0) - return r; - - if (!arg_quiet) - puts(state); - - return streq(state, "detached"); -} - static int dump_profiles(void) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1257,173 +1390,105 @@ static int dump_profiles(void) { return 0; } -static int help(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *link = NULL; +static int help(void) { + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; pager_open(arg_pager_flags); - r = terminal_urlify_man("portablectl", "1", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; - printf("%s [OPTIONS...] COMMAND ...\n\n" - "%sAttach or detach portable services from the local system.%s\n" - "\nCommands:\n" - " list List available portable service images\n" - " attach NAME|PATH [PREFIX...]\n" - " Attach the specified portable service image\n" - " detach NAME|PATH [PREFIX...]\n" - " Detach the specified portable service image\n" - " reattach NAME|PATH [PREFIX...]\n" - " Reattach the specified portable service image\n" - " inspect NAME|PATH [PREFIX...]\n" - " Show details of specified portable service image\n" - " is-attached NAME|PATH Query if portable service image is attached\n" - " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n" - " remove NAME|PATH... Remove a portable service image\n" - " set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n" - "\nOptions:\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" - " -q --quiet Suppress informational messages\n" - " -p --profile=PROFILE Pick security profile for portable service\n" - " --copy=copy|auto|symlink|mixed\n" - " Pick copying or symlinking of resources\n" - " --runtime Attach portable service until next reboot only\n" - " --no-reload Don't reload the system and service manager\n" - " --cat When inspecting include unit and os-release file\n" - " contents\n" - " --enable Immediately enable/disable the portable service\n" - " after attach/detach\n" - " --now Immediately start/stop the portable service after\n" - " attach/before detach\n" - " --no-block Don't block waiting for attach --now to complete\n" - " --extension=PATH Extend the image with an overlay\n" - " --force Skip 'already active' check when attaching or\n" - " detaching an image (with extensions)\n" - " --clean When detaching, also remove configuration, state,\n" - " cache, logs or runtime data of the portable\n" - " service(s)\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Attach or detach portable services in the local system."); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("portablectl", "1"); 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_COPY, - ARG_RUNTIME, - ARG_NO_RELOAD, - ARG_CAT, - ARG_ENABLE, - ARG_NOW, - ARG_NO_BLOCK, - ARG_EXTENSION, - ARG_FORCE, - ARG_CLEAN, - 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 }, - { "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' }, - { "quiet", no_argument, NULL, 'q' }, - { "profile", required_argument, NULL, 'p' }, - { "copy", required_argument, NULL, ARG_COPY }, - { "runtime", no_argument, NULL, ARG_RUNTIME }, - { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, - { "cat", no_argument, NULL, ARG_CAT }, - { "enable", no_argument, NULL, ARG_ENABLE }, - { "now", no_argument, NULL, ARG_NOW }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "extension", required_argument, NULL, ARG_EXTENSION }, - { "force", no_argument, NULL, ARG_FORCE }, - { "clean", no_argument, NULL, ARG_CLEAN }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - {} - }; - - int r, c; +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, "hH:M:qp:", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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 = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress informational messages"): arg_quiet = true; break; - case 'p': - if (streq(optarg, "help")) + OPTION('p', "profile", "PROFILE", + "Pick security profile for portable service"): + if (streq(opts.arg, "help")) return dump_profiles(); - if (!filename_is_valid(optarg)) + if (!filename_is_valid(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unit profile name not valid: %s", optarg); + "Unit profile name not valid: %s", opts.arg); - arg_profile = optarg; + arg_profile = opts.arg; break; - case ARG_COPY: - if (streq(optarg, "auto")) + OPTION_LONG("copy", "MODE", + "Pick copying or symlinking of resources " + "(copy, auto, symlink, mixed)"): + if (streq(opts.arg, "auto")) arg_copy_mode = NULL; - else if (STR_IN_SET(optarg, "copy", "symlink", "mixed")) - arg_copy_mode = optarg; - else if (streq(optarg, "help")) { + else if (STR_IN_SET(opts.arg, "copy", "symlink", "mixed")) + arg_copy_mode = opts.arg; + else if (streq(opts.arg, "help")) { puts("auto\n" "copy\n" "symlink\n" @@ -1431,90 +1496,81 @@ static int parse_argv(int argc, char *argv[]) { return 0; } else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse --copy= argument: %s", optarg); - + "Failed to parse --copy= argument: %s", opts.arg); break; - case ARG_RUNTIME: + OPTION_LONG("runtime", NULL, + "Attach portable service until next reboot only"): arg_runtime = true; break; - case ARG_NO_RELOAD: + OPTION_LONG("no-reload", NULL, + "Don't reload the system and service manager"): arg_reload = false; break; - case ARG_CAT: + OPTION_LONG("cat", NULL, + "When inspecting include unit and os-release file contents"): arg_cat = true; break; - case ARG_ENABLE: + OPTION_LONG("enable", NULL, + "Immediately enable/disable the portable service after attach/detach"): arg_enable = true; break; - case ARG_NOW: + OPTION_LONG("now", NULL, + "Immediately start/stop the portable service after attach/before detach"): arg_now = true; break; - case ARG_NO_BLOCK: + OPTION_LONG("no-block", NULL, + "Don't block waiting for attach --now to complete"): arg_no_block = true; break; - case ARG_EXTENSION: - r = strv_extend(&arg_extension_images, optarg); + OPTION_LONG("extension", "PATH", + "Extend the image with an overlay"): + r = strv_extend(&arg_extension_images, opts.arg); if (r < 0) return log_oom(); break; - case ARG_FORCE: + OPTION_LONG("force", NULL, + "Skip 'already active' check when attaching or detaching an image (with extensions)"): arg_force = true; break; - case ARG_CLEAN: + OPTION_LONG("clean", NULL, + "When detaching, also remove configuration, state, " + "cache, logs or runtime data of the portable service(s)"): arg_clean = true; break; - case ARG_USER: + OPTION_LONG("user", NULL, /* help= */ NULL): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, /* help= */ NULL): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *remaining_args = option_parser_get_args(&opts); return 1; } 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 }, - {} - }; - + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/portable/portabled-image-bus.c b/src/portable/portabled-image-bus.c index 48f0274a54cd6..5517ea10a129e 100644 --- a/src/portable/portabled-image-bus.c +++ b/src/portable/portabled-image-bus.c @@ -591,14 +591,6 @@ static int normalize_portable_changes( CLEANUP_ARRAY(changes, n_changes, portable_changes_free); - /* Corner case: only detached, nothing attached */ - if (n_changes_attached == 0) { - memcpy(changes, changes_detached, sizeof(PortableChange) * n_changes_detached); - *ret_changes = TAKE_PTR(changes); - *ret_n_changes = n_changes_detached; - return 0; - } - for (size_t i = 0; i < n_changes_detached; ++i) { bool found = false; diff --git a/src/pstore/meson.build b/src/pstore/meson.build index d6bb925789778..a94f1b1e8841c 100644 --- a/src/pstore/meson.build +++ b/src/pstore/meson.build @@ -12,7 +12,6 @@ executables += [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ], }, ] diff --git a/src/ptyfwd/ptyfwd-tool.c b/src/ptyfwd/ptyfwd-tool.c index 519bd3641d514..e7b531c873088 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,76 @@ 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 opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + int r; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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, opts.arg); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind >= argc) + if (option_parser_get_n_args(&opts) == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected command line, refusing."); + *remaining_args = option_parser_get_args(&opts); return 1; } @@ -155,11 +137,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..289291761f029 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 { @@ -281,7 +282,7 @@ static int save_seed_file( return log_error_errno(r, "Failed to write new random seed file: %m"); if (ftruncate(seed_fd, k) < 0) - return log_error_errno(r, "Failed to truncate random seed file: %m"); + return log_error_errno(errno, "Failed to truncate random seed file: %m"); r = fsync_full(seed_fd); if (r < 0) @@ -296,75 +297,74 @@ 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 opts = { argc, argv }; + int r; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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(option_parser_get_args(&opts), 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..6d3278c3ca65f 100644 --- a/src/repart/meson.build +++ b/src/repart/meson.build @@ -8,17 +8,16 @@ executables += [ executable_template + { 'name' : 'systemd-repart', 'public' : true, - 'extract' : files('repart.c'), - 'link_with' : [ - libshared, - libshared_fdisk, - ], + 'extract' : files( + 'iso9660.c', + 'repart.c', + 'repart-list-candidate-devices.c', + ), 'dependencies' : [ libblkid_cflags, - libfdisk, + libfdisk_cflags, libmount_cflags, - libopenssl, - threads, + libopenssl_cflags, ], }, executable_template + { @@ -28,16 +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, - threads, + libopenssl_cflags, ], }, ] diff --git a/src/repart/repart-list-candidate-devices.c b/src/repart/repart-list-candidate-devices.c new file mode 100644 index 0000000000000..f4f5a8aa2c90b --- /dev/null +++ b/src/repart/repart-list-candidate-devices.c @@ -0,0 +1,245 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-device.h" +#include "sd-json.h" +#include "sd-varlink.h" + +#include "blockdev-list.h" +#include "device-util.h" +#include "json-util.h" +#include "repart-list-candidate-devices.h" + +typedef struct ListCandidateDevicesContext { + sd_varlink *link; /* Owned ref; freed in list_candidate_devices_context_free(). */ + sd_device_monitor *monitor; + BlockDevListFlags flags; + dev_t root_devno, whole_root_devno; +} ListCandidateDevicesContext; + +static ListCandidateDevicesContext* list_candidate_devices_context_free(ListCandidateDevicesContext *c) { + if (!c) + return NULL; + + sd_device_monitor_unref(c->monitor); + sd_varlink_unref(c->link); + return mfree(c); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(ListCandidateDevicesContext*, list_candidate_devices_context_free); + +static void vl_on_disconnect(sd_varlink_server *server, sd_varlink *link, void *userdata) { + assert(server); + assert(link); + + list_candidate_devices_context_free(sd_varlink_set_userdata(link, NULL)); +} + +static int list_candidate_devices_send_remove(sd_varlink *link, sd_device *dev) { + int r; + + assert(link); + assert(dev); + + const char *node; + r = sd_device_get_devname(dev, &node); + if (r < 0) { + log_device_debug_errno(dev, r, "Failed to acquire device node of removed block device, ignoring: %m"); + return 0; + } + + return sd_varlink_notifybo(link, + SD_JSON_BUILD_PAIR("action", JSON_BUILD_CONST_STRING("remove")), + SD_JSON_BUILD_PAIR_STRING("node", node)); +} + +static int list_candidate_devices_send_add(sd_varlink *link, const BlockDevice *d, bool with_action) { + int r; + + assert(link); + assert(d); + + /* In subscribe mode we tag every reply with action=add so the discriminator is meaningful; + * in plain enumeration mode we omit it (older clients don't know the field). */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR_CONDITION(with_action, "action", JSON_BUILD_CONST_STRING("add")), + SD_JSON_BUILD_PAIR_STRING("node", d->node), + JSON_BUILD_PAIR_STRV_NON_EMPTY("symlinks", d->symlinks), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("diskseq", d->diskseq, UINT64_MAX), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("sizeBytes", d->size, UINT64_MAX), + JSON_BUILD_PAIR_STRING_NON_EMPTY("model", d->model), + JSON_BUILD_PAIR_STRING_NON_EMPTY("vendor", d->vendor), + JSON_BUILD_PAIR_STRING_NON_EMPTY("subsystem", d->subsystem)); + if (r < 0) + return r; + + /* Subscribe mode sets no sentinel, so a final reply would terminate the streaming call after + * the first device; use intermediate notify messages to keep it open. Plain enumeration relies + * on the sentinel mechanism, which buffers and chains replies. */ + return with_action ? sd_varlink_notify(link, v) : sd_varlink_reply(link, v); +} + +static int list_candidate_devices_on_uevent(sd_device_monitor *monitor, sd_device *dev, void *userdata) { + ListCandidateDevicesContext *c = ASSERT_PTR(userdata); + int r; + + assert(dev); + + sd_device_action_t action; + r = sd_device_get_action(dev, &action); + if (r < 0) { + log_device_debug_errno(dev, r, "Failed to acquire uevent action of block device, ignoring: %m"); + return 0; + } + + if (action == SD_DEVICE_REMOVE) + (void) list_candidate_devices_send_remove(c->link, dev); + else { + /* All non-REMOVE actions are treated as "device exists afterwards". Re-run the full + * filter set; the tri-state distinguishes "ignore entirely" (static-filtered) from + * "client should treat this as a removal" (dynamic-filtered, e.g. size became 0 or + * read-only became 1). */ + _cleanup_(block_device_done) BlockDevice d = BLOCK_DEVICE_NULL; + r = blockdev_list_one(dev, c->flags, c->root_devno, c->whole_root_devno, &d); + if (r < 0) + return r; + + switch (r) { + case BLOCKDEV_LIST_MATCH_NO: + case BLOCKDEV_LIST_MATCH_SKIPPED: + break; + + case BLOCKDEV_LIST_MATCH_FILTERED: + (void) list_candidate_devices_send_remove(c->link, dev); + break; + + case BLOCKDEV_LIST_MATCH_YES: + (void) list_candidate_devices_send_add(c->link, &d, /* with_action= */ true); + break; + + default: + assert_not_reached(); + } + } + + /* Push out any notifications we just queued so the client doesn't have to wait for the next + * event-loop tick to receive them. */ + (void) sd_varlink_flush(c->link); + return 0; +} + +int vl_method_list_candidate_devices( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + struct { + bool ignore_root; + bool ignore_empty; + bool subscribe; + } p = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "ignoreRoot", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, voffsetof(p, ignore_root), 0 }, + { "ignoreEmpty", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, voffsetof(p, ignore_empty), 0 }, + { "subscribe", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, voffsetof(p, subscribe), 0 }, + {} + }; + + int r; + + assert(link); + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + const BlockDevListFlags listflags = + BLOCKDEV_LIST_SHOW_SYMLINKS| + BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING| + BLOCKDEV_LIST_IGNORE_ZRAM| + BLOCKDEV_LIST_METADATA| + BLOCKDEV_LIST_IGNORE_READ_ONLY| + (p.ignore_empty ? BLOCKDEV_LIST_IGNORE_EMPTY : 0)| + (p.ignore_root ? BLOCKDEV_LIST_IGNORE_ROOT : 0); + + _cleanup_(list_candidate_devices_context_freep) ListCandidateDevicesContext *c = NULL; + + if (p.subscribe) { + /* Start the monitor *before* the initial enumeration so we don't lose events that fire + * during the enumeration window. Duplicate add events for the same device are + * legitimate and documented; clients upsert by identifier. */ + + c = new(ListCandidateDevicesContext, 1); + if (!c) + return -ENOMEM; + + *c = (ListCandidateDevicesContext) { + .flags = listflags, + }; + + (void) blockdev_list_get_root_devnos(listflags, &c->root_devno, &c->whole_root_devno); + + r = sd_device_monitor_new(&c->monitor); + if (r < 0) + return log_error_errno(r, "Failed to allocate block device monitor: %m"); + + (void) sd_device_monitor_set_description(c->monitor, "repart-candidate-devices"); + + r = sd_device_monitor_filter_add_match_subsystem_devtype(c->monitor, "block", /* devtype= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to configure block device monitor filter: %m"); + + r = sd_device_monitor_attach_event(c->monitor, sd_varlink_get_event(link)); + if (r < 0) + return log_error_errno(r, "Failed to attach block device monitor to event loop: %m"); + + r = sd_device_monitor_start(c->monitor, list_candidate_devices_on_uevent, c); + if (r < 0) + return log_error_errno(r, "Failed to start block device monitor: %m"); + } else { + /* Non-subscribing: the sentinel fires if no replies were sent. */ + r = sd_varlink_set_sentinel(link, "io.systemd.Repart.NoCandidateDevices"); + if (r < 0) + return r; + } + + BlockDevice *l = NULL; + size_t n = 0; + CLEANUP_ARRAY(l, n, block_device_array_free); + + r = blockdev_list(listflags, &l, &n); + if (r < 0) + return r; + + FOREACH_ARRAY(d, l, n) { + r = list_candidate_devices_send_add(link, d, /* with_action= */ p.subscribe); + if (r < 0) + return r; + } + + if (!p.subscribe) + return 0; + + /* Subscribing: a single "ready" sentinel marks the boundary between initial enumeration and + * live events. The call stays open after we return; live events flow through + * list_candidate_devices_on_uevent(). */ + r = sd_varlink_notifybo(link, SD_JSON_BUILD_PAIR("action", JSON_BUILD_CONST_STRING("ready"))); + if (r < 0) + return r; + + /* Install the on-disconnect handler lazily, only once we actually have a subscription to clean + * up. The call is idempotent for the same callback. */ + r = sd_varlink_server_bind_disconnect(sd_varlink_get_server(link), vl_on_disconnect); + if (r < 0) + return r; + + /* Hand ownership of the context to the link; vl_on_disconnect() frees it. */ + c->link = sd_varlink_ref(link); + sd_varlink_set_userdata(link, TAKE_PTR(c)); + + return 0; +} diff --git a/src/repart/repart-list-candidate-devices.h b/src/repart/repart-list-candidate-devices.h new file mode 100644 index 0000000000000..21277c8a5fd2f --- /dev/null +++ b/src/repart/repart-list-candidate-devices.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +int vl_method_list_candidate_devices( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata); diff --git a/src/repart/repart.c b/src/repart/repart.c index f6ca9aca0565a..1b1ad673c96d2 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -1,7 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include +#include +#include #include #include #include @@ -25,6 +26,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 +51,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 +62,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" @@ -67,6 +70,7 @@ #include "process-util.h" #include "random-util.h" #include "ratelimit.h" +#include "repart-list-candidate-devices.h" #include "reread-partition-table.h" #include "resize-fs.h" #include "rm-rf.h" @@ -200,6 +204,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 +217,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,11 +245,15 @@ 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, PROGRESS_LOADING_TABLE, PROGRESS_OPENING_COPY_BLOCK_SOURCES, + PROGRESS_OPENING_BLOCK_DEVICE_REPLACE_SOURCES, PROGRESS_ACQUIRING_PARTITION_LABELS, PROGRESS_MINIMIZING, PROGRESS_PLACING, @@ -251,6 +264,7 @@ typedef enum ProgressPhase { PROGRESS_ADJUSTING_PARTITION, PROGRESS_WRITING_TABLE, PROGRESS_REREADING_TABLE, + PROGRESS_REPLACING_DEVICE, _PROGRESS_PHASE_MAX, _PROGRESS_PHASE_INVALID = -EINVAL, } ProgressPhase; @@ -281,6 +295,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, @@ -406,6 +428,27 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(subvolume_hash_ops, char, path_has typedef struct Context Context; +typedef struct { + uint64_t devid; + int mountpoint_fd; + int source_fd; + char *source_path; + uint64_t source_size; + bool done; +} BtrfsReplacement; + +static BtrfsReplacement* btrfs_replacement_free(BtrfsReplacement *b) { + if (!b) + return NULL; + + safe_close(b->mountpoint_fd); + safe_close(b->source_fd); + free(b->source_path); + return mfree(b); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(BtrfsReplacement*, btrfs_replacement_free); + typedef struct Partition { Context *context; @@ -451,6 +494,9 @@ typedef struct Partition { uint64_t copy_blocks_done; char *format; + char *block_device_replace; + BtrfsReplacement *btrfs_replaced; + char *volume_name; char **exclude_files_source; char **exclude_files_target; char **make_directories; @@ -463,6 +509,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 +518,7 @@ typedef struct Partition { char *compression; char *compression_level; uint64_t fs_sector_size; + int discard; int add_validatefs; CopyFiles *copy_files; @@ -495,8 +543,8 @@ typedef struct Partition { RateLimit progress_ratelimit; char *supplement_for_name; - struct Partition *supplement_for, *supplement_target_for; - struct Partition *suppressing; + struct Partition *supplement_for, *supplemented_by; + bool suppressing_supplement; struct Partition *siblings[_VERITY_MODE_MAX]; @@ -505,7 +553,7 @@ typedef struct Partition { #define PARTITION_IS_FOREIGN(p) (!(p)->definition_path) #define PARTITION_EXISTS(p) (!!(p)->current_partition) -#define PARTITION_SUPPRESSED(p) ((p)->supplement_for && (p)->supplement_for->suppressing == (p)) +#define PARTITION_SUPPRESSED(p) ((p)->supplement_for && (p)->supplement_for->suppressing_supplement) struct FreeArea { Partition *after; @@ -525,6 +573,7 @@ struct Context { uint64_t start, end, total; struct fdisk_context *fdisk_context; + int fdisk_context_fd; uint64_t sector_size, grain_size, default_fs_sector_size; sd_id128_t seed; @@ -548,6 +597,8 @@ struct Context { bool defer_partitions_factory_reset; sd_varlink *link; /* If 'more' is used on the Varlink call, we'll send progress info over this link */ + + bool needs_rescan; }; static const char *empty_mode_table[_EMPTY_MODE_MAX] = { @@ -588,6 +639,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", @@ -602,26 +659,33 @@ static const char *minimize_mode_table[_MINIMIZE_MODE_MAX] = { }; static const char *progress_phase_table[_PROGRESS_PHASE_MAX] = { - [PROGRESS_LOADING_DEFINITIONS] = "loading-definitions", - [PROGRESS_LOADING_TABLE] = "loading-table", - [PROGRESS_OPENING_COPY_BLOCK_SOURCES] = "opening-copy-block-sources", - [PROGRESS_ACQUIRING_PARTITION_LABELS] = "acquiring-partition-labels", - [PROGRESS_MINIMIZING] = "minimizing", - [PROGRESS_PLACING] = "placing", - [PROGRESS_WIPING_DISK] = "wiping-disk", - [PROGRESS_WIPING_PARTITION] = "wiping-partition", - [PROGRESS_COPYING_PARTITION] = "copying-partition", - [PROGRESS_FORMATTING_PARTITION] = "formatting-partition", - [PROGRESS_ADJUSTING_PARTITION] = "adjusting-partition", - [PROGRESS_WRITING_TABLE] = "writing-table", - [PROGRESS_REREADING_TABLE] = "rereading-table", + [PROGRESS_LOADING_DEFINITIONS] = "loading-definitions", + [PROGRESS_LOADING_TABLE] = "loading-table", + [PROGRESS_OPENING_COPY_BLOCK_SOURCES] = "opening-copy-block-sources", + [PROGRESS_OPENING_BLOCK_DEVICE_REPLACE_SOURCES] = "opening-block-device-replace-sources", + [PROGRESS_ACQUIRING_PARTITION_LABELS] = "acquiring-partition-labels", + [PROGRESS_MINIMIZING] = "minimizing", + [PROGRESS_PLACING] = "placing", + [PROGRESS_WIPING_DISK] = "wiping-disk", + [PROGRESS_WIPING_PARTITION] = "wiping-partition", + [PROGRESS_COPYING_PARTITION] = "copying-partition", + [PROGRESS_FORMATTING_PARTITION] = "formatting-partition", + [PROGRESS_ADJUSTING_PARTITION] = "adjusting-partition", + [PROGRESS_WRITING_TABLE] = "writing-table", + [PROGRESS_REREADING_TABLE] = "rereading-table", + [PROGRESS_REPLACING_DEVICE] = "replacing-device", }; +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 +711,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 +787,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; @@ -729,23 +797,24 @@ static Partition *partition_new(Context *c) { static void partition_unlink_supplement(Partition *p) { assert(p); - assert(!p->supplement_for || !p->supplement_target_for); /* Can't be both */ + assert(!p->supplement_for || !p->supplemented_by); /* Can't be both */ - if (p->supplement_target_for) { - assert(p->supplement_target_for->supplement_for == p); + if (p->supplemented_by) { + assert(p->supplemented_by->supplement_for == p); - p->supplement_target_for->supplement_for = NULL; + p->supplemented_by->supplement_for = NULL; } if (p->supplement_for) { - assert(p->supplement_for->supplement_target_for == p); - assert(!p->supplement_for->suppressing || p->supplement_for->suppressing == p); + assert(p->supplement_for->supplemented_by == p); - p->supplement_for->supplement_target_for = p->supplement_for->suppressing = NULL; + p->supplement_for->supplemented_by = NULL; + p->supplement_for->suppressing_supplement = false; } p->supplement_for_name = mfree(p->supplement_for_name); - p->supplement_target_for = p->supplement_for = p->suppressing = NULL; + p->supplemented_by = p->supplement_for = NULL; + p->suppressing_supplement = false; } static Partition* partition_free(Partition *p) { @@ -759,9 +828,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); @@ -770,6 +839,9 @@ static Partition* partition_free(Partition *p) { safe_close(p->copy_blocks_fd); free(p->format); + free(p->block_device_replace); + btrfs_replacement_free(p->btrfs_replaced); + free(p->volume_name); strv_free(p->exclude_files_source); strv_free(p->exclude_files_target); strv_free(p->make_directories); @@ -814,6 +886,9 @@ static void partition_foreignize(Partition *p) { p->copy_blocks_root = NULL; p->format = mfree(p->format); + p->block_device_replace = mfree(p->block_device_replace); + p->btrfs_replaced = btrfs_replacement_free(p->btrfs_replaced); + p->volume_name = mfree(p->volume_name); p->exclude_files_source = strv_free(p->exclude_files_source); p->exclude_files_target = strv_free(p->exclude_files_target); p->make_directories = strv_free(p->make_directories); @@ -844,6 +919,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; @@ -913,6 +989,7 @@ static Context* context_new( .empty = empty, .dry_run = dry_run, .backing_fd = -EBADF, + .fdisk_context_fd = -EBADF, }; return context; @@ -941,7 +1018,8 @@ 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->fdisk_context_fd); safe_close(context->backing_fd); if (context->node_is_our_file) @@ -950,9 +1028,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); @@ -1021,7 +1101,7 @@ static void partition_drop_or_foreignize(Partition *p) { /* If a supplement partition is dropped, we don't want to merge in its settings. */ if (PARTITION_SUPPRESSED(p)) - p->supplement_for->suppressing = NULL; + p->supplement_for->suppressing_supplement = false; } } @@ -1098,7 +1178,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 +1195,18 @@ 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->btrfs_replaced) + assert_se(INC_SAFE(&d, round_up_size(p->btrfs_replaced->source_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) @@ -1132,8 +1214,8 @@ static uint64_t partition_min_size(const Context *context, const Partition *p) { } uint64_t min_size = p->size_min; - if (p->suppressing && (min_size == UINT64_MAX || p->suppressing->size_min > min_size)) - min_size = p->suppressing->size_min; + if (p->suppressing_supplement && (min_size == UINT64_MAX || p->supplemented_by->size_min > min_size)) + min_size = p->supplemented_by->size_min; /* Default to 10M min size, except if the file system is read-only, in which case let's not enforce a * minimum size, because even if we wanted to we couldn't take possession of the extra space @@ -1163,7 +1245,7 @@ static uint64_t partition_max_size(const Context *context, const Partition *p) { if (partition_designator_is_verity_sig(p->type.designator)) return VERITY_SIG_SIZE; - override_max = p->suppressing ? MIN(p->size_max, p->suppressing->size_max) : p->size_max; + override_max = p->suppressing_supplement ? MIN(p->size_max, p->supplemented_by->size_max) : p->size_max; if (override_max == UINT64_MAX) return UINT64_MAX; @@ -1180,16 +1262,16 @@ static uint64_t partition_min_padding(const Partition *p) { assert(p); - override_min = p->suppressing ? MAX(p->padding_min, p->suppressing->padding_min) : p->padding_min; + override_min = p->suppressing_supplement ? MAX(p->padding_min, p->supplemented_by->padding_min) : p->padding_min; return override_min != UINT64_MAX ? override_min : 0; } static uint64_t partition_max_padding(const Partition *p) { assert(p); - return p->suppressing ? MIN(p->padding_max, p->suppressing->padding_max) : p->padding_max; + return p->suppressing_supplement ? MIN(p->padding_max, p->supplemented_by->padding_max) : p->padding_max; } -static uint64_t partition_min_size_with_padding(Context *context, const Partition *p) { +static uint64_t partition_min_size_with_padding(const Context *context, const Partition *p) { uint64_t sz; /* Calculate the disk space we need for this partition plus any free space coming after it. This @@ -1220,7 +1302,7 @@ static uint64_t free_area_available(const FreeArea *a) { return a->size - a->allocated; } -static uint64_t free_area_current_end(Context *context, const FreeArea *a) { +static uint64_t free_area_current_end(const Context *context, const FreeArea *a) { assert(context); assert(a); @@ -1229,12 +1311,14 @@ 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); } -static uint64_t free_area_min_end(Context *context, const FreeArea *a) { +static uint64_t free_area_min_end(const Context *context, const FreeArea *a) { assert(context); assert(a); @@ -1248,7 +1332,7 @@ static uint64_t free_area_min_end(Context *context, const FreeArea *a) { return round_up_size(a->after->offset + partition_min_size_with_padding(context, a->after), context->grain_size); } -static uint64_t free_area_available_for_new_partitions(Context *context, const FreeArea *a) { +static uint64_t free_area_available_for_new_partitions(const Context *context, const FreeArea *a) { assert(context); assert(a); @@ -1259,13 +1343,15 @@ 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), free_area_available_for_new_partitions(context, *b)); } -static uint64_t charge_size(Context *context, uint64_t total, uint64_t amount) { +static uint64_t charge_size(const Context *context, uint64_t total, uint64_t amount) { assert(context); /* Subtract the specified amount from total, rounding up to multiple of 4K if there's room */ assert(amount <= total); @@ -1352,32 +1438,28 @@ static bool context_unmerge_and_allocate_partitions(Context *context) { /* First, let's try to un-suppress just one supplement partition and see if that gets us anywhere */ LIST_FOREACH(partitions, p, context->partitions) { - Partition *unsuppressed; - - if (!p->suppressing) + if (!p->suppressing_supplement) continue; - unsuppressed = TAKE_PTR(p->suppressing); + p->suppressing_supplement = false; if (context_allocate_partitions(context, NULL)) return true; - p->suppressing = unsuppressed; + p->suppressing_supplement = true; } /* Looks like not. So we have to un-suppress at least two partitions. We can do this recursively */ LIST_FOREACH(partitions, p, context->partitions) { - Partition *unsuppressed; - - if (!p->suppressing) + if (!p->suppressing_supplement) continue; - unsuppressed = TAKE_PTR(p->suppressing); + p->suppressing_supplement = false; if (context_unmerge_and_allocate_partitions(context)) return true; - p->suppressing = unsuppressed; + p->suppressing_supplement = true; } /* No combination of un-suppressed supplements made it possible to fit the partitions */ @@ -1386,15 +1468,15 @@ static bool context_unmerge_and_allocate_partitions(Context *context) { static uint32_t partition_weight(const Partition *p) { assert(p); - return p->suppressing ? p->suppressing->weight : p->weight; + return p->suppressing_supplement ? p->supplemented_by->weight : p->weight; } static uint32_t partition_padding_weight(const Partition *p) { assert(p); - return p->suppressing ? p->suppressing->padding_weight : p->padding_weight; + return p->suppressing_supplement ? p->supplemented_by->padding_weight : p->padding_weight; } -static int context_sum_weights(Context *context, FreeArea *a, uint64_t *ret) { +static int context_sum_weights(const Context *context, const FreeArea *a, uint64_t *ret) { uint64_t weight_sum = 0; assert(context); @@ -1458,8 +1540,8 @@ typedef enum GrowPartitionPhase { } GrowPartitionPhase; static bool context_grow_partitions_phase( - Context *context, - FreeArea *a, + const Context *context, + const FreeArea *a, GrowPartitionPhase phase, uint64_t *span, uint64_t *weight_sum) { @@ -1637,10 +1719,21 @@ static int context_grow_partitions_on_free_area(Context *context, FreeArea *a) { break; } - /* Yuck, still no one? Then make it padding */ - if (span > 0 && a->after) { - assert(a->after->new_padding != UINT64_MAX); - a->after->new_padding += span; + /* Partitions didn't want all the space? Then make it padding */ + if (span > 0) { + Partition *last_partition = NULL; + + LIST_FOREACH(partitions, p, context->partitions) + if (p->allocated_to_area == a) + last_partition = p; + + if (last_partition) { + assert(last_partition->new_padding != UINT64_MAX); + last_partition->new_padding += round_down_size(span, context->grain_size); + } else if (a->after) { + assert(a->after->new_padding != UINT64_MAX); + a->after->new_padding += round_down_size(span, context->grain_size); + } } return 0; @@ -1701,7 +1794,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 +1810,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 +1825,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; } } } @@ -2256,7 +2366,7 @@ static int config_parse_make_symlinks( int r; for (;;) { - _cleanup_free_ char *word = NULL, *path = NULL, *target = NULL, *d = NULL; + _cleanup_free_ char *word = NULL, *source = NULL, *target = NULL, *resolved_source = NULL, *resolved_target = NULL; r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE); if (r == -ENOMEM) @@ -2269,7 +2379,7 @@ static int config_parse_make_symlinks( return 0; const char *q = word; - r = extract_many_words(&q, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &path, &target); + r = extract_many_words(&q, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &target); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", q); continue; @@ -2280,18 +2390,26 @@ static int config_parse_make_symlinks( continue; } - r = specifier_printf(path, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, /* userdata= */ NULL, &d); + r = specifier_printf(source, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, /* userdata= */ NULL, &resolved_source); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to expand specifiers in Subvolumes= parameter, ignoring: %s", path); + "Failed to expand specifiers in MakeSymlinks= source, ignoring: %s", source); continue; } - r = path_simplify_and_warn(d, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue); + r = path_simplify_and_warn(resolved_source, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue); if (r < 0) continue; - r = strv_consume_pair(sv, TAKE_PTR(d), TAKE_PTR(target)); + r = specifier_printf(target, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, /* userdata= */ NULL, &resolved_target); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to expand specifiers in MakeSymlinks= target, ignoring: %s", target); + continue; + } + /* Don't simplify the symlink target, preserve the exact argument including relative components */ + + r = strv_consume_pair(sv, TAKE_PTR(resolved_source), TAKE_PTR(resolved_target)); if (r < 0) return log_error_errno(r, "Failed to add symlink to list: %m"); } @@ -2652,6 +2770,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 +2814,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); @@ -2737,13 +2858,13 @@ static bool partition_add_validatefs(const Partition *p) { static bool partition_needs_populate(const Partition *p) { assert(p); - assert(!p->supplement_for || !p->suppressing); /* Avoid infinite recursion */ + assert(!p->supplement_for || !p->suppressing_supplement); /* Avoid infinite recursion */ return p->n_copy_files > 0 || !strv_isempty(p->make_directories) || !strv_isempty(p->make_symlinks) || partition_add_validatefs(p) || - (p->suppressing && partition_needs_populate(p->suppressing)); + (p->suppressing_supplement && partition_needs_populate(p->supplemented_by)); } static MakeFileSystemFlags partition_mkfs_flags(const Partition *p) { @@ -2796,11 +2917,15 @@ 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) log_debug_errno(r, "Failed to send varlink notify progress notification, ignoring: %m"); + + r = sd_varlink_flush(c->link); + if (r < 0) + log_debug_errno(r, "Failed to flush varlink notify progress notification, ignoring: %m"); } return 0; @@ -2820,10 +2945,10 @@ static int partition_read_definition( { "Partition", "Priority", config_parse_int32, 0, &p->priority }, { "Partition", "Weight", config_parse_weight, 0, &p->weight }, { "Partition", "PaddingWeight", config_parse_weight, 0, &p->padding_weight }, - { "Partition", "SizeMinBytes", config_parse_size4096, -1, &p->size_min }, - { "Partition", "SizeMaxBytes", config_parse_size4096, 1, &p->size_max }, - { "Partition", "PaddingMinBytes", config_parse_size4096, -1, &p->padding_min }, - { "Partition", "PaddingMaxBytes", config_parse_size4096, 1, &p->padding_max }, + { "Partition", "SizeMinBytes", config_parse_size4096, 1, &p->size_min }, + { "Partition", "SizeMaxBytes", config_parse_size4096, -1, &p->size_max }, + { "Partition", "PaddingMinBytes", config_parse_size4096, 1, &p->padding_min }, + { "Partition", "PaddingMaxBytes", config_parse_size4096, -1, &p->padding_max }, { "Partition", "FactoryReset", config_parse_bool, 0, &p->factory_reset }, { "Partition", "CopyBlocks", config_parse_copy_blocks, 0, p }, { "Partition", "Format", config_parse_fstype, 0, &p->format }, @@ -2851,11 +2976,15 @@ 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 }, + { "Partition", "BlockDeviceReplace", config_parse_path, 0, &p->block_device_replace }, + { "Partition", "VolumeName", config_parse_string, CONFIG_PARSE_STRING_SAFE, &p->volume_name }, {} }; _cleanup_free_ char *filename = NULL; @@ -2906,6 +3035,34 @@ static int partition_read_definition( return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "Format=/CopyFiles=/MakeDirectories=/MakeSymlinks= and CopyBlocks= cannot be combined, refusing."); + if (p->block_device_replace && (p->format || partition_needs_populate(p))) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "Format=/CopyFiles=/MakeDirectories=/MakeSymlinks= and BlockDeviceReplace= cannot be combined, refusing."); + + if ((p->copy_blocks_path || p->copy_blocks_auto) && p->block_device_replace) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "CopyBlocks= and BlockDeviceReplace= cannot be combined, refusing."); + + if (p->block_device_replace && arg_offline == 1) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "BlockDeviceReplace= is incompatible with --offline=yes, refusing."); + + if (p->volume_name && !filename_is_valid(p->volume_name)) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "VolumeName= has an invalid filename value, refusing."); + + if (p->volume_name && strlen(p->volume_name) > (DM_NAME_LEN - 1)) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "VolumeName= is too long, refusing."); + + if (p->volume_name && p->encrypt == ENCRYPT_OFF) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "VolumeName= requires Encrypt= to be enabled, refusing."); + + if (p->volume_name && !p->block_device_replace) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "VolumeName= requires BlockDeviceReplace= to be set, refusing."); + if (partition_needs_populate(p) && streq_ptr(p->format, "swap")) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "Format=swap and CopyFiles=/MakeDirectories=/MakeSymlinks= cannot be combined, refusing."); @@ -2915,7 +3072,7 @@ static int partition_read_definition( if (p->type.designator == PARTITION_SWAP) format = "swap"; - else if (partition_needs_populate(p) || (p->encrypt != ENCRYPT_OFF && !(p->copy_blocks_path || p->copy_blocks_auto))) + else if (partition_needs_populate(p) || (p->encrypt != ENCRYPT_OFF && !(p->copy_blocks_path || p->copy_blocks_auto || p->block_device_replace))) /* Pick "vfat" as file system for esp and xbootldr partitions, otherwise default to "ext4". */ format = IN_SET(p->type.designator, PARTITION_ESP, PARTITION_XBOOTLDR) ? "vfat" : "ext4"; @@ -2929,7 +3086,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 +3100,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 +3118,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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); if (r < 0) return log_syntax(NULL, LOG_ERR, path, 1, r, "libcryptsetup not found, Verity=/Encrypt= are not supported: %m"); @@ -2972,10 +3133,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 +3177,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) || @@ -3073,16 +3256,17 @@ static int find_verity_sibling(Context *context, Partition *p, VerityMode mode, return 0; } -static int context_open_and_lock_backing_fd(const char *node, int operation, int *backing_fd) { +static int context_open_and_lock_backing_fd(const char *node, int operation, int mode, int *backing_fd) { _cleanup_close_ int fd = -EBADF; assert(node); assert(backing_fd); + assert(IN_SET(mode, O_RDONLY, O_RDWR)); if (*backing_fd >= 0) return 0; - fd = open(node, O_RDONLY|O_CLOEXEC); + fd = open(node, mode|O_CLOEXEC); if (fd < 0) return log_error_errno(errno, "Failed to open device '%s': %m", node); @@ -3111,31 +3295,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 +3329,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 +3348,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; @@ -3195,33 +3359,33 @@ static int context_copy_from_one(Context *context, const char *src) { assert(src); - r = context_open_and_lock_backing_fd(src, LOCK_SH, &fd); + r = context_open_and_lock_backing_fd(src, LOCK_SH, O_RDONLY, &fd); 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 +3395,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 +3417,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; @@ -3299,8 +3463,8 @@ static int context_copy_from_one(Context *context, const char *src) { if (!np->copy_blocks_path) return log_oom(); - np->copy_blocks_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (np->copy_blocks_fd < 0) + np->copy_blocks_fd = r = RET_NERRNO(fcntl(fd, F_DUPFD_CLOEXEC, 3)); + if (r < 0) return log_error_errno(r, "Failed to duplicate file descriptor of %s: %m", src); np->copy_blocks_offset = start; @@ -3370,10 +3534,10 @@ static int supplement_find_target(const Context *context, const Partition *suppl return log_syntax(NULL, LOG_ERR, supplement->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), "SupplementFor= target is itself configured as a supplement."); - if (p->suppressing) + if (p->suppressing_supplement) return log_syntax(NULL, LOG_ERR, supplement->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), "SupplementFor= target already has a supplement defined: %s", - p->suppressing->definition_path); + p->supplemented_by->definition_path); *ret = p; return 0; @@ -3468,6 +3632,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; @@ -3508,7 +3693,8 @@ static int context_read_definitions(Context *context) { "PaddingMinBytes= larger than PaddingMaxBytes= when merged with SupplementFor= target."); p->supplement_for = tgt; - tgt->suppressing = tgt->supplement_target_for = p; + tgt->supplemented_by = p; + tgt->suppressing_supplement = true; } return 0; @@ -3518,14 +3704,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 +3722,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,12 +3765,17 @@ 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 */ } +static int context_open_mode(Context *context) { + return ASSERT_PTR(context)->dry_run ? O_RDONLY : O_RDWR; +} + static int context_load_partition_table(Context *context) { + _cleanup_close_ int fd = -EBADF; _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL; _cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL; uint64_t left_boundary = UINT64_MAX, first_lba, last_lba, nsectors; @@ -3598,6 +3789,7 @@ static int context_load_partition_table(Context *context) { assert(context); assert(context->node); assert(!context->fdisk_context); + assert(context->fdisk_context_fd < 0); assert(!context->free_areas); assert(context->start == UINT64_MAX); assert(context->end == UINT64_MAX); @@ -3605,13 +3797,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; @@ -3619,6 +3811,7 @@ static int context_load_partition_table(Context *context) { r = context_open_and_lock_backing_fd( context->node, context->dry_run ? LOCK_SH : LOCK_EX, + context_open_mode(context), &context->backing_fd); if (r < 0) return r; @@ -3644,16 +3837,20 @@ 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( + if (context->backing_fd < 0) { + fd = open(context->node, context_open_mode(context)|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open backing node '%s': %m", context->node); + } + r = sym_fdisk_assign_device_by_fd( c, - context->backing_fd >= 0 ? FORMAT_PROC_FD_PATH(context->backing_fd) : context->node, + context->backing_fd >= 0 ? context->backing_fd : fd, + context->node, context->dry_run); if (r == -EINVAL && arg_size_auto) { struct stat st; @@ -3662,7 +3859,7 @@ static int context_load_partition_table(Context *context) { * it if automatic sizing is requested. */ if (context->backing_fd < 0) - r = stat(context->node, &st); + r = fstat(fd, &st); else r = fstat(context->backing_fd, &st); if (r < 0) @@ -3670,9 +3867,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,8 +3880,9 @@ 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_open_mode(context), &context->backing_fd); if (r < 0) return r; @@ -3695,15 +3893,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 +3909,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 +3927,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 +3949,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 +3964,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 +3974,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 +3995,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 +4015,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 +4051,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 +4084,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) @@ -3905,18 +4103,18 @@ static int context_load_partition_table(Context *context) { LIST_FOREACH(partitions, p, context->partitions) if (PARTITION_SUPPRESSED(p) && PARTITION_EXISTS(p)) - p->supplement_for->suppressing = NULL; + p->supplement_for->suppressing_supplement = false; 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); @@ -3957,6 +4155,7 @@ static int context_load_partition_table(Context *context) { context->default_fs_sector_size = fs_secsz; context->grain_size = grainsz; context->fdisk_context = TAKE_PTR(c); + context->fdisk_context_fd = TAKE_FD(fd); return from_scratch; } @@ -3984,12 +4183,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; } @@ -4002,7 +4201,7 @@ static void context_unload_partition_table(Context *context) { /* A supplement partition is only ever un-suppressed if the existing partition table prevented * us from suppressing it. So when unloading the partition table, we must re-suppress. */ if (p->supplement_for) - p->supplement_for->suppressing = p; + p->supplement_for->suppressing_supplement = true; } context->start = UINT64_MAX; @@ -4010,9 +4209,10 @@ 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; } + context->fdisk_context_fd = safe_close(context->fdisk_context_fd); context_free_free_areas(context); } @@ -4020,6 +4220,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)); @@ -4131,6 +4333,15 @@ static int context_dump_partitions(Context *context) { (void) table_set_align_percent(t, table_get_cell(t, 0, 11), 100); (void) table_set_align_percent(t, table_get_cell(t, 0, 12), 100); + size_t n_partitions = 0; + LIST_FOREACH(partitions, p, context->partitions) { + if (p->dropped) + continue; + + n_partitions++; + } + + size_t cur_n_partition = 0; LIST_FOREACH(partitions, p, context->partitions) { _cleanup_free_ char *size_change = NULL, *padding_change = NULL, *partname = NULL, *rh = NULL; char uuid_buffer[SD_ID128_UUID_STRING_MAX]; @@ -4139,13 +4350,15 @@ static int context_dump_partitions(Context *context) { if (p->dropped) continue; + cur_n_partition++; + if (p->current_size == UINT64_MAX) activity = "create"; else if (p->current_size != p->new_size) 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) @@ -4179,10 +4392,10 @@ static int context_dump_partitions(Context *context) { TABLE_UINT64, p->offset, TABLE_UINT64, p->current_size == UINT64_MAX ? 0 : p->current_size, TABLE_UINT64, p->new_size, - TABLE_STRING, size_change, TABLE_SET_COLOR, !p->partitions_next && sum_size > 0 ? ansi_underline() : NULL, + TABLE_STRING, size_change, TABLE_SET_COLOR, cur_n_partition == n_partitions && sum_size > 0 ? ansi_underline() : NULL, TABLE_UINT64, p->current_padding == UINT64_MAX ? 0 : p->current_padding, TABLE_UINT64, p->new_padding, - TABLE_STRING, padding_change, TABLE_SET_COLOR, !p->partitions_next && sum_padding > 0 ? ansi_underline() : NULL, + TABLE_STRING, padding_change, TABLE_SET_COLOR, cur_n_partition == n_partitions && sum_padding > 0 ? ansi_underline() : NULL, TABLE_STRING, activity ?: "unchanged", TABLE_STRING, rh, TABLE_STRV, p->drop_in_files, @@ -4298,6 +4511,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 +4525,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 +4737,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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); 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 +4810,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 +4907,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 +4924,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."); } @@ -4740,7 +4955,7 @@ static int context_discard_gap_after(Context *context, Partition *p) { return 0; } -static bool partition_defer(Context *c, const Partition *p) { +static bool partition_defer(const Context *c, const Partition *p) { assert(c); assert(p); @@ -4806,6 +5021,7 @@ typedef struct DecryptedPartitionTarget { int fd; char *dm_name; char *volume; + bool keep; struct crypt_device *device; } DecryptedPartitionTarget; @@ -4818,11 +5034,14 @@ static DecryptedPartitionTarget* decrypted_partition_target_free(DecryptedPartit safe_close(t->fd); - /* udev or so might access out block device in the background while we are done. Let's hence - * force detach the volume. We sync'ed before, hence this should be safe. */ - r = sym_crypt_deactivate_by_name(t->device, t->dm_name, CRYPT_DEACTIVATE_FORCE); - if (r < 0) - log_warning_errno(r, "Failed to deactivate LUKS device, ignoring: %m"); + if (!t->keep) { + /* udev or so might access our block device in the background while we are done. Let's hence + * force detach the volume. We sync'ed before, hence this should be safe. */ + r = sym_crypt_deactivate_by_name(t->device, t->dm_name, CRYPT_DEACTIVATE_FORCE); + if (r < 0) + log_warning_errno(r, "Failed to deactivate LUKS device, ignoring: %m"); + } else + log_debug("Keeping encrypted device '%s' open.", t->dm_name); sym_crypt_free(t->device); free(t->dm_name); @@ -4832,8 +5051,30 @@ static DecryptedPartitionTarget* decrypted_partition_target_free(DecryptedPartit return NULL; } +/* BlockPartition represents partitions that have been created with BLKPG */ +typedef struct { + int fd; + int whole_fd; /* not owned */ + int nr; + uint64_t offset; + uint64_t size; + char *node; +} BlockPartition; + +static BlockPartition* block_partition_free(BlockPartition *p) { + if (!p) + return NULL; + + safe_close(p->fd); + free(p->node); + return mfree(p); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(BlockPartition*, block_partition_free); + typedef struct { LoopDevice *loop; + BlockPartition *block_partition; int fd; char *path; int whole_fd; @@ -4842,7 +5083,7 @@ typedef struct { static int partition_target_fd(PartitionTarget *t) { assert(t); - assert(t->loop || t->fd >= 0 || t->whole_fd >= 0); + assert(t->loop || t->fd >= 0 || t->whole_fd >= 0 || t->block_partition); if (t->decrypted) return t->decrypted->fd; @@ -4850,6 +5091,9 @@ static int partition_target_fd(PartitionTarget *t) { if (t->loop) return t->loop->fd; + if (t->block_partition) + return t->block_partition->fd; + if (t->fd >= 0) return t->fd; @@ -4858,7 +5102,7 @@ static int partition_target_fd(PartitionTarget *t) { static const char* partition_target_path(PartitionTarget *t) { assert(t); - assert(t->loop || t->path); + assert(t->loop || t->path || t->block_partition); if (t->decrypted) return t->decrypted->volume; @@ -4866,6 +5110,9 @@ static const char* partition_target_path(PartitionTarget *t) { if (t->loop) return t->loop->node; + if (t->block_partition) + return t->block_partition->node; + return t->path; } @@ -4877,6 +5124,7 @@ static PartitionTarget* partition_target_free(PartitionTarget *t) { loop_device_unref(t->loop); safe_close(t->fd); unlink_and_free(t->path); + block_partition_free(t->block_partition); return mfree(t); } @@ -4906,7 +5154,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 +5195,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) @@ -4966,20 +5214,85 @@ static int partition_target_prepare( return 0; } - /* Loopback block devices are not only useful to turn regular files into block devices, but - * also to cut out sections of block devices into new block devices. */ - if (arg_offline <= 0) { - r = loop_device_make(whole_fd, O_RDWR, p->offset, size, context->sector_size, 0, LOCK_EX, &d); - if (r < 0 && loop_device_error_is_fatal(p, r)) - return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno); - if (r >= 0) { - t->loop = TAKE_PTR(d); + r = blockdev_partscan_enabled_fd(whole_fd); + if (r > 0) { + _cleanup_close_ int dev_fd = -EBADF; + _cleanup_free_ char *part_node = NULL; + _cleanup_(block_partition_freep) BlockPartition *b = NULL; + int nr; + + /* blkpg takes int for partition numbers */ + if (p->partno >= INT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Partition number %" PRIu64 " is too large for blkpg.", p->partno); + nr = p->partno + 1; + + part_node = sym_fdisk_partname(context->node, p->partno + 1); + if (!part_node) + return log_oom(); + + /* There is no corresponding call to block_device_remove_partition because we want to + * keep them alive if we succeed, and the rescan will remove them if possible if + * there is an error before writing the partition table. + */ + log_debug("Adding partition '%s' (nr=%i, offset=%" PRIu64 " [align=%" PRIu64 "], size=%" PRIu64 " [align=%" PRIu64 "])", + part_node, nr, + p->offset, NATURAL_ALIGNMENT(p->offset), + size, NATURAL_ALIGNMENT(size)); + r = block_device_add_partition(whole_fd, nr, p->offset, size); + if (r < 0) + return log_error_errno(r, "Failed to create new partition '%s': %m", part_node); + + context->needs_rescan = true; + + dev_fd = open(part_node, O_RDWR|O_CLOEXEC|O_NOCTTY); + if (dev_fd < 0) { + r = -errno; + int q = block_device_remove_partition(whole_fd, nr); + if (q < 0) + log_warning_errno(q, "Error while removing block device partition '%s', ignoring: %m", part_node); + return log_error_errno(r, "Failed to open new partition '%s': %m", part_node); + } + + /* No need to flock for udev, the whole disk fd is already locked. */ + + b = new(BlockPartition, 1); + if (!b) { + r = block_device_remove_partition(whole_fd, nr); + if (r < 0) + log_warning_errno(r, "Error while removing block device partition '%s', ignoring: %m", part_node); + + return log_oom(); + } + + *b = (BlockPartition) { + .fd = TAKE_FD(dev_fd), + .whole_fd = whole_fd, + .nr = nr, + .offset = p->offset, + .size = size, + .node = TAKE_PTR(part_node), + }; + + t->block_partition = TAKE_PTR(b); *ret = TAKE_PTR(t); return 0; - } + } else { + if (!IN_SET(r, 0, -ENOTBLK)) + log_warning_errno(r, "Could not detect whether the device can be partitioned, assuming it cannot be: %m"); + /* Loopback block devices are not only useful to turn regular files into block devices, but + * also to cut out sections of block devices into new block devices. */ + r = loop_device_make(whole_fd, O_RDWR, p->offset, size, context->sector_size, /* loop_flags= */ 0, LOCK_EX, &d); + if (r < 0 && loop_device_error_is_fatal(p, r)) + return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno); + if (r >= 0) { + t->loop = TAKE_PTR(d); + *ret = TAKE_PTR(t); + return 0; + } - log_debug_errno(r, "No access to loop devices, falling back to a regular file"); + log_debug_errno(r, "No access to loop devices, falling back to a regular file"); + } } /* If we can't allocate a loop device, let's write to a regular file that we copy into the final @@ -5006,6 +5319,11 @@ static int partition_target_grow(PartitionTarget *t, uint64_t size) { r = loop_device_refresh_size(t->loop, UINT64_MAX, size); if (r < 0) return log_error_errno(r, "Failed to refresh loopback device size: %m"); + } else if (t->block_partition) { + r = block_device_resize_partition(t->block_partition->whole_fd, t->block_partition->nr, t->block_partition->offset, size); + if (r < 0) + return log_error_errno(r, "Failed to resize partition %d: %m", t->block_partition->nr); + t->block_partition->size = size; } else if (t->fd >= 0) { if (ftruncate(t->fd, size) < 0) return log_error_errno(errno, "Failed to grow '%s' to %s by truncation: %m", @@ -5022,7 +5340,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); @@ -5033,15 +5351,15 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget r = loop_device_sync(t->loop); if (r < 0) return log_error_errno(r, "Failed to sync loopback device: %m"); + } else if (t->block_partition) { + if (fsync(t->block_partition->fd) < 0) + return log_error_errno(errno, "Failed to sync blkpg partition: %m"); } else if (t->fd >= 0) { struct stat st; 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 +5368,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 { @@ -5094,7 +5412,7 @@ static size_t dmcrypt_proper_key_size(Partition *p) { } } -static int partition_encrypt(Context *context, Partition *p, PartitionTarget *target, bool offline) { +static int partition_encrypt(Context *context, Partition *p, PartitionTarget *target, bool offline, bool temporary) { #if HAVE_LIBCRYPTSETUP #if HAVE_TPM2 _cleanup_(erase_and_freep) char *base64_encoded = NULL; @@ -5111,9 +5429,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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); if (r < 0) - return log_error_errno(r, "libcryptsetup not found, cannot encrypt: %m"); + return r; log_info("Encrypting future partition %" PRIu64 "...", p->partno); @@ -5156,7 +5474,13 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta if (ftruncate(fileno(h), luks_params.sector_size) < 0) return log_error_errno(errno, "Failed to grow temporary LUKS header file: %m"); } else { - if (asprintf(&dm_name, "luks-repart-%08" PRIx64, random_u64()) < 0) + if (!temporary && p->volume_name) + dm_name = strdup(p->volume_name); + else if (!temporary && filename_is_valid(vl)) + dm_name = strdup(vl); + else + dm_name = asprintf_safe("luks-repart-%08" PRIx64, random_u64()); + if (!dm_name) return log_oom(); vol = path_join("/dev/mapper/", dm_name); @@ -5164,7 +5488,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 +5524,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 +5882,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 +5944,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 +5959,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 +5968,9 @@ static int partition_format_verity_hash( (void) partition_hint(p, node, &hint); - r = dlopen_cryptsetup(); + r = DLOPEN_CRYPTSETUP(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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 +6061,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 +6070,25 @@ static int sign_verity_roothash( assert(iovec_is_set(roothash)); assert(ret_signature); + r = DLOPEN_LIBCRYPTO(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + 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)); + return log_openssl_errors(LOG_ERR, "Failed to calculate PKCS7 signature"); - 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)); + return log_openssl_errors(LOG_ERR, "Failed to convert PKCS7 signature to DER"); *ret_signature = IOVEC_MAKE(TAKE_PTR(sig), sigsz); @@ -5732,21 +6098,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 +6155,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 +6191,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; @@ -5938,8 +6336,8 @@ static int context_copy_blocks(Context *context) { if (r < 0) return r; - if (p->encrypt != ENCRYPT_OFF && t->loop) { - r = partition_encrypt(context, p, t, /* offline= */ false); + if (p->encrypt != ENCRYPT_OFF && (t->loop || t->block_partition)) { + r = partition_encrypt(context, p, t, /* offline= */ false, /* temporary= */ true); if (r < 0) return r; } @@ -5962,8 +6360,8 @@ static int context_copy_blocks(Context *context) { log_info("Copying in of '%s' on block level completed.", p->copy_blocks_path); - if (p->encrypt != ENCRYPT_OFF && !t->loop) { - r = partition_encrypt(context, p, t, /* offline= */ true); + if (p->encrypt != ENCRYPT_OFF && !t->loop && !t->block_partition) { + r = partition_encrypt(context, p, t, /* offline= */ true, /* temporary= */ true); if (r < 0) return r; } @@ -6078,15 +6476,15 @@ static int make_copy_files_denylist( /* Add the user configured excludes. */ - if (p->suppressing) { + if (p->suppressing_supplement) { r = shallow_join_strv(&override_exclude_src, p->exclude_files_source, - p->suppressing->exclude_files_source); + p->supplemented_by->exclude_files_source); if (r < 0) return r; r = shallow_join_strv(&override_exclude_tgt, p->exclude_files_target, - p->suppressing->exclude_files_target); + p->supplemented_by->exclude_files_target); if (r < 0) return r; } @@ -6218,14 +6616,14 @@ static int make_subvolumes_hashmap(const Partition *p, Hashmap **ret) { TAKE_PTR(path); } - if (p->suppressing) { - Hashmap *suppressing; + if (p->suppressing_supplement) { + Hashmap *supplemented_by; - r = make_subvolumes_hashmap(p->suppressing, &suppressing); + r = make_subvolumes_hashmap(p->supplemented_by, &supplemented_by); if (r < 0) return r; - r = hashmap_merge(hashmap, suppressing); + r = hashmap_merge(hashmap, supplemented_by); if (r < 0) return log_oom(); } @@ -6264,14 +6662,14 @@ static int make_subvolumes_by_source_inode_hashmap( return r; } - if (p->suppressing) { - Hashmap *suppressing; + if (p->suppressing_supplement) { + Hashmap *supplemented_by; - r = make_subvolumes_by_source_inode_hashmap(p->suppressing, source, target, &suppressing); + r = make_subvolumes_by_source_inode_hashmap(p->supplemented_by, source, target, &supplemented_by); if (r < 0) return r; - r = hashmap_merge(hashmap, suppressing); + r = hashmap_merge(hashmap, supplemented_by); if (r < 0) return log_oom(); } @@ -6335,12 +6733,14 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { return log_oom(); size_t n_copy_files = p->n_copy_files; - if (p->suppressing) { + if (p->suppressing_supplement) { if (!GREEDY_REALLOC_APPEND(copy_files, n_copy_files, - p->suppressing->copy_files, p->suppressing->n_copy_files)) + p->supplemented_by->copy_files, p->supplemented_by->n_copy_files)) return log_oom(); } + bool copy_ownership = fstype_can_ownership(p->format); + /* copy_tree_at() automatically copies the permissions of source directories to target directories if * it created them. However, the root directory is created by us, so we have to manually take care * that it is initialized. We use the first source directory targeting "/" as the metadata source for @@ -6362,12 +6762,16 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_copy_source), line->source); (void) copy_xattr(sfd, NULL, rfd, NULL, COPY_ALL_XATTRS); - (void) copy_access(sfd, rfd); + if (copy_ownership) + (void) copy_access(sfd, rfd); (void) copy_times(sfd, rfd, 0); break; } + uid_t uid = copy_ownership ? UID_INVALID : getuid(); + gid_t gid = copy_ownership ? GID_INVALID : getgid(); + FOREACH_ARRAY(line, copy_files, n_copy_files) { _cleanup_hashmap_free_ Hashmap *denylist = NULL; _cleanup_hashmap_free_ Hashmap *subvolumes_by_source_inode = NULL; @@ -6424,14 +6828,14 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { r = copy_tree_at( sfd, ".", pfd, fn, - UID_INVALID, GID_INVALID, + uid, gid, line->flags, denylist, subvolumes_by_source_inode); } else r = copy_tree_at( sfd, ".", tfd, ".", - UID_INVALID, GID_INVALID, + uid, gid, line->flags, denylist, subvolumes_by_source_inode); if (r < 0) @@ -6478,7 +6882,8 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { return log_error_errno(r, "Failed to copy '%s' to '%s%s': %m", line->source, strempty(arg_copy_source), line->target); (void) copy_xattr(sfd, NULL, tfd, NULL, COPY_ALL_XATTRS); - (void) copy_access(sfd, tfd); + if (copy_ownership) + (void) copy_access(sfd, tfd); (void) copy_times(sfd, tfd, 0); if (ts != USEC_INFINITY) { @@ -6506,8 +6911,8 @@ static int do_make_directories(Partition *p, const char *root) { if (r < 0) return r; - if (p->suppressing) { - r = shallow_join_strv(&override_dirs, p->make_directories, p->suppressing->make_directories); + if (p->suppressing_supplement) { + r = shallow_join_strv(&override_dirs, p->make_directories, p->supplemented_by->make_directories); if (r < 0) return r; } @@ -6560,8 +6965,8 @@ static int make_subvolumes_read_only(Partition *p, const char *root) { return log_error_errno(r, "Failed to make subvolume '%s' read-only: %m", subvolume->path); } - if (p->suppressing) { - r = make_subvolumes_read_only(p->suppressing, root); + if (p->suppressing_supplement) { + r = make_subvolumes_read_only(p->supplemented_by, root); if (r < 0) return r; } @@ -6757,7 +7162,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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); r = pidref_safe_fork( "(sd-copy)", @@ -6879,142 +7284,277 @@ static int finalize_extra_mkfs_options(const Partition *p, const char *root, cha if (r < 0) return r; - if (p->suppressing) { - r = append_btrfs_subvols(&sv, p->suppressing->subvolumes, NULL); + if (p->suppressing_supplement) { + r = append_btrfs_subvols(&sv, p->supplemented_by->subvolumes, NULL); if (r < 0) return r; - r = append_btrfs_inode_flags(&sv, p->suppressing->subvolumes); + r = append_btrfs_inode_flags(&sv, p->supplemented_by->subvolumes); if (r < 0) return r; } + + if (p->minimize != MINIMIZE_OFF && strv_extend(&sv, "--shrink") < 0) + return log_oom(); } *ret = TAKE_PTR(sv); return 0; } -static int context_mkfs(Context *context) { +static int context_block_device_replace(Context *context) { int r; assert(context); - /* Make a file system */ - LIST_FOREACH(partitions, p, context->partitions) { - _cleanup_(rm_rf_physical_and_freep) char *root = NULL; _cleanup_(partition_target_freep) PartitionTarget *t = NULL; - _cleanup_strv_free_ char **extra_mkfs_options = NULL; if (p->dropped) continue; - if (PARTITION_EXISTS(p)) /* Never format existing partitions */ + if (PARTITION_EXISTS(p)) continue; - if (!p->format) + if (!p->btrfs_replaced) continue; if (partition_defer(context, p)) continue; - /* For offline signing case */ - if (!set_isempty(arg_verity_settings) && partition_designator_is_verity_sig(p->type.designator)) - return partition_format_verity_sig(context, p); - - /* Minimized partitions will use the copy blocks logic so skip those here. */ - if (p->copy_blocks_fd >= 0) - continue; + assert(!p->btrfs_replaced->done); - (void) context_notify(context, PROGRESS_FORMATTING_PARTITION, p->definition_path, UINT_MAX); + (void) context_notify(context, PROGRESS_REPLACING_DEVICE, p->definition_path, UINT_MAX); assert(p->offset != UINT64_MAX); assert(p->new_size != UINT64_MAX); - assert(p->new_size >= (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0)); - /* If we're doing encryption, keep free space at the end which is required - * for cryptsetup's offline encryption. */ r = partition_target_prepare(context, p, - p->new_size - (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0), + p->new_size, /* need_path= */ true, &t); if (r < 0) return r; - if (p->encrypt != ENCRYPT_OFF && t->loop) { - r = partition_target_grow(t, p->new_size); - if (r < 0) - return r; - - r = partition_encrypt(context, p, t, /* offline= */ false); + if (p->encrypt != ENCRYPT_OFF) { + r = partition_encrypt(context, p, t, /* offline= */ false, /* temporary= */ false); if (r < 0) return log_error_errno(r, "Failed to encrypt device: %m"); } - log_info("Formatting future partition %" PRIu64 ".", p->partno); + log_info("Replacing partition %" PRIu64 ".", p->partno); - /* If we're not writing to a loop device or if we're populating a read-only filesystem, we - * have to populate using the filesystem's mkfs's --root= (or equivalent) option. To do that, - * we need to set up the final directory tree beforehand. */ + /* btrfs_replace calls a synchronous ioctl and will return when replace is finished */ + r = btrfs_replace(p->btrfs_replaced->mountpoint_fd, p->btrfs_replaced->devid, partition_target_path(t)); + if (r < 0) + return log_error_errno(r, "Failed to replace btrfs device on partition %" PRIu64 ": %m", p->partno); - if (partition_needs_populate(p) && - (!t->loop || fstype_is_ro(p->format) || (streq_ptr(p->format, "btrfs") && p->compression))) { - if (!mkfs_supports_root_option(p->format)) - return log_error_errno(SYNTHETIC_ERRNO(ENODEV), - "Loop device access is required to populate %s filesystems.", - p->format); + p->btrfs_replaced->done = true; - r = partition_populate_directory(context, p, &root); - if (r < 0) - return r; - } + if (t->decrypted) + t->decrypted->keep = true; - r = finalize_extra_mkfs_options(p, root, &extra_mkfs_options); - if (r < 0) - return r; + log_info("Successfully replaced partition %" PRIu64 ".", p->partno); + } - r = make_filesystem( - partition_target_path(t), - p->format, - strempty(p->new_label), - root, - p->fs_uuid, - partition_mkfs_flags(p), - partition_fs_sector_size(context, p), - p->compression, - p->compression_level, - extra_mkfs_options); - if (r < 0) - return r; + return 0; +} - /* The mkfs binary we invoked might have removed our temporary file when we're not operating - * on a loop device, so open the file again to make sure our file descriptor points to actual - * new file. */ +static void context_btrfs_replace_resize(Context *context) { + int r; - if (t->fd >= 0 && t->path && !t->loop) { - safe_close(t->fd); - t->fd = open(t->path, O_RDWR|O_CLOEXEC); - if (t->fd < 0) + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) { + if (!p->btrfs_replaced) + continue; + + if (!p->btrfs_replaced->done) + continue; + + r = btrfs_resize_max(p->btrfs_replaced->mountpoint_fd, p->btrfs_replaced->devid); + if (r < 0) + log_warning_errno(r, "Could not resize btrfs filesystem moved to partition %" PRIu64 ", proceeding without resizing: %m", p->partno); + else + log_info("Successfully resized partition %" PRIu64 ".", p->partno); + } +} + +static void context_btrfs_replace_back(Context *context) { + int r; + + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) { + if (!p->btrfs_replaced) + continue; + + if (!p->btrfs_replaced->done) + continue; + + r = btrfs_replace(p->btrfs_replaced->mountpoint_fd, p->btrfs_replaced->devid, p->btrfs_replaced->source_path); + if (r < 0) + log_warning_errno(r, "Could not move back btrfs filesystem from partition %" PRIu64 ", leaving it on new device: %m", p->partno); + } +} + +static int check_mkfs(const Context *context) { + int r; + + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) { + if (p->dropped) + continue; + + if (PARTITION_EXISTS(p)) /* Never format existing partitions */ + continue; + + if (!p->format) + continue; + + if (partition_defer(context, p)) + continue; + + /* For offline signing case */ + if (!set_isempty(arg_verity_settings) && partition_designator_is_verity_sig(p->type.designator)) + continue; + + /* Minimized partitions will use the copy blocks logic so skip those here. */ + if (p->copy_blocks_fd >= 0) + continue; + + /* We don't yet quite know if have_root= will be true, so just pass -1 which + * means "not sure". */ + r = mkfs_find_or_warn(p->format, /* have_root= */ -1, /* ret= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int context_mkfs(Context *context) { + int r; + + assert(context); + + /* Make a file system */ + + LIST_FOREACH(partitions, p, context->partitions) { + _cleanup_(rm_rf_physical_and_freep) char *root = NULL; + _cleanup_(partition_target_freep) PartitionTarget *t = NULL; + _cleanup_strv_free_ char **extra_mkfs_options = NULL; + + if (p->dropped) + continue; + + if (PARTITION_EXISTS(p)) /* Never format existing partitions */ + continue; + + if (!p->format) + continue; + + if (partition_defer(context, p)) + continue; + + /* For offline signing case */ + if (!set_isempty(arg_verity_settings) && partition_designator_is_verity_sig(p->type.designator)) + return partition_format_verity_sig(context, p); + + /* Minimized partitions will use the copy blocks logic so skip those here. */ + if (p->copy_blocks_fd >= 0) + continue; + + (void) context_notify(context, PROGRESS_FORMATTING_PARTITION, p->definition_path, UINT_MAX); + + assert(p->offset != UINT64_MAX); + assert(p->new_size != UINT64_MAX); + assert(p->new_size >= (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0)); + + /* If we're doing encryption, keep free space at the end which is required + * for cryptsetup's offline encryption. */ + r = partition_target_prepare(context, p, + p->new_size - (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0), + /* need_path= */ true, + &t); + if (r < 0) + return r; + + if (p->encrypt != ENCRYPT_OFF && (t->loop || t->block_partition)) { + r = partition_target_grow(t, p->new_size); + if (r < 0) + return r; + + r = partition_encrypt(context, p, t, /* offline= */ false, /* temporary= */ true); + if (r < 0) + return log_error_errno(r, "Failed to encrypt device: %m"); + } + + log_info("Formatting future partition %" PRIu64 ".", p->partno); + + /* If we're not writing to a loop device or if we're populating a read-only filesystem, we + * have to populate using the filesystem's mkfs's --root= (or equivalent) option. To do that, + * we need to set up the final directory tree beforehand. */ + + if (partition_needs_populate(p) && + ((!t->loop && !t->block_partition) || fstype_is_ro(p->format) || (streq_ptr(p->format, "btrfs") && p->compression))) { + if (!mkfs_supports_root_option(p->format)) + return log_error_errno(SYNTHETIC_ERRNO(ENODEV), + "Loop device or block partition access is required to populate %s filesystems.", + p->format); + + r = partition_populate_directory(context, p, &root); + if (r < 0) + return r; + } + + r = finalize_extra_mkfs_options(p, root, &extra_mkfs_options); + if (r < 0) + return r; + + r = make_filesystem( + partition_target_path(t), + p->format, + strempty(p->new_label), + root, + p->fs_uuid, + partition_mkfs_flags(p), + partition_fs_sector_size(context, p), + p->compression, + p->compression_level, + extra_mkfs_options); + if (r < 0) + return r; + + /* The mkfs binary we invoked might have removed our temporary file when we're not operating + * on a loop device, so open the file again to make sure our file descriptor points to actual + * new file. */ + + if (t->fd >= 0 && t->path && !t->loop && !t->block_partition) { + safe_close(t->fd); + t->fd = open(t->path, O_RDWR|O_CLOEXEC); + if (t->fd < 0) return log_error_errno(errno, "Failed to reopen temporary file: %m"); } log_info("Successfully formatted future partition %" PRIu64 ".", p->partno); - /* If we're writing to a loop device, we can now mount the empty filesystem and populate it. */ + /* If we're writing to a loop device or BLKPG partition, we can now mount the empty filesystem and populate it. */ if (partition_needs_populate(p) && !root) { - assert(t->loop); + assert(t->loop || t->block_partition); r = partition_populate_filesystem(context, p, partition_target_path(t)); if (r < 0) return r; } - if (p->encrypt != ENCRYPT_OFF && !t->loop) { + if (p->encrypt != ENCRYPT_OFF && !t->loop && !t->block_partition) { r = partition_target_grow(t, p->new_size); if (r < 0) return r; - r = partition_encrypt(context, p, t, /* offline= */ true); + r = partition_encrypt(context, p, t, /* offline= */ true, /* temporary= */ true); if (r < 0) return log_error_errno(r, "Failed to encrypt device: %m"); } @@ -7260,7 +7800,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 +7873,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 +7886,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 +7895,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 +7906,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 +7919,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 +7966,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 +8111,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,17 +8130,404 @@ 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_partscan(Context *context) { + int capable, r; + + assert(context); + + context->needs_rescan = false; + + 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) + return log_error_errno(capable, "Failed to check if block device supports partition scanning: %m"); + else if (capable > 0) { + log_info("Informing kernel about changed partitions..."); + (void) context_notify(context, PROGRESS_REREADING_TABLE, /* object= */ NULL, UINT_MAX); + + 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."); + return 0; } static int context_write_partition_table(Context *context) { _cleanup_(fdisk_unref_tablep) struct fdisk_table *original_table = NULL; - int capable, r; + int r; assert(context); @@ -7638,7 +8565,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"); @@ -7646,44 +8573,93 @@ static int context_write_partition_table(Context *context) { * gaps between partitions, just to be sure. */ r = context_wipe_and_discard(context); if (r < 0) - return r; + goto error; r = context_copy_blocks(context); if (r < 0) - return r; + goto error; r = context_mkfs(context); if (r < 0) - return r; + goto error; + + /* We are now moving destructively btrfs filesystems into the disk before we have written the + * partitions. This is OK because the main use case is that the btrfs filesystems moved are initially + * volatile (in ram disk for example) with little data to save. But we do not want to finish the gpt + * table in case we lose power and reboot and try to boot that incomplete disk. + */ + r = context_block_device_replace(context); + if (r < 0) + goto error; r = context_mangle_partitions(context); if (r < 0) - return r; + goto error; log_info("Writing new partition table."); (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) { + r = log_error_errno(r, "Failed to write partition table: %m"); + goto error; + } + + context_btrfs_replace_resize(context); + + r = context_partscan(context); if (r < 0) - return log_error_errno(r, "Failed to write partition table: %m"); + return r; - capable = blockdev_partscan_enabled_fd(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) - return log_error_errno(capable, "Failed to check if block device supports partition scanning: %m"); - else if (capable > 0) { - log_info("Informing kernel about changed partitions..."); - (void) context_notify(context, PROGRESS_REREADING_TABLE, /* object= */ NULL, UINT_MAX); + log_info("Partition table written."); - r = reread_partition_table_fd(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."); + return 0; + + error: + context_btrfs_replace_back(context); + + if (context->needs_rescan) + (void) context_partscan(context); + + return r; +} + +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("All done."); + 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 +8718,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,10 +8730,13 @@ 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"); + /* We do not want to stop if partscan has errors */ + (void) context_partscan(context); + log_info("Successfully deleted %zu partitions.", n); return 1; } @@ -7814,9 +8793,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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); if (r < 0) - return log_error_errno(r, "Failed to find libblkid: %m"); + return r; b = sym_blkid_new_probe(); if (!b) @@ -8169,9 +9148,6 @@ static int context_open_copy_block_paths( assert(context); - if (!context->partitions) - return 0; - LIST_FOREACH(partitions, p, context->partitions) { _cleanup_close_ int source_fd = -EBADF; _cleanup_free_ char *opened = NULL; @@ -8268,13 +9244,79 @@ static int context_open_copy_block_paths( p->copy_blocks_fd = TAKE_FD(source_fd); p->copy_blocks_size = size; - free_and_replace(p->copy_blocks_path, opened); + free_and_replace(p->copy_blocks_path, opened); + + /* When copying from an existing partition copy that partitions UUID if none is configured explicitly */ + if (!p->new_uuid_is_set && !sd_id128_is_null(uuid)) { + p->new_uuid = uuid; + p->new_uuid_is_set = true; + } + } + + return 0; +} + +static int context_open_btrfs_filesystems(Context *context) { + int r; + + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) { + _cleanup_(btrfs_replacement_freep) BtrfsReplacement *replacement = NULL; + + if (p->dropped) + continue; + + if (PARTITION_EXISTS(p)) + continue; + + if (!p->block_device_replace) + continue; + + if (partition_defer(context, p)) + continue; + + (void) context_notify(context, PROGRESS_OPENING_BLOCK_DEVICE_REPLACE_SOURCES, p->definition_path, UINT_MAX); + + replacement = new(BtrfsReplacement, 1); + if (!replacement) + return log_oom(); + + *replacement = (BtrfsReplacement) { + .mountpoint_fd = -EBADF, + .source_fd = -EBADF, + }; + + replacement->mountpoint_fd = xopenat(AT_FDCWD, p->block_device_replace, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (replacement->mountpoint_fd < 0) + return log_error_errno(replacement->mountpoint_fd, "Failed to open mountpoint %s for btrfs filesystem: %m", p->block_device_replace); + + r = fd_is_fs_type(replacement->mountpoint_fd, BTRFS_SUPER_MAGIC); + if (r < 0) + return log_error_errno(r, "Failed to check filesystem for mountpoint %s: %m", p->block_device_replace); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Mountpoint %s is not a btrfs filesystem", p->block_device_replace); + + r = btrfs_get_block_device_at_full(replacement->mountpoint_fd, "", &replacement->devid, &replacement->source_path, /* ret= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to find device id for btrfs filesystem: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Btrfs filesystem has multiple devices."); + + /* We need to keep the source device open otherwise, it might be collected. */ + replacement->source_fd = open(replacement->source_path, O_RDONLY|O_CLOEXEC); + if (replacement->source_fd < 0) + return log_error_errno(errno, "Failed to open source device %s: %m", replacement->source_path); + + r = fd_verify_block(replacement->source_fd); + if (r < 0) + return log_error_errno(r, "Device %s is not a block device: %m", replacement->source_path); + + r = blockdev_get_device_size(replacement->source_fd, &replacement->source_size); + if (r < 0) + return log_error_errno(r, "Failed to get device size %s: %m", replacement->source_path); - /* When copying from an existing partition copy that partitions UUID if none is configured explicitly */ - if (!p->new_uuid_is_set && !sd_id128_is_null(uuid)) { - p->new_uuid = uuid; - p->new_uuid_is_set = true; - } + p->btrfs_replaced = TAKE_PTR(replacement); } return 0; @@ -8646,6 +9688,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 +9705,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 +9725,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 +9751,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 +9781,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 +9818,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 +9904,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 +9939,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 +10104,341 @@ 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", + }; + + Table *option_tables[ELEMENTSOF(option_groups)] = {}; + CLEANUP_ELEMENTS(option_tables, table_unref_array_clear); + + 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 opts = { argc, argv }; + bool auto_public_key_pcr_mask = true, auto_pcrlock = true; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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=", opts.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(opts.arg)) { arg_empty = EMPTY_UNSET; break; } - arg_empty = empty_mode_from_string(optarg); + arg_empty = empty_mode_from_string(opts.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", opts.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=", opts.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=", opts.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(opts.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(opts.arg, 1024, &arg_grain_size); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse --grain-size= parameter: %s", opts.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(opts.arg); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid architecture '%s'.", opts.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(opts.arg, "auto")) { + arg_size = UINT64_MAX; + arg_size_auto = true; + break; + } + + r = parse_size(opts.arg, 1024, &parsed); if (r < 0) - return r; + return log_error_errno(r, "Failed to parse --size= parameter: %s", opts.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(opts.arg)) { arg_seed = SD_ID128_NULL; arg_randomize = false; - } else if (streq(optarg, "random")) + } else if (streq(opts.arg, "random")) arg_randomize = true; else { - r = sd_id128_from_string(optarg, &arg_seed); + r = sd_id128_from_string(opts.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", opts.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=", opts.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=", opts.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(opts.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=", opts.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(opts.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(opts.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(opts.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(opts.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_SHOW_SYMLINKS| + BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING| + BLOCKDEV_LIST_IGNORE_ZRAM| + BLOCKDEV_LIST_IGNORE_READ_ONLY, + /* 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, opts.arg); + if (r < 0) + return r; + break; + + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - optarg, + opts.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, opts.arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - optarg, + opts.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(opts.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(opts.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(opts.arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(opts.arg, "auto")) { + device = strdup(opts.arg); if (!device) return log_oom(); } @@ -9464,64 +10448,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(opts.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(opts.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", opts.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(opts.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(opts.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(opts.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(opts.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(opts.arg, &arg_filter_partitions, &arg_n_filter_partitions); if (r < 0) return r; @@ -9529,12 +10514,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(opts.arg, &arg_filter_partitions, &arg_n_filter_partitions); if (r < 0) return r; @@ -9542,59 +10528,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(opts.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=", opts.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=", opts.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(opts.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(opts.arg, /* suppress_root= */ false, &p); if (r < 0) return r; @@ -9604,83 +10575,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(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid DDI type: %s", opts.arg); - r = free_and_strdup_warn(&arg_make_ddi, optarg); + r = free_and_strdup_warn(&arg_make_ddi, opts.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(opts.arg)) { arg_append_fstab = APPEND_AUTO; break; } - arg_append_fstab = append_mode_from_string(optarg); + arg_append_fstab = append_mode_from_string(opts.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", opts.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(opts.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(opts.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=", opts.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(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-system=.", opts.arg); + + r = free_and_strdup_warn(&arg_eltorito_system, opts.arg); + if (r < 0) + return r; + + break; - case ARG_JOIN_SIGNATURE: - r = parse_join_signature(optarg, &arg_verity_settings); + OPTION_LONG("el-torito-volume", "STRING", + "Set the volume identifier in the ISO9660 descriptor"): + if (!iso9660_volume_name_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-volume=.", opts.arg); + + r = free_and_strdup_warn(&arg_eltorito_volume, opts.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(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-publisher=.", opts.arg); + + r = free_and_strdup_warn(&arg_eltorito_publisher, opts.arg); + if (r < 0) + return r; - default: - assert_not_reached(); + break; } - if (argc - optind > 1) + if (option_parser_get_n_args(&opts) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected at most one argument, the path to the block device or image file."); @@ -9760,11 +10758,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(&opts); + 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; @@ -9905,13 +10904,21 @@ static int acquire_root_devno( assert(ret); assert(ret_fd); - fd = chase_and_open(p, root, CHASE_PREFIX_ROOT, mode, &found_path); + fd = chase_and_open(p, root, CHASE_PREFIX_ROOT, (mode & ~O_ACCMODE_STRICT) | O_RDONLY, &found_path); if (fd < 0) return fd; if (fstat(fd, &st) < 0) return -errno; + if ((S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)) && (mode & O_ACCMODE_STRICT) != O_RDONLY) { + _cleanup_close_ int new_fd = fd_reopen(fd, mode); + if (new_fd < 0) + return new_fd; + + close_and_replace(fd, new_fd); + } + if (S_ISREG(st.st_mode)) { *ret = TAKE_PTR(found_path); *ret_fd = TAKE_FD(fd); @@ -9971,7 +10978,7 @@ static int acquire_root_devno( static int find_root(Context *context) { _cleanup_free_ char *device = NULL; - int r; + int r, open_flags = O_CLOEXEC|context_open_mode(context); assert(context); @@ -9987,9 +10994,9 @@ static int find_root(Context *context) { if (!s) return log_oom(); - fd = xopenat_full(AT_FDCWD, arg_node, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOFOLLOW, XO_NOCOW, 0666); + fd = xopenat_full(AT_FDCWD, arg_node, open_flags|O_CREAT|O_EXCL|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; @@ -9999,7 +11006,7 @@ static int find_root(Context *context) { /* Note that we don't specify a root argument here: if the user explicitly configured a node * we'll take it relative to the host, not the image */ - r = acquire_root_devno(arg_node, NULL, O_RDONLY|O_CLOEXEC, &context->node, &context->backing_fd); + r = acquire_root_devno(arg_node, NULL, open_flags, &context->node, &context->backing_fd); if (r == -EUCLEAN) return btrfs_log_dev_root(LOG_ERR, r, arg_node); if (r < 0) @@ -10021,7 +11028,7 @@ static int find_root(Context *context) { FOREACH_STRING(p, "/", "/usr") { - r = acquire_root_devno(p, arg_root, O_RDONLY|O_DIRECTORY|O_CLOEXEC, &context->node, + r = acquire_root_devno(p, arg_root, open_flags|O_DIRECTORY, &context->node, &context->backing_fd); if (r < 0) { if (r == -EUCLEAN) @@ -10034,7 +11041,7 @@ static int find_root(Context *context) { } else if (r < 0) return log_error_errno(r, "Failed to read symlink /run/systemd/volatile-root: %m"); else { - r = acquire_root_devno(device, NULL, O_RDONLY|O_CLOEXEC, &context->node, &context->backing_fd); + r = acquire_root_devno(device, /* root= */ NULL, open_flags, &context->node, &context->backing_fd); if (r == -EUCLEAN) return btrfs_log_dev_root(LOG_ERR, r, device); if (r < 0) @@ -10058,7 +11065,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 +11073,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"); @@ -10265,6 +11272,57 @@ static int determine_auto_size( return 0; } +static void context_sort_partitions(Context *context) { + assert(context); + + Partition *p; + LIST_HEAD(Partition, new_partitions) = NULL; + LIST_HEAD(Partition, existing_partitions) = NULL; + LIST_HEAD(Partition, dropped_partitions) = NULL; + + while ((p = LIST_POP(partitions, context->partitions))) { + if (p->allocated_to_area) + LIST_APPEND(partitions, new_partitions, p); + else if (p->dropped) + LIST_APPEND(partitions, dropped_partitions, p); + else + LIST_APPEND(partitions, existing_partitions, p); + } + + /* First sort existing partitions by their offset */ + while ((p = LIST_POP(partitions, existing_partitions))) { + Partition *cursor = NULL; + + assert(p->offset != UINT64_MAX); + + LIST_FOREACH(partitions, q, context->partitions) + if (p->offset > q->offset) + cursor = q; + + LIST_INSERT_AFTER(partitions, context->partitions, cursor, p); + } + + /* Then insert the partitions we'll newly create in the free areas */ + while ((p = LIST_POP(partitions, new_partitions))) { + FreeArea *a = p->allocated_to_area; + Partition *cursor = a->after; + + /* Advance past partitions of this area that we already inserted */ + LIST_FOREACH(partitions, q, context->partitions) + if (q->allocated_to_area == a) + cursor = q; + + LIST_INSERT_AFTER(partitions, context->partitions, cursor, p); + } + + /* Finally append any dropped partitions to the end of the list */ + while ((p = LIST_POP(partitions, dropped_partitions))) { + assert(p->offset == UINT64_MAX); + + LIST_APPEND(partitions, context->partitions, p); + } +} + static int context_ponder(Context *context) { int r; @@ -10309,6 +11367,10 @@ static int context_ponder(Context *context) { p->definition_path, p->supplement_for->definition_path); } + /* Now that we know which new partition goes into which free area, reorder + * the partitions list so that the list is in the right order. */ + context_sort_partitions(context); + /* Now assign free space according to the weight logic */ r = context_grow_partitions(context); if (r < 0) @@ -10320,68 +11382,6 @@ static int context_ponder(Context *context) { return 0; } -static int vl_method_list_candidate_devices( - sd_varlink *link, - sd_json_variant *parameters, - sd_varlink_method_flags_t flags, - void *userdata) { - - struct { - bool ignore_root; - bool ignore_empty; - } p = {}; - - static const sd_json_dispatch_field dispatch_table[] = { - { "ignoreRoot", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, voffsetof(p, ignore_root), 0 }, - { "ignoreEmpty", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, voffsetof(p, ignore_empty), 0 }, - {} - }; - - int r; - - assert(link); - assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); - - r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); - if (r != 0) - return r; - - BlockDevice *l = NULL; - size_t n = 0; - CLEANUP_ARRAY(l, n, block_device_array_free); - - r = blockdev_list( - BLOCKDEV_LIST_SHOW_SYMLINKS| - BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING| - BLOCKDEV_LIST_IGNORE_ZRAM| - BLOCKDEV_LIST_METADATA| - (p.ignore_empty ? BLOCKDEV_LIST_IGNORE_EMPTY : 0)| - (p.ignore_root ? BLOCKDEV_LIST_IGNORE_ROOT : 0), - &l, - &n); - if (r < 0) - return r; - - r = varlink_set_sentinel(link, "io.systemd.Repart.NoCandidateDevices"); - if (r < 0) - return r; - - FOREACH_ARRAY(d, l, n) { - r = sd_varlink_replybo(link, - SD_JSON_BUILD_PAIR_STRING("node", d->node), - JSON_BUILD_PAIR_STRV_NON_EMPTY("symlinks", d->symlinks), - JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("diskseq", d->diskseq, UINT64_MAX), - JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("sizeBytes", d->size, UINT64_MAX), - JSON_BUILD_PAIR_STRING_NON_EMPTY("model", d->model), - JSON_BUILD_PAIR_STRING_NON_EMPTY("vendor", d->vendor), - JSON_BUILD_PAIR_STRING_NON_EMPTY("subsystem", d->subsystem)); - if (r < 0) - return r; - } - - return 0; -} - static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_empty_mode, EmptyMode, empty_mode_from_string); typedef struct RunParameters { @@ -10408,13 +11408,13 @@ static int vl_method_run( void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { - { "node", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(RunParameters, node), SD_JSON_NULLABLE }, - { "empty", SD_JSON_VARIANT_STRING, json_dispatch_empty_mode, offsetof(RunParameters, empty), SD_JSON_MANDATORY }, - { "seed", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(RunParameters, seed), SD_JSON_NULLABLE }, - { "dryRun", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, dry_run), SD_JSON_MANDATORY }, - { "definitions", SD_JSON_VARIANT_ARRAY, json_dispatch_strv_path, offsetof(RunParameters, definitions), SD_JSON_MANDATORY|SD_JSON_STRICT }, - { "deferPartitionsEmpty", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, defer_partitions_empty), SD_JSON_NULLABLE }, - { "deferPartitionsFactoryReset", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, defer_partitions_factory_reset), SD_JSON_NULLABLE }, + { "node", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(RunParameters, node), SD_JSON_NULLABLE }, + { "empty", SD_JSON_VARIANT_STRING, json_dispatch_empty_mode, offsetof(RunParameters, empty), SD_JSON_MANDATORY }, + { "seed", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(RunParameters, seed), SD_JSON_NULLABLE }, + { "dryRun", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, dry_run), SD_JSON_MANDATORY }, + { "definitions", SD_JSON_VARIANT_ARRAY, json_dispatch_strv_path, offsetof(RunParameters, definitions), SD_JSON_STRICT }, + { "deferPartitionsEmpty", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, defer_partitions_empty), SD_JSON_NULLABLE }, + { "deferPartitionsFactoryReset", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, defer_partitions_factory_reset), SD_JSON_NULLABLE }, {} }; @@ -10458,7 +11458,9 @@ static int vl_method_run( return r; if (p.node) { - context->node = TAKE_PTR(p.node); + r = acquire_root_devno(p.node, NULL, O_CLOEXEC|context_open_mode(context), &context->node, &context->backing_fd); + if (r < 0) + return log_error_errno(r, "Failed to open file or determine backing device of %s: %m", p.node); r = context_load_partition_table(context); if (r == -EHWPOISON) @@ -10473,6 +11475,10 @@ static int vl_method_run( if (r < 0) return r; + r = context_open_btrfs_filesystems(context); + if (r < 0) + return r; + r = context_acquire_partition_uuids_and_labels(context); if (r < 0) return r; @@ -10498,6 +11504,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 +11556,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); @@ -10557,10 +11571,9 @@ static int vl_server(void) { /* Invocation as Varlink service */ - r = varlink_server_new( - &varlink_server, - SD_VARLINK_SERVER_ROOT_ONLY, - /* userdata= */ NULL); + r = varlink_server_new(&varlink_server, + SD_VARLINK_SERVER_ROOT_ONLY | SD_VARLINK_SERVER_MYSELF_ONLY, + /* userdata= */ NULL); if (r < 0) return log_error_errno(r, "Failed to allocate Varlink server: %m"); @@ -10595,9 +11608,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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; if (arg_varlink) return vl_server(); @@ -10726,6 +11739,10 @@ static int run(int argc, char *argv[]) { return r; context->from_scratch = r > 0; /* Starting from scratch */ + r = check_mkfs(context); + if (r < 0) + return r; + if (arg_can_factory_reset) { r = context_can_factory_reset(context); if (r < 0) @@ -10763,6 +11780,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = context_open_btrfs_filesystems(context); + if (r < 0) + return r; + /* Make sure each partition has a unique UUID and unique label */ r = context_acquire_partition_uuids_and_labels(context); if (r < 0) @@ -10812,6 +11833,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 +11852,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..d0a316c6897d2 100644 --- a/src/report/meson.build +++ b/src/report/meson.build @@ -4,6 +4,50 @@ executables += [ libexec_template + { 'name' : 'systemd-report', 'public' : true, - 'sources' : files('report.c'), + 'sources' : files( + 'report.c', + 'report-generate.c', + 'report-sign.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', + ), + }, + libexec_template + { + 'name' : 'systemd-report-files', + 'sources' : files( + 'report-files.c', + 'report-files-server.c', + ), + }, + libexec_template + { + 'name' : 'systemd-report-sign-plain', + 'conditions' : [ + 'HAVE_OPENSSL', + ], + 'sources' : files( + 'report-sign-plain.c', + ), + 'dependencies' : libopenssl_cflags, + }, + libexec_template + { + 'name' : 'systemd-report-sign-tsm', + 'sources' : files( + 'report-sign-tsm.c', + ), }, ] diff --git a/src/report/report-basic-server.c b/src/report/report-basic-server.c new file mode 100644 index 0000000000000..e65481a190ebe --- /dev/null +++ b/src/report/report-basic-server.c @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink.h" + +#include "ansi-color.h" +#include "build.h" +#include "format-table.h" +#include "log.h" +#include "main-func.h" +#include "options.h" +#include "report-basic.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; + 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 = 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_(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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + } + + if (option_parser_get_n_args(&opts) > 0) + 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..56f7065c68d31 --- /dev/null +++ b/src/report/report-basic.c @@ -0,0 +1,686 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "sd-device.h" +#include "sd-id128.h" +#include "sd-json.h" +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "architecture.h" +#include "confidential-virt.h" +#include "cpu-set-util.h" +#include "env-file.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hostname-setup.h" +#include "hostname-util.h" +#include "limits-util.h" +#include "log.h" +#include "metrics.h" +#include "os-util.h" +#include "report-basic.h" +#include "string-util.h" +#include "virt.h" + +static int architecture_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + assert(mf && mf->name); + assert(link); + + return metric_build_send_string( + mf, + link, + /* object= */ NULL, + architecture_to_string(uname_architecture()), + /* fields= */ NULL); +} + +static int boot_id_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + sd_id128_t id; + int r; + + assert(mf && mf->name); + assert(link); + + r = sd_id128_get_boot(&id); + if (r < 0) + return r; + + return metric_build_send_string( + mf, + link, + /* object= */ NULL, + SD_ID128_TO_STRING(id), + /* fields= */ NULL); +} + +static int hostname_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + _cleanup_free_ char *hostname = NULL; + int r; + + assert(mf && mf->name); + assert(link); + + r = gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT, &hostname); + if (r < 0) + return r; + + return metric_build_send_string( + mf, + link, + /* object= */ NULL, + hostname, + /* fields= */ NULL); +} + +static int kernel_version_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + struct utsname u; + + assert(mf && mf->name); + assert(link); + + assert_se(uname(&u) >= 0); + + return metric_build_send_string( + mf, + link, + /* object= */ NULL, + u.release, + /* fields= */ NULL); +} + +static int machine_id_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + sd_id128_t id; + int r; + + assert(mf && mf->name); + assert(link); + + r = sd_id128_get_machine(&id); + if (r < 0) + return r; + + return metric_build_send_string( + mf, + link, + /* object= */ NULL, + SD_ID128_TO_STRING(id), + /* fields= */ NULL); +} + +static int physical_memory_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + assert(mf && mf->name); + assert(link); + + return metric_build_send_unsigned( + mf, + link, + /* object= */ NULL, + physical_memory(), + /* fields= */ NULL); +} + +static int cpus_online_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + int r; + + assert(mf && mf->name); + assert(link); + + unsigned n_cpus; + r = cpus_online(&n_cpus); + if (r < 0) + return r; + + return metric_build_send_unsigned( + mf, + link, + /* object= */ NULL, + n_cpus, + /* fields= */ NULL); +} + +static int load_average_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + enum { + LOAD_AVERAGE_FIELD_1MIN, + LOAD_AVERAGE_FIELD_5MIN, + LOAD_AVERAGE_FIELD_15MIN, + _LOAD_AVERAGE_FIELD_MAX, + }; + + int r; + + assert(mf && mf->name); + assert(link); + + /* The classic Linux load average, i.e. the exponentially damped moving average of the number of + * runnable plus uninterruptible tasks over the last 1, 5 and 15 minutes. The kernel exposes these as + * fixed-point numbers shifted left by SI_LOAD_SHIFT bits. */ + + struct sysinfo info; + if (sysinfo(&info) < 0) + return log_debug_errno(errno, "Failed to call sysinfo(): %m"); + + assert_cc(_LOAD_AVERAGE_FIELD_MAX == ELEMENTSOF(info.loads)); + + for (size_t i = 0; i < _LOAD_AVERAGE_FIELD_MAX; i++) { + + r = metric_build_send_double( + mf + i, + link, + /* object= */ NULL, + (double) info.loads[i] / (UINT64_C(1) << SI_LOAD_SHIFT), + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int swap_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + assert(mf && mf->name); + assert(link); + + /* The total amount of configured swap space, in bytes. */ + + struct sysinfo info; + if (sysinfo(&info) < 0) + return log_debug_errno(errno, "Failed to call sysinfo(): %m"); + + /* Overflow is unrealistic (would need >16 EiB of swap), but use MUL_SAFE to make this obvious to + * static analyzers. */ + uint64_t swap; + assert_se(MUL_SAFE(&swap, (uint64_t) info.totalswap, (uint64_t) info.mem_unit)); + + return metric_build_send_unsigned( + mf, + link, + /* object= */ NULL, + swap, + /* fields= */ NULL); +} + +enum { + FIELD_PRETTY_NAME, + FIELD_NAME, + FIELD_ID, + FIELD_CPE_NAME, + FIELD_VARIANT_ID, + FIELD_VERSION_ID, + FIELD_BUILD_ID, + FIELD_IMAGE_VERSION, + FIELD_IMAGE_ID, + FIELD_SUPPORT_END, + FIELD_EXPERIMENT, + FIELD_SYSEXT_LEVEL, + FIELD_CONFEXT_LEVEL, + _FIELD_MAX, +}; + +static int os_release_generate(const MetricFamily mf[static _FIELD_MAX - 1], sd_varlink *link, void *userdata) { + char* values[_FIELD_MAX] = {}; + CLEANUP_ELEMENTS(values, free_many_charp); + int r; + + assert(mf && mf->name); + assert(link); + + r = parse_os_release(NULL, + "PRETTY_NAME", &values[FIELD_PRETTY_NAME], + "NAME", &values[FIELD_NAME], + "ID", &values[FIELD_ID], + "CPE_NAME", &values[FIELD_CPE_NAME], + "VARIANT_ID", &values[FIELD_VARIANT_ID], + "VERSION_ID", &values[FIELD_VERSION_ID], + "BUILD_ID", &values[FIELD_BUILD_ID], + "IMAGE_VERSION", &values[FIELD_IMAGE_VERSION], + "IMAGE_ID", &values[FIELD_IMAGE_ID], + "SUPPORT_END", &values[FIELD_SUPPORT_END], + "EXPERIMENT", &values[FIELD_EXPERIMENT], + "SYSEXT_LEVEL", &values[FIELD_SYSEXT_LEVEL], + "CONFEXT_LEVEL", &values[FIELD_CONFEXT_LEVEL]); + if (r < 0) { + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to read os-release file, ignoring: %m"); + return 0; + } + + for (size_t i = 1; i < _FIELD_MAX; i++) { + const char *v = values[i]; + if (i == FIELD_NAME && values[FIELD_PRETTY_NAME]) + v = values[FIELD_PRETTY_NAME]; /* Prefer PRETTY_NAME to NAME */ + + if (v) { + r = metric_build_send_string( + mf + i - 1, + link, + /* object= */ NULL, + v, + /* fields= */ NULL); + if (r < 0) + return r; + } + } + + return 0; +} + +static int machine_info_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + enum { + MACHINE_INFO_FIELD_PRETTY_HOSTNAME, + MACHINE_INFO_FIELD_DEPLOYMENT, + MACHINE_INFO_FIELD_LOCATION, + MACHINE_INFO_FIELD_TAGS, + _MACHINE_INFO_FIELD_MAX, + }; + + char* values[_MACHINE_INFO_FIELD_MAX] = {}; + CLEANUP_ELEMENTS(values, free_many_charp); + int r; + + assert(mf && mf->name); + assert(link); + + r = parse_env_file(/* f= */ NULL, etc_machine_info(), + "PRETTY_HOSTNAME", &values[MACHINE_INFO_FIELD_PRETTY_HOSTNAME], + "DEPLOYMENT", &values[MACHINE_INFO_FIELD_DEPLOYMENT], + "LOCATION", &values[MACHINE_INFO_FIELD_LOCATION], + "TAGS", &values[MACHINE_INFO_FIELD_TAGS]); + if (r < 0) { + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to read machine-info file, ignoring: %m"); + return 0; + } + + for (size_t i = 0; i < _MACHINE_INFO_FIELD_MAX; i++) { + const char *v = values[i]; + if (!v) + continue; + + r = metric_build_send_string( + mf + i, + link, + /* object= */ NULL, + v, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int smbios_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + enum { + SMBIOS_FIELD_SYS_VENDOR, + SMBIOS_FIELD_PRODUCT_NAME, + SMBIOS_FIELD_PRODUCT_VERSION, + SMBIOS_FIELD_PRODUCT_SKU, + SMBIOS_FIELD_PRODUCT_FAMILY, + SMBIOS_FIELD_PRODUCT_SERIAL, + SMBIOS_FIELD_PRODUCT_UUID, + SMBIOS_FIELD_BOARD_VENDOR, + SMBIOS_FIELD_BOARD_NAME, + SMBIOS_FIELD_BOARD_VERSION, + SMBIOS_FIELD_BOARD_SERIAL, + SMBIOS_FIELD_BOARD_ASSET_TAG, + SMBIOS_FIELD_BIOS_VENDOR, + SMBIOS_FIELD_BIOS_VERSION, + SMBIOS_FIELD_BIOS_DATE, + SMBIOS_FIELD_CHASSIS_TYPE, + SMBIOS_FIELD_CHASSIS_VENDOR, + SMBIOS_FIELD_CHASSIS_SERIAL, + SMBIOS_FIELD_CHASSIS_ASSET_TAG, + _SMBIOS_FIELD_MAX, + }; + + /* The sysfs attribute names exposed by the kernel below /sys/class/dmi/id/. The order must match the + * SMBIOS_STANDARD_FIELD() entries in the metric family table below. */ + static const char* const smbios_files[_SMBIOS_FIELD_MAX] = { + /* SMBIOS Type 1 */ + [SMBIOS_FIELD_SYS_VENDOR] = "sys_vendor", + [SMBIOS_FIELD_PRODUCT_NAME] = "product_name", + [SMBIOS_FIELD_PRODUCT_VERSION] = "product_version", + [SMBIOS_FIELD_PRODUCT_SKU] = "product_sku", + [SMBIOS_FIELD_PRODUCT_FAMILY] = "product_family", + [SMBIOS_FIELD_PRODUCT_SERIAL] = "product_serial", + [SMBIOS_FIELD_PRODUCT_UUID] = "product_uuid", + /* SMBIOS Type 2 */ + [SMBIOS_FIELD_BOARD_VENDOR] = "board_vendor", + [SMBIOS_FIELD_BOARD_NAME] = "board_name", + [SMBIOS_FIELD_BOARD_VERSION] = "board_version", + [SMBIOS_FIELD_BOARD_SERIAL] = "board_serial", + [SMBIOS_FIELD_BOARD_ASSET_TAG] = "board_asset_tag", + /* SMBIOS Type 0 */ + [SMBIOS_FIELD_BIOS_VENDOR] = "bios_vendor", + [SMBIOS_FIELD_BIOS_VERSION] = "bios_version", + [SMBIOS_FIELD_BIOS_DATE] = "bios_date", + /* SMBIOS Type 3 */ + [SMBIOS_FIELD_CHASSIS_TYPE] = "chassis_type", + [SMBIOS_FIELD_CHASSIS_VENDOR] = "chassis_vendor", + [SMBIOS_FIELD_CHASSIS_SERIAL] = "chassis_serial", + [SMBIOS_FIELD_CHASSIS_ASSET_TAG] = "chassis_asset_tag", + }; + + int r; + + assert(mf && mf->name); + assert(link); + + /* Reports the fundamental SMBIOS/DMI identification fields. Some of these (serial numbers, asset + * tags, the system UUID) are privacy sensitive and only readable by root — if we lack the + * privileges to read them we simply skip them. */ + + _cleanup_close_ int dir_fd = open("/sys/class/dmi/id", O_RDONLY|O_DIRECTORY|O_CLOEXEC); + if (dir_fd < 0) { + log_full_errno(ERRNO_IS_DEVICE_ABSENT(errno) ? LOG_DEBUG : LOG_WARNING, errno, + "Failed to open /sys/class/dmi/id/, ignoring: %m"); + return 0; + } + + for (size_t i = 0; i < _SMBIOS_FIELD_MAX; i++) { + _cleanup_free_ char *buf = NULL; + + r = read_virtual_file_at(dir_fd, smbios_files[i], /* max_size= */ SIZE_MAX, &buf, /* ret_size= */ NULL); + if (r < 0) { + log_full_errno(r == -ENOENT || ERRNO_IS_NEG_PRIVILEGE(r) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to read SMBIOS field '%s', ignoring: %m", smbios_files[i]); + continue; + } + + delete_trailing_chars(buf, NEWLINE); + + if (isempty(buf)) + continue; + + if (!string_is_safe(buf, STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS)) { + log_debug("SMBIOS field '%s' contains unsafe characters, ignoring.", smbios_files[i]); + continue; + } + + r = metric_build_send_string( + mf + i, + link, + /* object= */ NULL, + buf, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int tpm2_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + enum { + TPM2_FIELD_MANUFACTURER, + TPM2_FIELD_VENDOR_STRING, + _TPM2_FIELD_MAX, + }; + + /* The udev properties set by the 'tpm2_id' builtin on the tpmrm device. The order must match the + * metric family table entries below. */ + static const char* const tpm2_properties[_TPM2_FIELD_MAX] = { + [TPM2_FIELD_MANUFACTURER] = "ID_TPM2_MANUFACTURER", + [TPM2_FIELD_VENDOR_STRING] = "ID_TPM2_VENDOR_STRING", + }; + + int r; + + assert(mf && mf->name); + assert(link); + + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + r = sd_device_new_from_subsystem_sysname(&dev, "tpmrm", "tpmrm0"); + if (r < 0) { + log_full_errno(ERRNO_IS_NEG_DEVICE_ABSENT(r) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to open tpmrm0 device, ignoring: %m"); + return 0; + } + + for (size_t i = 0; i < _TPM2_FIELD_MAX; i++) { + const char *v; + + r = sd_device_get_property_value(dev, tpm2_properties[i], &v); + if (r < 0) { + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to read TPM2 property '%s', ignoring: %m", tpm2_properties[i]); + continue; + } + + if (isempty(v)) + continue; + + r = metric_build_send_string( + mf + i, + link, + /* object= */ NULL, + v, + /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int confidential_virtualization_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + assert(mf && mf->name); + assert(link); + + ConfidentialVirtualization cv = detect_confidential_virtualization(); + if (cv < 0) + return cv; + + return metric_build_send_string( + mf, + link, + /* object= */ NULL, + confidential_virtualization_to_string(cv), + /* fields= */ NULL); +} + +static int virtualization_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + assert(mf && mf->name); + assert(link); + + Virtualization v = detect_virtualization(); + if (v < 0) + return v; + + return metric_build_send_string( + mf, + link, + /* object= */ NULL, + virtualization_to_string(v), + /* fields= */ NULL); +} + +#define OS_RELEASE_STANDARD_FIELD(name) \ + { \ + METRIC_IO_SYSTEMD_BASIC_PREFIX "OSRelease." name, \ + "Operating system identification (" name "= field from os-release)", \ + METRIC_FAMILY_TYPE_STRING, \ + .generate = NULL, \ + } + +#define MACHINE_INFO_STANDARD_FIELD(name) \ + { \ + METRIC_IO_SYSTEMD_BASIC_PREFIX "MachineInfo." name, \ + "Machine identification (" name "= field from machine-info)", \ + METRIC_FAMILY_TYPE_STRING, \ + .generate = NULL, \ + } + +#define SMBIOS_STANDARD_FIELD(name) \ + { \ + METRIC_IO_SYSTEMD_BASIC_PREFIX "SMBIOS." name, \ + "Firmware/hardware identification (" name " field from SMBIOS/DMI)", \ + METRIC_FAMILY_TYPE_STRING, \ + .generate = NULL, \ + } + +static const MetricFamily metric_family_table[] = { + /* Keep entries ordered alphabetically */ + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "Architecture", + "CPU architecture", + METRIC_FAMILY_TYPE_STRING, + .generate = architecture_generate, + }, + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "BootID", + "Current boot ID", + METRIC_FAMILY_TYPE_STRING, + .generate = boot_id_generate, + }, + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "ConfidentialVirtualization", + "Confidential computing technology", + METRIC_FAMILY_TYPE_STRING, + .generate = confidential_virtualization_generate, + }, + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "CPUsOnline", + "Number of CPUs currently online", + METRIC_FAMILY_TYPE_GAUGE, + .generate = cpus_online_generate, + }, + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "Hostname", + "System hostname", + METRIC_FAMILY_TYPE_STRING, + .generate = hostname_generate, + }, + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "KernelVersion", + "Kernel version", + METRIC_FAMILY_TYPE_STRING, + .generate = kernel_version_generate, + }, + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "LoadAverage1Min", + "System load average over the last 1 minute", + METRIC_FAMILY_TYPE_GAUGE, + .generate = load_average_generate, + }, + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "LoadAverage5Min", + "System load average over the last 5 minutes", + METRIC_FAMILY_TYPE_GAUGE, + .generate = NULL, + }, + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "LoadAverage15Min", + "System load average over the last 15 minutes", + METRIC_FAMILY_TYPE_GAUGE, + .generate = NULL, + }, + /* Keep those ↑ in sync with load_average_generate(). */ + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "MachineID", + "Machine ID", + METRIC_FAMILY_TYPE_STRING, + .generate = machine_id_generate, + }, + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "MachineInfo.PRETTY_HOSTNAME", + "Pretty hostname (PRETTY_HOSTNAME= field from machine-info)", + METRIC_FAMILY_TYPE_STRING, + .generate = machine_info_generate, + }, + MACHINE_INFO_STANDARD_FIELD("DEPLOYMENT"), + MACHINE_INFO_STANDARD_FIELD("LOCATION"), + MACHINE_INFO_STANDARD_FIELD("TAGS"), + /* Keep those ↑ in sync with machine_info_generate(). */ + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "OSRelease.NAME", + "Operating system human-readable name (PRETTY_NAME= or NAME= field from os-release)", + METRIC_FAMILY_TYPE_STRING, + .generate = os_release_generate, + }, + OS_RELEASE_STANDARD_FIELD("ID"), + OS_RELEASE_STANDARD_FIELD("CPE_NAME"), + OS_RELEASE_STANDARD_FIELD("VARIANT_ID"), + OS_RELEASE_STANDARD_FIELD("VERSION_ID"), + OS_RELEASE_STANDARD_FIELD("BUILD_ID"), + OS_RELEASE_STANDARD_FIELD("IMAGE_VERSION"), + OS_RELEASE_STANDARD_FIELD("IMAGE_ID"), + OS_RELEASE_STANDARD_FIELD("SUPPORT_END"), + OS_RELEASE_STANDARD_FIELD("EXPERIMENT"), + OS_RELEASE_STANDARD_FIELD("SYSEXT_LEVEL"), + OS_RELEASE_STANDARD_FIELD("CONFEXT_LEVEL"), + /* Keep those ↑ in sync with os_release_generate(). */ + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "PhysicalMemoryBytes", + "Installed physical memory in bytes", + METRIC_FAMILY_TYPE_GAUGE, + .generate = physical_memory_generate, + }, + { + /* NB: Here we use the naming of the field as per SMBIOS specification, i.e. undo the weird + * renaming that Linux did on the fields. When new fields are added here, please make sure to + * check the specification again for naming them. */ + METRIC_IO_SYSTEMD_BASIC_PREFIX "SMBIOS.SystemManufacturer", + "Firmware/hardware identification (SystemManufacturer field from SMBIOS/DMI)", + METRIC_FAMILY_TYPE_STRING, + .generate = smbios_generate, + }, + SMBIOS_STANDARD_FIELD("SystemProductName"), + SMBIOS_STANDARD_FIELD("SystemVersion"), + SMBIOS_STANDARD_FIELD("SystemSKUNumber"), + SMBIOS_STANDARD_FIELD("SystemFamily"), + SMBIOS_STANDARD_FIELD("SystemSerialNumber"), + SMBIOS_STANDARD_FIELD("SystemUUID"), + SMBIOS_STANDARD_FIELD("BaseBoardManufacturer"), + SMBIOS_STANDARD_FIELD("BaseBoardProduct"), + SMBIOS_STANDARD_FIELD("BaseBoardVersion"), + SMBIOS_STANDARD_FIELD("BaseBoardSerial"), + SMBIOS_STANDARD_FIELD("BaseBoardAssetTag"), + SMBIOS_STANDARD_FIELD("FirmwareVendor"), + SMBIOS_STANDARD_FIELD("FirmwareVersion"), + SMBIOS_STANDARD_FIELD("FirmwareReleaseDate"), + SMBIOS_STANDARD_FIELD("ChassisType"), + SMBIOS_STANDARD_FIELD("ChassisManufacturer"), + SMBIOS_STANDARD_FIELD("ChassisSerialNumber"), + SMBIOS_STANDARD_FIELD("ChassisAssetTagNumber"), + /* Keep those ↑ in sync with smbios_generate(). */ + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "SwapBytes", + "Total configured swap space in bytes", + METRIC_FAMILY_TYPE_GAUGE, + .generate = swap_generate, + }, + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "TPM2.Manufacturer", + "TPM2 device manufacturer (ID_TPM2_MANUFACTURER property of the tpmrm0 device)", + METRIC_FAMILY_TYPE_STRING, + .generate = tpm2_generate, + }, + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "TPM2.VendorString", + "TPM2 device vendor string (ID_TPM2_VENDOR_STRING property of the tpmrm0 device)", + METRIC_FAMILY_TYPE_STRING, + .generate = NULL, + }, + /* Keep those ↑ in sync with tpm2_generate(). */ + { + METRIC_IO_SYSTEMD_BASIC_PREFIX "Virtualization", + "Virtualization type", + METRIC_FAMILY_TYPE_STRING, + .generate = virtualization_generate, + }, + {} +}; + +int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return metrics_method_describe(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) { + return metrics_method_list(metric_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..8f123cb17fe3e --- /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 METRIC_IO_SYSTEMD_BASIC_PREFIX "io.systemd.Basic." + +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-cgroup-server.c b/src/report/report-cgroup-server.c new file mode 100644 index 0000000000000..279edbb8d13ed --- /dev/null +++ b/src/report/report-cgroup-server.c @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink.h" + +#include "build.h" +#include "format-table.h" +#include "help-util.h" +#include "log.h" +#include "main-func.h" +#include "options.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; + int r; + + r = varlink_server_new(&vs, SD_VARLINK_SERVER_INHERIT_USERDATA, /* userdata= */ NULL); + 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_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...]"); + help_abstract("Report cgroup metrics."); + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-report-cgroup", "8"); + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + } + + if (option_parser_get_n_args(&opts) > 0) + 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..240f09f9f2a4f --- /dev/null +++ b/src/report/report-cgroup.c @@ -0,0 +1,402 @@ +/* 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 "errno-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" + +/* Parse cpu.stat for a cgroup once, extracting usage_usec, user_usec and system_usec + * in a single read so each scrape only opens the file once per cgroup. */ +static int cpu_stat_parse(const char *cgroup_path, uint64_t ret[static 3]) { + char* strings[3] = {}; + CLEANUP_ELEMENTS(strings, free_many_charp); + uint64_t values[3]; + int r; + + assert(cgroup_path); + + r = cg_get_keyed_attribute( + cgroup_path, + "cpu.stat", + STRV_MAKE("usage_usec", "user_usec", "system_usec"), + strings); + if (r < 0) + return r; + + for (unsigned i = 0; i < 3; i++) { + r = safe_atou64(strings[i], &values[i]); + if (r < 0) + return r; + } + + for (unsigned i = 0; i < 3; i++) + ret[i] = values[i] * NSEC_PER_USEC; + return 0; +} + +static int cpu_usage_send( + const MetricFamily *mf, + sd_varlink *link, + const char *path, + const char *unit) { + + static const char* const types[] = { "total", "user", "system" }; + uint64_t values[3]; + int r; + + assert(mf && mf->name); + assert(link); + assert(path); + assert(unit); + + r = cpu_stat_parse(path, values); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Failed to read %s/%s, ignoring: %m", path, "cpu.stat"); + return 0; + } + + for (unsigned i = 0; i < 3; i++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; + + r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", types[i])); + if (r < 0) + return r; + + r = metric_build_send_unsigned(mf, link, unit, values[i], 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[static 2]) { + _cleanup_free_ char *path = NULL; + uint64_t rbytes = 0, rios = 0; + int r; + + assert(ret); + + r = cg_get_path(cgroup_path, "io.stat", &path); + if (r < 0) + return r; + + _cleanup_fclose_ FILE *f = fopen(path, "re"); + if (!f) + return errno_or_else(EIO); + + 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[0] = rbytes; + ret[1] = rios; + return 0; +} + +static int io_read_send( + const MetricFamily mf[static 2], + sd_varlink *link, + const char *path, + const char *unit) { + + uint64_t values[2]; + int r; + + assert(mf && mf[0].name && mf[1].name); + assert(link); + assert(path); + assert(unit); + + r = io_stat_parse(path, values); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Failed to read %s/%s, ignoring: %m", path, "io.stat"); + return 0; + } + + for (unsigned i = 0; i < 2; i++) { + r = metric_build_send_unsigned(mf + i, link, unit, values[i], /* fields= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + +static int memory_usage_send( + const MetricFamily *mf, + sd_varlink *link, + const char *path, + const char *unit) { + + static const char* const types[] = { "current", "available", "peak" }; + bool bad[ELEMENTSOF(types)] = {}; + uint64_t current = 0, limit = UINT64_MAX, peak = 0; + int r; + + assert(mf && mf->name); + assert(link); + assert(path); + assert(unit); + + r = cg_get_attribute_as_uint64(path, "memory.current", ¤t); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Failed to read %s/%s, ignoring: %m", path, "memory.current"); + + bad[0] = bad[1] = true; + + } else { + /* Walk up the cgroup tree to find the tightest memory limit */ + _cleanup_free_ char *path_buf = strdup(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) + bad[1] = true; + } + + r = cg_get_attribute_as_uint64(path, "memory.peak", &peak); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Failed to read %s/%s, ignoring: %m", path, "memory.peak"); + bad[2] = true; + } + + uint64_t values[] = { current, limit - current, peak }; + for (unsigned i = 0; i < ELEMENTSOF(values); i++) { + if (bad[i]) + continue; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fields = NULL; + r = sd_json_buildo(&fields, SD_JSON_BUILD_PAIR_STRING("type", types[i])); + if (r < 0) + return r; + + r = metric_build_send_unsigned(mf, link, unit, values[i], fields); + if (r < 0) + return r; + } + + return 0; +} + +static int tasks_current_send( + const MetricFamily *mf, + sd_varlink *link, + const char *path, + const char *unit) { + + uint64_t val; + int r; + + assert(mf && mf->name); + assert(link); + assert(path); + assert(unit); + + r = cg_get_attribute_as_uint64(path, "pids.current", &val); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Failed to read %s/%s, ignoring: %m", path, "pids.current"); + return 0; + } + + return metric_build_send_unsigned(mf, link, unit, val, /* fields= */ NULL); +} + +static int walk_cgroups( + const MetricFamily mf[static 5], + sd_varlink *link, + const char *path) { + + int r; + + assert(mf && mf[0].name && mf[1].name && mf[2].name && mf[3].name && mf[4].name); + assert(mf[0].generate && !mf[1].generate && !mf[2].generate && !mf[3].generate && !mf[4].generate); + assert(path); + + _cleanup_free_ char *unit = NULL; + r = cg_path_get_unit(path, &unit); + if (r >= 0) { + r = cpu_usage_send(mf + 0, link, path, unit); + if (r < 0) + return r; + + r = io_read_send(mf + 1, link, path, unit); + if (r < 0) + return r; + + r = memory_usage_send(mf + 3, link, path, unit); + if (r < 0) + return r; + + r = tasks_current_send(mf + 4, link, path, unit); + if (r < 0) + return r; + + 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; + + _cleanup_closedir_ DIR *d = NULL; + 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(mf, link, child); + if (r < 0) + return r; + } + + return 0; +} + +static int cgroup_stats_send( + const MetricFamily mf[static 5], + sd_varlink *link, + void *userdata) { + + assert(mf); + assert(link); + assert(!userdata); + + return walk_cgroups(mf, link, ""); +} + +static const MetricFamily cgroup_metric_family_table[] = { + /* Keep metrics ordered alphabetically */ + { + METRIC_IO_SYSTEMD_CGROUP_PREFIX "CpuUsage", + "Per unit metric: CPU usage in nanoseconds (type=total|user|system)", + METRIC_FAMILY_TYPE_COUNTER, + .generate = cgroup_stats_send, + }, + { + METRIC_IO_SYSTEMD_CGROUP_PREFIX "IOReadBytes", + "Per unit metric: IO bytes read", + METRIC_FAMILY_TYPE_COUNTER, + .generate = NULL, + }, + { + METRIC_IO_SYSTEMD_CGROUP_PREFIX "IOReadOperations", + "Per unit metric: IO read operations", + METRIC_FAMILY_TYPE_COUNTER, + .generate = NULL, + }, + { + METRIC_IO_SYSTEMD_CGROUP_PREFIX "MemoryUsage", + "Per unit metric: memory usage in bytes", + METRIC_FAMILY_TYPE_GAUGE, + .generate = NULL, + }, + { + METRIC_IO_SYSTEMD_CGROUP_PREFIX "TasksCurrent", + "Per unit metric: current number of tasks", + METRIC_FAMILY_TYPE_GAUGE, + .generate = NULL, + }, + {} +}; + +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) { + return metrics_method_list(cgroup_metric_family_table, link, parameters, flags, userdata); +} diff --git a/src/report/report-cgroup.h b/src/report/report-cgroup.h new file mode 100644 index 0000000000000..ce1e55b280bd3 --- /dev/null +++ b/src/report/report-cgroup.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +#define METRIC_IO_SYSTEMD_CGROUP_PREFIX "io.systemd.CGroup." + +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-files-server.c b/src/report/report-files-server.c new file mode 100644 index 0000000000000..c1f90ccd7bbfa --- /dev/null +++ b/src/report/report-files-server.c @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink.h" + +#include "build.h" +#include "format-table.h" +#include "help-util.h" +#include "log.h" +#include "main-func.h" +#include "options.h" +#include "report-files.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; + 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 = 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_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...]"); + help_abstract("Report the contents of files as system report metrics."); + help_section("Options"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-report-files@.service", "8"); + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + } + + if (option_parser_get_n_args(&opts) > 0) + 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-files.c b/src/report/report-files.c new file mode 100644 index 0000000000000..af2c38b5fe6f7 --- /dev/null +++ b/src/report/report-files.c @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "chase.h" +#include "conf-files.h" +#include "fd-util.h" +#include "fileio.h" +#include "log.h" +#include "metrics.h" +#include "path-util.h" +#include "report-files.h" +#include "string-util.h" +#include "strv.h" +#include "utf8.h" +#include "varlink-idl-util.h" + +/* Upper bounds, to protect against pathologically large directories or files. */ +#define REPORT_FILES_MAX 1024U +#define REPORT_FILE_SIZE_MAX (4U * 1024U * 1024U) + +/* The directories we look for files to report in. This is the usual CONF_PATHS() set (/etc/, /run/, + * /usr/local/lib/, /usr/lib/), plus an extra directory below /var/lib/ for persistent local additions. Files + * (typically symlinks to the actual files to report) dropped into any of these are reported as metrics, keyed + * by their name. Entries in earlier directories override identically named ones in later directories. */ +static const char* const report_files_dirs[] = { + "/etc/systemd/report.files", + "/run/systemd/report.files", + "/var/lib/systemd/report.files", + "/usr/local/lib/systemd/report.files", + "/usr/lib/systemd/report.files", + NULL, +}; + +static MetricFamily* metric_family_array_free(MetricFamily *families) { + if (!families) + return NULL; + + /* The array is NULL-name terminated. We own the name/description strings. */ + for (MetricFamily *mf = families; mf->name; mf++) { + free((char*) mf->name); + free((char*) mf->description); + } + + return mfree(families); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(MetricFamily*, metric_family_array_free); + +static int file_metric_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + int r; + + assert(mf && mf->name); + assert(link); + + /* Recover the file name from the metric family name: it is the part following the interface prefix. + * We look it up across our directories again (rather than caching the path found while building the + * metric list), and read the first instance we find, matching the precedence used at enumeration. */ + const char *field = startswith(mf->name, METRIC_IO_SYSTEMD_FILES_PREFIX); + assert(field); + + _cleanup_free_ char *buf = NULL, *discovered_path = NULL; + size_t size = 0; + STRV_FOREACH(d, report_files_dirs) { + _cleanup_free_ char *path = path_join(*d, field); + if (!path) + return log_oom(); + + _cleanup_free_ char *resolved = NULL; + _cleanup_close_ int fd = chase_and_open(path, /* root= */ NULL, CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC, &resolved); + if (fd == -ENOENT) /* Not in this directory (or dangling symlink): try the next one. */ + continue; + if (fd < 0) { + log_warning_errno(fd, "Failed to open '%s', skipping: %m", path); + return 0; + } + + r = read_full_file_full( + fd, + /* filename= */ NULL, + /* offset= */ UINT64_MAX, + REPORT_FILE_SIZE_MAX, + READ_FULL_FILE_FAIL_WHEN_LARGER, + /* bind_name= */ NULL, + &buf, + &size); + if (r < 0) { + log_warning_errno(r, "Failed to read '%s', skipping: %m", path); + return 0; + } + + discovered_path = TAKE_PTR(resolved); + break; + } + + if (!discovered_path) { + log_debug("File for metric '%s' unavailable, skipping.", mf->name); + return 0; + } + + /* Metric values are JSON strings, so we can only report text files. Skip anything that isn't valid, + * NUL-free UTF-8. */ + if (memchr(buf, 0, size) || !utf8_is_valid(buf)) { + log_debug("File for metric '%s' is not valid UTF-8 text, skipping.", mf->name); + return 0; + } + + return metric_build_send_string(mf, link, discovered_path, buf, /* fields= */ NULL); +} + +static int build_file_metrics(MetricFamily **ret) { + _cleanup_(metric_family_array_freep) MetricFamily *families = NULL; + _cleanup_strv_free_ char **files = NULL; + size_t n = 0; + int r; + + assert(ret); + + /* Enumerate the files to report across all our directories, deduplicated by name. The entry name is + * used as the metric field name. */ + r = conf_files_list_strv(&files, /* suffix= */ NULL, /* root= */ NULL, CONF_FILES_REGULAR, report_files_dirs); + if (r < 0) + return log_error_errno(r, "Failed to enumerate report files: %m"); + + STRV_FOREACH(f, files) { + _cleanup_free_ char *base = NULL; + r = path_extract_filename(*f, &base); + if (r < 0) + return log_error_errno(r, "Failed to extract file name from '%s': %m", *f); + + /* The name becomes the metric field name, so it must be a valid one. */ + if (!varlink_idl_field_name_is_valid(base)) { + log_debug("Report file '%s' does not have a valid metric field name, skipping.", *f); + continue; + } + + if (n >= REPORT_FILES_MAX) { + log_warning("More than %u report files found, not reporting the rest.", REPORT_FILES_MAX); + break; + } + + _cleanup_free_ char *name = strjoin(METRIC_IO_SYSTEMD_FILES_PREFIX, base); + _cleanup_free_ char *description = strjoin("Contents of the '", base, "' report file"); + if (!name || !description) + return log_oom(); + + /* Room for the new entry plus the NULL-name terminator. */ + if (!GREEDY_REALLOC(families, n + 2)) + return log_oom(); + + families[n++] = (MetricFamily) { + .name = TAKE_PTR(name), + .description = TAKE_PTR(description), + .type = METRIC_FAMILY_TYPE_STRING, + .generate = file_metric_generate, + }; + families[n] = (MetricFamily) {}; /* terminator */ + } + + /* The metrics helpers expect a valid, terminated array even when empty. */ + if (!families) { + families = new0(MetricFamily, 1); + if (!families) + return log_oom(); + } + + *ret = TAKE_PTR(families); + return 0; +} + +int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + int r; + + _cleanup_(metric_family_array_freep) MetricFamily *families = NULL; + r = build_file_metrics(&families); + if (r < 0) + return r; + + return metrics_method_list(families, link, parameters, flags, /* userdata= */ NULL); +} + +int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + int r; + + _cleanup_(metric_family_array_freep) MetricFamily *families = NULL; + r = build_file_metrics(&families); + if (r < 0) + return r; + + return metrics_method_describe(families, link, parameters, flags, /* userdata= */ NULL); +} diff --git a/src/report/report-files.h b/src/report/report-files.h new file mode 100644 index 0000000000000..0bdee8cb9753b --- /dev/null +++ b/src/report/report-files.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +#define METRIC_IO_SYSTEMD_FILES_PREFIX "io.systemd.Files." + +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-generate.c b/src/report/report-generate.c new file mode 100644 index 0000000000000..7dc46bf2188d0 --- /dev/null +++ b/src/report/report-generate.c @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "log.h" +#include "report.h" +#include "report-generate.h" +#include "report-sign.h" +#include "time-util.h" + +int context_build_report(Context *context, sd_json_variant **ret) { + int r; + + /* Convert the variant array to a JSON report. */ + + assert(context); + assert(ret); + + usec_t ts = now(CLOCK_REALTIME); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *report = NULL; + r = sd_json_buildo(&report, + SD_JSON_BUILD_PAIR_STRING("mediaType", "application/vnd.io.systemd.report"), + SD_JSON_BUILD_PAIR("timestamp", + SD_JSON_BUILD_STRING(FORMAT_TIMESTAMP_STYLE(ts, TIMESTAMP_UTC))), + SD_JSON_BUILD_PAIR("metrics", + SD_JSON_BUILD_VARIANT_ARRAY(context->metrics, context->n_metrics))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON data: %m"); + + /* Normalize the report, to make signing more robust (note that we sign a specific binary formatting + * of it though, this is hence not load bearing, but still useful) */ + // FIXME: this causes a test failure https://github.com/systemd/systemd/issues/42669 + // r = sd_json_variant_normalize(&report); + // if (r < 0) + // return log_error_errno(r, "Failed to normalize report JSON: %m"); + + *ret = TAKE_PTR(report); + return 0; +} + +int context_generate_report(Context *context) { + int r; + + assert(context); + + /* Make a structured report and either print it or upload it. */ + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *report = NULL; + r = context_build_report(context, &report); + if (r < 0) + return r; + + if (arg_sign) { + r = context_sign_report(context, report, arg_json_format_flags, /* output= */ NULL); + if (r < 0) + return r; + } else { + r = sd_json_variant_dump(report, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to dump json object: %m"); + } + + return 0; +} diff --git a/src/report/report-generate.h b/src/report/report-generate.h new file mode 100644 index 0000000000000..acde3db14c386 --- /dev/null +++ b/src/report/report-generate.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "report.h" + +int context_build_report(Context *context, sd_json_variant **ret); + +int context_generate_report(Context *context); diff --git a/src/report/report-sign-plain.c b/src/report/report-sign-plain.c new file mode 100644 index 0000000000000..6aca27810c7ee --- /dev/null +++ b/src/report/report-sign-plain.c @@ -0,0 +1,403 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-json.h" +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "build.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 "help-util.h" +#include "iovec-util.h" +#include "json-util.h" +#include "lock-util.h" +#include "log.h" +#include "main-func.h" +#include "metrics.h" +#include "options.h" +#include "stat-util.h" +#include "tmpfile-util.h" +#include "varlink-io.systemd.Metrics.h" +#include "varlink-io.systemd.Report.Signer.h" +#include "varlink-util.h" + +#define REPORT_SIGN_PLAIN_DIR "/var/lib/systemd/report.sign.plain" +#define REPORT_SIGN_PLAIN_PRIVATE_KEY "local.private" +#define REPORT_SIGN_PLAIN_PUBLIC_KEY "local.public" + +#define METRIC_IO_SYSTEMD_SIGN_PLAIN_PREFIX "io.systemd.Report.SignPlain." + +static int load_key(int dir_fd, EVP_PKEY **ret) { + _cleanup_fclose_ FILE *f = NULL; + struct stat st; + int r; + + assert(dir_fd >= 0); + assert(ret); + + r = xfopenat(dir_fd, REPORT_SIGN_PLAIN_PRIVATE_KEY, "re", /* open_flags= */ 0, &f); + if (r == -ENOENT) { + *ret = NULL; + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to open private key file '%s/%s': %m", + REPORT_SIGN_PLAIN_DIR, REPORT_SIGN_PLAIN_PRIVATE_KEY); + + if (fstat(fileno(f), &st) < 0) + return log_error_errno(errno, "Failed to stat private key file: %m"); + + r = stat_verify_regular(&st); + if (r < 0) + return log_error_errno(r, "Private key file is not a regular file: %m"); + + if (st.st_uid != 0 || (st.st_mode & 0077) != 0) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "Private key file '%s/%s' is accessible by more than the root user, refusing.", + REPORT_SIGN_PLAIN_DIR, REPORT_SIGN_PLAIN_PRIVATE_KEY); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = sym_PEM_read_PrivateKey(f, /* x= */ NULL, /* cb= */ NULL, /* u= */ NULL); + if (!pkey) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to load private key from '%s/%s'.", + REPORT_SIGN_PLAIN_DIR, REPORT_SIGN_PLAIN_PRIVATE_KEY); + + log_debug("Successfully loaded private key from '%s/%s'.", REPORT_SIGN_PLAIN_DIR, REPORT_SIGN_PLAIN_PRIVATE_KEY); + + *ret = TAKE_PTR(pkey); + return 1; +} + +static int write_key_file(int dir_fd, EVP_PKEY *pkey, bool private_key, const char *name, mode_t mode) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *temp = NULL; + int r; + + assert(dir_fd >= 0); + assert(pkey); + assert(name); + + r = fopen_temporary_at(dir_fd, name, &f, &temp); + if (r < 0) + return log_error_errno(r, "Failed to open key file '%s/%s' for writing: %m", REPORT_SIGN_PLAIN_DIR, name); + + CLEANUP_TMPFILE_AT(dir_fd, temp); + + if (private_key) + r = sym_PEM_write_PrivateKey(f, pkey, /* enc= */ NULL, /* kstr= */ NULL, /* klen= */ 0, /* cb= */ NULL, /* u= */ NULL); + else + r = sym_PEM_write_PUBKEY(f, pkey); + if (r <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write %s key.", private_key ? "private" : "public"); + + if (fchmod(fileno(f), mode) < 0) + return log_error_errno(errno, "Failed to adjust key file access mode: %m"); + + r = fflush_sync_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write key file: %m"); + + f = safe_fclose(f); + + /* Never overwrite an existing private key: we must not destroy it. The public key is derived from the + * private one and carries no secret, hence may be refreshed unconditionally. */ + if (private_key) + r = rename_noreplace(dir_fd, temp, dir_fd, name); + else + r = RET_NERRNO(renameat(dir_fd, temp, dir_fd, name)); + if (r < 0) { + if (private_key && r == -EEXIST) + return log_error_errno(r, "Private key file '%s/%s' appeared while generating a new one, refusing to overwrite it.", + REPORT_SIGN_PLAIN_DIR, name); + + return log_error_errno(r, "Failed to move key file '%s/%s' into place: %m", REPORT_SIGN_PLAIN_DIR, name); + } + + return 0; +} + +static int generate_key(int dir_fd, EVP_PKEY **ret) { + int r; + + assert(dir_fd >= 0); + assert(ret); + + /* Generate a new Ed25519 key pair. */ + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, /* e= */ NULL); + if (!ctx) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to allocate Ed25519 key generation context."); + + 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 Ed25519 key pair for signing reports."); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + if (sym_EVP_PKEY_keygen(ctx, &pkey) <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate Ed25519 key pair."); + + log_info("Successfully generated Ed25519 key pair."); + + /* Write out the private key (PKCS#8 PEM; this includes the public key too), readable by root only. */ + r = write_key_file(dir_fd, pkey, /* private_key= */ true, REPORT_SIGN_PLAIN_PRIVATE_KEY, 0400); + if (r < 0) + return r; + + /* Write out the public key too, as a convenience for verification, world readable. */ + r = write_key_file(dir_fd, pkey, /* private_key= */ false, REPORT_SIGN_PLAIN_PUBLIC_KEY, 0444); + if (r < 0) + return r; + + r = RET_NERRNO(fsync(dir_fd)); + if (r < 0) + return log_error_errno(r, "Failed to sync directory '%s': %m", REPORT_SIGN_PLAIN_DIR); + + *ret = TAKE_PTR(pkey); + return 1; +} + +static int acquire_key(EVP_PKEY **ret) { + int r; + + assert(ret); + + /* Open (creating if necessary) and exclusively lock the key directory, so that loading/generating the + * key is safe against parallel invocations (we are spawned once per connection). The directory is + * created with mode 0700, so that nobody else can pre-create it with looser permissions. */ + _cleanup_close_ int dir_fd = xopenat_lock_full( + AT_FDCWD, + REPORT_SIGN_PLAIN_DIR, + O_CLOEXEC|O_DIRECTORY|O_CREAT, + /* xopen_flags= */ 0, + /* mode= */ 0700, + LOCK_BSD, + LOCK_EX); + if (dir_fd < 0) + return log_error_errno(dir_fd, "Failed to open and lock directory '%s': %m", REPORT_SIGN_PLAIN_DIR); + + /* First try to load the key off disk. If loading fails for any reason other than the key not + * existing yet (e.g. it is malformed or has bad permissions) we propagate the error and refuse to + * touch the existing file. Only if the key is genuinely missing do we generate a fresh one. */ + r = load_key(dir_fd, ret); + if (r != 0) + return r; + + return generate_key(dir_fd, ret); +} + +static int vl_method_sign( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + static const sd_json_dispatch_field dispatch_table[] = { + { "digest", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, 0, SD_JSON_MANDATORY }, + { "algorithm", SD_JSON_VARIANT_STRING, NULL, 0, SD_JSON_MANDATORY }, + {} + }; + + _cleanup_(iovec_done) struct iovec digest = {}; + int r; + + assert(link); + assert(parameters); + + /* Signing is privileged: we share the Varlink server (and possibly the socket) with the unprivileged + * metrics interface, so we cannot rely on a server-wide access check and must gate this method + * ourselves. Only root (i.e. ourselves) may request signatures. */ + r = varlink_check_privileged_peer(link); + if (r < 0) + return r; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &digest); + if (r != 0) + return r; + + if (!iovec_is_set(&digest)) + return sd_varlink_error_invalid_parameter_name(link, "digest"); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + r = acquire_key(&pkey); + if (r < 0) + return r; + + /* Sign the provided digest directly. We pass a NULL message digest algorithm, which for a suitable + * signing algorithm such as Ed25519 means the input bytes are signed as-is, without hashing them + * first. The caller already passes us a digest, hence we must not hash it again. */ + _cleanup_(iovec_done) struct iovec signature = {}; + r = digest_and_sign(/* md= */ NULL, pkey, digest.iov_base, digest.iov_len, &signature.iov_base, &signature.iov_len); + if (r < 0) + return log_error_errno(r, "Failed to sign digest: %m"); + + _cleanup_free_ char *pubkey = NULL; + r = openssl_pubkey_to_pem(pkey, &pubkey); + if (r < 0) + return log_error_errno(r, "Failed to extract public key: %m"); + + /* Return a single signature object, carrying the raw Ed25519 (EdDSA) signature and the public key it + * was made with, in PEM form. */ + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR( + "data", + SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_IOVEC_BASE64("signature", &signature), + SD_JSON_BUILD_PAIR_STRING("key", pubkey))))); +} + +static int public_key_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) { + int r; + + assert(mf && mf->name); + assert(link); + + /* Expose the public key the signer uses also as metrics. This has the nice effect that the signature + * will cover the public key, in the sense of a self-signed certificate. We acquire it through the + * very same logic as the Sign() method, i.e. we load it off disk, or generate it on first use if it + * is still missing. */ + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + r = acquire_key(&pkey); + if (r < 0) + return r; + + _cleanup_free_ char *pubkey = NULL; + r = openssl_pubkey_to_pem(pkey, &pubkey); + if (r < 0) + return log_error_errno(r, "Failed to extract public key: %m"); + + return metric_build_send_string( + mf, + link, + /* object= */ NULL, + pubkey, + /* fields= */ NULL); +} + +static const MetricFamily metric_family_table[] = { + { + METRIC_IO_SYSTEMD_SIGN_PLAIN_PREFIX "PublicKey", + "PEM-encoded public key used to sign system reports with the local software key", + METRIC_FAMILY_TYPE_STRING, + .generate = public_key_generate, + }, + {} +}; + +static int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return metrics_method_list(metric_family_table, link, parameters, flags, userdata); +} + +static int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return metrics_method_describe(metric_family_table, link, parameters, flags, userdata); +} + +static int vl_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *vs = NULL; + int r; + + r = DLOPEN_LIBCRYPTO(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + + /* No server-wide access restriction: the metrics interface is meant to be reachable unprivileged, + * while the signing method enforces root access on its own (see vl_method_sign()). */ + r = varlink_server_new(&vs, /* flags= */ 0, /* userdata= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + /* We serve two interfaces off the same server: the signing API and a metrics provider that exposes our + * public key. Either is reachable regardless of which socket the connection arrived on. */ + r = sd_varlink_server_add_interface_many( + vs, + &vl_interface_io_systemd_Report_Signer, + &vl_interface_io_systemd_Metrics); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interfaces: %m"); + + r = sd_varlink_server_bind_method_many( + vs, + "io.systemd.Report.Signer.Sign", vl_method_sign, + "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_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...]"); + help_abstract("Sign system reports with a local software key."); + help_section("Options"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-report-sign-plain@.service", "8"); + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + } + + if (option_parser_get_n_args(&opts) > 0) + 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-sign-tsm.c b/src/report/report-sign-tsm.c new file mode 100644 index 0000000000000..0929fbbc0b645 --- /dev/null +++ b/src/report/report-sign-tsm.c @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" +#include "sd-varlink.h" + +#include "build.h" +#include "format-table.h" +#include "help-util.h" +#include "iovec-util.h" +#include "json-util.h" +#include "log.h" +#include "main-func.h" +#include "options.h" +#include "sha256.h" +#include "string-util.h" +#include "tsm-report.h" +#include "varlink-io.systemd.Report.Signer.h" +#include "varlink-util.h" + +typedef struct SignParameters { + struct iovec digest; + const char *algorithm; +} SignParameters; + +static void sign_parameters_done(SignParameters *p) { + iovec_done(&p->digest); +} + +static int vl_method_sign( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + static const sd_json_dispatch_field dispatch_table[] = { + { "digest", SD_JSON_VARIANT_STRING, json_dispatch_unhex_iovec, offsetof(SignParameters, digest), SD_JSON_MANDATORY }, + { "algorithm", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(SignParameters, algorithm), SD_JSON_MANDATORY }, + {} + }; + + _cleanup_(sign_parameters_done) SignParameters sp = {}; + _cleanup_(tsm_report_freep) TsmReport *report = NULL; + int r; + + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &sp); + if (r != 0) + return r; + + if (!streq(sp.algorithm, "SHA256")) + return sd_varlink_error_invalid_parameter_name(link, "algorithm"); + if (sp.digest.iov_len != SHA256_DIGEST_SIZE) + return sd_varlink_error_invalid_parameter_name(link, "digest"); + + uint8_t report_data[TSM_REPORT_DATA_SIZE] = {}; + memcpy(report_data, sp.digest.iov_base, sp.digest.iov_len); + + r = tsm_report_acquire(&IOVEC_MAKE(report_data, sizeof report_data), /* options= */ NULL, &report); + if (IN_SET(r, -EOPNOTSUPP, -ENXIO)) + /* Returning an error will fail the whole report signing, so we return + * no signature instead which will be ignored by the aggregator. */ + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_EMPTY_ARRAY("data")); + if (r < 0) + return log_error_errno(r, "Failed to acquire TSM report: %m"); + + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR("data", + SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("provider", report->provider), + JSON_BUILD_PAIR_IOVEC_BASE64("outblob", &report->outblob), + SD_JSON_BUILD_PAIR_CONDITION(iovec_is_set(&report->auxblob), + "auxblob", JSON_BUILD_IOVEC_BASE64(&report->auxblob)), + SD_JSON_BUILD_PAIR_CONDITION(iovec_is_set(&report->manifestblob), + "manifestblob", JSON_BUILD_IOVEC_BASE64(&report->manifestblob)))))); +} + +static int vl_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *vs = NULL; + int r; + + r = varlink_server_new(&vs, SD_VARLINK_SERVER_ROOT_ONLY|SD_VARLINK_SERVER_MYSELF_ONLY, /* userdata= */ NULL); + 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_Report_Signer); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = sd_varlink_server_bind_method(vs, "io.systemd.Report.Signer.Sign", vl_method_sign); + 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_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...]"); + help_abstract("Get an attestation report via configfs Trusted Security Module (TSM) " + "that includes the hash of the system report."); + help_section("Options"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-report-sign-tsm@.service", "8"); + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + OPTION_COMMON_HELP: + return help(); + OPTION_COMMON_VERSION: + return version(); + } + + if (option_parser_get_n_args(&opts) > 0) + 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-sign.c b/src/report/report-sign.c new file mode 100644 index 0000000000000..150547dcf47ed --- /dev/null +++ b/src/report/report-sign.c @@ -0,0 +1,229 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "errno-util.h" +#include "json-util.h" +#include "log.h" +#include "memstream-util.h" +#include "path-util.h" +#include "report.h" +#include "report-sign.h" +#include "sha256.h" +#include "time-util.h" +#include "varlink-util.h" + +#define REPORT_SIGN_DIR "/run/systemd/report.sign" +#define REPORT_SIGN_TIMEOUT_USEC USEC_PER_MINUTE + +typedef struct Signature { + char *mechanism; + sd_json_variant *data; +} Signature; + +typedef struct SignatureList { + Signature *signatures; + size_t n_signatures; + int result; +} SignatureList; + +static void signature_done(Signature *s) { + assert(s); + + s->data = sd_json_variant_unref(s->data); + s->mechanism = mfree(s->mechanism); +} + +static void signature_list_done(SignatureList *sl) { + assert(sl); + + FOREACH_ARRAY(s, sl->signatures, sl->n_signatures) + signature_done(s); + + sl->signatures = mfree(sl->signatures); + sl->n_signatures = 0; +} + +static int execute_dir_reply( + sd_varlink *link, + sd_json_variant *reply, + const char *error_id, + sd_varlink_reply_flags_t flags, + void *userdata) { + + SignatureList *sl = ASSERT_PTR(userdata); + int r; + + assert(link); + + /* Get the socket name */ + const char *p = ASSERT_PTR(sd_varlink_get_description(link)); + + _cleanup_free_ char *sn = NULL; + r = path_extract_filename(p, &sn); + if (r < 0) + return log_error_errno(r, "Failed to extract service name from '%s': %m", p); + + if (error_id) { + r = sd_varlink_error_to_errno(error_id, reply); + RET_GATHER(sl->result, r); + return log_error_errno(r, "Signing via Varlink service '%s' failed: %s", p, error_id); + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + + static const sd_json_dispatch_field dispatch_table[] = { + { "data", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, /* offset= */ 0, /* flags= */ 0 }, + {}, + }; + + r = sd_json_dispatch(reply, dispatch_table, /* flags= */ 0, &array); + if (r < 0) + return log_error_errno(r, "Failed to dispatch method reply: %m"); + + size_t n = 0; + if (array) { + sd_json_variant *s; + JSON_VARIANT_ARRAY_FOREACH(s, array) { + if (!GREEDY_REALLOC(sl->signatures, sl->n_signatures + 1)) + return log_oom(); + + Signature *i = sl->signatures + sl->n_signatures; + + i->mechanism = strdup(sn); + if (!i->mechanism) + return log_oom(); + + i->data = sd_json_variant_ref(s); + sl->n_signatures++; + + n++; + } + } + + if (n == 0) + log_info("Mechanism '%s' succeeded, but returned no signatures.", p); + else + log_info("Successfully acquired %zu signatures from '%s'", n, p); + + return 0; +} + +int context_sign_report( + Context *context, + sd_json_variant *report, + sd_json_format_flags_t format_flags, + FILE *output) { + int r; + + assert(context); + assert(report); + + /* When generating a signed report we switch to JSON-SEQ. We'll put the report as first object in the + * stream, and then signature objects after it, that cover the precise binary representation of the + * first object. We normalize the report JSON first, but this is not load bearing, as the signature + * is about the binary representation of the JSON object sent over the wire, not the JSON object + * itself. */ + + if (!output) + output = stdout; + + /* For the report itself we'll use the normalized, dense formatting, in order to make things as + * reproducible as possible. */ + _cleanup_free_ char *text = NULL; + r = sd_json_variant_format(report, SD_JSON_FORMAT_SEQ, &text); + if (r < 0) + return log_error_errno(r, "Failed to format JSON data: %m"); + + uint8_t digest[SHA256_DIGEST_SIZE]; + sha256_direct(text, strlen(text), digest); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; + r = sd_json_buildo(¶ms, + SD_JSON_BUILD_PAIR_HEX("digest", digest, sizeof(digest)), + SD_JSON_BUILD_PAIR_STRING("algorithm", "SHA256")); + if (r < 0) + return log_error_errno(r, "Failed to build JSON data: %m"); + + _cleanup_(signature_list_done) SignatureList sl = {}; + ssize_t jobs = varlink_execute_directory( + REPORT_SIGN_DIR, + "io.systemd.Report.Signer.Sign", + params, + /* more= */ false, + REPORT_SIGN_TIMEOUT_USEC, + execute_dir_reply, + /* userdata= */ &sl); + if (jobs < 0) + return log_error_errno(jobs, "Failed to execute signing via '%s': %m", REPORT_SIGN_DIR); + if (jobs == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), + "No signing mechanism found via '%s'.", REPORT_SIGN_DIR); + if (sl.result < 0) + /* The details were printed at error level by execute_dir_reply above. */ + return log_debug_errno(sl.result, "Signing via '%s' failed: %m", REPORT_SIGN_DIR); + if (sl.n_signatures == 0) + return log_debug_errno(SYNTHETIC_ERRNO(ENOPKG), + "No signatures acquired via '%s'.", REPORT_SIGN_DIR); + + if (fputs(text, output) == EOF) + return log_error_errno(errno, "Failed to write report: %m"); + + /* For the signatures we can use the requested formattting */ + format_flags |= SD_JSON_FORMAT_SEQ; + format_flags &= ~SD_JSON_FORMAT_OFF; + + FOREACH_ARRAY(s, sl.signatures, sl.n_signatures) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *sig = NULL; + + r = sd_json_buildo(&sig, + SD_JSON_BUILD_PAIR_STRING("mediaType", "application/vnd.io.systemd.report.signature"), + SD_JSON_BUILD_PAIR_STRING("mechanism", s->mechanism), + SD_JSON_BUILD_PAIR_HEX("sha256", digest, sizeof(digest)), + JSON_BUILD_PAIR_VARIANT_NON_EMPTY("data", s->data)); + if (r < 0) + return log_error_errno(r, "Failed to build JSON data: %m"); + + r = sd_json_variant_normalize(&sig); + if (r < 0) + return log_error_errno(r, "Failed to normalize JSON object: %m"); + + r = sd_json_variant_dump(sig, format_flags, output, /* prefix= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to dump json object: %m"); + } + + log_debug("Signing via '%s' finished successfully.", REPORT_SIGN_DIR); + return 0; +} + +int context_sign_report_as_string( + Context *context, + sd_json_variant *report, + sd_json_format_flags_t format_flags, + char **ret) { + + int r; + + assert(context); + assert(report); + assert(ret); + + _cleanup_(memstream_done) MemStream ms = {}; + + FILE *f = memstream_init(&ms); + if (!f) + return log_oom(); + + r = context_sign_report(context, report, format_flags, f); + if (r < 0) + return r; + + r = memstream_finalize(&ms, ret, /* ret_size= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to finalize memory stream: %m"); + + return 0; +} diff --git a/src/report/report-sign.h b/src/report/report-sign.h new file mode 100644 index 0000000000000..c5264252b9128 --- /dev/null +++ b/src/report/report-sign.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "report.h" + +int context_sign_report(Context *context, sd_json_variant *report, sd_json_format_flags_t format_flags, FILE *output); + +int context_sign_report_as_string(Context *context, sd_json_variant *report, sd_json_format_flags_t format_flags, char **ret); diff --git a/src/report/report-upload.c b/src/report/report-upload.c new file mode 100644 index 0000000000000..e21c0044e63a5 --- /dev/null +++ b/src/report/report-upload.c @@ -0,0 +1,280 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "alloc-util.h" +#include "errno-util.h" +#include "log.h" +#include "report.h" +#include "report-generate.h" +#include "report-sign.h" +#include "report-upload.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "utf8.h" +#include "varlink-util.h" +#include "version.h" + +#define REPORT_UPLOAD_DIR "/run/systemd/report.upload" +#define SERVER_ANSWER_MAX (1U * 1024U * 1024U) + +#if HAVE_LIBCURL +#include "curl-util.h" + +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; +} +#endif + +static int http_upload_report(Context *context, sd_json_variant *report) { +#if HAVE_LIBCURL + _cleanup_(curl_slist_free_allp) struct curl_slist *header = NULL; + char error[CURL_ERROR_SIZE] = {}; + int r; + + r = DLOPEN_CURL(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + + /* Upload a JSON report in text form as a single JSON object, instead of a JSON-SEQ list. */ + + _cleanup_free_ char *text = NULL; + if (arg_sign) { + r = context_sign_report_as_string(context, report, /* format_flags= */ 0, &text); + if (r < 0) + return r; + } else { + r = sd_json_variant_format(report, /* flags= */ 0, &text); + 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, text)) + 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 +} + +static int execute_dir_reply( + sd_varlink *link, + sd_json_variant *reply, + const char *error_id, + sd_varlink_reply_flags_t flags, + void *userdata) { + + assert(link); + + Context *context = ASSERT_PTR(userdata); + int r; + + if (error_id) { + r = sd_varlink_error_to_errno(error_id, reply); + RET_GATHER(context->upload_result, r); + log_error_errno(r, "Upload via Varlink failed: %s", error_id); + if (reply) + (void) sd_json_variant_dump(reply, arg_json_format_flags, + /* f= */ NULL, /* prefix= */ NULL); + return r; + } + + printf("Upload via Varlink was successful; reply: "); + // TODO: once we know what we want to put in the reply, replace the JSON dump by + // some formatted output. + r = sd_json_variant_dump(reply, arg_json_format_flags, stderr, /* prefix= */ ">>> "); + if (r < 0) + return log_error_errno(r, "Failed to dump json object: %m"); + + return 0; +} + +static int varlink_upload_report(Context *context, sd_json_variant *report) { + int r; + + assert(context); + assert(report); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; + if (arg_sign) { + _cleanup_free_ char *buf = NULL; + + r = context_sign_report_as_string(context, report, /* format_flags= */ 0, &buf); + if (r < 0) + return r; + + r = sd_json_buildo(¶ms, + SD_JSON_BUILD_PAIR_BASE64("reportData", buf, strlen(buf))); + } else + r = sd_json_buildo(¶ms, + SD_JSON_BUILD_PAIR_VARIANT("report", report)); + if (r < 0) + return log_error_errno(r, "Failed to build JSON data: %m"); + + ssize_t jobs = varlink_execute_directory( + REPORT_UPLOAD_DIR, + "io.systemd.Report.Uploader.Upload", + params, + /* more= */ false, + arg_network_timeout_usec, + execute_dir_reply, + /* userdata= */ context); + if (jobs < 0) + return log_error_errno(jobs, "Failed to execute upload via %s: %m", REPORT_UPLOAD_DIR); + if (jobs == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), + "No upload mechanism found via %s.", REPORT_UPLOAD_DIR); + if (context->upload_result < 0) + /* The details were printed at error level by execute_dir_reply above. */ + return log_debug_errno(context->upload_result, "Upload via %s failed: %m", REPORT_UPLOAD_DIR); + + log_debug("Upload via %s finished successfully.", REPORT_UPLOAD_DIR); + return 0; +} + +int context_upload_report(Context *context) { + int r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *report = NULL; + r = context_build_report(context, &report); + if (r < 0) + return r; + + return (arg_url ? http_upload_report : varlink_upload_report)(context, report); +} diff --git a/src/report/report-upload.h b/src/report/report-upload.h new file mode 100644 index 0000000000000..eb4cb87f2317d --- /dev/null +++ b/src/report/report-upload.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "report.h" + +int context_upload_report(Context *context); diff --git a/src/report/report.c b/src/report/report.c index 3c2f89e002909..c3b014a008085 100644 --- a/src/report/report.c +++ b/src/report/report.c @@ -1,22 +1,24 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-event.h" #include "sd-varlink.h" #include "alloc-util.h" -#include "ansi-color.h" #include "build.h" #include "chase.h" #include "dirent-util.h" #include "format-table.h" +#include "help-util.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 "report-generate.h" +#include "report-sign.h" +#include "report-upload.h" #include "runtime-scope.h" #include "set.h" #include "sort-util.h" @@ -24,34 +26,32 @@ #include "strv.h" #include "time-util.h" #include "varlink-idl-util.h" +#include "varlink-io.systemd.Report.h" +#include "varlink-util.h" #include "verbs.h" +#include "web-util.h" -#define METRICS_MAX 1024U +#define METRICS_MAX 4096U #define METRICS_LINKS_MAX 128U #define TIMEOUT_USEC (30 * USEC_PER_SEC) /* 30 seconds */ static PagerFlags arg_pager_flags = 0; 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; - -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; +sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; +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; +bool arg_sign = false; + +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 +72,20 @@ 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); + context->matches = strv_free(context->matches); 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 int metric_compare(sd_json_variant *const *a, sd_json_variant *const *b) { const char *name_a, *name_b, *object_a, *object_b; @@ -194,13 +195,13 @@ static Verdict metrics_verdict(LinkInfo *li, sd_json_variant *metric) { /* Check it against any specified matches */ bool matches; - if (strv_isempty(arg_matches)) + if (strv_isempty(li->context->matches)) matches = true; else { matches = false; /* Allow exact matches or prefix matches */ - STRV_FOREACH(i, arg_matches) + STRV_FOREACH(i, li->context->matches) if (streq(metric_name, *i) || metric_startswith_prefix(metric_name, *i)) { matches = true; @@ -216,7 +217,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,10 +230,15 @@ 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); + else if (streq(error_id, "io.systemd.Metrics.NoSuchMetric")) + log_debug("Varlink connection to '%s' reported no more metrics, ignoring.", li->name); else log_warning("Varlink error from '%s', ignoring: %s", li->name, error_id); @@ -269,7 +275,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; @@ -280,6 +286,10 @@ static int metrics_call(Context *context, const char *name, const char *path) { if (r < 0) return log_error_errno(r, "Unable to connect to %s: %m", path); + r = sd_varlink_set_description(vl, name); + if (r < 0) + return log_error_errno(r, "Failed to set varlink description: %m"); + r = sd_varlink_set_relative_timeout(vl, TIMEOUT_USEC); if (r < 0) return log_error_errno(r, "Failed to set varlink timeout: %m"); @@ -288,14 +298,15 @@ 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 = context->action == ACTION_DESCRIBE_METRICS ? + "io.systemd.Metrics.Describe" : + "io.systemd.Metrics.List"; /* This is the method for all other actions. */ + + 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 +319,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 +331,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 +386,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 +438,7 @@ static int metrics_output_describe(Context *context, Table **ret) { return 0; } -static int metrics_output(Context *context) { +static int output_collected(Context *context) { int r; assert(context); @@ -446,19 +458,19 @@ static int metrics_output(Context *context) { if (context->n_metrics == 0 && arg_legend) 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: - r = metrics_output_describe(context, &table); + case ACTION_DESCRIBE_METRICS: + r = output_collected_describe(context, &table); break; default: @@ -483,28 +495,33 @@ static int metrics_output(Context *context) { return 0; } -static int parse_metrics_matches(char **matches) { +static int parse_metrics_matches(char **input, char ***ret) { int r; - STRV_FOREACH(i, matches) { + assert(ret); + + _cleanup_strv_free_ char **matches = NULL; + STRV_FOREACH(i, input) { r = metrics_name_valid(*i); if (r < 0) return log_error_errno(r, "Failed to determine if '%s' is a valid metric name: %m", *i); if (!r && !varlink_idl_interface_name_is_valid(*i)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Match is not a valid family name or prefix: %s", *i); - if (strv_extend(&arg_matches, *i) < 0) + if (strv_extend(&matches, *i) < 0) return log_oom(); } - strv_sort_uniq(arg_matches); + strv_sort_uniq(matches); + + *ret = TAKE_PTR(matches); return 0; } -static bool test_service_matches(const char *service) { +static bool test_service_matches(const char *service, char **matches) { assert(service); - if (strv_isempty(arg_matches)) + if (strv_isempty(matches)) return true; /* Only contact services whose name is either a prefix of any of the specified metrics families, or @@ -517,7 +534,7 @@ static bool test_service_matches(const char *service) { * it should also be fine to specify a full metric name, and then go directly to the relevant services, and ask for matching metrics. */ - STRV_FOREACH(i, arg_matches) { + STRV_FOREACH(i, matches) { if (streq(service, *i)) return true; @@ -529,7 +546,7 @@ static bool test_service_matches(const char *service) { return false; } -static int readdir_sources(char **ret_directory, DirectoryEntries **ret) { +static int readdir_sources(char **matches, char **ret_directory, DirectoryEntries **ret) { int r; assert(ret_directory); @@ -565,7 +582,7 @@ static int readdir_sources(char **ret_directory, DirectoryEntries **ret) { if (!varlink_idl_interface_name_is_valid(d->d_name)) continue; - if (!test_service_matches(d->d_name)) + if (!test_service_matches(d->d_name, matches)) continue; de->entries[m++] = *i; @@ -579,81 +596,121 @@ static int readdir_sources(char **ret_directory, DirectoryEntries **ret) { return m > 0; } -static int verb_metrics(int argc, char *argv[], void *userdata) { - Action action; +static int context_collect_metrics(Context *context) { int r; - assert(argc >= 1); - assert(argv); + /* Contacts all known metrics sources, issues the appropriate Varlink call on each and runs the + * event loop until all replies came in. Expects the caller to have set up context->event + * beforehand. The collected metrics end up in context->metrics. */ - /* Enable JSON-SEQ mode here, since we'll dump a large series of JSON objects */ - arg_json_format_flags |= SD_JSON_FORMAT_SEQ; + assert(context); + assert(context->event); - if (streq_ptr(argv[0], "metrics")) - action = ACTION_LIST; - else { - assert(streq_ptr(argv[0], "describe-metrics")); - action = ACTION_DESCRIBE; + _cleanup_free_ DirectoryEntries *de = NULL; + _cleanup_free_ char *sources_path = NULL; + r = readdir_sources(context->matches, &sources_path, &de); + if (r < 0) + return r; + if (r == 0) + return 0; + + FOREACH_ARRAY(i, de->entries, de->n_entries) { + struct dirent *d = *i; + + if (set_size(context->link_infos) >= METRICS_LINKS_MAX) { + context->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); } - r = parse_metrics_matches(argv + 1); + context->n_contacted_sources = set_size(context->link_infos); + + if (context->n_contacted_sources == 0) + return 0; + + r = sd_event_loop(context->event); if (r < 0) - return r; + return log_error_errno(r, "Failed to run event loop: %m"); + + return 1; +} + +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", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_DESCRIBE_METRICS, + "Describe available metrics"); +VERB_FULL(verb_metrics, "generate", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_GENERATE, + "Build a report with metrics"); +VERB_FULL(verb_metrics, "upload", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_UPLOAD, + "Upload a report with 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, ACTION_GENERATE, ACTION_UPLOAD)); + + if (IN_SET(action, ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS)) + /* Enable JSON-SEQ mode for the first two verbs, since we'll dump a large series of JSON + * objects. In the report format, we return a single JSON object, so don't do this. */ + arg_json_format_flags |= SD_JSON_FORMAT_SEQ; _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); + r = parse_metrics_matches(argv + 1, &context.matches); 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_LINKS_MAX) { - n_skipped_sources++; - break; - } - - _cleanup_free_ char *p = path_join(sources_path, d->d_name); - if (!p) - return log_oom(); + r = sd_event_default(&context.event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); - (void) metrics_call(&context, d->d_name, p); - } - } + 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"); - if (set_isempty(context.link_infos)) { + r = context_collect_metrics(&context); + if (r < 0) + return r; + if (r == 0) { if (arg_legend) log_info("No metrics sources found."); } else { - assert(context.event); + switch (action) { - r = sd_event_loop(context.event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); + case ACTION_LIST_METRICS: + case ACTION_DESCRIBE_METRICS: + r = output_collected(&context); + break; + + case ACTION_GENERATE: + r = context_generate_report(&context); + break; - r = metrics_output(&context); + case ACTION_UPLOAD: + r = context_upload_report(&context); + break; + + default: + assert_not_reached(); + } if (r < 0) return r; } - if (n_skipped_sources > 0) + if (context.n_skipped_sources > 0) return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN), - "Too many metrics sources, only %u sources contacted, %zu sources skipped.", - set_size(context.link_infos), n_skipped_sources); + "Too many metrics sources, only %zu sources contacted, %zu sources skipped.", + context.n_contacted_sources, context.n_skipped_sources); if (context.n_invalid_metrics > 0) return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN), "%zu metrics are not valid.", @@ -665,7 +722,8 @@ static int verb_metrics(int argc, char *argv[], void *userdata) { return 0; } -static int verb_list_sources(int argc, char *argv[], void *userdata) { +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"); @@ -674,7 +732,7 @@ static int verb_list_sources(int argc, char *argv[], void *userdata) { _cleanup_free_ char *sources_path = NULL; _cleanup_free_ DirectoryEntries *de = NULL; - r = readdir_sources(&sources_path, &de); + r = readdir_sources(/* matches= */ NULL, &sources_path, &de); if (r < 0) return r; if (r > 0) @@ -720,137 +778,267 @@ static int verb_list_sources(int argc, char *argv[], void *userdata) { return 0; } -static int verb_help(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *link = NULL; +static int vl_method_generate_internal( + sd_varlink *link, + sd_json_variant *parameters, + bool sign) { + int r; - r = terminal_urlify_man("systemd-report", "1", &link); + assert(link); + assert(parameters); + + _cleanup_strv_free_ char **input_matches = NULL; + + static const sd_json_dispatch_field dispatch_table[] = { + { "matches", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, 0, 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &input_matches); + if (r != 0) + return r; + + _cleanup_(context_done) Context context = { + .action = ACTION_GENERATE, + }; + + r = parse_metrics_matches(input_matches, &context.matches); if (r < 0) - return log_oom(); + return sd_varlink_error_invalid_parameter_name(link, "matches"); + + r = sd_event_new(&context.event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + r = context_collect_metrics(&context); + if (r < 0) + return r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *report = NULL; + r = context_build_report(&context, &report); + if (r < 0) + return r; + + if (sign) { + /* Use compact JSON formatting (no pretty/color/seq flags), matching the on-the-wire format + * used for uploads. context_sign_report() adds the JSON-SEQ record separators itself. */ + _cleanup_free_ char *s = NULL; + r = context_sign_report_as_string(&context, report, /* format_flags= */ 0, &s); + if (r < 0) + return r; + + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_BASE64("reportData", s, strlen(s))); + } + + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_VARIANT("report", report)); +} + +static int vl_method_generate( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + return vl_method_generate_internal(link, parameters, /* sign= */ false); +} + +static int vl_method_generate_signed( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + return vl_method_generate_internal(link, parameters, /* sign= */ true); +} - 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", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); +static int vl_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *vs = NULL; + int r; + + r = varlink_server_new(&vs, SD_VARLINK_SERVER_MYSELF_ONLY|SD_VARLINK_SERVER_ROOT_ONLY, /* userdata= */ NULL); + 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_Report); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = sd_varlink_server_bind_method_many( + vs, + "io.systemd.Report.Generate", vl_method_generate, + "io.systemd.Report.GenerateSigned", vl_method_generate_signed); + 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 parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_USER, - ARG_SYSTEM, - ARG_JSON, - }; +static int help(void) { + int r; - 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 }, - {} - }; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; - int c, r; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options, verbs); + + help_cmdline("[OPTIONS...] COMMAND ..."); + help_abstract("Acquire metrics from local sources."); + help_section("Commands"); + + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-report", "1"); + return 0; +} + +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, "hj", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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, opts.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, opts.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, opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("trust", "FILENAME|all", + "Specify CA certificate or disable checking (default: \"" REPORT_TRUST_FILE "\")"): + r = free_and_strdup_warn(&arg_trust, opts.arg); + if (r < 0) + return r; + break; -static int report_main(int argc, char *argv[]) { + OPTION_LONG("network-timeout", "SEC", "Specify timeout for network upload operation"): + r = parse_sec(opts.arg, &arg_network_timeout_usec); + if (r < 0) + return log_error_errno(r, "Failed to parse --network-timeout value: %s", opts.arg); + 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("extra-header", "NAME: VALUE", + "Inject additional header into the upload request"): + if (isempty(opts.arg)) { + arg_extra_headers = strv_free(arg_extra_headers); + break; + } - return dispatch_verb(argc, argv, verbs, NULL); + if (!http_header_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header: %s", opts.arg); + + if (strv_extend(&arg_extra_headers, opts.arg) < 0) + return log_oom(); + break; + + OPTION_LONG("sign", "BOOL", "Sign the generated report."): + r = parse_boolean_argument("--sign", opts.arg, &arg_sign); + if (r < 0) + return r; + 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(&opts); + return 1; } static int run(int argc, char *argv[]) { + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + /* If invoked as a socket-activated Varlink service (Accept=yes), act as the io.systemd.Report + * server instead of running the command line interface. */ + 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 vl_server(); + + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return report_main(argc, argv); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/report/report.h b/src/report/report.h new file mode 100644 index 0000000000000..177491d0d748c --- /dev/null +++ b/src/report/report.h @@ -0,0 +1,38 @@ +/* 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 sd_json_format_flags_t arg_json_format_flags; +extern char *arg_url, *arg_key, *arg_cert, *arg_trust; +extern char **arg_extra_headers; +extern usec_t arg_network_timeout_usec; +extern bool arg_sign; + +typedef enum Action { + ACTION_LIST_METRICS, + ACTION_DESCRIBE_METRICS, + ACTION_GENERATE, + ACTION_UPLOAD, + _ACTION_MAX, + _ACTION_INVALID = -EINVAL, +} Action; + +/* The structure for collected "metrics". */ +typedef struct Context { + Action action; + sd_event *event; + Set *link_infos; + char **matches; /* Metric families to include, or NULL/empty for all */ + sd_json_variant **metrics; /* Collected metrics for sorting */ + size_t n_metrics, n_skipped_metrics, n_invalid_metrics, n_contacted_sources, n_skipped_sources; + + int upload_result; + struct iovec_wrapper upload_answer; +} Context; diff --git a/src/resolve/meson.build b/src/resolve/meson.build index be2979343f3f0..f024b02047c7b 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,9 +70,7 @@ endif resolve_common_template = { 'dependencies' : [ libidn2_cflags, - libopenssl, - libm, - threads, + libopenssl_cflags, ], } diff --git a/src/resolve/org.freedesktop.resolve1.policy b/src/resolve/org.freedesktop.resolve1.policy index 097e78e73ca7e..6fa243856c7a4 100644 --- a/src/resolve/org.freedesktop.resolve1.policy +++ b/src/resolve/org.freedesktop.resolve1.policy @@ -205,4 +205,26 @@ unix-user:systemd-resolve + + Flush DNS caches + Authentication is required to flush DNS caches. + + auth_admin + auth_admin + auth_admin_keep + + unix-user:systemd-resolve + + + + Reset server features + Authentication is required to reset server features. + + auth_admin + auth_admin + auth_admin_keep + + unix-user:systemd-resolve + + diff --git a/src/resolve/resolvconf-compat.c b/src/resolve/resolvconf-compat.c index d8e68d524a094..d3ddec755a244 100644 --- a/src/resolve/resolvconf-compat.c +++ b/src/resolve/resolvconf-compat.c @@ -1,14 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" #include "build.h" #include "extract-word.h" #include "fileio.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" -#include "pretty-print.h" +#include "options.h" #include "resolvconf-compat.h" #include "resolvectl.h" #include "string-util.h" @@ -21,36 +22,32 @@ typedef enum LookupType { } LookupType; static int resolvconf_help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("resolvectl", "1", &link); + r = option_parser_get_help_table_ns("resolvconf", &options); if (r < 0) - return log_oom(); - - printf("%1$s -a INTERFACE < FILE\n" - "%1$s -d INTERFACE\n" - "\n" - "Register DNS server and domain configuration with systemd-resolved.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -a Register per-interface DNS server and domain data\n" - " -d Unregister per-interface DNS server and domain data\n" - " -p Do not use this interface as default route\n" - " -f Ignore if specified interface does not exist\n" - " -x Send DNS traffic preferably over this interface\n" - "\n" + return r; + + help_cmdline("-a INTERFACE = 0); assert(argv); @@ -204,89 +182,86 @@ int resolvconf_parse_argv(int argc, char *argv[]) { arg_mode = _MODE_INVALID; - while ((c = getopt_long(argc, argv, "hadxpfm:uIi:l:Rr:vV", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "resolvconf" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_NAMESPACE("resolvconf"): {} + + OPTION_COMMON_HELP: return resolvconf_help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); /* -a and -d is what everybody can agree on */ - case 'a': + OPTION_SHORT('a', NULL, "Register per-interface DNS server and domain data"): arg_mode = MODE_SET_LINK; break; - case 'd': + OPTION_SHORT('d', NULL, "Unregister per-interface DNS server and domain data"): arg_mode = MODE_REVERT_LINK; break; - /* The exclusive/private/force stuff is an openresolv invention, we support in some skewed way */ - case 'x': - lookup_type = LOOKUP_TYPE_EXCLUSIVE; - break; - - case 'p': + OPTION_SHORT('p', NULL, "Do not use this interface as default route"): lookup_type = LOOKUP_TYPE_PRIVATE; break; - case 'f': + OPTION_SHORT('f', NULL, "Ignore if specified interface does not exist"): arg_ifindex_permissive = true; break; + /* The exclusive/private/force stuff is an openresolv invention, we support in some skewed way */ + OPTION_SHORT('x', NULL, "Send DNS traffic preferably over this interface"): + lookup_type = LOOKUP_TYPE_EXCLUSIVE; + break; + /* The metrics stuff is an openresolv invention we ignore (and don't really need) */ - case 'm': - log_debug("Switch -%c ignored.", c); + OPTION_SHORT('m', "ARG", /* help= */ NULL): + log_debug("Switch -%c ignored.", opts.opt->short_code); break; /* -u supposedly should "update all subscribers". We have no subscribers, hence let's make this a NOP, and exit immediately, cleanly. */ - case 'u': - log_info("Switch -%c ignored.", c); + OPTION_SHORT('u', NULL, /* help= */ NULL): + log_info("Switch -%c ignored.", opts.opt->short_code); return 0; /* The following options are openresolv inventions we don't support. */ - case 'I': - case 'i': - case 'l': - case 'R': - case 'r': - case 'v': - case 'V': + OPTION_SHORT('I', NULL, /* help= */ NULL): {} + OPTION_SHORT('i', "ARG", /* help= */ NULL): {} + OPTION_SHORT('l', "ARG", /* help= */ NULL): {} + OPTION_SHORT('R', NULL, /* help= */ NULL): {} + OPTION_SHORT('r', "ARG", /* help= */ NULL): {} + OPTION_SHORT('v', NULL, /* help= */ NULL): {} + OPTION_SHORT('V', NULL, /* help= */ NULL): return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Switch -%c not supported.", c); + "Switch -%c not supported.", opts.opt->short_code); /* The Debian resolvconf commands we don't support. */ - case ARG_ENABLE_UPDATES: + OPTION_LONG("enable-updates", NULL, /* help= */ NULL): return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Switch --enable-updates not supported."); - case ARG_DISABLE_UPDATES: + OPTION_LONG("disable-updates", NULL, /* help= */ NULL): return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Switch --disable-updates not supported."); - case ARG_UPDATES_ARE_ENABLED: + OPTION_LONG("updates-are-enabled", NULL, /* help= */ NULL): return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Switch --updates-are-enabled not supported."); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_mode == _MODE_INVALID) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected either -a or -d on the command line."); - if (optind+1 != argc) + if (option_parser_get_n_args(&opts) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected interface name as argument."); - r = ifname_resolvconf_mangle(argv[optind]); + r = ifname_resolvconf_mangle(option_parser_get_arg(&opts, 0)); if (r <= 0) return r; - optind++; if (arg_mode == MODE_SET_LINK) { r = parse_stdin(lookup_type); diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 8d887b1fcef06..af7ed4367266d 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -13,39 +12,41 @@ #include "af-list.h" #include "alloc-util.h" +#include "ansi-color.h" #include "argv-util.h" #include "build.h" #include "bus-common-errors.h" #include "bus-error.h" #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" #include "dns-rr.h" #include "errno-list.h" #include "errno-util.h" -#include "escape.h" #include "format-ifname.h" #include "format-table.h" +#include "glyph-util.h" +#include "help-util.h" #include "hostname-util.h" #include "json-util.h" #include "main-func.h" #include "missing-network.h" #include "netlink-util.h" -#include "openssl-util.h" +#include "options.h" #include "ordered-set.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "polkit-agent.h" -#include "pretty-print.h" #include "resolvconf-compat.h" #include "resolve-util.h" #include "resolvectl.h" #include "resolved-def.h" #include "resolved-util.h" +#include "resolve-varlink-util.h" #include "set.h" #include "socket-netlink.h" #include "string-table.h" @@ -122,6 +123,18 @@ static const char* const status_mode_json_field_table[_STATUS_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(status_mode_json_field, StatusMode); +static int strv_extend_extended_bool(char ***strv, const char *name, const char *value) { + int r; + + if (value) { + r = parse_boolean(value); + if (r >= 0) + return strv_extendf(strv, "%s%s", plus_minus(r), name); + } + + return strv_extendf(strv, "%s=%s", name, value ?: "???"); +} + static int acquire_bus(sd_bus **ret) { _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; int r; @@ -264,20 +277,101 @@ static void print_ifindex_comment(int printed_so_far, int ifindex) { ansi_grey(), ifname, ansi_normal()); } -static int resolve_host_error(const char *name, int r, const sd_bus_error *error) { - if (sd_bus_error_has_name(error, BUS_ERROR_DNS_NXDOMAIN)) - return log_error_errno(r, "%s: %s", name, bus_error_message(error, r)); +static int varlink_log_resolve_error(const char *name, const char *error_id, sd_json_variant *reply, bool warn_missing) { + int r; + + assert(name); + assert(!isempty(error_id)); + + int ret = sd_varlink_error_to_errno(error_id, reply); + static const struct { + const char *error_id; + const char *msg; + } error_message_table[] = { + { "io.systemd.Resolve.NoNameServers", "No appropriate name servers or networks for name found" }, + { "io.systemd.Resolve.QueryTimedOut", "Query timed out" }, + { "io.systemd.Resolve.MaxAttemptsReached", "All attempts to contact name servers or networks failed" }, + { "io.systemd.Resolve.InvalidReply", "Received invalid reply" }, + { "io.systemd.Resolve.QueryAborted", "Query aborted" }, + { "io.systemd.Resolve.QueryRefused", "DNS query type refused" }, + { "io.systemd.Resolve.NoTrustAnchor", "No suitable trust anchor known" }, + { "io.systemd.Resolve.ResourceRecordTypeUnsupported", "Server does not support requested resource record type" }, + { "io.systemd.Resolve.NetworkDown", "Network is down" }, + { "io.systemd.Resolve.NoSource", "All suitable resolution sources turned off" }, + { "io.systemd.Resolve.StubLoop", "Configured DNS server loops back to us" }, + { "io.systemd.Resolve.ZoneTransfersNotPermitted", "Zone transfers not permitted via this programming interface" }, + }; + FOREACH_ELEMENT(em, error_message_table) + if (streq(em->error_id, error_id)) + return log_error_errno(ret, "%s: resolve call failed: %s", name, em->msg); + + if (streq(error_id, "io.systemd.Resolve.NoSuchResourceRecord")) + return log_error_errno(ret, "%s: resolve call failed: '%s' does not have any RR of the requested type", name, name); + + if (streq(error_id, "io.systemd.Resolve.CNAMELoop")) + return log_error_errno(ret, "%s: resolve call failed: CNAME loop detected, or CNAME resolving disabled on '%s'", name, name); + + if (streq(error_id, "io.systemd.Resolve.ServiceNotProvided")) + return log_error_errno(ret, "%s: resolve call failed: '%s' does not provide the requested service", name, name); + + if (streq(error_id, "io.systemd.Resolve.InconsistentServiceRecords")) + return log_error_errno(ret, "%s: resolve call failed: '%s' does not provide a consistent set of service resource records", name, name); + + _cleanup_(resolve_error_done) ResolveError error = { + .rcode = _DNS_RCODE_INVALID, + .ede_rcode = _DNS_EDE_RCODE_INVALID, + }; + r = dispatch_resolve_error(/* name = */ NULL, reply, SD_JSON_LOG, &error); + if (r < 0) + log_debug_errno(r, "Failed to dispatch error JSON, ignoring: %m"); + + _cleanup_free_ char *msg_extended = NULL; + if (error.ede_rcode >= 0) { + msg_extended = strjoin(" (", + FORMAT_DNS_EDE_RCODE(error.ede_rcode), + !isempty(error.ede_msg) ? ": " : "", + strempty(error.ede_msg), + ")"); + if (!msg_extended) + return log_oom(); + } + + if (streq(error_id, "io.systemd.Resolve.DNSSECValidationFailed")) + return log_error_errno(ret, "%s: resolve call failed: DNSSEC validation failed: %s%s", name, error.result, strempty(msg_extended)); + + if (error.rcode != _DNS_RCODE_INVALID) { + if (error.rcode == DNS_RCODE_NXDOMAIN) { + if (!warn_missing) + return -ENXIO; + + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "%s: resolve call failed: Name '%s' not found%s%s", + name, error.query_string ?: name, error.ede_rcode >= 0 ? ":" : "", strempty(msg_extended)); + } + + return log_error_errno(ret, "%s: resolve call failed: Could not resolve '%s', server or network returned error: %s%s", + name, error.query_string ?: name, FORMAT_DNS_RCODE(error.rcode), strempty(msg_extended)); + } - return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(error, r)); + return log_error_errno(ret, "%s: resolve call failed: %s", name, error_id); } -static int resolve_host(sd_bus *bus, const char *name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *canonical = NULL; - unsigned c = 0; - uint64_t flags; - usec_t ts; +static int varlink_connect_with_query_timeout(sd_varlink **vl) { + int r; + + assert(vl); + + r = sd_varlink_connect_address(vl, "/run/systemd/resolve/io.systemd.Resolve"); + if (r < 0) + return log_error_errno(r, "Failed to connect to service /run/systemd/resolve/io.systemd.Resolve: %m"); + + r = sd_varlink_set_relative_timeout(*vl, SD_RESOLVED_QUERY_TIMEOUT_USEC); + if (r < 0) + return log_error_errno(r, "Failed to set query timeout: %m"); + + return 0; +} + +static int resolve_host(const char *name) { int r; assert(name); @@ -287,99 +381,76 @@ static int resolve_host(sd_bus *bus, const char *name) { log_debug("Resolving %s (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(arg_ifname) ? "*" : arg_ifname); - r = bus_message_new_method_call(bus, &req, bus_resolve_mgr, "ResolveHostname"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags); + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = varlink_connect_with_query_timeout(&vl); if (r < 0) - return bus_log_create_error(r); + return r; - ts = now(CLOCK_MONOTONIC); + usec_t ts = now(CLOCK_MONOTONIC); - r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply); + const char *error_id = NULL; + sd_json_variant *v = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.Resolve.ResolveHostname", + &v, + &error_id, + SD_JSON_BUILD_PAIR_STRING("name", name), + JSON_BUILD_PAIR_CONDITION_UNSIGNED(arg_ifindex > 0, "ifindex", arg_ifindex), + JSON_BUILD_PAIR_CONDITION_UNSIGNED(arg_family != AF_UNSPEC, "family", arg_family), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("flags", arg_flags)); if (r < 0) - return resolve_host_error(name, r, &error); + return log_error_errno(r, "Failed to issue varlink call: %m"); ts = now(CLOCK_MONOTONIC) - ts; - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (!isempty(error_id)) + return varlink_log_resolve_error(name, error_id, v, /* warn_missing = */ true); + + _cleanup_(resolve_hostname_reply_done) ResolveHostnameReply reply = {}; + r = dispatch_resolve_hostname_reply(/* name = */ NULL, v, SD_JSON_LOG, &reply); if (r < 0) - return bus_log_parse_error(r); + return r; - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + bool first = true; + FOREACH_ARRAY(address, reply.addresses, reply.n_addresses) { _cleanup_free_ char *pretty = NULL; - int ifindex, family, k; - union in_addr_union a; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "i", &ifindex); - if (r < 0) - return bus_log_parse_error(r); - - sd_bus_error_free(&error); - r = bus_message_read_in_addr_auto(reply, &error, &family, &a); - if (r < 0 && !sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)) - return log_error_errno(r, "%s: systemd-resolved returned invalid result: %s", name, bus_error_message(&error, r)); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)) { - log_debug_errno(r, "%s: systemd-resolved returned invalid result, ignoring: %s", name, bus_error_message(&error, r)); - continue; - } - - r = in_addr_ifindex_to_string(family, &a, ifindex, &pretty); + r = in_addr_ifindex_to_string(address->family, &address->in_addr.address, address->ifindex, &pretty); if (r < 0) return log_error_errno(r, "Failed to print address for %s: %m", name); - k = printf("%*s%s %s%s%s", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - ansi_highlight(), pretty, ansi_normal()); + int k = printf("%*s%s %s%s%s", + (int) strlen(name), + first ? name : "", + first ? ":" : " ", + ansi_highlight(), + pretty, + ansi_normal()); - print_ifindex_comment(k, ifindex); + print_ifindex_comment(k, address->ifindex); fputc('\n', stdout); - c++; + first = false; } - 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_read(reply, "st", &canonical, &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(name, canonical)) + if (!streq(name, reply.name)) printf("%*s%s (%s)\n", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - canonical); + (int) strlen(name), + reply.n_addresses == 0 ? name : "", + reply.n_addresses == 0 ? ":" : " ", + reply.name); - if (c == 0) - return log_error_errno(SYNTHETIC_ERRNO(ESRCH), - "%s: no addresses found", name); + if (reply.n_addresses == 0) + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "%s: no addresses found", name); - print_source(flags, ts); + print_source(reply.flags, ts); return 0; } -static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *pretty = NULL; - uint64_t flags; - unsigned c = 0; - usec_t ts; +static int resolve_address(int family, const union in_addr_union *address, int ifindex) { int r; - assert(bus); assert(IN_SET(family, AF_INET, AF_INET6)); assert(address); @@ -389,93 +460,72 @@ static int resolve_address(sd_bus *bus, int family, const union in_addr_union *a if (ifindex <= 0) ifindex = arg_ifindex; + _cleanup_free_ char *pretty = NULL; r = in_addr_ifindex_to_string(family, address, ifindex, &pretty); if (r < 0) return log_oom(); log_debug("Resolving %s.", pretty); - r = bus_message_new_method_call(bus, &req, bus_resolve_mgr, "ResolveAddress"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "ii", ifindex, family); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family)); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "t", arg_flags); + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = varlink_connect_with_query_timeout(&vl); if (r < 0) - return bus_log_create_error(r); + return r; - ts = now(CLOCK_MONOTONIC); + usec_t ts = now(CLOCK_MONOTONIC); - r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply); + const char *error_id = NULL; + sd_json_variant *v = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.Resolve.ResolveAddress", + &v, + &error_id, + SD_JSON_BUILD_PAIR_BYTE_ARRAY("address", &address->bytes, FAMILY_ADDRESS_SIZE_SAFE(family)), + SD_JSON_BUILD_PAIR_UNSIGNED("family", family), + JSON_BUILD_PAIR_CONDITION_UNSIGNED(ifindex > 0, "ifindex", ifindex), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("flags", arg_flags)); if (r < 0) - return log_error_errno(r, "%s: resolve call failed: %s", pretty, bus_error_message(&error, r)); + return log_error_errno(r, "Failed to issue varlink call: %m"); ts = now(CLOCK_MONOTONIC) - ts; - r = sd_bus_message_enter_container(reply, 'a', "(is)"); - if (r < 0) - return bus_log_create_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "is")) > 0) { - const char *n; - int k; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "is", &ifindex, &n); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return r; + if (!isempty(error_id)) + return varlink_log_resolve_error(pretty, error_id, v, /* warn_missing = */ true); - k = printf("%*s%s %s%s%s", - (int) strlen(pretty), c == 0 ? pretty : "", - c == 0 ? ":" : " ", - ansi_highlight(), n, ansi_normal()); + _cleanup_(resolve_address_reply_done) ResolveAddressReply reply = {}; + r = dispatch_resolve_address_reply(/* name = */ NULL, v, SD_JSON_LOG, &reply); + if (r < 0) + return r; - print_ifindex_comment(k, ifindex); + bool first = true; + FOREACH_ARRAY(name, reply.names, reply.n_names) { + int k = printf("%*s%s %s%s%s", + (int) strlen(pretty), + first ? pretty : "", + first ? ":" : " ", + ansi_highlight(), + name->name, + ansi_normal()); + + print_ifindex_comment(k, name->ifindex); fputc('\n', stdout); - c++; + first = false; } - 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_read(reply, "t", &flags); - if (r < 0) - return bus_log_parse_error(r); - if (c == 0) - return log_error_errno(SYNTHETIC_ERRNO(ESRCH), - "%s: no names found", pretty); + if (reply.n_names == 0) + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "%s: no names found", pretty); - print_source(flags, ts); + print_source(reply.flags, ts); return 0; } -static int output_rr_packet(const void *d, size_t l, int ifindex) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; +static int output_rr_packet(DnsResourceRecord *rr, int ifindex) { int r; - assert(d || l == 0); - - r = dns_resource_record_new_from_raw(&rr, d, l); - if (r < 0) - return log_error_errno(r, "Failed to parse RR: %m"); + assert(rr); if (sd_json_format_enabled(arg_json_format_flags)) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; @@ -558,14 +608,7 @@ static bool single_label_nonsynthetic(const char *name) { return !streq(name, first_label); } -static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type, bool warn_missing) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *idnafied = NULL; - bool needs_authentication = false; - unsigned n = 0; - uint64_t flags; - usec_t ts; +static int resolve_record(const char *name, uint16_t class, uint16_t type, bool warn_missing) { int r; assert(name); @@ -576,6 +619,7 @@ static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_ log_notice("(Note that search domains are not appended when --type= is specified. " "Please specify fully qualified domain names, or remove --type= switch from invocation in order to request regular hostname resolution.)"); + _cleanup_free_ char *idnafied = NULL; r = idna_candidate(name, &idnafied); if (r < 0) return r; @@ -584,85 +628,76 @@ static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_ "Please specify translated domain names — i.e. '%s' — when resolving raw records, or remove --type= switch from invocation in order to request regular hostname resolution.", idnafied); - r = bus_message_new_method_call(bus, &req, bus_resolve_mgr, "ResolveRecord"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, arg_flags); + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = varlink_connect_with_query_timeout(&vl); if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - if (warn_missing || r != -ENXIO) - log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r)); return r; - } - ts = now(CLOCK_MONOTONIC) - ts; + usec_t ts = now(CLOCK_MONOTONIC); - r = sd_bus_message_enter_container(reply, 'a', "(iqqay)"); + const char *error_id = NULL; + sd_json_variant *v = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.Resolve.ResolveRecord", + &v, + &error_id, + SD_JSON_BUILD_PAIR_STRING("name", name), + SD_JSON_BUILD_PAIR_UNSIGNED("type", type), + JSON_BUILD_PAIR_CONDITION_UNSIGNED(arg_ifindex > 0, "ifindex", arg_ifindex), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("class", class), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("flags", arg_flags)); if (r < 0) - return bus_log_parse_error(r); + return log_error_errno(r, "Failed to issue varlink call: %m"); - while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) { - uint16_t c, t; - int ifindex; - const void *d; - size_t l; + ts = now(CLOCK_MONOTONIC) - ts; - assert_cc(sizeof(int) == sizeof(int32_t)); + if (!isempty(error_id)) { + if (streq(error_id, "io.systemd.Resolve.ResourceRecordTypeInvalidForQuery")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified resource record type %" PRIu16 " may not be used in a query", type); - r = sd_bus_message_read(reply, "iqq", &ifindex, &c, &t); - if (r < 0) - return bus_log_parse_error(r); + if (streq(error_id, "io.systemd.Resolve.ResourceRecordTypeObsolete")) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Specified DNS resource record type %" PRIu16 " is obsolete", type); - r = sd_bus_message_read_array(reply, 'y', &d, &l); - if (r < 0) - return bus_log_parse_error(r); + return varlink_log_resolve_error(name, error_id, v, warn_missing); + } - r = sd_bus_message_exit_container(reply); + _cleanup_(resolve_record_reply_done) ResolveRecordReply reply = {}; + r = dispatch_resolve_record_reply(/* name = */ NULL, v, SD_JSON_LOG, &reply); + if (r < 0) + return r; + + bool needs_authentication = false; + FOREACH_ARRAY(record, reply.records, reply.n_records) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + r = dns_resource_record_new_from_raw(&rr, record->raw.iov_base, record->raw.iov_len); if (r < 0) - return bus_log_parse_error(r); + return log_error_errno(r, "Failed to parse RR: %m"); if (arg_raw == RAW_PACKET) { - uint64_t u64 = htole64(l); + uint64_t u64 = htole64(record->raw.iov_len); fwrite(&u64, sizeof(u64), 1, stdout); - fwrite(d, 1, l, stdout); + fwrite(record->raw.iov_base, 1, record->raw.iov_len, stdout); } else { - r = output_rr_packet(d, l, ifindex); + r = output_rr_packet(rr, record->ifindex); if (r < 0) return r; } - if (dns_type_needs_authentication(t)) + if (dns_type_needs_authentication(rr->key->type)) needs_authentication = true; - - n++; } - 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_read(reply, "t", &flags); - if (r < 0) - return bus_log_parse_error(r); - if (n == 0) { + if (reply.n_records == 0) { if (warn_missing) log_error("%s: no records found", name); return -ESRCH; } - print_source(flags, ts); + print_source(reply.flags, ts); - if ((flags & SD_RESOLVED_AUTHENTICATED) == 0 && needs_authentication) { + if ((reply.flags & SD_RESOLVED_AUTHENTICATED) == 0 && needs_authentication) { fflush(stdout); fprintf(stderr, "\n%s" @@ -677,12 +712,11 @@ static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_ return 0; } -static int resolve_rfc4501(sd_bus *bus, const char *name) { +static int resolve_rfc4501(const char *name) { uint16_t type = 0, class = 0; const char *p, *q, *n; int r; - assert(bus); assert(name); assert(startswith(name, "dns:")); @@ -780,56 +814,44 @@ static int resolve_rfc4501(sd_bus *bus, const char *name) { if (type == 0) type = arg_type ?: DNS_TYPE_A; - return resolve_record(bus, n, class, type, true); + return resolve_record(n, class, type, true); invalid: return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid DNS URI: %s", name); } -static int verb_query(int argc, char **argv, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; +VERB(verb_query, "query", "HOSTNAME|ADDRESS…", 2, VERB_ANY, 0, + "Resolve domain names, IPv4 and IPv6 addresses"); +static int verb_query(int argc, char *argv[], uintptr_t _data, void *userdata) { int ret = 0, r; - r = acquire_bus(&bus); - if (r < 0) - return r; - if (arg_type != 0) STRV_FOREACH(p, strv_skip(argv, 1)) - RET_GATHER(ret, resolve_record(bus, *p, arg_class, arg_type, true)); + RET_GATHER(ret, resolve_record(*p, arg_class, arg_type, true)); else STRV_FOREACH(p, strv_skip(argv, 1)) { if (startswith(*p, "dns:")) - RET_GATHER(ret, resolve_rfc4501(bus, *p)); + RET_GATHER(ret, resolve_rfc4501(*p)); else { int family, ifindex; union in_addr_union a; r = in_addr_ifindex_from_string_auto(*p, &family, &a, &ifindex); if (r >= 0) - RET_GATHER(ret, resolve_address(bus, family, &a, ifindex)); + RET_GATHER(ret, resolve_address(family, &a, ifindex)); else - RET_GATHER(ret, resolve_host(bus, *p)); + RET_GATHER(ret, resolve_host(*p)); } } return ret; } -static int resolve_service(sd_bus *bus, const char *name, const char *type, const char *domain) { - const char *canonical_name, *canonical_type, *canonical_domain; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - size_t indent, sz; - uint64_t flags; - const char *p; - unsigned c; - usec_t ts; +static int resolve_service(const char *name, const char *type, const char *domain) { int r; - assert(bus); assert(domain); name = empty_to_null(name); @@ -842,141 +864,83 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons else log_debug("Resolving service type %s (family %s, interface %s).", domain, af_to_name(arg_family) ?: "*", isempty(arg_ifname) ? "*" : arg_ifname); - r = bus_message_new_method_call(bus, &req, bus_resolve_mgr, "ResolveService"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isssit", arg_ifindex, name, type, domain, arg_family, arg_flags); + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = varlink_connect_with_query_timeout(&vl); if (r < 0) - return bus_log_create_error(r); + return r; - ts = now(CLOCK_MONOTONIC); + usec_t ts = now(CLOCK_MONOTONIC); - r = sd_bus_call(bus, req, SD_RESOLVED_QUERY_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return log_error_errno(r, "Resolve call failed: %s", bus_error_message(&error, r)); + const char *error_id = NULL; + sd_json_variant *v = NULL; + r = sd_varlink_callbo( + vl, + "io.systemd.Resolve.ResolveService", + &v, + &error_id, + SD_JSON_BUILD_PAIR_STRING("domain", domain), + JSON_BUILD_PAIR_STRING_NON_EMPTY("name", name), + JSON_BUILD_PAIR_STRING_NON_EMPTY("type", type), + JSON_BUILD_PAIR_CONDITION_UNSIGNED(arg_ifindex > 0, "ifindex", arg_ifindex), + JSON_BUILD_PAIR_CONDITION_UNSIGNED(arg_family != AF_UNSPEC, "family", arg_family), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("flags", arg_flags)); + if (r < 0) + return log_error_errno(r, "Failed to issue varlink call: %m"); ts = now(CLOCK_MONOTONIC) - ts; - r = sd_bus_message_enter_container(reply, 'a', "(qqqsa(iiay)s)"); - if (r < 0) - return bus_log_parse_error(r); - - indent = - (name ? strlen(name) + 1 : 0) + - (type ? strlen(type) + 1 : 0) + - strlen(domain) + 2; + if (!isempty(error_id)) + return varlink_log_resolve_error(domain, error_id, v, /* warn_missing = */ true); - c = 0; - while ((r = sd_bus_message_enter_container(reply, 'r', "qqqsa(iiay)s")) > 0) { - uint16_t priority, weight, port; - const char *hostname, *canonical; + _cleanup_(resolve_service_reply_done) ResolveServiceReply reply = {}; + r = dispatch_resolve_service_reply(/* name = */ NULL, v, SD_JSON_LOG, &reply); + if (r < 0) + return r; - r = sd_bus_message_read(reply, "qqqs", &priority, &weight, &port, &hostname); - if (r < 0) - return bus_log_parse_error(r); + size_t indent = (name ? strlen(name) + 1 : 0) + + (type ? strlen(type) + 1 : 0) + + strlen(domain) + 2; + bool first = true; + FOREACH_ARRAY(service, reply.services, reply.n_services) { if (name) - printf("%*s%s", (int) strlen(name), c == 0 ? name : "", c == 0 ? "/" : " "); + printf("%*s%s", (int) strlen(name), first ? name : "", first ? "/" : " "); if (type) - printf("%*s%s", (int) strlen(type), c == 0 ? type : "", c == 0 ? "/" : " "); + printf("%*s%s", (int) strlen(type), first ? type : "", first ? "/" : " "); printf("%*s%s %s:%u [priority=%u, weight=%u]\n", - (int) strlen(domain), c == 0 ? domain : "", - c == 0 ? ":" : " ", - hostname, port, - priority, weight); - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + (int) strlen(domain), + first ? domain : "", + first ? ":" : " ", + service->hostname, + service->port, + service->priority, + service->weight); + + FOREACH_ARRAY(address, service->addresses, service->n_addresses) { _cleanup_free_ char *pretty = NULL; - int ifindex, family, k; - union in_addr_union a; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "i", &ifindex); - if (r < 0) - return bus_log_parse_error(r); - - sd_bus_error_free(&error); - r = bus_message_read_in_addr_auto(reply, &error, &family, &a); - if (r < 0 && !sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)) - return log_error_errno(r, "%s: systemd-resolved returned invalid result: %s", name, bus_error_message(&error, r)); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)) { - log_debug_errno(r, "%s: systemd-resolved returned invalid result, ignoring: %s", name, bus_error_message(&error, r)); - continue; - } - - r = in_addr_ifindex_to_string(family, &a, ifindex, &pretty); + r = in_addr_ifindex_to_string(address->family, &address->in_addr.address, address->ifindex, &pretty); if (r < 0) return log_error_errno(r, "Failed to print address for %s: %m", name); - k = printf("%*s%s", (int) indent, "", pretty); - print_ifindex_comment(k, ifindex); + int k = printf("%*s%s", (int) indent, "", pretty); + + print_ifindex_comment(k, address->ifindex); fputc('\n', stdout); } - 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_read(reply, "s", &canonical); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(hostname, canonical)) - printf("%*s(%s)\n", (int) indent, "", canonical); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - c++; - } - 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_enter_container(reply, 'a', "ay"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read_array(reply, 'y', (const void**) &p, &sz)) > 0) { - _cleanup_free_ char *escaped = NULL; - escaped = cescape_length(p, sz); - if (!escaped) - return log_oom(); + if (service->canonical_name && !streq_ptr(service->hostname, service->canonical_name)) + printf("%*s(%s)\n", (int) indent, "", service->canonical_name); - printf("%*s%s\n", (int) indent, "", escaped); + first = false; } - 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_read(reply, "ssst", &canonical_name, &canonical_type, &canonical_domain, &flags); - if (r < 0) - return bus_log_parse_error(r); + STRV_FOREACH(p, reply.txt) + printf("%*s%s\n", (int) indent, "", *p); - canonical_name = empty_to_null(canonical_name); - canonical_type = empty_to_null(canonical_type); + const char *canonical_name = empty_to_null(reply.canonical.name); + const char *canonical_type = empty_to_null(reply.canonical.type); + const char *canonical_domain = reply.canonical.domain; if (!streq_ptr(name, canonical_name) || !streq_ptr(type, canonical_type) || @@ -992,35 +956,29 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons printf("%s)\n", canonical_domain); } - print_source(flags, ts); + print_source(reply.flags, ts); return 0; } -static int verb_service(int argc, char **argv, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - +VERB(verb_service, "service", "[[NAME] TYPE] DOMAIN", 2, 4, 0, + "Resolve service (SRV)"); +static int verb_service(int argc, char *argv[], uintptr_t _data, void *userdata) { if (sd_json_format_enabled(arg_json_format_flags)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); if (argc == 2) - return resolve_service(bus, NULL, NULL, argv[1]); + return resolve_service(NULL, NULL, argv[1]); else if (argc == 3) - return resolve_service(bus, NULL, argv[1], argv[2]); + return resolve_service(NULL, argv[1], argv[2]); else - return resolve_service(bus, argv[1], argv[2], argv[3]); + return resolve_service(argv[1], argv[2], argv[3]); } #if HAVE_OPENSSL -static int resolve_openpgp(sd_bus *bus, const char *address) { +static int resolve_openpgp(const char *address) { int r; - assert(bus); assert(address); const char *domain = strrchr(address, '@'); @@ -1051,7 +1009,6 @@ static int resolve_openpgp(sd_bus *bus, const char *address) { log_debug("Looking up \"%s\".", full); r = resolve_record( - bus, full, arg_class ?: DNS_CLASS_IN, arg_type ?: DNS_TYPE_OPENPGPKEY, @@ -1071,7 +1028,6 @@ static int resolve_openpgp(sd_bus *bus, const char *address) { log_debug("Looking up \"%s\".", full); return resolve_record( - bus, full, arg_class ?: DNS_CLASS_IN, arg_type ?: DNS_TYPE_OPENPGPKEY, @@ -1079,20 +1035,17 @@ static int resolve_openpgp(sd_bus *bus, const char *address) { } #endif -static int verb_openpgp(int argc, char **argv, void *userdata) { +VERB(verb_openpgp, "openpgp", "EMAIL@DOMAIN…", 2, VERB_ANY, 0, + "Query OpenPGP public key"); +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; - - r = acquire_bus(&bus); - if (r < 0) - return r; + int ret = 0; if (sd_json_format_enabled(arg_json_format_flags)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); STRV_FOREACH(p, strv_skip(argv, 1)) - RET_GATHER(ret, resolve_openpgp(bus, *p)); + RET_GATHER(ret, resolve_openpgp(*p)); return ret; #else @@ -1100,13 +1053,12 @@ static int verb_openpgp(int argc, char **argv, void *userdata) { #endif } -static int resolve_tlsa(sd_bus *bus, const char *family, const char *address) { +static int resolve_tlsa(const char *family, const char *address) { const char *port; uint16_t port_num = 443; _cleanup_free_ char *full = NULL; int r; - assert(bus); assert(address); port = strrchr(address, ':'); @@ -1127,7 +1079,7 @@ static int resolve_tlsa(sd_bus *bus, const char *family, const char *address) { log_debug("Looking up \"%s\".", full); - return resolve_record(bus, full, + return resolve_record(full, arg_class ?: DNS_CLASS_IN, arg_type ?: DNS_TYPE_TLSA, true); } @@ -1136,18 +1088,15 @@ 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) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; +VERB(verb_tlsa, "tlsa", "DOMAIN[:PORT]…", 2, VERB_ANY, 0, + "Query TLS public key"); +static int verb_tlsa(int argc, char *argv[], uintptr_t _data, void *userdata) { const char *family = "tcp"; char **args; - int r, ret = 0; + int ret = 0; assert(argc >= 2); - r = acquire_bus(&bus); - if (r < 0) - return r; - if (sd_json_format_enabled(arg_json_format_flags)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); @@ -1158,266 +1107,283 @@ static int verb_tlsa(int argc, char **argv, void *userdata) { args = strv_skip(argv, 1); STRV_FOREACH(p, args) - RET_GATHER(ret, resolve_tlsa(bus, family, *p)); + RET_GATHER(ret, resolve_tlsa(family, *p)); return ret; } -static int show_statistics(int argc, char **argv, void *userdata) { - _cleanup_(table_unrefp) Table *table = NULL; - sd_json_variant *reply = NULL; +static int varlink_dump_dns_configuration(sd_json_variant **ret) { _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; + sd_json_variant *v; int r; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + assert(ret); - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve"); if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + return log_error_errno(r, "Failed to connect to service /run/systemd/resolve/io.systemd.Resolve: %m"); - r = varlink_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.DumpStatistics", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + r = varlink_call_and_log(vl, "io.systemd.Resolve.DumpDNSConfiguration", /* parameters= */ NULL, &reply); if (r < 0) return r; - if (sd_json_format_enabled(arg_json_format_flags)) - return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); + v = sd_json_variant_by_key(reply, "configuration"); - struct statistics { - sd_json_variant *transactions; - sd_json_variant *cache; - sd_json_variant *dnssec; - } statistics; + if (!sd_json_variant_is_array(v)) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "DumpDNSConfiguration() response missing 'configuration' key."); - static const sd_json_dispatch_field statistics_dispatch_table[] = { - { "transactions", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, transactions), SD_JSON_MANDATORY }, - { "cache", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, cache), SD_JSON_MANDATORY }, - { "dnssec", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, dnssec), SD_JSON_MANDATORY }, - {}, - }; + TAKE_PTR(reply); + *ret = sd_json_variant_ref(v); + return 0; +} - r = sd_json_dispatch(reply, statistics_dispatch_table, SD_JSON_LOG, &statistics); - if (r < 0) - return r; +static int status_json_filter_links(sd_json_variant **configuration, char **links) { + _cleanup_set_free_ Set *links_by_index = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + sd_json_variant *w; + int r; - struct transactions { - uint64_t n_current_transactions; - uint64_t n_transactions_total; - uint64_t n_timeouts_total; - uint64_t n_timeouts_served_stale_total; - uint64_t n_failure_responses_total; - uint64_t n_failure_responses_served_stale_total; - } transactions; + assert(configuration); - static const sd_json_dispatch_field transactions_dispatch_table[] = { - { "currentTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_current_transactions), SD_JSON_MANDATORY }, - { "totalTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_transactions_total), SD_JSON_MANDATORY }, - { "totalTimeouts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_total), SD_JSON_MANDATORY }, - { "totalTimeoutsServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_served_stale_total), SD_JSON_MANDATORY }, - { "totalFailedResponses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_total), SD_JSON_MANDATORY }, - { "totalFailedResponsesServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_served_stale_total), SD_JSON_MANDATORY }, - {}, - }; + if (links) + STRV_FOREACH(ifname, links) { + int ifindex = rtnl_resolve_interface_or_warn(/* rtnl= */ NULL, *ifname); + if (ifindex < 0) + return ifindex; - r = sd_json_dispatch(statistics.transactions, transactions_dispatch_table, SD_JSON_LOG, &transactions); - if (r < 0) - return r; + r = set_ensure_put(&links_by_index, NULL, INT_TO_PTR(ifindex)); + if (r < 0) + return r; + } - struct cache { - uint64_t cache_size; - uint64_t n_cache_hit; - uint64_t n_cache_miss; - } cache; + JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { + int ifindex = sd_json_variant_unsigned(sd_json_variant_by_key(w, "ifindex")); - static const sd_json_dispatch_field cache_dispatch_table[] = { - { "size", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, cache_size), SD_JSON_MANDATORY }, - { "hits", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_hit), SD_JSON_MANDATORY }, - { "misses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_miss), SD_JSON_MANDATORY }, - {}, - }; + if (links_by_index) { + if (ifindex <= 0) + /* Possibly invalid, but most likely unset because this is global + * or delegate configuration. */ + continue; - r = sd_json_dispatch(statistics.cache, cache_dispatch_table, SD_JSON_LOG, &cache); - if (r < 0) - return r; + if (!set_contains(links_by_index, INT_TO_PTR(ifindex))) + continue; - struct dnsssec { - uint64_t n_dnssec_secure; - uint64_t n_dnssec_insecure; - uint64_t n_dnssec_bogus; - uint64_t n_dnssec_indeterminate; - } dnsssec; + } else if (ifindex == LOOPBACK_IFINDEX) + /* By default, exclude the loopback interface. */ + continue; - static const sd_json_dispatch_field dnssec_dispatch_table[] = { - { "secure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_secure), SD_JSON_MANDATORY }, - { "insecure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_insecure), SD_JSON_MANDATORY }, - { "bogus", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_bogus), SD_JSON_MANDATORY }, - { "indeterminate", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_indeterminate), SD_JSON_MANDATORY }, - {}, - }; + r = sd_json_variant_append_array(&v, w); + if (r < 0) + return r; + } - r = sd_json_dispatch(statistics.dnssec, dnssec_dispatch_table, SD_JSON_LOG, &dnsssec); - if (r < 0) - return r; + return json_variant_unref_and_replace(*configuration, v); +} - table = table_new_vertical(); - if (!table) - return log_oom(); +static int status_json_filter_fields(sd_json_variant **configuration, StatusMode mode) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + sd_json_variant *w; + const char *field; + int r; - r = table_add_many(table, - TABLE_STRING, "Transactions", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Current Transactions", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, transactions.n_current_transactions, - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_FIELD, "Total Transactions", - TABLE_UINT64, transactions.n_transactions_total, - TABLE_EMPTY, TABLE_EMPTY, - TABLE_STRING, "Cache", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Current Cache Size", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, cache.cache_size, - TABLE_FIELD, "Cache Hits", - TABLE_UINT64, cache.n_cache_hit, - TABLE_FIELD, "Cache Misses", - TABLE_UINT64, cache.n_cache_miss, - TABLE_EMPTY, TABLE_EMPTY, - TABLE_STRING, "Failure Transactions", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Total Timeouts", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, transactions.n_timeouts_total, - TABLE_FIELD, "Total Timeouts (Stale Data Served)", - TABLE_UINT64, transactions.n_timeouts_served_stale_total, - TABLE_FIELD, "Total Failure Responses", - TABLE_UINT64, transactions.n_failure_responses_total, - TABLE_FIELD, "Total Failure Responses (Stale Data Served)", - TABLE_UINT64, transactions.n_failure_responses_served_stale_total, - TABLE_EMPTY, TABLE_EMPTY, - TABLE_STRING, "DNSSEC Verdicts", - TABLE_SET_COLOR, ansi_highlight(), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, - TABLE_FIELD, "Secure", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, dnsssec.n_dnssec_secure, - TABLE_FIELD, "Insecure", - TABLE_UINT64, dnsssec.n_dnssec_insecure, - TABLE_FIELD, "Bogus", - TABLE_UINT64, dnsssec.n_dnssec_bogus, - TABLE_FIELD, "Indeterminate", - TABLE_UINT64, dnsssec.n_dnssec_indeterminate - ); - if (r < 0) - return table_log_add_error(r); + assert(configuration); - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); + field = status_mode_json_field_to_string(mode); + if (!field) + /* Nothing to filter for this mode. */ + return 0; - return 0; + JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { + /* Always include identifier fields like ifname or delegate, and include the requested + * field even if it is empty in the configuration. */ + r = sd_json_variant_append_arraybo( + &v, + JSON_BUILD_PAIR_VARIANT_NON_NULL("ifname", sd_json_variant_by_key(w, "ifname")), + JSON_BUILD_PAIR_VARIANT_NON_NULL("ifindex", sd_json_variant_by_key(w, "ifindex")), + JSON_BUILD_PAIR_VARIANT_NON_NULL("delegate", sd_json_variant_by_key(w, "delegate")), + SD_JSON_BUILD_PAIR_VARIANT(field, sd_json_variant_by_key(w, field))); + if (r < 0) + return r; + } + + return json_variant_unref_and_replace(*configuration, v); } -static int reset_statistics(int argc, char **argv, void *userdata) { - sd_json_variant *reply = NULL; - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; +static int format_dns_server_one(DNSConfiguration *configuration, DNSServer *s, char **ret) { + bool global; int r; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + assert(s); + assert(ret); - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); - if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + global = !(configuration->ifindex > 0 || configuration->delegate); - r = varlink_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.ResetStatistics", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (global && s->ifindex > 0 && s->ifindex != LOOPBACK_IFINDEX) { + /* This one has an (non-loopback) ifindex set, and we were told to suppress those. Hence do so. */ + *ret = NULL; + return 0; + } + + r = in_addr_port_ifindex_name_to_string( + s->family, + &s->in_addr, + s->port != 53 ? s->port : 0, + s->ifindex, + s->server_name, + ret); if (r < 0) return r; - if (sd_json_format_enabled(arg_json_format_flags)) - return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); - - return 0; + return 1; } -static int flush_caches(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; +static int format_dns_servers(DNSConfiguration *configuration, OrderedSet *servers, char ***ret) { int r; - r = acquire_bus(&bus); - if (r < 0) - return r; + assert(ret); - r = bus_call_method(bus, bus_resolve_mgr, "FlushCaches", &error, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to flush caches: %s", bus_error_message(&error, r)); + _cleanup_strv_free_ char **l = NULL; + DNSServer *s; + ORDERED_SET_FOREACH(s, servers) { + _cleanup_free_ char *str = NULL; + r = format_dns_server_one(configuration, s, &str); + if (r < 0) + return r; + if (r > 0) { + r = strv_consume(&l, TAKE_PTR(str)); + if (r < 0) + return log_oom(); + } + } + *ret = TAKE_PTR(l); return 0; } -static int reset_server_features(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; +static int format_search_domains(DNSConfiguration *configuration, OrderedSet *domains, char ***ret) { int r; - r = acquire_bus(&bus); - if (r < 0) - return r; + assert(ret); - r = bus_call_method(bus, bus_resolve_mgr, "ResetServerFeatures", &error, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to reset server features: %s", bus_error_message(&error, r)); + _cleanup_strv_free_ char **l = NULL; + SearchDomain *d; + ORDERED_SET_FOREACH(d, domains) { + if (!(configuration->ifindex > 0 || configuration->delegate) && d->ifindex > 0) + /* Only show the global ones here */ + continue; + + _cleanup_free_ char *str = NULL; + if (d->route_only) + str = strjoin("~", d->name); + else + str = strdup(d->name); + if (!str) + return log_oom(); + r = strv_consume(&l, TAKE_PTR(str)); + if (r < 0) + return log_oom(); + } + + *ret = TAKE_PTR(l); return 0; } -static int status_print_strv(DNSConfiguration *c, char **p) { - const unsigned indent = strlen("Global: "); /* Use the same indentation everywhere to make things nice */ - int pos1, pos2; +static int format_protocol_status(DNSConfiguration *configuration, char ***ret) { + _cleanup_strv_free_ char **s = NULL; + int r; - assert(c); + assert(configuration); + assert(ret); - if (c->ifname) - printf("%s%nLink %i (%s)%n%s:", ansi_highlight(), &pos1, c->ifindex, c->ifname, &pos2, ansi_normal()); - else if (c->delegate) - printf("%s%nDelegate %s%n%s:", ansi_highlight(), &pos1, c->delegate, &pos2, ansi_normal()); - else - printf("%s%nGlobal%n%s:", ansi_highlight(), &pos1, &pos2, ansi_normal()); + if (configuration->ifindex > 0) { + r = strv_extendf(&s, "%sDefaultRoute", plus_minus(configuration->default_route)); + if (r < 0) + return r; + } - size_t cols = columns(), position = pos2 - pos1 + 2; + r = strv_extend_extended_bool(&s, "LLMNR", configuration->llmnr_mode_str); + if (r < 0) + return r; - STRV_FOREACH(i, p) { - size_t our_len = utf8_console_width(*i); /* This returns -1 on invalid utf-8 (which shouldn't happen). - * If that happens, we'll just print one item per line. */ + r = strv_extend_extended_bool(&s, "mDNS", configuration->mdns_mode_str); + if (r < 0) + return r; - if (position <= indent || size_add(size_add(position, 1), our_len) < cols) { - printf(" %s", *i); - position = size_add(size_add(position, 1), our_len); - } else { - printf("\n%*s%s", (int) indent, "", *i); - position = size_add(our_len, indent); - } + r = strv_extend_extended_bool(&s, "DNSOverTLS", configuration->dns_over_tls_mode_str); + if (r < 0) + return r; + + r = strv_extendf(&s, "DNSSEC=%s/%s", + configuration->dnssec_mode_str ?: "???", + configuration->dnssec_supported ? "supported" : "unsupported"); + if (r < 0) + return r; + + *ret = TAKE_PTR(s); + return 0; +} + +static int format_scopes_string(DNSConfiguration *configuration, char **ret) { + assert(configuration); + assert(ret); + + if (!configuration->dns_scopes) { + *ret = NULL; + return 0; } - printf("\n"); + uint64_t scopes_mask = 0; + DNSScope *scope; + SET_FOREACH(scope, configuration->dns_scopes) { + if (streq(scope->protocol, "dns")) + scopes_mask |= SD_RESOLVED_DNS; + else if (streq(scope->protocol, "llmnr")) + scopes_mask |= scope->family == AF_INET ? + SD_RESOLVED_LLMNR_IPV4 : + SD_RESOLVED_LLMNR_IPV6; + else if (streq(scope->protocol, "mdns")) + scopes_mask |= scope->family == AF_INET ? + SD_RESOLVED_MDNS_IPV4 : + SD_RESOLVED_MDNS_IPV6; + } + + _cleanup_free_ char *buf = NULL; + if (asprintf(&buf, "%s%s%s%s%s", + scopes_mask & SD_RESOLVED_DNS ? "DNS " : "", + scopes_mask & SD_RESOLVED_LLMNR_IPV4 ? "LLMNR/IPv4 " : "", + scopes_mask & SD_RESOLVED_LLMNR_IPV6 ? "LLMNR/IPv6 " : "", + scopes_mask & SD_RESOLVED_MDNS_IPV4 ? "mDNS/IPv4 " : "", + scopes_mask & SD_RESOLVED_MDNS_IPV6 ? "mDNS/IPv6 " : "") < 0) + return log_oom(); + size_t len = strlen(buf); + assert(len > 0); + buf[len - 1] = '\0'; + + *ret = TAKE_PTR(buf); return 0; } +static void status_print_header(DNSConfiguration *c) { + assert(c); + + if (c->ifname) + printf("%sLink %i (%s)%s\n", + ansi_highlight(), + c->ifindex, + c->ifname, + ansi_normal()); + else if (c->delegate) + printf("%sDelegate %s%s\n", + ansi_highlight(), + c->delegate, + ansi_normal()); + else + printf("%sGlobal%s\n", ansi_highlight(), ansi_normal()); +} + static void status_print_string(DNSConfiguration *c, const char *p) { assert(c); @@ -1438,22 +1404,37 @@ static void status_print_string(DNSConfiguration *c, const char *p) { printf("%sGlobal%s: %s\n", ansi_highlight(), ansi_normal(), p); } -static void status_print_header(DNSConfiguration *c) { +static int status_print_strv(DNSConfiguration *c, char **p) { + const unsigned indent = strlen("Global: "); /* Use the same indentation everywhere to make things nice */ + int pos1, pos2; + assert(c); if (c->ifname) - printf("%sLink %i (%s)%s\n", - ansi_highlight(), - c->ifindex, - c->ifname, - ansi_normal()); + printf("%s%nLink %i (%s)%n%s:", ansi_highlight(), &pos1, c->ifindex, c->ifname, &pos2, ansi_normal()); else if (c->delegate) - printf("%sDelegate %s%s\n", - ansi_highlight(), - c->delegate, - ansi_normal()); + printf("%s%nDelegate %s%n%s:", ansi_highlight(), &pos1, c->delegate, &pos2, ansi_normal()); else - printf("%sGlobal%s\n", ansi_highlight(), ansi_normal()); + printf("%s%nGlobal%n%s:", ansi_highlight(), &pos1, &pos2, ansi_normal()); + + size_t cols = columns(), position = pos2 - pos1 + 2; + + STRV_FOREACH(i, p) { + size_t our_len = utf8_console_width(*i); /* This returns -1 on invalid utf-8 (which shouldn't happen). + * If that happens, we'll just print one item per line. */ + + if (position <= indent || size_add(size_add(position, 1), our_len) < cols) { + printf(" %s", *i); + position = size_add(size_add(position, 1), our_len); + } else { + printf("\n%*s%s", (int) indent, "", *i); + position = size_add(our_len, indent); + } + } + + printf("\n"); + + return 0; } static int dump_list(Table *table, const char *field, char * const *l) { @@ -1471,343 +1452,75 @@ static int dump_list(Table *table, const char *field, char * const *l) { return 0; } -static int strv_extend_extended_bool(char ***strv, const char *name, const char *value) { - int r; - - if (value) { - r = parse_boolean(value); - if (r >= 0) - return strv_extendf(strv, "%s%s", plus_minus(r), name); - } - - return strv_extendf(strv, "%s=%s", name, value ?: "???"); -} - -static int status_json_filter_fields(sd_json_variant **configuration, StatusMode mode) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - sd_json_variant *w; - const char *field; +static int print_configuration(DNSConfiguration *configuration, StatusMode mode, bool *empty_line) { + _cleanup_(table_unrefp) Table *table = NULL; int r; assert(configuration); - field = status_mode_json_field_to_string(mode); - if (!field) - /* Nothing to filter for this mode. */ - return 0; + pager_open(arg_pager_flags); - JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { - /* Always include identifier fields like ifname or delegate, and include the requested - * field even if it is empty in the configuration. */ - r = sd_json_variant_append_arraybo( - &v, - JSON_BUILD_PAIR_VARIANT_NON_NULL("ifname", sd_json_variant_by_key(w, "ifname")), - JSON_BUILD_PAIR_VARIANT_NON_NULL("ifindex", sd_json_variant_by_key(w, "ifindex")), - JSON_BUILD_PAIR_VARIANT_NON_NULL("delegate", sd_json_variant_by_key(w, "delegate")), - SD_JSON_BUILD_PAIR_VARIANT(field, sd_json_variant_by_key(w, field))); + bool global = !(configuration->ifindex > 0 || configuration->delegate); + if (mode == STATUS_DNS) { + _cleanup_strv_free_ char **l = NULL; + r = format_dns_servers(configuration, configuration->dns_servers, &l); if (r < 0) return r; - } - JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); - return 0; -} + return status_print_strv(configuration, l); -static int status_json_filter_links(sd_json_variant **configuration, char **links) { - _cleanup_set_free_ Set *links_by_index = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - sd_json_variant *w; - int r; + } else if (mode == STATUS_DOMAIN) { + _cleanup_strv_free_ char **l = NULL; + r = format_search_domains(configuration, configuration->search_domains, &l); + if (r < 0) + return r; - assert(configuration); + return status_print_strv(configuration, l); - if (links) - STRV_FOREACH(ifname, links) { - int ifindex = rtnl_resolve_interface_or_warn(/* rtnl= */ NULL, *ifname); - if (ifindex < 0) - return ifindex; + } else if (mode == STATUS_NTA) { + if (configuration->delegate) + return 0; - r = set_ensure_put(&links_by_index, NULL, INT_TO_PTR(ifindex)); - if (r < 0) - return r; - } + return status_print_strv(configuration, configuration->negative_trust_anchors); - JSON_VARIANT_ARRAY_FOREACH(w, *configuration) { - int ifindex = sd_json_variant_unsigned(sd_json_variant_by_key(w, "ifindex")); + } else if (mode == STATUS_DEFAULT_ROUTE) { + if (global) + return 0; - if (links_by_index) { - if (ifindex <= 0) - /* Possibly invalid, but most likely unset because this is global - * or delegate configuration. */ - continue; + status_print_string(configuration, yes_no(configuration->default_route)); - if (!set_contains(links_by_index, INT_TO_PTR(ifindex))) - continue; + return 0; - } else if (ifindex == LOOPBACK_IFINDEX) - /* By default, exclude the loopback interface. */ - continue; + } else if (IN_SET(mode, STATUS_LLMNR, STATUS_MDNS, STATUS_DNS_OVER_TLS, STATUS_DNSSEC)) { + if (configuration->delegate) + return 0; - r = sd_json_variant_append_array(&v, w); - if (r < 0) - return r; - } + status_print_string(configuration, + strna(mode == STATUS_LLMNR ? configuration->llmnr_mode_str : + mode == STATUS_MDNS ? configuration->mdns_mode_str : + mode == STATUS_DNS_OVER_TLS ? configuration->dns_over_tls_mode_str : + configuration->dnssec_mode_str)); - JSON_VARIANT_REPLACE(*configuration, TAKE_PTR(v)); - return 0; -} + return 0; -static int varlink_dump_dns_configuration(sd_json_variant **ret) { - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL; - sd_json_variant *v; - int r; + } else if (mode != STATUS_ALL) + return 0; - assert(ret); + if (empty_line && *empty_line) + fputc('\n', stdout); - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve"); - if (r < 0) - return log_error_errno(r, "Failed to connect to service /run/systemd/resolve/io.systemd.Resolve: %m"); + status_print_header(configuration); - r = varlink_call_and_log(vl, "io.systemd.Resolve.DumpDNSConfiguration", /* parameters= */ NULL, &reply); - if (r < 0) - return r; + table = table_new_vertical(); + if (!table) + return log_oom(); - v = sd_json_variant_by_key(reply, "configuration"); - - if (!sd_json_variant_is_array(v)) - return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "DumpDNSConfiguration() response missing 'configuration' key."); - - TAKE_PTR(reply); - *ret = sd_json_variant_ref(v); - return 0; -} - -static int format_dns_server_one(DNSConfiguration *configuration, DNSServer *s, char **ret) { - bool global; - int r; - - assert(s); - assert(ret); - - global = !(configuration->ifindex > 0 || configuration->delegate); - - if (global && s->ifindex > 0 && s->ifindex != LOOPBACK_IFINDEX) { - /* This one has an (non-loopback) ifindex set, and we were told to suppress those. Hence do so. */ - *ret = NULL; - return 0; - } - - r = in_addr_port_ifindex_name_to_string( - s->family, - &s->in_addr, - s->port != 53 ? s->port : 0, - s->ifindex, - s->server_name, - ret); - if (r < 0) - return r; - - return 1; -} - -static int format_dns_servers(DNSConfiguration *configuration, OrderedSet *servers, char ***ret) { - int r; - - assert(ret); - - _cleanup_strv_free_ char **l = NULL; - DNSServer *s; - ORDERED_SET_FOREACH(s, servers) { - _cleanup_free_ char *str = NULL; - r = format_dns_server_one(configuration, s, &str); - if (r < 0) - return r; - if (r > 0) { - r = strv_consume(&l, TAKE_PTR(str)); - if (r < 0) - return log_oom(); - } - } - - *ret = TAKE_PTR(l); - return 0; -} - -static int format_search_domains(DNSConfiguration *configuration, OrderedSet *domains, char ***ret) { - int r; - - assert(ret); - - _cleanup_strv_free_ char **l = NULL; - SearchDomain *d; - ORDERED_SET_FOREACH(d, domains) { - if (!(configuration->ifindex > 0 || configuration->delegate) && d->ifindex > 0) - /* Only show the global ones here */ - continue; - - _cleanup_free_ char *str = NULL; - if (d->route_only) - str = strjoin("~", d->name); - else - str = strdup(d->name); - if (!str) - return log_oom(); - - r = strv_consume(&l, TAKE_PTR(str)); - if (r < 0) - return log_oom(); - } - - *ret = TAKE_PTR(l); - return 0; -} - -static int format_protocol_status(DNSConfiguration *configuration, char ***ret) { - _cleanup_strv_free_ char **s = NULL; - int r; - - assert(configuration); - assert(ret); - - if (configuration->ifindex > 0) { - r = strv_extendf(&s, "%sDefaultRoute", plus_minus(configuration->default_route)); - if (r < 0) - return r; - } - - r = strv_extend_extended_bool(&s, "LLMNR", configuration->llmnr_mode_str); - if (r < 0) - return r; - - r = strv_extend_extended_bool(&s, "mDNS", configuration->mdns_mode_str); - if (r < 0) - return r; - - r = strv_extend_extended_bool(&s, "DNSOverTLS", configuration->dns_over_tls_mode_str); - if (r < 0) - return r; - - r = strv_extendf(&s, "DNSSEC=%s/%s", - configuration->dnssec_mode_str ?: "???", - configuration->dnssec_supported ? "supported" : "unsupported"); - if (r < 0) - return r; - - *ret = TAKE_PTR(s); - return 0; -} - -static int format_scopes_string(DNSConfiguration *configuration, char **ret) { - assert(configuration); - assert(ret); - - if (!configuration->dns_scopes) { - *ret = NULL; - return 0; - } - - uint64_t scopes_mask = 0; - DNSScope *scope; - SET_FOREACH(scope, configuration->dns_scopes) { - if (streq(scope->protocol, "dns")) - scopes_mask |= SD_RESOLVED_DNS; - else if (streq(scope->protocol, "llmnr")) - scopes_mask |= scope->family == AF_INET ? - SD_RESOLVED_LLMNR_IPV4 : - SD_RESOLVED_LLMNR_IPV6; - else if (streq(scope->protocol, "mdns")) - scopes_mask |= scope->family == AF_INET ? - SD_RESOLVED_MDNS_IPV4 : - SD_RESOLVED_MDNS_IPV6; - } - - _cleanup_free_ char *buf = NULL; - if (asprintf(&buf, "%s%s%s%s%s", - scopes_mask & SD_RESOLVED_DNS ? "DNS " : "", - scopes_mask & SD_RESOLVED_LLMNR_IPV4 ? "LLMNR/IPv4 " : "", - scopes_mask & SD_RESOLVED_LLMNR_IPV6 ? "LLMNR/IPv6 " : "", - scopes_mask & SD_RESOLVED_MDNS_IPV4 ? "mDNS/IPv4 " : "", - scopes_mask & SD_RESOLVED_MDNS_IPV6 ? "mDNS/IPv6 " : "") < 0) - return log_oom(); - - size_t len = strlen(buf); - assert(len > 0); - buf[len - 1] = '\0'; - - *ret = TAKE_PTR(buf); - return 0; -} - -static int print_configuration(DNSConfiguration *configuration, StatusMode mode, bool *empty_line) { - _cleanup_(table_unrefp) Table *table = NULL; - int r; - - assert(configuration); - - pager_open(arg_pager_flags); - - bool global = !(configuration->ifindex > 0 || configuration->delegate); - if (mode == STATUS_DNS) { - _cleanup_strv_free_ char **l = NULL; - r = format_dns_servers(configuration, configuration->dns_servers, &l); - if (r < 0) - return r; - - return status_print_strv(configuration, l); - - } else if (mode == STATUS_DOMAIN) { - _cleanup_strv_free_ char **l = NULL; - r = format_search_domains(configuration, configuration->search_domains, &l); - if (r < 0) - return r; - - return status_print_strv(configuration, l); - - } else if (mode == STATUS_NTA) { - if (configuration->delegate) - return 0; - - return status_print_strv(configuration, configuration->negative_trust_anchors); - - } else if (mode == STATUS_DEFAULT_ROUTE) { - if (global) - return 0; - - status_print_string(configuration, yes_no(configuration->default_route)); - - return 0; - - } else if (IN_SET(mode, STATUS_LLMNR, STATUS_MDNS, STATUS_DNS_OVER_TLS, STATUS_DNSSEC)) { - if (configuration->delegate) - return 0; - - status_print_string(configuration, - strna(mode == STATUS_LLMNR ? configuration->llmnr_mode_str : - mode == STATUS_MDNS ? configuration->mdns_mode_str : - mode == STATUS_DNS_OVER_TLS ? configuration->dns_over_tls_mode_str : - configuration->dnssec_mode_str)); - - return 0; - - } else if (mode != STATUS_ALL) - return 0; - - if (empty_line && *empty_line) - fputc('\n', stdout); - - status_print_header(configuration); - - table = table_new_vertical(); - if (!table) - return log_oom(); - - if (configuration->ifindex > 0) { - r = table_add_many(table, - TABLE_FIELD, "Current Scopes", - TABLE_SET_MINIMUM_WIDTH, 19); - if (r < 0) - return table_log_add_error(r); + if (configuration->ifindex > 0) { + r = table_add_many(table, + TABLE_FIELD, "Current Scopes", + TABLE_SET_MINIMUM_WIDTH, 19); + if (r < 0) + return table_log_add_error(r); _cleanup_free_ char *s = NULL; r = format_scopes_string(configuration, &s); @@ -1890,9 +1603,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,169 +1698,223 @@ 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) { +VERB(verb_status, "status", "[LINK…]", VERB_ANY, VERB_ANY, VERB_DEFAULT, + "Show link and server status"); +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { return status_full(STATUS_ALL, strv_skip(argv, 1)); } -static int call_dns(sd_bus *bus, char **dns, const BusLocator *locator, sd_bus_error *error, bool extended) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; +VERB(verb_show_statistics, "statistics", NULL, VERB_ANY, 1, 0, + "Show resolver statistics"); +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; int r; (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = bus_message_new_method_call(bus, &req, locator, extended ? "SetLinkDNSEx" : "SetLinkDNS"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "i", arg_ifindex); + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); if (r < 0) - return bus_log_create_error(r); + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - r = sd_bus_message_open_container(req, 'a', extended ? "(iayqs)" : "(iay)"); + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.DumpStatistics", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); if (r < 0) - return bus_log_create_error(r); + return r; - /* If only argument is the empty string, then call SetLinkDNS() with an - * empty list, which will clear the list of domains for an interface. */ - if (!strv_equal(dns, STRV_MAKE(""))) - STRV_FOREACH(p, dns) { - _cleanup_free_ char *name = NULL; - struct in_addr_data data; - uint16_t port; - int ifindex; - - r = in_addr_port_ifindex_name_from_string_auto(*p, &data.family, &data.address, &port, &ifindex, &name); - if (r < 0) - return log_error_errno(r, "Failed to parse DNS server address: %s", *p); - - if (ifindex != 0 && ifindex != arg_ifindex) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ifindex: %i", ifindex); - - r = sd_bus_message_open_container(req, 'r', extended ? "iayqs" : "iay"); - if (r < 0) - return bus_log_create_error(r); + if (sd_json_format_enabled(arg_json_format_flags)) + return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); - r = sd_bus_message_append(req, "i", data.family); - if (r < 0) - return bus_log_create_error(r); + struct statistics { + sd_json_variant *transactions; + sd_json_variant *cache; + sd_json_variant *dnssec; + } statistics; - r = sd_bus_message_append_array(req, 'y', &data.address, FAMILY_ADDRESS_SIZE(data.family)); - if (r < 0) - return bus_log_create_error(r); + static const sd_json_dispatch_field statistics_dispatch_table[] = { + { "transactions", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, transactions), SD_JSON_MANDATORY }, + { "cache", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, cache), SD_JSON_MANDATORY }, + { "dnssec", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct statistics, dnssec), SD_JSON_MANDATORY }, + {}, + }; - if (extended) { - r = sd_bus_message_append(req, "q", port); - if (r < 0) - return bus_log_create_error(r); + r = sd_json_dispatch(reply, statistics_dispatch_table, SD_JSON_LOG, &statistics); + if (r < 0) + return r; - r = sd_bus_message_append(req, "s", name); - if (r < 0) - return bus_log_create_error(r); - } + struct transactions { + uint64_t n_current_transactions; + uint64_t n_transactions_total; + uint64_t n_timeouts_total; + uint64_t n_timeouts_served_stale_total; + uint64_t n_failure_responses_total; + uint64_t n_failure_responses_served_stale_total; + } transactions; - r = sd_bus_message_close_container(req); - if (r < 0) - return bus_log_create_error(r); - } + static const sd_json_dispatch_field transactions_dispatch_table[] = { + { "currentTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_current_transactions), SD_JSON_MANDATORY }, + { "totalTransactions", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_transactions_total), SD_JSON_MANDATORY }, + { "totalTimeouts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_total), SD_JSON_MANDATORY }, + { "totalTimeoutsServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_timeouts_served_stale_total), SD_JSON_MANDATORY }, + { "totalFailedResponses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_total), SD_JSON_MANDATORY }, + { "totalFailedResponsesServedStale", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct transactions, n_failure_responses_served_stale_total), SD_JSON_MANDATORY }, + {}, + }; - r = sd_bus_message_close_container(req); + r = sd_json_dispatch(statistics.transactions, transactions_dispatch_table, SD_JSON_LOG, &transactions); if (r < 0) - return bus_log_create_error(r); + return r; - r = sd_bus_call(bus, req, 0, error, NULL); - if (r < 0 && extended && sd_bus_error_has_name(error, SD_BUS_ERROR_UNKNOWN_METHOD)) { - sd_bus_error_free(error); - return call_dns(bus, dns, locator, error, false); - } - return r; -} + struct cache { + uint64_t cache_size; + uint64_t n_cache_hit; + uint64_t n_cache_miss; + } cache; -static int verb_dns(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; + static const sd_json_dispatch_field cache_dispatch_table[] = { + { "size", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, cache_size), SD_JSON_MANDATORY }, + { "hits", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_hit), SD_JSON_MANDATORY }, + { "misses", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct cache, n_cache_miss), SD_JSON_MANDATORY }, + {}, + }; - r = acquire_bus(&bus); + r = sd_json_dispatch(statistics.cache, cache_dispatch_table, SD_JSON_LOG, &cache); if (r < 0) return r; - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_DNS); + struct dnsssec { + uint64_t n_dnssec_secure; + uint64_t n_dnssec_insecure; + uint64_t n_dnssec_bogus; + uint64_t n_dnssec_indeterminate; + } dnsssec; - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DNS); + static const sd_json_dispatch_field dnssec_dispatch_table[] = { + { "secure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_secure), SD_JSON_MANDATORY }, + { "insecure", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_insecure), SD_JSON_MANDATORY }, + { "bogus", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_bogus), SD_JSON_MANDATORY }, + { "indeterminate", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct dnsssec, n_dnssec_indeterminate), SD_JSON_MANDATORY }, + {}, + }; - char **args = strv_skip(argv, 2); - r = call_dns(bus, args, bus_resolve_mgr, &error, true); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + r = sd_json_dispatch(statistics.dnssec, dnssec_dispatch_table, SD_JSON_LOG, &dnsssec); + if (r < 0) + return r; - r = call_dns(bus, args, bus_network_mgr, &error, true); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + table = table_new_vertical(); + if (!table) + return log_oom(); - return log_error_errno(r, "Failed to set DNS configuration: %s", bus_error_message(&error, r)); - } + r = table_add_many(table, + TABLE_STRING, "Transactions", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Current Transactions", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, transactions.n_current_transactions, + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_FIELD, "Total Transactions", + TABLE_UINT64, transactions.n_transactions_total, + TABLE_EMPTY, TABLE_EMPTY, + TABLE_STRING, "Cache", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Current Cache Size", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, cache.cache_size, + TABLE_FIELD, "Cache Hits", + TABLE_UINT64, cache.n_cache_hit, + TABLE_FIELD, "Cache Misses", + TABLE_UINT64, cache.n_cache_miss, + TABLE_EMPTY, TABLE_EMPTY, + TABLE_STRING, "Failure Transactions", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Total Timeouts", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, transactions.n_timeouts_total, + TABLE_FIELD, "Total Timeouts (Stale Data Served)", + TABLE_UINT64, transactions.n_timeouts_served_stale_total, + TABLE_FIELD, "Total Failure Responses", + TABLE_UINT64, transactions.n_failure_responses_total, + TABLE_FIELD, "Total Failure Responses (Stale Data Served)", + TABLE_UINT64, transactions.n_failure_responses_served_stale_total, + TABLE_EMPTY, TABLE_EMPTY, + TABLE_STRING, "DNSSEC Verdicts", + TABLE_SET_COLOR, ansi_highlight(), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, + TABLE_FIELD, "Secure", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, dnsssec.n_dnssec_secure, + TABLE_FIELD, "Insecure", + TABLE_UINT64, dnsssec.n_dnssec_insecure, + TABLE_FIELD, "Bogus", + TABLE_UINT64, dnsssec.n_dnssec_bogus, + TABLE_FIELD, "Indeterminate", + TABLE_UINT64, dnsssec.n_dnssec_indeterminate + ); + if (r < 0) + return table_log_add_error(r); - return 0; + return table_print_or_warn(table); } -static int call_domain(sd_bus *bus, char **domain, const BusLocator *locator, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; +VERB(verb_reset_statistics, "reset-statistics", NULL, VERB_ANY, 1, 0, + "Reset resolver statistics"); +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; (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = bus_message_new_method_call(bus, &req, locator, "SetLinkDomains"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "i", arg_ifindex); + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); if (r < 0) - return bus_log_create_error(r); + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - r = sd_bus_message_open_container(req, 'a', "(sb)"); + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.ResetStatistics", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); if (r < 0) - return bus_log_create_error(r); + return r; - /* If only argument is the empty string, then call SetLinkDomains() with an - * empty list, which will clear the list of domains for an interface. */ - if (!strv_equal(domain, STRV_MAKE(""))) - STRV_FOREACH(p, domain) { - const char *n; + if (sd_json_format_enabled(arg_json_format_flags)) + return sd_json_variant_dump(reply, arg_json_format_flags, NULL, NULL); - n = **p == '~' ? *p + 1 : *p; + return 0; +} - r = dns_name_is_valid(n); - if (r < 0) - return log_error_errno(r, "Failed to validate specified domain %s: %m", n); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Domain not valid: %s", - n); +VERB(verb_flush_caches, "flush-caches", NULL, VERB_ANY, 1, 0, + "Flush all local DNS caches"); +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; - r = sd_bus_message_append(req, "(sb)", n, **p == '~'); - if (r < 0) - return bus_log_create_error(r); - } + r = acquire_bus(&bus); + if (r < 0) + return r; - r = sd_bus_message_close_container(req); + r = bus_call_method(bus, bus_resolve_mgr, "FlushCaches", &error, NULL, NULL); if (r < 0) - return bus_log_create_error(r); + return log_error_errno(r, "Failed to flush caches: %s", bus_error_message(&error, r)); - return sd_bus_call(bus, req, 0, error, NULL); + return 0; } -static int verb_domain(int argc, char **argv, void *userdata) { +VERB(verb_reset_server_features, "reset-server-features", NULL, VERB_ANY, 1, 0, + "Forget learnt DNS server feature levels"); +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; @@ -2156,249 +1923,670 @@ static int verb_domain(int argc, char **argv, void *userdata) { if (r < 0) return r; - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } + r = bus_call_method(bus, bus_resolve_mgr, "ResetServerFeatures", &error, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to reset server features: %s", bus_error_message(&error, r)); - if (arg_ifindex <= 0) - return status_all(STATUS_DOMAIN); + return 0; +} - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DOMAIN); +static int print_question(char prefix, const char *color, sd_json_variant *question) { + sd_json_variant *q = NULL; + int r; - char **args = strv_skip(argv, 2); - r = call_domain(bus, args, bus_resolve_mgr, &error); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + assert(color); - r = call_domain(bus, args, bus_network_mgr, &error); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + JSON_VARIANT_ARRAY_FOREACH(q, question) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + char buf[DNS_RESOURCE_KEY_STRING_MAX]; - return log_error_errno(r, "Failed to set domain configuration: %s", bus_error_message(&error, r)); + r = dns_resource_key_from_json(q, &key); + if (r < 0) { + log_warning_errno(r, "Received monitor message with invalid question key, ignoring: %m"); + continue; + } + + printf("%s%s %c%s: %s\n", + color, + glyph(GLYPH_ARROW_RIGHT), + prefix, + ansi_normal(), + dns_resource_key_to_string(key, buf, sizeof(buf))); } return 0; } -static int verb_default_route(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, b; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_DEFAULT_ROUTE); +static int print_answer(sd_json_variant *answer) { + sd_json_variant *a; + int r; - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DEFAULT_ROUTE); + JSON_VARIANT_ARRAY_FOREACH(a, answer) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ void *d = NULL; + sd_json_variant *jraw; + const char *s; + size_t l; - b = parse_boolean(argv[2]); - if (b < 0) - return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]); + jraw = sd_json_variant_by_key(a, "raw"); + if (!jraw) { + log_warning("Received monitor answer lacking valid raw data, ignoring."); + continue; + } - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + r = sd_json_variant_unbase64(jraw, &d, &l); + if (r < 0) { + log_warning_errno(r, "Failed to undo base64 encoding of monitor answer raw data, ignoring."); + continue; + } - r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDefaultRoute", &error, NULL, "ib", arg_ifindex, b); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + r = dns_resource_record_new_from_raw(&rr, d, l); + if (r < 0) { + log_warning_errno(r, "Failed to parse monitor answer RR, ignoring: %m"); + continue; + } - r = bus_call_method(bus, bus_network_mgr, "SetLinkDefaultRoute", &error, NULL, "ib", arg_ifindex, b); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + s = dns_resource_record_to_string(rr); + if (!s) + return log_oom(); - return log_error_errno(r, "Failed to set default route configuration: %s", bus_error_message(&error, r)); + printf("%s%s A%s: %s\n", + ansi_highlight_yellow(), + glyph(GLYPH_ARROW_LEFT), + ansi_normal(), + s); } return 0; } -static int verb_llmnr(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; - _cleanup_free_ char *global_llmnr_support_str = NULL; - ResolveSupport global_llmnr_support, llmnr_support; - int r; +typedef struct MonitorQueryParams { + sd_json_variant *question; + sd_json_variant *answer; + sd_json_variant *collected_questions; + int rcode; + int error; + int ede_code; + const char *state; + const char *result; + const char *ede_msg; +} MonitorQueryParams; - r = acquire_bus(&bus); - if (r < 0) - return r; +static void monitor_query_params_done(MonitorQueryParams *p) { + assert(p); - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } + sd_json_variant_unref(p->question); + sd_json_variant_unref(p->answer); + sd_json_variant_unref(p->collected_questions); +} - if (arg_ifindex <= 0) - return status_all(STATUS_LLMNR); +static void monitor_query_dump(sd_json_variant *v) { + static const sd_json_dispatch_field dispatch_table[] = { + { "question", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, question), SD_JSON_MANDATORY }, + { "answer", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, answer), 0 }, + { "collectedQuestions", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, collected_questions), 0 }, + { "state", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, state), SD_JSON_MANDATORY }, + { "result", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, result), 0 }, + { "rcode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, rcode), 0 }, + { "errno", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, error), 0 }, + { "extendedDNSErrorCode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, ede_code), 0 }, + { "extendedDNSErrorMessage", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, ede_msg), 0 }, + {} + }; - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_LLMNR); + _cleanup_(monitor_query_params_done) MonitorQueryParams p = { + .rcode = -1, + .ede_code = -1, + }; - llmnr_support = resolve_support_from_string(argv[2]); - if (llmnr_support < 0) - return log_error_errno(llmnr_support, "Invalid LLMNR setting: %s", argv[2]); + assert(v); - r = bus_get_property_string(bus, bus_resolve_mgr, "LLMNR", &error, &global_llmnr_support_str); - if (r < 0) - return log_error_errno(r, "Failed to get the global LLMNR support state: %s", bus_error_message(&error, r)); + if (sd_json_dispatch(v, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &p) < 0) + return; - global_llmnr_support = resolve_support_from_string(global_llmnr_support_str); - if (global_llmnr_support < 0) - return log_error_errno(global_llmnr_support, "Received invalid global LLMNR setting: %s", global_llmnr_support_str); + /* First show the current question */ + print_question('Q', ansi_highlight_cyan(), p.question); - if (global_llmnr_support < llmnr_support) - log_warning("Setting LLMNR support level \"%s\" for \"%s\", but the global support level is \"%s\".", - argv[2], arg_ifname, global_llmnr_support_str); + /* And then show the questions that led to this one in case this was a CNAME chain */ + print_question('C', ansi_highlight_grey(), p.collected_questions); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + printf("%s%s S%s: %s", + streq_ptr(p.state, "success") ? ansi_highlight_green() : ansi_highlight_red(), + glyph(GLYPH_ARROW_LEFT), + ansi_normal(), + streq_ptr(p.state, "errno") ? ERRNO_NAME(p.error) : + streq_ptr(p.state, "rcode-failure") ? strna(dns_rcode_to_string(p.rcode)) : + strna(p.state)); - r = bus_call_method(bus, bus_resolve_mgr, "SetLinkLLMNR", &error, NULL, "is", arg_ifindex, argv[2]); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + if (!isempty(p.result)) + printf(": %s", p.result); - r = bus_call_method(bus, bus_network_mgr, "SetLinkLLMNR", &error, NULL, "is", arg_ifindex, argv[2]); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + if (p.ede_code >= 0) + printf(" (%s%s%s)", + FORMAT_DNS_EDE_RCODE(p.ede_code), + !isempty(p.ede_msg) ? ": " : "", + strempty(p.ede_msg)); - return log_error_errno(r, "Failed to set LLMNR configuration: %s", bus_error_message(&error, r)); - } + puts(""); - return 0; + print_answer(p.answer); } -static int verb_mdns(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; - _cleanup_free_ char *global_mdns_support_str = NULL; - ResolveSupport global_mdns_support, mdns_support; - int r; +static int monitor_reply( + sd_varlink *link, + sd_json_variant *parameters, + const char *error_id, + sd_varlink_reply_flags_t flags, + void *userdata) { - r = acquire_bus(&bus); - if (r < 0) - return r; + assert(link); - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } + if (error_id) { + bool disconnect; - if (arg_ifindex <= 0) - return status_all(STATUS_MDNS); + disconnect = streq(error_id, SD_VARLINK_ERROR_DISCONNECTED); + if (disconnect) + log_info("Disconnected."); + else + log_error("Varlink error: %s", error_id); - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_MDNS); + (void) sd_event_exit(ASSERT_PTR(sd_varlink_get_event(link)), disconnect ? EXIT_SUCCESS : EXIT_FAILURE); + return 0; + } - mdns_support = resolve_support_from_string(argv[2]); - if (mdns_support < 0) - return log_error_errno(mdns_support, "Invalid mDNS setting: %s", argv[2]); + if (sd_json_variant_by_key(parameters, "ready")) { + /* The first message coming in will just indicate that we are now subscribed. We let our + * caller know if they asked for it. Once the caller sees this they should know that we are + * not going to miss any queries anymore. */ + (void) sd_notify(/* unset_environment=false */ false, "READY=1"); + return 0; + } - r = bus_get_property_string(bus, bus_resolve_mgr, "MulticastDNS", &error, &global_mdns_support_str); - if (r < 0) - return log_error_errno(r, "Failed to get the global mDNS support state: %s", bus_error_message(&error, r)); + if (!sd_json_format_enabled(arg_json_format_flags)) { + monitor_query_dump(parameters); + printf("\n"); + } else + sd_json_variant_dump(parameters, arg_json_format_flags, NULL, NULL); - global_mdns_support = resolve_support_from_string(global_mdns_support_str); - if (global_mdns_support < 0) - return log_error_errno(global_mdns_support, "Received invalid global mDNS setting: %s", global_mdns_support_str); + fflush(stdout); - if (global_mdns_support < mdns_support) - log_warning("Setting mDNS support level \"%s\" for \"%s\", but the global support level is \"%s\".", - argv[2], arg_ifname, global_mdns_support_str); + return 0; +} + +VERB(verb_monitor, "monitor", NULL, VERB_ANY, 1, 0, + "Monitor DNS queries"); +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; (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = bus_call_method(bus, bus_resolve_mgr, "SetLinkMulticastDNS", &error, NULL, "is", arg_ifindex, argv[2]); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); - r = bus_call_method( - bus, - bus_network_mgr, - "SetLinkMulticastDNS", - &error, - NULL, - "is", arg_ifindex, argv[2]); + r = sd_event_set_signal_exit(event, true); + if (r < 0) + return log_error_errno(r, "Failed to enable exit on SIGINT/SIGTERM: %m"); + + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + if (r < 0) + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + + r = sd_varlink_set_relative_timeout(vl, USEC_INFINITY); /* We want the monitor to run basically forever */ + if (r < 0) + return log_error_errno(r, "Failed to set varlink timeout: %m"); + + r = sd_varlink_attach_event(vl, event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); + + r = sd_varlink_bind_reply(vl, monitor_reply); + if (r < 0) + return log_error_errno(r, "Failed to bind reply callback to varlink connection: %m"); + + r = sd_varlink_observebo( + vl, + "io.systemd.Resolve.Monitor.SubscribeQueryResults", + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return log_error_errno(r, "Failed to issue SubscribeQueryResults() varlink call: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + r = sd_event_get_exit_code(event, &c); + if (r < 0) + return log_error_errno(r, "Failed to get exit code: %m"); + + return c; +} + +static int call_dns(sd_bus *bus, char **dns, const BusLocator *locator, sd_bus_error *error, bool extended) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; + int r; + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = bus_message_new_method_call(bus, &req, locator, extended ? "SetLinkDNSEx" : "SetLinkDNS"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "i", arg_ifindex); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(req, 'a', extended ? "(iayqs)" : "(iay)"); + if (r < 0) + return bus_log_create_error(r); + + /* If only argument is the empty string, then call SetLinkDNS() with an + * empty list, which will clear the list of domains for an interface. */ + if (!strv_equal(dns, STRV_MAKE(""))) + STRV_FOREACH(p, dns) { + _cleanup_free_ char *name = NULL; + struct in_addr_data data; + uint16_t port; + int ifindex; + + r = in_addr_port_ifindex_name_from_string_auto(*p, &data.family, &data.address, &port, &ifindex, &name); + if (r < 0) + return log_error_errno(r, "Failed to parse DNS server address: %s", *p); + + if (ifindex != 0 && ifindex != arg_ifindex) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ifindex: %i", ifindex); + + r = sd_bus_message_open_container(req, 'r', extended ? "iayqs" : "iay"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "i", data.family); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_array(req, 'y', &data.address, FAMILY_ADDRESS_SIZE(data.family)); + if (r < 0) + return bus_log_create_error(r); + + if (extended) { + r = sd_bus_message_append(req, "q", port); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "s", name); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(req); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(req); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, req, 0, error, NULL); + if (r < 0 && extended && sd_bus_error_has_name(error, SD_BUS_ERROR_UNKNOWN_METHOD)) { + sd_bus_error_free(error); + return call_dns(bus, dns, locator, error, false); } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + return r; +} - return log_error_errno(r, "Failed to set MulticastDNS configuration: %s", bus_error_message(&error, r)); +static int dump_cache_item(sd_json_variant *item) { + + struct item_info { + sd_json_variant *key; + sd_json_variant *rrs; + const char *type; + uint64_t until; + } item_info = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "key", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct item_info, key), SD_JSON_MANDATORY }, + { "rrs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct item_info, rrs), 0 }, + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct item_info, type), 0 }, + { "until", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct item_info, until), 0 }, + {}, + }; + + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; + int r, c = 0; + + r = sd_json_dispatch(item, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &item_info); + if (r < 0) + return r; + + r = dns_resource_key_from_json(item_info.key, &k); + if (r < 0) + return log_error_errno(r, "Failed to turn JSON data to resource key: %m"); + + if (item_info.type) + printf("%s %s%s%s\n", DNS_RESOURCE_KEY_TO_STRING(k), ansi_highlight_red(), item_info.type, ansi_normal()); + else { + sd_json_variant *i; + + JSON_VARIANT_ARRAY_FOREACH(i, item_info.rrs) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ void *data = NULL; + sd_json_variant *raw; + size_t size; + + raw = sd_json_variant_by_key(i, "raw"); + if (!raw) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "raw field missing from RR JSON data."); + + r = sd_json_variant_unbase64(raw, &data, &size); + if (r < 0) + return log_error_errno(r, "Unable to decode raw RR JSON data: %m"); + + r = dns_resource_record_new_from_raw(&rr, data, size); + if (r < 0) + return log_error_errno(r, "Failed to parse DNS data: %m"); + + printf("%s\n", dns_resource_record_to_string(rr)); + c++; + } } - return 0; + return c; } -static int verb_dns_over_tls(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; +static int dump_cache_scope(sd_json_variant *scope) { + struct scope_info { + const char *protocol; + int family; + int ifindex; + const char *ifname; + sd_json_variant *cache; + const char *dnssec_mode; + const char *dns_over_tls_mode; + } scope_info = { + .family = AF_UNSPEC, + }; + sd_json_variant *i; + int r, c = 0; - r = acquire_bus(&bus); + static const sd_json_dispatch_field dispatch_table[] = { + { "protocol", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, protocol), SD_JSON_MANDATORY }, + { "family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(struct scope_info, family), SD_JSON_RELAX }, + { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct scope_info, ifindex), SD_JSON_RELAX }, + { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, ifname), 0 }, + { "cache", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct scope_info, cache), SD_JSON_MANDATORY }, + { "dnssec", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dnssec_mode), 0 }, + { "dnsOverTLS", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dns_over_tls_mode), 0 }, + {}, + }; + + r = sd_json_dispatch(scope, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &scope_info); if (r < 0) return r; - if (argc >= 2) { - r = ifname_mangle(argv[1]); + printf("%sScope protocol=%s", ansi_underline(), scope_info.protocol); + + if (scope_info.family != AF_UNSPEC) + printf(" family=%s", af_to_name(scope_info.family)); + + if (scope_info.ifindex > 0) + printf(" ifindex=%i", scope_info.ifindex); + if (scope_info.ifname) + printf(" ifname=%s", scope_info.ifname); + + if (dns_protocol_from_string(scope_info.protocol) == DNS_PROTOCOL_DNS) { + if (scope_info.dnssec_mode) + printf(" DNSSEC=%s", scope_info.dnssec_mode); + if (scope_info.dns_over_tls_mode) + printf(" DNSOverTLS=%s", scope_info.dns_over_tls_mode); + } + + printf("%s\n", ansi_normal()); + + JSON_VARIANT_ARRAY_FOREACH(i, scope_info.cache) { + r = dump_cache_item(i); if (r < 0) return r; + + c += r; } - if (arg_ifindex <= 0) - return status_all(STATUS_DNS_OVER_TLS); + if (c == 0) + printf("%sNo entries.%s\n\n", ansi_grey(), ansi_normal()); + else + printf("\n"); - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DNS_OVER_TLS); + return 0; +} + +VERB(verb_show_cache, "show-cache", NULL, VERB_ANY, 1, 0, + "Show cache contents"); +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; (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDNSOverTLS", &error, NULL, "is", arg_ifindex, argv[2]); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + if (r < 0) + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - r = bus_call_method( - bus, - bus_network_mgr, - "SetLinkDNSOverTLS", - &error, - NULL, - "is", arg_ifindex, argv[2]); + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.DumpCache", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return r; + + d = sd_json_variant_by_key(reply, "dump"); + if (!d) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response is missing 'dump' key."); + + if (!sd_json_variant_is_array(d)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response 'dump' field not an array"); + + if (!sd_json_format_enabled(arg_json_format_flags)) { + sd_json_variant *i; + + JSON_VARIANT_ARRAY_FOREACH(i, d) { + r = dump_cache_scope(i); + if (r < 0) + return r; + } + + return 0; } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; - return log_error_errno(r, "Failed to set DNSOverTLS configuration: %s", bus_error_message(&error, r)); + return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); +} + +static int dump_server_state(sd_json_variant *server) { + _cleanup_(table_unrefp) Table *table = NULL; + TableCell *cell; + + struct server_state { + const char *server_name; + const char *type; + const char *ifname; + int ifindex; + const char *verified_feature_level; + const char *possible_feature_level; + const char *dnssec_mode; + bool dnssec_supported; + size_t received_udp_fragment_max; + uint64_t n_failed_udp; + uint64_t n_failed_tcp; + bool packet_truncated; + bool packet_bad_opt; + bool packet_rrsig_missing; + bool packet_invalid; + bool packet_do_off; + } server_state = { + .ifindex = -1, + }; + + int r; + + static const sd_json_dispatch_field dispatch_table[] = { + { "Server", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, server_name), SD_JSON_MANDATORY }, + { "Type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, type), SD_JSON_MANDATORY }, + { "Interface", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, ifname), 0 }, + { "InterfaceIndex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct server_state, ifindex), SD_JSON_RELAX }, + { "VerifiedFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, verified_feature_level), 0 }, + { "PossibleFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, possible_feature_level), 0 }, + { "DNSSECMode", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, dnssec_mode), SD_JSON_MANDATORY }, + { "DNSSECSupported", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, dnssec_supported), SD_JSON_MANDATORY }, + { "ReceivedUDPFragmentMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, received_udp_fragment_max), SD_JSON_MANDATORY }, + { "FailedUDPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_udp), SD_JSON_MANDATORY }, + { "FailedTCPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_tcp), SD_JSON_MANDATORY }, + { "PacketTruncated", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_truncated), SD_JSON_MANDATORY }, + { "PacketBadOpt", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_bad_opt), SD_JSON_MANDATORY }, + { "PacketRRSIGMissing", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_rrsig_missing), SD_JSON_MANDATORY }, + { "PacketInvalid", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_invalid), SD_JSON_MANDATORY }, + { "PacketDoOff", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_do_off), SD_JSON_MANDATORY }, + {}, + }; + + r = sd_json_dispatch(server, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &server_state); + if (r < 0) + return r; + + table = table_new_vertical(); + if (!table) + return log_oom(); + + assert_se(cell = table_get_cell(table, 0, 0)); + (void) table_set_ellipsize_percent(table, cell, 100); + (void) table_set_align_percent(table, cell, 0); + + r = table_add_cell_stringf(table, NULL, "Server: %s", server_state.server_name); + if (r < 0) + return table_log_add_error(r); + + r = table_add_many(table, + TABLE_EMPTY, + TABLE_FIELD, "Type", + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_STRING, server_state.type); + if (r < 0) + return table_log_add_error(r); + + if (server_state.ifname) { + r = table_add_many(table, + TABLE_FIELD, "Interface", + TABLE_STRING, server_state.ifname); + if (r < 0) + return table_log_add_error(r); + } + + if (server_state.ifindex >= 0) { + r = table_add_many(table, + TABLE_FIELD, "Interface Index", + TABLE_INT, server_state.ifindex); + if (r < 0) + return table_log_add_error(r); + } + + if (server_state.verified_feature_level) { + r = table_add_many(table, + TABLE_FIELD, "Verified feature level", + TABLE_STRING, server_state.verified_feature_level); + if (r < 0) + return table_log_add_error(r); + } + + if (server_state.possible_feature_level) { + r = table_add_many(table, + TABLE_FIELD, "Possible feature level", + TABLE_STRING, server_state.possible_feature_level); + if (r < 0) + return table_log_add_error(r); + } + + r = table_add_many(table, + TABLE_FIELD, "DNSSEC Mode", + TABLE_STRING, server_state.dnssec_mode, + TABLE_FIELD, "DNSSEC Supported", + TABLE_STRING, yes_no(server_state.dnssec_supported), + TABLE_FIELD, "Maximum UDP fragment size received", + TABLE_UINT64, server_state.received_udp_fragment_max, + TABLE_FIELD, "Failed UDP attempts", + TABLE_UINT64, server_state.n_failed_udp, + TABLE_FIELD, "Failed TCP attempts", + TABLE_UINT64, server_state.n_failed_tcp, + TABLE_FIELD, "Seen truncated packet", + TABLE_STRING, yes_no(server_state.packet_truncated), + TABLE_FIELD, "Seen OPT RR getting lost", + TABLE_STRING, yes_no(server_state.packet_bad_opt), + TABLE_FIELD, "Seen RRSIG RR missing", + TABLE_STRING, yes_no(server_state.packet_rrsig_missing), + TABLE_FIELD, "Seen invalid packet", + TABLE_STRING, yes_no(server_state.packet_invalid), + TABLE_FIELD, "Server dropped DO flag", + TABLE_STRING, yes_no(server_state.packet_do_off), + TABLE_SET_ALIGN_PERCENT, 0, + TABLE_EMPTY, TABLE_EMPTY); + + if (r < 0) + return table_log_add_error(r); + + return table_print_or_warn(table); +} + +VERB(verb_show_server_state, "show-server-state", NULL, VERB_ANY, 1, 0, + "Show servers state"); +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; + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); + if (r < 0) + return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + + r = varlink_callbo_and_log( + vl, + "io.systemd.Resolve.Monitor.DumpServerState", + &reply, + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + if (r < 0) + return r; + + d = sd_json_variant_by_key(reply, "dump"); + if (!d) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response is missing 'dump' key."); + + if (!sd_json_variant_is_array(d)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "DumpCache() response 'dump' field not an array"); + + if (!sd_json_format_enabled(arg_json_format_flags)) { + sd_json_variant *i; + + JSON_VARIANT_ARRAY_FOREACH(i, d) { + r = dump_server_state(i); + if (r < 0) + return r; + } + + return 0; } - return 0; + return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); } -static int verb_dnssec(int argc, char **argv, void *userdata) { +VERB(verb_dns, "dns", "[LINK [SERVER…]]", VERB_ANY, VERB_ANY, 0, + "Get/set per-interface DNS server address"); +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; @@ -2414,37 +2602,36 @@ static int verb_dnssec(int argc, char **argv, void *userdata) { } if (arg_ifindex <= 0) - return status_all(STATUS_DNSSEC); + return status_all(STATUS_DNS); if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_DNSSEC); - - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + return status_ifindex(arg_ifindex, STATUS_DNS); - r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDNSSEC", &error, NULL, "is", arg_ifindex, argv[2]); + char **args = strv_skip(argv, 2); + r = call_dns(bus, args, bus_resolve_mgr, &error, true); if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { sd_bus_error_free(&error); - r = bus_call_method(bus, bus_network_mgr, "SetLinkDNSSEC", &error, NULL, "is", arg_ifindex, argv[2]); + r = call_dns(bus, args, bus_network_mgr, &error, true); } if (r < 0) { if (arg_ifindex_permissive && sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) return 0; - return log_error_errno(r, "Failed to set DNSSEC configuration: %s", bus_error_message(&error, r)); + return log_error_errno(r, "Failed to set DNS configuration: %s", bus_error_message(&error, r)); } return 0; } -static int call_nta(sd_bus *bus, char **nta, const BusLocator *locator, sd_bus_error *error) { +static int call_domain(sd_bus *bus, char **domain, const BusLocator *locator, sd_bus_error *error) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; int r; (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = bus_message_new_method_call(bus, &req, locator, "SetLinkDNSSECNegativeTrustAnchors"); + r = bus_message_new_method_call(bus, &req, locator, "SetLinkDomains"); if (r < 0) return bus_log_create_error(r); @@ -2452,72 +2639,41 @@ static int call_nta(sd_bus *bus, char **nta, const BusLocator *locator, sd_bus_ if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_append_strv(req, nta); + r = sd_bus_message_open_container(req, 'a', "(sb)"); if (r < 0) return bus_log_create_error(r); - return sd_bus_call(bus, req, 0, error, NULL); -} - -static int verb_nta(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; - char **args; - bool clear; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - if (argc >= 2) { - r = ifname_mangle(argv[1]); - if (r < 0) - return r; - } - - if (arg_ifindex <= 0) - return status_all(STATUS_NTA); - - if (argc < 3) - return status_ifindex(arg_ifindex, STATUS_NTA); - - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + /* If only argument is the empty string, then call SetLinkDomains() with an + * empty list, which will clear the list of domains for an interface. */ + if (!strv_equal(domain, STRV_MAKE(""))) + STRV_FOREACH(p, domain) { + const char *n; - /* If only argument is the empty string, then call SetLinkDNSSECNegativeTrustAnchors() - * with an empty list, which will clear the list of domains for an interface. */ - args = strv_skip(argv, 2); - clear = strv_equal(args, STRV_MAKE("")); + n = **p == '~' ? *p + 1 : *p; - if (!clear) - STRV_FOREACH(p, args) { - r = dns_name_is_valid(*p); + r = dns_name_is_valid(n); if (r < 0) - return log_error_errno(r, "Failed to validate specified domain %s: %m", *p); + return log_error_errno(r, "Failed to validate specified domain %s: %m", n); if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Domain not valid: %s", - *p); - } - - r = call_nta(bus, clear ? NULL : args, bus_resolve_mgr, &error); - if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { - sd_bus_error_free(&error); + n); - r = call_nta(bus, clear ? NULL : args, bus_network_mgr, &error); - } - if (r < 0) { - if (arg_ifindex_permissive && - sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) - return 0; + r = sd_bus_message_append(req, "(sb)", n, **p == '~'); + if (r < 0) + return bus_log_create_error(r); + } - return log_error_errno(r, "Failed to set DNSSEC NTA configuration: %s", bus_error_message(&error, r)); - } + r = sd_bus_message_close_container(req); + if (r < 0) + return bus_log_create_error(r); - return 0; + return sd_bus_call(bus, req, 0, error, NULL); } -static int verb_revert_link(int argc, char **argv, void *userdata) { +VERB(verb_domain, "domain", "[LINK [DOMAIN…]]", VERB_ANY, VERB_ANY, 0, + "Get/set per-interface search domain"); +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; @@ -2533,630 +2689,458 @@ static int verb_revert_link(int argc, char **argv, void *userdata) { } if (arg_ifindex <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Interface argument required."); + return status_all(STATUS_DOMAIN); - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_DOMAIN); - r = bus_call_method(bus, bus_resolve_mgr, "RevertLink", &error, NULL, "i", arg_ifindex); + char **args = strv_skip(argv, 2); + r = call_domain(bus, args, bus_resolve_mgr, &error); if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { sd_bus_error_free(&error); - r = bus_call_method(bus, bus_network_mgr, "RevertLinkDNS", &error, NULL, "i", arg_ifindex); + r = call_domain(bus, args, bus_network_mgr, &error); } if (r < 0) { if (arg_ifindex_permissive && sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) return 0; - return log_error_errno(r, "Failed to revert interface configuration: %s", bus_error_message(&error, r)); + return log_error_errno(r, "Failed to set domain configuration: %s", bus_error_message(&error, r)); } return 0; } -static int verb_log_level(int argc, char *argv[], void *userdata) { +VERB(verb_default_route, "default-route", "[LINK [BOOL]]", VERB_ANY, 3, 0, + "Get/set per-interface default route flag"); +static int verb_default_route(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - assert(IN_SET(argc, 1, 2)); - - return verb_log_control_common(bus, "org.freedesktop.resolve1", argv[0], argc == 2 ? argv[1] : NULL); -} - -static int print_question(char prefix, const char *color, sd_json_variant *question) { - sd_json_variant *q = NULL; - int r; - - assert(color); - - JSON_VARIANT_ARRAY_FOREACH(q, question) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - char buf[DNS_RESOURCE_KEY_STRING_MAX]; - - r = dns_resource_key_from_json(q, &key); - if (r < 0) { - log_warning_errno(r, "Received monitor message with invalid question key, ignoring: %m"); - continue; - } - - printf("%s%s %c%s: %s\n", - color, - glyph(GLYPH_ARROW_RIGHT), - prefix, - ansi_normal(), - dns_resource_key_to_string(key, buf, sizeof(buf))); - } - - return 0; -} - -static int print_answer(sd_json_variant *answer) { - sd_json_variant *a; - int r; - - JSON_VARIANT_ARRAY_FOREACH(a, answer) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ void *d = NULL; - sd_json_variant *jraw; - const char *s; - size_t l; - - jraw = sd_json_variant_by_key(a, "raw"); - if (!jraw) { - log_warning("Received monitor answer lacking valid raw data, ignoring."); - continue; - } - - r = sd_json_variant_unbase64(jraw, &d, &l); - if (r < 0) { - log_warning_errno(r, "Failed to undo base64 encoding of monitor answer raw data, ignoring."); - continue; - } - - r = dns_resource_record_new_from_raw(&rr, d, l); - if (r < 0) { - log_warning_errno(r, "Failed to parse monitor answer RR, ignoring: %m"); - continue; - } - - s = dns_resource_record_to_string(rr); - if (!s) - return log_oom(); - - printf("%s%s A%s: %s\n", - ansi_highlight_yellow(), - glyph(GLYPH_ARROW_LEFT), - ansi_normal(), - s); - } - - return 0; -} - -typedef struct MonitorQueryParams { - sd_json_variant *question; - sd_json_variant *answer; - sd_json_variant *collected_questions; - int rcode; - int error; - int ede_code; - const char *state; - const char *result; - const char *ede_msg; -} MonitorQueryParams; - -static void monitor_query_params_done(MonitorQueryParams *p) { - assert(p); - - sd_json_variant_unref(p->question); - sd_json_variant_unref(p->answer); - sd_json_variant_unref(p->collected_questions); -} - -static void monitor_query_dump(sd_json_variant *v) { - static const sd_json_dispatch_field dispatch_table[] = { - { "question", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, question), SD_JSON_MANDATORY }, - { "answer", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, answer), 0 }, - { "collectedQuestions", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(MonitorQueryParams, collected_questions), 0 }, - { "state", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, state), SD_JSON_MANDATORY }, - { "result", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, result), 0 }, - { "rcode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, rcode), 0 }, - { "errno", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, error), 0 }, - { "extendedDNSErrorCode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(MonitorQueryParams, ede_code), 0 }, - { "extendedDNSErrorMessage", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MonitorQueryParams, ede_msg), 0 }, - {} - }; - - _cleanup_(monitor_query_params_done) MonitorQueryParams p = { - .rcode = -1, - .ede_code = -1, - }; - - assert(v); - - if (sd_json_dispatch(v, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &p) < 0) - return; - - /* First show the current question */ - print_question('Q', ansi_highlight_cyan(), p.question); - - /* And then show the questions that led to this one in case this was a CNAME chain */ - print_question('C', ansi_highlight_grey(), p.collected_questions); - - printf("%s%s S%s: %s", - streq_ptr(p.state, "success") ? ansi_highlight_green() : ansi_highlight_red(), - glyph(GLYPH_ARROW_LEFT), - ansi_normal(), - streq_ptr(p.state, "errno") ? ERRNO_NAME(p.error) : - streq_ptr(p.state, "rcode-failure") ? strna(dns_rcode_to_string(p.rcode)) : - strna(p.state)); - - if (!isempty(p.result)) - printf(": %s", p.result); - - if (p.ede_code >= 0) - printf(" (%s%s%s)", - FORMAT_DNS_EDE_RCODE(p.ede_code), - !isempty(p.ede_msg) ? ": " : "", - strempty(p.ede_msg)); + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r, b; - puts(""); + r = acquire_bus(&bus); + if (r < 0) + return r; - print_answer(p.answer); -} + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } -static int monitor_reply( - sd_varlink *link, - sd_json_variant *parameters, - const char *error_id, - sd_varlink_reply_flags_t flags, - void *userdata) { + if (arg_ifindex <= 0) + return status_all(STATUS_DEFAULT_ROUTE); - assert(link); + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_DEFAULT_ROUTE); - if (error_id) { - bool disconnect; + b = parse_boolean(argv[2]); + if (b < 0) + return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]); - disconnect = streq(error_id, SD_VARLINK_ERROR_DISCONNECTED); - if (disconnect) - log_info("Disconnected."); - else - log_error("Varlink error: %s", error_id); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - (void) sd_event_exit(ASSERT_PTR(sd_varlink_get_event(link)), disconnect ? EXIT_SUCCESS : EXIT_FAILURE); - return 0; - } + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDefaultRoute", &error, NULL, "ib", arg_ifindex, b); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); - if (sd_json_variant_by_key(parameters, "ready")) { - /* The first message coming in will just indicate that we are now subscribed. We let our - * caller know if they asked for it. Once the caller sees this they should know that we are - * not going to miss any queries anymore. */ - (void) sd_notify(/* unset_environment=false */ false, "READY=1"); - return 0; + r = bus_call_method(bus, bus_network_mgr, "SetLinkDefaultRoute", &error, NULL, "ib", arg_ifindex, b); } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; - if (!sd_json_format_enabled(arg_json_format_flags)) { - monitor_query_dump(parameters); - printf("\n"); - } else - sd_json_variant_dump(parameters, arg_json_format_flags, NULL, NULL); - - fflush(stdout); + return log_error_errno(r, "Failed to set default route configuration: %s", bus_error_message(&error, r)); + } return 0; } -static int verb_monitor(int argc, char *argv[], void *userdata) { - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; - int r, c; - - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); +VERB(verb_llmnr, "llmnr", "[LINK [MODE]]", VERB_ANY, 3, 0, + "Get/set per-interface LLMNR mode"); +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; + ResolveSupport global_llmnr_support, llmnr_support; + int r; - r = sd_event_default(&event); + r = acquire_bus(&bus); if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); + return r; - r = sd_event_set_signal_exit(event, true); - if (r < 0) - return log_error_errno(r, "Failed to enable exit on SIGINT/SIGTERM: %m"); + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); - if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); + if (arg_ifindex <= 0) + return status_all(STATUS_LLMNR); - r = sd_varlink_set_relative_timeout(vl, USEC_INFINITY); /* We want the monitor to run basically forever */ - if (r < 0) - return log_error_errno(r, "Failed to set varlink timeout: %m"); + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_LLMNR); - r = sd_varlink_attach_event(vl, event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) - return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); + llmnr_support = resolve_support_from_string(argv[2]); + if (llmnr_support < 0) + return log_error_errno(llmnr_support, "Invalid LLMNR setting: %s", argv[2]); - r = sd_varlink_bind_reply(vl, monitor_reply); + r = bus_get_property_string(bus, bus_resolve_mgr, "LLMNR", &error, &global_llmnr_support_str); if (r < 0) - return log_error_errno(r, "Failed to bind reply callback to varlink connection: %m"); + return log_error_errno(r, "Failed to get the global LLMNR support state: %s", bus_error_message(&error, r)); - r = sd_varlink_observebo( - vl, - "io.systemd.Resolve.Monitor.SubscribeQueryResults", - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); - if (r < 0) - return log_error_errno(r, "Failed to issue SubscribeQueryResults() varlink call: %m"); + global_llmnr_support = resolve_support_from_string(global_llmnr_support_str); + if (global_llmnr_support < 0) + return log_error_errno(global_llmnr_support, "Received invalid global LLMNR setting: %s", global_llmnr_support_str); - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); + if (global_llmnr_support < llmnr_support) + log_warning("Setting LLMNR support level \"%s\" for \"%s\", but the global support level is \"%s\".", + argv[2], arg_ifname, global_llmnr_support_str); - r = sd_event_get_exit_code(event, &c); - if (r < 0) - return log_error_errno(r, "Failed to get exit code: %m"); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - return c; -} + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkLLMNR", &error, NULL, "is", arg_ifindex, argv[2]); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); -static int dump_cache_item(sd_json_variant *item) { + r = bus_call_method(bus, bus_network_mgr, "SetLinkLLMNR", &error, NULL, "is", arg_ifindex, argv[2]); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; - struct item_info { - sd_json_variant *key; - sd_json_variant *rrs; - const char *type; - uint64_t until; - } item_info = {}; + return log_error_errno(r, "Failed to set LLMNR configuration: %s", bus_error_message(&error, r)); + } - static const sd_json_dispatch_field dispatch_table[] = { - { "key", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant_noref, offsetof(struct item_info, key), SD_JSON_MANDATORY }, - { "rrs", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct item_info, rrs), 0 }, - { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct item_info, type), 0 }, - { "until", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct item_info, until), 0 }, - {}, - }; + return 0; +} - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; - int r, c = 0; +VERB(verb_mdns, "mdns", "[LINK [MODE]]", VERB_ANY, 3, 0, + "Get/set per-interface MulticastDNS mode"); +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; + ResolveSupport global_mdns_support, mdns_support; + int r; - r = sd_json_dispatch(item, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &item_info); + r = acquire_bus(&bus); if (r < 0) return r; - r = dns_resource_key_from_json(item_info.key, &k); + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } + + if (arg_ifindex <= 0) + return status_all(STATUS_MDNS); + + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_MDNS); + + mdns_support = resolve_support_from_string(argv[2]); + if (mdns_support < 0) + return log_error_errno(mdns_support, "Invalid mDNS setting: %s", argv[2]); + + r = bus_get_property_string(bus, bus_resolve_mgr, "MulticastDNS", &error, &global_mdns_support_str); if (r < 0) - return log_error_errno(r, "Failed to turn JSON data to resource key: %m"); + return log_error_errno(r, "Failed to get the global mDNS support state: %s", bus_error_message(&error, r)); - if (item_info.type) - printf("%s %s%s%s\n", DNS_RESOURCE_KEY_TO_STRING(k), ansi_highlight_red(), item_info.type, ansi_normal()); - else { - sd_json_variant *i; + global_mdns_support = resolve_support_from_string(global_mdns_support_str); + if (global_mdns_support < 0) + return log_error_errno(global_mdns_support, "Received invalid global mDNS setting: %s", global_mdns_support_str); - JSON_VARIANT_ARRAY_FOREACH(i, item_info.rrs) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ void *data = NULL; - sd_json_variant *raw; - size_t size; + if (global_mdns_support < mdns_support) + log_warning("Setting mDNS support level \"%s\" for \"%s\", but the global support level is \"%s\".", + argv[2], arg_ifname, global_mdns_support_str); - raw = sd_json_variant_by_key(i, "raw"); - if (!raw) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "raw field missing from RR JSON data."); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = sd_json_variant_unbase64(raw, &data, &size); - if (r < 0) - return log_error_errno(r, "Unable to decode raw RR JSON data: %m"); + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkMulticastDNS", &error, NULL, "is", arg_ifindex, argv[2]); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); - r = dns_resource_record_new_from_raw(&rr, data, size); - if (r < 0) - return log_error_errno(r, "Failed to parse DNS data: %m"); + r = bus_call_method( + bus, + bus_network_mgr, + "SetLinkMulticastDNS", + &error, + NULL, + "is", arg_ifindex, argv[2]); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; - printf("%s\n", dns_resource_record_to_string(rr)); - c++; - } + return log_error_errno(r, "Failed to set MulticastDNS configuration: %s", bus_error_message(&error, r)); } - return c; + return 0; } -static int dump_cache_scope(sd_json_variant *scope) { - - struct scope_info { - const char *protocol; - int family; - int ifindex; - const char *ifname; - sd_json_variant *cache; - const char *dnssec_mode; - const char *dns_over_tls_mode; - } scope_info = { - .family = AF_UNSPEC, - }; - sd_json_variant *i; - int r, c = 0; - - static const sd_json_dispatch_field dispatch_table[] = { - { "protocol", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, protocol), SD_JSON_MANDATORY }, - { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(struct scope_info, family), 0 }, - { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct scope_info, ifindex), SD_JSON_RELAX }, - { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, ifname), 0 }, - { "cache", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant_noref, offsetof(struct scope_info, cache), SD_JSON_MANDATORY }, - { "dnssec", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dnssec_mode), 0 }, - { "dnsOverTLS", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct scope_info, dns_over_tls_mode), 0 }, - {}, - }; +VERB(verb_dns_over_tls, "dnsovertls", "[LINK [MODE]]", VERB_ANY, 3, 0, + "Get/set per-interface DNS-over-TLS mode"); +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; - r = sd_json_dispatch(scope, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &scope_info); + r = acquire_bus(&bus); if (r < 0) return r; - printf("%sScope protocol=%s", ansi_underline(), scope_info.protocol); - - if (scope_info.family != AF_UNSPEC) - printf(" family=%s", af_to_name(scope_info.family)); + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } - if (scope_info.ifindex > 0) - printf(" ifindex=%i", scope_info.ifindex); - if (scope_info.ifname) - printf(" ifname=%s", scope_info.ifname); + if (arg_ifindex <= 0) + return status_all(STATUS_DNS_OVER_TLS); - if (dns_protocol_from_string(scope_info.protocol) == DNS_PROTOCOL_DNS) { - if (scope_info.dnssec_mode) - printf(" DNSSEC=%s", scope_info.dnssec_mode); - if (scope_info.dns_over_tls_mode) - printf(" DNSOverTLS=%s", scope_info.dns_over_tls_mode); - } + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_DNS_OVER_TLS); - printf("%s\n", ansi_normal()); + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - JSON_VARIANT_ARRAY_FOREACH(i, scope_info.cache) { - r = dump_cache_item(i); - if (r < 0) - return r; + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDNSOverTLS", &error, NULL, "is", arg_ifindex, argv[2]); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); - c += r; + r = bus_call_method( + bus, + bus_network_mgr, + "SetLinkDNSOverTLS", + &error, + NULL, + "is", arg_ifindex, argv[2]); } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; - if (c == 0) - printf("%sNo entries.%s\n\n", ansi_grey(), ansi_normal()); - else - printf("\n"); + return log_error_errno(r, "Failed to set DNSOverTLS configuration: %s", bus_error_message(&error, r)); + } return 0; } -static int verb_show_cache(int argc, char *argv[], void *userdata) { - sd_json_variant *reply = NULL, *d = NULL; - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; +VERB(verb_dnssec, "dnssec", "[LINK [MODE]]", VERB_ANY, 3, 0, + "Get/set per-interface DNSSEC mode"); +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; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); - if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - - r = varlink_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.DumpCache", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + r = acquire_bus(&bus); if (r < 0) return r; - d = sd_json_variant_by_key(reply, "dump"); - if (!d) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response is missing 'dump' key."); + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } - if (!sd_json_variant_is_array(d)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response 'dump' field not an array"); + if (arg_ifindex <= 0) + return status_all(STATUS_DNSSEC); - if (!sd_json_format_enabled(arg_json_format_flags)) { - sd_json_variant *i; + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_DNSSEC); - JSON_VARIANT_ARRAY_FOREACH(i, d) { - r = dump_cache_scope(i); - if (r < 0) - return r; - } + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - return 0; - } + r = bus_call_method(bus, bus_resolve_mgr, "SetLinkDNSSEC", &error, NULL, "is", arg_ifindex, argv[2]); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); - return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); -} + r = bus_call_method(bus, bus_network_mgr, "SetLinkDNSSEC", &error, NULL, "is", arg_ifindex, argv[2]); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; -static int dump_server_state(sd_json_variant *server) { - _cleanup_(table_unrefp) Table *table = NULL; - TableCell *cell; + return log_error_errno(r, "Failed to set DNSSEC configuration: %s", bus_error_message(&error, r)); + } - struct server_state { - const char *server_name; - const char *type; - const char *ifname; - int ifindex; - const char *verified_feature_level; - const char *possible_feature_level; - const char *dnssec_mode; - bool dnssec_supported; - size_t received_udp_fragment_max; - uint64_t n_failed_udp; - uint64_t n_failed_tcp; - bool packet_truncated; - bool packet_bad_opt; - bool packet_rrsig_missing; - bool packet_invalid; - bool packet_do_off; - } server_state = { - .ifindex = -1, - }; + return 0; +} +static int call_nta(sd_bus *bus, char **nta, const BusLocator *locator, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL; int r; - static const sd_json_dispatch_field dispatch_table[] = { - { "Server", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, server_name), SD_JSON_MANDATORY }, - { "Type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, type), SD_JSON_MANDATORY }, - { "Interface", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, ifname), 0 }, - { "InterfaceIndex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(struct server_state, ifindex), SD_JSON_RELAX }, - { "VerifiedFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, verified_feature_level), 0 }, - { "PossibleFeatureLevel", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, possible_feature_level), 0 }, - { "DNSSECMode", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct server_state, dnssec_mode), SD_JSON_MANDATORY }, - { "DNSSECSupported", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, dnssec_supported), SD_JSON_MANDATORY }, - { "ReceivedUDPFragmentMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, received_udp_fragment_max), SD_JSON_MANDATORY }, - { "FailedUDPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_udp), SD_JSON_MANDATORY }, - { "FailedTCPAttempts", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(struct server_state, n_failed_tcp), SD_JSON_MANDATORY }, - { "PacketTruncated", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_truncated), SD_JSON_MANDATORY }, - { "PacketBadOpt", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_bad_opt), SD_JSON_MANDATORY }, - { "PacketRRSIGMissing", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_rrsig_missing), SD_JSON_MANDATORY }, - { "PacketInvalid", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_invalid), SD_JSON_MANDATORY }, - { "PacketDoOff", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct server_state, packet_do_off), SD_JSON_MANDATORY }, - {}, - }; + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = sd_json_dispatch(server, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &server_state); + r = bus_message_new_method_call(bus, &req, locator, "SetLinkDNSSECNegativeTrustAnchors"); if (r < 0) - return r; - - table = table_new_vertical(); - if (!table) - return log_oom(); + return bus_log_create_error(r); - assert_se(cell = table_get_cell(table, 0, 0)); - (void) table_set_ellipsize_percent(table, cell, 100); - (void) table_set_align_percent(table, cell, 0); + r = sd_bus_message_append(req, "i", arg_ifindex); + if (r < 0) + return bus_log_create_error(r); - r = table_add_cell_stringf(table, NULL, "Server: %s", server_state.server_name); + r = sd_bus_message_append_strv(req, nta); if (r < 0) - return table_log_add_error(r); + return bus_log_create_error(r); - r = table_add_many(table, - TABLE_EMPTY, - TABLE_FIELD, "Type", - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_STRING, server_state.type); + return sd_bus_call(bus, req, 0, error, NULL); +} + +VERB(verb_nta, "nta", "[LINK [DOMAIN…]]", VERB_ANY, VERB_ANY, 0, + "Get/set per-interface DNSSEC NTA"); +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; + bool clear; + int r; + + r = acquire_bus(&bus); if (r < 0) - return table_log_add_error(r); + return r; - if (server_state.ifname) { - r = table_add_many(table, - TABLE_FIELD, "Interface", - TABLE_STRING, server_state.ifname); + if (argc >= 2) { + r = ifname_mangle(argv[1]); if (r < 0) - return table_log_add_error(r); + return r; } - if (server_state.ifindex >= 0) { - r = table_add_many(table, - TABLE_FIELD, "Interface Index", - TABLE_INT, server_state.ifindex); - if (r < 0) - return table_log_add_error(r); - } + if (arg_ifindex <= 0) + return status_all(STATUS_NTA); - if (server_state.verified_feature_level) { - r = table_add_many(table, - TABLE_FIELD, "Verified feature level", - TABLE_STRING, server_state.verified_feature_level); - if (r < 0) - return table_log_add_error(r); - } + if (argc < 3) + return status_ifindex(arg_ifindex, STATUS_NTA); - if (server_state.possible_feature_level) { - r = table_add_many(table, - TABLE_FIELD, "Possible feature level", - TABLE_STRING, server_state.possible_feature_level); - if (r < 0) - return table_log_add_error(r); - } + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - r = table_add_many(table, - TABLE_FIELD, "DNSSEC Mode", - TABLE_STRING, server_state.dnssec_mode, - TABLE_FIELD, "DNSSEC Supported", - TABLE_STRING, yes_no(server_state.dnssec_supported), - TABLE_FIELD, "Maximum UDP fragment size received", - TABLE_UINT64, server_state.received_udp_fragment_max, - TABLE_FIELD, "Failed UDP attempts", - TABLE_UINT64, server_state.n_failed_udp, - TABLE_FIELD, "Failed TCP attempts", - TABLE_UINT64, server_state.n_failed_tcp, - TABLE_FIELD, "Seen truncated packet", - TABLE_STRING, yes_no(server_state.packet_truncated), - TABLE_FIELD, "Seen OPT RR getting lost", - TABLE_STRING, yes_no(server_state.packet_bad_opt), - TABLE_FIELD, "Seen RRSIG RR missing", - TABLE_STRING, yes_no(server_state.packet_rrsig_missing), - TABLE_FIELD, "Seen invalid packet", - TABLE_STRING, yes_no(server_state.packet_invalid), - TABLE_FIELD, "Server dropped DO flag", - TABLE_STRING, yes_no(server_state.packet_do_off), - TABLE_SET_ALIGN_PERCENT, 0, - TABLE_EMPTY, TABLE_EMPTY); + /* If only argument is the empty string, then call SetLinkDNSSECNegativeTrustAnchors() + * with an empty list, which will clear the list of domains for an interface. */ + args = strv_skip(argv, 2); + clear = strv_equal(args, STRV_MAKE("")); + + if (!clear) + STRV_FOREACH(p, args) { + r = dns_name_is_valid(*p); + if (r < 0) + return log_error_errno(r, "Failed to validate specified domain %s: %m", *p); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Domain not valid: %s", + *p); + } - if (r < 0) - return table_log_add_error(r); + r = call_nta(bus, clear ? NULL : args, bus_resolve_mgr, &error); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); + r = call_nta(bus, clear ? NULL : args, bus_network_mgr, &error); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to set DNSSEC NTA configuration: %s", bus_error_message(&error, r)); + } return 0; } -static int verb_show_server_state(int argc, char *argv[], void *userdata) { - sd_json_variant *reply = NULL, *d = NULL; - _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; +VERB(verb_revert_link, "revert", "LINK", VERB_ANY, 2, 0, + "Revert per-interface configuration"); +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; - (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - - r = sd_varlink_connect_address(&vl, "/run/systemd/resolve/io.systemd.Resolve.Monitor"); - if (r < 0) - return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - - r = varlink_callbo_and_log( - vl, - "io.systemd.Resolve.Monitor.DumpServerState", - &reply, - SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", arg_ask_password)); + r = acquire_bus(&bus); if (r < 0) return r; - d = sd_json_variant_by_key(reply, "dump"); - if (!d) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response is missing 'dump' key."); + if (argc >= 2) { + r = ifname_mangle(argv[1]); + if (r < 0) + return r; + } - if (!sd_json_variant_is_array(d)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "DumpCache() response 'dump' field not an array"); + if (arg_ifindex <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Interface argument required."); - if (!sd_json_format_enabled(arg_json_format_flags)) { - sd_json_variant *i; + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); - JSON_VARIANT_ARRAY_FOREACH(i, d) { - r = dump_server_state(i); - if (r < 0) - return r; - } + r = bus_call_method(bus, bus_resolve_mgr, "RevertLink", &error, NULL, "i", arg_ifindex); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_LINK_BUSY)) { + sd_bus_error_free(&error); - return 0; + r = bus_call_method(bus, bus_network_mgr, "RevertLinkDNS", &error, NULL, "i", arg_ifindex); + } + if (r < 0) { + if (arg_ifindex_permissive && + sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_LINK)) + return 0; + + return log_error_errno(r, "Failed to revert interface configuration: %s", bus_error_message(&error, r)); } - return sd_json_variant_dump(d, arg_json_format_flags, NULL, NULL); + return 0; } -static void help_protocol_types(void) { - if (arg_legend) - puts("Known protocol types:"); - puts("dns\n" - "llmnr\n" - "llmnr-ipv4\n" - "llmnr-ipv6\n" - "mdns\n" - "mdns-ipv4\n" - "mdns-ipv6"); +VERB(verb_log_level, "log-level", "[LEVEL]", VERB_ANY, 2, 0, + "Get/set logging threshold for systemd-resolved"); +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; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + assert(IN_SET(argc, 1, 2)); + + return verb_log_control_common(bus, "org.freedesktop.resolve1", argv[0], argc == 2 ? argv[1] : NULL); +} + +static int parse_protocol(const char *arg) { + if (streq(arg, "help")) { + if (arg_legend) + puts("Known protocol types:"); + puts("dns\n" + "llmnr\n" + "llmnr-ipv4\n" + "llmnr-ipv6\n" + "mdns\n" + "mdns-ipv4\n" + "mdns-ipv6"); + return 0; + } + + if (streq(arg, "dns")) + arg_flags |= SD_RESOLVED_DNS; + else if (streq(arg, "llmnr")) + arg_flags |= SD_RESOLVED_LLMNR; + else if (streq(arg, "llmnr-ipv4")) + arg_flags |= SD_RESOLVED_LLMNR_IPV4; + else if (streq(arg, "llmnr-ipv6")) + arg_flags |= SD_RESOLVED_LLMNR_IPV6; + else if (streq(arg, "mdns")) + arg_flags |= SD_RESOLVED_MDNS; + else if (streq(arg, "mdns-ipv4")) + arg_flags |= SD_RESOLVED_MDNS_IPV4; + else if (streq(arg, "mdns-ipv6")) + arg_flags |= SD_RESOLVED_MDNS_IPV6; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown protocol specifier: %s", arg); + return 1; } static void help_dns_types(void) { @@ -3174,245 +3158,116 @@ static void help_dns_classes(void) { } static int compat_help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("resolvectl", "1", &link); + r = option_parser_get_help_table_ns("systemd-resolve", &options); if (r < 0) - return log_oom(); + return r; pager_open(arg_pager_flags); - printf("%1$s [OPTIONS...] HOSTNAME|ADDRESS...\n" - "%1$s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n" - "%1$s [OPTIONS...] --openpgp EMAIL@DOMAIN...\n" - "%1$s [OPTIONS...] --statistics\n" - "%1$s [OPTIONS...] --reset-statistics\n" - "\n" - "%2$sResolve domain names, IPv4 and IPv6 addresses, DNS records, and services.%3$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " -4 Resolve IPv4 addresses\n" - " -6 Resolve IPv6 addresses\n" - " -i --interface=INTERFACE Look on interface\n" - " -p --protocol=PROTO|help Look via protocol\n" - " -t --type=TYPE|help Query RR with DNS type\n" - " -c --class=CLASS|help Query RR with DNS class\n" - " --service Resolve service (SRV)\n" - " --service-address=BOOL Resolve address for services (default: yes)\n" - " --service-txt=BOOL Resolve TXT records for services (default: yes)\n" - " --openpgp Query OpenPGP public key\n" - " --tlsa Query TLS public key\n" - " --cname=BOOL Follow CNAME redirects (default: yes)\n" - " --search=BOOL Use search domains for single-label names\n" - " (default: yes)\n" - " --raw[=payload|packet] Dump the answer as binary data\n" - " --legend=BOOL Print headers and additional info (default: yes)\n" - " --statistics Show resolver statistics\n" - " --reset-statistics Reset resolver statistics\n" - " --status Show link and server status\n" - " --flush-caches Flush all local DNS caches\n" - " --reset-server-features\n" - " Forget learnt DNS server feature levels\n" - " --set-dns=SERVER Set per-interface DNS server address\n" - " --set-domain=DOMAIN Set per-interface search domain\n" - " --set-llmnr=MODE Set per-interface LLMNR mode\n" - " --set-mdns=MODE Set per-interface MulticastDNS mode\n" - " --set-dnsovertls=MODE Set per-interface DNS-over-TLS mode\n" - " --set-dnssec=MODE Set per-interface DNSSEC mode\n" - " --set-nta=DOMAIN Set per-interface DNSSEC NTA\n" - " --revert Revert per-interface configuration\n" - "\nSee the %4$s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + help_cmdline("[OPTIONS…] HOSTNAME|ADDRESS…"); + help_cmdline("[OPTIONS…] --service [[NAME] TYPE] DOMAIN"); + help_cmdline("[OPTIONS…] --openpgp EMAIL@DOMAIN…"); + help_cmdline("[OPTIONS…] --statistics"); + help_cmdline("[OPTIONS…] --reset-statistics"); + help_abstract("Resolve domain names, IPv4 and IPv6 addresses, DNS records, and services."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("resolvectl", "1"); return 0; } static int native_help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; - r = terminal_urlify_man("resolvectl", "1", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; + + r = option_parser_get_help_table_ns("resolvectl", &options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); pager_open(arg_pager_flags); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n" - "%5$sSend control commands to the network name resolution manager, or%6$s\n" - "%5$sresolve domain names, IPv4 and IPv6 addresses, DNS records, and services.%6$s\n" - "\n%3$sCommands:%4$s\n" - " query HOSTNAME|ADDRESS... Resolve domain names, IPv4 and IPv6 addresses\n" - " service [[NAME] TYPE] DOMAIN Resolve service (SRV)\n" - " openpgp EMAIL@DOMAIN... Query OpenPGP public key\n" - " tlsa DOMAIN[:PORT]... Query TLS public key\n" - " status [LINK...] Show link and server status\n" - " statistics Show resolver statistics\n" - " reset-statistics Reset resolver statistics\n" - " flush-caches Flush all local DNS caches\n" - " reset-server-features Forget learnt DNS server feature levels\n" - " monitor Monitor DNS queries\n" - " show-cache Show cache contents\n" - " show-server-state Show servers state\n" - " dns [LINK [SERVER...]] Get/set per-interface DNS server address\n" - " domain [LINK [DOMAIN...]] Get/set per-interface search domain\n" - " default-route [LINK [BOOL]] Get/set per-interface default route flag\n" - " llmnr [LINK [MODE]] Get/set per-interface LLMNR mode\n" - " mdns [LINK [MODE]] Get/set per-interface MulticastDNS mode\n" - " dnsovertls [LINK [MODE]] Get/set per-interface DNS-over-TLS mode\n" - " dnssec [LINK [MODE]] Get/set per-interface DNSSEC mode\n" - " nta [LINK [DOMAIN...]] Get/set per-interface DNSSEC NTA\n" - " revert LINK Revert per-interface configuration\n" - " log-level [LEVEL] Get/set logging threshold for systemd-resolved\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-ask-password Do not prompt for password\n" - " -4 Resolve IPv4 addresses\n" - " -6 Resolve IPv6 addresses\n" - " -i --interface=INTERFACE Look on interface\n" - " -p --protocol=PROTO|help Look via protocol\n" - " -t --type=TYPE|help Query RR with DNS type\n" - " -c --class=CLASS|help Query RR with DNS class\n" - " --service-address=BOOL Resolve address for services (default: yes)\n" - " --service-txt=BOOL Resolve TXT records for services (default: yes)\n" - " --cname=BOOL Follow CNAME redirects (default: yes)\n" - " --validate=BOOL Allow DNSSEC validation (default: yes)\n" - " --synthesize=BOOL Allow synthetic response (default: yes)\n" - " --cache=BOOL Allow response from cache (default: yes)\n" - " --stale-data=BOOL Allow response from cache with stale data (default: yes)\n" - " --relax-single-label=BOOL Allow single label lookups to go upstream (default: no)\n" - " --zone=BOOL Allow response from locally registered mDNS/LLMNR\n" - " records (default: yes)\n" - " --trust-anchor=BOOL Allow response from local trust anchor (default:\n" - " yes)\n" - " --network=BOOL Allow response from network (default: yes)\n" - " --search=BOOL Use search domains for single-label names (default:\n" - " yes)\n" - " --raw[=payload|packet] Dump the answer as binary data\n" - " --legend=BOOL Print headers and additional info (default: yes)\n" - " --json=MODE Output as JSON\n" - " -j Same as --json=pretty on tty, --json=short\n" - " otherwise\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Send control commands to the network name resolution manager, or\n" + "resolve domain names, IPv4 and IPv6 addresses, DNS records, and services."); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("resolvectl", "1"); return 0; } -static int verb_help(int argc, char **argv, void *userdata) { - return native_help(); -} - -static int compat_parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_LEGEND, - ARG_SERVICE, - ARG_CNAME, - ARG_SERVICE_ADDRESS, - ARG_SERVICE_TXT, - ARG_OPENPGP, - ARG_TLSA, - ARG_RAW, - ARG_SEARCH, - ARG_STATISTICS, - ARG_RESET_STATISTICS, - ARG_STATUS, - ARG_FLUSH_CACHES, - ARG_RESET_SERVER_FEATURES, - ARG_NO_PAGER, - ARG_SET_DNS, - ARG_SET_DOMAIN, - ARG_SET_LLMNR, - ARG_SET_MDNS, - ARG_SET_DNS_OVER_TLS, - ARG_SET_DNSSEC, - ARG_SET_NTA, - ARG_REVERT_LINK, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "class", required_argument, NULL, 'c' }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "interface", required_argument, NULL, 'i' }, - { "protocol", required_argument, NULL, 'p' }, - { "cname", required_argument, NULL, ARG_CNAME }, - { "service", no_argument, NULL, ARG_SERVICE }, - { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, - { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, - { "openpgp", no_argument, NULL, ARG_OPENPGP }, - { "tlsa", optional_argument, NULL, ARG_TLSA }, - { "raw", optional_argument, NULL, ARG_RAW }, - { "search", required_argument, NULL, ARG_SEARCH }, - { "statistics", no_argument, NULL, ARG_STATISTICS, }, - { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS }, - { "status", no_argument, NULL, ARG_STATUS }, - { "flush-caches", no_argument, NULL, ARG_FLUSH_CACHES }, - { "reset-server-features", no_argument, NULL, ARG_RESET_SERVER_FEATURES }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "set-dns", required_argument, NULL, ARG_SET_DNS }, - { "set-domain", required_argument, NULL, ARG_SET_DOMAIN }, - { "set-llmnr", required_argument, NULL, ARG_SET_LLMNR }, - { "set-mdns", required_argument, NULL, ARG_SET_MDNS }, - { "set-dnsovertls", required_argument, NULL, ARG_SET_DNS_OVER_TLS }, - { "set-dnssec", required_argument, NULL, ARG_SET_DNSSEC }, - { "set-nta", required_argument, NULL, ARG_SET_NTA }, - { "revert", no_argument, NULL, ARG_REVERT_LINK }, - {} - }; +VERB_COMMON_HELP_HIDDEN(native_help); - int c, r; +static int compat_parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "systemd-resolve" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_NAMESPACE("systemd-resolve"): {} + + OPTION_COMMON_HELP: return compat_help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case '4': + OPTION_SHORT('4', NULL, "Resolve IPv4 addresses"): arg_family = AF_INET; break; - case '6': + OPTION_SHORT('6', NULL, "Resolve IPv6 addresses"): arg_family = AF_INET6; break; - case 'i': - r = ifname_mangle(optarg); + OPTION('i', "interface", "INTERFACE", "Look on interface"): + r = ifname_mangle(opts.arg); if (r < 0) return r; break; - case 't': - if (streq(optarg, "help")) { + OPTION('p', "protocol", "PROTO|help", "Look via protocol"): + r = parse_protocol(opts.arg); + if (r <= 0) + return r; + break; + + OPTION('t', "type", "TYPE|help", "Query RR with DNS type"): + if (streq(opts.arg, "help")) { help_dns_types(); return 0; } - r = dns_type_from_string(optarg); + r = dns_type_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse RR record type %s: %m", optarg); + return log_error_errno(r, "Failed to parse RR record type %s: %m", opts.arg); arg_type = (uint16_t) r; assert((int) arg_type == r); @@ -3420,190 +3275,162 @@ static int compat_parse_argv(int argc, char *argv[]) { arg_mode = MODE_RESOLVE_RECORD; break; - case 'c': - if (streq(optarg, "help")) { + OPTION('c', "class", "CLASS|help", "Query RR with DNS class"): + if (streq(opts.arg, "help")) { help_dns_classes(); return 0; } - r = dns_class_from_string(optarg); + r = dns_class_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse RR record class %s: %m", optarg); + return log_error_errno(r, "Failed to parse RR record class %s: %m", opts.arg); arg_class = (uint16_t) r; assert((int) arg_class == r); break; - case ARG_LEGEND: - r = parse_boolean_argument("--legend=", optarg, &arg_legend); - if (r < 0) - return r; + OPTION_LONG("service", NULL, "Resolve service (SRV)"): + arg_mode = MODE_RESOLVE_SERVICE; break; - case 'p': - if (streq(optarg, "help")) { - help_protocol_types(); - return 0; - } else if (streq(optarg, "dns")) - arg_flags |= SD_RESOLVED_DNS; - else if (streq(optarg, "llmnr")) - arg_flags |= SD_RESOLVED_LLMNR; - else if (streq(optarg, "llmnr-ipv4")) - arg_flags |= SD_RESOLVED_LLMNR_IPV4; - else if (streq(optarg, "llmnr-ipv6")) - arg_flags |= SD_RESOLVED_LLMNR_IPV6; - else if (streq(optarg, "mdns")) - arg_flags |= SD_RESOLVED_MDNS; - else if (streq(optarg, "mdns-ipv4")) - arg_flags |= SD_RESOLVED_MDNS_IPV4; - else if (streq(optarg, "mdns-ipv6")) - arg_flags |= SD_RESOLVED_MDNS_IPV6; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown protocol specifier: %s", optarg); - + OPTION_LONG("service-address", "BOOL", "Resolve address for services (default: yes)"): + r = parse_boolean_argument("--service-address=", opts.arg, NULL); + if (r < 0) + return r; + SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); break; - case ARG_SERVICE: - arg_mode = MODE_RESOLVE_SERVICE; + OPTION_LONG("service-txt", "BOOL", "Resolve TXT records for services (default: yes)"): + r = parse_boolean_argument("--service-txt=", opts.arg, NULL); + if (r < 0) + return r; + SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); break; - case ARG_OPENPGP: + OPTION_LONG("openpgp", NULL, "Query OpenPGP public key"): arg_mode = MODE_RESOLVE_OPENPGP; break; - case ARG_TLSA: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "tlsa", "FAMILY", "Query TLS public key"): arg_mode = MODE_RESOLVE_TLSA; - if (!optarg || service_family_is_valid(optarg)) - arg_service_family = optarg; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown service family \"%s\".", optarg); - break; - - case ARG_RAW: - if (on_tty()) - return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), - "Refusing to write binary data to tty."); - - if (optarg == NULL || streq(optarg, "payload")) - arg_raw = RAW_PAYLOAD; - else if (streq(optarg, "packet")) - arg_raw = RAW_PACKET; + if (!opts.arg || service_family_is_valid(opts.arg)) + arg_service_family = opts.arg; else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown --raw specifier \"%s\".", - optarg); - - arg_legend = false; + "Unknown service family \"%s\".", opts.arg); break; - case ARG_CNAME: - r = parse_boolean_argument("--cname=", optarg, NULL); + OPTION_LONG("cname", "BOOL", "Follow CNAME redirects (default: yes)"): + r = parse_boolean_argument("--cname=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0); break; - case ARG_SERVICE_ADDRESS: - r = parse_boolean_argument("--service-address=", optarg, NULL); - if (r < 0) - return r; - SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); - break; - - case ARG_SERVICE_TXT: - r = parse_boolean_argument("--service-txt=", optarg, NULL); - if (r < 0) - return r; - SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); - break; - - case ARG_SEARCH: - r = parse_boolean_argument("--search=", optarg, NULL); + OPTION_LONG("search", "BOOL", "Use search domains for single-label names (default: yes)"): + r = parse_boolean_argument("--search=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0); break; - case ARG_STATISTICS: + OPTION_LONG("statistics", NULL, "Show resolver statistics"): arg_mode = MODE_STATISTICS; break; - case ARG_RESET_STATISTICS: + OPTION_LONG("reset-statistics", NULL, "Reset resolver statistics"): arg_mode = MODE_RESET_STATISTICS; break; - case ARG_FLUSH_CACHES: - arg_mode = MODE_FLUSH_CACHES; - break; - - case ARG_RESET_SERVER_FEATURES: - arg_mode = MODE_RESET_SERVER_FEATURES; + OPTION_LONG("status", NULL, "Show link and server status"): + arg_mode = MODE_STATUS; break; - case ARG_STATUS: - arg_mode = MODE_STATUS; + OPTION_LONG("flush-caches", NULL, "Flush all local DNS caches"): + arg_mode = MODE_FLUSH_CACHES; break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION_LONG("reset-server-features", NULL, + "Forget learnt DNS server feature levels"): + arg_mode = MODE_RESET_SERVER_FEATURES; break; - case ARG_SET_DNS: - r = strv_extend(&arg_set_dns, optarg); + OPTION_LONG("set-dns", "SERVER", "Set per-interface DNS server address"): + r = strv_extend(&arg_set_dns, opts.arg); if (r < 0) return log_oom(); arg_mode = MODE_SET_LINK; break; - case ARG_SET_DOMAIN: - r = strv_extend(&arg_set_domain, optarg); + OPTION_LONG("set-domain", "DOMAIN", "Set per-interface search domain"): + r = strv_extend(&arg_set_domain, opts.arg); if (r < 0) return log_oom(); arg_mode = MODE_SET_LINK; break; - case ARG_SET_LLMNR: - arg_set_llmnr = optarg; + OPTION_LONG("set-llmnr", "MODE", "Set per-interface LLMNR mode"): + arg_set_llmnr = opts.arg; arg_mode = MODE_SET_LINK; break; - case ARG_SET_MDNS: - arg_set_mdns = optarg; + OPTION_LONG("set-mdns", "MODE", "Set per-interface MulticastDNS mode"): + arg_set_mdns = opts.arg; arg_mode = MODE_SET_LINK; break; - case ARG_SET_DNS_OVER_TLS: - arg_set_dns_over_tls = optarg; + OPTION_LONG("set-dnsovertls", "MODE", "Set per-interface DNS-over-TLS mode"): + arg_set_dns_over_tls = opts.arg; arg_mode = MODE_SET_LINK; break; - case ARG_SET_DNSSEC: - arg_set_dnssec = optarg; + OPTION_LONG("set-dnssec", "MODE", "Set per-interface DNSSEC mode"): + arg_set_dnssec = opts.arg; arg_mode = MODE_SET_LINK; break; - case ARG_SET_NTA: - r = strv_extend(&arg_set_nta, optarg); + OPTION_LONG("set-nta", "DOMAIN", "Set per-interface DNSSEC NTA"): + r = strv_extend(&arg_set_nta, opts.arg); if (r < 0) return log_oom(); arg_mode = MODE_SET_LINK; break; - case ARG_REVERT_LINK: + OPTION_LONG("revert", NULL, "Revert per-interface configuration"): arg_mode = MODE_REVERT_LINK; break; - case '?': - return -EINVAL; + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "raw", "payload|packet", + "Dump the answer as binary data"): + if (on_tty()) + return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), + "Refusing to write binary data to tty."); + + if (opts.arg == NULL || streq(opts.arg, "payload")) + arg_raw = RAW_PAYLOAD; + else if (streq(opts.arg, "packet")) + arg_raw = RAW_PACKET; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown --raw specifier \"%s\".", + opts.arg); + + arg_legend = false; + break; + + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; - default: - assert_not_reached(); + OPTION_LONG("legend", "BOOL", "Print headers and additional info (default: yes)"): + r = parse_boolean_argument("--legend=", opts.arg, &arg_legend); + if (r < 0) + return r; + break; } if (arg_type == 0 && arg_class != 0) @@ -3627,272 +3454,209 @@ static int compat_parse_argv(int argc, char *argv[]) { "--set-dns=, --set-domain=, --set-llmnr=, --set-mdns=, --set-dnsovertls=, --set-dnssec=, --set-nta= and --revert require --interface=."); } + *remaining_args = option_parser_get_args(&opts); return 1 /* work to do */; } -static int native_parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_LEGEND, - ARG_CNAME, - ARG_VALIDATE, - ARG_SYNTHESIZE, - ARG_CACHE, - ARG_ZONE, - ARG_TRUST_ANCHOR, - ARG_NETWORK, - ARG_SERVICE_ADDRESS, - ARG_SERVICE_TXT, - ARG_RAW, - ARG_SEARCH, - ARG_NO_PAGER, - ARG_NO_ASK_PASSWORD, - ARG_JSON, - ARG_STALE_DATA, - ARG_RELAX_SINGLE_LABEL, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "class", required_argument, NULL, 'c' }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "interface", required_argument, NULL, 'i' }, - { "protocol", required_argument, NULL, 'p' }, - { "cname", required_argument, NULL, ARG_CNAME }, - { "validate", required_argument, NULL, ARG_VALIDATE }, - { "synthesize", required_argument, NULL, ARG_SYNTHESIZE }, - { "cache", required_argument, NULL, ARG_CACHE }, - { "zone", required_argument, NULL, ARG_ZONE }, - { "trust-anchor", required_argument, NULL, ARG_TRUST_ANCHOR }, - { "network", required_argument, NULL, ARG_NETWORK }, - { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, - { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, - { "raw", optional_argument, NULL, ARG_RAW }, - { "search", required_argument, NULL, ARG_SEARCH }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "json", required_argument, NULL, ARG_JSON }, - { "stale-data", required_argument, NULL, ARG_STALE_DATA }, - { "relax-single-label", required_argument, NULL, ARG_RELAX_SINGLE_LABEL }, - {} - }; - - int c, r; +static int native_parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "h46i:t:c:p:j", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "resolvectl" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_NAMESPACE("resolvectl"): {} + + OPTION_COMMON_HELP: return native_help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case '4': + OPTION_SHORT('4', NULL, "Resolve IPv4 addresses"): arg_family = AF_INET; break; - case '6': + OPTION_SHORT('6', NULL, "Resolve IPv6 addresses"): arg_family = AF_INET6; break; - case 'i': - r = ifname_mangle(optarg); + OPTION('i', "interface", "INTERFACE", "Look on interface"): + r = ifname_mangle(opts.arg); if (r < 0) return r; break; - case 't': - if (streq(optarg, "help")) { + OPTION('p', "protocol", "PROTO|help", "Look via protocol"): + r = parse_protocol(opts.arg); + if (r <= 0) + return r; + break; + + OPTION('t', "type", "TYPE|help", "Query RR with DNS type"): + if (streq(opts.arg, "help")) { help_dns_types(); return 0; } - r = dns_type_from_string(optarg); + r = dns_type_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse RR record type %s: %m", optarg); + return log_error_errno(r, "Failed to parse RR record type %s: %m", opts.arg); arg_type = (uint16_t) r; assert((int) arg_type == r); break; - case 'c': - if (streq(optarg, "help")) { + OPTION('c', "class", "CLASS|help", "Query RR with DNS class"): + if (streq(opts.arg, "help")) { help_dns_classes(); return 0; } - r = dns_class_from_string(optarg); + r = dns_class_from_string(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse RR record class %s: %m", optarg); + return log_error_errno(r, "Failed to parse RR record class %s: %m", opts.arg); arg_class = (uint16_t) r; assert((int) arg_class == r); break; - case ARG_LEGEND: - r = parse_boolean_argument("--legend=", optarg, &arg_legend); + OPTION_LONG("service-address", "BOOL", "Resolve address for services (default: yes)"): + r = parse_boolean_argument("--service-address=", opts.arg, NULL); if (r < 0) return r; + SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); break; - case 'p': - if (streq(optarg, "help")) { - help_protocol_types(); - return 0; - } else if (streq(optarg, "dns")) - arg_flags |= SD_RESOLVED_DNS; - else if (streq(optarg, "llmnr")) - arg_flags |= SD_RESOLVED_LLMNR; - else if (streq(optarg, "llmnr-ipv4")) - arg_flags |= SD_RESOLVED_LLMNR_IPV4; - else if (streq(optarg, "llmnr-ipv6")) - arg_flags |= SD_RESOLVED_LLMNR_IPV6; - else if (streq(optarg, "mdns")) - arg_flags |= SD_RESOLVED_MDNS; - else if (streq(optarg, "mdns-ipv4")) - arg_flags |= SD_RESOLVED_MDNS_IPV4; - else if (streq(optarg, "mdns-ipv6")) - arg_flags |= SD_RESOLVED_MDNS_IPV6; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown protocol specifier: %s", - optarg); - - break; - - case ARG_RAW: - if (on_tty()) - return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), - "Refusing to write binary data to tty."); - - if (optarg == NULL || streq(optarg, "payload")) - arg_raw = RAW_PAYLOAD; - else if (streq(optarg, "packet")) - arg_raw = RAW_PACKET; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown --raw specifier \"%s\".", - optarg); - - arg_legend = false; + OPTION_LONG("service-txt", "BOOL", "Resolve TXT records for services (default: yes)"): + r = parse_boolean_argument("--service-txt=", opts.arg, NULL); + if (r < 0) + return r; + SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); break; - case ARG_CNAME: - r = parse_boolean_argument("--cname=", optarg, NULL); + OPTION_LONG("cname", "BOOL", "Follow CNAME redirects (default: yes)"): + r = parse_boolean_argument("--cname=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_CNAME, r == 0); break; - case ARG_VALIDATE: - r = parse_boolean_argument("--validate=", optarg, NULL); + OPTION_LONG("validate", "BOOL", "Allow DNSSEC validation (default: yes)"): + r = parse_boolean_argument("--validate=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_VALIDATE, r == 0); break; - case ARG_SYNTHESIZE: - r = parse_boolean_argument("--synthesize=", optarg, NULL); + OPTION_LONG("synthesize", "BOOL", "Allow synthetic response (default: yes)"): + r = parse_boolean_argument("--synthesize=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_SYNTHESIZE, r == 0); break; - case ARG_CACHE: - r = parse_boolean_argument("--cache=", optarg, NULL); + OPTION_LONG("cache", "BOOL", "Allow response from cache (default: yes)"): + r = parse_boolean_argument("--cache=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_CACHE, r == 0); break; - case ARG_STALE_DATA: - r = parse_boolean_argument("--stale-data=", optarg, NULL); + OPTION_LONG("stale-data", "BOOL", + "Allow response from cache with stale data (default: yes)"): + r = parse_boolean_argument("--stale-data=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_STALE, r == 0); break; - case ARG_ZONE: - r = parse_boolean_argument("--zone=", optarg, NULL); - if (r < 0) - return r; - SET_FLAG(arg_flags, SD_RESOLVED_NO_ZONE, r == 0); - break; - - case ARG_TRUST_ANCHOR: - r = parse_boolean_argument("--trust-anchor=", optarg, NULL); + OPTION_LONG("relax-single-label", "BOOL", + "Allow single label lookups to go upstream (default: no)"): + r = parse_boolean_argument("--relax-single-label=", opts.arg, NULL); if (r < 0) return r; - SET_FLAG(arg_flags, SD_RESOLVED_NO_TRUST_ANCHOR, r == 0); + SET_FLAG(arg_flags, SD_RESOLVED_RELAX_SINGLE_LABEL, r > 0); break; - case ARG_NETWORK: - r = parse_boolean_argument("--network=", optarg, NULL); + OPTION_LONG("zone", "BOOL", + "Allow response from locally registered mDNS/LLMNR records (default: yes)"): + r = parse_boolean_argument("--zone=", opts.arg, NULL); if (r < 0) return r; - SET_FLAG(arg_flags, SD_RESOLVED_NO_NETWORK, r == 0); + SET_FLAG(arg_flags, SD_RESOLVED_NO_ZONE, r == 0); break; - case ARG_SERVICE_ADDRESS: - r = parse_boolean_argument("--service-address=", optarg, NULL); + OPTION_LONG("trust-anchor", "BOOL", + "Allow response from local trust anchor (default: yes)"): + r = parse_boolean_argument("--trust-anchor=", opts.arg, NULL); if (r < 0) return r; - SET_FLAG(arg_flags, SD_RESOLVED_NO_ADDRESS, r == 0); + SET_FLAG(arg_flags, SD_RESOLVED_NO_TRUST_ANCHOR, r == 0); break; - case ARG_SERVICE_TXT: - r = parse_boolean_argument("--service-txt=", optarg, NULL); + OPTION_LONG("network", "BOOL", "Allow response from network (default: yes)"): + r = parse_boolean_argument("--network=", opts.arg, NULL); if (r < 0) return r; - SET_FLAG(arg_flags, SD_RESOLVED_NO_TXT, r == 0); + SET_FLAG(arg_flags, SD_RESOLVED_NO_NETWORK, r == 0); break; - case ARG_SEARCH: - r = parse_boolean_argument("--search=", optarg, NULL); + OPTION_LONG("search", "BOOL", "Use search domains for single-label names (default: yes)"): + r = parse_boolean_argument("--search=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0); break; - case ARG_RELAX_SINGLE_LABEL: - r = parse_boolean_argument("--relax-single-label=", optarg, NULL); - if (r < 0) - return r; - SET_FLAG(arg_flags, SD_RESOLVED_RELAX_SINGLE_LABEL, r > 0); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "raw", "payload|packet", + "Dump the answer as binary data"): + if (on_tty()) + return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), + "Refusing to write binary data to tty."); + + if (opts.arg == NULL || streq(opts.arg, "payload")) + arg_raw = RAW_PAYLOAD; + else if (streq(opts.arg, "packet")) + arg_raw = RAW_PACKET; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown --raw specifier \"%s\".", + opts.arg); + + arg_legend = false; break; - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) + OPTION_LONG("legend", "BOOL", "Print headers and additional info (default: yes)"): + r = parse_boolean_argument("--legend=", opts.arg, &arg_legend); + if (r < 0) return r; + break; + OPTION_COMMON_JSON: + r = parse_json_argument(opts.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(); } if (arg_type == 0 && arg_class != 0) @@ -3905,140 +3669,107 @@ static int native_parse_argv(int argc, char *argv[]) { if (arg_class != 0 && arg_type == 0) arg_type = DNS_TYPE_A; + *remaining_args = option_parser_get_args(&opts); return 1 /* work to do */; } -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}, - {} - }; - - return dispatch_verb(argc, argv, verbs, /* userdata= */ NULL); -} - -static int translate(const char *verb, const char *single_arg, size_t num_args, char **args) { +static int translate(const char *verb, const char *single_arg, char **args) { char **fake, **p; size_t num; assert(verb); - assert(num_args == 0 || args); - num = !!single_arg + num_args + 1; + num = !!single_arg + strv_length(args) + 1; p = fake = newa0(char *, num + 1); *p++ = (char *) verb; if (single_arg) *p++ = (char *) single_arg; - FOREACH_ARRAY(arg, args, num_args) - *p++ = *arg; + STRV_FOREACH(a, args) + *p++ = *a; - optind = 0; - return native_main((int) num, fake); + return dispatch_verb(fake, /* userdata= */ NULL); } -static int compat_main(int argc, char *argv[]) { +static int compat_main(char **args) { int r = 0; switch (arg_mode) { case MODE_RESOLVE_HOST: case MODE_RESOLVE_RECORD: - return translate("query", NULL, argc - optind, argv + optind); + return translate("query", NULL, args); case MODE_RESOLVE_SERVICE: - return translate("service", NULL, argc - optind, argv + optind); + return translate("service", NULL, args); case MODE_RESOLVE_OPENPGP: - return translate("openpgp", NULL, argc - optind, argv + optind); + return translate("openpgp", NULL, args); case MODE_RESOLVE_TLSA: - return translate("tlsa", arg_service_family, argc - optind, argv + optind); + return translate("tlsa", arg_service_family, args); case MODE_STATISTICS: - return translate("statistics", NULL, 0, NULL); + return translate("statistics", NULL, NULL); case MODE_RESET_STATISTICS: - return translate("reset-statistics", NULL, 0, NULL); + return translate("reset-statistics", NULL, NULL); case MODE_FLUSH_CACHES: - return translate("flush-caches", NULL, 0, NULL); + return translate("flush-caches", NULL, NULL); case MODE_RESET_SERVER_FEATURES: - return translate("reset-server-features", NULL, 0, NULL); + return translate("reset-server-features", NULL, NULL); case MODE_STATUS: - return translate("status", NULL, argc - optind, argv + optind); + return translate("status", NULL, args); case MODE_SET_LINK: assert(arg_ifname); if (arg_disable_default_route) { - r = translate("default-route", arg_ifname, 1, STRV_MAKE("no")); + r = translate("default-route", arg_ifname, STRV_MAKE("no")); if (r < 0) return r; } if (arg_set_dns) { - r = translate("dns", arg_ifname, strv_length(arg_set_dns), arg_set_dns); + r = translate("dns", arg_ifname, arg_set_dns); if (r < 0) return r; } if (arg_set_domain) { - r = translate("domain", arg_ifname, strv_length(arg_set_domain), arg_set_domain); + r = translate("domain", arg_ifname, arg_set_domain); if (r < 0) return r; } if (arg_set_nta) { - r = translate("nta", arg_ifname, strv_length(arg_set_nta), arg_set_nta); + r = translate("nta", arg_ifname, arg_set_nta); if (r < 0) return r; } if (arg_set_llmnr) { - r = translate("llmnr", arg_ifname, 1, (char **) &arg_set_llmnr); + r = translate("llmnr", arg_ifname, STRV_MAKE(arg_set_llmnr)); if (r < 0) return r; } if (arg_set_mdns) { - r = translate("mdns", arg_ifname, 1, (char **) &arg_set_mdns); + r = translate("mdns", arg_ifname, STRV_MAKE(arg_set_mdns)); if (r < 0) return r; } if (arg_set_dns_over_tls) { - r = translate("dnsovertls", arg_ifname, 1, (char **) &arg_set_dns_over_tls); + r = translate("dnsovertls", arg_ifname, STRV_MAKE(arg_set_dns_over_tls)); if (r < 0) return r; } if (arg_set_dnssec) { - r = translate("dnssec", arg_ifname, 1, (char **) &arg_set_dnssec); + r = translate("dnssec", arg_ifname, STRV_MAKE(arg_set_dnssec)); if (r < 0) return r; } @@ -4048,7 +3779,7 @@ static int compat_main(int argc, char *argv[]) { case MODE_REVERT_LINK: assert(arg_ifname); - return translate("revert", arg_ifname, 0, NULL); + return translate("revert", arg_ifname, NULL); case _MODE_INVALID: assert_not_reached(); @@ -4058,6 +3789,7 @@ static int compat_main(int argc, char *argv[]) { } static int run(int argc, char **argv) { + char **args = NULL; bool compat = false; int r; @@ -4069,16 +3801,16 @@ static int run(int argc, char **argv) { r = resolvconf_parse_argv(argc, argv); } else if (invoked_as(argv, "systemd-resolve")) { compat = true; - r = compat_parse_argv(argc, argv); + r = compat_parse_argv(argc, argv, &args); } else - r = native_parse_argv(argc, argv); + r = native_parse_argv(argc, argv, &args); if (r <= 0) return r; if (compat) - return compat_main(argc, argv); + return compat_main(args); - return native_main(argc, argv); + return dispatch_verb(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 9e075277ec196..53092bd6d33af 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); @@ -1830,9 +1842,21 @@ static int bus_method_get_link(sd_bus_message *message, void *userdata, sd_bus_e static int bus_method_flush_caches(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.flush-caches", + /* 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, "cache flush"); manager_flush_caches(m, LOG_INFO); @@ -1842,9 +1866,21 @@ static int bus_method_flush_caches(sd_bus_message *message, void *userdata, sd_b static int bus_method_reset_server_features(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-server-features", + /* 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, "server feature reset"); (void) dns_stream_disconnect_all(m); @@ -2019,10 +2055,6 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, if (r == 0) return 1; /* Polkit will call us back */ - r = hashmap_ensure_put(&m->dnssd_registered_services, &string_hash_ops, service->id, service); - if (r < 0) - return r; - r = sd_bus_track_new(sd_bus_message_get_bus(message), &bus_track, dnssd_registered_service_on_bus_track, service); if (r < 0) return r; @@ -2033,6 +2065,10 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, service->manager = m; + r = hashmap_ensure_put(&m->dnssd_registered_services, &string_hash_ops, service->id, service); + if (r < 0) + return r; + service = NULL; manager_refresh_rrs(m); diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index 117bf7ccc3241..2a5c4eb6509fe 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -8,6 +8,7 @@ #include "ordered-set.h" #include "proc-cmdline.h" #include "resolved-conf.h" +#include "resolved-dns-cache.h" #include "resolved-dns-search-domain.h" #include "resolved-dns-server.h" #include "resolved-dns-stub.h" @@ -304,6 +305,28 @@ int manager_parse_config_file(Manager *m) { return 0; } +int config_parse_dns_cache_max( + 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) { + + Manager *m = ASSERT_PTR(userdata); + + assert(ltype >= 0 && ltype < _DNS_PROTOCOL_MAX); + + return config_parse_unsigned_bounded( + unit, filename, line, section, section_line, lvalue, rvalue, + 0, CACHE_MAX_UPPER_LIMIT, true, + &m->cache_max[ltype]); +} + int config_parse_record_types( const char *unit, const char *filename, diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h index 71899d36f680d..51be831086074 100644 --- a/src/resolve/resolved-conf.h +++ b/src/resolve/resolved-conf.h @@ -19,4 +19,5 @@ CONFIG_PARSER_PROTOTYPE(config_parse_dns_servers); CONFIG_PARSER_PROTOTYPE(config_parse_search_domains); CONFIG_PARSER_PROTOTYPE(config_parse_dns_stub_listener_mode); CONFIG_PARSER_PROTOTYPE(config_parse_dns_stub_listener_extra); +CONFIG_PARSER_PROTOTYPE(config_parse_dns_cache_max); CONFIG_PARSER_PROTOTYPE(config_parse_record_types); 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-cache.c b/src/resolve/resolved-dns-cache.c index 5540ce5c0593d..3d5dc2772a83a 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -18,10 +18,6 @@ #include "string-util.h" #include "time-util.h" -/* Never cache more than 4K entries. RFC 1536, Section 5 suggests to - * leave DNS caches unbounded, but that's crazy. */ -#define CACHE_MAX 4096 - /* We never keep any item longer than 2h in our cache unless StaleRetentionSec is greater than zero. */ #define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR) @@ -68,7 +64,7 @@ struct DnsCacheItem { }; /* Returns true if this is a cache item created as result of an explicit lookup, or created as "side-effect" - * of another request. "Primary" entries will carry the full answer data (with NSEC, …) that can aso prove + * of another request. "Primary" entries will carry the full answer data (with NSEC, …) that can also prove * wildcard expansion, non-existence and such, while entries that were created as "side-effect" just contain * immediate RR data for the specified RR key, but nothing else. */ #define DNS_CACHE_ITEM_IS_PRIMARY(item) (!!(item)->answer) @@ -184,9 +180,12 @@ static void dns_cache_make_space(DnsCache *c, unsigned add) { if (add <= 0) return; + if (c->cache_max == 0) + return; + /* Makes space for n new entries. Note that we actually allow - * the cache to grow beyond CACHE_MAX, but only when we shall - * add more RRs to the cache than CACHE_MAX at once. In that + * the cache to grow beyond cache_max, but only when we shall + * add more RRs to the cache than cache_max at once. In that * case the cache will be emptied completely otherwise. */ for (;;) { @@ -196,7 +195,7 @@ static void dns_cache_make_space(DnsCache *c, unsigned add) { if (prioq_isempty(c->by_expiry)) break; - if (prioq_size(c->by_expiry) + add < CACHE_MAX) + if (prioq_size(c->by_expiry) + add < c->cache_max) break; i = prioq_peek(c->by_expiry); @@ -753,6 +752,10 @@ int dns_cache_put( assert(c); assert(owner_address); + /* Check cache mode here too, since the mDNS caller doesn't guard against Cache=no. */ + if (cache_mode == DNS_CACHE_MODE_NO || c->cache_max == 0) + return 0; + dns_cache_remove_previous(c, key, answer); /* We only care for positive replies and NXDOMAINs, on all other replies we will simply flush the respective diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h index be98a8a567665..54ae110c09ca9 100644 --- a/src/resolve/resolved-dns-cache.h +++ b/src/resolve/resolved-dns-cache.h @@ -3,11 +3,17 @@ #include "resolved-forward.h" +/* Never cache more than 4K entries by default. RFC 1536, Section 5 suggests to + * leave DNS caches unbounded, but that's crazy. */ +#define DEFAULT_CACHE_MAX 4096U +#define CACHE_MAX_UPPER_LIMIT (1U << 24) + typedef struct DnsCache { Hashmap *by_key; Prioq *by_expiry; unsigned n_hit; unsigned n_miss; + unsigned cache_max; } DnsCache; void dns_cache_flush(DnsCache *c); 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..36eb3595d5bec 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; @@ -68,6 +62,17 @@ static int rr_compare(DnsResourceRecord * const *a, DnsResourceRecord * const *b return CMP(DNS_RESOURCE_RECORD_RDATA_SIZE(x), DNS_RESOURCE_RECORD_RDATA_SIZE(y)); } +/* The DNSSEC verification and digest helpers below reserve -EOPNOTSUPP to mean "this algorithm or digest + * is not supported (or is disabled by host policy)" — a condition their callers deliberately treat as an + * insecure-but-accepted result (DNSSEC_UNSUPPORTED_ALGORITHM and friends). Once an algorithm has been + * established as supported, a low-level OpenSSL failure during the actual computation can nonetheless + * translate to -EOPNOTSUPP (e.g. an OpenSSL provider pushing ERR_R_UNSUPPORTED, ERR_R_FETCH_FAILED, or + * ERR_R_DISABLED under FIPS or similar). Such a failure must fail closed, so collapse it onto -EIO and + * keep -EOPNOTSUPP exclusively for the genuine unsupported-algorithm signal. */ +static int dnssec_verify_errno(int r) { + return r == -EOPNOTSUPP ? -EIO : r; +} + static int dnssec_rsa_verify_raw( const EVP_MD *hash_algorithm, const void *signature, size_t signature_size, @@ -84,48 +89,48 @@ 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; + return log_openssl_errors(LOG_DEBUG, "Failed to convert RSA exponent to BIGNUM"); - m = BN_bin2bn(modulus, modulus_size, NULL); + m = sym_BN_bin2bn(modulus, modulus_size, NULL); if (!m) - return -EIO; + return log_openssl_errors(LOG_DEBUG, "Failed to convert RSA modulus to BIGNUM"); - rpubkey = RSA_new(); + rpubkey = sym_RSA_new(); if (!rpubkey) return -ENOMEM; - if (RSA_set0_key(rpubkey, m, e, NULL) <= 0) - return -EIO; + if (sym_RSA_set0_key(rpubkey, m, e, NULL) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set RSA public key"); 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) - return -EIO; + if (sym_EVP_PKEY_assign_RSA(epubkey, sym_RSAPublicKey_dup(rpubkey)) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to assign RSA public key"); - 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) - return -EIO; + if (sym_EVP_PKEY_verify_init(ctx) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize RSA signature verification"); - if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) - return -EIO; + if (sym_EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set RSA padding"); - if (EVP_PKEY_CTX_set_signature_md(ctx, hash_algorithm) <= 0) - return -EIO; + if (sym_EVP_PKEY_CTX_set_signature_md(ctx, hash_algorithm) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set RSA signature digest"); - 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()); + return log_openssl_errors(LOG_DEBUG, "Signature verification failed"); REENABLE_WARNING; return r; @@ -146,10 +151,15 @@ static int dnssec_rsa_verify( assert(rrsig); assert(dnskey); + if (dnskey->dnskey.key_size < 1) + return -EINVAL; + if (*(uint8_t*) dnskey->dnskey.key == 0) { /* exponent is > 255 bytes long */ - exponent = (uint8_t*) dnskey->dnskey.key + 3; + if (dnskey->dnskey.key_size < 3) + return -EINVAL; + exponent_size = ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) | ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]); @@ -160,13 +170,12 @@ static int dnssec_rsa_verify( if (3 + exponent_size >= dnskey->dnskey.key_size) return -EINVAL; + exponent = (uint8_t*) dnskey->dnskey.key + 3; modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size; modulus_size = dnskey->dnskey.key_size - 3 - exponent_size; } else { /* exponent is <= 255 bytes long */ - - exponent = (uint8_t*) dnskey->dnskey.key + 1; exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0]; if (exponent_size <= 0) @@ -175,6 +184,7 @@ static int dnssec_rsa_verify( if (1 + exponent_size >= dnskey->dnskey.key_size) return -EINVAL; + exponent = (uint8_t*) dnskey->dnskey.key + 1; modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size; modulus_size = dnskey->dnskey.key_size - 1 - exponent_size; } @@ -206,56 +216,55 @@ 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) - return -EIO; + if (sym_EC_POINT_oct2point(ec_group, p, key, key_size, bctx) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to parse EC public key point"); - eckey = EC_KEY_new(); + eckey = sym_EC_KEY_new(); if (!eckey) return -ENOMEM; - if (EC_KEY_set_group(eckey, ec_group) <= 0) - return -EIO; + if (sym_EC_KEY_set_group(eckey, ec_group) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set EC group"); - if (EC_KEY_set_public_key(eckey, p) <= 0) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "EC_POINT_bn2point failed: 0x%lx", ERR_get_error()); + if (sym_EC_KEY_set_public_key(eckey, p) <= 0) + return log_openssl_errors(LOG_DEBUG, "EC_KEY_set_public_key failed"); - assert(EC_KEY_check_key(eckey) == 1); + if (sym_EC_KEY_check_key(eckey) != 1) + return log_openssl_errors(LOG_DEBUG, "EC_KEY_check_key failed"); - r = BN_bin2bn(signature_r, signature_r_size, NULL); + r = sym_BN_bin2bn(signature_r, signature_r_size, NULL); if (!r) - return -EIO; + return log_openssl_errors(LOG_DEBUG, "Failed to convert ECDSA signature r to BIGNUM"); - s = BN_bin2bn(signature_s, signature_s_size, NULL); + s = sym_BN_bin2bn(signature_s, signature_s_size, NULL); if (!s) - return -EIO; + return log_openssl_errors(LOG_DEBUG, "Failed to convert ECDSA signature s to BIGNUM"); /* 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) - return -EIO; + if (sym_ECDSA_SIG_set0(sig, r, s) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set ECDSA signature"); 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()); + return log_openssl_errors(LOG_DEBUG, "Signature verification failed"); REENABLE_WARNING; return k; @@ -323,30 +332,28 @@ 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()); + return log_openssl_errors(LOG_DEBUG, "EVP_PKEY_new_raw_public_key failed"); - 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) - return -EIO; + if (sym_EVP_DigestVerifyInit(ctx, &pctx, NULL, NULL, evkey) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize EdDSA verification"); - 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()); + return log_openssl_errors(LOG_DEBUG, "Signature verification failed"); return r; } @@ -379,12 +386,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 +508,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; @@ -629,11 +636,13 @@ static int dnssec_rrset_verify_sig( switch (rrsig->rrsig.algorithm) { case DNSSEC_ALGORITHM_ED25519: - return dnssec_eddsa_verify( + /* The algorithm is supported, so a -EOPNOTSUPP from the actual verification is a hard + * crypto failure, not an unsupported-algorithm condition: fail closed. */ + return dnssec_verify_errno(dnssec_eddsa_verify( rrsig->rrsig.algorithm, sig_data, sig_size, rrsig, - dnskey); + dnskey)); case DNSSEC_ALGORITHM_ED448: return -EOPNOTSUPP; default: @@ -642,18 +651,20 @@ 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) - return -EIO; + if (sym_EVP_DigestUpdate(ctx, sig_data, sig_size) <= 0) + return dnssec_verify_errno(log_openssl_errors(LOG_DEBUG, "Failed to update digest")); - if (EVP_DigestFinal_ex(ctx, hash, &hash_size) <= 0) - return -EIO; + if (sym_EVP_DigestFinal_ex(ctx, hash, &hash_size) <= 0) + return dnssec_verify_errno(log_openssl_errors(LOG_DEBUG, "Failed to finalize digest")); assert(hash_size > 0); } @@ -664,20 +675,20 @@ static int dnssec_rrset_verify_sig( case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: case DNSSEC_ALGORITHM_RSASHA256: case DNSSEC_ALGORITHM_RSASHA512: - return dnssec_rsa_verify( + return dnssec_verify_errno(dnssec_rsa_verify( md_algorithm, hash, hash_size, rrsig, - dnskey); + dnskey)); case DNSSEC_ALGORITHM_ECDSAP256SHA256: case DNSSEC_ALGORITHM_ECDSAP384SHA384: - return dnssec_ecdsa_verify( + return dnssec_verify_errno(dnssec_ecdsa_verify( md_algorithm, rrsig->rrsig.algorithm, hash, hash_size, rrsig, - dnskey); + dnskey)); default: assert_not_reached(); @@ -704,6 +715,11 @@ int dnssec_verify_rrset( assert(rrsig); assert(dnskey); assert(result); + + r = DLOPEN_LIBCRYPTO(LOG_WARNING, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + if (r < 0) + return r; + assert(rrsig->key->type == DNS_TYPE_RRSIG); assert(dnskey->key->type == DNS_TYPE_DNSKEY); @@ -912,9 +928,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 +935,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 +1052,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 +1073,10 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, assert(dnskey); assert(ds); + r = DLOPEN_LIBCRYPTO(LOG_WARNING, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + 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,21 +1107,23 @@ 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) - return -EIO; + if (sym_EVP_DigestUpdate(ctx, wire_format, encoded_length) <= 0) + return dnssec_verify_errno(log_openssl_errors(LOG_DEBUG, "Failed to update digest")); if (mask_revoke) md_add_uint16(ctx, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE); @@ -1109,11 +1136,11 @@ 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) - return -EIO; + if (sym_EVP_DigestUpdate(ctx, dnskey->dnskey.key, dnskey->dnskey.key_size) <= 0) + return dnssec_verify_errno(log_openssl_errors(LOG_DEBUG, "Failed to update digest")); - if (EVP_DigestFinal_ex(ctx, result, NULL) <= 0) - return -EIO; + if (sym_EVP_DigestFinal_ex(ctx, result, NULL) <= 0) + return dnssec_verify_errno(log_openssl_errors(LOG_DEBUG, "Failed to finalize digest")); return memcmp(result, ds->ds.digest, ds->ds.digest_size) == 0; } @@ -1121,6 +1148,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 +1173,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 +1198,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 +1213,10 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { assert(name); assert(ret); + r = DLOPEN_LIBCRYPTO(LOG_WARNING, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + if (r < 0) + return r; + if (nsec3->key->type != DNS_TYPE_NSEC3) return -EINVAL; @@ -1190,42 +1229,42 @@ 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) - return -EIO; - if (EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) - return -EIO; + if (sym_EVP_DigestUpdate(ctx, wire_format, r) <= 0) + return dnssec_verify_errno(log_openssl_errors(LOG_DEBUG, "Failed to update digest")); + if (sym_EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) + return dnssec_verify_errno(log_openssl_errors(LOG_DEBUG, "Failed to update digest")); uint8_t result[EVP_MAX_MD_SIZE]; - if (EVP_DigestFinal_ex(ctx, result, NULL) <= 0) - return -EIO; + if (sym_EVP_DigestFinal_ex(ctx, result, NULL) <= 0) + return dnssec_verify_errno(log_openssl_errors(LOG_DEBUG, "Failed to finalize digest")); 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) - return -EIO; - if (EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) - return -EIO; - - if (EVP_DigestFinal_ex(ctx, result, NULL) <= 0) - return -EIO; + if (sym_EVP_DigestInit_ex(ctx, algorithm, NULL) <= 0) + return -EOPNOTSUPP; + if (sym_EVP_DigestUpdate(ctx, result, hash_size) <= 0) + return dnssec_verify_errno(log_openssl_errors(LOG_DEBUG, "Failed to update digest")); + if (sym_EVP_DigestUpdate(ctx, nsec3->nsec3.salt, nsec3->nsec3.salt_size) <= 0) + return dnssec_verify_errno(log_openssl_errors(LOG_DEBUG, "Failed to update digest")); + + if (sym_EVP_DigestFinal_ex(ctx, result, NULL) <= 0) + return dnssec_verify_errno(log_openssl_errors(LOG_DEBUG, "Failed to finalize digest")); } memcpy(ret, result, hash_size); @@ -2037,6 +2076,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..d48896494f90c 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" @@ -75,6 +76,7 @@ int dns_scope_new( .protocol = protocol, .family = family, .resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC, + .cache.cache_max = m->cache_max[protocol], /* Enforce ratelimiting for the multicast protocols */ .ratelimit = { MULTICAST_RATELIMIT_INTERVAL_USEC, MULTICAST_RATELIMIT_BURST }, @@ -1814,7 +1816,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..4a4c3f001579b 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; @@ -102,6 +102,19 @@ DnsStubListenerExtra *dns_stub_listener_extra_free(DnsStubListenerExtra *p) { p->udp_event_source = sd_event_source_disable_unref(p->udp_event_source); p->tcp_event_source = sd_event_source_disable_unref(p->tcp_event_source); + assert(p->manager); + + /* Detach the raw back-pointers that DnsStream and DnsQuery objects keep to this listener before we + * free it. Otherwise a later dns_stream_complete()/dns_query_free() during the same reload would + * dereference the freed slab: dns_query_free() reads q->stub_listener_extra->queries_by_packet. */ + LIST_FOREACH(streams, s, p->manager->dns_streams) + if (s->stub_listener_extra == p) + s->stub_listener_extra = NULL; + + LIST_FOREACH(queries, q, p->manager->dns_queries) + if (q->stub_listener_extra == p) + q->stub_listener_extra = NULL; + hashmap_free(p->queries_by_packet); return mfree(p); @@ -146,7 +159,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..0bad4a512a352 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -326,6 +326,8 @@ int dns_transaction_new( r = hashmap_replace(s->transactions_by_key, first->key, first); if (r < 0) { LIST_REMOVE(transactions_by_key, first, t); + hashmap_remove(s->manager->dns_transactions, UINT_TO_PTR(t->id)); + t->id = 0; return r; } } @@ -786,8 +788,20 @@ static int dns_transaction_emit_tcp(DnsTransaction *t) { assert(t->server); r = dnstls_stream_connect_tls(s, t->server); - if (r < 0) + if (r < 0) { + /* If libcrypto is not available treat this like a TLS connection loss, so + * that opportunistic DNS-over-TLS downgrades to plaintext instead of + * re-selecting a TLS feature level and failing on every attempt. */ + if (r == -EOPNOTSUPP) { + log_struct_once(LOG_WARNING, + LOG_MESSAGE_ID(SD_MESSAGE_MISSING_DEPENDENCY_STR), + LOG_ITEM("FEATURE=DNS-over-TLS"), + LOG_MESSAGE("DNS-over-TLS has been requested but the required TLS libraries (libssl/libcrypto) are not installed.")); + dns_server_packet_lost(t->server, IPPROTO_TCP, t->current_feature_level); + return -ECONNREFUSED; + } return r; + } } #endif @@ -2621,7 +2635,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 +2853,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 +3281,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 +3314,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 +3508,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-bus.c b/src/resolve/resolved-dnssd-bus.c index 4459b0444c3ad..17eab683b77df 100644 --- a/src/resolve/resolved-dnssd-bus.c +++ b/src/resolve/resolved-dnssd-bus.c @@ -31,6 +31,7 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_ /* good_user= */ s->originator, /* flags= */ 0, &m->polkit_registry, + /* ret_admin= */ NULL, error); 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..6bcbdb526023f 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,64 @@ int dnstls_stream_connect_tls(DnsStream *stream, DnsServer *server) { assert(stream->manager); assert(server); - rb = BIO_new_socket(stream->fd, 0); + r = dnstls_manager_init(stream->manager); + if (r < 0) + return r; + + 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); + + /* Clear any errors left in the thread-local queue by a prior connection attempt (resolved drives + * everything from a single event-loop thread), so the translation below reflects this + * SSL_set_session() failure rather than a stale FIFO entry. */ + sym_ERR_clear_error(); + 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)); + return openssl_to_errno(sym_ERR_get_error()); + 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 +139,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 +151,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 +169,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 +180,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 +206,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 +241,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 +267,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 +299,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 +325,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 +360,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,33 +398,43 @@ 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) { + _cleanup_(SSL_CTX_freep) SSL_CTX *ctx = NULL; int r; assert(manager); - manager->dnstls_data.ctx = SSL_CTX_new(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)); + /* Load libcrypto/libssl on first use, so that the dependencies can be optional. */ + + if (manager->dnstls_data.ctx) + return 0; - r = SSL_CTX_set_min_proto_version(manager->dnstls_data.ctx, TLS1_2_VERSION); + r = DLOPEN_LIBCRYPTO(LOG_WARNING, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + if (r < 0) + return r; + + r = DLOPEN_LIBSSL(LOG_WARNING, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + if (r < 0) + return r; + + ctx = sym_SSL_CTX_new(sym_TLS_client_method()); + if (!ctx) + return log_openssl_errors(LOG_WARNING, "Failed to create SSL context"); + + r = sym_SSL_CTX_set_min_proto_version(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)); + return log_openssl_errors(LOG_WARNING, "Failed to set protocol version on SSL context"); - (void) SSL_CTX_set_options(manager->dnstls_data.ctx, SSL_OP_NO_COMPRESSION); + (void) sym_SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION); - r = SSL_CTX_set_default_verify_paths(manager->dnstls_data.ctx); + r = sym_SSL_CTX_set_default_verify_paths(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)); + return log_openssl_errors(LOG_WARNING, "Failed to load system trust store"); + + manager->dnstls_data.ctx = TAKE_PTR(ctx); return 0; } @@ -420,5 +442,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..b5f31f91397a6 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -6,6 +6,7 @@ _Pragma("GCC diagnostic ignored \"-Wzero-as-null-pointer-constant\"") #endif #include #include "conf-parser.h" +#include "dns-packet.h" #include "resolved-conf.h" #include "resolved-dns-server.h" #include "resolved-manager.h" @@ -31,8 +32,12 @@ 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) +Resolve.DNSCacheSize, config_parse_dns_cache_max, DNS_PROTOCOL_DNS, 0 +Resolve.MulticastDNSCacheSize, config_parse_dns_cache_max, DNS_PROTOCOL_MDNS, 0 +Resolve.LLMNRCacheSize, config_parse_dns_cache_max, DNS_PROTOCOL_LLMNR, 0 Resolve.StaleRetentionSec, config_parse_sec, 0, offsetof(Manager, stale_retention_usec) Resolve.RefuseRecordTypes, config_parse_record_types, 0, offsetof(Manager, refuse_record_types) 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-bus.c b/src/resolve/resolved-link-bus.c index ed4485671c8ad..ba5b00c239afb 100644 --- a/src/resolve/resolved-link-bus.c +++ b/src/resolve/resolved-link-bus.c @@ -321,7 +321,7 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_ if (r < 0) return r; - for (;;) { + for (unsigned n_names = 0;; n_names++) { _cleanup_free_ char *prefixed = NULL; const char *name; int route_only; @@ -339,6 +339,8 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search domain %s", name); if (!route_only && dns_name_is_root(name)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Root domain is not suitable as search domain"); + if (n_names >= LINK_SEARCH_DOMAINS_MAX) + return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many search domains per link"); if (route_only) { prefixed = strjoin("~", name); @@ -681,6 +683,9 @@ int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, v if (r < 0) return r; + if (strv_length(ntas) > LINK_NEGATIVE_TRUST_ANCHORS_MAX) + return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many negative trust anchors per link"); + STRV_FOREACH(i, ntas) { r = dns_name_is_valid(*i); if (r < 0) 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-link.h b/src/resolve/resolved-link.h index 44a6b511c1b67..4c81bdbe66695 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -11,6 +11,7 @@ #define LINK_SEARCH_DOMAINS_MAX 1024 #define LINK_DNS_SERVERS_MAX 256 +#define LINK_NEGATIVE_TRUST_ANCHORS_MAX 2048 typedef struct LinkAddress { Link *link; diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index a0fb74ec3567a..faf539195b048 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" @@ -368,7 +369,7 @@ static int manager_network_monitor_listen(Manager *m) { if (r < 0) return r; - r = sd_event_source_set_priority(m->network_event_source, SD_EVENT_PRIORITY_IMPORTANT+5); + r = sd_event_source_set_priority(m->network_event_source, SD_EVENT_PRIORITY_IMPORTANT-5); if (r < 0) return r; @@ -637,8 +638,11 @@ 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; + for (DnsProtocol p = 0; p < _DNS_PROTOCOL_MAX; p++) + m->cache_max[p] = DEFAULT_CACHE_MAX; m->stale_retention_usec = 0; m->refuse_record_types = set_free(m->refuse_record_types); m->resolv_conf_stat = (struct stat) {}; @@ -659,6 +663,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 +738,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, @@ -744,12 +754,6 @@ int manager_new(Manager **ret) { if (r < 0) log_warning_errno(r, "Failed to parse configuration file, ignoring: %m"); -#if ENABLE_DNS_OVER_TLS - r = dnstls_manager_init(m); - if (r < 0) - return r; -#endif - r = sd_event_default(&m->event); if (r < 0) return r; @@ -917,6 +921,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 +1460,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 +2152,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)); } @@ -2324,7 +2331,7 @@ int manager_send_dns_configuration_changed(Manager *m, Link *l, bool reset) { if (sd_json_variant_equal(configuration, m->dns_configuration_json)) return 0; - JSON_VARIANT_REPLACE(m->dns_configuration_json, TAKE_PTR(configuration)); + json_variant_unref_and_replace(m->dns_configuration_json, configuration); r = varlink_many_notify(m->varlink_dns_configuration_subscription, m->dns_configuration_json); if (r < 0) diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 4f595e6d04c24..6fbd7d39fd4c9 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -25,6 +25,7 @@ typedef struct Manager { DnssecMode dnssec_mode; DnsOverTlsMode dns_over_tls_mode; DnsCacheMode enable_cache; + unsigned cache_max[_DNS_PROTOCOL_MAX]; bool cache_from_localhost; DnsStubListenerMode dns_stub_listener_mode; usec_t stale_retention_usec; @@ -123,6 +124,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-resolv-conf.c b/src/resolve/resolved-resolv-conf.c index 5052bb531e25d..cae546ff82a9f 100644 --- a/src/resolve/resolved-resolv-conf.c +++ b/src/resolve/resolved-resolv-conf.c @@ -21,7 +21,7 @@ #include "string-table.h" #include "string-util.h" #include "strv.h" -#include "tmpfile-util-label.h" +#include "tmpfile-util.h" int manager_check_resolv_conf(const Manager *m) { struct stat st, own; diff --git a/src/resolve/resolved-socket-graveyard.c b/src/resolve/resolved-socket-graveyard.c index bfa3055dedee4..753570f7adc54 100644 --- a/src/resolve/resolved-socket-graveyard.c +++ b/src/resolve/resolved-socket-graveyard.c @@ -2,7 +2,7 @@ #include "sd-event.h" #include "alloc-util.h" -#include "assert-fundamental.h" +#include "assert-util.h" #include "log.h" #include "resolved-manager.h" #include "resolved-socket-graveyard.h" diff --git a/src/resolve/resolved-static-records.c b/src/resolve/resolved-static-records.c new file mode 100644 index 0000000000000..348422e244eba --- /dev/null +++ b/src/resolve/resolved-static-records.c @@ -0,0 +1,218 @@ +/* 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 "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_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + unsigned line = 0, column = 0; + r = sd_json_parse_fd(cf->result, cf->fd, SD_JSON_PARSE_REOPEN_FD, &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..7b591ebbb9068 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) >= 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..2617b62775a7d 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -135,7 +135,8 @@ static int reply_query_state(DnsQuery *q) { /* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ return sd_varlink_errorbo(q->varlink_request, "io.systemd.Resolve.DNSError", - SD_JSON_BUILD_PAIR_INTEGER("rcode", DNS_RCODE_NXDOMAIN)); + SD_JSON_BUILD_PAIR_INTEGER("rcode", DNS_RCODE_NXDOMAIN), + JSON_BUILD_PAIR_STRING_NON_EMPTY("queryString", dns_query_string(q))); case DNS_TRANSACTION_RCODE_FAILURE: return sd_varlink_errorbo(q->varlink_request, "io.systemd.Resolve.DNSError", @@ -143,7 +144,8 @@ static int reply_query_state(DnsQuery *q) { SD_JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0, "extendedDNSErrorCode", SD_JSON_BUILD_INTEGER(q->answer_ede_rcode)), SD_JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg), - "extendedDNSErrorMessage", SD_JSON_BUILD_STRING(q->answer_ede_msg))); + "extendedDNSErrorMessage", SD_JSON_BUILD_STRING(q->answer_ede_msg)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("queryString", dns_query_string(q))); case DNS_TRANSACTION_NULL: case DNS_TRANSACTION_PENDING: @@ -211,6 +213,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; @@ -343,7 +348,7 @@ static int vl_method_resolve_hostname(sd_varlink *link, sd_json_variant *paramet static const sd_json_dispatch_field dispatch_table[] = { { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(LookupParameters, ifindex), SD_JSON_RELAX }, { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, name), SD_JSON_MANDATORY }, - { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(LookupParameters, family), 0 }, + { "family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(LookupParameters, family), SD_JSON_RELAX }, { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LookupParameters, flags), 0 }, {} }; @@ -374,9 +379,6 @@ static int vl_method_resolve_hostname(sd_varlink *link, sd_json_variant *paramet if (r == 0) return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("name")); - if (!IN_SET(p.family, AF_UNSPEC, AF_INET, AF_INET6)) - return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); - if (validate_and_mangle_query_flags(m, &p.flags, p.name, SD_RESOLVED_NO_SEARCH) < 0) return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); @@ -472,7 +474,7 @@ static void vl_method_resolve_address_complete(DnsQuery *query) { static int vl_method_resolve_address(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(LookupParameters, ifindex), SD_JSON_RELAX }, - { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(LookupParameters, family), SD_JSON_MANDATORY }, + { "family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(LookupParameters, family), SD_JSON_MANDATORY }, { "address", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(LookupParameters, address), SD_JSON_MANDATORY }, { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LookupParameters, flags), 0 }, {} @@ -498,9 +500,6 @@ static int vl_method_resolve_address(sd_varlink *link, sd_json_variant *paramete if (r != 0) return r; - if (!IN_SET(p.family, AF_INET, AF_INET6)) - return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); - if (FAMILY_ADDRESS_SIZE(p.family) != p.address.iov_len) return sd_varlink_error(link, "io.systemd.Resolve.BadAddressSize", NULL); @@ -958,7 +957,7 @@ static int vl_method_resolve_service(sd_varlink* link, sd_json_variant* paramete { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParametersResolveService, type), 0 }, { "domain", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParametersResolveService, domain), SD_JSON_MANDATORY }, { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(LookupParametersResolveService, ifindex), SD_JSON_RELAX }, - { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(LookupParametersResolveService, family), 0 }, + { "family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(LookupParametersResolveService, family), SD_JSON_RELAX }, { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LookupParametersResolveService, flags), 0 }, {} }; @@ -984,9 +983,6 @@ static int vl_method_resolve_service(sd_varlink* link, sd_json_variant* paramete if (r != 0) return r; - if (!IN_SET(p.family, AF_INET, AF_INET6, AF_UNSPEC)) - return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); - if (isempty(p.name)) p.name = NULL; else if (!dns_service_name_is_valid(p.name)) diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c index aa165927a423f..66171e9b34983 100644 --- a/src/resolve/resolved.c +++ b/src/resolve/resolved.c @@ -12,7 +12,7 @@ #include "label-util.h" #include "log.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "resolved-bus.h" #include "resolved-manager.h" #include "resolved-resolv-conf.h" @@ -44,13 +44,12 @@ static int run(int argc, char *argv[]) { /* Drop privileges, but only if we have been started as root. If we are not running as root we assume most * privileges are already dropped and we can't create our directory. */ if (getuid() == 0) { - const char *user = "systemd-resolve"; uid_t uid; gid_t gid; - r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0); + r = get_user_creds("systemd-resolve", /* flags= */ 0, NULL, &uid, &gid, NULL, NULL); if (r < 0) - return log_error_errno(r, "Cannot resolve user name %s: %m", user); + return log_error_errno(r, "Cannot resolve user name %s: %m", "systemd-resolve"); /* As we're root, we can create the directory where resolv.conf will live */ r = mkdir_safe_label("/run/systemd/resolve", 0755, uid, gid, MKDIR_WARN_MODE); diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in index 656bc7c0eb7ea..edd7344ab86f3 100644 --- a/src/resolve/resolved.conf.in +++ b/src/resolve/resolved.conf.in @@ -18,7 +18,7 @@ [Resolve] # Some examples of DNS servers which may be used for DNS= and FallbackDNS=: -# Cloudflare: 1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com 2606:4700:4700::1111#cloudflare-dns.com 2606:4700:4700::1001#cloudflare-dns.com +# Cloudflare: 1.1.1.1#one.one.one.one 1.0.0.1#one.one.one.one 2606:4700:4700::1111#one.one.one.one 2606:4700:4700::1001#one.one.one.one # Google: 8.8.8.8#dns.google 8.8.4.4#dns.google 2001:4860:4860::8888#dns.google 2001:4860:4860::8844#dns.google # Quad9: 9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net 2620:fe::fe#dns.quad9.net 2620:fe::9#dns.quad9.net # @@ -36,9 +36,13 @@ #LLMNR={{DEFAULT_LLMNR_MODE_STR}} #Cache=yes #CacheFromLocalhost=no +#DNSCacheSize=4096 +#MulticastDNSCacheSize=4096 +#LLMNRCacheSize=4096 #DNSStubListener=yes #DNSStubListenerExtra= #ReadEtcHosts=yes +#ReadStaticRecords=yes #ResolveUnicastSingleLabel=no #StaleRetentionSec=0 #RefuseRecordTypes= diff --git a/src/resolve/test-dns-cache.c b/src/resolve/test-dns-cache.c index 705878422e081..6dc28f39fccb0 100644 --- a/src/resolve/test-dns-cache.c +++ b/src/resolve/test-dns-cache.c @@ -21,7 +21,9 @@ #include "tmpfile-util.h" static DnsCache new_cache(void) { - return (DnsCache) {}; + return (DnsCache) { + .cache_max = DEFAULT_CACHE_MAX, + }; } typedef struct PutArgs { @@ -511,6 +513,79 @@ TEST(dns_a_to_cname_success_escaped_name_returns_error) { ASSERT_TRUE(dns_cache_is_empty(&cache)); } +TEST(dns_cache_size_honored) { + _cleanup_(dns_cache_unrefp) DnsCache cache = new_cache(); + _cleanup_(put_args_unrefp) PutArgs put_args = mk_put_args(); + + cache.cache_max = 4; + + put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "one.example.com"); + ASSERT_NOT_NULL(put_args.key); + put_args.rcode = DNS_RCODE_SUCCESS; + answer_add_a(&put_args, put_args.key, 0xc0a80101, 3600, DNS_ANSWER_CACHEABLE); + ASSERT_OK(cache_put(&cache, &put_args)); + + dns_resource_key_unref(put_args.key); + dns_answer_unref(put_args.answer); + put_args.answer = dns_answer_new(1); + ASSERT_NOT_NULL(put_args.answer); + + put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "two.example.com"); + ASSERT_NOT_NULL(put_args.key); + answer_add_a(&put_args, put_args.key, 0xc0a80102, 3600, DNS_ANSWER_CACHEABLE); + ASSERT_OK(cache_put(&cache, &put_args)); + + dns_resource_key_unref(put_args.key); + dns_answer_unref(put_args.answer); + put_args.answer = dns_answer_new(1); + ASSERT_NOT_NULL(put_args.answer); + + put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "three.example.com"); + ASSERT_NOT_NULL(put_args.key); + answer_add_a(&put_args, put_args.key, 0xc0a80103, 3600, DNS_ANSWER_CACHEABLE); + ASSERT_OK(cache_put(&cache, &put_args)); + + dns_resource_key_unref(put_args.key); + dns_answer_unref(put_args.answer); + put_args.answer = dns_answer_new(1); + ASSERT_NOT_NULL(put_args.answer); + + put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "four.example.com"); + ASSERT_NOT_NULL(put_args.key); + answer_add_a(&put_args, put_args.key, 0xc0a80104, 3600, DNS_ANSWER_CACHEABLE); + ASSERT_OK(cache_put(&cache, &put_args)); + + dns_resource_key_unref(put_args.key); + dns_answer_unref(put_args.answer); + put_args.answer = dns_answer_new(1); + ASSERT_NOT_NULL(put_args.answer); + + put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "five.example.com"); + ASSERT_NOT_NULL(put_args.key); + answer_add_a(&put_args, put_args.key, 0xc0a80105, 3600, DNS_ANSWER_CACHEABLE); + ASSERT_OK(cache_put(&cache, &put_args)); + + /* Each dns_cache_put() call reserves space for both the answer RR and the key (cache_keys=2), + * so eviction triggers when prioq_size + 2 >= cache_max (i.e. at the 3rd entry with cache_max=4). + * After 5 inserts, only the last 2 entries remain. */ + ASSERT_EQ(dns_cache_size(&cache), 2u); +} + +TEST(dns_cache_size_zero_evicts_all) { + _cleanup_(dns_cache_unrefp) DnsCache cache = new_cache(); + _cleanup_(put_args_unrefp) PutArgs put_args = mk_put_args(); + + cache.cache_max = 0; + + put_args.key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "www.example.com"); + ASSERT_NOT_NULL(put_args.key); + put_args.rcode = DNS_RCODE_SUCCESS; + answer_add_a(&put_args, put_args.key, 0xc0a8017f, 3600, DNS_ANSWER_CACHEABLE); + ASSERT_OK(cache_put(&cache, &put_args)); + + ASSERT_TRUE(dns_cache_is_empty(&cache)); +} + /* ================================================================ * dns_cache_lookup() * ================================================================ */ 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..28a510dea6467 100644 --- a/src/resolve/test-dns-packet-extract.c +++ b/src/resolve/test-dns-packet-extract.c @@ -400,7 +400,7 @@ TEST(packet_validate_query_too_many_questions) { ASSERT_ERROR(dns_packet_validate_query(packet), EBADMSG); } -TEST(packet_validate_query_with_anwser) { +TEST(packet_validate_query_with_answer) { _cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL; ASSERT_OK(dns_packet_new(&packet, DNS_PROTOCOL_DNS, 0, DNS_PACKET_SIZE_MAX)); @@ -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-dnssec.c b/src/resolve/test-dnssec.c index eaa4ef9959850..f6ea6214f42b8 100644 --- a/src/resolve/test-dnssec.c +++ b/src/resolve/test-dnssec.c @@ -521,6 +521,59 @@ TEST(dnssec_verify_rrset) { assert_se(result == DNSSEC_VALIDATED); } +TEST(dnssec_verify_rrset_invalid_rsa_dnskey) { + static const uint8_t signature_blob[] = { 0x00 }; + static const uint8_t dnskey_blob[] = { 0x00 }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnssecResult result; + + ASSERT_NOT_NULL(a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "example.com.")); + + a->a.in_addr.s_addr = inet_addr("192.0.2.1"); + + ASSERT_NOT_NULL(dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "example.com.")); + + dnskey->dnskey.flags = DNSKEY_FLAG_ZONE_KEY; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; + dnskey->dnskey.key_size = sizeof(dnskey_blob); + ASSERT_NOT_NULL(dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob))); + + ASSERT_NOT_NULL(rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "example.com.")); + + rrsig->rrsig.type_covered = DNS_TYPE_A; + rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; + rrsig->rrsig.labels = 2; + rrsig->rrsig.original_ttl = 3600; + rrsig->rrsig.expiration = 2000000000; + rrsig->rrsig.inception = 1000000000; + rrsig->rrsig.key_tag = dnssec_keytag(dnskey, false); + ASSERT_NOT_NULL(rrsig->rrsig.signer = strdup("example.com.")); + rrsig->rrsig.signature_size = sizeof(signature_blob); + ASSERT_NOT_NULL(rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size)); + + ASSERT_GT(dnssec_key_match_rrsig(a->key, rrsig), 0); + ASSERT_GT(dnssec_rrsig_match_dnskey(rrsig, dnskey, false), 0); + + ASSERT_NOT_NULL(answer = dns_answer_new(1)); + ASSERT_OK(dns_answer_add(answer, a, 0, DNS_ANSWER_AUTHENTICATED, NULL)); + + ASSERT_ERROR(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, + rrsig->rrsig.inception * USEC_PER_SEC, &result), + EINVAL); + + dnskey->dnskey.key = mfree(dnskey->dnskey.key); + dnskey->dnskey.key_size = 0; + rrsig->rrsig.key_tag = dnssec_keytag(dnskey, false); + + ASSERT_GT(dnssec_rrsig_match_dnskey(rrsig, dnskey, false), 0); + ASSERT_ERROR(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, + rrsig->rrsig.inception * USEC_PER_SEC, &result), + EINVAL); +} + TEST(dnssec_verify_rrset2) { static const uint8_t signature_blob[] = { 0x48, 0x45, 0xc8, 0x8b, 0xc0, 0x14, 0x92, 0xf5, 0x15, 0xc6, 0x84, 0x9d, 0x2f, 0xe3, 0x32, 0x11, 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/rfkill/rfkill.c b/src/rfkill/rfkill.c index f5b67e5f01c75..36ae1d8d23483 100644 --- a/src/rfkill/rfkill.c +++ b/src/rfkill/rfkill.c @@ -10,6 +10,7 @@ #include "sd-device.h" #include "alloc-util.h" +#include "device-private.h" #include "device-util.h" #include "errno-util.h" #include "escape.h" @@ -82,7 +83,7 @@ static int find_device( return log_full_errno(ERRNO_IS_DEVICE_ABSENT(r) ? LOG_DEBUG : LOG_ERR, r, "Failed to open device '%s': %m", sysname); - r = sd_device_get_sysattr_value(device, "name", &name); + r = device_get_sysattr_safe_string(device, "name", &name); if (r < 0) return log_device_debug_errno(device, r, "Device has no name, ignoring: %m"); 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..2e37a0a2f63f3 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -1,14 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include +#include #include #include #include +#include "sd-bus-protocol.h" #include "sd-bus.h" #include "sd-daemon.h" #include "sd-event.h" @@ -22,6 +23,7 @@ #include "bus-locator.h" #include "bus-map-properties.h" #include "bus-message-util.h" +#include "bus-polkit.h" #include "bus-unit-util.h" #include "bus-util.h" #include "bus-wait-for-jobs.h" @@ -40,9 +42,11 @@ #include "format-table.h" #include "format-util.h" #include "fs-util.h" +#include "help-util.h" #include "hostname-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "osc-context.h" #include "pager.h" #include "parse-argument.h" @@ -71,6 +75,10 @@ static bool arg_scope = false; static bool arg_remain_after_exit = false; static bool arg_no_block = false; static bool arg_wait = false; +static bool arg_default_command = false; +static bool arg_remove_timestamp = false; +static bool arg_reset_timestamp = false; +static bool arg_validate = false; static const char *arg_unit = NULL; static char *arg_description = NULL; static char *arg_slice = NULL; @@ -105,6 +113,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; @@ -138,127 +147,65 @@ STATIC_DESTRUCTOR_REGISTER(arg_shell_prompt_prefix, freep); STATIC_DESTRUCTOR_REGISTER(arg_area, freep); static int help(void) { - _cleanup_free_ char *link = NULL; int r; pager_open(arg_pager_flags); - r = terminal_urlify_man("systemd-run", "1", &link); - if (r < 0) - return log_oom(); + static const char* const groups[] = { + NULL, + "Path options", + "Socket options", + "Timer options", + }; + + Table *tables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(tables, table_unref_array_clear); + + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + r = option_parser_get_help_table_full("systemd-run", groups[i], &tables[i]); + if (r < 0) + return r; + } + + (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], tables[3]); + + help_cmdline("[OPTIONS...] COMMAND [ARGUMENTS...]"); + help_abstract("Run the specified command in a transient scope or service."); + + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + help_section(groups[i] ?: "Options"); - printf("%1$s [OPTIONS...] COMMAND [ARGUMENTS...]\n" - "\n%5$sRun the specified command in a transient scope or service.%6$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-ask-password Do not prompt for password\n" - " --user Run as user unit\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --scope Run this as scope rather than service\n" - " -u --unit=UNIT Run under the specified unit name\n" - " -p --property=NAME=VALUE Set service or scope unit property\n" - " --description=TEXT Description for unit\n" - " --slice=SLICE Run in the specified slice\n" - " --slice-inherit Inherit the slice from the caller\n" - " --expand-environment=BOOL Control expansion of environment variables\n" - " --no-block Do not wait until operation finished\n" - " -r --remain-after-exit Leave service around until explicitly stopped\n" - " --wait Wait until service stopped again\n" - " --send-sighup Send SIGHUP when terminating\n" - " --service-type=TYPE Service type\n" - " --uid=USER Run as system user\n" - " --gid=GROUP Run as system group\n" - " --nice=NICE Nice level\n" - " --working-directory=PATH Set working directory\n" - " -d --same-dir Inherit working directory from caller\n" - " --root-directory=PATH Set root directory\n" - " -R --same-root-dir Inherit root directory from caller\n" - " -E --setenv=NAME[=VALUE] Set environment variable\n" - " -t --pty Run service on pseudo TTY as STDIN/STDOUT/\n" - " STDERR\n" - " -T --pty-late Just like --pty, but leave TTY access to\n" - " agents until unit is started up\n" - " -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" - " --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" - " --job-mode=MODE Specify how to deal with already queued jobs,\n" - " when queueing a new job\n" - " --ignore-failure Ignore the exit status of the invoked process\n" - " --background=COLOR Set ANSI color for background\n" - " --no-pager Do not pipe output into a pager\n" - "\n%3$sPath options:%4$s\n" - " --path-property=NAME=VALUE Set path unit property\n" - "\n%3$sSocket options:%4$s\n" - " --socket-property=NAME=VALUE Set socket unit property\n" - "\n%3$sTimer options:%4$s\n" - " --on-active=SECONDS Run after SECONDS delay\n" - " --on-boot=SECONDS Run SECONDS after machine was booted up\n" - " --on-startup=SECONDS Run SECONDS after systemd activation\n" - " --on-unit-active=SECONDS Run SECONDS after the last activation\n" - " --on-unit-inactive=SECONDS Run SECONDS after the last deactivation\n" - " --on-calendar=SPEC Realtime timer\n" - " --on-timezone-change Run when the timezone changes\n" - " --on-clock-change Run when the realtime clock jumps\n" - " --timer-property=NAME=VALUE Set timer unit property\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), ansi_normal(), - ansi_highlight(), ansi_normal()); + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + help_man_page_reference("systemd-run", "1"); return 0; } static int help_sudo_mode(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *opts_table = NULL; int r; - r = terminal_urlify_man("run0", "1", &link); - if (r < 0) - return log_oom(); - /* NB: Let's not go overboard with short options: we try to keep a modicum of compatibility with * sudo's short switches, hence please do not introduce new short switches unless they have a roughly * equivalent purpose on sudo. Use long options for everything private to run0. */ - printf("%s [OPTIONS...] COMMAND [ARGUMENTS...]\n" - "\n%sElevate privileges interactively.%s\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " --no-ask-password Do not prompt for password\n" - " --machine=CONTAINER Operate on local container\n" - " --unit=UNIT Run under the specified unit name\n" - " --property=NAME=VALUE Set service or scope unit property\n" - " --description=TEXT Description for unit\n" - " --slice=SLICE Run in the specified slice\n" - " --slice-inherit Inherit the slice\n" - " -u --user=USER Run as system user\n" - " -g --group=GROUP Run as system group\n" - " --nice=NICE Nice level\n" - " -D --chdir=PATH Set working directory\n" - " --via-shell Invoke command via target user's login shell\n" - " -i Shortcut for --via-shell --chdir='~'\n" - " --setenv=NAME[=VALUE] Set environment variable\n" - " --background=COLOR Set ANSI color for background\n" - " --pty Request allocation of a pseudo TTY for stdio\n" - " --pty-late Just like --pty, but leave TTY access to agents\n" - " until unit is started up\n" - " --pipe Request direct pipe for stdio\n" - " --shell-prompt-prefix=PREFIX Set $SHELL_PROMPT_PREFIX\n" - " --lightweight=BOOLEAN Control whether to register a session with service manager\n" - " or without\n" - " --area=AREA Home area to log into\n" - " --empower Give privileges to selected or current user\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + r = option_parser_get_help_table_ns("run0", &opts_table); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] COMMAND [ARGUMENTS...]"); + help_abstract("Elevate privileges interactively."); + + help_section("Options"); + r = table_print_or_warn(opts_table); + if (r < 0) + return r; + + help_man_page_reference("run0", "1"); return 0; } @@ -303,289 +250,309 @@ static char** make_login_shell_cmdline(const char *shell) { } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_USER, - ARG_SYSTEM, - ARG_SCOPE, - ARG_DESCRIPTION, - ARG_SLICE, - ARG_SLICE_INHERIT, - ARG_EXPAND_ENVIRONMENT, - ARG_SEND_SIGHUP, - ARG_SERVICE_TYPE, - ARG_EXEC_USER, - ARG_EXEC_GROUP, - ARG_NICE, - ARG_ON_ACTIVE, - ARG_ON_BOOT, - ARG_ON_STARTUP, - ARG_ON_UNIT_ACTIVE, - ARG_ON_UNIT_INACTIVE, - ARG_ON_CALENDAR, - ARG_ON_TIMEZONE_CHANGE, - ARG_ON_CLOCK_CHANGE, - ARG_TIMER_PROPERTY, - ARG_PATH_PROPERTY, - ARG_SOCKET_PROPERTY, - ARG_NO_BLOCK, - ARG_NO_ASK_PASSWORD, - ARG_WAIT, - ARG_WORKING_DIRECTORY, - ARG_ROOT_DIRECTORY, - ARG_JOB_MODE, - ARG_IGNORE_FAILURE, - ARG_BACKGROUND, - ARG_NO_PAGER, - ARG_JSON, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "capsule", required_argument, NULL, 'C' }, - { "scope", no_argument, NULL, ARG_SCOPE }, - { "unit", required_argument, NULL, 'u' }, - { "description", required_argument, NULL, ARG_DESCRIPTION }, - { "slice", required_argument, NULL, ARG_SLICE }, - { "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT }, - { "remain-after-exit", no_argument, NULL, 'r' }, - { "expand-environment", required_argument, NULL, ARG_EXPAND_ENVIRONMENT }, - { "send-sighup", no_argument, NULL, ARG_SEND_SIGHUP }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "service-type", required_argument, NULL, ARG_SERVICE_TYPE }, - { "wait", no_argument, NULL, ARG_WAIT }, - { "uid", required_argument, NULL, ARG_EXEC_USER }, - { "gid", required_argument, NULL, ARG_EXEC_GROUP }, - { "nice", required_argument, NULL, ARG_NICE }, - { "setenv", required_argument, NULL, 'E' }, - { "property", required_argument, NULL, 'p' }, - { "tty", no_argument, NULL, 't' }, /* deprecated alias */ - { "pty", no_argument, NULL, 't' }, - { "pty-late", no_argument, NULL, 'T' }, - { "pipe", no_argument, NULL, 'P' }, - { "quiet", no_argument, NULL, 'q' }, - { "verbose", no_argument, NULL, 'v' }, - { "on-active", required_argument, NULL, ARG_ON_ACTIVE }, - { "on-boot", required_argument, NULL, ARG_ON_BOOT }, - { "on-startup", required_argument, NULL, ARG_ON_STARTUP }, - { "on-unit-active", required_argument, NULL, ARG_ON_UNIT_ACTIVE }, - { "on-unit-inactive", required_argument, NULL, ARG_ON_UNIT_INACTIVE }, - { "on-calendar", required_argument, NULL, ARG_ON_CALENDAR }, - { "on-timezone-change", no_argument, NULL, ARG_ON_TIMEZONE_CHANGE }, - { "on-clock-change", no_argument, NULL, ARG_ON_CLOCK_CHANGE }, - { "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY }, - { "path-property", required_argument, NULL, ARG_PATH_PROPERTY }, - { "socket-property", required_argument, NULL, ARG_SOCKET_PROPERTY }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "collect", no_argument, NULL, 'G' }, - { "working-directory", required_argument, NULL, ARG_WORKING_DIRECTORY }, - { "same-dir", no_argument, NULL, 'd' }, - { "root-directory", required_argument, NULL, ARG_ROOT_DIRECTORY }, - { "same-root-dir", no_argument, NULL, 'R' }, - { "shell", no_argument, NULL, 'S' }, - { "job-mode", required_argument, NULL, ARG_JOB_MODE }, - { "ignore-failure", no_argument, NULL, ARG_IGNORE_FAILURE }, - { "background", required_argument, NULL, ARG_BACKGROUND }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "json", required_argument, NULL, ARG_JSON }, - {}, - }; - bool with_trigger = false, same_dir = false; - int r, c; + 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, "+hrC:H:M:E:p:tTPqvGdSu:", options, NULL)) >= 0) + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "systemd-run" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_NAMESPACE("systemd-run"): {} + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Run as user unit"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Talk to the service manager (implied default)"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case 'C': - r = capsule_name_is_valid(optarg); + OPTION_COMMON_HOST: + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = opts.arg; + break; + + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) - return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); + return r; + break; + + OPTION('C', "capsule", "NAME", "Operate on specified capsule"): + r = capsule_name_is_valid(opts.arg); + if (r < 0) + return log_error_errno(r, "Unable to validate capsule name '%s': %m", opts.arg); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", opts.arg); - arg_host = optarg; + arg_host = opts.arg; arg_transport = BUS_TRANSPORT_CAPSULE; arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SCOPE: + OPTION_LONG("scope", NULL, "Run this as scope rather than service"): arg_scope = true; break; - case 'u': - arg_unit = optarg; + OPTION('u', "unit", "UNIT", "Run under the specified unit name"): + arg_unit = opts.arg; + break; + + OPTION('p', "property", "NAME=VALUE", "Set service or scope unit property"): + if (strv_extend(&arg_property, opts.arg) < 0) + return log_oom(); break; - case ARG_DESCRIPTION: - r = free_and_strdup_warn(&arg_description, optarg); + OPTION_LONG("description", "TEXT", "Description for unit"): + r = free_and_strdup_warn(&arg_description, opts.arg); if (r < 0) return r; break; - case ARG_SLICE: - r = free_and_strdup_warn(&arg_slice, optarg); + OPTION_LONG("slice", "SLICE", "Run in the specified slice"): + r = free_and_strdup_warn(&arg_slice, opts.arg); if (r < 0) return r; break; - case ARG_SLICE_INHERIT: + OPTION_LONG("slice-inherit", NULL, "Inherit the slice from the caller"): arg_slice_inherit = true; break; - case ARG_EXPAND_ENVIRONMENT: - r = parse_boolean_argument("--expand-environment=", optarg, &arg_expand_environment); + OPTION_LONG("expand-environment", "BOOL", + "Control expansion of environment variables"): + r = parse_boolean_argument("--expand-environment=", opts.arg, &arg_expand_environment); if (r < 0) return r; break; - case ARG_SEND_SIGHUP: - arg_send_sighup = true; + OPTION_LONG("no-block", NULL, "Do not wait until operation finished"): + arg_no_block = true; break; - case 'r': + OPTION('r', "remain-after-exit", NULL, + "Leave service around until explicitly stopped"): arg_remain_after_exit = true; break; - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + OPTION_LONG("wait", NULL, "Wait until service stopped again"): + arg_wait = true; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); - if (r < 0) - return r; + OPTION_LONG("send-sighup", NULL, "Send SIGHUP when terminating"): + arg_send_sighup = true; break; - case ARG_SERVICE_TYPE: - arg_service_type = optarg; + OPTION_LONG("service-type", "TYPE", "Service type"): + arg_service_type = opts.arg; break; - case ARG_EXEC_USER: - r = free_and_strdup_warn(&arg_exec_user, optarg); + OPTION_LONG("uid", "USER", "Run as system user"): + r = free_and_strdup_warn(&arg_exec_user, opts.arg); if (r < 0) return r; break; - case ARG_EXEC_GROUP: - arg_exec_group = optarg; + OPTION_LONG("gid", "GROUP", "Run as system group"): + arg_exec_group = opts.arg; break; - case ARG_NICE: - r = parse_nice(optarg, &arg_nice); + OPTION_LONG("nice", "NICE", "Nice level"): + r = parse_nice(opts.arg, &arg_nice); if (r < 0) - return log_error_errno(r, "Failed to parse nice value: %s", optarg); + return log_error_errno(r, "Failed to parse nice value: %s", opts.arg); arg_nice_set = true; break; - case 'E': - r = strv_env_replace_strdup_passthrough(&arg_environment, optarg); + OPTION_LONG("working-directory", "PATH", "Set working directory"): + r = parse_path_argument(opts.arg, true, &arg_working_directory); if (r < 0) - return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); + return r; + same_dir = false; break; - case 'p': - if (strv_extend(&arg_property, optarg) < 0) - return log_oom(); + OPTION('d', "same-dir", NULL, "Inherit working directory from caller"): { + _cleanup_free_ char *p = NULL; + r = safe_getcwd(&p); + if (r < 0) + return log_error_errno(r, "Failed to get current working directory: %m"); + + if (empty_or_root(p)) + arg_working_directory = mfree(arg_working_directory); + else + free_and_replace(arg_working_directory, p); + + same_dir = true; + break; + } + + OPTION_LONG("root-directory", "PATH", "Set root directory"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_root_directory); + if (r < 0) + return r; break; - case 'T': /* --pty-late */ - case 't': /* --pty */ + OPTION('R', "same-root-dir", NULL, "Inherit root directory from caller"): + r = free_and_strdup_warn(&arg_root_directory, "/"); + if (r < 0) + return r; + break; + + OPTION('E', "setenv", "NAME[=VALUE]", "Set environment variable"): + r = strv_env_replace_strdup_passthrough(&arg_environment, opts.arg); + if (r < 0) + return log_error_errno(r, "Cannot assign environment variable %s: %m", opts.arg); + break; + + OPTION_LONG("tty", NULL, NULL): {} /* deprecated alias for --pty */ + OPTION('t', "pty", NULL, + "Run service on pseudo TTY as STDIN/STDOUT/STDERR"): {} + OPTION('T', "pty-late", NULL, + "Just like --pty, but leave TTY access to agents until unit is started up"): arg_stdio |= ARG_STDIO_PTY; - arg_pty_late = c == 'T'; + arg_pty_late = opts.opt->short_code == 'T'; break; - case 'P': /* --pipe */ + OPTION('P', "pipe", NULL, "Pass STDIN/STDOUT/STDERR directly to service"): arg_stdio |= ARG_STDIO_DIRECT; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress information messages during runtime"): arg_quiet = true; break; - case 'v': + OPTION('v', "verbose", NULL, "Show unit logs while executing operation"): arg_verbose = true; break; - case ARG_ON_ACTIVE: - r = add_timer_property("OnActiveSec", optarg); + OPTION_LONG("output", "MODE", + "Controls formatting of verbose logs, see journalctl for valid values"): + if (streq(opts.arg, "help")) + return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); + + arg_output = output_mode_from_string(opts.arg); + if (arg_output < 0) + return log_error_errno(arg_output, "Unknown output format '%s'.", opts.arg); + break; + + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); + if (r <= 0) + return r; + break; + + OPTION('G', "collect", NULL, "Unload unit after it ran, even when failed"): + arg_aggressive_gc = true; + break; + + OPTION('S', "shell", NULL, "Invoke a $SHELL interactively"): + arg_shell = true; + break; + + OPTION_LONG("job-mode", "MODE", + "Specify how to deal with already queued jobs, when queueing a new job"): + if (streq(opts.arg, "help")) + return DUMP_STRING_TABLE(job_mode, JobMode, _JOB_MODE_MAX); + + r = job_mode_from_string(opts.arg); + if (r < 0) + return log_error_errno(r, "Invalid job mode: %s", opts.arg); + + arg_job_mode = r; + break; + + OPTION_LONG("ignore-failure", NULL, "Ignore the exit status of the invoked process"): + arg_ignore_failure = true; + break; + + OPTION_LONG("background", "COLOR", "Set ANSI color for background"): + r = parse_background_argument(opts.arg, &arg_background); + if (r < 0) + return r; + break; + + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + OPTION_GROUP("Path options"): {} + + OPTION_LONG("path-property", "NAME=VALUE", "Set path unit property"): + if (strv_extend(&arg_path_property, opts.arg) < 0) + return log_oom(); + break; + + OPTION_GROUP("Socket options"): {} + + OPTION_LONG("socket-property", "NAME=VALUE", "Set socket unit property"): + if (strv_extend(&arg_socket_property, opts.arg) < 0) + return log_oom(); + break; + + OPTION_GROUP("Timer options"): {} + + OPTION_LONG("on-active", "SECONDS", "Run after SECONDS delay"): + r = add_timer_property("OnActiveSec", opts.arg); if (r < 0) return r; arg_with_timer = true; break; - case ARG_ON_BOOT: - r = add_timer_property("OnBootSec", optarg); + OPTION_LONG("on-boot", "SECONDS", "Run SECONDS after machine was booted up"): + r = add_timer_property("OnBootSec", opts.arg); if (r < 0) return r; arg_with_timer = true; break; - case ARG_ON_STARTUP: - r = add_timer_property("OnStartupSec", optarg); + OPTION_LONG("on-startup", "SECONDS", "Run SECONDS after systemd activation"): + r = add_timer_property("OnStartupSec", opts.arg); if (r < 0) return r; arg_with_timer = true; break; - case ARG_ON_UNIT_ACTIVE: - r = add_timer_property("OnUnitActiveSec", optarg); + OPTION_LONG("on-unit-active", "SECONDS", "Run SECONDS after the last activation"): + r = add_timer_property("OnUnitActiveSec", opts.arg); if (r < 0) return r; arg_with_timer = true; break; - case ARG_ON_UNIT_INACTIVE: - r = add_timer_property("OnUnitInactiveSec", optarg); + OPTION_LONG("on-unit-inactive", "SECONDS", + "Run SECONDS after the last deactivation"): + r = add_timer_property("OnUnitInactiveSec", opts.arg); if (r < 0) return r; arg_with_timer = true; break; - case ARG_ON_CALENDAR: { + OPTION_LONG("on-calendar", "SPEC", "Realtime timer"): { _cleanup_(calendar_spec_freep) CalendarSpec *cs = NULL; - r = calendar_spec_from_string(optarg, &cs); + r = calendar_spec_from_string(opts.arg, &cs); if (r < 0) return log_error_errno(r, "Failed to parse calendar event specification: %m"); @@ -600,7 +567,7 @@ static int parse_argv(int argc, char *argv[]) { else if (r < 0) return log_error_errno(r, "Failed to calculate next time calendar expression elapses: %m"); - r = add_timer_property("OnCalendar", optarg); + r = add_timer_property("OnCalendar", opts.arg); if (r < 0) return r; @@ -608,7 +575,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_ON_TIMEZONE_CHANGE: + OPTION_LONG("on-timezone-change", NULL, "Run when the timezone changes"): r = add_timer_property("OnTimezoneChange", "yes"); if (r < 0) return r; @@ -616,7 +583,7 @@ static int parse_argv(int argc, char *argv[]) { arg_with_timer = true; break; - case ARG_ON_CLOCK_CHANGE: + OPTION_LONG("on-clock-change", NULL, "Run when the realtime clock jumps"): r = add_timer_property("OnClockChange", "yes"); if (r < 0) return r; @@ -624,13 +591,12 @@ static int parse_argv(int argc, char *argv[]) { arg_with_timer = true; break; - case ARG_TIMER_PROPERTY: - - if (strv_extend(&arg_timer_property, optarg) < 0) + OPTION_LONG("timer-property", "NAME=VALUE", "Set timer unit property"): + if (strv_extend(&arg_timer_property, opts.arg) < 0) return log_oom(); arg_with_timer = arg_with_timer || - STARTSWITH_SET(optarg, + STARTSWITH_SET(opts.arg, "OnActiveSec=", "OnBootSec=", "OnStartupSec=", @@ -638,111 +604,6 @@ static int parse_argv(int argc, char *argv[]) { "OnUnitInactiveSec=", "OnCalendar="); break; - - case ARG_PATH_PROPERTY: - - if (strv_extend(&arg_path_property, optarg) < 0) - return log_oom(); - - break; - - case ARG_SOCKET_PROPERTY: - - if (strv_extend(&arg_socket_property, optarg) < 0) - return log_oom(); - - break; - - case ARG_NO_BLOCK: - arg_no_block = true; - break; - - case ARG_WAIT: - arg_wait = true; - break; - - case ARG_WORKING_DIRECTORY: - r = parse_path_argument(optarg, true, &arg_working_directory); - if (r < 0) - return r; - - same_dir = false; - break; - - case 'd': { - _cleanup_free_ char *p = NULL; - - r = safe_getcwd(&p); - if (r < 0) - return log_error_errno(r, "Failed to get current working directory: %m"); - - if (empty_or_root(p)) - arg_working_directory = mfree(arg_working_directory); - else - free_and_replace(arg_working_directory, p); - - same_dir = true; - break; - } - - case ARG_ROOT_DIRECTORY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root_directory); - if (r < 0) - return r; - - break; - - case 'R': - r = free_and_strdup_warn(&arg_root_directory, "/"); - if (r < 0) - return r; - - break; - - case 'G': - arg_aggressive_gc = true; - break; - - case 'S': - arg_shell = true; - break; - - case ARG_JOB_MODE: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(job_mode, JobMode, _JOB_MODE_MAX); - - r = job_mode_from_string(optarg); - if (r < 0) - return log_error_errno(r, "Invalid job mode: %s", optarg); - - arg_job_mode = r; - break; - - case ARG_IGNORE_FAILURE: - arg_ignore_failure = true; - break; - - case ARG_BACKGROUND: - r = parse_background_argument(optarg, &arg_background); - if (r < 0) - return r; - break; - - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; - - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } /* If we are talking to the per-user instance PolicyKit isn't going to help */ @@ -791,13 +652,14 @@ static int parse_argv(int argc, char *argv[]) { if (arg_pty_late < 0) arg_pty_late = false; /* For systemd-run this defaults to false, for compat reasons */ - if (argc > optind) { + char **args = option_parser_get_args(&opts); + if (!strv_isempty(args)) { char **l; if (arg_shell) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "If --shell is used, no command line is expected."); - l = strv_copy(argv + optind); + l = strv_copy(args); if (!l) return log_oom(); @@ -914,217 +776,174 @@ static Glyph pty_window_glyph(void) { } static int parse_argv_sudo_mode(int argc, char *argv[]) { - - enum { - ARG_NO_ASK_PASSWORD = 0x100, - ARG_MACHINE, - ARG_UNIT, - ARG_PROPERTY, - ARG_DESCRIPTION, - ARG_SLICE, - ARG_SLICE_INHERIT, - ARG_NICE, - ARG_SETENV, - ARG_BACKGROUND, - ARG_PTY, - ARG_PTY_LATE, - ARG_PIPE, - ARG_SHELL_PROMPT_PREFIX, - ARG_LIGHTWEIGHT, - ARG_AREA, - ARG_VIA_SHELL, - ARG_EMPOWER, - ARG_SAME_ROOT_DIR, - }; + int r; /* If invoked as "run0" binary, let's expose a more sudo-like interface. We add various extensions * though (but limit the extension to long options). */ - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "machine", required_argument, NULL, ARG_MACHINE }, - { "unit", required_argument, NULL, ARG_UNIT }, - { "property", required_argument, NULL, ARG_PROPERTY }, - { "description", required_argument, NULL, ARG_DESCRIPTION }, - { "slice", required_argument, NULL, ARG_SLICE }, - { "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT }, - { "user", required_argument, NULL, 'u' }, - { "group", required_argument, NULL, 'g' }, - { "nice", required_argument, NULL, ARG_NICE }, - { "chdir", required_argument, NULL, 'D' }, - { "via-shell", no_argument, NULL, ARG_VIA_SHELL }, - { "login", no_argument, NULL, 'i' }, /* compat with sudo, --via-shell + --chdir='~' */ - { "setenv", required_argument, NULL, ARG_SETENV }, - { "background", required_argument, NULL, ARG_BACKGROUND }, - { "pty", no_argument, NULL, ARG_PTY }, - { "pty-late", no_argument, NULL, ARG_PTY_LATE }, - { "pipe", no_argument, NULL, ARG_PIPE }, - { "shell-prompt-prefix", required_argument, NULL, ARG_SHELL_PROMPT_PREFIX }, - { "lightweight", required_argument, NULL, ARG_LIGHTWEIGHT }, - { "area", required_argument, NULL, ARG_AREA }, - { "empower", no_argument, NULL, ARG_EMPOWER }, - { "same-root-dir", no_argument, NULL, ARG_SAME_ROOT_DIR }, - {}, - }; - - int r, c; - 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, "+hVu:g:D:i", options, NULL)) >= 0) + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "run0" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_NAMESPACE("run0"): {} + + OPTION_COMMON_HELP: return help_sudo_mode(); - case 'V': + OPTION('V', "version", NULL, "Show package version"): return version(); - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + OPTION('n', "non-interactive", NULL, "Do not prompt for password"): arg_ask_password = false; break; - case ARG_MACHINE: - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_LONG("machine", "CONTAINER", "Operate on local container"): + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case ARG_UNIT: - arg_unit = optarg; + OPTION_LONG("unit", "UNIT", "Run under the specified unit name"): + arg_unit = opts.arg; break; - case ARG_PROPERTY: - if (strv_extend(&arg_property, optarg) < 0) + OPTION_LONG("property", "NAME=VALUE", "Set service or scope unit property"): + if (strv_extend(&arg_property, opts.arg) < 0) return log_oom(); - break; - case ARG_DESCRIPTION: - r = free_and_strdup_warn(&arg_description, optarg); + OPTION_LONG("description", "TEXT", "Description for unit"): + r = free_and_strdup_warn(&arg_description, opts.arg); if (r < 0) return r; break; - case ARG_SLICE: - r = free_and_strdup_warn(&arg_slice, optarg); + OPTION_LONG("slice", "SLICE", "Run in the specified slice"): + r = free_and_strdup_warn(&arg_slice, opts.arg); if (r < 0) return r; break; - case ARG_SLICE_INHERIT: + OPTION_LONG("slice-inherit", NULL, "Inherit the slice"): arg_slice_inherit = true; break; - case 'u': - r = free_and_strdup_warn(&arg_exec_user, optarg); + OPTION('k', "reset-timestamp", NULL, "Revoke temporary authorization"): + arg_reset_timestamp = true; + break; + + OPTION('K', "remove-timestamp", NULL, "Revoke all temporary authorizations for this user session"): + arg_remove_timestamp = true; + break; + + OPTION('v', "validate", NULL, "Request temporary authorization from polkit"): + arg_validate = true; + break; + + OPTION('u', "user", "USER", "Run as system user"): + r = free_and_strdup_warn(&arg_exec_user, opts.arg); if (r < 0) return r; break; - case 'g': - arg_exec_group = optarg; + OPTION('g', "group", "GROUP", "Run as system group"): + arg_exec_group = opts.arg; break; - case ARG_NICE: - r = parse_nice(optarg, &arg_nice); + OPTION_LONG("nice", "NICE", "Nice level"): + r = parse_nice(opts.arg, &arg_nice); if (r < 0) - return log_error_errno(r, "Failed to parse nice value: %s", optarg); + return log_error_errno(r, "Failed to parse nice value: %s", opts.arg); arg_nice_set = true; break; - case 'D': - if (streq(optarg, "~")) - r = free_and_strdup_warn(&arg_working_directory, optarg); + OPTION('D', "chdir", "PATH", "Set working directory"): + if (streq(opts.arg, "~")) + r = free_and_strdup_warn(&arg_working_directory, opts.arg); else /* Root will be manually suppressed later. */ - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_working_directory); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_working_directory); if (r < 0) return r; + break; + OPTION_LONG("via-shell", NULL, "Invoke command via target user's login shell"): + arg_via_shell = true; break; - case ARG_SETENV: - r = strv_env_replace_strdup_passthrough(&arg_environment, optarg); + OPTION_LONG("login", NULL, NULL): {} /* hidden compat alias for -i */ + OPTION_SHORT('i', NULL, "Shortcut for --via-shell --chdir='~'"): + r = free_and_strdup_warn(&arg_working_directory, "~"); if (r < 0) - return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); + return r; + arg_via_shell = true; break; - case ARG_BACKGROUND: - r = parse_background_argument(optarg, &arg_background); + OPTION_LONG("setenv", "NAME[=VALUE]", "Set environment variable"): + r = strv_env_replace_strdup_passthrough(&arg_environment, opts.arg); if (r < 0) - return r; + return log_error_errno(r, "Cannot assign environment variable %s: %m", opts.arg); + break; + OPTION_LONG("background", "COLOR", "Set ANSI color for background"): + r = parse_background_argument(opts.arg, &arg_background); + if (r < 0) + return r; break; - case ARG_PTY: - case ARG_PTY_LATE: + OPTION_LONG("pty", NULL, "Request allocation of a pseudo TTY for stdio"): {} + OPTION_LONG("pty-late", NULL, + "Just like --pty, but leave TTY access to agents until unit is started up"): arg_stdio |= ARG_STDIO_PTY; - arg_pty_late = c == ARG_PTY_LATE; + arg_pty_late = streq(opts.opt->long_code, "pty-late"); break; - case ARG_PIPE: + OPTION_LONG("pipe", NULL, "Request direct pipe for stdio"): arg_stdio |= ARG_STDIO_DIRECT; break; - case ARG_SHELL_PROMPT_PREFIX: - r = free_and_strdup_warn(&arg_shell_prompt_prefix, optarg); + OPTION_LONG("shell-prompt-prefix", "PREFIX", "Set $SHELL_PROMPT_PREFIX"): + r = free_and_strdup_warn(&arg_shell_prompt_prefix, opts.arg); if (r < 0) return r; break; - case ARG_LIGHTWEIGHT: - r = parse_tristate_argument_with_auto("--lightweight=", optarg, &arg_lightweight); + OPTION_LONG("lightweight", "BOOLEAN", + "Control whether to register a session with service manager or without"): + r = parse_tristate_argument_with_auto("--lightweight=", opts.arg, &arg_lightweight); if (r < 0) return r; break; - case ARG_AREA: + OPTION_LONG("area", "AREA", "Home area to log into"): /* We allow an empty --area= specification to allow logging into the primary home directory */ - if (!isempty(optarg) && !filename_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid area name, refusing: %s", optarg); + if (!isempty(opts.arg) && !filename_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid area name, refusing: %s", opts.arg); - r = free_and_strdup_warn(&arg_area, optarg); + r = free_and_strdup_warn(&arg_area, opts.arg); if (r < 0) return r; - break; - case 'i': - r = free_and_strdup_warn(&arg_working_directory, "~"); - if (r < 0) - return r; - - _fallthrough_; - case ARG_VIA_SHELL: - arg_via_shell = true; - break; - - case ARG_EMPOWER: + OPTION_LONG("empower", NULL, "Give privileges to selected or current user"): arg_empower = true; break; - case ARG_SAME_ROOT_DIR: + OPTION_LONG("same-root-dir", NULL, NULL): /* hidden */ r = free_and_strdup_warn(&arg_root_directory, "/"); if (r < 0) return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_working_directory) { @@ -1166,19 +985,32 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { arg_stdio = isatty_safe(STDIN_FILENO) && isatty_safe(STDOUT_FILENO) && isatty_safe(STDERR_FILENO) ? ARG_STDIO_PTY : ARG_STDIO_DIRECT; log_debug("Using %s stdio mode.", arg_stdio == ARG_STDIO_PTY ? "pty" : "direct"); if (arg_pty_late < 0) - arg_pty_late = arg_ask_password; /* for run0 this defaults to on, except if --no-ask-pasword is used */ + arg_pty_late = arg_ask_password; /* for run0 this defaults to on, except if --no-ask-password is used */ arg_expand_environment = false; arg_send_sighup = true; _cleanup_strv_free_ char **l = NULL; - if (argc > optind) { - l = strv_copy(argv + optind); + char **args = option_parser_get_args(&opts); + bool custom_slice = arg_slice_inherit || arg_slice; + if (custom_slice && arg_lightweight >= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--lightweight= may not be combined with a custom slice"); + if (custom_slice && !isempty(arg_area)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--area= may not be combined with a custom slice"); + + if (!strv_isempty(args)) { + if (arg_validate) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Option '--validate' cannot be used with a command"); + l = strv_copy(args); if (!l) return log_oom(); } else if (!arg_via_shell) { const char *e; + arg_default_command = true; e = strv_env_get(arg_environment, "SHELL"); if (e) { arg_exec_path = strdup(e); @@ -1284,9 +1116,12 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { } if (!strv_env_get(arg_environment, "XDG_SESSION_CLASS")) { + const char *class = NULL; + if (custom_slice) + class = "none"; /* If logging into an area, imply lightweight mode */ - if (arg_lightweight < 0 && !isempty(arg_area)) + else if (arg_lightweight < 0 && !isempty(arg_area)) arg_lightweight = true; /* When using run0 to acquire privileges temporarily, let's not pull in session manager by @@ -1296,14 +1131,14 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { * this for root or --empower though, under the assumption that if a regular user temporarily * transitions into another regular user it's a better default that the full user environment is * uniformly available. */ - if (arg_lightweight < 0 && (become_root() || arg_empower)) + else if (arg_lightweight < 0 && (become_root() || arg_empower)) arg_lightweight = true; - if (arg_lightweight >= 0) { - const char *class = - arg_lightweight ? (arg_stdio == ARG_STDIO_PTY ? (become_root() ? "user-early-light" : "user-light") : "background-light") : + if (arg_lightweight >= 0) + class = arg_lightweight ? (arg_stdio == ARG_STDIO_PTY ? (become_root() ? "user-early-light" : "user-light") : "background-light") : (arg_stdio == ARG_STDIO_PTY ? (become_root() ? "user-early" : "user") : "background"); + if (class) { log_debug("Setting XDG_SESSION_CLASS to '%s'.", class); r = strv_env_assign(&arg_environment, "XDG_SESSION_CLASS", class); @@ -2437,7 +2272,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); @@ -2540,7 +2375,7 @@ static int start_transient_service(sd_bus *bus) { r = unit_name_mangle_with_suffix( arg_unit, "as unit", - arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, + (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN) | UNIT_NAME_MANGLE_STRICT, ".service", &c.unit); if (r < 0) @@ -2569,7 +2404,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) @@ -2684,7 +2519,7 @@ static int start_transient_scope(sd_bus *bus) { if (arg_unit) { r = unit_name_mangle_with_suffix(arg_unit, "as unit", - arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, + (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN) | UNIT_NAME_MANGLE_STRICT, ".scope", &scope); if (r < 0) return log_error_errno(r, "Failed to mangle scope name: %m"); @@ -2728,7 +2563,7 @@ static int start_transient_scope(sd_bus *bus) { if (r < 0) return bus_log_create_error(r); - r = sd_bus_call(bus, m, 0, &error, &reply); + r = sd_bus_call(bus, m, /* usec = */ 0, &error, &reply); if (r < 0) { if (sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY, SD_BUS_ERROR_PROPERTY_READ_ONLY) && allow_pidfd) { log_debug("Retrying with classic PIDs."); @@ -2774,7 +2609,7 @@ static int start_transient_scope(sd_bus *bus) { if (arg_exec_group) { gid_t gid; - r = get_group_creds(&arg_exec_group, &gid, 0); + r = get_group_creds(arg_exec_group, /* flags= */ 0, /* ret_name= */ NULL, &gid); if (r < 0) return log_error_errno(r, "Failed to resolve group '%s': %s", arg_exec_group, STRERROR_GROUP(r)); @@ -2784,19 +2619,18 @@ static int start_transient_scope(sd_bus *bus) { } if (arg_exec_user) { - const char *un = arg_exec_user, *home, *shell; + _cleanup_free_ char *user = NULL, *home = NULL, *shell = NULL; uid_t uid; gid_t gid; - r = get_user_creds(&un, &uid, &gid, &home, &shell, - USER_CREDS_CLEAN|USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_PREFER_NSS); + r = get_user_creds(arg_exec_user, + USER_CREDS_CLEAN|USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_PREFER_NSS, + &user, &uid, &gid, &home, &shell); if (r < 0) return log_error_errno(r, "Failed to resolve user '%s': %s", arg_exec_user, STRERROR_USER(r)); - r = free_and_strdup_warn(&arg_exec_user, un); - if (r < 0) - return r; + free_and_replace(arg_exec_user, user); if (home) { r = strv_extendf(&user_env, "HOME=%s", home); @@ -2818,10 +2652,9 @@ static int start_transient_scope(sd_bus *bus) { if (r < 0) return log_oom(); - if (!arg_exec_group) { - if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); - } + if (!arg_exec_group && + setresgid(gid, gid, gid) < 0) + return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); if (setresuid(uid, uid, uid) < 0) return log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", uid); @@ -2987,13 +2820,13 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) { default: r = unit_name_mangle_with_suffix(arg_unit, "as unit", - arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, + (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN) | UNIT_NAME_MANGLE_STRICT, ".service", &service); if (r < 0) return log_error_errno(r, "Failed to mangle unit name: %m"); r = unit_name_mangle_with_suffix(arg_unit, "as trigger", - arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, + (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN) | UNIT_NAME_MANGLE_STRICT, suffix, &trigger); if (r < 0) return log_error_errno(r, "Failed to mangle unit name: %m"); @@ -3060,6 +2893,299 @@ static bool shall_make_executable_absolute(void) { return true; } +static int polkit_check_authorization(sd_bus *bus, PolkitFlags flags, char **ret_tmpauthz_id) { + _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; + pid_t pid; + _cleanup_close_ int pidfd = -EBADF; + _cleanup_free_ char *tmpauthz_id = NULL; + int is_authorized, is_challenge; + int r; + + assert(bus); + + r = sd_bus_message_new_method_call(bus, &m, + "org.freedesktop.PolicyKit1", + "/org/freedesktop/PolicyKit1/Authority", + "org.freedesktop.PolicyKit1.Authority", + "CheckAuthorization"); + if (r < 0) + return bus_log_create_error(r); + + pid = getpid_cached(); + + /* Polkit requires pidfd to honor temporary authorizations */ + pidfd = pidfd_open(pid, 0); + if (pidfd < 0) + return log_debug_errno(errno, "pidfd_open failed: %m"); + + r = sd_bus_message_append(m, "(sa{sv})s", "unix-process", 4, "pid", "u", (uint32_t) pid, + "start-time", "t", UINT64_C(0), "uid", "i", (uint32_t) geteuid(), "pidfd", "h", pidfd, + "org.freedesktop.systemd1.manage-units"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "a{ss}us", /* details = */ 0, (uint32_t) flags, /* cancel_id = */ NULL); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, /* usec = */ 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to check authorization: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, 'r', "bba{ss}"); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "bb", &is_authorized, &is_challenge); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, 'a', "{ss}"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + const char *key, *value; + r = sd_bus_message_enter_container(reply, 'e', "ss"); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + r = sd_bus_message_read(reply, "ss", &key, &value); + if (r < 0) + return bus_log_parse_error(r); + + if (streq(key, "polkit.temporary_authorization_id")) { + r = free_and_strdup(&tmpauthz_id, value); + if (r < 0) + return log_oom(); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + + r = sd_bus_message_exit_container(reply); /* a{ss} */ + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); /* (bba{ss}) */ + if (r < 0) + return bus_log_parse_error(r); + + if (ret_tmpauthz_id && is_authorized) + *ret_tmpauthz_id = TAKE_PTR(tmpauthz_id); + + return is_authorized; +} + +static int revoke_temporary_authorization_by_id(sd_bus *bus, const char *id) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(id); + + r = sd_bus_message_new_method_call(bus, &m, + "org.freedesktop.PolicyKit1", + "/org/freedesktop/PolicyKit1/Authority", + "org.freedesktop.PolicyKit1.Authority", + "RevokeTemporaryAuthorizationById"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", id); + if (r < 0) + return bus_log_create_error(r); + + log_debug("Revoking temporary authorization %s", id); + r = sd_bus_call(bus, m, /* usec = */ 0, &error, /* ret_reply= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to revoke temporary authorization %s: %s", + id, bus_error_message(&error, r)); + + return 0; +} + +static int check_polkit_subject_for_uid(sd_bus_message *m) { + const char *kind = NULL; + uid_t uid = UID_INVALID; + int r; + + r = sd_bus_message_enter_container(m, 'r', "sa{sv}"); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(m, "s", &kind); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(m, 'a', "{sv}"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + const char *key, *contents; + char type; + + r = sd_bus_message_enter_container(m, 'e', "sv"); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + r = sd_bus_message_read(m, "s", &key); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_peek_type(m, &type, &contents); + if (r < 0) + return bus_log_parse_error(r); + + if (streq(key, "pid")) { + if (*contents != SD_BUS_TYPE_UINT32) + return bus_log_parse_error(SYNTHETIC_ERRNO(EINVAL)); + r = sd_bus_message_skip(m, "v"); + if (r < 0) + return bus_log_parse_error(r); + } else if (streq(key, "start-time")) { + if (*contents != SD_BUS_TYPE_UINT64) + return bus_log_parse_error(SYNTHETIC_ERRNO(EINVAL)); + r = sd_bus_message_skip(m, "v"); + if (r < 0) + return bus_log_parse_error(r); + } else if (streq(key, "uid")) { + if (*contents != SD_BUS_TYPE_INT32) + return bus_log_parse_error(SYNTHETIC_ERRNO(EINVAL)); + r = sd_bus_message_read(m, "v", "i", &uid); + if (r < 0) + return bus_log_parse_error(r); + } else if (streq(key, "pidfd")) { + if (*contents != SD_BUS_TYPE_UNIX_FD) + return bus_log_parse_error(SYNTHETIC_ERRNO(EINVAL)); + r = sd_bus_message_skip(m, "v"); + if (r < 0) + return bus_log_parse_error(r); + } else { + r = sd_bus_message_skip(m, "v"); + if (r < 0) + return bus_log_parse_error(r); + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + } + + r = sd_bus_message_exit_container(m); /* a(sa{sv}) */ + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); /* (a(sa{sv})) */ + if (r < 0) + return bus_log_parse_error(r); + + return uid_is_valid(uid) && uid == geteuid(); +} + +static int revoke_temporary_authorizations(sd_bus *bus) { + _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 *session_id = NULL; + int r; + + assert(bus); + + session_id = getenv("XDG_SESSION_ID"); + if (!session_id) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "XDG_SESSION_ID is not set"); + + r = sd_bus_message_new_method_call(bus, &m, + "org.freedesktop.PolicyKit1", + "/org/freedesktop/PolicyKit1/Authority", + "org.freedesktop.PolicyKit1.Authority", + "EnumerateTemporaryAuthorizations"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "(sa{sv})", "unix-session", 1, "session-id", "s", session_id); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, /* usec = */ 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to enumerate temporary authorizations: %s", + bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, 'a', "(ss(sa{sv})tt)"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + const char *id = NULL, *action_id = NULL; + + r = sd_bus_message_enter_container(reply, 'r', "ss(sa{sv})tt"); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + r = sd_bus_message_read(reply, "ss", &id, &action_id); + if (r < 0) + return bus_log_parse_error(r); + + if (streq(action_id, "org.freedesktop.systemd1.manage-units")) { + r = check_polkit_subject_for_uid(reply); + if (r < 0) + return r; + if (r > 0) { + r = revoke_temporary_authorization_by_id(bus, id); + if (r < 0) + return r; + } + } else { + r = sd_bus_message_skip(reply, "(sa{sv})"); + if (r < 0) + return bus_log_parse_error(r); + } + + r = sd_bus_message_skip(reply, "tt"); + 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); + + return 0; +} + +static int polkit_validate(sd_bus *bus) { + PolkitFlags flags = POLKIT_ALWAYS_QUERY; + int r; + + if (arg_ask_password) + flags |= POLKIT_ALLOW_INTERACTIVE; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + r = polkit_check_authorization(bus, (uint32_t) (flags & _POLKIT_MASK_PUBLIC), NULL); + if (r < 0) + return r; + if (r == 0) /* not authorized */ + return 1; + + return 0; +} + static int run(int argc, char* argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -3124,6 +3250,33 @@ static int run(int argc, char* argv[]) { if (r < 0) return r; + if (arg_remove_timestamp) { + r = revoke_temporary_authorizations(bus); + if (r < 0) + return r; + if (arg_validate) + return polkit_validate(bus); + if (arg_default_command) + return 0; + } else if (arg_reset_timestamp) { + _cleanup_free_ char *tmpauthz_id = NULL; + const PolkitFlags flags = POLKIT_ALWAYS_QUERY; + r = polkit_check_authorization(bus, (uint32_t) (flags & _POLKIT_MASK_PUBLIC), &tmpauthz_id); + if (r < 0) + return r; + if (r > 0 && tmpauthz_id) { + r = revoke_temporary_authorization_by_id(bus, tmpauthz_id); + if (r < 0) + return r; + } + if (arg_validate) + return polkit_validate(bus); + if (arg_default_command) + return 0; + } + + if (arg_validate) + return polkit_validate(bus); if (arg_scope) return start_transient_scope(bus); if (arg_path_property) diff --git a/src/run/systemd-run0.in b/src/run/systemd-run0.in index c628ef237d746..6ad114f0def31 100644 --- a/src/run/systemd-run0.in +++ b/src/run/systemd-run0.in @@ -8,6 +8,8 @@ {% endif %} account required pam_unix.so +auth required pam_deny.so + {% if HAVE_SELINUX %} session required pam_selinux.so close session required pam_selinux.so open 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..5408ffc61526e 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 "efi-fundamental.h" +#include "crypto-util.h" +#include "efi.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,97 @@ 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 opts = { argc, argv }; + int r; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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, opts.arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - optarg, + opts.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, opts.arg); if (r < 0) return r; break; - case ARG_PRIVATE_KEY_SOURCE: + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - optarg, + opts.arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) @@ -164,29 +145,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(opts.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(opts.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 +173,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(&opts); return 1; } @@ -227,13 +203,12 @@ 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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1 string: %s", - ERR_error_string(ERR_get_error(), NULL)); + if (sym_ASN1_STRING_set(link->value.file->value.unicode, obsolete, sizeof(obsolete)) == 0) + return log_openssl_errors(LOG_ERR, "Failed to set ASN1 string"); _cleanup_(SpcPeImageData_freep) SpcPeImageData *peid = SpcPeImageData_new(); if (!peid) @@ -244,46 +219,42 @@ static int spc_indirect_data_content_new(const void *digest, size_t digestsz, ui _cleanup_free_ uint8_t *peidraw = NULL; 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)); + return log_openssl_errors(LOG_ERR, "Failed to convert SpcPeImageData to BER"); _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)); + return log_openssl_errors(LOG_ERR, "Failed to get SpcPeImageData object"); - idc->data->value->value.sequence->data = TAKE_PTR(peidraw); - idc->data->value->value.sequence->length = peidrawsz; - idc->messageDigest->digestAlgorithm->algorithm = OBJ_nid2obj(NID_sha256); + if (!sym_ASN1_STRING_set(idc->data->value->value.sequence, peidraw, peidrawsz)) + return log_openssl_errors(LOG_ERR, "Failed to set ASN1_STRING data."); + + 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)); + return log_openssl_errors(LOG_ERR, "Failed to get SHA256 object"); - 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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set digest: %s", - ERR_error_string(ERR_get_error(), NULL)); + if (sym_ASN1_OCTET_STRING_set(idc->messageDigest->digest, digest, digestsz) == 0) + return log_openssl_errors(LOG_ERR, "Failed to set digest"); _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)); + return log_openssl_errors(LOG_ERR, "Failed to convert SpcIndirectDataContent to BER"); *ret_idc = TAKE_PTR(idcraw); *ret_idcsz = (size_t) idcrawsz; @@ -299,12 +270,11 @@ 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)); + return log_openssl_errors(LOG_ERR, "Failed to get current time"); } 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 +316,32 @@ 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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to add smimecap signed attribute to signer info: %s", - ERR_error_string(ERR_get_error(), NULL)); + if (sym_PKCS7_add_attrib_smimecap(si, smcap) == 0) + return log_openssl_errors(LOG_ERR, "Failed to add smimecap signed attribute to signer info"); - if (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)); + if (sym_PKCS7_add_attrib_content_type(si, NULL) == 0) + return log_openssl_errors(LOG_ERR, "Failed to add content type signed attribute to signer info"); _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) - 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)); + if (sym_PKCS7_add0_attrib_signing_time(si, time) == 0) + return log_openssl_errors(LOG_ERR, "Failed to add signing time signed attribute to signer info"); 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)); + return log_openssl_errors(LOG_ERR, "Failed to get SpcIndirectDataContent object"); - if (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)); + if (sym_PKCS7_add_signed_attribute(si, NID_pkcs9_contentType, V_ASN1_OBJECT, idc) == 0) + return log_openssl_errors(LOG_ERR, "Failed to add signed attribute to pkcs7 signer info"); *ret_p7 = TAKE_PTR(p7); *ret_si = TAKE_PTR(si); @@ -386,23 +351,20 @@ 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)); + return log_openssl_errors(LOG_ERR, "Failed to create PKCS7 data bio"); 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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse ASN.1 object: %s", - ERR_error_string(ERR_get_error(), NULL)); + if (sym_ASN1_get_object(&p, &psz, &tag, &class, size) & 0x80) + return log_openssl_errors(LOG_ERR, "Failed to parse ASN.1 object"); - if (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)); + if (sym_BIO_write(bio, p, psz) < 0) + return log_openssl_errors(LOG_ERR, "Failed to write to PKCS7 data bio"); *ret = TAKE_PTR(bio); @@ -414,31 +376,29 @@ 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)); + return log_openssl_errors(LOG_ERR, "Failed to find digest bio"); EVP_MD_CTX *mdc; - if (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)); + if (sym_BIO_get_md_ctx(mdbio, &mdc) <= 0) + return log_openssl_errors(LOG_ERR, "Failed to get digest context from bio"); unsigned char digest[EVP_MAX_MD_SIZE]; unsigned digestsz; - if (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)); + if (sym_EVP_DigestFinal_ex(mdc, digest, &digestsz) == 0) + return log_openssl_errors(LOG_ERR, "Failed to get digest"); - if (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)); + if (sym_PKCS7_add1_attrib_digest(si, digest, digestsz) == 0) + return log_openssl_errors(LOG_ERR, "Failed to add PKCS9 message digest signed attribute to signer info"); 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 +406,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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + if (argc < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No input file specified"); @@ -478,7 +442,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); if (r < 0) - return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + return r; } r = openssl_load_private_key( @@ -508,9 +472,8 @@ 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))) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse signed attributes: %s", - ERR_error_string(ERR_get_error(), NULL)); + if (!sym_ASN1_item_d2i((ASN1_VALUE **) &signed_attributes, &p, contentsz, sym_PKCS7_ATTR_SIGN_it())) + return log_openssl_errors(LOG_ERR, "Failed to parse signed attributes"); } if (arg_signed_data_signature) { @@ -547,7 +510,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 +539,9 @@ 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)); + return log_openssl_errors(LOG_ERR, "Failed to convert signed attributes ASN.1 to DER"); r = loop_write(dstfd, abuf, alen); if (r < 0) @@ -594,51 +556,46 @@ 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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to sign data: %s", - ERR_error_string(ERR_get_error(), NULL)); + if (sym_PKCS7_dataFinal(p7, bio) == 0) + return log_openssl_errors(LOG_ERR, "Failed to sign data"); } - _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)); + return log_openssl_errors(LOG_ERR, "Failed to get SpcIndirectDataContent object"); - 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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1 string: %s", - ERR_error_string(ERR_get_error(), NULL)); + if (sym_ASN1_STRING_set(p7c->d.other->value.sequence, idcraw, idcrawsz) == 0) + return log_openssl_errors(LOG_ERR, "Failed to set ASN1 string"); - if (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)); + if (sym_PKCS7_set_content(p7, p7c) == 0) + return log_openssl_errors(LOG_ERR, "Failed to set PKCS7 data"); 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)); + return log_openssl_errors(LOG_ERR, "Failed to convert PKCS7 signature to DER"); _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL; _cleanup_free_ PeHeader *pe_header = NULL; @@ -738,20 +695,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(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/shared/acl-util.c b/src/shared/acl-util.c index ae4684414ca8e..84fca4da0e444 100644 --- a/src/shared/acl-util.c +++ b/src/shared/acl-util.c @@ -3,19 +3,20 @@ #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" #include "user-util.h" #if HAVE_ACL -static void *libacl_dl = NULL; - DLSYM_PROTOTYPE(acl_add_perm); DLSYM_PROTOTYPE(acl_calc_mask); DLSYM_PROTOTYPE(acl_copy_entry); @@ -42,17 +43,18 @@ 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", - "Support for file Access Control Lists (ACLs)", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libacl.so.1"); +int dlopen_libacl(int log_level) { +#if HAVE_ACL + static void *libacl_dl = NULL; + + LIBACL_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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 +81,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 +95,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 +340,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 +417,7 @@ int parse_acl( if (!split) return -ENOMEM; - r = dlopen_libacl(); + r = dlopen_libacl(LOG_DEBUG); if (r < 0) return r; @@ -588,7 +595,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 +654,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 +720,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 +808,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..981f43a8cc082 100644 --- a/src/shared/acl-util.h +++ b/src/shared/acl-util.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dlopen.h" + #include "shared-forward.h" #if HAVE_ACL @@ -36,8 +38,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); @@ -62,6 +62,17 @@ static inline int acl_set_perm(acl_permset_t ps, acl_perm_t p, bool b) { return (b ? sym_acl_add_perm : sym_acl_delete_perm)(ps, p); } +#define LIBACL_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("acl", \ + "Support for file Access Control Lists (ACLs)", \ + priority, \ + "libacl.so.1") + +#define DLOPEN_LIBACL(log_level, priority) \ + ({ \ + LIBACL_NOTE(priority); \ + dlopen_libacl(log_level); \ + }) #else typedef void* acl_t; @@ -85,10 +96,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; } @@ -96,8 +103,12 @@ static inline int devnode_acl(int fd, const Set *uids) { static inline int fd_add_uid_acl_permission(int fd, uid_t uid, unsigned mask) { return -EOPNOTSUPP; } + +#define DLOPEN_LIBACL(log_level, priority) dlopen_libacl(log_level) #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..c2e874553e3ec 100644 --- a/src/shared/apparmor-util.c +++ b/src/shared/apparmor-util.c @@ -1,14 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - -#include "alloc-util.h" #include "apparmor-util.h" -#include "fileio.h" #include "log.h" -#include "parse-util.h" -static void *libapparmor_dl = NULL; +#if HAVE_APPARMOR + +#include + +#include "sd-dlopen.h" + +#include "fileio.h" DLSYM_PROTOTYPE(aa_change_onexec) = NULL; DLSYM_PROTOTYPE(aa_change_profile) = NULL; @@ -19,26 +20,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; @@ -46,22 +27,47 @@ bool mac_apparmor_use(void) { if (cached_use >= 0) return cached_use; - _cleanup_free_ char *p = NULL; - r = read_one_line_file("/sys/module/apparmor/parameters/enabled", &p); + r = read_boolean_file("/sys/module/apparmor/parameters/enabled"); if (r < 0) { if (r != -ENOENT) - log_debug_errno(r, "Failed to read /sys/module/apparmor/parameters/enabled, assuming AppArmor is not available: %m"); + log_debug_errno(r, "Failed to read and parse /sys/module/apparmor/parameters/enabled, assuming AppArmor is not available: %m"); return (cached_use = false); } - - r = parse_boolean(p); - if (r < 0) - log_debug_errno(r, "Failed to parse /sys/module/apparmor/parameters/enabled, assuming AppArmor is not available: %m"); - if (r <= 0) + 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 + static void *libapparmor_dl = NULL; + + 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..79d2f00ebd8a6 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 @@ -76,6 +76,9 @@ static const BaseFilesystem table[] = { #elif defined(__arm__) /* No /lib64 on arm. The linker is /lib/ld-linux-armhf.so.3. */ # define KNOW_LIB64_DIRS 1 +#elif defined(__hppa__) + /* No /lib32 or /lib64 on hppa. The linker is /usr/lib/hppa-linux-gnu/ld.so.1. */ +# define KNOW_LIB64_DIRS 1 #elif defined(__i386__) || defined(__x86_64__) { "lib64", 0, "usr/lib64\0" "usr/lib\0", "ld-linux-x86-64.so.2" }, @@ -114,6 +117,8 @@ static const BaseFilesystem table[] = { /* powerpc64-linux-gnu */ # else /* powerpc-linux-gnu */ + /* No /lib32 or /lib64 on powerpc. The linker is /usr/lib/powerpc-linux-gnu/ld.so.1. */ +# define KNOW_LIB64_DIRS 1 # endif #elif defined(__riscv) # if __riscv_xlen == 32 diff --git a/src/shared/battery-util.c b/src/shared/battery-util.c index 8ca9a6d4b2be3..7e18c2ee87ab8 100644 --- a/src/shared/battery-util.c +++ b/src/shared/battery-util.c @@ -43,7 +43,7 @@ static int device_is_power_sink(sd_device *device) { FOREACH_DEVICE(e, d) { const char *val; - r = sd_device_get_sysattr_value(d, "power_role", &val); + r = device_get_sysattr_safe_string(d, "power_role", &val); if (r < 0) { if (r != -ENOENT) log_device_debug_errno(d, r, "Failed to read 'power_role' sysfs attribute, ignoring: %m"); @@ -75,11 +75,10 @@ static bool battery_is_discharging(sd_device *d) { assert(d); - r = sd_device_get_sysattr_value(d, "scope", &val); - if (r < 0) { - if (r != -ENOENT) - log_device_debug_errno(d, r, "Failed to read 'scope' sysfs attribute, ignoring: %m"); - } else if (streq(val, "Device")) { + r = device_get_sysattr_streq(d, "scope", "Device"); + if (r < 0 && r != -ENOENT) + log_device_debug_errno(d, r, "Failed to read 'scope' sysfs attribute, ignoring: %m"); + if (r > 0) { log_device_debug(d, "The power supply is a device battery, ignoring device."); return false; } @@ -93,7 +92,7 @@ static bool battery_is_discharging(sd_device *d) { } /* Possible values: "Unknown", "Charging", "Discharging", "Not charging", "Full" */ - r = sd_device_get_sysattr_value(d, "status", &val); + r = device_get_sysattr_safe_string(d, "status", &val); if (r < 0) { log_device_debug_errno(d, r, "Failed to read 'status' sysfs attribute, assuming the battery is discharging: %m"); return true; @@ -130,7 +129,7 @@ int on_ac_power(void) { * https://docs.kernel.org/admin-guide/abi-testing.html#abi-file-testing-sysfs-class-power */ const char *val; - r = sd_device_get_sysattr_value(d, "type", &val); + r = device_get_sysattr_safe_string(d, "type", &val); if (r < 0) { log_device_debug_errno(d, r, "Failed to read 'type' sysfs attribute, ignoring device: %m"); continue; diff --git a/src/shared/binfmt-util.c b/src/shared/binfmt-util.c index d21fd10136fb4..0faca5966341c 100644 --- a/src/shared/binfmt-util.c +++ b/src/shared/binfmt-util.c @@ -18,6 +18,12 @@ int binfmt_mounted_and_writable(void) { fd = RET_NERRNO(open("/proc/sys/fs/binfmt_misc", O_CLOEXEC | O_DIRECTORY | O_PATH)); if (fd == -ENOENT) return false; + /* ELOOP happens when binfmt_misc is an automount point under a read-only bind mount of /proc — + * the kernel cannot trigger the automount and returns ELOOP instead. Common in mock/Koji buildroots. */ + if (fd == -ELOOP || ERRNO_IS_NEG_PRIVILEGE(fd)) { + log_debug_errno(fd, "Failed to open /proc/sys/fs/binfmt_misc, ignoring: %m"); + return false; + } if (fd < 0) return fd; diff --git a/src/shared/blkid-util.c b/src/shared/blkid-util.c index ae20b47d9ef12..233d45c4f0c87 100644 --- a/src/shared/blkid-util.c +++ b/src/shared/blkid-util.c @@ -1,16 +1,14 @@ /* 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" #if HAVE_BLKID -static void *libblkid_dl = NULL; - DLSYM_PROTOTYPE(blkid_do_fullprobe) = NULL; DLSYM_PROTOTYPE(blkid_do_probe) = NULL; DLSYM_PROTOTYPE(blkid_do_safeprobe) = NULL; @@ -48,54 +46,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 +94,55 @@ 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 + static void *libblkid_dl = NULL; + + LIBBLKID_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + + 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..c211bbf3abc0f 100644 --- a/src/shared/blkid-util.h +++ b/src/shared/blkid-util.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dlopen.h" + #include "shared-forward.h" #if HAVE_BLKID @@ -46,8 +48,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 +65,20 @@ 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); + +#define LIBBLKID_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("blkid", \ + "Support for block device identification", \ + priority, \ + "libblkid.so.1") + +#define DLOPEN_LIBBLKID(log_level, priority) \ + ({ \ + LIBBLKID_NOTE(priority); \ + dlopen_libblkid(log_level); \ + }) #else -static inline int dlopen_libblkid(void) { - return -EOPNOTSUPP; -} +#define DLOPEN_LIBBLKID(log_level, priority) dlopen_libblkid(log_level) #endif + +int dlopen_libblkid(int log_level); diff --git a/src/shared/blockdev-list.c b/src/shared/blockdev-list.c index d856b1cb48cac..3c3ec59f0636d 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" @@ -26,7 +27,7 @@ void block_device_done(BlockDevice *d) { void block_device_array_free(BlockDevice *d, size_t n_devices) { FOREACH_ARRAY(i, d, n_devices) - block_device_done(d); + block_device_done(i); free(d); } @@ -86,99 +87,157 @@ static int blockdev_get_subsystem(sd_device *d, char **ret_subsystem) { return ret < 0 ? ret : -ENOENT; } -int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *ret_n_devices) { - _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; +int blockdev_list_get_root_devnos(BlockDevListFlags flags, dev_t *ret_root, dev_t *ret_whole_root) { int r; - assert(!!ret_devices == !!ret_n_devices); - - /* If ret_devices/ret_n_devices are passed, returns a list of matching block devices, otherwise - * prints the list to stdout */ + /* Looks up the devno of the block device the OS is booted from, plus the whole-disk devno (e.g. + * /dev/sda for /dev/sda3). Returns both as 0 if root can't be determined – which is fine, callers + * pass these to blockdev_list_one() and devnum_set_and_equal() handles zero gracefully. + * + * Split out of blockdev_list() so subscribers can look them up once up-front instead of per uevent. */ - BlockDevice *l = NULL; - size_t n = 0; - CLEANUP_ARRAY(l, n, block_device_array_free); + dev_t root_devno = 0, whole_root_devno = 0; - 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); + 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"); } + } - if (sd_device_enumerator_new(&e) < 0) - return log_oom(); + if (ret_root) + *ret_root = root_devno; + if (ret_whole_root) + *ret_whole_root = whole_root_devno; - r = sd_device_enumerator_add_match_subsystem(e, "block", /* match= */ true); - if (r < 0) - return log_error_errno(r, "Failed to add subsystem match: %m"); + return 0; +} - if (FLAGS_SET(flags, BLOCKDEV_LIST_REQUIRE_LUKS)) { - r = sd_device_enumerator_add_match_property(e, "ID_FS_TYPE", "crypto_LUKS"); - if (r < 0) - return log_error_errno(r, "Failed to add match for LUKS block devices: %m"); +int blockdev_list_one( + sd_device *dev, + BlockDevListFlags flags, + dev_t root_devno, + dev_t whole_root_devno, + BlockDevice *ret) { + + int r; + + assert(dev); + + /* Per-device counterpart to blockdev_list(). Applies the same filters and metadata collection, + * and reports via a multi-valued return: + * + * MATCH_NO – cleanly fails a static filter (never interesting) + * MATCH_YES – fully matches + * MATCH_FILTERED – passes static filters but fails a dynamic one (IGNORE_EMPTY / IGNORE_READ_ONLY) + * MATCH_SKIPPED – an unexpected error tripped during filter evaluation; the caller should + * treat this like _FILTERED, but the distinct code makes the soft failure + * visible rather than silently swallowed here. + * + * On each of the four success codes *ret is initialized: BLOCK_DEVICE_NULL for MATCH_NO and + * MATCH_SKIPPED, fully populated for MATCH_YES / MATCH_FILTERED (with d.size / d.read_only + * reflecting the current state so callers can tell which dynamic filter tripped). On negative + * return *ret is untouched. + * + * blockdev_list() itself collapses everything except _YES back into "skip". */ + + const char *node; + r = sd_device_get_devname(dev, &node); + if (r < 0) { + log_device_warning_errno(dev, r, "Failed to get device node of discovered block device, ignoring: %m"); + goto skipped; } - FOREACH_DEVICE(e, dev) { - const char *node; + if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_ROOT) && root_devno != 0) { + dev_t devno; - r = sd_device_get_devname(dev, &node); + r = sd_device_get_devnum(dev, &devno); if (r < 0) { - log_device_warning_errno(dev, r, "Failed to get device node of discovered block device, ignoring: %m"); - continue; + log_device_warning_errno(dev, r, "Failed to get major/minor of discovered block device, ignoring: %m"); + goto skipped; } - if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_ROOT) && root_devno != 0) { - dev_t devno; + if (devnum_set_and_equal(devno, root_devno) || + devnum_set_and_equal(devno, whole_root_devno)) + goto no_match; + } - r = sd_device_get_devnum(dev, &devno); - if (r < 0) { - log_device_warning_errno(dev, r, "Failed to get major/minor of discovered block device, ignoring: %m"); - continue; - } + if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_ZRAM)) { + r = device_sysname_startswith(dev, "zram"); + if (r < 0) { + log_device_warning_errno(dev, r, "Failed to check device name of discovered block device '%s', ignoring: %m", node); + goto skipped; + } + if (r > 0) + goto no_match; + } - if (devno == root_devno) - continue; + if (FLAGS_SET(flags, BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING)) { + r = blockdev_partscan_enabled(dev); + if (r < 0) { + log_device_warning_errno(dev, r, "Unable to determine whether '%s' supports partition scanning, skipping device: %m", node); + goto skipped; } + if (r == 0) { + log_device_debug(dev, "Device '%s' does not support partition scanning, skipping.", node); + goto no_match; + } + } - if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_ZRAM)) { - r = device_sysname_startswith(dev, "zram"); - if (r < 0) { - log_device_warning_errno(dev, r, "Failed to check device name of discovered block device '%s', ignoring: %m", node); - continue; - } - if (r > 0) - continue; + if (FLAGS_SET(flags, BLOCKDEV_LIST_REQUIRE_LUKS)) { + const char *fstype; + r = sd_device_get_property_value(dev, "ID_FS_TYPE", &fstype); + if (r == -ENOENT) + goto no_match; /* No detected filesystem → not a LUKS superblock. */ + if (r < 0) { + log_device_warning_errno(dev, r, "Failed to acquire ID_FS_TYPE of device '%s', ignoring: %m", node); + goto skipped; } + if (!streq(fstype, "crypto_LUKS")) + goto no_match; + } - if (FLAGS_SET(flags, BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING)) { - r = blockdev_partscan_enabled(dev); - if (r < 0) { - log_device_warning_errno(dev, r, "Unable to determine whether '%s' supports partition scanning, skipping device: %m", node); - continue; - } - if (r == 0) { - log_device_debug(dev, "Device '%s' does not support partition scanning, skipping.", node); - continue; - } + bool dynamic_filtered = false; + + uint64_t size = UINT64_MAX; + if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_EMPTY) || ret) { + r = device_get_sysattr_u64(dev, "size", &size); + if (r < 0) + log_device_debug_errno(dev, r, "Failed to acquire size of device '%s', ignoring: %m", node); + else + /* the 'size' sysattr is always in multiples of 512, even on 4K sector block devices! */ + assert_se(MUL_ASSIGN_SAFE(&size, 512)); /* Overflow check for coverity */ + + if (size == 0 && FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_EMPTY)) { + log_device_debug(dev, "Device '%s' has a zero size, assuming drive without a medium.", node); + dynamic_filtered = true; } + } - uint64_t size = UINT64_MAX; - if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_EMPTY) || ret_devices) { - r = device_get_sysattr_u64(dev, "size", &size); - if (r < 0) - log_device_debug_errno(dev, r, "Failed to acquire size of device '%s', ignoring: %m", node); - else - /* the 'size' sysattr is always in multiples of 512, even on 4K sector block devices! */ - assert_se(MUL_ASSIGN_SAFE(&size, 512)); /* Overflow check for coverity */ + int ro = -1; + if (FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_READ_ONLY) || FLAGS_SET(flags, BLOCKDEV_LIST_METADATA)) { + 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 (size == 0 && FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_EMPTY)) { - log_device_debug(dev, "Device '%s' has a zero size, assuming drive without a medium, skipping.", node); - continue; - } + if (ro > 0 && FLAGS_SET(flags, BLOCKDEV_LIST_IGNORE_READ_ONLY)) { + log_device_debug(dev, "Device '%s' is read-only.", node); + dynamic_filtered = true; } + } + + if (!ret) + return dynamic_filtered ? BLOCKDEV_LIST_MATCH_FILTERED : BLOCKDEV_LIST_MATCH_YES; + /* Wrap the metadata population in a nested scope so the cleanup-attributed locals don't + * intersect the goto-target label below (jumping into a protected scope is a clang error). */ + { _cleanup_strv_free_ char **list = NULL; if (FLAGS_SET(flags, BLOCKDEV_LIST_SHOW_SYMLINKS)) { FOREACH_DEVICE_DEVLINK(dev, sl) @@ -195,35 +254,95 @@ int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *re (void) blockdev_get_subsystem(dev, &subsystem); } - if (ret_devices) { - uint64_t diskseq = UINT64_MAX; - r = sd_device_get_diskseq(dev, &diskseq); - if (r < 0) - log_device_debug_errno(dev, r, "Failed to acquire diskseq of device '%s', ignoring: %m", node); + uint64_t diskseq = UINT64_MAX; + r = sd_device_get_diskseq(dev, &diskseq); + if (r < 0) + log_device_debug_errno(dev, r, "Failed to acquire diskseq of device '%s', ignoring: %m", node); + + _cleanup_free_ char *m = strdup(node); + if (!m) + return log_oom(); + + *ret = (BlockDevice) { + .node = TAKE_PTR(m), + .symlinks = TAKE_PTR(list), + .diskseq = diskseq, + .size = size, + .model = TAKE_PTR(model), + .vendor = TAKE_PTR(vendor), + .subsystem = TAKE_PTR(subsystem), + .read_only = ro, + }; + + return dynamic_filtered ? BLOCKDEV_LIST_MATCH_FILTERED : BLOCKDEV_LIST_MATCH_YES; + } - if (!GREEDY_REALLOC(l, n+1)) - return log_oom(); +no_match: + if (ret) + *ret = BLOCK_DEVICE_NULL; + return BLOCKDEV_LIST_MATCH_NO; - _cleanup_free_ char *m = strdup(node); - if (!m) - return log_oom(); +skipped: + if (ret) + *ret = BLOCK_DEVICE_NULL; + return BLOCKDEV_LIST_MATCH_SKIPPED; +} + +int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *ret_n_devices) { + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + int r; + + assert(!!ret_devices == !!ret_n_devices); + + /* If ret_devices/ret_n_devices are passed, returns a list of matching block devices, otherwise + * prints the list to stdout */ + + BlockDevice *l = NULL; + size_t n = 0; + CLEANUP_ARRAY(l, n, block_device_array_free); - l[n++] = (BlockDevice) { - .node = TAKE_PTR(m), - .symlinks = TAKE_PTR(list), - .diskseq = diskseq, - .size = size, - .model = TAKE_PTR(model), - .vendor = TAKE_PTR(vendor), - .subsystem = TAKE_PTR(subsystem), - }; + dev_t root_devno = 0, whole_root_devno = 0; + (void) blockdev_list_get_root_devnos(flags, &root_devno, &whole_root_devno); + if (sd_device_enumerator_new(&e) < 0) + return log_oom(); + + r = sd_device_enumerator_add_match_subsystem(e, "block", /* match= */ true); + if (r < 0) + return log_error_errno(r, "Failed to add subsystem match: %m"); + + if (FLAGS_SET(flags, BLOCKDEV_LIST_REQUIRE_LUKS)) { + /* blockdev_list_one() enforces this filter authoritatively; the enumerator match is just + * an optimization so we don't iterate non-LUKS devices here. */ + r = sd_device_enumerator_add_match_property(e, "ID_FS_TYPE", "crypto_LUKS"); + if (r < 0) + return log_error_errno(r, "Failed to add match for LUKS block devices: %m"); + } + + FOREACH_DEVICE(e, dev) { + _cleanup_(block_device_done) BlockDevice d = BLOCK_DEVICE_NULL; + + r = blockdev_list_one(dev, flags, root_devno, whole_root_devno, ret_devices ? &d : NULL); + if (r < 0) + return r; + if (r != BLOCKDEV_LIST_MATCH_YES) + continue; /* MATCH_NO or MATCH_FILTERED – bulk enumeration treats both as "skip" */ + + if (ret_devices) { + if (!GREEDY_REALLOC(l, n+1)) + return log_oom(); + + l[n++] = TAKE_STRUCT(d); } else { + const char *node; + if (sd_device_get_devname(dev, &node) < 0) + continue; + printf("%s\n", node); if (FLAGS_SET(flags, BLOCKDEV_LIST_SHOW_SYMLINKS)) - STRV_FOREACH(i, list) - printf("%s%s%s%s\n", on_tty() ? " " : "", ansi_grey(), *i, ansi_normal()); + FOREACH_DEVICE_DEVLINK(dev, sl) + printf("%s%s%s%s\n", on_tty() ? " " : "", ansi_grey(), sl, ansi_normal()); } } diff --git a/src/shared/blockdev-list.h b/src/shared/blockdev-list.h index 845f336be5b65..8658b45753ae0 100644 --- a/src/shared/blockdev-list.h +++ b/src/shared/blockdev-list.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "basic-forward.h" #include "shared-forward.h" typedef enum BlockDevListFlags { @@ -10,9 +11,16 @@ 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 */ + BLOCKDEV_LIST_IGNORE_READ_ONLY = 1 << 7, /* Ignore read-only block devices */ } BlockDevListFlags; +/* The "dynamic" filters – ones the kernel can flip at runtime via sysattrs on a live device. + * Useful for callers that want to distinguish "device is hard-filtered (never interesting)" from + * "device just transitioned in or out of the candidate set". */ +#define BLOCKDEV_LIST_DYNAMIC_FILTERS_MASK \ + (BLOCKDEV_LIST_IGNORE_EMPTY | BLOCKDEV_LIST_IGNORE_READ_ONLY) + typedef struct BlockDevice { char *node; char **symlinks; @@ -21,14 +29,38 @@ 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, \ } +/* Return values of blockdev_list_one(). Not its own type – returned as int. */ +enum { + BLOCKDEV_LIST_MATCH_NO, /* Hard-filtered out by a static filter (subsystem, ZRAM, + * IGNORE_ROOT, REQUIRE_PARTITION_SCANNING, REQUIRE_LUKS, …). */ + BLOCKDEV_LIST_MATCH_YES, /* Passes all filters. */ + BLOCKDEV_LIST_MATCH_FILTERED, /* Passes the static filters; fails at least one dynamic filter + * (IGNORE_EMPTY, IGNORE_READ_ONLY). */ + BLOCKDEV_LIST_MATCH_SKIPPED, /* Filter evaluation hit an unexpected error (failed sysattr + * read, missing devname, …). Callers should typically treat + * this like _FILTERED (device not currently a candidate), + * but distinguishing it makes the soft failure visible + * rather than silently swallowed inside blockdev_list_one(). */ +}; + void block_device_done(BlockDevice *d); void block_device_array_free(BlockDevice *d, size_t n_devices); +int blockdev_list_get_root_devnos(BlockDevListFlags flags, dev_t *ret_root, dev_t *ret_whole_root); +int blockdev_list_one( + sd_device *dev, + BlockDevListFlags flags, + dev_t root_devno, + dev_t whole_root_devno, + BlockDevice *ret); + int blockdev_list(BlockDevListFlags flags, BlockDevice **ret_devices, size_t *ret_n_devices); diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 12a4f59c28a69..bff2047aa1edb 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -207,7 +207,7 @@ int block_device_new_from_fd(int fd, BlockDeviceLookupFlags flags, sd_device **r r = block_device_get_originating(dev_whole_disk, &dev_origin, /* recursive= */ false); if (r >= 0) - device_unref_and_replace(dev, dev_origin); + device_unref_and_replace_new_ref(dev, dev_origin); else if (r != -ENOENT) return r; } @@ -642,15 +642,9 @@ int path_get_whole_disk(const char *path, bool backing, dev_t *ret) { return fd_get_whole_disk(fd, backing, ret); } -int block_device_add_partition( - int fd, - const char *name, - int nr, - uint64_t start, - uint64_t size) { +int block_device_add_partition(int fd, int nr, uint64_t start, uint64_t size) { assert(fd >= 0); - assert(name); assert(nr > 0); struct blkpg_partition bp = { @@ -665,21 +659,12 @@ int block_device_add_partition( .datalen = sizeof(bp), }; - if (strlen(name) >= sizeof(bp.devname)) - return -EINVAL; - - strcpy(bp.devname, name); - return RET_NERRNO(ioctl(fd, BLKPG, &ba)); } -int block_device_remove_partition( - int fd, - const char *name, - int nr) { +int block_device_remove_partition(int fd, int nr) { assert(fd >= 0); - assert(name); assert(nr > 0); struct blkpg_partition bp = { @@ -692,11 +677,6 @@ int block_device_remove_partition( .datalen = sizeof(bp), }; - if (strlen(name) >= sizeof(bp.devname)) - return -EINVAL; - - strcpy(bp.devname, name); - return RET_NERRNO(ioctl(fd, BLKPG, &ba)); } @@ -824,7 +804,7 @@ int block_device_remove_all_partitions(sd_device *dev, int fd) { if (r < 0 && r != -ENOENT) log_debug_errno(r, "Failed to forget btrfs device %s, ignoring: %m", devname); - r = block_device_remove_partition(fd, devname, nr); + r = block_device_remove_partition(fd, nr); if (r == -ENODEV) { log_debug("Kernel removed partition %s before us, ignoring", devname); continue; diff --git a/src/shared/blockdev-util.h b/src/shared/blockdev-util.h index 3a20a5307d2e5..e268783d5eb17 100644 --- a/src/shared/blockdev-util.h +++ b/src/shared/blockdev-util.h @@ -45,8 +45,8 @@ int path_is_encrypted(const char *path); int fd_get_whole_disk(int fd, bool backing, dev_t *ret); int path_get_whole_disk(const char *path, bool backing, dev_t *ret); -int block_device_add_partition(int fd, const char *name, int nr, uint64_t start, uint64_t size); -int block_device_remove_partition(int fd, const char *name, int nr); +int block_device_add_partition(int fd, int nr, uint64_t start, uint64_t size); +int block_device_remove_partition(int fd, int nr); int block_device_resize_partition(int fd, int nr, uint64_t start, uint64_t size); int partition_enumerator_new(sd_device *dev, sd_device_enumerator **ret); int block_device_remove_all_partitions(sd_device *dev, int fd); diff --git a/src/shared/boot-entry.c b/src/shared/boot-entry.c index 0f1d8090247a6..2e86ba8b31829 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) { @@ -22,7 +21,7 @@ static int entry_token_load_one(int rfd, const char *dir, BootEntryTokenType *ty _cleanup_fclose_ FILE *f = NULL; int r; - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); assert(dir); assert(type); assert(*type == BOOT_ENTRY_TOKEN_AUTO); @@ -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) @@ -58,7 +57,7 @@ static int entry_token_load_one(int rfd, const char *dir, BootEntryTokenType *ty static int entry_token_load(int rfd, const char *conf_root, BootEntryTokenType *type, char **token) { int r; - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); assert(type); assert(*type == BOOT_ENTRY_TOKEN_AUTO); assert(token); @@ -98,7 +97,7 @@ static int entry_token_from_os_release(int rfd, BootEntryTokenType *type, char * _cleanup_free_ char *id = NULL, *image_id = NULL; int r; - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); assert(type); assert(IN_SET(*type, BOOT_ENTRY_TOKEN_AUTO, BOOT_ENTRY_TOKEN_OS_IMAGE_ID, BOOT_ENTRY_TOKEN_OS_ID)); assert(token); @@ -151,7 +150,7 @@ int boot_entry_token_ensure_at( int r; - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); assert(type); assert(token); @@ -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..77ee218996e3c 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -7,17 +7,18 @@ #include "alloc-util.h" #include "bootspec.h" -#include "bootspec-fundamental.h" #include "chase.h" #include "devnum-util.h" #include "dirent-util.h" #include "efi-loader.h" #include "efivars.h" #include "env-file.h" +#include "errno-util.h" #include "extract-word.h" #include "fd-util.h" #include "fileio.h" #include "find-esp.h" +#include "json-util.h" #include "log.h" #include "parse-util.h" #include "path-util.h" @@ -31,6 +32,7 @@ #include "string-util.h" #include "strv.h" #include "uki.h" +#include "utf8.h" static const char* const boot_entry_type_description_table[_BOOT_ENTRY_TYPE_MAX] = { [BOOT_ENTRY_TYPE1] = "Boot Loader Specification Type #1 (.conf)", @@ -64,15 +66,65 @@ static const char* const boot_entry_source_table[_BOOT_ENTRY_SOURCE_MAX] = { DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_source, BootEntrySource); -static void boot_entry_addons_done(BootEntryAddons *addons) { - assert(addons); +static BootEntryExtraType boot_entry_extra_type_from_filename(const char *path) { + if (!path) + return _BOOT_ENTRY_EXTRA_TYPE_INVALID; - FOREACH_ARRAY(addon, addons->items, addons->n_items) { - free(addon->cmdline); - free(addon->location); + if (endswith_no_case(path, ".addon.efi")) + return BOOT_ENTRY_ADDON; + if (endswith_no_case(path, ".confext.raw")) + return BOOT_ENTRY_CONFEXT; + if (endswith_no_case(path, ".sysext.raw")) + return BOOT_ENTRY_SYSEXT; + if (endswith_no_case(path, ".cred")) + return BOOT_ENTRY_CREDENTIAL; + + return _BOOT_ENTRY_EXTRA_TYPE_INVALID; +} + +static void boot_entry_extras_done(BootEntryExtras *extras) { + assert(extras); + + FOREACH_ARRAY(extra, extras->items, extras->n_items) { + free(extra->location); + free(extra->cmdline); } - addons->items = mfree(addons->items); - addons->n_items = 0; + extras->items = mfree(extras->items); + extras->n_items = 0; +} + +static int boot_entry_extras_add( + BootEntryExtras *extras, + BootEntryExtraType type, + const char *path, + const char *cmdline) { + + assert(extras); + assert(type >= 0); + assert(type < _BOOT_ENTRY_EXTRA_TYPE_MAX); + assert(path); + + _cleanup_free_ char *p = strdup(path); + if (!p) + return -ENOMEM; + + _cleanup_free_ char *c = NULL; + if (cmdline) { + c = strdup(cmdline); + if (!c) + return -ENOMEM; + } + + if (!GREEDY_REALLOC(extras->items, extras->n_items + 1)) + return -ENOMEM; + + extras->items[extras->n_items++] = (BootEntryExtra) { + .type = type, + .location = TAKE_PTR(p), + .cmdline = TAKE_PTR(c), + }; + + return 0; } static void boot_entry_free(BootEntry *entry) { @@ -90,7 +142,7 @@ static void boot_entry_free(BootEntry *entry) { free(entry->machine_id); free(entry->architecture); strv_free(entry->options); - boot_entry_addons_done(&entry->local_addons); + boot_entry_extras_done(&entry->local_extras); free(entry->kernel); free(entry->efi); free(entry->uki); @@ -212,6 +264,50 @@ static int parse_path_many( return strv_extend_strv_consume(s, TAKE_PTR(f), /* filter_duplicates= */ false); } +static int parse_extra( + const char *fname, + unsigned line, + const char *field, + BootEntryExtras *extras, + const char *p) { + + int r; + + assert(extras); + + _cleanup_strv_free_ char **l = strv_split(p, NULL); + if (!l) + return -ENOMEM; + + STRV_FOREACH(i, l) { + _cleanup_free_ char *c = NULL; + r = mangle_path(fname, line, field, *i, &c); + if (r < 0) + return r; + if (r == 0) + continue; + + BootEntryExtraType type = boot_entry_extra_type_from_filename(c); + if (type < 0) { + log_debug_errno(type, "Failed to determine boot entry extra type of '%s', skipping: %m", c); + continue; + } + + /* Let's filter out EFI addons for now. We have no protocol for passing them from sd-boot to + * sd-stub, hence supporting them would require major plumbing first. */ + if (type == BOOT_ENTRY_ADDON) { + log_debug("EFI addons are currently not supported for Type #1 entries, skipping '%s'.", c); + continue; + } + + r = boot_entry_extras_add(extras, type, c, /* cmdline= */ NULL); + if (r < 0) + return r; + } + + return 0; +} + static int parse_tries(const char *fname, const char **p, unsigned *ret) { _cleanup_free_ char *d = NULL; unsigned tries; @@ -231,13 +327,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 +353,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 +386,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 +434,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); @@ -417,6 +516,8 @@ static int boot_entry_load_type1( r = parse_path_one(tmp.path, line, field, &tmp.device_tree, p); else if (streq(field, "devicetree-overlay")) r = parse_path_many(tmp.path, line, field, &tmp.device_tree_overlay, p); + else if (streq(field, "extra")) + r = parse_extra(tmp.path, line, field, &tmp.local_extras, p); else { log_syntax(NULL, LOG_WARNING, tmp.path, line, 0, "Unknown line '%s', ignoring.", field); continue; @@ -454,7 +555,7 @@ int boot_config_load_type1( return r; config->n_entries++; - entry->global_addons = &config->global_addons[source]; + entry->global_extras = &config->global_extras[source]; return 0; } @@ -475,8 +576,8 @@ void boot_config_free(BootConfig *config) { boot_entry_free(i); free(config->entries); - FOREACH_ELEMENT(i, config->global_addons) - boot_entry_addons_done(i); + FOREACH_ELEMENT(i, config->global_extras) + boot_entry_extras_done(i); set_free(config->inodes_seen); } @@ -555,6 +656,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 +690,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 +703,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; } @@ -698,77 +809,156 @@ static int boot_entries_find_type1( return 0; } -static int boot_entry_load_unified( - const char *root, - const BootEntrySource source, - const char *path, - unsigned profile, - const char *osrelease_text, - const char *profile_text, - const char *cmdline_text, - BootEntry *ret) { +static void mangle_osrelease_string(char **s, const char *field) { + assert(s); + assert(field); - _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; + if (!isempty(*s) && !string_has_cc(*s, /* ok= */ NULL) && utf8_is_valid(*s)) + return; - assert(root); - assert(path); - assert(osrelease_text); - assert(ret); + if (*s) { + log_debug("OS release field '%s' is not clean, suppressing.", field); + *s = mfree(*s); + } +} - k = path_startswith(path, root); - if (!k) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path); +int bootspec_extract_osrelease( + const char *text, + char **ret_good_name, + char **ret_good_version, + char **ret_good_sort_key, + char **ret_os_id, + char **ret_os_version_id, + char **ret_image_id, + char **ret_image_version) { - f = fmemopen_unlocked((void*) osrelease_text, strlen(osrelease_text), "r"); - if (!f) - return log_oom(); + int r; + + assert(text); - r = parse_env_file(f, "os-release", + _cleanup_free_ char *os_pretty_name = NULL, *image_id = NULL, *os_name = NULL, *os_id = NULL, + *image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL; + r = parse_env_data(text, /* size= */ SIZE_MAX, + "os-release", "PRETTY_NAME", &os_pretty_name, - "IMAGE_ID", &os_image_id, + "IMAGE_ID", &image_id, "NAME", &os_name, "ID", &os_id, - "IMAGE_VERSION", &os_image_version, + "IMAGE_VERSION", &image_version, "VERSION", &os_version, "VERSION_ID", &os_version_id, "BUILD_ID", &os_build_id); if (r < 0) - return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path); + return r; + + mangle_osrelease_string(&os_pretty_name, "PRETTY_NAME"); + mangle_osrelease_string(&image_id, "IMAGE_ID"); + mangle_osrelease_string(&os_name, "NAME"); + mangle_osrelease_string(&os_id, "ID"); + mangle_osrelease_string(&image_version, "IMAGE_VERSION"); + mangle_osrelease_string(&os_version, "VERSION"); + mangle_osrelease_string(&os_version_id, "VERSION_ID"); + mangle_osrelease_string(&os_build_id, "BUILD_ID"); + const char *good_name, *good_version, *good_sort_key; if (!bootspec_pick_name_version_sort_key( os_pretty_name, - os_image_id, + image_id, os_name, os_id, - os_image_version, + image_version, os_version, os_version_id, os_build_id, &good_name, &good_version, &good_sort_key)) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path); + return -EBADMSG; - _cleanup_free_ char *profile_id = NULL, *profile_title = NULL; - if (profile_text) { - fclose(f); + _cleanup_free_ char *copy_good_name = NULL, *copy_good_version = NULL, *copy_good_sort_key = NULL; + if (ret_good_name) { + copy_good_name = strdup(good_name); + if (!copy_good_name) + return -ENOMEM; + } - f = fmemopen_unlocked((void*) profile_text, strlen(profile_text), "r"); - if (!f) - return log_oom(); + if (ret_good_version && good_version) { + copy_good_version = strdup(good_version); + if (!copy_good_version) + return -ENOMEM; + } + + if (ret_good_sort_key && good_sort_key) { + copy_good_sort_key = strdup(good_sort_key); + if (!copy_good_sort_key) + return -ENOMEM; + } + + if (ret_good_name) + *ret_good_name = TAKE_PTR(copy_good_name); + if (ret_good_version) + *ret_good_version = TAKE_PTR(copy_good_version); + if (ret_good_sort_key) + *ret_good_sort_key = TAKE_PTR(copy_good_sort_key); + + if (ret_os_id) + *ret_os_id = TAKE_PTR(os_id); + if (ret_os_version_id) + *ret_os_version_id = TAKE_PTR(os_version_id); + if (ret_image_id) + *ret_image_id = TAKE_PTR(image_id); + if (ret_image_version) + *ret_image_version = TAKE_PTR(image_version); + + return 0; +} + +static int boot_entry_load_unified( + const char *root, + const BootEntrySource source, + const char *path, + unsigned profile, + const char *osrelease_text, + const char *profile_text, + const char *cmdline_text, + BootEntry *ret) { + + int r; + + assert(root); + assert(path); + assert(osrelease_text); + assert(ret); + + const char *k = path_startswith(path, root); + if (!k) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path); - r = parse_env_file( - f, "profile", + _cleanup_free_ char *good_name = NULL, *good_version = NULL, *good_sort_key = NULL, *os_id = NULL, *os_version_id = NULL; + r = bootspec_extract_osrelease( + osrelease_text, + &good_name, + &good_version, + &good_sort_key, + &os_id, + &os_version_id, + /* ret_image_id= */ NULL, + /* ret_image_version= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to extract name/version/sort-key from os-release data from unified kernel image %s, refusing: %m", path); + + _cleanup_free_ char *profile_id = NULL, *profile_title = NULL; + if (profile_text) { + r = parse_env_data( + profile_text, /* size= */ SIZE_MAX, + ".profile", "ID", &profile_id, "TITLE", &profile_title); if (r < 0) return log_error_errno(r, "Failed to parse profile data from unified kernel image '%s': %m", path); } + _cleanup_free_ char *fname = NULL; r = path_extract_filename(path, &fname); if (r < 0) return log_error_errno(r, "Failed to extract file name from '%s': %m", path); @@ -777,7 +967,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); @@ -959,7 +1149,7 @@ static int trim_cmdline(char **cmdline) { * the ones we do care about and we are willing to load into memory have this size limit.) */ #define PE_SECTION_SIZE_MAX (4U*1024U*1024U) -static int pe_find_uki_sections( +int pe_find_uki_sections( int fd, const char *path, unsigned profile, @@ -975,9 +1165,6 @@ static int pe_find_uki_sections( assert(fd >= 0); assert(path); assert(profile != UINT_MAX); - assert(ret_osrelease); - assert(ret_profile); - assert(ret_cmdline); r = pe_load_headers_and_sections(fd, path, §ions, &pe_header); if (r < 0) @@ -1034,13 +1221,22 @@ static int pe_find_uki_sections( if (trim_cmdline(&cmdline_text) < 0) return log_oom(); - *ret_osrelease = TAKE_PTR(osrelease_text); - *ret_profile = TAKE_PTR(profile_text); - *ret_cmdline = TAKE_PTR(cmdline_text); + if (ret_osrelease) + *ret_osrelease = TAKE_PTR(osrelease_text); + if (ret_profile) + *ret_profile = TAKE_PTR(profile_text); + if (ret_cmdline) + *ret_cmdline = TAKE_PTR(cmdline_text); return 1; nothing: - *ret_osrelease = *ret_profile = *ret_cmdline = NULL; + if (ret_osrelease) + *ret_osrelease = NULL; + if (ret_profile) + *ret_profile = NULL; + if (ret_cmdline) + *ret_cmdline = NULL; + return 0; } @@ -1055,6 +1251,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) @@ -1088,126 +1285,140 @@ static int pe_find_addon_sections( return 0; } -static int insert_boot_entry_addon( - BootEntryAddons *addons, - char *location, - char *cmdline) { - - assert(addons); - - if (!GREEDY_REALLOC(addons->items, addons->n_items + 1)) - return log_oom(); - - addons->items[addons->n_items++] = (BootEntryAddon) { - .location = location, - .cmdline = cmdline, - }; - - return 0; -} - -static int boot_entries_find_unified_addons( +static int boot_entries_find_unified_extras( BootConfig *config, int d_fd, - const char *addon_dir, - const char *root, - BootEntryAddons *ret_addons) { + const char *extra_dir, + BootEntryExtraType only_type, + const char *where, + bool suppress_seen, + BootEntryExtras *extras) { - _cleanup_closedir_ DIR *d = NULL; - _cleanup_free_ char *full = NULL; - _cleanup_(boot_entry_addons_done) BootEntryAddons addons = {}; int r; - assert(ret_addons); assert(config); + assert(extras); - r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d); + _cleanup_closedir_ DIR *d = NULL; + r = chase_and_opendirat( + /* root_fd= */ d_fd, + /* dir_fd= */ d_fd, + extra_dir, + /* chase_flags= */ 0, + /* ret_path= */ NULL, + &d); if (r == -ENOENT) return 0; if (r < 0) - return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(addon_dir)); - - FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) { - _cleanup_free_ char *j = NULL, *cmdline = NULL, *location = NULL; - _cleanup_close_ int fd = -EBADF; + return log_error_errno(r, "Failed to open '%s/%s': %m", where, skip_leading_slash(extra_dir)); + FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read '%s': %m", extra_dir)) { if (!dirent_is_file(de)) continue; - if (!endswith_no_case(de->d_name, ".addon.efi")) + BootEntryExtraType type = boot_entry_extra_type_from_filename(de->d_name); + if (type < 0) { + log_debug_errno(type, "Unrecognized extra file '%s', skipping.", de->d_name); + continue; + } + if (only_type >= 0 && type != only_type) { + log_debug("Extra file '%s' type not permitted in '%s', skipping.", de->d_name, extra_dir); continue; + } - fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY); - if (fd < 0) { - log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name); + _cleanup_free_ char *location = path_join(extra_dir, de->d_name); + if (!location) + return log_oom(); + + _cleanup_close_ int pin_fd = openat(dirfd(d), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW); + if (pin_fd < 0) { + log_debug_errno(errno, "Failed to pin '%s', ignoring: %m", location); continue; } - r = config_check_inode_relevant_and_unseen(config, fd, de->d_name); - if (r < 0) - return r; - if (r == 0) /* inode already seen or otherwise not relevant */ + r = fd_verify_regular(pin_fd); + if (r < 0) { + log_debug_errno(r, "Unrecognized inode type of '%s', skipping.", location); continue; + } - j = path_join(full, de->d_name); - if (!j) - return log_oom(); + if (suppress_seen) { + r = config_check_inode_relevant_and_unseen(config, pin_fd, location); + if (r < 0) + return r; + if (r == 0) /* inode already seen or otherwise not relevant */ + continue; + } - if (pe_find_addon_sections(fd, j, &cmdline) <= 0) - continue; + _cleanup_free_ char *cmdline = NULL; + if (type == BOOT_ENTRY_ADDON) { + _cleanup_close_ int fd = fd_reopen(pin_fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd < 0) { + log_debug_errno(fd, "Failed to open '%s', ignoring: %m", location); + continue; + } - location = strdup(j); - if (!location) - return log_oom(); + /* Try to extract the command line, but let's handle any failures gracefully, but + * still mention the extra file exists. */ + (void) pe_find_addon_sections(fd, location, &cmdline); + } - r = insert_boot_entry_addon(&addons, location, cmdline); + r = boot_entry_extras_add(extras, type, location, cmdline); if (r < 0) return r; - - TAKE_PTR(location); - TAKE_PTR(cmdline); } - *ret_addons = TAKE_STRUCT(addons); return 0; } -static int boot_entries_find_unified_global_addons( +static int boot_entries_find_unified_global_extras( BootConfig *config, - const char *root, - const char *d_name, - BootEntryAddons *ret_addons) { - - int r; - _cleanup_closedir_ DIR *d = NULL; + const char *where, + const char *extra_dir, + BootEntryExtraType only_type, + BootEntryExtras *extras) { - assert(ret_addons); + assert(extras); - r = chase_and_opendir(root, NULL, CHASE_PROHIBIT_SYMLINKS, NULL, &d); - if (r == -ENOENT) + _cleanup_close_ int where_fd = RET_NERRNO(open(where, O_DIRECTORY|O_CLOEXEC)); + if (where_fd == -ENOENT) return 0; - if (r < 0) - return log_error_errno(r, "Failed to open '%s/%s': %m", root, skip_leading_slash(d_name)); - - return boot_entries_find_unified_addons(config, dirfd(d), d_name, root, ret_addons); + if (where_fd < 0) + return log_error_errno(where_fd, "Failed to open '%s': %m", where); + + return boot_entries_find_unified_extras( + config, + where_fd, + extra_dir, + only_type, + where, + /* suppress_seen= */ true, + extras); } -static int boot_entries_find_unified_local_addons( +static int boot_entries_find_unified_local_extras( BootConfig *config, int d_fd, - const char *d_name, - const char *root, + const char *uki, + const char *where, BootEntry *ret) { - _cleanup_free_ char *addon_dir = NULL; + _cleanup_free_ char *extra_dir = NULL; assert(ret); - addon_dir = strjoin(d_name, ".extra.d"); - if (!addon_dir) + extra_dir = strjoin(uki, ".extra.d"); + if (!extra_dir) return log_oom(); - return boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons); + return boot_entries_find_unified_extras( + config, + d_fd, + extra_dir, + /* only_type= */ _BOOT_ENTRY_EXTRA_TYPE_INVALID, + where, + /* suppress_seen= */ false, + &ret->local_extras); } static int boot_entries_find_unified( @@ -1269,11 +1480,11 @@ static int boot_entries_find_unified( if (boot_entry_load_unified(root, source, j, p, osrelease, profile, cmdline, entry) < 0) continue; - /* look for .efi.extra.d */ - (void) boot_entries_find_unified_local_addons(config, dirfd(d), de->d_name, full, entry); + /* Look for .efi.extra.d/ */ + (void) boot_entries_find_unified_local_extras(config, dirfd(d), de->d_name, full, entry); - /* Set up the backpointer, so that we can find the global addons */ - entry->global_addons = &config->global_addons[source]; + /* Set up the backpointer, so that we can find the global extras */ + entry->global_extras = &config->global_extras[source]; config->n_entries++; } @@ -1514,45 +1725,73 @@ int boot_config_finalize(BootConfig *config) { return 0; } -int boot_config_load( +static int boot_entries_load( BootConfig *config, - const char *esp_path, - const char *xbootldr_path) { + BootEntrySource source, + const char *where) { /* Mount point of ESP/XBOOTLDR */ int r; assert(config); + assert(source >= 0); + assert(source < _BOOT_ENTRY_SOURCE_MAX); - if (esp_path) { - r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf"); - if (r < 0) - return r; + if (!where) + return 0; - r = boot_entries_find_type1(config, esp_path, BOOT_ENTRY_ESP, "/loader/entries"); - if (r < 0) - return r; + r = boot_entries_find_type1(config, where, source, "/loader/entries"); + if (r < 0) + return r; - r = boot_entries_find_unified(config, esp_path, BOOT_ENTRY_ESP, "/EFI/Linux/"); - if (r < 0) - return r; + r = boot_entries_find_unified(config, where, source, "/EFI/Linux/"); + if (r < 0) + return r; + + static const struct { + BootEntryExtraType extra_type; + const char *directory; + } table[] = { + { BOOT_ENTRY_ADDON, "/loader/addons/" }, + { BOOT_ENTRY_CONFEXT, "/loader/extensions/" }, + { BOOT_ENTRY_SYSEXT, "/loader/extensions/" }, + { BOOT_ENTRY_CREDENTIAL, "/loader/credentials/" }, + }; - r = boot_entries_find_unified_global_addons(config, esp_path, "/loader/addons/", - &config->global_addons[BOOT_ENTRY_ESP]); + FOREACH_ELEMENT(i, table) { + r = boot_entries_find_unified_global_extras( + config, + where, + i->directory, + i->extra_type, + &config->global_extras[source]); if (r < 0) return r; } - if (xbootldr_path) { - r = boot_entries_find_type1(config, xbootldr_path, BOOT_ENTRY_XBOOTLDR, "/loader/entries"); + return 0; +} + +int boot_config_load( + BootConfig *config, + const char *esp_path, + const char *xbootldr_path) { + + int r; + + assert(config); + + if (esp_path) { + r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf"); if (r < 0) return r; - r = boot_entries_find_unified(config, xbootldr_path, BOOT_ENTRY_XBOOTLDR, "/EFI/Linux/"); + r = boot_entries_load(config, BOOT_ENTRY_ESP, esp_path); if (r < 0) return r; + } - r = boot_entries_find_unified_global_addons(config, xbootldr_path, "/loader/addons/", - &config->global_addons[BOOT_ENTRY_XBOOTLDR]); + if (xbootldr_path) { + r = boot_entries_load(config, BOOT_ENTRY_XBOOTLDR, xbootldr_path); if (r < 0) return r; } @@ -1587,11 +1826,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 */ @@ -1607,7 +1863,7 @@ int boot_config_augment_from_loader( char **found_by_loader, bool auto_only) { - static const BootEntryAddons no_addons = (BootEntryAddons) {}; + static const BootEntryExtras no_extras = (BootEntryExtras) {}; static const char *const title_table[] = { /* Pretty names for a few well-known automatically discovered entries. */ "auto-osx", "macOS", @@ -1666,7 +1922,7 @@ int boot_config_augment_from_loader( .tries_left = UINT_MAX, .tries_done = UINT_MAX, .profile = UINT_MAX, - .global_addons = &no_addons, + .global_extras = &no_extras, }; } @@ -1689,10 +1945,10 @@ static void boot_entry_file_list( const char *field, const char *root, const char *p, - int *ret_status) { + int *pstatus) { assert(p); - assert(ret_status); + assert(pstatus); int status = chase_and_access(p, root, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, F_OK, NULL); @@ -1707,16 +1963,23 @@ static void boot_entry_file_list( } else printf("%s\n", p); - if (*ret_status == 0 && status < 0) - *ret_status = status; + if (*pstatus == 0 && status < 0) + *pstatus = status; } -static void print_addon( - BootEntryAddon *addon, - const char *addon_str) { +static void print_extra( + const BootEntry *e, + const BootEntryExtra *extra, + const char *field, + int *status) { - printf(" %s: %s\n", addon_str, addon->location); - printf(" options: %s%s\n", glyph(GLYPH_TREE_RIGHT), addon->cmdline); + assert(e); + assert(extra); + + boot_entry_file_list(field, e->root, extra->location, status); + + if (extra->cmdline) + printf(" options: %s%s\n", glyph(GLYPH_TREE_RIGHT), extra->cmdline); } static int indent_embedded_newlines(char *cmdline, char **ret_cmdline) { @@ -1734,12 +1997,10 @@ static int indent_embedded_newlines(char *cmdline, char **ret_cmdline) { return -ENOMEM; *ret_cmdline = TAKE_PTR(t); - return 0; } -static int print_cmdline(const BootEntry *e) { - +static int print_cmdline(const BootEntry *e, int *status) { _cleanup_free_ char *options = NULL, *combined_cmdline = NULL, *t2 = NULL; assert(e); @@ -1760,17 +2021,20 @@ static int print_cmdline(const BootEntry *e) { return log_oom(); } - FOREACH_ARRAY(addon, e->global_addons->items, e->global_addons->n_items) { - print_addon(addon, "global-addon"); - if (!strextend(&t2, " ", addon->cmdline)) - return log_oom(); + FOREACH_ARRAY(extra, e->global_extras->items, e->global_extras->n_items) { + print_extra(e, extra, "extra", status); + + if (extra->cmdline) + if (!strextend(&t2, " ", extra->cmdline)) + return log_oom(); } - FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) { - /* Add space at the beginning of addon_str to align it correctly */ - print_addon(addon, " local-addon"); - if (!strextend(&t2, " ", addon->cmdline)) - return log_oom(); + FOREACH_ARRAY(extra, e->local_extras.items, e->local_extras.n_items) { + print_extra(e, extra, "extra", status); + + if (extra->cmdline) + if (!strextend(&t2, " ", extra->cmdline)) + return log_oom(); } /* Don't print the combined cmdline if it's same as options. */ @@ -1787,19 +2051,19 @@ static int print_cmdline(const BootEntry *e) { } static int json_addon( - BootEntryAddon *addon, - const char *addon_str, + const BootEntryExtra *extra, + const char *extra_str, sd_json_variant **array) { int r; - assert(addon); - assert(addon_str); + assert(extra); + assert(extra_str); r = sd_json_variant_append_arraybo( array, - SD_JSON_BUILD_PAIR_STRING(addon_str, addon->location), - SD_JSON_BUILD_PAIR_STRING("options", addon->cmdline)); + SD_JSON_BUILD_PAIR_STRING(extra_str, extra->location), + JSON_BUILD_PAIR_STRING_NON_EMPTY("options", extra->cmdline)); if (r < 0) return log_oom(); @@ -1823,20 +2087,31 @@ static int json_cmdline( return log_oom(); } - FOREACH_ARRAY(addon, e->global_addons->items, e->global_addons->n_items) { - r = json_addon(addon, "globalAddon", &addons_array); + /* NB: these JSON fields are kinda obsolete, we want the more generic 'extra' ones to be used. */ + FOREACH_ARRAY(extra, e->global_extras->items, e->global_extras->n_items) { + if (extra->type != BOOT_ENTRY_ADDON) + continue; + + r = json_addon(extra, "globalAddon", &addons_array); if (r < 0) return r; - if (!strextend(&combined_cmdline, " ", addon->cmdline)) - return log_oom(); + + if (extra->cmdline) + if (!strextend(&combined_cmdline, " ", extra->cmdline)) + return log_oom(); } - FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) { - r = json_addon(addon, "localAddon", &addons_array); + FOREACH_ARRAY(extra, e->local_extras.items, e->local_extras.n_items) { + if (extra->type != BOOT_ENTRY_ADDON) + continue; + + r = json_addon(extra, "localAddon", &addons_array); if (r < 0) return r; - if (!strextend(&combined_cmdline, " ", addon->cmdline)) - return log_oom(); + + if (extra->cmdline) + if (!strextend(&combined_cmdline, " ", extra->cmdline)) + return log_oom(); } r = sd_json_variant_merge_objectbo( @@ -1947,7 +2222,7 @@ int show_boot_entry( *s, &status); - r = print_cmdline(e); + r = print_cmdline(e, &status); if (r < 0) return r; @@ -2027,6 +2302,24 @@ int boot_entry_to_json(const BootConfig *c, size_t i, sd_json_variant **ret) { if (r < 0) return log_oom(); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *jextras = NULL; + FOREACH_ARRAY(extra, e->global_extras->items, e->global_extras->n_items) { + r = sd_json_variant_append_arrayb(&jextras, SD_JSON_BUILD_STRING(extra->location)); + if (r < 0) + return log_oom(); + } + FOREACH_ARRAY(extra, e->local_extras.items, e->local_extras.n_items) { + r = sd_json_variant_append_arrayb(&jextras, SD_JSON_BUILD_STRING(extra->location)); + if (r < 0) + return log_oom(); + } + + r = sd_json_variant_merge_objectbo( + &v, + SD_JSON_BUILD_PAIR_CONDITION(!!jextras, "extras", SD_JSON_BUILD_VARIANT(jextras))); + if (r < 0) + return log_oom(); + *ret = TAKE_PTR(v); return 1; } diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index f325dcae25154..0560bfb3a4ac2 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -4,6 +4,8 @@ #include "shared-forward.h" +#include "../fundamental/bootspec.h" /* IWYU pragma: export */ + typedef enum BootEntryType { BOOT_ENTRY_TYPE1, /* Boot Loader Specification Type #1 entries: *.conf files */ BOOT_ENTRY_TYPE2, /* Boot Loader Specification Type #2 entries: *.efi files (UKIs) */ @@ -20,25 +22,35 @@ typedef enum BootEntrySource { _BOOT_ENTRY_SOURCE_INVALID = -EINVAL, } BootEntrySource; -typedef struct BootEntryAddon { +typedef enum BootEntryExtraType { + BOOT_ENTRY_ADDON, + BOOT_ENTRY_CONFEXT, + BOOT_ENTRY_SYSEXT, + BOOT_ENTRY_CREDENTIAL, + _BOOT_ENTRY_EXTRA_TYPE_MAX, + _BOOT_ENTRY_EXTRA_TYPE_INVALID = -EINVAL, +} BootEntryExtraType; + +typedef struct BootEntryExtra { + BootEntryExtraType type; char *location; - char *cmdline; -} BootEntryAddon; + char *cmdline; /* only for BOOT_ENTRY_ADDON */ +} BootEntryExtra; -typedef struct BootEntryAddons { - BootEntryAddon *items; +typedef struct BootEntryExtras { + BootEntryExtra *items; size_t n_items; -} BootEntryAddons; +} BootEntryExtras; 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; @@ -46,8 +58,8 @@ typedef struct BootEntry { char *machine_id; char *architecture; char **options; - BootEntryAddons local_addons; - const BootEntryAddons *global_addons; /* Backpointer into the BootConfig; we don't own this here */ + BootEntryExtras local_extras; + const BootEntryExtras *global_extras; /* Backpointer into the BootConfig; we don't own this here */ char *kernel; /* linux is #defined to 1, yikes! */ char *efi; char *uki; @@ -84,7 +96,7 @@ typedef struct BootConfig { BootEntry *entries; size_t n_entries; - BootEntryAddons global_addons[_BOOT_ENTRY_SOURCE_MAX]; + BootEntryExtras global_extras[_BOOT_ENTRY_SOURCE_MAX]; ssize_t default_entry; ssize_t selected_entry; @@ -116,6 +128,16 @@ static inline const BootEntry* boot_config_default_entry(const BootConfig *confi return config->entries + config->default_entry; } +static inline const BootEntry* boot_config_selected_entry(const BootConfig *config) { + assert(config); + + if (config->selected_entry < 0) + return NULL; + + assert((size_t) config->selected_entry < config->n_entries); + return config->entries + config->selected_entry; +} + void boot_config_free(BootConfig *config); int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path); @@ -152,3 +174,7 @@ int show_boot_entries( int boot_filename_extract_tries(const char *fname, char **ret_stripped, unsigned *ret_tries_left, unsigned *ret_tries_done); int boot_entry_to_json(const BootConfig *c, size_t i, sd_json_variant **ret); + +int pe_find_uki_sections(int fd, const char *path, unsigned profile, char **ret_osrelease, char **ret_profile, char **ret_cmdline); + +int bootspec_extract_osrelease(const char *text, char **ret_good_name, char **ret_good_version, char **ret_good_sort_key, char **ret_os_id, char **ret_os_version_id, char **ret_image_id, char **ret_image_version); diff --git a/src/shared/bpf-compat.h b/src/shared/bpf-compat.h deleted file mode 100644 index 7f76ee87a0781..0000000000000 --- a/src/shared/bpf-compat.h +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include - -/* libbpf has been moving quickly. - * They added new symbols in the 0.x versions and shortly after removed - * deprecated symbols in 1.0. - * We only need bpf_map_create and libbpf_probe_bpf_prog_type so we work - * around the incompatibility here by: - * - declaring both symbols, and looking for either depending on the libbpf - * so version we found - * - having helpers that automatically use the appropriate version behind the - * new API for easy cleanup later - * - * The advantage of doing this instead of only looking for the symbols declared at - * compile time is that we can then load either the old or the new symbols at runtime - * regardless of the version we were compiled with */ - -/* declare the struct for libbpf <= 0.6.0 -- it causes no harm on newer versions */ -struct bpf_map_create_opts; - -/* new symbols available from 0.7.0. - * We need the symbols here: - * - after bpf_map_create_opts struct has been defined for older libbpf - * - before the compat static inline helpers that use them. - * When removing this file move these back to bpf-dlopen.h */ -extern int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *); -extern struct bpf_map* (*sym_bpf_object__next_map)(const struct bpf_object *obj, const struct bpf_map *map); - -/* compat symbols removed in libbpf 1.0 */ -extern int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags); - -/* helpers to use the available variant behind new API */ -static inline int compat_bpf_map_create( - enum bpf_map_type map_type, - const char *map_name, - __u32 key_size, - __u32 value_size, - __u32 max_entries, - const struct bpf_map_create_opts *opts) { - - if (sym_bpf_map_create) - return sym_bpf_map_create(map_type, map_name, key_size, - value_size, max_entries, opts); - - return sym_bpf_create_map(map_type, key_size, value_size, max_entries, - 0 /* opts->map_flags, but opts is always NULL for us so skip build dependency on the type */); -} diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c deleted file mode 100644 index 0e7632eb343e8..0000000000000 --- a/src/shared/bpf-dlopen.c +++ /dev/null @@ -1,213 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "bpf-dlopen.h" -#include "dlfcn-util.h" -#include "errno-util.h" -#include "initrd-util.h" -#include "log.h" - -#if HAVE_LIBBPF - -/* libbpf changed types of function prototypes around, so we need to disable some type checking for older - * libbpf. We consider everything older than 0.7 too old for accurate type checks. */ -#if defined(__LIBBPF_CURRENT_VERSION_GEQ) -#if __LIBBPF_CURRENT_VERSION_GEQ(0, 7) -#define MODERN_LIBBPF 1 -#endif -#endif -#if !defined(MODERN_LIBBPF) -#define MODERN_LIBBPF 0 -#endif - -DLSYM_PROTOTYPE(bpf_link__destroy) = NULL; -DLSYM_PROTOTYPE(bpf_link__fd) = NULL; -DLSYM_PROTOTYPE(bpf_link__open) = NULL; -DLSYM_PROTOTYPE(bpf_link__pin) = NULL; -DLSYM_PROTOTYPE(bpf_map__fd) = NULL; -DLSYM_PROTOTYPE(bpf_map__name) = NULL; -DLSYM_PROTOTYPE(bpf_map__set_inner_map_fd) = NULL; -DLSYM_PROTOTYPE(bpf_map__set_max_entries) = NULL; -DLSYM_PROTOTYPE(bpf_map__set_pin_path) = NULL; -DLSYM_PROTOTYPE(bpf_map_delete_elem) = NULL; -DLSYM_PROTOTYPE(bpf_map_get_fd_by_id) = NULL; -DLSYM_PROTOTYPE(bpf_map_lookup_elem) = NULL; -DLSYM_PROTOTYPE(bpf_map_update_elem) = NULL; -DLSYM_PROTOTYPE(bpf_object__attach_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__destroy_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__detach_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__load_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__name) = NULL; -DLSYM_PROTOTYPE(bpf_object__open_skeleton) = NULL; -DLSYM_PROTOTYPE(bpf_object__pin_maps) = NULL; -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); - -/* compat symbols removed in libbpf 1.0 */ -int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags); - -_printf_(2,0) -static int bpf_print_func(enum libbpf_print_level level, const char *fmt, va_list ap) { -#if !LOG_TRACE - /* libbpf logs a lot of details at its debug level, which we don't need to see. */ - if (level == LIBBPF_DEBUG) - return 0; -#endif - /* All other levels are downgraded to LOG_DEBUG */ - - /* errno is used here, on the assumption that if the log message uses %m, errno will be set to - * something useful. Otherwise, it shouldn't matter, we may pass 0 or some bogus value. */ - return log_internalv(LOG_DEBUG, errno, NULL, 0, NULL, fmt, ap); -} - -int dlopen_bpf_full(int log_level) { - static int cached = 0; - int r; - - if (cached != 0) - return cached; - - ELF_NOTE_DLOPEN("bpf", - "Support firewalling and sandboxing with BPF", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, - "libbpf.so.1", "libbpf.so.0"); - - DISABLE_WARNING_DEPRECATED_DECLARATIONS; - - _cleanup_(dlclosep) void *dl = NULL; - r = dlopen_safe("libbpf.so.1", &dl, /* reterr_dlerror= */ NULL); - if (r < 0) { - /* libbpf < 1.0.0 (we rely on 0.1.0+) provide most symbols we care about, but - * unfortunately not all until 0.7.0. See bpf-compat.h for more details. - * Once we consider we can assume 0.7+ is present we can just use the same symbol - * list for both files, and when we assume 1.0+ is present we can remove this dlopen */ - const char *dle = NULL; - r = dlopen_safe("libbpf.so.0", &dl, &dle); - if (r < 0) { - log_full_errno(in_initrd() ? LOG_DEBUG : log_level, r, - "Neither libbpf.so.1 nor libbpf.so.0 are installed, cgroup BPF features disabled: %s", dle ?: STRERROR(r)); - return (cached = -EOPNOTSUPP); /* turn into recognizable error */ - } - - log_debug("Loaded 'libbpf.so.0' via dlopen()"); - - /* symbols deprecated in 1.0 we use as compat */ - r = dlsym_many_or_warn( - dl, LOG_DEBUG, -#if MODERN_LIBBPF - /* Don't exist anymore in new libbpf, hence cannot type check them */ - DLSYM_ARG_FORCE(bpf_create_map) -#else - DLSYM_ARG(bpf_create_map) -#endif - ); - - /* NB: we don't try to load bpf_object__next_map() on old versions */ - } else { - log_debug("Loaded 'libbpf.so.1' via dlopen()"); - - /* symbols available from 0.7.0 */ - r = dlsym_many_or_warn( - dl, LOG_DEBUG, -#if MODERN_LIBBPF - DLSYM_ARG(bpf_map_create), - DLSYM_ARG(bpf_object__next_map) -#else - /* These symbols did not exist in old libbpf, hence we cannot type check them */ - DLSYM_ARG_FORCE(bpf_map_create), - DLSYM_ARG_FORCE(bpf_object__next_map) -#endif - ); - } - if (r < 0) - return cached = log_full_errno(log_level, r, "Failed to load libbpf symbols, cgroup BPF features disabled: %m"); - - r = dlsym_many_or_warn( - dl, LOG_DEBUG, - DLSYM_ARG(bpf_link__destroy), - DLSYM_ARG(bpf_link__fd), - DLSYM_ARG(bpf_link__open), - DLSYM_ARG(bpf_link__pin), - DLSYM_ARG(bpf_map__fd), - DLSYM_ARG(bpf_map__name), - DLSYM_ARG(bpf_map__set_inner_map_fd), - DLSYM_ARG(bpf_map__set_max_entries), - DLSYM_ARG(bpf_map__set_pin_path), - DLSYM_ARG(bpf_map_delete_elem), - DLSYM_ARG(bpf_map_get_fd_by_id), - DLSYM_ARG(bpf_map_lookup_elem), - DLSYM_ARG(bpf_map_update_elem), - DLSYM_ARG(bpf_object__attach_skeleton), - DLSYM_ARG(bpf_object__destroy_skeleton), - DLSYM_ARG(bpf_object__detach_skeleton), - DLSYM_ARG(bpf_object__load_skeleton), - DLSYM_ARG(bpf_object__name), - DLSYM_ARG(bpf_object__open_skeleton), - DLSYM_ARG(bpf_object__pin_maps), -#if MODERN_LIBBPF - DLSYM_ARG(bpf_program__attach), - DLSYM_ARG(bpf_program__attach_cgroup), - DLSYM_ARG(bpf_program__attach_lsm), -#else - /* libbpf added a "const" to function parameters where it should not have, ignore this type incompatibility */ - DLSYM_ARG_FORCE(bpf_program__attach), - DLSYM_ARG_FORCE(bpf_program__attach_cgroup), - DLSYM_ARG_FORCE(bpf_program__attach_lsm), -#endif - DLSYM_ARG(bpf_program__name), - DLSYM_ARG(libbpf_get_error), - DLSYM_ARG(libbpf_set_print), - DLSYM_ARG(ring_buffer__epoll_fd), - DLSYM_ARG(ring_buffer__free), - DLSYM_ARG(ring_buffer__new), - DLSYM_ARG(ring_buffer__poll)); - if (r < 0) - return cached = log_full_errno(log_level, r, "Failed to load libbpf symbols, cgroup BPF features disabled: %m"); - - /* We set the print helper unconditionally. Otherwise libbpf will emit not useful log messages. */ - (void) sym_libbpf_set_print(bpf_print_func); - - REENABLE_WARNING; - - bpf_dl = TAKE_PTR(dl); - - return cached = true; -} - -int bpf_get_error_translated(const void *ptr) { - int r; - - r = sym_libbpf_get_error(ptr); - - switch (r) { - case -524: - /* Workaround for kernel bug, BPF returns an internal error instead of translating it, until - * it is fixed: - * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/errno.h?h=v6.9&id=a38297e3fb012ddfa7ce0321a7e5a8daeb1872b6#n27 - */ - return -EOPNOTSUPP; - default: - return r; - } -} - -#else - -int dlopen_bpf_full(int log_level) { - return log_once_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), - "libbpf support is not compiled in, cgroup BPF features disabled."); -} -#endif diff --git a/src/shared/bpf-dlopen.h b/src/shared/bpf-dlopen.h deleted file mode 100644 index ecae890035436..0000000000000 --- a/src/shared/bpf-dlopen.h +++ /dev/null @@ -1,57 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include - -#if HAVE_LIBBPF - -#include /* IWYU pragma: export */ -#include /* IWYU pragma: export */ - -#include "bpf-compat.h" /* IWYU pragma: export */ -#include "dlfcn-util.h" -#include "shared-forward.h" - -extern DLSYM_PROTOTYPE(bpf_link__destroy); -extern DLSYM_PROTOTYPE(bpf_link__fd); -extern DLSYM_PROTOTYPE(bpf_link__open); -extern DLSYM_PROTOTYPE(bpf_link__pin); -extern DLSYM_PROTOTYPE(bpf_map__fd); -extern DLSYM_PROTOTYPE(bpf_map__name); -extern DLSYM_PROTOTYPE(bpf_map__set_inner_map_fd); -extern DLSYM_PROTOTYPE(bpf_map__set_max_entries); -extern DLSYM_PROTOTYPE(bpf_map__set_pin_path); -extern DLSYM_PROTOTYPE(bpf_map_delete_elem); -extern DLSYM_PROTOTYPE(bpf_map_get_fd_by_id); -extern DLSYM_PROTOTYPE(bpf_map_lookup_elem); -extern DLSYM_PROTOTYPE(bpf_map_update_elem); -/* The *_skeleton APIs are autogenerated by bpftool, the targets can be found - * in ./build/src/core/bpf/socket-bind/socket-bind.skel.h */ -extern DLSYM_PROTOTYPE(bpf_object__attach_skeleton); -extern DLSYM_PROTOTYPE(bpf_object__destroy_skeleton); -extern DLSYM_PROTOTYPE(bpf_object__detach_skeleton); -extern DLSYM_PROTOTYPE(bpf_object__load_skeleton); -extern DLSYM_PROTOTYPE(bpf_object__name); -extern DLSYM_PROTOTYPE(bpf_object__open_skeleton); -extern DLSYM_PROTOTYPE(bpf_object__pin_maps); -extern DLSYM_PROTOTYPE(bpf_program__attach); -extern DLSYM_PROTOTYPE(bpf_program__attach_cgroup); -extern DLSYM_PROTOTYPE(bpf_program__attach_lsm); -extern DLSYM_PROTOTYPE(bpf_program__name); -extern DLSYM_PROTOTYPE(libbpf_set_print); -extern DLSYM_PROTOTYPE(ring_buffer__epoll_fd); -extern DLSYM_PROTOTYPE(ring_buffer__free); -extern DLSYM_PROTOTYPE(ring_buffer__new); -extern DLSYM_PROTOTYPE(ring_buffer__poll); - -/* libbpf sometimes returns error codes that make sense only in the kernel, like 524 for EOPNOTSUPP. Use - * this helper instead of libbpf_get_error() to ensure some of the known ones are translated into errnos - * we understand. */ -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); -} diff --git a/src/shared/bpf-link.c b/src/shared/bpf-link.c index 72d374c235997..4ae402625b8e8 100644 --- a/src/shared/bpf-link.c +++ b/src/shared/bpf-link.c @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "bpf-dlopen.h" +#include "bpf-util.h" #include "bpf-link.h" #include "serialize.h" @@ -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. */ @@ -19,6 +19,22 @@ bool bpf_can_link_program(struct bpf_program *prog) { return bpf_get_error_translated(link) == -EBADF; } +bool bpf_can_link_lsm_program(struct bpf_program *prog) { + _cleanup_(bpf_link_freep) struct bpf_link *link = NULL; + + assert(prog); + + if (dlopen_bpf(LOG_DEBUG) < 0) + return false; + + link = sym_bpf_program__attach_lsm(prog); + + /* If bpf_program__attach_lsm fails the resulting value stores libbpf error code instead of memory + * pointer. That is the case when the helper is called on architectures where BPF trampoline (hence + * BPF_LSM_MAC attach type) is not supported. */ + return bpf_get_error_translated(link) == 0; +} + int bpf_serialize_link(FILE *f, FDSet *fds, const char *key, struct bpf_link *link) { assert(key); diff --git a/src/shared/bpf-link.h b/src/shared/bpf-link.h index 79da1c2fea26c..4de95eb2e1ee3 100644 --- a/src/shared/bpf-link.h +++ b/src/shared/bpf-link.h @@ -8,6 +8,7 @@ #include "shared-forward.h" bool bpf_can_link_program(struct bpf_program *prog); +bool bpf_can_link_lsm_program(struct bpf_program *prog); int bpf_serialize_link(FILE *f, FDSet *fds, const char *key, struct bpf_link *link); 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/bpf-util.c b/src/shared/bpf-util.c new file mode 100644 index 0000000000000..92fc05f8fc8d5 --- /dev/null +++ b/src/shared/bpf-util.c @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dlopen.h" + +#include "bpf-util.h" +#include "dlfcn-util.h" +#include "initrd-util.h" +#include "log.h" +#include "strv.h" + +#if HAVE_LIBBPF + +/* libbpf changed types of function prototypes around, so we need to disable some type checking for older + * libbpf. We consider everything older than 0.7 too old for accurate type checks. */ +#if defined(__LIBBPF_CURRENT_VERSION_GEQ) +#if __LIBBPF_CURRENT_VERSION_GEQ(0, 7) +#define MODERN_LIBBPF 1 +#endif +#endif +#if !defined(MODERN_LIBBPF) +#define MODERN_LIBBPF 0 +#endif + +static DLSYM_PROTOTYPE(libbpf_get_error) = NULL; + +DLSYM_PROTOTYPE(bpf_create_map) = NULL; +DLSYM_PROTOTYPE(bpf_link__destroy) = NULL; +DLSYM_PROTOTYPE(bpf_link__fd) = NULL; +DLSYM_PROTOTYPE(bpf_link__open) = NULL; +DLSYM_PROTOTYPE(bpf_link__pin) = NULL; +DLSYM_PROTOTYPE(bpf_map__fd) = NULL; +DLSYM_PROTOTYPE(bpf_map__name) = NULL; +DLSYM_PROTOTYPE(bpf_map__set_inner_map_fd) = NULL; +DLSYM_PROTOTYPE(bpf_map__set_max_entries) = NULL; +DLSYM_PROTOTYPE(bpf_map__set_pin_path) = NULL; +DLSYM_PROTOTYPE(bpf_map_create) = NULL; +DLSYM_PROTOTYPE(bpf_map_delete_elem) = NULL; +DLSYM_PROTOTYPE(bpf_map_get_fd_by_id) = NULL; +DLSYM_PROTOTYPE(bpf_map_lookup_elem) = NULL; +DLSYM_PROTOTYPE(bpf_map_update_elem) = NULL; +DLSYM_PROTOTYPE(bpf_obj_get_info_by_fd) = NULL; +DLSYM_PROTOTYPE(bpf_object__attach_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__destroy_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__detach_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__load_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__name) = NULL; +DLSYM_PROTOTYPE(bpf_object__next_map) = NULL; +DLSYM_PROTOTYPE(bpf_object__open_skeleton) = NULL; +DLSYM_PROTOTYPE(bpf_object__pin_maps) = NULL; +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(bpf_program__set_autoload) = NULL; +static int missing_bpf_token_create(int bpffs_fd, struct bpf_token_create_opts *opts) { + return -ENOSYS; +} +DLSYM_PROTOTYPE(bpf_token_create) = missing_bpf_token_create; +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; + +_printf_(2,0) +static int bpf_print_func(enum libbpf_print_level level, const char *fmt, va_list ap) { +#if !LOG_TRACE + /* libbpf logs a lot of details at its debug level, which we don't need to see. */ + if (level == LIBBPF_DEBUG) + return 0; +#endif + /* All other levels are downgraded to LOG_DEBUG */ + + /* errno is used here, on the assumption that if the log message uses %m, errno will be set to + * something useful. Otherwise, it shouldn't matter, we may pass 0 or some bogus value. */ + return log_internalv(LOG_DEBUG, errno, NULL, 0, NULL, fmt, ap); +} + +int dlopen_bpf(int log_level) { + static void *bpf_dl = NULL; + static int cached = 0; + int r = -ENOENT; + + if (bpf_dl) + return 1; /* Already loaded */ + + if (cached < 0) + return cached; /* Already tried, and failed. */ + + BPF_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); + + DISABLE_WARNING_DEPRECATED_DECLARATIONS; + + FOREACH_STRING(soname, "libbpf.so.1", "libbpf.so.0") { + r = dlopen_many_sym_or_warn( + &bpf_dl, soname, LOG_DEBUG, + DLSYM_ARG(bpf_link__destroy), + DLSYM_ARG(bpf_link__fd), + DLSYM_ARG(bpf_link__open), + DLSYM_ARG(bpf_link__pin), + DLSYM_ARG(bpf_map__fd), + DLSYM_ARG(bpf_map__name), + DLSYM_ARG(bpf_map__set_inner_map_fd), + DLSYM_ARG(bpf_map__set_max_entries), + DLSYM_ARG(bpf_map__set_pin_path), + DLSYM_ARG(bpf_map_delete_elem), + DLSYM_ARG(bpf_map_get_fd_by_id), + DLSYM_ARG(bpf_map_lookup_elem), + DLSYM_ARG(bpf_map_update_elem), + DLSYM_ARG(bpf_obj_get_info_by_fd), + DLSYM_ARG(bpf_object__attach_skeleton), + DLSYM_ARG(bpf_object__destroy_skeleton), + DLSYM_ARG(bpf_object__detach_skeleton), + DLSYM_ARG(bpf_object__load_skeleton), + DLSYM_ARG(bpf_object__name), + DLSYM_ARG(bpf_object__open_skeleton), + DLSYM_ARG(bpf_object__pin_maps), +#if MODERN_LIBBPF + DLSYM_ARG(bpf_program__attach), + DLSYM_ARG(bpf_program__attach_cgroup), + DLSYM_ARG(bpf_program__attach_lsm), +#else + /* libbpf added a "const" to function parameters where it should not have, ignore this type incompatibility */ + DLSYM_ARG_FORCE(bpf_program__attach), + DLSYM_ARG_FORCE(bpf_program__attach_cgroup), + DLSYM_ARG_FORCE(bpf_program__attach_lsm), +#endif + DLSYM_ARG(bpf_program__name), + DLSYM_ARG(bpf_program__set_autoload), + DLSYM_ARG(libbpf_get_error), + DLSYM_ARG(libbpf_set_print), + DLSYM_ARG(ring_buffer__epoll_fd), + DLSYM_ARG(ring_buffer__free), + DLSYM_ARG(ring_buffer__new), + DLSYM_ARG(ring_buffer__poll)); + if (r >= 0) + break; + } + REENABLE_WARNING; + if (r < 0) + return cached = log_full_errno(in_initrd() ? LOG_DEBUG : log_level, r, + "Neither libbpf.so.1 nor libbpf.so.0 are installed, cgroup BPF features disabled."); + + /* Version-specific symbols: bpf_create_map exists only in libbpf < 1.0; bpf_map_create and + * bpf_object__next_map only in 0.7+. bpf_token_create only in 1.5+. Unresolved prototypes keep + * their initializers (NULL, or a fallback returning -ENOSYS for bpf_token_create). */ + DLSYM_OPTIONAL(bpf_dl, bpf_create_map); + DLSYM_OPTIONAL(bpf_dl, bpf_map_create); + DLSYM_OPTIONAL(bpf_dl, bpf_object__next_map); + DLSYM_OPTIONAL(bpf_dl, bpf_token_create); + + /* We set the print helper unconditionally. Otherwise libbpf will emit not useful log messages. */ + (void) sym_libbpf_set_print(bpf_print_func); + + return 1; +} + +int bpf_get_error_translated(const void *ptr) { + int r; + + r = sym_libbpf_get_error(ptr); + + switch (r) { + case -524: + /* Workaround for kernel bug, BPF returns an internal error instead of translating it, until + * it is fixed: + * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/errno.h?h=v6.9&id=a38297e3fb012ddfa7ce0321a7e5a8daeb1872b6#n27 + */ + return -EOPNOTSUPP; + default: + return r; + } +} + +#else + +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."); +} +#endif diff --git a/src/shared/bpf-util.h b/src/shared/bpf-util.h new file mode 100644 index 0000000000000..faf7349b8a079 --- /dev/null +++ b/src/shared/bpf-util.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "sd-dlopen.h" + +#if HAVE_LIBBPF + +#include /* IWYU pragma: export */ +#include /* IWYU pragma: export */ + +#include "dlfcn-util.h" +#include "shared-forward.h" + +/* Always redeclare these so DLSYM_PROTOTYPE's typeof() resolves regardless of libbpf version; + * suppress the warning when the libbpf headers already declare them. + * - bpf_map_create, bpf_object__next_map: available since libbpf 0.7 + * - bpf_token_create: available since libbpf 1.5 + * - bpf_create_map: removed in libbpf 1.0 + */ +DISABLE_WARNING_REDUNDANT_DECLS; +/* NOLINTBEGIN(readability-redundant-declaration) */ +struct bpf_map_create_opts; +struct bpf_token_create_opts; +extern int bpf_create_map(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags); +extern int bpf_map_create(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *); +extern struct bpf_map* bpf_object__next_map(const struct bpf_object *obj, const struct bpf_map *map); +extern int bpf_token_create(int bpffs_fd, struct bpf_token_create_opts *opts); +/* NOLINTEND(readability-redundant-declaration) */ +REENABLE_WARNING; + +extern DLSYM_PROTOTYPE(bpf_create_map); +extern DLSYM_PROTOTYPE(bpf_link__destroy); +extern DLSYM_PROTOTYPE(bpf_link__fd); +extern DLSYM_PROTOTYPE(bpf_link__open); +extern DLSYM_PROTOTYPE(bpf_link__pin); +extern DLSYM_PROTOTYPE(bpf_map__fd); +extern DLSYM_PROTOTYPE(bpf_map__name); +extern DLSYM_PROTOTYPE(bpf_map__set_inner_map_fd); +extern DLSYM_PROTOTYPE(bpf_map__set_max_entries); +extern DLSYM_PROTOTYPE(bpf_map__set_pin_path); +extern DLSYM_PROTOTYPE(bpf_map_create); +extern DLSYM_PROTOTYPE(bpf_map_delete_elem); +extern DLSYM_PROTOTYPE(bpf_map_get_fd_by_id); +extern DLSYM_PROTOTYPE(bpf_map_lookup_elem); +extern DLSYM_PROTOTYPE(bpf_map_update_elem); +extern DLSYM_PROTOTYPE(bpf_obj_get_info_by_fd); +/* The *_skeleton APIs are autogenerated by bpftool, the targets can be found + * in ./build/src/core/bpf/socket-bind/socket-bind.skel.h */ +extern DLSYM_PROTOTYPE(bpf_object__attach_skeleton); +extern DLSYM_PROTOTYPE(bpf_object__destroy_skeleton); +extern DLSYM_PROTOTYPE(bpf_object__detach_skeleton); +extern DLSYM_PROTOTYPE(bpf_object__load_skeleton); +extern DLSYM_PROTOTYPE(bpf_object__name); +extern DLSYM_PROTOTYPE(bpf_object__next_map); +extern DLSYM_PROTOTYPE(bpf_object__open_skeleton); +extern DLSYM_PROTOTYPE(bpf_object__pin_maps); +extern DLSYM_PROTOTYPE(bpf_program__attach); +extern DLSYM_PROTOTYPE(bpf_program__attach_cgroup); +extern DLSYM_PROTOTYPE(bpf_program__attach_lsm); +extern DLSYM_PROTOTYPE(bpf_program__name); +extern DLSYM_PROTOTYPE(bpf_program__set_autoload); +extern DLSYM_PROTOTYPE(bpf_token_create); +extern DLSYM_PROTOTYPE(libbpf_set_print); +extern DLSYM_PROTOTYPE(ring_buffer__epoll_fd); +extern DLSYM_PROTOTYPE(ring_buffer__free); +extern DLSYM_PROTOTYPE(ring_buffer__new); +extern DLSYM_PROTOTYPE(ring_buffer__poll); + +/* Helper that uses the available variant behind the new API. */ +static inline int compat_bpf_map_create( + enum bpf_map_type map_type, + const char *map_name, + __u32 key_size, + __u32 value_size, + __u32 max_entries, + const struct bpf_map_create_opts *opts) { + + if (sym_bpf_map_create) + return sym_bpf_map_create(map_type, map_name, key_size, + value_size, max_entries, opts); + + return sym_bpf_create_map(map_type, key_size, value_size, max_entries, + 0 /* opts->map_flags, but opts is always NULL for us so skip build dependency on the type */); +} + +/* libbpf sometimes returns error codes that make sense only in the kernel, like 524 for EOPNOTSUPP. Use + * this helper instead of libbpf_get_error() to ensure some of the known ones are translated into errnos + * we understand. */ +int bpf_get_error_translated(const void *ptr); + +#define BPF_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("bpf", \ + "Support firewalling and sandboxing with BPF", \ + priority, \ + "libbpf.so.1", "libbpf.so.0") + +#define DLOPEN_BPF(log_level, priority) \ + ({ \ + BPF_NOTE(priority); \ + dlopen_bpf(log_level); \ + }) +#else +#define DLOPEN_BPF(log_level, priority) dlopen_bpf(log_level) +#endif + +int dlopen_bpf(int log_level); diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 9bd0a74c5fe83..4833149005f15 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -23,6 +23,7 @@ #include "rm-rf.h" #include "sparse-endian.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-util.h" #include "time-util.h" @@ -95,15 +96,20 @@ int btrfs_subvol_get_read_only_fd(int fd) { return !!(flags & BTRFS_SUBVOL_RDONLY); } -int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { +int btrfs_get_block_device_at_full(int dir_fd, const char *path, uint64_t *ret_devid, char **ret_path, dev_t *ret) { struct btrfs_ioctl_fs_info_args fsi = {}; _cleanup_close_ int fd = -EBADF; uint64_t id; int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - assert(path); - assert(ret); + /* + * Returns: + * ret_devid - the device id in the filesystem for the returned block device + * ret_path - the path to the returned block device + * ret - the returned block device + */ + + assert(wildcard_fd_is_valid(dir_fd)); fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); if (fd < 0) @@ -120,7 +126,12 @@ int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { /* We won't do this for btrfs RAID */ if (fsi.num_devices != 1) { - *ret = 0; + if (ret_devid) + *ret_devid = 0; + if (ret_path) + *ret_path = NULL; + if (ret) + *ret = 0; return 0; } @@ -129,6 +140,7 @@ int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { .devid = id, }; struct stat st; + _cleanup_free_ char *device_path = NULL; if (ioctl(fd, BTRFS_IOC_DEV_INFO, &di) < 0) { if (errno == ENODEV) @@ -149,13 +161,23 @@ 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; - *ret = st.st_rdev; + device_path = strdup((char*) di.path); + if (!device_path) + return -ENOMEM; + + if (ret_path) + *ret_path = TAKE_PTR(device_path); + if (ret) + *ret = st.st_rdev; + if (ret_devid) + *ret_devid = id; return 1; } @@ -863,33 +885,17 @@ int btrfs_qgroup_unassign(int fd, uint64_t child, uint64_t parent) { } static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol_id, BtrfsRemoveFlags flags) { - struct btrfs_ioctl_search_args args = { - .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, - - .key.min_objectid = BTRFS_FIRST_FREE_OBJECTID, - .key.max_objectid = BTRFS_LAST_FREE_OBJECTID, - - .key.min_type = BTRFS_ROOT_BACKREF_KEY, - .key.max_type = BTRFS_ROOT_BACKREF_KEY, - - .key.min_transid = 0, - .key.max_transid = UINT64_MAX, - }; - 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) @@ -907,8 +913,9 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol if (r == 0) /* Not a btrfs subvolume */ return -ENOTTY; - /* Before we try anything, let's see if 'user_subvol_rm_allowed' is enabled and we can just remove - * the dir directly */ + /* Before we try anything, attempt VFS rmdir(). For an empty subvolume btrfs_rmdir dispatches + * to btrfs_delete_subvolume; for the non-empty case it returns ENOTEMPTY and we fall through + * to the destroy ioctl below. */ if (unlinkat(fd, subvolume, AT_REMOVEDIR) >= 0) goto finish; @@ -918,51 +925,63 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol return r; } - /* First, try to remove the subvolume. If it happens to be - * already empty, this will just work. */ + /* Try the destroy ioctl. Unlike unlinkat() above, this drops the entire subvolume tree, so + * regular files inside don't matter; it only returns ENOTEMPTY when there are nested + * subvolumes (BTRFS_ROOT_REF_KEY entries), which we then handle below. */ strncpy(vol_args.name, subvolume, sizeof(vol_args.name)-1); - if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &vol_args) >= 0) - goto finish; - if (!(flags & BTRFS_REMOVE_RECURSIVE) || errno != ENOTEMPTY) - return -errno; - - /* OK, the subvolume is not empty, let's look for child - * subvolumes, and remove them, first */ - - args.key.min_offset = args.key.max_offset = subvol_id; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - struct btrfs_ioctl_search_header sh; - const void *body; - - args.key.nr_items = 256; - if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) - return -errno; - - if (args.key.nr_items <= 0) + int destroy_errno = 0; + for (;;) { + if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &vol_args) >= 0) + goto finish; + destroy_errno = errno; + + /* Without CAP_SYS_ADMIN the destroy ioctl is rejected for a read-only subvolume: with + * EROFS when 'user_subvol_rm_allowed' is set (btrfs_permission() fails the inode + * MAY_WRITE check), or with EPERM/EACCES when it is not (the privilege check fires + * first). In either case, if the subvolume is read-only, clear the RDONLY flag (only + * inode ownership is required) and retry. This lets the retry succeed when removal is + * permitted, and otherwise leaves the subvolume writable so the caller (e.g. rm_rf()) + * can empty and rmdir() it without privileges. */ + if (made_writable || !ERRNO_IS_FS_WRITE_REFUSED(destroy_errno)) break; - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(sh, body, args) { - _cleanup_free_ char *p = NULL; + r = btrfs_subvol_get_read_only_fd(subvol_fd); + if (r < 0) + return r; + if (r == 0) + break; /* Not read-only; clearing the flag would not help. */ - btrfs_ioctl_search_args_set(&args, &sh); + r = btrfs_subvol_set_read_only_fd(subvol_fd, false); + if (r < 0) + return r; + made_writable = true; + } + if (!(flags & BTRFS_REMOVE_RECURSIVE) || destroy_errno != ENOTEMPTY) + return -destroy_errno; + + /* OK, there are nested subvolumes — enumerate and recurse into them, then retry. + * BTRFS_IOC_GET_SUBVOL_ROOTREF and BTRFS_IOC_INO_LOOKUP_USER (kernel 4.18+) enumerate child + * subvolumes without requiring CAP_SYS_ADMIN in the initial user namespace, unlike the older + * BTRFS_IOC_TREE_SEARCH ioctl. */ + + struct btrfs_ioctl_get_subvol_rootref_args rootref_args = {}; + + for (;;) { + /* BTRFS_IOC_GET_SUBVOL_ROOTREF returns up to BTRFS_MAX_ROOTREF_BUFFER_NUM entries per + * call. If more are available, it returns -EOVERFLOW with num_items filled in and + * min_treeid advanced so we can resume on the next iteration. */ + int ioctl_ret = ioctl(subvol_fd, BTRFS_IOC_GET_SUBVOL_ROOTREF, &rootref_args); + if (ioctl_ret < 0 && errno != EOVERFLOW) + return -errno; - if (sh.type != BTRFS_ROOT_BACKREF_KEY) - continue; - if (sh.offset != subvol_id) - continue; + for (uint8_t i = 0; i < rootref_args.num_items; i++) { + uint64_t child_subvol_id = rootref_args.rootref[i].treeid; - const struct btrfs_root_ref *ref = body; - p = memdup_suffix0((char*) ref + sizeof(struct btrfs_root_ref), le64toh(ref->name_len)); - if (!p) - return -ENOMEM; - - struct btrfs_ioctl_ino_lookup_args ino_args = { - .treeid = subvol_id, - .objectid = htole64(ref->dirid), + struct btrfs_ioctl_ino_lookup_user_args lookup_args = { + .dirid = rootref_args.rootref[i].dirid, + .treeid = child_subvol_id, }; - - if (ioctl(fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0) + if (ioctl(subvol_fd, BTRFS_IOC_INO_LOOKUP_USER, &lookup_args) < 0) return -errno; if (!made_writable) { @@ -973,29 +992,26 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol made_writable = true; } - if (isempty(ino_args.name)) - /* Subvolume is in the top-level - * directory of the subvolume. */ - r = subvol_remove_children(subvol_fd, p, sh.objectid, flags); + if (isempty(lookup_args.path)) + /* Subvolume is in the top-level directory of the subvolume. */ + r = subvol_remove_children(subvol_fd, lookup_args.name, child_subvol_id, flags); else { _cleanup_close_ int child_fd = -EBADF; - /* Subvolume is somewhere further down, - * hence we need to open the + /* Subvolume is somewhere further down, hence we need to open the * containing directory first */ - child_fd = openat(subvol_fd, ino_args.name, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); + child_fd = openat(subvol_fd, lookup_args.path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); if (child_fd < 0) return -errno; - r = subvol_remove_children(child_fd, p, sh.objectid, flags); + r = subvol_remove_children(child_fd, lookup_args.name, child_subvol_id, flags); } if (r < 0) return r; } - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) + if (ioctl_ret >= 0) break; } @@ -1017,7 +1033,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; @@ -1226,19 +1242,6 @@ static int subvol_snapshot_children( uint64_t old_subvol_id, BtrfsSnapshotFlags flags) { - struct btrfs_ioctl_search_args args = { - .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, - - .key.min_objectid = BTRFS_FIRST_FREE_OBJECTID, - .key.max_objectid = BTRFS_LAST_FREE_OBJECTID, - - .key.min_type = BTRFS_ROOT_BACKREF_KEY, - .key.max_type = BTRFS_ROOT_BACKREF_KEY, - - .key.min_transid = 0, - .key.max_transid = UINT64_MAX, - }; - struct btrfs_ioctl_vol_args_v2 vol_args = { .flags = flags & BTRFS_SNAPSHOT_READ_ONLY ? BTRFS_SUBVOL_RDONLY : 0, .fd = old_fd, @@ -1296,50 +1299,36 @@ static int subvol_snapshot_children( return flags & BTRFS_SNAPSHOT_LOCK_BSD ? TAKE_FD(subvolume_fd) : 0; } - args.key.min_offset = args.key.max_offset = old_subvol_id; + /* Enumerate child subvolumes via BTRFS_IOC_GET_SUBVOL_ROOTREF + BTRFS_IOC_INO_LOOKUP_USER + * (kernel 4.18+), neither of which requires CAP_SYS_ADMIN, unlike BTRFS_IOC_TREE_SEARCH. */ - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - struct btrfs_ioctl_search_header sh; - const void *body; + struct btrfs_ioctl_get_subvol_rootref_args rootref_args = {}; - args.key.nr_items = 256; - if (ioctl(old_fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) + for (;;) { + /* BTRFS_IOC_GET_SUBVOL_ROOTREF returns up to BTRFS_MAX_ROOTREF_BUFFER_NUM entries per + * call. If more are available, it returns -EOVERFLOW with num_items filled in and + * min_treeid advanced so we can resume on the next iteration. */ + int ioctl_ret = ioctl(old_fd, BTRFS_IOC_GET_SUBVOL_ROOTREF, &rootref_args); + if (ioctl_ret < 0 && errno != EOVERFLOW) return -errno; - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(sh, body, args) { - _cleanup_free_ char *p = NULL, *c = NULL, *np = NULL; + for (uint8_t i = 0; i < rootref_args.num_items; i++) { + _cleanup_free_ char *c = NULL, *np = NULL; _cleanup_close_ int old_child_fd = -EBADF, new_child_fd = -EBADF; - - btrfs_ioctl_search_args_set(&args, &sh); - - if (sh.type != BTRFS_ROOT_BACKREF_KEY) - continue; - - /* Avoid finding the source subvolume a second time */ - if (sh.offset != old_subvol_id) - continue; + uint64_t child_subvol_id = rootref_args.rootref[i].treeid; /* Avoid running into loops if the new subvolume is below the old one. */ - if (sh.objectid == new_subvol_id) + if (child_subvol_id == new_subvol_id) continue; - const struct btrfs_root_ref *ref = body; - p = memdup_suffix0((char*) ref + sizeof(struct btrfs_root_ref), le64toh(ref->name_len)); - if (!p) - return -ENOMEM; - - struct btrfs_ioctl_ino_lookup_args ino_args = { - .treeid = old_subvol_id, - .objectid = htole64(ref->dirid), + struct btrfs_ioctl_ino_lookup_user_args lookup_args = { + .dirid = rootref_args.rootref[i].dirid, + .treeid = child_subvol_id, }; - - if (ioctl(old_fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0) + if (ioctl(old_fd, BTRFS_IOC_INO_LOOKUP_USER, &lookup_args) < 0) return -errno; - c = path_join(ino_args.name, p); + c = path_join(lookup_args.path, lookup_args.name); if (!c) return -ENOMEM; @@ -1347,7 +1336,7 @@ static int subvol_snapshot_children( if (old_child_fd < 0) return -errno; - np = path_join(subvolume, ino_args.name); + np = path_join(subvolume, lookup_args.path); if (!np) return -ENOMEM; @@ -1372,7 +1361,7 @@ static int subvol_snapshot_children( /* When btrfs clones the subvolumes, child subvolumes appear as empty * directories. Remove them, so that we can create a new snapshot in their place */ - if (unlinkat(new_child_fd, p, AT_REMOVEDIR) < 0) { + if (unlinkat(new_child_fd, lookup_args.name, AT_REMOVEDIR) < 0) { int k = -errno; if (flags & BTRFS_SNAPSHOT_READ_ONLY) @@ -1381,7 +1370,7 @@ static int subvol_snapshot_children( return k; } - r = subvol_snapshot_children(old_child_fd, new_child_fd, p, sh.objectid, + r = subvol_snapshot_children(old_child_fd, new_child_fd, lookup_args.name, child_subvol_id, flags & ~(BTRFS_SNAPSHOT_FALLBACK_COPY|BTRFS_SNAPSHOT_LOCK_BSD)); /* Restore the readonly flag */ @@ -1397,8 +1386,7 @@ static int subvol_snapshot_children( return r; } - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) + if (ioctl_ret >= 0) break; } @@ -1430,7 +1418,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; @@ -2168,3 +2156,50 @@ int btrfs_get_file_physical_offset_fd(int fd, uint64_t *ret) { return -ENODATA; } + +int btrfs_replace(int fdmntpnt, uint64_t device_id, const char *target) { + struct btrfs_ioctl_dev_replace_args replace = { + .cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START, + .result = UINT64_MAX, + .start = { + .srcdevid = device_id, + .cont_reading_from_srcdev_mode = BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS, + }, + }; + + assert(fdmntpnt >= 0); + assert(target); + + if (strlen(target) >= sizeof(replace.start.tgtdev_name)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Path to the btrfs replace target is too long"); + strncpy((char *)replace.start.tgtdev_name, target, sizeof(replace.start.tgtdev_name)); + + if (ioctl(fdmntpnt, BTRFS_IOC_DEV_REPLACE, &replace) < 0) + return -errno; + + switch (replace.result) { + case BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR: + break; + case BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED: + return log_debug_errno(SYNTHETIC_ERRNO(ECANCELED), "btrfs replace was not started"); + case BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED: + return log_debug_errno(SYNTHETIC_ERRNO(EALREADY), "btrfs replace was already started on this device"); + case BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS: + return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "btrfs scrub is in progress"); + default: + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "An unknown btrfs error status occurred"); + } + + return 0; +} + +int btrfs_resize_max(int fdmntpnt, uint64_t devid) { + struct btrfs_ioctl_vol_args args = {}; + + assert(fdmntpnt >= 0); + + assert_cc(STRLEN(":max") + DECIMAL_STR_MAX(uint64_t) + 1 <= sizeof(args.name)); + xsprintf(args.name, "%" PRIu64 ":max", devid); + + return RET_NERRNO(ioctl(fdmntpnt, BTRFS_IOC_RESIZE, &args)); +} diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h index 55fb07656a59d..da7c0481a6ab9 100644 --- a/src/shared/btrfs-util.h +++ b/src/shared/btrfs-util.h @@ -3,9 +3,10 @@ #include "sd-id128.h" -#include "btrfs.h" /* IWYU pragma: export */ #include "shared-forward.h" +#include "../basic/btrfs-util.h" /* IWYU pragma: export */ + typedef struct BtrfsSubvolInfo { uint64_t subvol_id; usec_t otime; /* creation time */ @@ -49,12 +50,15 @@ static inline int btrfs_is_subvol(const char *path) { return btrfs_is_subvol_at(AT_FDCWD, path); } -int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret); +int btrfs_get_block_device_at_full(int dir_fd, const char *path, uint64_t *ret_devid, char **ret_path, dev_t *ret); +static inline int btrfs_get_block_device_at(int dir_fd, const char *path, dev_t *ret) { + return btrfs_get_block_device_at_full(dir_fd, path, NULL, NULL, ret); +} 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); @@ -133,3 +137,6 @@ bool btrfs_might_be_subvol(const struct stat *st) _pure_; int btrfs_forget_device(const char *path); int btrfs_get_file_physical_offset_fd(int fd, uint64_t *ret); + +int btrfs_replace(int fdmntpnt, uint64_t device_id, const char *target); +int btrfs_resize_max(int fdmntpnt, uint64_t devid); diff --git a/src/shared/bus-map-properties.c b/src/shared/bus-map-properties.c index 49b1e44030e73..d695ca4fc2a20 100644 --- a/src/shared/bus-map-properties.c +++ b/src/shared/bus-map-properties.c @@ -173,21 +173,29 @@ int bus_message_map_all_properties( if (r < 0) return bus_log_parse_error_debug(r); - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents); - if (r < 0) - return bus_log_parse_error_debug(r); - - v = (uint8_t *)userdata + prop->offset; - if (map[i].set) - r = prop->set(sd_bus_message_get_bus(m), member, m, reterr_error, v); - else - r = map_basic(m, flags, v); - if (r < 0) - return bus_log_parse_error_debug(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error_debug(r); + if (prop->signature && !streq(contents, prop->signature)) { + log_debug("Unexpected type for property '%s': expected '%s', got '%s', ignoring.", + member, prop->signature, contents); + r = sd_bus_message_skip(m, "v"); + if (r < 0) + return bus_log_parse_error_debug(r); + } else { + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents); + if (r < 0) + return bus_log_parse_error_debug(r); + + v = (uint8_t*) userdata + prop->offset; + if (prop->set) + r = prop->set(sd_bus_message_get_bus(m), member, m, reterr_error, v); + else + r = map_basic(m, flags, v); + if (r < 0) + return bus_log_parse_error_debug(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error_debug(r); + } } else { r = sd_bus_message_skip(m, "v"); if (r < 0) diff --git a/src/shared/bus-polkit.c b/src/shared/bus-polkit.c index f19db2a6eb008..73fae1140ba7d 100644 --- a/src/shared/bus-polkit.c +++ b/src/shared/bus-polkit.c @@ -17,15 +17,18 @@ #include "strv.h" #include "varlink-util.h" -static int bus_message_check_good_user(sd_bus_message *m, uid_t good_user) { +static int bus_message_check_good_user(sd_bus_message *m, uid_t good_user, bool *ret_admin) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; uid_t sender_uid; int r; assert(m); - if (good_user == UID_INVALID) + if (good_user == UID_INVALID) { + if (ret_admin) + *ret_admin = false; return false; + } r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds); if (r < 0) @@ -38,6 +41,9 @@ static int bus_message_check_good_user(sd_bus_message *m, uid_t good_user) { if (r < 0) return r; + if (ret_admin) + *ret_admin = sender_uid == 0; + return sender_uid == good_user; } @@ -121,7 +127,7 @@ int bus_test_polkit( /* Tests non-interactively! */ - r = bus_message_check_good_user(call, good_user); + r = bus_message_check_good_user(call, good_user, /* ret_admin= */ NULL); if (r != 0) return r; @@ -178,6 +184,7 @@ int bus_test_polkit( typedef struct AsyncPolkitQueryAction { char *action; char **details; + bool admin; /* true when the action was authorized by auth_admin/auth_admin_keep (i.e.: by a privileged user) */ LIST_FIELDS(struct AsyncPolkitQueryAction, authorized); } AsyncPolkitQueryAction; @@ -250,13 +257,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); @@ -315,8 +319,41 @@ static int async_polkit_read_reply(sd_bus_message *reply, AsyncPolkitQuery *q) { } r = sd_bus_message_enter_container(reply, 'r', "bba{ss}"); - if (r >= 0) - r = sd_bus_message_read(reply, "bb", &authorized, &challenge); + if (r < 0) + return r; + r = sd_bus_message_read(reply, "bb", &authorized, &challenge); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'a', "{ss}"); + if (r < 0) + return r; + + for (;;) { + const char *k, *v; + + r = sd_bus_message_enter_container(reply, 'e', "ss"); + if (r < 0) + return r; + if (r == 0) + break; + + r = sd_bus_message_read(reply, "ss", &k, &v); + if (r < 0) + return r; + if (r > 0 && streq(k, "polkit.result") && STR_IN_SET(v, "auth_admin_keep", "auth_admin")) + a->admin = true; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(reply); if (r < 0) return r; @@ -349,7 +386,7 @@ static int async_polkit_process_reply(sd_bus_message *reply, AsyncPolkitQuery *q if (r < 0) return r; - /* Now, let's dispatch the original message a second time be re-enqueing. This will then traverse the + /* Now, let's dispatch the original message a second time be re-enqueuing. This will then traverse the * whole message processing again, and thus re-validating and re-retrieving the "userdata" field * again. * @@ -414,14 +451,22 @@ static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_e static bool async_polkit_query_have_action( AsyncPolkitQuery *q, const char *action, - const char **details) { + const char **details, + /* Reports whether the subject authenticated as admin, instead of a regular user */ + bool *ret_admin) { assert(q); assert(action); LIST_FOREACH(authorized, a, q->authorized_actions) - if (streq(a->action, action) && strv_equal(a->details, (char**) details)) + if (streq(a->action, action) && strv_equal(a->details, (char**) details)) { + if (ret_admin) + *ret_admin = a->admin; return true; + } + + if (ret_admin) + *ret_admin = false; return false; } @@ -431,13 +476,19 @@ static int async_polkit_query_check_action( const char *action, const char **details, PolkitFlags flags, + bool *ret_admin, sd_bus_error *reterr_error) { + bool admin = false; /* Not privileged until proven otherwise */ + assert(q); assert(action); - if (async_polkit_query_have_action(q, action, details)) + if (async_polkit_query_have_action(q, action, details, &admin)) { + if (ret_admin) + *ret_admin = admin; return 1; /* Allow! */ + } if (q->error_action && streq(q->error_action->action, action)) return sd_bus_error_copy(reterr_error, &q->error); @@ -445,13 +496,21 @@ static int async_polkit_query_check_action( if (q->denied_action && streq(q->denied_action->action, action)) return -EACCES; /* Deny! */ - if (q->absent_action) - return FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW) ? 1 /* Allow! */ : -EACCES /* Deny! */; + if (q->absent_action) { + if (!FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW)) + return -EACCES; + if (ret_admin) + *ret_admin = admin; + return 1; + } /* Also deny if we've got an auth. failure for a previous action */ if (q->denied_action || q->error_action) return -EBUSY; + if (ret_admin) + *ret_admin = admin; + return 0; /* no reply yet */ } #endif @@ -485,9 +544,9 @@ static int async_polkit_query_check_action( * processed and the polkit action to verify. * 2. bus_verify_polkit_async() checks the registry for an existing query object associated with the * message. Let's assume this is the first call, so it finds nothing. - * 3. A new AsyncPolkitQuery object is created and an async. D-Bus call to polkit is made. The + * 3. A new AsyncPolkitQuery object is created and an async D-Bus call to polkit is made. The * function then returns 0. The method handler returns 1 to tell sd-bus that the processing of - * the message has been interrupted. + * the message has been interrupted. * 4. (Later) A reply from polkit is received and async_polkit_callback() is called. * 5. async_polkit_callback() reads the reply and stores its result in the passed query. * 6. async_polkit_callback() enqueues the original message again. @@ -544,8 +603,10 @@ int bus_verify_polkit_async_full( uid_t good_user, PolkitFlags flags, Hashmap **registry, + bool *ret_admin, sd_bus_error *reterr_error) { + bool admin = false; /* Not privileged until proven otherwise */ int r; assert(call); @@ -554,9 +615,12 @@ int bus_verify_polkit_async_full( log_debug("Trying to acquire polkit authentication for '%s'.", action); - r = bus_message_check_good_user(call, good_user); - if (r != 0) + r = bus_message_check_good_user(call, good_user, &admin); + if (r != 0) { + if (r > 0 && ret_admin) + *ret_admin = admin; return r; + } #if ENABLE_POLKIT _cleanup_(async_polkit_query_unrefp) AsyncPolkitQuery *q = NULL; @@ -565,8 +629,10 @@ int bus_verify_polkit_async_full( /* This is a repeated invocation of this function, hence let's check if we've already got * a response from polkit for this action */ if (q) { - r = async_polkit_query_check_action(q, action, details, flags, reterr_error); + r = async_polkit_query_check_action(q, action, details, flags, &admin, reterr_error); if (r != 0) { + if (r > 0 && ret_admin) + *ret_admin = admin; log_debug("Found matching previous polkit authentication for '%s'.", action); return r; } @@ -578,8 +644,11 @@ int bus_verify_polkit_async_full( r = sd_bus_query_sender_privilege(call, /* capability= */ -1); if (r < 0) return r; - if (r > 0) + if (r > 0) { + if (ret_admin) + *ret_admin = true; return 1; + } #if ENABLE_POLKIT } @@ -632,25 +701,38 @@ int bus_verify_polkit_async_full( TAKE_PTR(q); + if (ret_admin) + *ret_admin = admin; + return 0; #else - return FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW) ? 1 : -EACCES; + if (!FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW)) + return -EACCES; + if (ret_admin) + *ret_admin = admin; + return 1; #endif } -static int varlink_check_good_user(sd_varlink *link, uid_t good_user) { +static int varlink_check_good_user(sd_varlink *link, uid_t good_user, bool *ret_admin) { int r; assert(link); - if (good_user == UID_INVALID) + if (good_user == UID_INVALID) { + if (ret_admin) + *ret_admin = false; return false; + } uid_t peer_uid; r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) return r; + if (ret_admin) + *ret_admin = peer_uid == 0; + return good_user == peer_uid; } @@ -765,8 +847,10 @@ int varlink_verify_polkit_async_full( const char **details, uid_t good_user, PolkitFlags flags, - Hashmap **registry) { + Hashmap **registry, + bool *ret_admin) { + bool admin = false; /* Not privileged until proven otherwise */ int r; assert(link); @@ -777,16 +861,22 @@ int varlink_verify_polkit_async_full( /* This is the same as bus_verify_polkit_async_full(), but authenticates the peer of a varlink * connection rather than the sender of a bus message. */ - r = varlink_check_good_user(link, good_user); - if (r != 0) + r = varlink_check_good_user(link, good_user, &admin); + if (r != 0) { + if (r > 0 && ret_admin) + *ret_admin = admin; return r; + } #if ENABLE_POLKIT if (!FLAGS_SET(flags, POLKIT_ALWAYS_QUERY)) { #endif r = varlink_check_peer_privilege(link); - if (r != 0) + if (r != 0) { + if (r > 0 && ret_admin) + *ret_admin = true; return r; + } #if ENABLE_POLKIT } @@ -797,7 +887,7 @@ int varlink_verify_polkit_async_full( * a response from polkit for this action */ if (q) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = async_polkit_query_check_action(q, action, details, flags, &error); + r = async_polkit_query_check_action(q, action, details, flags, &admin, &error); if (r != 0) log_debug("Found matching previous polkit authentication for '%s'.", action); if (r < 0) { @@ -811,8 +901,11 @@ int varlink_verify_polkit_async_full( return r; } - if (r > 0) + if (r > 0) { + if (ret_admin) + *ret_admin = admin; return r; + } } _cleanup_(sd_bus_unrefp) sd_bus *mybus = NULL; @@ -874,13 +967,20 @@ int varlink_verify_polkit_async_full( TAKE_PTR(q); + if (ret_admin) + *ret_admin = admin; + return 0; #else - return FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW) ? 1 : -EACCES; + if (!FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW)) + return -EACCES; + if (ret_admin) + *ret_admin = admin; + return 1; #endif } -bool varlink_has_polkit_action(sd_varlink *link, const char *action, const char **details, Hashmap **registry) { +bool varlink_has_polkit_action(sd_varlink *link, const char *action, const char **details, Hashmap **registry, bool *ret_admin) { assert(link); assert(action); assert(registry); @@ -892,8 +992,10 @@ bool varlink_has_polkit_action(sd_varlink *link, const char *action, const char if (!q) return false; - return async_polkit_query_have_action(q, action, details); + return async_polkit_query_have_action(q, action, details, ret_admin); #else + if (ret_admin) + *ret_admin = false; /* Not privileged until proven otherwise */ return false; #endif } diff --git a/src/shared/bus-polkit.h b/src/shared/bus-polkit.h index 9ce8de4f0227b..887ed2a55bc88 100644 --- a/src/shared/bus-polkit.h +++ b/src/shared/bus-polkit.h @@ -16,14 +16,14 @@ typedef enum PolkitFlags { int bus_test_polkit(sd_bus_message *call, const char *action, const char **details, uid_t good_user, bool *ret_challenge, sd_bus_error *reterr_error); -int bus_verify_polkit_async_full(sd_bus_message *call, const char *action, const char **details, uid_t good_user, PolkitFlags flags, Hashmap **registry, sd_bus_error *reterr_error); +int bus_verify_polkit_async_full(sd_bus_message *call, const char *action, const char **details, uid_t good_user, PolkitFlags flags, Hashmap **registry, bool *ret_admin, sd_bus_error *reterr_error); static inline int bus_verify_polkit_async(sd_bus_message *call, const char *action, const char **details, Hashmap **registry, sd_bus_error *reterr_error) { - return bus_verify_polkit_async_full(call, action, details, UID_INVALID, 0, registry, reterr_error); + return bus_verify_polkit_async_full(call, action, details, UID_INVALID, 0, registry, NULL, reterr_error); } -int varlink_verify_polkit_async_full(sd_varlink *link, sd_bus *bus, const char *action, const char **details, uid_t good_user, PolkitFlags flags, Hashmap **registry); +int varlink_verify_polkit_async_full(sd_varlink *link, sd_bus *bus, const char *action, const char **details, uid_t good_user, PolkitFlags flags, Hashmap **registry, bool *ret_admin); static inline int varlink_verify_polkit_async(sd_varlink *link, sd_bus *bus, const char *action, const char **details, Hashmap **registry) { - return varlink_verify_polkit_async_full(link, bus, action, details, UID_INVALID, 0, registry); + return varlink_verify_polkit_async_full(link, bus, action, details, UID_INVALID, 0, registry, NULL); } /* A sd_json_dispatch_field initializer that makes sure the allowInteractiveAuthentication boolean field we want for @@ -43,4 +43,4 @@ extern const sd_json_dispatch_field dispatch_table_polkit_only[]; SD_VARLINK_FIELD_COMMENT("Controls whether interactive authentication (via polkit) shall be allowed. If unspecified defaults to false."), \ SD_VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE) -bool varlink_has_polkit_action(sd_varlink *link, const char *action, const char **details, Hashmap **registry); +bool varlink_has_polkit_action(sd_varlink *link, const char *action, const char **details, Hashmap **registry, bool *ret_admin); 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..48a48f71b2170 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -224,6 +224,58 @@ static int bus_append_strv_colon(sd_bus_message *m, const char *field, const cha return bus_append_strv_full(m, field, eq, ":" WHITESPACE, EXTRACT_UNQUOTE); } +static int bus_append_xattr(sd_bus_message *m, const char *field, const char *eq) { + int r; + + assert(m); + assert(field); + + /* Sends an extended attribute assignment of the form "name=value" as an a(ss) array of one + * name/value pair (or as an empty array if the value is empty, which resets the list). */ + + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_basic(m, 's', field); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'v', "a(ss)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(ss)"); + if (r < 0) + return bus_log_create_error(r); + + if (!isempty(eq)) { + _cleanup_free_ char *name = NULL, *value = NULL; + + r = split_pair(eq, "=", &name, &value); + if (r < 0) + return log_error_errno(r, "Failed to parse extended attribute expression '%s': %m", eq); + + r = sd_bus_message_append(m, "(ss)", name, value); + 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); + + r = sd_bus_message_close_container(m); + 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 1; +} + static int bus_append_byte_array(sd_bus_message *m, const char *field, const void *buf, size_t n) { int r; @@ -1175,6 +1227,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); @@ -2142,7 +2196,7 @@ static int bus_append_protect_hostname(sd_bus_message *m, const char *field, con int r; /* The command-line field is called "ProtectHostname". We also accept "ProtectHostnameEx" as the - * field name for backward compatibility. We set ProtectHostame or ProtectHostnameEx. */ + * field name for backward compatibility. We set ProtectHostname or ProtectHostnameEx. */ r = parse_boolean(eq); if (r >= 0) @@ -2382,7 +2436,10 @@ static const BusProperty cgroup_properties[] = { { "ManagedOOMSwap", bus_append_string }, { "ManagedOOMMemoryPressure", bus_append_string }, { "ManagedOOMPreference", bus_append_string }, + { "OOMRules", bus_append_strv }, { "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 }, @@ -2399,6 +2456,7 @@ static const BusProperty cgroup_properties[] = { { "StartupAllowedCPUs", bus_append_parse_cpu_set }, { "AllowedMemoryNodes", bus_append_parse_cpu_set }, { "StartupAllowedMemoryNodes", bus_append_parse_cpu_set }, + { "CPUSetPartition", bus_append_string }, { "DisableControllers", bus_append_strv }, { "Delegate", bus_append_parse_delegate }, { "MemoryMin", bus_append_parse_resource_limit }, @@ -2421,6 +2479,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 }, @@ -2680,12 +2740,14 @@ static const BusProperty service_properties[] = { { "TimeoutStartFailureMode", bus_append_string }, { "TimeoutStopFailureMode", bus_append_string }, { "FileDescriptorStorePreserve", bus_append_string }, + { "LUOSession", bus_append_strv }, { "PermissionsStartOnly", bus_append_parse_boolean }, { "RootDirectoryStartOnly", bus_append_parse_boolean }, { "RemainAfterExit", bus_append_parse_boolean }, { "GuessMainPID", bus_append_parse_boolean }, { "RestartSec", bus_append_parse_sec_rename }, { "RestartMaxDelaySec", bus_append_parse_sec_rename }, + { "RestartRandomizedDelaySec", bus_append_parse_sec_rename }, { "TimeoutStartSec", bus_append_parse_sec_rename }, { "TimeoutStopSec", bus_append_parse_sec_rename }, { "TimeoutAbortSec", bus_append_parse_sec_rename }, @@ -2776,6 +2838,9 @@ static const BusProperty socket_properties[] = { { "Timestamping", bus_append_string }, { "DeferTrigger", bus_append_string }, { "Symlinks", bus_append_strv }, + { "XAttrEntryPoint", bus_append_xattr }, + { "XAttrListen", bus_append_xattr }, + { "XAttrAccept", bus_append_xattr }, { "SocketProtocol", bus_append_parse_ip_protocol }, { "ListenStream", bus_append_listen }, { "ListenDatagram", bus_append_listen }, diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c index dc13ae0f77994..225d811d19280 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) @@ -1115,7 +1127,7 @@ static int find_matching_component( assert(val); - /* Finds the *earliest* matching time specified by one of the CalendarCompoment items in chain c. + /* Finds the *earliest* matching time specified by one of the CalendarComponent items in chain c. * If no matches can be found, returns -ENOENT. * Otherwise, updates *val to the matching time. 1 is returned if *val was changed, 0 otherwise. */ @@ -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/color-util.c b/src/shared/color-util.c index 1912785954720..1d30732147f83 100644 --- a/src/shared/color-util.c +++ b/src/shared/color-util.c @@ -1,8 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "color-util.h" +#include "math-util.h" void rgb_to_hsv(double r, double g, double b, double *ret_h, double *ret_s, double *ret_v) { @@ -11,8 +10,8 @@ void rgb_to_hsv(double r, double g, double b, assert(g >= 0 && g <= 1); assert(b >= 0 && b <= 1); - double max_color = fmax(r, fmax(g, b)); - double min_color = fmin(r, fmin(g, b)); + double max_color = MAX(r, MAX(g, b)); + double min_color = MIN(r, MIN(g, b)); double delta = max_color - min_color; if (ret_v) @@ -32,13 +31,15 @@ void rgb_to_hsv(double r, double g, double b, if (ret_h) { if (delta > 0) { if (r >= max_color) - *ret_h = 60 * fmod((g - b) / delta, 6); + *ret_h = 60 * (g - b) / delta; else if (g >= max_color) *ret_h = 60 * (((b - r) / delta) + 2); - else if (b >= max_color) + else /* b >= max_color */ *ret_h = 60 * (((r - g) / delta) + 4); - *ret_h = fmod(*ret_h, 360); + /* The r-max branch produces (-60, 60); fold the negative half up. */ + if (*ret_h < 0) + *ret_h += 360; } else *ret_h = NAN; } @@ -49,29 +50,33 @@ void hsv_to_rgb(double h, double s, double v, double c, x, m, r, g, b; + assert(h >= 0 && h <= 360); assert(s >= 0 && s <= 100); assert(v >= 0 && v <= 100); assert(ret_r); assert(ret_g); assert(ret_b); - h = fmod(h, 360); c = (s / 100.0) * (v / 100.0); - x = c * (1 - fabs(fmod(h / 60.0, 2) - 1)); m = (v / 100) - c; - if (h >= 0 && h < 60) - r = c, g = x, b = 0.0; - else if (h >= 60 && h < 120) - r = x, g = c, b = 0.0; - else if (h >= 120 && h < 180) - r = 0.0, g = c, b = x; - else if (h >= 180 && h < 240) - r = 0.0, g = x, b = c; - else if (h >= 240 && h < 300) - r = x, g = 0.0, b = c; - else - r = c, g = 0.0, b = x; + /* Split h into sector index k ∈ [0, 6] and fractional offset f ∈ [0, 1) within the sector. + * Within each sector exactly one of {r, g, b} equals c, one equals 0, and the third (x) + * ramps linearly between them — ascending in even sectors, descending in odd. h == 360 is + * the cyclic boundary equivalent to h == 0, and maps to sector 0. */ + double seg = h / 60.0; + int k = (int) seg % 6; + double f = seg - (int) seg; + x = c * (k & 1 ? 1.0 - f : f); + + switch (k) { + case 0: r = c; g = x; b = 0.0; break; + case 1: r = x; g = c; b = 0.0; break; + case 2: r = 0.0; g = c; b = x; break; + case 3: r = 0.0; g = x; b = c; break; + case 4: r = x; g = 0.0; b = c; break; + default: r = c; g = 0.0; b = x; break; + } *ret_r = (uint8_t) ((r + m) * 255); *ret_g = (uint8_t) ((g + m) * 255); diff --git a/src/shared/condition.c b/src/shared/condition.c index 903662edf1a8f..2b4a29ed5c60d 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -33,7 +33,9 @@ #include "fileio.h" #include "fs-util.h" #include "glob-util.h" +#include "hmac.h" #include "hostname-setup.h" +#include "hostname-util.h" #include "id128-util.h" #include "ima-util.h" #include "initrd-util.h" @@ -62,6 +64,7 @@ #include "tomoyo-util.h" #include "tpm2-util.h" #include "uid-classification.h" +#include "unaligned.h" #include "user-util.h" #include "virt.h" @@ -82,11 +85,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; } @@ -316,6 +317,37 @@ static int condition_test_osrelease(Condition *c, char **env) { return true; } +static int condition_test_machine_tag(Condition *c, char **env) { + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_MACHINE_TAG); + + _cleanup_free_ char *tags = NULL; + r = parse_env_file( + /* f= */ NULL, etc_machine_info(), + "TAGS", &tags); + if (r < 0 && r != -ENOENT) { + log_debug_errno(r, "Failed to read /etc/machine-info, ignoring: %m"); + return false; + } + + /* Silently ignore invalid tags, matching the Tags D-Bus property */ + _cleanup_strv_free_ char **l = NULL; + r = machine_tags_from_string(tags, /* graceful= */ true, &l); + if (r < 0) { + log_debug_errno(r, "Failed to parse machine tags '%s', ignoring: %m", tags); + return false; + } + + STRV_FOREACH(i, l) + if (fnmatch(c->parameter, *i, /* flags= */ 0) == 0) + return true; + + return false; +} + static int condition_test_memory(Condition *c, char **env) { CompareOperator operator; uint64_t m, k; @@ -398,8 +430,7 @@ static int condition_test_user(Condition *c, char **env) { if (streq(username, c->parameter)) return 1; - const char *u = c->parameter; - r = get_user_creds(&u, &id, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING); + r = get_user_creds(c->parameter, USER_CREDS_ALLOW_MISSING, NULL, &id, NULL, NULL, NULL); if (r < 0) return 0; @@ -692,6 +723,82 @@ static int condition_test_host(Condition *c, char **env) { return true; } +static uint32_t condition_fraction_value(sd_id128_t machine_id, const char *text) { + /* Maps (machine ID, text) to a value uniformly distributed over [0, UINT32_MAX], via + * HMAC-SHA256 keyed by the machine ID over 'text'. The machine ID keys the hash, so each + * machine lands at a stable but unpredictable point; 'text' selects the subset, so that + * distinct rollouts (different texts) pick independent subsets of a fleet. */ + assert(text); + + uint8_t res[SHA256_DIGEST_SIZE]; + hmac_sha256(&machine_id, sizeof(machine_id), text, strlen(text), res); + + return unaligned_read_le32(res); +} + +static int condition_test_fraction(Condition *c, char **env) { + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_FRACTION); + + /* Expected syntax: "[TAG ]PERCENT". The percentage is mandatory; the optional leading tag is used as + * a hash salt, so that distinct staged rollouts select independent subsets of a fleet. The condition + * is true for approximately PERCENT of all machines — on a given machine when its derived value + * falls below the threshold. This is useful for staged rollouts: hand the same unit to a whole fleet + * and only a fraction of it will be enabled, with each machine deciding locally and stably (no + * coordination required). */ + + const char *p = c->parameter; + _cleanup_free_ char *first = NULL, *second = NULL; + + r = extract_first_word(&p, &first, /* separators=*/ NULL, /* flags= */ 0); + if (r < 0) + return log_debug_errno(r, "Failed to parse ConditionFraction=%s: %m", c->parameter); + if (r == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "ConditionFraction= value is empty."); + + r = extract_first_word(&p, &second, /* separators= */ NULL, /* flags= */ 0); + if (r < 0) + return log_debug_errno(r, "Failed to parse ConditionFraction=%s: %m", c->parameter); + + const char *tag, *percent; + if (r == 0) { + /* A single field: it's the percentage, with no tag. */ + tag = NULL; + percent = first; + } else if (isempty(p)) { + /* Two fields: TAG followed by PERCENT. */ + tag = first; + percent = second; + } else + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "ConditionFraction=%s has trailing garbage.", c->parameter); + + int permyriad = parse_permyriad(percent); + if (permyriad < 0) + return log_debug_errno(permyriad, "Failed to parse percentage in ConditionFraction=%s: %m", c->parameter); + if (permyriad == 0) + return false; /* 0% → matches no machine */ + if (permyriad >= 10000) + return true; /* 100% → matches every machine */ + + /* Implicitly prefix the tag with a fixed namespace string, so that an absent or empty tag + * still yields a well-defined, non-trivial value (never the HMAC of the empty string), and + * so this value space is domain-separated from other uses of the machine ID. */ + _cleanup_free_ char *text = strjoin("systemd-fraction-", strempty(tag)); + if (!text) + return log_oom_debug(); + + sd_id128_t mid; + r = sd_id128_get_machine(&mid); + if (r < 0) + return log_debug_errno(r, "Failed to get machine ID for ConditionFraction=: %m"); + + return condition_fraction_value(mid, text) < UINT32_SCALE_FROM_PERMYRIAD(permyriad); +} + static int condition_test_ac_power(Condition *c, char **env) { int r; @@ -741,6 +848,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; } @@ -1252,6 +1361,7 @@ int condition_test(Condition *c, char **env) { [CONDITION_SECURITY] = condition_test_security, [CONDITION_CAPABILITY] = condition_test_capability, [CONDITION_HOST] = condition_test_host, + [CONDITION_FRACTION] = condition_test_fraction, [CONDITION_AC_POWER] = condition_test_ac_power, [CONDITION_ARCHITECTURE] = condition_test_architecture, [CONDITION_FIRMWARE] = condition_test_firmware, @@ -1265,6 +1375,7 @@ int condition_test(Condition *c, char **env) { [CONDITION_ENVIRONMENT] = condition_test_environment, [CONDITION_CPU_FEATURE] = condition_test_cpufeature, [CONDITION_OS_RELEASE] = condition_test_osrelease, + [CONDITION_MACHINE_TAG] = condition_test_machine_tag, [CONDITION_MEMORY_PRESSURE] = condition_test_psi, [CONDITION_CPU_PRESSURE] = condition_test_psi, [CONDITION_IO_PRESSURE] = condition_test_psi, @@ -1364,6 +1475,7 @@ static const char* const _condition_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_FIRMWARE] = "ConditionFirmware", [CONDITION_VIRTUALIZATION] = "ConditionVirtualization", [CONDITION_HOST] = "ConditionHost", + [CONDITION_FRACTION] = "ConditionFraction", [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine", [CONDITION_VERSION] = "ConditionVersion", [CONDITION_CREDENTIAL] = "ConditionCredential", @@ -1391,6 +1503,7 @@ static const char* const _condition_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_ENVIRONMENT] = "ConditionEnvironment", [CONDITION_CPU_FEATURE] = "ConditionCPUFeature", [CONDITION_OS_RELEASE] = "ConditionOSRelease", + [CONDITION_MACHINE_TAG] = "ConditionMachineTag", [CONDITION_MEMORY_PRESSURE] = "ConditionMemoryPressure", [CONDITION_CPU_PRESSURE] = "ConditionCPUPressure", [CONDITION_IO_PRESSURE] = "ConditionIOPressure", @@ -1420,6 +1533,7 @@ static const char* const _assert_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_FIRMWARE] = "AssertFirmware", [CONDITION_VIRTUALIZATION] = "AssertVirtualization", [CONDITION_HOST] = "AssertHost", + [CONDITION_FRACTION] = "AssertFraction", [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine", [CONDITION_VERSION] = "AssertVersion", [CONDITION_CREDENTIAL] = "AssertCredential", @@ -1447,6 +1561,7 @@ static const char* const _assert_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_ENVIRONMENT] = "AssertEnvironment", [CONDITION_CPU_FEATURE] = "AssertCPUFeature", [CONDITION_OS_RELEASE] = "AssertOSRelease", + [CONDITION_MACHINE_TAG] = "AssertMachineTag", [CONDITION_MEMORY_PRESSURE] = "AssertMemoryPressure", [CONDITION_CPU_PRESSURE] = "AssertCPUPressure", [CONDITION_IO_PRESSURE] = "AssertIOPressure", diff --git a/src/shared/condition.h b/src/shared/condition.h index d2274522f4f3b..1f5c33284c3fe 100644 --- a/src/shared/condition.h +++ b/src/shared/condition.h @@ -9,6 +9,7 @@ typedef enum ConditionType { CONDITION_FIRMWARE, CONDITION_VIRTUALIZATION, CONDITION_HOST, + CONDITION_FRACTION, CONDITION_KERNEL_COMMAND_LINE, CONDITION_VERSION, CONDITION_CREDENTIAL, @@ -20,6 +21,7 @@ typedef enum ConditionType { CONDITION_ENVIRONMENT, CONDITION_CPU_FEATURE, CONDITION_OS_RELEASE, + CONDITION_MACHINE_TAG, CONDITION_MEMORY_PRESSURE, CONDITION_CPU_PRESSURE, CONDITION_IO_PRESSURE, diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index 1d1d8a10ccd0b..f9f113eadc025 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) @@ -600,7 +603,7 @@ static int normalize_root_fd(const char *root, int *root_fd, int *ret_opened_fd) /* Normalizes a root dir specification: if root_fd is already valid, keep it. Otherwise, we open the * specified dir */ - if (*root_fd >= 0 || IN_SET(*root_fd, AT_FDCWD, XAT_FDROOT)) { + if (wildcard_fd_is_valid(*root_fd)) { *ret_opened_fd = -EBADF; return 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..f61b16caee0b4 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -12,7 +12,7 @@ #include #include "alloc-util.h" -#include "btrfs.h" +#include "btrfs-util.h" #include "chattr-util.h" #include "copy.h" #include "dirent-util.h" @@ -25,6 +25,7 @@ #include "mountpoint-util.h" #include "nulstr-util.h" #include "path-util.h" +#include "recurse-dir.h" #include "rm-rf.h" #include "selinux-util.h" #include "signal-util.h" @@ -71,31 +72,6 @@ static ssize_t try_copy_file_range( return r; } -enum { - FD_IS_NO_PIPE, - FD_IS_BLOCKING_PIPE, - FD_IS_NONBLOCKING_PIPE, -}; - -static int fd_is_nonblock_pipe(int fd) { - struct stat st; - int flags; - - /* Checks whether the specified file descriptor refers to a pipe, and if so if O_NONBLOCK is set. */ - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISFIFO(st.st_mode)) - return FD_IS_NO_PIPE; - - flags = fcntl(fd, F_GETFL); - if (flags < 0) - return -errno; - - return FLAGS_SET(flags, O_NONBLOCK) ? FD_IS_NONBLOCKING_PIPE : FD_IS_BLOCKING_PIPE; -} - static int look_for_signals(CopyFlags copy_flags) { int r; @@ -164,9 +140,9 @@ int copy_bytes_full( void *userdata) { _cleanup_close_ int fdf_opened = -EBADF, fdt_opened = -EBADF; - bool try_cfr = true, try_sendfile = true, try_splice = true; + bool try_cfr = true, try_sendfile = true; uint64_t copied_total = 0; - int r, nonblock_pipe = -1; + int r; assert(fdf >= 0); assert(fdt >= 0); @@ -193,16 +169,25 @@ 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 the current file offset, unless we already sought to 0 anyway. */ + 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) @@ -357,7 +342,7 @@ int copy_bytes_full( } } - /* First try copy_file_range(), unless we already tried */ + /* First, try copy_file_range(), unless we already tried */ if (try_cfr) { n = try_copy_file_range(fdf, NULL, fdt, NULL, m, 0u); if (n < 0) { @@ -378,13 +363,13 @@ int copy_bytes_full( * back to simple read()s in case we encounter empty files. * * See: https://lwn.net/Articles/846403/ */ - try_cfr = try_sendfile = try_splice = false; + try_cfr = try_sendfile = false; } else /* Success! */ goto next; } - /* First try sendfile(), unless we already tried */ + /* Second, try sendfile(), unless we already tried */ if (try_sendfile) { n = sendfile(fdt, fdf, NULL, m); if (n < 0) { @@ -398,76 +383,13 @@ int copy_bytes_full( if (copied_total > 0) break; - try_sendfile = try_splice = false; /* same logic as above for copy_file_range() */ - } else - /* Success! */ - goto next; - } - - /* Then try splice, unless we already tried. */ - if (try_splice) { - - /* splice()'s asynchronous I/O support is a bit weird. When it encounters a pipe file - * descriptor, then it will ignore its O_NONBLOCK flag and instead only honour the - * SPLICE_F_NONBLOCK flag specified in its flag parameter. Let's hide this behaviour - * here, and check if either of the specified fds are a pipe, and if so, let's pass - * the flag automatically, depending on O_NONBLOCK being set. - * - * Here's a twist though: when we use it to move data between two pipes of which one - * has O_NONBLOCK set and the other has not, then we have no individual control over - * O_NONBLOCK behaviour. Hence in that case we can't use splice() and still guarantee - * systematic O_NONBLOCK behaviour, hence don't. */ - - if (nonblock_pipe < 0) { - int a, b; - - /* Check if either of these fds is a pipe, and if so non-blocking or not */ - a = fd_is_nonblock_pipe(fdf); - if (a < 0) - return a; - - b = fd_is_nonblock_pipe(fdt); - if (b < 0) - return b; - - if ((a == FD_IS_NO_PIPE && b == FD_IS_NO_PIPE) || - (a == FD_IS_BLOCKING_PIPE && b == FD_IS_NONBLOCKING_PIPE) || - (a == FD_IS_NONBLOCKING_PIPE && b == FD_IS_BLOCKING_PIPE)) - - /* splice() only works if one of the fds is a pipe. If neither is, - * let's skip this step right-away. As mentioned above, if one of the - * two fds refers to a blocking pipe and the other to a non-blocking - * pipe, we can't use splice() either, hence don't try either. This - * hence means we can only use splice() if either only one of the two - * fds is a pipe, or if both are pipes with the same nonblocking flag - * setting. */ - - try_splice = false; - else - nonblock_pipe = a == FD_IS_NONBLOCKING_PIPE || b == FD_IS_NONBLOCKING_PIPE; - } - } - - if (try_splice) { - n = splice(fdf, NULL, fdt, NULL, m, nonblock_pipe ? SPLICE_F_NONBLOCK : 0); - if (n < 0) { - if (!IN_SET(errno, EINVAL, ENOSYS)) - return -errno; - - try_splice = false; - /* use fallback below */ - } else if (n == 0) { /* likely EOF */ - - if (copied_total > 0) - break; - - try_splice = false; /* same logic as above for copy_file_range() + sendfile() */ + try_sendfile = false; /* same logic as above for copy_file_range() */ } else /* Success! */ goto next; } - /* As a fallback just copy bits by hand */ + /* Third, as a fallback just copy bits by hand */ { uint8_t buf[MIN(m, COPY_BUFFER_SIZE)], *p = buf; ssize_t z; @@ -642,7 +564,7 @@ static int hardlink_context_setup( * <= 0, because in that case we will not actually allocate the hardlink inode lookup table directory * on disk (we do so lazily, when the first candidate with .n_link > 1 is seen). This means, in the * common case where hardlinks are not used at all or only for few files the fact that we store the - * table on disk shouldn't matter perfomance-wise. */ + * table on disk shouldn't matter performance-wise. */ if (!FLAGS_SET(copy_flags, COPY_HARDLINKS)) return 0; @@ -1123,6 +1045,7 @@ static int fd_copy_directory( .parent_fd = -EBADF, }; + _cleanup_free_ DirectoryEntries *des = NULL; _cleanup_close_ int fdf = -EBADF, fdt = -EBADF; _cleanup_closedir_ DIR *d = NULL; struct stat dt_st; @@ -1186,14 +1109,20 @@ static int fd_copy_directory( goto finish; } - FOREACH_DIRENT_ALL(de, d, return -errno) { + /* Walk children in deterministic (alphabetical) order. The natural readdir() order depends on the + * source filesystem's directory storage (e.g. ext4 dir hash) and varies across hosts, which leaks + * into the destination when it records entries in insertion order (e.g. vfat). Sorting here keeps + * copy_tree() reproducible regardless of the source filesystem layout. */ + r = readdir_all(dirfd(d), RECURSE_DIR_SORT, &des); + if (r < 0) + return r; + + FOREACH_ARRAY(i, des->entries, des->n_entries) { + struct dirent *de = *i; const char *child_display_path = NULL; _cleanup_free_ char *dp = NULL; struct stat buf; - if (dot_or_dot_dot(de->d_name)) - continue; - r = look_for_signals(copy_flags); if (r < 0) return r; 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..85d0e11402386 100644 --- a/src/shared/cpu-set-util.c +++ b/src/shared/cpu-set-util.c @@ -6,6 +6,7 @@ #include "alloc-util.h" #include "bitfield.h" +#include "cgroup-util.h" #include "cpu-set-util.h" #include "extract-word.h" #include "log.h" @@ -159,6 +160,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 +197,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); @@ -396,3 +401,50 @@ int cpu_set_from_dbus(const uint8_t *bits, size_t size, CPUSet *ret) { *ret = TAKE_STRUCT(c); return 0; } + +static int cgroup_cpus_effective(unsigned *ret) { + int r; + + assert(ret); + + _cleanup_free_ char *root = NULL; + r = cg_get_root_path(&root); + if (r < 0) + return log_debug_errno(r, "Failed to determine root cgroup: %m"); + + _cleanup_free_ char *value = NULL; + r = cg_get_attribute(root, "cpuset.cpus.effective", &value); + if (r < 0) + return log_debug_errno(r, "Failed to read cpuset.cpus.effective cgroup attribute: %m"); + + _cleanup_(cpu_set_done) CPUSet cpus = {}; + r = parse_cpu_set(value, &cpus); + if (r < 0) + return log_debug_errno(r, "Failed to parse cpuset.cpus.effective cgroup attribute: %m"); + + *ret = (unsigned) MIN(cpu_set_count(&cpus), UINT_MAX); + return 0; +} + +int cpus_online(unsigned *ret) { + int r; + + assert(ret); + + /* In order to support containers nicely that have a configured cpuset we'll take the minimum of the + * physically reported amount of CPUs and the limit configured for the root cgroup, if there is + * any. */ + + long sc = sysconf(_SC_NPROCESSORS_ONLN); + if (sc < 0) + return log_debug_errno(errno, "sysconf(_SC_NPROCESSORS_ONLN) failed: %m"); + + unsigned cg, lc = (unsigned) CLAMP((unsigned long) sc, 1U, UINT_MAX); + r = cgroup_cpus_effective(&cg); + if (r < 0) + *ret = lc; + else + *ret = CLAMP(cg, 1U, lc); + + return 0; +} diff --git a/src/shared/cpu-set-util.h b/src/shared/cpu-set-util.h index a74e264e1267b..1f89ce2020701 100644 --- a/src/shared/cpu-set-util.h +++ b/src/shared/cpu-set-util.h @@ -38,3 +38,13 @@ int cpu_set_to_dbus(const CPUSet *c, uint8_t **ret, size_t *ret_size); int cpu_set_from_dbus(const uint8_t *bits, size_t size, CPUSet *ret); int cpus_in_affinity_mask(void); + +static inline size_t cpu_set_count(const CPUSet *c) { + if (!c) + return 0; + if (c->allocated <= 0) + return 0; + return CPU_COUNT_S(c->allocated, c->set); +} + +int cpus_online(unsigned *ret); diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 2aac4d253bb76..739973aa46130 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" @@ -31,8 +28,7 @@ #include "json-util.h" #include "log.h" #include "memory-util.h" -#include "mkdir-label.h" -#include "openssl-util.h" +#include "mkdir.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; + + assert_se(cc = sym_EVP_aes_256_gcm()); - ksz = EVP_CIPHER_key_length(cc); + 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,12 @@ 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)); + return log_openssl_errors(LOG_ERR, "Failed to allocate encryption object"); - if (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)); + if (sym_EVP_EncryptInit_ex(context, cc, NULL, md, iv.iov_base) != 1) + return log_openssl_errors(LOG_ERR, "Failed to initialize encryption context"); /* Just an upper estimate */ output.iov_len = @@ -1061,6 +1080,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 +1134,8 @@ 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) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s", - ERR_error_string(ERR_get_error(), NULL)); + if (sym_EVP_EncryptUpdate(context, NULL, &added, output.iov_base, p) != 1) + return log_openssl_errors(LOG_ERR, "Failed to write AAD data"); /* Now construct the metadata header */ ml = strlen_ptr(name); @@ -1129,27 +1149,24 @@ 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) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt metadata header: %s", - ERR_error_string(ERR_get_error(), NULL)); + 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_openssl_errors(LOG_ERR, "Failed to encrypt metadata header"); 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) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt data: %s", - ERR_error_string(ERR_get_error(), NULL)); + if (sym_EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, input->iov_base, input->iov_len) != 1) + return log_openssl_errors(LOG_ERR, "Failed to encrypt data"); 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) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize data encryption: %s", - ERR_error_string(ERR_get_error(), NULL)); + if (sym_EVP_EncryptFinal_ex(context, (uint8_t*) output.iov_base + p, &added) != 1) + return log_openssl_errors(LOG_ERR, "Failed to finalize data encryption"); assert(added >= 0); assert((size_t) added <= output.iov_len - p); @@ -1158,9 +1175,8 @@ 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) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get tag: %s", - ERR_error_string(ERR_get_error(), NULL)); + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output.iov_base + p) != 1) + return log_openssl_errors(LOG_ERR, "Failed to get tag"); p += tsz; assert(p <= output.iov_len); @@ -1204,6 +1220,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 +1433,61 @@ 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)); + return log_openssl_errors(LOG_ERR, "Failed to allocate decryption object"); - if (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)); + if (sym_EVP_DecryptInit_ex(context, cc, NULL, NULL, NULL) != 1) + return log_openssl_errors(LOG_ERR, "Failed to initialize decryption context"); - if (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)); + if (sym_EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, le32toh(h->iv_size), NULL) != 1) + return log_openssl_errors(LOG_ERR, "Failed to set IV size on decryption context"); - if (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)); + if (sym_EVP_DecryptInit_ex(context, NULL, NULL, md, h->iv) != 1) + return log_openssl_errors(LOG_ERR, "Failed to set IV and key"); - if (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)); + if (sym_EVP_DecryptUpdate(context, NULL, &added, input->iov_base, p) != 1) + return log_openssl_errors(LOG_ERR, "Failed to write AAD data"); 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)); + return log_openssl_errors(LOG_ERR, "Failed to decrypt data"); 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) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set tag: %s", - ERR_error_string(ERR_get_error(), NULL)); + 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_openssl_errors(LOG_ERR, "Failed to set tag"); - if (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)); + if (sym_EVP_DecryptFinal_ex(context, (uint8_t*) plaintext.iov_base + plaintext.iov_len, &added) != 1) { + log_openssl_errors(LOG_ERR, "Decryption failed (incorrect key?)"); + /* A GCM tag/authentication mismatch (wrong key or corrupted blob) is the common case here + * and typically leaves the OpenSSL error queue empty, so we can't rely on the translated + * errno. Return -EBADMSG unconditionally: it's this function's documented "corrupted file" + * code, and credentials_varlink_error_table[] maps it to io.systemd.Credentials.BadFormat. */ + return -EBADMSG; + } plaintext.iov_len += added; @@ -1690,8 +1710,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 +1720,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"); @@ -1790,16 +1805,13 @@ int pick_up_credentials(const PickUpCredential *table, size_t n_table_entry) { return log_error_errno(credential_dir_fd, "Failed to open credentials directory: %m"); _cleanup_free_ DirectoryEntries *des = NULL; - r = readdir_all(credential_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des); + r = readdir_all(credential_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_REGULAR, &des); if (r < 0) return log_error_errno(r, "Failed to enumerate credentials: %m"); FOREACH_ARRAY(i, des->entries, des->n_entries) { struct dirent *de = *i; - if (de->d_type != DT_REG) - continue; - FOREACH_ARRAY(t, table, n_table_entry) { r = pick_up_credential_one(credential_dir_fd, de->d_name, t); if (r != 0) { diff --git a/src/shared/crypto-util.c b/src/shared/crypto-util.c new file mode 100644 index 0000000000000..5b1d6d8136635 --- /dev/null +++ b/src/shared/crypto-util.c @@ -0,0 +1,2491 @@ +/* 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 "random-util.h" +#include "string-util.h" +#include "strv.h" + +#if HAVE_OPENSSL +# 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 +}; + +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; +static DLSYM_PROTOTYPE(ASN1_INTEGER_dup) = NULL; +static DLSYM_PROTOTYPE(ASN1_INTEGER_free) = NULL; +static DLSYM_PROTOTYPE(ASN1_INTEGER_set) = 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_CTX_secure_new) = NULL; +DLSYM_PROTOTYPE(BN_add) = NULL; +DLSYM_PROTOTYPE(BN_add_word) = NULL; +DLSYM_PROTOTYPE(BN_bin2bn) = NULL; +DLSYM_PROTOTYPE(BN_bn2bin) = NULL; +DLSYM_PROTOTYPE(BN_bn2binpad) = NULL; +DLSYM_PROTOTYPE(BN_bn2nativepad) = NULL; +DLSYM_PROTOTYPE(BN_check_prime) = NULL; +DLSYM_PROTOTYPE(BN_clear_free) = NULL; +DLSYM_PROTOTYPE(BN_cmp) = NULL; +DLSYM_PROTOTYPE(BN_copy) = NULL; +DLSYM_PROTOTYPE(BN_free) = NULL; +DLSYM_PROTOTYPE(BN_is_negative) = NULL; +DLSYM_PROTOTYPE(BN_mod_exp) = NULL; +DLSYM_PROTOTYPE(BN_mod_inverse) = NULL; +DLSYM_PROTOTYPE(BN_mod_lshift1_quick) = NULL; +DLSYM_PROTOTYPE(BN_mod_mul) = NULL; +DLSYM_PROTOTYPE(BN_mod_sqr) = NULL; +DLSYM_PROTOTYPE(BN_mod_sub) = NULL; +DLSYM_PROTOTYPE(BN_mul) = NULL; +DLSYM_PROTOTYPE(BN_new) = NULL; +DLSYM_PROTOTYPE(BN_nnmod) = NULL; +DLSYM_PROTOTYPE(BN_num_bits) = NULL; +DLSYM_PROTOTYPE(BN_secure_new) = NULL; +DLSYM_PROTOTYPE(BN_set_word) = NULL; +DLSYM_PROTOTYPE(BN_sub_word) = 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; +DLSYM_PROTOTYPE(EVP_MAC_CTX_new) = NULL; +DLSYM_PROTOTYPE(EVP_MAC_fetch) = NULL; +DLSYM_PROTOTYPE(EVP_MAC_final) = NULL; +DLSYM_PROTOTYPE(EVP_MAC_free) = NULL; +DLSYM_PROTOTYPE(EVP_MAC_init) = NULL; +DLSYM_PROTOTYPE(EVP_MAC_update) = NULL; +DLSYM_PROTOTYPE(EVP_MD_CTX_copy_ex) = 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; +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(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; +DLSYM_PROTOTYPE(OSSL_PARAM_BLD_free) = NULL; +DLSYM_PROTOTYPE(OSSL_PARAM_BLD_new) = NULL; +static DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_octet_string) = NULL; +DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_utf8_string) = NULL; +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(PEM_write_PUBKEY) = NULL; +DLSYM_PROTOTYPE(PEM_write_PrivateKey) = NULL; +DLSYM_PROTOTYPE(PEM_write_X509) = NULL; +DLSYM_PROTOTYPE(PKCS5_PBKDF2_HMAC) = NULL; +DLSYM_PROTOTYPE(PKCS7_ATTR_SIGN_it) = NULL; +DLSYM_PROTOTYPE(PKCS7_SIGNER_INFO_free) = 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; +DLSYM_PROTOTYPE(SHA1) = NULL; +DLSYM_PROTOTYPE(SHA512) = NULL; +DLSYM_PROTOTYPE(X509_ALGOR_free) = NULL; +static DLSYM_PROTOTYPE(X509_ALGOR_set0) = NULL; +DLSYM_PROTOTYPE(X509_ATTRIBUTE_free) = NULL; +DLSYM_PROTOTYPE(X509_NAME_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; +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(X509_gmtime_adj) = 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_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; +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; + +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(RSAPublicKey_dup) = NULL; +DLSYM_PROTOTYPE(RSA_free) = NULL; +DLSYM_PROTOTYPE(RSA_new) = NULL; +DLSYM_PROTOTYPE(RSA_set0_key) = NULL; +DLSYM_PROTOTYPE(RSA_size) = NULL; +REENABLE_WARNING; +#endif + +#ifndef OPENSSL_NO_UI_CONSOLE +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 + static void *libcrypto_dl = NULL; + int r; + + LIBCRYPTO_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); + + FOREACH_STRING(soname, "libcrypto.so.4", "libcrypto.so.3") { + r = dlopen_many_sym_or_warn( + &libcrypto_dl, + soname, + 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_IA5STRING_it), + DLSYM_ARG(ASN1_INTEGER_dup), + DLSYM_ARG(ASN1_INTEGER_free), + DLSYM_ARG(ASN1_INTEGER_set), + 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(ASN1_get_object), + DLSYM_ARG(ASN1_item_d2i), + DLSYM_ARG(ASN1_item_free), + DLSYM_ARG(ASN1_item_i2d), + DLSYM_ARG(ASN1_item_new), + DLSYM_ARG(BIO_ctrl), + DLSYM_ARG(BIO_find_type), + DLSYM_ARG(BIO_free), + DLSYM_ARG(BIO_free_all), + DLSYM_ARG(BIO_new), + DLSYM_ARG(BIO_new_mem_buf), + DLSYM_ARG(BIO_new_socket), + DLSYM_ARG(BIO_s_mem), + DLSYM_ARG(BIO_write), + DLSYM_ARG(BN_CTX_free), + DLSYM_ARG(BN_CTX_new), + DLSYM_ARG(BN_CTX_secure_new), + DLSYM_ARG(BN_add), + DLSYM_ARG(BN_add_word), + DLSYM_ARG(BN_bin2bn), + DLSYM_ARG(BN_bn2bin), + DLSYM_ARG(BN_bn2binpad), + DLSYM_ARG(BN_bn2nativepad), + DLSYM_ARG(BN_check_prime), + DLSYM_ARG(BN_clear_free), + DLSYM_ARG(BN_cmp), + DLSYM_ARG(BN_copy), + DLSYM_ARG(BN_free), + DLSYM_ARG(BN_is_negative), + DLSYM_ARG(BN_mod_exp), + DLSYM_ARG(BN_mod_inverse), + DLSYM_ARG(BN_mod_lshift1_quick), + DLSYM_ARG(BN_mod_mul), + DLSYM_ARG(BN_mod_sqr), + DLSYM_ARG(BN_mod_sub), + DLSYM_ARG(BN_mul), + DLSYM_ARG(BN_new), + DLSYM_ARG(BN_nnmod), + DLSYM_ARG(BN_num_bits), + DLSYM_ARG(BN_secure_new), + DLSYM_ARG(BN_set_word), + DLSYM_ARG(BN_sub_word), + DLSYM_ARG(CRYPTO_free), + DLSYM_ARG(ECDSA_SIG_free), + DLSYM_ARG(EC_GROUP_free), + DLSYM_ARG(EC_GROUP_get0_generator), + DLSYM_ARG(EC_GROUP_get0_order), + DLSYM_ARG(EC_GROUP_get_curve), + DLSYM_ARG(EC_GROUP_get_curve_name), + DLSYM_ARG(EC_GROUP_get_field_type), + 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(ERR_clear_error), + DLSYM_ARG(ERR_error_string), + DLSYM_ARG(ERR_error_string_n), + DLSYM_ARG(ERR_get_error), + DLSYM_ARG(ERR_peek_last_error), + 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), + DLSYM_ARG(EVP_EncryptInit_ex), + DLSYM_ARG(EVP_EncryptUpdate), + 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_copy_ex), + 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_get0_name), + DLSYM_ARG(EVP_MD_get_size), + DLSYM_ARG(EVP_MD_get_type), + DLSYM_ARG(EVP_PKEY_CTX_free), + DLSYM_ARG(EVP_PKEY_CTX_new), + DLSYM_ARG(EVP_PKEY_CTX_new_from_name), + DLSYM_ARG(EVP_PKEY_CTX_new_id), + DLSYM_ARG(EVP_PKEY_CTX_set0_rsa_oaep_label), + 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_derive), + DLSYM_ARG(EVP_PKEY_derive_init), + DLSYM_ARG(EVP_PKEY_derive_set_peer), + DLSYM_ARG(EVP_PKEY_encrypt), + DLSYM_ARG(EVP_PKEY_encrypt_init), + DLSYM_ARG(EVP_PKEY_eq), + DLSYM_ARG(EVP_PKEY_free), + DLSYM_ARG(EVP_PKEY_fromdata), + DLSYM_ARG(EVP_PKEY_fromdata_init), + DLSYM_ARG(EVP_PKEY_get1_encoded_public_key), + 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_keygen), + DLSYM_ARG(EVP_PKEY_keygen_init), + DLSYM_ARG(EVP_PKEY_new), + DLSYM_ARG(EVP_PKEY_new_raw_public_key), + DLSYM_ARG(EVP_PKEY_verify), + DLSYM_ARG(EVP_PKEY_verify_init), + DLSYM_ARG(EVP_aes_256_ctr), + DLSYM_ARG(EVP_aes_256_gcm), + DLSYM_ARG(EVP_get_cipherbyname), + DLSYM_ARG(EVP_get_digestbyname), + DLSYM_ARG(EVP_sha1), + DLSYM_ARG(EVP_sha256), + DLSYM_ARG(EVP_sha384), + DLSYM_ARG(EVP_sha512), + DLSYM_ARG(HMAC), + 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_INFO_free), + DLSYM_ARG(OSSL_STORE_INFO_get1_CERT), + DLSYM_ARG(OSSL_STORE_INFO_get1_PKEY), + DLSYM_ARG(OSSL_STORE_close), + DLSYM_ARG(OSSL_STORE_expect), + DLSYM_ARG(OSSL_STORE_load), + DLSYM_ARG(OSSL_STORE_open), + DLSYM_ARG(PEM_read_PUBKEY), + DLSYM_ARG(PEM_read_PrivateKey), + DLSYM_ARG(PEM_read_X509), + DLSYM_ARG(PEM_read_bio_PrivateKey), + DLSYM_ARG(PEM_read_bio_X509), + DLSYM_ARG(PEM_write_PUBKEY), + DLSYM_ARG(PEM_write_PrivateKey), + DLSYM_ARG(PEM_write_X509), + DLSYM_ARG(PKCS5_PBKDF2_HMAC), + DLSYM_ARG(PKCS7_ATTR_SIGN_it), + DLSYM_ARG(PKCS7_SIGNER_INFO_free), + DLSYM_ARG(PKCS7_SIGNER_INFO_new), + DLSYM_ARG(PKCS7_SIGNER_INFO_set), + DLSYM_ARG(PKCS7_add0_attrib_signing_time), + DLSYM_ARG(PKCS7_add1_attrib_digest), + 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_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_verify), + DLSYM_ARG(SHA1), + DLSYM_ARG(SHA512), + DLSYM_ARG(X509_ALGOR_free), + DLSYM_ARG(X509_ALGOR_set0), + DLSYM_ARG(X509_ATTRIBUTE_free), + DLSYM_ARG(X509_NAME_free), + DLSYM_ARG(X509_NAME_oneline), + DLSYM_ARG(X509_NAME_set), + DLSYM_ARG(X509_VERIFY_PARAM_set1_host), + DLSYM_ARG(X509_VERIFY_PARAM_set1_ip), + DLSYM_ARG(X509_VERIFY_PARAM_set_hostflags), + DLSYM_ARG(X509_free), + DLSYM_ARG(X509_get0_serialNumber), + 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_gmtime_adj), + DLSYM_ARG(d2i_ASN1_OCTET_STRING), + DLSYM_ARG(d2i_ECPKParameters), + DLSYM_ARG(d2i_PKCS7), + DLSYM_ARG(d2i_PUBKEY), + DLSYM_ARG(d2i_X509), + DLSYM_ARG(i2d_ASN1_INTEGER), + DLSYM_ARG(i2d_PKCS7), + DLSYM_ARG(i2d_PKCS7_fp), + DLSYM_ARG(i2d_PUBKEY), + DLSYM_ARG(i2d_PUBKEY_fp), + DLSYM_ARG(i2d_PublicKey), + DLSYM_ARG(i2d_X509), + DLSYM_ARG(i2d_X509_NAME), +#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(ECDSA_SIG_new), + DLSYM_ARG_FORCE(ECDSA_SIG_set0), + DLSYM_ARG_FORCE(ECDSA_do_verify), + 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(EVP_PKEY_assign), + DLSYM_ARG_FORCE(RSAPublicKey_dup), + DLSYM_ARG_FORCE(RSA_free), + DLSYM_ARG_FORCE(RSA_new), + DLSYM_ARG_FORCE(RSA_set0_key), + DLSYM_ARG_FORCE(RSA_size), +#endif +#ifndef OPENSSL_NO_UI_CONSOLE + DLSYM_ARG(UI_OpenSSL), + DLSYM_ARG(UI_create_method), + DLSYM_ARG(UI_destroy_method), + DLSYM_ARG(UI_get0_output_string), + DLSYM_ARG(UI_get_default_method), + DLSYM_ARG(UI_get_method), + DLSYM_ARG(UI_get_string_type), + 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_set_default_method), + DLSYM_ARG(UI_set_result), +#endif + NULL); + if (r >= 0) + break; + } + if (r < 0) { + log_full_errno(log_level, r, "Neither libcrypto.so.4 nor libcrypto.so.3 could be loaded"); + return -EOPNOTSUPP; /* turn into recognizable error */ + } + + return r; +#else + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libcrypto support is not compiled in."); +#endif +} + +#if HAVE_OPENSSL + +int openssl_to_errno(unsigned long e) { + if (e == 0) + return -ENOTRECOVERABLE; + + if (ERR_SYSTEM_ERROR(e)) + /* ERR_GET_REASON() returns the raw errno in this case. OpenSSL can record a system error + * with a zero errno though (e.g. bio_sock2.c raises ERR_LIB_SYS with a socket error that + * "may be 0"), which would yield 0 here. Clamp that to -ENOTRECOVERABLE so we never return 0 + * and break the negative-return invariant that the log_openssl_errors() call sites depend + * on. */ + return -ERR_GET_REASON(e) ?: -ENOTRECOVERABLE; + + switch (ERR_GET_REASON(e)) { + + case ERR_R_MALLOC_FAILURE: + return -ENOMEM; + + case ERR_R_PASSED_NULL_PARAMETER: + case ERR_R_PASSED_INVALID_ARGUMENT: +#ifdef ERR_R_INVALID_PROPERTY_DEFINITION + case ERR_R_INVALID_PROPERTY_DEFINITION: +#endif + return -EINVAL; + + case ERR_R_UNSUPPORTED: + case ERR_R_FETCH_FAILED: + case ERR_R_DISABLED: + return -EOPNOTSUPP; + + case ERR_R_NESTED_ASN1_ERROR: + case ERR_R_MISSING_ASN1_EOS: + return -EBADMSG; + +#ifdef ERR_R_INTERRUPTED_OR_CANCELLED + case ERR_R_INTERRUPTED_OR_CANCELLED: + return -EINTR; +#endif + + default: + /* Includes the internal/should-not-happen reasons (ERR_R_INTERNAL_ERROR, + * ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED, ERR_R_INIT_FAIL, ERR_R_OPERATION_FAIL, …) and the + * "error originated in sub-library X" markers, none of which have a meaningful errno. Use + * -ENOTRECOVERABLE for these opaque OpenSSL failures, matching the convention used for + * unexpected crypto/digest failures elsewhere in the tree, and keeping them distinct from + * genuine -EIO (disk/socket) errors. */ + return -ENOTRECOVERABLE; + } +} + +int log_openssl_errors_internal(int level, const char *file, int line, const char *func, const char *format, ...) { + _cleanup_free_ char *prefix = NULL; + va_list ap; + int r; + + va_start(ap, format); + r = vasprintf(&prefix, format, ap); + va_end(ap); + if (r < 0) + return log_oom_full(level); + + char buf[512]; /* openssl docs require >= 256 */ + int ret = 0; + for (;;) { + unsigned long e = sym_ERR_get_error(); + if (e == 0) + break; + + sym_ERR_error_string_n(e, buf, sizeof(buf)); + + /* The queue is drained oldest-first (ERR_get_error() is FIFO), and the oldest entry is + * normally the deepest, most-specific reason while newer entries are higher-level + * "came-from" wrappers that translate to the -ENOTRECOVERABLE fallback. Keep the first + * specific (non-fallback) errno we see, so a trailing wrapper can't shadow it. */ + int translated = openssl_to_errno(e); + if (ret == 0 || (ret == -ENOTRECOVERABLE && translated != -ENOTRECOVERABLE)) + ret = translated; + + log_internal(level, SYNTHETIC_ERRNO(translated), file, line, func, "%s: %s", prefix, buf); + } + + if (ret == 0) /* The queue was empty. */ + return log_internal(level, SYNTHETIC_ERRNO(ENOTRECOVERABLE), file, line, func, "%s: No OpenSSL errors.", prefix); + + return ret; +} + +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); + + _cleanup_fclose_ FILE *f = NULL; + f = fmemopen((void*) pem, pem_size, "r"); + if (!f) + return log_oom_debug(); + + _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(LOG_DEBUG, "Failed to parse PEM"); + + *ret = TAKE_PTR(pkey); + return 0; +} + +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 (sym_PEM_write_PUBKEY(f, pkey) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to write public key in PEM format"); + + return memstream_finalize(&m, ret, /* ret_size= */ NULL); +} + +/* Returns the number of bytes generated by the specified digest algorithm. This can be used only for + * fixed-size algorithms, e.g. md5, sha1, sha256, etc. Do not use this for variable-sized digest algorithms, + * 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); + + 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 = sym_EVP_MD_get_size(md); + if (digest_size == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to get Digest size"); + + *ret_digest_size = digest_size; + + return 0; +} + +/* Calculate the digest hash value for the provided data, using the specified digest algorithm. Returns 0 on + * success, -EOPNOTSUPP if the digest algorithm is not supported, or < 0 for any other error. */ +int openssl_digest_many( + const char *digest_alg, + const struct iovec data[], + size_t n_data, + void **ret_digest, + size_t *ret_digest_size) { + + int r; + + assert(digest_alg); + assert(data || n_data == 0); + assert(ret_digest); + /* ret_digest_size is optional, as caller may already know the digest size */ + + 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 = sym_EVP_MD_CTX_new(); + if (!ctx) + return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_MD_CTX"); + + if (!sym_EVP_DigestInit_ex(ctx, md, NULL)) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize EVP_MD_CTX"); + + for (size_t i = 0; i < n_data; i++) + if (!sym_EVP_DigestUpdate(ctx, data[i].iov_base, data[i].iov_len)) + return log_openssl_errors(LOG_DEBUG, "Failed to update Digest"); + + size_t digest_size; + r = openssl_digest_size(digest_alg, &digest_size); + if (r < 0) + return r; + + _cleanup_free_ void *buf = malloc(digest_size); + if (!buf) + return log_oom_debug(); + + unsigned size; + if (!sym_EVP_DigestFinal_ex(ctx, buf, &size)) + return log_openssl_errors(LOG_DEBUG, "Failed to finalize Digest"); + + assert(size == digest_size); + + *ret_digest = TAKE_PTR(buf); + if (ret_digest_size) + *ret_digest_size = size; + + return 0; +} + +/* Calculate the HMAC digest hash value for the provided data, using the provided key and specified digest + * algorithm. Returns 0 on success, -EOPNOTSUPP if the digest algorithm is not supported, or < 0 for any + * other error. */ +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) { + + 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 */ + + 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 = sym_EVP_MAC_fetch(NULL, "HMAC", NULL); + if (!mac) + return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_MAC"); + + _cleanup_(EVP_MAC_CTX_freep) EVP_MAC_CTX *ctx = sym_EVP_MAC_CTX_new(mac); + if (!ctx) + return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_MAC_CTX"); + + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); + if (!bld) + return log_openssl_errors(LOG_DEBUG, "Failed to create new OSSL_PARAM_BLD"); + + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_MAC_PARAM_DIGEST, (char*) digest_alg, 0)) + return log_openssl_errors(LOG_DEBUG, "Failed to set HMAC OSSL_MAC_PARAM_DIGEST"); + + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); + if (!params) + return log_openssl_errors(LOG_DEBUG, "Failed to build HMAC OSSL_PARAM"); + + if (!sym_EVP_MAC_init(ctx, key, key_size, params)) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize EVP_MAC_CTX"); + + for (size_t i = 0; i < n_data; i++) + if (!sym_EVP_MAC_update(ctx, data[i].iov_base, data[i].iov_len)) + return log_openssl_errors(LOG_DEBUG, "Failed to update HMAC"); + + size_t digest_size = sym_EVP_MAC_CTX_get_mac_size(ctx); + if (digest_size == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to get HMAC digest size"); + + _cleanup_free_ void *buf = malloc(digest_size); + if (!buf) + return log_oom_debug(); + + size_t size; + if (!sym_EVP_MAC_final(ctx, buf, &size, digest_size)) + return log_openssl_errors(LOG_DEBUG, "Failed to finalize HMAC"); + + assert(size == digest_size); + + *ret_digest = TAKE_PTR(buf); + if (ret_digest_size) + *ret_digest_size = size; + + return 0; +} + +/* Symmetric Cipher encryption using the alg-bits-mode cipher, e.g. AES-128-CFB. The key is required and must + * be at least the minimum required key length for the cipher. The IV is optional but, if provided, it must + * be at least the minimum iv length for the cipher. If no IV is provided and the cipher requires one, a + * buffer of zeroes is used. Returns 0 on success, -EOPNOTSUPP if the cipher algorithm is not supported, or < + * 0 on any other error. */ +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) { + + int r; + + assert(alg); + assert(bits > 0); + assert(mode); + assert(key); + assert(iv || iv_size == 0); + assert(data || n_data == 0); + 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 = 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 = sym_EVP_CIPHER_CTX_new(); + if (!ctx) + return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_CIPHER_CTX"); + + /* Verify enough key data was provided. */ + 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 = sym_EVP_CIPHER_get_iv_length(cipher); + assert(cipher_iv_length >= 0); + _cleanup_free_ void *zero_iv = NULL; + if (iv_size == 0) { + zero_iv = malloc0(cipher_iv_length); + if (!zero_iv) + return log_oom_debug(); + + iv = zero_iv; + iv_size = (size_t) cipher_iv_length; + } + if ((size_t) cipher_iv_length > iv_size) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Not enough IV bytes provided, require %d", cipher_iv_length); + + if (!sym_EVP_EncryptInit(ctx, cipher, key, iv)) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize EVP_CIPHER_CTX."); + + int cipher_block_size = sym_EVP_CIPHER_CTX_get_block_size(ctx); + assert(cipher_block_size > 0); + + _cleanup_free_ uint8_t *buf = NULL; + size_t size = 0; + + for (size_t i = 0; i < n_data; i++) { + /* Cipher may produce (up to) input length + cipher block size of output. */ + if (!GREEDY_REALLOC(buf, size + data[i].iov_len + cipher_block_size)) + return log_oom_debug(); + + int update_size; + if (!sym_EVP_EncryptUpdate(ctx, &buf[size], &update_size, data[i].iov_base, data[i].iov_len)) + return log_openssl_errors(LOG_DEBUG, "Failed to update Cipher."); + + size += update_size; + } + + if (!GREEDY_REALLOC(buf, size + cipher_block_size)) + return log_oom_debug(); + + int final_size; + if (!sym_EVP_EncryptFinal_ex(ctx, &buf[size], &final_size)) + return log_openssl_errors(LOG_DEBUG, "Failed to finalize Cipher."); + + *ret = TAKE_PTR(buf); + *ret_size = size + final_size; + + return 0; +} + +/* Perform Single-Step (aka "Concat") KDF. Currently, this only supports using the digest for the auxiliary + * function. The derive_size parameter specifies how many bytes are derived. + * + * For more details see: https://www.openssl.org/docs/manmaster/man7/EVP_KDF-SS.html */ +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 r; + + assert(digest); + assert(key); + assert(derive_size > 0); + assert(ret); + + 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(LOG_DEBUG, "Failed to create new EVP_KDF"); + + _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = sym_EVP_KDF_CTX_new(kdf); + if (!ctx) + return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_KDF_CTX"); + + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); + if (!bld) + return log_openssl_errors(LOG_DEBUG, "Failed to create new OSSL_PARAM_BLD"); + + _cleanup_free_ void *buf = malloc(derive_size); + if (!buf) + return log_oom_debug(); + + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, 0)) + return log_openssl_errors(LOG_DEBUG, "Failed to add KDF-SS OSSL_KDF_PARAM_DIGEST"); + + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, (char*) key, key_size)) + return log_openssl_errors(LOG_DEBUG, "Failed to add KDF-SS OSSL_KDF_PARAM_KEY"); + + if (salt) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, (char*) salt, salt_size)) + return log_openssl_errors(LOG_DEBUG, "Failed to add KDF-SS OSSL_KDF_PARAM_SALT"); + + if (info) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, (char*) info, info_size)) + return log_openssl_errors(LOG_DEBUG, "Failed to add KDF-SS OSSL_KDF_PARAM_INFO"); + + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); + if (!params) + return log_openssl_errors(LOG_DEBUG, "Failed to build KDF-SS OSSL_PARAM"); + + if (sym_EVP_KDF_derive(ctx, buf, derive_size, params) <= 0) + return log_openssl_errors(LOG_DEBUG, "OpenSSL KDF-SS derive failed"); + + *ret = TAKE_PTR(buf); + + return 0; +} + +/* Perform Key-Based HMAC KDF. The mode must be "COUNTER" or "FEEDBACK". The parameter naming is from the + * OpenSSL api, and maps to SP800-108 naming as "...key, salt, info, and seed correspond to KI, Label, + * Context, and IV (respectively)...". The derive_size parameter specifies how many bytes are derived. + * + * For more details see: https://www.openssl.org/docs/manmaster/man7/EVP_KDF-KB.html */ +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 r; + + assert(mode); + assert(strcaseeq(mode, "COUNTER") || strcaseeq(mode, "FEEDBACK")); + assert(digest); + assert(key || key_size == 0); + assert(salt || salt_size == 0); + assert(info || info_size == 0); + assert(seed || seed_size == 0); + assert(derive_size > 0); + assert(ret); + + 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(LOG_DEBUG, "Failed to create new EVP_KDF"); + + _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = sym_EVP_KDF_CTX_new(kdf); + if (!ctx) + return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_KDF_CTX"); + + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); + if (!bld) + return log_openssl_errors(LOG_DEBUG, "Failed to create new OSSL_PARAM_BLD"); + + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_MAC, (char*) "HMAC", 0)) + return log_openssl_errors(LOG_DEBUG, "Failed to add KDF-KB OSSL_KDF_PARAM_MAC"); + + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_MODE, (char*) mode, 0)) + return log_openssl_errors(LOG_DEBUG, "Failed to add KDF-KB OSSL_KDF_PARAM_MODE"); + + if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, 0)) + return log_openssl_errors(LOG_DEBUG, "Failed to add KDF-KB OSSL_KDF_PARAM_DIGEST"); + + if (key) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, (char*) key, key_size)) + return log_openssl_errors(LOG_DEBUG, "Failed to add KDF-KB OSSL_KDF_PARAM_KEY"); + + if (salt) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, (char*) salt, salt_size)) + return log_openssl_errors(LOG_DEBUG, "Failed to add KDF-KB OSSL_KDF_PARAM_SALT"); + + if (info) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, (char*) info, info_size)) + return log_openssl_errors(LOG_DEBUG, "Failed to add KDF-KB OSSL_KDF_PARAM_INFO"); + + if (seed) + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SEED, (char*) seed, seed_size)) + return log_openssl_errors(LOG_DEBUG, "Failed to add KDF-KB OSSL_KDF_PARAM_SEED"); + + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); + if (!params) + return log_openssl_errors(LOG_DEBUG, "Failed to build KDF-KB OSSL_PARAM"); + + _cleanup_free_ void *buf = malloc(derive_size); + if (!buf) + return log_oom_debug(); + + if (sym_EVP_KDF_derive(ctx, buf, derive_size, params) <= 0) + return log_openssl_errors(LOG_DEBUG, "OpenSSL KDF-KB derive failed"); + + *ret = TAKE_PTR(buf); + + return 0; +} + +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 r; + + assert(pkey); + assert(digest_alg); + assert(decrypted_key); + assert(decrypted_key_size > 0); + assert(ret_encrypt_key); + assert(ret_encrypt_key_size); + + 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 = sym_EVP_PKEY_CTX_new((EVP_PKEY*) pkey, NULL); + if (!ctx) + return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_PKEY_CTX"); + + if (sym_EVP_PKEY_encrypt_init(ctx) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize EVP_PKEY_CTX"); + + if (sym_EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to configure RSA-OAEP padding"); + + if (sym_EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to configure RSA-OAEP MD"); + + if (label) { + _cleanup_free_ char *duplabel = strdup(label); + if (!duplabel) + return log_oom_debug(); + + if (sym_EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, duplabel, strlen(duplabel) + 1) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to configure RSA-OAEP label"); + /* ctx owns this now, don't free */ + TAKE_PTR(duplabel); + } + + size_t size = 0; + if (sym_EVP_PKEY_encrypt(ctx, NULL, &size, decrypted_key, decrypted_key_size) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to determine RSA-OAEP encrypted key size"); + + _cleanup_free_ void *buf = malloc(size); + if (!buf) + return log_oom_debug(); + + if (sym_EVP_PKEY_encrypt(ctx, buf, &size, decrypted_key, decrypted_key_size) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to RSA-OAEP encrypt"); + + *ret_encrypt_key = TAKE_PTR(buf); + *ret_encrypt_key_size = size; + + return 0; +} + +int rsa_pkey_to_suitable_key_size( + EVP_PKEY *pkey, + size_t *ret_suitable_key_size) { + + size_t suitable_key_size; + int bits, r; + + assert(pkey); + assert(ret_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. */ + + 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 = sym_EVP_PKEY_get_bits(pkey); + log_debug("Bits in RSA key: %i", bits); + + /* We use RSA-OAEP for the cleartext, which has 2*hash_len + 2 bytes of overhead (e.g. 66 bytes + * for SHA-256, 42 for SHA-1). Leave plenty of headroom by only generating a key half the size of + * the RSA modulus. */ + suitable_key_size = bits / 8 / 2; + + if (suitable_key_size < 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Uh, RSA key size too short?"); + + *ret_suitable_key_size = suitable_key_size; + return 0; +} + +/* Generate RSA public key from provided "n" and "e" values. Numbers "n" and "e" must be provided here + * 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); + assert(e); + assert(e_size != 0); + assert(ret); + + 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(LOG_DEBUG, "Failed to create new EVP_PKEY_CTX"); + + if (sym_EVP_PKEY_fromdata_init(ctx) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize EVP_PKEY_CTX"); + + OSSL_PARAM params[3]; + +#if __BYTE_ORDER == __BIG_ENDIAN + 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) + return log_oom_debug(); + + _cleanup_free_ void *native_e = memdup_reverse(e, e_size); + if (!native_e) + return log_oom_debug(); + + 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] = sym_OSSL_PARAM_construct_end(); + + if (sym_EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to create RSA EVP_PKEY"); + + *ret = TAKE_PTR(pkey); + + return 0; +} + +/* Get the "n" and "e" values from the pkey. The values are returned in "bin" format, i.e. BN_bn2bin(). */ +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 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 (!sym_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &bn_n)) + return log_openssl_errors(LOG_DEBUG, "Failed to get RSA n"); + + _cleanup_(BN_freep) BIGNUM *bn_e = NULL; + if (!sym_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &bn_e)) + return log_openssl_errors(LOG_DEBUG, "Failed to get RSA 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(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; + *ret_e = TAKE_PTR(e); + *ret_e_size = e_size; + + return 0; +} + +/* Generate ECC public key from provided curve ID and x/y points. */ +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 r; + + assert(x); + assert(y); + assert(ret); + + 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(LOG_DEBUG, "Failed to create new EVP_PKEY_CTX"); + + _cleanup_(BN_freep) BIGNUM *bn_x = sym_BN_bin2bn(x, x_size, NULL); + if (!bn_x) + return log_openssl_errors(LOG_DEBUG, "Failed to create BIGNUM x"); + + _cleanup_(BN_freep) BIGNUM *bn_y = sym_BN_bin2bn(y, y_size, NULL); + if (!bn_y) + return log_openssl_errors(LOG_DEBUG, "Failed to create BIGNUM y"); + + _cleanup_(EC_GROUP_freep) EC_GROUP *group = sym_EC_GROUP_new_by_curve_name(curve_id); + if (!group) + return log_openssl_errors(LOG_DEBUG, "ECC curve id %d not supported", curve_id); + + _cleanup_(EC_POINT_freep) EC_POINT *point = sym_EC_POINT_new(group); + if (!point) + return log_openssl_errors(LOG_DEBUG, "Failed to create new EC_POINT"); + + if (!sym_EC_POINT_set_affine_coordinates(group, point, bn_x, bn_y, NULL)) + return log_openssl_errors(LOG_DEBUG, "Failed to set ECC coordinates"); + + if (sym_EVP_PKEY_fromdata_init(ctx) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize EVP_PKEY_CTX"); + + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); + if (!bld) + return log_openssl_errors(LOG_DEBUG, "Failed to create new OSSL_PARAM_BLD"); + + 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(LOG_DEBUG, "Failed to add ECC OSSL_PKEY_PARAM_GROUP_NAME"); + + _cleanup_(OPENSSL_freep) void *pbuf = NULL; + size_t pbuf_len = 0; + pbuf_len = sym_EC_POINT_point2buf(group, point, POINT_CONVERSION_UNCOMPRESSED, (unsigned char**) &pbuf, NULL); + if (pbuf_len == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to convert ECC point to buffer"); + + if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pbuf, pbuf_len)) + return log_openssl_errors(LOG_DEBUG, "Failed to add ECC OSSL_PKEY_PARAM_PUB_KEY"); + + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); + if (!params) + return log_openssl_errors(LOG_DEBUG, "Failed to build ECC OSSL_PARAM"); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + if (sym_EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to create ECC EVP_PKEY"); + + *ret = TAKE_PTR(pkey); + return 0; +} + +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) { + + _cleanup_(BN_freep) BIGNUM *bn_x = NULL, *bn_y = NULL; + int curve_id, r; + + assert(pkey); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + size_t name_size; + if (!sym_EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, NULL, 0, &name_size)) + return log_openssl_errors(LOG_DEBUG, "Failed to get ECC group name size"); + + _cleanup_free_ char *name = new(char, name_size + 1); + if (!name) + return log_oom_debug(); + + if (!sym_EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, name, name_size + 1, NULL)) + return log_openssl_errors(LOG_DEBUG, "Failed to get ECC group name"); + + curve_id = sym_OBJ_sn2nid(name); + if (curve_id == NID_undef) + return log_openssl_errors(LOG_DEBUG, "Failed to get ECC curve id"); + + if (!sym_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &bn_x)) + return log_openssl_errors(LOG_DEBUG, "Failed to get ECC point x"); + + if (!sym_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &bn_y)) + return log_openssl_errors(LOG_DEBUG, "Failed to get ECC point 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(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; + if (ret_x) + *ret_x = TAKE_PTR(x); + if (ret_x_size) + *ret_x_size = x_size; + if (ret_y) + *ret_y = TAKE_PTR(y); + if (ret_y_size) + *ret_y_size = y_size; + + return 0; +} + +/* 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); + + 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(LOG_DEBUG, "Failed to create new EVP_PKEY_CTX"); + + if (sym_EVP_PKEY_keygen_init(ctx) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize EVP_PKEY_CTX"); + + if (sym_EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curve_id) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set ECC curve %d", curve_id); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + if (sym_EVP_PKEY_keygen(ctx, &pkey) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to generate ECC key"); + + *ret = TAKE_PTR(pkey); + + return 0; +} + +/* Perform ECDH to derive an ECC shared secret between the provided private key and public peer key. For two + * keys, this will result in the same shared secret in either direction; ECDH using Alice's private key and + * Bob's public (peer) key will result in the same shared secret as ECDH using Bob's private key and Alice's + * public (peer) key. On success, this returns 0 and provides the shared secret; otherwise this returns an + * error. */ +int ecc_ecdh(const EVP_PKEY *private_pkey, + const EVP_PKEY *peer_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); + + 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(LOG_DEBUG, "Failed to create new EVP_PKEY_CTX"); + + if (sym_EVP_PKEY_derive_init(ctx) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize EVP_PKEY_CTX"); + + if (sym_EVP_PKEY_derive_set_peer(ctx, (EVP_PKEY*) peer_pkey) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set ECC derive peer"); + + size_t shared_secret_size; + if (sym_EVP_PKEY_derive(ctx, NULL, &shared_secret_size) <= 0) + return log_openssl_errors(LOG_DEBUG, "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 (sym_EVP_PKEY_derive(ctx, (unsigned char*) shared_secret, &shared_secret_size) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to derive ECC shared secret"); + + *ret_shared_secret = TAKE_PTR(shared_secret); + *ret_shared_secret_size = shared_secret_size; + + return 0; +} + +int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size) { + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* m = NULL; + _cleanup_free_ void *d = NULL, *h = NULL; + int sz, lsz, msz; + unsigned umsz; + unsigned char *dd; + int r; + + /* Calculates a message digest of the DER encoded public key */ + + assert(pk); + assert(md); + assert(ret); + assert(ret_size); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + sz = sym_i2d_PublicKey(pk, NULL); + if (sz < 0) + return log_openssl_errors(LOG_DEBUG, "Unable to convert public key to DER format"); + + dd = d = malloc(sz); + if (!d) + return log_oom_debug(); + + lsz = sym_i2d_PublicKey(pk, &dd); + if (lsz < 0) + return log_openssl_errors(LOG_DEBUG, "Unable to convert public key to DER format"); + + m = sym_EVP_MD_CTX_new(); + if (!m) + return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_MD_CTX"); + + if (sym_EVP_DigestInit_ex(m, md, NULL) != 1) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize %s context", sym_EVP_MD_get0_name(md)); + + if (sym_EVP_DigestUpdate(m, d, lsz) != 1) + return log_openssl_errors(LOG_DEBUG, "Failed to run %s context", sym_EVP_MD_get0_name(md)); + + msz = sym_EVP_MD_get_size(md); + assert(msz > 0); + + h = malloc(msz); + if (!h) + return log_oom_debug(); + + umsz = msz; + if (sym_EVP_DigestFinal_ex(m, h, &umsz) != 1) + return log_openssl_errors(LOG_DEBUG, "Failed to finalize hash context"); + + assert(umsz == (unsigned) msz); + + *ret = TAKE_PTR(h); + *ret_size = msz; + + return 0; +} + +int digest_and_sign( + const EVP_MD *md, + EVP_PKEY *privkey, + const void *data, size_t size, + void **ret, size_t *ret_size) { + + int r; + + assert(privkey); + 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 { + assert(data); + + if (size == SIZE_MAX) /* If SIZE_MAX input is a string whose size we determine automatically */ + size = strlen(data); + } + + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = sym_EVP_MD_CTX_new(); + if (!mdctx) + return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_MD_CTX"); + + /* Note that a NULL 'md' (message digest algorithm) means to sign the provided data directly, without + * hashing it first, as long as a suitable signing algorithm is used that supports this, such as + * Ed25519 (PureEdDSA). For such algorithms callers may pass an already calculated digest as input. */ + 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(sym_ERR_peek_last_error()) == EVP_R_INVALID_DIGEST; + r = log_openssl_errors(LOG_DEBUG, "Failed to initialize signature context"); + return invalid_digest ? -EADDRNOTAVAIL : r; + } + + /* Determine signature size */ + size_t ss; + if (sym_EVP_DigestSign(mdctx, NULL, &ss, data, size) != 1) + return log_openssl_errors(LOG_DEBUG, "Failed to determine size of signature"); + + _cleanup_free_ void *sig = malloc(ss); + if (!sig) + return log_oom_debug(); + + if (sym_EVP_DigestSign(mdctx, sig, &ss, data, size) != 1) + return log_openssl_errors(LOG_DEBUG, "Failed to sign data"); + + *ret = TAKE_PTR(sig); + *ret_size = ss; + return 0; +} + +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); + + /* This function sets up a new PKCS7 signing context. If a private key is provided, the context is + * set up for "in-band" signing with PKCS7_dataFinal(). If a private key is not provided, the context + * is set up for "out-of-band" signing, meaning the signature has to be provided by the user and + * copied into the signer info's "enc_digest" field. If the signing hash algorithm is not provided, + * SHA-256 is used. */ + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(PKCS7_freep) PKCS7 *p7 = sym_PKCS7_new(); + if (!p7) + return log_oom(); + + if (sym_PKCS7_set_type(p7, NID_pkcs7_signed) == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set PKCS7 type"); + + if (sym_PKCS7_content_new(p7, NID_pkcs7_data) == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set PKCS7 content"); + + if (sym_PKCS7_add_certificate(p7, certificate) == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set PKCS7 certificate"); + + int x509_pknid = 0; + if (sym_X509_get_signature_info(certificate, NULL, &x509_pknid, NULL, NULL) == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to get X509 digest NID"); + + const EVP_MD *md = sym_EVP_get_digestbyname(hash_algorithm ?: "SHA256"); + if (!md) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported digest algorithm '%s'", + hash_algorithm ?: "SHA256"); + + _cleanup_(PKCS7_SIGNER_INFO_freep) PKCS7_SIGNER_INFO *si = sym_PKCS7_SIGNER_INFO_new(); + if (!si) + return log_oom(); + + if (private_key) { + if (sym_PKCS7_SIGNER_INFO_set(si, certificate, private_key, md) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to configure signer info"); + } else { + if (sym_ASN1_INTEGER_set(si->version, 1) == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set signer info version"); + + if (sym_X509_NAME_set(&si->issuer_and_serial->issuer, sym_X509_get_issuer_name(certificate)) == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set signer info issuer"); + + 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_openssl_errors(LOG_DEBUG, "Failed to set signer info serial"); + + if (sym_X509_ALGOR_set0(si->digest_alg, sym_OBJ_nid2obj(sym_EVP_MD_get_type(md)), V_ASN1_NULL, NULL) == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set signer info digest algorithm"); + + if (sym_X509_ALGOR_set0(si->digest_enc_alg, sym_OBJ_nid2obj(x509_pknid), V_ASN1_NULL, NULL) == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set signer info signing algorithm"); + } + + if (sym_PKCS7_add_signer(p7, si) == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set PKCS7 signer info"); + + *ret_p7 = TAKE_PTR(p7); + if (ret_si) + /* We do not pass ownership here, 'si' object remains owned by 'p7' object. */ + *ret_si = si; + + TAKE_PTR(si); + + return 0; +} + +int string_hashsum( + const char *s, + size_t len, + const char *md_algorithm, + char **ret) { + + _cleanup_free_ void *hash = NULL; + size_t hash_size; + _cleanup_free_ char *enc = NULL; + int r; + + assert(s || len == 0); + assert(md_algorithm); + assert(ret); + + r = openssl_digest(md_algorithm, s, len, &hash, &hash_size); + if (r < 0) + return r; + + enc = hexmem(hash, hash_size); + if (!enc) + return -ENOMEM; + + *ret = TAKE_PTR(enc); + return 0; +} + +static int ecc_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) { + + 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; + size_t decrypted_key_size, saved_key_size; + int r; + + _cleanup_free_ char *curve_name = NULL; + size_t 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(LOG_DEBUG, "Failed to determine PKEY group name length"); + + len++; + curve_name = new(char, len); + if (!curve_name) + return log_oom_debug(); + + if (sym_EVP_PKEY_get_group_name(pkey, curve_name, len, &len) != 1) + return log_openssl_errors(LOG_DEBUG, "Failed to get PKEY group name"); + + 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"); + + r = ecc_ecdh(pkey_new, pkey, &decrypted_key, &decrypted_key_size); + if (r < 0) + return log_debug_errno(r, "Failed to derive shared secret: %m"); + + /* EVP_PKEY_get1_encoded_public_key() always returns uncompressed format of EC points. + See https://github.com/openssl/openssl/discussions/22835 */ + saved_key_size = sym_EVP_PKEY_get1_encoded_public_key(pkey_new, &saved_key); + if (saved_key_size == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to convert the generated public key to SEC1 format"); + + *ret_decrypted_key = TAKE_PTR(decrypted_key); + *ret_decrypted_key_size = decrypted_key_size; + *ret_saved_key = TAKE_PTR(saved_key); + *ret_saved_key_size = saved_key_size; + return 0; +} + +static int rsa_pkey_generate_volume_keys( + EVP_PKEY *pkey, + const char *digest_alg, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size, + void **ret_saved_key, + size_t *ret_saved_key_size) { + + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_free_ void *saved_key = NULL; + size_t decrypted_key_size, saved_key_size; + int r; + + assert(digest_alg); + 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."); + + log_debug("Generating %zu bytes random key.", decrypted_key_size); + + decrypted_key = malloc(decrypted_key_size); + if (!decrypted_key) + return log_oom_debug(); + + r = crypto_random_bytes(decrypted_key, decrypted_key_size); + if (r < 0) + return log_debug_errno(r, "Failed to generate random key: %m"); + + r = rsa_oaep_encrypt_bytes( + pkey, + digest_alg, + /* label= */ NULL, /* matches PKCS#11 CKZ_DATA_SPECIFIED with empty source */ + decrypted_key, decrypted_key_size, + &saved_key, &saved_key_size); + if (r < 0) + return log_debug_errno(r, "Failed to RSA-OAEP encrypt random key: %m"); + + *ret_decrypted_key = TAKE_PTR(decrypted_key); + *ret_decrypted_key_size = decrypted_key_size; + *ret_saved_key = TAKE_PTR(saved_key); + *ret_saved_key_size = saved_key_size; + return 0; +} + +int pkey_generate_volume_keys( + EVP_PKEY *pkey, + const char *rsa_oaep_digest_alg, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size, + 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); + + 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: + return rsa_pkey_generate_volume_keys(pkey, rsa_oaep_digest_alg ?: "SHA-1", ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size); + + case EVP_PKEY_EC: + return ecc_pkey_generate_volume_keys(pkey, ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size); + + case NID_undef: + 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", sym_OBJ_nid2sn(type)); + } +} + +static int load_key_from_provider( + const char *provider, + const char *private_key_uri, + 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 (!sym_OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) + return log_openssl_errors(LOG_DEBUG, "Failed to load OpenSSL provider '%s'", provider); + if (!sym_OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true)) + return log_openssl_errors(LOG_DEBUG, "Failed to load OpenSSL provider 'default'"); + + _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = sym_OSSL_STORE_open( + private_key_uri, + ui_method, + /* ui_data= */ NULL, + /* post_process= */ NULL, + /* post_process_data= */ NULL); + if (!store) + return log_openssl_errors(LOG_DEBUG, "Failed to open OpenSSL store via '%s'", private_key_uri); + + if (sym_OSSL_STORE_expect(store, OSSL_STORE_INFO_PKEY) == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to filter store by private keys"); + + _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = sym_OSSL_STORE_load(store); + if (!info) + return log_openssl_errors(LOG_DEBUG, "Failed to load OpenSSL store via '%s'", private_key_uri); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = sym_OSSL_STORE_INFO_get1_PKEY(info); + if (!private_key) + return log_openssl_errors(LOG_DEBUG, "Failed to load private key via '%s'", private_key_uri); + + *ret = TAKE_PTR(private_key); + + return 0; +} + +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 = sym_ENGINE_by_id(engine); + if (!e) + return log_openssl_errors(LOG_DEBUG, "Failed to load signing engine '%s'", engine); + + if (sym_ENGINE_init(e) == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize signing engine '%s'", engine); + + _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(LOG_DEBUG, "Failed to load private key from '%s'", private_key_uri); + REENABLE_WARNING; + + *ret = TAKE_PTR(private_key); + + return 0; +#else + return -EOPNOTSUPP; +#endif +} + +#ifndef OPENSSL_NO_UI_CONSOLE +static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) { + int r; + + 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*) sym_UI_method_get_ex_data(sym_UI_get_method(ui), 0); + if (!req) + return (sym_UI_method_get_reader(sym_UI_OpenSSL()))(ui, 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); + if (r < 0) { + log_error_errno(r, "Failed to query for PIN: %m"); + return 0; + } + + if (strv_length(l) != 1) { + log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected only a single password/pin."); + return 0; + } + + if (sym_UI_set_result(ui, uis, *l) != 0) { + log_openssl_errors(LOG_DEBUG, "Failed to set user interface result"); + return 0; + } + + return 1; + } + default: + return (sym_UI_method_get_reader(sym_UI_OpenSSL()))(ui, uis); + } +} +#endif + +static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) { + _cleanup_(erase_and_freep) char *rawkey = NULL; + _cleanup_(BIO_freep) BIO *kb = NULL; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL; + size_t rawkeysz; + int r; + + 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, + NULL, + &rawkey, &rawkeysz); + if (r < 0) + return log_debug_errno(r, "Failed to read key file '%s': %m", path); + + kb = sym_BIO_new_mem_buf(rawkey, rawkeysz); + if (!kb) + return log_oom_debug(); + + pk = sym_PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL); + if (!pk) + return log_openssl_errors(LOG_DEBUG, "Failed to parse PEM private key"); + + *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 + 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(LOG_DEBUG, "Failed to initialize openssl user interface"); + + if (sym_UI_method_set_reader(method, openssl_ask_password_ui_read) != 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set openssl user interface reader"); + + OpenSSLAskPasswordUI *ui = new(OpenSSLAskPasswordUI, 1); + if (!ui) + return log_oom_debug(); + + *ui = (OpenSSLAskPasswordUI) { + .method = TAKE_PTR(method), + .request = *request, + }; + + sym_UI_set_default_method(ui->method); + + if (sym_UI_method_set_ex_data(ui->method, 0, &ui->request) == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set extra data for UI method"); + + *ret = TAKE_PTR(ui); + return 0; +#else + return -EOPNOTSUPP; +#endif +} + +static int load_x509_certificate_from_file(const char *path, X509 **ret) { + _cleanup_free_ char *rawcert = NULL; + _cleanup_(X509_freep) X509 *cert = NULL; + _cleanup_(BIO_freep) BIO *cb = NULL; + size_t rawcertsz; + int r; + + 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, + NULL, + &rawcert, &rawcertsz); + if (r < 0) + return log_debug_errno(r, "Failed to read certificate file '%s': %m", path); + + cb = sym_BIO_new_mem_buf(rawcert, rawcertsz); + if (!cb) + return log_oom_debug(); + + 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", + sym_ERR_error_string(sym_ERR_get_error(), NULL)); + + *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 (!sym_OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) + return log_openssl_errors(LOG_DEBUG, "Failed to load OpenSSL provider '%s'", provider); + if (!sym_OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true)) + return log_openssl_errors(LOG_DEBUG, "Failed to load OpenSSL provider 'default'"); + + _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = sym_OSSL_STORE_open( + certificate_uri, + /* ui_method= */ NULL, + /* ui_method= */ NULL, + /* post_process= */ NULL, + /* post_process_data= */ NULL); + if (!store) + return log_openssl_errors(LOG_DEBUG, "Failed to open OpenSSL store via '%s'", certificate_uri); + + if (sym_OSSL_STORE_expect(store, OSSL_STORE_INFO_CERT) == 0) + return log_openssl_errors(LOG_DEBUG, "Failed to filter store by X.509 certificates"); + + _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = sym_OSSL_STORE_load(store); + if (!info) + return log_openssl_errors(LOG_DEBUG, "Failed to load OpenSSL store via '%s'", certificate_uri); + + _cleanup_(X509_freep) X509 *cert = sym_OSSL_STORE_INFO_get1_CERT(info); + if (!cert) + return log_openssl_errors(LOG_DEBUG, "Failed to load certificate via '%s'", certificate_uri); + + *ret = TAKE_PTR(cert); + + return 0; +} + +OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) { + if (!ui) + return NULL; + +#ifndef OPENSSL_NO_UI_CONSOLE + 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, r; + + assert(cert); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + dersz = sym_i2d_X509(cert, &der); + if (dersz < 0) + return log_openssl_errors(LOG_DEBUG, "Unable to convert PEM certificate to DER format"); + + sha256_direct(der, dersz, buffer); + return 0; +} + +int openssl_load_x509_certificate( + CertificateSourceType certificate_source_type, + const char *certificate_source, + const char *certificate, + X509 **ret) { + + int r; + + assert(certificate); + + switch (certificate_source_type) { + + case OPENSSL_CERTIFICATE_SOURCE_FILE: + r = load_x509_certificate_from_file(certificate, ret); + break; + case OPENSSL_CERTIFICATE_SOURCE_PROVIDER: + r = load_x509_certificate_from_provider(certificate_source, certificate, ret); + break; + default: + assert_not_reached(); + } + if (r < 0) + return log_debug_errno( + r, + "Failed to load certificate '%s' from OpenSSL certificate source %s: %m", + certificate, + certificate_source); + + return 0; +} + +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 r; + + /* The caller must keep the OpenSSLAskPasswordUI object alive as long as the EVP_PKEY object so that + * the user can enter any needed hardware token pin to unlock the private key when needed. */ + + assert(private_key); + assert(request); + assert(ret_private_key); + assert(ret_user_interface); + + if (private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { + r = openssl_load_private_key_from_file(private_key, ret_private_key); + if (r < 0) + return r; + + *ret_user_interface = NULL; + } else { + _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; + r = openssl_ask_password_ui_new(request, &ui); + if (r < 0) + return log_debug_errno(r, "Failed to allocate ask-password user interface: %m"); + + UI_METHOD *ui_method = NULL; +#ifndef OPENSSL_NO_UI_CONSOLE + ui_method = ui->method; +#endif + + switch (private_key_source_type) { + + case OPENSSL_KEY_SOURCE_ENGINE: + r = load_key_from_engine(private_key_source, private_key, ui_method, ret_private_key); + break; + case OPENSSL_KEY_SOURCE_PROVIDER: + r = load_key_from_provider(private_key_source, private_key, ui_method, ret_private_key); + break; + default: + assert_not_reached(); + } + if (r < 0) + return log_debug_errno( + r, + "Failed to load key '%s' from OpenSSL private key source %s: %m", + private_key, + private_key_source); + + *ret_user_interface = TAKE_PTR(ui); + } + + return 0; +} + +int openssl_extract_public_key(EVP_PKEY *private_key, EVP_PKEY **ret) { + int r; + + 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 (sym_i2d_PUBKEY_fp(tf, private_key) != 1) + return log_openssl_errors(LOG_DEBUG, "Failed to extract public key in DER format"); + + _cleanup_(erase_and_freep) char *buf = NULL; + size_t len; + r = memstream_finalize(&m, &buf, &len); + if (r < 0) + return r; + + const unsigned char *t = (const unsigned char*) buf; + if (!sym_d2i_PUBKEY(ret, &t, len)) + return log_openssl_errors(LOG_DEBUG, "Failed to parse public key in DER format"); + + return 0; +} +#endif + +int parse_openssl_certificate_source_argument( + const char *argument, + char **certificate_source, + CertificateSourceType *certificate_source_type) { + + CertificateSourceType type; + const char *e = NULL; + int r; + + assert(argument); + assert(certificate_source); + assert(certificate_source_type); + + if (streq(argument, "file")) + type = OPENSSL_CERTIFICATE_SOURCE_FILE; + else if ((e = startswith(argument, "provider:"))) + type = OPENSSL_CERTIFICATE_SOURCE_PROVIDER; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid certificate source '%s'", argument); + + r = free_and_strdup_warn(certificate_source, e); + if (r < 0) + return r; + + *certificate_source_type = type; + + return 0; +} + +int parse_openssl_key_source_argument( + const char *argument, + char **private_key_source, + KeySourceType *private_key_source_type) { + + KeySourceType type; + const char *e = NULL; + int r; + + assert(argument); + assert(private_key_source); + assert(private_key_source_type); + + if (streq(argument, "file")) + type = OPENSSL_KEY_SOURCE_FILE; + else if ((e = startswith(argument, "engine:"))) + type = OPENSSL_KEY_SOURCE_ENGINE; + else if ((e = startswith(argument, "provider:"))) + type = OPENSSL_KEY_SOURCE_PROVIDER; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid private key source '%s'", argument); + + r = free_and_strdup_warn(private_key_source, e); + if (r < 0) + return r; + + *private_key_source_type = type; + + return 0; +} diff --git a/src/shared/crypto-util.h b/src/shared/crypto-util.h new file mode 100644 index 0000000000000..bd7dda0931c44 --- /dev/null +++ b/src/shared/crypto-util.h @@ -0,0 +1,468 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-dlopen.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); + +int dlopen_libcrypto(int log_level); + +#define X509_FINGERPRINT_SIZE SHA256_DIGEST_SIZE + +#if HAVE_OPENSSL +#define LIBCRYPTO_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("libcrypto", \ + "Support for cryptographic operations", \ + priority, \ + "libcrypto.so.4", "libcrypto.so.3") + +#define DLOPEN_LIBCRYPTO(log_level, priority) \ + ({ \ + LIBCRYPTO_NOTE(priority); \ + dlopen_libcrypto(log_level); \ + }) + +# 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 */ + +# 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_IA5STRING_it); +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(ASN1_get_object); +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(BIO_ctrl); +extern DLSYM_PROTOTYPE(BIO_find_type); +extern DLSYM_PROTOTYPE(BIO_free); +extern DLSYM_PROTOTYPE(BIO_free_all); +extern DLSYM_PROTOTYPE(BIO_new); +extern DLSYM_PROTOTYPE(BIO_new_mem_buf); +extern DLSYM_PROTOTYPE(BIO_new_socket); +extern DLSYM_PROTOTYPE(BIO_s_mem); +extern DLSYM_PROTOTYPE(BIO_write); +extern DLSYM_PROTOTYPE(BN_CTX_free); +extern DLSYM_PROTOTYPE(BN_CTX_new); +extern DLSYM_PROTOTYPE(BN_CTX_secure_new); +extern DLSYM_PROTOTYPE(BN_add); +extern DLSYM_PROTOTYPE(BN_add_word); +extern DLSYM_PROTOTYPE(BN_bin2bn); +extern DLSYM_PROTOTYPE(BN_bn2bin); +extern DLSYM_PROTOTYPE(BN_bn2binpad); +extern DLSYM_PROTOTYPE(BN_bn2nativepad); +extern DLSYM_PROTOTYPE(BN_check_prime); +extern DLSYM_PROTOTYPE(BN_clear_free); +extern DLSYM_PROTOTYPE(BN_cmp); +extern DLSYM_PROTOTYPE(BN_copy); +extern DLSYM_PROTOTYPE(BN_free); +extern DLSYM_PROTOTYPE(BN_is_negative); +extern DLSYM_PROTOTYPE(BN_mod_exp); +extern DLSYM_PROTOTYPE(BN_mod_inverse); +extern DLSYM_PROTOTYPE(BN_mod_lshift1_quick); +extern DLSYM_PROTOTYPE(BN_mod_mul); +extern DLSYM_PROTOTYPE(BN_mod_sqr); +extern DLSYM_PROTOTYPE(BN_mod_sub); +extern DLSYM_PROTOTYPE(BN_mul); +extern DLSYM_PROTOTYPE(BN_new); +extern DLSYM_PROTOTYPE(BN_nnmod); +extern DLSYM_PROTOTYPE(BN_num_bits); +extern DLSYM_PROTOTYPE(BN_secure_new); +extern DLSYM_PROTOTYPE(BN_set_word); +extern DLSYM_PROTOTYPE(BN_sub_word); +extern DLSYM_PROTOTYPE(CRYPTO_free); +extern DLSYM_PROTOTYPE(ECDSA_SIG_free); +extern DLSYM_PROTOTYPE(EC_GROUP_free); +extern DLSYM_PROTOTYPE(EC_GROUP_get0_generator); +extern DLSYM_PROTOTYPE(EC_GROUP_get0_order); +extern DLSYM_PROTOTYPE(EC_GROUP_get_curve); +extern DLSYM_PROTOTYPE(EC_GROUP_get_curve_name); +extern DLSYM_PROTOTYPE(EC_GROUP_get_field_type); +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(ERR_clear_error); +extern DLSYM_PROTOTYPE(ERR_error_string); +extern DLSYM_PROTOTYPE(ERR_error_string_n); +extern DLSYM_PROTOTYPE(ERR_get_error); +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_MAC_CTX_free); +extern DLSYM_PROTOTYPE(EVP_MAC_CTX_new); +extern DLSYM_PROTOTYPE(EVP_MAC_fetch); +extern DLSYM_PROTOTYPE(EVP_MAC_final); +extern DLSYM_PROTOTYPE(EVP_MAC_free); +extern DLSYM_PROTOTYPE(EVP_MAC_init); +extern DLSYM_PROTOTYPE(EVP_MAC_update); +extern DLSYM_PROTOTYPE(EVP_MD_CTX_copy_ex); +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_get0_name); +extern DLSYM_PROTOTYPE(EVP_MD_get_size); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_free); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_new); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_new_from_name); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_new_id); +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); +extern DLSYM_PROTOTYPE(EVP_PKEY_fromdata_init); +extern DLSYM_PROTOTYPE(EVP_PKEY_get_base_id); +extern DLSYM_PROTOTYPE(EVP_PKEY_get_id); +extern DLSYM_PROTOTYPE(EVP_PKEY_keygen); +extern DLSYM_PROTOTYPE(EVP_PKEY_keygen_init); +extern DLSYM_PROTOTYPE(EVP_PKEY_new); +extern DLSYM_PROTOTYPE(EVP_PKEY_new_raw_public_key); +extern DLSYM_PROTOTYPE(EVP_PKEY_verify); +extern DLSYM_PROTOTYPE(EVP_PKEY_verify_init); +extern DLSYM_PROTOTYPE(EVP_aes_256_ctr); +extern DLSYM_PROTOTYPE(EVP_aes_256_gcm); +extern DLSYM_PROTOTYPE(EVP_get_cipherbyname); +extern DLSYM_PROTOTYPE(EVP_get_digestbyname); +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(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_BLD_free); +extern DLSYM_PROTOTYPE(OSSL_PARAM_BLD_new); +extern DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_utf8_string); +extern DLSYM_PROTOTYPE(OSSL_PARAM_BLD_to_param); +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_PUBKEY); +extern DLSYM_PROTOTYPE(PEM_read_PrivateKey); +extern DLSYM_PROTOTYPE(PEM_read_X509); +extern DLSYM_PROTOTYPE(PEM_write_PUBKEY); +extern DLSYM_PROTOTYPE(PEM_write_PrivateKey); +extern DLSYM_PROTOTYPE(PEM_write_X509); +extern DLSYM_PROTOTYPE(PKCS5_PBKDF2_HMAC); +extern DLSYM_PROTOTYPE(PKCS7_ATTR_SIGN_it); +extern DLSYM_PROTOTYPE(PKCS7_SIGNER_INFO_free); +extern DLSYM_PROTOTYPE(PKCS7_add0_attrib_signing_time); +extern DLSYM_PROTOTYPE(PKCS7_add1_attrib_digest); +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_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_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_NAME_free); +extern DLSYM_PROTOTYPE(X509_NAME_oneline); +extern DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set1_host); +extern DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set1_ip); +extern DLSYM_PROTOTYPE(X509_VERIFY_PARAM_set_hostflags); +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(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(i2d_ASN1_INTEGER); +extern DLSYM_PROTOTYPE(i2d_PKCS7); +extern DLSYM_PROTOTYPE(i2d_PKCS7_fp); +extern DLSYM_PROTOTYPE(i2d_PUBKEY); +extern DLSYM_PROTOTYPE(i2d_X509); +extern DLSYM_PROTOTYPE(i2d_X509_NAME); + +#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(RSAPublicKey_dup); +extern DLSYM_PROTOTYPE(RSA_free); +extern DLSYM_PROTOTYPE(RSA_new); +extern DLSYM_PROTOTYPE(RSA_set0_key); +extern DLSYM_PROTOTYPE(RSA_size); +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_BN_one(a) sym_BN_set_word(a, 1) +#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(BIGNUM*, sym_BN_clear_free, BN_clear_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_BLD*, sym_OSSL_PARAM_BLD_free, OSSL_PARAM_BLD_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); +} + +/* Translates an OpenSSL error code (as returned by ERR_get_error()) into a negative errno. Returns + * -ENOTRECOVERABLE when passed 0 or when the error's reason has no more specific errno. */ +int openssl_to_errno(unsigned long e); + +int log_openssl_errors_internal(int level, const char *file, int line, const char *func, const char *format, ...) _printf_(5, 6); + +/* Logs `format` at `level`, suffixed with each error from the OpenSSL thread-local error queue (or + * "No OpenSSL errors." when it is empty), and returns a negative errno derived from the last error + * (-ENOTRECOVERABLE when the queue is empty or the reason isn't recognized). */ +#define log_openssl_errors(level, format, ...) \ + log_openssl_errors_internal(level, PROJECT_FILE, __LINE__, __func__, format, ##__VA_ARGS__) + +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_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, const char *rsa_oaep_digest_alg, 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); + +#else +#define DLOPEN_LIBCRYPTO(log_level, priority) dlopen_libcrypto(log_level) +#endif diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index a766a92b2037f..35a59721f8842 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" @@ -16,11 +17,11 @@ #include "strv.h" #if HAVE_LIBCRYPTSETUP -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 +37,16 @@ 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_logf) = 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,16 +54,28 @@ 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; +static int missing_crypt_set_keyring_to_link( + struct crypt_device *cd, + const char *key_description, + const char *old_key_description, + const char *key_type_desc, + const char *keyring_to_link_vk) { + return -ENOSYS; +} +DLSYM_PROTOTYPE(crypt_set_keyring_to_link) = missing_crypt_set_keyring_to_link; 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; -#if HAVE_CRYPT_TOKEN_SET_EXTERNAL_PATH -DLSYM_PROTOTYPE(crypt_token_set_external_path) = NULL; -#endif +static int missing_crypt_token_set_external_path(const char *path) { + return -ENOSYS; +} +DLSYM_PROTOTYPE(crypt_token_set_external_path) = missing_crypt_token_set_external_path; DLSYM_PROTOTYPE(crypt_token_status) = NULL; DLSYM_PROTOTYPE(crypt_volume_key_get) = NULL; DLSYM_PROTOTYPE(crypt_volume_key_keyring) = NULL; @@ -95,7 +113,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 +139,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 +166,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 +195,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 +217,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 +253,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,8 +271,9 @@ int cryptsetup_get_volume_key_id( } #endif -int dlopen_cryptsetup(void) { +int dlopen_cryptsetup(int log_level) { #if HAVE_LIBCRYPTSETUP + static void *cryptsetup_dl = NULL; int r; /* libcryptsetup added crypt_reencrypt() in 2.2.0, and marked it obsolete in 2.4.0, replacing it with @@ -270,16 +281,15 @@ 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", - "Support for disk encryption, integrity, and authentication", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, - "libcryptsetup.so.12"); + CRYPTSETUP_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); 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 +305,16 @@ 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_logf), 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), @@ -310,13 +325,12 @@ int dlopen_cryptsetup(void) { 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), -#if HAVE_CRYPT_TOKEN_SET_EXTERNAL_PATH - DLSYM_ARG(crypt_token_set_external_path), -#endif DLSYM_ARG(crypt_token_status), DLSYM_ARG(crypt_volume_key_get), DLSYM_ARG(crypt_volume_key_keyring), @@ -325,6 +339,12 @@ int dlopen_cryptsetup(void) { if (r <= 0) return r; + /* Optional symbols: present in libcryptsetup 2.7+ only. If unresolved, the prototype keeps its + * static initializer pointing at a fallback that returns -ENOSYS, so call sites can invoke the + * symbol unconditionally. */ + DLSYM_OPTIONAL(cryptsetup_dl, crypt_set_keyring_to_link); + DLSYM_OPTIONAL(cryptsetup_dl, crypt_token_set_external_path); + /* Redirect the default logging calls of libcryptsetup to our own logging infra. (Note that * libcryptsetup also maintains per-"struct crypt_device" log functions, which we'll also set * whenever allocating a "struct crypt_device" context. Why set both? To be defensive: maybe some @@ -334,18 +354,17 @@ int dlopen_cryptsetup(void) { const char *e = secure_getenv("SYSTEMD_CRYPTSETUP_TOKEN_PATH"); if (e) { -#if HAVE_CRYPT_TOKEN_SET_EXTERNAL_PATH r = sym_crypt_token_set_external_path(e); - if (r < 0) + if (r == -ENOSYS) + log_debug("Loaded libcryptsetup does not support setting the external token path, not setting it to '%s'.", e); + else if (r < 0) log_debug_errno(r, "Failed to set the libcryptsetup external token path to '%s', ignoring: %m", e); -#else - log_debug("libcryptsetup version does not support setting the external token path, not setting it to '%s'.", e); -#endif } 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..1b3ab8b9604f0 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -1,15 +1,32 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dlopen.h" + #include "dlfcn-util.h" #include "shared-forward.h" #if HAVE_LIBCRYPTSETUP #include /* IWYU pragma: export */ +/* Available since libcryptsetup 2.7. Always redeclare so DLSYM_PROTOTYPE's typeof() resolves on older + * headers; suppress the warning when newer libcryptsetup already declares them. */ +DISABLE_WARNING_REDUNDANT_DECLS; +/* NOLINTBEGIN(readability-redundant-declaration) */ +extern int crypt_set_keyring_to_link(struct crypt_device *cd, + const char *key_description, + const char *old_key_description, + const char *key_type_desc, + const char *keyring_to_link_vk); +extern int crypt_token_set_external_path(const char *path); +/* NOLINTEND(readability-redundant-declaration) */ +REENABLE_WARNING; + 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 +42,16 @@ 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_logf); 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,26 +59,26 @@ 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); +extern DLSYM_PROTOTYPE(crypt_set_keyring_to_link); 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); -#if HAVE_CRYPT_TOKEN_SET_EXTERNAL_PATH extern DLSYM_PROTOTYPE(crypt_token_set_external_path); -#endif extern DLSYM_PROTOTYPE(crypt_token_status); extern DLSYM_PROTOTYPE(crypt_volume_key_get); 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) @@ -69,9 +91,23 @@ int cryptsetup_add_token_json(struct crypt_device *cd, sd_json_variant *v); int cryptsetup_get_volume_key_prefix(struct crypt_device *cd, const char *volume_name, char **ret); int cryptsetup_get_volume_key_id(struct crypt_device *cd, const char *volume_name, const void *volume_key, size_t volume_key_size, char **ret); + +#define CRYPTSETUP_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("cryptsetup", \ + "Support for disk encryption, integrity, and authentication", \ + priority, \ + "libcryptsetup.so.12") + +#define DLOPEN_CRYPTSETUP(log_level, priority) \ + ({ \ + CRYPTSETUP_NOTE(priority); \ + dlopen_cryptsetup(log_level); \ + }) +#else +#define DLOPEN_CRYPTSETUP(log_level, priority) dlopen_cryptsetup(log_level) #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/shared/curl-util.c b/src/shared/curl-util.c new file mode 100644 index 0000000000000..961a97ad2f795 --- /dev/null +++ b/src/shared/curl-util.c @@ -0,0 +1,637 @@ +/* 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 "dlfcn-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "version.h" + +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; +static DLSYM_PROTOTYPE(curl_multi_add_handle) = NULL; +static DLSYM_PROTOTYPE(curl_multi_assign) = NULL; +static DLSYM_PROTOTYPE(curl_multi_cleanup) = NULL; +static DLSYM_PROTOTYPE(curl_multi_info_read) = NULL; +static DLSYM_PROTOTYPE(curl_multi_init) = NULL; +static DLSYM_PROTOTYPE(curl_multi_remove_handle) = NULL; +static DLSYM_PROTOTYPE(curl_multi_setopt) = NULL; +static DLSYM_PROTOTYPE(curl_multi_socket_action) = NULL; +DLSYM_PROTOTYPE(curl_slist_append) = NULL; +DLSYM_PROTOTYPE(curl_slist_free_all) = NULL; + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(CURLM*, sym_curl_multi_cleanup, curl_multi_cleanupp, NULL); + +struct CurlGlue { + unsigned n_ref; + sd_event *event; + CURLM *curl; + sd_event_source *timer; + Hashmap *ios; + sd_event_source *defer; + Set *slots; /* CurlSlot* — back-pointer set; floating slots are kept alive here */ +}; + +struct CurlSlot { + unsigned n_ref; + CurlGlue *glue; /* NULL once disconnected (callback fired, cancelled, or glue died) */ + CURL *easy; /* owned; cleared once the easy handle has been freed */ + bool floating; + curl_finished_t callback; + void *userdata; +}; + +static void curl_slot_disconnect(CurlSlot *slot, bool unref) { + assert(slot); + + /* Tear down the slot's connection to the glue: pull the easy handle out of the multi, + * curl_easy_cleanup() it, and remove the slot from the glue's lookup set. Floating + * slots are owned by that set, so on disconnect we drop the implicit ref (when + * unref=true; the recursive call from curl_slot_free passes false to avoid infinite + * recursion). Non-floating slots release the back-ref they held on the glue. + * + * Idempotent: once slot->glue is NULL, subsequent calls are no-ops. */ + + if (!slot->glue) + return; + + CurlGlue *glue = slot->glue; + + if (slot->easy) { + if (glue->curl) + (void) sym_curl_multi_remove_handle(glue->curl, slot->easy); + sym_curl_easy_cleanup(slot->easy); + slot->easy = NULL; + } + + set_remove(glue->slots, slot); + slot->glue = NULL; + + if (!slot->floating) + curl_glue_unref(glue); + else if (unref) + curl_slot_unref(slot); +} + +static CurlSlot* curl_slot_free(CurlSlot *slot) { + if (!slot) + return NULL; + + curl_slot_disconnect(slot, /* unref= */ false); + return mfree(slot); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(CurlSlot, curl_slot, curl_slot_free); + +CURL* curl_slot_get_easy(CurlSlot *slot) { + assert(slot); + return slot->easy; +} + +CurlGlue* curl_slot_get_glue(CurlSlot *slot) { + assert(slot); + return slot->glue; +} + +static void curl_glue_check_finished(CurlGlue *g) { + int r; + + assert(g); + + /* sd_event_get_exit_code() returns -ENODATA if no exit was scheduled yet */ + r = sd_event_get_exit_code(g->event, /* ret= */ NULL); + if (r >= 0) + return; /* exit scheduled? Then don't process this anymore */ + if (r != -ENODATA) + log_debug_errno(r, "Unexpected error while checking for event loop exit code, ignoring: %m"); + + CURLMsg *msg; + int k = 0; + msg = sym_curl_multi_info_read(g->curl, &k); + if (!msg) + return; + + if (msg->msg == CURLMSG_DONE) { + CURL *easy = msg->easy_handle; + CURLcode code = msg->data.result; + CurlSlot *slot = NULL; + + if (sym_curl_easy_getinfo(easy, CURLINFO_PRIVATE, (char**) &slot) == CURLE_OK && slot) { + /* Pin the slot across the callback: a floating slot's only + * reference is the one held via the glue's slots set, and + * disconnect drops it. */ + curl_slot_ref(slot); + + if (slot->callback) { + r = slot->callback(slot, easy, code, slot->userdata); + if (r < 0) + log_debug_errno(r, "Curl finished callback returned error, ignoring: %m"); + } + + curl_slot_disconnect(slot, /* unref= */ true); + curl_slot_unref(slot); + } + } + + /* This is a queue, process another item soon, but do so in a later event loop iteration. */ + (void) sd_event_source_set_enabled(g->defer, SD_EVENT_ONESHOT); +} + +static int curl_glue_on_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + CurlGlue *g = ASSERT_PTR(userdata); + int action, k = 0; + + assert(s); + + if (FLAGS_SET(revents, EPOLLIN | EPOLLOUT)) + action = CURL_POLL_INOUT; + else if (revents & EPOLLIN) + action = CURL_POLL_IN; + else if (revents & EPOLLOUT) + action = CURL_POLL_OUT; + else + action = 0; + + 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."); + + curl_glue_check_finished(g); + return 0; +} + +static int curl_glue_socket_callback(CURL *curl, curl_socket_t s, int action, void *userdata, void *socketp) { + sd_event_source *io = socketp; + CurlGlue *g = ASSERT_PTR(userdata); + uint32_t events = 0; + int r; + + assert(curl); + + if (action == CURL_POLL_REMOVE) { + if (io) { + sd_event_source_disable_unref(io); + + hashmap_remove(g->ios, FD_TO_PTR(s)); + } + + return 0; + } + + /* Don't configure io event source anymore when the event loop is dead already. */ + if (g->event && sd_event_get_state(g->event) == SD_EVENT_FINISHED) + return 0; + + r = hashmap_ensure_allocated(&g->ios, &trivial_hash_ops); + if (r < 0) { + log_oom(); + return -1; + } + + if (action == CURL_POLL_IN) + events = EPOLLIN; + else if (action == CURL_POLL_OUT) + events = EPOLLOUT; + else if (action == CURL_POLL_INOUT) + events = EPOLLIN|EPOLLOUT; + + if (io) { + if (sd_event_source_set_io_events(io, events) < 0) + return -1; + + if (sd_event_source_set_enabled(io, SD_EVENT_ON) < 0) + return -1; + } else { + if (sd_event_add_io(g->event, &io, s, events, curl_glue_on_io, g) < 0) + return -1; + + if (sym_curl_multi_assign(g->curl, s, io) != CURLM_OK) + return -1; + + (void) sd_event_source_set_description(io, "curl-io"); + + r = hashmap_put(g->ios, FD_TO_PTR(s), io); + if (r < 0) { + log_oom(); + sd_event_source_unref(io); + return -1; + } + } + + return 0; +} + +static int curl_glue_on_timer(sd_event_source *s, uint64_t usec, void *userdata) { + CurlGlue *g = ASSERT_PTR(userdata); + int k = 0; + + assert(s); + + 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."); + + curl_glue_check_finished(g); + return 0; +} + +static int curl_glue_timer_callback(CURLM *curl, long timeout_ms, void *userdata) { + CurlGlue *g = ASSERT_PTR(userdata); + usec_t usec; + + assert(curl); + + /* Don't configure timer anymore when the event loop is dead already. */ + if (g->timer) { + sd_event *event_loop = sd_event_source_get_event(g->timer); + if (event_loop && sd_event_get_state(event_loop) == SD_EVENT_FINISHED) + return 0; + } + + if (timeout_ms < 0) { + if (sd_event_source_set_enabled(g->timer, SD_EVENT_OFF) < 0) + return -1; + + return 0; + } + + usec = (usec_t) timeout_ms * USEC_PER_MSEC + USEC_PER_MSEC - 1; + + if (g->timer) { + if (sd_event_source_set_time_relative(g->timer, usec) < 0) + return -1; + + if (sd_event_source_set_enabled(g->timer, SD_EVENT_ONESHOT) < 0) + return -1; + } else { + if (sd_event_add_time_relative(g->event, &g->timer, CLOCK_BOOTTIME, usec, 0, curl_glue_on_timer, g) < 0) + return -1; + + (void) sd_event_source_set_description(g->timer, "curl-timer"); + } + + return 0; +} + +static int curl_glue_on_defer(sd_event_source *s, void *userdata) { + CurlGlue *g = ASSERT_PTR(userdata); + + assert(s); + + curl_glue_check_finished(g); + return 0; +} + +static CurlGlue* curl_glue_free(CurlGlue *g) { + sd_event_source *io; + CurlSlot *slot; + + if (!g) + return NULL; + + /* Drain any slots still hanging off us. By construction only floating slots can + * be here: connected non-floating slots hold a glue back-ref, so glue's last ref + * couldn't have dropped while one was attached. disconnect(unref=true) does the + * floating slot's free as part of its work. set_steal_first() pops up front so + * forward progress doesn't depend on disconnect's internal set_remove(). */ + while ((slot = set_steal_first(g->slots))) + curl_slot_disconnect(slot, /* unref= */ true); + g->slots = set_free(g->slots); + + if (g->curl) + sym_curl_multi_cleanup(g->curl); + + while ((io = hashmap_steal_first(g->ios))) + sd_event_source_unref(io); + + hashmap_free(g->ios); + + sd_event_source_disable_unref(g->timer); + sd_event_source_disable_unref(g->defer); + sd_event_unref(g->event); + return mfree(g); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(CurlGlue, curl_glue, curl_glue_free); + +int curl_glue_new(CurlGlue **glue, sd_event *event) { + _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; + _cleanup_(curl_multi_cleanupp) CURLM *c = NULL; + _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 { + r = sd_event_default(&e); + if (r < 0) + return r; + } + + c = sym_curl_multi_init(); + if (!c) + return -ENOMEM; + + g = new(CurlGlue, 1); + if (!g) + return -ENOMEM; + + *g = (CurlGlue) { + .n_ref = 1, + .event = TAKE_PTR(e), + .curl = TAKE_PTR(c), + }; + + if (sym_curl_multi_setopt(g->curl, CURLMOPT_SOCKETDATA, g) != CURLM_OK) + return -EINVAL; + + if (sym_curl_multi_setopt(g->curl, CURLMOPT_SOCKETFUNCTION, curl_glue_socket_callback) != CURLM_OK) + return -EINVAL; + + if (sym_curl_multi_setopt(g->curl, CURLMOPT_TIMERDATA, g) != CURLM_OK) + return -EINVAL; + + 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); + if (r < 0) + return r; + + (void) sd_event_source_set_description(g->defer, "curl-defer"); + + *glue = TAKE_PTR(g); + + return 0; +} + +int curl_glue_make(CURL **ret, const char *url) { + _cleanup_(curl_easy_cleanupp) CURL *c = NULL; + const char *useragent; + int r; + + assert(ret); + assert(url); + + r = dlopen_curl(LOG_DEBUG); + if (r < 0) + return r; + + c = sym_curl_easy_init(); + if (!c) + return -ENOMEM; + + if (DEBUG_LOGGING) + (void) sym_curl_easy_setopt(c, CURLOPT_VERBOSE, 1L); + + if (sym_curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK) + return -EIO; + + useragent = strjoina(program_invocation_short_name, "/" GIT_VERSION); + if (sym_curl_easy_setopt(c, CURLOPT_USERAGENT, useragent) != CURLE_OK) + return -EIO; + + if (sym_curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) + return -EIO; + + if (sym_curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L) != CURLE_OK) + return -EIO; + + if (sym_curl_easy_setopt(c, CURLOPT_LOW_SPEED_TIME, 60L) != CURLE_OK) + return -EIO; + + 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 (sym_curl_easy_setopt(c, CURLOPT_PROTOCOLS_STR, "HTTP,HTTPS,FILE") != CURLE_OK) +#else + 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; + + *ret = TAKE_PTR(c); + return 0; +} + +int curl_glue_perform_async( + CurlGlue *g, + CURL *easy, + curl_finished_t cb, + void *userdata, + CurlSlot **ret_slot) { + + int r; + + assert(g); + assert(easy); + + _cleanup_(curl_slot_unrefp) CurlSlot *slot = new(CurlSlot, 1); + if (!slot) + return -ENOMEM; + + *slot = (CurlSlot) { + .n_ref = 1, + .glue = NULL, /* wired up below, after we've committed to the multi */ + .easy = easy, + .floating = !ret_slot, + .callback = cb, + .userdata = userdata, + }; + + r = set_ensure_put(&g->slots, &trivial_hash_ops, slot); + if (r < 0) + return r; + assert(r > 0); + + if (sym_curl_multi_add_handle(g->curl, easy) != CURLM_OK) { + set_remove(g->slots, slot); + return -EIO; + } + + /* Stash the slot pointer on the easy handle so curl_glue_check_finished() can recover + * it on completion. Set this only after we've fully committed to the multi, so that + * error paths above don't leave a dangling pointer on the easy handle. */ + if (sym_curl_easy_setopt(easy, CURLOPT_PRIVATE, slot) != CURLE_OK) { + sym_curl_multi_remove_handle(g->curl, easy); + set_remove(g->slots, slot); + return -EIO; + } + + slot->glue = g; + if (!slot->floating) + curl_glue_ref(g); + + /* Transfer the slot's single reference: to the caller for non-floating slots, or to + * the glue's slot set (implicitly, until disconnect drops it) for floating ones. */ + if (ret_slot) + *ret_slot = slot; + + TAKE_PTR(slot); + return 0; +} + +struct curl_slist *curl_slist_new(const char *first, ...) { + struct curl_slist *l; + va_list ap; + + if (!first) + return NULL; + + l = sym_curl_slist_append(NULL, first); + if (!l) + return NULL; + + va_start(ap, first); + + for (;;) { + struct curl_slist *n; + const char *i; + + i = va_arg(ap, const char*); + if (!i) + break; + + n = sym_curl_slist_append(l, i); + if (!n) { + va_end(ap); + sym_curl_slist_free_all(l); + return NULL; + } + + l = n; + } + + va_end(ap); + return l; +} + +int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value) { + const char *p; + char *s; + + assert(value); + + p = memory_startswith_no_case(contents, sz, field); + if (!p) + return 0; + + sz -= p - (const char*) contents; + + if (memchr(p, 0, sz)) + return 0; + + /* Skip over preceding whitespace */ + while (sz > 0 && strchr(WHITESPACE, p[0])) { + p++; + sz--; + } + + /* Truncate trailing whitespace */ + while (sz > 0 && strchr(WHITESPACE, p[sz-1])) + sz--; + + s = strndup(p, sz); + if (!s) + return -ENOMEM; + + *value = s; + return 1; +} + +int curl_parse_http_time(const char *t, usec_t *ret) { + assert(t); + assert(ret); + + time_t v = sym_curl_getdate(t, NULL); + if (v == (time_t) -1) + return -EINVAL; + + if ((usec_t) v >= USEC_INFINITY / USEC_PER_SEC) /* check overflow */ + return -ERANGE; + + *ret = (usec_t) v * USEC_PER_SEC; + + 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 + static void *curl_dl = NULL; + + CURL_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); + + 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..33bf3684c9eea --- /dev/null +++ b/src/shared/curl-util.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-dlopen.h" + +#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_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 int (*curl_finished_t)(CurlSlot *slot, CURL *curl, CURLcode code, void *userdata); + +int curl_glue_new(CurlGlue **glue, sd_event *event); +DECLARE_TRIVIAL_REF_UNREF_FUNC(CurlGlue, curl_glue); +DEFINE_TRIVIAL_CLEANUP_FUNC(CurlGlue*, curl_glue_unref); + +/* Build a CURL easy handle with sane defaults. The caller configures any + * additional options (headers, write callbacks, …) before handing it off to + * curl_glue_perform_async(). */ +int curl_glue_make(CURL **ret, const char *url); + +/* Hand a configured CURL easy handle off to the multi for execution. The slot + * takes ownership of the easy handle: once the slot is released (the callback + * has fired, the caller has dropped its last ref, or the glue is being freed), + * the handle is removed from the multi and freed. + * + * If ret_slot is NULL the slot is allocated as floating: the glue keeps it + * alive until the callback fires or the glue is torn down. Otherwise a + * reference is returned to the caller; releasing that reference cancels the + * call. */ +int curl_glue_perform_async( + CurlGlue *g, + CURL *easy, + curl_finished_t cb, + void *userdata, + CurlSlot **ret_slot); + +CURL* curl_slot_get_easy(CurlSlot *slot); +CurlGlue* curl_slot_get_glue(CurlSlot *slot); + +DECLARE_TRIVIAL_REF_UNREF_FUNC(CurlSlot, curl_slot); +DEFINE_TRIVIAL_CLEANUP_FUNC(CurlSlot*, curl_slot_unref); + +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(struct curl_slist*, sym_curl_slist_free_all, curl_slist_free_allp, NULL); + +#define CURL_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("curl", \ + "Support for downloading and uploading files over HTTP", \ + priority, \ + "libcurl.so.4") + +#define DLOPEN_CURL(log_level, priority) \ + ({ \ + CURL_NOTE(priority); \ + dlopen_curl(log_level); \ + }) +#else +#define DLOPEN_CURL(log_level, priority) dlopen_curl(log_level) +#endif + +int dlopen_curl(int log_level); diff --git a/src/shared/daemon-util.c b/src/shared/daemon-util.c index 321a9e58dafc3..d85e52bcb68c3 100644 --- a/src/shared/daemon-util.c +++ b/src/shared/daemon-util.c @@ -7,6 +7,7 @@ #include "errno-util.h" #include "fd-util.h" #include "log.h" +#include "parse-util.h" #include "string-util.h" #include "time-util.h" @@ -84,6 +85,27 @@ int notify_push_fdf(int fd, const char *format, ...) { return notify_push_fd(fd, name); } +bool fdstore_detected(void) { + static int cached = -1; + int r; + + if (cached >= 0) + return cached; + + const char *e = getenv("FDSTORE"); + if (isempty(e)) + return (cached = 0); + + unsigned u; + r = safe_atou(e, &u); + if (r < 0) { + log_debug_errno(r, "Failed to parse 'FDSTORE=%s', ignoring: %m", e); + return (cached = 0); + } + + return (cached = u > 0); +} + int notify_reloading_full(const char *status) { int r; diff --git a/src/shared/daemon-util.h b/src/shared/daemon-util.h index 708a32985c6c5..089e418f7b809 100644 --- a/src/shared/daemon-util.h +++ b/src/shared/daemon-util.h @@ -27,6 +27,8 @@ int close_and_notify_warn(int fd, const char *name); int notify_push_fd(int fd, const char *name); int notify_push_fdf(int fd, const char *format, ...) _printf_(2, 3); +bool fdstore_detected(void); + int notify_reloading_full(const char *status); static inline int notify_reloading(void) { return notify_reloading_full("Reloading configuration..."); diff --git a/src/shared/dev-setup.c b/src/shared/dev-setup.c index e79db91ddee53..9424e22001564 100644 --- a/src/shared/dev-setup.c +++ b/src/shared/dev-setup.c @@ -9,7 +9,7 @@ #include "fs-util.h" #include "label-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "nulstr-util.h" #include "path-util.h" #include "stat-util.h" diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 4c0752c979128..9994ff5da451e 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) @@ -954,6 +956,7 @@ int image_find(RuntimeScope scope, _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; r = path_pick(root, + rfd, rfd, fname_path, /* This has to be the unresolved entry with the .v suffix */ &filter, @@ -1095,7 +1098,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 +1113,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) @@ -1171,6 +1178,7 @@ int image_discover( _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; r = path_pick(root, + rfd, rfd, fname_path, /* This has to be the unresolved entry with the .v suffix */ &filter, @@ -1889,17 +1897,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 +2193,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..f9556ea6b493e 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" @@ -52,12 +47,11 @@ #include "json-util.h" #include "libmount-util.h" #include "loop-util.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #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; @@ -347,6 +320,63 @@ int probe_filesystem_full( #endif } +int probe_partition_table(int fd, char **ret_pttype) { + + /* Probes the whole device referenced by fd for a partition table and returns its blkid type (e.g. + * "gpt" or "dos") in *ret_pttype, or NULL if none is found. Returns a negative error on failure + * (including -EUCLEAN for ambiguous results). */ + +#if HAVE_BLKID + _cleanup_(blkid_free_probep) blkid_probe b = NULL; + const char *pttype = NULL; + int r; + + assert(fd >= 0); + assert(ret_pttype); + + r = dlopen_libblkid(LOG_DEBUG); + if (r < 0) + return r; + + b = sym_blkid_new_probe(); + if (!b) + return -ENOMEM; + + errno = 0; + r = sym_blkid_probe_set_device(b, fd, /* offset= */ 0, /* size= */ 0 /* i.e. everything */); + if (r != 0) + return errno_or_else(ENOMEM); + + sym_blkid_probe_enable_partitions(b, 1); + + errno = 0; + r = sym_blkid_do_safeprobe(b); + if (r == _BLKID_SAFEPROBE_NOT_FOUND) { + log_debug("No partition table detected."); + *ret_pttype = NULL; + return 0; + } + if (r == _BLKID_SAFEPROBE_AMBIGUOUS) + return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN), "Partition table results ambiguous."); + if (r == _BLKID_SAFEPROBE_ERROR) + return log_debug_errno(errno_or_else(EIO), "Failed to probe for partition table: %m"); + + assert(r == _BLKID_SAFEPROBE_FOUND); + + (void) sym_blkid_probe_lookup_value(b, "PTTYPE", &pttype, /* len= */ NULL); + if (!pttype) { + log_debug("No partition table detected."); + *ret_pttype = NULL; + return 0; + } + + log_debug("Probed partition table type '%s'.", pttype); + return strdup_to_full(ret_pttype, pttype); +#else + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Compiled without blkid support, cannot probe for partition table."); +#endif +} + #if HAVE_BLKID static int image_policy_may_use( const ImagePolicy *policy, @@ -457,11 +487,15 @@ static int partition_is_luks2_integrity(int part_fd, uint64_t offset, uint64_t s if (sz != sizeof(header)) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read LUKS header."); - if (memcmp(header.luks_magic, LUKS2_MAGIC, sizeof(header.luks_magic)) != 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Partition's magic is not LUKS."); + if (memcmp(header.luks_magic, LUKS2_MAGIC, sizeof(header.luks_magic)) != 0) { + log_debug("Partition does not have a LUKS magic header, assuming no integrity."); + return 0; + } - if (be16toh(header.version) != 2) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported LUKS header version: %" PRIu16 ".", be16toh(header.version)); + if (be16toh(header.version) != 2) { + log_debug("Partition is LUKS v%" PRIu16 ", not LUKS2, assuming no integrity.", be16toh(header.version)); + return 0; + } if (be64toh(header.hdr_len) > size) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "LUKS header length exceeds partition size."); @@ -469,6 +503,9 @@ static int partition_is_luks2_integrity(int part_fd, uint64_t offset, uint64_t s if (be64toh(header.hdr_len) <= LUKS2_FIXED_HDR_SIZE || offset > UINT64_MAX - be64toh(header.hdr_len)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid LUKS header length: %" PRIu64 ".", be64toh(header.hdr_len)); + if (be64toh(header.hdr_len) - LUKS2_FIXED_HDR_SIZE > 16U * 1024U * 1024U) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "LUKS header JSON area too large: %" PRIu64 ".", be64toh(header.hdr_len)); + json_len = be64toh(header.hdr_len) - LUKS2_FIXED_HDR_SIZE; json = malloc(json_len + 1); if (!json) @@ -737,7 +774,7 @@ static int acquire_sig_for_roothash( ssize_t n = pread(fd, buf, partition_size, partition_offset); if (n < 0) - return -ENOMEM; + return -errno; if ((uint64_t) n != partition_size) return -EIO; @@ -963,6 +1000,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 +1134,7 @@ static int dissect_image( } } - r = dlopen_libblkid(); + r = dlopen_libblkid(LOG_DEBUG); if (r < 0) return r; @@ -1149,10 +1187,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; @@ -1303,7 +1338,7 @@ static int dissect_image( * partition already existent. */ if (FLAGS_SET(flags, DISSECT_IMAGE_ADD_PARTITION_DEVICES)) { - r = block_device_add_partition(fd, node, nr, (uint64_t) start * 512, (uint64_t) size * 512); + r = block_device_add_partition(fd, nr, (uint64_t) start * 512, (uint64_t) size * 512); if (r < 0) { if (r != -EBUSY) return log_debug_errno(r, "BLKPG_ADD_PARTITION failed: %m"); @@ -1397,126 +1432,62 @@ 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 = {}; r = acquire_sig_for_roothash( fd, - start * 512, - size * 512, + (uint64_t) start * 512, + (uint64_t) size * 512, &root_hash, /* 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 +1735,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 +1862,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 +2200,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) { @@ -2395,7 +2358,7 @@ int partition_pick_mount_options( case PARTITION_XBOOTLDR: flags |= MS_NOSUID|MS_NOEXEC|MS_NOSYMFOLLOW; - /* The ESP might contain a pre-boot random seed. Let's make this unaccessible to regular + /* The ESP might contain a pre-boot random seed. Let's make this inaccessible to regular * userspace. ESP/XBOOTLDR is almost certainly VFAT, hence if we don't know assume it is. */ if (!fstype || fstype_can_fmask_dmask(fstype)) if (!strextend_with_separator(&options, ",", "fmask=0177,dmask=0077")) @@ -2954,30 +2917,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 +2955,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 +2983,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 +3003,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 +3053,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 +3081,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 +3130,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 +3145,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 +3161,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 +3169,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 +3187,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_openssl_errors(LOG_DEBUG, "Userspace PKCS#7 validation failed"); return r; #else @@ -3342,6 +3335,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 +3345,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 +3375,7 @@ static int verity_partition( return 0; } - r = dlopen_cryptsetup(); + r = dlopen_cryptsetup(LOG_DEBUG); if (r < 0) return r; @@ -3392,9 +3387,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 +3415,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 +3520,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 +3600,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 +3790,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) @@ -3876,7 +3880,7 @@ int verity_settings_load( if (r < 0) { _cleanup_free_ char *p = NULL; - if (r != -ENOENT && !ERRNO_IS_XATTR_ABSENT(r)) + if (!ERRNO_IS_XATTR_ABSENT(r)) return r; p = build_auxiliary_path(image, ".roothash"); @@ -3905,7 +3909,7 @@ int verity_settings_load( if (r < 0) { _cleanup_free_ char *p = NULL; - if (r != -ENOENT && !ERRNO_IS_XATTR_ABSENT(r)) + if (!ERRNO_IS_XATTR_ABSENT(r)) return r; p = build_auxiliary_path(image, ".usrhash"); @@ -4106,7 +4110,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 +4238,7 @@ int dissected_image_acquire_metadata( assert(m); - r = dlopen_libmount(); + r = dlopen_libmount(LOG_DEBUG); if (r < 0) return r; @@ -4757,7 +4761,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, @@ -5351,6 +5355,9 @@ int mountfsd_mount_image_fd( }; } + if (!di) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Partition list is empty."); + di->single_file_system = p.single_file_system; di->image_size = p.image_size; di->sector_size = p.sector_size; @@ -5562,7 +5569,7 @@ int mountfsd_make_directory( _cleanup_close_ int fd = open(parent, O_DIRECTORY|O_CLOEXEC); if (fd < 0) - return log_debug_errno(r, "Failed to open '%s': %m", parent); + return log_debug_errno(errno, "Failed to open '%s': %m", parent); return mountfsd_make_directory_fd(vl, fd, dirname, mode, flags, ret_directory_fd); } diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 09d7db5952b60..fe6af790bfb21 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -160,6 +160,8 @@ static inline int probe_filesystem(const char *path, char **ret_fstype) { return probe_filesystem_full(-1, path, 0, UINT64_MAX, /* bool restrict_fstypes= */ false, ret_fstype); } +int probe_partition_table(int fd, char **ret_pttype); + int dissect_log_error(int log_level, int r, const char *name, const VeritySettings *verity); int dissect_image_file(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, const ImageFilter *filter, DissectImageFlags flags, DissectedImage **ret); int dissect_image_file_and_warn(const char *path, const VeritySettings *verity, const MountOptions *mount_options, const ImagePolicy *image_policy, const ImageFilter *filter, DissectImageFlags flags, DissectedImage **ret); 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-configuration.c b/src/shared/dns-configuration.c index 1194e872585d6..01b59bbd892b9 100644 --- a/src/shared/dns-configuration.c +++ b/src/shared/dns-configuration.c @@ -36,7 +36,7 @@ static int dispatch_dns_server(const char *name, sd_json_variant *variant, sd_js static const sd_json_dispatch_field dns_server_dispatch_table[] = { { "address", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(DNSServer, addr), SD_JSON_MANDATORY }, { "addressString", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, - { "family", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(DNSServer, family), SD_JSON_MANDATORY }, + { "family", SD_JSON_VARIANT_INTEGER, json_dispatch_address_family, offsetof(DNSServer, family), SD_JSON_MANDATORY }, { "port", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint16, offsetof(DNSServer, port), 0 }, { "ifindex", SD_JSON_VARIANT_UNSIGNED, json_dispatch_ifindex, offsetof(DNSServer, ifindex), SD_JSON_RELAX }, { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSServer, server_name), 0 }, @@ -177,12 +177,12 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( static int dispatch_dns_scope(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { static const sd_json_dispatch_field dns_scope_dispatch_table[] = { - { "protocol", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, protocol), SD_JSON_MANDATORY }, - { "family", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint, offsetof(DNSScope, family), 0 }, - { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, ifname), 0 }, - { "ifindex", SD_JSON_VARIANT_UNSIGNED, json_dispatch_ifindex, offsetof(DNSScope, ifindex), SD_JSON_RELAX }, - { "dnssec", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, dnssec_mode_str), 0 }, - { "dnsOverTLS", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, dns_over_tls_mode_str), 0 }, + { "protocol", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, protocol), SD_JSON_MANDATORY }, + { "family", SD_JSON_VARIANT_INTEGER, json_dispatch_address_family, offsetof(DNSScope, family), SD_JSON_RELAX }, + { "ifname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, ifname), 0 }, + { "ifindex", SD_JSON_VARIANT_UNSIGNED, json_dispatch_ifindex, offsetof(DNSScope, ifindex), SD_JSON_RELAX }, + { "dnssec", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, dnssec_mode_str), 0 }, + { "dnsOverTLS", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(DNSScope, dns_over_tls_mode_str), 0 }, {}, }; DNSScope **ret = ASSERT_PTR(userdata); 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..429e5c82c1774 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 */ @@ -2438,48 +2446,67 @@ static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) { static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question) { _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - unsigned n; + unsigned n, prealloc; int r; + assert(ret_question); + n = DNS_PACKET_QDCOUNT(p); - if (n > 0) { - question = dns_question_new(n); - if (!question) - return -ENOMEM; + if (n == 0) { + *ret_question = NULL; + return 0; + } - _cleanup_set_free_ Set *keys = NULL; /* references to keys are kept by Question */ + /* Calculate the maximum number of potential questions the remaining packet data can actually + * contain: p->size - p->rindex are the remaining unread bytes in the packet, and 5U is the minimum + * size of each question - 1 (QNAME) + 2 (QTYPE) + 2 (QCLASS). */ + prealloc = (p->size - p->rindex) / 5U; + if (prealloc == 0) + /* QDCOUNT > 0 but there's not enough space left for a single question. */ + return -EMSGSIZE; - keys = set_new(&dns_resource_key_hash_ops); - if (!keys) - return log_oom(); + question = dns_question_new(n); + if (!question) + return -ENOMEM; - r = set_reserve(keys, n * 2); /* Higher multipliers give slightly higher efficiency through - * hash collisions, but the gains quickly drop off after 2. */ - if (r < 0) - return r; + _cleanup_set_free_ Set *keys = NULL; /* references to keys are kept by Question */ - for (unsigned i = 0; i < n; i++) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - bool qu; + keys = set_new(&dns_resource_key_hash_ops); + if (!keys) + return log_oom(); - r = dns_packet_read_key(p, &key, &qu, NULL); - if (r < 0) - return r; + /* Pre-allocate the question hashmap, but cap the pre-allocation to a number of questions the + * packet can realistically contain. That is, pick the minimal value from the claimed number + * of questions (n) and a maximum number of potential questions the remaining packet data can + * actually contain, see above. + * + * Note for the multiplication: higher multipliers give slightly higher efficiency through + * hash collisions, but the gains quickly drop off after 2. */ + r = set_reserve(keys, MIN(n, prealloc) * 2); + if (r < 0) + return r; - if (!dns_type_is_valid_query(key->type)) - return -EBADMSG; + for (unsigned i = 0; i < n; i++) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + bool qu; - r = set_put(keys, key); - if (r < 0) - return r; - if (r == 0) - /* Already in the Question, let's skip */ - continue; + r = dns_packet_read_key(p, &key, &qu, NULL); + if (r < 0) + return r; - r = dns_question_add_raw(question, key, qu ? DNS_QUESTION_WANTS_UNICAST_REPLY : 0); - if (r < 0) - return r; - } + if (!dns_type_is_valid_query(key->type)) + return -EBADMSG; + + r = set_put(keys, key); + if (r < 0) + return r; + if (r == 0) + /* Already in the Question, let's skip */ + continue; + + r = dns_question_add_raw(question, key, qu ? DNS_QUESTION_WANTS_UNICAST_REPLY : 0); + if (r < 0) + return r; } *ret_question = TAKE_PTR(question); @@ -2489,16 +2516,31 @@ static int dns_packet_extract_question(DnsPacket *p, DnsQuestion **ret_question) static int dns_packet_extract_answer(DnsPacket *p, DnsAnswer **ret_answer) { _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - unsigned n; + unsigned n, prealloc; _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL; bool bad_opt = false; int r; + assert(ret_answer); + n = DNS_PACKET_RRCOUNT(p); - if (n == 0) + if (n == 0) { + *ret_answer = NULL; return 0; + } + + /* Calculate the maximum number of potential RRs the remaining packet data can actually contain: + * p->size - p->rindex are the remaining unread bytes in the packet, and the 11U is the minimum size + * of each RR - 1 (NAME) + 2 (TYPE) + 2 (CLASS) + 4 (TTL) + 2 (RDLENGTH). */ + prealloc = (p->size - p->rindex) / 11U; + if (prealloc == 0) + /* RRCOUNT > 0 but there's not enough space left for a single RR. */ + return -EMSGSIZE; - answer = dns_answer_new(n); + /* Pre-allocate the answer hashmap, but cap the pre-allocation to a number of RRs the packet can + * realistically contain. That is, pick the minimal value from the claimed number of RRs (n) and a + * maximum number of potential RRs the remaining packet data can actually contain, see above. */ + answer = dns_answer_new(MIN(n, prealloc)); if (!answer) return -ENOMEM; @@ -2849,7 +2891,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-question.c b/src/shared/dns-question.c index ac4cc8e998007..28840d64b948a 100644 --- a/src/shared/dns-question.c +++ b/src/shared/dns-question.c @@ -608,6 +608,9 @@ int dns_json_dispatch_question(const char *name, sd_json_variant *variant, sd_js if (!sd_json_variant_is_array(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); + if (sd_json_variant_elements(variant) > DNS_QUESTION_ITEMS_MAX) + return json_log(variant, flags, SYNTHETIC_ERRNO(E2BIG), "Too many questions in a single query."); + _cleanup_(dns_question_unrefp) DnsQuestion *nq = NULL; nq = dns_question_new(sd_json_variant_elements(variant)); if (!nq) diff --git a/src/shared/dns-question.h b/src/shared/dns-question.h index 4b0fc68fd648c..85de7ad06d8d7 100644 --- a/src/shared/dns-question.h +++ b/src/shared/dns-question.h @@ -5,6 +5,8 @@ #include "shared-forward.h" +#define DNS_QUESTION_ITEMS_MAX 128U + /* A simple array of resource keys */ typedef enum DnsQuestionFlags { diff --git a/src/shared/dns-rr.c b/src/shared/dns-rr.c index 0fa730c13baa2..eb91910e835f8 100644 --- a/src/shared/dns-rr.c +++ b/src/shared/dns-rr.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" #include "bitmap.h" #include "dns-answer.h" /* IWYU pragma: keep */ @@ -13,6 +11,7 @@ #include "hash-funcs.h" #include "hexdecoct.h" #include "json-util.h" +#include "math-util.h" #include "memory-util.h" #include "siphash24.h" #include "string-table.h" @@ -773,9 +772,9 @@ static char* format_location(uint32_t latitude, uint32_t longitude, uint32_t alt int lat = latitude >= 1U<<31 ? (int) (latitude - (1U<<31)) : (int) ((1U<<31) - latitude); int lon = longitude >= 1U<<31 ? (int) (longitude - (1U<<31)) : (int) ((1U<<31) - longitude); double alt = altitude >= 10000000u ? altitude - 10000000u : -(double)(10000000u - altitude); - double siz = (size >> 4) * exp10((double) (size & 0xF)); - double hor = (horiz_pre >> 4) * exp10((double) (horiz_pre & 0xF)); - double ver = (vert_pre >> 4) * exp10((double) (vert_pre & 0xF)); + double siz = (size >> 4) * xexp10i(size & 0xF); + double hor = (horiz_pre >> 4) * xexp10i(horiz_pre & 0xF); + double ver = (vert_pre >> 4) * xexp10i(vert_pre & 0xF); if (asprintf(&s, "%d %d %.3f %c %d %d %.3f %c %.2fm %.2fm %.2fm %.2fm", (lat / 60000 / 60), @@ -2012,6 +2011,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 +2215,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 +2308,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 +2513,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/edit-util.c b/src/shared/edit-util.c index d48c36c1d5c2b..4180d2a6f7df5 100644 --- a/src/shared/edit-util.c +++ b/src/shared/edit-util.c @@ -13,12 +13,12 @@ #include "fileio.h" #include "fs-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "path-util.h" #include "process-util.h" #include "string-util.h" #include "strv.h" -#include "tmpfile-util-label.h" +#include "tmpfile-util.h" typedef struct EditFile { EditFileContext *context; diff --git a/src/shared/efi-api.c b/src/shared/efi-api.c index 8a3a40cbecba7..56b055a0c8a94 100644 --- a/src/shared/efi-api.c +++ b/src/shared/efi-api.c @@ -5,7 +5,7 @@ #include "alloc-util.h" #include "dirent-util.h" #include "efi-api.h" -#include "efi-fundamental.h" +#include "efi.h" #include "efivars.h" #include "fd-util.h" #include "fileio.h" @@ -645,21 +645,25 @@ bool efi_has_tpm2(void) { } sd_id128_t efi_guid_to_id128(const void *guid) { - const EFI_GUID *uuid = ASSERT_PTR(guid); /* cast is safe, because struct efi_guid is packed */ + EFI_GUID uuid; sd_id128_t id128; - id128.bytes[0] = (uuid->Data1 >> 24) & 0xff; - id128.bytes[1] = (uuid->Data1 >> 16) & 0xff; - id128.bytes[2] = (uuid->Data1 >> 8) & 0xff; - id128.bytes[3] = uuid->Data1 & 0xff; + /* The input pointer is not guaranteed to be aligned (e.g. when pointing into a serialized EFI + * variable buffer), so copy into a properly aligned local first. */ + memcpy(&uuid, ASSERT_PTR(guid), sizeof(uuid)); - id128.bytes[4] = (uuid->Data2 >> 8) & 0xff; - id128.bytes[5] = uuid->Data2 & 0xff; + id128.bytes[0] = (uuid.Data1 >> 24) & 0xff; + id128.bytes[1] = (uuid.Data1 >> 16) & 0xff; + id128.bytes[2] = (uuid.Data1 >> 8) & 0xff; + id128.bytes[3] = uuid.Data1 & 0xff; - id128.bytes[6] = (uuid->Data3 >> 8) & 0xff; - id128.bytes[7] = uuid->Data3 & 0xff; + id128.bytes[4] = (uuid.Data2 >> 8) & 0xff; + id128.bytes[5] = uuid.Data2 & 0xff; - memcpy(&id128.bytes[8], uuid->Data4, sizeof(uuid->Data4)); + id128.bytes[6] = (uuid.Data3 >> 8) & 0xff; + id128.bytes[7] = uuid.Data3 & 0xff; + + memcpy(&id128.bytes[8], uuid.Data4, sizeof(uuid.Data4)); return id128; } diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c index 1f4fc665c03b8..20e4719bb067e 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; @@ -408,3 +433,14 @@ bool efi_loader_entry_name_valid(const char *s) { return in_charset(s, ALPHANUMERICAL "+-_.@"); } + +bool efi_loader_entry_title_valid(const char *s) { + return string_is_safe(s, /* flags= */ 0); +} + +bool efi_loader_entry_resource_filename_valid(const char *s) { + /* Validates file names so that they are safe for their inclusion in boot loader type #1 + * entries. i.e. may not contain CCs, and should be ASCII */ + + return string_is_safe(s, STRING_ASCII|STRING_FILENAME); +} diff --git a/src/shared/efi-loader.h b/src/shared/efi-loader.h index 5b614cd0a7ee0..c51c2dbb2f918 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); @@ -22,3 +23,5 @@ int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat int efi_get_variable_id128(const char *variable, sd_id128_t *ret); bool efi_loader_entry_name_valid(const char *s); +bool efi_loader_entry_title_valid(const char *s); +bool efi_loader_entry_resource_filename_valid(const char *s); diff --git a/src/shared/elf-util.c b/src/shared/elf-util.c index e199b42c82a24..2210ab39f1423 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" @@ -37,9 +38,6 @@ #if HAVE_ELFUTILS -static void *dw_dl = NULL; -static void *elf_dl = NULL; - /* libdw symbols */ static DLSYM_PROTOTYPE(dwarf_attr_integrate) = NULL; static DLSYM_PROTOTYPE(dwarf_diename) = NULL; @@ -55,9 +53,12 @@ static DLSYM_PROTOTYPE(dwfl_begin) = NULL; static DLSYM_PROTOTYPE(dwfl_build_id_find_elf) = NULL; static DLSYM_PROTOTYPE(dwfl_core_file_attach) = NULL; static DLSYM_PROTOTYPE(dwfl_core_file_report) = NULL; -#if HAVE_DWFL_SET_SYSROOT +/* New in elfutils 0.192. Always redeclare so DLSYM_PROTOTYPE's typeof() resolves on older headers; suppress + * the warning when newer libdw already declared it. */ +DISABLE_WARNING_REDUNDANT_DECLS; +extern int dwfl_set_sysroot(Dwfl *dwfl, const char *sysroot); /* NOLINT(readability-redundant-declaration) */ +REENABLE_WARNING; static DLSYM_PROTOTYPE(dwfl_set_sysroot) = NULL; -#endif static DLSYM_PROTOTYPE(dwfl_end) = NULL; static DLSYM_PROTOTYPE(dwfl_errmsg) = NULL; static DLSYM_PROTOTYPE(dwfl_errno) = NULL; @@ -90,17 +91,19 @@ static DLSYM_PROTOTYPE(gelf_getnote) = NULL; #endif -int dlopen_dw(void) { +int dlopen_dw(int log_level) { #if HAVE_ELFUTILS + static void *dw_dl = NULL; 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), @@ -119,9 +122,6 @@ int dlopen_dw(void) { DLSYM_ARG(dwfl_module_getelf), DLSYM_ARG(dwfl_begin), DLSYM_ARG(dwfl_core_file_report), -#if HAVE_DWFL_SET_SYSROOT - DLSYM_ARG(dwfl_set_sysroot), -#endif DLSYM_ARG(dwfl_report_end), DLSYM_ARG(dwfl_getmodules), DLSYM_ARG(dwfl_core_file_attach), @@ -137,23 +137,39 @@ int dlopen_dw(void) { if (r <= 0) return r; + /* Optional symbol: present in libdw 0.192+. NULL pointer is fine; call sites check at use. */ + DLSYM_OPTIONAL(dw_dl, dwfl_set_sysroot); + return 1; #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "libdw support is not compiled in."); +#endif +} + +bool dlopen_dw_has_dwfl_set_sysroot(void) { +#if HAVE_ELFUTILS + if (dlopen_dw(LOG_DEBUG) < 0) + return false; + return sym_dwfl_set_sysroot; +#else + return false; #endif } -int dlopen_elf(void) { +int dlopen_elf(int log_level) { #if HAVE_ELFUTILS + static void *elf_dl = NULL; 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 +186,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 +423,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; @@ -614,6 +631,7 @@ static int module_callback(Dwfl_Module *mod, void **userdata, const char *name, static int parse_core( int fd, + const char *executable, const char *root, char **ret, sd_json_variant **ret_package_metadata, @@ -654,15 +672,15 @@ static int parse_core( if (empty_or_root(root)) root = NULL; -#if HAVE_DWFL_SET_SYSROOT - if (root && sym_dwfl_set_sysroot(c.dwfl, root) < 0) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Could not set root directory, dwfl_set_sysroot() failed: %s", sym_dwfl_errmsg(sym_dwfl_errno())); -#else - if (root) - log_warning("Compiled without dwfl_set_sysroot() support, ignoring provided root directory."); -#endif + if (root) { + if (sym_dwfl_set_sysroot) { + if (sym_dwfl_set_sysroot(c.dwfl, root) < 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Could not set root directory, dwfl_set_sysroot() failed: %s", sym_dwfl_errmsg(sym_dwfl_errno())); + } else + log_warning("Loaded libdw does not support dwfl_set_sysroot(), ignoring provided root directory."); + } - if (sym_dwfl_core_file_report(c.dwfl, c.elf, NULL) < 0) + if (sym_dwfl_core_file_report(c.dwfl, c.elf, executable) < 0) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Could not parse core file, dwfl_core_file_report() failed: %s", sym_dwfl_errmsg(sym_dwfl_errno())); if (sym_dwfl_report_end(c.dwfl, NULL, NULL) != 0) @@ -729,7 +747,7 @@ static int parse_elf( if (elf_header.e_type == ET_CORE) { _cleanup_free_ char *out = NULL; - r = parse_core(fd, root, ret ? &out : NULL, &package_metadata, &dlopen_metadata); + r = parse_core(fd, executable, root, ret ? &out : NULL, &package_metadata, &dlopen_metadata); if (r < 0) return log_warning_errno(r, "Failed to inspect core file: %m"); @@ -821,11 +839,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; @@ -975,25 +993,25 @@ int parse_elf_object( } if (ret_package_metadata) { - _cleanup_fclose_ FILE *json_in = NULL; - - json_in = take_fdopen(&package_metadata_pipe[0], "r"); - if (!json_in) - return -errno; - - r = sd_json_parse_file(json_in, NULL, 0, &package_metadata, NULL, NULL); + r = sd_json_parse_fd( + /* path= */ NULL, + TAKE_FD(package_metadata_pipe[0]), + SD_JSON_PARSE_DONATE_FD, + &package_metadata, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL); if (r < 0 && r != -ENODATA) /* ENODATA: json was empty, so we got nothing, but that's ok */ log_warning_errno(r, "Failed to read or parse package metadata, ignoring: %m"); } if (ret_dlopen_metadata) { - _cleanup_fclose_ FILE *json_in = NULL; - - json_in = take_fdopen(&dlopen_metadata_pipe[0], "r"); - if (!json_in) - return -errno; - - r = sd_json_parse_file(json_in, NULL, 0, &dlopen_metadata, NULL, NULL); + r = sd_json_parse_fd( + /* path= */ NULL, + TAKE_FD(dlopen_metadata_pipe[0]), + SD_JSON_PARSE_DONATE_FD, + &dlopen_metadata, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL); if (r < 0 && r != -ENODATA) /* ENODATA: json was empty, so we got nothing, but that's ok */ log_warning_errno(r, "Failed to read or parse dlopen metadata, ignoring: %m"); } diff --git a/src/shared/elf-util.h b/src/shared/elf-util.h index b5c3db80ee217..7ccbec56c6461 100644 --- a/src/shared/elf-util.h +++ b/src/shared/elf-util.h @@ -3,8 +3,10 @@ #include "shared-forward.h" -int dlopen_dw(void); -int dlopen_elf(void); +int dlopen_dw(int log_level); +int dlopen_elf(int log_level); + +bool dlopen_dw_has_dwfl_set_sysroot(void); /* 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/ethtool-link-mode.py b/src/shared/ethtool-link-mode.py index 6d23f3c43aee9..c64fa6c456381 100755 --- a/src/shared/ethtool-link-mode.py +++ b/src/shared/ethtool-link-mode.py @@ -7,7 +7,7 @@ import sys OVERRIDES = { - 'autoneg' : 'autonegotiation', + 'autoneg': 'autonegotiation', } mode = sys.argv[1] diff --git a/src/shared/ethtool-util.c b/src/shared/ethtool-util.c index 74a979c184e9d..44e9c49e817c2 100644 --- a/src/shared/ethtool-util.c +++ b/src/shared/ethtool-util.c @@ -13,7 +13,7 @@ #include "extract-word.h" #include "fd-util.h" #include "log.h" -#include "macro-fundamental.h" +#include "macro.h" #include "memory-util.h" #include "parse-util.h" #include "socket-util.h" 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/fdisk-util.c b/src/shared/fdisk-util.c index 2a0b7d765b360..8efafbc96e469 100644 --- a/src/shared/fdisk-util.c +++ b/src/shared/fdisk-util.c @@ -1,17 +1,162 @@ /* 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" +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_assign_device_by_fd) = 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 + static void *fdisk_dl = NULL; + + FDISK_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); + + 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_assign_device_by_fd), + 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 +179,7 @@ int fdisk_new_context_at( dir_fd = fd; } - c = fdisk_new_context(); + c = sym_fdisk_new_context(); if (!c) return -ENOMEM; @@ -45,12 +190,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 +209,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 +223,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 +244,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 +306,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..367c6cd051e44 100644 --- a/src/shared/fdisk-util.h +++ b/src/shared/fdisk-util.h @@ -1,16 +1,82 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dlopen.h" + +#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_assign_device_by_fd); +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); @@ -20,4 +86,19 @@ int fdisk_partition_get_type_as_id128(struct fdisk_partition *p, sd_id128_t *ret int fdisk_partition_get_attrs_as_uint64(struct fdisk_partition *pa, uint64_t *ret); int fdisk_partition_set_attrs_as_uint64(struct fdisk_partition *pa, uint64_t flags); +#define FDISK_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("fdisk", \ + "Support for reading and writing partition tables", \ + priority, \ + "libfdisk.so.1") + +#define DLOPEN_FDISK(log_level, priority) \ + ({ \ + FDISK_NOTE(priority); \ + dlopen_fdisk(log_level); \ + }) +#else +#define DLOPEN_FDISK(log_level, priority) dlopen_fdisk(log_level) #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..201222e10e5b2 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,12 +321,9 @@ 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)); + assert(wildcard_fd_is_valid(rfd)); assert(path); /* This logs about all errors, except: @@ -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, @@ -442,7 +420,7 @@ int find_esp_and_warn_at( VerifyESPFlags flags; int r; - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); /* This logs about all errors except: * @@ -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(wildcard_fd_is_valid(rfd)); 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) { @@ -804,12 +796,12 @@ int find_xbootldr_and_warn_at( /* Similar to find_esp_and_warn(), but finds the XBOOTLDR partition. Returns the same errors. */ - assert(rfd >= 0 || IN_SET(rfd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(rfd)); 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..441e137ec03cd 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_POINTER_ARRAY_CLEAR_FUNC(sd_netlink_message*, sd_netlink_message_unref); static int nfnl_open_expr_container(sd_netlink_message *m, const char *name) { int r; @@ -737,7 +725,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_ELEMENTS(messages, sd_netlink_message_unref_array_clear); size_t msgcnt = 0, ip_type_size; uint32_t set_id = 0; int ip_type, r; @@ -1038,7 +1026,7 @@ int fw_nftables_add_masquerade( * Note that this doesn't protect against external sabotage such as a * 'while true; nft flush ruleset; done'. There is nothing that could be done about that short * of extending the kernel to allow tables to be owned by stystemd-networkd and making them - * non-deleteable except by the 'owning process'. */ + * non-deletable except by the 'owning process'. */ r = fw_nftables_init_family(nfnl, af); if (r < 0) @@ -1059,7 +1047,7 @@ static int fw_nftables_add_local_dnat_internal( const union in_addr_union *previous_remote) { sd_netlink_message *messages[3] = {}; - _unused_ _cleanup_(netlink_message_unref_manyp) sd_netlink_message **unref = messages; + CLEANUP_ELEMENTS(messages, sd_netlink_message_unref_array_clear); 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..ce9d965677211 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" @@ -87,10 +92,10 @@ static int on_child_notify(sd_event_source *s, int fd, uint32_t revents, void *u return 0; } -int fork_notify(char * const *argv, PidRef *ret_pidref) { +int fork_notify(char * const *argv, fork_notify_handler_t child_handler, void *child_userdata, PidRef *ret_pidref) { int r; - assert(!strv_isempty(argv)); + assert(argv); assert(ret_pidref); if (!is_main_thread()) @@ -141,6 +146,12 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) { _exit(EXIT_MEMORY); } + /* After fork and before exec one can execute custom code in this function + * but since all open FDs were closed only limited actions are safe (e.g., setenv), + * akin to how in a signal handler only certain things are safe. */ + if (child_handler) + child_handler(child_userdata); + r = invoke_callout_binary(argv[0], argv); log_debug_errno(r, "Failed to invoke %s: %m", argv[0]); _exit(EXIT_EXEC); @@ -205,7 +216,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 +239,90 @@ int journal_fork(RuntimeScope scope, char * const* units, PidRef *ret_pidref) { *u) < 0) return log_oom_debug(); - return fork_notify(argv, ret_pidref); + if (output >= 0) + if (strv_extendf(&argv, "--output=%s", output_mode_to_string(output)) < 0) + return log_oom_debug(); + + return fork_notify(argv, /* child_handler= */ NULL, /* child_userdata= */ NULL, ret_pidref); +} + +static void set_journal_remote_config(void *userdata) { + 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); + } +} + +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("systemd-journal-remote", &sd_journal_remote); + if (r == -ENOENT) + r = find_executable_full( + "systemd-journal-remote", + /* root= */ NULL, + STRV_MAKE(LIBEXECDIR), + /* use_path_envvar= */ false, + &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(); + + return fork_notify(argv, set_journal_remote_config, /* child_userdata= */ NULL, ret_pidref); } diff --git a/src/shared/fork-notify.h b/src/shared/fork-notify.h index 103ab78983371..cf22f48ba2ace 100644 --- a/src/shared/fork-notify.h +++ b/src/shared/fork-notify.h @@ -1,12 +1,24 @@ /* 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); +typedef void (*fork_notify_handler_t)(void *userdata); + +int fork_notify(char * const *argv, fork_notify_handler_t child_handler, void *child_userdata, PidRef *ret_pidref); 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..360fdf596cfc8 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; - } - - 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; + return (d->formatted = TAKE_PTR(p)); } - 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; - } - - case TABLE_UUID: { - char *p; + return sd_id128_to_string(d->id128, d->formatted); - 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; - - break; + return (d->formatted = asprintf_safe(DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum))); - 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,12 +2170,86 @@ static const char* table_data_rgap_underline(const TableData *d) { return NULL; } -int table_print(Table *t, FILE *f) { - 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; +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) { _cleanup_free_ size_t *sorted = NULL; - uint64_t *column_weight, weight_sum; int r; assert(t); @@ -2384,7 +2260,7 @@ int table_print(Table *t, FILE *f) { /* Ensure we have no incomplete rows */ assert(t->n_cells % t->n_columns == 0); - n_rows = t->n_cells / t->n_columns; + size_t n_rows = t->n_cells / t->n_columns; assert(n_rows > 0); /* at least the header row must be complete */ if (t->sort_map) { @@ -2400,17 +2276,14 @@ int table_print(Table *t, FILE *f) { typesafe_qsort_r(sorted, n_rows, table_data_compare, t); } - if (t->display_map) - display_columns = t->n_display_map; - else - display_columns = t->n_columns; - + size_t display_columns = t->display_map ? t->n_display_map : t->n_columns; assert(display_columns > 0); - minimum_width = newa(size_t, display_columns); - maximum_width = newa(size_t, display_columns); - requested_width = newa(size_t, display_columns); - column_weight = newa0(uint64_t, display_columns); + size_t *minimum_width = newa(size_t, display_columns), + *maximum_width = newa(size_t, display_columns), + *requested_width = newa(size_t, display_columns), + *width = NULL; + uint64_t *column_weight = newa0(uint64_t, display_columns); for (size_t j = 0; j < display_columns; j++) { minimum_width[j] = 1; @@ -2493,10 +2366,11 @@ int table_print(Table *t, FILE *f) { } /* One space between each column */ + size_t table_requested_width, table_minimum_width, table_maximum_width, table_effective_width; table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1; /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */ - weight_sum = 0; + uint64_t weight_sum = 0; for (size_t j = 0; j < display_columns; j++) { weight_sum += column_weight[j]; @@ -2788,9 +2662,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 +2689,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 +2737,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 +2752,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 +2797,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 +3016,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 +3184,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..ba4d33cfc6719 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_POINTER_ARRAY_CLEAR_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/fsprg-openssl.c b/src/shared/fsprg-openssl.c new file mode 100644 index 0000000000000..167f0607f4225 --- /dev/null +++ b/src/shared/fsprg-openssl.c @@ -0,0 +1,668 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* + * This is based on + * fsprg v0.1 - (seekable) forward-secure pseudorandom generator + * Copyright © 2012 B. Poettering + * Contact: fsprg@point-at-infinity.org + * + * OpenSSL port of the original libgcrypt-based implementation. + * + * See "Practical Secure Logging: Seekable Sequential Key Generators" + * by G. A. Marson, B. Poettering for details: + * + * http://eprint.iacr.org/2013/397 + */ + +#include + +#include "alloc-util.h" /* IWYU pragma: keep */ +#include "crypto-util.h" +#include "fsprg-openssl.h" +#include "iovec-util.h" +#include "logarithm.h" +#include "sparse-endian.h" +#include "unaligned.h" + +#define RND_GEN_P 0x01 +#define RND_GEN_Q 0x02 +#define RND_GEN_X 0x03 + +/* Suppress a false positive from GCC's -Wstringop-overflow warning. Keep a local helper so the compiler can + * propagate the range check within this translation unit. */ +static bool secpar_is_valid(uint16_t secpar) { + return + secpar % 16 == 0 && + secpar >= 16 && + secpar <= 16384; +} + +bool fsprg_secpar_is_valid(uint16_t secpar) { + return secpar_is_valid(secpar); +} + +size_t fsprg_state_size(uint16_t secpar) { + assert(secpar_is_valid(secpar)); + + /* See comment in parse_state(). */ + return sizeof(uint16_t) + 2 * secpar / 8 + sizeof(uint64_t); +} + +#if HAVE_OPENSSL +static int mpi_export(struct iovec *iov, const BIGNUM *x) { + assert(iovec_is_set(iov)); + assert(x); + + if (sym_BN_is_negative(x)) + return -EINVAL; + + if (sym_BN_num_bytes(x) > (int) iov->iov_len) + return -ENOSPC; + + if (sym_BN_bn2binpad(x, iov->iov_base, iov->iov_len) != (int) iov->iov_len) + return -EIO; + + return 0; +} + +static int mpi_import(const struct iovec *iov, BIGNUM **ret) { + assert(iovec_is_set(iov)); + assert(ret); + + /* Allocate a new BIGNUM. */ + _cleanup_(BN_clear_freep) BIGNUM *x = sym_BN_secure_new(); + if (!x) + return -ENOMEM; + + if (!sym_BN_bin2bn(iov->iov_base, iov->iov_len, x)) + return -EIO; + + *ret = TAKE_PTR(x); + return 0; +} + +static int det_randomize(const struct iovec *seed, uint32_t idx, struct iovec *iov) { + assert(iovec_is_set(seed)); + assert(iovec_is_valid(iov)); + + /* Expand (seed, idx) into a deterministic pseudorandom byte stream. */ + + if (!iovec_is_set(iov)) + return 0; + + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = sym_EVP_MD_CTX_new(); + if (!ctx) + return -ENOMEM; + + if (sym_EVP_DigestInit_ex(ctx, sym_EVP_sha256(), NULL) <= 0) + return -EIO; + + if (sym_EVP_DigestUpdate(ctx, seed->iov_base, seed->iov_len) <= 0) + return -EIO; + + if (sym_EVP_DigestUpdate(ctx, (be32_t[]) { htobe32(idx) }, sizeof(be32_t)) <= 0) + return -EIO; + + struct iovec v = *iov; + for (uint32_t ctr = 0; iovec_is_set(&v); ctr++) { + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *tmp = sym_EVP_MD_CTX_new(); + if (!tmp) + return -ENOMEM; + + if (sym_EVP_MD_CTX_copy_ex(tmp, ctx) <= 0) + return -EIO; + + if (sym_EVP_DigestUpdate(tmp, (be32_t[]) { htobe32(ctr) }, sizeof(be32_t)) <= 0) + return -EIO; + + unsigned dlen; + uint8_t digest[EVP_MAX_MD_SIZE]; + CLEANUP_ERASE(digest); + + if (sym_EVP_DigestFinal_ex(tmp, digest, &dlen) <= 0) + return -EIO; + + size_t n = MIN(v.iov_len, dlen); + memcpy(v.iov_base, digest, n); + iovec_inc(&v, n); + } + + return 0; +} + +/* deterministically generate from seed/idx a prime of length (secpar / 2) bits that is 3 mod 4 */ +static int generate_prime3mod4( + uint16_t secpar, + const struct iovec *seed, + uint32_t idx, + BN_CTX *bn_ctx, + BIGNUM **ret) { + + int r; + + assert(iovec_is_set(seed)); + assert(bn_ctx); + assert(ret); + + if (!secpar_is_valid(secpar)) + return -EINVAL; + + _cleanup_(iovec_erase) struct iovec iov = IOVEC_ALLOCA(secpar / 2 / 8); + r = det_randomize(seed, idx, &iov); + if (r < 0) + return r; + + uint8_t *buf = iov.iov_base; + + /* Set the upper two bits so that n = pq has the maximum size. */ + buf[0] |= 0xc0; + + /* Make the candidate congruent to 3 (mod 4). */ + buf[iov.iov_len - 1] |= 0x03; + + _cleanup_(BN_clear_freep) BIGNUM *p = NULL; + r = mpi_import(&iov, &p); + if (r < 0) + return r; + + for (;;) { + /* negative: error, 0: composite, 1: prime */ + r = sym_BN_check_prime(p, bn_ctx, /* cb= */ NULL); + if (r < 0) + return -EIO; + if (r > 0) + break; + + /* Increment p by 4. */ + if (sym_BN_add_word(p, 4) <= 0) + return -EIO; + + /* Check if p is still (secpar / 2) bits. */ + if (sym_BN_num_bits(p) != secpar / 2) + return -EOVERFLOW; + } + + *ret = TAKE_PTR(p); + return 0; +} + +static int generate_keys( + uint16_t secpar, + const struct iovec *seed, + BN_CTX *bn_ctx, + BIGNUM **ret_p, /* prime */ + BIGNUM **ret_q, /* prime */ + BIGNUM **ret_n) { /* n = p * q */ + + int r; + + assert(iovec_is_set(seed)); + assert(bn_ctx); + assert(ret_p); + assert(ret_q); + assert(ret_n); + + if (!secpar_is_valid(secpar)) + return -EINVAL; + + _cleanup_(BN_clear_freep) BIGNUM *p = NULL; + r = generate_prime3mod4(secpar, seed, RND_GEN_P, bn_ctx, &p); + if (r < 0) + return r; + + _cleanup_(BN_clear_freep) BIGNUM *q = NULL; + r = generate_prime3mod4(secpar, seed, RND_GEN_Q, bn_ctx, &q); + if (r < 0) + return r; + + _cleanup_(BN_clear_freep) BIGNUM *n = sym_BN_secure_new(); + if (!n) + return -ENOMEM; + + if (sym_BN_mul(n, p, q, bn_ctx) <= 0) + return -EIO; + + if (sym_BN_num_bits(n) != secpar) + return -EIO; + + *ret_p = TAKE_PTR(p); + *ret_q = TAKE_PTR(q); + *ret_n = TAKE_PTR(n); + return 0; +} + +/* deterministically generate from seed/idx a quadratic residue (mod n) */ +static int generate_square( + uint16_t secpar, + const struct iovec *seed, + uint32_t idx, + const BIGNUM *n, + BN_CTX *bn_ctx, + BIGNUM **ret) { + + int r; + + assert(iovec_is_set(seed)); + assert(n); + assert(bn_ctx); + assert(ret); + + if (!secpar_is_valid(secpar)) + return -EINVAL; + + _cleanup_(iovec_erase) struct iovec iov = IOVEC_ALLOCA(secpar / 8); + r = det_randomize(seed, idx, &iov); + if (r < 0) + return r; + + *(uint8_t*) iov.iov_base &= 0x7f; /* Clear the upper bit so that we are likely to have x < n. */ + + _cleanup_(BN_clear_freep) BIGNUM *x = NULL; + r = mpi_import(&iov, &x); + if (r < 0) + return r; + + /* x < n should always hold for a valid modulus generated by generate_keys(). */ + if (sym_BN_cmp(x, n) >= 0) + return -EINVAL; + + /* x := x^2 mod n */ + if (sym_BN_mod_sqr(x, x, n, bn_ctx) <= 0) + return -EIO; + + *ret = TAKE_PTR(x); + return 0; +} + +/* Compute 2^m mod phi(p), where p is prime and phi(p) = p - 1. */ +static int compute_two_pow_mod_phi( + uint64_t m, + const BIGNUM *p, + BN_CTX *bn_ctx, + BIGNUM **ret) { + + assert(p); + assert(bn_ctx); + assert(ret); + + _cleanup_(BN_clear_freep) BIGNUM *phi = sym_BN_secure_new(); + if (!phi) + return -ENOMEM; + + /* phi := p */ + if (!sym_BN_copy(phi, p)) + return -EIO; + + /* phi := p - 1 */ + if (sym_BN_sub_word(phi, 1) <= 0) + return -EIO; + + _cleanup_(BN_clear_freep) BIGNUM *x = sym_BN_secure_new(); + if (!x) + return -ENOMEM; + + /* x := 1 */ + if (!sym_BN_one(x)) + return -EIO; + + if (m == 0) { + /* 2^0 mod phi = 1. */ + *ret = TAKE_PTR(x); + return 0; + } + + /* Square-and-multiply. Iterate over the bits of m from MSB to LSB. */ + for (int n = LOG2ULL(m); n >= 0; n--) { + if (sym_BN_mod_sqr(x, x, phi, bn_ctx) <= 0) /* x := x^2 mod phi */ + return -EIO; + + if (m & (UINT64_C(1) << n) && + sym_BN_mod_lshift1_quick(x, x, phi) <= 0) /* x := 2x mod phi */ + return -EIO; + } + + *ret = TAKE_PTR(x); + return 0; +} + +/* Decompose x ∈ Z_n into (xp, xq) ∈ Z_p × Z_q using the Chinese Remainder Theorem. */ +static int crt_decompose( + const BIGNUM *x, + const BIGNUM *p, + const BIGNUM *q, + BN_CTX *bn_ctx, + BIGNUM **ret_xp, + BIGNUM **ret_xq) { + + assert(x); + assert(p); + assert(q); + assert(bn_ctx); + assert(ret_xp); + assert(ret_xq); + + _cleanup_(BN_clear_freep) BIGNUM *xp = sym_BN_secure_new(); + if (!xp) + return -ENOMEM; + + _cleanup_(BN_clear_freep) BIGNUM *xq = sym_BN_secure_new(); + if (!xq) + return -ENOMEM; + + if (sym_BN_nnmod(xp, x, p, bn_ctx) <= 0) + return -EIO; + + if (sym_BN_nnmod(xq, x, q, bn_ctx) <= 0) + return -EIO; + + *ret_xp = TAKE_PTR(xp); + *ret_xq = TAKE_PTR(xq); + return 0; +} + +/* Compose (xp, xq) ∈ Z_p × Z_q into x ∈ Z_n using the Chinese Remainder Theorem. */ +static int crt_compose( + const BIGNUM *xp, + const BIGNUM *xq, + const BIGNUM *p, + const BIGNUM *q, + BN_CTX *bn_ctx, + BIGNUM **ret) { + + assert(xp); + assert(xq); + assert(p); + assert(q); + assert(bn_ctx); + assert(ret); + + _cleanup_(BN_clear_freep) BIGNUM *x = sym_BN_secure_new(); + if (!x) + return -ENOMEM; + + /* x := xq - xp mod q */ + if (sym_BN_mod_sub(x, xq, xp, q, bn_ctx) <= 0) + return -EIO; + + /* Compute p^-1 mod q. */ + _cleanup_(BN_clear_freep) BIGNUM *p_inv = sym_BN_secure_new(); + if (!p_inv) + return -ENOMEM; + + if (!sym_BN_mod_inverse(p_inv, p, q, bn_ctx)) + return -EIO; + + /* x := (xq - xp) * p^-1 mod q */ + if (sym_BN_mod_mul(x, x, p_inv, q, bn_ctx) <= 0) + return -EIO; + + /* x := p * ((xq - xp) * p^-1 mod q) */ + if (sym_BN_mul(x, x, p, bn_ctx) <= 0) + return -EIO; + + /* x := p * ((xq - xp) * p^-1 mod q) + xp */ + if (sym_BN_add(x, x, xp) <= 0) + return -EIO; + + *ret = TAKE_PTR(x); + return 0; +} + +static int save_state( + uint16_t secpar, + const BIGNUM *modulus, + const BIGNUM *current, + uint64_t epoch, + struct iovec *state) { + + int r; + + assert(modulus); + assert(current); + assert(iovec_is_set(state)); + + if (!secpar_is_valid(secpar)) + return -EINVAL; + + if (state->iov_len != fsprg_state_size(secpar)) + return -EINVAL; + + uint8_t *s = state->iov_base; + + /* header */ + unaligned_write_be16(s, secpar / 16 - 1); + s += sizeof(uint16_t); + + /* modulus */ + r = mpi_export(&IOVEC_MAKE(s, secpar / 8), modulus); + if (r < 0) + return r; + s += secpar / 8; + + /* current */ + r = mpi_export(&IOVEC_MAKE(s, secpar / 8), current); + if (r < 0) + return r; + s += secpar / 8; + + /* epoch */ + unaligned_write_be64(s, epoch); + return 0; +} + +static int parse_state( + const struct iovec *state, + uint16_t *ret_secpar, + BIGNUM **ret_modulus, + BIGNUM **ret_current, + uint64_t *ret_epoch) { + + int r; + + assert(iovec_is_set(state)); + + /* The serialized state consists of: + * - header: 2 bytes. Encodes secpar / 16 - 1, where 16 <= secpar <= 16384. + * - modulus (a.k.a. n): secpar / 8 bytes. + * - current (a.k.a. x): secpar / 8 bytes. + * - epoch: 8 bytes. */ + + if (state->iov_len < sizeof(uint16_t)) + return -EBADMSG; + + uint16_t header = unaligned_read_be16(state->iov_base); + if (header >= 1024) + return -EBADMSG; + + uint16_t secpar = 16 * (header + 1); + if (state->iov_len != fsprg_state_size(secpar)) + return -EBADMSG; + + _cleanup_(BN_clear_freep) BIGNUM *modulus = NULL; + if (ret_modulus) { + r = mpi_import(&IOVEC_MAKE((uint8_t*) state->iov_base + sizeof(uint16_t), secpar / 8), &modulus); + if (r < 0) + return r; + } + + _cleanup_(BN_clear_freep) BIGNUM *current = NULL; + if (ret_current) { + r = mpi_import(&IOVEC_MAKE((uint8_t*) state->iov_base + sizeof(uint16_t) + secpar / 8, secpar / 8), ¤t); + if (r < 0) + return r; + } + + if (ret_secpar) + *ret_secpar = secpar; + if (ret_modulus) + *ret_modulus = TAKE_PTR(modulus); + if (ret_current) + *ret_current = TAKE_PTR(current); + if (ret_epoch) + *ret_epoch = unaligned_read_be64((uint8_t*) state->iov_base + sizeof(uint16_t) + 2 * secpar / 8); + return 0; +} +#endif + +int fsprg_generate_state( + uint16_t secpar, + uint64_t epoch, + const struct iovec *seed, + struct iovec *state) { + +#if HAVE_OPENSSL + int r; + + assert(iovec_is_set(seed)); + assert(iovec_is_set(state)); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + if (!secpar_is_valid(secpar)) + return -EINVAL; + + _cleanup_(BN_CTX_freep) BN_CTX *bn_ctx = sym_BN_CTX_secure_new(); + if (!bn_ctx) + return -ENOMEM; + + _cleanup_(BN_clear_freep) BIGNUM *p = NULL, *q = NULL, *n = NULL; + r = generate_keys(secpar, seed, bn_ctx, &p, &q, &n); + if (r < 0) + return r; + + _cleanup_(BN_clear_freep) BIGNUM *x = NULL; + r = generate_square(secpar, seed, RND_GEN_X, n, bn_ctx, &x); + if (r < 0) + return r; + + if (epoch != 0) { + /* Decompose x from Z_n into Z_p × Z_q using CRT. */ + _cleanup_(BN_clear_freep) BIGNUM *xp = NULL, *xq = NULL; + r = crt_decompose(x, p, q, bn_ctx, &xp, &xq); + if (r < 0) + return r; + + /* Compute 2^epoch (mod phi(p)). */ + _cleanup_(BN_clear_freep) BIGNUM *kp = NULL; + r = compute_two_pow_mod_phi(epoch, p, bn_ctx, &kp); + if (r < 0) + return r; + + /* Compute 2^epoch (mod phi(q)). */ + _cleanup_(BN_clear_freep) BIGNUM *kq = NULL; + r = compute_two_pow_mod_phi(epoch, q, bn_ctx, &kq); + if (r < 0) + return r; + + /* Compute x^(2^epoch) (mod p). */ + if (sym_BN_mod_exp(xp, xp, kp, p, bn_ctx) <= 0) + return -EIO; + + /* Compute x^(2^epoch) (mod q). */ + if (sym_BN_mod_exp(xq, xq, kq, q, bn_ctx) <= 0) + return -EIO; + + /* Reconstruct x modulo n from its residues modulo p and q. */ + BN_clear_freep(&x); + r = crt_compose(xp, xq, p, q, bn_ctx, &x); + if (r < 0) + return r; + } + + return save_state(secpar, n, x, epoch, state); +#else + return -EOPNOTSUPP; +#endif +} + +int fsprg_evolve(struct iovec *state) { +#if HAVE_OPENSSL + int r; + + assert(iovec_is_set(state)); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + uint16_t secpar; + uint64_t epoch; + _cleanup_(BN_clear_freep) BIGNUM *n = NULL, *x = NULL; + r = parse_state(state, &secpar, &n, &x, &epoch); + if (r < 0) + return r; + + _cleanup_(BN_CTX_freep) BN_CTX *bn_ctx = sym_BN_CTX_secure_new(); + if (!bn_ctx) + return -ENOMEM; + + /* Update the current state x := x^2 mod n */ + if (sym_BN_mod_sqr(x, x, n, bn_ctx) <= 0) + return -EIO; + + /* Store the updated current state. */ + uint8_t *s = (uint8_t*) state->iov_base + sizeof(uint16_t) + secpar / 8; + r = mpi_export(&IOVEC_MAKE(s, secpar / 8), x); + if (r < 0) + return r; + + /* Increment epoch */ + unaligned_write_be64(s + secpar / 8, epoch + 1); + return 0; +#else + return -EOPNOTSUPP; +#endif +} + +int fsprg_get_epoch(const struct iovec *state, uint64_t *ret) { +#if HAVE_OPENSSL + int r; + + assert(iovec_is_set(state)); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + return parse_state( + state, + /* ret_secpar= */ NULL, + /* ret_modulus= */ NULL, + /* ret_current= */ NULL, + ret); +#else + return -EOPNOTSUPP; +#endif +} + +int fsprg_get_key(const struct iovec *state, struct iovec *key) { +#if HAVE_OPENSSL + int r; + + assert(iovec_is_set(state)); + assert(iovec_is_valid(key)); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + uint16_t secpar; + r = parse_state(state, + /* ret_secpar= */ &secpar, + /* ret_modulus= */ NULL, + /* ret_current= */ NULL, + /* ret_epoch= */ NULL); + if (r < 0) + return r; + + struct iovec seed = IOVEC_MAKE( + (uint8_t*) state->iov_base + sizeof(uint16_t), + 2 * secpar / 8 + sizeof(uint64_t)); + + return det_randomize(&seed, /* idx= */ 0, key); +#else + return -EOPNOTSUPP; +#endif +} diff --git a/src/shared/fsprg-openssl.h b/src/shared/fsprg-openssl.h new file mode 100644 index 0000000000000..e9e5649617326 --- /dev/null +++ b/src/shared/fsprg-openssl.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-forward.h" + +#define FSPRG_RECOMMENDED_SECPAR 1536 +#define FSPRG_RECOMMENDED_SEEDLEN (96/8) + +bool fsprg_secpar_is_valid(uint16_t secpar); +size_t fsprg_state_size(uint16_t secpar); +int fsprg_generate_state( + uint16_t secpar, + uint64_t epoch, + const struct iovec *seed, + struct iovec *state); +int fsprg_evolve(struct iovec *state); +int fsprg_get_epoch(const struct iovec *state, uint64_t *ret); +int fsprg_get_key(const struct iovec *state, struct iovec *key); diff --git a/src/shared/generate-dns_type-gperf.py b/src/shared/generate-dns_type-gperf.py index 0fd003a8023ae..79d92cb617999 100755 --- a/src/shared/generate-dns_type-gperf.py +++ b/src/shared/generate-dns_type-gperf.py @@ -1,26 +1,24 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later -"""Generate %-from-name.gperf from %-list.txt -""" +"""Generate %-from-name.gperf from %-list.txt""" import sys name, prefix, input = sys.argv[1:] -print("""\ -%{ +print(f'''\ +%{{ _Pragma("GCC diagnostic ignored \\"-Wimplicit-fallthrough\\"") #if __GNUC__ >= 15 _Pragma("GCC diagnostic ignored \\"-Wzero-as-null-pointer-constant\\"") #endif -%}""") -print("""\ -struct {}_name {{ const char* name; int id; }}; +%}} +struct {name}_name {{ const char* name; int id; }}; %null-strings -%%""".format(name)) +%%''') for line in open(input): line = line.rstrip() s = line.replace('_', '-') - print("{}, {}{}".format(s, prefix, line)) + print(f'{s}, {prefix}{line}') diff --git a/src/shared/generate-syscall-list.py b/src/shared/generate-syscall-list.py index c0975a06da812..111e8b7367e04 100755 --- a/src/shared/generate-syscall-list.py +++ b/src/shared/generate-syscall-list.py @@ -4,4 +4,4 @@ import sys for line in open(sys.argv[1]): - print('"{}\\0"'.format(line.strip())) + print(f'"{line.strip()}\\0"') diff --git a/src/shared/generator.c b/src/shared/generator.c index 603e969436e9b..fa5bd25f1f82d 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -14,7 +14,7 @@ #include "generator.h" #include "initrd-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mountpoint-util.h" #include "parse-util.h" #include "path-util.h" @@ -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")) @@ -818,12 +817,18 @@ int generator_hook_up_quotacheck( if (isempty(fstype) || streq(fstype, "auto")) return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Couldn't determine filesystem type for %s, quota cannot be activated", what); + if (fstype_has_internal_quota(fstype)) { + log_debug("%s handles quotas internally, skipping quotacheck/quotaon setup for %s", fstype, what); + return 0; + } if (!fstype_needs_quota(fstype)) return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Quota was requested for %s, but not supported, ignoring: %s", what, fstype); /* quotacheck unit for system root */ - if (path_equal(where, "/")) - return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_ROOT_SERVICE); + if (path_equal(where, "/")) { + r = generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_ROOT_SERVICE); + return r < 0 ? r : 1; + } r = unit_name_path_escape(where, &instance); if (r < 0) @@ -839,7 +844,8 @@ int generator_hook_up_quotacheck( if (r < 0) return log_error_errno(r, "Failed to make unit name from path '%s': %m", where); - return generator_add_symlink_full(dir, where_unit, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_SERVICE, instance); + r = generator_add_symlink_full(dir, where_unit, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_SERVICE, instance); + return r < 0 ? r : 1; } int generator_hook_up_quotaon( @@ -915,7 +921,12 @@ int generator_write_cryptsetup_unit_section(FILE *f, const char *source) { fprintf(f, "\n" "DefaultDependencies=no\n" - "After=cryptsetup-pre.target systemd-udevd-kernel.socket systemd-tpm2-setup-early.service\n" + /* The ordering against systemd-udevd.service is mostly about shutdown, not startup: on stop + * the device is detached via a device-mapper ioctl that carries a udev cookie, and we block in + * dm_udev_wait() until udev releases it (see 95-dm-notify.rules). Ordering After= it means we + * are stopped first, while udevd is still around to service that cookie; otherwise during + * soft-reboot/shutdown udevd may exit first and the detach hangs until the job timeout. */ + "After=cryptsetup-pre.target systemd-udevd-kernel.socket systemd-udevd.service systemd-tpm2-setup-early.service\n" "Before=blockdev@dev-mapper-%%i.target\n" "Wants=blockdev@dev-mapper-%%i.target\n" "IgnoreOnIsolate=true\n"); @@ -987,7 +998,11 @@ int generator_write_veritysetup_unit_section(FILE *f, const char *source) { fprintf(f, "DefaultDependencies=no\n" "IgnoreOnIsolate=true\n" - "After=veritysetup-pre.target systemd-udevd-kernel.socket\n" + /* The systemd-udevd.service ordering is mostly about shutdown, not startup: on stop the dm + * device is detached via an ioctl carrying a udev cookie that blocks in dm_udev_wait() until + * udev releases it (95-dm-notify.rules). Stopping before udevd keeps it around to service the + * cookie; otherwise during soft-reboot/shutdown udevd may exit first and the detach hangs. */ + "After=veritysetup-pre.target systemd-udevd-kernel.socket systemd-udevd.service\n" "Before=blockdev@dev-mapper-%%i.target\n" "Wants=blockdev@dev-mapper-%%i.target\n"); diff --git a/src/shared/gnutls-util.c b/src/shared/gnutls-util.c new file mode 100644 index 0000000000000..4a13dbe1ec9cd --- /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 +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 + static void *gnutls_dl = NULL; + + 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/group-record.c b/src/shared/group-record.c index 85433b15374ef..361030f5a2719 100644 --- a/src/shared/group-record.c +++ b/src/shared/group-record.c @@ -34,6 +34,7 @@ static GroupRecord *group_record_free(GroupRecord *g) { free(g->group_name); free(g->realm); free(g->group_name_and_realm_auto); + strv_free(g->aliases); free(g->description); strv_free(g->members); @@ -92,6 +93,7 @@ static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_j { "matchMachineId", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, { "matchHostname", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, { "gid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(GroupRecord, gid), 0 }, + { "aliases", SD_JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, aliases), SD_JSON_RELAX }, { "members", SD_JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, members), SD_JSON_RELAX }, { "administrators", SD_JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, administrators), SD_JSON_RELAX }, {}, @@ -176,6 +178,7 @@ int group_record_load( static const sd_json_dispatch_field group_dispatch_table[] = { { "groupName", SD_JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(GroupRecord, group_name), SD_JSON_RELAX }, + { "aliases", SD_JSON_VARIANT_ARRAY, json_dispatch_user_group_list, offsetof(GroupRecord, aliases), SD_JSON_RELAX }, { "realm", SD_JSON_VARIANT_STRING, json_dispatch_realm, offsetof(GroupRecord, realm), 0 }, { "uuid", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(GroupRecord, uuid), 0 }, { "description", SD_JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(GroupRecord, description), 0 }, @@ -345,6 +348,12 @@ bool group_record_matches_group_name(const GroupRecord *g, const char *group_nam if (streq_ptr(g->group_name_and_realm_auto, group_name)) return true; + if (strv_contains(g->aliases, group_name)) + return true; + + if (record_name_matches_alias_realm(group_name, (char * const*) g->aliases, g->realm)) + return true; + return false; } @@ -370,7 +379,8 @@ bool group_record_match(GroupRecord *h, const UserDBMatch *match) { h->description, }; - if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names)) + if (!user_name_fuzzy_match(names, ELEMENTSOF(names), match->fuzzy_names) && + !user_name_fuzzy_match((const char**) h->aliases, strv_length(h->aliases), match->fuzzy_names)) return false; } diff --git a/src/shared/group-record.h b/src/shared/group-record.h index 04f3e07997653..35968aa29fbb8 100644 --- a/src/shared/group-record.h +++ b/src/shared/group-record.h @@ -12,6 +12,7 @@ typedef struct GroupRecord { char *group_name; char *realm; char *group_name_and_realm_auto; + char **aliases; sd_id128_t uuid; char *description; diff --git a/src/shared/help-util.c b/src/shared/help-util.c new file mode 100644 index 0000000000000..8968ba1377151 --- /dev/null +++ b/src/shared/help-util.c @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "ansi-color.h" +#include "help-util.h" +#include "path-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) { + const char *progname = + last_path_component(secure_getenv("SYSTEMD_INVOKED_AS")) + ?: program_invocation_short_name; + + assert(arguments); + + printf("%s>%s %s %s\n", + ansi_grey(), + ansi_normal(), + progname, + 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..e40ca8a66298b 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; @@ -314,6 +317,35 @@ static int read_swap_entries(SwapEntries *ret) { return 0; } +static int get_proc_meminfo_active(uint64_t *ret) { + _cleanup_free_ char *active_str = NULL; + uint64_t active; + int r; + + assert(ret); + + r = get_proc_field("/proc/meminfo", "Active(anon)", &active_str); + if (r < 0) + return log_debug_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m"); + + r = safe_atou64(active_str, &active); + if (r < 0) + return log_debug_errno(r, "Failed to parse Active(anon) '%s' from /proc/meminfo: %m", active_str); + + *ret = active; + return 0; +} + +static uint64_t swap_free(const SwapEntry *s) { + assert(s); + return LESS_BY(s->size, s->used); +} + +static bool swap_can_hold_image(const SwapEntry *s, uint64_t active) { + assert(s); + return active > 0 && active <= swap_free(s) * HIBERNATION_SWAP_THRESHOLD; +} + /* Attempt to find a suitable device for hibernation by parsing /proc/swaps, /sys/power/resume, and * /sys/power/resume_offset. * @@ -329,7 +361,8 @@ static int read_swap_entries(SwapEntries *ret) { * 1 - Values are set in /sys/power/resume and /sys/power/resume_offset. * * 0 - No values are set in /sys/power/resume and /sys/power/resume_offset. - * ret will represent the highest priority swap with most remaining space discovered in /proc/swaps. + * ret will represent the highest priority swap that can hold the hibernation image. If no swap is + * large enough, ret will represent the highest priority swap with most remaining space. * * Negative value in the case of error */ int find_suitable_hibernation_device_full(HibernationDevice *ret_device, uint64_t *ret_size, uint64_t *ret_used) { @@ -337,10 +370,15 @@ int find_suitable_hibernation_device_full(HibernationDevice *ret_device, uint64_ SwapEntry *entry = NULL; uint64_t resume_config_offset; dev_t resume_config_devno; + uint64_t active = 0; int r; assert(!ret_size == !ret_used); + /* Best-effort: used to prefer swaps that can hold the hibernation image. + * On failure, selection falls back to priority + free space only. */ + (void) get_proc_meminfo_active(&active); + r = read_resume_config(&resume_config_devno, &resume_config_offset); if (r < 0) return r; @@ -376,8 +414,14 @@ int find_suitable_hibernation_device_full(HibernationDevice *ret_device, uint64_ } if (!entry || - swap->priority > entry->priority || - swap->size - swap->used > entry->size - entry->used) + /* prefer a swap that can hold the image over one that cannot */ + (swap_can_hold_image(swap, active) && !swap_can_hold_image(entry, active)) || + /* among equal capacity fitness: higher priority wins */ + (swap_can_hold_image(swap, active) == swap_can_hold_image(entry, active) && + (swap->priority > entry->priority || + /* equal priority: more free space wins */ + (swap->priority == entry->priority && + swap_free(swap) > swap_free(entry))))) entry = swap; } @@ -415,28 +459,8 @@ int find_suitable_hibernation_device_full(HibernationDevice *ret_device, uint64_ return resume_config_devno > 0; } -static int get_proc_meminfo_active(unsigned long long *ret) { - _cleanup_free_ char *active_str = NULL; - unsigned long long active; - int r; - - assert(ret); - - r = get_proc_field("/proc/meminfo", "Active(anon)", &active_str); - if (r < 0) - return log_debug_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m"); - - r = safe_atollu(active_str, &active); - if (r < 0) - return log_debug_errno(r, "Failed to parse Active(anon) '%s' from /proc/meminfo: %m", active_str); - - *ret = active; - return 0; -} - int hibernation_is_safe(void) { - unsigned long long active; - uint64_t size, used; + uint64_t active, size, used; bool resume_set, bypass_space_check; int r; @@ -465,7 +489,7 @@ int hibernation_is_safe(void) { return r; r = active <= (size - used) * HIBERNATION_SWAP_THRESHOLD; - log_debug("Detected %s swap for hibernation: Active(anon)=%llu kB, size=%" PRIu64 " kB, used=%" PRIu64 " kB, threshold=%.2g%%", + log_debug("Detected %s swap for hibernation: Active(anon)=%" PRIu64 " kB, size=%" PRIu64 " kB, used=%" PRIu64 " kB, threshold=%.2g%%", r ? "enough" : "not enough", active, size, used, 100 * HIBERNATION_SWAP_THRESHOLD); if (!r) return -ENOSPC; diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c index 50a0f077fa7bf..2512292b0cd2d 100644 --- a/src/shared/hostname-setup.c +++ b/src/shared/hostname-setup.c @@ -1,13 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include #include +#include #include #include #include "sd-daemon.h" #include "alloc-util.h" +#include "constants.h" #include "creds-util.h" #include "fd-util.h" #include "fileio.h" @@ -23,6 +26,8 @@ #include "proc-cmdline.h" #include "process-util.h" #include "siphash24.h" +#include "stat-util.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" @@ -79,6 +84,30 @@ int shorten_overlong(const char *s, char **ret) { return 1; } +static int validate_and_substitute_hostname_from_source(const char *raw, const char *source, char **ret) { + int r; + + assert(raw); + assert(source); + assert(ret); + + /* Validate a raw hostname value that may carry '?'/'$' wildcards, expand the wildcards, then validate the + * concrete result. Shared by the credential and kernel command line paths. */ + if (!hostname_is_valid(raw, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK|VALID_HOSTNAME_WORD_TOKEN)) + return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Hostname specified %s is invalid, ignoring: %s", source, raw); + + _cleanup_free_ char *substituted = NULL; + r = hostname_substitute_wildcards(raw, &substituted); + if (r < 0) + return log_warning_errno(r, "Failed to substitute wildcards in hostname specified %s, ignoring: %m", source); + + if (!hostname_is_valid(substituted, VALID_HOSTNAME_TRAILING_DOT)) /* check that the expanded hostname is valid */ + return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Hostname specified %s is invalid after expansion, ignoring: %s", source, substituted); + + *ret = TAKE_PTR(substituted); + return 0; +} + static int acquire_hostname_from_credential(char **ret) { _cleanup_free_ char *cred = NULL; int r; @@ -91,14 +120,33 @@ static int acquire_hostname_from_credential(char **ret) { if (r == 0) /* not found */ return -ENXIO; - if (!hostname_is_valid(cred, VALID_HOSTNAME_TRAILING_DOT)) /* check that the hostname we return is valid */ - return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Hostname specified in system.hostname credential is invalid, ignoring: %s", cred); + r = validate_and_substitute_hostname_from_source(cred, "in the system.hostname credential", ret); + if (r < 0) + return r; log_info("Initializing hostname from credential."); - *ret = TAKE_PTR(cred); return 0; } +static int acquire_hostname_from_cmdline(char **ret) { + _cleanup_free_ char *hn = NULL; + int r; + + assert(ret); + + r = proc_cmdline_get_key("systemd.hostname", 0, &hn); + if (r < 0) + return log_warning_errno(r, "Failed to retrieve system hostname from kernel command line, ignoring: %m"); + if (r == 0) /* not specified */ + return -ENXIO; + + /* The name may contain '?'/'$' wildcards (see hostname(5)). In the initrd the word lists (and + * possibly the machine ID) are typically not available yet so returning here means the default + * hostname will be used. Once the host file system is up the expansion succeeds and the intended + * name is applied. */ + return validate_and_substitute_hostname_from_source(hn, "on the kernel command line", ret); +} + int read_etc_hostname_stream(FILE *f, bool substitute_wildcards, char **ret) { int r; @@ -119,9 +167,13 @@ int read_etc_hostname_stream(FILE *f, bool substitute_wildcards, char **ret) { continue; if (substitute_wildcards) { - r = hostname_substitute_wildcards(line); + _cleanup_free_ char *substituted = NULL; + + r = hostname_substitute_wildcards(line, &substituted); if (r < 0) return r; + + free_and_replace(line, substituted); } hostname_cleanup(line); /* normalize the hostname */ @@ -130,7 +182,7 @@ int read_etc_hostname_stream(FILE *f, bool substitute_wildcards, char **ret) { if (!hostname_is_valid( line, VALID_HOSTNAME_TRAILING_DOT| - (substitute_wildcards ? 0 : VALID_HOSTNAME_QUESTION_MARK))) + (substitute_wildcards ? 0 : VALID_HOSTNAME_QUESTION_MARK|VALID_HOSTNAME_WORD_TOKEN))) return -EBADMSG; *ret = TAKE_PTR(line); @@ -177,17 +229,9 @@ int hostname_setup(bool really) { bool enoent = false; int r; - r = proc_cmdline_get_key("systemd.hostname", 0, &hn); - if (r < 0) - log_warning_errno(r, "Failed to retrieve system hostname from kernel command line, ignoring: %m"); - else if (r > 0) { - if (hostname_is_valid(hn, VALID_HOSTNAME_TRAILING_DOT)) - source = HOSTNAME_TRANSIENT; - else { - log_warning("Hostname specified on kernel command line is invalid, ignoring: %s", hn); - hn = mfree(hn); - } - } + r = acquire_hostname_from_cmdline(&hn); + if (r >= 0) + source = HOSTNAME_TRANSIENT; if (!hn) { r = read_etc_hostname(/* path= */ NULL, /* substitute_wildcards= */ true, &hn); @@ -254,48 +298,224 @@ static const char* const hostname_source_table[] = { DEFINE_STRING_TABLE_LOOKUP(hostname_source, HostnameSource); -int hostname_substitute_wildcards(char *name) { +static int hostname_open_wordlist(const char *file, FILE **ret) { + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(file); + assert(ret); + + /* Opens one of the numbered hostname word list files ("1", "2", "3", ...) for the '$' wildcards. */ + const char *override = secure_getenv("SYSTEMD_HOSTNAME_WORDLIST_PATH"); + r = search_and_fopen( + file, + "re", + /* root= */ NULL, + override ? (const char**) STRV_MAKE(override) : (const char**) CONF_PATHS_STRV("systemd/hostname-wordlist"), + &f, + /* ret_path= */ NULL); + if (r < 0) + return r; + + *ret = TAKE_PTR(f); + return 0; +} + +static bool normalize_and_validate_word(char *word) { + assert(word); + + if (IN_SET(word[0], '\0', '#')) /* empty line or comment */ + return false; + + ascii_strlower(word); + return hostname_is_valid(word, /* flags= */ 0); +} + +static int pick_word_linear_scan(FILE *f, off_t offset, char **ret) { + int r; + + assert(f); + assert(ret); + + if (fseeko(f, offset, SEEK_SET) < 0) + return -errno; + + bool wrapped = false; + r = read_line(f, LONG_LINE_MAX, NULL); /* discard the partial line we landed in */ + if (r < 0) + return r; + if (r == 0) { + wrapped = true; + rewind(f); + } + + for (;;) { + _cleanup_free_ char *line = NULL; + + r = read_stripped_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) { /* hit EOF: we started at a random offset, wrap around to the beginning */ + if (wrapped) /* already wrapped once, the file contains no usable word at all */ + return -ENOENT; + wrapped = true; + rewind(f); + continue; + } + if (normalize_and_validate_word(line)) { + *ret = TAKE_PTR(line); + return 0; + } + } +} + +static int hostname_pick_word(sd_id128_t mid, size_t pos, char **ret) { + static const sd_id128_t word_key = SD_ID128_MAKE(2d,9f,1c,7a,4b,8e,43,11,9a,6d,5f,02,c8,77,e3,14); + _cleanup_fclose_ FILE *f = NULL; + struct stat st; + int r; + + assert(pos >= 1); + assert(ret); + + /* The n-th '$' in a template reads the word list file named after its position, i.e. "1", "2", ... */ + char file[DECIMAL_STR_MAX(size_t)]; + xsprintf(file, "%zu", pos); + + r = hostname_open_wordlist(file, &f); + if (r < 0) + return r; + + if (fstat(fileno(f), &st) < 0) + return -errno; + r = stat_verify_regular(&st); + if (r < 0) + return r; + if (st.st_size == 0) + return -ENOENT; + + /* Pick a word without reading the whole list into memory: + * 1. pick a random offset in the file [0 … st.st_size-1] + * 2. if offset is zero, read a full line from the beginning of the file, use that. + * 3. otherwise, seek to offset minus 1 and read one character. + * 4. if that character is newline, then read a full line after it, and use that as result + * 5. otherwise, goto 1 + * + * As a safety net terminate after a fixed number iterations (for pathological wordlists) + * This stream is independent of the '?' nibble stream so pure-'?' * templates keep producing + * byte-identical output. Stable as long as the wordlist is stable. */ + off_t offset = 0; + const unsigned int MAX_ITERATIONS = 64; + for (unsigned i = 0; i < MAX_ITERATIONS; i++) { + _cleanup_free_ char *line = NULL; + + struct siphash state; + siphash24_init(&state, word_key.bytes); + siphash24_compress_typesafe(mid, &state); + siphash24_compress_typesafe(pos, &state); + siphash24_compress_typesafe(i, &state); /* counter mode */ + offset = (off_t) (siphash24_finalize(&state) % (uint64_t) st.st_size); + + if (offset > 0) { + if (fseeko(f, offset - 1, SEEK_SET) < 0) + return -errno; + if (fgetc(f) != '\n') + continue; /* not a line start */ + } else if (fseeko(f, 0, SEEK_SET) < 0) /* offset 0 always begins the first line */ + return -errno; + + r = read_stripped_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) /* raced with truncation */ + continue; + if (normalize_and_validate_word(line)) { + *ret = TAKE_PTR(line); + return 0; + } + /* Comment/empty/invalid line: resample rather than advancing, to keep the pick uniform. */ + } + + /* We exhausted the uniform attempts, this should never happen but if it does fallback to picking the + * next word after our last attempt. */ + log_warning("hostname_pick_word did not find a usable word after %u in wordlist %zu", MAX_ITERATIONS, pos); + return pick_word_linear_scan(f, offset, ret); +} + +int hostname_substitute_wildcards(const char *name, char **ret) { static const sd_id128_t key = SD_ID128_MAKE(98,10,ad,df,8d,7d,4f,b5,89,1b,4b,56,ac,c2,26,8f); sd_id128_t mid = SD_ID128_NULL; - size_t left_bits = 0, counter = 0; + _cleanup_free_ char *result = NULL; + size_t left_bits = 0, counter = 0, word_pos = 0, n_result = 0; uint64_t h = 0; int r; assert(name); + assert(ret); - /* Replaces every occurrence of '?' in the specified string with a nibble hashed from - * /etc/machine-id. This is supposed to be used on /etc/hostname files that want to automatically - * configure a hostname derived from the machine ID in some form. + if (isempty(name)) + return strdup_to(ret, ""); + + /* Expands wildcards in the specified string, deriving the inserted values deterministically from + * /etc/machine-id: + * + * '?' is replaced by a single hex nibble hashed from the machine ID. + * '$' is replaced by a word picked from a word list; the n-th '$' in the string uses the list + * file named "n" * - * Note that this does not directly use the machine ID, because that's not necessarily supposed to be - * public information to be broadcast on the network, while the hostname certainly is. */ + * This is supposed to be used on /etc/hostname files that want to automatically configure a hostname + * derived from the machine ID in some form, e.g. "$-$-????". + * + * Note that this does not directly expose the machine ID, because that's not necessarily supposed to + * be public information to be broadcast on the network, while the hostname certainly is. */ - for (char *n = name; ; n++) { - n = strchr(n, '?'); - if (!n) - return 0; + for (const char *n = name; *n; n++) { + if (IN_SET(*n, '?', '$') && sd_id128_is_null(mid)) { + r = sd_id128_get_machine(&mid); + if (r < 0) + return r; + } - if (left_bits <= 0) { - if (sd_id128_is_null(mid)) { - r = sd_id128_get_machine(&mid); - if (r < 0) - return r; + if (*n == '?') { + if (left_bits <= 0) { + struct siphash state; + siphash24_init(&state, key.bytes); + siphash24_compress_typesafe(mid, &state); + siphash24_compress_typesafe(counter, &state); /* counter mode */ + h = siphash24_finalize(&state); + left_bits = sizeof(h) * 8; + counter++; } - struct siphash state; - siphash24_init(&state, key.bytes); - siphash24_compress(&mid, sizeof(mid), &state); - siphash24_compress(&counter, sizeof(counter), &state); /* counter mode */ - h = siphash24_finalize(&state); - left_bits = sizeof(h) * 8; - counter++; - } + assert(left_bits >= 4); + char c = hexchar(h & 0xf); + h >>= 4; + left_bits -= 4; + + if (!GREEDY_REALLOC_APPEND(result, n_result, &c, 1)) + return -ENOMEM; - assert(left_bits >= 4); - *n = hexchar(h & 0xf); - h >>= 4; - left_bits -= 4; + } else if (*n == '$') { + /* Each '$' is an independent word token; the n-th one picks from word list "n". + * There is no escape for a literal '$', as it is not a valid hostname character. */ + _cleanup_free_ char *w = NULL; + r = hostname_pick_word(mid, ++word_pos, &w); + if (r < 0) + return r; + + if (!GREEDY_REALLOC_APPEND(result, n_result, w, strlen(w))) + return -ENOMEM; + + } else if (!GREEDY_REALLOC_APPEND(result, n_result, n, 1)) + return -ENOMEM; } + + if (!GREEDY_REALLOC(result, n_result + 1)) + return -ENOMEM; + result[n_result] = 0; + + *ret = TAKE_PTR(result); + return 0; } char* get_default_hostname(void) { @@ -305,13 +525,20 @@ char* get_default_hostname(void) { if (!h) return NULL; - r = hostname_substitute_wildcards(h); + _cleanup_free_ char *substituted = NULL; + r = hostname_substitute_wildcards(h, &substituted); if (r < 0) { log_debug_errno(r, "Failed to substitute wildcards in hostname, falling back to built-in name: %m"); return strdup(FALLBACK_HOSTNAME); } - return TAKE_PTR(h); + /* Each token expands to a whole word, so the concrete name may exceed the length limit. */ + if (!hostname_is_valid(substituted, VALID_HOSTNAME_TRAILING_DOT)) { + log_debug("Substituted hostname '%s' is invalid, falling back to built-in name.", substituted); + return strdup(FALLBACK_HOSTNAME); + } + + return TAKE_PTR(substituted); } int gethostname_full(GetHostnameFlags flags, char **ret) { diff --git a/src/shared/hostname-setup.h b/src/shared/hostname-setup.h index ee1c932d28480..802f7c9f20256 100644 --- a/src/shared/hostname-setup.h +++ b/src/shared/hostname-setup.h @@ -22,7 +22,7 @@ int read_etc_hostname(const char *path, bool substitute_wildcards, char **ret); void hostname_update_source_hint(const char *hostname, HostnameSource source); int hostname_setup(bool really); -int hostname_substitute_wildcards(char *name); +int hostname_substitute_wildcards(const char *name, char **ret); char* get_default_hostname(void); diff --git a/src/shared/hwdb-util.c b/src/shared/hwdb-util.c index 29942254f37d7..b42681a289cc7 100644 --- a/src/shared/hwdb-util.c +++ b/src/shared/hwdb-util.c @@ -15,7 +15,7 @@ #include "hwdb-util.h" #include "label-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "nulstr-util.h" #include "path-util.h" #include "sort-util.h" @@ -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..01c808b57fead 100644 --- a/src/shared/idn-util.c +++ b/src/shared/idn-util.c @@ -3,21 +3,29 @@ #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", - "Support for internationalized domain names", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, - "libidn2.so.0"); +#endif + +int dlopen_idn(int log_level) { +#if HAVE_LIBIDN2 + IDN_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); 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..9bed27140fde9 100644 --- a/src/shared/idn-util.h +++ b/src/shared/idn-util.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dlopen.h" + #include "shared-forward.h" #if HAVE_LIBIDN2 @@ -12,9 +14,19 @@ 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); +#define IDN_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("idn", \ + "Support for internationalized domain names", \ + priority, \ + "libidn2.so.0") + +#define DLOPEN_IDN(log_level, priority) \ + ({ \ + IDN_NOTE(priority); \ + dlopen_idn(log_level); \ + }) #else -static inline int dlopen_idn(void) { - return -EOPNOTSUPP; -} +#define DLOPEN_IDN(log_level, priority) dlopen_idn(log_level) #endif + +int dlopen_idn(int log_level); diff --git a/src/shared/image-policy.c b/src/shared/image-policy.c index 59d1161d04657..622b5c2aa14b3 100644 --- a/src/shared/image-policy.c +++ b/src/shared/image-policy.c @@ -948,6 +948,12 @@ ImagePolicy* image_policy_free(ImagePolicy *p) { return mfree(p); } +ImagePolicy* image_policy_copy(const ImagePolicy *p) { + assert(p); + + return memdup(p, offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * p->n_policies); +} + int image_policy_ignore_designators(const ImagePolicy *p, const PartitionDesignator table[], size_t n_table, ImagePolicy **ret) { assert(p); assert(table || n_table == 0); diff --git a/src/shared/image-policy.h b/src/shared/image-policy.h index fe2bf0d20041a..b0c229e71eabd 100644 --- a/src/shared/image-policy.h +++ b/src/shared/image-policy.h @@ -119,6 +119,8 @@ ImagePolicy* image_policy_free(ImagePolicy *p); DEFINE_TRIVIAL_CLEANUP_FUNC(ImagePolicy*, image_policy_free); +ImagePolicy* image_policy_copy(const ImagePolicy *p); + CONFIG_PARSER_PROTOTYPE(config_parse_image_policy); int parse_image_policy_argument(const char *s, ImagePolicy **policy); 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/initrd-cpio.c b/src/shared/initrd-cpio.c new file mode 100644 index 0000000000000..e19bbcd34104b --- /dev/null +++ b/src/shared/initrd-cpio.c @@ -0,0 +1,185 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "alloc-util.h" +#include "creds-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "initrd-cpio.h" +#include "io-util.h" +#include "log.h" +#include "machine-credential.h" +#include "memory-util.h" +#include "memstream-util.h" +#include "string-util.h" +#include "tmpfile-util.h" + +static void write_cpio_word(FILE *f, uint32_t v) { + assert(f); + + /* Writes a CPIO header 8 character hex value */ + + fprintf(f, "%08" PRIx32, v); +} + +static int append_pad4(FILE *f) { + off_t p; + + assert(f); + + /* Appends NUL bytes until the stream position is a multiple of 4 */ + + p = ftello(f); + if (p < 0) + return -errno; + + for (size_t pad = (4 - ((size_t) p & 3)) & 3; pad > 0; pad--) + fputc(0, f); + + return 0; +} + +static int append_cpio_entry( + FILE *f, + uint32_t mode, /* full mode incl. S_IFDIR or S_IFREG */ + const char *path, + const void *data, /* NULL for directories */ + size_t data_size, /* 0 for directories */ + uint32_t *inode_counter) { + + int r; + + assert(f); + assert(path); + assert(data || data_size == 0); + assert(inode_counter); + + if (data_size > UINT32_MAX) /* cpio cannot deal with > 32-bit file sizes */ + return -EFBIG; + + if (*inode_counter == UINT32_MAX) /* more than 2^32-1 inodes? cpio cannot represent that either */ + return -EOVERFLOW; + + size_t namesize = strlen(path) + 1; + if (namesize > UINT32_MAX) /* cpio also cannot deal with names > 32-bit */ + return -ENAMETOOLONG; + + fputs("070701", f); /* magic ID */ + write_cpio_word(f, (*inode_counter)++); /* inode */ + write_cpio_word(f, mode); /* mode */ + write_cpio_word(f, 0); /* uid */ + write_cpio_word(f, 0); /* gid */ + write_cpio_word(f, 1); /* nlink */ + write_cpio_word(f, 0); /* mtime */ + write_cpio_word(f, data_size); /* size */ + write_cpio_word(f, 0); /* major(dev) */ + write_cpio_word(f, 0); /* minor(dev) */ + write_cpio_word(f, 0); /* major(rdev) */ + write_cpio_word(f, 0); /* minor(rdev) */ + write_cpio_word(f, namesize); /* fname size */ + write_cpio_word(f, 0); /* crc */ + + fwrite(path, 1, namesize, f); + + r = append_pad4(f); + if (r < 0) + return r; + + if (data_size > 0) + fwrite(data, 1, data_size, f); + + return append_pad4(f); +} + +static void append_cpio_trailer(FILE *f) { + static const char trailer[] = + "070701" + "00000000" + "00000000" + "00000000" + "00000000" + "00000001" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "0000000b" + "00000000" + "TRAILER!!!\0\0\0"; /* There's a fourth NUL byte appended here, because this is a string */ + + assert_cc(sizeof(trailer) % 4 == 0); + assert(f); + + fwrite(trailer, 1, sizeof trailer, f); +} + +int initrd_cpio_credentials_to_tempfile( + const MachineCredentialContext *creds, + char **ret_path) { + + _cleanup_(memstream_done) MemStream m = {}; + _cleanup_(unlink_and_freep) char *path = NULL; + _cleanup_close_ int fd = -EBADF; + _cleanup_(erase_and_freep) char *buf = NULL; /* holds plaintext credential bytes; scrub on free */ + size_t buf_size = 0; + uint32_t inode = 1; + FILE *f; + int r; + + assert(creds); + assert(ret_path); + + if (creds->n_credentials == 0) { + *ret_path = NULL; + return 0; + } + + f = memstream_init(&m); + if (!f) + return log_oom(); + + r = append_cpio_entry(f, S_IFDIR | 0555, ".extra", NULL, 0, &inode); + if (r < 0) + return log_error_errno(r, "Failed to write '.extra' directory entry to credentials cpio: %m"); + r = append_cpio_entry(f, S_IFDIR | 0500, ".extra/system_credentials", NULL, 0, &inode); + if (r < 0) + return log_error_errno(r, "Failed to write '.extra/system_credentials' directory entry to credentials cpio: %m"); + + FOREACH_ARRAY(c, creds->credentials, creds->n_credentials) { + if (!credential_name_valid(c->id)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name '%s'.", strna(c->id)); + + _cleanup_free_ char *cpath = strjoin(".extra/system_credentials/", c->id, ".cred"); + if (!cpath) + return log_oom(); + + r = append_cpio_entry(f, S_IFREG | 0400, cpath, c->data, c->size, &inode); + if (r < 0) + return log_error_errno(r, "Failed to write credential '%s' to credentials cpio: %m", c->id); + } + + append_cpio_trailer(f); + + r = memstream_finalize(&m, &buf, &buf_size); + if (r < 0) + return log_error_errno(r, "Failed to finalize credentials cpio: %m"); + + r = tempfn_random_child(NULL, "credentials-cpio", &path); + if (r < 0) + return log_error_errno(r, "Failed to generate temp file name: %m"); + + fd = open(path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600); + if (fd < 0) + return log_error_errno(errno, "Failed to create temp file %s: %m", path); + + r = loop_write(fd, buf, buf_size); + if (r < 0) + return log_error_errno(r, "Failed to write credentials cpio: %m"); + + *ret_path = TAKE_PTR(path); + return 0; +} diff --git a/src/shared/initrd-cpio.h b/src/shared/initrd-cpio.h new file mode 100644 index 0000000000000..dd6a7376361b5 --- /dev/null +++ b/src/shared/initrd-cpio.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +/* Builds a new CPIO archive containing each credential as a + * file under .extra/system_credentials/.cred, writes it to a + * freshly-created temp file, and returns the path in *ret_path. + * Caller takes ownership and is responsible for unlink()+free(). + * If creds contains no credentials, no file will be created and + * *ret_path is set to NULL. */ +int initrd_cpio_credentials_to_tempfile( + const MachineCredentialContext *creds, + char **ret_path); 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..6fc43a8df4ee9 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -22,7 +22,7 @@ #include "install.h" #include "install-printf.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "path-lookup.h" #include "path-util.h" #include "rm-rf.h" @@ -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/ipvlan-util.c b/src/shared/ipvlan-util.c index 1906c8026f470..5c271ba4b688b 100644 --- a/src/shared/ipvlan-util.c +++ b/src/shared/ipvlan-util.c @@ -12,7 +12,7 @@ static const char* const ipvlan_mode_table[_NETDEV_IPVLAN_MODE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(ipvlan_mode, IPVlanMode); static const char* const ipvlan_flags_table[_NETDEV_IPVLAN_FLAGS_MAX] = { - [NETDEV_IPVLAN_FLAGS_BRIGDE] = "bridge", + [NETDEV_IPVLAN_FLAGS_BRIDGE] = "bridge", [NETDEV_IPVLAN_FLAGS_PRIVATE] = "private", [NETDEV_IPVLAN_FLAGS_VEPA] = "vepa", }; diff --git a/src/shared/ipvlan-util.h b/src/shared/ipvlan-util.h index 4cb74f3dcbb92..194e5cea04289 100644 --- a/src/shared/ipvlan-util.h +++ b/src/shared/ipvlan-util.h @@ -14,7 +14,7 @@ typedef enum IPVlanMode { } IPVlanMode; typedef enum IPVlanFlags { - NETDEV_IPVLAN_FLAGS_BRIGDE, + NETDEV_IPVLAN_FLAGS_BRIDGE, NETDEV_IPVLAN_FLAGS_PRIVATE = IPVLAN_F_PRIVATE, NETDEV_IPVLAN_FLAGS_VEPA = IPVLAN_F_VEPA, _NETDEV_IPVLAN_FLAGS_MAX, diff --git a/src/shared/journal-authenticate.c b/src/shared/journal-authenticate.c new file mode 100644 index 0000000000000..20434c9b0b275 --- /dev/null +++ b/src/shared/journal-authenticate.c @@ -0,0 +1,596 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "alloc-util.h" +#include "crypto-util.h" +#include "fd-util.h" +#include "fsprg-openssl.h" +#include "hexdecoct.h" +#include "iovec-util.h" +#include "journal-authenticate.h" +#include "journal-def.h" +#include "journal-file.h" +#include "log.h" +#include "memory-util.h" +#include "string-util.h" +#include "time-util.h" + +#if HAVE_OPENSSL + +struct JournalAuthContext { + EVP_MAC *hmac; + EVP_MAC_CTX *hmac_ctx; + OSSL_PARAM *ossl_params; + bool hmac_running; + + FSSHeader *fss_file; + size_t fss_mmap_size; + + uint64_t fss_start_usec; + uint64_t fss_interval_usec; + + struct iovec fsprg_state; + struct iovec fsprg_seed; +}; + +static JournalAuthContext* journal_auth_free(JournalAuthContext *c) { + if (!c) + return NULL; + + if (c->fss_file) + munmap(c->fss_file, c->fss_mmap_size); + else + iovec_done_erase(&c->fsprg_state); + + iovec_done_erase(&c->fsprg_seed); + + if (c->ossl_params) + sym_OSSL_PARAM_free(c->ossl_params); + if (c->hmac_ctx) + sym_EVP_MAC_CTX_free(c->hmac_ctx); + if (c->hmac) + sym_EVP_MAC_free(c->hmac); + + return mfree(c); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(JournalAuthContext*, journal_auth_free); + +static void* fssheader_free(FSSHeader *p) { + /* mmap() returns MAP_FAILED on error and sets the errno */ + if (!p || p == MAP_FAILED) + return NULL; + + assert_se(munmap(p, PAGE_ALIGN(sizeof(FSSHeader))) >= 0); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(FSSHeader*, fssheader_free); + +static int journal_auth_load(JournalAuthContext **ret) { + int r; + + assert(ret); + + /* This function is used to determine whether sealing should be enabled in the journal header so we + * can't check the header to check if sealing is enabled here. */ + + sd_id128_t machine; + r = sd_id128_get_machine(&machine); + if (r < 0) + return r; + + _cleanup_free_ char *path = NULL; + if (asprintf(&path, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss", + SD_ID128_FORMAT_VAL(machine)) < 0) + return -ENOMEM; + + _cleanup_close_ int fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY, 0600); + if (fd < 0) { + if (errno != ENOENT) + log_error_errno(errno, "Failed to open %s: %m", path); + + return -errno; + } + + struct stat st; + if (fstat(fd, &st) < 0) + return -errno; + + if (st.st_size < (off_t) sizeof(FSSHeader)) + return -ENODATA; + + _cleanup_(fssheader_freep) FSSHeader *header = + mmap(NULL, PAGE_ALIGN(sizeof(FSSHeader)), PROT_READ, MAP_SHARED, fd, 0); + if (header == MAP_FAILED) + return -errno; + + if (memcmp(header->signature, (const uint8_t[]) FSS_HEADER_SIGNATURE, 8) != 0) + return -EBADMSG; + + if (header->incompatible_flags != 0) + return -EPROTONOSUPPORT; + + if (le64toh(header->header_size) < sizeof(FSSHeader)) + return -EBADMSG; + + uint16_t secpar = le16toh(header->fsprg_secpar); + if (!fsprg_secpar_is_valid(secpar)) + return -EBADMSG; + + if (le64toh(header->fsprg_state_size) != fsprg_state_size(secpar)) + return -EBADMSG; + + uint64_t fss_file_size; + if (!ADD_SAFE(&fss_file_size, le64toh(header->header_size), le64toh(header->fsprg_state_size))) + return -EBADMSG; + + if (fss_file_size >= SIZE_MAX) + return -EBADMSG; + + if ((uint64_t) st.st_size < fss_file_size) + return -ENODATA; + + if (!sd_id128_equal(machine, header->machine_id)) + return -EHOSTDOWN; + + if (le64toh(header->start_usec) <= 0 || le64toh(header->interval_usec) <= 0) + return -EBADMSG; + + _cleanup_(journal_auth_freep) JournalAuthContext *c = new0(JournalAuthContext, 1); + if (!c) + return -ENOMEM; + + size_t sz = PAGE_ALIGN(fss_file_size); + if (sz >= SIZE_MAX) + return -EBADMSG; + + FSSHeader *p = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) + return -errno; + + *c = (JournalAuthContext) { + .fss_mmap_size = sz, + .fss_file = p, + + .fss_start_usec = le64toh(p->start_usec), + .fss_interval_usec = le64toh(p->interval_usec), + + .fsprg_state = IOVEC_MAKE( + (uint8_t*) p + le64toh(p->header_size), + le64toh(p->fsprg_state_size)), + }; + + *ret = TAKE_PTR(c); + return 0; +} + +static int journal_auth_load_key(JournalAuthContext **ret, const char *key) { + int r; + + assert(ret); + assert(key); + + size_t seed_size = FSPRG_RECOMMENDED_SEEDLEN; + _cleanup_(erase_and_freep) uint8_t *seed = malloc(seed_size); + if (!seed) + return -ENOMEM; + + const char *k = key; + for (size_t c = 0; c < seed_size; c++) { + int x, y; + + k = skip_leading_chars(k, "-"); + + x = unhexchar(*k); + if (x < 0) + return -EKEYREJECTED; + k++; + + y = unhexchar(*k); + if (y < 0) + return -EKEYREJECTED; + k++; + + seed[c] = (uint8_t) (x * 16 + y); + } + + if (*k != '/') + return -EKEYREJECTED; + k++; + + uint64_t start, interval; + r = sscanf(k, "%"PRIx64"-%"PRIx64, &start, &interval); + if (r != 2) + return -EKEYREJECTED; + + if (start == 0 || interval == 0) + return -EKEYREJECTED; + + uint64_t start_usec; + if (!MUL_SAFE(&start_usec, start, interval)) + return -EKEYREJECTED; + + _cleanup_(journal_auth_freep) JournalAuthContext *c = new(JournalAuthContext, 1); + if (!c) + return -ENOMEM; + + *c = (JournalAuthContext) { + .fss_start_usec = start_usec, + .fss_interval_usec = interval, + + .fsprg_seed = IOVEC_MAKE(TAKE_PTR(seed), seed_size), + }; + + *ret = TAKE_PTR(c); + return 0; +} + +static int journal_auth_epoch_to_realtime_usec(const JournalAuthContext *c, uint64_t epoch, usec_t *ret_start, usec_t *ret_end) { + assert(c); + assert(c->fss_start_usec > 0); + assert(c->fss_interval_usec > 0); + + uint64_t start, end; + if (!MUL_SAFE(&start, epoch, c->fss_interval_usec) || + !INC_SAFE(&start, c->fss_start_usec) || + !ADD_SAFE(&end, start, c->fss_interval_usec)) + return -ERANGE; + + if (ret_start) + *ret_start = start; + if (ret_end) + *ret_end = end; + + return 0; +} + +static int journal_auth_next_evolve_usec(const JournalAuthContext *c, usec_t *ret) { + int r; + + assert(c); + + uint64_t epoch; + r = fsprg_get_epoch(&c->fsprg_state, &epoch); + if (r < 0) + return r; + + return journal_auth_epoch_to_realtime_usec(c, epoch, /* ret_start= */ NULL, ret); +} + +static int journal_auth_seek(JournalAuthContext *c, uint64_t goal) { + int r; + + assert(c); + assert(iovec_is_set(&c->fsprg_seed)); + + if (iovec_is_set(&c->fsprg_state)) { + uint64_t epoch; + r = fsprg_get_epoch(&c->fsprg_state, &epoch); + if (r < 0) + return r; + if (goal == epoch) + return 0; + + if (goal == epoch + 1) + return fsprg_evolve(&c->fsprg_state); + } else { + r = iovec_alloc(fsprg_state_size(FSPRG_RECOMMENDED_SECPAR), &c->fsprg_state); + if (r < 0) + return r; + } + + log_debug("Seeking FSPRG key to %"PRIu64".", goal); + return fsprg_generate_state(FSPRG_RECOMMENDED_SECPAR, goal, &c->fsprg_seed, &c->fsprg_state); +} + +static int journal_auth_setup(JournalAuthContext *c) { + int r; + + assert(c); + + if (c->hmac) + return 0; + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(EVP_MAC_freep) EVP_MAC *hmac = sym_EVP_MAC_fetch(NULL, "HMAC", NULL); + if (!hmac) + return log_openssl_errors(LOG_DEBUG, "EVP_MAC_fetch() failed"); + + _cleanup_(EVP_MAC_CTX_freep) EVP_MAC_CTX *ctx = sym_EVP_MAC_CTX_new(hmac); + if (!ctx) + return log_openssl_errors(LOG_DEBUG, "EVP_MAC_CTX_new() failed"); + + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); + if (!bld) + return log_openssl_errors(LOG_DEBUG, "OSSL_PARAM_BLD_new() failed"); + + if (sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_MAC_PARAM_DIGEST, "SHA256", 0) <= 0) + return log_openssl_errors(LOG_DEBUG, "OSSL_PARAM_BLD_push_utf8_string() failed"); + + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); + if (!params) + return log_openssl_errors(LOG_DEBUG, "OSSL_PARAM_BLD_to_param() failed"); + + c->hmac = TAKE_PTR(hmac); + c->hmac_ctx = TAKE_PTR(ctx); + c->ossl_params = TAKE_PTR(params); + return 0; +} + +static int journal_auth_start(JournalAuthContext *c) { + int r; + + assert(c); + + if (c->hmac_running) + return 0; + + r = journal_auth_setup(c); + if (r < 0) + return r; + + uint8_t key[256 / 8]; /* Let's pass 256 bit from FSPRG to HMAC */ + CLEANUP_ERASE(key); + r = fsprg_get_key(&c->fsprg_state, &IOVEC_MAKE(key, sizeof(key))); + if (r < 0) + return r; + + /* Prepare HMAC for next cycle */ + if (sym_EVP_MAC_init(c->hmac_ctx, key, sizeof(key), c->ossl_params) <= 0) + return log_openssl_errors(LOG_DEBUG, "sym_EVP_MAC_init() failed"); + + c->hmac_running = true; + return 0; +} + +static int journal_auth_end(JournalAuthContext *c, uint8_t ret[static TAG_LENGTH]) { + assert(c); + assert(ret); + + if (!c->hmac_running) + return -EINVAL; + + c->hmac_running = false; + + uint8_t tag[TAG_LENGTH]; + CLEANUP_ERASE(tag); + + size_t len; + if (sym_EVP_MAC_final(c->hmac_ctx, tag, &len, TAG_LENGTH) <= 0 || len != TAG_LENGTH) + return -EIO; + + memcpy(ret, tag, TAG_LENGTH); + return 0; +} + +static int journal_auth_put_header(JournalAuthContext *c, JournalFile *f) { + int r; + + assert(c); + assert(f); + + r = journal_auth_start(c); + if (r < 0) + return r; + + /* All but state+reserved, boot_id, arena_size, + * tail_object_offset, n_objects, n_entries, + * tail_entry_seqnum, head_entry_seqnum, entry_array_offset, + * head_entry_realtime, tail_entry_realtime, + * tail_entry_monotonic, n_data, n_fields, n_tags, + * n_entry_arrays. */ + + if (sym_EVP_MAC_update(c->hmac_ctx, f->header->signature, offsetof(Header, state) - offsetof(Header, signature)) <= 0) + return -EIO; + if (sym_EVP_MAC_update(c->hmac_ctx, (void*) &f->header->file_id, offsetof(Header, tail_entry_boot_id) - offsetof(Header, file_id)) <= 0) + return -EIO; + if (sym_EVP_MAC_update(c->hmac_ctx, (void*) &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id)) <= 0) + return -EIO; + if (sym_EVP_MAC_update(c->hmac_ctx, (void*) &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset)) <= 0) + return -EIO; + + return 0; +} + +static int journal_auth_put_object(JournalAuthContext *c, JournalFile *f, ObjectType type, Object *o, uint64_t p) { + int r; + + assert(c); + assert(f); + + r = journal_auth_start(c); + if (r < 0) + return r; + + if (!o) { + r = journal_file_move_to_object(f, type, p, &o); + if (r < 0) + return r; + } else if (type > OBJECT_UNUSED && o->object.type != type) + return -EBADMSG; + + if (sym_EVP_MAC_update(c->hmac_ctx, (void*) o, offsetof(ObjectHeader, payload)) <= 0) + return -EIO; + + switch (o->object.type) { + + case OBJECT_DATA: + /* All but hash and payload are mutable */ + if (sym_EVP_MAC_update(c->hmac_ctx, (void*) &o->data.hash, sizeof(o->data.hash)) <= 0) + return -EIO; + if (sym_EVP_MAC_update(c->hmac_ctx, journal_file_data_payload_field(f, o), le64toh(o->object.size) - journal_file_data_payload_offset(f)) <= 0) + return -EIO; + break; + + case OBJECT_FIELD: + /* Same here */ + if (sym_EVP_MAC_update(c->hmac_ctx, (void*) &o->field.hash, sizeof(o->field.hash)) <= 0) + return -EIO; + if (sym_EVP_MAC_update(c->hmac_ctx, o->field.payload, le64toh(o->object.size) - offsetof(Object, field.payload)) <= 0) + return -EIO; + break; + + case OBJECT_ENTRY: + /* All */ + if (sym_EVP_MAC_update(c->hmac_ctx, (void*) &o->entry.seqnum, le64toh(o->object.size) - offsetof(Object, entry.seqnum)) <= 0) + return -EIO; + break; + + case OBJECT_FIELD_HASH_TABLE: + case OBJECT_DATA_HASH_TABLE: + case OBJECT_ENTRY_ARRAY: + /* Nothing: everything is mutable */ + break; + + case OBJECT_TAG: + /* All but the tag itself */ + if (sym_EVP_MAC_update(c->hmac_ctx, (void*) &o->tag.seqnum, sizeof(o->tag.seqnum)) <= 0) + return -EIO; + if (sym_EVP_MAC_update(c->hmac_ctx, (void*) &o->tag.epoch, sizeof(o->tag.epoch)) <= 0) + return -EIO; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int journal_auth_append_tag(JournalAuthContext *c, JournalFile *f) { + int r; + + assert(c); + assert(f); + + r = journal_auth_start(c); + if (r < 0) + return r; + + Object *o; + uint64_t p; + r = journal_file_append_object(f, OBJECT_TAG, sizeof(struct TagObject), &o, &p); + if (r < 0) + return r; + + uint64_t seqnum = le64toh(f->header->n_tags) + 1; + f->header->n_tags = htole64(seqnum); + + o->tag.seqnum = htole64(seqnum); + + uint64_t epoch; + r = fsprg_get_epoch(&c->fsprg_state, &epoch); + if (r < 0) + return r; + o->tag.epoch = htole64(epoch); + + log_debug("Writing tag %"PRIu64" for epoch %"PRIu64"", + le64toh(o->tag.seqnum), epoch); + + /* Add the tag object itself, so that we can protect its + * header. This will exclude the actual hash value in it */ + r = journal_auth_put_object(c, f, OBJECT_TAG, o, p); + if (r < 0) + return r; + + /* Get the HMAC tag and store it in the object */ + return journal_auth_end(c, o->tag.tag); +} + +static int journal_auth_append_tag_first(JournalAuthContext *c, JournalFile *f) { + uint64_t p; + int r; + + assert(c); + assert(f); + + log_debug("Calculating first tag..."); + + r = journal_auth_put_header(c, f); + if (r < 0) + return r; + + p = le64toh(f->header->field_hash_table_offset); + if (p < offsetof(Object, hash_table.items)) + return -EINVAL; + p -= offsetof(Object, hash_table.items); + + r = journal_auth_put_object(c, f, OBJECT_FIELD_HASH_TABLE, NULL, p); + if (r < 0) + return r; + + p = le64toh(f->header->data_hash_table_offset); + if (p < offsetof(Object, hash_table.items)) + return -EINVAL; + p -= offsetof(Object, hash_table.items); + + r = journal_auth_put_object(c, f, OBJECT_DATA_HASH_TABLE, NULL, p); + if (r < 0) + return r; + + return journal_auth_append_tag(c, f); +} + +static int journal_auth_append_tag_maybe(JournalAuthContext *c, JournalFile *f, usec_t realtime) { + int r; + + assert(c); + assert(c->fss_start_usec > 0); + assert(c->fss_interval_usec > 0); + assert(f); + + if (realtime <= 0) + realtime = now(CLOCK_REALTIME); + + uint64_t goal = usec_sub_unsigned(realtime, c->fss_start_usec) / c->fss_interval_usec; + + for (;;) { + uint64_t epoch; + r = fsprg_get_epoch(&c->fsprg_state, &epoch); + if (r < 0) + return r; + if (epoch >= goal) + return 0; + + r = journal_auth_append_tag(c, f); + if (r < 0) + return r; + + r = fsprg_evolve(&c->fsprg_state); + if (r < 0) + return r; + } +} + +static const JournalAuthOps journal_auth_ops = { + .free = journal_auth_free, + .load = journal_auth_load, + .load_key = journal_auth_load_key, + .epoch_to_realtime_usec = journal_auth_epoch_to_realtime_usec, + .next_evolve_usec = journal_auth_next_evolve_usec, + .seek = journal_auth_seek, + .start = journal_auth_start, + .end = journal_auth_end, + .put_header = journal_auth_put_header, + .put_object = journal_auth_put_object, + .append_tag = journal_auth_append_tag, + .append_tag_first = journal_auth_append_tag_first, + .append_tag_maybe = journal_auth_append_tag_maybe, +}; + +void journal_auth_init(void) { + journal_auth_set_ops(&journal_auth_ops); +} + +#else + +void journal_auth_init(void) { +} + +#endif /* HAVE_OPENSSL */ diff --git a/src/shared/journal-authenticate.h b/src/shared/journal-authenticate.h new file mode 100644 index 0000000000000..a12afb9ac7af0 --- /dev/null +++ b/src/shared/journal-authenticate.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "journal-authenticate-internal.h" /* IWYU pragma: export */ +#include "shared-forward.h" + +void journal_auth_init(void); diff --git a/src/shared/journal-file-util.c b/src/shared/journal-file-util.c index 8aecda96105b2..1567defac5f99 100644 --- a/src/shared/journal-file-util.c +++ b/src/shared/journal-file-util.c @@ -405,23 +405,15 @@ bool journal_file_is_offlining(JournalFile *f) { return true; } -void journal_file_write_final_tag(JournalFile *f) { - assert(f); -#if HAVE_GCRYPT - if (!JOURNAL_HEADER_SEALED(f->header) || !journal_file_writable(f)) - return; - - int r = journal_file_append_tag(f); - if (r < 0) - log_debug_errno(r, "Failed to append tag when closing journal: %m"); -#endif -} - JournalFile* journal_file_offline_close(JournalFile *f) { + int r; + if (!f) return NULL; - journal_file_write_final_tag(f); + r = journal_file_auth_append_tag(f); + if (r < 0) + log_debug_errno(r, "Failed to append tag when closing journal, ignoring: %m"); if (sd_event_source_get_enabled(f->post_change_timer, NULL) > 0) journal_file_post_change(f); @@ -464,7 +456,10 @@ int journal_file_rotate( assert(f); assert(*f); - journal_file_write_final_tag(*f); + r = journal_file_auth_append_tag(*f); + if (r < 0) + log_debug_errno(r, "Failed to append tag when closing journal, ignoring: %m"); + r = journal_file_archive(*f, &path); if (r < 0) return r; diff --git a/src/shared/journal-file-util.h b/src/shared/journal-file-util.h index d3ffe4e7a6dfa..a3a87812187d5 100644 --- a/src/shared/journal-file-util.h +++ b/src/shared/journal-file-util.h @@ -6,7 +6,6 @@ int journal_file_set_offline(JournalFile *f, bool wait); bool journal_file_is_offlining(JournalFile *f); -void journal_file_write_final_tag(JournalFile *f); JournalFile* journal_file_offline_close(JournalFile *f); DEFINE_TRIVIAL_CLEANUP_FUNC(JournalFile*, journal_file_offline_close); diff --git a/src/shared/journal-importer.c b/src/shared/journal-importer.c index 3aa472a2d1cf6..128866e17c5a8 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)); @@ -290,7 +292,7 @@ int journal_importer_process_data(JournalImporter *imp) { switch (imp->state) { case IMPORTER_STATE_LINE: { - char *line, *sep; + char *line = NULL; /* avoid false maybe-uninitialized warning */ size_t n = 0; assert(imp->data_size == 0); @@ -315,7 +317,7 @@ int journal_importer_process_data(JournalImporter *imp) { COREDUMP\n LLLLLLLL0011223344...\n */ - sep = memchr(line, '=', n); + char *sep = memchr(line, '=', n); if (sep) { /* chomp newline */ n--; @@ -323,7 +325,7 @@ int journal_importer_process_data(JournalImporter *imp) { if (!journal_field_valid(line, sep - line, true)) { char buf[64], *t; - t = strndupa_safe(line, sep - line); + t = strndupa_safe(line, MIN((size_t) (sep - line), sizeof buf)); log_debug("Ignoring invalid field: \"%s\"", cellescape(buf, sizeof buf, t)); @@ -342,7 +344,7 @@ int journal_importer_process_data(JournalImporter *imp) { if (!journal_field_valid(line, n - 1, true)) { char buf[64], *t; - t = strndupa_safe(line, n - 1); + t = strndupa_safe(line, MIN(n - 1, sizeof buf)); log_debug("Ignoring invalid field: \"%s\"", cellescape(buf, sizeof buf, t)); 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-config.c b/src/shared/kernel-config.c index 415730bf1a39f..3f575439a7840 100644 --- a/src/shared/kernel-config.c +++ b/src/shared/kernel-config.c @@ -28,7 +28,7 @@ int load_kernel_install_conf_at( }; int r; - assert(root_fd >= 0 || IN_SET(root_fd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(root_fd)); if (conf_root) { _cleanup_free_ char *conf = path_join(conf_root, "install.conf"); diff --git a/src/shared/kernel-image.c b/src/shared/kernel-image.c index d3e18d19f41a8..406c3e89cfd7d 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, @@ -135,24 +130,23 @@ int inspect_kernel( _cleanup_close_ int fd = -EBADF; int r; - assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); - assert(filename); + assert(wildcard_fd_is_valid(dir_fd)); 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/label-util.h b/src/shared/label-util.h index c7ed307dee25a..720bd06533628 100644 --- a/src/shared/label-util.h +++ b/src/shared/label-util.h @@ -2,7 +2,8 @@ #pragma once #include "shared-forward.h" -#include "label.h" /* IWYU pragma: export */ + +#include "../basic/label-util.h" /* IWYU pragma: export */ typedef enum LabelFixFlags { LABEL_IGNORE_ENOENT = 1 << 0, diff --git a/src/shared/libarchive-util.c b/src/shared/libarchive-util.c index d2714bd81c183..cc28c2d3471f2 100644 --- a/src/shared/libarchive-util.c +++ b/src/shared/libarchive-util.c @@ -2,12 +2,13 @@ #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 -static void *libarchive_dl = NULL; - DLSYM_PROTOTYPE(archive_entry_acl_add_entry) = NULL; DLSYM_PROTOTYPE(archive_entry_acl_next) = NULL; DLSYM_PROTOTYPE(archive_entry_acl_reset) = NULL; @@ -15,17 +16,15 @@ DLSYM_PROTOTYPE(archive_entry_fflags) = NULL; DLSYM_PROTOTYPE(archive_entry_filetype) = NULL; DLSYM_PROTOTYPE(archive_entry_free) = NULL; DLSYM_PROTOTYPE(archive_entry_gid) = NULL; -#if HAVE_ARCHIVE_ENTRY_GID_IS_SET -DLSYM_PROTOTYPE(archive_entry_gid_is_set) = NULL; -#else -int sym_archive_entry_gid_is_set(struct archive_entry *e) { +static int missing_archive_entry_gid_is_set(struct archive_entry *e) { return gid_is_valid(sym_archive_entry_gid(e)); } -#endif +DLSYM_PROTOTYPE(archive_entry_gid_is_set) = missing_archive_entry_gid_is_set; DLSYM_PROTOTYPE(archive_entry_hardlink) = NULL; -#if HAVE_ARCHIVE_ENTRY_HARDLINK_IS_SET -DLSYM_PROTOTYPE(archive_entry_hardlink_is_set) = NULL; -#endif +static int missing_archive_entry_hardlink_is_set(struct archive_entry *e) { + return !!sym_archive_entry_hardlink(e); +} +DLSYM_PROTOTYPE(archive_entry_hardlink_is_set) = missing_archive_entry_hardlink_is_set; DLSYM_PROTOTYPE(archive_entry_mode) = NULL; DLSYM_PROTOTYPE(archive_entry_mtime) = NULL; DLSYM_PROTOTYPE(archive_entry_mtime_is_set) = NULL; @@ -50,13 +49,10 @@ DLSYM_PROTOTYPE(archive_entry_set_uid) = NULL; DLSYM_PROTOTYPE(archive_entry_sparse_add_entry) = NULL; DLSYM_PROTOTYPE(archive_entry_symlink) = NULL; DLSYM_PROTOTYPE(archive_entry_uid) = NULL; -#if HAVE_ARCHIVE_ENTRY_UID_IS_SET -DLSYM_PROTOTYPE(archive_entry_uid_is_set) = NULL; -#else -int sym_archive_entry_uid_is_set(struct archive_entry *e) { +static int missing_archive_entry_uid_is_set(struct archive_entry *e) { return uid_is_valid(sym_archive_entry_uid(e)); } -#endif +DLSYM_PROTOTYPE(archive_entry_uid_is_set) = missing_archive_entry_uid_is_set; DLSYM_PROTOTYPE(archive_entry_xattr_add_entry) = NULL; DLSYM_PROTOTYPE(archive_entry_xattr_next) = NULL; DLSYM_PROTOTYPE(archive_entry_xattr_reset) = NULL; @@ -77,17 +73,19 @@ 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", - "Support for decompressing archive files", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, - "libarchive.so.13"); +int dlopen_libarchive(int log_level) { +#if HAVE_LIBARCHIVE + static void *libarchive_dl = NULL; + int r; - return dlopen_many_sym_or_warn( + LIBARCHIVE_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); + + r = 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), @@ -95,13 +93,7 @@ int dlopen_libarchive(void) { DLSYM_ARG(archive_entry_filetype), DLSYM_ARG(archive_entry_free), DLSYM_ARG(archive_entry_gid), -#if HAVE_ARCHIVE_ENTRY_GID_IS_SET - DLSYM_ARG(archive_entry_gid_is_set), -#endif DLSYM_ARG(archive_entry_hardlink), -#if HAVE_ARCHIVE_ENTRY_HARDLINK_IS_SET - DLSYM_ARG(archive_entry_hardlink_is_set), -#endif DLSYM_ARG(archive_entry_mode), DLSYM_ARG(archive_entry_mtime), DLSYM_ARG(archive_entry_mtime_is_set), @@ -126,9 +118,6 @@ int dlopen_libarchive(void) { DLSYM_ARG(archive_entry_sparse_add_entry), DLSYM_ARG(archive_entry_symlink), DLSYM_ARG(archive_entry_uid), -#if HAVE_ARCHIVE_ENTRY_UID_IS_SET - DLSYM_ARG(archive_entry_uid_is_set), -#endif DLSYM_ARG(archive_entry_xattr_add_entry), DLSYM_ARG(archive_entry_xattr_next), DLSYM_ARG(archive_entry_xattr_reset), @@ -149,8 +138,24 @@ 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)); + if (r <= 0) + return r; + + /* Optional symbols: archive_entry_gid_is_set and archive_entry_uid_is_set exist only in libarchive + * 3.7.3+, archive_entry_hardlink_is_set exists only in 3.7.5+. If missing, sym_X keeps its + * fallback-function initializer (see above). */ + DLSYM_OPTIONAL(libarchive_dl, archive_entry_gid_is_set); + DLSYM_OPTIONAL(libarchive_dl, archive_entry_uid_is_set); + DLSYM_OPTIONAL(libarchive_dl, archive_entry_hardlink_is_set); + + return 1; +#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..afaf3712007a8 100644 --- a/src/shared/libarchive-util.h +++ b/src/shared/libarchive-util.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dlopen.h" + #include "shared-forward.h" #if HAVE_LIBARCHIVE @@ -9,6 +11,16 @@ #include "dlfcn-util.h" +/* Newer symbols that might not be in the header we build against. Always redeclare so DLSYM_PROTOTYPE's + * typeof() resolves; suppress the warning when newer libarchive already declared them. */ +DISABLE_WARNING_REDUNDANT_DECLS; +/* NOLINTBEGIN(readability-redundant-declaration) */ +extern int archive_entry_gid_is_set(struct archive_entry *e); +extern int archive_entry_hardlink_is_set(struct archive_entry *e); +extern int archive_entry_uid_is_set(struct archive_entry *e); +/* NOLINTEND(readability-redundant-declaration) */ +REENABLE_WARNING; + extern DLSYM_PROTOTYPE(archive_entry_acl_add_entry); extern DLSYM_PROTOTYPE(archive_entry_acl_next); extern DLSYM_PROTOTYPE(archive_entry_acl_reset); @@ -16,19 +28,9 @@ extern DLSYM_PROTOTYPE(archive_entry_fflags); extern DLSYM_PROTOTYPE(archive_entry_filetype); extern DLSYM_PROTOTYPE(archive_entry_free); extern DLSYM_PROTOTYPE(archive_entry_gid); -#if HAVE_ARCHIVE_ENTRY_GID_IS_SET extern DLSYM_PROTOTYPE(archive_entry_gid_is_set); -#else -int sym_archive_entry_gid_is_set(struct archive_entry *e); -#endif extern DLSYM_PROTOTYPE(archive_entry_hardlink); -#if HAVE_ARCHIVE_ENTRY_HARDLINK_IS_SET extern DLSYM_PROTOTYPE(archive_entry_hardlink_is_set); -#else -static inline int sym_archive_entry_hardlink_is_set(struct archive_entry *e) { - return !!sym_archive_entry_hardlink(e); -} -#endif extern DLSYM_PROTOTYPE(archive_entry_mode); extern DLSYM_PROTOTYPE(archive_entry_mtime); extern DLSYM_PROTOTYPE(archive_entry_mtime_is_set); @@ -53,11 +55,7 @@ extern DLSYM_PROTOTYPE(archive_entry_set_uid); extern DLSYM_PROTOTYPE(archive_entry_sparse_add_entry); extern DLSYM_PROTOTYPE(archive_entry_symlink); extern DLSYM_PROTOTYPE(archive_entry_uid); -#if HAVE_ARCHIVE_ENTRY_UID_IS_SET extern DLSYM_PROTOTYPE(archive_entry_uid_is_set); -#else -int sym_archive_entry_uid_is_set(struct archive_entry *e); -#endif extern DLSYM_PROTOTYPE(archive_entry_xattr_add_entry); extern DLSYM_PROTOTYPE(archive_entry_xattr_next); extern DLSYM_PROTOTYPE(archive_entry_xattr_reset); @@ -79,16 +77,24 @@ 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 +#define LIBARCHIVE_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("archive", \ + "Support for decompressing archive files", \ + priority, \ + "libarchive.so.13") -static inline int dlopen_libarchive(void) { - return -EOPNOTSUPP; -} +#define DLOPEN_LIBARCHIVE(log_level, priority) \ + ({ \ + LIBARCHIVE_NOTE(priority); \ + dlopen_libarchive(log_level); \ + }) +#else +#define DLOPEN_LIBARCHIVE(log_level, priority) dlopen_libarchive(log_level) #endif + +int dlopen_libarchive(int log_level); diff --git a/src/shared/libaudit-util.c b/src/shared/libaudit-util.c index 4bdd4c1317edf..41daa372b5874 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" @@ -12,8 +14,6 @@ #include "socket-util.h" #if HAVE_AUDIT -static void *libaudit_dl = NULL; - static DLSYM_PROTOTYPE(audit_close) = NULL; DLSYM_PROTOTYPE(audit_log_acct_message) = NULL; DLSYM_PROTOTYPE(audit_log_user_avc_message) = NULL; @@ -21,24 +21,28 @@ 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", + static void *libaudit_dl = NULL; + + 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..d9710566e6d2e 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" @@ -13,60 +15,15 @@ #include "strv.h" #if HAVE_LIBCRYPT -static void *libcrypt_dl = NULL; - +#ifdef __GLIBC__ 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; +static DLSYM_PROTOTYPE(crypt_gensalt_ra) = missing_crypt_gensalt_ra; +static DLSYM_PROTOTYPE(crypt_preferred_method) = missing_crypt_preferred_method; +static DLSYM_PROTOTYPE(crypt_ra) = missing_crypt_ra; #endif - return 0; -} int make_salt(char **ret) { const char *e; @@ -75,7 +32,7 @@ int make_salt(char **ret) { assert(ret); - r = dlopen_libcrypt(); + r = dlopen_libcrypt(LOG_DEBUG); if (r < 0) return r; @@ -122,7 +79,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 +127,44 @@ 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 void *libcrypt_dl = NULL; + static int cached = 0; + int r = -ENOENT; + + if (libcrypt_dl) + return 1; /* 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"); + + FOREACH_STRING(soname, "libcrypt.so.2", "libcrypt.so.1", "libcrypt.so.1.1") { + r = dlopen_many_sym_or_warn( + &libcrypt_dl, soname, LOG_DEBUG, + DLSYM_ARG(crypt_gensalt_ra), + DLSYM_ARG(crypt_preferred_method), + DLSYM_ARG(crypt_ra)); + if (r >= 0) + break; + } + if (r < 0) + return cached = log_full_errno(log_level, r, "Failed to load libcrypt: %m"); +#endif + return 1; +#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..4a0d7e6d64754 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" @@ -11,7 +13,9 @@ #include "format-table.h" #include "glyph-util.h" #include "iovec-util.h" +#include "locale-util.h" #include "plymouth-util.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "unistd.h" @@ -21,8 +25,6 @@ #define FIDO_ERR_UV_BLOCKED 0x3c #endif -static void *libfido2_dl = NULL; - DLSYM_PROTOTYPE(fido_assert_allow_cred) = NULL; DLSYM_PROTOTYPE(fido_assert_free) = NULL; DLSYM_PROTOTYPE(fido_assert_hmac_secret_len) = NULL; @@ -57,6 +59,7 @@ DLSYM_PROTOTYPE(fido_dev_close) = NULL; DLSYM_PROTOTYPE(fido_dev_free) = NULL; DLSYM_PROTOTYPE(fido_dev_get_assert) = NULL; DLSYM_PROTOTYPE(fido_dev_get_cbor_info) = NULL; +DLSYM_PROTOTYPE(fido_dev_get_retry_count) = NULL; DLSYM_PROTOTYPE(fido_dev_info_free) = NULL; DLSYM_PROTOTYPE(fido_dev_info_manifest) = NULL; DLSYM_PROTOTYPE(fido_dev_info_manufacturer_string) = NULL; @@ -78,17 +81,19 @@ static void fido_log_propagate_handler(const char *s) { #endif -int dlopen_libfido2(void) { +int dlopen_libfido2(int log_level) { #if HAVE_LIBFIDO2 + static void *libfido2_dl = NULL; 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), @@ -123,6 +128,7 @@ int dlopen_libfido2(void) { DLSYM_ARG(fido_dev_free), DLSYM_ARG(fido_dev_get_assert), DLSYM_ARG(fido_dev_get_cbor_info), + DLSYM_ARG(fido_dev_get_retry_count), DLSYM_ARG(fido_dev_info_free), DLSYM_ARG(fido_dev_info_manifest), DLSYM_ARG(fido_dev_info_manufacturer_string), @@ -145,7 +151,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 } @@ -357,7 +364,7 @@ static int fido2_is_cred_in_specific_token( /* According to CTAP 2.1 specification, to do pre-flight we need to set up option to false * with optionally pinUvAuthParam in assertion[1]. But for authenticator that doesn't support * user presence, once up option is present, the authenticator may return CTAP2_ERR_UNSUPPORTED_OPTION[2]. - * So we simplely omit the option in that case. + * So we simply omit the option in that case. * Reference: * 1: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#pre-flight * 2: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion (in step 5) @@ -490,7 +497,7 @@ static int fido2_use_hmac_hash_specific_token( log_notice("%s%sPlease confirm presence on security token to unlock.", emoji_enabled() ? glyph(GLYPH_TOUCH) : "", emoji_enabled() ? " " : ""); - plymouth_start_interaction("Please confirm presence on security token to unlock.", &plymouth_displayed); + plymouth_start_interaction(_("Please confirm presence on security token to unlock."), &plymouth_displayed); } } @@ -506,7 +513,7 @@ static int fido2_use_hmac_hash_specific_token( log_notice("%s%sPlease verify user on security token to unlock.", emoji_enabled() ? glyph(GLYPH_TOUCH) : "", emoji_enabled() ? " " : ""); - plymouth_start_interaction("Please verify user on security token to unlock.", &plymouth_displayed); + plymouth_start_interaction(_("Please verify user on security token to unlock."), &plymouth_displayed); } } @@ -547,7 +554,7 @@ static int fido2_use_hmac_hash_specific_token( log_notice("%s%sPlease confirm presence on security to unlock.", emoji_enabled() ? glyph(GLYPH_TOUCH) : "", emoji_enabled() ? " " : ""); - plymouth_start_interaction("Please confirm presence on security token to unlock.", &plymouth_displayed); + plymouth_start_interaction(_("Please confirm presence on security token to unlock."), &plymouth_displayed); retry_with_up = true; } @@ -655,9 +662,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); @@ -739,6 +746,8 @@ int fido2_generate_hmac_hash( const char *user_icon, const char *askpw_icon, const char *askpw_credential, + AskPasswordFlags askpw_flags, + const char *pin, Fido2EnrollFlags lock_with, int cred_alg, const struct iovec *salt, @@ -781,9 +790,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) @@ -913,28 +922,57 @@ int fido2_generate_hmac_hash( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Token asks for PIN but doesn't advertise 'clientPin' feature."); - AskPasswordFlags askpw_flags = ASK_PASSWORD_ACCEPT_CACHED; + AskPasswordFlags pin_askpw_flags = askpw_flags | ASK_PASSWORD_ACCEPT_CACHED; for (;;) { - _cleanup_strv_free_erase_ char **pin = NULL; - AskPasswordRequest req = { - .tty_fd = -EBADF, - .message = "Please enter security token PIN:", - .icon = askpw_icon, - .keyring = "fido2-pin", - .credential = askpw_credential, - .until = USEC_INFINITY, - .hup_fd = -EBADF, - }; - - r = ask_password_auto(&req, askpw_flags, &pin); - if (r < 0) - return log_error_errno(r, "Failed to acquire user PIN: %m"); - - askpw_flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + _cleanup_strv_free_erase_ char **pins = NULL; + + if (pin) { + /* A PIN was supplied by the caller: use it directly, don't prompt. */ + pins = strv_new(pin); + if (!pins) + return log_oom(); + } else if (FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS)) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option."); + else { + int pin_retries = -1; + r = sym_fido_dev_get_retry_count(d, &pin_retries); + if (r != FIDO_OK) { + log_warning("Failed to obtain number of retries before lock-out for PIN " + "authentication, ignoring: %s", sym_fido_strerr(r)); + pin_retries = -1; + } + + _cleanup_free_ char *ask_pin_msg = NULL; + if (pin_retries >= 0) { + ask_pin_msg = asprintf_safe(_("Please enter security token PIN " + "(remaining attempts before lock-out: %d):"), + pin_retries); + if (!ask_pin_msg) + return log_oom(); + } + + AskPasswordRequest req = { + .tty_fd = -EBADF, + .message = pin_retries >= 0 + ? ask_pin_msg + : _("Please enter security token PIN:"), + .icon = askpw_icon, + .keyring = "fido2-pin", + .credential = askpw_credential, + .until = USEC_INFINITY, + .hup_fd = -EBADF, + }; + + r = ask_password_auto(&req, pin_askpw_flags, &pins); + if (r < 0) + return log_error_errno(r, "Failed to acquire user PIN: %m"); + + pin_askpw_flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + } r = FIDO_ERR_PIN_INVALID; - STRV_FOREACH(i, pin) { + STRV_FOREACH(i, pins) { if (isempty(*i)) { log_notice("PIN may not be empty."); continue; @@ -954,6 +992,11 @@ int fido2_generate_hmac_hash( if (r != FIDO_ERR_PIN_INVALID) break; + /* A caller-supplied PIN that's wrong won't get better by retrying: fail instead of + * looping forever (we'd never prompt). */ + if (pin) + break; + log_notice("PIN incorrect, please try again."); } } @@ -969,6 +1012,9 @@ int fido2_generate_hmac_hash( if (r == FIDO_ERR_UNSUPPORTED_ALGORITHM) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Token doesn't support credential algorithm %s.", fido2_algorithm_to_string(cred_alg)); + if (r == FIDO_ERR_PIN_INVALID) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "PIN incorrect."); if (r != FIDO_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate FIDO2 credential: %s", sym_fido_strerr(r)); @@ -1161,7 +1207,16 @@ static int check_device_is_fido2_with_hmac_secret( r = verify_features(d, path, LOG_DEBUG, ret_has_rk, ret_has_client_pin, ret_has_up, ret_has_uv, ret_has_always_uv); if (r == -ENODEV) { /* Not a FIDO2 device, or not implementing 'hmac-secret' */ - *ret_has_rk = *ret_has_client_pin = *ret_has_up = *ret_has_uv = *ret_has_always_uv = false; + if (ret_has_rk) + *ret_has_rk = false; + if (ret_has_client_pin) + *ret_has_client_pin = false; + if (ret_has_up) + *ret_has_up = false; + if (ret_has_uv) + *ret_has_uv = false; + if (ret_has_always_uv) + *ret_has_always_uv = false; return false; } if (r < 0) @@ -1179,9 +1234,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 +1293,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" @@ -1266,6 +1319,109 @@ int fido2_list_devices(void) { #endif } +void fido2_device_info_done(Fido2DeviceInfo *d) { + assert(d); + + d->path = mfree(d->path); + d->manufacturer = mfree(d->manufacturer); + d->product = mfree(d->product); +} + +void fido2_device_info_free_many(Fido2DeviceInfo *a, size_t n) { + FOREACH_ARRAY(i, a, n) + fido2_device_info_done(i); + + free(a); +} + +int fido2_enumerate_devices(Fido2DeviceInfo **ret, size_t *ret_n) { +#if HAVE_LIBFIDO2 + Fido2DeviceInfo *devices = NULL; + size_t n_devices = 0, allocated = 64, found = 0; + fido_dev_info_t *di = NULL; + int r; + + CLEANUP_ARRAY(devices, n_devices, fido2_device_info_free_many); + + assert(ret); + assert(ret_n); + + r = dlopen_libfido2(LOG_ERR); + if (r < 0) + return r; + + di = sym_fido_dev_info_new(allocated); + if (!di) + return log_oom(); + + r = sym_fido_dev_info_manifest(di, allocated, &found); + if (r == FIDO_ERR_INTERNAL || (r == FIDO_OK && found == 0)) { + /* The library returns FIDO_ERR_INTERNAL when no devices are found. I wish it wouldn't. */ + r = 0; + goto finish; + } + if (r != FIDO_OK) { + r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enumerate FIDO2 devices: %s", sym_fido_strerr(r)); + goto finish; + } + + for (size_t i = 0; i < found; i++) { + bool has_rk, has_client_pin, has_up, has_uv, has_always_uv; + const fido_dev_info_t *entry; + const char *path; + + entry = sym_fido_dev_info_ptr(di, i); + if (!entry) { + r = log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get device information for FIDO device %zu.", i); + goto finish; + } + + path = sym_fido_dev_info_path(entry); + + r = check_device_is_fido2_with_hmac_secret(path, &has_rk, &has_client_pin, &has_up, &has_uv, &has_always_uv); + if (r < 0) + goto finish; + if (r == 0) /* Not a FIDO2 device suitable for enrollment, skip it */ + continue; + + if (!GREEDY_REALLOC(devices, n_devices + 1)) { + r = log_oom(); + goto finish; + } + + Fido2DeviceInfo *d = devices + n_devices; + *d = (Fido2DeviceInfo) {}; + + /* The manufacturer / product strings are optional (NULL when the device doesn't report + * them), but a failure to duplicate any string is a genuine OOM and thus fatal. */ + if (strdup_to(&d->path, path) < 0 || + strdup_to(&d->manufacturer, empty_to_null(sym_fido_dev_info_manufacturer_string(entry))) < 0 || + strdup_to(&d->product, empty_to_null(sym_fido_dev_info_product_string(entry))) < 0) { + fido2_device_info_done(d); + r = log_oom(); + goto finish; + } + + n_devices++; + } + + r = 0; + +finish: + sym_fido_dev_info_free(&di, allocated); + if (r < 0) + return r; + + *ret = TAKE_PTR(devices); + *ret_n = n_devices; + return 0; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "FIDO2 tokens not supported on this build."); +#endif +} + int fido2_find_device_auto(char **ret) { #if HAVE_LIBFIDO2 _cleanup_free_ char *copy = NULL; @@ -1275,9 +1431,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 +1508,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..12a0201332fa4 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 @@ -57,6 +57,7 @@ extern DLSYM_PROTOTYPE(fido_dev_close); extern DLSYM_PROTOTYPE(fido_dev_free); extern DLSYM_PROTOTYPE(fido_dev_get_assert); extern DLSYM_PROTOTYPE(fido_dev_get_cbor_info); +extern DLSYM_PROTOTYPE(fido_dev_get_retry_count); extern DLSYM_PROTOTYPE(fido_dev_info_free); extern DLSYM_PROTOTYPE(fido_dev_info_manifest); extern DLSYM_PROTOTYPE(fido_dev_info_manufacturer_string); @@ -116,6 +117,8 @@ int fido2_generate_hmac_hash( const char *user_icon, const char *askpw_icon, const char *askpw_credential, + AskPasswordFlags askpw_flags, + const char *pin, Fido2EnrollFlags lock_with, int cred_alg, const struct iovec *salt, @@ -134,4 +137,17 @@ static inline int parse_fido2_algorithm(const char *s, int *ret) { int fido2_list_devices(void); int fido2_find_device_auto(char **ret); +typedef struct Fido2DeviceInfo { + char *path; + char *manufacturer; /* may be NULL if the device doesn't report it */ + char *product; /* may be NULL if the device doesn't report it */ +} Fido2DeviceInfo; + +void fido2_device_info_done(Fido2DeviceInfo *d); +void fido2_device_info_free_many(Fido2DeviceInfo *a, size_t n); + +/* Enumerates the FIDO2 tokens that implement the 'hmac-secret' extension (i.e. are suitable for + * enrollment), returning a newly allocated array. */ +int fido2_enumerate_devices(Fido2DeviceInfo **ret, size_t *ret_n); + int fido2_have_device(const char *device); diff --git a/src/shared/libmount-util.c b/src/shared/libmount-util.c index c6c6074c25953..fd83da136ff3d 100644 --- a/src/shared/libmount-util.c +++ b/src/shared/libmount-util.c @@ -1,12 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - -#include "fstab-util.h" #include "libmount-util.h" #include "log.h" -static void *libmount_dl = NULL; +#if HAVE_LIBMOUNT + +#include + +#include "sd-dlopen.h" + +#include "fstab-util.h" DLSYM_PROTOTYPE(mnt_free_iter) = NULL; DLSYM_PROTOTYPE(mnt_free_table) = NULL; @@ -40,49 +43,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 +57,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 +113,52 @@ int libmount_is_leaf( return r == 1; } + +#endif + +int dlopen_libmount(int log_level) { +#if HAVE_LIBMOUNT + static void *libmount_dl = NULL; + + LIBMOUNT_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + + 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..44e58ec932040 100644 --- a/src/shared/libmount-util.h +++ b/src/shared/libmount-util.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dlopen.h" + #include "shared-forward.h" #if HAVE_LIBMOUNT @@ -42,8 +44,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); @@ -75,17 +75,28 @@ int libmount_is_leaf( struct libmnt_table *table, struct libmnt_fs *fs); +#define LIBMOUNT_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("mount", \ + "Support for mount enumeration", \ + priority, \ + "libmount.so.1") + +#define DLOPEN_LIBMOUNT(log_level, priority) \ + ({ \ + LIBMOUNT_NOTE(priority); \ + dlopen_libmount(log_level); \ + }) #else 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); return NULL; } +#define DLOPEN_LIBMOUNT(log_level, priority) dlopen_libmount(log_level) #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/locale-setup.c b/src/shared/locale-setup.c index 6b66c1544366c..294963640e0ea 100644 --- a/src/shared/locale-setup.c +++ b/src/shared/locale-setup.c @@ -5,10 +5,12 @@ #include #include "alloc-util.h" +#include "efivars.h" #include "env-file.h" #include "env-util.h" #include "errno-util.h" #include "fd-util.h" +#include "iovec-util.h" #include "locale-setup.h" #include "log.h" #include "proc-cmdline.h" @@ -318,3 +320,77 @@ const char* etc_vconsole_conf(void) { return cached; } + +int locale_lang_from_efi(char **ret, LocaleLangFromEfiFlags flags) { + int r; + + assert(ret); + + if (!is_efi_boot()) { + *ret = NULL; + return 0; + } + + /* NB: unlike most other UEFI variables, PlatformLang is actually in 7bit ASCII! Hence we are not + * using efi_get_variable_string() here */ + _cleanup_(iovec_done) struct iovec iov = {}; + r = efi_get_variable(EFI_GLOBAL_VARIABLE_STR("PlatformLang"), /* ret_attribute= */ NULL, &iov.iov_base, &iov.iov_len); + if (r == -ENOENT) { + *ret = NULL; + return 0; + } + if (r < 0) + return log_debug_errno(r, "Failed to read PlatformLang EFI variable: %m"); + + _cleanup_free_ char *tag = NULL; + r = make_cstring(iov.iov_base, iov.iov_len, MAKE_CSTRING_ALLOW_TRAILING_NUL, &tag); + if (r < 0) + return log_debug_errno(r, "Failed to convert PlatformLang EFI variable to C string: %m"); + + /* Convert the UEFI BCP47 language tag into a glibc tag. We'll not bother with the complexity of the + * whole spec, but just convert "xx-XX" into "xx_XX", with some flexibility on the case + * sensitivity. This is a best-effort thing anyway. */ + + if (strlen(tag) != 5 || + !strchr(LETTERS, tag[0]) || + !strchr(LETTERS, tag[1]) || + tag[2] != '-' || + !strchr(LETTERS, tag[3]) || + !strchr(LETTERS, tag[4])) { + log_debug("PlatformLang variable does not have the form 'xx-XX', ignoring: %s", tag); + *ret = NULL; + return 0; + } + + tag[0] = ascii_tolower(tag[0]); + tag[1] = ascii_tolower(tag[1]); + tag[2] = '_'; + tag[3] = ascii_toupper(tag[3]); + tag[4] = ascii_toupper(tag[4]); + + /* Let's optionally suppress en_US locale, since that's almost certainly just the built-in default + * locale of the firmware. Since we typically prefer C.UTF-8 over en_US.UTF-8 as default, let's hence + * suppress it. */ + if (FLAGS_SET(flags, LOCALE_SUPPRESS_EN_US) && streq(tag, "en_US")) { + log_debug("Firmware language is en_US, suppressing because likely just the firmware default."); + *ret = NULL; + return 0; + } + + if (!strextend(&tag, ".UTF-8")) + return -ENOMEM; + + if (FLAGS_SET(flags, LOCALE_REQUIRE_INSTALLED)) { + r = locale_is_installed(tag); + if (r < 0) + return r; + if (r == 0) { + log_debug("Determined locale '%s' from PlatformLang, but it isn't installed, ignoring.", tag); + *ret = NULL; + return 0; + } + } + + *ret = TAKE_PTR(tag); + return 1; +} diff --git a/src/shared/locale-setup.h b/src/shared/locale-setup.h index 8b71064cad13c..d720a53adb268 100644 --- a/src/shared/locale-setup.h +++ b/src/shared/locale-setup.h @@ -31,3 +31,10 @@ int locale_setup(char ***environment); const char* etc_locale_conf(void); const char* etc_vconsole_conf(void); + +typedef enum LocaleLangFromEfiFlags { + LOCALE_REQUIRE_INSTALLED = 1 << 0, + LOCALE_SUPPRESS_EN_US = 1 << 1, +} LocaleLangFromEfiFlags; + +int locale_lang_from_efi(char **ret, LocaleLangFromEfiFlags flags); diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 3666c1639845c..513d76558e313 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -1039,7 +1039,8 @@ void json_escape( assert(f); assert(p); - if (!(flags & OUTPUT_SHOW_ALL) && l >= JSON_THRESHOLD) + if (((flags & OUTPUT_SKIP_UNPRINTABLE) && !utf8_is_printable(p, l)) || + (!(flags & OUTPUT_SHOW_ALL) && l >= JSON_THRESHOLD)) fputs("null", f); else if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(p, l)) { @@ -1099,9 +1100,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, @@ -1124,6 +1125,8 @@ static int update_json_data( r = sd_json_variant_new_null(&v); else if (utf8_is_printable(value, size)) r = sd_json_variant_new_stringn(&v, value, size); + else if (flags & OUTPUT_SKIP_UNPRINTABLE) + r = sd_json_variant_new_null(&v); else r = sd_json_variant_new_array_bytes(&v, value, size); if (r < 0) @@ -1704,9 +1707,7 @@ static int add_matches_for_coredump_uid(sd_journal *j, MatchUnitFlag flags, cons return 0; if (cached_uid == 0) { - const char *user = "systemd-coredump"; - - r = get_user_creds(&user, &cached_uid, NULL, NULL, NULL, 0); + r = get_user_creds("systemd-coredump", /* flags= */ 0, NULL, &cached_uid, NULL, NULL, NULL); if (r < 0) { log_debug_errno(r, "Failed to resolve systemd-coredump user, ignoring: %m"); cached_uid = UID_INVALID; diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 8c924296488d2..4e2b2f70c1130 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -15,6 +15,7 @@ #include "alloc-util.h" #include "blockdev-util.h" #include "data-fd-util.h" +#include "device-private.h" #include "device-util.h" #include "devnum-util.h" #include "dissect-image.h" @@ -30,9 +31,12 @@ #include "stat-util.h" #include "stdio-util.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" static void cleanup_clear_loop_close(int *fd) { + assert(fd); + if (*fd < 0) return; @@ -376,9 +380,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 +390,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 +404,121 @@ 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_fd_open(int fd, int f_flags, int *ret_to_close) { + int r; + + assert(fd >= 0); + assert(ret_to_close); + + /* blkid- and pread-based probing has no special handling for the strict alignment requirements of + * O_DIRECT, so if fd was opened with O_DIRECT we reopen it without for the probing logic. Returns the + * fd to use for probing; when a new fd had to be opened it is also stored in *ret_to_close for the + * caller to close, otherwise *ret_to_close is set to -EBADF and the original fd is returned. */ + + if (!FLAGS_SET(f_flags, O_DIRECT)) { + *ret_to_close = -EBADF; + return fd; + } + + r = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NONBLOCK); + if (r < 0) + return r; + + return (*ret_to_close = r); +} + +static int fd_has_partition_table(int fd) { + _cleanup_free_ char *pttype = NULL; + int r; + + assert(fd >= 0); + + /* Checks whether the device carries a partition table the image dissection logic acts upon. We use + * this to decide whether wrapping the device in a loopback device with partition scanning enabled + * actually serves a purpose: if there are no partitions to expose we can hand back the original fd + * instead. Expects an fd suitable for probing, i.e. opened without O_DIRECT (see probe_fd_open()). */ + + r = probe_partition_table(fd, &pttype); /* already logs on error */ + if (r < 0) + return r; + + /* Only GPT and MBR ("dos") tables are understood by the dissection logic and require partition + * scanning to expose their partitions; anything else it treats as unpartitioned, so a loopback + * device wouldn't help (and STRPTR_IN_SET() handles a NULL pttype, i.e. no table, as false). */ + return STRPTR_IN_SET(pttype, "gpt", "dos"); +} + +static int loop_device_can_shortcut( + int fd, + uint64_t offset, + uint64_t size, + uint32_t sector_size, + uint32_t device_ssz, + uint32_t loop_flags) { + + int r; + + /* Returns whether we can hand back the original block device fd instead of allocating a real + * loopback device for it: it must cover the whole device, the requested sector size must match the + * device's sector size, and if partscan was requested the device must either already have it enabled + * or — unless the caller declared it may populate the image via LOOP_DEVICE_MAY_POPULATE_PARTITION_TABLE + * — carry no partition table at all (in which case there are no partitions to scan and the loopback + * would serve no purpose). */ + + assert(fd >= 0); + + if (offset != 0) + return false; + if (!IN_SET(size, 0, UINT64_MAX)) + return false; + if (sector_size != device_ssz) + return false; + + if (FLAGS_SET(loop_flags, LO_FLAGS_PARTSCAN)) { + r = blockdev_partscan_enabled_fd(fd); + if (r < 0) + return r; + if (r == 0) { + /* Partition scanning was requested but cannot be enabled on this device (e.g. it's a + * partition itself). If the caller might write a (nested) partition table into the + * device, it must get a real loopback device so scanning works once the table is + * there. */ + if (FLAGS_SET(loop_flags, LOOP_DEVICE_MAY_POPULATE_PARTITION_TABLE)) + return false; + + /* Otherwise we shortcut when the device carries no partition table: there are then no + * partitions to scan, and routing e.g. a multi-device btrfs member through a loop + * device breaks it, see https://github.com/systemd/systemd/issues/42520. + * + * If we can't probe the device, fall back to allocating a real loop device rather than + * failing the whole operation: we can't prove there's no partition table, and the + * image is potentially untrusted (a crafted or corrupt partition table can make the + * probe fail, e.g. with -EUCLEAN), so failing here would be a fail-unsafe DoS. */ + r = fd_has_partition_table(fd); + if (r != 0) + return false; + } + } + + return true; +} + static int loop_device_make_internal( const char *path, int fd, @@ -425,16 +531,21 @@ static int loop_device_make_internal( LoopDevice **ret) { _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; - _cleanup_close_ int reopened_fd = -EBADF, control = -EBADF; + _cleanup_close_ int reopened_fd = -EBADF, control = -EBADF, probe_close_fd = -EBADF; _cleanup_free_ char *backing_file = NULL; struct loop_config config; - int r, f_flags; + int r, f_flags, probe_fd = -EBADF; struct stat st; assert(fd >= 0); 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 +560,54 @@ 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. */ + + if (probe_fd < 0) { + probe_fd = probe_fd_open(fd, f_flags, &probe_close_fd); + if (probe_fd < 0) + return probe_fd; + } + + r = probe_sector_size(probe_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)) - /* 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. */ + 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 (probe_fd < 0) { + probe_fd = probe_fd_open(fd, f_flags, &probe_close_fd); + if (probe_fd < 0) + return probe_fd; + } + + r = loop_device_can_shortcut(probe_fd, offset, size, sector_size, device_ssz, loop_flags); + if (r < 0) + return r; + if (r > 0) 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 +646,27 @@ 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, + /* Use the specified flags, but strip our systemd-internal flags and the read-only and + * partscan flags (the latter handled separately below/above), and force autoclear */ + .lo_flags = ((loop_flags & ~(LOOP_DEVICE_MAY_POPULATE_PARTITION_TABLE|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 +758,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 +907,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) @@ -939,7 +1078,7 @@ int loop_device_open( #endif nr = info.lo_number; - if (sd_device_get_sysattr_value(dev, "loop/backing_file", &s) >= 0) { + if (device_get_sysattr_safe_string(dev, "loop/backing_file", &s) >= 0) { backing_file = strdup(s); if (!backing_file) return -ENOMEM; @@ -1178,6 +1317,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/loop-util.h b/src/shared/loop-util.h index ed02a69d878db..ae8374d17bd5c 100644 --- a/src/shared/loop-util.h +++ b/src/shared/loop-util.h @@ -26,6 +26,18 @@ typedef struct LoopDevice { /* Returns true if LoopDevice object is not actually a loopback device but some other block device we just wrap */ #define LOOP_DEVICE_IS_FOREIGN(d) ((d)->nr < 0) +/* systemd-internal flags OR'd into the loop_flags argument of loop_device_make() and friends, in addition to + * the kernel's LO_FLAGS_*. These live in high bits to stay clear of the kernel values and are masked out + * before the flags reach the kernel. + * + * LOOP_DEVICE_MAY_POPULATE_PARTITION_TABLE: by default, when LO_FLAGS_PARTSCAN is requested but cannot be + * enabled on the device and the device carries no partition table, we hand back the original fd instead of + * allocating a loopback device — there's nothing to scan, and routing e.g. a multi-device btrfs member + * through a loopback breaks it (https://github.com/systemd/systemd/issues/42520). Callers that might write a + * (nested) partition table into the device and rely on partition scanning to pick it up afterwards must set + * this flag to force a real loopback device even when the device is currently unpartitioned. */ +#define LOOP_DEVICE_MAY_POPULATE_PARTITION_TABLE (UINT32_C(1) << 16) + int loop_device_make(int fd, int open_flags, uint64_t offset, uint64_t size, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret); int loop_device_make_by_path_at(int dir_fd, const char *path, int open_flags, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret); static inline int loop_device_make_by_path(const char *path, int open_flags, uint32_t sector_size, uint32_t loop_flags, int lock_op, LoopDevice **ret) { diff --git a/src/shared/luo-util.c b/src/shared/luo-util.c new file mode 100644 index 0000000000000..5de805e1f640e --- /dev/null +++ b/src/shared/luo-util.c @@ -0,0 +1,418 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include +#include + +#include "sd-json.h" + +#include "alloc-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "json-util.h" +#include "log.h" +#include "luo-util.h" +#include "memfd-util.h" +#include "parse-util.h" +#include "stat-util.h" +#include "string-util.h" + +/* Kernel API defined at https://docs.kernel.org/userspace-api/liveupdate.html The /dev/liveupdate is a + * single-owner singleton, only a single process at any given time can open it. Callers can create named + * "sessions", and then add FDs to them. The session name can be used to retrieve the session after reboot. + * To identify an FD, a 64bit token (what we would call an 'index' in our codebase) is passed in, and the + * caller is responsible for coming up with the token and tracking them. */ + +int luo_open_device(void) { + return RET_NERRNO(open("/dev/liveupdate", O_RDWR|O_CLOEXEC)); +} + +int luo_create_session(int device_fd, const char *name) { + struct liveupdate_ioctl_create_session args = { + .size = sizeof(args), + .fd = -EBADF, + }; + + assert(device_fd >= 0); + assert(name); + + if (strlen(name) >= sizeof(args.name)) + return -ENAMETOOLONG; + + strncpy_exact((char *) args.name, name, sizeof(args.name)); + + if (ioctl(device_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &args) < 0) + return -errno; + + return args.fd; +} + +int luo_retrieve_session(int device_fd, const char *name) { + struct liveupdate_ioctl_retrieve_session args = { + .size = sizeof(args), + .fd = -EBADF, + }; + + assert(device_fd >= 0); + assert(name); + + if (strlen(name) >= sizeof(args.name)) + return -ENAMETOOLONG; + + strncpy_exact((char *) args.name, name, sizeof(args.name)); + + if (ioctl(device_fd, LIVEUPDATE_IOCTL_RETRIEVE_SESSION, &args) < 0) + return -errno; + + return args.fd; +} + +int luo_session_preserve_fd(int session_fd, int fd, uint64_t token) { + struct liveupdate_session_preserve_fd args = { + .size = sizeof(args), + .fd = fd, + .token = token, + }; + + assert(session_fd >= 0); + assert(fd >= 0); + + return RET_NERRNO(ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &args)); +} + +int luo_session_retrieve_fd(int session_fd, uint64_t token) { + struct liveupdate_session_retrieve_fd args = { + .size = sizeof(args), + .fd = -EBADF, + .token = token, + }; + int r; + + assert(session_fd >= 0); + + if (ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &args) < 0) + return -errno; + + r = fd_cloexec(args.fd, true); + if (r < 0) { + safe_close(args.fd); + return r; + } + + return args.fd; +} + +int luo_session_finish(int session_fd) { + struct liveupdate_session_finish args = { + .size = sizeof(args), + }; + + assert(session_fd >= 0); + + return RET_NERRNO(ioctl(session_fd, LIVEUPDATE_SESSION_FINISH, &args)); +} + +bool luo_session_name_is_valid(const char *name) { + /* Used for FDNAME: no whitespace, no ":", no control characters, etc. */ + return fdname_is_valid(name) && string_is_safe(name, STRING_DISALLOW_WHITESPACE); +} + +int luo_parse_serialization(sd_json_variant **ret, int **ret_fds, size_t *ret_n_fds) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *root = NULL; + _cleanup_free_ int *fd_list = NULL; + size_t n_fds = 0; + int serialize_fd = -EBADF, r; + + assert(ret); + assert(ret_fds); + assert(ret_n_fds); + + const char *luo_fd_str = secure_getenv("SYSTEMD_LUO_SERIALIZE_FD"); + if (!luo_fd_str) { + *ret = NULL; + *ret_fds = NULL; + *ret_n_fds = 0; + return 0; + } + + serialize_fd = parse_fd(luo_fd_str); + if (serialize_fd < 0) + return log_warning_errno(serialize_fd, + "Failed to parse SYSTEMD_LUO_SERIALIZE_FD='%s': %m", luo_fd_str); + + r = sd_json_parse_fd( + /* path= */ NULL, + serialize_fd, + SD_JSON_PARSE_MUST_BE_OBJECT|SD_JSON_PARSE_REOPEN_FD, + &root, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL); + if (r < 0) + return log_warning_errno(r, "Failed to parse LUO serialization JSON: %m"); + + /* Collect all fd numbers referenced in the JSON (plus the serialization fd itself) + * so the caller can protect them from close_all_fds(). */ + sd_json_variant *units = sd_json_variant_by_key(root, "units"); + if (units && !sd_json_variant_is_object(units)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "LUO serialization 'units' field is not an object, ignoring."); + + const char *unit_id _unused_; + sd_json_variant *unit_json; + + JSON_VARIANT_OBJECT_FOREACH(unit_id, unit_json, units) { + sd_json_variant *entry, *fdstore; + + fdstore = sd_json_variant_by_key(unit_json, "fdstore"); + if (fdstore && !sd_json_variant_is_array(fdstore)) { + log_warning("LUO serialization 'fdstore' field is not an array, skipping."); + continue; + } + + JSON_VARIANT_ARRAY_FOREACH(entry, fdstore) { + struct { + int fd; + } p = { + .fd = -EBADF, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "fd", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, voffsetof(p, fd), 0 }, + {} + }; + + r = sd_json_dispatch(entry, dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG|SD_JSON_WARNING, &p); + if (r < 0) + continue; + + if (p.fd < 0) + continue; + + if (!GREEDY_REALLOC(fd_list, n_fds + 1)) + return log_oom(); + + fd_list[n_fds++] = p.fd; + } + } + + /* Also protect the serialization fd itself */ + if (!GREEDY_REALLOC(fd_list, n_fds + 1)) + return log_oom(); + fd_list[n_fds++] = serialize_fd; + + log_debug("Parsed LUO serialization with %zu fd(s) to preserve.", n_fds); + + *ret = TAKE_PTR(root); + *ret_fds = TAKE_PTR(fd_list); + *ret_n_fds = n_fds; + return 0; +} + +int luo_preserve_fd_stores(sd_json_variant *serialization, int *ret_session_fd) { + _cleanup_close_ int device_fd = -EBADF, session_fd = -EBADF; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *mapping = NULL, *units = NULL; + const char *unit_id; + sd_json_variant *unit_json, *unit_object; + uint64_t token = LUO_MAPPING_INDEX + 1; + int r; + + assert(ret_session_fd); + + if (!serialization) { + *ret_session_fd = -EBADF; + return 0; /* No LUO serialization, nothing to preserve */ + } + + device_fd = luo_open_device(); + if (ERRNO_IS_NEG_DEVICE_ABSENT(device_fd)) { + *ret_session_fd = -EBADF; + return 0; /* LUO not supported, ignore */ + } + if (device_fd < 0) + return log_error_errno(device_fd, "Failed to open /dev/liveupdate: %m"); + + session_fd = luo_create_session(device_fd, LUO_SESSION_NAME); + if (session_fd < 0) + return log_error_errno(session_fd, "Failed to create LUO session '%s': %m", LUO_SESSION_NAME); + + /* Build the mapping JSON for the new kernel's PID 1 and preserve each fd. + * JSON format: { "unit_id": { "fdstore": [ {"type": "fd", "name": "...", "token": N}, + * {"type": "luo_session", "name": "...", "sessionName": "..."} ] }, ... } + * + * For regular fds: type=fd, preserved in the systemd session with the given LUO token. + * For LUO session fds: type=luo_session, the session survives kexec independently, as it cannot be + * nested. */ + unit_object = sd_json_variant_by_key(serialization, "units"); + if (unit_object && !sd_json_variant_is_object(unit_object)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "LUO serialization 'units' field is not an object"); + + JSON_VARIANT_OBJECT_FOREACH(unit_id, unit_json, unit_object) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *fd_list = NULL; + sd_json_variant *entry, *fds_json; + + fds_json = sd_json_variant_by_key(unit_json, "fdstore"); + if (!sd_json_variant_is_array(fds_json)) { + log_warning("LUO mapping fdstore for unit '%s' is not a JSON array, skipping.", unit_id); + continue; + } + + JSON_VARIANT_ARRAY_FOREACH(entry, fds_json) { + struct { + const char *type; + const char *name; + int fd; + const char *session_name; + } p = { + .fd = -EBADF, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, type), SD_JSON_MANDATORY }, + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + { "fd", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, voffsetof(p, fd), 0 }, + { "sessionName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, session_name), 0 }, + {} + }; + + r = sd_json_dispatch(entry, dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG|SD_JSON_WARNING, &p); + if (r < 0) + continue; + + if (streq(p.type, "fd")) { + if (p.fd < 0) { + log_warning("LUO mapping for unit '%s' fd '%s': missing or negative fd, skipping.", unit_id, p.name); + continue; + } + + /* Regular fd, preserve in the systemd session */ + r = luo_session_preserve_fd(session_fd, p.fd, token); + if (r < 0) { + log_warning_errno(r, "Failed to preserve LUO fd %i (name '%s') with token %" PRIu64 ", will be lost across kexec: %m", p.fd, p.name, token); + continue; + } + + r = sd_json_variant_append_arraybo( + &fd_list, + SD_JSON_BUILD_PAIR_STRING("type", "fd"), + SD_JSON_BUILD_PAIR_STRING("name", p.name), + SD_JSON_BUILD_PAIR_UNSIGNED("token", token)); + if (r < 0) + return log_error_errno(r, "Failed to build LUO mapping: %m"); + + ++token; + } else if (streq(p.type, "luo_session")) { + if (!p.session_name) { + log_warning("LUO mapping for unit '%s' fd '%s': missing sessionName, skipping.", unit_id, p.name); + continue; + } + + /* Remember the FDStore name to session name mapping */ + r = sd_json_variant_append_arraybo( + &fd_list, + SD_JSON_BUILD_PAIR_STRING("type", "luo_session"), + SD_JSON_BUILD_PAIR_STRING("name", p.name), + SD_JSON_BUILD_PAIR_STRING("sessionName", p.session_name)); + if (r < 0) + return log_error_errno(r, "Failed to build LUO mapping for session fd: %m"); + + log_debug("LUO session fd '%s' (session '%s') recorded in mapping.", + p.name, p.session_name); + } else + log_warning("Unknown fd type '%s' for unit '%s' fd '%s', skipping.", p.type, unit_id, p.name); + } + + if (fd_list) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *unit_entry = NULL; + + r = sd_json_buildo( + &unit_entry, + SD_JSON_BUILD_PAIR_VARIANT("fdstore", fd_list)); + if (r < 0) + return log_error_errno(r, "Failed to build LUO unit object: %m"); + + r = sd_json_variant_set_field(&units, unit_id, unit_entry); + if (r < 0) + return log_error_errno(r, "Failed to add unit to LUO mapping: %m"); + } + } + + sd_json_variant *version = sd_json_variant_by_key(serialization, "version"); + if (!sd_json_variant_is_unsigned(version)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "LUO serialization 'version' field is missing or not an unsigned integer"); + + sd_json_variant *state = sd_json_variant_by_key(serialization, "state"); + + r = sd_json_buildo( + &mapping, + SD_JSON_BUILD_PAIR("version", SD_JSON_BUILD_VARIANT(version)), + SD_JSON_BUILD_PAIR_CONDITION(!!state, "state", SD_JSON_BUILD_VARIANT(state)), + SD_JSON_BUILD_PAIR_CONDITION(!!units, "units", SD_JSON_BUILD_VARIANT(units))); + if (r < 0) + return log_error_errno(r, "Failed to build LUO mapping: %m"); + + /* Store the mapping as a memfd at LUO token 0 */ + _cleanup_free_ char *mapping_text = NULL; + + r = sd_json_variant_format(mapping, /* flags= */ 0, &mapping_text); + if (r < 0) + return log_error_errno(r, "Failed to format LUO mapping JSON: %m"); + + _cleanup_close_ int mapping_fd = -EBADF; + + mapping_fd = memfd_new_and_seal_string("luo-mapping", mapping_text); + if (mapping_fd < 0) + return log_error_errno(mapping_fd, "Failed to create LUO mapping memfd: %m"); + + r = luo_session_preserve_fd(session_fd, mapping_fd, LUO_MAPPING_INDEX); + if (r < 0) + return log_error_errno(r, "Failed to preserve LUO mapping memfd: %m"); + + log_info("Preserved fd stores in LUO session '%s' for kexec.", LUO_SESSION_NAME); + + /* Return the session fd to the caller as it must stay open until the kexec syscall, + * otherwise the kernel discards the session. */ + *ret_session_fd = TAKE_FD(session_fd); + return 1; +} + +int fd_get_luo_session_name(int fd, char **ret) { + _cleanup_free_ char *path = NULL; + int r; + + assert(fd >= 0); + + // TODO: switch to LUO specific inode magic once available + r = fd_is_fs_type(fd, ANON_INODE_FS_MAGIC); + if (r < 0) + return r; + if (r == 0) + return -EMEDIUMTYPE; + + r = fd_get_path(fd, &path); + if (r < 0) + return r; + + /* Path is "anon_inode:[luo_session] " */ + const char *suffix = startswith(path, "anon_inode:[luo_session] "); + if (isempty(suffix)) + return -EMEDIUMTYPE; + + if (ret) + return strdup_to(ret, suffix); + + return 0; +} + +int fd_is_luo_session(int fd) { + int r; + + r = fd_get_luo_session_name(fd, /* ret= */ NULL); + if (r == -EMEDIUMTYPE) + return false; + if (r < 0) + return r; + + return true; +} diff --git a/src/shared/luo-util.h b/src/shared/luo-util.h new file mode 100644 index 0000000000000..96500e24fc421 --- /dev/null +++ b/src/shared/luo-util.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "basic-forward.h" +#include "sd-forward.h" + +#define LUO_SESSION_NAME "systemd" + +/* Index (token) 0 in the LUO session is always the mapping memfd, which contains a versioned JSON document + * with manager-level state and a "units" object mapping unit ids to per-unit objects with an "fdstore" array + * of fd store entries: + * + * { + * "version": 1, + * "state": { }, + * "units": { + * "unit-name.service": { + * "fdstore": [ + * { "type": "fd", "name": "fdname1", "token": 1 }, + * { "type": "luo_session", "name": "fdname3", "sessionName": "unit.service/myapp" } + * ] + * } + * } + * } + * + * type=fd: the fd was preserved in the "systemd" LUO session with the given token. + * type=luo_session: a service-owned LUO session that survives kexec independently, + * retrieved by session_name on the next boot. + */ +#define LUO_MAPPING_INDEX UINT64_C(0) +#define LUO_PROTOCOL_VERSION UINT64_C(1) + +int luo_open_device(void); +int luo_create_session(int device_fd, const char *name); +int luo_retrieve_session(int device_fd, const char *name); +int luo_session_preserve_fd(int session_fd, int fd, uint64_t token); +int luo_session_retrieve_fd(int session_fd, uint64_t token); +int luo_session_finish(int session_fd); + +bool luo_session_name_is_valid(const char *name); + +int luo_parse_serialization(sd_json_variant **ret, int **ret_fds, size_t *ret_n_fds); +int luo_preserve_fd_stores(sd_json_variant *serialization, int *ret_session_fd); + +int fd_is_luo_session(int fd); +int fd_get_luo_session_name(int fd, char **ret); 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..d1c4f710aec4f --- /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)); + 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_full_errno(r == -ENXIO ? LOG_DEBUG : LOG_INFO, /* Might have already been noticed and unregistered by machined itself */ + 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) { + 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..43a4fdfdd81b9 --- /dev/null +++ b/src/shared/machine-util.c @@ -0,0 +1,279 @@ +/* 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 "parse-util.h" +#include "storage-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.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 read_only_mode_table[_READ_ONLY_MAX] = { + [READ_ONLY_NO] = "no", + [READ_ONLY_YES] = "yes", + [READ_ONLY_AUTO] = "auto", +}; + +DEFINE_STRING_TABLE_LOOKUP(read_only_mode, ReadOnlyMode); + +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; +} + +BindVolume* bind_volume_free(BindVolume *v) { + if (!v) + return NULL; + + free(v->provider); + free(v->volume); + free(v->config); + free(v->template); + + return mfree(v); +} + +static int bind_volume_apply_extra(BindVolume *v, const char *key, const char *value) { + int r; + + assert(v); + assert(key); + assert(value); + + if (streq(key, "template")) { + if (v->template) + return -EINVAL; + if (!storage_template_name_is_valid(value)) + return -EINVAL; + r = free_and_strdup(&v->template, value); + if (r < 0) + return r; + return 0; + } + + if (streq(key, "create")) { + if (v->create_mode >= 0) + return -EINVAL; + CreateMode m = create_mode_from_string(value); + if (m < 0) + return m; + v->create_mode = m; + return 0; + } + + if (STR_IN_SET(key, "read-only", "ro")) { + if (v->read_only >= 0) + return -EINVAL; + ReadOnlyMode m = read_only_mode_from_string(value); + if (m < 0) { + r = parse_boolean(value); + if (r < 0) + return r; + m = r ? READ_ONLY_YES : READ_ONLY_NO; + } + v->read_only = m; + return 0; + } + + if (STR_IN_SET(key, "size", "create-size")) { + if (v->create_size_bytes != UINT64_MAX) + return -EINVAL; + uint64_t sz; + r = parse_size(value, 1024, &sz); + if (r < 0) + return r; + if (sz == 0) + return -EINVAL; + v->create_size_bytes = sz; + return 0; + } + + if (streq(key, "request-as")) { + if (v->request_as >= 0) + return -EINVAL; + VolumeType t = volume_type_from_string(value); + if (t < 0) + return t; + v->request_as = t; + return 0; + } + + return -EINVAL; +} + +int bind_volume_parse(const char *arg, BindVolume **ret) { + _cleanup_(bind_volume_freep) BindVolume *v = NULL; + int r; + + assert(arg); + assert(ret); + + v = new(BindVolume, 1); + if (!v) + return -ENOMEM; + + *v = BIND_VOLUME_INIT; + + const char *p = arg; + _cleanup_free_ char *provider = NULL, *volume = NULL, *config = NULL; + + r = extract_first_word(&p, &provider, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0 || isempty(provider) || !storage_provider_name_is_valid(provider)) + return -EINVAL; + + r = extract_first_word(&p, &volume, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0 || isempty(volume) || !storage_volume_name_is_valid(volume)) + return -EINVAL; + + r = extract_first_word(&p, &config, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + + v->provider = TAKE_PTR(provider); + v->volume = TAKE_PTR(volume); + if (!isempty(config)) { + if (!string_is_safe(config, /* flags= */ 0)) + return -EINVAL; + v->config = TAKE_PTR(config); + } + + for (;;) { + _cleanup_free_ char *kv = NULL, *key = NULL, *value = NULL; + + r = extract_first_word(&p, &kv, ",", 0); + if (r < 0) + return r; + if (r == 0) + break; + + r = split_pair(kv, "=", &key, &value); + if (r < 0) + return r; + if (isempty(key)) + return -EINVAL; + + r = bind_volume_apply_extra(v, key, value); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +int machine_storage_name_split(const char *s, char **ret_provider, char **ret_volume) { + _cleanup_free_ char *p = NULL, *v = NULL; + int r; + + if (isempty(s)) + return -EINVAL; + + r = split_pair(s, ":", &p, &v); + if (r < 0) + return r; + + if (!storage_provider_name_is_valid(p) || !storage_volume_name_is_valid(v)) + return -EINVAL; + + if (ret_provider) + *ret_provider = TAKE_PTR(p); + if (ret_volume) + *ret_volume = TAKE_PTR(v); + return 0; +} diff --git a/src/shared/machine-util.h b/src/shared/machine-util.h new file mode 100644 index 0000000000000..a992e24448076 --- /dev/null +++ b/src/shared/machine-util.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" +#include "storage-util.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); + +typedef enum ReadOnlyMode { + READ_ONLY_NO, + READ_ONLY_YES, + READ_ONLY_AUTO, + _READ_ONLY_MAX, + _READ_ONLY_INVALID = -EINVAL, +} ReadOnlyMode; + +DECLARE_STRING_TABLE_LOOKUP(read_only_mode, ReadOnlyMode); + +/* Map ReadOnlyMode onto the Acquire() wire tristate (-1 unset/auto, 0 no, 1 yes). */ +static inline int read_only_mode_to_tristate(ReadOnlyMode m) { + switch (m) { + case READ_ONLY_NO: return 0; + case READ_ONLY_YES: return 1; + default: return -1; + } +} + +/* Parsed "PROVIDER:VOLUME[:CONFIG][:K=V,K=V,...]" used by --bind-volume, + * machinectl bind-volume, and (future) the BindVolume= unit setting. The 'config' + * field is opaque here and interpreted per-backend (vmspawn: a DiskType name; + * nspawn: a mount path). */ +typedef struct BindVolume { + char *provider; + char *volume; + char *config; + + /* Acquire() parameters parsed from the trailing key=value list. */ + char *template; + CreateMode create_mode; + ReadOnlyMode read_only; + uint64_t create_size_bytes; + VolumeType request_as; +} BindVolume; + +#define BIND_VOLUME_INIT \ + (BindVolume) { \ + .create_mode = _CREATE_MODE_INVALID, \ + .read_only = _READ_ONLY_INVALID, \ + .create_size_bytes = UINT64_MAX, \ + .request_as = _VOLUME_TYPE_INVALID, \ + } + +BindVolume* bind_volume_free(BindVolume *v); +DEFINE_TRIVIAL_CLEANUP_FUNC(BindVolume*, bind_volume_free); + +int bind_volume_parse(const char *arg, BindVolume **ret); + +/* Validate a ":" binding name as used by AddStorage/RemoveStorage. + * ret_provider/ret_volume may each be NULL when the caller only wants validation. */ +int machine_storage_name_split(const char *s, char **ret_provider, char **ret_volume); diff --git a/src/shared/meson.build b/src/shared/meson.build index bbc0307999324..7c6505e015131 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', @@ -18,8 +19,8 @@ shared_sources = files( 'boot-entry.c', 'boot-timestamps.c', 'bootspec.c', - 'bpf-dlopen.c', 'bpf-program.c', + 'bpf-util.c', 'bridge-util.c', 'btrfs-util.c', 'bus-get-properties.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,31 +80,38 @@ shared_sources = files( 'exit-status.c', 'extension-util.c', 'factory-reset.c', + 'fdisk-util.c', 'fdset.c', 'fido2-util.c', 'find-esp.c', 'firewall-util.c', 'fork-notify.c', 'format-table.c', + 'fsprg-openssl.c', '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', 'in-addr-prefix-util.c', + 'initrd-cpio.c', 'install-file.c', 'install-printf.c', 'install.c', 'ioprio-util.c', 'ip-protocol-list.c', 'ipvlan-util.c', + 'journal-authenticate.c', 'journal-file-util.c', 'journal-importer.c', 'journal-util.c', @@ -114,6 +124,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', @@ -121,13 +132,17 @@ shared_sources = files( 'loop-util.c', 'loopback-setup.c', 'lsm-util.c', + 'luo-util.c', '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', - 'mkdir-label.c', + 'microhttpd-util.c', + 'mkdir.c', 'mkfs-util.c', 'module-util.c', 'mount-setup.c', @@ -142,10 +157,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 +178,7 @@ shared_sources = files( 'printk-util.c', 'prompt-util.c', 'ptyfwd.c', + 'qmp-client.c', 'qrcode-util.c', 'quota-util.c', 'reboot-util.c', @@ -170,6 +187,7 @@ shared_sources = files( 'resize-fs.c', 'resolve-hook-util.c', 'resolve-util.c', + 'resolve-varlink-util.c', 'rm-rf.c', 'seccomp-util.c', 'securebits-util.c', @@ -181,15 +199,20 @@ 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', + 'storage-util.c', 'switch-root.c', + 'swtpm-util.c', 'tar-util.c', - 'tmpfile-util-label.c', + 'tmpfile-util.c', 'tomoyo-util.c', 'tpm2-util.c', 'tpm2-event-log.c', + 'tsm-report.c', 'udev-util.c', 'unit-file.c', 'user-record-nss.c', @@ -201,14 +224,18 @@ shared_sources = files( 'varlink-io.systemd.AskPassword.c', 'varlink-io.systemd.BootControl.c', 'varlink-io.systemd.Credentials.c', + 'varlink-io.systemd.CryptEnroll.c', 'varlink-io.systemd.FactoryReset.c', 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Import.c', + 'varlink-io.systemd.InstanceMetadata.c', + 'varlink-io.systemd.Job.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', @@ -220,12 +247,20 @@ shared_sources = files( 'varlink-io.systemd.PCRExtend.c', 'varlink-io.systemd.PCRLock.c', 'varlink-io.systemd.Repart.c', + 'varlink-io.systemd.Report.c', + 'varlink-io.systemd.Report.Signer.c', + 'varlink-io.systemd.Report.Uploader.c', '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.StorageProvider.c', + 'varlink-io.systemd.SysUpdate.c', + 'varlink-io.systemd.SysUpdate.Notify.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', @@ -237,6 +272,7 @@ shared_sources = files( 'vlan-util.c', 'volatile-util.c', 'vpick.c', + 'vsock-util.c', 'wall.c', 'watchdog.c', 'web-util.c', @@ -270,22 +306,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], @@ -375,8 +395,7 @@ shared_sources += [dns_type_from_name_inc, dns_type_to_name_inc] libshared_name = 'systemd-shared-@0@'.format(shared_lib_tag) -libshared_deps = [threads, - libacl_cflags, +libshared_deps = [libacl_cflags, libapparmor_cflags, libarchive_cflags, libaudit_cflags, @@ -384,23 +403,25 @@ libshared_deps = [threads, libbpf_cflags, libcrypt_cflags, libcryptsetup_cflags, - libdl, + libcurl_cflags, 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, libpwquality_cflags, libqrencode_cflags, - librt, libseccomp_cflags, libselinux_cflags, + libucontext, libxenctrl_cflags, libxz_cflags, libzstd_cflags, @@ -435,15 +456,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..cb00977b539c8 100644 --- a/src/shared/metrics.c +++ b/src/shared/metrics.c @@ -58,6 +58,7 @@ static const char * const metric_family_type_table[_METRIC_FAMILY_TYPE_MAX] = { [METRIC_FAMILY_TYPE_COUNTER] = "counter", [METRIC_FAMILY_TYPE_GAUGE] = "gauge", [METRIC_FAMILY_TYPE_STRING] = "string", + [METRIC_FAMILY_TYPE_OBJECT] = "object", }; DEFINE_STRING_TABLE_LOOKUP_TO_STRING(metric_family_type, MetricFamilyType); @@ -73,15 +74,14 @@ static int metric_family_build_json(const MetricFamily *mf, sd_json_variant **re } int metrics_method_describe( - const MetricFamily metric_family_table[], + const MetricFamily mfs[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - int r; - assert(metric_family_table); + assert(mfs); assert(link); assert(parameters); assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); @@ -90,11 +90,11 @@ 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; - for (const MetricFamily *mf = metric_family_table; mf && mf->name; mf++) { + for (const MetricFamily *mf = mfs; mf->name; mf++) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; r = metric_family_build_json(mf, &v); @@ -110,15 +110,14 @@ int metrics_method_describe( } int metrics_method_list( - const MetricFamily metric_family_table[], + const MetricFamily mfs[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - int r; - assert(metric_family_table); + assert(mfs); assert(link); assert(parameters); assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); @@ -127,29 +126,32 @@ 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; - MetricFamilyContext ctx = { .link = link }; - for (const MetricFamily *mf = metric_family_table; mf && mf->name; mf++) { - assert(mf->generate); - - ctx.metric_family = mf; - r = mf->generate(&ctx, userdata); - if (r < 0) - return log_debug_errno( - r, "Failed to list metrics for metric family '%s': %m", mf->name); - } + for (const MetricFamily *mf = mfs; mf->name; mf++) + /* Metrics without .generate are handled by the preceding entry with .generate. + * Call the generating functions that are defined. */ + if (mf->generate) { + r = mf->generate(mf, link, userdata); + if (r < 0) + return log_debug_errno(r, "Failed to list metrics for metric family '%s': %m", mf->name); + } return 0; } -static int metric_build_send(MetricFamilyContext *context, const char *object, sd_json_variant *value, sd_json_variant *fields) { - assert(context); +static int metric_build_send( + const MetricFamily *mf, + sd_varlink *link, + const char *object, + sd_json_variant *value, + sd_json_variant *fields) { + + assert(mf); + assert(link); assert(value); - assert(context->link); - assert(context->metric_family); if (fields) { assert(sd_json_variant_is_object(fields)); @@ -160,33 +162,86 @@ static int metric_build_send(MetricFamilyContext *context, const char *object, s assert(sd_json_variant_is_string(e)); } - return sd_varlink_replybo(context->link, - SD_JSON_BUILD_PAIR_STRING("name", context->metric_family->name), + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_STRING("name", mf->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)); } -int metric_build_send_string(MetricFamilyContext *context, const char *object, const char *value, sd_json_variant *fields) { +int metric_build_send_string( + const MetricFamily *mf, + sd_varlink *link, + const char *object, + const char *value, + sd_json_variant *fields) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; int r; + assert(mf); + assert(link); 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 metric_build_send(context, object, v, fields); + return metric_build_send(mf, link, object, v, fields); } -int metric_build_send_unsigned(MetricFamilyContext *context, const char *object, uint64_t value, sd_json_variant *fields) { +int metric_build_send_unsigned( + const MetricFamily *mf, + sd_varlink *link, + const char *object, + uint64_t value, + sd_json_variant *fields) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; int r; + assert(mf); + assert(link); + r = sd_json_variant_new_unsigned(&v, value); if (r < 0) return log_debug_errno(r, "Failed to allocate JSON unsigned: %m"); - return metric_build_send(context, object, v, fields); + return metric_build_send(mf, link, object, v, fields); +} + +int metric_build_send_double( + const MetricFamily *mf, + sd_varlink *link, + const char *object, + double value, + sd_json_variant *fields) { + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + assert(mf); + assert(link); + + r = sd_json_variant_new_real(&v, value); + if (r < 0) + return log_debug_errno(r, "Failed to allocate JSON real: %m"); + + return metric_build_send(mf, link, object, v, fields); +} + +int metric_build_send_object( + const MetricFamily *mf, + sd_varlink *link, + const char *object, + sd_json_variant *value, + sd_json_variant *fields) { + + assert(mf); + assert(link); + assert(value); + assert(sd_json_variant_is_object(value)); + + return metric_build_send(mf, link, object, value, fields); } diff --git a/src/shared/metrics.h b/src/shared/metrics.h index ffc28cb0a9d8c..8d6dcec999457 100644 --- a/src/shared/metrics.h +++ b/src/shared/metrics.h @@ -7,18 +7,14 @@ typedef enum MetricFamilyType { METRIC_FAMILY_TYPE_COUNTER, METRIC_FAMILY_TYPE_GAUGE, METRIC_FAMILY_TYPE_STRING, + METRIC_FAMILY_TYPE_OBJECT, _METRIC_FAMILY_TYPE_MAX, _METRIC_FAMILY_TYPE_INVALID = -EINVAL, } MetricFamilyType; typedef struct MetricFamily MetricFamily; -typedef struct MetricFamilyContext { - const MetricFamily* metric_family; - sd_varlink *link; -} MetricFamilyContext; - -typedef int (*metric_family_generate_func_t) (MetricFamilyContext *mfc, void *userdata); +typedef int (*metric_family_generate_func_t) (const MetricFamily *mf, sd_varlink *link, void *userdata); typedef struct MetricFamily { const char *name; @@ -38,8 +34,10 @@ int metrics_setup_varlink_server( DECLARE_STRING_TABLE_LOOKUP_TO_STRING(metric_family_type, MetricFamilyType); -int metrics_method_describe(const MetricFamily metric_family_table[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); -int metrics_method_list(const MetricFamily metric_family_table[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int metrics_method_describe(const MetricFamily mfs[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int metrics_method_list(const MetricFamily mfs[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); -int metric_build_send_string(MetricFamilyContext* context, const char *object, const char *value, sd_json_variant *fields); -int metric_build_send_unsigned(MetricFamilyContext* context, const char *object, uint64_t value, sd_json_variant *fields); +int metric_build_send_string(const MetricFamily* mf, sd_varlink *link, const char *object, const char *value, sd_json_variant *fields); +int metric_build_send_unsigned(const MetricFamily* mf, sd_varlink *link, const char *object, uint64_t value, sd_json_variant *fields); +int metric_build_send_double(const MetricFamily* mf, sd_varlink *link, const char *object, double value, sd_json_variant *fields); +int metric_build_send_object(const MetricFamily* mf, sd_varlink *link, const char *object, sd_json_variant *value, sd_json_variant *fields); diff --git a/src/journal-remote/microhttpd-util.c b/src/shared/microhttpd-util.c similarity index 66% rename from src/journal-remote/microhttpd-util.c rename to src/shared/microhttpd-util.c index e69f32f7ab536..9820a1e400015 100644 --- a/src/journal-remote/microhttpd-util.c +++ b/src/shared/microhttpd-util.c @@ -2,17 +2,70 @@ #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 +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 + static void *microhttpd_dl = NULL; + + MICROHTTPD_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); + + 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 +89,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 +121,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 +169,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 +193,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 +222,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 +248,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 +261,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 +279,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 +289,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 +312,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 65% rename from src/journal-remote/microhttpd-util.h rename to src/shared/microhttpd-util.h index 80142e24f59c5..39b2b9267b2e3 100644 --- a/src/journal-remote/microhttpd-util.h +++ b/src/shared/microhttpd-util.h @@ -1,11 +1,28 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dlopen.h" + +#include "shared-forward.h" + +int dlopen_microhttpd(int log_level); + #if HAVE_MICROHTTPD +#define MICROHTTPD_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("microhttpd", \ + "Support for embedded HTTP server via libmicrohttpd", \ + priority, \ + "libmicrohttpd.so.12") + +#define DLOPEN_MICROHTTPD(log_level, priority) \ + ({ \ + MICROHTTPD_NOTE(priority); \ + dlopen_microhttpd(log_level); \ + }) #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 +75,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 +144,9 @@ 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); +#else +#define DLOPEN_MICROHTTPD(log_level, priority) dlopen_microhttpd(log_level) #endif diff --git a/src/shared/mkdir-label.c b/src/shared/mkdir.c similarity index 98% rename from src/shared/mkdir-label.c rename to src/shared/mkdir.c index 36473b10669ad..1228bca19bb3e 100644 --- a/src/shared/mkdir-label.c +++ b/src/shared/mkdir.c @@ -4,7 +4,7 @@ #include "errno-util.h" #include "label-util.h" /* IWYU pragma: keep */ -#include "mkdir-label.h" +#include "mkdir.h" #include "selinux-util.h" #include "smack-util.h" diff --git a/src/shared/mkdir-label.h b/src/shared/mkdir.h similarity index 93% rename from src/shared/mkdir-label.h rename to src/shared/mkdir.h index c884190a0e25f..b02e02a8f083f 100644 --- a/src/shared/mkdir-label.h +++ b/src/shared/mkdir.h @@ -2,7 +2,8 @@ #pragma once #include "shared-forward.h" -#include "mkdir.h" + +#include "../basic/mkdir.h" /* IWYU pragma: export */ int mkdirat_label(int dirfd, const char *path, mode_t mode); diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c index b3e575efd37fd..80c509f99b010 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -39,6 +39,56 @@ int mkfs_exists(const char *fstype) { return true; } +int mkfs_find_or_warn(const char *fstype, int have_root, char **ret) { + int r; + + assert(fstype); + + if (fstype_is_ro(fstype) && have_root == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Cannot generate read-only filesystem %s without a source tree.", fstype); + + const char *bin = NULL; + if (streq(fstype, "swap")) { + if (have_root > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "A swap filesystem can't be populated, refusing"); + bin = "mkswap"; + } else if (streq(fstype, "squashfs")) + bin = "mksquashfs"; + else if (streq(fstype, "erofs")) + bin = "mkfs.erofs"; + else if (fstype_is_ro(fstype)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Don't know how to create read-only file system '%s', refusing.", fstype); + if (bin) { + r = find_executable(bin, ret); + if (r == -ENOENT) + return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "%s binary not available.", bin); + if (r < 0) + return log_error_errno(r, "Failed to determine whether %s binary exists: %m", bin); + return 0; + } + + if (have_root > 0 && !mkfs_supports_root_option(fstype)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Populating with source tree is not supported for %s", fstype); + r = mkfs_exists(fstype); + if (r < 0) + return log_error_errno(r, "Failed to determine whether mkfs binary for %s exists: %m", fstype); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkfs binary for %s not available.", fstype); + + if (ret) { + char *mkfs = strjoin("mkfs.", fstype); + if (!mkfs) + return log_oom(); + *ret = mkfs; + } + + return 0; +} + int mkfs_supports_root_option(const char *fstype) { return fstype_is_ro(fstype) || STR_IN_SET(fstype, "ext2", "ext3", "ext4", "btrfs", "vfat", "xfs"); } @@ -74,11 +124,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; @@ -97,72 +148,172 @@ static int mangle_fat_label(const char *s, char **ret) { return 0; } -static int do_mcopy(const char *node, const char *root) { - _cleanup_free_ char *mcopy = NULL; - _cleanup_strv_free_ char **argv = NULL; - _cleanup_free_ DirectoryEntries *de = NULL; +static int mtools_exec(char *const *argv) { int r; + assert(argv); + assert(argv[0]); + + r = pidref_safe_fork( + "(mtools)", + FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|FORK_CLOSE_ALL_FDS, + /* ret= */ NULL); + if (r < 0) + return r; + if (r == 0) { + /* Avoid failures caused by mismatch in expectations between mkfs.vfat and mtools by + * disabling the stricter checks using MTOOLS_SKIP_CHECK. Force TZ=UTC and forward + * SOURCE_DATE_EPOCH so that mtools produces deterministic FAT timestamps. */ + execve(argv[0], argv, + STRV_MAKE("MTOOLS_SKIP_CHECK=1", + "TZ=UTC", + strv_find_prefix(environ, "SOURCE_DATE_EPOCH="))); + + log_error_errno(errno, "Failed to execute %s: %m", argv[0]); + _exit(EXIT_FAILURE); + } + + return 0; +} + +static int mcopy_flush_files( + const char *mcopy_bin, + const char *node, + const char *dest_rel, + char ***file_batch) { + + _cleanup_strv_free_ char **argv = NULL, **batch = TAKE_PTR(*file_batch); + _cleanup_free_ char *dest = NULL; + + assert(mcopy_bin); assert(node); - assert(root); + assert(dest_rel); + assert(file_batch); - /* Return early if there's nothing to copy. */ - if (dir_is_empty(root, /* ignore_hidden_or_backup= */ false)) + if (strv_isempty(batch)) return 0; - r = find_executable("mcopy", &mcopy); - if (r == -ENOENT) - return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "Could not find mcopy binary."); - if (r < 0) - return log_error_errno(r, "Failed to determine whether mcopy binary exists: %m"); + /* mcopy treats ::dir/ as the destination directory. The trailing slash makes it copy the + * source files into it rather than renaming a single source to that path. */ + dest = strjoin("::", dest_rel, "/"); + if (!dest) + return log_oom(); - argv = strv_new(mcopy, "-s", "-p", "-Q", "-m", "-i", node); + argv = strv_new(mcopy_bin, "-p", "-Q", "-m", "-i", node); if (!argv) return log_oom(); - /* mcopy copies the top level directory instead of everything in it so we have to pass all - * the subdirectories to mcopy instead to end up with the correct directory structure. */ + STRV_FOREACH(p, batch) + if (strv_extend(&argv, *p) < 0) + return log_oom(); + + if (strv_extend(&argv, dest) < 0) + return log_oom(); - r = readdir_all_at(AT_FDCWD, root, RECURSE_DIR_SORT|RECURSE_DIR_ENSURE_TYPE, &de); + return mtools_exec(argv); +} + +static int do_mcopy_recurse( + const char *mcopy_bin, + const char *mmd_bin, + const char *node, + const char *src_root, + const char *dest_rel) { + + _cleanup_free_ DirectoryEntries *de = NULL; + _cleanup_strv_free_ char **file_batch = NULL; + int r; + + assert(mcopy_bin); + assert(mmd_bin); + assert(node); + assert(src_root); + assert(dest_rel); + + /* Walk the source in deterministic (alphabetical) order so the FAT directory entries are + * inserted in a host-independent sequence. We can't rely on `mcopy -s` to do this, as mtools + * recurses via the platform's readdir() so the order is FS dependent. Instead we drive the + * recursion here and issue per-item mmd/mcopy invocations interleaved per parent + * directory, batching consecutive sibling files so the fork cost stays bounded. */ + r = readdir_all_at(AT_FDCWD, src_root, RECURSE_DIR_SORT|RECURSE_DIR_ENSURE_TYPE, &de); if (r < 0) - return log_error_errno(r, "Failed to read '%s' contents: %m", root); + return log_error_errno(r, "Failed to read '%s' contents: %m", src_root); for (size_t i = 0; i < de->n_entries; i++) { - _cleanup_free_ char *p = NULL; + struct dirent *ent = de->entries[i]; + _cleanup_free_ char *src = NULL; - p = path_join(root, de->entries[i]->d_name); - if (!p) + if (!IN_SET(ent->d_type, DT_REG, DT_DIR)) { + log_debug("%s/%s is not a file/directory which are the only file types supported by vfat, ignoring", + src_root, ent->d_name); + continue; + } + + src = path_join(src_root, ent->d_name); + if (!src) return log_oom(); - if (!IN_SET(de->entries[i]->d_type, DT_REG, DT_DIR)) { - log_debug("%s is not a file/directory which are the only file types supported by vfat, ignoring", p); + if (ent->d_type == DT_REG) { + if (strv_consume(&file_batch, TAKE_PTR(src)) < 0) + return log_oom(); continue; } - if (strv_consume(&argv, TAKE_PTR(p)) < 0) + /* Directory. Flush pending file siblings first so the parent FAT directory's entry + * order matches the sorted enumeration above, then create the subdir and recurse. */ + r = mcopy_flush_files(mcopy_bin, node, dest_rel, &file_batch); + if (r < 0) + return r; + + _cleanup_free_ char *dst = strjoin("::", dest_rel, "/", ent->d_name); + if (!dst) return log_oom(); - } - if (strv_extend(&argv, "::") < 0) - return log_oom(); + /* Note: mmd accepts only -D and -i; there is no -Q quiet flag like mcopy has. */ + _cleanup_strv_free_ char **argv = strv_new(mmd_bin, "-i", node, dst); + if (!argv) + return log_oom(); - r = pidref_safe_fork( - "(mcopy)", - FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR|FORK_CLOSE_ALL_FDS, - /* ret= */ NULL); - if (r < 0) - return r; - if (r == 0) { - /* Avoid failures caused by mismatch in expectations between mkfs.vfat and mcopy by disabling - * the stricter mcopy checks using MTOOLS_SKIP_CHECK. */ - execve(mcopy, argv, STRV_MAKE("MTOOLS_SKIP_CHECK=1", "TZ=UTC", strv_find_prefix(environ, "SOURCE_DATE_EPOCH="))); + r = mtools_exec(argv); + if (r < 0) + return r; - log_error_errno(errno, "Failed to execute mcopy: %m"); + _cleanup_free_ char *child_rel = strjoin(dest_rel, "/", ent->d_name); + if (!child_rel) + return log_oom(); - _exit(EXIT_FAILURE); + r = do_mcopy_recurse(mcopy_bin, mmd_bin, node, src, child_rel); + if (r < 0) + return r; } - return 0; + return mcopy_flush_files(mcopy_bin, node, dest_rel, &file_batch); +} + +static int do_mcopy(const char *node, const char *root) { + _cleanup_free_ char *mcopy = NULL, *mmd = NULL; + int r; + + assert(node); + assert(root); + + /* Return early if there's nothing to copy. */ + if (dir_is_empty(root, /* ignore_hidden_or_backup= */ false)) + return 0; + + r = find_executable("mcopy", &mcopy); + if (r == -ENOENT) + return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "Could not find mcopy binary."); + if (r < 0) + return log_error_errno(r, "Failed to determine whether mcopy binary exists: %m"); + + r = find_executable("mmd", &mmd); + if (r == -ENOENT) + return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "Could not find mmd binary."); + if (r < 0) + return log_error_errno(r, "Failed to determine whether mmd binary exists: %m"); + + return do_mcopy_recurse(mcopy, mmd, node, root, ""); } int make_filesystem( @@ -189,52 +340,9 @@ int make_filesystem( assert(fstype); assert(label); - if (fstype_is_ro(fstype) && !root) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Cannot generate read-only filesystem %s without a source tree.", - fstype); - - if (streq(fstype, "swap")) { - if (root) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "A swap filesystem can't be populated, refusing"); - r = find_executable("mkswap", &mkfs); - if (r == -ENOENT) - return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkswap binary not available."); - if (r < 0) - return log_error_errno(r, "Failed to determine whether mkswap binary exists: %m"); - } else if (streq(fstype, "squashfs")) { - r = find_executable("mksquashfs", &mkfs); - if (r == -ENOENT) - return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mksquashfs binary not available."); - if (r < 0) - return log_error_errno(r, "Failed to determine whether mksquashfs binary exists: %m"); - - } else if (streq(fstype, "erofs")) { - r = find_executable("mkfs.erofs", &mkfs); - if (r == -ENOENT) - return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkfs.erofs binary not available."); - if (r < 0) - return log_error_errno(r, "Failed to determine whether mkfs.erofs binary exists: %m"); - - } else if (fstype_is_ro(fstype)) { - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Don't know how to create read-only file system '%s', refusing.", - fstype); - } else { - if (root && !mkfs_supports_root_option(fstype)) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Populating with source tree is not supported for %s", fstype); - r = mkfs_exists(fstype); - if (r < 0) - return log_error_errno(r, "Failed to determine whether mkfs binary for %s exists: %m", fstype); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "mkfs binary for %s is not available.", fstype); - - mkfs = strjoin("mkfs.", fstype); - if (!mkfs) - return log_oom(); - } + r = mkfs_find_or_warn(fstype, /* have_root= */ !!root, &mkfs); + if (r < 0) + return r; if (STR_IN_SET(fstype, "ext2", "ext3", "ext4", "xfs", "swap")) { size_t max_len = diff --git a/src/shared/mkfs-util.h b/src/shared/mkfs-util.h index 7ab78be98a62c..b23f0cf74575c 100644 --- a/src/shared/mkfs-util.h +++ b/src/shared/mkfs-util.h @@ -12,6 +12,7 @@ typedef enum MakeFilesystemFlags { } MakeFileSystemFlags; int mkfs_exists(const char *fstype); +int mkfs_find_or_warn(const char *fstype, int have_root, char **ret); int mkfs_supports_root_option(const char *fstype); diff --git a/src/shared/module-util.c b/src/shared/module-util.c index a7f9e178e3c2a..e060c69d9d675 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" @@ -9,8 +11,6 @@ #if HAVE_KMOD -static void *libkmod_dl = NULL; - DLSYM_PROTOTYPE(kmod_list_next) = NULL; DLSYM_PROTOTYPE(kmod_load_resources) = NULL; DLSYM_PROTOTYPE(kmod_module_get_initstate) = NULL; @@ -25,31 +25,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 +152,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 +168,32 @@ int module_setup_context(struct kmod_ctx **ret) { } #endif + +int dlopen_libkmod(int log_level) { +#if HAVE_KMOD + static void *libkmod_dl = NULL; + + LIBKMOD_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + + 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..730e14c31b32a 100644 --- a/src/shared/module-util.h +++ b/src/shared/module-util.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dlopen.h" + #include "shared-forward.h" #if HAVE_KMOD @@ -23,8 +25,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); @@ -37,13 +37,21 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct kmod_list*, sym_kmod_module_unref int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose); int module_setup_context(struct kmod_ctx **ret); +#define LIBKMOD_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("kmod", \ + "Support for loading kernel modules", \ + priority, \ + "libkmod.so.2") + +#define DLOPEN_LIBKMOD(log_level, priority) \ + ({ \ + LIBKMOD_NOTE(priority); \ + dlopen_libkmod(log_level); \ + }) #else struct kmod_ctx; -static inline int dlopen_libkmod(void) { - return -EOPNOTSUPP; -} static inline int module_setup_context(struct kmod_ctx **ret) { return -EOPNOTSUPP; @@ -53,4 +61,7 @@ static inline int module_load_and_warn(struct kmod_ctx *ctx, const char *module, return -EOPNOTSUPP; } +#define DLOPEN_LIBKMOD(log_level, priority) dlopen_libkmod(log_level) #endif + +int dlopen_libkmod(int log_level); diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c index 8c6cd2b12b69a..f402990cf747c 100644 --- a/src/shared/mount-setup.c +++ b/src/shared/mount-setup.c @@ -12,7 +12,7 @@ #include "fileio.h" #include "label-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-setup.h" #include "mount-util.h" #include "mountpoint-util.h" diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index f3eaa7ba97ad9..cda0070b14e54 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -21,7 +21,7 @@ #include "hashmap.h" #include "libmount-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "namespace-util.h" @@ -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..65f38ba3b3df2 100644 --- a/src/shared/mstack.c +++ b/src/shared/mstack.c @@ -139,7 +139,7 @@ static int mstack_load_one(MStack *mstack, const char *dir, int dir_fd, const ch }; _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; - r = path_pick(dir, dir_fd, fname, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE, &result); + r = path_pick(dir, dir_fd, dir_fd, fname, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE, &result); if (r < 0) return log_debug_errno(r, "Failed to resolve '%s' directory: %m", fname); if (r == 0) @@ -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-naming-scheme.c b/src/shared/netif-naming-scheme.c index 607094eecfd29..978399ca4139f 100644 --- a/src/shared/netif-naming-scheme.c +++ b/src/shared/netif-naming-scheme.c @@ -31,6 +31,7 @@ static const NamingScheme naming_schemes[] = { { "v258", NAMING_V258 }, { "v259", NAMING_V259 }, { "v260", NAMING_V260 }, + { "v261", NAMING_V261 }, /* … add more schemes here, as the logic to name devices is updated … */ EXTRA_NET_NAMING_MAP @@ -178,7 +179,7 @@ int device_get_sysattr_bool_filtered(sd_device *device, const char *sysattr) { return device_get_sysattr_bool(device, sysattr); } -int device_get_sysattr_value_filtered(sd_device *device, const char *sysattr, const char **ret_value) { +int device_get_sysattr_safe_string_filtered(sd_device *device, const char *sysattr, const char **ret_value) { int r; r = naming_sysattr_allowed(device, sysattr); @@ -187,5 +188,5 @@ int device_get_sysattr_value_filtered(sd_device *device, const char *sysattr, co if (r == 0) return -ENOENT; - return sd_device_get_sysattr_value(device, sysattr, ret_value); + return device_get_sysattr_safe_string(device, sysattr, ret_value); } diff --git a/src/shared/netif-naming-scheme.h b/src/shared/netif-naming-scheme.h index c13d7649c9ef3..1848cb570bc9a 100644 --- a/src/shared/netif-naming-scheme.h +++ b/src/shared/netif-naming-scheme.h @@ -44,6 +44,9 @@ typedef enum NamingSchemeFlags { NAMING_USE_INTERFACE_PROPERTY = 1 << 20, /* Use INTERFACE udev property, rather than sysname, when no renaming is requested. */ NAMING_DEVICETREE_ALIASES_WLAN = 1 << 21, /* Generate names from devicetree aliases for WLAN devices */ NAMING_MCTP = 1 << 22, /* Use "mc" prefix for MCTP devices */ + NAMING_SUBFUNC = 1 << 23, /* Generate names for auxiliary sub-function (SF) network + * devices (e.g. mlx5_core SFs), based on the parent PF's + * PCI path and the user-defined sfnum, with an "S" suffix. */ /* And now the masks that combine the features above */ NAMING_V238 = 0, @@ -67,6 +70,7 @@ typedef enum NamingSchemeFlags { NAMING_V258 = NAMING_V257 | NAMING_USE_INTERFACE_PROPERTY, NAMING_V259 = NAMING_V258 | NAMING_DEVICETREE_ALIASES_WLAN, NAMING_V260 = NAMING_V259 | NAMING_MCTP, + NAMING_V261 = NAMING_V260 | NAMING_SUBFUNC, EXTRA_NET_NAMING_SCHEMES @@ -104,4 +108,4 @@ DECLARE_STRING_TABLE_LOOKUP(alternative_names_policy, NamePolicy); int device_get_sysattr_int_filtered(sd_device *device, const char *sysattr, int *ret_value); int device_get_sysattr_unsigned_filtered(sd_device *device, const char *sysattr, unsigned *ret_value); int device_get_sysattr_bool_filtered(sd_device *device, const char *sysattr); -int device_get_sysattr_value_filtered(sd_device *device, const char *sysattr, const char **ret_value); +int device_get_sysattr_safe_string_filtered(sd_device *device, const char *sysattr, const char **ret_value); diff --git a/src/shared/netif-sriov.c b/src/shared/netif-sriov.c index f621a9dde7e4f..f5df7fb392ac6 100644 --- a/src/shared/netif-sriov.c +++ b/src/shared/netif-sriov.c @@ -5,6 +5,7 @@ #include "alloc-util.h" #include "conf-parser.h" +#include "device-private.h" #include "device-util.h" #include "ether-addr-util.h" #include "netif-sriov.h" @@ -267,28 +268,11 @@ int sr_iov_set_netlink_message(SRIOV *sr_iov, SRIOVAttribute attr, sd_netlink_me } int sr_iov_get_num_vfs(sd_device *device, uint32_t *ret) { - const char *str; - uint32_t n; - int r; - - assert(device); - assert(ret); - - r = sd_device_get_sysattr_value(device, "device/sriov_numvfs", &str); - if (r < 0) - return r; - - r = safe_atou32(str, &n); - if (r < 0) - return r; - - *ret = n; - return 0; + return device_get_sysattr_u32(device, "device/sriov_numvfs", ret); } int sr_iov_set_num_vfs(sd_device *device, uint32_t num_vfs, OrderedHashmap *sr_iov_by_section) { char val[DECIMAL_STR_MAX(uint32_t)]; - const char *str; int r; assert(device); @@ -327,14 +311,9 @@ int sr_iov_set_num_vfs(sd_device *device, uint32_t num_vfs, OrderedHashmap *sr_i * maximum allowed number of VFs from the sriov_totalvfs sysattr. Note that the sysattr * currently exists only for PCI drivers. Hence, ignore -ENOENT. * TODO: netdevsim provides the information in debugfs. */ - r = sd_device_get_sysattr_value(device, "device/sriov_totalvfs", &str); + uint32_t max_num_vfs; + r = device_get_sysattr_u32(device, "device/sriov_totalvfs", &max_num_vfs); if (r >= 0) { - uint32_t max_num_vfs; - - r = safe_atou32(str, &max_num_vfs); - if (r < 0) - return log_device_debug_errno(device, r, "Failed to parse device/sriov_totalvfs sysfs attribute '%s': %m", str); - if (num_vfs > max_num_vfs) return log_device_debug_errno(device, SYNTHETIC_ERRNO(ERANGE), "Specified number of virtual functions is out of range. " 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..b783cb80b78ae 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( @@ -372,6 +372,7 @@ int nsresource_add_netif_veth( static const sd_json_dispatch_field dispatch_table[] = { { "hostInterfaceName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(InterfaceParams, host_interface_name), SD_JSON_MANDATORY }, { "namespaceInterfaceName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(InterfaceParams, namespace_interface_name), SD_JSON_MANDATORY }, + {} }; _cleanup_(interface_params_done) InterfaceParams p = {}; @@ -437,6 +438,7 @@ int nsresource_add_netif_tap( static const sd_json_dispatch_field dispatch_table[] = { { "hostInterfaceName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(InterfaceParams, host_interface_name), SD_JSON_MANDATORY }, { "interfaceFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(InterfaceParams, interface_fd_index), SD_JSON_MANDATORY }, + {} }; _cleanup_(interface_params_done) InterfaceParams p = {}; diff --git a/src/shared/nsresource.h b/src/shared/nsresource.h index 5633fd9bf35bc..c26dd4f8a553f 100644 --- a/src/shared/nsresource.h +++ b/src/shared/nsresource.h @@ -2,9 +2,10 @@ #pragma once #include "shared-forward.h" +#include "user-util.h" /* Helpful constants for the only numbers of UIDs that can currently be allocated */ -#define NSRESOURCE_UIDS_64K 0x10000U +#define NSRESOURCE_UIDS_64K USERNS_RANGE_SIZE #define NSRESOURCE_UIDS_1 1U int nsresource_connect(sd_varlink **ret); diff --git a/src/shared/numa-util.c b/src/shared/numa-util.c index c011c4381d422..af8770106e365 100644 --- a/src/shared/numa-util.c +++ b/src/shared/numa-util.c @@ -26,7 +26,7 @@ bool numa_policy_is_valid(const NUMAPolicy *policy) { if (policy->nodes.set && numa_policy_get_type(policy) == MPOL_PREFERRED && - CPU_COUNT_S(policy->nodes.allocated, policy->nodes.set) != 1) + cpu_set_count(&policy->nodes) != 1) return false; return true; @@ -73,7 +73,11 @@ int apply_numa_policy(const NUMAPolicy *policy) { assert(policy); if (get_mempolicy(NULL, NULL, 0, NULL, 0) < 0 && errno == ENOSYS) - return -EOPNOTSUPP; + /* NUMA syscall interface not available (kernel compiled without NUMA support). + * Return -ENOSYS so callers can distinguish this from -EOPNOTSUPP, which we + * return below when the syscall interface exists but the requested policy is + * not supported by this kernel version. */ + return -ENOSYS; if (!numa_policy_is_valid(policy)) return -EINVAL; @@ -83,12 +87,34 @@ int apply_numa_policy(const NUMAPolicy *policy) { return r; r = set_mempolicy(numa_policy_get_type(policy), nodes, maxnode); - if (r < 0) + if (r < 0) { + // FIXME: This compatibility code path shall be removed once kernel 6.9 + // becomes the new minimal baseline (MPOL_WEIGHTED_INTERLEAVE). + if (errno == EINVAL && IN_SET(numa_policy_get_type(policy), + MPOL_PREFERRED_MANY, MPOL_WEIGHTED_INTERLEAVE)) + return -EOPNOTSUPP; return -errno; + } return 0; } +int numa_node_get_cpus(size_t node, CPUSet *ret) { + char p[STRLEN("/sys/devices/system/node/node//cpulist") + DECIMAL_STR_MAX(size_t) + 1]; + _cleanup_free_ char *cpulist = NULL; + int r; + + assert(ret); + + xsprintf(p, "/sys/devices/system/node/node%zu/cpulist", node); + + r = read_virtual_file(p, SIZE_MAX, &cpulist, /* ret_size= */ NULL); + if (r < 0) + return r; + + return parse_cpu_set(cpulist, ret); +} + int numa_to_cpu_set(const NUMAPolicy *policy, CPUSet *ret) { _cleanup_(cpu_set_done) CPUSet s = {}; int r; @@ -97,20 +123,11 @@ int numa_to_cpu_set(const NUMAPolicy *policy, CPUSet *ret) { assert(ret); for (size_t i = 0; i < policy->nodes.allocated * 8; i++) { - _cleanup_free_ char *l = NULL; - char p[STRLEN("/sys/devices/system/node/node//cpulist") + DECIMAL_STR_MAX(size_t) + 1]; - if (!CPU_ISSET_S(i, policy->nodes.allocated, policy->nodes.set)) continue; - xsprintf(p, "/sys/devices/system/node/node%zu/cpulist", i); - - r = read_one_line_file(p, &l); - if (r < 0) - return r; - _cleanup_(cpu_set_done) CPUSet part = {}; - r = parse_cpu_set(l, &part); + r = numa_node_get_cpus(i, &part); if (r < 0) return r; @@ -154,6 +171,63 @@ static int numa_max_node(void) { return max_node; } +int numa_get_node_from_cpu(unsigned cpu, unsigned *ret) { + _cleanup_closedir_ DIR *d = NULL; + int r; + + assert(ret); + + d = opendir("/sys/devices/system/node"); + if (!d) + return -errno; + + FOREACH_DIRENT(de, d, break) { + char p[STRLEN("/sys/devices/system/node/node/cpulist") + DECIMAL_STR_MAX(unsigned) + 1]; + _cleanup_(cpu_set_done) CPUSet cpus = {}; + _cleanup_free_ char *cpulist = NULL; + const char *n; + unsigned node; + + if (de->d_type != DT_DIR) + continue; + + n = startswith(de->d_name, "node"); + if (!n) + continue; + + r = safe_atou(n, &node); + if (r < 0) { + log_debug_errno(r, "Failed to parse node number %s to unsigned, ignoring: %m", n); + continue; + } + + xsprintf(p, "/sys/devices/system/node/node%u/cpulist", node); + + r = read_virtual_file(p, SIZE_MAX, &cpulist, /* ret_size= */ NULL); + if (r < 0) { + log_debug_errno(r, "Failed to read %s, ignoring: %m", p); + continue; + } + + r = parse_cpu_set(cpulist, &cpus); + if (r < 0) { + log_debug_errno(r, "Failed to parse cpu set %s, ignoring: %m", cpulist); + continue; + } + + if (CPU_ISSET_S(cpu, cpus.allocated, cpus.set)) { + *ret = node; + return 0; + } + } + + /* CPU not found in any NUMA node, assume node 0 */ + log_debug("CPU %u not found in any NUMA node, assuming node 0.", cpu); + *ret = 0; + + return 0; +} + int numa_mask_add_all(CPUSet *mask) { int m; @@ -177,11 +251,13 @@ int numa_mask_add_all(CPUSet *mask) { } static const char* const mpol_table[] = { - [MPOL_DEFAULT] = "default", - [MPOL_PREFERRED] = "preferred", - [MPOL_BIND] = "bind", - [MPOL_INTERLEAVE] = "interleave", - [MPOL_LOCAL] = "local", + [MPOL_DEFAULT] = "default", + [MPOL_PREFERRED] = "preferred", + [MPOL_BIND] = "bind", + [MPOL_INTERLEAVE] = "interleave", + [MPOL_LOCAL] = "local", + [MPOL_PREFERRED_MANY] = "preferred-many", + [MPOL_WEIGHTED_INTERLEAVE] = "weighted-interleave", }; DEFINE_STRING_TABLE_LOOKUP(mpol, int); diff --git a/src/shared/numa-util.h b/src/shared/numa-util.h index c684dea803eca..8b9a81852f769 100644 --- a/src/shared/numa-util.h +++ b/src/shared/numa-util.h @@ -7,7 +7,7 @@ #include "shared-forward.h" static inline bool mpol_is_valid(int t) { - return t >= MPOL_DEFAULT && t <= MPOL_LOCAL; + return t >= MPOL_DEFAULT && t <= MPOL_WEIGHTED_INTERLEAVE; } typedef struct NUMAPolicy { @@ -29,8 +29,11 @@ static inline void numa_policy_reset(NUMAPolicy *p) { } int apply_numa_policy(const NUMAPolicy *policy); +int numa_node_get_cpus(size_t node, CPUSet *ret); int numa_to_cpu_set(const NUMAPolicy *policy, CPUSet *ret); +int numa_get_node_from_cpu(unsigned cpu, unsigned *ret); + int numa_mask_add_all(CPUSet *mask); DECLARE_STRING_TABLE_LOOKUP(mpol, int); diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c deleted file mode 100644 index 9e0b9987463d9..0000000000000 --- a/src/shared/openssl-util.c +++ /dev/null @@ -1,1652 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "alloc-util.h" -#include "ask-password-api.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 - -# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) -# include -DISABLE_WARNING_DEPRECATED_DECLARATIONS; -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ENGINE*, ENGINE_free, NULL); -REENABLE_WARNING; -# endif - -#ifndef OPENSSL_NO_UI_CONSOLE -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UI_METHOD*, UI_destroy_method, NULL); -#endif - -/* 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). */ -#define log_openssl_errors(fmt, ...) _log_openssl_errors(UNIQ, fmt, ##__VA_ARGS__) -#define _log_openssl_errors(u, fmt, ...) \ - ({ \ - size_t UNIQ_T(MAX, u) = 512 /* arbitrary, but openssl doc states it must be >= 256 */; \ - _cleanup_free_ char *UNIQ_T(BUF, u) = malloc(UNIQ_T(MAX, u)); \ - !UNIQ_T(BUF, u) \ - ? log_oom_debug() \ - : __log_openssl_errors(u, UNIQ_T(BUF, u), UNIQ_T(MAX, u), fmt, ##__VA_ARGS__) \ - ?: log_debug_errno(SYNTHETIC_ERRNO(EIO), fmt ": No OpenSSL errors.", ##__VA_ARGS__); \ - }) -#define __log_openssl_errors(u, buf, max, fmt, ...) \ - ({ \ - int UNIQ_T(R, u) = 0; \ - for (;;) { \ - unsigned long UNIQ_T(E, u) = ERR_get_error(); \ - if (UNIQ_T(E, u) == 0) \ - break; \ - 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) { - assert(pem); - assert(ret); - - if (pem_size == SIZE_MAX) - pem_size = strlen(pem); - - _cleanup_fclose_ FILE *f = NULL; - f = fmemopen((void*) pem, pem_size, "r"); - 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); - if (!pkey) - return log_openssl_errors("Failed to parse PEM"); - - *ret = TAKE_PTR(pkey); - return 0; -} - -int openssl_pubkey_to_pem(EVP_PKEY *pkey, char **ret) { - assert(pkey); - assert(ret); - - _cleanup_(memstream_done) MemStream m = {}; - FILE *f = memstream_init(&m); - if (!f) - return -ENOMEM; - - if (PEM_write_PUBKEY(f, pkey) <= 0) - return -EIO; - - return memstream_finalize(&m, ret, /* ret_size= */ NULL); -} - -/* Returns the number of bytes generated by the specified digest algorithm. This can be used only for - * fixed-size algorithms, e.g. md5, sha1, sha256, etc. Do not use this for variable-sized digest algorithms, - * 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) { - assert(digest_alg); - assert(ret_digest_size); - - _cleanup_(EVP_MD_freep) EVP_MD *md = 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); - if (digest_size == 0) - return log_openssl_errors("Failed to get Digest size"); - - *ret_digest_size = digest_size; - - return 0; -} - -/* Calculate the digest hash value for the provided data, using the specified digest algorithm. Returns 0 on - * success, -EOPNOTSUPP if the digest algorithm is not supported, or < 0 for any other error. */ -int openssl_digest_many( - const char *digest_alg, - const struct iovec data[], - size_t n_data, - void **ret_digest, - size_t *ret_digest_size) { - - int r; - - assert(digest_alg); - 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); - 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(); - if (!ctx) - return log_openssl_errors("Failed to create new EVP_MD_CTX"); - - if (!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)) - return log_openssl_errors("Failed to update Digest"); - - size_t digest_size; - r = openssl_digest_size(digest_alg, &digest_size); - if (r < 0) - return r; - - _cleanup_free_ void *buf = malloc(digest_size); - if (!buf) - return log_oom_debug(); - - unsigned size; - if (!EVP_DigestFinal_ex(ctx, buf, &size)) - return log_openssl_errors("Failed to finalize Digest"); - - assert(size == digest_size); - - *ret_digest = TAKE_PTR(buf); - if (ret_digest_size) - *ret_digest_size = size; - - return 0; -} - -/* Calculate the HMAC digest hash value for the provided data, using the provided key and specified digest - * algorithm. Returns 0 on success, -EOPNOTSUPP if the digest algorithm is not supported, or < 0 for any - * other error. */ -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) { - - 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); - 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); - 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); - 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(); - 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)) - 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); - if (!params) - return log_openssl_errors("Failed to build HMAC OSSL_PARAM"); - - if (!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)) - return log_openssl_errors("Failed to update HMAC"); - - size_t digest_size = EVP_MAC_CTX_get_mac_size(ctx); - if (digest_size == 0) - return log_openssl_errors("Failed to get HMAC digest size"); - - _cleanup_free_ void *buf = malloc(digest_size); - if (!buf) - return log_oom_debug(); - - size_t size; - if (!EVP_MAC_final(ctx, buf, &size, digest_size)) - return log_openssl_errors("Failed to finalize HMAC"); - - assert(size == digest_size); - - *ret_digest = TAKE_PTR(buf); - if (ret_digest_size) - *ret_digest_size = size; - - return 0; -} - -/* Symmetric Cipher encryption using the alg-bits-mode cipher, e.g. AES-128-CFB. The key is required and must - * be at least the minimum required key length for the cipher. The IV is optional but, if provided, it must - * be at least the minimum iv length for the cipher. If no IV is provided and the cipher requires one, a - * buffer of zeroes is used. Returns 0 on success, -EOPNOTSUPP if the cipher algorithm is not supported, or < - * 0 on any other error. */ -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) { - - assert(alg); - assert(bits > 0); - assert(mode); - assert(key); - assert(iv || iv_size == 0); - assert(data || n_data == 0); - assert(ret); - assert(ret_size); - - _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); - 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(); - 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); - 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); - assert(cipher_iv_length >= 0); - _cleanup_free_ void *zero_iv = NULL; - if (iv_size == 0) { - zero_iv = malloc0(cipher_iv_length); - if (!zero_iv) - return log_oom_debug(); - - iv = zero_iv; - iv_size = (size_t) cipher_iv_length; - } - if ((size_t) cipher_iv_length > iv_size) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "Not enough IV bytes provided, require %d", cipher_iv_length); - - if (!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); - assert(cipher_block_size > 0); - - _cleanup_free_ uint8_t *buf = NULL; - size_t size = 0; - - for (size_t i = 0; i < n_data; i++) { - /* Cipher may produce (up to) input length + cipher block size of output. */ - if (!GREEDY_REALLOC(buf, size + data[i].iov_len + cipher_block_size)) - return log_oom_debug(); - - int update_size; - if (!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; - } - - if (!GREEDY_REALLOC(buf, size + cipher_block_size)) - return log_oom_debug(); - - int final_size; - if (!EVP_EncryptFinal_ex(ctx, &buf[size], &final_size)) - return log_openssl_errors("Failed to finalize Cipher."); - - *ret = TAKE_PTR(buf); - *ret_size = size + final_size; - - return 0; -} - -/* Perform Single-Step (aka "Concat") KDF. Currently, this only supports using the digest for the auxiliary - * function. The derive_size parameter specifies how many bytes are derived. - * - * For more details see: https://www.openssl.org/docs/manmaster/man7/EVP_KDF-SS.html */ -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) { - - assert(digest); - assert(key); - assert(derive_size > 0); - assert(ret); - - _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = 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); - 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(); - if (!bld) - return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); - - _cleanup_free_ void *buf = malloc(derive_size); - if (!buf) - return log_oom_debug(); - - if (!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)) - 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)) - 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)) - 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); - if (!params) - return log_openssl_errors("Failed to build KDF-SS OSSL_PARAM"); - - if (EVP_KDF_derive(ctx, buf, derive_size, params) <= 0) - return log_openssl_errors("OpenSSL KDF-SS derive failed"); - - *ret = TAKE_PTR(buf); - - return 0; -} - -/* Perform Key-Based HMAC KDF. The mode must be "COUNTER" or "FEEDBACK". The parameter naming is from the - * OpenSSL api, and maps to SP800-108 naming as "...key, salt, info, and seed correspond to KI, Label, - * Context, and IV (respectively)...". The derive_size parameter specifies how many bytes are derived. - * - * For more details see: https://www.openssl.org/docs/manmaster/man7/EVP_KDF-KB.html */ -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) { - - assert(mode); - assert(strcaseeq(mode, "COUNTER") || strcaseeq(mode, "FEEDBACK")); - assert(digest); - assert(key || key_size == 0); - assert(salt || salt_size == 0); - assert(info || info_size == 0); - assert(seed || seed_size == 0); - assert(derive_size > 0); - assert(ret); - - _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = 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); - 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(); - 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)) - 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)) - 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)) - 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)) - 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)) - 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)) - 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)) - 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); - if (!params) - return log_openssl_errors("Failed to build KDF-KB OSSL_PARAM"); - - _cleanup_free_ void *buf = malloc(derive_size); - if (!buf) - return log_oom_debug(); - - if (EVP_KDF_derive(ctx, buf, derive_size, params) <= 0) - return log_openssl_errors("OpenSSL KDF-KB derive failed"); - - *ret = TAKE_PTR(buf); - - return 0; -} - -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) { - - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = NULL; - _cleanup_free_ void *b = NULL; - size_t l; - - ctx = 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) - return log_openssl_errors("Failed to initialize public key context"); - - if (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) - 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) - return log_openssl_errors("Failed to determine encrypted key size"); - - *ret_encrypt_key = TAKE_PTR(b); - *ret_encrypt_key_size = l; - return 0; -} - -/* Encrypt the key data using RSA-OAEP with the provided label and specified digest algorithm. Returns 0 on - * success, -EOPNOTSUPP if the digest algorithm is not supported, or < 0 for any other error. */ -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) { - - assert(pkey); - assert(digest_alg); - assert(label); - assert(decrypted_key); - assert(decrypted_key_size > 0); - assert(ret_encrypt_key); - assert(ret_encrypt_key_size); - - _cleanup_(EVP_MD_freep) EVP_MD *md = 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); - if (!ctx) - return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); - - if (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) - return log_openssl_errors("Failed to configure RSA-OAEP padding"); - - if (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) - 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) - 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) - return log_openssl_errors("Failed to RSA-OAEP encrypt"); - - *ret_encrypt_key = TAKE_PTR(buf); - *ret_encrypt_key_size = size; - - return 0; -} - -int rsa_pkey_to_suitable_key_size( - EVP_PKEY *pkey, - size_t *ret_suitable_key_size) { - - size_t suitable_key_size; - int bits; - - assert(pkey); - assert(ret_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) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "X.509 certificate does not refer to RSA key."); - - bits = EVP_PKEY_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 - * generate a random key half the size of the RSA length */ - suitable_key_size = bits / 8 / 2; - - if (suitable_key_size < 1) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Uh, RSA key size too short?"); - - *ret_suitable_key_size = suitable_key_size; - return 0; -} - -/* Generate RSA public key from provided "n" and "e" values. Numbers "n" and "e" must be provided here - * 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; - - assert(n); - assert(n_size != 0); - assert(e); - assert(e_size != 0); - assert(ret); - - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = 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) - 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); -#else - _cleanup_free_ void *native_n = memdup_reverse(n, n_size); - if (!native_n) - return log_oom_debug(); - - _cleanup_free_ void *native_e = memdup_reverse(e, 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); -#endif - params[2] = OSSL_PARAM_construct_end(); - - if (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); - - return 0; -} - -/* Get the "n" and "e" values from the pkey. The values are returned in "bin" format, i.e. BN_bn2bin(). */ -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) { - - assert(pkey); - assert(ret_n); - assert(ret_n_size); - assert(ret_e); - assert(ret_e_size); - - _cleanup_(BN_freep) BIGNUM *bn_n = NULL; - if (!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)) - 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); - _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); - - *ret_n = TAKE_PTR(n); - *ret_n_size = n_size; - *ret_e = TAKE_PTR(e); - *ret_e_size = e_size; - - return 0; -} - -/* Generate ECC public key from provided curve ID and x/y points. */ -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) { - - assert(x); - assert(y); - assert(ret); - - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = 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); - if (!bn_x) - return log_openssl_errors("Failed to create BIGNUM x"); - - _cleanup_(BN_freep) BIGNUM *bn_y = 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); - 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); - 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)) - return log_openssl_errors("Failed to set ECC coordinates"); - - if (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(); - 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)) - 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); - 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)) - 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); - 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) - return log_openssl_errors("Failed to create ECC EVP_PKEY"); - - *ret = TAKE_PTR(pkey); - return 0; -} - -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) { - - _cleanup_(BN_freep) BIGNUM *bn_x = NULL, *bn_y = NULL; - int curve_id; - - assert(pkey); - - size_t name_size; - if (!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)) - return log_openssl_errors("Failed to get ECC group name"); - - curve_id = 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)) - 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)) - 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); - _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); - - if (ret_curve_id) - *ret_curve_id = curve_id; - if (ret_x) - *ret_x = TAKE_PTR(x); - if (ret_x_size) - *ret_x_size = x_size; - if (ret_y) - *ret_y = TAKE_PTR(y); - if (ret_y_size) - *ret_y_size = y_size; - - return 0; -} - -/* Generate a new ECC key for the specified ECC curve id. */ -int ecc_pkey_new(int curve_id, EVP_PKEY **ret) { - assert(ret); - - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = 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) - return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); - - if (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) - return log_openssl_errors("Failed to generate ECC key"); - - *ret = TAKE_PTR(pkey); - - return 0; -} - -/* Perform ECDH to derive an ECC shared secret between the provided private key and public peer key. For two - * keys, this will result in the same shared secret in either direction; ECDH using Alice's private key and - * Bob's public (peer) key will result in the same shared secret as ECDH using Bob's private key and Alice's - * public (peer) key. On success, this returns 0 and provides the shared secret; otherwise this returns an - * error. */ -int ecc_ecdh(const EVP_PKEY *private_pkey, - const EVP_PKEY *peer_pkey, - void **ret_shared_secret, - size_t *ret_shared_secret_size) { - - 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); - if (!ctx) - return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); - - if (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) - 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) - 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) - return log_openssl_errors("Failed to derive ECC shared secret"); - - *ret_shared_secret = TAKE_PTR(shared_secret); - *ret_shared_secret_size = shared_secret_size; - - return 0; -} - -int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size) { - _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* m = NULL; - _cleanup_free_ void *d = NULL, *h = NULL; - int sz, lsz, msz; - unsigned umsz; - unsigned char *dd; - - /* Calculates a message digest of the DER encoded public key */ - - assert(pk); - assert(md); - assert(ret); - assert(ret_size); - - sz = i2d_PublicKey(pk, NULL); - if (sz < 0) - return log_openssl_errors("Unable to convert public key to DER format"); - - dd = d = malloc(sz); - if (!d) - return log_oom_debug(); - - lsz = i2d_PublicKey(pk, &dd); - if (lsz < 0) - return log_openssl_errors("Unable to convert public key to DER format"); - - m = 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 (EVP_DigestUpdate(m, d, lsz) != 1) - return log_openssl_errors("Failed to run %s context", EVP_MD_name(md)); - - msz = EVP_MD_size(md); - assert(msz > 0); - - h = malloc(msz); - if (!h) - return log_oom_debug(); - - umsz = msz; - if (EVP_DigestFinal_ex(m, h, &umsz) != 1) - return log_openssl_errors("Failed to finalize hash context"); - - assert(umsz == (unsigned) msz); - - *ret = TAKE_PTR(h); - *ret_size = msz; - - return 0; -} - -int digest_and_sign( - const EVP_MD *md, - EVP_PKEY *privkey, - const void *data, size_t size, - void **ret, size_t *ret_size) { - - int r; - - assert(privkey); - assert(ret); - assert(ret_size); - - if (size == 0) - data = ""; /* make sure to pass a valid pointer to OpenSSL */ - else { - assert(data); - - if (size == SIZE_MAX) /* If SIZE_MAX input is a string whose size we determine automatically */ - size = strlen(data); - } - - _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = 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) { - /* 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; - 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) - 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) - return log_openssl_errors("Failed to sign data"); - - *ret = TAKE_PTR(sig); - *ret_size = ss; - return 0; -} - -int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, const char *hash_algorithm, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si) { - assert(certificate); - assert(ret_p7); - - /* This function sets up a new PKCS7 signing context. If a private key is provided, the context is - * set up for "in-band" signing with PKCS7_dataFinal(). If a private key is not provided, the context - * is set up for "out-of-band" signing, meaning the signature has to be provided by the user and - * 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(); - if (!p7) - return log_oom(); - - if (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)); - - if (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)); - - if (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)); - - int x509_pknid = 0; - if (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)); - - const EVP_MD *md = 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(); - if (!si) - return log_oom(); - - if (private_key) { - if (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)); - } else { - if (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)); - - if (X509_NAME_set(&si->issuer_and_serial->issuer, 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)); - - ASN1_INTEGER_free(si->issuer_and_serial->serial); - si->issuer_and_serial->serial = ASN1_INTEGER_dup(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)); - - if (X509_ALGOR_set0(si->digest_alg, OBJ_nid2obj(EVP_MD_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)); - - if (X509_ALGOR_set0(si->digest_enc_alg, 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)); - } - - if (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)); - - *ret_p7 = TAKE_PTR(p7); - if (ret_si) - /* We do not pass ownership here, 'si' object remains owned by 'p7' object. */ - *ret_si = si; - - TAKE_PTR(si); - - return 0; -} - -int string_hashsum( - const char *s, - size_t len, - const char *md_algorithm, - char **ret) { - - _cleanup_free_ void *hash = NULL; - size_t hash_size; - _cleanup_free_ char *enc = NULL; - int r; - - assert(s || len == 0); - assert(md_algorithm); - assert(ret); - - r = openssl_digest(md_algorithm, s, len, &hash, &hash_size); - if (r < 0) - return r; - - enc = hexmem(hash, hash_size); - if (!enc) - return -ENOMEM; - - *ret = TAKE_PTR(enc); - return 0; -} - -static int ecc_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) { - - _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_new = NULL; - _cleanup_(erase_and_freep) void *decrypted_key = NULL; - _cleanup_free_ unsigned char *saved_key = NULL; - size_t decrypted_key_size, saved_key_size; - int r; - - _cleanup_free_ char *curve_name = NULL; - size_t len = 0; - - if (EVP_PKEY_get_group_name(pkey, NULL, 0, &len) != 1 || len == 0) - return log_openssl_errors("Failed to determine PKEY group name length"); - - len++; - curve_name = new(char, len); - if (!curve_name) - return log_oom_debug(); - - if (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); - if (r < 0) - return log_debug_errno(r, "Failed to generate a new EC keypair: %m"); - - r = ecc_ecdh(pkey_new, pkey, &decrypted_key, &decrypted_key_size); - if (r < 0) - return log_debug_errno(r, "Failed to derive shared secret: %m"); - - /* 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); - if (saved_key_size == 0) - return log_openssl_errors("Failed to convert the generated public key to SEC1 format"); - - *ret_decrypted_key = TAKE_PTR(decrypted_key); - *ret_decrypted_key_size = decrypted_key_size; - *ret_saved_key = TAKE_PTR(saved_key); - *ret_saved_key_size = saved_key_size; - return 0; -} - -static int rsa_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) { - - _cleanup_(erase_and_freep) void *decrypted_key = NULL; - _cleanup_free_ void *saved_key = NULL; - size_t decrypted_key_size, saved_key_size; - int r; - - 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."); - - log_debug("Generating %zu bytes random key.", decrypted_key_size); - - decrypted_key = malloc(decrypted_key_size); - if (!decrypted_key) - return log_oom_debug(); - - r = crypto_random_bytes(decrypted_key, decrypted_key_size); - if (r < 0) - return log_debug_errno(r, "Failed to generate random key: %m"); - - r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &saved_key, &saved_key_size); - if (r < 0) - return log_debug_errno(r, "Failed to encrypt random key: %m"); - - *ret_decrypted_key = TAKE_PTR(decrypted_key); - *ret_decrypted_key_size = decrypted_key_size; - *ret_saved_key = TAKE_PTR(saved_key); - *ret_saved_key_size = saved_key_size; - return 0; -} - -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) { - - 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); - switch (type) { - - case EVP_PKEY_RSA: - return rsa_pkey_generate_volume_keys(pkey, ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size); - - case EVP_PKEY_EC: - return ecc_pkey_generate_volume_keys(pkey, ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size); - - case NID_undef: - 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)); - } -} - -static int load_key_from_provider( - const char *provider, - const char *private_key_uri, - UI_METHOD *ui_method, - EVP_PKEY **ret) { - - assert(provider); - assert(private_key_uri); - assert(ret); - - /* 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)) - return log_openssl_errors("Failed to load OpenSSL provider '%s'", provider); - if (!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( - private_key_uri, - ui_method, - /* ui_data= */ NULL, - /* post_process= */ NULL, - /* post_process_data= */ NULL); - 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) - return log_openssl_errors("Failed to filter store by private keys"); - - _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = 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); - if (!private_key) - return log_openssl_errors("Failed to load private key via '%s'", private_key_uri); - - *ret = TAKE_PTR(private_key); - - return 0; -} - -static int load_key_from_engine(const char *engine, const char *private_key_uri, UI_METHOD *ui_method, EVP_PKEY **ret) { - assert(engine); - assert(private_key_uri); - assert(ret); - -#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) - DISABLE_WARNING_DEPRECATED_DECLARATIONS; - _cleanup_(ENGINE_freep) ENGINE *e = ENGINE_by_id(engine); - if (!e) - return log_openssl_errors("Failed to load signing engine '%s'", engine); - - if (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); - if (!private_key) - return log_openssl_errors("Failed to load private key from '%s'", private_key_uri); - REENABLE_WARNING; - - *ret = TAKE_PTR(private_key); - - return 0; -#else - return -EOPNOTSUPP; -#endif -} - -#ifndef OPENSSL_NO_UI_CONSOLE -static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) { - int r; - - switch(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); - if (!req) - return (UI_method_get_reader(UI_OpenSSL()))(ui, uis); - - req->message = 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); - if (r < 0) { - log_error_errno(r, "Failed to query for PIN: %m"); - return 0; - } - - if (strv_length(l) != 1) { - log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected only a single password/pin."); - return 0; - } - - if (UI_set_result(ui, uis, *l) != 0) { - log_openssl_errors("Failed to set user interface result"); - return 0; - } - - return 1; - } - default: - return (UI_method_get_reader(UI_OpenSSL()))(ui, uis); - } -} -#endif - -static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) { - _cleanup_(erase_and_freep) char *rawkey = NULL; - _cleanup_(BIO_freep) BIO *kb = NULL; - _cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL; - size_t rawkeysz; - int r; - - assert(path); - assert(ret); - - 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, - NULL, - &rawkey, &rawkeysz); - if (r < 0) - return log_debug_errno(r, "Failed to read key file '%s': %m", path); - - kb = BIO_new_mem_buf(rawkey, rawkeysz); - if (!kb) - return log_oom_debug(); - - pk = 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)); - - if (ret) - *ret = TAKE_PTR(pk); - - return 0; -} - -static int openssl_ask_password_ui_new(const AskPasswordRequest *request, OpenSSLAskPasswordUI **ret) { - assert(ret); - -#ifndef OPENSSL_NO_UI_CONSOLE - _cleanup_(UI_destroy_methodp) UI_METHOD *method = 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) - return log_openssl_errors("Failed to set openssl user interface reader"); - - OpenSSLAskPasswordUI *ui = new(OpenSSLAskPasswordUI, 1); - if (!ui) - return log_oom_debug(); - - *ui = (OpenSSLAskPasswordUI) { - .method = TAKE_PTR(method), - .request = *request, - }; - - UI_set_default_method(ui->method); - - if (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); - return 0; -#else - return -EOPNOTSUPP; -#endif -} - -static int load_x509_certificate_from_file(const char *path, X509 **ret) { - _cleanup_free_ char *rawcert = NULL; - _cleanup_(X509_freep) X509 *cert = NULL; - _cleanup_(BIO_freep) BIO *cb = NULL; - size_t rawcertsz; - int r; - - assert(path); - assert(ret); - - r = read_full_file_full( - AT_FDCWD, path, UINT64_MAX, SIZE_MAX, - READ_FULL_FILE_CONNECT_SOCKET, - NULL, - &rawcert, &rawcertsz); - if (r < 0) - return log_debug_errno(r, "Failed to read certificate file '%s': %m", path); - - cb = BIO_new_mem_buf(rawcert, rawcertsz); - if (!cb) - return log_oom_debug(); - - cert = 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)); - - if (ret) - *ret = TAKE_PTR(cert); - - return 0; -} - -static int load_x509_certificate_from_provider(const char *provider, const char *certificate_uri, X509 **ret) { - assert(provider); - assert(certificate_uri); - assert(ret); - - /* 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)) - return log_openssl_errors("Failed to load OpenSSL provider '%s'", provider); - if (!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( - certificate_uri, - /* ui_method= */ NULL, - /* ui_method= */ NULL, - /* post_process= */ NULL, - /* post_process_data= */ NULL); - 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) - 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); - 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); - if (!cert) - return log_openssl_errors("Failed to load certificate via '%s'", certificate_uri); - - *ret = TAKE_PTR(cert); - - return 0; -} - -OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) { - if (!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); -#endif - return mfree(ui); -} - -int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) { - _cleanup_free_ uint8_t *der = NULL; - int dersz; - - assert(cert); - - dersz = i2d_X509(cert, &der); - if (dersz < 0) - return log_openssl_errors("Unable to convert PEM certificate to DER format"); - - sha256_direct(der, dersz, buffer); - return 0; -} - -int openssl_load_x509_certificate( - CertificateSourceType certificate_source_type, - const char *certificate_source, - const char *certificate, - X509 **ret) { - - int r; - - assert(certificate); - - switch (certificate_source_type) { - - case OPENSSL_CERTIFICATE_SOURCE_FILE: - r = load_x509_certificate_from_file(certificate, ret); - break; - case OPENSSL_CERTIFICATE_SOURCE_PROVIDER: - r = load_x509_certificate_from_provider(certificate_source, certificate, ret); - break; - default: - assert_not_reached(); - } - if (r < 0) - return log_debug_errno( - r, - "Failed to load certificate '%s' from OpenSSL certificate source %s: %m", - certificate, - certificate_source); - - return 0; -} - -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 r; - - /* The caller must keep the OpenSSLAskPasswordUI object alive as long as the EVP_PKEY object so that - * the user can enter any needed hardware token pin to unlock the private key when needed. */ - - assert(private_key); - assert(request); - assert(ret_private_key); - assert(ret_user_interface); - - if (private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { - r = openssl_load_private_key_from_file(private_key, ret_private_key); - if (r < 0) - return r; - - *ret_user_interface = NULL; - } else { - _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; - r = openssl_ask_password_ui_new(request, &ui); - if (r < 0) - return log_debug_errno(r, "Failed to allocate ask-password user interface: %m"); - - UI_METHOD *ui_method = NULL; -#ifndef OPENSSL_NO_UI_CONSOLE - ui_method = ui->method; -#endif - - switch (private_key_source_type) { - - case OPENSSL_KEY_SOURCE_ENGINE: - r = load_key_from_engine(private_key_source, private_key, ui_method, ret_private_key); - break; - case OPENSSL_KEY_SOURCE_PROVIDER: - r = load_key_from_provider(private_key_source, private_key, ui_method, ret_private_key); - break; - default: - assert_not_reached(); - } - if (r < 0) - return log_debug_errno( - r, - "Failed to load key '%s' from OpenSSL private key source %s: %m", - private_key, - private_key_source); - - *ret_user_interface = TAKE_PTR(ui); - } - - return 0; -} - -int openssl_extract_public_key(EVP_PKEY *private_key, EVP_PKEY **ret) { - int r; - - assert(private_key); - assert(ret); - - _cleanup_(memstream_done) MemStream m = {}; - FILE *tf = memstream_init(&m); - if (!tf) - return -ENOMEM; - - if (i2d_PUBKEY_fp(tf, private_key) != 1) - return -EIO; - - _cleanup_(erase_and_freep) char *buf = NULL; - size_t len; - r = memstream_finalize(&m, &buf, &len); - if (r < 0) - return r; - - const unsigned char *t = (const unsigned char*) buf; - if (!d2i_PUBKEY(ret, &t, len)) - return -EIO; - - return 0; -} -#endif - -int parse_openssl_certificate_source_argument( - const char *argument, - char **certificate_source, - CertificateSourceType *certificate_source_type) { - - CertificateSourceType type; - const char *e = NULL; - int r; - - assert(argument); - assert(certificate_source); - assert(certificate_source_type); - - if (streq(argument, "file")) - type = OPENSSL_CERTIFICATE_SOURCE_FILE; - else if ((e = startswith(argument, "provider:"))) - type = OPENSSL_CERTIFICATE_SOURCE_PROVIDER; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid certificate source '%s'", argument); - - r = free_and_strdup_warn(certificate_source, e); - if (r < 0) - return r; - - *certificate_source_type = type; - - return 0; -} - -int parse_openssl_key_source_argument( - const char *argument, - char **private_key_source, - KeySourceType *private_key_source_type) { - - KeySourceType type; - const char *e = NULL; - int r; - - assert(argument); - assert(private_key_source); - assert(private_key_source_type); - - if (streq(argument, "file")) - type = OPENSSL_KEY_SOURCE_FILE; - else if ((e = startswith(argument, "engine:"))) - type = OPENSSL_KEY_SOURCE_ENGINE; - else if ((e = startswith(argument, "provider:"))) - type = OPENSSL_KEY_SOURCE_PROVIDER; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid private key source '%s'", argument); - - r = free_and_strdup_warn(private_key_source, e); - if (r < 0) - return r; - - *private_key_source_type = type; - - return 0; -} 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..1a24106fc5403 --- /dev/null +++ b/src/shared/options.c @@ -0,0 +1,537 @@ +/* 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_NAMESPACE_MARKER | + 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 the arguments in between. */ + char *saved = argv[source]; + memmove(argv + target + 1, argv + target, (source - target) * sizeof(char*)); + argv[target] = saved; +} + +static int partial_match_error( + const OptionParser *state, + 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_full_errno(LOG_ERR + state->log_level_shift, 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_full_errno(LOG_ERR + state->log_level_shift, + 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) { + + /* We define this one early, since we use goto below, and need to guarantee its initialization */ + _cleanup_free_ char *_optname = NULL; /* allocated option name */ + int r; + + assert(state); + + /* Check and initialize */ + switch (state->state) { + + case OPTION_PARSER_INIT: { + assert(state->mode >= 0 && state->mode < _OPTION_PARSER_MODE_MAX); + /* Make sure the shifted log level is between LOG_EMERG/0 and LOG_DEBUG/7. */ + assert(state->log_level_shift >= LOG_EMERG - LOG_ERR && + state->log_level_shift <= LOG_DEBUG - LOG_ERR); + + if (state->argc < 1) { + r = log_full_errno(LOG_ERR + state->log_level_shift, + SYNTHETIC_ERRNO(EUCLEAN), + "argv cannot be empty"); + goto fail; + } + + assert_se((size_t) state->argc == strv_length(state->argv)); /* Make sure argc/argv are consistent */ + + /* Figure out the right range of options */ + bool in_ns = state->namespace == NULL; /* Are we currently in the section of the array that + * forms namespace ? The first part is the + * default unnamed namespace, so if the namespace was + * not specified, we are in it. */ + if (in_ns) + state->namespace_start = options; + + const Option *opt; + + /* Verify that the option array didn't get mangled within a namespace. */ + for (opt = options; opt < options_end; opt++) + if (opt + 1 < options_end && !FLAGS_SET((opt + 1)->flags, OPTION_NAMESPACE_MARKER)) + assert_se(opt->id < (opt + 1)->id); + + for (opt = options; opt < options_end; opt++) { + bool ns_marker = FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER); + if (!in_ns) { + in_ns = ns_marker && streq(state->namespace, opt->long_code); + if (in_ns) + state->namespace_start = opt + 1; + continue; + } + if (ns_marker) + break; /* End of namespace */ + } + assert(state->namespace_start); + state->namespace_end = opt; + + state->optind = state->positional_offset = 1; + state->state = OPTION_PARSER_RUNNING; + break; + } + + case OPTION_PARSER_RUNNING: + case OPTION_PARSER_STOPPING: + break; + + case OPTION_PARSER_DONE: + goto done; + + case OPTION_PARSER_FAILED: + return log_full_errno(LOG_ERR + state->log_level_shift, + SYNTHETIC_ERRNO(ESTALE), + "Option parser failed before, refusing."); + + default: + assert_not_reached(); + } + + /* Look for the next option */ + + const Option *option = NULL; /* initialization to appease gcc 13 */ + const char *optname = NULL, *optval = NULL; + 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) + goto done; + + 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++); + goto done; + } + + /* If we are in OPTION_PARSER_STOPPING state we only wanted to read one more "--" if + * there is one, nothing else, hence it's time to say goodbye now. */ + if (state->state == OPTION_PARSER_STOPPING) + goto done; + + 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) + goto done; + + 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 = state->namespace_start;; option++) { + /* If OPTION_PARSER_RETURN_POSITIONAL_ARGS is specified, + * OPTION_POSITIONAL must be used. */ + assert(option < state->namespace_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) { + r = log_oom_full(LOG_ERR + state->log_level_shift); + goto fail; + } + + /* 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 = state->namespace_start;; option++) { + if (option >= state->namespace_end) { + if (n_partial_matches == 0) { + r = log_full_errno(LOG_ERR + state->log_level_shift, + SYNTHETIC_ERRNO(EINVAL), + "%s: unrecognized option '%s'", + program_invocation_short_name, optname); + goto fail; + } + if (n_partial_matches > 1) { + r = partial_match_error( + state, + state->namespace_start, + state->namespace_end, + optname, + n_partial_matches); + goto fail; + } + + /* 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) { + r = log_oom_full(LOG_ERR + state->log_level_shift); + goto fail; + } + optname = _optname; + + for (option = state->namespace_start;; option++) { + if (option >= state->namespace_end) { + r = log_full_errno(LOG_ERR + state->log_level_shift, + SYNTHETIC_ERRNO(EINVAL), + "%s: unrecognized option '%s'", + program_invocation_short_name, optname); + goto fail; + } + + 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)) { + r = log_full_errno(LOG_ERR + state->log_level_shift, + SYNTHETIC_ERRNO(EINVAL), + "%s: option '%s' doesn't allow an argument", + program_invocation_short_name, optname); + goto fail; + } + if (!handling_positional_arg && !optval && option_arg_required(option)) { + if (!state->argv[state->optind + 1]) { + r = log_full_errno(LOG_ERR + state->log_level_shift, + SYNTHETIC_ERRNO(EINVAL), + "%s: option '%s' requires an argument", + program_invocation_short_name, optname); + goto fail; + } + 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->state = OPTION_PARSER_STOPPING; + + state->opt = option; + state->arg = optval; + return option->id; + + done: + state->state = OPTION_PARSER_DONE; + state->opt = NULL; + state->arg = NULL; + return 0; + + fail: + /* Invalidate the object for good on the first error */ + assert(r < 0); + state->state = OPTION_PARSER_FAILED; + return r; +} + +char* option_parser_peek_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_peek_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->state == OPTION_PARSER_DONE); + 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->state == OPTION_PARSER_DONE); + assert(state->positional_offset <= state->argc); + + return state->argc - state->positional_offset; +} + +char* option_parser_get_arg(const OptionParser *state, size_t i) { + assert(state->optind > 0); + assert(state->state == OPTION_PARSER_DONE); + assert(state->positional_offset <= state->argc); + + return (size_t) (state->argc - state->positional_offset) > i ? state->argv[state->positional_offset + i] : NULL; +} + +char* option_get_synopsis(const Option *opt, const char *joiner, bool show_metavar) { + assert(opt); + assert(!(opt->flags & (OPTION_NAMESPACE_MARKER | + OPTION_GROUP_MARKER))); /* The markers should not be displayed */ + + if (opt->flags & (OPTION_HELP_ENTRY_VERBATIM | OPTION_POSITIONAL_ENTRY)) + return strdup(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(sc, + joiner, + opt->long_code ? "--" : "", + strempty(opt->long_code), + need_eq ? "=" : ""); + + bool need_quote = opt->metavar && strchr(opt->metavar, ' '); + return strjoin(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_full( + const Option options[], + const Option options_end[], + const char *namespace, + 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_ns = namespace == NULL; /* Are we currently in the section of the array that forms namespace + * ? The first part is the default unnamed namespace, so + * if the namespace was not specified, we are in it. */ + + bool in_group = group == NULL; /* Are we currently in the section of the array that forms group + * ? The first part is the default group, so if the group was + * not specified, we are in it. */ + + for (const Option *opt = options; opt < options_end; opt++) { + bool ns_marker = FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER); + if (!in_ns) { + in_ns = ns_marker && streq(namespace, opt->long_code); + continue; + } + if (ns_marker) + break; /* End of namespace */ + + 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; + + _cleanup_free_ char *s = option_get_synopsis(opt, " ", /* show_metavar= */ true); + if (!s) + return log_oom(); + + /* 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. */ + bool shift_left = opt->short_code != 0 || FLAGS_SET(opt->flags, OPTION_HELP_ENTRY_VERBATIM); + const char *prefix = shift_left ? " " : " "; + _cleanup_free_ char *t = strjoin(prefix, s); + if (!t) + return log_oom(); + + r = table_add_many(table, TABLE_STRING, t); + if (r < 0) + return table_log_add_error(r); + + _cleanup_strv_free_ char **split = strv_split(opt->help, /* separators= */ NULL); + if (!split) + return log_oom(); + + r = table_add_many(table, TABLE_STRV_WRAPPED, split); + if (r < 0) + return table_log_add_error(r); + } + + assert(!table_isempty(table)); /* The namespace or group were not found. Something is off. */ + + 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..f02eb30027f24 --- /dev/null +++ b/src/shared/options.h @@ -0,0 +1,282 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +/* Option namespace/group explanation: + * the list of options is split into namespaces, and a namespace is split into groups. + * By default, options defined in a single program are all placed in a single (unnamed) namespace + * and in a single (unnamed) group. OPTION_NAMESPACE() marks the beginning of a named namespace. + * OPTION_GROUP() marks the beginning of a named group. + * + * Note: if multiple namespaces are used, they should all be named, i.e. each separate parse_argv + * instance should have OPTION_NAMESPACE first, and then its set of OPTION()s. (This is because + * clang reorders OPTIONs coming from different functions. So an unnamed group could end up being + * merged with one of the earlier groups. It seems that reordering within a single function does + * not happen.) + * + * When groups are used, the first group may be named (with OPTION_GROUP appearing before any + * options), or it may be unnamed. Both variants should work fine. + */ + +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_NAMESPACE_MARKER = 1U << 3, /* Fake option entry to separate namespaces */ + OPTION_GROUP_MARKER = 1U << 4, /* Fake option entry to separate groups */ + OPTION_HELP_ENTRY = 1U << 5, /* Fake option entry to insert an additional help line */ + OPTION_HELP_ENTRY_VERBATIM = 1U << 6, /* Same, but use the long_code in the first column as written */ +} OptionFlags; + +/* Note: the alignment attribute must match the one applied to each variable via _alignptr_ in + * _OPTION() below. Otherwise the struct's sizeof and the actual stride between consecutive entries + * placed in the SYSTEMD_OPTIONS section would not match on architectures where the natural + * alignment of the struct is smaller than sizeof(void*) (e.g. m68k). That would cause the + * pointer-arithmetic-based iteration over the section to read from padding bytes. */ +typedef struct _alignptr_ Option { + int id; + OptionFlags flags; + char short_code; + const char *long_code; + const char *metavar; + uintptr_t data; + const char *help; +} Option; +assert_cc(sizeof(Option) % sizeof(void*) == 0); + +#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 namespace . + * The define is structured as 'case' so that it can be followed by ':' and indented appropriately. */ +#define OPTION_NAMESPACE(ns) \ + _OPTION(__COUNTER__, OPTION_NAMESPACE_MARKER, /* sc= */ 0, /* lc= */ ns, /* mv= */ NULL, /* d= */ 0u, /* h= */ NULL) + +/* 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) + +/* This can be used when custom error handling is needed. */ +#define OPTION_ERROR \ + case INT_MIN ... -1 + +#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_SYSTEM \ + OPTION_LONG("system", NULL, "Operate in system mode") + +#define OPTION_COMMON_USER \ + OPTION_LONG("user", NULL, "Operate in per-user mode") + +#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_ENTRY_TOKEN \ + OPTION_LONG("entry-token", "TOKEN", \ + "Entry token to use for this installation " \ + "(machine-id, os-id, os-image-id, auto, literal:…)") + +#define OPTION_COMMON_MAKE_ENTRY_DIRECTORY \ + OPTION_LONG("make-entry-directory", \ + "BOOL|auto", "Create $BOOT/ENTRY-TOKEN/ directory") + +#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) + +#define OPTION_COMMON_RESOLVE_NAMES \ + OPTION('N', "resolve-names", "MODE", \ + "When to resolve users and groups (early, late, or never)") + +/* 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, + + /* 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 enum OptionParserState { + OPTION_PARSER_INIT, + OPTION_PARSER_RUNNING, + OPTION_PARSER_STOPPING, /* We processed an option with OPTION_STOPS_PARSING, and will eat up one + * more "--", but nothing else. */ + OPTION_PARSER_DONE, /* Option parsing completed (could be because we reached the end, or because + * "--" was fully processed, or because we hit a terminating option). */ + OPTION_PARSER_FAILED, /* We encountered a parse error, and terminated option parsing. */ + _OPTION_PARSER_MAX, +} OptionParserState; + +typedef struct OptionParser { + /* Those four should stay first so that it's possible to initialize the struct as { argc, argv } + * or { argc, argv, mode } or { argc, argv, mode, namespace }. */ + int argc; /* The original argc. */ + char **argv; /* The argv array, possibly reordered. */ + OptionParserMode mode; + const char *namespace; /* The namespace, may be NULL. */ + int log_level_shift; /* The log level difference from the default of LOG_ERR. + * Allowed values are -3..4. + * Use 4 == LOG_DEBUG - LOG_ERR to log at debug level. */ + + const Option *namespace_start, *namespace_end; /* The range of options that are part of our namespace. */ + + OptionParserState state; + 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. */ + + /* The two variables below encompass the state of the last option_parse() call. + * Before parsing has commenced, and after it has finished, they will be NULL. */ + const Option *opt; /* … the matched option or NULL */ + const char *arg; /* … the argument or NULL */ +} OptionParser; + +int option_parse( + const Option options[], + const Option options_end[], + OptionParser *state); + +/* Iterate over options. Don't forget to handle errors (negative c)! */ +#define FOREACH_OPTION(c, state) \ + for (int c; (c = option_parse(__start_SYSTEMD_OPTIONS, __stop_SYSTEMD_OPTIONS, state)) != 0; ) + +#define FOREACH_OPTION_OR_RETURN(c, state) \ + for (int c; (c = option_parse(__start_SYSTEMD_OPTIONS, __stop_SYSTEMD_OPTIONS, state)) != 0; ) \ + if (c < 0) \ + return c; \ + else + +/* Those helpers are used *during* option parsing and allow looking at or taking the next item in + * the argv array, either an option or a positional parameter. */ +char* option_parser_peek_next_arg(const OptionParser *state); +char* option_parser_consume_next_arg(OptionParser *state); + +/* Those helpers are used *after* option parsing and return the positional arguments (and unparsed + * options in case option parsing was stopped early, e.g. via "--"). */ +char** option_parser_get_args(const OptionParser *state); +size_t option_parser_get_n_args(const OptionParser *state); +char* option_parser_get_arg(const OptionParser *state, size_t i); + +char* option_get_synopsis(const Option *opt, const char *joiner, bool show_metavar); + +int _option_parser_get_help_table_full( + const Option options[], + const Option options_end[], + const char *namespace, + const char *group, + Table **ret); +#define option_parser_get_help_table_full(namespace, group, ret) \ + _option_parser_get_help_table_full(__start_SYSTEMD_OPTIONS, __stop_SYSTEMD_OPTIONS, namespace, group, ret) +#define option_parser_get_help_table_ns(ns, ret) \ + option_parser_get_help_table_full(ns, /* group= */ NULL, ret) +#define option_parser_get_help_table_group(group, ret) \ + option_parser_get_help_table_full(/* namespace= */ NULL, group, ret) +#define option_parser_get_help_table(ret) \ + option_parser_get_help_table_group(/* group= */ NULL, ret) diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h index 15f85117575a8..151bf6b4bcec2 100644 --- a/src/shared/output-mode.h +++ b/src/shared/output-mode.h @@ -43,12 +43,13 @@ typedef enum OutputFlags { OUTPUT_UTC = 1 << 6, OUTPUT_NO_HOSTNAME = 1 << 7, OUTPUT_TRUNCATE_NEWLINE = 1 << 8, + OUTPUT_SKIP_UNPRINTABLE = 1 << 9, /* Specific to process tree output */ - OUTPUT_KERNEL_THREADS = 1 << 9, - OUTPUT_CGROUP_XATTRS = 1 << 10, - OUTPUT_CGROUP_ID = 1 << 11, - OUTPUT_HIDE_EXTRA = 1 << 12, + OUTPUT_KERNEL_THREADS = 1 << 10, + OUTPUT_CGROUP_XATTRS = 1 << 11, + OUTPUT_CGROUP_ID = 1 << 12, + OUTPUT_HIDE_EXTRA = 1 << 13, } OutputFlags; sd_json_format_flags_t output_mode_to_json_format_flags(OutputMode m); diff --git a/src/shared/pam-util.c b/src/shared/pam-util.c index d6158ec8caec4..0fe4207a0b7a1 100644 --- a/src/shared/pam-util.c +++ b/src/shared/pam-util.c @@ -1,30 +1,37 @@ /* 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" -static void *libpam_dl = NULL; - DLSYM_PROTOTYPE(pam_acct_mgmt) = NULL; DLSYM_PROTOTYPE(pam_close_session) = NULL; DLSYM_PROTOTYPE(pam_end) = NULL; +DLSYM_PROTOTYPE(pam_get_authtok_noverify) = NULL; +DLSYM_PROTOTYPE(pam_get_authtok_verify) = NULL; DLSYM_PROTOTYPE(pam_get_data) = NULL; DLSYM_PROTOTYPE(pam_get_item) = NULL; +DLSYM_PROTOTYPE(pam_get_user) = NULL; +DLSYM_PROTOTYPE(pam_getenv) = NULL; DLSYM_PROTOTYPE(pam_getenvlist) = NULL; DLSYM_PROTOTYPE(pam_open_session) = NULL; +DLSYM_PROTOTYPE(pam_prompt) = NULL; DLSYM_PROTOTYPE(pam_putenv) = NULL; DLSYM_PROTOTYPE(pam_set_data) = NULL; DLSYM_PROTOTYPE(pam_set_item) = NULL; @@ -34,33 +41,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 +363,54 @@ int pam_prompt_graceful(pam_handle_t *pamh, int style, char **ret_response, cons return PAM_SUCCESS; } + +int pam_putenv_assign(pam_handle_t *pamh, const char *name, const char *value) { + _cleanup_(erase_and_freep) char *s = NULL; + + assert(pamh); + assert(name); + assert(value); + + if (asprintf(&s, "%s=%s", name, value) < 0) + return PAM_BUF_ERR; + + return sym_pam_putenv(pamh, s); +} + +#endif + +int dlopen_libpam(int log_level) { +#if HAVE_PAM + static void *libpam_dl = NULL; + + LIBPAM_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + + 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_authtok_noverify), + DLSYM_ARG(pam_get_authtok_verify), + DLSYM_ARG(pam_get_data), + DLSYM_ARG(pam_get_item), + DLSYM_ARG(pam_get_user), + DLSYM_ARG(pam_getenv), + DLSYM_ARG(pam_getenvlist), + DLSYM_ARG(pam_open_session), + DLSYM_ARG(pam_prompt), + 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..408c371e8d743 100644 --- a/src/shared/pam-util.h +++ b/src/shared/pam-util.h @@ -1,12 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dlopen.h" + #include "shared-forward.h" #if HAVE_PAM #include #include -#include /* IWYU pragma: export */ +#include /* IWYU pragma: export */ #include #include "dlfcn-util.h" @@ -14,10 +16,15 @@ extern DLSYM_PROTOTYPE(pam_acct_mgmt); extern DLSYM_PROTOTYPE(pam_close_session); extern DLSYM_PROTOTYPE(pam_end); +extern DLSYM_PROTOTYPE(pam_get_authtok_noverify); +extern DLSYM_PROTOTYPE(pam_get_authtok_verify); extern DLSYM_PROTOTYPE(pam_get_data); extern DLSYM_PROTOTYPE(pam_get_item); +extern DLSYM_PROTOTYPE(pam_get_user); +extern DLSYM_PROTOTYPE(pam_getenv); extern DLSYM_PROTOTYPE(pam_getenvlist); extern DLSYM_PROTOTYPE(pam_open_session); +extern DLSYM_PROTOTYPE(pam_prompt); extern DLSYM_PROTOTYPE(pam_putenv); extern DLSYM_PROTOTYPE(pam_set_data); extern DLSYM_PROTOTYPE(pam_set_item); @@ -27,7 +34,10 @@ extern DLSYM_PROTOTYPE(pam_strerror); extern DLSYM_PROTOTYPE(pam_syslog); extern DLSYM_PROTOTYPE(pam_vsyslog); -int dlopen_libpam(void); +/* sym_pam_prompt() replacement for the pam_info() macro from , which expands to a direct + * pam_prompt() call. */ +#define sym_pam_info(pamh, fmt, ...) \ + sym_pam_prompt((pamh), PAM_TEXT_INFO, NULL, (fmt), ## __VA_ARGS__) void pam_log_setup(void); @@ -92,10 +102,23 @@ 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 +/* Equivalent of pam_misc_setenv(pamh, name, value, 0), without the libpam_misc dep — builds "name=value" + * and hands it to sym_pam_putenv(), then erases the buffer before freeing in case it carried a secret. */ +int pam_putenv_assign(pam_handle_t *pamh, const char *name, const char *value); -static inline int dlopen_libpam(void) { - return -EOPNOTSUPP; -} +#define LIBPAM_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("pam", \ + "Support for LinuxPAM", \ + priority, \ + "libpam.so.0") +#define DLOPEN_LIBPAM(log_level, priority) \ + ({ \ + LIBPAM_NOTE(priority); \ + dlopen_libpam(log_level); \ + }) +#else +#define DLOPEN_LIBPAM(log_level, priority) dlopen_libpam(log_level) #endif + +int dlopen_libpam(int log_level); diff --git a/src/shared/parse-argument.c b/src/shared/parse-argument.c index 39e5328e7037e..bfd21a910e0e5 100644 --- a/src/shared/parse-argument.c +++ b/src/shared/parse-argument.c @@ -31,7 +31,7 @@ int parse_boolean_argument(const char *optname, const char *s, bool *ret) { *ret = r; return r; } else { - /* s may be NULL. This is controlled by getopt_long() parameters. */ + /* s may be NULL. This is controlled by OPTION flags. */ if (ret) *ret = true; return true; @@ -42,10 +42,10 @@ int parse_tristate_argument_with_auto(const char *optname, const char *s, int *r int r; assert(optname); - assert(s); /* We refuse NULL optarg here, since that would be ambiguous on cmdline: - for --enable-a[=BOOL], --enable-a is intuitively interpreted as true rather than "auto" - (parse_boolean_argument() does exactly that). IOW, tristate options should require - arguments. */ + assert(s); /* We refuse NULL arg here, since that would be ambiguous on cmdline: for + * --enable-a[=BOOL], --enable-a is intuitively interpreted as true rather than "auto" + * (parse_boolean_argument() does exactly that). IOW, tristate options should require + * arguments. */ r = parse_tristate_full(s, "auto", ret); if (r < 0) @@ -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..f32245d344cbe 100644 --- a/src/shared/password-quality-util-passwdqc.c +++ b/src/shared/password-quality-util-passwdqc.c @@ -2,25 +2,26 @@ #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 +33,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 +136,18 @@ int check_password_quality( #endif -int dlopen_passwdqc(void) { +int dlopen_passwdqc(int log_level) { #if HAVE_PASSWDQC - ELF_NOTE_DLOPEN("passwdqc", + static void *passwdqc_dl = NULL; + + 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..34d9b1aa16e26 100644 --- a/src/shared/password-quality-util-pwquality.c +++ b/src/shared/password-quality-util-pwquality.c @@ -2,30 +2,31 @@ #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 +71,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 +152,18 @@ 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", + static void *pwquality_dl = NULL; + + 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..52b65a95a35f3 100644 --- a/src/shared/pcre2-util.c +++ b/src/shared/pcre2-util.c @@ -1,13 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-dlopen.h" + #include "dlfcn-util.h" #include "hash-funcs.h" #include "log.h" #include "pcre2-util.h" #if HAVE_PCRE2 -static void *pcre2_dl = NULL; - DLSYM_PROTOTYPE(pcre2_match_data_create) = NULL; DLSYM_PROTOTYPE(pcre2_match_data_free) = NULL; DLSYM_PROTOTYPE(pcre2_code_free) = NULL; @@ -26,11 +26,14 @@ 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", + static void *pcre2_dl = NULL; + + 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; @@ -123,7 +127,7 @@ int pattern_matches_and_log(pcre2_code *compiled_pattern, const char *message, s assert(message); /* pattern_compile_and_log() must be called before this function is called and that function already * dlopens pcre2 so we can assert on it being available here. */ - assert(pcre2_dl); + assert(sym_pcre2_match); md = sym_pcre2_match_data_create(1, NULL); if (!md) 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..5bd87e03c91ca 100644 --- a/src/shared/pcrextend-util.c +++ b/src/shared/pcrextend-util.c @@ -2,6 +2,7 @@ #include "sd-device.h" #include "sd-id128.h" +#include "sd-json.h" #include "sd-varlink.h" #include "alloc-util.h" @@ -18,8 +19,11 @@ #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" +#include "user-record.h" static int device_get_file_system_word( sd_device *d, @@ -35,7 +39,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; @@ -185,6 +189,58 @@ int pcrextend_product_id_word(char **ret) { return 0; } +int pcrextend_login_word(UserRecord *ur, char **ret) { + int r; + + assert(ur); + assert(ret); + + /* Reduce the user record to the sections that make up its stable, host-specific identity, and turn + * that into a word to measure into the 'login' NvPCR. We deliberately keep the 'regular', + * 'perMachine' and 'binding' sections (the portable identity plus how it is realized on *this* + * host), but drop 'privileged' (so password hash churn doesn't perturb the NvPCR), 'secret' and + * 'status' (transient) and 'signature' (so the scheme is identical for signed and unsigned records). + * Only 'regular' is required, the others are merely allowed, so plain NSS users reduce cleanly. */ + UserRecordLoadFlags mask = + USER_RECORD_REQUIRE_REGULAR | + USER_RECORD_ALLOW_PER_MACHINE | + USER_RECORD_ALLOW_BINDING | + USER_RECORD_STRIP_PRIVILEGED | + USER_RECORD_STRIP_SECRET | + USER_RECORD_STRIP_STATUS | + USER_RECORD_STRIP_SIGNATURE | + USER_RECORD_PERMISSIVE; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + r = user_group_record_mangle(ur->json, mask, &v, /* ret_mask= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to reduce user record of '%s': %m", ur->user_name); + + /* Normalize so the serialization is canonical (stable key order) regardless of how the record was + * assembled by the various userdb backends. */ + r = sd_json_variant_normalize(&v); + if (r < 0) + return log_error_errno(r, "Failed to normalize user record of '%s': %m", ur->user_name); + + /* Format to compact, single-line JSON (no SD_JSON_FORMAT_NEWLINE), so the measured word stays on one + * line and the colon-separated word structure is unambiguous. */ + _cleanup_free_ char *text = NULL; + r = sd_json_variant_format(v, /* flags= */ 0, &text); + if (r < 0) + return log_error_errno(r, "Failed to format user record of '%s': %m", ur->user_name); + + _cleanup_free_ char *name_escaped = xescape(ur->user_name, ":"); /* Avoid ambiguity around ":" */ + if (!name_escaped) + return log_oom(); + + _cleanup_free_ char *word = strjoin("login:", name_escaped, ":", text); + if (!word) + return log_oom(); + + *ret = TAKE_PTR(word); + return 0; +} + int pcrextend_verity_word( const char *name, const struct iovec *root_hash, @@ -195,6 +251,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 +342,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 = sha256_direct_hex(data->iov_base, data->iov_len); + 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..c753c265683bf 100644 --- a/src/shared/pcrextend-util.h +++ b/src/shared/pcrextend-util.h @@ -1,9 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include + +#include "shared-forward.h" + 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_login_word(UserRecord *ur, 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..fc6576a875770 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" @@ -12,6 +13,11 @@ #include "string-table.h" #include "string-util.h" +/* Cap on the (VirtualSize - SizeOfRawData) zero-padding the UKI hasher + * will produce for a single section. Any value beyond this is treated as + * a malformed PE — bounds the hash work an attacker can drive (#42344). */ +#define UKI_HASH_VIRTUAL_SIZE_PADDING_MAX (64U * 1024U * 1024U) + /* Note: none of these function change the file position of the provided fd, as they use pread() */ bool pe_header_is_64bit(const PeHeader *h) { @@ -139,6 +145,12 @@ int pe_load_headers( if (!IN_SET(le16toh(pe_header->optional.Magic), UINT16_C(0x010B), UINT16_C(0x020B))) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Optional header magic invalid."); + /* The optional header must be at least large enough to cover everything + * up to and including NumberOfRvaAndSizes — otherwise the equality + * check below would read uninitialised memory for that field. */ + if (pe_header_size(pe_header) < PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory)) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Optional header too small."); + if (pe_header_size(pe_header) != PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory) + sizeof(IMAGE_DATA_DIRECTORY) * (uint64_t) le32toh(PE_HEADER_OPTIONAL_FIELD(pe_header, NumberOfRvaAndSizes))) @@ -159,6 +171,7 @@ int pe_load_sections( IMAGE_SECTION_HEADER **ret_sections) { _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + struct stat st; size_t nos; ssize_t n; @@ -181,6 +194,26 @@ int pe_load_sections( if ((size_t) n != sizeof(IMAGE_SECTION_HEADER) * nos) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while reading section table."); + /* The section's raw bytes must fit inside the file. This is the + * fundamental invariant the parser relies on later (pe_hash, uki_hash, + * pe_read_section_data, ...); reject obvious malformations early so + * downstream loops don't get driven by attacker-controlled sizes. */ + if (fstat(fd, &st) < 0) + return log_debug_errno(errno, "Failed to stat PE file: %m"); + + FOREACH_ARRAY(section, sections, nos) { + uint64_t prd = le32toh(section->PointerToRawData), srd = le32toh(section->SizeOfRawData), end; + + /* SizeOfRawData == 0 is legitimate (BSS-like, uninitialised) — + * PointerToRawData is then meaningless and not used as an offset. */ + if (srd == 0) + continue; + + if (!ADD_SAFE(&end, prd, srd) || end > (uint64_t) st.st_size) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "PE section raw data exceeds file size."); + } + if (ret_sections) *ret_sections = TAKE_PTR(sections); @@ -199,7 +232,7 @@ int pe_read_section_data( size_t n = le32toh(section->VirtualSize); if (n > MIN(max_size, (size_t) SSIZE_MAX)) - return -E2BIG; + return -EBADMSG; _cleanup_free_ void *data = malloc(n+1); if (!data) @@ -209,7 +242,7 @@ int pe_read_section_data( if (ss < 0) return -errno; if ((size_t) ss != n) - return -EIO; + return -EBADMSG; if (ret_size) *ret_size = n; @@ -325,7 +358,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 +391,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 +413,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 +458,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 +478,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); @@ -482,7 +519,8 @@ int pe_checksum(int fd, uint32_t *ret) { return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Short read from PE file"); for (size_t i = 0; i < (size_t) n / 2; i++) { - if (off + i >= checksum_offset && off + i < checksum_offset + sizeof(pe_header->optional.CheckSum)) + size_t pos = off + i * sizeof(uint16_t); + if (pos >= checksum_offset && pos < checksum_offset + sizeof(pe_header->optional.CheckSum)) continue; uint16_t val = le16toh(buf[i]); @@ -525,6 +563,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 +575,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 +595,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))); @@ -568,10 +610,20 @@ int uki_hash(int fd, uint8_t zeroes[1024] = {}; size_t remaining = le32toh(section->VirtualSize) - le32toh(section->SizeOfRawData); + /* Bound the zero-padding hash work. An attacker can otherwise + * set VirtualSize close to UINT32_MAX with SizeOfRawData = 0, + * driving ~4 GiB of SHA-256 work per section on a tiny file + * (issue #42344 — wedges the parser for >10 s on 382 B). */ + if (remaining > UKI_HASH_VIRTUAL_SIZE_PADDING_MAX) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "Section VirtualSize exceeds SizeOfRawData by %zu bytes (cap %zu); refusing to hash.", + remaining, + (size_t) UKI_HASH_VIRTUAL_SIZE_PADDING_MAX); + 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 +635,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 +644,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-padding.h b/src/shared/pkcs11-padding.h new file mode 100644 index 0000000000000..c65f9617d9355 --- /dev/null +++ b/src/shared/pkcs11-padding.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "basic-forward.h" + +typedef enum Pkcs11RsaPadding { + PKCS11_RSA_PADDING_PKCS1V15, /* CKM_RSA_PKCS, RFC 8017 PKCS#1 v1.5 (legacy) */ + PKCS11_RSA_PADDING_OAEP_SHA1, /* CKM_RSA_PKCS_OAEP with SHA-1/MGF1-SHA-1 (more supported) */ + PKCS11_RSA_PADDING_OAEP_SHA256, /* CKM_RSA_PKCS_OAEP with SHA-256/MGF1-SHA-256 (preferred if supported) */ + _PKCS11_RSA_PADDING_MAX, + _PKCS11_RSA_PADDING_INVALID = -EINVAL, +} Pkcs11RsaPadding; + +const char* pkcs11_rsa_padding_to_string(Pkcs11RsaPadding i); +Pkcs11RsaPadding pkcs11_rsa_padding_from_string(const char *s); + +static inline const char* pkcs11_rsa_padding_to_oaep_hash(Pkcs11RsaPadding p) { + switch (p) { + case PKCS11_RSA_PADDING_OAEP_SHA1: + return "SHA-1"; + case PKCS11_RSA_PADDING_OAEP_SHA256: + return "SHA-256"; + default: + return NULL; + } +} diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index 3062bcc554196..60235fa3e0882 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -1,16 +1,23 @@ /* 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-table.h" #include "string-util.h" #include "strv.h" @@ -35,9 +42,15 @@ bool pkcs11_uri_valid(const char *uri) { return true; } -#if HAVE_P11KIT +static const char* const pkcs11_rsa_padding_table[_PKCS11_RSA_PADDING_MAX] = { + [PKCS11_RSA_PADDING_PKCS1V15] = "pkcs1", + [PKCS11_RSA_PADDING_OAEP_SHA1] = "oaep-sha1", + [PKCS11_RSA_PADDING_OAEP_SHA256] = "oaep-sha256", +}; -static void *p11kit_dl = NULL; +DEFINE_STRING_TABLE_LOOKUP(pkcs11_rsa_padding, Pkcs11RsaPadding); + +#if HAVE_P11KIT DLSYM_PROTOTYPE(p11_kit_module_get_name) = NULL; DLSYM_PROTOTYPE(p11_kit_modules_finalize_and_release) = NULL; @@ -63,7 +76,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 +96,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 +112,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 +128,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 +232,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 +294,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 +414,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 +440,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 +458,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 +558,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."); + return log_openssl_errors(LOG_DEBUG, "Failed to create an EVP_PKEY_CTX for EC."); - if (EVP_PKEY_fromdata_init(ctx) != 1) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to init an EVP_PKEY_CTX for EC."); + if (sym_EVP_PKEY_fromdata_init(ctx) != 1) + return log_openssl_errors(LOG_DEBUG, "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) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract EC parameters from EC_GROUP."); + if (sym_EC_GROUP_get_curve(group, bn_p, bn_a, bn_b, bnctx) != 1) + return log_openssl_errors(LOG_DEBUG, "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,36 +636,36 @@ 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 ) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to store EC parameters in native byte order."); + 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_openssl_errors(LOG_DEBUG, "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."); + return log_openssl_errors(LOG_DEBUG, "Failed to determine size of a EC generator."); generator = malloc(generator_size); 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(); + return log_openssl_errors(LOG_DEBUG, "Failed to convert a EC generator to octet string."); + + 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) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create EVP_PKEY from EC parameters."); + if (sym_EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, ec_params) != 1) + return log_openssl_errors(LOG_DEBUG, "Failed to create EVP_PKEY from EC parameters."); break; } default: @@ -657,16 +682,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,18 +710,22 @@ 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."); + return log_openssl_errors(LOG_DEBUG, "Failed to format X.509 subject name as string."); log_debug("Using X.509 certificate issued for '%s'.", t); @@ -872,6 +900,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 +977,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,33 +1028,37 @@ 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"); + return log_openssl_errors(LOG_ERR, "Failed to determine size of a compressed EC point"); compressed_point = malloc(compressed_point_size); 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"); + return log_openssl_errors(LOG_ERR, "Failed to convert a EC point to compressed format"); *ret_compressed_point = TAKE_PTR(compressed_point); *ret_compressed_point_size = compressed_point_size; @@ -1070,6 +1107,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), @@ -1140,17 +1180,60 @@ static int pkcs11_token_decrypt_data_rsa( CK_OBJECT_HANDLE object, const void *encrypted_data, size_t encrypted_data_size, + Pkcs11RsaPadding rsa_padding, void **ret_decrypted_data, size_t *ret_decrypted_data_size) { - static const CK_MECHANISM mechanism = { - .mechanism = CKM_RSA_PKCS + /* For RSA-OAEP we use SHA-256 or SHA-1 with the matching MGF1 and an empty (zero-length) label. + * SHA-256 is preferred when the token supports it, but for example SoftHSM doesn't support it. */ + CK_RSA_PKCS_OAEP_PARAMS oaep_params_sha1 = { + .hashAlg = CKM_SHA_1, + .mgf = CKG_MGF1_SHA1, + .source = CKZ_DATA_SPECIFIED, + }; + CK_RSA_PKCS_OAEP_PARAMS oaep_params_sha256 = { + .hashAlg = CKM_SHA256, + .mgf = CKG_MGF1_SHA256, + .source = CKZ_DATA_SPECIFIED, }; + CK_MECHANISM mechanism; _cleanup_(erase_and_freep) CK_BYTE *dbuffer = NULL; CK_ULONG dbuffer_size = 0; CK_RV rv; - rv = m->C_DecryptInit(session, (CK_MECHANISM*) &mechanism, object); + assert(ret_decrypted_data); + assert(ret_decrypted_data_size); + + switch (rsa_padding) { + + case PKCS11_RSA_PADDING_PKCS1V15: + mechanism = (CK_MECHANISM) { + .mechanism = CKM_RSA_PKCS, + }; + break; + + case PKCS11_RSA_PADDING_OAEP_SHA1: + mechanism = (CK_MECHANISM) { + .mechanism = CKM_RSA_PKCS_OAEP, + .pParameter = &oaep_params_sha1, + .ulParameterLen = sizeof(oaep_params_sha1), + }; + break; + + case PKCS11_RSA_PADDING_OAEP_SHA256: + mechanism = (CK_MECHANISM) { + .mechanism = CKM_RSA_PKCS_OAEP, + .pParameter = &oaep_params_sha256, + .ulParameterLen = sizeof(oaep_params_sha256), + }; + break; + + default: + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Unknown RSA padding scheme requested for PKCS#11 decryption."); + } + + rv = m->C_DecryptInit(session, &mechanism, object); if (rv != CKR_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize decryption on security token: %s", sym_p11_kit_strerror(rv)); @@ -1174,7 +1257,8 @@ static int pkcs11_token_decrypt_data_rsa( return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to decrypt key on security token: %s", sym_p11_kit_strerror(rv)); - log_info("Successfully decrypted key with security token."); + log_info("Successfully decrypted key with security token (%s padding).", + pkcs11_rsa_padding_to_string(rsa_padding)); *ret_decrypted_data = TAKE_PTR(dbuffer); *ret_decrypted_data_size = dbuffer_size; @@ -1187,6 +1271,7 @@ int pkcs11_token_decrypt_data( CK_OBJECT_HANDLE object, const void *encrypted_data, size_t encrypted_data_size, + Pkcs11RsaPadding rsa_padding, void **ret_decrypted_data, size_t *ret_decrypted_data_size) { @@ -1207,7 +1292,7 @@ int pkcs11_token_decrypt_data( switch (key_type) { case CKK_RSA: - return pkcs11_token_decrypt_data_rsa(m, session, object, encrypted_data, encrypted_data_size, ret_decrypted_data, ret_decrypted_data_size); + return pkcs11_token_decrypt_data_rsa(m, session, object, encrypted_data, encrypted_data_size, rsa_padding, ret_decrypted_data, ret_decrypted_data_size); case CKK_EC: return pkcs11_token_decrypt_data_ecc(m, session, object, encrypted_data, encrypted_data_size, ret_decrypted_data, ret_decrypted_data_size); @@ -1228,7 +1313,7 @@ int pkcs11_token_acquire_rng( assert(m); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1314,7 +1399,7 @@ static int slot_process( assert(m); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1397,7 +1482,7 @@ static int module_process( assert(m); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1461,7 +1546,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; @@ -1495,13 +1580,95 @@ int pkcs11_find_token( struct pkcs11_acquire_public_key_callback_data { char *pin_used; EVP_PKEY *pkey; + Pkcs11RsaPadding rsa_padding; const char *askpw_friendly_name, *askpw_icon, *askpw_credential; AskPasswordFlags askpw_flags; }; 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_token_try_oaep_mechanism( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE private_key, + CK_MECHANISM_TYPE hash_alg, + CK_RSA_PKCS_MGF_TYPE mgf) { + + CK_RSA_PKCS_OAEP_PARAMS params = { + .hashAlg = hash_alg, + .mgf = mgf, + .source = CKZ_DATA_SPECIFIED, + }; + CK_MECHANISM mechanism = { + .mechanism = CKM_RSA_PKCS_OAEP, + .pParameter = ¶ms, + .ulParameterLen = sizeof(params), + }; + CK_RV rv; + + rv = m->C_DecryptInit(session, &mechanism, private_key); + if (rv != CKR_OK) + return -EIO; + + /* Accepted: terminate the operation. Per PKCS#11 spec section 5.2 any cryptographic function + * returning an error other than CKR_BUFFER_TOO_SMALL terminates the active operation, so feed + * deliberately invalid (too-short) ciphertext to C_Decrypt to trigger a cancellation that's + * likely to be portable across token implementations. */ + CK_BYTE in[1] = {}; + CK_BYTE out[1]; + CK_ULONG out_len = sizeof(out); + (void) m->C_Decrypt(session, in, sizeof(in), out, &out_len); + + return 0; +} + +static int pkcs11_token_probe_rsa_oaep_padding( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE private_key, + Pkcs11RsaPadding *ret) { + + CK_KEY_TYPE key_type; + CK_ATTRIBUTE key_type_template = { CKA_KEY_TYPE, &key_type, sizeof(key_type) }; + CK_RV rv; + int r; + + assert(m); + assert(ret); + + /* Probes whether the token will accept the OAEP-SHA256 or OAEP-SHA1 mechanism for decryption with + * the given private key. On any failure (wrong key type, related-object lookup ambiguity yielded + * an EC private key, both mechanisms rejected, etc.) we bail out, this can only be best-effort. */ + rv = m->C_GetAttributeValue(session, private_key, &key_type_template, /* ulCount= */ 1); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to retrieve private key type: %s", sym_p11_kit_strerror(rv)); + if (key_type != CKK_RSA) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Located private key is not RSA, skipping OAEP padding probe."); + + /* For RSA-OAEP we use SHA-256 or SHA-1 with the matching MGF1 and an empty (zero-length) label. + * SHA-256 is preferred when the token supports it, but for example SoftHSM doesn't support it, so + * fall back to SHA-1 if it gets rejected. */ + r = pkcs11_token_try_oaep_mechanism(m, session, private_key, CKM_SHA256, CKG_MGF1_SHA256); + if (r >= 0) { + *ret = PKCS11_RSA_PADDING_OAEP_SHA256; + log_debug("Token accepts OAEP-SHA256 for decryption."); + return 0; + } + + r = pkcs11_token_try_oaep_mechanism(m, session, private_key, CKM_SHA_1, CKG_MGF1_SHA1); + if (r >= 0) { + *ret = PKCS11_RSA_PADDING_OAEP_SHA1; + log_debug("Token accepts OAEP-SHA1 for decryption."); + return 0; + } + + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Token rejected both OAEP-SHA256 and OAEP-SHA1."); } static int pkcs11_acquire_public_key_callback( @@ -1653,9 +1820,13 @@ 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."); + return log_openssl_errors(LOG_ERR, "Failed to extract public key from X.509 certificate."); } success: /* Let's read some random data off the token and write it to the kernel pool before we generate our @@ -1663,6 +1834,30 @@ static int pkcs11_acquire_public_key_callback( * kernel's and the token's pool */ (void) pkcs11_token_acquire_rng(m, session); + /* Decide whether the enrolled key needs RSA padding metadata at all based on the public key we + * extracted (RSA vs EC). For RSA, default to OAEP-SHA1 which works on all known tokens (e.g. + * SoftHSM only accepts SHA-1), then try to upgrade to OAEP-SHA256 by probing the matching private + * key on the token. The probe is best-effort: if we cannot find the private key (e.g. token holds + * only the certificate, or the related-object lookup is ambiguous because CKA_ID is missing or + * doesn't match) we keep the OAEP-SHA1 default. For non-RSA keys (EC) we leave rsa_padding as + * _INVALID, so no padding tag is recorded and the decrypt path uses ECDH instead. */ + data->rsa_padding = _PKCS11_RSA_PADDING_INVALID; + + if (sym_EVP_PKEY_get_base_id(pkey) == EVP_PKEY_RSA) { + CK_OBJECT_HANDLE prototype = public_key != CK_INVALID_HANDLE ? public_key : certificate; + CK_OBJECT_HANDLE private_key = CK_INVALID_HANDLE; + Pkcs11RsaPadding padding = PKCS11_RSA_PADDING_OAEP_SHA1; /* default to SHA1 unless we can do better */ + + if (pkcs11_token_find_related_object(m, session, prototype, CKO_PRIVATE_KEY, &private_key) >= 0) { + r = pkcs11_token_probe_rsa_oaep_padding(m, session, private_key, &padding); + if (r < 0) + log_info_errno(r, "Token rejected both OAEP-SHA256 and OAEP-SHA1, defaulting RSA enrollment to OAEP-SHA1."); + } else + log_info("No matching PKCS#11 private key found, OAEP padding probe skipped, defaulting RSA enrollment to OAEP-SHA1."); + + data->rsa_padding = padding; + } + data->pin_used = TAKE_PTR(pin_used); data->pkey = TAKE_PTR(pkey); return 0; @@ -1675,6 +1870,7 @@ int pkcs11_acquire_public_key( const char *askpw_credential, AskPasswordFlags askpw_flags, EVP_PKEY **ret_pkey, + Pkcs11RsaPadding *ret_rsa_padding, char **ret_pin_used) { _cleanup_(pkcs11_acquire_public_key_callback_data_release) struct pkcs11_acquire_public_key_callback_data data = { @@ -1682,6 +1878,7 @@ int pkcs11_acquire_public_key( .askpw_icon = askpw_icon, .askpw_credential = askpw_credential, .askpw_flags = askpw_flags, + .rsa_padding = _PKCS11_RSA_PADDING_INVALID, }; int r; @@ -1697,6 +1894,8 @@ int pkcs11_acquire_public_key( return r; *ret_pkey = TAKE_PTR(data.pkey); + if (ret_rsa_padding) + *ret_rsa_padding = data.rsa_padding; if (ret_pin_used) *ret_pin_used = TAKE_PTR(data.pin_used); return 0; @@ -1720,7 +1919,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 +1963,19 @@ static int list_callback( } #endif -int dlopen_p11kit(void) { +int dlopen_p11kit(int log_level) { #if HAVE_P11KIT - ELF_NOTE_DLOPEN("p11-kit", + static void *p11kit_dl = NULL; + + 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 +1993,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 +2016,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 +2040,7 @@ static int auto_callback( assert(slot_info); assert(token_info); - r = dlopen_p11kit(); + r = dlopen_p11kit(LOG_DEBUG); if (r < 0) return r; @@ -1939,6 +2138,7 @@ int pkcs11_crypt_device_callback( object, data->encrypted_key, data->encrypted_key_size, + data->rsa_padding, &data->decrypted_key, &data->decrypted_key_size); if (r < 0) diff --git a/src/shared/pkcs11-util.h b/src/shared/pkcs11-util.h index 51279eabc47ad..f4599a50f16c6 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 */ @@ -12,6 +8,7 @@ #include "ask-password-api.h" #include "dlfcn-util.h" +#include "pkcs11-padding.h" #include "shared-forward.h" bool pkcs11_uri_valid(const char *uri); @@ -70,7 +67,7 @@ int pkcs11_token_read_x509_certificate(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE se #endif int pkcs11_token_find_private_key(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, P11KitUri *search_uri, CK_OBJECT_HANDLE *ret_object); -int pkcs11_token_decrypt_data(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, const void *encrypted_data, size_t encrypted_data_size, void **ret_decrypted_data, size_t *ret_decrypted_data_size); +int pkcs11_token_decrypt_data(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, const void *encrypted_data, size_t encrypted_data_size, Pkcs11RsaPadding rsa_padding, void **ret_decrypted_data, size_t *ret_decrypted_data_size); int pkcs11_token_acquire_rng(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session); @@ -78,7 +75,7 @@ typedef int (*pkcs11_find_token_callback_t)(CK_FUNCTION_LIST *m, CK_SESSION_HAND int pkcs11_find_token(const char *pkcs11_uri, pkcs11_find_token_callback_t callback, void *userdata); #if HAVE_OPENSSL -int pkcs11_acquire_public_key(const char *uri, const char *askpw_friendly_name, const char *askpw_icon, const char *askpw_credential, AskPasswordFlags askpw_flags, EVP_PKEY **ret_pkey, char **ret_pin_used); +int pkcs11_acquire_public_key(const char *uri, const char *askpw_friendly_name, const char *askpw_icon, const char *askpw_credential, AskPasswordFlags askpw_flags, EVP_PKEY **ret_pkey, Pkcs11RsaPadding *ret_rsa_padding, char **ret_pin_used); #endif typedef struct { @@ -91,6 +88,7 @@ typedef struct { bool free_encrypted_key; const char *askpw_credential; AskPasswordFlags askpw_flags; + Pkcs11RsaPadding rsa_padding; } pkcs11_crypt_device_callback_data; void pkcs11_crypt_device_callback_data_release(pkcs11_crypt_device_callback_data *data); @@ -106,7 +104,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..c5ace49f608a0 100644 --- a/src/shared/pretty-print.c +++ b/src/shared/pretty-print.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -100,6 +99,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 +126,8 @@ int file_url_from_path(const char *path, char **ret) { char *url = NULL; int r; + assert(ret); + if (uname(&u) < 0) return -errno; @@ -175,11 +177,11 @@ int terminal_urlify_path(const char *path, const char *text, char **ret) { return terminal_urlify(url, text, ret); } -int terminal_urlify_man(const char *page, const char *section, char **ret) { +int terminal_urlify_man_full(const char *page, const char *section, const char *suffix, char **ret) { const char *url, *text; url = strjoina("man:", page, "(", section, ")"); - text = strjoina(page, "(", section, ") man page"); + text = strjoina(page, "(", section, ")", suffix); return terminal_urlify(url, text, ret); } @@ -388,6 +390,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 +493,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"); @@ -558,7 +566,10 @@ void draw_progress_bar_unbuffered(const char *prefix, double percentage) { * https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC * https://github.com/microsoft/terminal/pull/8055 */ - fprintf(stderr, ANSI_OSC "9;4;1;%u" ANSI_ST, (unsigned) ceil(percentage)); + unsigned percentage_ceil = (unsigned) percentage; + if ((double) percentage_ceil < percentage) + percentage_ceil++; + fprintf(stderr, ANSI_OSC "9;4;1;%u" ANSI_ST, percentage_ceil); size_t cols = columns(); size_t prefix_width = utf8_console_width(prefix) + 1 /* space */; diff --git a/src/shared/pretty-print.h b/src/shared/pretty-print.h index 6b759dd346945..44cc692011d4b 100644 --- a/src/shared/pretty-print.h +++ b/src/shared/pretty-print.h @@ -21,7 +21,10 @@ bool urlify_enabled(void); int terminal_urlify(const char *url, const char *text, char **ret); int terminal_urlify_path(const char *path, const char *text, char **ret); -int terminal_urlify_man(const char *page, const char *section, char **ret); +int terminal_urlify_man_full(const char *page, const char *section, const char *suffix, char **ret); +static inline int terminal_urlify_man(const char *page, const char *section, char **ret) { + return terminal_urlify_man_full(page, section, " man page", ret); +} typedef enum CatFlags { CAT_CONFIG_OFF = 0, diff --git a/src/shared/prompt-util.c b/src/shared/prompt-util.c index 9811cb1527873..a7bcfe32e332c 100644 --- a/src/shared/prompt-util.c +++ b/src/shared/prompt-util.c @@ -12,31 +12,46 @@ #include "parse-util.h" #include "pretty-print.h" #include "prompt-util.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "terminal-util.h" +typedef struct CompletionData { + char **menu; /* What to show in menu */ + char **accepted; /* What to accept (usually larger than the menu, but may be NULL if same) */ +} CompletionData; + static int get_completions( const char *key, + GetCompletionsFlags flags, char ***ret_list, void *userdata) { + CompletionData *data = ASSERT_PTR(userdata); int r; assert(ret_list); - if (!userdata) { + /* Figure out the list to operate on. We'll generally work based on the "accepted" list, if it is + * set. If not we'll operate with the full menu. When doing pre-selection we'll also pick the menu */ + char **l = data->accepted && !FLAGS_SET(flags, GET_COMPLETIONS_PRESELECT) ? data->accepted : data->menu; + + if (strv_isempty(l)) { *ret_list = NULL; return 0; } - _cleanup_strv_free_ char **copy = strv_copy(userdata); + _cleanup_strv_free_ char **copy = strv_copy(l); if (!copy) return -ENOMEM; - r = strv_extend(©, "list"); - if (r < 0) - return r; + /* Never consider "list" for preselecting an item, but do consider it when doing a regular completion */ + if (!FLAGS_SET(flags, GET_COMPLETIONS_PRESELECT)) { + r = strv_extend(©, "list"); + if (r < 0) + return r; + } *ret_list = TAKE_PTR(copy); return 0; @@ -45,8 +60,9 @@ static int get_completions( int prompt_loop( const char *text, Glyph emoji, - char **menu, /* if non-NULL: choices to suggest */ - char **accepted, /* if non-NULL: choices to accept (should be a superset of 'menu') */ + const char *prefill, /* if non-NULL: prefill prompt with this string */ + char **menu, /* if non-NULL: choices to suggest */ + char **accepted, /* if non-NULL: choices to accept (should be a superset of 'menu') */ unsigned ellipsize_percentage, size_t n_columns, size_t column_width, @@ -101,8 +117,9 @@ int prompt_loop( _cleanup_free_ char *p = NULL; r = ask_string_full( &p, + prefill, get_completions, - accepted ?: menu, + &(CompletionData) { menu, accepted }, "%s%s%s%s: ", emoji >= 0 ? glyph(emoji) : "", emoji >= 0 ? " " : "", @@ -196,6 +213,47 @@ int prompt_loop( } } +static int boolean_is_valid(const char *name, void *userdata) { + return parse_boolean(name) >= 0; +} + +int prompt_loop_yes_no(const char *question, bool def, bool *ret) { + int r; + + assert(question); + assert(ret); + + char **l = STRV_MAKE("yes", "no"); + + _cleanup_free_ char *reply = NULL; + r = prompt_loop(question, + GLYPH_WARNING_SIGN, + /* prefill= */ def ? "yes" : "no", + /* menu= */ l, + /* accepted= */ NULL, + /* ellipsize_percentage= */ 20, + /* n_columns= */ 2, + /* column_width= */ 40, + /* is_valid= */ boolean_is_valid, + /* refresh= */ NULL, + /* userdata= */ NULL, + PROMPT_SHOW_MENU|PROMPT_MAY_SKIP|PROMPT_HIDE_MENU_HINT|PROMPT_HIDE_SKIP_HINT, + &reply); + if (r < 0) + return r; + if (r == 0) { /* Skipped (empty input): fall back to the default. */ + *ret = def; + return 0; + } + + r = parse_boolean(reply); + if (r < 0) + return log_error_errno(r, "Failed to parse reply: %s", reply); + + *ret = r; + return 1; +} + /* Default: bright white on blue background */ #define ANSI_COLOR_CHROME "\x1B[0;44;1;37m" @@ -220,11 +278,12 @@ int chrome_show( _cleanup_free_ char *b = NULL, *ansi_color_reverse = NULL; if (!bottom) { - _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL, *documentation_url = NULL; + _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL, *documentation_url = NULL, *fancy_name = NULL; r = parse_os_release( /* root= */ NULL, "PRETTY_NAME", &pretty_name, + "FANCY_NAME", &fancy_name, "NAME", &os_name, "ANSI_COLOR", &ansi_color, "ANSI_COLOR_REVERSE", &ansi_color_reverse, @@ -244,7 +303,11 @@ int chrome_show( free_and_replace(ansi_color_reverse, j); } - if (asprintf(&b, "\x1B[0;%sm %s %s", c, m, ansi_color_reverse ?: ANSI_COLOR_CHROME) < 0) + if (use_fancy_name(unescape_fancy_name(&fancy_name))) + b = asprintf_safe("\x1B[0;%sm \x1B[0m%s\x1B[0;%sm %s", c, fancy_name, c, ansi_color_reverse ?: ANSI_COLOR_CHROME); + else + b = asprintf_safe("\x1B[0;%sm %s %s", c, m, ansi_color_reverse ?: ANSI_COLOR_CHROME); + if (!b) return log_oom_debug(); if (documentation_url) { @@ -349,11 +412,12 @@ static int vl_on_reply(sd_varlink *link, sd_json_variant *parameters, const char int mute_console(sd_varlink **ret_link) { int r; - assert(ret_link); - /* Talks to the MuteConsole service, and asks for output to the console to be muted, as long as the * connection is retained */ + if (!ret_link) + return 0; + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *link = NULL; r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.MuteConsole"); if (r < 0) diff --git a/src/shared/prompt-util.h b/src/shared/prompt-util.h index 436127b32c237..255433b6717cb 100644 --- a/src/shared/prompt-util.h +++ b/src/shared/prompt-util.h @@ -14,6 +14,7 @@ typedef enum PromptFlags { int prompt_loop(const char *text, Glyph emoji, + const char *prefill, char **menu, char **accepted, unsigned ellipsize_percentage, @@ -25,6 +26,8 @@ int prompt_loop(const char *text, PromptFlags flags, char **ret); +int prompt_loop_yes_no(const char *question, bool def, bool *ret); + int chrome_show(const char *top, const char *bottom); void chrome_hide(void); diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index 2e8d77dee1c43..fcc1500dc5c8b 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; @@ -931,6 +934,24 @@ int pty_forward_new( assert(master >= 0); assert(ret); + if (!FLAGS_SET(flags, PTY_FORWARD_READ_ONLY)) { + /* If stdin isn't actually opened for reading, or refers to the read end of a pipe (or + * socket) whose peer has already hung up, then there's nothing for us to forward — imply + * read-only mode. */ + r = RET_NERRNO(fcntl(STDIN_FILENO, F_GETFL)); + if (r < 0 && r != -EBADF) + return log_debug_errno(errno, "Failed to query stdin flags: %m"); + if (r == -EBADF || (r & O_ACCMODE_STRICT) == O_WRONLY) + flags |= PTY_FORWARD_READ_ONLY; + else { + r = pipe_eof(STDIN_FILENO); + if (r < 0) + log_debug_errno(r, "Failed to check whether stdin is at EOF, ignoring: %m"); + else if (r > 0) + flags |= PTY_FORWARD_READ_ONLY; + } + } + f = new(PTYForward, 1); if (!f) return -ENOMEM; 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..8de903de1ad8c --- /dev/null +++ b/src/shared/qmp-client.c @@ -0,0 +1,1119 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-event.h" +#include "sd-future.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 = freshly reffed "return" value), -EIO on QMP error + * (ret_error_desc set to a freshly allocated string). Caller owns both outputs. */ +static int qmp_parse_response(sd_json_variant *v, sd_json_variant **ret_result, char **reterr_error_desc) { + const char *desc; + + desc = qmp_extract_error_description(v); + if (desc) { + if (reterr_error_desc) { + *reterr_error_desc = strdup(desc); + if (!*reterr_error_desc) + return -ENOMEM; + } + return -EIO; + } + + if (ret_result) + *ret_result = sd_json_variant_ref(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) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; + _cleanup_free_ 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 the reply up after its pump loop. 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); + /* qmp_client_handle_disconnect() above drained every entry via qmp_client_fail_pending(); + * the set is borrow-only for non-floating slots, so set_free() can't safely run a + * destructor over leftovers — enforce the drain invariant instead. */ + assert(set_isempty(c->slots)); + 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 pick the reply up after its pump loop. 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); +} + +typedef struct QmpFuture { + QmpSlot *slot; /* owned, non-floating; NULL once disconnected */ + sd_json_variant *result; + char *error_desc; +} QmpFuture; + +static void* qmp_future_alloc(void) { + return new0(QmpFuture, 1); +} + +static void qmp_future_free(sd_future *f) { + QmpFuture *qf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + qmp_slot_unref(qf->slot); + sd_json_variant_unref(qf->result); + free(qf->error_desc); + free(qf); +} + +static int qmp_future_cancel(sd_future *f) { + QmpFuture *qf = ASSERT_PTR(sd_future_get_private(ASSERT_PTR(f))); + + /* Drop the pending slot so dispatch_reply won't try to fire our callback (and touch + * freed memory) when the reply eventually arrives. */ + qf->slot = qmp_slot_unref(qf->slot); + return sd_future_resolve(f, -ECANCELED); +} + +static const sd_future_ops qmp_call_future_ops = { + .size = sizeof(sd_future_ops), + .alloc = qmp_future_alloc, + .free = qmp_future_free, + .cancel = qmp_future_cancel, +}; + +static int qmp_future_callback( + QmpClient *c, + sd_json_variant *result, + const char *desc, + int error, + void *userdata) { + + sd_future *f = ASSERT_PTR(userdata); + QmpFuture *qf = ASSERT_PTR(sd_future_get_private(f)); + int r; + + assert(result || desc || error); + + if (result) + qf->result = sd_json_variant_ref(result); + if (desc) { + qf->error_desc = strdup(desc); + if (!qf->error_desc) + /* No usable reply payload to surface — propagate as transport-style + * failure so suspend() / sd_future_result() see the OOM. */ + return sd_future_resolve(f, -ENOMEM); + } + + /* Resolve with 0 on a success reply and -EIO on a QMP-level error (matching the synchronous + * path's errno for the desc-without-ret_error_desc case), so a caller awaiting the future + * learns about call failures from the resolution value alone. The reply payload itself + * (result or error_desc) is always stashed on the QmpFuture so future_get_qmp_reply() can + * hand the description string back on top of the bare -EIO. With no reply at all (transport + * failure, disconnect), resolve with the propagated transport errno; cancellation surfaces + * as -ECANCELED via qmp_future_cancel(). */ + if (result) + r = 0; + else if (desc) + r = -EIO; + else + r = error; + + return sd_future_resolve(f, r); +} + +int qmp_client_call_future( + QmpClient *c, + const char *command, + QmpClientArgs *args, + sd_future **ret) { + + int r; + + assert(c); + assert(command); + assert(ret); + + _cleanup_(sd_future_unrefp) sd_future *f = NULL; + r = sd_future_new(&qmp_call_future_ops, &f); + if (r < 0) + return r; + + QmpFuture *qf = sd_future_get_private(f); + + r = qmp_client_send(c, command, args, qmp_future_callback, f, &qf->slot); + if (r < 0) + return r; + + *ret = TAKE_PTR(f); + return 0; +} + +/* Extract the reply from a resolved qmp_client_call_future(). Returns 1 on success (with + * *ret_result a fresh reference the caller unrefs), -EIO on a QMP-level error (with the detail + * description copied into *ret_error_desc when the caller passed one to receive it), and the + * future's negative resume errno when no reply landed at all (transport failure / cancellation). + */ +int future_get_qmp_reply(sd_future *f, sd_json_variant **ret_result, char **reterr_error_desc) { + assert(f); + assert(sd_future_get_ops(f) == &qmp_call_future_ops); + assert(sd_future_state(f) == SD_FUTURE_RESOLVED); + + QmpFuture *qf = ASSERT_PTR(sd_future_get_private(f)); + + /* No reply at all: transport failure or cancellation — surface the future result. */ + if (!qf->result && !qf->error_desc) + return sd_future_result(f); + + if (qf->error_desc) { + if (reterr_error_desc) { + char *desc = strdup(qf->error_desc); + if (!desc) + return -ENOMEM; + *reterr_error_desc = desc; + } + return -EIO; + } + + if (reterr_error_desc) + *reterr_error_desc = NULL; + if (ret_result) + *ret_result = sd_json_variant_ref(qf->result); + + return 1; +} + +static int qmp_client_call_suspend( + QmpClient *c, + const char *command, + QmpClientArgs *args, + sd_json_variant **ret_result, + char **ret_error_desc) { + + int r; + + assert(c); + assert(command); + assert(sd_fiber_is_running()); + + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *call = NULL; + r = qmp_client_call_future(c, command, args, &call); + if (r < 0) + return r; + + r = sd_fiber_suspend(); + + /* If the future isn't resolved, the suspend was interrupted before a reply arrived (fiber + * cancelled, fiber-wide SD_FIBER_TIMEOUT scope expired, …). There's no reply to extract, + * so surface the resume error directly. When the future is resolved, future_get_qmp_reply() + * already encodes success (1), QMP-level error (-EIO with the desc captured if asked for), + * and no-reply (negative future result) — pass it through. */ + if (sd_future_state(call) != SD_FUTURE_RESOLVED) + return r; + + return future_get_qmp_reply(call, ret_result, ret_error_desc); +} + +int qmp_client_call( + QmpClient *c, + const char *command, + QmpClientArgs *args, + sd_json_variant **ret_result, + char **reterr_error_desc) { + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; + _cleanup_free_ char *desc = NULL; + int r; + + assert_return(c, -EINVAL); + assert_return(command, -EINVAL); + + /* If we're on a fiber sharing the QMP client's event loop, use the async + suspend path so + * multiple concurrent qmp_client_call() invocations across fibers don't deadlock each other + * on the process+wait pump. */ + if (sd_fiber_is_running() && qmp_client_get_event(c) == sd_fiber_get_event()) + return qmp_client_call_suspend(c, command, args, ret_result, reterr_error_desc); + + _cleanup_(qmp_slot_unrefp) QmpSlot *slot = NULL; + + /* 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; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *current = TAKE_PTR(c->current); + r = qmp_parse_response(current, &result, &desc); + if (r < 0 && r != -EIO) + return r; + + /* QMP-level error: copy the description into *ret_error_desc when the caller asked for it, + * and surface the failure via the return value (matching qmp_client_call_suspend() / + * sd_bus_call()'s "negative on error" convention). */ + if (desc) { + if (reterr_error_desc) + *reterr_error_desc = TAKE_PTR(desc); + return -EIO; + } + + if (ret_result) + *ret_result = TAKE_PTR(result); + if (reterr_error_desc) + *reterr_error_desc = NULL; + + 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..0bf435384caaf --- /dev/null +++ b/src/shared/qmp-client.h @@ -0,0 +1,106 @@ +/* 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); + +int qmp_client_call( + QmpClient *client, + const char *command, + QmpClientArgs *args, + sd_json_variant **ret_result, + char **reterr_error_desc); + +int qmp_client_call_future( + QmpClient *client, + const char *command, + QmpClientArgs *args, + sd_future **ret); + +int future_get_qmp_reply( + sd_future *f, + sd_json_variant **ret_result, + char **reterr_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..44d674ba6a3ae 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" @@ -20,24 +22,24 @@ #define UNICODE_UPPER_HALF_BLOCK UTF8("▀") #if HAVE_QRENCODE -static void *qrcode_dl = NULL; - 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 + static void *qrcode_dl = NULL; 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 +48,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 +210,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..e1d51559d0434 100644 --- a/src/shared/reread-partition-table.c +++ b/src/shared/reread-partition-table.c @@ -179,7 +179,7 @@ static int process_partition( * just to make a point. */ partition = sd_device_unref(partition); - r = block_device_remove_partition(fd, subnode, nr); + r = block_device_remove_partition(fd, nr); if (r < 0) return log_device_debug_errno(d, r, "Failed to remove kernel partition device '%s' in order to recreate it: %m", subnode); @@ -189,7 +189,7 @@ static int process_partition( log_device_debug(d, "Adding partition %i...", nr); - r = block_device_add_partition(fd, subnode, nr, (uint64_t) start * 512U, (uint64_t) size * 512U); + r = block_device_add_partition(fd, nr, (uint64_t) start * 512U, (uint64_t) size * 512U); if (r < 0) return log_device_debug_errno(d, r, "Failed to add kernel partition device %i to partition table values: %m", nr); @@ -228,7 +228,7 @@ static int remove_partitions(sd_device *d, int fd, sd_device_enumerator *e, Set log_device_debug(partition, "Kernel knows partition %u which we didn't find, removing.", nr); - r = block_device_remove_partition(fd, devname, (int) nr); + r = block_device_remove_partition(fd, (int) nr); if (r < 0) /* NB: when logging we use the parent device below, after all the partition device ceased existing by now, most likely */ RET_GATHER(ret, log_device_debug_errno(d, r, "Failed to remove kernel partition device '%s' that vanished from partition table: %m", devname)); else { @@ -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/resize-fs.c b/src/shared/resize-fs.c index 147af7ec33da6..7a7854666486f 100644 --- a/src/shared/resize-fs.c +++ b/src/shared/resize-fs.c @@ -10,7 +10,7 @@ #include "resize-fs.h" #include "stat-util.h" #include "stdio-util.h" -#include "string-util-fundamental.h" +#include "string-util.h" int resize_fs(int fd, uint64_t sz, uint64_t *ret_size) { struct statfs sfs; diff --git a/src/shared/resolve-varlink-util.c b/src/shared/resolve-varlink-util.c new file mode 100644 index 0000000000000..779770a523c18 --- /dev/null +++ b/src/shared/resolve-varlink-util.c @@ -0,0 +1,376 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "dns-packet.h" +#include "iovec-util.h" +#include "json-util.h" +#include "resolve-varlink-util.h" +#include "strv.h" + +void resolve_error_done(ResolveError *error) { + if (!error) + return; + + error->ede_msg = mfree(error->ede_msg); + error->query_string = mfree(error->query_string); + error->result = mfree(error->result); +} + +int dispatch_resolve_error(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolve_error_dispatch_table[] = { + { "rcode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(ResolveError, rcode), 0 }, + { "extendedDNSErrorCode", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(ResolveError, ede_rcode), 0 }, + { "extendedDNSErrorMessage", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolveError, ede_msg), 0 }, + { "queryString", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolveError, query_string), 0 }, + { "result", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolveError, result), 0 }, + {}, + }; + ResolveError *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolve_error_done) ResolveError error = { + .rcode = _DNS_RCODE_INVALID, + .ede_rcode = _DNS_EDE_RCODE_INVALID, + }; + r = sd_json_dispatch(variant, resolve_error_dispatch_table, flags | SD_JSON_ALLOW_EXTENSIONS, &error); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(error); + return 0; +} + +static int dispatch_resolved_address(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolved_address_dispatch_table[] = { + { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(ResolvedAddress, ifindex), SD_JSON_RELAX }, + { "family", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_address_family, offsetof(ResolvedAddress, family), SD_JSON_MANDATORY }, + { "address", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_in_addr_data, offsetof(ResolvedAddress, in_addr), SD_JSON_MANDATORY }, + {}, + }; + ResolvedAddress *ret = ASSERT_PTR(userdata); + int r; + + ResolvedAddress address = {}; + r = sd_json_dispatch(variant, resolved_address_dispatch_table, flags & ~SD_JSON_MANDATORY, &address); + if (r < 0) + return r; + + if (address.family != address.in_addr.family) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "'%s' address and family are inconsistent.", strna(name)); + + *ret = TAKE_STRUCT(address); + return 0; +} + +static int dispatch_resolved_address_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + ResolveHostnameReply *reply = ASSERT_PTR(userdata); + int r; + + sd_json_variant *v; + JSON_VARIANT_ARRAY_FOREACH(v, variant) { + if (!GREEDY_REALLOC0(reply->addresses, reply->n_addresses + 1)) + return json_log_oom(variant, flags); + + r = dispatch_resolved_address(name, v, flags, &reply->addresses[reply->n_addresses++]); + if (r < 0) + return r; + } + + return 0; +} + +void resolve_hostname_reply_done(ResolveHostnameReply *reply) { + if (!reply) + return; + + reply->name = mfree(reply->name); + reply->addresses = mfree(reply->addresses); + reply->n_addresses = 0; +} + +int dispatch_resolve_hostname_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolve_hostname_reply_dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolveHostnameReply, name), SD_JSON_MANDATORY }, + { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ResolveHostnameReply, flags), SD_JSON_MANDATORY }, + { "addresses", SD_JSON_VARIANT_ARRAY, dispatch_resolved_address_array, 0, SD_JSON_MANDATORY }, + {}, + }; + ResolveHostnameReply *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolve_hostname_reply_done) ResolveHostnameReply reply = {}; + r = sd_json_dispatch(variant, resolve_hostname_reply_dispatch_table, flags | SD_JSON_ALLOW_EXTENSIONS, &reply); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(reply); + return 0; +} + +static void resolved_name_done(ResolvedName *name) { + if (!name) + return; + + name->name = mfree(name->name); +} + +static int dispatch_resolved_name(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolved_name_dispatch_table[] = { + { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(ResolvedName, ifindex), SD_JSON_RELAX }, + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolvedName, name), SD_JSON_MANDATORY }, + {}, + }; + ResolvedName *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolved_name_done) ResolvedName resolved_name = {}; + r = sd_json_dispatch(variant, resolved_name_dispatch_table, flags & ~SD_JSON_MANDATORY, &resolved_name); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(resolved_name); + return 0; +} + +static int dispatch_resolved_name_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + ResolveAddressReply *reply = ASSERT_PTR(userdata); + int r; + + sd_json_variant *v; + JSON_VARIANT_ARRAY_FOREACH(v, variant) { + if (!GREEDY_REALLOC0(reply->names, reply->n_names + 1)) + return json_log_oom(variant, flags); + + r = dispatch_resolved_name(name, v, flags, &reply->names[reply->n_names++]); + if (r < 0) + return r; + } + + return 0; +} + +void resolve_address_reply_done(ResolveAddressReply *reply) { + if (!reply) + return; + + FOREACH_ARRAY(n, reply->names, reply->n_names) + resolved_name_done(n); + reply->names = mfree(reply->names); + reply->n_names = 0; +} + +int dispatch_resolve_address_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolve_address_reply_dispatch_table[] = { + { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ResolveAddressReply, flags), SD_JSON_MANDATORY }, + { "names", SD_JSON_VARIANT_ARRAY, dispatch_resolved_name_array, 0, SD_JSON_MANDATORY }, + {}, + }; + ResolveAddressReply *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolve_address_reply_done) ResolveAddressReply reply = {}; + r = sd_json_dispatch(variant, resolve_address_reply_dispatch_table, flags | SD_JSON_ALLOW_EXTENSIONS, &reply); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(reply); + return 0; +} + +static void resolved_record_done(ResolvedRecord *record) { + if (!record) + return; + + iovec_done(&record->raw); +} + +static int dispatch_resolved_record(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolved_record_dispatch_table[] = { + { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(ResolvedRecord, ifindex), SD_JSON_RELAX }, + { "raw", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_unbase64_iovec, offsetof(ResolvedRecord, raw), SD_JSON_MANDATORY }, + { "rr", _SD_JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, + {}, + }; + ResolvedRecord *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolved_record_done) ResolvedRecord record = {}; + r = sd_json_dispatch(variant, resolved_record_dispatch_table, flags & ~SD_JSON_MANDATORY, &record); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(record); + return 0; +} + +static int dispatch_resolved_record_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + ResolveRecordReply *reply = ASSERT_PTR(userdata); + int r; + + sd_json_variant *v; + JSON_VARIANT_ARRAY_FOREACH(v, variant) { + if (!GREEDY_REALLOC0(reply->records, reply->n_records + 1)) + return json_log_oom(variant, flags); + + r = dispatch_resolved_record(name, v, flags, &reply->records[reply->n_records++]); + if (r < 0) + return r; + } + + return 0; +} + +void resolve_record_reply_done(ResolveRecordReply *reply) { + if (!reply) + return; + + FOREACH_ARRAY(record, reply->records, reply->n_records) + resolved_record_done(record); + reply->records = mfree(reply->records); + reply->n_records = 0; +} + +int dispatch_resolve_record_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolve_record_reply_dispatch_table[] = { + { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ResolveRecordReply, flags), SD_JSON_MANDATORY }, + { "rrs", SD_JSON_VARIANT_ARRAY, dispatch_resolved_record_array, 0, SD_JSON_MANDATORY }, + {}, + }; + ResolveRecordReply *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolve_record_reply_done) ResolveRecordReply reply = {}; + r = sd_json_dispatch(variant, resolve_record_reply_dispatch_table, flags | SD_JSON_ALLOW_EXTENSIONS, &reply); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(reply); + return 0; +} + +static void resolved_service_done(ResolvedService *service) { + if (!service) + return; + + service->hostname = mfree(service->hostname); + service->canonical_name = mfree(service->canonical_name); + service->addresses = mfree(service->addresses); + service->n_addresses = 0; +} + +static int dispatch_resolved_service_address_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + ResolvedService *service = ASSERT_PTR(userdata); + int r; + + sd_json_variant *v; + JSON_VARIANT_ARRAY_FOREACH(v, variant) { + if (!GREEDY_REALLOC0(service->addresses, service->n_addresses + 1)) + return json_log_oom(variant, flags); + + r = dispatch_resolved_address(name, v, flags, &service->addresses[service->n_addresses++]); + if (r < 0) + return r; + } + + return 0; +} + +static int dispatch_resolved_service(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolved_service_dispatch_table[] = { + { "priority", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16, offsetof(ResolvedService, priority), SD_JSON_MANDATORY }, + { "weight", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16, offsetof(ResolvedService, weight), SD_JSON_MANDATORY }, + { "port", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16, offsetof(ResolvedService, port), SD_JSON_MANDATORY }, + { "hostname", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolvedService, hostname), SD_JSON_MANDATORY }, + { "canonicalName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolvedService, canonical_name), SD_JSON_NULLABLE }, + { "addresses", SD_JSON_VARIANT_ARRAY, dispatch_resolved_service_address_array, 0, SD_JSON_NULLABLE }, + {}, + }; + ResolvedService *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolved_service_done) ResolvedService service = {}; + r = sd_json_dispatch(variant, resolved_service_dispatch_table, flags & ~SD_JSON_MANDATORY, &service); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(service); + return 0; +} + +static int dispatch_resolved_service_array(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + ResolveServiceReply *reply = ASSERT_PTR(userdata); + int r; + + sd_json_variant *v; + JSON_VARIANT_ARRAY_FOREACH(v, variant) { + if (!GREEDY_REALLOC0(reply->services, reply->n_services + 1)) + return json_log_oom(variant, flags); + + r = dispatch_resolved_service(name, v, flags, &reply->services[reply->n_services++]); + if (r < 0) + return r; + } + + return 0; +} + +static void resolved_canonical_done(ResolvedCanonical *canonical) { + if (!canonical) + return; + + canonical->name = mfree(canonical->name); + canonical->type = mfree(canonical->type); + canonical->domain = mfree(canonical->domain); +} + +static int dispatch_resolved_canonical(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolved_canonical_dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolvedCanonical, name), SD_JSON_NULLABLE }, + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolvedCanonical, type), SD_JSON_MANDATORY }, + { "domain", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(ResolvedCanonical, domain), SD_JSON_MANDATORY }, + {}, + }; + ResolvedCanonical *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolved_canonical_done) ResolvedCanonical canonical = {}; + r = sd_json_dispatch(variant, resolved_canonical_dispatch_table, flags & ~SD_JSON_MANDATORY, &canonical); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(canonical); + return 0; +} + +void resolve_service_reply_done(ResolveServiceReply *reply) { + if (!reply) + return; + + FOREACH_ARRAY(service, reply->services, reply->n_services) + resolved_service_done(service); + reply->services = mfree(reply->services); + reply->n_services = 0; + reply->txt = strv_free(reply->txt); + resolved_canonical_done(&reply->canonical); +} + +int dispatch_resolve_service_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field resolve_service_reply_dispatch_table[] = { + { "services", SD_JSON_VARIANT_ARRAY, dispatch_resolved_service_array, 0, SD_JSON_MANDATORY }, + { "txt", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(ResolveServiceReply, txt), SD_JSON_NULLABLE }, + { "canonical", SD_JSON_VARIANT_OBJECT, dispatch_resolved_canonical, offsetof(ResolveServiceReply, canonical), SD_JSON_MANDATORY }, + { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(ResolveServiceReply, flags), SD_JSON_MANDATORY }, + {}, + }; + ResolveServiceReply *ret = ASSERT_PTR(userdata); + int r; + + _cleanup_(resolve_service_reply_done) ResolveServiceReply reply = {}; + r = sd_json_dispatch(variant, resolve_service_reply_dispatch_table, flags | SD_JSON_ALLOW_EXTENSIONS, &reply); + if (r < 0) + return r; + + *ret = TAKE_STRUCT(reply); + return 0; +} diff --git a/src/shared/resolve-varlink-util.h b/src/shared/resolve-varlink-util.h new file mode 100644 index 0000000000000..a05e18daea516 --- /dev/null +++ b/src/shared/resolve-varlink-util.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "in-addr-util.h" +#include "shared-forward.h" + +typedef struct ResolveError { + int rcode; + int ede_rcode; + char *ede_msg; + char *query_string; + char *result; +} ResolveError; + +void resolve_error_done(ResolveError *error); + +int dispatch_resolve_error(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); + +typedef struct ResolvedAddress { + int ifindex; + int family; + struct in_addr_data in_addr; +} ResolvedAddress; + +typedef struct ResolveHostnameReply { + char *name; + uint64_t flags; + ResolvedAddress *addresses; + size_t n_addresses; +} ResolveHostnameReply; + +void resolve_hostname_reply_done(ResolveHostnameReply *reply); + +int dispatch_resolve_hostname_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); + +typedef struct ResolvedName { + int ifindex; + char *name; +} ResolvedName; + +typedef struct ResolveAddressReply { + uint64_t flags; + ResolvedName *names; + size_t n_names; +} ResolveAddressReply; + +void resolve_address_reply_done(ResolveAddressReply *reply); + +int dispatch_resolve_address_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); + +typedef struct ResolvedRecord { + int ifindex; + struct iovec raw; +} ResolvedRecord; + +typedef struct ResolveRecordReply { + uint64_t flags; + ResolvedRecord *records; + size_t n_records; +} ResolveRecordReply; + +void resolve_record_reply_done(ResolveRecordReply *reply); + +int dispatch_resolve_record_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); + +typedef struct ResolvedService { + uint16_t priority; + uint16_t weight; + uint16_t port; + char *hostname; + char *canonical_name; + ResolvedAddress *addresses; + size_t n_addresses; +} ResolvedService; + +typedef struct ResolvedCanonical { + char *name; + char *type; + char *domain; +} ResolvedCanonical; + +typedef struct ResolveServiceReply { + ResolvedService *services; + size_t n_services; + char **txt; + ResolvedCanonical canonical; + uint64_t flags; +} ResolveServiceReply; + +void resolve_service_reply_done(ResolveServiceReply *reply); + +int dispatch_resolve_service_reply(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c index f375d25428c4b..573d6aeb04557 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) @@ -227,10 +228,14 @@ static int rm_rf_inner_child( r = btrfs_subvol_remove_at(fd, fname, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); if (r < 0) { - if (!IN_SET(r, -ENOTTY, -EINVAL)) + if (!IN_SET(r, -ENOTTY, -EINVAL, -EPERM, -EACCES)) return r; - /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */ + /* ENOTTY, then it wasn't a btrfs subvolume. EPERM/EACCES means we lack + * the privileges for the destroy ioctl (no CAP_SYS_ADMIN and no + * 'user_subvol_rm_allowed'); btrfs_subvol_remove_at() will have cleared + * the read-only flag where it could, so fall through and try to empty the + * subvolume recursively and rmdir() it, which an unprivileged owner may do. */ } else /* It was a subvolume, done. */ return 1; @@ -268,6 +273,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..0ba0d6cd91c0f 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" @@ -32,8 +34,6 @@ #include "strv.h" #if HAVE_SECCOMP -static void *libseccomp_dl = NULL; - DLSYM_PROTOTYPE(seccomp_api_get) = NULL; DLSYM_PROTOTYPE(seccomp_arch_add) = NULL; DLSYM_PROTOTYPE(seccomp_arch_exist) = NULL; @@ -48,31 +48,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 +250,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 +315,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 +1065,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 +1145,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 +1211,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,12 +1334,12 @@ 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; - log_syntax(unit, FLAGS_SET(flags, SECCOMP_PARSE_LOG) ? LOG_WARNING : LOG_DEBUG, filename, line, r, + log_syntax(unit, FLAGS_SET(flags, SECCOMP_PARSE_LOG) ? LOG_INFO : LOG_DEBUG, filename, line, r, "System call %s cannot be resolved as libseccomp is not available, ignoring: %m", name); return 0; } @@ -1415,7 +1392,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 +1520,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 +1572,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 +1609,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 +1795,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 +1900,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 +2020,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 +2113,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 +2162,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 +2200,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 +2416,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 +2543,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 +2600,35 @@ int seccomp_suppress_sync(void) { #endif +int dlopen_libseccomp(int log_level) { +#if HAVE_SECCOMP + static void *libseccomp_dl = NULL; + + LIBSECCOMP_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + + 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..89dcaf5ca74b0 100644 --- a/src/shared/seccomp-util.h +++ b/src/shared/seccomp-util.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dlopen.h" + #include "errno-util.h" #include "shared-forward.h" @@ -23,8 +25,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); @@ -157,18 +157,29 @@ int parse_syscall_and_errno(const char *in, char **name, int *error); int seccomp_suppress_sync(void); +#define LIBSECCOMP_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("seccomp", \ + "Support for Seccomp Sandboxes", \ + priority, \ + "libseccomp.so.2") + +#define DLOPEN_LIBSECCOMP(log_level, priority) \ + ({ \ + LIBSECCOMP_NOTE(priority); \ + dlopen_libseccomp(log_level); \ + }) #else static inline bool is_seccomp_available(void) { return false; } -static inline int dlopen_libseccomp(void) { - return -EOPNOTSUPP; -} +#define DLOPEN_LIBSECCOMP(log_level, priority) dlopen_libseccomp(log_level) #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..eb1f28a814429 100644 --- a/src/shared/selinux-util.c +++ b/src/shared/selinux-util.c @@ -1,30 +1,34 @@ /* 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); @@ -50,8 +54,6 @@ static int mac_selinux_label_post(int dir_fd, const char *path, bool created) { return 0; } -static void *libselinux_dl = NULL; - DLSYM_PROTOTYPE(avc_open) = NULL; DLSYM_PROTOTYPE(context_free) = NULL; DLSYM_PROTOTYPE(context_new) = NULL; @@ -86,17 +88,18 @@ 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", - "Support for SELinux", - ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, - "libselinux.so.1"); +int dlopen_libselinux(int log_level) { +#if HAVE_SELINUX + static void *libselinux_dl = NULL; + + LIBSELINUX_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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 +134,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 +163,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 +189,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 +303,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 +353,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 +361,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 +403,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 +503,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..98cfb6bbe40b4 100644 --- a/src/shared/selinux-util.h +++ b/src/shared/selinux-util.h @@ -3,6 +3,8 @@ #include +#include "sd-dlopen.h" + #include "shared-forward.h" #if HAVE_SELINUX @@ -13,8 +15,6 @@ #include "dlfcn-util.h" -int dlopen_libselinux(void); - extern DLSYM_PROTOTYPE(avc_open); extern DLSYM_PROTOTYPE(context_free); extern DLSYM_PROTOTYPE(context_new); @@ -52,17 +52,29 @@ extern DLSYM_PROTOTYPE(string_to_security_class); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(char*, sym_freecon, freeconp, NULL); +#define LIBSELINUX_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("selinux", \ + "Support for SELinux", \ + priority, \ + "libselinux.so.1") + +#define DLOPEN_LIBSELINUX(log_level, priority) \ + ({ \ + LIBSELINUX_NOTE(priority); \ + dlopen_libselinux(log_level); \ + }) #else -static inline int dlopen_libselinux(void) { - return -EOPNOTSUPP; -} static inline void freeconp(char **p) { assert(*p == NULL); } + +#define DLOPEN_LIBSELINUX(log_level, priority) dlopen_libselinux(log_level) #endif +int dlopen_libselinux(int log_level); + #define _cleanup_freecon_ _cleanup_(freeconp) /* This accepts 0 error, like _zerook(). */ diff --git a/src/shared/service-util.c b/src/shared/service-util.c index 70d59af6c51d1..7c1df8e73adad 100644 --- a/src/shared/service-util.c +++ b/src/shared/service-util.c @@ -1,53 +1,53 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include -#include "alloc-util.h" #include "build.h" #include "bus-object.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" -#include "pretty-print.h" +#include "options.h" #include "runtime-scope.h" #include "service-util.h" -typedef enum HelpFlags { - HELP_WITH_BUS_INTROSPECT = 1 << 0, - HELP_WITH_RUNTIME_SCOPE = 1 << 1, -} HelpFlags; - -static int help(const char *program_path, - const char *service, +static int help(const char *service, const char *description, - HelpFlags flags) { + bool with_bus_introspect, + bool with_runtime_scope) { - _cleanup_free_ char *link = NULL; + static const char* const groups[] = { + NULL, + "Bus introspection", + "Runtime scope", + }; + + bool conds[ELEMENTSOF(groups)] = { true, with_bus_introspect, with_runtime_scope }; + Table* tables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(tables, table_unref_array_clear); int r; - r = terminal_urlify_man(service, "8", &link); - if (r < 0) - return log_oom(); - - printf("%1$s [OPTIONS...]\n" - "\n%5$s%7$s%6$s\n" - "\nThis program takes no positional arguments.\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - "%8$s" - "%9$s" - "\nSee the %2$s for details.\n", - program_path, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal(), - description, - FLAGS_SET(flags, HELP_WITH_BUS_INTROSPECT) ? " --bus-introspect=PATH Write D-Bus XML introspection data\n" : "", - FLAGS_SET(flags, HELP_WITH_RUNTIME_SCOPE) ? " --system Start service in system mode\n" - " --user Start service in user mode\n" : ""); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) + if (conds[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[1] ? tables[2] : NULL); + + help_cmdline("[OPTIONS...]"); + help_abstract(description); + help_section("Options"); + for (size_t i = 0; i < ELEMENTSOF(groups); i++) + if (conds[i]) { + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + + help_man_page_reference(service, "8"); return 0; /* No further action */ } @@ -58,64 +58,50 @@ int service_parse_argv( RuntimeScope *runtime_scope, int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_BUS_INTROSPECT, - ARG_SYSTEM, - ARG_USER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "bus-introspect", required_argument, NULL, ARG_BUS_INTROSPECT }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - {} - }; - - int c; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - return help(argv[0], - service, + OPTION_COMMON_HELP: + return help(service, description, - (bus_objects ? HELP_WITH_BUS_INTROSPECT : 0) | - (runtime_scope ? HELP_WITH_RUNTIME_SCOPE : 0)); + /* with_bus_introspect= */ bus_objects, + /* with_runtime_scope= */ runtime_scope); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_BUS_INTROSPECT: - return bus_introspect_implementations( - stdout, - optarg, - bus_objects); + OPTION_GROUP("Bus introspection"): {} - case ARG_SYSTEM: - case ARG_USER: - if (!runtime_scope) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This service cannot be run in --system or --user mode, refusing."); + OPTION_LONG("bus-introspect", "PATH", "Write D-Bus XML introspection data"): + /* The option is defined in the shared option table, but it's not supported in this binary, + * so we pretend it doesn't exist. */ + if (!bus_objects) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This service does not support the --bus-introspect= option."); - *runtime_scope = c == ARG_SYSTEM ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER; - break; + return bus_introspect_implementations(stdout, opts.arg, bus_objects); - case '?': - return -EINVAL; + OPTION_GROUP("Runtime scope"): {} - default: - assert_not_reached(); + OPTION_LONG_DATA("system", NULL, /* data= */ RUNTIME_SCOPE_SYSTEM, + "Start service in system mode"): {} + OPTION_LONG_DATA("user", NULL, /* data= */ RUNTIME_SCOPE_USER, + "Start service in user mode"): + if (!runtime_scope) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This service does not support the --system/--user options."); + + *runtime_scope = opts.opt->data; + break; } - if (optind < argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "This program takes no arguments."); + if (option_parser_get_n_args(&opts) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); return 1; /* Further action */ } diff --git a/src/shared/shared-forward.h b/src/shared/shared-forward.h index e07b25c056aac..751a6f71dc359 100644 --- a/src/shared/shared-forward.h +++ b/src/shared/shared-forward.h @@ -14,6 +14,7 @@ struct in_addr_full; typedef enum AskPasswordFlags AskPasswordFlags; typedef enum BootEntryTokenType BootEntryTokenType; +typedef enum BootEntrySource BootEntrySource; typedef enum BusPrintPropertyFlags BusPrintPropertyFlags; typedef enum BusTransport BusTransport; typedef enum CatFlags CatFlags; @@ -45,8 +46,10 @@ 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 BootEntry BootEntry; typedef struct BPFProgram BPFProgram; typedef struct BusObjectImplementation BusObjectImplementation; typedef struct CalendarSpec CalendarSpec; @@ -54,6 +57,8 @@ typedef struct Condition Condition; typedef struct ConfigSection ConfigSection; typedef struct ConfigTableItem ConfigTableItem; typedef struct CPUSet CPUSet; +typedef struct CurlGlue CurlGlue; +typedef struct CurlSlot CurlSlot; typedef struct DissectedImage DissectedImage; typedef struct DnsAnswer DnsAnswer; typedef struct DnsPacket DnsPacket; @@ -78,6 +83,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..6380a4e3af65e 100644 --- a/src/shared/smack-util.c +++ b/src/shared/smack-util.c @@ -13,7 +13,6 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" -#include "label.h" #include "label-util.h" #include "log.h" #include "path-util.h" @@ -149,16 +148,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-label.c b/src/shared/socket-label.c index 3f2ac99b9b0ec..b06b00e08577b 100644 --- a/src/shared/socket-label.c +++ b/src/shared/socket-label.c @@ -7,14 +7,16 @@ #include "fd-util.h" #include "fs-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "parse-util.h" #include "selinux-util.h" #include "smack-util.h" #include "socket-label.h" #include "socket-util.h" #include "string-table.h" +#include "strv.h" #include "umask-util.h" +#include "xattr-util.h" static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX] = { [SOCKET_ADDRESS_DEFAULT] = "default", @@ -36,6 +38,32 @@ SocketAddressBindIPv6Only socket_address_bind_ipv6_only_or_bool_from_string(cons return socket_address_bind_ipv6_only_from_string(s); } +int socket_set_xattrs(int fd, const char *path, char **xattrs) { + int r; + + assert(wildcard_fd_is_valid(fd)); + + if (strv_isempty(xattrs)) + return 0; + + r = socket_xattr_supported(); + if (r <= 0) + return r; + + int c = 0; + STRV_FOREACH_PAIR(name, value, xattrs) { + r = xsetxattr(fd, path, AT_EMPTY_PATH, *name, *value); + if (r < 0) { + log_debug_errno(r, "Failed to set extended attribute '%s' on socket, ignoring: %m", *name); + continue; + } + + c++; + } + + return c; +} + int socket_address_listen( const SocketAddress *a, int flags, @@ -48,7 +76,9 @@ int socket_address_listen( mode_t directory_mode, mode_t socket_mode, const char *selinux_label, - const char *smack_label) { + const char *smack_label, + char **xattr_entrypoint, + char **xattr_listen) { _cleanup_close_ int fd = -EBADF; const char *p; @@ -119,6 +149,8 @@ int socket_address_listen( if (r < 0) return r; + (void) socket_set_xattrs(fd, /* path= */ NULL, xattr_listen); + p = socket_address_get_path(a); if (p) { /* Create parents */ @@ -138,11 +170,14 @@ int socket_address_listen( if (r < 0) return r; } + if (smack_label) { r = mac_smack_apply(p, SMACK_ATTR_ACCESS, smack_label); if (r < 0) log_warning_errno(r, "Failed to apply SMACK label for socket path, ignoring: %m"); } + + (void) socket_set_xattrs(AT_FDCWD, p, xattr_entrypoint); } else { if (bind(fd, &a->sockaddr.sa, a->size) < 0) return -errno; diff --git a/src/shared/socket-label.h b/src/shared/socket-label.h index 48a500c081830..15dc9d86fe7c2 100644 --- a/src/shared/socket-label.h +++ b/src/shared/socket-label.h @@ -26,4 +26,8 @@ int socket_address_listen( mode_t directory_mode, mode_t socket_mode, const char *selinux_label, - const char *smack_label); + const char *smack_label, + char **xattr_entrypoint, + char **xattr_listen); + +int socket_set_xattrs(int fd, const char *path, char **xattrs); diff --git a/src/shared/socket-netlink.c b/src/shared/socket-netlink.c index 885606b6e0da2..a9b31744180f2 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); @@ -195,7 +198,9 @@ int make_socket_fd(int log_level, const char* address, int type, int flags) { 0755, 0644, /* selinux_label= */ NULL, - /* smack_label= */ NULL); + /* smack_label= */ NULL, + /* xattr_entrypoint= */ NULL, + /* xattr_listen= */ NULL); if (fd < 0 || log_get_max_level() >= log_level) { _cleanup_free_ char *p = NULL; @@ -348,7 +353,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 +362,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 +375,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 +501,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..9d2066899f2fe 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; @@ -343,7 +347,7 @@ int specifier_user_name(char specifier, const void *data, const char *root, cons * to be able to run this in PID 1, where our user ID is 0, but where NSS lookups are not allowed. * We don't use getusername_malloc() here, because we don't want to look at $USER, to remain - * consistent with specifer_user_id() below. + * consistent with specifier_user_id() below. */ t = uid_to_name(uid); diff --git a/src/shared/ssl-util.c b/src/shared/ssl-util.c new file mode 100644 index 0000000000000..4b7ab2ec29f74 --- /dev/null +++ b/src/shared/ssl-util.c @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-dlopen.h" + +#include "log.h" /* IWYU pragma: keep */ +#include "ssl-util.h" +#include "strv.h" + +#if HAVE_OPENSSL + +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 + static void *libssl_dl = NULL; + int r; + + LIBSSL_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); + + FOREACH_STRING(soname, "libssl.so.4", "libssl.so.3") { + r = dlopen_many_sym_or_warn( + &libssl_dl, + soname, + 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)); + if (r >= 0) + break; + } + if (r < 0) { + log_full_errno(log_level, r, "Neither libssl.so.4 nor libssl.so.3 could be loaded"); + return -EOPNOTSUPP; /* turn into recognizable error */ + } + + return 0; +#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..857d4b5e17356 --- /dev/null +++ b/src/shared/ssl-util.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-dlopen.h" + +#include "shared-forward.h" + +int dlopen_libssl(int log_level); + +#if HAVE_OPENSSL +#define LIBSSL_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("libssl", \ + "Support for TLS", \ + priority, \ + "libssl.so.4", "libssl.so.3") + +#define DLOPEN_LIBSSL(log_level, priority) \ + ({ \ + LIBSSL_NOTE(priority); \ + dlopen_libssl(log_level); \ + }) + +# 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); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(SSL_CTX*, sym_SSL_CTX_free, SSL_CTX_freep, NULL); + +#else +#define DLOPEN_LIBSSL(log_level, priority) dlopen_libssl(log_level) +#endif diff --git a/src/shared/storage-util.c b/src/shared/storage-util.c new file mode 100644 index 0000000000000..3a3c1e6c57d3f --- /dev/null +++ b/src/shared/storage-util.c @@ -0,0 +1,130 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "json-util.h" +#include "machine-util.h" +#include "path-lookup.h" +#include "path-util.h" +#include "runtime-scope.h" +#include "string-table.h" +#include "storage-util.h" + +static const char *volume_type_table[_VOLUME_TYPE_MAX] = { + [VOLUME_BLK] = "blk", + [VOLUME_REG] = "reg", + [VOLUME_DIR] = "dir", +}; + +static const char *create_mode_table[_CREATE_MODE_MAX] = { + [CREATE_ANY] = "any", + [CREATE_NEW] = "new", + [CREATE_OPEN] = "open", +}; + +DEFINE_STRING_TABLE_LOOKUP(volume_type, VolumeType); +DEFINE_STRING_TABLE_LOOKUP(create_mode, CreateMode); + +JSON_DISPATCH_ENUM_DEFINE(json_dispatch_volume_type, VolumeType, volume_type_from_string); +JSON_DISPATCH_ENUM_DEFINE(json_dispatch_create_mode, CreateMode, create_mode_from_string); + +void storage_acquire_reply_done(StorageAcquireReply *reply) { + if (!reply) + return; + + reply->fd = safe_close(reply->fd); +} + +int storage_acquire_volume( + RuntimeScope scope, + const BindVolume *bv, + bool allow_interactive_auth, + char **reterr_error_id, + StorageAcquireReply *ret) { + + int r; + + assert(bv); + assert(bv->provider); + assert(bv->volume); + assert(ret); + + /* Defense-in-depth: this is a libshared helper that may grow new callers; reject + * provider names that could escape the StorageProvider runtime directory. */ + if (!storage_provider_name_is_valid(bv->provider)) + return -EINVAL; + + _cleanup_free_ char *socket_path = NULL; + r = runtime_directory_generic(scope, "systemd/io.systemd.StorageProvider", &socket_path); + if (r < 0) + return r; + + if (!path_extend(&socket_path, bv->provider)) + return -ENOMEM; + + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + r = sd_varlink_connect_address(&link, socket_path); + if (r < 0) + return r; + + r = sd_varlink_set_allow_fd_passing_input(link, true); + if (r < 0) + return r; + + sd_json_variant *mreply = NULL; + const char *merror_id = NULL; + r = sd_varlink_callbo( + link, + "io.systemd.StorageProvider.Acquire", + &mreply, + &merror_id, + SD_JSON_BUILD_PAIR_STRING("name", bv->volume), + JSON_BUILD_PAIR_CONDITION_STRING(bv->create_mode >= 0, "createMode", create_mode_to_string(bv->create_mode)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("template", bv->template), + JSON_BUILD_PAIR_TRISTATE_NON_NULL("readOnly", read_only_mode_to_tristate(bv->read_only)), + JSON_BUILD_PAIR_CONDITION_STRING(bv->request_as >= 0, "requestAs", volume_type_to_string(bv->request_as)), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("createSizeBytes", bv->create_size_bytes, UINT64_MAX), + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", allow_interactive_auth)); + if (r < 0) + return r; + + if (merror_id) { + if (reterr_error_id) { + char *copy = strdup(merror_id); + if (!copy) + return -ENOMEM; + *reterr_error_id = copy; + } + + r = sd_varlink_error_to_errno(merror_id, mreply); + return r == -EBADR ? -EPROTO : r; + } + + /* tmp.fd holds the JSON fd index until sd_varlink_take_fd() swaps it for the real fd. */ + StorageAcquireReply tmp = STORAGE_ACQUIRE_REPLY_INIT; + + static const sd_json_dispatch_field dispatch_table[] = { + { "fileDescriptorIndex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, voffsetof(StorageAcquireReply, fd), SD_JSON_MANDATORY }, + { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, voffsetof(StorageAcquireReply, read_only), 0 }, + { "type", SD_JSON_VARIANT_STRING, json_dispatch_volume_type, voffsetof(StorageAcquireReply, type), SD_JSON_MANDATORY }, + { "baseUID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, voffsetof(StorageAcquireReply, base_uid), 0 }, + { "baseGID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, voffsetof(StorageAcquireReply, base_gid), 0 }, + {} + }; + + r = sd_json_dispatch(mreply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &tmp); + if (r < 0) + return r; + if (tmp.fd < 0) + return -EBADMSG; + + _cleanup_close_ int fd = sd_varlink_take_fd(link, tmp.fd); + if (fd < 0) + return fd; + + tmp.fd = TAKE_FD(fd); + *ret = tmp; + return 0; +} diff --git a/src/shared/storage-util.h b/src/shared/storage-util.h new file mode 100644 index 0000000000000..fe78112d24b82 --- /dev/null +++ b/src/shared/storage-util.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-json.h" + +#include "string-util.h" + +/* This closely follows the kernel's inode type naming, i.e. is supposed to be a subset of what + * inode_type_from_string() parses. */ +typedef enum VolumeType { + VOLUME_BLK, + VOLUME_REG, + VOLUME_DIR, + _VOLUME_TYPE_MAX, + _VOLUME_TYPE_INVALID = -EINVAL, +} VolumeType; + +typedef enum CreateMode { + CREATE_ANY, + CREATE_NEW, + CREATE_OPEN, + _CREATE_MODE_MAX, + _CREATE_MODE_INVALID = -EINVAL, +} CreateMode; + +DECLARE_STRING_TABLE_LOOKUP(volume_type, VolumeType); +DECLARE_STRING_TABLE_LOOKUP(create_mode, CreateMode); + +int json_dispatch_volume_type(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); +int json_dispatch_create_mode(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); + +static inline bool storage_volume_name_is_valid(const char *n) { + return string_is_safe(n, /* flags= */ 0); +} + +static inline bool storage_template_name_is_valid(const char *n) { + return string_is_safe(n, /* flags= */ 0); +} + +static inline bool storage_provider_name_is_valid(const char *n) { + return string_is_safe(n, STRING_FILENAME); +} + +typedef struct StorageAcquireReply { + int fd; + VolumeType type; + int read_only; + uid_t base_uid; + gid_t base_gid; +} StorageAcquireReply; + +#define STORAGE_ACQUIRE_REPLY_INIT \ + (StorageAcquireReply) { \ + .fd = -EBADF, \ + .type = _VOLUME_TYPE_INVALID, \ + .read_only = -1, \ + .base_uid = UID_INVALID, \ + .base_gid = GID_INVALID, \ + } + +void storage_acquire_reply_done(StorageAcquireReply *reply); + +/* On varlink failure, reterr_error_id (if non-NULL) is set to the io.systemd.StorageProvider.* + * error name. The reply is untouched on any error. */ +typedef struct BindVolume BindVolume; +int storage_acquire_volume( + RuntimeScope scope, + const BindVolume *bv, + bool allow_interactive_auth, + char **reterr_error_id, + StorageAcquireReply *ret); diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index 6017200d79c3e..3bb2611523510 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -11,7 +11,7 @@ #include "errno-util.h" #include "fd-util.h" #include "log.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "rm-rf.h" @@ -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..1fa0ad3725577 --- /dev/null +++ b/src/shared/swtpm-util.c @@ -0,0 +1,255 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-json.h" + +#include "alloc-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.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" +#include "sync-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_close_ int state_dir_fd = open(state_dir, O_RDONLY|O_DIRECTORY|O_CLOEXEC); + if (state_dir_fd < 0) + return log_error_errno(errno, "Failed to open 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"); + + _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_at( + state_dir_fd, "swtpm-localca.conf", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC, + "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_at( + state_dir_fd, "swtpm-localca.options", + "--platform-manufacturer systemd\n" + "--platform-version 2.1\n" + "--platform-model swtpm\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); + 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_at( + state_dir_fd, "swtpm_setup.conf", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC, + "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); + } + + /* Persist swtpm_setup's freshly created TPM state before writing the completion marker. */ + r = syncfs_path(state_dir_fd, NULL); + if (r < 0) + return log_error_errno(r, "Failed to sync TPM state directory: %m"); + + /* Marker, written last, signals that manufacturing completed successfully. */ + _cleanup_close_ int marker_fd = xopenat(state_dir_fd, SWTPM_MANUFACTURED_MARKER, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOFOLLOW); + if (marker_fd < 0) + return log_error_errno(marker_fd, "Failed to write '%s' marker: %m", SWTPM_MANUFACTURED_MARKER); + + return 0; +} diff --git a/src/shared/swtpm-util.h b/src/shared/swtpm-util.h new file mode 100644 index 0000000000000..8897d17126abc --- /dev/null +++ b/src/shared/swtpm-util.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* Marker file written into the TPM state directory once swtpm_setup has successfully created the TPM state. + * It is written last, so its presence reliably means a complete TPM was manufactured, rather than a manufacture + * that was interrupted halfway through. */ +#define SWTPM_MANUFACTURED_MARKER ".manufactured" + +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..e2603680bda78 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -14,7 +14,6 @@ #include "chattr-util.h" #include "fd-util.h" #include "fs-util.h" -#include "hexdecoct.h" #include "iovec-util.h" #include "libarchive-util.h" #include "mountpoint-util.h" @@ -69,14 +68,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 +79,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 +88,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 +457,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 +510,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 +534,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 +657,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 +766,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 +777,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 +797,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 +826,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 +898,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 +919,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 +933,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 +950,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 +997,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 +1036,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 +1115,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 +1159,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) @@ -1160,7 +1183,7 @@ static int hardlink_lookup( else assert(d->have_unique_mount_id == (r > 0)); - m = hexmem(SHA256_DIRECT(handle->f_handle, handle->handle_bytes), SHA256_DIGEST_SIZE); + m = sha256_direct_hex(handle->f_handle, handle->handle_bytes); if (!m) return log_oom(); @@ -1380,7 +1403,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 +1426,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 +1477,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 +1553,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 +1573,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 +1606,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 +1633,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/test-varlink-idl-util.h b/src/shared/test-varlink-idl-util.h new file mode 100644 index 0000000000000..7a27230d89963 --- /dev/null +++ b/src/shared/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/shared/tests.c b/src/shared/tests.c index 55464c3553fbe..1dc54318fa2f5 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 */ @@ -359,7 +361,7 @@ const char* ci_environment(void) { if (getenv("SALSA_CI_IMAGES")) return (ans = "salsa-ci"); - FOREACH_STRING(var, "CI", "CONTINOUS_INTEGRATION") { + FOREACH_STRING(var, "CI", "CONTINUOUS_INTEGRATION") { /* Those vars are booleans according to Semaphore and Travis docs: * https://docs.travis-ci.com/user/environment-variables/#default-environment-variables * https://docs.semaphoreci.com/ci-cd-environment/environment-variables/#ci @@ -378,20 +380,18 @@ int run_test_table(const TestFunc *start, const TestFunc *end) { _cleanup_strv_free_ char **tests = NULL; int r = EXIT_SUCCESS; bool ran = false; - const char *e; if (!start) return r; - e = getenv("TESTFUNCS"); + const char *e = getenv("TESTFUNCS"); if (e) { - r = strv_split_full(&tests, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return log_error_errno(r, "Failed to parse $TESTFUNCS: %m"); + int n = strv_split_full(&tests, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (n < 0) + return log_error_errno(n, "Failed to parse $TESTFUNCS: %m"); } - for (const TestFunc *t = ALIGN_PTR(start); t + 1 <= end; t = ALIGN_PTR(t + 1)) { - + for (const TestFunc *t = start; t + 1 <= end; t++) { if (tests && !strv_contains(tests, t->name)) continue; diff --git a/src/shared/tests.h b/src/shared/tests.h index ae57cab3863c5..3f8fdf7fa7694 100644 --- a/src/shared/tests.h +++ b/src/shared/tests.h @@ -89,7 +89,8 @@ int define_hex_ptr_internal(const char *hex, void **name, size_t *name_len); /* Provide a convenient way to check if we're running in CI. */ const char* ci_environment(void); -typedef struct TestFunc { +/* Note: see the comment on struct Option in options.h for why _alignptr_ is required here. */ +typedef struct _alignptr_ TestFunc { union f { void (*void_func)(void); int (*int_func)(void); @@ -98,10 +99,10 @@ typedef struct TestFunc { bool has_ret:1; bool sd_booted:1; } TestFunc; +assert_cc(sizeof(TestFunc) % sizeof(void*) == 0); /* 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 +111,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 +479,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/tmpfile-util-label.c b/src/shared/tmpfile-util.c similarity index 95% rename from src/shared/tmpfile-util-label.c rename to src/shared/tmpfile-util.c index b928460f1bedb..9a5360d6d22df 100644 --- a/src/shared/tmpfile-util-label.c +++ b/src/shared/tmpfile-util.c @@ -4,7 +4,6 @@ #include "selinux-util.h" #include "tmpfile-util.h" -#include "tmpfile-util-label.h" int fopen_temporary_at_label( int dir_fd, diff --git a/src/shared/tmpfile-util-label.h b/src/shared/tmpfile-util.h similarity index 50% rename from src/shared/tmpfile-util-label.h rename to src/shared/tmpfile-util.h index d8920cc697f83..23db87cbb18af 100644 --- a/src/shared/tmpfile-util-label.h +++ b/src/shared/tmpfile-util.h @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "shared-forward.h" +#include "../basic/tmpfile-util.h" /* IWYU pragma: export */ -/* These functions are split out of tmpfile-util.h (and not for example just flags to the functions they - * wrap) in order to optimize linking: this way, -lselinux is needed only for the callers of these functions - * that need selinux, but not for all. */ +/* These functions extend the basic tmpfile-util.h API with shared-only functionality (selinux labelling). + * Targets that link libshared automatically pick up this version via -Isrc/shared; targets that only have + * src/basic on their include path fall through to the basic header. */ int fopen_temporary_at_label(int dir_fd, const char *target, const char *path, FILE **f, char **temp_path); static inline int fopen_temporary_label(const char *target, const char *path, FILE **f, char **temp_path) { diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index e5694361422fd..d1be7d564ce34 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,17 +49,56 @@ #include "time-util.h" #include "tpm2-pcr.h" #include "tpm2-util.h" +#include "unaligned.h" #include "virt.h" -#if HAVE_OPENSSL -# include -#endif +/* The PCR banks we are willing to bind policies to, in order of preference. We keep SHA256 as the top + * preference for backwards compatibility (systems that already bind to it keep using the same bank, so + * existing TPM2-sealed secrets remain valid), then prefer SHA384 over SHA512: SHA384 is computed in 64-bit + * words like SHA512 but produces a shorter digest, so it takes up less room in the TPM event log, whose + * memory is usually bounded. SHA512 comes next, and we only fall back to the weak SHA1 as a last resort. */ +static const uint16_t tpm2_pcr_bank_preference[] = { + TPM2_ALG_SHA256, + TPM2_ALG_SHA384, + TPM2_ALG_SHA512, + TPM2_ALG_SHA1, +}; -#if HAVE_TPM2 -static void *libtss2_esys_dl = NULL; -static void *libtss2_rc_dl = NULL; -static void *libtss2_mu_dl = NULL; +/* The reduced preference list used when re-deriving the bank for legacy enrollments that did not record one + * (see tpm2_get_best_pcr_bank_legacy()). Such enrollments were sealed by code that only ever considered + * SHA256 and SHA1, so they can only be bound to one of those two banks. We must restrict the choice to exactly this set. */ +static const uint16_t tpm2_pcr_bank_preference_legacy[] = { + TPM2_ALG_SHA256, + TPM2_ALG_SHA1, +}; +static int pcr_bank_from_efi_active( + const uint16_t *banks, + size_t n_banks, + uint32_t active_banks, + uint16_t *ret) { + + assert(banks); + assert(ret); + + FOREACH_ARRAY(bank, banks, n_banks) + if (BIT_SET(active_banks, *bank)) { + *ret = *bank; + return 0; + } + + return -EOPNOTSUPP; +} + +int tpm2_pcr_bank_from_efi_active(uint32_t active_banks, uint16_t *ret) { + return pcr_bank_from_efi_active(tpm2_pcr_bank_preference, ELEMENTSOF(tpm2_pcr_bank_preference), active_banks, ret); +} + +int tpm2_pcr_bank_from_efi_active_legacy(uint32_t active_banks, uint16_t *ret) { + return pcr_bank_from_efi_active(tpm2_pcr_bank_preference_legacy, ELEMENTSOF(tpm2_pcr_bank_preference_legacy), active_banks, ret); +} + +#if HAVE_TPM2 static DLSYM_PROTOTYPE(Esys_Create) = NULL; static DLSYM_PROTOTYPE(Esys_CreateLoaded) = NULL; static DLSYM_PROTOTYPE(Esys_CreatePrimary) = NULL; @@ -121,16 +166,14 @@ 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) { + static void *libtss2_esys_dl = NULL; int r; - ELF_NOTE_DLOPEN("tpm", - "Support for TPM", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, - "libtss2-esys.so.0"); + TPM2_ESYS_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); 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 +229,23 @@ static int dlopen_tpm2_esys(void) { return 0; } -static int dlopen_tpm2_rc(void) { - ELF_NOTE_DLOPEN("tpm", - "Support for TPM", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, - "libtss2-rc.so.0"); +static int dlopen_tpm2_rc(int log_level) { + static void *libtss2_rc_dl = NULL; + + TPM2_RC_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); 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", - "Support for TPM", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, - "libtss2-mu.so.0"); +static int dlopen_tpm2_mu(int log_level) { + static void *libtss2_mu_dl = NULL; + + TPM2_MU_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); 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 +267,46 @@ static int dlopen_tpm2_mu(void) { DLSYM_ARG(Tss2_MU_UINT32_Marshal)); } +static int dlopen_tpm2_tcti_device(int log_level) { + static void *libtss2_tcti_device_dl = NULL; + + /* 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. */ + + TPM2_TCTI_DEVICE_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); + + 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,8 +389,177 @@ static int tpm2_get_capability( return more == TPM2_YES; } +static int tpm2_get_capability_property(Tpm2Context *c, uint32_t property, uint32_t *ret_value) { + int r; + + assert(c); + assert(ret_value); + + TPMU_CAPABILITIES capabilities = {}; + r = tpm2_get_capability(c, TPM2_CAP_TPM_PROPERTIES, property, 1, &capabilities); + if (r < 0) + return r; + + if (capabilities.tpmProperties.count == 0 || + capabilities.tpmProperties.tpmProperty[0].property != property) + return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "TPM property 0x%04" PRIx32 " does not exist", property); + + *ret_value = capabilities.tpmProperties.tpmProperty[0].value; + return 0; +} + +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) +/* The TCG reference library spec (part 2) doesn't guarantee a minimum size for the + * TPM2B_MAX_NV_BUFFER type. However, the PC-Client PTP spec does set a minimum value of 512, + * so we'll just assume this if the TPM didn't report a value or reports an implausible value. */ +#define FALLBACK_MAX_NV_BUFFER_SIZE 512u + static int tpm2_cache_capabilities(Tpm2Context *c) { TPMU_CAPABILITIES capability; int r; @@ -463,6 +692,26 @@ static int tpm2_cache_capabilities(Tpm2Context *c) { log_debug("TPM bug: reported multiple PCR sets; using only first set."); c->capability_pcrs = capability.assignedPCR; + /* Cache the value of TPM_PT_NV_BUFFER_MAX, which defines the maximum size of TPM2B_MAX_NV_BUFFER and + * which limits the amount of data that TPM2_NV_Read can return in a single command. This may be + * smaller than the size of a NV index payload, particularly if that payload contains a X509 + * certificate with a RSA public key. */ + uint32_t max_nv_buffer_size = 0; + + r = tpm2_get_capability_property(c, TPM2_PT_NV_BUFFER_MAX, &max_nv_buffer_size); + if (r == -ENOENT) { + log_debug("TPM bug: didn't report a value for TPM_PT_NV_BUFFER_MAX; using %"PRIu32".", FALLBACK_MAX_NV_BUFFER_SIZE); + max_nv_buffer_size = FALLBACK_MAX_NV_BUFFER_SIZE; + } else if (r < 0) + return r; + if (max_nv_buffer_size == 0 || max_nv_buffer_size > UINT16_MAX) { + /* TPM2B types have a uint16 size field. If the TPM reported a maximum size that is larger + * than this, or 0, then consider this as implausible and pick the default fallback. */ + log_debug("TPM bug: reported implausible value for TPM_PT_NV_BUFFER_MAX; using %"PRIu32".", FALLBACK_MAX_NV_BUFFER_SIZE); + max_nv_buffer_size = FALLBACK_MAX_NV_BUFFER_SIZE; + } + c->max_nv_buffer_size = (uint16_t) max_nv_buffer_size; + return 0; } @@ -646,7 +895,7 @@ static bool tpm2_supports_tpmt_sym_def(Tpm2Context *c, const TPMT_SYM_DEF *param assert(c); assert(parameters); - /* Unfortunately, TPMT_SYM_DEF and TPMT_SYM_DEF_OBEJECT are separately defined, even though they are + /* Unfortunately, TPMT_SYM_DEF and TPMT_SYM_DEF_OBJECT are separately defined, even though they are * functionally identical. */ TPMT_SYM_DEF_OBJECT object = { .algorithm = parameters->algorithm, @@ -671,6 +920,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 +949,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 +968,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 +977,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 +993,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 +1035,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); @@ -922,6 +1187,33 @@ static int tpm2_read_public( return 0; } +static int tpm2_read_nv_public( + Tpm2Context *c, + const Tpm2Handle *session, + const Tpm2Handle *handle, + TPM2B_NV_PUBLIC **ret_nv_public, + TPM2B_NAME **ret_name) { + + TSS2_RC rc; + + assert(c); + assert(handle); + + rc = sym_Esys_NV_ReadPublic( + c->esys_context, + handle->esys_handle, + session ? session->esys_handle : ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + ret_nv_public, + ret_name); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to read NV public info: %s", sym_Tss2_RC_Decode(rc)); + + return 0; +} + /* Create a Tpm2Handle object that references a pre-existing handle in the TPM, at the handle index provided. * This should be used only for persistent, transient, or NV handles; and the handle must already exist in * the TPM at the specified handle index. The handle index should not be 0. Returns 1 if found, 0 if the @@ -931,9 +1223,7 @@ int tpm2_index_to_handle( Tpm2Context *c, TPM2_HANDLE index, const Tpm2Handle *session, - TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, - TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle) { TSS2_RC rc; @@ -974,12 +1264,8 @@ int tpm2_index_to_handle( return r; if (r == 0) { log_debug("TPM handle 0x%08" PRIx32 " not populated.", index); - if (ret_public) - *ret_public = NULL; if (ret_name) *ret_name = NULL; - if (ret_qname) - *ret_qname = NULL; if (ret_handle) *ret_handle = NULL; return 0; @@ -1006,12 +1292,109 @@ int tpm2_index_to_handle( return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to read public info: %s", sym_Tss2_RC_Decode(rc)); - if (ret_public || ret_name || ret_qname) { - r = tpm2_read_public(c, session, handle, ret_public, ret_name, ret_qname); + if (ret_name) { + r = tpm2_get_name(c, handle, ret_name); + if (r < 0) + return r; + } + + if (ret_handle) + *ret_handle = TAKE_PTR(handle); + + return 1; +} + +/* Create a Tpm2Handle object that references a pre-existing persistent or transient object in the TPM, at + * the handle index provided. The handle index should not be 0. Returns 1 if found, 0 if the index is empty, + * or < 0 on error. Also see tpm2_get_srk() below; the SRK is a commonly used persistent Tpm2Handle. */ +int tpm2_object_index_to_handle( + Tpm2Context *c, + TPM2_HANDLE index, + const Tpm2Handle *session, + TPM2B_PUBLIC **ret_public, + TPM2B_NAME **ret_name, + TPM2B_NAME **ret_qname, + Tpm2Handle **ret_handle) { + + int r; + + assert(c); + + _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; + _cleanup_(Esys_Freep) TPM2B_NAME *name = NULL; + r = tpm2_index_to_handle(c, index, session, ret_name ? &name : NULL, &handle); + if (r < 0) + return r; + if (r == 0) { + if (ret_public) + *ret_public = NULL; + if (ret_name) + *ret_name = NULL; + if (ret_qname) + *ret_qname = NULL; + if (ret_handle) + *ret_handle = NULL; + return 0; + } + + if (ret_public || ret_qname) { + sym_Esys_Free(name); + name = NULL; + + r = tpm2_read_public(c, session, handle, ret_public, &name, ret_qname); if (r < 0) return r; } + if (ret_name) + *ret_name = TAKE_PTR(name); + if (ret_handle) + *ret_handle = TAKE_PTR(handle); + + return 1; +} + +/* Create a Tpm2Handle object that references a pre-existing NV index in the TPM, at the handle index + * provided. The handle index should not be 0. Returns 1 if found, 0 if the index is empty, or < 0 on + * error. */ +int tpm2_nv_index_to_handle( + Tpm2Context *c, + TPM2_HANDLE index, + const Tpm2Handle *session, + TPM2B_NV_PUBLIC **ret_nv_public, + TPM2B_NAME **ret_name, + Tpm2Handle **ret_handle) { + + int r; + + assert(c); + + _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; + _cleanup_(Esys_Freep) TPM2B_NAME *name = NULL; + r = tpm2_index_to_handle(c, index, session, ret_name ? &name : NULL, &handle); + if (r < 0) + return r; + if (r == 0) { + if (ret_nv_public) + *ret_nv_public = NULL; + if (ret_name) + *ret_name = NULL; + if (ret_handle) + *ret_handle = NULL; + return 0; + } + + if (ret_nv_public) { + sym_Esys_Free(name); + name = NULL; + + r = tpm2_read_nv_public(c, session, handle, ret_nv_public, &name); + if (r < 0) + return r; + } + + if (ret_name) + *ret_name = TAKE_PTR(name); if (ret_handle) *ret_handle = TAKE_PTR(handle); @@ -1364,7 +1747,7 @@ int tpm2_get_srk( TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle) { - return tpm2_index_to_handle(c, TPM2_SRK_HANDLE, session, ret_public, ret_name, ret_qname, ret_handle); + return tpm2_object_index_to_handle(c, TPM2_SRK_HANDLE, session, ret_public, ret_name, ret_qname, ret_handle); } /* Get the SRK, creating one if needed. Returns 1 if a new SRK was created and persisted, 0 if an SRK already @@ -2317,7 +2700,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 +2743,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 +2786,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; @@ -2688,9 +3071,23 @@ static int tpm2_bank_has24(const TPMS_PCR_SELECTION *selection) { return valid; } -int tpm2_get_best_pcr_bank( +static char* pcr_bank_preference_to_string(const uint16_t *banks, size_t n_banks) { + _cleanup_free_ char *s = NULL; + + assert(banks); + + FOREACH_ARRAY(bank, banks, n_banks) + if (!strextend_with_separator(&s, ", ", tpm2_hash_alg_to_string(*bank))) + return NULL; + + return TAKE_PTR(s); +} + +static int get_best_pcr_bank( Tpm2Context *c, uint32_t pcr_mask, + const uint16_t *banks, + size_t n_banks, TPMI_ALG_HASH *ret) { TPMI_ALG_HASH supported_hash = 0, hash_with_valid_pcr = 0; @@ -2698,26 +3095,34 @@ int tpm2_get_best_pcr_bank( assert(c); assert(ret); + assert(banks); + assert(n_banks > 0); 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; - else if (BIT_SET(efi_banks, TPM2_ALG_SHA1)) - *ret = TPM2_ALG_SHA1; - else - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Firmware reports neither SHA1 nor SHA256 PCR banks, cannot operate."); + r = pcr_bank_from_efi_active(banks, n_banks, efi_banks, ret); + if (r == -EOPNOTSUPP) { + _cleanup_free_ char *bank_list = pcr_bank_preference_to_string(banks, n_banks); + return log_debug_errno(r, "Firmware reports none of the PCR banks we can use (%s), cannot operate.", strna(bank_list)); + } + if (r < 0) + return r; - log_debug("Picked best PCR bank %s based on firmware reported banks.", tpm2_hash_alg_to_string(*ret)); + if (*ret == TPM2_ALG_SHA1) + log_notice("Firmware reports SHA1 as the best active PCR bank, binding policy to it. This reduces the security level substantially."); + else + log_debug("Picked best PCR bank %s based on firmware reported banks.", tpm2_hash_alg_to_string(*ret)); return 0; } @@ -2727,75 +3132,65 @@ int tpm2_get_best_pcr_bank( return 0; } - FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection, &c->capability_pcrs) { - TPMI_ALG_HASH hash = selection->hash; + /* Walk our banks in order of preference. Because the list is already sorted best-first, the first + * bank that qualifies is the most preferred one — no ranking needed. */ + FOREACH_ARRAY(bank, banks, n_banks) { int good; - /* For now we are only interested in the SHA1 and SHA256 banks */ - if (!IN_SET(hash, TPM2_ALG_SHA256, TPM2_ALG_SHA1)) + /* Skip banks the TPM doesn't expose with a full set of 24 PCRs. */ + if (!tpm2_tpml_pcr_selection_has_mask(&c->capability_pcrs, *bank, TPM2_PCRS_MASK)) continue; - r = tpm2_bank_has24(selection); - if (r < 0) - return r; - if (!r) - continue; + /* First supported bank we reach is the most preferred supported one. */ + if (supported_hash == 0) + supported_hash = *bank; - good = tpm2_pcr_mask_good(c, hash, pcr_mask); + good = tpm2_pcr_mask_good(c, *bank, pcr_mask); if (good < 0) return good; - - if (hash == TPM2_ALG_SHA256) { - supported_hash = TPM2_ALG_SHA256; - if (good) { - /* Great, SHA256 is supported and has initialized PCR values, we are done. */ - hash_with_valid_pcr = TPM2_ALG_SHA256; - break; - } - } else { - assert(hash == TPM2_ALG_SHA1); - - if (supported_hash == 0) - supported_hash = TPM2_ALG_SHA1; - - if (good && hash_with_valid_pcr == 0) - hash_with_valid_pcr = TPM2_ALG_SHA1; + if (good) { + /* First supported bank with initialized PCRs is the most preferred such bank; we + * cannot do any better, so we are done. */ + hash_with_valid_pcr = *bank; + break; } } - /* We preferably pick SHA256, but only if its PCRs are initialized or neither the SHA1 nor the SHA256 - * PCRs are initialized. If SHA256 is not supported but SHA1 is and its PCRs are too, we prefer - * SHA1. - * - * We log at LOG_NOTICE level whenever we end up using the SHA1 bank or when the PCRs we bind to are - * not initialized. */ - - if (hash_with_valid_pcr == TPM2_ALG_SHA256) { - assert(supported_hash == TPM2_ALG_SHA256); - log_debug("TPM2 device supports SHA256 PCR bank and SHA256 PCRs are valid, yay!"); - *ret = TPM2_ALG_SHA256; - } else if (hash_with_valid_pcr == TPM2_ALG_SHA1) { - if (supported_hash == TPM2_ALG_SHA256) - log_notice("TPM2 device supports both SHA1 and SHA256 PCR banks, but only SHA1 PCRs are valid, falling back to SHA1 bank. This reduces the security level substantially."); - else { - assert(supported_hash == TPM2_ALG_SHA1); - log_notice("TPM2 device lacks support for SHA256 PCR bank, but SHA1 bank is supported and SHA1 PCRs are valid, falling back to SHA1 bank. This reduces the security level substantially."); - } + /* Prefer a bank whose selected PCRs are actually initialized. If none of the supported banks has + * initialized PCRs we still proceed with the most preferred supported bank, but the resulting PCR + * policy is then effectively unenforced. */ - *ret = TPM2_ALG_SHA1; - } else if (supported_hash == TPM2_ALG_SHA256) { - log_notice("TPM2 device supports SHA256 PCR bank but none of the selected PCRs are valid! Firmware apparently did not initialize any of the selected PCRs. Proceeding anyway with SHA256 bank. PCR policy effectively unenforced!"); - *ret = TPM2_ALG_SHA256; - } else if (supported_hash == TPM2_ALG_SHA1) { - log_notice("TPM2 device lacks support for SHA256 bank, but SHA1 bank is supported, but none of the selected PCRs are valid! Firmware apparently did not initialize any of the selected PCRs. Proceeding anyway with SHA1 bank. PCR policy effectively unenforced!"); - *ret = TPM2_ALG_SHA1; - } else + if (hash_with_valid_pcr != 0) { + if (hash_with_valid_pcr == TPM2_ALG_SHA1) + log_notice("Only the weak SHA1 PCR bank has initialized PCRs, binding policy to it. This reduces the security level substantially."); + else if (hash_with_valid_pcr != supported_hash) + log_notice("Preferred %s PCR bank has uninitialized PCRs, binding policy to the %s bank instead.", + tpm2_hash_alg_to_string(supported_hash), tpm2_hash_alg_to_string(hash_with_valid_pcr)); + else + log_debug("Binding policy to %s PCR bank with initialized PCRs.", tpm2_hash_alg_to_string(hash_with_valid_pcr)); + + *ret = hash_with_valid_pcr; + } else if (supported_hash != 0) { + log_notice("TPM2 device supports the %s PCR bank but none of the selected PCRs are initialized! Firmware apparently did not measure into any of them. Proceeding anyway, but the PCR policy is effectively unenforced!", + tpm2_hash_alg_to_string(supported_hash)); + *ret = supported_hash; + } else { + _cleanup_free_ char *bank_list = pcr_bank_preference_to_string(banks, n_banks); return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "TPM2 module supports neither SHA1 nor SHA256 PCR banks, cannot operate."); + "TPM2 module supports none of the PCR banks we can use (%s), cannot operate.", strna(bank_list)); + } return 0; } +int tpm2_get_best_pcr_bank(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH *ret) { + return get_best_pcr_bank(c, pcr_mask, tpm2_pcr_bank_preference, ELEMENTSOF(tpm2_pcr_bank_preference), ret); +} + +int tpm2_get_best_pcr_bank_legacy(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH *ret) { + return get_best_pcr_bank(c, pcr_mask, tpm2_pcr_bank_preference_legacy, ELEMENTSOF(tpm2_pcr_bank_preference_legacy), ret); +} + int tpm2_get_good_pcr_banks( Tpm2Context *c, uint32_t pcr_mask, @@ -2811,13 +3206,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 +3288,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 +3310,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 +3731,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 +3815,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 +3871,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 +3932,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 +3988,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 +4027,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 +4081,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 +4216,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 +4271,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 +4363,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 +4764,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 +4777,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 +4875,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 +5147,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 +5225,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 +5257,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 +5314,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 +5334,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 +5349,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 +5365,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 +5474,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 +5544,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 +5585,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 +5622,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); @@ -5421,7 +5839,7 @@ int tpm2_seal(Tpm2Context *c, if (r < 0) return r; } else if (IN_SET(TPM2_HANDLE_TYPE(seal_key_handle), TPM2_HT_TRANSIENT, TPM2_HT_PERSISTENT)) { - r = tpm2_index_to_handle( + r = tpm2_object_index_to_handle( c, seal_key_handle, /* session= */ NULL, @@ -5590,6 +6008,7 @@ int tpm2_unseal(Tpm2Context *c, int r; assert(n_blobs > 0); + assert(blobs); assert(iovec_is_valid(pubkey)); assert(ret_secret); @@ -5613,9 +6032,9 @@ int tpm2_unseal(Tpm2Context *c, return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Number of provided known policy hashes (%zu) does not match policy requirements (%zu or 0).", n_known_policy_hash, n_shards); /* Older code did not save the pcr_bank, and unsealing needed to detect the best pcr bank to use, - * so we need to handle that legacy situation. */ + * so we need to handle that legacy situation. Use the legacy variant, which only ever picks SHA256 or SHA1. */ if (pcr_bank == UINT16_MAX) { - r = tpm2_get_best_pcr_bank(c, hash_pcr_mask|pubkey_pcr_mask, &pcr_bank); + r = tpm2_get_best_pcr_bank_legacy(c, hash_pcr_mask|pubkey_pcr_mask, &pcr_bank); if (r < 0) return r; } @@ -5950,6 +6369,116 @@ int tpm2_write_policy_nv_index( return 0; } +int tpm2_define_data_nv_index( + Tpm2Context *c, + const Tpm2Handle *session, + TPM2_HANDLE requested_nv_index, + const struct iovec *data, + TPM2_HANDLE *ret_nv_index, + Tpm2Handle **ret_nv_handle) { + + _cleanup_(tpm2_handle_freep) Tpm2Handle *new_handle = NULL; + TPM2_HANDLE nv_index = 0; + TSS2_RC rc; + int r; + + assert(c); + assert(iovec_is_set(data)); + + /* Allocates an ordinary NV index sized to hold 'data' and writes 'data' into it. The index is created + * with AUTHREAD/AUTHWRITE attributes so it can be read back with tpm2_read_nv_index(). */ + + if (data->iov_len == 0 || data->iov_len > UINT16_MAX) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid NV index data size %zu.", data->iov_len); + + r = tpm2_handle_new(c, &new_handle); + if (r < 0) + return r; + + new_handle->flush = false; /* This is a persistent NV index, don't flush hence */ + + for (unsigned try = 0;; try++) { + if (requested_nv_index != 0) + nv_index = requested_nv_index; + else + nv_index = generate_random_nv_index(); + + TPM2B_NV_PUBLIC public_info = { + .size = sizeof_field(TPM2B_NV_PUBLIC, nvPublic), + .nvPublic = { + .nvIndex = nv_index, + .nameAlg = TPM2_ALG_SHA256, + .attributes = TPM2_NT_ORDINARY | TPMA_NV_AUTHWRITE | TPMA_NV_AUTHREAD | TPMA_NV_NO_DA, + .dataSize = data->iov_len, + }, + }; + + rc = sym_Esys_NV_DefineSpace( + c->esys_context, + /* authHandle= */ ESYS_TR_RH_OWNER, + /* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD, + /* shandle2= */ ESYS_TR_NONE, + /* shandle3= */ ESYS_TR_NONE, + /* auth= */ NULL, + &public_info, + &new_handle->esys_handle); + if (rc == TSS2_RC_SUCCESS) + break; + if (rc != TPM2_RC_NV_DEFINED) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to allocate NV index: %s", sym_Tss2_RC_Decode(rc)); + if (requested_nv_index != 0) { + assert(nv_index == requested_nv_index); + return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), + "Requested NV index 0x%" PRIx32 " already taken.", requested_nv_index); + } + if (try >= 24U) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Too many attempts trying to allocate NV index: %s", sym_Tss2_RC_Decode(rc)); + + log_debug("NV index 0x%" PRIx32 " already taken, trying another one (%u tries left)", nv_index, 24U - try); + } + + log_debug("NV index 0x%" PRIx32 " successfully allocated.", nv_index); + + /* TPM2_NV_Write is bounded by TPM_PT_NV_BUFFER_MAX just like TPM2_NV_Read, so write in chunks no + * larger than what the TPM accepts in a single command. */ + size_t max_buffer_size = MIN((size_t) c->max_nv_buffer_size, (size_t) TPM2_MAX_NV_BUFFER_SIZE); + assert(max_buffer_size > 0); + + for (size_t offset = 0; offset < data->iov_len;) { + size_t chunk_size = MIN(data->iov_len - offset, max_buffer_size); + + TPM2B_MAX_NV_BUFFER buffer = { .size = chunk_size }; + memcpy(buffer.buffer, (const uint8_t*) data->iov_base + offset, chunk_size); + + rc = sym_Esys_NV_Write( + c->esys_context, + /* authHandle= */ new_handle->esys_handle, + /* nvIndex= */ new_handle->esys_handle, + /* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD, + /* shandle2= */ ESYS_TR_NONE, + /* shandle3= */ ESYS_TR_NONE, + &buffer, + /* offset= */ offset); + if (rc != TSS2_RC_SUCCESS) { + (void) tpm2_undefine_nv_index(c, session, nv_index, new_handle); + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to write NV index 0x%" PRIx32 ": %s", nv_index, sym_Tss2_RC_Decode(rc)); + } + + offset += chunk_size; + } + + if (ret_nv_index) + *ret_nv_index = nv_index; + if (ret_nv_handle) + *ret_nv_handle = TAKE_PTR(new_handle); + + return 0; +} + int tpm2_undefine_nv_index( Tpm2Context *c, const Tpm2Handle *session, @@ -5976,7 +6505,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, @@ -6040,34 +6570,20 @@ int tpm2_define_nvpcr_nv_index( new_handle = tpm2_handle_free(new_handle); - r = tpm2_index_to_handle( + _cleanup_(Esys_Freep) TPM2B_NV_PUBLIC *nv_public_real = NULL; + r = tpm2_nv_index_to_handle( c, nv_index, session, - /* ret_public= */ NULL, + &nv_public_real, /* ret_name= */ NULL, - /* ret_qname= */ NULL, &new_handle); - if (r < 0) - return log_debug_errno(r, "Failed to acquire handle to existing NV index 0x%" PRIu32 ".", nv_index); + if (r <= 0) + return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to acquire handle to existing NV index 0x%" PRIu32 ".", nv_index); log_debug("Successfully acquired handle to existing NV index 0x%" PRIx32 ".", nv_index); - _cleanup_(Esys_Freep) TPM2B_NV_PUBLIC *nv_public_real = NULL; - rc = sym_Esys_NV_ReadPublic( - c->esys_context, - /* nvIndex= */ new_handle->esys_handle, - /* shandle1= */ ESYS_TR_NONE, - /* shandle2= */ ESYS_TR_NONE, - /* shandle3= */ ESYS_TR_NONE, - &nv_public_real, - /* ret_nv_name= */ NULL); - if (rc != TSS2_RC_SUCCESS) - return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed read public data of nvindex 0x%x: %s", nv_index, sym_Tss2_RC_Decode(rc)); - - log_debug("Read public info for nvindex 0x%x.", nv_index); - if (nv_public_real->size < endoffsetof_field(TPMS_NV_PUBLIC, attributes) + sizeof_field(TPMS_NV_PUBLIC, dataSize) || nv_public_real->nvPublic.nvIndex != public_info.nvPublic.nvIndex || nv_public_real->nvPublic.nameAlg != public_info.nvPublic.nameAlg || @@ -6095,7 +6611,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 +6652,7 @@ int tpm2_extend_nvpcr_nv_index( return 0; } +#endif int tpm2_read_nv_index( Tpm2Context *c, @@ -6145,55 +6662,68 @@ int tpm2_read_nv_index( struct iovec *ret_value) { TPM2_RC rc; + int r; assert(c); assert(nv_index); assert(nv_handle); _cleanup_(Esys_Freep) TPM2B_NV_PUBLIC *nv_public = NULL; - rc = sym_Esys_NV_ReadPublic( - c->esys_context, - /* nvIndex= */ nv_handle->esys_handle, - /* shandle1= */ ESYS_TR_NONE, - /* shandle2= */ ESYS_TR_NONE, - /* shandle3= */ ESYS_TR_NONE, - &nv_public, - /* ret_nv_name= */ NULL); - if (rc != TSS2_RC_SUCCESS) - return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed read public data of nvindex 0x%x: %s", nv_index, sym_Tss2_RC_Decode(rc)); - - log_debug("Read public info for nvindex 0x%x, value size is %zu", nv_index, (size_t) nv_public->nvPublic.dataSize); + r = tpm2_read_nv_public(c, /* session= */ NULL, nv_handle, &nv_public, /* ret_name= */ NULL); + if (r < 0) + return r; - _cleanup_(Esys_Freep) TPM2B_MAX_NV_BUFFER *value = NULL; - rc = sym_Esys_NV_Read( - c->esys_context, - /* authHandle= */ nv_handle->esys_handle, - /* nvIndex= */ nv_handle->esys_handle, - /* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD, - /* shandle2= */ ESYS_TR_NONE, - /* shandle3= */ ESYS_TR_NONE, - nv_public->nvPublic.dataSize, - /* offset= */ 0, - &value); - if (rc != TSS2_RC_SUCCESS) - return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed read contents of nvindex 0x%x: %s", nv_index, sym_Tss2_RC_Decode(rc)); + size_t data_size = nv_public->nvPublic.dataSize; + log_debug("Read public info for nvindex 0x%x, value size is %zu", nv_index, data_size); - if (ret_value) { - assert(value); + /* TPM2_NV_Read returns the data in a TPM2B_MAX_NV_BUFFER, whose maximum size is bounded by the value + * of the TPM_PT_NV_BUFFER_MAX property. This limits the amount of data that can be read in a single + * command. As this limit can be smaller than the size of the NV index payload (particularly if the + * payload contains a X509 certificate with a RSA public key), we read the contents in chunks no + * larger than what the TPM reports it can return in a single command. */ - struct iovec result = { - .iov_base = memdup(value->buffer, value->size), - .iov_len = value->size, - }; + /* Never ask for more than the buffer the TPM library hands back can hold. */ + size_t max_buffer_size = MIN((size_t) c->max_nv_buffer_size, (size_t) TPM2_MAX_NV_BUFFER_SIZE); + assert(max_buffer_size > 0); /* tpm2_cache_capabilities sets this to 512 if the TPM reported 0. */ + _cleanup_(iovec_done) struct iovec result = {}; + if (data_size > 0) { + result.iov_base = malloc(data_size); if (!result.iov_base) return log_oom_debug(); + } - *ret_value = TAKE_STRUCT(result); + while (result.iov_len < data_size) { + size_t chunk_size = MIN(data_size - result.iov_len, max_buffer_size); + + _cleanup_(Esys_Freep) TPM2B_MAX_NV_BUFFER *chunk = NULL; + rc = sym_Esys_NV_Read( + c->esys_context, + /* authHandle= */ nv_handle->esys_handle, + /* nvIndex= */ nv_handle->esys_handle, + /* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD, + /* shandle2= */ ESYS_TR_NONE, + /* shandle3= */ ESYS_TR_NONE, + chunk_size, + /* offset= */ result.iov_len, + &chunk); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed read contents of nvindex 0x%x: %s", nv_index, sym_Tss2_RC_Decode(rc)); + assert(chunk); + if (chunk->size != chunk_size) + /* On success, TPM2_NV_Read should return exactly what we asked for. */ + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "TPM returned an unexpected amount of data (%" PRIu16 ") reading nvindex 0x%x.", + chunk->size, nv_index); + + memcpy((uint8_t*) result.iov_base + result.iov_len, chunk->buffer, chunk->size); + result.iov_len += chunk->size; } + if (ret_value) + *ret_value = TAKE_STRUCT(result); + return 0; } @@ -6211,6 +6741,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 +6807,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 +6857,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 +6917,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 +6929,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 +7011,9 @@ 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", + [TPM2_EVENT_LOGIN] = "login", }; DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType); @@ -6609,17 +7140,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 +7212,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 +7227,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 +7256,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++; @@ -6763,6 +7303,7 @@ typedef struct NvPCRData { char *name; uint16_t algorithm; uint32_t nv_index; + uint64_t priority; } NvPCRData; static void nvpcr_data_done(NvPCRData *d) { @@ -6802,11 +7343,13 @@ static int nvpcr_data_load(const char *name, NvPCRData *ret) { { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(NvPCRData, name), SD_JSON_MANDATORY }, { "algorithm", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_tpm2_algorithm, offsetof(NvPCRData, algorithm), 0 }, { "nvIndex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(NvPCRData, nv_index), SD_JSON_MANDATORY }, + { "priority", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(NvPCRData, priority), 0 }, {}, }; _cleanup_(nvpcr_data_done) NvPCRData p = { .algorithm = TPM2_ALG_SHA256, + .priority = TPM2_NVPCR_PRIORITY_DEFAULT, }; r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) @@ -6819,7 +7362,7 @@ static int nvpcr_data_load(const char *name, NvPCRData *ret) { return 0; } -int tpm2_nvpcr_get_index(const char *name, uint32_t *ret) { +int tpm2_nvpcr_get_index(const char *name, uint32_t *ret_nv_index, uint64_t *ret_priority) { int r; _cleanup_(nvpcr_data_done) NvPCRData p = {}; @@ -6827,8 +7370,10 @@ int tpm2_nvpcr_get_index(const char *name, uint32_t *ret) { if (r < 0) return r; - if (ret) - *ret = p.nv_index; + if (ret_nv_index) + *ret_nv_index = p.nv_index; + if (ret_priority) + *ret_priority = p.priority; return 0; } @@ -6851,6 +7396,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 +7423,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 +7437,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; @@ -6898,12 +7447,14 @@ int tpm2_nvpcr_extend_bytes( c, p.nv_index, session, - /* ret_public= */ NULL, /* ret_name= */ NULL, - /* ret_qname= */ NULL, &nv_handle); if (r < 0) return log_debug_errno(r, "Failed to acquire handle to NV index 0x%" PRIu32 ".", p.nv_index); + if (r == 0) + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), + "NvPCR '%s' is anchored but its NV index 0x%" PRIx32 " is no longer present on the TPM, refusing extend.", + name, p.nv_index); log_debug("Successfully acquired handle to existing NV index 0x%" PRIx32 ".", p.nv_index); @@ -6975,7 +7526,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 +7858,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 +7909,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 +7943,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 +7971,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; @@ -7468,7 +8067,8 @@ int tpm2_nvpcr_read( const Tpm2Handle *session, const char *name, struct iovec *ret_value, - uint32_t *ret_nv_index) { + uint32_t *ret_nv_index, + uint64_t *ret_priority) { #if HAVE_OPENSSL int r; @@ -7495,6 +8095,8 @@ int tpm2_nvpcr_read( *ret_value = (struct iovec) {}; if (ret_nv_index) *ret_nv_index = p.nv_index; + if (ret_priority) + *ret_priority = p.priority; return 0; } @@ -7504,9 +8106,7 @@ int tpm2_nvpcr_read( c, p.nv_index, session, - /* ret_public= */ NULL, /* ret_name= */ NULL, - /* ret_qname= */ NULL, &nv_handle); if (r < 0) return log_debug_errno(r, "Failed to acquire handle to NV index 0x%" PRIu32 ".", p.nv_index); @@ -7531,6 +8131,8 @@ int tpm2_nvpcr_read( if (ret_nv_index) *ret_nv_index = p.nv_index; + if (ret_priority) + *ret_priority = p.priority; return r; #else /* HAVE_OPENSSL */ @@ -8077,6 +8679,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 +8817,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 +8839,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 +9502,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 +9846,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..43461215477a2 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -1,9 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "sd-dlopen.h" + #include "bitfield.h" -#include "openssl-util.h" +#include "iovec-util.h" #include "shared-forward.h" +#include "sha256.h" typedef enum TPM2Flags { TPM2_FLAGS_USE_PIN = 1 << 0, @@ -39,9 +42,31 @@ 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 +#define TPM2_ESYS_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("tpm", "Support for TPM", priority, "libtss2-esys.so.0") +#define TPM2_RC_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("tpm", "Support for TPM", priority, "libtss2-rc.so.0") +#define TPM2_MU_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("tpm", "Support for TPM", priority, "libtss2-mu.so.0") +#define TPM2_TCTI_DEVICE_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("tpm", "Support for TPM", priority, "libtss2-tcti-device.so.0") + +#define TPM2_NOTE(priority) \ + ({ \ + TPM2_ESYS_NOTE(priority); \ + TPM2_RC_NOTE(priority); \ + TPM2_MU_NOTE(priority); \ + TPM2_TCTI_DEVICE_NOTE(priority); \ + }) + +#define DLOPEN_TPM2(log_level, priority) \ + ({ \ + TPM2_NOTE(priority); \ + dlopen_tpm2(log_level); \ + }) #include /* IWYU pragma: export */ #include /* IWYU pragma: export */ @@ -53,6 +78,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; @@ -62,6 +89,7 @@ typedef struct Tpm2Context { TPM2_ECC_CURVE *capability_ecc_curves; size_t n_capability_ecc_curves; TPML_PCR_SELECTION capability_pcrs; + uint16_t max_nv_buffer_size; } Tpm2Context; int tpm2_context_new(const char *device, Tpm2Context **ret_context); @@ -132,6 +160,8 @@ bool tpm2_test_parms(Tpm2Context *c, TPMI_ALG_PUBLIC alg, const TPMU_PUBLIC_PARM int tpm2_get_good_pcr_banks(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH **ret_banks); int tpm2_get_good_pcr_banks_strv(Tpm2Context *c, uint32_t pcr_mask, char ***ret); int tpm2_get_best_pcr_bank(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH *ret); +/* Like tpm2_get_best_pcr_bank(), but restricted to SHA256/SHA1 for re-deriving the bank of legacy enrollments */ +int tpm2_get_best_pcr_bank_legacy(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH *ret); const char* tpm2_userspace_log_path(void); const char* tpm2_firmware_log_path(void); @@ -146,6 +176,9 @@ typedef enum Tpm2UserspaceEventType { TPM2_EVENT_NVPCR_INIT, TPM2_EVENT_NVPCR_SEPARATOR, TPM2_EVENT_DM_VERITY, + TPM2_EVENT_IMDS_USERDATA, + TPM2_EVENT_OS_SEPARATOR, + TPM2_EVENT_LOGIN, _TPM2_USERSPACE_EVENT_TYPE_MAX, _TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL, } Tpm2UserspaceEventType; @@ -153,11 +186,16 @@ typedef enum Tpm2UserspaceEventType { DECLARE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType); int tpm2_pcr_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const struct iovec *data, const struct iovec *secret, Tpm2UserspaceEventType event_type, const char *description); -int tpm2_nvpcr_get_index(const char *name, uint32_t *ret); + +/* Default allocation priority for NvPCRs that do not specify one explicitly. Lower values are more + * important and are allocated first when the TPM's NV index space is constrained. */ +#define TPM2_NVPCR_PRIORITY_DEFAULT UINT64_C(1000) + +int tpm2_nvpcr_get_index(const char *name, uint32_t *ret_nv_index, uint64_t *ret_priority); int tpm2_nvpcr_extend_bytes(Tpm2Context *c, const Tpm2Handle *session, const char *name, const struct iovec *data, const struct iovec *secret, Tpm2UserspaceEventType event_type, const char *description); int tpm2_nvpcr_acquire_anchor_secret(struct iovec *ret, bool sync_secondary); int tpm2_nvpcr_initialize(Tpm2Context *c, const Tpm2Handle *session, const char *name, const struct iovec *anchor_secret); -int tpm2_nvpcr_read(Tpm2Context *c, const Tpm2Handle *session, const char *name, struct iovec *ret, uint32_t *ret_nv_index); +int tpm2_nvpcr_read(Tpm2Context *c, const Tpm2Handle *session, const char *name, struct iovec *ret, uint32_t *ret_nv_index, uint64_t *ret_priority); uint32_t tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s); void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPMS_PCR_SELECTION *ret); @@ -195,6 +233,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); @@ -249,7 +305,9 @@ int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, char **ret_path) int tpm2_pcrlock_policy_load(const char *path, Tpm2PCRLockPolicy *ret_policy); int tpm2_pcrlock_policy_from_credentials(const struct iovec *srk, const struct iovec *nv, Tpm2PCRLockPolicy *ret); -int tpm2_index_to_handle(Tpm2Context *c, TPM2_HANDLE index, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); +int tpm2_index_to_handle(Tpm2Context *c, TPM2_HANDLE index, const Tpm2Handle *session, TPM2B_NAME **ret_name, Tpm2Handle **ret_handle); +int tpm2_object_index_to_handle(Tpm2Context *c, TPM2_HANDLE index, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); +int tpm2_nv_index_to_handle(Tpm2Context *c, TPM2_HANDLE index, const Tpm2Handle *session, TPM2B_NV_PUBLIC **ret_nv_public, TPM2B_NAME **ret_name, Tpm2Handle **ret_handle); int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE *ret_index); int tpm2_pcr_read(Tpm2Context *c, const TPML_PCR_SELECTION *pcr_selection, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); @@ -304,8 +362,7 @@ 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_define_data_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const struct iovec *data, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle); 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); @@ -386,6 +443,7 @@ static inline int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, ch return -ENOENT; } +#define DLOPEN_TPM2(log_level, priority) dlopen_tpm2(log_level) #endif /* HAVE_TPM2 */ int tpm2_list_devices(bool legend, bool quiet); @@ -430,6 +488,12 @@ int tpm2_parse_luks2_json(sd_json_variant *v, int *ret_keyslot, uint32_t *ret_ha #define TPM2_ALG_RSA 0x1 #endif +/* Picks the most preferred PCR bank (SHA256 > SHA384 > SHA512 > SHA1) out of the firmware-reported active + * banks bitmask. Defined unconditionally (no TPM2 libraries required) so it can be unit tested. */ +int tpm2_pcr_bank_from_efi_active(uint32_t active_banks, uint16_t *ret); +/* Like tpm2_pcr_bank_from_efi_active(), but restricted to SHA256/SHA1, for re-deriving the bank of legacy enrollments */ +int tpm2_pcr_bank_from_efi_active_legacy(uint32_t active_banks, uint16_t *ret); + int tpm2_hash_alg_to_size(uint16_t alg); const char* tpm2_hash_alg_to_string(uint16_t alg) _const_; @@ -477,6 +541,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 +551,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/tsm-report.c b/src/shared/tsm-report.c new file mode 100644 index 0000000000000..d10eb7fdbfac8 --- /dev/null +++ b/src/shared/tsm-report.c @@ -0,0 +1,214 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-id128.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "format-util.h" +#include "fs-util.h" +#include "io-util.h" +#include "iovec-util.h" +#include "log.h" +#include "parse-util.h" +#include "process-util.h" +#include "stdio-util.h" +#include "tsm-report.h" + +#define TSM_REPORT_PATH "/sys/kernel/config/tsm/report" + +TsmReport *tsm_report_free(TsmReport *report) { + if (!report) + return NULL; + + free(report->provider); + iovec_done(&report->outblob); + iovec_done(&report->auxblob); + iovec_done(&report->manifestblob); + + return mfree(report); +} + +static int read_generation(int entry_fd, uint64_t *ret) { + _cleanup_free_ char *s = NULL; + int r; + + assert(entry_fd >= 0); + assert(ret); + + /* The kernel bumps this counter on every option write to the entry. We snapshot it before writing + * and re-check it after reading the report to detect a writer racing us on this entry. */ + + r = read_one_line_file_at(entry_fd, "generation", &s); + if (r < 0) + return log_debug_errno(r, "Failed to read 'generation' attribute: %m"); + + r = safe_atou64(s, ret); + if (r < 0) + return log_debug_errno(r, "Failed to parse 'generation' attribute: %m"); + + return 0; +} + +static int tsm_report_fill( + int entry_fd, + const struct iovec *report_data, + const TsmReportOptions *options, + TsmReport **ret) { + + _cleanup_close_ int inblob_fd = -EBADF; + _cleanup_(tsm_report_freep) TsmReport *report = NULL; + _cleanup_free_ char *floor = NULL; + bool has_privlevel = false, has_floor = false; + unsigned privlevel = 0, privlevel_floor = 0; + int r; + + assert(entry_fd >= 0); + assert(report_data); + assert(ret); + + r = read_one_line_file_at(entry_fd, "privlevel_floor", &floor); + if (r >= 0) { + r = safe_atou(floor, &privlevel_floor); + if (r < 0) + return log_debug_errno(r, "Failed to parse 'privlevel_floor' attribute: %m"); + has_floor = true; + } else if (r != -ENOENT) + return log_debug_errno(r, "Failed to read 'privlevel_floor' attribute: %m"); + /* -ENOENT: provider has no privlevel concept, leave it unset. */ + + if (options && options->privlevel_set) { + if (!has_floor) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "TSM provider does not support 'privlevel'."); + + if (options->privlevel < privlevel_floor) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Requested privlevel %u is below the provider's floor %u.", + options->privlevel, privlevel_floor); + + privlevel = options->privlevel; + has_privlevel = true; + } else if (has_floor) { + privlevel = privlevel_floor; + has_privlevel = true; + } + + /* Snapshot the write-generation counter before touching any input. Note that tracking generations + * is defense in-depth, as we are already operating on a private directory per report request. */ + uint64_t generation; + r = read_generation(entry_fd, &generation); + if (r < 0) + return r; + + /* Write inputs. Each successful option write bumps the kernel's generation by one. */ + + if (has_privlevel) { + char privlvl_buf[DECIMAL_STR_MAX(unsigned)]; + xsprintf(privlvl_buf, "%u", privlevel); + + r = write_string_file_at(entry_fd, "privlevel", + privlvl_buf, + WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return log_debug_errno(r, "Failed to write 'privlevel' attribute: %m"); + generation++; + } + + inblob_fd = openat(entry_fd, "inblob", O_WRONLY|O_CLOEXEC); + if (inblob_fd < 0) + return log_debug_errno(errno, "Failed to open 'inblob' attribute: %m"); + r = loop_write(inblob_fd, report_data->iov_base, report_data->iov_len); + if (r < 0) + return log_debug_errno(r, "Failed to write 'inblob' attribute: %m"); + inblob_fd = safe_close(inblob_fd); /* configfs commits the buffered write only on close. */ + generation++; + + /* Read output. */ + + report = new0(TsmReport, 1); + if (!report) + return log_oom_debug(); + + r = read_full_file_at(entry_fd, "outblob", + (char**) &report->outblob.iov_base, &report->outblob.iov_len); + if (r < 0) + return log_debug_errno(r, "Failed to read 'outblob' attribute: %m"); + + r = read_one_line_file_at(entry_fd, "provider", &report->provider); + if (r < 0) + return log_debug_errno(r, "Failed to read 'provider' attribute: %m"); + + r = read_full_file_at(entry_fd, "auxblob", + (char**) &report->auxblob.iov_base, &report->auxblob.iov_len); + if (r < 0 && r != -ENOENT) /* auxblob is optional */ + return log_debug_errno(r, "Failed to read 'auxblob' attribute: %m"); + + r = read_full_file_at(entry_fd, "manifestblob", + (char**) &report->manifestblob.iov_base, &report->manifestblob.iov_len); + if (r < 0 && r != -ENOENT) /* manifestblob is optional */ + return log_debug_errno(r, "Failed to read 'manifestblob' attribute: %m"); + + uint64_t generation_now; + r = read_generation(entry_fd, &generation_now); + if (r < 0) + return r; + if (generation_now != generation) + return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), + "TSM report generation changed during acquisition (%" PRIu64 " != %" PRIu64 "), concurrent access?", + generation_now, generation); + + *ret = TAKE_PTR(report); + return 0; +} + +int tsm_report_acquire( + const struct iovec *report_data, + const TsmReportOptions *options, + TsmReport **ret) { + + _cleanup_close_ int report_fd = -EBADF, entry_fd = -EBADF; + _cleanup_free_ char *name = NULL; + sd_id128_t rnd; + int r; + + assert(ret); + + if (!report_data || !iovec_is_set(report_data)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Report data can't be empty"); + if (report_data->iov_len != TSM_REPORT_DATA_SIZE) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Report data must be %u bytes.", + TSM_REPORT_DATA_SIZE); + + report_fd = open(TSM_REPORT_PATH, O_DIRECTORY|O_CLOEXEC|O_RDONLY); + if (report_fd < 0) { + if (errno == ENOENT) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "configfs-tsm interface not available at " TSM_REPORT_PATH "."); + return log_debug_errno(errno, "Failed to open " TSM_REPORT_PATH ": %m"); + } + + /* Private, unique entry name so we don't race with other callers. + * PID is included for attribution. */ + r = sd_id128_randomize(&rnd); + if (r < 0) + return log_debug_errno(r, "Failed to generate report entry name: %m"); + r = asprintf(&name, "systemd-report-" PID_FMT "-%s", getpid_cached(), SD_ID128_TO_STRING(rnd)); + if (r < 0) + return log_oom_debug(); + + entry_fd = open_mkdir_at(report_fd, name, O_EXCL|O_RDONLY|O_CLOEXEC, 0700); + if (entry_fd < 0) + return log_debug_errno(entry_fd, "Failed to create TSM report entry: %m"); + + r = tsm_report_fill(entry_fd, report_data, options, ret); + + /* Remove the entry regardless of success/failure. */ + if (unlinkat(report_fd, name, AT_REMOVEDIR) < 0) + log_debug_errno(errno, "Failed to remove TSM report entry '%s', ignoring: %m", name); + + return r; +} diff --git a/src/shared/tsm-report.h b/src/shared/tsm-report.h new file mode 100644 index 0000000000000..6b7d63ac608e1 --- /dev/null +++ b/src/shared/tsm-report.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "shared-forward.h" + +#define TSM_REPORT_DATA_SIZE 64U + +/* Optional knobs. Mirrors the write-only attributes other than inblob. + * Zero-initialize ({}) and set only what you need. NULL options == all defaults. */ +typedef struct TsmReportOptions { + unsigned privlevel; /* e.g. SEV-SNP VMPL */ + bool privlevel_set; /* privlevel_floor is used when unset */ + /* service_provider currently not supported. */ +} TsmReportOptions; + +/* Result. Mirrors the read-only attributes. */ +typedef struct TsmReport { + char *provider; /* e.g. "sev_guest", "tdx_guest" */ + struct iovec outblob; /* the attestation report */ + struct iovec auxblob; /* optional, unset if empty (e.g. SEV cert_table) */ + struct iovec manifestblob; /* optional, unset if empty */ +} TsmReport; + +TsmReport *tsm_report_free(TsmReport *report); +DEFINE_TRIVIAL_CLEANUP_FUNC(TsmReport*, tsm_report_free); + +/* Acquire an attestation report via configfs-tsm. + * report_data: mandatory inblob to include in the report, TSM_REPORT_DATA_SIZE bytes + * options: optional, NULL for defaults + * ret: result, freed with tsm_report_free() + * Returns -EOPNOTSUPP if the configfs-tsm interface is absent, -ENXIO if it is present + * but no provider is registered. Other negative errnos are real failures. */ +int tsm_report_acquire( + const struct iovec *report_data, + const TsmReportOptions *options, + TsmReport **ret); 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-nss.c b/src/shared/user-record-nss.c index 96adbb21f1dcd..405ec2c4343de 100644 --- a/src/shared/user-record-nss.c +++ b/src/shared/user-record-nss.c @@ -8,6 +8,7 @@ #include "errno-util.h" #include "format-util.h" #include "group-record.h" +#include "json-util.h" #include "libcrypt-util.h" #include "log.h" #include "string-util.h" @@ -45,9 +46,23 @@ static int strv_extend_strv_utf8_only(char ***dst, char **src, bool filter_dupli return strv_extend_strv(dst, t, filter_duplicates); } +static int nss_add_valid_alias(char ***aliases, const char *name, const char *alias_name) { + assert(aliases); + assert(name); + + if (isempty(alias_name) || streq_ptr(alias_name, name)) + return 0; + + if (!valid_user_group_name(alias_name, VALID_USER_RELAX)) + return 0; + + return strv_extend(aliases, alias_name); +} + int nss_passwd_to_user_record( const struct passwd *pwd, const struct spwd *spwd, + const char *alias_name, UserRecord **ret) { _cleanup_(user_record_unrefp) UserRecord *hr = NULL; @@ -69,6 +84,10 @@ int nss_passwd_to_user_record( if (r < 0) return r; + r = nss_add_valid_alias(&hr->aliases, hr->user_name, alias_name); + if (r < 0) + return r; + /* Some bad NSS modules synthesize GECOS fields with embedded ":" or "\n" characters, which are not * something we can output in /etc/passwd compatible format, since these are record separators * there. We normally refuse that, but we need to maintain compatibility with arbitrary NSS modules, @@ -150,6 +169,7 @@ int nss_passwd_to_user_record( r = sd_json_buildo( &hr->json, SD_JSON_BUILD_PAIR_STRING("userName", hr->user_name), + JSON_BUILD_PAIR_STRV_NON_EMPTY("aliases", hr->aliases), SD_JSON_BUILD_PAIR_UNSIGNED("uid", hr->uid), SD_JSON_BUILD_PAIR_UNSIGNED("gid", user_record_gid(hr)), SD_JSON_BUILD_PAIR_CONDITION(!!hr->real_name, "realName", SD_JSON_BUILD_STRING(hr->real_name)), @@ -240,7 +260,7 @@ int nss_user_record_by_name( } else incomplete = true; - r = nss_passwd_to_user_record(result, sresult, ret); + r = nss_passwd_to_user_record(result, sresult, name, ret); if (r < 0) return r; @@ -274,7 +294,7 @@ int nss_user_record_by_uid( } else incomplete = true; - r = nss_passwd_to_user_record(result, sresult, ret); + r = nss_passwd_to_user_record(result, sresult, /* alias_name= */ NULL, ret); if (r < 0) return r; @@ -286,6 +306,7 @@ int nss_user_record_by_uid( int nss_group_to_group_record( const struct group *grp, const struct sgrp *sgrp, + const char *alias_name, GroupRecord **ret) { _cleanup_(group_record_unrefp) GroupRecord *g = NULL; @@ -307,6 +328,10 @@ int nss_group_to_group_record( if (!g->group_name) return -ENOMEM; + r = nss_add_valid_alias(&g->aliases, g->group_name, alias_name); + if (r < 0) + return r; + r = strv_extend_strv_utf8_only(&g->members, grp->gr_mem, false); if (r < 0) return r; @@ -332,6 +357,7 @@ int nss_group_to_group_record( r = sd_json_buildo( &g->json, SD_JSON_BUILD_PAIR_STRING("groupName", g->group_name), + JSON_BUILD_PAIR_STRV_NON_EMPTY("aliases", g->aliases), SD_JSON_BUILD_PAIR_UNSIGNED("gid", g->gid), SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->members), "members", SD_JSON_BUILD_STRV(g->members)), SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->hashed_password), "privileged", SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_STRV("hashedPassword", g->hashed_password))), @@ -412,7 +438,7 @@ int nss_group_record_by_name( } else incomplete = true; - r = nss_group_to_group_record(result, sresult, ret); + r = nss_group_to_group_record(result, sresult, name, ret); if (r < 0) return r; @@ -446,7 +472,7 @@ int nss_group_record_by_gid( } else incomplete = true; - r = nss_group_to_group_record(result, sresult, ret); + r = nss_group_to_group_record(result, sresult, /* alias_name= */ NULL, ret); if (r < 0) return r; diff --git a/src/shared/user-record-nss.h b/src/shared/user-record-nss.h index 835b1d686baa4..53681c317827f 100644 --- a/src/shared/user-record-nss.h +++ b/src/shared/user-record-nss.h @@ -5,13 +5,13 @@ /* Synthesize UserRecord and GroupRecord objects from NSS data */ -int nss_passwd_to_user_record(const struct passwd *pwd, const struct spwd *spwd, UserRecord **ret); +int nss_passwd_to_user_record(const struct passwd *pwd, const struct spwd *spwd, const char *alias_name, UserRecord **ret); int nss_spwd_for_passwd(const struct passwd *pwd, struct spwd *ret_spwd, char **ret_buffer); int nss_user_record_by_name(const char *name, bool with_shadow, UserRecord **ret); int nss_user_record_by_uid(uid_t uid, bool with_shadow, UserRecord **ret); -int nss_group_to_group_record(const struct group *grp, const struct sgrp *sgrp, GroupRecord **ret); +int nss_group_to_group_record(const struct group *grp, const struct sgrp *sgrp, const char *alias_name, GroupRecord **ret); int nss_sgrp_for_group(const struct group *grp, struct sgrp *ret_sgrp, char **ret_buffer); int nss_group_record_by_name(const char *name, bool with_shadow, GroupRecord **ret); 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..3a005df08e520 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; @@ -492,6 +518,7 @@ static int json_dispatch_locales(const char *name, sd_json_variant *variant, sd_ char ***l = userdata; const char *locale; sd_json_variant *e; + size_t s = 0; int r; if (sd_json_variant_is_null(variant)) { @@ -510,7 +537,7 @@ static int json_dispatch_locales(const char *name, sd_json_variant *variant, sd_ if (!locale_is_valid(locale)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of valid locales.", strna(name)); - r = strv_extend(&n, locale); + r = strv_extend_with_size(&n, &s, locale); if (r < 0) return json_log_oom(variant, flags); } @@ -554,11 +581,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; @@ -567,6 +594,7 @@ static int json_dispatch_weight(const char *name, sd_json_variant *variant, sd_j int json_dispatch_user_group_list(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { char ***list = ASSERT_PTR(userdata); _cleanup_strv_free_ char **l = NULL; + size_t s = 0; int r; if (!sd_json_variant_is_array(variant)) @@ -580,7 +608,7 @@ int json_dispatch_user_group_list(const char *name, sd_json_variant *variant, sd if (!valid_user_group_name(sd_json_variant_string(e), FLAGS_SET(flags, SD_JSON_RELAX) ? VALID_USER_RELAX : 0)) return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a valid user/group name: %s", sd_json_variant_string(e)); - r = strv_extend(&l, sd_json_variant_string(e)); + r = strv_extend_with_size(&l, &s, sd_json_variant_string(e)); if (r < 0) return json_log(e, flags, r, "Failed to append array element: %m"); } @@ -701,6 +729,29 @@ static int dispatch_pkcs11_key_data(const char *name, sd_json_variant *variant, return 0; } +static int dispatch_pkcs11_padding(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + Pkcs11RsaPadding *p = ASSERT_PTR(userdata); + Pkcs11RsaPadding v; + + if (sd_json_variant_is_null(variant)) { + *p = PKCS11_RSA_PADDING_PKCS1V15; + 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)); + + v = pkcs11_rsa_padding_from_string(sd_json_variant_string(variant)); + if (v < 0) + return json_log(variant, flags, v, + "JSON field '%s' has unsupported value '%s'.", + strna(name), sd_json_variant_string(variant)); + + *p = v; + return 0; +} + static int dispatch_pkcs11_key(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { UserRecord *h = userdata; sd_json_variant *e; @@ -714,6 +765,7 @@ static int dispatch_pkcs11_key(const char *name, sd_json_variant *variant, sd_js { "uri", SD_JSON_VARIANT_STRING, dispatch_pkcs11_uri, offsetof(Pkcs11EncryptedKey, uri), SD_JSON_MANDATORY }, { "data", SD_JSON_VARIANT_STRING, dispatch_pkcs11_key_data, 0, SD_JSON_MANDATORY }, { "hashedPassword", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Pkcs11EncryptedKey, hashed_password), SD_JSON_MANDATORY }, + { "padding", SD_JSON_VARIANT_STRING, dispatch_pkcs11_padding, offsetof(Pkcs11EncryptedKey, padding), 0 }, {}, }; @@ -724,7 +776,9 @@ static int dispatch_pkcs11_key(const char *name, sd_json_variant *variant, sd_js return log_oom(); Pkcs11EncryptedKey *k = h->pkcs11_encrypted_key + h->n_pkcs11_encrypted_key; - *k = (Pkcs11EncryptedKey) {}; + *k = (Pkcs11EncryptedKey) { + .padding = PKCS11_RSA_PADDING_PKCS1V15, /* legacy default if field is absent */ + }; r = sd_json_dispatch(e, pkcs11_key_dispatch_table, flags, k); if (r < 0) { @@ -1161,7 +1215,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 +1542,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 +1647,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 +1889,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 +1950,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 +2127,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) { @@ -2333,8 +2400,7 @@ static int remove_self_modifiable_json_fields_common(UserRecord *current, sd_jso return r; } - JSON_VARIANT_REPLACE(*target, TAKE_PTR(v)); - return 0; + return json_variant_unref_and_replace(*target, v); } static int remove_self_modifiable_json_fields(UserRecord *current, UserRecord *h, sd_json_variant **ret) { @@ -2419,8 +2485,7 @@ static int remove_self_modifiable_json_fields(UserRecord *current, UserRecord *h return r; } - JSON_VARIANT_REPLACE(*ret, TAKE_PTR(v)); - return 0; + return json_variant_unref_and_replace(*ret, v); } int user_record_self_changes_allowed(UserRecord *current, UserRecord *incoming) { @@ -2448,7 +2513,7 @@ int user_record_self_changes_allowed(UserRecord *current, UserRecord *incoming) * `selfModifiableFields` fields are unset in their record. * 2) This user crafts a request to add the following to their record: * { "memberOf": ["wheel"], "selfModifiableFields": ["memberOf", "selfModifiableFields"] } - * 3) We remove the `mebmerOf` and `selfModifiabileFields` fields from `incoming` + * 3) We remove the `memberOf` and `selfModifiabileFields` fields from `incoming` * 4) `current` and `incoming` compare as equal, so we let the change happen * 5) the user has granted themselves administrator privileges */ @@ -2710,11 +2775,24 @@ bool user_record_matches_user_name(const UserRecord *u, const char *user_name) { if (strv_contains(u->aliases, user_name)) return true; - const char *realm = strrchr(user_name, '@'); - if (realm && streq_ptr(realm+1, u->realm)) - STRV_FOREACH(a, u->aliases) - if (startswith(user_name, *a) == realm) - return true; + if (record_name_matches_alias_realm(user_name, (char * const*) u->aliases, u->realm)) + return true; + + return false; +} + +bool record_name_matches_alias_realm(const char *name, char * const *aliases, const char *realm) { + const char *suffix; + + assert(name); + + suffix = strrchr(name, '@'); + if (!suffix || !streq_ptr(suffix + 1, realm)) + return false; + + STRV_FOREACH(a, aliases) + if (startswith(name, *a) == suffix) + return true; return false; } diff --git a/src/shared/user-record.h b/src/shared/user-record.h index c3d52d2bd71aa..aee78694a90fa 100644 --- a/src/shared/user-record.h +++ b/src/shared/user-record.h @@ -1,9 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include + #include "sd-id128.h" #include "bitfield.h" +#include "pkcs11-padding.h" #include "rlimit-util.h" #include "shared-forward.h" @@ -187,6 +190,10 @@ typedef struct Pkcs11EncryptedKey { /* Where to find the private key to decrypt the encrypted passphrase above */ char *uri; + /* Which RSA padding scheme was used to wrap the encrypted passphrase. Defaults to PKCS#1 v1.5 for + * legacy records that omit the field; new enrollments use RSA-OAEP with SHA-256 or SHA-1. */ + Pkcs11RsaPadding padding; + /* What to test the decrypted passphrase against to allow access (classic UNIX password hash). Note * that the decrypted passphrase is also used for unlocking LUKS and fscrypt, and if the account is * backed by LUKS or fscrypt the hashed password is only an additional layer of authentication, not @@ -266,6 +273,7 @@ typedef struct UserRecord { char *password_hint; char *icon_name; char *location; + struct tm birth_date; char *blob_directory; Hashmap *blob_manifest; @@ -538,6 +546,7 @@ bool userdb_match_is_set(const UserDBMatch *match) _pure_; void userdb_match_done(UserDBMatch *match); +bool record_name_matches_alias_realm(const char *name, char * const *aliases, const char *realm); bool user_name_fuzzy_match(const char *names[], size_t n_names, char **matches); bool user_record_match(UserRecord *u, const UserDBMatch *match); diff --git a/src/shared/userdb.c b/src/shared/userdb.c index 9891e0e733392..14848150c5b47 100644 --- a/src/shared/userdb.c +++ b/src/shared/userdb.c @@ -1124,7 +1124,7 @@ static int userdb_iterator_get_one(UserDBIterator *iterator, UserRecord **ret) { incomplete = true; } - r = nss_passwd_to_user_record(pw, r >= 0 ? &spwd : NULL, ret); + r = nss_passwd_to_user_record(pw, r >= 0 ? &spwd : NULL, /* alias_name= */ NULL, ret); if (r < 0) return r; @@ -1570,7 +1570,7 @@ static int groupdb_iterator_get_one(UserDBIterator *iterator, GroupRecord **ret) incomplete = true; } - r = nss_group_to_group_record(gr, r >= 0 ? &sgrp : NULL, ret); + r = nss_group_to_group_record(gr, r >= 0 ? &sgrp : NULL, /* alias_name= */ NULL, ret); if (r < 0) return r; 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..d9b148daac66a 100644 --- a/src/shared/varlink-idl-common.c +++ b/src/shared/varlink-idl-common.c @@ -53,3 +53,87 @@ 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_STRUCT_TYPE( + ExecCommandStatus, + SD_VARLINK_FIELD_COMMENT("Process ID"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PID, ProcessId, 0), + SD_VARLINK_FIELD_COMMENT("Timestamp when the process started"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(StartTimestamp, Timestamp, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Timestamp when the process exited"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExitTimestamp, Timestamp, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Timestamp when the process was handed off to the executor"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(HandoffTimestamp, Timestamp, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Termination reason (si_code): CLD_EXITED, CLD_KILLED, CLD_DUMPED"), + SD_VARLINK_DEFINE_FIELD(Code, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Exit code or signal number (si_status), meaning depends on Code"), + SD_VARLINK_DEFINE_FIELD(Status, SD_VARLINK_INT, 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( + OOMPolicy, + SD_VARLINK_DEFINE_ENUM_VALUE(continue), + SD_VARLINK_DEFINE_ENUM_VALUE(stop), + 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..5bd351734a9b9 100644 --- a/src/shared/varlink-idl-common.h +++ b/src/shared/varlink-idl-common.h @@ -8,3 +8,10 @@ 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_ExecCommandStatus; +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_OOMPolicy; +extern const sd_varlink_symbol vl_type_EmergencyAction; diff --git a/src/shared/varlink-io.systemd.BootControl.c b/src/shared/varlink-io.systemd.BootControl.c index 70203300ffe5e..0be93990d9aaa 100644 --- a/src/shared/varlink-io.systemd.BootControl.c +++ b/src/shared/varlink-io.systemd.BootControl.c @@ -27,7 +27,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The location of the local addon."), SD_VARLINK_DEFINE_FIELD(localAddon, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The command line options by the addon."), - SD_VARLINK_DEFINE_FIELD(options, SD_VARLINK_STRING, 0)); + SD_VARLINK_DEFINE_FIELD(options, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_STRUCT_TYPE( BootEntry, @@ -80,6 +80,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(isSelected, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Addon images of the entry."), SD_VARLINK_DEFINE_FIELD_BY_TYPE(addons, BootEntryAddon, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("Extra files associated with the entry."), + SD_VARLINK_DEFINE_FIELD(extras, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), SD_VARLINK_FIELD_COMMENT("Command line options of the entry."), SD_VARLINK_DEFINE_FIELD(cmdline, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); @@ -134,6 +136,76 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_FIELD_COMMENT("If true the boot loader will be registered in an EFI boot entry via EFI variables, otherwise this is omitted"), SD_VARLINK_DEFINE_INPUT(touchVariables, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + Unlink, + SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors passed along with this message, pointing to file descriptor to root file system to operate on"), + SD_VARLINK_DEFINE_INPUT(rootFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Root directory to operate relative to. If both this and rootFileDescriptor is specified, this is purely informational. If only this is specified, it is what will be used."), + SD_VARLINK_DEFINE_INPUT(rootDirectory, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Selects how to identify boot entries"), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(bootEntryTokenType, BootEntryTokenType, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The ID of the boot loader entry to remove."), + SD_VARLINK_DEFINE_INPUT(id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true, remove the oldest entry."), + SD_VARLINK_DEFINE_INPUT(oldest, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + BootEntryExtraFile, + SD_VARLINK_FIELD_COMMENT("The name of the extra file"), + SD_VARLINK_DEFINE_FIELD(filename, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors, pointing to a file descriptor referencing the extra file to copy in. Either this or the 'data' field below must be set – not both, not neither."), + SD_VARLINK_DEFINE_FIELD(fileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Literal data to place in the extra file."), + SD_VARLINK_DEFINE_FIELD(data, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + Link, + SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors passed along with this message, pointing to file descriptor to root file system to operate on"), + SD_VARLINK_DEFINE_INPUT(rootFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Root directory to operate relative to. If both this and rootFileDescriptor is specified, this is purely informational. If only this is specified, it is what will be used."), + SD_VARLINK_DEFINE_INPUT(rootDirectory, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Selects how to identify boot entries"), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(bootEntryTokenType, BootEntryTokenType, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The entry title for the newly created boot menu entry"), + SD_VARLINK_DEFINE_INPUT(entryTitle, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The entry version for the newly created boot menu entry"), + SD_VARLINK_DEFINE_INPUT(entryVersion, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The commit number for the newly created boot menu entry"), + SD_VARLINK_DEFINE_INPUT(entryCommit, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Target filename for the kernel image (UKI) in the $BOOT partition"), + SD_VARLINK_DEFINE_INPUT(kernelFilename, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors passed along with this message, pointing to file descriptor to the kernel image to copy"), + SD_VARLINK_DEFINE_INPUT(kernelFileDescriptor, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("An array of 'extra' files for this entry, i.e. credentials, confexts, sysexts, addons."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(extraFiles, BootEntryExtraFile, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("What to set the triesLeft counter of the boot menu entry to initially."), + SD_VARLINK_DEFINE_INPUT(triesLeft, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("How much space to always keep free on ESP/XBOOTLDR. Defaults to 1 MiB"), + SD_VARLINK_DEFINE_INPUT(keepFree, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The IDs of the created boot loader entries."), + SD_VARLINK_DEFINE_OUTPUT(ids, SD_VARLINK_STRING, SD_VARLINK_ARRAY)); + +static SD_VARLINK_DEFINE_METHOD( + LinkAuto, + SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors passed along with this message, pointing to file descriptor to root file system to operate on"), + SD_VARLINK_DEFINE_INPUT(rootFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Root directory to operate relative to. If both this and rootFileDescriptor is specified, this is purely informational. If only this is specified, it is what will be used."), + SD_VARLINK_DEFINE_INPUT(rootDirectory, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Selects how to identify boot entries"), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(bootEntryTokenType, BootEntryTokenType, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The entry title for the newly created boot menu entry"), + SD_VARLINK_DEFINE_INPUT(entryTitle, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The entry version for the newly created boot menu entry"), + SD_VARLINK_DEFINE_INPUT(entryVersion, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The commit number for the newly created boot menu entry"), + SD_VARLINK_DEFINE_INPUT(entryCommit, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("What to set the triesLeft counter of the boot menu entry to initially."), + SD_VARLINK_DEFINE_INPUT(triesLeft, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("How much space to always keep free on ESP/XBOOTLDR. Defaults to 1 MiB"), + SD_VARLINK_DEFINE_INPUT(keepFree, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The IDs of the created boot loader entries."), + SD_VARLINK_DEFINE_OUTPUT(ids, SD_VARLINK_STRING, SD_VARLINK_ARRAY)); + static SD_VARLINK_DEFINE_ERROR( RebootToFirmwareNotSupported); @@ -143,9 +215,15 @@ static SD_VARLINK_DEFINE_ERROR( static SD_VARLINK_DEFINE_ERROR( NoESPFound); +static SD_VARLINK_DEFINE_ERROR( + NoDollarBootFound); + static SD_VARLINK_DEFINE_ERROR( BootEntryTokenUnavailable); +static SD_VARLINK_DEFINE_ERROR( + InvalidKernelImage); + SD_VARLINK_DEFINE_INTERFACE( io_systemd_BootControl, "io.systemd.BootControl", @@ -156,6 +234,8 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_BootEntrySource, SD_VARLINK_SYMBOL_COMMENT("A structure encapsulating an addon of a boot entry"), &vl_type_BootEntryAddon, + SD_VARLINK_SYMBOL_COMMENT("An additional file to install"), + &vl_type_BootEntryExtraFile, SD_VARLINK_SYMBOL_COMMENT("A structure encapsulating a boot entry"), &vl_type_BootEntry, SD_VARLINK_SYMBOL_COMMENT("The operation to execute"), @@ -170,11 +250,21 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_BootEntryTokenType, SD_VARLINK_SYMBOL_COMMENT("Install the boot loader on the ESP."), &vl_method_Install, + SD_VARLINK_SYMBOL_COMMENT("Unlink a boot menu item"), + &vl_method_Unlink, + SD_VARLINK_SYMBOL_COMMENT("Install a kernel as boot menu item"), + &vl_method_Link, + SD_VARLINK_SYMBOL_COMMENT("Like Link(), but automatically discovers the UKI and extra resources staged below /var/lib/systemd/uki/"), + &vl_method_LinkAuto, SD_VARLINK_SYMBOL_COMMENT("SetRebootToFirmware() and GetRebootToFirmware() return this if the firmware does not actually support the reboot-to-firmware-UI concept."), &vl_error_RebootToFirmwareNotSupported, SD_VARLINK_SYMBOL_COMMENT("No boot entry defined."), &vl_error_NoSuchBootEntry, SD_VARLINK_SYMBOL_COMMENT("No EFI System Partition (ESP) found."), &vl_error_NoESPFound, - SD_VARLINK_SYMBOL_COMMENT("The select boot entry token could not be determined."), - &vl_error_BootEntryTokenUnavailable); + SD_VARLINK_SYMBOL_COMMENT("Neither ESP nor XBOOTLDR found, hence no $BOOT location identified."), + &vl_error_NoDollarBootFound, + SD_VARLINK_SYMBOL_COMMENT("The selected boot entry token could not be determined."), + &vl_error_BootEntryTokenUnavailable, + SD_VARLINK_SYMBOL_COMMENT("The specified kernel image is not valid."), + &vl_error_InvalidKernelImage); diff --git a/src/shared/varlink-io.systemd.CryptEnroll.c b/src/shared/varlink-io.systemd.CryptEnroll.c new file mode 100644 index 0000000000000..6855299e7639e --- /dev/null +++ b/src/shared/varlink-io.systemd.CryptEnroll.c @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bus-polkit.h" +#include "varlink-io.systemd.CryptEnroll.h" + +/* The credential types this interface knows about. The same enum is used for the 'mechanism' to enroll, for + * the slot types to wipe, and for the slot types reported by ListSlots. Note that only password, recovery key + * and fido2 may actually be *enrolled* via the Enroll() method; pkcs11 and tpm2 slots can be listed and wiped, + * but enrolling them requires the systemd-cryptenroll command line. Enroll() rejects them with an + * InvalidParameter error rather than via interface validation, so they are part of this enum. */ +static SD_VARLINK_DEFINE_ENUM_TYPE( + EnrollMechanism, + SD_VARLINK_FIELD_COMMENT("A regular passphrase"), + SD_VARLINK_DEFINE_ENUM_VALUE(password), + SD_VARLINK_FIELD_COMMENT("A randomly generated recovery key"), + SD_VARLINK_DEFINE_ENUM_VALUE(recovery), + SD_VARLINK_FIELD_COMMENT("A PKCS#11 security token (not enrollable via this interface)"), + SD_VARLINK_DEFINE_ENUM_VALUE(pkcs11), + SD_VARLINK_FIELD_COMMENT("A FIDO2 security token"), + SD_VARLINK_DEFINE_ENUM_VALUE(fido2), + SD_VARLINK_FIELD_COMMENT("A TPM2 device (not enrollable via this interface)"), + SD_VARLINK_DEFINE_ENUM_VALUE(tpm2)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + Enroll, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("Path to the LUKS2 block device or image to operate on."), + SD_VARLINK_DEFINE_INPUT(node, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Which kind of credential to enroll. Only 'password', 'recovery' and 'fido2' may be enrolled via this interface for now; 'pkcs11' and 'tpm2' are rejected with an InvalidParameter error, currently."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(mechanism, EnrollMechanism, 0), + + SD_VARLINK_FIELD_COMMENT("How to unlock the volume for the enrollment operation is inferred from which of the following fields are set: setting unlockPassword unlocks via that password, unlockKeyFile/unlockKeyFileDescriptor via a key file, unlockFido2Device via FIDO2, unlockTpm2Device via TPM2. Exactly one must be set."), + SD_VARLINK_DEFINE_INPUT(unlockPassword, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Path to a key file to unlock the volume with."), + SD_VARLINK_DEFINE_INPUT(unlockKeyFile, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Index into the file descriptors passed along with this call, identifying a key file to unlock the volume with."), + SD_VARLINK_DEFINE_INPUT(unlockKeyFileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Path to a FIDO2 device to unlock the volume with. Leave the path empty to automatically discover a suitable device."), + SD_VARLINK_DEFINE_INPUT(unlockFido2Device, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Path to a TPM2 device to unlock the volume with. Leave the path empty to automatically discover a suitable device."), + SD_VARLINK_DEFINE_INPUT(unlockTpm2Device, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + + SD_VARLINK_FIELD_COMMENT("The passphrase to enroll, when mechanism is 'password'. Contains key material."), + SD_VARLINK_DEFINE_INPUT(password, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + + SD_VARLINK_FIELD_COMMENT("Path to the FIDO2 device to enroll, when mechanism is 'fido2'. Leave empty to automatically discover a suitable device."), + SD_VARLINK_DEFINE_INPUT(fido2Device, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The FIDO2 token PIN to use for enrollment. Contains key material."), + SD_VARLINK_DEFINE_INPUT(fido2Pin, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Whether unlocking the FIDO2 enrollment shall require a client PIN. Defaults to true."), + SD_VARLINK_DEFINE_INPUT(fido2WithClientPin, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Whether unlocking the FIDO2 enrollment shall require user presence (touch). Defaults to true."), + SD_VARLINK_DEFINE_INPUT(fido2WithUserPresence, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Whether unlocking the FIDO2 enrollment shall require user verification. Defaults to false."), + SD_VARLINK_DEFINE_INPUT(fido2WithUserVerification, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + + SD_VARLINK_FIELD_COMMENT("Explicit list of keyslot indexes to wipe after enrolling."), + SD_VARLINK_DEFINE_INPUT(wipeSlots, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Wipe all already-enrolled slots of these types after enrolling."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(wipeTypes, EnrollMechanism, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + + VARLINK_DEFINE_POLKIT_INPUT, + + SD_VARLINK_FIELD_COMMENT("The keyslot the new credential was enrolled into. Set on the terminating reply only."), + SD_VARLINK_DEFINE_OUTPUT(slot, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The keyslots wiped as part of this call, if any."), + SD_VARLINK_DEFINE_OUTPUT(wipedSlots, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The generated recovery key, when mechanism is 'recoveryKey'. Contains key material."), + SD_VARLINK_DEFINE_OUTPUT(recoveryKey, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Progress indicator, only sent on intermediate replies when 'more' was set. Currently the only value is 'touch', signalling that the FIDO2 token is waiting for a touch. Unset on the terminating reply."), + SD_VARLINK_DEFINE_OUTPUT(state, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + ListSlots, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("Path to the LUKS2 block device or image to operate on."), + SD_VARLINK_DEFINE_INPUT(node, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("A currently enrolled keyslot index."), + SD_VARLINK_DEFINE_OUTPUT(slot, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The type of the keyslot."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(type, EnrollMechanism, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_ERROR(VolumeUnderForeignManagement); +static SD_VARLINK_DEFINE_ERROR(PasswordRequired); +static SD_VARLINK_DEFINE_ERROR(PasswordIncorrect); +static SD_VARLINK_DEFINE_ERROR(FidoDeviceNotFound); +static SD_VARLINK_DEFINE_ERROR(FidoActionTimeout); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_CryptEnroll, + "io.systemd.CryptEnroll", + SD_VARLINK_INTERFACE_COMMENT("API for enrolling credentials into LUKS2 volumes via systemd-cryptenroll."), + SD_VARLINK_SYMBOL_COMMENT("The credential types this interface knows about, used both as the enrollment mechanism and as the slot type reported by ListSlots."), + &vl_type_EnrollMechanism, + SD_VARLINK_SYMBOL_COMMENT("Enroll a credential into a LUKS2 volume. When enrolling a FIDO2 token that requires user presence, the call must be made with the 'more' flag, so that the server can report when a touch is required via a 'touch' state notification."), + &vl_method_Enroll, + SD_VARLINK_SYMBOL_COMMENT("Enumerate the keyslots currently enrolled in a LUKS2 volume, one per reply. Must be called with the 'more' flag."), + &vl_method_ListSlots, + SD_VARLINK_SYMBOL_COMMENT("The volume is managed by another subsystem (e.g. systemd-homed) and may not be enrolled into directly."), + &vl_error_VolumeUnderForeignManagement, + SD_VARLINK_SYMBOL_COMMENT("A password is required to unlock the volume, but none was provided."), + &vl_error_PasswordRequired, + SD_VARLINK_SYMBOL_COMMENT("The provided password did not unlock the volume."), + &vl_error_PasswordIncorrect, + SD_VARLINK_SYMBOL_COMMENT("No matching FIDO2 device was found."), + &vl_error_FidoDeviceNotFound, + SD_VARLINK_SYMBOL_COMMENT("The FIDO2 token was not interacted with in time."), + &vl_error_FidoActionTimeout); diff --git a/src/shared/varlink-io.systemd.CryptEnroll.h b/src/shared/varlink-io.systemd.CryptEnroll.h new file mode 100644 index 0000000000000..dcb626a47b3fd --- /dev/null +++ b/src/shared/varlink-io.systemd.CryptEnroll.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_CryptEnroll; 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.Hostname.c b/src/shared/varlink-io.systemd.Hostname.c index e06587039b510..ca7b2b534b2c9 100644 --- a/src/shared/varlink-io.systemd.Hostname.c +++ b/src/shared/varlink-io.systemd.Hostname.c @@ -6,45 +6,146 @@ static SD_VARLINK_DEFINE_METHOD( Describe, VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("The current system hostname."), SD_VARLINK_DEFINE_OUTPUT(Hostname, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The statically configured hostname, from /etc/hostname. Null if none is configured."), SD_VARLINK_DEFINE_OUTPUT(StaticHostname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The pretty (free-form, UTF-8) hostname. Null if none is configured."), SD_VARLINK_DEFINE_OUTPUT(PrettyHostname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The fallback hostname used when neither a static nor a transient hostname is set."), SD_VARLINK_DEFINE_OUTPUT(DefaultHostname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Indicates where the current hostname originates from, one of 'static', 'transient' or 'default'."), SD_VARLINK_DEFINE_OUTPUT(HostnameSource, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The name of an icon representing this system, following the XDG icon naming spec. Null if none is configured."), SD_VARLINK_DEFINE_OUTPUT(IconName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The chassis type of this system, e.g. 'desktop', 'laptop', 'server', 'vm', … Null if unknown."), SD_VARLINK_DEFINE_OUTPUT(Chassis, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("An unique identifier of the system chassis."), SD_VARLINK_DEFINE_OUTPUT(ChassisAssetTag, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The deployment environment of this system, e.g. 'production' or 'staging'. Null if none is configured."), SD_VARLINK_DEFINE_OUTPUT(Deployment, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("A human-readable description of the physical location of this system. Null if none is configured."), SD_VARLINK_DEFINE_OUTPUT(Location, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The operating system kernel name, as reported by uname(2), e.g. 'Linux'."), SD_VARLINK_DEFINE_OUTPUT(KernelName, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The kernel release string, as reported by uname(2)."), SD_VARLINK_DEFINE_OUTPUT(KernelRelease, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The kernel version string, as reported by uname(2)."), SD_VARLINK_DEFINE_OUTPUT(KernelVersion, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("'Pretty' name of the OS. This is the primary OS identifier that is suitable for presentation to the user. Typically includes a version too, but doesn't have to."), SD_VARLINK_DEFINE_OUTPUT(OperatingSystemPrettyName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("'Fancy' name of the OS; may contain non-ASCII Unicode chars, as well as basic ANSI sequences. This is similar to 'OperatingSystemPrettyName', but is preferably used on terminals that support ANSI sequences and full Unicode."), SD_VARLINK_DEFINE_OUTPUT(OperatingSystemFancyName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The CPE name of the OS, from the CPE_NAME= field of os-release."), SD_VARLINK_DEFINE_OUTPUT(OperatingSystemCPEName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The home URL of the OS, from the HOME_URL= field of os-release."), SD_VARLINK_DEFINE_OUTPUT(OperatingSystemHomeURL, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The end-of-support time of the OS, in µs since the UNIX epoch. Null if not known."), SD_VARLINK_DEFINE_OUTPUT(OperatingSystemSupportEnd, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The full contents of os-release, as an array of 'KEY=VALUE' strings."), SD_VARLINK_DEFINE_OUTPUT(OperatingSystemReleaseData, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("The OS image identifier, from the IMAGE_ID= field of os-release."), SD_VARLINK_DEFINE_OUTPUT(OperatingSystemImageID, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The OS image version, from the IMAGE_VERSION= field of os-release."), SD_VARLINK_DEFINE_OUTPUT(OperatingSystemImageVersion, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The full contents of /etc/machine-info, as an array of 'KEY=VALUE' strings."), SD_VARLINK_DEFINE_OUTPUT(MachineInformationData, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("The hardware vendor of this system. Null if not known."), SD_VARLINK_DEFINE_OUTPUT(HardwareVendor, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The hardware model of this system. Null if not known."), SD_VARLINK_DEFINE_OUTPUT(HardwareModel, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The hardware serial number of this system. Reading it requires privileges. Null if not known."), SD_VARLINK_DEFINE_OUTPUT(HardwareSerial, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The hardware SKU (stock keeping unit) of this system. Null if not known."), SD_VARLINK_DEFINE_OUTPUT(HardwareSKU, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The hardware version of this system. Null if not known."), SD_VARLINK_DEFINE_OUTPUT(HardwareVersion, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The firmware version of this system. Null if not known."), SD_VARLINK_DEFINE_OUTPUT(FirmwareVersion, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The firmware vendor of this system. Null if not known."), SD_VARLINK_DEFINE_OUTPUT(FirmwareVendor, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The firmware build date, in µs since the UNIX epoch. Null if not known."), SD_VARLINK_DEFINE_OUTPUT(FirmwareDate, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The 128-bit machine ID of this system, formatted as a hexadecimal string."), SD_VARLINK_DEFINE_OUTPUT(MachineID, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The 128-bit boot ID of the current boot, formatted as a hexadecimal string."), SD_VARLINK_DEFINE_OUTPUT(BootID, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The DMI product UUID of this system. Reading it requires privileges. Null if not known."), SD_VARLINK_DEFINE_OUTPUT(ProductUUID, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The AF_VSOCK context ID (CID) of this system. Null if not known."), SD_VARLINK_DEFINE_OUTPUT(VSockCID, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + SetHostname, + SD_VARLINK_FIELD_COMMENT("The transient (runtime) hostname to set. If null the transient hostname is reset, so that the static hostname is used again."), + SD_VARLINK_DEFINE_INPUT(newValue, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + SetStaticHostname, + SD_VARLINK_FIELD_COMMENT("The static hostname to set, stored in /etc/hostname. If null the static hostname is removed."), + SD_VARLINK_DEFINE_INPUT(newValue, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + SetPrettyHostname, + SD_VARLINK_FIELD_COMMENT("The pretty (free-form, UTF-8) hostname to set. If null the pretty hostname is removed."), + SD_VARLINK_DEFINE_INPUT(newValue, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + SetIconName, + SD_VARLINK_FIELD_COMMENT("The icon name to set. If null the icon name is removed."), + SD_VARLINK_DEFINE_INPUT(newValue, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + SetChassis, + SD_VARLINK_FIELD_COMMENT("The chassis type to set. If null the chassis type is removed."), + SD_VARLINK_DEFINE_INPUT(newValue, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + SetDeployment, + SD_VARLINK_FIELD_COMMENT("The deployment environment to set. If null the deployment is removed."), + SD_VARLINK_DEFINE_INPUT(newValue, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + SetLocation, + SD_VARLINK_FIELD_COMMENT("The physical location to set. If null the location is removed."), + SD_VARLINK_DEFINE_INPUT(newValue, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + SetTags, + SD_VARLINK_FIELD_COMMENT("If specified, the machine tag list is first reset to exactly these tags, before the 'add' and 'remove' fields are applied. If null, the current tag list is used as basis instead."), + SD_VARLINK_DEFINE_INPUT(set, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("Machine tags to add to the list."), + SD_VARLINK_DEFINE_INPUT(add, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("Machine tags to remove from the list."), + SD_VARLINK_DEFINE_INPUT(remove, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + VARLINK_DEFINE_POLKIT_INPUT); + SD_VARLINK_DEFINE_INTERFACE( io_systemd_Hostname, "io.systemd.Hostname", - &vl_method_Describe); + SD_VARLINK_INTERFACE_COMMENT("APIs for querying and changing the system hostname and related machine metadata."), + SD_VARLINK_SYMBOL_COMMENT("Returns hostname, kernel, OS, hardware, firmware and other machine metadata in one call."), + &vl_method_Describe, + SD_VARLINK_SYMBOL_COMMENT("Sets the transient (runtime) hostname."), + &vl_method_SetHostname, + SD_VARLINK_SYMBOL_COMMENT("Sets the static hostname, stored in /etc/hostname."), + &vl_method_SetStaticHostname, + SD_VARLINK_SYMBOL_COMMENT("Sets the pretty (free-form, UTF-8) hostname."), + &vl_method_SetPrettyHostname, + SD_VARLINK_SYMBOL_COMMENT("Sets the icon name representing this system."), + &vl_method_SetIconName, + SD_VARLINK_SYMBOL_COMMENT("Sets the chassis type of this system."), + &vl_method_SetChassis, + SD_VARLINK_SYMBOL_COMMENT("Sets the deployment environment of this system."), + &vl_method_SetDeployment, + SD_VARLINK_SYMBOL_COMMENT("Sets the physical location of this system."), + &vl_method_SetLocation, + SD_VARLINK_SYMBOL_COMMENT("Edits the machine tag list: optionally resets it to 'set', then adds 'add' and removes 'remove'."), + &vl_method_SetTags); diff --git a/src/shared/varlink-io.systemd.Import.c b/src/shared/varlink-io.systemd.Import.c index b4e2ab03e2d4e..13d239b9dacb2 100644 --- a/src/shared/varlink-io.systemd.Import.c +++ b/src/shared/varlink-io.systemd.Import.c @@ -87,7 +87,7 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_DEFINE_INPUT_BY_TYPE(verify, ImageVerify, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("If true, an existing image by the local name is deleted. Defaults to false."), SD_VARLINK_DEFINE_INPUT(force, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Whether to make the image read-only after downloading. Defaults ot false."), + SD_VARLINK_FIELD_COMMENT("Whether to make the image read-only after downloading. Defaults to false."), SD_VARLINK_DEFINE_INPUT(readOnly, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Whether to keep a pristine copy of the download separate from the locally installed image. Defaults to false."), SD_VARLINK_DEFINE_INPUT(keepDownload, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), 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.Job.c b/src/shared/varlink-io.systemd.Job.c new file mode 100644 index 0000000000000..b31e476511bb0 --- /dev/null +++ b/src/shared/varlink-io.systemd.Job.c @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Job.h" + +/* Keep in sync with job_type_table[] in src/core/job.c */ +SD_VARLINK_DEFINE_ENUM_TYPE( + JobType, + SD_VARLINK_DEFINE_ENUM_VALUE(start), + SD_VARLINK_DEFINE_ENUM_VALUE(verify_active), + SD_VARLINK_DEFINE_ENUM_VALUE(stop), + SD_VARLINK_DEFINE_ENUM_VALUE(reload), + SD_VARLINK_DEFINE_ENUM_VALUE(reload_or_start), + SD_VARLINK_DEFINE_ENUM_VALUE(restart), + SD_VARLINK_DEFINE_ENUM_VALUE(try_restart), + SD_VARLINK_DEFINE_ENUM_VALUE(try_reload), + SD_VARLINK_DEFINE_ENUM_VALUE(nop)); + +/* Keep in sync with job_state_table[] in src/core/job.c */ +SD_VARLINK_DEFINE_ENUM_TYPE( + JobState, + SD_VARLINK_DEFINE_ENUM_VALUE(waiting), + SD_VARLINK_DEFINE_ENUM_VALUE(running), + SD_VARLINK_DEFINE_ENUM_VALUE(finished)); + +/* Keep in sync with job_result_table[] in src/core/job.c */ +SD_VARLINK_DEFINE_ENUM_TYPE( + JobResult, + SD_VARLINK_DEFINE_ENUM_VALUE(done), + SD_VARLINK_DEFINE_ENUM_VALUE(canceled), + SD_VARLINK_DEFINE_ENUM_VALUE(timeout), + SD_VARLINK_DEFINE_ENUM_VALUE(failed), + SD_VARLINK_DEFINE_ENUM_VALUE(dependency), + SD_VARLINK_DEFINE_ENUM_VALUE(skipped), + SD_VARLINK_DEFINE_ENUM_VALUE(invalid), + SD_VARLINK_DEFINE_ENUM_VALUE(assert), + SD_VARLINK_DEFINE_ENUM_VALUE(unsupported), + SD_VARLINK_DEFINE_ENUM_VALUE(collected), + SD_VARLINK_DEFINE_ENUM_VALUE(once), + SD_VARLINK_DEFINE_ENUM_VALUE(frozen), + SD_VARLINK_DEFINE_ENUM_VALUE(concurrency)); + +/* Field names match the D-Bus Job properties (Id, Unit, JobType, State) */ +#define VARLINK_DEFINE_JOB_FIELDS(direction) \ + SD_VARLINK_FIELD_COMMENT("The numeric job ID"), \ + SD_VARLINK_DEFINE_##direction(Id, SD_VARLINK_INT, 0), \ + SD_VARLINK_FIELD_COMMENT("The unit name this job operates on"), \ + SD_VARLINK_DEFINE_##direction(Unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("The job type"), \ + SD_VARLINK_DEFINE_##direction##_BY_TYPE(JobType, JobType, 0), \ + SD_VARLINK_FIELD_COMMENT("Current job state. 'finished' indicates the job has completed; in that case Result is also set."), \ + SD_VARLINK_DEFINE_##direction##_BY_TYPE(State, JobState, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("Job result. Only set once the job has reached the 'finished' state."), \ + SD_VARLINK_DEFINE_##direction##_BY_TYPE(Result, JobResult, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("Activation details describing what triggered this job, as key-value pairs"), \ + SD_VARLINK_DEFINE_##direction(ActivationDetails, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_MAP) + +SD_VARLINK_DEFINE_STRUCT_TYPE( + Job, + VARLINK_DEFINE_JOB_FIELDS(FIELD)); + +static SD_VARLINK_DEFINE_ERROR( + NoSuchJob, + SD_VARLINK_DEFINE_FIELD(parameter, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + List, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("If non-null, filter by job ID"), + SD_VARLINK_DEFINE_INPUT(id, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If non-null, filter by unit name"), + SD_VARLINK_DEFINE_INPUT(unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_JOB_FIELDS(OUTPUT)); + +static SD_VARLINK_DEFINE_METHOD( + Cancel, + SD_VARLINK_FIELD_COMMENT("The job ID to cancel"), + SD_VARLINK_DEFINE_INPUT(id, SD_VARLINK_INT, 0)); + +static SD_VARLINK_DEFINE_METHOD( + ClearAll); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_Job, + "io.systemd.Job", + SD_VARLINK_INTERFACE_COMMENT("Job management interface for the systemd service manager."), + SD_VARLINK_SYMBOL_COMMENT("List queued jobs"), + &vl_method_List, + SD_VARLINK_SYMBOL_COMMENT("Cancel a specific job"), + &vl_method_Cancel, + SD_VARLINK_SYMBOL_COMMENT("Cancel all pending jobs"), + &vl_method_ClearAll, + SD_VARLINK_SYMBOL_COMMENT("Job type"), + &vl_type_JobType, + SD_VARLINK_SYMBOL_COMMENT("Job state"), + &vl_type_JobState, + SD_VARLINK_SYMBOL_COMMENT("Job result"), + &vl_type_JobResult, + SD_VARLINK_SYMBOL_COMMENT("A job object"), + &vl_type_Job, + SD_VARLINK_SYMBOL_COMMENT("The specified job does not exist"), + &vl_error_NoSuchJob); diff --git a/src/shared/varlink-io.systemd.Job.h b/src/shared/varlink-io.systemd.Job.h new file mode 100644 index 0000000000000..029d56aa2122a --- /dev/null +++ b/src/shared/varlink-io.systemd.Job.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_symbol vl_type_JobType; +extern const sd_varlink_symbol vl_type_JobState; +extern const sd_varlink_symbol vl_type_JobResult; +extern const sd_varlink_symbol vl_type_Job; +extern const sd_varlink_interface vl_interface_io_systemd_Job; diff --git a/src/shared/varlink-io.systemd.Login.c b/src/shared/varlink-io.systemd.Login.c index cf09d18286679..3b5a1a59c7640 100644 --- a/src/shared/varlink-io.systemd.Login.c +++ b/src/shared/varlink-io.systemd.Login.c @@ -3,7 +3,7 @@ #include "varlink-idl-common.h" #include "varlink-io.systemd.Login.h" -static SD_VARLINK_DEFINE_ENUM_TYPE( +SD_VARLINK_DEFINE_ENUM_TYPE( SessionType, SD_VARLINK_DEFINE_ENUM_VALUE(unspecified), SD_VARLINK_DEFINE_ENUM_VALUE(tty), @@ -12,7 +12,7 @@ static SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_DEFINE_ENUM_VALUE(mir), SD_VARLINK_DEFINE_ENUM_VALUE(web)); -static SD_VARLINK_DEFINE_ENUM_TYPE( +SD_VARLINK_DEFINE_ENUM_TYPE( SessionClass, SD_VARLINK_FIELD_COMMENT("Regular user sessions"), SD_VARLINK_DEFINE_ENUM_VALUE(user), @@ -37,37 +37,68 @@ static SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_FIELD_COMMENT("The special session of the service manager for the root user"), SD_VARLINK_DEFINE_ENUM_VALUE(manager_early)); +#define VARLINK_DEFINE_SESSION_FIELDS(direction) \ + SD_VARLINK_FIELD_COMMENT("Numeric UNIX UID of the user owning this session"), \ + SD_VARLINK_DEFINE_##direction(UID, SD_VARLINK_INT, 0), \ + SD_VARLINK_FIELD_COMMENT("PID of the session leader"), \ + SD_VARLINK_DEFINE_##direction##_BY_TYPE(PID, ProcessId, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("The type of the session"), \ + SD_VARLINK_DEFINE_##direction##_BY_TYPE(Type, SessionType, 0), \ + SD_VARLINK_FIELD_COMMENT("The class of the session"), \ + SD_VARLINK_DEFINE_##direction##_BY_TYPE(Class, SessionClass, 0), \ + SD_VARLINK_FIELD_COMMENT("PAM service name"), \ + SD_VARLINK_DEFINE_##direction(Service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("Desktop identifier"), \ + SD_VARLINK_DEFINE_##direction(Desktop, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("Seat this session is assigned to"), \ + SD_VARLINK_DEFINE_##direction(Seat, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("Virtual terminal number of the session, if applicable"), \ + SD_VARLINK_DEFINE_##direction(VTNr, SD_VARLINK_INT, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("TTY device of the session"), \ + SD_VARLINK_DEFINE_##direction(TTY, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("X11 display of the session"), \ + SD_VARLINK_DEFINE_##direction(Display, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("Whether this is a remote session"), \ + SD_VARLINK_DEFINE_##direction(Remote, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("Remote host, if this is a remote session"), \ + SD_VARLINK_DEFINE_##direction(RemoteHost, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("Remote user, if this is a remote session"), \ + SD_VARLINK_DEFINE_##direction(RemoteUser, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), \ + SD_VARLINK_FIELD_COMMENT("Device paths this session has special access to"), \ + SD_VARLINK_DEFINE_##direction(ExtraDeviceAccess, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY) + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SessionContext, + VARLINK_DEFINE_SESSION_FIELDS(FIELD)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SessionRuntime, + SD_VARLINK_FIELD_COMMENT("The session identifier"), + SD_VARLINK_DEFINE_FIELD(Id, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("systemd scope unit name"), + SD_VARLINK_DEFINE_FIELD(Scope, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Audit session ID"), + SD_VARLINK_DEFINE_FIELD(Audit, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The session timestamps"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timestamp, Timestamp, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Current state of the session"), + SD_VARLINK_DEFINE_FIELD(State, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Whether the session is active (i.e. in the foreground)"), + SD_VARLINK_DEFINE_FIELD(Active, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Whether the session is idle"), + SD_VARLINK_DEFINE_FIELD(IdleHint, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Timestamp when the session went idle, only present when IdleHint is true"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(IdleSinceHint, Timestamp, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Whether the session is currently locked"), + SD_VARLINK_DEFINE_FIELD(LockedHint, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Whether this session class supports idle tracking"), + SD_VARLINK_DEFINE_FIELD(CanIdle, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Whether this session class supports locking"), + SD_VARLINK_DEFINE_FIELD(CanLock, SD_VARLINK_BOOL, 0)); + static SD_VARLINK_DEFINE_METHOD( CreateSession, - SD_VARLINK_FIELD_COMMENT("Numeric UNIX UID of the user this session shall be owned by"), - SD_VARLINK_DEFINE_INPUT(UID, SD_VARLINK_INT, 0), - SD_VARLINK_FIELD_COMMENT("Process that shall become the leader of the session. If null defaults to the IPC client."), - SD_VARLINK_DEFINE_INPUT_BY_TYPE(PID, ProcessId, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("PAM service name of the program requesting the session"), - SD_VARLINK_DEFINE_INPUT(Service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("The type of the session"), - SD_VARLINK_DEFINE_INPUT_BY_TYPE(Type, SessionType, 0), - SD_VARLINK_FIELD_COMMENT("The class of the session"), - SD_VARLINK_DEFINE_INPUT_BY_TYPE(Class, SessionClass, 0), - SD_VARLINK_FIELD_COMMENT("An identifier for the chosen desktop"), - SD_VARLINK_DEFINE_INPUT(Desktop, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("The name of the seat to assign this session to"), - SD_VARLINK_DEFINE_INPUT(Seat, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("The virtual terminal number to assign this session to"), - SD_VARLINK_DEFINE_INPUT(VTNr, SD_VARLINK_INT, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("The TTY device to assign this session to, if applicable"), - SD_VARLINK_DEFINE_INPUT(TTY, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("The X11 display for this session"), - SD_VARLINK_DEFINE_INPUT(Display, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("If true this is a remote session"), - SD_VARLINK_DEFINE_INPUT(Remote, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("User name on the remote site, if known"), - SD_VARLINK_DEFINE_INPUT(RemoteUser, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("Host name of the remote host"), - SD_VARLINK_DEFINE_INPUT(RemoteHost, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("List of additional hardware devices that this session is granted access to." - "For every $ID in the list, this adds access for all devices tagged with \"xaccess-$ID\" in udev."), - SD_VARLINK_DEFINE_INPUT(ExtraDeviceAccess, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + VARLINK_DEFINE_SESSION_FIELDS(INPUT), SD_VARLINK_FIELD_COMMENT("The identifier string of the session of the user."), SD_VARLINK_DEFINE_OUTPUT(Id, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("The runtime path ($XDG_RUNTIME_DIR) of the user."), @@ -83,17 +114,156 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_FIELD_COMMENT("The assigned session class"), SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(Class, SessionClass, 0)); +static SD_VARLINK_DEFINE_METHOD_FULL( + ListSessions, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("If non-null, the identifier string of a session to look up. Supports the special names 'self' and 'auto' which resolve to the caller's session."), + SD_VARLINK_DEFINE_INPUT(Id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If non-null, return the session containing the process with this PID. If both Id and PID are specified they must reference the same session, otherwise NoSuchSession is returned."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(PID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Configuration of the session"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(context, SessionContext, 0), + SD_VARLINK_FIELD_COMMENT("Runtime information of the session"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(runtime, SessionRuntime, 0)); + static SD_VARLINK_DEFINE_METHOD( ReleaseSession, SD_VARLINK_FIELD_COMMENT("The identifier string of the session to release. If unspecified or 'self', will return the callers session."), SD_VARLINK_DEFINE_INPUT(Id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_STRUCT_TYPE( + UserContext, + SD_VARLINK_FIELD_COMMENT("Numeric UNIX UID"), + SD_VARLINK_DEFINE_FIELD(UID, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Numeric UNIX GID"), + SD_VARLINK_DEFINE_FIELD(GID, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("User name"), + SD_VARLINK_DEFINE_FIELD(Name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Whether lingering is enabled for this user"), + SD_VARLINK_DEFINE_FIELD(Linger, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Service manager unit name"), + SD_VARLINK_DEFINE_FIELD(Service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("User slice unit name"), + SD_VARLINK_DEFINE_FIELD(Slice, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Path to the user's runtime directory"), + SD_VARLINK_DEFINE_FIELD(RuntimePath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + UserRuntime, + SD_VARLINK_FIELD_COMMENT("Display session identifier"), + SD_VARLINK_DEFINE_FIELD(Display, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Current state of the user"), + SD_VARLINK_DEFINE_FIELD(State, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Identifiers of sessions belonging to this user"), + SD_VARLINK_DEFINE_FIELD(Sessions, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("Timestamp when the user was 'started' for the first time"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timestamp, Timestamp, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Whether the user is idle"), + SD_VARLINK_DEFINE_FIELD(IdleHint, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Timestamp when the user went idle, only present when IdleHint is true"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(IdleSinceHint, Timestamp, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + ListUsers, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("If non-null, the UNIX UID of a user to look up."), + SD_VARLINK_DEFINE_INPUT(UID, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If non-null, return the user owning the cgroup containing the process with this PID. If both UID and PID are specified they must reference the same user, otherwise NoSuchUser is returned."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(PID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Configuration of the user"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(context, UserContext, 0), + SD_VARLINK_FIELD_COMMENT("Runtime information of the user"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(runtime, UserRuntime, 0)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SeatContext, + SD_VARLINK_FIELD_COMMENT("The seat identifier"), + SD_VARLINK_DEFINE_FIELD(Id, SD_VARLINK_STRING, 0)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SeatRuntime, + SD_VARLINK_FIELD_COMMENT("The currently active session on this seat, if any"), + SD_VARLINK_DEFINE_FIELD(ActiveSession, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Identifiers of sessions assigned to this seat"), + SD_VARLINK_DEFINE_FIELD(Sessions, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("Whether this seat supports text terminal sessions"), + SD_VARLINK_DEFINE_FIELD(CanTTY, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Whether this seat supports graphical sessions"), + SD_VARLINK_DEFINE_FIELD(CanGraphical, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Whether the seat is idle"), + SD_VARLINK_DEFINE_FIELD(IdleHint, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Timestamp when the seat went idle, only present when IdleHint is true"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(IdleSinceHint, Timestamp, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + ListSeats, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("If non-null, the identifier string of a seat to look up."), + SD_VARLINK_DEFINE_INPUT(Id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Configuration of the seat"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(context, SeatContext, 0), + SD_VARLINK_FIELD_COMMENT("Runtime information of the seat"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(runtime, SeatRuntime, 0)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + InhibitMode, + SD_VARLINK_FIELD_COMMENT("Hard inhibition; blocks the operation entirely"), + SD_VARLINK_DEFINE_ENUM_VALUE(block), + SD_VARLINK_FIELD_COMMENT("Weak hard inhibition; like block, but the user who set it can override it without polkit authorization"), + SD_VARLINK_DEFINE_ENUM_VALUE(block_weak), + SD_VARLINK_FIELD_COMMENT("Soft inhibition; delays the operation but ultimately permits it"), + SD_VARLINK_DEFINE_ENUM_VALUE(delay)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + InhibitWhat, + SD_VARLINK_DEFINE_ENUM_VALUE(shutdown), + SD_VARLINK_DEFINE_ENUM_VALUE(sleep), + SD_VARLINK_DEFINE_ENUM_VALUE(idle), + SD_VARLINK_DEFINE_ENUM_VALUE(handle_power_key), + SD_VARLINK_DEFINE_ENUM_VALUE(handle_suspend_key), + SD_VARLINK_DEFINE_ENUM_VALUE(handle_hibernate_key), + SD_VARLINK_DEFINE_ENUM_VALUE(handle_lid_switch), + SD_VARLINK_DEFINE_ENUM_VALUE(handle_reboot_key)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + InhibitorContext, + SD_VARLINK_FIELD_COMMENT("The inhibitor identifier"), + SD_VARLINK_DEFINE_FIELD(Id, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("What is being inhibited"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(What, InhibitWhat, SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("A human-readable descriptive string of who is taking the inhibition"), + SD_VARLINK_DEFINE_FIELD(Who, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("A human-readable descriptive string of why the inhibition is taken"), + SD_VARLINK_DEFINE_FIELD(Why, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The inhibition mode"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Mode, InhibitMode, 0), + SD_VARLINK_FIELD_COMMENT("The UID of the user taking the inhibition"), + SD_VARLINK_DEFINE_FIELD(UID, SD_VARLINK_INT, 0)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + InhibitorRuntime, + SD_VARLINK_FIELD_COMMENT("The PID of the process taking the inhibition"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The inhibitor timestamps"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Since, Timestamp, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + ListInhibitors, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("Configuration of the inhibitor"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(context, InhibitorContext, 0), + SD_VARLINK_FIELD_COMMENT("Runtime information of the inhibitor"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(runtime, InhibitorRuntime, 0)); + static SD_VARLINK_DEFINE_ERROR(NoSuchSession); +static SD_VARLINK_DEFINE_ERROR(NoSuchUser); static SD_VARLINK_DEFINE_ERROR(NoSuchSeat); +static SD_VARLINK_DEFINE_ERROR(NoSuchInhibitor); 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, @@ -101,18 +271,52 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_INTERFACE_COMMENT("APIs for managing login sessions."), SD_VARLINK_SYMBOL_COMMENT("Process identifier"), &vl_type_ProcessId, + SD_VARLINK_SYMBOL_COMMENT("Dual timestamp"), + &vl_type_Timestamp, SD_VARLINK_SYMBOL_COMMENT("Various types of sessions"), &vl_type_SessionType, SD_VARLINK_SYMBOL_COMMENT("Various classes of sessions"), &vl_type_SessionClass, + SD_VARLINK_SYMBOL_COMMENT("Configuration aspects of a session (suitable for reuse as creation input)"), + &vl_type_SessionContext, + SD_VARLINK_SYMBOL_COMMENT("Runtime state and dynamic information of a session"), + &vl_type_SessionRuntime, SD_VARLINK_SYMBOL_COMMENT("Allocates a new session."), &vl_method_CreateSession, - SD_VARLINK_SYMBOL_COMMENT("Releases an existing session. Currently, will be refuses unless originating from the session to release itself."), + SD_VARLINK_SYMBOL_COMMENT("Releases an existing session. Currently, will be refused unless originating from the session to release itself."), &vl_method_ReleaseSession, + SD_VARLINK_SYMBOL_COMMENT("Lists current sessions. If an Id or PID filter is provided, returns the single matching session; otherwise streams all current sessions (requires the 'more' flag)."), + &vl_method_ListSessions, + SD_VARLINK_SYMBOL_COMMENT("Configuration aspects of a user"), + &vl_type_UserContext, + SD_VARLINK_SYMBOL_COMMENT("Runtime state and dynamic information of a user"), + &vl_type_UserRuntime, + SD_VARLINK_SYMBOL_COMMENT("Lists current users. If a UID or PID filter is provided, returns the single matching user; otherwise streams all current users (requires the 'more' flag)."), + &vl_method_ListUsers, + SD_VARLINK_SYMBOL_COMMENT("Configuration aspects of a seat"), + &vl_type_SeatContext, + SD_VARLINK_SYMBOL_COMMENT("Runtime state and dynamic information of a seat"), + &vl_type_SeatRuntime, + SD_VARLINK_SYMBOL_COMMENT("Lists current seats. If an Id filter is provided, returns the single matching seat; otherwise streams all current seats (requires the 'more' flag)."), + &vl_method_ListSeats, + SD_VARLINK_SYMBOL_COMMENT("Inhibition mode"), + &vl_type_InhibitMode, + SD_VARLINK_SYMBOL_COMMENT("Operations that can be inhibited"), + &vl_type_InhibitWhat, + SD_VARLINK_SYMBOL_COMMENT("Configuration aspects of an inhibitor"), + &vl_type_InhibitorContext, + SD_VARLINK_SYMBOL_COMMENT("Runtime state and dynamic information of an inhibitor"), + &vl_type_InhibitorRuntime, + SD_VARLINK_SYMBOL_COMMENT("Lists all current inhibitors."), + &vl_method_ListInhibitors, SD_VARLINK_SYMBOL_COMMENT("No session by this name found"), &vl_error_NoSuchSession, SD_VARLINK_SYMBOL_COMMENT("No seat by this name found"), &vl_error_NoSuchSeat, + SD_VARLINK_SYMBOL_COMMENT("No user by this UID found"), + &vl_error_NoSuchUser, + SD_VARLINK_SYMBOL_COMMENT("No inhibitor found"), + &vl_error_NoSuchInhibitor, SD_VARLINK_SYMBOL_COMMENT("Process already member of a session"), &vl_error_AlreadySessionMember, SD_VARLINK_SYMBOL_COMMENT("The specified virtual terminal (VT) is already taken by another session"), @@ -120,4 +324,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.Login.h b/src/shared/varlink-io.systemd.Login.h index 5453cdfb25f88..2d751eebdf9ed 100644 --- a/src/shared/varlink-io.systemd.Login.h +++ b/src/shared/varlink-io.systemd.Login.h @@ -3,4 +3,9 @@ #include "sd-varlink-idl.h" +extern const sd_varlink_symbol vl_type_SessionType; +extern const sd_varlink_symbol vl_type_SessionClass; +extern const sd_varlink_symbol vl_type_InhibitMode; +extern const sd_varlink_symbol vl_type_InhibitWhat; + extern const sd_varlink_interface vl_interface_io_systemd_Login; 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..8aad8babb98f7 --- /dev/null +++ b/src/shared/varlink-io.systemd.MachineInstance.c @@ -0,0 +1,93 @@ +/* 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_METHOD( + AddStorage, + SD_VARLINK_FIELD_COMMENT("Index of the attached file descriptor for the storage volume"), + SD_VARLINK_DEFINE_INPUT(fileDescriptorIndex, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Caller-supplied identifier for this binding (any non-empty string; machinectl uses ':' from the StorageProvider, but the form is not required)"), + SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Backend-specific configuration"), + SD_VARLINK_DEFINE_INPUT(config, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + RemoveStorage, + SD_VARLINK_FIELD_COMMENT("Identifier of the binding to detach (as supplied to AddStorage)"), + SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0)); + +static SD_VARLINK_DEFINE_METHOD( + ReplaceStorage, + SD_VARLINK_FIELD_COMMENT("Index of the attached file descriptor for the new backing storage"), + SD_VARLINK_DEFINE_INPUT(fileDescriptorIndex, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Identifier of the existing binding whose backing is being replaced (as supplied to AddStorage)"), + SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0)); + +static SD_VARLINK_DEFINE_ERROR(NotConnected); +static SD_VARLINK_DEFINE_ERROR(NotSupported); +static SD_VARLINK_DEFINE_ERROR(NoSuchStorage); +static SD_VARLINK_DEFINE_ERROR(StorageExists); +static SD_VARLINK_DEFINE_ERROR(StorageImmutable); +static SD_VARLINK_DEFINE_ERROR(BadConfig); +static SD_VARLINK_DEFINE_ERROR(ConfigNotSupported); + +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("Attach a storage volume (passed via file descriptor) to the running machine"), + &vl_method_AddStorage, + SD_VARLINK_SYMBOL_COMMENT("Detach a previously-attached storage volume from the running machine"), + &vl_method_RemoveStorage, + SD_VARLINK_SYMBOL_COMMENT("Replace the backing of a previously-attached storage volume in place"), + &vl_method_ReplaceStorage, + 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, + SD_VARLINK_SYMBOL_COMMENT("The named storage binding does not exist"), + &vl_error_NoSuchStorage, + SD_VARLINK_SYMBOL_COMMENT("A storage binding with this name already exists"), + &vl_error_StorageExists, + SD_VARLINK_SYMBOL_COMMENT("The storage binding cannot be detached at runtime (e.g. attached at boot)"), + &vl_error_StorageImmutable, + SD_VARLINK_SYMBOL_COMMENT("The supplied 'config' value is not valid for this backend"), + &vl_error_BadConfig, + SD_VARLINK_SYMBOL_COMMENT("The supplied 'config' value is recognized but not supported by this backend"), + &vl_error_ConfigNotSupported); 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..9331a2b1aee62 100644 --- a/src/shared/varlink-io.systemd.Manager.c +++ b/src/shared/varlink-io.systemd.Manager.c @@ -3,6 +3,18 @@ #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)); + static SD_VARLINK_DEFINE_STRUCT_TYPE( LogLevelStruct, SD_VARLINK_FIELD_COMMENT("'console' target log level"), @@ -25,13 +37,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 +75,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 +99,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, @@ -166,7 +188,11 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("Exit code of the manager"), SD_VARLINK_DEFINE_FIELD(ExitCode, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("Encodes how many soft-reboots were successfully completed"), - SD_VARLINK_DEFINE_FIELD(SoftRebootsCount, SD_VARLINK_INT, 0)); + SD_VARLINK_DEFINE_FIELD(SoftRebootsCount, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Encodes how many consecutive kexec reboots were successfully completed since the last full boot. Zero if the Live Update Orchestrator is not available."), + SD_VARLINK_DEFINE_FIELD(KExecsCount, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Number of successfully completed configuration reloads"), + SD_VARLINK_DEFINE_FIELD(ReloadCount, SD_VARLINK_INT, 0)); static SD_VARLINK_DEFINE_METHOD( Describe, @@ -193,6 +219,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 +240,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 +257,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..620d80e3a6ff6 100644 --- a/src/shared/varlink-io.systemd.Manager.h +++ b/src/shared/varlink-io.systemd.Manager.h @@ -4,3 +4,5 @@ #include "sd-varlink-idl.h" extern const sd_varlink_interface vl_interface_io_systemd_Manager; + +extern const sd_varlink_symbol vl_type_LogTarget; diff --git a/src/shared/varlink-io.systemd.Metrics.c b/src/shared/varlink-io.systemd.Metrics.c index f4623f1f159a7..61dea5db929f0 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( @@ -11,7 +9,9 @@ static SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_FIELD_COMMENT("A gauge metric family type which is a value that can go up and down"), SD_VARLINK_DEFINE_ENUM_VALUE(gauge), SD_VARLINK_FIELD_COMMENT("A string metric family type"), - SD_VARLINK_DEFINE_ENUM_VALUE(string)); + SD_VARLINK_DEFINE_ENUM_VALUE(string), + SD_VARLINK_FIELD_COMMENT("An object metric family type whose value is an arbitrary JSON object"), + SD_VARLINK_DEFINE_ENUM_VALUE(object)); static SD_VARLINK_DEFINE_ERROR(NoSuchMetric); 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..fcfdcd3d650a8 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( @@ -31,6 +29,15 @@ static SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_FIELD_COMMENT("Always create a new partition table, potentially overwriting an existing table"), SD_VARLINK_DEFINE_ENUM_VALUE(force)); +static SD_VARLINK_DEFINE_ENUM_TYPE( + BlockDeviceAction, + SD_VARLINK_FIELD_COMMENT("The device is currently present and a candidate. Emitted both during the initial enumeration and for live uevents (any action other than 'remove' is propagated as 'add', including the synthesized transitions when a device becomes empty or read-only and a relevant ignore* input is in effect)."), + SD_VARLINK_DEFINE_ENUM_VALUE(add), + SD_VARLINK_FIELD_COMMENT("The device is no longer a candidate, either because the kernel reported a remove event, or because a dynamic filter (ignoreEmpty/ignoreReadOnly) flipped it out of the candidate set. Clients should drop the device identified by 'node'. It is permitted (and harmless) to receive a 'remove' for a device the client never saw an 'add' for; clients should ignore these silently."), + SD_VARLINK_DEFINE_ENUM_VALUE(remove), + SD_VARLINK_FIELD_COMMENT("Sentinel sent exactly once in subscribe mode, after the initial enumeration is complete. Everything that follows reflects live uevents. Carries no other fields."), + SD_VARLINK_DEFINE_ENUM_VALUE(ready)); + static SD_VARLINK_DEFINE_METHOD_FULL( Run, SD_VARLINK_SUPPORTS_MORE, @@ -43,7 +50,7 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_FIELD_COMMENT("The seed value to derive partition and file system UUIDs from"), SD_VARLINK_DEFINE_INPUT(seed, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Path to directory containing definition files."), - SD_VARLINK_DEFINE_INPUT(definitions, SD_VARLINK_STRING, SD_VARLINK_ARRAY), + SD_VARLINK_DEFINE_INPUT(definitions, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("If true, automatically defer creation of all partitions whose label is \"empty\"."), SD_VARLINK_DEFINE_INPUT(deferPartitionsEmpty, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("If true, automatically defer creation of all partitions which are marked for factory reset."), @@ -66,8 +73,12 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_DEFINE_INPUT(ignoreRoot, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Control whether to include block devices with zero size in the list, i.e. typically block devices without any inserted medium. Defaults to false, i.e. empty block devices are included."), SD_VARLINK_DEFINE_INPUT(ignoreEmpty, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("The device node path of the block device."), - SD_VARLINK_DEFINE_OUTPUT(node, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("If true, keep the call open after the initial enumeration and stream live add/remove notifications as block-subsystem uevents arrive. The end of the initial enumeration is marked by exactly one notification with action='ready' and no other fields. Defaults to false."), + SD_VARLINK_DEFINE_INPUT(subscribe, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Discriminator field. Only set in subscribe mode. 'add' carries the full device record, 'remove' carries only node, 'ready' carries no other fields and is sent once after the initial enumeration."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(action, BlockDeviceAction, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The device node path of the block device. Identifies the device on 'remove' too."), + SD_VARLINK_DEFINE_OUTPUT(node, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("List of symlinks pointing to the device node, if any."), SD_VARLINK_DEFINE_OUTPUT(symlinks, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The Linux kernel disk sequence number identifying the medium."), @@ -108,6 +119,8 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_EmptyMode, SD_VARLINK_SYMBOL_COMMENT("Progress phase identifiers. Note that we might add more phases here, and thus identifiers. Frontends can choose to display the phase to the user in some human readable form, or not do that, but if they do it and they receive a notification for a so far unknown phase, they should just ignore it."), &vl_type_ProgressPhase, + SD_VARLINK_SYMBOL_COMMENT("Discriminator on streamed ListCandidateDevices replies in subscribe mode."), + &vl_type_BlockDeviceAction, SD_VARLINK_SYMBOL_COMMENT("Invoke the actual repartitioning operation, either in dry-run mode or for real. If invoked with 'more' enabled will report progress, otherwise will just report completion."), &vl_method_Run, diff --git a/src/shared/varlink-io.systemd.Report.Signer.c b/src/shared/varlink-io.systemd.Report.Signer.c new file mode 100644 index 0000000000000..4c40285b46b8e --- /dev/null +++ b/src/shared/varlink-io.systemd.Report.Signer.c @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Report.Signer.h" + +static SD_VARLINK_DEFINE_METHOD( + Sign, + SD_VARLINK_FIELD_COMMENT("The digest of the data to sign."), + SD_VARLINK_DEFINE_INPUT(digest, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The digest algorithm used for the digest field above. For now this is always SHA256, but this might be changed eventually."), + SD_VARLINK_DEFINE_INPUT(algorithm, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("An array of signature objects"), + SD_VARLINK_DEFINE_OUTPUT(data, SD_VARLINK_OBJECT, SD_VARLINK_ARRAY)); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_Report_Signer, + "io.systemd.Report.Signer", + SD_VARLINK_INTERFACE_COMMENT("Backend API for signing reports. This interface shall be implemented by services linked into /run/systemd/report.sign/."), + SD_VARLINK_SYMBOL_COMMENT("Sign a report, identified by a digest. This may return zero, one or more signatures, as appropriate. For example some backend might have multiple keys or algorithms available, that are appropriate, in which case it can generate multiple signatures."), + &vl_method_Sign); diff --git a/src/shared/varlink-io.systemd.Report.Signer.h b/src/shared/varlink-io.systemd.Report.Signer.h new file mode 100644 index 0000000000000..f9a918dbc53f1 --- /dev/null +++ b/src/shared/varlink-io.systemd.Report.Signer.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_Report_Signer; diff --git a/src/shared/varlink-io.systemd.Report.Uploader.c b/src/shared/varlink-io.systemd.Report.Uploader.c new file mode 100644 index 0000000000000..ace9108b2e0a3 --- /dev/null +++ b/src/shared/varlink-io.systemd.Report.Uploader.c @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Report.Uploader.h" + +static SD_VARLINK_DEFINE_METHOD( + Upload, + SD_VARLINK_FIELD_COMMENT("Report data as JSON variant. Either this field or reportData (below) have to be specified. This mode is used if signing is not used, and hence a precise binary formatting of the JSON data is not relevant."), + SD_VARLINK_DEFINE_INPUT(report, SD_VARLINK_OBJECT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Report data in Base64. Either this field or report (above) have to be specified. This mode is used if signing is enabled, as a precise binary formatting of the JSON data is important to authenticate the signature. This data contains a JSON-SEQ compliant stream of objects, the first being the report, the following ones signature objects."), + SD_VARLINK_DEFINE_INPUT(reportData, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_Report_Uploader, + "io.systemd.Report.Uploader", + SD_VARLINK_INTERFACE_COMMENT("Backend API for uploading reports. This interface shall be implemented by services linked into /run/systemd/report.upload/"), + SD_VARLINK_SYMBOL_COMMENT("Upload a report now."), + &vl_method_Upload); diff --git a/src/shared/varlink-io.systemd.Report.Uploader.h b/src/shared/varlink-io.systemd.Report.Uploader.h new file mode 100644 index 0000000000000..19e1c78314af5 --- /dev/null +++ b/src/shared/varlink-io.systemd.Report.Uploader.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_Report_Uploader; diff --git a/src/shared/varlink-io.systemd.Report.c b/src/shared/varlink-io.systemd.Report.c new file mode 100644 index 0000000000000..38f2f780bd7fc --- /dev/null +++ b/src/shared/varlink-io.systemd.Report.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Report.h" + +static SD_VARLINK_DEFINE_METHOD( + Generate, + SD_VARLINK_FIELD_COMMENT("Selects which metrics to include in the report, as an array of metric family names or prefixes thereof. If unset or empty all available metrics are included. This matches the [MATCH…] arguments of the systemd-report command line tool."), + SD_VARLINK_DEFINE_INPUT(matches, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The generated report as a JSON object. This mode does not sign the report, hence a precise binary formatting of the JSON data is not relevant."), + SD_VARLINK_DEFINE_OUTPUT(report, SD_VARLINK_OBJECT, 0)); + +static SD_VARLINK_DEFINE_METHOD( + GenerateSigned, + SD_VARLINK_FIELD_COMMENT("Selects which metrics to include in the report, as an array of metric family names or prefixes thereof. If unset or empty all available metrics are included. This matches the [MATCH…] arguments of the systemd-report command line tool."), + SD_VARLINK_DEFINE_INPUT(matches, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The generated, signed report in Base64. A precise binary formatting of the JSON data is important to authenticate the signature. This data contains a JSON-SEQ compliant stream of objects, the first being the report, the following ones signature objects."), + SD_VARLINK_DEFINE_OUTPUT(reportData, SD_VARLINK_STRING, 0)); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_Report, + "io.systemd.Report", + SD_VARLINK_INTERFACE_COMMENT("Frontend API for generating system reports. This interface is implemented by systemd-report, which aggregates the metrics exposed by the io.systemd.Metrics services linked into /run/systemd/report/."), + SD_VARLINK_SYMBOL_COMMENT("Generate a report and return it as a JSON object."), + &vl_method_Generate, + SD_VARLINK_SYMBOL_COMMENT("Generate a signed report and return it Base64 encoded."), + &vl_method_GenerateSigned); diff --git a/src/shared/varlink-io.systemd.Report.h b/src/shared/varlink-io.systemd.Report.h new file mode 100644 index 0000000000000..85856134285bd --- /dev/null +++ b/src/shared/varlink-io.systemd.Report.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_Report; diff --git a/src/shared/varlink-io.systemd.Resolve.c b/src/shared/varlink-io.systemd.Resolve.c index e8de8b39f8197..6c4ef094ca7bc 100644 --- a/src/shared/varlink-io.systemd.Resolve.c +++ b/src/shared/varlink-io.systemd.Resolve.c @@ -337,6 +337,7 @@ static SD_VARLINK_DEFINE_ERROR(StubLoop); static SD_VARLINK_DEFINE_ERROR( DNSError, SD_VARLINK_DEFINE_FIELD(rcode, SD_VARLINK_INT, 0), + SD_VARLINK_DEFINE_FIELD(queryString, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_FIELD(extendedDNSErrorCode, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_ERROR(CNAMELoop); 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.StorageProvider.c b/src/shared/varlink-io.systemd.StorageProvider.c new file mode 100644 index 0000000000000..cd2a4f3fda0bc --- /dev/null +++ b/src/shared/varlink-io.systemd.StorageProvider.c @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bus-polkit.h" +#include "varlink-io.systemd.StorageProvider.h" + +static SD_VARLINK_DEFINE_ENUM_TYPE( + VolumeType, + SD_VARLINK_FIELD_COMMENT("Block device storage volumes, block-addressable"), + SD_VARLINK_DEFINE_ENUM_VALUE(blk), + SD_VARLINK_FIELD_COMMENT("Regular file storage volumes, byte-addressable"), + SD_VARLINK_DEFINE_ENUM_VALUE(reg), + SD_VARLINK_FIELD_COMMENT("POSIX file system storage volumes, path/offset-addressable"), + SD_VARLINK_DEFINE_ENUM_VALUE(dir)); + +static SD_VARLINK_DEFINE_ENUM_TYPE( + CreateMode, + SD_VARLINK_FIELD_COMMENT("Open if exists already, create if missing"), + SD_VARLINK_DEFINE_ENUM_VALUE(any), + SD_VARLINK_FIELD_COMMENT("Create if missing, fail if exists already"), + SD_VARLINK_DEFINE_ENUM_VALUE(new), + SD_VARLINK_FIELD_COMMENT("Open if exists already, fail if missing"), + SD_VARLINK_DEFINE_ENUM_VALUE(open)); + +static SD_VARLINK_DEFINE_METHOD( + Acquire, + SD_VARLINK_FIELD_COMMENT("The name of the storage volume to acquire"), + SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Determines whether to open or create a storage volume"), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(createMode, CreateMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The template to use when creating a new storage volume"), + SD_VARLINK_DEFINE_INPUT(template, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Controls read/write access to the storage volume. If false and the storage volume cannot be opened in writable mode the call will fail. If null, storage volume will be acquired in writable mode if possible, read-only otherwise. If true, storage volume will be opened in read-only mode (and fail if that's not possible)."), + SD_VARLINK_DEFINE_INPUT(readOnly, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Dictates what kind of storage volume to request. Some storage volumes can be acquired either as regular file or as block device. In all other cases if this value doesn't match the volume type, the request will fail."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(requestAs, VolumeType, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The size of the storage volume, if one is created. Has no effect if no storage volume is created."), + SD_VARLINK_DEFINE_INPUT(createSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("Returns an index into the array of file descriptors associated with this reply. This may be used to get the file descriptor of the volume. The file descriptor must be properly opened, i.e. not an O_PATH file descriptor."), + SD_VARLINK_DEFINE_OUTPUT(fileDescriptorIndex, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("The storage volume type, i.e. ultimately the inode type of the returned file descriptor"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(type, VolumeType, 0), + SD_VARLINK_FIELD_COMMENT("Whether storage volume has been opened in read-only mode"), + SD_VARLINK_DEFINE_OUTPUT(readOnly, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("Base UID for the returned file descriptor (if directory). If not specified shall default to 0."), + SD_VARLINK_DEFINE_OUTPUT(baseUID, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Base GID for the returned file descriptor (if directory). If not specified shall default to 0."), + SD_VARLINK_DEFINE_OUTPUT(baseGID, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + ListVolumes, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("Specifies a shell glob to filter enumeration by"), + SD_VARLINK_DEFINE_INPUT(matchName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The storage volume's primary name"), + SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Additional names"), + SD_VARLINK_DEFINE_OUTPUT(aliases, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("The type of the storage volume"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(type, VolumeType, 0), + SD_VARLINK_FIELD_COMMENT("Whether the storage volume is read-only."), + SD_VARLINK_DEFINE_OUTPUT(readOnly, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Size in bytes, if known"), + SD_VARLINK_DEFINE_OUTPUT(sizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Used bytes, if known"), + SD_VARLINK_DEFINE_OUTPUT(usedBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + ListTemplates, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("Specifies a shell glob to filter enumeration by"), + SD_VARLINK_DEFINE_INPUT(matchName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The template's name"), + SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The type of the storage volumes defined by this template"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(type, VolumeType, 0)); + +static SD_VARLINK_DEFINE_ERROR(NoSuchVolume); +static SD_VARLINK_DEFINE_ERROR(VolumeExists); +static SD_VARLINK_DEFINE_ERROR(NoSuchTemplate); +static SD_VARLINK_DEFINE_ERROR(TypeNotSupported); +static SD_VARLINK_DEFINE_ERROR(WrongType); +static SD_VARLINK_DEFINE_ERROR(CreateNotSupported); +static SD_VARLINK_DEFINE_ERROR(CreateSizeRequired); +static SD_VARLINK_DEFINE_ERROR(ReadOnlyVolume); +static SD_VARLINK_DEFINE_ERROR(BadTemplate); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_StorageProvider, + "io.systemd.StorageProvider", + SD_VARLINK_INTERFACE_COMMENT("Storage Provider API, a generic interface for acquiring access to storage volumes"), + SD_VARLINK_SYMBOL_COMMENT("Encodes three classes of storage volumes. This follows the kernel's nomenclature for inode types, i.e. reg, dir, blk."), + &vl_type_VolumeType, + SD_VARLINK_SYMBOL_COMMENT("Determines whether to open existing or create a new storage volume."), + &vl_type_CreateMode, + SD_VARLINK_SYMBOL_COMMENT("Acquires a file descriptor for a storage volume."), + &vl_method_Acquire, + SD_VARLINK_SYMBOL_COMMENT("Lists available storage volumes."), + &vl_method_ListVolumes, + SD_VARLINK_SYMBOL_COMMENT("Lists available templates."), + &vl_method_ListTemplates, + SD_VARLINK_SYMBOL_COMMENT("No storage volume under the specified name exists."), + &vl_error_NoSuchVolume, + SD_VARLINK_SYMBOL_COMMENT("A storage volume under the specified name already exists."), + &vl_error_VolumeExists, + SD_VARLINK_SYMBOL_COMMENT("No template under the specified name exists."), + &vl_error_NoSuchTemplate, + SD_VARLINK_SYMBOL_COMMENT("The specified volume type is not supported by this backend or system."), + &vl_error_TypeNotSupported, + SD_VARLINK_SYMBOL_COMMENT("The volume's type does not match the requested volume type."), + &vl_error_WrongType, + SD_VARLINK_SYMBOL_COMMENT("This backend does not support storage volume creation of the requested type."), + &vl_error_CreateNotSupported, + SD_VARLINK_SYMBOL_COMMENT("This backend or selected volume type requires a storage volume size to be specified if the storage volume does not exist yet and needs to be created."), + &vl_error_CreateSizeRequired, + SD_VARLINK_SYMBOL_COMMENT("A storage volume was to be acquired in writable mode, but only read-only access is permitted."), + &vl_error_ReadOnlyVolume, + SD_VARLINK_SYMBOL_COMMENT("Template not suitable for this storage volume type."), + &vl_error_BadTemplate); diff --git a/src/shared/varlink-io.systemd.StorageProvider.h b/src/shared/varlink-io.systemd.StorageProvider.h new file mode 100644 index 0000000000000..707d05644f2cf --- /dev/null +++ b/src/shared/varlink-io.systemd.StorageProvider.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_StorageProvider; diff --git a/src/shared/varlink-io.systemd.SysUpdate.Notify.c b/src/shared/varlink-io.systemd.SysUpdate.Notify.c new file mode 100644 index 0000000000000..2fecc6005f18a --- /dev/null +++ b/src/shared/varlink-io.systemd.SysUpdate.Notify.c @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.SysUpdate.Notify.h" + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + Resource, + SD_VARLINK_DEFINE_FIELD(transfer, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + OnCompletedUpdate, + SD_VARLINK_DEFINE_INPUT(component, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT(version, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(resources, Resource, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_SysUpdate_Notify, + "io.systemd.SysUpdate.Notify", + &vl_type_Resource, + &vl_method_OnCompletedUpdate); diff --git a/src/shared/varlink-io.systemd.SysUpdate.Notify.h b/src/shared/varlink-io.systemd.SysUpdate.Notify.h new file mode 100644 index 0000000000000..72deccb380bc4 --- /dev/null +++ b/src/shared/varlink-io.systemd.SysUpdate.Notify.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_SysUpdate_Notify; diff --git a/src/shared/varlink-io.systemd.SysUpdate.c b/src/shared/varlink-io.systemd.SysUpdate.c new file mode 100644 index 0000000000000..e80142f9c510f --- /dev/null +++ b/src/shared/varlink-io.systemd.SysUpdate.c @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bus-polkit.h" +#include "varlink-io.systemd.SysUpdate.h" + +static SD_VARLINK_DEFINE_ENUM_TYPE( + TargetClass, + SD_VARLINK_FIELD_COMMENT("Container or machine managed by systemd-machined.service(8)."), + SD_VARLINK_DEFINE_ENUM_VALUE(machine), + SD_VARLINK_FIELD_COMMENT("Portable service."), + SD_VARLINK_DEFINE_ENUM_VALUE(portable), + SD_VARLINK_FIELD_COMMENT("System extension managed by systemd-sysext.service(8)."), + SD_VARLINK_DEFINE_ENUM_VALUE(sysext), + SD_VARLINK_FIELD_COMMENT("Configuration extension managed by systemd-confext.service(8)."), + SD_VARLINK_DEFINE_ENUM_VALUE(confext), + SD_VARLINK_FIELD_COMMENT("Host system."), + SD_VARLINK_DEFINE_ENUM_VALUE(host), + SD_VARLINK_FIELD_COMMENT("Component managed by systemd-sysupdate.service(8)."), + SD_VARLINK_DEFINE_ENUM_VALUE(component)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + TargetIdentifier, + SD_VARLINK_FIELD_COMMENT("Where the target was enumerated."), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(class, TargetClass, 0), + SD_VARLINK_FIELD_COMMENT("Name of the target, unique within a class."), + SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + CheckNew, + SD_VARLINK_FIELD_COMMENT("Target to check for updates for."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(target, TargetIdentifier, 0), + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("The version string for the new version which is available."), + SD_VARLINK_DEFINE_OUTPUT(available, SD_VARLINK_STRING, 0)); + +static SD_VARLINK_DEFINE_ERROR(NoUpdateNeeded); +static SD_VARLINK_DEFINE_ERROR(NoSuchTarget); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_SysUpdate, + "io.systemd.SysUpdate", + SD_VARLINK_INTERFACE_COMMENT("APIs to manage system updates"), + + /* Methods */ + SD_VARLINK_SYMBOL_COMMENT("Check if there’s a new version available"), + &vl_method_CheckNew, + + /* Types */ + SD_VARLINK_SYMBOL_COMMENT("Class of a Target."), + &vl_type_TargetClass, + SD_VARLINK_SYMBOL_COMMENT("Identifier for a component of the system (i.e. the host itself, a sysext, a confext, etc.) that can be updated by systemd-sysupdate(8)."), + &vl_type_TargetIdentifier, + + /* Errors */ + SD_VARLINK_SYMBOL_COMMENT("Error indicating that no update is currently available to update to."), + &vl_error_NoUpdateNeeded, + SD_VARLINK_SYMBOL_COMMENT("Error indicating the specified target doesn’t exist"), + &vl_error_NoSuchTarget); diff --git a/src/shared/varlink-io.systemd.SysUpdate.h b/src/shared/varlink-io.systemd.SysUpdate.h new file mode 100644 index 0000000000000..8399cdfbc3909 --- /dev/null +++ b/src/shared/varlink-io.systemd.SysUpdate.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_SysUpdate; diff --git a/src/shared/varlink-io.systemd.Unit.c b/src/shared/varlink-io.systemd.Unit.c index a008b506e9b1d..5a8e7c98331ed 100644 --- a/src/shared/varlink-io.systemd.Unit.c +++ b/src/shared/varlink-io.systemd.Unit.c @@ -1,6 +1,187 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "varlink-idl-common.h" +#include "varlink-io.systemd.Job.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_VALUE(on_success)); + +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_VALUE(preferred_many), + SD_VARLINK_DEFINE_ENUM_VALUE(weighted_interleave)); + +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( @@ -83,6 +264,12 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The device permissions"), SD_VARLINK_DEFINE_FIELD(permissions, SD_VARLINK_STRING, 0)); +static SD_VARLINK_DEFINE_ENUM_TYPE( + CPUSetPartition, + SD_VARLINK_DEFINE_ENUM_VALUE(member), + SD_VARLINK_DEFINE_ENUM_VALUE(root), + SD_VARLINK_DEFINE_ENUM_VALUE(isolated)); + static SD_VARLINK_DEFINE_STRUCT_TYPE( CGroupContext, @@ -103,6 +290,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_DEFINE_FIELD(AllowedCPUs, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#AllowedCPUs="), SD_VARLINK_DEFINE_FIELD(StartupAllowedCPUs, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.resource-control.html#CPUSetPartition="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CPUSetPartition, CPUSetPartition, SD_VARLINK_NULLABLE), /* Memory Accounting and Control * https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#Memory%20Accounting%20and%20Control */ @@ -198,7 +387,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 +396,36 @@ 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#OOMRules="), + SD_VARLINK_DEFINE_FIELD(OOMRules, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), 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"), @@ -235,10 +434,12 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( /* ExecContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( WorkingDirectory, - SD_VARLINK_FIELD_COMMENT("The path to the working directory"), - SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The path to the working directory. Mutually exclusive with 'home'"), + SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true, use the configured user's home directory as the working directory. Mutually exclusive with 'path'"), + SD_VARLINK_DEFINE_FIELD(home, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Whether the path to the working directory is allowed to not exist"), - SD_VARLINK_DEFINE_FIELD(missingOK, SD_VARLINK_BOOL, 0)); + SD_VARLINK_DEFINE_FIELD(missingOK, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_STRUCT_TYPE( PartitionMountOptions, @@ -414,7 +615,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootImageOptions="), SD_VARLINK_DEFINE_FIELD_BY_TYPE(RootImageOptions, PartitionMountOptions, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootEphemeral="), - SD_VARLINK_DEFINE_FIELD(RootEphemeral, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(RootEphemeral, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootHash="), SD_VARLINK_DEFINE_FIELD(RootHash, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootHash="), @@ -426,19 +627,19 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootVerity="), SD_VARLINK_DEFINE_FIELD(RootVerity, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootImagePolicy="), - SD_VARLINK_DEFINE_FIELD(RootImagePolicy, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(RootImagePolicy, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootImagePolicy="), - SD_VARLINK_DEFINE_FIELD(MountImagePolicy, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(MountImagePolicy, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RootImagePolicy="), - SD_VARLINK_DEFINE_FIELD(ExtensionImagePolicy, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(ExtensionImagePolicy, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#MountAPIVFS="), - SD_VARLINK_DEFINE_FIELD(MountAPIVFS, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(MountAPIVFS, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(BindLogSockets, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), 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, SD_VARLINK_NULLABLE), 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, SD_VARLINK_NULLABLE), 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="), @@ -457,7 +658,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#User="), SD_VARLINK_DEFINE_FIELD(Group, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#DynamicUser="), - SD_VARLINK_DEFINE_FIELD(DynamicUser, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(DynamicUser, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#SupplementaryGroups="), SD_VARLINK_DEFINE_FIELD(SupplementaryGroups, 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#SetLoginEnvironment="), @@ -475,7 +676,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( /* Security * https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#Security */ SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#NoNewPrivileges="), - SD_VARLINK_DEFINE_FIELD(NoNewPrivileges, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(NoNewPrivileges, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#SecureBits="), SD_VARLINK_DEFINE_FIELD(SecureBits, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), @@ -491,13 +692,13 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( /* Process Properties * https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#Process%20Properties */ SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#LimitCPU="), - SD_VARLINK_DEFINE_FIELD_BY_TYPE(Limits, ResourceLimitTable, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Limits, ResourceLimitTable, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#UMask="), SD_VARLINK_DEFINE_FIELD(UMask, SD_VARLINK_INT, SD_VARLINK_NULLABLE), 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, SD_VARLINK_NULLABLE), 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="), @@ -505,40 +706,40 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#Personality="), SD_VARLINK_DEFINE_FIELD(Personality, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#IgnoreSIGPIPE="), - SD_VARLINK_DEFINE_FIELD(IgnoreSIGPIPE, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(IgnoreSIGPIPE, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), /* Scheduling * https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#Scheduling */ 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_DEFINE_FIELD(Nice, SD_VARLINK_INT, SD_VARLINK_NULLABLE), 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, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(CPUSchedulingPriority, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#CPUSchedulingResetOnFork="), - SD_VARLINK_DEFINE_FIELD(CPUSchedulingResetOnFork, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(CPUSchedulingResetOnFork, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), 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, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(IOSchedulingPriority, SD_VARLINK_INT, SD_VARLINK_NULLABLE), 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, SD_VARLINK_NULLABLE), /* 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, SD_VARLINK_NULLABLE), 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, SD_VARLINK_NULLABLE), 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 +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#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, SD_VARLINK_NULLABLE), 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,35 +767,35 @@ 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, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(PrivateDevices, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateNetwork="), - SD_VARLINK_DEFINE_FIELD(PrivateNetwork, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(PrivateNetwork, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#NetworkNamespacePath="), SD_VARLINK_DEFINE_FIELD(NetworkNamespacePath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#PrivateIPC="), - SD_VARLINK_DEFINE_FIELD(PrivateIPC, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(PrivateIPC, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), 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, SD_VARLINK_NULLABLE), 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, SD_VARLINK_NULLABLE), 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, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(ProtectClock, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectKernelTunables="), - SD_VARLINK_DEFINE_FIELD(ProtectKernelTunables, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(ProtectKernelTunables, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#ProtectKernelModules="), - SD_VARLINK_DEFINE_FIELD(ProtectKernelModules, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(ProtectKernelModules, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(ProtectKernelLogs, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), 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, SD_VARLINK_NULLABLE), 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 +805,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, SD_VARLINK_NULLABLE), 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="), @@ -614,19 +815,19 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#BPFDelegateAttachments="), SD_VARLINK_DEFINE_FIELD(BPFDelegateAttachments, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#LockPersonality="), - SD_VARLINK_DEFINE_FIELD(LockPersonality, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(LockPersonality, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#MemoryDenyWriteExecute="), - SD_VARLINK_DEFINE_FIELD(MemoryDenyWriteExecute, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(MemoryDenyWriteExecute, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RestrictRealtime="), - SD_VARLINK_DEFINE_FIELD(RestrictRealtime, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(RestrictRealtime, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#RestrictSUIDSGID="), - SD_VARLINK_DEFINE_FIELD(RestrictSUIDSGID, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(RestrictSUIDSGID, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Whether to remove all System V and POSIX IPC objects owned by the user and group this unit runs under"), - SD_VARLINK_DEFINE_FIELD(RemoveIPC, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(RemoveIPC, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), 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 +854,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, SD_VARLINK_NULLABLE), 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, SD_VARLINK_NULLABLE), 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, SD_VARLINK_NULLABLE), 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"), @@ -683,7 +884,7 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#SyslogLevel="), SD_VARLINK_DEFINE_FIELD(SyslogLevel, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#SyslogLevelPrefix="), - SD_VARLINK_DEFINE_FIELD(SyslogLevelPrefix, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(SyslogLevelPrefix, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#TTYPath="), SD_VARLINK_DEFINE_FIELD(TTYPath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man"PROJECT_VERSION_STR"systemd.exec.html#TTYReset="), @@ -715,7 +916,640 @@ 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, SD_VARLINK_NULLABLE)); + +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)); + +/* PathContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.path.html */ +SD_VARLINK_DEFINE_ENUM_TYPE( + PathType, + SD_VARLINK_DEFINE_ENUM_VALUE(PathExists), + SD_VARLINK_DEFINE_ENUM_VALUE(PathExistsGlob), + SD_VARLINK_DEFINE_ENUM_VALUE(DirectoryNotEmpty), + SD_VARLINK_DEFINE_ENUM_VALUE(PathChanged), + SD_VARLINK_DEFINE_ENUM_VALUE(PathModified)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + PathSpec, + SD_VARLINK_FIELD_COMMENT("Path spec type"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(type, PathType, 0), + SD_VARLINK_FIELD_COMMENT("Path"), + SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, 0)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + PathContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.path.html#PathExists="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Paths, PathSpec, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.path.html#Unit="), + SD_VARLINK_DEFINE_FIELD(Unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.path.html#MakeDirectory="), + SD_VARLINK_DEFINE_FIELD(MakeDirectory, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.path.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.path.html#TriggerLimitIntervalSec="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(TriggerLimit, RateLimit, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + PathResult, + 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(unit_start_limit_hit), + SD_VARLINK_DEFINE_ENUM_VALUE(trigger_limit_hit)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + PathRuntime, + SD_VARLINK_FIELD_COMMENT("Result of path operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, PathResult, 0)); + +/* ScopeContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.scope.html */ +static SD_VARLINK_DEFINE_STRUCT_TYPE( + ScopeContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.scope.html#OOMPolicy="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OOMPolicy, OOMPolicy, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.scope.html#RuntimeMaxSec="), + SD_VARLINK_DEFINE_FIELD(RuntimeMaxUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.scope.html#RuntimeRandomizedExtraSec="), + SD_VARLINK_DEFINE_FIELD(RuntimeRandomizedExtraUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.scope.html#TimeoutStopSec="), + SD_VARLINK_DEFINE_FIELD(TimeoutStopUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ScopeResult, + SD_VARLINK_DEFINE_ENUM_VALUE(success), + SD_VARLINK_DEFINE_ENUM_VALUE(resources), + SD_VARLINK_DEFINE_ENUM_VALUE(timeout), + SD_VARLINK_DEFINE_ENUM_VALUE(oom_kill)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + ScopeRuntime, + SD_VARLINK_FIELD_COMMENT("Result of scope operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, ScopeResult, 0)); + +/* SocketContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html */ +SD_VARLINK_DEFINE_ENUM_TYPE( + SocketBindIPv6Only, + SD_VARLINK_DEFINE_ENUM_VALUE(default), + SD_VARLINK_DEFINE_ENUM_VALUE(both), + SD_VARLINK_DEFINE_ENUM_VALUE(ipv6_only)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + SocketTimestamping, + SD_VARLINK_DEFINE_ENUM_VALUE(off), + SD_VARLINK_DEFINE_ENUM_VALUE(us), + SD_VARLINK_DEFINE_ENUM_VALUE(ns)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + SocketDeferTrigger, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(yes), + SD_VARLINK_DEFINE_ENUM_VALUE(patient)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SocketListen, + SD_VARLINK_FIELD_COMMENT("Socket type"), + SD_VARLINK_DEFINE_FIELD(type, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Socket address"), + SD_VARLINK_DEFINE_FIELD(address, SD_VARLINK_STRING, 0)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SocketContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#ListenStream="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Listen, SocketListen, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SocketProtocol="), + SD_VARLINK_DEFINE_FIELD(SocketProtocol, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#BindIPv6Only="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(BindIPv6Only, SocketBindIPv6Only, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Backlog="), + SD_VARLINK_DEFINE_FIELD(Backlog, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#BindToDevice="), + SD_VARLINK_DEFINE_FIELD(BindToDevice, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SocketUser="), + SD_VARLINK_DEFINE_FIELD(SocketUser, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SocketUser="), + SD_VARLINK_DEFINE_FIELD(SocketGroup, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SocketMode="), + SD_VARLINK_DEFINE_FIELD(SocketMode, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.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.socket.html#Accept="), + SD_VARLINK_DEFINE_FIELD(Accept, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Writable="), + SD_VARLINK_DEFINE_FIELD(Writable, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#FlushPending="), + SD_VARLINK_DEFINE_FIELD(FlushPending, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#MaxConnections="), + SD_VARLINK_DEFINE_FIELD(MaxConnections, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#MaxConnectionsPerSource="), + SD_VARLINK_DEFINE_FIELD(MaxConnectionsPerSource, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#KeepAlive="), + SD_VARLINK_DEFINE_FIELD(KeepAlive, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#KeepAliveTimeSec="), + SD_VARLINK_DEFINE_FIELD(KeepAliveTimeUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#KeepAliveIntervalSec="), + SD_VARLINK_DEFINE_FIELD(KeepAliveIntervalUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#KeepAliveProbes="), + SD_VARLINK_DEFINE_FIELD(KeepAliveProbes, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#NoDelay="), + SD_VARLINK_DEFINE_FIELD(NoDelay, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Priority="), + SD_VARLINK_DEFINE_FIELD(Priority, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#DeferAcceptSec="), + SD_VARLINK_DEFINE_FIELD(DeferAcceptUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#ReceiveBuffer="), + SD_VARLINK_DEFINE_FIELD(ReceiveBuffer, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SendBuffer="), + SD_VARLINK_DEFINE_FIELD(SendBuffer, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#IPTOS="), + SD_VARLINK_DEFINE_FIELD(IPTOS, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#IPTTL="), + SD_VARLINK_DEFINE_FIELD(IPTTL, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Mark="), + SD_VARLINK_DEFINE_FIELD(Mark, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#ReusePort="), + SD_VARLINK_DEFINE_FIELD(ReusePort, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SmackLabel="), + SD_VARLINK_DEFINE_FIELD(SmackLabel, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SmackLabelIPIn="), + SD_VARLINK_DEFINE_FIELD(SmackLabelIPIn, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SmackLabelIPOut="), + SD_VARLINK_DEFINE_FIELD(SmackLabelIPOut, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#SELinuxContextFromNet="), + SD_VARLINK_DEFINE_FIELD(SELinuxContextFromNet, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#PipeSize="), + SD_VARLINK_DEFINE_FIELD(PipeSize, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#MessageQueueMaxMessages="), + SD_VARLINK_DEFINE_FIELD(MessageQueueMaxMessages, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#MessageQueueMessageSize="), + SD_VARLINK_DEFINE_FIELD(MessageQueueMessageSize, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#FreeBind="), + SD_VARLINK_DEFINE_FIELD(FreeBind, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Transparent="), + SD_VARLINK_DEFINE_FIELD(Transparent, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Broadcast="), + SD_VARLINK_DEFINE_FIELD(Broadcast, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#PassCredentials="), + SD_VARLINK_DEFINE_FIELD(PassCredentials, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#PassPIDFD="), + SD_VARLINK_DEFINE_FIELD(PassPIDFD, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#PassSecurity="), + SD_VARLINK_DEFINE_FIELD(PassSecurity, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#PassPacketInfo="), + SD_VARLINK_DEFINE_FIELD(PassPacketInfo, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#AcceptFileDescriptors="), + SD_VARLINK_DEFINE_FIELD(AcceptFileDescriptors, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Timestamping="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timestamping, SocketTimestamping, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#TCPCongestion="), + SD_VARLINK_DEFINE_FIELD(TCPCongestion, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#ExecStartPre="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStartPre, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#ExecStartPost="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStartPost, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#ExecStopPre="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStopPre, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#ExecStopPost="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStopPost, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#TimeoutSec="), + SD_VARLINK_DEFINE_FIELD(TimeoutUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#RemoveOnStop="), + SD_VARLINK_DEFINE_FIELD(RemoveOnStop, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#Symlinks="), + SD_VARLINK_DEFINE_FIELD(Symlinks, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#FileDescriptorName="), + SD_VARLINK_DEFINE_FIELD(FileDescriptorName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#TriggerLimitIntervalSec="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(TriggerLimit, RateLimit, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#PollLimitIntervalSec="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(PollLimit, RateLimit, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#DeferTrigger="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(DeferTrigger, SocketDeferTrigger, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#DeferTriggerMaxSec="), + SD_VARLINK_DEFINE_FIELD(DeferTriggerMaxUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.socket.html#PassFileDescriptorsToExec="), + SD_VARLINK_DEFINE_FIELD(PassFileDescriptorsToExec, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + SocketResult, + 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(trigger_limit_hit), + SD_VARLINK_DEFINE_ENUM_VALUE(service_start_limit_hit)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SocketRuntime, + SD_VARLINK_FIELD_COMMENT("PID of the current socket control process"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ControlPID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Result of socket operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, SocketResult, 0), + SD_VARLINK_FIELD_COMMENT("Result of cleaning operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CleanResult, SocketResult, 0), + SD_VARLINK_FIELD_COMMENT("Number of current connections"), + SD_VARLINK_DEFINE_FIELD(NConnections, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Number of accepted connections"), + SD_VARLINK_DEFINE_FIELD(NAccepted, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Number of refused connections"), + SD_VARLINK_DEFINE_FIELD(NRefused, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + 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)); + +/* SwapContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.swap.html */ +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SwapContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.swap.html#What="), + SD_VARLINK_DEFINE_FIELD(What, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.swap.html#Priority="), + SD_VARLINK_DEFINE_FIELD(Priority, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.swap.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.swap.html#TimeoutSec="), + SD_VARLINK_DEFINE_FIELD(TimeoutUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Activate command"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecActivate, ExecCommand, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Deactivate command"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecDeactivate, ExecCommand, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + SwapResult, + 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)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + SwapRuntime, + SD_VARLINK_FIELD_COMMENT("PID of the current swap control process"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ControlPID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Result of swap operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, SwapResult, 0), + SD_VARLINK_FIELD_COMMENT("Result of cleaning operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CleanResult, SwapResult, 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)); + +/* TimerContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html */ +SD_VARLINK_DEFINE_ENUM_TYPE( + TimerBase, + SD_VARLINK_DEFINE_ENUM_VALUE(OnActiveUSec), + SD_VARLINK_DEFINE_ENUM_VALUE(OnBootUSec), + SD_VARLINK_DEFINE_ENUM_VALUE(OnStartupUSec), + SD_VARLINK_DEFINE_ENUM_VALUE(OnUnitActiveUSec), + SD_VARLINK_DEFINE_ENUM_VALUE(OnUnitInactiveUSec), + SD_VARLINK_DEFINE_ENUM_VALUE(OnCalendar)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + TimerSpec, + SD_VARLINK_FIELD_COMMENT("Timer base type"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(base, TimerBase, 0), + SD_VARLINK_FIELD_COMMENT("Timer value in microseconds (for monotonic timers)"), + SD_VARLINK_DEFINE_FIELD(usec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Calendar specification string (for calendar timers)"), + SD_VARLINK_DEFINE_FIELD(calendar, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + TimerContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#OnActiveSec="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timers, TimerSpec, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#Unit="), + SD_VARLINK_DEFINE_FIELD(Unit, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#OnClockChange="), + SD_VARLINK_DEFINE_FIELD(OnClockChange, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#OnClockChange="), + SD_VARLINK_DEFINE_FIELD(OnTimezoneChange, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#AccuracySec="), + SD_VARLINK_DEFINE_FIELD(AccuracyUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#RandomizedDelaySec="), + SD_VARLINK_DEFINE_FIELD(RandomizedDelayUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#RandomizedOffsetSec="), + SD_VARLINK_DEFINE_FIELD(RandomizedOffsetUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#FixedRandomDelay="), + SD_VARLINK_DEFINE_FIELD(FixedRandomDelay, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#Persistent="), + SD_VARLINK_DEFINE_FIELD(Persistent, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#WakeSystem="), + SD_VARLINK_DEFINE_FIELD(WakeSystem, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#RemainAfterElapse="), + SD_VARLINK_DEFINE_FIELD(RemainAfterElapse, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.timer.html#DeferReactivation="), + SD_VARLINK_DEFINE_FIELD(DeferReactivation, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + TimerResult, + SD_VARLINK_DEFINE_ENUM_VALUE(success), + SD_VARLINK_DEFINE_ENUM_VALUE(resources), + SD_VARLINK_DEFINE_ENUM_VALUE(start_limit_hit)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + TimerRuntime, + SD_VARLINK_FIELD_COMMENT("Result of timer operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, TimerResult, 0), + SD_VARLINK_FIELD_COMMENT("Next elapse time in realtime clock"), + SD_VARLINK_DEFINE_FIELD(NextElapseUSecRealtime, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Next elapse time in monotonic clock"), + SD_VARLINK_DEFINE_FIELD(NextElapseUSecMonotonic, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Last time the timer triggered"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(LastTriggerUSec, Timestamp, SD_VARLINK_NULLABLE)); + +/* ServiceContext + * https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html */ +SD_VARLINK_DEFINE_ENUM_TYPE( + ServiceType, + SD_VARLINK_DEFINE_ENUM_VALUE(simple), + SD_VARLINK_DEFINE_ENUM_VALUE(exec), + SD_VARLINK_DEFINE_ENUM_VALUE(forking), + SD_VARLINK_DEFINE_ENUM_VALUE(oneshot), + SD_VARLINK_DEFINE_ENUM_VALUE(dbus), + SD_VARLINK_DEFINE_ENUM_VALUE(notify), + SD_VARLINK_FIELD_COMMENT("Like notify, but also implements a reload protocol via SIGHUP."), + SD_VARLINK_DEFINE_ENUM_VALUE(notify_reload), + SD_VARLINK_DEFINE_ENUM_VALUE(idle)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ServiceExitType, + SD_VARLINK_DEFINE_ENUM_VALUE(main), + SD_VARLINK_DEFINE_ENUM_VALUE(cgroup)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ServiceRestart, + SD_VARLINK_DEFINE_ENUM_VALUE(no), + SD_VARLINK_DEFINE_ENUM_VALUE(on_success), + SD_VARLINK_DEFINE_ENUM_VALUE(on_failure), + SD_VARLINK_DEFINE_ENUM_VALUE(on_abnormal), + SD_VARLINK_DEFINE_ENUM_VALUE(on_watchdog), + SD_VARLINK_DEFINE_ENUM_VALUE(on_abort), + SD_VARLINK_DEFINE_ENUM_VALUE(always)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ServiceRestartMode, + SD_VARLINK_DEFINE_ENUM_VALUE(normal), + SD_VARLINK_DEFINE_ENUM_VALUE(direct), + SD_VARLINK_DEFINE_ENUM_VALUE(debug)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ServiceTimeoutFailureMode, + SD_VARLINK_DEFINE_ENUM_VALUE(terminate), + SD_VARLINK_DEFINE_ENUM_VALUE(abort), + SD_VARLINK_DEFINE_ENUM_VALUE(kill)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + NotifyAccess, + SD_VARLINK_DEFINE_ENUM_VALUE(none), + SD_VARLINK_DEFINE_ENUM_VALUE(all), + SD_VARLINK_DEFINE_ENUM_VALUE(exec), + SD_VARLINK_DEFINE_ENUM_VALUE(main)); + +SD_VARLINK_DEFINE_ENUM_TYPE( + ServiceResult, + SD_VARLINK_DEFINE_ENUM_VALUE(success), + SD_VARLINK_DEFINE_ENUM_VALUE(resources), + SD_VARLINK_DEFINE_ENUM_VALUE(protocol), + 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(watchdog), + SD_VARLINK_DEFINE_ENUM_VALUE(start_limit_hit), + SD_VARLINK_DEFINE_ENUM_VALUE(oom_kill), + SD_VARLINK_DEFINE_ENUM_VALUE(exec_condition)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + ExitStatusSet, + SD_VARLINK_FIELD_COMMENT("Exit status codes"), + SD_VARLINK_DEFINE_FIELD(statuses, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Signal names"), + SD_VARLINK_DEFINE_FIELD(signals, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + OpenFile, + SD_VARLINK_FIELD_COMMENT("Path to the file to open"), + SD_VARLINK_DEFINE_FIELD(path, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Name for the file descriptor"), + SD_VARLINK_DEFINE_FIELD(fileDescriptorName, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Open file flags"), + SD_VARLINK_DEFINE_FIELD(flags, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + ServiceContext, + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#Type="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Type, ServiceType, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExitType="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExitType, ServiceExitType, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#Restart="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Restart, ServiceRestart, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RestartMode="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(RestartMode, ServiceRestartMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#PIDFile="), + SD_VARLINK_DEFINE_FIELD(PIDFile, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RestartSec="), + SD_VARLINK_DEFINE_FIELD(RestartUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RestartSteps="), + SD_VARLINK_DEFINE_FIELD(RestartSteps, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RestartMaxDelaySec="), + SD_VARLINK_DEFINE_FIELD(RestartMaxDelayUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RestartRandomizedDelaySec="), + SD_VARLINK_DEFINE_FIELD(RestartRandomizedDelayUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#TimeoutStartSec="), + SD_VARLINK_DEFINE_FIELD(TimeoutStartUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#TimeoutStopSec="), + SD_VARLINK_DEFINE_FIELD(TimeoutStopUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#TimeoutStartFailureMode="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(TimeoutStartFailureMode, ServiceTimeoutFailureMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#TimeoutStopFailureMode="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(TimeoutStopFailureMode, ServiceTimeoutFailureMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RuntimeMaxSec="), + SD_VARLINK_DEFINE_FIELD(RuntimeMaxUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RuntimeRandomizedExtraSec="), + SD_VARLINK_DEFINE_FIELD(RuntimeRandomizedExtraUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#WatchdogSec="), + SD_VARLINK_DEFINE_FIELD(WatchdogUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RemainAfterExit="), + SD_VARLINK_DEFINE_FIELD(RemainAfterExit, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RootDirectoryStartOnly="), + SD_VARLINK_DEFINE_FIELD(RootDirectoryStartOnly, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#GuessMainPID="), + SD_VARLINK_DEFINE_FIELD(GuessMainPID, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#SuccessExitStatus="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(SuccessExitStatus, ExitStatusSet, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RestartPreventExitStatus="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(RestartPreventExitStatus, ExitStatusSet, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RestartForceExitStatus="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(RestartForceExitStatus, ExitStatusSet, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#BusName="), + SD_VARLINK_DEFINE_FIELD(BusName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#NotifyAccess="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(NotifyAccess, NotifyAccess, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#FileDescriptorStoreMax="), + SD_VARLINK_DEFINE_FIELD(FileDescriptorStoreMax, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#FileDescriptorStorePreserve="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(FileDescriptorStorePreserve, ExecPreserveMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#USBFunctionDescriptors="), + SD_VARLINK_DEFINE_FIELD(USBFunctionDescriptors, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#USBFunctionStrings="), + SD_VARLINK_DEFINE_FIELD(USBFunctionStrings, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#OOMPolicy="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OOMPolicy, OOMPolicy, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#OpenFile="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OpenFile, OpenFile, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExtraFileDescriptorNames="), + SD_VARLINK_DEFINE_FIELD(ExtraFileDescriptorNames, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ReloadSignal="), + SD_VARLINK_DEFINE_FIELD(ReloadSignal, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#RefreshOnReload="), + SD_VARLINK_DEFINE_FIELD(RefreshOnReload, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecCondition="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecCondition, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecStart="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStart, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecStartPre="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStartPre, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecStartPost="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStartPost, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecReload="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecReload, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecReloadPost="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecReloadPost, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecStop="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStop, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.service.html#ExecStopPost="), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStopPost, ExecCommand, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + ServiceRuntime, + SD_VARLINK_FIELD_COMMENT("Main process PID"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(MainPID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Control process PID"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ControlPID, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Status text set by the service"), + SD_VARLINK_DEFINE_FIELD(StatusText, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Status errno set by the service"), + SD_VARLINK_DEFINE_FIELD(StatusErrno, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Status D-Bus error set by the service"), + SD_VARLINK_DEFINE_FIELD(StatusBusError, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Status Varlink error set by the service"), + SD_VARLINK_DEFINE_FIELD(StatusVarlinkError, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Result of service operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Result, ServiceResult, 0), + SD_VARLINK_FIELD_COMMENT("Result of reload operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ReloadResult, ServiceResult, 0), + SD_VARLINK_FIELD_COMMENT("Result of cleaning operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(CleanResult, ServiceResult, 0), + SD_VARLINK_FIELD_COMMENT("Result of live mount operation"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(LiveMountResult, ServiceResult, 0), + SD_VARLINK_FIELD_COMMENT("Number of file descriptors in the store"), + SD_VARLINK_DEFINE_FIELD(NFileDescriptorStore, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Number of restarts"), + SD_VARLINK_DEFINE_FIELD(NRestarts, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Next restart delay"), + SD_VARLINK_DEFINE_FIELD(RestartUSecNext, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Timeout for abort"), + SD_VARLINK_DEFINE_FIELD(TimeoutAbortUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Main process execution status"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecMain, ExecCommandStatus, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecCondition commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecConditionStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecStartPre commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStartPreStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecStart commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStartStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecStartPost commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStartPostStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecReload commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecReloadStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecReloadPost commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecReloadPostStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecStop commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStopStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Execution status of ExecStopPost commands"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(ExecStopPostStatus, ExecCommandStatus, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + 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)); /* UnitContext */ static SD_VARLINK_DEFINE_STRUCT_TYPE( @@ -729,10 +1563,15 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The parameter passed to the condition"), SD_VARLINK_DEFINE_FIELD(parameter, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); +/* UnitContext is used both as input to StartTransient (subset settable at creation time: ID, + * Description, Service, and the Exec subset {WorkingDirectory, Environment, SetCredential, + * SetCredentialEncrypted}) and as output from List/StartTransient (full unit configuration). Fields + * that are not settable at creation time are rejected with PropertyNotSupported when supplied as + * input. */ static SD_VARLINK_DEFINE_STRUCT_TYPE( UnitContext, SD_VARLINK_FIELD_COMMENT("The unit type"), - SD_VARLINK_DEFINE_FIELD(Type, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD(Type, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The unit ID"), SD_VARLINK_DEFINE_FIELD(ID, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("The aliases of this unit"), @@ -799,29 +1638,29 @@ 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, 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(OnFailureJobMode, SD_VARLINK_STRING, 0), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(OnFailureJobMode, JobMode, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(IgnoreOnIsolate, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#StopWhenUnneeded="), - SD_VARLINK_DEFINE_FIELD(StopWhenUnneeded, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(StopWhenUnneeded, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#RefuseManualStart="), - SD_VARLINK_DEFINE_FIELD(RefuseManualStart, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(RefuseManualStart, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#RefuseManualStart="), - SD_VARLINK_DEFINE_FIELD(RefuseManualStop, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(RefuseManualStop, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#AllowIsolate="), - SD_VARLINK_DEFINE_FIELD(AllowIsolate, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(AllowIsolate, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("https://www.freedesktop.org/software/systemd/man/"PROJECT_VERSION_STR"/systemd.unit.html#DefaultDependencies="), - SD_VARLINK_DEFINE_FIELD(DefaultDependencies, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(DefaultDependencies, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), 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_DEFINE_FIELD(SurviveFinalKillSignal, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), 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, 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(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 +1670,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="), @@ -864,17 +1703,35 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE( SD_VARLINK_FIELD_COMMENT("The unit file preset for this unit"), SD_VARLINK_DEFINE_FIELD(UnitFilePreset, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Whether this unit is transient"), - SD_VARLINK_DEFINE_FIELD(Transient, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(Transient, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Whether this unit is perpetual"), - SD_VARLINK_DEFINE_FIELD(Perpetual, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(Perpetual, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("When true, logs about this unit will be at debug level regardless of other log level settings"), - SD_VARLINK_DEFINE_FIELD(DebugInvocation, SD_VARLINK_BOOL, 0), + SD_VARLINK_DEFINE_FIELD(DebugInvocation, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), /* Other contexts */ 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 service context of the unit (only for .service units)"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Service, ServiceContext, 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), + SD_VARLINK_FIELD_COMMENT("The path context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Path, PathContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The scope context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The socket context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Socket, SocketContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The swap context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Swap, SwapContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The timer context of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timer, TimerContext, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_STRUCT_TYPE( ActivationDetails, @@ -948,6 +1805,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 +1901,23 @@ 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), + SD_VARLINK_FIELD_COMMENT("The path runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Path, PathRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The scope runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Scope, ScopeRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The service runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Service, ServiceRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The socket runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Socket, SocketRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The swap runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Swap, SwapRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The timer runtime of the unit"), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timer, TimerRuntime, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD_FULL( List, @@ -1035,6 +1947,28 @@ static SD_VARLINK_DEFINE_ERROR( PropertyNotSupported, SD_VARLINK_DEFINE_FIELD(property, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_ERROR(UnitExists); +static SD_VARLINK_DEFINE_ERROR(UnitTypeNotSupported); +static SD_VARLINK_DEFINE_ERROR(BadUnitSetting); + +static SD_VARLINK_DEFINE_METHOD_FULL( + StartTransient, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("Unit context. Must include ID (the unit name). Only the subset of fields settable at creation time is accepted; supplying any other field returns PropertyNotSupported."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(context, UnitContext, 0), + SD_VARLINK_FIELD_COMMENT("Job mode. Defaults to replace."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(mode, JobMode, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true and 'more' is set, stream job state change notifications. Defaults to false."), + SD_VARLINK_DEFINE_INPUT(notifyJobChanges, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true and 'more' is set, stream unit runtime notifications on state changes. Defaults to false."), + SD_VARLINK_DEFINE_INPUT(notifyUnitChanges, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Unit context. Set in the final reply."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(context, UnitContext, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Unit runtime state. Set in the final reply and in intermediate streaming notifications when notifyUnitChanges is true."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(runtime, UnitRuntime, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The job that was enqueued. Always set in the final streaming reply; also included in intermediate streaming notifications when notifyJobChanges is true."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(job, Job, SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_METHOD( SetProperties, SD_VARLINK_FIELD_COMMENT("The name of the unit to operate on."), @@ -1051,6 +1985,8 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_List, SD_VARLINK_SYMBOL_COMMENT("Set unit properties"), &vl_method_SetProperties, + SD_VARLINK_SYMBOL_COMMENT("Create a transient unit and start it"), + &vl_method_StartTransient, &vl_type_RateLimit, SD_VARLINK_SYMBOL_COMMENT("An object to represent a unit's conditions"), &vl_type_Condition, @@ -1066,6 +2002,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,13 +2015,35 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_CGroupRestrictNetworkInterfaces, &vl_type_CGroupNFTSet, &vl_type_CGroupBPFProgram, + &vl_type_CGroupController, &vl_type_CGroupDeviceAllow, + &vl_type_CPUSetPartition, SD_VARLINK_SYMBOL_COMMENT("CGroup context of a unit"), &vl_type_CGroupContext, SD_VARLINK_SYMBOL_COMMENT("CGroup runtime of a unit"), &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 +2070,71 @@ 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_ExecCommandStatus, + &vl_type_MountContext, + &vl_type_MountResult, + &vl_type_MountRuntime, + &vl_type_PathType, + &vl_type_PathSpec, + &vl_type_PathContext, + &vl_type_PathResult, + &vl_type_PathRuntime, + &vl_type_OOMPolicy, + &vl_type_ScopeContext, + &vl_type_ScopeResult, + &vl_type_ScopeRuntime, + &vl_type_SocketBindIPv6Only, + &vl_type_SocketTimestamping, + &vl_type_SocketDeferTrigger, + &vl_type_SocketListen, + &vl_type_SocketContext, + &vl_type_SocketResult, + &vl_type_SocketRuntime, + &vl_type_SwapContext, + &vl_type_SwapResult, + &vl_type_SwapRuntime, + &vl_type_TimerBase, + &vl_type_TimerSpec, + &vl_type_TimerContext, + &vl_type_TimerResult, + &vl_type_TimerRuntime, + + /* UnitContext enums */ + &vl_type_CollectMode, + &vl_type_EmergencyAction, + + /* Shared types (used by both StartTransient and Unit.List) */ + &vl_type_ExitStatusSet, + &vl_type_NotifyAccess, + &vl_type_OpenFile, + SD_VARLINK_SYMBOL_COMMENT("Service-specific context"), + &vl_type_ServiceContext, + &vl_type_ServiceExitType, + &vl_type_ServiceRestart, + &vl_type_ServiceRestartMode, + &vl_type_ServiceResult, + &vl_type_ServiceRuntime, + &vl_type_ServiceTimeoutFailureMode, + SD_VARLINK_SYMBOL_COMMENT("Service type"), + &vl_type_ServiceType, + SD_VARLINK_SYMBOL_COMMENT("Job mode"), + &vl_type_JobMode, + SD_VARLINK_SYMBOL_COMMENT("Job type (defined in io.systemd.Job)"), + &vl_type_JobType, + SD_VARLINK_SYMBOL_COMMENT("Job state (defined in io.systemd.Job)"), + &vl_type_JobState, + SD_VARLINK_SYMBOL_COMMENT("Job result (defined in io.systemd.Job)"), + &vl_type_JobResult, + SD_VARLINK_SYMBOL_COMMENT("A job object (defined in io.systemd.Job)"), + &vl_type_Job, + /* Errors */ SD_VARLINK_SYMBOL_COMMENT("No matching unit found"), &vl_error_NoSuchUnit, @@ -1115,9 +2142,15 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_error_UnitMasked, SD_VARLINK_SYMBOL_COMMENT("Unit is in a fatal error state"), &vl_error_UnitError, - SD_VARLINK_SYMBOL_COMMENT("Changing this property via SetProperties() is not supported"), + SD_VARLINK_SYMBOL_COMMENT("The named property cannot be set (via SetProperties() or at creation time via StartTransient())"), &vl_error_PropertyNotSupported, SD_VARLINK_SYMBOL_COMMENT("Job for the unit may only be enqueued by dependencies"), &vl_error_OnlyByDependency, SD_VARLINK_SYMBOL_COMMENT("A unit that requires D-Bus cannot be started as D-Bus is shutting down"), - &vl_error_DBusShuttingDown); + &vl_error_DBusShuttingDown, + SD_VARLINK_SYMBOL_COMMENT("A unit with this name already exists"), + &vl_error_UnitExists, + SD_VARLINK_SYMBOL_COMMENT("This unit type does not support transient units"), + &vl_error_UnitTypeNotSupported, + SD_VARLINK_SYMBOL_COMMENT("The unit file content contains invalid settings"), + &vl_error_BadUnitSetting); diff --git a/src/shared/varlink-io.systemd.Unit.h b/src/shared/varlink-io.systemd.Unit.h index 25433294534f9..000ed1961105c 100644 --- a/src/shared/varlink-io.systemd.Unit.h +++ b/src/shared/varlink-io.systemd.Unit.h @@ -4,3 +4,48 @@ #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_PathType; +extern const sd_varlink_symbol vl_type_PathResult; +extern const sd_varlink_symbol vl_type_ScopeResult; +extern const sd_varlink_symbol vl_type_SocketBindIPv6Only; +extern const sd_varlink_symbol vl_type_SocketTimestamping; +extern const sd_varlink_symbol vl_type_SocketDeferTrigger; +extern const sd_varlink_symbol vl_type_SocketResult; +extern const sd_varlink_symbol vl_type_SwapResult; +extern const sd_varlink_symbol vl_type_TimerBase; +extern const sd_varlink_symbol vl_type_TimerResult; +extern const sd_varlink_symbol vl_type_CollectMode; +extern const sd_varlink_symbol vl_type_JobMode; +extern const sd_varlink_symbol vl_type_NotifyAccess; +extern const sd_varlink_symbol vl_type_ServiceExitType; +extern const sd_varlink_symbol vl_type_ServiceRestart; +extern const sd_varlink_symbol vl_type_ServiceRestartMode; +extern const sd_varlink_symbol vl_type_ServiceResult; +extern const sd_varlink_symbol vl_type_ServiceTimeoutFailureMode; +extern const sd_varlink_symbol vl_type_ServiceType; 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..15e28b3e1b0c7 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,11 +10,12 @@ 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), - SD_VARLINK_DEFINE_FIELD(duration, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + SD_VARLINK_DEFINE_FIELD(duration, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_DEFINE_FIELD(rules, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( ReportManagedOOMCGroups, @@ -22,5 +24,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..9e56ce823df42 100644 --- a/src/shared/vconsole-util.c +++ b/src/shared/vconsole-util.c @@ -4,6 +4,7 @@ #include #include "alloc-util.h" +#include "efivars.h" #include "env-util.h" #include "extract-word.h" #include "fd-util.h" @@ -87,10 +88,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) { @@ -578,6 +579,109 @@ int find_language_fallback(const char *lang, char **ret) { } } +int find_vconsole_keymap_for_bcp47(const char *tag, char **ret) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *fallback = NULL; + const char *map; + int r; + + /* Look up a vconsole keymap by RFC 4646 / BCP 47 language tag (e.g. "de-DE") using the optional + * sixth column of /usr/share/systemd/kbd-model-map. That column lists comma-separated tags the + * row matches. An exact (case-insensitive) tag match returns immediately; if no exact match + * exists, the first row whose tag matches the input's primary subtag wins. Returns 1 on match, + * 0 otherwise. */ + + assert(tag); + assert(ret); + + if (isempty(tag)) { + *ret = NULL; + return 0; + } + + size_t primary_len = strcspn(tag, "-"); + if (primary_len == 0) { + *ret = NULL; + return 0; + } + + map = systemd_kbd_model_map(); + f = fopen(map, "re"); + if (!f) + return -errno; + + for (unsigned n = 0;;) { + _cleanup_strv_free_ char **a = NULL, **tags = NULL; + + r = read_next_mapping(map, 5, UINT_MAX, f, &n, &a); + if (r < 0) + return r; + if (r == 0) + break; + + /* The BCP 47 tag list is the optional 6th column. "-" / empty means "no tags". */ + if (strv_length(a) < 6 || isempty(a[5]) || streq(a[5], "-")) + continue; + + r = strv_split_full(&tags, a[5], ",", /* flags= */ 0); + if (r < 0) + return r; + + STRV_FOREACH(t, tags) { + if (strcaseeq(*t, tag)) { + log_debug("Found vconsole keymap '%s' for BCP 47 tag '%s' (exact match).", + a[0], tag); + + r = strdup_to(ret, a[0]); + if (r < 0) + return r; + + return 1; + } + if (!fallback && strlen(*t) == primary_len && !strchr(*t, '-') && strncaseeq(*t, tag, primary_len)) { + fallback = strdup(a[0]); + if (!fallback) + return -ENOMEM; + } + } + } + + if (!fallback) { + *ret = NULL; + return 0; + } + + log_debug("Found vconsole keymap '%s' for BCP 47 tag '%s' (primary subtag match).", fallback, tag); + *ret = TAKE_PTR(fallback); + return 1; +} + +int vconsole_keymap_from_efi(char **ret) { + int r; + + assert(ret); + + if (!is_efi_boot()) { + *ret = NULL; + return 0; + } + + _cleanup_free_ char *tag = NULL; + r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderKeyboardLayout"), &tag); + if (r == -ENOENT) { + *ret = NULL; + return 0; + } + if (r < 0) + return log_debug_errno(r, "Failed to read LoaderKeyboardLayout EFI variable: %m"); + + r = find_vconsole_keymap_for_bcp47(tag, ret); + if (r < 0) + return log_debug_errno(r, "Failed to look up vconsole keymap for firmware tag '%s': %m", tag); + + return r; +} + int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env) { int r; diff --git a/src/shared/vconsole-util.h b/src/shared/vconsole-util.h index 494bc6ea489e6..416eb86eb54b9 100644 --- a/src/shared/vconsole-util.h +++ b/src/shared/vconsole-util.h @@ -39,4 +39,7 @@ typedef int (*X11VerifyCallback)(const X11Context *xc); int vconsole_convert_to_x11(const VCContext *vc, X11VerifyCallback verify, X11Context *ret); int x11_convert_to_vconsole(const X11Context *xc, VCContext *ret); +int find_vconsole_keymap_for_bcp47(const char *tag, char **ret); +int vconsole_keymap_from_efi(char **ret); + int vconsole_serialize(const VCContext *vc, const X11Context *xc, char ***env); diff --git a/src/shared/verbs.c b/src/shared/verbs.c index b7b78619fdede..34e8a897d6a2f 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "env-util.h" +#include "format-table.h" #include "log.h" #include "string-util.h" #include "strv.h" +#include "terminal-util.h" #include "verbs.h" #include "virt.h" @@ -57,43 +57,53 @@ 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); + assert(verbs_end > verbs); + assert((uintptr_t) verbs % sizeof(void*) == 0); + assert(verbs[0].verb); + + for (const Verb *verb = verbs; verb < verbs_end; verb++) { + if (verb_is_metadata(verb)) + continue; - 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; + 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(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata) { + int r; assert(verbs); - assert(verbs[0].dispatch); + assert(verbs_end > verbs); + assert((uintptr_t) verbs % sizeof(void*) == 0); 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(); } + assert(!strv_isempty(verb_strv)); /* At least one verb should be defined… */ if (name) { /* Be more helpful to the user, and give a hint what the user might have wanted to type. */ @@ -114,18 +124,16 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { "Command verb required (one of %s).", joined); } - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Command verb '%s' required.", verbs[0].verb); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Command verb '%s' required.", verb_strv[0]); } 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 +142,119 @@ 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); +} + +#define VERB_SYNOPSIS_WIDTH_SANE 25 + +static const char* find_point_to_break(const char *s, size_t max_width) { + /* Locate the first space, preferably after max_width, or the last space otherwise. + * Return the part after the space. */ + + if (strlen(s) <= max_width) + return NULL; + + const char *p = strchr(s + max_width, ' ') ?: strrchr(s, ' '); + return p ? p + 1 : NULL; +} + +static int verb_add_help_one(Table *table, const Verb *verb) { + assert(table); + assert(verb); + + bool is_default = FLAGS_SET(verb->flags, VERB_DEFAULT); + int r; + + /* 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 = strjoin(" ", + is_default ? "[" : "", + verb->verb, + verb->argspec ? " " : "", + strempty(verb->argspec), + is_default ? "]" : ""); + if (!s) + return log_oom(); + + const char *ss = NULL; + if (columns() < VERB_SYNOPSIS_WIDTH_SANE * 4) { + /* If the synopsis is very wide, try to split it up. But do this only if the terminal + * is not very wide. If it _is_ wide, the broken up synopsis would look silly. */ + const char *p = find_point_to_break(s, VERB_SYNOPSIS_WIDTH_SANE), *p2 = NULL; + if (p) { + const char *s1 = strndupa_safe(s, p - s), *s2 = NULL; + + p2 = find_point_to_break(p, VERB_SYNOPSIS_WIDTH_SANE - 4); /* we indent by two spaces more */ + if (p2) + s2 = strndupa_safe(p, p2 - p); + + if (s2) + ss = strjoina(s1, "\n ", s2, "\n ", p2); + else + ss = strjoina(s1, "\n ", p); + } + } + + r = table_add_cell(table, NULL, TABLE_STRING, ss ?: s); + if (r < 0) + return table_log_add_error(r); + + _cleanup_strv_free_ char **t = strv_split(verb->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); + + return 0; +} + +int _verbs_get_help_table( + const Verb verbs[], + const Verb verbs_end[], + const char *group, + Table **ret) { + int r; + + assert(verbs); + assert(verbs_end > verbs); + assert((uintptr_t) verbs % sizeof(void*) == 0); + 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; + + r = verb_add_help_one(table, verb); + if (r < 0) + return 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..253181e8c47f2 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -8,18 +8,102 @@ 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 { +/* Note: see the comment on struct Option in options.h for why _alignptr_ is required here. */ +typedef struct _alignptr_ { 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; +assert_cc(sizeof(Verb) % sizeof(void*) == 0); + +#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) +#define VERB_DEFAULT_NOARG(d, v, h) \ + VERB_SCOPE(static, d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ VERB_DEFAULT, 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[]); -int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata); +const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]); + +int _dispatch_verb(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata); +#define dispatch_verb(args, userdata) \ + _dispatch_verb(args, __start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, 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(__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/vlan-util.h b/src/shared/vlan-util.h index b02001f238397..51efb3c7cccbd 100644 --- a/src/shared/vlan-util.h +++ b/src/shared/vlan-util.h @@ -7,7 +7,7 @@ #define VLANID_MAX 4094 #define VLANID_INVALID UINT16_MAX -/* Note that we permit VLAN Id 0 here, as that is apparently OK by the Linux kernel */ +/* Note that we permit VLAN ID 0 here, as that is apparently OK by the Linux kernel */ static inline bool vlanid_is_valid(uint16_t id) { return id <= VLANID_MAX; } 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..c661f92fafc95 100644 --- a/src/shared/vpick.c +++ b/src/shared/vpick.c @@ -176,8 +176,9 @@ static int errno_from_mode(uint32_t type_mask, mode_t found) { } static int pin_choice( - const char *toplevel_path, - int toplevel_fd, + const char *root_path, + int root_fd, + int dir_fd, const char *inode_path, int _inode_fd, /* we always take ownership of the fd, even on failure */ unsigned tries_left, @@ -190,15 +191,17 @@ static int pin_choice( _cleanup_free_ char *resolved_path = NULL; int r; - assert(toplevel_fd >= 0 || IN_SET(toplevel_fd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(root_fd)); + assert(wildcard_fd_is_valid(dir_fd)); assert(inode_path); assert(filter); assert(ret); if (inode_fd < 0 || FLAGS_SET(flags, PICK_RESOLVE)) { - r = chaseat(toplevel_fd, + r = chaseat(root_fd, + dir_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) @@ -211,11 +214,11 @@ static int pin_choice( struct stat st; if (fstat(inode_fd, &st) < 0) return log_debug_errno(errno, "Failed to stat discovered inode '%s%s': %m", - empty_to_root(toplevel_path), skip_leading_slash(inode_path)); + empty_to_root(root_path), skip_leading_slash(inode_path)); if (filter->type_mask != 0 && !BIT_SET(filter->type_mask, IFTODT(st.st_mode))) { log_debug("Inode '%s/%s' has wrong type, found '%s'.", - empty_to_root(toplevel_path), skip_leading_slash(inode_path), + empty_to_root(root_path), skip_leading_slash(inode_path), inode_type_to_string(st.st_mode)); *ret = PICK_RESULT_NULL; return 0; @@ -310,8 +313,9 @@ static bool architecture_matches(const PickFilter *filter, Architecture a) { } static int make_choice( - const char *toplevel_path, - int toplevel_fd, + const char *root_path, + int root_fd, + int dir_fd, const char *inode_path, int _inode_fd, /* we always take ownership of the fd, even on failure */ const PickFilter *filter, @@ -321,13 +325,14 @@ static int make_choice( _cleanup_close_ int inode_fd = TAKE_FD(_inode_fd); int r; - assert(toplevel_fd >= 0 || IN_SET(toplevel_fd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(root_fd)); + assert(wildcard_fd_is_valid(dir_fd)); assert(inode_path); assert(filter); assert(ret); if (inode_fd < 0) { - r = chaseat(toplevel_fd, inode_path, CHASE_AT_RESOLVE_IN_ROOT, NULL, &inode_fd); + r = chaseat(root_fd, dir_fd, inode_path, /* flags= */ 0, NULL, &inode_fd); if (r < 0) return r; } @@ -344,18 +349,19 @@ 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(root_fd, dir_fd, p, /* flags= */ 0, &object_path, &object_fd); if (r == -ENOENT) { *ret = PICK_RESULT_NULL; return 0; } if (r < 0) return log_debug_errno(r, "Failed to open '%s/%s': %m", - empty_to_root(toplevel_path), skip_leading_slash(p)); + empty_to_root(root_path), skip_leading_slash(p)); return pin_choice( - toplevel_path, - toplevel_fd, + root_path, + root_fd, + dir_fd, FLAGS_SET(flags, PICK_RESOLVE) ? object_path : p, TAKE_FD(object_fd), /* unconditionally pass ownership of the fd */ /* tries_left= */ UINT_MAX, @@ -370,16 +376,16 @@ static int make_choice( /* Underspecified, so we do our enumeration dance */ /* Convert O_PATH to a regular directory fd */ - _cleanup_close_ int dir_fd = fd_reopen(inode_fd, O_DIRECTORY|O_RDONLY|O_CLOEXEC); - if (dir_fd < 0) - return log_debug_errno(dir_fd, "Failed to reopen '%s/%s' as directory: %m", - empty_to_root(toplevel_path), skip_leading_slash(inode_path)); + _cleanup_close_ int enumerate_fd = fd_reopen(inode_fd, O_DIRECTORY|O_RDONLY|O_CLOEXEC); + if (enumerate_fd < 0) + return log_debug_errno(enumerate_fd, "Failed to reopen '%s/%s' as directory: %m", + empty_to_root(root_path), skip_leading_slash(inode_path)); _cleanup_free_ DirectoryEntries *de = NULL; - r = readdir_all(dir_fd, 0, &de); + r = readdir_all(enumerate_fd, 0, &de); if (r < 0) return log_debug_errno(r, "Failed to read directory '%s/%s': %m", - empty_to_root(toplevel_path), skip_leading_slash(inode_path)); + empty_to_root(root_path), skip_leading_slash(inode_path)); _cleanup_(pick_result_done) PickResult best = PICK_RESULT_NULL; @@ -456,8 +462,9 @@ static int make_choice( return log_oom_debug(); _cleanup_(pick_result_done) PickResult found = PICK_RESULT_NULL; - r = pin_choice(toplevel_path, - toplevel_fd, + r = pin_choice(root_path, + root_fd, + dir_fd, p, /* _inode_fd= */ -EBADF, found_tries_left, @@ -500,8 +507,9 @@ static int make_choice( } static int path_pick_one( - const char *toplevel_path, - int toplevel_fd, + const char *root_path, + int root_fd, + int dir_fd, const char *path, const PickFilter *filter, PickFlags flags, @@ -512,7 +520,8 @@ static int path_pick_one( uint32_t filter_type_mask; int r; - assert(toplevel_fd >= 0 || IN_SET(toplevel_fd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(root_fd)); + assert(wildcard_fd_is_valid(dir_fd)); assert(path); assert(filter); assert(ret); @@ -536,8 +545,9 @@ static int path_pick_one( /* Explicit basename specified, then shortcut things and do .v mode regardless of the path name. */ if (filter->basename) return make_choice( - toplevel_path, - toplevel_fd, + root_path, + root_fd, + dir_fd, path, /* inode_fd= */ -EBADF, filter, @@ -622,8 +632,9 @@ static int path_pick_one( } return make_choice( - toplevel_path, - toplevel_fd, + root_path, + root_fd, + dir_fd, enumeration_path, /* inode_fd= */ -EBADF, &(const PickFilter) { @@ -639,8 +650,9 @@ static int path_pick_one( bypass: /* Don't make any choice, but just use the passed path literally */ return pin_choice( - toplevel_path, - toplevel_fd, + root_path, + root_fd, + dir_fd, path, /* inode_fd= */ -EBADF, /* tries_left= */ UINT_MAX, @@ -650,8 +662,9 @@ static int path_pick_one( ret); } -int path_pick(const char *toplevel_path, - int toplevel_fd, +int path_pick(const char *root_path, + int root_fd, + int dir_fd, const char *path, const PickFilter filters[], size_t n_filters, @@ -661,7 +674,8 @@ int path_pick(const char *toplevel_path, _cleanup_(pick_result_done) PickResult best = PICK_RESULT_NULL; int r; - assert(toplevel_fd >= 0 || IN_SET(toplevel_fd, AT_FDCWD, XAT_FDROOT)); + assert(wildcard_fd_is_valid(root_fd)); + assert(wildcard_fd_is_valid(dir_fd)); assert(path); assert(filters || n_filters == 0); assert(ret); @@ -670,7 +684,7 @@ int path_pick(const char *toplevel_path, for (size_t i = 0; i < n_filters; i++) { _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; - r = path_pick_one(toplevel_path, toplevel_fd, path, &filters[i], flags, &result); + r = path_pick_one(root_path, root_fd, dir_fd, path, &filters[i], flags, &result); if (r < 0) return r; if (r == 0) @@ -716,8 +730,9 @@ int path_pick_update_warn( /* This updates the first argument if needed! */ - r = path_pick(/* toplevel_path= */ NULL, - /* toplevel_fd= */ AT_FDCWD, + r = path_pick(/* root_path= */ NULL, + /* root_fd= */ AT_FDCWD, + /* dir_fd= */ AT_FDCWD, *path, filters, n_filters, diff --git a/src/shared/vpick.h b/src/shared/vpick.h index 55596d24dfe0f..de67cbe35384d 100644 --- a/src/shared/vpick.h +++ b/src/shared/vpick.h @@ -45,8 +45,9 @@ void pick_result_done(PickResult *p); int pick_result_compare(const PickResult *a, const PickResult *b, PickFlags flags); -int path_pick(const char *toplevel_path, - int toplevel_fd, +int path_pick(const char *root_path, + int root_fd, + int dir_fd, const char *path, const PickFilter filters[], size_t n_filters, diff --git a/src/shared/vsock-util.c b/src/shared/vsock-util.c new file mode 100644 index 0000000000000..1fd05903fa621 --- /dev/null +++ b/src/shared/vsock-util.c @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-hwdb.h" + +#include "fd-util.h" +#include "fileio.h" +#include "log.h" +#include "parse-util.h" +#include "string-util.h" +#include "vsock-util.h" + +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); + + *ret = TAKE_PTR(modalias); + return 0; +} + +static int smbios_get_accepts_any(void) { + static int accepts_any = -1; + int r; + + if (accepts_any >= 0) + return accepts_any; + + _cleanup_free_ char *modalias = NULL; + r = smbios_get_modalias(&modalias); + if (r == -ENOENT) + return (accepts_any = false); + if (r < 0) + return log_debug_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_debug_errno(r, "Failed to open hwdb: %m"); + + const char *value; + r = sd_hwdb_get(hwdb, modalias, "VSOCK_ACCEPT_VMADDR_CID_ANY", &value); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Failed to get VSOCK_ACCEPT_VMADDR_CID_ANY, ignoring: %m"); + return (accepts_any = false); + } + log_debug("VSOCK_ACCEPT_VMADDR_CID_ANY: %s", value); + + r = parse_boolean(value); + if (r < 0) { + log_debug_errno(r, "Failed to parse VSOCK_ACCEPT_VMADDR_CID_ANY, ignoring: %m"); + return (accepts_any = false); + } + + return (accepts_any = r); +} + +int vsock_get_local_cid(unsigned *ret) { + _cleanup_close_ int vsock_fd = -EBADF; + + vsock_fd = open("/dev/vsock", O_RDONLY|O_CLOEXEC); + if (vsock_fd < 0) + return log_debug_errno(errno, "Failed to open %s: %m", "/dev/vsock"); + + unsigned tmp; + if (ioctl(vsock_fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, &tmp) < 0) + return log_debug_errno(errno, "Failed to query local AF_VSOCK CID: %m"); + log_debug("Local AF_VSOCK CID: %u", tmp); + + /* If ret == NULL, we just want to check if AF_VSOCK is available, so accept any + * address. Otherwise, filter out special addresses that cannot be used to identify + * _this_ machine from the outside. */ + if (ret && + (IN_SET(tmp, VMADDR_CID_LOCAL, VMADDR_CID_HOST) || + (tmp == VMADDR_CID_ANY && smbios_get_accepts_any() <= 0))) + return log_debug_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), + "IOCTL_VM_SOCKETS_GET_LOCAL_CID returned special value (%u), ignoring.", tmp); + + if (ret) + *ret = tmp; + return 0; +} diff --git a/src/shared/vsock-util.h b/src/shared/vsock-util.h new file mode 100644 index 0000000000000..77332964a0da5 --- /dev/null +++ b/src/shared/vsock-util.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include /* IWYU pragma: export */ + +int vsock_get_local_cid(unsigned *ret); 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..585a2f85a680d 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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..bb68c11592356 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -3,7 +3,6 @@ Copyright © 2010 ProFUSION embedded systems ***/ -#include #include #include #include @@ -11,6 +10,7 @@ #include #include "sd-daemon.h" +#include "sd-json.h" #include "sd-messages.h" #include "alloc-util.h" @@ -29,10 +29,11 @@ #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 "luo-util.h" +#include "options.h" #include "parse-util.h" #include "pidref.h" #include "printk-util.h" @@ -51,109 +52,96 @@ #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. + * + * Note: when new options are added here, also add them to the exclusion list in proc-cmdline.c! */ + + OptionParser opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + int r; - /* "-" 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) + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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", opts.arg); break; - case ARG_LOG_TARGET: - r = log_set_target_from_string(optarg); + OPTION_COMMON_LOG_TARGET: + r = log_set_target_from_string(opts.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", opts.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 (opts.arg) { + r = log_show_color_from_string(opts.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", opts.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 (opts.arg) { + r = log_show_location_from_string(opts.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", opts.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 (opts.arg) { + r = log_show_time_from_string(opts.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", opts.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(opts.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", opts.arg); break; - case ARG_TIMEOUT: - r = parse_sec(optarg, &arg_timeout); + OPTION_LONG("timeout", "TIME", + "Overall shutdown timeout"): + r = parse_sec(opts.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", opts.arg); break; - case '\001': + OPTION_POSITIONAL: if (!arg_verb) - arg_verb = optarg; + arg_verb = opts.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 +274,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,19 +331,60 @@ 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); + } else if (detect_virtualization() != VIRTUALIZATION_NONE) + /* Enforce the minimum uptime, but don't bother with it in containers/VMs, since – unlike on + * bare metal – the screen output isn't flushed out immediately when we reboot (as real PC + * firmwares do). But skip only if there wasn't an explicit configuration, to let users + * override this. */ + return; + + 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, NULL }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *luo_serialization = NULL; + _cleanup_close_ int luo_session_fd = -EBADF; + _cleanup_free_ int *luo_fds = NULL; _cleanup_free_ char *cgroup = NULL; + size_t n_luo_fds = 0; int cmd, r; + /* If PID 1 passed us an LUO serialization fd, parse it first so we know which fds to keep open. */ + (void) luo_parse_serialization(&luo_serialization, &luo_fds, &n_luo_fds); + /* Close random fds we might have get passed, just for paranoia, before we open any new fds, for * example for logging. After all this tool's purpose is about detaching any pinned resources, and * open file descriptors are the primary way to pin resources. Note that we don't really expect any - * fds to be passed here. */ - (void) close_all_fds(NULL, 0); + * fds to be passed here, except for LUO fds that need to survive until kexec. */ + (void) close_all_fds(luo_fds, n_luo_fds); /* The log target defaults to console, but the original systemd process will pass its log target in through a * command line argument, which will override this default. Also, ensure we'll never log to the journal or @@ -595,6 +640,8 @@ int main(int argc, char *argv[]) { notify_supervisor(); + sleep_until_minimum_uptime(); + if (streq(arg_verb, "exit")) { if (in_container) { log_info("Exiting container."); @@ -609,23 +656,13 @@ 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 */ + /* Preserve fd stores via the kernel Live Update Orchestrator before kexec. + * The session fd must stay open until the kexec syscall. */ + (void) luo_preserve_fd_stores(luo_serialization, &luo_session_fd); - (void) execl(KEXEC, KEXEC, "-e", NULL); - log_debug_errno(errno, "Failed to execute '" KEXEC "' binary, proceeding with reboot(RB_KEXEC): %m"); + log_info("Rebooting with kexec."); - /* 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/battery-capacity.c b/src/sleep/battery-capacity.c index f6a1ed25ac3f9..168fff91c067d 100644 --- a/src/sleep/battery-capacity.c +++ b/src/sleep/battery-capacity.c @@ -5,6 +5,7 @@ #include "alloc-util.h" #include "battery-capacity.h" #include "battery-util.h" +#include "device-private.h" #include "device-util.h" #include "extract-word.h" #include "fd-util.h" @@ -368,20 +369,13 @@ int battery_trip_point_alarm_exists(void) { return log_debug_errno(r, "Failed to initialize battery enumerator: %m"); FOREACH_DEVICE(e, dev) { - const char *alarm_attr; - int has_alarm; - has_battery = true; - r = sd_device_get_sysattr_value(dev, "alarm", &alarm_attr); + int has_alarm; + r = device_get_sysattr_int(dev, "alarm", &has_alarm); if (r < 0) return log_device_debug_errno(dev, r, "Failed to read battery alarm attribute: %m"); - r = safe_atoi(alarm_attr, &has_alarm); - if (r < 0) - return log_device_debug_errno(dev, r, - "Failed to parse battery alarm attribute '%s': %m", - alarm_attr); if (has_alarm <= 0) return false; } diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index 43aaede5b023a..24cd632111696 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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + } + + *ret_args = option_parser_get_args(&opts); + 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(args, sleep_config); +} + DEFINE_MAIN_FUNCTION(run); diff --git a/src/socket-activate/meson.build b/src/socket-activate/meson.build index a4d18b58a8dab..628dbe79ffccc 100644 --- a/src/socket-activate/meson.build +++ b/src/socket-activate/meson.build @@ -5,6 +5,5 @@ executables += [ 'name' : 'systemd-socket-activate', 'public' : true, 'sources' : files('socket-activate.c'), - 'dependencies' : threads, }, ] diff --git a/src/socket-activate/socket-activate.c b/src/socket-activate/socket-activate.c index e7102b62a1aee..be0179ccc9eeb 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,62 @@ 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 opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + 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_OR_RETURN(c, &opts) 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, opts.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 +384,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 +393,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, opts.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", opts.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(opts.arg, ":"); if (!names) return log_oom(); @@ -437,7 +422,7 @@ static int parse_argv(int argc, char *argv[]) { log_warning("File descriptor name \"%s\" is not valid.", esc); } - /* Empty optargs means one empty name */ + /* Empty argument means one empty name */ r = strv_extend_strv(&arg_fdnames, strv_isempty(names) ? STRV_MAKE("") : names, false); @@ -446,22 +431,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(&opts); + if (strv_isempty(*remaining_args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s: command to execute is missing.", program_invocation_short_name); @@ -488,11 +470,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/meson.build b/src/socket-proxy/meson.build index 52d63a8440c88..1106b445e9cd4 100644 --- a/src/socket-proxy/meson.build +++ b/src/socket-proxy/meson.build @@ -5,6 +5,5 @@ executables += [ 'name' : 'systemd-socket-proxyd', 'public' : true, 'sources' : files('socket-proxyd.c'), - 'dependencies' : threads, }, ] diff --git a/src/socket-proxy/socket-proxyd.c b/src/socket-proxy/socket-proxyd.c index 71172326da125..8be25f176e166 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,22 +14,42 @@ #include "errno-util.h" #include "event-util.h" #include "fd-util.h" +#include "format-table.h" +#include "in-addr-util.h" +#include "io-util.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-table.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; +typedef enum ProxyProtocol { + PROXY_NONE, + PROXY_V1, + _PROXY_PROTOCOL_MAX, + _PROXY_PROTOCOL_INVALID = -EINVAL, +} ProxyProtocol; + +static const char* const proxy_protocol_table[_PROXY_PROTOCOL_MAX] = { + [PROXY_V1] = "v1", +}; + +static ProxyProtocol arg_proxy_protocol = PROXY_NONE; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(proxy_protocol, ProxyProtocol); + typedef struct Context { sd_event *event; sd_resolve *resolve; @@ -45,13 +63,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 +78,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,165 +146,91 @@ 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"); +static int connection_forward_done(SocketForward *sf, int error, void *userdata) { + Connection *c = ASSERT_PTR(userdata); - assert(r > 0); - *sz = r; + if (error < 0) + log_error_errno(error, "Forwarding failed: %m"); - return 0; + connection_release(c); + return 0; /* ignore errors, continue serving */ } -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; +static int send_proxy_protocol_v1(Connection *c) { + _cleanup_free_ char *header = NULL; + int r, header_len; + union sockaddr_union local_sa, remote_sa; + socklen_t sa_len; 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) { - 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; + r = sd_is_socket(c->server_fd, AF_UNSPEC, SOCK_STREAM, /* listening= */ 0); + if (r < 0) { + log_warning_errno(r, "Failed to issue SO_TYPE, reporting fallback proxy data 'UNKNOWN': %m"); + goto unknown; + } + if (r == 0) { + log_warning("Only TCP is supported by the PROXY protocol, reporting fallback proxy data 'UNKNOWN'."); + goto unknown; + } - return 1; + sa_len = sizeof(local_sa); + if (getsockname(c->server_fd, &local_sa.sa, &sa_len) < 0) { + log_warning_errno(errno, "Failed to get local address (getsockname), reporting fallback proxy data 'UNKNOWN': %m"); + goto unknown; + } -quit: - connection_release(c); - return 0; /* ignore errors, continue serving */ -} + sa_len = sizeof(remote_sa); + if (getpeername(c->server_fd, &remote_sa.sa, &sa_len) < 0) { + log_warning_errno(errno, "Failed to get remote address (getpeername), reporting fallback proxy data 'UNKNOWN': %m"); + goto unknown; + } -static int connection_enable_event_sources(Connection *c) { - uint32_t a = 0, b = 0; - int r; + const char *proto = NULL; + switch (remote_sa.sa.sa_family) { - assert(c); + case AF_INET: + proto = "TCP4"; + break; - 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; + case AF_INET6: + proto = "TCP6"; + break; - 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; + default: + log_warning("Only TCP over IPv4 and IPv6 are supported, reporting fallback proxy data 'UNKNOWN'."); + goto unknown; + } - 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; + const union in_addr_union *remote_addr = sockaddr_in_addr(&remote_sa.sa); + const union in_addr_union *local_addr = sockaddr_in_addr(&local_sa.sa); + + unsigned remote_port, local_port; + assert_se(sockaddr_port(&remote_sa.sa, &remote_port) >= 0); + assert_se(sockaddr_port(&local_sa.sa, &local_port) >= 0); + + header_len = asprintf( + &header, "PROXY %s %s %s %u %u\r\n", + proto, + IN_ADDR_TO_STRING(remote_sa.sa.sa_family, remote_addr), + IN_ADDR_TO_STRING(local_sa.sa.sa_family, local_addr), + remote_port, + local_port); + if (header_len < 0) + return log_oom(); + r = loop_write_full(c->client_fd, header, header_len, 10 * USEC_PER_SEC); if (r < 0) - return log_error_errno(r, "Failed to set up server event source: %m"); + return log_error_errno(r, "Failed to write to backend host: %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; + /* success */ + return 0; +unknown: + /* ignore previous errors - server can decide to deny UNKNOWN connections */ + r = loop_write_full(c->client_fd, "PROXY UNKNOWN\r\n", SIZE_MAX, 10 * USEC_PER_SEC); if (r < 0) - return log_error_errno(r, "Failed to set up client event source: %m"); + return log_error_errno(r, "Failed to write to backend host: %m"); return 0; } @@ -302,17 +240,20 @@ static int connection_complete(Connection *c) { 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); - if (r < 0) - return r; + if (arg_proxy_protocol == PROXY_V1) { + r = send_proxy_protocol_v1(c); + if (r < 0) + return r; + } - r = connection_enable_event_sources(c); + 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; + return log_error_errno(r, "Failed to set up forwarding: %m"); return 0; } @@ -336,7 +277,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 +305,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 +413,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 +487,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 +498,48 @@ 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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.arg, &arg_connections_max); + if (r < 0) + return log_error_errno(r, "Failed to parse --connections-max= argument: %s", opts.arg); if (arg_connections_max < 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -621,28 +547,29 @@ 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(opts.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", opts.arg); break; - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION_LONG("proxy-protocol", "v1", + "Enable PROXY protocol: v1"): + arg_proxy_protocol = proxy_protocol_from_string(opts.arg); + if (arg_proxy_protocol < 0) + return log_error_errno(arg_proxy_protocol, "Failed to parse --proxy-protocol= argument: %s", opts.arg); + break; } - 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(&opts); + 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/20-systemd-ssh-proxy.conf.in b/src/ssh-generator/20-systemd-ssh-proxy.conf.in index e43560aa74b26..f88eca3275c61 100644 --- a/src/ssh-generator/20-systemd-ssh-proxy.conf.in +++ b/src/ssh-generator/20-systemd-ssh-proxy.conf.in @@ -10,6 +10,7 @@ Host .host machine/.host # Make sure machine/* can be used to connect to local machines registered in machined. # Host unix/* unix%* vsock/* vsock%* vsock-mux/* vsock-mux%* machine/* machine%* + User root ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy %h %p ProxyUseFdpass yes CheckHostIP no diff --git a/src/ssh-generator/ssh-generator.c b/src/ssh-generator/ssh-generator.c index 3923710b5cca0..7d67d4178ec89 100644 --- a/src/ssh-generator/ssh-generator.c +++ b/src/ssh-generator/ssh-generator.c @@ -109,15 +109,20 @@ static int make_sshd_template_unit( if (r < 0) return r; + /* sshd reads AuthorizedKeysFile after dropping to the authenticating user's UID, so the + * 0400 credential file under $CREDENTIALS_DIRECTORY is unreadable for non-root users. + * Materialize a 0444 copy in a RuntimeDirectory so the ephemeral key works for any user. */ fprintf(f, "[Unit]\n" "Description=OpenSSH Per-Connection Server Daemon\n" "Documentation=man:systemd-ssh-generator(8) man:sshd(8)\n" "\n" "[Service]\n" - "ExecStart=-%s -i -o \"AuthorizedKeysFile ${CREDENTIALS_DIRECTORY}/ssh.ephemeral-authorized_keys-all .ssh/authorized_keys\"\n" + "ExecStartPre=systemd-tmpfiles --create --inline 'f^ /run/sshd-generated-%%i/authorized_keys 0444 root root - ssh.ephemeral-authorized_keys-all'\n" + "ExecStart=-%s -i -o \"AuthorizedKeysFile /run/sshd-generated-%%i/authorized_keys .ssh/authorized_keys\"\n" "StandardInput=socket\n" - "ImportCredential=ssh.ephemeral-authorized_keys-all\n", + "ImportCredential=ssh.ephemeral-authorized_keys-all\n" + "RuntimeDirectory=sshd-generated-%%i\n", sshd_binary); r = fflush_and_check(f); @@ -238,8 +243,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 +343,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..dd2b2370b4b7f 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,191 @@ #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 opts = { argc, argv }; + const char *verb = NULL; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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 = opts.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(opts.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(opts.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 +199,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(&opts) > 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(&opts)); + 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(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/ssh-generator/ssh-util.c b/src/ssh-generator/ssh-util.c index b7af0454870ed..a3863b2805eb1 100644 --- a/src/ssh-generator/ssh-util.c +++ b/src/ssh-generator/ssh-util.c @@ -5,8 +5,8 @@ #include "errno-util.h" #include "log.h" -#include "socket-util.h" #include "ssh-util.h" +#include "vsock-util.h" int vsock_open_or_warn(int *ret) { int fd = RET_NERRNO(socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0)); diff --git a/src/stdio-bridge/stdio-bridge.c b/src/stdio-bridge/stdio-bridge.c index 52c87559a4933..01686d2cd6c0b 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,65 @@ 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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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 = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_bus_path, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.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(&opts) > 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/storage/io.systemd.storage.policy b/src/storage/io.systemd.storage.policy new file mode 100644 index 0000000000000..7b25553501520 --- /dev/null +++ b/src/storage/io.systemd.storage.policy @@ -0,0 +1,40 @@ + + + + + + + + The systemd Project + https://systemd.io + + + Allow access to block storage volumes + Authentication is required for an application to gain access to block storage volume '$(name)'. + + auth_admin + auth_admin + auth_admin_keep + + + + + Allow access to file system storage volumes + Authentication is required for an application to gain access to file system storage volume '$(name)'. + + auth_admin + auth_admin + auth_admin_keep + + + diff --git a/src/storage/meson.build b/src/storage/meson.build new file mode 100644 index 0000000000000..bcd68e612da9b --- /dev/null +++ b/src/storage/meson.build @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +executables += [ + libexec_template + { + 'name' : 'systemd-storage-block', + 'sources' : files('storage-block.c'), + }, + libexec_template + { + 'name' : 'systemd-storage-fs', + 'sources' : files('storage-fs.c'), + }, + executable_template + { + 'name' : 'storagectl', + 'public' : true, + 'sources' : files('storagectl.c'), + }, +] + +install_symlink('mount.storage', + pointing_to : sbin_to_bin + 'storagectl', + install_dir : sbindir) + +install_data('io.systemd.storage.policy', + install_dir : polkitpolicydir) diff --git a/src/storage/storage-block.c b/src/storage/storage-block.c new file mode 100644 index 0000000000000..b33bf9ce40bd3 --- /dev/null +++ b/src/storage/storage-block.c @@ -0,0 +1,439 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-device.h" +#include "sd-json.h" +#include "sd-varlink.h" + +#include "blockdev-list.h" +#include "build.h" +#include "bus-polkit.h" +#include "device-private.h" +#include "device-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "format-table.h" +#include "hashmap.h" +#include "help-util.h" +#include "json-util.h" +#include "log.h" +#include "main-func.h" +#include "options.h" +#include "path-util.h" +#include "storage-util.h" +#include "strv.h" +#include "varlink-io.systemd.StorageProvider.h" +#include "varlink-util.h" + +static int block_device_pick_name( + const BlockDevice *d, + const char **ret_name, + char ***ret_aliases) { + + int r; + + assert(d); + assert(d->node); + assert(ret_name); + assert(ret_aliases); + + static const char *const prefixes[] = { + /* The list of preferred prefixes, in order of preference. Note: for security reasons we only + * use identifiers that do not depend on the *contents* of the device, i.e. we restrict + * ourselves to IDs whose fields are either chosen by whoever created the kernel device or are + * hardware properties, but not names generated from superblock metainformation or similar. */ + "/dev/mapper", + "/dev/disk/by-loop-ref", + "/dev/disk/by-id", + "/dev/disk/by-path", + }; + + const char* found[ELEMENTSOF(prefixes)] = {}; + _cleanup_strv_free_ char **aliases = NULL; + size_t best = SIZE_MAX; + STRV_FOREACH(sl, d->symlinks) { + bool matched = false; + for (size_t i = 0; i < ELEMENTSOF(prefixes); i++) { + if (!path_startswith(*sl, prefixes[i])) + continue; + + if (found[i]) { + /* Two symlinks with the same prefix? Then keep the lower one. */ + if (path_compare(*sl, found[i]) > 0) + continue; + + r = strv_extend(&aliases, found[i]); + if (r < 0) + return r; + } + + found[i] = *sl; + if (i < best) + best = i; + matched = true; + } + + if (!matched) { + r = strv_extend(&aliases, *sl); + if (r < 0) + return r; + } + } + + if (best == SIZE_MAX) /* No preferred prefix found, use the kernel device name */ + *ret_name = d->node; + else { + /* We found a preferred prefix, add the kernel device name to the aliases then. */ + r = strv_extend(&aliases, d->node); + if (r < 0) + return r; + + /* If there are any less preferred prefixes also add them to the aliases array */ + for (size_t i = best + 1; i < ELEMENTSOF(prefixes); i++) { + if (!found[i]) + continue; + + r = strv_extend(&aliases, found[i]); + if (r < 0) + return r; + } + + *ret_name = found[best]; + } + + strv_sort(aliases); + *ret_aliases = TAKE_PTR(aliases); + + return 0; +} + +static bool block_device_match(const BlockDevice *d, const char *match) { + assert(d); + assert(d->node); + + if (!match) + return true; + + if (fnmatch(match, d->node, FNM_NOESCAPE) == 0) + return true; + + STRV_FOREACH(sl, d->symlinks) + if (fnmatch(match, *sl, FNM_NOESCAPE) == 0) + return true; + + return false; +} + +static int vl_method_list_volumes( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(link); + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + struct { + const char *match_name; + } p = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "matchName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, match_name), 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + BlockDevice *l = NULL; + size_t n = 0; + CLEANUP_ARRAY(l, n, block_device_array_free); + + r = blockdev_list( + BLOCKDEV_LIST_SHOW_SYMLINKS| + BLOCKDEV_LIST_IGNORE_ROOT| + BLOCKDEV_LIST_IGNORE_EMPTY| + BLOCKDEV_LIST_METADATA, + &l, + &n); + if (r < 0) + return r; + + r = sd_varlink_set_sentinel(link, "io.systemd.StorageProvider.NoSuchVolume"); + if (r < 0) + return r; + + FOREACH_ARRAY(d, l, n) { + const char *name = NULL; + _cleanup_strv_free_ char **aliases = NULL; + + if (!block_device_match(d, p.match_name)) + continue; + + r = block_device_pick_name(d, &name, &aliases); + if (r < 0) + return r; + + r = sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_STRING("name", name), + JSON_BUILD_PAIR_STRV_NON_EMPTY("aliases", aliases), + SD_JSON_BUILD_PAIR_STRING("type", "blk"), + SD_JSON_BUILD_PAIR_CONDITION(d->read_only >= 0, "readOnly", SD_JSON_BUILD_BOOLEAN(d->read_only)), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("sizeBytes", d->size, UINT64_MAX)); + if (r < 0) + return r; + } + + return 0; +} + +static int vl_method_list_templates( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + /* This storage provider does not support templates */ + assert(link); + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchTemplate", NULL); +} + +static int device_open_disk_auto_rw(sd_device *d, int *read_only) { + assert(d); + assert(read_only); + + int fd = sd_device_open(d, *read_only > 0 ? O_RDONLY : O_RDWR); + if (fd < 0) { + if (!ERRNO_IS_NEG_FS_WRITE_REFUSED(fd) || *read_only >= 0) + return log_device_debug_errno(d, fd, "Failed to open device in %s mode: %m", *read_only > 0 ? "read-only" : "read-write"); + + /* Try again in read-only mode */ + fd = sd_device_open(d, O_RDONLY); + if (fd < 0) + return log_device_debug_errno(d, fd, "Failed to open device in read-only mode, too: %m"); + + *read_only = true; + } else + *read_only = *read_only > 0; + + return fd; +} + +static int vl_method_acquire( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + Hashmap **polkit_registry = ASSERT_PTR(userdata); + int r; + + assert(link); + + struct { + const char *name; + CreateMode create_mode; + const char *template; + int read_only; + VolumeType request_as; + uint64_t create_size; + } p = { + .create_mode = CREATE_ANY, + .read_only = -1, + .request_as = _VOLUME_TYPE_INVALID, + .create_size = UINT64_MAX, /* never actually used here, just validated; we don't allow creation of block devices here */ + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + { "createMode", SD_JSON_VARIANT_STRING, json_dispatch_create_mode, voffsetof(p, create_mode), 0 }, + { "template", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, template), 0 }, + { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, voffsetof(p, read_only), 0 }, + { "requestAs", SD_JSON_VARIANT_STRING, json_dispatch_volume_type, voffsetof(p, request_as), 0 }, + { "createSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, create_size), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (!storage_volume_name_is_valid(p.name)) + return sd_varlink_error_invalid_parameter_name(link, "name"); + if (!path_startswith(p.name, "/dev") || !path_is_normalized(p.name)) + return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchVolume", NULL); + + if (!IN_SET(p.create_mode, CREATE_ANY, CREATE_OPEN)) + return sd_varlink_error(link, "io.systemd.StorageProvider.CreateNotSupported", NULL); + + /* off_t is signed, hence refuse overly long requests */ + if (p.create_size != UINT64_MAX && p.create_size > INT64_MAX) + return sd_varlink_error_invalid_parameter_name(link, "createSizeBytes"); + + if (!isempty(p.template)) { + if (!storage_template_name_is_valid(p.template)) + return sd_varlink_error_invalid_parameter_name(link, "template"); + + return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchTemplate", NULL); + } + + if (p.request_as >= 0 && p.request_as != VOLUME_BLK) + return sd_varlink_error(link, "io.systemd.StorageProvider.TypeNotSupported", NULL); + + const char *details[] = { + "name", p.name, + NULL + }; + + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + "io.systemd.storage.block.acquire", + details, + polkit_registry); + if (r <= 0) + return r; + + _cleanup_(sd_device_unrefp) sd_device *d = NULL; + r = sd_device_new_from_devname(&d, p.name); + if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) + return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchVolume", NULL); + if (r < 0) + return r; + + if (!device_in_subsystem(d, "block")) + return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchVolume", NULL); + + /* The error returns are sometimes a bit inconclusive (i.e. read-only media might appear as + * inaccessible due to a permission issue), hence let's do an explicit check first, to give good + * answers */ + if (p.read_only <= 0) { + r = device_get_sysattr_bool(d, "ro"); + if (r < 0) + log_device_debug_errno(d, r, "Failed to acquire read-only flag of device '%s', ignoring: %m", p.name); + else if (r > 0) { + if (p.read_only == 0) + return sd_varlink_error(link, "io.systemd.StorageProvider.ReadOnlyVolume", NULL); + + p.read_only = true; + } + } + + _cleanup_close_ int fd = device_open_disk_auto_rw(d, &p.read_only); + if (ERRNO_IS_NEG_FS_WRITE_REFUSED(fd)) + return sd_varlink_error(link, "io.systemd.StorageProvider.ReadOnlyVolume", NULL); + if (fd < 0) + return fd; + + assert(p.read_only >= 0); /* flag is now definitely initialized to either true or false, not negative anymore */ + + int idx = sd_varlink_push_fd(link, fd); + if (idx < 0) + return idx; + + TAKE_FD(fd); + + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_INTEGER("fileDescriptorIndex", idx), + SD_JSON_BUILD_PAIR_STRING("type", "blk"), + SD_JSON_BUILD_PAIR_BOOLEAN("readOnly", p.read_only)); +} + +static int vl_server(void) { + int r; + + _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL; + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + r = varlink_server_new( + &varlink_server, + SD_VARLINK_SERVER_HANDLE_SIGINT| + SD_VARLINK_SERVER_HANDLE_SIGTERM| + SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT| + SD_VARLINK_SERVER_INHERIT_USERDATA, + &polkit_registry); + 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_StorageProvider); + 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.StorageProvider.Acquire", vl_method_acquire, + "io.systemd.StorageProvider.ListVolumes", vl_method_list_volumes, + "io.systemd.StorageProvider.ListTemplates", vl_method_list_templates); + 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) { + int r; + + help_cmdline("[OPTIONS...]"); + help_abstract("Simple block device backed storage provider"); + + _cleanup_(table_unrefp) Table *options = NULL; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_section("Options"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-storage-block", "8"); + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + } + + if (option_parser_get_n_args(&opts) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); + + 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/storage/storage-fs.c b/src/storage/storage-fs.c new file mode 100644 index 0000000000000..5b2bc415fb650 --- /dev/null +++ b/src/storage/storage-fs.c @@ -0,0 +1,807 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include + +#include "sd-device.h" +#include "sd-json.h" + +#include "alloc-util.h" +#include "build.h" +#include "bus-polkit.h" +#include "chase.h" +#include "chattr-util.h" +#include "device-private.h" +#include "device-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "format-table.h" +#include "fs-util.h" +#include "hashmap.h" +#include "help-util.h" +#include "log.h" +#include "main-func.h" +#include "mount-util.h" +#include "options.h" +#include "path-lookup.h" +#include "path-util.h" +#include "recurse-dir.h" +#include "runtime-scope.h" +#include "stat-util.h" +#include "storage-util.h" +#include "string-table.h" +#include "tmpfile-util.h" +#include "uid-classification.h" +#include "varlink-io.systemd.StorageProvider.h" +#include "varlink-util.h" + +static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + +/* For now we maintain a simple, compiled-in list of templates. One of those days we might want to move these + * into configurable drop-in files on disk. */ +typedef enum Template { + TEMPLATE_SPARSE_FILE, + TEMPLATE_ALLOCATED_FILE, + TEMPLATE_DIRECTORY, + TEMPLATE_SUBVOLUME, + _TEMPLATE_MAX, + _TEMPLATE_INVALID = -EINVAL, +} Template; + +static const char *template_table[_TEMPLATE_MAX] = { + [TEMPLATE_SPARSE_FILE] = "sparse-file", + [TEMPLATE_ALLOCATED_FILE] = "allocated-file", + [TEMPLATE_DIRECTORY] = "directory", + [TEMPLATE_SUBVOLUME] = "subvolume", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(template, Template); + +static VolumeType volume_type_from_template(Template t) { + switch (t) { + + case TEMPLATE_SPARSE_FILE: + case TEMPLATE_ALLOCATED_FILE: + return VOLUME_REG; + + case TEMPLATE_DIRECTORY: + case TEMPLATE_SUBVOLUME: + return VOLUME_DIR; + + default: + return _VOLUME_TYPE_INVALID; + } +} + +static int open_storage_dir(void) { + int r; + + _cleanup_free_ char *state_dir = NULL; + r = state_directory_generic(arg_runtime_scope, /* suffix= */ NULL, &state_dir); + if (r < 0) + return log_error_errno(r, "Failed to get state directory path: %m"); + + _cleanup_close_ int state_fd = chase_and_open(state_dir, /* root= */ NULL, CHASE_TRIGGER_AUTOFS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, O_CLOEXEC|O_CREAT|O_DIRECTORY, /* ret_path= */ NULL); + if (state_fd < 0) + return log_error_errno(state_fd, "Failed to open '%s': %m", state_dir); + + /* First we try to open the storage directory. If it exists this will work and we are happy. If we + * get ENOENT we'll try to create it. If that works, great. If we get EEXIST we'll try to reopen it + * again, to deal with other instances of ourselves racing with us. We only do this exactly once + * though, under the assumption that the dir is never removed, only created during runtime. */ + _cleanup_close_ int storage_fd = chase_and_openat(XAT_FDROOT, state_fd, "storage", CHASE_TRIGGER_AUTOFS|CHASE_MUST_BE_DIRECTORY, O_CLOEXEC|O_DIRECTORY, /* ret_path= */ NULL); + if (storage_fd == -ENOENT) { + storage_fd = xopenat_full(state_fd, "storage", O_EXCL|O_CREAT|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW, XO_LABEL|XO_SUBVOLUME, 0700); + if (storage_fd == -EEXIST) + storage_fd = chase_and_openat(XAT_FDROOT, state_fd, "storage", CHASE_TRIGGER_AUTOFS|CHASE_MUST_BE_DIRECTORY, O_CLOEXEC|O_DIRECTORY, /* ret_path= */ NULL); + } + if (storage_fd < 0) + return log_error_errno(storage_fd, "Failed to open '%s/storage/': %m", state_dir); + + return TAKE_FD(storage_fd); +} + +static int vl_method_list_volumes( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(link); + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + struct { + const char *match_name; + } p = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "matchName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, match_name), 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + _cleanup_close_ int fd = open_storage_dir(); + if (fd < 0) + return fd; + + _cleanup_free_ DirectoryEntries *dentries = NULL; + r = readdir_all(fd, RECURSE_DIR_SORT, &dentries); + if (r < 0) + return r; + + r = sd_varlink_set_sentinel(link, "io.systemd.StorageProvider.NoSuchVolume"); + if (r < 0) + return r; + + FOREACH_ARRAY(dp, dentries->entries, dentries->n_entries) { + struct dirent *d = *dp; + + if (!IN_SET(d->d_type, DT_REG, DT_DIR, DT_LNK, DT_BLK, DT_UNKNOWN)) + continue; + + const char *e = endswith(d->d_name, ".volume"); + if (!e) + continue; + + _cleanup_free_ char *n = strndup(d->d_name, e - d->d_name); + if (!n) + return log_oom_debug(); + + if (!storage_volume_name_is_valid(n)) + continue; + + if (p.match_name && fnmatch(p.match_name, n, FNM_NOESCAPE) != 0) + continue; + + _cleanup_close_ int pin_fd = -EBADF; + r = chaseat(XAT_FDROOT, fd, d->d_name, CHASE_TRIGGER_AUTOFS, /* ret_path= */ NULL, &pin_fd); + if (r < 0) { + log_debug_errno(r, "Failed to stat() '%s' in storage directory, ignoring: %m", d->d_name); + continue; + } + + struct stat st; + if (fstat(pin_fd, &st) < 0) + return log_debug_errno(errno, "Failed to stat() '%s' in storage directory: %m", d->d_name); + + uint64_t size = UINT64_MAX, used = UINT64_MAX; + bool ro = false; + + switch (st.st_mode & S_IFMT) { + case S_IFREG: + ro = (st.st_mode & 0222) == 0; + size = st.st_size; + used = (uint64_t) st.st_blocks * UINT64_C(512); + break; + + case S_IFDIR: + r = fd_is_read_only_fs(pin_fd); + if (r < 0) + log_debug_errno(r, "Failed to determine if '%s' is read-only, ignoring", d->d_name); + else + ro = r > 0; + break; + + case S_IFBLK: { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + + r = sd_device_new_from_stat_rdev(&dev, &st); + if (r < 0) + log_debug_errno(r, "Failed to acquire device for '%s', ignoring: %m", d->d_name); + else { + r = device_get_sysattr_bool(dev, "ro"); + if (r < 0) + log_device_debug_errno(dev, r, "Failed to get read/only state of '%s', ignoring: %m", d->d_name); + else + ro = r > 0; + + r = device_get_sysattr_u64(dev, "size", &size); + if (r < 0) + log_device_debug_errno(dev, r, "Failed to acquire size of device '%s', ignoring: %m", d->d_name); + else + /* the 'size' sysattr is always in multiples of 512, even on 4K sector block devices! */ + assert_se(MUL_ASSIGN_SAFE(&size, 512)); /* Overflow check for coverity */ + } + + break; + } + + default: + log_debug("Volume of unexpected inode type, ignoring: %s", d->d_name); + continue; + } + + r = sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_STRING("name", n), + SD_JSON_BUILD_PAIR_STRING("type", inode_type_to_string(st.st_mode)), + SD_JSON_BUILD_PAIR_BOOLEAN("readOnly", ro), + SD_JSON_BUILD_PAIR_CONDITION(size != UINT64_MAX, "sizeBytes", SD_JSON_BUILD_UNSIGNED(size)), + SD_JSON_BUILD_PAIR_CONDITION(used != UINT64_MAX, "usedBytes", SD_JSON_BUILD_UNSIGNED(used))); + if (r < 0) + return r; + } + + return 0; +} + +static int vl_method_list_templates( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(link); + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + struct { + const char *match_name; + } p = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "matchName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, match_name), 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + r = sd_varlink_set_sentinel(link, "io.systemd.StorageProvider.NoSuchTemplate"); + if (r < 0) + return r; + + for (Template t = 0; t < _TEMPLATE_MAX; t++) { + const char *n = template_to_string(t); + + if (p.match_name && fnmatch(p.match_name, n, FNM_NOESCAPE) != 0) + continue; + + r = sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_STRING("name", n), + SD_JSON_BUILD_PAIR_STRING("type", volume_type_to_string(volume_type_from_template(t)))); + if (r < 0) + return r; + } + + return 0; +} + +static int create_volume_dir( + int storage_fd, + const char *filename, + Template t) { + + int r; + + assert(storage_fd >= 0); + assert(filename); + + XOpenFlags xopen_flags; + switch (t) { + + case TEMPLATE_DIRECTORY: + xopen_flags = 0; + break; + + case TEMPLATE_SUBVOLUME: + xopen_flags = XO_SUBVOLUME; + break; + + default: + return -ENOMEDIUM; /* Recognizable error for: template doesn't apply here */ + } + + _cleanup_free_ char *tf = NULL; + r = tempfn_random(filename, /* extra= */ NULL, &tf); + if (r < 0) + return r; + + _cleanup_close_ int volume_fd = xopenat_full(storage_fd, tf, O_CREAT|O_EXCL|O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW, xopen_flags, 0700); + if (volume_fd < 0) + return volume_fd; + + _cleanup_close_ int root_fd = xopenat_full(volume_fd, "root", O_CREAT|O_EXCL|O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW, xopen_flags, 0755); + if (root_fd < 0) { + r = root_fd; + goto fail; + } + + r = RET_NERRNO(fchown(root_fd, FOREIGN_UID_MIN, FOREIGN_UID_MIN)); + if (r < 0) + goto fail; + + r = rename_noreplace(storage_fd, tf, storage_fd, filename); + if (r < 0) + goto fail; + + return TAKE_FD(root_fd); + +fail: + if (root_fd >= 0) { + assert(volume_fd >= 0); + root_fd = safe_close(root_fd); + (void) unlinkat(volume_fd, "root", AT_REMOVEDIR); + } + + if (volume_fd >= 0) { + volume_fd = safe_close(volume_fd); + (void) unlinkat(storage_fd, tf, AT_REMOVEDIR); + } + + return r; +} + +static int create_volume_reg( + int storage_fd, + const char *filename, + Template t, + uint64_t create_size) { + int r; + + assert(storage_fd >= 0); + assert(filename); + + bool sparse; + switch (t) { + + case TEMPLATE_SPARSE_FILE: + sparse = true; + break; + + case TEMPLATE_ALLOCATED_FILE: + sparse = false; + break; + + default: + return -ENOMEDIUM; /* Recognizable error for: template doesn't apply here */ + } + + _cleanup_free_ char *tf = NULL; + _cleanup_close_ int fd = open_tmpfile_linkable_at(storage_fd, filename, O_RDWR|O_CLOEXEC, &tf); + if (fd < 0) + return fd; + + CLEANUP_TMPFILE_AT(storage_fd, tf); + + r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL); + if (r < 0 && !ERRNO_IS_IOCTL_NOT_SUPPORTED(r)) + return r; + + if (create_size > 0) { + if (sparse) + r = RET_NERRNO(ftruncate(fd, create_size)); + else + r = RET_NERRNO(fallocate(fd, /* mode= */ 0, /* offset= */ 0, create_size)); + if (r < 0) + return r; + } + + r = RET_NERRNO(fchmod(fd, 0600)); + if (r < 0) + return r; + + r = link_tmpfile_at(fd, storage_fd, tf, filename, /* flags= */ 0); + if (r < 0) + return r; + + tf = mfree(tf); /* disarm clean-up */ + + return TAKE_FD(fd); +} + +static int vl_method_acquire( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + Hashmap **polkit_registry = ASSERT_PTR(userdata); + int r; + + assert(link); + + struct { + const char *name; + CreateMode create_mode; + const char *template; + int read_only; + VolumeType request_as; + uint64_t create_size; + } p = { + .create_mode = CREATE_ANY, + .read_only = -1, + .request_as = _VOLUME_TYPE_INVALID, + .create_size = UINT64_MAX, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + { "createMode", SD_JSON_VARIANT_STRING, json_dispatch_create_mode, voffsetof(p, create_mode), 0 }, + { "template", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, template), 0 }, + { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, voffsetof(p, read_only), 0 }, + { "requestAs", SD_JSON_VARIANT_STRING, json_dispatch_volume_type, voffsetof(p, request_as), 0 }, + { "createSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, create_size), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (!storage_volume_name_is_valid(p.name)) + return sd_varlink_error_invalid_parameter_name(link, "name"); + + if (!IN_SET(p.create_mode, CREATE_ANY, CREATE_OPEN, CREATE_NEW)) + return sd_varlink_error(link, "io.systemd.StorageProvider.CreateNotSupported", NULL); + + /* off_t is signed, hence refuse overly long requests */ + if (p.create_size != UINT64_MAX && p.create_size > INT64_MAX) + return sd_varlink_error_invalid_parameter_name(link, "createSizeBytes"); + + Template t = _TEMPLATE_INVALID; + if (!isempty(p.template)) { + if (!storage_template_name_is_valid(p.template)) + return sd_varlink_error_invalid_parameter_name(link, "template"); + + t = template_from_string(p.template); + if (t < 0) + return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchTemplate", NULL); + } + + if (p.read_only > 0) { + if (p.create_mode == CREATE_NEW) + return sd_varlink_error_invalid_parameter_name(link, "readOnly"); + + p.create_mode = CREATE_OPEN; + } + + /* Add a suffix so that we are never attempted to open a temporary file assuming it was a valid + * volume. */ + _cleanup_free_ char *filename = strjoin(p.name, ".volume"); + if (!filename) + return log_oom_debug(); + + if (!filename_is_valid(filename)) + return sd_varlink_error_invalid_parameter_name(link, "name"); + + if (arg_runtime_scope != RUNTIME_SCOPE_USER) { + const char *details[] = { + "name", p.name, + NULL + }; + + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + "io.systemd.storage.fs.acquire", + details, + polkit_registry); + if (r <= 0) + return r; + } + + _cleanup_close_ int storage_fd = open_storage_dir(); + if (storage_fd < 0) + return storage_fd; + + _cleanup_close_ int pin_fd = -EBADF, real_fd = -EBADF; + r = chaseat(XAT_FDROOT, storage_fd, filename, CHASE_TRIGGER_AUTOFS, /* ret_path= */ NULL, &pin_fd); + if (r < 0) { + if (r != -ENOENT) + return r; + if (p.create_mode == CREATE_OPEN || p.read_only > 0) + return sd_varlink_error(link, "io.systemd.StorageProvider.NoSuchVolume", NULL); + + /* Doesn't exist yet: create it now */ + + if (p.request_as < 0) /* Make a choice: pick default type */ + p.request_as = t < 0 ? VOLUME_DIR : volume_type_from_template(t); + + /* Try to create the volume */ + switch (p.request_as) { + + case VOLUME_DIR: { + + if (t < 0) /* Make a choice: pick default template */ + t = TEMPLATE_SUBVOLUME; + + real_fd = create_volume_dir(storage_fd, filename, t); + break; + } + + case VOLUME_REG: { + if (p.create_size == UINT64_MAX) + return sd_varlink_error(link, "io.systemd.StorageProvider.CreateSizeRequired", NULL); + + if (t < 0) /* Make a choice: pick default template */ + t = TEMPLATE_SPARSE_FILE; + + real_fd = create_volume_reg(storage_fd, filename, t, p.create_size); + break; + } + + case VOLUME_BLK: + /* We don't support creating block devices, we only support if they are symlinked + * into the storage directory. */ + return sd_varlink_error(link, "io.systemd.StorageProvider.CreateNotSupported", NULL); + + default: + assert_not_reached(); + } + + if (real_fd == -ENOMEDIUM) + return sd_varlink_error(link, "io.systemd.StorageProvider.BadTemplate", NULL); + if (real_fd == -EEXIST) { + if (p.create_mode == CREATE_NEW) + return sd_varlink_error(link, "io.systemd.StorageProvider.VolumeExists", NULL); + + /* If we failed to open the volume and reached this point, then the volume already + * exists by now (i.e. we ran into a race). In that case, try to pin it a second time + * (but only once, let's never loop around this). */ + r = chaseat(XAT_FDROOT, storage_fd, filename, CHASE_TRIGGER_AUTOFS, /* ret_path= */ NULL, &pin_fd); + if (r < 0) + return r; + } else if (real_fd < 0) + return real_fd; + + } else if (p.create_mode == CREATE_NEW) + return sd_varlink_error(link, "io.systemd.StorageProvider.VolumeExists", NULL); + + /* At this point, we either already opened the real fd, or we managed to pin it (but not both) */ + assert((real_fd >= 0) != (pin_fd >= 0)); + + /* Let's first settle the volume type */ + struct stat st; + if (fstat(real_fd >= 0 ? real_fd : pin_fd, &st) < 0) + return -errno; + + if (p.request_as == VOLUME_REG) { + /* First, check for the other supported types and generate a nice error */ + if (IN_SET(st.st_mode & S_IFMT, S_IFDIR, S_IFBLK)) + return sd_varlink_error(link, "io.systemd.StorageProvider.WrongType", NULL); + + /* Second verify cover all other types */ + r = stat_verify_regular(&st); + if (r < 0) + return r; + } else if (p.request_as == VOLUME_DIR) { + if (IN_SET(st.st_mode & S_IFMT, S_IFREG, S_IFBLK)) + return sd_varlink_error(link, "io.systemd.StorageProvider.WrongType", NULL); + + r = stat_verify_directory(&st); + if (r < 0) + return r; + } else if (p.request_as == VOLUME_BLK) { + if (IN_SET(st.st_mode & S_IFMT, S_IFREG, S_IFDIR)) + return sd_varlink_error(link, "io.systemd.StorageProvider.WrongType", NULL); + + r = stat_verify_block(&st); + if (r < 0) + return r; + + } else if (S_ISREG(st.st_mode)) + p.request_as = VOLUME_REG; + else if (S_ISDIR(st.st_mode)) + p.request_as = VOLUME_DIR; + else if (S_ISBLK(st.st_mode)) + p.request_as = VOLUME_BLK; + else + return log_debug_errno(SYNTHETIC_ERRNO(EBADF), "Unexpected inode type, refusing."); + + /* Let's now acquire a real fd for the pinned fd, if we still need to */ + if (real_fd < 0) { + assert(pin_fd >= 0); + + XOpenFlags xopen_flags = + (p.read_only < 0 && !S_ISDIR(st.st_mode) ? XO_AUTO_RW_RO : 0); + int open_flags = + (p.read_only < 0 ? 0 : (p.read_only > 0 || S_ISDIR(st.st_mode) ? O_RDONLY : O_RDWR)); + + const char *subdir = NULL; + if (p.request_as == VOLUME_DIR) { + /* We place the root of the directory tree one level down, to separate ownership of + * the inode: the upper inode is owned by the host, the lower one by the volume. This + * matters so that the host one can be owned by the host's root, and the volume one + * by the foreign UID range. */ + subdir = "root"; + open_flags |= O_DIRECTORY|O_NOFOLLOW; + } + + real_fd = xopenat_full(pin_fd, subdir, open_flags|O_CLOEXEC, xopen_flags, /* mode= */ MODE_INVALID); + if (real_fd < 0) + return log_debug_errno(real_fd, "Failed to reopen volume fd for '%s': %m", filename); + + /* In directory mode we might be looking at a different inode node, refresh the stat data */ + if (p.request_as == VOLUME_DIR && fstat(real_fd, &st) < 0) + return -errno; + } + + assert(real_fd >= 0); + + bool ro; + switch (p.request_as) { + + case VOLUME_REG: + case VOLUME_BLK: { + assert(IN_SET(st.st_mode & S_IFMT, S_IFREG, S_IFBLK)); + + int open_flags = fcntl(real_fd, F_GETFL, 0); + if (open_flags < 0) + return -errno; + + ro = (open_flags & O_ACCMODE_STRICT) == O_RDONLY; + break; + } + + case VOLUME_DIR: { + assert(S_ISDIR(st.st_mode)); + + if (!uid_is_foreign(st.st_uid) || + !gid_is_foreign(st.st_gid)) + return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Storage directory not owned by foreign UID/GID range."); + + /* Let's now generate a new mount for the directory tree, where propagation is disabled, and the + * flags are all set to good defaults */ + _cleanup_close_ int mount_fd = open_tree_attr_with_fallback( + real_fd, + /* path= */ NULL, + OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_SYMLINK_NOFOLLOW, + &(struct mount_attr) { + .attr_set = (p.read_only > 0 ? MOUNT_ATTR_RDONLY : 0), + .attr_clr = MOUNT_ATTR_NOSUID|MOUNT_ATTR_NOEXEC|MOUNT_ATTR_NODEV, + .propagation = MS_PRIVATE, + }); + if (mount_fd < 0) + return log_debug_errno(mount_fd, "Failed to generate per-volume mount: %m"); + + /* Let's turn on propagation again now that it is disconnected, simply because MS_SHARED is + * generally the default for everything we return. */ + + if (mount_setattr(mount_fd, "", AT_EMPTY_PATH|AT_SYMLINK_NOFOLLOW, + &(struct mount_attr) { + .propagation = MS_SHARED, + }, MOUNT_ATTR_SIZE_VER0) < 0) + return log_debug_errno(errno, "Failed to enable propagation on per-volume mount: %m"); + + close_and_replace(real_fd, mount_fd); + + r = fd_is_read_only_fs(real_fd); + if (r < 0) + return r; + + ro = r > 0; + break; + } + + default: + assert_not_reached(); + } + + if (p.read_only == 0 && ro) + return sd_varlink_error(link, "io.systemd.StorageProvider.ReadOnlyVolume", NULL); + + int idx = sd_varlink_push_fd(link, real_fd); + if (idx < 0) + return idx; + + TAKE_FD(real_fd); + + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_INTEGER("fileDescriptorIndex", idx), + SD_JSON_BUILD_PAIR_STRING("type", inode_type_to_string(st.st_mode)), + SD_JSON_BUILD_PAIR_BOOLEAN("readOnly", ro), + SD_JSON_BUILD_PAIR_CONDITION(p.request_as == VOLUME_DIR, "baseUID", SD_JSON_BUILD_INTEGER(FOREIGN_UID_BASE)), + SD_JSON_BUILD_PAIR_CONDITION(p.request_as == VOLUME_DIR, "baseGID", SD_JSON_BUILD_INTEGER(FOREIGN_UID_BASE))); +} + +static int vl_server(void) { + int r; + + _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL; + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + r = varlink_server_new( + &varlink_server, + SD_VARLINK_SERVER_HANDLE_SIGINT| + SD_VARLINK_SERVER_HANDLE_SIGTERM| + SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT| + SD_VARLINK_SERVER_INHERIT_USERDATA, + &polkit_registry); + 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_StorageProvider); + 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.StorageProvider.Acquire", vl_method_acquire, + "io.systemd.StorageProvider.ListVolumes", vl_method_list_volumes, + "io.systemd.StorageProvider.ListTemplates", vl_method_list_templates); + 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) { + int r; + + help_cmdline("[OPTIONS...]"); + help_abstract("Simple file system backed storage provider"); + + _cleanup_(table_unrefp) Table *options = NULL; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_section("Options"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-storage-fs", "8"); + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_COMMON_SYSTEM: + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + OPTION_COMMON_USER: + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + } + + if (option_parser_get_n_args(&opts) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); + + 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/storage/storagectl.c b/src/storage/storagectl.c new file mode 100644 index 0000000000000..b899513616dc5 --- /dev/null +++ b/src/storage/storagectl.c @@ -0,0 +1,743 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink.h" + +#include +#include +#include + +#include "alloc-util.h" +#include "ansi-color.h" +#include "argv-util.h" +#include "build.h" +#include "bus-util.h" +#include "errno-list.h" +#include "escape.h" +#include "extract-word.h" +#include "fd-util.h" +#include "format-table.h" +#include "format-util.h" +#include "help-util.h" +#include "machine-util.h" +#include "main-func.h" +#include "mount-util.h" +#include "namespace-util.h" +#include "options.h" +#include "parse-argument.h" +#include "parse-util.h" +#include "path-lookup.h" +#include "path-util.h" +#include "polkit-agent.h" +#include "recurse-dir.h" +#include "runtime-scope.h" +#include "socket-util.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "storage-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "varlink-util.h" +#include "verbs.h" + +static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; +static PagerFlags arg_pager_flags = 0; +static bool arg_legend = true; +static bool arg_ask_password = true; +static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + +static int help(void) { + int r; + + help_cmdline("[OPTIONS...] COMMAND"); + help_abstract("Enumerate storage volumes and providers."); + + _cleanup_(table_unrefp) Table *verbs = NULL; + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + + _cleanup_(table_unrefp) Table *options = NULL; + r = option_parser_get_help_table_ns("storagectl", &options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + help_section("Commands"); + + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("storagectl", "1"); + return 0; +} + +VERB_COMMON_HELP_HIDDEN(help); + +static const char *ro_color(int ro) { + if (ro > 0) + return ansi_highlight_red(); + if (ro == 0) + return ansi_highlight_green(); + + return NULL; +} + +static int on_list_reply( + sd_varlink *link, + sd_json_variant *parameters, + const char *error_id, + sd_varlink_reply_flags_t flags, + void* userdata) { + + Table *t = ASSERT_PTR(userdata); + int r; + + assert(link); + + const char *d = ASSERT_PTR(sd_varlink_get_description(link)); + + if (error_id) { + log_debug("%s: Received error '%s', ignoring.", d, error_id); + return 0; + } + + _cleanup_free_ char *provider = NULL; + r = path_extract_filename(d, &provider); + if (r < 0) + return log_error_errno(r, "Failed to extract provider name from socket path: %m"); + + struct { + const char *name; + const char *type; + int read_only; + uint64_t size_bytes; + uint64_t used_bytes; + } p = { + .read_only = -1, + .size_bytes = UINT64_MAX, + .used_bytes = UINT64_MAX, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), 0 }, + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, type), 0 }, + { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, voffsetof(p, read_only), 0 }, + { "sizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, size_bytes), 0 }, + { "usedBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, used_bytes), 0 }, + {} + }; + + r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return log_error_errno(r, "Failed to decode List() reply: %m"); + + r = table_add_many( + t, + TABLE_STRING, provider, + TABLE_STRING, p.name, + TABLE_STRING, p.type, + TABLE_TRISTATE, p.read_only, + TABLE_SET_COLOR, ro_color(p.read_only)); + if (r < 0) + return table_log_add_error(r); + + if (p.size_bytes == UINT64_MAX) + r = table_add_many(t, TABLE_EMPTY, TABLE_SET_ALIGN_PERCENT, 100); + else + r = table_add_many(t, TABLE_SIZE, p.size_bytes, TABLE_SET_ALIGN_PERCENT, 100); + if (r < 0) + return table_log_add_error(r); + + if (p.used_bytes == UINT64_MAX) + r = table_add_many(t, TABLE_EMPTY, TABLE_SET_ALIGN_PERCENT, 100); + else + r = table_add_many(t, TABLE_SIZE, p.used_bytes, TABLE_SET_ALIGN_PERCENT, 100); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +VERB(verb_list_volumes, "volumes", "GLOB", /* min_args= */ VERB_ANY, /* max_args= */ 2, VERB_DEFAULT, "List storage volumes"); +static int verb_list_volumes(int argc, char *argv[], uintptr_t data, void *userdata) { + int r; + + assert(argc <= 2); + + _cleanup_free_ char *socket_path = NULL; + r = runtime_directory_generic(arg_runtime_scope, "systemd/io.systemd.StorageProvider", &socket_path); + if (r < 0) + return log_error_errno(r, "Failed to determine socket directory: %m"); + + _cleanup_(table_unrefp) Table *t = table_new("provider", "name", "type", "ro", "size", "used"); + if (!t) + return log_oom(); + + (void) table_set_sort(t, (size_t) 0, (size_t) 1); + table_set_ersatz_string(t, TABLE_ERSATZ_DASH); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + if (argc >= 2) { + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR_STRING("matchName", argv[1])); + if (r < 0) + return log_oom(); + } + + ssize_t n = varlink_execute_directory( + socket_path, + "io.systemd.StorageProvider.ListVolumes", + v, + /* more= */ true, + /* timeout_usec= */ 0, /* 0 means default */ + on_list_reply, + t); + if (n < 0 && n != -ENOENT) + return log_error_errno(n, "Failed to enumerate storage volumes: %m"); + + if (!table_isempty(t)) { + r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); + if (r < 0) + return r; + } + + if (arg_legend && FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (table_isempty(t)) + printf("No storage volumes.\n"); + else + printf("\n%zu storage volumes listed.\n", table_get_rows(t) - 1); + } + + return 0; +} + +static int on_list_templates_reply( + sd_varlink *link, + sd_json_variant *parameters, + const char *error_id, + sd_varlink_reply_flags_t flags, + void* userdata) { + + Table *t = ASSERT_PTR(userdata); + int r; + + assert(link); + + const char *d = ASSERT_PTR(sd_varlink_get_description(link)); + + if (error_id) { + log_debug("%s: Received error '%s', ignoring.", d, error_id); + return 0; + } + + _cleanup_free_ char *provider = NULL; + r = path_extract_filename(d, &provider); + if (r < 0) + return log_error_errno(r, "Failed to extract provider name from socket path: %m"); + + struct { + const char *name; + const char *type; + } p = { + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), 0 }, + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, type), 0 }, + {} + }; + + r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return log_error_errno(r, "Failed to decode ListTemplates() reply: %m"); + + r = table_add_many( + t, + TABLE_STRING, provider, + TABLE_STRING, p.name, + TABLE_STRING, p.type); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +VERB(verb_templates, "templates", "GLOB", /* min_args= */ VERB_ANY, /* max_args= */ 2, /* flags= */ 0, "List storage volume templates"); +static int verb_templates(int argc, char *argv[], uintptr_t data, void *userdata) { + int r; + + assert(argc <= 2); + + _cleanup_free_ char *socket_path = NULL; + r = runtime_directory_generic(arg_runtime_scope, "systemd/io.systemd.StorageProvider", &socket_path); + if (r < 0) + return log_error_errno(r, "Failed to determine socket directory: %m"); + + _cleanup_(table_unrefp) Table *t = table_new("provider", "name", "type"); + if (!t) + return log_oom(); + + (void) table_set_sort(t, (size_t) 0, (size_t) 1); + table_set_ersatz_string(t, TABLE_ERSATZ_DASH); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + if (argc >= 2) { + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR_STRING("matchName", argv[1])); + if (r < 0) + return log_oom(); + } + + ssize_t n = varlink_execute_directory( + socket_path, + "io.systemd.StorageProvider.ListTemplates", + v, + /* more= */ true, + /* timeout_usec= */ 0, /* 0 means default */ + on_list_templates_reply, + t); + if (n < 0 && n != -ENOENT) + return log_error_errno(n, "Failed to enumerate storage volume templates: %m"); + + if (!table_isempty(t)) { + r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); + if (r < 0) + return r; + } + + if (arg_legend && FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (table_isempty(t)) + printf("No templates.\n"); + else + printf("\n%zu templates listed.\n", table_get_rows(t) - 1); + } + + return 0; +} + +VERB_NOARG(verb_providers, "providers", "List storage providers"); +static int verb_providers(int argc, char *argv[], uintptr_t data, void *userdata) { + int r; + + _cleanup_free_ char *socket_path = NULL; + r = runtime_directory_generic(arg_runtime_scope, "systemd/io.systemd.StorageProvider", &socket_path); + if (r < 0) + return log_error_errno(r, "Failed to determine socket directory: %m"); + + _cleanup_(table_unrefp) Table *t = table_new("provider", "listening"); + if (!t) + return log_oom(); + + (void) table_set_sort(t, (size_t) 0); + table_set_ersatz_string(t, TABLE_ERSATZ_DASH); + + _cleanup_close_ int fd = open(socket_path, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to open '%s': %m", socket_path); + } else { + _cleanup_free_ DirectoryEntries *dentries = NULL; + r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_MUST_BE_SOCKET, &dentries); + if (r < 0) + return log_error_errno(r, "Failed to enumerate '%s': %m", socket_path); + + FOREACH_ARRAY(dp, dentries->entries, dentries->n_entries) { + struct dirent *de = *dp; + + if (!storage_provider_name_is_valid(de->d_name)) + continue; + + _cleanup_close_ int socket_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (socket_fd < 0) + return log_error_errno(errno, "Failed to allocate AF_UNIX/SOCK_STREAM socket: %m"); + + _cleanup_free_ char *no = NULL; + r = connect_unix_path(socket_fd, fd, de->d_name); + if (r < 0) { + no = strjoin("no (", ERRNO_NAME(r), ")"); + if (!no) + return log_oom(); + } + + r = table_add_many(t, + TABLE_STRING, de->d_name, + TABLE_STRING, no ?: "yes", + TABLE_SET_COLOR, ansi_highlight_green_red(!no)); + if (r < 0) + return table_log_add_error(r); + } + } + + if (!table_isempty(t)) { + r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); + if (r < 0) + return r; + } + + if (arg_legend && FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) { + if (table_isempty(t)) + printf("No providers.\n"); + else + printf("\n%zu providers listed.\n", table_get_rows(t) - 1); + } + + return 0; +} + +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; + + assert(argc >= 0); + assert(argv); + assert(remaining_args); + + OptionParser opts = { argc, argv, .namespace = "storagectl" }; + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_NAMESPACE("storagectl"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + OPTION_COMMON_NO_LEGEND: + arg_legend = false; + break; + + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); + if (r <= 0) + return r; + break; + + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + OPTION_COMMON_SYSTEM: + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + OPTION_COMMON_USER: + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + } + + *remaining_args = option_parser_get_args(&opts); + return 1; +} + +static int run_as_mount_helper(int argc, char *argv[]) { + int r; + + /* Implements util-linux "external helper" command line interface, as per mount(8) man page. + * + * Usage: + * + * mount -t storage fs:mydirvolume /some/place # Directory volumes + * mount -t storage.ext4 fs:myblkvolume /some/place # Block volumes + */ + + const char *fstype = NULL, *options = NULL; + bool fake = false; + + OptionParser opts = { argc, argv, .namespace = "mount.storage" }; + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_NAMESPACE("mount.storage"): {} + + OPTION_SHORT('f', NULL, /* help= */ NULL): + fake = true; + break; + + OPTION_SHORT('o', "OPTIONS", /* help= */ NULL): + options = opts.arg; + break; + + OPTION_SHORT('t', "FSTYPE", /* help= */ NULL): + fstype = startswith(opts.arg, "storage."); + if (fstype) { + /* Paranoia: don't allow "storage.storage.storage.…" chains... */ + if (startswith(fstype, "storage.") || streq(fstype, "storage")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing nested storage volumes."); + } else if (!streq(opts.arg, "storage")) + log_warning("Unexpected file system type '%s', ignoring.", opts.arg); + + break; + + OPTION_SHORT('s', NULL, /* help= */ NULL): {} /* sloppy mount options */ + OPTION_SHORT('n', NULL, /* help= */ NULL): {} /* aka --no-mtab */ + OPTION_SHORT('v', NULL, /* help= */ NULL): /* aka --verbose */ + log_debug("Ignoring option -%c, not implemented.", opts.opt->short_code); + break; + + OPTION_SHORT('N', "NS", /* help= */ NULL): /* aka --namespace= */ + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Option -%c is not implemented, refusing.", + opts.opt->short_code); + } + + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + + if (n_args != 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Expected a storage volume specification and target directory as only arguments."); + + const char *colon = strchr(args[0], ':'); + if (!colon) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid storage volume specification, refusing: %s", args[0]); + + _cleanup_free_ char *provider = strndup(args[0], colon - args[0]); + if (!provider) + return log_oom(); + if (!storage_provider_name_is_valid(provider)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid storage provider name: %s", provider); + + _cleanup_free_ char *name = strdup(colon + 1); + if (!name) + return log_oom(); + if (!storage_volume_name_is_valid(name)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid storage volume name: %s", name); + + _cleanup_free_ char *path = NULL; + r = parse_path_argument(args[1], /* suppress_root= */ false, &path); + if (r < 0) + return r; + + _cleanup_free_ char *filtered = NULL, *template = NULL; + CreateMode create_mode = _CREATE_MODE_INVALID; + uint64_t create_size = UINT64_MAX; + ReadOnlyMode read_only = _READ_ONLY_INVALID; + for (const char *p = options;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, ",", EXTRACT_KEEP_QUOTE|EXTRACT_UNESCAPE_SEPARATORS); + if (r < 0) + return log_error_errno(r, "Failed to extract mount option: %m"); + if (r == 0) + break; + + const char *t = startswith(word, "storage."); + if (t) { + const char *v; + if ((v = startswith(t, "create="))) { + create_mode = create_mode_from_string(v); + if (create_mode < 0) + return log_error_errno(create_mode, "Failed to parse storage.create= parameter: %s", v); + } else if ((v = startswith(t, "create-size="))) { + r = parse_size(v, /* base= */ 1024, &create_size); + if (r < 0) + return log_error_errno(r, "Failed to parse storage.create-size= parameter: %s", v); + } else if ((v = startswith(t, "template="))) { + if (!storage_template_name_is_valid(v)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid template name, refusing: %s", v); + + r = free_and_strdup(&template, v); + if (r < 0) + return log_oom(); + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown mount option '%s', refusing.", word); + } else if (streq(word, "ro")) + read_only = READ_ONLY_YES; + else if (streq(word, "rw")) + read_only = READ_ONLY_NO; + else if (!strextend_with_separator(&filtered, ",", word)) + return log_oom(); + } + + if (fake) + return 0; + + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + + VolumeType requested_type = fstype ? VOLUME_REG : VOLUME_DIR; + + BindVolume bv = BIND_VOLUME_INIT; + bv.provider = provider; + bv.volume = name; + bv.create_mode = create_mode; + bv.template = template; + bv.read_only = read_only; + bv.request_as = requested_type; + bv.create_size_bytes = create_size; + + _cleanup_(storage_acquire_reply_done) StorageAcquireReply reply = STORAGE_ACQUIRE_REPLY_INIT; + _cleanup_free_ char *acquire_error_id = NULL; + r = storage_acquire_volume(arg_runtime_scope, &bv, arg_ask_password, &acquire_error_id, &reply); + if (r < 0 && fstype && + STR_IN_SET(strna(acquire_error_id), + "io.systemd.StorageProvider.TypeNotSupported", + "io.systemd.StorageProvider.WrongType")) { + _cleanup_(storage_acquire_reply_done) StorageAcquireReply retry = STORAGE_ACQUIRE_REPLY_INIT; + assert(bv.request_as == VOLUME_REG); + bv.request_as = VOLUME_BLK; + int k = storage_acquire_volume(arg_runtime_scope, &bv, arg_ask_password, /* reterr_error_id= */ NULL, &retry); + if (k >= 0) { + storage_acquire_reply_done(&reply); + reply = retry; + retry = STORAGE_ACQUIRE_REPLY_INIT; + acquire_error_id = mfree(acquire_error_id); + requested_type = VOLUME_BLK; + r = 0; + } + } + + if (r < 0) { + const char *eid = acquire_error_id; + + if (streq_ptr(eid, "io.systemd.StorageProvider.NoSuchVolume")) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Volume '%s' not known.", name); + if (streq_ptr(eid, "io.systemd.StorageProvider.NoSuchTemplate")) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Template '%s' not known.", template); + if (streq_ptr(eid, "io.systemd.StorageProvider.VolumeExists")) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Volume '%s' exists already.", name); + if (streq_ptr(eid, "io.systemd.StorageProvider.TypeNotSupported")) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Storage provider does not support the specified volume type '%s'.", volume_type_to_string(requested_type)); + if (streq_ptr(eid, "io.systemd.StorageProvider.WrongType")) + return log_error_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "Volume '%s' is not of type '%s'.", name, volume_type_to_string(requested_type)); + if (streq_ptr(eid, "io.systemd.StorageProvider.CreateNotSupported")) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Storage provider does not support creating volumes."); + if (streq_ptr(eid, "io.systemd.StorageProvider.CreateSizeRequired")) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Storage provider requires a create size to be provided when creating volumes on-the-fly. Use 'storage.create-size=' mount option."); + if (streq_ptr(eid, "io.systemd.StorageProvider.ReadOnlyVolume")) + return log_error_errno(SYNTHETIC_ERRNO(EROFS), "Volume '%s' is read-only.", name); + if (streq_ptr(eid, "io.systemd.StorageProvider.BadTemplate")) + return log_error_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "Template does not apply to this volume type."); + + if (eid) + return log_error_errno(r, "Failed to issue io.systemd.StorageProvider.Acquire() varlink call (%s): %m", eid); + return log_error_errno(r, "Failed to issue io.systemd.StorageProvider.Acquire() varlink call: %m"); + } + + struct stat st; + if (fstat(reply.fd, &st) < 0) + return log_error_errno(errno, "Failed to stat returned file descriptor: %m"); + + _cleanup_strv_free_ char **cmdline = strv_new("mount", "-c"); + if (!cmdline) + return log_oom(); + + if (fstype) { + if (!IN_SET(reply.type, VOLUME_REG, VOLUME_BLK)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Mounting as file system type '%s' requested, but volume is not a block device or regular file.", fstype); + + r = stat_verify_regular_or_block(&st); + if (r < 0) + return log_error_errno(r, "File descriptor for block/regular volume is not a block or regular inode: %m"); + + if (strv_extend_strv(&cmdline, STRV_MAKE("-t", fstype), /* filter_duplicates= */ false) < 0) + return log_oom(); + } else { + if (reply.type != VOLUME_DIR) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Mount as directory requested, but volume is not a directory."); + + if (!uid_is_valid(reply.base_uid) || !gid_is_valid(reply.base_gid)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provider did not report base UID/GID, cannot mount."); + + if (reply.base_uid > UINT32_MAX - USERNS_RANGE_SIZE || + reply.base_gid > UINT32_MAX - USERNS_RANGE_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Returned base UID/GID out of range."); + + r = stat_verify_directory(&st); + if (r < 0) + return log_error_errno(r, "File descriptor for directory volume is not a directory inode: %m"); + + if (st.st_uid < reply.base_uid || st.st_uid >= reply.base_uid + USERNS_RANGE_SIZE || + st.st_gid < reply.base_gid || st.st_gid >= reply.base_gid + USERNS_RANGE_SIZE) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), "File descriptor for directory volume is not owned by base UID/GID range, refusing."); + + /* Now move the mount into our own UID/GID range */ + _cleanup_free_ char *uid_line = asprintf_safe( + UID_FMT " " UID_FMT " " UID_FMT "\n", + reply.base_uid, (uid_t) 0, USERNS_RANGE_SIZE); + _cleanup_free_ char *gid_line = asprintf_safe( + GID_FMT " " GID_FMT " " GID_FMT "\n", + reply.base_gid, (gid_t) 0, USERNS_RANGE_SIZE); + if (!uid_line || !gid_line) + return log_oom(); + + _cleanup_close_ int userns_fd = userns_acquire(uid_line, gid_line, /* setgroups_deny= */ true); + if (userns_fd < 0) + return log_error_errno(userns_fd, "Failed to acquire new user namespace: %m"); + + _cleanup_close_ int remapped_fd = open_tree_attr_with_fallback( + reply.fd, + /* path= */ NULL, + OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC, + &(struct mount_attr) { + .attr_set = MOUNT_ATTR_IDMAP, + .userns_fd = userns_fd, + }); + if (remapped_fd < 0) + return log_error_errno(remapped_fd, "Failed to set ID mapping on returned mount: %m"); + + close_and_replace(reply.fd, remapped_fd); + + if (strv_extend(&cmdline, "--bind") < 0) + return log_oom(); + } + + if (reply.read_only > 0) + read_only = READ_ONLY_YES; + + if (!strextend_with_separator(&filtered, ",", read_only == READ_ONLY_YES ? "ro" : "rw")) + return log_oom(); + + if (strv_extend_strv(&cmdline, STRV_MAKE("-o", filtered), /* filter_duplicates= */ false) < 0) + return log_oom(); + + if (strv_extend_strv(&cmdline, STRV_MAKE(FORMAT_PROC_FD_PATH(reply.fd), path), /* filter_duplicates= */ false) < 0) + return log_oom(); + + r = fd_cloexec(reply.fd, false); + if (r < 0) + return log_error_errno(r, "Failed to disable O_CLOEXEC for mount fd: %m"); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *q = quote_command_line(cmdline, SHELL_ESCAPE_EMPTY); + log_debug("Chain-loading: %s", strna(q)); + } + + /* NB: we do not honour $PATH here, since as plugin to /bin/mount we might be called in a setuid() + * context, and hence don't want to chain to programs potentially under user control. */ + execv("/bin/mount", cmdline); + return log_error_errno(errno, "Failed to execute mount tool: %m"); +} + +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + + if (invoked_as(argv, "mount.storage")) + return run_as_mount_helper(argc, argv); + + char **args = NULL; + r = parse_argv(argc, argv, &args); + if (r <= 0) + return r; + + return dispatch_verb(args, /* userdata= */ NULL); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/storagetm/storagetm.c b/src/storagetm/storagetm.c index c6caaa1260ba3..2d59ab1408c9d 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,85 @@ 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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "NQN invalid: %s", opts.arg); - if (free_and_strdup(&arg_nqn, optarg) < 0) + if (free_and_strdup(&arg_nqn, opts.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(&opts); 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) { @@ -271,6 +258,8 @@ static int nvme_subsystem_write_metadata(int subsystem_fd, sd_device *device) { } if (w) { _cleanup_free_ char *truncated = strndup(w, 40); /* kernel refuses more than 40 chars (as per nvme spec) */ + if (!truncated) + return log_oom(); /* The default string stored in 'attr_model' is "Linux" btw. */ r = write_string_file_at(subsystem_fd, "attr_model", truncated, WRITE_STRING_FILE_DISABLE_BUFFER); @@ -746,6 +735,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..e124b56fc9c12 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,109 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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(&opts); + + 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 +428,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 +437,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..4cc98836281b1 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -11,13 +10,17 @@ #include "sd-json.h" #include "sd-varlink.h" +#include "ansi-color.h" #include "argv-util.h" #include "blkid-util.h" #include "blockdev-util.h" #include "build.h" +#include "bus-common-errors.h" +#include "bus-locator.h" #include "bus-polkit.h" #include "bus-unit-util.h" #include "bus-util.h" +#include "bus-wait-for-jobs.h" #include "capability-util.h" #include "chase.h" #include "conf-parser.h" @@ -34,6 +37,7 @@ #include "format-table.h" #include "fs-util.h" #include "hashmap.h" +#include "help-util.h" #include "image-policy.h" #include "initrd-util.h" #include "label-util.h" /* IWYU pragma: keep */ @@ -44,23 +48,27 @@ #include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" +#include "options.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #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" #include "selinux-util.h" +#include "set.h" #include "sort-util.h" #include "stat-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" #include "time-util.h" +#include "unit-name.h" +#include "varlink-io.systemd.SysUpdate.Notify.h" #include "varlink-io.systemd.sysext.h" #include "varlink-util.h" #include "verbs.h" @@ -287,16 +295,60 @@ static int is_our_mount_point( return true; } -static int need_reload( +static int split_unit_string(const char *s, const char *field, const char *extension, Set **units) { + _cleanup_strv_free_ char **split = NULL; + int r; + + assert(field); + assert(extension); + assert(units); + + if (isempty(s)) + return 0; + + split = strv_split(s, /* separators= whitespace */ NULL); + if (!split) + return log_oom(); + + STRV_FOREACH(u, split) { + if (!unit_name_is_valid(*u, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) { + log_warning("Invalid unit name '%s' in %s= of %s, ignoring.", *u, field, extension); + continue; + } + + r = set_put_strdup(units, *u); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int get_extension_release_metadata( ImageClass image_class, char **hierarchies, - bool no_reload) { - - /* Parse the mounted images to find out if we need to reload the daemon. */ + bool no_reload, + bool *ret_need_reload, + Set **ret_restart_units, + Set **ret_reload_or_restart_units) { + + /* Parse the mounted images to find out if we need to reload the daemon, and which units to + * restart or reload-or-restart afterwards. */ + _cleanup_set_free_ Set *restart_units = NULL, *reload_or_restart_units = NULL; + bool need_to_reload = false; int r; - if (no_reload) - return false; + if (no_reload || !isempty(arg_root)) { + /* With --root= we'd be talking to the host service manager about extensions merged + * into a foreign root, which is never what we want. */ + if (ret_need_reload) + *ret_need_reload = false; + if (ret_restart_units) + *ret_restart_units = NULL; + if (ret_reload_or_restart_units) + *ret_reload_or_restart_units = NULL; + return 0; + } STRV_FOREACH(p, hierarchies) { _cleanup_free_ char *f = NULL, *buf = NULL, *resolved = NULL; @@ -332,8 +384,7 @@ static int need_reload( STRV_FOREACH(extension, mounted_extensions) { _cleanup_strv_free_ char **extension_release = NULL; - const char *extension_reload_manager = NULL; - int b; + const char *value; r = load_extension_release_pairs(arg_root, image_class, *extension, /* relax_extension_release_check= */ true, &extension_release); if (r < 0) { @@ -341,23 +392,50 @@ static int need_reload( continue; } - extension_reload_manager = strv_env_pairs_get(extension_release, "EXTENSION_RELOAD_MANAGER"); - if (isempty(extension_reload_manager)) - continue; + value = strv_env_pairs_get(extension_release, "EXTENSION_RELOAD_MANAGER"); + if (!isempty(value)) { + int b = parse_boolean(value); + if (b < 0) + log_warning_errno(b, "Failed to parse EXTENSION_RELOAD_MANAGER= of %s, ignoring: %m", *extension); + else if (b) + need_to_reload = true; + } - b = parse_boolean(extension_reload_manager); - if (b < 0) { - log_warning_errno(b, "Failed to parse the extension metadata to know if the manager needs to be reloaded, ignoring: %m"); - continue; + if (ret_restart_units) { + value = strv_env_pairs_get(extension_release, "EXTENSION_RESTART_UNITS"); + r = split_unit_string(value, "EXTENSION_RESTART_UNITS", *extension, &restart_units); + if (r < 0) + return r; + if (!isempty(value)) + /* Listing units to restart implies a manager reload because a unit + * shipped (and later removed) by the extension would otherwise not + * be visible to the manager when RestartUnit/StopUnit is issued. */ + need_to_reload = true; } - if (b) - /* If at least one extension wants a reload, we reload. */ - return true; + if (ret_reload_or_restart_units) { + value = strv_env_pairs_get(extension_release, "EXTENSION_RELOAD_OR_RESTART_UNITS"); + r = split_unit_string(value, "EXTENSION_RELOAD_OR_RESTART_UNITS", *extension, &reload_or_restart_units); + if (r < 0) + return r; + if (!isempty(value)) + need_to_reload = true; + } } } - return false; + /* If a unit is listed in both, restart takes precedence over reload-or-restart. */ + const char *u; + SET_FOREACH(u, restart_units) + free(set_remove(reload_or_restart_units, u)); + + if (ret_need_reload) + *ret_need_reload = need_to_reload; + if (ret_restart_units) + *ret_restart_units = TAKE_PTR(restart_units); + if (ret_reload_or_restart_units) + *ret_reload_or_restart_units = TAKE_PTR(reload_or_restart_units); + return 0; } static int move_submounts(const char *src, const char *dst) { @@ -423,351 +501,75 @@ static int daemon_reload(void) { return bus_service_manager_reload(bus); } -static int unmerge_hierarchy(ImageClass image_class, const char *p, const char *submounts_path) { - - _cleanup_free_ char *dot_dir = NULL, *work_dir_info_file = NULL; - int n_unmerged = 0; +static int dispatch_unit_method(sd_bus *bus, BusWaitForJobs *w, Set *units, const char *method) { + const char *unit; int r; - assert(p); - - dot_dir = path_join(p, image_class_info[image_class].dot_directory_name); - if (!dot_dir) - return log_oom(); - - work_dir_info_file = path_join(dot_dir, "work_dir"); - if (!work_dir_info_file) - return log_oom(); - - for (;;) { - _cleanup_free_ char *escaped_work_dir_in_root = NULL, *work_dir = NULL; - - /* We only unmount /usr/ if it is a mount point and really one of ours, in order not to break - * systems where /usr/ is a mount point of its own already. */ - - r = is_our_mount_point(image_class, p); - if (r < 0) - return r; - if (r == 0) - break; - - r = read_one_line_file(work_dir_info_file, &escaped_work_dir_in_root); - if (r < 0) { - if (r != -ENOENT) - return log_error_errno(r, "Failed to read '%s': %m", work_dir_info_file); - } else { - _cleanup_free_ char *work_dir_in_root = NULL; - ssize_t l; - - l = cunescape_length(escaped_work_dir_in_root, r, 0, &work_dir_in_root); - if (l < 0) - return log_error_errno(l, "Failed to unescape work directory path: %m"); - work_dir = path_join(arg_root, work_dir_in_root); - if (!work_dir) - return log_oom(); - } - - r = umount_verbose(LOG_DEBUG, dot_dir, MNT_DETACH|UMOUNT_NOFOLLOW); - if (r < 0) { - /* EINVAL is possibly "not a mount point". Let it slide as it's expected to occur if - * the whole hierarchy was read-only, so the dot directory inside it was not - * bind-mounted as read-only. */ - if (r != -EINVAL) - return log_error_errno(r, "Failed to unmount '%s': %m", dot_dir); - } - - /* After we've unmounted the metadata directory, save all other submounts so we can restore - * them after unmerging the hierarchy. */ - r = move_submounts(p, submounts_path); - if (r < 0) - return r; - - r = umount_verbose(LOG_ERR, p, MNT_DETACH|UMOUNT_NOFOLLOW); - if (r < 0) - return r; - - if (work_dir) { - r = rm_rf(work_dir, REMOVE_ROOT | REMOVE_MISSING_OK | REMOVE_PHYSICAL); - if (r < 0) - return log_error_errno(r, "Failed to remove '%s': %m", work_dir); - } - - log_info("Unmerged '%s'.", p); - n_unmerged++; - } - - return n_unmerged; -} - -static int unmerge_subprocess( - ImageClass image_class, - char **hierarchies, - const char *workspace) { - - int r, ret = 0; - - assert(workspace); - assert(path_startswith(workspace, "/run/")); - - /* Mark the whole of /run as MS_SLAVE, so that we can mount stuff below it that doesn't show up on - * the host otherwise. */ - r = mount_nofollow_verbose(LOG_ERR, NULL, "/run", NULL, MS_SLAVE|MS_REC, NULL); - if (r < 0) - return r; - - /* Let's create the workspace if it's missing */ - r = mkdir_p(workspace, 0700); - if (r < 0) - return log_error_errno(r, "Failed to create '%s': %m", workspace); - - STRV_FOREACH(h, hierarchies) { - _cleanup_free_ char *submounts_path = NULL, *resolved = NULL; - - submounts_path = path_join(workspace, "submounts", *h); - if (!submounts_path) - return log_oom(); - - r = chase(*h, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL); - if (r == -ENOENT) { - log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *h); - continue; - } - if (r < 0) { - RET_GATHER(ret, log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *h)); - continue; + assert(bus); + assert(w); + assert(method); + + /* Issue the given method for each unit and wait for the jobs to complete. Fall back to StopUnit + * when the call reports that the unit's file is gone, e.g., on unmerge when the unit was part of + * the extension, because otherwise the running service is leaked. */ + + SET_FOREACH(unit, units) { + _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 *m = method, *job; + + r = bus_call_method(bus, bus_systemd_mgr, m, &error, &reply, "ss", unit, "replace"); + if (r < 0 && sd_bus_error_has_names(&error, BUS_ERROR_NO_SUCH_UNIT, BUS_ERROR_LOAD_FAILED)) { + sd_bus_error_free(&error); + m = "StopUnit"; + r = bus_call_method(bus, bus_systemd_mgr, m, &error, &reply, "ss", unit, "replace"); + if (r < 0 && sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT)) { + log_debug("Unit '%s' is already gone, nothing to stop.", unit); + continue; + } } - - r = unmerge_hierarchy(image_class, resolved, submounts_path); if (r < 0) { - RET_GATHER(ret, r); + log_warning("Failed to %s unit '%s': %s", m, unit, bus_error_message(&error, r)); continue; } - if (r == 0) - continue; - - /* If we unmerged something, then we have to move the submounts from the hierarchy back into - * place in the host's original hierarchy. */ - r = move_submounts(submounts_path, resolved); + r = sd_bus_message_read(reply, "o", &job); if (r < 0) - return r; - } - - return ret; -} - -static int unmerge( - ImageClass image_class, - char **hierarchies, - bool no_reload) { - - bool need_to_reload; - int r; - - (void) dlopen_libmount(); - - r = need_reload(image_class, hierarchies, no_reload); - if (r < 0) - return r; - need_to_reload = r > 0; - - r = pidref_safe_fork( - "(sd-unmerge)", - FORK_WAIT|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_NEW_MOUNTNS, - /* ret= */ NULL); - if (r < 0) - return r; - if (r == 0) { - /* Child with its own mount namespace */ - - r = unmerge_subprocess(image_class, hierarchies, "/run/systemd/sysext"); + return bus_log_parse_error(r); - /* Our namespace ceases to exist here, also implicitly detaching all temporary mounts we - * created below /run. Nice! */ - - _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); - } - - if (need_to_reload) { - r = daemon_reload(); + r = bus_wait_for_jobs_one(w, job, BUS_WAIT_JOBS_LOG_ERROR, /* extra_args= */ NULL); if (r < 0) - return r; + log_warning_errno(r, "Failed to wait for %s job of unit '%s', ignoring: %m", m, unit); } return 0; } -static int verb_unmerge(int argc, char **argv, void *userdata) { - int r; - - r = have_effective_cap(CAP_SYS_ADMIN); - if (r < 0) - return log_error_errno(r, "Failed to check if we have enough privileges: %m"); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged."); - - return unmerge(arg_image_class, - arg_hierarchies, - arg_no_reload); -} - -static int parse_image_class_parameter(sd_varlink *link, const char *value, ImageClass *image_class, char ***hierarchies) { - _cleanup_strv_free_ char **h = NULL; - ImageClass c; +static int restart_units(Set *restart_units, Set *reload_or_restart_units) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; int r; - assert(link); - assert(image_class); - - if (!value) + if (set_isempty(restart_units) && set_isempty(reload_or_restart_units)) return 0; - c = image_class_from_string(value); - if (!IN_SET(c, IMAGE_SYSEXT, IMAGE_CONFEXT)) - return sd_varlink_error_invalid_parameter_name(link, "class"); - - if (hierarchies) { - r = parse_env_extension_hierarchies(&h, image_class_info[c].name_env); - if (r < 0) - return log_error_errno(r, "Failed to parse environment variable: %m"); - - strv_free_and_replace(*hierarchies, h); - } - - *image_class = c; - return 0; -} - -typedef struct MethodUnmergeParameters { - const char *class; - int no_reload; -} MethodUnmergeParameters; - -static int vl_method_unmerge(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - - static const sd_json_dispatch_field dispatch_table[] = { - { "class", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodUnmergeParameters, class), 0 }, - { "noReload", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MethodUnmergeParameters, no_reload), 0 }, - VARLINK_DISPATCH_POLKIT_FIELD, - {} - }; - MethodUnmergeParameters p = { - .no_reload = -1, - }; - Hashmap **polkit_registry = ASSERT_PTR(userdata); - _cleanup_strv_free_ char **hierarchies = NULL; - ImageClass image_class = arg_image_class; - bool no_reload; - int r; - - assert(link); - - r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); - if (r != 0) - return r; - - no_reload = p.no_reload >= 0 ? p.no_reload : arg_no_reload; - - r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies); + r = bus_connect_system_systemd(&bus); if (r < 0) - return r; + return log_error_errno(r, "Failed to get D-Bus connection: %m"); - r = varlink_verify_polkit_async( - link, - /* bus= */ NULL, - image_class_info[image_class].polkit_rw_action_id, - (const char**) STRV_MAKE( - "verb", "unmerge", - "noReload", one_zero(no_reload)), - polkit_registry); - if (r <= 0) - return r; + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Failed to set up job watcher: %m"); - r = unmerge(image_class, hierarchies ?: arg_hierarchies, no_reload); + r = dispatch_unit_method(bus, w, restart_units, "RestartUnit"); if (r < 0) return r; - return sd_varlink_reply(link, NULL); -} - -static int verb_status(int argc, char **argv, void *userdata) { - _cleanup_(table_unrefp) Table *t = NULL; - int r, ret = 0; - - t = table_new("hierarchy", "extensions", "since"); - if (!t) - return log_oom(); - - table_set_ersatz_string(t, TABLE_ERSATZ_DASH); - - STRV_FOREACH(p, arg_hierarchies) { - _cleanup_free_ char *resolved = NULL, *f = NULL, *buf = NULL; - _cleanup_strv_free_ char **l = NULL; - struct stat st; - - r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL); - if (r == -ENOENT) { - log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p); - continue; - } - if (r < 0) { - log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *p); - goto inner_fail; - } - - r = is_our_mount_point(arg_image_class, resolved); - if (r < 0) - goto inner_fail; - if (r == 0) { - r = table_add_many( - t, - TABLE_PATH, *p, - TABLE_STRING, "none", - TABLE_SET_COLOR, ansi_grey(), - TABLE_EMPTY); - if (r < 0) - return table_log_add_error(r); - - continue; - } - - f = path_join(resolved, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural); - if (!f) - return log_oom(); - - r = read_full_file(f, &buf, NULL); - if (r < 0) - return log_error_errno(r, "Failed to open '%s': %m", f); - - l = strv_split_newlines(buf); - if (!l) - return log_oom(); - - if (stat(*p, &st) < 0) - return log_error_errno(errno, "Failed to stat() '%s': %m", *p); - - r = table_add_many( - t, - TABLE_PATH, *p, - TABLE_STRV, l, - TABLE_TIMESTAMP, timespec_load(&st.st_mtim)); - if (r < 0) - return table_log_add_error(r); - - continue; - - inner_fail: - if (ret == 0) - ret = r; - } - - (void) table_set_sort(t, (size_t) 0); - - r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); + r = dispatch_unit_method(bus, w, reload_or_restart_units, "ReloadOrRestartUnit"); if (r < 0) return r; - return ret; + return 0; } static int append_overlayfs_path_option( @@ -959,9 +761,66 @@ static OverlayFSPaths *overlayfs_paths_free(OverlayFSPaths *op) { free(op->work_dir); strv_free(op->lower_dirs); - return mfree(op); + return mfree(op); +} +DEFINE_TRIVIAL_CLEANUP_FUNC(OverlayFSPaths *, overlayfs_paths_free); + +static int parse_env(void) { + const char *env_var; + int r; + + env_var = secure_getenv(image_class_info[arg_image_class].mode_env); + if (env_var) { + r = parse_mutable_mode(env_var); + if (r < 0) + log_warning("Failed to parse %s environment variable value '%s'. Ignoring.", + image_class_info[arg_image_class].mode_env, env_var); + else { + arg_mutable = r; + arg_mutable_set = true; + } + } + + env_var = secure_getenv(image_class_info[arg_image_class].opts_env); + if (env_var) + arg_overlayfs_mount_options = env_var; + + /* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and + * /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline + * switch. */ + r = parse_env_extension_hierarchies(&arg_hierarchies, image_class_info[arg_image_class].name_env); + if (r < 0) + return log_error_errno(r, "Failed to parse %s environment variable: %m", image_class_info[arg_image_class].name_env); + + return 0; +} + +static int parse_image_class_parameter(sd_varlink *link, const char *value, ImageClass *image_class, char ***hierarchies) { + _cleanup_strv_free_ char **h = NULL; + ImageClass c; + int r; + + assert(link); + assert(image_class); + + if (!value) + return 0; + + c = image_class_from_string(value); + if (!IN_SET(c, IMAGE_SYSEXT, IMAGE_CONFEXT)) + return sd_varlink_error_invalid_parameter_name(link, "class"); + + if (hierarchies) { + r = parse_env_extension_hierarchies(&h, image_class_info[c].name_env); + if (r < 0) + return log_error_errno(r, "Failed to parse environment variable: %m"); + + strv_free_and_replace(*hierarchies, h); + } + + *image_class = c; + return 0; } -DEFINE_TRIVIAL_CLEANUP_FUNC(OverlayFSPaths *, overlayfs_paths_free); static int resolve_hierarchy(const char *hierarchy, char **ret_resolved_hierarchy) { _cleanup_free_ char *resolved_path = NULL; @@ -1055,7 +914,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)); @@ -1683,7 +1542,7 @@ static int store_info_in_meta( /* Make sure the top-level dir has an mtime marking the point we established the merge */ if (utimensat(AT_FDCWD, meta_path, NULL, AT_SYMLINK_NOFOLLOW) < 0) - return log_error_errno(r, "Failed to fix mtime of '%s': %m", meta_path); + return log_error_errno(errno, "Failed to fix mtime of '%s': %m", meta_path); return 0; } @@ -1785,11 +1644,14 @@ 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); } -static const ImagePolicy *pick_image_policy(const Image *img) { +static const ImagePolicy* pick_image_policy(const Image *img) { assert(img); assert(img->path); @@ -1821,6 +1683,184 @@ static const ImagePolicy *pick_image_policy(const Image *img) { return image_class_info[img->class].default_image_policy; } +static int unmerge_hierarchy(ImageClass image_class, const char *p, const char *submounts_path) { + _cleanup_free_ char *dot_dir = NULL, *work_dir_info_file = NULL; + int n_unmerged = 0; + int r; + + assert(p); + + dot_dir = path_join(p, image_class_info[image_class].dot_directory_name); + if (!dot_dir) + return log_oom(); + + work_dir_info_file = path_join(dot_dir, "work_dir"); + if (!work_dir_info_file) + return log_oom(); + + for (;;) { + _cleanup_free_ char *escaped_work_dir_in_root = NULL, *work_dir = NULL; + + /* We only unmount /usr/ if it is a mount point and really one of ours, in order not to break + * systems where /usr/ is a mount point of its own already. */ + + r = is_our_mount_point(image_class, p); + if (r < 0) + return r; + if (r == 0) + break; + + r = read_one_line_file(work_dir_info_file, &escaped_work_dir_in_root); + if (r < 0) { + if (r != -ENOENT) + return log_error_errno(r, "Failed to read '%s': %m", work_dir_info_file); + } else { + _cleanup_free_ char *work_dir_in_root = NULL; + ssize_t l; + + l = cunescape_length(escaped_work_dir_in_root, r, 0, &work_dir_in_root); + if (l < 0) + return log_error_errno(l, "Failed to unescape work directory path: %m"); + work_dir = path_join(arg_root, work_dir_in_root); + if (!work_dir) + return log_oom(); + } + + r = umount_verbose(LOG_DEBUG, dot_dir, MNT_DETACH|UMOUNT_NOFOLLOW); + if (r < 0) { + /* EINVAL is possibly "not a mount point". Let it slide as it's expected to occur if + * the whole hierarchy was read-only, so the dot directory inside it was not + * bind-mounted as read-only. */ + if (r != -EINVAL) + return log_error_errno(r, "Failed to unmount '%s': %m", dot_dir); + } + + /* After we've unmounted the metadata directory, save all other submounts so we can restore + * them after unmerging the hierarchy. */ + r = move_submounts(p, submounts_path); + if (r < 0) + return r; + + r = umount_verbose(LOG_ERR, p, MNT_DETACH|UMOUNT_NOFOLLOW); + if (r < 0) + return r; + + if (work_dir) { + r = rm_rf(work_dir, REMOVE_ROOT | REMOVE_MISSING_OK | REMOVE_PHYSICAL); + if (r < 0) + return log_error_errno(r, "Failed to remove '%s': %m", work_dir); + } + + log_info("Unmerged '%s'.", p); + n_unmerged++; + } + + return n_unmerged; +} + +static int unmerge_subprocess( + ImageClass image_class, + char **hierarchies, + const char *workspace) { + + int r, ret = 0; + + assert(workspace); + assert(path_startswith(workspace, "/run/")); + + /* Mark the whole of /run as MS_SLAVE, so that we can mount stuff below it that doesn't show up on + * the host otherwise. */ + r = mount_nofollow_verbose(LOG_ERR, NULL, "/run", NULL, MS_SLAVE|MS_REC, NULL); + if (r < 0) + return r; + + /* Let's create the workspace if it's missing */ + r = mkdir_p(workspace, 0700); + if (r < 0) + return log_error_errno(r, "Failed to create '%s': %m", workspace); + + STRV_FOREACH(h, hierarchies) { + _cleanup_free_ char *submounts_path = NULL, *resolved = NULL; + + submounts_path = path_join(workspace, "submounts", *h); + if (!submounts_path) + return log_oom(); + + r = chase(*h, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL); + if (r == -ENOENT) { + log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *h); + continue; + } + if (r < 0) { + RET_GATHER(ret, log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *h)); + continue; + } + + r = unmerge_hierarchy(image_class, resolved, submounts_path); + if (r < 0) { + RET_GATHER(ret, r); + continue; + } + if (r == 0) + continue; + + /* If we unmerged something, then we have to move the submounts from the hierarchy back into + * place in the host's original hierarchy. */ + + r = move_submounts(submounts_path, resolved); + if (r < 0) + return r; + } + + return ret; +} + +static int unmerge( + ImageClass image_class, + char **hierarchies, + bool no_reload) { + + _cleanup_set_free_ Set *units_to_restart = NULL, *units_to_reload_or_restart = NULL; + bool need_to_reload; + int r; + + (void) DLOPEN_LIBMOUNT(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + + r = get_extension_release_metadata(image_class, hierarchies, no_reload, &need_to_reload, &units_to_restart, &units_to_reload_or_restart); + if (r < 0) + return r; + + r = pidref_safe_fork( + "(sd-unmerge)", + FORK_WAIT|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_NEW_MOUNTNS, + /* ret= */ NULL); + if (r < 0) + return r; + if (r == 0) { + /* Child with its own mount namespace */ + + r = unmerge_subprocess(image_class, hierarchies, "/run/systemd/sysext"); + + /* Our namespace ceases to exist here, also implicitly detaching all temporary mounts we + * created below /run. Nice! */ + + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); + } + + if (need_to_reload) { + r = daemon_reload(); + if (r < 0) + return r; + } + + /* The sets will be empty when opted out */ + r = restart_units(units_to_restart, units_to_reload_or_restart); + if (r < 0) + return r; + + return 0; +} + static int merge_subprocess( ImageClass image_class, char **hierarchies, @@ -2367,54 +2407,142 @@ static int merge(ImageClass image_class, int noexec, Hashmap *images) { - int r; + int r; + + (void) DLOPEN_CRYPTSETUP(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + (void) DLOPEN_LIBBLKID(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + (void) DLOPEN_LIBMOUNT(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = pidref_safe_fork("(sd-merge)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_NEW_MOUNTNS, &pidref); + if (r < 0) + return log_error_errno(r, "Failed to fork off child: %m"); + if (r == 0) { + /* Child with its own mount namespace */ + + r = merge_subprocess(image_class, hierarchies, force, always_refresh, noexec, images, "/run/systemd/sysext"); + + /* Our namespace ceases to exist here, also implicitly detaching all temporary mounts we + * created below /run. Nice! */ + + if (r < 0) + _exit(EXIT_FAILURE); + if (r == MERGE_NOTHING_FOUND) + _exit(MERGE_EXIT_NOTHING_FOUND); + if (r == MERGE_SKIP_REFRESH) + _exit(MERGE_EXIT_SKIP_REFRESH); + + _exit(EXIT_SUCCESS); + } + + r = pidref_wait_for_terminate_and_check("(sd-merge)", &pidref, WAIT_LOG_ABNORMAL); + if (r < 0) + return r; + if (r == MERGE_EXIT_NOTHING_FOUND) + return 0; /* Tell refresh to unmount */ + if (r == MERGE_EXIT_SKIP_REFRESH) + return 1; /* Same return code as below when we have merged new */ + if (r > 0) + return log_error_errno(SYNTHETIC_ERRNO(EPROTO), "Failed to merge hierarchies"); + + _cleanup_set_free_ Set *units_to_restart = NULL, *units_to_reload_or_restart = NULL; + bool need_to_reload; + r = get_extension_release_metadata(image_class, hierarchies, no_reload, &need_to_reload, &units_to_restart, &units_to_reload_or_restart); + if (r < 0) + return r; + if (need_to_reload) { + r = daemon_reload(); + if (r < 0) + return r; + } + + /* The sets will be empty when opted out */ + r = restart_units(units_to_restart, units_to_reload_or_restart); + if (r < 0) + return r; + + return 1; +} + +VERB_DEFAULT_NOARG(verb_status, "status", "Show current merge status (default)"); +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(table_unrefp) Table *t = NULL; + int r, ret = 0; + + t = table_new("hierarchy", "extensions", "since"); + if (!t) + return log_oom(); + + table_set_ersatz_string(t, TABLE_ERSATZ_DASH); + + STRV_FOREACH(p, arg_hierarchies) { + _cleanup_free_ char *resolved = NULL, *f = NULL, *buf = NULL; + _cleanup_strv_free_ char **l = NULL; + struct stat st; + + r = chase(*p, arg_root, CHASE_PREFIX_ROOT, &resolved, NULL); + if (r == -ENOENT) { + log_debug_errno(r, "Hierarchy '%s%s' does not exist, ignoring.", strempty(arg_root), *p); + continue; + } + if (r < 0) { + log_error_errno(r, "Failed to resolve path to hierarchy '%s%s': %m", strempty(arg_root), *p); + goto inner_fail; + } + + r = is_our_mount_point(arg_image_class, resolved); + if (r < 0) + goto inner_fail; + if (r == 0) { + r = table_add_many( + t, + TABLE_PATH, *p, + TABLE_STRING, "none", + TABLE_SET_COLOR, ansi_grey(), + TABLE_EMPTY); + if (r < 0) + return table_log_add_error(r); + + continue; + } - (void) dlopen_cryptsetup(); - (void) dlopen_libblkid(); - (void) dlopen_libmount(); + f = path_join(resolved, image_class_info[arg_image_class].dot_directory_name, image_class_info[arg_image_class].short_identifier_plural); + if (!f) + return log_oom(); - _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - r = pidref_safe_fork("(sd-merge)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_NEW_MOUNTNS, &pidref); - if (r < 0) - return log_error_errno(r, "Failed to fork off child: %m"); - if (r == 0) { - /* Child with its own mount namespace */ + r = read_full_file(f, &buf, NULL); + if (r < 0) + return log_error_errno(r, "Failed to open '%s': %m", f); - r = merge_subprocess(image_class, hierarchies, force, always_refresh, noexec, images, "/run/systemd/sysext"); + l = strv_split_newlines(buf); + if (!l) + return log_oom(); - /* Our namespace ceases to exist here, also implicitly detaching all temporary mounts we - * created below /run. Nice! */ + if (stat(*p, &st) < 0) + return log_error_errno(errno, "Failed to stat() '%s': %m", *p); + r = table_add_many( + t, + TABLE_PATH, *p, + TABLE_STRV, l, + TABLE_TIMESTAMP, timespec_load(&st.st_mtim)); if (r < 0) - _exit(EXIT_FAILURE); - if (r == MERGE_NOTHING_FOUND) - _exit(MERGE_EXIT_NOTHING_FOUND); - if (r == MERGE_SKIP_REFRESH) - _exit(MERGE_EXIT_SKIP_REFRESH); + return table_log_add_error(r); - _exit(EXIT_SUCCESS); + continue; + + inner_fail: + if (ret == 0) + ret = r; } - r = pidref_wait_for_terminate_and_check("(sd-merge)", &pidref, WAIT_LOG_ABNORMAL); - if (r < 0) - return r; - if (r == MERGE_EXIT_NOTHING_FOUND) - return 0; /* Tell refresh to unmount */ - if (r == MERGE_EXIT_SKIP_REFRESH) - return 1; /* Same return code as below when we have merged new */ - if (r > 0) - return log_error_errno(SYNTHETIC_ERRNO(EPROTO), "Failed to merge hierarchies"); + (void) table_set_sort(t, (size_t) 0); - r = need_reload(image_class, hierarchies, no_reload); + r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); if (r < 0) return r; - if (r > 0) { - r = daemon_reload(); - if (r < 0) - return r; - } - return 1; + return ret; } static int image_discover_and_read_metadata(ImageClass image_class, Hashmap **ret_images) { @@ -2434,8 +2562,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 +2601,8 @@ static int look_for_merged_hierarchies( return 0; } -static int verb_merge(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_merge, "merge", "Merge extensions into relevant hierarchies"); +static int verb_merge(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_hashmap_free_ Hashmap *images = NULL; const char *which; int r; @@ -2594,6 +2722,73 @@ static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_var return sd_varlink_reply(link, NULL); } +VERB_NOARG(verb_unmerge, "unmerge", "Unmerge extensions from relevant hierarchies"); +static int verb_unmerge(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + r = have_effective_cap(CAP_SYS_ADMIN); + if (r < 0) + return log_error_errno(r, "Failed to check if we have enough privileges: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged."); + + return unmerge(arg_image_class, + arg_hierarchies, + arg_no_reload); +} + +typedef struct MethodUnmergeParameters { + const char *class; + int no_reload; +} MethodUnmergeParameters; + +static int vl_method_unmerge(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + + static const sd_json_dispatch_field dispatch_table[] = { + { "class", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodUnmergeParameters, class), 0 }, + { "noReload", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MethodUnmergeParameters, no_reload), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + MethodUnmergeParameters p = { + .no_reload = -1, + }; + Hashmap **polkit_registry = ASSERT_PTR(userdata); + _cleanup_strv_free_ char **hierarchies = NULL; + ImageClass image_class = arg_image_class; + bool no_reload; + int r; + + assert(link); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + no_reload = p.no_reload >= 0 ? p.no_reload : arg_no_reload; + + r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies); + if (r < 0) + return r; + + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + image_class_info[image_class].polkit_rw_action_id, + (const char**) STRV_MAKE( + "verb", "unmerge", + "noReload", one_zero(no_reload)), + polkit_registry); + if (r <= 0) + return r; + + r = unmerge(image_class, hierarchies ?: arg_hierarchies, no_reload); + if (r < 0) + return r; + + return sd_varlink_reply(link, NULL); +} + static int refresh( ImageClass image_class, char **hierarchies, @@ -2637,7 +2832,8 @@ static int refresh( return r; } -static int verb_refresh(int argc, char **argv, void *userdata) { +VERB_NOARG(verb_refresh, "refresh", "Unmerge/merge extensions again"); +static int verb_refresh(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; r = have_effective_cap(CAP_SYS_ADMIN); @@ -2703,7 +2899,44 @@ 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 refresh_class(ImageClass image_class) { + _cleanup_strv_free_ char **hierarchies = NULL; + int r; + + r = parse_env_extension_hierarchies(&hierarchies, image_class_info[image_class].name_env); + if (r < 0) + return log_error_errno(r, "Failed to determine %s hierarchies: %m", image_class_info[image_class].short_identifier); + + return refresh(image_class, hierarchies, arg_force, arg_no_reload, arg_always_refresh, arg_noexec); +} + +static int vl_method_on_completed_update(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + int r; + + assert(link); + + /* Only honour update notifications if they come from root */ + r = varlink_check_privileged_peer(link); + if (r < 0) + return r; + + /* Triggered by systemd-sysupdate after an update completed. We deliberately ignore all parameters + * (we don't even dispatch them). This method is reached through the sysext socket, but a single + * notification refreshes both image classes, so freshly downloaded sysexts and confexts are both + * picked up, equivalent to "systemd-sysext refresh" plus "systemd-confext refresh". We attempt both + * classes and only fail afterwards, so a problem with one does not prevent refreshing the other. */ + + r = 0; + RET_GATHER(r, refresh_class(IMAGE_SYSEXT)); + RET_GATHER(r, refresh_class(IMAGE_CONFEXT)); + if (r < 0) + return r; + + return sd_varlink_reply(link, NULL); +} + +VERB_NOARG(verb_list, "list", "List installed extensions"); +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 +3008,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,127 +3027,92 @@ 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) { - _cleanup_free_ char *link = NULL; +static int help(void) { + _cleanup_(table_unrefp) Table *verbs = NULL, *commands = NULL, *options = NULL; int r; - r = terminal_urlify_man(image_class_info[arg_image_class].full_identifier, "8", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; + + 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, verbs, commands, options); + + help_cmdline("[OPTIONS...] COMMAND"); + help_abstract(image_class_info[arg_image_class].blurb); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + r = table_print_or_warn(commands); + if (r < 0) + return r; - printf("%1$s [OPTIONS...] COMMAND\n" - "\n%5$s%7$s%6$s\n" - "\n%3$sCommands:%4$s\n" - " status Show current merge status (default)\n" - " merge Merge extensions into relevant hierarchies\n" - " unmerge Unmerge extensions from relevant hierarchies\n" - " refresh Unmerge/merge extensions again\n" - " list List installed extensions\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\n%3$sOptions:%4$s\n" - " --mutable=yes|no|auto|import|ephemeral|ephemeral-import|help\n" - " Specify a mutability mode of the merged hierarchy\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --root=PATH Operate relative to root path\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - " --force Ignore version incompatibilities\n" - " --no-reload Do not reload the service manager\n" - " --always-refresh=yes|no\n" - " Do not skip refresh when no changes were found\n" - " --image-policy=POLICY\n" - " Specify disk image dissection policy\n" - " --noexec=BOOL Whether to mount extension overlay with noexec\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal(), - image_class_info[arg_image_class].blurb); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference(image_class_info[arg_image_class].full_identifier, "8"); return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_ROOT, - ARG_JSON, - ARG_FORCE, - ARG_IMAGE_POLICY, - ARG_NOEXEC, - ARG_NO_RELOAD, - ARG_ALWAYS_REFRESH, - ARG_MUTABLE, - }; - - 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 }, - { "root", required_argument, NULL, ARG_ROOT }, - { "json", required_argument, NULL, ARG_JSON }, - { "force", no_argument, NULL, ARG_FORCE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "noexec", required_argument, NULL, ARG_NOEXEC }, - { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, - { "always-refresh", required_argument, NULL, ARG_ALWAYS_REFRESH }, - { "mutable", required_argument, NULL, ARG_MUTABLE }, - {} - }; +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); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - return verb_help(argc, argv, NULL); + OPTION_COMMON_HELP: + return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; - - case ARG_NO_LEGEND: - arg_legend = false; - break; + OPTION_GROUP("Options"): {} - case ARG_ROOT: - r = parse_path_argument(optarg, false, &arg_root); + OPTION_LONG("root", "PATH", "Operate relative to root PATH"): + r = parse_path_argument(opts.arg, false, &arg_root); if (r < 0) return r; /* If --root= is provided, do not reload the service manager */ arg_no_reload = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; + OPTION_LONG("mutable", "MODE", + "Specify a mutability mode (yes, no, auto, import, ephemeral, ephemeral-import, help)"): + if (streq(opts.arg, "help")) { + if (arg_legend) + puts("Known mutability modes:"); - break; + return DUMP_STRING_TABLE(mutable_mode, MutableMode, _MUTABLE_MAX); + } - case ARG_FORCE: - arg_force = true; + r = parse_mutable_mode(opts.arg); + if (r < 0) + return log_error_errno(r, "Failed to parse argument to --mutable=: %s", opts.arg); + arg_mutable = r; + arg_mutable_set = true; 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(opts.arg, &arg_image_policy); if (r < 0) return r; /* When the CLI flag is given we initialize even if NULL @@ -2922,44 +3120,41 @@ static int parse_argv(int argc, char *argv[]) { arg_image_policy_set = true; break; - case ARG_NOEXEC: - r = parse_boolean_argument("--noexec", optarg, NULL); + OPTION_LONG("noexec", "BOOL", "Whether to mount extension overlay with noexec"): + r = parse_boolean_argument("--noexec", opts.arg, NULL); if (r < 0) return r; arg_noexec = r; break; - case ARG_NO_RELOAD: + OPTION_LONG("force", NULL, "Ignore version incompatibilities"): + arg_force = true; + break; + + OPTION_LONG("no-reload", NULL, "Do not reload the service manager"): arg_no_reload = true; break; - case ARG_ALWAYS_REFRESH: - r = parse_boolean_argument("--always-refresh", optarg, &arg_always_refresh); + OPTION_LONG("always-refresh", "BOOL", "Whether to refresh when no changes were found"): + r = parse_boolean_argument("--always-refresh", opts.arg, &arg_always_refresh); if (r < 0) return r; break; - case ARG_MUTABLE: - if (streq(optarg, "help")) { - if (arg_legend) - puts("Known mutability modes:"); - - return DUMP_STRING_TABLE(mutable_mode, MutableMode, _MUTABLE_MAX); - } - - r = parse_mutable_mode(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse argument to --mutable=: %s", optarg); - arg_mutable = r; - arg_mutable_set = true; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case '?': - return -EINVAL; + OPTION_COMMON_NO_LEGEND: + arg_legend = false; + break; - default: - assert_not_reached(); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); + if (r <= 0) + return r; + break; } r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); @@ -2968,55 +3163,12 @@ static int parse_argv(int argc, char *argv[]) { if (r > 0) arg_varlink = true; + *ret_args = option_parser_get_args(&opts); return 1; } -static int parse_env(void) { - const char *env_var; - int r; - - env_var = secure_getenv(image_class_info[arg_image_class].mode_env); - if (env_var) { - r = parse_mutable_mode(env_var); - if (r < 0) - log_warning("Failed to parse %s environment variable value '%s'. Ignoring.", - image_class_info[arg_image_class].mode_env, env_var); - else { - arg_mutable = r; - arg_mutable_set = true; - } - } - - env_var = secure_getenv(image_class_info[arg_image_class].opts_env); - if (env_var) - arg_overlayfs_mount_options = env_var; - - /* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and - * /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline - * switch. */ - r = parse_env_extension_hierarchies(&arg_hierarchies, image_class_info[arg_image_class].name_env); - if (r < 0) - return log_error_errno(r, "Failed to parse %s environment variable: %m", image_class_info[arg_image_class].name_env); - - return 0; -} - -static int sysext_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "merge", VERB_ANY, 1, 0, verb_merge }, - { "unmerge", VERB_ANY, 1, 0, verb_unmerge }, - { "refresh", VERB_ANY, 1, 0, verb_refresh }, - { "list", VERB_ANY, 1, 0, verb_list }, - { "help", VERB_ANY, 1, 0, verb_help }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { + char **args = NULL; int r; log_setup(); @@ -3029,10 +3181,26 @@ static int run(int argc, char *argv[]) { return r; /* Parse command line */ - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); 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. */ @@ -3053,16 +3221,20 @@ static int run(int argc, char *argv[]) { 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_sysext); + r = sd_varlink_server_add_interface_many( + varlink_server, + &vl_interface_io_systemd_sysext, + &vl_interface_io_systemd_SysUpdate_Notify); if (r < 0) - return log_error_errno(r, "Failed to add Varlink interface: %m"); + return log_error_errno(r, "Failed to add Varlink interfaces: %m"); r = sd_varlink_server_bind_method_many( varlink_server, "io.systemd.sysext.Merge", vl_method_merge, "io.systemd.sysext.Unmerge", vl_method_unmerge, "io.systemd.sysext.Refresh", vl_method_refresh, - "io.systemd.sysext.List", vl_method_list); + "io.systemd.sysext.List", vl_method_list, + "io.systemd.SysUpdate.Notify.OnCompletedUpdate", vl_method_on_completed_update); if (r < 0) return log_error_errno(r, "Failed to bind Varlink methods: %m"); @@ -3075,7 +3247,7 @@ static int run(int argc, char *argv[]) { return EXIT_SUCCESS; } - return sysext_main(argc, argv); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/sysinstall/meson.build b/src/sysinstall/meson.build new file mode 100644 index 0000000000000..1d8be6036564b --- /dev/null +++ b/src/sysinstall/meson.build @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +executables += [ + executable_template + { + 'name' : 'systemd-sysinstall', + 'public' : true, + 'conditions' : ['ENABLE_SYSINSTALL'], + 'sources' : files('sysinstall.c'), + }, +] diff --git a/src/sysinstall/sysinstall.c b/src/sysinstall/sysinstall.c new file mode 100644 index 0000000000000..5d684cb0abc65 --- /dev/null +++ b/src/sysinstall/sysinstall.c @@ -0,0 +1,1427 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "ansi-color.h" +#include "blockdev-list.h" +#include "build.h" +#include "build-path.h" +#include "chase.h" +#include "conf-files.h" +#include "constants.h" +#include "efi-loader.h" +#include "efivars.h" +#include "env-file.h" +#include "escape.h" +#include "fd-util.h" +#include "find-esp.h" +#include "format-table.h" +#include "format-util.h" +#include "fs-util.h" +#include "glyph-util.h" +#include "help-util.h" +#include "image-policy.h" +#include "json-util.h" +#include "locale-setup.h" +#include "log.h" +#include "loop-util.h" +#include "machine-credential.h" +#include "main-func.h" +#include "mount-util.h" +#include "options.h" +#include "os-util.h" +#include "parse-argument.h" +#include "parse-util.h" +#include "path-util.h" +#include "prompt-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "varlink-util.h" + +static char *arg_node = NULL; +static bool arg_welcome = true; +static int arg_erase = -1; /* tri-state */ +static bool arg_confirm = true; +static bool arg_summary = true; +static char **arg_definitions = NULL; +static char *arg_kernel_image = NULL; +static bool arg_reboot = false; +static int arg_touch_variables = -1; /* tri-state */ +static MachineCredentialContext arg_credentials = {}; +static bool arg_copy_locale = true; +static bool arg_copy_keymap = true; +static bool arg_copy_timezone = true; +static bool arg_chrome = true; +static bool arg_mute_console = false; + +STATIC_DESTRUCTOR_REGISTER(arg_node, freep); +STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_kernel_image, freep); +STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done); + +static int help(void) { + int r; + + _cleanup_(table_unrefp) Table *options = NULL; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("[OPTIONS...] [DEVICE]"); + help_abstract("Installs the OS to another block device."); + help_section("Options:"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-sysinstall", "8"); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_LONG("welcome", "no", "Disable the welcome text"): + r = parse_boolean_argument("--welcome=", opts.arg, &arg_welcome); + if (r < 0) + return r; + + break; + + OPTION_LONG("erase", "BOOL", "Whether to erase the target disk"): + r = parse_tristate_argument_with_auto("--erase=", opts.arg, &arg_erase); + if (r < 0) + return r; + break; + + OPTION_LONG("confirm", "no", "Disable query for confirmation"): + r = parse_boolean_argument("--confirm=", opts.arg, &arg_confirm); + if (r < 0) + return r; + break; + + OPTION_LONG("summary", "no", "Disable summary before beginning operation"): + r = parse_boolean_argument("--summary=", opts.arg, &arg_summary); + if (r < 0) + return r; + break; + + OPTION_LONG("definitions", "DIR", "Find partition definitions in specified directory"): { + _cleanup_free_ char *path = NULL; + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &path); + if (r < 0) + return r; + if (strv_consume(&arg_definitions, TAKE_PTR(path)) < 0) + return log_oom(); + break; + } + + OPTION_LONG("reboot", "BOOL", "Whether to reboot after installation is complete"): + r = parse_boolean_argument("--reboot=", opts.arg, &arg_reboot); + if (r < 0) + return r; + break; + + OPTION_LONG("variables", "BOOL", "Whether to modify EFI variables"): + r = parse_tristate_argument_with_auto("--variables=", opts.arg, &arg_touch_variables); + if (r < 0) + return r; + break; + + OPTION_LONG("kernel", "IMAGE", "Explicitly pick kernel image to install"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_kernel_image); + if (r < 0) + return r; + break; + + OPTION_LONG("set-credential", "ID:VALUE", "Install a credential with literal value to target system"): + r = machine_credential_set(&arg_credentials, opts.arg); + if (r < 0) + return r; + break; + + OPTION_LONG("load-credential", "ID:PATH", "Load a credential to install to new system from file or AF_UNIX stream socket"): + r = machine_credential_load(&arg_credentials, opts.arg); + if (r < 0) + return r; + + break; + + OPTION_LONG("copy-locale", "no", "Don't copy current locale to target system"): + r = parse_boolean_argument("--copy-locale=", opts.arg, &arg_copy_locale); + if (r < 0) + return r; + break; + + OPTION_LONG("copy-keymap", "no", "Don't copy current keymap to target system"): + r = parse_boolean_argument("--copy-keymap=", opts.arg, &arg_copy_keymap); + if (r < 0) + return r; + break; + + OPTION_LONG("copy-timezone", "no", "Don't copy current timezone to target system"): + r = parse_boolean_argument("--copy-timezone=", opts.arg, &arg_copy_timezone); + if (r < 0) + return r; + break; + + OPTION_LONG("chrome", "no", "Whether to show a color bar at top and bottom of terminal"): + r = parse_boolean_argument("--chrome=", opts.arg, &arg_chrome); + if (r < 0) + return r; + + break; + + OPTION_LONG("mute-console", "BOOL", "Whether to disallow kernel/PID 1 writes to the console while running"): + r = parse_boolean_argument("--mute-console=", opts.arg, &arg_mute_console); + if (r < 0) + return r; + break; + } + + char **args = option_parser_get_args(&opts); + + if (strv_length(args) > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments."); + if (!strv_isempty(args)) { + arg_node = strdup(args[0]); + if (!arg_node) + return log_oom(); + } + + return 1; +} + +static int print_welcome(sd_varlink **mute_console_link) { + _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL, *fancy_name = NULL; + const char *pn, *ac; + int r; + + assert(mute_console_link); + + if (!*mute_console_link && arg_mute_console) + (void) mute_console(mute_console_link); + + if (!arg_welcome) + return 0; + + r = parse_os_release( + /* root= */ NULL, + "PRETTY_NAME", &pretty_name, + "FANCY_NAME", &fancy_name, + "NAME", &os_name, + "ANSI_COLOR", &ansi_color); + if (r < 0) + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to read os-release file, ignoring: %m"); + + pn = os_release_pretty_name(pretty_name, os_name); + ac = isempty(ansi_color) ? "0" : ansi_color; + + if (use_fancy_name(unescape_fancy_name(&fancy_name))) + printf(ANSI_HIGHLIGHT "Welcome to the " ANSI_NORMAL "%s" ANSI_HIGHLIGHT " Installer!" ANSI_NORMAL "\n", fancy_name); + else if (colors_enabled()) + printf(ANSI_HIGHLIGHT "Welcome to the " ANSI_NORMAL "\x1B[%sm%s" ANSI_HIGHLIGHT " Installer!" ANSI_NORMAL "\n", ac, pn); + else + printf("Welcome to the %s Installer!\n", pn); + + putchar('\n'); + + return 0; +} + +static int connect_to_repart(sd_varlink **link) { + int r; + + assert(link); + + if (*link) { + /* Reset the time-out to default here, since we are reusing the connection, but might enqueue + * a different operation */ + r = sd_varlink_set_relative_timeout(*link, 0); + if (r < 0) + return r; + + return 0; + } + + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *repart = NULL; + fd = pin_callout_binary("systemd-repart", &repart); + if (fd < 0) + return log_error_errno(fd, "Failed to find systemd-repart binary: %m"); + + r = sd_varlink_connect_exec(link, repart, /* argv= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to connect to systemd-repart: %m"); + + return 1; +} + +static int acquire_device_list( + sd_varlink **link, + char ***ret_menu, + char ***ret_accepted) { + int r; + + r = connect_to_repart(link); + if (r < 0) + return r; + + _cleanup_strv_free_ char **menu = NULL, **accepted = NULL; + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_collectbo( + *link, + "io.systemd.Repart.ListCandidateDevices", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_BOOLEAN("ignoreRoot", true)); + if (r < 0) + return log_error_errno(r, "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %m"); + if (streq_ptr(error_id, "io.systemd.Repart.NoCandidateDevices")) + log_debug("No candidate devices found."); + else if (error_id) { + r = sd_varlink_error_to_errno(error_id, reply); /* If this is a system errno style error, output it with %m */ + if (r != -EBADR) + return log_error_errno(r, "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %m"); + + return log_error_errno(r, "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %s", error_id); + } else { + sd_json_variant *i; + JSON_VARIANT_ARRAY_FOREACH(i, reply) { + _cleanup_(block_device_done) BlockDevice bd = BLOCK_DEVICE_NULL; + + static const sd_json_dispatch_field dispatch_table[] = { + { "node", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(BlockDevice, node), SD_JSON_MANDATORY }, + { "symlinks", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(BlockDevice, symlinks), 0 }, + {} + }; + + r = sd_json_dispatch(i, dispatch_table, SD_JSON_LOG|SD_JSON_ALLOW_EXTENSIONS, &bd); + if (r < 0) + return r; + + if (strv_extend(&accepted, bd.node) < 0) + return log_oom(); + if (strv_extend_strv(&accepted, bd.symlinks, /* filter_duplicates= */ true) < 0) + return log_oom(); + + /* Prefer the by-id and by-loop-ref because they typically contain the strings most + * directly understood by the user */ + const char *n = strv_find_prefix(bd.symlinks, "/dev/disk/by-id/"); + if (!n) + n = strv_find_prefix(bd.symlinks, "/dev/disk/by-loop-ref/"); + if (!n) + n = bd.node; + + if (strv_extend(&menu, n) < 0) + return log_oom(); + } + } + + *ret_menu = TAKE_PTR(menu); + *ret_accepted = TAKE_PTR(accepted); + return 0; +} + +static int device_is_valid(const char *node, void *userdata) { + + if (!path_is_valid(node) || !path_is_absolute(node)) { + log_error("Not a valid absolute file system path, refusing: %s", node); + return false; + } + + struct stat st; + if (stat(node, &st) < 0) { + log_error_errno(errno, "Failed to check if '%s' is a valid block device node: %m", node); + return false; + } + if (!S_ISBLK(st.st_mode)) { + log_error("Path '%s' does not refer to a valid block device node, refusing.", node); + return false; + } + + return true; +} + +static int refresh_devices(char ***ret_menu, char ***ret_accepted, void *userdata) { + sd_varlink **repart_link = ASSERT_PTR(userdata); + + (void) acquire_device_list(repart_link, ret_menu, ret_accepted); + return 0; +} + +static int prompt_block_device(sd_varlink **repart_link, char **ret_node) { + int r; + + putchar('\n'); + + _cleanup_strv_free_ char **menu = NULL, **accepted = NULL; + (void) acquire_device_list(repart_link, &menu, &accepted); + + r = prompt_loop("Please enter target disk device", + GLYPH_COMPUTER_DISK, + /* prefill= */ NULL, + menu, + accepted, + /* ellipsize_percentage= */ 20, + /* n_columns= */ 1, + /* column_width= */ 80, + device_is_valid, + refresh_devices, + /* userdata= */ repart_link, + PROMPT_SHOW_MENU|PROMPT_SHOW_MENU_NOW|PROMPT_MAY_SKIP|PROMPT_HIDE_SKIP_HINT|PROMPT_HIDE_MENU_HINT, + ret_node); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled."); + + return 0; +} + +static int read_space_metrics( + sd_json_variant *v, + uint64_t *min_size, + uint64_t *current_size, + uint64_t *need_free) { + + int r; + + struct { + uint64_t min_size; + uint64_t current_size; + uint64_t need_free; + } p = { + .min_size = UINT64_MAX, + .current_size = UINT64_MAX, + .need_free = UINT64_MAX, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "minimalSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, min_size), 0 }, + { "currentSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, current_size), 0 }, + { "needFreeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, need_free), 0 }, + {} + }; + + r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return r; + + if (min_size) + *min_size = p.min_size; + if (current_size) + *current_size = p.current_size; + if (need_free) + *need_free = p.need_free; + + return 0; +} + +static int invoke_repart( + sd_varlink **link, + const char *node, + bool erase, + bool dry_run, + uint64_t *min_size, /* initialized both on success and error */ + uint64_t *current_size, /* ditto */ + uint64_t *need_free) { /* ditto */ + + int r; + + assert(link); + + /* Note, if dry_run is true, then ENOSPC, E2BIG, EHWPOISON will not be logged about beyond LOG_DEBUG, + * but all other errors will be */ + + r = connect_to_repart(link); + if (r < 0) { + read_space_metrics(/* v= */ NULL, min_size, current_size, need_free); + return r; + } + + if (!dry_run) { + /* Seeding the partitions might be very slow, disable timeout */ + r = sd_varlink_set_relative_timeout(*link, UINT64_MAX); + if (r < 0) + return log_error_errno(r, "Failed to disable IPC timeout: %m"); + } + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = sd_varlink_callbo( + *link, + "io.systemd.Repart.Run", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_STRING("node", node), + SD_JSON_BUILD_PAIR_STRING("empty", erase ? "force" : "allow"), + SD_JSON_BUILD_PAIR_BOOLEAN("dryRun", dry_run), + SD_JSON_BUILD_PAIR_CONDITION(!!arg_definitions, "definitions", SD_JSON_BUILD_STRV(arg_definitions)), + SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsEmpty", true), + SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsFactoryReset", true)); + if (r < 0) { + read_space_metrics(/* v= */ NULL, min_size, current_size, need_free); + return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m"); + } + if (error_id) { + if (streq(error_id, "io.systemd.Repart.InsufficientFreeSpace")) { + (void) read_space_metrics(reply, min_size, current_size, need_free); + return log_full_errno( + dry_run ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(ENOSPC), + "Not enough free space on disk, cannot install."); + } + if (streq(error_id, "io.systemd.Repart.DiskTooSmall")) { + (void) read_space_metrics(reply, min_size, current_size, need_free); + return log_full_errno( + dry_run ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(E2BIG), + "Disk too small for installation, cannot install."); + } + + /* For all other errors reset the metrics */ + read_space_metrics(/* v= */ NULL, min_size, current_size, need_free); + + if (streq(error_id, "io.systemd.Repart.ConflictingDiskLabelPresent")) + return log_full_errno( + dry_run ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(EHWPOISON), + "A conflicting disk label is already present on the target disk, cannot install unless disk is erased."); + + r = sd_varlink_error_to_errno(error_id, reply); /* If this is a system errno style error, output it with %m */ + if (r != -EBADR) + return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m"); + + return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %s", error_id); + } + + (void) read_space_metrics(reply, min_size, current_size, need_free); + + return 0; +} + +static int prompt_erase( + bool can_add, + int *ret_erase) { + int r; + + assert(ret_erase); + + putchar('\n'); + + char **l = can_add ? STRV_MAKE("keep", "erase") : STRV_MAKE("erase"); + + _cleanup_free_ char *reply = NULL; + r = prompt_loop(can_add ? + "Please type 'keep' to install the OS in addition to what the disk already contains, or 'erase' to erase all data on the disk" : + "Please type 'erase' to confirm that all data on the disk shall be erased", + GLYPH_BROOM, + /* prefill= */ NULL, + /* menu= */ l, + /* accepted= */ l, + /* ellipsize_percentage= */ 20, + /* n_columns= */ 2, + /* column_width= */ 40, + /* is_valid= */ NULL, + /* refresh= */ NULL, + /* userdata= */ NULL, + PROMPT_SHOW_MENU|PROMPT_MAY_SKIP|PROMPT_HIDE_MENU_HINT|PROMPT_HIDE_SKIP_HINT, + &reply); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled."); + + if (streq(reply, "erase")) + *ret_erase = true; + else if (streq(reply, "keep")) + *ret_erase = false; + else + assert_not_reached(); + + return 0; +} + +static int prompt_touch_variables(void) { + int r; + + if (arg_touch_variables >= 0) + return 0; + + putchar('\n'); + + char **l = STRV_MAKE("yes", "no"); + + _cleanup_free_ char *reply = NULL; + r = prompt_loop("Type 'yes' to register OS installation in firmware variables of the local system, 'no' otherwise", + GLYPH_ROCKET, + /* prefill= */ "yes", + /* menu= */ l, + /* accepted= */ l, + /* ellipsize_percentage= */ 20, + /* n_columns= */ 2, + /* column_width= */ 40, + /* is_valid= */ NULL, + /* refresh= */ NULL, + /* userdata= */ NULL, + PROMPT_SHOW_MENU|PROMPT_MAY_SKIP|PROMPT_HIDE_MENU_HINT|PROMPT_HIDE_SKIP_HINT, + &reply); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation cancelled."); + + r = parse_boolean(reply); + if (r < 0) + return log_error_errno(r, "Failed to parse reply: %s", reply); + + arg_touch_variables = r; + + return 0; +} + +static int prompt_confirm(void) { + int r; + + if (!arg_confirm) + return 0; + + putchar('\n'); + + bool yes; + r = prompt_loop_yes_no(arg_summary ? "Please type 'yes' to confirm the choices above and begin the installation" : + "Please type 'yes' to begin the installation", + /* def= */ false, + &yes); + if (r < 0) + return r; + if (!yes) + return log_error_errno(SYNTHETIC_ERRNO(ECANCELED), "Installation not confirmed, cancelling."); + + return 0; +} + +static int validate_run(sd_varlink **repart_link, const char *node) { + int r; + + assert(repart_link); + assert(node); + + /* First loop: either with explicitly configured --erase= value, or false. A second loop only if not configured explicitly. */ + bool try_erase = arg_erase > 0, conflicting_disk_label = false; + for (;;) { + uint64_t min_size = UINT64_MAX, current_size = UINT64_MAX, need_free = UINT64_MAX; + r = invoke_repart( + repart_link, + node, + try_erase, + /* dry_run= */ true, + &min_size, + ¤t_size, + &need_free); + if (r == -ENOSPC) { + /* The disk is large enough, but there's not enough unallocated space. Hence proceed, but require erasing */ + if (try_erase || arg_erase >= 0) + return log_error_errno(r, "The selected disk is big enough for the installation but does not have enough free space."); + + log_notice("The selected disk is big enough for the installation but does not have enough free space. Installation will require erasing."); + if (need_free != UINT64_MAX) + log_info("Required free space is %s.", FORMAT_BYTES(need_free)); + + try_erase = true; + } else if (r == -E2BIG) { + /* Won't fit, whatever we do */ + log_error_errno(r, "The selected disk is not large enough for an OS installation."); + if (current_size != UINT64_MAX) + log_info("The size of the selected disk is %s, but a minimal size of %s is required.", + FORMAT_BYTES(current_size), + FORMAT_BYTES(min_size)); + return r; + } else if (r == -EHWPOISON) { + if (try_erase || arg_erase >= 0) + return log_error_errno(r, "The selected disk contains a conflicting disk label, refusing."); + + log_debug("Disk contains a conflicting disk label, checking if we could install the OS after erasing it."); + try_erase = true; + conflicting_disk_label = true; + continue; + } else if (r < 0) + /* invoke_repart() already logged about all other errors */ + return r; + else + /* Nice, we can add the OS to the disk, without erasing anything. */ + log_info("The selected disk has enough free space for an installation of the OS."); + + if (conflicting_disk_label) + log_warning("A conflicting disk label has been found, and must be erased for installation."); + + if (arg_erase < 0) { + r = prompt_erase(/* can_add= */ !try_erase, &arg_erase); + if (r < 0) + return r; + } + + return 0; + } +} + +static int show_summary(void) { + int r; + + if (!arg_summary) + return 0; + + printf("\n" + "%sSummary:%s\n", ansi_underline(), ansi_normal()); + + _cleanup_(table_unrefp) Table *table = table_new_vertical(); + if (!table) + return log_oom(); + + r = table_add_many( + table, + TABLE_FIELD, "Selected Disk", + TABLE_STRING, arg_node, + TABLE_FIELD, "Erase Disk", + TABLE_BOOLEAN, arg_erase, + TABLE_SET_COLOR, arg_erase ? ansi_highlight_red() : NULL, + TABLE_FIELD, "Register in Firmware", + TABLE_BOOLEAN, arg_touch_variables); + if (r < 0) + return table_log_add_error(r); + + static const char * const map[] = { + "firstboot.keymap", "Keyboard Map", + "firstboot.locale", "Locale", + "firstboot.locale-messages", "Locale (Messages)", + "firstboot.timezone", "Timezone", + NULL + }; + + STRV_FOREACH_PAIR(id, text, map) { + MachineCredential *c = machine_credential_find(&arg_credentials, *id); + if (!c) + continue; + + _cleanup_free_ char *escaped = cescape_length(c->data, c->size); + if (!escaped) + return log_oom(); + + r = table_add_many( + table, + TABLE_FIELD, *text, + TABLE_STRING, escaped); + if (r < 0) + return table_log_add_error(r); + } + + unsigned n_extra_credentials = 0; + FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) { + bool covered = false; + + STRV_FOREACH_PAIR(id, text, map) + if (streq(*id, cred->id)) { + covered = true; + break; + } + + if (!covered) + n_extra_credentials++; + } + + if (n_extra_credentials > 0) { + r = table_add_many( + table, + TABLE_FIELD, "Extra Credentials", + TABLE_UINT, n_extra_credentials); + if (r < 0) + return table_log_add_error(r); + } + + r = table_print(table); + if (r < 0) + return r; + + return 0; +} + +static int find_current_kernel( + char **ret_filename, + int *ret_fd) { + + int r; + + sd_id128_t uuid; + r = efi_stub_get_device_part_uuid(&uuid); + if (r == -ENOENT) + return log_error_errno(r, "Cannot find current kernel, no stub partition UUID passed via EFI variables."); + if (r < 0) + return log_error_errno(r, "Unable to determine stub partition UUID: %m"); + + _cleanup_free_ char *image = NULL; + r = efi_get_variable_path(EFI_LOADER_VARIABLE_STR("StubImageIdentifier"), &image); + if (r == -ENOENT) + return log_error_errno(r, "Cannot find current kernel, no stub EFI binary path passed."); + if (r < 0) + return log_error_errno(r, "Unable to determine stub EFI binary path: %m"); + + /* Note: we search for the *host* ESP here (i.e. the one the current EFI paths relate to), not the + * one of the target image */ + + _cleanup_free_ char *partition_path = NULL; + _cleanup_close_ int partition_fd = -EBADF; + sd_id128_t partition_uuid; + r = find_esp_and_warn_full( + /* root= */ NULL, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &partition_path, + &partition_fd, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + &partition_uuid, + /* ret_devid= */ NULL); + if (r < 0 && r != -ENOKEY) + return r; + if (r < 0 || !sd_id128_equal(uuid, partition_uuid)) { + partition_path = mfree(partition_path); + partition_fd = safe_close(partition_fd); + + r = find_xbootldr_and_warn_full( + /* root= */ NULL, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &partition_path, + &partition_fd, + &partition_uuid, + /* ret_devid= */ NULL); + if (r < 0 && r != -ENOKEY) + return r; + + if (r < 0 || !sd_id128_equal(uuid, partition_uuid)) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Unable to find UKI on ESP/XBOOTLDR partitions."); + } + + _cleanup_free_ char *resolved = NULL; + _cleanup_close_ int fd = chase_and_openat( + /* root_fd= */ partition_fd, + /* dir_fd= */ partition_fd, + image, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_REGULAR, + O_RDONLY|O_CLOEXEC, + &resolved); + if (fd < 0) + return log_error_errno(fd, "Failed to find EFI binary '%s' on partition '%s': %m", image, partition_path); + + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(resolved, &fn); + if (r < 0) + return log_error_errno(r, "Failed to extract UKI file name from '%s': %m", resolved); + + if (ret_filename) + *ret_filename = TAKE_PTR(fn); + if (ret_fd) + *ret_fd = TAKE_FD(fd); + + return 0; +} + +static int connect_to_bootctl(sd_varlink **link) { + int r; + + assert(link); + + if (*link) + return 0; + + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *bootctl = NULL; + fd = pin_callout_binary("bootctl", &bootctl); + if (fd < 0) + return log_error_errno(fd, "Failed to find bootctl binary: %m"); + + r = sd_varlink_connect_exec(link, bootctl, /* argv= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to connect to bootctl: %m"); + + r = sd_varlink_set_allow_fd_passing_output(*link, true); + if (r < 0) + return log_error_errno(r, "Failed to enable fd passing to bootctl: %m"); + + return 1; +} + +static int invoke_bootctl_install( + sd_varlink **link, + const char *root_dir, + int root_fd) { + int r; + + assert(link); + assert(root_dir); + assert(root_fd >= 0); + + r = connect_to_bootctl(link); + if (r < 0) + return r; + + int fd_idx = sd_varlink_push_dup_fd(*link, root_fd); + if (fd_idx < 0) + return log_error_errno(fd_idx, "Failed to submit root fd onto Varlink connection: %m"); + + const char *error_id = NULL; + r = varlink_callbo_and_log( + *link, + "io.systemd.BootControl.Install", + /* reply= */ NULL, + &error_id, + SD_JSON_BUILD_PAIR_STRING("operation", "new"), + SD_JSON_BUILD_PAIR_INTEGER("rootFileDescriptor", fd_idx), + SD_JSON_BUILD_PAIR_STRING("rootDirectory", root_dir), + SD_JSON_BUILD_PAIR_BOOLEAN("touchVariables", arg_touch_variables)); + if (r < 0) + return r; + + return 0; +} + +static int invoke_bootctl_link( + sd_varlink **link, + const char *root_dir, + int root_fd, + char **encrypted_credentials) { + int r; + + assert(link); + assert(root_dir); + assert(root_fd >= 0); + + r = connect_to_bootctl(link); + if (r < 0) + return r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + STRV_FOREACH_PAIR(name, value, encrypted_credentials) { + _cleanup_free_ char *j = strjoin(*name, ".cred"); + if (!j) + return log_oom(); + + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_STRING("filename", j), + SD_JSON_BUILD_PAIR_BASE64("data", *value, strlen(*value))); + if (r < 0) + return log_error_errno(r, "Failed to append credential to message: %m"); + } + + int root_fd_idx = sd_varlink_push_dup_fd(*link, root_fd); + if (root_fd_idx < 0) + return log_error_errno(root_fd_idx, "Failed to submit root fd onto Varlink connection: %m"); + + _cleanup_free_ char *kernel_filename = NULL; + _cleanup_close_ int kernel_fd = -EBADF; + if (arg_kernel_image) { + r = path_extract_filename(arg_kernel_image, &kernel_filename); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", arg_kernel_image); + if (r == O_DIRECTORY) + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Kernel path '%s' refers to directory, must be regular file, refusing.", arg_kernel_image); + + kernel_fd = xopenat_full(XAT_FDROOT, arg_kernel_image, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID); + if (kernel_fd < 0) + return log_error_errno(kernel_fd, "Failed to open kernel image '%s': %m", arg_kernel_image); + + } else { + r = find_current_kernel(&kernel_filename, &kernel_fd); + if (r < 0) + return r; + } + + int kernel_fd_idx = sd_varlink_push_dup_fd(*link, kernel_fd); + if (kernel_fd_idx < 0) + return log_error_errno(kernel_fd_idx, "Failed to submit kernel fd onto Varlink connection: %m"); + + const char *error_id = NULL; + r = varlink_callbo_and_log( + *link, + "io.systemd.BootControl.Link", + /* reply= */ NULL, + &error_id, + SD_JSON_BUILD_PAIR_INTEGER("rootFileDescriptor", root_fd_idx), + SD_JSON_BUILD_PAIR_STRING("rootDirectory", root_dir), + JSON_BUILD_PAIR_STRING_NON_EMPTY("kernelFilename", kernel_filename), + SD_JSON_BUILD_PAIR_INTEGER("kernelFileDescriptor", kernel_fd_idx), + SD_JSON_BUILD_PAIR_CONDITION(!!array, "extraFiles", SD_JSON_BUILD_VARIANT(array))); + if (r < 0) + return r; + + return 0; +} + +static int maybe_reboot(void) { + int r; + + if (!arg_reboot) + return 0; + + log_notice("%s%sSystem will reboot now.", + emoji_enabled() ? glyph(GLYPH_CIRCLE_ARROW) : "", emoji_enabled() ? " " : ""); + + if (!any_key_to_proceed()) + return 0; + + log_notice("%s%sInitiating reboot.", + emoji_enabled() ? glyph(GLYPH_CIRCLE_ARROW) : "", emoji_enabled() ? " " : ""); + + _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; + r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.Shutdown"); + if (r < 0) + return log_error_errno(r, "Failed to connect to systemd-logind: %m"); + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = varlink_callbo_and_log( + link, + "io.systemd.Shutdown.Reboot", + &reply, + &error_id); + if (r < 0) + return r; + + return 0; +} + +static int read_credential_locale(void) { + int r; + + if (!arg_copy_locale) + return 0; + + if (machine_credential_find(&arg_credentials, "firstboot.locale") || + machine_credential_find(&arg_credentials, "firstboot.locale-messages")) + return 0; + + /* For the main locale we check the two env vars, and if neither is there, we use LC_NUMERIC, since + * it seems to be one of the most fundamental ones, and is not LC_MESSAGES for which we have a + * separate setting after all */ + const char *l = getenv("LC_ALL") ?: getenv("LANG") ?: setlocale(LC_NUMERIC, NULL); + if (l) { + r = machine_credential_add(&arg_credentials, "firstboot.locale", l, /* size= */ SIZE_MAX); + if (r < 0) + return log_oom(); + } + + const char *m = setlocale(LC_MESSAGES, NULL); + if (m && !streq_ptr(m, l)) { + r = machine_credential_add(&arg_credentials, "firstboot.locale-messages", m, /* size= */ SIZE_MAX); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int read_credential_keymap(void) { + int r; + + if (!arg_copy_keymap) + return 0; + + if (machine_credential_find(&arg_credentials, "firstboot.keymap")) + return 0; + + _cleanup_free_ char *keymap = NULL; + r = parse_env_file( + /* f= */ NULL, + etc_vconsole_conf(), + "KEYMAP", &keymap); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to parse '%s': %m", etc_vconsole_conf()); + + if (!isempty(keymap)) { + r = machine_credential_add(&arg_credentials, "firstboot.keymap", keymap, /* size= */ SIZE_MAX); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int read_credential_timezone(void) { + int r; + + if (!arg_copy_timezone) + return 0; + + if (machine_credential_find(&arg_credentials, "firstboot.timezone")) + return 0; + + _cleanup_free_ char *tz = NULL; + r = get_timezone_prefer_env(&tz); + if (r < 0) + log_warning_errno(r, "Failed to read timezone, skipping timezone propagation: %m"); + else { + r = machine_credential_add(&arg_credentials, "firstboot.timezone", tz, /* size= */ SIZE_MAX); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int read_credentials(void) { + int r; + + r = read_credential_locale(); + if (r < 0) + return r; + + r = read_credential_keymap(); + if (r < 0) + return r; + + r = read_credential_timezone(); + if (r < 0) + return r; + + return 0; +} + +static int connect_to_creds(sd_varlink **link) { + int r; + + assert(link); + + if (*link) + return 0; + + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *creds = NULL; + fd = pin_callout_binary("systemd-creds", &creds); + if (fd < 0) + return log_error_errno(fd, "Failed to find systemd-creds binary: %m"); + + r = sd_varlink_connect_exec(link, creds, /* argv= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to connect to systemd-creds: %m"); + + return 1; +} + +static int encrypt_one_credential(sd_varlink **link, const MachineCredential *input, char ***encrypted) { + int r; + + assert(link); + assert(input); + assert(encrypted); + + log_info("Encrypting credential '%s'...", input->id); + + r = connect_to_creds(link); + if (r < 0) + return r; + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = varlink_callbo_and_log( + *link, + "io.systemd.Credentials.Encrypt", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_STRING("name", input->id), + SD_JSON_BUILD_PAIR_BASE64("data", input->data, input->size), + SD_JSON_BUILD_PAIR_STRING("scope", "system"), + /* We pick the 'auto_initrd' key for this, since we want TPM if available, but are fine with NULL if not */ + SD_JSON_BUILD_PAIR_STRING("withKey", "auto_initrd")); + if (r < 0) + return r; + + static const sd_json_dispatch_field dispatch_table[] = { + { "blob", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 }, + {} + }; + + const char *blob = NULL; + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &blob); + if (r < 0) + return r; + + r = strv_extend_many(encrypted, input->id, blob); + if (r < 0) + return r; + + return 0; +} + +static int encrypt_credentials(sd_varlink **link, char ***encrypted) { + int r; + + assert(link); + assert(encrypted); + + FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) { + r = encrypt_one_credential(link, cred, encrypted); + if (r < 0) + return r; + } + + return 0; +} + +static const ImagePolicy image_policy = { + .n_policies = 4, + .policies = { + /* We mount / and /usr/ so that we can get access to /etc/machine-id and /etc/kernel/ */ + { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + }, + .default_flags = PARTITION_POLICY_IGNORE, +}; + +static int settle_definitions(void) { + int r; + + if (arg_definitions) + return 0; + + /* If /usr/lib/repart.sysinstall.d/ is populated, use it, otherwise use the regular definition + * files */ + + _cleanup_strv_free_ char **files = NULL; + r = conf_files_list_strv( + &files, + ".conf", + /* root= */ NULL, + CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN|CONF_FILES_DONT_PREFIX_ROOT, + (const char**) CONF_PATHS_STRV("repart.sysinstall.d")); + if (r < 0) + return log_error_errno(r, "Failed to enumerate *.conf files: %m"); + + if (!strv_isempty(files)) { + arg_definitions = strv_copy(CONF_PATHS_STRV("repart.sysinstall.d")); + if (!arg_definitions) + return log_oom(); + } + + return 0; +} + +static void end_marker(void) { + + if (!arg_welcome) + return; + + printf("\n%sExiting first boot settings tool.%s\n\n", ansi_grey(), ansi_normal()); + fflush(stdout); +} + +static int run(int argc, char *argv[]) { + int r; + + setlocale(LC_ALL, ""); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + log_setup(); + + r = settle_definitions(); + if (r < 0) + return r; + + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *mute_console_link = NULL; + if (arg_welcome) { + if (arg_mute_console) + (void) mute_console(&mute_console_link); + + (void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0); + + if (arg_chrome) + chrome_show("Operating System Installer", /* bottom= */ NULL); + } + + DEFER_VOID_CALL(end_marker); + DEFER_VOID_CALL(chrome_hide); + + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *repart_link = NULL; + if (arg_node) { + r = print_welcome(&mute_console_link); + if (r < 0) + return r; + + r = validate_run(&repart_link, arg_node); + if (r < 0) + return r; + } else { + /* Determine the minimum disk size */ + uint64_t min_size = UINT64_MAX; + r = invoke_repart( + &repart_link, + /* node= */ NULL, + /* erase= */ true, + /* dry_run= */ true, + &min_size, + /* current_size= */ NULL, + /* need_free= */ NULL); + if (r < 0) + return r; + + r = print_welcome(&mute_console_link); + if (r < 0) + return r; + + log_info("Required minimal installation disk size is %s.", FORMAT_BYTES(min_size)); + + for (;;) { + _cleanup_free_ char *node = NULL; + r = prompt_block_device(&repart_link, &node); + if (r < 0) + return r; + + r = validate_run(&repart_link, node); + if (IN_SET(r, -ENOSPC, -E2BIG, -EHWPOISON)) /* Device is no fit, pick other */ + continue; + if (r < 0) + return r; + + arg_node = TAKE_PTR(node); + break; + } + } + + r = prompt_touch_variables(); + if (r < 0) + return r; + + r = read_credentials(); + if (r < 0) + return r; + + /* Verify we have everything we need */ + assert(arg_node); + assert(arg_erase >= 0); + assert(arg_touch_variables >= 0); + + r = show_summary(); + if (r < 0) + return r; + + r = prompt_confirm(); + if (r < 0) + return r; + + putchar('\n'); + + log_notice("%s%sEncrypting credentials...", + emoji_enabled() ? glyph(GLYPH_LOCK_AND_KEY) : "", emoji_enabled() ? " " : ""); + + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *creds_link = NULL; + _cleanup_strv_free_ char **encrypted_credentials = NULL; + r = encrypt_credentials(&creds_link, &encrypted_credentials); + if (r < 0) + return r; + + log_notice("%s%sInstalling partitions...", + emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); + + /* Do the main part of the installation */ + r = invoke_repart( + &repart_link, + arg_node, + arg_erase, + /* dry_run= */ false, + /* min_size= */ NULL, + /* current_size= */ NULL, + /* need_free= */ NULL); + if (r < 0) + return r; + + log_notice("%s%sMounting partitions...", + emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); + + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; + _cleanup_(umount_and_freep) char *root_dir = NULL; + _cleanup_close_ int root_fd = -EBADF; + r = mount_image_privately_interactively( + arg_node, + &image_policy, + DISSECT_IMAGE_REQUIRE_ROOT | + DISSECT_IMAGE_RELAX_VAR_CHECK | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY | + DISSECT_IMAGE_DISCARD_ANY | + DISSECT_IMAGE_GPT_ONLY | + DISSECT_IMAGE_FSCK | + DISSECT_IMAGE_USR_NO_ROOT | + DISSECT_IMAGE_ADD_PARTITION_DEVICES | + DISSECT_IMAGE_PIN_PARTITION_DEVICES, + &root_dir, + &root_fd, + &loop_device); + if (r < 0) + return log_error_errno(r, "Failed to mount new image: %m"); + + log_notice("%s%sInstalling kernel...", + emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); + + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *bootctl_link = NULL; + r = invoke_bootctl_link(&bootctl_link, root_dir, root_fd, encrypted_credentials); + if (r < 0) + return r; + + log_notice("%s%sInstalling boot loader...", + emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); + + r = invoke_bootctl_install(&bootctl_link, root_dir, root_fd); + if (r < 0) + return r; + + log_notice("%s%sUnmounting partitions...", + emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); + + root_fd = safe_close(root_fd); + r = umount_recursive(root_dir, /* flags= */ 0); + if (r < 0) + log_warning_errno(r, "Failed to unmount target disk, proceeding anyway: %m"); + loop_device = loop_device_unref(loop_device); + sync(); + + log_notice("%s%sInstallation succeeded.", + emoji_enabled() ? glyph(GLYPH_SPARKLES) : "", emoji_enabled() ? " " : ""); + + r = maybe_reboot(); + if (r < 0) + return r; + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/systemctl/fuzz-systemctl-parse-argv.c b/src/systemctl/fuzz-systemctl-parse-argv.c index d30b9d8fed040..9d487f7c6ea64 100644 --- a/src/systemctl/fuzz-systemctl-parse-argv.c +++ b/src/systemctl/fuzz-systemctl-parse-argv.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "bus-util.h" @@ -15,6 +14,48 @@ #include "systemctl.h" #include "systemctl-util.h" +static int verb_noop(int argc, char *argv[], uintptr_t _data, void *userdata) { return 0; } +_alias_(verb_noop) + verb_add_dependency, + verb_bind, + verb_cancel, + verb_cat, + verb_clean_or_freeze, + verb_edit, + verb_enable, + verb_get_default, + verb_import_environment, + verb_is_active, + verb_is_enabled, + verb_is_failed, + verb_is_system_running, + verb_kill, + verb_list_automounts, + verb_list_dependencies, + verb_list_jobs, + verb_list_machines, + verb_list_paths, + verb_list_sockets, + verb_list_timers, + verb_list_unit_files, + verb_list_units, + verb_log_setting, + verb_mount_image, + verb_preset_all, + verb_reset_failed, + verb_service_log_setting, + verb_service_watchdogs, + verb_set_default, + verb_set_environment, + verb_set_property, + verb_show, + verb_show_environment, + verb_start_special, + verb_start_system_special, + verb_switch_root, + verb_trivial_method, + verb_whoami; + int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { _cleanup_strv_free_ char **argv = NULL; _cleanup_close_ int orig_stdout_fd = -EBADF; @@ -46,17 +87,16 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { log_warning_errno(orig_stdout_fd, "Failed to duplicate fd 1: %m"); else assert_se(freopen("/dev/null", "w", stdout)); - - opterr = 0; /* do not print errors */ } /* We need to reset some global state manually here since libfuzzer feeds a single process with * multiple inputs, so we might carry over state from previous invocations that can trigger * certain asserts. */ - optind = 0; /* this tells the getopt machinery to reinitialize */ arg_transport = BUS_TRANSPORT_LOCAL; - r = systemctl_dispatch_parse_argv(strv_length(argv), argv); + r = systemctl_dispatch_parse_argv(strv_length(argv), argv, + /* log_level_shift= */ LOG_DEBUG - LOG_ERR, + /* remaining_args= */ NULL); if (r < 0) log_error_errno(r, "Failed to parse args: %m"); else diff --git a/src/systemctl/meson.build b/src/systemctl/meson.build index 2ce11c8f48d0a..882704c9d7d87 100644 --- a/src/systemctl/meson.build +++ b/src/systemctl/meson.build @@ -58,7 +58,6 @@ executables += [ liblz4_cflags, libxz_cflags, libzstd_cflags, - threads, ], 'install_tag' : 'systemctl', }, 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..1022fd0d12f1b 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; @@ -83,10 +83,9 @@ int verb_clean_or_freeze(int argc, char *argv[], void *userdata) { r = sd_bus_call(bus, m, 0, &error, NULL); if (r < 0) { log_error_errno(r, "Failed to %s unit %s: %s", argv[0], *name, bus_error_message(&error, r)); - if (ret == EXIT_SUCCESS) { + if (ret == EXIT_SUCCESS) ret = r; - continue; - } + continue; } if (w) { 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..217487b64350c 100644 --- a/src/systemctl/systemctl-compat-halt.c +++ b/src/systemctl/systemctl-compat-halt.c @@ -1,12 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-daemon.h" -#include "alloc-util.h" +#include "ansi-color.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" -#include "pretty-print.h" +#include "options.h" #include "process-util.h" #include "reboot-util.h" #include "systemctl.h" @@ -17,126 +17,107 @@ #include "utmp-wtmp.h" static int halt_help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("halt", "8", &link); + r = option_parser_get_help_table_ns("halt", &options); if (r < 0) - return log_oom(); + return r; /* Note: if you are tempted to add new command line switches here, please do not. Let this * compatibility command rest in peace. Its interface is not even owned by us as much as it is by * sysvinit. If you add something new, add it to "systemctl halt", "systemctl reboot", "systemctl * poweroff" instead. */ - printf("%s [OPTIONS...]%s\n" - "\n%s%s the system.%s\n" - "\nOptions:\n" - " --help Show this help\n" - " --halt Halt the machine\n" - " -p --poweroff Switch off the machine\n" - " --reboot Reboot the machine\n" - " -f --force Force immediate halt/power-off/reboot\n" - " -w --wtmp-only Don't halt/power-off/reboot, just write wtmp record\n" - " -d --no-wtmp Don't write wtmp record\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n" - "\n%sThis is a compatibility interface, please use the more powerful 'systemctl %s' command instead.%s\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - arg_action == ACTION_REBOOT ? " [ARG]" : "", - ansi_highlight(), arg_action == ACTION_REBOOT ? "Reboot" : - arg_action == ACTION_POWEROFF ? "Power off" : - "Halt", ansi_normal(), - ansi_highlight_red(), arg_action == ACTION_REBOOT ? "reboot" : - arg_action == ACTION_POWEROFF ? "poweroff" : - "halt", ansi_normal(), - link); + help_cmdline(arg_action == ACTION_REBOOT ? "[OPTIONS…] [ARG]" : "[OPTIONS…]"); + help_abstract(arg_action == ACTION_REBOOT ? "Reboot the system." : + arg_action == ACTION_POWEROFF ? "Power off the system." : + "Halt the system."); - return 0; -} + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; -int halt_parse_argv(int argc, char *argv[]) { - enum { - ARG_HELP = 0x100, - ARG_HALT, - ARG_REBOOT, - ARG_NO_WALL - }; + printf("\n%sThis is a compatibility interface, please use the more powerful 'systemctl %s' command instead.%s\n", + ansi_highlight_red(), + arg_action == ACTION_REBOOT ? "reboot" : + arg_action == ACTION_POWEROFF ? "poweroff" : + "halt", + ansi_normal()); - static const struct option options[] = { - { "help", no_argument, NULL, ARG_HELP }, - { "halt", no_argument, NULL, ARG_HALT }, - { "poweroff", no_argument, NULL, 'p' }, - { "reboot", no_argument, NULL, ARG_REBOOT }, - { "force", no_argument, NULL, 'f' }, - { "wtmp-only", no_argument, NULL, 'w' }, - { "no-wtmp", no_argument, NULL, 'd' }, - { "no-sync", no_argument, NULL, 'n' }, - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - {} - }; + help_man_page_reference("halt", "8"); + return 0; +} - int c, r; +int halt_parse_argv(int argc, char *argv[], int log_level_shift) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "pfwdnih", options, NULL)) >= 0) + OptionParser opts = { + argc, argv, + .namespace = "halt", + .log_level_shift = log_level_shift, + }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case ARG_HELP: + OPTION_NAMESPACE("halt"): {} + + OPTION_LONG("help", NULL, "Show this help"): return halt_help(); - case ARG_HALT: + OPTION_LONG("halt", NULL, "Halt the machine"): arg_action = ACTION_HALT; break; - case 'p': + OPTION('p', "poweroff", NULL, "Switch off the machine"): if (arg_action != ACTION_REBOOT) arg_action = ACTION_POWEROFF; break; - case ARG_REBOOT: + OPTION_LONG("reboot", NULL, "Reboot the machine"): arg_action = ACTION_REBOOT; break; - case 'f': + OPTION('f', "force", NULL, "Force immediate halt/power-off/reboot"): arg_force = 2; break; - case 'w': + OPTION('w', "wtmp-only", NULL, "Don't halt/power-off/reboot, just write wtmp record"): arg_dry_run = true; break; - case 'd': + OPTION('d', "no-wtmp", NULL, "Don't write wtmp record"): arg_no_wtmp = true; break; - case 'n': - arg_no_sync = true; + OPTION_LONG("no-wall", NULL, "Don't send wall message before halt/power-off/reboot"): + arg_no_wall = true; break; - case ARG_NO_WALL: - arg_no_wall = true; + /* Hidden compat-only options. */ + OPTION('n', "no-sync", NULL, /* help= */ NULL): + arg_no_sync = true; break; - case 'i': - case 'h': + OPTION_SHORT('i', NULL, /* help= */ NULL): {} + OPTION_SHORT('h', NULL, /* help= */ NULL): /* Compatibility nops */ break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (arg_action == ACTION_REBOOT && (argc == optind || argc == optind + 1)) { - r = update_reboot_parameter_and_warn(argc == optind + 1 ? argv[optind] : NULL, false); + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + + if (arg_action == ACTION_REBOOT && n_args <= 1) { + r = update_reboot_parameter_and_warn(args[0], false); if (r < 0) return r; - } else if (optind < argc) + } else if (n_args > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments."); @@ -170,7 +151,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-halt.h b/src/systemctl/systemctl-compat-halt.h index 85b9dda0e4a0e..3658e52e1aa21 100644 --- a/src/systemctl/systemctl-compat-halt.h +++ b/src/systemctl/systemctl-compat-halt.h @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int halt_parse_argv(int argc, char *argv[]); - +int halt_parse_argv(int argc, char *argv[], int log_level_shift); int halt_main(void); diff --git a/src/systemctl/systemctl-compat-shutdown.c b/src/systemctl/systemctl-compat-shutdown.c index 877ea8378b375..d93fa91ca09ec 100644 --- a/src/systemctl/systemctl-compat-shutdown.c +++ b/src/systemctl/systemctl-compat-shutdown.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - -#include "alloc-util.h" +#include "ansi-color.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "parse-util.h" -#include "pretty-print.h" #include "reboot-util.h" #include "string-util.h" #include "strv.h" @@ -14,38 +14,31 @@ #include "time-util.h" static int shutdown_help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("shutdown", "8", &link); + r = option_parser_get_help_table_ns("shutdown", &options); if (r < 0) - return log_oom(); + return r; /* Note: if you are tempted to add new command line switches here, please do not. Let this * compatibility command rest in peace. Its interface is not even owned by us as much as it is by * sysvinit. If you add something new, add it to "systemctl halt", "systemctl reboot", "systemctl * poweroff" instead. */ - printf("%s [OPTIONS...] [TIME] [WALL...]\n" - "\n%sShut down the system.%s\n" - "\nOptions:\n" - " --help Show this help\n" - " -H --halt Halt the machine\n" - " -P --poweroff Power-off the machine\n" - " -r --reboot Reboot the machine\n" - " -h Equivalent to --poweroff, overridden by --halt\n" - " -k Don't halt/power-off/reboot, just send warnings\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n" - " -c Cancel a pending shutdown\n" - " --show Show pending shutdown\n" - "\n%sThis is a compatibility interface, please use the more powerful 'systemctl halt',\n" - "'systemctl poweroff', 'systemctl reboot' commands instead.%s\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), ansi_normal(), - ansi_highlight_red(), ansi_normal(), - link); + help_cmdline("[OPTIONS…] [TIME] [WALL…]"); + help_abstract("Shut down the system."); + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\n%sThis is a compatibility interface, please use the more powerful 'systemctl halt',\n" + "'systemctl poweroff', 'systemctl reboot' commands instead.%s\n", + ansi_highlight_red(), ansi_normal()); + help_man_page_reference("shutdown", "8"); return 0; } @@ -108,14 +101,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; @@ -124,105 +116,91 @@ static int parse_shutdown_time_spec(const char *t, usec_t *ret) { return 0; } -int shutdown_parse_argv(int argc, char *argv[]) { - enum { - ARG_HELP = 0x100, - ARG_NO_WALL, - ARG_SHOW - }; - - static const struct option options[] = { - { "help", no_argument, NULL, ARG_HELP }, - { "halt", no_argument, NULL, 'H' }, - { "poweroff", no_argument, NULL, 'P' }, - { "reboot", no_argument, NULL, 'r' }, - { "kexec", no_argument, NULL, 'K' }, /* not documented extension */ - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - { "show", no_argument, NULL, ARG_SHOW }, - {} - }; - - char **wall = NULL; - int c, r; +int shutdown_parse_argv(int argc, char *argv[], int log_level_shift) { + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "HPrhkKat:fFc", options, NULL)) >= 0) + OptionParser opts = { + argc, argv, + .namespace = "shutdown", + .log_level_shift = log_level_shift, + }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case ARG_HELP: + OPTION_NAMESPACE("shutdown"): {} + + OPTION_LONG("help", NULL, "Show this help"): return shutdown_help(); - case 'H': + OPTION('H', "halt", NULL, "Halt the machine"): arg_action = ACTION_HALT; break; - case 'P': + OPTION('P', "poweroff", NULL, "Power-off the machine"): arg_action = ACTION_POWEROFF; break; - case 'r': + OPTION('r', "reboot", NULL, "Reboot the machine"): if (kexec_loaded()) arg_action = ACTION_KEXEC; else arg_action = ACTION_REBOOT; break; - case 'K': - arg_action = ACTION_KEXEC; - break; - - case 'h': + OPTION_SHORT('h', NULL, "Equivalent to --poweroff, overridden by --halt"): if (arg_action != ACTION_HALT) arg_action = ACTION_POWEROFF; break; - case 'k': + OPTION_SHORT('k', NULL, "Don't halt/power-off/reboot, just send warnings"): arg_dry_run = true; break; - case ARG_NO_WALL: + OPTION_LONG("no-wall", NULL, "Don't send wall message before halt/power-off/reboot"): arg_no_wall = true; break; - case 'a': - case 't': /* Note that we also ignore any passed argument to -t, not just the -t itself */ - case 'f': - case 'F': - /* Compatibility nops */ - break; - - case 'c': + OPTION_SHORT('c', NULL, "Cancel a pending shutdown"): arg_action = ACTION_CANCEL_SHUTDOWN; break; - case ARG_SHOW: + OPTION_LONG("show", NULL, "Show pending shutdown"): arg_action = ACTION_SHOW_SHUTDOWN; break; - case '?': - return -EINVAL; + /* Hidden compat options. */ + OPTION('K', "kexec", NULL, /* help= */ NULL): + arg_action = ACTION_KEXEC; + break; - default: - assert_not_reached(); + OPTION_SHORT('a', NULL, /* help= */ NULL): {} /* compatibility noops */ + OPTION_SHORT('f', NULL, /* help= */ NULL): {} + OPTION_SHORT('F', NULL, /* help= */ NULL): {} + OPTION_SHORT('t', "ARG", /* help= */ NULL): + break; } - if (argc > optind && arg_action != ACTION_CANCEL_SHUTDOWN) { - r = parse_shutdown_time_spec(argv[optind], &arg_when); - if (r < 0) { - log_error("Failed to parse time specification: %s", argv[optind]); - return r; - } + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + + if (n_args > 0 && arg_action != ACTION_CANCEL_SHUTDOWN) { + r = parse_shutdown_time_spec(args[0], &arg_when); + if (r < 0) + return log_error_errno(r, "Failed to parse time specification: %s", args[0]); } else arg_when = USEC_INFINITY; /* logind chooses on server side */ - if (argc > optind && arg_action == ACTION_CANCEL_SHUTDOWN) + char **wall = NULL; + if (n_args > 0 && arg_action == ACTION_CANCEL_SHUTDOWN) /* No time argument for shutdown cancel */ - wall = argv + optind; - else if (argc > optind + 1) + wall = args; + else if (n_args > 1) /* We skip the time argument */ - wall = argv + optind + 1; + wall = args + 1; if (wall) { char **copy = strv_copy(wall); @@ -231,7 +209,5 @@ int shutdown_parse_argv(int argc, char *argv[]) { strv_free_and_replace(arg_wall, copy); } - optind = argc; - return 1; } diff --git a/src/systemctl/systemctl-compat-shutdown.h b/src/systemctl/systemctl-compat-shutdown.h index 7acf9414c9ab9..9181a31f862f9 100644 --- a/src/systemctl/systemctl-compat-shutdown.h +++ b/src/systemctl/systemctl-compat-shutdown.h @@ -1,4 +1,4 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int shutdown_parse_argv(int argc, char *argv[]); +int shutdown_parse_argv(int argc, char *argv[], int log_level_shift); 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..5ad2066557284 100644 --- a/src/systemctl/systemctl-enable.c +++ b/src/systemctl/systemctl-enable.c @@ -13,7 +13,6 @@ #include "path-lookup.h" #include "path-util.h" #include "string-util.h" -#include "strv-fundamental.h" #include "strv.h" #include "systemctl.h" #include "systemctl-daemon-reload.h" @@ -80,7 +79,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 +401,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..8e2b1c170d5f5 100644 --- a/src/systemctl/systemctl-main.c +++ b/src/systemctl/systemctl-main.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "dissect-image.h" @@ -11,148 +10,23 @@ #include "main-func.h" #include "mount-util.h" #include "stat-util.h" -#include "systemctl.h" -#include "systemctl-add-dependency.h" -#include "systemctl-cancel-job.h" -#include "systemctl-clean-or-freeze.h" +#include "strv.h" #include "systemctl-compat-halt.h" -#include "systemctl-daemon-reload.h" -#include "systemctl-edit.h" -#include "systemctl-enable.h" -#include "systemctl-is-active.h" -#include "systemctl-is-enabled.h" -#include "systemctl-is-system-running.h" -#include "systemctl-kill.h" -#include "systemctl-list-dependencies.h" -#include "systemctl-list-jobs.h" -#include "systemctl-list-machines.h" -#include "systemctl-list-unit-files.h" -#include "systemctl-list-units.h" -#include "systemctl-log-setting.h" #include "systemctl-logind.h" -#include "systemctl-mount.h" -#include "systemctl-preset-all.h" -#include "systemctl-reset-failed.h" -#include "systemctl-service-watchdogs.h" -#include "systemctl-set-default.h" -#include "systemctl-set-environment.h" -#include "systemctl-set-property.h" -#include "systemctl-show.h" -#include "systemctl-start-special.h" -#include "systemctl-start-unit.h" -#include "systemctl-switch-root.h" -#include "systemctl-trivial-method.h" #include "systemctl-util.h" -#include "systemctl-whoami.h" -#include "verbs.h" +#include "systemctl.h" #include "virt.h" -static int systemctl_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "list-units", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list_units }, - { "list-unit-files", VERB_ANY, VERB_ANY, 0, verb_list_unit_files }, - { "list-automounts", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_automounts }, - { "list-paths", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_paths }, - { "list-sockets", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_sockets }, - { "list-timers", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_timers }, - { "list-jobs", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_jobs }, - { "list-machines", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_machines }, - { "clear-jobs", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_trivial_method }, - { "cancel", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_cancel }, - { "start", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, - { "stop", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, - { "condstop", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, /* For compatibility with ALTLinux */ - { "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 }, - { "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 */ - { "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 */ - { "condrestart", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_start }, /* For compatibility with RH */ - { "isolate", 2, 2, VERB_ONLINE_ONLY, verb_start }, - { "kill", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_kill }, - { "clean", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_clean_or_freeze }, - { "freeze", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_clean_or_freeze }, - { "thaw", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_clean_or_freeze }, - { "is-active", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_is_active }, - { "check", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_is_active }, /* deprecated alias of is-active */ - { "is-failed", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_is_failed }, - { "show", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_show }, - { "cat", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_cat }, - { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_show }, - { "help", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_show }, - { "daemon-reload", 1, 1, VERB_ONLINE_ONLY, verb_daemon_reload }, - { "daemon-reexec", 1, 1, VERB_ONLINE_ONLY, verb_daemon_reload }, - { "log-level", VERB_ANY, 2, VERB_ONLINE_ONLY, verb_log_setting }, - { "log-target", VERB_ANY, 2, VERB_ONLINE_ONLY, verb_log_setting }, - { "service-log-level", 2, 3, VERB_ONLINE_ONLY, verb_service_log_setting }, - { "service-log-target", 2, 3, VERB_ONLINE_ONLY, verb_service_log_setting }, - { "service-watchdogs", VERB_ANY, 2, VERB_ONLINE_ONLY, verb_service_watchdogs }, - { "show-environment", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_show_environment }, - { "set-environment", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_set_environment }, - { "unset-environment", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_set_environment }, - { "import-environment", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_import_environment }, - { "halt", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "poweroff", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "reboot", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "kexec", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "soft-reboot", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "sleep", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "suspend", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "hibernate", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "hybrid-sleep", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "suspend-then-hibernate",VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "default", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_special }, - { "rescue", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "emergency", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, - { "exit", VERB_ANY, 2, VERB_ONLINE_ONLY, verb_start_special }, - { "reset-failed", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_reset_failed }, - { "enable", 2, VERB_ANY, 0, verb_enable }, - { "disable", 2, VERB_ANY, 0, verb_enable }, - { "is-enabled", 2, VERB_ANY, 0, verb_is_enabled }, - { "reenable", 2, VERB_ANY, 0, verb_enable }, - { "preset", 2, VERB_ANY, 0, verb_enable }, - { "preset-all", VERB_ANY, 1, 0, verb_preset_all }, - { "mask", 2, VERB_ANY, 0, verb_enable }, - { "unmask", 2, VERB_ANY, 0, verb_enable }, - { "link", 2, VERB_ANY, 0, verb_enable }, - { "revert", 2, VERB_ANY, 0, verb_enable }, - { "switch-root", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_switch_root }, - { "list-dependencies", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_list_dependencies }, - { "set-default", 2, 2, 0, verb_set_default }, - { "get-default", VERB_ANY, 1, 0, verb_get_default }, - { "set-property", 3, VERB_ANY, VERB_ONLINE_ONLY, verb_set_property }, - { "is-system-running", VERB_ANY, 1, 0, verb_is_system_running }, - { "add-wants", 3, VERB_ANY, 0, verb_add_dependency }, - { "add-requires", 3, VERB_ANY, 0, verb_add_dependency }, - { "edit", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_edit }, - { "bind", 3, 4, VERB_ONLINE_ONLY, verb_bind }, - { "mount-image", 4, 5, VERB_ONLINE_ONLY, verb_mount_image }, - { "whoami", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_whoami }, - {} - }; - - const Verb *verb = verbs_find_verb(argv[optind], verbs); - 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=.", - argv[optind] ?: verb->verb); - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_freep) char *mounted_dir = NULL; + char **args = STRV_EMPTY; int r; setlocale(LC_ALL, ""); log_setup(); - r = systemctl_dispatch_parse_argv(argc, argv); + r = systemctl_dispatch_parse_argv(argc, argv, /* log_level_shift= */ 0, &args); if (r <= 0) goto finish; @@ -200,7 +74,7 @@ static int run(int argc, char *argv[]) { switch (arg_action) { case ACTION_SYSTEMCTL: - r = systemctl_main(argc, argv); + r = systemctl_main(args); break; /* Legacy command aliases set arg_action. They provide some fallbacks, e.g. to tell sysvinit to 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..82b8568813365 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(); @@ -1464,6 +1467,25 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m return 1; + } else if (contents[0] == SD_BUS_TYPE_STRUCT_BEGIN && + STR_IN_SET(name, "XAttrEntryPoint", "XAttrListen", "XAttrAccept")) { + const char *xname, *xvalue; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(ss)", &xname, &xvalue)) > 0) + bus_print_property_valuef(name, expected_value, flags, "%s=%s", xname, xvalue); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 1; + } else if (contents[0] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "TimersMonotonic")) { const char *base; uint64_t v, next_elapse; @@ -2201,7 +2223,7 @@ static int show_one( { "ActiveExitTimestamp", "t", NULL, offsetof(UnitStatusInfo, active_exit_timestamp) }, { "InactiveEnterTimestamp", "t", NULL, offsetof(UnitStatusInfo, inactive_enter_timestamp) }, { "RuntimeMaxUSec", "t", NULL, offsetof(UnitStatusInfo, runtime_max_sec) }, - { "InvocationID", "s", bus_map_id128, offsetof(UnitStatusInfo, invocation_id) }, + { "InvocationID", "ay", bus_map_id128, offsetof(UnitStatusInfo, invocation_id) }, { "NeedDaemonReload", "b", NULL, offsetof(UnitStatusInfo, need_daemon_reload) }, { "Transient", "b", NULL, offsetof(UnitStatusInfo, transient) }, { "ExecMainPID", "u", NULL, offsetof(UnitStatusInfo, main_pid) }, @@ -2482,7 +2504,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..ab0285289bc29 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,8 @@ #include "bus-error.h" #include "bus-locator.h" #include "efivars.h" +#include "extract-word.h" +#include "fd-util.h" #include "log.h" #include "parse-util.h" #include "path-util.h" @@ -22,20 +25,43 @@ #include "systemctl-trivial-method.h" #include "systemctl-util.h" +static int deduplicate_kernel_cmdline(char **cmdline) { + int r; + + assert(cmdline); + + /* Split, reverse, deduplicate, reverse again: the last occurrence of each identical argument wins. */ + + _cleanup_strv_free_ char **l = NULL; + r = strv_split_full(&l, *cmdline, /* separators= */ NULL, + EXTRACT_KEEP_QUOTE | EXTRACT_RETAIN_ESCAPE | EXTRACT_RELAX); + if (r < 0) + return r; + + strv_reverse(l); + strv_uniq(l); + strv_reverse(l); + + _cleanup_free_ char *joined = strv_join(l, " "); + if (!joined) + return -ENOMEM; + + free_and_replace(*cmdline, joined); + return 0; +} + 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 || arg_reuse_kernel_cmdline) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--kernel-cmdline=/--kernel-cmdline-reuse 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 +77,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 +91,100 @@ 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(); + /* Assemble in a fixed order: UKI -> current cmdline -> appended cmdline, and dedup at the end */ + if (!isempty(arg_reuse_kernel_cmdline) && !strextend_with_separator(&options, " ", arg_reuse_kernel_cmdline)) + return log_oom(); + + if (!isempty(arg_kernel_cmdline) && !strextend_with_separator(&options, " ", arg_kernel_cmdline)) + return log_oom(); + + if (!isempty(arg_reuse_kernel_cmdline) || !isempty(arg_kernel_cmdline)) { + r = deduplicate_kernel_cmdline(&options); + if (r < 0) + return log_error_errno(r, "Failed to deduplicate kernel command line: %m"); + } + 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 (IN_SET(saved_errno, ENOEXEC, EINVAL)) { + /* 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. + * + * Most architectures' kexec_file_load() returns ENOEXEC when no image loader matches + * (the default behavior of kexec_image_probe_default()), but arm64's image_probe() + * returns EINVAL for an unrecognized magic. Accept both, otherwise `systemctl kexec` + * of a UKI never reaches the extraction path on arm64. */ + 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 +224,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 +295,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 +352,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 +362,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 +370,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..9bc924a07d5d2 100644 --- a/src/systemctl/systemctl-start-unit.c +++ b/src/systemctl/systemctl-start-unit.c @@ -2,7 +2,6 @@ #include "sd-bus.h" -#include "alloc-util.h" #include "ansi-color.h" #include "bus-common-errors.h" #include "bus-error.h" @@ -192,6 +191,95 @@ static int start_unit_one( return r; } +static int start_units_one_transaction( + sd_bus *bus, + char * const *names, + const char *job_type, + const char *mode, + sd_bus_error *error, + BusWaitForJobs *w, + BusWaitForUnits *wu, + char ***ret_stopped_units) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + int r; + + assert(bus); + assert(!strv_isempty(names)); + assert(job_type); + assert(mode); + assert(error); + + log_debug("Executing dbus call org.freedesktop.systemd1.Manager EnqueueUnitJobMany(%s, %s) for %zu units", + job_type, mode, strv_length(names)); + + r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "EnqueueUnitJobMany"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, (char**) names); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "sst", job_type, mode, UINT64_C(0)); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, /* usec= */ 0, error, &reply); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'a', "(uosos)"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + const char *path, *unit_id, *jt; + uint32_t id; + + r = sd_bus_message_read(reply, "(uosos)", &id, &path, &unit_id, NULL, &jt); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + log_debug("Enqueued job %" PRIu32 " for %s/%s as %s", id, unit_id, jt, path); + + if (need_daemon_reload(bus, unit_id) > 0) + warn_unit_file_changed(unit_id); + + if (w) { + log_debug("Adding %s to the set", path); + r = bus_wait_for_jobs_add(w, path); + if (r < 0) + return log_error_errno(r, "Failed to watch job for %s: %m", unit_id); + } + + if (wu) { + r = bus_wait_for_units_add_unit(wu, unit_id, BUS_WAIT_FOR_INACTIVE|BUS_WAIT_NO_JOB, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to watch unit %s: %m", unit_id); + } + + /* Only record units for which the daemon actually enqueued a stop job. The + * server may optimize away stops for units that are already inactive, in + * which case the corresponding entry has a different job type (or is + * omitted), and we must not warn about "unit can still be triggered by…" + * for those. */ + if (ret_stopped_units && streq(jt, "stop")) { + r = strv_extend(ret_stopped_units, unit_id); + if (r < 0) + return log_oom(); + } + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return 0; +} + static int enqueue_marked_jobs( sd_bus *bus, BusWaitForJobs *w) { @@ -289,11 +377,11 @@ 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; - _cleanup_free_ char **stopped_units = NULL; /* Do not use _cleanup_strv_free_ */ + _cleanup_strv_free_ char **stopped_units = NULL; _cleanup_strv_free_ char **names = NULL; bool is_enqueue_marked_jobs = false; int r, ret = EXIT_SUCCESS; @@ -332,16 +420,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 */ @@ -401,22 +489,59 @@ int verb_start(int argc, char *argv[], void *userdata) { if (is_enqueue_marked_jobs) ret = enqueue_marked_jobs(bus, w); else { - if (arg_verbose) - (void) journal_fork(arg_runtime_scope, names, &journal_pid); + bool fallback = true; - STRV_FOREACH(name, names) { + if (arg_verbose) + (void) journal_fork(arg_runtime_scope, names, arg_output, &journal_pid); + + /* When operating on multiple units in one go, prefer the new EnqueueUnitJobMany() method + * which builds a single transaction. This ensures After= ordering is honored regardless + * of the order on the command line (see issue #8102). For show-transaction or dry-run we + * stick to per-unit calls. */ + if (arg_action == ACTION_SYSTEMCTL && + strv_length(names) > 1 && + !arg_show_transaction && + !arg_dry_run && + !streq(mode, "isolate")) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = start_unit_one(bus, method, job_type, *name, mode, &error, w, wu); - if (ret == EXIT_SUCCESS && r < 0) + r = start_units_one_transaction(bus, names, job_type, mode, &error, w, wu, + streq(method, "StopUnit") ? &stopped_units : NULL); + if (r >= 0) + fallback = false; + else if (sd_bus_error_has_names(&error, + SD_BUS_ERROR_UNKNOWN_METHOD, + SD_BUS_ERROR_INVALID_ARGS)) { + log_debug_errno(r, "EnqueueUnitJobMany() unavailable or unsupported (%s), falling back to per-unit calls.", + bus_error_message(&error, r)); + } else if (sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED) && + STR_IN_SET(method, "TryRestartUnit", "ReloadOrTryRestartUnit")) { + /* try-restart variants silently ignore masked units in the per-unit + * path; preserve that by falling back so each unit is evaluated + * individually. */ + log_debug_errno(r, "Batched %s aborted due to masked unit (%s), falling back to per-unit calls.", + method, bus_error_message(&error, r)); + } else { + log_error_errno(r, "Failed to enqueue jobs: %s", bus_error_message(&error, r)); ret = translate_bus_error_to_exit_status(r, &error); - - if (r >= 0 && streq(method, "StopUnit")) { - r = strv_push(&stopped_units, *name); - if (r < 0) - return log_oom(); + fallback = false; /* Real error, don't fall back. */ } } + + if (fallback) + STRV_FOREACH(name, names) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + r = start_unit_one(bus, method, job_type, *name, mode, &error, w, wu); + if (ret == EXIT_SUCCESS && r < 0) + ret = translate_bus_error_to_exit_status(r, &error); + + if (r >= 0 && streq(method, "StopUnit")) { + r = strv_extend(&stopped_units, *name); + if (r < 0) + return log_oom(); + } + } } if (!arg_no_block) { 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..8e8d7181121e3 100644 --- a/src/systemctl/systemctl-util.c +++ b/src/systemctl/systemctl-util.c @@ -508,7 +508,7 @@ int unit_find_paths( * Finds where the unit is defined on disk. Returns 0 if the unit is not found. Returns 1 if it is * found, and sets: * - * - the path to the unit in *ret_frament_path, if it exists on disk, + * - the path to the unit in *ret_fragment_path, if it exists on disk, * * - and a strv of existing drop-ins in *ret_dropin_paths, if the arg is not NULL and any dropins * were found. @@ -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..ef90e62a0008c 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -10,14 +9,17 @@ #include "bus-util.h" #include "capsule-util.h" #include "extract-word.h" +#include "format-table.h" +#include "help-util.h" #include "image-policy.h" #include "install.h" +#include "options.h" #include "output-mode.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" -#include "pretty-print.h" +#include "proc-cmdline.h" #include "static-destruct.h" #include "string-table.h" #include "string-util.h" @@ -27,6 +29,7 @@ #include "systemctl-compat-shutdown.h" #include "systemctl-logind.h" #include "time-util.h" +#include "verbs.h" char **arg_types = NULL; char **arg_states = NULL; @@ -68,6 +71,8 @@ char *arg_image = NULL; usec_t arg_when = 0; bool arg_stdin = false; const char *arg_reboot_argument = NULL; +char *arg_kernel_cmdline = NULL; +char *arg_reuse_kernel_cmdline = NULL; enum action arg_action = ACTION_SYSTEMCTL; BusTransport arg_transport = BUS_TRANSPORT_LOCAL; const char *arg_host = NULL; @@ -98,6 +103,8 @@ 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_reuse_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,234 +113,87 @@ 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; + static const char *const vgroups[] = { + "Unit Commands", + "Unit File Commands", + "Machine Commands", + "Job Commands", + "Environment Commands", + "Manager State Commands", + "System Commands", + }; + + Table *vtables[ELEMENTSOF(vgroups)] = {}; + CLEANUP_ELEMENTS(vtables, table_unref_array_clear); + _cleanup_(table_unrefp) Table *options_table = NULL; int r; + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + r = verbs_get_help_table_group(vgroups[i], &vtables[i]); + if (r < 0) + return r; + } + + r = option_parser_get_help_table_ns("systemctl", &options_table); + if (r < 0) + return r; + + assert_cc(ELEMENTSOF(vtables) == 7); + (void) table_sync_column_widths(0, options_table, + vtables[0], vtables[1], vtables[2], vtables[3], + vtables[4], vtables[5], vtables[6]); + pager_open(arg_pager_flags); - r = terminal_urlify_man("systemctl", "1", &link); + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Query or send control commands to the system manager."); + + for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) { + help_section(vgroups[i]); + r = table_print_or_warn(vtables[i]); + if (r < 0) + return r; + } + + help_section("Options"); + r = table_print_or_warn(options_table); if (r < 0) - return log_oom(); - - 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" - " list-automounts [PATTERN...] List automount units currently in memory,\n" - " ordered by path\n" - " list-paths [PATTERN...] List path units currently in memory,\n" - " ordered by path\n" - " list-sockets [PATTERN...] List socket units currently in memory,\n" - " ordered by address\n" - " list-timers [PATTERN...] List timer units currently in memory,\n" - " ordered by next elapse\n" - " is-active PATTERN... Check whether units are active\n" - " is-failed [PATTERN...] Check whether units are failed or\n" - " system is in degraded state\n" - " status [PATTERN...|PID...] Show runtime status of one or more units\n" - " show [PATTERN...|JOB...] Show properties of one or more\n" - " units/jobs or the manager\n" - " cat PATTERN... Show files and drop-ins of specified units\n" - " help PATTERN...|PID... Show manual for one or more units\n" - " list-dependencies [UNIT...] Recursively show units which are required\n" - " or wanted by the units or by which those\n" - " units are required or wanted\n" - " start UNIT... Start (activate) one or more units\n" - " stop UNIT... Stop (deactivate) one or more units\n" - " 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" - " 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" - " if supported, otherwise restart\n" - " isolate UNIT Start one unit and stop all others\n" - " kill UNIT... Send signal to processes of a unit\n" - " clean UNIT... Clean runtime, cache, state, logs or\n" - " configuration of unit\n" - " freeze PATTERN... Freeze execution of unit processes\n" - " thaw PATTERN... Resume execution of a frozen unit\n" - " set-property UNIT PROPERTY=VALUE... Sets one or more properties of a unit\n" - " bind UNIT PATH [PATH] Bind-mount a path from the host into a\n" - " unit's namespace\n" - " mount-image UNIT PATH [PATH [OPTS]] Mount an image from the host into a\n" - " unit's namespace\n" - " service-log-level SERVICE [LEVEL] Get/set logging threshold for service\n" - " service-log-target SERVICE [TARGET] Get/set logging target for service\n" - " 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" - " 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" - " preset UNIT... Enable/disable one or more unit files\n" - " based on preset configuration\n" - " preset-all Enable/disable all unit files based on\n" - " preset configuration\n" - " is-enabled UNIT... Check whether unit files are enabled\n" - " mask UNIT... Mask one or more units\n" - " unmask UNIT... Unmask one or more units\n" - " link PATH... Link one or more units files into\n" - " the search path\n" - " revert UNIT... Revert one or more unit files to vendor\n" - " version\n" - " add-wants TARGET UNIT... Add 'Wants' dependency for the target\n" - " on specified one or more units\n" - " add-requires TARGET UNIT... Add 'Requires' dependency for the target\n" - " 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-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" - " 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" - " default Enter system default mode\n" - " rescue Enter system rescue mode\n" - " emergency Enter system emergency mode\n" - " halt Shut down and halt the system\n" - " poweroff Shut down and power-off the system\n" - " reboot Shut down and reboot the system\n" - " kexec Shut down and reboot the system with kexec\n" - " soft-reboot Shut down and reboot userspace\n" - " exit [EXIT_CODE] Request user instance or container exit\n" - " switch-root [ROOT [INIT]] Change to a different root file system\n" - " sleep Put the system to sleep (through one of\n" - " the operations below)\n" - " suspend Suspend the system\n" - " 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" - " --version Show package version\n" - " --system Connect to system manager\n" - " --user Connect to user service manager\n" - " -C --capsule=NAME Connect to service manager of specified capsule\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on a local container\n" - " -t --type=TYPE List units of a particular type\n" - " --state=STATE List units with particular LOAD or SUB or ACTIVE state\n" - " --failed Shortcut for --state=failed\n" - " -p --property=NAME Show only properties by this name\n" - " -P NAME Equivalent to --value --property=NAME\n" - " -a --all Show all properties/all units currently in memory,\n" - " including dead/empty ones. To list all units installed\n" - " on the system, use 'list-unit-files' instead.\n" - " -l --full Don't ellipsize unit names on output\n" - " -r --recursive Show unit list of host and local containers\n" - " --reverse Show reverse dependencies with 'list-dependencies'\n" - " --before Show units ordered before with 'list-dependencies'\n" - " --after Show units ordered after with 'list-dependencies'\n" - " --with-dependencies Show unit dependencies with 'status', 'cat',\n" - " 'list-units', and 'list-unit-files'.\n" - " --job-mode=MODE Specify how to deal with already queued jobs, when\n" - " queueing a new job\n" - " -T --show-transaction When enqueuing a unit job, show full transaction\n" - " --show-types When showing sockets, explicitly show their type\n" - " --value When showing properties, only print the value\n" - " --check-inhibitors=MODE\n" - " Whether to check inhibitors before shutting down,\n" - " sleeping, or hibernating\n" - " -i Shortcut for --check-inhibitors=no\n" - " -s --signal=SIGNAL Which signal to send\n" - " --kill-whom=WHOM Whom to send signal to\n" - " --kill-value=INT Signal value to enqueue\n" - " --kill-subgroup=PATH\n" - " Send signal to sub-control group only\n" - " --what=RESOURCES Which types of resources to remove\n" - " --now Start or stop unit after enabling or disabling it\n" - " --dry-run Only print what would be done\n" - " Currently supported by verbs: halt, poweroff, reboot,\n" - " kexec, soft-reboot, suspend, hibernate, \n" - " suspend-then-hibernate, hybrid-sleep, default,\n" - " rescue, emergency, and exit.\n" - " -q --quiet Suppress output\n" - " -v --verbose Show unit logs while executing operation\n" - " --no-warn Suppress several warnings shown by default\n" - " --wait For (re)start, wait until service stopped again\n" - " For is-system-running, wait until startup is completed\n" - " For kill, wait until service stopped\n" - " --no-block Do not wait until operation finished\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n" - " --message=MESSAGE Specify human-readable reason for system shutdown\n" - " --no-reload Don't reload daemon after en-/dis-abling unit files\n" - " --legend=BOOL Enable/disable the legend (column headers and hints)\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password Do not ask for system passwords\n" - " --global Edit/enable/disable/mask default user unit files\n" - " globally\n" - " --runtime Edit/enable/disable/mask unit files temporarily until\n" - " next reboot\n" - " -f --force When enabling unit files, override existing symlinks\n" - " When shutting down, execute action immediately\n" - " --preset-mode= Apply only enable, only disable, or all presets\n" - " --root=PATH Edit/enable/disable/mask unit files in the specified\n" - " root directory\n" - " --image=PATH Edit/enable/disable/mask unit files in the specified\n" - " disk image\n" - " --image-policy=POLICY\n" - " Specify disk image dissection policy\n" - " -n --lines=INTEGER Number of journal entries to show\n" - " -o --output=STRING Change journal output mode (short, short-precise,\n" - " short-iso, short-iso-precise, short-full,\n" - " short-monotonic, short-unix, short-delta,\n" - " verbose, export, json, json-pretty, json-sse, cat)\n" - " --firmware-setup Tell the firmware to show the setup menu on next boot\n" - " --boot-loader-menu=TIME\n" - " Boot into boot loader menu on next boot\n" - " --boot-loader-entry=NAME\n" - " Boot into a specific boot loader entry on next boot\n" - " --reboot-argument=ARG\n" - " Specify argument string to pass to reboot()\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" - " --read-only Create read-only bind mount\n" - " --mkdir Create directory before mounting, if missing\n" - " --marked Restart/reload previously marked units\n" - " --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()); + return r; + + help_man_page_reference("systemctl", "1"); return 0; } -static void help_types(void) { - if (arg_legend != 0) - puts("Available unit types:"); +static int parse_property_argument(const char *value, char ***properties) { + int r; + + assert(value); + assert(properties); + + if (isempty(value) && !*properties) { + /* Make sure that if the empty property list was specified, we won't show any properties. */ + *properties = strv_new(NULL); + if (!*properties) + return log_oom(); + } else + for (const char *p = value;;) { + _cleanup_free_ char *prop = NULL; - DUMP_STRING_TABLE(unit_type, UnitType, _UNIT_TYPE_MAX); + r = extract_first_word(&p, &prop, ",", /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to parse property: '%s'", value); + if (r == 0) + break; + + if (strv_consume(properties, TAKE_PTR(prop)) < 0) + return log_oom(); + } + + return 0; } -static void help_states(void) { +static int help_states(void) { if (arg_legend != 0) puts("Available unit load states:"); DUMP_STRING_TABLE(unit_load_state, UnitLoadState, _UNIT_LOAD_STATE_MAX); @@ -388,669 +248,638 @@ static void help_states(void) { if (arg_legend != 0) puts("\nAvailable timer unit substates:"); - DUMP_STRING_TABLE(timer_state, TimerState, _TIMER_STATE_MAX); + return DUMP_STRING_TABLE(timer_state, TimerState, _TIMER_STATE_MAX); } -static int systemctl_parse_argv(int argc, char *argv[]) { - enum { - ARG_FAIL = 0x100, /* compatibility only */ - ARG_REVERSE, - ARG_AFTER, - ARG_BEFORE, - ARG_CHECK_INHIBITORS, - ARG_DRY_RUN, - ARG_SHOW_TYPES, - ARG_IRREVERSIBLE, /* compatibility only */ - ARG_IGNORE_DEPENDENCIES, /* compatibility only */ - ARG_VALUE, - ARG_VERSION, - ARG_USER, - ARG_SYSTEM, - ARG_GLOBAL, - ARG_NO_BLOCK, - ARG_LEGEND, - ARG_NO_LEGEND, /* compatibility only */ - ARG_NO_PAGER, - ARG_NO_WALL, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_NO_RELOAD, - ARG_KILL_WHOM, - ARG_KILL_VALUE, - ARG_NO_ASK_PASSWORD, - ARG_FAILED, - ARG_RUNTIME, - ARG_PLAIN, - ARG_STATE, - ARG_JOB_MODE, - ARG_PRESET_MODE, - ARG_FIRMWARE_SETUP, - ARG_BOOT_LOADER_MENU, - ARG_BOOT_LOADER_ENTRY, - ARG_NOW, - ARG_MESSAGE, - ARG_WITH_DEPENDENCIES, - ARG_WAIT, - ARG_WHAT, - ARG_REBOOT_ARG, - ARG_TIMESTAMP_STYLE, - ARG_READ_ONLY, - ARG_MKDIR, - ARG_MARKED, - ARG_NO_WARN, - ARG_DROP_IN, - ARG_WHEN, - ARG_STDIN, - ARG_KILL_SUBGROUP, - }; +static int parse_states_argument(const char *value, char ***states) { + int r; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "property", required_argument, NULL, 'p' }, - { "all", no_argument, NULL, 'a' }, - { "reverse", no_argument, NULL, ARG_REVERSE }, - { "after", no_argument, NULL, ARG_AFTER }, - { "before", no_argument, NULL, ARG_BEFORE }, - { "show-types", no_argument, NULL, ARG_SHOW_TYPES }, - { "failed", no_argument, NULL, ARG_FAILED }, - { "full", no_argument, NULL, 'l' }, - { "job-mode", required_argument, NULL, ARG_JOB_MODE }, - { "fail", no_argument, NULL, ARG_FAIL }, /* compatibility only */ - { "irreversible", no_argument, NULL, ARG_IRREVERSIBLE }, /* compatibility only */ - { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES }, /* compatibility only */ - { "ignore-inhibitors", no_argument, NULL, 'i' }, /* compatibility only */ - { "check-inhibitors", required_argument, NULL, ARG_CHECK_INHIBITORS }, - { "value", no_argument, NULL, ARG_VALUE }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "global", no_argument, NULL, ARG_GLOBAL }, - { "capsule", required_argument, NULL, 'C' }, - { "wait", no_argument, NULL, ARG_WAIT }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, /* compatibility only */ - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - { "dry-run", no_argument, NULL, ARG_DRY_RUN }, - { "quiet", no_argument, NULL, 'q' }, - { "verbose", no_argument, NULL, 'v' }, - { "no-warn", no_argument, NULL, ARG_NO_WARN }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "force", no_argument, NULL, 'f' }, - { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, - { "kill-whom", required_argument, NULL, ARG_KILL_WHOM }, - { "kill-value", required_argument, NULL, ARG_KILL_VALUE }, - { "signal", required_argument, NULL, 's' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "runtime", no_argument, NULL, ARG_RUNTIME }, - { "lines", required_argument, NULL, 'n' }, - { "output", required_argument, NULL, 'o' }, - { "plain", no_argument, NULL, ARG_PLAIN }, - { "state", required_argument, NULL, ARG_STATE }, - { "recursive", no_argument, NULL, 'r' }, - { "with-dependencies", no_argument, NULL, ARG_WITH_DEPENDENCIES }, - { "preset-mode", required_argument, NULL, ARG_PRESET_MODE }, - { "firmware-setup", no_argument, NULL, ARG_FIRMWARE_SETUP }, - { "boot-loader-menu", required_argument, NULL, ARG_BOOT_LOADER_MENU }, - { "boot-loader-entry", required_argument, NULL, ARG_BOOT_LOADER_ENTRY }, - { "now", no_argument, NULL, ARG_NOW }, - { "message", required_argument, NULL, ARG_MESSAGE }, - { "show-transaction", no_argument, NULL, 'T' }, - { "what", required_argument, NULL, ARG_WHAT }, - { "reboot-argument", required_argument, NULL, ARG_REBOOT_ARG }, - { "timestamp", required_argument, NULL, ARG_TIMESTAMP_STYLE }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "mkdir", no_argument, NULL, ARG_MKDIR }, - { "marked", no_argument, NULL, ARG_MARKED }, - { "drop-in", required_argument, NULL, ARG_DROP_IN }, - { "when", required_argument, NULL, ARG_WHEN }, - { "stdin", no_argument, NULL, ARG_STDIN }, - { "kill-subgroup", required_argument, NULL, ARG_KILL_SUBGROUP }, - {} - }; + assert(value); + assert(states); - int c, r; + if (isempty(value)) { + /* reset the setting */ + *states = strv_free(*states); + return 1; + } - assert(argc >= 0); - assert(argv); + for (const char *p = value;;) { + _cleanup_free_ char *s = NULL; - /* We default to allowing interactive authorization only in systemctl (not in the legacy commands) */ - arg_ask_password = true; + r = extract_first_word(&p, &s, ",", /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to parse --state=: %m"); + if (r == 0) + break; - while ((c = getopt_long(argc, argv, "hC:t:p:P:alqvfs:H:M:n:o:iTr.::", options, NULL)) >= 0) + if (streq(s, "help")) + return help_states(); - switch (c) { + if (strv_consume(states, TAKE_PTR(s)) < 0) + return log_oom(); + } - case 'h': - return systemctl_help(); + return 1; +} - case ARG_VERSION: - return version(); +static int help_types(void) { + if (arg_legend != 0) + puts("Available unit types:"); - case 't': - if (isempty(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--type= requires arguments."); + return DUMP_STRING_TABLE(unit_type, UnitType, _UNIT_TYPE_MAX); +} - for (const char *p = optarg;;) { - _cleanup_free_ char *type = NULL; +static int parse_types_argument(const char *value, char ***types, char ***states) { + int r; - r = extract_first_word(&p, &type, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse type: %s", optarg); - if (r == 0) - break; - - if (streq(type, "help")) { - help_types(); - return 0; - } - - if (unit_type_from_string(type) >= 0) { - if (strv_consume(&arg_types, TAKE_PTR(type)) < 0) - return log_oom(); - continue; - } - - /* It's much nicer to use --state= for load states, but let's support this in - * --types= too for compatibility with old versions */ - if (unit_load_state_from_string(type) >= 0) { - if (strv_consume(&arg_states, TAKE_PTR(type)) < 0) - return log_oom(); - continue; - } - - log_error("Unknown unit type or load state '%s'.", type); - return log_info_errno(SYNTHETIC_ERRNO(EINVAL), - "Use -t help to see a list of allowed values."); - } + assert(value); + assert(types); + assert(states); + if (isempty(value)) { + /* reset the setting */ + *types = strv_free(*types); + return 1; + } + + for (const char *p = value;;) { + _cleanup_free_ char *type = NULL; + + r = extract_first_word(&p, &type, ",", /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to parse -t/--type=: %m"); + if (r == 0) break; - case 'P': - SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); - _fallthrough_; - - case 'p': - /* Make sure that if the empty property list was specified, we won't show any - properties. */ - if (isempty(optarg) && !arg_properties) { - arg_properties = new0(char*, 1); - if (!arg_properties) - return log_oom(); - } else - for (const char *p = optarg;;) { - _cleanup_free_ char *prop = NULL; - - r = extract_first_word(&p, &prop, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse property: %s", optarg); - if (r == 0) - break; - - if (strv_consume(&arg_properties, TAKE_PTR(prop)) < 0) - return log_oom(); - } + if (streq(type, "help")) + return help_types(); - /* If the user asked for a particular property, show it, even if it is empty. */ - SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); + if (unit_type_from_string(type) >= 0) { + if (strv_consume(types, TAKE_PTR(type)) < 0) + return log_oom(); + continue; + } - break; + /* It's much nicer to use --state= for load states, but let's support this in + * --types= too for compatibility with old versions */ + if (unit_load_state_from_string(type) >= 0) { + if (strv_consume(states, TAKE_PTR(type)) < 0) + return log_oom(); + continue; + } - case 'a': - SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); - arg_all = true; - break; + log_error("Unknown unit type or load state '%s'.", type); + return log_info_errno(SYNTHETIC_ERRNO(EINVAL), + "Use -t help to see a list of allowed values."); + } - case ARG_REVERSE: - arg_dependency = DEPENDENCY_REVERSE; - break; + return 1; +} - case ARG_AFTER: - arg_dependency = DEPENDENCY_AFTER; - arg_jobs_after = true; - break; +static int parse_what_argument(const char *value, char ***clean_what) { + int r; - case ARG_BEFORE: - arg_dependency = DEPENDENCY_BEFORE; - arg_jobs_before = true; - break; + assert(value); + assert(clean_what); - case ARG_SHOW_TYPES: - arg_show_types = true; - break; + if (isempty(value)) { + /* reset the setting */ + *clean_what = strv_free(*clean_what); + return 1; + } - case ARG_VALUE: - SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); - break; + for (const char *p = value;;) { + _cleanup_free_ char *k = NULL; - case ARG_JOB_MODE: - _arg_job_mode = optarg; + r = extract_first_word(&p, &k, ",", /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to parse --what=: %m"); + if (r == 0) break; - case ARG_FAIL: - _arg_job_mode = "fail"; - break; + if (streq(k, "help")) { + puts("runtime\n" + "state\n" + "cache\n" + "logs\n" + "configuration\n" + "fdstore\n" + "all"); + return 0; + } - case ARG_IRREVERSIBLE: - _arg_job_mode = "replace-irreversibly"; - break; + r = strv_consume(clean_what, TAKE_PTR(k)); + if (r < 0) + return log_oom(); + } - case ARG_IGNORE_DEPENDENCIES: - _arg_job_mode = "ignore-dependencies"; - break; + return 1; +} - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; - break; +static int systemctl_parse_argv(int argc, char *argv[], int log_level_shift, char ***remaining_args) { + int r; - case ARG_SYSTEM: + assert(argc >= 0); + assert(argv); + + /* We default to allowing interactive authorization only in systemctl (not in the legacy commands) */ + arg_ask_password = true; + + OptionParser opts = { + argc, argv, + .namespace = "systemctl", + .log_level_shift = log_level_shift, + }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_NAMESPACE("systemctl"): {} + + OPTION_COMMON_HELP: + return systemctl_help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_LONG("system", NULL, "Connect to the system service manager"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case ARG_GLOBAL: - arg_runtime_scope = RUNTIME_SCOPE_GLOBAL; + OPTION_LONG("user", NULL, "Connect to the user service manager"): + arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case 'C': - r = capsule_name_is_valid(optarg); + OPTION('C', "capsule", "NAME", "Connect to service manager of specified capsule"): + r = capsule_name_is_valid(opts.arg); if (r < 0) - return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); + return log_error_errno(r, "Unable to validate capsule name '%s': %m", opts.arg); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", opts.arg); - arg_host = optarg; + arg_host = opts.arg; arg_transport = BUS_TRANSPORT_CAPSULE; arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_WAIT: - arg_wait = true; - break; - - case ARG_NO_BLOCK: - arg_no_block = true; - break; - - case ARG_NO_LEGEND: - arg_legend = false; + OPTION_COMMON_HOST: + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = opts.arg; break; - case ARG_LEGEND: - r = parse_boolean_argument("--legend", optarg, NULL); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; - arg_legend = r; break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION('t', "type", "TYPE", "List units of a particular type"): + r = parse_types_argument(opts.arg, &arg_types, &arg_states); + if (r <= 0) + return r; break; - case ARG_NO_WALL: - arg_no_wall = true; + OPTION_LONG("state", "STATE", "List units with particular LOAD or SUB or ACTIVE state"): + r = parse_states_argument(opts.arg, &arg_states); + if (r <= 0) + return r; break; - case ARG_ROOT: - r = parse_path_argument(optarg, false, &arg_root); - if (r < 0) - return r; + OPTION_LONG("failed", NULL, "Shortcut for --state=failed"): + if (strv_extend(&arg_states, "failed") < 0) + return log_oom(); break; - case ARG_IMAGE: - r = parse_path_argument(optarg, false, &arg_image); + OPTION('p', "property", "NAME", "Show only properties by this name"): {} + OPTION_SHORT('P', "NAME", "Equivalent to --value --property=NAME"): + r = parse_property_argument(opts.arg, &arg_properties); if (r < 0) return r; + + /* If the user asked for a particular property, show it, even if it is empty. */ + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); + + if (opts.opt->short_code == 'P') + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); + break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); - if (r < 0) - return r; + OPTION('a', "all", NULL, + "Show all properties/all units currently in memory, including dead/empty ones. " + "To list all units installed on the system, use 'list-unit-files' instead"): + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); + arg_all = true; break; - case 'l': + OPTION('l', "full", NULL, "Don't ellipsize unit names on output"): arg_full = true; break; - case ARG_FAILED: - if (strv_extend(&arg_states, "failed") < 0) - return log_oom(); + OPTION('r', "recursive", NULL, "Show unit list of host and local containers"): + if (geteuid() != 0) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "--recursive requires root privileges"); + arg_recursive = true; break; - case ARG_DRY_RUN: - arg_dry_run = true; + OPTION_LONG("reverse", NULL, "Show reverse dependencies with 'list-dependencies'"): + arg_dependency = DEPENDENCY_REVERSE; break; - case 'q': - arg_quiet = true; + OPTION_LONG("before", NULL, "Show units ordered before with 'list-dependencies'"): + arg_dependency = DEPENDENCY_BEFORE; + arg_jobs_before = true; + break; - if (arg_legend < 0) - arg_legend = false; + OPTION_LONG("after", NULL, "Show units ordered after with 'list-dependencies'"): + arg_dependency = DEPENDENCY_AFTER; + arg_jobs_after = true; + break; + + OPTION_LONG("with-dependencies", NULL, + "Show unit dependencies with 'status', 'cat', 'list-units', and 'list-unit-files'"): + arg_with_dependencies = true; + break; + OPTION_LONG("job-mode", "MODE", + "Specify how to deal with already queued jobs, when queueing a new job"): + _arg_job_mode = opts.arg; break; - case 'v': - arg_verbose = true; + OPTION('T', "show-transaction", NULL, "When enqueuing a unit job, show full transaction"): + arg_show_transaction = true; break; - case 'f': - arg_force++; + OPTION_LONG("show-types", NULL, "When showing sockets, explicitly show their type"): + arg_show_types = true; break; - case ARG_NO_RELOAD: - arg_no_reload = true; + OPTION_LONG("value", NULL, "When showing properties, only print the value"): + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); break; - case ARG_KILL_WHOM: - arg_kill_whom = optarg; + OPTION_LONG("check-inhibitors", "MODE", + "Whether to check inhibitors before shutting down, sleeping, or hibernating"): + r = parse_tristate_argument_with_auto("--check-inhibitors=", opts.arg, &arg_check_inhibitors); + if (r < 0) + return r; break; - case ARG_KILL_VALUE: { + OPTION_LONG("ignore-inhibitors", NULL, /* help= */ NULL): {} + + OPTION_SHORT('i', NULL, "Shortcut for --check-inhibitors=no"): + arg_check_inhibitors = 0; + break; + + OPTION('s', "signal", "SIGNAL", "Which signal to send"): + r = parse_signal_argument(opts.arg, &arg_signal); + if (r <= 0) + return r; + break; + + OPTION_LONG("kill-whom", "WHOM", "Whom to send signal to"): + arg_kill_whom = opts.arg; + break; + + OPTION_LONG("kill-value", "INT", "Signal value to enqueue"): { unsigned u; - if (isempty(optarg)) { + if (isempty(opts.arg)) { arg_kill_value_set = false; return 0; } /* First, try to parse unsigned, so that we can support the prefixes 0x, 0o, 0b */ - r = safe_atou_full(optarg, 0, &u); + r = safe_atou_full(opts.arg, 0, &u); if (r < 0) /* If this didn't work, try as signed integer, without those prefixes */ - r = safe_atoi(optarg, &arg_kill_value); + r = safe_atoi(opts.arg, &arg_kill_value); else if (u > INT_MAX) r = -ERANGE; else arg_kill_value = (int) u; if (r < 0) - return log_error_errno(r, "Unable to parse signal queue value: %s", optarg); + return log_error_errno(r, "Unable to parse signal queue value: %s", opts.arg); arg_kill_value_set = true; break; } - case 's': - r = parse_signal_argument(optarg, &arg_signal); + OPTION_LONG("kill-subgroup", "PATH", "Send signal to sub-control group only"): { + if (empty_or_root(opts.arg)) { + arg_kill_subgroup = mfree(arg_kill_subgroup); + break; + } + + _cleanup_free_ char *p = NULL; + if (path_simplify_alloc(opts.arg, &p) < 0) + return log_oom(); + + if (!path_is_safe(p)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Control group sub-path '%s' is not valid.", p); + + free_and_replace(arg_kill_subgroup, p); + break; + } + + OPTION_LONG("what", "RESOURCES", "Which types of resources to remove"): + r = parse_what_argument(opts.arg, &arg_clean_what); if (r <= 0) return r; break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; + OPTION_LONG("now", NULL, "Start or stop unit after enabling or disabling it"): + arg_now = true; break; - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + OPTION_LONG("dry-run", NULL, + "Only print what would be done. Currently supported by verbs: halt, poweroff, " + "reboot, kexec, soft-reboot, suspend, hibernate, suspend-then-hibernate, " + "hybrid-sleep, default, rescue, emergency, and exit."): + arg_dry_run = true; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); - if (r < 0) - return r; + OPTION('q', "quiet", NULL, "Suppress output"): + arg_quiet = true; + + if (arg_legend < 0) + arg_legend = false; + break; - case ARG_RUNTIME: - arg_runtime = true; + OPTION('v', "verbose", NULL, "Show unit logs while executing operation"): + arg_verbose = true; break; - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse lines '%s'", - optarg); + OPTION_LONG("no-warn", NULL, "Suppress several warnings shown by default"): + arg_no_warn = true; break; - case 'o': - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); + OPTION_LONG("wait", NULL, + "For (re)start, wait until service stopped again. " + "For is-system-running, wait until startup is completed. " + "For kill, wait until service stopped."): + arg_wait = true; + break; - arg_output = output_mode_from_string(optarg); - if (arg_output < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown output '%s'.", - optarg); + OPTION_LONG("no-block", NULL, "Do not wait until operation finished"): + arg_no_block = true; + break; - if (OUTPUT_MODE_IS_JSON(arg_output)) { - arg_legend = false; - arg_plain = true; - } + OPTION_LONG("no-wall", NULL, "Don't send wall message before halt/power-off/reboot"): + arg_no_wall = true; break; - case 'i': - arg_check_inhibitors = 0; + OPTION_LONG("message", "MESSAGE", "Specify human-readable reason for system shutdown"): + if (strv_extend(&arg_wall, opts.arg) < 0) + return log_oom(); + break; + + OPTION_LONG("no-reload", NULL, "Don't reload daemon after en-/dis-abling unit files"): + arg_no_reload = true; break; - case ARG_CHECK_INHIBITORS: - r = parse_tristate_argument_with_auto("--check-inhibitors=", optarg, &arg_check_inhibitors); + OPTION_LONG("legend", "BOOL", "Enable/disable the legend (column headers and hints)"): + r = parse_boolean_argument("--legend", opts.arg, NULL); if (r < 0) return r; + arg_legend = r; break; - case ARG_PLAIN: - arg_plain = true; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_FIRMWARE_SETUP: - arg_firmware_setup = true; + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; break; - case ARG_BOOT_LOADER_MENU: + OPTION_LONG("global", NULL, "Edit/enable/disable/mask default user unit files globally"): + arg_runtime_scope = RUNTIME_SCOPE_GLOBAL; + break; - r = parse_sec(optarg, &arg_boot_loader_menu); - if (r < 0) - return log_error_errno(r, "Failed to parse --boot-loader-menu= argument '%s': %m", optarg); + OPTION_LONG("runtime", NULL, + "Edit/enable/disable/mask unit files temporarily until next reboot"): + arg_runtime = true; + break; + OPTION('f', "force", NULL, + "When enabling unit files, override existing symlinks. " + "When shutting down, execute action immediately."): + arg_force++; break; - case ARG_BOOT_LOADER_ENTRY: + OPTION_LONG("preset-mode", "MODE", "Apply only enable, only disable, or all presets"): + if (streq(opts.arg, "help")) + return DUMP_STRING_TABLE(unit_file_preset_mode, UnitFilePresetMode, _UNIT_FILE_PRESET_MODE_MAX); - if (streq(optarg, "help")) { /* Yes, this means, "help" is not a valid boot loader entry name we can deal with */ - r = help_boot_loader_entry(); - if (r < 0) - return r; + arg_preset_mode = unit_file_preset_mode_from_string(opts.arg); + if (arg_preset_mode < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse preset mode: %s.", opts.arg); - return 0; - } + break; - arg_boot_loader_entry = empty_to_null(optarg); + OPTION_LONG("root", "PATH", + "Edit/enable/disable/mask unit files in the specified root directory"): + r = parse_path_argument(opts.arg, false, &arg_root); + if (r < 0) + return r; break; - case ARG_STATE: - if (isempty(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--state= requires arguments."); + OPTION_LONG("image", "PATH", + "Edit/enable/disable/mask unit files in the specified disk image"): + r = parse_path_argument(opts.arg, false, &arg_image); + if (r < 0) + return r; + break; - for (const char *p = optarg;;) { - _cleanup_free_ char *s = NULL; + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(opts.arg, &arg_image_policy); + if (r < 0) + return r; + break; - r = extract_first_word(&p, &s, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse state: %s", optarg); - if (r == 0) - break; + OPTION('n', "lines", "INTEGER", "Number of journal entries to show"): + if (safe_atou(opts.arg, &arg_lines) < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse lines '%s'", + opts.arg); + break; - if (streq(s, "help")) { - help_states(); - return 0; - } + OPTION('o', "output", "STRING", + "Change journal output mode (short, short-precise, short-iso, short-iso-precise, " + "short-full, short-monotonic, short-unix, short-delta, verbose, export, json, " + "json-pretty, json-sse, cat)"): + if (streq(opts.arg, "help")) + return DUMP_STRING_TABLE(output_mode, OutputMode, _OUTPUT_MODE_MAX); + + arg_output = output_mode_from_string(opts.arg); + if (arg_output < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown output '%s'.", + opts.arg); - if (strv_consume(&arg_states, TAKE_PTR(s)) < 0) - return log_oom(); + if (OUTPUT_MODE_IS_JSON(arg_output)) { + arg_legend = false; + arg_plain = true; } break; - case 'r': - if (geteuid() != 0) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), - "--recursive requires root privileges."); + OPTION_LONG("firmware-setup", NULL, "Tell the firmware to show the setup menu on next boot"): + arg_firmware_setup = true; + break; + + OPTION_LONG("boot-loader-menu", "TIME", "Boot into boot loader menu on next boot"): + r = parse_sec(opts.arg, &arg_boot_loader_menu); + if (r < 0) + return log_error_errno(r, "Failed to parse --boot-loader-menu= argument '%s': %m", opts.arg); - arg_recursive = true; break; - case ARG_PRESET_MODE: - if (streq(optarg, "help")) - return DUMP_STRING_TABLE(unit_file_preset_mode, UnitFilePresetMode, _UNIT_FILE_PRESET_MODE_MAX); + OPTION_LONG("boot-loader-entry", "NAME", + "Boot into a specific boot loader entry on next boot"): + if (streq(opts.arg, "help")) { /* Yes, this means, "help" is not a valid boot loader entry name we can deal with */ + r = help_boot_loader_entry(); + if (r < 0) + return r; - arg_preset_mode = unit_file_preset_mode_from_string(optarg); - if (arg_preset_mode < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse preset mode: %s.", optarg); + return 0; + } + arg_boot_loader_entry = empty_to_null(opts.arg); break; - case ARG_NOW: - arg_now = true; + OPTION_LONG("reboot-argument", "ARG", "Specify argument string to pass to reboot()"): + arg_reboot_argument = opts.arg; break; - case ARG_MESSAGE: - if (strv_extend(&arg_wall, optarg) < 0) - return log_oom(); - break; + OPTION_LONG("kernel-cmdline", "CMDLINE", + "Append to the kernel command line when loading the kernel " + "from the booted boot loader entry"): + if (isempty(opts.arg)) { + arg_kernel_cmdline = mfree(arg_kernel_cmdline); + break; + } - case 'T': - arg_show_transaction = true; - break; + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--kernel-cmdline= argument contains invalid characters: %s", opts.arg); - case ARG_WITH_DEPENDENCIES: - arg_with_dependencies = true; + if (!strextend_with_separator(&arg_kernel_cmdline, " ", opts.arg)) + return log_oom(); break; - case ARG_WHAT: - if (isempty(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--what= requires arguments (see --what=help)."); + OPTION_LONG("kernel-cmdline-reuse", NULL, + "Like --kernel-cmdline=, but reuse the current kernel command line"): { + _cleanup_free_ char *cmdline = NULL; - for (const char *p = optarg;;) { - _cleanup_free_ char *k = NULL; + r = proc_cmdline(&cmdline); + if (r < 0) + return log_error_errno(r, "Failed to read current kernel command line: %m"); - r = extract_first_word(&p, &k, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse directory type: %s", optarg); - if (r == 0) - break; - - if (streq(k, "help")) { - puts("runtime\n" - "state\n" - "cache\n" - "logs\n" - "configuration\n" - "fdstore\n" - "all"); - return 0; - } - - r = strv_consume(&arg_clean_what, TAKE_PTR(k)); - if (r < 0) - return log_oom(); - } + const char *stripped = empty_to_null(strstrip(cmdline)); + if (!stripped) + break; + + if (!string_is_safe(stripped, STRING_ALLOW_GLOBS|STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Current kernel command line contains invalid characters: %s", stripped); + r = free_and_strdup_warn(&arg_reuse_kernel_cmdline, stripped); + if (r < 0) + return r; break; + } - case ARG_REBOOT_ARG: - arg_reboot_argument = optarg; + OPTION_LONG("plain", NULL, "Print unit dependencies as a list instead of a tree"): + arg_plain = true; break; - case ARG_TIMESTAMP_STYLE: - if (streq(optarg, "help")) + OPTION_LONG("timestamp", "FORMAT", + "Change format of printed timestamps (pretty, unix, us, utc, us+utc)"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(timestamp_style, TimestampStyle, _TIMESTAMP_STYLE_MAX); - arg_timestamp_style = timestamp_style_from_string(optarg); + arg_timestamp_style = timestamp_style_from_string(opts.arg); if (arg_timestamp_style < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid value: %s.", optarg); + "Invalid value: %s.", opts.arg); break; - case ARG_READ_ONLY: + OPTION_LONG("read-only", NULL, "Create read-only bind mount"): arg_read_only = true; break; - case ARG_MKDIR: + OPTION_LONG("mkdir", NULL, "Create directory before mounting, if missing"): arg_mkdir = true; break; - case ARG_MARKED: + OPTION_LONG("marked", NULL, "Restart/reload previously marked units"): arg_marked = true; break; - case ARG_NO_WARN: - arg_no_warn = true; - break; - - case ARG_DROP_IN: - arg_drop_in = optarg; + OPTION_LONG("drop-in", "NAME", "Edit unit files using the specified drop-in file name"): + arg_drop_in = opts.arg; break; - case ARG_WHEN: - if (streq(optarg, "show")) { + OPTION_LONG("when", "TIME", + "Schedule halt/power-off/reboot/kexec action after a certain timestamp"): + if (streq(opts.arg, "show")) { arg_action = ACTION_SYSTEMCTL_SHOW_SHUTDOWN; return 1; } - if (STR_IN_SET(optarg, "", "cancel")) { + if (STR_IN_SET(opts.arg, "", "cancel")) { arg_action = ACTION_CANCEL_SHUTDOWN; return 1; } - if (streq(optarg, "auto")) { + if (streq(opts.arg, "auto")) { arg_when = USEC_INFINITY; /* logind chooses on server side */ break; } - r = parse_timestamp(optarg, &arg_when); + r = parse_timestamp(opts.arg, &arg_when); if (r < 0) - return log_error_errno(r, "Failed to parse --when= argument '%s': %m", optarg); + return log_error_errno(r, "Failed to parse --when= argument '%s': %m", opts.arg); if (!timestamp_is_set(arg_when)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid timestamp '%s' specified for --when=.", optarg); + "Invalid timestamp '%s' specified for --when=.", opts.arg); break; - case ARG_STDIN: + OPTION_LONG("stdin", NULL, "Read new contents of edited file from stdin"): arg_stdin = true; break; - case ARG_KILL_SUBGROUP: { - if (empty_or_root(optarg)) { - arg_kill_subgroup = mfree(arg_kill_subgroup); - break; - } + /* Compatibility-only options, not shown in --help. */ + OPTION_LONG("fail", NULL, /* help= */ NULL): + _arg_job_mode = "fail"; + break; - _cleanup_free_ char *p = NULL; - if (path_simplify_alloc(optarg, &p) < 0) - return log_oom(); + OPTION_LONG("irreversible", NULL, /* help= */ NULL): + _arg_job_mode = "replace-irreversibly"; + break; - if (!path_is_safe(p)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Control group sub-path '%s' is not valid.", p); + OPTION_LONG("ignore-dependencies", NULL, /* help= */ NULL): + _arg_job_mode = "ignore-dependencies"; + break; - free_and_replace(arg_kill_subgroup, p); + OPTION_LONG("no-legend", NULL, /* help= */ NULL): + arg_legend = false; break; - } - case '.': + OPTION_SHORT_FLAGS(OPTION_OPTIONAL_ARG, '.', "ARG", /* help= */ NULL): /* Output an error mimicking getopt, and print a hint afterwards */ log_error("%s: invalid option -- '.'", program_invocation_name); log_notice("Hint: to specify units starting with a dash, use \"--\":\n" - " %s [OPTIONS...] COMMAND -- -.%s ...", - program_invocation_name, optarg ?: "mount"); - _fallthrough_; - - case '?': + " %s [OPTIONS…] COMMAND -- -.%s …", + program_invocation_name, opts.arg ?: "mount"); return -EINVAL; - - default: - assert_not_reached(); } /* If we are in --user mode, there's no point in talking to PolicyKit or the infra to query system @@ -1070,17 +899,20 @@ static int systemctl_parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--wait may not be combined with --no-block."); - bool do_reload_or_restart = streq_ptr(argv[optind], "reload-or-restart"); + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); + + bool do_reload_or_restart = streq_ptr(args[0], "reload-or-restart"); if (arg_marked) { if (!do_reload_or_restart) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--marked may only be used with 'reload-or-restart'."); - if (optind + 1 < argc) + if (n_args > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No additional arguments allowed with 'reload-or-restart --marked'."); } else if (do_reload_or_restart) { - if (optind + 1 >= argc) + if (n_args <= 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "List of units to restart/reload is required."); } @@ -1089,30 +921,228 @@ static int systemctl_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."); + if (remaining_args) + *remaining_args = args; return 1; } -int systemctl_dispatch_parse_argv(int argc, char *argv[]) { +int systemctl_dispatch_parse_argv(int argc, char *argv[], int log_level_shift, char ***remaining_args) { assert(argc >= 0); assert(argv); if (invoked_as(argv, "halt")) { arg_action = ACTION_HALT; - return halt_parse_argv(argc, argv); + return halt_parse_argv(argc, argv, log_level_shift); } else if (invoked_as(argv, "poweroff")) { arg_action = ACTION_POWEROFF; - return halt_parse_argv(argc, argv); + return halt_parse_argv(argc, argv, log_level_shift); } else if (invoked_as(argv, "reboot")) { arg_action = ACTION_REBOOT; - return halt_parse_argv(argc, argv); + return halt_parse_argv(argc, argv, log_level_shift); } else if (invoked_as(argv, "shutdown")) { arg_action = ACTION_POWEROFF; - return shutdown_parse_argv(argc, argv); + return shutdown_parse_argv(argc, argv, log_level_shift); + + } else { + arg_action = ACTION_SYSTEMCTL; + return systemctl_parse_argv(argc, argv, log_level_shift, remaining_args); } +} + +VERB_GROUP("Unit Commands"); + +VERB_SCOPE(, verb_list_units, "list-units", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, + "List units currently in memory"); +VERB_SCOPE(, verb_list_automounts, "list-automounts", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "List automount units currently in memory, ordered by path"); +VERB_SCOPE(, verb_list_paths, "list-paths", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "List path units currently in memory, ordered by path"); +VERB_SCOPE(, verb_list_sockets, "list-sockets", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "List socket units currently in memory, ordered by address"); +VERB_SCOPE(, verb_list_timers, "list-timers", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "List timer units currently in memory, ordered by next elapse"); +VERB_SCOPE(, verb_is_active, "is-active", "PATTERN…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Check whether units are active"); +VERB_SCOPE(, verb_is_failed, "is-failed", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Check whether units are failed or system is in degraded state"); +VERB_SCOPE(, verb_show, "status", "[PATTERN…|PID…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Show runtime status of one or more units"); +VERB_SCOPE(, verb_show, "show", "[PATTERN…|JOB…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Show properties of one or more units/jobs or the manager"); +VERB_SCOPE(, verb_cat, "cat", "PATTERN…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Show files and drop-ins of specified units"); +VERB_SCOPE(, verb_show, "help", "PATTERN…|PID…", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Show manual for one or more units"); +VERB_SCOPE(, verb_list_dependencies, "list-dependencies", "[UNIT…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Recursively show units which are required or wanted by the units " + "or by which those units are required or wanted"); +VERB_SCOPE(, verb_start, "start", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Start (activate) one or more units"); +VERB_SCOPE(, verb_start, "stop", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Stop (deactivate) one or more units"); +VERB_SCOPE(, verb_start, "reload", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Reload one or more units"); +VERB_SCOPE(, verb_start, "restart", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Start or restart one or more units"); +VERB_SCOPE(, verb_start, "try-restart", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Restart one or more units if active"); +VERB_SCOPE(, verb_start, "enqueue-marked", NULL, 1, 1, VERB_ONLINE_ONLY, + "Enqueue jobs for all marked units"); +VERB_SCOPE(, verb_start, "reload-or-restart", "UNIT…", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Reload one or more units if possible, otherwise start or restart"); +VERB_SCOPE(, verb_start, "try-reload-or-restart", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "If active, reload one or more units, if supported, otherwise restart"); +VERB_SCOPE(, verb_start, "isolate", "UNIT", 2, 2, VERB_ONLINE_ONLY, + "Start one unit and stop all others"); +VERB_SCOPE(, verb_kill, "kill", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Send signal to processes of a unit"); +VERB_SCOPE(, verb_clean_or_freeze, "clean", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Clean runtime, cache, state, logs or configuration of unit"); +VERB_SCOPE(, verb_clean_or_freeze, "freeze", "PATTERN…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Freeze execution of unit processes"); +VERB_SCOPE(, verb_clean_or_freeze, "thaw", "PATTERN…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Resume execution of a frozen unit"); +VERB_SCOPE(, verb_set_property, "set-property", "UNIT PROPERTY=VALUE…", 3, VERB_ANY, VERB_ONLINE_ONLY, + "Sets one or more properties of a unit"); +VERB_SCOPE(, verb_bind, "bind", "UNIT PATH [PATH]", 3, 4, VERB_ONLINE_ONLY, + "Bind-mount a path from the host into a unit's namespace"); +VERB_SCOPE(, verb_mount_image, "mount-image", "UNIT PATH [PATH [OPTS]]", 4, 5, VERB_ONLINE_ONLY, + "Mount an image from the host into a unit's namespace"); +VERB_SCOPE(, verb_service_log_setting, "service-log-level", "SERVICE [LEVEL]", 2, 3, VERB_ONLINE_ONLY, + "Get/set logging threshold for service"); +VERB_SCOPE(, verb_service_log_setting, "service-log-target", "SERVICE [TARGET]", 2, 3, VERB_ONLINE_ONLY, + "Get/set logging target for service"); +VERB_SCOPE(, verb_reset_failed, "reset-failed", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Reset failed state for all, one, or more units"); +VERB_SCOPE(, verb_whoami, "whoami", "[PID…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Return unit caller or specified PIDs are part of"); + +VERB_GROUP("Unit File Commands"); + +VERB_SCOPE(, verb_list_unit_files, "list-unit-files", "[PATTERN…]", VERB_ANY, VERB_ANY, 0, + "List installed unit files"); +VERB_SCOPE(, verb_enable, "enable", "[UNIT…|PATH…]", 2, VERB_ANY, 0, + "Enable one or more unit files"); +VERB_SCOPE(, verb_enable, "disable", "UNIT…", 2, VERB_ANY, 0, + "Disable one or more unit files"); +VERB_SCOPE(, verb_enable, "reenable", "UNIT…", 2, VERB_ANY, 0, + "Reenable one or more unit files"); +VERB_SCOPE(, verb_enable, "preset", "UNIT…", 2, VERB_ANY, 0, + "Enable/disable one or more unit files based on preset configuration"); +VERB_SCOPE(, verb_preset_all, "preset-all", NULL, VERB_ANY, 1, 0, + "Enable/disable all unit files based on preset configuration"); +VERB_SCOPE(, verb_is_enabled, "is-enabled", "UNIT…", 2, VERB_ANY, 0, + "Check whether unit files are enabled"); +VERB_SCOPE(, verb_enable, "mask", "UNIT…", 2, VERB_ANY, 0, + "Mask one or more units"); +VERB_SCOPE(, verb_enable, "unmask", "UNIT…", 2, VERB_ANY, 0, + "Unmask one or more units"); +VERB_SCOPE(, verb_enable, "link", "PATH…", 2, VERB_ANY, 0, + "Link one or more units files into the search path"); +VERB_SCOPE(, verb_enable, "revert", "UNIT…", 2, VERB_ANY, 0, + "Revert one or more unit files to vendor version"); +VERB_SCOPE(, verb_add_dependency, "add-wants", "TARGET UNIT…", 3, VERB_ANY, 0, + "Add 'Wants' dependency for the target on specified one or more units"); +VERB_SCOPE(, verb_add_dependency, "add-requires", "TARGET UNIT…", 3, VERB_ANY, 0, + "Add 'Requires' dependency for the target on specified one or more units"); +VERB_SCOPE(, verb_edit, "edit", "UNIT…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Edit one or more unit files"); +VERB_SCOPE(, verb_get_default, "get-default", NULL, VERB_ANY, 1, 0, + "Get the name of the default target"); +VERB_SCOPE(, verb_set_default, "set-default", "TARGET", 2, 2, 0, + "Set the default target"); + +VERB_GROUP("Machine Commands"); + +VERB_SCOPE(, verb_list_machines, "list-machines", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "List local containers and host"); + +VERB_GROUP("Job Commands"); + +VERB_SCOPE(, verb_list_jobs, "list-jobs", "[PATTERN…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "List jobs"); +VERB_SCOPE(, verb_cancel, "cancel", "[JOB…]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Cancel all, one, or more jobs"); + +VERB_GROUP("Environment Commands"); + +VERB_SCOPE(, verb_show_environment, "show-environment", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Dump environment"); +VERB_SCOPE(, verb_set_environment, "set-environment", "VARIABLE=VALUE…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Set one or more environment variables"); +VERB_SCOPE(, verb_set_environment, "unset-environment", "VARIABLE…", 2, VERB_ANY, VERB_ONLINE_ONLY, + "Unset one or more environment variables"); +VERB_SCOPE(, verb_import_environment, "import-environment", "VARIABLE…", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Import all or some environment variables"); + +VERB_GROUP("Manager State Commands"); + +VERB_SCOPE(, verb_daemon_reload, "daemon-reload", NULL, 1, 1, VERB_ONLINE_ONLY, + "Reload systemd manager configuration"); +VERB_SCOPE(, verb_daemon_reload, "daemon-reexec", NULL, 1, 1, VERB_ONLINE_ONLY, + "Reexecute systemd manager"); +VERB_SCOPE(, verb_log_setting, "log-level", "[LEVEL]", VERB_ANY, 2, VERB_ONLINE_ONLY, + "Get/set logging threshold for manager"); +VERB_SCOPE(, verb_log_setting, "log-target", "[TARGET]", VERB_ANY, 2, VERB_ONLINE_ONLY, + "Get/set logging target for manager"); +VERB_SCOPE(, verb_service_watchdogs, "service-watchdogs", "[BOOL]", VERB_ANY, 2, VERB_ONLINE_ONLY, + "Get/set service watchdog state"); + +VERB_GROUP("System Commands"); + +VERB_SCOPE(, verb_is_system_running, "is-system-running", NULL, VERB_ANY, 1, 0, + "Check whether system is fully running"); +VERB_SCOPE(, verb_start_special, "default", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Enter system default mode"); +VERB_SCOPE(, verb_start_system_special, "rescue", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Enter system rescue mode"); +VERB_SCOPE(, verb_start_system_special, "emergency", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Enter system emergency mode"); +VERB_SCOPE(, verb_start_system_special, "halt", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Shut down and halt the system"); +VERB_SCOPE(, verb_start_system_special, "poweroff", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Shut down and power-off the system"); +VERB_SCOPE(, verb_start_system_special, "reboot", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Shut down and reboot the system"); +VERB_SCOPE(, verb_start_system_special, "kexec", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Shut down and reboot the system with kexec"); +VERB_SCOPE(, verb_start_system_special, "soft-reboot", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Shut down and reboot userspace"); +VERB_SCOPE(, verb_start_special, "exit", "[EXIT_CODE]", VERB_ANY, 2, VERB_ONLINE_ONLY, + "Request user instance or container exit"); +VERB_SCOPE(, verb_switch_root, "switch-root", "[ROOT [INIT]]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, + "Change to a different root file system"); +VERB_SCOPE(, verb_start_system_special, "sleep", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Put the system to sleep (through one of the operations below)"); +VERB_SCOPE(, verb_start_system_special, "suspend", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Suspend the system"); +VERB_SCOPE(, verb_start_system_special, "hibernate", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Hibernate the system"); +VERB_SCOPE(, verb_start_system_special, "hybrid-sleep", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Hibernate and suspend the system"); +VERB_SCOPE(, verb_start_system_special, "suspend-then-hibernate", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, + "Suspend the system, wake after a period of time, and hibernate"); + +/* Compatibility aliases / deprecated verbs hidden from --help. */ +VERB_SCOPE(, verb_trivial_method, "clear-jobs", NULL, VERB_ANY, 1, VERB_ONLINE_ONLY, /* help= */ NULL); /* systemctl < 4 */ +VERB_SCOPE(, verb_start, "condstop", NULL, 2, VERB_ANY, VERB_ONLINE_ONLY, /* help= */ NULL); /* ALTLinux */ +VERB_SCOPE(, verb_start, "reload-or-try-restart", NULL, 2, VERB_ANY, VERB_ONLINE_ONLY, /* help= */ NULL); /* systemctl <= 228 */ +VERB_SCOPE(, verb_start, "force-reload", NULL, 2, VERB_ANY, VERB_ONLINE_ONLY, /* help= */ NULL); /* SysV */ +VERB_SCOPE(, verb_start, "condreload", NULL, 2, VERB_ANY, VERB_ONLINE_ONLY, /* help= */ NULL); /* ALTLinux */ +VERB_SCOPE(, verb_start, "condrestart", NULL, 2, VERB_ANY, VERB_ONLINE_ONLY, /* help= */ NULL); /* RH */ +VERB_SCOPE(, verb_is_active, "check", NULL, 2, VERB_ANY, VERB_ONLINE_ONLY, /* help= */ NULL); /* deprecated alias of is-active */ + +int systemctl_main(char **args) { + assert((uintptr_t) __start_SYSTEMD_VERBS % sizeof(void*) == 0); + const Verb *verb = verbs_find_verb(args[0], __start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS); + + 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=.", + args[0] ?: verb->verb); - arg_action = ACTION_SYSTEMCTL; - return systemctl_parse_argv(argc, argv); + return dispatch_verb(args, NULL); } diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index 8d4b1a614da28..8f05bdb46e5f4 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -76,6 +76,8 @@ 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 char *arg_reuse_kernel_cmdline; extern enum action arg_action; extern BusTransport arg_transport; extern const char *arg_host; @@ -101,4 +103,5 @@ static inline const char* arg_job_mode(void) { return _arg_job_mode ?: "replace"; } -int systemctl_dispatch_parse_argv(int argc, char *argv[]); +int systemctl_dispatch_parse_argv(int argc, char *argv[], int log_level_shift, char ***remaining_args); +int systemctl_main(char **args); diff --git a/src/systemd/_sd-common.h b/src/systemd/_sd-common.h index f9c9a2627d55c..8da080bf18e61 100644 --- a/src/systemd/_sd-common.h +++ b/src/systemd/_sd-common.h @@ -69,6 +69,20 @@ typedef void (*_sd_destroy_t)(void *userdata); # define _SD_STRINGIFY(x) _SD_XSTRINGIFY(x) #endif +/* Mirror of CONCATENATE / UNIQ from macro-fundamental.h, available to public sd-* headers. */ +#ifndef _SD_CONCATENATE +# define _SD_XCONCATENATE(x, y) x ## y +# define _SD_CONCATENATE(x, y) _SD_XCONCATENATE(x, y) +#endif + +#ifndef _SD_UNIQ +# ifdef __COUNTER__ +# define _SD_UNIQ __COUNTER__ +# else +# define _SD_UNIQ __LINE__ +# endif +#endif + #ifndef _SD_BEGIN_DECLARATIONS # ifdef __cplusplus # define _SD_BEGIN_DECLARATIONS \ diff --git a/src/systemd/meson.build b/src/systemd/meson.build index c3d2c1befb71a..cef6e3c93f549 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', @@ -27,8 +28,8 @@ _not_installed_headers = [ 'sd-dhcp-client.h', 'sd-dhcp-duid.h', 'sd-dhcp-lease.h', - 'sd-dhcp-option.h', 'sd-dhcp-protocol.h', + 'sd-dhcp-relay.h', 'sd-dhcp-server-lease.h', 'sd-dhcp-server.h', 'sd-dhcp6-client.h', @@ -36,6 +37,7 @@ _not_installed_headers = [ 'sd-dhcp6-option.h', 'sd-dhcp6-protocol.h', 'sd-dns-resolver.h', + 'sd-future.h', 'sd-ipv4acd.h', 'sd-ipv4ll.h', 'sd-lldp-rx.h', diff --git a/src/systemd/sd-bus-vtable.h b/src/systemd/sd-bus-vtable.h index 5c11ca8ae5b71..036bda3fe47e9 100644 --- a/src/systemd/sd-bus-vtable.h +++ b/src/systemd/sd-bus-vtable.h @@ -44,6 +44,7 @@ __extension__ enum { SD_BUS_VTABLE_PROPERTY_EXPLICIT = 1ULL << 7, SD_BUS_VTABLE_SENSITIVE = 1ULL << 8, /* covers both directions: method call + reply */ SD_BUS_VTABLE_ABSOLUTE_OFFSET = 1ULL << 9, + /* Bit 10 is reserved for the private SD_BUS_VTABLE_METHOD_FIBER flag (see bus-internal.h). */ _SD_BUS_VTABLE_CAPABILITY_MASK = 0xFFFFULL << 40 }; diff --git a/src/systemd/sd-device.h b/src/systemd/sd-device.h index 8eb784b1cf8d3..87aab9f8c58aa 100644 --- a/src/systemd/sd-device.h +++ b/src/systemd/sd-device.h @@ -71,8 +71,8 @@ int sd_device_get_syspath(sd_device *device, const char **ret); int sd_device_get_subsystem(sd_device *device, const char **ret); int sd_device_get_driver_subsystem(sd_device *device, const char **ret); int sd_device_get_devtype(sd_device *device, const char **ret); -int sd_device_get_devnum(sd_device *device, dev_t *devnum); -int sd_device_get_ifindex(sd_device *device, int *ifindex); +int sd_device_get_devnum(sd_device *device, dev_t *ret); +int sd_device_get_ifindex(sd_device *device, int *ret); int sd_device_get_driver(sd_device *device, const char **ret); int sd_device_get_devpath(sd_device *device, const char **ret); int sd_device_get_devname(sd_device *device, const char **ret); @@ -102,10 +102,10 @@ sd_device* sd_device_get_child_next(sd_device *device, const char **ret_suffix); int sd_device_has_tag(sd_device *device, const char *tag); int sd_device_has_current_tag(sd_device *device, const char *tag); -int sd_device_get_property_value(sd_device *device, const char *key, const char **value); +int sd_device_get_property_value(sd_device *device, const char *key, const char **ret); int sd_device_get_trigger_uuid(sd_device *device, sd_id128_t *ret); int sd_device_get_sysattr_value_with_size(sd_device *device, const char *sysattr, const char **ret_value, size_t *ret_size); -int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **ret_value); +int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **ret); int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, const char *value); int sd_device_set_sysattr_valuef(sd_device *device, const char *sysattr, const char *format, ...) _sd_printf_(3, 4); diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index b2995a961f320..c0216d36386c0 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -30,7 +30,6 @@ struct in_addr; typedef struct sd_device sd_device; typedef struct sd_dhcp_client_id sd_dhcp_client_id; typedef struct sd_dhcp_lease sd_dhcp_lease; -typedef struct sd_dhcp_option sd_dhcp_option; typedef struct sd_event sd_event; enum { @@ -51,7 +50,7 @@ int sd_dhcp_client_set_callback( sd_dhcp_client *client, sd_dhcp_client_callback_t cb, void *userdata); - +int sd_dhcp_client_set_anonymize(sd_dhcp_client *client, int b); int sd_dhcp_client_set_request_option( sd_dhcp_client *client, uint8_t option); @@ -130,15 +129,12 @@ int sd_dhcp_client_set_vendor_class_identifier( int sd_dhcp_client_set_mud_url( sd_dhcp_client *client, const char *mudurl); -int sd_dhcp_client_set_user_class( - sd_dhcp_client *client, - char * const *user_class); int sd_dhcp_client_get_lease( sd_dhcp_client *client, sd_dhcp_lease **ret); -int sd_dhcp_client_set_service_type( +int sd_dhcp_client_set_ip_service_type( sd_dhcp_client *client, - int type); + uint8_t type); int sd_dhcp_client_set_socket_priority( sd_dhcp_client *client, int socket_priority); @@ -150,29 +146,24 @@ int sd_dhcp_client_set_bootp( int bootp); int sd_dhcp_client_set_send_release(sd_dhcp_client *client, int enable); -int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v); -int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v); - int sd_dhcp_client_is_running(sd_dhcp_client *client); int sd_dhcp_client_stop(sd_dhcp_client *client); 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); -/* NOTE: anonymize parameter is used to initialize PRL memory with different - * options when using RFC7844 Anonymity Profiles */ -int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize); +int sd_dhcp_client_new(sd_dhcp_client **ret); int sd_dhcp_client_attach_event( sd_dhcp_client *client, 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-lease.h b/src/systemd/sd-dhcp-lease.h index 3aa67c3fa540b..a912ecb655022 100644 --- a/src/systemd/sd-dhcp-lease.h +++ b/src/systemd/sd-dhcp-lease.h @@ -44,7 +44,7 @@ __extension__ typedef enum _SD_ENUM_TYPE_S64(sd_dhcp_lease_server_type_t) { _SD_ENUM_FORCE_S64(DHCP_LEASE_SERVER_TYPE) } sd_dhcp_lease_server_type_t; -int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *ret); int sd_dhcp_lease_get_timestamp(sd_dhcp_lease *lease, clockid_t clock, uint64_t *ret); int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint64_t *ret); int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint64_t *ret); @@ -52,30 +52,23 @@ int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint64_t *ret); int sd_dhcp_lease_get_lifetime_timestamp(sd_dhcp_lease *lease, clockid_t clock, uint64_t *ret); int sd_dhcp_lease_get_t1_timestamp(sd_dhcp_lease *lease, clockid_t clock, uint64_t *ret); int sd_dhcp_lease_get_t2_timestamp(sd_dhcp_lease *lease, clockid_t clock, uint64_t *ret); -int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *ret); +int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *ret); int sd_dhcp_lease_get_prefix(sd_dhcp_lease *lease, struct in_addr *ret_prefix, uint8_t *ret_prefixlen); -int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_servers(sd_dhcp_lease *lease, sd_dhcp_lease_server_type_t what, const struct in_addr **addr); -int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_pop3(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_smtp(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_lpr(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu); -int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname); -int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains); -int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname); -int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path); +int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **ret); +int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *ret); +int sd_dhcp_lease_get_servers(sd_dhcp_lease *lease, sd_dhcp_lease_server_type_t what, const struct in_addr **ret); +int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **ret); +int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **ret); +int sd_dhcp_lease_get_sip(sd_dhcp_lease *lease, const struct in_addr **ret); +int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *ret); +int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **ret); +int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***ret); +int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **ret); int sd_dhcp_lease_get_captive_portal(sd_dhcp_lease *lease, const char **ret); -int sd_dhcp_lease_get_dnr(sd_dhcp_lease *lease, sd_dns_resolver **ret_resolvers); +int sd_dhcp_lease_get_dnr(sd_dhcp_lease *lease, sd_dns_resolver **ret); int sd_dhcp_lease_get_static_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret); int sd_dhcp_lease_get_classless_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret); -int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len); -int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id **ret); int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **ret); int sd_dhcp_lease_get_6rd( sd_dhcp_lease *lease, @@ -86,9 +79,9 @@ int sd_dhcp_lease_get_6rd( size_t *ret_n_br_addresses); int sd_dhcp_lease_has_6rd(sd_dhcp_lease *lease); -int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination); -int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length); -int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway); +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *ret); +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *ret); +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *ret); _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref); diff --git a/src/systemd/sd-dhcp-option.h b/src/systemd/sd-dhcp-option.h deleted file mode 100644 index edc671b1b5789..0000000000000 --- a/src/systemd/sd-dhcp-option.h +++ /dev/null @@ -1,34 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#ifndef foosddhcpoptionhfoo -#define foosddhcpoptionhfoo - -/*** - 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. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include "_sd-common.h" -#include "sd-dhcp-protocol.h" /* IWYU pragma: export */ - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_dhcp_option sd_dhcp_option; - -int sd_dhcp_option_new(uint8_t option, const void *data, size_t length, sd_dhcp_option **ret); -_SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_option); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_option, sd_dhcp_option_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-dhcp-protocol.h b/src/systemd/sd-dhcp-protocol.h index c25498502dac9..8ba28777a3c4f 100644 --- a/src/systemd/sd-dhcp-protocol.h +++ b/src/systemd/sd-dhcp-protocol.h @@ -23,180 +23,201 @@ _SD_BEGIN_DECLARATIONS; /* https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#options */ enum { - SD_DHCP_OPTION_PAD = 0, /* [RFC2132] */ - SD_DHCP_OPTION_SUBNET_MASK = 1, /* [RFC2132] */ - SD_DHCP_OPTION_TIME_OFFSET = 2, /* [RFC2132], deprecated by 100 and 101 */ - SD_DHCP_OPTION_ROUTER = 3, /* [RFC2132] */ - SD_DHCP_OPTION_TIME_SERVER = 4, /* [RFC2132] */ - SD_DHCP_OPTION_NAME_SERVER = 5, /* [RFC2132] */ - SD_DHCP_OPTION_DOMAIN_NAME_SERVER = 6, /* [RFC2132] */ - SD_DHCP_OPTION_LOG_SERVER = 7, /* [RFC2132] */ - SD_DHCP_OPTION_QUOTES_SERVER = 8, /* [RFC2132] */ - SD_DHCP_OPTION_LPR_SERVER = 9, /* [RFC2132] */ - SD_DHCP_OPTION_IMPRESS_SERVER = 10, /* [RFC2132] */ - SD_DHCP_OPTION_RLP_SERVER = 11, /* [RFC2132] */ - SD_DHCP_OPTION_HOST_NAME = 12, /* [RFC2132] */ - SD_DHCP_OPTION_BOOT_FILE_SIZE = 13, /* [RFC2132] */ - SD_DHCP_OPTION_MERIT_DUMP_FILE = 14, /* [RFC2132] */ - SD_DHCP_OPTION_DOMAIN_NAME = 15, /* [RFC2132] */ - SD_DHCP_OPTION_SWAP_SERVER = 16, /* [RFC2132] */ - SD_DHCP_OPTION_ROOT_PATH = 17, /* [RFC2132] */ - SD_DHCP_OPTION_EXTENSION_FILE = 18, /* [RFC2132] */ - SD_DHCP_OPTION_FORWARD = 19, /* [RFC2132] */ - SD_DHCP_OPTION_SOURCE_ROUTE = 20, /* [RFC2132] */ - SD_DHCP_OPTION_POLICY_FILTER = 21, /* [RFC2132] */ - SD_DHCP_OPTION_MAX_DATAGRAM_ASSEMBLY = 22, /* [RFC2132] */ - SD_DHCP_OPTION_DEFAULT_IP_TTL = 23, /* [RFC2132] */ - SD_DHCP_OPTION_MTU_TIMEOUT = 24, /* [RFC2132] */ - SD_DHCP_OPTION_MTU_PLATEAU = 25, /* [RFC2132] */ - SD_DHCP_OPTION_MTU_INTERFACE = 26, /* [RFC2132] */ - SD_DHCP_OPTION_MTU_SUBNET = 27, /* [RFC2132] */ - SD_DHCP_OPTION_BROADCAST = 28, /* [RFC2132] */ - SD_DHCP_OPTION_MASK_DISCOVERY = 29, /* [RFC2132] */ - SD_DHCP_OPTION_MASK_SUPPLIER = 30, /* [RFC2132] */ - SD_DHCP_OPTION_ROUTER_DISCOVERY = 31, /* [RFC2132] */ - SD_DHCP_OPTION_ROUTER_REQUEST = 32, /* [RFC2132] */ - SD_DHCP_OPTION_STATIC_ROUTE = 33, /* [RFC2132] */ - SD_DHCP_OPTION_TRAILERS = 34, /* [RFC2132] */ - SD_DHCP_OPTION_ARP_TIMEOUT = 35, /* [RFC2132] */ - SD_DHCP_OPTION_ETHERNET = 36, /* [RFC2132] */ - SD_DHCP_OPTION_DEFAULT_TCP_TTL = 37, /* [RFC2132] */ - SD_DHCP_OPTION_KEEPALIVE_TIME = 38, /* [RFC2132] */ - SD_DHCP_OPTION_KEEPALIVE_DATA = 39, /* [RFC2132] */ - SD_DHCP_OPTION_NIS_DOMAIN = 40, /* [RFC2132] */ - SD_DHCP_OPTION_NIS_SERVER = 41, /* [RFC2132] */ - SD_DHCP_OPTION_NTP_SERVER = 42, /* [RFC2132] */ - SD_DHCP_OPTION_VENDOR_SPECIFIC = 43, /* [RFC2132] */ - SD_DHCP_OPTION_NETBIOS_NAME_SERVER = 44, /* [RFC2132] */ - SD_DHCP_OPTION_NETBIOS_DIST_SERVER = 45, /* [RFC2132] */ - SD_DHCP_OPTION_NETBIOS_NODE_TYPE = 46, /* [RFC2132] */ - SD_DHCP_OPTION_NETBIOS_SCOPE = 47, /* [RFC2132] */ - SD_DHCP_OPTION_X_WINDOW_FONT = 48, /* [RFC2132] */ - SD_DHCP_OPTION_X_WINDOW_MANAGER = 49, /* [RFC2132] */ - SD_DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, /* [RFC2132] */ - SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, /* [RFC2132] */ - SD_DHCP_OPTION_OVERLOAD = 52, /* [RFC2132] */ - SD_DHCP_OPTION_MESSAGE_TYPE = 53, /* [RFC2132] */ - SD_DHCP_OPTION_SERVER_IDENTIFIER = 54, /* [RFC2132] */ - SD_DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, /* [RFC2132] */ - SD_DHCP_OPTION_ERROR_MESSAGE = 56, /* [RFC2132] */ - SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, /* [RFC2132] */ - SD_DHCP_OPTION_RENEWAL_TIME = 58, /* [RFC2132] */ - SD_DHCP_OPTION_REBINDING_TIME = 59, /* [RFC2132] */ - SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60, /* [RFC2132] */ - SD_DHCP_OPTION_CLIENT_IDENTIFIER = 61, /* [RFC2132] */ - SD_DHCP_OPTION_NETWARE_IP_DOMAIN = 62, /* [RFC2242] */ - SD_DHCP_OPTION_NETWARE_IP_OPTION = 63, /* [RFC2242] */ - SD_DHCP_OPTION_NIS_DOMAIN_NAME = 64, /* [RFC2132] */ - 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_SMTP_SERVER = 69, /* [RFC2132] */ - SD_DHCP_OPTION_POP3_SERVER = 70, /* [RFC2132] */ - SD_DHCP_OPTION_NNTP_SERVER = 71, /* [RFC2132] */ - SD_DHCP_OPTION_WWW_SERVER = 72, /* [RFC2132] */ - SD_DHCP_OPTION_FINGER_SERVER = 73, /* [RFC2132] */ - SD_DHCP_OPTION_IRC_SERVER = 74, /* [RFC2132] */ - SD_DHCP_OPTION_STREETTALK_SERVER = 75, /* [RFC2132] */ - SD_DHCP_OPTION_STDA_SERVER = 76, /* [RFC2132] */ - SD_DHCP_OPTION_USER_CLASS = 77, /* [RFC3004] */ - SD_DHCP_OPTION_DIRECTORY_AGENT = 78, /* [RFC2610] */ - SD_DHCP_OPTION_SERVICE_SCOPE = 79, /* [RFC2610] */ - SD_DHCP_OPTION_RAPID_COMMIT = 80, /* [RFC4039] */ - SD_DHCP_OPTION_FQDN = 81, /* [RFC4702] */ - SD_DHCP_OPTION_RELAY_AGENT_INFORMATION = 82, /* [RFC3046] */ - SD_DHCP_OPTION_ISNS = 83, /* [RFC4174] */ + SD_DHCP_OPTION_PAD = 0, /* [RFC2132] */ + SD_DHCP_OPTION_SUBNET_MASK = 1, /* [RFC2132] */ + SD_DHCP_OPTION_TIME_OFFSET = 2, /* [RFC2132], deprecated by 100 and 101 */ + SD_DHCP_OPTION_ROUTER = 3, /* [RFC2132] */ + SD_DHCP_OPTION_TIME_SERVER = 4, /* [RFC2132] */ + SD_DHCP_OPTION_NAME_SERVER = 5, /* [RFC2132] */ + SD_DHCP_OPTION_DOMAIN_NAME_SERVER = 6, /* [RFC2132] */ + SD_DHCP_OPTION_LOG_SERVER = 7, /* [RFC2132] */ + SD_DHCP_OPTION_QUOTES_SERVER = 8, /* [RFC2132] */ + SD_DHCP_OPTION_LPR_SERVER = 9, /* [RFC2132] */ + SD_DHCP_OPTION_IMPRESS_SERVER = 10, /* [RFC2132] */ + SD_DHCP_OPTION_RLP_SERVER = 11, /* [RFC2132] */ + SD_DHCP_OPTION_HOST_NAME = 12, /* [RFC2132] */ + SD_DHCP_OPTION_BOOT_FILE_SIZE = 13, /* [RFC2132] */ + SD_DHCP_OPTION_MERIT_DUMP_FILE = 14, /* [RFC2132] */ + SD_DHCP_OPTION_DOMAIN_NAME = 15, /* [RFC2132] */ + SD_DHCP_OPTION_SWAP_SERVER = 16, /* [RFC2132] */ + SD_DHCP_OPTION_ROOT_PATH = 17, /* [RFC2132] */ + SD_DHCP_OPTION_EXTENSION_FILE = 18, /* [RFC2132] */ + SD_DHCP_OPTION_FORWARD = 19, /* [RFC2132] */ + SD_DHCP_OPTION_SOURCE_ROUTE = 20, /* [RFC2132] */ + SD_DHCP_OPTION_POLICY_FILTER = 21, /* [RFC2132] */ + SD_DHCP_OPTION_MAX_DATAGRAM_ASSEMBLY = 22, /* [RFC2132] */ + SD_DHCP_OPTION_DEFAULT_IP_TTL = 23, /* [RFC2132] */ + SD_DHCP_OPTION_MTU_TIMEOUT = 24, /* [RFC2132] */ + SD_DHCP_OPTION_MTU_PLATEAU = 25, /* [RFC2132] */ + SD_DHCP_OPTION_MTU_INTERFACE = 26, /* [RFC2132] */ + SD_DHCP_OPTION_MTU_SUBNET = 27, /* [RFC2132] */ + SD_DHCP_OPTION_BROADCAST = 28, /* [RFC2132] */ + SD_DHCP_OPTION_MASK_DISCOVERY = 29, /* [RFC2132] */ + SD_DHCP_OPTION_MASK_SUPPLIER = 30, /* [RFC2132] */ + SD_DHCP_OPTION_ROUTER_DISCOVERY = 31, /* [RFC2132] */ + SD_DHCP_OPTION_ROUTER_REQUEST = 32, /* [RFC2132] */ + SD_DHCP_OPTION_STATIC_ROUTE = 33, /* [RFC2132] */ + SD_DHCP_OPTION_TRAILERS = 34, /* [RFC2132] */ + SD_DHCP_OPTION_ARP_TIMEOUT = 35, /* [RFC2132] */ + SD_DHCP_OPTION_ETHERNET = 36, /* [RFC2132] */ + SD_DHCP_OPTION_DEFAULT_TCP_TTL = 37, /* [RFC2132] */ + SD_DHCP_OPTION_KEEPALIVE_TIME = 38, /* [RFC2132] */ + SD_DHCP_OPTION_KEEPALIVE_DATA = 39, /* [RFC2132] */ + SD_DHCP_OPTION_NIS_DOMAIN = 40, /* [RFC2132] */ + SD_DHCP_OPTION_NIS_SERVER = 41, /* [RFC2132] */ + SD_DHCP_OPTION_NTP_SERVER = 42, /* [RFC2132] */ + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION = 43, /* [RFC2132] */ + SD_DHCP_OPTION_NETBIOS_NAME_SERVER = 44, /* [RFC2132] */ + SD_DHCP_OPTION_NETBIOS_DIST_SERVER = 45, /* [RFC2132] */ + SD_DHCP_OPTION_NETBIOS_NODE_TYPE = 46, /* [RFC2132] */ + SD_DHCP_OPTION_NETBIOS_SCOPE = 47, /* [RFC2132] */ + SD_DHCP_OPTION_X_WINDOW_FONT = 48, /* [RFC2132] */ + SD_DHCP_OPTION_X_WINDOW_MANAGER = 49, /* [RFC2132] */ + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, /* [RFC2132] */ + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, /* [RFC2132] */ + SD_DHCP_OPTION_OVERLOAD = 52, /* [RFC2132] */ + SD_DHCP_OPTION_MESSAGE_TYPE = 53, /* [RFC2132] */ + SD_DHCP_OPTION_SERVER_IDENTIFIER = 54, /* [RFC2132] */ + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, /* [RFC2132] */ + SD_DHCP_OPTION_ERROR_MESSAGE = 56, /* [RFC2132] */ + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, /* [RFC2132] */ + SD_DHCP_OPTION_RENEWAL_TIME = 58, /* [RFC2132] */ + SD_DHCP_OPTION_REBINDING_TIME = 59, /* [RFC2132] */ + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60, /* [RFC2132] */ + SD_DHCP_OPTION_CLIENT_IDENTIFIER = 61, /* [RFC2132] */ + SD_DHCP_OPTION_NETWARE_IP_DOMAIN = 62, /* [RFC2242] */ + SD_DHCP_OPTION_NETWARE_IP_OPTION = 63, /* [RFC2242] */ + SD_DHCP_OPTION_NIS_DOMAIN_NAME = 64, /* [RFC2132] */ + 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_ADDRESS = 68, /* [RFC2132] */ + SD_DHCP_OPTION_SMTP_SERVER = 69, /* [RFC2132] */ + SD_DHCP_OPTION_POP3_SERVER = 70, /* [RFC2132] */ + SD_DHCP_OPTION_NNTP_SERVER = 71, /* [RFC2132] */ + SD_DHCP_OPTION_WWW_SERVER = 72, /* [RFC2132] */ + SD_DHCP_OPTION_FINGER_SERVER = 73, /* [RFC2132] */ + SD_DHCP_OPTION_IRC_SERVER = 74, /* [RFC2132] */ + SD_DHCP_OPTION_STREETTALK_SERVER = 75, /* [RFC2132] */ + SD_DHCP_OPTION_STDA_SERVER = 76, /* [RFC2132] */ + SD_DHCP_OPTION_USER_CLASS = 77, /* [RFC3004] */ + SD_DHCP_OPTION_DIRECTORY_AGENT = 78, /* [RFC2610] */ + SD_DHCP_OPTION_SERVICE_SCOPE = 79, /* [RFC2610] */ + SD_DHCP_OPTION_RAPID_COMMIT = 80, /* [RFC4039] */ + SD_DHCP_OPTION_FQDN = 81, /* [RFC4702] */ + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION = 82, /* [RFC3046] */ + SD_DHCP_OPTION_ISNS = 83, /* [RFC4174] */ /* option code 84 is unassigned [RFC3679] */ - SD_DHCP_OPTION_NDS_SERVER = 85, /* [RFC2241] */ - SD_DHCP_OPTION_NDS_TREE_NAME = 86, /* [RFC2241] */ - SD_DHCP_OPTION_NDS_CONTEXT = 87, /* [RFC2241] */ - SD_DHCP_OPTION_BCMCS_CONTROLLER_DOMAIN_NAME = 88, /* [RFC4280] */ - SD_DHCP_OPTION_BCMCS_CONTROLLER_ADDRESS = 89, /* [RFC4280] */ - SD_DHCP_OPTION_AUTHENTICATION = 90, /* [RFC3118] */ - SD_DHCP_OPTION_CLIENT_LAST_TRANSACTION_TIME = 91, /* [RFC4388] */ - SD_DHCP_OPTION_ASSOCIATED_IP = 92, /* [RFC4388] */ - SD_DHCP_OPTION_CLIENT_SYSTEM = 93, /* [RFC4578] */ - SD_DHCP_OPTION_CLIENT_NDI = 94, /* [RFC4578] */ - SD_DHCP_OPTION_LDAP = 95, /* [RFC3679] */ + SD_DHCP_OPTION_NDS_SERVER = 85, /* [RFC2241] */ + SD_DHCP_OPTION_NDS_TREE_NAME = 86, /* [RFC2241] */ + SD_DHCP_OPTION_NDS_CONTEXT = 87, /* [RFC2241] */ + SD_DHCP_OPTION_BCMCS_CONTROLLER_DOMAIN_NAME = 88, /* [RFC4280] */ + SD_DHCP_OPTION_BCMCS_CONTROLLER_ADDRESS = 89, /* [RFC4280] */ + SD_DHCP_OPTION_AUTHENTICATION = 90, /* [RFC3118] */ + SD_DHCP_OPTION_CLIENT_LAST_TRANSACTION_TIME = 91, /* [RFC4388] */ + SD_DHCP_OPTION_ASSOCIATED_IP = 92, /* [RFC4388] */ + SD_DHCP_OPTION_CLIENT_SYSTEM = 93, /* [RFC4578] */ + SD_DHCP_OPTION_CLIENT_NDI = 94, /* [RFC4578] */ + SD_DHCP_OPTION_LDAP = 95, /* [RFC3679] */ /* option code 96 is unassigned [RFC3679] */ - SD_DHCP_OPTION_UUID = 97, /* [RFC4578] */ - SD_DHCP_OPTION_USER_AUTHENTICATION = 98, /* [RFC2485] */ - SD_DHCP_OPTION_GEOCONF_CIVIC = 99, /* [RFC4776] */ - SD_DHCP_OPTION_POSIX_TIMEZONE = 100, /* [RFC4833] */ - SD_DHCP_OPTION_TZDB_TIMEZONE = 101, /* [RFC4833] */ + SD_DHCP_OPTION_UUID = 97, /* [RFC4578] */ + SD_DHCP_OPTION_USER_AUTHENTICATION = 98, /* [RFC2485] */ + SD_DHCP_OPTION_GEOCONF_CIVIC = 99, /* [RFC4776] */ + SD_DHCP_OPTION_POSIX_TIMEZONE = 100, /* [RFC4833] */ + SD_DHCP_OPTION_TZDB_TIMEZONE = 101, /* [RFC4833] */ /* option codes 102-107 are unassigned [RFC3679] */ - SD_DHCP_OPTION_IPV6_ONLY_PREFERRED = 108, /* [RFC8925] */ - SD_DHCP_OPTION_DHCP4O6_SOURCE_ADDRESS = 109, /* [RFC8539] */ + SD_DHCP_OPTION_IPV6_ONLY_PREFERRED = 108, /* [RFC8925] */ + SD_DHCP_OPTION_DHCP4O6_SOURCE_ADDRESS = 109, /* [RFC8539] */ /* option codes 110-111 are unassigned [RFC3679] */ - SD_DHCP_OPTION_NETINFO_ADDRESS = 112, /* [RFC3679] */ - SD_DHCP_OPTION_NETINFO_TAG = 113, /* [RFC3679] */ - SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL = 114, /* [RFC8910] */ + SD_DHCP_OPTION_NETINFO_ADDRESS = 112, /* [RFC3679] */ + SD_DHCP_OPTION_NETINFO_TAG = 113, /* [RFC3679] */ + SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL = 114, /* [RFC8910] */ /* option code 115 is unassigned [RFC3679] */ - SD_DHCP_OPTION_AUTO_CONFIG = 116, /* [RFC2563] */ - SD_DHCP_OPTION_NAME_SERVICE_SEARCH = 117, /* [RFC2937] */ - SD_DHCP_OPTION_SUBNET_SELECTION = 118, /* [RFC3011] */ - SD_DHCP_OPTION_DOMAIN_SEARCH = 119, /* [RFC3397] */ - SD_DHCP_OPTION_SIP_SERVER = 120, /* [RFC3361] */ - SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, /* [RFC3442] */ - SD_DHCP_OPTION_CABLELABS_CLIENT_CONFIGURATION = 122, /* [RFC3495] */ - SD_DHCP_OPTION_GEOCONF = 123, /* [RFC6225] */ - SD_DHCP_OPTION_VENDOR_CLASS = 124, /* [RFC3925] */ - SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION = 125, /* [RFC3925] */ + SD_DHCP_OPTION_AUTO_CONFIG = 116, /* [RFC2563] */ + SD_DHCP_OPTION_NAME_SERVICE_SEARCH = 117, /* [RFC2937] */ + SD_DHCP_OPTION_SUBNET_SELECTION = 118, /* [RFC3011] */ + SD_DHCP_OPTION_DOMAIN_SEARCH = 119, /* [RFC3397] */ + SD_DHCP_OPTION_SIP_SERVER = 120, /* [RFC3361] */ + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, /* [RFC3442] */ + SD_DHCP_OPTION_CABLELABS_CLIENT_CONFIGURATION = 122, /* [RFC3495] */ + SD_DHCP_OPTION_GEOCONF = 123, /* [RFC6225] */ + SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_CLASS = 124, /* [RFC3925] */ + SD_DHCP_OPTION_VENDOR_IDENTIFYING_VENDOR_SPECIFIC_INFORMATION = 125, /* [RFC3925] */ /* option codes 126-127 are unassigned [RFC3679] */ /* option codes 128-135 are assigned to use by PXE, but they are vendor specific [RFC4578] */ - SD_DHCP_OPTION_PANA_AGENT = 136, /* [RFC5192] */ - SD_DHCP_OPTION_LOST_SERVER_FQDN = 137, /* [RFC5223] */ - SD_DHCP_OPTION_CAPWAP_AC_ADDRESS = 138, /* [RFC5417] */ - SD_DHCP_OPTION_MOS_ADDRESS = 139, /* [RFC5678] */ - SD_DHCP_OPTION_MOS_FQDN = 140, /* [RFC5678] */ - SD_DHCP_OPTION_SIP_SERVICE_DOMAIN = 141, /* [RFC6011] */ - SD_DHCP_OPTION_ANDSF_ADDRESS = 142, /* [RFC6153] */ - SD_DHCP_OPTION_SZTP_REDIRECT = 143, /* [RFC8572] */ - SD_DHCP_OPTION_GEOLOC = 144, /* [RFC6225] */ - SD_DHCP_OPTION_FORCERENEW_NONCE_CAPABLE = 145, /* [RFC6704] */ - SD_DHCP_OPTION_RDNSS_SELECTION = 146, /* [RFC6731] */ - SD_DHCP_OPTION_DOTS_RI = 147, /* [RFC8973] */ - SD_DHCP_OPTION_DOTS_ADDRESS = 148, /* [RFC8973] */ + SD_DHCP_OPTION_PANA_AGENT = 136, /* [RFC5192] */ + SD_DHCP_OPTION_LOST_SERVER_FQDN = 137, /* [RFC5223] */ + SD_DHCP_OPTION_CAPWAP_AC_ADDRESS = 138, /* [RFC5417] */ + SD_DHCP_OPTION_MOS_ADDRESS = 139, /* [RFC5678] */ + SD_DHCP_OPTION_MOS_FQDN = 140, /* [RFC5678] */ + SD_DHCP_OPTION_SIP_SERVICE_DOMAIN = 141, /* [RFC6011] */ + SD_DHCP_OPTION_ANDSF_ADDRESS = 142, /* [RFC6153] */ + SD_DHCP_OPTION_SZTP_REDIRECT = 143, /* [RFC8572] */ + SD_DHCP_OPTION_GEOLOC = 144, /* [RFC6225] */ + SD_DHCP_OPTION_FORCERENEW_NONCE_CAPABLE = 145, /* [RFC6704] */ + SD_DHCP_OPTION_RDNSS_SELECTION = 146, /* [RFC6731] */ + SD_DHCP_OPTION_DOTS_RI = 147, /* [RFC8973] */ + SD_DHCP_OPTION_DOTS_ADDRESS = 148, /* [RFC8973] */ /* option code 149 is unassigned [RFC3942] */ - SD_DHCP_OPTION_TFTP_SERVER_ADDRESS = 150, /* [RFC5859] */ - SD_DHCP_OPTION_STATUS_CODE = 151, /* [RFC6926] */ - SD_DHCP_OPTION_BASE_TIME = 152, /* [RFC6926] */ - SD_DHCP_OPTION_START_TIME_OF_STATE = 153, /* [RFC6926] */ - SD_DHCP_OPTION_QUERY_START_TIME = 154, /* [RFC6926] */ - SD_DHCP_OPTION_QUERY_END_TIME = 155, /* [RFC6926] */ - SD_DHCP_OPTION_DHCP_STATE = 156, /* [RFC6926] */ - SD_DHCP_OPTION_DATA_SOURCE = 157, /* [RFC6926] */ - SD_DHCP_OPTION_PCP_SERVER = 158, /* [RFC7291] */ - SD_DHCP_OPTION_PORT_PARAMS = 159, /* [RFC7618] */ + SD_DHCP_OPTION_TFTP_SERVER_ADDRESS = 150, /* [RFC5859] */ + SD_DHCP_OPTION_STATUS_CODE = 151, /* [RFC6926] */ + SD_DHCP_OPTION_BASE_TIME = 152, /* [RFC6926] */ + SD_DHCP_OPTION_START_TIME_OF_STATE = 153, /* [RFC6926] */ + SD_DHCP_OPTION_QUERY_START_TIME = 154, /* [RFC6926] */ + SD_DHCP_OPTION_QUERY_END_TIME = 155, /* [RFC6926] */ + SD_DHCP_OPTION_DHCP_STATE = 156, /* [RFC6926] */ + SD_DHCP_OPTION_DATA_SOURCE = 157, /* [RFC6926] */ + SD_DHCP_OPTION_PCP_SERVER = 158, /* [RFC7291] */ + SD_DHCP_OPTION_PORT_PARAMS = 159, /* [RFC7618] */ /* option code 160 is unassigned [RFC7710][RFC8910] */ - SD_DHCP_OPTION_MUD_URL = 161, /* [RFC8520] */ - SD_DHCP_OPTION_V4_DNR = 162, /* [RFC9463] */ + SD_DHCP_OPTION_MUD_URL = 161, /* [RFC8520] */ + SD_DHCP_OPTION_V4_DNR = 162, /* [RFC9463] */ /* option codes 163-174 are unassigned [RFC3942] */ /* option codes 175-177 are temporary assigned. */ /* option codes 178-207 are unassigned [RFC3942] */ - SD_DHCP_OPTION_PXELINUX_MAGIC = 208, /* [RFC5071] Deprecated */ - SD_DHCP_OPTION_CONFIGURATION_FILE = 209, /* [RFC5071] */ - SD_DHCP_OPTION_PATH_PREFIX = 210, /* [RFC5071] */ - SD_DHCP_OPTION_REBOOT_TIME = 211, /* [RFC5071] */ - SD_DHCP_OPTION_6RD = 212, /* [RFC5969] */ - SD_DHCP_OPTION_ACCESS_DOMAIN = 213, /* [RFC5986] */ + SD_DHCP_OPTION_PXELINUX_MAGIC = 208, /* [RFC5071] Deprecated */ + SD_DHCP_OPTION_CONFIGURATION_FILE = 209, /* [RFC5071] */ + SD_DHCP_OPTION_PATH_PREFIX = 210, /* [RFC5071] */ + SD_DHCP_OPTION_REBOOT_TIME = 211, /* [RFC5071] */ + SD_DHCP_OPTION_6RD = 212, /* [RFC5969] */ + SD_DHCP_OPTION_ACCESS_DOMAIN = 213, /* [RFC5986] */ /* option codes 214-219 are unassigned */ - SD_DHCP_OPTION_SUBNET_ALLOCATION = 220, /* [RFC6656] */ - SD_DHCP_OPTION_VIRTUAL_SUBNET_SELECTION = 221, /* [RFC6607] */ + SD_DHCP_OPTION_SUBNET_ALLOCATION = 220, /* [RFC6656] */ + SD_DHCP_OPTION_VIRTUAL_SUBNET_SELECTION = 221, /* [RFC6607] */ /* option codes 222-223 are unassigned [RFC3942] */ /* option codes 224-254 are reserved for private use */ - SD_DHCP_OPTION_PRIVATE_BASE = 224, - SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE = 249, /* [RFC7844] */ - SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY = 252, /* [RFC7844] */ - SD_DHCP_OPTION_PRIVATE_LAST = 254, - SD_DHCP_OPTION_END = 255 /* [RFC2132] */ + SD_DHCP_OPTION_PRIVATE_BASE = 224, + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE = 249, /* [RFC7844] */ + SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY = 252, /* [RFC7844] */ + SD_DHCP_OPTION_PRIVATE_LAST = 254, + SD_DHCP_OPTION_END = 255 /* [RFC2132] */ }; -/* Suboptions for SD_DHCP_OPTION_RELAY_AGENT_INFORMATION option */ +/* Suboptions for SD_DHCP_OPTION_RELAY_AGENT_INFORMATION option. See + * https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#relay-agent-sub-options */ enum { - SD_DHCP_RELAY_AGENT_CIRCUIT_ID = 1, - SD_DHCP_RELAY_AGENT_REMOTE_ID = 2 + SD_DHCP_RELAY_AGENT_CIRCUIT_ID = 1, + SD_DHCP_RELAY_AGENT_REMOTE_ID = 2, + /* suboption code 3 is reserved */ + SD_DHCP_RELAY_AGENT_DOCSIS_DEVICE_CLASS = 4, + SD_DHCP_RELAY_AGENT_LINK_SELECTION = 5, + SD_DHCP_RELAY_AGENT_SUBSCRIBER_ID = 6, + SD_DHCP_RELAY_AGENT_RADIUS_ATTRIBUTE = 7, + SD_DHCP_RELAY_AGENT_AUTHENTICATION = 8, + SD_DHCP_RELAY_AGENT_VENDOR_SPECIFIC_INFORMATION = 9, + SD_DHCP_RELAY_AGENT_FLAGS = 10, + SD_DHCP_RELAY_AGENT_SERVER_IDENTIFIER_OVERRIDE = 11, + SD_DHCP_RELAY_AGENT_IDENTIFIER = 12, + SD_DHCP_RELAY_AGENT_ACCESS_TECHNOLOGY_TYPE = 13, + SD_DHCP_RELAY_AGENT_ACCESS_NETWORK_NAME = 14, + SD_DHCP_RELAY_AGENT_ACCESS_POINT_NAME = 15, + SD_DHCP_RELAY_AGENT_ACCESS_POINT_BSSID = 16, + SD_DHCP_RELAY_AGENT_OPERATOR_IDENTIFIER = 17, + SD_DHCP_RELAY_AGENT_OPERATOR_REALM = 18, + SD_DHCP_RELAY_AGENT_SOURCE_PORT = 19, + /* suboption code 20-150 are unassigned */ + SD_DHCP_RELAY_AGENT_VIRTUAL_SUBNET_SELECTION = 151, + SD_DHCP_RELAY_AGENT_VIRTUAL_SUBNET_SELECTION_CONTROL = 152 }; _SD_END_DECLARATIONS; diff --git a/src/systemd/sd-dhcp-relay.h b/src/systemd/sd-dhcp-relay.h new file mode 100644 index 0000000000000..ae8c8c8007ddf --- /dev/null +++ b/src/systemd/sd-dhcp-relay.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcprelayhfoo +#define foosddhcprelayhfoo + +/*** + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +struct in_addr; +struct iovec; + +typedef struct sd_event sd_event; +typedef struct sd_dhcp_relay sd_dhcp_relay; +typedef struct sd_dhcp_relay_interface sd_dhcp_relay_interface; + +_SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_relay); +_SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_relay_interface); + +int sd_dhcp_relay_new(sd_dhcp_relay **ret); + +int sd_dhcp_relay_is_running(sd_dhcp_relay *relay); + +int sd_dhcp_relay_attach_event(sd_dhcp_relay *relay, sd_event *event, int64_t priority); +int sd_dhcp_relay_detach_event(sd_dhcp_relay *relay); +sd_event *sd_dhcp_relay_get_event(sd_dhcp_relay *relay); + +int sd_dhcp_relay_set_server_address(sd_dhcp_relay *relay, const struct in_addr *address); +int sd_dhcp_relay_get_server_address(sd_dhcp_relay *relay, struct in_addr *ret); +int sd_dhcp_relay_set_server_port(sd_dhcp_relay *relay, uint16_t port); +int sd_dhcp_relay_set_remote_id(sd_dhcp_relay *relay, const struct iovec *iov); +int sd_dhcp_relay_set_server_identifier_override(sd_dhcp_relay *relay, int b); + +int sd_dhcp_relay_add_interface(sd_dhcp_relay *relay, int ifindex, int is_upstream, sd_dhcp_relay_interface **ret); + +int sd_dhcp_relay_interface_set_ifname(sd_dhcp_relay_interface *interface, const char *ifname); +int sd_dhcp_relay_interface_get_ifname(sd_dhcp_relay_interface *interface, const char **ret); +int sd_dhcp_relay_interface_set_address(sd_dhcp_relay_interface *interface, const struct in_addr *address, uint8_t prefixlen); +int sd_dhcp_relay_interface_get_address(sd_dhcp_relay_interface *interface, struct in_addr *ret_address, uint8_t *ret_prefixlen); +int sd_dhcp_relay_interface_set_port(sd_dhcp_relay_interface *interface, uint16_t port); +int sd_dhcp_relay_interface_set_ip_service_type(sd_dhcp_relay_interface *interface, uint8_t type); +int sd_dhcp_relay_interface_is_running(sd_dhcp_relay_interface *interface); +int sd_dhcp_relay_interface_start(sd_dhcp_relay_interface *interface); +int sd_dhcp_relay_interface_stop(sd_dhcp_relay_interface *interface); + +int sd_dhcp_relay_downstream_set_gateway_address(sd_dhcp_relay_interface *interface, const struct in_addr *address); +int sd_dhcp_relay_downstream_set_circuit_id(sd_dhcp_relay_interface *interface, const struct iovec *iov); +int sd_dhcp_relay_downstream_set_virtual_subnet_selection(sd_dhcp_relay_interface *interface, const struct iovec *iov); + +int sd_dhcp_relay_upstream_set_priority(sd_dhcp_relay_interface *interface, int priority); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_relay, sd_dhcp_relay_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_relay_interface, sd_dhcp_relay_interface_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h index ef13776201a70..15741f96b6ef3 100644 --- a/src/systemd/sd-dhcp-server.h +++ b/src/systemd/sd-dhcp-server.h @@ -26,7 +26,6 @@ _SD_BEGIN_DECLARATIONS; typedef struct sd_event sd_event; -typedef struct sd_dhcp_option sd_dhcp_option; typedef struct sd_dhcp_server sd_dhcp_server; enum { @@ -42,7 +41,7 @@ _SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_server); int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int64_t priority); int sd_dhcp_server_detach_event(sd_dhcp_server *server); -sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server); +sd_event* sd_dhcp_server_get_event(sd_dhcp_server *server); typedef void (*sd_dhcp_server_callback_t)(sd_dhcp_server *server, uint64_t event, void *userdata); @@ -54,11 +53,11 @@ int sd_dhcp_server_start(sd_dhcp_server *server); int sd_dhcp_server_stop(sd_dhcp_server *server); int sd_dhcp_server_configure_pool(sd_dhcp_server *server, const struct in_addr *address, unsigned char prefixlen, uint32_t offset, uint32_t size); +int sd_dhcp_server_set_ip_service_type(sd_dhcp_server *server, uint8_t type); int sd_dhcp_server_set_boot_server_address(sd_dhcp_server *server, const struct in_addr *address); 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); -int sd_dhcp_server_set_bind_to_interface(sd_dhcp_server *server, int enabled); int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *tz); int sd_dhcp_server_set_domain_name(sd_dhcp_server *server, const char *domain_name); int sd_dhcp_server_set_router(sd_dhcp_server *server, const struct in_addr *router); @@ -76,8 +75,6 @@ int sd_dhcp_server_set_sip(sd_dhcp_server *server, const struct in_addr sip[], s int sd_dhcp_server_set_pop3(sd_dhcp_server *server, const struct in_addr pop3[], size_t n); int sd_dhcp_server_set_smtp(sd_dhcp_server *server, const struct in_addr smtp[], size_t n); -int sd_dhcp_server_add_option(sd_dhcp_server *server, sd_dhcp_option *v); -int sd_dhcp_server_add_vendor_option(sd_dhcp_server *server, sd_dhcp_option *v); int sd_dhcp_server_set_static_lease( sd_dhcp_server *server, const struct in_addr *address, @@ -93,10 +90,6 @@ int sd_dhcp_server_set_rapid_commit(sd_dhcp_server *server, int enabled); int sd_dhcp_server_forcerenew(sd_dhcp_server *server); -int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server); -int sd_dhcp_server_set_relay_target(sd_dhcp_server *server, const struct in_addr* address); -int sd_dhcp_server_set_relay_agent_information(sd_dhcp_server *server, const char* circuit_id, const char* remote_id); - int sd_dhcp_server_get_lease_address_by_name(sd_dhcp_server *server, const char *name, struct in_addr *ret); _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_server, sd_dhcp_server_unref); diff --git a/src/systemd/sd-dlopen.h b/src/systemd/sd-dlopen.h new file mode 100644 index 0000000000000..b108bdae28b7b --- /dev/null +++ b/src/systemd/sd-dlopen.h @@ -0,0 +1,112 @@ +/* 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 + +/* 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" + +/* The "R" (SHF_GNU_RETAIN) flag is only understood by binutils >= 2.36, so it can be disabled by defining + * _SD_ELF_NOTE_DLOPEN_SECTION_FLAGS as 'aG' manually, but note that if --gc-sections is used when linking, + * the note will be dropped. */ +#ifndef _SD_ELF_NOTE_DLOPEN_SECTION_FLAGS +# define _SD_ELF_NOTE_DLOPEN_SECTION_FLAGS "aGR" +#endif + +/* This guard tag ensures the assembler folds identical notes when compiling. Unfortunately hppa redefines + * '.equ' so it cannot be used. '.equiv' works on all arches, but it breaks with binutils 2.35 which is used + * on CentOS 9, so we need an ifdef here until the binutils baseline is updated, as above. */ +#if defined(__hppa__) || defined(__hppa64__) +# define _SD_ELF_NOTE_DLOPEN_GUARD ".set" +#else +# define _SD_ELF_NOTE_DLOPEN_GUARD ".equ" +#endif + +/* The json argument is a C string containing already backslash-escaped double quotes (i.e. the bytes + * [{\"feature\"...), so that once it is pasted into the assembler's .asciz directive the assembler decodes + * them back to plain quotes. The note is emitted into a COMDAT group keyed on the payload itself, which + * makes byte-identical notes fold to a single copy: the assembler folds duplicates within a translation + * unit (via the .ifndef guard) and the linker folds them across translation units (via the COMDAT group). + * This way a binary that triggers the same optional dependency from multiple call sites ends up with just + * one note for it. The n_type value below must match SD_ELF_NOTE_DLOPEN_TYPE (it is spelled out as a + * literal here as it cannot be expanded inside the assembler string). */ +#define _SD_ELF_NOTE_DLOPEN(json) \ + __asm__ ( \ + ".ifndef \"sd_dlopen:" json "\"\n" \ + _SD_ELF_NOTE_DLOPEN_GUARD " \"sd_dlopen:" json "\", 1\n" /* guard tag, to fold identical notes */ \ + ".pushsection .note.dlopen, \"" _SD_ELF_NOTE_DLOPEN_SECTION_FLAGS "\", %note, \"sd_dlopen:" json "\", comdat\n" \ + ".balign 4\n" /* notes require 4-byte alignment for the header and fields */ \ + ".long 884f - 883f\n" /* n_namesz: byte length of the vendor name (label 883->884) */ \ + ".long 882f - 881f\n" /* n_descsz: byte length of the JSON payload (label 881->882) */ \ + ".long 0x407c0c0a\n" /* n_type: must match SD_ELF_NOTE_DLOPEN_TYPE */ \ + "883:\n" /* start of vendor name */ \ + ".asciz \"" SD_ELF_NOTE_DLOPEN_VENDOR "\"\n" \ + "884:\n" /* end of vendor name */ \ + ".balign 4\n" \ + "881:\n" /* start of JSON payload */ \ + ".asciz \"" json "\"\n" \ + "882:\n" /* end of JSON payload */ \ + ".balign 4\n" \ + ".popsection\n" \ + ".endif\n") + +#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__) "}]") + +#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-future.h b/src/systemd/sd-future.h new file mode 100644 index 0000000000000..5e0fa22525615 --- /dev/null +++ b/src/systemd/sd-future.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdfuturefoo +#define foosdfuturefoo + +/*** + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +struct iovec; +struct pollfd; +struct sockaddr; +struct msghdr; +struct timespec; + +typedef struct sd_event sd_event; +typedef struct sd_future sd_future; +typedef struct sd_future_ops sd_future_ops; +typedef int (*sd_future_func_t)(sd_future *f); +typedef int (*sd_fiber_func_t)(void *userdata); +typedef _sd_destroy_t sd_fiber_destroy_t; + +struct sd_future_ops { + size_t size; + void* (*alloc)(void); + void (*free)(sd_future *f); + int (*cancel)(sd_future *f); + int (*set_priority)(sd_future *f, int64_t priority); +}; + +__extension__ typedef enum _SD_ENUM_TYPE_S64(sd_future_state_t) { + SD_FUTURE_PENDING, + SD_FUTURE_RESOLVED, + _SD_ENUM_FORCE_S64(SD_FUTURE_STATE) +} sd_future_state_t; + +int sd_future_new(const sd_future_ops *ops, sd_future **ret); +int sd_future_cancel(sd_future *f); +int sd_future_resolve(sd_future *f, int result); + +_SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_future); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_future, sd_future_unref); +void sd_future_unref_array_clear(sd_future *array[], size_t n); +void sd_future_unref_array(sd_future *array[], size_t n); + +sd_future* sd_future_cancel_wait_unref(sd_future *f); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_future, sd_future_cancel_wait_unref); +void sd_future_cancel_wait_unref_array_clear(sd_future *array[], size_t n); +void sd_future_cancel_wait_unref_array(sd_future *array[], size_t n); + +int sd_future_state(sd_future *f); +int sd_future_result(sd_future *f); +void* sd_future_get_userdata(sd_future *f); +void* sd_future_get_private(sd_future *f); +const sd_future_ops* sd_future_get_ops(sd_future *f); + +int sd_future_set_callback(sd_future *f, sd_future_func_t callback, void *userdata); +int sd_future_set_priority(sd_future *f, int64_t priority); + +int sd_future_new_wait(sd_future *target, sd_future **ret); + +int sd_fiber_new(sd_event *e, const char *name, sd_fiber_func_t func, void *userdata, sd_fiber_destroy_t destroy, sd_future **ret); + +int sd_fiber_set_floating(sd_future *f, int b); +int sd_fiber_get_floating(sd_future *f); + +int sd_fiber_is_running(void); +sd_future* sd_fiber_get_current(void); +int sd_fiber_get_priority(int64_t *ret); +sd_event* sd_fiber_get_event(void); + +int sd_fiber_yield(void); +int sd_fiber_sleep(uint64_t usec); +int sd_fiber_await(sd_future *target); +int sd_fiber_suspend(void); +int sd_fiber_resume(sd_future *f, int result); + +sd_future* sd_fiber_timeout(uint64_t timeout); + +#define SD_FIBER_TIMEOUT(timeout) _SD_FIBER_TIMEOUT(_SD_UNIQ, (timeout)) +#define _SD_FIBER_TIMEOUT(uniq, timeout) \ + sd_future *_SD_CONCATENATE(_sd_fto_, uniq) __attribute__((cleanup(sd_future_cancel_wait_unrefp), unused)) = sd_fiber_timeout(timeout) + +#define SD_FIBER_WITH_TIMEOUT(timeout) _SD_FIBER_WITH_TIMEOUT(_SD_UNIQ, (timeout)) +#define _SD_FIBER_WITH_TIMEOUT(uniq, timeout) \ + for (sd_future *_SD_CONCATENATE(_sd_fto_, uniq) __attribute__((cleanup(sd_future_cancel_wait_unrefp), unused)) = sd_fiber_timeout(timeout), \ + *_SD_CONCATENATE(_sd_fto_b_, uniq) = (sd_future*) (uintptr_t) 1; \ + _SD_CONCATENATE(_sd_fto_b_, uniq); \ + _SD_CONCATENATE(_sd_fto_b_, uniq) = NULL) + +/* Fiber I/O operations - use sd-event for non-blocking I/O when in fiber context */ +ssize_t sd_fiber_read(int fd, void *buf, size_t count); +ssize_t sd_fiber_write(int fd, const void *buf, size_t count); +ssize_t sd_fiber_readv(int fd, const struct iovec *iov, int iovcnt); +ssize_t sd_fiber_writev(int fd, const struct iovec *iov, int iovcnt); +ssize_t sd_fiber_recv(int sockfd, void *buf, size_t len, int flags); +ssize_t sd_fiber_send(int sockfd, const void *buf, size_t len, int flags); +int sd_fiber_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); +ssize_t sd_fiber_recvmsg(int sockfd, struct msghdr *msg, int flags); +ssize_t sd_fiber_sendmsg(int sockfd, const struct msghdr *msg, int flags); +ssize_t sd_fiber_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); +ssize_t sd_fiber_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); +int sd_fiber_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags); +#ifndef __STRICT_ANSI__ +int sd_fiber_ppoll(struct pollfd *fds, size_t n_fds, const struct timespec *timeout, const sigset_t *sigmask); +#endif + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/systemd/sd-hwdb.h b/src/systemd/sd-hwdb.h index 4504c5b5b1dca..35aa1b291e096 100644 --- a/src/systemd/sd-hwdb.h +++ b/src/systemd/sd-hwdb.h @@ -28,10 +28,10 @@ _SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_hwdb); int sd_hwdb_new(sd_hwdb **ret); int sd_hwdb_new_from_path(const char *path, sd_hwdb **ret); -int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **value); +int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **ret); int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias); -int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value); +int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **ret_key, const char **ret_value); /* the inverse condition avoids ambiguity of dangling 'else' after the macro */ #define SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value) \ diff --git a/src/systemd/sd-json.h b/src/systemd/sd-json.h index 359149ef9e0e2..5ad825f3c1a92 100644 --- a/src/systemd/sd-json.h +++ b/src/systemd/sd-json.h @@ -181,7 +181,12 @@ 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_JSON_PARSE_SEEK0 = 1 << 3, /* seek to offset 0 before reading */ + SD_JSON_PARSE_DONATE_FD = 1 << 4, /* pass ownership of fd to call, incl. on failure */ + SD_JSON_PARSE_REOPEN_FD = 1 << 5, /* reopen the specified file descriptor */ _SD_ENUM_FORCE_S64(JSON_PARSE_FLAGS) } sd_json_parse_flags_t; @@ -191,6 +196,7 @@ int sd_json_parse(const char *string, sd_json_parse_flags_t flags, sd_json_varia 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_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); 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_fd(const char *path, int fd, sd_json_parse_flags_t flags, sd_json_variant **ret, unsigned *reterr_line, unsigned *reterr_column); enum { /* Do not use these directly, use the SD_JSON_BUILD_*() macros below */ diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index 7bc4199408f13..bb6a17d9b6eb5 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -306,8 +306,14 @@ _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) + +#define SD_MESSAGE_MISSING_DEPENDENCY SD_ID128_MAKE(ea,c6,d3,94,c9,25,49,3d,ea,c2,ba,99,f3,c2,bb,11) +#define SD_MESSAGE_MISSING_DEPENDENCY_STR SD_ID128_MAKE_STR(ea,c6,d3,94,c9,25,49,3d,ea,c2,ba,99,f3,c2,bb,11) _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..b6f9436606568 100644 --- a/src/sysupdate/meson.build +++ b/src/sysupdate/meson.build @@ -2,11 +2,13 @@ systemd_sysupdate_sources = files( 'sysupdate-cache.c', + 'sysupdate-cleanup.c', 'sysupdate-feature.c', 'sysupdate-instance.c', 'sysupdate-partition.c', 'sysupdate-pattern.c', 'sysupdate-resource.c', + 'sysupdate-target.c', 'sysupdate-transfer.c', 'sysupdate-update-set.c', 'sysupdate.c', @@ -21,28 +23,22 @@ systemd_updatectl_sources = files( ) executables += [ - libexec_template + { + executable_template + { 'name' : 'systemd-sysupdate', 'public' : true, 'conditions' : ['ENABLE_SYSUPDATE'], 'sources' : systemd_sysupdate_sources, 'extract' : systemd_sysupdate_extract_sources, - 'link_with' : [ - libshared, - libshared_fdisk, - ], 'dependencies' : [ - libfdisk, - libopenssl, - threads, + libfdisk_cflags, + libopenssl_cflags, ], }, libexec_template + { 'name' : 'systemd-sysupdated', 'dbus' : true, 'conditions' : ['ENABLE_SYSUPDATED'], - 'sources' : files('sysupdated.c'), - 'dependencies' : threads, + 'sources' : files('sysupdated.c', 'sysupdate-target.c'), }, executable_template + { 'name' : 'updatectl', @@ -51,6 +47,11 @@ executables += [ 'objects' : ['systemd-sysupdate'], 'conditions' : ['ENABLE_SYSUPDATED'], }, + test_template + { + 'sources' : files('test-sysupdate-util.c'), + 'objects' : ['systemd-sysupdate'], + 'conditions' : ['ENABLE_SYSUPDATE'], + }, ] if conf.get('ENABLE_SYSUPDATED') == 1 @@ -61,3 +62,10 @@ if conf.get('ENABLE_SYSUPDATED') == 1 install_data('org.freedesktop.sysupdate1.policy', install_dir : polkitpolicydir) endif + +if conf.get('ENABLE_SYSUPDATE') == 1 + # symlink for backwards compatibility after rename + install_symlink('systemd-sysupdate', + pointing_to : libexecdir_to_bin / 'systemd-sysupdate', + install_dir : libexecdir) +endif diff --git a/src/sysupdate/org.freedesktop.sysupdate1.policy b/src/sysupdate/org.freedesktop.sysupdate1.policy index 8b7fa420c27ae..d74cf2066c8a4 100644 --- a/src/sysupdate/org.freedesktop.sysupdate1.policy +++ b/src/sysupdate/org.freedesktop.sysupdate1.policy @@ -29,6 +29,9 @@ a user at the console, and rpm-ostree (generally) needs an "administrative user" at the computer. Without this default, distributions hoping to use sysupdate as an update mechanism will have to set the policy to it anyhow. + + Cancellation actions are always allowed by default, as the user can typically pull the plug on + the computer to prevent an action completing anyway. --> @@ -41,6 +44,16 @@ + + Cancel checking for system updates + Authentication is required to cancel checking for system updates. + + auth_admin + auth_admin + yes + + + Install system updates Authentication is required to install system updates. @@ -51,6 +64,16 @@ + + Cancel installing system updates + Authentication is required to cancel installing system updates. + + auth_admin + auth_admin + yes + + + Install specific system version Authentication is required to update the system to a specific (possibly old) version. @@ -61,9 +84,19 @@ + + Cancel installing specific system version + Authentication is required to cancel updating the system to a specific (possibly old) version. + + auth_admin + auth_admin + yes + + + - Cleanup old system updates - Authentication is required to cleanup old system updates. + Clean up old system updates + Authentication is required to clean up old system updates. auth_admin auth_admin @@ -71,6 +104,16 @@ + + Cancel cleaning up old system updates + Authentication is required to cancel cleanup of old system updates. + + auth_admin + auth_admin + yes + + + Manage optional features Authentication is required to manage optional features. 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-cleanup.c b/src/sysupdate/sysupdate-cleanup.c new file mode 100644 index 0000000000000..2d0229423d5bd --- /dev/null +++ b/src/sysupdate/sysupdate-cleanup.c @@ -0,0 +1,444 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "alloc-util.h" +#include "chase.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "log.h" +#include "path-util.h" +#include "recurse-dir.h" +#include "rm-rf.h" +#include "sha256.h" +#include "string-util.h" +#include "strv.h" +#include "sysupdate.h" +#include "sysupdate-cleanup.h" +#include "sysupdate-pattern.h" +#include "sysupdate-resource.h" +#include "sysupdate-transfer.h" +#include "sysupdate-util.h" + +/* This implements the "installdb", which is a simple database of directories + patterns that we ever + * installed something into, i.e. for any resource we ever considered "owned" by a transfer file. This is + * useful to automatically clean up "orphaned" files that used to be owned by a transfer file, but might no + * longer be in newer versions of those transfer files, or where the transfer files/components got + * removed/disabled altogether. + * + * This is ultimately just a per-component content-addressable database implemented via a special directory + * in /var/lib/systemd/sysupdate/ that carries symlinks to store the data. Whenever we drop a file into the + * system we create an entry in it. An entry symlink's filename is a SHA256 hash of the symlink's target. The + * target encodes the directory of the transfer file used, suffixed by the pattern used by the transfer + * file. If a transfer file lists multiple patterns, multiple entries are generated, one for each pattern. + * + * The on-disk layout hence looks roughly like this (for a component "foo" with a transfer file that has + * Path=/var/lib/machines/ and two patterns "image_@v.raw" and "image_@v.efi"): + * + * /var/lib/systemd/sysupdate/ + * └── installdb.foo/ + * ├── 8cbee6aa38b98811598118ebbc0eb4c1b7e479e7bfa4312c0b36edc765d1733b → /var/lib/machines/./image_@v.raw + * └── 042266dec8deae09c1e75f3d015734513b75a1daa38b4173c907b5345cf4ed41 → /var/lib/machines/./image_@v.efi + * + * (For the component-less case the directory is just called "installdb", without the ".foo" suffix.) The + * symlink names are the SHA256 hashes (in hex) of their respective targets, and the "/./" separates the + * directory part from the pattern part of the target. + * + * With this in place we have an always updated database of any file and pattern ever owned by any transfer + * file we operated on. When doing a clean-up run, we now iterate through all installdb directories (i.e + * every component ever installed), and all entries in them. We look for all files the entries match. We then + * check if the current set of transfer files also owns these files. If yes, we keep both those files and the + * installdb entry. If however no current transfer files own these files anymore, we first delete the files, + * and then the installdb entry, since it no longer matches any files. */ + +static int context_installdb_acquire_fd(Context *c, bool make) { + assert(c); + + if (c->installdb_fd >= 0) + return 1; + + _cleanup_free_ char *j = NULL; + const char *p; + if (c->component) { + j = strjoin("/var/lib/systemd/sysupdate/installdb.", c->component); + if (!j) + return log_oom(); + + p = j; + } else + p = "/var/lib/systemd/sysupdate/installdb"; + + ChaseFlags flags = CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT; + + if (make) + flags |= CHASE_MKDIR_0755; + + c->installdb_fd = chase_and_open( + p, + c->root, + flags, + O_DIRECTORY|O_CLOEXEC|(make ? O_CREAT : 0), + /* ret_path= */ NULL); + if (c->installdb_fd == -ENOENT && !make) + return 0; + if (c->installdb_fd < 0) + return log_error_errno(c->installdb_fd, "Failed to open install database '%s%s': %m", empty_or_root(c->root) ? "" : c->root, p); + + return 1; +} + +static int installdb_make_names(const char *path, const char *pattern, char **ret_key, char **ret_value) { + assert(path); + assert(pattern); + + /* We'll generate a string from the location and the pattern that looks a lot like a path, but + * actually isn't, it's a path concatenated with a pattern. We separate both parts with /./. */ + _cleanup_free_ char *s = strjoin(path, "/./", pattern); + if (!s) + return log_oom(); + + _cleanup_free_ char *h = sha256_direct_hex(s, SIZE_MAX); + if (!h) + return log_oom(); + + if (ret_key) + *ret_key = TAKE_PTR(h); + + if (ret_value) + *ret_value = TAKE_PTR(s); + + return 0; +} + +int context_installdb_record( + Context *c, + const char *path, + char **patterns) { + + int r; + + assert(c); + assert(path); + + /* Creates installdb entries for the specified pairs of directory and pattern. This is called + * whenever we install a new file. */ + + if (strv_isempty(patterns)) + return 0; + + /* The provided path comes with c->root prefixed. Strip it here again */ + const char *p = c->root ? ASSERT_PTR(path_startswith(path, c->root)) : path; + + r = context_installdb_acquire_fd(c, /* make= */ true); + if (r < 0) + return r; + + int ret = 0; + STRV_FOREACH(i, patterns) { + _cleanup_free_ char *key = NULL, *value = NULL; + r = installdb_make_names(p, *i, &key, &value); + if (r < 0) + return r; + + r = symlinkat_idempotent(value, c->installdb_fd, key, /* make_relative= */ false); + if (r < 0) + RET_GATHER(ret, log_warning_errno(r, "Failed to add '%s' in '%s' entry to install database: %m", *i, path)); + } + + return ret; +} + +static int context_is_path_currently_owned( + Context *c, + const char *path, + const char *relpath) { + + int r; + + assert(c); + assert(path); + assert(relpath); + + /* Checks if the there's a transfer file for the directoy 'path', and then if any of its patterns + * match 'relpath' */ + + FOREACH_ARRAY(_t, c->transfers, c->n_transfers) { + Transfer *t = *_t; + + if (!RESOURCE_IS_FILESYSTEM(t->target.type)) + continue; + + if (!path_equal(t->target.path, path)) + continue; + + /* OK, so we found a transfer that covers this directory. Now let's see if any of its patterns match */ + + r = pattern_match_many(t->target.patterns, relpath, /* ret= */ NULL); + if (r < 0) { + _cleanup_free_ char *cl = strv_join(t->target.patterns, "', '"); + if (!cl) + return log_oom(); + + return log_error_errno(r, "Failed to match patterns '%s' against '%s': %m", cl, relpath); + } + + if (IN_SET(r, PATTERN_MATCH_YES, PATTERN_MATCH_RETRY)) /* Yay, this path is pinned by this transfer file */ + return true; + + assert(r == PATTERN_MATCH_NO); + } + + return false; /* We found nothing! The path seems to be unowned. */ +} + +static int context_installdb_process_directory( + Context *c, + const char *path, /* The configured Path= in the original transfer file */ + const char *relpath, /* For recursive path matches the path we encountered so far */ + int dir_fd, + DirectoryEntries *de, + const char *pattern) { + + int r; + + assert(c); + assert(path); + assert(dir_fd >= 0); + assert(de); + assert(pattern); + + int ret = 0; + bool keep_installdb = false; + FOREACH_ARRAY(_d, de->entries, de->n_entries) { + const struct dirent *d = *_d; + + assert(IN_SET(d->d_type, DT_REG, DT_DIR)); /* caller must have filtered via readdir_all() RECURSE_DIR_MUST_BE_xyz flags already */ + + _cleanup_free_ char *j = NULL; + const char *p; + if (relpath) { + j = path_join(relpath, d->d_name); + if (!j) + return log_oom(); + + p = j; + } else + p = d->d_name; + + /* Let's see if this entry matches the pattern we recorded in the installdb? */ + r = pattern_match(pattern, p, /* ret= */ NULL); + if (r < 0) { + log_warning_errno(r, "Failed to match pattern '%s' against '%s', ignoring: %m", pattern, p); + /* Can't match, do not clean up */ + continue; + } + if (r == PATTERN_MATCH_NO) /* No match, do not clean up */ + continue; + if (r == PATTERN_MATCH_RETRY) { + /* Might match in a subdirectory */ + + if (d->d_type != DT_DIR) + continue; + + _cleanup_close_ int subdir_fd = RET_NERRNO(openat(dir_fd, d->d_name, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW)); + if (subdir_fd == -ENOENT) + continue; + if (subdir_fd < 0) { + RET_GATHER(ret, log_warning_errno(subdir_fd, "Failed to open directory '%s', skipping: %m", p)); + continue; + } + + _cleanup_free_ DirectoryEntries *subde = NULL; + r = readdir_all(subdir_fd, RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_MUST_BE_DIRECTORY|RECURSE_DIR_MUST_BE_REGULAR, &subde); + if (r < 0) { + RET_GATHER(ret, log_error_errno(r, "Failed to enumerate resource path '%s': %m", p)); + continue; + } + + r = context_installdb_process_directory(c, path, p, subdir_fd, subde, pattern); + if (r < 0) + RET_GATHER(ret, r); + else + keep_installdb = keep_installdb || r; + continue; + } + + assert(r == PATTERN_MATCH_YES); + + /* Ah, we have a match, this is a candidate for cleanup. Let's see if any of the currently defined transfer files want to own it */ + + r = context_is_path_currently_owned(c, path, p); + if (r < 0) + return r; + if (r > 0) { + log_debug("Path '%s' is owned by current transfer files, keeping.", p); + + keep_installdb = true; /* We are keeping the file, let's also keep the installdb entry for it hence */ + continue; + } + + /* OK, we found an orphaned inode that was owned by a previous invocation, but is no longer + * owned by any of the current transfer files. Delete it. */ + + r = rm_rf_child(dir_fd, d->d_name, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD); + if (r < 0) { + if (r != -ENOENT) + RET_GATHER(ret, log_warning_errno(r, "Failed to remove '%s' which is no longer owned by any transfer files: %m", p)); + + continue; + } + + log_info("Successfully removed '%s' which is no longer owned by any transfer files.", p); + } + + /* Report back if there's a reason to keep the installdb entry for this directory */ + return ret < 0 ? ret : keep_installdb; +} + +static int context_installdb_process_entry( + Context *c, + const char *key, + const char *value) { + + int r; + + assert(c); + assert(key); + assert(value); + + _cleanup_free_ char *h = sha256_direct_hex(value, SIZE_MAX); + if (!h) + return log_oom(); + + if (!streq(key, h)) { + log_notice("Invalid hash of install database entry '%s' → '%s', expunging.", key, value); + return 0; + } + + const char *s = strstr(value, "/./"); + if (!s) { + log_notice("Malformed install database entry '%s' → '%s', expunging.", key, value); + return 0; + } + + _cleanup_free_ char *path = strndup(value, s - value); + if (!path) + return log_oom(); + + if (!path_is_absolute(path) || !path_is_normalized(path)) { + log_notice("Install database path '%s' of entry '%s' → '%s' is invalid, expunging database entry.", path, key, value); + return 0; + } + + const char *pattern = s + 3; + + /* NB: We set CHASE_PROHIBIT_SYMLINKS because the path was normalized by the writer of the entry + * already, and if it isn't anymore, then something is fishy. */ + _cleanup_close_ int dir_fd = chase_and_open(path, c->root, CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY|O_CLOEXEC, /* ret_path= */ NULL); + if (dir_fd == -ENOENT) { + log_debug("Install database path '%s' does not exist, expunging database entry.", path); + return 0; + } + if (dir_fd < 0) + return log_error_errno(dir_fd, "Failed to open resource path '%s': %m", path); + + _cleanup_free_ DirectoryEntries *de = NULL; + r = readdir_all(dir_fd, RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_MUST_BE_DIRECTORY|RECURSE_DIR_MUST_BE_REGULAR, &de); + if (r < 0) + return log_error_errno(r, "Failed to enumerate resource path '%s': %m", path); + + return context_installdb_process_directory(c, path, /* relpath= */ NULL, dir_fd, de, pattern); +} + +int installdb_cleanup_component(Context *context) { + int r; + + assert(context); + + r = context_installdb_acquire_fd(context, /* make= */ false); + if (r < 0) + return r; + if (r == 0) { + log_debug("Not cleaning up component '%s', install database is empty.", strna(context->component)); + return 0; + } + + _cleanup_free_ DirectoryEntries *de = NULL; + r = readdir_all(context->installdb_fd, RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_MUST_BE_SYMLINK, &de); + if (r < 0) + return log_error_errno(r, "Failed to enumerate install database for component '%s': %m", strna(context->component)); + + int ret = 0; + FOREACH_ARRAY(_d, de->entries, de->n_entries) { + const struct dirent *d = *_d; + + _cleanup_free_ char *v = NULL; + r = readlinkat_malloc(context->installdb_fd, d->d_name, &v); + if (r == -ENOENT) + continue; + if (r < 0) { + log_warning_errno(r, "Failed to read symlink '%s', ignoring: %m", d->d_name); + continue; + } + + r = context_installdb_process_entry(context, d->d_name, v); + if (r < 0) { + RET_GATHER(ret, r); + continue; + } + if (r > 0) /* Still good, keep installdb entry */ + continue; + + r = RET_NERRNO(unlinkat(context->installdb_fd, d->d_name, /* flags= */ 0)); + if (r < 0 && r != -ENOENT) + RET_GATHER(ret, log_warning_errno(r, "Failed to remove install database entry '%s': %m", d->d_name)); + } + + return ret; +} + +int installdb_list_components(Context *context, char ***ret) { + int r; + + assert(context); + assert(ret); + + _cleanup_close_ int dir_fd = chase_and_open( + "/var/lib/systemd/sysupdate", + context->root, + CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT, + O_DIRECTORY|O_CLOEXEC, + /* ret_path= */ NULL); + if (dir_fd == -ENOENT) { + *ret = NULL; + return 0; + } + if (dir_fd < 0) + return log_error_errno(dir_fd, "Failed to open '/var/lib/systemd/sysupdate/': %m"); + + _cleanup_free_ DirectoryEntries *de = NULL; + r = readdir_all(dir_fd, RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_MUST_BE_DIRECTORY, &de); + if (r < 0) + return log_error_errno(r, "Failed to enumerate installdb directory '/var/lib/systemd/sysupdate/': %m"); + + _cleanup_strv_free_ char **l = NULL; + FOREACH_ARRAY(_d, de->entries, de->n_entries) { + const struct dirent *d = *_d; + + const char *e = startswith(d->d_name, "installdb."); + if (!e) + continue; + + if (!component_name_valid(e)) + continue; + + if (strv_extend(&l, e) < 0) + return log_oom(); + } + + strv_sort_uniq(l); + *ret = TAKE_PTR(l); + return 0; +} diff --git a/src/sysupdate/sysupdate-cleanup.h b/src/sysupdate/sysupdate-cleanup.h new file mode 100644 index 0000000000000..d7b261e35bcd2 --- /dev/null +++ b/src/sysupdate/sysupdate-cleanup.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sysupdate-forward.h" + +int context_installdb_record(Context *c, const char *path, char **patterns); + +int installdb_cleanup_component(Context *context); +int installdb_list_components(Context *context, char ***ret); diff --git a/src/sysupdate/sysupdate-feature.c b/src/sysupdate/sysupdate-feature.c index 795d4ef90a35d..0c9bb112fe9b7 100644 --- a/src/sysupdate/sysupdate-feature.c +++ b/src/sysupdate/sysupdate-feature.c @@ -54,6 +54,7 @@ static int config_parse_url_specifiers( void *data, void *userdata) { char **s = ASSERT_PTR(data); + const char *root = ASSERT_PTR(userdata); _cleanup_free_ char *resolved = NULL; int r; @@ -64,7 +65,7 @@ static int config_parse_url_specifiers( return 0; } - r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved); + r = specifier_printf(rvalue, NAME_MAX, specifier_table, root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in %s=, ignoring: %s", lvalue, rvalue); @@ -80,7 +81,7 @@ static int config_parse_url_specifiers( return free_and_replace(*s, resolved); } -int feature_read_definition(Feature *f, const char *path, const char *const *dirs) { +int feature_read_definition(Feature *f, const char *root, const char *path, const char *const *dirs) { assert(f); ConfigTableItem table[] = { @@ -105,14 +106,14 @@ int feature_read_definition(Feature *f, const char *path, const char *const *dir STRV_MAKE_CONST(path), dirs, strjoina(filename, ".d"), - arg_root, + root, /* root_fd= */ -EBADF, "Feature\0", config_item_table_lookup, table, CONFIG_PARSE_WARN, - /* userdata= */ NULL, - /* stats_by_path= */ NULL, - /* drop_in_files= */ NULL); + (void *) root, + /* ret_stats_by_path= */ NULL, + /* ret_drop_in_files= */ NULL); if (r < 0) return r; diff --git a/src/sysupdate/sysupdate-feature.h b/src/sysupdate/sysupdate-feature.h index b42df5c27e62b..7d7495087c50f 100644 --- a/src/sysupdate/sysupdate-feature.h +++ b/src/sysupdate/sysupdate-feature.h @@ -22,4 +22,4 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Feature*, feature_unref); extern const struct hash_ops feature_hash_ops; -int feature_read_definition(Feature *f, const char *path, const char *const *conf_file_dirs); +int feature_read_definition(Feature *f, const char *root, const char *path, const char *const *conf_file_dirs); diff --git a/src/sysupdate/sysupdate-forward.h b/src/sysupdate/sysupdate-forward.h index a8659d756b2cd..f32cb55d32b5d 100644 --- a/src/sysupdate/sysupdate-forward.h +++ b/src/sysupdate/sysupdate-forward.h @@ -6,7 +6,9 @@ #include "shared-forward.h" /* IWYU pragma: export */ typedef struct Context Context; -typedef struct PartitionInfo PartitionInfo; -typedef struct Resource Resource; typedef struct Instance Instance; typedef struct InstanceMetadata InstanceMetadata; +typedef struct PartitionInfo PartitionInfo; +typedef struct Resource Resource; +typedef struct Transfer Transfer; +typedef struct UpdateSet UpdateSet; diff --git a/src/sysupdate/sysupdate-partition.c b/src/sysupdate/sysupdate-partition.c index 94a69850af57c..6361093231dcb 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + 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-pattern.c b/src/sysupdate/sysupdate-pattern.c index 76155dcd2924d..f06265c698fa1 100644 --- a/src/sysupdate/sysupdate-pattern.c +++ b/src/sysupdate/sysupdate-pattern.c @@ -420,12 +420,16 @@ int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret) { } default: - assert_se("unexpected pattern element"); + assert_not_reached(); } p = n; } + /* We matched the whole pattern, but if the string continues over the end of the pattern, refuse */ + if (*p != '\0') + goto nope; + if (ret) { *ret = found; found = (InstanceMetadata) INSTANCE_METADATA_NULL; @@ -454,7 +458,7 @@ int pattern_match_many(char **patterns, const char *s, InstanceMetadata *ret) { r = pattern_match(*p, s, &found); if (r < 0) return r; - if (r > 0) { + if (r != PATTERN_MATCH_NO) { if (ret) { *ret = found; found = (InstanceMetadata) INSTANCE_METADATA_NULL; diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c index 3be0943e4c0a3..275d8d7c15e04 100644 --- a/src/sysupdate/sysupdate-resource.c +++ b/src/sysupdate/sysupdate-resource.c @@ -139,9 +139,11 @@ static int resource_load_from_directory_recursive( if ((stripped = startswith(de->d_name, ".sysupdate.partial."))) { de_d_name_stripped = stripped; is_partial = true; + is_pending = false; } else if ((stripped = startswith(de->d_name, ".sysupdate.pending."))) { de_d_name_stripped = stripped; is_pending = true; + is_partial = false; } else de_d_name_stripped = de->d_name; @@ -156,6 +158,8 @@ static int resource_load_from_directory_recursive( return log_oom(); r = pattern_match_many(rr->patterns, rel_joined_for_matching, &extracted_fields); + if (r < 0) + return log_error_errno(r, "Failed to match pattern: %m"); if (r == PATTERN_MATCH_RETRY) { _cleanup_closedir_ DIR *subdir = NULL; @@ -168,10 +172,7 @@ static int resource_load_from_directory_recursive( return r; if (r == 0) continue; - } - else if (r < 0) - return log_error_errno(r, "Failed to match pattern: %m"); - else if (r == PATTERN_MATCH_NO) + } else if (r == PATTERN_MATCH_NO) continue; if (de->d_type == DT_DIR && m != S_IFDIR) @@ -192,6 +193,9 @@ static int resource_load_from_directory_recursive( if (instance->metadata.mode == MODE_INVALID) instance->metadata.mode = st.st_mode & 0775; /* mask out world-writability and suid and stuff, for safety */ + /* Can’t be both partial and pending. */ + assert(!(is_partial && is_pending)); + instance->is_partial = is_partial; instance->is_pending = is_pending; } @@ -229,18 +233,22 @@ static int resource_load_from_blockdev(Resource *rr) { assert(rr); + r = DLOPEN_FDISK(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + 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; @@ -309,6 +317,9 @@ static int resource_load_from_blockdev(Resource *rr) { if (instance->metadata.read_only < 0) instance->metadata.read_only = instance->partition_info.read_only; + /* Can’t be both partial and pending. */ + assert(!(is_partial && is_pending)); + instance->is_partial = is_partial; instance->is_pending = is_pending; } @@ -430,27 +441,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 +488,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) { @@ -533,6 +531,11 @@ static int resource_load_from_web( r = unhexmem_full(p, 64, /* secure= */ false, &h.iov_base, &h.iov_len); if (r < 0) return log_error_errno(r, "Failed to parse digest at manifest line %zu, refusing.", line_nr); + if (h.iov_len != sizeof_field(InstanceMetadata, sha256sum)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Manifest hash at line %zu decoded to %zu bytes, refusing.", + line_nr, + h.iov_len); p += 64, left -= 64; @@ -852,9 +855,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-target.c b/src/sysupdate/sysupdate-target.c new file mode 100644 index 0000000000000..ef132960f33e6 --- /dev/null +++ b/src/sysupdate/sysupdate-target.c @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "string-table.h" +#include "sysupdate-target.h" + +static const char* const target_class_table[_TARGET_CLASS_MAX] = { + [TARGET_MACHINE] = "machine", + [TARGET_PORTABLE] = "portable", + [TARGET_SYSEXT] = "sysext", + [TARGET_CONFEXT] = "confext", + [TARGET_COMPONENT] = "component", + [TARGET_HOST] = "host", +}; + +DEFINE_STRING_TABLE_LOOKUP(target_class, TargetClass); + +void target_identifier_done(TargetIdentifier *t) { + assert(t); + + t->class = _TARGET_CLASS_INVALID; + t->name = mfree(t->name); +} diff --git a/src/sysupdate/sysupdate-target.h b/src/sysupdate/sysupdate-target.h new file mode 100644 index 0000000000000..9c0a13bf4ad0a --- /dev/null +++ b/src/sysupdate/sysupdate-target.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "os-util.h" + +typedef enum TargetClass { + /* These should try to match ImageClass from src/basic/os-util.h */ + TARGET_MACHINE = IMAGE_MACHINE, + TARGET_PORTABLE = IMAGE_PORTABLE, + TARGET_SYSEXT = IMAGE_SYSEXT, + TARGET_CONFEXT = IMAGE_CONFEXT, + _TARGET_CLASS_IS_IMAGE_CLASS_MAX, + + /* sysupdate-specific classes */ + TARGET_HOST = _TARGET_CLASS_IS_IMAGE_CLASS_MAX, + TARGET_COMPONENT, + + _TARGET_CLASS_MAX, + _TARGET_CLASS_INVALID = -EINVAL, +} TargetClass; + +/* Let's ensure when the number of classes is updated things are updated here too */ +assert_cc((int) _IMAGE_CLASS_MAX == (int) _TARGET_CLASS_IS_IMAGE_CLASS_MAX); + +DECLARE_STRING_TABLE_LOOKUP(target_class, TargetClass); + +typedef struct TargetIdentifier { + TargetClass class; + char *name; +} TargetIdentifier; + +void target_identifier_done(TargetIdentifier *t); diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c index 46f3129f5d993..c79743cad6908 100644 --- a/src/sysupdate/sysupdate-transfer.c +++ b/src/sysupdate/sysupdate-transfer.c @@ -35,6 +35,7 @@ #include "strv.h" #include "sync-util.h" #include "sysupdate.h" +#include "sysupdate-cleanup.h" #include "sysupdate-feature.h" #include "sysupdate-instance.h" #include "sysupdate-pattern.h" @@ -121,11 +122,12 @@ static int config_parse_protect_version( _cleanup_free_ char *resolved = NULL; char ***protected_versions = ASSERT_PTR(data); + Transfer *t = ASSERT_PTR(userdata); int r; assert(rvalue); - r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved); + r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in ProtectVersion=, ignoring: %s", rvalue); @@ -159,11 +161,12 @@ static int config_parse_min_version( _cleanup_free_ char *resolved = NULL; char **version = ASSERT_PTR(data); + Transfer *t = ASSERT_PTR(userdata); int r; assert(rvalue); - r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved); + r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in MinVersion=, ignoring: %s", rvalue); @@ -191,6 +194,7 @@ static int config_parse_url_specifiers( void *data, void *userdata) { char ***s = ASSERT_PTR(data); + Transfer *t = ASSERT_PTR(userdata); _cleanup_free_ char *resolved = NULL; int r; @@ -201,7 +205,7 @@ static int config_parse_url_specifiers( return 0; } - r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved); + r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in %s=, ignoring: %s", lvalue, rvalue); @@ -235,11 +239,12 @@ static int config_parse_current_symlink( _cleanup_free_ char *resolved = NULL; char **current_symlink = ASSERT_PTR(data); + Transfer *t = ASSERT_PTR(userdata); int r; assert(rvalue); - r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved); + r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in CurrentSymlink=, ignoring: %s", rvalue); @@ -306,6 +311,7 @@ static int config_parse_resource_pattern( void *userdata) { char ***patterns = ASSERT_PTR(data); + Transfer *t = ASSERT_PTR(userdata); int r; assert(rvalue); @@ -327,7 +333,7 @@ static int config_parse_resource_pattern( if (r == 0) break; - r = specifier_printf(word, NAME_MAX, specifier_table, arg_root, NULL, &resolved); + r = specifier_printf(word, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in MatchPattern=, ignoring: %s", rvalue); @@ -360,6 +366,7 @@ static int config_parse_resource_path( void *userdata) { _cleanup_free_ char *resolved = NULL; Resource *rr = ASSERT_PTR(data); + Transfer *t = ASSERT_PTR(userdata); int r; assert(rvalue); @@ -370,7 +377,7 @@ static int config_parse_resource_path( return 0; } - r = specifier_printf(rvalue, PATH_MAX-1, specifier_table, arg_root, NULL, &resolved); + r = specifier_printf(rvalue, PATH_MAX-1, specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in Path=, ignoring: %s", rvalue); @@ -548,16 +555,16 @@ int transfer_read_definition(Transfer *t, const char *path, const char **dirs, H STRV_MAKE_CONST(path), dirs, strjoina(filename, ".d"), - arg_root, + t->context->root, /* root_fd= */ -EBADF, "Transfer\0" "Source\0" "Target\0", config_item_table_lookup, table, CONFIG_PARSE_WARN, - /* userdata= */ NULL, - /* stats_by_path= */ NULL, - /* drop_in_files= */ NULL); + t, + /* ret_stats_by_path= */ NULL, + /* ret_drop_in_files= */ NULL); if (r < 0) return r; @@ -612,7 +619,7 @@ int transfer_read_definition(Transfer *t, const char *path, const char **dirs, H return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "Source specification lacks Path=."); - if (t->source.path_relative_to == PATH_RELATIVE_TO_EXPLICIT && !arg_transfer_source) + if (t->source.path_relative_to == PATH_RELATIVE_TO_EXPLICIT && !t->context->transfer_source) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "PathRelativeTo=explicit requires --transfer-source= to be specified."); @@ -681,7 +688,7 @@ int transfer_resolve_paths( assert(t); - r = resource_resolve_path(&t->source, root, arg_transfer_source, node); + r = resource_resolve_path(&t->source, root, t->context->transfer_source, node); if (r < 0) return r; @@ -816,10 +823,13 @@ int transfer_vacuum( continue; } - /* If this is listed among the protected versions, then let's not remove it */ - if (strv_contains(t->protected_versions, instance->metadata.version) || - (extra_protected_version && streq(extra_protected_version, instance->metadata.version))) { - log_debug("Version '%s' is pending/partial but protected, not removing.", instance->metadata.version); + /* If this is pending and listed among the protected versions, then let's not remove it. + * In future, we will also want to keep partial protected versions, but that’s only useful + * once we support resuming downloads. */ + if (instance->is_pending && + (strv_contains(t->protected_versions, instance->metadata.version) || + (extra_protected_version && streq(extra_protected_version, instance->metadata.version)))) { + log_debug("Version '%s' is pending but protected, not removing.", instance->metadata.version); i++; continue; } @@ -834,7 +844,7 @@ int transfer_vacuum( r = transfer_instance_vacuum(t, instance); if (r < 0) - return 0; + return r; instance_free(instance); memmove(t->target.instances + i, t->target.instances + i + 1, (t->target.n_instances - i - 1) * sizeof(Instance*)); @@ -845,7 +855,7 @@ int transfer_vacuum( /* Second, calculate how many instances to keep, based on the instance limit — but keep at least one */ - instances_max = arg_instances_max != UINT64_MAX ? arg_instances_max : t->instances_max; + instances_max = t->context->instances_max != UINT64_MAX ? t->context->instances_max : t->instances_max; assert(instances_max >= 1); if (instances_max == UINT64_MAX) /* Keep infinite instances? */ limit = UINT64_MAX; @@ -933,7 +943,7 @@ int transfer_vacuum( r = transfer_instance_vacuum(t, oldest); if (r < 0) - return 0; + return r; instance_free(oldest); memmove(t->target.instances + p, t->target.instances + p + 1, (t->target.n_instances - p - 1) * sizeof(Instance*)); @@ -1009,6 +1019,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) @@ -1339,7 +1350,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra * download. */ if (!i->metadata.sha256sum_set) - return log_error_errno(r, "SHA256 checksum not known for download '%s', refusing.", i->path); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SHA256 checksum not known for download '%s', refusing.", i->path); digest = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum)); if (!digest) @@ -1364,7 +1375,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra SYSTEMD_IMPORT_PATH, "raw", "--direct", /* just copy/unpack the specified file, don't do anything else */ - arg_sync ? "--sync=yes" : "--sync=no", + t->context->sync ? "--sync=yes" : "--sync=no", i->path, t->temporary_partial_path), t, i, cb, userdata); @@ -1381,7 +1392,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra "--direct", /* just copy/unpack the specified file, don't do anything else */ "--offset", offset, "--size-max", max_size, - arg_sync ? "--sync=yes" : "--sync=no", + t->context->sync ? "--sync=yes" : "--sync=no", i->path, t->target.path), t, i, cb, userdata); @@ -1404,7 +1415,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra SYSTEMD_IMPORT_FS_PATH, "run", "--direct", /* just untar the specified file, don't do anything else */ - arg_sync ? "--sync=yes" : "--sync=no", + t->context->sync ? "--sync=yes" : "--sync=no", t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no", i->path, t->temporary_partial_path), @@ -1421,7 +1432,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra SYSTEMD_IMPORT_PATH, "tar", "--direct", /* just untar the specified file, don't do anything else */ - arg_sync ? "--sync=yes" : "--sync=no", + t->context->sync ? "--sync=yes" : "--sync=no", t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no", i->path, t->temporary_partial_path), @@ -1442,7 +1453,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra "raw", "--direct", /* just download the specified URL, don't download anything else */ "--verify", digest, /* validate by explicit SHA256 sum */ - arg_sync ? "--sync=yes" : "--sync=no", + t->context->sync ? "--sync=yes" : "--sync=no", i->path, t->temporary_partial_path), t, i, cb, userdata); @@ -1460,7 +1471,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra "--verify", digest, /* validate by explicit SHA256 sum */ "--offset", offset, "--size-max", max_size, - arg_sync ? "--sync=yes" : "--sync=no", + t->context->sync ? "--sync=yes" : "--sync=no", i->path, t->target.path), t, i, cb, userdata); @@ -1482,7 +1493,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra "--direct", /* just download the specified URL, don't download anything else */ "--verify", digest, /* validate by explicit SHA256 sum */ t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no", - arg_sync ? "--sync=yes" : "--sync=no", + t->context->sync ? "--sync=yes" : "--sync=no", i->path, t->temporary_partial_path), t, i, cb, userdata); @@ -1523,7 +1534,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra } /* Synchronize */ - if (arg_sync && need_sync) { + if (t->context->sync && need_sync) { if (t->target.type == RESOURCE_REGULAR_FILE) r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_partial_path); else { @@ -1609,8 +1620,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(). */ @@ -1666,6 +1679,8 @@ int transfer_install_instance( resource_type_to_string(t->target.type)); t->temporary_pending_path = mfree(t->temporary_pending_path); + + (void) context_installdb_record(t->context, t->target.path, t->target.patterns); } if (t->final_partition_label) { @@ -1744,6 +1759,10 @@ int transfer_install_instance( if (r < 0) return log_error_errno(r, "Failed to make symlink path '%s' relative to '%s': %m", link_target, parent); + r = mkdir_parents(link_path, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create directory for current symlink '%s': %m", link_path); + r = symlink_atomic(relative, link_path); if (r < 0) return log_error_errno(r, "Failed to update current symlink '%s' %s '%s': %m", diff --git a/src/sysupdate/sysupdate-update-set-flags.c b/src/sysupdate/sysupdate-update-set-flags.c index 36801938f65f1..7b684576b0614 100644 --- a/src/sysupdate/sysupdate-update-set-flags.c +++ b/src/sysupdate/sysupdate-update-set-flags.c @@ -70,6 +70,11 @@ const char* update_set_flags_to_string(UpdateSetFlags flags) { case UPDATE_INSTALLED|UPDATE_PARTIAL|UPDATE_NEWEST|UPDATE_PROTECTED: case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_NEWEST: case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_NEWEST|UPDATE_PROTECTED: + /* can also contain pending instances: */ + case UPDATE_INSTALLED|UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_NEWEST: + case UPDATE_INSTALLED|UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_NEWEST|UPDATE_PROTECTED: + case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_NEWEST: + case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_NEWEST|UPDATE_PROTECTED: return "current+partial"; case UPDATE_AVAILABLE|UPDATE_NEWEST: diff --git a/src/sysupdate/sysupdate-util.c b/src/sysupdate/sysupdate-util.c index 1aba63cf7828c..b3ed72577e911 100644 --- a/src/sysupdate/sysupdate-util.c +++ b/src/sysupdate/sysupdate-util.c @@ -4,8 +4,14 @@ #include "bus-error.h" #include "bus-locator.h" +#include "conf-files.h" +#include "constants.h" #include "log.h" #include "login-util.h" +#include "path-util.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" #include "sysupdate-util.h" int reboot_now(void) { @@ -24,3 +30,72 @@ int reboot_now(void) { return 0; } + +bool component_name_valid(const char *c) { + /* See if the specified string enclosed in the directory prefix+suffix would be a valid file name */ + + if (!string_is_safe(c, STRING_FILENAME_PART)) + return false; + + /* Stack allocation is safe, since STRING_FILENAME_PART includes a length check */ + const char *j = strjoina("sysupdate.", c, ".d"); + + return filename_is_valid(j); +} + +int get_component_list(const char *root, char ***ret) { + int r; + + assert(ret); + + ConfFile **directories = NULL; + size_t n_directories = 0; + CLEANUP_ARRAY(directories, n_directories, conf_file_free_array); + + r = conf_files_list_strv_full( + ".d", + root, + CONF_FILES_DIRECTORY|CONF_FILES_WARN, + (const char * const *) CONF_PATHS_STRV(""), + &directories, + &n_directories); + if (r < 0) + return r; + + _cleanup_set_free_ Set *names = NULL; + + FOREACH_ARRAY(i, directories, n_directories) { + ConfFile *e = *i; + + const char *s = startswith(e->filename, "sysupdate."); + if (!s) + continue; + + const char *a = endswith(s, ".d"); + if (!a) + continue; + + if (a == s) + continue; + + _cleanup_free_ char *n = strndup(s, a - s); + if (!n) + return log_oom(); + + if (!component_name_valid(n)) + continue; + + r = set_ensure_consume(&names, &string_hash_ops_free, TAKE_PTR(n)); + if (r < 0 && r != -EEXIST) + return r; + } + + _cleanup_strv_free_ char **z = set_to_strv(&names); + if (!z) + return -ENOMEM; + + strv_sort(z); + + *ret = TAKE_PTR(z); + return 0; +} diff --git a/src/sysupdate/sysupdate-util.h b/src/sysupdate/sysupdate-util.h index 8504fd24b7d27..07d9def70872e 100644 --- a/src/sysupdate/sysupdate-util.h +++ b/src/sysupdate/sysupdate-util.h @@ -1,8 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ - #pragma once +#include + int reboot_now(void); -#define SD_SYSUPDATE_OFFLINE (UINT64_C(1) << 0) +#define SD_SYSUPDATE_OFFLINE (UINT64_C(1) << 0) #define SD_SYSUPDATE_FLAGS_ALL (SD_SYSUPDATE_OFFLINE) + +bool component_name_valid(const char *c); +int get_component_list(const char *root, char ***ret); diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 7693ec373caac..e5af39849c573 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -1,55 +1,71 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include +#include "sd-bus.h" #include "sd-daemon.h" +#include "sd-json.h" +#include "sd-varlink.h" #include "build.h" +#include "bus-polkit.h" #include "conf-files.h" #include "constants.h" +#include "discover-image.h" #include "dissect-image.h" +#include "env-util.h" +#include "errno-util.h" +#include "fd-util.h" #include "format-table.h" #include "glyph-util.h" +#include "hashmap.h" +#include "help-util.h" #include "hexdecoct.h" #include "image-policy.h" +#include "json-util.h" #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" #include "parse-util.h" -#include "path-util.h" #include "pretty-print.h" -#include "set.h" +#include "runtime-scope.h" #include "sort-util.h" #include "specifier.h" #include "string-util.h" #include "strv.h" #include "sysupdate.h" +#include "sysupdate-cleanup.h" #include "sysupdate-feature.h" #include "sysupdate-instance.h" +#include "sysupdate-target.h" #include "sysupdate-transfer.h" #include "sysupdate-update-set.h" #include "sysupdate-util.h" -#include "utf8.h" +#include "varlink-io.systemd.SysUpdate.h" +#include "varlink-util.h" #include "verbs.h" static char *arg_definitions = NULL; -bool arg_sync = true; -uint64_t arg_instances_max = UINT64_MAX; +static bool arg_sync = true; +static uint64_t arg_instances_max = UINT64_MAX; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; -char *arg_root = NULL; +static char *arg_root = NULL; static char *arg_image = NULL; static bool arg_reboot = false; +static int arg_cleanup = -1; static char *arg_component = NULL; +static bool arg_component_all = false; static int arg_verify = -1; static ImagePolicy *arg_image_policy = NULL; static bool arg_offline = false; -char *arg_transfer_source = NULL; +static char *arg_transfer_source = NULL; +static bool arg_varlink = false; STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); @@ -64,59 +80,112 @@ const Specifier specifier_table[] = { {} }; -typedef struct Context { - Transfer **transfers; - size_t n_transfers; - - Transfer **disabled_transfers; - size_t n_disabled_transfers; - - Hashmap *features; /* Defined features, keyed by ID */ - - UpdateSet **update_sets; - size_t n_update_sets; - - UpdateSet *newest_installed, *candidate; +#define CONTEXT_NULL \ + (Context) { \ + .sync = true, \ + .instances_max = UINT64_MAX, \ + .verify = -1, \ + .cleanup = -1, \ + .installdb_fd = -EBADF, \ + .target_identifier.class = _TARGET_CLASS_INVALID, \ + } - Hashmap *web_cache; /* Cache for downloaded resources, keyed by URL */ -} Context; +void context_done(Context *c) { + assert(c); -static Context* context_free(Context *c) { - if (!c) - return NULL; + c->mounted_dir = umount_and_rmdir_and_free(c->mounted_dir); + c->loop_device = loop_device_unref(c->loop_device); FOREACH_ARRAY(tr, c->transfers, c->n_transfers) transfer_free(*tr); - free(c->transfers); + c->transfers = mfree(c->transfers); + c->n_transfers = 0; FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) transfer_free(*tr); - free(c->disabled_transfers); + c->disabled_transfers = mfree(c->disabled_transfers); + c->n_disabled_transfers = 0; - hashmap_free(c->features); + c->features = hashmap_free(c->features); FOREACH_ARRAY(us, c->update_sets, c->n_update_sets) update_set_free(*us); - free(c->update_sets); + c->update_sets = mfree(c->update_sets); + c->n_update_sets = 0; + + c->web_cache = hashmap_free(c->web_cache); - hashmap_free(c->web_cache); + c->installdb_fd = safe_close(c->installdb_fd); - return mfree(c); + c->definitions = mfree(c->definitions); + c->root = mfree(c->root); + c->image = mfree(c->image); + c->component = mfree(c->component); + c->image_policy = image_policy_free(c->image_policy); + c->transfer_source = mfree(c->transfer_source); + + target_identifier_done(&c->target_identifier); } -DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free); +static int context_from_cmdline(Context *ret) { + assert(ret); + + _cleanup_(context_done) Context context = CONTEXT_NULL; + + context.instances_max = arg_instances_max; + context.sync = arg_sync; + context.reboot = arg_reboot; + context.verify = arg_verify; + context.offline = arg_offline; + context.cleanup = arg_cleanup; + context.component_all = arg_component_all; + + if (strdup_to(&context.definitions, arg_definitions) < 0) + return log_oom(); -static Context* context_new(void) { - /* For now, no fields to initialize non-zero */ - return new0(Context, 1); + if (strdup_to(&context.root, arg_root) < 0) + return log_oom(); + + if (strdup_to(&context.image, arg_image) < 0) + return log_oom(); + + if (strdup_to(&context.component, arg_component) < 0) + return log_oom(); + + if (strdup_to(&context.transfer_source, arg_transfer_source) < 0) + return log_oom(); + + if (arg_image_policy) { + context.image_policy = image_policy_copy(arg_image_policy); + if (!context.image_policy) + return log_oom(); + } + + *ret = TAKE_GENERIC(context, Context, CONTEXT_NULL); + return 0; } -static void free_transfers(Transfer **array, size_t n) { - FOREACH_ARRAY(t, array, n) - transfer_free(*t); - free(array); +/* Stores any long-running server state which needs to persist between varlink calls, such as state for + * pending polkit requests */ +typedef struct Server { + sd_bus *system_bus; + Hashmap *polkit_registry; +} Server; + +#define SERVER_NULL \ + (Server) { \ + /* all fields fine with being initialised to NULL */ \ + } + +static void server_done(Server *s) { + assert(s); + + s->polkit_registry = hashmap_free(s->polkit_registry); + s->system_bus = sd_bus_flush_close_unref(s->system_bus); } +static DEFINE_POINTER_ARRAY_FREE_FUNC(Transfer*, transfer_free); + static int read_definitions( Context *c, const char **dirs, @@ -128,15 +197,15 @@ 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); assert(suffix); - r = conf_files_list_strv_full(suffix, arg_root, + r = conf_files_list_strv_full(suffix, c->root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN, dirs, &files, &n_files); if (r < 0) @@ -155,7 +224,7 @@ static int read_definitions( if (r < 0) return r; - r = transfer_resolve_paths(t, arg_root, node); + r = transfer_resolve_paths(t, c->root, node); if (r < 0) return r; @@ -175,15 +244,20 @@ static int read_definitions( return 0; } -static int context_read_definitions(Context *c, const char* node, bool requires_enabled_transfers) { +typedef enum ReadDefinitionsFlags { + READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS = 1 << 0, + READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS = 1 << 1, +} ReadDefinitionsFlags; + +static int context_read_definitions(Context *c, const char* node, ReadDefinitionsFlags flags) { _cleanup_strv_free_ char **dirs = NULL; int r; assert(c); - if (arg_definitions) - dirs = strv_new(arg_definitions); - else if (arg_component) { + if (c->definitions) + dirs = strv_new(c->definitions); + else if (c->component) { char **l = CONF_PATHS_STRV(""); size_t i = 0; @@ -194,7 +268,7 @@ static int context_read_definitions(Context *c, const char* node, bool requires_ STRV_FOREACH(dir, l) { char *j; - j = strjoin(*dir, "sysupdate.", arg_component, ".d"); + j = strjoin(*dir, "sysupdate.", c->component, ".d"); if (!j) return log_oom(); @@ -208,9 +282,9 @@ 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, + r = conf_files_list_strv_full(".feature", c->root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN, (const char**) dirs, &files, &n_files); if (r < 0) @@ -224,7 +298,7 @@ static int context_read_definitions(Context *c, const char* node, bool requires_ if (!f) return log_oom(); - r = feature_read_definition(f, e->result, (const char**) dirs); + r = feature_read_definition(f, c->root, e->result, (const char**) dirs); if (r < 0) return r; @@ -248,11 +322,12 @@ static int context_read_definitions(Context *c, const char* node, bool requires_ log_warning("As of v257, transfer definitions should have the '.transfer' extension."); } - if (c->n_transfers + (requires_enabled_transfers ? 0 : c->n_disabled_transfers) == 0) { - if (arg_component) + if (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS) && + c->n_transfers + (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS) ? 0 : c->n_disabled_transfers) == 0) { + if (c->component) return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No transfer definitions for component '%s' found.", - arg_component); + c->component); return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No transfer definitions found."); @@ -273,7 +348,7 @@ static int context_load_installed_instances(Context *c) { r = resource_load_instances( &t->target, - arg_verify >= 0 ? arg_verify : t->verify, + c->verify >= 0 ? c->verify : t->verify, &c->web_cache); if (r < 0) return r; @@ -284,7 +359,7 @@ static int context_load_installed_instances(Context *c) { r = resource_load_instances( &t->target, - arg_verify >= 0 ? arg_verify : t->verify, + c->verify >= 0 ? c->verify : t->verify, &c->web_cache); if (r < 0) return r; @@ -305,7 +380,7 @@ static int context_load_available_instances(Context *c) { r = resource_load_instances( &t->source, - arg_verify >= 0 ? arg_verify : t->verify, + c->verify >= 0 ? c->verify : t->verify, &c->web_cache); if (r < 0) return r; @@ -397,13 +472,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; @@ -415,7 +489,10 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags extra_flags |= UPDATE_PROTECTED; /* Partial or pending updates by definition are not incomplete, they’re - * partial/pending instead */ + * partial/pending instead. While an individual Instance cannot be both partial and + * pending, an UpdateSet as a whole can contain both partial and pending instances. */ + assert(!match || !(match->is_partial && match->is_pending)); + if (match && match->is_partial) extra_flags = (extra_flags | UPDATE_PARTIAL) & ~UPDATE_INCOMPLETE; @@ -507,8 +584,9 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0) c->candidate = NULL; - /* Newest installed is still pending and no candidate is set? Then it becomes the candidate. */ - if (c->newest_installed && FLAGS_SET(c->newest_installed->flags, UPDATE_PENDING) && + /* Newest installed is still pending or partial and no candidate is set? Then it becomes the candidate. */ + if (c->newest_installed && + (c->newest_installed->flags & (UPDATE_PENDING|UPDATE_PARTIAL)) && !c->candidate) c->candidate = c->newest_installed; @@ -526,7 +604,7 @@ static int context_discover_update_sets(Context *c) { if (r < 0) return r; - if (!arg_offline) { + if (!c->offline) { log_info("Determining available update sets%s", glyph(GLYPH_ELLIPSIS)); r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE); @@ -671,7 +749,7 @@ static int context_show_version(Context *c, const char *version) { Instance *i = *inst; if (!i) { - assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE)); + assert(us->flags & (UPDATE_INCOMPLETE|UPDATE_PARTIAL|UPDATE_PENDING)); continue; } @@ -926,20 +1004,73 @@ static int context_vacuum( return 0; } -static int context_make_offline(Context **ret, const char *node, bool requires_enabled_transfers) { - _cleanup_(context_freep) Context* context = NULL; +typedef enum ProcessImageFlags { + PROCESS_IMAGE_READ_ONLY = 1 << 0, +} ProcessImageFlags; + +static int context_process_image( + Context *c, + ProcessImageFlags flags) { + + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; int r; - assert(ret); + assert(c); - /* Allocates a context object and initializes everything we can initialize offline, i.e. without - * checking on the update source (i.e. the Internet) what versions are available */ + if (!c->image) + return 0; + + assert(!c->root); + assert(!c->mounted_dir); + assert(!c->loop_device); + + r = mount_image_privately_interactively( + c->image, + c->image_policy, + (FLAGS_SET(flags, PROCESS_IMAGE_READ_ONLY) ? DISSECT_IMAGE_READ_ONLY : 0) | + DISSECT_IMAGE_FSCK | + DISSECT_IMAGE_MKDIR | + DISSECT_IMAGE_GROWFS | + DISSECT_IMAGE_RELAX_VAR_CHECK | + DISSECT_IMAGE_USR_NO_ROOT | + DISSECT_IMAGE_GENERIC_ROOT | + DISSECT_IMAGE_REQUIRE_ROOT | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, + &mounted_dir, + /* ret_dir_fd= */ NULL, + &loop_device); + if (r < 0) + return r; - context = context_new(); - if (!context) + c->root = strdup(mounted_dir); + if (!c->root) return log_oom(); - r = context_read_definitions(context, node, requires_enabled_transfers); + c->mounted_dir = TAKE_PTR(mounted_dir); + c->loop_device = TAKE_PTR(loop_device); + + return 0; +} + +static int context_list_components(Context *context, char ***ret_component_names, bool *ret_has_default_component); + +static int context_load_offline( + Context *context, + ProcessImageFlags process_image_flags, + ReadDefinitionsFlags read_definitions_flags) { + int r; + + assert(context); + + /* Sets up a context object and initializes everything we can initialize offline, i.e. without + * checking on the update source (i.e. the Internet) what versions are available */ + + r = context_process_image(context, process_image_flags); + if (r < 0) + return r; + + r = context_read_definitions(context, context->loop_device ? context->loop_device->node : NULL, read_definitions_flags); if (r < 0) return r; @@ -947,24 +1078,27 @@ static int context_make_offline(Context **ret, const char *node, bool requires_e if (r < 0) return r; - *ret = TAKE_PTR(context); return 0; } -static int context_make_online(Context **ret, const char *node) { - _cleanup_(context_freep) Context* context = NULL; +static int context_load_online( + Context *context, + ProcessImageFlags process_image_flags) { int r; - assert(ret); + assert(context); - /* Like context_make_offline(), but also communicates with the update source looking for new + /* Like context_load_offline(), but also communicates with the update source looking for new * versions (as long as --offline is not specified on the command line). */ - r = context_make_offline(&context, node, /* requires_enabled_transfers= */ true); + r = context_load_offline( + context, + process_image_flags, + READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS); if (r < 0) return r; - if (!arg_offline) { + if (!context->offline) { r = context_load_available_instances(context); if (r < 0) return r; @@ -974,10 +1108,137 @@ static int context_make_online(Context **ret, const char *node) { if (r < 0) return r; - *ret = TAKE_PTR(context); return 0; } +static bool image_type_can_sysupdate(ImageType image_type) { + /* systemd-sysupdate doesn't support mstack images yet */ + return IN_SET(image_type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME, IMAGE_RAW, IMAGE_BLOCK); +} + +static int context_load_paths_from_image(Context *context, Image *image) { + assert(context); + assert(image); + + assert(!context->root); + assert(!context->image); + + switch (image->type) { + case IMAGE_DIRECTORY: + case IMAGE_SUBVOLUME: + context->root = strdup(image->path); + if (!context->root) + return log_oom(); + return 0; + case IMAGE_RAW: + case IMAGE_BLOCK: + context->image = strdup(image->path); + if (!context->image) + return log_oom(); + return 0; + default: + assert_not_reached(); + } +} + +/* Load a Context to point to the target given by the TargetIdentifier. The TargetIdentifier will have been + * syntactically validated by dispatch_target_identifier(), but might still point to components which don’t + * exist, images which the user isn’t privileged to access, etc. This function validates the TargetIdentifier + * against an enumerated list of known targets, which are safe to update without additional permissions. */ +static int context_load_online_from_target(Context *context, ProcessImageFlags process_image_flags) { + int r; + + assert(context); + assert(context->target_identifier.class != _TARGET_CLASS_INVALID); + + /* These shouldn’t have been set up some other way first */ + assert(!context->component); + assert(!context->root); + assert(!context->image); + + switch (context->target_identifier.class) { + case TARGET_MACHINE: + case TARGET_PORTABLE: + case TARGET_SYSEXT: + case TARGET_CONFEXT: { + _cleanup_hashmap_free_ Hashmap *images = NULL; + Image *image, *selected_image = NULL; + + /* These are all image-based target classes, so first find the corresponding image. */ + r = image_discover(RUNTIME_SCOPE_SYSTEM, (ImageClass) context->target_identifier.class, NULL, &images); + if (r < 0) + return r; + + HASHMAP_FOREACH(image, images) { + bool have = false; + _cleanup_(context_done) Context image_context = CONTEXT_NULL; + + if (image_is_host(image)) + continue; /* We already enroll the host ourselves */ + + if (!image_type_can_sysupdate(image->type)) + continue; + + if (!streq(image->name, context->target_identifier.name)) + continue; + + r = context_load_paths_from_image(&image_context, image); + if (r < 0) + return r; + + /* Load the components in a separate Context specific to the given Image before + * committing to loading that state to the main Context. */ + r = context_load_offline(&image_context, 0, 0); + if (r < 0) + return r; + + r = context_list_components(&image_context, /* ret_component_names= */ NULL, &have); + if (r < 0) + return r; + if (!have) { + log_debug("Skipping %s because it has no default component", image->path); + continue; + } + + /* This is the match we were looking for */ + selected_image = image; + break; + } + + if (!selected_image) + return -ENOENT; + + r = context_load_paths_from_image(context, selected_image); + if (r < 0) + return r; + + break; + } + case TARGET_HOST: + /* No additional setup needed */ + break; + case TARGET_COMPONENT: { + _cleanup_strv_free_ char **component_names = NULL; + + r = context_list_components(context, &component_names, /* ret_has_default_component= */ NULL); + if (r < 0) + return r; + + if (!strv_contains(component_names, context->target_identifier.name)) + return -ENOENT; + + context->component = strdup(context->target_identifier.name); + if (!context->component) + return log_oom(); + break; + } + default: + assert_not_reached(); + } + + return context_load_online(context, process_image_flags); +} + static int context_on_acquire_progress(const Transfer *t, const Instance *inst, unsigned percentage) { const Context *c = ASSERT_PTR(t->context); size_t i, n = c->n_transfers; @@ -1030,9 +1291,7 @@ static int context_acquire( if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE)) log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version); else if (FLAGS_SET(us->flags, UPDATE_PARTIAL)) { - log_info("Selected update '%s' is already acquired and partially installed. Vacuum it to try installing again.", us->version); - - return 0; + return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Selected update '%s' is already acquired and partially installed. Vacuum it to try installing again.", us->version); } else if (FLAGS_SET(us->flags, UPDATE_PENDING)) { log_info("Selected update '%s' is already acquired and pending installation.", us->version); @@ -1087,7 +1346,7 @@ static int context_acquire( if (r < 0) return r; - if (arg_sync) + if (c->sync) sync(); (void) sd_notifyf(/* unset_environment= */ false, @@ -1112,7 +1371,7 @@ static int context_acquire( return r; } - if (arg_sync) + if (c->sync) sync(); return 1; @@ -1182,6 +1441,82 @@ static int context_process_partial_and_pending( return 1; } +static int notify_subscribers_reply( + sd_varlink *link, + sd_json_variant *reply, + const char *error_id, + sd_varlink_reply_flags_t flags, + void *userdata) { + + assert(link); + + if (error_id) + log_warning("Notification subscriber '%s' returned error, ignoring: %s", + strna(sd_varlink_get_description(link)), error_id); + + return 0; +} + +static int context_notify_subscribers(Context *c, UpdateSet *us) { + int r; + + assert(c); + + /* 'us' is NULL when we are forced to notify even though no update was applied (via + * SYSTEMD_SYSUPDATE_FORCE_NOTIFY=1). In that case we send neither a version nor a resource list. */ + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *resources = NULL; + if (us) + for (size_t i = 0; i < c->n_transfers; i++) { + Instance *inst = us->instances[i]; + Transfer *t = c->transfers[i]; + + if (inst->resource == &t->target && + !inst->is_pending) + continue; + + /* Report where the resource was installed *to* (not the source it came from): the + * final on-disk path for filesystem targets, the partition device node for partition + * targets. */ + const char *target_path = + RESOURCE_IS_FILESYSTEM(t->target.type) ? t->final_path : + t->target.type == RESOURCE_PARTITION ? t->partition_info.device : + NULL; + + r = sd_json_variant_append_arraybo( + &resources, + SD_JSON_BUILD_PAIR_STRING("transfer", t->id), + SD_JSON_BUILD_PAIR_CONDITION(!!target_path, "path", SD_JSON_BUILD_STRING(target_path))); + if (r < 0) + return log_warning_errno(r, "Failed to build sysupdate notify resources list, skipping notification: %m"); + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; + r = sd_json_buildo( + ¶ms, + SD_JSON_BUILD_PAIR_CONDITION(!!c->component, "component", SD_JSON_BUILD_STRING(c->component)), + SD_JSON_BUILD_PAIR_CONDITION(!!us, "version", SD_JSON_BUILD_STRING(us ? us->version : NULL)), + SD_JSON_BUILD_PAIR_CONDITION(!!resources, "resources", SD_JSON_BUILD_VARIANT(resources))); + if (r < 0) + return log_warning_errno(r, "Failed to build sysupdate notify parameters, skipping notification: %m"); + + ssize_t n = varlink_execute_directory( + VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK, + "io.systemd.SysUpdate.Notify.OnCompletedUpdate", + params, + /* more= */ false, + /* timeout_usec= */ 5 * USEC_PER_MINUTE, + notify_subscribers_reply, + /* userdata= */ NULL); + if (n < 0) + log_debug_errno(n, "Failed to dispatch sysupdate notification to %s, ignoring: %m", + VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK); + else if (n > 0) + log_debug("Dispatched sysupdate notification to %zi subscribers in %s.", n, VARLINK_DIR_SYSUPDATE_NOTIFY_HOOK); + + return 0; +} + static int context_install( Context *c, const char *version, @@ -1207,7 +1542,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]; @@ -1217,13 +1554,16 @@ static int context_install( !inst->is_pending) continue; - r = transfer_install_instance(t, inst, arg_root); + r = transfer_install_instance(t, inst, c->root); if (r < 0) return r; } log_info("%s Successfully installed update '%s'.", glyph(GLYPH_SPARKLES), us->version); + if (!c->root) + (void) context_notify_subscribers(c, us); + (void) sd_notifyf(/* unset_environment= */ false, "STATUS=Installed '%s'.", us->version); @@ -1233,55 +1573,58 @@ static int context_install( return 1; } -static int process_image( - bool ro, - char **ret_mounted_dir, - LoopDevice **ret_loop_device) { +static JSON_DISPATCH_ENUM_DEFINE(dispatch_target_class, TargetClass, target_class_from_string); - _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; - _cleanup_(umount_and_freep) char *mounted_dir = NULL; +static int dispatch_target_identifier(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + TargetIdentifier *t = ASSERT_PTR(userdata); + static const sd_json_dispatch_field dispatch[] = { + { "class", SD_JSON_VARIANT_STRING, dispatch_target_class, voffsetof(*t, class), SD_JSON_MANDATORY }, + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, voffsetof(*t, name), SD_JSON_NULLABLE }, + {} + }; int r; - assert(ret_mounted_dir); - assert(ret_loop_device); - - if (!arg_image) - return 0; - - assert(!arg_root); - - r = mount_image_privately_interactively( - arg_image, - arg_image_policy, - (ro ? DISSECT_IMAGE_READ_ONLY : 0) | - DISSECT_IMAGE_FSCK | - DISSECT_IMAGE_MKDIR | - DISSECT_IMAGE_GROWFS | - DISSECT_IMAGE_RELAX_VAR_CHECK | - DISSECT_IMAGE_USR_NO_ROOT | - DISSECT_IMAGE_GENERIC_ROOT | - DISSECT_IMAGE_REQUIRE_ROOT | - DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, - &mounted_dir, - /* ret_dir_fd= */ NULL, - &loop_device); + r = sd_json_dispatch(variant, dispatch, flags, t); if (r < 0) return r; - arg_root = strdup(mounted_dir); - if (!arg_root) - return log_oom(); + /* Name is mandatory unless class is `host` */ + if ((t->class == TARGET_HOST) != (!t->name)) + return json_log(variant, flags, SYNTHETIC_ERRNO(ENXIO), "Target name does not match class."); - *ret_mounted_dir = TAKE_PTR(mounted_dir); - *ret_loop_device = TAKE_PTR(loop_device); + if (t->class == TARGET_COMPONENT && !component_name_valid(t->name)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", t->name); return 0; } -static int verb_list(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; +static int verify_polkit(Context *context, sd_varlink *link, const char *action, const char **details) { + int r; + Server *s = ASSERT_PTR(sd_varlink_get_userdata(ASSERT_PTR(link))); + + assert(context); + + if (!s->system_bus) { + r = sd_bus_open_system_with_description(&s->system_bus, "sysupdate-system"); + if (r < 0) + return log_error_errno(r, "Failed to get system bus connection: %m"); + + r = sd_bus_attach_event(s->system_bus, sd_varlink_get_event(link), SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach system bus to event loop: %m"); + } + + return varlink_verify_polkit_async(link, + s->system_bus, + action, + details, + &s->polkit_registry); +} + +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_(context_done) Context context = CONTEXT_NULL; _cleanup_strv_free_ char **appstream_urls = NULL; const char *version; int r; @@ -1289,25 +1632,28 @@ static int verb_list(int argc, char **argv, void *userdata) { assert(argc <= 2); version = argc >= 2 ? argv[1] : NULL; - r = process_image(/* ro= */ true, &mounted_dir, &loop_device); + r = context_from_cmdline(&context); if (r < 0) return r; - r = context_make_online(&context, loop_device ? loop_device->node : NULL); + if (context.component_all) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); + + r = context_load_online(&context, PROCESS_IMAGE_READ_ONLY); if (r < 0) return r; if (version) - return context_show_version(context, version); + return context_show_version(&context, version); else if (!sd_json_format_enabled(arg_json_format_flags)) - return context_show_table(context); + return context_show_table(&context); else { _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; _cleanup_strv_free_ char **versions = NULL; const char *current = NULL; bool current_is_pending = false; - FOREACH_ARRAY(update_set, context->update_sets, context->n_update_sets) { + FOREACH_ARRAY(update_set, context.update_sets, context.n_update_sets) { UpdateSet *us = *update_set; if (FLAGS_SET(us->flags, UPDATE_INSTALLED) && @@ -1321,7 +1667,7 @@ static int verb_list(int argc, char **argv, void *userdata) { return log_oom(); } - FOREACH_ARRAY(tr, context->transfers, context->n_transfers) + FOREACH_ARRAY(tr, context.transfers, context.n_transfers) STRV_FOREACH(appstream_url, (*tr)->appstream) { /* Avoid duplicates */ if (strv_contains(appstream_urls, *appstream_url)) @@ -1346,10 +1692,10 @@ static int verb_list(int argc, char **argv, void *userdata) { } } -static int verb_features(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; +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_(context_done) Context context = CONTEXT_NULL; _cleanup_(table_unrefp) Table *table = NULL; const char *feature_id; Feature *f; @@ -1358,18 +1704,24 @@ static int verb_features(int argc, char **argv, void *userdata) { assert(argc <= 2); feature_id = argc >= 2 ? argv[1] : NULL; - r = process_image(/* ro= */ true, &mounted_dir, &loop_device); + r = context_from_cmdline(&context); if (r < 0) return r; - r = context_make_offline(&context, loop_device ? loop_device->node : NULL, /* requires_enabled_transfers= */ false); + if (context.component_all) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); + + r = context_load_offline( + &context, + PROCESS_IMAGE_READ_ONLY, + READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS); if (r < 0) return r; if (feature_id) { _cleanup_strv_free_ char **transfers = NULL; - f = hashmap_get(context->features, feature_id); + f = hashmap_get(context.features, feature_id); if (!f) return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Optional feature not found: %s", @@ -1379,7 +1731,7 @@ static int verb_features(int argc, char **argv, void *userdata) { if (!table) return log_oom(); - FOREACH_ARRAY(tr, context->transfers, context->n_transfers) { + FOREACH_ARRAY(tr, context.transfers, context.n_transfers) { Transfer *t = *tr; if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id)) @@ -1390,7 +1742,7 @@ static int verb_features(int argc, char **argv, void *userdata) { return log_oom(); } - FOREACH_ARRAY(tr, context->disabled_transfers, context->n_disabled_transfers) { + FOREACH_ARRAY(tr, context.disabled_transfers, context.n_disabled_transfers) { Transfer *t = *tr; if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id)) @@ -1445,7 +1797,7 @@ static int verb_features(int argc, char **argv, void *userdata) { if (!table) return log_oom(); - HASHMAP_FOREACH(f, context->features) { + HASHMAP_FOREACH(f, context.features) { r = table_add_many(table, TABLE_BOOLEAN_CHECKMARK, f->enabled, TABLE_SET_COLOR, ansi_highlight_green_red(f->enabled), @@ -1462,7 +1814,7 @@ static int verb_features(int argc, char **argv, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; _cleanup_strv_free_ char **features = NULL; - HASHMAP_FOREACH(f, context->features) { + HASHMAP_FOREACH(f, context.features) { r = strv_extend(&features, f->id); if (r < 0) return log_oom(); @@ -1480,34 +1832,39 @@ static int verb_features(int argc, char **argv, void *userdata) { return 0; } -static int verb_check_new(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; +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_(context_done) Context context = CONTEXT_NULL; int r; assert(argc <= 1); - r = process_image(/* ro= */ true, &mounted_dir, &loop_device); + r = context_from_cmdline(&context); if (r < 0) return r; - r = context_make_online(&context, loop_device ? loop_device->node : NULL); + if (context.component_all) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); + + r = context_load_online( + &context, + PROCESS_IMAGE_READ_ONLY); if (r < 0) return r; if (!sd_json_format_enabled(arg_json_format_flags)) { - if (!context->candidate) { + if (!context.candidate) { log_debug("No candidate found."); return EXIT_FAILURE; } - puts(context->candidate->version); + puts(context.candidate->version); } else { _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; - if (context->candidate) - r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("available", context->candidate->version)); + if (context.candidate) + r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("available", context.candidate->version)); else r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_NULL("available")); if (r < 0) @@ -1521,27 +1878,50 @@ 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; +static int vl_method_check_new(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + _cleanup_(context_done) Context context = CONTEXT_NULL; int r; - assert(argc <= 1); + assert(link); - if (arg_instances_max < 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "The --instances-max argument must be >= 1 while vacuuming"); + static const sd_json_dispatch_field dispatch_table[] = { + { "target", SD_JSON_VARIANT_OBJECT, dispatch_target_identifier, voffsetof(context, target_identifier), SD_JSON_MANDATORY }, + VARLINK_DISPATCH_POLKIT_FIELD, + {}, + }; - r = process_image(/* ro= */ false, &mounted_dir, &loop_device); + r = sd_varlink_dispatch(link, parameters, dispatch_table, &context); + if (r != 0) + return r; + + r = verify_polkit(&context, link, "org.freedesktop.sysupdate1.check", + (const char**) STRV_MAKE( + "class", target_class_to_string(context.target_identifier.class), + "offline", "0", + context.target_identifier.name ? "name" : NULL, context.target_identifier.name)); + if (r <= 0) + return r; + + if (getenv_bool("SYSTEMD_SYSUPDATE_NO_VERIFY") > 0) + context.verify = 0; + + /* CheckNew is always online */ + context.offline = false; + + r = context_load_online_from_target(&context, PROCESS_IMAGE_READ_ONLY); + if (r == -ENOENT) + return sd_varlink_error(link, "io.systemd.SysUpdate.NoSuchTarget", NULL); if (r < 0) return r; - r = context_make_offline(&context, loop_device ? loop_device->node : NULL, /* requires_enabled_transfers= */ false); + if (context.candidate) + r = sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("available", context.candidate->version)); + else + r = sd_varlink_error(link, "io.systemd.SysUpdate.NoUpdateNeeded", NULL); if (r < 0) return r; - return context_vacuum(context, 0, NULL); + return 0; } typedef enum { @@ -1550,9 +1930,7 @@ typedef enum { } UpdateActionFlags; static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flags) { - _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; - _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; - _cleanup_(context_freep) Context* context = NULL; + _cleanup_(context_done) Context context = CONTEXT_NULL; _cleanup_free_ char *booted_version = NULL; UpdateSet *applied = NULL; const char *version; @@ -1561,62 +1939,95 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag assert(argc <= 2); version = argc >= 2 ? argv[1] : NULL; - if (arg_instances_max < 2) + r = context_from_cmdline(&context); + if (r < 0) + return r; + + if (context.component_all) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); + + if (context.instances_max < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --instances-max argument must be >= 2 while updating"); - if (arg_reboot) { + if (context.reboot) { /* If automatic reboot on completion is requested, let's first determine the currently booted image */ - r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version); + r = parse_os_release(context.root, "IMAGE_VERSION", &booted_version); if (r < 0) return log_error_errno(r, "Failed to parse /etc/os-release: %m"); if (!booted_version) return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field."); } - r = process_image(/* ro= */ false, &mounted_dir, &loop_device); - if (r < 0) - return r; - - r = context_make_online(&context, loop_device ? loop_device->node : NULL); - if (r < 0) - return r; + bool installed = false; + int ret = 0; - if (action_flags & UPDATE_ACTION_ACQUIRE) - r = context_acquire(context, version); - else - r = context_process_partial_and_pending(context, version); - if (r < 0) - return r; /* error */ + r = context_load_online( + &context, + /* process_image_flags= */ 0); + if (r < 0) { + if (r != -ENOENT) + return r; - if (action_flags & UPDATE_ACTION_INSTALL && r > 0) /* update needed */ - r = context_install(context, version, &applied); - if (r < 0) - return r; + /* No transfer files found. In that case, still do the installdb cleanup below */ + RET_GATHER(ret, r); + } else { + if (action_flags & UPDATE_ACTION_ACQUIRE) + r = context_acquire(&context, version); + else + r = context_process_partial_and_pending(&context, version); + if (r < 0) + return r; - if (r > 0 && arg_reboot) { - assert(applied); - assert(booted_version); + if (FLAGS_SET(action_flags, UPDATE_ACTION_INSTALL) && r > 0) { /* installation of update indicated */ + r = context_install(&context, version, &applied); + if (r < 0) + return r; - if (strverscmp_improved(applied->version, booted_version) > 0) { - log_notice("Newly installed version is newer than booted version, rebooting."); - return reboot_now(); + installed = r > 0; } - if (strverscmp_improved(applied->version, booted_version) == 0 && - FLAGS_SET(applied->flags, UPDATE_INCOMPLETE)) { - log_notice("Currently booted version was incomplete and has been repaired, rebooting."); - return reboot_now(); + /* context_install() returns > 0 (and emits a notification) only if it actually applied an update. If + * nothing was applied but SYSTEMD_SYSUPDATE_FORCE_NOTIFY=1 is set, still notify subscribers (without a + * resource list), so e.g. a kernel/policy refresh can be triggered unconditionally. */ + if ((action_flags & UPDATE_ACTION_INSTALL) && !installed) { + int f = secure_getenv_bool("SYSTEMD_SYSUPDATE_FORCE_NOTIFY"); + if (f < 0 && f != -ENXIO) + log_debug_errno(f, "Failed to parse $SYSTEMD_SYSUPDATE_FORCE_NOTIFY, ignoring: %m"); + if (f > 0) + (void) context_notify_subscribers(&context, /* us= */ NULL); } + } - log_info("Booted version is newer or identical to newly installed version, not rebooting."); + if (context.cleanup > 0) + RET_GATHER(ret, installdb_cleanup_component(&context)); + + if (installed) { + /* We installed something, yay */ + + if (context.reboot) { + assert(applied); + assert(booted_version); + + if (strverscmp_improved(applied->version, booted_version) > 0) { + log_notice("Newly installed version is newer than booted version, rebooting."); + RET_GATHER(ret, reboot_now()); + } else if (strverscmp_improved(applied->version, booted_version) == 0 && + FLAGS_SET(applied->flags, UPDATE_INCOMPLETE)) { + log_notice("Currently booted version was incomplete and has been repaired, rebooting."); + RET_GATHER(ret, reboot_now()); + } else + log_info("Booted version is newer or identical to newly installed version, not rebooting."); + } } - return 0; + return ret; } -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,44 +2036,90 @@ 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) { - _cleanup_(context_freep) Context* context = NULL; +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_(context_done) Context context = CONTEXT_NULL; + int r; + + assert(argc <= 1); + + r = context_from_cmdline(&context); + if (r < 0) + return r; + + if (context.component_all) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); + + if (context.instances_max < 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "The --instances-max argument must be >= 1 while vacuuming"); + + r = context_load_offline( + &context, + /* process_image_flags= */ 0, + READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS); + 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_done) Context context = CONTEXT_NULL; _cleanup_free_ char *booted_version = NULL; int r; assert(argc == 1); - if (arg_image || arg_root) + r = context_from_cmdline(&context); + if (r < 0) + return r; + + if (context.image || context.root) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --root=/--image= switches may not be combined with the '%s' operation.", argv[0]); - r = context_make_offline(&context, /* node= */ NULL, /* requires_enabled_transfers= */ true); + if (context.component || context.component_all) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "The --component= and --component-all switches may not be combined with the '%s' operation, which only applies to the booted OS version.", argv[0]); + + r = context_load_offline( + &context, + /* process_image_flags= */ 0, + READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS); if (r < 0) return r; log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS)); - r = context_discover_update_sets_by_flag(context, UPDATE_INSTALLED); + r = context_discover_update_sets_by_flag(&context, UPDATE_INSTALLED); if (r < 0) return r; - if (!context->newest_installed) + if (!context.newest_installed) return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Couldn't find any suitable installed versions."); - r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version); - if (r < 0) /* yes, arg_root is NULL here, but we have to pass something, and it's a lot more readable + r = parse_os_release(context.root, "IMAGE_VERSION", &booted_version); + if (r < 0) /* yes, context.root is NULL here, but we have to pass something, and it's a lot more readable * if we see what the first argument is about */ return log_error_errno(r, "Failed to parse /etc/os-release: %m"); if (!booted_version) return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION= field."); - r = strverscmp_improved(context->newest_installed->version, booted_version); + r = strverscmp_improved(context.newest_installed->version, booted_version); if (r > 0) { log_notice("Newest installed version '%s' is newer than booted version '%s'.%s", - context->newest_installed->version, booted_version, + context.newest_installed->version, booted_version, streq(argv[0], "pending") ? " Reboot recommended." : ""); if (streq(argv[0], "reboot")) @@ -1671,10 +2128,10 @@ static int verb_pending_or_reboot(int argc, char **argv, void *userdata) { return EXIT_SUCCESS; } else if (r == 0) log_info("Newest installed version '%s' matches booted version '%s'.", - context->newest_installed->version, booted_version); + context.newest_installed->version, booted_version); else log_warning("Newest installed version '%s' is older than booted version '%s'.", - context->newest_installed->version, booted_version); + context.newest_installed->version, booted_version); if (streq(argv[0], "pending")) /* When called as 'pending' tell the caller via failure exit code that there's nothing newer installed */ return EXIT_FAILURE; @@ -1682,91 +2139,61 @@ static int verb_pending_or_reboot(int argc, char **argv, void *userdata) { return EXIT_SUCCESS; } -static int component_name_valid(const char *c) { - _cleanup_free_ char *j = NULL; - - /* See if the specified string enclosed in the directory prefix+suffix would be a valid file name */ +static int context_list_components(Context *context, char ***ret_component_names, bool *ret_has_default_component) { + int r; - if (isempty(c)) - return false; + assert(context); - if (string_has_cc(c, NULL)) - return false; + _cleanup_strv_free_ char **z = NULL; + r = get_component_list(context->root, &z); + if (r < 0) + return r; - if (!utf8_is_valid(c)) - return false; + if (ret_component_names) + *ret_component_names = TAKE_PTR(z); - j = strjoin("sysupdate.", c, ".d"); - if (!j) - return -ENOMEM; + /* Does the system have at least one transfer file in /etc/sysupdate.d, which can be considered a + * TARGET_HOST? See target_get_argument() in sysupdated.c */ + if (ret_has_default_component) + *ret_has_default_component = (!context->definitions && + !context->component && + !context->root && + !context->image && + context->n_transfers > 0); - return filename_is_valid(j); + return 0; } -static int verb_components(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_set_free_ Set *names = NULL; +VERB_NOARG(verb_components, "components", + "Show list of components"); +static int verb_components(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(context_done) Context context = CONTEXT_NULL; + _cleanup_strv_free_ char **component_names = NULL; bool has_default_component = false; int r; assert(argc <= 1); - r = process_image(/* ro= */ false, &mounted_dir, &loop_device); + r = context_from_cmdline(&context); if (r < 0) return r; - ConfFile **directories = NULL; - size_t n_directories = 0; + if (context.component_all) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); - CLEANUP_ARRAY(directories, n_directories, conf_file_free_many); - - r = conf_files_list_strv_full(".d", arg_root, CONF_FILES_DIRECTORY|CONF_FILES_WARN, - (const char * const *) CONF_PATHS_STRV(""), &directories, &n_directories); + r = context_load_offline( + &context, + /* process_image_flags= */ 0, + /* read_definitions_flags= */ 0); if (r < 0) - return log_error_errno(r, "Failed to enumerate directories: %m"); - - FOREACH_ARRAY(i, directories, n_directories) { - ConfFile *e = *i; - - if (streq(e->filename, "sysupdate.d")) { - has_default_component = true; - continue; - } - - const char *s = startswith(e->filename, "sysupdate."); - if (!s) - continue; - - const char *a = endswith(s, ".d"); - if (!a) - continue; - - _cleanup_free_ char *n = strndup(s, a - s); - if (!n) - return log_oom(); - - r = component_name_valid(n); - if (r < 0) - return log_error_errno(r, "Unable to validate component name '%s': %m", n); - if (r == 0) - continue; - - r = set_ensure_put(&names, &string_hash_ops_free, n); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Failed to add component '%s' to set: %m", n); - TAKE_PTR(n); - } - - /* We use simple free() rather than strv_free() here, since set_free() will free the strings for us */ - _cleanup_free_ char **z = set_get_strv(names); - if (!z) - return log_oom(); + return r; - strv_sort(z); + r = context_list_components(&context, &component_names, &has_default_component); + if (r < 0) + return log_error_errno(r, "Failed to enumerate components: %m"); if (!sd_json_format_enabled(arg_json_format_flags)) { - if (!has_default_component && set_isempty(names)) { + if (!has_default_component && strv_isempty(component_names)) { log_info("No components defined."); return 0; } @@ -1775,13 +2202,13 @@ static int verb_components(int argc, char **argv, void *userdata) { printf("%s%s\n", ansi_highlight(), ansi_normal()); - STRV_FOREACH(i, z) + STRV_FOREACH(i, component_names) puts(*i); } else { _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component), - SD_JSON_BUILD_PAIR_STRV("components", z)); + SD_JSON_BUILD_PAIR_STRV("components", component_names)); if (r < 0) return log_error_errno(r, "Failed to create JSON: %m"); @@ -1793,189 +2220,204 @@ static int verb_components(int argc, char **argv, void *userdata) { return 0; } -static int verb_help(int argc, char **argv, void *userdata) { - _cleanup_free_ char *link = NULL; +VERB_NOARG(verb_cleanup, "cleanup", "Clean up orphaned files"); +static int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(context_done) Context context = CONTEXT_NULL; int r; - r = terminal_urlify_man("systemd-sysupdate", "8", &link); + assert(argc <= 1); + + r = context_from_cmdline(&context); if (r < 0) - return log_oom(); + return r; - 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", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + if (context.cleanup == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invocation of 'cleanup' with --cleanup=no is contradictory, refusing."); - return 0; + r = context_load_offline( + &context, + /* process_image_flags= */ 0, + /* read_definitions_flags= */ 0); + if (r < 0) + return r; + + int ret = 0; + RET_GATHER(ret, installdb_cleanup_component(&context)); + + if (context.component_all) { + _cleanup_strv_free_ char **z = NULL; + r = installdb_list_components(&context, &z); + if (r < 0) + return log_error_errno(r, "Failed to enumerate components: %m"); + + STRV_FOREACH(i, z) { + _cleanup_(context_done) Context component_context = CONTEXT_NULL; + + r = context_from_cmdline(&component_context); + if (r < 0) + return r; + + /* Override the component with our iter. This needs to be done in a fresh Context + * as the installdb_fd and other state are specific to the component. */ + r = free_and_strdup_warn(&component_context.component, *i); + if (r < 0) + return r; + + r = context_load_offline( + &component_context, + /* process_image_flags= */ 0, + /* read_definitions_flags= */ 0); + if (r < 0) + return r; + + RET_GATHER(ret, installdb_cleanup_component(&component_context)); + } + } + + return ret; } -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 int help(void) { + _cleanup_(table_unrefp) Table *common_options = NULL, *options = NULL, *verbs = NULL; + int r; - 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 }, - {} - }; + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; - int c, 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); + + help_cmdline("[OPTIONS…] [VERSION]"); + help_abstract("Update OS images."); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + r = table_print_or_warn(common_options); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-sysupdate", "8"); + return 0; +} + +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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.arg)) { + arg_component = mfree(arg_component); + arg_component_all = false; + break; + } + + if (!component_name_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", opts.arg); - case 'm': - r = safe_atou64(optarg, &arg_instances_max); + r = free_and_strdup_warn(&arg_component, opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", optarg); + return r; + arg_component_all = false; break; - case ARG_SYNC: - r = parse_boolean_argument("--sync=", optarg, &arg_sync); - if (r < 0) - return r; + OPTION('A', "component-all", NULL, "Process all components"): + + arg_component = mfree(arg_component); + arg_component_all = true; 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(opts.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(opts.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(opts.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(opts.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(opts.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(opts.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", opts.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=", opts.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=", opts.arg, &b); if (r < 0) return r; @@ -1983,24 +2425,42 @@ 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); + OPTION_LONG("cleanup", "BOOL", "Clean up orphaned files after completing update"): { + bool b; + + r = parse_boolean_argument("--cleanup=", opts.arg, &b); if (r < 0) return r; + arg_cleanup = b; + break; + } + + 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(opts.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."); @@ -2008,29 +2468,48 @@ static int parse_argv(int argc, char *argv[]) { if ((arg_image || arg_root) && arg_reboot) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --reboot switch may not be combined with --root= or --image=."); + if (arg_reboot && arg_component) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --reboot switch may not be combined with --component=, as automatic reboots only apply to the booted OS version."); + if (arg_definitions && arg_component) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined."); + 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) + arg_varlink = true; + + *remaining_args = option_parser_get_args(&opts); 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 }, - {} - }; +static int vl_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + _cleanup_(server_done) Server server = SERVER_NULL; + int r; + + r = varlink_server_new(&varlink_server, + SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA, + &server); + 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_SysUpdate); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); - return dispatch_verb(argc, argv, verbs, NULL); + r = sd_varlink_server_bind_method_many( + varlink_server, + "io.systemd.SysUpdate.CheckNew", vl_method_check_new); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink method: %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 run(int argc, char *argv[]) { @@ -2038,11 +2517,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; - return sysupdate_main(argc, argv); + if (arg_varlink) + return vl_server(); /* Invocation as Varlink service */ + + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/sysupdate/sysupdate.h b/src/sysupdate/sysupdate.h index 092aeb877100a..02a9f7c8f348c 100644 --- a/src/sysupdate/sysupdate.h +++ b/src/sysupdate/sysupdate.h @@ -3,10 +3,48 @@ #include "specifier.h" #include "sysupdate-forward.h" +#include "sysupdate-target.h" -extern bool arg_sync; -extern uint64_t arg_instances_max; -extern char *arg_root; -extern char *arg_transfer_source; +typedef struct Context { + /* Parameters/Command line arguments: */ + char *definitions; + bool sync; + uint64_t instances_max; + char *root; + char *image; + bool reboot; + int cleanup; + char *component; + bool component_all; + int verify; + ImagePolicy *image_policy; + bool offline; + char *transfer_source; + + /* Loaded state: */ + LoopDevice *loop_device; + char *mounted_dir; + + Transfer **transfers; + size_t n_transfers; + + Transfer **disabled_transfers; + size_t n_disabled_transfers; + + Hashmap *features; /* Defined features, keyed by ID */ + + UpdateSet **update_sets; + size_t n_update_sets; + + UpdateSet *newest_installed, *candidate; + + Hashmap *web_cache; /* Cache for downloaded resources, keyed by URL */ + + int installdb_fd; + + TargetIdentifier target_identifier; +} Context; + +void context_done(Context *c); extern const Specifier specifier_table[]; diff --git a/src/sysupdate/sysupdated.c b/src/sysupdate/sysupdated.c index c123842c1084f..8022fde77eb6f 100644 --- a/src/sysupdate/sysupdated.c +++ b/src/sysupdate/sysupdated.c @@ -24,7 +24,6 @@ #include "escape.h" #include "event-util.h" #include "fd-util.h" -#include "fileio.h" #include "format-util.h" #include "hashmap.h" #include "log.h" @@ -41,6 +40,7 @@ #include "signal-util.h" #include "string-table.h" #include "strv.h" +#include "sysupdate-target.h" #include "sysupdate-util.h" #include "utf8.h" @@ -65,25 +65,6 @@ typedef struct Manager { /* Forward declare so that jobs can call it on exit */ static void manager_check_idle(Manager *m); -typedef enum TargetClass { - /* These should try to match ImageClass from src/basic/os-util.h */ - TARGET_MACHINE = IMAGE_MACHINE, - TARGET_PORTABLE = IMAGE_PORTABLE, - TARGET_SYSEXT = IMAGE_SYSEXT, - TARGET_CONFEXT = IMAGE_CONFEXT, - _TARGET_CLASS_IS_IMAGE_CLASS_MAX, - - /* sysupdate-specific classes */ - TARGET_HOST = _TARGET_CLASS_IS_IMAGE_CLASS_MAX, - TARGET_COMPONENT, - - _TARGET_CLASS_MAX, - _TARGET_CLASS_INVALID = -EINVAL, -} TargetClass; - -/* Let's ensure when the number of classes is updated things are updated here too */ -assert_cc((int) _IMAGE_CLASS_MAX == (int) _TARGET_CLASS_IS_IMAGE_CLASS_MAX); - typedef struct Target { Manager *manager; @@ -139,17 +120,6 @@ struct Job { JobReady detach_cb; /* Callback called when job has started. Detaches the job to run in the background */ }; -static const char* const target_class_table[_TARGET_CLASS_MAX] = { - [TARGET_MACHINE] = "machine", - [TARGET_PORTABLE] = "portable", - [TARGET_SYSEXT] = "sysext", - [TARGET_CONFEXT] = "confext", - [TARGET_COMPONENT] = "component", - [TARGET_HOST] = "host", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(target_class, TargetClass); - static const char* const job_type_table[_JOB_TYPE_MAX] = { [JOB_LIST] = "list", [JOB_DESCRIBE] = "describe", @@ -184,8 +154,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; @@ -245,8 +216,13 @@ static int job_parse_child_output(int _fd, sd_json_variant **ret) { return 0; } - r = sd_json_parse_file_at(/* f= */ NULL, fd, /* path= */ NULL, /* flags= */ 0, - &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); + r = sd_json_parse_fd( + /* path= */ "stdout", + TAKE_FD(fd), + SD_JSON_PARSE_DONATE_FD|SD_JSON_PARSE_SEEK0, + &v, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL); if (r < 0) return log_debug_errno(r, "Failed to parse child output as JSON: %m"); @@ -590,19 +566,19 @@ static int job_method_cancel(sd_bus_message *msg, void *userdata, sd_bus_error * case JOB_LIST: case JOB_DESCRIBE: case JOB_CHECK_NEW: - action = "org.freedesktop.sysupdate1.check"; + action = "org.freedesktop.sysupdate1.cancel-check"; break; case JOB_ACQUIRE: case JOB_INSTALL: if (j->version) - action = "org.freedesktop.sysupdate1.update-to-version"; + action = "org.freedesktop.sysupdate1.cancel-update-to-version"; else - action = "org.freedesktop.sysupdate1.update"; + action = "org.freedesktop.sysupdate1.cancel-update"; break; case JOB_VACUUM: - action = "org.freedesktop.sysupdate1.vacuum"; + action = "org.freedesktop.sysupdate1.cancel-vacuum"; break; case JOB_DESCRIBE_FEATURE: @@ -728,8 +704,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; @@ -769,7 +746,6 @@ static int target_new(Manager *m, TargetClass class, const char *name, const cha static int sysupdate_run_simple(sd_json_variant **ret, Target *t, ...) { _cleanup_close_pair_ int pipe[2] = EBADF_PAIR; _cleanup_(pidref_done_sigkill_wait) PidRef pid = PIDREF_NULL; - _cleanup_fclose_ FILE *f = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; _cleanup_free_ char *target_arg = NULL; int r; @@ -843,11 +819,14 @@ static int sysupdate_run_simple(sd_json_variant **ret, Target *t, ...) { } pipe[1] = safe_close(pipe[1]); - f = take_fdopen(&pipe[0], "r"); - if (!f) - return -errno; - r = sd_json_parse_file(f, "stdout", 0, &v, NULL, NULL); + r = sd_json_parse_fd( + "stdout", + TAKE_FD(pipe[0]), + SD_JSON_PARSE_DONATE_FD, + &v, + /* reterr_line= */ NULL, + /* reterr_column= */ NULL); if (r < 0) return log_debug_errno(r, "Failed to parse JSON: %m"); @@ -975,8 +954,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 +1103,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 +1191,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 +1311,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 +1404,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 +1430,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 +1452,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 +1859,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/test-sysupdate-util.c b/src/sysupdate/test-sysupdate-util.c new file mode 100644 index 0000000000000..b9a5123e058ea --- /dev/null +++ b/src/sysupdate/test-sysupdate-util.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sysupdate-util.h" +#include "tests.h" + +TEST(component_name_valid) { + /* Valid component names: anything that turns "sysupdate..d" into a valid filename. */ + ASSERT_TRUE(component_name_valid("foo")); + ASSERT_TRUE(component_name_valid("foo-bar")); + ASSERT_TRUE(component_name_valid("foo.bar")); + ASSERT_TRUE(component_name_valid("foo_bar_baz")); + ASSERT_TRUE(component_name_valid("0815")); + ASSERT_TRUE(component_name_valid("über")); /* valid UTF-8 is fine */ + + /* Invalid: empty, slashes, control characters, invalid UTF-8. */ + ASSERT_FALSE(component_name_valid("")); + ASSERT_FALSE(component_name_valid("foo/bar")); + ASSERT_FALSE(component_name_valid("/foo")); + ASSERT_FALSE(component_name_valid("foo/")); + ASSERT_FALSE(component_name_valid("foo\tbar")); + ASSERT_FALSE(component_name_valid("foo\nbar")); + ASSERT_FALSE(component_name_valid("foo\x7f")); + ASSERT_FALSE(component_name_valid("\xff")); /* not valid UTF-8 */ +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/sysupdate/updatectl.c b/src/sysupdate/updatectl.c index 60523181bcdb1..cd41aeb9007f9 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" @@ -35,6 +35,7 @@ static bool arg_legend = true; static bool arg_reboot = false; static bool arg_offline = false; static bool arg_now = false; +static bool arg_ask_password = true; static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; static const char *arg_host = NULL; @@ -75,6 +76,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 +93,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 +645,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 +758,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; @@ -860,6 +868,10 @@ static int update_render_progress(sd_event_source *source, void *userdata) { clear_progress_bar_unbuffered(target); fprintf(stderr, "%s: %s Already up-to-date\n", target, GREEN_CHECK_MARK()); n--; /* Don't consider this target in the total */ + } else if (progress == -EUCLEAN) { + clear_progress_bar_unbuffered(target); + fprintf(stderr, "%s: %s Update is already acquired and partially installed. Vacuum it to try installing again.\n", target, RED_CROSS_MARK()); + total += 100; } else if (progress < 0) { clear_progress_bar_unbuffered(target); fprintf(stderr, "%s: %s %s\n", target, RED_CROSS_MARK(), STRERROR(progress)); @@ -1068,7 +1080,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 +1258,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 +1300,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 +1355,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 +1501,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 +1552,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 +1637,105 @@ 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 opts = { argc, argv }; - case 'h': - return help(); + FOREACH_OPTION_OR_RETURN(c, &opts) + 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 = opts.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; + OPTION_GROUP("Verbs"): {} + + OPTION_COMMON_NO_ASK_PASSWORD: + arg_ask_password = false; break; - case '?': - return -EINVAL; + OPTION_COMMON_HELP: + return help(); - default: - assert_not_reached(); + OPTION_COMMON_VERSION: + return version(); } - } + *ret_args = option_parser_get_args(&opts); return 1; } @@ -1722,23 +1743,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; @@ -1746,12 +1757,11 @@ static int run(int argc, char *argv[]) { if (r < 0) return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM); - if (arg_transport == BUS_TRANSPORT_LOCAL) - polkit_agent_open(); + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - (void) sd_bus_set_allow_interactive_authorization(bus, true); + (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - return dispatch_verb(argc, argv, verbs, bus); + return dispatch_verb(args, bus); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index a113928f97789..48d256b15c23b 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" @@ -39,7 +40,7 @@ #include "strv.h" #include "sync-util.h" #include "time-util.h" -#include "tmpfile-util-label.h" +#include "tmpfile-util.h" #include "uid-classification.h" #include "uid-range.h" #include "user-util.h" @@ -351,7 +352,7 @@ static int make_backup(const char *target, const char *x) { return r; if (rename(dst_tmp, backup) < 0) - return errno; + return -errno; dst_tmp = mfree(dst_tmp); /* disable the unlink_and_freep() hook now that the file has been renamed */ return 0; @@ -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; @@ -814,7 +821,7 @@ static int write_temporary_group( r = putgrent_with_members(c, &n, group); if (r < 0) return log_error_errno(r, "Failed to add new group \"%s\" to temporary group file: %m", - gr->gr_name); + n.gr_name); group_changed = true; } @@ -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,125 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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(opts.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(opts.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(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must be an absolute path."); - if (!endswith(optarg, ".conf")) + if (!endswith(opts.arg, ".conf")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must have the extension '.conf'."); - arg_replace = optarg; + arg_replace = opts.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(&opts); + size_t n_args = option_parser_get_n_args(&opts); + 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 +2183,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 +2269,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 +2320,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/libsystemd/sd-journal/fsprg.c b/src/test/fsprg.c similarity index 100% rename from src/libsystemd/sd-journal/fsprg.c rename to src/test/fsprg.c diff --git a/src/libsystemd/sd-journal/fsprg.h b/src/test/fsprg.h similarity index 100% rename from src/libsystemd/sd-journal/fsprg.h rename to src/test/fsprg.h diff --git a/src/basic/gcrypt-util.c b/src/test/gcrypt-util.c similarity index 87% rename from src/basic/gcrypt-util.c rename to src/test/gcrypt-util.c index ebbc3e33bc736..6197ddb9c27e4 100644 --- a/src/basic/gcrypt-util.c +++ b/src/test/gcrypt-util.c @@ -2,12 +2,13 @@ #include +#include "sd-dlopen.h" + #include "gcrypt-util.h" +#include "log.h" /* IWYU pragma: keep */ #if HAVE_GCRYPT -static void *gcrypt_dl = NULL; - static DLSYM_PROTOTYPE(gcry_control) = NULL; static DLSYM_PROTOTYPE(gcry_check_version) = NULL; DLSYM_PROTOTYPE(gcry_md_close) = NULL; @@ -39,19 +40,17 @@ DLSYM_PROTOTYPE(gcry_mpi_subm) = NULL; DLSYM_PROTOTYPE(gcry_mpi_sub_ui) = NULL; DLSYM_PROTOTYPE(gcry_prime_check) = NULL; 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", - "Support for journald forward-sealing", - ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, - "libgcrypt.so.20"); + static void *gcrypt_dl = NULL; + + GCRYPT_NOTE(SD_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED); 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), @@ -82,10 +81,10 @@ int dlopen_gcrypt(void) { DLSYM_ARG(gcry_mpi_subm), DLSYM_ARG(gcry_mpi_sub_ui), DLSYM_ARG(gcry_prime_check), - DLSYM_ARG(gcry_randomize), - DLSYM_ARG(gcry_strerror)); + DLSYM_ARG(gcry_randomize)); #else - return -EOPNOTSUPP; + return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), + "gcrypt support is not compiled in."); #endif } @@ -93,7 +92,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/test/gcrypt-util.h similarity index 71% rename from src/basic/gcrypt-util.h rename to src/test/gcrypt-util.h index 147a174da0026..03404886974ce 100644 --- a/src/basic/gcrypt-util.h +++ b/src/test/gcrypt-util.h @@ -2,13 +2,27 @@ #pragma once +#include "sd-dlopen.h" + #include "basic-forward.h" -int dlopen_gcrypt(void); +int dlopen_gcrypt(int log_level); int initialize_libgcrypt(bool secmem); #if HAVE_GCRYPT +#define GCRYPT_NOTE(priority) \ + SD_ELF_NOTE_DLOPEN("gcrypt", \ + "Support for journald forward-sealing", \ + priority, \ + "libgcrypt.so.20") + +#define DLOPEN_GCRYPT(log_level, priority) \ + ({ \ + GCRYPT_NOTE(priority); \ + dlopen_gcrypt(log_level); \ + }) + #include /* IWYU pragma: export */ #include "dlfcn-util.h" @@ -42,7 +56,6 @@ extern DLSYM_PROTOTYPE(gcry_mpi_subm); extern DLSYM_PROTOTYPE(gcry_mpi_sub_ui); extern DLSYM_PROTOTYPE(gcry_prime_check); extern DLSYM_PROTOTYPE(gcry_randomize); -extern DLSYM_PROTOTYPE(gcry_strerror); /* Copied from gcry_md_putc from gcrypt.h due to the need to call the sym_ variant */ #define sym_gcry_md_putc(h,c) \ @@ -52,12 +65,8 @@ extern DLSYM_PROTOTYPE(gcry_strerror); sym_gcry_md_write((h__), NULL, 0); \ (h__)->buf[(h__)->bufpos++] = (c) & 0xff; \ } while(false) -#else -typedef struct gcry_md_handle *gcry_md_hd_t; - -static inline void sym_gcry_md_close(gcry_md_hd_t h) { - assert(h == NULL); -} -#endif DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(gcry_md_hd_t, sym_gcry_md_close, NULL); +#else +#define DLOPEN_GCRYPT(log_level, priority) dlopen_gcrypt(log_level) +#endif diff --git a/src/test/generate-sym-test.py b/src/test/generate-sym-test.py index b8d64d623f04f..065ad14447a5e 100755 --- a/src/test/generate-sym-test.py +++ b/src/test/generate-sym-test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later # -# ruff: noqa: E501 UP015 +# ruff: noqa: UP015 import os import re @@ -120,43 +120,43 @@ def process_source_file(file: IO[str]) -> None: continue -print("""/* SPDX-License-Identifier: LGPL-2.1-or-later */ +print('''/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include -""") +''') for header in sys.argv[3:]: with open(header, 'r') as f: if process_header_file(f): print('#include "{}"'.format(header.split('/')[-1])) -print(""" +print(''' /* We want to check deprecated symbols too, without complaining */ #pragma GCC diagnostic ignored "-Wdeprecated-declarations" -""") +''') -print(""" +print(''' struct symbol { const char *name; const void *symbol; }; -static struct symbol symbols_from_sym[] = {""") +static struct symbol symbols_from_sym[] = {''') with open(sys.argv[1], 'r') as f: process_sym_file(f) -print(""" {} -}, symbols_from_header[] = {""") +print(''' {} +}, symbols_from_header[] = {''') for header in sys.argv[3:]: with open(header, 'r') as f: print(process_header_file(f), end='') -print(""" {} -}, symbols_from_source[] = {""") +print(''' {} +}, symbols_from_source[] = {''') for dirpath, _, filenames in sorted(os.walk(sys.argv[2])): for filename in sorted(filenames): @@ -168,7 +168,7 @@ def process_source_file(file: IO[str]) -> None: with p.open('rt') as f: process_source_file(f) -print(""" {} +print(''' {} }; static int sort_callback(const void *a, const void *b) { @@ -239,4 +239,4 @@ def process_source_file(file: IO[str]) -> None: } return n_error == 0 ? EXIT_SUCCESS : EXIT_FAILURE; -}""") +}''') diff --git a/src/test/meson.build b/src/test/meson.build index adbcd3c0d4dcf..9d5a79c78e58e 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -58,6 +58,7 @@ simple_tests += files( 'test-alloc-util.c', 'test-architecture.c', 'test-argv-util.c', + 'test-arphrd-util.c', 'test-audit-util.c', 'test-barrier.c', 'test-binfmt-util.c', @@ -111,25 +112,27 @@ simple_tests += files( 'test-format-util.c', 'test-fs-util.c', 'test-fstab-util.c', - 'test-getopt.c', 'test-glob-util.c', 'test-gpt.c', + 'test-group-record-nss.c', 'test-gunicode.c', 'test-hash-funcs.c', 'test-hexdecoct.c', 'test-hmac.c', 'test-hostname-setup.c', 'test-hostname-util.c', - 'test-id128.c', 'test-image-filter.c', 'test-image-policy.c', 'test-import-util.c', 'test-in-addr-prefix-util.c', 'test-in-addr-util.c', + 'test-initrd-cpio.c', 'test-install-file.c', 'test-install-root.c', 'test-io-util.c', 'test-iovec-util.c', + 'test-iovec-wrapper.c', + 'test-ip-protocol-list.c', 'test-journal-importer.c', 'test-kbd-util.c', 'test-label.c', @@ -141,6 +144,7 @@ simple_tests += files( 'test-log.c', 'test-logarithm.c', 'test-login-util.c', + 'test-machine-util.c', 'test-macro.c', 'test-memfd-util.c', 'test-memory-util.c', @@ -156,33 +160,41 @@ 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', 'test-parse-argument.c', 'test-parse-helpers.c', + 'test-parse-util.c', 'test-path-lookup.c', 'test-path-util.c', + 'test-pe-binary.c', 'test-percent-util.c', 'test-pidref.c', + 'test-pressure.c', 'test-pretty-print.c', 'test-printf.c', 'test-prioq.c', 'test-proc-cmdline.c', + 'test-process-util.c', 'test-procfs-util.c', + 'test-progress-bar.c', 'test-psi-util.c', + 'test-qmp-client.c', + 'test-qrcode-util.c', 'test-ratelimit.c', 'test-raw-clone.c', 'test-recovery-key.c', 'test-recurse-dir.c', 'test-replace-var.c', + 'test-reread-partition-table.c', 'test-rlimit-util.c', 'test-rm-rf.c', - 'test-sd-hwdb.c', - 'test-sd-path.c', 'test-secure-bits.c', 'test-serialize.c', 'test-set.c', + 'test-set-disable-mempool.c', 'test-sha1.c', 'test-sha256.c', 'test-sigbus.c', @@ -211,6 +223,7 @@ simple_tests += files( 'test-unaligned.c', 'test-unit-file.c', 'test-user-record.c', + 'test-user-record-nss.c', 'test-user-util.c', 'test-utf8.c', 'test-verbs.c', @@ -224,10 +237,8 @@ simple_tests += files( common_test_dependencies = [ libmount_cflags, - librt, libseccomp_cflags, libselinux_cflags, - threads, ] executables += [ @@ -239,9 +250,6 @@ executables += [ 'sources' : files('test-af-list.c') + generated_gperf_headers, }, - test_template + { - 'sources' : files('test-arphrd-util.c') - }, test_template + { 'sources' : files('test-ask-password-api.c'), 'type' : 'manual', @@ -256,11 +264,7 @@ executables += [ }, test_template + { 'sources' : files('test-btrfs.c'), - 'type' : 'manual', - }, - test_template + { - 'sources' : files('test-btrfs-physical-offset.c'), - 'type' : 'manual', + 'timeout' : 120, }, test_template + { 'sources' : files('test-chase-manual.c'), @@ -287,8 +291,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 + { @@ -296,24 +300,30 @@ executables += [ 'type' : 'manual', }, test_template + { - 'sources' : files('test-dlopen-so.c'), + 'sources' : files( + 'test-dlopen-so.c', + 'gcrypt-util.c', + ), 'dependencies' : [ libblkid_cflags, + libfdisk_cflags, + libgcrypt_cflags, + libgnutls_cflags, libkmod_cflags, + libmicrohttpd_cflags, libmount_cflags, libp11kit_cflags, libseccomp_cflags, ], }, test_template + { - # only static linking apart from libdl, to make sure that the - # module is linked to all libraries that it uses. + # only static linking, to make sure that the module is linked + # to all libraries that it uses. 'sources' : files('test-dlopen.c'), 'link_with' : [ libc_wrapper_static, libbasic_static, ], - 'dependencies' : libdl, 'install' : false, 'type' : 'manual', }, @@ -325,6 +335,23 @@ executables += [ 'sources' : files('test-fd-util.c'), 'dependencies' : libseccomp_cflags, }, + test_template + { + 'sources' : files('test-fdstore.c'), + 'link_with' : libsystemd, + 'type' : 'manual', + }, + test_template + { + 'sources' : files( + 'test-fsprg.c', + 'fsprg.c', + 'gcrypt-util.c', + ), + 'dependencies' : [ + libgcrypt_cflags, + libopenssl_cflags, + ], + 'conditions' : ['HAVE_OPENSSL'], + }, test_template + { 'sources' : files( 'test-hashmap.c', @@ -334,16 +361,17 @@ executables += [ ], 'timeout' : 180, }, - test_template + { - 'sources' : files('test-ip-protocol-list.c'), - }, test_template + { 'sources' : files('test-ipcrm.c'), 'type' : 'unsafe', }, test_template + { - 'sources' : files('test-json.c'), - 'dependencies' : libm, + 'sources' : files('test-kexec.c'), + 'link_with' : [libshared], + }, + test_template + { + 'sources' : files('test-curl-util.c'), + 'conditions' : ['HAVE_LIBCURL'], }, test_template + { 'sources' : files('test-libcrypt-util.c'), @@ -353,22 +381,19 @@ executables += [ test_template + { 'sources' : files('test-libmount.c'), 'conditions' : ['HAVE_LIBMOUNT'], - 'dependencies' : [ - libmount_cflags, - threads, - ], + 'dependencies' : libmount_cflags, }, test_template + { 'sources' : files('test-loopback.c'), 'dependencies' : common_test_dependencies, }, test_template + { - 'sources' : files('test-math-util.c'), - 'dependencies' : libm, + 'sources' : files('test-luo.c'), + 'type' : 'manual', }, test_template + { - 'sources' : files('test-mempress.c'), - 'dependencies' : threads, + 'sources' : files('test-math-util.c'), + 'dependencies' : libm, }, test_template + { 'sources' : files('test-mount-util.c'), @@ -388,56 +413,33 @@ executables += [ test_template + { 'sources' : files('test-nss-hosts.c'), 'extract' : files('nss-test-util.c'), - 'dependencies' : [ - libdl, - libseccomp_cflags, - ], + 'dependencies' : libseccomp_cflags, 'conditions' : ['ENABLE_NSS'], 'timeout' : 120, }, test_template + { 'sources' : files('test-nss-users.c'), 'objects' : ['test-nss-hosts'], - '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 + { - 'sources' : files('test-parse-util.c'), - 'dependencies' : libm, - }, test_template + { 'sources' : files('test-shift-uid.c'), 'type' : 'manual', }, test_template + { - 'sources' : files('test-process-util.c'), - 'dependencies' : threads, - }, - test_template + { - 'sources' : files('test-progress-bar.c'), - }, - test_template + { - 'sources' : files('test-qrcode-util.c'), - 'dependencies' : libdl, + 'sources' : files('test-qmp-client-qemu.c'), + 'objects' : ['systemd-vmspawn'], + 'conditions' : ['ENABLE_VMSPAWN'], }, test_template + { 'sources' : files('test-random-util.c'), - 'dependencies' : libm, 'timeout' : 120, }, - test_template + { - 'sources' : files('test-reread-partition-table.c'), - }, test_template + { 'sources' : files('test-reread-partition-table-manual.c'), 'type' : 'manual', @@ -456,10 +458,6 @@ executables += [ 'sources' : files('test-selinux.c'), 'dependencies' : libselinux_cflags, }, - test_template + { - 'sources' : files('test-set-disable-mempool.c'), - 'dependencies' : threads, - }, test_template + { 'sources' : files('test-sizeof.c'), 'link_with' : [ @@ -477,7 +475,7 @@ executables += [ }, test_template + { 'sources' : files('test-tpm2.c'), - 'dependencies' : libopenssl, + 'dependencies' : libopenssl_cflags, 'timeout' : 120, }, test_template + { @@ -485,12 +483,14 @@ executables += [ 'conditions' : ['ENABLE_UTMP'], }, test_template + { - 'sources' : files('test-varlink.c'), - 'dependencies' : threads, + 'sources' : files('test-varlink-idl-machine.c'), + 'objects' : ['systemd-machined'], + 'conditions' : ['ENABLE_MACHINED'], }, test_template + { - 'sources' : files('test-varlink-idl.c'), - 'dependencies' : threads, + 'sources' : files('test-varlink-idl-login.c'), + 'objects' : ['systemd-logind'], + 'conditions' : ['ENABLE_LOGIND'], }, test_template + { 'sources' : files('test-watchdog.c'), @@ -529,9 +529,15 @@ executables += [ 'sources' : files('test-bpf-restrict-fs.c'), 'dependencies' : common_test_dependencies, }, + core_test_template + { + 'sources' : files('test-bpf-restrict-fsaccess.c'), + 'dependencies' : common_test_dependencies, + 'bpf_programs' : ['restrict-fsaccess'], + 'type' : 'manual', + }, core_test_template + { 'sources' : files('test-bpf-token.c'), - 'dependencies' : common_test_dependencies + libbpf, + 'dependencies' : common_test_dependencies + libbpf_cflags, 'conditions' : ['BPF_FRAMEWORK'], 'type' : 'manual', }, @@ -564,6 +570,7 @@ executables += [ core_test_template + { 'sources' : files('test-execute.c'), 'dependencies' : common_test_dependencies, + 'type' : meson.is_cross_build() ? 'manual' : '', 'timeout' : 360, }, core_test_template + { @@ -579,8 +586,7 @@ executables += [ 'dependencies' : common_test_dependencies, }, core_test_template + { - 'sources' : files('test-loop-block.c'), - 'dependencies' : [threads], + 'sources' : files('test-loop-util.c'), 'parallel' : false, }, core_test_template + { @@ -588,10 +594,7 @@ executables += [ }, core_test_template + { 'sources' : files('test-namespace.c'), - 'dependencies' : [ - threads, - libmount_cflags, - ], + 'dependencies' : libmount_cflags, }, core_test_template + { 'sources' : files('test-ns.c'), @@ -609,7 +612,6 @@ executables += [ }, core_test_template + { 'sources' : files('test-socket-bind.c'), - 'dependencies' : libdl, 'conditions' : ['BPF_FRAMEWORK'], }, core_test_template + { @@ -623,6 +625,15 @@ executables += [ 'sources' : files('test-unit-serialize.c'), 'dependencies' : common_test_dependencies, }, + core_test_template + { + 'sources' : files('test-varlink-idl-job.c'), + }, + core_test_template + { + 'sources' : files('test-varlink-idl-manager.c'), + }, + core_test_template + { + 'sources' : files('test-varlink-idl-unit.c'), + }, core_test_template + { 'sources' : files('test-watch-pid.c'), 'dependencies' : common_test_dependencies, @@ -643,7 +654,6 @@ executables += [ libbasic_static, libsystemd, ], - 'dependencies' : threads, }, test_template + { 'sources' : files('../libudev/test-udev-device-thread.c'), @@ -652,7 +662,6 @@ executables += [ libbasic_static, libudev, ], - 'dependencies' : threads, }, test_template + { 'sources' : files('../libudev/test-libudev.c'), diff --git a/src/test/test-acl-util.c b/src/test/test-acl-util.c index 5967c4b406160..6bf63d1e4faba 100644 --- a/src/test/test-acl-util.c +++ b/src/test/test-acl-util.c @@ -41,8 +41,7 @@ TEST_RET(add_acls_for_user) { ASSERT_OK_ZERO_ERRNO(system(cmd)); if (getuid() == 0 && !userns_has_single_user()) { - const char *nobody = NOBODY_USER_NAME; - r = get_user_creds(&nobody, &uid, NULL, NULL, NULL, 0); + r = get_user_creds(NOBODY_USER_NAME, /* flags= */ 0, NULL, &uid, NULL, NULL, NULL); if (r < 0) uid = 0; } else diff --git a/src/test/test-bpf-firewall.c b/src/test/test-bpf-firewall.c index c3d8e7d5d54b8..e591d0d5ea60e 100644 --- a/src/test/test-bpf-firewall.c +++ b/src/test/test-bpf-firewall.c @@ -75,7 +75,7 @@ int main(int argc, char *argv[]) { /* The simple tests succeeded. Now let's try full unit-based use-case. */ ASSERT_OK(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m)); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service))); ASSERT_EQ(unit_add_name(u, "foo.service"), 0); diff --git a/src/test/test-bpf-foreign-programs.c b/src/test/test-bpf-foreign-programs.c index bf56451d2501c..9d9093bee7647 100644 --- a/src/test/test-bpf-foreign-programs.c +++ b/src/test/test-bpf-foreign-programs.c @@ -303,7 +303,7 @@ int main(int argc, char *argv[]) { assert_se(runtime_dir = setup_fake_runtime_dir()); ASSERT_OK(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m)); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_OK(test_bpf_cgroup_programs(m, "single_prog.service", single_prog, ELEMENTSOF(single_prog))); diff --git a/src/test/test-bpf-restrict-fs.c b/src/test/test-bpf-restrict-fs.c index 9d54ad033760e..8e41537f21ff7 100644 --- a/src/test/test-bpf-restrict-fs.c +++ b/src/test/test-bpf-restrict-fs.c @@ -87,7 +87,7 @@ int main(int argc, char *argv[]) { ASSERT_NOT_NULL((runtime_dir = setup_fake_runtime_dir())); ASSERT_OK(manager_new(RUNTIME_SCOPE_SYSTEM, MANAGER_TEST_RUN_BASIC, &m)); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); /* We need to enable access to the filesystem where the binary is so we * add @common-block and @application */ diff --git a/src/test/test-bpf-restrict-fsaccess.c b/src/test/test-bpf-restrict-fsaccess.c new file mode 100644 index 0000000000000..776d75eaec8c0 --- /dev/null +++ b/src/test/test-bpf-restrict-fsaccess.c @@ -0,0 +1,223 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* + * Test helper for RestrictFileSystemAccess= BPF enforcement tests. + * + * Usage: + * test-bpf-restrict-fsaccess attach — Load, attach, print IDs, then block. + * Kill the process to detach (synchronous + * via bpf_link_put_direct on last FD close). + * test-bpf-restrict-fsaccess check — Check BPF LSM + require_signatures preconditions + * test-bpf-restrict-fsaccess mmap-exec PATH — Attempt PROT_READ|PROT_EXEC mmap of PATH + * test-bpf-restrict-fsaccess anon-mmap-exec — Attempt anonymous PROT_READ|PROT_EXEC mmap + * test-bpf-restrict-fsaccess mprotect-exec PATH — mmap PATH PROT_READ, then mprotect to PROT_EXEC + * + * When "attach" is used, the BPF LSM program is loaded with initramfs_s_dev + * set to the current rootfs s_dev, so the calling test script (running from + * the rootfs) continues to work. The process holds all link FDs and blocks; + * when killed, close() drops the last reference synchronously. + */ + +#include +#include +#include +#include +#include + +#include "bpf-restrict-fsaccess.h" +#include "devnum-util.h" +#include "fd-util.h" +#include "log.h" +#include "string-util.h" +#include "tests.h" + +/* ---- mmap/mprotect probe commands (no BPF dependency) ---- + * + * These exercise the mmap_file, file_mprotect, and anonymous-mmap LSM hooks. + * The test script copies a file to tmpfs and passes its path here. + * Returns 0 if the operation was allowed, negative errno if denied. */ + +static int do_mmap_exec(const char *path) { + _cleanup_close_ int fd = -EBADF; + void *addr; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + + addr = mmap(NULL, 4096, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) + return log_info_errno(errno, "PROT_EXEC mmap of %s denied: %m", path); + + (void) munmap(addr, 4096); + log_info("PROT_EXEC mmap of %s succeeded", path); + return 0; +} + +static int do_anon_mmap_exec(void) { + void *addr; + + addr = mmap(NULL, 4096, PROT_READ | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (addr == MAP_FAILED) + return log_info_errno(errno, "Anonymous PROT_EXEC mmap denied: %m"); + + (void) munmap(addr, 4096); + log_info("Anonymous PROT_EXEC mmap succeeded"); + return 0; +} + +static int do_mprotect_exec(const char *path) { + _cleanup_close_ int fd = -EBADF; + void *addr; + int r; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + + addr = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) + return log_error_errno(errno, "PROT_READ mmap of %s failed: %m", path); + + r = mprotect(addr, 4096, PROT_READ | PROT_EXEC); + if (r < 0) + r = -errno; + + (void) munmap(addr, 4096); + + if (r < 0) + return log_info_errno(r, "mprotect PROT_EXEC on %s denied: %m", path); + + log_info("mprotect PROT_EXEC on %s succeeded", path); + return 0; +} + +#if BPF_FRAMEWORK && HAVE_LSM_INTEGRITY_TYPE +#include "bpf-util.h" +#include "restrict-fsaccess-skel.h" + +static struct restrict_fsaccess_bpf *restrict_fsaccess_bpf_free(struct restrict_fsaccess_bpf *obj) { + restrict_fsaccess_bpf__destroy(obj); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct restrict_fsaccess_bpf *, restrict_fsaccess_bpf_free); + +static int do_attach(void) { + _cleanup_(restrict_fsaccess_bpf_freep) struct restrict_fsaccess_bpf *obj = NULL; + struct stat st; + int r; + + r = DLOPEN_BPF(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + if (r < 0) + return log_error_errno(r, "Failed to dlopen libbpf: %m"); + + r = bpf_restrict_fsaccess_prepare(&obj); + if (r < 0) + return r; + + /* Set initramfs_s_dev to rootfs s_dev so the test script keeps running */ + if (stat("/", &st) < 0) + return log_error_errno(errno, "Failed to stat /: %m"); + + obj->bss->initramfs_s_dev = STAT_DEV_TO_KERNEL(st.st_dev); + log_info("Set initramfs_s_dev to %u:%u (kernel dev_t=0x%x)", + major(st.st_dev), minor(st.st_dev), obj->bss->initramfs_s_dev); + + r = restrict_fsaccess_bpf__attach(obj); + if (r < 0) + return log_error_errno(r, "Failed to attach BPF programs: %m"); + + /* Populate guard globals so the guard protects our BPF objects */ + r = bpf_restrict_fsaccess_populate_guard(obj); + if (r < 0) + return log_error_errno(r, "Failed to populate guard globals: %m"); + + printf("VERITY_MAP_ID=%u\n", (unsigned) obj->bss->protected_map_id_verity); + printf("BSS_MAP_ID=%u\n", (unsigned) obj->bss->protected_map_id_bss); + + /* Print comma-separated prog and link IDs for guard tests */ + printf("PROG_IDS=\""); + for (size_t i = 0; i < _RESTRICT_FILESYSTEM_ACCESS_LINK_MAX; i++) + printf("%s%u", i > 0 ? "," : "", (unsigned) obj->bss->protected_prog_ids[i]); + printf("\"\n"); + + printf("LINK_IDS=\""); + for (size_t i = 0; i < _RESTRICT_FILESYSTEM_ACCESS_LINK_MAX; i++) + printf("%s%u", i > 0 ? "," : "", (unsigned) obj->bss->protected_link_ids[i]); + printf("\"\n"); + + fflush(stdout); + + /* Block until killed. The _cleanup_ destructor holds all link FDs via + * the skeleton. When this process is killed, close() on the FDs goes + * through bpf_link_put_direct() which synchronously detaches the + * trampoline before the process exits. No bpffs pins needed. */ + log_info("BPF programs attached, waiting for signal to detach..."); + for (;;) + pause(); + + /* unreachable — cleanup happens via signal/exit */ +} + +static int do_check(void) { + if (!bpf_restrict_fsaccess_supported()) { + log_error("BPF LSM is not available"); + return -EOPNOTSUPP; + } + log_info("BPF LSM: supported"); + + if (!dm_verity_require_signatures()) { + log_error("dm-verity require_signatures is not enabled"); + return -ENOKEY; + } + log_info("dm-verity require_signatures: enabled"); + + return 0; +} + +int main(int argc, char *argv[]) { + test_setup_logging(LOG_DEBUG); + + if (argc < 2) { + log_error("Usage: %s attach|check|mmap-exec|anon-mmap-exec|mprotect-exec", + program_invocation_short_name); + return EXIT_FAILURE; + } + + if (streq(argv[1], "attach")) + return do_attach() < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + if (streq(argv[1], "check")) + return do_check() < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + if (streq(argv[1], "mmap-exec") && argc == 3) + return do_mmap_exec(argv[2]) < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + if (streq(argv[1], "anon-mmap-exec")) + return do_anon_mmap_exec() < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + if (streq(argv[1], "mprotect-exec") && argc == 3) + return do_mprotect_exec(argv[2]) < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + + log_error("Usage: %s attach|check|mmap-exec PATH|anon-mmap-exec|mprotect-exec PATH", + program_invocation_short_name); + return EXIT_FAILURE; +} + +#else /* ! BPF_FRAMEWORK || ! HAVE_LSM_INTEGRITY_TYPE */ + +int main(int argc, char *argv[]) { + test_setup_logging(LOG_DEBUG); + + /* mmap/mprotect probes work without BPF */ + if (argc >= 2) { + if (streq(argv[1], "mmap-exec") && argc == 3) + return do_mmap_exec(argv[2]) < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + if (streq(argv[1], "anon-mmap-exec")) + return do_anon_mmap_exec() < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + if (streq(argv[1], "mprotect-exec") && argc == 3) + return do_mprotect_exec(argv[2]) < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + } + + log_info("BPF framework not available, attach/check not supported"); + return 77; /* skip */ +} + +#endif diff --git a/src/test/test-bpf-token.c b/src/test/test-bpf-token.c index f0ba50e715f84..f7156f7ff537a 100644 --- a/src/test/test-bpf-token.c +++ b/src/test/test-bpf-token.c @@ -1,25 +1,35 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include +#include "bpf-util.h" #include "fd-util.h" +#include "main-func.h" #include "tests.h" -static int intro(void) { -#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); +static int run(int argc, char *argv[]) { +#if HAVE_LIBBPF + int r; + + r = DLOPEN_BPF(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + if (r < 0) + return r; + + _cleanup_close_ int bpffs_fd = open("/sys/fs/bpf", O_CLOEXEC|O_RDONLY); if (bpffs_fd < 0) return log_error_errno(errno, "Failed to open '/sys/fs/bpf': %m"); - _cleanup_close_ int token_fd = bpf_token_create(bpffs_fd, /* opts= */ NULL); + _cleanup_close_ int token_fd = sym_bpf_token_create(bpffs_fd, /* opts= */ NULL); + if (token_fd == -ENOSYS) + return log_tests_skipped("bpf_token_create() unavailable (libbpf too old or kernel lacks BPF_TOKEN_CREATE support)"); if (token_fd < 0) - return log_error_errno(errno, "Failed to create bpf token: %m"); + return log_error_errno(token_fd, "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"); + return log_tests_skipped("BPF framework support is disabled"); #endif } -DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/test/test-btrfs-physical-offset.c b/src/test/test-btrfs-physical-offset.c deleted file mode 100644 index e039a63b933bd..0000000000000 --- a/src/test/test-btrfs-physical-offset.c +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include - -#include "btrfs-util.h" -#include "fd-util.h" -#include "log.h" -#include "memory-util.h" -#include "tests.h" - -int main(int argc, char *argv[]) { - _cleanup_close_ int fd = -EBADF; - uint64_t offset; - int r; - - assert(argc == 2); - assert(!isempty(argv[1])); - - test_setup_logging(LOG_DEBUG); - - fd = open(argv[1], O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - log_error_errno(errno, "Failed to open '%s': %m", argv[1]); - return EXIT_FAILURE; - } - - r = btrfs_get_file_physical_offset_fd(fd, &offset); - if (r < 0) { - log_error_errno(r, "Failed to get physical offset of '%s': %m", argv[1]); - return EXIT_FAILURE; - } - - printf("%" PRIu64 "\n", offset / page_size()); - return EXIT_SUCCESS; -} diff --git a/src/test/test-btrfs.c b/src/test/test-btrfs.c index b3fcf8068ec46..da3c4bf9ca069 100644 --- a/src/test/test-btrfs.c +++ b/src/test/test-btrfs.c @@ -1,202 +1,271 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include +#include #include #include +#include "alloc-util.h" #include "btrfs-util.h" +#include "chattr-util.h" #include "fd-util.h" #include "fileio.h" #include "format-util.h" #include "fs-util.h" #include "log.h" +#include "memory-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "pidref.h" +#include "process-util.h" +#include "rm-rf.h" +#include "stat-util.h" #include "string-util.h" #include "tests.h" #include "time-util.h" +#include "tmpfile-util.h" -int main(int argc, char *argv[]) { - BtrfsQuotaInfo quota; - int r, fd; +static int open_test_subvol(char **ret_path) { + const char *vtd; + int r; - test_setup_logging(LOG_DEBUG); + r = var_tmp_dir(&vtd); + if (r < 0) + return r; - fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - log_error_errno(errno, "Failed to open root directory: %m"); - else { - BtrfsSubvolInfo info; - - r = btrfs_subvol_get_info_fd(fd, 0, &info); - if (r < 0) - log_error_errno(r, "Failed to get subvolume info: %m"); - else { - log_info("otime: %s", FORMAT_TIMESTAMP(info.otime)); - log_info("read-only (search): %s", yes_no(info.read_only)); - } - - r = btrfs_qgroup_get_quota_fd(fd, 0, "a); - if (r < 0) - log_error_errno(r, "Failed to get quota info: %m"); - else { - log_info("referenced: %s", strna(FORMAT_BYTES(quota.referenced))); - log_info("exclusive: %s", strna(FORMAT_BYTES(quota.exclusive))); - log_info("referenced_max: %s", strna(FORMAT_BYTES(quota.referenced_max))); - log_info("exclusive_max: %s", strna(FORMAT_BYTES(quota.exclusive_max))); - } - - r = btrfs_subvol_get_read_only_fd(fd); - if (r < 0) - log_error_errno(r, "Failed to get read only flag: %m"); - else - log_info("read-only (ioctl): %s", yes_no(r)); - - safe_close(fd); - } + _cleanup_free_ char *base = path_join(vtd, "test-btrfs"), *p = NULL; + if (!base) + return -ENOMEM; - r = btrfs_subvol_make(AT_FDCWD, "/xxxtest"); + r = tempfn_random(base, /* extra= */ NULL, &p); if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); + return r; - r = write_string_file("/xxxtest/file", "ljsadhfljasdkfhlkjdsfha", WRITE_STRING_FILE_CREATE); - if (r < 0) - log_error_errno(r, "Failed to write file: %m"); + int fd = xopenat_full(AT_FDCWD, p, O_DIRECTORY|O_CREAT|O_CLOEXEC, XO_SUBVOLUME, MODE_INVALID); + if (fd < 0) + return fd; - r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxtest", AT_FDCWD, "/xxxtest2", 0); - if (r < 0) - log_error_errno(r, "Failed to make snapshot: %m"); + if (ret_path) + *ret_path = TAKE_PTR(p); - r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxtest", AT_FDCWD, "/xxxtest3", BTRFS_SNAPSHOT_READ_ONLY); - if (r < 0) - log_error_errno(r, "Failed to make snapshot: %m"); + return fd; +} - r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxtest", AT_FDCWD, "/xxxtest4", BTRFS_SNAPSHOT_LOCK_BSD); - if (r < 0) - log_error_errno(r, "Failed to make snapshot: %m"); - if (r >= 0) - assert_se(xopenat_lock(AT_FDCWD, "/xxxtest4", 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); +TEST_RET(info) { + _cleanup_(rm_rf_subvolume_and_freep) char *dir = NULL; + _cleanup_close_ int dir_fd = ASSERT_OK(open_test_subvol(&dir)); + BtrfsSubvolInfo info; + BtrfsQuotaInfo quota; + int r; - safe_close(r); + r = btrfs_subvol_get_info_fd(dir_fd, 0, &info); + if (r == -ENOTTY) + return log_tests_skipped("BTRFS_IOC_GET_SUBVOL_INFO not supported " + "(missing 32-bit compat handler in kernel?)"); + ASSERT_OK(r); + log_info("otime: %s", FORMAT_TIMESTAMP(info.otime)); + log_info("read-only (search): %s", yes_no(info.read_only)); - r = btrfs_subvol_remove("/xxxtest", BTRFS_REMOVE_QUOTA); + r = btrfs_qgroup_get_quota_fd(dir_fd, 0, "a); if (r < 0) - log_error_errno(r, "Failed to remove subvolume: %m"); + log_info_errno(r, "Failed to get quota info: %m"); + else { + log_info("referenced: %s", strna(FORMAT_BYTES(quota.referenced))); + log_info("exclusive: %s", strna(FORMAT_BYTES(quota.exclusive))); + log_info("referenced_max: %s", strna(FORMAT_BYTES(quota.referenced_max))); + log_info("exclusive_max: %s", strna(FORMAT_BYTES(quota.exclusive_max))); + } - r = btrfs_subvol_remove("/xxxtest2", BTRFS_REMOVE_QUOTA); - if (r < 0) - log_error_errno(r, "Failed to remove subvolume: %m"); + r = ASSERT_OK(btrfs_subvol_get_read_only_fd(dir_fd)); + log_info("read-only (ioctl): %s", yes_no(r)); - r = btrfs_subvol_remove("/xxxtest3", BTRFS_REMOVE_QUOTA); - if (r < 0) - log_error_errno(r, "Failed to remove subvolume: %m"); + return EXIT_SUCCESS; +} - r = btrfs_subvol_remove("/xxxtest4", BTRFS_REMOVE_QUOTA); - if (r < 0) - log_error_errno(r, "Failed to remove subvolume: %m"); +TEST(subvol) { + _cleanup_(rm_rf_subvolume_and_freep) char *dir = NULL; + _cleanup_close_ int dir_fd = ASSERT_OK(open_test_subvol(&dir)); - r = btrfs_subvol_snapshot_at(AT_FDCWD, "/etc", AT_FDCWD, "/etc2", - BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_FALLBACK_COPY); - if (r < 0) - log_error_errno(r, "Failed to make snapshot: %m"); + ASSERT_OK(btrfs_subvol_make(dir_fd, "test1")); + ASSERT_OK(write_string_file_at(dir_fd, "test1/file", "ljsadhfljasdkfhlkjdsfha", WRITE_STRING_FILE_CREATE)); - r = btrfs_subvol_remove("/etc2", BTRFS_REMOVE_QUOTA); - if (r < 0) - log_error_errno(r, "Failed to remove subvolume: %m"); + ASSERT_OK(btrfs_subvol_snapshot_at(dir_fd, "test1", dir_fd, "test2", 0)); + ASSERT_OK(btrfs_subvol_snapshot_at(dir_fd, "test1", dir_fd, "test3", BTRFS_SNAPSHOT_READ_ONLY)); - r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); + _unused_ _cleanup_close_ int locked_fd = ASSERT_OK(btrfs_subvol_snapshot_at(dir_fd, "test1", dir_fd, "test4", BTRFS_SNAPSHOT_LOCK_BSD)); + ASSERT_ERROR(xopenat_lock(dir_fd, "test4", 0, LOCK_BSD, LOCK_EX|LOCK_NB), EAGAIN); - r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/xxxrectest2"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); + /* The destroy ioctl needs CAP_SYS_ADMIN; without it, leave cleanup to rm_rf_subvolume_and_freep. */ + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "test1", BTRFS_REMOVE_QUOTA), -EPERM); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "test2", BTRFS_REMOVE_QUOTA), -EPERM); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "test3", BTRFS_REMOVE_QUOTA), -EPERM); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "test4", BTRFS_REMOVE_QUOTA), -EPERM); +} - r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/xxxrectest3"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); +TEST(fallback_copy) { + _cleanup_(rm_rf_subvolume_and_freep) char *dir = NULL; + _cleanup_close_ int dir_fd = ASSERT_OK(open_test_subvol(&dir)); - r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/xxxrectest3/sub"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); + /* Snapshot a regular directory (not a subvolume) — exercises the FALLBACK_COPY path. */ + ASSERT_OK_ERRNO(mkdirat(dir_fd, "src", 0755)); + ASSERT_OK(write_string_file_at(dir_fd, "src/file1", "hello", WRITE_STRING_FILE_CREATE)); + ASSERT_OK(write_string_file_at(dir_fd, "src/file2", "world", WRITE_STRING_FILE_CREATE)); - if (mkdir("/xxxrectest/dir", 0755) < 0) - log_error_errno(errno, "Failed to make directory: %m"); + ASSERT_OK(btrfs_subvol_snapshot_at(dir_fd, "src", dir_fd, "snap", + BTRFS_SNAPSHOT_READ_ONLY|BTRFS_SNAPSHOT_FALLBACK_COPY)); - r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/dir/xxxrectest4"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "snap", BTRFS_REMOVE_QUOTA), -EPERM); +} - if (mkdir("/xxxrectest/dir/xxxrectest4/dir", 0755) < 0) - log_error_errno(errno, "Failed to make directory: %m"); +TEST(recursive) { + _cleanup_(rm_rf_subvolume_and_freep) char *dir = NULL; + _cleanup_close_ int dir_fd = ASSERT_OK(open_test_subvol(&dir)); - r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/dir/xxxrectest4/dir/xxxrectest5"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); + ASSERT_OK(btrfs_subvol_make(dir_fd, "rec")); + ASSERT_OK(btrfs_subvol_make(dir_fd, "rec/sv2")); + ASSERT_OK(btrfs_subvol_make(dir_fd, "rec/sv3")); + ASSERT_OK(btrfs_subvol_make(dir_fd, "rec/sv3/sub")); - if (mkdir("/xxxrectest/mnt", 0755) < 0) - log_error_errno(errno, "Failed to make directory: %m"); + ASSERT_OK_ERRNO(mkdirat(dir_fd, "rec/dir", 0755)); + ASSERT_OK(btrfs_subvol_make(dir_fd, "rec/dir/sv4")); + ASSERT_OK_ERRNO(mkdirat(dir_fd, "rec/dir/sv4/dir", 0755)); + ASSERT_OK(btrfs_subvol_make(dir_fd, "rec/dir/sv4/dir/sv5")); - r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxrectest", AT_FDCWD, "/xxxrectest2", BTRFS_SNAPSHOT_RECURSIVE); - if (r < 0) - log_error_errno(r, "Failed to snapshot subvolume: %m"); + ASSERT_OK_ERRNO(mkdirat(dir_fd, "rec/mnt", 0755)); - r = btrfs_subvol_remove("/xxxrectest", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); - if (r < 0) - log_error_errno(r, "Failed to recursively remove subvolume: %m"); + ASSERT_OK(btrfs_subvol_snapshot_at(dir_fd, "rec", dir_fd, "rec-snap", BTRFS_SNAPSHOT_RECURSIVE)); - r = btrfs_subvol_remove("/xxxrectest2", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); - if (r < 0) - log_error_errno(r, "Failed to recursively remove subvolume: %m"); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "rec", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE), -EPERM); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "rec-snap", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE), -EPERM); +} - r = btrfs_subvol_make(AT_FDCWD, "/xxxquotatest"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); +TEST(quota) { + if (!slow_tests_enabled()) + return (void) log_tests_skipped("slow tests are disabled"); - r = btrfs_subvol_auto_qgroup("/xxxquotatest", 0, true); - if (r < 0) - log_error_errno(r, "Failed to set up auto qgroup: %m"); + _cleanup_(rm_rf_subvolume_and_freep) char *dir = NULL; + _cleanup_close_ int dir_fd = ASSERT_OK(open_test_subvol(&dir)); + BtrfsQuotaInfo quota; + int r; - r = btrfs_subvol_make(AT_FDCWD, "/xxxquotatest/beneath"); - if (r < 0) - log_error_errno(r, "Failed to make subvolume: %m"); + _cleanup_free_ char *qt = ASSERT_NOT_NULL(path_join(dir, "quotatest")), + *qt2 = ASSERT_NOT_NULL(path_join(dir, "quotatest2")), + *beneath = ASSERT_NOT_NULL(path_join(dir, "quotatest/beneath")), + *snap_beneath = ASSERT_NOT_NULL(path_join(dir, "quotatest2/beneath")); - r = btrfs_subvol_auto_qgroup("/xxxquotatest/beneath", 0, false); - if (r < 0) - log_error_errno(r, "Failed to set up auto qgroup: %m"); + ASSERT_OK(btrfs_subvol_make(dir_fd, "quotatest")); + /* The qgroup/quota ioctls require CAP_SYS_ADMIN; skip the rest of the test if we don't have it + * or quotas are not enabled on this filesystem. */ + r = btrfs_subvol_auto_qgroup(qt, 0, true); + if (r == -EPERM) + return (void) log_tests_skipped("not running privileged"); + if (IN_SET(r, -ENOTCONN, -ENOENT)) + return (void) log_tests_skipped_errno(r, "btrfs quotas not enabled on this filesystem"); + ASSERT_OK(r); - r = btrfs_qgroup_set_limit("/xxxquotatest/beneath", 0, 4ULL * 1024 * 1024 * 1024); - if (r < 0) - log_error_errno(r, "Failed to set up quota limit: %m"); + ASSERT_OK(btrfs_subvol_make(dir_fd, "quotatest/beneath")); + ASSERT_OK(btrfs_subvol_auto_qgroup(beneath, 0, false)); + ASSERT_OK(btrfs_qgroup_set_limit(beneath, 0, 4ULL * 1024 * 1024 * 1024)); - r = btrfs_subvol_set_subtree_quota_limit("/xxxquotatest", 0, 5ULL * 1024 * 1024 * 1024); - if (r < 0) - log_error_errno(r, "Failed to set up quota limit: %m"); + ASSERT_OK(btrfs_subvol_set_subtree_quota_limit(qt, 0, 5ULL * 1024 * 1024 * 1024)); - r = btrfs_subvol_snapshot_at(AT_FDCWD, "/xxxquotatest", AT_FDCWD, "/xxxquotatest2", - BTRFS_SNAPSHOT_RECURSIVE|BTRFS_SNAPSHOT_QUOTA); - if (r < 0) - log_error_errno(r, "Failed to set up snapshot: %m"); + ASSERT_OK(btrfs_subvol_snapshot_at(dir_fd, "quotatest", dir_fd, "quotatest2", + BTRFS_SNAPSHOT_RECURSIVE|BTRFS_SNAPSHOT_QUOTA)); - r = btrfs_qgroup_get_quota("/xxxquotatest2/beneath", 0, "a); - if (r < 0) - log_error_errno(r, "Failed to query quota: %m"); + ASSERT_OK(btrfs_qgroup_get_quota(snap_beneath, 0, "a)); + ASSERT_EQ(quota.referenced_max, 4ULL * 1024 * 1024 * 1024); - if (r >= 0) - assert_se(quota.referenced_max == 4ULL * 1024 * 1024 * 1024); + ASSERT_OK(btrfs_subvol_get_subtree_quota(qt2, 0, "a)); + ASSERT_EQ(quota.referenced_max, 5ULL * 1024 * 1024 * 1024); - r = btrfs_subvol_get_subtree_quota("/xxxquotatest2", 0, "a); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "quotatest", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE), -EPERM); + ASSERT_OK_OR(btrfs_subvol_remove_at(dir_fd, "quotatest2", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE), -EPERM); +} + +TEST(physical_offset) { + _cleanup_free_ char *btrfs_progs = NULL; + int r = find_executable("btrfs", &btrfs_progs); if (r < 0) - log_error_errno(r, "Failed to query quota: %m"); + return (void) log_tests_skipped_errno(r, "btrfs(8) not found"); + + _cleanup_(rm_rf_subvolume_and_freep) char *dir = NULL; + _cleanup_close_ int dir_fd = ASSERT_OK(open_test_subvol(&dir)); + + /* Set NOCOW on the subvol dir so the swapfile inherits it on creation. Older btrfs-progs + * versions don't reliably set NOCOW from `btrfs filesystem mkswapfile`. */ + ASSERT_OK(chattr_fd(dir_fd, FS_NOCOW_FL, FS_NOCOW_FL)); + + /* btrfs filesystem mkswapfile produces a NOCOW, contiguous file with a swap header — exactly + * what btrfs inspect-internal map-swapfile expects, and what btrfs_get_file_physical_offset_fd + * works with. */ + _cleanup_free_ char *path = ASSERT_NOT_NULL(path_join(dir, "swapfile")); + r = ASSERT_OK(pidref_safe_fork("(mkswapfile)", + FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_WAIT, + NULL)); + if (r == 0) { + execlp(btrfs_progs, "btrfs", "filesystem", "mkswapfile", "-s", "1m", path, NULL); + _exit(EXIT_FAILURE); + } - if (r >= 0) - assert_se(quota.referenced_max == 5ULL * 1024 * 1024 * 1024); + _cleanup_close_ int fd = ASSERT_OK_ERRNO(openat(dir_fd, "swapfile", O_RDONLY|O_CLOEXEC|O_NOCTTY)); + + unsigned attrs; + ASSERT_OK(read_attr_fd(fd, &attrs)); + if (!FLAGS_SET(attrs, FS_NOCOW_FL) || FLAGS_SET(attrs, FS_COMPR_FL)) + return (void) log_tests_skipped("swapfile is not NOCOW/non-compressed (old btrfs-progs?)"); + + /* btrfs_get_file_physical_offset_fd() uses BTRFS_IOC_TREE_SEARCH, which needs CAP_SYS_ADMIN. */ + uint64_t offset; + r = btrfs_get_file_physical_offset_fd(fd, &offset); + if (r == -EPERM) + return (void) log_tests_skipped("not running privileged"); + ASSERT_OK(r); + + /* Cross-check against `btrfs inspect-internal map-swapfile -r`, which prints the first + * physical address in page units. */ + _cleanup_close_pair_ int pipe_fds[2] = EBADF_PAIR; + ASSERT_OK_ERRNO(pipe2(pipe_fds, O_CLOEXEC)); + + _cleanup_(pidref_done) PidRef inspect = PIDREF_NULL; + r = pidref_safe_fork_full("(btrfs-inspect)", + (int[3]) { -EBADF, pipe_fds[1], STDERR_FILENO }, + /* except_fds= */ NULL, /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG|FORK_REARRANGE_STDIO, + &inspect); + ASSERT_OK(r); + if (r == 0) { + execlp(btrfs_progs, "btrfs", "inspect-internal", "map-swapfile", "-r", path, NULL); + _exit(EXIT_FAILURE); + } + pipe_fds[1] = safe_close(pipe_fds[1]); + + _cleanup_fclose_ FILE *f = ASSERT_NOT_NULL(take_fdopen(&pipe_fds[0], "r")); + _cleanup_free_ char *out = NULL; + ASSERT_OK(read_full_stream(f, &out, /* ret_size= */ NULL)); + ASSERT_OK_EQ(pidref_wait_for_terminate_and_check("(btrfs-inspect)", &inspect, 0), 0); + + uint64_t expected; + ASSERT_OK(safe_atou64(strstrip(out), &expected)); + ASSERT_EQ(offset / page_size(), expected); + log_info("physical offset: page %" PRIu64, expected); +} + +static int intro(void) { + const char *vtd; + int r; - r = btrfs_subvol_remove("/xxxquotatest", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); + r = var_tmp_dir(&vtd); if (r < 0) - log_error_errno(r, "Failed to remove subvolume: %m"); + return log_tests_skipped_errno(r, "Failed to resolve /var/tmp"); - r = btrfs_subvol_remove("/xxxquotatest2", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); + r = path_is_fs_type(vtd, BTRFS_SUPER_MAGIC); if (r < 0) - log_error_errno(r, "Failed to remove subvolume: %m"); + return log_tests_skipped_errno(r, "Failed to determine filesystem type of %s", vtd); + if (r == 0) + return log_tests_skipped("%s is not on btrfs", vtd); - return 0; + return EXIT_SUCCESS; } + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-bus-unit-util.c b/src/test/test-bus-unit-util.c index a330bd80545d3..ae2cb19c9ce0a 100644 --- a/src/test/test-bus-unit-util.c +++ b/src/test/test-bus-unit-util.c @@ -117,6 +117,11 @@ TEST(cgroup_properties) { "StartupAllowedMemoryNodes=0", "StartupAllowedMemoryNodes=1-3", + "CPUSetPartition=member", + "CPUSetPartition=root", + "CPUSetPartition=isolated", + "CPUSetPartition=", + "DisableControllers=cpu", "DisableControllers= " " cpu cpuacct cpuset io blkio memory devices pids bpf-firewall bpf-devices " diff --git a/src/test/test-bus-util.c b/src/test/test-bus-util.c index 6361d7f17a1a5..55d094afc1ad3 100644 --- a/src/test/test-bus-util.c +++ b/src/test/test-bus-util.c @@ -2,6 +2,8 @@ #include "sd-bus.h" +#include "bus-internal.h" +#include "bus-map-properties.h" #include "bus-util.h" #include "log.h" #include "tests.h" @@ -44,4 +46,176 @@ TEST(destroy_callback) { ASSERT_EQ(n_called, 1); } +/* Guard field detects an over-wide write spilling past the declared slot. */ +struct numeric_target { + uint32_t value; + uint32_t guard; +}; + +struct strv_target { + char **list; + char **guard; +}; + +#define GUARD_PATTERN UINT32_C(0x5A5A5A5A) + +/* Unconnected bus, only good for constructing messages locally. */ +static void map_properties_fake_bus(sd_bus **ret) { + sd_bus *bus = NULL; + + ASSERT_OK(sd_bus_new(&bus)); + bus->state = BUS_RUNNING; /* Fake state to allow message creation */ + *ret = bus; +} + +static void map_properties_seal_for_read(sd_bus_message *m) { + ASSERT_OK(sd_bus_message_seal(m, 1, 0)); + ASSERT_OK(sd_bus_message_rewind(m, true)); +} + +/* Over-wide scalar wire type must not write past the declared slot. */ +TEST(map_all_properties_numeric_type_confusion) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + struct numeric_target data = { .value = 0, .guard = GUARD_PATTERN }; + + static const struct bus_properties_map map[] = { + { "Value", "u", NULL, offsetof(struct numeric_target, value) }, + {}, + }; + + map_properties_fake_bus(&bus); + + ASSERT_OK(sd_bus_message_new_method_call(bus, &m, "foo.bar", "/", "foo.bar", "Get")); + ASSERT_OK(sd_bus_message_append(m, "a{sv}", 1, "Value", "t", (uint64_t) 0xAABBCCDD11223344ULL)); + map_properties_seal_for_read(m); + + ASSERT_OK(bus_message_map_all_properties(m, map, /* flags= */ 0, /* reterr_error= */ NULL, &data)); + + /* Skipped, adjacent field intact. */ + ASSERT_EQ(data.guard, GUARD_PATTERN); +} + +/* Scalar "t" against a declared "as" char** slot must be skipped, not planted+freed. */ +TEST(map_all_properties_pointer_type_confusion) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + char *guard_marker = NULL; + struct strv_target data = { .guard = &guard_marker }; + + static const struct bus_properties_map map[] = { + { "List", "as", NULL, offsetof(struct strv_target, list) }, + {}, + }; + + map_properties_fake_bus(&bus); + + ASSERT_OK(sd_bus_message_new_method_call(bus, &m, "foo.bar", "/", "foo.bar", "Get")); + ASSERT_OK(sd_bus_message_append(m, "a{sv}", 1, "List", "t", (uint64_t) 0xDEADBEEFCAFEBABEULL)); + map_properties_seal_for_read(m); + + ASSERT_OK(bus_message_map_all_properties(m, map, /* flags= */ 0, /* reterr_error= */ NULL, &data)); + + ASSERT_NULL(data.list); + ASSERT_PTR_EQ(data.guard, &guard_marker); +} + +/* Mismatched array element type ("ao" vs "as") must be skipped. "ao" is one read_strv_extend() + * accepts, so the signature guard, not the reader, is what rejects it. */ +TEST(map_all_properties_array_type_confusion) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + char *guard_marker = NULL; + struct strv_target data = { .guard = &guard_marker }; + + static const struct bus_properties_map map[] = { + { "List", "as", NULL, offsetof(struct strv_target, list) }, + {}, + }; + + map_properties_fake_bus(&bus); + + ASSERT_OK(sd_bus_message_new_method_call(bus, &m, "foo.bar", "/", "foo.bar", "Get")); + ASSERT_OK(sd_bus_message_append(m, "a{sv}", 1, "List", "ao", 1, "/foo/bar")); + map_properties_seal_for_read(m); + + ASSERT_OK(bus_message_map_all_properties(m, map, /* flags= */ 0, /* reterr_error= */ NULL, &data)); + + /* Skipped before map_basic()'s strv branch runs. */ + ASSERT_NULL(data.list); + ASSERT_PTR_EQ(data.guard, &guard_marker); +} + +/* Correctly typed "u" variant is still read into the slot. */ +TEST(map_all_properties_correct_type) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + struct numeric_target data = { .value = 0, .guard = GUARD_PATTERN }; + + static const struct bus_properties_map map[] = { + { "Value", "u", NULL, offsetof(struct numeric_target, value) }, + {}, + }; + + map_properties_fake_bus(&bus); + + ASSERT_OK(sd_bus_message_new_method_call(bus, &m, "foo.bar", "/", "foo.bar", "Get")); + ASSERT_OK(sd_bus_message_append(m, "a{sv}", 1, "Value", "u", (uint32_t) 42)); + map_properties_seal_for_read(m); + + ASSERT_OK(bus_message_map_all_properties(m, map, /* flags= */ 0, /* reterr_error= */ NULL, &data)); + + ASSERT_EQ(data.value, UINT32_C(42)); + ASSERT_EQ(data.guard, GUARD_PATTERN); +} + +static int record_set_called(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *reterr_error, void *userdata) { + bool *called = ASSERT_PTR(userdata); + int r; + + /* Consume the value, like a real set callback would. */ + r = sd_bus_message_skip(m, NULL); + if (r < 0) + return r; + + *called = true; + return 0; +} + +/* Wrong-type variant must be skipped before a property-set callback runs. */ +TEST(map_all_properties_set_callback_type_confusion) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + bool called = false; + + static const struct bus_properties_map map[] = { + { "List", "as", record_set_called, 0 }, + {}, + }; + + map_properties_fake_bus(&bus); + + /* Wrong type: the set callback must not run. */ + ASSERT_OK(sd_bus_message_new_method_call(bus, &m, "foo.bar", "/", "foo.bar", "Get")); + ASSERT_OK(sd_bus_message_append(m, "a{sv}", 1, "List", "t", (uint64_t) 0xDEADBEEFCAFEBABEULL)); + map_properties_seal_for_read(m); + + ASSERT_OK(bus_message_map_all_properties(m, map, /* flags= */ 0, /* reterr_error= */ NULL, &called)); + ASSERT_FALSE(called); + + /* Matching type: the set callback runs as before. */ + m = sd_bus_message_unref(m); + ASSERT_OK(sd_bus_message_new_method_call(bus, &m, "foo.bar", "/", "foo.bar", "Get")); + ASSERT_OK(sd_bus_message_append(m, "a{sv}", 1, "List", "as", 1, "item")); + map_properties_seal_for_read(m); + + ASSERT_OK(bus_message_map_all_properties(m, map, /* flags= */ 0, /* reterr_error= */ NULL, &called)); + ASSERT_TRUE(called); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-cgroup-mask.c b/src/test/test-cgroup-mask.c index 6b123f8761b47..967341508053e 100644 --- a/src/test/test-cgroup-mask.c +++ b/src/test/test-cgroup-mask.c @@ -51,7 +51,7 @@ TEST_RET(cgroup_mask, .sd_booted = true) { m->defaults.tasks_accounting = false; m->defaults.tasks_max = CGROUP_TASKS_MAX_UNSET; - assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL, NULL) >= 0); /* Load units and verify hierarchy. */ ASSERT_OK(manager_load_startable_unit_or_warn(m, "parent.slice", NULL, &parent)); diff --git a/src/test/test-chase-manual.c b/src/test/test-chase-manual.c index 376cd12c42628..410522ceb161f 100644 --- a/src/test/test-chase-manual.c +++ b/src/test/test-chase-manual.c @@ -1,84 +1,73 @@ /* 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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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 = opts.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 |= opts.opt->data; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind == argc) + *ret_args = option_parser_get_args(&opts); + if (strv_isempty(*ret_args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument is required."); return 1; @@ -89,18 +78,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..23ceef53285e7 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), ELOOP); + ASSERT_ERROR(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), ELOOP); + ASSERT_ERROR(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL), ELOOP); + ASSERT_ERROR(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), ELOOP); + ASSERT_ERROR(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL), ELOOP); + ASSERT_ERROR(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), ELOOP); 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..84f7eefac38ab 100644 --- a/src/test/test-chid.c +++ b/src/test/test-chid.c @@ -1,9 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "chid-fundamental.h" +#include "chid.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-color-util.c b/src/test/test-color-util.c index 3d00d8719d82a..a1369f4ef5231 100644 --- a/src/test/test-color-util.c +++ b/src/test/test-color-util.c @@ -6,58 +6,126 @@ TEST(hsv_to_rgb) { uint8_t r, g, b; + /* Black at any hue. */ hsv_to_rgb(0, 0, 0, &r, &g, &b); - assert(r == 0 && g == 0 && b == 0); + ASSERT_EQ(r, 0); + ASSERT_EQ(g, 0); + ASSERT_EQ(b, 0); hsv_to_rgb(60, 0, 0, &r, &g, &b); - assert(r == 0 && g == 0 && b == 0); + ASSERT_EQ(r, 0); + ASSERT_EQ(g, 0); + ASSERT_EQ(b, 0); + /* White: s=0 must ignore hue. */ hsv_to_rgb(0, 0, 100, &r, &g, &b); - assert(r == 255 && g == 255 && b == 255); - - hsv_to_rgb(0, 100, 100, &r, &g, &b); - assert(r == 255 && g == 0 && b == 0); - - hsv_to_rgb(120, 100, 100, &r, &g, &b); - assert(r == 0 && g == 255 && b == 0); - - hsv_to_rgb(240, 100, 100, &r, &g, &b); - assert(r == 0 && g == 0 && b == 255); - + ASSERT_EQ(r, 255); + ASSERT_EQ(g, 255); + ASSERT_EQ(b, 255); + + hsv_to_rgb(180, 0, 100, &r, &g, &b); + ASSERT_EQ(r, 255); + ASSERT_EQ(g, 255); + ASSERT_EQ(b, 255); + + /* Pure primary and secondary colors — one per sector boundary. */ + hsv_to_rgb(0, 100, 100, &r, &g, &b); /* red */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 0); + hsv_to_rgb(60, 100, 100, &r, &g, &b); /* yellow */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 255); ASSERT_EQ(b, 0); + hsv_to_rgb(120, 100, 100, &r, &g, &b); /* green */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 255); ASSERT_EQ(b, 0); + hsv_to_rgb(180, 100, 100, &r, &g, &b); /* cyan */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 255); ASSERT_EQ(b, 255); + hsv_to_rgb(240, 100, 100, &r, &g, &b); /* blue */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 0); ASSERT_EQ(b, 255); + hsv_to_rgb(300, 100, 100, &r, &g, &b); /* magenta */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 255); + + /* Sector midpoints. Catches inverted ramp direction (k & 1 ? 1-f : f) in any single + * sector — a regression would shift exactly one channel by ±half-intensity. */ + hsv_to_rgb(30, 100, 100, &r, &g, &b); /* orange — sector 0 */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 127); ASSERT_EQ(b, 0); + hsv_to_rgb(90, 100, 100, &r, &g, &b); /* chartreuse — sector 1 */ + ASSERT_EQ(r, 127); ASSERT_EQ(g, 255); ASSERT_EQ(b, 0); + hsv_to_rgb(150, 100, 100, &r, &g, &b); /* spring green — sector 2 */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 255); ASSERT_EQ(b, 127); + hsv_to_rgb(210, 100, 100, &r, &g, &b); /* azure — sector 3 */ + ASSERT_EQ(r, 0); ASSERT_EQ(g, 127); ASSERT_EQ(b, 255); + hsv_to_rgb(270, 100, 100, &r, &g, &b); /* violet — sector 4 */ + ASSERT_EQ(r, 127); ASSERT_EQ(g, 0); ASSERT_EQ(b, 255); + hsv_to_rgb(330, 100, 100, &r, &g, &b); /* rose — sector 5 */ + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 127); + + /* Just below 360 — ensures (int) seg stays in sector 5 and doesn't wrap to 6. */ + hsv_to_rgb(359.5, 100, 100, &r, &g, &b); + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 2); + + /* Exactly 360 — cyclic boundary, equivalent to 0 (red). Callers like color_for_pcr() in + * pcrlock.c can reach this via floating-point arithmetic (e.g. 360.0/23*23 == 360.0). */ + hsv_to_rgb(360, 100, 100, &r, &g, &b); + ASSERT_EQ(r, 255); ASSERT_EQ(g, 0); ASSERT_EQ(b, 0); + + /* Non-trivial s/v: regression check for the multiply-and-cast path. */ hsv_to_rgb(311, 52, 62, &r, &g, &b); - assert(r == 158 && g == 75 && b == 143); + ASSERT_EQ(r, 158); ASSERT_EQ(g, 75); ASSERT_EQ(b, 143); } TEST(rgb_to_hsv) { double h, s, v; + + /* Grayscale: delta == 0, h is NaN, s == 0. */ rgb_to_hsv(0, 0, 0, &h, &s, &v); - assert(s <= 0); - assert(v <= 0); + ASSERT_TRUE(s <= 0); + ASSERT_TRUE(v <= 0); rgb_to_hsv(1, 1, 1, &h, &s, &v); - assert(s <= 0); - assert(v >= 100); - - rgb_to_hsv(1, 0, 0, &h, &s, &v); - assert(h >= 359 || h <= 1); - assert(s >= 100); - assert(v >= 100); - - rgb_to_hsv(0, 1, 0, &h, &s, &v); - assert(h >= 119 && h <= 121); - assert(s >= 100); - assert(v >= 100); - - rgb_to_hsv(0, 0, 1, &h, &s, &v); - assert(h >= 239 && h <= 241); - assert(s >= 100); - assert(v >= 100); - + ASSERT_TRUE(s <= 0); + ASSERT_TRUE(v >= 100); + + rgb_to_hsv(0.5, 0.5, 0.5, &h, &s, &v); + ASSERT_TRUE(s <= 0); + ASSERT_TRUE(v >= 49 && v <= 51); + + /* Pure primary colors. */ + rgb_to_hsv(1, 0, 0, &h, &s, &v); /* red */ + ASSERT_TRUE(h >= 0 && h <= 1); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + rgb_to_hsv(0, 1, 0, &h, &s, &v); /* green */ + ASSERT_TRUE(h >= 119 && h <= 121); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + rgb_to_hsv(0, 0, 1, &h, &s, &v); /* blue */ + ASSERT_TRUE(h >= 239 && h <= 241); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + /* Pure secondary colors — each exercises a different "max" branch. Magenta exercises + * the negative-hue wrap from the r-max branch (raw value is -60). */ + rgb_to_hsv(1, 1, 0, &h, &s, &v); /* yellow — r-max branch, positive */ + ASSERT_TRUE(h >= 59 && h <= 61); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + rgb_to_hsv(0, 1, 1, &h, &s, &v); /* cyan — g-max branch */ + ASSERT_TRUE(h >= 179 && h <= 181); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + rgb_to_hsv(1, 0, 1, &h, &s, &v); /* magenta — r-max branch, wrapped */ + ASSERT_TRUE(h >= 299 && h <= 301); + ASSERT_TRUE(s >= 100); + ASSERT_TRUE(v >= 100); + + /* Mixed values. */ rgb_to_hsv(0.5, 0.6, 0.7, &h, &s, &v); - assert(h >= 209 && h <= 211); - assert(s >= 28 && s <= 31); - assert(v >= 69 && v <= 71); + ASSERT_TRUE(h >= 209 && h <= 211); + ASSERT_TRUE(s >= 28 && s <= 31); + ASSERT_TRUE(v >= 69 && v <= 71); } DEFINE_TEST_MAIN(LOG_DEBUG); 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-condition.c b/src/test/test-condition.c index 234041d1268a1..a4c57dc1979dc 100644 --- a/src/test/test-condition.c +++ b/src/test/test-condition.c @@ -34,6 +34,7 @@ #include "rm-rf.h" #include "selinux-util.h" #include "smack-util.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "tests.h" @@ -243,6 +244,85 @@ TEST(condition_test_host) { condition_free(condition); } +TEST(condition_test_fraction) { + Condition *condition; + int r; + + /* The 0%/100% boundaries are deterministic and short-circuit before the machine ID is even + * read, so these run everywhere. */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, "0%", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, "100%", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* A tag does not change the boundary behaviour. */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, "sometag 0%", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, "sometag 100%", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* Negation flips the boundaries. */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, "100%", /* trigger= */ false, /* negate= */ true))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, "0%", /* trigger= */ false, /* negate= */ true))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* Malformed values must propagate an error rather than silently passing or failing. */ + FOREACH_STRING(bad, + "", /* empty */ + "abc", /* not a number */ + "30", /* missing percent sign */ + "30 %", /* percent token is just '%' */ + "150%", /* out of range */ + "tag 30% extra", /* trailing garbage */ + "a b 30%") { /* unquoted multi-word tag → trailing garbage */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, bad, /* trigger= */ false, /* negate= */ false))); + ASSERT_FAIL(condition_test(condition, environ)); + condition_free(condition); + } + + sd_id128_t id; + r = sd_id128_get_machine(&id); + if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) + return (void) log_tests_skipped("/etc/machine-id missing"); + ASSERT_OK(r); + + /* Distribution check: for a fixed machine ID, varying the tag spreads results uniformly, so + * about PERCENT of the tags match. This is the statistical dual of the production scenario, + * where the tag is fixed and the machine ID varies across a fleet, and it also pins down the + * direction of the comparison (a higher percentage matches more, not fewer). The result is + * deterministic for a given machine ID, and the slack is many standard deviations wide, so + * this does not flake. */ + static const unsigned percentages[] = { 1, 20, 50, 80 }; + FOREACH_ELEMENT(pct, percentages) { + const unsigned n = 10000, slack = 3 * n / 100; /* ±3 percentage points */ + unsigned match = 0; + + for (unsigned i = 0; i < n; i++) { + char param[64]; + xsprintf(param, "tag-%u %u%%", i, *pct); + + ASSERT_NOT_NULL((condition = condition_new(CONDITION_FRACTION, param, /* trigger= */ false, /* negate= */ false))); + r = ASSERT_OK(condition_test(condition, environ)); + match += r > 0; + condition_free(condition); + } + + unsigned expected = *pct * n / 100; + ASSERT_TRUE(match + slack >= expected); + ASSERT_TRUE(expected + slack >= match); + } +} + TEST(condition_test_architecture) { Condition *condition; const char *sa; @@ -1334,6 +1414,132 @@ TEST(condition_test_os_release) { condition_free(condition); } +TEST(condition_test_machine_tag) { + Condition *condition; + + /* etc_machine_info() caches the path on first use, so redirect it before anything reads it and + * rewrite the same file (truncating) for each scenario rather than switching paths. */ + _cleanup_free_ char *saved = NULL; + ASSERT_OK(free_and_strdup(&saved, getenv("SYSTEMD_ETC_MACHINE_INFO"))); + + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + ASSERT_OK(mkdtemp_malloc(NULL, &d)); + + _cleanup_free_ char *f = path_join(d, "machine-info"); + ASSERT_NOT_NULL(f); + ASSERT_OK_ERRNO(setenv("SYSTEMD_ETC_MACHINE_INFO", f, /* overwrite= */ true)); + + ASSERT_OK(write_string_file(f, "TAGS=\"webserver:frontend:berlin\"\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE)); + + /* Exact match */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "webserver", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* Glob match */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "front*", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* No match */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "database", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + /* Negation matches when the tag is absent, ... */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "database", /* trigger= */ false, /* negate= */ true))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* ... and does not match when the tag is present */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "webserver", /* trigger= */ false, /* negate= */ true))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + /* Invalid tags in the file are ignored (matching the Tags D-Bus property) */ + ASSERT_OK(write_string_file(f, "TAGS=\"in valid:good\"\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE)); + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "in valid", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "good", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* No tags configured at all → never matches */ + ASSERT_OK(write_string_file(f, "PRETTY_HOSTNAME=\"x\"\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE)); + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "webserver", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + /* Key/value (parameterized) tags */ + ASSERT_OK(write_string_file(f, "TAGS=\"role=webserver:env=prod:berlin\"\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE)); + + /* Exact match of a key/value tag */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "role=webserver", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* Glob on the value */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "role=web*", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* Glob on the key */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "*=prod", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* A bare key does not match an assignment (the "=" is part of the tag) */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "role", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + /* Right key, wrong value → no match */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "role=database", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + /* A plain (non-parameterized) tag still matches alongside key/value tags */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "berlin", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + /* Negation against a key/value tag */ + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "role=database", /* trigger= */ false, /* negate= */ true))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "role=webserver", /* trigger= */ false, /* negate= */ true))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + /* Conflicting values for the same key: graceful parsing keeps the first (lexicographically smallest) + * value and drops the rest, so only "env=prod" remains visible. */ + ASSERT_OK(write_string_file(f, "TAGS=\"env=staging:env=prod\"\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE)); + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "env=prod", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "env=staging", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + + /* Invalid key/value tags in the file are ignored, valid ones still match */ + ASSERT_OK(write_string_file(f, "TAGS=\"bad-=x:role=good\"\n", + WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_TRUNCATE)); + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "bad-=x", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_ZERO(condition_test(condition, environ)); + condition_free(condition); + ASSERT_NOT_NULL((condition = condition_new(CONDITION_MACHINE_TAG, "role=good", /* trigger= */ false, /* negate= */ false))); + ASSERT_OK_POSITIVE(condition_test(condition, environ)); + condition_free(condition); + + ASSERT_OK(set_unset_env("SYSTEMD_ETC_MACHINE_INFO", saved, /* overwrite= */ true)); +} + TEST(condition_test_psi) { Condition *condition; CGroupMask mask; diff --git a/src/test/test-copy.c b/src/test/test-copy.c index 758a597fc539a..290a6bed8f9b8 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -141,7 +141,7 @@ TEST(copy_tree) { "link2", "dir1/file"); char **hardlinks = STRV_MAKE("hlink", "file", "hlink2", "dir1/file"); - const char *unixsockp, *ignorep; + const char *unixsockp, *ignorep, *denydirp, *denyfilep; struct stat st; int xattr_worked = -1; /* xattr support is optional in temporary directories, hence use it if we can, * but don't fail if we can't */ @@ -194,6 +194,14 @@ TEST(copy_tree) { assert_se(hashmap_ensure_put(&denylist, &inode_hash_ops, cp, INT_TO_PTR(DENY_INODE)) >= 0); TAKE_PTR(cp); + denyfilep = strjoina(original_dir, "denycontents/file"); + assert_se(write_string_file(denyfilep, "denied", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) == 0); + denydirp = strjoina(original_dir, "denycontents"); + assert_se(RET_NERRNO(stat(denydirp, &st)) >= 0); + assert_se(cp = memdup(&st, sizeof(st))); + assert_se(hashmap_ensure_put(&denylist, &inode_hash_ops, cp, INT_TO_PTR(DENY_CONTENTS)) >= 0); + TAKE_PTR(cp); + assert_se(copy_tree(original_dir, copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_MERGE|COPY_HARDLINKS, denylist, NULL) == 0); STRV_FOREACH(p, files) { @@ -252,6 +260,13 @@ TEST(copy_tree) { ignorep = strjoina(copy_dir, "ignore/file"); assert_se(RET_NERRNO(access(ignorep, F_OK)) == -ENOENT); + /* The DENY_CONTENTS directory itself must exist in the copy, but its contents must not have been copied. */ + denydirp = strjoina(copy_dir, "denycontents"); + assert_se(stat(denydirp, &st) >= 0); + assert_se(S_ISDIR(st.st_mode)); + denyfilep = strjoina(copy_dir, "denycontents/file"); + assert_se(RET_NERRNO(access(denyfilep, F_OK)) == -ENOENT); + (void) rm_rf(copy_dir, REMOVE_ROOT|REMOVE_PHYSICAL); (void) rm_rf(original_dir, REMOVE_ROOT|REMOVE_PHYSICAL); } @@ -364,9 +379,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 +473,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 +538,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-cpu-set-util.c b/src/test/test-cpu-set-util.c index bd22e4ddb2aef..027e0ca2d4756 100644 --- a/src/test/test-cpu-set-util.c +++ b/src/test/test-cpu-set-util.c @@ -11,7 +11,8 @@ #define ASSERT_CPUSET_COUNT(c, n) \ ASSERT_NOT_NULL(c.set); \ ASSERT_GE(c.allocated, CPU_ALLOC_SIZE(n)); \ - ASSERT_EQ(CPU_COUNT_S(c.allocated, c.set), (n)) + ASSERT_EQ(CPU_COUNT_S(c.allocated, c.set), (n)); \ + ASSERT_EQ(cpu_set_count(&c), (size_t) (n)) #define ASSERT_CPUSET_ISSET(c, i) \ ASSERT_TRUE(CPU_ISSET_S(i, c.allocated, c.set)); @@ -283,4 +284,11 @@ TEST(cpu_set_add_range) { ASSERT_OK(cpu_set_add_range(&c, 0, 8191)); } +TEST(cpus_online) { + unsigned n_cpus = 0; + ASSERT_OK(cpus_online(&n_cpus)); + ASSERT_GE(n_cpus, 1U); + log_info("Number of CPUs currently online: %u", n_cpus); +} + DEFINE_TEST_MAIN(LOG_DEBUG); 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 91% rename from src/test/test-openssl.c rename to src/test/test-crypto-util.c index a09484a2ba8ad..528482eb9d647 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; @@ -93,7 +93,9 @@ TEST(invalid) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; DEFINE_HEX_PTR(key, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b7b"); - assert_se(openssl_pubkey_from_pem(key, key_len, &pkey) == -EIO); + /* OpenSSL's decoder reports ERR_R_UNSUPPORTED for this garbage, which openssl_to_errno() maps + * to -EOPNOTSUPP. */ + ASSERT_ERROR(openssl_pubkey_from_pem(key, key_len, &pkey), EOPNOTSUPP); ASSERT_NULL(pkey); } @@ -101,9 +103,11 @@ static const struct { const char *alg; size_t size; } digest_size_table[] = { +#ifndef OPENSSL_NO_SHA1 /* SHA1 "family" */ { "sha1", 20, }, { "sha-1", 20, }, +#endif /* SHA2 family */ { "sha224", 28, }, { "sha256", 32, }, @@ -122,14 +126,18 @@ static const struct { { "sha3-256", 32, }, { "sha3-384", 48, }, { "sha3-512", 64, }, +#ifndef OPENSSL_NO_SM3 /* SM3 family */ { "sm3", 32, }, +#endif +#ifndef OPENSSL_NO_MD5 /* MD5 family */ { "md5", 16, }, +#endif }; TEST(digest_size) { - size_t size; + size_t size = 0; /* avoid false maybe-uninitialized warning */ FOREACH_ELEMENT(t, digest_size_table) { assert(openssl_digest_size(t->alg, &size) >= 0); @@ -465,4 +473,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-curl-util.c b/src/test/test-curl-util.c new file mode 100644 index 0000000000000..fb3d278200671 --- /dev/null +++ b/src/test/test-curl-util.c @@ -0,0 +1,280 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "alloc-util.h" +#include "curl-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "io-util.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +#define ASSERT_CURL_OK(expr) \ + ({ \ + CURLcode _code = (expr); \ + if (_code != CURLE_OK) \ + log_test_failed("Expected \"%s\" to be CURLE_OK, but got %d/%s",\ + #expr, (int) _code, sym_curl_easy_strerror(_code)); \ + }) + +/* Per-request context: the write callback appends bytes to ->body, and the + * on_finished callback stashes the CURLcode plus a "fired" flag. Each test + * uses one or more of these and cleans them up via context_done(). */ +typedef struct Context { + sd_event *event; + char *body; + size_t body_len; + bool finished; + CURLcode result; +} Context; + +static void context_done(Context *f) { + f->event = sd_event_unref(f->event); + f->body = mfree(f->body); +} + +static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userdata) { + Context *f = ASSERT_PTR(userdata); + size_t sz = size * nmemb; + + if (!GREEDY_REALLOC(f->body, f->body_len + sz + 1)) + return 0; + memcpy(f->body + f->body_len, contents, sz); + f->body[f->body_len + sz] = 0; + f->body_len += sz; + return sz; +} + +static int on_finished(CurlSlot *slot, CURL *curl, CURLcode code, void *userdata) { + Context *f = ASSERT_PTR(userdata); + + f->finished = true; + f->result = code; + + return sd_event_exit(f->event, 0); +} + +static int make_tmp_url(char **ret_path, char **ret_url, const char *body) { + const char *t; + ASSERT_OK(tmp_dir(&t)); + + _cleanup_(unlink_and_freep) char *path = ASSERT_NOT_NULL(strjoin(t, "/test-curl-util.XXXXXX")); + + _cleanup_close_ int fd = ASSERT_OK(mkostemp_safe(path)); + ASSERT_OK(loop_write(fd, body, strlen(body))); + + char *url = ASSERT_NOT_NULL(strjoin("file://", path)); + + *ret_url = url; + *ret_path = TAKE_PTR(path); + return 0; +} + +static int build_easy(const char *url, Context *f, CURL **ret) { + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + ASSERT_OK(curl_glue_make(&easy, url)); + + ASSERT_CURL_OK(sym_curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, write_callback)); + ASSERT_CURL_OK(sym_curl_easy_setopt(easy, CURLOPT_WRITEDATA, f)); + + *ret = TAKE_PTR(easy); + return 0; +} + +TEST(curl_glue_lifecycle) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_default(&event)); + + _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; + ASSERT_OK(curl_glue_new(&g, event)); + + /* ref/unref roundtrip */ + ASSERT_PTR_EQ(curl_glue_ref(g), g); + ASSERT_NULL(curl_glue_unref(g)); +} + +TEST(curl_glue_make) { + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + ASSERT_OK(curl_glue_make(&easy, "file:///dev/null")); + ASSERT_NOT_NULL(easy); +} + +TEST(curl_perform_floating) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_default(&event)); + + _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; + ASSERT_OK(curl_glue_new(&g, event)); + + _cleanup_(unlink_and_freep) char *path = NULL; + _cleanup_free_ char *url = NULL; + ASSERT_OK(make_tmp_url(&path, &url, "hello world")); + + _cleanup_(context_done) Context f = { .event = sd_event_ref(event) }; + + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + ASSERT_OK(build_easy(url, &f, &easy)); + + /* Floating: pass NULL for ret_slot. The glue owns the slot until completion. */ + ASSERT_OK(curl_glue_perform_async(g, easy, on_finished, &f, /* ret_slot= */ NULL)); + TAKE_PTR(easy); + + ASSERT_OK(sd_event_loop(event)); + + ASSERT_TRUE(f.finished); + ASSERT_CURL_OK(f.result); + ASSERT_STREQ(f.body, "hello world"); +} + +TEST(curl_perform_slot) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_default(&event)); + + _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; + ASSERT_OK(curl_glue_new(&g, event)); + + _cleanup_(unlink_and_freep) char *path = NULL; + _cleanup_free_ char *url = NULL; + ASSERT_OK(make_tmp_url(&path, &url, "slot test")); + + _cleanup_(context_done) Context f = { .event = sd_event_ref(event) }; + + _cleanup_(curl_easy_cleanupp) CURL *easy = NULL; + ASSERT_OK(build_easy(url, &f, &easy)); + + _cleanup_(curl_slot_unrefp) CurlSlot *slot = NULL; + ASSERT_OK(curl_glue_perform_async(g, easy, on_finished, &f, &slot)); + TAKE_PTR(easy); + + ASSERT_NOT_NULL(slot); + ASSERT_NOT_NULL(curl_slot_get_easy(slot)); + ASSERT_PTR_EQ(curl_slot_get_glue(slot), g); + + ASSERT_OK(sd_event_loop(event)); + + ASSERT_TRUE(f.finished); + ASSERT_CURL_OK(f.result); + ASSERT_STREQ(f.body, "slot test"); + + /* After completion, disconnect has cleared the slot's back-pointers; the slot itself + * is still alive because we hold a ref. Releasing it must be a clean no-op. */ + ASSERT_NULL(curl_slot_get_easy(slot)); + ASSERT_NULL(curl_slot_get_glue(slot)); +} + +TEST(curl_perform_cancel) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_default(&event)); + + _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; + ASSERT_OK(curl_glue_new(&g, event)); + + _cleanup_(unlink_and_freep) char *path = NULL; + _cleanup_free_ char *url = NULL; + ASSERT_OK(make_tmp_url(&path, &url, "payload")); + + /* Two requests: cancelled is unref'd before we run the loop; sentinel runs to + * completion and exits the loop. After the loop returns we know the dispatcher had + * an opportunity to fire any pending completion — so cancelled.finished staying false + * means our cancel actually prevented the callback from running, not just outraced it. */ + _cleanup_(context_done) Context cancelled = { .event = sd_event_ref(event) }; + _cleanup_(context_done) Context sentinel = { .event = sd_event_ref(event) }; + + _cleanup_(curl_easy_cleanupp) CURL *easy_cancelled = NULL, *easy_sentinel = NULL; + ASSERT_OK(build_easy(url, &cancelled, &easy_cancelled)); + ASSERT_OK(build_easy(url, &sentinel, &easy_sentinel)); + + _cleanup_(curl_slot_unrefp) CurlSlot *slot = NULL; + ASSERT_OK(curl_glue_perform_async(g, easy_cancelled, on_finished, &cancelled, &slot)); + TAKE_PTR(easy_cancelled); + + /* Cancel by dropping our only reference: removes the easy handle from the multi and + * cleans it up. The callback must not fire afterwards. */ + slot = curl_slot_unref(slot); + + /* The sentinel runs as floating; its callback will exit the loop on completion. */ + ASSERT_OK(curl_glue_perform_async(g, easy_sentinel, on_finished, &sentinel, /* ret_slot= */ NULL)); + TAKE_PTR(easy_sentinel); + + ASSERT_OK(sd_event_loop(event)); + + ASSERT_TRUE(sentinel.finished); + ASSERT_FALSE(cancelled.finished); +} + +typedef struct ConcurrentReq { + Context ctx; + const char *expected; + unsigned *remaining; +} ConcurrentReq; + +static int concurrent_on_finished(CurlSlot *slot, CURL *curl, CURLcode code, void *userdata) { + ConcurrentReq *cr = ASSERT_PTR(userdata); + + cr->ctx.finished = true; + cr->ctx.result = code; + + (*cr->remaining)--; + if (*cr->remaining == 0) + return sd_event_exit(cr->ctx.event, 0); + return 0; +} + +TEST(curl_concurrent) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + ASSERT_OK(sd_event_default(&event)); + + _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL; + ASSERT_OK(curl_glue_new(&g, event)); + + _cleanup_(unlink_and_freep) char *path_a = NULL, *path_b = NULL, *path_c = NULL; + _cleanup_free_ char *url_a = NULL, *url_b = NULL, *url_c = NULL; + ASSERT_OK(make_tmp_url(&path_a, &url_a, "alpha")); + ASSERT_OK(make_tmp_url(&path_b, &url_b, "bravo")); + ASSERT_OK(make_tmp_url(&path_c, &url_c, "charlie")); + + unsigned remaining = 3; + ConcurrentReq reqs[3] = { + { .ctx = { .event = sd_event_ref(event) }, .expected = "alpha", .remaining = &remaining }, + { .ctx = { .event = sd_event_ref(event) }, .expected = "bravo", .remaining = &remaining }, + { .ctx = { .event = sd_event_ref(event) }, .expected = "charlie", .remaining = &remaining }, + }; + + _cleanup_(curl_easy_cleanupp) CURL *ea = NULL, *eb = NULL, *ec = NULL; + ASSERT_OK(build_easy(url_a, &reqs[0].ctx, &ea)); + ASSERT_OK(build_easy(url_b, &reqs[1].ctx, &eb)); + ASSERT_OK(build_easy(url_c, &reqs[2].ctx, &ec)); + + /* All three fire as floating slots; the only way the loop exits is through the + * remaining-counter hitting zero, which means every callback fired with the right + * userdata routed to its respective body. */ + ASSERT_OK(curl_glue_perform_async(g, ea, concurrent_on_finished, &reqs[0], NULL)); + TAKE_PTR(ea); + ASSERT_OK(curl_glue_perform_async(g, eb, concurrent_on_finished, &reqs[1], NULL)); + TAKE_PTR(eb); + ASSERT_OK(curl_glue_perform_async(g, ec, concurrent_on_finished, &reqs[2], NULL)); + TAKE_PTR(ec); + + ASSERT_OK(sd_event_loop(event)); + + ASSERT_EQ(remaining, 0u); + + FOREACH_ARRAY(r, reqs, ELEMENTSOF(reqs)) { + ASSERT_TRUE(r->ctx.finished); + ASSERT_CURL_OK(r->ctx.result); + ASSERT_STREQ(r->ctx.body, r->expected); + context_done(&r->ctx); + } +} + +static int intro(void) { + if (dlopen_curl(LOG_DEBUG) < 0) + return log_tests_skipped("libcurl not available"); + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index 4b805326982aa..de1d7671392e8 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -3,18 +3,24 @@ #include "acl-util.h" #include "apparmor-util.h" #include "blkid-util.h" -#include "bpf-dlopen.h" +#include "bpf-util.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" #include "libcrypt-util.h" #include "libfido2-util.h" #include "libmount-util.h" +#include "locale-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 +30,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 +49,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); @@ -55,19 +66,24 @@ static int run(int argc, char **argv) { ASSERT_DLOPEN(dlopen_libblkid, HAVE_BLKID); ASSERT_DLOPEN(dlopen_libcrypt, HAVE_LIBCRYPT); ASSERT_DLOPEN(dlopen_libfido2, HAVE_LIBFIDO2); + ASSERT_OK(dlopen_libintl(LOG_DEBUG)); /* Required to be available at build time. */ ASSERT_DLOPEN(dlopen_libkmod, HAVE_KMOD); ASSERT_DLOPEN(dlopen_libmount, HAVE_LIBMOUNT); 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..5a6a1a4204406 100644 --- a/src/test/test-engine.c +++ b/src/test/test-engine.c @@ -97,7 +97,7 @@ int main(int argc, char *argv[]) { if (manager_errno_skip_test(r)) return log_tests_skipped_errno(r, "manager_new"); assert_se(r >= 0); - assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL, NULL) >= 0); printf("Load1:\n"); assert_se(manager_load_startable_unit_or_warn(m, "a.service", NULL, &a) >= 0); @@ -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-escape.c b/src/test/test-escape.c index 7162f7ff2dc3c..1ab5b61eb6e07 100644 --- a/src/test/test-escape.c +++ b/src/test/test-escape.c @@ -63,6 +63,15 @@ TEST(xescape_full) { test_xescape_full_one(true); } +TEST(xescape_full_ellipsis) { + _cleanup_free_ char *t = NULL; + + /* Every byte escapes to four columns, so the escaped output fills strlen*4 bytes and the + * forced ellipsis used to be written past the end of the buffer. */ + assert_se(t = xescape_full("\001\001\001", /* bad= */ NULL, 80, XESCAPE_FORCE_ELLIPSIS)); + ASSERT_STREQ(t, "\\x01\\x01\\x01..."); +} + TEST(cunescape) { _cleanup_free_ char *unescaped = NULL; 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..1a832ad2f711f 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -78,20 +78,29 @@ static void wait_for_service_finish(Manager *m, Unit *unit) { ASSERT_NOT_NULL(m); - /* Bump the timeout when running in plain QEMU, as some more involved tests might start hitting the - * default 2m timeout (like exec-dynamicuser-statedir.service) */ - if (detect_virtualization() == VIRTUALIZATION_QEMU) + /* Bump the timeout when running in plain QEMU or in CI, as some more involved tests might start + * hitting the default 2m timeout (like exec-dynamicuser-statedir.service). */ + if (detect_virtualization() == VIRTUALIZATION_QEMU || ci_environment()) timeout *= 2; printf("%s\n", unit->id); exec_context_dump(&service->exec_context, stdout, "\t"); + /* Use a per-Exec timeout rather than a service timeout, as especially under sanitizers some test + * units running many commands can hit the service timeout. */ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; ASSERT_OK(sd_event_add_time_relative(m->event, &s, CLOCK_MONOTONIC, timeout, 0, time_handler, unit)); /* Here, sd_event_loop() cannot be used, as the sd_event object will be reused in the next test case. */ - while (!IN_SET(service->state, SERVICE_DEAD, SERVICE_FAILED)) + ExecCommand *last_command = service->main_command; + while (!IN_SET(service->state, SERVICE_DEAD, SERVICE_FAILED)) { ASSERT_OK(sd_event_run(m->event, 100 * USEC_PER_MSEC)); + + if (service->main_command != last_command) { + last_command = service->main_command; + ASSERT_OK(sd_event_source_set_time_relative(s, timeout)); + } + } } static void check_main_result(const char *file, unsigned line, const char *func, @@ -1027,11 +1036,6 @@ static void test_exec_dynamicuser(Manager *m) { return; } - if (strstr_ptr(ci_environment(), "github-actions")) { - log_notice("%s: skipping test on GH Actions because of systemd/systemd#10337", __func__); - return; - } - int status = can_unshare ? 0 : EXIT_NAMESPACE; test(m, "exec-dynamicuser-fixeduser.service", status, CLD_EXITED); @@ -1435,7 +1439,7 @@ static void run_tests(RuntimeScope scope, char **patterns) { ASSERT_OK(r); m->defaults.std_output = EXEC_OUTPUT_INHERIT; /* don't rely on host journald */ - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); /* Uncomment below if you want to make debugging logs stored to journal. */ //manager_override_log_target(m, LOG_TARGET_AUTO); @@ -1616,10 +1620,6 @@ TEST(run_tests_unprivileged) { static int intro(void) { int r; -#if HAS_FEATURE_ADDRESS_SANITIZER - if (strstr_ptr(ci_environment(), "travis") || strstr_ptr(ci_environment(), "github-actions")) - return log_tests_skipped("Running on Travis CI/GH Actions under ASan, see https://github.com/systemd/systemd/issues/10696"); -#endif /* It is needed otherwise cgroup creation fails */ if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) return log_tests_skipped("not privileged"); @@ -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-fdstore.c b/src/test/test-fdstore.c new file mode 100644 index 0000000000000..0d7c16faf7d36 --- /dev/null +++ b/src/test/test-fdstore.c @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* In 'store' mode pushes a couple of memfds with known content into the supervisor's fd store via FDSTORE=1 + * sd_notify() messages. In 'check' mode reads back the fds passed via LISTEN_FDS and verifies the content + * matches what was pushed. + * + * This binary is intentionally linked against libsystemd only so that it can go in the minimal image. */ + +#include +#include +#include +#include +#include +#include +#include + +#include "sd-daemon.h" + +#define DATA_A "fdstore-data-a" +#define DATA_B "fdstore-data-b" + +#define _cleanup_(f) __attribute__((cleanup(f))) + +static void closep(int *fd) { + if (!fd || *fd < 0) + return; + + close(*fd); + *fd = -EBADF; +} + +static _Noreturn void exit_handler(int sig) { + _exit(EXIT_SUCCESS); +} + +static int push_one(const char *fdname, const char *content) { + _cleanup_(closep) int fd = -EBADF; + int r; + + assert(fdname); + assert(content); + + fd = memfd_create(fdname, MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd < 0) { + fprintf(stderr, "memfd_create(%s) failed: %m\n", fdname); + return -errno; + } + + size_t len = strlen(content); + if (write(fd, content, len) != (ssize_t) len) { + fprintf(stderr, "write(%s) failed: %m\n", fdname); + return -errno; + } + + char msg[256]; + r = snprintf(msg, sizeof(msg), "FDSTORE=1\nFDNAME=%s", fdname); + if (r < 0 || (size_t) r >= sizeof(msg)) { + if (r >= 0) + errno = ENOBUFS; + fprintf(stderr, "FDSTORE message for fdname=%s did not fit in buffer\n", fdname); + return -errno; + } + + r = sd_pid_notify_with_fds(0, /* unset_environment= */ 0, msg, &fd, 1); + if (r < 0) { + errno = -r; + fprintf(stderr, "sd_pid_notify_with_fds(%s) failed: %m\n", fdname); + return r; + } + if (r == 0) { + fprintf(stderr, "NOTIFY_SOCKET not set\n"); + return -ENOENT; + } + + return 0; +} + +static int do_store(void) { + int r; + + if (push_one("test-fd-a", DATA_A) < 0) + return EXIT_FAILURE; + + if (push_one("test-fd-b", DATA_B) < 0) + return EXIT_FAILURE; + + /* Wait for our supervisor to actually process the FDSTORE messages before we exit, otherwise + * the cgroup-based pidref to unit lookup may fail once we're gone. */ + r = sd_notify_barrier(0, 5 * 1000 * 1000); + if (r < 0) { + errno = -r; + fprintf(stderr, "sd_notify_barrier failed: %m\n"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +static int do_check(void) { + bool seen_a = false, seen_b = false; + int n; + + n = sd_listen_fds(/* unset_environment= */ 0); + if (n < 0) { + errno = -n; + fprintf(stderr, "sd_listen_fds failed: %m\n"); + return EXIT_FAILURE; + } + if (n < 2) { + fprintf(stderr, "Expected at least 2 fds via LISTEN_FDS, got %d\n", n); + return EXIT_FAILURE; + } + + for (int i = 0; i < n; i++) { + int fd = SD_LISTEN_FDS_START + i; + char buf[256] = {}; + ssize_t k; + + if (lseek(fd, 0, SEEK_SET) < 0) { + fprintf(stderr, "lseek(fd=%d) failed: %m\n", fd); + return EXIT_FAILURE; + } + k = read(fd, buf, sizeof(buf) - 1); + if (k < 0) { + fprintf(stderr, "read(fd=%d) failed: %m\n", fd); + return EXIT_FAILURE; + } + buf[k] = 0; + + if (strcmp(buf, DATA_A) == 0) + seen_a = true; + else if (strcmp(buf, DATA_B) == 0) + seen_b = true; + else + fprintf(stderr, "Unexpected fd content: '%s'\n", buf); + } + + if (!seen_a || !seen_b) { + fprintf(stderr, "Missing expected fds: seen_a=%d seen_b=%d\n", seen_a, seen_b); + return EXIT_FAILURE; + } + + printf("Payload received both preserved fds with matching content.\n"); + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) { + int r; + + if (argc < 2) { + fprintf(stderr, "Usage: %s store|check\n", argv[0]); + return EXIT_FAILURE; + } + + if (strcmp(argv[1], "store") == 0) + r = do_store(); + else if (strcmp(argv[1], "check") == 0) + r = do_check(); + else { + fprintf(stderr, "Unknown verb: %s\n", argv[1]); + return EXIT_FAILURE; + } + + if (r != EXIT_SUCCESS) + return r; + + /* On success, stay alive so if we are a container payload we keep it running. Install handlers + * for the signals an outer supervisor may use to terminate us, so we exit cleanly (with status 0) + * and the container service ends up in 'inactive' rather than 'failed'. */ + struct sigaction sa = { .sa_handler = exit_handler }; + if (sigaction(SIGTERM, &sa, /* __oact= */ NULL) < 0 || + sigaction(SIGINT, &sa, /* __oact= */ NULL) < 0) { + fprintf(stderr, "Failed to install signal handlers: %m\n"); + return EXIT_FAILURE; + } + + for (;;) + pause(); +} diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index 38d92299467a7..1be693bea221f 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,199 @@ TEST(fdopen_independent) { f = safe_fclose(f); } +TEST(write_data_file_atomic_at) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + int r; + + ASSERT_OK(mkdtemp_malloc("/tmp/test-wdfaa-XXXXXX", &t)); + ASSERT_TRUE(path_is_absolute(t)); + + const char *abs_wdfa = strjoina(t, "/wdfa"); + const char *rel_wdfa = abs_wdfa + 1; /* path relative to / */ + const char *abs_zzz = strjoina(t, "/zzz"); + const char *abs_zzz_wdfa = strjoina(t, "/zzz/wdfa"); + const char *rel_zzz_wdfa = abs_zzz_wdfa + 1; + + struct iovec a = IOVEC_MAKE_STRING("hallo"); + ASSERT_OK(write_data_file_atomic_at(AT_FDCWD, abs_wdfa, &a, /* flags= */ 0)); + + _cleanup_(iovec_done) struct iovec ra = {}; + ASSERT_OK(read_full_file(abs_wdfa, (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_TRUE(iovec_equal(&a, &ra)); + ASSERT_OK_ERRNO(unlink(abs_wdfa)); + + ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, rel_wdfa, &a, /* flags= */ 0)); + iovec_done(&ra); + ASSERT_OK(read_full_file(abs_wdfa, (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_TRUE(iovec_equal(&a, &ra)); + ASSERT_OK_ERRNO(unlink(abs_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, strjoina(t, "/"), &a, /* flags= */ 0), EISDIR); + + _cleanup_free_ char *cwd = NULL; + ASSERT_OK(safe_getcwd(&cwd)); + ASSERT_OK_ERRNO(chdir(t)); + + ASSERT_OK(write_data_file_atomic_at(AT_FDCWD, "wdfa", &a, /* flags= */ 0)); + iovec_done(&ra); + ASSERT_OK(read_full_file(abs_wdfa, (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_TRUE(iovec_equal(&a, &ra)); + ASSERT_OK_ERRNO(unlink(abs_wdfa)); + + ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, rel_wdfa, &a, /* flags= */ 0)); + iovec_done(&ra); + ASSERT_OK(read_full_file(abs_wdfa, (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_TRUE(iovec_equal(&a, &ra)); + ASSERT_OK_ERRNO(unlink(abs_wdfa)); + + ASSERT_OK_ERRNO(chdir(cwd)); + + ASSERT_ERROR(write_data_file_atomic_at(XAT_FDROOT, rel_zzz_wdfa, &a, /* flags= */ 0), ENOENT); + ASSERT_OK(write_data_file_atomic_at(XAT_FDROOT, rel_zzz_wdfa, &a, WRITE_DATA_FILE_MKDIR_0755)); + iovec_done(&ra); + ASSERT_OK(read_full_file(abs_zzz_wdfa, (char**) &ra.iov_base, &ra.iov_len)); + ASSERT_TRUE(iovec_equal(&a, &ra)); + ASSERT_OK_ERRNO(unlink(abs_zzz_wdfa)); + + r = write_data_file_atomic_at(AT_FDCWD, abs_zzz, &a, /* flags= */ 0); + /* In Gitlab CI this fails with EISDIR */ + ASSERT_TRUE(IN_SET(r, -EEXIST, -EISDIR)); + + ASSERT_OK_ERRNO(rmdir(abs_zzz)); +} + +TEST(read_one_line_file_at_xat_fdroot) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_free_ char *fn = NULL, *buf = NULL; + + ASSERT_OK(mkdtemp_malloc("/tmp/test-r1lf-xatfd-XXXXXX", &t)); + ASSERT_TRUE(path_is_absolute(t)); + + ASSERT_NOT_NULL(fn = path_join(t, "hello")); + ASSERT_OK(write_string_file(fn, "first line\nsecond line", WRITE_STRING_FILE_CREATE)); + + /* XAT_FDROOT is supposed to root the path at the host's "/"; the implementation prepends a "/" so + * we pass the path without leading slash. */ + ASSERT_OK_EQ(read_one_line_file_at(XAT_FDROOT, fn + 1, &buf), (int) STRLEN("first line\n")); + ASSERT_STREQ(buf, "first line"); + buf = mfree(buf); + + /* Sanity check: AT_FDCWD with the absolute path gives the same result. */ + ASSERT_OK_EQ(read_one_line_file_at(AT_FDCWD, fn, &buf), (int) STRLEN("first line\n")); + ASSERT_STREQ(buf, "first line"); + buf = mfree(buf); + + /* /proc/version should always be readable via XAT_FDROOT (some build envs may restrict it; tolerate + * that). */ + int r = read_one_line_file_at(XAT_FDROOT, "proc/version", &buf); + if (!ERRNO_IS_NEG_PRIVILEGE(r)) { + ASSERT_OK(r); + ASSERT_FALSE(isempty(buf)); + buf = mfree(buf); + } + + /* Non-existent path through XAT_FDROOT should yield -ENOENT. */ + ASSERT_ERROR(read_one_line_file_at(XAT_FDROOT, "tmp/this/path/really/should/not/exist", &buf), ENOENT); + + /* Now create a Unix socket in the same temp dir, and verify that read_one_line_file_at() returns + * -ENXIO when pointed at it via XAT_FDROOT — read_one_line_file_at() does not enable the socket + * fallback. */ + _cleanup_free_ char *sockpath = NULL; + ASSERT_NOT_NULL(sockpath = path_join(t, "socket")); + + _cleanup_close_ int listener = -EBADF; + ASSERT_OK(listener = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, /* protocol= */ 0)); + + union sockaddr_union sa; + ASSERT_OK(sockaddr_un_set_path(&sa.un, sockpath)); + ASSERT_OK_ERRNO(bind(listener, &sa.sa, sockaddr_un_len(&sa.un))); + ASSERT_OK_ERRNO(listen(listener, 1)); + + ASSERT_ERROR(read_one_line_file_at(XAT_FDROOT, sockpath + 1, &buf), ENXIO); + + /* But read_full_file_full() with READ_FULL_FILE_CONNECT_SOCKET *does* enable the socket fallback, + * which routes through xfopenat_unix_socket() and connect_unix_path() — both now teach the + * XAT_FDROOT codepath. Use that to exercise the socket open via XAT_FDROOT. */ + static const char test_sock_str[] = "hello via xat_fdroot socket\n"; + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int pr = ASSERT_OK(pidref_safe_fork("(server)", FORK_DEATHSIG_SIGTERM|FORK_LOG, &pidref)); + if (pr == 0) { + _cleanup_close_ int rfd = -EBADF; + ASSERT_OK(rfd = accept4(listener, /* addr= */ NULL, /* addrlen= */ NULL, SOCK_CLOEXEC)); + ASSERT_OK_EQ_ERRNO(write(rfd, test_sock_str, sizeof(test_sock_str) - 1), + (ssize_t) sizeof(test_sock_str) - 1); + _exit(EXIT_SUCCESS); + } + + _cleanup_free_ char *data = NULL; + size_t size; + ASSERT_OK(read_full_file_full(XAT_FDROOT, sockpath + 1, + /* offset= */ UINT64_MAX, /* size= */ SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, /* bind_name= */ NULL, + &data, &size)); + ASSERT_EQ(size, sizeof(test_sock_str) - 1); + ASSERT_STREQ(data, test_sock_str); + + ASSERT_OK(pidref_wait_for_terminate_and_check("(server)", &pidref, WAIT_LOG)); +} + +TEST(read_boolean_file) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-read-boolean-file-XXXXXX"; + _cleanup_close_ int fd = -EBADF, dfd = -EBADF; + const char *rel; + + ASSERT_OK(fd = mkostemp_safe(fn)); + + ASSERT_OK(write_string_file(fn, "yes", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file(fn), true); + ASSERT_OK_EQ(read_boolean_file_at(AT_FDCWD, fn), true); + + ASSERT_OK(write_string_file(fn, "0", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file(fn), false); + ASSERT_OK_EQ(read_boolean_file_at(AT_FDCWD, fn), false); + + ASSERT_OK(write_string_file(fn, "true\nignored\n", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file(fn), true); + + ASSERT_OK(write_string_file(fn, "garbage", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_ERROR(read_boolean_file(fn), EINVAL); + + ASSERT_ERROR(read_boolean_file("/tmp/this-file-better-not-exist-XXX"), ENOENT); + + /* Now test XAT_FDROOT: filename is relative, looked up against "/" */ + ASSERT_TRUE(path_startswith(fn, "/")); + rel = fn + 1; + + ASSERT_OK(write_string_file(fn, "on", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file_at(XAT_FDROOT, rel), true); + + ASSERT_OK(write_string_file(fn, "off", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file_at(XAT_FDROOT, rel), false); + + ASSERT_ERROR(read_boolean_file_at(XAT_FDROOT, "tmp/this-file-better-not-exist-XXX"), ENOENT); + + /* And confirm XAT_FDROOT ignores the cwd: chdir somewhere unrelated, then look up + * the same relative-to-/ path. */ + _cleanup_free_ char *cwd = NULL; + ASSERT_OK(safe_getcwd(&cwd)); + ASSERT_OK_ERRNO(chdir("/usr")); + + ASSERT_OK(write_string_file(fn, "yes", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file_at(XAT_FDROOT, rel), true); + + ASSERT_OK_ERRNO(chdir(cwd)); + + /* Also test the dir_fd >= 0 path using an actual fd for /tmp. */ + ASSERT_OK(dfd = open("/tmp", O_DIRECTORY|O_CLOEXEC)); + ASSERT_OK(write_string_file(fn, "1", WRITE_STRING_FILE_TRUNCATE)); + _cleanup_free_ char *bn = NULL; + ASSERT_OK(path_extract_filename(fn, &bn)); + ASSERT_OK_EQ(read_boolean_file_at(dfd, bn), true); +} + 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..6fea526d67f73 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "argv-util.h" +#include "capability-util.h" #include "copy.h" #include "fd-util.h" #include "fs-util.h" @@ -16,6 +17,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 +742,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, or as a user with CAP_DAC_OVERRIDE. */ + + if (have_effective_cap(CAP_DAC_OVERRIDE) <= 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 +1016,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-fsprg.c b/src/test/test-fsprg.c new file mode 100644 index 0000000000000..94183e2246202 --- /dev/null +++ b/src/test/test-fsprg.c @@ -0,0 +1,511 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#if HAVE_VALGRIND_VALGRIND_H +# include +#endif + +#include "crypto-util.h" +#include "fsprg.h" +#include "fsprg-openssl.h" +#include "gcrypt-util.h" +#include "iovec-util.h" +#include "journal-def.h" +#include "stdio-util.h" +#include "tests.h" + +#define STATE_LENGTH (2 + 2 * FSPRG_RECOMMENDED_SECPAR / 8 + 8) +#define KEY_LENGTH 32 + +static const uint8_t expected_state[4][STATE_LENGTH] = { + { + 0x00, 0x5f, 0xcd, 0x65, 0x12, 0x14, 0xa6, 0x15, + 0xe4, 0xd3, 0xc9, 0xe7, 0x5c, 0x70, 0x2c, 0x37, + 0xca, 0xa7, 0x21, 0xf4, 0x40, 0xf3, 0x6a, 0x88, + 0xe3, 0xce, 0x85, 0x9b, 0xe4, 0x8f, 0x90, 0x8c, + 0x4a, 0xad, 0xf4, 0xcf, 0x99, 0xcf, 0xdc, 0x3b, + 0x29, 0xad, 0x59, 0x72, 0x05, 0xa3, 0xca, 0x3e, + 0xf9, 0x72, 0x9f, 0x7c, 0xb6, 0xa4, 0xd6, 0x86, + 0xb4, 0x64, 0x5a, 0xc4, 0xe0, 0x95, 0x98, 0xb7, + 0xdf, 0xc5, 0x79, 0x8a, 0x48, 0xff, 0xd6, 0x71, + 0xab, 0x0f, 0x17, 0x54, 0xdd, 0x3d, 0xd0, 0x23, + 0x18, 0xc9, 0x4c, 0xf4, 0x40, 0xcf, 0x34, 0xc9, + 0x33, 0x04, 0x35, 0x4b, 0x2c, 0xd9, 0xbd, 0xcf, + 0x59, 0x62, 0x18, 0x2d, 0x9e, 0x5a, 0x11, 0x71, + 0x4c, 0x1f, 0x78, 0x0e, 0x65, 0x75, 0xe6, 0x15, + 0x2f, 0x61, 0x32, 0x3c, 0xac, 0xaa, 0xa8, 0x6a, + 0x1d, 0x11, 0xb4, 0x91, 0x38, 0xd3, 0x34, 0x31, + 0xd7, 0xfa, 0xff, 0xef, 0xbf, 0xb9, 0x90, 0x4f, + 0x0d, 0x29, 0x16, 0xb6, 0x64, 0x48, 0x5b, 0x7b, + 0x00, 0x42, 0x7a, 0xab, 0x2b, 0xe7, 0xee, 0x9b, + 0x5d, 0x0c, 0xe9, 0x14, 0x74, 0x72, 0x29, 0xd9, + 0x10, 0xa5, 0xd5, 0x95, 0xbb, 0x9f, 0x85, 0xc0, + 0xa2, 0xfa, 0xbb, 0x84, 0xf5, 0x59, 0x11, 0xdb, + 0xf4, 0xce, 0x08, 0xe4, 0x36, 0x1c, 0xeb, 0xc9, + 0xa6, 0xcd, 0xdb, 0xf2, 0x10, 0xab, 0x51, 0xb2, + 0x8c, 0x9d, 0xc9, 0xf8, 0x35, 0xe1, 0xec, 0x25, + 0xd5, 0xca, 0x98, 0x7a, 0x4f, 0xbd, 0x45, 0x50, + 0xbf, 0xff, 0x1c, 0xd8, 0x42, 0x95, 0xcb, 0xc5, + 0xa7, 0x0f, 0x75, 0xda, 0x6d, 0x5b, 0xa2, 0xeb, + 0x5b, 0x26, 0x9e, 0xe8, 0xa3, 0x23, 0x04, 0x4b, + 0x5b, 0x26, 0xb7, 0xe4, 0x7f, 0xcd, 0x82, 0x66, + 0x23, 0x7d, 0xf8, 0x57, 0x2c, 0x22, 0x0b, 0x7e, + 0x87, 0x3f, 0xe1, 0xc1, 0x1a, 0xea, 0xc3, 0x42, + 0x40, 0x03, 0xaf, 0x11, 0xd5, 0x07, 0xa1, 0x79, + 0x55, 0x34, 0x4a, 0x41, 0x6a, 0x75, 0x68, 0x13, + 0x37, 0xfd, 0x38, 0x97, 0x52, 0x4f, 0xbb, 0x68, + 0xb4, 0x9f, 0xa8, 0xda, 0x9c, 0x4c, 0x8e, 0x65, + 0x3e, 0x42, 0x19, 0x40, 0x5f, 0x47, 0xe8, 0x50, + 0x0c, 0xb3, 0xc5, 0x6e, 0x0e, 0xe6, 0x6b, 0xe7, + 0xb9, 0x1f, 0xe0, 0x66, 0x2a, 0x38, 0x8e, 0x38, + 0xa8, 0x7f, 0xfd, 0x2c, 0x92, 0xf8, 0xf2, 0x91, + 0x36, 0x90, 0xfd, 0x14, 0x1d, 0x07, 0xae, 0x71, + 0x7f, 0x3c, 0xaa, 0xd6, 0xc4, 0x10, 0x20, 0xe5, + 0xd0, 0xc2, 0xa2, 0xe7, 0x5e, 0x36, 0x91, 0xf7, + 0xff, 0x27, 0x4e, 0x81, 0xc0, 0x85, 0xab, 0x0a, + 0x46, 0x88, 0xe7, 0x32, 0xce, 0x83, 0x4a, 0x09, + 0x72, 0xb3, 0xae, 0xdd, 0x65, 0xf8, 0x13, 0xd0, + 0x9f, 0x4f, 0x45, 0x83, 0xfa, 0x41, 0x1f, 0x8d, + 0xfe, 0x96, 0xbd, 0xe0, 0x32, 0x5c, 0xb9, 0x87, + 0xd8, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + }, + { + 0x00, 0x5f, 0xcd, 0x65, 0x12, 0x14, 0xa6, 0x15, + 0xe4, 0xd3, 0xc9, 0xe7, 0x5c, 0x70, 0x2c, 0x37, + 0xca, 0xa7, 0x21, 0xf4, 0x40, 0xf3, 0x6a, 0x88, + 0xe3, 0xce, 0x85, 0x9b, 0xe4, 0x8f, 0x90, 0x8c, + 0x4a, 0xad, 0xf4, 0xcf, 0x99, 0xcf, 0xdc, 0x3b, + 0x29, 0xad, 0x59, 0x72, 0x05, 0xa3, 0xca, 0x3e, + 0xf9, 0x72, 0x9f, 0x7c, 0xb6, 0xa4, 0xd6, 0x86, + 0xb4, 0x64, 0x5a, 0xc4, 0xe0, 0x95, 0x98, 0xb7, + 0xdf, 0xc5, 0x79, 0x8a, 0x48, 0xff, 0xd6, 0x71, + 0xab, 0x0f, 0x17, 0x54, 0xdd, 0x3d, 0xd0, 0x23, + 0x18, 0xc9, 0x4c, 0xf4, 0x40, 0xcf, 0x34, 0xc9, + 0x33, 0x04, 0x35, 0x4b, 0x2c, 0xd9, 0xbd, 0xcf, + 0x59, 0x62, 0x18, 0x2d, 0x9e, 0x5a, 0x11, 0x71, + 0x4c, 0x1f, 0x78, 0x0e, 0x65, 0x75, 0xe6, 0x15, + 0x2f, 0x61, 0x32, 0x3c, 0xac, 0xaa, 0xa8, 0x6a, + 0x1d, 0x11, 0xb4, 0x91, 0x38, 0xd3, 0x34, 0x31, + 0xd7, 0xfa, 0xff, 0xef, 0xbf, 0xb9, 0x90, 0x4f, + 0x0d, 0x29, 0x16, 0xb6, 0x64, 0x48, 0x5b, 0x7b, + 0x00, 0x42, 0x7a, 0xab, 0x2b, 0xe7, 0xee, 0x9b, + 0x5d, 0x0c, 0xe9, 0x14, 0x74, 0x72, 0x29, 0xd9, + 0x10, 0xa5, 0xd5, 0x95, 0xbb, 0x9f, 0x85, 0xc0, + 0xa2, 0xfa, 0xbb, 0x84, 0xf5, 0x59, 0x11, 0xdb, + 0xf4, 0xce, 0x08, 0xe4, 0x36, 0x1c, 0xeb, 0xc9, + 0xa6, 0xcd, 0xdb, 0xf2, 0x10, 0xab, 0x51, 0xb2, + 0x8c, 0x9d, 0x4a, 0x62, 0x5d, 0xd1, 0x34, 0x5b, + 0x56, 0x44, 0x04, 0x10, 0x9e, 0x43, 0x7f, 0x62, + 0x18, 0x1f, 0x5d, 0x68, 0x8f, 0xa8, 0x26, 0xab, + 0xd4, 0xf9, 0xa6, 0xef, 0xfa, 0x3f, 0x41, 0x09, + 0xae, 0x44, 0x8f, 0x9e, 0xd3, 0x4c, 0xc5, 0x7b, + 0x35, 0x50, 0x7f, 0xc0, 0xba, 0xd9, 0x42, 0xe9, + 0x89, 0x9b, 0x4f, 0x30, 0x40, 0x02, 0x96, 0xed, + 0x7c, 0x67, 0x7e, 0xab, 0x26, 0x3b, 0xc9, 0x65, + 0xf6, 0x16, 0x9b, 0x7e, 0x28, 0xf2, 0xed, 0xb9, + 0xfd, 0x9d, 0xe4, 0x68, 0x67, 0x65, 0xf2, 0x5d, + 0x54, 0x43, 0xd5, 0x2d, 0xfd, 0xf4, 0x2d, 0xfd, + 0x49, 0x00, 0xca, 0x35, 0xc9, 0x87, 0x7c, 0xd4, + 0xfe, 0xfb, 0x9d, 0x8d, 0xda, 0x1c, 0x54, 0xc2, + 0x26, 0xed, 0xe5, 0xaa, 0xde, 0xa9, 0x41, 0x88, + 0x88, 0xec, 0x17, 0xab, 0x12, 0x00, 0xc4, 0x71, + 0x8f, 0xe4, 0x5a, 0xf9, 0x55, 0xe6, 0xd9, 0x56, + 0x45, 0x86, 0x6a, 0x1c, 0xd1, 0x09, 0xb1, 0xfc, + 0x0d, 0xd0, 0x15, 0x2c, 0xa1, 0xb6, 0xe9, 0x95, + 0x01, 0xe1, 0x71, 0xb3, 0xb7, 0xbf, 0x57, 0xd5, + 0x15, 0xec, 0xe7, 0xde, 0x85, 0x90, 0xb2, 0x47, + 0x51, 0x0f, 0x0f, 0x9b, 0xcf, 0x21, 0x0c, 0x77, + 0x21, 0xe7, 0xbb, 0x44, 0xbb, 0x70, 0xbf, 0xfb, + 0x29, 0x58, 0x50, 0xdc, 0x01, 0xfb, 0x2b, 0xdf, + 0x8d, 0x74, 0xc5, 0x89, 0xe1, 0x29, 0xcd, 0x97, + 0xe5, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, + }, + { + 0x00, 0x5f, 0xcd, 0x65, 0x12, 0x14, 0xa6, 0x15, + 0xe4, 0xd3, 0xc9, 0xe7, 0x5c, 0x70, 0x2c, 0x37, + 0xca, 0xa7, 0x21, 0xf4, 0x40, 0xf3, 0x6a, 0x88, + 0xe3, 0xce, 0x85, 0x9b, 0xe4, 0x8f, 0x90, 0x8c, + 0x4a, 0xad, 0xf4, 0xcf, 0x99, 0xcf, 0xdc, 0x3b, + 0x29, 0xad, 0x59, 0x72, 0x05, 0xa3, 0xca, 0x3e, + 0xf9, 0x72, 0x9f, 0x7c, 0xb6, 0xa4, 0xd6, 0x86, + 0xb4, 0x64, 0x5a, 0xc4, 0xe0, 0x95, 0x98, 0xb7, + 0xdf, 0xc5, 0x79, 0x8a, 0x48, 0xff, 0xd6, 0x71, + 0xab, 0x0f, 0x17, 0x54, 0xdd, 0x3d, 0xd0, 0x23, + 0x18, 0xc9, 0x4c, 0xf4, 0x40, 0xcf, 0x34, 0xc9, + 0x33, 0x04, 0x35, 0x4b, 0x2c, 0xd9, 0xbd, 0xcf, + 0x59, 0x62, 0x18, 0x2d, 0x9e, 0x5a, 0x11, 0x71, + 0x4c, 0x1f, 0x78, 0x0e, 0x65, 0x75, 0xe6, 0x15, + 0x2f, 0x61, 0x32, 0x3c, 0xac, 0xaa, 0xa8, 0x6a, + 0x1d, 0x11, 0xb4, 0x91, 0x38, 0xd3, 0x34, 0x31, + 0xd7, 0xfa, 0xff, 0xef, 0xbf, 0xb9, 0x90, 0x4f, + 0x0d, 0x29, 0x16, 0xb6, 0x64, 0x48, 0x5b, 0x7b, + 0x00, 0x42, 0x7a, 0xab, 0x2b, 0xe7, 0xee, 0x9b, + 0x5d, 0x0c, 0xe9, 0x14, 0x74, 0x72, 0x29, 0xd9, + 0x10, 0xa5, 0xd5, 0x95, 0xbb, 0x9f, 0x85, 0xc0, + 0xa2, 0xfa, 0xbb, 0x84, 0xf5, 0x59, 0x11, 0xdb, + 0xf4, 0xce, 0x08, 0xe4, 0x36, 0x1c, 0xeb, 0xc9, + 0xa6, 0xcd, 0xdb, 0xf2, 0x10, 0xab, 0x51, 0xb2, + 0x8c, 0x9d, 0x57, 0xb9, 0x1b, 0x5d, 0x82, 0x02, + 0x77, 0x8c, 0x96, 0x3e, 0x36, 0x0c, 0x17, 0x8f, + 0x61, 0x3b, 0x3f, 0x17, 0x7d, 0x1d, 0xa9, 0x03, + 0xf8, 0x57, 0x4b, 0xf7, 0x48, 0xb8, 0x65, 0xfc, + 0xbb, 0xd4, 0x2b, 0x19, 0x06, 0xa3, 0x82, 0x61, + 0xe0, 0x21, 0x62, 0x31, 0xb4, 0xd2, 0x5b, 0xf0, + 0x4b, 0x21, 0xcd, 0xca, 0x84, 0x84, 0xc0, 0x1b, + 0x55, 0x4d, 0xf0, 0x32, 0xc9, 0x37, 0x57, 0xd9, + 0x63, 0x1b, 0x00, 0x65, 0x61, 0x93, 0x1d, 0xa4, + 0xb0, 0x86, 0xef, 0x4f, 0x15, 0x88, 0xf8, 0x58, + 0xcd, 0xfb, 0xc6, 0x2b, 0x09, 0xcd, 0x9a, 0x32, + 0x44, 0x97, 0x8e, 0xea, 0xfa, 0x3c, 0x5a, 0x7d, + 0x94, 0xda, 0x09, 0x94, 0x27, 0xa0, 0xc5, 0x57, + 0xa8, 0x2d, 0xc5, 0xc4, 0xdf, 0x7c, 0x5f, 0xb4, + 0x9b, 0x27, 0x0b, 0x62, 0xc1, 0x55, 0xcc, 0x55, + 0xb6, 0xfa, 0xf8, 0x70, 0x66, 0x48, 0xba, 0x21, + 0xe5, 0x9c, 0x27, 0x3f, 0x6b, 0xc5, 0xf3, 0x00, + 0x03, 0x52, 0x90, 0x06, 0xa3, 0xb9, 0xda, 0xee, + 0x87, 0xab, 0x78, 0x96, 0x93, 0x69, 0x34, 0x5a, + 0x04, 0x77, 0xcf, 0x0d, 0x20, 0xa3, 0xd7, 0x42, + 0x27, 0x8d, 0x59, 0xfe, 0x89, 0x67, 0x3e, 0x5d, + 0xe0, 0x0c, 0xb6, 0xad, 0xf0, 0x36, 0x09, 0x60, + 0xc1, 0x7c, 0xce, 0x2d, 0xe4, 0x75, 0xaa, 0xf5, + 0xa3, 0x5f, 0x72, 0x3b, 0xf2, 0x73, 0xb4, 0x9b, + 0xb5, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, + }, + { + 0x00, 0x5f, 0xcd, 0x65, 0x12, 0x14, 0xa6, 0x15, + 0xe4, 0xd3, 0xc9, 0xe7, 0x5c, 0x70, 0x2c, 0x37, + 0xca, 0xa7, 0x21, 0xf4, 0x40, 0xf3, 0x6a, 0x88, + 0xe3, 0xce, 0x85, 0x9b, 0xe4, 0x8f, 0x90, 0x8c, + 0x4a, 0xad, 0xf4, 0xcf, 0x99, 0xcf, 0xdc, 0x3b, + 0x29, 0xad, 0x59, 0x72, 0x05, 0xa3, 0xca, 0x3e, + 0xf9, 0x72, 0x9f, 0x7c, 0xb6, 0xa4, 0xd6, 0x86, + 0xb4, 0x64, 0x5a, 0xc4, 0xe0, 0x95, 0x98, 0xb7, + 0xdf, 0xc5, 0x79, 0x8a, 0x48, 0xff, 0xd6, 0x71, + 0xab, 0x0f, 0x17, 0x54, 0xdd, 0x3d, 0xd0, 0x23, + 0x18, 0xc9, 0x4c, 0xf4, 0x40, 0xcf, 0x34, 0xc9, + 0x33, 0x04, 0x35, 0x4b, 0x2c, 0xd9, 0xbd, 0xcf, + 0x59, 0x62, 0x18, 0x2d, 0x9e, 0x5a, 0x11, 0x71, + 0x4c, 0x1f, 0x78, 0x0e, 0x65, 0x75, 0xe6, 0x15, + 0x2f, 0x61, 0x32, 0x3c, 0xac, 0xaa, 0xa8, 0x6a, + 0x1d, 0x11, 0xb4, 0x91, 0x38, 0xd3, 0x34, 0x31, + 0xd7, 0xfa, 0xff, 0xef, 0xbf, 0xb9, 0x90, 0x4f, + 0x0d, 0x29, 0x16, 0xb6, 0x64, 0x48, 0x5b, 0x7b, + 0x00, 0x42, 0x7a, 0xab, 0x2b, 0xe7, 0xee, 0x9b, + 0x5d, 0x0c, 0xe9, 0x14, 0x74, 0x72, 0x29, 0xd9, + 0x10, 0xa5, 0xd5, 0x95, 0xbb, 0x9f, 0x85, 0xc0, + 0xa2, 0xfa, 0xbb, 0x84, 0xf5, 0x59, 0x11, 0xdb, + 0xf4, 0xce, 0x08, 0xe4, 0x36, 0x1c, 0xeb, 0xc9, + 0xa6, 0xcd, 0xdb, 0xf2, 0x10, 0xab, 0x51, 0xb2, + 0x8c, 0x9d, 0x24, 0x8b, 0xa0, 0xb7, 0xf0, 0xd8, + 0xea, 0x6e, 0x24, 0x61, 0x57, 0xfa, 0xd8, 0xd3, + 0xf8, 0x10, 0xaf, 0x7e, 0x45, 0xc5, 0xea, 0x7d, + 0xa4, 0x0d, 0x14, 0x2b, 0x4c, 0xc9, 0x64, 0x48, + 0x12, 0xf1, 0x36, 0x2d, 0xef, 0x33, 0xb6, 0x74, + 0x4c, 0xdf, 0xd5, 0x59, 0x80, 0xa4, 0xfc, 0xa5, + 0x7c, 0x09, 0x24, 0xd7, 0xd5, 0xdf, 0xd6, 0xa5, + 0x2f, 0xa3, 0x49, 0x8a, 0xa3, 0x23, 0xa6, 0x11, + 0x76, 0x0e, 0xe3, 0xcd, 0xd7, 0x64, 0x0f, 0xbe, + 0x3f, 0x99, 0x0d, 0x05, 0x14, 0xd7, 0xa8, 0x2b, + 0x79, 0x6d, 0xb3, 0x12, 0x76, 0x93, 0xae, 0x35, + 0xbe, 0x81, 0xb7, 0xd2, 0xcf, 0x39, 0xd2, 0xad, + 0xb9, 0xc4, 0x3f, 0xfb, 0xe2, 0xc3, 0x32, 0x46, + 0x4d, 0x0b, 0x84, 0x8a, 0x2c, 0x77, 0x2f, 0x49, + 0xac, 0x96, 0xe0, 0x30, 0xeb, 0x38, 0x8f, 0x2f, + 0x01, 0x1b, 0xf3, 0x3c, 0xb5, 0xee, 0x8c, 0x62, + 0x9c, 0x9d, 0x0d, 0xb4, 0x57, 0x6c, 0x8d, 0xf5, + 0xe2, 0x16, 0xc0, 0x62, 0x9c, 0x52, 0x49, 0xf8, + 0xc7, 0xac, 0xb0, 0xa0, 0xe8, 0x81, 0xd9, 0x05, + 0x87, 0xda, 0x09, 0x6f, 0xf7, 0x95, 0xc7, 0x68, + 0x69, 0xbb, 0x68, 0xf4, 0x76, 0x3e, 0x2f, 0xd1, + 0x9d, 0x18, 0xc1, 0x47, 0x59, 0x3d, 0x56, 0x57, + 0x26, 0x10, 0x64, 0xc5, 0x9c, 0x35, 0xe8, 0x58, + 0x1b, 0x0e, 0x20, 0x97, 0xdf, 0xfa, 0xcc, 0x88, + 0xa0, 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, + }, +}, expected_key[4][KEY_LENGTH] = { + { + 0xca, 0xe4, 0x2e, 0xba, 0x74, 0x55, 0x4f, 0x10, + 0x4f, 0x72, 0xa4, 0x3a, 0x0b, 0x00, 0xa0, 0xc8, + 0xd8, 0x8f, 0xa7, 0x11, 0x8d, 0x56, 0x2a, 0x72, + 0xe4, 0xa8, 0xd2, 0x69, 0xf9, 0xd8, 0xa0, 0xf1, + }, + { + 0x63, 0x1d, 0x52, 0x11, 0xed, 0x13, 0xa8, 0x8e, + 0x4c, 0xe0, 0x06, 0x2e, 0xe6, 0xa3, 0xa1, 0xf6, + 0x7a, 0xf1, 0xc4, 0x1e, 0x18, 0xbf, 0x74, 0xe2, + 0x52, 0x23, 0x86, 0x56, 0x1b, 0x0e, 0x78, 0x7d, + }, + { + 0x3f, 0xb2, 0xb6, 0x39, 0xe3, 0x54, 0x41, 0x04, + 0xf3, 0x48, 0x5f, 0x50, 0xf7, 0x8b, 0xd6, 0xe7, + 0x8e, 0x45, 0xc2, 0xee, 0xbf, 0xdd, 0xfd, 0x83, + 0x34, 0x7a, 0x9a, 0x8a, 0xf3, 0x4d, 0xd4, 0xc6, + }, + { + 0x98, 0x76, 0x29, 0xe2, 0x27, 0xf0, 0x0e, 0x2a, + 0xff, 0xc8, 0xfd, 0x3b, 0xcf, 0xcb, 0x9f, 0x6d, + 0xe9, 0x80, 0xfe, 0x6e, 0xd9, 0xc0, 0x43, 0x29, + 0xc5, 0x53, 0x5f, 0xf7, 0x86, 0x33, 0x3b, 0x70, + }, +}, expected_tag[4][TAG_LENGTH] = { + { + 0x10, 0xa4, 0x14, 0x19, 0xec, 0xf1, 0x9b, 0x6d, + 0xe5, 0xe3, 0x12, 0x2b, 0x06, 0xee, 0x5b, 0xac, + 0xa7, 0x11, 0x0b, 0x70, 0x15, 0xfd, 0x47, 0xa4, + 0xb9, 0xd7, 0x45, 0x19, 0x4b, 0xde, 0x92, 0x5d, + }, + { + 0x75, 0x54, 0x5b, 0x6a, 0xfb, 0x30, 0x43, 0x3b, + 0x29, 0x94, 0x58, 0xee, 0x62, 0xd9, 0x62, 0x2b, + 0x61, 0x88, 0xed, 0xce, 0xc9, 0x95, 0x1e, 0x82, + 0x9e, 0xd6, 0xd1, 0x90, 0x23, 0xc6, 0x90, 0x65, + }, + { + 0x2f, 0x8b, 0x64, 0xa4, 0x21, 0xe8, 0x74, 0x93, + 0xa2, 0x3a, 0x6a, 0xa9, 0x0d, 0x7e, 0xb0, 0x80, + 0xce, 0x74, 0x80, 0xb9, 0x06, 0xae, 0xec, 0x86, + 0x4c, 0x32, 0x99, 0xf9, 0x48, 0x1f, 0x0c, 0xad, + }, + { + 0x39, 0x9e, 0xf6, 0xc3, 0x1e, 0xca, 0x02, 0xb2, + 0x6a, 0xe2, 0xa6, 0x47, 0x0b, 0xb5, 0xb6, 0xaf, + 0x46, 0xd7, 0x99, 0x13, 0xc6, 0x07, 0xa8, 0x80, + 0x73, 0x35, 0x75, 0x73, 0xfd, 0x03, 0x5b, 0x3e, + }, +}; + +static void generate_seed(struct iovec *seed) { + assert(iovec_is_set(seed)); + + uint8_t *p = seed->iov_base; + for (size_t i = 0; i < seed->iov_len; i++) + *p++ = (uint8_t) i; +} + +TEST(fsprg) { + size_t n_states = 20; + + /* Generating private keys is slow when running on valgrind or with sanitizers. */ +#if HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) + n_states = ELEMENTSOF(expected_state); +#endif +#if HAS_FEATURE_ADDRESS_SANITIZER + n_states = ELEMENTSOF(expected_state); +#endif + + struct iovec *states = new0(struct iovec, n_states); + CLEANUP_ARRAY(states, n_states, iovec_array_free); + + if (initialize_libgcrypt(false) >= 0) { + struct iovec seed = IOVEC_ALLOCA(FSPRG_RECOMMENDED_SEEDLEN), + msk = IOVEC_ALLOCA(FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR)), + mpk = IOVEC_ALLOCA(FSPRG_mpkinbytes(FSPRG_RECOMMENDED_SECPAR)), + state = IOVEC_ALLOCA(FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR)), + key = IOVEC_ALLOCA(KEY_LENGTH); + + generate_seed(&seed); + + /* Generate secret keys */ + ASSERT_OK(FSPRG_GenMK(msk.iov_base, mpk.iov_base, seed.iov_base, seed.iov_len, FSPRG_RECOMMENDED_SECPAR)); + + /* Generate states by FSPRG_Seek() */ + for (size_t i = 0; i < n_states; i++) { + ASSERT_OK(FSPRG_Seek(state.iov_base, i, msk.iov_base, seed.iov_base, seed.iov_len)); + + /* Verify state */ + if (i < ELEMENTSOF(expected_state)) + ASSERT_TRUE(iovec_equal(&state, &IOVEC_MAKE(expected_state[i], sizeof(expected_state[i])))); + + /* Verify key */ + ASSERT_OK(FSPRG_GetKey(state.iov_base, key.iov_base, key.iov_len, 0)); + if (i < ELEMENTSOF(expected_key)) + ASSERT_TRUE(iovec_equal(&key, &IOVEC_MAKE(expected_key[i], sizeof(expected_key[i])))); + + /* Verify epoch */ + ASSERT_EQ(FSPRG_GetEpoch(state.iov_base), (uint64_t) i); + + ASSERT_NOT_NULL(iovec_memdup(&state, &states[i])); + } + + /* Generate state by FSPRG_GetState0() */ + ASSERT_OK(FSPRG_GenState0(state.iov_base, mpk.iov_base, seed.iov_base, seed.iov_len)); + ASSERT_TRUE(iovec_equal(&state, &states[0])); + + /* Generate states by FSPRG_Evolve() */ + for (size_t i = 1; i < n_states; i++) { + ASSERT_OK(FSPRG_Evolve(state.iov_base)); + ASSERT_TRUE(iovec_equal(&state, &states[i])); + } + } + + if (dlopen_libcrypto(LOG_DEBUG) >= 0) { + struct iovec seed = IOVEC_ALLOCA(FSPRG_RECOMMENDED_SEEDLEN), + state = IOVEC_ALLOCA(fsprg_state_size(FSPRG_RECOMMENDED_SECPAR)), + key = IOVEC_ALLOCA(32); + + generate_seed(&seed); + + /* Generate states by fsprg_generate_state() */ + for (size_t i = 0; i < n_states; i++) { + ASSERT_OK(fsprg_generate_state(FSPRG_RECOMMENDED_SECPAR, i, &seed, &state)); + + /* Verify state */ + if (i < ELEMENTSOF(expected_state)) + ASSERT_EQ(iovec_memcmp(&state, &IOVEC_MAKE(expected_state[i], sizeof(expected_state[i]))), 0); + + /* Verify key */ + ASSERT_OK(fsprg_get_key(&state, &key)); + if (i < ELEMENTSOF(expected_key)) + ASSERT_EQ(iovec_memcmp(&key, &IOVEC_MAKE(expected_key[i], sizeof(expected_key[i]))), 0); + + /* Verify epoch */ + uint64_t epoch; + ASSERT_OK(fsprg_get_epoch(&state, &epoch)); + ASSERT_EQ(epoch, (uint64_t) i); + + if (iovec_is_set(&states[i])) + ASSERT_EQ(iovec_memcmp(&state, &states[i]), 0); + else + ASSERT_NOT_NULL(iovec_memdup(&state, &states[i])); + } + + /* Generate state by fsprg_generate_state(0) */ + ASSERT_OK(fsprg_generate_state(FSPRG_RECOMMENDED_SECPAR, 0, &seed, &state)); + ASSERT_EQ(iovec_memcmp(&state, &states[0]), 0); + + /* Generate states by fsprg_evolve() */ + for (unsigned i = 1; i < n_states; i++) { + ASSERT_OK(fsprg_evolve(&state)); + ASSERT_EQ(iovec_memcmp(&state, &states[i]), 0); + } + } +} + +TEST(hmac) { + /* This emulates what journald and journalctl does. + * 1. journalctl generates random seed, calculate initial state, and write the state in + * /var/log/journal/$MACHINE_ID/fss. + * 2. journald loads the fss file when a new journal file is created by journal_file_fss_load(). + * 3. Allocate HMAC with SHA256 by journal_file_hmac_setup(). + * 4. Write inital tag in journal file by journal_file_append_first_tag(). + * 5. For each journal entry, + * - initialize HMAC with fsprg key by journal_file_hmac_start(), + * - put each object data to the HMAC by journal_file_hmac_put_object(), + * - finalize HMAC and write the value with the fsprg epoch to a tag object by + * journal_file_maybe_append_tag(). */ + + size_t n_tags = 20; + struct iovec *tags = new0(struct iovec, n_tags); + CLEANUP_ARRAY(tags, n_tags, iovec_array_free); + +#if HAVE_GCRYPT + if (initialize_libgcrypt(false) >= 0) { + struct iovec seed = IOVEC_ALLOCA(FSPRG_RECOMMENDED_SEEDLEN), + msk = IOVEC_ALLOCA(FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR)), + mpk = IOVEC_ALLOCA(FSPRG_mpkinbytes(FSPRG_RECOMMENDED_SECPAR)), + state = IOVEC_ALLOCA(FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR)), + key = IOVEC_ALLOCA(KEY_LENGTH), + tag = IOVEC_ALLOCA(TAG_LENGTH); + + generate_seed(&seed); + + /* Generate secret keys */ + ASSERT_OK(FSPRG_GenMK(msk.iov_base, mpk.iov_base, seed.iov_base, seed.iov_len, FSPRG_RECOMMENDED_SECPAR)); + + /* Calculate initial state */ + ASSERT_OK(FSPRG_GenState0(state.iov_base, mpk.iov_base, seed.iov_base, seed.iov_len)); + + /* Allocate HMAC */ + _cleanup_(sym_gcry_md_closep) gcry_md_hd_t hmac = NULL; + ASSERT_EQ(gcry_err_code(sym_gcry_md_open(&hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC)), (gcry_err_code_t) GPG_ERR_NO_ERROR); + + for (size_t i = 0; i < n_tags; i++) { + /* Reset the previous state */ + sym_gcry_md_reset(hmac); + + /* Initialize HMAC with fsprg key */ + ASSERT_OK(FSPRG_GetKey(state.iov_base, key.iov_base, key.iov_len, 0)); + ASSERT_EQ(gcry_err_code(sym_gcry_md_setkey(hmac, key.iov_base, key.iov_len)), (gcry_err_code_t) GPG_ERR_NO_ERROR); + + /* Put dummy data */ + _cleanup_free_ char *str = ASSERT_NOT_NULL(asprintf_safe("dummy data: %zu, hoge hoge hoge hoge hoge", i)); + sym_gcry_md_write(hmac, str, strlen(str)); + + /* Finalize HMAC and generate tag. */ + memcpy(tag.iov_base, sym_gcry_md_read(hmac, 0), tag.iov_len); + + /* Verify tag */ + if (i < ELEMENTSOF(expected_tag)) + ASSERT_TRUE(iovec_equal(&tag, &IOVEC_MAKE(expected_tag[i], sizeof(expected_tag[i])))); + + /* Save tag */ + ASSERT_NOT_NULL(iovec_memdup(&tag, &tags[i])); + + /* Increment epoch */ + ASSERT_OK(FSPRG_Evolve(state.iov_base)); + } + } +#endif + +#if HAVE_OPENSSL + if (dlopen_libcrypto(LOG_DEBUG) >= 0) { + struct iovec seed = IOVEC_ALLOCA(FSPRG_RECOMMENDED_SEEDLEN), + state = IOVEC_ALLOCA(fsprg_state_size(FSPRG_RECOMMENDED_SECPAR)), + key = IOVEC_ALLOCA(KEY_LENGTH), + tag = IOVEC_ALLOCA(TAG_LENGTH); + + generate_seed(&seed); + + /* Calculate initial state */ + ASSERT_OK(fsprg_generate_state(FSPRG_RECOMMENDED_SECPAR, 0, &seed, &state)); + + /* Allocate HMAC */ + _cleanup_(EVP_MAC_freep) EVP_MAC *hmac = ASSERT_NOT_NULL(sym_EVP_MAC_fetch(NULL, "HMAC", NULL)); + _cleanup_(EVP_MAC_CTX_freep) EVP_MAC_CTX *hmac_ctx = ASSERT_NOT_NULL(sym_EVP_MAC_CTX_new(hmac)); + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = ASSERT_NOT_NULL(sym_OSSL_PARAM_BLD_new()); + ASSERT_OK_POSITIVE(sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_MAC_PARAM_DIGEST, "SHA256", 0)); + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = ASSERT_NOT_NULL(sym_OSSL_PARAM_BLD_to_param(bld)); + + for (size_t i = 0; i < n_tags; i++) { + /* Initialize HMAC with fsprg key */ + ASSERT_OK(fsprg_get_key(&state, &key)); + ASSERT_OK_POSITIVE(sym_EVP_MAC_init(hmac_ctx, key.iov_base, key.iov_len, params)); + + /* Put dummy data */ + _cleanup_(iovec_done) struct iovec data = + IOVEC_MAKE_STRING(ASSERT_NOT_NULL(asprintf_safe("dummy data: %zu, hoge hoge hoge hoge hoge", i))); + ASSERT_OK_POSITIVE(sym_EVP_MAC_update(hmac_ctx, data.iov_base, data.iov_len)); + + /* Finalize HMAC and generate tag. */ + size_t len; + ASSERT_OK_POSITIVE(sym_EVP_MAC_final(hmac_ctx, tag.iov_base, &len, tag.iov_len)); + ASSERT_EQ(len, tag.iov_len); + + /* Verify tag */ + if (i < ELEMENTSOF(expected_tag)) + ASSERT_TRUE(iovec_equal(&tag, &IOVEC_MAKE(expected_tag[i], sizeof(expected_tag[i])))); + + /* Compare with the tag generated by gcrypt */ + if (iovec_is_set(&tags[i])) + ASSERT_TRUE(iovec_equal(&tag, &tags[i])); + + /* Increment epoch */ + ASSERT_OK(fsprg_evolve(&state)); + } + } +#endif +} + +static int intro(void) { + if (DLOPEN_GCRYPT(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 0 && + DLOPEN_LIBCRYPTO(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 0) + return EXIT_TEST_SKIP; + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-getopt.c b/src/test/test-getopt.c deleted file mode 100644 index e17621af6a944..0000000000000 --- a/src/test/test-getopt.c +++ /dev/null @@ -1,516 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include - -#include "strv.h" -#include "tests.h" - -typedef struct Entry { - int opt; - const char *argument; - const char *nextarg; -} Entry; - -static void test_getopt_long_one( - char **argv, - const char *optstring, - const struct option *longopts, - const Entry *entries, - char **remaining) { - - _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 c, argc = strv_length(argv); - size_t i = 0, n_entries = 0; - - for (const Entry *e = entries; e && e->opt != 0; e++) - n_entries++; - - optind = 0; - while ((c = getopt_long(argc, argv, optstring, longopts, NULL)) >= 0) { - if (c < 0x100) - log_debug("%c: %s", c, strna(optarg)); - else - log_debug("0x%x: %s", (unsigned) c, strna(optarg)); - - ASSERT_LT(i, n_entries); - ASSERT_EQ(c, entries[i].opt); - ASSERT_STREQ(optarg, entries[i].argument); - if (entries[i].nextarg) - ASSERT_STREQ(argv[optind], entries[i].nextarg); - i++; - } - - ASSERT_EQ(i, n_entries); - ASSERT_LE(optind, argc); - ASSERT_EQ(argc - optind, (int) strv_length(remaining)); - for (int j = optind; j < argc; j++) - ASSERT_STREQ(argv[j], remaining[j - optind]); - ASSERT_STREQ(argv[0], saved_argv0); -} - -TEST(getopt_long) { - enum { - ARG_VERSION = 0x100, - ARG_REQUIRED, - ARG_OPTIONAL, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version" , no_argument, NULL, ARG_VERSION }, - { "required1", required_argument, NULL, 'r' }, - { "required2", required_argument, NULL, ARG_REQUIRED }, - { "optional1", optional_argument, NULL, 'o' }, - { "optional2", optional_argument, NULL, ARG_OPTIONAL }, - {}, - }; - - test_getopt_long_one(STRV_MAKE("arg0"), - "hr:o::", options, - NULL, - NULL); - - 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", - "--", - "string1", - "string2", - "string3", - "string4"), - "hr:o::", options, - NULL, - STRV_MAKE("string1", - "string2", - "string3", - "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", - "string1", - "string2", - "string3", - "string4", - "--"), - "hr:o::", options, - NULL, - STRV_MAKE("string1", - "string2", - "string3", - "string4")); - - test_getopt_long_one(STRV_MAKE("arg0", - "--help"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - NULL); - - test_getopt_long_one(STRV_MAKE("arg0", - "-h"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - NULL); - - test_getopt_long_one(STRV_MAKE("arg0", - "--help", - "string1", - "string2", - "string3", - "string4"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string4")); - - test_getopt_long_one(STRV_MAKE("arg0", - "-h", - "string1", - "string2", - "string3", - "string4"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string4")); - - test_getopt_long_one(STRV_MAKE("arg0", - "string1", - "string2", - "--help", - "string3", - "string4"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string4")); - - test_getopt_long_one(STRV_MAKE("arg0", - "string1", - "string2", - "-h", - "string3", - "string4"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string4")); - - test_getopt_long_one(STRV_MAKE("arg0", - "string1", - "string2", - "string3", - "string4", - "--help"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string4")); - - test_getopt_long_one(STRV_MAKE("arg0", - "string1", - "string2", - "string3", - "string4", - "-h"), - "hr:o::", options, - (Entry[]) { - { 'h', NULL }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string4")); - - test_getopt_long_one(STRV_MAKE("arg0", - "--required1", "reqarg1"), - "hr:o::", options, - (Entry[]) { - { 'r', "reqarg1" }, - {} - }, - NULL); - - test_getopt_long_one(STRV_MAKE("arg0", - "-r", "reqarg1"), - "hr:o::", options, - (Entry[]) { - { 'r', "reqarg1" }, - {} - }, - NULL); - - test_getopt_long_one(STRV_MAKE("arg0", - "string1", - "string2", - "-r", "reqarg1"), - "hr:o::", options, - (Entry[]) { - { 'r', "reqarg1" }, - {} - }, - STRV_MAKE("string1", - "string2")); - - test_getopt_long_one(STRV_MAKE("arg0", - "--optional1=optarg1"), - "hr:o::", options, - (Entry[]) { - { 'o', "optarg1" }, - {} - }, - NULL); - - test_getopt_long_one(STRV_MAKE("arg0", - "--optional1", "string1"), - "hr:o::", options, - (Entry[]) { - { 'o', NULL, "string1" }, - {} - }, - STRV_MAKE("string1")); - - test_getopt_long_one(STRV_MAKE("arg0", - "-ooptarg1"), - "hr:o::", options, - (Entry[]) { - { 'o', "optarg1" }, - {} - }, NULL); - - test_getopt_long_one(STRV_MAKE("arg0", - "-o", "string1"), - "hr:o::", options, - (Entry[]) { - { 'o', NULL, "string1" }, - {} - }, - STRV_MAKE("string1")); - - test_getopt_long_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"), - "hr:o::", options, - (Entry[]) { - { 'h' }, - { ARG_VERSION }, - { 'r', "reqarg1" }, - { ARG_REQUIRED, "reqarg2" }, - { 'r', "reqarg3" }, - { ARG_REQUIRED, "reqarg4" }, - { 'o', NULL, "string4" }, - { ARG_OPTIONAL, NULL, "string5" }, - { 'o', "optarg1" }, - { ARG_OPTIONAL, "optarg2" }, - { 'h' }, - { 'r', "reqarg5" }, - { 'r', "reqarg6" }, - { 'o', "optarg3" }, - { 'o', NULL, "string6" }, - { 'o', NULL, "-h" }, - { 'h' }, - { 'o', NULL, "--help" }, - { 'h' }, - { 'h' }, - { 'o', "optarg4" }, - { 'h' }, - { 'r', "reqarg6" }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string4", - "string5", - "string6", - "string7", - "--help", - "--required1", - "--optional1")); -} -static void test_getopt_one( - char **argv, - const char *optstring, - const Entry *entries, - char **remaining) { - - _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 c, argc = strv_length(argv); - size_t i = 0, n_entries = 0; - - for (const Entry *e = entries; e && e->opt != 0; e++) - n_entries++; - - optind = 0; - while ((c = getopt(argc, argv, optstring)) >= 0) { - log_debug("%c: %s", c, strna(optarg)); - - ASSERT_LT(i, n_entries); - ASSERT_EQ(c, entries[i].opt); - ASSERT_STREQ(optarg, entries[i].argument); - if (entries[i].nextarg) - ASSERT_STREQ(argv[optind], entries[i].nextarg); - i++; - } - - ASSERT_EQ(i, n_entries); - ASSERT_LE(optind, argc); - ASSERT_EQ(argc - optind, (int) strv_length(remaining)); - for (int j = optind; j < argc; j++) - ASSERT_STREQ(argv[j], remaining[j - optind]); - ASSERT_STREQ(argv[0], saved_argv0); -} - -TEST(getopt) { - test_getopt_one(STRV_MAKE("arg0"), - "hr:o::", - NULL, - NULL); - - test_getopt_one(STRV_MAKE("arg0", - "string1", - "string2"), - "hr:o::", - NULL, - STRV_MAKE("string1", - "string2")); - - test_getopt_one(STRV_MAKE("arg0", - "-h"), - "hr:o::", - (Entry[]) { - { 'h', NULL }, - {} - }, - NULL); - - test_getopt_one(STRV_MAKE("arg0", - "-r", "reqarg1"), - "hr:o::", - (Entry[]) { - { 'r', "reqarg1" }, - {} - }, - NULL); - - test_getopt_one(STRV_MAKE("arg0", - "string1", - "string2", - "-r", "reqarg1"), - "hr:o::", - (Entry[]) { - { 'r', "reqarg1" }, - {} - }, - STRV_MAKE("string1", - "string2")); - - test_getopt_one(STRV_MAKE("arg0", - "-ooptarg1"), - "hr:o::", - (Entry[]) { - { 'o', "optarg1" }, - {} - }, - NULL); - - test_getopt_one(STRV_MAKE("arg0", - "-o", "string1"), - "hr:o::", - (Entry[]) { - { 'o', NULL, "string1" }, - {} - }, - STRV_MAKE("string1")); - - test_getopt_one(STRV_MAKE("arg0", - "string1", - "string2", - "string3", - "-h", - "-r", "reqarg5", - "-rreqarg6", - "-ooptarg3", - "-o", - "string6", - "-o", - "-h", - "-o", - "string7", - "-hooptarg4", - "-hrreqarg6"), - "hr:o::", - (Entry[]) { - { 'h' }, - { 'r', "reqarg5" }, - { 'r', "reqarg6" }, - { 'o', "optarg3" }, - { 'o', NULL, "string6" }, - { 'o', NULL, "-h" }, - { 'h' }, - { 'o', NULL, "string7" }, - { 'h' }, - { 'o', "optarg4" }, - { 'h' }, - { 'r', "reqarg6" }, - {} - }, - STRV_MAKE("string1", - "string2", - "string3", - "string6", - "string7")); -} - -DEFINE_TEST_MAIN(LOG_DEBUG); 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-group-record-nss.c b/src/test/test-group-record-nss.c new file mode 100644 index 0000000000000..2eb5eed53dd40 --- /dev/null +++ b/src/test/test-group-record-nss.c @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-json.h" +#include "group-record.h" +#include "strv.h" +#include "tests.h" +#include "user-record-nss.h" + +TEST(nss_group_alias) { + static const struct group grp = { + .gr_name = (char*) "domain users", + .gr_gid = 1000, + }; + _cleanup_(group_record_unrefp) GroupRecord *g = NULL; + sd_json_variant *aliases; + + ASSERT_OK(nss_group_to_group_record(&grp, NULL, "domain users@example.test", &g)); + ASSERT_TRUE(group_record_matches_group_name(g, "domain users@example.test")); + ASSERT_TRUE(strv_contains(g->aliases, "domain users@example.test")); + + aliases = ASSERT_NOT_NULL(sd_json_variant_by_key(g->json, "aliases")); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_index(aliases, 0)), "domain users@example.test"); +} + +TEST(nss_group_invalid_alias) { + static const struct group grp = { + .gr_name = (char*) "domain users", + .gr_gid = 1000, + }; + _cleanup_(group_record_unrefp) GroupRecord *g = NULL; + + ASSERT_OK(nss_group_to_group_record(&grp, NULL, "domain/users", &g)); + ASSERT_FALSE(group_record_matches_group_name(g, "domain/users")); + ASSERT_TRUE(strv_isempty(g->aliases)); + ASSERT_NULL(sd_json_variant_by_key(g->json, "aliases")); +} + +TEST(nss_group_alias_realm) { + static const struct group grp = { + .gr_name = (char*) "domain users", + .gr_gid = 1000, + }; + _cleanup_(group_record_unrefp) GroupRecord *g = NULL; + + ASSERT_OK(nss_group_to_group_record(&grp, NULL, "domain-users", &g)); + g->realm = ASSERT_NOT_NULL(strdup("example.test")); + + ASSERT_TRUE(group_record_matches_group_name(g, "domain-users@example.test")); +} + +TEST(nss_group_alias_same_as_canonical_noop) { + static const struct group grp = { + .gr_name = (char*) "domain users", + .gr_gid = 1000, + }; + _cleanup_(group_record_unrefp) GroupRecord *g = NULL; + + ASSERT_OK(nss_group_to_group_record(&grp, NULL, "domain users", &g)); + ASSERT_TRUE(strv_isempty(g->aliases)); + ASSERT_NULL(sd_json_variant_by_key(g->json, "aliases")); +} + +TEST(nss_group_null_alias_noop) { + static const struct group grp = { + .gr_name = (char*) "domain users", + .gr_gid = 1000, + }; + _cleanup_(group_record_unrefp) GroupRecord *g = NULL; + + ASSERT_OK(nss_group_to_group_record(&grp, NULL, NULL, &g)); + ASSERT_TRUE(strv_isempty(g->aliases)); + ASSERT_NULL(sd_json_variant_by_key(g->json, "aliases")); +} + +TEST(nss_group_alias_fuzzy_match) { + static const struct group grp = { + .gr_name = (char*) "canonical group", + .gr_gid = 1000, + }; + _cleanup_(group_record_unrefp) GroupRecord *g = NULL; + UserDBMatch match = USERDB_MATCH_NULL; + + ASSERT_OK(nss_group_to_group_record(&grp, NULL, "external-group", &g)); + match.fuzzy_names = ASSERT_NOT_NULL(strv_new("ternal")); + + ASSERT_TRUE(group_record_match(g, &match)); + userdb_match_done(&match); +} + +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-hostname-setup.c b/src/test/test-hostname-setup.c index e3fc8a220b6a4..e72bf1f16d775 100644 --- a/src/test/test-hostname-setup.c +++ b/src/test/test-hostname-setup.c @@ -10,8 +10,10 @@ #include "hostname-setup.h" #include "hostname-util.h" #include "id128-util.h" +#include "path-util.h" #include "pidref.h" #include "process-util.h" +#include "rm-rf.h" #include "tests.h" #include "tmpfile-util.h" @@ -86,24 +88,81 @@ TEST(hostname_substitute_wildcards) { return (void) log_tests_skipped_errno(r, "skipping wildcard hostname tests, no machine ID defined"); _cleanup_free_ char *buf = NULL; - ASSERT_NOT_NULL((buf = strdup(""))); - ASSERT_OK(hostname_substitute_wildcards(buf)); + ASSERT_OK(hostname_substitute_wildcards("", &buf)); ASSERT_STREQ(buf, ""); ASSERT_NULL(buf = mfree(buf)); - ASSERT_NOT_NULL((buf = strdup("hogehoge"))); - ASSERT_OK(hostname_substitute_wildcards(buf)); + ASSERT_OK(hostname_substitute_wildcards("hogehoge", &buf)); ASSERT_STREQ(buf, "hogehoge"); ASSERT_NULL(buf = mfree(buf)); - ASSERT_NOT_NULL((buf = strdup("hoge??hoge??foo?"))); - ASSERT_OK(hostname_substitute_wildcards(buf)); + ASSERT_OK(hostname_substitute_wildcards("hoge??hoge??foo?", &buf)); log_debug("hostname_substitute_wildcards(\"hoge??hoge??foo?\"): → \"%s\"", buf); ASSERT_EQ(fnmatch("hoge??hoge??foo?", buf, /* flags= */ 0), 0); ASSERT_TRUE(hostname_is_valid(buf, /* flags= */ 0)); ASSERT_NULL(buf = mfree(buf)); } +TEST(hostname_substitute_wildcards_words) { + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + int r; + + r = sd_id128_get_machine(NULL); + if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) + return (void) log_tests_skipped_errno(r, "skipping word hostname tests, no machine ID defined"); + + ASSERT_OK(mkdtemp_malloc("/tmp/hostname-wordlist.XXXXXX", &d)); + + /* The n-th '$' reads the word list file named after its position. */ + _cleanup_free_ char *one_list = ASSERT_PTR(path_join(d, "1")); + _cleanup_free_ char *two_list = ASSERT_PTR(path_join(d, "2")); + ASSERT_OK(write_string_file(one_list, "happy\nsad\n# comment\n\njolly\n", WRITE_STRING_FILE_CREATE)); + ASSERT_OK(write_string_file(two_list, "octopus\nfalcon\nINVALID_WORD!\nbadger\n", WRITE_STRING_FILE_CREATE)); + ASSERT_OK(setenv("SYSTEMD_HOSTNAME_WORDLIST_PATH", d, /* overwrite= */ true)); + + _cleanup_free_ char *a = NULL, *b = NULL; + ASSERT_OK(hostname_substitute_wildcards("$-$", &a)); + log_debug("hostname_substitute_wildcards(\"$-$\"): → \"%s\"", a); + ASSERT_TRUE(hostname_is_valid(a, /* flags= */ 0)); + + /* Fully deterministic: same machine ID + same lists → same name */ + ASSERT_OK(hostname_substitute_wildcards("$-$", &b)); + ASSERT_STREQ(a, b); + + /* Missing list (no file "3") → error (caller falls back to built-in hostname) */ + _cleanup_free_ char *e = NULL; + ASSERT_ERROR(hostname_substitute_wildcards("$-$-$", &e), ENOENT); + + ASSERT_OK(unsetenv("SYSTEMD_HOSTNAME_WORDLIST_PATH")); +} + +TEST(hostname_setup_cmdline_wildcards) { + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + int r; + + r = sd_id128_get_machine(NULL); + if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) + return (void) log_tests_skipped_errno(r, "skipping cmdline wildcard hostname tests, no machine ID defined"); + + ASSERT_OK(mkdtemp_malloc("/tmp/hostname-wordlist.XXXXXX", &d)); + ASSERT_OK(setenv("SYSTEMD_PROC_CMDLINE", "systemd.hostname=$-????", /* overwrite= */ true)); + ASSERT_OK(setenv("SYSTEMD_HOSTNAME_WORDLIST_PATH", d, /* overwrite= */ true)); + + /* Word list missing (as e.g. in the initrd): the kernel command line hostname is ignored and the + * usual fallback logic applies, hostname_setup() must still succeed. */ + ASSERT_OK(hostname_setup(/* really= */ false)); + + /* Word list present: hostname_setup() now exercises the cmdline expansion path and must still + * succeed. With really=false nothing is applied and the resolved name is not surfaced, so the + * concrete expansion is asserted separately in the hostname_substitute_wildcards test above. */ + _cleanup_free_ char *one_list = ASSERT_PTR(path_join(d, "1")); + ASSERT_OK(write_string_file(one_list, "happy\nsad\n", WRITE_STRING_FILE_CREATE)); + ASSERT_OK(hostname_setup(/* really= */ false)); + + ASSERT_OK(unsetenv("SYSTEMD_PROC_CMDLINE")); + ASSERT_OK(unsetenv("SYSTEMD_HOSTNAME_WORDLIST_PATH")); +} + TEST(hostname_setup) { hostname_setup(false); } diff --git a/src/test/test-hostname-util.c b/src/test/test-hostname-util.c index b3e3b1b84a753..513c8edc9b83c 100644 --- a/src/test/test-hostname-util.c +++ b/src/test/test-hostname-util.c @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "alloc-util.h" #include "hostname-util.h" #include "string-util.h" +#include "strv.h" #include "tests.h" TEST(hostname_is_valid) { @@ -117,4 +119,121 @@ TEST(split_user_at_host) { test_split_user_at_host_one("aa@@@bb", "aa", "@@bb", 1); } +TEST(machine_tag_is_valid) { + assert_se(machine_tag_is_valid("foo")); + assert_se(machine_tag_is_valid("foo-bar.baz")); + assert_se(machine_tag_is_valid("Webserver01")); + assert_se(machine_tag_is_valid("a")); + assert_se(machine_tag_is_valid("a=")); /* empty value is OK */ + assert_se(machine_tag_is_valid("a=b")); + assert_se(machine_tag_is_valid("foo.bar=")); + assert_se(machine_tag_is_valid("foo.bar-baz=zuziuziuz")); + assert_se(machine_tag_is_valid("foo=bar.baz")); /* "." and "-" are fine inside a value */ + assert_se(machine_tag_is_valid("foo=bar-")); /* even as the very last char of a value */ + assert_se(machine_tag_is_valid("foo=.bar")); /* and as the very first char of a value */ + assert_se(machine_tag_is_valid("foo=bar=")); /* a value may itself contain a "=" */ + assert_se(machine_tag_is_valid("a=b=c")); /* only the first "=" is the separator */ + + assert_se(!machine_tag_is_valid(NULL)); + assert_se(!machine_tag_is_valid("")); + assert_se(!machine_tag_is_valid("foo:bar")); /* colon is the separator */ + assert_se(!machine_tag_is_valid("foo bar")); + assert_se(!machine_tag_is_valid("fööbar")); /* non-ASCII */ + assert_se(!machine_tag_is_valid("foo/bar")); + assert_se(!machine_tag_is_valid("foo_bar")); + assert_se(!machine_tag_is_valid("-foo")); + assert_se(!machine_tag_is_valid("foo-")); + assert_se(!machine_tag_is_valid(".foo")); + assert_se(!machine_tag_is_valid("foo.")); + assert_se(!machine_tag_is_valid("=b")); + assert_se(!machine_tag_is_valid("=")); + assert_se(!machine_tag_is_valid(".foo=asd")); /* "." not allowed as first char */ + assert_se(!machine_tag_is_valid("foo.=asd")); /* "." not allowed as last char of key */ + assert_se(!machine_tag_is_valid("foo-=asd")); /* "-" not allowed as last char of key */ + assert_se(!machine_tag_is_valid("_foo=asd")); /* "_" is not in the charset */ + assert_se(!machine_tag_is_valid("foo_=sda")); + assert_se(!machine_tag_is_valid("foo=a_b")); /* ... not even in the value */ + assert_se(!machine_tag_is_valid("foo=a:b")); /* colon is the separator, not allowed in a value */ + + /* Length boundary: 255 characters is fine, 256 is too long */ + _cleanup_free_ char *max = strrep("a", 255), *over = strrep("a", 256); + assert_se(max); + assert_se(over); + assert_se(machine_tag_is_valid(max)); + assert_se(!machine_tag_is_valid(over)); +} + +TEST(machine_tag_list_is_valid) { + assert_se(machine_tag_list_is_valid(NULL)); /* empty list is valid */ + assert_se(machine_tag_list_is_valid(STRV_MAKE("a"))); + assert_se(machine_tag_list_is_valid(STRV_MAKE("foo", "bar", "c-d.e"))); + assert_se(machine_tag_list_is_valid(STRV_MAKE("foo=uuu", "bar=qqqq", "c-d.e"))); + assert_se(machine_tag_list_is_valid(STRV_MAKE("foo=aa", "foo=aa"))); /* same key + same value is OK */ + assert_se(machine_tag_list_is_valid(STRV_MAKE("foo", "foo=aa"))); /* bare key and assignment coexist */ + assert_se(machine_tag_list_is_valid(STRV_MAKE("foo=1", "foobar=2"))); /* one key is a prefix of the other */ + assert_se(machine_tag_list_is_valid(STRV_MAKE("ab=1", "a=2"))); /* ... and the other way around */ + + assert_se(!machine_tag_list_is_valid(STRV_MAKE("foo", "b:c"))); + assert_se(!machine_tag_list_is_valid(STRV_MAKE("foo", ""))); + assert_se(!machine_tag_list_is_valid(STRV_MAKE("foo=aa", "foo=b"))); /* same key, different value */ + assert_se(!machine_tag_list_is_valid(STRV_MAKE("a=1", "b=2", "a=3"))); /* ... also when not adjacent */ + assert_se(!machine_tag_list_is_valid(STRV_MAKE("foo=aa", "bar", "foo=aa", "foo=b"))); + assert_se(!machine_tag_list_is_valid(STRV_MAKE("=aa"))); +} + +TEST(machine_tags_from_string) { + _cleanup_strv_free_ char **l = NULL; + + ASSERT_OK(machine_tags_from_string(NULL, /* graceful= */ false, &l)); + assert_se(strv_isempty(l)); + l = strv_free(l); + + ASSERT_OK(machine_tags_from_string("", /* graceful= */ true, &l)); + assert_se(strv_isempty(l)); + l = strv_free(l); + + /* Sorted and deduplicated */ + ASSERT_OK(machine_tags_from_string("foo:bar:foo:baz", /* graceful= */ false, &l)); + assert_se(strv_equal(l, STRV_MAKE("bar", "baz", "foo"))); + l = strv_free(l); + + /* Graceful: invalid tags are dropped, valid ones kept (sorted/deduplicated) */ + ASSERT_OK(machine_tags_from_string("foo:in valid:bar:foo", /* graceful= */ true, &l)); + assert_se(strv_equal(l, STRV_MAKE("bar", "foo"))); + l = strv_free(l); + + /* Graceful: all tags invalid → empty list */ + ASSERT_OK(machine_tags_from_string("in valid:also invalid", /* graceful= */ true, &l)); + assert_se(strv_isempty(l)); + l = strv_free(l); + + /* Fatal: a single invalid tag fails the whole parse */ + ASSERT_ERROR(machine_tags_from_string("foo:in valid:bar", /* graceful= */ false, &l), EINVAL); + assert_se(!l); + + /* With assignment */ + ASSERT_OK(machine_tags_from_string("foo=aa:bar=aaa:foo2=x:baz", /* graceful= */ false, &l)); + assert_se(strv_equal(l, STRV_MAKE("bar=aaa", "baz", "foo2=x", "foo=aa"))); + l = strv_free(l); + + /* Graceful: a duplicate key is suppressed, keeping the first (i.e. lexicographically smallest) value */ + ASSERT_OK(machine_tags_from_string("foo=zzz:foo=aaa:foo=mmm", /* graceful= */ true, &l)); + assert_se(strv_equal(l, STRV_MAKE("foo=aaa"))); + l = strv_free(l); + + /* Graceful: a bare key and an assignment for the same name are not considered duplicates */ + ASSERT_OK(machine_tags_from_string("foo:foo=aaa", /* graceful= */ true, &l)); + assert_se(strv_equal(l, STRV_MAKE("foo", "foo=aaa"))); + l = strv_free(l); + + /* Graceful: an invalid value is dropped, conflicting keys that remain are deduplicated */ + ASSERT_OK(machine_tags_from_string("foo=a_b:foo=good:foo=zzz", /* graceful= */ true, &l)); + assert_se(strv_equal(l, STRV_MAKE("foo=good"))); + l = strv_free(l); + + /* Fatal: conflicting values for the same key fail the whole parse */ + ASSERT_ERROR(machine_tags_from_string("foo=a:foo=b", /* graceful= */ false, &l), EINVAL); + assert_se(!l); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-initrd-cpio.c b/src/test/test-initrd-cpio.c new file mode 100644 index 0000000000000..cea23a6f1c3ed --- /dev/null +++ b/src/test/test-initrd-cpio.c @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "initrd-cpio.h" +#include "machine-credential.h" +#include "path-util.h" +#include "rm-rf.h" +#include "tests.h" +#include "tmpfile-util.h" + +TEST_RET(initrd_cpio_credentials_basic) { + struct stat st; + size_t foo_size, bar_size; + _cleanup_free_ char *cmd = NULL, *extra_path = NULL, *creds_path = NULL, *foo_path = NULL, *bar_path = NULL, *foo_content = NULL, *bar_content = NULL; + _cleanup_(machine_credential_context_done) MachineCredentialContext creds = {}; + _cleanup_(unlink_and_freep) char *cpio_path = NULL; + _cleanup_(rm_rf_physical_and_freep) char *extract_dir = NULL; + int r; + + r = find_executable("cpio", NULL); + if (r < 0) + return log_tests_skipped_errno(r, "Could not find cpio binary: %m"); + + ASSERT_OK(machine_credential_add(&creds, "foo", "hello", 5)); + ASSERT_OK(machine_credential_add(&creds, "bar", "abc\0def", 7)); + + ASSERT_OK(initrd_cpio_credentials_to_tempfile(&creds, &cpio_path)); + ASSERT_NOT_NULL(cpio_path); + + ASSERT_OK(mkdtemp_malloc(NULL, &extract_dir)); + ASSERT_OK(asprintf(&cmd, "cd %s && cpio -idm < %s", extract_dir, cpio_path)); + ASSERT_OK_ZERO_ERRNO(system(cmd)); + + ASSERT_NOT_NULL(extra_path = path_join(extract_dir, ".extra")); + ASSERT_OK_ERRNO(stat(extra_path, &st)); + ASSERT_TRUE(S_ISDIR(st.st_mode)); + ASSERT_EQ((mode_t) (st.st_mode & 07777), (mode_t) 0555); + + ASSERT_NOT_NULL(creds_path = path_join(extract_dir, ".extra/system_credentials")); + ASSERT_OK_ERRNO(stat(creds_path, &st)); + ASSERT_TRUE(S_ISDIR(st.st_mode)); + ASSERT_EQ((mode_t) (st.st_mode & 07777), (mode_t) 0500); + + ASSERT_NOT_NULL(foo_path = path_join(extract_dir, ".extra/system_credentials/foo.cred")); + ASSERT_OK_ERRNO(stat(foo_path, &st)); + ASSERT_TRUE(S_ISREG(st.st_mode)); + ASSERT_EQ((mode_t) (st.st_mode & 07777), (mode_t) 0400); + ASSERT_OK(read_full_file(foo_path, &foo_content, &foo_size)); + ASSERT_EQ(foo_size, 5U); + ASSERT_EQ(memcmp(foo_content, "hello", 5), 0); + + ASSERT_NOT_NULL(bar_path = path_join(extract_dir, ".extra/system_credentials/bar.cred")); + ASSERT_OK_ERRNO(stat(bar_path, &st)); + ASSERT_TRUE(S_ISREG(st.st_mode)); + ASSERT_EQ((mode_t) (st.st_mode & 07777), (mode_t) 0400); + ASSERT_OK(read_full_file(bar_path, &bar_content, &bar_size)); + ASSERT_EQ(bar_size, 7U); + ASSERT_EQ(memcmp(bar_content, "abc\0def", 7), 0); + + return 0; +} + +TEST(initrd_cpio_credentials_rejects_invalid_name) { + _cleanup_(machine_credential_context_done) MachineCredentialContext creds = {}; + _cleanup_(unlink_and_freep) char *cpio_path = NULL; + + /* Bypass the validating machine_credential_add()/_set()/_load() helpers and inject an id that would + * escape the .extra/system_credentials/ directory, to exercise the writer's defense-in-depth check. */ + ASSERT_NOT_NULL(creds.credentials = new0(MachineCredential, 1)); + creds.n_credentials = 1; + ASSERT_NOT_NULL(creds.credentials[0].id = strdup("../../etc/evil")); + ASSERT_NOT_NULL(creds.credentials[0].data = memdup("x", 1)); + creds.credentials[0].size = 1; + + ASSERT_ERROR(initrd_cpio_credentials_to_tempfile(&creds, &cpio_path), EINVAL); + ASSERT_NULL(cpio_path); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-iovec-util.c b/src/test/test-iovec-util.c index 217ee8cf5d9ec..e510e86ca144a 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,36 @@ 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); +} + +TEST(iovec_done_and_memdup) { + _cleanup_(iovec_done) struct iovec iov = {}; + + ASSERT_OK_ZERO(iovec_done_and_memdup(&iov, NULL)); + ASSERT_TRUE(!iovec_is_set(&iov)); + ASSERT_OK_ZERO(iovec_done_and_memdup(&iov, &(struct iovec) {})); + ASSERT_TRUE(!iovec_is_set(&iov)); + ASSERT_OK_POSITIVE(iovec_done_and_memdup(&iov, &IOVEC_MAKE_STRING("aaa"))); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE_STRING("aaa"))); + ASSERT_OK_POSITIVE(iovec_done_and_memdup(&iov, &IOVEC_MAKE_STRING("bbbbb"))); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE_STRING("bbbbb"))); + ASSERT_OK_POSITIVE(iovec_done_and_memdup(&iov, NULL)); + ASSERT_TRUE(!iovec_is_set(&iov)); + ASSERT_OK_POSITIVE(iovec_done_and_memdup(&iov, &IOVEC_MAKE_STRING("ccc"))); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE_STRING("ccc"))); + ASSERT_OK_POSITIVE(iovec_done_and_memdup(&iov, &(struct iovec) {})); + ASSERT_TRUE(!iovec_is_set(&iov)); + ASSERT_OK_ZERO(iovec_done_and_memdup(&iov, &iov)); + ASSERT_TRUE(!iovec_is_set(&iov)); + ASSERT_OK_POSITIVE(iovec_done_and_memdup(&iov, &IOVEC_MAKE_STRING("ddd"))); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE_STRING("ddd"))); + ASSERT_OK_ZERO(iovec_done_and_memdup(&iov, &iov)); + ASSERT_TRUE(iovec_equal(&iov, &IOVEC_MAKE_STRING("ddd"))); +} + 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..523089058707c --- /dev/null +++ b/src/test/test-iovec-wrapper.c @@ -0,0 +1,702 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "iovec-util.h" +#include "iovec-wrapper.h" +#include "random-util.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); + + ASSERT_OK(iovw_put_full(&iovw, /* accept_zero= */ false, NULL, 0)); + ASSERT_EQ(iovw.count, 3U); + ASSERT_OK(iovw_put_full(&iovw, /* accept_zero= */ true, NULL, 0)); + ASSERT_EQ(iovw.count, 4U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[3], &(struct iovec) {})); +} + +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"))); + + ASSERT_OK(iovw_put_iov_full(&iovw, /* accept_zero= */ false, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 3U); + ASSERT_OK(iovw_put_iov_full(&iovw, /* accept_zero= */ true, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 4U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[3], &(struct iovec) {})); +} + +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_OK(iovw_put_iov_full(&source, /* accept_zero= */ true, &(struct iovec) {})); + ASSERT_EQ(source.count, 4U); + + /* 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, 4U); + 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"))); + ASSERT_TRUE(iovec_equal(&source.iovec[3], &(struct iovec) {})); + + ASSERT_OK(iovw_put_iovw_full(&target, /* accept_zero= */ true, &source)); + ASSERT_EQ(target.count, 10U); + 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"))); + ASSERT_TRUE(iovec_equal(&target.iovec[6], &IOVEC_MAKE_STRING("aaa"))); + ASSERT_TRUE(iovec_equal(&target.iovec[7], &IOVEC_MAKE_STRING("bbb"))); + ASSERT_TRUE(iovec_equal(&target.iovec[8], &IOVEC_MAKE_STRING("ccc"))); + ASSERT_TRUE(iovec_equal(&target.iovec[9], &(struct iovec) {})); + + /* 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); + + ASSERT_OK(iovw_extend_full(&iovw, /* accept_zero= */ false, NULL, 0)); + ASSERT_EQ(iovw.count, 2U); + ASSERT_OK(iovw_extend_full(&iovw, /* accept_zero= */ true, NULL, 0)); + ASSERT_EQ(iovw.count, 3U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &(struct iovec) {})); +} + +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"))); + + ASSERT_OK(iovw_extend_iov_full(&iovw, /* accept_zero= */ false, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 3U); + ASSERT_OK(iovw_extend_iov_full(&iovw, /* accept_zero= */ true, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 4U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[3], &(struct iovec) {})); +} + +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_OK(iovw_put_full(&source, /* accept_zero= */ true, NULL, 0)); + ASSERT_EQ(source.count, 3U); + + /* 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); + + ASSERT_OK(iovw_extend_iovw_full(&target, /* accept_zero= */ true, &source)); + ASSERT_EQ(target.count, 6U); + ASSERT_TRUE(iovec_equal(&target.iovec[0], &IOVEC_MAKE_STRING("zero"))); + ASSERT_TRUE(iovec_equal(&target.iovec[1], &IOVEC_MAKE_STRING("one"))); + ASSERT_TRUE(iovec_equal(&target.iovec[2], &IOVEC_MAKE_STRING("twotwo"))); + ASSERT_TRUE(iovec_equal(&target.iovec[3], &IOVEC_MAKE_STRING("one"))); + ASSERT_TRUE(iovec_equal(&target.iovec[4], &IOVEC_MAKE_STRING("twotwo"))); + ASSERT_TRUE(iovec_equal(&target.iovec[5], &(struct iovec) {})); + + /* Source is unchanged */ + ASSERT_EQ(source.count, 3U); + + /* 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); + + ASSERT_OK(iovw_consume_full(&iovw, /* accept_zero= */ false, NULL, 0)); + ASSERT_EQ(iovw.count, 1U); + ASSERT_OK(iovw_consume_full(&iovw, /* accept_zero= */ true, NULL, 0)); + ASSERT_EQ(iovw.count, 2U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[1], &(struct iovec) {})); + q = ASSERT_NOT_NULL(strdup("")); + ASSERT_OK(iovw_consume_full(&iovw, /* accept_zero= */ true, q, 0)); + ASSERT_EQ(iovw.count, 3U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &(struct iovec) {})); +} + +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); + + ASSERT_OK(iovw_consume_iov_full(&iovw, /* accept_zero= */ false, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 1U); + ASSERT_OK(iovw_consume_iov_full(&iovw, /* accept_zero= */ true, &(struct iovec) {})); + ASSERT_EQ(iovw.count, 2U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[1], &(struct iovec) {})); + iov = (struct iovec) { + .iov_base = ASSERT_NOT_NULL(strdup("")), + .iov_len = 0, + }; + ASSERT_OK(iovw_consume_iov_full(&iovw, /* accept_zero= */ true, &iov)); + ASSERT_EQ(iovw.count, 3U); + ASSERT_TRUE(iovec_equal(&iovw.iovec[2], &(struct iovec) {})); +} + +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); + + ASSERT_EQ(iovw_size(NULL), 0U); +} + +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"); +} + +TEST(iovw_merge_and_iovec_split) { + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}, iovw2 = {}; + _cleanup_(iovec_done) struct iovec v = {}, v2 = {}; + uint8_t *p; + + struct iovec + a = IOVEC_MAKE_STRING("aaa"), + b = IOVEC_MAKE_STRING("bbbb"), + c = IOVEC_MAKE_STRING("ccccc"); + + /* single entry */ + ASSERT_OK(iovw_extend_iov(&iovw, &a)); + + ASSERT_OK(iovw_merge(&iovw, sizeof(uint8_t), &v)); + ASSERT_EQ(v.iov_len, 1 + a.iov_len); + p = ASSERT_NOT_NULL(v.iov_base); + ASSERT_EQ(*p++, a.iov_len); + ASSERT_EQ(memcmp(p, a.iov_base, a.iov_len), 0); + p += a.iov_len; + ASSERT_OK(iovec_split(&v, sizeof(uint8_t), &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + iovec_done(&v); + iovw_done_free(&iovw2); + + ASSERT_OK(iovw_merge(&iovw, sizeof(uint16_t), &v)); + ASSERT_EQ(v.iov_len, sizeof(uint16_t) + a.iov_len); + p = ASSERT_NOT_NULL(v.iov_base); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, a.iov_len); + ASSERT_EQ(memcmp(p, a.iov_base, a.iov_len), 0); + p += a.iov_len; + ASSERT_OK(iovec_split(&v, sizeof(uint16_t), &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + iovec_done(&v); + iovw_done_free(&iovw2); + + ASSERT_OK(iovw_merge(&iovw, sizeof(uint32_t), &v)); + ASSERT_EQ(v.iov_len, sizeof(uint32_t) + a.iov_len); + p = ASSERT_NOT_NULL(v.iov_base); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, a.iov_len); + ASSERT_EQ(memcmp(p, a.iov_base, a.iov_len), 0); + p += a.iov_len; + ASSERT_OK(iovec_split(&v, sizeof(uint32_t), &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + iovec_done(&v); + iovw_done_free(&iovw2); + + /* multiple entries */ + ASSERT_OK(iovw_extend_iov(&iovw, &b)); + ASSERT_OK(iovw_extend_iov(&iovw, &c)); + + ASSERT_OK(iovw_merge(&iovw, sizeof(uint8_t), &v)); + ASSERT_EQ(v.iov_len, 3 + a.iov_len + b.iov_len + c.iov_len); + p = ASSERT_NOT_NULL(v.iov_base); + ASSERT_EQ(*p++, a.iov_len); + ASSERT_EQ(memcmp(p, a.iov_base, a.iov_len), 0); + p += a.iov_len; + ASSERT_EQ(*p++, b.iov_len); + ASSERT_EQ(memcmp(p, b.iov_base, b.iov_len), 0); + p += b.iov_len; + ASSERT_EQ(*p++, c.iov_len); + ASSERT_EQ(memcmp(p, c.iov_base, c.iov_len), 0); + ASSERT_OK(iovec_split(&v, sizeof(uint8_t), &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + iovec_done(&v); + iovw_done_free(&iovw2); + + ASSERT_OK(iovw_merge(&iovw, sizeof(uint16_t), &v)); + ASSERT_EQ(v.iov_len, 3 * sizeof(uint16_t) + a.iov_len + b.iov_len + c.iov_len); + p = ASSERT_NOT_NULL(v.iov_base); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, a.iov_len); + ASSERT_EQ(memcmp(p, a.iov_base, a.iov_len), 0); + p += a.iov_len; + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, b.iov_len); + ASSERT_EQ(memcmp(p, b.iov_base, b.iov_len), 0); + p += b.iov_len; + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, c.iov_len); + ASSERT_EQ(memcmp(p, c.iov_base, c.iov_len), 0); + ASSERT_OK(iovec_split(&v, sizeof(uint16_t), &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + iovec_done(&v); + iovw_done_free(&iovw2); + + ASSERT_OK(iovw_merge(&iovw, sizeof(uint32_t), &v)); + ASSERT_EQ(v.iov_len, 3 * sizeof(uint32_t) + a.iov_len + b.iov_len + c.iov_len); + p = ASSERT_NOT_NULL(v.iov_base); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, a.iov_len); + ASSERT_EQ(memcmp(p, a.iov_base, a.iov_len), 0); + p += a.iov_len; + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, b.iov_len); + ASSERT_EQ(memcmp(p, b.iov_base, b.iov_len), 0); + p += b.iov_len; + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, 0); + ASSERT_EQ(*p++, c.iov_len); + ASSERT_EQ(memcmp(p, c.iov_base, c.iov_len), 0); + ASSERT_OK(iovec_split(&v, sizeof(uint32_t), &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + iovec_done(&v); + iovw_done_free(&iovw2); + + /* with empty entries */ + _cleanup_(iovw_done) struct iovec_wrapper with_empty = { + .iovec = ASSERT_PTR(new0(struct iovec, 6)), + .count = 6, + }; + with_empty.iovec[0] = a; + with_empty.iovec[2] = b; + with_empty.iovec[4] = c; + ASSERT_OK(iovw_merge(&iovw, sizeof(uint8_t), &v)); + ASSERT_OK(iovw_merge(&with_empty, sizeof(uint8_t), &v2)); + ASSERT_TRUE(iovec_equal(&v, &v2)); + + iovec_done(&v); + iovec_done(&v2); + + size_t sz = 6 + a.iov_len + b.iov_len + c.iov_len; + _cleanup_free_ uint8_t *buf = ASSERT_PTR(new(uint8_t, sz)); + p = buf; + *p++ = a.iov_len; + p = mempcpy(p, a.iov_base, a.iov_len); + *p++ = 0; + *p++ = b.iov_len; + p = mempcpy(p, b.iov_base, b.iov_len); + *p++ = 0; + *p++ = c.iov_len; + p = mempcpy(p, c.iov_base, c.iov_len); + *p++ = 0; + ASSERT_OK(iovec_split(&IOVEC_MAKE(buf, sz), sizeof(uint8_t), &iovw2)); + ASSERT_TRUE(iovw_equal(&iovw, &iovw2)); + + iovw_done_free(&iovw2); + + /* truncated */ + ASSERT_OK(iovw_merge(&iovw, sizeof(uint8_t), &v)); + ASSERT_ERROR(iovec_split(&IOVEC_MAKE(v.iov_base, v.iov_len - 1), sizeof(uint8_t), &iovw2), EBADMSG); + + iovec_done(&v); + + /* too long */ + _cleanup_(iovec_done) struct iovec large = {}; + ASSERT_OK(random_bytes_allocate_iovec(256, &large)); + ASSERT_ERROR(iovw_merge(&(struct iovec_wrapper) { .iovec = &large, .count = 1, }, sizeof(uint8_t), &v), ERANGE); + ASSERT_OK(iovw_merge(&(struct iovec_wrapper) { .iovec = &large, .count = 1, }, sizeof(uint16_t), &v)); + ASSERT_OK(iovec_split(&v, sizeof(uint16_t), &iovw2)); + ASSERT_EQ(iovw2.count, 1u); + ASSERT_TRUE(iovec_equal(&iovw2.iovec[0], &large)); + + iovec_done(&v); + iovw_done_free(&iovw2); + + /* No entry */ + ASSERT_OK(iovw_merge(&(struct iovec_wrapper) {}, sizeof(uint8_t), &v)); + ASSERT_FALSE(iovec_is_set(&v)); + + ASSERT_OK(iovw_merge(NULL, sizeof(uint8_t), &v)); + ASSERT_FALSE(iovec_is_set(&v)); + + ASSERT_OK(iovec_split(&(struct iovec) {}, sizeof(uint8_t), &iovw2)); + ASSERT_TRUE(iovw_isempty(&iovw2)); + + ASSERT_OK(iovec_split(NULL, sizeof(uint8_t), &iovw2)); + ASSERT_TRUE(iovw_isempty(&iovw2)); + + /* empty entry only */ + ASSERT_OK(iovw_merge(&(struct iovec_wrapper) { .iovec = &(struct iovec) {}, .count = 1, }, sizeof(uint8_t), &v)); + ASSERT_FALSE(iovec_is_set(&v)); + + ASSERT_OK(iovec_split(&IOVEC_MAKE("", 1), sizeof(uint8_t), &iovw2)); + ASSERT_TRUE(iovw_isempty(&iovw2)); + +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-ipcrm.c b/src/test/test-ipcrm.c index 238f0bf666987..057dc0ae05a72 100644 --- a/src/test/test-ipcrm.c +++ b/src/test/test-ipcrm.c @@ -13,7 +13,7 @@ static int run(int argc, char *argv[]) { test_setup_logging(LOG_INFO); - r = get_user_creds(&name, &uid, NULL, NULL, NULL, 0); + r = get_user_creds(name, /* flags= */ 0, NULL, &uid, NULL, NULL, NULL); if (r == -ESRCH) return log_tests_skipped("Failed to resolve user"); if (r < 0) 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-label.c b/src/test/test-label.c index 5ce9988ce277d..3bd96a3a1b794 100644 --- a/src/test/test-label.c +++ b/src/test/test-label.c @@ -7,7 +7,7 @@ #include "errno-util.h" #include "fd-util.h" #include "fs-util.h" -#include "label.h" +#include "label-util.h" #include "path-util.h" #include "string-util.h" #include "tests.h" diff --git a/src/test/test-load-fragment.c b/src/test/test-load-fragment.c index 892e471d8ab0c..259bd142e4efb 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; @@ -108,7 +108,7 @@ TEST(config_parse_exec) { } ASSERT_OK(r); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service))); @@ -430,7 +430,7 @@ TEST(config_parse_log_extra_fields) { } ASSERT_OK(r); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service))); @@ -788,7 +788,7 @@ TEST(config_parse_unit_env_file) { } ASSERT_OK(r); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service))); ASSERT_OK_ZERO(unit_add_name(u, "foobar.service")); @@ -912,7 +912,7 @@ TEST(unit_is_recursive_template_dependency) { } ASSERT_OK(r); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service))); ASSERT_OK_ZERO(unit_add_name(u, "foobar@1.service")); @@ -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) { @@ -1006,7 +1006,7 @@ TEST(config_parse_open_file) { } ASSERT_OK(r); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service))); ASSERT_OK_ZERO(unit_add_name(u, "foobar.service")); @@ -1065,7 +1065,7 @@ TEST(config_parse_service_refresh_on_reload) { } ASSERT_OK(r); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service))); ASSERT_OK_ZERO(unit_add_name(u, "foobar.service")); 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-block.c deleted file mode 100644 index 76046f98ada19..0000000000000 --- a/src/test/test-loop-block.c +++ /dev/null @@ -1,380 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-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" -#include "parse-util.h" -#include "path-util.h" -#include "string-util.h" -#include "strv.h" -#include "tests.h" -#include "time-util.h" -#include "tmpfile-util.h" -#include "virt.h" - -static unsigned arg_n_threads = 5; -static unsigned arg_n_iterations = 3; -static usec_t arg_timeout = 0; - -#if HAVE_BLKID -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); -} - -static void verify_dissected_image_harder(DissectedImage *dissected) { - verify_dissected_image(dissected); - - ASSERT_STREQ(dissected->partitions[PARTITION_ESP].fstype, "vfat"); - ASSERT_STREQ(dissected->partitions[PARTITION_XBOOTLDR].fstype, "vfat"); - ASSERT_STREQ(dissected->partitions[PARTITION_ROOT].fstype, "ext4"); - ASSERT_STREQ(dissected->partitions[PARTITION_HOME].fstype, "ext4"); -} - -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; - _cleanup_(umount_and_rmdir_and_freep) char *mounted = NULL; - _cleanup_(dissected_image_unrefp) DissectedImage *dissected = NULL; - - if (now(CLOCK_MONOTONIC) >= end) { - log_notice("Time's up, exiting thread's loop"); - break; - } - - log_notice("> Thread iteration #%u.", i); - - assert_se(mkdtemp_malloc(NULL, &mounted) >= 0); - - 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); - - log_notice("Acquired loop device %s, will mount on %s", loop->node, mounted); - - r = 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); - - log_info("Dissected loop device %s", loop->node); - - for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) { - if (!dissected->partitions[d].found) - continue; - - log_notice("Found node %s fstype %s designator %s", - dissected->partitions[d].node, - dissected->partitions[d].fstype, - partition_designator_to_string(d)); - } - - verify_dissected_image(dissected); - - r = 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); - - /* 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); - - log_notice("Unmounting %s", mounted); - mounted = umount_and_rmdir_and_free(mounted); - - log_notice("Unmounted."); - - dissected = dissected_image_unref(dissected); - - log_notice("Detaching loop device %s", loop->node); - loop = loop_device_unref(loop); - log_notice("Detached loop device."); - } - - log_notice("Leaving thread"); - - return NULL; -} -#endif - -static bool have_root_gpt_type(void) { -#ifdef SD_GPT_ROOT_NATIVE - return true; -#else - return false; -#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; - 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 (r < 0) - return log_error_errno(r, "Failed to parse first argument (number of threads): %s", 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 (r < 0) - return log_error_errno(r, "Failed to parse second argument (number of iterations): %s", 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 (r < 0) - return log_error_errno(r, "Failed to parse third argument (timeout): %s", argv[3]); - } - - if (argc >= 5) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments (expected 3 at max)."); - - if (!have_root_gpt_type()) - return log_tests_skipped("No root partition GPT defined for this architecture"); - - r = find_executable("sfdisk", NULL); - 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); - - assert_se(cmd = strjoin("sfdisk ", p)); - assert_se(sfdisk = popen(cmd, "we")); - - /* A reasonably complex partition table that fits on a 64K disk */ - fputs("label: gpt\n" - "size=32M, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B\n" - "size=32M, type=BC13C2FF-59E6-4262-A352-B275FD6F7172\n" - "size=32M, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F\n" - "size=32M, type=", sfdisk); - -#ifdef SD_GPT_ROOT_NATIVE - fprintf(sfdisk, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_GPT_ROOT_NATIVE)); -#else - fprintf(sfdisk, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_GPT_ROOT_X86_64)); -#endif - - fputs("\n" - "size=32M, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915\n", sfdisk); - - assert_se(pclose(sfdisk) == 0); - sfdisk = NULL; - -#if HAVE_BLKID - assert_se(dissect_image_file( - p, - /* verity= */ NULL, - /* mount_options= */ NULL, - /* image_policy= */ NULL, - /* image_filter= */ NULL, - /* flags= */ 0, - &dissected) >= 0); - 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 (detect_container() != 0 || running_in_chroot() != 0) - return log_tests_skipped("Test not supported in a container/chroot, requires udev/uevent notifications"); - - assert_se(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &loop) >= 0); - -#if HAVE_BLKID - assert_se(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); - verify_dissected_image(dissected); - - FOREACH_STRING(fs, "vfat", "ext4") { - r = mkfs_exists(fs); - assert_se(r >= 0); - if (!r) { - log_tests_skipped("mkfs.{vfat|ext4} not installed"); - return 0; - } - } - 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_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_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_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); - - dissected = dissected_image_unref(dissected); - - /* We created the file systems now via the per-partition block devices. But the dissection code might - * probe them via the whole block device. These block devices have separate buffer caches though, - * 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); - - /* Try to read once, without pinning or adding partitions, i.e. by only accessing the whole block - * device. */ - assert_se(dissect_loop_device( - loop, - /* verity= */ NULL, - /* mount_options= */ NULL, - /* image_policy= */ NULL, - /* image_filter= */ NULL, - /* flags= */ 0, - &dissected) >= 0); - 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( - loop, - /* verity= */ NULL, - /* mount_options= */ NULL, - /* image_policy= */ NULL, - /* image_filter= */ NULL, - DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, - &dissected) >= 0); - verify_dissected_image_harder(dissected); - - assert_se(mkdtemp_malloc(NULL, &mounted) >= 0); - - /* 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); - - /* 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); - - /* This first (writable) mount will initialize the mount point dirs, so that the subsequent read-only ones can work */ - assert_se(dissected_image_mount( - dissected, - mounted, - /* uid_shift= */ UID_INVALID, - /* uid_range= */ UID_INVALID, - /* userns_fd= */ -EBADF, - 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_se(umount_recursive(mounted, 0) >= 0); - loop = loop_device_unref(loop); - - log_notice("Threads are being started now"); - - /* zero timeout means pick default: let's make sure we run for 10s on slow systems at max */ - if (arg_timeout == 0) - arg_timeout = slow_tests_enabled() ? 5 * USEC_PER_SEC : 1 * USEC_PER_SEC; - - end = usec_add(now(CLOCK_MONOTONIC), arg_timeout); - - 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); - - log_notice("All threads started now."); - - if (arg_n_threads == 1) - ASSERT_NULL(thread_func(FD_TO_PTR(fd))); - else - for (unsigned i = 0; i < arg_n_threads; i++) { - log_notice("Joining thread #%u.", i); - - void *k; - assert_se(pthread_join(threads[i], &k) == 0); - assert_se(!k); - - log_notice("Joined thread #%u.", i); - } - - log_notice("Threads are all terminated now."); -#else - log_notice("Cutting test short, since we do not have libblkid."); -#endif - return 0; -} - -DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/test/test-loop-util.c b/src/test/test-loop-util.c new file mode 100644 index 0000000000000..00fe1a33f6102 --- /dev/null +++ b/src/test/test-loop-util.c @@ -0,0 +1,709 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include +#include +#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 "mkfs-util.h" +#include "mount-util.h" +#include "namespace-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "string-util.h" +#include "strv.h" +#include "tests.h" +#include "time-util.h" +#include "tmpfile-util.h" +#include "virt.h" + +static unsigned arg_n_threads = 5; +static unsigned arg_n_iterations = 3; +static usec_t arg_timeout = 0; + +#if HAVE_BLKID +static usec_t end = 0; + +static void verify_dissected_image(DissectedImage *dissected) { + 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) { + verify_dissected_image(dissected); + + ASSERT_STREQ(dissected->partitions[PARTITION_ESP].fstype, "vfat"); + ASSERT_STREQ(dissected->partitions[PARTITION_XBOOTLDR].fstype, "vfat"); + ASSERT_STREQ(dissected->partitions[PARTITION_ROOT].fstype, "ext4"); + ASSERT_STREQ(dissected->partitions[PARTITION_HOME].fstype, "ext4"); +} + +static void* thread_func(void *ptr) { + int fd = PTR_TO_FD(ptr); + + for (unsigned i = 0; i < arg_n_iterations; i++) { + _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *mounted = NULL; + _cleanup_(dissected_image_unrefp) DissectedImage *dissected = NULL; + + if (now(CLOCK_MONOTONIC) >= end) { + log_notice("Time's up, exiting thread's loop"); + break; + } + + log_notice("> Thread iteration #%u.", i); + + ASSERT_OK(mkdtemp_malloc(NULL, &mounted)); + + 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); + + 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)); + + log_info("Dissected loop device %s", loop->node); + + for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) { + if (!dissected->partitions[d].found) + continue; + + log_notice("Found node %s fstype %s designator %s", + dissected->partitions[d].node, + dissected->partitions[d].fstype, + partition_designator_to_string(d)); + } + + verify_dissected_image(dissected); + + ASSERT_OK(dissected_image_mount( + dissected, + mounted, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* userns_fd= */ -EBADF, + 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_OK(loop_device_flock(loop, LOCK_UN)); + + log_notice("Unmounting %s", mounted); + mounted = umount_and_rmdir_and_free(mounted); + + log_notice("Unmounted."); + + dissected = dissected_image_unref(dissected); + + log_notice("Detaching loop device %s", loop->node); + loop = loop_device_unref(loop); + log_notice("Detached loop device."); + } + + log_notice("Leaving thread"); + + return NULL; +} +#endif + +static bool have_root_gpt_type(void) { +#ifdef SD_GPT_ROOT_NATIVE + return true; +#else + return false; +#endif +} + +static int intro(void) { + int r; + + log_show_tid(true); + log_show_time(true); + log_show_color(true); + + 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", 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 (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", 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 (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", saved_argv[3]); + } + + if (saved_argc >= 5) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments (expected 3 at max)."); + + if (!have_root_gpt_type()) + return log_tests_skipped("No root partition GPT defined for this architecture"); + + r = find_executable("sfdisk", NULL); + if (r < 0) + return log_tests_skipped_errno(r, "Could not find sfdisk command"); + + return EXIT_SUCCESS; +} + +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" + "size=32M, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B\n" + "size=32M, type=BC13C2FF-59E6-4262-A352-B275FD6F7172\n" + "size=32M, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F\n" + "size=32M, type=", sfdisk); + +#ifdef SD_GPT_ROOT_NATIVE + fprintf(sfdisk, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_GPT_ROOT_NATIVE)); +#else + fprintf(sfdisk, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(SD_GPT_ROOT_X86_64)); +#endif + + fputs("\n" + "size=32M, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915\n", sfdisk); + + ASSERT_EQ(pclose(sfdisk), 0); + sfdisk = NULL; + +#if HAVE_BLKID + ASSERT_OK(dissect_image_file( + p, + /* verity= */ NULL, + /* mount_options= */ NULL, + /* image_policy= */ NULL, + /* image_filter= */ NULL, + /* flags= */ 0, + &dissected)); + verify_dissected_image(dissected); + dissected = dissected_image_unref(dissected); +#endif + + 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(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_EX, &loop)); + +#if HAVE_BLKID + 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)); + verify_dissected_image(dissected); + + FOREACH_STRING(fs, "vfat", "ext4") { + if (ASSERT_OK(mkfs_exists(fs)) == 0) { + log_tests_skipped("mkfs.{vfat|ext4} not installed"); + return; + } + } + + 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_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_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_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); + + /* We created the file systems now via the per-partition block devices. But the dissection code might + * probe them via the whole block device. These block devices have separate buffer caches though, + * 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_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_OK(dissect_loop_device( + loop, + /* verity= */ NULL, + /* mount_options= */ NULL, + /* image_policy= */ NULL, + /* image_filter= */ NULL, + /* flags= */ 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_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)); + verify_dissected_image_harder(dissected); + + 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_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_OK(detach_mount_namespace()); + + /* This first (writable) mount will initialize the mount point dirs, so that the subsequent read-only ones can work */ + ASSERT_OK(dissected_image_mount( + dissected, + mounted, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + /* userns_fd= */ -EBADF, + 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_OK(loop_device_flock(loop, LOCK_UN)); + + ASSERT_OK(umount_recursive(mounted, 0)); + loop = loop_device_unref(loop); + + log_notice("Threads are being started now"); + + /* zero timeout means pick default: let's make sure we run for 10s on slow systems at max */ + if (arg_timeout == 0) + arg_timeout = slow_tests_enabled() ? 5 * USEC_PER_SEC : 1 * USEC_PER_SEC; + + end = usec_add(now(CLOCK_MONOTONIC), arg_timeout); + + if (arg_n_threads > 1) + for (unsigned i = 0; i < arg_n_threads; i++) + ASSERT_EQ(pthread_create(threads + i, NULL, thread_func, FD_TO_PTR(fd)), 0); + + log_notice("All threads started now."); + + if (arg_n_threads == 1) + ASSERT_NULL(thread_func(FD_TO_PTR(fd))); + else + for (unsigned i = 0; i < arg_n_threads; i++) { + log_notice("Joining thread #%u.", i); + + void *k; + ASSERT_EQ(pthread_join(threads[i], &k), 0); + ASSERT_NULL(k); + + log_notice("Joined thread #%u.", i); + } + + log_notice("Threads are all terminated now."); +#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; +} + +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); +} + +TEST(partscan_required) { + _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)); + + /* Set up a backing loop device without LO_FLAGS_PARTSCAN. */ + ASSERT_OK(loop_device_make(fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_EX, &block_loop)); + ASSERT_TRUE(block_loop->created); + ASSERT_OK(loop_device_flock(block_loop, LOCK_SH)); + + /* Without LO_FLAGS_PARTSCAN: shortcut should be taken (reuse existing loop). */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 0, 0, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + loop = loop_device_unref(loop); + + /* With LO_FLAGS_PARTSCAN: backing loop has partscan disabled, so a new loop device with + * partscan must be created. */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, 0, UINT64_MAX, 0, LO_FLAGS_PARTSCAN, LOCK_SH, &loop)); + ASSERT_TRUE(loop->created); + loop = loop_device_unref(loop); +} + +TEST(partscan_not_needed_without_partition_table) { +#if HAVE_BLKID + _cleanup_(loop_device_unrefp) LoopDevice *block_loop = NULL, *loop = NULL; + _cleanup_free_ char *p = 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; + } + + /* The regression in 663f0bf5cb allocated a loop device whenever partition scanning was requested but + * couldn't be enabled, regardless of the device's contents. That's harmless for most file systems but + * fatal for multi-device btrfs, which rejects seeing the same member via both the original device and + * the loop device (https://github.com/systemd/systemd/issues/42520). A loop device is only ever needed + * to expose a nested partition table though, so any device without one — here simply an empty device — + * must take the shortcut. */ + ASSERT_OK(tempfn_random_child("/var/tmp", "loop-util", &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)); + (void) unlink(p); + + /* Set up a backing loop device without LO_FLAGS_PARTSCAN. */ + ASSERT_OK(loop_device_make(fd, O_RDWR, /* offset= */ 0, UINT64_MAX, /* sector_size= */ 0, /* loop_flags= */ 0, LOCK_EX, &block_loop)); + ASSERT_TRUE(block_loop->created); + ASSERT_OK(loop_device_flock(block_loop, LOCK_SH)); + + /* By default LO_FLAGS_PARTSCAN is requested but there's no partition table to scan, so the shortcut + * must be taken (reuse the device) rather than allocating a new loop device. */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, /* offset= */ 0, UINT64_MAX, /* sector_size= */ 0, LO_FLAGS_PARTSCAN, LOCK_SH, &loop)); + ASSERT_FALSE(loop->created); + loop = loop_device_unref(loop); + + /* But a caller that declares it may populate the image with a partition table must get a real loop + * device even though the device is currently unpartitioned. */ + ASSERT_OK(loop_device_make(block_loop->fd, O_RDWR, /* offset= */ 0, UINT64_MAX, /* sector_size= */ 0, LO_FLAGS_PARTSCAN|LOOP_DEVICE_MAY_POPULATE_PARTITION_TABLE, LOCK_SH, &loop)); + ASSERT_TRUE(loop->created); +#else + log_tests_skipped("blkid not available"); +#endif +} + +static void test_nested_partition_table_one(const char *nested_table) { +#if HAVE_BLKID + _cleanup_(dissected_image_unrefp) DissectedImage *dissected = NULL; + _cleanup_(loop_device_unrefp) LoopDevice *outer_loop = NULL, *loop = NULL; + _cleanup_pclose_ FILE *sfdisk = NULL; + _cleanup_close_ int fd = -EBADF, part_fd = -EBADF; + _cleanup_free_ char *p = NULL, *cmd = NULL; + const char *node; + + assert(nested_table); + + 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; + } + + /* Build an image with a single root partition spanning the whole disk. */ + 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")); + fputs("label: gpt\n" + "type=", sfdisk); +#ifdef SD_GPT_ROOT_NATIVE + fprintf(sfdisk, SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(SD_GPT_ROOT_NATIVE)); +#else + fprintf(sfdisk, SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(SD_GPT_ROOT_X86_64)); +#endif + ASSERT_EQ(pclose(sfdisk), 0); + sfdisk = NULL; + (void) unlink(p); + + /* Wrap it in a loopback device with partition scanning and let the dissection logic materialize the + * partition block device via BLKPG. That's synchronous, so unlike waiting for udev to create the + * node this is not racy. */ + ASSERT_OK(loop_device_make(fd, O_RDWR, /* offset= */ 0, UINT64_MAX, /* sector_size= */ 0, LO_FLAGS_PARTSCAN, LOCK_EX, &outer_loop)); + ASSERT_OK(dissect_loop_device( + outer_loop, + /* verity= */ NULL, + /* mount_options= */ NULL, + /* image_policy= */ NULL, + /* image_filter= */ NULL, + DISSECT_IMAGE_ADD_PARTITION_DEVICES|DISSECT_IMAGE_PIN_PARTITION_DEVICES, + &dissected)); + ASSERT_TRUE(dissected->partitions[PARTITION_ROOT].found); + node = ASSERT_NOT_NULL(dissected->partitions[PARTITION_ROOT].node); + + /* Carve a nested partition table into that partition, mimicking the pmOS/android case (663f0bf5cb) + * where a partition carries a partition table the kernel won't scan, as partition devices don't + * support partition scanning. We write and read it back through the same partition node, so the + * buffer cache stays coherent. */ + cmd = mfree(cmd); + cmd = ASSERT_NOT_NULL(strjoin("sfdisk --no-reread --no-tell-kernel ", node)); + sfdisk = ASSERT_NOT_NULL(popen(cmd, "we")); + fputs(nested_table, sfdisk); + ASSERT_EQ(pclose(sfdisk), 0); + sfdisk = NULL; + + /* The partition has partition scanning disabled but now carries a partition table, so the shortcut + * must be refused and a real loop device with partition scanning allocated (even without + * LOOP_DEVICE_MAY_POPULATE_PARTITION_TABLE, since there genuinely is a table to scan). */ + part_fd = ASSERT_OK_ERRNO(open(node, O_RDWR|O_CLOEXEC|O_NOCTTY)); + ASSERT_OK(loop_device_make(part_fd, O_RDWR, /* offset= */ 0, UINT64_MAX, /* sector_size= */ 0, LO_FLAGS_PARTSCAN, LOCK_SH, &loop)); + ASSERT_TRUE(loop->created); +#else + log_tests_skipped("blkid not available"); +#endif +} + +TEST(partscan_required_for_nested_gpt) { + test_nested_partition_table_one("label: gpt\n" + "size=1MiB, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4\n"); +} + +TEST(partscan_required_for_nested_mbr) { + /* Make sure an MBR ("dos") table — which the dissection logic acts on just like GPT — likewise + * prevents the shortcut, i.e. partition-table detection isn't limited to GPT. */ + test_nested_partition_table_one("label: dos\n" + "size=1MiB, type=83\n"); +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/test/test-luo.c b/src/test/test-luo.c new file mode 100644 index 0000000000000..9fd7ec80042a4 --- /dev/null +++ b/src/test/test-luo.c @@ -0,0 +1,404 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* Helper for TEST-91-LIVEUPDATE: creates memfds and stores them in the fd store, + * creates a LUO session directly via /dev/liveupdate and stores a memfd in it, + * or verifies everything after kexec. + * + * Usage: + * test-luo store - create memfds and a LUO session, push all to the fd store + * test-luo check - verify fd store content and LUO session memfd after kexec + * test-luo store-hijack - store a fd store entry holding a child LUO session named like + * PID 1's own ("systemd"), to exercise the serialize-side anti-hijack guard + * test-luo check-hijack - verify the hijacking session fd was NOT serialized/restored after kexec + */ + +#include +#include +#include + +#include "sd-daemon.h" + +#include "fd-util.h" +#include "log.h" +#include "luo-util.h" +#include "main-func.h" +#include "memfd-util.h" +#include "parse-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" + +#define TEST_DATA_1 "liveupdate-test-data-1" +#define TEST_DATA_2 "liveupdate-test-data-2" +#define SESSION_MEMFD_DATA "luo-session-memfd-test-data" +#define SESSION_MEMFD_TOKEN UINT64_C(42) + +/* Name PID 1 reserves for its own LUO session (must match LUO_SESSION_NAME). A unit that tries to + * preserve a child session under this name is an attempt to hijack PID 1's namespace, and the + * serialize-side guard in manager_luo_serialize_fd_stores() must refuse to serialize it. */ +#define HIJACK_SESSION_NAME LUO_SESSION_NAME +#define HIJACK_FDNAME "hijackfd" +#define HIJACK_MEMFD_DATA "luo-hijack-memfd-test-data" +#define HIJACK_MEMFD_TOKEN UINT64_C(99) + +static int do_store(const char *prefix) { + _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF; + int r; + + fd1 = memfd_new_and_seal("luo-test-1", TEST_DATA_1, strlen(TEST_DATA_1)); + if (fd1 < 0) + return log_error_errno(fd1, "Failed to create memfd 1: %m"); + + fd2 = memfd_new_and_seal("luo-test-2", TEST_DATA_2, strlen(TEST_DATA_2)); + if (fd2 < 0) + return log_error_errno(fd2, "Failed to create memfd 2: %m"); + + r = sd_pid_notify_with_fds(0, /* unset_environment= */ false, "FDSTORE=1\nFDNAME=testfd1", &fd1, 1); + if (r < 0) + return log_error_errno(r, "Failed to store memfd 1 in fd store: %m"); + + r = sd_pid_notify_with_fds(0, /* unset_environment= */ false, "FDSTORE=1\nFDNAME=testfd2", &fd2, 1); + if (r < 0) + return log_error_errno(r, "Failed to store memfd 2 in fd store: %m"); + + log_info("Stored 2 memfds in fd store."); + + /* Create a LUO session directly via /dev/liveupdate, put a memfd in it, and store the session fd */ + _cleanup_close_ int device_fd = -EBADF, session_fd = -EBADF, session_memfd = -EBADF; + const char *session_name = strjoina(prefix, "-direct"); + + device_fd = luo_open_device(); + if (device_fd < 0) + return log_error_errno(device_fd, "Failed to open /dev/liveupdate: %m"); + + session_fd = luo_create_session(device_fd, session_name); + if (session_fd < 0) + return log_error_errno(session_fd, "Failed to create LUO session '%s': %m", session_name); + + session_memfd = memfd_new_and_seal("session-test", SESSION_MEMFD_DATA, strlen(SESSION_MEMFD_DATA)); + if (session_memfd < 0) + return log_error_errno(session_memfd, "Failed to create session memfd: %m"); + + r = luo_session_preserve_fd(session_fd, session_memfd, SESSION_MEMFD_TOKEN); + if (r < 0) + return log_error_errno(r, "Failed to preserve memfd in session: %m"); + + r = sd_pid_notifyf_with_fds(0, false, &session_fd, 1, "FDSTORE=1\nFDNAME=%s-direct", prefix); + if (r < 0) + return log_error_errno(r, "Failed to store session fd in fd store: %m"); + TAKE_FD(session_fd); + + log_info("Stored LUO session '%s' with memfd in fd store.", session_name); + + /* Wait for PID 1 to actually process all our FDSTORE notifications before we exit, otherwise + * the cgroup-based pidref to unit lookup may fail once we're gone, and the fds end up closed. */ + r = sd_notify_barrier(0, 5 * USEC_PER_SEC); + if (r < 0) + return log_error_errno(r, "Failed to wait for notification barrier: %m"); + + return 0; +} + +static int do_check(const char *prefix) { + const char *e; + _cleanup_strv_free_ char **names = NULL; + const char *session_fdname = strjoina(prefix, "-direct"); + size_t n_fds; + int r; + + /* sd_listen_fds_with_names() checks LISTEN_PID which won't match since we're a child process. + * Read LISTEN_FDS and LISTEN_FDNAMES directly from the environment instead. */ + e = getenv("LISTEN_FDS"); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No LISTEN_FDS environment variable set"); + + r = safe_atozu(e, &n_fds); + if (r < 0) + return log_error_errno(r, "Failed to parse LISTEN_FDS='%s': %m", e); + if (n_fds == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No file descriptors in fd store after kexec"); + + log_info("Got %zu fd(s) in fd store after kexec.", n_fds); + + /* Parse LISTEN_FDNAMES to match fds by name, not position */ + e = getenv("LISTEN_FDNAMES"); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No LISTEN_FDNAMES environment variable set"); + + names = strv_split(e, ":"); + if (!names) + return log_oom(); + assert(n_fds == strv_length(names)); + + static const struct { + const char *name; + const char *expected; + } checks[] = { + { "testfd1", TEST_DATA_1 }, + { "testfd2", TEST_DATA_2 }, + }; + + if (n_fds < ELEMENTSOF(checks)) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "Not enough fds in fd store after kexec: expected at least %zu, got %zu", + ELEMENTSOF(checks), n_fds); + + for (size_t i = 0; i < ELEMENTSOF(checks); i++) { + char buf[256]; + ssize_t n; + size_t idx = 0; + int fd = -EBADF; + + /* Find the fd by name */ + STRV_FOREACH(name, names) { + if (idx >= n_fds) + break; + if (streq(*name, checks[i].name)) { + fd = SD_LISTEN_FDS_START + idx; + break; + } + idx++; + } + + if (fd < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "fd '%s' not found in LISTEN_FDNAMES", checks[i].name); + + /* memfds are sealed; pread() avoids needing a separate lseek() */ + n = pread(fd, buf, sizeof(buf) - 1, 0); + if (n < 0) + return log_error_errno(errno, "Failed to read fd %d: %m", fd); + + buf[n] = '\0'; + + if (!streq(buf, checks[i].expected)) + return log_error_errno( + SYNTHETIC_ERRNO(EBADMSG), + "Content mismatch for '%s': expected '%s', got '%s'", + checks[i].name, checks[i].expected, buf); + + /* Remove the fd from the fd store so we don't keep accumulating duplicates across + * repeated invocations (and across repeated kexec cycles). */ + r = sd_pid_notifyf(0, /* unset_environment= */ false, + "FDSTOREREMOVE=1\nFDNAME=%s", checks[i].name); + if (r < 0) + return log_error_errno(r, "Failed to remove fd '%s' from fd store: %m", checks[i].name); + + log_info("Verified fd '%s': content matches.", checks[i].name); + } + + log_info("All fd store checks passed."); + + /* Verify the LUO session fd survived and its memfd content is intact */ + int session_fd = -EBADF; + size_t idx = 0; + STRV_FOREACH(name, names) { + if (idx >= n_fds) + break; + if (streq(*name, session_fdname)) { + session_fd = SD_LISTEN_FDS_START + idx; + break; + } + idx++; + } + + if (session_fd < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "LUO session fd '%s' not found in fd store", session_fdname); + + r = fd_is_luo_session(session_fd); + if (r < 0) + return log_error_errno(r, "Failed to check if fd is LUO session: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "fd '%s' is not a LUO session!", session_fdname); + + _cleanup_close_ int session_memfd = luo_session_retrieve_fd(session_fd, SESSION_MEMFD_TOKEN); + if (session_memfd < 0) + return log_error_errno(session_memfd, "Failed to retrieve memfd from session: %m"); + + char sbuf[256]; + ssize_t sn = pread(session_memfd, sbuf, sizeof(sbuf) - 1, 0); + if (sn < 0) + return log_error_errno(errno, "Failed to read session memfd: %m"); + sbuf[sn] = '\0'; + + if (!streq(sbuf, SESSION_MEMFD_DATA)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Session memfd content mismatch: expected '%s', got '%s'", + SESSION_MEMFD_DATA, sbuf); + + /* Remove the LUO session fd from the fd store as well. */ + r = sd_pid_notifyf(0, /* unset_environment= */ false, + "FDSTOREREMOVE=1\nFDNAME=%s", session_fdname); + if (r < 0) + return log_error_errno(r, "Failed to remove fd '%s' from fd store: %m", session_fdname); + + log_info("Verified LUO session memfd content matches."); + + /* Wait for PID 1 to actually process all our FDSTORE notifications before we exit, otherwise + * the cgroup-based pidref to unit lookup may fail once we're gone, and the fds end up closed. */ + r = sd_notify_barrier(0, 5 * USEC_PER_SEC); + if (r < 0) + return log_error_errno(r, "Failed to wait for notification barrier: %m"); + + return 0; +} + +static int do_store_hijack(void) { + int r; + + /* Create a child LUO session named exactly like PID 1's own session, put a memfd in it, and push the + * session fd into our fd store. On kexec, PID 1's manager_luo_serialize_fd_stores() must detect the + * reserved session name and refuse to serialize this entry (anti-hijack guard). */ + _cleanup_close_ int device_fd = -EBADF, session_fd = -EBADF, session_memfd = -EBADF; + + device_fd = luo_open_device(); + if (device_fd < 0) + return log_error_errno(device_fd, "Failed to open /dev/liveupdate: %m"); + + session_fd = luo_create_session(device_fd, HIJACK_SESSION_NAME); + if (session_fd < 0) + return log_error_errno(session_fd, "Failed to create hijacking LUO session '%s': %m", HIJACK_SESSION_NAME); + + session_memfd = memfd_new_and_seal("hijack-test", HIJACK_MEMFD_DATA, strlen(HIJACK_MEMFD_DATA)); + if (session_memfd < 0) + return log_error_errno(session_memfd, "Failed to create hijack session memfd: %m"); + + r = luo_session_preserve_fd(session_fd, session_memfd, HIJACK_MEMFD_TOKEN); + if (r < 0) + return log_error_errno(r, "Failed to preserve memfd in hijack session: %m"); + + r = sd_pid_notify_with_fds(0, /* unset_environment= */ false, "FDSTORE=1\nFDNAME=" HIJACK_FDNAME, &session_fd, 1); + if (r < 0) + return log_error_errno(r, "Failed to store hijack session fd in fd store: %m"); + TAKE_FD(session_fd); + + log_info("Stored hijacking LUO session '%s' with memfd in fd store.", HIJACK_SESSION_NAME); + + /* Wait for PID 1 to actually process the FDSTORE notification before we exit, otherwise + * the cgroup-based pidref to unit lookup may fail once we're gone, and the fd ends up closed. */ + r = sd_notify_barrier(0, 5 * USEC_PER_SEC); + if (r < 0) + return log_error_errno(r, "Failed to wait for notification barrier: %m"); + + return 0; +} + +static int do_check_hijack(void) { + _cleanup_strv_free_ char **names = NULL; + const char *e; + size_t n_fds; + int r; + + /* The hijacking session fd ("hijackfd") must NOT have survived kexec: PID 1 refused to serialize it + * because its session name infringes PID 1's reserved namespace. So it must be absent from + * LISTEN_FDNAMES here. */ + e = getenv("LISTEN_FDS"); + if (!e) { + log_info("No LISTEN_FDS set after kexec, hijack fd correctly not restored."); + return 0; + } + + r = safe_atozu(e, &n_fds); + if (r < 0) + return log_error_errno(r, "Failed to parse LISTEN_FDS='%s': %m", e); + + e = getenv("LISTEN_FDNAMES"); + if (!e) { + if (n_fds == 0) { + log_info("No fds restored after kexec, hijack fd correctly not restored."); + return 0; + } + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "LISTEN_FDS=%zu but no LISTEN_FDNAMES set", n_fds); + } + + names = strv_split(e, ":"); + if (!names) + return log_oom(); + + if (strv_contains(names, HIJACK_FDNAME)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Hijacking session fd '%s' was restored after kexec, anti-hijack guard failed!", + HIJACK_FDNAME); + + log_info("Verified hijacking session fd '%s' was not restored after kexec.", HIJACK_FDNAME); + return 0; +} + +static int do_check_sessions(int argc, char *argv[]) { + const char *e; + _cleanup_strv_free_ char **names = NULL; + int n_fds, n_verified = 0, r; + + /* Verify that named LUO sessions are present in the fd store */ + e = getenv("LISTEN_FDS"); + if (!e) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No LISTEN_FDS environment variable set!"); + + r = safe_atoi(e, &n_fds); + if (r < 0) + return log_error_errno(r, "Failed to parse LISTEN_FDS='%s': %m", e); + + e = getenv("LISTEN_FDNAMES"); + if (e) { + names = strv_split(e, ":"); + if (!names) + return log_oom(); + } + + for (int i = 2; i < argc; i++) { + const char *session_name = argv[i]; + int fd = -EBADF; + + /* Find the fd by name */ + STRV_FOREACH(name, names) { + int idx = (int) (name - names); + if (idx >= n_fds) + break; + if (streq(*name, session_name)) { + fd = SD_LISTEN_FDS_START + idx; + break; + } + } + + if (fd < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "LUO session '%s' not found in fd store!", session_name); + + r = fd_is_luo_session(fd); + if (r < 0) + return log_error_errno(r, "Failed to check if fd '%s' is a LUO session: %m", session_name); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "fd '%s' is not a LUO session!", session_name); + + log_info("Verified LUO session '%s' is present and valid.", session_name); + n_verified++; + } + + log_info("All %d LUO session(s) verified.", n_verified); + return 0; +} + +static int run(int argc, char *argv[]) { + if (argc < 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Usage: %s store|check|store-hijack|check-hijack [PREFIX] | check-sessions NAME...", argv[0]); + + const char *prefix = argc > 2 ? argv[2] : "luosession"; + + if (streq(argv[1], "store")) + return do_store(prefix); + if (streq(argv[1], "check")) + return do_check(prefix); + if (streq(argv[1], "store-hijack")) + return do_store_hijack(); + if (streq(argv[1], "check-hijack")) + return do_check_hijack(); + if (streq(argv[1], "check-sessions")) + return do_check_sessions(argc, argv); + + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown command: %s", argv[1]); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/test/test-machine-util.c b/src/test/test-machine-util.c new file mode 100644 index 0000000000000..8774be2189228 --- /dev/null +++ b/src/test/test-machine-util.c @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "machine-util.h" +#include "tests.h" + +TEST(bind_volume_parse_minimal) { + _cleanup_(bind_volume_freep) BindVolume *v = NULL; + + ASSERT_OK(bind_volume_parse("block:/dev/sda", &v)); + ASSERT_STREQ(v->provider, "block"); + ASSERT_STREQ(v->volume, "/dev/sda"); + ASSERT_NULL(v->config); + ASSERT_NULL(v->template); + ASSERT_EQ(v->create_mode, _CREATE_MODE_INVALID); + ASSERT_EQ(v->request_as, _VOLUME_TYPE_INVALID); + ASSERT_EQ(v->read_only, _READ_ONLY_INVALID); + ASSERT_EQ(v->create_size_bytes, UINT64_MAX); +} + +TEST(bind_volume_parse_with_config) { + _cleanup_(bind_volume_freep) BindVolume *v = NULL; + + ASSERT_OK(bind_volume_parse("block:/dev/sda:virtio-scsi", &v)); + ASSERT_STREQ(v->provider, "block"); + ASSERT_STREQ(v->volume, "/dev/sda"); + ASSERT_STREQ(v->config, "virtio-scsi"); +} + +TEST(bind_volume_parse_empty_config) { + _cleanup_(bind_volume_freep) BindVolume *v = NULL; + + ASSERT_OK(bind_volume_parse("fs:vol-1::create=new,size=64M,template=sparse-file", &v)); + ASSERT_STREQ(v->provider, "fs"); + ASSERT_STREQ(v->volume, "vol-1"); + ASSERT_NULL(v->config); + ASSERT_EQ(v->create_mode, CREATE_NEW); + ASSERT_STREQ(v->template, "sparse-file"); + ASSERT_EQ(v->create_size_bytes, UINT64_C(64) * 1024 * 1024); +} + +TEST(bind_volume_parse_full) { + _cleanup_(bind_volume_freep) BindVolume *v = NULL; + + ASSERT_OK(bind_volume_parse( + "fs:vol-2:nvme:create=any,template=allocated-file,size=128M,ro=auto,request-as=blk", + &v)); + ASSERT_STREQ(v->provider, "fs"); + ASSERT_STREQ(v->volume, "vol-2"); + ASSERT_STREQ(v->config, "nvme"); + ASSERT_EQ(v->create_mode, CREATE_ANY); + ASSERT_STREQ(v->template, "allocated-file"); + ASSERT_EQ(v->request_as, VOLUME_BLK); + ASSERT_EQ(v->create_size_bytes, UINT64_C(128) * 1024 * 1024); + ASSERT_EQ(v->read_only, READ_ONLY_AUTO); +} + +TEST(bind_volume_parse_read_only) { + _cleanup_(bind_volume_freep) BindVolume *v = NULL; + + ASSERT_OK(bind_volume_parse("block:/dev/sdb:scsi-cd:read-only=yes", &v)); + ASSERT_EQ(v->read_only, READ_ONLY_YES); + + v = bind_volume_free(v); + ASSERT_OK(bind_volume_parse("block:/dev/sdb:scsi-cd:ro=no", &v)); + ASSERT_EQ(v->read_only, READ_ONLY_NO); +} + +TEST(bind_volume_parse_invalid) { + BindVolume *v = NULL; + + /* Missing provider */ + ASSERT_ERROR(bind_volume_parse(":vol", &v), EINVAL); + ASSERT_NULL(v); + + /* Missing volume */ + ASSERT_ERROR(bind_volume_parse("block:", &v), EINVAL); + ASSERT_NULL(v); + + /* Provider with control char */ + ASSERT_ERROR(bind_volume_parse("bl\x01ock:vol", &v), EINVAL); + ASSERT_NULL(v); + + /* Config with control char */ + ASSERT_ERROR(bind_volume_parse("block:vol:nv\x01me", &v), EINVAL); + ASSERT_NULL(v); + + /* Unknown extras key */ + ASSERT_ERROR(bind_volume_parse("block:vol::bogus=foo", &v), EINVAL); + ASSERT_NULL(v); + + /* Bogus create mode */ + ASSERT_ERROR(bind_volume_parse("block:vol::create=bogus", &v), EINVAL); + ASSERT_NULL(v); + + /* Bogus request-as */ + ASSERT_ERROR(bind_volume_parse("block:vol::request-as=bogus", &v), EINVAL); + ASSERT_NULL(v); + + /* Extras entry without '=' */ + ASSERT_ERROR(bind_volume_parse("block:vol::nokey", &v), EINVAL); + ASSERT_NULL(v); + + /* Empty key (=value with no key) */ + ASSERT_ERROR(bind_volume_parse("block:vol::=value", &v), EINVAL); + ASSERT_NULL(v); + + /* Duplicate key */ + ASSERT_ERROR(bind_volume_parse("block:vol::create=new,create=any", &v), EINVAL); + ASSERT_NULL(v); + + /* Aliased duplicate (size / create-size) */ + ASSERT_ERROR(bind_volume_parse("block:vol::size=64M,create-size=128M", &v), EINVAL); + ASSERT_NULL(v); + + /* Zero-byte size */ + ASSERT_ERROR(bind_volume_parse("block:vol::size=0", &v), EINVAL); + ASSERT_NULL(v); + + /* Duplicate read-only with explicit yes/no values */ + ASSERT_ERROR(bind_volume_parse("block:vol::read-only=yes,read-only=no", &v), EINVAL); + ASSERT_NULL(v); + ASSERT_ERROR(bind_volume_parse("block:vol::read-only=yes,ro=auto", &v), EINVAL); + ASSERT_NULL(v); +} + +TEST(machine_storage_name_split) { + _cleanup_free_ char *p = NULL, *v = NULL; + + ASSERT_OK(machine_storage_name_split("block:/dev/sda", &p, &v)); + ASSERT_STREQ(p, "block"); + ASSERT_STREQ(v, "/dev/sda"); + + /* NULL outputs — validate-only mode */ + ASSERT_OK(machine_storage_name_split("fs:vol-1", NULL, NULL)); + + ASSERT_ERROR(machine_storage_name_split(NULL, NULL, NULL), EINVAL); + ASSERT_ERROR(machine_storage_name_split("", NULL, NULL), EINVAL); + ASSERT_ERROR(machine_storage_name_split("no-colon", NULL, NULL), EINVAL); + ASSERT_ERROR(machine_storage_name_split(":vol", NULL, NULL), EINVAL); + ASSERT_ERROR(machine_storage_name_split("block:", NULL, NULL), EINVAL); + ASSERT_ERROR(machine_storage_name_split("bl\x01ock:vol", NULL, NULL), EINVAL); +} + +DEFINE_TEST_MAIN(LOG_INFO); 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-math-util.c b/src/test/test-math-util.c index 9771576fcbb6b..805351761f353 100644 --- a/src/test/test-math-util.c +++ b/src/test/test-math-util.c @@ -5,6 +5,15 @@ #include "math-util.h" #include "tests.h" +/* Computed at runtime via a noinline + volatile combination so the result crosses the function ABI + * boundary at the FPU's current precision (80-bit on i386/x87). Used to probe fp_equal's handling + * of excess precision — see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323 for why + * -fexcess-precision=standard doesn't fully cover the caller side of a function return on x87. */ +static double _noinline_ one_tenth_via_division(void) { + volatile double ten = 10.0; + return 1.0 / ten; +} + TEST(iszero_safe) { /* zeros */ assert_se(iszero_safe(0.0)); @@ -105,6 +114,43 @@ TEST(fp_equal) { assert_se( fp_equal(0, 1 / INFINITY)); assert_se( fp_equal(42 / INFINITY, 1 / -INFINITY)); assert_se(!fp_equal(42 / INFINITY, INFINITY / INFINITY)); + + assert_se( fp_equal(one_tenth_via_division(), 0.1)); +} + +TEST(xexp10i) { + /* Table-lookup range: every value is exact in binary64 */ + ASSERT_TRUE(fp_equal(xexp10i(0), 1.0)); + ASSERT_TRUE(fp_equal(xexp10i(1), 10.0)); + ASSERT_TRUE(fp_equal(xexp10i(2), 100.0)); + ASSERT_TRUE(fp_equal(xexp10i(22), 1e22)); + + /* Negative exponents */ + ASSERT_TRUE(fp_equal(xexp10i(-1), 0.1)); + ASSERT_TRUE(fp_equal(xexp10i(-3), 0.001)); + + /* Beyond the table: result still matches a plain 10.0 multiplication chain (no precision + * claim beyond binary64) */ + ASSERT_TRUE(xexp10i(23) > 0.9e23 && xexp10i(23) < 1.1e23); + ASSERT_TRUE(xexp10i(100) > 0.9e100 && xexp10i(100) < 1.1e100); + + /* Overflow saturates to +Inf, underflow to 0 — matching glibc exp10() */ + ASSERT_TRUE(isinf(xexp10i(400))); + ASSERT_TRUE(fp_equal(xexp10i(-400), 0.0)); + + /* Pathological inputs must still terminate quickly thanks to the internal cap */ + ASSERT_TRUE(isinf(xexp10i(INT_MAX))); + ASSERT_TRUE(fp_equal(xexp10i(INT_MIN), 0.0)); + + /* Regression guard for the DBL_MAX round-trip described in math-util.c: 10^308 must be small + * enough that DBL_MAX's mantissa (≈ 1.7976931348623157) multiplied by it does not overflow + * to +Inf. Delegating to __builtin_powi(10.0, n) here breaks this — libgcc's __powidf2 + * accumulates a few ULPs of error through repeated squaring, and the product spills over. + * Matching test-json's delta of 0.0001, the reconstructed value must land within 0.01% of + * DBL_MAX. */ + double dbl_max_reconstructed = 1.7976931348623157 * xexp10i(308); + ASSERT_FALSE(isinf(dbl_max_reconstructed)); + ASSERT_TRUE(ABS(1.0 - DBL_MAX / dbl_max_reconstructed) < 0.0001); } DEFINE_TEST_MAIN(LOG_DEBUG); 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..41c67a3cf958b 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -1,8 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include +#include #include #include #include @@ -213,7 +215,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; @@ -282,6 +284,57 @@ TEST(userns_get_base_uid) { ASSERT_ERROR(userns_get_base_uid(fd, &base_uid, &base_gid), ENOMSG); } +TEST(namespace_open_by_id) { + /* Try our own user namespace first to see if the kernel exposes ns_id at all. */ + _cleanup_close_ int userns_fd = ASSERT_OK_ERRNO(open("/proc/self/ns/user", O_RDONLY|O_CLOEXEC)); + + uint64_t ns_id; + int r = RET_NERRNO(ioctl(userns_fd, NS_GET_ID, &ns_id)); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return (void) log_tests_skipped("NS_GET_ID is not supported by this kernel"); + ASSERT_OK(r); + + /* namespace_open_by_id() refuses with -EPERM outside the initial user/pid namespace, since + * the kernel restricts open_by_handle_at() on nsfs to the initial userns and pidns and to + * CAP_SYS_ADMIN. */ + _cleanup_close_ int opened = namespace_open_by_id(ns_id); + if (IN_SET(opened, -EPERM, -ESTALE)) + return (void) log_tests_skipped("not in initial user namespace or missing CAP_SYS_ADMIN"); + if (IN_SET(opened, -EOPNOTSUPP, -EINVAL)) + return (void) log_tests_skipped("nsfs lookup by ns_id is not supported by this kernel"); + ASSERT_OK(opened); + + struct stat orig_st, opened_st; + ASSERT_OK_ERRNO(fstat(userns_fd, &orig_st)); + ASSERT_OK_ERRNO(fstat(opened, &opened_st)); + ASSERT_EQ(orig_st.st_ino, opened_st.st_ino); + + opened = safe_close(opened); + + ASSERT_ERROR(namespace_open_by_id(0), EINVAL); + + _cleanup_close_ int transient_fd = userns_acquire_empty(); + if (ERRNO_IS_NEG_NOT_SUPPORTED(transient_fd) || ERRNO_IS_NEG_PRIVILEGE(transient_fd)) + return (void) log_tests_skipped("cannot acquire userns for transient lookup test"); + ASSERT_OK(transient_fd); + + uint64_t transient_id; + ASSERT_OK_ERRNO(ioctl(transient_fd, NS_GET_ID, &transient_id)); + ASSERT_NE(transient_id, ns_id); + + opened = ASSERT_OK(namespace_open_by_id(transient_id)); + + struct stat transient_st, transient_opened_st; + ASSERT_OK_ERRNO(fstat(transient_fd, &transient_st)); + ASSERT_OK_ERRNO(fstat(opened, &transient_opened_st)); + ASSERT_EQ(transient_st.st_ino, transient_opened_st.st_ino); + opened = safe_close(opened); + + /* Close the only reference. The namespace is now dead — lookup must fail. */ + transient_fd = safe_close(transient_fd); + ASSERT_ERROR(namespace_open_by_id(transient_id), ESTALE); +} + TEST(process_is_owned_by_uid) { int r; 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..21fb7a2d028c1 --- /dev/null +++ b/src/test/test-options.c @@ -0,0 +1,1569 @@ +/* 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, + const char *namespace) { + + _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 opts = { argc, argv, mode, namespace }; + for (int c; (c = option_parse(options, options + n_options, &opts)) != 0; ) { + ASSERT_OK(c); + ASSERT_NOT_NULL(opts.opt); + + log_debug("%c %s: %s=%s", + opts.opt->short_code != 0 ? opts.opt->short_code : ' ', + opts.opt->long_code ?: "", + strnull(opts.opt->metavar), strnull(opts.arg)); + + ASSERT_LT(i, n_entries); + if (entries[i].long_code) + ASSERT_TRUE(streq_ptr(opts.opt->long_code, entries[i].long_code)); + if (entries[i].short_code != 0) + ASSERT_EQ(opts.opt->short_code, entries[i].short_code); + ASSERT_TRUE(streq_ptr(opts.arg, entries[i].argument)); + i++; + } + + ASSERT_EQ(i, n_entries); + + char **args = option_parser_get_args(&opts); + ASSERT_TRUE(strv_equal(args, remaining)); + ASSERT_STREQ(argv[0], saved_argv0); + + size_t l = strv_length(remaining); + ASSERT_EQ(option_parser_get_n_args(&opts), l); + ASSERT_STREQ(option_parser_get_arg(&opts, 0), l > 0 ? remaining[0] : NULL); + ASSERT_STREQ(option_parser_get_arg(&opts, 1), l > 1 ? remaining[1] : NULL); + ASSERT_STREQ(option_parser_get_arg(&opts, 2), l > 2 ? remaining[2] : NULL); + ASSERT_STREQ(option_parser_get_arg(&opts, 3), l > 3 ? remaining[3] : NULL); +} + +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 opts = { argc, argv }; + + int c = option_parse(options, options + n_options, &opts); + 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 }, + { 7, .long_code = "NS2", .flags = OPTION_NAMESPACE_MARKER }, + { 8, .short_code = 'h', .long_code = "help2" }, + { 9, .long_code = "version2" }, + { 10, .long_code = "NS3", .flags = OPTION_NAMESPACE_MARKER }, + { 11, .short_code = 'h', .long_code = "help3" }, + { 12, .long_code = "version3" }, + {} + }; + + test_option_parse_one(STRV_MAKE("arg0"), + options, + NULL, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "string3", + "string4"), + options, + NULL, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "--", + "string1", + "--help", + "-h", + "string4"), + options, + NULL, + STRV_MAKE("string1", + "--help", + "-h", + "string4"), + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "--", + "--", + "string4"), + options, + NULL, + STRV_MAKE("string1", + "string2", + "--", + "string4"), + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "string3", + "string4", + "--"), + options, + NULL, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "--help"), + options, + (Entry[]) { + { "help" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "--help", + "string1", + "--help"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "--help"), + OPTION_PARSER_STOP_AT_FIRST_NONOPTION, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "-h"), + options, + (Entry[]) { + { "help" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "--help", + "string1", + "string2", + "string3", + "string4"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "-h", + "string1", + "string2", + "string3", + "string4"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "--help", + "string3", + "string4"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "-h", + "string3", + "string4"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "string3", + "string4", + "--help"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "string3", + "string4", + "-h"), + options, + (Entry[]) { + { "help" }, + {} + }, + STRV_MAKE("string1", + "string2", + "string3", + "string4"), + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "--required1", "reqarg1"), + options, + (Entry[]) { + { "required1", "reqarg1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "-r", "reqarg1"), + options, + (Entry[]) { + { "required1", "reqarg1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "string1", + "string2", + "-r", "reqarg1"), + options, + (Entry[]) { + { "required1", "reqarg1" }, + {} + }, + STRV_MAKE("string1", + "string2"), + OPTION_PARSER_NORMAL, + NULL); + + 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, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "--optional1=optarg1"), + options, + (Entry[]) { + { "optional1", "optarg1" }, + {} + }, + NULL, + OPTION_PARSER_STOP_AT_FIRST_NONOPTION, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "--optional1", "string1"), + options, + (Entry[]) { + { "optional1", NULL }, + {} + }, + STRV_MAKE("string1"), + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "-ooptarg1"), + options, + (Entry[]) { + { "optional1", "optarg1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + test_option_parse_one(STRV_MAKE("arg0", + "-o", "string1"), + options, + (Entry[]) { + { "optional1", NULL }, + {} + }, + STRV_MAKE("string1"), + OPTION_PARSER_NORMAL, + NULL); + + 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, + NULL); + + /* Check that we can access options from NS2 */ + test_option_parse_one(STRV_MAKE("arg0", + "-h", /* This verifies that we're using the right namespace */ + "--help2", + "--version2", + "string1"), + options, + (Entry[]) { + { "help2" }, + { "help2" }, + { "version2" }, + {} + }, + STRV_MAKE("string1"), + OPTION_PARSER_NORMAL, + "NS2"); + + /* Check that we can access options from NS3 */ + test_option_parse_one(STRV_MAKE("arg0", + "-h", /* This verifies that we're using the right namespace */ + "--help3", + "--version3", + "string1"), + options, + (Entry[]) { + { "help3" }, + { "help3" }, + { "version3" }, + {} + }, + STRV_MAKE("string1"), + OPTION_PARSER_NORMAL, + "NS3"); +} + +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, + NULL); + + /* 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, + NULL); + + /* --exec with no trailing args */ + test_option_parse_one(STRV_MAKE("arg0", + "--exec"), + options, + (Entry[]) { + { "exec" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + /* --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, + NULL); + + /* "--" 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, + NULL); + + /* "--" 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, + NULL); + + /* 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, + NULL); +} + +TEST(option_group_marker) { + static const Option options[] = { + { __COUNTER__, .short_code = 'h', .long_code = "help" }, + { __COUNTER__, .long_code = "version" }, + { __COUNTER__, .long_code = "AdvancedGroup", .flags = OPTION_GROUP_MARKER }, + { __COUNTER__, .long_code = "debug" }, + { __COUNTER__, .long_code = "Advance" }, /* prefix match with the group */ + { __COUNTER__, .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, + NULL); + + /* Check that group marker name is ignored */ + test_option_parse_one(STRV_MAKE("arg0", + "--debug", + "--version"), + options, + (Entry[]) { + { "debug" }, + { "version" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* Short option with inline arg */ + test_option_parse_one(STRV_MAKE("arg0", + "-ofoo.txt"), + options, + (Entry[]) { + { "output", "foo.txt" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + /* 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, + NULL); + + /* Optional arg option at end of argv */ + test_option_parse_one(STRV_MAKE("arg0", + "--output"), + options, + (Entry[]) { + { "output", NULL }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); +} + +/* Check that we correctly implement the behaviour of + * systemd-analyze -a -b unit-shell -c -d name -e -f + * systemd-analyze -a -b other-verb -c -d name -e -f + * systemd-analyze -a -b -c -d -e -f + * where '-a', '-b', '-c', '-d' are "our" options, but '-e -f' is part of the commandline + * for unit-shell, but not in the other cases. */ +static void test_option_parsing_stops_at_second_nonoption_one( + char **cmdline, + unsigned options_to_see, + unsigned verbs_to_see, + char **args_to_see) { + + static const Option options[] = { + { 1, .short_code = 'a' }, + { 2, .short_code = 'b' }, + { 3, .short_code = 'c' }, + { 4, .short_code = 'd' }, + { 5, .short_code = 'e' }, + { 6, .short_code = 'f' }, + { 7, .long_code = "(positional)", .flags = OPTION_POSITIONAL_ENTRY }, + {}, + }; + + OptionParser opts = { strv_length(cmdline), cmdline, + .mode = OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + unsigned options_seen = 0; + unsigned verbs_seen = 0; + for (int c; (c = option_parse(options, options + ELEMENTSOF(options) - 1, &opts)) != 0; ) { + ASSERT_OK(c); + ASSERT_NOT_NULL(opts.opt); + + switch (opts.opt->id) { + case 1 ... 6: + options_seen++; + break; + case 7: + verbs_seen++; + ASSERT_EQ(opts.mode, (OptionParserMode) OPTION_PARSER_RETURN_POSITIONAL_ARGS); + + if (streq(opts.arg, "unit-shell")) + opts.mode = OPTION_PARSER_STOP_AT_FIRST_NONOPTION; + else if (streq(opts.arg, "other-verb")) + opts.mode = OPTION_PARSER_NORMAL; + else + assert_not_reached(); + break; + default: + assert_not_reached(); + } + } + + ASSERT_EQ(options_seen, options_to_see); + ASSERT_EQ(verbs_seen, verbs_to_see); + ASSERT_TRUE(strv_equal(option_parser_get_args(&opts), args_to_see)); +} + +TEST(option_parsing_stops_at_second_nonoption) { + test_option_parsing_stops_at_second_nonoption_one( + STRV_MAKE("systemd-analyze", "-a", "-b", "unit-shell", "-c", "-d", "name", "-e", "-f"), + 4, 1, + STRV_MAKE("name", "-e", "-f")); + test_option_parsing_stops_at_second_nonoption_one( + STRV_MAKE("systemd-analyze", "-a", "-b", "other-verb", "-c", "-d", "name", "-e", "-f"), + 6, 1, + STRV_MAKE("name")); + test_option_parsing_stops_at_second_nonoption_one( + STRV_MAKE("systemd-analyze", "-a", "-b", "-c", "-d", "-e"), + 5, 0, + STRV_EMPTY); +} + +/* Test the OPTION, OPTION_LONG, OPTION_SHORT, OPTION_FULL, OPTION_GROUP macros + * by using them in a FOREACH_OPTION switch, as they would be used in real code. */ + +static void test_macros_parse_one( + char **argv, + const Entry *entries, + char **remaining, + OptionParserMode mode, + const char *namespace) { + + _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 opts = { argc, argv, mode, namespace }; + + FOREACH_OPTION(c, &opts) { + + assert(c >= 0); + + log_debug("%c %s: %s=%s", + opts.opt->short_code != 0 ? opts.opt->short_code : ' ', + opts.opt->long_code ?: "", + strnull(opts.opt->metavar), strnull(opts.arg)); + + ASSERT_LT(i, n_entries); + if (entries[i].long_code) + ASSERT_TRUE(streq_ptr(opts.opt->long_code, entries[i].long_code)); + if (entries[i].short_code != 0) + ASSERT_EQ(opts.opt->short_code, entries[i].short_code); + ASSERT_TRUE(streq_ptr(opts.arg, entries[i].argument)); + + if (streq_ptr(entries[i].long_code, "optional2")) + ASSERT_EQ(opts.opt->data, 666u); + else + ASSERT_EQ(opts.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; + + OPTION_NAMESPACE("namespaced options"): {} + + OPTION('r', "required2", "ARG", "Required arg option"): + break; + + default: + log_error("Unexpected option id: %d", c); + assert_not_reached(); + } + } + + ASSERT_EQ(i, n_entries); + + char **args = option_parser_get_args(&opts); + ASSERT_TRUE(strv_equal(args, remaining)); + ASSERT_STREQ(argv[0], saved_argv0); + ASSERT_NULL(opts.opt); + ASSERT_NULL(opts.arg); +} + +TEST(option_macros) { + /* OPTION: long form */ + test_macros_parse_one(STRV_MAKE("arg0", + "--help"), + (Entry[]) { + { "help" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + /* OPTION: short form */ + test_macros_parse_one(STRV_MAKE("arg0", + "-h"), + (Entry[]) { + { "help" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + /* OPTION_LONG: only accessible via long form */ + test_macros_parse_one(STRV_MAKE("arg0", + "--version"), + (Entry[]) { + { "version" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + /* OPTION_SHORT: only accessible via short form */ + test_macros_parse_one(STRV_MAKE("arg0", + "-v"), + (Entry[]) { + { .short_code = 'v' }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + /* OPTION with required arg: long --required=ARG */ + test_macros_parse_one(STRV_MAKE("arg0", + "--required=val1"), + (Entry[]) { + { "required", "val1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + /* OPTION with required arg: long --required ARG */ + test_macros_parse_one(STRV_MAKE("arg0", + "--required", "val1"), + (Entry[]) { + { "required", "val1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + /* OPTION with required arg: short -r ARG */ + test_macros_parse_one(STRV_MAKE("arg0", + "-r", "val1"), + (Entry[]) { + { "required", "val1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + /* OPTION with required arg: short -rARG */ + test_macros_parse_one(STRV_MAKE("arg0", + "-rval1"), + (Entry[]) { + { "required", "val1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + /* OPTION_FULL with OPTION_OPTIONAL_ARG: long with = */ + test_macros_parse_one(STRV_MAKE("arg0", + "--optional=val1"), + (Entry[]) { + { "optional", "val1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + /* 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, + NULL); + + /* OPTION_FULL with OPTION_OPTIONAL_ARG: short inline */ + test_macros_parse_one(STRV_MAKE("arg0", + "-oval1"), + (Entry[]) { + { "optional", "val1" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* 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, + NULL); + + /* Second namespace, OPTION: long form */ + test_macros_parse_one(STRV_MAKE("arg0", + "--required2=arg"), + (Entry[]) { + { "required2", "arg" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + "namespaced options"); + + /* Second namespace, OPTION: short form */ + test_macros_parse_one(STRV_MAKE("arg0", + "-rarg"), + (Entry[]) { + { "required2", "arg" }, + {} + }, + NULL, + OPTION_PARSER_NORMAL, + "namespaced options"); +} + +/* 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[] = { + { __COUNTER__, .short_code = 'h', .long_code = "help" }, + { __COUNTER__, .long_code = "user", .metavar = "NAME", .flags = OPTION_OPTIONAL_ARG }, + { __COUNTER__, .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, + NULL); + + /* --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, + NULL); + + /* --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_peek_next_arg/consume_next_arg to grab it. */ + { + char **argv = STRV_MAKE("arg0", "--user", "someuser", "pos1"); + int argc = strv_length(argv); + + OptionParser opts = { argc, argv }; + + ASSERT_OK_POSITIVE(option_parse(options, options + 3, &opts)); + ASSERT_STREQ(opts.opt->long_code, "user"); + ASSERT_NULL(opts.arg); + ASSERT_STREQ(option_parser_peek_next_arg(&opts), "someuser"); + ASSERT_STREQ(option_parser_consume_next_arg(&opts), "someuser"); + + ASSERT_EQ(option_parse(options, options + 3, &opts), 0); + ASSERT_NULL(opts.opt); + ASSERT_NULL(opts.arg); + + ASSERT_TRUE(strv_equal(option_parser_get_args(&opts), 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 opts = { argc, argv }; + + ASSERT_OK_POSITIVE(option_parse(options, options + 3, &opts)); + ASSERT_STREQ(opts.opt->long_code, "user"); + ASSERT_NULL(opts.arg); + ASSERT_NULL(option_parser_peek_next_arg(&opts)); + ASSERT_NULL(option_parser_consume_next_arg(&opts)); + + ASSERT_EQ(option_parse(options, options + 3, &opts), 0); + ASSERT_NULL(opts.opt); + ASSERT_NULL(opts.arg); + + ASSERT_TRUE(strv_isempty(option_parser_get_args(&opts))); + } + + /* --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 opts = { argc, argv }; + + ASSERT_OK_POSITIVE(option_parse(options, options + 3, &opts)); + ASSERT_STREQ(opts.opt->long_code, "user"); + ASSERT_NULL(opts.arg); + ASSERT_STREQ(option_parser_peek_next_arg(&opts), "-u"); + + ASSERT_OK_POSITIVE(option_parse(options, options + 3, &opts)); + ASSERT_STREQ(opts.opt->long_code, "uid"); + ASSERT_STREQ(opts.arg, "nobody"); + ASSERT_NULL(option_parser_peek_next_arg(&opts)); + ASSERT_NULL(option_parser_consume_next_arg(&opts)); + + ASSERT_EQ(option_parse(options, options + 3, &opts), 0); + ASSERT_NULL(opts.opt); + ASSERT_NULL(opts.arg); + + ASSERT_TRUE(strv_isempty(option_parser_get_args(&opts))); + } + + /* "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 opts = { argc, argv }; + int scope_seen = 0; + int nobody_seen = 0; + + for (int c; (c = option_parse(options, options + 3, &opts)) != 0; ) { + ASSERT_OK(c); + + if (streq_ptr(opts.opt->long_code, "user")) { + const char *arg = opts.arg; + + if (!arg) { + const char *t = option_parser_peek_next_arg(&opts); + if (t && t[0] != '-') + arg = option_parser_consume_next_arg(&opts); + } + + if (arg) { + ASSERT_STREQ(arg, "nobody"); + nobody_seen ++; + } else + scope_seen ++; + + } else if (streq_ptr(opts.opt->long_code, "uid")) { + ASSERT_STREQ(opts.arg, "nobody"); + nobody_seen ++; + } + } + + ASSERT_EQ(nobody_seen, 2); + ASSERT_EQ(scope_seen, 2); + ASSERT_TRUE(strv_equal(option_parser_get_args(&opts), STRV_MAKE("nogroup"))); + ASSERT_NULL(opts.opt); + ASSERT_NULL(opts.arg); + } +} + +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-parse-util.c b/src/test/test-parse-util.c index 0c2cc500b13b0..bc74856fde629 100644 --- a/src/test/test-parse-util.c +++ b/src/test/test-parse-util.c @@ -2,7 +2,6 @@ #include #include -#include #include #include "capability-util.h" @@ -660,7 +659,7 @@ TEST(safe_atod) { ASSERT_ERROR(safe_atod("junk", &d), EINVAL); ASSERT_OK_ZERO(safe_atod("0.2244", &d)); - assert_se(fabs(d - 0.2244) < 0.000001); + assert_se(ABS(d - 0.2244) < 0.000001); ASSERT_ERROR(safe_atod("0,5", &d), EINVAL); ASSERT_ERROR(safe_atod("", &d), EINVAL); @@ -672,7 +671,7 @@ TEST(safe_atod) { return (void) log_tests_skipped_errno(errno, "locale de_DE.utf8 not found"); ASSERT_OK_ZERO(safe_atod("0.2244", &d)); - assert_se(fabs(d - 0.2244) < 0.000001); + assert_se(ABS(d - 0.2244) < 0.000001); ASSERT_ERROR(safe_atod("0,5", &d), EINVAL); ASSERT_ERROR(safe_atod("", &d), EINVAL); diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index ef4f29172d838..1dc260a46732e 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -24,8 +24,12 @@ TEST(print_paths) { } TEST(path) { + assert_se(!path_is_absolute(NULL)); + assert_se(!path_is_absolute("")); assert_se( path_is_absolute("/")); + assert_se( path_is_absolute("//")); assert_se(!path_is_absolute("./")); + assert_se(!path_is_absolute("foo/bar")); assert_se( PATH_IN_SET("/bin", "/", "/bin", "/foo")); assert_se( PATH_IN_SET("/bin", "/bin")); @@ -42,6 +46,21 @@ TEST(path) { assert_se(!path_equal(NULL, "a")); } +TEST(path_is_normalized) { + assert_se( path_is_normalized("/")); + assert_se( path_is_normalized("/usr/bin")); + assert_se( path_is_normalized("usr/bin")); + + assert_se(!path_is_normalized("")); + assert_se(!path_is_normalized(".")); + assert_se(!path_is_normalized("./usr/bin")); + assert_se(!path_is_normalized("/usr//bin")); + assert_se(!path_is_normalized("/usr/./bin")); + assert_se(!path_is_normalized("/usr/bin/.")); + assert_se(!path_is_normalized("../usr/bin")); + assert_se(!path_is_normalized("/usr/../bin")); +} + TEST(is_path) { assert_se(!is_path("foo")); assert_se(!is_path("dos.ext")); @@ -760,6 +779,9 @@ TEST(path_startswith) { test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/barfo", NULL, NULL); test_path_startswith_one("/foo/bar/barfoo/", "/foo/bar/bar", NULL, NULL); test_path_startswith_one("/foo/bar/barfoo/", "/fo", NULL, NULL); + test_path_startswith_one("/usr/binary", "/usr/bin", NULL, NULL); + test_path_startswith_one("/foo/barista", "/foo/bar", NULL, NULL); + test_path_startswith_one("foo/barista", "foo/bar", NULL, NULL); } static void test_path_startswith_return_leading_slash_one(const char *path, const char *prefix, const char *expected) { diff --git a/src/test/test-path.c b/src/test/test-path.c index d282cfbf89113..ff1dee8474515 100644 --- a/src/test/test-path.c +++ b/src/test/test-path.c @@ -16,402 +16,386 @@ #include "tests.h" #include "unit.h" -typedef void (*test_function_t)(Manager *m); +static char *runtime_dir = NULL; -static int setup_test(Manager **m) { - char **tests_path = STRV_MAKE("exists", "existsglobFOOBAR", "changed", "modified", "unit", - "directorynotempty", "makedirectory"); - Manager *tmp = NULL; +static int setup_test(Manager **ret) { int r; - assert_se(m); - r = enter_cgroup_subroot(NULL); if (r == -ENOMEDIUM) return log_tests_skipped("cgroupfs not available"); - r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &tmp); + _cleanup_(manager_freep) Manager *m = NULL; + r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m); if (manager_errno_skip_test(r)) return log_tests_skipped_errno(r, "manager_new"); - assert_se(r >= 0); - assert_se(manager_startup(tmp, NULL, NULL, NULL) >= 0); - - STRV_FOREACH(test_path, tests_path) { - _cleanup_free_ char *p = NULL; - - p = strjoin("/tmp/test-path_", *test_path); - assert_se(p); - + ASSERT_OK(r); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); + + FOREACH_STRING(s, + "exists", + "existsglobFOOBAR", + "changed", + "modified", + "unit", + "directorynotempty", + "makedirectory") { + + _cleanup_free_ char *p = ASSERT_NOT_NULL(strjoin("/tmp/test-path_", s)); (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); } - *m = tmp; - + *ret = TAKE_PTR(m); return 0; } -static void shutdown_test(Manager *m) { - assert_se(m); - - manager_free(m); -} - -static Service *service_for_path(Manager *m, Path *path, const char *service_name) { - _cleanup_free_ char *tmp = NULL; - Unit *service_unit = NULL; - - assert_se(m); - assert_se(path); +static Service* service_for_path(Manager *m, Path *path, const char *service_name) { + ASSERT_NOT_NULL(m); + ASSERT_NOT_NULL(path); + Unit *service_unit; if (!service_name) { - assert_se(tmp = strreplace(UNIT(path)->id, ".path", ".service")); + _cleanup_free_ char *tmp = ASSERT_NOT_NULL(strreplace(UNIT(path)->id, ".path", ".service")); service_unit = manager_get_unit(m, tmp); } else service_unit = manager_get_unit(m, service_name); - assert_se(service_unit); + ASSERT_NOT_NULL(service_unit); return SERVICE(service_unit); } -static int _check_states(unsigned line, - Manager *m, Path *path, Service *service, PathState path_state, ServiceState service_state) { - assert_se(m); - assert_se(service); +static int _check_states( + unsigned line, + Manager *m, + Path *path, + Service *service, + PathState path_state, + ServiceState service_state) { - usec_t end = now(CLOCK_MONOTONIC) + 30 * USEC_PER_SEC; + ASSERT_NOT_NULL(m); + ASSERT_NOT_NULL(service); + + usec_t end = usec_add(now(CLOCK_MONOTONIC), 30 * USEC_PER_SEC); + PathState last_path_state = _PATH_STATE_INVALID; + PathResult last_path_result = _PATH_RESULT_INVALID; + ServiceState last_service_state = _SERVICE_STATE_INVALID; + ServiceResult last_service_result = _SERVICE_RESULT_INVALID; while (path->state != path_state || service->state != service_state || path->result != PATH_SUCCESS || service->result != SERVICE_SUCCESS) { - assert_se(sd_event_run(m->event, 100 * USEC_PER_MSEC) >= 0); + ASSERT_OK(sd_event_run(m->event, 100 * USEC_PER_MSEC)); usec_t n = now(CLOCK_MONOTONIC); - log_info("line %u: %s: state = %s; result = %s (left: %" PRIi64 ")", - line, - UNIT(path)->id, - path_state_to_string(path->state), - path_result_to_string(path->result), - (int64_t) (end - n)); - log_info("line %u: %s: state = %s; result = %s", - line, - UNIT(service)->id, - service_state_to_string(service->state), - service_result_to_string(service->result)); - - if (service->state == SERVICE_FAILED && - (service->main_exec_status.status == EXIT_CGROUP || service->result == SERVICE_FAILURE_RESOURCES)) { - const char *ci = ci_environment(); - - /* On a general purpose system we may fail to start the service for reasons which are - * not under our control: permission limits, resource exhaustion, etc. Let's skip the - * test in those cases. On developer machines we require proper setup. */ - if (!ci) - return log_notice_errno(SYNTHETIC_ERRNO(ECANCELED), - "Failed to start service %s, aborting test: %s/%s", - UNIT(service)->id, - service_state_to_string(service->state), - service_result_to_string(service->result)); - - /* On Salsa we can't setup cgroups so the unit always fails. The test checks if it - * can but continues if it cannot at the beginning, but on Salsa it fails here. */ - if (streq(ci, "salsa-ci")) - exit(EXIT_TEST_SKIP); + if (path->state != last_path_state || path->result != last_path_result || + service->state != last_service_state || service->result != last_service_result) { + log_info("line %u: %s: state = %s; result = %s (left: %" PRIi64 ")", + line, + UNIT(path)->id, + path_state_to_string(path->state), + path_result_to_string(path->result), + (int64_t) (end - n)); + log_info("line %u: %s: state = %s; result = %s", + line, + UNIT(service)->id, + service_state_to_string(service->state), + service_result_to_string(service->result)); + last_path_state = path->state; + last_path_result = path->result; + last_service_state = service->state; + last_service_result = service->result; } - if (n >= end) { - log_error("Test timeout when testing %s", UNIT(path)->id); - exit(EXIT_FAILURE); - } + /* We may fail to start the service for reasons which are not under our control: cgroup + * setup denied, permission limits, resource exhaustion, etc. RESOURCES is terminal here + * for path units that don't auto-retry (PathChanged, PathModified) — they'd just sit in + * the failure state until the test timeout. Skip rather than wait. */ + if (service->state == SERVICE_FAILED && + (service->main_exec_status.status == EXIT_CGROUP || service->result == SERVICE_FAILURE_RESOURCES)) + return log_tests_skipped("Failed to start service %s: %s/%s", + UNIT(service)->id, + service_state_to_string(service->state), + service_result_to_string(service->result)); + + /* SERVICE_FAILURE_START_LIMIT_HIT is terminal: the unit won't recover without an explicit + * reset, so further looping is pointless. Skip the test rather than burning the 30s timeout. */ + if (service->state == SERVICE_FAILED && + service->result == SERVICE_FAILURE_START_LIMIT_HIT) + return log_tests_skipped("Failed to start service %s: %s/%s", + UNIT(service)->id, + service_state_to_string(service->state), + service_result_to_string(service->result)); + + if (n >= end) + log_test_failed("Test timeout when testing %s", UNIT(path)->id); } return 0; } -#define check_states(...) _check_states(__LINE__, __VA_ARGS__) -static void test_path_exists(Manager *m) { +#define check_states(...) \ + do { \ + int _r = _check_states(__LINE__, __VA_ARGS__); \ + if (_r != 0) \ + return _r; \ + } while (0) + +TEST_RET(path_exists) { const char *test_path = "/tmp/test-path_exists"; - Unit *unit = NULL; - Path *path = NULL; - Service *service = NULL; + int r; - assert_se(m); + _cleanup_(manager_freep) Manager *m = NULL; + r = setup_test(&m); + if (r != 0) + return r; - assert_se(manager_load_startable_unit_or_warn(m, "path-exists.path", NULL, &unit) >= 0); + Unit *unit; + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-exists.path", NULL, &unit)); - path = PATH(unit); - service = service_for_path(m, path, NULL); + Path *path = PATH(unit); + Service *service = service_for_path(m, path, NULL); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service restarts if file still exists */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK_ZERO(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL)); + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_existsglob(Manager *m) { +TEST_RET(path_existsglob) { const char *test_path = "/tmp/test-path_existsglobFOOBAR"; - Unit *unit = NULL; - Path *path = NULL; - Service *service = NULL; + int r; - assert_se(m); + _cleanup_(manager_freep) Manager *m = NULL; + r = setup_test(&m); + if (r != 0) + return r; - assert_se(manager_load_startable_unit_or_warn(m, "path-existsglob.path", NULL, &unit) >= 0); + Unit *unit; + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-existsglob.path", NULL, &unit)); - path = PATH(unit); - service = service_for_path(m, path, NULL); + Path *path = PATH(unit); + Service *service = service_for_path(m, path, NULL); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service restarts if file still exists */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK_ZERO(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL)); + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_changed(Manager *m) { +TEST_RET(path_changed) { const char *test_path = "/tmp/test-path_changed"; - FILE *f; - Unit *unit = NULL; - Path *path = NULL; - Service *service = NULL; + int r; - assert_se(m); + _cleanup_(manager_freep) Manager *m = NULL; + r = setup_test(&m); + if (r != 0) + return r; - assert_se(manager_load_startable_unit_or_warn(m, "path-changed.path", NULL, &unit) >= 0); + Unit *unit; + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-changed.path", NULL, &unit)); - path = PATH(unit); - service = service_for_path(m, path, NULL); + Path *path = PATH(unit); + Service *service = service_for_path(m, path, NULL); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service does not restart if file still exists */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - f = fopen(test_path, "w"); - assert_se(f); - fclose(f); + fclose(ASSERT_NOT_NULL(fopen(test_path, "w"))); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_modified(Manager *m) { - _cleanup_fclose_ FILE *f = NULL; +TEST_RET(path_modified) { const char *test_path = "/tmp/test-path_modified"; - Unit *unit = NULL; - Path *path = NULL; - Service *service = NULL; + int r; - assert_se(m); + _cleanup_(manager_freep) Manager *m = NULL; + r = setup_test(&m); + if (r != 0) + return r; - assert_se(manager_load_startable_unit_or_warn(m, "path-modified.path", NULL, &unit) >= 0); + Unit *unit; + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-modified.path", NULL, &unit)); - path = PATH(unit); - service = service_for_path(m, path, NULL); + Path *path = PATH(unit); + Service *service = service_for_path(m, path, NULL); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service does not restart if file still exists */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - f = fopen(test_path, "w"); - assert_se(f); + _cleanup_fclose_ FILE *f = ASSERT_NOT_NULL(fopen(test_path, "w")); fputs("test", f); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_unit(Manager *m) { +TEST_RET(path_unit) { const char *test_path = "/tmp/test-path_unit"; - Unit *unit = NULL; - Path *path = NULL; - Service *service = NULL; + int r; - assert_se(m); + _cleanup_(manager_freep) Manager *m = NULL; + r = setup_test(&m); + if (r != 0) + return r; - assert_se(manager_load_startable_unit_or_warn(m, "path-unit.path", NULL, &unit) >= 0); + Unit *unit; + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-unit.path", NULL, &unit)); - path = PATH(unit); - service = service_for_path(m, path, "path-mycustomunit.service"); + Path *path = PATH(unit); + Service *service = service_for_path(m, path, "path-mycustomunit.service"); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(touch(test_path) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_path)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK_ZERO(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL)); + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_directorynotempty(Manager *m) { +TEST_RET(path_directorynotempty) { const char *test_file, *test_path = "/tmp/test-path_directorynotempty/"; - Unit *unit = NULL; - Path *path = NULL; - Service *service = NULL; + int r; - assert_se(m); + _cleanup_(manager_freep) Manager *m = NULL; + r = setup_test(&m); + if (r != 0) + return r; - assert_se(manager_load_startable_unit_or_warn(m, "path-directorynotempty.path", NULL, &unit) >= 0); + Unit *unit; + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-directorynotempty.path", NULL, &unit)); - path = PATH(unit); - service = service_for_path(m, path, NULL); + Path *path = PATH(unit); + Service *service = service_for_path(m, path, NULL); - assert_se(access(test_path, F_OK) < 0); + ASSERT_FAIL(access(test_path, F_OK)); - assert_se(unit_start(unit, NULL) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK(unit_start(unit, NULL)); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); /* MakeDirectory default to no */ - assert_se(access(test_path, F_OK) < 0); + ASSERT_FAIL(access(test_path, F_OK)); - assert_se(mkdir_p(test_path, 0755) >= 0); + ASSERT_OK(mkdir_p(test_path, 0755)); test_file = strjoina(test_path, "test_file"); - assert_se(touch(test_file) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(touch(test_file)); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); /* Service restarts if directory is still not empty */ - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING) < 0) - return; + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_RUNNING, SERVICE_RUNNING); - assert_se(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); - assert_se(unit_stop(UNIT(service)) >= 0); - if (check_states(m, path, service, PATH_WAITING, SERVICE_DEAD) < 0) - return; + ASSERT_OK_ZERO(rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL)); + ASSERT_OK(unit_stop(UNIT(service))); + check_states(m, path, service, PATH_WAITING, SERVICE_DEAD); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); + return 0; } -static void test_path_makedirectory_directorymode(Manager *m) { +TEST_RET(path_makedirectory_directorymode) { const char *test_path = "/tmp/test-path_makedirectory/"; - Unit *unit = NULL; - struct stat s; + int r; - assert_se(m); + _cleanup_(manager_freep) Manager *m = NULL; + r = setup_test(&m); + if (r != 0) + return r; - assert_se(manager_load_startable_unit_or_warn(m, "path-makedirectory.path", NULL, &unit) >= 0); + Unit *unit; + ASSERT_OK(manager_load_startable_unit_or_warn(m, "path-makedirectory.path", NULL, &unit)); - assert_se(access(test_path, F_OK) < 0); + ASSERT_FAIL(access(test_path, F_OK)); - assert_se(unit_start(unit, NULL) >= 0); + ASSERT_OK(unit_start(unit, NULL)); /* Check if the directory has been created */ - assert_se(access(test_path, F_OK) >= 0); + ASSERT_OK_ERRNO(access(test_path, F_OK)); /* Check the mode we specified with DirectoryMode=0744 */ - assert_se(stat(test_path, &s) >= 0); - assert_se((s.st_mode & S_IRWXU) == 0700); - assert_se((s.st_mode & S_IRWXG) == 0040); - assert_se((s.st_mode & S_IRWXO) == 0004); + struct stat s; + ASSERT_OK_ERRNO(stat(test_path, &s)); + ASSERT_EQ((mode_t) (s.st_mode & S_IRWXU), (mode_t) 0700); + ASSERT_EQ((mode_t) (s.st_mode & S_IRWXG), (mode_t) 0040); + ASSERT_EQ((mode_t) (s.st_mode & S_IRWXO), (mode_t) 0004); - assert_se(unit_stop(unit) >= 0); + ASSERT_OK(unit_stop(unit)); (void) rm_rf(test_path, REMOVE_ROOT|REMOVE_PHYSICAL); + return 0; } -int main(int argc, char *argv[]) { - static const test_function_t tests[] = { - test_path_exists, - test_path_existsglob, - test_path_changed, - test_path_modified, - test_path_unit, - test_path_directorynotempty, - test_path_makedirectory_directorymode, - NULL, - }; - +static int intro(void) { _cleanup_free_ char *test_path = NULL; - _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; umask(022); - test_setup_logging(LOG_INFO); - ASSERT_OK(get_testdata_dir("test-path", &test_path)); ASSERT_OK(setenv_unit_path(test_path)); - assert_se(runtime_dir = setup_fake_runtime_dir()); - - for (const test_function_t *test = tests; *test; test++) { - Manager *m = NULL; - int r; - - /* We create a clean environment for each test */ - r = setup_test(&m); - if (r != 0) - return r; + ASSERT_NOT_NULL(runtime_dir = setup_fake_runtime_dir()); - (*test)(m); - - shutdown_test(m); - } + return EXIT_SUCCESS; +} - return 0; +static int outro(void) { + runtime_dir = rm_rf_physical_and_free(runtime_dir); + return EXIT_SUCCESS; } + +DEFINE_TEST_MAIN_FULL(LOG_INFO, intro, outro); diff --git a/src/test/test-pe-binary.c b/src/test/test-pe-binary.c new file mode 100644 index 0000000000000..af806009b7990 --- /dev/null +++ b/src/test/test-pe-binary.c @@ -0,0 +1,523 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* Regression tests for the algorithmic-complexity DoS fix in src/shared/pe-binary.c + * (issue #42344 — PE file with attacker-controlled VirtualSize wedges uki_hash + * in an unbounded SHA-256 zero-padding loop). + * + * Coverage: + * - pe_load_sections() rejects sections whose PointerToRawData + SizeOfRawData + * exceeds the actual file size (PLAN §3.1). + * - uki_hash() refuses to hash a section whose VirtualSize exceeds + * SizeOfRawData by more than UKI_HASH_VIRTUAL_SIZE_PADDING_MAX (=64 MiB) + * and instead returns -EBADMSG (PLAN §3.2). + * + * Tests build small PE32+ images in a memfd. The header layout mirrors the + * canonical 382-byte reproducer (DOS at 0x00, e_lfanew=0x40, PE32+ optional + * header of 0xf0 bytes, section table starting at file offset 0x148) so + * pe_load_headers() accepts every test fixture; only the section table and + * file size are varied per test. */ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "memfd-util.h" +#include "pe-binary.h" +#include "tests.h" +#include "uki.h" +#include "unaligned.h" + +#if HAVE_OPENSSL +# include "crypto-util.h" +#endif + +/* Match the cap defined in src/shared/pe-binary.c (PLAN §3.2). */ +#define UKI_HASH_PADDING_CAP_BYTES (64U * 1024U * 1024U) + +/* File offsets of the PE32+ header skeleton this test builds. */ +#define SECTION_TABLE_OFFSET 0x148 +#define IMAGE_FILE_HEADER_NOS_OFFSET 0x46 /* NumberOfSections (le16) */ +#define IMAGE_SECTION_HEADER_BYTES 40 + +/* Header skeleton, byte-for-byte compatible with the canonical reproducer + * so that pe_load_headers() succeeds without further mutation. We only + * patch NumberOfSections at runtime (offset 0x46) and append section + * headers + raw data after the optional header. */ +static const uint8_t HEADER_SKELETON[SECTION_TABLE_OFFSET] = { + /* DOS header: "MZ" at 0x00, e_lfanew = 0x40 at 0x3c */ + [0x00] = 'M', 'Z', + [0x3c] = 0x40, 0x00, 0x00, 0x00, + + /* PE signature at 0x40 */ + [0x40] = 'P', 'E', 0x00, 0x00, + + /* IMAGE_FILE_HEADER at 0x44 */ + [0x44] = 0x64, 0x86, /* Machine = 0x8664 (x86_64) */ + /* [0x46]: NumberOfSections — patched per test */ + [0x54] = 0xf0, 0x00, /* SizeOfOptionalHeader = 0xf0 */ + [0x56] = 0x22, 0x00, /* Characteristics = executable image */ + + /* IMAGE_OPTIONAL_HEADER (PE32+) at 0x58 */ + [0x58] = 0x0b, 0x02, /* Magic = 0x020b (PE32+) */ + [0x9c] = 0x0a, 0x00, /* Subsystem = EFI Application */ + [0xc4] = 0x10, 0x00, 0x00, 0x00, /* NumberOfRvaAndSizes = 16 (matches SizeOfOptionalHeader=0xf0) */ +}; + +typedef struct SectionSpec { + const char *name; /* PE section name; truncated/padded to 8 bytes on write */ + uint32_t virtual_size; + uint32_t virtual_address; + uint32_t size_of_raw_data; + uint32_t pointer_to_raw_data; +} SectionSpec; + +/* Build a PE32+ image in a fresh memfd containing the given sections. + * + * If override_file_size > 0, the memfd is written with exactly that many + * bytes (zero-padded if larger than what the sections need). This lets a + * test claim section data past the actual EOF, or reserve headroom past + * the last section. If 0, the file is sized to exactly contain the + * headers + section table + every section's raw data. */ +static int build_pe_file( + const SectionSpec *specs, + size_t n_sections, + size_t override_file_size) { + + /* Minimum size is "headers + section table"; sections with SizeOfRawData>0 + * must additionally fit inside the file. */ + size_t base = SECTION_TABLE_OFFSET + n_sections * IMAGE_SECTION_HEADER_BYTES; + size_t needed = base; + FOREACH_ARRAY(spec, specs, n_sections) + if (spec->size_of_raw_data > 0) { + uint64_t end = (uint64_t) spec->pointer_to_raw_data + spec->size_of_raw_data; + if (end > needed) + needed = end; + } + + size_t file_size = override_file_size > 0 ? override_file_size : needed; + ASSERT_GE(file_size, base); + _cleanup_free_ uint8_t *buf = ASSERT_NOT_NULL(new0(uint8_t, file_size)); + + memcpy(buf, HEADER_SKELETON, sizeof(HEADER_SKELETON)); + unaligned_write_le16(buf + IMAGE_FILE_HEADER_NOS_OFFSET, (uint16_t) n_sections); + + /* Section table at 0x148. */ + FOREACH_ARRAY(spec, specs, n_sections) { + uint8_t *sh = buf + SECTION_TABLE_OFFSET + (spec - specs) * IMAGE_SECTION_HEADER_BYTES; + size_t nlen = strnlen(spec->name, 8); + memcpy(sh, spec->name, nlen); + unaligned_write_le32(sh + 8, spec->virtual_size); + unaligned_write_le32(sh + 12, spec->virtual_address); + unaligned_write_le32(sh + 16, spec->size_of_raw_data); + unaligned_write_le32(sh + 20, spec->pointer_to_raw_data); + /* Characteristics, relocs, etc., left zero. */ + } + + int fd = ASSERT_OK(memfd_new("test-pe-binary")); + + /* Write only file_size bytes — that lets the caller construct a + * PE whose section table claims data past EOF. */ + ASSERT_OK_EQ_ERRNO(write(fd, buf, file_size), (ssize_t) file_size); + ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_SET)); + return fd; +} + +static int load_headers_and_sections( + int fd, + IMAGE_DOS_HEADER **ret_dos, + PeHeader **ret_pe, + IMAGE_SECTION_HEADER **ret_sections) { + + int r = pe_load_headers(fd, ret_dos, ret_pe); + if (r < 0) + return r; + return pe_load_sections(fd, *ret_dos, *ret_pe, ret_sections); +} + +/* ====================================================================== + * pe_load_headers — SizeOfOptionalHeader / NumberOfRvaAndSizes bound + * ====================================================================== */ + +/* If SizeOfOptionalHeader is so small that pread() does not actually + * populate NumberOfRvaAndSizes, the optional-header size check at the end + * of pe_load_headers would read uninitialised heap memory (caught by MSAN + * under CIFuzz). pe_load_headers must reject such files with -EBADMSG + * before touching that field. */ +TEST(pe_load_headers_optional_header_too_small) { + /* Minimum allowed by the existing magic check is 2 bytes (only + * IMAGE_OPTIONAL_HEADER.Magic). That is well below the + * NumberOfRvaAndSizes offset for both PE32 and PE32+. */ + uint8_t buf[64 + 4 + 20 + 2] = {}; /* DOS + PE sig + file hdr + 2-byte optional */ + buf[0] = 'M'; buf[1] = 'Z'; + unaligned_write_le32(buf + 0x3c, 64); + memcpy(buf + 64, "PE\0\0", 4); + /* IMAGE_FILE_HEADER: leave most fields zero, set + * SizeOfOptionalHeader = 2 (offset 0x10 within IMAGE_FILE_HEADER). */ + unaligned_write_le16(buf + 64 + 4 + 16, 2); + /* Optional header Magic = 0x020B (PE32+). */ + unaligned_write_le16(buf + 64 + 4 + 20, 0x020B); + + _cleanup_close_ int fd = ASSERT_OK(memfd_new("test-pe-binary")); + ASSERT_OK_EQ_ERRNO(write(fd, buf, sizeof(buf)), (ssize_t) sizeof(buf)); + ASSERT_OK_ERRNO(lseek(fd, 0, SEEK_SET)); + + _cleanup_free_ IMAGE_DOS_HEADER *dos = NULL; + _cleanup_free_ PeHeader *pe = NULL; + ASSERT_ERROR(pe_load_headers(fd, &dos, &pe), EBADMSG); +} + +/* ====================================================================== + * pe_load_sections — bounds check from PLAN §3.1 + * ====================================================================== */ + +/* Happy path: a section whose raw data fits comfortably inside the file + * (i.e. file has plenty of trailing headroom beyond the section). */ +TEST(pe_load_sections_valid_in_bounds) { + SectionSpec s = { + .name = ".initrd", + .virtual_size = 16, + .size_of_raw_data = 16, + .pointer_to_raw_data = SECTION_TABLE_OFFSET + IMAGE_SECTION_HEADER_BYTES, + }; + _cleanup_close_ int fd = build_pe_file(&s, /* n_sections= */ 1, /* override_file_size= */ 4096); + _cleanup_free_ IMAGE_DOS_HEADER *dos = NULL; + _cleanup_free_ PeHeader *pe = NULL; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + + ASSERT_OK(load_headers_and_sections(fd, &dos, &pe, §ions)); +} + +/* Boundary: PointerToRawData + SizeOfRawData == file_size is OK. */ +TEST(pe_load_sections_section_exactly_fills_file) { + SectionSpec s = { + .name = ".initrd", + .virtual_size = 16, + .size_of_raw_data = 16, + .pointer_to_raw_data = SECTION_TABLE_OFFSET + IMAGE_SECTION_HEADER_BYTES, + }; + size_t exact_size = (size_t) s.pointer_to_raw_data + s.size_of_raw_data; + _cleanup_close_ int fd = build_pe_file(&s, /* n_sections= */ 1, exact_size); + _cleanup_free_ IMAGE_DOS_HEADER *dos = NULL; + _cleanup_free_ PeHeader *pe = NULL; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + + ASSERT_OK(load_headers_and_sections(fd, &dos, &pe, §ions)); +} + +/* Section's raw data claims to extend one byte past EOF — must be rejected. */ +TEST(pe_load_sections_section_past_eof_by_one) { + SectionSpec s = { + .name = ".initrd", + .virtual_size = 16, + .size_of_raw_data = 16, + .pointer_to_raw_data = SECTION_TABLE_OFFSET + IMAGE_SECTION_HEADER_BYTES, + }; + size_t short_size = (size_t) s.pointer_to_raw_data + s.size_of_raw_data - 1; + _cleanup_close_ int fd = build_pe_file(&s, /* n_sections= */ 1, short_size); + _cleanup_free_ IMAGE_DOS_HEADER *dos = NULL; + _cleanup_free_ PeHeader *pe = NULL; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + + ASSERT_ERROR(load_headers_and_sections(fd, &dos, &pe, §ions), EBADMSG); +} + +/* PointerToRawData itself already past EOF, with non-zero SizeOfRawData. */ +TEST(pe_load_sections_pointer_way_past_eof) { + SectionSpec s = { + .name = ".initrd", + .virtual_size = 16, + .size_of_raw_data = 16, + .pointer_to_raw_data = UINT32_C(0x10000000), + }; + _cleanup_close_ int fd = build_pe_file(&s, /* n_sections= */ 1, /* override_file_size= */ 4096); + _cleanup_free_ IMAGE_DOS_HEADER *dos = NULL; + _cleanup_free_ PeHeader *pe = NULL; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + + ASSERT_ERROR(load_headers_and_sections(fd, &dos, &pe, §ions), EBADMSG); +} + +/* SizeOfRawData == 0 means "BSS-equivalent / uninitialised"; PointerToRawData + * is a don't-care and must NOT be bounds-checked, even when nonsense. */ +TEST(pe_load_sections_size_zero_with_huge_pointer) { + SectionSpec s = { + .name = ".initrd", + .virtual_size = 0x100, + .size_of_raw_data = 0, + .pointer_to_raw_data = UINT32_MAX, + }; + _cleanup_close_ int fd = build_pe_file(&s, /* n_sections= */ 1, /* override_file_size= */ 0); + _cleanup_free_ IMAGE_DOS_HEADER *dos = NULL; + _cleanup_free_ PeHeader *pe = NULL; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + + ASSERT_OK(load_headers_and_sections(fd, &dos, &pe, §ions)); +} + +/* SizeOfRawData == 0 with PointerToRawData == 0 — most natural BSS case. */ +TEST(pe_load_sections_size_zero_pointer_zero) { + SectionSpec s = { + .name = ".initrd", + .virtual_size = 0x40, + .size_of_raw_data = 0, + .pointer_to_raw_data = 0, + }; + _cleanup_close_ int fd = build_pe_file(&s, /* n_sections= */ 1, /* override_file_size= */ 0); + _cleanup_free_ IMAGE_DOS_HEADER *dos = NULL; + _cleanup_free_ PeHeader *pe = NULL; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + + ASSERT_OK(load_headers_and_sections(fd, &dos, &pe, §ions)); +} + +/* PointerToRawData + SizeOfRawData overflows uint32 (both near UINT32_MAX). + * The fix uses uint64 arithmetic + __builtin_add_overflow, so this must be + * rejected, not silently wrap. */ +TEST(pe_load_sections_uint32_overflow_sum) { + SectionSpec s = { + .name = ".initrd", + .virtual_size = UINT32_C(0xffffffff), + .size_of_raw_data = UINT32_C(0x80000000), + .pointer_to_raw_data = UINT32_C(0x80000000), + }; + /* uint32 sum wraps to 0, but uint64 sum = 0x100000000 — must still + * be rejected against a tiny file. */ + _cleanup_close_ int fd = build_pe_file(&s, /* n_sections= */ 1, /* override_file_size= */ 4096); + _cleanup_free_ IMAGE_DOS_HEADER *dos = NULL; + _cleanup_free_ PeHeader *pe = NULL; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + + ASSERT_ERROR(load_headers_and_sections(fd, &dos, &pe, §ions), EBADMSG); +} + +/* Zero sections: bounds check loop runs zero times, fstat still succeeds. */ +TEST(pe_load_sections_zero_sections) { + _cleanup_close_ int fd = build_pe_file(NULL, /* n_sections= */ 0, /* override_file_size= */ 0); + _cleanup_free_ IMAGE_DOS_HEADER *dos = NULL; + _cleanup_free_ PeHeader *pe = NULL; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + + ASSERT_OK(load_headers_and_sections(fd, &dos, &pe, §ions)); +} + +/* Multiple sections, all valid — happy path with N>1. */ +TEST(pe_load_sections_multi_all_valid) { + size_t base = SECTION_TABLE_OFFSET + 3 * IMAGE_SECTION_HEADER_BYTES; + SectionSpec specs[3] = { + { .name = ".linux", .virtual_size = 16, .size_of_raw_data = 16, + .pointer_to_raw_data = (uint32_t) base }, + { .name = ".initrd", .virtual_size = 8, .size_of_raw_data = 8, + .pointer_to_raw_data = (uint32_t) (base + 16) }, + { .name = ".cmdline", .virtual_size = 0, .size_of_raw_data = 0, + .pointer_to_raw_data = 0 }, + }; + _cleanup_close_ int fd = build_pe_file(specs, /* n_sections= */ 3, /* override_file_size= */ 0); + _cleanup_free_ IMAGE_DOS_HEADER *dos = NULL; + _cleanup_free_ PeHeader *pe = NULL; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + + ASSERT_OK(load_headers_and_sections(fd, &dos, &pe, §ions)); +} + +/* Multiple sections; one of them claims data past EOF — the whole file + * must be rejected, not just that section silently skipped. */ +TEST(pe_load_sections_multi_one_past_eof) { + size_t base = SECTION_TABLE_OFFSET + 3 * IMAGE_SECTION_HEADER_BYTES; + SectionSpec specs[3] = { + { .name = ".linux", .virtual_size = 16, .size_of_raw_data = 16, + .pointer_to_raw_data = (uint32_t) base }, + { .name = ".initrd", .virtual_size = 8, .size_of_raw_data = 8, + .pointer_to_raw_data = (uint32_t) (base + 16) }, + { .name = ".cmdline", .virtual_size = 32, .size_of_raw_data = 32, + .pointer_to_raw_data = UINT32_C(0x40000000) }, /* nonsense */ + }; + _cleanup_close_ int fd = build_pe_file(specs, /* n_sections= */ 3, /* override_file_size= */ 4096); + _cleanup_free_ IMAGE_DOS_HEADER *dos = NULL; + _cleanup_free_ PeHeader *pe = NULL; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + + ASSERT_ERROR(load_headers_and_sections(fd, &dos, &pe, §ions), EBADMSG); +} + +/* ====================================================================== + * uki_hash — zero-padding cap from PLAN §3.2 + * ====================================================================== */ + +#if HAVE_OPENSSL + +/* Helper: call uki_hash on a freshly built single-section PE. Returns the + * uki_hash() return value; any allocated hashes are freed. */ +static int call_uki_hash_with(SectionSpec spec) { + _cleanup_close_ int fd = build_pe_file(&spec, /* n_sections= */ 1, /* override_file_size= */ 0); + _cleanup_free_ IMAGE_DOS_HEADER *dos = NULL; + _cleanup_free_ PeHeader *pe = NULL; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + + int r = load_headers_and_sections(fd, &dos, &pe, §ions); + if (r < 0) + return r; + + void *hashes[_UNIFIED_SECTION_MAX] = {}; + size_t hash_size = 0; + r = uki_hash(fd, sym_EVP_sha256(), hashes, &hash_size); + free_many(hashes, _UNIFIED_SECTION_MAX); + return r; +} + +/* No padding needed (VirtualSize == SizeOfRawData) — the cap branch is + * skipped entirely; hash succeeds. */ +TEST_RET(uki_hash_no_padding) { + if (dlopen_libcrypto(LOG_DEBUG) < 0) + return log_tests_skipped("openssl not available"); + + SectionSpec s = { + .name = ".initrd", + .virtual_size = 16, + .size_of_raw_data = 16, + .pointer_to_raw_data = SECTION_TABLE_OFFSET + IMAGE_SECTION_HEADER_BYTES, + }; + ASSERT_OK(call_uki_hash_with(s)); + return EXIT_SUCCESS; +} + +/* One-byte padding — well below the cap. Must succeed. */ +TEST_RET(uki_hash_one_byte_padding) { + if (dlopen_libcrypto(LOG_DEBUG) < 0) + return log_tests_skipped("openssl not available"); + + SectionSpec s = { + .name = ".initrd", + .virtual_size = 17, + .size_of_raw_data = 16, + .pointer_to_raw_data = SECTION_TABLE_OFFSET + IMAGE_SECTION_HEADER_BYTES, + }; + ASSERT_OK(call_uki_hash_with(s)); + return EXIT_SUCCESS; +} + +/* Boundary: VirtualSize - SizeOfRawData == cap (64 MiB) — must succeed + * (PLAN §4 says the comparison is strict ">"). SHA-256 of 64 MiB of zeros + * is ~100–200 ms on a modern CPU but can be much slower on emulated arches, + * so gate behind the slow-tests opt-in. */ +TEST_RET(uki_hash_at_cap_boundary) { + if (!slow_tests_enabled()) + return log_tests_skipped("slow tests disabled"); + if (dlopen_libcrypto(LOG_DEBUG) < 0) + return log_tests_skipped("openssl not available"); + + SectionSpec s = { + .name = ".initrd", + .virtual_size = UKI_HASH_PADDING_CAP_BYTES, + .size_of_raw_data = 0, + .pointer_to_raw_data = 0, + }; + ASSERT_OK(call_uki_hash_with(s)); + return EXIT_SUCCESS; +} + +/* One byte over the cap — must be rejected with -EBADMSG. */ +TEST_RET(uki_hash_one_over_cap_rejected) { + if (dlopen_libcrypto(LOG_DEBUG) < 0) + return log_tests_skipped("openssl not available"); + + SectionSpec s = { + .name = ".initrd", + .virtual_size = UKI_HASH_PADDING_CAP_BYTES + 1, + .size_of_raw_data = 0, + .pointer_to_raw_data = 0, + }; + ASSERT_ERROR(call_uki_hash_with(s), EBADMSG); + return EXIT_SUCCESS; +} + +/* VirtualSize == UINT32_MAX, SizeOfRawData == 0. This is the worst-case + * shape an attacker can produce: ~4 GiB of zero-padding. Without the cap + * this is the >10 s wedge from #42344; with the cap it must return + * -EBADMSG essentially instantly. */ +TEST_RET(uki_hash_max_virtual_size_rejected) { + if (dlopen_libcrypto(LOG_DEBUG) < 0) + return log_tests_skipped("openssl not available"); + + SectionSpec s = { + .name = ".initrd", + .virtual_size = UINT32_MAX, + .size_of_raw_data = 0, + .pointer_to_raw_data = 0, + }; + ASSERT_ERROR(call_uki_hash_with(s), EBADMSG); + return EXIT_SUCCESS; +} + +/* Reproduces the exact section shape of the canonical 382-byte slow-unit + * (.initrd VS=0xff000000 RSD=0). Pre-fix this drove ~8.5 s of SHA-256; the + * cap must reject it instantly. This is the in-code analogue of the + * regression input pinned in test/fuzz/fuzz-pe-binary/. */ +TEST_RET(uki_hash_canonical_42344_reproducer_pattern) { + if (dlopen_libcrypto(LOG_DEBUG) < 0) + return log_tests_skipped("openssl not available"); + + SectionSpec s = { + .name = ".initrd", + .virtual_size = UINT32_C(0xff000000), + .size_of_raw_data = 0, + .pointer_to_raw_data = 0, + }; + ASSERT_ERROR(call_uki_hash_with(s), EBADMSG); + return EXIT_SUCCESS; +} + +/* Multiple sections, one of which trips the cap — uki_hash() must abort + * the entire call (return -EBADMSG) rather than silently skipping. */ +TEST_RET(uki_hash_one_bad_among_many_rejected) { + if (dlopen_libcrypto(LOG_DEBUG) < 0) + return log_tests_skipped("openssl not available"); + + size_t base = SECTION_TABLE_OFFSET + 2 * IMAGE_SECTION_HEADER_BYTES; + SectionSpec specs[2] = { + { .name = ".linux", .virtual_size = 16, .size_of_raw_data = 16, + .pointer_to_raw_data = (uint32_t) base }, + { .name = ".initrd", .virtual_size = UKI_HASH_PADDING_CAP_BYTES + 1, + .size_of_raw_data = 0, .pointer_to_raw_data = 0 }, + }; + _cleanup_close_ int fd = build_pe_file(specs, /* n_sections= */ 2, /* override_file_size= */ 0); + _cleanup_free_ IMAGE_DOS_HEADER *dos = NULL; + _cleanup_free_ PeHeader *pe = NULL; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + + ASSERT_OK(load_headers_and_sections(fd, &dos, &pe, §ions)); + + void *hashes[_UNIFIED_SECTION_MAX] = {}; + size_t hash_size = 0; + ASSERT_ERROR(uki_hash(fd, sym_EVP_sha256(), hashes, &hash_size), EBADMSG); + free_many(hashes, _UNIFIED_SECTION_MAX); + return EXIT_SUCCESS; +} + +/* Sanity check: uki_hash on a "no UKI sections present" PE doesn't hit + * the cap path at all (no section with VS>SRD that matches the unified + * sections table). Just confirms the cap doesn't reject the empty case. */ +TEST_RET(uki_hash_empty_pe_does_not_reject) { + if (dlopen_libcrypto(LOG_DEBUG) < 0) + return log_tests_skipped("openssl not available"); + + _cleanup_close_ int fd = build_pe_file(NULL, /* n_sections= */ 0, /* override_file_size= */ 0); + _cleanup_free_ IMAGE_DOS_HEADER *dos = NULL; + _cleanup_free_ PeHeader *pe = NULL; + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + + ASSERT_OK(load_headers_and_sections(fd, &dos, &pe, §ions)); + + void *hashes[_UNIFIED_SECTION_MAX] = {}; + size_t hash_size = 0; + /* No UKI sections to hash — succeeds without hitting the cap path. */ + ASSERT_OK(uki_hash(fd, sym_EVP_sha256(), hashes, &hash_size)); + free_many(hashes, _UNIFIED_SECTION_MAX); + return EXIT_SUCCESS; +} + +#endif /* HAVE_OPENSSL */ + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-pressure.c b/src/test/test-pressure.c new file mode 100644 index 0000000000000..c1305edac012b --- /dev/null +++ b/src/test/test-pressure.c @@ -0,0 +1,668 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include +#include + +#include "sd-bus.h" +#include "sd-event.h" + +#include "bus-error.h" +#include "bus-locator.h" +#include "bus-wait-for-jobs.h" +#include "cgroup-util.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 */ + +static int event_loop_with_timeout(sd_event *event) { + ASSERT_NOT_NULL(event); + + /* Note, the default meson test timeout is 30 seconds. */ + ASSERT_OK(sd_event_add_time_relative( + event, + /* ret= */ NULL, + CLOCK_MONOTONIC, + 20 * USEC_PER_SEC, + /* accuracy= */ 0, + /* callback= */ NULL, + INT_TO_PTR(-ETIMEDOUT))); + + if (ASSERT_OK_OR(sd_event_loop(event), -ETIMEDOUT) < 0) + return log_tests_skipped("event loop timeout"); + + return 0; +} + +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")); + + if (event_loop_with_timeout(e) != 0) { + (void) pthread_cancel(th); + (void) pthread_join(th, /* retval= */ NULL); + return; + } + + 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 */ + +static int controller_supported_on_unit(sd_bus *bus, const char *unit_name, const char *unit_path, CGroupMask controller) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *cg = NULL; + CGroupMask mask; + int r; + + r = sd_bus_get_property_string(bus, "org.freedesktop.systemd1", unit_path, + unit_dbus_interface_from_name(unit_name), "ControlGroup", + &error, &cg); + if (r < 0) + return log_notice_errno(r, "Failed to read ControlGroup property: %s", bus_error_message(&error, r)); + + if (isempty(cg)) + return -ENODATA; + + r = cg_mask_supported_subtree(cg, &mask); + if (r < 0) + return log_notice_errno(r, "Failed to read supported controllers for %s: %m", cg); + + return FLAGS_SET(mask, controller); +} + +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)); + + _cleanup_free_ char *uo = ASSERT_NOT_NULL(unit_dbus_path_from_name(scope)); + r = controller_supported_on_unit(bus, scope, uo, CGROUP_MASK_MEMORY); + if (r <= 0) + return (void) log_tests_skipped("Memory controller not available on scope"); + + 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)); + + 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); + + if (event_loop_with_timeout(e) != 0) + return; + + 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)); + + _cleanup_free_ char *uo = ASSERT_NOT_NULL(unit_dbus_path_from_name(scope)); + r = controller_supported_on_unit(bus, scope, uo, CGROUP_MASK_CPU); + if (r <= 0) + return (void) log_tests_skipped("CPU controller not available on scope"); + + 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); + + if (event_loop_with_timeout(e) != 0) + return; + + 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)); + + _cleanup_free_ char *uo = ASSERT_NOT_NULL(unit_dbus_path_from_name(scope)); + r = controller_supported_on_unit(bus, scope, uo, CGROUP_MASK_IO); + if (r <= 0) + return (void) log_tests_skipped("IO controller not available on scope"); + + 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); + + if (event_loop_with_timeout(e) != 0) + return; + + 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-process-util.c b/src/test/test-process-util.c index 4c803fbbd9c38..63458117cb937 100644 --- a/src/test/test-process-util.c +++ b/src/test/test-process-util.c @@ -1125,6 +1125,21 @@ TEST(getenv_for_pid) { } } +TEST(invoked_as) { + ASSERT_FALSE(invoked_as(NULL, "foobar")); + ASSERT_FALSE(invoked_as(NULL, "barbar")); + + ASSERT_EQ(setenv("SYSTEMD_INVOKED_AS", "/usr/bin/foobar", 1), 0); + + ASSERT_TRUE(invoked_as(NULL, "foobar")); + ASSERT_FALSE(invoked_as(NULL, "barbar")); + ASSERT_TRUE(invoked_as(NULL, "foo")); + ASSERT_TRUE(invoked_as(STRV_MAKE("barbar", "barbar", "y"), "foobar")); + ASSERT_FALSE(invoked_as(STRV_MAKE("barbar", "barbar", "y"), "barbar")); + + ASSERT_EQ(unsetenv("SYSTEMD_INVOKED_AS"), 0); +} + static int intro(void) { log_show_color(true); return EXIT_SUCCESS; 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..9c56df29bc6a9 --- /dev/null +++ b/src/test/test-qmp-client.c @@ -0,0 +1,531 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-event.h" +#include "sd-future.h" +#include "sd-json.h" + +#include "errno-util.h" +#include "fd-util.h" +#include "json-stream.h" +#include "qmp-client.h" +#include "string-util.h" +#include "tests.h" + +/* Mock QMP server runs as an sd-fiber alongside the client on the same event loop. Its + * JsonStream uses the suspending json_stream_wait()/json_stream_flush() helpers, so the mock + * fiber yields whenever it's blocked on I/O and the client makes progress in the meantime. */ + +static JsonStreamPhase mock_qmp_phase(void *userdata) { + return JSON_STREAM_PHASE_READING; +} + +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)); +} + +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)); + } +} + +static void mock_qmp_send(JsonStream *s, sd_json_variant *v) { + ASSERT_OK(json_stream_enqueue(s, v)); + ASSERT_OK(json_stream_flush(s)); +} + +static void mock_qmp_send_greeting(JsonStream *s) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + ASSERT_OK(sd_json_buildo(&v, + SD_JSON_BUILD_PAIR("QMP", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("version", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR("qemu", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_UNSIGNED("micro", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("minor", 2), + SD_JSON_BUILD_PAIR_UNSIGNED("major", 9))))), + SD_JSON_BUILD_PAIR("capabilities", SD_JSON_BUILD_STRV(STRV_MAKE("oob"))))))); + mock_qmp_send(s, v); +} + +/* Receive one command, assert it matches `expected_command`, return its id (borrowed from *cmd). */ +static sd_json_variant* mock_qmp_expect(JsonStream *s, const char *expected_command, sd_json_variant **cmd) { + 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); + return ASSERT_NOT_NULL(sd_json_variant_by_key(*cmd, "id")); +} + +/* Send a reply for a previously-received command id. Passing NULL reply_data sends {}. */ +static void mock_qmp_reply(JsonStream *s, sd_json_variant *id, sd_json_variant *reply_data) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *empty = NULL, *response = NULL; + + if (!reply_data) { + ASSERT_OK(sd_json_build(&empty, SD_JSON_BUILD_EMPTY_OBJECT)); + reply_data = empty; + } + + ASSERT_OK(sd_json_buildo(&response, + SD_JSON_BUILD_PAIR("return", SD_JSON_BUILD_VARIANT(reply_data)), + SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(id)))); + + mock_qmp_send(s, response); +} + +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; + mock_qmp_reply(s, mock_qmp_expect(s, expected_command, &cmd), reply_data); +} + +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, *response = NULL; + sd_json_variant *id = mock_qmp_expect(s, expected_command, &cmd); + + ASSERT_OK(sd_json_buildo(&response, + SD_JSON_BUILD_PAIR("error", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("class", "GenericError"), + SD_JSON_BUILD_PAIR_STRING("desc", error_desc))), + SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_VARIANT(id)))); + + mock_qmp_send(s, response); +} + +static void mock_qmp_handshake(JsonStream *s) { + mock_qmp_send_greeting(s); + mock_qmp_expect_and_reply(s, "qmp_capabilities", NULL); +} + +/* Reply to query-status with a running=true/status="running" payload. */ +static void mock_qmp_query_status_running(JsonStream *s) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + ASSERT_OK(sd_json_buildo(&v, + SD_JSON_BUILD_PAIR_BOOLEAN("running", true), + SD_JSON_BUILD_PAIR_STRING("status", "running"))); + mock_qmp_expect_and_reply(s, "query-status", v); +} + +/* Drive a mock+client pair on a single event loop. The client fiber runs as userdata=client, + * the mock fiber as userdata=fd (the server-side socket). */ +static void run_qmp_test(sd_fiber_func_t mock_fn, sd_fiber_func_t client_fn) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_future_unrefp) sd_future *client_f = NULL, *mock_f = NULL; + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_close_pair_ int qmp_fds[2] = EBADF_PAIR; + + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + ASSERT_OK(qmp_client_connect_fd(&client, TAKE_FD(qmp_fds[0]))); + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + ASSERT_OK(sd_fiber_new(event, "mock", mock_fn, FD_TO_PTR(TAKE_FD(qmp_fds[1])), NULL, &mock_f)); + ASSERT_OK(sd_fiber_new(event, "client", client_fn, client, NULL, &client_f)); + + ASSERT_OK(sd_event_loop(event)); + ASSERT_OK(sd_future_result(client_f)); + ASSERT_OK(sd_future_result(mock_f)); +} + +static int mock_qmp_basic_fiber(void *userdata) { + _cleanup_(json_stream_done) JsonStream s = {}; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *stop_event = NULL; + + mock_qmp_init(&s, PTR_TO_FD(userdata)); + mock_qmp_handshake(&s); + + mock_qmp_query_status_running(&s); + mock_qmp_expect_and_reply(&s, "stop", NULL); + + ASSERT_OK(sd_json_buildo(&stop_event, + SD_JSON_BUILD_PAIR_STRING("event", "STOP"), + SD_JSON_BUILD_PAIR("timestamp", SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_UNSIGNED("seconds", 1234), + SD_JSON_BUILD_PAIR_UNSIGNED("microseconds", 5678))))); + mock_qmp_send(&s, stop_event); + + mock_qmp_expect_and_reply(&s, "cont", NULL); + return 0; +} + +static int test_event_callback( + QmpClient *client, + const char *event, + sd_json_variant *data, + void *userdata) { + + bool *event_received = ASSERT_PTR(userdata); + + /* Ignore the synthetic SHUTDOWN emitted when the mock closes the connection. */ + if (streq(event, "STOP")) + *event_received = true; + + return 0; +} + +static int qmp_client_basic_fiber(void *userdata) { + QmpClient *client = ASSERT_NOT_NULL(userdata); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; + _cleanup_free_ char *error_desc = NULL; + bool event_received = false; + + qmp_client_bind_event(client, test_event_callback, &event_received); + + ASSERT_OK_POSITIVE(qmp_client_call(client, "query-status", NULL, &result, &error_desc)); + ASSERT_NULL(error_desc); + + 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"); + + ASSERT_OK_POSITIVE(qmp_client_call(client, "stop", NULL, NULL, NULL)); + ASSERT_OK_POSITIVE(qmp_client_call(client, "cont", NULL, NULL, NULL)); + + ASSERT_TRUE(event_received); + ASSERT_OK(sd_event_exit(sd_fiber_get_event(), 0)); + return 0; +} + +TEST(qmp_basic) { + run_qmp_test(mock_qmp_basic_fiber, qmp_client_basic_fiber); +} + +static int mock_qmp_eof_fiber(void *userdata) { + _cleanup_(json_stream_done) JsonStream s = {}; + + mock_qmp_init(&s, PTR_TO_FD(userdata)); + mock_qmp_handshake(&s); + /* Return; _cleanup_ closes the fd → client sees EOF. */ + return 0; +} + +static int qmp_client_eof_fiber(void *userdata) { + QmpClient *client = ASSERT_NOT_NULL(userdata); + int r = qmp_client_call(client, "query-status", NULL, NULL, NULL); + ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(r)); + ASSERT_OK(sd_event_exit(sd_fiber_get_event(), 0)); + return 0; +} + +TEST(qmp_eof) { + run_qmp_test(mock_qmp_eof_fiber, qmp_client_eof_fiber); +} + +static int mock_qmp_call_fiber(void *userdata) { + _cleanup_(json_stream_done) JsonStream s = {}; + + mock_qmp_init(&s, PTR_TO_FD(userdata)); + mock_qmp_handshake(&s); + + mock_qmp_query_status_running(&s); + mock_qmp_expect_and_reply_error(&s, "stop", "not running"); + mock_qmp_expect_and_reply_error(&s, "stop", "still not running"); + return 0; +} + +static int qmp_client_call_fiber(void *userdata) { + QmpClient *client = ASSERT_NOT_NULL(userdata); + _cleanup_(sd_future_cancel_wait_unrefp) sd_future *f = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; + _cleanup_free_ char *error_desc = NULL; + + /* Exercise qmp_client_call_future() + sd_fiber_await() + future_get_qmp_reply() + * directly — success path. */ + ASSERT_OK(qmp_client_call_future(client, "query-status", NULL, &f)); + ASSERT_OK(sd_fiber_await(f)); + ASSERT_OK(sd_future_result(f)); + ASSERT_OK(future_get_qmp_reply(f, &result, &error_desc)); + + ASSERT_NULL(error_desc); + 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-level error: future resolves with -EIO, sd_fiber_await() returns -EIO, and + * future_get_qmp_reply() also returns -EIO (mirroring future_get_bus_reply()) — with the + * detailed description captured via error_desc on top, and result left NULL. */ + f = sd_future_unref(f); + result = sd_json_variant_unref(result); + error_desc = mfree(error_desc); + + ASSERT_OK(qmp_client_call_future(client, "stop", NULL, &f)); + ASSERT_ERROR(sd_fiber_await(f), EIO); + ASSERT_ERROR(sd_future_result(f), EIO); + ASSERT_ERROR(future_get_qmp_reply(f, &result, &error_desc), EIO); + + ASSERT_NULL(result); + ASSERT_STREQ(error_desc, "not running"); + + /* qmp_client_call() also surfaces QMP errors as -EIO, regardless of whether the caller + * passed ret_error_desc. */ + ASSERT_ERROR(qmp_client_call(client, "stop", NULL, NULL, NULL), EIO); + ASSERT_OK(sd_event_exit(sd_fiber_get_event(), 0)); + return 0; +} + +TEST(qmp_call) { + run_qmp_test(mock_qmp_call_fiber, qmp_client_call_fiber); +} + +static int mock_qmp_call_disconnect_fiber(void *userdata) { + _cleanup_(json_stream_done) JsonStream s = {}; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *stop_cmd = NULL; + + mock_qmp_init(&s, PTR_TO_FD(userdata)); + mock_qmp_handshake(&s); + + /* Consume the stop command but don't reply — cleanup closes the fd and the client + * sees a disconnect while suspended. */ + mock_qmp_recv(&s, &stop_cmd); + return 0; +} + +static int qmp_client_call_disconnect_fiber(void *userdata) { + QmpClient *client = ASSERT_NOT_NULL(userdata); + int r = qmp_client_call(client, "stop", NULL, NULL, NULL); + ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(r)); + ASSERT_OK(sd_event_exit(sd_fiber_get_event(), 0)); + return 0; +} + +TEST(qmp_call_disconnect) { + run_qmp_test(mock_qmp_call_disconnect_fiber, qmp_client_call_disconnect_fiber); +} + +static int mock_qmp_fd_fiber(void *userdata) { + _cleanup_(json_stream_done) JsonStream s = {}; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *cap_cmd = NULL, *addfd_cmd = NULL, + *addfd_return = NULL; + + mock_qmp_init(&s, PTR_TO_FD(userdata)); + ASSERT_OK(json_stream_set_allow_fd_passing_input(&s, true, true)); + + mock_qmp_send_greeting(&s); + + /* The fd may ride with either command depending on AF_UNIX coalescing; count across both. */ + sd_json_variant *cap_id = mock_qmp_expect(&s, "qmp_capabilities", &cap_cmd); + size_t n_fds_total = json_stream_get_n_input_fds(&s); + json_stream_close_input_fds(&s); + mock_qmp_reply(&s, cap_id, NULL); + + sd_json_variant *addfd_id = mock_qmp_expect(&s, "add-fd", &addfd_cmd); + n_fds_total += json_stream_get_n_input_fds(&s); + json_stream_close_input_fds(&s); + ASSERT_EQ(n_fds_total, (size_t) 1); + + ASSERT_OK(sd_json_buildo(&addfd_return, + SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0), + SD_JSON_BUILD_PAIR_UNSIGNED("fd", 42))); + mock_qmp_reply(&s, addfd_id, addfd_return); + return 0; +} + +static int qmp_client_invoke_with_fd_fiber(void *userdata) { + QmpClient *client = ASSERT_NOT_NULL(userdata); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + _cleanup_close_ int fd_to_pass = -EBADF; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; + + fd_to_pass = ASSERT_OK_ERRNO(eventfd(0, EFD_CLOEXEC)); + + ASSERT_OK(sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0))); + + ASSERT_OK_POSITIVE(qmp_client_call(client, "add-fd", + QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), + &result, NULL)); + ASSERT_NOT_NULL(result); + ASSERT_OK(sd_event_exit(sd_fiber_get_event(), 0)); + return 0; +} + +TEST(qmp_invoke_with_fd) { + run_qmp_test(mock_qmp_fd_fiber, qmp_client_invoke_with_fd_fiber); +} + +static int on_dead_peer_reply( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + bool *fired = ASSERT_PTR(userdata); + + /* Peer was closed before the write hit the wire; expect a disconnect. */ + ASSERT_TRUE(ERRNO_IS_NEG_DISCONNECT(error)); + *fired = true; + return 0; +} + +/* Verify caller-supplied fds passed through QMP_CLIENT_ARGS_FD() are closed on client teardown + * even when the peer is already dead: invoke enqueues, the queue item owns the fd, unref closes. */ +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; + _cleanup_close_pair_ int qmp_fds[2] = EBADF_PAIR; + int saved_fd_value; + bool callback_fired = false; + + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + qmp_fds[1] = safe_close(qmp_fds[1]); + + fd_to_pass = ASSERT_OK_ERRNO(eventfd(0, EFD_CLOEXEC)); + saved_fd_value = fd_to_pass; + + ASSERT_OK(sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0))); + ASSERT_OK(qmp_client_connect_fd(&client, TAKE_FD(qmp_fds[0]))); + + ASSERT_OK(qmp_client_invoke(client, NULL, "add-fd", + QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), + on_dead_peer_reply, &callback_fired)); + ASSERT_EQ(fd_to_pass, -EBADF); + ASSERT_OK_ERRNO(fcntl(saved_fd_value, F_GETFD)); + + client = qmp_client_unref(client); + ASSERT_ERROR_ERRNO(fcntl(saved_fd_value, F_GETFD), EBADF); + ASSERT_TRUE(callback_fired); +} + +/* Shared mock for the two slot tests: the follow-up stop is what drives the event loop long + * enough to dispatch the query-status reply. */ +static int mock_qmp_slot_fiber(void *userdata) { + _cleanup_(json_stream_done) JsonStream s = {}; + + mock_qmp_init(&s, PTR_TO_FD(userdata)); + mock_qmp_handshake(&s); + + mock_qmp_query_status_running(&s); + mock_qmp_expect_and_reply(&s, "stop", NULL); + return 0; +} + +static int nop_callback( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + return 0; +} + +/* Tripwire for the cancel test: if it fires, the cancel didn't do its job. */ +static int tripwire_callback( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + bool *fired = ASSERT_PTR(userdata); + *fired = true; + return 0; +} + +static int qmp_client_invoke_slot_lifecycle_fiber(void *userdata) { + QmpClient *client = ASSERT_NOT_NULL(userdata); + _cleanup_(qmp_slot_unrefp) QmpSlot *slot = NULL; + + ASSERT_OK(qmp_client_invoke(client, &slot, "query-status", NULL, nop_callback, NULL)); + ASSERT_PTR_EQ(qmp_slot_get_client(slot), client); + + /* Drive the loop via a follow-up stop; its suspending call lets both replies dispatch. */ + ASSERT_OK_POSITIVE(qmp_client_call(client, "stop", NULL, NULL, NULL)); + + /* After dispatch the slot is disconnected from the client but still owned by us. */ + ASSERT_NULL(qmp_slot_get_client(slot)); + + /* Explicit out-of-order unref exercises the already-disconnected path in qmp_slot_free(). */ + slot = qmp_slot_unref(slot); + ASSERT_OK(sd_event_exit(sd_fiber_get_event(), 0)); + return 0; +} + +TEST(qmp_invoke_slot_lifecycle) { + run_qmp_test(mock_qmp_slot_fiber, qmp_client_invoke_slot_lifecycle_fiber); +} + +static int qmp_client_invoke_slot_cancel_fiber(void *userdata) { + QmpClient *client = ASSERT_NOT_NULL(userdata); + QmpSlot *slot = NULL; + bool fired = false; + + ASSERT_OK(qmp_client_invoke(client, &slot, "query-status", NULL, tripwire_callback, &fired)); + + /* Drop our sole ref → slot disconnects from the client's pending set. The enqueued + * query-status is still on the wire; its reply lands on an unknown id and is discarded. */ + slot = qmp_slot_unref(slot); + + ASSERT_OK_POSITIVE(qmp_client_call(client, "stop", NULL, NULL, NULL)); + + ASSERT_FALSE(fired); + ASSERT_OK(sd_event_exit(sd_fiber_get_event(), 0)); + return 0; +} + +TEST(qmp_invoke_slot_cancel) { + run_qmp_test(mock_qmp_slot_fiber, qmp_client_invoke_slot_cancel_fiber); +} + +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 real QAPI strings. Verify we walk all object entries to find members 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")); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-random-util.c b/src/test/test-random-util.c index e5972718b46ff..bceb7b937e0a8 100644 --- a/src/test/test-random-util.c +++ b/src/test/test-random-util.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "hexdecoct.h" #include "log.h" #include "memory-util.h" @@ -15,7 +13,7 @@ TEST(random_bytes) { for (size_t i = 1; i < sizeof buf; i++) { random_bytes(buf, i); if (i + 1 < sizeof buf) - assert_se(buf[i] == 0); + ASSERT_EQ(buf[i], 0); hexdump(stdout, buf, i); } @@ -25,9 +23,9 @@ TEST(crypto_random_bytes) { uint8_t buf[16] = {}; for (size_t i = 1; i < sizeof buf; i++) { - assert_se(crypto_random_bytes(buf, i) == 0); + ASSERT_OK(crypto_random_bytes(buf, i)); if (i + 1 < sizeof buf) - assert_se(buf[i] == 0); + ASSERT_EQ(buf[i], 0); hexdump(stdout, buf, i); } @@ -53,21 +51,25 @@ static void test_random_u64_range_one(unsigned mod) { /* Print histogram: vertical axis — value, horizontal axis — count. * * The expected value is always TOTAL/mod, because the distribution should be flat. The expected - * variance is TOTAL×p×(1-p), where p==1/mod, and standard deviation the root of the variance. - * Assert that the deviation from the expected value is less than 6 standard deviations. + * variance is TOTAL×p×(1-p), where p==1/mod. Assert that the deviation from the expected value + * is less than 6 standard deviations by comparing squared values (diff² < 36·variance), which + * avoids a sqrt() call and the libm dependency that comes with it at -O0. */ unsigned scale = 2 * max / (columns() < 20 ? 80 : columns() - 20); double exp = (double) TOTAL / mod; + double variance = exp * (mod > 1 ? mod - 1 : 1) / mod; for (size_t i = 0; i < mod; i++) { - double dev = (count[i] - exp) / sqrt(exp * (mod > 1 ? mod - 1 : 1) / mod); - log_debug("%02zu: %5u (%+.3f)%*s", - i, count[i], dev, + double diff = count[i] - exp; + double dev_sq = diff * diff / variance; + + log_debug("%02zu: %5u (z²=%.3f)%*s", + i, count[i], dev_sq, (int) (count[i] / scale), "x"); - assert_se(fabs(dev) < 6); /* 6 sigma is excessive, but this check should be enough to - * identify catastrophic failure while minimizing false - * positives. */ + ASSERT_TRUE(dev_sq < 36); /* 36 = 6²; 6 sigma is excessive, but this check should be + * enough to identify catastrophic failure while minimizing + * false positives. */ } } diff --git a/src/test/test-recurse-dir.c b/src/test/test-recurse-dir.c index b29a7eab9d0ab..453b89d172498 100644 --- a/src/test/test-recurse-dir.c +++ b/src/test/test-recurse-dir.c @@ -2,14 +2,17 @@ #include #include +#include #include "fd-util.h" #include "log.h" #include "recurse-dir.h" +#include "rm-rf.h" #include "stat-util.h" #include "strv.h" #include "tests.h" #include "time-util.h" +#include "tmpfile-util.h" static char **list_nftw = NULL; @@ -119,6 +122,71 @@ static int recurse_dir_callback( return RECURSE_DIR_CONTINUE; } +static void assert_entries(DirectoryEntries *de, char **expected) { + ASSERT_NOT_NULL(de); + + /* Verifies that the directory entries enumerated in 'de' are exactly the ones listed in + * 'expected' (order is irrelevant). */ + + ASSERT_EQ(de->n_entries, strv_length(expected)); + + FOREACH_ARRAY(i, de->entries, de->n_entries) + ASSERT_TRUE(strv_contains(expected, (*i)->d_name)); + + STRV_FOREACH(e, expected) { + bool found = false; + + FOREACH_ARRAY(i, de->entries, de->n_entries) + if (streq((*i)->d_name, *e)) { + found = true; + break; + } + + ASSERT_TRUE(found); + } +} + +static void check_readdir_all(int tfd, RecurseDirFlags flags, char **expected) { + _cleanup_free_ DirectoryEntries *de = NULL; + _cleanup_close_ int fd = -EBADF; + + /* readdir_all() consumes the directory fd offset, hence reopen a fresh fd for each enumeration. */ + ASSERT_OK(fd = fd_reopen(tfd, O_DIRECTORY|O_CLOEXEC)); + ASSERT_OK(readdir_all(fd, flags, &de)); + assert_entries(de, expected); +} + +static void test_must_be_flags(void) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, reg_fd = -EBADF; + + log_info("/* %s */", __func__); + + /* Populate a temporary directory with one entry of each interesting type and verify that the + * RECURSE_DIR_MUST_BE_* flags select exactly the right subset. */ + + ASSERT_OK(tfd = mkdtemp_open(NULL, O_DIRECTORY|O_CLOEXEC, &t)); + + ASSERT_OK_ERRNO(mkdirat(tfd, "dir", 0777)); + ASSERT_OK_ERRNO(reg_fd = openat(tfd, "reg", O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC, 0666)); + reg_fd = safe_close(reg_fd); + ASSERT_OK_ERRNO(symlinkat("reg", tfd, "lnk")); + ASSERT_OK_ERRNO(mknodat(tfd, "sock", S_IFSOCK|0666, 0)); + + /* Without any MUST_BE flag we get all four entries. */ + check_readdir_all(tfd, 0, STRV_MAKE("dir", "reg", "lnk", "sock")); + + /* A single MUST_BE flag selects exactly the entries of the matching type. */ + check_readdir_all(tfd, RECURSE_DIR_MUST_BE_DIRECTORY, STRV_MAKE("dir")); + check_readdir_all(tfd, RECURSE_DIR_MUST_BE_REGULAR, STRV_MAKE("reg")); + check_readdir_all(tfd, RECURSE_DIR_MUST_BE_SYMLINK, STRV_MAKE("lnk")); + check_readdir_all(tfd, RECURSE_DIR_MUST_BE_SOCKET, STRV_MAKE("sock")); + + /* The flags may be combined, in which case we get the union of the matching entries. */ + check_readdir_all(tfd, RECURSE_DIR_MUST_BE_REGULAR|RECURSE_DIR_MUST_BE_SYMLINK, STRV_MAKE("reg", "lnk")); + check_readdir_all(tfd, RECURSE_DIR_MUST_BE_DIRECTORY|RECURSE_DIR_MUST_BE_SOCKET, STRV_MAKE("dir", "sock")); +} + int main(int argc, char *argv[]) { _cleanup_strv_free_ char **list_recurse_dir = NULL; const char *p; @@ -128,6 +196,8 @@ int main(int argc, char *argv[]) { log_show_color(true); test_setup_logging(LOG_INFO); + test_must_be_flags(); + if (argc > 1) p = argv[1]; else diff --git a/src/test/test-sched-prio.c b/src/test/test-sched-prio.c index c1305ac4abd21..a523c01e8f01b 100644 --- a/src/test/test-sched-prio.c +++ b/src/test/test-sched-prio.c @@ -33,7 +33,7 @@ int main(int argc, char *argv[]) { if (manager_errno_skip_test(r)) return log_tests_skipped_errno(r, "manager_new"); assert_se(r >= 0); - assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL, NULL) >= 0); /* load idle ok */ assert_se(manager_load_startable_unit_or_warn(m, "sched_idle_ok.service", NULL, &idle_ok) >= 0); diff --git a/src/test/test-sd-hwdb.c b/src/test/test-sd-hwdb.c deleted file mode 100644 index 0030096a7c4fd..0000000000000 --- a/src/test/test-sd-hwdb.c +++ /dev/null @@ -1,83 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "sd-hwdb.h" - -#include "errno-util.h" -#include "hwdb-internal.h" -#include "nulstr-util.h" -#include "tests.h" - -TEST(failed_enumerate) { - _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; - const char *key, *value; - - assert_se(sd_hwdb_new(&hwdb) == 0); - - assert_se(sd_hwdb_seek(hwdb, "no-such-modalias-should-exist") == 0); - - assert_se(sd_hwdb_enumerate(hwdb, &key, &value) == 0); - ASSERT_RETURN_EXPECTED_SE(sd_hwdb_enumerate(hwdb, &key, NULL) == -EINVAL); - ASSERT_RETURN_EXPECTED_SE(sd_hwdb_enumerate(hwdb, NULL, &value) == -EINVAL); -} - -#define DELL_MODALIAS \ - "evdev:atkbd:dmi:bvnXXX:bvrYYY:bdZZZ:svnDellXXX:pnYYY:" - -TEST(basic_enumerate) { - _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; - const char *key, *value; - size_t len1 = 0, len2 = 0; - int r; - - assert_se(sd_hwdb_new(&hwdb) == 0); - - assert_se(sd_hwdb_seek(hwdb, DELL_MODALIAS) == 0); - - for (;;) { - r = sd_hwdb_enumerate(hwdb, &key, &value); - assert_se(IN_SET(r, 0, 1)); - if (r == 0) - break; - assert_se(key); - assert_se(value); - log_debug("A: \"%s\" → \"%s\"", key, value); - len1 += strlen(key) + strlen(value); - } - - SD_HWDB_FOREACH_PROPERTY(hwdb, DELL_MODALIAS, key, value) { - log_debug("B: \"%s\" → \"%s\"", key, value); - len2 += strlen(key) + strlen(value); - } - - assert_se(len1 == len2); -} - -TEST(sd_hwdb_new_from_path) { - _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; - int r; - - ASSERT_RETURN_EXPECTED_SE(sd_hwdb_new_from_path(NULL, &hwdb) == -EINVAL); - ASSERT_RETURN_EXPECTED_SE(sd_hwdb_new_from_path("", &hwdb) == -EINVAL); - assert_se(sd_hwdb_new_from_path("/path/that/should/not/exist", &hwdb) < 0); - - NULSTR_FOREACH(hwdb_bin_path, HWDB_BIN_PATHS) { - r = sd_hwdb_new_from_path(hwdb_bin_path, &hwdb); - if (r >= 0) - break; - } - - assert_se(r >= 0); -} - -static int intro(void) { - _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; - int r; - - r = sd_hwdb_new(&hwdb); - if (r == -ENOENT || ERRNO_IS_PRIVILEGE(r)) - return log_tests_skipped_errno(r, "cannot open hwdb"); - - return EXIT_SUCCESS; -} - -DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); 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-sha1.c b/src/test/test-sha1.c index 3a5ff148df47e..6356400c62887 100644 --- a/src/test/test-sha1.c +++ b/src/test/test-sha1.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "hexdecoct.h" -#include "sha1-fundamental.h" +#include "sha1.h" #include "tests.h" static void sha1_process_string(const char *key, struct sha1_ctx *ctx) { diff --git a/src/test/test-signal-util.c b/src/test/test-signal-util.c index 822556022d9f5..541ff8c7fcabe 100644 --- a/src/test/test-signal-util.c +++ b/src/test/test-signal-util.c @@ -103,6 +103,17 @@ TEST(signal_from_string) { test_signal_from_string_number("-2", -ERANGE); } +TEST(signal_code_to_string) { + assert_se(streq_ptr(signal_code_to_string(SIGSEGV, SEGV_MAPERR), "SEGV_MAPERR")); + assert_se(streq_ptr(signal_code_to_string(SIGILL, ILL_ILLOPC), "ILL_ILLOPC")); + assert_se(streq_ptr(signal_code_to_string(SIGCHLD, CLD_CONTINUED), "CLD_CONTINUED")); + assert_se(streq_ptr(signal_code_to_string(SIGABRT, SI_TKILL), "SI_TKILL")); + assert_se(streq_ptr(signal_code_to_string(SIGABRT, SI_USER), "SI_USER")); + assert_se(streq_ptr(signal_code_to_string(SIGABRT, SI_KERNEL), "SI_KERNEL")); + assert_se(streq_ptr(signal_code_to_string(SIGSYS, SYS_SECCOMP), "SYS_SECCOMP")); + assert_se(signal_code_to_string(SIGABRT, 99) == NULL); +} + TEST(block_signals) { assert_se(signal_is_blocked(SIGUSR1) == 0); assert_se(signal_is_blocked(SIGALRM) == 0); diff --git a/src/test/test-socket-bind.c b/src/test/test-socket-bind.c index 4e4fdbedd0fa9..63d249d6b4efe 100644 --- a/src/test/test-socket-bind.c +++ b/src/test/test-socket-bind.c @@ -133,7 +133,7 @@ int main(int argc, char *argv[]) { assert_se(runtime_dir = setup_fake_runtime_dir()); assert_se(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0); - assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + assert_se(manager_startup(m, NULL, NULL, NULL, NULL) >= 0); assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "2000", STRV_MAKE("2000"), STRV_MAKE("any")) >= 0); assert_se(test_socket_bind(m, "socket_bind_test.service", netcat_path, "2000", STRV_MAKE("ipv6:2001-2002"), STRV_MAKE("any")) >= 0); diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c index 090839ac06842..038140d4c578c 100644 --- a/src/test/test-socket-util.c +++ b/src/test/test-socket-util.c @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include #include "alloc-util.h" @@ -530,4 +533,34 @@ TEST(getpeerpidref) { ASSERT_TRUE(!pidref_equal(&pidref0, &pidref_pid1)); } +TEST(tos_to_priority) { + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS7), TC_PRIO_CONTROL); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS6), TC_PRIO_CONTROL); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS5), TC_PRIO_INTERACTIVE); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS4), TC_PRIO_INTERACTIVE); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS3), TC_PRIO_INTERACTIVE_BULK); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS2), TC_PRIO_INTERACTIVE_BULK); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS1), TC_PRIO_BULK); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS0), TC_PRIO_BESTEFFORT); + + /* check if lower bits are correctly filtered. */ + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS7 | IPTOS_LOWDELAY), TC_PRIO_CONTROL); + ASSERT_EQ(tos_to_priority(IPTOS_CLASS_CS1 | IPTOS_LOWCOST), TC_PRIO_BULK); + + ASSERT_EQ(tos_to_priority(0x00), TC_PRIO_BESTEFFORT); + ASSERT_EQ(tos_to_priority(0xff), TC_PRIO_CONTROL); +} + +TEST(socket_xattr_supported) { + int r; + + r = socket_xattr_supported(); + ASSERT_OK(r); + + log_info("Extended attributes on socket inodes supported: %s", yes_no(r)); + + /* A second call must agree with the first. */ + ASSERT_EQ(socket_xattr_supported(), r); +} + DEFINE_TEST_MAIN(LOG_DEBUG); 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..970421c7abb26 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -9,6 +9,17 @@ #include "strv.h" #include "tests.h" +TEST(ellipsize_mem_ansi_short) { + _cleanup_free_ char *a = ellipsize_mem("X\x1b[m", 4, 1, 50); + assert_se(a); + + _cleanup_free_ char *b = ellipsize_mem(" \x1b[A", 4, 1, 0); + assert_se(b); + + _cleanup_free_ char *c = ellipsize_mem("\x1b[m", 3, 1, 50); + assert_se(c); +} + TEST(xsprintf) { char buf[5]; @@ -325,6 +336,8 @@ TEST(strrep) { ASSERT_STREQ(onea, "waldo"); ASSERT_STREQ(threea, "waldowaldowaldo"); + + ASSERT_NULL(strrep("waldo", SIZE_MAX - 1)); } TEST(string_has_cc) { @@ -533,6 +546,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"; @@ -615,6 +651,45 @@ TEST(split_pair) { ASSERT_OK(split_pair("===", "==", &a, &b)); ASSERT_STREQ(a, ""); ASSERT_STREQ(b, "="); + a = mfree(a); + b = mfree(b); + + /* The output parameters are optional */ + ASSERT_OK(split_pair("foo=bar", "=", NULL, &b)); + ASSERT_NULL(a); + ASSERT_STREQ(b, "bar"); + b = mfree(b); + ASSERT_OK(split_pair("foo=bar", "=", &a, NULL)); + ASSERT_STREQ(a, "foo"); + ASSERT_NULL(b); + a = mfree(a); + ASSERT_OK(split_pair("foo=bar", "=", NULL, NULL)); + ASSERT_NULL(a); + ASSERT_NULL(b); + /* ... but the separator must still be present */ + ASSERT_ERROR(split_pair("foo", "=", NULL, NULL), EINVAL); +} + +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) { @@ -1448,4 +1523,198 @@ 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_NEWLINES: newlines allowed, quotes/globs still rejected. */ + ASSERT_TRUE(string_is_safe("hello", STRING_ALLOW_NEWLINES)); + ASSERT_TRUE(string_is_safe("hello world", STRING_ALLOW_NEWLINES)); + ASSERT_TRUE(string_is_safe("\n", STRING_ALLOW_NEWLINES)); + ASSERT_TRUE(string_is_safe("a\nb", STRING_ALLOW_NEWLINES)); + ASSERT_TRUE(string_is_safe("foo\n", STRING_ALLOW_NEWLINES)); + ASSERT_TRUE(string_is_safe("\nfoo", STRING_ALLOW_NEWLINES)); + ASSERT_TRUE(string_is_safe("foo\nbar", STRING_ALLOW_NEWLINES)); + ASSERT_FALSE(string_is_safe("foo\\nbar", STRING_ALLOW_NEWLINES)); /* literal backslash, not newline, rejected */ + ASSERT_FALSE(string_is_safe("\"", STRING_ALLOW_NEWLINES)); /* quotes still rejected */ + ASSERT_FALSE(string_is_safe("*", STRING_ALLOW_NEWLINES)); /* globs still rejected */ + + /* 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("foo\nbar", STRING_ALLOW_BACKSLASHES)); /* newline still rejected */ + 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\nb", STRING_ALLOW_QUOTES)); /* newline still rejected */ + 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("foo\nbar", STRING_ALLOW_GLOBS)); /* newline still rejected */ + 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)); + + /* STRING_DISALLOW_WHITESPACE: rejects whitespace (space, tab, newline, carriage return). */ + ASSERT_TRUE(string_is_safe("hello", STRING_DISALLOW_WHITESPACE)); + ASSERT_TRUE(string_is_safe("foo-bar_baz", STRING_DISALLOW_WHITESPACE)); + ASSERT_TRUE(string_is_safe("über", STRING_DISALLOW_WHITESPACE)); /* valid UTF-8 still allowed */ + ASSERT_TRUE(string_is_safe("hello world", 0)); /* space accepted by default */ + ASSERT_FALSE(string_is_safe("hello world", STRING_DISALLOW_WHITESPACE)); /* but not with the flag */ + ASSERT_FALSE(string_is_safe(" ", STRING_DISALLOW_WHITESPACE)); + ASSERT_FALSE(string_is_safe("foo ", STRING_DISALLOW_WHITESPACE)); + ASSERT_FALSE(string_is_safe(" foo", STRING_DISALLOW_WHITESPACE)); + ASSERT_FALSE(string_is_safe("a\tb", STRING_DISALLOW_WHITESPACE)); + ASSERT_FALSE(string_is_safe("a\rb", STRING_DISALLOW_WHITESPACE)); + /* The flag overrides STRING_ALLOW_NEWLINES for the newline character, which is whitespace too. */ + ASSERT_TRUE(string_is_safe("a\nb", STRING_ALLOW_NEWLINES)); + ASSERT_FALSE(string_is_safe("a\nb", STRING_ALLOW_NEWLINES | STRING_DISALLOW_WHITESPACE)); + + /* STRING_FILENAME_PART: like STRING_FILENAME, but "." and ".." are accepted; '/' still rejected. */ + ASSERT_TRUE(string_is_safe("hello", STRING_FILENAME_PART)); + ASSERT_TRUE(string_is_safe("hello.txt", STRING_FILENAME_PART)); + ASSERT_TRUE(string_is_safe("...", STRING_FILENAME_PART)); + ASSERT_TRUE(string_is_safe(".hidden", STRING_FILENAME_PART)); + ASSERT_TRUE(string_is_safe(".", STRING_FILENAME_PART)); /* accepted, unlike STRING_FILENAME */ + ASSERT_TRUE(string_is_safe("..", STRING_FILENAME_PART)); /* accepted, unlike STRING_FILENAME */ + ASSERT_FALSE(string_is_safe("", STRING_FILENAME_PART)); /* empty still rejected by default */ + ASSERT_TRUE(string_is_safe("", STRING_FILENAME_PART | STRING_ALLOW_EMPTY)); /* ... unless explicitly allowed */ + ASSERT_FALSE(string_is_safe("/", STRING_FILENAME_PART)); + ASSERT_FALSE(string_is_safe("/foo", STRING_FILENAME_PART)); + ASSERT_FALSE(string_is_safe("foo/bar", STRING_FILENAME_PART)); + ASSERT_FALSE(string_is_safe("foo/", STRING_FILENAME_PART)); + + /* 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_ASCII | + STRING_ALLOW_EMPTY | + STRING_ALLOW_NEWLINES | + 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\nbar", all)); /* newline allowed */ + 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..386c54f78719e 100644 --- a/src/test/test-strv.c +++ b/src/test/test-strv.c @@ -510,6 +510,26 @@ TEST(strv_overlap) { assert_se(!strv_overlap((char **)input_table, (char**)input_table_unique)); } +TEST(strv_remove_strv) { + _cleanup_strv_free_ char **l = NULL; + + ASSERT_OK(strv_extend_strv(&l, STRV_MAKE("one", "two", "three", "four"), false)); + + ASSERT_PTR_EQ(strv_remove_strv(l, STRV_MAKE("two", "four")), l); + ASSERT_TRUE(strv_equal(l, STRV_MAKE("one", "three"))); + + /* Removing entries not present is a no-op. */ + ASSERT_PTR_EQ(strv_remove_strv(l, STRV_MAKE("nope")), l); + ASSERT_TRUE(strv_equal(l, STRV_MAKE("one", "three"))); + + /* Empty removal list leaves the list untouched. */ + ASSERT_PTR_EQ(strv_remove_strv(l, STRV_EMPTY), l); + ASSERT_TRUE(strv_equal(l, STRV_MAKE("one", "three"))); + + /* Removing from an empty/NULL list is fine. */ + ASSERT_NULL(strv_remove_strv(NULL, STRV_MAKE("one"))); +} + TEST(strv_sort) { const char* input_table[] = { "durian", @@ -799,14 +819,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..19ee7b5a72322 100644 --- a/src/test/test-tpm2.c +++ b/src/test/test-tpm2.c @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "architecture.h" +#include "crypto-util.h" #include "hexdecoct.h" +#include "iovec-util.h" +#include "random-util.h" #include "tests.h" #include "tpm2-util.h" #include "virt.h" @@ -46,6 +48,70 @@ TEST(tpm2_pcr_index_from_string) { assert_se(tpm2_pcr_index_from_string("24") == -EINVAL); } +TEST(tpm2_pcr_bank_from_efi_active) { + uint16_t bank; + + /* SHA256 is the top preference whenever it is active. */ + ASSERT_OK(tpm2_pcr_bank_from_efi_active((1u << TPM2_ALG_SHA1) | (1u << TPM2_ALG_SHA256) | (1u << TPM2_ALG_SHA384) | (1u << TPM2_ALG_SHA512), &bank)); + ASSERT_EQ(bank, TPM2_ALG_SHA256); + + /* Without SHA256, SHA384 is preferred over SHA512 (shorter digest, less TPM event log space), and + * both win over SHA1. */ + ASSERT_OK(tpm2_pcr_bank_from_efi_active((1u << TPM2_ALG_SHA1) | (1u << TPM2_ALG_SHA384) | (1u << TPM2_ALG_SHA512), &bank)); + ASSERT_EQ(bank, TPM2_ALG_SHA384); + ASSERT_OK(tpm2_pcr_bank_from_efi_active((1u << TPM2_ALG_SHA1) | (1u << TPM2_ALG_SHA512), &bank)); + ASSERT_EQ(bank, TPM2_ALG_SHA512); + + /* SHA384-only firmware must resolve, not fail. */ + ASSERT_OK(tpm2_pcr_bank_from_efi_active(1u << TPM2_ALG_SHA384, &bank)); + ASSERT_EQ(bank, TPM2_ALG_SHA384); + + /* Single-bank cases pick the obvious bank. */ + ASSERT_OK(tpm2_pcr_bank_from_efi_active(1u << TPM2_ALG_SHA256, &bank)); + ASSERT_EQ(bank, TPM2_ALG_SHA256); + ASSERT_OK(tpm2_pcr_bank_from_efi_active(1u << TPM2_ALG_SHA512, &bank)); + ASSERT_EQ(bank, TPM2_ALG_SHA512); + ASSERT_OK(tpm2_pcr_bank_from_efi_active(1u << TPM2_ALG_SHA1, &bank)); + ASSERT_EQ(bank, TPM2_ALG_SHA1); + + /* No bank we are willing to use -> -EOPNOTSUPP. Empty mask, or only a bank we cannot hash in + * software (SM3_256, TCG algorithm id 0x12). */ + ASSERT_ERROR(tpm2_pcr_bank_from_efi_active(0, &bank), EOPNOTSUPP); + ASSERT_ERROR(tpm2_pcr_bank_from_efi_active(1u << 0x12, &bank), EOPNOTSUPP); +} + +TEST(tpm2_pcr_bank_from_efi_active_legacy) { + uint16_t bank; + + /* The legacy variant re-derives the bank for old enrollments that did not record one. Such secrets + * could only ever have been sealed against SHA256 or SHA1, so the choice MUST stay restricted to + * those two banks regardless of which stronger banks the firmware reports as active — otherwise we'd + * re-derive a bank the secret was never bound to and silently fail to unseal. */ + + /* SHA256 stays the top preference. */ + ASSERT_OK(tpm2_pcr_bank_from_efi_active_legacy((1u << TPM2_ALG_SHA1) | (1u << TPM2_ALG_SHA256) | (1u << TPM2_ALG_SHA384) | (1u << TPM2_ALG_SHA512), &bank)); + ASSERT_EQ(bank, TPM2_ALG_SHA256); + + /* The crucial backwards-compatibility case: with SHA384/SHA512 active but no SHA256, the legacy + * variant must fall back to SHA1, NOT pick the stronger SHA384 the way the non-legacy variant does + * (compare the SHA384 result in the test above). */ + ASSERT_OK(tpm2_pcr_bank_from_efi_active_legacy((1u << TPM2_ALG_SHA1) | (1u << TPM2_ALG_SHA384) | (1u << TPM2_ALG_SHA512), &bank)); + ASSERT_EQ(bank, TPM2_ALG_SHA1); + + /* Single-bank cases for the two banks we accept. */ + ASSERT_OK(tpm2_pcr_bank_from_efi_active_legacy(1u << TPM2_ALG_SHA256, &bank)); + ASSERT_EQ(bank, TPM2_ALG_SHA256); + ASSERT_OK(tpm2_pcr_bank_from_efi_active_legacy(1u << TPM2_ALG_SHA1, &bank)); + ASSERT_EQ(bank, TPM2_ALG_SHA1); + + /* Banks the legacy variant never binds to -> -EOPNOTSUPP, even when active. A secret could not have + * been sealed against these by the old code, so there is nothing to re-derive. */ + ASSERT_ERROR(tpm2_pcr_bank_from_efi_active_legacy(1u << TPM2_ALG_SHA384, &bank), EOPNOTSUPP); + ASSERT_ERROR(tpm2_pcr_bank_from_efi_active_legacy(1u << TPM2_ALG_SHA512, &bank), EOPNOTSUPP); + ASSERT_ERROR(tpm2_pcr_bank_from_efi_active_legacy((1u << TPM2_ALG_SHA384) | (1u << TPM2_ALG_SHA512), &bank), EOPNOTSUPP); + ASSERT_ERROR(tpm2_pcr_bank_from_efi_active_legacy(0, &bank), EOPNOTSUPP); +} + TEST(tpm2_util_pbkdf2_hmac_sha256) { /* @@ -782,25 +848,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) { @@ -876,13 +942,6 @@ static void check_tpm2b_public_from_rsa_pem(const char *pem, const char *hexn, u } TEST(tpm2b_public_from_openssl_pkey) { - // TODO: this test fails on s390x but only on Github Actions, re-enable once - // https://github.com/systemd/systemd/issues/38229 is fixed - if (strstr_ptr(ci_environment(), "github-actions") && uname_architecture() == ARCHITECTURE_S390X) { - log_notice("%s: skipping test on GH Actions because of systemd/systemd#38229", __func__); - return; - } - /* standard ECC key */ check_tpm2b_public_from_ecc_pem("2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a30444151634451674145726a6e4575424c73496c3972687068777976584e50686a346a426e500a44586e794a304b395579724e6764365335413532542b6f5376746b436a365a726c34685847337741515558706f426c532b7448717452714c35513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a", "ae39c4b812ec225f6b869870caf5cd3e18f88c19cf0d79f22742bd532acd81de", @@ -1271,7 +1330,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) { @@ -1316,6 +1375,81 @@ static void check_seal_unseal(Tpm2Context *c) { } } +static void check_nv_index_read(Tpm2Context *c) { + int r; + uint8_t payload[1031]; + + assert(c); + + TEST_LOG_FUNC(); + + random_bytes(payload, sizeof(payload)); + struct iovec data = IOVEC_MAKE(payload, sizeof(payload)); + + /* Test chunked reads first by mocking c->max_nv_buffer_size with several values that are less than + * the payload size and the TPM's reported size for TPM2_PT_NV_BUFFER_MAX. */ + TPM2_HANDLE nv_index = 0; + _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; + r = tpm2_define_data_nv_index(c, /* session= */ NULL, /* requested_nv_index= */ 0, &data, &nv_index, &nv_handle); + if (r < 0) { + /* Could fail because the index size is greater than the value of TPM2_PT_NV_INDEX_MAX, or + * there isn't enough space available. */ + log_notice_errno(r, "Could not allocate NV index, skipping NV index read test: %m"); + return; + } + ASSERT_NE(nv_index, 0U); + ASSERT_NOT_NULL(nv_handle); + + uint16_t saved_max_nv_buffer_size = c->max_nv_buffer_size; + + static const uint16_t chunk_sizes[] = { 128, 256, 512, 1024 }; + FOREACH_ELEMENT(cs, chunk_sizes) { + if (*cs >= saved_max_nv_buffer_size) + continue; + + c->max_nv_buffer_size = *cs; + + _cleanup_(iovec_done) struct iovec value = {}; + ASSERT_OK_ZERO(tpm2_read_nv_index(c, /* session= */ NULL, nv_index, nv_handle, &value)); + ASSERT_TRUE(iovec_equal(&value, &data)); + } + + c->max_nv_buffer_size = saved_max_nv_buffer_size; + + ASSERT_OK_ZERO(tpm2_undefine_nv_index(c, /* session= */ NULL, nv_index, nv_handle)); + nv_index = 0; + nv_handle = tpm2_handle_free(nv_handle); + + /* Test reading of a payload with the size of the reported TPM2_PT_NV_BUFFER_MAX. */ + _cleanup_free_ void *payload2 = malloc(c->max_nv_buffer_size); + ASSERT_NOT_NULL(payload2); + random_bytes(payload2, c->max_nv_buffer_size); + struct iovec data2 = IOVEC_MAKE(payload2, c->max_nv_buffer_size); + ASSERT_OK_ZERO(tpm2_define_data_nv_index(c, /* session= */ NULL, /* requested_nv_index= */ 0, &data2, &nv_index, &nv_handle)); + ASSERT_NE(nv_index, 0U); + ASSERT_NOT_NULL(nv_handle); + + _cleanup_(iovec_done) struct iovec value = {}; + ASSERT_OK_ZERO(tpm2_read_nv_index(c, /* session= */ NULL, nv_index, nv_handle, &value)); + ASSERT_TRUE(iovec_equal(&value, &data2)); + + ASSERT_OK_ZERO(tpm2_undefine_nv_index(c, /* session= */ NULL, nv_index, nv_handle)); + nv_index = 0; + nv_handle = tpm2_handle_free(nv_handle); + iovec_done(&value); + + /* Test reading of a payload which is smaller than the reported size of TPM2_PT_NV_BUFFER_MAX. */ + data.iov_len = 36; + ASSERT_OK_ZERO(tpm2_define_data_nv_index(c, /* session= */ NULL, /* requested_nv_index= */ 0, &data, &nv_index, &nv_handle)); + ASSERT_NE(nv_index, 0U); + ASSERT_NOT_NULL(nv_handle); + + ASSERT_OK_ZERO(tpm2_read_nv_index(c, /* session= */ NULL, nv_index, nv_handle, &value)); + ASSERT_TRUE(iovec_equal(&value, &data)); + + ASSERT_OK_ZERO(tpm2_undefine_nv_index(c, /* session= */ NULL, nv_index, nv_handle)); +} + TEST_RET(tests_which_require_tpm) { _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; int r = 0; @@ -1329,6 +1463,7 @@ TEST_RET(tests_which_require_tpm) { check_best_srk_template(c); check_get_or_create_srk(c); check_seal_unseal(c); + check_nv_index_read(c); #if HAVE_OPENSSL r = check_calculate_seal(c); diff --git a/src/test/test-uid-range.c b/src/test/test-uid-range.c index 6eef98153ef55..fdcbd8896e80a 100644 --- a/src/test/test-uid-range.c +++ b/src/test/test-uid-range.c @@ -318,6 +318,68 @@ TEST(uid_range_partition) { p = uid_range_free(p); + /* Small entry preceding a large entry: the small entry must be dropped and the large entry + * partitioned without the in-place backward-fill write cursor aliasing the still-live small entry + * slot. */ + ASSERT_OK(uid_range_add_str(&p, "0-4")); + ASSERT_OK(uid_range_add_str(&p, "100-129")); + ASSERT_EQ(uid_range_entries(p), 2U); + ASSERT_OK(uid_range_partition(p, 10)); + ASSERT_EQ(uid_range_entries(p), 3U); + ASSERT_EQ(p->entries[0].start, 100U); + ASSERT_EQ(p->entries[0].nr, 10U); + ASSERT_EQ(p->entries[1].start, 110U); + ASSERT_EQ(p->entries[1].nr, 10U); + ASSERT_EQ(p->entries[2].start, 120U); + ASSERT_EQ(p->entries[2].nr, 10U); + + p = uid_range_free(p); + + /* A too-small entry between two partitionable entries is dropped; the others still partition. */ + ASSERT_OK(uid_range_add_str(&p, "0-4")); /* nr=5 < size -> dropped */ + ASSERT_OK(uid_range_add_str(&p, "50-69")); /* nr=20 -> 2 parts */ + ASSERT_OK(uid_range_add_str(&p, "200-204")); /* nr=5 < size -> dropped */ + ASSERT_OK(uid_range_add_str(&p, "1000-1029")); /* nr=30 -> 3 parts */ + ASSERT_EQ(uid_range_entries(p), 4U); + ASSERT_OK(uid_range_partition(p, 10)); + ASSERT_EQ(uid_range_entries(p), 5U); + ASSERT_EQ(p->entries[0].start, 50U); + ASSERT_EQ(p->entries[0].nr, 10U); + ASSERT_EQ(p->entries[1].start, 60U); + ASSERT_EQ(p->entries[1].nr, 10U); + ASSERT_EQ(p->entries[2].start, 1000U); + ASSERT_EQ(p->entries[2].nr, 10U); + ASSERT_EQ(p->entries[3].start, 1010U); + ASSERT_EQ(p->entries[3].nr, 10U); + ASSERT_EQ(p->entries[4].start, 1020U); + ASSERT_EQ(p->entries[4].nr, 10U); + + p = uid_range_free(p); + + /* A too-small entry before a partitionable entry is dropped. */ + ASSERT_OK(uid_range_add_str(&p, "0-4")); /* nr=5 < size -> dropped */ + ASSERT_OK(uid_range_add_str(&p, "100-119")); /* nr=20 -> 2 parts */ + ASSERT_OK(uid_range_partition(p, 10)); + ASSERT_EQ(uid_range_entries(p), 2U); + ASSERT_EQ(p->entries[0].start, 100U); + ASSERT_EQ(p->entries[0].nr, 10U); + ASSERT_EQ(p->entries[1].start, 110U); + ASSERT_EQ(p->entries[1].nr, 10U); + + p = uid_range_free(p); + + /* A too-small entry after a partitionable entry is dropped. */ + ASSERT_OK(uid_range_add_str(&p, "0-199")); /* nr=200 -> 2 parts */ + ASSERT_OK(uid_range_add_str(&p, "1000-1004")); /* nr=5 < size -> dropped */ + ASSERT_OK(uid_range_partition(p, 100)); + ASSERT_EQ(uid_range_entries(p), 2U); + ASSERT_EQ(p->entries[0].start, 0U); + ASSERT_EQ(p->entries[0].nr, 100U); + ASSERT_EQ(p->entries[1].start, 100U); + ASSERT_EQ(p->entries[1].nr, 100U); + + p = uid_range_free(p); + /* Partition size of 1 */ ASSERT_OK(uid_range_add_str(&p, "100-102")); ASSERT_OK(uid_range_partition(p, 1)); diff --git a/src/test/test-unit-name.c b/src/test/test-unit-name.c index 672162244fd71..7f71f5fdc74e1 100644 --- a/src/test/test-unit-name.c +++ b/src/test/test-unit-name.c @@ -251,6 +251,18 @@ static void test_unit_name_mangle_with_suffix_one(const char *arg, int expected, ASSERT_STREQ(s, expected_name); } +static void test_unit_name_mangle_with_suffix_strict_one( + const char *arg, const char *suffix, int expected, const char *expected_name) { + _cleanup_free_ char *s = NULL; + int r; + + r = unit_name_mangle_with_suffix(arg, NULL, UNIT_NAME_MANGLE_WARN | UNIT_NAME_MANGLE_STRICT, suffix, &s); + log_debug("%s: %s (suffix=%s) -> %d, %s", __func__, arg, suffix, r, strnull(s)); + + assert_se(r == expected); + ASSERT_STREQ(s, expected_name); +} + TEST(unit_name_mangle_with_suffix) { test_unit_name_mangle_with_suffix_one("", -EINVAL, NULL); @@ -282,6 +294,28 @@ TEST(unit_name_mangle_with_suffix) { test_unit_name_mangle_with_suffix_one("/./.././../proc/", 1, "proc.mount"); } +TEST(unit_name_mangle_with_suffix_strict) { + /* Matching suffix should succeed */ + test_unit_name_mangle_with_suffix_strict_one("foo.service", ".service", 0, "foo.service"); + test_unit_name_mangle_with_suffix_strict_one("foo.mount", ".mount", 0, "foo.mount"); + test_unit_name_mangle_with_suffix_strict_one("/home", ".mount", 1, "home.mount"); + test_unit_name_mangle_with_suffix_strict_one("/dev/sda", ".device", 1, "dev-sda.device"); + test_unit_name_mangle_with_suffix_strict_one("foo@bar.service", ".service", 0, "foo@bar.service"); + test_unit_name_mangle_with_suffix_strict_one("a.timer.service", ".service", 0, "a.timer.service"); + + /* Mismatched suffix should fail with -EINVAL */ + test_unit_name_mangle_with_suffix_strict_one("foo.mount", ".service", -EINVAL, NULL); + test_unit_name_mangle_with_suffix_strict_one("foo.service", ".scope", -EINVAL, NULL); + test_unit_name_mangle_with_suffix_strict_one("/home", ".service", -EINVAL, NULL); + test_unit_name_mangle_with_suffix_strict_one("/dev/sda", ".service", -EINVAL, NULL); + test_unit_name_mangle_with_suffix_strict_one("foo.automount", ".mount", -EINVAL, NULL); + test_unit_name_mangle_with_suffix_strict_one("foo.mount", ".automount", -EINVAL, NULL); + test_unit_name_mangle_with_suffix_strict_one("foo@bar.timer", ".service", -EINVAL, NULL); + + /* Non-path names that need mangling should get the requested suffix and thus pass */ + test_unit_name_mangle_with_suffix_strict_one("foo", ".service", 1, "foo.service"); +} + TEST_RET(unit_printf, .sd_booted = true) { _cleanup_free_ char *architecture, *os_image_version, *boot_id = NULL, *os_build_id, diff --git a/src/test/test-user-record-nss.c b/src/test/test-user-record-nss.c new file mode 100644 index 0000000000000..22a055f57cf26 --- /dev/null +++ b/src/test/test-user-record-nss.c @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-json.h" +#include "strv.h" +#include "tests.h" +#include "user-record.h" +#include "user-record-nss.h" + +TEST(nss_user_alias) { + static const struct passwd pwd = { + .pw_name = (char*) "testuser", + .pw_uid = 1000, + .pw_gid = 1000, + .pw_gecos = (char*) "testuser", + .pw_dir = (char*) "/home/testuser", + .pw_shell = (char*) "/bin/bash", + }; + _cleanup_(user_record_unrefp) UserRecord *u = NULL; + sd_json_variant *aliases; + + ASSERT_OK(nss_passwd_to_user_record(&pwd, NULL, "testuser@example.test", &u)); + ASSERT_TRUE(user_record_matches_user_name(u, "testuser@example.test")); + ASSERT_TRUE(strv_contains(u->aliases, "testuser@example.test")); + + aliases = ASSERT_NOT_NULL(sd_json_variant_by_key(u->json, "aliases")); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_index(aliases, 0)), "testuser@example.test"); +} + +TEST(nss_user_invalid_alias) { + static const struct passwd pwd = { + .pw_name = (char*) "testuser", + .pw_uid = 1000, + .pw_gid = 1000, + .pw_gecos = (char*) "testuser", + .pw_dir = (char*) "/home/testuser", + .pw_shell = (char*) "/bin/bash", + }; + _cleanup_(user_record_unrefp) UserRecord *u = NULL; + + ASSERT_OK(nss_passwd_to_user_record(&pwd, NULL, "testuser/bad", &u)); + ASSERT_FALSE(user_record_matches_user_name(u, "testuser/bad")); + ASSERT_TRUE(strv_isempty(u->aliases)); + ASSERT_NULL(sd_json_variant_by_key(u->json, "aliases")); +} + +TEST(nss_user_alias_realm) { + static const struct passwd pwd = { + .pw_name = (char*) "testuser", + .pw_uid = 1000, + .pw_gid = 1000, + .pw_gecos = (char*) "testuser", + .pw_dir = (char*) "/home/testuser", + .pw_shell = (char*) "/bin/bash", + }; + _cleanup_(user_record_unrefp) UserRecord *u = NULL; + + ASSERT_OK(nss_passwd_to_user_record(&pwd, NULL, "testuser-short", &u)); + u->realm = ASSERT_NOT_NULL(strdup("example.test")); + + ASSERT_TRUE(user_record_matches_user_name(u, "testuser-short@example.test")); +} + +TEST(nss_user_alias_same_as_canonical_noop) { + static const struct passwd pwd = { + .pw_name = (char*) "testuser", + .pw_uid = 1000, + .pw_gid = 1000, + .pw_gecos = (char*) "testuser", + .pw_dir = (char*) "/home/testuser", + .pw_shell = (char*) "/bin/bash", + }; + _cleanup_(user_record_unrefp) UserRecord *u = NULL; + + ASSERT_OK(nss_passwd_to_user_record(&pwd, NULL, "testuser", &u)); + ASSERT_TRUE(strv_isempty(u->aliases)); + ASSERT_NULL(sd_json_variant_by_key(u->json, "aliases")); +} + +TEST(nss_user_null_alias_noop) { + static const struct passwd pwd = { + .pw_name = (char*) "testuser", + .pw_uid = 1000, + .pw_gid = 1000, + .pw_gecos = (char*) "testuser", + .pw_dir = (char*) "/home/testuser", + .pw_shell = (char*) "/bin/bash", + }; + _cleanup_(user_record_unrefp) UserRecord *u = NULL; + + ASSERT_OK(nss_passwd_to_user_record(&pwd, NULL, NULL, &u)); + ASSERT_TRUE(strv_isempty(u->aliases)); + ASSERT_NULL(sd_json_variant_by_key(u->json, "aliases")); +} + +TEST(nss_user_alias_fuzzy_match) { + static const struct passwd pwd = { + .pw_name = (char*) "canonical", + .pw_uid = 1000, + .pw_gid = 1000, + .pw_gecos = (char*) "canonical", + .pw_dir = (char*) "/home/canonical", + .pw_shell = (char*) "/bin/bash", + }; + _cleanup_(user_record_unrefp) UserRecord *u = NULL; + UserDBMatch match = USERDB_MATCH_NULL; + + ASSERT_OK(nss_passwd_to_user_record(&pwd, NULL, "external-login", &u)); + match.fuzzy_names = ASSERT_NOT_NULL(strv_new("ternal")); + + ASSERT_TRUE(user_record_match(u, &match)); + userdb_match_done(&match); +} + +DEFINE_TEST_MAIN(LOG_INFO); 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-user-util.c b/src/test/test-user-util.c index 47bd01f9d0f80..53ad9422f3406 100644 --- a/src/test/test-user-util.c +++ b/src/test/test-user-util.c @@ -287,8 +287,7 @@ TEST(valid_shell) { } static void test_get_user_creds_one(const char *id, const char *name, uid_t uid, gid_t gid, const char *home, const char *shell) { - const char *rhome = NULL; - const char *rshell = NULL; + _cleanup_free_ char *rname = NULL, *rhome = NULL, *rshell = NULL; uid_t ruid = UID_INVALID; gid_t rgid = GID_INVALID; int r; @@ -296,15 +295,15 @@ static void test_get_user_creds_one(const char *id, const char *name, uid_t uid, log_info("/* %s(\"%s\", \"%s\", "UID_FMT", "GID_FMT", \"%s\", \"%s\") */", __func__, id, name, uid, gid, home, shell); - r = get_user_creds(&id, &ruid, &rgid, &rhome, &rshell, 0); + r = get_user_creds(id, /* flags= */ 0, &rname, &ruid, &rgid, &rhome, &rshell); log_info_errno(r, "got \"%s\", "UID_FMT", "GID_FMT", \"%s\", \"%s\": %m", - id, ruid, rgid, strnull(rhome), strnull(rshell)); + strnull(rname), ruid, rgid, strnull(rhome), strnull(rshell)); if (!synthesize_nobody() && streq(name, NOBODY_USER_NAME)) { log_info("(skipping detailed tests because nobody is not synthesized)"); return; } ASSERT_OK(r); - ASSERT_STREQ(id, name); + ASSERT_STREQ(rname, name); ASSERT_EQ(ruid, uid); ASSERT_EQ(rgid, gid); ASSERT_TRUE(path_equal(rhome, home)); @@ -317,21 +316,22 @@ TEST(get_user_creds) { test_get_user_creds_one("65534", NOBODY_USER_NAME, UID_NOBODY, GID_NOBODY, "/", NOLOGIN); } -static void test_get_group_creds_one(const char *id, const char *name, gid_t gid) { +static void test_get_group_creds_one(const char *id, const char *expected_name, gid_t expected_gid) { gid_t rgid = GID_INVALID; + _cleanup_free_ char *rnam = NULL; int r; - log_info("/* %s(\"%s\", \"%s\", "GID_FMT") */", __func__, id, name, gid); + log_info("/* %s(\"%s\", \"%s\", "GID_FMT") */", __func__, id, expected_name, expected_gid); - r = get_group_creds(&id, &rgid, 0); - log_info_errno(r, "got \"%s\", "GID_FMT": %m", id, rgid); - if (!synthesize_nobody() && streq(name, NOBODY_GROUP_NAME)) { + r = get_group_creds(id, /* flags= */ 0, &rnam, &rgid); + log_info_errno(r, "→ \"%s\", "GID_FMT": %m", strnull(rnam), rgid); + if (!synthesize_nobody() && streq(expected_name, NOBODY_GROUP_NAME)) { log_info("(skipping detailed tests because nobody is not synthesized)"); return; } ASSERT_OK(r); - ASSERT_STREQ(id, name); - ASSERT_EQ(rgid, gid); + ASSERT_STREQ(rnam, expected_name); + ASSERT_EQ(rgid, expected_gid); } TEST(get_group_creds) { diff --git a/src/test/test-utf8.c b/src/test/test-utf8.c index ed7625061ac22..9ab62976814ff 100644 --- a/src/test/test-utf8.c +++ b/src/test/test-utf8.c @@ -167,6 +167,15 @@ TEST(utf8_escape_non_printable_full) { } } +TEST(utf8_escape_non_printable_full_ellipsis) { + _cleanup_free_ char *p = NULL; + + /* Every byte escapes to four columns, so the escaped output fills strlen*4 bytes and the + * forced ellipsis used to be written past the end of the buffer. */ + assert_se(p = utf8_escape_non_printable_full("\001\001\001", 80, /* force_ellipsis= */ true)); + ASSERT_STREQ(p, "\\x01\\x01\\x01…"); +} + TEST(utf16_to_utf8) { const char16_t utf16[] = { htole16('a'), htole16(0xd800), htole16('b'), htole16(0xdc00), htole16('c'), htole16(0xd801), htole16(0xdc37) }; static const char utf8[] = { 'a', 'b', 'c', 0xf0, 0x90, 0x90, 0xb7 }; diff --git a/src/test/test-varlink-idl-job.c b/src/test/test-varlink-idl-job.c new file mode 100644 index 0000000000000..23c75d573b50f --- /dev/null +++ b/src/test/test-varlink-idl-job.c @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "job.h" +#include "tests.h" +#include "test-varlink-idl-util.h" +#include "varlink-io.systemd.Job.h" + +TEST(job_enums_idl) { + TEST_IDL_ENUM(JobType, job_type, vl_type_JobType); + TEST_IDL_ENUM(JobState, job_state, vl_type_JobState); + TEST_IDL_ENUM(JobResult, job_result, vl_type_JobResult); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-varlink-idl-login.c b/src/test/test-varlink-idl-login.c new file mode 100644 index 0000000000000..fb8ef81363748 --- /dev/null +++ b/src/test/test-varlink-idl-login.c @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "logind-inhibit.h" +#include "logind-session.h" +#include "tests.h" +#include "test-varlink-idl-util.h" +#include "varlink-io.systemd.Login.h" + +TEST(login_enums_idl) { + TEST_IDL_ENUM(SessionType, session_type, vl_type_SessionType); + /* SessionClass has SESSION_NONE ("none") as an internal sentinel that is intentionally not exposed + * via Varlink. Only validate the IDL→C direction. */ + TEST_IDL_ENUM_FROM_STRING(SessionClass, session_class, vl_type_SessionClass); + TEST_IDL_ENUM(InhibitMode, inhibit_mode, vl_type_InhibitMode); + /* InhibitWhat is a bit-flag enum: inhibit_what_to_string() accepts a bitmask and + * the index does not correspond to a sequential integer, so the TEST_IDL_ENUM + * round-trip macro doesn't apply. The flag→string mapping is exercised through + * ListInhibitors integration tests in TEST-35-LOGIN.sh. */ +} + +DEFINE_TEST_MAIN(LOG_DEBUG); 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..51d5b9396f3f5 --- /dev/null +++ b/src/test/test-varlink-idl-unit.c @@ -0,0 +1,115 @@ +/* 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 "path.h" +#include "process-util.h" +#include "scope.h" +#include "service.h" +#include "socket.h" +#include "swap.h" +#include "tests.h" +#include "test-varlink-idl-util.h" +#include "timer.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(ExecMemoryTHP, exec_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); + + /* ServiceContext enums */ + TEST_IDL_ENUM(ServiceType, service_type, vl_type_ServiceType); + TEST_IDL_ENUM(ServiceExitType, service_exit_type, vl_type_ServiceExitType); + TEST_IDL_ENUM(ServiceRestart, service_restart, vl_type_ServiceRestart); + TEST_IDL_ENUM(ServiceRestartMode, service_restart_mode, vl_type_ServiceRestartMode); + TEST_IDL_ENUM(ServiceTimeoutFailureMode, service_timeout_failure_mode, vl_type_ServiceTimeoutFailureMode); + + /* ServiceRuntime enums */ + TEST_IDL_ENUM(NotifyAccess, notify_access, vl_type_NotifyAccess); + TEST_IDL_ENUM(ServiceResult, service_result, vl_type_ServiceResult); + + /* PathContext enums */ + TEST_IDL_ENUM(PathType, path_type, vl_type_PathType); + + /* PathRuntime enums */ + TEST_IDL_ENUM(PathResult, path_result, vl_type_PathResult); + + /* ScopeRuntime enums */ + TEST_IDL_ENUM(ScopeResult, scope_result, vl_type_ScopeResult); + + /* SocketContext enums */ + TEST_IDL_ENUM(SocketAddressBindIPv6Only, socket_address_bind_ipv6_only, vl_type_SocketBindIPv6Only); + TEST_IDL_ENUM(SocketTimestamping, socket_timestamping, vl_type_SocketTimestamping); + TEST_IDL_ENUM(SocketDeferTrigger, socket_defer_trigger, vl_type_SocketDeferTrigger); + + /* SocketRuntime enums */ + TEST_IDL_ENUM(SocketResult, socket_result, vl_type_SocketResult); + + /* SwapRuntime enums */ + TEST_IDL_ENUM(SwapResult, swap_result, vl_type_SwapResult); + + /* TimerContext enums */ + for (TimerBase b = 0; b < _TIMER_BASE_MAX; b++) { + _cleanup_free_ char *s = timer_base_to_usec_string(b); + assert_se(s); + test_enum_to_string_name(s, &vl_type_TimerBase); + } + + /* TimerRuntime enums */ + TEST_IDL_ENUM(TimerResult, timer_result, vl_type_TimerResult); + + /* 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-verbs.c b/src/test/test-verbs.c index a28fc9b55b274..56062b0bd6bdc 100644 --- a/src/test/test-verbs.c +++ b/src/test/test-verbs.c @@ -1,29 +1,28 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "strv.h" #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; } #define test_dispatch_one(argv, verbs, expected) \ - optind = 0; \ - assert_se(dispatch_verb(strv_length(argv), argv, verbs, NULL) == expected); + assert_se(_dispatch_verb(argv, verbs, verbs + ELEMENTSOF(verbs) - 1, NULL) == expected); 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 +43,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 +65,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 +81,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/test/test-vpick.c b/src/test/test-vpick.c index 400380b5754f3..6fea29e594ca6 100644 --- a/src/test/test-vpick.c +++ b/src/test/test-vpick.c @@ -48,7 +48,7 @@ TEST(path_pick) { }; if (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64)) { - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); ASSERT_TRUE(S_ISREG(result.st.st_mode)); ASSERT_STREQ(result.version, "99"); ASSERT_EQ(result.architecture, ARCHITECTURE_X86); @@ -58,7 +58,7 @@ TEST(path_pick) { } filter.architecture = ARCHITECTURE_X86_64; - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); ASSERT_TRUE(S_ISREG(result.st.st_mode)); ASSERT_STREQ(result.version, "55"); ASSERT_EQ(result.architecture, ARCHITECTURE_X86_64); @@ -66,7 +66,7 @@ TEST(path_pick) { pick_result_done(&result); filter.architecture = ARCHITECTURE_IA64; - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); ASSERT_TRUE(S_ISREG(result.st.st_mode)); ASSERT_STREQ(result.version, "5"); ASSERT_EQ(result.architecture, ARCHITECTURE_IA64); @@ -75,7 +75,7 @@ TEST(path_pick) { filter.architecture = _ARCHITECTURE_INVALID; filter.version = "5"; - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); ASSERT_TRUE(S_ISREG(result.st.st_mode)); ASSERT_STREQ(result.version, "5"); if (native_architecture() != ARCHITECTURE_IA64) { @@ -85,7 +85,7 @@ TEST(path_pick) { pick_result_done(&result); filter.architecture = ARCHITECTURE_IA64; - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); ASSERT_TRUE(S_ISREG(result.st.st_mode)); ASSERT_STREQ(result.version, "5"); ASSERT_EQ(result.architecture, ARCHITECTURE_IA64); @@ -93,7 +93,7 @@ TEST(path_pick) { pick_result_done(&result); filter.architecture = ARCHITECTURE_CRIS; - ASSERT_OK_ZERO(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); + ASSERT_OK_ZERO(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); ASSERT_EQ(result.st.st_mode, MODE_INVALID); ASSERT_NULL(result.version); ASSERT_LT(result.architecture, 0); @@ -104,7 +104,7 @@ TEST(path_pick) { filter.architecture = _ARCHITECTURE_INVALID; filter.version = NULL; if (IN_SET(native_architecture(), ARCHITECTURE_X86_64, ARCHITECTURE_X86)) { - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); ASSERT_TRUE(S_ISREG(result.st.st_mode)); ASSERT_STREQ(result.version, "55"); @@ -123,7 +123,7 @@ TEST(path_pick) { pp = ASSERT_NOT_NULL(path_join(p, "foo.v/foo___.raw")); if (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64)) { - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); ASSERT_TRUE(S_ISREG(result.st.st_mode)); ASSERT_STREQ(result.version, "55"); ASSERT_EQ(result.architecture, native_architecture()); @@ -137,10 +137,10 @@ TEST(path_pick) { pp = ASSERT_NOT_NULL(path_join(p, "foo.v/foo_5.raw")); filter.type_mask = UINT32_C(1) << DT_DIR; - ASSERT_OK_ZERO(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); + ASSERT_OK_ZERO(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); filter.type_mask = UINT32_C(1) << DT_REG; - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); ASSERT_TRUE(S_ISREG(result.st.st_mode)); ASSERT_NULL(result.version); ASSERT_EQ(result.architecture, _ARCHITECTURE_INVALID); @@ -153,7 +153,7 @@ TEST(path_pick) { filter.architecture = ARCHITECTURE_S390; filter.basename = "quux"; - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, &filter, /* n_filters= */ 1, PICK_ARCHITECTURE|PICK_TRIES, &result)); ASSERT_TRUE(S_ISREG(result.st.st_mode)); ASSERT_STREQ(result.version, "2"); ASSERT_EQ(result.tries_left, 4U); @@ -209,7 +209,7 @@ TEST(pick_filter_image_any) { _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; /* Test pick_filter_image_any: should pick the highest version, which is the directory test_5 */ - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result)); ASSERT_TRUE(S_ISDIR(result.st.st_mode)); ASSERT_STREQ(result.version, "5"); ASSERT_TRUE(endswith(result.path, "/test_5")); @@ -219,14 +219,14 @@ TEST(pick_filter_image_any) { ASSERT_OK(unlinkat(sub_dfd, "test_4", AT_REMOVEDIR)); ASSERT_OK(unlinkat(sub_dfd, "test_5", AT_REMOVEDIR)); - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result)); ASSERT_TRUE(S_ISREG(result.st.st_mode)); ASSERT_STREQ(result.version, "3"); ASSERT_TRUE(endswith(result.path, "/test_3.raw")); pick_result_done(&result); /* Verify that pick_filter_image_raw only matches .raw files */ - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), PICK_ARCHITECTURE, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), PICK_ARCHITECTURE, &result)); ASSERT_TRUE(S_ISREG(result.st.st_mode)); ASSERT_STREQ(result.version, "3"); ASSERT_TRUE(endswith(result.path, "/test_3.raw")); @@ -239,12 +239,12 @@ TEST(pick_filter_image_any) { ASSERT_OK(unlinkat(sub_dfd, "test_3.raw", 0)); /* Now only test_10.txt, test_11.img, and test_12 remain - none should match */ - ASSERT_OK_ZERO(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result)); + ASSERT_OK_ZERO(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result)); /* But if we add a directory, it should be picked */ ASSERT_OK(mkdirat(sub_dfd, "test_6", 0755)); - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result)); ASSERT_TRUE(S_ISDIR(result.st.st_mode)); ASSERT_STREQ(result.version, "6"); ASSERT_TRUE(endswith(result.path, "/test_6")); @@ -263,7 +263,7 @@ TEST(pick_filter_image_any) { pick_result_done(&result); - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE, &result)); ASSERT_TRUE(S_ISDIR(result.st.st_mode)); ASSERT_STREQ(result.version, "2"); ASSERT_TRUE(endswith(result.path, "/myimage_2")); @@ -273,7 +273,7 @@ TEST(pick_filter_image_any) { ASSERT_OK(unlinkat(sub_dfd, "myimage_1", AT_REMOVEDIR)); ASSERT_OK(unlinkat(sub_dfd, "myimage_2", AT_REMOVEDIR)); - ASSERT_OK_ZERO(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE, &result)); + ASSERT_OK_ZERO(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE, &result)); } TEST(path_pick_resolve) { @@ -294,26 +294,26 @@ TEST(path_pick_resolve) { _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; /* Test without PICK_RESOLVE - should return the symlink path */ - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE, &result)); ASSERT_STREQ(result.version, "2"); ASSERT_TRUE(endswith(result.path, "/resolve_2.raw")); pick_result_done(&result); /* Test with PICK_RESOLVE - should return the resolved (target) path */ - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE|PICK_RESOLVE, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_any, ELEMENTSOF(pick_filter_image_any), PICK_ARCHITECTURE|PICK_RESOLVE, &result)); ASSERT_STREQ(result.version, "2"); ASSERT_TRUE(endswith(result.path, "/target_file.raw")); pick_result_done(&result); /* Test pick_filter_image_dir without PICK_RESOLVE */ - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE, &result)); ASSERT_TRUE(S_ISDIR(result.st.st_mode)); ASSERT_STREQ(result.version, "1"); ASSERT_TRUE(endswith(result.path, "/resolve_1")); pick_result_done(&result); /* Test pick_filter_image_dir with PICK_RESOLVE */ - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE|PICK_RESOLVE, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_dir, ELEMENTSOF(pick_filter_image_dir), PICK_ARCHITECTURE|PICK_RESOLVE, &result)); ASSERT_TRUE(S_ISDIR(result.st.st_mode)); ASSERT_STREQ(result.version, "1"); ASSERT_TRUE(endswith(result.path, "/target_dir")); @@ -323,12 +323,12 @@ TEST(path_pick_resolve) { ASSERT_OK(symlinkat("target_file.raw", dfd, "intermediate_link.raw")); ASSERT_OK(symlinkat("../intermediate_link.raw", sub_dfd, "resolve_3.raw")); - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), PICK_ARCHITECTURE, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), PICK_ARCHITECTURE, &result)); ASSERT_STREQ(result.version, "3"); ASSERT_TRUE(endswith(result.path, "/resolve_3.raw")); pick_result_done(&result); - ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), PICK_ARCHITECTURE|PICK_RESOLVE, &result)); + ASSERT_OK_POSITIVE(path_pick(NULL, AT_FDCWD, AT_FDCWD, pp, pick_filter_image_raw, ELEMENTSOF(pick_filter_image_raw), PICK_ARCHITECTURE|PICK_RESOLVE, &result)); ASSERT_STREQ(result.version, "3"); /* The chain should be fully resolved to target_file.raw */ ASSERT_TRUE(endswith(result.path, "/target_file.raw")); diff --git a/src/test/test-watch-pid.c b/src/test/test-watch-pid.c index 0a325dfb0880a..59f043f49ca33 100644 --- a/src/test/test-watch-pid.c +++ b/src/test/test-watch-pid.c @@ -21,7 +21,7 @@ TEST(watch_pid) { ASSERT_NOT_NULL(runtime_dir = setup_fake_runtime_dir()); ASSERT_OK(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m)); - ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL, NULL)); ASSERT_NOT_NULL(a = unit_new(m, sizeof(Service))); ASSERT_OK(unit_add_name(a, "a.service")); diff --git a/src/test/test-xattr-util.c b/src/test/test-xattr-util.c index eeb0d8a8afc80..4d39a0a1f10a6 100644 --- a/src/test/test-xattr-util.c +++ b/src/test/test-xattr-util.c @@ -46,6 +46,16 @@ TEST(getxattr_at_malloc) { r = getxattr_at_malloc(fd, "usr", "user.idontexist", 0, &value, /* ret_size= */ NULL); ASSERT_TRUE(ERRNO_IS_NEG_XATTR_ABSENT(r)); + /* A non-existent path component must also be treated as xattr-absent. The kernel + * returns -ENOENT in this case, which is inherited from stat(2)'s error list + * (see ERRORS in getxattr(2)). Without this, callers that semantically just want + * to know "is the xattr there?" end up logging spurious errors for paths that + * are simply gone — e.g. journald reading a unit's cgroup xattr after the + * cgroup directory has been torn down. */ + r = getxattr_at_malloc(fd, "this-path-does-not-exist-XXXXXX", "user.idontexist", + 0, &value, /* ret_size= */ NULL); + ASSERT_TRUE(ERRNO_IS_NEG_XATTR_ABSENT(r)); + safe_close(fd); ASSERT_OK_ERRNO(fd = open(x, O_PATH|O_CLOEXEC)); ASSERT_OK(getxattr_at_malloc(fd, NULL, "user.foo", 0, &value, /* ret_size= */ NULL)); diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index 04730de114743..2c9ab9b6fd777 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_DEFAULT_NOARG(verb_status, "status", "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,131 @@ 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 opts = { argc, argv }; - case 'h': + FOREACH_OPTION_OR_RETURN(c, &opts) + 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 = opts.arg; break; - case ARG_ADJUST_SYSTEM_CLOCK: - arg_adjust_system_clock = true; + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.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, opts.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 (opts.opt->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(&opts); 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 +1028,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(args, bus); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c index 5ba8644334da5..5d20e6ca0616f 100644 --- a/src/timedate/timedated.c +++ b/src/timedate/timedated.c @@ -693,6 +693,7 @@ static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error * /* good_user= */ UID_INVALID, interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -768,6 +769,7 @@ static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error /* good_user= */ UID_INVALID, interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -904,6 +906,7 @@ static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *erro /* good_user= */ UID_INVALID, interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -970,6 +973,7 @@ static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error /* good_user= */ UID_INVALID, interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, + /* ret_admin= */ NULL, error); if (r < 0) return r; @@ -1109,7 +1113,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/meson.build b/src/timesync/meson.build index b30724577202a..c42fcd3e70963 100644 --- a/src/timesync/meson.build +++ b/src/timesync/meson.build @@ -35,10 +35,7 @@ executables += [ 'sources' : timesyncd_sources, 'extract' : timesyncd_extract_sources, 'link_with' : timesyncd_link_with, - 'dependencies' : [ - libm, - threads, - ], + 'dependencies' : libm, }, libexec_template + { 'name' : 'systemd-time-wait-sync', diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c index 79a9a629c1410..b0f22516dfe33 100644 --- a/src/timesync/timesyncd-manager.c +++ b/src/timesync/timesyncd-manager.c @@ -244,7 +244,7 @@ static int manager_adjust_clock(Manager *m, double offset, int leap_sec) { /* For small deltas, tell the kernel to gradually adjust the system clock to the NTP time, larger * deltas are just directly set. */ - if (fabs(offset) < NTP_MAX_ADJUST) { + if (ABS(offset) < NTP_MAX_ADJUST) { tmx = (struct timex) { .modes = ADJ_STATUS | ADJ_NANO | ADJ_OFFSET | ADJ_TIMECONST | ADJ_MAXERROR | ADJ_ESTERROR, .status = STA_PLL, @@ -346,7 +346,7 @@ static bool manager_sample_spike_detection(Manager *m, double offset, double del return false; /* always accept offset if we are farther off than the round-trip delay */ - if (fabs(offset) > delay) + if (ABS(offset) > delay) return false; /* we need a few samples before looking at them */ @@ -354,11 +354,11 @@ static bool manager_sample_spike_detection(Manager *m, double offset, double del return false; /* do not accept anything worse than the maximum possible error of the best sample */ - if (fabs(offset) > m->samples[idx_min].delay) + if (ABS(offset) > m->samples[idx_min].delay) return true; /* compare the difference between the current offset to the previous offset and jitter */ - return fabs(offset - m->samples[idx_cur].offset) > 3 * jitter; + return ABS(offset - m->samples[idx_cur].offset) > 3 * jitter; } static void manager_adjust_poll(Manager *m, double offset, bool spike) { @@ -371,20 +371,20 @@ static void manager_adjust_poll(Manager *m, double offset, bool spike) { } /* set to minimal poll interval */ - if (!spike && fabs(offset) > NTP_ACCURACY_SEC) { + if (!spike && ABS(offset) > NTP_ACCURACY_SEC) { m->poll_interval_usec = m->poll_interval_min_usec; return; } /* increase polling interval */ - if (fabs(offset) < NTP_ACCURACY_SEC * 0.25) { + if (ABS(offset) < NTP_ACCURACY_SEC * 0.25) { if (m->poll_interval_usec < m->poll_interval_max_usec) m->poll_interval_usec *= 2; return; } /* decrease polling interval */ - if (spike || fabs(offset) > NTP_ACCURACY_SEC * 0.75) { + if (spike || ABS(offset) > NTP_ACCURACY_SEC * 0.75) { if (m->poll_interval_usec > m->poll_interval_min_usec) m->poll_interval_usec /= 2; return; @@ -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..f357ef8865d8d 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,21 @@ 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 opts = { argc, argv }; - case '?': - return -EINVAL; + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { - default: - assert_not_reached(); + OPTION('r', "root", "PATH", "Operate on an alternate filesystem root"): + arg_root = opts.arg; + break; } + *ret_args = option_parser_get_args(&opts); return 0; } @@ -71,15 +62,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..738f09a9633ab 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -15,6 +14,7 @@ #include "bitfield.h" #include "btrfs-util.h" #include "build.h" +#include "capability-list.h" #include "capability-util.h" #include "chase.h" #include "chattr-util.h" @@ -31,6 +31,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" @@ -41,10 +42,11 @@ #include "log.h" #include "loop-util.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #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" @@ -103,6 +105,8 @@ typedef enum ItemType { RECURSIVE_SET_XATTR = 'T', SET_ACL = 'a', RECURSIVE_SET_ACL = 'A', + SET_FCAPS = 'k', + RECURSIVE_SET_FCAPS = 'K', SET_ATTRIBUTE = 'h', RECURSIVE_SET_ATTRIBUTE = 'H', IGNORE_PATH = 'x', @@ -125,6 +129,18 @@ typedef enum AgeBy { AGE_BY_DEFAULT_DIR = AGE_BY_ATIME | AGE_BY_BTIME | AGE_BY_MTIME, } AgeBy; +typedef struct FCapsPatch { + uint64_t mask; + uint64_t set; +} FCapsPatch; + +typedef struct FCapsUpdate { + uid_t rootuid; + FCapsPatch inheritable; + FCapsPatch permitted; + FCapsPatch effective; +} FCapsUpdate; + typedef struct Item { ItemType type; @@ -138,6 +154,7 @@ typedef struct Item { acl_t acl_access_exec; acl_t acl_default; #endif + FCapsUpdate fcaps; uid_t uid; gid_t gid; mode_t mode; @@ -170,6 +187,8 @@ typedef struct Item { bool ignore_if_target_missing:1; + bool fcaps_set:1; + OperationMask done; } Item; @@ -186,6 +205,7 @@ typedef enum DirectoryType { DIRECTORY_STATE, DIRECTORY_CACHE, DIRECTORY_LOGS, + DIRECTORY_SHARED, _DIRECTORY_TYPE_MAX, } DirectoryType; @@ -199,6 +219,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 +229,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 @@ -273,6 +294,7 @@ static int specifier_directory( [DIRECTORY_STATE] = { SD_PATH_SYSTEM_STATE_PRIVATE }, [DIRECTORY_CACHE] = { SD_PATH_SYSTEM_STATE_CACHE }, [DIRECTORY_LOGS] = { SD_PATH_SYSTEM_STATE_LOGS }, + [DIRECTORY_SHARED] = { SD_PATH_SYSTEM_SHARED }, }; static const struct table_entry paths_user[] = { @@ -280,6 +302,7 @@ static int specifier_directory( [DIRECTORY_STATE] = { SD_PATH_USER_STATE_PRIVATE }, [DIRECTORY_CACHE] = { SD_PATH_USER_STATE_CACHE }, [DIRECTORY_LOGS] = { SD_PATH_USER_STATE_PRIVATE, "log" }, + [DIRECTORY_SHARED] = { SD_PATH_USER_SHARED }, }; const struct table_entry *paths; @@ -287,6 +310,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; @@ -405,6 +429,8 @@ static bool needs_glob(ItemType t) { RECURSIVE_SET_XATTR, SET_ACL, RECURSIVE_SET_ACL, + SET_FCAPS, + RECURSIVE_SET_FCAPS, SET_ATTRIBUTE, RECURSIVE_SET_ATTRIBUTE, IGNORE_PATH, @@ -586,9 +612,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 +716,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 +735,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 +892,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 +1270,7 @@ static int parse_acl_cond_exec( assert(cond_exec); assert(ret); - r = dlopen_libacl(); + r = DLOPEN_LIBACL(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); if (r < 0) return r; @@ -1351,7 +1389,7 @@ static int path_set_acl( assert(c); - r = dlopen_libacl(); + r = DLOPEN_LIBACL(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); if (r < 0) return r; @@ -1384,16 +1422,20 @@ static int path_set_acl( strna(t), pretty); if (!arg_dry_run && - sym_acl_set_file(path, type, dup) < 0) { - if (ERRNO_IS_NOT_SUPPORTED(errno)) + (r = RET_NERRNO(sym_acl_set_file(path, type, dup))) < 0) { + if (ERRNO_IS_NOT_SUPPORTED(r)) /* No error if filesystem doesn't support ACLs. Return negative. */ - return -errno; - else - /* Return positive to indicate we already warned */ - return -log_error_errno(errno, - "Setting %s ACL \"%s\" on %s failed: %m", - type == ACL_TYPE_ACCESS ? "access" : "default", - strna(t), pretty); + return r; + if (r == -EINVAL && running_in_chroot() > 0) + return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Setting %s ACL \"%s\" on %s failed. A chroot environment was detected, ignoring.", + type == ACL_TYPE_ACCESS ? "access" : "default", + strna(t), pretty); + /* Return positive to indicate we already warned */ + return -log_error_errno(r, + "Setting %s ACL \"%s\" on %s failed: %m", + type == ACL_TYPE_ACCESS ? "access" : "default", + strna(t), pretty); } return 0; } @@ -1487,6 +1529,271 @@ static int path_set_acls( return r; } +static int capability_vfs_from_string(const char *s, FCapsUpdate *ret) { + FCapsUpdate set = { + .rootuid = UID_INVALID, + }; + + assert(s); + assert(ret); + + for (const char *p = s;;) { + _cleanup_free_ char *word = NULL, *keys = NULL; + char *value, sep; + int r; + + r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX); + if (r < 0) + return log_debug_errno(r, "Failed to split words from '%s': %m", p); + if (r == 0) + break; + + value = strpbrk(word, "=+-"); + if (!value) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse key/value '%s': %m", word); + keys = strndup(word, value - word); + if (!keys) + return log_oom(); + sep = *value; + value++; + + if (sep == '=' && streq(keys, "rootuid")) { + r = parse_uid(value, &set.rootuid); + if (r < 0) + return log_debug_errno(r, "Failed to parse rootuid value '%s': %m", value); + } else { + uint64_t caps = 0; + + if (!in_charset(value, "eip")) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse value '%s': %m", value); + + if (STR_IN_SET(keys, "all", "")) + caps = all_capabilities(); + else + for (const char *remaining_keys = keys; remaining_keys && *remaining_keys;) { + _cleanup_free_ char *key = NULL; + + r = extract_first_word(&remaining_keys, &key, ",", /* flags= */0); + if (r < 0) + return log_debug_errno(r, "Failed to parse capability list '%s': %m", keys); + if (r == 0) + break; + if (streq(key, "all")) + caps = all_capabilities(); + else { + r = capability_from_name(key); + if (r < 0) + return log_debug_errno(r, "Failed to parse capability '%s': %m", key); + caps |= UINT64_C(1) << r; + } + } + + if (sep == '=') { + set.permitted.mask |= caps; + set.inheritable.mask |= caps; + set.effective.mask |= caps; + } + if (IN_SET(sep, '=', '+')) { + if (strchr(value, 'p')) + set.permitted.set |= caps; + if (strchr(value, 'i')) + set.inheritable.set |= caps; + if (strchr(value, 'e')) + set.effective.set |= caps; + } else { + if (strchr(value, 'p')) { + set.permitted.mask |= caps; + set.permitted.set &= ~caps; + } + if (strchr(value, 'i')) { + set.inheritable.mask |= caps; + set.inheritable.set &= ~caps; + } + if (strchr(value, 'e')) { + set.effective.mask |= caps; + set.effective.set &= ~caps; + } + } + } + } + + *ret = set; + + return 0; +} + +static size_t cap_data_size(uint32_t revision) { + switch (revision) { + case VFS_CAP_REVISION_1: + return XATTR_CAPS_SZ_1; + case VFS_CAP_REVISION_2: + return XATTR_CAPS_SZ_2; + case VFS_CAP_REVISION_3: + return XATTR_CAPS_SZ_3; + default: + return SIZE_MAX; + } +} + +static bool inode_type_can_fcaps(mode_t mode) { + return S_ISREG(mode); +} + +static int apply_fcaps(int fd, const char *path, bool append, const FCapsUpdate *set) { + struct vfs_ns_cap_data val = { + .magic_etc = htole32(VFS_CAP_REVISION), + }; + le32_t effective[VFS_CAP_U32] = {}; + struct stat st; + int r; + + assert(fd >= 0); + assert(path); + assert(set); + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat(%s): %m", path); + + if (hardlink_vulnerable(&st)) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "Refusing to set file capabilities on hardlinked file %s while the fs.protected_hardlinks sysctl is turned off.", + path); + + if (!inode_type_can_fcaps(st.st_mode)) { + log_debug("Skipping file capabilities for '%s' (inode type does not support file capabilities).", path); + return 0; + } + + if (append) { + _cleanup_free_ char *xattr_data = NULL; + size_t xattr_data_len; + + r = fgetxattr_malloc(fd, "security.capability", &xattr_data, &xattr_data_len); + if (r == -ENODATA) + log_debug("No capabilities found for '%s'", path); + else if (r < 0) + return log_error_errno(r, "Failed to read capabilities of '%s': %m", path); + else { + _cleanup_free_ struct vfs_ns_cap_data *original = NULL; + + if (xattr_data_len < endoffsetof_field(struct vfs_ns_cap_data, magic_etc)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extended attributes for capabilities are too small"); + + original = realloc0(xattr_data, sizeof(struct vfs_ns_cap_data)); + if (!original) + return log_oom(); + xattr_data = NULL; + + size_t expected_size = cap_data_size(le32toh(original->magic_etc) & VFS_CAP_REVISION_MASK); + if (expected_size == SIZE_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown type of file capabilities"); + if (xattr_data_len != expected_size) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Size of file capabilities does not match its type"); + + if (FLAGS_SET(le32toh(original->magic_etc), VFS_CAP_FLAGS_EFFECTIVE)) + for (size_t n = 0; n < VFS_CAP_U32; n++) + effective[n] = original->data[n].permitted | original->data[n].inheritable; + + for (size_t n = 0; n < VFS_CAP_U32; n++) { + val.data[n].permitted = original->data[n].permitted; + val.data[n].inheritable = original->data[n].inheritable; + } + + val.rootid = original->rootid; + } + } + + for (size_t n = 0; n < VFS_CAP_U32; n++) { + size_t bit_shift = 32*n; + val.data[n].inheritable &= htole32(~(set->inheritable.mask >> bit_shift) & UINT32_C(0xffffffff)); + val.data[n].inheritable |= htole32((set->inheritable.set >> bit_shift) & UINT32_C(0xffffffff)); + val.data[n].permitted &= htole32(~(set->permitted.mask >> bit_shift) & UINT32_C(0xffffffff)); + val.data[n].permitted |= htole32((set->permitted.set >> bit_shift) & UINT32_C(0xffffffff)); + effective[n] &= htole32(~(set->effective.mask >> bit_shift) & UINT32_C(0xffffffff)); + effective[n] |= htole32((set->effective.set >> bit_shift) & UINT32_C(0xffffffff)); + if (effective[n] != 0) + val.magic_etc |= htole32(VFS_CAP_FLAGS_EFFECTIVE); + } + + if (FLAGS_SET(le32toh(val.magic_etc), VFS_CAP_FLAGS_EFFECTIVE)) + for (size_t n = 0; n < VFS_CAP_U32; n++) + if ((val.data[n].permitted | val.data[n].inheritable) != effective[n]) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Inconsistent effective bits"); + + if (set->rootuid != UID_INVALID) + val.rootid = htole32(set->rootuid); + + log_action("Would try to set", "Trying to set", + "%s capabilities on %s", path); + + if (!arg_dry_run) { + r = xsetxattr_full(fd, /* path= */ NULL, AT_EMPTY_PATH, "security.capability", (void*)&val, sizeof(val), /* xattr_flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to setcap '%s': %m", path); + } + + return 0; +} + +static int parse_caps_from_arg(Item *item) { + FCapsUpdate fcaps; + int r; + + assert(item); + + r = capability_vfs_from_string(item->argument, &fcaps); + if (r < 0) { + log_full_errno(arg_graceful ? LOG_DEBUG : LOG_WARNING, + r, "Failed to parse capabilities \"%s\", ignoring: %m", item->argument); + return 0; + } + + item->fcaps_set = true; + item->fcaps = fcaps; + + return 0; +} + +static int fd_set_caps( + Context *c, + Item *item, + int fd, + const char *path, + const struct stat *st, + CreationMode creation) { + assert(c); + assert(item); + assert(fd >= 0); + assert(path); + + if (!item->fcaps_set) + return 0; + return apply_fcaps(fd, path, item->append_or_force, &item->fcaps); +} + +static int path_set_caps( + Context *c, + Item *item, + const char *path, + CreationMode creation) { + _cleanup_close_ int fd = -EBADF; + + assert(c); + assert(item); + assert(path); + + if (!item->fcaps_set) + return 0; + + fd = path_open_safe(path); + if (fd == -ENOENT) + return 0; + if (fd < 0) + return fd; + + return apply_fcaps(fd, path, item->append_or_force, &item->fcaps); +} + static int parse_attribute_from_arg(Item *item) { static const struct { char character; @@ -2633,7 +2940,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); @@ -2940,6 +3247,18 @@ static int create_item(Context *c, Item *i) { return r; break; + case SET_FCAPS: + r = glob_item(c, i, path_set_caps); + if (r < 0) + return r; + break; + + case RECURSIVE_SET_FCAPS: + r = glob_item_recursively(c, i, fd_set_caps); + if (r < 0) + return r; + break; + case SET_ATTRIBUTE: r = glob_item(c, i, path_set_attribute); if (r < 0) @@ -3111,6 +3430,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 +3441,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 +3463,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, @@ -3455,7 +3778,7 @@ static int find_uid(const char *user, uid_t *ret_uid, Hashmap **cache) { /* Second: pass to NSS if we are running "online" */ if (!arg_root) - return get_user_creds(&user, ret_uid, NULL, NULL, NULL, 0); + return get_user_creds(user, /* flags= */ 0, NULL, ret_uid, NULL, NULL, NULL); /* Third, synthesize "root" unconditionally */ if (streq(user, "root")) { @@ -3480,7 +3803,7 @@ static int find_gid(const char *group, gid_t *ret_gid, Hashmap **cache) { /* Second: pass to NSS if we are running "online" */ if (!arg_root) - return get_group_creds(&group, ret_gid, 0); + return get_group_creds(group, /* flags= */ 0, /* ret_name= */ NULL, ret_gid); /* Third, synthesize "root" unconditionally */ if (streq(group, "root")) { @@ -3585,11 +3908,13 @@ static int parse_line( assert(fname); assert(line >= 1); assert(buffer); + assert(invalid_config); const Specifier specifier_table[] = { { 'h', specifier_user_home, NULL }, { 'C', specifier_directory, UINT_TO_PTR(DIRECTORY_CACHE) }, + { 'D', specifier_directory, UINT_TO_PTR(DIRECTORY_SHARED) }, { 'L', specifier_directory, UINT_TO_PTR(DIRECTORY_LOGS) }, { 'S', specifier_directory, UINT_TO_PTR(DIRECTORY_STATE) }, { 't', specifier_directory, UINT_TO_PTR(DIRECTORY_RUNTIME) }, @@ -3796,6 +4121,23 @@ static int parse_line( return r; break; + case SET_FCAPS: + case RECURSIVE_SET_FCAPS: + if (unbase64) { + *invalid_config = true; + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for capabilities."); + } + if (!i.argument) { + *invalid_config = true; + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Set capabilities requires argument."); + } + r = parse_caps_from_arg(&i); + if (r < 0) + return r; + break; + case SET_ATTRIBUTE: case RECURSIVE_SET_ATTRIBUTE: if (unbase64) { @@ -4123,211 +4465,172 @@ 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; + + 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, "hE", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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, opts.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, opts.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(opts.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(opts.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(opts.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(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must be an absolute path."); - if (!endswith(optarg, ".conf")) + if (!endswith(opts.arg, ".conf")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The argument to --replace= must have the extension '.conf'."); - arg_replace = optarg; + arg_replace = opts.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(&opts); + size_t n_args = option_parser_get_n_args(&opts); + 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 +4638,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 +4654,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 +4719,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 +4855,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 +4911,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 +4955,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..55f0ce26d31cb 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'), @@ -36,6 +45,7 @@ executables += [ if conf.get('ENABLE_BOOTLOADER') == 1 and conf.get('HAVE_OPENSSL') == 1 and conf.get('HAVE_TPM2') == 1 nvpcrs = [ 'cryptsetup', 'hardware', + 'login', 'verity'] foreach n : nvpcrs custom_target( diff --git a/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in b/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in index acec95206f807..3f01ef34eb4d7 100644 --- a/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in +++ b/src/tpm2-setup/nvpcr/cryptsetup.nvpcr.in @@ -1,5 +1,6 @@ { "name" : "cryptsetup", "algorithm" : "sha256", - "nvIndex" : {{TPM2_NVPCR_BASE + 1}} + "nvIndex" : {{TPM2_NVPCR_BASE + 1}}, + "priority" : 700 } diff --git a/src/tpm2-setup/nvpcr/hardware.nvpcr.in b/src/tpm2-setup/nvpcr/hardware.nvpcr.in index ddeb7f99e505f..bb4a3afa6957a 100644 --- a/src/tpm2-setup/nvpcr/hardware.nvpcr.in +++ b/src/tpm2-setup/nvpcr/hardware.nvpcr.in @@ -1,5 +1,6 @@ { "name" : "hardware", "algorithm" : "sha256", - "nvIndex" : {{TPM2_NVPCR_BASE + 0}} + "nvIndex" : {{TPM2_NVPCR_BASE + 0}}, + "priority" : 500 } diff --git a/src/tpm2-setup/nvpcr/login.nvpcr.in b/src/tpm2-setup/nvpcr/login.nvpcr.in new file mode 100644 index 0000000000000..babad7aaf89a7 --- /dev/null +++ b/src/tpm2-setup/nvpcr/login.nvpcr.in @@ -0,0 +1,6 @@ +{ + "name" : "login", + "algorithm" : "sha256", + "nvIndex" : {{TPM2_NVPCR_BASE + 3}}, + "priority" : 800 +} diff --git a/src/tpm2-setup/nvpcr/verity.nvpcr.in b/src/tpm2-setup/nvpcr/verity.nvpcr.in index b4fb62bd76244..7d1630d6cd15c 100644 --- a/src/tpm2-setup/nvpcr/verity.nvpcr.in +++ b/src/tpm2-setup/nvpcr/verity.nvpcr.in @@ -1,5 +1,6 @@ { "name" : "verity", "algorithm" : "sha256", - "nvIndex" : {{TPM2_NVPCR_BASE + 2}} + "nvIndex" : {{TPM2_NVPCR_BASE + 2}}, + "priority" : 300 } diff --git a/src/tpm2-setup/tpm2-clear.c b/src/tpm2-setup/tpm2-clear.c index 0800a90747961..19186ecc02fd8 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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts) != 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..5a4a88bf9f428 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,97 @@ 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. We also order after the .mount units (not just the + * .automount units): ordering after the automount is enough for start-up, but only an + * ordering against the actual mount unit ensures swtpm is stopped (releasing the ESP) before + * the file system is unmounted on shutdown. This is a no-op at start-up, as the mount has no + * job of its own there (it is triggered on access via the automount). */ + 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 boot.mount efi.mount\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 +173,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..20bb3f7011a9f 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,17 +9,21 @@ #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" +#include "sort-util.h" #include "string-util.h" #include "strv.h" #include "tmpfile-util.h" @@ -37,94 +41,75 @@ 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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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(opts.arg, "auto") ? NULL : opts.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(opts.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", opts.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(&opts) != 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,8 +321,8 @@ 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) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write SRK public key file '%s'.", pem_path); + if (sym_PEM_write_PUBKEY(f, tpm2_key.pkey) <= 0) + return log_openssl_errors(LOG_ERR, "Failed to write SRK public key file '%s'.", pem_path); if (fchmod(fileno(f), 0444) < 0) return log_error_errno(errno, "Failed to adjust access mode of SRK public key file '%s' to 0444: %m", pem_path); @@ -385,7 +370,8 @@ static int setup_srk(void) { typedef struct SetupNvPCRContext { Tpm2Context *tpm2_context; struct iovec anchor_secret; - size_t n_already, n_anchored, n_failed; + size_t n_already, n_anchored, n_failed, n_skipped; + bool nv_space_exhausted; /* Set once the TPM ran out of NV index space, so we skip the rest. */ Set *done; } SetupNvPCRContext; @@ -409,6 +395,15 @@ static int setup_nvpcr_one( if (set_contains(c->done, name)) return 0; + /* If the TPM already ran out of NV index space, don't even try to allocate further NvPCRs. As + * NvPCRs are processed in order of priority (most important first), everything still pending is + * less important than what already didn't fit. */ + if (c->nv_space_exhausted) { + log_debug("TPM NV index space already exhausted, skipping allocation of NvPCR '%s'.", name); + c->n_skipped++; + return 0; + } + r = tpm2_nvpcr_initialize(c->tpm2_context, /* session= */ NULL, name, &c->anchor_secret); if (r == -EUNATCH) { assert(!iovec_is_set(&c->anchor_secret)); @@ -422,11 +417,21 @@ static int setup_nvpcr_one( r = tpm2_nvpcr_initialize(c->tpm2_context, /* session= */ NULL, name, &c->anchor_secret); } - if (r == -ENOSPC) { + if (r == -EOPNOTSUPP) { 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("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) { + /* The TPM's NV index space is exhausted. Remember this so we skip the remaining (less + * important) NvPCRs, and report it gracefully at the end rather than failing the boot. + * Logged at notice level, not error. */ + c->nv_space_exhausted = true; + c->n_skipped++; + return log_struct_errno(LOG_NOTICE, r, + LOG_MESSAGE("The TPM's NV index space is exhausted, skipping allocation of NvPCR '%s' and any less important ones: %m", name), + LOG_MESSAGE_ID(SD_MESSAGE_TPM_NVINDEX_EXHAUSTED_STR)); } if (r < 0) { c->n_failed++; @@ -444,9 +449,29 @@ static int setup_nvpcr_one( return 0; } +typedef struct NvPCREntry { + const char *name; /* points into the strv 'l' in setup_nvpcr() */ + uint64_t priority; +} NvPCREntry; + +static int nvpcr_entry_compare(const NvPCREntry *a, const NvPCREntry *b) { + int r; + + assert(a); + assert(b); + + /* Lower priority value means more important, hence allocate it first. Break ties by name + * (ascending) so the resulting order is fully deterministic. */ + r = CMP(a->priority, b->priority); + if (r != 0) + return r; + + return strcmp(a->name, b->name); +} + 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( @@ -458,8 +483,33 @@ static int setup_nvpcr(void) { if (r < 0) return log_error_errno(r, "Failed to find .nvpcr files: %m"); + /* Pair each NvPCR name with its allocation priority, then sort, so that we allocate the most + * important NvPCRs first. This matters when the TPM's NV index space is too small to fit all of + * them: we then degrade gracefully, skipping the least important NvPCRs. */ + size_t n = strv_length(l); + _cleanup_free_ NvPCREntry *entries = new(NvPCREntry, n); + if (!entries) + return log_oom(); + + for (size_t i = 0; i < n; i++) { + uint64_t priority = TPM2_NVPCR_PRIORITY_DEFAULT; + + r = tpm2_nvpcr_get_index(l[i], /* ret_nv_index= */ NULL, &priority); + if (r < 0) + /* If we can't read the definition, assume the default priority and let the + * per-NvPCR error path in setup_nvpcr_one() report the actual problem. */ + log_warning_errno(r, "Failed to read priority of NvPCR '%s', assuming default priority %" PRIu64 ": %m", l[i], priority); + + entries[i] = (NvPCREntry) { + .name = l[i], + .priority = priority, + }; + } + + typesafe_qsort(entries, n, nvpcr_entry_compare); + int ret = 0; - STRV_FOREACH(i, l) { + FOREACH_ARRAY(e, entries, n) { if (!c.tpm2_context) { /* Inability to contact the TPM shall be fatal for us */ r = tpm2_context_new_or_warn(arg_tpm2_device, &c.tpm2_context); @@ -467,17 +517,20 @@ static int setup_nvpcr(void) { return r; } + log_debug("Setting up NvPCR '%s' (priority %" PRIu64 ").", e->name, e->priority); + /* But if we fail to initialize some NvPCR, we go on */ - RET_GATHER(ret, setup_nvpcr_one(&c, *i)); + RET_GATHER(ret, setup_nvpcr_one(&c, e->name)); } - 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.nv_space_exhausted) + log_notice("Skipped %zu lowest-priority NvPCR(s) because the TPM's NV index space is exhausted, proceeding anyway.", c.n_skipped); if (c.n_failed > 0) log_warning("%zu NvPCRs failed to initialize, proceeding anyway.", c.n_failed); @@ -489,9 +542,16 @@ static int setup_nvpcr(void) { log_info("%zu NvPCRs initialized. (%zu NvPCRs were already initialized.)", c.n_anchored, c.n_already); } else if (c.n_already > 0) log_info("%zu NvPCRs already initialized.", c.n_already); - else if (c.n_failed == 0) + else if (c.n_failed == 0 && c.n_skipped == 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 +564,30 @@ 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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; + + r = DLOPEN_TPM2(LOG_ERR, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + 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..33d5defd06083 --- /dev/null +++ b/src/tpm2-setup/tpm2-swtpm.c @@ -0,0 +1,246 @@ +/* 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 "rm-rf.h" +#include "sha256.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"); + } + + /* manufacture_swtpm() writes its marker only after swtpm_setup has fully created the TPM state. + * If the marker file is missing, the existing state is incomplete and recreation is needed. */ + r = RET_NERRNO(faccessat(state_fd, SWTPM_MANUFACTURED_MARKER, F_OK, AT_SYMLINK_NOFOLLOW)); + if (r >= 0) { + log_debug("TPM state directory holds a fully manufactured TPM, not manufacturing a TPM."); + return 0; + } + if (r != -ENOENT) + return log_error_errno(r, "Failed to check for TPM manufacture marker: %m"); + + if (!in_initrd()) + return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "swtpm TPM state directory has not been initialized in the initrd, refusing."); + + /* Cleanup incomplete state before recreating. */ + _cleanup_close_ int wipe_fd = fd_reopen(state_fd, O_RDONLY|O_DIRECTORY|O_CLOEXEC); + if (wipe_fd < 0) + return log_error_errno(wipe_fd, "Failed to reopen swtpm state directory: %m"); + r = rm_rf_children(TAKE_FD(wipe_fd), REMOVE_PHYSICAL, /* root_dev= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to clear incomplete swtpm state directory: %m"); + + log_debug("TPM state directory holds no fully manufactured TPM, 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..c7b1fb3e1fbe8 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 "mkdir.h" +#include "options.h" #include "path-util.h" #include "pidref.h" #include "pretty-print.h" @@ -442,112 +443,84 @@ 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 opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) 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 (opts.arg) { + if (isempty(opts.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, opts.arg); if (r < 0) return r; } break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind != argc) + if (option_parser_get_n_args(&opts) > 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..1cde89ad6f602 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,46 @@ 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 opts = { argc, argv }; - while ((c = getopt_long(argc, argv, "xh", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts); + 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..4708b56325595 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" @@ -322,7 +325,7 @@ static int cd_media_compat(Context *c) { static int cd_inquiry(Context *c) { struct scsi_cmd sc; - unsigned char inq[36]; + unsigned char inq[36] = {}; int r; assert(c); @@ -357,7 +360,7 @@ static int feature_profiles(Context *c, const unsigned char *profiles, size_t si } static int cd_profiles_old_mmc(Context *c) { - disc_information discinfo; + disc_information discinfo = {}; struct scsi_cmd sc; size_t len; int r; @@ -408,7 +411,7 @@ static int cd_profiles_old_mmc(Context *c) { static int cd_profiles(Context *c) { struct scsi_cmd sc; - unsigned char features[65530]; + unsigned char features[65530] = {}; unsigned cur_profile; size_t len; int r; @@ -500,8 +503,8 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(media_state, MediaState); static int dvd_ram_media_update_state(Context *c) { struct scsi_cmd sc; - unsigned char dvdstruct[8]; - unsigned char format[12]; + unsigned char dvdstruct[8] = {}; + unsigned char format[12] = {}; unsigned char len; int r; @@ -565,7 +568,7 @@ static int dvd_ram_media_update_state(Context *c) { static int dvd_media_update_state(Context *c) { struct scsi_cmd sc; - unsigned char buffer[32 * 2048]; + unsigned char buffer[32 * 2048] = {}; int r; r = dvd_ram_media_update_state(c); @@ -611,7 +614,7 @@ static int dvd_media_update_state(Context *c) { static int cd_media_info(Context *c) { struct scsi_cmd sc; - unsigned char header[32]; + unsigned char header[32] = {}; MediaState state; int r; @@ -663,8 +666,8 @@ static int cd_media_info(Context *c) { static int cd_media_toc(Context *c) { struct scsi_cmd sc; - unsigned char header[12]; - unsigned char toc[65536]; + unsigned char header[12] = {}; + unsigned char toc[65536] = {}; unsigned num_tracks; size_t len; int r; @@ -898,60 +901,62 @@ 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 opts = { argc, argv }; - while ((c = getopt_long(argc, argv, "deluh", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts); + 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..64af7b8028770 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,48 @@ 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 opts = { argc, argv }; - while ((c = getopt_long(argc, argv, "F:hV", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) 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 = opts.arg; + break; } + if (option_parser_get_n_args(&opts) > 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..30bc96c526bdf 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,53 @@ #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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts); + 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..a591ba40c0cca 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,51 @@ 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 opts = { argc, argv }; - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts); return 1; } @@ -280,32 +280,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 +308,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return iocost_main(argc, argv); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/udev/meson.build b/src/udev/meson.build index 7df5b3de857b1..375645e7593ed 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') @@ -118,7 +122,6 @@ udev_dependencies = [ libblkid_cflags, libkmod_cflags, libmount_cflags, - threads, ] udev_plugin_template = executable_template + { @@ -134,7 +137,6 @@ udev_common_template = { 'dependencies' : [ libacl_cflags, libblkid_cflags, - threads, ], } udev_test_template = test_template + udev_common_template @@ -148,8 +150,8 @@ udev_binaries_dict = [ keyboard_keys_from_name_inc, 'extract' : udevadm_extract_sources, 'include_directories' : [ - libexec_template['include_directories'], include_directories('.', 'net'), + libexec_template['include_directories'], ], 'dependencies' : udev_dependencies, 'link_with' : udev_link_with, diff --git a/src/udev/mtd_probe/mtd_probe.c b/src/udev/mtd_probe/mtd_probe.c index d79a0617e21e0..7280573646d66 100644 --- a/src/udev/mtd_probe/mtd_probe.c +++ b/src/udev/mtd_probe/mtd_probe.c @@ -19,48 +19,58 @@ */ #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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts); + 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 +83,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-gperf.gperf b/src/udev/net/link-config-gperf.gperf index ef68dab339fff..535c9e9dbb76b 100644 --- a/src/udev/net/link-config-gperf.gperf +++ b/src/udev/net/link-config-gperf.gperf @@ -40,6 +40,7 @@ Match.Version, config_parse_net_condition, Match.Credential, config_parse_net_condition, CONDITION_CREDENTIAL, offsetof(LinkConfig, conditions) Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(LinkConfig, conditions) Match.Firmware, config_parse_net_condition, CONDITION_FIRMWARE, offsetof(LinkConfig, conditions) +Match.MachineTag, config_parse_net_condition, CONDITION_MACHINE_TAG, offsetof(LinkConfig, conditions) Link.Description, config_parse_string, 0, offsetof(LinkConfig, description) /* udev property */ Link.Property, config_parse_udev_property, 0, offsetof(LinkConfig, properties) @@ -132,6 +133,10 @@ Link.TxMaxCoalescedHighFrames, config_parse_coalesce_u32, Link.CoalescePacketRateSampleIntervalSec, config_parse_coalesce_sec, 0, offsetof(LinkConfig, coalesce.rate_sample_interval) /* Rx RPS CPU mask */ Link.ReceivePacketSteeringCPUMask, config_parse_rps_cpu_mask, 0, offsetof(LinkConfig, rps_cpu_mask) +/* IRQ affinity settings */ +Link.IRQAffinityPolicy, config_parse_irq_affinity_policy, 0, offsetof(LinkConfig, irq_affinity_policy) +Link.IRQAffinity, config_parse_cpu_set, 0, offsetof(LinkConfig, irq_affinity_cpus) +Link.IRQAffinityNUMA, config_parse_irq_affinity_numa, 0, offsetof(LinkConfig, irq_affinity_numa) /* SR-IOV settings */ Link.SR-IOVVirtualFunctions, config_parse_sr_iov_num_vfs, 0, offsetof(LinkConfig, sr_iov_num_vfs) SR-IOV.VirtualFunction, config_parse_sr_iov_uint32, 0, offsetof(LinkConfig, sr_iov_by_section) diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index eefa95dc5f68d..306686845e916 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include @@ -15,6 +16,7 @@ #include "creds-util.h" #include "device-private.h" #include "device-util.h" +#include "dirent-util.h" #include "escape.h" #include "ether-addr-util.h" #include "ethtool-util.h" @@ -31,13 +33,16 @@ #include "netif-util.h" #include "netlink-util.h" #include "network-util.h" +#include "numa-util.h" #include "parse-util.h" #include "path-util.h" #include "proc-cmdline.h" #include "random-util.h" #include "socket-util.h" +#include "sort-util.h" #include "specifier.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -78,6 +83,7 @@ static LinkConfig* link_config_free(LinkConfig *config) { free(config->wol_password_file); erase_and_free(config->wol_password); cpu_set_done(&config->rps_cpu_mask); + cpu_set_done(&config->irq_affinity_cpus); ordered_hashmap_free(config->sr_iov_by_section); @@ -270,6 +276,8 @@ int link_load_one(LinkConfigContext *ctx, const char *filename) { .eee_enabled = -1, .eee_tx_lpi_enabled = -1, .eee_tx_lpi_timer_usec = USEC_INFINITY, + .irq_affinity_policy = _IRQ_AFFINITY_POLICY_INVALID, + .irq_affinity_numa = IRQ_AFFINITY_NUMA_UNSET, }; FOREACH_ELEMENT(feature, config->features) @@ -558,26 +566,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 +635,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 +650,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."); } @@ -942,6 +930,741 @@ static int link_apply_sr_iov_config(Link *link) { return 0; } +/* Get the local NUMA node for a network device from sysfs. + * Returns -ENOENT if numa_node file doesn't exist or shows -1 (no NUMA). */ +static int link_get_device_numa_node(Link *link, unsigned *ret) { + int r, node; + + assert(link); + assert(link->event); + assert(link->event->dev); + assert(ret); + + r = device_get_sysattr_int(link->event->dev, "device/numa_node", &node); + if (r < 0) + return r; + + /* -1 means no NUMA node (non-NUMA system or device not associated with a node) */ + if (node < 0) + return -ENOENT; + + *ret = (unsigned) node; + return 0; +} + +/* CPU topology information for IRQ affinity spread algorithm. */ +typedef struct CPUTopology { + unsigned cpu; + unsigned numa_node; + unsigned package_id; + unsigned die_id; /* L3 cache domain / chiplet */ + unsigned core_id; + bool is_first_thread; /* First hyperthread of a physical core */ +} CPUTopology; + +/* Die (L3 cache domain) information for spread algorithm */ +typedef struct DieInfo { + unsigned die_id; + unsigned *cpus; /* CPUs in this die (first HT only, sorted by core) */ + size_t cpu_count; + size_t next_idx; /* For round-robin within die */ +} DieInfo; + +/* Returns the first thread of a CPU siblings list */ +static int cpu_topology_get_first_thread(sd_device *cpu_node, unsigned *ret) { + const char *content, *end; + int r; + + assert(cpu_node); + assert(ret); + + r = sd_device_get_sysattr_value(cpu_node, "topology/thread_siblings_list", &content); + if (r < 0) + return r; + + end = content + strcspn(content, ",-"); + + _cleanup_free_ char *first = strndup(content, end - content); + if (!first) + return -ENOMEM; + + return safe_atou(first, ret); +} + +static int cpu_topology_compare(const CPUTopology *a, const CPUTopology *b) { + int r; + + assert(a); + assert(b); + + /* Sort by die first (for L3 cache grouping), then core, then CPU number */ + r = CMP(a->die_id, b->die_id); + if (r != 0) + return r; + + r = CMP(a->core_id, b->core_id); + if (r != 0) + return r; + + return CMP(a->cpu, b->cpu); +} + +/* Comparison function for sorting CPUs by CPU number (for die ID assignment) */ +static int cpu_number_compare(const CPUTopology *a, const CPUTopology *b) { + assert(a); + assert(b); + + return CMP(a->cpu, b->cpu); +} + +/* Assign logical die IDs based on L3 cache sharing topology. + * + * For IRQ spreading, the goal is to distribute interrupts across CPUs that + * don't share cache, minimizing cache line contention when processing packets. + * The L3 cache boundary is the key locality domain: CPUs sharing an L3 can + * exchange data cheaply, while cross-L3 communication is expensive. + * + * We use L3 shared_cpu_list rather than the kernel's physical die_id because: + * - On AMD EPYC, multiple CCXs on the same physical die have separate L3 caches + * - On Intel with Sub-NUMA Clustering, one die may have multiple L3 domains + * - L3 sharing reflects actual data locality, not physical packaging */ +static int assign_sequential_die_ids(CPUTopology *cpus, size_t count) { + _cleanup_strv_free_ char **l3_groups = NULL; + int r; + + assert(cpus); + + /* First, sort CPUs by CPU number for consistent discovery order */ + typesafe_qsort(cpus, count, cpu_number_compare); + + /* Assign die IDs based on order of L3 shared_cpu_list discovery */ + FOREACH_ARRAY(cpu, cpus, count) { + _cleanup_(sd_device_unrefp) sd_device *cpu_node = NULL; + char cpu_path[STRLEN("/sys/devices/system/cpu/cpu") + DECIMAL_STR_MAX(unsigned) + 1]; + const char *l3_list; + unsigned die_id = 0; + bool found = false; + + xsprintf(cpu_path, "/sys/devices/system/cpu/cpu%u", cpu->cpu); + r = sd_device_new_from_syspath(&cpu_node, cpu_path); + if (r < 0) + return r; + + r = sd_device_get_sysattr_value(cpu_node, "cache/index3/shared_cpu_list", &l3_list); + if (r < 0) { + /* No L3 info, fall back to package ID */ + cpu->die_id = cpu->package_id; + continue; + } + + /* Check if we've seen this L3 group before */ + STRV_FOREACH(g, l3_groups) { + if (streq(*g, l3_list)) { + cpu->die_id = die_id; + found = true; + break; + } + die_id++; + } + + if (!found) { + /* New L3 group, assign next sequential die ID */ + cpu->die_id = die_id; + r = strv_extend(&l3_groups, l3_list); + if (r < 0) + return r; + } + } + + return 0; +} + +static int discover_cpu_topology(CPUTopology **ret, size_t *ret_count) { + _cleanup_(sd_device_unrefp) sd_device *parent_node = NULL; + _cleanup_free_ CPUTopology *cpus = NULL; + const char *name; + size_t count = 0; + int r; + + assert(ret); + assert(ret_count); + + r = sd_device_new_from_syspath(&parent_node, "/sys/devices/system/cpu"); + if (r < 0) + return r; + + FOREACH_DEVICE_CHILD_WITH_SUFFIX(parent_node, cpu_node, name) { + char topo_path[STRLEN("/sys/devices/system/cpu/cpu/topology") + DECIMAL_STR_MAX(unsigned) + 1]; + const char *n; + unsigned cpu, online, first_thread; + + n = startswith(name, "cpu"); + if (!n) + continue; + + r = safe_atou(n, &cpu); + if (r < 0) { + log_debug_errno(r, "Failed to convert %s to unsigned, skipping: %m", n); + continue; + } + + r = device_get_sysattr_unsigned(cpu_node, "online", &online); + if (r == -ENOENT) + online = 1; /* CPU 0 lacks 'online' file, assume online */ + else if (r < 0 || online == 0) + continue; + + /* Check if topology directory exists (filters out cpu0 on some systems) */ + xsprintf(topo_path, "/sys/devices/system/cpu/cpu%u/topology", cpu); + if (access(topo_path, F_OK) < 0) { + log_debug_errno(errno, "Failed to access %s, ignoring: %m", topo_path); + continue; + } + + if (!GREEDY_REALLOC(cpus, count + 1)) + return -ENOMEM; + + cpus[count].cpu = cpu; + + r = numa_get_node_from_cpu(cpu, &cpus[count].numa_node); + if (r < 0) { + log_debug_errno(r, "Failed to get NUMA node for CPU %u, assuming NUMA node 0: %m", cpu); + cpus[count].numa_node = 0; + } + + r = device_get_sysattr_unsigned(cpu_node, "topology/physical_package_id", &cpus[count].package_id); + if (r < 0) { + log_device_debug_errno(cpu_node, r, "Failed to get physical_package_id, assuming package ID 0: %m"); + cpus[count].package_id = 0; + } + + /* die_id will be assigned later by assign_sequential_die_ids() */ + cpus[count].die_id = 0; + + r = device_get_sysattr_unsigned(cpu_node, "topology/core_id", &cpus[count].core_id); + if (r < 0) { + log_device_debug_errno(cpu_node, r, "Failed to get core_id, assuming core ID %u: %m", cpu); + cpus[count].core_id = cpu; + } + + r = cpu_topology_get_first_thread(cpu_node, &first_thread); + if (r < 0) + cpus[count].is_first_thread = true; + else + cpus[count].is_first_thread = (first_thread == cpu); + + count++; + } + + if (count == 0) + return -ENOENT; + + /* Assign sequential die IDs based on L3 discovery order */ + r = assign_sequential_die_ids(cpus, count); + if (r < 0) + return r; + + /* Sort CPUs by topology for consistent ordering */ + typesafe_qsort(cpus, count, cpu_topology_compare); + + *ret = TAKE_PTR(cpus); + *ret_count = count; + + return 0; +} + +/* Reorder indices so consecutive elements are maximally spread apart. + * + * Uses recursive divide-and-conquer: split in half, permute each half, + * then interleave. This ensures elements originally far apart become adjacent. + * + * Example trace for [0,1,2,3,4,5,6,7]: + * split into [0,1,2,3] and [4,5,6,7] + * recurse left: [0,1,2,3] -> [0,2,1,3] + * recurse right: [4,5,6,7] -> [4,6,5,7] + * interleave -> [0,4,2,6,1,5,3,7] + * + * The first N elements of the output are roughly evenly distributed across the + * original range, for any N. This is useful when assigning IRQs to CPUs: if a + * NIC has fewer IRQs than CPUs, the assigned CPUs will still be spread across + * the CPUs rather than all at the beginning. */ +static int equidist_permute(size_t *indices, size_t n_indices) { + _cleanup_free_ size_t *left = NULL, *right = NULL; + size_t left_count, right_count; + size_t li = 0, ri = 0, ti = 0; + int r; + + assert(indices); + + if (n_indices <= 1) + return 0; + + left_count = DIV_ROUND_UP(n_indices, 2); + right_count = n_indices - left_count; + + /* Recursively permute each half */ + left = newdup(size_t, indices, left_count); + right = newdup(size_t, &indices[left_count], right_count); + if (!left || !right) + return log_oom(); + + r = equidist_permute(left, left_count); + if (r < 0) + return r; + + r = equidist_permute(right, right_count); + if (r < 0) + return r; + + /* Interleave: left[0], right[0], left[1], right[1], ... */ + for (size_t i = 0; i < n_indices; i++) { + if (i % 2 == 0 && li < left_count) + indices[ti++] = left[li++]; + else if (ri < right_count) + indices[ti++] = right[ri++]; + else if (li < left_count) + indices[ti++] = left[li++]; + } + + return 0; +} + +static void die_info_free(DieInfo *dies, size_t count) { + assert(dies || count == 0); + + FOREACH_ARRAY(die, dies, count) + free(die->cpus); + free(dies); +} + +/* Build die information from topology, grouping CPUs by L3/die and filtering to first HT only */ +static int build_die_info(const CPUTopology *topology, size_t topology_count, DieInfo **ret, size_t *ret_count) { + DieInfo *dies = NULL; + size_t die_count = 0; + int r; + + assert(topology); + assert(ret); + assert(ret_count); + + CLEANUP_ARRAY(dies, die_count, die_info_free); + + FOREACH_ARRAY(cpu_topology, topology, topology_count) { + DieInfo *die = NULL; + + /* Only consider first hyperthreads for initial spread */ + if (!cpu_topology->is_first_thread) + continue; + + /* Find or create die entry */ + for (size_t j = 0; j < die_count; j++) + if (dies[j].die_id == cpu_topology->die_id) { + die = &dies[j]; + break; + } + + if (!die) { + if (!GREEDY_REALLOC(dies, die_count + 1)) + return log_oom(); + die = &dies[die_count++]; + *die = (DieInfo) { .die_id = cpu_topology->die_id }; + } + + if (!GREEDY_REALLOC(die->cpus, die->cpu_count + 1)) + return log_oom(); + + die->cpus[die->cpu_count++] = cpu_topology->cpu; + } + + /* Sort dies by die_id for determinism, then apply equidist to CPUs within each die */ + FOREACH_ARRAY(die, dies, die_count) { + _cleanup_free_ unsigned *reordered = NULL; + _cleanup_free_ size_t *indices = new(size_t, die->cpu_count); + if (!indices) + return log_oom(); + + for (size_t j = 0; j < die->cpu_count; j++) + indices[j] = j; + + r = equidist_permute(indices, die->cpu_count); + if (r < 0) + return r; + + /* Reorder CPUs according to equidist permutation */ + reordered = new(unsigned, die->cpu_count); + if (!reordered) + return log_oom(); + + for (size_t j = 0; j < die->cpu_count; j++) + reordered[j] = die->cpus[indices[j]]; + + memcpy(die->cpus, reordered, die->cpu_count * sizeof(unsigned)); + } + + *ret = TAKE_PTR(dies); + *ret_count = die_count; + + return 0; +} + +/* Select CPUs for IRQ affinity spreading with optimal topology distribution. + * + * Algorithm: + * 1. Group CPUs by die (L3 cache domain), using only first hyperthreads + * 2. Apply equidistant permutation to both die order and CPUs within each die, + * so consecutive selections are maximally spread (e.g., [0,1,2,3] -> [0,2,1,3]) + * 3. Round-robin across dies, picking one CPU per die per round + * 4. If more IRQs than physical cores, wrap around and reuse the same CPUs + * + * Ensures each IRQ gets a dedicated physical core before any core handles + * multiple IRQs. Two IRQs on one physical core time-share but benefit from warm + * cache, whereas spreading across SMT siblings causes resource contention with + * no cache benefit. + * Maximizes physical distance between consecutively assigned IRQs, improving + * cache distribution even when only a few IRQs are assigned. */ +static int select_spread_cpus( + const CPUTopology *topology, + size_t topology_count, + size_t n_irqs, + unsigned **ret, + size_t *ret_count) { + + _cleanup_free_ unsigned *selected = NULL; + _cleanup_free_ size_t *die_order = NULL; + DieInfo *dies = NULL; + size_t die_count = 0, selected_count = 0; + int r; + + assert(topology); + assert(ret); + assert(ret_count); + + CLEANUP_ARRAY(dies, die_count, die_info_free); + + selected = new(unsigned, n_irqs); + if (!selected) + return -ENOMEM; + + /* Build die information with first HT CPUs only */ + r = build_die_info(topology, topology_count, &dies, &die_count); + if (r < 0) + return r; + + if (die_count == 0) + return -ENOENT; + + /* Create equidistant die ordering */ + die_order = new(size_t, die_count); + if (!die_order) + return -ENOMEM; + + for (size_t i = 0; i < die_count; i++) + die_order[i] = i; + + r = equidist_permute(die_order, die_count); + if (r < 0) + return r; + + /* Round-robin across dies, picking one CPU from each die at a time */ + size_t dies_exhausted = 0; + while (selected_count < n_irqs) { + bool made_progress = false; + + for (size_t i = 0; i < die_count && selected_count < n_irqs; i++) { + DieInfo *die = &dies[die_order[i]]; + + if (die->next_idx >= die->cpu_count) + continue; + + selected[selected_count++] = die->cpus[die->next_idx++]; + made_progress = true; + + if (die->next_idx >= die->cpu_count) + dies_exhausted++; + } + + if (made_progress) + continue; + + /* All first HTs exhausted, wrap around for remaining IRQs */ + if (dies_exhausted < die_count) + break; + + /* Reset all dies for round-robin wrap */ + FOREACH_ARRAY(die, dies, die_count) + die->next_idx = 0; + dies_exhausted = 0; + } + + *ret = TAKE_PTR(selected); + *ret_count = selected_count; + + return 0; +} + +static int set_irq_affinity(Link *link, unsigned irq, unsigned cpu) { + _cleanup_free_ char *affinity_path = NULL, *mask_str = NULL; + unsigned n_groups = cpu / 32; + int r; + + assert(link); + + if (asprintf(&affinity_path, "/proc/irq/%u/smp_affinity", irq) < 0) + return log_oom(); + + /* Convert CPU number to hex bitmask. + * For CPU N, set bit N (1 << N). CPUs are split by comma-separated + * 32-bits groups. To assign CPU 32, we should write 1,00000000 */ + + if (asprintf(&mask_str, "%x", 1U << (cpu % 32)) < 0) + return log_oom(); + + for (unsigned i = 0; i < n_groups; i++) + if (!strextend(&mask_str, ",00000000")) + return log_oom(); + + r = write_string_file(affinity_path, mask_str, WRITE_STRING_FILE_DISABLE_BUFFER); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to set IRQ %u affinity to CPU %u: %m", irq, cpu); + + log_link_debug(link, "Set IRQ %u affinity to CPU %u.", irq, cpu); + + return 0; +} + +static int link_apply_irq_affinity_spread(Link *link, const CPUSet *allowed_cpus) { + _cleanup_closedir_ DIR *dir = NULL; + _cleanup_free_ CPUTopology *topology = NULL; + _cleanup_free_ unsigned *irqs = NULL; + _cleanup_free_ unsigned *spread_cpus = NULL; + size_t topology_count = 0, irq_count = 0, spread_count = 0; + int r; + + assert(link); + + r = device_opendir(link->event->dev, "device/msi_irqs", &dir); + if (r < 0) { + if (r != -ENOENT) + return log_link_error_errno(link, r, "Failed to open device/msi_irqs: %m"); + log_link_debug_errno(link, r, "No MSI IRQs found, skipping IRQ affinity configuration: %m"); + return 0; + } + + FOREACH_DIRENT(de, dir, return log_link_error_errno(link, errno, "Failed to read directory device/msi_irqs: %m")) { + unsigned irq; + + r = safe_atou(de->d_name, &irq); + if (r < 0) + return log_link_error_errno(link, r, "Failed to convert parse IRQ number: %s", de->d_name); + + if (!GREEDY_REALLOC(irqs, irq_count + 1)) + return log_oom(); + + irqs[irq_count++] = irq; + } + + if (irq_count == 0) { + log_link_debug(link, "No IRQs found, skipping spread."); + return 0; + } + + typesafe_qsort(irqs, irq_count, cmp_unsigned); + + r = discover_cpu_topology(&topology, &topology_count); + if (r < 0) + return log_link_error_errno(link, r, "Failed to discover CPU topology: %m"); + + /* Filter topology by allowed CPUs if specified */ + if (allowed_cpus && allowed_cpus->set) { + _cleanup_free_ CPUTopology *filtered_topology = new(CPUTopology, topology_count); + size_t filtered_count = 0; + + if (!filtered_topology) + return log_oom(); + + for (size_t i = 0; i < topology_count; i++) + if (CPU_ISSET_S(topology[i].cpu, allowed_cpus->allocated, allowed_cpus->set)) + filtered_topology[filtered_count++] = topology[i]; + + if (filtered_count == 0) { + log_link_warning(link, "IRQAffinity= filter excludes all CPUs, skipping spread."); + return 0; + } + + log_link_debug(link, "Filtered to %zu CPUs (from %zu) based on IRQAffinity=.", filtered_count, topology_count); + + free_and_replace(topology, filtered_topology); + topology_count = filtered_count; + } + + log_link_debug(link, "Spreading %zu IRQs across %zu CPUs.", irq_count, topology_count); + + /* Select CPUs using maximum distance algorithm */ + r = select_spread_cpus(topology, topology_count, irq_count, &spread_cpus, &spread_count); + if (r < 0) + return log_link_error_errno(link, r, "Failed to select spread CPUs: %m"); + + for (size_t i = 0; i < spread_count; i++) + (void) set_irq_affinity(link, irqs[i], spread_cpus[i]); + + log_link_info(link, "Applied IRQ affinity policy 'spread' across %zu CPUs for %zu IRQs.", + MIN(topology_count, irq_count), irq_count); + + return 0; +} + +static int link_apply_irq_affinity_single(Link *link, const CPUSet *allowed_cpus) { + _cleanup_closedir_ DIR *dir = NULL; + unsigned target_cpu = 0; + int r; + + assert(link); + assert(link->config); + assert(ASSERT_PTR(link->event)->dev); + + /* If IRQAffinity= is specified, use the first allowed CPU instead of CPU 0 */ + if (allowed_cpus && allowed_cpus->set) { + bool found = false; + + for (unsigned cpu = 0; cpu < allowed_cpus->allocated * 8; cpu++) + if (CPU_ISSET_S(cpu, allowed_cpus->allocated, allowed_cpus->set)) { + target_cpu = cpu; + found = true; + break; + } + + if (!found) { + log_link_warning(link, "IRQAffinity= filter excludes all CPUs, skipping single."); + return 0; + } + } + + r = device_opendir(link->event->dev, "device/msi_irqs", &dir); + if (r < 0) { + if (r != -ENOENT) + return log_link_error_errno(link, r, "Failed to open device/msi_irqs: %m"); + log_link_debug_errno(link, r, "No MSI IRQs found, skipping IRQ affinity configuration: %m"); + return 0; + } + + FOREACH_DIRENT(de, dir, return log_link_error_errno(link, errno, "Failed to read directory device/msi_irqs: %m")) { + unsigned irq; + + r = safe_atou(de->d_name, &irq); + if (r < 0) + return log_link_error_errno(link, r, "Failed to convert parse IRQ number: %s", de->d_name); + + (void) set_irq_affinity(link, irq, target_cpu); + } + + log_link_info(link, "Applied IRQ affinity policy 'single' (pinning to CPU %u).", target_cpu); + + return 0; +} + +static int link_apply_irq_affinity(Link *link) { + _cleanup_(cpu_set_done) CPUSet effective_cpus = {}; + const char *syspath; + unsigned numa_node = IRQ_AFFINITY_NUMA_UNSET; + int r; + + assert(link); + assert(link->config); + assert(ASSERT_PTR(link->event)->dev); + + if (link->event->event_mode != EVENT_UDEV_WORKER) { + log_link_debug(link, "Running in test mode, skipping application of IRQ affinity settings."); + return 0; + } + + if (link->config->irq_affinity_policy < 0) + return 0; + + r = sd_device_get_syspath(link->event->dev, &syspath); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get syspath: %m"); + + /* Compute effective CPU set from IRQAffinity= and IRQAffinityNUMA= */ + if (link->config->irq_affinity_numa != IRQ_AFFINITY_NUMA_UNSET) { + _cleanup_(cpu_set_done) CPUSet numa_cpus = {}; + + /* Resolve "local" to the actual NUMA node */ + if (link->config->irq_affinity_numa == IRQ_AFFINITY_NUMA_LOCAL) { + r = link_get_device_numa_node(link, &numa_node); + if (r < 0) { + log_link_warning_errno( + link, r, + "Failed to determine local NUMA node for device, skipping IRQ affinity configuration: %m"); + return 0; + } + log_link_debug(link, "Device is on NUMA node %u.", numa_node); + } else + numa_node = link->config->irq_affinity_numa; + + /* Get CPUs for the NUMA node */ + r = numa_node_get_cpus(numa_node, &numa_cpus); + if (r < 0) { + log_link_warning_errno( + link, r, + "Failed to get CPUs for NUMA node %u, skipping IRQ affinity configuration: %m", + numa_node); + return 0; + } + + /* If IRQAffinity= is also specified, compute intersection */ + if (link->config->irq_affinity_cpus.set) { + /* Compute intersection of IRQAffinity= and NUMA CPUs */ + size_t max_allocated = MAX(numa_cpus.allocated, link->config->irq_affinity_cpus.allocated); + + r = cpu_set_realloc(&effective_cpus, max_allocated * 8); + if (r < 0) + return log_oom(); + + for (size_t i = 0; i < max_allocated * 8; i++) { + bool in_numa = i < numa_cpus.allocated * 8 && + CPU_ISSET_S(i, numa_cpus.allocated, numa_cpus.set); + bool in_affinity = i < link->config->irq_affinity_cpus.allocated * 8 && + CPU_ISSET_S(i, link->config->irq_affinity_cpus.allocated, link->config->irq_affinity_cpus.set); + + if (in_numa && in_affinity) { + r = cpu_set_add(&effective_cpus, i); + if (r < 0) + return log_oom(); + } + } + + /* Check if intersection is empty */ + if (!effective_cpus.set || CPU_COUNT_S(effective_cpus.allocated, effective_cpus.set) == 0) { + log_link_warning( + link, + "IRQAffinity= and IRQAffinityNUMA= intersection is empty, skipping IRQ affinity configuration."); + return 0; + } + + log_link_debug(link, "Using intersection of IRQAffinity= and NUMA node %u CPUs.", numa_node); + } else { + /* Only NUMA filtering, use NUMA CPUs directly */ + effective_cpus = TAKE_STRUCT(numa_cpus); + log_link_debug(link, "Using CPUs from NUMA node %u.", numa_node); + } + } else if (link->config->irq_affinity_cpus.set) { + /* Only IRQAffinity= specified, copy it */ + r = cpu_set_add_set(&effective_cpus, &link->config->irq_affinity_cpus); + if (r < 0) + return log_oom(); + } + /* else: no filtering, effective_cpus remains empty (meaning use all CPUs) */ + + switch (link->config->irq_affinity_policy) { + case IRQ_AFFINITY_POLICY_SINGLE: + return link_apply_irq_affinity_single(link, effective_cpus.set ? &effective_cpus : NULL); + case IRQ_AFFINITY_POLICY_SPREAD: + return link_apply_irq_affinity_spread(link, effective_cpus.set ? &effective_cpus : NULL); + default: + assert_not_reached(); + } +} + static int link_apply_rps_cpu_mask(Link *link) { _cleanup_free_ char *mask_str = NULL; LinkConfig *config; @@ -1071,6 +1794,10 @@ int link_apply_config(LinkConfigContext *ctx, Link *link) { if (r < 0) return r; + r = link_apply_irq_affinity(link); + if (r < 0) + return r; + return link_apply_udev_properties(link); } @@ -1271,7 +1998,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; } @@ -1420,3 +2147,61 @@ DEFINE_CONFIG_PARSE_ENUMV(config_parse_name_policy, name_policy, NamePolicy, DEFINE_CONFIG_PARSE_ENUMV(config_parse_alternative_names_policy, alternative_names_policy, NamePolicy, _NAMEPOLICY_INVALID); + +int config_parse_irq_affinity_numa( + 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) { + + unsigned tmp, *numa = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *numa = IRQ_AFFINITY_NUMA_UNSET; + return 0; + } + + if (streq(rvalue, "local")) { + *numa = IRQ_AFFINITY_NUMA_LOCAL; + return 0; + } + + /* Parse as NUMA node number */ + r = safe_atou(rvalue, &tmp); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + + /* UINT_MAX and UINT_MAX-1 are used to flag "unset" and "local NUMA node" respectively. */ + if (tmp >= IRQ_AFFINITY_NUMA_LOCAL) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid NUMA node number %u, ignoring assignment: %s", tmp, rvalue); + return 0; + } + + *numa = tmp; + + return 0; +} + +static const char* const irq_affinity_policy_table[_IRQ_AFFINITY_POLICY_MAX] = { + [IRQ_AFFINITY_POLICY_SINGLE] = "single", + [IRQ_AFFINITY_POLICY_SPREAD] = "spread", +}; + +DEFINE_STRING_TABLE_LOOKUP(irq_affinity_policy, IRQAffinityPolicy); +DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT( + config_parse_irq_affinity_policy, + irq_affinity_policy, + IRQAffinityPolicy, + _IRQ_AFFINITY_POLICY_INVALID); diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h index e00327c01526b..a2b0def4c3372 100644 --- a/src/udev/net/link-config.h +++ b/src/udev/net/link-config.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include + #include "sd-device.h" #include "cpu-set-util.h" @@ -22,6 +24,17 @@ typedef enum MACAddressPolicy { _MAC_ADDRESS_POLICY_INVALID = -EINVAL, } MACAddressPolicy; +typedef enum IRQAffinityPolicy { + IRQ_AFFINITY_POLICY_SINGLE, + IRQ_AFFINITY_POLICY_SPREAD, + _IRQ_AFFINITY_POLICY_MAX, + _IRQ_AFFINITY_POLICY_INVALID = -EINVAL, +} IRQAffinityPolicy; + +/* Special values for IRQAffinityNUMA= */ +#define IRQ_AFFINITY_NUMA_UNSET UINT_MAX +#define IRQ_AFFINITY_NUMA_LOCAL (IRQ_AFFINITY_NUMA_UNSET - 1) + typedef struct Link { UdevEvent *event; LinkConfig *config; @@ -113,6 +126,11 @@ struct LinkConfig { /* Rx RPS CPU mask */ CPUSet rps_cpu_mask; + /* IRQ affinity */ + IRQAffinityPolicy irq_affinity_policy; + CPUSet irq_affinity_cpus; + unsigned irq_affinity_numa; + /* SR-IOV */ uint32_t sr_iov_num_vfs; OrderedHashmap *sr_iov_by_section; @@ -136,6 +154,7 @@ int link_get_config(LinkConfigContext *ctx, Link *link); int link_apply_config(LinkConfigContext *ctx, Link *link); DECLARE_STRING_TABLE_LOOKUP(mac_address_policy, MACAddressPolicy); +DECLARE_STRING_TABLE_LOOKUP(irq_affinity_policy, IRQAffinityPolicy); /* gperf lookup function */ const struct ConfigPerfItem* link_config_gperf_lookup(const char *str, GPERF_LEN_TYPE length); @@ -150,3 +169,5 @@ CONFIG_PARSER_PROTOTYPE(config_parse_mac_address_policy); CONFIG_PARSER_PROTOTYPE(config_parse_name_policy); CONFIG_PARSER_PROTOTYPE(config_parse_alternative_names_policy); CONFIG_PARSER_PROTOTYPE(config_parse_rps_cpu_mask); +CONFIG_PARSER_PROTOTYPE(config_parse_irq_affinity_policy); +CONFIG_PARSER_PROTOTYPE(config_parse_irq_affinity_numa); diff --git a/src/udev/net/test-link-config-tables.c b/src/udev/net/test-link-config-tables.c index 499778505c442..58b628bc9b388 100644 --- a/src/udev/net/test-link-config-tables.c +++ b/src/udev/net/test-link-config-tables.c @@ -8,6 +8,7 @@ int main(int argc, char **argv) { test_setup_logging(LOG_DEBUG); test_table(MACAddressPolicy, mac_address_policy, MAC_ADDRESS_POLICY); + test_table(IRQAffinityPolicy, irq_affinity_policy, IRQ_AFFINITY_POLICY); return EXIT_SUCCESS; } 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..93b65816e497e 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 opts = { argc, argv }; + int r; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { - case 'd': + OPTION_COMMON_HELP: + return help(); + + 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, opts.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, opts.arg); break; - case 'g': - all_good = true; + OPTION('p', "page", "0x80|0x83|pre-spc3-83", "SCSI page"): + r = parse_page_code(opts.arg, &default_page_code); + if (r < 0) + return r; + break; + + OPTION('s', "sg-version", "3|4", "Use SGv3 or SGv4"): + r = safe_atoi(opts.arg, &sg_version); + if (r < 0) + return log_error_errno(r, "Invalid SG version '%s'", opts.arg); + if (!IN_SET(sg_version, 3, 4)) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), + "Unknown SG version '%s'", opts.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(&opts); + 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); - - optind = 1; /* reset this global extern */ - while (retval == 0) { - option = getopt_long(newargc, newargv, "bgp:", options, NULL); - if (option == -1) - break; + _cleanup_strv_free_ char **newargv = NULL; + r = get_file_options(vendor_str, model_str, &newargv); + if (r <= 0) + return r; - switch (option) { - case 'b': - *good_bad = 0; - break; + size_t newargc = strv_length(newargv); + if (newargc > INT_MAX) + return log_oom(); /* Close enough :) */ - case 'g': - *good_bad = 1; - break; + OptionParser opts = { newargc, newargv }; - 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_OR_RETURN(c, &opts) + if (opts.opt->short_code == 'b') + *good_bad = 0; + else if (opts.opt->short_code == 'g') + *good_bad = 1; + else if (opts.opt->short_code == 'p') { + r = parse_page_code(opts.arg, page_code); + if (r < 0) + return r; + } else { + _cleanup_free_ char *synopsis = + option_get_synopsis(opts.opt, "/", /* show_metavar=*/ false); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Option %s not supported in the config file.", + strnull(synopsis)); } - } - 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..dfb4b497227fe 100644 --- a/src/udev/test-udev-rule-runner.c +++ b/src/udev/test-udev-rule-runner.c @@ -12,7 +12,7 @@ #include "label-util.h" #include "log.h" #include "main-func.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "mount-util.h" #include "namespace-util.h" #include "parse-util.h" @@ -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..0a75ee8dcfeb3 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -10,7 +10,6 @@ #endif #include -#include #include #include #include @@ -20,11 +19,13 @@ #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" #include "initrd-util.h" #include "gpt.h" +#include "options.h" #include "parse-util.h" #include "string-util.h" #include "strv.h" @@ -421,6 +422,92 @@ static int read_loopback_backing_inode( return 0; } +static int gpt_boot_disk_needs_loop(sd_device *dev, int fd, ssize_t *ret_sector_size) { + int r; + + assert(dev); + assert(fd >= 0); + assert(ret_sector_size); + + /* Determine whether the boot disk needs a loop device to expose its partitions. 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 whether this is the boot + * disk by comparing GPT partition UUIDs with the ESP/XBOOTLDR UUID exported by the boot loader. + * The kernel can't parse the partition table itself in this case, so the caller sets up a loop + * device with the returned sector size. + * + * 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 too, if the + * disk contains the ESP we booted from, a loop device is needed to expose the partitions. + * + * Returns 1 if the boot disk needs a loop device, 0 if not. On success the GPT sector size is + * returned in *ret_sector_size (0 if the disk has no GPT), regardless of whether a loop device + * is needed. */ + + _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) { + *ret_sector_size = 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) { + /* The kernel can parse the partition table itself; no loop device needed. */ + *ret_sector_size = gpt_ssz; + 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"); + + /* No loader partition UUID set, can't tell whether this is the boot disk. */ + *ret_sector_size = gpt_ssz; + 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."); + *ret_sector_size = gpt_ssz; + return 1; /* boot disk needs a loop device */ + } + + *ret_sector_size = gpt_ssz; + 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; @@ -433,14 +520,7 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { int64_t offset = 0; int r; - static const struct option options[] = { - { "offset", required_argument, NULL, 'o' }, - { "hint", required_argument, NULL, 'H' }, - { "noraid", no_argument, NULL, 'R' }, - {} - }; - - r = dlopen_libblkid(); + r = DLOPEN_LIBBLKID(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); if (r < 0) return log_device_debug_errno(dev, r, "blkid not available: %m"); @@ -449,43 +529,32 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { if (!pr) return log_device_debug_errno(dev, errno_or_else(ENOMEM), "Failed to create blkid prober: %m"); - for (;;) { - int option; + OptionParser opts = { argc, argv, .namespace = "udev-builtin-blkid" }; - option = getopt_long(argc, argv, "o:H:R", options, NULL); - if (option == -1) - break; + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_NAMESPACE("udev-builtin-blkid"): {} - switch (option) { - case 'H': + OPTION('H', "hint", "HINT", NULL): errno = 0; - r = sym_blkid_probe_set_hint(pr, optarg, 0); + r = sym_blkid_probe_set_hint(pr, opts.arg, 0); if (r < 0) - return log_device_error_errno(dev, errno_or_else(ENOMEM), "Failed to use '%s' probing hint: %m", optarg); + return log_device_error_errno(dev, errno_or_else(ENOMEM), "Failed to use '%s' probing hint: %m", opts.arg); break; - case 'o': - r = safe_atoi64(optarg, &offset); + + OPTION('o', "offset", "OFFSET", NULL): + r = safe_atoi64(opts.arg, &offset); if (r < 0) - return log_device_error_errno(dev, r, "Failed to parse '%s' as an integer: %m", optarg); + return log_device_error_errno(dev, r, "Failed to parse '%s' as an integer: %m", opts.arg); if (offset < 0) return log_device_error_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid offset %"PRIi64".", offset); break; - case 'R': + + OPTION('R', "noraid", NULL, NULL): noraid = true; break; } - } - - 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) @@ -499,6 +568,35 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { return ignore ? 0 : fd; } + /* If the kernel can't parse the boot disk's partition table, set properties so a udev rule sets + * up a loop device (with the correct sector size) to expose the partitions. We still probe the + * device itself below for its whole-disk properties (filesystem type, partition table UUID, and + * so on); partition and root discovery happen on the loop device instead. We don't need to + * suppress the latter here: blkid probes at the device's own logical sector size, so a GPT + * written for a different sector size is simply not detected, and both PART_ENTRY_* and + * find_gpt_root() only ever act on partitions the kernel actually registered — which, for a disk + * that needs a loop device, is none. */ + if (offset == 0) { + ssize_t gpt_ssz = 0; + + r = gpt_boot_disk_needs_loop(dev, fd, &gpt_ssz); + if (gpt_ssz > 0) + udev_builtin_add_propertyf(event, "ID_PART_GPT_SECTOR_SIZE", "%zi", gpt_ssz); + if (r > 0) + udev_builtin_add_property(event, "ID_PART_GPT_AUTO_ROOT_DISK_NEEDS_LOOP", "1"); + } + + 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-hwdb.c b/src/udev/udev-builtin-hwdb.c index 082af2e6031bd..45766e4e6d9d5 100644 --- a/src/udev/udev-builtin-hwdb.c +++ b/src/udev/udev-builtin-hwdb.c @@ -1,15 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "sd-hwdb.h" #include "alloc-util.h" +#include "device-private.h" #include "device-util.h" #include "hwdb-util.h" -#include "parse-util.h" +#include "options.h" #include "string-util.h" #include "udev-builtin.h" @@ -40,28 +40,25 @@ int udev_builtin_hwdb_lookup( continue; r = udev_builtin_add_property(event, key, value); - if (r < 0) + if (r == -ENOMEM) return r; - n++; + if (r >= 0) + n++; } return n; } static const char* modalias_usb(sd_device *dev, char *s, size_t size) { - const char *v, *p, *n = NULL; - uint16_t vn, pn; + const char *n = NULL; + uint16_t v, p; - if (sd_device_get_sysattr_value(dev, "idVendor", &v) < 0) - return NULL; - if (sd_device_get_sysattr_value(dev, "idProduct", &p) < 0) + if (device_get_sysattr_u16_full(dev, "idVendor", 16, &v) < 0) return NULL; - if (safe_atoux16(v, &vn) < 0) + if (device_get_sysattr_u16_full(dev, "idProduct", 16, &p) < 0) return NULL; - if (safe_atoux16(p, &pn) < 0) - return NULL; - (void) sd_device_get_sysattr_value(dev, "product", &n); + (void) device_get_sysattr_safe_string(dev, "product", &n); - (void) snprintf(s, size, "usb:v%04Xp%04X:%s", vn, pn, strempty(n)); + (void) snprintf(s, size, "usb:v%04Xp%04X:%s", v, p, strempty(n)); return s; } @@ -128,13 +125,6 @@ static int udev_builtin_hwdb_search( } static int builtin_hwdb(UdevEvent *event, int argc, char *argv[]) { - static const struct option options[] = { - { "filter", required_argument, NULL, 'f' }, - { "device", required_argument, NULL, 'd' }, - { "subsystem", required_argument, NULL, 's' }, - { "lookup-prefix", required_argument, NULL, 'p' }, - {} - }; const char *filter = NULL, *device = NULL, *subsystem = NULL, *prefix = NULL; _cleanup_(sd_device_unrefp) sd_device *srcdev = NULL; sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); @@ -143,35 +133,34 @@ static int builtin_hwdb(UdevEvent *event, int argc, char *argv[]) { if (!hwdb) return -EINVAL; - for (;;) { - int option; + OptionParser opts = { argc, argv, .namespace = "udev-builtin-hwdb" }; - option = getopt_long(argc, argv, "f:d:s:p:", options, NULL); - if (option == -1) - break; + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { - switch (option) { - case 'f': - filter = optarg; + OPTION_NAMESPACE("udev-builtin-hwdb"): {} + + OPTION('f', "filter", "FILTER", NULL): + filter = opts.arg; break; - case 'd': - device = optarg; + OPTION('d', "device", "DEVICE", NULL): + device = opts.arg; break; - case 's': - subsystem = optarg; + OPTION('s', "subsystem", "SUBSYSTEM", NULL): + subsystem = opts.arg; break; - case 'p': - prefix = optarg; + OPTION('p', "lookup-prefix", "PREFIX", NULL): + prefix = opts.arg; break; } - } /* query a specific key given as argument */ - if (argv[optind]) { - r = udev_builtin_hwdb_lookup(event, prefix, argv[optind], filter); + char *modalias = option_parser_get_arg(&opts, 0); + if (modalias) { + r = udev_builtin_hwdb_lookup(event, prefix, modalias, filter); if (r < 0) return log_device_debug_errno(dev, r, "Failed to look up hwdb: %m"); if (r == 0) diff --git a/src/udev/udev-builtin-input_id.c b/src/udev/udev-builtin-input_id.c index ecb2afe0488b5..ec232f3e93248 100644 --- a/src/udev/udev-builtin-input_id.c +++ b/src/udev/udev-builtin-input_id.c @@ -9,6 +9,7 @@ #include #include +#include "device-private.h" #include "device-util.h" #include "fd-util.h" #include "parse-util.h" @@ -85,7 +86,7 @@ static void get_cap_mask( unsigned long val; int r; - if (sd_device_get_sysattr_value(pdev, attr, &v) < 0) + if (device_get_sysattr_safe_string(pdev, attr, &v) < 0) v = ""; xsprintf(text, "%s", v); @@ -130,17 +131,14 @@ static void get_cap_mask( } static struct input_id get_input_id(sd_device *dev) { - const char *v; struct input_id id = {}; - if (sd_device_get_sysattr_value(dev, "id/bustype", &v) >= 0) - (void) safe_atoux16(v, &id.bustype); - if (sd_device_get_sysattr_value(dev, "id/vendor", &v) >= 0) - (void) safe_atoux16(v, &id.vendor); - if (sd_device_get_sysattr_value(dev, "id/product", &v) >= 0) - (void) safe_atoux16(v, &id.product); - if (sd_device_get_sysattr_value(dev, "id/version", &v) >= 0) - (void) safe_atoux16(v, &id.version); + assert(dev); + + (void) device_get_sysattr_u16_full(dev, "id/bustype", 16, &id.bustype); + (void) device_get_sysattr_u16_full(dev, "id/vendor", 16, &id.vendor); + (void) device_get_sysattr_u16_full(dev, "id/product", 16, &id.product); + (void) device_get_sysattr_u16_full(dev, "id/version", 16, &id.version); return id; } @@ -386,9 +384,7 @@ static int builtin_input_id(UdevEvent *event, int argc, char *argv[]) { /* walk up the parental chain until we find the real input device; the * argument is very likely a subdevice of this, like eventN */ for (pdev = dev; pdev; ) { - const char *s; - - if (sd_device_get_sysattr_value(pdev, "capabilities/ev", &s) >= 0) + if (sd_device_get_sysattr_value(pdev, "capabilities/ev", /* ret= */ NULL) >= 0) break; if (sd_device_get_parent_with_subsystem_devtype(pdev, "input", NULL, &pdev) >= 0) diff --git a/src/udev/udev-builtin-keyboard.c b/src/udev/udev-builtin-keyboard.c index 5ab40a35526d3..926ee50b3e53e 100644 --- a/src/udev/udev-builtin-keyboard.c +++ b/src/udev/udev-builtin-keyboard.c @@ -5,6 +5,7 @@ #include #include +#include "device-private.h" #include "device-util.h" #include "errno-util.h" #include "fd-util.h" @@ -32,7 +33,7 @@ static int install_force_release(sd_device *dev, const unsigned *release, unsign if (r < 0) return log_device_error_errno(dev, r, "Failed to get serio parent: %m"); - r = sd_device_get_sysattr_value(atkbd, "force_release", &cur); + r = device_get_sysattr_safe_string(atkbd, "force_release", &cur); if (r < 0) return log_device_error_errno(atkbd, r, "Failed to get force-release attribute: %m"); @@ -90,6 +91,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..d368fb2337230 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -165,6 +165,46 @@ static int get_virtfn_info(sd_device *pcidev, sd_device **ret_physfn_pcidev, cha return -ENOENT; } +static int get_subfunc_info(sd_device *aux_dev, sd_device **ret_parent_pcidev, char **ret_suffix) { + sd_device *parent; + unsigned sfnum; + char *suffix; + int r; + + assert(aux_dev); + assert(ret_parent_pcidev); + assert(ret_suffix); + + /* The auxiliary device must expose an 'sfnum' attribute. This is currently used by the + * mlx5_core driver for sub-function (SF) auxiliary devices. The sfnum is the user-defined + * stable identifier passed to "devlink port add ... sfnum N". */ + r = device_get_sysattr_unsigned_filtered(aux_dev, "sfnum", &sfnum); + if (r < 0) + return r; + + /* Walk one hop up: the auxiliary device's parent must be a PCI function. It can be either + * the PF directly, or an SR-IOV VF — mlx5 also supports SFs hosted on VFs (VF-SF), see + * Documentation/networking/representors.rst in the kernel tree. The VF case is handled by + * the existing virtfn logic in names_pci(), so here we just return the immediate PCI + * parent and a single "S" suffix piece. */ + r = sd_device_get_parent(aux_dev, &parent); + if (r < 0) + return r; + + r = device_in_subsystem(parent, "pci"); + if (r < 0) + return r; + if (r == 0) + return -ENODEV; + + if (asprintf(&suffix, "S%u", sfnum) < 0) + return log_oom_debug(); + + *ret_parent_pcidev = sd_device_ref(parent); + *ret_suffix = suffix; + return 0; +} + static int get_port_specifier(sd_device *dev, char **ret) { const char *phys_port_name; unsigned dev_port; @@ -175,7 +215,7 @@ static int get_port_specifier(sd_device *dev, char **ret) { assert(ret); /* First, try to use the kernel provided front panel port name for multiple port PCI device. */ - r = device_get_sysattr_value_filtered(dev, "phys_port_name", &phys_port_name); + r = device_get_sysattr_safe_string_filtered(dev, "phys_port_name", &phys_port_name); if (r >= 0 && !isempty(phys_port_name)) { if (naming_scheme_has(NAMING_SR_IOV_R)) { int vf_id = -1; @@ -249,7 +289,7 @@ static int pci_get_onboard_index(sd_device *dev, unsigned *ret) { return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Naming scheme does not allow onboard index==0."); if (!is_valid_onboard_index(idx)) - return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ENOENT), + return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Not a valid onboard index: %u", idx); *ret = idx; @@ -293,7 +333,7 @@ static int names_pci_onboard_label(UdevEvent *event, sd_device *pci_dev, const c assert(prefix); /* retrieve on-board label from firmware */ - r = device_get_sysattr_value_filtered(pci_dev, "label", &label); + r = device_get_sysattr_safe_string_filtered(pci_dev, "label", &label); if (r < 0) return log_device_debug_errno(pci_dev, r, "Failed to get PCI onboard label: %m"); @@ -346,7 +386,7 @@ static bool is_pci_bridge(sd_device *dev) { assert(dev); - if (device_get_sysattr_value_filtered(dev, "modalias", &v) < 0) + if (device_get_sysattr_safe_string_filtered(dev, "modalias", &v) < 0) return false; if (!startswith(v, "pci:")) @@ -388,14 +428,14 @@ static int parse_hotplug_slot_from_function_id(sd_device *dev, int slots_dirfd, return 0; } - if (device_get_sysattr_value_filtered(dev, "function_id", &attr) < 0) { + if (device_get_sysattr_safe_string_filtered(dev, "function_id", &attr) < 0) { *ret = 0; return 0; } r = safe_atou64(attr, &function_id); if (r < 0) - return log_device_debug_errno(dev, r, "Failed to parse function_id, ignoring: %s", attr); + return log_device_debug_errno(dev, r, "Failed to parse function_id, ignoring: '%s'", attr); if (function_id <= 0 || function_id > UINT32_MAX) return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), @@ -407,7 +447,7 @@ static int parse_hotplug_slot_from_function_id(sd_device *dev, int slots_dirfd, "PCI slot path is too long, ignoring."); if (faccessat(slots_dirfd, filename, F_OK, 0) < 0) - return log_device_debug_errno(dev, errno, "Cannot access %s under pci slots, ignoring: %m", filename); + return log_device_debug_errno(dev, errno, "Cannot access '%s' under pci slots, ignoring: %m", filename); *ret = (uint32_t) function_id; return 1; /* Found. We should ignore domain part. */ @@ -451,7 +491,7 @@ static int pci_get_hotplug_slot_from_address( if (!path) return log_oom_debug(); - if (device_get_sysattr_value_filtered(pci, path, &address) < 0) + if (device_get_sysattr_safe_string_filtered(pci, path, &address) < 0) continue; /* match slot address with device by stripping the function */ @@ -530,7 +570,7 @@ static int get_device_firmware_node_sun(sd_device *dev, uint32_t *ret) { assert(dev); assert(ret); - r = device_get_sysattr_value_filtered(dev, "firmware_node/sun", &attr); + r = device_get_sysattr_safe_string_filtered(dev, "firmware_node/sun", &attr); if (r < 0) return log_device_debug_errno(dev, r, "Failed to read firmware_node/sun, ignoring: %m"); @@ -707,11 +747,11 @@ static int names_vio(UdevEvent *event, const char *prefix) { if (r != 8) return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), - "VIO bus ID and slot ID have invalid length: %s", s); + "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); + "VIO bus ID and slot ID contain invalid characters: '%s'", s); /* Parse only slot ID (the last 4 hexdigits). */ r = safe_atou_full(s + 4, 16, &slotid); @@ -767,8 +807,8 @@ 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_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), + "Platform vendor contains invalid characters: '%s'", vendor); ascii_strlower(vendor); @@ -864,7 +904,7 @@ static int names_devicetree_alias_prefix(UdevEvent *event, const char *prefix, c if (!alias_index) continue; - if (device_get_sysattr_value_filtered(aliases_dev, alias, &alias_path) < 0) + if (device_get_sysattr_safe_string_filtered(aliases_dev, alias, &alias_path) < 0) continue; if (!path_equal(ofnode_path, alias_path)) @@ -878,14 +918,14 @@ static int names_devicetree_alias_prefix(UdevEvent *event, const char *prefix, c r = safe_atou(alias_index, &i); if (r < 0) return log_device_debug_errno(dev, r, - "Could not get index of alias %s: %m", alias); + "Could not get index of alias '%s': %m", alias); conflict = alias_prefix; } /* ...but make sure we don't have an alias conflict */ - if (i == 0 && device_get_sysattr_value_filtered(aliases_dev, conflict, NULL) >= 0) + if (i == 0 && device_get_sysattr_safe_string_filtered(aliases_dev, conflict, NULL) >= 0) return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EEXIST), - "DeviceTree alias conflict: %s and %s both exist.", + "DeviceTree alias conflict: '%s' and '%s' both exist.", alias_prefix, alias_prefix_0); char str[ALTIFNAMSIZ]; @@ -928,24 +968,56 @@ static int names_devicetree(UdevEvent *event, const char *prefix) { static int names_pci(UdevEvent *event, const char *prefix) { sd_device *parent, *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); - _cleanup_(sd_device_unrefp) sd_device *physfn_pcidev = NULL; - _cleanup_free_ char *virtfn_suffix = NULL; + _cleanup_(sd_device_unrefp) sd_device *physfn_pcidev = NULL, *parent_pcidev = NULL; + _cleanup_free_ char *virtfn_suffix = NULL, *subfunc_suffix = NULL, *combined_suffix = NULL; + const char *suffix = NULL; assert(prefix); - /* check if our direct parent is a PCI device with no other bus in-between */ - if (get_matching_parent(dev, STRV_MAKE("pci"), /* skip_virtio= */ true, &parent) < 0) + /* If the network device's direct parent is an auxiliary device that exposes a stable + * 'sfnum' attribute (currently mlx5_core sub-functions), peel off the SF identity into + * a 'S' suffix piece and pick up the aux device's underlying PCI function as + * 'parent'. The aux device is just a bump on the path; everything below — PF/VF + * resolution, slot/onboard lookup — proceeds the same way as for any PCI-rooted + * network device. */ + if (naming_scheme_has(NAMING_SUBFUNC)) { + sd_device *aux; + + if (get_matching_parent(dev, STRV_MAKE("auxiliary"), + /* skip_virtio= */ false, &aux) >= 0) + (void) get_subfunc_info(aux, &parent_pcidev, &subfunc_suffix); + } + + /* SF: parent is the aux device's PCI function. Otherwise the network device's direct + * parent must itself be a PCI device. */ + if (subfunc_suffix) + parent = parent_pcidev; + else if (get_matching_parent(dev, STRV_MAKE("pci"), /* skip_virtio= */ true, &parent) < 0) return 0; - /* If this is an SR-IOV virtual device, get base name using physical device and add virtfn suffix. */ + /* If parent is an SR-IOV VF, walk to the parent PF and add a 'v' suffix piece. The + * onboard BIOS label is intentionally not exposed for any "child function" (VF, SF, or + * VF-SF), since the label refers to the parent's physical port, not to a logical child. */ + bool is_child_function = !!subfunc_suffix; if (naming_scheme_has(NAMING_SR_IOV_V) && - get_virtfn_info(parent, &physfn_pcidev, &virtfn_suffix) >= 0) + get_virtfn_info(parent, &physfn_pcidev, &virtfn_suffix) >= 0) { parent = physfn_pcidev; - else + is_child_function = true; + } + if (!is_child_function) (void) names_pci_onboard_label(event, parent, prefix); - (void) names_pci_onboard(event, parent, prefix, virtfn_suffix); - (void) names_pci_slot(event, parent, prefix, virtfn_suffix); + /* Compose the final suffix in PF -> [VF ->] SF order, e.g. "v0", "S88", or "v0S88". */ + if (virtfn_suffix && subfunc_suffix) { + combined_suffix = strjoin(virtfn_suffix, subfunc_suffix); + if (!combined_suffix) + return log_oom_debug(); + suffix = combined_suffix; + } else + suffix = virtfn_suffix ?: subfunc_suffix; + + (void) names_pci_onboard(event, parent, prefix, suffix); + (void) names_pci_slot(event, parent, prefix, suffix); return 0; } @@ -1121,7 +1193,7 @@ static int names_ccw(UdevEvent *event, const char *prefix) { */ bus_id_len = strlen(bus_id); if (!IN_SET(bus_id_len, 8, 9)) - return log_device_debug_errno(cdev, SYNTHETIC_ERRNO(EINVAL), "Invalid bus_id: %s", bus_id); + return log_device_debug_errno(cdev, SYNTHETIC_ERRNO(EINVAL), "Invalid bus_id: '%s'", bus_id); /* Strip leading zeros from the bus id for aesthetic purposes. This * keeps the ccw names stable, yet much shorter in general case of @@ -1196,7 +1268,7 @@ static int names_mac(UdevEvent *event, const char *prefix) { return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "addr_assign_type=%u, MAC address is not permanent.", assign_type); - r = device_get_sysattr_value_filtered(dev, "address", &s); + r = device_get_sysattr_safe_string_filtered(dev, "address", &s); if (r < 0) return log_device_debug_errno(dev, r, "Failed to read 'address' attribute: %m"); @@ -1241,7 +1313,7 @@ static int names_netdevsim(UdevEvent *event, const char *prefix) { if (r < 0) return log_device_debug_errno(netdevsimdev, r, "Failed to get device sysnum: %m"); - r = device_get_sysattr_value_filtered(dev, "phys_port_name", &phys_port_name); + r = device_get_sysattr_safe_string_filtered(dev, "phys_port_name", &phys_port_name); if (r < 0) return log_device_debug_errno(dev, r, "Failed to get 'phys_port_name' attribute: %m"); if (isempty(phys_port_name)) @@ -1281,7 +1353,7 @@ static int names_xen(UdevEvent *event, const char *prefix) { p = startswith(vif, "vif-"); if (!p) - return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid vif name: %s.", vif); + return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid vif name: '%s'.", vif); r = safe_atou_full(p, SAFE_ATO_REFUSE_PLUS_MINUS | SAFE_ATO_REFUSE_LEADING_ZERO | SAFE_ATO_REFUSE_LEADING_WHITESPACE | 10, &id); diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c index 4db20e4a13f9c..8ae66b30f2676 100644 --- a/src/udev/udev-builtin-path_id.c +++ b/src/udev/udev-builtin-path_id.c @@ -5,7 +5,6 @@ * Logic based on Hannes Reinecke's shell script. */ -#include #include #include #include @@ -109,7 +108,7 @@ static sd_device* handle_scsi_fibre_channel(sd_device *parent, char **path) { return NULL; if (sd_device_new_from_subsystem_sysname(&fcdev, "fc_transport", sysname) < 0) return NULL; - if (sd_device_get_sysattr_value(fcdev, "port_name", &port) < 0) + if (device_get_sysattr_safe_string(fcdev, "port_name", &port) < 0) return NULL; format_lun_number(parent, &lun); @@ -134,7 +133,7 @@ static sd_device* handle_scsi_sas_wide_port(sd_device *parent, char **path) { return NULL; if (sd_device_new_from_subsystem_sysname(&sasdev, "sas_device", sysname) < 0) return NULL; - if (sd_device_get_sysattr_value(sasdev, "sas_address", &sas_address) < 0) + if (device_get_sysattr_safe_string(sasdev, "sas_address", &sas_address) < 0) return NULL; format_lun_number(parent, &lun); @@ -176,7 +175,7 @@ static sd_device* handle_scsi_sas(sd_device *parent, char **path) { return handle_scsi_sas_wide_port(parent, path); /* Get connected phy */ - if (sd_device_get_sysattr_value(target_sasdev, "phy_identifier", &phy_id) < 0) + if (device_get_sysattr_safe_string(target_sasdev, "phy_identifier", &phy_id) < 0) return NULL; /* The port's parent is either hba or expander */ @@ -188,7 +187,7 @@ static sd_device* handle_scsi_sas(sd_device *parent, char **path) { /* Get expander device */ if (sd_device_new_from_subsystem_sysname(&expander_sasdev, "sas_device", sysname) >= 0) { /* Get expander's address */ - if (sd_device_get_sysattr_value(expander_sasdev, "sas_address", &sas_address) < 0) + if (device_get_sysattr_safe_string(expander_sasdev, "sas_address", &sas_address) < 0) return NULL; } @@ -225,7 +224,7 @@ static sd_device* handle_scsi_iscsi(sd_device *parent, char **path) { if (sd_device_new_from_subsystem_sysname(&sessiondev, "iscsi_session", sysname) < 0) return NULL; - if (sd_device_get_sysattr_value(sessiondev, "targetname", &target) < 0) + if (device_get_sysattr_safe_string(sessiondev, "targetname", &target) < 0) return NULL; if (sd_device_get_sysnum(transportdev, &sysnum) < 0) @@ -234,9 +233,9 @@ static sd_device* handle_scsi_iscsi(sd_device *parent, char **path) { if (sd_device_new_from_subsystem_sysname(&conndev, "iscsi_connection", connname) < 0) return NULL; - if (sd_device_get_sysattr_value(conndev, "persistent_address", &addr) < 0) + if (device_get_sysattr_safe_string(conndev, "persistent_address", &addr) < 0) return NULL; - if (sd_device_get_sysattr_value(conndev, "persistent_port", &port) < 0) + if (device_get_sysattr_safe_string(conndev, "persistent_port", &port) < 0) return NULL; format_lun_number(parent, &lun); @@ -269,7 +268,7 @@ static sd_device* handle_scsi_ata(sd_device *parent, char **path, char **compat_ if (sd_device_new_from_subsystem_sysname(&atadev, "ata_port", sysname) < 0) return NULL; - if (sd_device_get_sysattr_value(atadev, "port_no", &port_no) < 0) + if (device_get_sysattr_safe_string(atadev, "port_no", &port_no) < 0) return NULL; if (bus != 0) @@ -376,7 +375,7 @@ static sd_device* handle_scsi_hyperv(sd_device *parent, char **path, size_t guid if (sd_device_get_parent(hostdev, &vmbusdev) < 0) return NULL; - if (sd_device_get_sysattr_value(vmbusdev, "device_id", &guid_str) < 0) + if (device_get_sysattr_safe_string(vmbusdev, "device_id", &guid_str) < 0) return NULL; if (strlen(guid_str) < guid_str_len || guid_str[0] != '{' || guid_str[guid_str_len-1] != '}') @@ -398,11 +397,13 @@ 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; /* firewire */ - if (sd_device_get_sysattr_value(parent, "ieee1394_id", &id) >= 0) { + if (device_get_sysattr_safe_string(parent, "ieee1394_id", &id) >= 0) { path_prepend(path, "ieee1394-0x%s", id); *supported_parent = true; return skip_subsystem(parent, "scsi"); @@ -454,6 +455,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; @@ -469,18 +472,13 @@ static void handle_scsi_tape(sd_device *dev, char **path) { static int get_usb_revision(sd_device *dev) { uint8_t protocol; - const char *s; int r; assert(dev); /* Returns usb revision 1, 2, or 3. */ - r = sd_device_get_sysattr_value(dev, "bDeviceProtocol", &s); - if (r < 0) - return r; - - r = safe_atou8_full(s, 16, &protocol); + r = device_get_sysattr_u8_full(dev, "bDeviceProtocol", 16, &protocol); if (r < 0) return r; @@ -488,11 +486,10 @@ static int get_usb_revision(sd_device *dev) { case USB_HUB_PR_HS_NO_TT: /* Full speed hub (USB1) or Hi-speed hub without TT (USB2) */ /* See speed_show() in drivers/usb/core/sysfs.c of the kernel. */ - r = sd_device_get_sysattr_value(dev, "speed", &s); + r = device_get_sysattr_streq(dev, "speed", "480"); if (r < 0) return r; - - if (streq(s, "480")) + if (r > 0) return 2; return 1; @@ -567,8 +564,8 @@ static sd_device* handle_ap(sd_device *parent, char **path) { assert(parent); assert(path); - if (sd_device_get_sysattr_value(parent, "type", &type) >= 0 && - sd_device_get_sysattr_value(parent, "ap_functions", &func) >= 0) + if (device_get_sysattr_safe_string(parent, "type", &type) >= 0 && + device_get_sysattr_safe_string(parent, "ap_functions", &func) >= 0) path_prepend(path, "ap-%s-%s", type, func); else { const char *sysname; @@ -654,7 +651,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 == '-') { @@ -714,6 +711,18 @@ static int builtin_path_id(UdevEvent *event, int argc, char *argv[]) { path_prepend(&path, "serio-%s", sysnum); parent = skip_subsystem(parent, "serio"); } + } else if (device_in_subsystem(parent, "auxiliary") > 0) { + unsigned sfnum; + + /* sfnum is the user-defined sub-function number (devlink port add ... + * sfnum N). Prepend it so an SF leaf device gets an ID_PATH distinct + * from its parent PF/VF; aux devices without 'sfnum' emit no token to + * preserve pre-patch ID_PATH values. */ + if (device_get_sysattr_unsigned(parent, "sfnum", &sfnum) >= 0) { + path_prepend(&path, "sf-%u", sfnum); + if (compat_path) + path_prepend(&compat_path, "sf-%u", sfnum); + } } else if (device_in_subsystem(parent, "pci") > 0) { path_prepend(&path, "pci-%s", sysname); if (compat_path) @@ -784,7 +793,7 @@ static int builtin_path_id(UdevEvent *event, int argc, char *argv[]) { } else if (device_in_subsystem(parent, "nvme", "nvme-subsystem") > 0) { const char *nsid; - if (sd_device_get_sysattr_value(dev, "nsid", &nsid) >= 0) { + if (device_get_sysattr_safe_string(dev, "nsid", &nsid) >= 0) { path_prepend(&path, "nvme-%s", nsid); if (compat_path) path_prepend(&compat_path, "nvme-%s", nsid); diff --git a/src/udev/udev-builtin-tpm2_id.c b/src/udev/udev-builtin-tpm2_id.c new file mode 100644 index 0000000000000..c082c2c598e27 --- /dev/null +++ b/src/udev/udev-builtin-tpm2_id.c @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "device-util.h" +#include "errno-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 (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { + log_device_debug_errno(dev, r, "Full TPM2 support is not available, skipping identification of TPM2 device '%s'.", dn); + return 0; + } + 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 == -ENOMEM) + return log_oom(); + if (r < 0) + log_device_warning_errno(dev, r, "Failed to set ID_TPM2_MANUFACTURER property, ignoring: %m"); + } + + if (!isempty(info.vendor_string)) { + r = udev_builtin_add_property(event, "ID_TPM2_VENDOR_STRING", info.vendor_string); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + log_device_warning_errno(dev, r, "Failed to set ID_TPM2_VENDOR_STRING property, ignoring: %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 == -ENOMEM) + return log_oom(); + if (r < 0) + log_device_warning_errno(dev, r, "Failed to set ID_TPM2_MODALIAS property, ignoring: %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..be9675fe8851e 100644 --- a/src/udev/udev-builtin-usb_id.c +++ b/src/udev/udev-builtin-usb_id.c @@ -11,113 +11,78 @@ #include #include "device-nodes.h" +#include "device-private.h" #include "device-util.h" #include "fd-util.h" -#include "parse-util.h" #include "string-util.h" #include "strxcpyx.h" #include "udev-builtin.h" #include "udev-util.h" -static void set_usb_iftype(char *to, int if_class_num, size_t len) { - const char *type = "generic"; - +static const char* set_usb_iftype(unsigned if_class_num) { switch (if_class_num) { - case 1: - type = "audio"; - break; - case 2: /* CDC-Control */ - break; - case 3: - type = "hid"; - break; - case 5: /* Physical */ - break; - case 6: - type = "media"; - break; - case 7: - type = "printer"; - break; - case 8: - type = "storage"; - break; - case 9: - type = "hub"; - break; - case 0x0a: /* CDC-Data */ - break; - case 0x0b: /* Chip/Smart Card */ - break; - case 0x0d: /* Content Security */ - break; + case 0x01: + return "audio"; + case 0x03: + return "hid"; + case 0x06: + return "media"; + case 0x07: + return "printer"; + case 0x08: + return "storage"; + case 0x09: + return "hub"; case 0x0e: - type = "video"; - break; - case 0xdc: /* Diagnostic Device */ - break; - case 0xe0: /* Wireless Controller */ - break; - case 0xfe: /* Application-specific */ - break; - case 0xff: /* Vendor-specific */ - break; + return "video"; + default: + /* Other known types: + * 0x02: CDC-Control + * 0x05: Physical + * 0x0a: CDC-Data + * 0x0b: Chip/Smart Card + * 0x0d: Content Security + * 0xdc: Diagnostic Device + * 0xe0: Wireless Controller + * 0xfe: Application-specific + * 0xff: Vendor-specific */ + return "generic"; } - strncpy(to, type, len); - to[len-1] = '\0'; } -static int set_usb_mass_storage_ifsubtype(char *to, const char *from, size_t len) { - int type_num = 0; - const char *type = "generic"; - - if (safe_atoi(from, &type_num) >= 0) - switch (type_num) { - case 1: /* RBC devices */ - type = "rbc"; - break; - case 2: - type = "atapi"; - break; - case 3: - type = "tape"; - break; - case 4: /* UFI */ - type = "floppy"; - break; - case 6: /* Transparent SPC-2 devices */ - type = "scsi"; - break; - } - - strscpy(to, len, type); - return type_num; +static const char* set_usb_mass_storage_ifsubtype(uint8_t type_num) { + switch (type_num) { + case 0x01: /* RBC devices */ + return "rbc"; + case 0x02: + return "atapi"; + case 0x03: + return "tape"; + case 0x04: /* UFI */ + return "floppy"; + case 0x06: /* Transparent SPC-2 devices */ + return "scsi"; + default: + return "generic"; + } } -static void set_scsi_type(char *to, const char *from, size_t len) { - unsigned type_num; - const char *type = "generic"; - - if (safe_atou(from, &type_num) >= 0) - switch (type_num) { - case 0: - case 0xe: - type = "disk"; - break; - case 1: - type = "tape"; - break; - case 4: - case 7: - case 0xf: - type = "optical"; - break; - case 5: - type = "cd"; - break; - } - - strscpy(to, len, type); +static const char* set_scsi_type(unsigned type_num) { + switch (type_num) { + case 0x00: + case 0x0e: + return "disk"; + case 0x01: + return "tape"; + case 0x04: + case 0x07: + case 0x0f: + return "optical"; + case 0x05: + return "cd"; + default: + return "generic"; + } } #define USB_DT_DEVICE 0x01 @@ -143,6 +108,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 +137,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; @@ -217,12 +186,12 @@ static int dev_if_packed_info(sd_device *dev, char *ifs_str, size_t len) { static int builtin_usb_id(UdevEvent *event, int argc, char *argv[]) { sd_device *dev_interface, *dev_usb, *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); const char *syspath, *sysname, *interface_syspath, *vendor_id, *product_id, - *ifnum = NULL, *driver = NULL, *if_class, *if_subclass; + *ifnum = NULL, *driver = NULL, *type_str = NULL; char *s, model_str[64] = "", model_str_enc[256], serial_str[UDEV_NAME_SIZE] = "", - packed_if_str[UDEV_NAME_SIZE] = "", revision_str[64] = "", type_str[64] = "", + packed_if_str[UDEV_NAME_SIZE] = "", revision_str[64] = "", instance_str[64] = "", serial[256], vendor_str[64] = "", vendor_str_enc[256]; - unsigned if_class_num; - int r, protocol = 0; + unsigned if_class_num, protocol = 0; + int r; size_t l; r = sd_device_get_syspath(dev, &syspath); @@ -248,24 +217,21 @@ static int builtin_usb_id(UdevEvent *event, int argc, char *argv[]) { r = sd_device_get_syspath(dev_interface, &interface_syspath); if (r < 0) return log_device_debug_errno(dev_interface, r, "Failed to get syspath: %m"); - (void) sd_device_get_sysattr_value(dev_interface, "bInterfaceNumber", &ifnum); - (void) sd_device_get_sysattr_value(dev_interface, "driver", &driver); + (void) device_get_sysattr_safe_string(dev_interface, "bInterfaceNumber", &ifnum); + (void) device_get_sysattr_safe_string(dev_interface, "driver", &driver); - r = sd_device_get_sysattr_value(dev_interface, "bInterfaceClass", &if_class); + r = device_get_sysattr_unsigned_full(dev_interface, "bInterfaceClass", 16, &if_class_num); if (r < 0) - return log_device_debug_errno(dev_interface, r, "Failed to get bInterfaceClass attribute: %m"); + return log_device_debug_errno(dev_interface, r, "Failed to read bInterfaceClass attribute: %m"); - r = safe_atou_full(if_class, 16, &if_class_num); - if (r < 0) - return log_device_debug_errno(dev_interface, r, "Failed to parse if_class: %m"); if (if_class_num == 8) { /* mass storage */ - if (sd_device_get_sysattr_value(dev_interface, "bInterfaceSubClass", &if_subclass) >= 0) - protocol = set_usb_mass_storage_ifsubtype(type_str, if_subclass, sizeof(type_str)-1); + if (device_get_sysattr_unsigned_full(dev_interface, "bInterfaceSubClass", 16, &protocol) >= 0) + type_str = set_usb_mass_storage_ifsubtype(protocol); } else - set_usb_iftype(type_str, if_class_num, sizeof(type_str)-1); + type_str = set_usb_iftype(if_class_num); - log_device_debug(dev_interface, "if_class:%u protocol:%i", if_class_num, protocol); + log_device_debug(dev_interface, "if_class:%u protocol:%u", if_class_num, protocol); /* usb device directory */ r = sd_device_get_parent_with_subsystem_devtype(dev_interface, "usb", "usb_device", &dev_usb); @@ -278,7 +244,7 @@ static int builtin_usb_id(UdevEvent *event, int argc, char *argv[]) { /* mass storage : SCSI or ATAPI */ if (IN_SET(protocol, 6, 2)) { sd_device *dev_scsi; - const char *scsi_sysname, *scsi_model, *scsi_vendor, *scsi_type, *scsi_rev; + const char *scsi_sysname, *scsi_model, *scsi_vendor, *scsi_rev; int host, bus, target, lun; /* get scsi device */ @@ -313,12 +279,13 @@ static int builtin_usb_id(UdevEvent *event, int argc, char *argv[]) { udev_replace_whitespace(scsi_model, model_str, sizeof(model_str)-1); udev_replace_chars(model_str, NULL); - r = sd_device_get_sysattr_value(dev_scsi, "type", &scsi_type); + unsigned scsi_type; + r = device_get_sysattr_unsigned(dev_scsi, "type", &scsi_type); if (r < 0) { log_device_debug_errno(dev_scsi, r, "Failed to get SCSI type attribute: %m"); goto fallback; } - set_scsi_type(type_str, scsi_type, sizeof(type_str)-1); + type_str = set_scsi_type(scsi_type); r = sd_device_get_sysattr_value(dev_scsi, "rev", &scsi_rev); if (r < 0) { diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index 5f559f5c9a10c..fbed496c4ec40 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" @@ -29,6 +28,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 @@ -125,8 +127,6 @@ int udev_builtin_run(UdevEvent *event, UdevBuiltinCommand cmd, const char *comma if (r < 0) return r; - /* we need '0' here to reset the internal state */ - optind = 0; return builtins[cmd]->cmd(event, strv_length(argv), argv); } @@ -136,13 +136,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-config.c b/src/udev/udev-config.c index 17deadfe76071..27a72f2ac8dcd 100644 --- a/src/udev/udev-config.c +++ b/src/udev/udev-config.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include #include "conf-parser.h" @@ -8,10 +7,12 @@ #include "daemon-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "hashmap.h" +#include "help-util.h" #include "limits-util.h" +#include "options.h" #include "parse-util.h" -#include "pretty-print.h" #include "proc-cmdline.h" #include "serialize.h" #include "signal-util.h" @@ -149,110 +150,91 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat } static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd-udevd.service", "8", &link); + r = option_parser_get_help_table_ns("udevd", &options); if (r < 0) - return log_oom(); - - printf("%s [OPTIONS...]\n\n" - "Rule-based manager for device events and files.\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -d --daemon Detach and run in the background\n" - " -D --debug Enable debug output\n" - " -c --children-max=INT Set maximum number of workers\n" - " -e --exec-delay=SECONDS Seconds to wait before executing RUN=\n" - " -t --event-timeout=SECONDS Seconds to wait before terminating an event\n" - " -N --resolve-names=early|late|never\n" - " When to resolve users and groups\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - link); + return r; + + help_cmdline("[OPTIONS...]"); + help_abstract("Rule-based manager for device events and files."); + + help_section("Options"); + + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("systemd-udevd.service", "8"); return 0; } static int parse_argv(int argc, char *argv[], UdevConfig *config) { - enum { - ARG_TIMEOUT_SIGNAL, - }; - - static const struct option options[] = { - { "daemon", no_argument, NULL, 'd' }, - { "debug", no_argument, NULL, 'D' }, - { "children-max", required_argument, NULL, 'c' }, - { "exec-delay", required_argument, NULL, 'e' }, - { "event-timeout", required_argument, NULL, 't' }, - { "resolve-names", required_argument, NULL, 'N' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "timeout-signal", required_argument, NULL, ARG_TIMEOUT_SIGNAL }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); assert(config); - while ((c = getopt_long(argc, argv, "c:de:Dt:N:hV", options, NULL)) >= 0) { + OptionParser opts = { argc, argv, OPTION_PARSER_NORMAL, "udevd" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'd': + OPTION_NAMESPACE("udevd"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + printf("%s\n", GIT_VERSION); + return 0; + + OPTION('d', "daemon", NULL, "Detach and run in the background"): arg_daemonize = true; break; - case 'c': - r = safe_atou(optarg, &config->children_max); - if (r < 0) - log_warning_errno(r, "Failed to parse --children-max= value '%s', ignoring: %m", optarg); + + OPTION('D', "debug", NULL, "Enable debug output"): + arg_debug = true; + config->log_level = LOG_DEBUG; break; - case 'e': - r = parse_sec(optarg, &config->exec_delay_usec); + + OPTION('c', "children-max", "INT", "Set maximum number of workers"): + r = safe_atou(opts.arg, &config->children_max); if (r < 0) - log_warning_errno(r, "Failed to parse --exec-delay= value '%s', ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse --children-max= value '%s', ignoring: %m", opts.arg); break; - case ARG_TIMEOUT_SIGNAL: - r = signal_from_string(optarg); - if (r <= 0) - log_warning_errno(r, "Failed to parse --timeout-signal= value '%s', ignoring: %m", optarg); - else - config->timeout_signal = r; - break; - case 't': - r = parse_sec(optarg, &config->timeout_usec); + OPTION('e', "exec-delay", "SECONDS", "Seconds to wait before executing RUN="): + r = parse_sec(opts.arg, &config->exec_delay_usec); if (r < 0) - log_warning_errno(r, "Failed to parse --event-timeout= value '%s', ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse --exec-delay= value '%s', ignoring: %m", opts.arg); break; - case 'D': - arg_debug = true; - config->log_level = LOG_DEBUG; + + OPTION('t', "event-timeout", "SECONDS", "Seconds to wait before terminating an event"): + r = parse_sec(opts.arg, &config->timeout_usec); + if (r < 0) + log_warning_errno(r, "Failed to parse --event-timeout= value '%s', ignoring: %m", opts.arg); break; - case 'N': { - ResolveNameTiming t; - t = resolve_name_timing_from_string(optarg); + OPTION_COMMON_RESOLVE_NAMES: { + ResolveNameTiming t = resolve_name_timing_from_string(opts.arg); if (t < 0) - log_warning("Invalid --resolve-names= value '%s', ignoring.", optarg); + log_warning("Invalid --resolve-names= value '%s', ignoring.", opts.arg); else config->resolve_name_timing = t; break; } - case 'h': - return help(); - case 'V': - printf("%s\n", GIT_VERSION); - return 0; - case '?': - return -EINVAL; - default: - assert_not_reached(); + OPTION_LONG("timeout-signal", "SIGNAL", "Signal used when terminating an event"): + r = signal_from_string(opts.arg); + if (r <= 0) + log_warning_errno(r, "Failed to parse --timeout-signal= value '%s', ignoring: %m", opts.arg); + else + config->timeout_signal = r; + break; } - } return 1; } 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-event.c b/src/udev/udev-event.c index 49c692944efe3..fd79c5fea7511 100644 --- a/src/udev/udev-event.c +++ b/src/udev/udev-event.c @@ -226,6 +226,15 @@ static int rename_netif(UdevEvent *event) { } (void) device_add_property(dev, "ID_RENAMING", NULL); + /* When rename_netif() returns an error, the caller bails out of event_execute_rules() before + * device_update_db(dev) is called. Without persisting 'dev' here we would leave the on-disk + * database holding only the (stale) cloned state, dropping every property that udev rules added + * to the device during this event (e.g. ENV{}= assignments alongside the failing NAME=). + * In the -EBUSY case (r == 0) the caller continues normally and will write the DB itself, so + * only persist here when we're actually about to bail out. */ + if (r < 0) + (void) device_update_db(dev); + return r; } diff --git a/src/udev/udev-manager.c b/src/udev/udev-manager.c index 46c8a85d98608..df13df6b50680 100644 --- a/src/udev/udev-manager.c +++ b/src/udev/udev-manager.c @@ -6,6 +6,7 @@ #include "sd-varlink.h" +#include "cgroup-setup.h" #include "cgroup-util.h" #include "common-signal.h" #include "daemon-util.h" @@ -21,6 +22,7 @@ #include "io-util.h" #include "list.h" #include "notify-recv.h" +#include "path-util.h" #include "pidref.h" #include "prioq.h" #include "process-util.h" @@ -226,7 +228,7 @@ Manager* manager_free(Manager *manager) { sd_event_source_unref(manager->kill_workers_event); sd_event_unref(manager->event); - free(manager->cgroup); + free(manager->workers_cgroup); return mfree(manager); } @@ -586,6 +588,14 @@ static int worker_spawn(Manager *manager, Event *event) { .manager_pid = manager_pid, }; + if (manager->workers_cgroup) { + r = cg_attach(manager->workers_cgroup, /* pid= */ 0); + if (r < 0) { + log_error_errno(r, "Failed to move worker into cgroup '%s': %m", manager->workers_cgroup); + _exit(EXIT_FAILURE); + } + } + if (setenv("NOTIFY_SOCKET", manager->worker_notify_socket_path, /* overwrite= */ true) < 0) { log_error_errno(errno, "Failed to set $NOTIFY_SOCKET: %m"); _exit(EXIT_FAILURE); @@ -631,8 +641,13 @@ static int event_run(Event *event) { return 0; } + /* No idle worker could accept the event. If we already reached the worker limit, e.g. because + * we just killed the only idle worker above, leave the event queued and wait for SIGCHLD of an + * exiting worker to free up a slot. on_post() will retry processing the queue. */ + if (hashmap_size(manager->workers) >= manager->config.children_max) + return 0; + /* start new worker and pass initial device */ - assert(hashmap_size(manager->workers) < manager->config.children_max); r = worker_spawn(manager, event); if (r < 0) return r; @@ -678,7 +693,7 @@ static void event_find_blocker(Event *event) { log_device_debug(event->dev, "SEQNUM=%" PRIu64 " blocked by SEQNUM=%" PRIu64, event->seqnum, e->seqnum); - unref_and_replace_full(event->blocker, e, event_ref, event_unref); + unref_and_replace_new_ref(event->blocker, e, event_ref, event_unref); return; } @@ -1345,9 +1360,10 @@ static int on_post(sd_event_source *s, void *userdata) { if (!hashmap_isempty(manager->workers)) return 0; /* There still exist idle workers. */ - if (manager->cgroup && set_isempty(manager->synthesize_change_child_event_sources)) - /* cleanup possible left-over processes in our cgroup */ - (void) cg_kill(manager->cgroup, SIGKILL, CGROUP_IGNORE_SELF, /* killed_pids= */ NULL, /* log_kill= */ NULL, /* userdata= */ NULL); + if (manager->workers_cgroup && set_isempty(manager->synthesize_change_child_event_sources)) + /* cleanup possible left-over processes in the workers cgroup */ + if (cg_kill_kernel_sigkill(manager->workers_cgroup, /* ret_n_pids_killed= */ NULL) == -EOPNOTSUPP) + (void) cg_kill(manager->workers_cgroup, SIGKILL, CGROUP_IGNORE_SELF, /* killed_pids= */ NULL, /* log_kill= */ NULL, /* userdata= */ NULL); return 0; } @@ -1475,12 +1491,30 @@ int manager_main(Manager *manager) { assert(manager); _cleanup_free_ char *cgroup = NULL; - r = cg_pid_get_path(0, &cgroup); + r = cg_pid_get_path(/* pid= */ 0, &cgroup); if (r < 0) log_debug_errno(r, "Failed to get cgroup, ignoring: %m"); else if (endswith(cgroup, "/udev")) { /* If we are in a subcgroup /udev/ we assume it was delegated to us */ log_debug("Running in delegated subcgroup '%s'.", cgroup); - manager->cgroup = TAKE_PTR(cgroup); + + /* Try to create a sibling 'workers' cgroup and spawn all workers inside it, so that we can + * use cgroup.kill to atomically clear all workers. */ + _cleanup_free_ char *workers_cgroup = NULL; + r = path_extract_directory(cgroup, &workers_cgroup); + if (r < 0) + return log_warning_errno(r, "Failed to extract parent of cgroup '%s': %m", cgroup); + + if (!path_extend(&workers_cgroup, "workers")) + return log_oom(); + + r = cg_create(workers_cgroup); + if (r < 0) + log_warning_errno(r, "Failed to create workers cgroup '%s', ignoring: %m", workers_cgroup); + else { + log_debug("Running workers in delegated subcgroup '%s'.", workers_cgroup); + manager->workers_cgroup = TAKE_PTR(workers_cgroup); + } + } r = manager_setup_event(manager); diff --git a/src/udev/udev-manager.h b/src/udev/udev-manager.h index 2eaaf5b0c3d57..c1b98c52d1da3 100644 --- a/src/udev/udev-manager.h +++ b/src/udev/udev-manager.h @@ -39,7 +39,7 @@ typedef struct Manager { Hashmap *workers; LIST_HEAD(Event, events); Event *last_event; - char *cgroup; + char *workers_cgroup; UdevRules *rules; Hashmap *properties; diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c index d30ffd9a56e42..ba28a8b809e87 100644 --- a/src/udev/udev-node.c +++ b/src/udev/udev-node.c @@ -20,7 +20,7 @@ #include "hashmap.h" #include "hexdecoct.h" #include "label-util.h" -#include "mkdir-label.h" +#include "mkdir.h" #include "parse-util.h" #include "path-util.h" #include "selinux-util.h" @@ -79,84 +79,48 @@ static int node_create_symlink(sd_device *dev, const char *devnode, const char * } static int stack_directory_read_one(int dirfd, const char *id, char **devnode, int *priority) { - _cleanup_free_ char *buf = NULL; - int tmp_prio, r; + int r; assert(dirfd >= 0); assert(id); assert(priority); - /* This reads priority and device node from the symlink under /run/udev/links (or udev database). + /* This reads priority and device node from the symlink under /run/udev/links/ directory. * If 'devnode' is NULL, obtained priority is always set to '*priority'. If 'devnode' is non-NULL, - * this updates '*devnode' and '*priority'. */ + * this updates '*devnode' and '*priority' if the obtained one has a higher priority. */ - /* First, let's try to read the entry with the new format, which should replace the old format pretty - * quickly. */ + _cleanup_free_ char *buf = NULL; r = readlinkat_malloc(dirfd, id, &buf); - if (r >= 0) { - char *colon; - - /* With the new format, the devnode and priority can be obtained from symlink itself. */ - - colon = strchr(buf, ':'); - if (!colon || colon == buf) - return -EINVAL; + if (r < 0) + return r == -ENOENT ? -ENODEV : r; - *colon = '\0'; + char *colon = strchr(buf, ':'); + if (!colon || colon == buf) + return -EINVAL; - /* Of course, this check is racy, but it is not necessary to be perfect. Even if the device - * node will be removed after this check, we will receive 'remove' uevent, and the invalid - * symlink will be removed during processing the event. The check is just for shortening the - * timespan that the symlink points to a non-existing device node. */ - if (access(colon + 1, F_OK) < 0) - return -ENODEV; + *colon = '\0'; - r = safe_atoi(buf, &tmp_prio); - if (r < 0) - return r; + /* Of course, this check is racy, but it is not necessary to be perfect. Even if the device + * node will be removed after this check, we will receive 'remove' uevent, and the invalid + * symlink will be removed during processing the event. The check is just for shortening the + * timespan that the symlink points to a non-existing device node. */ + if (access(colon + 1, F_OK) < 0) + return -ENODEV; - if (!devnode) - goto finalize; + int tmp_prio; + r = safe_atoi(buf, &tmp_prio); + if (r < 0) + return r; + if (devnode) { if (*devnode && tmp_prio <= *priority) return 0; /* Unchanged */ r = free_and_strdup(devnode, colon + 1); if (r < 0) return r; + } - } else if (r == -EINVAL) { /* Not a symlink ? try the old format */ - _cleanup_(sd_device_unrefp) sd_device *dev = NULL; - const char *val; - - /* Old format. The devnode and priority must be obtained from uevent and udev database. */ - - r = sd_device_new_from_device_id(&dev, id); - if (r < 0) - return r; - - r = device_get_devlink_priority(dev, &tmp_prio); - if (r < 0) - return r; - - if (!devnode) - goto finalize; - - if (*devnode && tmp_prio <= *priority) - return 0; /* Unchanged */ - - r = sd_device_get_devname(dev, &val); - if (r < 0) - return r; - - r = free_and_strdup(devnode, val); - if (r < 0) - return r; - - } else - return r == -ENOENT ? -ENODEV : r; - -finalize: *priority = tmp_prio; return 1; /* Updated */ } diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index f0e4fbccfd791..b96ba0fe33532 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); @@ -2447,11 +2448,12 @@ static int udev_rule_apply_token_to_event( continue; r = device_add_property(dev, key, value); + if (r == -ENOMEM) + return log_oom(); if (r < 0) - return log_event_error_errno(event, token, r, - "Failed to add property %s=%s: %m", - key, value); - log_event_trace(event, token, "Imported property \"%s=%s\".", key, value); + log_event_warning_errno(event, token, r, "Failed to import property \"%s=%s\", ignoring: %m", key, value); + else + log_event_trace(event, token, "Imported property \"%s=%s\".", key, value); } assert_not_reached(); @@ -2513,11 +2515,12 @@ static int udev_rule_apply_token_to_event( continue; r = device_add_property(dev, key, value); + if (r == -ENOMEM) + return log_oom(); if (r < 0) - return log_event_error_errno(event, token, r, - "Failed to add property %s=%s: %m", - key, value); - log_event_trace(event, token, "Imported property \"%s=%s\".", key, value); + log_event_warning_errno(event, token, r, "Failed to import property \"%s=%s\", ignoring: %m", key, value); + else + log_event_trace(event, token, "Imported property \"%s=%s\".", key, value); } return log_event_result(event, token, token->op == OP_MATCH); @@ -2567,10 +2570,12 @@ static int udev_rule_apply_token_to_event( token->value); r = device_add_property(dev, token->value, val); + if (r == -ENOMEM) + return log_oom(); if (r < 0) - return log_event_error_errno(event, token, r, "Failed to add property \"%s=%s\": %m", - token->value, val); - log_event_trace(event, token, "Imported property \"%s=%s\".", token->value, val); + log_event_warning_errno(event, token, r, "Failed to import property \"%s=%s\", ignoring: %m", token->value, val); + else + log_event_trace(event, token, "Imported property \"%s=%s\".", token->value, val); return log_event_result(event, token, token->op == OP_MATCH); } @@ -2587,10 +2592,12 @@ static int udev_rule_apply_token_to_event( const char *val = value ?: "1"; r = device_add_property(dev, token->value, val); + if (r == -ENOMEM) + return log_oom(); if (r < 0) - return log_event_error_errno(event, token, r, "Failed to add property \"%s=%s\": %m", - token->value, val); - log_event_trace(event, token, "Imported property \"%s=%s\".", token->value, val); + log_event_warning_errno(event, token, r, "Failed to import property \"%s=%s\", ignoring: %m", token->value, val); + else + log_event_trace(event, token, "Imported property \"%s=%s\".", token->value, val); return log_event_result(event, token, token->op == OP_MATCH); } @@ -2613,10 +2620,14 @@ static int udev_rule_apply_token_to_event( continue; r = device_add_property(dev, key, val); + if (r == -ENOMEM) + return log_oom(); if (r < 0) - return log_event_error_errno(event, token, r, "Failed to add property \"%s=%s\": %m", key, val); - log_event_trace(event, token, "Imported property \"%s=%s\".", key, val); - have = true; + log_event_warning_errno(event, token, r, "Failed to import property \"%s=%s\", ignoring: %m", key, val); + else { + log_event_trace(event, token, "Imported property \"%s=%s\".", key, val); + have = true; + } } return log_event_result(event, token, token->op == (have ? OP_MATCH : OP_NOMATCH)); @@ -2881,9 +2892,12 @@ static int udev_rule_apply_token_to_event( udev_replace_chars_and_log(event, token, p, /* allow= */ NULL, "property value"); r = device_add_property(dev, name, value_new); + if (r == -ENOMEM) + return log_oom(); if (r < 0) - return log_event_error_errno(event, token, r, "Failed to set property \"%s=%s\": %m", name, value_new); - log_event_trace(event, token, "Set property \"%s=%s\".", name, value_new); + log_event_warning_errno(event, token, r, "Failed to set property \"%s=%s\", ignoring: %m", name, value_new); + else + log_event_trace(event, token, "Set property \"%s=%s\".", name, value_new); return true; } case TK_A_TAG: { diff --git a/src/udev/udev-varlink.c b/src/udev/udev-varlink.c index 78bcc11fd4f4b..183265fc5353c 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; @@ -186,7 +189,7 @@ int manager_start_varlink_server(Manager *manager, int fd) { return log_error_errno(r, "Failed to attach Varlink connection to event loop: %m"); if (fd < 0) - r = sd_varlink_server_listen_address(v, UDEV_VARLINK_ADDRESS, 0600); + r = sd_varlink_server_listen_address(v, UDEV_VARLINK_ADDRESS, 0644); else r = sd_varlink_server_listen_fd(v, fd); if (r < 0) diff --git a/src/udev/udev-watch.c b/src/udev/udev-watch.c index 08bc3492bb0b0..0636c325e6bc6 100644 --- a/src/udev/udev-watch.c +++ b/src/udev/udev-watch.c @@ -337,21 +337,20 @@ static int udev_watch_restore(Manager *manager) { return log_warning_errno(errno, "Failed to open old watches directory '%s': %m", old); FOREACH_DIRENT(de, dir, break) { - - /* For backward compatibility, read symlink from watch handle to device ID. This is necessary - * when udevd is restarted after upgrading from v248 or older. The new format (ID -> wd) was - * introduced by e7f781e473f5119bf9246208a6de9f6b76a39c5d (v249). */ - - int wd; - if (safe_atoi(de->d_name, &wd) < 0) - continue; /* This should be ID -> wd symlink. Skipping. */ + if (in_charset(de->d_name, DIGITS)) + continue; /* This should be wd -> ID symlink. Skipping. */ _cleanup_(sd_device_unrefp) sd_device *dev = NULL; - r = device_new_from_watch_handle_at(&dev, dirfd(dir), wd); + r = sd_device_new_from_device_id(&dev, de->d_name); if (r < 0) { + _cleanup_free_ char *wd_str = NULL; + + if (ERRNO_IS_NEG_DEVICE_ABSENT(r) || DEBUG_LOGGING) + (void) readlinkat_malloc(dirfd(dir), de->d_name, &wd_str); + log_full_errno(ERRNO_IS_NEG_DEVICE_ABSENT(r) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to create sd_device object from saved watch handle '%i', ignoring: %m", - wd); + "Failed to create sd_device object from device ID '%s' for watch handle '%s', ignoring: %m", + de->d_name, strna(wd_str)); continue; } 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..fcce76663e1a5 100644 --- a/src/udev/udevadm-cat.c +++ b/src/udev/udevadm-cat.c @@ -1,14 +1,14 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include - #include "alloc-util.h" #include "conf-files.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "parse-argument.h" #include "pretty-print.h" #include "static-destruct.h" -#include "strv.h" #include "udevadm.h" #include "udevadm-util.h" @@ -19,83 +19,75 @@ static bool arg_config = false; STATIC_DESTRUCTOR_REGISTER(arg_root, freep); static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("udevadm", "8", &link); + r = option_parser_get_help_table_ns("udevadm-cat", &options); if (r < 0) - return log_oom(); - - printf("%s cat [OPTIONS] [FILE...]\n" - "\n%sShow udev rules files.%s\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --tldr Skip comments and empty lines\n" - " --config Show udev.conf rather than udev rules files\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + return r; + help_cmdline("cat [OPTIONS...] [FILE...]"); + help_abstract("Show udev rules files."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ROOT = 0x100, - ARG_TLDR, - ARG_CONFIG, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "tldr", no_argument, NULL, ARG_TLDR }, - { "config", no_argument, NULL, ARG_CONFIG }, - {} - }; - - int r, c; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); + + OptionParser opts = { argc, argv, .namespace = "udevadm-cat" }; - while ((c = getopt_long(argc, argv, "hVN:", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + + OPTION_NAMESPACE("udevadm-cat"): {} + + OPTION_COMMON_HELP: return help(); - case 'V': + + OPTION_COMMON_VERSION_WITH_HIDDEN_V: return print_version(); - 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(opts.arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - case ARG_TLDR: + + OPTION_LONG("tldr", NULL, + "Skip comments and empty lines"): arg_cat_flags = CAT_TLDR; break; - case ARG_CONFIG: + + OPTION_LONG("config", NULL, + "Show udev.conf rather than udev rules files"): arg_config = true; break; - case '?': - return -EINVAL; - default: - assert_not_reached(); } - if (arg_config && optind < argc) + if (arg_config && option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Combination of --config and FILEs is not supported."); + *remaining_args = option_parser_get_args(&opts); return 1; } -int cat_main(int argc, char *argv[], void *userdata) { +int verb_cat_main(int argc, char *argv[], uintptr_t _data, void *userdata) { + char **args = NULL; int r; - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -105,9 +97,9 @@ 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); + r = search_rules_files(args, arg_root, &files, &n_files); if (r < 0) return r; diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c index 492b00f222b0c..0a0bb35fb5dd5 100644 --- a/src/udev/udevadm-control.c +++ b/src/udev/udevadm-control.c @@ -1,12 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include -#include #include #include "creds-util.h" #include "errno-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "parse-argument.h" #include "parse-util.h" #include "static-destruct.h" @@ -47,151 +48,121 @@ static bool arg_has_control_commands(void) { } static int help(void) { - printf("%s control OPTION\n\n" - "Control the udev daemon.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -e --exit Instruct the daemon to cleanup and exit\n" - " -l --log-level=LEVEL Set the udev log level for the daemon\n" - " -s --stop-exec-queue Do not execute events, queue only\n" - " -S --start-exec-queue Execute events, flush queue\n" - " -R --reload Reload rules and databases\n" - " -p --property=KEY=VALUE Set a global property for all events\n" - " -m --children-max=N Maximum number of children\n" - " --ping Wait for udev to respond to a ping message\n" - " --trace=BOOL Enable/disable trace logging\n" - " --revert Revert previously set configurations\n" - " -t --timeout=SECONDS Maximum time to block for a reply\n" - " --load-credentials Load udev rules from credentials\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table_ns("udevadm-control", &options); + if (r < 0) + return r; + + help_cmdline("control OPTION"); + help_abstract("Control the udev daemon."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_PING = 0x100, - ARG_TRACE, - ARG_REVERT, - ARG_LOAD_CREDENTIALS, - }; - - static const struct option options[] = { - { "exit", no_argument, NULL, 'e' }, - { "log-level", required_argument, NULL, 'l' }, - { "log-priority", required_argument, NULL, 'l' }, /* for backward compatibility */ - { "stop-exec-queue", no_argument, NULL, 's' }, - { "start-exec-queue", no_argument, NULL, 'S' }, - { "reload", no_argument, NULL, 'R' }, - { "reload-rules", no_argument, NULL, 'R' }, /* alias for -R */ - { "property", required_argument, NULL, 'p' }, - { "env", required_argument, NULL, 'p' }, /* alias for -p */ - { "children-max", required_argument, NULL, 'm' }, - { "ping", no_argument, NULL, ARG_PING }, - { "trace", required_argument, NULL, ARG_TRACE }, - { "revert", no_argument, NULL, ARG_REVERT }, - { "timeout", required_argument, NULL, 't' }, - { "load-credentials", no_argument, NULL, ARG_LOAD_CREDENTIALS }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "el:sSRp:m:t:Vh", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "udevadm-control" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'e': + OPTION_NAMESPACE("udevadm-control"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('e', "exit", NULL, "Instruct the daemon to cleanup and exit"): arg_exit = true; break; - case 'l': - arg_log_level = log_level_from_string(optarg); + OPTION_LONG("log-priority", "LEVEL", NULL): {} /* backward compat alias for --log-level */ + OPTION('l', "log-level", "LEVEL", "Set the udev log level for the daemon"): + arg_log_level = log_level_from_string(opts.arg); if (arg_log_level < 0) - return log_error_errno(arg_log_level, "Failed to parse log level '%s': %m", optarg); + return log_error_errno(arg_log_level, "Failed to parse log level '%s': %m", opts.arg); break; - case 's': + OPTION('s', "stop-exec-queue", NULL, "Do not execute events, queue only"): arg_start_exec_queue = false; break; - case 'S': + OPTION('S', "start-exec-queue", NULL, "Execute events, flush queue"): arg_start_exec_queue = true; break; - case 'R': + OPTION_LONG("reload-rules", NULL, NULL): {} /* hidden alias for -R */ + OPTION('R', "reload", NULL, "Reload rules and databases"): arg_reload = true; break; - case 'p': - if (!strchr(optarg, '=')) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "expect = instead of '%s'", optarg); + OPTION_LONG("env", "KEY=VALUE", NULL): {} /* hidden alias for -p */ + OPTION('p', "property", "KEY=VALUE", "Set a global property for all events"): + if (!strchr(opts.arg, '=')) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "expect = instead of '%s'", opts.arg); - r = strv_extend(&arg_env, optarg); + r = strv_extend(&arg_env, opts.arg); if (r < 0) return log_error_errno(r, "Failed to extend environment: %m"); break; - case 'm': { + OPTION('m', "children-max", "N", "Maximum number of children"): { unsigned i; - r = safe_atou(optarg, &i); + r = safe_atou(opts.arg, &i); if (r < 0) - return log_error_errno(r, "Failed to parse maximum number of children '%s': %m", optarg); + return log_error_errno(r, "Failed to parse maximum number of children '%s': %m", opts.arg); arg_max_children = i; break; } - case ARG_PING: + OPTION_LONG("ping", NULL, "Wait for udev to respond to a ping message"): arg_ping = true; break; - case ARG_TRACE: - r = parse_boolean_argument("--trace=", optarg, NULL); + OPTION_LONG("trace", "BOOL", "Enable/disable trace logging"): + r = parse_boolean_argument("--trace=", opts.arg, NULL); if (r < 0) return r; arg_trace = r; break; - case ARG_REVERT: + OPTION_LONG("revert", NULL, "Revert previously set configurations"): arg_revert = true; break; - case 't': - r = parse_sec(optarg, &arg_timeout); + OPTION('t', "timeout", "SECONDS", "Maximum time to block for a reply"): + r = parse_sec(opts.arg, &arg_timeout); if (r < 0) - return log_error_errno(r, "Failed to parse timeout value '%s': %m", optarg); + return log_error_errno(r, "Failed to parse timeout value '%s': %m", opts.arg); break; - case ARG_LOAD_CREDENTIALS: + OPTION_LONG("load-credentials", NULL, "Load udev rules from credentials"): arg_load_credentials = true; break; - - case 'V': - return print_version(); - - case 'h': - return help(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_has_control_commands() && !arg_load_credentials) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No control command option is specified."); - if (optind < argc) + if (option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Extraneous argument: %s", argv[optind]); + "This subprogram takes no positional arguments."); return 1; } @@ -331,7 +302,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..bb6f03d540890 100644 --- a/src/udev/udevadm-hwdb.c +++ b/src/udev/udevadm-hwdb.c @@ -1,10 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include +#include "format-table.h" +#include "help-util.h" #include "hwdb-util.h" #include "log.h" +#include "options.h" #include "udevadm.h" static const char *arg_test = NULL; @@ -14,71 +16,70 @@ static bool arg_update = false; static bool arg_strict = false; static int help(void) { - printf("%s hwdb [OPTIONS]\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -u --update Update the hardware database\n" - " -s --strict When updating, return non-zero exit value on any parsing error\n" - " --usr Generate in " UDEVLIBEXECDIR " instead of /etc/udev\n" - " -t --test=MODALIAS Query database and print result\n" - " -r --root=PATH Alternative root path in the filesystem\n\n" - "NOTE:\n" - "The sub-command 'hwdb' is deprecated, and is left for backwards compatibility.\n" - "Please use systemd-hwdb instead.\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table_ns("udevadm-hwdb", &options); + if (r < 0) + return r; + help_cmdline("hwdb [OPTIONS]"); + help_abstract("Update or query the hardware database."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nNOTE:\n" + "The sub-command 'hwdb' is deprecated, and is left for backwards compatibility.\n" + "Please use systemd-hwdb instead.\n"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_USR = 0x100, - }; - - static const struct option options[] = { - { "update", no_argument, NULL, 'u' }, - { "usr", no_argument, NULL, ARG_USR }, - { "strict", no_argument, NULL, 's' }, - { "test", required_argument, NULL, 't' }, - { "root", required_argument, NULL, 'r' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; - - int c; - - while ((c = getopt_long(argc, argv, "ust:r:Vh", options, NULL)) >= 0) + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv, .namespace = "udevadm-hwdb" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'u': + + OPTION_NAMESPACE("udevadm-hwdb"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('u', "update", NULL, "Update the hardware database"): arg_update = true; break; - 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 't': - arg_test = optarg; + + OPTION_LONG("usr", NULL, + "Generate in " UDEVLIBEXECDIR " instead of /etc/udev"): + arg_hwdb_bin_dir = UDEVLIBEXECDIR; + break; + + OPTION('t', "test", "MODALIAS", "Query database and print result"): + arg_test = opts.arg; break; - case 'r': - arg_root = optarg; + + OPTION('r', "root", "PATH", "Alternative root path in the filesystem"): + arg_root = opts.arg; break; - case 'V': - return print_version(); - case 'h': - return help(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } 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..f2944b02f3197 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -20,7 +19,10 @@ #include "errno-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "glyph-util.h" +#include "help-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "sort-util.h" @@ -160,7 +162,7 @@ static int print_all_attributes(sd_device *device, bool is_parent) { if (skip_attribute(name)) continue; - r = sd_device_get_sysattr_value(device, name, &value); + r = device_get_sysattr_safe_string(device, name, &value); if (r >= 0) { /* skip any values that look like a path */ if (value[0] == '/') @@ -262,7 +264,7 @@ static int print_all_attributes_in_json(sd_device *device, bool is_parent) { if (skip_attribute(name)) continue; - r = sd_device_get_sysattr_value(device, name, &value); + r = device_get_sysattr_safe_string(device, name, &value); if (r >= 0) { /* skip any values that look like a path */ if (value[0] == '/') @@ -800,51 +802,21 @@ static int query_device(QueryType query, sd_device* device) { } static int help(void) { - printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n" - "Query sysfs or the udev database.\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -q --query=TYPE Query device information:\n" - " name Name of device node\n" - " symlink Pointing to node\n" - " path sysfs device path\n" - " property The device properties\n" - " all All values\n" - " --property=NAME Show only properties by this name\n" - " --value When showing properties, print only their values\n" - " -p --path=SYSPATH sysfs device path used for query or attribute walk\n" - " -n --name=NAME Node or symlink name used for query or attribute walk\n" - " -r --root Prepend dev directory to path names\n" - " -a --attribute-walk Print all key matches walking along the chain\n" - " of parent devices\n" - " -t --tree Show tree of devices\n" - " -d --device-id-of-file=FILE Print major:minor of device containing this file\n" - " -x --export Export key/value pairs\n" - " -P --export-prefix Export the key name with a prefix\n" - " -e --export-db Export the content of the udev database\n" - " -c --cleanup-db Clean up the udev database\n" - " -w --wait-for-initialization[=SECONDS]\n" - " Wait for device to be initialized\n" - " --no-pager Do not pipe output into a pager\n" - " --json=pretty|short|off Generate JSON output\n" - " --subsystem-match=SUBSYSTEM\n" - " Query devices matching a subsystem\n" - " --subsystem-nomatch=SUBSYSTEM\n" - " Query devices not matching a subsystem\n" - " --attr-match=FILE[=VALUE]\n" - " Query devices that match an attribute\n" - " --attr-nomatch=FILE[=VALUE]\n" - " Query devices that do not match an attribute\n" - " --property-match=KEY=VALUE\n" - " Query devices with matching properties\n" - " --tag-match=TAG Query devices with a matching tag\n" - " --sysname-match=NAME Query devices with this /sys path\n" - " --name-match=NAME Query devices with this /dev name\n" - " --parent-match=NAME Query devices with this parent device\n" - " --initialized-match Query devices that are already initialized\n" - " --initialized-nomatch Query devices that are not initialized yet\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table_ns("udevadm-info", &options); + if (r < 0) + return r; + + help_cmdline("info [OPTIONS] [DEVPATH|FILE]"); + help_abstract("Query sysfs or the udev database."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } @@ -1006,90 +978,64 @@ static int print_tree(sd_device* below) { } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_PROPERTY = 0x100, - ARG_VALUE, - ARG_NO_PAGER, - ARG_JSON, - ARG_SUBSYSTEM_MATCH, - ARG_SUBSYSTEM_NOMATCH, - ARG_ATTR_MATCH, - ARG_ATTR_NOMATCH, - ARG_PROPERTY_MATCH, - ARG_TAG_MATCH, - ARG_SYSNAME_MATCH, - ARG_NAME_MATCH, - ARG_PARENT_MATCH, - ARG_INITIALIZED_MATCH, - ARG_INITIALIZED_NOMATCH, - }; - - static const struct option options[] = { - { "attribute-walk", no_argument, NULL, 'a' }, - { "tree", no_argument, NULL, 't' }, - { "cleanup-db", no_argument, NULL, 'c' }, - { "device-id-of-file", required_argument, NULL, 'd' }, - { "export", no_argument, NULL, 'x' }, - { "export-db", no_argument, NULL, 'e' }, - { "export-prefix", required_argument, NULL, 'P' }, - { "help", no_argument, NULL, 'h' }, - { "name", required_argument, NULL, 'n' }, - { "path", required_argument, NULL, 'p' }, - { "property", required_argument, NULL, ARG_PROPERTY }, - { "query", required_argument, NULL, 'q' }, - { "root", no_argument, NULL, 'r' }, - { "value", no_argument, NULL, ARG_VALUE }, - { "version", no_argument, NULL, 'V' }, - { "wait-for-initialization", optional_argument, NULL, 'w' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "json", required_argument, NULL, ARG_JSON }, - { "subsystem-match", required_argument, NULL, ARG_SUBSYSTEM_MATCH }, - { "subsystem-nomatch", required_argument, NULL, ARG_SUBSYSTEM_NOMATCH }, - { "attr-match", required_argument, NULL, ARG_ATTR_MATCH }, - { "attr-nomatch", required_argument, NULL, ARG_ATTR_NOMATCH }, - { "property-match", required_argument, NULL, ARG_PROPERTY_MATCH }, - { "tag-match", required_argument, NULL, ARG_TAG_MATCH }, - { "sysname-match", required_argument, NULL, ARG_SYSNAME_MATCH }, - { "name-match", required_argument, NULL, ARG_NAME_MATCH }, - { "parent-match", required_argument, NULL, ARG_PARENT_MATCH }, - { "initialized-match", no_argument, NULL, ARG_INITIALIZED_MATCH }, - { "initialized-nomatch", no_argument, NULL, ARG_INITIALIZED_NOMATCH }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "atced:n:p:q:rxP:w::Vh", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "udevadm-info" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case ARG_PROPERTY: + OPTION_NAMESPACE("udevadm-info"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('q', "query", "TYPE", "Query device information:"): {} + OPTION_HELP_VERBATIM(" name", "- name of device node"): {} + OPTION_HELP_VERBATIM(" symlink", "- pointing to node"): {} + OPTION_HELP_VERBATIM(" path", "- sysfs device path"): {} + OPTION_HELP_VERBATIM(" property", "- the device properties"): {} + OPTION_HELP_VERBATIM(" all", "- all values"): + arg_query = query_type_from_string(opts.arg); + if (arg_query < 0) { + if (streq(opts.arg, "env")) /* deprecated */ + arg_query = QUERY_PROPERTY; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown query type '%s'", opts.arg); + } + break; + + OPTION_LONG("property", "NAME", "Show only properties by this name"): /* Make sure that if the empty property list was specified, we won't show any properties. */ - if (isempty(optarg) && !arg_properties) { + if (isempty(opts.arg) && !arg_properties) { arg_properties = new0(char*, 1); if (!arg_properties) return log_oom(); } else { - r = strv_split_and_extend(&arg_properties, optarg, ",", true); + r = strv_split_and_extend(&arg_properties, opts.arg, ",", true); if (r < 0) return log_oom(); } break; - case ARG_VALUE: + OPTION_LONG("value", NULL, + "When showing properties, print only their values"): arg_value = true; break; - case 'n': - case 'p': { - const char *prefix = c == 'n' ? "/dev/" : "/sys/"; + OPTION('p', "path", "SYSPATH", "sysfs device path used for query or attribute walk"): {} /* fall through */ + OPTION('n', "name", "NAME", "Node or symlink name used for query or attribute walk"): { + const char *prefix = opts.opt->short_code == 'n' ? "/dev/" : "/sys/"; char *path; - path = path_join(path_startswith(optarg, prefix) ? NULL : prefix, optarg); + path = path_join(path_startswith(opts.arg, prefix) ? NULL : prefix, opts.arg); if (!path) return log_oom(); @@ -1099,159 +1045,147 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 'q': - arg_query = query_type_from_string(optarg); - if (arg_query < 0) { - if (streq(optarg, "env")) /* deprecated */ - arg_query = QUERY_PROPERTY; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown query type '%s'", optarg); - } + OPTION('r', "root", NULL, "Prepend dev directory to path names"): + arg_root = true; break; - case 'r': - arg_root = true; + OPTION('a', "attribute-walk", NULL, + "Print all key matches walking along the chain of parent devices"): + arg_action_type = ACTION_ATTRIBUTE_WALK; + break; + + OPTION('t', "tree", NULL, "Show tree of devices"): + arg_action_type = ACTION_TREE; break; - case 'd': + OPTION('d', "device-id-of-file", "FILE", + "Print major:minor of device containing this file"): arg_action_type = ACTION_DEVICE_ID_FILE; - r = free_and_strdup(&arg_name, optarg); + r = free_and_strdup(&arg_name, opts.arg); if (r < 0) return log_oom(); break; - case 'a': - arg_action_type = ACTION_ATTRIBUTE_WALK; + OPTION('x', "export", NULL, "Export key/value pairs"): + arg_export = true; break; - case 't': - arg_action_type = ACTION_TREE; + OPTION('P', "export-prefix", "NAME", "Export the key name with a prefix"): + arg_export = true; + arg_export_prefix = opts.arg; break; - case 'e': + OPTION('e', "export-db", NULL, "Export the content of the udev database"): arg_action_type = ACTION_EXPORT; break; - case 'c': + OPTION('c', "cleanup-db", NULL, "Clean up the udev database"): arg_action_type = ACTION_CLEANUP_DB; break; - case 'x': - arg_export = true; - break; - - case 'P': - arg_export = true; - arg_export_prefix = optarg; - break; - - case 'w': - if (optarg) { - r = parse_sec(optarg, &arg_wait_for_initialization_timeout); + OPTION_FULL(OPTION_OPTIONAL_ARG, 'w', "wait-for-initialization", "SECS", + "Wait for device to be initialized"): + if (opts.arg) { + r = parse_sec(opts.arg, &arg_wait_for_initialization_timeout); if (r < 0) return log_error_errno(r, "Failed to parse timeout value: %m"); } else arg_wait_for_initialization_timeout = USEC_INFINITY; break; - case 'V': - return print_version(); - - case 'h': - return help(); - - 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(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_SUBSYSTEM_MATCH: - r = strv_extend(&arg_subsystem_match, optarg); + OPTION_LONG("subsystem-match", "SUBSYSTEM", + "Query devices matching a subsystem"): + r = strv_extend(&arg_subsystem_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_SUBSYSTEM_NOMATCH: - r = strv_extend(&arg_subsystem_nomatch, optarg); + OPTION_LONG("subsystem-nomatch", "SUBSYSTEM", + "Query devices not matching a subsystem"): + r = strv_extend(&arg_subsystem_nomatch, opts.arg); if (r < 0) return log_oom(); break; - case ARG_ATTR_MATCH: - if (!strchr(optarg, '=')) + OPTION_LONG("attr-match", "FILE[=VALUE]", + "Query devices that match an attribute"): + if (!strchr(opts.arg, '=')) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected = instead of '%s'", optarg); + "Expected = instead of '%s'", opts.arg); - r = strv_extend(&arg_attr_match, optarg); + r = strv_extend(&arg_attr_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_ATTR_NOMATCH: - if (!strchr(optarg, '=')) + OPTION_LONG("attr-nomatch", "FILE[=VALUE]", + "Query devices that do not match an attribute"): + if (!strchr(opts.arg, '=')) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected = instead of '%s'", optarg); + "Expected = instead of '%s'", opts.arg); - r = strv_extend(&arg_attr_nomatch, optarg); + r = strv_extend(&arg_attr_nomatch, opts.arg); if (r < 0) return log_oom(); break; - case ARG_PROPERTY_MATCH: - if (!strchr(optarg, '=')) + OPTION_LONG("property-match", "KEY=VALUE", + "Query devices with matching properties"): + if (!strchr(opts.arg, '=')) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected = instead of '%s'", optarg); + "Expected = instead of '%s'", opts.arg); - r = strv_extend(&arg_property_match, optarg); + r = strv_extend(&arg_property_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_TAG_MATCH: - r = strv_extend(&arg_tag_match, optarg); + OPTION_LONG("tag-match", "TAG", "Query devices with a matching tag"): + r = strv_extend(&arg_tag_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_SYSNAME_MATCH: - r = strv_extend(&arg_sysname_match, optarg); + OPTION_LONG("sysname-match", "NAME", "Query devices with this /sys path"): + r = strv_extend(&arg_sysname_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_NAME_MATCH: - r = strv_extend(&arg_name_match, optarg); + OPTION_LONG("name-match", "NAME", "Query devices with this /dev name"): + r = strv_extend(&arg_name_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_PARENT_MATCH: - r = strv_extend(&arg_parent_match, optarg); + OPTION_LONG("parent-match", "NAME", "Query devices with this parent device"): + r = strv_extend(&arg_parent_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_INITIALIZED_MATCH: + OPTION_LONG("initialized-match", NULL, + "Query devices that are already initialized"): arg_initialized_match = MATCH_INITIALIZED_YES; break; - case ARG_INITIALIZED_NOMATCH: + OPTION_LONG("initialized-nomatch", NULL, + "Query devices that are not initialized yet"): arg_initialized_match = MATCH_INITIALIZED_NO; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - r = strv_extend_strv(&arg_devices, argv + optind, /* filter_duplicates= */ false); + r = strv_extend_strv(&arg_devices, option_parser_get_args(&opts), /* filter_duplicates= */ false); if (r < 0) return log_error_errno(r, "Failed to build argument list: %m"); @@ -1275,7 +1209,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..fd4b6a9059a4b 100644 --- a/src/udev/udevadm-lock.c +++ b/src/udev/udevadm-lock.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include #include #include @@ -9,11 +8,14 @@ #include "device-util.h" #include "fd-util.h" #include "fdset.h" +#include "format-table.h" +#include "glyph-util.h" #include "hash-funcs.h" +#include "help-util.h" #include "lock-util.h" +#include "options.h" #include "path-util.h" #include "pidref.h" -#include "pretty-print.h" #include "process-util.h" #include "signal-util.h" #include "sort-util.h" @@ -33,70 +35,52 @@ STATIC_DESTRUCTOR_REGISTER(arg_backing, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_cmdline, strv_freep); static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("udevadm", "8", &link); + r = option_parser_get_help_table_ns("udevadm-lock", &options); if (r < 0) - return log_oom(); + return r; - printf("%s [OPTIONS...] COMMAND\n" - "%s [OPTIONS...] --print\n" - "\n%sLock a block device and run a command.%s\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -d --device=DEVICE Block device to lock\n" - " -b --backing=FILE File whose backing block device to lock\n" - " -t --timeout=SECS Block at most the specified time waiting for lock\n" - " -p --print Only show which block device the lock would be taken on\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + help_cmdline("lock [OPTIONS...] COMMAND"); + help_cmdline("lock [OPTIONS...] --print"); + help_abstract("Lock a block device and run a command."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "device", required_argument, NULL, 'd' }, - { "backing", required_argument, NULL, 'b' }, - { "timeout", required_argument, NULL, 't' }, - { "print", no_argument, NULL, 'p' }, - {} - }; - - int c, r; + 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, arg_print ? "hVd:b:t:p" : "+hVd:b:t:p", options, NULL)) >= 0) + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "udevadm-lock" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_NAMESPACE("udevadm-lock"): {} + + OPTION_COMMON_HELP: return help(); - case 'V': + OPTION('V', "version", NULL, "Show package version"): return print_version(); - case 'd': - case 'b': { + OPTION('d', "device", "DEVICE", "Block device to lock"): {} /* fall through */ + OPTION('b', "backing", "FILE", "File whose backing block device to lock"): { _cleanup_free_ char *s = NULL; - char ***l = c == 'd' ? &arg_devices : &arg_backing; + char ***l = opts.opt->short_code == 'd' ? &arg_devices : &arg_backing; - r = path_make_absolute_cwd(optarg, &s); + r = path_make_absolute_cwd(opts.arg, &s); if (r < 0) - return log_error_errno(r, "Failed to make path '%s' absolute: %m", optarg); + return log_error_errno(r, "Failed to make path '%s' absolute: %m", opts.arg); path_simplify(s); @@ -107,31 +91,26 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 't': - r = parse_sec(optarg, &arg_timeout_usec); + OPTION('t', "timeout", "SECS", "Block at most the specified time waiting for lock"): + r = parse_sec(opts.arg, &arg_timeout_usec); 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", opts.arg); break; - case 'p': + OPTION('p', "print", NULL, "Only show which block device the lock would be taken on"): arg_print = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + char **args = option_parser_get_args(&opts); if (arg_print) { - if (optind != argc) + if (!strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No arguments expected."); } else { - if (optind + 1 > argc) + if (strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments, command to execute."); - arg_cmdline = strv_copy(argv + optind); + arg_cmdline = strv_copy(args); if (!arg_cmdline) return log_oom(); } @@ -228,7 +207,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..76c9d16d1f963 100644 --- a/src/udev/udevadm-monitor.c +++ b/src/udev/udevadm-monitor.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include - #include "sd-device.h" #include "sd-event.h" @@ -9,8 +7,11 @@ #include "device-monitor-private.h" #include "device-private.h" #include "device-util.h" +#include "format-table.h" #include "format-util.h" #include "hashmap.h" +#include "help-util.h" +#include "options.h" #include "set.h" #include "static-destruct.h" #include "string-util.h" @@ -65,6 +66,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"); @@ -97,60 +100,70 @@ static int setup_monitor(MonitorNetlinkGroup sender, sd_event *event, sd_device_ } static int help(void) { - printf("%s monitor [OPTIONS]\n\n" - "Listen to kernel and udev events.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -p --property Print the event properties\n" - " -k --kernel Print kernel uevents\n" - " -u --udev Print udev events\n" - " -s --subsystem-match=SUBSYSTEM[/DEVTYPE] Filter events by subsystem\n" - " -t --tag-match=TAG Filter events by tag\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + r = option_parser_get_help_table_ns("udevadm-monitor", &options); + if (r < 0) + return r; + + help_cmdline("monitor [OPTIONS]"); + help_abstract("Listen to kernel and udev events."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "property", no_argument, NULL, 'p' }, - { "environment", no_argument, NULL, 'e' }, /* alias for -p */ - { "kernel", no_argument, NULL, 'k' }, - { "udev", no_argument, NULL, 'u' }, - { "subsystem-match", required_argument, NULL, 's' }, - { "tag-match", required_argument, NULL, 't' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; - - int r, c; - - while ((c = getopt_long(argc, argv, "pekus:t:Vh", options, NULL)) >= 0) + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv, .namespace = "udevadm-monitor" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'p': - case 'e': + + OPTION_NAMESPACE("udevadm-monitor"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('e', "environment", NULL, /* help= */ NULL): {} /* hidden alias for -p */ + OPTION('p', "property", NULL, "Print the event properties"): arg_show_property = true; break; - case 'k': + + OPTION('k', "kernel", NULL, "Print kernel uevents"): arg_print_kernel = true; break; - case 'u': + + OPTION('u', "udev", NULL, "Print udev events"): arg_print_udev = true; break; - case 's': { + + OPTION('s', "subsystem-match", "SUBSYSTEM[/DEVTYPE]", + "Filter events by subsystem"): { _cleanup_free_ char *subsystem = NULL, *devtype = NULL; const char *slash; - slash = strchr(optarg, '/'); + slash = strchr(opts.arg, '/'); if (slash) { devtype = strdup(slash + 1); if (!devtype) return log_oom(); - subsystem = strndup(optarg, slash - optarg); + subsystem = strndup(opts.arg, slash - opts.arg); } else - subsystem = strdup(optarg); + subsystem = strdup(opts.arg); if (!subsystem) return log_oom(); @@ -163,20 +176,12 @@ static int parse_argv(int argc, char *argv[]) { TAKE_PTR(devtype); break; } - case 't': - r = set_put_strdup(&arg_tag_filter, optarg); + + OPTION('t', "tag-match", "TAG", "Filter events by tag"): + r = set_put_strdup(&arg_tag_filter, opts.arg); if (r < 0) return log_oom(); break; - - case 'V': - return print_version(); - case 'h': - return help(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } if (!arg_print_kernel && !arg_print_udev) { @@ -187,7 +192,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..77882cc074a6c 100644 --- a/src/udev/udevadm-settle.c +++ b/src/udev/udevadm-settle.c @@ -4,7 +4,6 @@ * Copyright © 2009 Scott James Remnant */ -#include #include #include "sd-bus.h" @@ -14,6 +13,9 @@ #include "alloc-util.h" #include "bus-util.h" +#include "format-table.h" +#include "help-util.h" +#include "options.h" #include "path-util.h" #include "string-util.h" #include "strv.h" @@ -28,60 +30,63 @@ static usec_t arg_timeout_usec = 120 * USEC_PER_SEC; static const char *arg_exists = NULL; static int help(void) { - printf("%s settle [OPTIONS]\n\n" - "Wait for pending udev events.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -t --timeout=SEC Maximum time to wait for events\n" - " -E --exit-if-exists=FILE Stop waiting if file exists\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table_ns("udevadm-settle", &options); + if (r < 0) + return r; + help_cmdline("settle [OPTIONS]"); + help_abstract("Wait for pending udev events."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "timeout", required_argument, NULL, 't' }, - { "exit-if-exists", required_argument, NULL, 'E' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - { "seq-start", required_argument, NULL, 's' }, /* removed */ - { "seq-end", required_argument, NULL, 'e' }, /* removed */ - { "quiet", no_argument, NULL, 'q' }, /* removed */ - {} - }; - - int c, r; - - while ((c = getopt_long(argc, argv, "t:E:Vhs:e:q", options, NULL)) >= 0) { + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv, .namespace = "udevadm-settle" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 't': - r = parse_sec(optarg, &arg_timeout_usec); + + OPTION_NAMESPACE("udevadm-settle"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('t', "timeout", "SEC", "Maximum time to wait for events"): + r = parse_sec(opts.arg, &arg_timeout_usec); if (r < 0) - return log_error_errno(r, "Failed to parse timeout value '%s': %m", optarg); + return log_error_errno(r, "Failed to parse timeout value '%s': %m", opts.arg); break; - case 'E': - if (!path_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path: %s", optarg); - arg_exists = optarg; + OPTION('E', "exit-if-exists", "FILE", "Stop waiting if file exists"): + if (!path_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path: %s", opts.arg); + + arg_exists = opts.arg; break; - case 'V': - return print_version(); - case 'h': - return help(); - case 's': - case 'e': - case 'q': + + OPTION('s', "seq-start", "ARG", NULL): {} /* removed */ + OPTION('e', "seq-end", "ARG", NULL): {} /* removed */ + OPTION('q', "quiet", NULL, NULL): /* removed */ return log_info_errno(SYNTHETIC_ERRNO(EINVAL), "Option -%c no longer supported.", - c); - case '?': - return -EINVAL; - default: - assert_not_reached(); + opts.opt->short_code); } - } return 1; } @@ -184,7 +189,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..3de1366b59f20 100644 --- a/src/udev/udevadm-test-builtin.c +++ b/src/udev/udevadm-test-builtin.c @@ -1,11 +1,11 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include -#include - #include "device-private.h" #include "device-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "udev-builtin.h" #include "udevadm.h" #include "udevadm-util.h" @@ -15,55 +15,61 @@ static const char *arg_command = NULL; static const char *arg_syspath = NULL; 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" - " -a --action=ACTION|help Set action string\n" - "Commands:\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; - udev_builtin_list(); + r = option_parser_get_help_table_ns("udevadm-test-builtin", &options); + if (r < 0) + return r; + help_cmdline("test-builtin [OPTIONS] COMMAND DEVPATH"); + help_abstract("Test a built-in command."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_section("Commands"); + udev_builtin_list(); return 0; } static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "action", required_argument, NULL, 'a' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; + int r; - int r, c; + assert(argc >= 0); + assert(argv); - while ((c = getopt_long(argc, argv, "a:Vh", options, NULL)) >= 0) + OptionParser opts = { argc, argv, .namespace = "udevadm-test-builtin" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'a': - r = parse_device_action(optarg, &arg_action); + + OPTION_NAMESPACE("udevadm-test-builtin"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('a', "action", "ACTION|help", "Set action string"): + r = parse_device_action(opts.arg, &arg_action); if (r <= 0) return r; break; - case 'V': - return print_version(); - case 'h': - return help(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - if (argc != optind + 2) + if (option_parser_get_n_args(&opts) != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected two arguments: command string and device path."); - arg_command = ASSERT_PTR(argv[optind]); - arg_syspath = ASSERT_PTR(argv[optind+1]); + char **args = option_parser_get_args(&opts); + arg_command = ASSERT_PTR(args[0]); + arg_syspath = ASSERT_PTR(args[1]); 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..ba8217c8d36b3 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -3,7 +3,6 @@ * Copyright © 2003-2004 Greg Kroah-Hartman */ -#include #include #include @@ -12,7 +11,10 @@ #include "alloc-util.h" #include "device-private.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "parse-argument.h" #include "static-destruct.h" #include "strv.h" @@ -33,55 +35,59 @@ static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; STATIC_DESTRUCTOR_REGISTER(arg_extra_rules_dir, strv_freep); static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table_ns("udevadm-test", &options); + if (r < 0) + return r; - printf("%s test [OPTIONS] DEVPATH\n\n" - "Test an event run.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -a --action=ACTION|help Set action string\n" - " -N --resolve-names=early|late|never When to resolve names\n" - " -D --extra-rules-dir=DIR Also load rules from the directory\n" - " -v --verbose Show verbose logs\n" - " --json=pretty|short|off Generate JSON output\n", - program_invocation_short_name); + help_cmdline("test [OPTIONS] DEVPATH"); + help_abstract("Test an event run."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_JSON = 0x100, - }; - - static const struct option options[] = { - { "action", required_argument, NULL, 'a' }, - { "resolve-names", required_argument, NULL, 'N' }, - { "extra-rules-dir", required_argument, NULL, 'D' }, - { "verbose", no_argument, NULL, 'v' }, - { "json", required_argument, NULL, ARG_JSON }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - {} - }; - - int r, c; - - while ((c = getopt_long(argc, argv, "a:N:D:vVh", options, NULL)) >= 0) + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv, .namespace = "udevadm-test" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'a': - r = parse_device_action(optarg, &arg_action); + + OPTION_NAMESPACE("udevadm-test"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('a', "action", "ACTION|help", "Set action string"): + r = parse_device_action(opts.arg, &arg_action); if (r <= 0) return r; break; - case 'N': - r = parse_resolve_name_timing(optarg, &arg_resolve_name_timing); + + OPTION_COMMON_RESOLVE_NAMES: + r = parse_resolve_name_timing(opts.arg, &arg_resolve_name_timing); if (r <= 0) return r; break; - case 'D': { + + OPTION('D', "extra-rules-dir", "DIR", "Also load rules from the directory"): { _cleanup_free_ char *p = NULL; - r = parse_path_argument(optarg, /* suppress_root= */ false, &p); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &p); if (r < 0) return r; @@ -90,25 +96,20 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); break; } - case 'v': + + OPTION('v', "verbose", NULL, "Show verbose logs"): arg_verbose = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; break; - case 'V': - return print_version(); - case 'h': - return help(); - case '?': - return -EINVAL; - default: - assert_not_reached(); } - arg_syspath = argv[optind]; + char **args = option_parser_get_args(&opts); + arg_syspath = args[0]; if (!arg_syspath) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "syspath parameter missing."); @@ -129,7 +130,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..e1fdf323cbc20 100644 --- a/src/udev/udevadm-trigger.c +++ b/src/udev/udevadm-trigger.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include #include "sd-device.h" @@ -10,7 +9,10 @@ #include "device-enumerator-private.h" #include "device-private.h" #include "device-util.h" +#include "format-table.h" +#include "help-util.h" #include "id128-util.h" +#include "options.h" #include "set.h" #include "static-destruct.h" #include "string-table.h" @@ -320,221 +322,177 @@ static int setup_matches(sd_device_enumerator *e) { } static int help(void) { - printf("%s trigger [OPTIONS] DEVPATH\n\n" - "Request events from the kernel.\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -v --verbose Print the list of devices while running\n" - " -n --dry-run Do not actually trigger the events\n" - " -q --quiet Suppress error logging in triggering events\n" - " -t --type= Type of events to trigger\n" - " devices sysfs devices (default)\n" - " subsystems sysfs subsystems and drivers\n" - " all sysfs devices, subsystems, and drivers\n" - " -c --action=ACTION|help Event action value, default is \"change\"\n" - " -s --subsystem-match=SUBSYSTEM Trigger devices from a matching subsystem\n" - " -S --subsystem-nomatch=SUBSYSTEM Exclude devices from a matching subsystem\n" - " -a --attr-match=FILE[=VALUE] Trigger devices with a matching attribute\n" - " -A --attr-nomatch=FILE[=VALUE] Exclude devices with a matching attribute\n" - " -p --property-match=KEY=VALUE Trigger devices with a matching property\n" - " -g --tag-match=TAG Trigger devices with a matching tag\n" - " -y --sysname-match=NAME Trigger devices with this /sys path\n" - " --name-match=NAME Trigger devices with this /dev name\n" - " -b --parent-match=NAME Trigger devices with that parent device\n" - " --include-parents Trigger parent devices of found devices\n" - " --initialized-match Trigger devices that are already initialized\n" - " --initialized-nomatch Trigger devices that are not initialized yet\n" - " -w --settle Wait for the triggered events to complete\n" - " --wait-daemon[=SECONDS] Wait for udevd daemon to be initialized\n" - " before triggering uevents\n" - " --uuid Print synthetic uevent UUID\n" - " --prioritized-subsystem=SUBSYSTEM[,SUBSYSTEM…]\n" - " Trigger devices from a matching subsystem first\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table_ns("udevadm-trigger", &options); + if (r < 0) + return r; + + help_cmdline("trigger [OPTIONS] DEVPATH"); + help_abstract("Request events from the kernel."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_NAME = 0x100, - ARG_PING, - ARG_UUID, - ARG_PRIORITIZED_SUBSYSTEM, - ARG_INITIALIZED_MATCH, - ARG_INITIALIZED_NOMATCH, - ARG_INCLUDE_PARENTS, - }; - - static const struct option options[] = { - { "verbose", no_argument, NULL, 'v' }, - { "dry-run", no_argument, NULL, 'n' }, - { "quiet", no_argument, NULL, 'q' }, - { "type", required_argument, NULL, 't' }, - { "action", required_argument, NULL, 'c' }, - { "subsystem-match", required_argument, NULL, 's' }, - { "subsystem-nomatch", required_argument, NULL, 'S' }, - { "attr-match", required_argument, NULL, 'a' }, - { "attr-nomatch", required_argument, NULL, 'A' }, - { "property-match", required_argument, NULL, 'p' }, - { "tag-match", required_argument, NULL, 'g' }, - { "sysname-match", required_argument, NULL, 'y' }, - { "name-match", required_argument, NULL, ARG_NAME }, - { "parent-match", required_argument, NULL, 'b' }, - { "include-parents", no_argument, NULL, ARG_INCLUDE_PARENTS }, - { "initialized-match", no_argument, NULL, ARG_INITIALIZED_MATCH }, - { "initialized-nomatch", no_argument, NULL, ARG_INITIALIZED_NOMATCH }, - { "settle", no_argument, NULL, 'w' }, - { "wait-daemon", optional_argument, NULL, ARG_PING }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, - { "uuid", no_argument, NULL, ARG_UUID }, - { "prioritized-subsystem", required_argument, NULL, ARG_PRIORITIZED_SUBSYSTEM }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "vnqt:c:s:S:a:A:p:g:y:b:wVh", options, NULL)) >= 0) { + OptionParser opts = { argc, argv, .namespace = "udevadm-trigger" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'v': + + OPTION_NAMESPACE("udevadm-trigger"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('v', "verbose", NULL, "Print the list of devices while running"): arg_verbose = true; break; - case 'n': + OPTION('n', "dry-run", NULL, "Do not actually trigger the events"): arg_dry_run = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress error logging in triggering events"): arg_quiet = true; break; - case 't': - arg_scan_type = scan_type_from_string(optarg); + OPTION('t', "type", "TYPE", "Type of sysfs events to trigger:"): {} + OPTION_HELP_VERBATIM(" devices", "- devices (default)"): {} + OPTION_HELP_VERBATIM(" subsystems", "- subsystems and drivers"): {} + OPTION_HELP_VERBATIM(" all", "- devices, subsystems, and drivers"): + arg_scan_type = scan_type_from_string(opts.arg); if (arg_scan_type < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown type --type=%s", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown type --type=%s", opts.arg); break; - case 'c': - r = parse_device_action(optarg, &arg_action); + OPTION('c', "action", "ACTION|help", "Event action value, default is \"change\""): + r = parse_device_action(opts.arg, &arg_action); if (r <= 0) return r; break; - case 's': - r = strv_extend(&arg_subsystem_match, optarg); + OPTION('s', "subsystem-match", "SUBSYSTEM", + "Trigger devices from a matching subsystem"): + r = strv_extend(&arg_subsystem_match, opts.arg); if (r < 0) return log_oom(); break; - case 'S': - r = strv_extend(&arg_subsystem_nomatch, optarg); + OPTION('S', "subsystem-nomatch", "SUBSYSTEM", + "Exclude devices from a matching subsystem"): + r = strv_extend(&arg_subsystem_nomatch, opts.arg); if (r < 0) return log_oom(); break; - case 'a': - r = strv_extend(&arg_attr_match, optarg); + OPTION('a', "attr-match", "FILE[=VALUE]", + "Trigger devices with a matching attribute"): + r = strv_extend(&arg_attr_match, opts.arg); if (r < 0) return log_oom(); break; - case 'A': - r = strv_extend(&arg_attr_nomatch, optarg); + OPTION('A', "attr-nomatch", "FILE[=VALUE]", + "Exclude devices with a matching attribute"): + r = strv_extend(&arg_attr_nomatch, opts.arg); if (r < 0) return log_oom(); break; - case 'p': - r = strv_extend(&arg_property_match, optarg); + OPTION('p', "property-match", "KEY=VALUE", + "Trigger devices with a matching property"): + r = strv_extend(&arg_property_match, opts.arg); if (r < 0) return log_oom(); break; - case 'g': - r = strv_extend(&arg_tag_match, optarg); + OPTION('g', "tag-match", "TAG", "Trigger devices with a matching tag"): + r = strv_extend(&arg_tag_match, opts.arg); if (r < 0) return log_oom(); break; - case 'y': - r = strv_extend(&arg_sysname_match, optarg); + OPTION('y', "sysname-match", "NAME", "Trigger devices with this /sys path"): + r = strv_extend(&arg_sysname_match, opts.arg); if (r < 0) return log_oom(); break; - case 'b': - r = strv_extend(&arg_parent_match, optarg); + OPTION_LONG("name-match", "NAME", "Trigger devices with this /dev name"): + r = strv_extend(&arg_name_match, opts.arg); if (r < 0) return log_oom(); break; - case ARG_INCLUDE_PARENTS: + OPTION('b', "parent-match", "NAME", "Trigger devices with that parent device"): + r = strv_extend(&arg_parent_match, opts.arg); + if (r < 0) + return log_oom(); + break; + + OPTION_LONG("include-parents", NULL, "Trigger parent devices of found devices"): arg_include_parents = true; break; - case 'w': - arg_settle = true; + OPTION_LONG("initialized-match", NULL, + "Trigger devices that are already initialized"): + arg_initialized_match = MATCH_INITIALIZED_YES; break; - case ARG_NAME: - r = strv_extend(&arg_name_match, optarg); - if (r < 0) - return log_oom(); + OPTION_LONG("initialized-nomatch", NULL, + "Trigger devices that are not initialized yet"): + arg_initialized_match = MATCH_INITIALIZED_NO; + break; + + OPTION('w', "settle", NULL, "Wait for the triggered events to complete"): + arg_settle = true; break; - case ARG_PING: + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "wait-daemon", "SECONDS", + "Wait for udevd daemon to be initialized before triggering uevents"): arg_ping = true; - if (optarg) { - r = parse_sec(optarg, &arg_ping_timeout_usec); + if (opts.arg) { + r = parse_sec(opts.arg, &arg_ping_timeout_usec); if (r < 0) - log_error_errno(r, "Failed to parse timeout value '%s', ignoring: %m", optarg); + log_error_errno(r, "Failed to parse timeout value '%s', ignoring: %m", opts.arg); } break; - case ARG_UUID: + OPTION_LONG("uuid", NULL, "Print synthetic uevent UUID"): arg_uuid = true; break; - case ARG_PRIORITIZED_SUBSYSTEM: - r = strv_split_and_extend(&arg_prioritized_subsystems, optarg, ",", /* filter_duplicates= */ false); + OPTION_LONG("prioritized-subsystem", "SUBSYSTEM[,SUBSYSTEM…]", + "Trigger devices from a matching subsystem first"): + r = strv_split_and_extend(&arg_prioritized_subsystems, opts.arg, ",", /* filter_duplicates= */ false); if (r < 0) return log_oom(); break; - - case ARG_INITIALIZED_MATCH: - arg_initialized_match = MATCH_INITIALIZED_YES; - break; - - case ARG_INITIALIZED_NOMATCH: - arg_initialized_match = MATCH_INITIALIZED_NO; - break; - - case 'V': - return print_version(); - - case 'h': - return help(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } - r = strv_extend_strv(&arg_devices, argv + optind, /* filter_duplicates= */ false); + r = strv_extend_strv(&arg_devices, option_parser_get_args(&opts), /* filter_duplicates= */ false); if (r < 0) return log_error_errno(r, "Failed to build argument list: %m"); 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..8e90946960cef 100644 --- a/src/udev/udevadm-util.c +++ b/src/udev/udevadm-util.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include - #include "sd-bus.h" #include "alloc-util.h" @@ -144,7 +142,7 @@ int parse_resolve_name_timing(const char *str, ResolveNameTiming *ret) { if (streq(str, "help")) return DUMP_STRING_TABLE(resolve_name_timing, ResolveNameTiming, _RESOLVE_NAME_TIMING_MAX); - ResolveNameTiming v = resolve_name_timing_from_string(optarg); + ResolveNameTiming v = resolve_name_timing_from_string(str); if (v < 0) return log_error_errno(v, "--resolve-names= must be 'early', 'late', or 'never'."); @@ -302,7 +300,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 +309,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 +319,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..e7f803dfb1eac 100644 --- a/src/udev/udevadm-verify.c +++ b/src/udev/udevadm-verify.c @@ -1,16 +1,17 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include #include "alloc-util.h" +#include "ansi-color.h" #include "conf-files.h" #include "errno-util.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" +#include "options.h" #include "parse-argument.h" -#include "pretty-print.h" #include "static-destruct.h" -#include "strv.h" #include "udev-rules.h" #include "udevadm.h" #include "udevadm-util.h" @@ -23,81 +24,66 @@ static bool arg_style = true; STATIC_DESTRUCTOR_REGISTER(arg_root, freep); static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("udevadm", "8", &link); + r = option_parser_get_help_table_ns("udevadm-verify", &options); if (r < 0) - return log_oom(); - - printf("%s verify [OPTIONS] [FILE...]\n" - "\n%sVerify udev rules files.%s\n\n" - " -h --help Show this help\n" - " -V --version Show package version\n" - " -N --resolve-names=early|late|never When to resolve names\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --no-summary Do not show summary\n" - " --no-style Ignore style issues\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + return r; + + help_cmdline("verify [OPTIONS] [FILE...]"); + help_abstract("Verify udev rules files."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ROOT = 0x100, - ARG_NO_SUMMARY, - ARG_NO_STYLE, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "resolve-names", required_argument, NULL, 'N' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "no-summary", no_argument, NULL, ARG_NO_SUMMARY }, - { "no-style", no_argument, NULL, ARG_NO_STYLE }, - {} - }; - - int r, c; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); + + OptionParser opts = { argc, argv, .namespace = "udevadm-verify" }; - while ((c = getopt_long(argc, argv, "hVN:", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + + OPTION_NAMESPACE("udevadm-verify"): {} + + OPTION_COMMON_HELP: return help(); - case 'V': + + OPTION('V', "version", NULL, "Show package version"): return print_version(); - case 'N': - r = parse_resolve_name_timing(optarg, &arg_resolve_name_timing); + + OPTION_COMMON_RESOLVE_NAMES: + r = parse_resolve_name_timing(opts.arg, &arg_resolve_name_timing); if (r <= 0) return r; 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(opts.arg, /* suppress_root= */ true, &arg_root); if (r < 0) return r; break; - case ARG_NO_SUMMARY: + + OPTION_LONG("no-summary", NULL, "Do not show summary"): arg_summary = false; break; - case ARG_NO_STYLE: + OPTION_LONG("no-style", NULL, "Ignore style issues"): arg_style = false; break; - - case '?': - return -EINVAL; - default: - assert_not_reached(); } + *remaining_args = option_parser_get_args(&opts); return 1; } @@ -156,11 +142,12 @@ 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; + char **args = NULL; int r; - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -171,9 +158,9 @@ 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); + r = search_rules_files(args, arg_root, &files, &n_files); if (r < 0) return r; diff --git a/src/udev/udevadm-wait.c b/src/udev/udevadm-wait.c index 70b14a7e91e91..fa12e6c98c17c 100644 --- a/src/udev/udevadm-wait.c +++ b/src/udev/udevadm-wait.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include -#include #include #include @@ -10,7 +8,10 @@ #include "device-monitor-private.h" #include "device-util.h" #include "event-util.h" +#include "format-table.h" #include "fs-util.h" +#include "help-util.h" +#include "options.h" #include "parse-util.h" #include "path-util.h" #include "static-destruct.h" @@ -297,86 +298,79 @@ static int setup_periodic_timer(sd_event *event) { } static int help(void) { - printf("%s wait [OPTIONS] DEVICE [DEVICE…]\n\n" - "Wait for devices or device symlinks being created.\n\n" - " -h --help Print this message\n" - " -V --version Print version of the program\n" - " -t --timeout=SEC Maximum time to wait for the device\n" - " --initialized=BOOL Wait for devices being initialized by systemd-udevd\n" - " --removed Wait for devices being removed\n" - " --settle Also wait for all queued events being processed\n", - program_invocation_short_name); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table_ns("udevadm-wait", &options); + if (r < 0) + return r; + + help_cmdline("wait [OPTIONS] DEVICE [DEVICE…]"); + help_abstract("Wait for devices or device symlinks being created."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("udevadm", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_INITIALIZED = 0x100, - ARG_REMOVED, - ARG_SETTLE, - }; - - static const struct option options[] = { - { "timeout", required_argument, NULL, 't' }, - { "initialized", required_argument, NULL, ARG_INITIALIZED }, - { "removed", no_argument, NULL, ARG_REMOVED }, - { "settle", no_argument, NULL, ARG_SETTLE }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - {} - }; - - int c, r; - - while ((c = getopt_long(argc, argv, "t:hV", options, NULL)) >= 0) + int r; + + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv, .namespace = "udevadm-wait" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 't': - r = parse_sec(optarg, &arg_timeout_usec); + + OPTION_NAMESPACE("udevadm-wait"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION('V', "version", NULL, "Show package version"): + return print_version(); + + OPTION('t', "timeout", "SEC", "Maximum time to wait for the device"): + r = parse_sec(opts.arg, &arg_timeout_usec); if (r < 0) - return log_error_errno(r, "Failed to parse -t/--timeout= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse -t/--timeout= parameter: %s", opts.arg); break; - case ARG_INITIALIZED: - r = parse_boolean(optarg); + OPTION_LONG("initialized", "BOOL", + "Wait for devices being initialized by systemd-udevd"): + r = parse_boolean(opts.arg); if (r < 0) - return log_error_errno(r, "Failed to parse --initialized= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --initialized= parameter: %s", opts.arg); arg_wait_until = r ? WAIT_UNTIL_INITIALIZED : WAIT_UNTIL_ADDED; break; - case ARG_REMOVED: + OPTION_LONG("removed", NULL, "Wait for devices being removed"): arg_wait_until = WAIT_UNTIL_REMOVED; break; - case ARG_SETTLE: + OPTION_LONG("settle", NULL, "Also wait for all queued events being processed"): arg_settle = true; break; - - case 'V': - return print_version(); - - case 'h': - return help(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind >= argc) + char **args = option_parser_get_args(&opts); + if (strv_isempty(args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments, expected at least one device path or device symlink."); - arg_devices = strv_copy(argv + optind); + arg_devices = strv_copy(args); if (!arg_devices) return log_oom(); 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..9b7b09370a97f 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -1,87 +1,93 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -#include #include -#include "alloc-util.h" #include "argv-util.h" +#include "format-table.h" +#include "help-util.h" #include "label-util.h" #include "main-func.h" -#include "pretty-print.h" +#include "options.h" #include "udev-util.h" #include "udevadm.h" #include "udevd.h" #include "verbs.h" static int help(void) { - static const char *const short_descriptions[][2] = { - { "info", "Query sysfs or the udev database" }, - { "trigger", "Request events from the kernel" }, - { "settle", "Wait for pending udev events" }, - { "control", "Control the udev daemon" }, - { "monitor", "Listen to kernel and udev events" }, - { "test", "Test an event run" }, - { "test-builtin", "Test a built-in command" }, - { "verify", "Verify udev rules files" }, - { "cat", "Show udev rules files" }, - { "wait", "Wait for device or device symlink" }, - { "lock", "Lock a block device" }, - }; - - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; - r = terminal_urlify_man("udevadm", "8", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; + + r = option_parser_get_help_table_ns("udevadm", &options); + if (r < 0) + return r; - printf("%s [--help] [--version] [--debug] COMMAND [COMMAND OPTIONS]\n\n" - "Send control commands or test the device manager.\n\n" - "Commands:\n", - program_invocation_short_name); + (void) table_sync_column_widths(0, verbs, options); - FOREACH_ELEMENT(desc, short_descriptions) - printf(" %-12s %s\n", (*desc)[0], (*desc)[1]); + help_cmdline("[OPTIONS…] COMMAND [COMMAND OPTIONS…]"); + help_abstract("Send control commands or test the device manager."); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; - printf("\nSee the %s for details.\n", link); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("udevadm", "8"); return 0; } -static int parse_argv(int argc, char *argv[]) { - static const struct option options[] = { - { "debug", no_argument, NULL, 'd' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - {} - }; - int c; +VERB_COMMON_HELP(help); + +VERB_SCOPE(, verb_info_main, "info", "[DEVPATH|FILE]", VERB_ANY, VERB_ANY, 0, "Query sysfs or the udev database"); +VERB_SCOPE(, verb_trigger_main, "trigger", "DEVPATH", VERB_ANY, VERB_ANY, 0, "Request events from the kernel"); +VERB_SCOPE(, verb_settle_main, "settle", NULL, VERB_ANY, VERB_ANY, 0, "Wait for pending udev events"); +VERB_SCOPE(, verb_control_main, "control", "OPTION", VERB_ANY, VERB_ANY, 0, "Control the udev daemon"); +VERB_SCOPE(, verb_monitor_main, "monitor", NULL, VERB_ANY, VERB_ANY, 0, "Listen to kernel and udev events"); +VERB_SCOPE(, verb_test_main, "test", "DEVPATH", VERB_ANY, VERB_ANY, 0, "Test an event run"); +VERB_SCOPE(, verb_builtin_main, "test-builtin", "COMMAND DEVPATH", VERB_ANY, VERB_ANY, 0, "Test a built-in command"); +VERB_SCOPE(, verb_verify_main, "verify", "[FILE…]", VERB_ANY, VERB_ANY, 0, "Verify udev rules files"); +VERB_SCOPE(, verb_cat_main, "cat", "[FILE…]", VERB_ANY, VERB_ANY, 0, "Show udev rules files"); +VERB_SCOPE(, verb_wait_main, "wait", "DEVICE [DEVICE…]", VERB_ANY, VERB_ANY, 0, "Wait for device or device symlink"); +VERB_SCOPE(, verb_lock_main, "lock", "[OPTIONS…] COMMAND", VERB_ANY, VERB_ANY, 0, "Lock a block device"); +VERB_SCOPE(, verb_hwdb_main, "hwdb", NULL, VERB_ANY, VERB_ANY, 0, /* help= */ NULL); /* deprecated */ + +VERB_NOARG(verb_version_main, "version", /* help= */ NULL); +static int verb_version_main(int argc, char *argv[], uintptr_t _data, void *userdata) { + return print_version(); +} +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); + + OptionParser opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION, "udevadm" }; - /* 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, "+dhV", options, NULL)) >= 0) + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'd': - log_set_max_level(LOG_DEBUG); - break; + OPTION_NAMESPACE("udevadm"): {} - case 'h': + OPTION_COMMON_HELP: return help(); - case 'V': + OPTION_COMMON_VERSION_WITH_HIDDEN_V: return print_version(); - case '?': - return -EINVAL; - - default: - assert_not_reached(); + OPTION('d', "debug", NULL, "Enable debug logging"): + log_set_max_level(LOG_DEBUG); + break; } + *remaining_args = option_parser_get_args(&opts); return 1; /* work to do */ } @@ -91,37 +97,8 @@ int print_version(void) { return 0; } -static int version_main(int argc, char *argv[], void *userdata) { - return print_version(); -} - -static int help_main(int argc, char *argv[], 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 }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { + char **args = NULL; int r; if (invoked_as(argv, "udevd")) @@ -130,7 +107,7 @@ static int run(int argc, char *argv[]) { (void) udev_parse_config(); log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -138,7 +115,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - return udevadm_main(argc, argv); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); 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..4cbc481c7681d 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, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + (void) DLOPEN_LIBBLKID(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + (void) DLOPEN_LIBKMOD(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + (void) DLOPEN_LIBMOUNT(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); + (void) DLOPEN_TPM2(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED); 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..f1f40324de7f7 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,54 @@ #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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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(&opts); + 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 +84,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/test/meson.build b/src/ukify/test/meson.build index 68be54e228213..a5c7bbb37c33b 100644 --- a/src/ukify/test/meson.build +++ b/src/ukify/test/meson.build @@ -12,10 +12,15 @@ if want_ukify and want_tests != 'false' args += ['--flakes'] endif + efi_addon_for_test = '' + if not meson.is_cross_build() and efi_addon.length() > 0 + efi_addon_for_test = efi_addon[0].full_path() + endif + test('test-ukify', files('test_ukify.py'), args: args, - env : test_env + {'EFI_ADDON' : efi_addon.length() > 0 ? efi_addon[0].full_path() : ''}, + env : test_env + {'EFI_ADDON' : efi_addon_for_test}, timeout : 120, suite : 'ukify', depends : efi_addon) diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py index 224a38569f280..11f843ddb6015 100755 --- a/src/ukify/test/test_ukify.py +++ b/src/ukify/test/test_ukify.py @@ -33,8 +33,7 @@ sys.exit(77) try: - # pyflakes: noqa - import pefile # noqa + import pefile except ImportError as e: print(str(e), file=sys.stderr) sys.exit(77) @@ -59,42 +58,51 @@ slow_tests = True arg_tools = ['--tools', build_root] if build_root else [] -if build_root and ( - p := pathlib.Path(f"{build_root}/linux{ukify.guess_efi_arch()}.efi.stub") -).exists(): +if ( + build_root + and (p := pathlib.Path(f'{build_root}/linux{ukify.guess_efi_arch()}.efi.stub')).exists() +): # fmt: skip arg_tools += ['--stub', p] + def systemd_measure(): opts = ukify.create_parser().parse_args(arg_tools) return ukify.find_tool('systemd-measure', opts=opts) + def test_guess_efi_arch(): arch = ukify.guess_efi_arch() assert arch in ukify.EFI_ARCHES + def test_shell_join(): assert ukify.shell_join(['a', 'b', ' ']) == "a b ' '" + def test_round_up(): assert ukify.round_up(0) == 0 assert ukify.round_up(4095) == 4096 assert ukify.round_up(4096) == 4096 assert ukify.round_up(4097) == 8192 + def test_namespace_creation(): ns = ukify.create_parser().parse_args(()) assert ns.linux is None assert ns.initrd is None + def test_config_example(): ex = ukify.config_example() assert '[UKI]' in ex assert 'Splash = BMP' in ex + def test_apply_config(tmp_path): config = tmp_path / 'config1.conf' - config.write_text(textwrap.dedent( - f''' + config.write_text( + textwrap.dedent( + f''' [UKI] Linux = LINUX Initrd = initrd1 initrd2 @@ -117,7 +125,9 @@ def test_apply_config(tmp_path): PCRPrivateKey = some/path7 PCRPublicKey = some/path8 Phases = {':'.join(ukify.KNOWN_PHASES)} - ''')) + ''' + ) + ) ns = ukify.create_parser().parse_args(['build']) ns.linux = None @@ -125,9 +135,11 @@ def test_apply_config(tmp_path): ukify.apply_config(ns, config) assert ns.linux == pathlib.Path('LINUX') - assert ns.initrd == [pathlib.Path('initrd1'), - pathlib.Path('initrd2'), - pathlib.Path('initrd3')] + assert ns.initrd == [ + pathlib.Path('initrd1'), + pathlib.Path('initrd2'), + pathlib.Path('initrd3'), + ] assert ns.cmdline == '1 2 3 4 5\n6 7 8' assert ns.os_release == '@some/path1' assert ns.devicetree == pathlib.Path('some/path2') @@ -148,9 +160,11 @@ def test_apply_config(tmp_path): ukify.finalize_options(ns) assert ns.linux == pathlib.Path('LINUX') - assert ns.initrd == [pathlib.Path('initrd1'), - pathlib.Path('initrd2'), - pathlib.Path('initrd3')] + assert ns.initrd == [ + pathlib.Path('initrd1'), + pathlib.Path('initrd2'), + pathlib.Path('initrd3'), + ] assert ns.cmdline == '1 2 3 4 5 6 7 8' assert ns.os_release == pathlib.Path('some/path1') assert ns.devicetree == pathlib.Path('some/path2') @@ -168,6 +182,7 @@ def test_apply_config(tmp_path): assert ns.pcr_public_keys == ['some/path8'] assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] + def test_parse_args_minimal(): with pytest.raises(ValueError): ukify.parse_args([]) @@ -175,32 +190,39 @@ def test_parse_args_minimal(): opts = ukify.parse_args('arg1 arg2'.split()) assert opts.linux == pathlib.Path('arg1') assert opts.initrd == [pathlib.Path('arg2')] - assert opts.os_release in (pathlib.Path('/etc/os-release'), - pathlib.Path('/usr/lib/os-release')) + assert opts.os_release in ( + pathlib.Path('/etc/os-release'), + pathlib.Path('/usr/lib/os-release'), + ) + def test_parse_args_many_deprecated(): opts = ukify.parse_args( - ['/ARG1', '///ARG2', '/ARG3 WITH SPACE', - '--cmdline=a b c', - '--os-release=K1=V1\nK2=V2', - '--devicetree=DDDDTTTT', - '--splash=splash', - '--pcrpkey=PATH', - '--uname=1.2.3', - '--stub=STUBPATH', - '--pcr-private-key=PKEY1', - '--pcr-public-key=PKEY2', - '--pcr-banks=SHA1,SHA256', - '--signing-engine=ENGINE', - '--secureboot-private-key=SBKEY', - '--secureboot-certificate=SBCERT', - '--sign-kernel', - '--no-sign-kernel', - '--tools=TOOLZ///', - '--output=OUTPUT', - '--measure', - '--no-measure', - ]) + [ + '/ARG1', + '///ARG2', + '/ARG3 WITH SPACE', + '--cmdline=a b c', + '--os-release=K1=V1\nK2=V2', + '--devicetree=DDDDTTTT', + '--splash=splash', + '--pcrpkey=PATH', + '--uname=1.2.3', + '--stub=STUBPATH', + '--pcr-private-key=PKEY1', + '--pcr-public-key=PKEY2', + '--pcr-banks=SHA1,SHA256', + '--signing-engine=ENGINE', + '--secureboot-private-key=SBKEY', + '--secureboot-certificate=SBCERT', + '--sign-kernel', + '--no-sign-kernel', + '--tools=TOOLZ///', + '--output=OUTPUT', + '--measure', + '--no-measure', + ] + ) assert opts.linux == pathlib.Path('/ARG1') assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')] assert opts.cmdline == 'a b c' @@ -221,34 +243,37 @@ def test_parse_args_many_deprecated(): assert opts.output == pathlib.Path('OUTPUT') assert opts.measure is False + def test_parse_args_many(): opts = ukify.parse_args( - ['build', - '--linux=/ARG1', - '--initrd=///ARG2', - '--initrd=/ARG3 WITH SPACE', - '--cmdline=a b c', - '--os-release=K1=V1\nK2=V2', - '--devicetree=DDDDTTTT', - '--splash=splash', - '--pcrpkey=PATH', - '--uname=1.2.3', - '--stub=STUBPATH', - '--pcr-private-key=PKEY1', - '--pcr-public-key=PKEY2', - '--pcr-banks=SHA1,SHA256', - '--signing-engine=ENGINE', - '--secureboot-private-key=SBKEY', - '--secureboot-certificate=SBCERT', - '--sign-kernel', - '--no-sign-kernel', - '--tools=TOOLZ///', - '--output=OUTPUT', - '--measure', - '--no-measure', - '--policy-digest', - '--no-policy-digest', - ]) + [ + 'build', + '--linux=/ARG1', + '--initrd=///ARG2', + '--initrd=/ARG3 WITH SPACE', + '--cmdline=a b c', + '--os-release=K1=V1\nK2=V2', + '--devicetree=DDDDTTTT', + '--splash=splash', + '--pcrpkey=PATH', + '--uname=1.2.3', + '--stub=STUBPATH', + '--pcr-private-key=PKEY1', + '--pcr-public-key=PKEY2', + '--pcr-banks=SHA1,SHA256', + '--signing-engine=ENGINE', + '--secureboot-private-key=SBKEY', + '--secureboot-certificate=SBCERT', + '--sign-kernel', + '--no-sign-kernel', + '--tools=TOOLZ///', + '--output=OUTPUT', + '--measure', + '--no-measure', + '--policy-digest', + '--no-policy-digest', + ] + ) assert opts.linux == pathlib.Path('/ARG1') assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')] assert opts.cmdline == 'a b c' @@ -270,14 +295,17 @@ def test_parse_args_many(): assert opts.measure is False assert opts.policy_digest is False + def test_parse_sections(): opts = ukify.parse_args( - ['build', - '--linux=/ARG1', - '--initrd=/ARG2', - '--section=test:TESTTESTTEST', - '--section=test2:@FILE', - ]) + [ + 'build', + '--linux=/ARG1', + '--initrd=/ARG2', + '--section=test:TESTTESTTEST', + '--section=test2:@FILE', + ] + ) assert opts.linux == pathlib.Path('/ARG1') assert opts.initrd == [pathlib.Path('/ARG2')] @@ -293,11 +321,13 @@ def test_parse_sections(): assert opts.sections[1].tmpfile is None assert opts.sections[1].measure is False + def test_config_priority(tmp_path): config = tmp_path / 'config1.conf' # config: use pesign and give certdir + certname - config.write_text(textwrap.dedent( - f''' + config.write_text( + textwrap.dedent( + f''' [UKI] Linux = LINUX Initrd = initrd1 initrd2 @@ -321,44 +351,50 @@ def test_config_priority(tmp_path): PCRPrivateKey = some/path7 PCRPublicKey = some/path8 Phases = {':'.join(ukify.KNOWN_PHASES)} - ''')) + ''' + ) + ) # args: use sbsign and give key + cert, should override pesign opts = ukify.parse_args( - ['build', - '--linux=/ARG1', - '--initrd=///ARG2', - '--initrd=/ARG3 WITH SPACE', - '--cmdline= a b c ', - '--os-release=K1=V1\nK2=V2', - '--devicetree=DDDDTTTT', - '--splash=splash', - '--pcrpkey=PATH', - '--uname=1.2.3', - '--stub=STUBPATH', - '--pcr-private-key=PKEY1', - '--pcr-public-key=PKEY2', - '--pcr-banks=SHA1,SHA256', - '--signing-engine=ENGINE', - '--signtool=sbsign', - '--secureboot-private-key=SBKEY', - '--secureboot-certificate=SBCERT', - '--sign-kernel', - '--no-sign-kernel', - '--tools=TOOLZ///', - '--output=OUTPUT', - '--measure', - ]) + [ + 'build', + '--linux=/ARG1', + '--initrd=///ARG2', + '--initrd=/ARG3 WITH SPACE', + '--cmdline= a b c ', + '--os-release=K1=V1\nK2=V2', + '--devicetree=DDDDTTTT', + '--splash=splash', + '--pcrpkey=PATH', + '--uname=1.2.3', + '--stub=STUBPATH', + '--pcr-private-key=PKEY1', + '--pcr-public-key=PKEY2', + '--pcr-banks=SHA1,SHA256', + '--signing-engine=ENGINE', + '--signtool=sbsign', + '--secureboot-private-key=SBKEY', + '--secureboot-certificate=SBCERT', + '--sign-kernel', + '--no-sign-kernel', + '--tools=TOOLZ///', + '--output=OUTPUT', + '--measure', + ] + ) ukify.apply_config(opts, config) ukify.finalize_options(opts) assert opts.linux == pathlib.Path('/ARG1') - assert opts.initrd == [pathlib.Path('initrd1'), - pathlib.Path('initrd2'), - pathlib.Path('initrd3'), - pathlib.Path('/ARG2'), - pathlib.Path('/ARG3 WITH SPACE')] + assert opts.initrd == [ + pathlib.Path('initrd1'), + pathlib.Path('initrd2'), + pathlib.Path('initrd3'), + pathlib.Path('/ARG2'), + pathlib.Path('/ARG3 WITH SPACE'), + ] assert opts.cmdline == 'a b c' assert opts.os_release == 'K1=V1\nK2=V2' assert opts.devicetree == pathlib.Path('DDDDTTTT') @@ -370,16 +406,17 @@ def test_config_priority(tmp_path): assert opts.pcr_public_keys == ['PKEY2', 'some/path8'] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' - assert opts.signtool == 'sbsign' # from args - assert opts.sb_key == 'SBKEY' # from args - assert opts.sb_cert == pathlib.Path('SBCERT') # from args - assert opts.sb_certdir == 'some/path5' # from config - assert opts.sb_cert_name == 'some/name1' # from config + assert opts.signtool == 'sbsign' # from args + assert opts.sb_key == 'SBKEY' # from args + assert opts.sb_cert == pathlib.Path('SBCERT') # from args + assert opts.sb_certdir == 'some/path5' # from config + assert opts.sb_cert_name == 'some/name1' # from config assert opts.sign_kernel is False assert opts.tools == [pathlib.Path('TOOLZ/')] assert opts.output == pathlib.Path('OUTPUT') assert opts.measure is True + def test_help(capsys): with pytest.raises(SystemExit): ukify.parse_args(['--help']) @@ -387,6 +424,7 @@ def test_help(capsys): assert '--section' in out.out assert not out.err + def test_help_display(capsys): with pytest.raises(SystemExit): ukify.parse_args(['inspect', '--help']) @@ -394,6 +432,7 @@ def test_help_display(capsys): assert '--section' in out.out assert not out.err + def test_help_error_deprecated(capsys): with pytest.raises(SystemExit): ukify.parse_args(['a', 'b', '--no-such-option']) @@ -402,6 +441,7 @@ def test_help_error_deprecated(capsys): assert '--no-such-option' in out.err assert len(out.err.splitlines()) == 1 + def test_help_error(capsys): with pytest.raises(SystemExit): ukify.parse_args(['build', '--no-such-option']) @@ -410,11 +450,15 @@ def test_help_error(capsys): assert '--no-such-option' in out.err assert len(out.err.splitlines()) == 1 + @pytest.fixture(scope='session') def kernel_initrd(): items = sorted(glob.glob('/lib/modules/*/vmlinuz')) if not items: items = sorted(glob.glob('/boot/vmlinuz*')) + # Drop entries we cannot read (e.g. /boot/vmlinuz.old with mode 0600 on + # GitHub-hosted runners), the test opens the file later and would fail. + items = [p for p in items if os.access(p, os.R_OK)] if not items: return None @@ -426,6 +470,7 @@ def kernel_initrd(): # We don't look _into_ the initrd. Any file is OK. return ['--linux', linux, '--initrd', ukify.__file__] + def test_check_splash(): try: # pyflakes: noqa @@ -436,16 +481,19 @@ def test_check_splash(): with pytest.raises(OSError): ukify.check_splash(os.devnull) + def test_basic_operation(kernel_initrd, tmp_path): if kernel_initrd is None: pytest.skip('linux+initrd not found') output = f'{tmp_path}/basic.efi' - opts = ukify.parse_args([ - 'build', - *kernel_initrd, - f'--output={output}', - ]) + opts = ukify.parse_args( + [ + 'build', + *kernel_initrd, + f'--output={output}', + ] + ) try: ukify.check_inputs(opts) except OSError as e: @@ -458,20 +506,23 @@ def test_basic_operation(kernel_initrd, tmp_path): shutil.rmtree(tmp_path) + def test_sections(kernel_initrd, tmp_path): if kernel_initrd is None: pytest.skip('linux+initrd not found') output = f'{tmp_path}/basic.efi' - opts = ukify.parse_args([ - 'build', - *kernel_initrd, - f'--output={output}', - '--uname=1.2.3', - '--cmdline=ARG1 ARG2 ARG3', - '--os-release=K1=V1\nK2=V2\n', - '--section=.test:CONTENTZ', - ]) + opts = ukify.parse_args( + [ + 'build', + *kernel_initrd, + f'--output={output}', + '--uname=1.2.3', + '--cmdline=ARG1 ARG2 ARG3', + '--os-release=K1=V1\nK2=V2\n', + '--section=.test:CONTENTZ', + ] + ) try: ukify.check_inputs(opts) @@ -484,24 +535,25 @@ def test_sections(kernel_initrd, tmp_path): dump = subprocess.check_output(['objdump', '-h', output], text=True) for sect in 'text osrel cmdline linux initrd uname test'.split(): - assert re.search(fr'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) + assert re.search(rf'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) shutil.rmtree(tmp_path) + def test_addon(tmp_path): output = f'{tmp_path}/addon.efi' args = [ 'build', f'--output={output}', '--cmdline=ARG1 ARG2 ARG3', - """--sbat=sbat,1,foo + '''--sbat=sbat,1,foo foo,1 bar,2 -""", +''', '--section=.test:CONTENTZ', - """--sbat=sbat,1,foo + '''--sbat=sbat,1,foo baz,3 -""" +''', ] if stub := os.getenv('EFI_ADDON'): args += [f'--stub={stub}'] @@ -521,26 +573,33 @@ def test_addon(tmp_path): dump = subprocess.check_output(['objdump', '-h', output], text=True) for sect in 'text cmdline test sbat'.split(): - assert re.search(fr'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) + assert re.search(rf'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) pe = pefile.PE(output, fast_load=True) found = False for section in pe.sections: - if section.Name.rstrip(b"\x00").decode() == ".sbat": + if section.Name.rstrip(b'\x00').decode() == '.sbat': assert found is False - split = section.get_data().rstrip(b"\x00").decode().splitlines() - assert split == ["sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md", "foo,1", "bar,2", "baz,3"] + split = section.get_data().rstrip(b'\x00').decode().splitlines() + assert split == [ + 'sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md', + 'foo,1', + 'bar,2', + 'baz,3', + ] found = True assert found is True + def unbase64(filename): tmp = tempfile.NamedTemporaryFile() base64.decode(filename.open('rb'), tmp) tmp.flush() return tmp + def test_uname_scraping(kernel_initrd): if kernel_initrd is None: pytest.skip('linux+initrd not found') @@ -549,8 +608,9 @@ def test_uname_scraping(kernel_initrd): uname = ukify.Uname.scrape(kernel_initrd[1]) assert re.match(r'\d+\.\d+\.\d+', uname) + @pytest.mark.skipif(not slow_tests, reason='slow') -@pytest.mark.parametrize("days", [365*10, None]) +@pytest.mark.parametrize('days', [365 * 10, None]) def test_efi_signing_sbsign(days, kernel_initrd, tmp_path): if kernel_initrd is None: pytest.skip('linux+initrd not found') @@ -585,16 +645,20 @@ def test_efi_signing_sbsign(days, kernel_initrd, tmp_path): if shutil.which('sbverify'): # let's check that sbverify likes the resulting file - dump = subprocess.check_output([ - 'sbverify', - '--cert', cert.name, - output, - ], text=True) + dump = subprocess.check_output( + [ + 'sbverify', + '--cert', cert.name, + output, + ], + text=True, + ) # fmt: skip assert 'Signature verification OK' in dump shutil.rmtree(tmp_path) + @pytest.mark.skipif(not slow_tests, reason='slow') def test_efi_signing_pesign(kernel_initrd, tmp_path): if kernel_initrd is None: @@ -613,16 +677,18 @@ def test_efi_signing_pesign(kernel_initrd, tmp_path): subprocess.check_call(cmd) output = f'{tmp_path}/signed.efi' - opts = ukify.parse_args([ - 'build', - *kernel_initrd, - f'--output={output}', - '--uname=1.2.3', - '--signtool=pesign', - '--cmdline=ARG1 ARG2 ARG3', - f'--secureboot-certificate-name={name}', - f'--secureboot-certificate-dir={nss_db}', - ]) + opts = ukify.parse_args( + [ + 'build', + *kernel_initrd, + f'--output={output}', + '--uname=1.2.3', + '--signtool=pesign', + '--cmdline=ARG1 ARG2 ARG3', + f'--secureboot-certificate-name={name}', + f'--secureboot-certificate-dir={nss_db}', + ] + ) try: ukify.check_inputs(opts) @@ -632,15 +698,20 @@ def test_efi_signing_pesign(kernel_initrd, tmp_path): ukify.make_uki(opts) # let's check that pesign likes the resulting file - dump = subprocess.check_output([ - 'pesign', '-S', - '-i', output, - ], text=True) + dump = subprocess.check_output( + [ + 'pesign', + '-S', + '-i', output, + ], + text=True, + ) # fmt: skip assert f"The signer's common name is {author}" in dump shutil.rmtree(tmp_path) + def test_inspect(kernel_initrd, tmp_path, capsys, osrel=True): if kernel_initrd is None: pytest.skip('linux+initrd not found') @@ -652,9 +723,9 @@ def test_inspect(kernel_initrd, tmp_path, capsys, osrel=True): key = unbase64(ourdir / 'example.signing.key.base64') output = f'{tmp_path}/signed2.efi' - uname_arg='1.2.3' - osrel_arg='Linux' if osrel else '' - cmdline_arg='ARG1 ARG2 ARG3' + uname_arg = '1.2.3' + osrel_arg = 'Linux' if osrel else '' + cmdline_arg = 'ARG1 ARG2 ARG3' args = [ 'build', @@ -698,9 +769,11 @@ def test_inspect(kernel_initrd, tmp_path, capsys, osrel=True): shutil.rmtree(tmp_path) + def test_inspect_no_osrel(kernel_initrd, tmp_path, capsys): test_inspect(kernel_initrd, tmp_path, capsys, osrel=False) + @pytest.mark.skipif(not slow_tests, reason='slow') def test_pcr_signing(kernel_initrd, tmp_path): if kernel_initrd is None: @@ -722,7 +795,7 @@ def test_pcr_signing(kernel_initrd, tmp_path): '--uname=1.2.3', '--cmdline=ARG1 ARG2 ARG3', '--os-release=ID=foobar\n', - '--pcr-banks=sha384', # sha1 might not be allowed, use something else + '--pcr-banks=sha384', # sha1 might not be allowed, use something else f'--pcr-private-key={priv.name}', ] + arg_tools @@ -743,21 +816,25 @@ def test_pcr_signing(kernel_initrd, tmp_path): dump = subprocess.check_output(['objdump', '-h', output], text=True) for sect in 'text osrel cmdline linux initrd uname pcrsig'.split(): - assert re.search(fr'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) + assert re.search(rf'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) # objcopy fails when called without an output argument (EPERM). # It also fails when called with /dev/null (file truncated). # It also fails when called with /dev/zero (because it reads the # output file, infinitely in this case.) # So let's just call it with a dummy output argument. - subprocess.check_call([ - 'objcopy', - *(f'--dump-section=.{n}={tmp_path}/out.{n}' for n in ( - 'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline')), - output, - tmp_path / 'dummy', - ], - text=True) + subprocess.check_call( + [ + 'objcopy', + *( + f'--dump-section=.{n}={tmp_path}/out.{n}' + for n in ('pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline') + ), + output, + tmp_path / 'dummy', + ], + text=True, + ) assert open(tmp_path / 'out.pcrpkey').read() == open(pub.name).read() assert open(tmp_path / 'out.osrel').read() == 'ID=foobar\n' @@ -766,10 +843,11 @@ def test_pcr_signing(kernel_initrd, tmp_path): sig = open(tmp_path / 'out.pcrsig').read() sig = json.loads(sig) assert list(sig.keys()) == ['sha384'] - assert len(sig['sha384']) == 4 # four items for four phases + assert len(sig['sha384']) == 4 # four items for four phases shutil.rmtree(tmp_path) + @pytest.mark.skipif(not slow_tests, reason='slow') def test_pcr_signing2(kernel_initrd, tmp_path): if kernel_initrd is None: @@ -791,24 +869,27 @@ def test_pcr_signing2(kernel_initrd, tmp_path): output = f'{tmp_path}/signed.efi' assert kernel_initrd[0] == '--linux' - opts = ukify.parse_args([ - 'build', - *kernel_initrd[:2], - f'--initrd={microcode.name}', - *kernel_initrd[2:], - f'--output={output}', - '--uname=1.2.3', - '--cmdline=ARG1 ARG2 ARG3', - '--os-release=ID=foobar\n', - '--pcr-banks=sha384', - f'--pcrpkey={pub2.name}', - f'--pcr-public-key={pub.name}', - f'--pcr-private-key={priv.name}', - '--phases=enter-initrd enter-initrd:leave-initrd', - f'--pcr-public-key={pub2.name}', - f'--pcr-private-key={priv2.name}', - '--phases=sysinit ready shutdown final', # yes, those phase paths are not reachable - ] + arg_tools) + opts = ukify.parse_args( + [ + 'build', + *kernel_initrd[:2], + f'--initrd={microcode.name}', + *kernel_initrd[2:], + f'--output={output}', + '--uname=1.2.3', + '--cmdline=ARG1 ARG2 ARG3', + '--os-release=ID=foobar\n', + '--pcr-banks=sha384', + f'--pcrpkey={pub2.name}', + f'--pcr-public-key={pub.name}', + f'--pcr-private-key={priv.name}', + '--phases=enter-initrd enter-initrd:leave-initrd', + f'--pcr-public-key={pub2.name}', + f'--pcr-private-key={priv2.name}', + '--phases=sysinit ready shutdown final', # yes, those phase paths are not reachable + ] + + arg_tools + ) try: ukify.check_inputs(opts) @@ -821,16 +902,20 @@ def test_pcr_signing2(kernel_initrd, tmp_path): dump = subprocess.check_output(['objdump', '-h', output], text=True) for sect in 'text osrel cmdline linux initrd uname pcrsig'.split(): - assert re.search(fr'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) + assert re.search(rf'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) - subprocess.check_call([ - 'objcopy', - *(f'--dump-section=.{n}={tmp_path}/out.{n}' for n in ( - 'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline', 'initrd')), - output, - tmp_path / 'dummy', - ], - text=True) + subprocess.check_call( + [ + 'objcopy', + *( + f'--dump-section=.{n}={tmp_path}/out.{n}' + for n in ('pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline', 'initrd') + ), + output, + tmp_path / 'dummy', + ], + text=True, + ) assert open(tmp_path / 'out.pcrpkey').read() == open(pub2.name).read() assert open(tmp_path / 'out.osrel').read() == 'ID=foobar\n' @@ -841,22 +926,25 @@ def test_pcr_signing2(kernel_initrd, tmp_path): sig = open(tmp_path / 'out.pcrsig').read() sig = json.loads(sig) assert list(sig.keys()) == ['sha384'] - assert len(sig['sha384']) == 6 # six items for six phases paths + assert len(sig['sha384']) == 6 # six items for six phases paths shutil.rmtree(tmp_path) + def test_key_cert_generation(tmp_path): - opts = ukify.parse_args([ - 'genkey', - f"--pcr-public-key={tmp_path / 'pcr1.pub.pem'}", - f"--pcr-private-key={tmp_path / 'pcr1.priv.pem'}", - '--phases=enter-initrd enter-initrd:leave-initrd', - f"--pcr-public-key={tmp_path / 'pcr2.pub.pem'}", - f"--pcr-private-key={tmp_path / 'pcr2.priv.pem'}", - '--phases=sysinit ready', - f"--secureboot-private-key={tmp_path / 'sb.priv.pem'}", - f"--secureboot-certificate={tmp_path / 'sb.cert.pem'}", - ]) + opts = ukify.parse_args( + [ + 'genkey', + f'--pcr-public-key={tmp_path / "pcr1.pub.pem"}', + f'--pcr-private-key={tmp_path / "pcr1.priv.pem"}', + '--phases=enter-initrd enter-initrd:leave-initrd', + f'--pcr-public-key={tmp_path / "pcr2.pub.pem"}', + f'--pcr-private-key={tmp_path / "pcr2.priv.pem"}', + '--phases=sysinit ready', + f'--secureboot-private-key={tmp_path / "sb.priv.pem"}', + f'--secureboot-certificate={tmp_path / "sb.cert.pem"}', + ] + ) assert opts.verb == 'genkey' ukify.check_cert_and_keys_nonexistent(opts) @@ -867,39 +955,46 @@ def test_key_cert_generation(tmp_path): if not shutil.which('openssl'): return - for key in (tmp_path / 'pcr1.priv.pem', - tmp_path / 'pcr2.priv.pem', - tmp_path / 'sb.priv.pem'): - out = subprocess.check_output([ - 'openssl', 'rsa', - '-in', key, - '-text', - '-noout', - ], text = True) + for key in (tmp_path / 'pcr1.priv.pem', tmp_path / 'pcr2.priv.pem', tmp_path / 'sb.priv.pem'): + out = subprocess.check_output( + [ + 'openssl', 'rsa', + '-in', key, + '-text', + '-noout', + ], + text=True, + ) # fmt: skip assert 'Private-Key' in out assert '2048 bit' in out - for pub in (tmp_path / 'pcr1.pub.pem', - tmp_path / 'pcr2.pub.pem'): - out = subprocess.check_output([ - 'openssl', 'rsa', - '-pubin', - '-in', pub, - '-text', - '-noout', - ], text = True) + for pub in (tmp_path / 'pcr1.pub.pem', tmp_path / 'pcr2.pub.pem'): + out = subprocess.check_output( + [ + 'openssl', 'rsa', + '-pubin', + '-in', pub, + '-text', + '-noout', + ], + text=True, + ) # fmt: skip assert 'Public-Key' in out assert '2048 bit' in out - out = subprocess.check_output([ - 'openssl', 'x509', - '-in', tmp_path / 'sb.cert.pem', - '-text', - '-noout', - ], text = True) + out = subprocess.check_output( + [ + 'openssl', 'x509', + '-in', tmp_path / 'sb.cert.pem', + '-text', + '-noout', + ], + text=True, + ) # fmt: skip assert 'Certificate' in out assert re.search(r'Issuer: CN\s?=\s?SecureBoot signing key on host', out) + @pytest.mark.skipif(not slow_tests, reason='slow') def test_join_pcrsig(capsys, kernel_initrd, tmp_path): if kernel_initrd is None: @@ -965,5 +1060,6 @@ def test_join_pcrsig(capsys, kernel_initrd, tmp_path): shutil.rmtree(tmp_path) + if __name__ == '__main__': sys.exit(pytest.main(sys.argv)) diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index 6f492bc9ba07f..7e3dd9038cbd7 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -599,7 +599,7 @@ def sign(input_f: str, output_f: str, opts: UkifyConfig) -> None: ) cmd = [ tool, - "sign", + 'sign', '--private-key', opts.sb_key, '--certificate', opts.sb_cert, *( @@ -1109,7 +1109,7 @@ def pe_add_sections(opts: UkifyConfig, uki: UKI, output: str) -> None: encoded = json.dumps(j).encode() if len(encoded) > section.SizeOfRawData: raise PEError( - f'Not enough space in existing section .pcrsig of size {section.SizeOfRawData} to append new data of size {len(encoded)}' # noqa: E501 + f'Not enough space in existing section .pcrsig of size {section.SizeOfRawData} to append new data of size {len(encoded)}' ) section.Misc_VirtualSize = len(encoded) # bytes(n) results in an array of n zeroes @@ -1305,15 +1305,15 @@ def parse_efifw_dir(path: Path) -> bytes: return efifw_blob -STUB_SBAT = """\ +STUB_SBAT = '''\ sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md uki,1,UKI,uki,1,https://uapi-group.org/specifications/specs/unified_kernel_image/ -""" +''' -ADDON_SBAT = """\ +ADDON_SBAT = '''\ sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md uki-addon,1,UKI Addon,addon,1,https://www.freedesktop.org/software/systemd/man/latest/systemd-stub.html -""" +''' def make_uki(opts: UkifyConfig) -> None: @@ -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) @@ -1498,7 +1498,7 @@ def make_uki(opts: UkifyConfig) -> None: if names[0] != '.profile': raise ValueError( - f'Expected .profile section as first valid section in PE profile binary {profile} but got {names[0]}' # noqa: E501 + f'Expected .profile section as first valid section in PE profile binary {profile} but got {names[0]}' ) if names.count('.profile') > 1: @@ -1688,7 +1688,7 @@ def generate_keys(opts: UkifyConfig) -> None: if not work: raise ValueError( - 'genkey: --secureboot-private-key=/--secureboot-certificate= or --pcr-private-key/--pcr-public-key must be specified' # noqa: E501 + 'genkey: --secureboot-private-key=/--secureboot-certificate= or --pcr-private-key/--pcr-public-key must be specified' ) @@ -1762,6 +1762,8 @@ def config_list_prepend( old = getattr(namespace, dest, []) if old is None: old = [] + if not isinstance(value, list): + value = [value] setattr(namespace, dest, value + old) @staticmethod @@ -1859,6 +1861,17 @@ def add_to(self, parser: argparse.ArgumentParser) -> None: args = self._names() parser.add_argument(*args, **kwargs) + def parse_value(self, s: str) -> Any: + if self.action == argparse.BooleanOptionalAction: + # We need to handle this case separately: the options are called + # --foo and --no-foo, and no argument is parsed. But in the config + # file, we have Foo=yes or Foo=no. + return self.parse_boolean(s) + elif self.type: + return self.type(s) + + return s + def apply_config( self, namespace: argparse.Namespace, @@ -1870,24 +1883,13 @@ def apply_config( assert f'{section}/{key}' == self.config_key dest = self.argparse_dest() - conv: Callable[[str], Any] - if self.action == argparse.BooleanOptionalAction: - # We need to handle this case separately: the options are called - # --foo and --no-foo, and no argument is parsed. But in the config - # file, we have Foo=yes or Foo=no. - conv = self.parse_boolean - elif self.type: - conv = self.type - else: - conv = lambda s: s # noqa: E731 - # This is a bit ugly, but --initrd and --devicetree-auto are the only options # with multiple args on the command line and a space-separated list in the # config file. if self.name in ['--initrd', '--devicetree-auto']: - value = [conv(v) for v in value.split()] + value = [self.parse_value(v) for v in value.split()] else: - value = conv(value) + value = self.parse_value(value) self.config_push(namespace, group, dest, value) @@ -2016,6 +2018,7 @@ def config_example(self) -> tuple[Optional[str], Optional[str], Optional[str]]: default=[], action='append', config_key='UKI/SBAT', + config_push=ConfigItem.config_list_prepend, ), ConfigItem( '--pcrpkey', @@ -2116,14 +2119,14 @@ def config_example(self) -> tuple[Optional[str], Optional[str], Optional[str]]: ConfigItem( '--secureboot-private-key', dest='sb_key', - help='required by --signtool=sbsign|systemd-sbsign. Path to key file or engine/provider designation for SB signing', # noqa: E501 + help='required by --signtool=sbsign|systemd-sbsign. Path to key file or engine/provider designation for SB signing', config_key='UKI/SecureBootPrivateKey', ), ConfigItem( '--secureboot-certificate', dest='sb_cert', help=( - 'required by --signtool=sbsign. sbsign needs a path to certificate file or engine-specific designation for SB signing' # noqa: E501 + 'required by --signtool=sbsign. sbsign needs a path to certificate file or engine-specific designation for SB signing' ), config_key='UKI/SecureBootCertificate', ), @@ -2132,7 +2135,7 @@ def config_example(self) -> tuple[Optional[str], Optional[str], Optional[str]]: dest='sb_certdir', default='/etc/pki/pesign', help=( - 'required by --signtool=pesign. Path to nss certificate database directory for PE signing. Default is /etc/pki/pesign' # noqa: E501 + 'required by --signtool=pesign. Path to nss certificate database directory for PE signing. Default is /etc/pki/pesign' ), config_key='UKI/SecureBootCertificateDir', config_push=ConfigItem.config_set, @@ -2141,7 +2144,7 @@ def config_example(self) -> tuple[Optional[str], Optional[str], Optional[str]]: '--secureboot-certificate-name', dest='sb_cert_name', help=( - 'required by --signtool=pesign. pesign needs a certificate nickname of nss certificate database entry to use for PE signing' # noqa: E501 + 'required by --signtool=pesign. pesign needs a certificate nickname of nss certificate database entry to use for PE signing' ), config_key='UKI/SecureBootCertificateName', ), @@ -2326,11 +2329,11 @@ def create_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser( description='Build and sign Unified Kernel Images', usage='\n ' - + textwrap.dedent("""\ + + textwrap.dedent('''\ ukify {b}build{e} [--linux=LINUX] [--initrd=INITRD] [options…] ukify {b}genkey{e} [options…] ukify {b}inspect{e} FILE… [options…] - """).format(b=Style.bold, e=Style.reset), + ''').format(b=Style.bold, e=Style.reset), allow_abbrev=False, add_help=False, epilog='\n '.join(('config file:', *config_example())), @@ -2458,7 +2461,7 @@ def finalize_options(opts: argparse.Namespace) -> None: # both param given, infer sbsign and in case it was given, ensure signtool=sbsign if opts.signtool and opts.signtool not in ('sbsign', 'systemd-sbsign'): raise ValueError( - f'Cannot provide --signtool={opts.signtool} with --secureboot-private-key= and --secureboot-certificate=' # noqa: E501 + f'Cannot provide --signtool={opts.signtool} with --secureboot-private-key= and --secureboot-certificate=' ) if not opts.signtool: opts.signtool = 'sbsign' @@ -2478,7 +2481,7 @@ def finalize_options(opts: argparse.Namespace) -> None: if opts.sign_kernel and not opts.sb_key and not opts.sb_cert_name: raise ValueError( - '--sign-kernel requires either --secureboot-private-key= and --secureboot-certificate= (for sbsign) or --secureboot-certificate-name= (for pesign) to be specified' # noqa: E501 + '--sign-kernel requires either --secureboot-private-key= and --secureboot-certificate= (for sbsign) or --secureboot-certificate-name= (for pesign) to be specified' ) opts.profile = resolve_at_path(opts.profile) diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c index c50acca04529d..67ce353e114d2 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,57 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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(&opts) > 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..d27616c2cd37e 100644 --- a/src/update-utmp/update-utmp.c +++ b/src/update-utmp/update-utmp.c @@ -10,6 +10,7 @@ #include "libaudit-util.h" #include "log.h" #include "main-func.h" +#include "strv.h" #include "time-util.h" #include "utmp-wtmp.h" #include "verbs.h" @@ -51,7 +52,8 @@ static int get_startup_monotonic_time(Context *c, usec_t *ret) { return 0; } -static int on_reboot(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_on_reboot, "reboot", /* help= */ NULL); +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 +82,8 @@ static int on_reboot(int argc, char *argv[], void *userdata) { return q; } -static int on_shutdown(int argc, char *argv[], void *userdata) { +VERB_NOARG(verb_on_shutdown, "shutdown", /* help= */ NULL); +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. */ @@ -102,12 +105,6 @@ 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 }, - {} - }; - _cleanup_(context_clear) Context c = { .audit_fd = -EBADF, }; @@ -118,7 +115,7 @@ static int run(int argc, char *argv[]) { c.audit_fd = open_audit_fd_or_warn(); - return dispatch_verb(argc, argv, verbs, &c); + return dispatch_verb(strv_skip(argv, 1), &c); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/userdb/meson.build b/src/userdb/meson.build index a933a4907b32f..c25c40bc0ab0a 100644 --- a/src/userdb/meson.build +++ b/src/userdb/meson.build @@ -5,7 +5,6 @@ executables += [ 'name' : 'systemd-userwork', 'conditions' : ['ENABLE_USERDB'], 'sources' : files('userwork.c'), - 'dependencies' : threads, }, libexec_template + { 'name' : 'systemd-userdbd', @@ -14,13 +13,11 @@ executables += [ 'userdbd-manager.c', 'userdbd.c', ), - 'dependencies' : threads, }, executable_template + { 'name' : 'userdbctl', 'conditions' : ['ENABLE_USERDB'], 'sources' : files('userdbctl.c'), - 'dependencies' : threads, }, ] diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index 88f738a6061eb..fd349b282677b 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -1,10 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include "alloc-util.h" +#include "ansi-color.h" #include "bitfield.h" #include "build.h" #include "copy.h" @@ -18,13 +18,16 @@ #include "format-table.h" #include "format-util.h" #include "fs-util.h" +#include "glyph-util.h" +#include "help-util.h" #include "io-util.h" +#include "json-util.h" #include "log.h" #include "main-func.h" #include "mkdir.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" -#include "pretty-print.h" #include "recurse-dir.h" #include "socket-util.h" #include "string-table.h" @@ -407,7 +410,9 @@ static int table_add_uid_map( return n_added; } -static int display_user(int argc, char *argv[], void *userdata) { +VERB(verb_display_user, "user", "[USER…]", VERB_ANY, VERB_ANY, VERB_DEFAULT, + "Inspect user"); +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 +755,9 @@ 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) { +VERB(verb_display_group, "group", "[GROUP…]", VERB_ANY, VERB_ANY, 0, + "Inspect group"); +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 +958,11 @@ static int show_membership(const char *user, const char *group, Table *table) { return 0; } -static int display_memberships(int argc, char *argv[], void *userdata) { +VERB(verb_display_memberships, "users-in-group", "[GROUP…]", VERB_ANY, VERB_ANY, 0, + "Show users that are members of specified groups"); +VERB(verb_display_memberships, "groups-of-user", "[USER…]", VERB_ANY, VERB_ANY, 0, + "Show groups the specified users are members of"); +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 +1058,9 @@ static int display_memberships(int argc, char *argv[], void *userdata) { return ret; } -static int display_services(int argc, char *argv[], void *userdata) { +VERB(verb_display_services, "services", NULL, VERB_ANY, 1, 0, + "Show enabled database services"); +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 +1085,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,8 +1123,11 @@ static int display_services(int argc, char *argv[], void *userdata) { return 0; } -static int ssh_authorized_keys(int argc, char *argv[], void *userdata) { +VERB(verb_ssh_authorized_keys, "ssh-authorized-keys", "USER", 2, VERB_ANY, 0, + "Show SSH authorized keys for user"); +static int verb_ssh_authorized_keys(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + const char *username; char **chain_invocation; int r; @@ -1124,6 +1136,8 @@ static int ssh_authorized_keys(int argc, char *argv[], void *userdata) { if (arg_from_file) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--from-file= not supported when showing SSH authorized keys, refusing."); + username = argv[1]; + if (arg_chain) { /* If --chain is specified, the rest of the command line is the chain command */ @@ -1134,11 +1148,13 @@ static int ssh_authorized_keys(int argc, char *argv[], void *userdata) { /* Make similar restrictions on the chain command as OpenSSH itself makes on the primary command. */ if (!path_is_absolute(argv[2])) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Chain invocation of ssh-authorized-keys commands requires an absolute binary path argument."); + "Chain invocation of ssh-authorized-keys commands requires an absolute program path (got '%s').", + argv[2]); if (!path_is_normalized(argv[2])) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Chain invocation of ssh-authorized-keys commands requires an normalized binary path argument."); + "Chain invocation of ssh-authorized-keys commands requires a normalized program path (got '%s').", + argv[2]); chain_invocation = argv + 2; } else { @@ -1150,18 +1166,18 @@ static int ssh_authorized_keys(int argc, char *argv[], void *userdata) { chain_invocation = NULL; } - r = userdb_by_name(argv[1], /* match= */ NULL, arg_userdb_flags, &ur); + r = userdb_by_name(username, /* match= */ NULL, arg_userdb_flags, &ur); if (r == -ESRCH) - log_error_errno(r, "User %s does not exist.", argv[1]); + log_error_errno(r, "User %s does not exist.", username); else if (r == -EHOSTDOWN) log_error_errno(r, "Selected user database service is not available for this request."); else if (r == -EINVAL) - log_error_errno(r, "Failed to find user %s: %m (Invalid user name?)", argv[1]); + log_error_errno(r, "Failed to find user %s: %m (Invalid user name?)", username); else if (r < 0) - log_error_errno(r, "Failed to find user %s: %m", argv[1]); + log_error_errno(r, "Failed to find user %s: %m", username); else { if (strv_isempty(ur->ssh_authorized_keys)) - log_debug("User record for %s has no public SSH keys.", argv[1]); + log_debug("User record for %s has no public SSH keys.", username); else STRV_FOREACH(i, ur->ssh_authorized_keys) printf("%s\n", *i); @@ -1260,7 +1276,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 +1513,9 @@ static int load_credential_one( return 0; } -static int load_credentials(int argc, char *argv[], void *userdata) { +VERB(verb_load_credentials, "load-credentials", NULL, VERB_ANY, 1, 0, + "Write static user/group records from credentials"); +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,114 +1550,72 @@ static int load_credentials(int argc, char *argv[], void *userdata) { return r; } -static int help(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *link = NULL; +static int help(void) { + _cleanup_(table_unrefp) Table *verbs = NULL, *options = 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); + pager_open(arg_pager_flags); - r = terminal_urlify_man("userdbctl", "1", &link); + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Show user and group information."); + + help_section("Commands"); + r = table_print_or_warn(verbs); if (r < 0) - return log_oom(); + return r; - printf("%s [OPTIONS...] COMMAND ...\n\n" - "%sShow user and group information.%s\n" - "\nCommands:\n" - " user [USER…] Inspect user\n" - " group [GROUP…] Inspect group\n" - " users-in-group [GROUP…] Show users that are members of specified groups\n" - " groups-of-user [USER…] Show groups the specified users are members of\n" - " services Show enabled database services\n" - " ssh-authorized-keys USER Show SSH authorized keys for user\n" - " load-credentials Write static user/group records from credentials\n" - "\nOptions:\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" - " --output=MODE Select output mode (classic, friendly, table, json)\n" - " -j Equivalent to --output=json\n" - " -s --service=SERVICE[:SERVICE…]\n" - " Query the specified service\n" - " --with-nss=BOOL Control whether to include glibc NSS data\n" - " -N Do not synthesize or include glibc NSS data\n" - " (Same as --synthesize=no --with-nss=no)\n" - " --synthesize=BOOL Synthesize root/nobody user\n" - " --with-dropin=BOOL Control whether to include drop-in records\n" - " --with-varlink=BOOL Control whether to talk to services at all\n" - " --multiplexer=BOOL Control whether to use the multiplexer\n" - " --json=pretty|short JSON output mode\n" - " --chain Chain another command\n" - " --uid-min=ID Filter by minimum UID/GID (default 0)\n" - " --uid-max=ID Filter by maximum UID/GID (default 4294967294)\n" - " --uuid=UUID Filter by UUID\n" - " -z --fuzzy Do a fuzzy name search\n" - " --disposition=VALUE Filter by disposition\n" - " -I Equivalent to --disposition=intrinsic\n" - " -S Equivalent to --disposition=system\n" - " -R Equivalent to --disposition=regular\n" - " --boundaries=BOOL Show/hide UID/GID range boundaries in output\n" - " -B Equivalent to --boundaries=no\n" - " -F --from-file=PATH Read JSON record from file\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + help_man_page_reference("userdbctl", "1"); return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_OUTPUT, - ARG_WITH_NSS, - ARG_WITH_DROPIN, - ARG_WITH_VARLINK, - ARG_SYNTHESIZE, - ARG_MULTIPLEXER, - ARG_JSON, - ARG_CHAIN, - ARG_UID_MIN, - ARG_UID_MAX, - ARG_UUID, - ARG_DISPOSITION, - ARG_BOUNDARIES, - }; +VERB_COMMON_HELP_HIDDEN(help); - 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 }, - { "output", required_argument, NULL, ARG_OUTPUT }, - { "service", required_argument, NULL, 's' }, - { "with-nss", required_argument, NULL, ARG_WITH_NSS }, - { "with-dropin", required_argument, NULL, ARG_WITH_DROPIN }, - { "with-varlink", required_argument, NULL, ARG_WITH_VARLINK }, - { "synthesize", required_argument, NULL, ARG_SYNTHESIZE }, - { "multiplexer", required_argument, NULL, ARG_MULTIPLEXER }, - { "json", required_argument, NULL, ARG_JSON }, - { "chain", no_argument, NULL, ARG_CHAIN }, - { "uid-min", required_argument, NULL, ARG_UID_MIN }, - { "uid-max", required_argument, NULL, ARG_UID_MAX }, - { "uuid", required_argument, NULL, ARG_UUID }, - { "fuzzy", no_argument, NULL, 'z' }, - { "disposition", required_argument, NULL, ARG_DISPOSITION }, - { "boundaries", required_argument, NULL, ARG_BOUNDARIES }, - { "from-file", required_argument, NULL, 'F' }, - {} - }; +static int parse_from_file(const char *arg, sd_json_variant **ret) { + sd_json_variant *v = NULL; + int r; + assert(ret); + + if (!isempty(arg)) { + const char *fn = streq(arg, "-") ? NULL : arg; + unsigned line = 0; + 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."); + } + + *ret = v; + return 0; +} + +static int parse_argv(int argc, char *argv[], char ***remaining_args) { const char *e; int r; assert(argc >= 0); assert(argv); + assert(remaining_args); /* We are going to update this environment variable with our own, hence let's first read what is already set */ e = getenv("SYSTEMD_ONLY_USERDB"); @@ -1654,122 +1630,144 @@ static int parse_argv(int argc, char *argv[]) { arg_services = l; } - /* 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; - - for (;;) { - int c; - - c = getopt_long(argc, argv, - arg_chain ? "+hjs:NISRzBF:" : "hjs:NISRzBF:", /* When --chain was used disable parsing of further switches */ - options, NULL); - if (c < 0) - break; + OptionParser opts = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + _cleanup_strv_free_ char **args = NULL; + FOREACH_OPTION_OR_RETURN(c, &opts) { 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_OUTPUT: - if (streq(optarg, "help")) + OPTION_LONG("output", "MODE", + "Select output mode (classic, friendly, table, json)"): + if (streq(opts.arg, "help")) return DUMP_STRING_TABLE(output, Output, _OUTPUT_MAX); - arg_output = output_from_string(optarg); + arg_output = output_from_string(opts.arg); if (arg_output < 0) - return log_error_errno(arg_output, "Invalid --output= mode: %s", optarg); + return log_error_errno(arg_output, "Invalid --output= mode: %s", opts.arg); arg_json_format_flags = arg_output == OUTPUT_JSON ? SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_COLOR_AUTO : SD_JSON_FORMAT_OFF; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; arg_output = sd_json_format_enabled(arg_json_format_flags) ? OUTPUT_JSON : _OUTPUT_INVALID; break; - case 'j': + OPTION_SHORT('j', NULL, "Equivalent to --output=json"): arg_json_format_flags = SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_COLOR_AUTO; arg_output = OUTPUT_JSON; break; - case 's': - if (isempty(optarg)) + OPTION('s', "service", "SERVICE[:SERVICE…]", "Query the specified service"): + if (isempty(opts.arg)) arg_services = strv_free(arg_services); else { - r = strv_split_and_extend(&arg_services, optarg, ":", /* filter_duplicates= */ true); + r = strv_split_and_extend(&arg_services, opts.arg, ":", /* filter_duplicates= */ true); if (r < 0) return log_error_errno(r, "Failed to parse -s/--service= argument: %m"); } break; - case 'N': + OPTION_LONG("with-nss", "BOOL", "Control whether to include glibc NSS data"): + r = parse_boolean_argument("--with-nss=", opts.arg, NULL); + if (r < 0) + return r; + + SET_FLAG(arg_userdb_flags, USERDB_EXCLUDE_NSS, !r); + break; + + OPTION_SHORT('N', NULL, + "Do not synthesize or include glibc NSS data " + "(Same as --synthesize=no --with-nss=no)"): arg_userdb_flags |= USERDB_EXCLUDE_NSS|USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN; break; - case ARG_WITH_NSS: - r = parse_boolean_argument("--with-nss=", optarg, NULL); + OPTION_LONG("synthesize", "BOOL", "Synthesize root/nobody user"): + r = parse_boolean_argument("--synthesize=", opts.arg, NULL); if (r < 0) return r; - SET_FLAG(arg_userdb_flags, USERDB_EXCLUDE_NSS, !r); + SET_FLAG(arg_userdb_flags, USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN, !r); break; - case ARG_WITH_DROPIN: - r = parse_boolean_argument("--with-dropin=", optarg, NULL); + OPTION_LONG("with-dropin", "BOOL", "Control whether to include drop-in records"): + r = parse_boolean_argument("--with-dropin=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_userdb_flags, USERDB_EXCLUDE_DROPIN, !r); break; - case ARG_WITH_VARLINK: - r = parse_boolean_argument("--with-varlink=", optarg, NULL); + OPTION_LONG("with-varlink", "BOOL", "Control whether to talk to services at all"): + r = parse_boolean_argument("--with-varlink=", opts.arg, NULL); if (r < 0) return r; SET_FLAG(arg_userdb_flags, USERDB_EXCLUDE_VARLINK, !r); break; - case ARG_SYNTHESIZE: - r = parse_boolean_argument("--synthesize=", optarg, NULL); + OPTION_LONG("multiplexer", "BOOL", "Control whether to use the multiplexer"): + r = parse_boolean_argument("--multiplexer=", opts.arg, NULL); if (r < 0) return r; - SET_FLAG(arg_userdb_flags, USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN, !r); + SET_FLAG(arg_userdb_flags, USERDB_AVOID_MULTIPLEXER, !r); break; - case ARG_MULTIPLEXER: - r = parse_boolean_argument("--multiplexer=", optarg, NULL); + OPTION_LONG("chain", NULL, "Chain another command"): + arg_chain = true; + break; + + OPTION_POSITIONAL: + r = strv_extend(&args, opts.arg); if (r < 0) - return r; + return log_oom(); + break; - SET_FLAG(arg_userdb_flags, USERDB_AVOID_MULTIPLEXER, !r); + OPTION_LONG("uid-min", "ID", "Filter by minimum UID/GID (default 0)"): + r = parse_uid(opts.arg, &arg_uid_min); + if (r < 0) + return log_error_errno(r, "Failed to parse --uid-min= value: %s", opts.arg); break; - case ARG_CHAIN: - arg_chain = true; + OPTION_LONG("uid-max", "ID", "Filter by maximum UID/GID (default 4294967294)"): + r = parse_uid(opts.arg, &arg_uid_max); + if (r < 0) + return log_error_errno(r, "Failed to parse --uid-max= value: %s", opts.arg); break; - case ARG_DISPOSITION: { - UserDisposition d = user_disposition_from_string(optarg); + OPTION_LONG("uuid", "UUID", "Filter by UUID"): + r = sd_id128_from_string(opts.arg, &arg_uuid); + if (r < 0) + return log_error_errno(r, "Failed to parse --uuid= value: %s", opts.arg); + break; + + OPTION('z', "fuzzy", NULL, "Do a fuzzy name search"): + arg_fuzzy = true; + break; + + OPTION_LONG("disposition", "VALUE", "Filter by disposition"): { + UserDisposition d = user_disposition_from_string(opts.arg); if (d < 0) - return log_error_errno(d, "Unknown user disposition: %s", optarg); + return log_error_errno(d, "Unknown user disposition: %s", opts.arg); if (arg_disposition_mask == UINT64_MAX) arg_disposition_mask = 0; @@ -1778,87 +1776,63 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 'I': + OPTION_SHORT('I', NULL, "Equivalent to --disposition=intrinsic"): if (arg_disposition_mask == UINT64_MAX) arg_disposition_mask = 0; arg_disposition_mask |= UINT64_C(1) << USER_INTRINSIC; break; - case 'S': + OPTION_SHORT('S', NULL, "Equivalent to --disposition=system"): if (arg_disposition_mask == UINT64_MAX) arg_disposition_mask = 0; arg_disposition_mask |= UINT64_C(1) << USER_SYSTEM; break; - case 'R': + OPTION_SHORT('R', NULL, "Equivalent to --disposition=regular"): if (arg_disposition_mask == UINT64_MAX) arg_disposition_mask = 0; arg_disposition_mask |= UINT64_C(1) << USER_REGULAR; break; - case ARG_UID_MIN: - r = parse_uid(optarg, &arg_uid_min); - if (r < 0) - return log_error_errno(r, "Failed to parse --uid-min= value: %s", optarg); - break; - - case ARG_UID_MAX: - r = parse_uid(optarg, &arg_uid_max); - if (r < 0) - return log_error_errno(r, "Failed to parse --uid-max= value: %s", optarg); - break; - - case ARG_UUID: - r = sd_id128_from_string(optarg, &arg_uuid); - if (r < 0) - return log_error_errno(r, "Failed to parse --uuid= value: %s", optarg); - break; - - case 'z': - arg_fuzzy = true; - break; - - case ARG_BOUNDARIES: - r = parse_boolean_argument("boundaries", optarg, &arg_boundaries); + OPTION_LONG("boundaries", "BOOL", + "Show/hide UID/GID range boundaries in output"): + r = parse_boolean_argument("boundaries", opts.arg, &arg_boundaries); if (r < 0) return r; break; - case 'B': + OPTION_SHORT('B', NULL, "Equivalent to --boundaries=no"): arg_boundaries = false; break; - case 'F': { - if (isempty(optarg)) { - arg_from_file = sd_json_variant_unref(arg_from_file); - break; - } + OPTION('F', "from-file", "PATH", "Read JSON record from file"): { + sd_json_variant *v = NULL; /* initialization to appease gcc-14 */ - _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 = parse_from_file(opts.arg, &v); if (r < 0) - return log_syntax(/* unit= */ NULL, LOG_ERR, fn ?: "", line, r, "JSON parse failure."); + return r; - sd_json_variant_unref(arg_from_file); - arg_from_file = TAKE_PTR(v); + json_variant_unref_and_replace(arg_from_file, v); break; } - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + + /* When --chain was seen, stop parsing switches after the second positional argument: + * [OPTS0…, VERB, OPTS1…, USERNAME, OPTS2…, COMMAND, OPTS3…] + * We shall parse OPTS0, OPTS1, OPTS2, but OPTS3 are for COMMAND. + * --chain can be anywhere in OPTS0, OPTS1, OPTS2, or first in OPTS3. + */ + if (arg_chain && strv_length(args) >= 3) + opts.state = OPTION_PARSER_DONE; } if (arg_uid_min > arg_uid_max) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Minimum UID/GID " UID_FMT " is above maximum UID/GID " UID_FMT ", refusing.", arg_uid_min, arg_uid_max); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Minimum UID/GID " UID_FMT " is above maximum UID/GID " UID_FMT ", refusing.", + arg_uid_min, arg_uid_max); /* If not mask was specified, use the all bits on mask */ if (arg_disposition_mask == UINT64_MAX) @@ -1867,27 +1841,21 @@ static int parse_argv(int argc, char *argv[]) { if (arg_from_file) arg_boundaries = false; + /* We gathered some positional args in 'args' ourselves. Append the remaining ones. */ + if (strv_extend_strv(&args, option_parser_get_args(&opts), /* filter_duplicates= */ false) < 0) + return log_oom(); + + *remaining_args = TAKE_PTR(args); return 1; } 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 }, - {} - }; - + _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; @@ -1898,14 +1866,15 @@ static int run(int argc, char *argv[]) { if (!e) return log_oom(); - if (setenv("SYSTEMD_ONLY_USERDB", e, true) < 0) + r = RET_NERRNO(setenv("SYSTEMD_ONLY_USERDB", e, true)); + if (r < 0) return log_error_errno(r, "Failed to set $SYSTEMD_ONLY_USERDB: %m"); log_info("Enabled services: %s", e); } else assert_se(unsetenv("SYSTEMD_ONLY_USERDB") == 0); - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb(args, NULL); } DEFINE_MAIN_FUNCTION(run); 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..fa06a49b260b4 100644 --- a/src/userdb/userwork.c +++ b/src/userdb/userwork.c @@ -172,14 +172,16 @@ 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; - if (uid_is_valid(p.uid)) - r = userdb_by_uid(p.uid, &p.match, userdb_flags, &hr); - else if (p.name) + /* Prefer lookup by name if both name and UID are specified, so NSS-backed records preserve + * the requested lookup name as an alias before we check the UID below. */ + if (p.name) r = userdb_by_name(p.name, &p.match, userdb_flags, &hr); + else if (uid_is_valid(p.uid)) + r = userdb_by_uid(p.uid, &p.match, userdb_flags, &hr); else { _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; @@ -313,14 +315,16 @@ 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; - if (gid_is_valid(p.gid)) - r = groupdb_by_gid(p.gid, &p.match, userdb_flags, &g); - else if (p.name) + /* Prefer lookup by name if both name and GID are specified, so NSS-backed records preserve + * the requested lookup name as an alias before we check the GID below. */ + if (p.name) r = groupdb_by_name(p.name, &p.match, userdb_flags, &g); + else if (gid_is_valid(p.gid)) + r = groupdb_by_gid(p.gid, &p.match, userdb_flags, &g); else { _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; @@ -360,7 +364,7 @@ static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *paramet return sd_varlink_error(link, "io.systemd.UserDatabase.ServiceNotAvailable", NULL); } - if ((uid_is_valid(p.gid) && g->gid != p.gid) || + if ((gid_is_valid(p.gid) && g->gid != p.gid) || (p.name && !group_record_matches_group_name(g, p.name))) return sd_varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL); @@ -401,7 +405,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..994fcbeb13d03 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,78 @@ 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 opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.arg, "auto")) + r = free_and_strdup_warn(&arg_root, in_initrd() ? "/sysroot" : NULL); + else { + if (!path_is_absolute(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--root= argument must be 'auto' or absolute path, got: %s", opts.arg); - break; + r = parse_path_argument(opts.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(&opts); + + 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 +290,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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); 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..48c500f7c483b 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -1,24 +1,34 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include +#include +#include #include #include #include #include "sd-daemon.h" +#include "sd-netlink.h" #include "sd-varlink.h" #include "build.h" #include "bus-util.h" #include "chase.h" +#include "devnum-util.h" #include "env-util.h" +#include "errno-list.h" +#include "errno-util.h" +#include "escape.h" #include "fd-util.h" #include "fileio.h" #include "format-table.h" #include "format-util.h" +#include "fs-util.h" +#include "help-util.h" #include "log.h" #include "main-func.h" #include "memfd-util.h" +#include "netlink-sock-diag.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -30,6 +40,8 @@ #include "process-util.h" #include "recurse-dir.h" #include "runtime-scope.h" +#include "socket-forward.h" +#include "socket-util.h" #include "string-util.h" #include "strv.h" #include "terminal-util.h" @@ -38,6 +50,7 @@ #include "varlink-util.h" #include "verbs.h" #include "version.h" +#include "xattr-util.h" typedef struct PushFds { int *fds; @@ -52,6 +65,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; @@ -68,199 +82,161 @@ STATIC_DESTRUCTOR_REGISTER(arg_graceful, strv_freep); 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); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + 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", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + help_cmdline("[OPTIONS...] COMMAND ..."); + help_abstract("Introspect Varlink Services."); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + + 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); + + help_man_page_reference("varlinkctl", "1"); 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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(opts.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", opts.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", opts.arg); - if (strv_extend(&arg_graceful, optarg) < 0) + if (strv_extend(&arg_graceful, opts.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(opts.arg)) { arg_timeout = USEC_INFINITY; break; } - r = parse_sec(optarg, &arg_timeout); + r = parse_sec(opts.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", opts.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(opts.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(opts.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", opts.arg); } else { - int parsed_fd = parse_fd(optarg); + int parsed_fd = parse_fd(opts.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", opts.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 +248,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 +256,7 @@ static int parse_argv(int argc, char *argv[]) { strv_sort_uniq(arg_graceful); + *ret_args = option_parser_get_args(&opts); return 1; } @@ -366,7 +325,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 +366,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 +391,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 +420,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 +594,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 +757,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 +799,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 +964,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 +990,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 +1013,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 +1063,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 +1163,329 @@ 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); +VERB_NOARG(verb_list_sockets, "list-sockets", "List listening Varlink entrypoint sockets"); +static int verb_list_sockets(int argc, char *argv[], uintptr_t _data, void *userdata) { + int r; + + assert(argc <= 1); + + /* Enumerates listening, file-system bound AF_UNIX SOCK_STREAM sockets via the sock_diag netlink API, + * and lists those that are marked as Varlink entrypoints (i.e. carry the "user.varlink" xattr set to + * "entrypoint"). */ + + r = socket_xattr_supported(); + if (r < 0) + return log_error_errno(r, "Failed to check if S_IFSOCK inodes support xattrs: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "This kernel does not support extended attributes on socket inodes, cannot enumerate Varlink sockets."); + + _cleanup_(sd_netlink_unrefp) sd_netlink *nl = NULL; + r = sd_sock_diag_socket_open(&nl); + if (r < 0) + return log_error_errno(r, "Failed to open sock_diag netlink socket: %m"); + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + r = sd_sock_diag_message_new_unix_dump(nl, &req, 1U << TCP_LISTEN, UDIAG_SHOW_NAME|UDIAG_SHOW_VFS); + if (r < 0) + return log_error_errno(r, "Failed to allocate AF_UNIX socket dump request: %m"); + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *reply = NULL; + r = sd_netlink_call(nl, req, /* timeout= */ 0, &reply); + if (r < 0) + return log_error_errno(r, "Failed to issue AF_UNIX socket dump: %m"); + + _cleanup_(table_unrefp) Table *table = table_new("path", "access"); + if (!table) + return log_oom(); + + (void) table_set_sort(table, (size_t) 0); + + for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { + + r = sd_netlink_message_get_errno(m); + if (r < 0) { + log_warning_errno(r, "Error in AF_UNIX socket dump entry, ignoring: %m"); + continue; + } + + struct unix_diag_msg udm; + r = sd_sock_diag_message_get_unix(m, &udm); + if (r < 0) { + log_warning_errno(r, "Failed to read AF_UNIX socket dump header, ignoring: %m"); + continue; + } + + /* We only care about listening stream sockets. The kernel already filtered by state, but + * there's no way to filter by type in the request, so we do that here (and double check the + * state for good measure). */ + if (udm.udiag_type != SOCK_STREAM) + continue; + if (udm.udiag_state != TCP_LISTEN) + continue; + + /* Read the bound name. This is not NUL terminated on the wire, hence read it as raw data. */ + _cleanup_free_ void *name = NULL; + size_t name_size = 0; + r = sd_netlink_message_read_data(m, UNIX_DIAG_NAME, &name_size, &name); + if (r == -ENODATA) /* unnamed socket */ + continue; + if (r < 0) + return log_error_errno(r, "Failed to read AF_UNIX socket name: %m"); + + /* Safely turn the raw, not necessarily NUL-terminated, name into a C string. This also + * rejects any name with embedded NUL bytes. */ + _cleanup_free_ char *path = NULL; + r = make_cstring(name, name_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, &path); + if (r < 0) { + log_debug_errno(r, "Failed to convert AF_UNIX socket name to string, skipping: %m"); + continue; + } + if (!path_is_absolute(path)) { + log_debug("Got non-absolute AF_UNIX socket path '%s', skipping.", path); + continue; + } + + /* The kernel also reports the backing VFS inode/device, but only for file-system bound + * sockets. We require it, both as a filter and to validate the path below. */ + _cleanup_free_ void *vfs = NULL; + size_t vfs_size = 0; + r = sd_netlink_message_read_data(m, UNIX_DIAG_VFS, &vfs_size, &vfs); + if (r == -ENODATA) + continue; /* not fs bound */ + if (r < 0) + return log_error_errno(r, "Failed to read AF_UNIX socket VFS data: %m"); + if (vfs_size != sizeof(struct unix_diag_vfs)) { + log_warning("Got AF_UNIX socket VFS data of unexpected size, skipping."); + continue; + } + const struct unix_diag_vfs *uv = vfs; + + /* Validate the path the kernel reported: open it (without following a final-component + * symlink), and verify it really is a socket whose inode and backing device match what + * netlink told us. This guards against the path having been unlinked/replaced in the + * meantime, so that we only ever read xattrs off the right inode. */ + _cleanup_close_ int fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) { + log_debug_errno(errno, "Failed to open reported AF_UNIX socket path '%s', skipping: %m", path); + continue; + } + + struct stat st; + if (fstat(fd, &st) < 0) { + log_debug_errno(errno, "Failed to stat reported AF_UNIX socket path '%s', skipping: %m", path); + continue; + } + + if (!S_ISSOCK(st.st_mode)) { + log_debug("Reported AF_UNIX socket path '%s' is not a socket, skipping.", path); + continue; + } + + /* the unix_diag_vfs structure only gives us 32bit inode numbers, which it truncates. hence lets truncate the value before comparison */ + if (((st.st_ino ^ uv->udiag_vfs_ino) & UINT32_MAX) != 0) { + log_debug("Inode of reported AF_UNIX socket path '%s' does not match netlink data, skipping.", path); + continue; + } + + /* udiag_vfs_dev carries the kernel-internal dev_t encoding, which differs from the userspace + * dev_t in st_dev — hence translate before comparing. */ + if (STAT_DEV_TO_KERNEL(st.st_dev) != uv->udiag_vfs_dev) { + log_debug("Backing device of reported AF_UNIX socket path '%s' does not match netlink data, skipping.", path); + continue; + } + + /* The path is validated now, hence we may safely read the Varlink role xattr off the fd. We + * only list sockets that are marked as Varlink entrypoints. */ + _cleanup_free_ char *role = NULL; + r = fgetxattr_malloc(fd, "user.varlink", &role, /* ret_size= */ NULL); + if (r < 0) { + if (!ERRNO_IS_NEG_XATTR_ABSENT(r)) + log_debug_errno(r, "Failed to read 'user.varlink' xattr of '%s', skipping: %m", path); + continue; + } + if (!streq(role, "entrypoint")) + continue; + + _cleanup_free_ char *no = NULL; + r = access_fd(fd, W_OK); + if (r < 0) { + no = strjoin("no (", ERRNO_NAME(r), ")"); + if (!no) + return log_oom(); + } + + r = table_add_many( + table, + TABLE_PATH, path, + TABLE_STRING, no ?: "yes", + TABLE_SET_COLOR, ansi_highlight_green_red(!no)); + if (r < 0) + return r; + } + + if (!table_isempty(table) || sd_json_format_enabled(arg_json_format_flags)) { + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */ true); + if (r < 0) + return log_error_errno(r, "Failed to output table: %m"); + } + + if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) { + if (table_isempty(table)) + printf("No sockets found.\n"); + else + printf("\n%zu entrypoint sockets listed.\n", table_get_rows(table) - 1); + } + + return 0; +} + +/* 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 +1493,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(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index f1039319fd872..f8c5dffd1eea9 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -30,6 +30,7 @@ #include "string-util.h" #include "strv.h" #include "terminal-util.h" +#include "vconsole-util.h" typedef struct Context { char *keymap; @@ -72,6 +73,24 @@ static void context_merge_config( context_merge(dst, src, src_compat, font_unimap); } +static int context_read_efi(Context *c) { + _cleanup_(context_done) Context v = {}; + int r; + + assert(c); + + r = vconsole_keymap_from_efi(&v.keymap); + if (r < 0) + return r; + if (r == 0) { + log_debug("No vconsole keymap matches firmware-provided keyboard layout, ignoring."); + return 0; + } + + context_merge_config(c, &v, /* src_compat= */ NULL); + return 0; +} + static int context_read_creds(Context *c) { _cleanup_(context_done) Context v = {}; int r; @@ -144,10 +163,13 @@ static int context_read_proc_cmdline(Context *c) { static void context_load_config(Context *c) { assert(c); - /* Load data from credentials (lowest priority) */ + /* Pick up the firmware-provided keyboard layout if any (lowest priority) */ + (void) context_read_efi(c); + + /* Load data from credentials */ (void) context_read_creds(c); - /* Load data from configuration file (middle priority) */ + /* Load data from configuration file */ (void) context_read_env(c); /* Let the kernel command line override /etc/vconsole.conf (highest priority) */ @@ -155,6 +177,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 +195,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 +237,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 +309,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 +344,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 +370,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 +419,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 +451,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 +462,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 +498,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 +619,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 +656,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 +696,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..2b02694b9b1fd 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -10,14 +10,15 @@ #include "cryptsetup-util.h" #include "extract-word.h" #include "fileio.h" +#include "format-table.h" #include "fstab-util.h" +#include "help-util.h" #include "hexdecoct.h" #include "log.h" #include "main-func.h" #include "parse-util.h" #include "path-util.h" #include "pcrextend-util.h" -#include "pretty-print.h" #include "string-util.h" #include "strv.h" #include "tpm2-util.h" @@ -50,21 +51,22 @@ STATIC_DESTRUCTOR_REGISTER(arg_root_hash_signature, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_nvpcr, freep); static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL; int r; - r = terminal_urlify_man("systemd-veritysetup@.service", "8", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; + + help_cmdline("COMMAND ..."); + help_abstract("Attach or detach a verity protected block device."); - printf("%s attach VOLUME DATADEVICE HASHDEVICE ROOTHASH [OPTIONS]\n" - "%s detach VOLUME\n\n" - "Attach or detach a verity protected block device.\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - program_invocation_short_name, - link); + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + help_man_page_reference("systemd-veritysetup@.service", "8"); return 0; } @@ -116,6 +118,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 +223,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 +317,9 @@ static int parse_options(const char *options) { return r; } -static int verb_attach(int argc, char *argv[], void *userdata) { +VERB(verb_attach, "attach", "VOLUME DATADEVICE HASHDEVICE ROOTHASH [OPTIONS]", 5, 6, 0, + "Attach a verity protected block device"); +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 +380,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 +400,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,40 +420,44 @@ 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"); + bool signed_activation = false; 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 + signed_activation = true; } 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); (void) pcrextend_verity_now( volume, &IOVEC_MAKE(rh, rh_size), - &IOVEC_MAKE(arg_root_hash_signature, arg_root_hash_signature_size)); + signed_activation ? &IOVEC_MAKE(arg_root_hash_signature, arg_root_hash_signature_size) : NULL); return 0; } -static int verb_detach(int argc, char *argv[], void *userdata) { +VERB(verb_detach, "detach", "VOLUME", 2, 2, 0, + "Detach a verity protected block device"); +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 +468,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 +478,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,22 +486,20 @@ 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, SD_ELF_NOTE_DLOPEN_PRIORITY_REQUIRED); + if (r < 0) + return r; umask(0022); - static const Verb verbs[] = { - { "attach", 5, 6, 0, verb_attach }, - { "detach", 2, 2, 0, verb_detach }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb(strv_skip(argv, 1), /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); 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..76617cc6d46e3 100644 --- a/src/vmspawn/meson.build +++ b/src/vmspawn/meson.build @@ -6,13 +6,16 @@ endif vmspawn_sources = files( 'vmspawn.c', + 'vmspawn-bind-volume.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', + 'vmspawn-qemu-config.c', ) executables += [ @@ -26,4 +29,8 @@ executables += [ 'sources' : files('test-vmspawn-util.c'), 'objects' : ['systemd-vmspawn'], }, + test_template + { + 'sources' : files('test-vmspawn-qemu-config.c'), + 'objects' : ['systemd-vmspawn'], + }, ] diff --git a/src/vmspawn/test-vmspawn-qemu-config.c b/src/vmspawn/test-vmspawn-qemu-config.c new file mode 100644 index 0000000000000..001a431981525 --- /dev/null +++ b/src/vmspawn/test-vmspawn-qemu-config.c @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "memstream-util.h" +#include "tests.h" +#include "vmspawn-qemu-config.h" + +/* Render a single "key = value" pair through qemu_config_key() and return both the function's return code + * and (on success, when ret is set) the rendered bytes. */ +static int render_key(char **ret, const char *key, const char *value) { + _cleanup_(memstream_done) MemStream m = {}; + FILE *f; + int r; + + assert_se(f = memstream_init(&m)); + + r = qemu_config_key(f, key, value); + if (r < 0) + return r; + + if (ret) + assert_se(memstream_finalize(&m, ret, NULL) >= 0); + + return r; +} + +static int render_section(char **ret, const char *type, const char *id) { + _cleanup_(memstream_done) MemStream m = {}; + FILE *f; + int r; + + assert_se(f = memstream_init(&m)); + + r = qemu_config_section(f, type, id); + if (r < 0) + return r; + + if (ret) + assert_se(memstream_finalize(&m, ret, NULL) >= 0); + + return r; +} + +TEST(qemu_config_key_valid) { + _cleanup_free_ char *out = NULL; + + ASSERT_OK(render_key(&out, "readonly", "on")); + ASSERT_STREQ(out, " readonly = \"on\"\n"); + + /* Keys may carry the full identifier charset. */ + out = mfree(out); + ASSERT_OK(render_key(&out, "confidential-guest-support", "snp0")); + ASSERT_STREQ(out, " confidential-guest-support = \"snp0\"\n"); +} + +TEST(qemu_config_value_permits_path_bytes) { + _cleanup_free_ char *out = NULL; + + /* Legitimate path bytes, including backslash, must pass through a quoted value verbatim. */ + ASSERT_OK(render_key(&out, "file", "/usr/share/edk2/ovmf/OVMF_CODE.fd")); + ASSERT_STREQ(out, " file = \"/usr/share/edk2/ovmf/OVMF_CODE.fd\"\n"); + + out = mfree(out); + ASSERT_OK(render_key(&out, "file", "/weird/but\\legal/path")); + ASSERT_STREQ(out, " file = \"/weird/but\\legal/path\"\n"); + + /* '=', spaces, brackets etc. are all fine inside a quoted value. */ + out = mfree(out); + ASSERT_OK(render_key(&out, "cpus", "1,sockets=1")); + ASSERT_STREQ(out, " cpus = \"1,sockets=1\"\n"); +} + +TEST(qemu_config_value_rejects_quote_and_newline) { + /* These two bytes are the only ones that can break out of the quoted token; both must be refused. */ + ASSERT_ERROR(render_key(NULL, "file", "ab\"cd"), EINVAL); + ASSERT_ERROR(render_key(NULL, "file", "ab\ncd"), EINVAL); +} + +TEST(qemu_config_key_name_rejects_structure) { + /* Key names are emitted unquoted, so they must be plain identifiers. */ + ASSERT_ERROR(render_key(NULL, "fo=o", "v"), EINVAL); + ASSERT_ERROR(render_key(NULL, "fo\no", "v"), EINVAL); + ASSERT_ERROR(render_key(NULL, "fo\"o", "v"), EINVAL); + ASSERT_ERROR(render_key(NULL, "fo o", "v"), EINVAL); + ASSERT_ERROR(render_key(NULL, "foo]", "v"), EINVAL); + ASSERT_ERROR(render_key(NULL, "/foo", "v"), EINVAL); + ASSERT_ERROR(render_key(NULL, "", "v"), EINVAL); +} + +TEST(qemu_config_section_valid) { + _cleanup_free_ char *out = NULL; + + ASSERT_OK(render_section(&out, "drive", "ovmf-code")); + ASSERT_STREQ(out, "\n[drive \"ovmf-code\"]\n"); + + out = mfree(out); + ASSERT_OK(render_section(&out, "smp-opts", NULL)); + ASSERT_STREQ(out, "\n[smp-opts]\n"); +} + +TEST(qemu_config_section_type_rejects_structure) { + /* The section type is emitted unquoted as "[type]" — a ']' or newline here would let a caller + * close the header early or open a new section. */ + ASSERT_ERROR(render_section(NULL, "drive]", "id"), EINVAL); + ASSERT_ERROR(render_section(NULL, "dr\nive", "id"), EINVAL); + ASSERT_ERROR(render_section(NULL, "dr ive", "id"), EINVAL); + ASSERT_ERROR(render_section(NULL, "dr\"ive", "id"), EINVAL); + ASSERT_ERROR(render_section(NULL, "", "id"), EINVAL); +} + +TEST(qemu_config_section_id_rejects_structure) { + /* The id is quoted, but ']' and backslash are still hardened against future runtime data. */ + ASSERT_ERROR(render_section(NULL, "drive", "i\"d"), EINVAL); + ASSERT_ERROR(render_section(NULL, "drive", "i\nd"), EINVAL); + ASSERT_ERROR(render_section(NULL, "drive", "i]d"), EINVAL); + ASSERT_ERROR(render_section(NULL, "drive", "i\\d"), EINVAL); + ASSERT_ERROR(render_section(NULL, "drive", ""), EINVAL); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/vmspawn/vmspawn-bind-volume.c b/src/vmspawn/vmspawn-bind-volume.c new file mode 100644 index 0000000000000..8466cd199e976 --- /dev/null +++ b/src/vmspawn/vmspawn-bind-volume.c @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "log.h" +#include "runtime-scope.h" +#include "stat-util.h" +#include "storage-util.h" +#include "string-util.h" +#include "vmspawn-bind-volume.h" +#include "vmspawn-qmp.h" + +DiskType disk_type_from_bind_volume_config(const char *config) { + if (isempty(config)) + return DISK_TYPE_VIRTIO_BLK; + return disk_type_from_string(config); +} + +int vmspawn_bind_volume_acquire( + RuntimeScope scope, + const BindVolume *v, + bool removable, + sd_varlink *link, + DriveInfo **ret, + char **reterr_error_id) { + + _cleanup_(storage_acquire_reply_done) StorageAcquireReply reply = STORAGE_ACQUIRE_REPLY_INIT; + _cleanup_(drive_info_unrefp) DriveInfo *d = NULL; + _cleanup_free_ char *err = NULL; + int r; + + assert(v); + assert(ret); + + DiskType dt = disk_type_from_bind_volume_config(v->config); + if (dt < 0) { + r = dt; + goto fail; + } + + r = storage_acquire_volume(scope, v, /* allow_interactive_auth= */ false, &err, &reply); + if (r < 0) + goto fail; + + if (reply.type == VOLUME_DIR) { + r = log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Directory volumes are not supported for vmspawn block devices."); + goto fail; + } + + struct stat st; + if (fstat(reply.fd, &st) < 0) { + r = -errno; + goto fail; + } + r = stat_verify_regular_or_block(&st); + if (r < 0) + goto fail; + + d = drive_info_new(); + if (!d) { + r = -ENOMEM; + goto fail; + } + + d->id = strjoin(v->provider, ":", v->volume); + d->disk_driver = strdup(ASSERT_PTR(qemu_device_driver_to_string(dt))); + d->format = strdup("raw"); + d->path = strdup(v->volume); + if (!d->id || !d->disk_driver || !d->format || !d->path) { + r = -ENOMEM; + goto fail; + } + + d->disk_type = dt; + d->fd = TAKE_FD(reply.fd); + + if (reply.type == VOLUME_BLK || S_ISBLK(st.st_mode)) + d->flags |= QMP_DRIVE_BLOCK_DEVICE; + if (reply.read_only > 0 || dt == DISK_TYPE_VIRTIO_SCSI_CDROM) + d->flags |= QMP_DRIVE_READ_ONLY; + if (removable) + d->flags |= QMP_DRIVE_REMOVABLE; + d->link = sd_varlink_ref(link); + + *ret = TAKE_PTR(d); + return 0; + +fail: + if (reterr_error_id) + *reterr_error_id = TAKE_PTR(err); + return r; +} + +/* Takes ownership of fd unconditionally — it is closed on every error path too. */ +int vmspawn_bind_volume_attach_fd( + VmspawnQmpBridge *bridge, + sd_varlink *link, + int fd, + const char *name, + const char *config) { + + _cleanup_close_ int owned_fd = fd; + int r; + + assert(bridge); + assert(link); + assert(fd >= 0); + assert(name); + + DiskType dt = disk_type_from_bind_volume_config(config); + if (dt < 0) + return dt; + + struct stat st; + if (fstat(owned_fd, &st) < 0) + return -errno; + r = stat_verify_regular_or_block(&st); + if (r < 0) + return r; + + int oflags = fcntl(owned_fd, F_GETFL); + if (oflags < 0) + return -errno; + if (FLAGS_SET(oflags, O_PATH)) + return -EBADF; + if ((oflags & O_ACCMODE_STRICT) == O_WRONLY) + return -EBADF; + + _cleanup_(drive_info_unrefp) DriveInfo *d = drive_info_new(); + if (!d) + return -ENOMEM; + + d->id = strdup(name); + d->disk_driver = strdup(ASSERT_PTR(qemu_device_driver_to_string(dt))); + d->format = strdup("raw"); + d->path = strdup(name); + if (!d->id || !d->disk_driver || !d->format || !d->path) + return -ENOMEM; + + d->disk_type = dt; + d->fd = TAKE_FD(owned_fd); + if (S_ISBLK(st.st_mode)) + d->flags |= QMP_DRIVE_BLOCK_DEVICE; + if (dt == DISK_TYPE_VIRTIO_SCSI_CDROM || (oflags & O_ACCMODE_STRICT) == O_RDONLY) + d->flags |= QMP_DRIVE_READ_ONLY; + d->flags |= QMP_DRIVE_REMOVABLE; + d->link = sd_varlink_ref(link); + + return vmspawn_qmp_add_block_device(bridge, TAKE_PTR(d)); +} + +void bind_volumes_done(BindVolumes *bv) { + assert(bv); + FOREACH_ARRAY(v, bv->items, bv->n_items) + bind_volume_free(*v); + bv->items = mfree(bv->items); + bv->n_items = 0; +} + +int vmspawn_bind_volume_prepare_boot( + RuntimeScope scope, + const BindVolumes *bv, + DriveInfos *drives) { + + int r; + + assert(bv); + assert(drives); + + if (bv->n_items == 0) + return 0; + + if (!GREEDY_REALLOC(drives->drives, drives->n_drives + bv->n_items)) + return log_oom(); + + FOREACH_ARRAY(it, bv->items, bv->n_items) { + BindVolume *v = *it; + _cleanup_(drive_info_unrefp) DriveInfo *d = NULL; + _cleanup_free_ char *error_id = NULL; + + r = vmspawn_bind_volume_acquire( + scope, v, + /* removable= */ false, + /* link= */ NULL, + &d, &error_id); + if (r < 0) { + if (error_id) + return log_error_errno(r, + "Failed to acquire storage volume '%s:%s' (%s): %m", + v->provider, v->volume, error_id); + return log_error_errno(r, + "Failed to acquire storage volume '%s:%s': %m", + v->provider, v->volume); + } + + drives->drives[drives->n_drives++] = TAKE_PTR(d); + } + + return 0; +} diff --git a/src/vmspawn/vmspawn-bind-volume.h b/src/vmspawn/vmspawn-bind-volume.h new file mode 100644 index 0000000000000..23b3ff52f3cb3 --- /dev/null +++ b/src/vmspawn/vmspawn-bind-volume.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "machine-util.h" +#include "shared-forward.h" +#include "vmspawn-qmp.h" + +/* Empty/NULL defaults to virtio-blk; otherwise delegates to disk_type_from_string(). */ +DiskType disk_type_from_bind_volume_config(const char *config); + +/* Acquires the volume and builds a DriveInfo with id=":" (the + * bridge-visible name; QMP-side names are still allocated by add_block_device). */ +int vmspawn_bind_volume_acquire( + RuntimeScope scope, + const BindVolume *v, + bool removable, + sd_varlink *link, + DriveInfo **ret, + char **reterr_error_id); + +typedef struct BindVolumes { + BindVolume **items; + size_t n_items; +} BindVolumes; + +void bind_volumes_done(BindVolumes *bv); + +int vmspawn_bind_volume_prepare_boot( + RuntimeScope scope, + const BindVolumes *bv, + DriveInfos *drives); + +/* Takes ownership of fd unconditionally. */ +int vmspawn_bind_volume_attach_fd( + VmspawnQmpBridge *bridge, + sd_varlink *link, + int fd, + const char *name, + const char *config); diff --git a/src/vmspawn/vmspawn-qemu-config.c b/src/vmspawn/vmspawn-qemu-config.c new file mode 100644 index 0000000000000..a58e1f141bcce --- /dev/null +++ b/src/vmspawn/vmspawn-qemu-config.c @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "errno-util.h" +#include "log.h" +#include "string-util.h" +#include "vmspawn-qemu-config.h" + +/* Enforce QEMU's identifier grammar, so runtime data can never inject config structure. */ +static bool qemu_config_identifier_valid(const char *s) { + return !isempty(s) && in_charset(s, ALPHANUMERICAL ".-_"); +} + +/* Values are written quoted ('key = "%s"') and QEMU reads them literally between the quotes (no escapes), + * only '"' and newline can break out. Nevertheless run the value through string_is_safe(), which is + * stricter than QEMU's own parser. Relax if a valid use case occurs. */ +static bool qemu_config_value_valid(const char *value) { + return string_is_safe(value, STRING_ALLOW_EMPTY | STRING_ALLOW_BACKSLASHES | STRING_ALLOW_GLOBS); +} + +int qemu_config_key(FILE *f, const char *key, const char *value) { + assert(f); + assert(key); + assert(value); + + if (!qemu_config_identifier_valid(key)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "QEMU config key '%s' is not a valid identifier.", key); + if (!qemu_config_value_valid(value)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "QEMU config value '%s' contains unsafe characters.", 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_identifier_valid(type)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "QEMU config section type '%s' is not a valid identifier.", type); + + if (id) { + if (!qemu_config_identifier_valid(id)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "QEMU config section id '%s' is not a valid identifier.", 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..7dc58a8ccb1d2 --- /dev/null +++ b/src/vmspawn/vmspawn-qmp.c @@ -0,0 +1,1949 @@ +/* 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->qmp_file_node_name); + 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))); +} + +/* 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_format_node_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_format_node_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. Teardown deletes the + * format node first, then the file node — order matters because the format + * node holds a strong reference to its `file` child, which would block a + * file-first del with "Node X is busy: node is used as 'file' of Y". */ +static void vmspawn_qmp_block_device_teardown(QmpClient *client, + const char *qmp_node_name, + const char *qmp_file_node_name, + BlockDeviceStateFlags stages) { + assert(client); + + if (FLAGS_SET(stages, BLOCK_DEVICE_STATE_BLOCKDEV_ADDED) && qmp_node_name) { + _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 format"); + } + + if (FLAGS_SET(stages, BLOCK_DEVICE_STATE_FILE_NODE_ADDED) && qmp_file_node_name) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + if (sd_json_buildo(&args, SD_JSON_BUILD_PAIR_STRING("node-name", qmp_file_node_name)) >= 0) + (void) qmp_client_invoke(client, /* ret_slot= */ NULL, "blockdev-del", QMP_CLIENT_ARGS(args), + on_qmp_complete, (void*) "teardown blockdev-del file"); + } +} + +/* 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; + + /* Pin the object alive across bridge_unregister_drive() + drive_info_unref() below. */ + _cleanup_(drive_info_unrefp) DriveInfo *ref = drive_info_ref(d); + + vmspawn_qmp_block_device_teardown(ref->bridge->qmp, ref->qmp_node_name, + ref->qmp_file_node_name, ref->state); + ref->state = BLOCK_DEVICE_STATE_ADD_FAILED; + + if (bridge_unregister_drive(ref->bridge, ref)) + drive_info_unref(ref); + + if (ref->link) { + (void) reply_qmp_error(ref->link, error_desc, error); + ref->link = sd_varlink_unref(ref->link); + return 0; + } + + log_error_errno(error, "Block device '%s' setup failed: %s", + strna(ref->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(ref->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_file_node_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(file) was queued may have marked the + * chain FAILED. The file node we just created is orphaned — tear it + * down retroactively. */ + if (FLAGS_SET(d->state, BLOCK_DEVICE_STATE_ADD_FAILED)) { + vmspawn_qmp_block_device_teardown(d->bridge->qmp, + /* qmp_node_name= */ NULL, + d->qmp_file_node_name, + BLOCK_DEVICE_STATE_FILE_NODE_ADDED); + return 0; + } + + d->state |= BLOCK_DEVICE_STATE_FILE_NODE_ADDED; + return 0; +} + +static int on_add_format_node_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(format) was queued may have marked + * the chain FAILED. The format node we just created is orphaned — + * tear it down retroactively. The file node was already torn down by + * drive_info_add_fail at original failure time. */ + if (FLAGS_SET(d->state, BLOCK_DEVICE_STATE_ADD_FAILED)) { + vmspawn_qmp_block_device_teardown(d->bridge->qmp, d->qmp_node_name, + /* qmp_file_node_name= */ NULL, + 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_reply(d->link, NULL); + 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; +} + +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(); + if (asprintf(&drive->qmp_file_node_name, "vmspawn-%" PRIu64 "-file-%" PRIu64, + drive->counter, drive->file_generation) < 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; + } + + 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, &drive->fdset_id); + if (r < 0) + return r; + TAKE_PTR(slot_ref); + + /* Build flags for the file-level node: RO and NO_FLUSH from the drive + * plus IO_URING from the bridge feature probe. */ + QmpDriveFlags file_flags = drive->flags & (QMP_DRIVE_READ_ONLY|QMP_DRIVE_NO_FLUSH); + if (FLAGS_SET(bridge->features, VMSPAWN_QMP_FEATURE_IO_URING)) + file_flags |= QMP_DRIVE_IO_URING; + + QmpFileNodeParams file_params = { + .node_name = drive->qmp_file_node_name, + .filename = drive->fdset_path, + .driver = FLAGS_SET(drive->flags, QMP_DRIVE_BLOCK_DEVICE) ? "host_device" : "file", + .flags = file_flags, + }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *file_args = NULL; + r = qmp_build_blockdev_add_file(&file_params, &file_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(file_args), + on_add_file_node_stage, slot_ref); + if (r < 0) + return r; + TAKE_PTR(slot_ref); + + QmpFormatNodeParams format_params = { + .node_name = drive->qmp_node_name, + .format = drive->format, + .file_node_name = drive->qmp_file_node_name, + .flags = drive->flags, + }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *format_args = NULL; + r = qmp_build_blockdev_add_format(&format_params, &format_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(format_args), + on_add_format_node_stage, slot_ref); + if (r < 0) + return r; + TAKE_PTR(slot_ref); + + /* Release the monitor's original fd; blockdev-add(file) above took a dup. */ + slot_ref = drive_info_ref(drive); + r = qmp_fdset_remove(bridge->qmp, drive->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); + + 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 sd_varlink_error(link, "io.systemd.MachineInstance.NoSuchStorage", NULL); + if (!FLAGS_SET(drive->flags, QMP_DRIVE_REMOVABLE)) + return sd_varlink_error(link, "io.systemd.MachineInstance.StorageImmutable", NULL); + if (!FLAGS_SET(drive->state, BLOCK_DEVICE_STATE_BLOCKDEV_ADDED)) + return reply_qmp_error(link, "Block device add pending", -EBUSY); + if (drive->state & (BLOCK_DEVICE_STATE_REMOVE_PENDING|BLOCK_DEVICE_STATE_REPLACE_PENDING)) + return reply_qmp_error(link, "Block device replace/remove 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->qmp_file_node_name, drive->state); + + assert_se(bridge_unregister_drive(bridge, drive) == drive); + drive_info_unref(drive); + return 0; +} + +typedef enum ReplaceCtxStateFlags { + REPLACE_CTX_FAILED = 1u << 0, /* idempotency sentinel */ + REPLACE_CTX_NEW_BLOCKDEV_ADDED = 1u << 1, /* blockdev-add(new file) ack'd */ + REPLACE_CTX_REOPEN_COMMITTED = 1u << 2, /* blockdev-reopen ack'd; new state moved onto DriveInfo */ +} ReplaceCtxStateFlags; + +/* Bits ReplaceStorage may change; others are preserved across replace. */ +#define QMP_DRIVE_REPLACE_MUTABLE_MASK \ + (QMP_DRIVE_BLOCK_DEVICE | QMP_DRIVE_READ_ONLY | QMP_DRIVE_IO_URING) + +/* Per-ReplaceStorage state. One ref per outstanding QMP callback plus one for + * the entry-point local (until TAKE_PTR'd into the chain). On commit the + * contents are folded into DriveInfo. */ +typedef struct ReplaceCtx { + unsigned n_ref; + + DriveInfo *drive; /* ref'd */ + char *new_file_node_name; + char *new_fdset_path; + uint64_t new_fdset_id; + QmpDriveFlags new_flags; + + char *old_file_node_name; + + ReplaceCtxStateFlags state; +} ReplaceCtx; + +static ReplaceCtx* replace_ctx_free(ReplaceCtx *ctx) { + if (!ctx) + return NULL; + + drive_info_unref(ctx->drive); + free(ctx->new_file_node_name); + free(ctx->new_fdset_path); + free(ctx->old_file_node_name); + return mfree(ctx); +} + +DEFINE_PRIVATE_TRIVIAL_REF_FUNC(ReplaceCtx, replace_ctx); +DEFINE_PRIVATE_TRIVIAL_UNREF_FUNC(ReplaceCtx, replace_ctx, replace_ctx_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(ReplaceCtx*, replace_ctx_unref); + +/* First-error handler for the replace pipeline. Idempotent. Best-effort tears + * down whatever new-side state we created on the wire, clears REPLACE_PENDING, + * and replies on drive->link (if any). */ +static int replace_fail(ReplaceCtx *ctx, int error, const char *error_desc) { + assert(ctx); + + if (FLAGS_SET(ctx->state, REPLACE_CTX_FAILED)) + return 0; + ctx->state |= REPLACE_CTX_FAILED; + + DriveInfo *drive = ctx->drive; + assert(drive); + + /* If the new file node was added, del it; that also drops the fdset's + * dup so the new fdset auto-frees. If add-fd succeeded but blockdev-add + * never did, an explicit remove-fd is needed instead. */ + if (FLAGS_SET(ctx->state, REPLACE_CTX_NEW_BLOCKDEV_ADDED)) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + if (sd_json_buildo(&args, SD_JSON_BUILD_PAIR_STRING("node-name", ctx->new_file_node_name)) >= 0) + (void) qmp_client_invoke(drive->bridge->qmp, /* ret_slot= */ NULL, "blockdev-del", + QMP_CLIENT_ARGS(args), + on_qmp_complete, (void*) "replace rollback blockdev-del"); + } else if (ctx->new_fdset_path) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + if (sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", ctx->new_fdset_id)) >= 0) + (void) qmp_client_invoke(drive->bridge->qmp, /* ret_slot= */ NULL, "remove-fd", + QMP_CLIENT_ARGS(args), + on_qmp_complete, (void*) "replace rollback remove-fd"); + } + + drive->state &= ~BLOCK_DEVICE_STATE_REPLACE_PENDING; + _cleanup_(sd_varlink_unrefp) sd_varlink *link = TAKE_PTR(drive->link); + if (link) + return reply_qmp_error(link, error_desc, error); + return 0; +} + +static int on_replace_observe_stage( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(replace_ctx_unrefp) ReplaceCtx *ctx = ASSERT_PTR(userdata); + assert(client); + + if (error < 0) + return replace_fail(ctx, error, error_desc); + return 0; +} + +static int on_replace_blockdev_add_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(replace_ctx_unrefp) ReplaceCtx *ctx = ASSERT_PTR(userdata); + assert(client); + + if (error < 0) + return replace_fail(ctx, error, error_desc); + + /* If a sync error elsewhere has already marked the chain failed, the + * just-added file node is orphaned — tear it down retroactively. */ + if (FLAGS_SET(ctx->state, REPLACE_CTX_FAILED)) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + if (sd_json_buildo(&args, SD_JSON_BUILD_PAIR_STRING("node-name", ctx->new_file_node_name)) >= 0) + (void) qmp_client_invoke(ctx->drive->bridge->qmp, /* ret_slot= */ NULL, + "blockdev-del", QMP_CLIENT_ARGS(args), + on_qmp_complete, + (void*) "replace retroactive blockdev-del"); + return 0; + } + + ctx->state |= REPLACE_CTX_NEW_BLOCKDEV_ADDED; + return 0; +} + +static int on_replace_old_blockdev_del_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(replace_ctx_unrefp) ReplaceCtx *ctx = ASSERT_PTR(userdata); + assert(client); + + DriveInfo *drive = ctx->drive; + + /* The swap itself succeeded at reopen-commit time. If del of the old + * file node failed, the orphan persists until VM exit — log and reply + * success. The fdset auto-freed when the dup was released regardless. */ + if (error < 0) + log_warning("Failed to delete orphaned file node '%s' after replace: %s", + ctx->old_file_node_name, strna(error_desc)); + + drive->state &= ~BLOCK_DEVICE_STATE_REPLACE_PENDING; + _cleanup_(sd_varlink_unrefp) sd_varlink *link = TAKE_PTR(drive->link); + if (link) + (void) sd_varlink_reply(link, NULL); + + log_info("Block device '%s' backing replaced", drive->id); + return 0; +} + +static int on_replace_blockdev_reopen_complete( + QmpClient *client, + sd_json_variant *result, + const char *error_desc, + int error, + void *userdata) { + + _cleanup_(replace_ctx_unrefp) ReplaceCtx *ctx = ASSERT_PTR(userdata); + assert(client); + + if (error < 0) + return replace_fail(ctx, error, error_desc); + + DriveInfo *drive = ctx->drive; + + /* Atomic commit: the format graph now references the new file node. + * Move the new-side state from ctx onto DriveInfo so subsequent + * teardowns find the right names. */ + ctx->state |= REPLACE_CTX_REOPEN_COMMITTED; + free_and_replace(drive->qmp_file_node_name, ctx->new_file_node_name); + free_and_replace(drive->fdset_path, ctx->new_fdset_path); + drive->fdset_id = ctx->new_fdset_id; + /* Only commit the mutable bits so unrelated future flags aren't silently flipped. */ + drive->flags = (drive->flags & ~QMP_DRIVE_REPLACE_MUTABLE_MASK) | + (ctx->new_flags & QMP_DRIVE_REPLACE_MUTABLE_MASK); + + /* Trailing blockdev-del of the OLD file node. The format no longer + * references it, so it's an orphan; deleting it also drops the dup + * that kept the old fdset alive. */ + _cleanup_(sd_json_variant_unrefp) sd_json_variant *del_args = NULL; + int r = sd_json_buildo(&del_args, SD_JSON_BUILD_PAIR_STRING("node-name", ctx->old_file_node_name)); + if (r >= 0) { + _cleanup_(replace_ctx_unrefp) ReplaceCtx *slot_ref = replace_ctx_ref(ctx); + r = qmp_client_invoke(drive->bridge->qmp, /* ret_slot= */ NULL, + "blockdev-del", QMP_CLIENT_ARGS(del_args), + on_replace_old_blockdev_del_complete, slot_ref); + if (r >= 0) { + TAKE_PTR(slot_ref); + return 0; + } + } + + /* Couldn't even queue blockdev-del. The swap succeeded; reply success + * and leave the orphan to clean up at VM exit. */ + log_warning_errno(r, "Failed to queue blockdev-del for orphaned file node '%s': %m", + ctx->old_file_node_name); + + drive->state &= ~BLOCK_DEVICE_STATE_REPLACE_PENDING; + _cleanup_(sd_varlink_unrefp) sd_varlink *link = TAKE_PTR(drive->link); + if (link) + (void) sd_varlink_reply(link, NULL); + return 0; +} + +int vmspawn_qmp_replace_block_device( + VmspawnQmpBridge *bridge, + sd_varlink *link, + const char *id, + int fd, + QmpDriveFlags fd_flags) { + + _cleanup_close_ int owned_fd = fd; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *file_args = NULL, *reopen_args = NULL; + _cleanup_(replace_ctx_unrefp) ReplaceCtx *ctx = NULL; + /* Not _cleanup_'d: aliasing it with ctx tripped a gcc-12 + * -Wuse-after-free false positive on the cleanup chain. Error paths + * unref it explicitly; success leaks the ref to the callback. */ + ReplaceCtx *slot_ref; + int r; + + assert(bridge); + assert(link); + assert(id); + assert(fd >= 0); + + DriveInfo *drive = hashmap_get(bridge->block_devices, id); + if (!drive) + return sd_varlink_error(link, "io.systemd.MachineInstance.NoSuchStorage", NULL); + if (!FLAGS_SET(drive->flags, QMP_DRIVE_REMOVABLE)) + return sd_varlink_error(link, "io.systemd.MachineInstance.StorageImmutable", NULL); + /* QEMU's blockdev-reopen rejects RW->RO on a node with attached writers + * (the guest device). For an RW drive the new backing must be writable. */ + if (!FLAGS_SET(drive->flags, QMP_DRIVE_READ_ONLY) && FLAGS_SET(fd_flags, QMP_DRIVE_READ_ONLY)) + return sd_varlink_error_errno(link, -EROFS); + if (!FLAGS_SET(drive->state, BLOCK_DEVICE_STATE_BLOCKDEV_ADDED)) + return reply_qmp_error(link, "Block device add pending", -EBUSY); + if (drive->state & (BLOCK_DEVICE_STATE_REMOVE_PENDING|BLOCK_DEVICE_STATE_REPLACE_PENDING)) + return reply_qmp_error(link, "Block device replace/remove pending", -EBUSY); + assert(!drive->link); + assert(drive->qmp_file_node_name); + + /* Bump generation EARLY so failed attempts don't collide on retry. */ + uint64_t new_gen = ++drive->file_generation; + + _cleanup_free_ char *new_file_node_name = NULL; + if (asprintf(&new_file_node_name, "vmspawn-%" PRIu64 "-file-%" PRIu64, + drive->counter, new_gen) < 0) + return sd_varlink_error_errno(link, -ENOMEM); + + /* Compute new flags: keep the existing drive flags, swap in the + * caller-derived bits (only RO and BLOCK_DEVICE are caller-controlled), + * fold scsi-cd into RO, and fold in the bridge's io_uring feature. */ + const QmpDriveFlags FD_DERIVED_MASK = QMP_DRIVE_READ_ONLY | QMP_DRIVE_BLOCK_DEVICE; + QmpDriveFlags new_flags = (drive->flags & ~QMP_DRIVE_REPLACE_MUTABLE_MASK) | + (fd_flags & FD_DERIVED_MASK); + if (drive->disk_type == DISK_TYPE_VIRTIO_SCSI_CDROM) + new_flags |= QMP_DRIVE_READ_ONLY; + if (FLAGS_SET(bridge->features, VMSPAWN_QMP_FEATURE_IO_URING)) + new_flags |= QMP_DRIVE_IO_URING; + + ctx = new0(ReplaceCtx, 1); + if (!ctx) + return sd_varlink_error_errno(link, -ENOMEM); + ctx->n_ref = 1; + ctx->drive = drive_info_ref(drive); + ctx->new_file_node_name = TAKE_PTR(new_file_node_name); + ctx->new_flags = new_flags; + ctx->old_file_node_name = strdup(drive->qmp_file_node_name); + if (!ctx->old_file_node_name) + return sd_varlink_error_errno(link, -ENOMEM); + + drive->link = sd_varlink_ref(link); + drive->state |= BLOCK_DEVICE_STATE_REPLACE_PENDING; + + /* 1. add-fd → new fdset */ + slot_ref = replace_ctx_ref(ctx); + r = qmp_fdset_add(bridge->qmp, TAKE_FD(owned_fd), + on_replace_observe_stage, slot_ref, + &ctx->new_fdset_path, &ctx->new_fdset_id); + if (r < 0) { + replace_ctx_unref(slot_ref); + goto rollback_sync; + } + + /* 2. blockdev-add (new file node, new fdset) */ + QmpFileNodeParams file_params = { + .node_name = ctx->new_file_node_name, + .filename = ctx->new_fdset_path, + .driver = FLAGS_SET(new_flags, QMP_DRIVE_BLOCK_DEVICE) ? "host_device" : "file", + .flags = new_flags, + }; + r = qmp_build_blockdev_add_file(&file_params, &file_args); + if (r < 0) + goto rollback_sync; + + slot_ref = replace_ctx_ref(ctx); + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "blockdev-add", QMP_CLIENT_ARGS(file_args), + on_replace_blockdev_add_complete, slot_ref); + if (r < 0) { + replace_ctx_unref(slot_ref); + goto rollback_sync; + } + + /* 3. remove-fd (new fdset; blockdev-add holds the dup) */ + slot_ref = replace_ctx_ref(ctx); + r = qmp_fdset_remove(bridge->qmp, ctx->new_fdset_id, + on_replace_observe_stage, slot_ref); + if (r < 0) { + replace_ctx_unref(slot_ref); + goto rollback_sync; + } + + /* 4. blockdev-reopen the format node, file → new + * NB: the option set must be a superset of every field + * qmp_build_blockdev_add_format() may emit; otherwise reopen rejects + * "Cannot reset option X to default" or silently flips a flag. + * No "backing" field: only ephemeral overlays carry backing, and + * those are never REMOVABLE. */ + r = sd_json_buildo(&reopen_args, + SD_JSON_BUILD_PAIR("options", SD_JSON_BUILD_ARRAY( + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("node-name", drive->qmp_node_name), + SD_JSON_BUILD_PAIR_STRING("driver", drive->format), + SD_JSON_BUILD_PAIR_STRING("file", ctx->new_file_node_name), + /* blockdev-reopen resets unspecified options to driver defaults, + * so emit the format-agnostic options unconditionally. */ + SD_JSON_BUILD_PAIR_BOOLEAN("read-only", FLAGS_SET(new_flags, QMP_DRIVE_READ_ONLY)), + SD_JSON_BUILD_PAIR_STRING("discard", + FLAGS_SET(new_flags, QMP_DRIVE_DISCARD) ? "unmap" : "ignore"), + /* qcow2-only option; raw rejects it as an unknown property. */ + SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(new_flags, QMP_DRIVE_DISCARD_NO_UNREF), + "discard-no-unref", SD_JSON_BUILD_BOOLEAN(true)))))); + if (r < 0) + goto rollback_sync; + + slot_ref = replace_ctx_ref(ctx); + r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "blockdev-reopen", QMP_CLIENT_ARGS(reopen_args), + on_replace_blockdev_reopen_complete, slot_ref); + if (r < 0) { + replace_ctx_unref(slot_ref); + goto rollback_sync; + } + + return 0; + +rollback_sync: + /* Mark failed so any in-flight callbacks observe the failure and + * rollback their just-added state retroactively. */ + ctx->state |= REPLACE_CTX_FAILED; + drive->state &= ~BLOCK_DEVICE_STATE_REPLACE_PENDING; + drive->link = sd_varlink_unref(drive->link); + return sd_varlink_error_errno(link, r); +} + +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..f627c663757fe --- /dev/null +++ b/src/vmspawn/vmspawn-qmp.h @@ -0,0 +1,196 @@ +/* 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 */ + QMP_DRIVE_REMOVABLE = 1u << 7, /* may be detached at runtime via RemoveStorage */ +} 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 */ + BLOCK_DEVICE_STATE_FILE_NODE_ADDED = 1u << 3, /* blockdev-add(file) succeeded; teardown must del it */ + BLOCK_DEVICE_STATE_REPLACE_PENDING = 1u << 4, /* blockdev-reopen pipeline in flight */ +} 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 *qmp_file_node_name; /* "vmspawn--file-" — current file-level node */ + uint64_t file_generation; /* monotonically bumped per replace ATTEMPT */ + char *fdset_path; /* "/dev/fdset/N" */ + uint64_t fdset_id; /* numeric id matching fdset_path */ + 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_add_block_device(VmspawnQmpBridge *bridge, DriveInfo *drive); +int vmspawn_qmp_remove_block_device(VmspawnQmpBridge *bridge, sd_varlink *link, const char *id); +/* fd_flags encodes the new fd's properties: only QMP_DRIVE_READ_ONLY and + * QMP_DRIVE_BLOCK_DEVICE are caller-controlled; other bits are ignored. */ +int vmspawn_qmp_replace_block_device( + VmspawnQmpBridge *bridge, + sd_varlink *link, + const char *id, + int fd, + QmpDriveFlags fd_flags); +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..d05c4e1a9e1ad 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,29 @@ 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); + +static const char *const confidential_computing_table[_COCO_MAX] = { + [COCO_NO] = "no", + [COCO_AMD_SEV_SNP] = "sev-snp", +}; + +DEFINE_STRING_TABLE_LOOKUP(confidential_computing, ConfidentialComputing); diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h index ee937c993ac88..9be3afdef4c42 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,33 @@ 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 ConfidentialComputing { + COCO_NO, + COCO_AMD_SEV_SNP, + _COCO_MAX, + _COCO_INVALID = -EINVAL, +} ConfidentialComputing; + typedef enum SettingsMask { SETTING_START_MODE = UINT64_C(1) << 0, SETTING_MACHINE_ID = UINT64_C(1) << 6, @@ -42,4 +60,6 @@ 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); +DECLARE_STRING_TABLE_LOOKUP(confidential_computing, ConfidentialComputing); diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index 3f65fab2cc52b..8ecd26600064e 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" @@ -31,11 +32,11 @@ static const char* const architecture_to_qemu_table[_ARCHITECTURE_MAX] = { [ARCHITECTURE_X86_64] = "x86_64", /* differs from our name */ [ARCHITECTURE_X86] = "i386", /* differs from our name */ [ARCHITECTURE_LOONGARCH64] = "loongarch64", - [ARCHITECTURE_MIPS64_LE] = "mips", /* differs from our name */ - [ARCHITECTURE_MIPS_LE] = "mips", /* differs from our name */ + [ARCHITECTURE_MIPS64_LE] = "mips64el", /* differs from our name */ + [ARCHITECTURE_MIPS_LE] = "mipsel", /* differs from our name */ [ARCHITECTURE_PARISC] = "hppa", /* differs from our name */ - [ARCHITECTURE_PPC64_LE] = "ppc", /* differs from our name */ - [ARCHITECTURE_PPC64] = "ppc", /* differs from our name */ + [ARCHITECTURE_PPC64_LE] = "ppc64", /* differs from our name; qemu-system-ppc64 runs LE guests too */ + [ARCHITECTURE_PPC64] = "ppc64", /* differs from our name */ [ARCHITECTURE_PPC] = "ppc", [ARCHITECTURE_RISCV32] = "riscv32", [ARCHITECTURE_RISCV64] = "riscv64", @@ -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; @@ -203,7 +254,9 @@ static int get_firmware_search_dirs(char ***ret) { /* Search in: * - $XDG_CONFIG_HOME/qemu/firmware * - /etc/qemu/firmware - * - /usr/share/qemu/firmware + * - $XDG_DATA_HOME/qemu/firmware (default: ~/.local/share/qemu/firmware) + * - each entry in $XDG_DATA_DIRS suffixed with /qemu/firmware + * (default: /usr/local/share/qemu/firmware, /usr/share/qemu/firmware) * * Prioritising entries in "more specific" directories */ @@ -213,10 +266,27 @@ static int get_firmware_search_dirs(char ***ret) { return r; _cleanup_strv_free_ char **l = NULL; - l = strv_new(user_firmware_dir, "/etc/qemu/firmware", "/usr/share/qemu/firmware"); + l = strv_new(user_firmware_dir, "/etc/qemu/firmware"); if (!l) return log_oom_debug(); + _cleanup_strv_free_ char **data_dirs = NULL; + r = sd_path_lookup_strv(SD_PATH_SEARCH_SHARED, "/qemu/firmware", &data_dirs); + if (r < 0) + return r; + + r = strv_extend_strv(&l, data_dirs, /* filter_duplicates = */ true); + if (r < 0) + return log_oom_debug(); + + /* Always include /usr/share/qemu/firmware as a final fallback, + * even if a custom $XDG_DATA_DIRS replaced it. */ + r = strv_extend(&l, "/usr/share/qemu/firmware"); + if (r < 0) + return log_oom_debug(); + + strv_uniq(l); + *ret = TAKE_PTR(l); return 0; } @@ -243,7 +313,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 +323,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 +331,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 +350,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 +420,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; @@ -330,13 +443,8 @@ int find_ovmf_config(int search_sb, OvmfConfig **ret) { if (r < 0) return r; - /* Search in: - * - $XDG_CONFIG_HOME/qemu/firmware - * - /etc/qemu/firmware - * - /usr/share/qemu/firmware - * - * Prioritising entries in "more specific" directories - */ + /* Search paths are constructed by get_firmware_search_dirs(), + * prioritising entries in "more specific" directories. */ r = list_ovmf_config(&conf_files); if (r < 0) @@ -344,34 +452,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 +542,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..b75b8daa99455 --- /dev/null +++ b/src/vmspawn/vmspawn-varlink.c @@ -0,0 +1,587 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "alloc-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "log.h" +#include "path-util.h" +#include "qmp-client.h" +#include "stat-util.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-bind-volume.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_add_storage(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + int r; + + struct { + int fd_index; + const char *name; + const char *config; + } p = { + .fd_index = -1, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "fileDescriptorIndex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, voffsetof(p, fd_index), SD_JSON_MANDATORY }, + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + { "config", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, config), 0 }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (isempty(p.name)) + return sd_varlink_error_invalid_parameter_name(link, "name"); + + if (disk_type_from_bind_volume_config(p.config) < 0) + return sd_varlink_error(link, "io.systemd.MachineInstance.BadConfig", NULL); + + if (p.fd_index < 0) + return sd_varlink_error_invalid_parameter_name(link, "fileDescriptorIndex"); + + _cleanup_close_ int fd = sd_varlink_take_fd(link, p.fd_index); + if (fd < 0) + return sd_varlink_error_errno(link, fd); + + r = vmspawn_bind_volume_attach_fd(ctx->bridge, link, TAKE_FD(fd), p.name, p.config); + if (r == -EEXIST) + return sd_varlink_error(link, "io.systemd.MachineInstance.StorageExists", NULL); + if (r == -EOPNOTSUPP) + return sd_varlink_error(link, "io.systemd.MachineInstance.ConfigNotSupported", NULL); + if (r < 0) + return sd_varlink_error_errno(link, r); + + /* Async reply via on_add_device_add_complete. */ + return 0; +} + +static int vl_method_remove_storage(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + int r; + + struct { + const char *name; + } p = {}; + + static const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (isempty(p.name)) + return sd_varlink_error_invalid_parameter_name(link, "name"); + + return vmspawn_qmp_remove_block_device(ctx->bridge, link, p.name); +} + +static int vl_method_replace_storage(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + VmspawnVarlinkContext *ctx = ASSERT_PTR(userdata); + int r; + + struct { + int fd_index; + const char *name; + } p = { + .fd_index = -1, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "fileDescriptorIndex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, voffsetof(p, fd_index), SD_JSON_MANDATORY }, + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(p, name), SD_JSON_MANDATORY }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (isempty(p.name)) + return sd_varlink_error_invalid_parameter_name(link, "name"); + + if (p.fd_index < 0) + return sd_varlink_error_invalid_parameter_name(link, "fileDescriptorIndex"); + + _cleanup_close_ int fd = sd_varlink_take_fd(link, p.fd_index); + if (fd < 0) + return sd_varlink_error_errno(link, fd); + + struct stat st; + if (fstat(fd, &st) < 0) + return sd_varlink_error_errno(link, -errno); + r = stat_verify_regular_or_block(&st); + if (r < 0) + return sd_varlink_error_errno(link, r); + + int oflags = fcntl(fd, F_GETFL); + if (oflags < 0) + return sd_varlink_error_errno(link, -errno); + if (FLAGS_SET(oflags, O_PATH)) + return sd_varlink_error_errno(link, -EBADF); + if ((oflags & O_ACCMODE_STRICT) == O_WRONLY) + return sd_varlink_error_errno(link, -EBADF); + + QmpDriveFlags fd_flags = 0; + if (S_ISBLK(st.st_mode)) + fd_flags |= QMP_DRIVE_BLOCK_DEVICE; + if ((oflags & O_ACCMODE_STRICT) == O_RDONLY) + fd_flags |= QMP_DRIVE_READ_ONLY; + + return vmspawn_qmp_replace_block_device(ctx->bridge, link, p.name, TAKE_FD(fd), fd_flags); + /* Async reply via on_replace_old_blockdev_del_complete or replace_fail. */ +} + +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(); + + /* AddStorage receives an fd from the caller. */ + r = varlink_server_new(&ctx->varlink_server, + SD_VARLINK_SERVER_INHERIT_USERDATA | + SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT, + 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, + "io.systemd.MachineInstance.AddStorage", vl_method_add_storage, + "io.systemd.MachineInstance.RemoveStorage", vl_method_remove_storage, + "io.systemd.MachineInstance.ReplaceStorage", vl_method_replace_storage); + 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, 0644); + 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; + + ctx->varlink_server = sd_varlink_server_unref(ctx->varlink_server); + + drain_event_subscribers(&ctx->subscribed); + + ctx->bridge = vmspawn_qmp_bridge_free(ctx->bridge); + + 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..6e2329f7f9c35 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,23 +34,31 @@ #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" #include "group-record.h" +#include "help-util.h" #include "hexdecoct.h" #include "hostname-setup.h" #include "hostname-util.h" #include "id128-util.h" +#include "initrd-cpio.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 "options.h" #include "osc-context.h" #include "pager.h" #include "parse-argument.h" @@ -65,6 +72,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 +81,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" @@ -80,21 +90,37 @@ #include "user-record.h" #include "user-util.h" #include "utf8.h" +#include "vmspawn-bind-volume.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 + +/* First and one-past-last pcie.0 device-numbers used for multifunction-packed + * pcie-root-ports. Sits above the auto-assigned virtio devices (0x01-0x03) and + * below 0x1f, which q35 reserves for ICH9 LPC at 0x1f.0 (single-function). */ +#define VMSPAWN_PCIE_PACK_BASE_SLOT 0x10 +#define VMSPAWN_PCIE_PACK_END_SLOT 0x1f +#define VMSPAWN_PCIE_PACK_MAX_PORTS ((VMSPAWN_PCIE_PACK_END_SLOT - VMSPAWN_PCIE_PACK_BASE_SLOT) * 8) + +/* 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,34 +143,51 @@ 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 ConfidentialComputing arg_confidential_computing = COCO_NO; 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; static ExtraDriveContext arg_extra_drives = {}; +static BindVolumes arg_bind_volumes = {}; 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,113 +204,73 @@ 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); STATIC_DESTRUCTOR_REGISTER(arg_forward_journal, freep); STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline_extra, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_extra_drives, extra_drive_context_done); +STATIC_DESTRUCTOR_REGISTER(arg_bind_volumes, bind_volumes_done); 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); STATIC_DESTRUCTOR_REGISTER(arg_bind_user_groups, strv_freep); static int help(void) { - _cleanup_free_ char *link = NULL; int r; pager_open(arg_pager_flags); - r = terminal_urlify_man("systemd-vmspawn", "1", &link); - if (r < 0) - return log_oom(); + static const char* const groups[] = { + NULL, + "Image", + "Host Configuration", + "Networking", + "Execution", + "System Identity", + "Properties", + "User Namespacing", + "Mounts", + "Logging", + "SSH", + "Input/Output", + "Credentials", + }; + + Table* tables[ELEMENTSOF(groups)] = {}; + CLEANUP_ELEMENTS(tables, table_unref_array_clear); + + 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; + } - 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", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + (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]); + help_cmdline("[OPTIONS...] [ARGUMENTS...]"); + help_abstract("Spawn a command or OS in a virtual machine."); + + for (size_t i = 0; i < ELEMENTSOF(groups); i++) { + help_section(groups[i] ?: "Options"); + + r = table_print_or_warn(tables[i]); + if (r < 0) + return r; + } + + help_man_page_reference("systemd-vmspawn", "1"); return 0; } @@ -285,200 +288,174 @@ 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 opts = { argc, argv, OPTION_PARSER_STOP_AT_FIRST_NONOPTION }; + + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.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(opts.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(opts.arg); if (arg_image_format < 0) return log_error_errno(arg_image_format, - "Invalid image format: %s", optarg); + "Invalid image format: %s", opts.arg); 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(); - } + 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(opts.arg); + if (arg_image_disk_type < 0) + return log_error_errno(arg_image_disk_type, + "Invalid image disk type: %s", opts.arg); break; - case 'x': - arg_ephemeral = true; + OPTION_LONG("discard-disk", "BOOL", "Control processing of discard requests"): + r = parse_boolean_argument("--discard-disk=", opts.arg, &arg_discard_disk); + if (r < 0) + return r; break; - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; + OPTION('G', "grow-image", "BYTES", "Grow image file to specified size in bytes"): + if (isempty(opts.arg)) { + arg_grow_image = 0; + break; + } + + r = parse_size(opts.arg, 1024, &arg_grow_image); + if (r < 0) + return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", opts.arg); break; - case ARG_CPUS: - r = free_and_strdup_warn(&arg_cpus, optarg); + OPTION_GROUP("Host Configuration"): {} + + 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, opts.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(opts.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=", opts.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=", opts.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(opts.arg)) arg_vsock_cid = VMADDR_CID_ANY; else { unsigned cid; - r = vsock_parse_cid(optarg, &cid); + r = vsock_parse_cid(opts.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", opts.arg); if (!VSOCK_CID_IS_REGULAR(cid)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified CID is not regular, refusing: %u", cid); @@ -486,165 +463,155 @@ 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=", opts.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; - } + OPTION_LONG("tpm-state", "off|auto|PATH", "Where to store TPM state"): + r = isempty(opts.arg) ? false : + streq(opts.arg, "auto") ? true : + parse_boolean(opts.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_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); + if (!path_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --tpm-state= parameter: %s", opts.arg); - break; + if (!path_is_absolute(opts.arg) && !startswith(opts.arg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --tpm-state= parameter must be absolute or start with './': %s", opts.arg); - case ARG_QEMU_GUI: - arg_console_mode = CONSOLE_GUI; - break; + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_tpm_state_path); + if (r < 0) + return r; - case 'n': - arg_network_stack = NETWORK_STACK_TAP; + arg_tpm_state_mode = STATE_PATH; break; - case ARG_NETWORK_USER_MODE: - arg_network_stack = NETWORK_STACK_USER; - break; + OPTION_LONG("efi-nvram-template", "PATH", "Set the path to the EFI NVRAM template file to use"): + if (!isempty(opts.arg) && !path_is_absolute(opts.arg) && !startswith(opts.arg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required."); - 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."); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_efi_nvram_template); if (r < 0) - return log_error_errno(r, "Invalid UUID: %s", optarg); - + return r; break; - case ARG_REGISTER: - r = parse_boolean_argument("--register=", optarg, &arg_register); - if (r < 0) - return r; + OPTION_LONG("efi-nvram-state", "off|auto|PATH", "Where to store EFI Variable NVRAM state"): + r = isempty(opts.arg) ? false : + streq(opts.arg, "auto") ? true : + parse_boolean(opts.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; + } - break; + if (!path_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --efi-nvram-state= parameter: %s", opts.arg); - case ARG_KEEP_UNIT: - arg_keep_unit = true; - break; + if (!path_is_absolute(opts.arg) && !startswith(opts.arg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --efi-nvram-state= parameter must be absolute or start with './': %s", opts.arg); - case ARG_BIND: - case ARG_BIND_RO: - r = runtime_mount_parse(&arg_runtime_mounts, optarg, c == ARG_BIND_RO); + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_efi_nvram_state_path); if (r < 0) - return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", optarg); + return r; + arg_efi_nvram_state_mode = STATE_PATH; 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("secure-boot", "BOOL|auto", "Enable searching for firmware supporting SecureBoot"): { + int b; - r = parse_path_argument(buf ?: optarg, /* suppress_root= */ false, &drive_path); + r = parse_tristate_argument_with_auto("--secure-boot=", opts.arg, &b); 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, - }; + 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_SECURE_BOOT: - r = parse_tristate_argument_with_auto("--secure-boot=", optarg, &arg_secure_boot); - if (r < 0) - return r; - break; + 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(opts.arg) || streq(opts.arg, "auto")) { + arg_firmware = mfree(arg_firmware); + arg_firmware_type = _FIRMWARE_INVALID; + arg_firmware_describe = false; + break; + } - case ARG_PRIVATE_USERS: - r = parse_userns_uid_range(optarg, &arg_uid_shift, &arg_uid_range); - if (r < 0) - return r; - break; + if (streq(opts.arg, "list")) { + _cleanup_strv_free_ char **l = NULL; - case ARG_FORWARD_JOURNAL: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_forward_journal); - if (r < 0) - return r; - break; + r = list_ovmf_config(&l); + if (r < 0) + return log_error_errno(r, "Failed to list firmwares: %m"); - case ARG_PASS_SSH_KEY: - r = parse_boolean_argument("--pass-ssh-key=", optarg, &arg_pass_ssh_key); - if (r < 0) - return r; - break; + bool nl = false; + fputstrv(stdout, l, "\n", &nl); + if (nl) + putchar('\n'); - 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); + return 0; + } - r = free_and_strdup_warn(&arg_ssh_key_type, optarg); - if (r < 0) - return r; - break; + if (streq(opts.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; + } - case ARG_SET_CREDENTIAL: { - r = machine_credential_set(&arg_credentials, optarg); - if (r < 0) - return r; - break; - } + Firmware f = firmware_from_string(opts.arg); + if (f >= 0) { + arg_firmware = mfree(arg_firmware); + arg_firmware_type = f; + arg_firmware_describe = false; + break; + } - case ARG_LOAD_CREDENTIAL: { - r = machine_credential_load(&arg_credentials, optarg); + if (!path_is_absolute(opts.arg) && !startswith(opts.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", + opts.arg); + + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_firmware); if (r < 0) return r; + arg_firmware_type = FIRMWARE_UEFI; + arg_firmware_describe = false; break; } - case ARG_FIRMWARE: - if (streq(optarg, "list")) { + OPTION_LONG("firmware-features", "FEATURE,...|list", + "Require/exclude specific firmware features"): { + if (isempty(opts.arg)) { + arg_firmware_features_include = set_free(arg_firmware_features_include); + arg_firmware_features_exclude = set_free(arg_firmware_features_exclude); + break; + } + + if (streq(opts.arg, "list")) { _cleanup_strv_free_ char **l = NULL; - r = list_ovmf_config(&l); + r = list_ovmf_firmware_features(&l); if (r < 0) - return log_error_errno(r, "Failed to list firmwares: %m"); + return log_error_errno(r, "Failed to list firmware features: %m"); bool nl = false; fputstrv(stdout, l, "\n", &nl); @@ -654,147 +621,324 @@ 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."); + _cleanup_strv_free_ char **features = strv_split(opts.arg, ","); + if (!features) + return log_oom(); - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_firmware); - if (r < 0) - return r; + 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("coco", "no|sev-snp", "Run the guest as a confidential VM"): { + ConfidentialComputing cc = confidential_computing_from_string(opts.arg); + if (cc < 0) + return log_error_errno(cc, "Unknown --coco= value: %s", opts.arg); + arg_confidential_computing = cc; + break; + } + + OPTION_GROUP("Networking"): {} + + OPTION('n', "network-tap", NULL, "Create a TAP device for networking"): + arg_network_stack = NETWORK_STACK_TAP; + break; + OPTION_LONG("network-user-mode", NULL, "Use user mode networking"): + arg_network_stack = NETWORK_STACK_USER; break; - case ARG_DISCARD_DISK: - r = parse_boolean_argument("--discard-disk=", optarg, &arg_discard_disk); + OPTION_GROUP("Execution"): {} + + OPTION_LONG("linux", "PATH", "Specify the linux kernel for direct kernel boot"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_linux); if (r < 0) return r; break; - case ARG_BACKGROUND: - r = parse_background_argument(optarg, &arg_background); + OPTION_LONG("initrd", "PATH", "Specify the initrd for direct kernel boot"): { + _cleanup_free_ char *initrd_path = NULL; + r = parse_path_argument(opts.arg, /* 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 's': - if (isempty(optarg)) { + OPTION('s', "smbios11", "STRING", "Pass an arbitrary SMBIOS Type #11 string to the VM"): + if (isempty(opts.arg)) { arg_smbios11 = strv_free(arg_smbios11); 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 (!utf8_is_valid(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SMBIOS Type 11 string is not UTF-8 clean, refusing: %s", opts.arg); - if (strv_extend(&arg_smbios11, optarg) < 0) + if (strv_extend(&arg_smbios11, opts.arg) < 0) return log_oom(); - break; - case 'G': - if (isempty(optarg)) { - arg_grow_image = 0; - break; - } - - r = parse_size(optarg, 1024, &arg_grow_image); + OPTION_LONG("notify-ready", "BOOL", "Wait for ready notification from the VM"): + r = parse_boolean_argument("--notify-ready=", opts.arg, &arg_notify_ready); if (r < 0) - return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", optarg); - + return r; 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("System Identity"): {} - arg_tpm_state_mode = TPM_STATE_PATH; - break; + OPTION('M', "machine", "NAME", "Set the machine name for the VM"): + if (isempty(opts.arg)) + arg_machine = mfree(arg_machine); + else { + if (!hostname_is_valid(opts.arg, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid machine name: %s", opts.arg); + + r = free_and_strdup(&arg_machine, opts.arg); + if (r < 0) + return log_oom(); } + break; - r = isempty(optarg) ? false : - streq(optarg, "auto") ? true : - parse_boolean(optarg); + OPTION_LONG("uuid", "UUID", "Set a specific machine UUID for the VM"): + r = id128_from_string_nonzero(opts.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 --tpm-state= parameter: %s", optarg); - - arg_tpm_state_mode = r ? TPM_STATE_AUTO : TPM_STATE_OFF; - arg_tpm_state_path = mfree(arg_tpm_state_path); + return log_error_errno(r, "Invalid UUID: %s", opts.arg); break; - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; + OPTION_GROUP("Properties"): {} - case 'S': { + 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(opts.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", opts.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, opts.arg) < 0) return log_oom(); + break; + + OPTION_LONG("register", "BOOLEAN", "Register VM as machine"): + r = parse_tristate_argument_with_auto("--register=", opts.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; + + 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(opts.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(opts.opt->long_code, "bind-ro"); + r = runtime_mount_parse(&arg_runtime_mounts, opts.arg, read_only); + if (r < 0) + return log_error_errno(r, "Failed to parse --%s= argument %s: %m", + opts.opt->long_code, opts.arg); break; + } - case ARG_NOTIFY_READY: - r = parse_boolean_argument("--notify-ready=", optarg, &arg_notify_ready); + 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; + + r = parse_disk_spec(opts.arg, &format, &extra_disk_type, &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, + .disk_type = extra_disk_type, + }; break; + } + + OPTION_LONG("bind-volume", "PROVIDER:VOLUME[:CONFIG][:KEY=VALUE,...]", + "Acquire a storage volume from a StorageProvider and attach it to the VM"): { + _cleanup_(bind_volume_freep) BindVolume *bv = NULL; + + r = bind_volume_parse(opts.arg, &bv); + if (r < 0) + return log_error_errno(r, "Failed to parse --bind-volume= argument '%s': %m", opts.arg); + + if (disk_type_from_bind_volume_config(bv->config) < 0) { + _cleanup_free_ char *valid = NULL; + for (DiskType t = 0; t < _DISK_TYPE_MAX; t++) + if (!strextend_with_separator(&valid, ", ", disk_type_to_string(t))) + return log_oom(); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown device type '%s' for --bind-volume=. Valid values: %s.", + bv->config, valid); + } - 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); + FOREACH_ARRAY(it, arg_bind_volumes.items, arg_bind_volumes.n_items) + if (streq((*it)->provider, bv->provider) && streq((*it)->volume, bv->volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Volume '%s:%s' specified more than once for --bind-volume=.", + bv->provider, bv->volume); - if (strv_extend(&arg_bind_user, optarg) < 0) + if (!GREEDY_REALLOC(arg_bind_volumes.items, arg_bind_volumes.n_items + 1)) return log_oom(); + arg_bind_volumes.items[arg_bind_volumes.n_items++] = TAKE_PTR(bv); + break; + } + + OPTION_LONG("bind-user", "NAME", "Bind user from host to virtual machine"): + if (!valid_user_group_name(opts.arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", opts.arg); + if (strv_extend(&arg_bind_user, opts.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(opts.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", opts.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(opts.arg, /* flags= */ 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", opts.arg); - if (strv_extend(&arg_bind_user_groups, optarg) < 0) + if (strv_extend(&arg_bind_user_groups, opts.arg) < 0) return log_oom(); - break; - case ARG_SYSTEM: - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; - break; + OPTION_GROUP("Logging"): {} - case ARG_USER: - arg_runtime_scope = RUNTIME_SCOPE_USER; + OPTION_LONG("forward-journal", "FILE|DIR", "Forward the VM's journal to the host"): + r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_forward_journal); + if (r < 0) + return r; break; - case '?': - return -EINVAL; + OPTION_LONG("forward-journal-max-use", "BYTES", "Maximum disk space for forwarded journal"): + r = parse_size(opts.arg, 1024, &arg_forward_journal_max_use); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-max-use= value: %s", opts.arg); + break; + + OPTION_LONG("forward-journal-keep-free", "BYTES", "Minimum disk space to keep free"): + r = parse_size(opts.arg, 1024, &arg_forward_journal_keep_free); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-keep-free= value: %s", opts.arg); + break; + + OPTION_LONG("forward-journal-max-file-size", "BYTES", "Maximum size of individual journal files"): + r = parse_size(opts.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", opts.arg); + break; + + OPTION_LONG("forward-journal-max-files", "N", "Maximum number of journal files to keep"): + r = safe_atou64(opts.arg, &arg_forward_journal_max_files); + if (r < 0) + return log_error_errno(r, "Failed to parse --forward-journal-max-files= value: %s", opts.arg); + break; + + OPTION_GROUP("SSH"): {} + + OPTION_LONG("pass-ssh-key", "BOOL", "Create an SSH key to access the VM"): + r = parse_boolean_argument("--pass-ssh-key=", opts.arg, &arg_pass_ssh_key); + if (r < 0) + return r; + break; + + OPTION_LONG("ssh-key-type", "TYPE", "Choose what type of SSH key to pass"): + if (isempty(opts.arg)) { + arg_ssh_key_type = mfree(arg_ssh_key_type); + break; + } + + if (!string_is_safe(opts.arg, STRING_ALLOW_GLOBS)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value for --ssh-key-type=: %s", opts.arg); + + r = free_and_strdup_warn(&arg_ssh_key_type, opts.arg); + if (r < 0) + return r; + break; + + OPTION_GROUP("Input/Output"): {} + + OPTION_LONG("console", "MODE", + "Console mode (interactive, native, gui, read-only or headless)"): + arg_console_mode = console_mode_from_string(opts.arg); + if (arg_console_mode < 0) + return log_error_errno(arg_console_mode, "Failed to parse specified console mode: %s", opts.arg); + break; - default: - assert_not_reached(); + OPTION_LONG("console-transport", "TRANSPORT", "Console transport (virtio or serial)"): + arg_console_transport = console_transport_from_string(opts.arg); + if (arg_console_transport < 0) + return log_error_errno(arg_console_transport, "Failed to parse specified console transport: %s", opts.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(opts.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, opts.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, opts.arg); + if (r < 0) + return r; + break; } /* Drop duplicate --bind-user= and --bind-user-group= entries */ @@ -807,6 +951,18 @@ static int parse_argv(int argc, char *argv[]) { 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="); @@ -826,8 +982,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(&opts); + if (!strv_isempty(args)) { + arg_kernel_cmdline_extra = strv_copy(args); if (!arg_kernel_cmdline_extra) return log_oom(); } @@ -956,6 +1113,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 +1301,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 +1335,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 +1353,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 +1374,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 +1415,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 +1532,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 +1568,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 +1599,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(); @@ -1376,58 +1616,7 @@ static int start_tpm( if (r < 0) 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 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); + r = fork_notify(argv, /* child_handler= */ NULL, /* child_userdata= */ NULL, ret_pidref); if (r < 0) return r; @@ -1471,33 +1660,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( @@ -1533,7 +1719,7 @@ static int start_virtiofsd( union sockaddr_union su; r = sockaddr_un_set_path(&su.un, listen_address); if (r < 0) - return r; + return log_error_errno(r, "Failed to prepare unix socket '%s': %m", listen_address); _cleanup_close_ int sock = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); if (sock < 0) @@ -1555,8 +1741,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 +1894,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,18 +2131,435 @@ static int on_request_stop(sd_bus_message *m, void *userdata, sd_bus_error *erro return 0; } +static int make_sidecar_path(const char *suffix, char **ret) { + int r; + + assert(suffix); + assert(ret); + + const char *p = ASSERT_PTR(arg_image ?: arg_directory); + + _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); + + if (!strextend(&filename, suffix)) + return log_oom(); + + _cleanup_free_ char *j = path_join(parent, filename); + if (!j) + return log_oom(); + + *ret = TAKE_PTR(j); + return 0; +} + +/* 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); + + if (strlen(filename) <= max_len) + return strdup_to(ret, filename); + + uint8_t hash[SHA256_DIGEST_SIZE]; + sha256_direct(filename, strlen(filename), hash); + + _cleanup_free_ char *serial = hexmem(hash, max_len / 2); + if (!serial) + return -ENOMEM; + + *ret = TAKE_PTR(serial); + return 0; +} + +static int cmdline_add_ovmf(FILE *config_file, const OvmfConfig *ovmf_config, char **ret_ovmf_vars) { + int r; + + assert(config_file); + assert(ret_ovmf_vars); + + if (!ovmf_config) { + *ret_ovmf_vars = NULL; + return 0; + } + + 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; + + if (!ovmf_config->vars && !arg_efi_nvram_template) { + *ret_ovmf_vars = NULL; + return 0; + } + + 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 r; + + log_debug("Storing EFI NVRAM state persistently under '%s'.", arg_efi_nvram_state_path); + } + + 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(); + + 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); + + 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]; + + r = qemu_config_section(config_file, "chardev", "qmp", + "backend", "socket"); + if (r < 0) + return r; + + r = qemu_config_keyf(config_file, "fd", "%d", bridge_fds[1]); + if (r < 0) + return r; + + 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; + } + + 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(); + + 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); + } + + 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; + + 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(); + + 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 + arg_bind_volumes.n_items); + 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; + + r = vmspawn_bind_volume_prepare_boot(arg_runtime_scope, &arg_bind_volumes, 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, *kernel = 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; - const char *accel; int r; CLEANUP_ARRAY(children, n_children, fork_notify_terminate_many); @@ -1966,7 +2568,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { /* 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) { + 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 open system bus: %m"); @@ -1978,114 +2580,188 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { (void) sd_bus_set_allow_interactive_authorization(system_bus, arg_ask_password); } - /* 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); + /* 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 open user bus: %m"); + + 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_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_confidential_computing == COCO_AMD_SEV_SNP && !use_kvm) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "--coco=sev-snp requires KVM, but KVM is not available."); + + if (arg_firmware_type == FIRMWARE_UEFI && arg_confidential_computing != COCO_AMD_SEV_SNP) { + 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 (arg_confidential_computing == COCO_AMD_SEV_SNP) { + r = qemu_config_key(config_file, "kernel-irqchip", "split"); + 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 log_error_errno(r, "Failed to open system bus: %m"); + return r; + } - r = sd_bus_set_close_on_exit(user_bus, false); + if (ARCHITECTURE_SUPPORTS_CXL && arg_confidential_computing == COCO_NO) { + r = qemu_config_key(config_file, "cxl", "on"); if (r < 0) - return log_error_errno(r, "Failed to disable close-on-exit behaviour: %m"); - - runtime_bus = sd_bus_ref(user_bus); + return r; } - bool use_kvm = arg_kvm > 0; - if (arg_kvm < 0) { - r = qemu_check_kvm_support(); + if (arg_directory || arg_runtime_mounts.n_mounts != 0) { + r = qemu_config_key(config_file, "memory-backend", "mem"); if (r < 0) - return log_error_errno(r, "Failed to check for KVM support: %m"); - use_kvm = r; + return r; } - 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"); - - if (arg_secure_boot > 0 && !ovmf_config->supports_sb) { - assert(arg_firmware); - - return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), - "Secure Boot requested, but supplied OVMF firmware blob doesn't support it."); + if (ARCHITECTURE_SUPPORTS_HPET) { + r = qemu_config_key(config_file, "hpet", "off"); + if (r < 0) + return r; } - if (arg_secure_boot < 0) - log_debug("Using OVMF firmware %s Secure Boot support.", ovmf_config->supports_sb ? "with" : "without"); + if (arg_confidential_computing == COCO_AMD_SEV_SNP) { + r = qemu_config_key(config_file, "confidential-guest-support", "snp0"); + if (r < 0) + return r; + } - _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); + r = qemu_config_section(config_file, "smp-opts", /* id= */ NULL, + "cpus", arg_cpus ?: "1"); if (r < 0) return r; - r = bind_user_setup(bind_user_context, &arg_credentials, &arg_runtime_mounts); + r = qemu_config_section(config_file, "memory", /* id= */ NULL, + "size", mem); 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 (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_ram_max > 0) { + r = qemu_config_keyf(config_file, "maxmem", "%" PRIu64 "M", DIV_ROUND_UP(arg_ram_max, U64_MB)); 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); + r = qemu_config_keyf(config_file, "slots", "%u", arg_ram_slots > 0 ? arg_ram_slots : 1u); + 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."); + r = qemu_config_section(config_file, "object", "rng0", + "qom-type", "rng-random", + "filename", "/dev/urandom"); 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(); + return r; - 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) - return log_oom(); + r = qemu_config_section(config_file, "device", "rng-device0", + "driver", "virtio-rng-pci", + "rng", "rng0"); + if (r < 0) + return r; - if (!sd_id128_is_null(arg_uuid)) - if (strv_extend_many(&cmdline, "-uuid", SD_ID128_TO_UUID_STRING(arg_uuid)) < 0) - return log_oom(); + if (arg_confidential_computing == COCO_NO) { + r = qemu_config_section(config_file, "device", "balloon0", + "driver", "virtio-balloon", + "free-page-reporting", "on"); + 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) { @@ -2096,44 +2772,37 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_error_errno(r, "Failed to make up randomized vmgenid: %m"); } - if (strv_extend(&cmdline, "-device") < 0) - return log_oom(); + r = qemu_config_section(config_file, "device", "vmgenid0", + "driver", "vmgenid"); + if (r < 0) + return r; - if (strv_extendf(&cmdline, "vmgenid,guid=" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(vmgenid)) < 0) - return log_oom(); + r = qemu_config_keyf(config_file, "guid", SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(vmgenid)); + if (r < 0) + return r; } - /* 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; + /* 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 (asprintf(&subdir, "systemd/vmspawn.%" PRIx64, random_u64()) < 0) + 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 = runtime_directory(arg_runtime_scope, subdir, &runtime_dir); - 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); - if (r < 0) - return log_error_errno(r, "Failed to create runtime directory '%s': %m", runtime_dir); - - /* We created this, hence also destroy it */ - runtime_dir_destroy = TAKE_PTR(runtime_dir); - - runtime_dir = strdup(runtime_dir_destroy); - if (!runtime_dir) - return log_oom(); - } - - log_debug("Using runtime directory: %s", runtime_dir); - } - _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 +2824,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 +2888,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,53 +2900,63 @@ 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; + } + + if (arg_confidential_computing == COCO_AMD_SEV_SNP) { + /* SNP marks encrypted guest pages via the "C-bit" in the page table entry. On all + * SNP-capable processors (Milan and later) the C-bit lives at bit 51, which reduces + * the usable guest physical address space by one bit. + * Embed the hashes of kernel, initrd and cmdline into the firmware + * so they are covered by the launch measurement and the guest's + * boot chain starts from a measured state. */ + r = qemu_config_section(config_file, "object", "snp0", + "qom-type", "sev-snp-guest", + "cbitpos", "51", + "reduced-phys-bits", "1", + "kernel-hashes", "on"); + 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; } - r = strv_extend_many(&cmdline, "-cpu", + /* -cpu stays on cmdline since not all flags are supported in config. SNP needs a stable, + * named CPU model so the launch measurement is reproducible across hosts; EPYC-v4 is the + * baseline that covers all SNP-capable processors (Milan and later). */ + const char *cpu_model = #ifdef __x86_64__ - "max,hv_relaxed,hv-vapic,hv-time" + arg_confidential_computing == COCO_AMD_SEV_SNP ? "EPYC-v4" + : "max,hv_relaxed,hv-vapic,hv-time"; #else - "max" + "max"; #endif - ); + r = strv_extend_many(&cmdline, "-cpu", cpu_model); if (r < 0) return log_oom(); @@ -2295,125 +2964,154 @@ 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(); + 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 = 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(); + r = qemu_config_section(config_file, "device", "virtio-serial0", + "driver", "virtio-serial"); + if (r < 0) + return r; - _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; + r = qemu_config_section(config_file, "chardev", "vdagent", + "backend", "qemu-vdagent", + "clipboard", "on", + "debug", "0"); + if (r < 0) + return r; - r = tempfn_random_child(NULL, "vmspawn-", &ovmf_vars_to); + r = qemu_config_section(config_file, "device", "vdagent-port0", + "driver", "virtserialport", + "chardev", "vdagent", + "name", "org.qemu.guest_agent.0"); 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); + /* Attach a USB xHCI controller and a USB keyboard. We prefer USB over the implicit PS/2 + * keyboard so that EDK2's UsbKbDxe driver runs, which registers the default HII keyboard + * layout package — the PS/2 driver does not. That makes + * EFI_HII_DATABASE_PROTOCOL.GetKeyboardLayout() return a usable layout, which systemd-boot + * then exports via the LoaderKeyboardLayout EFI variable, which is useful for testing that + * codepath actually works. */ + r = qemu_config_section(config_file, "device", "xhci0", + "driver", "qemu-xhci"); + if (r < 0) + return r; - 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, "device", "usb-kbd0", + "driver", "usb-kbd", + "bus", "xhci0.0"); + if (r < 0) + return r; - r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK); + /* When using --console=gui the QEMU window closes immediately when the VM has stopped, so + * any console output at shutdown is lost, which makes debugging difficult. Ensure the VM + * stays booted for a minimum of 15s. */ + r = strv_prepend(&arg_kernel_cmdline_extra, "systemd.minimum_uptime_sec=15"); if (r < 0) - return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", ovmf_vars_from, ovmf_vars_to); + return log_oom(); - /* 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; + + 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; + } + } - r = strv_extendf(&cmdline, "file=%s,if=pflash,format=%s", escaped_ovmf_vars_to, ovmf_config_format(ovmf_config)); + _cleanup_(unlink_and_freep) char *ovmf_vars = NULL; + if (arg_confidential_computing != COCO_NO) { + r = strv_extend_many(&cmdline, "-bios", arg_firmware); if (r < 0) - return log_oom(); + return r; + } else { + r = cmdline_add_ovmf(config_file, ovmf_config, &ovmf_vars); + if (r < 0) + return r; } - if (kernel) { - r = strv_extend_many(&cmdline, "-kernel", kernel); + 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 +3129,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; @@ -2494,10 +3171,10 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { &tree_local_lock, &snapshot_directory); if (r < 0) - return r; + return log_error_errno(r, "Failed to create ephemeral snapshot of '%s': %m", arg_directory); - arg_directory = strdup(snapshot_directory); - if (!arg_directory) + r = free_and_strdup(&arg_directory, snapshot_directory); + if (r < 0) return log_oom(); } @@ -2521,79 +3198,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) - return log_oom(); - - if (strv_extendf(&cmdline, "socket,id=rootdir,path=%s", escaped_listen_address) < 0) - return log_oom(); - - if (strv_extend_many( - &cmdline, - "-device", - "vhost-user-fs-pci,queue-size=1024,chardev=rootdir,tag=root") < 0) - return log_oom(); - - 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(); - - _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); - - _cleanup_free_ char *escaped_drive_fn = escape_qemu_value(drive_fn); - if (!escaped_drive_fn) + _cleanup_free_ char *id = strdup("rootdir"), *tag = strdup("root"); + if (!id || !tag) 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, "virtio-blk-pci,drive=vmspawn_extra_%zu,serial=%s", i++, escaped_drive_fn) < 0) + 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(); } - if (arg_console_mode != CONSOLE_GUI) { - r = strv_prepend(&arg_kernel_cmdline_extra, "console=hvc0"); + /* Extra drive validation is done in the post-fork drive info construction loop + * to avoid stat()'ing each drive twice. */ + + 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_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_consume_prepend(&arg_kernel_cmdline_extra, TAKE_PTR(p)) < 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 +3274,66 @@ 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 >= INT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EFBIG), + "Existing fstab.extra credential is too large (%zu bytes).", + existing->size); + + 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 +3343,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); } @@ -2723,67 +3384,64 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_error_errno(r, "Failed to start tpm: %m"); log_debug_errno(r, "Failed to start tpm, ignoring: %m"); - } - - _cleanup_(sd_event_source_unrefp) sd_event_source *source = NULL; - r = event_add_child_pidref(event, &source, &child, WEXITED, on_child_exit, /* userdata= */ NULL); - if (r < 0) - return r; + } else { + _cleanup_(sd_event_source_unrefp) sd_event_source *source = NULL; + r = event_add_child_pidref(event, &source, &child, WEXITED, on_child_exit, /* userdata= */ NULL); + if (r < 0) + return r; - pidref_done(&child); - children[n_children++] = TAKE_PTR(source); + pidref_done(&child); + children[n_children++] = TAKE_PTR(source); + } } 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(); - - if (strv_extend_many(&cmdline, "-tpmdev", "emulator,id=tpm0,chardev=chrtpm") < 0) - return log_oom(); - - if (native_architecture() == ARCHITECTURE_X86_64) - r = strv_extend_many(&cmdline, "-device", "tpm-tis,tpmdev=tpm0"); - else if (IN_SET(native_architecture(), ARCHITECTURE_ARM64, ARCHITECTURE_ARM64_BE)) - r = strv_extend_many(&cmdline, "-device", "tpm-tis-device,tpmdev=tpm0"); + r = qemu_config_section(config_file, "chardev", "chrtpm", + "backend", "socket", + "path", tpm_socket_address); if (r < 0) - return log_oom(); - } - - char *initrd = NULL; - _cleanup_(rm_rf_physical_and_freep) char *merged_initrd = NULL; - size_t n_initrds = strv_length(arg_initrds); + return r; - if (n_initrds == 1) - initrd = arg_initrds[0]; - else if (n_initrds > 1) { - r = merge_initrds(&merged_initrd); + r = qemu_config_section(config_file, "tpmdev", "tpm0", + "type", "emulator", + "chardev", "chrtpm"); if (r < 0) return r; - initrd = merged_initrd; - } + const char *tpm_driver; + if (native_architecture() == ARCHITECTURE_X86_64) + tpm_driver = "tpm-tis"; + else if (IN_SET(native_architecture(), ARCHITECTURE_ARM64, ARCHITECTURE_ARM64_BE)) + tpm_driver = "tpm-tis-device"; + else + tpm_driver = NULL; - if (initrd) { - r = strv_extend_many(&cmdline, "-initrd", initrd); - if (r < 0) - return log_oom(); + if (tpm_driver) { + r = qemu_config_section(config_file, "device", "tpmdev0", + "driver", tpm_driver, + "tpmdev", "tpm0"); + if (r < 0) + return r; + } } 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; @@ -2841,58 +3499,188 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { /* on distros that provide their own sshd@.service file we need to provide a dropin which * picks up our public key credential */ + /* sshd reads AuthorizedKeysFile after dropping to the authenticating user's UID, so the + * 0400 credential file under %d/ is unreadable for non-root users. Materialize a 0444 + * copy in a RuntimeDirectory so the ephemeral key works for any user. */ r = machine_credential_add( &arg_credentials, "systemd.unit-dropin.sshd-vsock@.service", "[Service]\n" + "ExecStartPre=systemd-tmpfiles --create --inline 'f^ /run/sshd-vsock-%i/authorized_keys 0444 root root - ssh.ephemeral-authorized_keys-all'\n" "ExecStart=\n" - "ExecStart=-sshd -i -o 'AuthorizedKeysFile=%d/ssh.ephemeral-authorized_keys-all .ssh/authorized_keys'\n" - "ImportCredential=ssh.ephemeral-authorized_keys-all\n", + "ExecStart=-sshd -i -o 'AuthorizedKeysFile=/run/sshd-vsock-%i/authorized_keys .ssh/authorized_keys'\n" + "ImportCredential=ssh.ephemeral-authorized_keys-all\n" + "RuntimeDirectory=sshd-vsock-%i\n", SIZE_MAX); if (r < 0) 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"); - n = base64mem(cred->data, cred->size, &cred_data_b64); - if (n < 0) - return log_oom(); + r = add_vsock_credential(notify_sock_fd); + if (r < 0) + return log_error_errno(r, "Failed to add VSOCK credential: %m"); + } - p = path_join(smbios_dir, cred->id); - if (!p) - return log_oom(); + /* Under --coco=sev-snp the SMBIOS and fw_cfg channels normally used to deliver credentials are + * not covered by the launch measurement and are silently discarded by the guest PID1 in + * confidential VMs. Instead, package credentials into a cpio archive appended to the initrd + * (mirroring what systemd-stub does for ESP credentials) so they enter the launch measurement + * via QEMU's "kernel-hashes=on". The new initrd path requires a guest PID1 that knows about + * /.extra/system_credentials/, so we keep this scoped to SNP for now. Non-CoCo guests + * continue to use the SMBIOS path below, which works with older systemd versions too. + * Must run after all credential-mutating calls above so the cpio captures the complete set. */ + bool use_initrd_cpio = arg_confidential_computing == COCO_AMD_SEV_SNP && + arg_credentials.n_credentials > 0; + + _cleanup_(unlink_and_freep) char *credentials_cpio_path = NULL; + if (use_initrd_cpio) { + r = initrd_cpio_credentials_to_tempfile(&arg_credentials, &credentials_cpio_path); + if (r < 0) + return r; + r = strv_extend(&arg_initrds, credentials_cpio_path); + if (r < 0) + return log_oom(); + } + + char *initrd = NULL; + _cleanup_(rm_rf_physical_and_freep) char *merged_initrd = NULL; + size_t n_initrds = strv_length(arg_initrds); + + if (n_initrds == 1) + initrd = arg_initrds[0]; + else if (n_initrds > 1) { + r = merge_initrds(&merged_initrd); + if (r < 0) + return r; + + initrd = merged_initrd; + } + + if (initrd) { + r = strv_extend_many(&cmdline, "-initrd", initrd); + if (r < 0) + return log_oom(); + } + + /* Under SNP, credentials flow via the initrd cpio above. For everyone else, use the + * SMBIOS/fw_cfg/cmdline path. */ + if (!use_initrd_cpio) { + r = cmdline_add_credentials(&cmdline, smbios_dir_fd, smbios_dir); + if (r < 0) + return r; + } + + r = cmdline_add_kernel_cmdline(&cmdline, smbios_dir_fd, smbios_dir); + if (r < 0) + return r; - 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); + _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 the PCI devices that assign_pcie_ports() will place on a builtin port: + * one per non-SCSI drive (root + extras + bind volumes; SCSI drives share a + * virtio-scsi-pci controller drawn from the hotplug pool, see + * assign_pcie_ports()), one if network is configured, one per virtiofs entry, + * one if vsock is in use. Plus a fixed pool of hotplug spares for runtime + * device_add. */ + size_t n_drive_ports = 0; + if (!IN_SET(arg_image_disk_type, DISK_TYPE_VIRTIO_SCSI, DISK_TYPE_VIRTIO_SCSI_CDROM)) + n_drive_ports++; + FOREACH_ARRAY(d, arg_extra_drives.drives, arg_extra_drives.n_drives) { + DiskType dt = d->disk_type >= 0 ? d->disk_type : arg_image_disk_type; + if (!IN_SET(dt, DISK_TYPE_VIRTIO_SCSI, DISK_TYPE_VIRTIO_SCSI_CDROM)) + n_drive_ports++; + } + FOREACH_ARRAY(bv, arg_bind_volumes.items, arg_bind_volumes.n_items) { + DiskType dt = disk_type_from_bind_volume_config((*bv)->config); + if (dt < 0) + continue; /* unreachable: parser rejects invalid configs */ + if (!IN_SET(dt, DISK_TYPE_VIRTIO_SCSI, DISK_TYPE_VIRTIO_SCSI_CDROM)) + n_drive_ports++; + } + + size_t n_pcie_ports = + n_drive_ports + /* non-SCSI drives */ + (arg_network_stack != NETWORK_STACK_NONE ? 1 : 0) + /* network */ + (arg_directory ? 1 : 0) + /* rootdir virtiofs */ + arg_runtime_mounts.n_mounts + /* runtime virtiofs */ + (use_vsock ? 1 : 0) + /* vsock */ + VMSPAWN_PCIE_HOTPLUG_SPARES; /* hotplug pool */ + + /* Guard the unsigned subtraction below against future refactors that might drop the + * fixed additions. */ + assert(n_pcie_ports >= VMSPAWN_PCIE_HOTPLUG_SPARES); + + /* Cap derived from the packing range: cannot exceed VMSPAWN_PCIE_PACK_MAX_PORTS + * (= 15 slots × 8 functions = 120) without running into the 0x1f LPC slot. */ + if (n_pcie_ports > VMSPAWN_PCIE_PACK_MAX_PORTS) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), + "Too many PCIe root ports requested (%zu, max %u). " + "Reduce the number of extra drives or runtime mounts.", + n_pcie_ports, (unsigned) VMSPAWN_PCIE_PACK_MAX_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 = 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"); + /* chassis/slot are the PCIe-chassis identity (ACPI hotplug paths), + * independent of the PCI bus address below. */ + 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; + + /* Pack 8 root ports per pcie.0 device-number as multifunction, so 14 + * ports cost 2 slots on pcie.0 instead of 14. Each function remains + * independently hot-pluggable (QEMU docs/pcie.txt §5.1). */ + size_t pci_slot = VMSPAWN_PCIE_PACK_BASE_SLOT + i / 8; + size_t pci_fn = i % 8; + assert(pci_slot < VMSPAWN_PCIE_PACK_END_SLOT); + r = qemu_config_keyf(config_file, "addr", "0x%zx.%zu", pci_slot, pci_fn); + if (r < 0) + return r; + if (pci_fn == 0) { + r = qemu_config_key(config_file, "multifunction", "on"); + if (r < 0) + 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 +3692,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 +3707,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 +3737,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 +3811,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 +3835,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 +3931,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 +3966,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 +4049,110 @@ 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. + * Reject a user --initrd= first: discovery would overwrite (and leak) it, and the + * --initrd=/--linux= consistency check in verify_arguments() is bypassed once discovery + * sets arg_linux. */ + if (!strv_isempty(arg_initrds)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--initrd= cannot be used with --directory= unless --linux= is also specified."); + + 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'."); + + if (arg_confidential_computing == COCO_AMD_SEV_SNP) { + if (native_architecture() != ARCHITECTURE_X86_64) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "--coco=sev-snp is only supported on x86_64."); + if (arg_kvm == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--coco=sev-snp requires KVM, remove --kvm=no."); + if (arg_firmware_type != FIRMWARE_UEFI) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--coco can't be used with %s firmware", + firmware_to_string(arg_firmware_type)); + /* SNP can't use pflash + NVRAM split, so the firmware-descriptor + * machinery doesn't apply. Require an explicit raw .fd path and + * use it verbatim with -bios later. */ + if (!arg_firmware) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--coco=sev-snp requires --firmware=PATH " + "pointing at a raw SNP-built OVMF .fd binary."); + log_debug("Using raw SNP firmware at %s (no NVRAM, no Secure Boot).", arg_firmware); + if (set_contains(arg_firmware_features_include, "secure-boot")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--secure-boot=yes cannot be combined with --coco."); + if (arg_tpm > 0) + log_warning("TPM can't be trusted by the confidential computing guest"); + /* kernel-hashes=on only covers what QEMU itself loads via -kernel/-initrd/-append. + * Without --linux= the kernel and initrd come off disk via OVMF and aren't part + * of the launch measurement, leaving the guest unattestable in any meaningful + * way. Require direct kernel boot so the boot chain starts from a measured state. */ + if (!arg_linux) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--coco=sev-snp requires --linux= " + "so kernel, initrd and cmdline are covered by the launch measurement."); + } + return 0; } @@ -3235,10 +4172,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..85f2b5c1cbb37 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,164 @@ 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 opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) 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(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid basename string: %s", opts.arg); - r = free_and_strdup_warn(&arg_filter_basename, optarg); + r = free_and_strdup_warn(&arg_filter_basename, opts.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(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid version string: %s", opts.arg); - r = free_and_strdup_warn(&arg_filter_version, optarg); + r = free_and_strdup_warn(&arg_filter_version, opts.arg); if (r < 0) return r; break; - case 'A': - if (streq(optarg, "native")) + OPTION_SHORT('A', "ARCH", "Look for specified architecture"): + if (streq(opts.arg, "native")) arg_filter_architecture = native_architecture(); - else if (streq(optarg, "secondary")) { + else if (streq(opts.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(opts.arg, "uname")) arg_filter_architecture = uname_architecture(); - else if (streq(optarg, "auto")) + else if (streq(opts.arg, "auto")) arg_filter_architecture = _ARCHITECTURE_INVALID; else { - arg_filter_architecture = architecture_from_string(optarg); + arg_filter_architecture = architecture_from_string(opts.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", opts.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(opts.arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid suffix string: %s", opts.arg); - r = free_and_strdup_warn(&arg_filter_suffix, optarg); + r = free_and_strdup_warn(&arg_filter_suffix, opts.arg); if (r < 0) return r; break; - case 't': - if (isempty(optarg)) + OPTION('t', "type", "TYPE", "Look for specified inode type"): + if (isempty(opts.arg)) arg_filter_type_mask = 0; else { mode_t m; - m = inode_type_from_string(optarg); + m = inode_type_from_string(opts.arg); if (m == MODE_INVALID) - return log_error_errno(m, "Unknown inode type: %s", optarg); + return log_error_errno(m, "Unknown inode type: %s", opts.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(opts.arg, "arch")) /* accept abbreviation too */ arg_print = PRINT_ARCHITECTURE; else - arg_print = print_from_string(optarg); + arg_print = print_from_string(opts.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", opts.arg); break; - case ARG_RESOLVE: - r = parse_boolean(optarg); + OPTION_LONG("resolve", "BOOL", "Canonicalize the result path"): + r = parse_boolean(opts.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(&opts); return 1; } @@ -224,22 +222,24 @@ 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, - /* toplevel_fd= */ AT_FDCWD, + r = path_pick(/* root_path= */ NULL, + /* root_fd= */ AT_FDCWD, + /* dir_fd= */ AT_FDCWD, p, &(PickFilter) { .basename = arg_filter_basename, @@ -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/create-sys-script.py b/test/create-sys-script.py index 11ed185de071b..f2995bcfd6a27 100755 --- a/test/create-sys-script.py +++ b/test/create-sys-script.py @@ -1,28 +1,29 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later -OUTFILE_HEADER = """#!/usr/bin/env python3 +import filecmp +import os +import stat +import subprocess +import sys +import tempfile + +OUTFILE_HEADER = '''#!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later # # create-sys-script.py # # © 2017 Canonical Ltd. # Author: Dan Streetman -""" +''' # Use this only to (re-)create the test/sys-script.py script, # after adding or modifying anything in the test/sys/ directory -import os, sys -import stat -import tempfile -import filecmp -import subprocess - OUTFILE_MODE = 0o775 -OUTFILE_FUNCS = r""" +OUTFILE_FUNCS = r''' import os, sys import shutil @@ -36,9 +37,9 @@ def f(path, mode, contents): with open(path, "wb") as f: f.write(contents) os.chmod(path, mode) -""" +''' -OUTFILE_MAIN = """ +OUTFILE_MAIN = ''' if len(sys.argv) < 2: exit("Usage: {} ".format(sys.argv[0])) @@ -49,7 +50,7 @@ def f(path, mode, contents): if os.path.exists('sys'): shutil.rmtree('sys') -""" +''' def handle_dir(outfile, path): @@ -67,17 +68,17 @@ def escape_single_quotes(b): r = repr(b)[2:-1] # python escapes all ' only if there are ' and " in the string if '"' not in r: - r = r.replace("'", r"\'") + r = r.replace("'", r'\'') # return line with all ' escaped return r def handle_file(outfile, path): m = os.lstat(path).st_mode & 0o777 - with open(path, "rb") as f: + with open(path, 'rb') as f: b = f.read() - if b.count(b"\n") > 1: - r = "\n".join( escape_single_quotes(l) for l in b.split(b"\n") ) + if b.count(b'\n') > 1: + r = '\n'.join(escape_single_quotes(line) for line in b.split(b'\n')) r = f"b'''{r}'''" else: r = repr(b) @@ -85,7 +86,7 @@ def handle_file(outfile, path): def process_sysdir(outfile): - for (dirpath, dirnames, filenames) in os.walk('sys'): + for dirpath, dirnames, filenames in os.walk('sys'): handle_dir(outfile, dirpath) for d in dirnames: path = os.path.join(dirpath, d) @@ -105,17 +106,17 @@ def verify_dir(tmpd, path_a): mode_a = os.lstat(path_a).st_mode mode_b = os.lstat(path_b).st_mode if not stat.S_ISDIR(mode_b): - raise Exception("Not directory") + raise Exception('Not directory') if (mode_a & 0o777) != (mode_b & 0o777): - raise Exception("Permissions mismatch") + raise Exception('Permissions mismatch') def verify_link(tmpd, path_a): path_b = os.path.join(tmpd, path_a) if not stat.S_ISLNK(os.lstat(path_b).st_mode): - raise Exception("Not symlink") + raise Exception('Not symlink') if os.readlink(path_a) != os.readlink(path_b): - raise Exception("Symlink dest mismatch") + raise Exception('Symlink dest mismatch') def verify_file(tmpd, path_a): @@ -123,16 +124,16 @@ def verify_file(tmpd, path_a): mode_a = os.lstat(path_a).st_mode mode_b = os.lstat(path_b).st_mode if not stat.S_ISREG(mode_b): - raise Exception("Not file") + raise Exception('Not file') if (mode_a & 0o777) != (mode_b & 0o777): - raise Exception("Permissions mismatch") + raise Exception('Permissions mismatch') if not filecmp.cmp(path_a, path_b, shallow=False): - raise Exception("File contents mismatch") + raise Exception('File contents mismatch') def verify_script(tmpd): any = False - for (dirpath, dirnames, filenames) in os.walk("sys"): + for dirpath, dirnames, filenames in os.walk('sys'): any = True try: path = dirpath @@ -154,7 +155,8 @@ def verify_script(tmpd): if not any: exit('Nothing found!') -if __name__ == "__main__": + +if __name__ == '__main__': if len(sys.argv) < 2: exit('Usage: create-sys-script.py /path/to/test/') @@ -163,10 +165,9 @@ def verify_script(tmpd): os.chdir(sys.argv[1]) - with open(outfile, "w") as f: + with open(outfile, 'w') as f: os.chmod(outfile, OUTFILE_MODE) - f.write(OUTFILE_HEADER.replace(os.path.basename(sys.argv[0]), - os.path.basename(outfile))) + f.write(OUTFILE_HEADER.replace(os.path.basename(sys.argv[0]), os.path.basename(outfile))) f.write(OUTFILE_FUNCS) f.write(OUTFILE_MAIN) process_sysdir(f) diff --git a/test/fuzz/fuzz-dhcp-client/overload-file-roundtrip b/test/fuzz/fuzz-dhcp-client/overload-file-roundtrip new file mode 100644 index 0000000000000..43cee115a2e12 Binary files /dev/null and b/test/fuzz/fuzz-dhcp-client/overload-file-roundtrip differ diff --git a/test/fuzz/fuzz-dhcp-client/oversized-message b/test/fuzz/fuzz-dhcp-client/oversized-message new file mode 100644 index 0000000000000..a1b3f69aa864c Binary files /dev/null and b/test/fuzz/fuzz-dhcp-client/oversized-message differ diff --git a/test/fuzz/fuzz-dhcp-relay/discover b/test/fuzz/fuzz-dhcp-relay/discover new file mode 100644 index 0000000000000..4ffce3951fef0 Binary files /dev/null and b/test/fuzz/fuzz-dhcp-relay/discover differ diff --git a/test/fuzz/fuzz-dhcp-relay/offer b/test/fuzz/fuzz-dhcp-relay/offer new file mode 100644 index 0000000000000..fe11025f82c50 Binary files /dev/null and b/test/fuzz/fuzz-dhcp-relay/offer differ diff --git a/test/fuzz/fuzz-dhcp-server-relay/sample1 b/test/fuzz/fuzz-dhcp-relay/sample1 similarity index 100% rename from test/fuzz/fuzz-dhcp-server-relay/sample1 rename to test/fuzz/fuzz-dhcp-relay/sample1 diff --git a/test/fuzz/fuzz-dhcp-server-relay/sample2 b/test/fuzz/fuzz-dhcp-relay/sample2 similarity index 100% rename from test/fuzz/fuzz-dhcp-server-relay/sample2 rename to test/fuzz/fuzz-dhcp-relay/sample2 diff --git a/test/fuzz/fuzz-dhcp-server-relay/too-large-packet b/test/fuzz/fuzz-dhcp-relay/too-large-packet similarity index 100% rename from test/fuzz/fuzz-dhcp-server-relay/too-large-packet rename to test/fuzz/fuzz-dhcp-relay/too-large-packet diff --git a/test/fuzz/fuzz-hwdb/empty b/test/fuzz/fuzz-hwdb/empty new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/fuzz/fuzz-hwdb/hwdb-add-property-key b/test/fuzz/fuzz-hwdb/hwdb-add-property-key new file mode 100644 index 0000000000000..8ee12b18dbaf3 Binary files /dev/null and b/test/fuzz/fuzz-hwdb/hwdb-add-property-key differ diff --git a/test/fuzz/fuzz-hwdb/magic-only b/test/fuzz/fuzz-hwdb/magic-only new file mode 100644 index 0000000000000..4257e5d91f618 Binary files /dev/null and b/test/fuzz/fuzz-hwdb/magic-only differ diff --git a/test/fuzz/fuzz-hwdb/node-lookup-bsearch b/test/fuzz/fuzz-hwdb/node-lookup-bsearch new file mode 100644 index 0000000000000..5677045b5a3d4 Binary files /dev/null and b/test/fuzz/fuzz-hwdb/node-lookup-bsearch differ diff --git a/test/fuzz/fuzz-hwdb/overlong-fnmatch-key b/test/fuzz/fuzz-hwdb/overlong-fnmatch-key new file mode 100644 index 0000000000000..a1850c9d7d0b2 Binary files /dev/null and b/test/fuzz/fuzz-hwdb/overlong-fnmatch-key differ diff --git a/test/fuzz/fuzz-hwdb/trie-fnmatch-node-oob b/test/fuzz/fuzz-hwdb/trie-fnmatch-node-oob new file mode 100644 index 0000000000000..26e82c7872983 Binary files /dev/null and b/test/fuzz/fuzz-hwdb/trie-fnmatch-node-oob differ diff --git a/test/fuzz/fuzz-hwdb/trie-fnmatch-stack-overflow b/test/fuzz/fuzz-hwdb/trie-fnmatch-stack-overflow new file mode 100644 index 0000000000000..8f3cd0d8acb53 Binary files /dev/null and b/test/fuzz/fuzz-hwdb/trie-fnmatch-stack-overflow differ diff --git a/test/fuzz/fuzz-hwdb/trie-search-prefix-oob b/test/fuzz/fuzz-hwdb/trie-search-prefix-oob new file mode 100644 index 0000000000000..cb08783aa39a6 Binary files /dev/null and b/test/fuzz/fuzz-hwdb/trie-search-prefix-oob differ diff --git a/test/fuzz/fuzz-pe-binary/empty b/test/fuzz/fuzz-pe-binary/empty new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/fuzz/fuzz-pe-binary/minimal-pe b/test/fuzz/fuzz-pe-binary/minimal-pe new file mode 100644 index 0000000000000..66222cf4dad36 Binary files /dev/null and b/test/fuzz/fuzz-pe-binary/minimal-pe differ diff --git a/test/fuzz/fuzz-pe-binary/truncated-header b/test/fuzz/fuzz-pe-binary/truncated-header new file mode 100644 index 0000000000000..f0c277d1b3f3d Binary files /dev/null and b/test/fuzz/fuzz-pe-binary/truncated-header differ diff --git a/test/fuzz/fuzz-pe-binary/zero-padding b/test/fuzz/fuzz-pe-binary/zero-padding new file mode 100644 index 0000000000000..7265ce755ca27 Binary files /dev/null and b/test/fuzz/fuzz-pe-binary/zero-padding differ diff --git a/test/fuzz/fuzz-pe-binary/zero-padding-dtb b/test/fuzz/fuzz-pe-binary/zero-padding-dtb new file mode 100644 index 0000000000000..1ff64ed12df7d Binary files /dev/null and b/test/fuzz/fuzz-pe-binary/zero-padding-dtb differ diff --git a/test/fuzz/fuzz-pe-binary/zero-padding-uname b/test/fuzz/fuzz-pe-binary/zero-padding-uname new file mode 100644 index 0000000000000..6c6b2ab9c7590 Binary files /dev/null and b/test/fuzz/fuzz-pe-binary/zero-padding-uname differ diff --git a/test/fuzz/fuzz-systemctl-parse-argv/halt.input b/test/fuzz/fuzz-systemctl-parse-argv/halt.input new file mode 100644 index 0000000000000..26b15bd1833ba Binary files /dev/null and b/test/fuzz/fuzz-systemctl-parse-argv/halt.input differ diff --git a/test/fuzz/fuzz-systemctl-parse-argv/shutdown.input b/test/fuzz/fuzz-systemctl-parse-argv/shutdown.input new file mode 100644 index 0000000000000..cc62a983c1f25 Binary files /dev/null and b/test/fuzz/fuzz-systemctl-parse-argv/shutdown.input differ diff --git a/test/fuzz/fuzz-unit-file/directives-all.service b/test/fuzz/fuzz-unit-file/directives-all.service index 9fedef5a63a9e..0d6fadcc967ed 100644 --- a/test/fuzz/fuzz-unit-file/directives-all.service +++ b/test/fuzz/fuzz-unit-file/directives-all.service @@ -24,6 +24,7 @@ AssertHost= AssertIOPressure= AssertKernelCommandLine= AssertKernelVersion= +AssertMachineTag= AssertMemoryPressure= AssertNeedsUpdate= AssertOSRelease= @@ -72,6 +73,7 @@ ConditionHost= ConditionIOPressure= ConditionKernelCommandLine= ConditionKernelVersion= +ConditionMachineTag= ConditionMemoryPressure= ConditionNeedsUpdate= ConditionOSRelease= 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/generate-directives.py b/test/fuzz/generate-directives.py index d05108962f7b8..0f6a744e34421 100755 --- a/test/fuzz/generate-directives.py +++ b/test/fuzz/generate-directives.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later +import collections +import re import sys -import collections, re d = collections.defaultdict(list) for line in open(sys.argv[1]): 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 <> /tmp/success-failure-test-result && systemd-notify --ready && sleep infinity" +ExecStart=bash -c "echo failure >>/tmp/success-failure-test-result && systemd-notify --ready && sleep infinity" diff --git a/test/integration-tests/TEST-24-CRYPTSETUP/meson.build b/test/integration-tests/TEST-24-CRYPTSETUP/meson.build index bb2b2a15f5ebb..e0ee1bdd32677 100644 --- a/test/integration-tests/TEST-24-CRYPTSETUP/meson.build +++ b/test/integration-tests/TEST-24-CRYPTSETUP/meson.build @@ -15,7 +15,7 @@ integration_tests += [ ], 'qemu-args' : [ '-drive', 'id=keydev,if=none,format=raw,cache=unsafe,file=@0@'.format(meson.project_build_root() / 'mkosi.output/keydev.raw'), - '-device', 'scsi-hd,drive=keydev,serial=keydev', + '-device', 'virtio-blk-pci,drive=keydev,serial=keydev', ], 'mkosi-args' : integration_test_template['mkosi-args'] + [ '--runtime-size=11G', diff --git a/test/integration-tests/TEST-25-IMPORT/meson.build b/test/integration-tests/TEST-25-IMPORT/meson.build index 8dec5f37e73a8..3e651107ff5fc 100644 --- a/test/integration-tests/TEST-25-IMPORT/meson.build +++ b/test/integration-tests/TEST-25-IMPORT/meson.build @@ -3,5 +3,8 @@ integration_tests += [ integration_test_template + { 'name' : fs.name(meson.current_source_dir()), + # Newer tar started using openat2() (without openat() fallback) which + # we currently filter out completely in nspawn with --suppress-sync + 'suppress-sync' : false, }, ] diff --git a/test/integration-tests/TEST-59-RELOADING-RESTART/meson.build b/test/integration-tests/TEST-59-RELOADING-RESTART/meson.build index f5fae753e2d02..be275a81fc46e 100644 --- a/test/integration-tests/TEST-59-RELOADING-RESTART/meson.build +++ b/test/integration-tests/TEST-59-RELOADING-RESTART/meson.build @@ -3,6 +3,6 @@ integration_tests += [ integration_test_template + { 'name' : fs.name(meson.current_source_dir()), - 'coredump-exclude-regex' : '/(coreutils|sleep|bash|systemd-notify)$', + 'coredump-exclude-regex' : '/(coreutils|gnusleep|sleep|bash|systemd-notify)$', }, ] diff --git a/test/integration-tests/TEST-64-UDEV-STORAGE/meson.build b/test/integration-tests/TEST-64-UDEV-STORAGE/meson.build index 0b1b3d9a97f4b..a1c28f5e6ae60 100644 --- a/test/integration-tests/TEST-64-UDEV-STORAGE/meson.build +++ b/test/integration-tests/TEST-64-UDEV-STORAGE/meson.build @@ -115,7 +115,7 @@ udev_storage_tests += udev_storage_test_template + { } cmdline = [] -qemu_args = [] +qemu_args = ['-device', 'virtio-scsi-pci,id=scsi0'] # Add 16 multipath devices, each backed by 4 paths foreach ndisk : range(16) @@ -137,7 +137,7 @@ udev_storage_tests += udev_storage_test_template + { } cmdline = [] -qemu_args = [] +qemu_args = ['-device', 'virtio-scsi-pci,id=scsi0'] foreach i : range(10) id = f'drivesimultaneousevents@i@' @@ -176,23 +176,23 @@ udev_storage_tests += udev_storage_test_template + { cmdline = [] qemu_args = [] -foreach i : range(20) +foreach i : range(12) cmdline += [f'--drive=nvme@i@:1M::'] endforeach -foreach i : range(5) +foreach i : range(3) qemu_args += ['-device', f'nvme,drive=nvme@i@,serial=deadbeef@i@,max_ioqpairs=8'] endforeach -foreach i : range(5, 10) +foreach i : range(3, 6) qemu_args += ['-device', f'"nvme,drive=nvme@i@,serial= deadbeef @i@ ,max_ioqpairs=8"'] endforeach -foreach i : range(10, 15) +foreach i : range(6, 9) qemu_args += ['-device', f'"nvme,drive=nvme@i@,serial= dead/beef/@i@ ,max_ioqpairs=8"'] endforeach -foreach i : range(15, 20) +foreach i : range(9, 12) qemu_args += ['-device', f'"nvme,drive=nvme@i@,serial=dead/../../beef/@i@,max_ioqpairs=8"'] endforeach diff --git a/test/integration-tests/TEST-70-TPM2/meson.build b/test/integration-tests/TEST-70-TPM2/meson.build index bf66f8f73e3ce..5932215ceb6f3 100644 --- a/test/integration-tests/TEST-70-TPM2/meson.build +++ b/test/integration-tests/TEST-70-TPM2/meson.build @@ -10,5 +10,8 @@ integration_tests += [ 'vm' : true, 'firmware' : 'auto', 'tpm' : true, + 'cmdline' : integration_test_template['cmdline'] + [ + 'systemd.default_device_timeout_sec=300', + ], }, ] diff --git a/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py new file mode 100755 index 0000000000000..4eb3c95836aca --- /dev/null +++ b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py @@ -0,0 +1,55 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: LGPL-2.1-or-later + +import os +import socket +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_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..ee0b2294c5454 --- /dev/null +++ b/test/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py @@ -0,0 +1,77 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: LGPL-2.1-or-later + +import argparse +import json +import os +import socket +import 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: + self.send_error(400, "Missing 'metrics' 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..f4a4b25ad435c --- /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-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units/fdstore-pin.sh b/test/integration-tests/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units/fdstore-pin.sh index 4cb041a2845cf..5b5daf5618471 100755 --- a/test/integration-tests/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units/fdstore-pin.sh +++ b/test/integration-tests/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units/fdstore-pin.sh @@ -12,7 +12,7 @@ FILE="/tmp/fdstore-data.$PINNED" # a restart, the third a stop followed by a start if [ -e "$COUNTER" ] ; then - read -r N < "$COUNTER" + read -r N <"$COUNTER" else N=0 fi @@ -23,13 +23,13 @@ if [ "$N" -eq 0 ] ; then # First iteration test "${LISTEN_FDS:-0}" -eq 0 test ! -e "$FILE" - echo waldi > "$FILE" - systemd-notify --fd=3 --fdname="fd-$N-$PINNED" 3< "$FILE" + echo waldi >"$FILE" + systemd-notify --fd=3 --fdname="fd-$N-$PINNED" 3<"$FILE" elif [ "$N" -eq 1 ] || { [ "$N" -eq 2 ] && [ "$PINNED" -eq 1 ]; } ; then # Second iteration, or iteration with pinning on test "${LISTEN_FDS:-0}" -eq 1 # We reopen fd #3 here, so that the read offset is at zero each time (hence no <&3 here…) - read -r word < /proc/self/fd/3 + read -r word "$COUNTER" + echo $((N + 1)) >"$COUNTER" fi systemd-notify --ready --status="Ready" 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-86-MULTI-PROFILE-UKI/meson.build b/test/integration-tests/TEST-86-MULTI-PROFILE-UKI/meson.build index 51a70970906b9..acc3512e33d7e 100644 --- a/test/integration-tests/TEST-86-MULTI-PROFILE-UKI/meson.build +++ b/test/integration-tests/TEST-86-MULTI-PROFILE-UKI/meson.build @@ -7,5 +7,8 @@ integration_tests += [ 'vm' : true, 'firmware' : 'auto', 'tpm' : true, + 'cmdline' : integration_test_template['cmdline'] + [ + 'systemd.default_device_timeout_sec=300', + ], }, ] diff --git a/test/integration-tests/TEST-90-RESTRICT-FSACCESS/meson.build b/test/integration-tests/TEST-90-RESTRICT-FSACCESS/meson.build new file mode 100644 index 0000000000000..2f3228f010056 --- /dev/null +++ b/test/integration-tests/TEST-90-RESTRICT-FSACCESS/meson.build @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +# Config subtest: no require_signatures — tests error paths and config parsing. +integration_tests += [ + integration_test_template + { + 'name' : fs.name(meson.current_source_dir()), + 'vm' : true, + 'firmware' : 'auto', + 'configuration' : integration_test_template['configuration'] + { + 'env' : {'TEST_MATCH_SUBTEST' : 'config'}, + }, + }, +] + +# Enforce subtest: with require_signatures=1 — tests actual BPF enforcement +# via the SecureBoot DB → .platform keyring path (firmware: auto enables +# UEFI/SecureBoot in the test VM). +integration_tests += [ + integration_test_template + { + 'name' : fs.name(meson.current_source_dir()) + '-enforce', + 'vm' : true, + 'firmware' : 'auto', + 'cmdline' : integration_test_template['cmdline'] + [ + 'dm_verity.require_signatures=1', + ], + 'configuration' : integration_test_template['configuration'] + { + 'command' : '/usr/lib/systemd/tests/testdata/units/' + fs.name(meson.current_source_dir()) + '.sh', + 'env' : {'TEST_MATCH_SUBTEST' : 'enforce'}, + }, + }, +] + +# dm-verity keyring subtest: exercise the .dm-verity keyring provisioning path +# (kernel commit 033724b1c627, v7.0+) without UEFI/SecureBoot — boots with +# linux-noinitrd so the .platform keyring stays empty and the only way to +# trust the signed verity image is via the dedicated .dm-verity keyring. +integration_tests += [ + integration_test_template + { + 'name' : fs.name(meson.current_source_dir()) + '-dm-verity-keyring', + 'vm' : true, + 'cmdline' : integration_test_template['cmdline'] + [ + 'dm_verity.require_signatures=1', + 'dm_verity.keyring_unsealed=1', + ], + 'configuration' : integration_test_template['configuration'] + { + 'command' : '/usr/lib/systemd/tests/testdata/units/' + fs.name(meson.current_source_dir()) + '.sh', + 'env' : {'TEST_MATCH_SUBTEST' : 'dm-verity-keyring'}, + }, + }, +] diff --git a/test/integration-tests/TEST-91-LIVEUPDATE/meson.build b/test/integration-tests/TEST-91-LIVEUPDATE/meson.build new file mode 100644 index 0000000000000..460ef32476d66 --- /dev/null +++ b/test/integration-tests/TEST-91-LIVEUPDATE/meson.build @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +integration_tests += [ + integration_test_template + { + 'name' : 'TEST-91-LIVEUPDATE', + 'configuration' : integration_test_template['configuration'] + { + 'unit' : integration_test_template['configuration']['unit'] + { + 'DefaultDependencies' : 'no', + }, + 'service' : integration_test_template['configuration']['service'] + { + 'FileDescriptorStoreMax' : '20', + 'FileDescriptorStorePreserve' : 'yes', + 'NotifyAccess' : 'all', + }, + }, + 'cmdline' : ['kho=on', 'liveupdate=on', 'systemd.minimum_uptime_sec=0'], + 'storage' : 'persistent', + 'vm' : true, + 'firmware' : 'uefi', + }, +] diff --git a/test/integration-tests/TEST-92-TPM2-SWTPM/meson.build b/test/integration-tests/TEST-92-TPM2-SWTPM/meson.build new file mode 100644 index 0000000000000..917ad55bb7364 --- /dev/null +++ b/test/integration-tests/TEST-92-TPM2-SWTPM/meson.build @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +integration_tests += [ + integration_test_template + { + 'name' : fs.name(meson.current_source_dir()), + # The test reboots to verify TPM state persists across boots, so keep storage around. + 'storage' : 'persistent', + 'configuration' : integration_test_template['configuration'] + { + 'wants' : '@0@ tpm2.target'.format(integration_test_template['configuration']['wants']), + 'after' : '@0@ tpm2.target'.format(integration_test_template['configuration']['after']), + }, + 'vm' : true, + # We need to boot in EFI mode (with an initrd) so that an ESP exists for the software TPM to + # store its state on and so that systemd-tpm2-generator runs in the initrd. + 'firmware' : 'auto', + # Crucially, do NOT attach a hardware/firmware TPM: that's what makes the generator fall back + # to the software TPM, which is what this test exercises. + 'tpm' : false, + 'cmdline' : integration_test_template['cmdline'] + [ + # Opt into the software TPM fallback (off by default). + 'systemd.tpm2_software_fallback=yes', + 'systemd.default_device_timeout_sec=300', + ], + }, +] diff --git a/test/integration-tests/TEST-83-BTRFS/meson.build b/test/integration-tests/TEST-93-CLONESETUP/meson.build similarity index 100% rename from test/integration-tests/TEST-83-BTRFS/meson.build rename to test/integration-tests/TEST-93-CLONESETUP/meson.build diff --git a/test/integration-tests/integration-test-wrapper.py b/test/integration-tests/integration-test-wrapper.py index 0bbfb6044d434..43d11e0e2e1c0 100755 --- a/test/integration-tests/integration-test-wrapper.py +++ b/test/integration-tests/integration-test-wrapper.py @@ -21,13 +21,13 @@ from types import FrameType from typing import Optional -EMERGENCY_EXIT_DROPIN = """\ +EMERGENCY_EXIT_DROPIN = '''\ [Unit] Wants=emergency-exit.service -""" +''' -EMERGENCY_EXIT_SERVICE = """\ +EMERGENCY_EXIT_SERVICE = '''\ [Unit] DefaultDependencies=no Conflicts=shutdown.target @@ -38,7 +38,7 @@ [Service] ExecStart=false -""" +''' @dataclasses.dataclass(frozen=True) @@ -142,7 +142,19 @@ def process_sanitizer_report(args: argparse.Namespace, journal_file: Path) -> bo fatal_end = re.compile(r'==[0-9]+==HINT:\s+\w+Sanitizer') # 'Standard' errors: - standard_begin = re.compile(r'([0-9]+: runtime error|==[0-9]+==.+?\w+Sanitizer)') + # + # TODO: there's currently a bug in LLVM 22 due to which certain systemd + # units can throw the following warning: + # [ 3366.747202] systemd-oomd[93]: ==93==WARNING: ptrace appears to be blocked (is seccomp enabled?). + # LeakSanitizer may hang. + # [ 3366.747202] systemd-oomd[93]: ==93==Child exited with signal 15. + # + # which is then picked up by the following regex and causes the test to + # fail. Let's, temporarily, exclude this warning from the regex to mitigate + # this. + # + # See: https://github.com/llvm/llvm-project/issues/193714 + standard_begin = re.compile(r'([0-9]+: runtime error|==[0-9]+==(?!WARNING: ptrace).+?\w+Sanitizer)') standard_end = re.compile(r'SUMMARY:\s+(\w+)Sanitizer') # extract COMM @@ -395,6 +407,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() @@ -459,43 +472,43 @@ def main() -> None: name = args.name + (f'-{i}' if (i := os.getenv('MESON_TEST_ITERATION')) else '') dropin = textwrap.dedent( - """\ + '''\ [Service] StandardOutput=journal+console - """ + ''' ) if not shell: dropin += textwrap.dedent( - """ + ''' [Unit] SuccessAction=exit SuccessActionExitStatus=123 - """ + ''' ) if os.getenv('TEST_MATCH_SUBTEST'): dropin += textwrap.dedent( - f""" + f''' [Service] Environment=TEST_MATCH_SUBTEST={os.environ['TEST_MATCH_SUBTEST']} - """ + ''' ) if os.getenv('TEST_MATCH_TESTCASE'): dropin += textwrap.dedent( - f""" + f''' [Service] Environment=TEST_MATCH_TESTCASE={os.environ['TEST_MATCH_TESTCASE']} - """ + ''' ) if os.getenv('TEST_RUN_DFUZZER'): dropin += textwrap.dedent( - f""" + f''' [Service] Environment=TEST_RUN_DFUZZER={os.environ['TEST_RUN_DFUZZER']} - """ + ''' ) if os.getenv('TEST_JOURNAL_USE_TMP', '0') == '1': @@ -512,14 +525,14 @@ def main() -> None: if not sys.stdin.isatty(): dropin += textwrap.dedent( - """ + ''' [Unit] FailureAction=exit - """ + ''' ) elif not shell: dropin += textwrap.dedent( - """ + ''' [Unit] Wants=multi-user.target getty-pre.target Before=getty-pre.target @@ -533,17 +546,17 @@ def main() -> None: IgnoreSIGPIPE=no # bash ignores SIGTERM KillSignal=SIGHUP - """ + ''' ) if sys.stdin.isatty(): dropin += textwrap.dedent( - """ + ''' [Service] ExecStartPre=/usr/lib/systemd/tests/testdata/integration-test-setup.sh setup ExecStopPost=/usr/lib/systemd/tests/testdata/integration-test-setup.sh finalize StateDirectory=%N - """ + ''' ) if args.rtc: @@ -576,7 +589,7 @@ def main() -> None: *(['--forward-journal', journal_file] if journal_file else []), *( [ - '--credential', f'systemd.extra-unit.emergency-exit.service={shlex.quote(EMERGENCY_EXIT_SERVICE)}', # noqa: E501 + '--credential', f'systemd.extra-unit.emergency-exit.service={shlex.quote(EMERGENCY_EXIT_SERVICE)}', '--credential', f'systemd.unit-dropin.emergency.target={shlex.quote(EMERGENCY_EXIT_DROPIN)}', ] if not sys.stdin.isatty() @@ -612,7 +625,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: @@ -683,7 +700,7 @@ def main() -> None: iter = os.environ['GITHUB_RUN_ATTEMPT'] runner = os.environ['TEST_RUNNER'] artifact = ( - f'ci-{wf}-{id}-{iter}-{summary.distribution}-{summary.release}-{runner}-failed-test-journals' # noqa: E501 + f'ci-{wf}-{id}-{iter}-{summary.distribution}-{summary.release}-{runner}-failed-test-journals' ) ops += [f'gh run download {id} --name {artifact} -D ci/{artifact}'] journal_file = Path(f'ci/{artifact}/test/journal/{name}.journal') diff --git a/test/integration-tests/meson.build b/test/integration-tests/meson.build index 5d71e87c79cbc..228d808f51de7 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,17 +92,20 @@ 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', - 'TEST-83-BTRFS', 'TEST-84-STORAGETM', 'TEST-85-NETWORK', 'TEST-86-MULTI-PROFILE-UKI', 'TEST-87-AUX-UTILS-VM', 'TEST-88-UPGRADE', 'TEST-89-RESOLVED-MDNS', + 'TEST-90-RESTRICT-FSACCESS', + 'TEST-91-LIVEUPDATE', + 'TEST-92-TPM2-SWTPM', + 'TEST-93-CLONESETUP', ] subdir(dirname) endforeach @@ -139,6 +143,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..fd3dba3a60093 100644 --- a/test/meson.build +++ b/test/meson.build @@ -124,6 +124,21 @@ if want_tests != 'false' exe, suite : 'udev', args : ['verify', '--resolve-names=late', all_rules]) + + link_abi_targets = [libsystemd, libudev, libshared, libcore] + link_abi_targets += module_targets + foreach _, exe : executables_by_name + link_abi_targets += exe + endforeach + + if conf.get('BUILD_MODE_DEVELOPER') == 1 + test('test-link-abi', + files('test-link-abi.py'), + args : link_abi_targets, + depends : link_abi_targets, + suite : 'dist', + timeout : 120) + endif endif ############################################################ @@ -357,6 +372,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/networkd-test.py b/test/networkd-test.py index d9f7af3678260..27619da6ff1ab 100755 --- a/test/networkd-test.py +++ b/test/networkd-test.py @@ -33,8 +33,7 @@ NETWORK_UNITDIR = '/run/systemd/network' -NETWORKD_WAIT_ONLINE = shutil.which('systemd-networkd-wait-online', - path='/usr/lib/systemd:/lib/systemd') +NETWORKD_WAIT_ONLINE = shutil.which('systemd-networkd-wait-online', path='/usr/lib/systemd:/lib/systemd') RESOLV_CONF = '/run/systemd/resolve/resolv.conf' @@ -44,10 +43,10 @@ def setUpModule(): - global tmpmounts - """Initialize the environment, and perform sanity checks on it.""" + global tmpmounts + if shutil.which('networkctl') is None: raise unittest.SkipTest('networkd not installed') if shutil.which('resolvectl') is None: @@ -57,8 +56,10 @@ def setUpModule(): raise OSError(errno.ENOENT, 'systemd-networkd-wait-online not found') # Do not run any tests if the system is using networkd already and it's not virtualized - if (subprocess.call(['systemctl', 'is-active', '--quiet', 'systemd-networkd.service']) == 0 and - subprocess.call(['systemd-detect-virt', '--quiet']) != 0): + if ( + subprocess.call(['systemctl', 'is-active', '--quiet', 'systemd-networkd.service']) == 0 + and subprocess.call(['systemd-detect-virt', '--quiet']) != 0 + ): raise unittest.SkipTest('not virtualized and networkd is already active') # Ensure we don't mess with an existing networkd config @@ -80,7 +81,9 @@ def setUpModule(): # Generate debugging logs. os.makedirs('/run/systemd/system/systemd-networkd.service.d', exist_ok=True) - with open(f'/run/systemd/system/systemd-networkd.service.d/00-debug.conf', mode='w', encoding='utf-8') as f: + with open( + '/run/systemd/system/systemd-networkd.service.d/00-debug.conf', mode='w', encoding='utf-8' + ) as f: f.write('[Service]\nEnvironment=SYSTEMD_LOG_LEVEL=debug\n') subprocess.call(['systemctl', 'daemon-reload']) @@ -91,10 +94,15 @@ def setUpModule(): if subprocess.call(['getent', 'passwd', 'systemd-network']) != 0: subprocess.call(['useradd', '--system', '--no-create-home', 'systemd-network']) - for d in ['/etc/systemd/network', '/run/systemd/network', - '/run/systemd/netif', '/run/systemd/report', - '/run/systemd/resolve', '/run/systemd/resolve.hook']: - subprocess.check_call(["mount", "-m", "-t", "tmpfs", "none", d]) + for d in [ + '/etc/systemd/network', + '/run/systemd/network', + '/run/systemd/netif', + '/run/systemd/report', + '/run/systemd/resolve', + '/run/systemd/resolve.hook', + ]: + subprocess.check_call(['mount', '-m', '-t', 'tmpfs', 'none', d]) tmpmounts.append(d) if os.path.isdir('/run/systemd/resolve'): os.chmod('/run/systemd/resolve', 0o755) @@ -119,11 +127,11 @@ def setUpModule(): def tearDownModule(): global tmpmounts for d in tmpmounts: - subprocess.check_call(["umount", "--lazy", d]) + subprocess.check_call(['umount', '--lazy', d]) for u in stopped_units: - subprocess.call(["systemctl", "stop", u]) + subprocess.call(['systemctl', 'stop', u]) for u in running_units: - subprocess.call(["systemctl", "restart", u]) + subprocess.call(['systemctl', 'restart', u]) class NetworkdTestingUtilities: @@ -135,14 +143,12 @@ class NetworkdTestingUtilities: def add_veth_pair(self, veth, peer, veth_options=(), peer_options=()): """Add a veth interface pair, and queue them to be removed.""" - subprocess.check_call(['ip', 'link', 'add', 'name', veth] + - list(veth_options) + - ['type', 'veth', 'peer', 'name', peer] + - list(peer_options)) + subprocess.check_call(['ip', 'link', 'add', 'name', veth, *veth_options, + 'type', 'veth', 'peer', 'name', peer, *peer_options]) # fmt: skip self.addCleanup(subprocess.call, ['ip', 'link', 'del', 'dev', peer]) def write_config(self, path, contents): - """"Write a configuration file, and queue it to be removed.""" + """ "Write a configuration file, and queue it to be removed.""" with open(path, 'w') as f: f.write(contents) @@ -155,8 +161,8 @@ def write_network(self, unit_name, contents): def write_network_dropin(self, unit_name, dropin_name, contents): """Write a network unit drop-in, and queue it to be removed.""" - dropin_dir = os.path.join(NETWORK_UNITDIR, "{}.d".format(unit_name)) - dropin_path = os.path.join(dropin_dir, "{}.conf".format(dropin_name)) + dropin_dir = os.path.join(NETWORK_UNITDIR, f'{unit_name}.d') + dropin_path = os.path.join(dropin_dir, f'{dropin_name}.conf') os.makedirs(dropin_dir, exist_ok=True) self.addCleanup(os.rmdir, dropin_dir) @@ -169,7 +175,7 @@ def read_attr(self, link, attribute): # Note we don't want to check if interface `link' is managed, we # want to evaluate link variable and pass the value of the link to # assert_link_states e.g. eth0=managed. - self.assert_link_states(**{link:'managed'}) + self.assert_link_states(**{link: 'managed'}) with open(os.path.join('/sys/class/net', link, attribute)) as f: return f.readline().strip() @@ -191,8 +197,7 @@ def assert_link_states(self, **kwargs): interfaces = set(kwargs) # Wait for the requested interfaces, but don't fail for them. - subprocess.call([NETWORKD_WAIT_ONLINE, '--timeout=5'] + - ['--interface={}'.format(iface) for iface in kwargs]) + subprocess.call([NETWORKD_WAIT_ONLINE, '--timeout=5'] + [f'--interface={iface}' for iface in kwargs]) # Validate each link state found in the networkctl output. out = subprocess.check_output(['networkctl', '--no-legend']).rstrip() @@ -202,14 +207,13 @@ def assert_link_states(self, **kwargs): iface = fields[1] expected = kwargs[iface] actual = fields[-1] - if (actual != expected and - not (expected == 'managed' and actual != 'unmanaged')): - self.fail("Link {} expects state {}, found {}".format(iface, expected, actual)) + if actual != expected and not (expected == 'managed' and actual != 'unmanaged'): + self.fail(f'Link {iface} expects state {expected}, found {actual}') interfaces.remove(iface) # Ensure that all requested interfaces have been covered. if interfaces: - self.fail("Missing links in status output: {}".format(interfaces)) + self.fail(f'Missing links in status output: {interfaces}') class BridgeTest(NetworkdTestingUtilities, unittest.TestCase): @@ -217,7 +221,9 @@ class BridgeTest(NetworkdTestingUtilities, unittest.TestCase): def wait_online(self): try: - subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface', 'port1', '--interface', 'port2', '--timeout=10']) + subprocess.check_call( + [NETWORKD_WAIT_ONLINE, '--interface', 'port1', '--interface', 'port2', '--timeout=10'] + ) except (AssertionError, subprocess.CalledProcessError): # show networkd status, journal, and DHCP server log on failure print('---- interface status ----') @@ -233,40 +239,59 @@ def wait_online(self): print('---- journal ----') subprocess.check_output(['journalctl', '--sync']) sys.stdout.flush() - subprocess.call(['journalctl', '-b', '--no-pager', '--quiet', '-I', '-u', 'systemd-networkd.service']) + subprocess.call( + ['journalctl', '-b', '--no-pager', '--quiet', '-I', '-u', 'systemd-networkd.service'] + ) raise def setUp(self): - self.write_network('50-port1.netdev', '''\ + self.write_network( + '50-port1.netdev', + '''\ [NetDev] Name=port1 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-port2.netdev', '''\ +''', + ) + self.write_network( + '50-port2.netdev', + '''\ [NetDev] Name=port2 Kind=dummy MACAddress=12:34:56:78:9a:bd -''') - self.write_network('50-mybridge.netdev', '''\ +''', + ) + self.write_network( + '50-mybridge.netdev', + '''\ [NetDev] Name=mybridge Kind=bridge -''') - self.write_network('50-port1.network', '''\ +''', + ) + self.write_network( + '50-port1.network', + '''\ [Match] Name=port1 [Network] Bridge=mybridge -''') - self.write_network('50-port2.network', '''\ +''', + ) + self.write_network( + '50-port2.network', + '''\ [Match] Name=port2 [Network] Bridge=mybridge -''') - self.write_network('50-mybridge.network', '''\ +''', + ) + self.write_network( + '50-mybridge.network', + '''\ [Match] Name=mybridge [Network] @@ -274,7 +299,8 @@ def setUp(self): DNS=192.168.250.1 Address=192.168.250.33/24 Gateway=192.168.250.1 -''') +''', + ) subprocess.call(['systemctl', 'reset-failed', 'systemd-networkd', 'systemd-resolved']) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.wait_online() @@ -290,17 +316,18 @@ def tearDown(self): subprocess.check_call(['ip', 'link', 'del', 'port2']) def test_bridge_init(self): - self.assert_link_states( - port1='managed', - port2='managed', - mybridge='managed') + self.assert_link_states(port1='managed', port2='managed', mybridge='managed') def test_bridge_port_priority(self): self.assertEqual(self.read_attr('port1', 'brport/priority'), '32') - self.write_network_dropin('50-port1.network', 'priority', '''\ + self.write_network_dropin( + '50-port1.network', + 'priority', + '''\ [Bridge] Priority=28 -''') +''', + ) subprocess.check_call(['ip', 'link', 'set', 'dev', 'port1', 'down']) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.wait_online() @@ -309,10 +336,14 @@ def test_bridge_port_priority(self): def test_bridge_port_priority_set_zero(self): """It should be possible to set the bridge port priority to 0""" self.assertEqual(self.read_attr('port2', 'brport/priority'), '32') - self.write_network_dropin('50-port2.network', 'priority', '''\ + self.write_network_dropin( + '50-port2.network', + 'priority', + '''\ [Bridge] Priority=0 -''') +''', + ) subprocess.check_call(['ip', 'link', 'set', 'dev', 'port2', 'down']) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.wait_online() @@ -321,7 +352,10 @@ def test_bridge_port_priority_set_zero(self): def test_bridge_port_property(self): """Test the "[Bridge]" section keys""" self.assertEqual(self.read_attr('port2', 'brport/priority'), '32') - self.write_network_dropin('50-port2.network', 'property', '''\ + self.write_network_dropin( + '50-port2.network', + 'property', + '''\ [Bridge] UnicastFlood=true HairPin=true @@ -331,7 +365,8 @@ def test_bridge_port_property(self): AllowPortToBeRoot=true Cost=555 Priority=23 -''') +''', + ) subprocess.check_call(['ip', 'link', 'set', 'dev', 'port2', 'down']) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.wait_online() @@ -345,14 +380,15 @@ def test_bridge_port_property(self): self.assertEqual(self.read_attr('port2', 'brport/bpdu_guard'), '0') self.assertEqual(self.read_attr('port2', 'brport/root_block'), '0') + class ClientTestBase(NetworkdTestingUtilities): """Provide common methods for testing networkd against servers.""" @classmethod def setUpClass(klass): klass.orig_log_level = subprocess.check_output( - ['systemctl', 'show', '--value', '--property', 'LogLevel'], - universal_newlines=True).strip() + ['systemctl', 'show', '--value', '--property', 'LogLevel'], universal_newlines=True + ).strip() subprocess.check_call(['systemd-analyze', 'log-level', 'debug']) @classmethod @@ -368,9 +404,9 @@ def setUp(self): # get current journal cursor subprocess.check_output(['journalctl', '--sync']) - out = subprocess.check_output(['journalctl', '-b', '--quiet', - '--no-pager', '-n0', '--show-cursor'], - universal_newlines=True) + out = subprocess.check_output( + ['journalctl', '-b', '--quiet', '--no-pager', '-n0', '--show-cursor'], universal_newlines=True + ) self.assertTrue(out.startswith('-- cursor:')) self.journal_cursor = out.split()[-1] @@ -383,44 +419,44 @@ def tearDown(self): subprocess.call(['systemctl', 'stop', 'systemd-networkd-varlink.socket']) subprocess.call(['systemctl', 'stop', 'systemd-networkd-varlink-metrics.socket']) subprocess.call(['systemctl', 'stop', 'systemd-networkd.service']) - subprocess.call(['ip', 'link', 'del', 'dummy0'], - stderr=subprocess.DEVNULL) + subprocess.call(['ip', 'link', 'del', 'dummy0'], stderr=subprocess.DEVNULL) def show_journal(self, unit): - '''Show journal of given unit since start of the test''' + """Show journal of given unit since start of the test""" - print('---- {} ----'.format(unit)) + print(f'---- {unit} ----') subprocess.check_output(['journalctl', '--sync']) sys.stdout.flush() - subprocess.call(['journalctl', '-b', '--no-pager', '--quiet', - '--cursor', self.journal_cursor, '-u', unit]) + subprocess.call( + ['journalctl', '-b', '--no-pager', '--quiet', '--cursor', self.journal_cursor, '-u', unit] + ) def show_ifaces(self): - '''Show network interfaces''' + """Show network interfaces""" print('--- networkctl ---') sys.stdout.flush() subprocess.call(['networkctl', 'status', '-n', '0', '-a']) def show_resolvectl(self): - '''Show resolved settings''' + """Show resolved settings""" print('--- resolvectl ---') sys.stdout.flush() subprocess.call(['resolvectl']) def create_iface(self, ipv6=False): - '''Create test interface with DHCP server behind it''' + """Create test interface with DHCP server behind it""" raise NotImplementedError('must be implemented by a subclass') def shutdown_iface(self): - '''Remove test interface and stop DHCP server''' + """Remove test interface and stop DHCP server""" raise NotImplementedError('must be implemented by a subclass') def print_server_log(self): - '''Print DHCP server log for debugging failures''' + """Print DHCP server log for debugging failures""" raise NotImplementedError('must be implemented by a subclass') @@ -432,16 +468,18 @@ def start_unit(self, unit): self.show_journal(unit) raise - def do_test(self, coldplug=True, ipv6=False, extra_opts='', - online_timeout=10, dhcp_mode='yes'): + def do_test(self, coldplug=True, ipv6=False, extra_opts='', online_timeout=10, dhcp_mode='yes'): self.start_unit('systemd-resolved') - self.write_network(self.config, '''\ + self.write_network( + self.config, + f'''\ [Match] -Name={iface} +Name={self.iface} [Network] DHCP={dhcp_mode} {extra_opts} -'''.format(iface=self.iface, dhcp_mode=dhcp_mode, extra_opts=extra_opts)) +''', + ) if coldplug: # create interface first, then start networkd @@ -456,8 +494,9 @@ def do_test(self, coldplug=True, ipv6=False, extra_opts='', self.start_unit('systemd-networkd') try: - subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface', - self.iface, '--timeout=%i' % online_timeout]) + subprocess.check_call( + [NETWORKD_WAIT_ONLINE, '--interface', self.iface, f'--timeout={online_timeout:d}'] + ) if ipv6: # check iface state and IP 6 address; FIXME: we need to wait a bit @@ -465,7 +504,12 @@ def do_test(self, coldplug=True, ipv6=False, extra_opts='', # IPv6, but we want to wait for both for _ in range(10): out = subprocess.check_output(['ip', 'a', 'show', 'dev', self.iface]) - if b'state UP' in out and b'inet6 2600' in out and b'inet 192.168' in out and b'tentative' not in out: + if ( + b'state UP' in out + and b'inet6 2600' in out + and b'inet 192.168' in out + and b'tentative' not in out + ): break time.sleep(1) else: @@ -476,43 +520,43 @@ def do_test(self, coldplug=True, ipv6=False, extra_opts='', else: # should have link-local address on IPv6 only out = subprocess.check_output(['ip', '-6', 'a', 'show', 'dev', self.iface]) - self.assertRegex(out, br'inet6 fe80::.* scope link') + self.assertRegex(out, rb'inet6 fe80::.* scope link') self.assertNotIn(b'scope global', out) # should have IPv4 address out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface]) self.assertIn(b'state UP', out) - self.assertRegex(out, br'inet 192.168.5.\d+/.* scope global dynamic') + self.assertRegex(out, rb'inet 192.168.5.\d+/.* scope global dynamic') # check networkctl state out = subprocess.check_output(['networkctl']) - self.assertRegex(out, (r'{}\s+ether\s+[a-z-]+\s+unmanaged'.format(self.if_router)).encode()) - self.assertRegex(out, (r'{}\s+ether\s+routable\s+configured'.format(self.iface)).encode()) + self.assertRegex(out, (rf'{self.if_router}\s+ether\s+[a-z-]+\s+unmanaged').encode()) + self.assertRegex(out, (rf'{self.iface}\s+ether\s+routable\s+configured').encode()) out = subprocess.check_output(['networkctl', '-n', '0', 'status', self.iface]) - self.assertRegex(out, br'Type:\s+ether') - self.assertRegex(out, br'State:\s+routable.*configured') - self.assertRegex(out, br'Online state:\s+online') - self.assertRegex(out, br'Address:\s+192.168.5.\d+') + self.assertRegex(out, rb'Type:\s+ether') + self.assertRegex(out, rb'State:\s+routable.*configured') + self.assertRegex(out, rb'Online state:\s+online') + self.assertRegex(out, rb'Address:\s+192.168.5.\d+') if ipv6: - self.assertRegex(out, br'2600::') + self.assertRegex(out, rb'2600::') else: - self.assertNotIn(br'2600::', out) - self.assertRegex(out, br'fe80::') - self.assertRegex(out, br'Gateway:\s+192.168.5.1') - self.assertRegex(out, br'DNS:\s+192.168.5.1') + self.assertNotIn(rb'2600::', out) + self.assertRegex(out, rb'fe80::') + self.assertRegex(out, rb'Gateway:\s+192.168.5.1') + self.assertRegex(out, rb'DNS:\s+192.168.5.1') except (AssertionError, subprocess.CalledProcessError): # show networkd status, journal, and DHCP server log on failure with open(os.path.join(NETWORK_UNITDIR, self.config)) as f: - print('\n---- {} ----\n{}'.format(self.config, f.read())) + print(f'\n---- {self.config} ----\n{f.read()}') print('---- interface status ----') sys.stdout.flush() subprocess.call(['ip', 'a', 'show', 'dev', self.iface]) - print('---- networkctl status {} ----'.format(self.iface)) + print(f'---- networkctl status {self.iface} ----') sys.stdout.flush() rc = subprocess.call(['networkctl', '-n', '0', 'status', self.iface]) if rc != 0: - print("'networkctl status' exited with an unexpected code {}".format(rc)) + print(f"'networkctl status' exited with an unexpected code {rc}") self.show_journal('systemd-networkd.service') self.print_server_log() raise @@ -536,18 +580,15 @@ def test_coldplug_dhcp_yes_ip4(self): def test_coldplug_dhcp_yes_ip4_no_ra(self): # with disabling RA explicitly things should be fast - self.do_test(coldplug=True, ipv6=False, - extra_opts='IPv6AcceptRA=no') + self.do_test(coldplug=True, ipv6=False, extra_opts='IPv6AcceptRA=no') def test_coldplug_dhcp_ip4_only(self): # we have a 12s timeout on RA, so we need to wait longer - self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4', - online_timeout=15) + self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4', online_timeout=15) def test_coldplug_dhcp_ip4_only_no_ra(self): # with disabling RA explicitly things should be fast - self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4', - extra_opts='IPv6AcceptRA=no') + self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4', extra_opts='IPv6AcceptRA=no') def test_coldplug_dhcp_ip6(self): self.do_test(coldplug=True, ipv6=True) @@ -560,13 +601,18 @@ def test_hotplug_dhcp_ip6(self): self.do_test(coldplug=False, ipv6=True) def test_route_only_dns(self): - self.write_network('50-myvpn.netdev', '''\ + self.write_network( + '50-myvpn.netdev', + '''\ [NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-myvpn.network', '''\ +''', + ) + self.write_network( + '50-myvpn.network', + '''\ [Match] Name=dummy0 [Network] @@ -574,11 +620,11 @@ def test_route_only_dns(self): Address=192.168.42.100/24 DNS=192.168.42.1 Domains= ~company -''') +''', + ) try: - self.do_test(coldplug=True, ipv6=False, - extra_opts='IPv6AcceptRA=no') + self.do_test(coldplug=True, ipv6=False, extra_opts='IPv6AcceptRA=no') except subprocess.CalledProcessError as e: # networkd often fails to start in LXC: https://github.com/systemd/systemd/issues/11848 if IS_CONTAINER and e.cmd == ['systemctl', 'restart', 'systemd-networkd']: @@ -596,23 +642,28 @@ def test_route_only_dns(self): self.assertNotIn('nameserver 192.168.42.1\n', contents) def test_route_only_dns_all_domains(self): - self.write_network('50-myvpn.netdev', '''[NetDev] + self.write_network( + '50-myvpn.netdev', + '''[NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-myvpn.network', '''[Match] +''', + ) + self.write_network( + '50-myvpn.network', + '''[Match] Name=dummy0 [Network] IPv6AcceptRA=no Address=192.168.42.100/24 DNS=192.168.42.1 Domains= ~company ~. -''') +''', + ) try: - self.do_test(coldplug=True, ipv6=False, - extra_opts='IPv6AcceptRA=no') + self.do_test(coldplug=True, ipv6=False, extra_opts='IPv6AcceptRA=no') except subprocess.CalledProcessError as e: # networkd often fails to start in LXC: https://github.com/systemd/systemd/issues/11848 if IS_CONTAINER and e.cmd == ['systemctl', 'restart', 'systemd-networkd']: @@ -634,7 +685,7 @@ def test_route_only_dns_all_domains(self): @unittest.skipUnless(HAVE_DNSMASQ, 'dnsmasq not installed') class DnsmasqClientTest(ClientTestBase, unittest.TestCase): - '''Test networkd client against dnsmasq''' + """Test networkd client against dnsmasq""" def setUp(self): super().setUp() @@ -642,12 +693,25 @@ def setUp(self): self.iface_mac = 'de:ad:be:ef:47:11' def create_iface(self, ipv6=False, dnsmasq_opts=None): - '''Create test interface with DHCP server behind it''' + """Create test interface with DHCP server behind it""" # add veth pair - subprocess.check_call(['ip', 'link', 'add', 'name', self.iface, - 'address', self.iface_mac, - 'type', 'veth', 'peer', 'name', self.if_router]) + subprocess.check_call( + [ + 'ip', + 'link', + 'add', + 'name', + self.iface, + 'address', + self.iface_mac, + 'type', + 'veth', + 'peer', + 'name', + self.if_router, + ] + ) # give our router an IP subprocess.check_call(['ip', 'a', 'flush', 'dev', self.if_router]) @@ -666,14 +730,24 @@ def create_iface(self, ipv6=False, dnsmasq_opts=None): if dnsmasq_opts: extra_opts += dnsmasq_opts self.dnsmasq = subprocess.Popen( - ['dnsmasq', '--keep-in-foreground', '--log-queries=extra', '--log-dhcp', - '--log-facility=' + self.dnsmasq_log, '--conf-file=/dev/null', - '--dhcp-leasefile=' + lease_file, '--bind-interfaces', - '--interface=' + self.if_router, '--except-interface=lo', - '--dhcp-range=192.168.5.10,192.168.5.200'] + extra_opts) + [ + 'dnsmasq', + '--keep-in-foreground', + '--log-queries=extra', + '--log-dhcp', + '--log-facility=' + self.dnsmasq_log, + '--conf-file=/dev/null', + '--dhcp-leasefile=' + lease_file, + '--bind-interfaces', + '--interface=' + self.if_router, + '--except-interface=lo', + '--dhcp-range=192.168.5.10,192.168.5.200', + ] + + extra_opts + ) def shutdown_iface(self): - '''Remove test interface and stop DHCP server''' + """Remove test interface and stop DHCP server""" if self.if_router: subprocess.check_call(['ip', 'link', 'del', 'dev', self.if_router]) @@ -684,16 +758,17 @@ def shutdown_iface(self): self.dnsmasq = None def print_server_log(self, log_file=None): - '''Print DHCP server log for debugging failures''' + """Print DHCP server log for debugging failures""" path = log_file if log_file else self.dnsmasq_log with open(path) as f: - sys.stdout.write('\n\n---- {} ----\n{}\n------\n\n'.format(os.path.basename(path), f.read())) + sys.stdout.write(f'\n\n---- {os.path.basename(path)} ----\n{f.read()}\n------\n\n') def test_resolved_domain_restricted_dns(self): - '''resolved: domain-restricted DNS servers''' + """resolved: domain-restricted DNS servers""" - # enable DNSSEC in allow downgrade mode, and turn off stuff we don't want to test to make looking at logs easier + # enable DNSSEC in allow downgrade mode, and turn off stuff we don't want to test to make looking at + # logs easier conf = '/run/systemd/resolved.conf.d/test-enable-dnssec.conf' os.makedirs(os.path.dirname(conf), exist_ok=True) with open(conf, 'w') as f: @@ -703,14 +778,17 @@ def test_resolved_domain_restricted_dns(self): # create interface for generic connections; this will map all DNS names # to 192.168.42.1 self.create_iface(dnsmasq_opts=['--address=/#/192.168.42.1']) - self.write_network('50-general.network', '''\ + self.write_network( + '50-general.network', + f'''\ [Match] -Name={} +Name={self.iface} [Network] DHCP=ipv4 IPv6AcceptRA=no DNSSECNegativeTrustAnchors=search.example.com -'''.format(self.iface)) +''', + ) # create second device/dnsmasq for a .company/.lab VPN interface # static IPs for simplicity @@ -721,15 +799,26 @@ def test_resolved_domain_restricted_dns(self): vpn_dnsmasq_log = os.path.join(self.workdir, 'dnsmasq-vpn.log') vpn_dnsmasq = subprocess.Popen( - ['dnsmasq', '--keep-in-foreground', '--log-queries=extra', - '--log-facility=' + vpn_dnsmasq_log, '--conf-file=/dev/null', - '--dhcp-leasefile=/dev/null', '--bind-interfaces', - '--interface=testvpnrouter', '--except-interface=lo', - '--address=/math.lab/10.241.3.3', '--address=/cantina.company/10.241.4.4']) + [ + 'dnsmasq', + '--keep-in-foreground', + '--log-queries=extra', + '--log-facility=' + vpn_dnsmasq_log, + '--conf-file=/dev/null', + '--dhcp-leasefile=/dev/null', + '--bind-interfaces', + '--interface=testvpnrouter', + '--except-interface=lo', + '--address=/math.lab/10.241.3.3', + '--address=/cantina.company/10.241.4.4', + ] + ) self.addCleanup(vpn_dnsmasq.wait) self.addCleanup(vpn_dnsmasq.kill) - self.write_network('50-vpn.network', '''\ + self.write_network( + '50-vpn.network', + '''\ [Match] Name=testvpnclient [Network] @@ -738,11 +827,13 @@ def test_resolved_domain_restricted_dns(self): DNS=10.241.3.1 Domains=~company ~lab DNSSECNegativeTrustAnchors=company lab -''') +''', + ) self.start_unit('systemd-networkd') - subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface', self.iface, - '--interface=testvpnclient', '--timeout=20']) + subprocess.check_call( + [NETWORKD_WAIT_ONLINE, '--interface', self.iface, '--interface=testvpnclient', '--timeout=20'] + ) # ensure we start fresh with every test subprocess.check_call(['systemctl', 'restart', 'systemd-resolved']) @@ -784,7 +875,7 @@ def test_resolved_domain_restricted_dns(self): raise def test_resolved_etc_hosts(self): - '''resolved queries to /etc/hosts''' + """resolved queries to /etc/hosts""" # enabled DNSSEC in allow-downgrade mode conf = '/run/systemd/resolved.conf.d/test-enable-dnssec.conf' @@ -811,10 +902,14 @@ def test_resolved_etc_hosts(self): # note: different IPv4 address here, so that it's easy to tell apart # what resolved the query - self.create_iface(dnsmasq_opts=['--host-record=my.example.com,172.16.99.1,2600::99:99', - '--host-record=other.example.com,172.16.0.42,2600::42', - '--mx-host=example.com,mail.example.com'], - ipv6=True) + self.create_iface( + dnsmasq_opts=[ + '--host-record=my.example.com,172.16.99.1,2600::99:99', + '--host-record=other.example.com,172.16.0.42,2600::42', + '--mx-host=example.com,mail.example.com', + ], + ipv6=True, + ) self.do_test(coldplug=None, ipv6=True) try: @@ -848,7 +943,7 @@ def test_resolved_etc_hosts(self): raise def test_transient_hostname(self): - '''networkd sets transient hostname from DHCP''' + """networkd sets transient hostname from DHCP""" orig_hostname = socket.gethostname() self.addCleanup(socket.sethostname, orig_hostname) @@ -859,7 +954,7 @@ def test_transient_hostname(self): subprocess.check_call(['systemctl', 'stop', 'systemd-hostnamed.service']) self.addCleanup(subprocess.call, ['systemctl', 'stop', 'systemd-hostnamed.service']) - self.create_iface(dnsmasq_opts=['--dhcp-host={},192.168.5.210,testgreen'.format(self.iface_mac)]) + self.create_iface(dnsmasq_opts=[f'--dhcp-host={self.iface_mac},192.168.5.210,testgreen']) self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=no', dhcp_mode='ipv4') try: @@ -873,10 +968,10 @@ def test_transient_hostname(self): if b'testgreen' in out: break time.sleep(5) - sys.stdout.write('[retry %i] ' % retry) + sys.stdout.write(f'[retry {retry}] ') sys.stdout.flush() else: - self.fail('Transient hostname not found in hostnamectl:\n{}'.format(out.decode())) + self.fail(f'Transient hostname not found in hostnamectl:\n{out.decode()}') # and also applied to the system self.assertEqual(socket.gethostname(), 'testgreen') except AssertionError: @@ -886,24 +981,23 @@ def test_transient_hostname(self): raise def test_transient_hostname_with_static(self): - '''transient hostname is not applied if static hostname exists''' + """transient hostname is not applied if static hostname exists""" orig_hostname = socket.gethostname() self.addCleanup(socket.sethostname, orig_hostname) if not os.path.exists('/etc/hostname'): - self.write_config('/etc/hostname', "foobarqux") + self.write_config('/etc/hostname', 'foobarqux') else: - self.write_config('/run/hostname.tmp', "foobarqux") + self.write_config('/run/hostname.tmp', 'foobarqux') subprocess.check_call(['mount', '--bind', '/run/hostname.tmp', '/etc/hostname']) self.addCleanup(subprocess.call, ['umount', '/etc/hostname']) - socket.sethostname("foobarqux"); - + socket.sethostname('foobarqux') subprocess.check_call(['systemctl', 'stop', 'systemd-hostnamed.service']) self.addCleanup(subprocess.call, ['systemctl', 'stop', 'systemd-hostnamed.service']) - self.create_iface(dnsmasq_opts=['--dhcp-host={},192.168.5.210,testgreen'.format(self.iface_mac)]) + self.create_iface(dnsmasq_opts=[f'--dhcp-host={self.iface_mac},192.168.5.210,testgreen']) self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=no', dhcp_mode='ipv4') try: @@ -911,7 +1005,7 @@ def test_transient_hostname_with_static(self): out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface]) self.assertRegex(out, b'inet 192.168.5.210/24 .* scope global dynamic') # static hostname wins over transient one, thus *not* applied - self.assertEqual(socket.gethostname(), "foobarqux") + self.assertEqual(socket.gethostname(), 'foobarqux') except AssertionError: self.show_journal('systemd-networkd.service') self.show_journal('systemd-hostnamed.service') @@ -920,21 +1014,22 @@ def test_transient_hostname_with_static(self): class NetworkdClientTest(ClientTestBase, unittest.TestCase): - '''Test networkd client against networkd server''' + """Test networkd client against networkd server""" def setUp(self): super().setUp() self.dnsmasq = None def create_iface(self, ipv6=False, dhcpserver_opts=None): - '''Create test interface with DHCP server behind it''' + """Create test interface with DHCP server behind it""" # run "router-side" networkd in own mount namespace to shield it from # "client-side" configuration and networkd (fd, script) = tempfile.mkstemp(prefix='networkd-router.sh') self.addCleanup(os.remove, script) with os.fdopen(fd, 'w+') as f: - f.write('''\ + f.write( + '''\ #!/bin/sh set -eu mkdir -p /run/systemd/network @@ -984,34 +1079,46 @@ def create_iface(self, ipv6=False, dhcpserver_opts=None): # run networkd as in systemd-networkd.service exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ {{ s/^.*=//; s/^[@+-]//; s/^!*//; p}}') -'''.format(ifr=self.if_router, - ifc=self.iface, - addr6=('Address=2600::1/64' if ipv6 else ''), - dhopts=(dhcpserver_opts or ''))) +'''.format( + ifr=self.if_router, + ifc=self.iface, + addr6=('Address=2600::1/64' if ipv6 else ''), + dhopts=(dhcpserver_opts or ''), + ) + ) os.fchmod(fd, 0o755) - subprocess.check_call(['systemd-run', '--unit=networkd-test-router.service', - '-p', 'InaccessibleDirectories=-/etc/systemd/network', - '-p', 'InaccessibleDirectories=-/run/systemd/network', - '-p', 'InaccessibleDirectories=-/run/systemd/netif', - '-p', 'InaccessibleDirectories=-/run/systemd/report', - '-p', 'InaccessibleDirectories=-/run/systemd/resolve.hook', - '-p', 'InaccessibleDirectories=-/var/lib/systemd/network', - '--service-type=notify', script]) + subprocess.check_call( + [ + 'systemd-run', + '--unit=networkd-test-router.service', + '-p', 'InaccessibleDirectories=-/etc/systemd/network', + '-p', 'InaccessibleDirectories=-/run/systemd/network', + '-p', 'InaccessibleDirectories=-/run/systemd/netif', + '-p', 'InaccessibleDirectories=-/run/systemd/report', + '-p', 'InaccessibleDirectories=-/run/systemd/resolve.hook', + '-p', 'InaccessibleDirectories=-/var/lib/systemd/network', + '--service-type=notify', + script, + ] + ) # fmt: skip # wait until devices got created for _ in range(50): - if subprocess.run(['ip', 'link', 'show', 'dev', self.if_router], - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0: + if subprocess.run( + ['ip', 'link', 'show', 'dev', self.if_router], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ).returncode == 0: # fmt: skip break time.sleep(0.1) else: subprocess.call(['ip', 'link', 'show', 'dev', self.if_router]) - self.fail('Timed out waiting for {ifr} created.'.format(ifr=self.if_router)) + self.fail(f'Timed out waiting for {self.if_router} created.') def shutdown_iface(self): - '''Remove test interface and stop DHCP server''' + """Remove test interface and stop DHCP server""" if self.if_router: subprocess.check_call(['systemctl', 'stop', 'networkd-test-router.service']) @@ -1021,7 +1128,7 @@ def shutdown_iface(self): self.if_router = None def print_server_log(self): - '''Print DHCP server log for debugging failures''' + """Print DHCP server log for debugging failures""" self.show_journal('networkd-test-router.service') @@ -1038,13 +1145,18 @@ def test_search_domains(self): # we don't use this interface for this test self.if_router = None - self.write_network('50-test.netdev', '''\ + self.write_network( + '50-test.netdev', + '''\ [NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-test.network', '''\ +''', + ) + self.write_network( + '50-test.network', + '''\ [Match] Name=dummy0 [Network] @@ -1052,7 +1164,8 @@ def test_search_domains(self): Address=192.168.42.100/24 DNS=192.168.42.1 Domains= one two three four five six seven eight nine ten -''') +''', + ) self.start_unit('systemd-networkd') @@ -1068,24 +1181,34 @@ def test_dropin(self): # we don't use this interface for this test self.if_router = None - self.write_network('50-test.netdev', '''\ + self.write_network( + '50-test.netdev', + '''\ [NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-test.network', '''\ +''', + ) + self.write_network( + '50-test.network', + '''\ [Match] Name=dummy0 [Network] IPv6AcceptRA=no Address=192.168.42.100/24 DNS=192.168.42.1 -''') - self.write_network_dropin('50-test.network', 'dns', '''\ +''', + ) + self.write_network_dropin( + '50-test.network', + 'dns', + '''\ [Network] DNS=127.0.0.1 -''') +''', + ) self.start_unit('systemd-resolved') self.start_unit('systemd-networkd') @@ -1106,11 +1229,19 @@ def test_dropin(self): self.fail(f'Expected DNS servers not found in resolv.conf: {contents}') def test_dhcp_timezone(self): - '''networkd sets time zone from DHCP''' + """networkd sets time zone from DHCP""" def get_tz(): - out = subprocess.check_output(['busctl', 'get-property', 'org.freedesktop.timedate1', - '/org/freedesktop/timedate1', 'org.freedesktop.timedate1', 'Timezone']) + out = subprocess.check_output( + [ + 'busctl', + 'get-property', + 'org.freedesktop.timedate1', + '/org/freedesktop/timedate1', + 'org.freedesktop.timedate1', + 'Timezone', + ] + ) assert out.startswith(b's "') out = out.strip() assert out.endswith(b'"') @@ -1120,7 +1251,9 @@ def get_tz(): self.addCleanup(subprocess.call, ['timedatectl', 'set-timezone', orig_timezone]) self.create_iface(dhcpserver_opts='EmitTimezone=yes\nTimezone=Pacific/Honolulu') - self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=false\n[DHCP]\nUseTimezone=true', dhcp_mode='ipv4') + self.do_test( + coldplug=None, extra_opts='IPv6AcceptRA=false\n[DHCP]\nUseTimezone=true', dhcp_mode='ipv4' + ) # Should have applied the received timezone. This is asynchronous, so we need to wait for a while: for _ in range(20): @@ -1151,12 +1284,15 @@ def tearDown(self): def test_basic_matching(self): """Verify the Name= line works throughout this class.""" self.add_veth_pair('test_if1', 'fake_if2') - self.write_network('50-test.network', '''\ + self.write_network( + '50-test.network', + '''\ [Match] Name=test_* [Network] IPv6AcceptRA=no -''') +''', + ) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.assert_link_states(test_if1='managed', fake_if2='unmanaged') @@ -1165,15 +1301,17 @@ def test_inverted_matching(self): # Use a MAC address as the interfaces' common matching attribute # to avoid depending on udev, to support testing in containers. mac = '00:01:02:03:98:99' - self.add_veth_pair('test_veth', 'test_peer', - ['addr', mac], ['addr', mac]) - self.write_network('50-no-veth.network', '''\ + self.add_veth_pair('test_veth', 'test_peer', ['addr', mac], ['addr', mac]) + self.write_network( + '50-no-veth.network', + f'''\ [Match] -MACAddress={} +MACAddress={mac} Name=!nonexistent *peer* [Network] IPv6AcceptRA=no -'''.format(mac)) +''', + ) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.assert_link_states(test_veth='managed', test_peer='unmanaged') @@ -1192,14 +1330,14 @@ def setUp(self): # Define the contents of .network files to be read in order. self.configs = ( - "[Match]\nName=m1def\n", - "[Match]\nName=m1unm\n[Link]\nUnmanaged=yes\n", - "[Match]\nName=m1*\n[Link]\nUnmanaged=no\n", + '[Match]\nName=m1def\n', + '[Match]\nName=m1unm\n[Link]\nUnmanaged=yes\n', + '[Match]\nName=m1*\n[Link]\nUnmanaged=no\n', ) # Write out the .network files to be cleaned up automatically. for i, config in enumerate(self.configs): - self.write_network("%02d-test.network" % i, config) + self.write_network(f'{i:02d}-test.network', config) def tearDown(self): """Stop networkd.""" @@ -1215,43 +1353,30 @@ def test_unmanaged_setting(self): """Verify link states with Unmanaged= settings, hot-plug.""" subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.create_iface() - self.assert_link_states(m1def='managed', - m1man='managed', - m1unm='unmanaged', - m0unm='unmanaged') + self.assert_link_states(m1def='managed', m1man='managed', m1unm='unmanaged', m0unm='unmanaged') def test_unmanaged_setting_coldplug(self): """Verify link states with Unmanaged= settings, cold-plug.""" self.create_iface() subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) - self.assert_link_states(m1def='managed', - m1man='managed', - m1unm='unmanaged', - m0unm='unmanaged') + self.assert_link_states(m1def='managed', m1man='managed', m1unm='unmanaged', m0unm='unmanaged') def test_catchall_config(self): """Verify link states with a catch-all config, hot-plug.""" # Don't actually catch ALL interfaces. It messes up the host. - self.write_network('50-all.network', "[Match]\nName=m[01]???\n") + self.write_network('50-all.network', '[Match]\nName=m[01]???\n') subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.create_iface() - self.assert_link_states(m1def='managed', - m1man='managed', - m1unm='unmanaged', - m0unm='managed') + self.assert_link_states(m1def='managed', m1man='managed', m1unm='unmanaged', m0unm='managed') def test_catchall_config_coldplug(self): """Verify link states with a catch-all config, cold-plug.""" # Don't actually catch ALL interfaces. It messes up the host. - self.write_network('50-all.network', "[Match]\nName=m[01]???\n") + self.write_network('50-all.network', '[Match]\nName=m[01]???\n') self.create_iface() subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) - self.assert_link_states(m1def='managed', - m1man='managed', - m1unm='unmanaged', - m0unm='managed') + self.assert_link_states(m1def='managed', m1man='managed', m1unm='unmanaged', m0unm='managed') if __name__ == '__main__': - unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, - verbosity=2)) + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) diff --git a/test/rule-syntax-check.py b/test/rule-syntax-check.py index ec1c75a854cda..777012a2ac98d 100755 --- a/test/rule-syntax-check.py +++ b/test/rule-syntax-check.py @@ -14,21 +14,21 @@ sys.exit('Specify files to test as arguments') quoted_string_re = r'"(?:[^\\"]|\\.)*"' -no_args_tests = re.compile(r'(ACTION|DEVPATH|KERNELS?|NAME|SYMLINK|SUBSYSTEMS?|DRIVERS?|TAG|PROGRAM|RESULT|TEST)\s*(?:=|!)=\s*' + quoted_string_re + '$') +no_args_tests = re.compile(rf'(ACTION|DEVPATH|KERNELS?|NAME|SYMLINK|SUBSYSTEMS?|DRIVERS?|TAG|PROGRAM|RESULT|TEST)\s*(?:=|!)=\s*{quoted_string_re}$') # fmt: skip # PROGRAM can also be specified as an assignment. program_assign = re.compile(r'PROGRAM\s*=\s*' + quoted_string_re + '$') -args_tests = re.compile(r'(ATTRS?|ENV|CONST|TEST){([a-zA-Z0-9/_.*%-]+)}\s*(?:=|!)=\s*' + quoted_string_re + '$') -no_args_assign = re.compile(r'(NAME|SYMLINK|OWNER|GROUP|MODE|TAG|RUN|LABEL|GOTO|OPTIONS|IMPORT)\s*(?:\+=|:=|=)\s*' + quoted_string_re + '$') -args_assign = re.compile(r'(ATTR|ENV|IMPORT|RUN){([a-zA-Z0-9/_.*%-]+)}\s*(=|\+=)\s*' + quoted_string_re + '$') +args_tests = re.compile(rf'(ATTRS?|ENV|CONST|TEST){{([a-zA-Z0-9/_.*%-]+)}}\s*(?:=|!)=\s*{quoted_string_re}$') +no_args_assign = re.compile(rf'(NAME|SYMLINK|OWNER|GROUP|MODE|TAG|RUN|LABEL|GOTO|OPTIONS|IMPORT)\s*(?:\+=|:=|=)\s*{quoted_string_re}$') # fmt: skip +args_assign = re.compile(rf'(ATTR|ENV|IMPORT|RUN){{([a-zA-Z0-9/_.*%-]+)}}\s*(=|\+=)\s*{quoted_string_re}$') # Find comma-separated groups, but allow commas that are inside quoted strings. # Using quoted_string_re + '?' so that strings missing the last double quote # will still match for this part that splits on commas. -comma_separated_group_re = re.compile(r'(?:[^,"]|' + quoted_string_re + '?)+') +comma_separated_group_re = re.compile(rf'(?:[^,"]|{quoted_string_re}?)+') result = 0 buffer = '' for path in rules_files: - print('# looking at {}'.format(path)) + print(f'# looking at {path}') lineno = 0 for line in open(path): lineno += 1 @@ -50,11 +50,14 @@ # it generally improves the readability of the rules. for clause_match in comma_separated_group_re.finditer(line): clause = clause_match.group().strip() - if not (no_args_tests.match(clause) or args_tests.match(clause) or - no_args_assign.match(clause) or args_assign.match(clause) or - program_assign.match(clause)): - - print('Invalid line {}:{}: {}'.format(path, lineno, line)) + if not ( + no_args_tests.match(clause) + or args_tests.match(clause) + or no_args_assign.match(clause) + or args_assign.match(clause) + or program_assign.match(clause) + ): + print(f'Invalid line {path}:{lineno}: {line}') print(' clause:', clause) print() result = 1 diff --git a/test/run-unit-tests.py b/test/run-unit-tests.py index de8ac5c26cb43..1b790d7a5cf47 100755 --- a/test/run-unit-tests.py +++ b/test/run-unit-tests.py @@ -6,8 +6,10 @@ import pathlib import subprocess import sys + try: import colorama as c + GREEN = c.Fore.GREEN YELLOW = c.Fore.YELLOW RED = c.Fore.RED @@ -16,23 +18,38 @@ except ImportError: GREEN = YELLOW = RED = RESET_ALL = BRIGHT = '' + class total: total = None good = 0 skip = 0 fail = 0 + def argument_parser(): p = argparse.ArgumentParser() - p.add_argument('-u', '--unsafe', action='store_true', - help='run "unsafe" tests too') - p.add_argument('-A', '--artifact_directory', - help='store output from failed tests in this dir') - p.add_argument('-s', '--skip', action='append', default=[], - help='skip the named test') + p.add_argument( + '-u', + '--unsafe', + action='store_true', + help='run "unsafe" tests too', + ) + p.add_argument( + '-A', + '--artifact_directory', + help='store output from failed tests in this dir', + ) + p.add_argument( + '-s', + '--skip', + action='append', + default=[], + help='skip the named test', + ) return p + opts = argument_parser().parse_args() unittestdir = pathlib.Path(__file__).parent.absolute() / 'unit-tests' diff --git a/test/sd-script.py b/test/sd-script.py index 51ebf70c39eca..432136e692e6b 100755 --- a/test/sd-script.py +++ b/test/sd-script.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later # +# ruff: noqa: F821 +# # sd-script.py: create LOTS of sd device entries in fake sysfs # # (C) 2018 Martin Wilck, SUSE Linux GmbH @@ -15,63 +17,67 @@ # Note that sys-script.py already creates 10 sd device nodes # (sda+sdb and partitions). This script starts with sdc. -import re -import os import errno +import os +import re import sys + def d(path, mode): os.mkdir(path, mode) -def l(path, src): + +def l(path, src): # noqa: E743 os.symlink(src, path) + def f(path, mode, contents): - with open(path, "wb") as f: + with open(path, 'wb') as f: f.write(contents) os.chmod(path, mode) -class SD(object): +class SD: sd_major = [8] + list(range(65, 72)) + list(range(128, 136)) _name_re = re.compile(r'sd(?P[a-z]*)$') def _init_from_name(self, name): mt = self._name_re.match(name) if mt is None: - raise RuntimeError("invalid name %s" % name) - nm = mt.group("f") + raise RuntimeError(f'invalid name {name}') + nm = mt.group('f') base = 1 ls = nm[-1] nm = nm[:-1] - n = base * (ord(ls)-ord('a')) + n = base * (ord(ls) - ord('a')) while len(nm) > 0: ls = nm[-1] nm = nm[:-1] base *= 26 - n += base * (1 + ord(ls)-ord('a')) + n += base * (1 + ord(ls) - ord('a')) self._num = n def _init_from_dev(self, dev): - maj, min = dev.split(":") + maj, min = dev.split(':') maj = self.sd_major.index(int(maj, 10)) min = int(min, 10) num = int(min / 16) - self._num = 16*maj + num%16 + 256*int(num/16) + self._num = 16 * maj + num % 16 + 256 * int(num / 16) @staticmethod def _disk_num(a, b): - n = ord(a)-ord('a') + n = ord(a) - ord('a') if b != '': - n = 26 * (n+1) + ord(b)-ord('a') + n = 26 * (n + 1) + ord(b) - ord('a') return n @staticmethod def _get_major(n): - return SD.sd_major[(n%256)//16] + return SD.sd_major[(n % 256) // 16] + @staticmethod def _get_minor(n): - return 16 * (n % 16 + 16 * n//256) + return 16 * (n % 16 + 16 * n // 256) @staticmethod def _get_name(n): @@ -81,7 +87,7 @@ def _get_name(n): while n >= 0: s = chr(n % 26 + ord('a')) + s n = n // 26 - 1 - return "sd" + s + return 'sd' + s @staticmethod def _get_dev_t(n): @@ -90,9 +96,9 @@ def _get_dev_t(n): return (maj << 20) + min def __init__(self, arg): - if type(arg) is type(0): + if type(arg) is int: self._num = arg - elif arg.startswith("sd"): + elif arg.startswith('sd'): self._init_from_name(arg) else: self._init_from_dev(arg) @@ -110,9 +116,7 @@ def __hash__(self): return hash(self._num) def __str__(self): - return "%s/%s" % ( - self.devstr(), - self._get_name(self._num)) + return f'{self.devstr()}/{self._get_name(self._num)}' def major(self): return self._get_major(self._num) @@ -121,29 +125,27 @@ def minor(self): return self._get_minor(self._num) def devstr(self): - return "%d:%d" % (self._get_major(self._num), - self._get_minor(self._num)) + return f'{self._get_major(self._num)}:{self._get_minor(self._num)}' def namestr(self): return self._get_name(self._num) def longstr(self): - return "%d\t%s\t%s\t%08x" % (self._num, - self.devstr(), - self.namestr(), - self._get_dev_t(self._num)) + return f'{self._num}\t{self.devstr()}\t{self.namestr()}\t{self._get_dev_t(self._num):08x}' + class MySD(SD): def subst(self, first_sg): disk = { - "lun": self._num, - "major": self.major(), - "devnode": self.namestr(), - "disk_minor": self.minor(), - "sg_minor": first_sg + self._num, + 'lun': self._num, + 'major': self.major(), + 'devnode': self.namestr(), + 'disk_minor': self.minor(), + 'sg_minor': first_sg + self._num, } return disk + disk_template = r"""\ l('sys/bus/scsi/drivers/sd/7:0:0:{lun}', '../../../../devices/pci0000:00/0000:00:1d.7/usb5/5-1/5-1:1.0/host7/target7:0:0/7:0:0:{lun}') l('sys/bus/scsi/devices/7:0:0:{lun}', '../../../devices/pci0000:00/0000:00:1d.7/usb5/5-1/5-1:1.0/host7/target7:0:0/7:0:0:{lun}') @@ -282,29 +284,33 @@ def subst(self, first_sg): """ if len(sys.argv) != 3: - exit("Usage: {} ".format(sys.argv[0])) + exit(f'Usage: {sys.argv[0]} ') if not os.path.isdir(sys.argv[1]): - exit("Target dir {} not found".format(sys.argv[1])) + exit(f'Target dir {sys.argv[1]} not found') + def create_part_sysfs(disk, sd, prt): part = disk - part.update ({ - "part_num": prt, - "part_minor": disk["disk_minor"] + prt, - }) + part.update( + { + 'part_num': prt, + 'part_minor': disk['disk_minor'] + prt, + } + ) try: exec(part_template.format(**part)) except OSError: si = sys.exc_info()[1] - if (si.errno == errno.EEXIST): - print("sysfs structures for %s%d exist" % (sd.namestr(), prt)) + if si.errno == errno.EEXIST: + print(f'sysfs structures for {sd.namestr()}{prt} exist') else: - print("error for %s%d: %s" % (sd.namestr(), prt, si[1])) + print(f'error for {sd.namestr()}{prt}: {si[1]}') raise else: - print("sysfs structures for %s%d created" % (sd.namestr(), prt)) + print(f'sysfs structures for {sd.namestr()}{prt} created') + def create_disk_sysfs(dsk, first_sg, n): sd = MySD(dsk) @@ -314,17 +320,16 @@ def create_disk_sysfs(dsk, first_sg, n): exec(disk_template.format(**disk)) except OSError: si = sys.exc_info()[1] - if (si.errno == errno.EEXIST): - print("sysfs structures for %s exist" % sd.namestr()) - elif (si.errno == errno.ENOENT): - print("error for %s: %s - have you run sys-script py first?" % - (sd.namestr(), si.strerror)) + if si.errno == errno.EEXIST: + print(f'sysfs structures for {sd.namestr()} exist') + elif si.errno == errno.ENOENT: + print(f'error for {sd.namestr()}: {si.strerror} - have you run sys-script py first?') return -1 else: - print("error for %s: %s" % (sd.namestr(), si.strerror)) + print(f'error for {sd.namestr()}: {si.strerror}') raise else: - print("sysfs structures for %s created" % sd.namestr()) + print(f'sysfs structures for {sd.namestr()} created') n += 1 if n >= last: @@ -338,6 +343,7 @@ def create_disk_sysfs(dsk, first_sg, n): return n + os.chdir(sys.argv[1]) n = 0 last = int(sys.argv[2]) diff --git a/test/sys-script.py b/test/sys-script.py index e9ce665313525..be45a211e214c 100755 --- a/test/sys-script.py +++ b/test/sys-script.py @@ -6,30 +6,36 @@ # © 2017 Canonical Ltd. # Author: Dan Streetman -import os, sys +import os import shutil +import sys + def d(path, mode): os.mkdir(path, mode) -def l(path, src): + +def l(path, src): # noqa: E743 os.symlink(src, path) + def f(path, mode, contents): - with open(path, "wb") as f: + with open(path, 'wb') as f: f.write(contents) os.chmod(path, mode) + if len(sys.argv) < 2: - exit("Usage: {} ".format(sys.argv[0])) + exit(f'Usage: {sys.argv[0]} ') if not os.path.isdir(sys.argv[1]): - exit("Target dir {} not found".format(sys.argv[1])) + exit(f'Target dir {sys.argv[1]} not found') os.chdir(sys.argv[1]) if os.path.exists('sys'): shutil.rmtree('sys') +# fmt: off d('sys', 0o755) d('sys/kernel', 0o775) f('sys/kernel/kexec_crash_loaded', 0o664, b'0\n') @@ -16855,3 +16861,4 @@ def f(path, mode, contents): f('sys/devices/platform/i8042/serio1/input/input1/capabilities/sw', 0o644, b'0\n') f('sys/devices/platform/i8042/serio1/input/input1/capabilities/ev', 0o644, b'7\n') f('sys/devices/platform/i8042/serio1/input/input1/capabilities/led', 0o644, b'0\n') +# fmt: on 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-fstab-generator/test-22-bind.expected/initrd-root-fs.target.requires/sysroot.mount b/test/test-fstab-generator/test-22-bind.expected/initrd-root-fs.target.requires/sysroot.mount new file mode 120000 index 0000000000000..0c969cdbd4a99 --- /dev/null +++ b/test/test-fstab-generator/test-22-bind.expected/initrd-root-fs.target.requires/sysroot.mount @@ -0,0 +1 @@ +../sysroot.mount \ No newline at end of file diff --git a/test/test-fstab-generator/test-22-bind.expected/initrd-usr-fs.target.requires/sysroot.mount b/test/test-fstab-generator/test-22-bind.expected/initrd-usr-fs.target.requires/sysroot.mount new file mode 120000 index 0000000000000..0c969cdbd4a99 --- /dev/null +++ b/test/test-fstab-generator/test-22-bind.expected/initrd-usr-fs.target.requires/sysroot.mount @@ -0,0 +1 @@ +../sysroot.mount \ No newline at end of file diff --git a/test/test-fstab-generator/test-22-bind.expected/sysroot.mount b/test/test-fstab-generator/test-22-bind.expected/sysroot.mount new file mode 100644 index 0000000000000..bcc56c61b5057 --- /dev/null +++ b/test/test-fstab-generator/test-22-bind.expected/sysroot.mount @@ -0,0 +1,12 @@ +# Automatically generated by systemd-fstab-generator + +[Unit] +Documentation=man:fstab(5) man:systemd-fstab-generator(8) +SourcePath=/proc/cmdline +Before=initrd-root-fs.target +After=imports.target + +[Mount] +What=/run/machines/root +Where=/sysroot +Options=rw,bind,suid,dev,exec diff --git a/test/test-fstab-generator/test-22-bind.input b/test/test-fstab-generator/test-22-bind.input new file mode 100644 index 0000000000000..3e90e366abb97 --- /dev/null +++ b/test/test-fstab-generator/test-22-bind.input @@ -0,0 +1 @@ +root=bind:/run/machines/root 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-link-abi.py b/test/test-link-abi.py new file mode 100755 index 0000000000000..f069060e4feb4 --- /dev/null +++ b/test/test-link-abi.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1-or-later +"""Verify two ABI properties of the supplied ELF objects: + +1. No imported GLIBC symbol is newer than the baseline (default 2.34). +2. Each binary's NEEDED entries only reference glibc itself or our own + internal shared libraries — anything else means we've grown a hard + link-time dependency on a third-party library that should be dlopen()'d + instead. +""" + +import argparse +import re +import subprocess +import sys + +GLIBC_RE: re.Pattern[str] = re.compile(r'\bGLIBC_(\d+)\.(\d+)(?:\.\d+)?\b') +NEEDED_RE: re.Pattern[str] = re.compile(r'\(NEEDED\)\s+Shared library:\s+\[([^\]]+)\]') + +Version = tuple[int, int] + +# Shared libraries that ship as part of the C library itself; always allowed. +# Matched by prefix so the soversion is not hardcoded. glibc splits libc/libm/etc.; +# musl bundles everything into a single libc whose soname encodes the architecture +# (e.g. libc.musl-x86_64.so.1, libc.musl-aarch64.so.1). +LIBC_LIB_PREFIXES: tuple[str, ...] = ( + 'libc.so.', + 'libm.so.', + 'libresolv.so.', + 'libc.musl-', + 'libucontext.', +) + +# GCC runtime support libraries (stack unwinding, soft-float helpers, C++ standard library). The +# toolchain pulls these in automatically and they're part of every glibc toolchain. Matched by +# prefix so the soversion is not hardcoded. +GCC_LIB_PREFIXES: tuple[str, ...] = ( + 'libasan.so.', + 'libclang_rt.asan.so', + 'libclang_rt.ubsan.so', + 'libubsan.so.', + 'libgcc_s.so.', + 'libstdc++.so.', +) + +# Our own shared libraries; matched by prefix because the soname encodes the +# project version (libsystemd-shared-NNN.so, libsystemd-core-NNN.so). +INTERNAL_LIB_PREFIXES: tuple[str, ...] = ( + 'libsystemd-shared-', + 'libsystemd-core-', + 'libsystemd.so.', + 'libudev.so.', +) + + +def parse_baseline(s: str) -> Version: + m = re.fullmatch(r'(\d+)\.(\d+)', s) + if not m: + raise argparse.ArgumentTypeError(f'baseline must be MAJOR.MINOR, got {s!r}') + return (int(m.group(1)), int(m.group(2))) + + +def is_elf(path: str) -> bool: + """Cheap ELF magic check so readelf isn't run on non-ELF inputs.""" + try: + with open(path, 'rb') as f: + return f.read(4) == b'\x7fELF' + except OSError: + return False + + +def is_internal(name: str) -> bool: + return any(name.startswith(p) for p in INTERNAL_LIB_PREFIXES) + + +def is_dynamic_linker(name: str) -> bool: + # The runtime linker's filename depends on the architecture: ld-linux-x86-64.so.2, + # ld-linux-aarch64.so.1, ld-linux-armhf.so.3, ld-linux-riscv64-lp64d.so.1 on the + # "ld-" naming; ld.so.1 (mips, ppc, s390 32-bit), ld64.so.1 (s390x, ppc64 BE), + # and ld64.so.2 (ppc64le) on the older naming. musl uses ld-musl-.so.1 + # (already covered by the "ld-" prefix). + return name.startswith(('ld-', 'ld.so.', 'ld64.so.')) + + +def is_libc(name: str) -> bool: + return name.startswith(LIBC_LIB_PREFIXES) + + +def glibc_imports(path: str) -> list[tuple[str, str, Version]]: + """Return a sorted list of (symbol, "GLIBC_X.Y", version) triples for every + imported GLIBC-versioned symbol referenced by ``path``.""" + out = subprocess.check_output( + ['readelf', '-W', '--dyn-syms', path], + text=True, + stderr=subprocess.DEVNULL, + ) + + found: set[tuple[str, str, Version]] = set() + for line in out.splitlines(): + m = GLIBC_RE.search(line) + if not m: + continue + # readelf --dyn-syms lists both imported (Ndx=UND) and exported symbols; + # only the former carry a real link-time dependency on the listed glibc + # version. Skip anything that isn't UND. + parts = line.split() + if len(parts) < 8 or parts[6] != 'UND': + continue + ver: Version = (int(m.group(1)), int(m.group(2))) + sym = next((t for t in parts if '@' in t), line.strip()) + found.add((sym, m.group(0), ver)) + return sorted(found) + + +def needed_violations(path: str) -> list[str]: + """Return NEEDED entries outside the always-allowed set.""" + out = subprocess.check_output( + ['readelf', '-W', '-d', path], + text=True, + stderr=subprocess.DEVNULL, + ) + + bad: list[str] = [] + for line in out.splitlines(): + m = NEEDED_RE.search(line) + if not m: + continue + name = m.group(1) + if ( + is_libc(name) + or name.startswith(GCC_LIB_PREFIXES) + or is_dynamic_linker(name) + or is_internal(name) + ): + continue + bad.append(name) + return bad + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument( + '--baseline', + type=parse_baseline, + default=(2, 34), + help='Maximum allowed GLIBC version (default: 2.34)', + ) + ap.add_argument('paths', nargs='*', metavar='PATH', help='ELF files to check') + args = ap.parse_args() + + baseline: Version = args.baseline + + # First pass: collect all imports and NEEDED entries so we can derive an + # effective baseline before reporting any violations. + imports: dict[str, list[tuple[str, str, Version]]] = {} + needed: dict[str, list[str]] = {} + for path in args.paths: + if not is_elf(path): + # Some inputs passed by meson may be scripts or non-ELF + # generators; silently skip those rather than fail. + continue + imports[path] = glibc_imports(path) + needed[path] = needed_violations(path) + + # On architectures where glibc support was added after our baseline (e.g. + # loongarch64, introduced in glibc 2.36), every imported symbol will be + # tagged with a version newer than the baseline, so the baseline check + # would spuriously fail. Detect this by taking the minimum version observed + # across all binaries: if it exceeds the baseline, treat that minimum as + # the effective baseline instead. + observed = [v for syms in imports.values() for _, _, v in syms] + effective_baseline = baseline + if observed: + observed_min = min(observed) + if observed_min > baseline: + effective_baseline = observed_min + print( + f'Note: lowest observed GLIBC import is ' + f'GLIBC_{observed_min[0]}.{observed_min[1]}, newer than the requested baseline ' + f'GLIBC_{baseline[0]}.{baseline[1]}; assuming this architecture has no older ' + f'symbols available and using GLIBC_{effective_baseline[0]}.{effective_baseline[1]} ' + f'as the effective baseline.', + file=sys.stderr, + ) + + failed = 0 + for path, syms in imports.items(): + glibc_bad = sorted({(sym, ver_str) for sym, ver_str, ver in syms if ver > effective_baseline}) + needed_bad = needed[path] + + if glibc_bad or needed_bad: + failed += 1 + print(f'{path}:', file=sys.stderr) + if glibc_bad: + print( + f' imports symbols newer than GLIBC_{effective_baseline[0]}.{effective_baseline[1]}:', + file=sys.stderr, + ) + for sym, ver_str in glibc_bad: + print(f' {sym} ({ver_str})', file=sys.stderr) + if needed_bad: + print(' links against unexpected libraries (dlopen() them instead):', file=sys.stderr) + for name in needed_bad: + print(f' {name}', file=sys.stderr) + + checked = len(imports) + baseline_str = f'GLIBC_{effective_baseline[0]}.{effective_baseline[1]}' + if failed: + print(f'\nFAIL: {failed} of {checked} ELF objects failed the ABI checks.', file=sys.stderr) + return 1 + print( + f'OK: {checked} ELF objects checked; all within {baseline_str} and with only allowed NEEDED entries.' + ) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/test/test-network/conf/25-agent-client-peer.network b/test/test-network/conf/25-agent-client-peer.network index 4d7d758d29778..eaa3dfa57290a 100644 --- a/test/test-network/conf/25-agent-client-peer.network +++ b/test/test-network/conf/25-agent-client-peer.network @@ -3,13 +3,12 @@ Name=client-peer [Network] -Address=192.168.6.2/24 +Address=198.51.100.10/24 DHCPServer=yes -IPv4Forwarding=yes IPv6AcceptRA=no [DHCPServer] -RelayTarget=192.168.5.1 +RelayTarget=192.0.2.1 BindToInterface=no RelayAgentCircuitId=string:sample_circuit_id RelayAgentRemoteId=string:sample_remote_id diff --git a/test/test-network/conf/25-agent-client.network b/test/test-network/conf/25-agent-client.network index 219d40a9b7ca3..731f8fe5b6230 100644 --- a/test/test-network/conf/25-agent-client.network +++ b/test/test-network/conf/25-agent-client.network @@ -4,5 +4,4 @@ Name=client [Network] DHCP=yes -IPv4Forwarding=yes IPv6AcceptRA=no diff --git a/test/test-network/conf/25-agent-server-peer.network b/test/test-network/conf/25-agent-server-peer.network index 5e005c79ecd09..ef1e80fcadb02 100644 --- a/test/test-network/conf/25-agent-server-peer.network +++ b/test/test-network/conf/25-agent-server-peer.network @@ -3,6 +3,5 @@ Name=server-peer [Network] -Address=192.168.5.2/24 -IPv4Forwarding=yes +Address=192.0.2.2/24 IPv6AcceptRA=no diff --git a/test/test-network/conf/25-agent-server.network b/test/test-network/conf/25-agent-server.network deleted file mode 100644 index 0108039e6fa99..0000000000000 --- a/test/test-network/conf/25-agent-server.network +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later -[Match] -Name=server - -[Network] -Address=192.168.5.1/24 -IPv4Forwarding=yes -DHCPServer=yes -IPv6AcceptRA=no - -[DHCPServer] -BindToInterface=no -PoolOffset=150 -PoolSize=1 -DNS=192.168.5.1 -NTP=192.168.5.1 diff --git a/test/test-network/conf/25-agent-veth-server.netdev b/test/test-network/conf/25-agent-veth-server.netdev deleted file mode 100644 index 1427024d4792b..0000000000000 --- a/test/test-network/conf/25-agent-veth-server.netdev +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later -[NetDev] -Name=server -Kind=veth -MACAddress=12:34:56:78:9b:bc - -[Peer] -Name=server-peer -MACAddress=12:34:56:78:9b:bd 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-dhcp-relay-downstream.network b/test/test-network/conf/25-dhcp-relay-downstream.network new file mode 100644 index 0000000000000..49ff765e95f52 --- /dev/null +++ b/test/test-network/conf/25-dhcp-relay-downstream.network @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=relay-down + +[Network] +Address=198.51.100.10/24 +IPv6AcceptRA=no +DHCPRelay=downstream + +[DHCPRelay] +AgentAddress=198.51.100.10 +GatewayAddress=198.51.100.10 +CircuitId=string:test-dhcp-relay-circuit-id +VirtualSubnetSelection=string:subnet-hoge diff --git a/test/test-network/conf/25-dhcp-relay-upstream.network b/test/test-network/conf/25-dhcp-relay-upstream.network new file mode 100644 index 0000000000000..d84f6a71fe6ed --- /dev/null +++ b/test/test-network/conf/25-dhcp-relay-upstream.network @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=relay-up + +[Network] +Address=192.0.2.10/24 +IPv6AcceptRA=no +DHCPRelay=upstream + +[DHCPRelay] +AgentAddress=192.0.2.10 +InterfacePriority=100 diff --git a/test/test-network/conf/25-dhcp-relay.conf b/test/test-network/conf/25-dhcp-relay.conf new file mode 100644 index 0000000000000..5d6bcb2c4aeb6 --- /dev/null +++ b/test/test-network/conf/25-dhcp-relay.conf @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[DHCPRelay] +ServerAddress=192.0.2.1 +OverrideServerIdentifier=yes +RemoteId=string:test-dhcp-relay-remote-id 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..c07fb7a478cd2 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -3,7 +3,8 @@ # systemd-networkd tests # These tests can be executed in the systemd mkosi image when booted in QEMU. After booting the QEMU VM, -# simply run this file which can be found in the VM at /usr/lib/systemd/tests/testdata/test-network/systemd-networkd-tests.py. +# simply run this file which can be found in the VM at +# /usr/lib/systemd/tests/testdata/test-network/systemd-networkd-tests.py. # # To run an individual test, specify it as a command line argument in the form # of .. E.g. the NetworkdMTUTests class has a test @@ -27,8 +28,8 @@ import datetime import enum import errno -import itertools import ipaddress +import itertools import json import os import pathlib @@ -73,6 +74,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 @@ -84,8 +86,8 @@ asan_options = os.getenv('ASAN_OPTIONS') lsan_options = os.getenv('LSAN_OPTIONS') ubsan_options = os.getenv('UBSAN_OPTIONS') -with_coverage = os.getenv('COVERAGE_BUILD_DIR') != None -show_journal = True # When true, show journal on stopping networkd. +with_coverage = os.getenv('COVERAGE_BUILD_DIR') is not None +show_journal = True # When true, show journal on stopping networkd. active_units = [] protected_links = { @@ -107,54 +109,98 @@ saved_ipv6_rules = None saved_timezone = None + def rm_f(path): if os.path.exists(path): os.remove(path) + def rm_rf(path): shutil.rmtree(path, ignore_errors=True) + def cp(src, dst): shutil.copy(src, dst) + def cp_r(src, dst): shutil.copytree(src, dst, copy_function=shutil.copy, dirs_exist_ok=True) + def mkdir_p(path): os.makedirs(path, exist_ok=True) + def touch(path): pathlib.Path(path).touch() + # pylint: disable=R1710 def check_output(*command, **kwargs): # This checks the result and returns stdout (and stderr) on success. command = command[0].split() + list(command[1:]) - ret = subprocess.run(command, check=False, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs) + ret = subprocess.run( + command, + check=False, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + **kwargs, + ) if ret.returncode == 0: return ret.stdout.rstrip() # When returncode != 0, print stdout and stderr, then trigger CalledProcessError. print(ret.stdout) ret.check_returncode() + def call(*command, **kwargs): # This returns returncode. stdout and stderr are merged and shown in console command = command[0].split() + list(command[1:]) - return subprocess.run(command, check=False, universal_newlines=True, stderr=subprocess.STDOUT, **kwargs).returncode + return subprocess.run( + command, + check=False, + text=True, + stderr=subprocess.STDOUT, + **kwargs, + ).returncode + def call_check(*command, **kwargs): # Same as call() above, but it triggers CalledProcessError if rc != 0 command = command[0].split() + list(command[1:]) - return subprocess.run(command, check=False, universal_newlines=True, stderr=subprocess.STDOUT, **kwargs).check_returncode() + return subprocess.run( + command, + check=False, + text=True, + stderr=subprocess.STDOUT, + **kwargs, + ).check_returncode() + def call_quiet(*command, **kwargs): command = command[0].split() + list(command[1:]) - return subprocess.run(command, check=False, universal_newlines=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, **kwargs).returncode + return subprocess.run( + command, + check=False, + text=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + **kwargs, + ).returncode + def run(*command, **kwargs): # This returns CompletedProcess instance. command = command[0].split() + list(command[1:]) - return subprocess.run(command, check=False, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) + return subprocess.run( + command, + check=False, + text=True, + capture_output=True, + **kwargs, + ) + def check_json(string): try: @@ -163,6 +209,7 @@ def check_json(string): print(f"String is not a valid JSON: '{string}'") raise + def is_module_available(*module_names): for module_name in module_names: lsmod_output = check_output('lsmod') @@ -171,124 +218,19 @@ def is_module_available(*module_names): return False return True + def expectedFailureIfModuleIsNotAvailable(*module_names): def f(func): return func if is_module_available(*module_names) else unittest.expectedFailure(func) return f -def expectedFailureIfERSPANv0IsNotSupported(): - # erspan version 0 is supported since f989d546a2d5a9f001f6f8be49d98c10ab9b1897 (v5.8) - def f(func): - rc = call_quiet('ip link add dev erspan99 type erspan seq key 30 local 192.168.1.4 remote 192.168.1.1 erspan_ver 0') - remove_link('erspan99') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfERSPANv2IsNotSupported(): - # erspan version 2 is supported since f551c91de262ba36b20c3ac19538afb4f4507441 (v4.16) - def f(func): - rc = call_quiet('ip link add dev erspan99 type erspan seq key 30 local 192.168.1.4 remote 192.168.1.1 erspan_ver 2') - remove_link('erspan99') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfRoutingPolicyPortRangeIsNotAvailable(): - def f(func): - rc = call_quiet('ip rule add from 192.168.100.19 sport 1123-1150 dport 3224-3290 table 7') - call_quiet('ip rule del from 192.168.100.19 sport 1123-1150 dport 3224-3290 table 7') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfRoutingPolicyIPProtoIsNotAvailable(): - def f(func): - # IP protocol name is parsed by getprotobyname(), and it requires /etc/protocols. - # Hence. here we use explicit number: 6 == tcp. - rc = call_quiet('ip rule add not from 192.168.100.19 ipproto 6 table 7') - call_quiet('ip rule del not from 192.168.100.19 ipproto 6 table 7') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable(): - def f(func): - supported = False - if call_quiet('ip rule add from 192.168.100.19 table 7 uidrange 200-300') == 0: - ret = run('ip rule list from 192.168.100.19 table 7') - supported = ret.returncode == 0 and 'uidrange 200-300' in ret.stdout - call_quiet('ip rule del from 192.168.100.19 table 7 uidrange 200-300') - return func if supported else unittest.expectedFailure(func) - - return f - -def expectedFailureIfRoutingPolicyL3MasterDeviceIsNotAvailable(): - def f(func): - rc = call_quiet('ip rule add not from 192.168.100.19 l3mdev') - call_quiet('ip rule del not from 192.168.100.19 l3mdev') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfNexthopIsNotAvailable(): - def f(func): - rc = call_quiet('ip nexthop list') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfAlternativeNameIsNotAvailable(): - def f(func): - call_quiet('ip link add dummy98 type dummy') - supported = \ - call_quiet('ip link prop add dev dummy98 altname hogehogehogehogehoge') == 0 and \ - call_quiet('ip link show dev hogehogehogehogehoge') == 0 - remove_link('dummy98') - return func if supported else unittest.expectedFailure(func) - - return f - -def expectedFailureIfNetdevsimWithSRIOVIsNotAvailable(): - def f(func): - def finalize(func, supported): - call_quiet('rmmod netdevsim') - return func if supported else unittest.expectedFailure(func) - - call_quiet('rmmod netdevsim') - if call_quiet('modprobe netdevsim') != 0: - return finalize(func, False) - - try: - with open('/sys/bus/netdevsim/new_device', mode='w', encoding='utf-8') as f: - f.write('99 1') - except OSError: - return finalize(func, False) - - return finalize(func, os.path.exists('/sys/bus/netdevsim/devices/netdevsim99/sriov_numvfs')) - - return f - -def expectedFailureIfKernelReturnsInvalidFlags(): - ''' - This checks the kernel bug caused by 3ddc2231c8108302a8229d3c5849ee792a63230d (v6.9), - fixed by 1af7f88af269c4e06a4dc3bc920ff6cdf7471124 (v6.10, backported to 6.9.3). - ''' - def f(func): - call_quiet('ip link add dummy98 type dummy') - call_quiet('ip link set up dev dummy98') - call_quiet('ip address add 192.0.2.1/24 dev dummy98 noprefixroute') - output = check_output('ip address show dev dummy98') - remove_link('dummy98') - return func if 'noprefixroute' in output else unittest.expectedFailure(func) - - return f # pylint: disable=C0415 def compare_kernel_version(min_kernel_version): try: import platform + from packaging import version except ImportError: print('Failed to import either platform or packaging module, assuming the comparison failed') @@ -302,6 +244,23 @@ def compare_kernel_version(min_kernel_version): return version.parse(kver) >= version.parse(min_kernel_version) + +def networkd_has_sysctl_monitor_bpf(): + """Check whether systemd-networkd was built with ENABLE_SYSCTL_BPF support.""" + if not networkd_bin: + return False + try: + output = subprocess.run( + [networkd_bin, '--version'], + capture_output=True, + text=True, + check=False, + ).stdout + except (OSError, subprocess.SubprocessError): + return False + return '+BTF' in output.split() + + def copy_network_unit(*units, copy_dropins=True): """ Copy networkd unit files into the testbed. @@ -317,7 +276,10 @@ def copy_network_unit(*units, copy_dropins=True): mkdir_p(network_unit_dir) for unit in units: if copy_dropins and os.path.exists(os.path.join(networkd_ci_temp_dir, unit + '.d')): - cp_r(os.path.join(networkd_ci_temp_dir, unit + '.d'), os.path.join(network_unit_dir, unit + '.d')) + cp_r( + os.path.join(networkd_ci_temp_dir, unit + '.d'), + os.path.join(network_unit_dir, unit + '.d'), + ) if unit.endswith('.conf'): dropin = unit @@ -334,10 +296,11 @@ def copy_network_unit(*units, copy_dropins=True): if has_link: udevadm_reload() + def copy_credential(src, target): - mkdir_p(credstore_dir) - cp(os.path.join(networkd_ci_temp_dir, src), - os.path.join(credstore_dir, target)) + mkdir_p(credstore_dir) + cp(os.path.join(networkd_ci_temp_dir, src), os.path.join(credstore_dir, target)) + def remove_network_unit(*units): """ @@ -356,10 +319,12 @@ def remove_network_unit(*units): if has_link: udevadm_reload() + def touch_network_unit(*units): for unit in units: touch(os.path.join(network_unit_dir, unit)) + def clear_network_units(): has_link = False if os.path.exists(network_unit_dir): @@ -373,20 +338,24 @@ def clear_network_units(): if has_link: udevadm_reload() + def copy_networkd_conf_dropin(*dropins): """Copy networkd.conf dropin files into the testbed.""" mkdir_p(networkd_conf_dropin_dir) for dropin in dropins: cp(os.path.join(networkd_ci_temp_dir, dropin), networkd_conf_dropin_dir) + def remove_networkd_conf_dropin(*dropins): """Remove previously copied networkd.conf dropin files from the testbed.""" for dropin in dropins: rm_f(os.path.join(networkd_conf_dropin_dir, dropin)) + def clear_networkd_conf_dropins(): rm_rf(networkd_conf_dropin_dir) + def setup_systemd_udev_rules(): if not build_dir and not source_dir: return @@ -397,49 +366,55 @@ def setup_systemd_udev_rules(): if not path: continue - path = os.path.join(path, "rules.d") - print(f"Copying udev rules from {path} to {udev_rules_dir}") + path = os.path.join(path, 'rules.d') + print(f'Copying udev rules from {path} to {udev_rules_dir}') for rule in os.listdir(path): - if not rule.endswith(".rules"): + if not rule.endswith('.rules'): continue cp(os.path.join(path, rule), udev_rules_dir) + def clear_networkd_state_files(): rm_rf('/var/lib/systemd/network/') + def copy_udev_rule(*rules): """Copy udev rules""" mkdir_p(udev_rules_dir) for rule in rules: cp(os.path.join(networkd_ci_temp_dir, rule), udev_rules_dir) + def remove_udev_rule(*rules): """Remove previously copied udev rules""" for rule in rules: rm_f(os.path.join(udev_rules_dir, rule)) + def clear_udev_rules(): rm_rf(udev_rules_dir) + def save_active_units(): for u in [ - 'systemd-networkd.socket', - 'systemd-networkd-resolve-hook.socket', - 'systemd-networkd-varlink.socket', - 'systemd-networkd-varlink-metrics.socket', - 'systemd-networkd.service', - 'systemd-resolved-monitor.socket', - 'systemd-resolved-varlink.socket', - 'systemd-resolved.service', - 'systemd-timesyncd.service', - 'firewalld.service', - 'nftables.service', + 'systemd-networkd.socket', + 'systemd-networkd-resolve-hook.socket', + 'systemd-networkd-varlink.socket', + 'systemd-networkd-varlink-metrics.socket', + 'systemd-networkd.service', + 'systemd-resolved-monitor.socket', + 'systemd-resolved-varlink.socket', + 'systemd-resolved.service', + 'systemd-timesyncd.service', + 'firewalld.service', + 'nftables.service', ]: if call(f'systemctl is-active --quiet {u}') == 0: call(f'systemctl stop {u}') active_units.append(u) + def restore_active_units(): has_network_socket = False has_resolve_socket = False @@ -477,11 +452,13 @@ def restore_active_units(): for u in active_units: call(f'systemctl restart {u}') + def create_unit_dropin(unit, contents): mkdir_p(f'/run/systemd/system/{unit}.d') with open(f'/run/systemd/system/{unit}.d/00-override.conf', mode='w', encoding='utf-8') as f: f.write('\n'.join(contents)) + def create_service_dropin(service, command, additional_settings=None): drop_in = ['[Service]'] if command: @@ -519,37 +496,43 @@ def create_service_dropin(service, command, additional_settings=None): create_unit_dropin(f'{service}.service', drop_in) + def setup_system_units(): if build_dir or source_dir: mkdir_p('/run/systemd/system/') for unit in [ - 'systemd-networkd.service', - 'systemd-networkd.socket', - 'systemd-networkd-persistent-storage.service', - 'systemd-networkd-resolve-hook.socket', - 'systemd-networkd-varlink.socket', - 'systemd-networkd-varlink-metrics.socket', - 'systemd-resolved.service', - 'systemd-timesyncd.service', - 'systemd-udevd.service', + 'systemd-networkd.service', + 'systemd-networkd.socket', + 'systemd-networkd-persistent-storage.service', + 'systemd-networkd-resolve-hook.socket', + 'systemd-networkd-varlink.socket', + 'systemd-networkd-varlink-metrics.socket', + 'systemd-resolved.service', + 'systemd-timesyncd.service', + 'systemd-udevd.service', ]: for path in [build_dir, source_dir]: if not path: continue - fullpath = os.path.join(os.path.join(path, "units"), unit) + fullpath = os.path.join(os.path.join(path, 'units'), unit) if os.path.exists(fullpath): - print(f"Copying unit file from {fullpath} to /run/systemd/system/") + print(f'Copying unit file from {fullpath} to /run/systemd/system/') cp(fullpath, '/run/systemd/system/') break - create_service_dropin('systemd-networkd', networkd_bin, - ['[Service]', - 'Restart=no', - 'Environment=SYSTEMD_NETWORK_TEST_MODE=yes', - '[Unit]', - 'StartLimitIntervalSec=0']) + create_service_dropin( + 'systemd-networkd', + networkd_bin, + [ + '[Service]', + 'Restart=no', + 'Environment=SYSTEMD_NETWORK_TEST_MODE=yes', + '[Unit]', + 'StartLimitIntervalSec=0', + ], + ) create_service_dropin('systemd-resolved', resolved_bin) create_service_dropin('systemd-timesyncd', timesyncd_bin) @@ -559,7 +542,7 @@ def setup_system_units(): [ '[Unit]', 'StartLimitIntervalSec=0', - ] + ], ) create_unit_dropin( 'systemd-networkd-persistent-storage.service', @@ -572,28 +555,28 @@ def setup_system_units(): 'ExecStop=', f'ExecStop={networkctl_bin} persistent-storage no', 'Environment=SYSTEMD_LOG_LEVEL=debug' if enable_debug else '', - ] + ], ) create_unit_dropin( 'systemd-networkd-resolve-hook.socket', [ '[Unit]', 'StartLimitIntervalSec=0', - ] + ], ) create_unit_dropin( 'systemd-networkd-varlink.socket', [ '[Unit]', 'StartLimitIntervalSec=0', - ] + ], ) create_unit_dropin( 'systemd-networkd-varlink-metrics.socket', [ '[Unit]', 'StartLimitIntervalSec=0', - ] + ], ) create_unit_dropin( 'systemd-udevd.service', @@ -601,7 +584,7 @@ def setup_system_units(): '[Service]', 'ExecStart=', f'ExecStart=@{udevadm_bin} systemd-udevd', - ] + ], ) check_output('systemctl daemon-reload') @@ -614,6 +597,7 @@ def setup_system_units(): check_output('systemctl restart systemd-timesyncd.service') check_output('systemctl restart systemd-udevd.service') + def clear_system_units(): def rm_unit(name): rm_f(f'/run/systemd/system/{name}') @@ -631,15 +615,18 @@ def rm_unit(name): check_output('systemctl daemon-reload') check_output('systemctl restart systemd-udevd.service') + def link_exists(*links): for link in links: if call_quiet(f'ip link show {link}') != 0: return False return True + def link_resolve(link): return check_output(f'ip link show {link}').split(':')[1].strip().split('@')[0] + def remove_link(*links, protect=False): for link in links: if protect and link in protected_links: @@ -647,6 +634,7 @@ def remove_link(*links, protect=False): if link_exists(link): call(f'ip link del dev {link}') + def save_existing_links(): links = os.listdir('/sys/class/net') for link in links: @@ -656,6 +644,7 @@ def save_existing_links(): print('### The following links will be protected:') print(', '.join(sorted(list(protected_links)))) + def unmanage_existing_links(): mkdir_p(network_unit_dir) @@ -665,16 +654,19 @@ def unmanage_existing_links(): f.write(f'Name={link}\n') f.write('\n[Link]\nUnmanaged=yes\n') + def flush_links(): links = os.listdir('/sys/class/net') remove_link(*links, protect=True) + def flush_nexthops(): # Currently, the 'ip nexthop' command does not have 'save' and 'restore'. # Hence, we cannot restore nexthops in a simple way. # Let's assume there is no nexthop used in the system call_quiet('ip nexthop flush') + def save_routes(): # pylint: disable=global-statement global saved_routes @@ -682,6 +674,7 @@ def save_routes(): print('### The following routes will be protected:') print(saved_routes) + def flush_routes(): have = False output = check_output('ip route show table all') @@ -690,7 +683,7 @@ def flush_routes(): continue if 'proto kernel' in line: continue - if ' dev ' in line and not ' dev lo ' in line: + if ' dev ' in line and ' dev lo ' not in line: continue if not have: have = True @@ -698,9 +691,11 @@ def flush_routes(): print(f'# {line}') call(f'ip route del {line}') + def save_routing_policy_rules(): # pylint: disable=global-statement global saved_ipv4_rules, saved_ipv6_rules + def save(ipv): output = check_output(f'ip -{ipv} rule show') print(f'### The following IPv{ipv} routing policy rules will be protected:') @@ -710,6 +705,7 @@ def save(ipv): saved_ipv4_rules = save(4) saved_ipv6_rules = save(6) + def flush_routing_policy_rules(): def flush(ipv, saved_rules): have = False @@ -719,7 +715,7 @@ def flush(ipv, saved_rules): continue if not have: have = True - print(f'### Removing IPv{ipv} routing policy rules that did not exist when the test started.') + print(f'### Removing IPv{ipv} routing policy rules that did not exist when the test started.') # fmt: skip print(f'# {line}') words = line.replace('lookup [l3mdev-table]', 'l3mdev').replace('[detached]', '').split() priority = words[0].rstrip(':') @@ -728,19 +724,21 @@ def flush(ipv, saved_rules): flush(4, saved_ipv4_rules) flush(6, saved_ipv6_rules) + def flush_fou_ports(): ret = run('ip fou show') if ret.returncode != 0: - return # fou may not be supported + return # fou may not be supported for line in ret.stdout.splitlines(): port = line.split()[1] call(f'ip fou del port {port}') + def flush_l2tp_tunnels(): tids = [] ret = run('ip l2tp show tunnel') if ret.returncode != 0: - return # l2tp may not be supported + return # l2tp may not be supported for line in ret.stdout.splitlines(): words = line.split() if words[0] == 'Tunnel': @@ -754,10 +752,11 @@ def flush_l2tp_tunnels(): r = run(f'ip l2tp show tunnel tunnel_id {tid}') if r.returncode != 0 or len(r.stdout.rstrip()) == 0: break - time.sleep(.2) + time.sleep(0.2) else: print(f'Cannot remove L2TP tunnel {tid}, ignoring.') + def save_timezone(): # pylint: disable=global-statement global saved_timezone @@ -766,158 +765,207 @@ def save_timezone(): saved_timezone = r.stdout.rstrip() print(f'### Saved timezone: {saved_timezone}') + def restore_timezone(): if saved_timezone: call(*timedatectl_cmd, 'set-timezone', f'{saved_timezone}', env=env) + def read_link_attr(*args): with open(os.path.join('/sys/class/net', *args), encoding='utf-8') as f: return f.readline().strip() + def read_manager_state_file(): with open('/run/systemd/netif/state', encoding='utf-8') as f: return f.read() + def read_link_state_file(link): ifindex = read_link_attr(link, 'ifindex') path = os.path.join('/run/systemd/netif/links', ifindex) with open(path, encoding='utf-8') as f: return f.read() + def read_ip_sysctl_attr(link, attribute, ipv): with open(os.path.join('/proc/sys/net', ipv, 'conf', link, attribute), encoding='utf-8') as f: return f.readline().strip() + def read_ip_neigh_sysctl_attr(link, attribute, ipv): with open(os.path.join('/proc/sys/net', ipv, 'neigh', link, attribute), encoding='utf-8') as f: return f.readline().strip() + def read_ipv6_sysctl_attr(link, attribute): return read_ip_sysctl_attr(link, attribute, 'ipv6') + def read_ipv6_neigh_sysctl_attr(link, attribute): return read_ip_neigh_sysctl_attr(link, attribute, 'ipv6') + def read_ipv4_sysctl_attr(link, attribute): return read_ip_sysctl_attr(link, attribute, 'ipv4') + def read_mpls_sysctl_attr(link, attribute): return read_ip_sysctl_attr(link, attribute, 'mpls') + def stop_by_pid_file(pid_file): if not os.path.exists(pid_file): return - with open(pid_file, 'r', encoding='utf-8') as f: + with open(pid_file, encoding='utf-8') as f: pid = f.read().rstrip(' \t\r\n\0') os.kill(int(pid), signal.SIGTERM) for _ in range(25): try: os.kill(int(pid), 0) - print(f"PID {pid} is still alive, waiting...") - time.sleep(.2) + print(f'PID {pid} is still alive, waiting...') + time.sleep(0.2) except OSError as e: if e.errno == errno.ESRCH: break - print(f"Unexpected exception when waiting for {pid} to die: {e.errno}") + print(f'Unexpected exception when waiting for {pid} to die: {e.errno}') rm_f(pid_file) -def dnr_v4_instance_data(adn, addrs=None, prio=1, alpns=("dot",), dohpath=None): - b = bytes() - pack = lambda c, w=1: struct.pack('>' + "_BH_I"[w], len(c)) + c - pyton = lambda n, w=2: struct.pack('>' + "_BH_I"[w], n) + +def pack(c, width=1): + # big-endian unsigned char/short/integer + fmt = {1: '>B', 2: '>H', 4: '>I'} + return struct.pack(fmt[width], len(c)) + c + + +def pyton(n, width=2): + fmt = {1: '>B', 2: '>H', 4: '>I'} + return struct.pack(fmt[width], n) + + +class SvcParam(enum.Enum): + ALPN = 1 + DOHPATH = 7 + + +def dnr_v4_instance_data(adn, addrs=None, prio=1, alpns=('dot',), dohpath=None): ipv4 = ipaddress.IPv4Address - class SvcParam(enum.Enum): - ALPN = 1 - DOHPATH = 7 data = pyton(prio) adn = adn.rstrip('.') + '.' - data += pack(b.join(pack(label.encode('ascii')) for label in adn.split('.'))) + data += pack(b''.join(pack(label.encode('ascii')) for label in adn.split('.'))) - if not addrs: # adn-only mode + if not addrs: # adn-only mode return pack(data, 2) - data += pack(b.join(ipv4(addr).packed for addr in addrs)) - data += pyton(SvcParam.ALPN.value) + pack(b.join(pack(alpn.encode('ascii')) for alpn in alpns), 2) + data += pack(b''.join(ipv4(addr).packed for addr in addrs)) + data += pyton(SvcParam.ALPN.value) + pack(b''.join(pack(alpn.encode('ascii')) for alpn in alpns), 2) if dohpath is not None: data += pyton(SvcParam.DOHPATH.value) + pack(dohpath.encode('utf-8'), 2) return pack(data, 2) -def dnr_v6_instance_data(adn, addrs=None, prio=1, alpns=("dot",), dohpath=None): - b = bytes() - pack = lambda c, w=1: struct.pack('>' + "_BH_I"[w], len(c)) + c - pyton = lambda n, w=2: struct.pack('>' + "_BH_I"[w], n) + +def dnr_v6_instance_data(adn, addrs=None, prio=1, alpns=('dot',), dohpath=None): ipv6 = ipaddress.IPv6Address - class SvcParam(enum.Enum): - ALPN = 1 - DOHPATH = 7 data = pyton(prio) adn = adn.rstrip('.') + '.' - data += pack(b.join(pack(label.encode('ascii')) for label in adn.split('.')), 2) + data += pack(b''.join(pack(label.encode('ascii')) for label in adn.split('.')), 2) - if not addrs: # adn-only mode + if not addrs: # adn-only mode return data - data += pack(b.join(ipv6(addr).packed for addr in addrs), 2) - data += pyton(SvcParam.ALPN.value) + pack(b.join(pack(alpn.encode('ascii')) for alpn in alpns), 2) + data += pack(b''.join(ipv6(addr).packed for addr in addrs), 2) + data += pyton(SvcParam.ALPN.value) + pack(b''.join(pack(alpn.encode('ascii')) for alpn in alpns), 2) if dohpath is not None: data += pyton(SvcParam.DOHPATH.value) + pack(dohpath.encode('utf-8'), 2) 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 = '' + if namespace: + ns_command = ('ip', 'netns', 'exec', namespace) + else: + ns_command = () + command = ( - 'dnsmasq', - f'--log-facility={dnsmasq_log_file}', - '--log-queries=extra', - '--log-dhcp', - f'--pid-file={dnsmasq_pid_file}', - '--conf-file=/dev/null', - '--bind-interfaces', - f'--interface={interface}', - f'--dhcp-leasefile={dnsmasq_lease_file}', - '--enable-ra', - f'--dhcp-range={ipv6_range}{ra_mode},2m', - f'--dhcp-range={ipv4_range},2m', - '--dhcp-option=option:mtu,1492', - f'--dhcp-option=option:router,{ipv4_router}', - '--port=0', - '--no-resolv', - ) + additional_options + ns_command + + ( + 'dnsmasq', + f'--log-facility={dnsmasq_log_file}', + '--log-queries=extra', + '--log-dhcp', + f'--pid-file={dnsmasq_pid_file}', + '--conf-file=/dev/null', + '--bind-interfaces', + f'--interface={interface}', + f'--dhcp-leasefile={dnsmasq_lease_file}', + '--enable-ra', + f'--dhcp-range={ipv6_range}{ra_mode},2m', + f'--dhcp-range={ipv4_range},2m', + '--dhcp-option=option:mtu,1492', + f'--dhcp-option=option:router,{ipv4_router}', + '--port=0', + '--no-resolv', + ) + + additional_options + ) check_output(*command) + def stop_dnsmasq(): stop_by_pid_file(dnsmasq_pid_file) rm_f(dnsmasq_lease_file) rm_f(dnsmasq_log_file) + def read_dnsmasq_log_file(): with open(dnsmasq_log_file, encoding='utf-8') as f: return f.read() + def start_isc_dhcpd(conf_file, ipv, interface='veth-peer'): conf_file_path = os.path.join(networkd_ci_temp_dir, conf_file) - isc_dhcpd_command = f'dhcpd {ipv} -cf {conf_file_path} -lf {isc_dhcpd_lease_file} -pf {isc_dhcpd_pid_file} {interface}' + isc_dhcpd_command = f'dhcpd {ipv} -cf {conf_file_path} -lf {isc_dhcpd_lease_file} -pf {isc_dhcpd_pid_file} {interface}' # fmt: skip touch(isc_dhcpd_lease_file) check_output(isc_dhcpd_command) + def stop_isc_dhcpd(): stop_by_pid_file(isc_dhcpd_pid_file) rm_f(isc_dhcpd_lease_file) + def get_dbus_link_path(link): - out = subprocess.check_output(['busctl', 'call', 'org.freedesktop.network1', - '/org/freedesktop/network1', 'org.freedesktop.network1.Manager', - 'GetLinkByName', 's', link]) + out = subprocess.check_output( + [ + 'busctl', + 'call', + 'org.freedesktop.network1', + '/org/freedesktop/network1', + 'org.freedesktop.network1.Manager', + 'GetLinkByName', + 's', + link, + ] + ) assert out.startswith(b'io ') out = out.strip() @@ -925,35 +973,72 @@ 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) - out = subprocess.check_output(['busctl', 'get-property', 'org.freedesktop.network1', - link_path, f'org.freedesktop.network1.DHCPv{family}Client', 'State']) + out = subprocess.check_output( + [ + 'busctl', + 'get-property', + 'org.freedesktop.network1', + link_path, + f'org.freedesktop.network1.DHCPv{family}Client', + 'State', + ] + ) assert out.startswith(b's "') out = out.strip() assert out.endswith(b'"') return out[3:-1].decode() + def get_dhcp4_client_state(link): return get_dhcp_client_state(link, '4') + def get_dhcp6_client_state(link): return get_dhcp_client_state(link, '6') + def get_link_description(link): link_path = get_dbus_link_path(link) - out = subprocess.check_output(['busctl', 'call', 'org.freedesktop.network1', - link_path, 'org.freedesktop.network1.Link', 'Describe']) + out = subprocess.check_output( + [ + 'busctl', + 'call', + 'org.freedesktop.network1', + link_path, + 'org.freedesktop.network1.Link', + 'Describe', + ] + ) assert out.startswith(b's "') out = out.strip() assert out.endswith(b'"') json_raw = out[2:].decode() check_json(json_raw) - description = json.loads(json_raw) # Convert from escaped sequences to json + description = json.loads(json_raw) # Convert from escaped sequences to json check_json(description) - return json.loads(description) # Now parse the json + return json.loads(description) # Now parse the json + def start_radvd(*additional_options, config_file): config_file_path = os.path.join(networkd_ci_temp_dir, 'radvd', config_file) @@ -965,9 +1050,37 @@ def start_radvd(*additional_options, config_file): ) + additional_options check_output(*command) + 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') @@ -978,11 +1091,14 @@ def radvd_check_config(config_file): config_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'conf/radvd', config_file) return call(f'radvd --config={config_file_path} --configtest') == 0 + 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: @@ -998,9 +1114,11 @@ def read_networkd_log(invocation_id=None, since=None): check_output('journalctl --sync') return check_output(*command) + def networkd_is_failed(): return call_quiet('systemctl is-failed -q systemd-networkd.service') != 1 + def stop_networkd(show_logs=True, check_failed=True): global show_journal show_logs = show_logs and show_journal @@ -1027,12 +1145,14 @@ def stop_networkd(show_logs=True, check_failed=True): if check_failed: assert not networkd_is_failed() + def start_networkd(): check_output('systemctl start systemd-networkd') invocation_id = networkd_invocation_id() pid = networkd_pid() print(f'Started systemd-networkd.service: PID={pid}, Invocation ID={invocation_id}') + def restart_networkd(show_logs=True): global show_journal show_logs = show_logs and show_journal @@ -1046,8 +1166,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. @@ -1055,33 +1173,47 @@ def networkctl(*args): assert not networkd_is_failed() return check_output(*(networkctl_cmd + list(args)), env=env) + def networkctl_status(*args): return networkctl('-n', '0', 'status', *args) + def networkctl_json(*args): return networkctl('--json=short', 'status', *args) + def networkctl_reconfigure(*links): networkctl('reconfigure', *links) + def networkctl_reload(): networkctl('reload') + +def networkctl_dhcp_lease(*args): + return networkctl('dhcp-lease', *args) + + def resolvectl(*args): return check_output(*(resolvectl_cmd + list(args)), env=env) + def timedatectl(*args): return check_output(*(timedatectl_cmd + list(args)), env=env) + def udevadm(*args): return check_output(*(udevadm_cmd + list(args))) + def udevadm_reload(): udevadm('control', '--reload') + def udevadm_trigger(*args, action='add'): udevadm('trigger', '--settle', f'--action={action}', *args) + def setup_common(): # Protect existing links unmanage_existing_links() @@ -1092,6 +1224,7 @@ def setup_common(): sys.stdout.flush() check_output('journalctl --sync') + def tear_down_common(): # 1. stop DHCP/RA servers stop_dnsmasq() @@ -1104,6 +1237,9 @@ 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-relay') + call_quiet('ip netns del ns-server') # 4. remove links flush_l2tp_tunnels() @@ -1130,6 +1266,7 @@ def tear_down_common(): # 9. check the status of networkd assert not networkd_is_failed() + def setUpModule(): rm_rf(networkd_ci_temp_dir) cp_r(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'conf'), networkd_ci_temp_dir) @@ -1151,6 +1288,7 @@ def setUpModule(): setup_system_units() + def tearDownModule(): rm_rf(networkd_ci_temp_dir) clear_udev_rules() @@ -1167,7 +1305,8 @@ def tearDownModule(): sys.stdout.flush() check_output('journalctl --sync') -class Utilities(): + +class Utilities: # pylint: disable=no-member def check_link_exists(self, *link, expected=True): @@ -1219,7 +1358,9 @@ def wait_activated(self, link, state='down', trial=40): else: self.fail(f'Timed out waiting for {link} activated.') - def wait_operstate(self, link, operstate='degraded', setup_state='configured', setup_timeout=5, fail_assert=True): + def wait_operstate( + self, link, operstate='degraded', setup_state='configured', setup_timeout=5, fail_assert=True + ): """Wait for the link to reach the specified operstate and/or setup state. Specify None or '' for either operstate or setup_state to ignore that state. @@ -1242,7 +1383,13 @@ def wait_operstate(self, link, operstate='degraded', setup_state='configured', s if not link_exists(link): time.sleep(0.5) continue - output = networkctl_status(link) + try: + output = networkctl_status(link) + except subprocess.CalledProcessError: + # networkctl status may transiently fail e.g. when networkd has not + # yet picked up the link from the kernel. Retry until the timeout. + time.sleep(0.5) + continue if re.search(rf'(?m)^\s*State:\s+{operstate}\s+\({setup_state}\)\s*$', output): return True time.sleep(0.5) @@ -1251,7 +1398,17 @@ def wait_operstate(self, link, operstate='degraded', setup_state='configured', s self.fail(f'Timed out waiting for {link} to reach state {operstate}/{setup_state}') return False - def wait_online(self, *links_with_operstate, timeout='20s', bool_any=False, ipv4=False, ipv6=False, setup_state='configured', setup_timeout=5, bool_dns=False): + def wait_online( + self, + *links_with_operstate, + timeout='20s', + bool_any=False, + ipv4=False, + ipv6=False, + setup_state='configured', + setup_timeout=5, + bool_dns=False, + ): """Wait for the links to reach the specified operstate and/or setup state. This is similar to wait_operstate() but can be used for multiple links, @@ -1267,8 +1424,8 @@ def wait_online(self, *links_with_operstate, timeout='20s', bool_any=False, ipv4 Set 'bool_dns' to True to wait for DNS servers to be accessible. - Set 'ipv4' or 'ipv6' to True to wait for IPv4 address or IPv6 address, respectively, of each of the given links. - This is applied only for the operational state 'degraded' or above. + Set 'ipv4' or 'ipv6' to True to wait for IPv4 address or IPv6 address, respectively, of each of the + given links. This is applied only for the operational state 'degraded' or above. Note that this function waits for the links to reach *or exceed* the given operstate. However, the setup_state, if specified, must be matched *exactly*. @@ -1276,7 +1433,12 @@ def wait_online(self, *links_with_operstate, timeout='20s', bool_any=False, ipv4 This returns if the links reached the requested operstate/setup_state; otherwise it raises CalledProcessError or fails test assertion. """ - args = wait_online_cmd + [f'--timeout={timeout}'] + [f'--interface={link}' for link in links_with_operstate] + [f'--ignore={link}' for link in protected_links] + args = ( + wait_online_cmd + + [f'--timeout={timeout}'] + + [f'--interface={link}' for link in links_with_operstate] + + [f'--ignore={link}' for link in protected_links] + ) if bool_any: args += ['--any'] if bool_dns: @@ -1345,7 +1507,7 @@ def check_netlabel(self, interface, address, label='system_u:object_r:root_t:s0' print('## Checking NetLabel skipped: selinuxenabled command not found.') elif call_quiet('selinuxenabled') != 0: print('## Checking NetLabel skipped: SELinux disabled.') - elif not shutil.which('netlabelctl'): # not packaged by all distros + elif not shutil.which('netlabelctl'): # not packaged by all distros print('## Checking NetLabel skipped: netlabelctl command not found.') else: output = check_output('netlabelctl unlbl list') @@ -1356,7 +1518,7 @@ def setup_nftset(self, filter_name, filter_type, flags=''): if not shutil.which('nft'): print('## Setting up NFT sets skipped: nft command not found.') else: - if call(f'nft add table inet sd_test') != 0: + if call('nft add table inet sd_test') != 0: print('## Setting up NFT table failed.') self.fail() if call(f'nft add set inet sd_test {filter_name} {{ type {filter_type}; {flags} }}') != 0: @@ -1371,7 +1533,7 @@ def teardown_nftset(self, *filters): if call(f'nft delete set inet sd_test {filter_name}') != 0: print('## Tearing down NFT sets failed.') self.fail() - if call(f'nft delete table inet sd_test') != 0: + if call('nft delete table inet sd_test') != 0: print('## Tearing down NFT table failed.') self.fail() @@ -1405,15 +1567,14 @@ def networkctl_check_unit(self, ifname, netdev_file=None, network_file=None, lin if link_file: self.assertRegex(output, rf'Link File: .*/{link_file}\.link') -class NetworkctlTests(unittest.TestCase, Utilities): +class NetworkctlTests(unittest.TestCase, Utilities): def setUp(self): setup_common() def tearDown(self): tear_down_common() - @expectedFailureIfAlternativeNameIsNotAvailable() def test_altname(self): copy_network_unit('26-netdev-link-local-addressing-yes.network', '12-dummy.netdev', '12-dummy.link') start_networkd() @@ -1422,10 +1583,12 @@ def test_altname(self): output = networkctl_status('dummy98') self.assertRegex(output, 'hogehogehogehogehogehoge') - @expectedFailureIfAlternativeNameIsNotAvailable() def test_rename_to_altname(self): - copy_network_unit('26-netdev-link-local-addressing-yes.network', - '12-dummy.netdev', '12-dummy-rename-to-altname.link') + copy_network_unit( + '26-netdev-link-local-addressing-yes.network', + '12-dummy.netdev', + '12-dummy-rename-to-altname.link', + ) start_networkd() self.wait_online('dummyalt:degraded') @@ -1596,12 +1759,11 @@ def test_unit_file(self): self.assertIn('Network File: /run/systemd/network/11-test-unit-file.network', output) self.assertIn('/run/systemd/network/11-test-unit-file.network.d/dropin.conf', output) - self.check_networkd_log('test1: Configuring with /run/systemd/network/11-test-unit-file.network (dropins: /run/systemd/network/11-test-unit-file.network.d/dropin.conf).') + self.check_networkd_log( + 'test1: Configuring with /run/systemd/network/11-test-unit-file.network ' + '(dropins: /run/systemd/network/11-test-unit-file.network.d/dropin.conf).' + ) - # This test may be run on the system that has older udevd than 70f32a260b5ebb68c19ecadf5d69b3844896ba55 (v249). - # In that case, the udev DB for the loopback network interface may already have ID_NET_LINK_FILE property. - # Let's reprocess the interface and drop the property. - udevadm_trigger('/sys/class/net/lo') output = networkctl_status('lo') print(output) self.assertIn('Link File: n/a', output) @@ -1617,22 +1779,23 @@ def test_delete_links(self): def test_label(self): networkctl('label') -class NetworkdMatchTests(unittest.TestCase, Utilities): +class NetworkdMatchTests(unittest.TestCase, Utilities): def setUp(self): setup_common() def tearDown(self): tear_down_common() - @expectedFailureIfAlternativeNameIsNotAvailable() def test_match(self): - copy_network_unit('12-dummy-mac.netdev', - '12-dummy-match-mac-01.network', - '12-dummy-match-mac-02.network', - '12-dummy-match-renamed.network', - '12-dummy-match-altname.network', - '12-dummy-altname.link') + copy_network_unit( + '12-dummy-mac.netdev', + '12-dummy-match-mac-01.network', + '12-dummy-match-mac-02.network', + '12-dummy-match-renamed.network', + '12-dummy-match-altname.network', + '12-dummy-altname.link', + ) start_networkd() self.wait_online('dummy98:routable') @@ -1667,7 +1830,11 @@ def test_match(self): self.assertIn('Network File: /run/systemd/network/12-dummy-match-altname.network', output) def test_match_udev_property(self): - copy_network_unit('12-dummy.netdev', '13-not-match-udev-property.network', '14-match-udev-property.network') + copy_network_unit( + '12-dummy.netdev', + '13-not-match-udev-property.network', + '14-match-udev-property.network', + ) start_networkd() self.wait_online('dummy98:routable') @@ -1675,8 +1842,8 @@ def test_match_udev_property(self): print(output) self.assertRegex(output, 'Network File: /run/systemd/network/14-match-udev-property') -class WaitOnlineTests(unittest.TestCase, Utilities): +class WaitOnlineTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -1704,7 +1871,7 @@ def do_test_wait_online_dns( if network_dropin is not None: network_dropin_path = os.path.join( network_unit_dir, - '25-dhcp-client-use-dns-ipv4.network.d/test.conf' + '25-dhcp-client-use-dns-ipv4.network.d/test.conf', ) mkdir_p(os.path.dirname(network_dropin_path)) with open(network_dropin_path, 'w') as f: @@ -1713,7 +1880,7 @@ def do_test_wait_online_dns( copy_network_unit( '25-veth.netdev', '25-dhcp-client-use-dns-ipv4.network', - '25-dhcp-server.network' + '25-dhcp-server.network', ) start_networkd() self.wait_online('veth-peer:routable') @@ -1722,11 +1889,11 @@ def do_test_wait_online_dns( resolved_dropin = '/run/systemd/resolved.conf.d/global-dns.conf' mkdir_p(os.path.dirname(resolved_dropin)) with open(resolved_dropin, 'w') as f: - f.write(( - '[Resolve]\n' - f'DNS={global_dns}\n' - f'FallbackDNS={fallback_dns}\n' - )) + f.write(f''' +[Resolve] +DNS={global_dns} +FallbackDNS={fallback_dns} +''') self.addCleanup(os.remove, resolved_dropin) check_output('systemctl reload systemd-resolved') @@ -1740,61 +1907,61 @@ def do_test_wait_online_dns( if expect_timeout: # The above should have thrown an exception. - self.fail( - 'Expected systemd-networkd-wait-online to time out' - ) + self.fail('Expected systemd-networkd-wait-online to time out') except subprocess.CalledProcessError as e: if expect_timeout: self.assertRegex( e.output, - f'veth99: No link-specific DNS server is accessible', - f'Missing expected log message:\n{e.output}' + 'veth99: No link-specific DNS server is accessible', + f'Missing expected log message:\n{e.output}', ) else: - self.fail( - f'Command timed out:\n{e.output}' - ) + self.fail(f'Command timed out:\n{e.output}') finally: wait_online_env = wait_online_env_copy def test_wait_online_dns(self): - ''' test systemd-networkd-wait-online with --dns ''' + """test systemd-networkd-wait-online with --dns""" self.do_test_wait_online_dns() def test_wait_online_dns_global(self): - ''' + """ test systemd-networkd-wait-online with --dns, expect pass due to global DNS - ''' + """ # Set UseDNS=no, and allow global DNS to be used. self.do_test_wait_online_dns( global_dns='192.168.5.1', network_dropin=( - '[DHCPv4]\n' - 'UseDNS=no\n' - ) + ''' +[DHCPv4] +UseDNS=no +''' + ), ) def test_wait_online_dns_expect_timeout(self): - ''' test systemd-networkd-wait-online with --dns, and expect timeout ''' + """test systemd-networkd-wait-online with --dns, and expect timeout""" # Explicitly set DNSDefaultRoute=yes, and require link-specific DNS to be used. self.do_test_wait_online_dns( expect_timeout=True, network_dropin=( - '[Network]\n' - 'DNSDefaultRoute=yes\n' - '[DHCPv4]\n' - 'UseDNS=no\n' - ) + ''' +[Network] +DNSDefaultRoute=yes +[DHCPv4] +UseDNS=no +''' + ), ) def test_wait_online_dns_expect_timeout_global(self): - ''' + """ test systemd-networkd-wait-online with --dns, and expect timeout despite global DNS - ''' + """ # Configure Domains=~., and expect timeout despite global DNS servers # being available. @@ -1802,16 +1969,17 @@ def test_wait_online_dns_expect_timeout_global(self): expect_timeout=True, global_dns='192.168.5.1', network_dropin=( - '[Network]\n' - 'Domains=~.\n' - '[DHCPv4]\n' - 'UseDNS=no\n' - ) + ''' +[Network] +Domains=~. +[DHCPv4] +UseDNS=no +''' + ), ) class NetworkdNetDevTests(unittest.TestCase, Utilities): - def setUp(self): setup_common() @@ -1874,12 +2042,12 @@ def test_bridge(self): self.assertEqual(9, round(float(read_link_attr('bridge99', 'bridge', 'max_age')) / tick)) self.assertEqual(9, round(float(read_link_attr('bridge99', 'bridge', 'forward_delay')) / tick)) self.assertEqual(9, round(float(read_link_attr('bridge99', 'bridge', 'ageing_time')) / tick)) - self.assertEqual(9, int(read_link_attr('bridge99', 'bridge', 'priority'))) - self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'multicast_querier'))) - self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'multicast_snooping'))) - self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'stp_state'))) - self.assertEqual(3, int(read_link_attr('bridge99', 'bridge', 'multicast_igmp_version'))) - self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'no_linklocal_learn'))) + self.assertEqual(9, int(read_link_attr('bridge99', 'bridge', 'priority'))) + self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'multicast_querier'))) + self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'multicast_snooping'))) + self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'stp_state'))) + self.assertEqual(3, int(read_link_attr('bridge99', 'bridge', 'multicast_igmp_version'))) + self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'no_linklocal_learn'))) output = networkctl_status('bridge99') print(output) @@ -1906,20 +2074,20 @@ def test_bond(self): self.networkctl_check_unit('bond98', '25-bond-balanced-tlb') self.networkctl_check_unit('bond97', '25-bond-property') - self.check_link_attr('bond99', 'bonding', 'mode', '802.3ad 4') - self.check_link_attr('bond99', 'bonding', 'xmit_hash_policy', 'layer3+4 1') - self.check_link_attr('bond99', 'bonding', 'miimon', '1000') - self.check_link_attr('bond99', 'bonding', 'lacp_rate', 'fast 1') - self.check_link_attr('bond99', 'bonding', 'updelay', '2000') - self.check_link_attr('bond99', 'bonding', 'downdelay', '2000') - self.check_link_attr('bond99', 'bonding', 'resend_igmp', '4') - self.check_link_attr('bond99', 'bonding', 'min_links', '1') + self.check_link_attr('bond99', 'bonding', 'mode', '802.3ad 4') + self.check_link_attr('bond99', 'bonding', 'xmit_hash_policy', 'layer3+4 1') + self.check_link_attr('bond99', 'bonding', 'miimon', '1000') + self.check_link_attr('bond99', 'bonding', 'lacp_rate', 'fast 1') + self.check_link_attr('bond99', 'bonding', 'updelay', '2000') + self.check_link_attr('bond99', 'bonding', 'downdelay', '2000') + self.check_link_attr('bond99', 'bonding', 'resend_igmp', '4') + self.check_link_attr('bond99', 'bonding', 'min_links', '1') self.check_link_attr('bond99', 'bonding', 'ad_actor_sys_prio', '1218') - self.check_link_attr('bond99', 'bonding', 'ad_user_port_key', '811') - self.check_link_attr('bond99', 'bonding', 'ad_actor_system', '00:11:22:33:44:55') + self.check_link_attr('bond99', 'bonding', 'ad_user_port_key', '811') + self.check_link_attr('bond99', 'bonding', 'ad_actor_system', '00:11:22:33:44:55') - self.check_link_attr('bond98', 'bonding', 'mode', 'balance-tlb 5') - self.check_link_attr('bond98', 'bonding', 'tlb_dynamic_lb', '1') + self.check_link_attr('bond98', 'bonding', 'mode', 'balance-tlb 5') + self.check_link_attr('bond98', 'bonding', 'tlb_dynamic_lb', '1') output = networkctl_status('bond99') print(output) @@ -1935,7 +2103,7 @@ def test_bond(self): output = networkctl_status('bond97') print(output) - self.check_link_attr('bond97', 'bonding', 'arp_missed_max', '10') + self.check_link_attr('bond97', 'bonding', 'arp_missed_max', '10') self.check_link_attr('bond97', 'bonding', 'peer_notif_delay', '300000') def check_vlan(self, id, flags): @@ -1974,26 +2142,36 @@ def check_vlan(self, id, flags): self.assertRegex(output, 'inet 192.168.23.5/24 brd 192.168.23.255 scope global vlan99') def test_vlan(self): - copy_network_unit('21-vlan.netdev', '11-dummy.netdev', - '21-vlan.network', '21-vlan-test1.network') + copy_network_unit( + '21-vlan.netdev', + '11-dummy.netdev', + '21-vlan.network', + '21-vlan-test1.network', + ) start_networkd() self.check_vlan(id=99, flags=True) # Test for reloading .netdev file. See issue #34907. - with open(os.path.join(network_unit_dir, '21-vlan.netdev.d/override.conf'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '21-vlan.netdev.d/override.conf'), mode='a', encoding='utf-8' + ) as f: f.write('[VLAN]\nId=42\n') # VLAN ID cannot be changed, so we need to remove the existing netdev. - check_output("ip link del vlan99") + check_output('ip link del vlan99') networkctl_reload() self.check_vlan(id=42, flags=True) - with open(os.path.join(network_unit_dir, '21-vlan.netdev.d/override.conf'), mode='a', encoding='utf-8') as f: - f.write('[VLAN]\n' - 'GVRP=no\n' - 'MVRP=no\n' - 'LooseBinding=no\n' - 'ReorderHeader=no\n') + with open( + os.path.join(network_unit_dir, '21-vlan.netdev.d/override.conf'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[VLAN] +GVRP=no +MVRP=no +LooseBinding=no +ReorderHeader=no +''') # flags can be changed, hence it is not necessary to remove the existing netdev. networkctl_reload() @@ -2003,8 +2181,12 @@ def test_vlan_on_bond(self): # For issue #24377 (https://github.com/systemd/systemd/issues/24377), # which is fixed by b05e52000b4eee764b383cc3031da0a3739e996e (PR#24020). - copy_network_unit('21-bond-802.3ad.netdev', '21-bond-802.3ad.network', - '21-vlan-on-bond.netdev', '21-vlan-on-bond.network') + copy_network_unit( + '21-bond-802.3ad.netdev', + '21-bond-802.3ad.network', + '21-vlan-on-bond.netdev', + '21-vlan-on-bond.network', + ) start_networkd() self.wait_online('bond99:off') self.wait_operstate('vlan99', operstate='off', setup_state='configuring', setup_timeout=10) @@ -2027,14 +2209,22 @@ def test_macvtap(self): print(f'### test_macvtap(mode={mode})') with self.subTest(mode=mode): - copy_network_unit('21-macvtap.netdev', '26-netdev-link-local-addressing-yes.network', - '11-dummy.netdev', '25-macvtap.network') - with open(os.path.join(network_unit_dir, '21-macvtap.netdev'), mode='a', encoding='utf-8') as f: + copy_network_unit( + '21-macvtap.netdev', + '26-netdev-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-macvtap.network', + ) + with open( + os.path.join(network_unit_dir, '21-macvtap.netdev'), mode='a', encoding='utf-8' + ) as f: f.write('[MACVTAP]\nMode=' + mode) start_networkd() - self.wait_online('macvtap99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + self.wait_online( + 'macvtap99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) self.networkctl_check_unit('macvtap99', '21-macvtap', '26-netdev-link-local-addressing-yes') self.networkctl_check_unit('test1', '11-dummy', '25-macvtap') @@ -2044,8 +2234,10 @@ def test_macvtap(self): touch_network_unit('21-macvtap.netdev') networkctl_reload() - self.wait_online('macvtap99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + self.wait_online( + 'macvtap99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) @expectedFailureIfModuleIsNotAvailable('macvlan') def test_macvlan(self): @@ -2058,14 +2250,22 @@ def test_macvlan(self): print(f'### test_macvlan(mode={mode})') with self.subTest(mode=mode): - copy_network_unit('21-macvlan.netdev', '26-netdev-link-local-addressing-yes.network', - '11-dummy.netdev', '25-macvlan.network') - with open(os.path.join(network_unit_dir, '21-macvlan.netdev'), mode='a', encoding='utf-8') as f: + copy_network_unit( + '21-macvlan.netdev', + '26-netdev-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-macvlan.network', + ) + with open( + os.path.join(network_unit_dir, '21-macvlan.netdev'), mode='a', encoding='utf-8' + ) as f: f.write('[MACVLAN]\nMode=' + mode) start_networkd() - self.wait_online('macvlan99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + self.wait_online( + 'macvlan99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) self.networkctl_check_unit('macvlan99', '21-macvlan', '26-netdev-link-local-addressing-yes') self.networkctl_check_unit('test1', '11-dummy', '25-macvlan') @@ -2081,9 +2281,11 @@ def test_macvlan(self): remove_link('test1') time.sleep(1) - check_output("ip link add test1 type dummy") - self.wait_online('macvlan99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + check_output('ip link add test1 type dummy') + self.wait_online( + 'macvlan99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) output = check_output('ip -d link show test1') print(output) @@ -2094,13 +2296,15 @@ def test_macvlan(self): self.assertIn(' mtu 2000 ', output) self.assertIn(f' macvlan mode {mode} ', output) self.assertIn(' bcqueuelen 1234 ', output) - if ' bclim ' in output: # This is new in kernel and iproute2 v6.4 + if ' bclim ' in output: # This is new in kernel and iproute2 v6.4 self.assertIn(' bclim 2147483647 ', output) touch_network_unit('21-macvlan.netdev') networkctl_reload() - self.wait_online('macvlan99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + self.wait_online( + 'macvlan99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) @expectedFailureIfModuleIsNotAvailable('ipvlan') def test_ipvlan(self): @@ -2113,9 +2317,15 @@ def test_ipvlan(self): print(f'### test_ipvlan(mode={mode}, flag={flag})') with self.subTest(mode=mode, flag=flag): - copy_network_unit('25-ipvlan.netdev', '26-netdev-link-local-addressing-yes.network', - '11-dummy.netdev', '25-ipvlan.network') - with open(os.path.join(network_unit_dir, '25-ipvlan.netdev'), mode='a', encoding='utf-8') as f: + copy_network_unit( + '25-ipvlan.netdev', + '26-netdev-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-ipvlan.network', + ) + with open( + os.path.join(network_unit_dir, '25-ipvlan.netdev'), mode='a', encoding='utf-8' + ) as f: f.write('[IPVLAN]\nMode=' + mode + '\nFlags=' + flag) start_networkd() @@ -2142,9 +2352,14 @@ def test_hsr(self): print(f'### test_hsr(proto={proto}, supervision={supervision})') with self.subTest(proto=proto, supervision=supervision): - copy_network_unit('25-hsr.netdev', '25-hsr.network', - '11-dummy.netdev', '11-dummy.network', - '12-dummy.netdev', '12-dummy-no-address.network') + copy_network_unit( + '25-hsr.netdev', + '25-hsr.network', + '11-dummy.netdev', + '11-dummy.network', + '12-dummy.netdev', + '12-dummy-no-address.network', + ) with open(os.path.join(network_unit_dir, '25-hsr.netdev'), mode='a', encoding='utf-8') as f: f.write('Protocol=' + proto + '\nSupervision=' + str(supervision)) @@ -2175,9 +2390,15 @@ def test_ipvtap(self): print(f'### test_ipvtap(mode={mode}, flag={flag})') with self.subTest(mode=mode, flag=flag): - copy_network_unit('25-ipvtap.netdev', '26-netdev-link-local-addressing-yes.network', - '11-dummy.netdev', '25-ipvtap.network') - with open(os.path.join(network_unit_dir, '25-ipvtap.netdev'), mode='a', encoding='utf-8') as f: + copy_network_unit( + '25-ipvtap.netdev', + '26-netdev-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-ipvtap.network', + ) + with open( + os.path.join(network_unit_dir, '25-ipvtap.netdev'), mode='a', encoding='utf-8' + ) as f: f.write('[IPVTAP]\nMode=' + mode + '\nFlags=' + flag) start_networkd() @@ -2194,11 +2415,19 @@ def test_ipvtap(self): self.wait_online('ipvtap99:degraded', 'test1:degraded') def test_veth(self): - copy_network_unit('25-veth.netdev', '26-netdev-link-local-addressing-yes.network', - '25-veth-mtu.netdev') + copy_network_unit( + '25-veth.netdev', + '26-netdev-link-local-addressing-yes.network', + '25-veth-mtu.netdev', + ) start_networkd() - self.wait_online('veth99:degraded', 'veth-peer:degraded', 'veth-mtu:degraded', 'veth-mtu-peer:degraded') + self.wait_online( + 'veth99:degraded', + 'veth-peer:degraded', + 'veth-mtu:degraded', + 'veth-mtu-peer:degraded', + ) self.networkctl_check_unit('veth99', '25-veth', '26-netdev-link-local-addressing-yes') self.networkctl_check_unit('veth-peer', '25-veth', '26-netdev-link-local-addressing-yes') self.networkctl_check_unit('veth-mtu', '25-veth-mtu', '26-netdev-link-local-addressing-yes') @@ -2223,13 +2452,15 @@ def test_veth(self): touch_network_unit( '25-veth.netdev', '26-netdev-link-local-addressing-yes.network', - '25-veth-mtu.netdev') + '25-veth-mtu.netdev', + ) networkctl_reload() self.wait_online( 'veth99:degraded', 'veth-peer:degraded', 'veth-mtu:degraded', - 'veth-mtu-peer:degraded') + 'veth-mtu-peer:degraded', + ) def check_tuntap(self, attached): pid = networkd_pid() @@ -2237,24 +2468,25 @@ def check_tuntap(self, attached): output = check_output('ip -d -oneline tuntap show') print(output) + # fmt: off self.assertRegex(output, r'testtap99: tap pi (multi_queue |)vnet_hdr persist filter.*\tAttached to processes:') self.assertRegex(output, r'testtun99: tun pi (multi_queue |)vnet_hdr persist filter.*\tAttached to processes:') + # fmt: on if attached: - self.assertRegex(output, fr'testtap99: .*{name}\({pid}\)') - self.assertRegex(output, fr'testtun99: .*{name}\({pid}\)') + self.assertRegex(output, rf'testtap99: .*{name}\({pid}\)') + self.assertRegex(output, rf'testtun99: .*{name}\({pid}\)') self.assertRegex(output, r'testtap99: .*systemd\(1\)') self.assertRegex(output, r'testtun99: .*systemd\(1\)') output = check_output('ip -d link show testtun99') print(output) - # Old ip command does not support IFF_ flags - self.assertRegex(output, 'tun (type tun pi on vnet_hdr on multi_queue|addrgenmode) ') + self.assertIn('tun type tun pi on vnet_hdr on multi_queue', output) self.assertIn('UP,LOWER_UP', output) output = check_output('ip -d link show testtap99') print(output) - self.assertRegex(output, 'tun (type tap pi on vnet_hdr on multi_queue|addrgenmode) ') + self.assertIn('tun type tap pi on vnet_hdr on multi_queue', output) self.assertIn('UP,LOWER_UP', output) else: @@ -2264,7 +2496,7 @@ def check_tuntap(self, attached): for _ in range(20): output = check_output('ip -d link show testtun99') print(output) - self.assertRegex(output, 'tun (type tun pi on vnet_hdr on multi_queue|addrgenmode) ') + self.assertIn('tun type tun pi on vnet_hdr on multi_queue', output) if 'NO-CARRIER' in output: break time.sleep(0.5) @@ -2274,7 +2506,7 @@ def check_tuntap(self, attached): for _ in range(20): output = check_output('ip -d link show testtap99') print(output) - self.assertRegex(output, 'tun (type tap pi on vnet_hdr on multi_queue|addrgenmode) ') + self.assertIn('tun type tap pi on vnet_hdr on multi_queue', output) if 'NO-CARRIER' in output: break time.sleep(0.5) @@ -2327,8 +2559,12 @@ def test_vrf(self): @expectedFailureIfModuleIsNotAvailable('vcan') def test_vcan(self): - copy_network_unit('25-vcan.netdev', '26-netdev-link-local-addressing-yes.network', - '25-vcan98.netdev', '25-vcan98.network') + copy_network_unit( + '25-vcan.netdev', + '26-netdev-link-local-addressing-yes.network', + '25-vcan98.netdev', + '25-vcan98.network', + ) start_networkd() self.wait_online('vcan99:carrier', 'vcan98:carrier') @@ -2350,7 +2586,8 @@ def test_vcan(self): '25-vcan.netdev', '26-netdev-link-local-addressing-yes.network', '25-vcan98.netdev', - '25-vcan98.network') + '25-vcan98.network', + ) networkctl_reload() self.wait_online('vcan99:carrier', 'vcan98:carrier') @@ -2371,14 +2608,30 @@ def test_vxcan(self): @expectedFailureIfModuleIsNotAvailable('wireguard') def test_wireguard(self): - copy_credential('25-wireguard-endpoint-peer0-cred.txt', 'network.wireguard.peer0.endpoint') - copy_credential('25-wireguard-preshared-key-peer2-cred.txt', 'network.wireguard.peer2.psk') - copy_credential('25-wireguard-no-peer-private-key-cred.txt', 'network.wireguard.private.25-wireguard-no-peer') + copy_credential( + '25-wireguard-endpoint-peer0-cred.txt', + 'network.wireguard.peer0.endpoint', + ) + copy_credential( + '25-wireguard-preshared-key-peer2-cred.txt', + 'network.wireguard.peer2.psk', + ) + copy_credential( + '25-wireguard-no-peer-private-key-cred.txt', + 'network.wireguard.private.25-wireguard-no-peer', + ) - copy_network_unit('25-wireguard.netdev', '25-wireguard.network', - '25-wireguard-23-peers.netdev', '25-wireguard-23-peers.network', - '25-wireguard-public-key.txt', '25-wireguard-preshared-key.txt', '25-wireguard-private-key.txt', - '25-wireguard-no-peer.netdev', '25-wireguard-no-peer.network') + copy_network_unit( + '25-wireguard.netdev', + '25-wireguard.network', + '25-wireguard-23-peers.netdev', + '25-wireguard-23-peers.network', + '25-wireguard-public-key.txt', + '25-wireguard-preshared-key.txt', + '25-wireguard-private-key.txt', + '25-wireguard-no-peer.netdev', + '25-wireguard-no-peer.network', + ) start_networkd() self.wait_online('wg99:routable', 'wg98:routable', 'wg97:carrier') self.networkctl_check_unit('wg99', '25-wireguard', '25-wireguard') @@ -2472,10 +2725,12 @@ def test_wireguard(self): output = check_output('wg show wg99 private-key') self.assertEqual(output, 'EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=') output = check_output('wg show wg99 allowed-ips') + # fmt: off self.assertIn('9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=\t192.168.124.3/32', output) self.assertIn('TTiCUpCxS7zDn/ax4p5W6Evg41r8hOrnWQw2Sq6Nh10=\t192.168.124.2/32', output) self.assertIn('lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=\tfdbc:bae2:7871:e1fe:793:8636::/96 fdbc:bae2:7871:500:e1fe:793:8636:dad1/128', output) self.assertIn('RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=\t192.168.26.0/24 fd31:bf08:57cb::/48', output) + # fmt: on output = check_output('wg show wg99 persistent-keepalive') self.assertIn('9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=\toff', output) self.assertIn('TTiCUpCxS7zDn/ax4p5W6Evg41r8hOrnWQw2Sq6Nh10=\toff', output) @@ -2487,10 +2742,12 @@ def test_wireguard(self): self.assertIn('lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=\t(none)', output) self.assertIn('RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=\t192.168.27.3:51820', output) output = check_output('wg show wg99 preshared-keys') + # fmt: off self.assertIn('9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=\t6Fsg8XN0DE6aPQgAX4r2oazEYJOGqyHUz3QRH/jCB+I=', output) self.assertIn('TTiCUpCxS7zDn/ax4p5W6Evg41r8hOrnWQw2Sq6Nh10=\tit7nd33chCT/tKT2ZZWfYyp43Zs+6oif72hexnSNMqA=', output) self.assertIn('lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=\tcPLOy1YUrEI0EMMIycPJmOo0aTu3RZnw8bL5meVD6m0=', output) self.assertIn('RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=\tIIWIV17wutHv7t4cR6pOT91z6NSz/T8Arh0yaywhw3M=', output) + # fmt: on output = check_output('wg show wg98 private-key') self.assertEqual(output, 'CJQUtcS9emY2fLYqDlpSZiE/QJyHkPWr+WHtZLZ90FU=') @@ -2501,10 +2758,16 @@ def test_wireguard(self): self.assertEqual(output, '0x4d3') touch_network_unit( - '25-wireguard.netdev', '25-wireguard.network', - '25-wireguard-23-peers.netdev', '25-wireguard-23-peers.network', - '25-wireguard-public-key.txt', '25-wireguard-preshared-key.txt', '25-wireguard-private-key.txt', - '25-wireguard-no-peer.netdev', '25-wireguard-no-peer.network') + '25-wireguard.netdev', + '25-wireguard.network', + '25-wireguard-23-peers.netdev', + '25-wireguard-23-peers.network', + '25-wireguard-public-key.txt', + '25-wireguard-preshared-key.txt', + '25-wireguard-private-key.txt', + '25-wireguard-no-peer.netdev', + '25-wireguard-no-peer.network', + ) networkctl_reload() self.wait_online('wg99:routable', 'wg98:routable', 'wg97:carrier') @@ -2527,24 +2790,39 @@ def test_geneve(self): self.wait_online('geneve99:degraded') def _test_ipip_tunnel(self, mode): - copy_network_unit('12-dummy.netdev', '25-ipip.network', - '25-ipip-tunnel.netdev', '25-tunnel.network', - '25-ipip-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-ipip-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-ipip-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-ipip.network', + '25-ipip-tunnel.netdev', + '25-tunnel.network', + '25-ipip-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-ipip-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-ipip-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) if mode: - for netdev in ['25-ipip-tunnel.netdev', - '25-ipip-tunnel-local-any.netdev', - '25-ipip-tunnel-remote-any.netdev', - '25-ipip-tunnel-any-any.netdev']: + for netdev in [ + '25-ipip-tunnel.netdev', + '25-ipip-tunnel-local-any.netdev', + '25-ipip-tunnel-remote-any.netdev', + '25-ipip-tunnel-any-any.netdev', + ]: with open(os.path.join(network_unit_dir, netdev), mode='a', encoding='utf-8') as f: f.write(f'[Tunnel]\nMode={mode}\n') else: - mode = 'ipip' # kernel default + mode = 'ipip' # kernel default start_networkd() - self.wait_online('ipiptun99:routable', 'ipiptun98:routable', 'ipiptun97:routable', 'ipiptun96:routable', 'dummy98:degraded') + self.wait_online( + 'ipiptun99:routable', + 'ipiptun98:routable', + 'ipiptun97:routable', + 'ipiptun96:routable', + 'dummy98:degraded', + ) output = check_output('ip -d link show ipiptun99') print(output) @@ -2563,14 +2841,16 @@ def _test_ipip_tunnel(self, mode): '25-ipip-tunnel.netdev', '25-ipip-tunnel-local-any.netdev', '25-ipip-tunnel-remote-any.netdev', - '25-ipip-tunnel-any-any.netdev') + '25-ipip-tunnel-any-any.netdev', + ) networkctl_reload() self.wait_online( 'ipiptun99:routable', 'ipiptun98:routable', 'ipiptun97:routable', 'ipiptun96:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_ipip_tunnel(self): first = True @@ -2585,13 +2865,26 @@ def test_ipip_tunnel(self): self._test_ipip_tunnel(mode) def test_gre_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-gretun.network', - '25-gre-tunnel.netdev', '25-tunnel.network', - '25-gre-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-gre-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-gre-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-gretun.network', + '25-gre-tunnel.netdev', + '25-tunnel.network', + '25-gre-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-gre-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-gre-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) start_networkd() - self.wait_online('gretun99:routable', 'gretun98:routable', 'gretun97:routable', 'gretun96:routable', 'dummy98:degraded') + self.wait_online( + 'gretun99:routable', + 'gretun98:routable', + 'gretun97:routable', + 'gretun96:routable', + 'dummy98:degraded', + ) self.networkctl_check_unit('gretun99', '25-gre-tunnel', '25-tunnel') self.networkctl_check_unit('gretun98', '25-gre-tunnel-local-any', '25-tunnel-local-any') self.networkctl_check_unit('gretun97', '25-gre-tunnel-remote-any', '25-tunnel-remote-any') @@ -2631,30 +2924,43 @@ def test_gre_tunnel(self): '25-gre-tunnel.netdev', '25-gre-tunnel-local-any.netdev', '25-gre-tunnel-remote-any.netdev', - '25-gre-tunnel-any-any.netdev') + '25-gre-tunnel-any-any.netdev', + ) networkctl_reload() self.wait_online( 'gretun99:routable', 'gretun98:routable', 'gretun97:routable', 'gretun96:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_ip6gre_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-ip6gretun.network', - '25-ip6gre-tunnel.netdev', '25-tunnel.network', - '25-ip6gre-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-ip6gre-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-ip6gre-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-ip6gretun.network', + '25-ip6gre-tunnel.netdev', + '25-tunnel.network', + '25-ip6gre-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-ip6gre-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-ip6gre-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) start_networkd() - # Old kernels seem not to support IPv6LL address on ip6gre tunnel, So please do not use wait_online() here. - - self.wait_links('dummy98', 'ip6gretun99', 'ip6gretun98', 'ip6gretun97', 'ip6gretun96') + self.wait_online( + 'ip6gretun99:routable', + 'ip6gretun98:routable', + 'ip6gretun97:routable', + 'ip6gretun96:routable', + 'dummy98:degraded', + ) output = check_output('ip -d link show ip6gretun99') print(output) - self.assertRegex(output, 'ip6gre remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') + self.assertRegex(output, 'ip6gre remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') # fmt: skip output = check_output('ip -d link show ip6gretun98') print(output) self.assertRegex(output, 'ip6gre remote 2001:473:fece:cafe::5179 local any dev dummy98') @@ -2669,19 +2975,26 @@ def test_ip6gre_tunnel(self): '25-ip6gre-tunnel.netdev', '25-ip6gre-tunnel-local-any.netdev', '25-ip6gre-tunnel-remote-any.netdev', - '25-ip6gre-tunnel-any-any.netdev') + '25-ip6gre-tunnel-any-any.netdev', + ) networkctl_reload() - self.wait_links( - 'dummy98', - 'ip6gretun99', - 'ip6gretun98', - 'ip6gretun97', - 'ip6gretun96') + self.wait_online( + 'ip6gretun99:routable', + 'ip6gretun98:routable', + 'ip6gretun97:routable', + 'ip6gretun96:routable', + 'dummy98:degraded', + ) def test_gretap_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-gretap.network', - '25-gretap-tunnel.netdev', '25-tunnel.network', - '25-gretap-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-gretap.network', + '25-gretap-tunnel.netdev', + '25-tunnel.network', + '25-gretap-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('gretap99:routable', 'gretap98:routable', 'dummy98:degraded') self.networkctl_check_unit('gretap99', '25-gretap-tunnel', '25-tunnel') @@ -2707,17 +3020,24 @@ def test_gretap_tunnel(self): touch_network_unit( '25-gretap-tunnel.netdev', - '25-gretap-tunnel-local-any.netdev') + '25-gretap-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'gretap99:routable', 'gretap98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_ip6gretap_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-ip6gretap.network', - '25-ip6gretap-tunnel.netdev', '25-tunnel.network', - '25-ip6gretap-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-ip6gretap.network', + '25-ip6gretap-tunnel.netdev', + '25-tunnel.network', + '25-ip6gretap-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('ip6gretap99:routable', 'ip6gretap98:routable', 'dummy98:degraded') self.networkctl_check_unit('ip6gretap99', '25-ip6gretap-tunnel', '25-tunnel') @@ -2726,28 +3046,43 @@ def test_ip6gretap_tunnel(self): output = check_output('ip -d link show ip6gretap99') print(output) - self.assertRegex(output, 'ip6gretap remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') + self.assertRegex(output, 'ip6gretap remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') # fmt: skip output = check_output('ip -d link show ip6gretap98') print(output) self.assertRegex(output, 'ip6gretap remote 2001:473:fece:cafe::5179 local any dev dummy98') touch_network_unit( '25-ip6gretap-tunnel.netdev', - '25-ip6gretap-tunnel-local-any.netdev') + '25-ip6gretap-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'ip6gretap99:routable', 'ip6gretap98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_vti_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-vti.network', - '25-vti-tunnel.netdev', '25-tunnel.network', - '25-vti-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-vti-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-vti-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-vti.network', + '25-vti-tunnel.netdev', + '25-tunnel.network', + '25-vti-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-vti-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-vti-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) start_networkd() - self.wait_online('vtitun99:routable', 'vtitun98:routable', 'vtitun97:routable', 'vtitun96:routable', 'dummy98:degraded') + self.wait_online( + 'vtitun99:routable', + 'vtitun98:routable', + 'vtitun97:routable', + 'vtitun96:routable', + 'dummy98:degraded', + ) self.networkctl_check_unit('vtitun99', '25-vti-tunnel', '25-tunnel') self.networkctl_check_unit('vtitun98', '25-vti-tunnel-local-any', '25-tunnel-local-any') self.networkctl_check_unit('vtitun97', '25-vti-tunnel-remote-any', '25-tunnel-remote-any') @@ -2771,22 +3106,35 @@ def test_vti_tunnel(self): '25-vti-tunnel.netdev', '25-vti-tunnel-local-any.netdev', '25-vti-tunnel-remote-any.netdev', - '25-vti-tunnel-any-any.netdev') + '25-vti-tunnel-any-any.netdev', + ) networkctl_reload() self.wait_online( 'vtitun99:routable', 'vtitun98:routable', 'vtitun97:routable', 'vtitun96:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_vti6_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-vti6.network', - '25-vti6-tunnel.netdev', '25-tunnel.network', - '25-vti6-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-vti6-tunnel-remote-any.netdev', '25-tunnel-remote-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-vti6.network', + '25-vti6-tunnel.netdev', + '25-tunnel.network', + '25-vti6-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-vti6-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + ) start_networkd() - self.wait_online('vti6tun99:routable', 'vti6tun98:routable', 'vti6tun97:routable', 'dummy98:degraded') + self.wait_online( + 'vti6tun99:routable', + 'vti6tun98:routable', + 'vti6tun97:routable', + 'dummy98:degraded', + ) self.networkctl_check_unit('vti6tun99', '25-vti6-tunnel', '25-tunnel') self.networkctl_check_unit('vti6tun98', '25-vti6-tunnel-local-any', '25-tunnel-local-any') self.networkctl_check_unit('vti6tun97', '25-vti6-tunnel-remote-any', '25-tunnel-remote-any') @@ -2794,7 +3142,7 @@ def test_vti6_tunnel(self): output = check_output('ip -d link show vti6tun99') print(output) - self.assertRegex(output, 'vti6 remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') + self.assertRegex(output, 'vti6 remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') # fmt: skip output = check_output('ip -d link show vti6tun98') print(output) self.assertRegex(output, 'vti6 remote 2001:473:fece:cafe::5179 local (any|::) dev dummy98') @@ -2805,50 +3153,103 @@ def test_vti6_tunnel(self): touch_network_unit( '25-vti6-tunnel.netdev', '25-vti6-tunnel-local-any.netdev', - '25-vti6-tunnel-remote-any.netdev') + '25-vti6-tunnel-remote-any.netdev', + ) networkctl_reload() self.wait_online( 'vti6tun99:routable', 'vti6tun98:routable', 'vti6tun97:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def _test_ip6tnl_tunnel(self, mode): - copy_network_unit('12-dummy.netdev', '25-ip6tnl.network', - '25-ip6tnl-tunnel.netdev', '25-tunnel.network', - '25-ip6tnl-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-ip6tnl-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-veth.netdev', '25-ip6tnl-slaac.network', '25-ipv6-prefix.network', - '25-ip6tnl-tunnel-local-slaac.netdev', '25-ip6tnl-tunnel-local-slaac.network', - '25-ip6tnl-tunnel-external.netdev', '26-netdev-link-local-addressing-yes.network') + copy_network_unit( + '12-dummy.netdev', + '25-ip6tnl.network', + '25-ip6tnl-tunnel.netdev', + '25-tunnel.network', + '25-ip6tnl-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-ip6tnl-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-veth.netdev', + '25-ip6tnl-slaac.network', + '25-ipv6-prefix.network', + '25-ip6tnl-tunnel-local-slaac.netdev', + '25-ip6tnl-tunnel-local-slaac.network', + '25-ip6tnl-tunnel-external.netdev', + '26-netdev-link-local-addressing-yes.network', + ) if mode: - for netdev in ['25-ip6tnl-tunnel.netdev', - '25-ip6tnl-tunnel-local-any.netdev', - '25-ip6tnl-tunnel-remote-any.netdev', - '25-ip6tnl-tunnel-local-slaac.netdev', - '25-ip6tnl-tunnel-external.netdev']: + for netdev in [ + '25-ip6tnl-tunnel.netdev', + '25-ip6tnl-tunnel-local-any.netdev', + '25-ip6tnl-tunnel-remote-any.netdev', + '25-ip6tnl-tunnel-local-slaac.netdev', + '25-ip6tnl-tunnel-external.netdev', + ]: with open(os.path.join(network_unit_dir, netdev), mode='a', encoding='utf-8') as f: f.write(f'[Tunnel]\nMode={mode}\n') else: - mode = 'any' # kernel default - - start_networkd() - self.wait_online('ip6tnl99:routable', 'ip6tnl98:routable', 'ip6tnl97:routable', - 'ip6tnl-slaac:degraded', 'ip6tnl-external:degraded', - 'dummy98:degraded', 'veth99:routable', 'veth-peer:degraded') - self.networkctl_check_unit('ip6tnl99', '25-ip6tnl-tunnel', '25-tunnel') - self.networkctl_check_unit('ip6tnl98', '25-ip6tnl-tunnel-local-any', '25-tunnel-local-any') - self.networkctl_check_unit('ip6tnl97', '25-ip6tnl-tunnel-remote-any', '25-tunnel-remote-any') - self.networkctl_check_unit('ip6tnl-slaac', '25-ip6tnl-tunnel-local-slaac', '25-ip6tnl-tunnel-local-slaac') - self.networkctl_check_unit('ip6tnl-external', '25-ip6tnl-tunnel-external', '26-netdev-link-local-addressing-yes') - self.networkctl_check_unit('dummy98', '12-dummy', '25-ip6tnl') - self.networkctl_check_unit('veth99', '25-veth', '25-ip6tnl-slaac') - self.networkctl_check_unit('veth-peer', '25-veth', '25-ipv6-prefix') + mode = 'any' # kernel default + + start_networkd() + self.wait_online( + 'ip6tnl99:routable', + 'ip6tnl98:routable', + 'ip6tnl97:routable', + 'ip6tnl-slaac:degraded', + 'ip6tnl-external:degraded', + 'dummy98:degraded', + 'veth99:routable', + 'veth-peer:degraded', + ) + self.networkctl_check_unit( + 'ip6tnl99', + '25-ip6tnl-tunnel', + '25-tunnel', + ) + self.networkctl_check_unit( + 'ip6tnl98', + '25-ip6tnl-tunnel-local-any', + '25-tunnel-local-any', + ) + self.networkctl_check_unit( + 'ip6tnl97', + '25-ip6tnl-tunnel-remote-any', + '25-tunnel-remote-any', + ) + self.networkctl_check_unit( + 'ip6tnl-slaac', + '25-ip6tnl-tunnel-local-slaac', + '25-ip6tnl-tunnel-local-slaac', + ) + self.networkctl_check_unit( + 'ip6tnl-external', + '25-ip6tnl-tunnel-external', + '26-netdev-link-local-addressing-yes', + ) + self.networkctl_check_unit( + 'dummy98', + '12-dummy', + '25-ip6tnl', + ) + self.networkctl_check_unit( + 'veth99', + '25-veth', + '25-ip6tnl-slaac', + ) + self.networkctl_check_unit( + 'veth-peer', + '25-veth', + '25-ipv6-prefix', + ) output = check_output('ip -d link show ip6tnl99') print(output) - self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98', output) + self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98', output) # fmt: skip output = check_output('ip -d link show ip6tnl98') print(output) self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local any dev dummy98', output) @@ -2861,7 +3262,7 @@ def _test_ip6tnl_tunnel(self, mode): self.assertIn('ip6tnl external ', output) output = check_output('ip -d link show ip6tnl-slaac') print(output) - self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local 2002:da8:1:0:1034:56ff:fe78:9abc dev veth99', output) + self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local 2002:da8:1:0:1034:56ff:fe78:9abc dev veth99', output) # fmt: skip output = check_output('ip -6 address show veth99') print(output) @@ -2876,7 +3277,8 @@ def _test_ip6tnl_tunnel(self, mode): '25-ip6tnl-tunnel-local-any.netdev', '25-ip6tnl-tunnel-remote-any.netdev', '25-ip6tnl-tunnel-local-slaac.netdev', - '25-ip6tnl-tunnel-external.netdev') + '25-ip6tnl-tunnel-external.netdev', + ) networkctl_reload() self.wait_online( 'ip6tnl99:routable', @@ -2886,7 +3288,8 @@ def _test_ip6tnl_tunnel(self, mode): 'ip6tnl-external:degraded', 'dummy98:degraded', 'veth99:routable', - 'veth-peer:degraded') + 'veth-peer:degraded', + ) def test_ip6tnl_tunnel(self): first = True @@ -2901,24 +3304,39 @@ def test_ip6tnl_tunnel(self): self._test_ip6tnl_tunnel(mode) def _test_sit_tunnel(self, mode): - copy_network_unit('12-dummy.netdev', '25-sit.network', - '25-sit-tunnel.netdev', '25-tunnel.network', - '25-sit-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-sit-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-sit-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-sit.network', + '25-sit-tunnel.netdev', + '25-tunnel.network', + '25-sit-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-sit-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-sit-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) if mode: - for netdev in ['25-sit-tunnel.netdev', - '25-sit-tunnel-local-any.netdev', - '25-sit-tunnel-remote-any.netdev', - '25-sit-tunnel-any-any.netdev']: + for netdev in [ + '25-sit-tunnel.netdev', + '25-sit-tunnel-local-any.netdev', + '25-sit-tunnel-remote-any.netdev', + '25-sit-tunnel-any-any.netdev', + ]: with open(os.path.join(network_unit_dir, netdev), mode='a', encoding='utf-8') as f: f.write(f'[Tunnel]\nMode={mode}\n') else: - mode = 'ip6ip' # kernel default + mode = 'ip6ip' # kernel default start_networkd() - self.wait_online('sittun99:routable', 'sittun98:routable', 'sittun97:routable', 'sittun96:routable', 'dummy98:degraded') + self.wait_online( + 'sittun99:routable', + 'sittun98:routable', + 'sittun97:routable', + 'sittun96:routable', + 'dummy98:degraded', + ) self.networkctl_check_unit('sittun99', '25-sit-tunnel', '25-tunnel') self.networkctl_check_unit('sittun98', '25-sit-tunnel-local-any', '25-tunnel-local-any') self.networkctl_check_unit('sittun97', '25-sit-tunnel-remote-any', '25-tunnel-remote-any') @@ -2942,14 +3360,16 @@ def _test_sit_tunnel(self, mode): '25-sit-tunnel.netdev', '25-sit-tunnel-local-any.netdev', '25-sit-tunnel-remote-any.netdev', - '25-sit-tunnel-any-any.netdev') + '25-sit-tunnel-any-any.netdev', + ) networkctl_reload() self.wait_online( 'sittun99:routable', 'sittun98:routable', 'sittun97:routable', 'sittun96:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_sit_tunnel(self): first = True @@ -2964,8 +3384,12 @@ def test_sit_tunnel(self): self._test_sit_tunnel(mode) def test_isatap_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-isatap.network', - '25-isatap-tunnel.netdev', '25-tunnel.network') + copy_network_unit( + '12-dummy.netdev', + '25-isatap.network', + '25-isatap-tunnel.netdev', + '25-tunnel.network', + ) start_networkd() self.wait_online('isataptun99:routable', 'dummy98:degraded') self.networkctl_check_unit('isataptun99', '25-isatap-tunnel', '25-tunnel') @@ -2973,15 +3397,19 @@ def test_isatap_tunnel(self): output = check_output('ip -d link show isataptun99') print(output) - self.assertRegex(output, "isatap ") + self.assertRegex(output, 'isatap ') touch_network_unit('25-isatap-tunnel.netdev') networkctl_reload() self.wait_online('isataptun99:routable', 'dummy98:degraded') def test_6rd_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-6rd.network', - '25-6rd-tunnel.netdev', '25-tunnel.network') + copy_network_unit( + '12-dummy.netdev', + '25-6rd.network', + '25-6rd-tunnel.netdev', + '25-tunnel.network', + ) start_networkd() self.wait_online('sittun99:routable', 'dummy98:degraded') self.networkctl_check_unit('sittun99', '25-6rd-tunnel', '25-tunnel') @@ -2995,11 +3423,15 @@ def test_6rd_tunnel(self): networkctl_reload() self.wait_online('sittun99:routable', 'dummy98:degraded') - @expectedFailureIfERSPANv0IsNotSupported() def test_erspan_tunnel_v0(self): - copy_network_unit('12-dummy.netdev', '25-erspan.network', - '25-erspan0-tunnel.netdev', '25-tunnel.network', - '25-erspan0-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-erspan.network', + '25-erspan0-tunnel.netdev', + '25-tunnel.network', + '25-erspan0-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('erspan99:routable', 'erspan98:routable', 'dummy98:degraded') self.networkctl_check_unit('erspan99', '25-erspan0-tunnel', '25-tunnel') @@ -3029,17 +3461,24 @@ def test_erspan_tunnel_v0(self): touch_network_unit( '25-erspan0-tunnel.netdev', - '25-erspan0-tunnel-local-any.netdev') + '25-erspan0-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'erspan99:routable', 'erspan98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_erspan_tunnel_v1(self): - copy_network_unit('12-dummy.netdev', '25-erspan.network', - '25-erspan1-tunnel.netdev', '25-tunnel.network', - '25-erspan1-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-erspan.network', + '25-erspan1-tunnel.netdev', + '25-tunnel.network', + '25-erspan1-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('erspan99:routable', 'erspan98:routable', 'dummy98:degraded') self.networkctl_check_unit('erspan99', '25-erspan1-tunnel', '25-tunnel') @@ -3071,18 +3510,24 @@ def test_erspan_tunnel_v1(self): touch_network_unit( '25-erspan1-tunnel.netdev', - '25-erspan1-tunnel-local-any.netdev') + '25-erspan1-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'erspan99:routable', 'erspan98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) - @expectedFailureIfERSPANv2IsNotSupported() def test_erspan_tunnel_v2(self): - copy_network_unit('12-dummy.netdev', '25-erspan.network', - '25-erspan2-tunnel.netdev', '25-tunnel.network', - '25-erspan2-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-erspan.network', + '25-erspan2-tunnel.netdev', + '25-tunnel.network', + '25-erspan2-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('erspan99:routable', 'erspan98:routable', 'dummy98:degraded') self.networkctl_check_unit('erspan99', '25-erspan2-tunnel', '25-tunnel') @@ -3114,32 +3559,52 @@ def test_erspan_tunnel_v2(self): touch_network_unit( '25-erspan2-tunnel.netdev', - '25-erspan2-tunnel-local-any.netdev') + '25-erspan2-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'erspan99:routable', 'erspan98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_tunnel_independent(self): - copy_network_unit('25-ipip-tunnel-independent.netdev', '26-netdev-link-local-addressing-yes.network') + copy_network_unit( + '25-ipip-tunnel-independent.netdev', + '26-netdev-link-local-addressing-yes.network', + ) start_networkd() self.wait_online('ipiptun99:carrier') - self.networkctl_check_unit('ipiptun99', '25-ipip-tunnel-independent', '26-netdev-link-local-addressing-yes') + self.networkctl_check_unit( + 'ipiptun99', + '25-ipip-tunnel-independent', + '26-netdev-link-local-addressing-yes', + ) def test_tunnel_independent_loopback(self): - copy_network_unit('25-ipip-tunnel-independent-loopback.netdev', '26-netdev-link-local-addressing-yes.network') + copy_network_unit( + '25-ipip-tunnel-independent-loopback.netdev', + '26-netdev-link-local-addressing-yes.network', + ) start_networkd() self.wait_online('ipiptun99:carrier') - self.networkctl_check_unit('ipiptun99', '25-ipip-tunnel-independent-loopback', '26-netdev-link-local-addressing-yes') + self.networkctl_check_unit( + 'ipiptun99', + '25-ipip-tunnel-independent-loopback', + '26-netdev-link-local-addressing-yes', + ) @expectedFailureIfModuleIsNotAvailable('xfrm_interface') def test_xfrm(self): - copy_network_unit('12-dummy.netdev', '25-xfrm.network', - '25-xfrm.netdev', '25-xfrm-independent.netdev', - '26-netdev-link-local-addressing-yes.network') + copy_network_unit( + '12-dummy.netdev', + '25-xfrm.network', + '25-xfrm.netdev', + '25-xfrm-independent.netdev', + '26-netdev-link-local-addressing-yes.network', + ) start_networkd() self.wait_online('dummy98:degraded', 'xfrm98:degraded', 'xfrm99:degraded') @@ -3163,12 +3628,23 @@ def test_xfrm(self): @expectedFailureIfModuleIsNotAvailable('fou') def test_fou(self): - copy_network_unit('25-fou-ipproto-ipip.netdev', '25-fou-ipproto-gre.netdev', - '25-fou-ipip.netdev', '25-fou-sit.netdev', - '25-fou-gre.netdev', '25-fou-gretap.netdev') + copy_network_unit( + '25-fou-ipproto-ipip.netdev', + '25-fou-ipproto-gre.netdev', + '25-fou-ipip.netdev', + '25-fou-sit.netdev', + '25-fou-gre.netdev', + '25-fou-gretap.netdev', + ) start_networkd() - self.wait_online('ipiptun96:off', 'sittun96:off', 'gretun96:off', 'gretap96:off', setup_state='unmanaged') + self.wait_online( + 'ipiptun96:off', + 'sittun96:off', + 'gretun96:off', + 'gretap96:off', + setup_state='unmanaged', + ) self.networkctl_check_unit('ipiptun96', '25-fou-ipip') self.networkctl_check_unit('sittun96', '25-fou-sit') self.networkctl_check_unit('gretun96', '25-fou-gre') @@ -3193,25 +3669,52 @@ def test_fou(self): self.assertRegex(output, 'encap fou encap-sport auto encap-dport 55556') touch_network_unit( - '25-fou-ipproto-ipip.netdev', '25-fou-ipproto-gre.netdev', - '25-fou-ipip.netdev', '25-fou-sit.netdev', - '25-fou-gre.netdev', '25-fou-gretap.netdev') + '25-fou-ipproto-ipip.netdev', + '25-fou-ipproto-gre.netdev', + '25-fou-ipip.netdev', + '25-fou-sit.netdev', + '25-fou-gre.netdev', + '25-fou-gretap.netdev', + ) networkctl_reload() - self.wait_online('ipiptun96:off', 'sittun96:off', 'gretun96:off', 'gretap96:off', setup_state='unmanaged') + self.wait_online( + 'ipiptun96:off', + 'sittun96:off', + 'gretun96:off', + 'gretap96:off', + setup_state='unmanaged', + ) def test_vxlan(self): - copy_network_unit('11-dummy.netdev', '25-vxlan-test1.network', - '25-vxlan.netdev', '25-vxlan.network', - '25-vxlan-ipv6.netdev', '25-vxlan-ipv6.network', - '25-vxlan-independent.netdev', '26-netdev-link-local-addressing-yes.network', - '25-veth.netdev', '25-vxlan-veth99.network', '25-ipv6-prefix.network', - '25-vxlan-local-slaac.netdev', '25-vxlan-local-slaac.network', - '25-vxlan-external.netdev', '25-vxlan-external.network') - start_networkd() - - self.wait_online('test1:degraded', 'veth99:routable', 'veth-peer:degraded', - 'vxlan99:degraded', 'vxlan98:degraded', 'vxlan97:degraded', 'vxlan-slaac:degraded', - 'vxlan-external:degraded') + copy_network_unit( + '11-dummy.netdev', + '25-vxlan-test1.network', + '25-vxlan.netdev', + '25-vxlan.network', + '25-vxlan-ipv6.netdev', + '25-vxlan-ipv6.network', + '25-vxlan-independent.netdev', + '26-netdev-link-local-addressing-yes.network', + '25-veth.netdev', + '25-vxlan-veth99.network', + '25-ipv6-prefix.network', + '25-vxlan-local-slaac.netdev', + '25-vxlan-local-slaac.network', + '25-vxlan-external.netdev', + '25-vxlan-external.network', + ) + start_networkd() + + self.wait_online( + 'test1:degraded', + 'veth99:routable', + 'veth-peer:degraded', + 'vxlan99:degraded', + 'vxlan98:degraded', + 'vxlan97:degraded', + 'vxlan-slaac:degraded', + 'vxlan-external:degraded', + ) self.networkctl_check_unit('test1', '11-dummy', '25-vxlan-test1') self.networkctl_check_unit('veth99', '25-veth', '25-vxlan-veth99') self.networkctl_check_unit('veth-peer', '25-veth', '25-ipv6-prefix') @@ -3270,10 +3773,18 @@ def test_vxlan(self): self.assertIn('external', output) self.assertIn('vnifilter', output) - @unittest.skipUnless(compare_kernel_version("6"), reason="Causes kernel panic on unpatched kernels: https://bugzilla.kernel.org/show_bug.cgi?id=208315") + @unittest.skipUnless( + compare_kernel_version('6'), + reason='Causes kernel panic on unpatched kernels: https://bugzilla.kernel.org/show_bug.cgi?id=208315', + ) def test_macsec(self): - copy_network_unit('25-macsec.netdev', '25-macsec.network', '25-macsec.key', - '26-macsec.network', '12-dummy.netdev') + copy_network_unit( + '25-macsec.netdev', + '25-macsec.network', + '25-macsec.key', + '26-macsec.network', + '12-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:degraded', 'macsec99:routable') @@ -3328,7 +3839,7 @@ def test_ifb(self): networkctl_reload() self.wait_online('ifb99:degraded') - @unittest.skipUnless(os.cpu_count() >= 2, reason="CPU count should be >= 2 to pass this test") + @unittest.skipUnless(os.cpu_count() >= 2, reason='CPU count should be >= 2 to pass this test') def test_rps_cpu_1(self): copy_network_unit('12-dummy.netdev', '12-dummy.network', '25-rps-cpu-1.link') start_networkd() @@ -3340,7 +3851,7 @@ def test_rps_cpu_1(self): print(output) self.assertEqual(int(output.replace(',', ''), base=16), 2) - @unittest.skipUnless(os.cpu_count() >= 2, reason="CPU count should be >= 2 to pass this test") + @unittest.skipUnless(os.cpu_count() >= 2, reason='CPU count should be >= 2 to pass this test') def test_rps_cpu_0_1(self): copy_network_unit('12-dummy.netdev', '12-dummy.network', '25-rps-cpu-0-1.link') start_networkd() @@ -3352,7 +3863,7 @@ def test_rps_cpu_0_1(self): print(output) self.assertEqual(int(output.replace(',', ''), base=16), 3) - @unittest.skipUnless(os.cpu_count() >= 4, reason="CPU count should be >= 4 to pass this test") + @unittest.skipUnless(os.cpu_count() >= 4, reason='CPU count should be >= 4 to pass this test') def test_rps_cpu_multi(self): copy_network_unit('12-dummy.netdev', '12-dummy.network', '25-rps-cpu-multi.link') start_networkd() @@ -3388,7 +3899,7 @@ def test_rps_cpu(self): self.networkctl_check_unit('dummy98', '12-dummy', '12-dummy', '25-rps-cpu-all') output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus') print(output) - self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}') + self.assertEqual(f'{int(output.replace(",", ""), base=16):x}', f'{(1 << cpu_count) - 1:x}') remove_network_unit('25-rps-cpu-all.link') # disable @@ -3406,7 +3917,7 @@ def test_rps_cpu(self): self.networkctl_check_unit('dummy98', '12-dummy', '12-dummy', '25-rps-cpu-all') output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus') print(output) - self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}') + self.assertEqual(f'{int(output.replace(",", ""), base=16):x}', f'{(1 << cpu_count) - 1:x}') remove_network_unit('25-rps-cpu-all.link') # empty -> unchanged @@ -3415,7 +3926,7 @@ def test_rps_cpu(self): self.networkctl_check_unit('dummy98', '12-dummy', '12-dummy', '24-rps-cpu-empty') output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus') print(output) - self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}') + self.assertEqual(f'{int(output.replace(",", ""), base=16):x}', f'{(1 << cpu_count) - 1:x}') remove_network_unit('24-rps-cpu-empty.link') # 0, then empty -> unchanged @@ -3424,7 +3935,7 @@ def test_rps_cpu(self): self.networkctl_check_unit('dummy98', '12-dummy', '12-dummy', '25-rps-cpu-0-empty') output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus') print(output) - self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}') + self.assertEqual(f'{int(output.replace(",", ""), base=16):x}', f'{(1 << cpu_count) - 1:x}') remove_network_unit('25-rps-cpu-0-empty.link') # 0, then invalid -> 0 @@ -3445,8 +3956,8 @@ def test_rps_cpu(self): self.assertEqual(int(output.replace(',', ''), base=16), 1) remove_network_unit('24-rps-cpu-invalid.link') -class NetworkdL2TPTests(unittest.TestCase, Utilities): +class NetworkdL2TPTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -3455,8 +3966,12 @@ def tearDown(self): @expectedFailureIfModuleIsNotAvailable('l2tp_eth', 'l2tp_netlink') def test_l2tp_udp(self): - copy_network_unit('11-dummy.netdev', '25-l2tp-dummy.network', - '25-l2tp-udp.netdev', '25-l2tp.network') + copy_network_unit( + '11-dummy.netdev', + '25-l2tp-dummy.network', + '25-l2tp-udp.netdev', + '25-l2tp.network', + ) start_networkd() self.wait_online('test1:routable', 'l2tp-ses1:degraded', 'l2tp-ses2:degraded') @@ -3466,34 +3981,41 @@ def test_l2tp_udp(self): output = check_output('ip l2tp show tunnel tunnel_id 10') print(output) - self.assertRegex(output, "Tunnel 10, encap UDP") - self.assertRegex(output, "From 192.168.30.100 to 192.168.30.101") - self.assertRegex(output, "Peer tunnel 11") - self.assertRegex(output, "UDP source / dest ports: 3000/4000") - self.assertRegex(output, "UDP checksum: enabled") + self.assertRegex(output, 'Tunnel 10, encap UDP') + self.assertRegex(output, 'From 192.168.30.100 to 192.168.30.101') + self.assertRegex(output, 'Peer tunnel 11') + self.assertRegex(output, 'UDP source / dest ports: 3000/4000') + self.assertRegex(output, 'UDP checksum: enabled') output = check_output('ip l2tp show session tid 10 session_id 15') print(output) - self.assertRegex(output, "Session 15 in tunnel 10") - self.assertRegex(output, "Peer session 16, tunnel 11") - self.assertRegex(output, "interface name: l2tp-ses1") + self.assertRegex(output, 'Session 15 in tunnel 10') + self.assertRegex(output, 'Peer session 16, tunnel 11') + self.assertRegex(output, 'interface name: l2tp-ses1') output = check_output('ip l2tp show session tid 10 session_id 17') print(output) - self.assertRegex(output, "Session 17 in tunnel 10") - self.assertRegex(output, "Peer session 18, tunnel 11") - self.assertRegex(output, "interface name: l2tp-ses2") + self.assertRegex(output, 'Session 17 in tunnel 10') + self.assertRegex(output, 'Peer session 18, tunnel 11') + self.assertRegex(output, 'interface name: l2tp-ses2') touch_network_unit( - '11-dummy.netdev', '25-l2tp-dummy.network', - '25-l2tp-udp.netdev', '25-l2tp.network') + '11-dummy.netdev', + '25-l2tp-dummy.network', + '25-l2tp-udp.netdev', + '25-l2tp.network', + ) networkctl_reload() self.wait_online('test1:routable', 'l2tp-ses1:degraded', 'l2tp-ses2:degraded') @expectedFailureIfModuleIsNotAvailable('l2tp_eth', 'l2tp_ip', 'l2tp_netlink') def test_l2tp_ip(self): - copy_network_unit('11-dummy.netdev', '25-l2tp-dummy.network', - '25-l2tp-ip.netdev', '25-l2tp.network') + copy_network_unit( + '11-dummy.netdev', + '25-l2tp-dummy.network', + '25-l2tp-ip.netdev', + '25-l2tp.network', + ) start_networkd() self.wait_online('test1:routable', 'l2tp-ses3:degraded', 'l2tp-ses4:degraded') @@ -3503,30 +4025,33 @@ def test_l2tp_ip(self): output = check_output('ip l2tp show tunnel tunnel_id 10') print(output) - self.assertRegex(output, "Tunnel 10, encap IP") - self.assertRegex(output, "From 192.168.30.100 to 192.168.30.101") - self.assertRegex(output, "Peer tunnel 12") + self.assertRegex(output, 'Tunnel 10, encap IP') + self.assertRegex(output, 'From 192.168.30.100 to 192.168.30.101') + self.assertRegex(output, 'Peer tunnel 12') output = check_output('ip l2tp show session tid 10 session_id 25') print(output) - self.assertRegex(output, "Session 25 in tunnel 10") - self.assertRegex(output, "Peer session 26, tunnel 12") - self.assertRegex(output, "interface name: l2tp-ses3") + self.assertRegex(output, 'Session 25 in tunnel 10') + self.assertRegex(output, 'Peer session 26, tunnel 12') + self.assertRegex(output, 'interface name: l2tp-ses3') output = check_output('ip l2tp show session tid 10 session_id 27') print(output) - self.assertRegex(output, "Session 27 in tunnel 10") - self.assertRegex(output, "Peer session 28, tunnel 12") - self.assertRegex(output, "interface name: l2tp-ses4") + self.assertRegex(output, 'Session 27 in tunnel 10') + self.assertRegex(output, 'Peer session 28, tunnel 12') + self.assertRegex(output, 'interface name: l2tp-ses4') touch_network_unit( - '11-dummy.netdev', '25-l2tp-dummy.network', - '25-l2tp-ip.netdev', '25-l2tp.network') + '11-dummy.netdev', + '25-l2tp-dummy.network', + '25-l2tp-ip.netdev', + '25-l2tp.network', + ) networkctl_reload() self.wait_online('test1:routable', 'l2tp-ses3:degraded', 'l2tp-ses4:degraded') -class NetworkdNetworkTests(unittest.TestCase, Utilities): +class NetworkdNetworkTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -3545,34 +4070,34 @@ def test_ID_NET_MANAGED_BY(self): self.wait_online('test1:off', setup_state='unmanaged') def verify_address_static( - self, - label1: str, - label2: str, - label3: str, - broadcast1: str, - broadcast2: str, - broadcast3: str, - peer1: str, - peer2: str, - peer3: str, - peer4: str, - peer5: str, - peer6: str, - scope1: str, - scope2: str, - deprecated1: str, - deprecated2: str, - deprecated3: str, - deprecated4: str, - route_metric: int, - flag1: str, - flag2: str, - flag3: str, - flag4: str, - ip4_null_16: str, - ip4_null_24: str, - ip6_null_73: str, - ip6_null_74: str, + self, + label1: str, + label2: str, + label3: str, + broadcast1: str, + broadcast2: str, + broadcast3: str, + peer1: str, + peer2: str, + peer3: str, + peer4: str, + peer5: str, + peer6: str, + scope1: str, + scope2: str, + deprecated1: str, + deprecated2: str, + deprecated3: str, + deprecated4: str, + route_metric: int, + flag1: str, + flag2: str, + flag3: str, + flag4: str, + ip4_null_16: str, + ip4_null_24: str, + ip6_null_73: str, + ip6_null_74: str, ): output = check_output('ip address show dev dummy98') print(output) @@ -3614,12 +4139,12 @@ def verify_address_static( self.assertIn(f'inet6 2001:db8:0:f104::2/64 scope global{deprecated4}', output) # route metric - self.assertRegex(output, rf'inet 10.8.1.1/24 (metric {route_metric} |)brd 10.8.1.255 scope global dummy98') + self.assertRegex(output, rf'inet 10.8.1.1/24 (metric {route_metric} |)brd 10.8.1.255 scope global dummy98') # fmt: skip self.assertRegex(output, rf'inet6 2001:db8:0:f105::1/64 (metric {route_metric} |)scope global') output_route = check_output('ip -4 route show dev dummy98 10.8.1.0/24') print(output_route) - self.assertIn(f'10.8.1.0/24 proto kernel scope link src 10.8.1.1 metric {route_metric}', output_route) + self.assertIn(f'10.8.1.0/24 proto kernel scope link src 10.8.1.1 metric {route_metric}', output_route) # fmt: skip output_route = check_output('ip -6 route show dev dummy98 2001:db8:0:f105::/64') print(output_route) @@ -3633,9 +4158,9 @@ def verify_address_static( # null address self.assertTrue(ip4_null_16.endswith('.0.1')) - prefix16 = ip4_null_16[:-len('.0.1')] + prefix16 = ip4_null_16[: -len('.0.1')] self.assertTrue(ip4_null_24.endswith('.1')) - prefix24 = ip4_null_24[:-len('.1')] + prefix24 = ip4_null_24[: -len('.1')] self.assertIn(f'inet {ip4_null_16}/16 brd {prefix16}.255.255 scope global subnet16', output) self.assertIn(f'inet {ip4_null_24}/24 brd {prefix24}.255 scope global subnet24', output) self.assertIn(f'inet6 {ip6_null_73}/73 scope global', output) @@ -3655,7 +4180,6 @@ def verify_address_static( check_json(networkctl_json()) - @expectedFailureIfKernelReturnsInvalidFlags() def test_address_static(self): copy_network_unit('25-address-static.network', '12-dummy.netdev', copy_dropins=False) self.setup_nftset('addr4', 'ipv4_addr') @@ -3897,9 +4421,14 @@ def test_address_peer_ipv4(self): @expectedFailureIfModuleIsNotAvailable('vrf') def test_prefix_route(self): - copy_network_unit('25-prefix-route-with-vrf.network', '12-dummy.netdev', - '25-prefix-route-without-vrf.network', '11-dummy.netdev', - '25-vrf.netdev', '25-vrf.network') + copy_network_unit( + '25-prefix-route-with-vrf.network', + '12-dummy.netdev', + '25-prefix-route-without-vrf.network', + '11-dummy.netdev', + '25-vrf.netdev', + '25-vrf.network', + ) for trial in range(2): if trial == 0: start_networkd() @@ -3924,7 +4453,7 @@ def test_prefix_route(self): if trial == 0: # Kernel's bug? self.assertRegex(output, 'local fdde:11:22::1 proto kernel metric 0 pref medium') - #self.assertRegex(output, 'fdde:11:22::1 proto kernel metric 256 pref medium') + # self.assertRegex(output, 'fdde:11:22::1 proto kernel metric 256 pref medium') self.assertRegex(output, 'local fdde:11:33::1 proto kernel metric 0 pref medium') self.assertRegex(output, 'fdde:11:33::/64 proto kernel metric 256 pref medium') self.assertRegex(output, 'local fdde:11:44::1 proto kernel metric 0 pref medium') @@ -3998,7 +4527,7 @@ def test_configure_without_carrier_yes_ignore_carrier_loss_no(self): carrier_map = {'on': '1', 'off': '0'} routable_map = {'on': 'routable', 'off': 'no-carrier'} - for (carrier, have_config) in [('off', True), ('on', True), ('off', False)]: + for carrier, have_config in [('off', True), ('on', True), ('off', False)]: with self.subTest(carrier=carrier, have_config=have_config): if carrier_map[carrier] != read_link_attr('test1', 'carrier'): check_output(f'ip link set dev test1 carrier {carrier}') @@ -4019,7 +4548,7 @@ def check_routing_policy_rule_test1(self): output = check_output('ip rule list iif test1 priority 111') print(output) - self.assertRegex(output, r'111: from 192.168.100.18 tos (0x08|throughput) iif test1 oif test1 lookup 7') + self.assertRegex(output, r'111: from 192.168.100.18 tos (0x08|throughput) iif test1 oif test1 lookup 7') # fmt: skip output = check_output('ip -6 rule list iif test1 priority 100') print(output) @@ -4078,7 +4607,7 @@ def check_routing_policy_rule_dummy98(self): output = check_output('ip rule list priority 112') print(output) - self.assertRegex(output, r'112: from 192.168.101.18 tos (0x08|throughput) iif dummy98 oif dummy98 lookup 8') + self.assertRegex(output, r'112: from 192.168.101.18 tos (0x08|throughput) iif dummy98 oif dummy98 lookup 8') # fmt: skip def _test_routing_policy_rule(self, manage_foreign_routes): if not manage_foreign_routes: @@ -4125,8 +4654,12 @@ def test_routing_policy_rule(self): self._test_routing_policy_rule(manage_foreign_routes) def test_routing_policy_rule_restart_and_reconfigure(self): - copy_network_unit('25-routing-policy-rule-test1.network', '11-dummy.netdev', - '25-routing-policy-rule-dummy98.network', '12-dummy.netdev') + copy_network_unit( + '25-routing-policy-rule-test1.network', + '11-dummy.netdev', + '25-routing-policy-rule-dummy98.network', + '12-dummy.netdev', + ) # For #11280 and #34068. @@ -4213,7 +4746,8 @@ def test_routing_policy_rule_manual(self): # For issue #36244. copy_network_unit( '11-dummy.netdev', - '25-routing-policy-rule-manual.network') + '25-routing-policy-rule-manual.network', + ) start_networkd() self.wait_operstate('test1', operstate='off', setup_state='configuring', setup_timeout=20) @@ -4248,7 +4782,6 @@ def test_routing_policy_rule_manual(self): else: self.assertFalse(True) - @expectedFailureIfRoutingPolicyPortRangeIsNotAvailable() def test_routing_policy_rule_port_range(self): copy_network_unit('25-fibrule-port-range.network', '11-dummy.netdev') start_networkd() @@ -4263,7 +4796,6 @@ def test_routing_policy_rule_port_range(self): self.assertRegex(output, 'ipproto (tcp|ipproto-6) ') self.assertIn('lookup 7 ', output) - @expectedFailureIfRoutingPolicyIPProtoIsNotAvailable() def test_routing_policy_rule_invert(self): copy_network_unit('25-fibrule-invert.network', '11-dummy.netdev') start_networkd() @@ -4277,7 +4809,6 @@ def test_routing_policy_rule_invert(self): self.assertRegex(output, 'ipproto (tcp|ipproto-6) ') self.assertIn('lookup 7 ', output) - @expectedFailureIfRoutingPolicyL3MasterDeviceIsNotAvailable() def test_routing_policy_rule_l3mdev(self): copy_network_unit('25-fibrule-l3mdev.network', '11-dummy.netdev') start_networkd() @@ -4288,7 +4819,6 @@ def test_routing_policy_rule_l3mdev(self): self.assertIn('1500: from all lookup [l3mdev-table]', output) self.assertIn('2000: from all lookup [l3mdev-table] unreachable', output) - @expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable() def test_routing_policy_rule_uidrange(self): copy_network_unit('25-fibrule-uidrange.network', '11-dummy.netdev') start_networkd() @@ -4389,24 +4919,21 @@ def _check_route_static(self, test1_is_managed: bool): print('### ip route show 192.168.10.2') output = check_output('ip route show 192.168.10.2') print(output) - # old ip command does not show IPv6 gateways... self.assertIn('192.168.10.2 proto static', output) - self.assertIn('nexthop', output) - self.assertIn('dev test1 weight 20', output) - self.assertIn('dev test1 weight 30', output) - self.assertIn('dev dummy98 weight 10', output) - self.assertIn('dev dummy98 weight 5', output) + self.assertIn('nexthop via inet6 2001:1234:5:6fff:ff:ff:ff:ff dev test1 weight 20', output) + self.assertIn('nexthop via inet6 2001:1234:5:7fff:ff:ff:ff:ff dev test1 weight 30', output) + self.assertIn('nexthop via inet6 2001:1234:5:8fff:ff:ff:ff:ff dev dummy98 weight 10', output) + self.assertIn('nexthop via inet6 2001:1234:5:9fff:ff:ff:ff:ff dev dummy98 weight 5', output) print('### ip -6 route show 2001:1234:5:bfff:ff:ff:ff:ff') output = check_output('ip -6 route show 2001:1234:5:bfff:ff:ff:ff:ff') print(output) - # old ip command does not show 'nexthop' keyword and weight... self.assertIn('2001:1234:5:bfff:ff:ff:ff:ff', output) if test1_is_managed: - self.assertIn('via 2001:1234:5:6fff:ff:ff:ff:ff dev test1', output) - self.assertIn('via 2001:1234:5:7fff:ff:ff:ff:ff dev test1', output) - self.assertIn('via 2001:1234:5:8fff:ff:ff:ff:ff dev dummy98', output) - self.assertIn('via 2001:1234:5:9fff:ff:ff:ff:ff dev dummy98', output) + self.assertIn('nexthop via 2001:1234:5:6fff:ff:ff:ff:ff dev test1 weight 20', output) + self.assertIn('nexthop via 2001:1234:5:7fff:ff:ff:ff:ff dev test1 weight 30', output) + self.assertIn('nexthop via 2001:1234:5:8fff:ff:ff:ff:ff dev dummy98 weight 10', output) + self.assertIn('nexthop via 2001:1234:5:9fff:ff:ff:ff:ff dev dummy98 weight 5', output) print('### ip route show 192.168.20.0/24') output = check_output('ip route show 192.168.20.0/24') @@ -4454,8 +4981,12 @@ def _test_route_static(self, manage_foreign_routes): if not manage_foreign_routes: copy_networkd_conf_dropin('networkd-manage-foreign-routes-no.conf') - copy_network_unit('25-route-static.network', '12-dummy.netdev', - '25-route-static-test1.network', '11-dummy.netdev') + copy_network_unit( + '25-route-static.network', + '12-dummy.netdev', + '25-route-static-test1.network', + '11-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:routable', 'test1:routable') self._check_route_static(test1_is_managed=True) @@ -4512,7 +5043,8 @@ def test_route_static_issue_35047(self): '25-route-static-issue-35047.network', '25-route-static-issue-35047.network.d/step1.conf', '12-dummy.netdev', - copy_dropins=False) + copy_dropins=False, + ) start_networkd() self.wait_online('dummy98:routable') @@ -4524,9 +5056,27 @@ def test_route_static_issue_35047(self): self.assertIn('198.51.100.0/24 via 192.0.2.2 proto static', output) check_output('ip link set dev dummy98 down') - self.wait_route_dropped('dummy98', '192.0.2.2 proto kernel scope link src 192.0.2.1', ipv='-4', table='all', timeout_sec=10) - self.wait_route_dropped('dummy98', 'local 192.0.2.1 table local proto kernel scope host src 192.0.2.1', ipv='-4', table='all', timeout_sec=10) - self.wait_route_dropped('dummy98', '198.51.100.0/24 via 192.0.2.2 proto static', ipv='-4', table='all', timeout_sec=10) + self.wait_route_dropped( + 'dummy98', + '192.0.2.2 proto kernel scope link src 192.0.2.1', + ipv='-4', + table='all', + timeout_sec=10, + ) + self.wait_route_dropped( + 'dummy98', + 'local 192.0.2.1 table local proto kernel scope host src 192.0.2.1', + ipv='-4', + table='all', + timeout_sec=10, + ) + self.wait_route_dropped( + 'dummy98', + '198.51.100.0/24 via 192.0.2.2 proto static', + ipv='-4', + table='all', + timeout_sec=10, + ) print('### ip -4 route show table all dev dummy98') output = check_output('ip -4 route show table all dev dummy98') @@ -4565,6 +5115,7 @@ def test_route_static_issue_37714(self): print('### ip -4 route show table all dev dummy98') output = check_output('ip -4 route show table all dev dummy98') print(output) + # fmt: off self.assertIn('default via 192.168.0.193 table 249 proto static src 192.168.0.227 metric 128 onlink', output) self.assertIn('192.168.0.192/26 table 249 proto static scope link src 192.168.0.227 metric 128', output) self.assertIn('10.1.2.2 via 192.168.0.193 proto static src 192.168.0.227 metric 128 onlink', output) @@ -4572,16 +5123,19 @@ def test_route_static_issue_37714(self): self.assertIn('192.168.0.193 proto static scope link src 192.168.0.227 metric 128', output) self.assertIn('local 192.168.0.227 table local proto kernel scope host src 192.168.0.227', output) self.assertIn('broadcast 192.168.0.255 table local proto kernel scope link src 192.168.0.227', output) + # fmt: on print('### ip -6 route show table all dev dummy98') output = check_output('ip -6 route show table all dev dummy98') print(output) + # fmt: off self.assertIn('2000:f00::/64 table 249 proto static src 2000:f00::227 metric 128 pref medium', output) self.assertIn('default via 2000:f00::1 table 249 proto static src 2000:f00::227 metric 128 onlink pref medium', output) self.assertIn('fe80::/64 proto kernel metric 256 pref medium', output) self.assertIn('local 2000:f00::227 table local proto kernel metric 0 pref medium', output) - self.assertRegex(output, 'local fe80:[a-f0-9:]* table local proto kernel metric 0 pref medium', output) + self.assertRegex(output, 'local fe80:[a-f0-9:]* table local proto kernel metric 0 pref medium') self.assertIn('multicast ff00::/8 table local proto kernel metric 256 pref medium', output) + # fmt: on def test_route_via_ipv6(self): copy_network_unit('25-route-via-ipv6.network', '12-dummy.netdev') @@ -4603,6 +5157,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') @@ -4624,8 +5219,12 @@ def test_route_congctl(self): @expectedFailureIfModuleIsNotAvailable('vrf') def test_route_vrf(self): - copy_network_unit('25-route-vrf.network', '12-dummy.netdev', - '25-vrf.netdev', '25-vrf.network') + copy_network_unit( + '25-route-vrf.network', + '12-dummy.netdev', + '25-vrf.netdev', + '25-vrf.network', + ) start_networkd() self.wait_online('dummy98:routable', 'vrf99:carrier') @@ -4676,7 +5275,12 @@ def test_ip_route_ipv6_src_route(self): # a dummy device does not make the addresses go through tentative state, so we # reuse a bond from an earlier test, which does make the addresses go through # tentative state, and do our test on that - copy_network_unit('23-active-slave.network', '25-route-ipv6-src.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev') + copy_network_unit( + '23-active-slave.network', + '25-route-ipv6-src.network', + '25-bond-active-backup-slave.netdev', + '12-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:enslaved', 'bond199:routable') @@ -4697,7 +5301,7 @@ def test_route_preferred_source_with_existing_address(self): output = check_output('ip -6 route list dev dummy98') print(output) - self.assertIn('abcd::/16 via 2001:1234:56:8f63::1:1 proto static src 2001:1234:56:8f63::1', output) + self.assertIn('abcd::/16 via 2001:1234:56:8f63::1:1 proto static src 2001:1234:56:8f63::1', output) # fmt: skip output = check_output('ip -4 route list dev dummy98') print(output) @@ -4741,7 +5345,7 @@ def test_ipv6_proxy_ndp(self): self.assertRegex(output, f'2607:5300:203:5215:{i}::1 *proxy') def test_ipv6_neigh_retrans_time(self): - link='test25' + link = 'test25' copy_network_unit('25-dummy.netdev', '25-dummy.network') start_networkd() @@ -4791,10 +5395,16 @@ def test_ipv6_neigh_retrans_time(self): remove_network_unit('25-ipv6-neigh-retrans-time-4s.network') def test_neighbor(self): - copy_network_unit('12-dummy.netdev', '25-neighbor-dummy.network', '25-neighbor-dummy.network.d/10-step1.conf', - '25-gre-tunnel-remote-any.netdev', '25-neighbor-ip.network', - '25-ip6gre-tunnel-remote-any.netdev', '25-neighbor-ipv6.network', - copy_dropins=False) + copy_network_unit( + '12-dummy.netdev', + '25-neighbor-dummy.network', + '25-neighbor-dummy.network.d/10-step1.conf', + '25-gre-tunnel-remote-any.netdev', + '25-neighbor-ip.network', + '25-ip6gre-tunnel-remote-any.netdev', + '25-neighbor-ipv6.network', + copy_dropins=False, + ) start_networkd() self.wait_online('dummy98:degraded', 'gretun97:routable', 'ip6gretun97:routable') @@ -4807,7 +5417,7 @@ def test_neighbor(self): print('### ip neigh list dev ip6gretun97') output = check_output('ip neigh list dev ip6gretun97') print(output) - self.assertRegex(output, '2001:db8:0:f102::17 lladdr 2a:?00:ff:?de:45:?67:ed:?de:[0:]*:49:?88 PERMANENT') + self.assertRegex(output, '2001:db8:0:f102::17 lladdr 2a:?00:ff:?de:45:?67:ed:?de:[0:]*:49:?88 PERMANENT') # fmt: skip self.assertNotIn('2001:db8:0:f102::18', output) print('### ip neigh list dev dummy98') @@ -4838,8 +5448,10 @@ def test_neighbor(self): check_json(networkctl_json()) - remove_network_unit('25-neighbor-dummy.network.d/10-step1.conf', - '25-neighbor-dummy.network.d/10-step2.conf') + remove_network_unit( + '25-neighbor-dummy.network.d/10-step1.conf', + '25-neighbor-dummy.network.d/10-step2.conf', + ) copy_network_unit('25-neighbor-dummy.network.d/10-step3.conf') networkctl_reload() self.wait_online('dummy98:degraded') @@ -4854,8 +5466,12 @@ def test_neighbor(self): self.assertNotIn('2004:da8:1::1', output) def test_link_local_addressing(self): - copy_network_unit('25-link-local-addressing-yes.network', '11-dummy.netdev', - '25-link-local-addressing-no.network', '12-dummy.netdev') + copy_network_unit( + '25-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-link-local-addressing-no.network', + '12-dummy.netdev', + ) start_networkd() self.wait_online('test1:degraded', 'dummy98:carrier') @@ -4927,6 +5543,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') @@ -5077,7 +5694,7 @@ def _test_activation_policy(self, interface, test): start_networkd() always = test.startswith('always') - initial_up = test != 'manual' and not test.endswith('down') # note: default is up + initial_up = test != 'manual' and not test.endswith('down') # note: default is up expect_up = initial_up next_up = not expect_up @@ -5166,7 +5783,7 @@ def test_activation_policy_required_for_online(self): else: self.tearDown() - print(f'### test_activation_policy_required_for_online(policy={policy}, required={required})') + print(f'### test_activation_policy_required_for_online(policy={policy}, required={required})') # fmt: skip with self.subTest(policy=policy, required=required): self._test_activation_policy_required_for_online(policy, required) @@ -5249,20 +5866,28 @@ def test_keep_configuration_on_restart(self): call('ip -6 addr add 2001:db8:9999:f101::15/64 dev unmanaged0') # Wait for all addresses + # fmt: off self.wait_address('unmanaged0', 'inet 10.20.30.40/32', scope='global', ipv='-4', timeout_sec=10) self.wait_address('unmanaged0', 'inet6 2001:db8:9999:f101::15/64', scope='global', ipv='-6', timeout_sec=10) self.wait_address('unmanaged0', 'inet6 fe80::[0-9a-f:]*/64', scope='link', ipv='-6', timeout_sec=10) + # fmt: on # Wait for all routes + # fmt: off self.wait_route('unmanaged0', 'local 10.20.30.40 proto kernel', table='local', ipv='-4', timeout_sec=10) self.wait_route('unmanaged0', 'local fe80::[0-9a-f:]* proto kernel', table='local', ipv='-6', timeout_sec=10) self.wait_route('unmanaged0', 'multicast ff00::/8 proto kernel', table='local', ipv='-6', timeout_sec=10) self.wait_route('unmanaged0', '2001:db8:9999:f101::/64 proto kernel', table='main', ipv='-6', timeout_sec=10) self.wait_route('unmanaged0', 'fe80::/64 proto kernel', table='main', ipv='-6', timeout_sec=10) + # fmt: on # Start `ip monitor` with output to a temporary file with tempfile.TemporaryFile(mode='r+', prefix='ip_monitor_u') as logfile_unmanaged: - process_u = subprocess.Popen(['ip', 'monitor', 'dev', 'unmanaged0'], stdout=logfile_unmanaged, text=True) + process_u = subprocess.Popen( + ['ip', 'monitor', 'dev', 'unmanaged0'], + stdout=logfile_unmanaged, + text=True, + ) start_networkd() self.check_keep_configuration_on_restart() @@ -5406,10 +6031,12 @@ def check_nexthop(self, manage_foreign_nexthops, first): output = check_output('ip -6 route show dev veth99 2001:1234:5:8f62::1') print(output) + # fmt: off if first: self.assertEqual('2001:1234:5:8f62::1 nhid 2 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output) else: self.assertEqual('2001:1234:5:8f62::1 nhid 7 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output) + # fmt: on output = check_output('ip route show 10.10.10.13') print(output) @@ -5420,10 +6047,12 @@ def check_nexthop(self, manage_foreign_nexthops, first): output = check_output('ip -6 route show 2001:1234:5:8f62::2') print(output) + # fmt: off if first: self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 7 dev lo proto static metric 1024 pref medium', output) else: self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 2 dev lo proto static metric 1024 pref medium', output) + # fmt: on output = check_output('ip route show 10.10.10.14') print(output) @@ -5448,8 +6077,13 @@ def _test_nexthop(self, manage_foreign_nexthops): check_output('ip address add 192.168.20.20/24 dev dummy98') check_output('ip nexthop add id 42 via 192.168.20.2 dev dummy98') - copy_network_unit('25-nexthop-1.network', '25-veth.netdev', '25-veth-peer.network', - '12-dummy.netdev', '25-nexthop-dummy-1.network') + copy_network_unit( + '25-nexthop-1.network', + '25-veth.netdev', + '25-veth-peer.network', + '12-dummy.netdev', + '25-nexthop-dummy-1.network', + ) start_networkd() self.check_nexthop(manage_foreign_nexthops, first=True) @@ -5476,8 +6110,10 @@ def _test_nexthop(self, manage_foreign_nexthops): # Of course, networkctl_reconfigure() below is unnecessary in normal operation, but it is intentional # here to test reconfiguring with different .network files does not trigger race. # See also comments in link_drop_requests(). - networkctl_reconfigure('dummy98') # reconfigured with 25-nexthop-dummy-2.network - networkctl_reload() # reconfigured with 25-nexthop-dummy-1.network + # fmt: off + networkctl_reconfigure('dummy98') # reconfigured with 25-nexthop-dummy-2.network + networkctl_reload() # reconfigured with 25-nexthop-dummy-1.network + # fmt: on self.check_nexthop(manage_foreign_nexthops, first=True) @@ -5527,7 +6163,6 @@ def _test_nexthop(self, manage_foreign_nexthops): print(output) self.assertEqual(output, '') - @expectedFailureIfNexthopIsNotAvailable() def test_nexthop(self): first = True for manage_foreign_nexthops in [True, False]: @@ -5540,8 +6175,8 @@ def test_nexthop(self): with self.subTest(manage_foreign_nexthops=manage_foreign_nexthops): self._test_nexthop(manage_foreign_nexthops) -class NetworkdTCTests(unittest.TestCase, Utilities): +class NetworkdTCTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -5592,7 +6227,7 @@ def test_qdisc_codel(self): output = check_output('tc qdisc show dev dummy98') print(output) self.assertRegex(output, 'qdisc codel 33: root') - self.assertRegex(output, 'limit 2000p target 10(.0)?ms ce_threshold 100(.0)?ms interval 50(.0)?ms ecn') + self.assertRegex(output, 'limit 2000p target 10(.0)?ms ce_threshold 100(.0)?ms interval 50(.0)?ms ecn') # fmt: skip @expectedFailureIfModuleIsNotAvailable('sch_drr') def test_qdisc_drr(self): @@ -5644,7 +6279,7 @@ def test_qdisc_fq_codel(self): output = check_output('tc qdisc show dev dummy98') print(output) self.assertRegex(output, 'qdisc fq_codel 34: root') - self.assertRegex(output, 'limit 20480p flows 2048 quantum 1400 target 10(.0)?ms ce_threshold 100(.0)?ms interval 200(.0)?ms memory_limit 64Mb ecn') + self.assertRegex(output, 'limit 20480p flows 2048 quantum 1400 target 10(.0)?ms ce_threshold 100(.0)?ms interval 200(.0)?ms memory_limit 64Mb ecn') # fmt: skip @expectedFailureIfModuleIsNotAvailable('sch_fq_pie') def test_qdisc_fq_pie(self): @@ -5718,8 +6353,12 @@ def test_qdisc_htb_fifo(self): @expectedFailureIfModuleIsNotAvailable('sch_ingress') def test_qdisc_ingress(self): - copy_network_unit('25-qdisc-clsact.network', '12-dummy.netdev', - '25-qdisc-ingress.network', '11-dummy.netdev') + copy_network_unit( + '25-qdisc-clsact.network', + '12-dummy.netdev', + '25-qdisc-ingress.network', + '11-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:routable', 'test1:routable') @@ -5752,8 +6391,12 @@ def test_qdisc_multiq(self): @expectedFailureIfModuleIsNotAvailable('sch_netem') def test_qdisc_netem(self): - copy_network_unit('25-qdisc-netem.network', '12-dummy.netdev', - '25-qdisc-netem-compat.network', '11-dummy.netdev') + copy_network_unit( + '25-qdisc-netem.network', + '12-dummy.netdev', + '25-qdisc-netem-compat.network', + '11-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:routable', 'test1:routable') @@ -5823,7 +6466,7 @@ def test_qdisc_tbf(self): output = check_output('tc qdisc show dev dummy98') print(output) self.assertRegex(output, 'qdisc tbf 35: root') - self.assertRegex(output, 'rate 1Gbit burst 5000b peakrate 100Gbit minburst (987500b|999200b) lat 70(.0)?ms') + self.assertRegex(output, 'rate 1Gbit burst 5000b peakrate 100Gbit minburst (987500b|999200b) lat 70(.0)?ms') # fmt: skip @expectedFailureIfModuleIsNotAvailable('sch_teql') def test_qdisc_teql(self): @@ -5851,13 +6494,13 @@ def test_qdisc_drop(self): self.assertFalse(networkd_is_failed()) check_output('tc qdisc replace dev dummy98 root fq pacing') self.assertFalse(networkd_is_failed()) - check_output('tc qdisc replace dev dummy98 handle 10: root tbf rate 0.5mbit burst 5kb latency 70ms peakrate 1mbit minburst 1540') + check_output('tc qdisc replace dev dummy98 handle 10: root tbf rate 0.5mbit burst 5kb latency 70ms peakrate 1mbit minburst 1540') # fmt: skip self.assertFalse(networkd_is_failed()) check_output('tc qdisc add dev dummy98 parent 10:1 handle 100: sfq') self.assertFalse(networkd_is_failed()) -class NetworkdStateFileTests(unittest.TestCase, Utilities): +class NetworkdStateFileTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -5885,7 +6528,7 @@ def test_state_file(self): self.assertIn('REQUIRED_FAMILY_FOR_ONLINE=both', output) self.assertIn('ACTIVATION_POLICY=up', output) self.assertIn('NETWORK_FILE=/run/systemd/network/25-state-file-tests.network', output) - self.assertIn('DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com [1111:2222::3333]:1234#ccc.com', output) + self.assertIn('DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com [1111:2222::3333]:1234#ccc.com', output) # fmt: skip self.assertIn('NTP=0.fedora.pool.ntp.org 1.fedora.pool.ntp.org', output) self.assertIn('DOMAINS=hogehoge', output) self.assertIn('ROUTE_DOMAINS=foofoo', output) @@ -5932,7 +6575,7 @@ def test_state_file(self): output = read_link_state_file('dummy98') print(output) - self.assertIn('DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com [1111:2222::3333]:1234#ccc.com', output) + self.assertIn('DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com [1111:2222::3333]:1234#ccc.com', output) # fmt: skip self.assertIn('NTP=0.fedora.pool.ntp.org 1.fedora.pool.ntp.org', output) self.assertIn('DOMAINS=hogehoge', output) self.assertIn('ROUTE_DOMAINS=foofoo', output) @@ -5970,8 +6613,8 @@ def test_address_state(self): self.assertIn('IPV4_ADDRESS_STATE=off', output) self.assertIn('IPV6_ADDRESS_STATE=routable', output) -class NetworkdBondTests(unittest.TestCase, Utilities): +class NetworkdBondTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -6011,7 +6654,12 @@ def test_bond_keep_master(self): self.wait_online('dummy98:enslaved') def test_bond_active_slave(self): - copy_network_unit('23-active-slave.network', '23-bond199.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev') + copy_network_unit( + '23-active-slave.network', + '23-bond199.network', + '25-bond-active-backup-slave.netdev', + '12-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:enslaved', 'bond199:degraded') @@ -6030,12 +6678,18 @@ def test_bond_active_slave(self): '23-active-slave.network', '23-bond199.network', '25-bond-active-backup-slave.netdev', - '12-dummy.netdev') + '12-dummy.netdev', + ) networkctl_reload() self.wait_online('dummy98:enslaved', 'bond199:degraded') def test_bond_primary_slave(self): - copy_network_unit('23-primary-slave.network', '23-bond199.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev') + copy_network_unit( + '23-primary-slave.network', + '23-bond199.network', + '25-bond-active-backup-slave.netdev', + '12-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:enslaved', 'bond199:degraded') @@ -6046,7 +6700,9 @@ def test_bond_primary_slave(self): # for issue #25627 mkdir_p(os.path.join(network_unit_dir, '23-bond199.network.d')) for mac in ['00:11:22:33:44:55', '00:11:22:33:44:56']: - with open(os.path.join(network_unit_dir, '23-bond199.network.d/mac.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '23-bond199.network.d/mac.conf'), mode='w', encoding='utf-8' + ) as f: f.write(f'[Link]\nMACAddress={mac}\n') networkctl_reload() @@ -6057,8 +6713,13 @@ def test_bond_primary_slave(self): self.assertIn(f'link/ether {mac}', output) def test_bond_operstate(self): - copy_network_unit('25-bond.netdev', '11-dummy.netdev', '12-dummy.netdev', - '25-bond99.network', '25-bond-slave.network') + copy_network_unit( + '25-bond.netdev', + '11-dummy.netdev', + '12-dummy.netdev', + '25-bond99.network', + '25-bond-slave.network', + ) start_networkd() self.wait_online('dummy98:enslaved', 'test1:enslaved', 'bond99:routable') @@ -6107,8 +6768,8 @@ def test_bond_operstate(self): print(output) self.assertNotRegex(output, 'NO-CARRIER') -class NetworkdBridgeTests(unittest.TestCase, Utilities): +class NetworkdBridgeTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -6116,8 +6777,13 @@ def tearDown(self): tear_down_common() def test_bridge_mac_none(self): - copy_network_unit('12-dummy-mac.netdev', '26-bridge-mac-slave.network', - '26-bridge-mac.netdev', '26-bridge-mac-master.network', '26-bridge-mac.link') + copy_network_unit( + '12-dummy-mac.netdev', + '26-bridge-mac-slave.network', + '26-bridge-mac.netdev', + '26-bridge-mac-master.network', + '26-bridge-mac.link', + ) start_networkd() self.wait_online('dummy98:enslaved', 'bridge99:degraded') @@ -6130,9 +6796,13 @@ def test_bridge_mac_none(self): self.assertIn('link/ether 12:34:56:78:9a:01', output) def test_bridge_vlan(self): - copy_network_unit('11-dummy.netdev', '26-bridge-vlan-slave.network', - '26-bridge.netdev', '26-bridge-vlan-master.network', - copy_dropins=False) + copy_network_unit( + '11-dummy.netdev', + '26-bridge-vlan-slave.network', + '26-bridge.netdev', + '26-bridge-vlan-master.network', + copy_dropins=False, + ) start_networkd() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6165,8 +6835,10 @@ def test_bridge_vlan(self): self.assertNotIn(f'{i}', output) # Change vlan IDs - copy_network_unit('26-bridge-vlan-slave.network.d/10-override.conf', - '26-bridge-vlan-master.network.d/10-override.conf') + copy_network_unit( + '26-bridge-vlan-slave.network.d/10-override.conf', + '26-bridge-vlan-master.network.d/10-override.conf', + ) networkctl_reload() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6195,8 +6867,10 @@ def test_bridge_vlan(self): self.assertNotIn(f'{i}', output) # Remove several vlan IDs - copy_network_unit('26-bridge-vlan-slave.network.d/20-override.conf', - '26-bridge-vlan-master.network.d/20-override.conf') + copy_network_unit( + '26-bridge-vlan-slave.network.d/20-override.conf', + '26-bridge-vlan-master.network.d/20-override.conf', + ) networkctl_reload() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6225,8 +6899,10 @@ def test_bridge_vlan(self): self.assertNotIn(f'{i}', output) # Remove all vlan IDs - copy_network_unit('26-bridge-vlan-slave.network.d/30-override.conf', - '26-bridge-vlan-master.network.d/30-override.conf') + copy_network_unit( + '26-bridge-vlan-slave.network.d/30-override.conf', + '26-bridge-vlan-master.network.d/30-override.conf', + ) networkctl_reload() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6243,9 +6919,14 @@ def test_bridge_vlan(self): self.assertNotIn(f'{i}', output) def test_bridge_vlan_issue_20373(self): - copy_network_unit('11-dummy.netdev', '26-bridge-vlan-slave-issue-20373.network', - '26-bridge-issue-20373.netdev', '26-bridge-vlan-master-issue-20373.network', - '21-vlan.netdev', '21-vlan.network') + copy_network_unit( + '11-dummy.netdev', + '26-bridge-vlan-slave-issue-20373.network', + '26-bridge-issue-20373.netdev', + '26-bridge-vlan-master-issue-20373.network', + '21-vlan.netdev', + '21-vlan.network', + ) start_networkd() self.wait_online('test1:enslaved', 'bridge99:degraded', 'vlan99:routable') @@ -6262,8 +6943,12 @@ def test_bridge_vlan_issue_20373(self): self.assertIn('600', output) def test_bridge_mdb(self): - copy_network_unit('11-dummy.netdev', '26-bridge-mdb-slave.network', - '26-bridge.netdev', '26-bridge-mdb-master.network') + copy_network_unit( + '11-dummy.netdev', + '26-bridge-mdb-slave.network', + '26-bridge.netdev', + '26-bridge-mdb-master.network', + ) start_networkd() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6271,14 +6956,11 @@ def test_bridge_mdb(self): print(output) self.assertRegex(output, 'dev bridge99 port test1 grp ff02:aaaa:fee5::1:3 permanent *vid 4064') self.assertRegex(output, 'dev bridge99 port test1 grp 224.0.1.1 permanent *vid 4065') + self.assertRegex(output, 'dev bridge99 port bridge99 grp ff02:aaaa:fee5::1:4 temp *vid 4066') + self.assertRegex(output, 'dev bridge99 port bridge99 grp 224.0.1.2 temp *vid 4067') - # Old kernel may not support bridge MDB entries on bridge master - if call_quiet('bridge mdb add dev bridge99 port bridge99 grp 224.0.1.3 temp vid 4068') == 0: - self.assertRegex(output, 'dev bridge99 port bridge99 grp ff02:aaaa:fee5::1:4 temp *vid 4066') - self.assertRegex(output, 'dev bridge99 port bridge99 grp 224.0.1.2 temp *vid 4067') - - # Old kernel may not support L2 bridge MDB entries - if call_quiet('bridge mdb add dev bridge99 port bridge99 grp 01:80:c2:00:00:0f permanent vid 4070') == 0: + # The kernels older than 955062b03fa62b802a1ee34fbb04e39f7a70ae73 (v5.11) do not support L2 bridge MDB entries + if call_quiet('bridge mdb add dev bridge99 port bridge99 grp 01:80:c2:00:00:0f permanent vid 4070') == 0: # fmt: skip self.assertRegex(output, 'dev bridge99 port bridge99 grp 01:80:c2:00:00:0e permanent *vid 4069') def test_bridge_keep_master(self): @@ -6298,18 +6980,18 @@ def test_bridge_keep_master(self): output = check_output('bridge -d link show dummy98') print(output) - self.check_bridge_port_attr('bridge99', 'dummy98', 'path_cost', '400') - self.check_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'path_cost', '400') + self.check_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode', '1') self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood', '0') # CONFIG_BRIDGE_IGMP_SNOOPING=y self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast', '1', allow_enoent=True) - self.check_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress', '1', allow_enoent=True) - self.check_bridge_port_attr('bridge99', 'dummy98', 'learning', '0') - self.check_bridge_port_attr('bridge99', 'dummy98', 'priority', '23') - self.check_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard', '0') - self.check_bridge_port_attr('bridge99', 'dummy98', 'root_block', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress', '1', allow_enoent=True) + self.check_bridge_port_attr('bridge99', 'dummy98', 'learning', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'priority', '23') + self.check_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'root_block', '0') def check_bridge_property(self): self.wait_online('dummy98:enslaved', 'test1:enslaved', 'bridge99:routable') @@ -6336,31 +7018,38 @@ def check_bridge_property(self): output = check_output('bridge -d link show dummy98') print(output) - self.check_bridge_port_attr('bridge99', 'dummy98', 'path_cost', '400') - self.check_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'isolated', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'path_cost', '400') + self.check_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'isolated', '1') self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood', '0') # CONFIG_BRIDGE_IGMP_SNOOPING=y self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast', '1', allow_enoent=True) - self.check_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress', '1', allow_enoent=True) - self.check_bridge_port_attr('bridge99', 'dummy98', 'learning', '0') - self.check_bridge_port_attr('bridge99', 'dummy98', 'priority', '23') - self.check_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard', '0') - self.check_bridge_port_attr('bridge99', 'dummy98', 'root_block', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress', '1', allow_enoent=True) + self.check_bridge_port_attr('bridge99', 'dummy98', 'learning', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'priority', '23') + self.check_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'root_block', '0') output = check_output('bridge -d link show test1') print(output) - self.check_bridge_port_attr('bridge99', 'test1', 'priority', '0') + self.check_bridge_port_attr('bridge99', 'test1', 'priority', '0') self.assertIn('locked on', output) - if ' mab ' in output: # This is new in kernel and iproute2 v6.2 + if ' mab ' in output: # This is new in kernel and iproute2 v6.2 self.assertIn('mab on', output) def test_bridge_property(self): - copy_network_unit('11-dummy.netdev', '12-dummy.netdev', '26-bridge.netdev', - '26-bridge-slave-interface-1.network', '26-bridge-slave-interface-2.network', - '25-bridge99.network', '14-dummy.netdev', '26-bridge-vlan-tunnel.network') + copy_network_unit( + '11-dummy.netdev', + '12-dummy.netdev', + '26-bridge.netdev', + '26-bridge-slave-interface-1.network', + '26-bridge-slave-interface-2.network', + '25-bridge99.network', + '14-dummy.netdev', + '26-bridge-vlan-tunnel.network', + ) start_networkd() self.check_bridge_property() @@ -6372,7 +7061,8 @@ def test_bridge_property(self): '26-bridge-slave-interface-1.network', '26-bridge-slave-interface-2.network', '26-bridge-vlan-tunnel.network', - '25-bridge99.network') + '25-bridge99.network', + ) networkctl_reload() self.check_bridge_property() @@ -6415,7 +7105,7 @@ def test_bridge_property(self): output = check_output('ip address show bridge99') print(output) self.assertNotIn('192.168.0.15/24', output) - self.assertIn('192.168.0.16/24', output) # foreign address is kept + self.assertIn('192.168.0.16/24', output) # foreign address is kept print('### ip -6 route list table all dev bridge99') output = check_output('ip -6 route list table all dev bridge99') @@ -6436,8 +7126,11 @@ def test_bridge_property(self): self.assertIn('mtu 9000 ', output) def test_bridge_configure_without_carrier(self): - copy_network_unit('26-bridge.netdev', '26-bridge-configure-without-carrier.network', - '11-dummy.netdev') + copy_network_unit( + '26-bridge.netdev', + '26-bridge-configure-without-carrier.network', + '11-dummy.netdev', + ) start_networkd() # With ConfigureWithoutCarrier=yes, the bridge should remain configured for all these situations @@ -6445,13 +7138,18 @@ def test_bridge_configure_without_carrier(self): with self.subTest(test=test): if test == 'no-slave': # bridge has no slaves; it's up but *might* not have carrier - self.wait_operstate('bridge99', operstate=r'(no-carrier|routable)', setup_state=None, setup_timeout=30) + self.wait_operstate( + 'bridge99', + operstate=r'(no-carrier|routable)', + setup_state=None, + setup_timeout=30, + ) # due to a bug in the kernel, newly-created bridges are brought up # *with* carrier, unless they have had any setting changed; e.g. # their mac set, priority set, etc. Then, they will lose carrier # as soon as a (down) slave interface is added, and regain carrier # again once the slave interface is brought up. - #self.check_link_attr('bridge99', 'carrier', '0') + # self.check_link_attr('bridge99', 'carrier', '0') elif test == 'add-slave': # add slave to bridge, but leave it down; bridge is definitely no-carrier self.check_link_attr('test1', 'operstate', 'down') @@ -6484,9 +7182,14 @@ def test_bridge_configure_without_carrier(self): self.assertRegex(output, '10.1.2.1') def test_bridge_ignore_carrier_loss(self): - copy_network_unit('11-dummy.netdev', '12-dummy.netdev', '26-bridge.netdev', - '26-bridge-slave-interface-1.network', '26-bridge-slave-interface-2.network', - '25-bridge99-ignore-carrier-loss.network') + copy_network_unit( + '11-dummy.netdev', + '12-dummy.netdev', + '26-bridge.netdev', + '26-bridge-slave-interface-1.network', + '26-bridge-slave-interface-2.network', + '25-bridge99-ignore-carrier-loss.network', + ) start_networkd() self.wait_online('dummy98:enslaved', 'test1:enslaved', 'bridge99:routable') @@ -6501,8 +7204,11 @@ def test_bridge_ignore_carrier_loss(self): self.assertRegex(output, 'inet 192.168.0.16/24 scope global secondary bridge99') def test_bridge_ignore_carrier_loss_frequent_loss_and_gain(self): - copy_network_unit('26-bridge.netdev', '26-bridge-slave-interface-1.network', - '25-bridge99-ignore-carrier-loss.network') + copy_network_unit( + '26-bridge.netdev', + '26-bridge-slave-interface-1.network', + '25-bridge99-ignore-carrier-loss.network', + ) start_networkd() self.wait_online('bridge99:no-carrier') @@ -6522,8 +7228,8 @@ def test_bridge_ignore_carrier_loss_frequent_loss_and_gain(self): print(output) self.assertIn('from all to 8.8.8.8 lookup 100', output) -class NetworkdSRIOVTests(unittest.TestCase, Utilities): +class NetworkdSRIOVTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -6539,10 +7245,12 @@ def setup_netdevsim(self, id=99, num_ports=1, num_vfs=0): # Create VF. if num_vfs > 0: - with open(f'/sys/bus/netdevsim/devices/netdevsim{id}/sriov_numvfs', mode='w', encoding='utf-8') as f: + with open( + f'/sys/bus/netdevsim/devices/netdevsim{id}/sriov_numvfs', mode='w', encoding='utf-8' + ) as f: f.write(f'{num_vfs}') - @expectedFailureIfNetdevsimWithSRIOVIsNotAvailable() + @expectedFailureIfModuleIsNotAvailable('netdevsim') def test_sriov(self): copy_network_unit('25-netdevsim.link', '25-sriov.network') @@ -6553,13 +7261,14 @@ def test_sriov(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off', + ) - @expectedFailureIfNetdevsimWithSRIOVIsNotAvailable() + @expectedFailureIfModuleIsNotAvailable('netdevsim') def test_sriov_udev(self): copy_network_unit('25-sriov.link', '25-sriov-udev.network') @@ -6573,11 +7282,12 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off', + ) self.assertNotIn('vf 3', output) self.assertNotIn('vf 4', output) @@ -6589,12 +7299,13 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off\n *' - 'vf 3' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off\n *' + 'vf 3', + ) self.assertNotIn('vf 4', output) with open(os.path.join(network_unit_dir, '25-sriov.link'), mode='a', encoding='utf-8') as f: @@ -6605,12 +7316,13 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off\n *' - 'vf 3' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off\n *' + 'vf 3', + ) self.assertNotIn('vf 4', output) with open(os.path.join(network_unit_dir, '25-sriov.link'), mode='a', encoding='utf-8') as f: @@ -6621,10 +7333,11 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off', + ) self.assertNotIn('vf 2', output) self.assertNotIn('vf 3', output) self.assertNotIn('vf 4', output) @@ -6637,16 +7350,17 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off', + ) self.assertNotIn('vf 3', output) self.assertNotIn('vf 4', output) -class NetworkdLLDPTests(unittest.TestCase, Utilities): +class NetworkdLLDPTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -6668,12 +7382,12 @@ def test_lldp(self): self.fail() # With interface name - output = networkctl('lldp', 'veth99'); + output = networkctl('lldp', 'veth99') print(output) self.assertRegex(output, r'veth99 .* veth-peer .* .......a...') # With interface name pattern - output = networkctl('lldp', 've*9'); + output = networkctl('lldp', 've*9') print(output) self.assertRegex(output, r'veth99 .* veth-peer .* .......a...') @@ -6720,12 +7434,12 @@ def test_lldp(self): self.fail() # With interface name - output = networkctl('lldp', 'veth99'); + output = networkctl('lldp', 'veth99') print(output) self.assertRegex(output, r'veth99 .* veth-peer .* ....r......') # With interface name pattern - output = networkctl('lldp', 've*9'); + output = networkctl('lldp', 've*9') print(output) self.assertRegex(output, r'veth99 .* veth-peer .* ....r......') @@ -6757,13 +7471,13 @@ def test_lldp(self): # Compare the json output from sender and receiver sender_json = get_link_description('veth-peer')['LLDP'] - receiver_json = json.loads(networkctl('--json=short', 'lldp', 'veth99'))['Neighbors'][0]['Neighbors'][0] + receiver_json = json.loads(networkctl('--json=short', 'lldp', 'veth99'))['Neighbors'][0]['Neighbors'][0] # fmt: skip print(sender_json) print(receiver_json) self.assertEqual(sender_json, receiver_json) -class NetworkdRATests(unittest.TestCase, Utilities): +class NetworkdRATests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -6819,7 +7533,11 @@ def check_ipv6_token_static(self): self.assertRegex(output, '2002:da8:2:0:fa:de:ca:fe') def test_ipv6_token_static(self): - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-token-static.network', + ) start_networkd() self.check_ipv6_token_static() @@ -6840,43 +7558,95 @@ def test_ipv6_token_static(self): def test_ndisc_redirect(self): if not os.path.exists(test_ndisc_send): - self.skipTest(f"{test_ndisc_send} does not exist.") + self.skipTest(f'{test_ndisc_send} does not exist.') - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-token-static.network', + ) start_networkd() self.check_ipv6_token_static() # Introduce three redirect routes. + # fmt: off check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1:1:1a:2b:3c:4d --redirect-destination 2002:da8:1:1:1a:2b:3c:4d') check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1:2:1a:2b:3c:4d --redirect-destination 2002:da8:1:2:1a:2b:3c:4d') check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1:3:1a:2b:3c:4d --redirect-destination 2002:da8:1:3:1a:2b:3c:4d') + # fmt: on self.wait_route('veth99', '2002:da8:1:1:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) self.wait_route('veth99', '2002:da8:1:2:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) self.wait_route('veth99', '2002:da8:1:3:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) # Change the target address of the redirects. + # fmt: off check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address fe80::1 --redirect-destination 2002:da8:1:1:1a:2b:3c:4d') check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address fe80::2 --redirect-destination 2002:da8:1:2:1a:2b:3c:4d') - self.wait_route_dropped('veth99', '2002:da8:1:1:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) - self.wait_route_dropped('veth99', '2002:da8:1:2:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) - self.wait_route('veth99', r'2002:da8:1:1:1a:2b:3c:4d nhid [0-9]* via fe80::1 proto redirect', ipv='-6', timeout_sec=10) - self.wait_route('veth99', r'2002:da8:1:2:1a:2b:3c:4d nhid [0-9]* via fe80::2 proto redirect', ipv='-6', timeout_sec=10) + # fmt: on + self.wait_route_dropped( + 'veth99', + '2002:da8:1:1:1a:2b:3c:4d proto redirect', + ipv='-6', + timeout_sec=10, + ) + self.wait_route_dropped( + 'veth99', + '2002:da8:1:2:1a:2b:3c:4d proto redirect', + ipv='-6', + timeout_sec=10, + ) + self.wait_route( + 'veth99', + r'2002:da8:1:1:1a:2b:3c:4d nhid [0-9]* via fe80::1 proto redirect', + ipv='-6', + timeout_sec=10, + ) + self.wait_route( + 'veth99', + r'2002:da8:1:2:1a:2b:3c:4d nhid [0-9]* via fe80::2 proto redirect', + ipv='-6', + timeout_sec=10, + ) + + print('### before sending NA without router flag') + output = check_output('ip -6 route show dev veth99') + print(output) + self.assertIn('2002:da8:1::/64 proto ra', output) + self.assertIn('2002:da8:2::/64 proto ra', output) + self.assertRegex(output, 'default .* proto ra') + self.assertRegex(output, '2002:da8:1:1:1a:2b:3c:4d .* proto redirect') + self.assertRegex(output, '2002:da8:1:2:1a:2b:3c:4d .* proto redirect') + self.assertIn('2002:da8:1:3:1a:2b:3c:4d proto redirect', output) - # Send Neighbor Advertisement without the router flag to announce the default router is not available anymore. - # Then, verify that all redirect routes and the default route are dropped. + # Send Neighbor Advertisement without the router flag to announce the default router is not available + # anymore. Then, verify that all redirect routes and the default route are dropped. output = check_output('ip -6 address show dev veth-peer scope link') veth_peer_ipv6ll = re.search('fe80:[:0-9a-f]*', output).group() print(f'veth-peer IPv6LL address: {veth_peer_ipv6ll}') - check_output(f'{test_ndisc_send} --interface veth-peer --type neighbor-advertisement --target-address {veth_peer_ipv6ll} --is-router no') - self.wait_route_dropped('veth99', 'proto ra', ipv='-6', timeout_sec=10) + check_output(f'{test_ndisc_send} --interface veth-peer --type neighbor-advertisement --target-address {veth_peer_ipv6ll} --is-router no') # fmt: skip + self.wait_route_dropped('veth99', 'default .* proto ra', ipv='-6', timeout_sec=10) self.wait_route_dropped('veth99', 'proto redirect', ipv='-6', timeout_sec=10) + # Check if the non-default routes are unchanged, and others are actually dropped. + print('### after sending NA without router flag') + output = check_output('ip -6 route show dev veth99') + print(output) + self.assertIn('2002:da8:1::/64 proto ra', output) + self.assertIn('2002:da8:2::/64 proto ra', output) + self.assertNotRegex(output, 'default .* proto ra') + self.assertNotRegex(output, '2002:da8:1:1:1a:2b:3c:4d .* proto redirect') + self.assertNotRegex(output, '2002:da8:1:2:1a:2b:3c:4d .* proto redirect') + self.assertNotIn('2002:da8:1:3:1a:2b:3c:4d proto redirect', output) + # Check if sd-radv refuses RS from the same interface. # See https://github.com/systemd/systemd/pull/32267#discussion_r1566721306 since = datetime.datetime.now() check_output(f'{test_ndisc_send} --interface veth-peer --type rs --dest {veth_peer_ipv6ll}') - self.check_networkd_log('veth-peer: RADV: Received RS from the same interface, ignoring.', since=since) + self.check_networkd_log( + 'veth-peer: RADV: Received RS from the same interface, ignoring.', + since=since, + ) def check_ndisc_mtu(self, mtu): for _ in range(20): @@ -6889,11 +7659,13 @@ def check_ndisc_mtu(self, mtu): def test_ndisc_mtu(self): if not os.path.exists(test_ndisc_send): - self.skipTest(f"{test_ndisc_send} does not exist.") + self.skipTest(f'{test_ndisc_send} does not exist.') - copy_network_unit('25-veth.netdev', - '25-veth-peer-no-address.network', - '25-ipv6-prefix-veth-token-static.network') + copy_network_unit( + '25-veth.netdev', + '25-veth-peer-no-address.network', + '25-ipv6-prefix-veth-token-static.network', + ) start_networkd() self.wait_online('veth-peer:degraded') @@ -6917,16 +7689,24 @@ def test_ndisc_mtu(self): self.check_ndisc_mtu(1700) def test_ipv6_token_prefixstable(self): - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-token-prefixstable.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:degraded') output = check_output('ip -6 address show dev veth99') print(output) - self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable - self.assertIn('2002:da8:2:0:1034:56ff:fe78:9abc/64', output) # EUI64 + self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable + self.assertIn('2002:da8:2:0:1034:56ff:fe78:9abc/64', output) # EUI64 - with open(os.path.join(network_unit_dir, '25-ipv6-prefix-veth-token-prefixstable.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-ipv6-prefix-veth-token-prefixstable.network'), + mode='a', + encoding='utf-8', + ) as f: f.write('\n[IPv6AcceptRA]\nPrefixAllowList=2002:da8:1:0::/64\n') networkctl_reload() @@ -6934,28 +7714,57 @@ def test_ipv6_token_prefixstable(self): output = check_output('ip -6 address show dev veth99') print(output) - self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable - self.assertNotIn('2002:da8:2:0:1034:56ff:fe78:9abc/64', output) # EUI64 + self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable + self.assertNotIn('2002:da8:2:0:1034:56ff:fe78:9abc/64', output) # EUI64 check_output('ip address del 2002:da8:1:0:b47e:7975:fc7a:7d6e/64 dev veth99') check_output('ip address add 2002:da8:1:0:b47e:7975:fc7a:7d6e/64 dev veth-peer nodad') networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - self.wait_address('veth99', '2002:da8:1:0:da5d:e50a:43fd:5d0f/64', ipv='-6', timeout_sec=10) # the 2nd prefixstable - self.wait_address_dropped('veth99', '2002:da8:1:0:b47e:7975:fc7a:7d6e/64', ipv='-6', timeout_sec=10) # the 1st prefixstable + self.wait_address( + 'veth99', + '2002:da8:1:0:da5d:e50a:43fd:5d0f/64', + ipv='-6', + timeout_sec=10, + ) # the 2nd prefixstable + self.wait_address_dropped( + 'veth99', + '2002:da8:1:0:b47e:7975:fc7a:7d6e/64', + ipv='-6', + timeout_sec=10, + ) # the 1st prefixstable check_output('ip address del 2002:da8:1:0:da5d:e50a:43fd:5d0f/64 dev veth99') check_output('ip address add 2002:da8:1:0:da5d:e50a:43fd:5d0f/64 dev veth-peer nodad') networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - self.wait_address('veth99', '2002:da8:1:0:c7e4:77ec:eb31:1b0d/64', ipv='-6', timeout_sec=10) # the 3rd prefixstable - self.wait_address_dropped('veth99', '2002:da8:1:0:da5d:e50a:43fd:5d0f/64', ipv='-6', timeout_sec=10) # the 2nd prefixstable - self.wait_address_dropped('veth99', '2002:da8:1:0:b47e:7975:fc7a:7d6e/64', ipv='-6', timeout_sec=10) # the 1st prefixstable + self.wait_address( + 'veth99', + '2002:da8:1:0:c7e4:77ec:eb31:1b0d/64', + ipv='-6', + timeout_sec=10, + ) # the 3rd prefixstable + self.wait_address_dropped( + 'veth99', + '2002:da8:1:0:da5d:e50a:43fd:5d0f/64', + ipv='-6', + timeout_sec=10, + ) # the 2nd prefixstable + self.wait_address_dropped( + 'veth99', + '2002:da8:1:0:b47e:7975:fc7a:7d6e/64', + ipv='-6', + timeout_sec=10, + ) # the 1st prefixstable def test_ipv6_token_prefixstable_without_address(self): - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable-without-address.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-token-prefixstable-without-address.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:degraded') @@ -6965,21 +7774,29 @@ def test_ipv6_token_prefixstable_without_address(self): self.assertIn('2002:da8:2:0:f689:561a:8eda:7443', output) def test_router_hop_limit(self): - copy_network_unit('25-veth-client.netdev', - '25-veth-router.netdev', - '26-bridge.netdev', - '25-veth-bridge.network', - '25-veth-client.network', - '25-veth-router-hop-limit.network', - '25-bridge99.network') - start_networkd() - self.wait_online('client:routable', 'client-p:enslaved', - 'router:degraded', 'router-p:enslaved', - 'bridge99:routable') + copy_network_unit( + '25-veth-client.netdev', + '25-veth-router.netdev', + '26-bridge.netdev', + '25-veth-bridge.network', + '25-veth-client.network', + '25-veth-router-hop-limit.network', + '25-bridge99.network', + ) + start_networkd() + self.wait_online( + 'client:routable', + 'client-p:enslaved', + 'router:degraded', + 'router-p:enslaved', + 'bridge99:routable', + ) self.check_ipv6_sysctl_attr('client', 'hop_limit', '42') - with open(os.path.join(network_unit_dir, '25-veth-router-hop-limit.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-veth-router-hop-limit.network'), mode='a', encoding='utf-8' + ) as f: f.write('\n[IPv6SendRA]\nHopLimit=43\n') networkctl_reload() @@ -6996,14 +7813,26 @@ def check_router_preference(self, suffix, metric_1, preference_1, metric_2, pref self.wait_online('client:routable') self.wait_address('client', f'2002:da8:1:99:1034:56ff:fe78:9a{suffix}/64', ipv='-6', timeout_sec=10) self.wait_address('client', f'2002:da8:1:98:1034:56ff:fe78:9a{suffix}/64', ipv='-6', timeout_sec=10) - self.wait_route('client', rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric {metric_1}', ipv='-6', timeout_sec=10) - self.wait_route('client', rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric {metric_2}', ipv='-6', timeout_sec=10) + self.wait_route( + 'client', + rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric {metric_1}', + ipv='-6', + timeout_sec=10, + ) + self.wait_route( + 'client', + rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric {metric_2}', + ipv='-6', + timeout_sec=10, + ) print('### ip -6 route show dev client default') output = check_output('ip -6 route show dev client default') print(output) + # fmt: off self.assertRegex(output, rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric {metric_1} expires [0-9]*sec pref {preference_1}') self.assertRegex(output, rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric {metric_2} expires [0-9]*sec pref {preference_2}') + # fmt: on for i in [100, 200, 300, 512, 1024, 2048]: if i not in [metric_1, metric_2]: @@ -7014,20 +7843,26 @@ def check_router_preference(self, suffix, metric_1, preference_1, metric_2, pref self.assertNotIn(f'pref {i}', output) def test_router_preference(self): - copy_network_unit('25-veth-client.netdev', - '25-veth-router-high.netdev', - '25-veth-router-low.netdev', - '26-bridge.netdev', - '25-veth-bridge.network', - '25-veth-client.network', - '25-veth-router-high.network', - '25-veth-router-low.network', - '25-bridge99.network') - start_networkd() - self.wait_online('client-p:enslaved', - 'router-high:degraded', 'router-high-p:enslaved', - 'router-low:degraded', 'router-low-p:enslaved', - 'bridge99:routable') + copy_network_unit( + '25-veth-client.netdev', + '25-veth-router-high.netdev', + '25-veth-router-low.netdev', + '26-bridge.netdev', + '25-veth-bridge.network', + '25-veth-client.network', + '25-veth-router-high.network', + '25-veth-router-low.network', + '25-bridge99.network', + ) + start_networkd() + self.wait_online( + 'client-p:enslaved', + 'router-high:degraded', + 'router-high-p:enslaved', + 'router-low:degraded', + 'router-low-p:enslaved', + 'bridge99:routable', + ) networkctl_reconfigure('client') self.wait_online('client:routable') @@ -7035,64 +7870,135 @@ def test_router_preference(self): # change the map from preference to metric. with open(os.path.join(network_unit_dir, '25-veth-client.network'), mode='a', encoding='utf-8') as f: - f.write('\n[Link]\nMACAddress=12:34:56:78:9a:01\n[IPv6AcceptRA]\nRouteMetric=100:200:300\n') + f.write(''' +[Link] +MACAddress=12:34:56:78:9a:01 +[IPv6AcceptRA] +RouteMetric=100:200:300 +''') networkctl_reload() self.check_router_preference('01', 100, 'high', 300, 'low') # swap the preference (for issue #28439) - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=low\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=high\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=low +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=high +''') networkctl_reload() self.check_router_preference('01', 300, 'low', 100, 'high') # Use the same preference, and check if the two routes are not coalesced. See issue #33470. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=medium\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=medium\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=medium +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=medium +''') networkctl_reload() self.check_router_preference('01', 200, 'medium', 200, 'medium') # Use route options to configure default routes. # The preference specified in the RA header should be ignored. See issue #33468. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=high\n[IPv6RoutePrefix]\nRoute=::/0\nLifetimeSec=1200\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=low\n[IPv6RoutePrefix]\nRoute=::/0\nLifetimeSec=1200\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=high +[IPv6RoutePrefix] +Route=::/0 +LifetimeSec=1200 +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=low +[IPv6RoutePrefix] +Route=::/0 +LifetimeSec=1200 +''') networkctl_reload() self.check_router_preference('01', 200, 'medium', 200, 'medium') # Set zero lifetime to the route options. # The preference specified in the RA header should be used. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=0\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=0\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=0 +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=0 +''') networkctl_reload() self.check_router_preference('01', 100, 'high', 300, 'low') # Use route options with preference to configure default routes. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=1200\nPreference=low\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=1200\nPreference=high\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=1200 +Preference=low +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=1200 +Preference=high +''') networkctl_reload() self.check_router_preference('01', 300, 'low', 100, 'high') # Set zero lifetime again to the route options. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=0\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=0\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=0 +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=0 +''') networkctl_reload() self.check_router_preference('01', 100, 'high', 300, 'low') def _test_ndisc_vs_static_route(self, manage_foreign_nexthops): if not manage_foreign_nexthops: copy_networkd_conf_dropin('networkd-manage-foreign-nexthops-no.conf') - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-static-route.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-static-route.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:degraded') @@ -7102,7 +8008,7 @@ def _test_ndisc_vs_static_route(self, manage_foreign_nexthops): print(output) self.assertIn('via fe80::1034:56ff:fe78:9abd proto static metric 256 pref medium', output) if manage_foreign_nexthops: - self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9abd proto ra metric 256 expires [0-9]*sec pref medium') + self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9abd proto ra metric 256 expires [0-9]*sec pref medium') # fmt: skip else: self.assertNotIn('proto ra', output) @@ -7110,15 +8016,23 @@ def _test_ndisc_vs_static_route(self, manage_foreign_nexthops): output = check_output('ip -6 nexthop show dev veth99') print(output) if manage_foreign_nexthops: - self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abd dev veth99 scope link proto ra') + self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abd dev veth99 scope link proto ra') # fmt: skip else: self.assertEqual(output, '') # Also check if the static route is protected from RA with zero lifetime with open(os.path.join(network_unit_dir, '25-ipv6-prefix.network'), mode='a', encoding='utf-8') as f: - f.write('\n[Network]\nIPv6SendRA=no\n') - networkctl_reload() # This makes veth-peer being reconfigured, and send RA with zero lifetime - self.wait_route_dropped('veth99', r'default (nhid [0-9]* |)via fe80::1034:56ff:fe78:9abd proto ra metric 256', ipv='-6', timeout_sec=10) + f.write(''' +[Network] +IPv6SendRA=no +''') + networkctl_reload() # This makes veth-peer being reconfigured, and send RA with zero lifetime + self.wait_route_dropped( + 'veth99', + r'default (nhid [0-9]* |)via fe80::1034:56ff:fe78:9abd proto ra metric 256', + ipv='-6', + timeout_sec=10, + ) print('### ip -6 route show dev veth99 default') output = check_output('ip -6 route show dev veth99 default') @@ -7146,18 +8060,27 @@ def test_ndisc_vs_static_route(self): # radvd supports captive portal since v2.20. # https://github.com/radvd-project/radvd/commit/791179a7f730decbddb2290ef0e34aa85d71b1bc - @unittest.skipUnless(radvd_check_config('captive-portal.conf'), "Installed radvd doesn't support captive portals") + @unittest.skipUnless( + radvd_check_config('captive-portal.conf'), + "Installed radvd doesn't support captive portals", + ) def test_captive_portal(self): - copy_network_unit('25-veth-client.netdev', - '25-veth-router-captive.netdev', - '26-bridge.netdev', - '25-veth-client-captive.network', - '25-veth-router-captive.network', - '25-veth-bridge-captive.network', - '25-bridge99.network') + copy_network_unit( + '25-veth-client.netdev', + '25-veth-router-captive.netdev', + '26-bridge.netdev', + '25-veth-client-captive.network', + '25-veth-router-captive.network', + '25-veth-bridge-captive.network', + '25-bridge99.network', + ) start_networkd() - self.wait_online('bridge99:routable', 'client-p:enslaved', - 'router-captive:degraded', 'router-captivep:enslaved') + self.wait_online( + 'bridge99:routable', + 'client-p:enslaved', + 'router-captive:degraded', + 'router-captivep:enslaved', + ) start_radvd(config_file='captive-portal.conf') networkctl_reconfigure('client') @@ -7168,31 +8091,53 @@ def test_captive_portal(self): print(output) self.assertIn('Captive Portal: http://systemd.io', output) - @unittest.skipUnless(radvd_check_config('captive-portal.conf'), "Installed radvd doesn't support captive portals") + @unittest.skipUnless( + radvd_check_config('captive-portal.conf'), + "Installed radvd doesn't support captive portals", + ) def test_invalid_captive_portal(self): def radvd_write_config(captive_portal_uri): - with open(os.path.join(networkd_ci_temp_dir, 'radvd/bogus-captive-portal.conf'), mode='w', encoding='utf-8') as f: - f.write(f'interface router-captive {{ AdvSendAdvert on; AdvCaptivePortalAPI "{captive_portal_uri}"; prefix 2002:da8:1:99::/64 {{ AdvOnLink on; AdvAutonomous on; }}; }};') + with open( + os.path.join(networkd_ci_temp_dir, 'radvd/bogus-captive-portal.conf'), + mode='w', + encoding='utf-8', + ) as f: + f.write(f''' +interface router-captive {{ + AdvSendAdvert on; + AdvCaptivePortalAPI "{captive_portal_uri}"; + prefix 2002:da8:1:99::/64 {{ + AdvOnLink on; + AdvAutonomous on; + }}; +}}; +''') captive_portal_uris = [ - "42ěščěškd ěšč ě s", - " ", - "🤔", + '42ěščěškd ěšč ě s', + ' ', + '🤔', ] - copy_network_unit('25-veth-client.netdev', - '25-veth-router-captive.netdev', - '26-bridge.netdev', - '25-veth-client-captive.network', - '25-veth-router-captive.network', - '25-veth-bridge-captive.network', - '25-bridge99.network') + copy_network_unit( + '25-veth-client.netdev', + '25-veth-router-captive.netdev', + '26-bridge.netdev', + '25-veth-client-captive.network', + '25-veth-router-captive.network', + '25-veth-bridge-captive.network', + '25-bridge99.network', + ) start_networkd() - self.wait_online('bridge99:routable', 'client-p:enslaved', - 'router-captive:degraded', 'router-captivep:enslaved') + self.wait_online( + 'bridge99:routable', + 'client-p:enslaved', + 'router-captive:degraded', + 'router-captivep:enslaved', + ) for uri in captive_portal_uris: - print(f"Captive portal: {uri}") + print(f'Captive portal: {uri}') radvd_write_config(uri) stop_radvd() start_radvd(config_file='bogus-captive-portal.conf') @@ -7204,8 +8149,8 @@ def radvd_write_config(captive_portal_uri): print(output) self.assertNotIn('Captive Portal:', output) -class NetworkdDHCPServerTests(unittest.TestCase, Utilities): +class NetworkdDHCPServerTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -7222,7 +8167,10 @@ def check_dhcp_server(self, persist_leases='yes'): output = networkctl_status('veth-peer') print(output) - self.assertRegex(output, "Offered DHCP leases: 192.168.5.[0-9]*") + 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' @@ -7246,10 +8194,10 @@ def test_dhcp_server(self): self.wait_online('veth-peer:routable') for _ in range(10): - output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth-peer', env=env) + output = networkctl_status('veth-peer') if 'Offered DHCP leases: 192.168.5.' in output: break - time.sleep(.2) + time.sleep(0.2) else: self.fail() @@ -7286,7 +8234,11 @@ def test_dhcp_server_persist_leases_runtime(self): self.check_dhcp_server(persist_leases='runtime') def test_dhcp_server_null_server_address(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server-null-server-address.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client.network', + '25-dhcp-server-null-server-address.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7331,8 +8283,13 @@ def test_dhcp_server_null_server_address(self): self.assertIn(f'Offered DHCP leases: {client_address}', output) def test_dhcp_server_with_uplink(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server-downstream.network', - '12-dummy.netdev', '25-dhcp-server-uplink.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client.network', + '25-dhcp-server-downstream.network', + '12-dummy.netdev', + '25-dhcp-server-uplink.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7344,7 +8301,11 @@ def test_dhcp_server_with_uplink(self): self.assertIn('NTP: 192.168.5.1', output) def test_emit_router_timezone(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client-timezone-router.network', '25-dhcp-server-timezone-router.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-timezone-router.network', + '25-dhcp-server-timezone-router.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7355,7 +8316,11 @@ def test_emit_router_timezone(self): self.assertIn('Time Zone: Europe/Berlin', output) def test_dhcp_server_static_lease_mac_by_network(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client-static-lease.network', '25-dhcp-server-static-lease.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-static-lease.network', + '25-dhcp-server-static-lease.network', + ) copy_networkd_conf_dropin('10-dhcp-client-id-duid.conf') start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7387,9 +8352,11 @@ def test_dhcp_server_static_lease_duid(self): self.assertRegex(output, 'DHCPv4 Client ID: IAID:[0-9a-z]*/DUID') def test_dhcp_server_static_lease_hostname_simple(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-client-simple-hostname.network', - '25-dhcp-server-static-hostname.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-simple-hostname.network', + '25-dhcp-server-static-hostname.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7399,9 +8366,11 @@ def test_dhcp_server_static_lease_hostname_simple(self): self.assertEqual(data['DHCPv4Client']['Lease']['Hostname'], 'simple-host') def test_dhcp_server_static_lease_hostname_fqdn(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-client-fqdn-hostname.network', - '25-dhcp-server-static-hostname.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-fqdn-hostname.network', + '25-dhcp-server-static-hostname.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7411,7 +8380,11 @@ def test_dhcp_server_static_lease_hostname_fqdn(self): self.assertEqual(data['DHCPv4Client']['Lease']['Hostname'], 'fqdn.example.com') def test_dhcp_server_resolve_hook(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client-resolve-hook.network', '25-dhcp-server-resolve-hook.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-resolve-hook.network', + '25-dhcp-server-resolve-hook.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7421,7 +8394,6 @@ def test_dhcp_server_resolve_hook(self): class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities): - def setUp(self): setup_common() @@ -7429,34 +8401,158 @@ def tearDown(self): tear_down_common() def test_relay_agent(self): - copy_network_unit('25-agent-veth-client.netdev', - '25-agent-veth-server.netdev', - '25-agent-client.network', - '25-agent-server.network', - '25-agent-client-peer.network', - '25-agent-server-peer.network') + check_output('ip netns add ns-server') + check_output('ip link add server type veth peer server-peer') + 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') + + start_dnsmasq( + namespace='ns-server', + interface='server', + ipv4_range='198.51.100.100,198.51.100.109', + ipv4_router='198.51.100.10', + ) + + copy_network_unit( + '25-agent-veth-client.netdev', + '25-agent-client.network', + '25-agent-client-peer.network', + '25-agent-server-peer.network', + ) start_networkd() self.wait_online('client:routable') output = networkctl_status('client') print(output) - self.assertRegex(output, r'Address: 192.168.5.150 \(DHCPv4 via 192.168.5.1\)') + self.assertRegex(output, r'Address: 198\.51\.100\.10[0-9] \(DHCPv4 via 192\.0\.2\.1\)') + + def test_relay_agent_on_bridge(self): + copy_network_unit( + '25-agent-bridge.netdev', + '25-agent-veth-client.netdev', + '25-agent-bridge.network', + '25-agent-bridge-port.network', + '25-agent-client.network', + ) + start_networkd() + self.wait_online('bridge-relay:routable', 'client-peer:enslaved') + + # For issue #30763. + self.check_networkd_log('bridge-relay: Relaying DHCPv4 messages') + + def test_sd_dhcp_relay(self): + check_output('ip netns add ns-relay') + check_output('ip netns add ns-server') + + check_output('ip link add relay-down type veth peer client') + check_output('ip link set relay-down netns ns-relay') + check_output('ip netns exec ns-relay ip link set relay-down up') + + check_output('ip link add relay-up type veth peer server') + check_output('ip link set relay-up netns ns-relay') + check_output('ip netns exec ns-relay ip link set relay-up up') + + 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') + + with tempfile.TemporaryDirectory() as tmp: + conf_dir = pathlib.Path(tmp) / 'run/systemd/networkd.conf.d' + unit_dir = pathlib.Path(tmp) / 'run/systemd/network' + netif_dir = pathlib.Path(tmp) / 'run/systemd/netif' + mkdir_p(conf_dir) + mkdir_p(unit_dir) + mkdir_p(netif_dir) + + # Do not use shutil.chown(), as it fails when running with sanitizers. + check_output(f'chown systemd-network:systemd-network {netif_dir}') + + cp(pathlib.Path(networkd_ci_temp_dir) / '25-dhcp-relay.conf', conf_dir) + cp(pathlib.Path(networkd_ci_temp_dir) / '25-dhcp-relay-downstream.network', unit_dir) + cp(pathlib.Path(networkd_ci_temp_dir) / '25-dhcp-relay-upstream.network', unit_dir) + + cmd = [ + 'systemd-run', + '--unit=networkd-test-dhcp-relay.service', + '--service-type=notify-reload', + '-p', 'User=systemd-network', + '-p', 'FileDescriptorStoreMax=512', + '-p', 'AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_BPF CAP_SYS_ADMIN', + '-p', 'CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_BPF CAP_SYS_ADMIN', + '-p', f'BindPaths={conf_dir}:/run/systemd/networkd.conf.d', + '-p', f'BindPaths={unit_dir}:/run/systemd/network', + '-p', f'BindPaths={netif_dir}:/run/systemd/netif', + '-p', 'TemporaryFileSystem=/run/dbus', + '-p', 'TemporaryFileSystem=/run/systemd/report', + '-p', 'TemporaryFileSystem=/run/systemd/resolve.hook', + '-p', 'TemporaryFileSystem=/var/lib/systemd/network', + '-p', 'TemporaryFileSystem=/etc/systemd/network', + '-p', 'TemporaryFileSystem=/usr/lib/systemd/network', + '-p', 'ReadOnlyPaths=/sys', + '-p', 'NetworkNamespacePath=/run/netns/ns-relay', + ] # fmt: skip + if enable_debug: + cmd += ['-p', 'Environment=SYSTEMD_LOG_LEVEL=debug'] + if asan_options: + cmd += ['-p', f'Environment=ASAN_OPTIONS={asan_options}'] + if lsan_options: + cmd += ['-p', f'Environment=LSAN_OPTIONS={lsan_options}'] + if ubsan_options: + cmd += ['-p', f'Environment=UBSAN_OPTIONS={ubsan_options}'] + if asan_options or lsan_options or ubsan_options: + cmd += ['-p', 'TimeoutStopFailureMode=abort'] + + cmd += [networkd_bin] + + try: + check_output(*cmd) + + start_dnsmasq( + namespace='ns-server', + interface='server', + ipv4_range='198.51.100.100,198.51.100.109', + ipv4_router='198.51.100.10', + ) + + copy_network_unit('25-dhcp-client-simple.network') + 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'198\.51\.100\.10[0-9]/24') + + print('## ip -4 route show dev client') + output = check_output('ip -4 route show dev client') + print(output) + # fmt: off + self.assertRegex(output, r'default via 198\.51\.100\.10 proto dhcp src 198\.51\.100\.10[0-9]') + self.assertRegex(output, r'198\.51\.100\.0/24 proto kernel scope link src 198\.51\.100\.10[0-9]') + self.assertRegex(output, r'198\.51\.100\.10 proto dhcp scope link src 198\.51\.100\.10[0-9]') + # fmt: on + + print('## dnsmasq log') + output = read_dnsmasq_log_file() + print(output) + # The option 82 is logged as agent-info since dnsmasq-2.92, otherwise logged as agent-id. + self.assertRegex(output, r'option: 82 (agent-id|agent-info)') - def test_relay_agent_on_bridge(self): - copy_network_unit('25-agent-bridge.netdev', - '25-agent-veth-client.netdev', - '25-agent-bridge.network', - '25-agent-bridge-port.network', - '25-agent-client.network') - start_networkd() - self.wait_online('bridge-relay:routable', 'client-peer:enslaved') + print('## journal of networkd-test-dhcp-relay.service') + check_output('journalctl --sync') + output = check_output('journalctl --no-hostname --output=short-monotonic --unit=networkd-test-dhcp-relay.service -I') # fmt: skip + self.assertIn('relay-down: DHCPv4 relay: Received BOOTREQUEST', output) + self.assertIn('relay-up: DHCPv4 relay: Forwarded BOOTREQUEST', output) + self.assertIn('relay-up: DHCPv4 relay: Received BOOTREPLY', output) + self.assertIn('relay-down: DHCPv4 relay: Forwarded BOOTREPLY', output) + finally: + call_quiet('systemctl stop networkd-test-dhcp-relay.service') + call_quiet('systemctl reset-failed networkd-test-dhcp-relay.service') - # For issue #30763. - self.check_networkd_log('bridge-relay: DHCPv4 server: STARTED') class NetworkdDHCPClientTests(unittest.TestCase, Utilities): - def setUp(self): setup_common() @@ -7464,7 +8560,11 @@ def tearDown(self): tear_down_common() def test_dhcp_client_ipv6_only(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv6-only.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv6-only.network', + ) start_networkd() self.wait_online('veth-peer:carrier') @@ -7472,10 +8572,12 @@ def test_dhcp_client_ipv6_only(self): # information request mode # The name ipv6-only option may not be supported by older dnsmasq # start_dnsmasq('--dhcp-option=option:ipv6-only,300') - start_dnsmasq('--dhcp-option=108,00:00:02:00', - '--dhcp-option=option6:dns-server,[2600::ee]', - '--dhcp-option=option6:ntp-server,[2600::ff]', - ra_mode='ra-stateless') + start_dnsmasq( + '--dhcp-option=108,00:00:02:00', + '--dhcp-option=option6:dns-server,[2600::ee]', + '--dhcp-option=option6:ntp-server,[2600::ff]', + ra_mode='ra-stateless', + ) self.wait_online('veth99:routable', 'veth-peer:routable') # DHCPv6 REPLY for INFORMATION-REQUEST may be received after the link entered configured state. @@ -7484,7 +8586,7 @@ def test_dhcp_client_ipv6_only(self): output = read_link_state_file('veth99') if 'DNS=2600::ee' in output: break - time.sleep(.2) + time.sleep(0.2) # Check link state file print('## link state file') @@ -7514,9 +8616,11 @@ def test_dhcp_client_ipv6_only(self): # solicit mode stop_dnsmasq() - start_dnsmasq('--dhcp-option=108,00:00:02:00', - '--dhcp-option=option6:dns-server,[2600::ee]', - '--dhcp-option=option6:ntp-server,[2600::ff]') + start_dnsmasq( + '--dhcp-option=108,00:00:02:00', + '--dhcp-option=option6:dns-server,[2600::ee]', + '--dhcp-option=option6:ntp-server,[2600::ff]', + ) networkctl_reconfigure('veth99') self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7567,13 +8671,17 @@ def test_dhcp_client_ipv6_only(self): check_json(networkctl_json('veth99')) # Testing without rapid commit support - with open(os.path.join(network_unit_dir, '25-dhcp-client-ipv6-only.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client-ipv6-only.network'), mode='a', encoding='utf-8' + ) as f: f.write('\n[DHCPv6]\nRapidCommit=no\n') stop_dnsmasq() - start_dnsmasq('--dhcp-option=108,00:00:02:00', - '--dhcp-option=option6:dns-server,[2600::ee]', - '--dhcp-option=option6:ntp-server,[2600::ff]') + start_dnsmasq( + '--dhcp-option=108,00:00:02:00', + '--dhcp-option=option6:dns-server,[2600::ee]', + '--dhcp-option=option6:ntp-server,[2600::ff]', + ) networkctl_reload() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7620,25 +8728,29 @@ def test_dhcp_client_ipv6_only(self): check_json(networkctl_json('veth99')) def test_dhcp_client_ipv6_dbus_status(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv6-only.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv6-only.network', + ) start_networkd() self.wait_online('veth-peer:carrier') # Note that at this point the DHCPv6 client has not been started because no RA (with managed # bit set) has yet been received and the configuration does not include WithoutRA=true state = get_dhcp6_client_state('veth99') - print(f"DHCPv6 client state = {state}") + print(f'DHCPv6 client state = {state}') self.assertEqual(state, 'stopped') state = get_dhcp4_client_state('veth99') - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'selecting') start_dnsmasq('--dhcp-option=108,00:00:02:00') self.wait_online('veth99:routable', 'veth-peer:routable') state = get_dhcp6_client_state('veth99') - print(f"DHCPv6 client state = {state}") + print(f'DHCPv6 client state = {state}') self.assertEqual(state, 'bound') # DHCPv4 client will stop after an DHCPOFFER message received, so we need to wait for a while. @@ -7646,9 +8758,9 @@ def test_dhcp_client_ipv6_dbus_status(self): state = get_dhcp4_client_state('veth99') if state == 'stopped': break - time.sleep(.2) + time.sleep(0.2) - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'stopped') # restart dnsmasq to clear log @@ -7663,9 +8775,9 @@ def test_dhcp_client_ipv6_dbus_status(self): state = get_dhcp4_client_state('veth99') if state == 'stopped': break - time.sleep(.2) + time.sleep(0.2) - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'stopped') print('## dnsmasq log') @@ -7677,7 +8789,11 @@ def test_dhcp_client_ipv6_dbus_status(self): self.assertNotIn('DHCPACK(veth-peer)', output) def test_dhcp_client_ipv6_only_with_custom_client_identifier(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv6-only-custom-client-identifier.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv6-only-custom-client-identifier.network', + ) start_networkd() self.wait_online('veth-peer:carrier') @@ -7699,10 +8815,14 @@ def test_dhcp_client_ipv6_only_with_custom_client_identifier(self): self.assertIn('DHCPREPLY(veth-peer)', output) self.assertIn('sent size: 0 option: 14 rapid-commit', output) - @expectedFailureIfKernelReturnsInvalidFlags() def test_dhcp_client_ipv4_only(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-only.network', - '25-sit-dhcp4.netdev', '25-sit-dhcp4.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv4-only.network', + '25-sit-dhcp4.netdev', + '25-sit-dhcp4.network', + ) self.setup_nftset('addr4', 'ipv4_addr') self.setup_nftset('network4', 'ipv4_addr', 'flags interval;') @@ -7710,11 +8830,13 @@ def test_dhcp_client_ipv4_only(self): start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', - '--dhcp-option=option:sip-server,192.168.5.21,192.168.5.22', - '--dhcp-option=option:domain-search,example.com', - '--dhcp-alternate-port=67,5555', - ipv4_range='192.168.5.110,192.168.5.119') + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', + '--dhcp-option=option:sip-server,192.168.5.21,192.168.5.22', + '--dhcp-option=option:domain-search,example.com', + '--dhcp-alternate-port=67,5555', + ipv4_range='192.168.5.110,192.168.5.119', + ) self.wait_online('veth99:routable', 'veth-peer:routable', 'sit-dhcp4:carrier') self.wait_address('veth99', r'inet 192.168.5.11[0-9]*/24', ipv='-4') @@ -7723,7 +8845,7 @@ def test_dhcp_client_ipv4_only(self): print(output) self.assertIn('mtu 1492', output) self.assertIn('inet 192.168.5.250/24 brd 192.168.5.255 scope global veth99', output) - self.assertRegex(output, r'inet 192.168.5.11[0-9]/24 metric 24 brd 192.168.5.255 scope global secondary dynamic noprefixroute test-label') + self.assertRegex(output, r'inet 192.168.5.11[0-9]/24 metric 24 brd 192.168.5.255 scope global secondary dynamic noprefixroute test-label') # fmt: skip self.assertNotIn('2600::', output) output = check_output('ip -4 --json address show dev veth99') @@ -7798,7 +8920,7 @@ def test_dhcp_client_ipv4_only(self): print('## tunnel') output = check_output('ip -d link show sit-dhcp4') print(output) - self.assertRegex(output, fr'sit (ip6ip )?remote any local {address1} dev veth99') + self.assertRegex(output, rf'sit (ip6ip )?remote any local {address1} dev veth99') print('## dnsmasq log') output = read_dnsmasq_log_file() @@ -7808,13 +8930,52 @@ def test_dhcp_client_ipv4_only(self): self.assertIn('client provides name: test-hostname', output) self.assertIn('26:mtu', output) + print('### networkctl dhcp-lease') + output = networkctl_dhcp_lease('veth99') + print(output) + # header + self.assertIn('Hardware Type: ETHER', output) + self.assertIn('Hardware Address: 12:34:56:78:9a:bc', output) + self.assertIn(f'Client Address: {address1}', output) + self.assertIn('Server Address: 192.168.5.1', output) + # options + self.assertRegex(output, r'1 subnet mask *255\.255\.255\.0') + self.assertRegex(output, r'3 router *192\.168\.5\.1') + self.assertRegex(output, r'6 domain name server *192\.168\.5\.6\n *192\.168\.5\.7') + self.assertRegex(output, r'26 MTU size *1492') + self.assertRegex(output, r'28 broadcast address *192\.168\.5\.255') + self.assertRegex(output, r'51 lease time *2min') + self.assertRegex(output, r'53 message type *5') + self.assertRegex(output, r'54 server identifier *192\.168\.5\.1') + self.assertRegex(output, r'119 domain search *example\.com') + self.assertRegex(output, r'120 SIP server *192\.168\.5\.21\n *192\.168\.5\.22') + # extra arguments + output = networkctl_dhcp_lease('veth99', '1') + print(output) + self.assertRegex(output, r'1 subnet mask *255\.255\.255\.0') + output = networkctl_dhcp_lease('veth99', '1:auto') + print(output) + self.assertRegex(output, r'1 subnet mask *255\.255\.255\.0') + output = networkctl_dhcp_lease('veth99', '1:hex') + print(output) + self.assertRegex(output, 'CODE *NAME *DATA') + self.assertRegex(output, r'1 subnet mask *ff:ff:ff:00') + output = networkctl_dhcp_lease('veth99', '1:hex', '--no-legend') + print(output) + self.assertNotIn('CODE', output) + self.assertNotIn('NAME', output) + self.assertNotIn('DATA', output) + self.assertRegex(output, r'1 subnet mask *ff:ff:ff:00') + # change address range, DNS servers, and Domains stop_dnsmasq() - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1,192.168.5.7,192.168.5.8', - '--dhcp-option=option:sip-server,192.168.5.23,192.168.5.24', - '--dhcp-option=option:domain-search,foo.example.com', - '--dhcp-alternate-port=67,5555', - ipv4_range='192.168.5.120,192.168.5.129',) + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.1,192.168.5.7,192.168.5.8', + '--dhcp-option=option:sip-server,192.168.5.23,192.168.5.24', + '--dhcp-option=option:domain-search,foo.example.com', + '--dhcp-alternate-port=67,5555', + ipv4_range='192.168.5.120,192.168.5.129', + ) # Sleep for 120 sec as the dnsmasq minimum lease time can only be set to 120 print('Wait for the DHCP lease to be expired') @@ -7829,7 +8990,7 @@ def test_dhcp_client_ipv4_only(self): self.assertIn('mtu 1492', output) self.assertIn('inet 192.168.5.250/24 brd 192.168.5.255 scope global veth99', output) self.assertNotIn(f'{address1}', output) - self.assertRegex(output, r'inet 192.168.5.12[0-9]/24 metric 24 brd 192.168.5.255 scope global secondary dynamic noprefixroute test-label') + self.assertRegex(output, r'inet 192.168.5.12[0-9]/24 metric 24 brd 192.168.5.255 scope global secondary dynamic noprefixroute test-label') # fmt: skip self.assertNotIn('2600::', output) output = check_output('ip -4 --json address show dev veth99') @@ -7905,7 +9066,7 @@ def test_dhcp_client_ipv4_only(self): print('## tunnel') output = check_output('ip -d link show sit-dhcp4') print(output) - self.assertRegex(output, fr'sit (ip6ip )?remote any local {address2} dev veth99') + self.assertRegex(output, rf'sit (ip6ip )?remote any local {address2} dev veth99') print('## dnsmasq log') output = read_dnsmasq_log_file() @@ -7915,6 +9076,26 @@ def test_dhcp_client_ipv4_only(self): self.assertIn('client provides name: test-hostname', output) self.assertIn('26:mtu', output) + print('### networkctl dhcp-lease') + output = networkctl_dhcp_lease('veth99') + print(output) + # header + self.assertIn('Hardware Type: ETHER', output) + self.assertIn('Hardware Address: 12:34:56:78:9a:bc', output) + self.assertIn(f'Client Address: {address2}', output) + self.assertIn('Server Address: 192.168.5.1', output) + # options + self.assertRegex(output, r'1 subnet mask *255\.255\.255\.0') + self.assertRegex(output, r'3 router *192\.168\.5\.1') + self.assertRegex(output, r'6 domain name server *192\.168\.5\.1\n *192\.168\.5\.7\n *192\.168\.5\.8') + self.assertRegex(output, r'26 MTU size *1492') + self.assertRegex(output, r'28 broadcast address *192\.168\.5\.255') + self.assertRegex(output, r'51 lease time *2min') + self.assertRegex(output, r'53 message type *5') + self.assertRegex(output, r'54 server identifier *192\.168\.5\.1') + self.assertRegex(output, r'119 domain search *foo\.example\.com') + self.assertRegex(output, r'120 SIP server *192\.168\.5\.23\n *192\.168\.5\.24') + self.check_netlabel('veth99', r'192\.168\.5\.0/24') self.check_nftset('addr4', r'192\.168\.5\.1') @@ -7947,49 +9128,171 @@ 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') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv4-only.network', + ) start_networkd() self.wait_online('veth-peer:carrier') state = get_dhcp4_client_state('veth99') - print(f"State = {state}") + print(f'State = {state}') self.assertEqual(state, 'rebooting') - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', - '--dhcp-option=option:domain-search,example.com', - '--dhcp-alternate-port=67,5555', - ipv4_range='192.168.5.110,192.168.5.119') + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', + '--dhcp-option=option:domain-search,example.com', + '--dhcp-alternate-port=67,5555', + ipv4_range='192.168.5.110,192.168.5.119', + ) self.wait_online('veth99:routable', 'veth-peer:routable') self.wait_address('veth99', r'inet 192.168.5.11[0-9]*/24', ipv='-4') state = get_dhcp4_client_state('veth99') - print(f"State = {state}") + print(f'State = {state}') self.assertEqual(state, 'bound') def test_dhcp_client_allow_list(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-allow-list.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-allow-list.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') since = datetime.datetime.now() start_dnsmasq() - self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', since=since) + self.check_networkd_log( + 'veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', + since=since, + ) copy_network_unit('25-dhcp-client-allow-list.network.d/00-allow-list.conf') since = datetime.datetime.now() networkctl_reload() - self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', since=since) + self.check_networkd_log( + 'veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', + since=since, + ) copy_network_unit('25-dhcp-client-allow-list.network.d/10-deny-list.conf') since = datetime.datetime.now() networkctl_reload() - self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 found in deny-list, ignoring offer.', since=since) + self.check_networkd_log( + 'veth99: DHCPv4 server IP address 192.168.5.1 found in deny-list, ignoring offer.', + since=since, + ) - @unittest.skipUnless("--dhcp-rapid-commit" in run("dnsmasq --help").stdout, reason="dnsmasq is missing dhcp-rapid-commit support") + @unittest.skipUnless( + '--dhcp-rapid-commit' in run('dnsmasq --help').stdout, + reason='dnsmasq is missing dhcp-rapid-commit support', + ) def test_dhcp_client_rapid_commit(self): copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network') start_networkd() @@ -8000,7 +9303,7 @@ def test_dhcp_client_rapid_commit(self): self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24', ipv='-4') state = get_dhcp4_client_state('veth99') - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'bound') output = read_dnsmasq_log_file() @@ -8016,7 +9319,7 @@ def check_bootp_client(self, check_log): self.assertRegex(output, r'inet 192.168.5.[0-9]*/24') state = get_dhcp4_client_state('veth99') - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'bound') if check_log: @@ -8039,7 +9342,9 @@ def test_bootp_client(self): networkctl_reload() self.check_bootp_client(check_log=True) - with open(os.path.join(network_unit_dir, '25-bootp-client.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-bootp-client.network'), mode='a', encoding='utf-8' + ) as f: f.write('[DHCPv4]\nBOOTP=no\n') networkctl_reload() @@ -8054,7 +9359,9 @@ def test_bootp_client(self): self.assertIn('DHCPREQUEST(veth-peer)', output) self.assertIn('DHCPACK(veth-peer)', output) - with open(os.path.join(network_unit_dir, '25-bootp-client.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-bootp-client.network'), mode='a', encoding='utf-8' + ) as f: f.write('[DHCPv4]\nBOOTP=yes\n') since = datetime.datetime.now() @@ -8066,30 +9373,36 @@ def test_bootp_client(self): self.check_networkd_log('veth99: DHCPv4 client: RELEASE', since=since) def test_dhcp_client_ipv6_only_mode_without_ipv6_connectivity(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-server-ipv6-only-mode.network', - '25-dhcp-client-ipv6-only-mode.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-ipv6-only-mode.network', + '25-dhcp-client-ipv6-only-mode.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable', timeout='40s') self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24', ipv='-4') state = get_dhcp4_client_state('veth99') - print(f"State = {state}") + print(f'State = {state}') self.assertEqual(state, 'bound') def test_dhcp_client_ipv4_use_routes_gateway(self): first = True - for (routes, gateway, dns_and_ntp_routes, classless) in itertools.product([True, False], repeat=4): + for routes, gateway, dns_and_ntp_routes, classless in itertools.product([True, False], repeat=4): if first: first = False else: self.tearDown() + # fmt: off print(f'### test_dhcp_client_ipv4_use_routes_gateway(routes={routes}, gateway={gateway}, dns_and_ntp_routes={dns_and_ntp_routes}, classless={classless})') with self.subTest(routes=routes, gateway=gateway, dns_and_ntp_routes=dns_and_ntp_routes, classless=classless): self._test_dhcp_client_ipv4_use_routes_gateway(routes, gateway, dns_and_ntp_routes, classless) + # fmt: on - def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns_and_ntp_routes, classless): + def _test_dhcp_client_ipv4_use_routes_gateway( + self, use_routes, use_gateway, dns_and_ntp_routes, classless + ): testunit = '25-dhcp-client-ipv4-use-routes-use-gateway.network' testunits = ['25-veth.netdev', '25-dhcp-server-veth-peer.network', testunit] testunits.append(f'{testunit}.d/use-routes-{use_routes}.conf') @@ -8102,7 +9415,7 @@ def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns additional_options = [ '--dhcp-option=option:dns-server,192.168.5.10,8.8.8.8', '--dhcp-option=option:ntp-server,192.168.5.11,9.9.9.9', - '--dhcp-option=option:static-route,192.168.6.100,192.168.5.2,8.8.8.8,192.168.5.3' + '--dhcp-option=option:static-route,192.168.6.100,192.168.5.2,8.8.8.8,192.168.5.3', ] if classless: additional_options += [ @@ -8115,6 +9428,7 @@ def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns print(output) # Check UseRoutes= + # fmt: off if use_routes: if classless: self.assertRegex(output, r'default via 192.168.5.4 proto dhcp src 192.168.5.[0-9]* metric 1024') @@ -8136,20 +9450,22 @@ def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns self.assertNotRegex(output, r'8.0.0.0/8 via 192.168.5.3 proto dhcp src 192.168.5.[0-9]* metric 1024') self.assertNotRegex(output, r'192.168.5.2 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') self.assertNotRegex(output, r'192.168.5.3 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') + # fmt: on # Check UseGateway= if use_gateway and (not classless or not use_routes): self.assertRegex(output, r'default via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024') else: - self.assertNotRegex(output, r'default via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024') + self.assertNotRegex(output, r'default via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024') # fmt: skip # Check route to gateway if (use_gateway or dns_and_ntp_routes) and (not classless or not use_routes): self.assertRegex(output, r'192.168.5.1 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') else: - self.assertNotRegex(output, r'192.168.5.1 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') + self.assertNotRegex(output, r'192.168.5.1 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') # fmt: skip # Check RoutesToDNS= and RoutesToNTP= + # fmt: off if dns_and_ntp_routes: self.assertRegex(output, r'192.168.5.10 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') self.assertRegex(output, r'192.168.5.11 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') @@ -8168,11 +9484,16 @@ def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns self.assertNotRegex(output, r'192.168.5.11 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') self.assertNotRegex(output, r'8.8.8.8 via 192.168.5.[0-9]* proto dhcp src 192.168.5.[0-9]* metric 1024') self.assertNotRegex(output, r'9.9.9.9 via 192.168.5.[0-9]* proto dhcp src 192.168.5.[0-9]* metric 1024') + # fmt: on check_json(networkctl_json()) def test_dhcp_client_settings_anonymize(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-anonymize.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-anonymize.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() @@ -8186,9 +9507,11 @@ def test_dhcp_client_settings_anonymize(self): self.assertNotIn('26:mtu', output) def test_dhcp_keep_configuration_dynamic(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-server-veth-peer.network', - '25-dhcp-client-keep-configuration-dynamic.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-keep-configuration-dynamic.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() @@ -8196,8 +9519,7 @@ def test_dhcp_keep_configuration_dynamic(self): output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip # Stopping dnsmasq as networkd won't be allowed to renew the DHCP lease. stop_dnsmasq() @@ -8209,18 +9531,20 @@ def test_dhcp_keep_configuration_dynamic(self): # The lease address should be kept after the lease expired output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip stop_networkd() # The lease address should be kept after networkd stopped output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip - with open(os.path.join(network_unit_dir, '25-dhcp-client-keep-configuration-dynamic.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client-keep-configuration-dynamic.network'), + mode='a', + encoding='utf-8', + ) as f: f.write('[Network]\nDHCP=no\n') start_networkd() @@ -8229,13 +9553,14 @@ def test_dhcp_keep_configuration_dynamic(self): # Still the lease address should be kept after networkd restarted output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip def test_dhcp_keep_configuration_dynamic_on_stop(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-server-veth-peer.network', - '25-dhcp-client-keep-configuration-dynamic-on-stop.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-keep-configuration-dynamic-on-stop.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() @@ -8243,14 +9568,14 @@ def test_dhcp_keep_configuration_dynamic_on_stop(self): output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') # fmt: skip stop_dnsmasq() stop_networkd() output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') # fmt: skip start_networkd() self.wait_online('veth-peer:routable') @@ -8267,13 +9592,28 @@ def test_dhcp_client_reuse_address_as_static(self): self.wait_online('veth99:routable', 'veth-peer:routable') # link become 'routable' when at least one protocol provide an valid address. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) output = check_output('ip address show dev veth99 scope global') ipv4_address = re.search(r'192.168.5.[0-9]*/24', output).group() ipv6_address = re.search(r'2600::[0-9a-f:]*/128', output).group() - static_network = '\n'.join(['[Match]', 'Name=veth99', '[Network]', 'IPv6AcceptRA=no', 'Address=' + ipv4_address, 'Address=' + ipv6_address]) + static_network = f''' +[Match] +Name=veth99 +[Network] +IPv6AcceptRA=no +Address={ipv4_address} +Address={ipv6_address} +''' print(static_network) remove_network_unit('25-dhcp-client.network') @@ -8286,26 +9626,37 @@ def test_dhcp_client_reuse_address_as_static(self): output = check_output('ip -4 address show dev veth99 scope global') print(output) - self.assertRegex(output, f'inet {ipv4_address} brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, f'inet {ipv4_address} brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip output = check_output('ip -6 address show dev veth99 scope global') print(output) - self.assertRegex(output, f'inet6 {ipv6_address} scope global *\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, f'inet6 {ipv6_address} scope global *\n *valid_lft forever preferred_lft forever') # fmt: skip @expectedFailureIfModuleIsNotAvailable('vrf') def test_dhcp_client_vrf(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-vrf.network', - '25-vrf.netdev', '25-vrf.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-vrf.network', + '25-vrf.netdev', + '25-vrf.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() self.wait_online('veth99:routable', 'veth-peer:routable', 'vrf99:carrier') # link become 'routable' when at least one protocol provide an valid address. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) print('## ip -d link show dev vrf99') output = check_output('ip -d link show dev vrf99') @@ -8315,16 +9666,20 @@ def test_dhcp_client_vrf(self): print('## ip address show vrf vrf99') output = check_output('ip address show vrf vrf99') print(output) + # fmt: off self.assertRegex(output, 'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') self.assertRegex(output, 'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)') self.assertRegex(output, 'inet6 .* scope link') + # fmt: on print('## ip address show dev veth99') output = check_output('ip address show dev veth99') print(output) + # fmt: off self.assertRegex(output, 'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') self.assertRegex(output, 'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)') self.assertRegex(output, 'inet6 .* scope link') + # fmt: on print('## ip route show vrf vrf99') output = check_output('ip route show vrf vrf99') @@ -8339,8 +9694,11 @@ def test_dhcp_client_vrf(self): self.assertEqual(output, '') def test_dhcp_client_gateway_onlink_implicit(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', - '25-dhcp-client-gateway-onlink-implicit.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-gateway-onlink-implicit.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() @@ -8358,8 +9716,11 @@ def test_dhcp_client_gateway_onlink_implicit(self): self.assertRegex(output, 'onlink') def test_dhcp_client_with_ipv4ll(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', - '25-dhcp-client-with-ipv4ll.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-with-ipv4ll.network', + ) start_networkd() # we need to increase timeout above default, as this will need to wait for # systemd-networkd to get the dhcpv4 transient failure event @@ -8372,20 +9733,39 @@ def test_dhcp_client_with_ipv4ll(self): start_dnsmasq() print('Wait for a DHCP lease to be acquired and the IPv4LL address to be dropped') - self.wait_address('veth99', r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', ipv='-4') - self.wait_address_dropped('veth99', r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.254\.255\.255 scope link', scope='link', ipv='-4') + self.wait_address( + 'veth99', + r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', + ipv='-4', + ) + self.wait_address_dropped( + 'veth99', + r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.254\.255\.255 scope link', + scope='link', + ipv='-4', + ) self.wait_online('veth99:routable') output = check_output('ip -4 address show dev veth99') print(output) - self.assertRegex(output, r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic veth99') + self.assertRegex(output, r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic veth99') # fmt: skip self.assertNotIn('169.254.', output) self.assertNotIn('scope link', output) stop_dnsmasq() print('Wait for the DHCP lease to be expired and an IPv4LL address to be acquired') - self.wait_address_dropped('veth99', r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', ipv='-4', timeout_sec=130) - self.wait_address('veth99', r'inet 169\.254\.133\.11/16 metric 2048 brd 169\.254\.255\.255 scope link', scope='link', ipv='-4') + self.wait_address_dropped( + 'veth99', + r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', + ipv='-4', + timeout_sec=130, + ) + self.wait_address( + 'veth99', + r'inet 169\.254\.133\.11/16 metric 2048 brd 169\.254\.255\.255 scope link', + scope='link', + ipv='-4', + ) output = check_output('ip -4 address show dev veth99') print(output) @@ -8395,7 +9775,11 @@ def test_dhcp_client_with_ipv4ll(self): def test_dhcp_client_use_dns(self): def check(self, ipv4, ipv6, needs_reconfigure=False): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseDNS=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseDNS=') @@ -8407,9 +9791,18 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) # make resolved re-read the link state file resolvectl('revert', 'veth99') @@ -8427,12 +9820,19 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1', - '--dhcp-option=option6:dns-server,[2600::1]') + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.1', + '--dhcp-option=option6:dns-server,[2600::1]', + ) check(self, True, True) check(self, True, False) @@ -8442,7 +9842,11 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): def test_dhcp_client_default_use_domains(self): def check(self, common, ipv4, ipv6): mkdir_p(networkd_conf_dropin_dir) - with open(os.path.join(networkd_conf_dropin_dir, 'default_use_domains.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(networkd_conf_dropin_dir, 'default_use_domains.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[Network]\nUseDomains=') f.write('yes\n' if common else 'no\n') f.write('[DHCPv4]\nUseDomains=') @@ -8452,16 +9856,27 @@ def check(self, common, ipv4, ipv6): restart_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1', - '--dhcp-option=option6:dns-server,[2600::1]', - '--dhcp-option=option:domain-search,example.com', - '--dhcp-option=option6:domain-search,example.com') + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.1', + '--dhcp-option=option6:dns-server,[2600::1]', + '--dhcp-option=option:domain-search,example.com', + '--dhcp-option=option6:domain-search,example.com', + ) self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) for _ in range(20): output = resolvectl('domain', 'veth99') @@ -8480,7 +9895,12 @@ def check(self, common, ipv4, ipv6): stop_dnsmasq() remove_networkd_conf_dropin('default_use_domains.conf') - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) check(self, True, False, False) check(self, False, True, True) check(self, False, True, False) @@ -8490,7 +9910,11 @@ def check(self, common, ipv4, ipv6): def test_dhcp_client_use_dnr(self): def check(self, ipv4, ipv6, needs_reconfigure=False): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseDNS=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseDNS=') @@ -8502,9 +9926,18 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) # make resolved re-read the link state file resolvectl('revert', 'veth99') @@ -8523,16 +9956,33 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + def masq(bs): + return ':'.join(f'{b:02x}' for b in bs) + + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - dnr_v4 = dnr_v4_instance_data(adn = "dns.google", addrs = ["8.8.8.8", "8.8.4.4"]) - dnr_v4 += dnr_v4_instance_data(adn = "homer.simpson", addrs = ["0.7.4.2"], alpns = ("dot","h2","h3"), dohpath = "/springfield{?dns}") - dnr_v6 = dnr_v6_instance_data(adn = "dns.google", addrs = ["2001:4860:4860::8888", "2001:4860:4860::8844"]) - masq = lambda bs: ':'.join(f"{b:02x}" for b in bs) - start_dnsmasq(f'--dhcp-option=162,{masq(dnr_v4)}', - f'--dhcp-option=option6:144,{masq(dnr_v6)}') + dnr_v4 = dnr_v4_instance_data( + adn='dns.google', + addrs=['8.8.8.8', '8.8.4.4'], + ) + dnr_v4 += dnr_v4_instance_data( + adn='homer.simpson', + addrs=['0.7.4.2'], + alpns=('dot', 'h2', 'h3'), + dohpath='/springfield{?dns}', + ) + dnr_v6 = dnr_v6_instance_data( + adn='dns.google', + addrs=['2001:4860:4860::8888', '2001:4860:4860::8844'], + ) + start_dnsmasq(f'--dhcp-option=162,{masq(dnr_v4)}', f'--dhcp-option=option6:144,{masq(dnr_v6)}') check(self, True, True) check(self, True, False) @@ -8542,7 +9992,11 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): def test_dhcp_client_use_sip(self): def check(self, ipv4, ipv6, needs_reconfigure=False): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseSIP=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseSIP=') @@ -8553,9 +10007,18 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) output = networkctl_status('veth99') print(output) @@ -8572,13 +10035,20 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=option:sip-server,192.168.5.1', - '--dhcp-option=option6:sip-server,[2600::1]', - '--dhcp-option=option6:sip-server-domain,foo.example.com') + start_dnsmasq( + '--dhcp-option=option:sip-server,192.168.5.1', + '--dhcp-option=option6:sip-server,[2600::1]', + '--dhcp-option=option6:sip-server-domain,foo.example.com', + ) check(self, True, True) check(self, True, False) @@ -8588,7 +10058,11 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): def test_dhcp_client_use_captive_portal(self): def check(self, ipv4, ipv6, needs_reconfigure=False): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseCaptivePortal=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseCaptivePortal=') @@ -8600,9 +10074,18 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) output = networkctl_status('veth99') print(output) @@ -8613,12 +10096,19 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=114,http://systemd.io', - '--dhcp-option=option6:103,http://systemd.io') + start_dnsmasq( + '--dhcp-option=114,http://systemd.io', + '--dhcp-option=option6:103,http://systemd.io', + ) check(self, True, True) check(self, True, False) @@ -8628,7 +10118,11 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): def test_dhcp_client_reject_captive_portal(self): def check(self, ipv4, ipv6): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseCaptivePortal=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseCaptivePortal=') @@ -8638,9 +10132,18 @@ def check(self, ipv4, ipv6): networkctl_reload() self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) output = networkctl_status('veth99') print(output) @@ -8649,20 +10152,27 @@ def check(self, ipv4, ipv6): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=114,http://|invalid/url', - '--dhcp-option=option6:103,http://|invalid/url') + start_dnsmasq( + '--dhcp-option=114,http://|invalid/url', + '--dhcp-option=option6:103,http://|invalid/url', + ) check(self, True, True) check(self, True, False) check(self, False, True) check(self, False, False) -class NetworkdDHCPPDTests(unittest.TestCase, Utilities): +class NetworkdDHCPPDTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -8689,10 +10199,14 @@ def check_dhcp6_prefix(self, link): self.assertGreater(prefixInfo[0]['PreferredLifetimeUSec'], 0) self.assertGreater(prefixInfo[0]['ValidLifetimeUSec'], 0) - @unittest.skipUnless(shutil.which('dhcpd'), reason="dhcpd is not available") + @unittest.skipUnless(shutil.which('dhcpd'), reason='dhcpd is not available') def test_dhcp6pd_no_address(self): # For issue #29979. - copy_network_unit('25-veth.netdev', '25-dhcp6pd-server.network', '25-dhcp6pd-upstream-no-address.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp6pd-server.network', + '25-dhcp6pd-upstream-no-address.network', + ) start_networkd() self.wait_online('veth-peer:routable') @@ -8711,11 +10225,15 @@ def test_dhcp6pd_no_address(self): self.check_dhcp6_prefix('veth99') - @unittest.skipUnless(shutil.which('dhcpd'), reason="dhcpd is not available") + @unittest.skipUnless(shutil.which('dhcpd'), reason='dhcpd is not available') def test_dhcp6pd_no_assign(self): # Similar to test_dhcp6pd_no_assign(), but in this case UseAddress=yes (default), # However, the server does not provide IA_NA. For issue #31349. - copy_network_unit('25-veth.netdev', '25-dhcp6pd-server.network', '25-dhcp6pd-upstream-no-assign.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp6pd-server.network', + '25-dhcp6pd-upstream-no-assign.network', + ) start_networkd() self.wait_online('veth-peer:routable') @@ -8734,21 +10252,40 @@ def test_dhcp6pd_no_assign(self): self.check_dhcp6_prefix('veth99') - @unittest.skipUnless(shutil.which('dhcpd'), reason="dhcpd is not available") + @unittest.skipUnless(shutil.which('dhcpd'), reason='dhcpd is not available') def test_dhcp6pd(self): - copy_network_unit('25-veth.netdev', '25-dhcp6pd-server.network', '25-dhcp6pd-upstream.network', - '25-veth-downstream-veth97.netdev', '25-dhcp-pd-downstream-veth97.network', '25-dhcp-pd-downstream-veth97-peer.network', - '25-veth-downstream-veth98.netdev', '25-dhcp-pd-downstream-veth98.network', '25-dhcp-pd-downstream-veth98-peer.network', - '11-dummy.netdev', '25-dhcp-pd-downstream-test1.network', - '25-dhcp-pd-downstream-dummy97.network', - '12-dummy.netdev', '25-dhcp-pd-downstream-dummy98.network', - '13-dummy.netdev', '25-dhcp-pd-downstream-dummy99.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp6pd-server.network', + '25-dhcp6pd-upstream.network', + '25-veth-downstream-veth97.netdev', + '25-dhcp-pd-downstream-veth97.network', + '25-dhcp-pd-downstream-veth97-peer.network', + '25-veth-downstream-veth98.netdev', + '25-dhcp-pd-downstream-veth98.network', + '25-dhcp-pd-downstream-veth98-peer.network', + '11-dummy.netdev', + '25-dhcp-pd-downstream-test1.network', + '25-dhcp-pd-downstream-dummy97.network', + '12-dummy.netdev', + '25-dhcp-pd-downstream-dummy98.network', + '13-dummy.netdev', + '25-dhcp-pd-downstream-dummy99.network', + ) start_networkd() self.wait_online('veth-peer:routable') start_isc_dhcpd(conf_file='isc-dhcpd-dhcp6pd.conf', ipv='-6') - self.wait_online('veth99:routable', 'test1:routable', 'dummy98:routable', 'dummy99:degraded', - 'veth97:routable', 'veth97-peer:routable', 'veth98:routable', 'veth98-peer:routable') + self.wait_online( + 'veth99:routable', + 'test1:routable', + 'dummy98:routable', + 'dummy99:degraded', + 'veth97:routable', + 'veth97-peer:routable', + 'veth98:routable', + 'veth98-peer:routable', + ) self.setup_nftset('addr6', 'ipv6_addr') self.setup_nftset('network6', 'ipv6_addr', 'flags interval;') @@ -8774,31 +10311,45 @@ def test_dhcp6pd(self): print('### ip -6 address show dev veth99 scope global') output = check_output('ip -6 address show dev veth99 scope global') print(output) + # fmt: off # IA_NA self.assertRegex(output, 'inet6 3ffe:501:ffff:100::[0-9]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)') # address in IA_PD (Token=static) self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]10:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic') # address in IA_PD (Token=eui64) self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]10:1034:56ff:fe78:9abc/64 (metric 256 |)scope global dynamic') + # fmt: on # address in IA_PD (temporary) # Note that the temporary addresses may appear after the link enters configured state - self.wait_address('veth99', 'inet6 3ffe:501:ffff:[2-9a-f]10:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth99', + 'inet6 3ffe:501:ffff:[2-9a-f]10:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev test1 scope global') output = check_output('ip -6 address show dev test1 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('test1', 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'test1', + 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy98 scope global') output = check_output('ip -6 address show dev dummy98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy98', 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy98', + 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy99 scope global') output = check_output('ip -6 address show dev dummy99 scope global') @@ -8810,41 +10361,57 @@ def test_dhcp6pd(self): output = check_output('ip -6 address show dev veth97 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1034:56ff:fe78:9ace/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1034:56ff:fe78:9ace/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('veth97', 'inet6 3ffe:501:ffff:[2-9a-f]08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth97', + 'inet6 3ffe:501:ffff:[2-9a-f]08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth97-peer scope global') output = check_output('ip -6 address show dev veth97-peer scope global') print(output) # NDisc address (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (Token=eui64) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1034:56ff:fe78:9acf/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1034:56ff:fe78:9acf/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (temporary) - self.wait_address('veth97-peer', 'inet6 3ffe:501:ffff:[2-9a-f]08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth97-peer', + 'inet6 3ffe:501:ffff:[2-9a-f]08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth98 scope global') output = check_output('ip -6 address show dev veth98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abe/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abe/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('veth98', 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth98', + 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth98-peer scope global') output = check_output('ip -6 address show dev veth98-peer scope global') print(output) # NDisc address (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (Token=eui64) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abf/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abf/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (temporary) - self.wait_address('veth98-peer', 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth98-peer', + 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 route show type unreachable') output = check_output('ip -6 route show type unreachable') @@ -8899,9 +10466,13 @@ def test_dhcp6pd(self): output = check_output('ip -6 address show dev dummy97 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]01:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]01:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy97', 'inet6 3ffe:501:ffff:[2-9a-f]01:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy97', + 'inet6 3ffe:501:ffff:[2-9a-f]01:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 route show dev dummy97') output = check_output('ip -6 route show dev dummy97') @@ -8916,9 +10487,13 @@ def test_dhcp6pd(self): output = check_output('ip -6 address show dev dummy98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy98', 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy98', + 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy99 scope global') output = check_output('ip -6 address show dev dummy99 scope global') @@ -8964,34 +10539,46 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde print('### ip -4 address show dev veth99 scope global') output = check_output('ip -4 address show dev veth99 scope global') print(output) - self.assertRegex(output, fr'inet {address_prefix}[0-9]*/8 (metric 1024 |)brd 10.255.255.255 scope global dynamic veth99') + self.assertRegex(output, rf'inet {address_prefix}[0-9]*/8 (metric 1024 |)brd 10.255.255.255 scope global dynamic veth99') # fmt: skip print('### ip -6 address show dev veth99 scope global') output = check_output('ip -6 address show dev veth99 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+10:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+10:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+10:1034:56ff:fe78:9abc/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+10:1034:56ff:fe78:9abc/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) # Note that the temporary addresses may appear after the link enters configured state - self.wait_address('veth99', 'inet6 2001:db8:6464:[0-9a-f]+10:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth99', + 'inet6 2001:db8:6464:[0-9a-f]+10:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev test1 scope global') output = check_output('ip -6 address show dev test1 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('test1', 'inet6 2001:db8:6464:[0-9a-f]+00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'test1', + 'inet6 2001:db8:6464:[0-9a-f]+00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy98 scope global') output = check_output('ip -6 address show dev dummy98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy98', 'inet6 2001:db8:6464:[0-9a-f]+00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy98', + 'inet6 2001:db8:6464:[0-9a-f]+00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy99 scope global') output = check_output('ip -6 address show dev dummy99 scope global') @@ -9003,41 +10590,57 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde output = check_output('ip -6 address show dev veth97 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1034:56ff:fe78:9ace/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1034:56ff:fe78:9ace/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('veth97', 'inet6 2001:db8:6464:[0-9a-f]+08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth97', + 'inet6 2001:db8:6464:[0-9a-f]+08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth97-peer scope global') output = check_output('ip -6 address show dev veth97-peer scope global') print(output) # NDisc address (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1034:56ff:fe78:9acf/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1034:56ff:fe78:9acf/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (temporary) - self.wait_address('veth97-peer', 'inet6 2001:db8:6464:[0-9a-f]+08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth97-peer', + 'inet6 2001:db8:6464:[0-9a-f]+08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth98 scope global') output = check_output('ip -6 address show dev veth98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1034:56ff:fe78:9abe/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1034:56ff:fe78:9abe/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('veth98', 'inet6 2001:db8:6464:[0-9a-f]+09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth98', + 'inet6 2001:db8:6464:[0-9a-f]+09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth98-peer scope global') output = check_output('ip -6 address show dev veth98-peer scope global') print(output) # NDisc address (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1034:56ff:fe78:9abf/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1034:56ff:fe78:9abf/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (temporary) - self.wait_address('veth98-peer', 'inet6 2001:db8:6464:[0-9a-f]+09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth98-peer', + 'inet6 2001:db8:6464:[0-9a-f]+09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 route show type unreachable') output = check_output('ip -6 route show type unreachable') @@ -9088,9 +10691,13 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde output = check_output('ip -6 address show dev dummy97 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+01:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+01:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy97', 'inet6 2001:db8:6464:[0-9a-f]+01:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy97', + 'inet6 2001:db8:6464:[0-9a-f]+01:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 route show dev dummy97') output = check_output('ip -6 route show dev dummy97') @@ -9109,8 +10716,8 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde print(f'### ip -6 address show dev {tunnel_name}') output = check_output(f'ip -6 address show dev {tunnel_name}') print(output) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+0[23]:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global dynamic') - self.assertRegex(output, fr'inet6 ::{address_prefix_re}/96 scope global') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+0[23]:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global dynamic') # fmt: skip + self.assertRegex(output, rf'inet6 ::{address_prefix_re}/96 scope global') print(f'### ip -6 route show dev {tunnel_name}') output = check_output(f'ip -6 route show dev {tunnel_name}') @@ -9122,7 +10729,7 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde output = check_output('ip -6 route show default') print(output) self.assertIn('default', output) - self.assertRegex(output, fr'via ::{border_router} dev {tunnel_name}') + self.assertRegex(output, rf'via ::{border_router} dev {tunnel_name}') def test_dhcp4_6rd(self): def get_dhcp_6rd_prefix(link): @@ -9139,35 +10746,56 @@ def get_dhcp_6rd_prefix(link): return prefixInfo - copy_network_unit('25-veth.netdev', '25-dhcp4-6rd-server.network', '25-dhcp4-6rd-upstream.network', - '25-veth-downstream-veth97.netdev', '25-dhcp-pd-downstream-veth97.network', '25-dhcp-pd-downstream-veth97-peer.network', - '25-veth-downstream-veth98.netdev', '25-dhcp-pd-downstream-veth98.network', '25-dhcp-pd-downstream-veth98-peer.network', - '11-dummy.netdev', '25-dhcp-pd-downstream-test1.network', - '25-dhcp-pd-downstream-dummy97.network', - '12-dummy.netdev', '25-dhcp-pd-downstream-dummy98.network', - '13-dummy.netdev', '25-dhcp-pd-downstream-dummy99.network', - '80-6rd-tunnel.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp4-6rd-server.network', + '25-dhcp4-6rd-upstream.network', + '25-veth-downstream-veth97.netdev', + '25-dhcp-pd-downstream-veth97.network', + '25-dhcp-pd-downstream-veth97-peer.network', + '25-veth-downstream-veth98.netdev', + '25-dhcp-pd-downstream-veth98.network', + '25-dhcp-pd-downstream-veth98-peer.network', + '11-dummy.netdev', + '25-dhcp-pd-downstream-test1.network', + '25-dhcp-pd-downstream-dummy97.network', + '12-dummy.netdev', + '25-dhcp-pd-downstream-dummy98.network', + '13-dummy.netdev', + '25-dhcp-pd-downstream-dummy99.network', + '80-6rd-tunnel.network', + ) start_networkd() self.wait_online('veth-peer:routable') # ipv4masklen: 8 # 6rd-prefix: 2001:db8::/32 - # br-addresss: 10.0.0.1 + # br-address: 10.0.0.1 - start_dnsmasq('--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:01', - ipv4_range='10.100.100.100,10.100.100.200', - ipv4_router='10.0.0.1') - self.wait_online('veth99:routable', 'test1:routable', 'dummy98:routable', 'dummy99:degraded', - 'veth97:routable', 'veth97-peer:routable', 'veth98:routable', 'veth98-peer:routable') + start_dnsmasq( + '--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:01', + ipv4_range='10.100.100.100,10.100.100.200', + ipv4_router='10.0.0.1', + ) + self.wait_online( + 'veth99:routable', + 'test1:routable', + 'dummy98:routable', + 'dummy99:degraded', + 'veth97:routable', + 'veth97-peer:routable', + 'veth98:routable', + 'veth98-peer:routable', + ) # Check the DBus interface for assigned prefix information prefixInfo = get_dhcp_6rd_prefix('veth99') - self.assertEqual(prefixInfo['Prefix'], [32,1,13,184,0,0,0,0,0,0,0,0,0,0,0,0]) # 2001:db8:: + self.assertEqual(prefixInfo['Prefix'], [32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) # 2001:db8:: # fmt: skip self.assertEqual(prefixInfo['PrefixLength'], 32) self.assertEqual(prefixInfo['IPv4MaskLength'], 8) - self.assertEqual(prefixInfo['BorderRouters'], [[10,0,0,1]]) + self.assertEqual(prefixInfo['BorderRouters'], [[10, 0, 0, 1]]) # Test case for a downstream which appears later check_output('ip link add dummy97 type dummy') @@ -9182,30 +10810,56 @@ def get_dhcp_6rd_prefix(link): self.wait_online(f'{tunnel_name}:routable') - self.verify_dhcp4_6rd(tunnel_name, '10.100.100.1', '(10.100.100.1[0-9][0-9]|a64:64[6-9a-c][0-9a-f])', '(10.0.0.1|a00:1)') + self.verify_dhcp4_6rd( + tunnel_name, + '10.100.100.1', + '(10.100.100.1[0-9][0-9]|a64:64[6-9a-c][0-9a-f])', + '(10.0.0.1|a00:1)', + ) # Test case for reconfigure networkctl_reconfigure('dummy98', 'dummy99') self.wait_online('dummy98:routable', 'dummy99:degraded') - self.verify_dhcp4_6rd(tunnel_name, '10.100.100.1', '(10.100.100.1[0-9][0-9]|a64:64[6-9a-c][0-9a-f])', '(10.0.0.1|a00:1)') + self.verify_dhcp4_6rd( + tunnel_name, + '10.100.100.1', + '(10.100.100.1[0-9][0-9]|a64:64[6-9a-c][0-9a-f])', + '(10.0.0.1|a00:1)', + ) # Change the address range and (border) router, then if check the same tunnel is reused. stop_dnsmasq() - start_dnsmasq('--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:02', - ipv4_range='10.100.100.200,10.100.100.250', - ipv4_router='10.0.0.2') + start_dnsmasq( + '--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:02', + ipv4_range='10.100.100.200,10.100.100.250', + ipv4_router='10.0.0.2', + ) print('Wait for the DHCP lease to be renewed/rebind') time.sleep(120) - self.wait_online('veth99:routable', 'test1:routable', 'dummy97:routable', 'dummy98:routable', 'dummy99:degraded', - 'veth97:routable', 'veth97-peer:routable', 'veth98:routable', 'veth98-peer:routable') + self.wait_online( + 'veth99:routable', + 'test1:routable', + 'dummy97:routable', + 'dummy98:routable', + 'dummy99:degraded', + 'veth97:routable', + 'veth97-peer:routable', + 'veth98:routable', + 'veth98-peer:routable', + ) + + self.verify_dhcp4_6rd( + tunnel_name, + '10.100.100.2', + '(10.100.100.2[0-5][0-9]|a64:64[c-f][0-9a-f])', + '(10.0.0.2|a00:2)', + ) - self.verify_dhcp4_6rd(tunnel_name, '10.100.100.2', '(10.100.100.2[0-5][0-9]|a64:64[c-f][0-9a-f])', '(10.0.0.2|a00:2)') class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities): - def setUp(self): setup_common() @@ -9213,8 +10867,13 @@ def tearDown(self): tear_down_common() def test_ipv6_route_prefix(self): - copy_network_unit('25-veth.netdev', '25-ipv6ra-prefix-client.network', '25-ipv6ra-prefix.network', - '12-dummy.netdev', '25-ipv6ra-uplink.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6ra-prefix-client.network', + '25-ipv6ra-prefix.network', + '12-dummy.netdev', + '25-ipv6ra-uplink.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable', 'dummy98:routable') @@ -9239,7 +10898,7 @@ def test_ipv6_route_prefix(self): print('### ip -6 nexthop show dev veth-peer') output = check_output('ip -6 nexthop show dev veth-peer') print(output) - self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abc dev veth-peer scope link proto ra') + self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abc dev veth-peer scope link proto ra') # fmt: skip print('### ip -6 address show dev veth99') output = check_output('ip -6 address show dev veth99') @@ -9272,8 +10931,13 @@ def test_ipv6_route_prefix(self): self.assertEqual(prefix_length, 96) def test_ipv6_route_prefix_deny_list(self): - copy_network_unit('25-veth.netdev', '25-ipv6ra-prefix-client-deny-list.network', '25-ipv6ra-prefix.network', - '12-dummy.netdev', '25-ipv6ra-uplink.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6ra-prefix-client-deny-list.network', + '25-ipv6ra-prefix.network', + '12-dummy.netdev', + '25-ipv6ra-uplink.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable', 'dummy98:routable') @@ -9295,7 +10959,7 @@ def test_ipv6_route_prefix_deny_list(self): print('### ip -6 nexthop show dev veth-peer') output = check_output('ip -6 nexthop show dev veth-peer') print(output) - self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abc dev veth-peer scope link proto ra') + self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abc dev veth-peer scope link proto ra') # fmt: skip print('### ip -6 address show dev veth99') output = check_output('ip -6 address show dev veth99') @@ -9311,8 +10975,8 @@ def test_ipv6_route_prefix_deny_list(self): print(output) self.assertIn('example.com', output) -class NetworkdMTUTests(unittest.TestCase, Utilities): +class NetworkdMTUTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -9339,7 +11003,7 @@ def check_mtu(self, mtu, ipv6_mtu=None, reset=True): self.reset_check_mtu(mtu, ipv6_mtu) def reset_check_mtu(self, mtu, ipv6_mtu=None): - ''' test setting mtu/ipv6_mtu with interface already up ''' + """test setting mtu/ipv6_mtu with interface already up""" stop_networkd() # note - changing the device mtu resets the ipv6 mtu @@ -9365,39 +11029,50 @@ def test_mtu_link(self): self.check_mtu('1600', reset=False) def test_ipv6_mtu(self): - ''' set ipv6 mtu without setting device mtu ''' + """set ipv6 mtu without setting device mtu""" copy_network_unit('12-dummy.netdev', '12-dummy.network.d/ipv6-mtu-1400.conf') self.check_mtu('1500', '1400') def test_ipv6_mtu_toolarge(self): - ''' try set ipv6 mtu over device mtu (it shouldn't work) ''' + """try set ipv6 mtu over device mtu (it shouldn't work)""" copy_network_unit('12-dummy.netdev', '12-dummy.network.d/ipv6-mtu-1550.conf') self.check_mtu('1500', '1500') def test_mtu_network_ipv6_mtu(self): - ''' set ipv6 mtu and set device mtu via network file ''' - copy_network_unit('12-dummy.netdev', '12-dummy.network.d/mtu.conf', '12-dummy.network.d/ipv6-mtu-1550.conf') + """set ipv6 mtu and set device mtu via network file""" + copy_network_unit( + '12-dummy.netdev', + '12-dummy.network.d/mtu.conf', + '12-dummy.network.d/ipv6-mtu-1550.conf', + ) self.check_mtu('1600', '1550') def test_mtu_netdev_ipv6_mtu(self): - ''' set ipv6 mtu and set device mtu via netdev file ''' + """set ipv6 mtu and set device mtu via netdev file""" copy_network_unit('12-dummy-mtu.netdev', '12-dummy.network.d/ipv6-mtu-1550.conf') self.check_mtu('1600', '1550', reset=False) def test_mtu_link_ipv6_mtu(self): - ''' set ipv6 mtu and set device mtu via link file ''' + """set ipv6 mtu and set device mtu via link file""" copy_network_unit('12-dummy.netdev', '12-dummy-mtu.link', '12-dummy.network.d/ipv6-mtu-1550.conf') self.check_mtu('1600', '1550', reset=False) -class NetworkdSysctlTest(unittest.TestCase, Utilities): +class NetworkdSysctlTest(unittest.TestCase, Utilities): def setUp(self): setup_common() def tearDown(self): tear_down_common() - @unittest.skipUnless(compare_kernel_version("6.12"), reason="On kernels <= 6.12, bpf_current_task_under_cgroup() isn't available for program types BPF_PROG_TYPE_CGROUP_SYSCTL") + @unittest.skipUnless( + compare_kernel_version('6.12'), + reason='On kernels <= 6.12, bpf_current_task_under_cgroup() is not available for program types BPF_PROG_TYPE_CGROUP_SYSCTL', + ) + @unittest.skipUnless( + networkd_has_sysctl_monitor_bpf(), + reason='systemd-networkd was built without ENABLE_SYSCTL_BPF support (HAVE_VMLINUX_H=0)', + ) def test_sysctl_monitor(self): copy_network_unit('12-dummy.network', '12-dummy.netdev', '12-dummy.link') start_networkd() @@ -9414,26 +11089,127 @@ def test_sysctl_monitor(self): call('sysctl -w net.ipv6.conf.dummy98.hop_limit=4') call('sysctl -w net.ipv6.conf.dummy98.max_addresses=10') - log=read_networkd_log() + log = read_networkd_log() + # fmt: off self.assertRegex(log, r"Foreign process 'sysctl\[\d+\]' changed sysctl '/proc/sys/net/ipv6/conf/dummy98/accept_ra' from '0' to '1', conflicting with our setting to '0'") self.assertRegex(log, r"Foreign process 'sysctl\[\d+\]' changed sysctl '/proc/sys/net/ipv6/conf/dummy98/mtu' from '1550' to '1360', conflicting with our setting to '1550'") self.assertRegex(log, r"Foreign process 'sysctl\[\d+\]' changed sysctl '/proc/sys/net/ipv4/conf/dummy98/promote_secondaries' from '1' to '0', conflicting with our setting to '1'") self.assertRegex(log, r"Foreign process 'sysctl\[\d+\]' changed sysctl '/proc/sys/net/ipv6/conf/dummy98/proxy_ndp' from '0' to '1', conflicting with our setting to '0'") + # fmt: on self.assertNotIn("changed sysctl '/proc/sys/net/ipv6/conf/dummy98/hop_limit'", log) self.assertNotIn("changed sysctl '/proc/sys/net/ipv6/conf/dummy98/max_addresses'", log) - self.assertNotIn("Sysctl monitor BPF returned error", log) + self.assertNotIn('Sysctl monitor BPF returned error', log) + + +class NetworkdWWANTests(unittest.TestCase, Utilities): + def setUp(self): + setup_common() + + def tearDown(self): + stop_modem_manager_mock() + 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', + ) # fmt: skip + 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') - parser.add_argument('--source-dir', help='Path to source dir/git tree', dest='source_dir') - parser.add_argument('--valgrind', help='Enable valgrind', dest='use_valgrind', type=bool, nargs='?', const=True, default=use_valgrind) - parser.add_argument('--debug', help='Generate debugging logs', dest='enable_debug', type=bool, nargs='?', const=True, default=enable_debug) - parser.add_argument('--asan-options', help='ASAN options', dest='asan_options') - parser.add_argument('--lsan-options', help='LSAN options', dest='lsan_options') - parser.add_argument('--ubsan-options', help='UBSAN options', dest='ubsan_options') - parser.add_argument('--with-coverage', help='Loosen certain sandbox restrictions to make gcov happy', dest='with_coverage', type=bool, nargs='?', const=True, default=with_coverage) - parser.add_argument('--no-journal', help='Do not show journal of systemd-networkd on stop', dest='show_journal', action='store_false') + parser.add_argument( + '--build-dir', + help='Path to build dir', + dest='build_dir', + ) + parser.add_argument( + '--source-dir', + help='Path to source dir/git tree', + dest='source_dir', + ) + parser.add_argument( + '--valgrind', + help='Enable valgrind', + dest='use_valgrind', + type=bool, + nargs='?', + const=True, + default=use_valgrind, + ) + parser.add_argument( + '--debug', + help='Generate debugging logs', + dest='enable_debug', + type=bool, + nargs='?', + const=True, + default=enable_debug, + ) + parser.add_argument( + '--asan-options', + help='ASAN options', + dest='asan_options', + ) + parser.add_argument( + '--lsan-options', + help='LSAN options', + dest='lsan_options', + ) + parser.add_argument( + '--ubsan-options', + help='UBSAN options', + dest='ubsan_options', + ) + parser.add_argument( + '--with-coverage', + help='Loosen certain sandbox restrictions to make gcov happy', + dest='with_coverage', + type=bool, + nargs='?', + const=True, + default=with_coverage, + ) + parser.add_argument( + '--no-journal', + help='Do not show journal of systemd-networkd on stop', + dest='show_journal', + action='store_false', + ) ns, unknown_args = parser.parse_known_args(namespace=unittest) if ns.build_dir: @@ -9447,16 +11223,18 @@ def test_sysctl_monitor(self): udevadm_bin = os.path.join(ns.build_dir, 'udevadm') build_dir = ns.build_dir + # fmt: off if ns.source_dir: source_dir = ns.source_dir - assert os.path.exists(os.path.join(source_dir, "meson_options.txt")), f"{source_dir} doesn't appear to be a systemd source tree." - elif os.path.exists(os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../meson_options.txt"))): - source_dir = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) + assert os.path.exists(os.path.join(source_dir, 'meson_options.txt')), f"{source_dir} doesn't appear to be a systemd source tree." + elif os.path.exists(os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../meson_options.txt'))): + source_dir = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../')) else: source_dir = None + # fmt: on if networkd_bin is None or resolved_bin is None or timesyncd_bin is None: - print("networkd tests require networkd/resolved/timesyncd to be enabled") + print('networkd tests require networkd/resolved/timesyncd to be enabled') sys.exit(77) use_valgrind = ns.use_valgrind @@ -9464,7 +11242,7 @@ def test_sysctl_monitor(self): asan_options = ns.asan_options lsan_options = ns.lsan_options ubsan_options = ns.ubsan_options - with_coverage = ns.with_coverage or "COVERAGE_BUILD_DIR" in os.environ + with_coverage = ns.with_coverage or 'COVERAGE_BUILD_DIR' in os.environ show_journal = ns.show_journal if use_valgrind: @@ -9480,7 +11258,12 @@ def test_sysctl_monitor(self): if build_dir: test_ndisc_send = os.path.normpath(os.path.join(build_dir, 'test-ndisc-send')) else: - test_ndisc_send = '/usr/lib/tests/test-ndisc-send' + test_ndisc_send = '/usr/lib/systemd/tests/unit-tests/manual/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}) @@ -9500,6 +11283,6 @@ def test_sysctl_monitor(self): argv=[ sys.argv[0], *unknown_args, - *(["-k", match] if (match := os.getenv("TEST_MATCH_TESTCASE")) else []) + *(['-k', match] if (match := os.getenv('TEST_MATCH_TESTCASE')) else []), ], ) diff --git a/test/test-shutdown.py b/test/test-shutdown.py index d19a03742c246..9c63f131df3f6 100755 --- a/test/test-shutdown.py +++ b/test/test-shutdown.py @@ -13,27 +13,34 @@ def run(args): ret = 1 - logger = logging.getLogger("test-shutdown") + logger = logging.getLogger('test-shutdown') logfile = None if args.logfile: - logger.debug("Logging pexpect IOs to %s", args.logfile) + logger.debug('Logging pexpect IOs to %s', args.logfile) logfile = open(args.logfile, 'w') elif args.verbose: logfile = sys.stdout - logger.info("spawning test") - console = pexpect.spawn(args.command, args.arg, logfile=logfile, env={ - "TERM": "dumb", - }, encoding='utf-8', timeout=60) + logger.info('spawning test') + console = pexpect.spawn( + args.command, + args.arg, + logfile=logfile, + env={ + 'TERM': 'dumb', + }, + encoding='utf-8', + timeout=60, + ) - logger.debug("child pid %d", console.pid) + logger.debug('child pid %d', console.pid) try: - logger.info("waiting for login prompt") + logger.info('waiting for login prompt') console.expect('H login: ', 10) - logger.info("log in and start screen") + logger.info('log in and start screen') console.sendline('root') console.expect('bash.*# ', 10) console.sendline('screen') @@ -46,59 +53,59 @@ def run(args): console.sendline('systemctl is-system-running --wait') console.expect(r'\b(running|degraded)\b', 60) -# console.interact() + # console.interact() console.sendline('tty') console.expect(r'/dev/(pts/\d+)') pty = console.match.group(1) - logger.info("window 1 at tty %s", pty) + logger.info('window 1 at tty %s', pty) - logger.info("schedule reboot") + logger.info('schedule reboot') console.sendline('shutdown -r') console.expect("Reboot scheduled for (?P.*), use 'shutdown -c' to cancel", 2) date = console.match.group('date') - logger.info("reboot scheduled for %s", date) + logger.info('reboot scheduled for %s', date) console.sendcontrol('a') console.send('0') - logger.info("verify broadcast message") + logger.info('verify broadcast message') console.expect(f'Broadcast message from root@H on {pty}', 2) console.expect(f'The system will reboot at {date}', 2) - logger.info("check show output") + logger.info('check show output') console.sendline('shutdown --show') console.expect(f"Reboot scheduled for {date}, use 'shutdown -c' to cancel", 2) - logger.info("cancel shutdown") + logger.info('cancel shutdown') console.sendline('shutdown -c') console.sendcontrol('a') console.send('1') console.expect('System shutdown has been cancelled', 2) - logger.info("call for reboot") + logger.info('call for reboot') console.sendline('sleep 10; shutdown -r now') console.sendcontrol('a') console.send('0') - console.expect("The system will reboot now!", 12) + console.expect('The system will reboot now!', 12) - logger.info("waiting for reboot") + logger.info('waiting for reboot') console.expect('H login: ', 60) console.sendline('root') console.expect('bash.*# ', 10) - console.sendline('> /testok') + console.sendline('>/testok') - logger.info("power off") + logger.info('power off') console.sendline('poweroff') - logger.info("expect termination now") + logger.info('expect termination now') console.expect(pexpect.EOF) ret = 0 except Exception as e: logger.error(e) - logger.info("killing child pid %d", console.pid) + logger.info('killing child pid %d', console.pid) # Ask systemd-nspawn to stop and release the container's resources properly. console.kill(signal.SIGTERM) @@ -116,12 +123,31 @@ def run(args): return ret + def main(): - parser = argparse.ArgumentParser(description='test logind shutdown feature') - parser.add_argument("-v", "--verbose", action="store_true", help="verbose") - parser.add_argument("--logfile", metavar='FILE', help="Save all test input/output to the given path") - parser.add_argument("command", help="command to run") - parser.add_argument("arg", nargs='*', help="args for command") + parser = argparse.ArgumentParser( + description='test logind shutdown feature', + ) + parser.add_argument( + '-v', + '--verbose', + action='store_true', + help='verbose', + ) + parser.add_argument( + '--logfile', + metavar='FILE', + help='Save all test input/output to the given path', + ) + parser.add_argument( + 'command', + help='command to run', + ) + parser.add_argument( + 'arg', + nargs='*', + help='args for command', + ) args = parser.parse_args() @@ -134,6 +160,7 @@ def main(): return run(args) + if __name__ == '__main__': sys.exit(main()) diff --git a/test/test-systemd-tmpfiles.py b/test/test-systemd-tmpfiles.py index 37a5e9ef3e4b2..10e6f69b56ac4 100755 --- a/test/test-systemd-tmpfiles.py +++ b/test/test-systemd-tmpfiles.py @@ -6,13 +6,13 @@ # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. +import grp import os -import sys +import pwd import socket import subprocess +import sys import tempfile -import pwd -import grp from pathlib import Path try: @@ -20,40 +20,41 @@ except ImportError: id128 = None -EX_DATAERR = 65 # from sysexits.h +EX_DATAERR = 65 # from sysexits.h EXIT_TEST_SKIP = 77 -try: - subprocess.run -except AttributeError: - sys.exit(EXIT_TEST_SKIP) - exe_with_args = sys.argv[1:] temp_dir = tempfile.TemporaryDirectory(prefix='test-systemd-tmpfiles.') # If /tmp isn't owned by either 'root' or the current user # systemd-tmpfiles will exit with "Detected unsafe path transition" # breaking this test -tmpowner = os.stat("/tmp").st_uid +tmpowner = os.stat('/tmp').st_uid if tmpowner != 0 and tmpowner != os.getuid(): print("Skip: /tmp is not owned by 'root' or current user") sys.exit(EXIT_TEST_SKIP) + def test_line(line, *, user, returncode=EX_DATAERR, extra={}): args = ['--user'] if user else [] print('Running {} on {!r}'.format(' '.join(exe_with_args + args), line)) - c = subprocess.run(exe_with_args + ['--create', '-'] + args, - input=line, stdout=subprocess.PIPE, universal_newlines=True, - **extra) + c = subprocess.run( + exe_with_args + ['--create', '-'] + args, + input=line, + stdout=subprocess.PIPE, + text=True, + **extra, + ) assert c.returncode == returncode, c + def test_invalids(*, user): test_line('asdfa', user=user) test_line('f "open quote', user=user) test_line('f closed quote""', user=user) test_line('Y /unknown/letter', user=user) test_line('w non/absolute/path', user=user) - test_line('s', user=user) # s is for short + test_line('s', user=user) # s is for short test_line('f!! /too/many/bangs', user=user) test_line('f++ /too/many/plusses', user=user) test_line('f+!+ /too/many/plusses', user=user) @@ -77,12 +78,18 @@ def test_invalids(*, user): test_line('h - - -', user=user) test_line('H - - -', user=user) + def test_uninitialized_t(): if os.getuid() == 0: return - test_line('w /foo - - - - "specifier for --user %t"', - user=True, returncode=0, extra={'env':{'HOME': os.getenv('HOME')}}) + test_line( + 'w /foo - - - - "specifier for --user %t"', + user=True, + returncode=0, + extra={'env': {'HOME': os.getenv('HOME')}}, + ) + def test_content(line, expected, *, user, extra={}, subpath='/arg', path_cb=None): d = tempfile.TemporaryDirectory(prefix='test-content.', dir=temp_dir.name) @@ -92,24 +99,25 @@ def test_content(line, expected, *, user, extra={}, subpath='/arg', path_cb=None spec = line.format(arg) test_line(spec, user=user, returncode=0, extra=extra) content = open(arg).read() - print('expect: {!r}\nactual: {!r}'.format(expected, content)) + print(f'expect: {expected!r}\nactual: {content!r}') assert content == expected + def test_valid_specifiers(*, user): test_content('f {} - - - - two words', 'two words', user=user) if id128 and os.path.isfile('/etc/machine-id'): try: - test_content('f {} - - - - %m', '{}'.format(id128.get_machine().hex), user=user) + test_content('f {} - - - - %m', f'{id128.get_machine().hex}', user=user) except AssertionError as e: print(e) print('/etc/machine-id: {!r}'.format(open('/etc/machine-id').read())) print('/proc/cmdline: {!r}'.format(open('/proc/cmdline').read())) print('skipping') - test_content('f {} - - - - %b', '{}'.format(id128.get_boot().hex), user=user) - test_content('f {} - - - - %H', '{}'.format(socket.gethostname()), user=user) - test_content('f {} - - - - %v', '{}'.format(os.uname().release), user=user) - test_content('f {} - - - - %U', '{}'.format(os.getuid() if user else 0), user=user) - test_content('f {} - - - - %G', '{}'.format(os.getgid() if user else 0), user=user) + test_content('f {} - - - - %b', f'{id128.get_boot().hex}', user=user) + test_content('f {} - - - - %H', f'{socket.gethostname()}', user=user) + test_content('f {} - - - - %v', f'{os.uname().release}', user=user) + test_content('f {} - - - - %U', f'{os.getuid() if user else 0}', user=user) + test_content('f {} - - - - %G', f'{os.getgid() if user else 0}', user=user) try: puser = pwd.getpwuid(os.getuid() if user else 0) @@ -117,7 +125,7 @@ def test_valid_specifiers(*, user): puser = None if puser: - test_content('f {} - - - - %u', '{}'.format(puser.pw_name), user=user) + test_content('f {} - - - - %u', f'{puser.pw_name}', user=user) try: pgroup = grp.getgrgid(os.getgid() if user else 0) @@ -125,52 +133,62 @@ def test_valid_specifiers(*, user): pgroup = None if pgroup: - test_content('f {} - - - - %g', '{}'.format(pgroup.gr_name), user=user) + test_content('f {} - - - - %g', f'{pgroup.gr_name}', user=user) # Note that %h is the only specifier in which we look the environment, # because we check $HOME. Should we even be doing that? - home = os.path.expanduser("~") - test_content('f {} - - - - %h', '{}'.format(home), user=user) + home = os.path.expanduser('~') + test_content('f {} - - - - %h', f'{home}', user=user) xdg_runtime_dir = os.getenv('XDG_RUNTIME_DIR') if xdg_runtime_dir is not None or not user: test_content('f {} - - - - %t', xdg_runtime_dir if user else '/run', - user=user) + user=user) # fmt: skip xdg_state_home = os.getenv('XDG_STATE_HOME') if xdg_state_home is None and user: - xdg_state_home = os.path.join(home, ".local/state") + xdg_state_home = os.path.join(home, '.local/state') test_content('f {} - - - - %S', xdg_state_home if user else '/var/lib', - user=user) + user=user) # fmt: skip xdg_cache_home = os.getenv('XDG_CACHE_HOME') if xdg_cache_home is None and user: - xdg_cache_home = os.path.join(home, ".cache") + xdg_cache_home = os.path.join(home, '.cache') test_content('f {} - - - - %C', xdg_cache_home if user else '/var/cache', - user=user) + user=user) # fmt: skip + + xdg_data_home = os.getenv('XDG_DATA_HOME') + if xdg_data_home is None and user: + xdg_data_home = os.path.join(home, '.local/share') + test_content('f {} - - - - %D', + xdg_data_home if user else '/usr/share', + user=user) # fmt: skip test_content('f {} - - - - %L', os.path.join(xdg_state_home, 'log') if user else '/var/log', - user=user) + user=user) # fmt: skip test_content('f {} - - - - %%', '%', user=user) + def mkfifo(parent, subpath): os.makedirs(parent, mode=0o755, exist_ok=True) first_component = subpath.split('/')[1] path = parent + '/' + first_component - print('path: {}'.format(path)) + print(f'path: {path}') os.mkfifo(path) + def mkdir(parent, subpath): first_component = subpath.split('/')[1] path = parent + '/' + first_component os.makedirs(path, mode=0o755, exist_ok=True) os.symlink(path, path + '/self', target_is_directory=True) + def symlink(parent, subpath): link_path = parent + '/link-target' os.makedirs(parent, mode=0o755, exist_ok=True) @@ -180,6 +198,7 @@ def symlink(parent, subpath): path = parent + '/' + first_component os.symlink(link_path, path, target_is_directory=True) + def file(parent, subpath): content = 'file-' + subpath.split('/')[1] path = parent + subpath @@ -187,6 +206,7 @@ def file(parent, subpath): with open(path, 'wb') as f: f.write(content.encode()) + def valid_symlink(parent, subpath): target = 'link-target' link_path = parent + target @@ -195,6 +215,7 @@ def valid_symlink(parent, subpath): path = parent + '/' + first_component os.symlink(target, path, target_is_directory=True) + def test_hard_cleanup(*, user): type_cbs = [None, file, mkdir, symlink] if 'mkfifo' in dir(os): @@ -209,29 +230,44 @@ def test_hard_cleanup(*, user): label = 'valid_symlink-deep' test_content('f= {} - - - - ' + label, label, user=user, subpath='/deep/1/2', path_cb=valid_symlink) + def test_base64(): - test_content('f~ {} - - - - UGlmZgpQYWZmClB1ZmYgCg==', "Piff\nPaff\nPuff \n", user=False) + test_content('f~ {} - - - - UGlmZgpQYWZmClB1ZmYgCg==', 'Piff\nPaff\nPuff \n', user=False) + def test_conditionalized_execute_bit(): - c = subprocess.run(exe_with_args + ['--version', '|', 'grep', '-F', '+ACL'], shell=True, stdout=subprocess.DEVNULL) + c = subprocess.run( + exe_with_args + ['--version', '|', 'grep', '-F', '+ACL'], + shell=True, + stdout=subprocess.DEVNULL, + ) if c.returncode != 0: return 0 d = tempfile.TemporaryDirectory(prefix='test-acl.', dir=temp_dir.name) - temp = Path(d.name) / "cond_exec" + temp = Path(d.name) / 'cond_exec' temp.touch() temp.chmod(0o644) - test_line(f"a {temp} - - - - u:root:Xwr", user=False, returncode=0) - c = subprocess.run(["getfacl", "-Ec", temp], - stdout=subprocess.PIPE, check=True, text=True) - assert "user:root:rw-" in c.stdout + test_line(f'a {temp} - - - - u:root:Xwr', user=False, returncode=0) + c = subprocess.run( + ['getfacl', '-Ec', temp], + stdout=subprocess.PIPE, + check=True, + text=True, + ) + assert 'user:root:rw-' in c.stdout temp.chmod(0o755) - test_line(f"a+ {temp} - - - - u:root:Xwr,g:root:rX", user=False, returncode=0) - c = subprocess.run(["getfacl", "-Ec", temp], - stdout=subprocess.PIPE, check=True, text=True) - assert "user:root:rwx" in c.stdout and "group:root:r-x" in c.stdout + test_line(f'a+ {temp} - - - - u:root:Xwr,g:root:rX', user=False, returncode=0) + c = subprocess.run( + ['getfacl', '-Ec', temp], + stdout=subprocess.PIPE, + check=True, + text=True, + ) + assert 'user:root:rwx' in c.stdout and 'group:root:r-x' in c.stdout + if __name__ == '__main__': test_invalids(user=False) diff --git a/test/test-udev.py b/test/test-udev.py index 20b55de7d679d..da5d41efe0179 100755 --- a/test/test-udev.py +++ b/test/test-udev.py @@ -19,8 +19,9 @@ import dataclasses import functools +import grp import os -import pwd, grp +import pwd import re import stat import subprocess @@ -37,11 +38,11 @@ sys.exit(77) -SYS_SCRIPT = Path(__file__).with_name('sys-script.py') +SYS_SCRIPT = Path(__file__).with_name('sys-script.py') try: - UDEV_BIN = Path(os.environ['UDEV_RULE_RUNNER']) + UDEV_BIN = Path(os.environ['UDEV_RULE_RUNNER']) except KeyError: - UDEV_BIN = Path(__file__).parent / 'manual/test-udev-rule-runner' + UDEV_BIN = Path(__file__).parent / 'manual/test-udev-rule-runner' UDEV_BIN = UDEV_BIN.absolute() # Those will be set by the udev_setup() fixture @@ -53,11 +54,12 @@ rules_10k_tags = \ '\n'.join(f'KERNEL=="sda", TAG+="test{i + 1}"' - for i in range(10_000)) + for i in range(10_000)) # fmt: skip rules_10k_tags_continuation = \ ',\\\n'.join(('KERNEL=="sda"', - *(f'TAG+="test{i + 1}"' for i in range(10_000)))) + *(f'TAG+="test{i + 1}"' for i in range(10_000)))) # fmt: skip + @dataclasses.dataclass class Device: @@ -149,8 +151,10 @@ def check_remove(self) -> None: def listify(f): def wrap(*args, **kwargs): return list(f(*args, **kwargs)) + return functools.update_wrapper(wrap, f) + @listify def all_block_devs(exp_func) -> list[Device]: # Create a device list with all block devices under /sys @@ -167,9 +171,7 @@ def all_block_devs(exp_func) -> list[Device]: tgt = tgt[5:] exp, not_exp = exp_func(tgt) - yield Device(devpath=tgt, - exp_links=exp, - not_exp_links=not_exp) + yield Device(devpath=tgt, exp_links=exp, not_exp_links=not_exp) @dataclasses.dataclass @@ -200,71 +202,71 @@ def create_rules_file(self) -> None: UDEV_RULES.parent.mkdir(exist_ok=True, parents=True) UDEV_RULES.write_text(self.rules) + RULES = [ Rules.new( 'no rules', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', ), - rules = r""" + rules=r''' # - """), - + ''', + ), Rules.new( 'label test of scsi disc', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["boot_disk"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['boot_disk'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "label test of scsi disc", + 'label test of scsi disc', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["boot_disk"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['boot_disk'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "label test of scsi disc", + 'label test of scsi disc', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["boot_disk"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['boot_disk'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "label test of scsi partition", + 'label test of scsi partition', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["boot_disk1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['boot_disk1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" - """), - + ''', + ), Rules.new( - "label test of pattern match", + 'label test of pattern match', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["boot_disk1", "boot_disk1-4", "boot_disk1-5"], - not_exp_links = ["boot_disk1-1", "boot_disk1-2", "boot_disk1-3", "boot_disk1-6", "boot_disk1-7"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['boot_disk1', 'boot_disk1-4', 'boot_disk1-5'], + not_exp_links=['boot_disk1-1', 'boot_disk1-2', 'boot_disk1-3', 'boot_disk1-6', 'boot_disk1-7'], ), - - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="?ATA", SYMLINK+="boot_disk%n-1" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA?", SYMLINK+="boot_disk%n-2" SUBSYSTEMS=="scsi", ATTRS{vendor}=="A??", SYMLINK+="boot_disk%n" @@ -277,48 +279,61 @@ def create_rules_file(self) -> None: SUBSYSTEMS=="scsi", GOTO="skip-7" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n-7" LABEL="skip-7" - """), - + ''', + ), Rules.new( - "label test of multiple sysfs files", + 'label test of multiple sysfs files', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["boot_disk1"], - not_exp_links = ["boot_diskX1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['boot_disk1'], + not_exp_links=['boot_diskX1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS X ", SYMLINK+="boot_diskX%n" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="boot_disk%n" - """), - + ''', + ), Rules.new( - "label test of max sysfs files (skip invalid rule)", + 'label test of max sysfs files (skip invalid rule)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["boot_disk1", "boot_diskXY1"], - not_exp_links = ["boot_diskXX1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['boot_disk1', 'boot_diskXY1'], + not_exp_links=['boot_diskXX1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="32", SYMLINK+="boot_diskXX%n" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="1", SYMLINK+="boot_diskXY%n" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n" - """), - - Rules.new( - "SYMLINK tests", - Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["link1", "link2/foo", "link3/aaa/bbb", - "abs1", "abs2/foo", "abs3/aaa/bbb", - "default___replace_test/foo_aaa", - "string_escape___replace/foo_bbb", - "env_with_space", - "default/replace/mode_foo__hoge", - "replace_env_harder_foo__hoge", - "match", "unmatch"], - not_exp_links = ["removed1", "removed2", "removed3", "unsafe/../../path", "/nondev/path/will/be/refused"], - ), - rules = r""" + ''', + ), + Rules.new( + 'SYMLINK tests', + Device( + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=[ + 'link1', + 'link2/foo', + 'link3/aaa/bbb', + 'abs1', + 'abs2/foo', + 'abs3/aaa/bbb', + 'default___replace_test/foo_aaa', + 'string_escape___replace/foo_bbb', + 'env_with_space', + 'default/replace/mode_foo__hoge', + 'replace_env_harder_foo__hoge', + 'match', + 'unmatch', + ], + not_exp_links=[ + 'removed1', + 'removed2', + 'removed3', + 'unsafe/../../path', + '/nondev/path/will/be/refused', + ], + ), + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="removed1" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="removed1" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/./dev///removed2" @@ -336,98 +351,98 @@ def create_rules_file(self) -> None: SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", OPTIONS="string_escape=replace", ENV{.HOGE}="replace/env/harder?foo;;hoge", SYMLINK+="%E{.HOGE}" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK=="link1", SYMLINK+="match" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK!="removed1", SYMLINK+="unmatch" - """), - + ''', + ), Rules.new( - "catch device by *", + 'catch device by *', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem/0", "catch-all"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem/0', 'catch-all'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM*", SYMLINK+="modem/%n" KERNEL=="*", SYMLINK+="catch-all" - """), - + ''', + ), Rules.new( - "catch device by * - take 2", + 'catch device by * - take 2', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem/0"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem/0'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="*ACM1", SYMLINK+="bad" KERNEL=="*ACM0", SYMLINK+="modem/%n" - """), - + ''', + ), Rules.new( - "catch device by ?", + 'catch device by ?', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem/0"], - not_exp_links = ["modem/0-1", "modem/0-2"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem/0'], + not_exp_links=['modem/0-1', 'modem/0-2'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM??*", SYMLINK+="modem/%n-1" KERNEL=="ttyACM??", SYMLINK+="modem/%n-2" KERNEL=="ttyACM?", SYMLINK+="modem/%n" - """), - + ''', + ), Rules.new( - "catch device by character class", + 'catch device by character class', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem/0"], - not_exp_links = ["modem/0-1", "modem/0-2"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem/0'], + not_exp_links=['modem/0-1', 'modem/0-2'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[A-Z]*", SYMLINK+="modem/%n-1" KERNEL=="ttyACM?[0-9]", SYMLINK+="modem/%n-2" KERNEL=="ttyACM[0-9]*", SYMLINK+="modem/%n" - """), - + ''', + ), Rules.new( "don't replace kernel name", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( "comment lines in config file (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' # this is a comment KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( "comment lines in config file with whitespace (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' # this is a comment with whitespace before the comment KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( "whitespace only lines (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["whitespace"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['whitespace'], ), - rules = r""" + rules=r''' @@ -436,49 +451,49 @@ def create_rules_file(self) -> None: - """), - + ''', + ), Rules.new( "empty lines in config file (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( "backslashed multi lines in config file (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", \ SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "preserve backslashes, if they are not for a newline", + 'preserve backslashes, if they are not for a newline', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["aaa"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['aaa'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", PROGRAM=="/bin/echo -e \101", RESULT=="A", SYMLINK+="aaa" - """), - + ''', + ), Rules.new( "stupid backslashed multi lines in config file (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' # \ @@ -490,1364 +505,1361 @@ def create_rules_file(self) -> None: KERNEL=="ttyACM0", \ SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "subdirectory handling", + 'subdirectory handling', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["sub/direct/ory/modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['sub/direct/ory/modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK+="sub/direct/ory/modem" - """), - + ''', + ), Rules.new( - "parent device name match of scsi partition", + 'parent device name match of scsi partition', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["first_disk5"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['first_disk5'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="first_disk%n" - """), - + ''', + ), Rules.new( - "test substitution chars", + 'test substitution chars', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["Major:8:minor:5:kernelnumber:5:id:0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['Major:8:minor:5:kernelnumber:5:id:0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:%M:minor:%m:kernelnumber:%n:id:%b" - """), - + ''', + ), Rules.new( - "import of shell-value returned from program", + 'import of shell-value returned from program', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node12345678"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node12345678'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", IMPORT{program}="/bin/echo -e ' TEST_KEY=12345678\n TEST_key2=98765'", SYMLINK+="node$env{TEST_KEY}" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "substitution of sysfs value (%s{file})", + 'substitution of sysfs value (%s{file})', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["disk-ATA-sda"], - not_exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['disk-ATA-sda'], + not_exp_links=['modem'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="disk-%s{vendor}-%k" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "program result substitution", + 'program result substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["special-device-5"], - not_exp_links = ["not"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['special-device-5'], + not_exp_links=['not'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="-special-*", SYMLINK+="not" SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="special-*", SYMLINK+="%c-%n" - """), - + ''', + ), Rules.new( - "program result substitution (newline removal)", + 'program result substitution (newline removal)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["newline_removed"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['newline_removed'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo test", RESULT=="test", SYMLINK+="newline_removed" - """), - + ''', + ), Rules.new( - "program result substitution", + 'program result substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["test-0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['test-0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n test-%b", RESULT=="test-0:0*", SYMLINK+="%c" - """), - + ''', + ), Rules.new( - "program with lots of arguments", + 'program with lots of arguments', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo9"], - not_exp_links = ["foo3", "foo4", "foo5", "foo6", "foo7", "foo8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo9'], + not_exp_links=['foo3', 'foo4', 'foo5', 'foo6', 'foo7', 'foo8'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="%c{7}" - """), - + ''', + ), Rules.new( - "program with subshell", + 'program with subshell', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["bar9"], - not_exp_links = ["foo3", "foo4", "foo5", "foo6", "foo7", "foo8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['bar9'], + not_exp_links=['foo3', 'foo4', 'foo5', 'foo6', 'foo7', 'foo8'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/bash -c 'echo foo3 foo4 foo5 foo6 foo7 foo8 foo9 | sed s/foo9/bar9/'", KERNEL=="sda5", SYMLINK+="%c{7}" - """), - + ''', + ), Rules.new( - "program arguments combined with apostrophes", + 'program arguments combined with apostrophes', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo7"], - not_exp_links = ["foo3", "foo4", "foo5", "foo6", "foo8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo7'], + not_exp_links=['foo3', 'foo4', 'foo5', 'foo6', 'foo8'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n 'foo3 foo4' 'foo5 foo6 foo7 foo8'", KERNEL=="sda5", SYMLINK+="%c{5}" - """), - + ''', + ), Rules.new( - "program arguments combined with escaped double quotes, part 1", + 'program arguments combined with escaped double quotes, part 1', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo2"], - not_exp_links = ["foo1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo2'], + not_exp_links=['foo1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/bash -c 'printf %%s \"foo1 foo2\" | grep \"foo1 foo2\"'", KERNEL=="sda5", SYMLINK+="%c{2}" - """), - + ''', + ), Rules.new( - "program arguments combined with escaped double quotes, part 2", + 'program arguments combined with escaped double quotes, part 2', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo2"], - not_exp_links = ["foo1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo2'], + not_exp_links=['foo1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/bash -c \"printf %%s 'foo1 foo2' | grep 'foo1 foo2'\"", KERNEL=="sda5", SYMLINK+="%c{2}" - """), - + ''', + ), Rules.new( - "program arguments combined with escaped double quotes, part 3", + 'program arguments combined with escaped double quotes, part 3', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo2"], - not_exp_links = ["foo1", "foo3"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo2'], + not_exp_links=['foo1', 'foo3'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/bash -c 'printf \"%%s %%s\" \"foo1 foo2\" \"foo3\"| grep \"foo1 foo2\"'", KERNEL=="sda5", SYMLINK+="%c{2}" - """), - + ''', + ), Rules.new( - "characters before the %c{N} substitution", + 'characters before the %c{N} substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["my-foo9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['my-foo9'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{7}" - """), - + ''', + ), Rules.new( - "substitute the second to last argument", + 'substitute the second to last argument', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["my-foo8"], - not_exp_links = ["my-foo3", "my-foo4", "my-foo5", "my-foo6", "my-foo7", "my-foo9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['my-foo8'], + not_exp_links=['my-foo3', 'my-foo4', 'my-foo5', 'my-foo6', 'my-foo7', 'my-foo9'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{6}" - """), - + ''', + ), Rules.new( - "test substitution by variable name", + 'test substitution by variable name', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["Major:8-minor:5-kernelnumber:5-id:0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['Major:8-minor:5-kernelnumber:5-id:0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:$major-minor:$minor-kernelnumber:$number-id:$id" - """), - + ''', + ), Rules.new( - "test substitution by variable name 2", + 'test substitution by variable name 2', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["Major:8-minor:5-kernelnumber:5-id:0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['Major:8-minor:5-kernelnumber:5-id:0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="Major:$major-minor:%m-kernelnumber:$number-id:$id" - """), - + ''', + ), Rules.new( - "test substitution by variable name 3", + 'test substitution by variable name 3', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["850:0:0:05"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['850:0:0:05'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="%M%m%b%n" - """), - + ''', + ), Rules.new( - "test substitution by variable name 4", + 'test substitution by variable name 4', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["855"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['855'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="$major$minor$number" - """), - + ''', + ), Rules.new( - "test substitution by variable name 5", + 'test substitution by variable name 5', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["8550:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['8550:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="$major%m%n$id" - """), - + ''', + ), Rules.new( - "non matching SUBSYSTEMS for device with no parent", + 'non matching SUBSYSTEMS for device with no parent', Device( - "/devices/virtual/tty/console", - exp_links = ["TTY"], - not_exp_links = ["foo"], + '/devices/virtual/tty/console', + exp_links=['TTY'], + not_exp_links=['foo'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo", RESULT=="foo", SYMLINK+="foo" KERNEL=="console", SYMLINK+="TTY" - """), - + ''', + ), Rules.new( - "non matching SUBSYSTEMS", + 'non matching SUBSYSTEMS', Device( - "/devices/virtual/tty/console", - exp_links = ["TTY"], - not_exp_links = ["foo"], + '/devices/virtual/tty/console', + exp_links=['TTY'], + not_exp_links=['foo'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="foo", ATTRS{dev}=="5:1", SYMLINK+="foo" KERNEL=="console", SYMLINK+="TTY" - """), - + ''', + ), Rules.new( - "ATTRS match", + 'ATTRS match', Device( - "/devices/virtual/tty/console", - exp_links = ["foo", "TTY"], + '/devices/virtual/tty/console', + exp_links=['foo', 'TTY'], ), - rules = r""" + rules=r''' KERNEL=="console", SYMLINK+="TTY" ATTRS{dev}=="5:1", SYMLINK+="foo" - """), - + ''', + ), Rules.new( - "ATTR (empty file)", + 'ATTR (empty file)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["empty", "not-something"], - not_exp_links = ["something", "not-empty"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['empty', 'not-something'], + not_exp_links=['something', 'not-empty'], ), - rules = r""" + rules=r''' KERNEL=="sda", ATTR{test_empty_file}=="?*", SYMLINK+="something" KERNEL=="sda", ATTR{test_empty_file}!="", SYMLINK+="not-empty" KERNEL=="sda", ATTR{test_empty_file}=="", SYMLINK+="empty" KERNEL=="sda", ATTR{test_empty_file}!="?*", SYMLINK+="not-something" - """), - + ''', + ), Rules.new( - "ATTR (non-existent file)", + 'ATTR (non-existent file)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["non-existent", "wrong"], - not_exp_links = ["something", "empty", "not-empty", - "not-something", "something"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['non-existent', 'wrong'], + not_exp_links=['something', 'empty', 'not-empty', 'not-something', 'something'], ), - rules = r""" + rules=r''' KERNEL=="sda", ATTR{nofile}=="?*", SYMLINK+="something" KERNEL=="sda", ATTR{nofile}!="", SYMLINK+="not-empty" KERNEL=="sda", ATTR{nofile}=="", SYMLINK+="empty" KERNEL=="sda", ATTR{nofile}!="?*", SYMLINK+="not-something" KERNEL=="sda", TEST!="nofile", SYMLINK+="non-existent" KERNEL=="sda", SYMLINK+="wrong" - """), - + ''', + ), Rules.new( - "program and bus type match", + 'program and bus type match', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="usb", PROGRAM=="/bin/echo -n usb-%b", SYMLINK+="%c" SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n scsi-%b", SYMLINK+="%c" SUBSYSTEMS=="foo", PROGRAM=="/bin/echo -n foo-%b", SYMLINK+="%c" - """), - + ''', + ), Rules.new( - "sysfs parent hierarchy", + 'sysfs parent hierarchy', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' ATTRS{idProduct}=="007b", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "name test with ! in the name", + 'name test with ! in the name', Device( - "/devices/virtual/block/fake!blockdev0", - devnode = "fake/blockdev0", - exp_links = ["is/a/fake/blockdev0"], - not_exp_links = ["is/not/a/fake/blockdev0", "modem"], + '/devices/virtual/block/fake!blockdev0', + devnode='fake/blockdev0', + exp_links=['is/a/fake/blockdev0'], + not_exp_links=['is/not/a/fake/blockdev0', 'modem'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", SYMLINK+="is/not/a/%k" SUBSYSTEM=="block", SYMLINK+="is/a/%k" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "name test with ! in the name, but no matching rule", + 'name test with ! in the name, but no matching rule', Device( - "/devices/virtual/block/fake!blockdev0", - devnode = "fake/blockdev0", - not_exp_links = ["modem"], + '/devices/virtual/block/fake!blockdev0', + devnode='fake/blockdev0', + not_exp_links=['modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "KERNELS rule", + 'KERNELS rule', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0"], - not_exp_links = ["no-match", "short-id", "not-scsi"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0'], + not_exp_links=['no-match', 'short-id', 'not-scsi'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="usb", KERNELS=="0:0:0:0", SYMLINK+="not-scsi" SUBSYSTEMS=="scsi", KERNELS=="0:0:0:1", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNELS==":0", SYMLINK+="short-id" SUBSYSTEMS=="scsi", KERNELS=="/0:0:0:0", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="scsi-0:0:0:0" - """), - + ''', + ), Rules.new( - "KERNELS wildcard all", + 'KERNELS wildcard all', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0"], - not_exp_links = ["no-match", "before"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0'], + not_exp_links=['no-match', 'before'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="*:1", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNELS=="*:0:1", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNELS=="*:0:0:1", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNEL=="0:0:0:0", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNELS=="*", SYMLINK+="scsi-0:0:0:0" - """), - + ''', + ), Rules.new( - "KERNELS wildcard partial", + 'KERNELS wildcard partial', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0", "before"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0', 'before'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNELS=="*:0", SYMLINK+="scsi-0:0:0:0" - """), - + ''', + ), Rules.new( - "KERNELS wildcard partial 2", + 'KERNELS wildcard partial 2', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0", "before"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0', 'before'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNELS=="*:0:0:0", SYMLINK+="scsi-0:0:0:0" - """), - + ''', + ), Rules.new( - "substitute attr with link target value (first match)", + 'substitute attr with link target value (first match)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["driver-is-sd"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['driver-is-sd'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", SYMLINK+="driver-is-$attr{driver}" - """), - + ''', + ), Rules.new( - "substitute attr with link target value (currently selected device)", + 'substitute attr with link target value (currently selected device)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["driver-is-ahci"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['driver-is-ahci'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="pci", SYMLINK+="driver-is-$attr{driver}" - """), - + ''', + ), Rules.new( - "ignore ATTRS attribute whitespace", + 'ignore ATTRS attribute whitespace', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ignored"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ignored'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE", SYMLINK+="ignored" - """), - + ''', + ), Rules.new( - "do not ignore ATTRS attribute whitespace", + 'do not ignore ATTRS attribute whitespace', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["matched-with-space"], - not_exp_links = ["wrong-to-ignore"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['matched-with-space'], + not_exp_links=['wrong-to-ignore'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE ", SYMLINK+="wrong-to-ignore" SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE ", SYMLINK+="matched-with-space" - """), - + ''', + ), Rules.new( - "permissions USER=bad GROUP=name", + 'permissions USER=bad GROUP=name', Device( - "/devices/virtual/tty/tty33", - exp_perms = "0:0:0600", + '/devices/virtual/tty/tty33', + exp_perms='0:0:0600', ), - rules = r""" + rules=r''' KERNEL=="tty33", OWNER="bad", GROUP="name" - """), - + ''', + ), Rules.new( - "permissions OWNER=1", + 'permissions OWNER=1', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "1::0600", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='1::0600', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1" - """), - + ''', + ), Rules.new( - "permissions GROUP=1", + 'permissions GROUP=1', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = ":1:0660", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms=':1:0660', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="1" - """), - + ''', + ), Rules.new( - "textual user id", + 'textual user id', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "daemon::0600", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='daemon::0600', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="daemon" - """), - + ''', + ), Rules.new( - "textual group id", + 'textual group id', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = ":daemon:0660", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms=':daemon:0660', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="daemon" - """), - + ''', + ), Rules.new( - "textual user/group id", + 'textual user/group id', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "root:audio:0660", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='root:audio:0660', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="root", GROUP="audio" - """), - + ''', + ), Rules.new( - "permissions MODE=0777", + 'permissions MODE=0777', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "::0777", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='::0777', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", MODE="0777" - """), - + ''', + ), Rules.new( - "permissions OWNER=1 GROUP=1 MODE=0777", + 'permissions OWNER=1 GROUP=1 MODE=0777', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "1:1:0777", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='1:1:0777', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1", GROUP="1", MODE="0777" - """), - + ''', + ), Rules.new( - "permissions OWNER to 1", + 'permissions OWNER to 1', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1::", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1::', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1" - """), - + ''', + ), Rules.new( - "permissions GROUP to 1", + 'permissions GROUP to 1', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = ":1:0660", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms=':1:0660', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="1" - """), - + ''', + ), Rules.new( - "permissions MODE to 0060", + 'permissions MODE to 0060', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "::0060", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='::0060', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", MODE="0060" - """), - + ''', + ), Rules.new( - "permissions OWNER, GROUP, MODE", + 'permissions OWNER, GROUP, MODE', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1:1:0777", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1:1:0777', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1", GROUP="1", MODE="0777" - """), - + ''', + ), Rules.new( - "permissions only rule", + 'permissions only rule', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1:1:0777", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1:1:0777', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", OWNER="1", GROUP="1", MODE="0777" KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444" KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n" - """), - + ''', + ), Rules.new( - "multiple permissions only rule", + 'multiple permissions only rule', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1:1:0777", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1:1:0777', ), - rules = r""" + rules=r''' SUBSYSTEM=="tty", OWNER="1" SUBSYSTEM=="tty", GROUP="1" SUBSYSTEM=="tty", MODE="0777" KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444" KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n" - """), - + ''', + ), Rules.new( - "permissions only rule with override at SYMLINK+ rule", + 'permissions only rule with override at SYMLINK+ rule', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1:2:0777", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1:2:0777', ), - rules = r""" + rules=r''' SUBSYSTEM=="tty", OWNER="1" SUBSYSTEM=="tty", GROUP="1" SUBSYSTEM=="tty", MODE="0777" KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444" KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="2" - """), - + ''', + ), Rules.new( - "major/minor number test", + 'major/minor number test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_major_minor = "8:0", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_major_minor='8:0', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node" - """), - + ''', + ), Rules.new( - "big major number test", + 'big major number test', Device( - "/devices/virtual/misc/misc-fake1", - exp_links = ["node"], - exp_major_minor = "4095:1", + '/devices/virtual/misc/misc-fake1', + exp_links=['node'], + exp_major_minor='4095:1', ), - rules = r""" + rules=r''' KERNEL=="misc-fake1", SYMLINK+="node" - """), - + ''', + ), Rules.new( - "big major and big minor number test", + 'big major and big minor number test', Device( - "/devices/virtual/misc/misc-fake89999", - exp_links = ["node"], - exp_major_minor = "4095:89999", + '/devices/virtual/misc/misc-fake89999', + exp_links=['node'], + exp_major_minor='4095:89999', ), - rules = r""" + rules=r''' KERNEL=="misc-fake89999", SYMLINK+="node" - """), - + ''', + ), Rules.new( - "multiple symlinks with format char", + 'multiple symlinks with format char', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["symlink1-0", "symlink2-ttyACM0", "symlink3-"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['symlink1-0', 'symlink2-ttyACM0', 'symlink3-'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK="symlink1-%n symlink2-%k symlink3-%b" - """), - + ''', + ), Rules.new( - "multiple symlinks with a lot of s p a c e s", + 'multiple symlinks with a lot of s p a c e s', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["one", "two"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['one', 'two'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK=" one two " - """), - + ''', + ), Rules.new( - "symlink with spaces in substituted variable", + 'symlink with spaces in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}="one two three" SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with leading space in substituted variable", + 'symlink with leading space in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}=" one two three" SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with trailing space in substituted variable", + 'symlink with trailing space in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}="one two three " SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with lots of space in substituted variable", + 'symlink with lots of space in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}=" one two three " SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with multiple spaces in substituted variable", + 'symlink with multiple spaces in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}=" one two three " SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with space and var with space", + 'symlink with space and var with space', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["first", "name-one_two_three-end", - "another_symlink", "a", "b", "c"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['first', 'name-one_two_three-end', 'another_symlink', 'a', 'b', 'c'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}=" one two three " SYMLINK=" first name-$env{WITH_WS}-end another_symlink a b c " - """), - + ''', + ), Rules.new( - "symlink with env which contain slash (see #19309)", + 'symlink with env which contain slash (see #19309)', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["first", "name-aaa_bbb_ccc-end", - "another_symlink", "a", "b", "c"], - not_exp_links = ["ame-aaa/bbb/ccc-end"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['first', 'name-aaa_bbb_ccc-end', 'another_symlink', 'a', 'b', 'c'], + not_exp_links=['ame-aaa/bbb/ccc-end'], ), - rules = r""" + rules=r''' ENV{WITH_SLASH}="aaa/bbb/ccc" OPTIONS="string_escape=replace", ENV{REPLACED}="$env{WITH_SLASH}" SYMLINK=" first name-$env{REPLACED}-end another_symlink a b c " - """), - + ''', + ), Rules.new( - "symlink creation (same directory)", + 'symlink creation (same directory)', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK="modem%n" - """), - + ''', + ), Rules.new( - "multiple symlinks", + 'multiple symlinks', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["first-0", "second-0", "third-0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['first-0', 'second-0', 'third-0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK="first-%n second-%n third-%n" - """), - + ''', + ), Rules.new( "symlink name '.'", Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', ), # we get a warning, but the process does not fail - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="." - """), - + ''', + ), Rules.new( - "symlink node to itself", + 'symlink node to itself', Device( - "/devices/virtual/tty/tty0", + '/devices/virtual/tty/tty0', ), # we get a warning, but the process does not fail - rules = r""" + rules=r''' KERNEL=="tty0", SYMLINK+="tty0" - """), - + ''', + ), Rules.new( - "symlink %n substitution", + 'symlink %n substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["symlink0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['symlink0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink%n" - """), - + ''', + ), Rules.new( - "symlink %k substitution", + 'symlink %k substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["symlink-ttyACM0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['symlink-ttyACM0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink-%k" - """), - + ''', + ), Rules.new( - "symlink %M:%m substitution", + 'symlink %M:%m substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["major-166:0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['major-166:0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="major-%M:%m" - """), - + ''', + ), Rules.new( - "symlink %b substitution", + 'symlink %b substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["symlink-0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['symlink-0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="symlink-%b" - """), - + ''', + ), Rules.new( - "symlink %c substitution", + 'symlink %c substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["test"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['test'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo test", SYMLINK+="%c" - """), - + ''', + ), Rules.new( - "symlink %c{N} substitution", + 'symlink %c{N} substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["test"], - not_exp_links = ["symlink", "this"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['test'], + not_exp_links=['symlink', 'this'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2}" - """), - + ''', + ), Rules.new( - "symlink %c{N+} substitution", + 'symlink %c{N+} substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["test", "this"], - not_exp_links = ["symlink"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['test', 'this'], + not_exp_links=['symlink'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2+}" - """), - + ''', + ), Rules.new( - "symlink only rule with %c{N+}", + 'symlink only rule with %c{N+}', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["test", "this"], - not_exp_links = ["symlink"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['test', 'this'], + not_exp_links=['symlink'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/bin/echo link test this" SYMLINK+="%c{2+}" - """), - + ''', + ), Rules.new( - "symlink %s{filename} substitution", + 'symlink %s{filename} substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["166:0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['166:0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="%s{dev}" - """), - + ''', + ), Rules.new( - "program result substitution (numbered part of)", + 'program result substitution (numbered part of)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["link1", "link2"], - not_exp_links = ["node"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['link1', 'link2'], + not_exp_links=['node'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2", RESULT=="node *", SYMLINK+="%c{2} %c{3}" - """), - + ''', + ), Rules.new( - "program result substitution (numbered part of+)", + 'program result substitution (numbered part of+)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["link1", "link2", "link3", "link4"], - not_exp_links = ["node"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['link1', 'link2', 'link3', 'link4'], + not_exp_links=['node'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2 link3 link4", RESULT=="node *", SYMLINK+="%c{2+}" - """), - + ''', + ), Rules.new( - "SUBSYSTEM match test", + 'SUBSYSTEM match test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - not_exp_links = ["should_not_match", "should_not_match2"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + not_exp_links=['should_not_match', 'should_not_match2'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", SUBSYSTEM=="vc" SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", SUBSYSTEM=="block" SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match2", SUBSYSTEM=="vc" - """), - + ''', + ), Rules.new( - "DRIVERS match test", + 'DRIVERS match test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - not_exp_links = ["should_not_match"] + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + not_exp_links=['should_not_match'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", DRIVERS=="sd-wrong" SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", DRIVERS=="sd" - """), - + ''', + ), Rules.new( - "devnode substitution test", + 'devnode substitution test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/usr/bin/test -b %N" SYMLINK+="node" - """), - + ''', + ), Rules.new( - "parent node name substitution test", + 'parent node name substitution test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["sda-part-1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['sda-part-1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="%P-part-%n" - """), - + ''', + ), Rules.new( - "udev_root substitution", + 'udev_root substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["start-/dev-end"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['start-/dev-end'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="start-%r-end" - """), - + ''', + ), Rules.new( # This is not supported any more - "last_rule option", + 'last_rule option', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["last", "very-last"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['last', 'very-last'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="last", OPTIONS="last_rule" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="very-last" - """), - + ''', + ), Rules.new( - "negation KERNEL!=", + 'negation KERNEL!=', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["match", "before"], - not_exp_links = ["matches-but-is-negated"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['match', 'before'], + not_exp_links=['matches-but-is-negated'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL!="sda1", SYMLINK+="matches-but-is-negated" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNEL!="xsda1", SYMLINK+="match" - """), - + ''', + ), Rules.new( - "negation SUBSYSTEM!=", + 'negation SUBSYSTEM!=', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["before", "not-anything"], - not_exp_links = ["matches-but-is-negated"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['before', 'not-anything'], + not_exp_links=['matches-but-is-negated'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", SUBSYSTEM=="block", KERNEL!="sda1", SYMLINK+="matches-but-is-negated" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" SUBSYSTEMS=="scsi", SUBSYSTEM!="anything", SYMLINK+="not-anything" - """), - + ''', + ), Rules.new( - "negation PROGRAM!= exit code", + 'negation PROGRAM!= exit code', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["before", "nonzero-program"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['before', 'nonzero-program'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" KERNEL=="sda1", PROGRAM!="/bin/false", SYMLINK+="nonzero-program" - """), - + ''', + ), Rules.new( - "ENV{} test", + 'ENV{} test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["true"], - not_exp_links = ["bad", "wrong"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['true'], + not_exp_links=['bad', 'wrong'], ), - rules = r""" + rules=r''' ENV{ENV_KEY_TEST}="test" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", SYMLINK+="true" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "ENV{} test", + 'ENV{} test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["true"], - not_exp_links = ["bad", "wrong", "no"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['true'], + not_exp_links=['bad', 'wrong', 'no'], ), - rules = r""" + rules=r''' ENV{ENV_KEY_TEST}="test" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="yes", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sdax1", SYMLINK+="no" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sda1", SYMLINK+="true" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "ENV{} test (assign)", + 'ENV{} test (assign)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["true", "before"], - not_exp_links = ["no"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['true', 'before'], + not_exp_links=['no'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="true", SYMLINK+="true" - """), - + ''', + ), Rules.new( - "ENV{} test (assign 2 times)", + 'ENV{} test (assign 2 times)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["true", "before"], - not_exp_links = ["no", "bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['true', 'before'], + not_exp_links=['no', 'bad'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="absolutely-$env{ASSIGN}" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="true", SYMLINK+="bad" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="absolutely-true", SYMLINK+="true" - """), - + ''', + ), Rules.new( - "ENV{} test (assign2)", + 'ENV{} test (assign2)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part"], - not_exp_links = ["disk"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part'], + not_exp_links=['disk'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["disk"], - not_exp_links = ["part"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['disk'], + not_exp_links=['part'], ), - rules = r""" + rules=r''' SUBSYSTEM=="block", KERNEL=="*[0-9]", ENV{PARTITION}="true", ENV{MAINDEVICE}="false" SUBSYSTEM=="block", KERNEL=="*[!0-9]", ENV{PARTITION}="false", ENV{MAINDEVICE}="true" ENV{MAINDEVICE}=="true", SYMLINK+="disk" SUBSYSTEM=="block", SYMLINK+="before" ENV{PARTITION}=="true", SYMLINK+="part" - """), - + ''', + ), Rules.new( - "untrusted string sanitize", + 'untrusted string sanitize', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["sane"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['sane'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e name; (/usr/bin/badprogram)", RESULT=="name_ _/usr/bin/badprogram_", SYMLINK+="sane" - """), - + ''', + ), Rules.new( "untrusted string sanitize (don't replace utf8)", Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["uber"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['uber'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \xc3\xbcber" RESULT=="über", SYMLINK+="uber" - """), - + ''', + ), Rules.new( - "untrusted string sanitize (replace invalid utf8)", + 'untrusted string sanitize (replace invalid utf8)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["replaced"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['replaced'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \xef\xe8garbage", RESULT=="__garbage", SYMLINK+="replaced" - """), - + ''', + ), Rules.new( - "read sysfs value from parent device", + 'read sysfs value from parent device', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["serial-354172020305000"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['serial-354172020305000'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM*", ATTRS{serial}=="?*", SYMLINK+="serial-%s{serial}" - """), - + ''', + ), Rules.new( - "match against empty key string", + 'match against empty key string', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ok"], - not_exp_links = ["not-1-ok", "not-2-ok", "not-3-ok"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ok'], + not_exp_links=['not-1-ok', 'not-2-ok', 'not-3-ok'], ), - rules = r""" + rules=r''' KERNEL=="sda", ATTRS{nothing}!="", SYMLINK+="not-1-ok" KERNEL=="sda", ATTRS{nothing}=="", SYMLINK+="not-2-ok" KERNEL=="sda", ATTRS{vendor}!="", SYMLINK+="ok" KERNEL=="sda", ATTRS{vendor}=="", SYMLINK+="not-3-ok" - """), - + ''', + ), Rules.new( - "check ACTION value", + 'check ACTION value', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ok"], - not_exp_links = ["unknown-not-ok"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ok'], + not_exp_links=['unknown-not-ok'], ), - rules = r""" + rules=r''' ACTION=="unknown", KERNEL=="sda", SYMLINK+="unknown-not-ok" ACTION=="add", KERNEL=="sda", SYMLINK+="ok" - """), - + ''', + ), Rules.new( - "final assignment", + 'final assignment', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ok"], - exp_perms = "root:tty:0640", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ok'], + exp_perms='root:tty:0640', ), - rules = r""" + rules=r''' KERNEL=="sda", GROUP:="tty" KERNEL=="sda", GROUP="root", MODE="0640", SYMLINK+="ok" - """), - + ''', + ), Rules.new( - "final assignment 2", + 'final assignment 2', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ok"], - exp_perms = "root:tty:0640", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ok'], + exp_perms='root:tty:0640', ), - rules = r""" + rules=r''' KERNEL=="sda", GROUP:="tty" SUBSYSTEM=="block", MODE:="640" KERNEL=="sda", GROUP="root", MODE="0666", SYMLINK+="ok" - """), - + ''', + ), Rules.new( - "env substitution", + 'env substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node-add-me"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node-add-me'], ), - rules = r""" + rules=r''' KERNEL=="sda", MODE="0666", SYMLINK+="node-$env{ACTION}-me" - """), - + ''', + ), Rules.new( - "reset list to current value", + 'reset list to current value', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["three"], - not_exp_links = ["two", "one"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['three'], + not_exp_links=['two', 'one'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="one" KERNEL=="ttyACM[0-9]*", SYMLINK+="two" KERNEL=="ttyACM[0-9]*", SYMLINK="three" - """), - + ''', + ), Rules.new( - "test empty SYMLINK+ (empty override)", + 'test empty SYMLINK+ (empty override)', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right"], - not_exp_links = ["wrong"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right'], + not_exp_links=['wrong'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="wrong" KERNEL=="ttyACM[0-9]*", SYMLINK="" KERNEL=="ttyACM[0-9]*", SYMLINK+="right" - """), - + ''', + ), Rules.new( - "test multi matches", + 'test multi matches', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right", "before"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right', 'before'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM*", SYMLINK+="before" KERNEL=="ttyACM*|nothing", SYMLINK+="right" - """), - + ''', + ), Rules.new( - "test multi matches 2", + 'test multi matches 2', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right", "before"], - not_exp_links = ["nomatch"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right', 'before'], + not_exp_links=['nomatch'], ), - rules = r""" + rules=r''' KERNEL=="dontknow*|*nothing", SYMLINK+="nomatch" KERNEL=="ttyACM*", SYMLINK+="before" KERNEL=="dontknow*|ttyACM*|nothing*", SYMLINK+="right" - """), - + ''', + ), Rules.new( - "test multi matches 3", + 'test multi matches 3', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right"], - not_exp_links = ["nomatch", "wrong1", "wrong2"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right'], + not_exp_links=['nomatch', 'wrong1', 'wrong2'], ), - rules = r""" + rules=r''' KERNEL=="dontknow|nothing", SYMLINK+="nomatch" KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1" KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2" KERNEL=="dontknow|ttyACM0|nothing", SYMLINK+="right" - """), - + ''', + ), Rules.new( - "test multi matches 4", + 'test multi matches 4', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right"], - not_exp_links = ["nomatch", "wrong1", "wrong2", "wrong3"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right'], + not_exp_links=['nomatch', 'wrong1', 'wrong2', 'wrong3'], ), - rules = r""" + rules=r''' KERNEL=="dontknow|nothing", SYMLINK+="nomatch" KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1" KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2" KERNEL=="all|dontknow|ttyACM0", SYMLINK+="right" KERNEL=="ttyACM0a|nothing", SYMLINK+="wrong3" - """), - + ''', + ), Rules.new( - "test multi matches 5", + 'test multi matches 5', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG="foo" TAGS=="|foo", SYMLINK+="found" TAGS=="|aaa", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 6", + 'test multi matches 6', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", ENV{HOGE}="" ENV{HOGE}=="|foo", SYMLINK+="found" ENV{HOGE}=="aaa|bbb", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 7", + 'test multi matches 7', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG="foo" TAGS=="foo||bar", SYMLINK+="found" TAGS=="aaa||bbb", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 8", + 'test multi matches 8', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", ENV{HOGE}="" ENV{HOGE}=="foo||bar", SYMLINK+="found" ENV{HOGE}=="aaa|bbb", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 9", + 'test multi matches 9', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found", "found2"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found', 'found2'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG="foo" TAGS=="foo|", SYMLINK+="found" TAGS=="aaa|", SYMLINK+="bad" KERNEL=="sda", TAGS!="hoge", SYMLINK+="found2" KERNEL=="sda", TAGS!="foo", SYMLINK+="bad2" - """), - + ''', + ), Rules.new( - "test multi matches 10", + 'test multi matches 10', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", ENV{HOGE}="" ENV{HOGE}=="foo|", SYMLINK+="found" ENV{HOGE}=="aaa|bbb", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 11", + 'test multi matches 11', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG="c" TAGS=="foo||bar||c", SYMLINK+="found" TAGS=="aaa||bbb||ccc", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "TAG refuses invalid string", + 'TAG refuses invalid string', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["valid", "found"], - not_exp_links = ["empty", "invalid_char", "path", "bad", "bad2"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['valid', 'found'], + not_exp_links=['empty', 'invalid_char', 'path', 'bad', 'bad2'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG+="", TAG+="invalid.char", TAG+="path/is/also/invalid", TAG+="valid" TAGS=="", SYMLINK+="empty" TAGS=="invalid.char", SYMLINK+="invalid_char" @@ -1856,33 +1868,33 @@ def create_rules_file(self) -> None: TAGS=="valid|", SYMLINK+="found" TAGS=="aaa|", SYMLINK+="bad" TAGS=="aaa|bbb", SYMLINK+="bad2" - """), - + ''', + ), Rules.new( - "IMPORT parent test", + 'IMPORT parent test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["parent"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['parent'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["parentenv-parent_right"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['parentenv-parent_right'], ), - delay = 500000, # Serialized! We need to sleep here after adding sda - rules = r""" + delay=500000, # Serialized! We need to sleep here after adding sda + rules=r''' KERNEL=="sda1", IMPORT{parent}="PARENT*", SYMLINK+="parentenv-$env{PARENT_KEY}$env{WRONG_PARENT_KEY}" KERNEL=="sda", IMPORT{program}="/bin/echo -e 'PARENT_KEY=parent_right\nWRONG_PARENT_KEY=parent_wrong'" KERNEL=="sda", SYMLINK+="parent" - """), - + ''', + ), Rules.new( - "GOTO test", + 'GOTO test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["right"], - not_exp_links = ["wrong", "wrong2"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['right'], + not_exp_links=['wrong', 'wrong2'], ), - rules = r""" + rules=r''' KERNEL=="sda1", GOTO="TEST" KERNEL=="sda1", SYMLINK+="wrong" KERNEL=="sda1", GOTO="BAD" @@ -1890,207 +1902,207 @@ def create_rules_file(self) -> None: KERNEL=="sda1", SYMLINK+="right", LABEL="TEST", GOTO="end" KERNEL=="sda1", SYMLINK+="wrong2", LABEL="BAD" LABEL="end" - """), - + ''', + ), Rules.new( - "GOTO label does not exist", + 'GOTO label does not exist', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["right"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['right'], ), - rules = r""" + rules=r''' KERNEL=="sda1", GOTO="does-not-exist" KERNEL=="sda1", SYMLINK+="right", LABEL="exists" - """), - + ''', + ), Rules.new( - "SYMLINK+ compare test", + 'SYMLINK+ compare test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["right", "link"], - not_exp_links = ["wrong"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['right', 'link'], + not_exp_links=['wrong'], ), - rules = r""" + rules=r''' KERNEL=="sda1", SYMLINK+="link" KERNEL=="sda1", SYMLINK=="link*", SYMLINK+="right" KERNEL=="sda1", SYMLINK=="nolink*", SYMLINK+="wrong" - """), - + ''', + ), Rules.new( - "invalid key operation", + 'invalid key operation', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["yes"], - not_exp_links = ["no"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['yes'], + not_exp_links=['no'], ), - rules = r""" + rules=r''' KERNEL="sda1", SYMLINK+="no" KERNEL=="sda1", SYMLINK+="yes" - """), - + ''', + ), Rules.new( - "operator chars in attribute", + 'operator chars in attribute', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["yes"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['yes'], ), - rules = r""" + rules=r''' KERNEL=="sda", ATTR{test:colon+plus}=="?*", SYMLINK+="yes" - """), - + ''', + ), Rules.new( - "overlong comment line", + 'overlong comment line', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["yes"], - not_exp_links = ["no"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['yes'], + not_exp_links=['no'], ), - rules = r""" + rules=r''' # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 KERNEL=="sda1", SYMLINK+=="no" KERNEL=="sda1", SYMLINK+="yes" - """), - + ''', + ), Rules.new( - "magic subsys/kernel lookup", + 'magic subsys/kernel lookup', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["00:16:41:e2:8d:ff"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['00:16:41:e2:8d:ff'], ), - rules = r""" + rules=r''' KERNEL=="sda", SYMLINK+="$attr{[net/eth0]address}" - """), - + ''', + ), Rules.new( - "TEST absolute path", + 'TEST absolute path', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["there"], - not_exp_links = ["notthere"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['there'], + not_exp_links=['notthere'], ), - rules = r""" + rules=r''' TEST=="/etc/passwd", SYMLINK+="there" TEST!="/etc/passwd", SYMLINK+="notthere" - """), - + ''', + ), Rules.new( - "TEST subsys/kernel lookup", + 'TEST subsys/kernel lookup', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["yes"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['yes'], ), - rules = r""" + rules=r''' KERNEL=="sda", TEST=="[net/eth0]", SYMLINK+="yes" - """), - + ''', + ), Rules.new( - "TEST relative path", + 'TEST relative path', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["relative"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['relative'], ), - rules = r""" + rules=r''' KERNEL=="sda", TEST=="size", SYMLINK+="relative" - """), - + ''', + ), Rules.new( - "TEST wildcard substitution (find queue/nr_requests)", + 'TEST wildcard substitution (find queue/nr_requests)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found-subdir"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found-subdir'], ), - rules = r""" + rules=r''' KERNEL=="sda", TEST=="*/nr_requests", SYMLINK+="found-subdir" - """), - + ''', + ), Rules.new( - "TEST MODE=0000", + 'TEST MODE=0000', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_perms = "0:0:0000", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_perms='0:0:0000', ), - rules = r""" + rules=r''' KERNEL=="sda", MODE="0000" - """), - + ''', + ), Rules.new( - "TEST PROGRAM feeds OWNER, GROUP, MODE", + 'TEST PROGRAM feeds OWNER, GROUP, MODE', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_perms = "1:1:0400", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_perms='1:1:0400', ), - rules = r""" + rules=r''' KERNEL=="sda", MODE="666" KERNEL=="sda", PROGRAM=="/bin/echo 1 1 0400", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}" - """), - + ''', + ), Rules.new( - "TEST PROGRAM feeds MODE with overflow", + 'TEST PROGRAM feeds MODE with overflow', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_perms = "0:0:0440", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_perms='0:0:0440', ), - rules = r""" + rules=r''' KERNEL=="sda", MODE="440" KERNEL=="sda", PROGRAM=="/bin/echo 0 0 0400letsdoabuffferoverflow0123456789012345789012345678901234567890", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}" - """), - + ''', + ), Rules.new( - "magic [subsys/sysname] attribute substitution", + 'magic [subsys/sysname] attribute substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["sda-8741C4G-end"], - exp_perms = "0:0:0600", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['sda-8741C4G-end'], + exp_perms='0:0:0600', ), - rules = r""" + rules=r''' KERNEL=="sda", SYMLINK+="%k-%s{[dmi/id]product_name}-end" - """), - + ''', + ), Rules.new( - "builtin path_id", + 'builtin path_id', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0'], ), - rules = r""" + rules=r''' KERNEL=="sda", IMPORT{builtin}="path_id" KERNEL=="sda", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}" - """), - + ''', + ), Rules.new( - "add and match tag", + 'add and match tag', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", TAG+="green" TAGS=="green", SYMLINK+="found" TAGS=="blue", SYMLINK+="bad" - """), - + ''', + ), Rules.new( "don't crash with lots of tags", Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], ), - rules = f""" + rules=f''' {rules_10k_tags} TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="found" - """), - + ''', + ), Rules.new( - "continuations", + 'continuations', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = f""" + rules=f''' {rules_10k_tags_continuation} TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="bad" KERNEL=="sda",\\ @@ -2104,16 +2116,16 @@ def create_rules_file(self) -> None: \\ TAG+="hoge4" TAGS=="hoge1", TAGS=="hoge2", TAGS=="hoge3", TAGS=="hoge4", SYMLINK+="found" - """), - + ''', + ), Rules.new( - "continuations with empty line", + 'continuations with empty line', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' # empty line finishes continuation KERNEL=="sda", TAG+="foo" \ @@ -2122,16 +2134,16 @@ def create_rules_file(self) -> None: KERNEL=="sdb", TAG+="bbb" TAGS=="foo", SYMLINK+="found" TAGS=="aaa", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "continuations with space only line", + 'continuations with space only line', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = """ + rules=''' # space only line finishes continuation KERNEL=="sda", TAG+="foo" \\ \t @@ -2140,192 +2152,194 @@ def create_rules_file(self) -> None: KERNEL=="sdb", TAG+="bbb" TAGS=="foo", SYMLINK+="found" TAGS=="aaa", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "multiple devices", + 'multiple devices', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part-1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part-1'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["part-5"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['part-5'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6", - exp_links = ["part-6"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6', + exp_links=['part-6'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links = ["part-7"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7', + exp_links=['part-7'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8", - exp_links = ["part-8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8', + exp_links=['part-8'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9", - exp_links = ["part-9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9', + exp_links=['part-9'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10", - exp_links = ["part-10"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10', + exp_links=['part-10'], ), - rules = r""" + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n" - """), - + ''', + ), Rules.new( - "multiple devices, same link name, positive prio", + 'multiple devices, same link name, positive prio', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part-1"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part-1'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["part-5"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['part-5'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6", - not_exp_links = ["partition"], - exp_links = ["part-6"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6', + not_exp_links=['partition'], + exp_links=['part-6'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links = ["part-7", "partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7', + exp_links=['part-7', 'partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8", - not_exp_links = ["partition"], - exp_links = ["part-8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8', + not_exp_links=['partition'], + exp_links=['part-8'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9", - not_exp_links = ["partition"], - exp_links = ["part-9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9', + not_exp_links=['partition'], + exp_links=['part-9'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10", - not_exp_links = ["partition"], - exp_links = ["part-10"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10', + not_exp_links=['partition'], + exp_links=['part-10'], ), - repeat = 100, - rules = r""" + repeat=100, + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition" KERNEL=="*7", OPTIONS+="link_priority=10" - """), - + ''', + ), Rules.new( - "multiple devices, same link name, negative prio", + 'multiple devices, same link name, negative prio', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part-1"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part-1'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["part-5"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['part-5'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6", - not_exp_links = ["partition"], - exp_links = ["part-6"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6', + not_exp_links=['partition'], + exp_links=['part-6'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links = ["part-7", "partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7', + exp_links=['part-7', 'partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8", - not_exp_links = ["partition"], - exp_links = ["part-8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8', + not_exp_links=['partition'], + exp_links=['part-8'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9", - not_exp_links = ["partition"], - exp_links = ["part-9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9', + not_exp_links=['partition'], + exp_links=['part-9'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10", - not_exp_links = ["partition"], - exp_links = ["part-10"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10', + not_exp_links=['partition'], + exp_links=['part-10'], ), - rules = r""" + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition" KERNEL!="*7", OPTIONS+="link_priority=-10" - """), - + ''', + ), Rules.new( - "multiple devices, same link name, positive prio, sleep", + 'multiple devices, same link name, positive prio, sleep', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part-1"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part-1'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["part-5"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['part-5'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6", - not_exp_links = ["partition"], - exp_links = ["part-6"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6', + not_exp_links=['partition'], + exp_links=['part-6'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links = ["part-7", "partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7', + exp_links=['part-7', 'partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8", - not_exp_links = ["partition"], - exp_links = ["part-8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8', + not_exp_links=['partition'], + exp_links=['part-8'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9", - not_exp_links = ["partition"], - exp_links = ["part-9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9', + not_exp_links=['partition'], + exp_links=['part-9'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10", - not_exp_links = ["partition"], - exp_links = ["part-10"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10', + not_exp_links=['partition'], + exp_links=['part-10'], ), - delay = 10000, - rules = r""" + delay=10000, + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition" KERNEL=="*7", OPTIONS+="link_priority=10" - """), - + ''', + ), Rules.new( 'all_block_devs', - device_generator = lambda: \ - all_block_devs(lambda name: (["blockdev"], None) if name.endswith('/sda6') else (None, None)), - repeat = 10, - rules = r""" + device_generator=lambda: all_block_devs( + lambda name: (['blockdev'], None) if name.endswith('/sda6') else (None, None) + ), + repeat=10, + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sd*", SYMLINK+="blockdev" KERNEL=="sda6", OPTIONS+="link_priority=10" - """), - + ''', + ), Rules.new( - "case insensitive match", + 'case insensitive match', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["ok"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['ok'], ), - - rules = r""" + rules=r''' KERNEL==i"SDA1", SUBSYSTEMS==i"SCSI", ATTRS{vendor}==i"a?a", SYMLINK+="ok" - """), + ''', + ), ] + def fork_and_run_udev(action: str, rules: Rules) -> None: kinder = [] for k, device in enumerate(rules.devices): @@ -2351,13 +2365,11 @@ def environment_issue(): if os.getuid() != 0: return 'Must be root to run properly' - c = subprocess.run(['systemd-detect-virt', '-r', '-q'], - check=False) + c = subprocess.run(['systemd-detect-virt', '-r', '-q'], check=False) if c.returncode == 0: return 'Running in a chroot, skipping the test' - c = subprocess.run(['systemd-detect-virt', '-c', '-q'], - check=False) + c = subprocess.run(['systemd-detect-virt', '-c', '-q'], check=False) if c.returncode == 0: return 'Running in a container, skipping the test' @@ -2375,27 +2387,24 @@ def udev_setup(): _tmpdir = tempfile.TemporaryDirectory() tmpdir = Path(_tmpdir.name) - UDEV_RUN = tmpdir / 'run' + UDEV_RUN = tmpdir / 'run' UDEV_RULES = UDEV_RUN / 'udev-test.rules' udev_tmpfs = tmpdir / 'tmpfs' - UDEV_DEV = udev_tmpfs / 'dev' - UDEV_SYS = udev_tmpfs / 'sys' + UDEV_DEV = udev_tmpfs / 'dev' + UDEV_SYS = udev_tmpfs / 'sys' - subprocess.run(['umount', udev_tmpfs], - stderr=subprocess.DEVNULL, - check=False) + subprocess.run(['umount', udev_tmpfs], stderr=subprocess.DEVNULL, check=False) udev_tmpfs.mkdir(exist_ok=True, parents=True) - subprocess.check_call(['mount', '-v', - '-t', 'tmpfs', - '-o', 'rw,mode=0755,nosuid,noexec', - 'tmpfs', udev_tmpfs]) + subprocess.check_call( + ['mount', '-v', '-t', 'tmpfs', '-o', 'rw,mode=0755,nosuid,noexec', 'tmpfs', udev_tmpfs] + ) UDEV_DEV.mkdir(exist_ok=True) # setting group and mode of udev_dev ensures the tests work # even if the parent directory has setgid bit enabled. - os.chmod(UDEV_DEV,0o755) + os.chmod(UDEV_DEV, 0o755) os.chown(UDEV_DEV, 0, 0) os.mknod(UDEV_DEV / 'null', 0o600 | stat.S_IFCHR, os.makedev(1, 3)) @@ -2411,10 +2420,10 @@ def udev_setup(): os.chdir(tmpdir) - if subprocess.run([UDEV_BIN, 'check'], - check=False).returncode != 0: - pytest.skip(f'{UDEV_BIN} failed to set up the environment, skipping the test', - allow_module_level=True) + if subprocess.run([UDEV_BIN, 'check'], check=False).returncode != 0: + pytest.skip( + f'{UDEV_BIN} failed to set up the environment, skipping the test', allow_module_level=True + ) yield @@ -2423,7 +2432,7 @@ def udev_setup(): udev_tmpfs.rmdir() -@pytest.mark.parametrize("rules", RULES, ids=(rule.desc for rule in RULES)) +@pytest.mark.parametrize('rules', RULES, ids=(rule.desc for rule in RULES)) def test_udev(rules: Rules, udev_setup): assert udev_setup is None @@ -2441,6 +2450,7 @@ def test_udev(rules: Rules, udev_setup): for device in rules.devices: device.check_remove() + if __name__ == '__main__': issue = environment_issue() if issue: diff --git a/test/units/TEST-02-UNITTESTS.sh b/test/units/TEST-02-UNITTESTS.sh index ee5eab08d566d..25d3b8a5b3249 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 @@ -80,7 +80,7 @@ run_test() { --unit="$name" \ --wait "$test" && ret=0 || ret=$? - exec {LOCK_FD}> /lock + exec {LOCK_FD}>/lock flock --exclusive ${LOCK_FD} if [[ $ret -eq 77 ]] || [[ $ret -eq 127 ]]; then diff --git a/test/units/TEST-03-JOBS.sh b/test/units/TEST-03-JOBS.sh index 6f7494ef2f83e..67fe117fc8662 100755 --- a/test/units/TEST-03-JOBS.sh +++ b/test/units/TEST-03-JOBS.sh @@ -218,4 +218,76 @@ assert_eq "$(systemctl show "$UNIT_NAME" -P NRestarts)" "1" rm /run/systemd/system/"$UNIT_NAME" +# Test RestartRandomizedDelaySec= + +export UNIT_NAME="TEST-03-JOBS-restart-randomized-delay.service" + +cat >"/run/systemd/system/$UNIT_NAME" <|", then stop again so it never has to elapse. + systemctl start --no-block "$UNIT_NAME" + timeout 10 bash -c 'while [[ "$(systemctl show "'"$UNIT_NAME"'" -P SubState)" != "auto-restart" ]]; do sleep .2; done' + systemctl stop "$UNIT_NAME" + journalctl --sync + # needed because of -o pipefail + { journalctl -q --no-pager -o cat -b -u "$UNIT_NAME" --grep="Next restart interval calculated as" || true; } | + sed -n 's/.*calculated as: \(.*\) (randomized delay: \(.*\))$/\1|\2/p' | tail -n1 +} + +# Several samples + "not all equal": two draws could rarely render identically (~1e-6) and falsely fail. +DELAYS=() +TOTALS=() +for _ in {1..4}; do + IFS='|' read -r total delay <<<"$(get_restart_interval)" + TOTALS+=("$total") + DELAYS+=("$delay") +done + +systemctl log-level "$PREV_LOG_LEVEL" + +: "Chosen randomized restart delays: ${DELAYS[*]} (totals: ${TOTALS[*]})" +for delay in "${DELAYS[@]}"; do + assert_neq "$delay" "" + # Within bound: a value below 1s never renders a bare "s" token (only ms/us). + if [[ "$delay" =~ [0-9]s ]]; then + echo "FAIL: randomized restart delay '$delay' exceeds the configured 1s bound" >&2 + exit 1 + fi +done +# Total must vary, proving the jitter is folded into the armed timer (not merely logged). +all_equal=1 +for total in "${TOTALS[@]}"; do + [[ "$total" == "${TOTALS[0]}" ]] || all_equal=0 +done +assert_eq "$all_equal" "0" + touch /testok 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-04-JOURNAL.fss.sh b/test/units/TEST-04-JOURNAL.fss.sh index 50f0608280e37..b3f27195a7596 100755 --- a/test/units/TEST-04-JOURNAL.fss.sh +++ b/test/units/TEST-04-JOURNAL.fss.sh @@ -5,8 +5,8 @@ set -o pipefail # Forward Secure Sealing -if ! journalctl --version | grep -F +GCRYPT >/dev/null; then - echo "Built without gcrypt, skipping the FSS tests" +if ! journalctl --version | grep -F +OPENSSL >/dev/null; then + echo "Built without openssl, skipping the FSS tests" exit 0 fi diff --git a/test/units/TEST-04-JOURNAL.journal-gatewayd.sh b/test/units/TEST-04-JOURNAL.journal-gatewayd.sh index 617976e752fd2..64af25e9600b1 100755 --- a/test/units/TEST-04-JOURNAL.journal-gatewayd.sh +++ b/test/units/TEST-04-JOURNAL.journal-gatewayd.sh @@ -88,6 +88,12 @@ curl -LSfs \ --header "Range: entries=$TEST_CURSOR:1:1" \ http://localhost:19531/entries?SYSLOG_IDENTIFIER="$TEST_TAG" >"$LOG_FILE" jq -se "length == 0" "$LOG_FILE" +# A skip of INT64_MIN must not crash the daemon +curl -LSs \ + --header "Accept: application/json" \ + --header "Range: entries=:-9223372036854775808:1" \ + http://localhost:19531/entries >/dev/null || true +curl -LSfs http://localhost:19531/entries >"$LOG_FILE" # Check if the specified cursor refers to an existing entry and return just that entry curl -LSfs \ --header "Accept: application/json" \ diff --git a/test/units/TEST-04-JOURNAL.journal-remote.sh b/test/units/TEST-04-JOURNAL.journal-remote.sh index 5d4b13d70e054..14be8a1309691 100755 --- a/test/units/TEST-04-JOURNAL.journal-remote.sh +++ b/test/units/TEST-04-JOURNAL.journal-remote.sh @@ -310,3 +310,15 @@ systemctl stop systemd-journal-remote.{socket,service} rm -rf /var/log/journal/remote/* rm /run/systemd/journal-upload.conf.d/99-test.conf rm /run/systemd/journal-remote.conf.d/99-test.conf + +DOS_DIR="$(mktemp -d)" +{ head -c $((5 * 1024 * 1024)) /dev/zero | tr '\0' 'A'; printf '\n'; } >"$DOS_DIR/no-separator.export" +{ head -c $((5 * 1024 * 1024)) /dev/zero | tr '\0' 'A'; printf '=value\n'; } >"$DOS_DIR/long-field-name.export" +for export_file in "$DOS_DIR"/no-separator.export "$DOS_DIR"/long-field-name.export; do + rm -f "$DOS_DIR"/*.journal + timeout 30 /usr/lib/systemd/systemd-journal-remote \ + --split-mode=none \ + --output="$DOS_DIR/dos.journal" \ + "$export_file" || [[ $? -lt 124 ]] +done +rm -rf "$DOS_DIR" diff --git a/test/units/TEST-04-JOURNAL.journalctl-metrics.sh b/test/units/TEST-04-JOURNAL.journalctl-metrics.sh new file mode 100755 index 0000000000000..88aa91dd16b0f --- /dev/null +++ b/test/units/TEST-04-JOURNAL.journalctl-metrics.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# journalctl's Varlink server also exposes the io.systemd.Metrics provider that reports the most recent +# high-priority journal messages, socket-activated under /run/systemd/report/. +SOCKET="/run/systemd/report/io.systemd.Journal" + +# Ensure the socket is running, as some distros don't enable it by default. +systemctl start systemd-journalctl-metrics.socket +test -S "$SOCKET" + +# Both activating sockets share the same systemd-journal group access controls, so the server exposes both +# io.systemd.Metrics and io.systemd.JournalAccess regardless of which socket was connected to. +varlinkctl list-interfaces "$SOCKET" | grep io.systemd.Metrics >/dev/null +varlinkctl list-interfaces "$SOCKET" | grep io.systemd.JournalAccess >/dev/null + +# Describe must advertise our metric family. +varlinkctl --more call "$SOCKET" io.systemd.Metrics.Describe '{}' | + grep "io.systemd.Journal.HighPriorityMessage" >/dev/null + +# Seed a unique high-priority (crit) message right before listing, so it is guaranteed to be among the 10 +# most recent matches. The service runs with --priority=crit, so the level must be crit or higher. We run +# as root (_UID=0), so it lands in the system journal that the provider reads. +TAG="$(systemd-id128 new)" +systemd-cat -t "high-$TAG" -p crit echo "metrics-hiprio-$TAG" +# A low-priority (info) message that must NOT be reported. +systemd-cat -t "info-$TAG" -p info echo "metrics-info-$TAG" +journalctl --sync + +LIST="$(varlinkctl --more call "$SOCKET" io.systemd.Metrics.List '{}')" + +# Output is valid application/json-seq. +jq --seq . >/dev/null <<<"$LIST" + +# The crit message is reported as the whole journal entry object in .value, with every field under its raw +# journal name (journal_entry_to_json()). systemd-cat -t sets SYSLOG_IDENTIFIER, which maps to "object". +[ "$(jq --seq -r --arg o "high-$TAG" 'select(.object == $o) | .value.MESSAGE' <<<"$LIST")" = "metrics-hiprio-$TAG" ] +[ "$(jq --seq -r --arg o "high-$TAG" 'select(.object == $o) | .value.PRIORITY' <<<"$LIST")" = "2" ] + +# The info message is filtered out (priority below crit). +[ -z "$(jq --seq -r --arg o "info-$TAG" 'select(.object == $o) | .value.MESSAGE' <<<"$LIST")" ] + +# The list is capped at 10. Seed more than that and confirm exactly 10 are returned. (Count raw .name +# lines; jq --seq frames non-string output like `length` with an RS byte, but -r string output is clean.) +for ((i = 0; i < 15; i++)); do + systemd-cat -t "cap-$TAG" -p crit echo "metrics-cap-$TAG-$i" +done +journalctl --sync +LIST="$(varlinkctl --more call "$SOCKET" io.systemd.Metrics.List '{}')" +COUNT="$(jq --seq -r '.name' <<<"$LIST" | wc -l)" +test "$COUNT" -eq 10 diff --git a/test/units/TEST-04-JOURNAL.journalctl-varlink.sh b/test/units/TEST-04-JOURNAL.journalctl-varlink.sh index 2d7b19990c815..d1738487f5df3 100755 --- a/test/units/TEST-04-JOURNAL.journalctl-varlink.sh +++ b/test/units/TEST-04-JOURNAL.journalctl-varlink.sh @@ -5,6 +5,26 @@ set -o pipefail VARLINK_SOCKET="/run/systemd/io.systemd.JournalAccess" +# Wrapper around varlinkctl that retries up to 3 times when the server returns +# NoEntries to avoid spurious flaky failures +varlinkctl_get_entries() { + local output rc + for _ in 1 2 3; do + output="$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "$@" 2>&1)" && rc=0 || rc=$? + if [[ $rc -eq 0 ]]; then + printf '%s\n' "$output" + return 0 + fi + if ! grep -q 'io.systemd.JournalAccess.NoEntries' <<<"$output"; then + printf '%s\n' "$output" >&2 + return $rc + fi + journalctl --sync || true + done + printf '%s\n' "$output" >&2 + return $rc +} + # ensure the varlink basics work varlinkctl list-interfaces "$VARLINK_SOCKET" | grep io.systemd.JournalAccess varlinkctl introspect "$VARLINK_SOCKET" | grep "method GetEntries(" @@ -16,18 +36,18 @@ systemd-cat -t "$TAG" -p warning echo "varlink-test-warning" journalctl --sync # most basic call works -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{}' | jq --seq . +varlinkctl_get_entries '{}' | jq --seq . # validate the JSON has some basic properties (similar to journalctls json output) -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{}' | jq --seq '.entry | {MESSAGE, PRIORITY, _UID}' +varlinkctl_get_entries '{}' | jq --seq '.entry | {MESSAGE, PRIORITY, _UID}' # check that default limit works (100), we don't know how many entries we have so we just check # bounds -ENTRIES=$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{}' | wc -l) +ENTRIES=$(varlinkctl_get_entries '{}' | wc -l) test "$ENTRIES" -gt 0 test "$ENTRIES" -le 100 # check explicit limit -ENTRIES=$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"limit": 3}' | wc -l) +ENTRIES=$(varlinkctl_get_entries '{"limit": 3}' | wc -l) test "$ENTRIES" -le 3 # check unit filter: use transient units to get deterministic results @@ -38,14 +58,16 @@ systemd-run --unit="$UNIT_NAME_2" --wait bash -c 'echo hello-from-varlink-test-2 journalctl --sync # single unit filter -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\"]}" | grep -q "hello-from-varlink-test-1" -(! varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\"]}" | grep "hello-from-varlink-test-2") +SINGLE_OUTPUT="$(varlinkctl_get_entries "{\"units\": [\"$UNIT_NAME_1\"]}")" +grep "hello-from-varlink-test-1" >/dev/null <<<"$SINGLE_OUTPUT" +(! grep "hello-from-varlink-test-2" >/dev/null <<<"$SINGLE_OUTPUT") # multi unit filter -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\", \"$UNIT_NAME_2\"]}" | grep -q "hello-from-varlink-test-1" -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\", \"$UNIT_NAME_2\"]}" | grep -q "hello-from-varlink-test-2" +MULTI_OUTPUT="$(varlinkctl_get_entries "{\"units\": [\"$UNIT_NAME_1\", \"$UNIT_NAME_2\"]}")" +grep "hello-from-varlink-test-1" >/dev/null <<<"$MULTI_OUTPUT" +grep "hello-from-varlink-test-2" >/dev/null <<<"$MULTI_OUTPUT" # check priority filter: priority 4 (warning) should include our warning message -varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"priority": 4, "limit": 1000}' | grep -q "varlink-test-warning" +varlinkctl_get_entries '{"priority": 4, "limit": 1000}' | grep "varlink-test-warning" >/dev/null # check priority filter: priority 3 (error) should NOT include our warning (priority 4) (! varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"priority": 3, "limit": 1000}' | grep "varlink-test-warning") diff --git a/test/units/TEST-04-JOURNAL.reload.sh b/test/units/TEST-04-JOURNAL.reload.sh index 44003028aa292..a58c3de59f0fd 100755 --- a/test/units/TEST-04-JOURNAL.reload.sh +++ b/test/units/TEST-04-JOURNAL.reload.sh @@ -11,12 +11,12 @@ MACHINE_ID="$(persistent)" @@ -73,7 +74,8 @@ verify_journals "$VAL1" persistent : "Add entries in runtime" journalctl --relinquish -VAL2=$(write_to_journal) +write_to_journal +VAL2="$SERVICE_NAME" verify_journals "$VAL2" runtime : "Reload journald after relinquish (persistent->persistent)" @@ -84,13 +86,13 @@ verify_journals "$VAL1" persistent verify_journals "$VAL2" runtime : "Write new message and confirm it's written to runtime." -VAL=$(write_to_journal) -verify_journals "$VAL" runtime +write_to_journal +verify_journals "$SERVICE_NAME" runtime : "Flush and confirm that messages are written to system." journalctl --flush -VAL=$(write_to_journal) -verify_journals "$VAL" persistent +write_to_journal +verify_journals "$SERVICE_NAME" persistent # Test persistent->volatile cat </run/systemd/journald.conf.d/reload.conf @@ -100,16 +102,16 @@ EOF : "Confirm old message exists where it was written to persistent journal." systemctl reload systemd-journald.service -verify_journals "$VAL" persistent +verify_journals "$SERVICE_NAME" persistent : "Confirm that new message is written to runtime journal." -VAL=$(write_to_journal) -verify_journals "$VAL" runtime +write_to_journal +verify_journals "$SERVICE_NAME" runtime : "Test volatile works and logs are NOT getting written to system journal despite flush." journalctl --flush -VAL=$(write_to_journal) -verify_journals "$VAL" runtime +write_to_journal +verify_journals "$SERVICE_NAME" runtime : "Disable compression" cat </run/systemd/journald.conf.d/reload.conf @@ -154,8 +156,8 @@ if (( total_size > max_size )) && (( num_archived_journals > 0 )); then fi : "Write a message to runtime journal" -VAL=$(write_to_journal) -verify_journals "$VAL" runtime +write_to_journal +verify_journals "$SERVICE_NAME" runtime : "Reload volatile->persistent" cat </run/systemd/journald.conf.d/reload.conf @@ -165,15 +167,15 @@ EOF systemctl reload systemd-journald.service : "Confirm that previous message is still in runtime journal." -verify_journals "$VAL" runtime +verify_journals "$SERVICE_NAME" runtime : "Confirm that new messages are written to runtime journal." -VAL=$(write_to_journal) -verify_journals "$VAL" runtime +write_to_journal +verify_journals "$SERVICE_NAME" runtime : "Confirm that flushing writes to system journal." journalctl --flush -verify_journals "$VAL" persistent +verify_journals "$SERVICE_NAME" persistent : "Disable compression" cat </run/systemd/journald.conf.d/reload.conf diff --git a/test/units/TEST-06-SELINUX.sh b/test/units/TEST-06-SELINUX.sh index 5cf6e80815b12..76f5c1652bf07 100755 --- a/test/units/TEST-06-SELINUX.sh +++ b/test/units/TEST-06-SELINUX.sh @@ -39,12 +39,13 @@ CONTEXT="$(stat -c %C /proc/sys/kernel/core_pattern)" (! systemd-run --wait --pipe -p ConditionSecurity='selinux' false) systemd-run --wait --pipe -p ConditionSecurity='!selinux' false +# Pass a unique --machine= name on each invocation to avoid "already loaded" flakiness NSPAWN_ARGS=(systemd-nspawn -q --volatile=yes --directory=/ --bind-ro=/etc --inaccessible=/etc/machine-id) -[[ "$("${NSPAWN_ARGS[@]}" cat /proc/self/attr/current | tr -d '\0')" != "$CONTEXT" ]] -[[ "$("${NSPAWN_ARGS[@]}" --selinux-context="$CONTEXT" cat /proc/self/attr/current | tr -d '\0')" == "$CONTEXT" ]] -[[ "$("${NSPAWN_ARGS[@]}" stat --printf %C /run)" != "$CONTEXT" ]] -[[ "$("${NSPAWN_ARGS[@]}" --selinux-apifs-context="$CONTEXT" stat --printf %C /run)" == "$CONTEXT" ]] -[[ "$("${NSPAWN_ARGS[@]}" --selinux-apifs-context="$CONTEXT" --tmpfs=/tmp stat --printf %C /tmp)" == "$CONTEXT" ]] +[[ "$("${NSPAWN_ARGS[@]}" --machine="nspawn-test-0" cat /proc/self/attr/current | tr -d '\0')" != "$CONTEXT" ]] +[[ "$("${NSPAWN_ARGS[@]}" --machine="nspawn-test-1" --selinux-context="$CONTEXT" cat /proc/self/attr/current | tr -d '\0')" == "$CONTEXT" ]] +[[ "$("${NSPAWN_ARGS[@]}" --machine="nspawn-test-2" stat --printf %C /run)" != "$CONTEXT" ]] +[[ "$("${NSPAWN_ARGS[@]}" --machine="nspawn-test-3" --selinux-apifs-context="$CONTEXT" stat --printf %C /run)" == "$CONTEXT" ]] +[[ "$("${NSPAWN_ARGS[@]}" --machine="nspawn-test-4" --selinux-apifs-context="$CONTEXT" --tmpfs=/tmp stat --printf %C /tmp)" == "$CONTEXT" ]] if [[ -n "${TEST_SELINUX_CHECK_AVCS:-}" ]] && ((TEST_SELINUX_CHECK_AVCS)); then (! journalctl -t audit -g AVC -o cat) diff --git a/test/units/TEST-07-PID1.DeferReactivation.sh b/test/units/TEST-07-PID1.DeferReactivation.sh index ff795ff002239..8d16ff114507f 100755 --- a/test/units/TEST-07-PID1.DeferReactivation.sh +++ b/test/units/TEST-07-PID1.DeferReactivation.sh @@ -4,6 +4,13 @@ set -eux set -o pipefail +if [[ -v ASAN_OPTIONS ]]; then + # Under sanitizers the service is slow enough that the calendar timer with 5s resolution ends up + # missing ticks, making the test flaky + echo "Sanitizers detected, skipping the test..." + exit 0 +fi + systemctl start defer-reactivation.timer timeout 20 bash -c 'until [[ -e /tmp/defer-reactivation.log ]]; do sleep .5; done' 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..dea08f0404154 --- /dev/null +++ b/test/units/TEST-07-PID1.alias-corruption.sh @@ -0,0 +1,460 @@ +#!/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=() +} + +# Wait until at least one job for a unit name matching the given prefix is +# queued. Used to make sure the serialized state we're about to dump contains +# embedded 'job\n...\n\n' subsections so the deserialization path is fully +# exercised. +wait_for_pending_job() { + local prefix="${1:?}" + local i unit + + for i in {1..100}; do + for unit in $(systemctl list-jobs --no-legend | awk '{print $2}'); do + if [[ "$unit" == "$prefix"* ]]; then + return 0 + fi + done + sleep 0.1 + done + + echo "ERROR: no $prefix* jobs are pending" + systemctl list-jobs || true + return 1 +} + +run_test() { + local reload_cmd="${1:?}" + # If "with_pending_jobs", also queue hanging reload jobs on the running sus + # units, so that the serialized state contains embedded 'job' subsections + # to fully exercise the deserialization + local pending_jobs="${2:-}" + local n_sus=20 + local current_pid journal_warnings new_pid orig_pid p pid reload_start orphan orphan_cgroup unit warning_count + local orphan_units + + if [[ "$pending_jobs" == "with_pending_jobs" ]]; then + n_sus=100 + fi + + echo "" + echo "=========================================" + echo "Testing with: systemctl $reload_cmd${pending_jobs:+ ($pending_jobs)}" + echo "=========================================" + + cat >/run/systemd/system/legit.service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +EOF + + # Create 100 sus units. They must be running so systemd CANNOT garbage + # collect them. If they are dead/stopped, systemd can remove them from + # memory before serialization. + # + # In with_pending_jobs mode they additionally get a pending reload job + # queued via 'systemctl --no-block reload' AFTER they are running, so the + # serialized stream contains 'job\n...\n\n' subsections AND the units have + # a real MainPID. The skip-desync regression in unit_deserialize_state_skip() + # stops at the job subsection's empty line marker, leaving the rest of the + # serialized stream to be consumed as garbage. If legit.service is dropped + # from the collected names set as a result, the alias-protection branch in + # manager_deserialize_one_unit() is bypassed and a sus unit's MainPID + # overwrites legit.service's MainPID. + echo "Creating $n_sus sus units..." + for i in $(seq -f "%03g" 1 "$n_sus"); do + cat >/run/systemd/system/sus-"${i}".service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +ExecReload=/bin/sleep infinity +TimeoutStartSec=infinity +EOF + done + + systemctl daemon-reload + + echo "Starting legit unit..." + systemctl start legit.service + + echo "Starting sus units..." + for i in $(seq -f "%03g" 1 "$n_sus"); do + systemctl start sus-"${i}".service + done + + if [[ "$pending_jobs" == "with_pending_jobs" ]]; then + # Queue a hanging reload job on each running sus unit. ExecReload runs + # 'sleep infinity', so the reload job stays in the queue forever; the + # unit stays active with its real MainPID, and the serialized stream + # contains both 'main-pid=...' and 'job\n...\n\n' subsections. + for i in $(seq -f "%03g" 1 "$n_sus"); do + systemctl --no-block reload sus-"${i}".service + done + # Make sure at least one reload job is actually queued, otherwise the + # serialized stream might not contain any job subsections yet. + wait_for_pending_job sus- + fi + + echo "Setup complete: 1 running legit unit, $n_sus ${pending_jobs:+job-bearing }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 several times to + # reduce false negative rate (ordering luck). The skip-desync regression + # also depends on iteration order: legit.service must happen to be + # serialized right after a job-bearing unit for its name to be dropped from + # the collected set (which is what bypasses the alias-protection check), + # so multiple attempts are needed in both modes. + for attempt in 1 2 3; do + echo "" + echo "--- Attempt $attempt/3 ---" + + unset sus_pids + declare -A sus_pids + for i in $(seq -f "%03g" 1 "$n_sus"); do + pid=$(systemctl show -P MainPID sus-"${i}".service) + if (( pid != 0 )); then + sus_pids["sus-${i}"]=$pid + abandoned_pids+=("$pid") + fi + done + + echo "Converting sus units to symlinks -> legit.service..." + for i in $(seq -f "%03g" 1 "$n_sus"); 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 tracked in a synthesized + # orphan unit, not abandoned. + echo "Verifying sus unit processes were migrated into synthesized orphan units..." + 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 being preserved!" + return 1 + fi + # 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) is still running and the alias correctly does not claim it" + done + + # Verify that synthesized orphan units exist that cover all the + # previously-tracked PIDs. Each preserved process must belong to a + # orphaned-r* unit. Orphan units retain the original unit's type + # (.service here) and are marked load-state=not-found, so list-units + # with --all is required to see them. + echo "Verifying synthesized orphan units are present and own the PIDs..." + orphan_units=$(systemctl list-units --all --no-legend --plain 'orphaned-r*' | awk '{print $1}') + if [[ -z "$orphan_units" ]]; then + echo "ERROR: No orphaned-r* units were synthesized!" + systemctl list-units --all --no-legend --plain || true + return 1 + fi + echo "Found orphan units:" + echo "$orphan_units" + + # Build a set of all PIDs reported by any orphan unit (via Tasks/cgroup membership). + unset tracked_pids + declare -A tracked_pids + for orphan in $orphan_units; do + orphan_cgroup=$(systemctl show -P ControlGroup "$orphan") + if [[ -z "$orphan_cgroup" || ! -r "/sys/fs/cgroup${orphan_cgroup}/cgroup.procs" ]]; then + # Orphan unit may have been recorded with the original unit's cgroup path which still exists + echo "ERROR: Cannot read cgroup.procs for $orphan at expected path /sys/fs/cgroup${orphan_cgroup}/cgroup.procs" + return 1 + fi + while read -r p; do + [[ -n "$p" ]] && tracked_pids[$p]=1 + done < "/sys/fs/cgroup${orphan_cgroup}/cgroup.procs" + done + + # Cross-check: every original sus PID must appear under exactly one orphan unit cgroup. + for unit in "${!sus_pids[@]}"; do + pid=${sus_pids[$unit]} + if [[ -z "${tracked_pids[$pid]:-}" ]]; then + echo "ERROR: PID $pid (from $unit) is not tracked by any synthesized orphan unit!" + echo "Orphan unit contents:" + for orphan in $orphan_units; do + echo " $orphan -> $(systemctl show -P ControlGroup "$orphan")" + done + return 1 + fi + done + echo "All ${#sus_pids[@]} sus PIDs are tracked by synthesized orphan units." + + # Check consistency between journal warnings and synthesized orphan units. + echo "Checking journal for 'Synthesized orphan unit' warnings..." + journal_warnings=$(journalctl --since "$reload_start" --no-pager | grep "Synthesized orphan unit" || true) + warning_count=$(echo "$journal_warnings" | grep -c "Synthesized orphan unit" || true) + + echo "Found $warning_count 'Synthesized orphan unit' warnings" + + 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 + + # Stop synthesized orphan units (which terminates their tracked + # processes) so we get a clean slate for the next iteration. + for orphan in $orphan_units; do + systemctl stop "$orphan" 2>/dev/null || true + done + + 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 "%03g" 1 "$n_sus"); do + rm -f /run/systemd/system/sus-"${i}".service + cat >/run/systemd/system/sus-"${i}".service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +ExecReload=/bin/sleep infinity +TimeoutStartSec=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 "%03g" 1 "$n_sus"); do + systemctl start sus-"${i}".service + done + + if [[ "$pending_jobs" == "with_pending_jobs" ]]; then + for i in $(seq -f "%03g" 1 "$n_sus"); do + systemctl --no-block reload sus-"${i}".service + done + wait_for_pending_job sus- + fi + + 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 + # Stop any leftover synthesized orphan units from previous iterations. + # Do this in two passes since a queued stop job from the deserialized + # state may take a moment to dispatch and tear the unit down. + for orphan in $(systemctl list-units --all --no-legend --plain 'orphaned-r*' 2>/dev/null | awk '{print $1}'); do + systemctl stop "$orphan" 2>/dev/null || true + done + for orphan in $(systemctl list-units --all --no-legend --plain 'orphaned-r*' 2>/dev/null | awk '{print $1}'); do + systemctl kill --signal=SIGKILL "$orphan" 2>/dev/null || true + systemctl reset-failed "$orphan" 2>/dev/null || true + done + systemctl stop legit.service 2>/dev/null || true + systemctl stop hung-stop.service 2>/dev/null || true + for i in $(seq -f "%03g" 1 100); 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 + rm -f /run/systemd/system/hung-stop.service + systemctl daemon-reload +} + +# Verify that a JOB_STOP that was pending on the original unit at the moment of +# serialization is preserved on the synthesized orphan unit, so the stop is +# eventually carried out (the orphan is torn down) instead of sitting around +# forever. +test_stop_job_preserved() { + local reload_cmd="${1:?}" + local hung_pid orphan stop_job substate + + echo "" + echo "=========================================" + echo "Testing pending-stop preservation with: systemctl $reload_cmd" + echo "=========================================" + + # Service whose main process traps SIGTERM and never exits, so a "stop" + # request stays pending in the queue and remains serialized. + cat >/run/systemd/system/legit.service <<'EOF' +[Service] +Type=simple +ExecStart=/bin/sleep infinity +EOF + cat >/run/systemd/system/hung-stop.service <<'EOF' +[Service] +Type=notify +NotifyAccess=all +ExecStart=/bin/bash -c 'trap "" TERM; systemd-notify --ready; while :; do sleep infinity & wait $!; done' +TimeoutStopSec=infinity +SendSIGKILL=no +EOF + + systemctl daemon-reload + systemctl start legit.service + systemctl start hung-stop.service + + hung_pid=$(systemctl show -P MainPID hung-stop.service) + if (( hung_pid == 0 )); then + echo "ERROR: hung-stop.service did not start" + return 1 + fi + + # Queue a stop that will hang because the process traps SIGTERM and + # SendSIGKILL=no prevents escalation, so the job stays in the queue and + # is therefore present in the serialized state at reload time. + systemctl --no-block stop hung-stop.service + wait_for_pending_job hung-stop.service + + # Convert hung-stop.service into a symlink to legit.service: on the next + # reload the original unit becomes an alias of legit.service, and its + # serialized state (including the pending stop job) is fed into the + # synthesized orphaned-r* orphan unit. + rm -f /run/systemd/system/hung-stop.service + ln -sf /run/systemd/system/legit.service /run/systemd/system/hung-stop.service + + systemctl "$reload_cmd" + + orphan=$(systemctl list-units --all --no-legend --plain 'orphaned-r*' | awk '{print $1}' | head -n1) + if [[ -z "$orphan" ]]; then + echo "ERROR: no synthesized orphan unit was created" + systemctl list-units --all --no-legend --plain || true + return 1 + fi + echo "Synthesized orphan unit: $orphan" + + # The pending stop job from the original unit must have been carried over + # to the synthesized orphan, otherwise the orphan (and its tracked + # process) is leaked across the reload. Check if: + # - the stop job is still queued, + # - the orphan is in a stop-* sub-state, + # - the orphan has already finished stopping (dead/failed), + # - the orphan was already garbage-collected (no SubState reported). + stop_job=$(systemctl show -P Job "$orphan" 2>/dev/null || true) + substate=$(systemctl show -P SubState "$orphan" 2>/dev/null || true) + if [[ -z "$stop_job" ]] && [[ -n "$substate" ]] && ! [[ "$substate" =~ ^(stop-|dead|failed) ]]; then + echo "ERROR: stop job for original hung-stop.service was not preserved on $orphan!" + echo "Current substate: $substate" + systemctl list-jobs || true + return 1 + fi + + echo "Stop job for original hung-stop.service was correctly preserved on synthesized orphan $orphan" + + # Tear the hung orphan down for the next iteration. + systemctl kill --signal=SIGKILL "$orphan" 2>/dev/null || true + systemctl reset-failed "$orphan" 2>/dev/null || true + if kill -0 "$hung_pid" 2>/dev/null; then + kill -KILL "$hung_pid" 2>/dev/null || true + fi + + rm -f /run/systemd/system/hung-stop.service /run/systemd/system/legit.service + systemctl daemon-reload + + echo "$reload_cmd stop-job preservation test passed" +} + +trap cleanup_test_units EXIT + +run_test daemon-reload +cleanup_test_units +run_test daemon-reexec +cleanup_test_units +run_test daemon-reload with_pending_jobs +cleanup_test_units +run_test daemon-reexec with_pending_jobs +cleanup_test_units +test_stop_job_preserved daemon-reload +cleanup_test_units +test_stop_job_preserved 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.multi-units-transaction.sh b/test/units/TEST-07-PID1.multi-units-transaction.sh new file mode 100755 index 0000000000000..879c128a19782 --- /dev/null +++ b/test/units/TEST-07-PID1.multi-units-transaction.sh @@ -0,0 +1,465 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Operate multiple units in a single transaction. +# Issue: https://github.com/systemd/systemd/issues/8102 +# +# When 'systemctl start ' is executed, both units must be enqueued in a +# single transaction so that After= ordering is honoured regardless of the order +# of the arguments. Previously each unit was sent to PID1 in its own D-Bus +# request and thus its own transaction, which meant the ordering dependency was +# only effective when the dependee was queued before the dependent. + +MARKER_DIR="$(mktemp -d /tmp/issue8102.marker.XXXXXX)" +MARKER="$MARKER_DIR/done" +SOCK_DIR="$(mktemp -d /tmp/issue8102.sock.XXXXXX)" +SOCK_PATH="$SOCK_DIR/sock" + +at_exit() { + set +e + + systemctl stop issue8102-second.service issue8102-first.service + systemctl stop issue8102-sock-foo.service issue8102-sock-foo.socket + systemctl stop 'issue8102-many@*.service' + systemctl stop issue8102-conflict-a.service issue8102-conflict-b.service + systemctl stop issue8102-nop-main.service issue8102-nop-dep.service + systemctl reset-failed issue8102-second.service issue8102-first.service + systemctl reset-failed issue8102-sock-foo.service issue8102-sock-foo.socket + systemctl reset-failed 'issue8102-many@*.service' + systemctl reset-failed issue8102-conflict-a.service issue8102-conflict-b.service + systemctl reset-failed issue8102-nop-main.service issue8102-nop-dep.service + rm -f /run/systemd/system/issue8102-{first,second}.service + rm -f /run/systemd/system/issue8102-sock-foo.{service,socket} + rm -f /run/systemd/system/issue8102-many@.service + rm -f /run/systemd/system/issue8102-conflict-{a,b}.service + rm -f /run/systemd/system/issue8102-nop-{main,dep}.service + rm -rf "$MARKER_DIR" "$SOCK_DIR" + systemctl daemon-reload +} + +trap at_exit EXIT + +mkdir -p /run/systemd/system + +cat >/run/systemd/system/issue8102-first.service < "$MARKER"' +EOF + +cat >/run/systemd/system/issue8102-second.service </run/systemd/system/issue8102-sock-foo.socket </run/systemd/system/issue8102-sock-foo.service </dev/null +# Wait for the units to come back up after the restart. +# shellcheck disable=SC2016 +timeout 30s bash -c ' + while [[ "$(systemctl show -P ActiveState issue8102-sock-foo.service)" != active ]] || + [[ "$(systemctl show -P ActiveState issue8102-sock-foo.socket)" != active ]]; do + sleep 0.5 + done +' +test -S "$SOCK_PATH" + +# --------------------------------------------------------------------------- +# Argument validation corner cases for EnqueueUnitJobMany(). +# --------------------------------------------------------------------------- + +# Empty units array → the handler must reject the call with an INVALID_ARGS +# error before doing anything. +out=$(busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + EnqueueUnitJobMany \ + assst \ + 0 \ + start replace \ + 0 2>&1) && { echo 'busctl unexpectedly succeeded'; exit 1; } +echo "$out" | grep -F "No units specified" >/dev/null + +# Non-zero flags parameter is reserved and must be rejected. +out=$(busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + EnqueueUnitJobMany \ + assst \ + 1 issue8102-first.service \ + start replace \ + 1 2>&1) && { echo 'busctl unexpectedly succeeded'; exit 1; } +echo "$out" | grep -F "Invalid flags parameter" >/dev/null + +# Bogus job type → rejected before any job is constructed. +out=$(busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + EnqueueUnitJobMany \ + assst \ + 1 issue8102-first.service \ + not-a-real-job-type replace \ + 0 2>&1) && { echo 'busctl unexpectedly succeeded'; exit 1; } +echo "$out" | grep -F "Job type not-a-real-job-type invalid" >/dev/null + +# Bogus job mode → rejected before any job is constructed. +out=$(busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + EnqueueUnitJobMany \ + assst \ + 1 issue8102-first.service \ + start not-a-real-mode \ + 0 2>&1) && { echo 'busctl unexpectedly succeeded'; exit 1; } +echo "$out" | grep -F "Job mode not-a-real-mode invalid" >/dev/null + +# Unknown unit must be reported as an error and no partial state should remain +# in the job queue: list one valid + one bogus unit, ensure the call fails and +# that the valid unit has no pending start job afterwards. +systemctl stop issue8102-first.service +[[ "$(systemctl show -P ActiveState issue8102-first.service)" == inactive ]] + +(! busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + EnqueueUnitJobMany \ + assst \ + 2 issue8102-first.service issue8102-does-not-exist.service \ + start replace \ + 0 2>/dev/null) + +# busctl is synchronous: by the time the call returns with an error, PID1 has +# already rejected the transaction. Verify the valid unit was not started +# behind our back (the transaction must be all-or-nothing). +[[ "$(systemctl show -P ActiveState issue8102-first.service)" == inactive ]] + +# --------------------------------------------------------------------------- +# Many units in a single transaction. +# --------------------------------------------------------------------------- +# Build a template unit and instantiate a fair number of instances, all +# enqueued via a single EnqueueUnitJobMany() call to exercise the strv path with +# a transaction that anchors many units at once. + +cat >/run/systemd/system/issue8102-many@.service <<'EOF' +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/bin/true +EOF +systemctl daemon-reload + +MANY_COUNT=20 +mapfile -t MANY_UNITS < <(for i in $(seq 1 "$MANY_COUNT"); do printf 'issue8102-many@%d.service\n' "$i"; done) +MANY_BUSCTL_ARGS=("$MANY_COUNT" "${MANY_UNITS[@]}") + +busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + EnqueueUnitJobMany \ + assst \ + "${MANY_BUSCTL_ARGS[@]}" \ + start replace \ + 0 >/dev/null + +# Wait for every instance to become active. +# shellcheck disable=SC2016 +timeout 30s bash -c ' + for u in "$@"; do + while [[ "$(systemctl show -P ActiveState "$u")" != active ]]; do + sleep 0.2 + done + done +' bash "${MANY_UNITS[@]}" + +# Stop them all in one transaction too, verifying the stop path scales as well. +busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + EnqueueUnitJobMany \ + assst \ + "${MANY_BUSCTL_ARGS[@]}" \ + stop replace \ + 0 >/dev/null + +# shellcheck disable=SC2016 +timeout 30s bash -c ' + for u in "$@"; do + while [[ "$(systemctl show -P ActiveState "$u")" != inactive ]]; do + sleep 0.2 + done + done +' bash "${MANY_UNITS[@]}" + +# --------------------------------------------------------------------------- +# Incompatible transaction: two units that Conflict= with each other cannot be +# started together. Both start anchors would force the other unit to stop, so +# the transaction is unsatisfiable regardless of the chosen job mode. The +# handler must report an error and roll back, so that neither unit ends up +# active. +# --------------------------------------------------------------------------- + +cat >/run/systemd/system/issue8102-conflict-a.service </run/systemd/system/issue8102-conflict-b.service </dev/null) + +# Atomicity: neither unit must end up activated by the failed call. busctl is +# synchronous so by the time it returns PID1 has already rolled back. +[[ "$(systemctl show -P ActiveState issue8102-conflict-a.service)" == inactive ]] +[[ "$(systemctl show -P ActiveState issue8102-conflict-b.service)" == inactive ]] + +# --------------------------------------------------------------------------- +# reload-or-try-restart with a mix of reloadable and non-reloadable units. +# The socket cannot reload so it must be restarted, while a unit that does +# implement reload would be reloaded. Both must be active afterwards. +# --------------------------------------------------------------------------- + +systemctl start issue8102-sock-foo.service issue8102-sock-foo.socket +[[ "$(systemctl show -P ActiveState issue8102-sock-foo.service)" == active ]] +[[ "$(systemctl show -P ActiveState issue8102-sock-foo.socket)" == active ]] + +busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + EnqueueUnitJobMany \ + assst \ + 2 issue8102-sock-foo.service issue8102-sock-foo.socket \ + reload-or-try-restart replace \ + 0 >/dev/null + +# shellcheck disable=SC2016 +timeout 30s bash -c ' + while [[ "$(systemctl show -P ActiveState issue8102-sock-foo.service)" != active ]] || + [[ "$(systemctl show -P ActiveState issue8102-sock-foo.socket)" != active ]]; do + sleep 0.5 + done +' +test -S "$SOCK_PATH" + +# try-restart on a unit that is not currently running must be a no-op (no error) +# and must not start it. +systemctl stop issue8102-first.service +[[ "$(systemctl show -P ActiveState issue8102-first.service)" == inactive ]] + +busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + EnqueueUnitJobMany \ + assst \ + 1 issue8102-first.service \ + try-restart replace \ + 0 >/dev/null + +[[ "$(systemctl show -P ActiveState issue8102-first.service)" == inactive ]] + +# --------------------------------------------------------------------------- +# Regression test: a try-restart anchor that collapses to a NOP job (because +# the unit is inactive) must not crash PID1 when the very same unit is also +# pulled into the transaction as a regular start job by another anchor. +# +# --------------------------------------------------------------------------- + +cat >/run/systemd/system/issue8102-nop-dep.service </run/systemd/system/issue8102-nop-main.service </dev/null <<<"$out" +grep -F "issue8102-nop-main.service" >/dev/null <<<"$out" + +# shellcheck disable=SC2016 +timeout 30s bash -c ' + while [[ "$(systemctl show -P ActiveState issue8102-nop-dep.service)" != active ]] || + [[ "$(systemctl show -P ActiveState issue8102-nop-main.service)" != active ]]; do + sleep 0.5 + done +' + +# Repeat with the two anchors swapped in the array. The per-unit transaction list +# is built incrementally, so the NOP and the regular job end up in the opposite +# order; transaction_drop_nop() must handle both cases. +systemctl stop issue8102-nop-dep.service +[[ "$(systemctl show -P ActiveState issue8102-nop-dep.service)" == inactive ]] +[[ "$(systemctl show -P ActiveState issue8102-nop-main.service)" == active ]] + +out=$(busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + EnqueueUnitJobMany \ + assst \ + 2 issue8102-nop-main.service issue8102-nop-dep.service \ + try-restart replace \ + 0) +grep -F "issue8102-nop-dep.service" >/dev/null <<<"$out" +grep -F "issue8102-nop-main.service" >/dev/null <<<"$out" + +# shellcheck disable=SC2016 +timeout 30s bash -c ' + while [[ "$(systemctl show -P ActiveState issue8102-nop-dep.service)" != active ]] || + [[ "$(systemctl show -P ActiveState issue8102-nop-main.service)" != active ]]; do + sleep 0.5 + done +' + +# Ensure we are still running +systemctl daemon-reload diff --git a/test/units/TEST-07-PID1.nft.sh b/test/units/TEST-07-PID1.nft.sh index dbfefc281f9b4..3713cd6c86a90 100755 --- a/test/units/TEST-07-PID1.nft.sh +++ b/test/units/TEST-07-PID1.nft.sh @@ -24,12 +24,14 @@ systemd-run --unit test-nft.service --service-type=exec -p DynamicUser=yes \ -p 'NFTSet=cgroup:inet:sd_test:c user:inet:sd_test:u group:inet:sd_test:g' sleep 10000 run nft list set inet sd_test c grep -qF "test-nft.service" "$RUN_OUT" -uid=$(userdbctl user --json=short test-nft | jq .uid) -run nft list set inet sd_test u -grep -qF "$uid" "$RUN_OUT" -gid=$(userdbctl user --json=short test-nft | jq .gid) -run nft list set inet sd_test g -grep -qF "$gid" "$RUN_OUT" +if command -v userdbctl >/dev/null; then + uid=$(userdbctl user --json=short test-nft | jq .uid) + run nft list set inet sd_test u + grep -qF "$uid" "$RUN_OUT" + gid=$(userdbctl user --json=short test-nft | jq .gid) + run nft list set inet sd_test g + grep -qF "$gid" "$RUN_OUT" +fi systemctl stop test-nft.service # scope diff --git a/test/units/TEST-07-PID1.reload-count.sh b/test/units/TEST-07-PID1.reload-count.sh new file mode 100755 index 0000000000000..a41a4e467d233 --- /dev/null +++ b/test/units/TEST-07-PID1.reload-count.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Verify that the manager exposes a ReloadCount property that increments on +# every daemon-reload, resets to zero across daemon-reexec (since the count +# is not serialized), and is reachable over D-Bus, Varlink Describe, and the +# io.systemd.Metrics interface (queried via systemd-report). + +# systemd-report silently returns empty if the metrics source is missing, +# which would falsely pass the cross-checks below. Assert the socket exists +# so any failure points at the real problem. +test -S /run/systemd/report/io.systemd.Manager + +read_count_dbus() { + busctl -j get-property org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + ReloadCount | jq -r '.data' +} + +read_count_varlink() { + varlinkctl call /run/systemd/io.systemd.Manager \ + io.systemd.Manager.Describe '{}' | jq -r '.runtime.ReloadCount' +} + +read_count_report() { + local out + # Strip the RS separator that jq --seq re-emits on output. + out=$(/usr/lib/systemd/systemd-report metrics --json=short \ + io.systemd.Manager.ReloadCount \ + | jq --seq -r 'select(.name == "io.systemd.Manager.ReloadCount") | .value' \ + | tr -d '\036') + [[ -n "$out" ]] || { echo "ReloadCount metric missing from systemd-report output" >&2; return 1; } + echo "$out" +} + +# Sanity: all three transports must agree. +dbus_count=$(read_count_dbus) +varlink_count=$(read_count_varlink) +report_count=$(read_count_report) +(( dbus_count == varlink_count )) +(( dbus_count == report_count )) + +# A single reload bumps the counter by one. +before=$(read_count_dbus) +systemctl daemon-reload +(( $(read_count_dbus) == before + 1 )) + +# Multiple reloads accumulate. +systemctl daemon-reload +systemctl daemon-reload +(( $(read_count_dbus) == before + 3 )) + +# And all three transports still agree after the reload. +dbus_count=$(read_count_dbus) +varlink_count=$(read_count_varlink) +report_count=$(read_count_report) +(( dbus_count == varlink_count )) +(( dbus_count == report_count )) + +# A daemon-reexec resets the counter back to zero on all three transports, +# since the counter lives only in memory and is not carried across the reexec. +# `systemctl daemon-reexec` returns as soon as the old PID 1 closes its bus +# connection, which is before the new PID 1 has rebound /run/systemd/private. +# Use --watch-bind=yes to block on inotify until the new socket is live. +systemctl daemon-reexec +busctl --watch-bind=yes call org.freedesktop.systemd1 /org/freedesktop/systemd1 \ + org.freedesktop.DBus.Peer Ping >/dev/null + +(( $(read_count_dbus) == 0 )) +(( $(read_count_varlink) == 0 )) +(( $(read_count_report) == 0 )) diff --git a/test/units/TEST-07-PID1.socket-defer.sh b/test/units/TEST-07-PID1.socket-defer.sh index 304c3bce3d90c..d8a8ff02e0499 100755 --- a/test/units/TEST-07-PID1.socket-defer.sh +++ b/test/units/TEST-07-PID1.socket-defer.sh @@ -60,7 +60,9 @@ wait_for_start() { wait_for_stop() { timeout 30 bash -c "while systemctl -q is-active '$UNIT_NAME.service'; do sleep .5; done" - assert_eq "$(systemctl show "$UNIT_NAME.socket" -P SubState)" "listening" + # The socket's SubState transitions from 'running' to 'listening' shortly after the triggered + # service becomes inactive, so wait for that transition instead of checking once and racing. + timeout 30 bash -c "until [[ \$(systemctl show '$UNIT_NAME.socket' -P SubState) == 'listening' ]]; do sleep .5; done" } # DeferTrigger=no: job mode replace diff --git a/test/units/TEST-07-PID1.socket-on-failure.sh b/test/units/TEST-07-PID1.socket-on-failure.sh index affcec44ff265..ff947b10e2675 100755 --- a/test/units/TEST-07-PID1.socket-on-failure.sh +++ b/test/units/TEST-07-PID1.socket-on-failure.sh @@ -55,7 +55,7 @@ systemctl is-failed "$UNIT_NAME.socket" assert_eq "$(systemctl show "$UNIT_NAME.socket" -P SubState)" "failed" [[ ! -e "/tmp/$UNIT_NAME/test" ]] -timeout 10 bash -c "until systemctl is-failed TEST-07-PID1-socket-OnFailure.service; do sleep .5; done" +timeout --foreground 60 bash -c "until systemctl is-failed TEST-07-PID1-socket-OnFailure.service; do sleep .5; done" chattr -i "/tmp/$UNIT_NAME" @@ -65,9 +65,8 @@ mkdir "/tmp/$UNIT_NAME/test" systemctl is-failed "$UNIT_NAME.socket" assert_eq "$(systemctl show "$UNIT_NAME.socket" -P SubState)" "failed" -timeout 10 bash -c "while [[ -d '/tmp/$UNIT_NAME/test' ]]; do sleep .5; done" +timeout --foreground 60 bash -c "until systemctl is-active TEST-07-PID1-socket-OnFailure.service; do sleep .5; done" [[ ! -e "/tmp/$UNIT_NAME/test" ]] -systemctl is-active TEST-07-PID1-socket-OnFailure.service systemctl start "$UNIT_NAME.socket" systemctl is-active "$UNIT_NAME.socket" 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-07-PID1.transient-unit-container.sh b/test/units/TEST-07-PID1.transient-unit-container.sh index 03ffa91a7025c..af2e1e9315ff1 100755 --- a/test/units/TEST-07-PID1.transient-unit-container.sh +++ b/test/units/TEST-07-PID1.transient-unit-container.sh @@ -112,7 +112,7 @@ config_container_service() { mkdir -p "$container_systemd_dir" # Generate a phony random machine-id for the container - uuidgen -r | tr -d '-' | tr '[:upper:]' '[:lower:]' > "${CONTAINER_ROOT_FS}/etc/machine-id" + uuidgen -r | tr -d '-' | tr '[:upper:]' '[:lower:]' >"${CONTAINER_ROOT_FS}/etc/machine-id" cat <"$internal_test_service" [Unit] @@ -121,7 +121,7 @@ After=basic.target [Service] Type=oneshot -ExecStart=bash -c 'echo "$EXPECTED_OUTPUT" > "$guest_output"' +ExecStart=bash -c 'echo "$EXPECTED_OUTPUT" >"$guest_output"' ExecStartPost=systemctl --no-block exit 0 TimeoutStopSec=15s diff --git a/test/units/TEST-07-PID1.user-namespace-path.sh b/test/units/TEST-07-PID1.user-namespace-path.sh index 437968e8c2531..6d64a52bf88de 100755 --- a/test/units/TEST-07-PID1.user-namespace-path.sh +++ b/test/units/TEST-07-PID1.user-namespace-path.sh @@ -6,47 +6,50 @@ set -o pipefail # shellcheck source=test/units/util.sh . "$(dirname "$0")"/util.sh -# When sanitizers are used, export LD_PRELOAD with the sanitizers path, -# lsns doesn't work otherwise. -if [ -f /usr/lib/systemd/systemd-asan-env ]; then - # shellcheck source=/dev/null - . /usr/lib/systemd/systemd-asan-env - export LD_PRELOAD - export ASAN_OPTIONS -fi - # Only reuse the user namespace -systemd-run --unit=oldservice --property=Type=exec --property=PrivateUsers=true sleep 3600 +systemd-run --unit=oldservice --property=Type=notify --property=NotifyAccess=all --property=PrivateUsers=true bash -c 'systemd-notify --ready; exec sleep 3600' OLD_PID=$(systemctl show oldservice -p MainPID | awk -F= '{print $2}') -systemd-run --unit=newservice --property=Type=exec --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=PrivateNetwork=true sleep 3600 +systemd-run --unit=newservice --property=Type=notify --property=NotifyAccess=all --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=PrivateNetwork=true bash -c 'systemd-notify --ready; exec sleep 3600' NEW_PID=$(systemctl show newservice -p MainPID | awk -F= '{print $2}') -assert_neq "$(lsns -p "$OLD_PID" -o NS -t net -n)" "$(lsns -p "$NEW_PID" -o NS -t net -n)" -assert_eq "$(lsns -p "$OLD_PID" -o NS -t user -n)" "$(lsns -p "$NEW_PID" -o NS -t user -n)" +OLD_NETNS="$(readlink -v /proc/"$OLD_PID"/ns/net)" +NEW_NETNS="$(readlink -v /proc/"$NEW_PID"/ns/net)" +assert_neq "$OLD_NETNS" "$NEW_NETNS" +OLD_USERNS="$(readlink -v /proc/"$OLD_PID"/ns/user)" +NEW_USERNS="$(readlink -v /proc/"$NEW_PID"/ns/user)" +assert_eq "$OLD_USERNS" "$NEW_USERNS" systemctl stop oldservice newservice # Reuse the user and network namespaces -systemd-run --unit=oldservice --property=Type=exec --property=PrivateUsers=true --property=PrivateNetwork=true sleep 3600 +systemd-run --unit=oldservice --property=Type=notify --property=NotifyAccess=all --property=PrivateUsers=true --property=PrivateNetwork=true bash -c 'systemd-notify --ready; exec sleep 3600' OLD_PID=$(systemctl show oldservice -p MainPID | awk -F= '{print $2}') -systemd-run --unit=newservice --property=Type=exec --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=NetworkNamespacePath=/proc/"$OLD_PID"/ns/net sleep 3600 +systemd-run --unit=newservice --property=Type=notify --property=NotifyAccess=all --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=NetworkNamespacePath=/proc/"$OLD_PID"/ns/net bash -c 'systemd-notify --ready; exec sleep 3600' NEW_PID=$(systemctl show newservice -p MainPID | awk -F= '{print $2}') -assert_eq "$(lsns -p "$OLD_PID" -o NS -t net -n)" "$(lsns -p "$NEW_PID" -o NS -t net -n)" -assert_eq "$(lsns -p "$OLD_PID" -o NS -t user -n)" "$(lsns -p "$NEW_PID" -o NS -t user -n)" +OLD_NETNS="$(readlink -v /proc/"$OLD_PID"/ns/net)" +NEW_NETNS="$(readlink -v /proc/"$NEW_PID"/ns/net)" +assert_eq "$OLD_NETNS" "$NEW_NETNS" +OLD_USERNS="$(readlink -v /proc/"$OLD_PID"/ns/user)" +NEW_USERNS="$(readlink -v /proc/"$NEW_PID"/ns/user)" +assert_eq "$OLD_USERNS" "$NEW_USERNS" systemctl stop oldservice newservice # Delegate the network namespace -systemd-run --unit=oldservice --property=Type=exec --property=PrivateUsers=true sleep 3600 +systemd-run --unit=oldservice --property=Type=notify --property=NotifyAccess=all --property=PrivateUsers=true bash -c 'systemd-notify --ready; exec sleep 3600' OLD_PID=$(systemctl show oldservice -p MainPID | awk -F= '{print $2}') -systemd-run --unit=newservice --property=Type=exec --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=DelegateNamespaces=net --property=PrivateNetwork=true sleep 3600 +systemd-run --unit=newservice --property=Type=notify --property=NotifyAccess=all --property=UserNamespacePath=/proc/"$OLD_PID"/ns/user --property=DelegateNamespaces=net --property=PrivateNetwork=true bash -c 'systemd-notify --ready; exec sleep 3600' NEW_PID=$(systemctl show newservice -p MainPID | awk -F= '{print $2}') -assert_neq "$(lsns -p "$OLD_PID" -o NS -t net -n)" "$(lsns -p "$NEW_PID" -o NS -t net -n)" -assert_eq "$(lsns -p "$OLD_PID" -o NS -t user -n)" "$(lsns -p "$NEW_PID" -o NS -t user -n)" +OLD_NETNS="$(readlink -v /proc/"$OLD_PID"/ns/net)" +NEW_NETNS="$(readlink -v /proc/"$NEW_PID"/ns/net)" +assert_neq "$OLD_NETNS" "$NEW_NETNS" +OLD_USERNS="$(readlink -v /proc/"$OLD_PID"/ns/user)" +NEW_USERNS="$(readlink -v /proc/"$NEW_PID"/ns/user)" +assert_eq "$OLD_USERNS" "$NEW_USERNS" systemctl stop oldservice newservice diff --git a/test/units/TEST-08-INITRD.sh b/test/units/TEST-08-INITRD.sh index b59a5b99ffe63..7aa2a7e60d353 100755 --- a/test/units/TEST-08-INITRD.sh +++ b/test/units/TEST-08-INITRD.sh @@ -22,8 +22,8 @@ test -d /run/initrd-mount-target mountpoint /run/initrd-mount-target [[ -e /run/initrd-mount-target/hello-world ]] -# Copy the prepared exitrd to its intended location. -mkdir -p /run/initramfs -unzstd --stdout /exitrd | cpio --extract --make-directories --directory /run/initramfs/ +# The initrd-run-initramfs.service in the initrd should have populated /run/initramfs +# from the initrd's own contents before switch-root. +test -x /run/initramfs/shutdown touch /testok diff --git a/test/units/TEST-13-NSPAWN.importctl.sh b/test/units/TEST-13-NSPAWN.importctl.sh index 7eae005f85c73..88fde60d02a13 100755 --- a/test/units/TEST-13-NSPAWN.importctl.sh +++ b/test/units/TEST-13-NSPAWN.importctl.sh @@ -71,8 +71,8 @@ varlinkctl call --more /run/systemd/io.systemd.Import io.systemd.Import.ListTran varlinkctl call --more /run/systemd/io.systemd.Import io.systemd.Import.Pull '{"class":"confext","remote":"file:///var/tmp/importtest.tar.gz","local":"importtest8","type":"tar","verify":"no"}' cmp /var/tmp/importtest /var/lib/confexts/importtest8/importtest -echo -n "systemd.pull=tar,confext,verify=no:importtest9:file:///var/tmp/importtest.tar.gz " > "$TEST_CMDLINE" -cat /proc/cmdline >> "$TEST_CMDLINE" +echo -n "systemd.pull=tar,confext,verify=no:importtest9:file:///var/tmp/importtest.tar.gz " >"$TEST_CMDLINE" +cat /proc/cmdline >>"$TEST_CMDLINE" mount --bind "$TEST_CMDLINE" /proc/cmdline cat /proc/cmdline diff --git a/test/units/TEST-13-NSPAWN.machined.sh b/test/units/TEST-13-NSPAWN.machined.sh index 34307f3c8b162..6b07137ef811f 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 @@ -114,6 +120,7 @@ machinectl disable long-running long-running long-running container1 [[ "$(TERM=dumb machinectl shell testuser@ /usr/bin/bash -c 'echo -ne $FOO')" == "" ]] [[ "$(TERM=dumb machinectl shell --setenv=FOO=bar testuser@ /usr/bin/bash -c 'echo -ne $FOO')" == "bar" ]] +[[ "$(TERM=dumb machinectl shell testuser@ --setenv=FOO=bar /usr/bin/bash -c 'echo -ne $FOO')" == "bar" ]] [[ "$(machinectl show --property=State --value long-running)" == "running" ]] # Equivalent to machinectl kill --signal=SIGRTMIN+4 --kill-whom=leader @@ -332,11 +339,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..4c9173bfe03d1 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 +942,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 @@ -1142,6 +1191,21 @@ matrix_run_one() { ip a | grep -v -E '^1: lo.*UP' ip netns del nspawn_test + # test --network-namespace-path works when combined with --private-users=pick + ip netns add nspawn_test + ip netns exec nspawn_test ip link add foo type dummy + + if [[ "$IS_USERNS_SUPPORTED" == "yes" && "$api_vfs_writable" == "no" ]]; then + SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \ + systemd-nspawn --register=no \ + --directory="$root" \ + --private-users=pick \ + --network-namespace-path=/run/netns/nspawn_test \ + ip link show dev foo + fi + + ip netns del nspawn_test + rm -fr "$root" return 0 @@ -1307,7 +1371,10 @@ testcase_unpriv() { } testcase_fuse() { - if [[ "$(cat <>/dev/fuse 2>&1)" != 'cat: -: Operation not permitted' ]]; then + # On some kernels reading from /dev/fuse without an attached connection blocks indefinitely + # rather than returning EPERM, so guard the probe with a short timeout and skip the test + # whenever we don't get the expected error string. + if [[ "$(timeout --foreground 5 cat <>/dev/fuse 2>&1)" != 'cat: -: Operation not permitted' ]]; then echo "FUSE is not supported, skipping the test..." return 0 fi @@ -1338,7 +1405,7 @@ testcase_fuse() { # "cat: -: Operation not permitted" # pass the test; opened but not read # "bash: line 1: /dev/fuse: Operation not permitted" # fail the test; could not open # "" # fail the test; reading worked - [[ "$(systemd-nspawn --register=no --pipe --directory="$root" \ + [[ "$(timeout --foreground 30 systemd-nspawn --register=no --pipe --directory="$root" \ bash -c 'cat <>/dev/fuse' 2>&1)" == 'cat: -: Operation not permitted' ]] rm -fr "$root" @@ -1347,7 +1414,7 @@ testcase_fuse() { testcase_unpriv_fuse() { # Same as above, but for unprivileged operation. - if [[ "$(cat <>/dev/fuse 2>&1)" != 'cat: -: Operation not permitted' ]]; then + if [[ "$(timeout --foreground 5 cat <>/dev/fuse 2>&1)" != 'cat: -: Operation not permitted' ]]; then echo "FUSE is not supported, skipping the test..." return 0 fi @@ -1366,7 +1433,7 @@ testcase_unpriv_fuse() { create_dummy_ddi "$tmpdir" "$name" chown --recursive testuser: "$tmpdir" - [[ "$(run0 -u testuser --pipe systemd-run \ + [[ "$(timeout --foreground 60 run0 -u testuser --pipe systemd-run \ --user \ --pipe \ --property=Delegate=yes \ @@ -1458,7 +1525,7 @@ testcase_link_journal_host() { root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.link-journal.XXX)" create_dummy_container "$root" - systemd-id128 new > "$root"/etc/machine-id + systemd-id128 new >"$root"/etc/machine-id hoge="/var/log/journal/$(cat "$root"/etc/machine-id)/" mkdir -p "$hoge" @@ -1515,6 +1582,47 @@ testcase_volatile_link_journal_no_userns() { rm -fr "$root" "$journal_dir" } +testcase_boot_param_split() { + local root outdir + + root="$(mktemp -d /var/lib/machines/TEST-13-NSPAWN.boot-param-split.XXX)" + outdir="$(mktemp -d)" + create_dummy_container "$root" + + # Replace the init binary with a stub that records the argv and environment nspawn passes to it, + # so we can verify that kernel-cmdline-style KEY=VALUE arguments are split between PID 1's + # environment and argv the same way the kernel splits them. + mkdir -p "$root/usr/lib/systemd" + cat >"$root/usr/lib/systemd/systemd" <<'EOF' +#!/bin/bash +set -e +printf '%s\n' "$@" >/output/argv +env >/output/env +EOF + chmod +x "$root/usr/lib/systemd/systemd" + + # Cover the assignments that should land in env (FOO=bar, baz-qux=hello → baz_qux), the + # dotted assignments that should stay as argv (systemd.unit=…, some.thing=…), and the malformed + # entries that look env-like but must also stay as argv: empty key (=value), key starting with + # a digit (123=foo), key with characters that aren't valid in an env var name (foo!=bar). + systemd-nspawn --register=no \ + --directory="$root" \ + --bind="$outdir:/output" \ + --boot \ + FOO=bar baz-qux=hello systemd.unit=foo.target some.thing=yes plain-arg \ + =empty-key 123=leading-digit 'foo!=bad-char' + + diff <(printf 'systemd.unit=foo.target\nsome.thing=yes\nplain-arg\n=empty-key\n123=leading-digit\nfoo!=bad-char\n') "$outdir/argv" + grep '^FOO=bar$' >/dev/null "$outdir/env" + grep '^baz_qux=hello$' >/dev/null "$outdir/env" + (! grep -E '^(systemd\.unit|some\.thing)=' >/dev/null "$outdir/env") + (! grep -E '^(FOO|baz_qux)=' >/dev/null "$outdir/argv") + (! grep -E '^(123|foo!?)=' >/dev/null "$outdir/env") + (! grep -E '^=' >/dev/null "$outdir/env") + + rm -fr "$root" "$outdir" +} + testcase_cap_net_bind_service() { local root diff --git a/test/units/TEST-13-NSPAWN.pull-oci.sh b/test/units/TEST-13-NSPAWN.pull-oci.sh index e673b711f0fe4..b2a6382377a8d 100755 --- a/test/units/TEST-13-NSPAWN.pull-oci.sh +++ b/test/units/TEST-13-NSPAWN.pull-oci.sh @@ -101,7 +101,7 @@ EOF cat /var/tmp/pull-oci-test/v2/ocicombo/manifests/latest jq < /var/tmp/pull-oci-test/v2/ocicombo/manifests/latest -cat > /usr/lib/systemd/oci-registry/registry.localfile.oci-registry </usr/lib/systemd/oci-registry/registry.localfile.oci-registry </dev/null ls -alR /home/testuser/.local/state/machines/ocicombo.mstack -run0 -u testuser systemd-nspawn -q --pipe -M ocicombo /sbin/init | grep -q luftikus +run0 -u testuser systemd-nspawn -q --pipe -M ocicombo /sbin/init | grep luftikus >/dev/null -run0 -u testuser --pipe systemd-run -q --unit=fimpel --user -p PrivateUsers=managed -p RootMStack=/home/testuser/.local/state/machines/ocicombo.mstack --pipe /sbin/init | grep -q luftikus +run0 -u testuser --pipe systemd-run -q --unit=fimpel --user -p PrivateUsers=managed -p RootMStack=/home/testuser/.local/state/machines/ocicombo.mstack --pipe /sbin/init | grep luftikus >/dev/null run0 -u testuser machinectl list-images -a --user diff --git a/test/units/TEST-13-NSPAWN.unpriv.sh b/test/units/TEST-13-NSPAWN.unpriv.sh index cbf332aa22093..f51de0018e494 100755 --- a/test/units/TEST-13-NSPAWN.unpriv.sh +++ b/test/units/TEST-13-NSPAWN.unpriv.sh @@ -22,15 +22,22 @@ at_exit() { rm -rf /home/testuser/.local/state/machines/inodetest ||: rm -rf /home/testuser/.local/state/machines/inodetest2 ||: rm -rf /home/testuser/.local/state/machines/mangletest ||: + rm -rf /home/testuser/.local/state/machines/fdstore ||: machinectl terminate zurps ||: + machinectl terminate exfiltrate ||: + systemctl --user --machine testuser@ stop exfiltrate.service ||: + systemctl --user --machine testuser@ stop systemd-nspawn@fdstore.service ||: rm -f /etc/polkit-1/rules.d/registermachinetest.rules machinectl terminate nurps ||: machinectl terminate kurps ||: machinectl terminate wumms ||: machinectl terminate wamms ||: + machinectl terminate fdstore ||: 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 + loginctl disable-linger testuser } trap at_exit EXIT @@ -61,7 +68,7 @@ EOF loginctl enable-linger testuser run0 -u testuser mkdir -p .config/systemd/nspawn/ -run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" > .config/systemd/nspawn/zurps.nspawn" +run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" >.config/systemd/nspawn/zurps.nspawn" run0 -u testuser systemctl start --user systemd-nspawn@zurps.service machinectl status zurps @@ -104,18 +111,98 @@ 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 -run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" > .config/systemd/nspawn/kurps.nspawn" +run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" >.config/systemd/nspawn/kurps.nspawn" run0 -u testuser systemctl start --user systemd-nspawn@kurps.service machinectl terminate kurps -run0 -u testuser -D /var/tmp/image-tar/ bash -c 'sha256sum kurps.tar.gz > SHA256SUMS' +run0 -u testuser -D /var/tmp/image-tar/ bash -c 'sha256sum kurps.tar.gz >SHA256SUMS' run0 -u testuser importctl --user pull-tar file:///var/tmp/image-tar/kurps.tar.gz nurps --verify=checksum -m -run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" > .config/systemd/nspawn/nurps.nspawn" +run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" >.config/systemd/nspawn/nurps.nspawn" run0 -u testuser systemctl start --user systemd-nspawn@nurps.service machinectl terminate nurps @@ -148,7 +235,7 @@ assert_in 'wamms' "$(run0 -u testuser machinectl --user list-images)" run0 -u testuser machinectl --user image-status wamms run0 -u testuser machinectl --user show-image wamms -run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" > .config/systemd/nspawn/wamms.nspawn" +run0 -u testuser -i "echo -e \"[Exec]\nKillSignal=SIGKILL\n\" >.config/systemd/nspawn/wamms.nspawn" run0 -u testuser systemctl start --user systemd-nspawn@wamms.service run0 -u testuser systemctl stop --user systemd-nspawn@zurps.service @@ -224,4 +311,101 @@ tar -C /var/tmp/mangletest/ -cvzf /var/tmp/mangletest.tar.gz mangletest-0.1 run0 --pipe -u testuser importctl -m --user import-tar /var/tmp/mangletest.tar.gz cmp /var/tmp/mangletest/mangletest-0.1/usr/lib/os-release /home/testuser/.local/state/machines/mangletest/usr/lib/os-release -loginctl disable-linger testuser +# Verify the fd-store preservation chain works end-to-end across: +# payload (inside container) -> systemd-nspawn (user manager) -> user manager +# -> system PID 1 (user@.service fd store) +# Then restart the nspawn service and verify the inner payload actually +# receives the preserved fds back via LISTEN_FDS, with their original content. +create_dummy_container /home/testuser/.local/state/machines/fdstore +if [[ ! -x /home/testuser/.local/state/machines/fdstore/usr/bin/test-fdstore ]]; then + echo >&2 "test-fdstore not available in the minimal container, skipping fdstore tests" + exit 0 +fi +# The container init execs the helper directly so the FDSTORE notification is +# sent from PID 1 (nspawn rejects notify messages from anyone but the inner +# payload's init). The helper itself execs sleep on success to keep the +# container alive, and on failure it exits non-zero making the systemd-nspawn +# service fail. +cat >/home/testuser/.local/state/machines/fdstore/sbin/init <<'EOF' +#!/usr/bin/env bash +set -e +if [[ "${LISTEN_FDS:-0}" -gt 0 ]]; then + exec /usr/bin/test-fdstore check +else + exec /usr/bin/test-fdstore store +fi +EOF +chmod +x /home/testuser/.local/state/machines/fdstore/sbin/init +systemd-dissect --shift /home/testuser/.local/state/machines/fdstore foreign + +run0 -u testuser mkdir -p .config/systemd/nspawn/ +run0 -u testuser -i "cat >.config/systemd/nspawn/fdstore.nspawn <.config/systemd/user/systemd-nspawn@fdstore.service.d/fdstore.conf < nspawn (user-side systemd-nspawn@fdstore.service fd store) +n_nspawn_fds=$(run0 -u testuser systemctl --user show -P NFileDescriptorStore systemd-nspawn@fdstore.service) +test "${n_nspawn_fds}" -ge 2 + +# 2) nspawn -> user manager -> system PID 1 (user@.service fd store) +TESTUSER_UID=$(id -u testuser) +timeout 30s bash -c \ + "until [[ \"\$(systemctl show -P NFileDescriptorStore user@${TESTUSER_UID}.service)\" -ge 2 ]]; do sleep 0.5; done" +n_user_at_fds=$(systemctl show -P NFileDescriptorStore "user@${TESTUSER_UID}.service") +test "${n_user_at_fds}" -ge 2 + +# 3) Stop the nspawn service: payload is gone but FileDescriptorStorePreserve=on-success +# must keep the fds in the user-side fdstore (and propagated copy in PID 1). +run0 -u testuser systemctl --user stop systemd-nspawn@fdstore.service +n_nspawn_fds=$(run0 -u testuser systemctl --user show -P NFileDescriptorStore systemd-nspawn@fdstore.service) +test "${n_nspawn_fds}" -ge 2 + +# 4) Restart the service: nspawn must receive the preserved fds via LISTEN_FDS +# and forward them into the inner payload, which verifies the content matches. +run0 -u testuser systemctl start --user systemd-nspawn@fdstore.service +run0 -u testuser systemctl is-active --user systemd-nspawn@fdstore.service + +# 5) Stop the nspawn service and the user session +run0 -u testuser systemctl --user stop systemd-nspawn@fdstore.service +n_nspawn_fds=$(run0 -u testuser systemctl --user show -P NFileDescriptorStore systemd-nspawn@fdstore.service) +test "${n_nspawn_fds}" -ge 2 +systemctl stop "user@${TESTUSER_UID}.service" +n_user_at_fds=$(systemctl show -P NFileDescriptorStore "user@${TESTUSER_UID}.service") +test "${n_user_at_fds}" -ge 2 + +# 6) Restart the user session and container payload +systemctl start "user@${TESTUSER_UID}.service" +timeout 30s bash -c \ + "until systemctl is-active 'user@${TESTUSER_UID}.service' >/dev/null; do sleep 0.5; done" +run0 -u testuser systemctl --user start systemd-nspawn@fdstore.service +run0 -u testuser systemctl is-active --user systemd-nspawn@fdstore.service + +# 7) Failure case: with FileDescriptorStorePreserve=on-success, the fdstore must +# be dropped once the unit enters the permanent failed state (i.e. once all +# automated restart attempts driven by Restart= are exhausted). The +# systemd-nspawn@.service template doesn't set Restart=, so killing the inner +# payload with SIGKILL forces the unit straight into 'failed'. +timeout 30s bash -c \ + "until [[ \"\$(run0 -u testuser systemctl --user show -P NFileDescriptorStore systemd-nspawn@fdstore.service)\" -ge 2 ]]; do sleep 0.5; done" +run0 -u testuser systemctl --user kill --kill-whom=all -s SIGKILL systemd-nspawn@fdstore.service +timeout 30s bash -c \ + "until [[ \"\$(run0 -u testuser systemctl --user show -P ActiveState systemd-nspawn@fdstore.service)\" == failed ]]; do sleep 0.5; done" +# The fdstore must be discarded once the failed state is reached. +assert_eq "$(run0 -u testuser systemctl --user show -P NFileDescriptorStore systemd-nspawn@fdstore.service)" 0 +assert_eq "$(run0 -u testuser systemctl --user show -P SubState systemd-nspawn@fdstore.service)" failed +run0 -u testuser systemctl --user reset-failed systemd-nspawn@fdstore.service + +machinectl terminate fdstore 2>/dev/null || true diff --git a/test/units/TEST-17-UDEV.global-property.sh b/test/units/TEST-17-UDEV.global-property.sh index c9b070e2036ad..d1648ab9241e2 100755 --- a/test/units/TEST-17-UDEV.global-property.sh +++ b/test/units/TEST-17-UDEV.global-property.sh @@ -31,7 +31,7 @@ trap cleanup EXIT rules="/run/udev/rules.d/99-test-17.global-property.rules" mkdir -p "${rules%/*}" -cat > "$rules" <<'EOF' +cat >"$rules" <<'EOF' ENV{FOO}=="?*", ENV{PROP_FOO}="$env{FOO}" ENV{BAR}=="?*", ENV{PROP_BAR}="$env{BAR}" EOF diff --git a/test/units/TEST-17-UDEV.irq-affinity.sh b/test/units/TEST-17-UDEV.irq-affinity.sh new file mode 100755 index 0000000000000..863fc91a39b69 --- /dev/null +++ b/test/units/TEST-17-UDEV.irq-affinity.sh @@ -0,0 +1,429 @@ +#!/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 IRQAffinityPolicy configuration + +# Find a network interface with MSI IRQs (typically virtio-net in the VM) +# We need a real device to test actual IRQ affinity application +find_interface_with_msi_irqs() { + for iface in /sys/class/net/*; do + [[ -e "$iface" ]] || continue + iface_name=$(basename "$iface") + [[ "$iface_name" == "lo" ]] && continue + msi_irqs_path="$iface/device/msi_irqs" + if [[ -d "$msi_irqs_path" ]] && [[ -n "$(ls -A "$msi_irqs_path" 2>/dev/null)" ]]; then + echo "$iface_name" + return 0 + fi + done + return 1 +} + +get_interface_irqs() { + local iface="$1" + local msi_irqs_path="/sys/class/net/$iface/device/msi_irqs" + if [[ -d "$msi_irqs_path" ]]; then + ls "$msi_irqs_path" + fi +} + +check_irq_affinity() { + local irq="$1" + local expected_mask="$2" + local affinity + affinity=$(cat "/proc/irq/$irq/smp_affinity") + # Remove leading zeros and commas for comparison + affinity=$(echo "$affinity" | sed 's/^[0,]*//;s/,//g') + expected_mask=$(echo "$expected_mask" | sed 's/^[0,]*//;s/,//g') + [[ "$affinity" == "$expected_mask" ]] +} + +# Test 1: Verify IRQ affinity is actually applied on a real device +if iface=$(find_interface_with_msi_irqs); then + echo "Found interface with MSI IRQs: $iface" + + # Get the MAC address of the interface + mac=$(cat "/sys/class/net/$iface/address") + + # Get IRQs before applying policy + irqs=$(get_interface_irqs "$iface") + echo "Interface $iface has IRQs: $irqs" + + # Test 1a: test single policy + mkdir -p /run/systemd/network/ + cat >/run/systemd/network/00-test-irq-affinity.link </run/systemd/network/00-test-irq-affinity.link <1 CPU and >1 IRQ)" + fi + + # Test 1c: Test IRQAffinity= CPU filtering with single policy + # Pin to CPU 1 instead of default CPU 0 + cat >/run/systemd/network/00-test-irq-affinity.link <1 CPU)" + fi + + # Test 1d: Test IRQAffinity= with spread policy (restrict to subset of CPUs) + if [[ "$n_cpus" -ge 4 ]] && [[ "$irq_count" -gt 1 ]]; then + cat >/run/systemd/network/00-test-irq-affinity.link <=4 CPUs and >1 IRQ)" + fi + + # Test 1e: Test IRQAffinityNUMA= if NUMA is available + if [[ -d /sys/devices/system/node/node0 ]]; then + # Get CPUs on NUMA node 0 + numa0_cpus=$(cat /sys/devices/system/node/node0/cpulist) + echo "NUMA node 0 has CPUs: $numa0_cpus" + + cat >/run/systemd/network/00-test-irq-affinity.link </run/systemd/network/00-test-irq-affinity.link </dev/null; then + echo "Empty intersection correctly detected and logged" + else + echo "Note: Empty intersection test - check journal for error message" + fi + else + echo "Skipping empty intersection test (need 2 NUMA nodes)" + fi + + # Cleanup + rm -f /run/systemd/network/00-test-irq-affinity.link + udevadm control --reload +else + echo "No interface with MSI IRQs found, skipping actual IRQ affinity test" +fi + +# Test 2: Config parsing with dummy interfaces (no MSI IRQs) +mkdir -p /run/systemd/network/ +cat >/run/systemd/network/10-test-irq.link <&1) +assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq.link" "$output" + +# Test 3: Invalid policy values are rejected/warned +cat >/run/systemd/network/10-test-irq-invalid.link </run/systemd/network/10-test-irq-empty.link </run/systemd/network/10-test-irq-affinity-cpus.link <&1) +assert_in "ID_NET_LINK_FILE=/run/systemd/network/10-test-irq-affinity-cpus.link" "$output" + +# Test 6: IRQAffinityNUMA= config parsing +cat >/run/systemd/network/10-test-irq-affinity-numa.link </run/systemd/network/10-test-irq-affinity-numa-explicit.link </run/systemd/network/10-test-irq-affinity-combined.link < "$rules" <<'EOF' +cat >"$rules" <<'EOF' SUBSYSTEM!="mem", GOTO="end" KERNEL!="null", GOTO="end" ACTION=="remove", GOTO="end" @@ -19,7 +19,7 @@ ACTION=="remove", GOTO="end" IMPORT{db}="INVOCATIONS" IMPORT{program}="/usr/bin/bash -c 'systemctl show --property=InvocationID systemd-udevd.service'" ENV{INVOCATIONS}+="%E{ACTION}_%E{SEQNUM}_%E{InvocationID}" -ACTION=="add", RUN+="/usr/bin/bash -c ':> /tmp/marker'", RUN+="/usr/bin/sleep 10" +ACTION=="add", RUN+="/usr/bin/bash -c ': >/tmp/marker'", RUN+="/usr/bin/sleep 10" LABEL="end" EOF diff --git a/test/units/TEST-17-UDEV.rename-netif.sh b/test/units/TEST-17-UDEV.rename-netif.sh index 0abfe24bcd82b..393719efed510 100755 --- a/test/units/TEST-17-UDEV.rename-netif.sh +++ b/test/units/TEST-17-UDEV.rename-netif.sh @@ -100,7 +100,7 @@ timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /s # cleanup ip link del hoge -# shellcheck disable=SC2317 +# shellcheck disable=SC2317,SC2329 teardown_netif_renaming_conflict() { set +ex @@ -181,4 +181,58 @@ EOF test_netif_renaming_conflict +# shellcheck disable=SC2317,SC2329 +teardown_netif_renaming_keeps_properties() { + set +ex + + rm -f /run/udev/rules.d/50-testsuite.rules + udevadm control --reload --timeout=30 + + ip link del rename-src 2>/dev/null || : + ip link del rename-dst 2>/dev/null || : +} + +test_netif_renaming_keeps_properties() { + trap teardown_netif_renaming_keeps_properties RETURN + + cat >/run/udev/rules.d/50-testsuite.rules <"${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-22-TMPFILES.22.sh b/test/units/TEST-22-TMPFILES.22.sh new file mode 100755 index 0000000000000..37c9f709b75ce --- /dev/null +++ b/test/units/TEST-22-TMPFILES.22.sh @@ -0,0 +1,63 @@ +#!/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 + +rm -f /tmp/setcap +touch /tmp/setcap + +systemd-tmpfiles --dry-run --create - < $HOME/.local/state/foo/baz") -( ! systemd-run --user -p StateDirectory=foo:bar:ro --wait bash -c "echo foo > $HOME/.local/state/foo/baz") +( ! systemd-run --user -p StateDirectory=foo::ro --wait bash -c "echo foo >$HOME/.local/state/foo/baz") +( ! systemd-run --user -p StateDirectory=foo:bar:ro --wait bash -c "echo foo >$HOME/.local/state/foo/baz") ( ! test -f "$HOME"/.local/state/foo/baz) test -L "$HOME"/.local/state/bar diff --git a/test/units/TEST-24-CRYPTSETUP.sh b/test/units/TEST-24-CRYPTSETUP.sh index be0efa013c884..a2afd7d72095d 100755 --- a/test/units/TEST-24-CRYPTSETUP.sh +++ b/test/units/TEST-24-CRYPTSETUP.sh @@ -261,6 +261,64 @@ if [[ -d /usr/lib/softhsm/tokens ]]; then cryptsetup_start_and_check empty_pkcs11_auto cryptsetup luksKillSlot -q "$IMAGE_EMPTY" 2 cryptsetup token remove --token-id 0 "$IMAGE_EMPTY" + + # Verify that new RSA enrollments tag the LUKS2 token with the OAEP padding scheme. Old systemd + # versions ignore this field and fall back to PKCS#1 v1.5, which causes the token to refuse to + # decrypt the OAEP-wrapped blob, so this metadata is what makes graceful rejection on old systems + # possible. The actual OAEP hash variant (SHA-1 vs SHA-256) is probed at enrollment time and + # depends on what the token supports, but SoftHSM through at least 2.7.0 only accepts SHA-1 + PIN="1234" systemd-cryptenroll --pkcs11-token-uri="pkcs11:token=TestToken;object=RSATestKey" --unlock-key-file="$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY" + cryptsetup luksDump --dump-json-metadata "$IMAGE_EMPTY" | + jq -e '.tokens | with_entries(select(.value.type == "systemd-pkcs11"))[]["pkcs11-padding"] | select(. == "oaep-sha256" or . == "oaep-sha1")' >/dev/null + + # Forward-compat negative check: tampering with the padding tag must cause unlocking to fail + # cleanly (the token rejects the OAEP blob when asked to do PKCS#1 v1.5, and vice versa). + TOKEN_ID=$(cryptsetup luksDump --dump-json-metadata "$IMAGE_EMPTY" | + jq -r '.tokens | to_entries[] | select(.value.type == "systemd-pkcs11") | .key' | head -n1) + cryptsetup token export --token-id "$TOKEN_ID" "$IMAGE_EMPTY" | + jq '. + {"pkcs11-padding": "pkcs1"}' >"$WORKDIR/tampered-token.json" + cryptsetup token remove --token-id "$TOKEN_ID" "$IMAGE_EMPTY" + cryptsetup token import --json-file="$WORKDIR/tampered-token.json" --token-id "$TOKEN_ID" "$IMAGE_EMPTY" + cryptsetup_start_and_check -f empty_pkcs11_auto + # Restore the correct token and confirm unlock works again. + cryptsetup token remove --token-id "$TOKEN_ID" "$IMAGE_EMPTY" + cryptsetup luksKillSlot -q "$IMAGE_EMPTY" 2 + + PIN="1234" systemd-cryptenroll --pkcs11-token-uri="pkcs11:token=TestToken;object=RSATestKey" --unlock-key-file="$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY" + cryptsetup_start_and_check empty_pkcs11_auto + cryptsetup luksKillSlot -q "$IMAGE_EMPTY" 2 + cryptsetup token remove --token-id 0 "$IMAGE_EMPTY" + + # Backward-compat check: mock a legacy LUKS2 token wrapped with PKCS#1 v1.5 (no + # 'pkcs11-padding' field) and verify the new code can still decrypt it + SOFTHSM_MODULE=$(awk -F: '/^[[:space:]]*module[[:space:]]*:/ { gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit }' /usr/share/p11-kit/modules/softhsm2.module) + [[ -n "$SOFTHSM_MODULE" ]] || { echo "Could not locate libsofthsm2.so via /usr/share/p11-kit/modules/softhsm2.module" >&2; exit 1; } + # Read the RSA public key out of the matching certificate object on the token, generate random key and + # wrap it + pkcs11-tool --module "$SOFTHSM_MODULE" --token-label TestToken --pin 1234 \ + --read-object --type cert --label RSATestKey --output-file "$WORKDIR/rsa.der" + openssl x509 -inform DER -in "$WORKDIR/rsa.der" -pubkey -noout >"$WORKDIR/rsa-pub.pem" + openssl rand -out "$WORKDIR/decrypted.bin" 32 + openssl pkeyutl -encrypt -pubin -inkey "$WORKDIR/rsa-pub.pem" \ + -pkeyopt rsa_padding_mode:pkcs1 \ + -in "$WORKDIR/decrypted.bin" -out "$WORKDIR/encrypted.bin" + base64 -w0 "$WORKDIR/decrypted.bin" >"$WORKDIR/passphrase" + cryptsetup luksAddKey --batch-mode --key-file="$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY" "$WORKDIR/passphrase" + LEGACY_SLOT=$(cryptsetup luksDump --dump-json-metadata "$IMAGE_EMPTY" | + jq -r '.keyslots | keys | map(tonumber) | max') + ENCRYPTED_B64=$(base64 -w0 "$WORKDIR/encrypted.bin") + cat >"$WORKDIR/legacy-token.json" < ${path}/www/test-missing") + (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" bash -c "echo foo >${path}/www/test-missing") test -d "${path}"/zzz test ! -L "${path}"/zzz @@ -50,7 +50,7 @@ test_directory() { (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing) systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"="www::ro www:ro:ro" test -d "${path}"/www systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"="www::ro www:ro:ro" test -L "${path}"/ro - (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"="www::ro www:ro:ro" bash -c "echo foo > ${path}/www/test-missing") + (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"="www::ro www:ro:ro" bash -c "echo foo >${path}/www/test-missing") test -L "${path}"/zzz test -d "${path}"/private/zzz @@ -76,7 +76,7 @@ test_directory() { (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing) systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" test -d "${path}"/www systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" test -L "${path}"/ro - (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" bash -c "echo foo > ${path}/www/test-missing") + (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"="www::ro www:ro:ro" bash -c "echo foo >${path}/www/test-missing") test -d "${path}"/zzz test ! -L "${path}"/zzz diff --git a/test/units/TEST-35-LOGIN.sh b/test/units/TEST-35-LOGIN.sh index 58b3bcca07888..f3974ed7b7ac6 100755 --- a/test/units/TEST-35-LOGIN.sh +++ b/test/units/TEST-35-LOGIN.sh @@ -694,7 +694,7 @@ session optional pam_systemd.so default-capability-ambient-set=CAP_CHOWN,CAP_K session required pam_unix.so EOF - cat > "$SCRIPT" <<'EOF' + cat >"$SCRIPT" <<'EOF' #!/usr/bin/env bash set -ex typeset -i AMB MASK @@ -797,8 +797,219 @@ EOF systemctl stop "$RUN0UNIT3" } +teardown_varlink() ( + set +ex + + systemctl stop test-varlink-inhibit.service 2>/dev/null + cleanup_session + + return 0 +) + testcase_varlink() { - varlinkctl introspect /run/systemd/io.systemd.Login + local session uid session_out user_out seat_out self_err inhibitor_out + + if [[ ! -c /dev/tty2 ]]; then + echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}." + return + fi + + trap teardown_varlink RETURN + + local VARLINK_SOCKET="/run/systemd/io.systemd.Login" + + : "--- Introspect ---" + varlinkctl introspect "$VARLINK_SOCKET" + varlinkctl introspect "$VARLINK_SOCKET" | grep "method ListSessions" >/dev/null + varlinkctl introspect "$VARLINK_SOCKET" | grep "method ListUsers" >/dev/null + varlinkctl introspect "$VARLINK_SOCKET" | grep "method ListSeats" >/dev/null + varlinkctl introspect "$VARLINK_SOCKET" | grep "method ListInhibitors" >/dev/null + + : "--- Setup test session ---" + create_session + session=$(loginctl --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $1 }') + uid=$(id -ru logind-test-user) + local leader_pid + leader_pid=$(loginctl show-session "$session" -p Leader --value) + loginctl activate "$session" + + : "--- ListSessions: Id filter (single reply) ---" + session_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions "{\"Id\":\"$session\"}") + echo "$session_out" | jq -e ".runtime.Id == \"$session\"" >/dev/null + echo "$session_out" | jq -e ".context.UID == $uid" >/dev/null + echo "$session_out" | jq -e ".context.PID.pid == $leader_pid" >/dev/null + echo "$session_out" | jq -e '.context.TTY == "tty2"' >/dev/null + echo "$session_out" | jq -e '.context.Remote == false' >/dev/null + echo "$session_out" | jq -e '.context.Type == "tty"' >/dev/null + echo "$session_out" | jq -e '.context.Class == "user"' >/dev/null + echo "$session_out" | jq -e '.runtime.CanIdle == true' >/dev/null + echo "$session_out" | jq -e '.runtime.CanLock == true' >/dev/null + echo "$session_out" | jq -e '.runtime.State == "active"' >/dev/null + echo "$session_out" | jq -e '.runtime.Active == true' >/dev/null + # ExtraDeviceAccess may be absent (empty) but must be an array if present. + echo "$session_out" | jq -e '(.context | has("ExtraDeviceAccess") | not) or (.context.ExtraDeviceAccess | type == "array")' >/dev/null + + # nonexistent session id + (! varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions '{"Id":"nonexistent"}') + + : "--- ListSessions: PID filter (single reply) ---" + # Look up the session containing the session leader's PID. + local pid_out + pid_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions "{\"PID\":{\"pid\":$leader_pid}}") + echo "$pid_out" | jq -e ".runtime.Id == \"$session\"" >/dev/null + + : "--- ListSessions: Id+PID consistency check ---" + # Same session referenced two ways: must succeed. + local ok_out mismatch_err + ok_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions \ + "{\"Id\":\"$session\",\"PID\":{\"pid\":$leader_pid}}") + echo "$ok_out" | jq -e ".runtime.Id == \"$session\"" >/dev/null + # Mismatched Id and PID (PID 1 is not in $session): must fail with NoSuchSession. + mismatch_err=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions \ + "{\"Id\":\"$session\",\"PID\":{\"pid\":1}}" 2>&1 || true) + echo "$mismatch_err" | grep NoSuchSession >/dev/null + + : "--- ListSessions: streaming path ---" + # varlinkctl --more emits RFC 7464 JSON-seq (each record prefixed with RS/0x1e), so jq --seq is required. + varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListSessions '{}' \ + | jq --seq -e --arg s "$session" 'select(.runtime.Id == $s)' >/dev/null + test "$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListSessions '{}' | wc -l)" -ge 2 + # without --more and no filter: must fail with ExpectedMore (not assert()) so logind stays running. + local list_err + list_err=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions '{}' 2>&1 || true) + # varlinkctl rewrites SD_VARLINK_ERROR_EXPECTED_MORE to the friendly description + # below (see src/varlinkctl/varlinkctl.c), so match on that substring rather than + # the raw error id. + echo "$list_err" | grep "'more' flag" >/dev/null + systemctl is-active systemd-logind.service >/dev/null + + : "--- ListUsers: UID filter (single reply) ---" + user_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListUsers "{\"UID\":$uid}") + echo "$user_out" | jq -e ".context.UID == $uid" >/dev/null + echo "$user_out" | jq -e '.context.Name == "logind-test-user"' >/dev/null + echo "$user_out" | jq -e '.runtime.State' >/dev/null + echo "$user_out" | jq -e '.context.Linger == false' >/dev/null + # Sessions is now a flat array of session id strings (no wrapper object). + echo "$user_out" | jq -e ".runtime.Sessions[] | select(. == \"$session\")" >/dev/null + + : "--- ListUsers: PID filter (single reply) ---" + # PID of a process in the test user's session should map back to the user. + local pid_user_out + pid_user_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListUsers \ + "{\"PID\":{\"pid\":$leader_pid}}") + echo "$pid_user_out" | jq -e --argjson u "$uid" '.context.UID == $u' >/dev/null + + : "--- ListUsers: UID+PID consistency check ---" + # Same user referenced two ways: must succeed. + local ok_user_out mismatch_user_err + ok_user_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListUsers \ + "{\"UID\":$uid,\"PID\":{\"pid\":$leader_pid}}") + echo "$ok_user_out" | jq -e --argjson u "$uid" '.context.UID == $u' >/dev/null + # Mismatched UID and PID (PID 1 is systemd init, UID 0): must fail with NoSuchUser. + mismatch_user_err=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListUsers \ + "{\"UID\":$uid,\"PID\":{\"pid\":1}}" 2>&1 || true) + echo "$mismatch_user_err" | grep NoSuchUser >/dev/null + + : "--- ListUsers: empty input without --more must require --more ---" + # The DescribeUser-style caller-UID fallback was removed; a no-filter call without + # --more must fail with the EXPECTED_MORE error. + local empty_users_err + empty_users_err=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListUsers '{}' 2>&1 || true) + echo "$empty_users_err" | grep "'more' flag" >/dev/null + systemctl is-active systemd-logind.service >/dev/null + + # nonexistent UID + (! varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListUsers '{"UID":4294967294}') + + : "--- ListUsers: streaming path ---" + varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListUsers '{}' \ + | jq --seq -e 'select(.context.Name == "logind-test-user")' >/dev/null + test "$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListUsers '{}' | wc -l)" -ge 2 + + : "--- ListSeats: Id filter (single reply) ---" + seat_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSeats '{"Id":"seat0"}') + echo "$seat_out" | jq -e '.context.Id == "seat0"' >/dev/null + echo "$seat_out" | jq -e '.runtime.CanTTY == true' >/dev/null + # Sessions is a flat array of session id strings. + echo "$seat_out" | jq -e ".runtime.Sessions[] | select(. == \"$session\")" >/dev/null + + # nonexistent seat + (! varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSeats '{"Id":"seat-nonexistent"}') + + : "--- ListSeats: empty input without --more must require --more ---" + # The caller-seat fallback was removed; no filter + no --more = EXPECTED_MORE. + local empty_seats_err + empty_seats_err=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSeats '{}' 2>&1 || true) + echo "$empty_seats_err" | grep "'more' flag" >/dev/null + systemctl is-active systemd-logind.service >/dev/null + + : "--- ListSeats: self/auto from session-less context yields NoSuchSeat ---" + # self/auto still resolves via peer session; running as root outside any session + # has no peer session, so we must get NoSuchSeat (not NoSuchSession leaked from + # the lookup helper). + local self_err + for id_arg in '{"Id":"self"}' '{"Id":"auto"}'; do + self_err=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSeats "$id_arg" 2>&1 || true) + echo "$self_err" | grep NoSuchSeat >/dev/null + (! echo "$self_err" | grep NoSuchSession >/dev/null) + done + + : "--- ListSeats: streaming path ---" + varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListSeats '{}' | grep "seat0" >/dev/null + test "$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListSeats '{}' | wc -l)" -ge 1 + + : "--- ListInhibitors ---" + systemd-run --unit=test-varlink-inhibit.service --service-type=exec \ + systemd-inhibit --what=shutdown --who="varlink-test" --why="testing varlink" --mode=block \ + sleep infinity + timeout 10 bash -c "until varlinkctl call --more '$VARLINK_SOCKET' io.systemd.Login.ListInhibitors '{}' 2>/dev/null | grep varlink-test >/dev/null; do sleep 0.5; done" + + inhibitor_out=$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListInhibitors '{}') + # What is now an array of strings; pick our test record and validate fields with jq. + echo "$inhibitor_out" | jq --seq -e 'select(.context.Who == "varlink-test") | .context.What | type == "array" and contains(["shutdown"])' >/dev/null + echo "$inhibitor_out" | jq --seq -e 'select(.context.Who == "varlink-test") | .context.Mode == "block"' >/dev/null + echo "$inhibitor_out" | jq --seq -e 'select(.context.Who == "varlink-test") | .context.Why == "testing varlink"' >/dev/null + + # without --more should fail (ListInhibitors has no filter, --more required) + (! varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListInhibitors '{}') + + systemctl stop test-varlink-inhibit.service + + # Best-effort empty-list check: stopping the test inhibitor doesn't guarantee + # zero entries (other system inhibitors may be registered), but the contract + # we verify holds regardless — ListInhibitors has no single-lookup path, so + # it must never emit a NoSuchInhibitor sentinel. An empty result is just an + # empty parameters reply as the stream terminator; the IDL-validation skip + # in the sentinel handler ensures this passes validation. + local empty_inhibitor_err + empty_inhibitor_err=$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListInhibitors '{}' 2>&1 || true) + (! echo "$empty_inhibitor_err" | grep NoSuchInhibitor >/dev/null) + + : "--- ReleaseSession: NULL ID resolves to caller's session ---" + # A caller with a logind session calling ReleaseSession '{}' (no ID) must + # release its own session. We spawn the call inside a transient unit with + # PAM so pam_systemd creates a real session for the caller. + local PAMSERVICE_REL="pamserv-rel$RANDOM" + cat >/etc/pam.d/"$PAMSERVICE_REL" <&1 || true) + echo "$no_sess_rel" | grep NoSuchSession >/dev/null } testcase_restart() { diff --git a/test/units/TEST-36-NUMAPOLICY.sh b/test/units/TEST-36-NUMAPOLICY.sh index 71fcdb4524870..3eb9b2410fb51 100755 --- a/test/units/TEST-36-NUMAPOLICY.sh +++ b/test/units/TEST-36-NUMAPOLICY.sh @@ -34,7 +34,7 @@ testUnitNUMAConf="$testUnitFile.d/numa.conf" sleepAfterStart=3 # Journal cursor for easier navigation -journalCursorFile="jounalCursorFile" +journalCursorFile="journalCursorFile" startStrace() { coproc strace -qq -p 1 -o "$straceLog" -e set_mempolicy -s 1024 ${1:+"$1"} @@ -227,6 +227,26 @@ else pid1ReloadWithStrace grep -E "set_mempolicy\((MPOL_LOCAL|0x4 [^,]*), NULL" "$straceLog" + echo "PID1 NUMAPolicy support - Preferred-many policy w/o mask" + writePID1NUMAPolicy "preferred-many" + pid1ReloadWithJournal + grep "Failed to set NUMA memory policy, ignoring: Invalid argument" "$journalLog" + + echo "PID1 NUMAPolicy support - Preferred-many policy w/ mask" + writePID1NUMAPolicy "preferred-many" "0" + pid1ReloadWithStrace + grep -E "set_mempolicy\((MPOL_PREFERRED_MANY|0x5 [^,]*), \[0x0*1\]" "$straceLog" + + echo "PID1 NUMAPolicy support - Weighted-interleave policy w/o mask" + writePID1NUMAPolicy "weighted-interleave" + pid1ReloadWithJournal + grep "Failed to set NUMA memory policy, ignoring: Invalid argument" "$journalLog" + + echo "PID1 NUMAPolicy support - Weighted-interleave policy w/ mask" + writePID1NUMAPolicy "weighted-interleave" "0" + pid1ReloadWithStrace + grep -E "set_mempolicy\((MPOL_WEIGHTED_INTERLEAVE|0x6 [^,]*), \[0x0*1\]" "$straceLog" + echo "Unit file NUMAPolicy support - Default policy w/o mask" writeTestUnitNUMAPolicy "default" pid1StartUnitWithStrace "$testUnit" @@ -297,6 +317,34 @@ else # Mask must be ignored grep -E "set_mempolicy\((MPOL_LOCAL|0x4 [^,]*), NULL" "$straceLog" + echo "Unit file NUMAPolicy support - Preferred-many policy w/o mask" + writeTestUnitNUMAPolicy "preferred-many" + pid1StartUnitWithStrace "$testUnit" + pid1StopUnit "$testUnit" + [[ $(systemctl show "$testUnit" -P ExecMainStatus) == "242" ]] + + echo "Unit file NUMAPolicy support - Preferred-many policy w/ mask" + writeTestUnitNUMAPolicy "preferred-many" "0" + pid1StartUnitWithStrace "$testUnit" + systemctlCheckNUMAProperties "$testUnit" "preferred-many" "0" + varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"name\":\"$testUnit\"}" | jq -e '.context.Exec.NUMAPolicy == "preferred_many"' + pid1StopUnit "$testUnit" + grep -E "set_mempolicy\((MPOL_PREFERRED_MANY|0x5 [^,]*), \[0x0*1\]" "$straceLog" + + echo "Unit file NUMAPolicy support - Weighted-interleave policy w/o mask" + writeTestUnitNUMAPolicy "weighted-interleave" + pid1StartUnitWithStrace "$testUnit" + pid1StopUnit "$testUnit" + [[ $(systemctl show "$testUnit" -P ExecMainStatus) == "242" ]] + + echo "Unit file NUMAPolicy support - Weighted-interleave policy w/ mask" + writeTestUnitNUMAPolicy "weighted-interleave" "0" + pid1StartUnitWithStrace "$testUnit" + systemctlCheckNUMAProperties "$testUnit" "weighted-interleave" "0" + varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "{\"name\":\"$testUnit\"}" | jq -e '.context.Exec.NUMAPolicy == "weighted_interleave"' + pid1StopUnit "$testUnit" + grep -E "set_mempolicy\((MPOL_WEIGHTED_INTERLEAVE|0x6 [^,]*), \[0x0*1\]" "$straceLog" + echo "Unit file CPUAffinity=NUMA support" writeTestUnitNUMAPolicy "bind" "0" echo "CPUAffinity=numa" >>"$testUnitNUMAConf" @@ -343,6 +391,14 @@ else systemctlCheckNUMAProperties "$runUnit" "local" "" systemctl cat "$runUnit" | grep 'CPUAffinity=numa' >/dev/null pid1StopUnit "$runUnit" + + systemd-run -p NUMAPolicy=preferred-many -p NUMAMask=0 --unit "$runUnit" sleep 1000 + systemctlCheckNUMAProperties "$runUnit" "preferred-many" "0" + pid1StopUnit "$runUnit" + + systemd-run -p NUMAPolicy=weighted-interleave -p NUMAMask=0 --unit "$runUnit" sleep 1000 + systemctlCheckNUMAProperties "$runUnit" "weighted-interleave" "0" + pid1StopUnit "$runUnit" fi # Cleanup diff --git a/test/units/TEST-46-HOMED.sh b/test/units/TEST-46-HOMED.sh index 46abca9bace72..595f46b8a879c 100755 --- a/test/units/TEST-46-HOMED.sh +++ b/test/units/TEST-46-HOMED.sh @@ -54,12 +54,6 @@ systemctl kill -sUSR1 systemd-homed testcase_basic() { local TMP_SKEL - . /etc/os-release - if [[ "${ID_LIKE:-}" == alpine ]] && ! systemd-detect-virt -cq; then - # luks seems to be broken on alpine/postmarketos. - return 0 - fi - TMP_SKEL=$(mktemp -d) echo hogehoge >"$TMP_SKEL"/hoge @@ -87,6 +81,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 @@ -251,12 +268,6 @@ testcase_basic() { } testcase_blob() { - . /etc/os-release - if [[ "${ID_LIKE:-}" == alpine ]] && ! systemd-detect-virt -cq; then - # luks seems to be broken on alpine/postmarketos. - return 0 - fi - # blob directory tests # See docs/USER_RECORD_BLOB_DIRS.md checkblob() { @@ -563,6 +574,15 @@ EOF (! userdbctl ssh-authorized-keys dropin-user --chain '') (! SYSTEMD_LOG_LEVEL=debug userdbctl ssh-authorized-keys dropin-user --chain /usr/bin/false) + # Check that invocations with --chain work as expected + userdbctl ssh-authorized-keys --chain dropin-user /bin/echo --asdf | grep -e --asdf + userdbctl ssh-authorized-keys dropin-user --chain /bin/echo --asdf | grep -e --asdf + userdbctl ssh-authorized-keys dropin-user /bin/echo --chain --asdf | grep -e --asdf + userdbctl ssh-authorized-keys --chain dropin-user -- /bin/echo --asdf | grep -e --asdf + userdbctl ssh-authorized-keys --chain -- dropin-user /bin/echo --asdf | grep -e --asdf + userdbctl --chain -- ssh-authorized-keys dropin-user /bin/echo --asdf | grep -e --asdf + (! userdbctl --chain -- ssh-authorized-keys dropin-user -- /bin/echo --asdf) + (! userdbctl '') for opt in json multiplexer output synthesize with-dropin with-nss with-varlink; do (! userdbctl "--$opt=''") @@ -952,4 +972,141 @@ testcase_match() { homectl remove matchtest } +testcase_fscrypt() { + if ! command -v mkfs.ext4 >/dev/null; then + echo "e2fsprogs not installed, skipping fscrypt test." + return 0 + fi + + local IMAGE MNT + IMAGE="$(mktemp /tmp/fscrypt.XXXXXX.img)" + MNT="$(mktemp -d /tmp/fscrypt-mnt.XXXXXX)" + # shellcheck disable=SC2064 + trap "homectl deactivate fscrypttest 2>/dev/null || true; homectl remove fscrypttest 2>/dev/null || true; umount '$MNT' 2>/dev/null || true; rm -rf '$MNT' '$IMAGE'" RETURN ERR + + truncate -s 64M "$IMAGE" + if ! mkfs.ext4 -q -O encrypt "$IMAGE"; then + echo "mkfs.ext4 -O encrypt unsupported, skipping fscrypt test." + return 0 + fi + + if ! mount -o loop "$IMAGE" "$MNT"; then + echo "Cannot loop-mount fscrypt-capable ext4, skipping fscrypt test." + return 0 + fi + + if ! NEWPASSWORD=fsfsfs1234 homectl create fscrypttest \ + --storage=fscrypt \ + --image-path="$MNT/fscrypttest" \ + --rate-limit-interval=1s --rate-limit-burst=1000; then + echo "homed fscrypt backend not usable on this kernel, skipping." + return 0 + fi + + inspect fscrypttest + + (! PASSWORD=wrongpass timeout 10s homectl authenticate fscrypttest /home/fscrypttest/file1' + [[ "$(fscrypt_run0 fsfsfs1234 'cat /home/fscrypttest/file1')" == "hello fscrypt" ]] + fscrypt_run0 fsfsfs1234 'mkdir /home/fscrypttest/subdir' + fscrypt_run0 fsfsfs1234 'dd if=/dev/urandom of=/home/fscrypttest/subdir/blob bs=4096 count=8 status=none' + fscrypt_run0 fsfsfs1234 'cp /home/fscrypttest/subdir/blob /home/fscrypttest/subdir/blob.copy && cmp /home/fscrypttest/subdir/blob /home/fscrypttest/subdir/blob.copy' + fscrypt_run0 fsfsfs1234 'echo appended >>/home/fscrypttest/file1 && grep -F appended /home/fscrypttest/file1 >/dev/null' + fscrypt_run0 fsfsfs1234 'rm /home/fscrypttest/subdir/blob.copy && test ! -e /home/fscrypttest/subdir/blob.copy' + + systemctl stop user@"$(id -u fscrypttest)".service 2>/dev/null || true + homectl deactivate fscrypttest 2>/dev/null || true + wait_for_state fscrypttest inactive + + # After deactivation the fscrypt-encrypted directory is locked, so cleartext file names should not be visible + [[ ! -e "$MNT/fscrypttest/file1" ]] + + # Verify we actually use the v2 format + local SLOT + SLOT="$(getfattr --absolute-names --only-values -n trusted.fscrypt_slot0 "$MNT/fscrypttest")" + [[ "${SLOT:0:4}" == "\$v2:" ]] || { + echo "fscrypt slot 0 is not in v2 format: ${SLOT:0:32}" + return 1 + } + + PASSWORD=fsfsfs1234 homectl activate fscrypttest + [[ "$(fscrypt_run0 fsfsfs1234 'head -n1 /home/fscrypttest/file1')" == "hello fscrypt" ]] + systemctl stop user@"$(id -u fscrypttest)".service 2>/dev/null || true + homectl deactivate fscrypttest 2>/dev/null || true + wait_for_state fscrypttest inactive + + homectl update fscrypttest --real-name="Fscrypt Test" --offline + inspect fscrypttest | grep "Real Name: Fscrypt Test" >/dev/null + + PASSWORD=fsfsfs1234 NEWPASSWORD=newfsfs5678 homectl passwd fscrypttest + PASSWORD=newfsfs5678 homectl authenticate fscrypttest + (! PASSWORD=fsfsfs1234 timeout 10s homectl authenticate fscrypttest /dev/null || true + homectl deactivate fscrypttest 2>/dev/null || true + wait_for_state fscrypttest inactive + + homectl remove fscrypttest +} + +testcase_deactivate_busy() { + # Verify that "homectl deactivate" is robust against transient EBUSY + # failures of the umount() inside systemd-homework. This used to make + # TEST-46-HOMED occasionally fail when something briefly held a reference + # to the home mount at the moment the deactivation tried to unmount it. + # + # Reproduce the situation deterministically by spawning a background + # process whose cwd is the home directory: that holds the mount busy via + # the kernel's cwd reference until the process exits, so the initial + # umount2() call in homework will fail with EBUSY. homectl is expected to + # transparently retry the bus call until it succeeds (once the holder + # exits). + + NEWPASSWORD=hunter2 homectl create \ + --storage=directory \ + --enforce-password-policy=no \ + busytest + PASSWORD=hunter2 homectl activate busytest + inspect busytest + + # Make sure the home is actually mounted before we try to hold it busy, + # otherwise the subshell below would silently fail to acquire the cwd + # reference. + mountpoint /home/busytest + + # Spawn a process whose cwd is inside the home mount. `cd` is a shell + # builtin so the subshell process itself acquires the cwd reference, and + # `exec sleep` then preserves it across the exec. + ( cd /home/busytest && exec sleep 10 ) & + local busy_pid=$! + + # Wait until the kernel actually reports the cwd of the background + # process as the home directory, so we know the busy reference is in + # place before we attempt to deactivate. + timeout 5 bash -c "until [[ \"\$(readlink /proc/${busy_pid}/cwd 2>/dev/null)\" == /home/busytest ]]; do sleep 0.1; done" + + # The deactivate must succeed eventually: the first umount2() will fail + # with EBUSY, but homectl retries the call for up to 30 seconds, by + # which time the background process will have exited and released the + # cwd reference. + homectl deactivate busytest + wait_for_state busytest inactive + + wait "$busy_pid" || true + homectl remove busytest +} + run_testcases diff --git a/test/units/TEST-50-DISSECT.dissect.sh b/test/units/TEST-50-DISSECT.dissect.sh index 07e0d1a8a0488..2fe6bf19d95a8 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" \ @@ -383,7 +411,7 @@ ExecStart=bash -c ' \\ sleep 0.1; \\ done; \\ mount; \\ - mount | grep -F "on /tmp/img type squashfs" | grep -q -F "nosuid"; \\ + mount | grep -F "on /tmp/img type squashfs" | grep -F "nosuid" >/dev/null; \\ ' EOF systemctl start testservice-50d.service @@ -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" <"$mnt/@demo/os-release" + btrfs subvolume create "$mnt/@" + btrfs subvolume set-default "$mnt/@" + umount "$mnt" + mount -t btrfs "${loop}p1" "$mnt" + + systemd-run -P \ + -p MountImages="${loop}p1:/run/img-btrfs:subvol=@demo" \ + cat /run/img-btrfs/os-release | grep -F "MARKER=1" >/dev/null + # Double check that there's no loopdev + src="$(systemd-run -P \ + -p MountImages="${loop}p1:/run/img-btrfs:subvol=@demo" \ + findmnt -n -o SOURCE /run/img-btrfs)" + assert_eq "${src%%\[*}" "${loop}p1" + + trap - EXIT + cleanup_btrfs_mountimages +fi diff --git a/test/units/TEST-50-DISSECT.mountfsd.sh b/test/units/TEST-50-DISSECT.mountfsd.sh index 12a72f8257d5d..d25743502d328 100755 --- a/test/units/TEST-50-DISSECT.mountfsd.sh +++ b/test/units/TEST-50-DISSECT.mountfsd.sh @@ -175,13 +175,13 @@ EOF systemd-run -M testuser@ --user --pipe --wait \ --property RootImage="$MINIMAL_IMAGE.gpt" \ --property RootImageOptions="root:nosuid" \ - sh -c "test -e \"/dev/mapper/${MINIMAL_IMAGE_ROOTHASH}-verity\" && mount | grep -F squashfs | grep -q -F nosuid" + sh -c "test -e \"/dev/mapper/${MINIMAL_IMAGE_ROOTHASH}-verity\" && mount | grep -F squashfs | grep -F nosuid >/dev/null" systemd-run -M testuser@ --user --pipe --wait \ --property RootImage="$MINIMAL_IMAGE.raw" \ --property ExtensionImages=/tmp/app0.raw \ --property MountImages=/tmp/app0.raw:/var/tmp:nosuid \ - sh -c "test -e \"/dev/mapper/${MINIMAL_IMAGE_ROOTHASH}-verity\" && test -e \"/dev/mapper/$(/dev/null" rm -f /etc/polkit-1/rules.d/mountoptions.rules systemctl try-reload-or-restart polkit.service diff --git a/test/units/TEST-50-DISSECT.sh b/test/units/TEST-50-DISSECT.sh index 99e4991402393..07bed11adcbbc 100755 --- a/test/units/TEST-50-DISSECT.sh +++ b/test/units/TEST-50-DISSECT.sh @@ -14,6 +14,8 @@ set -o pipefail at_exit() { set +e + rm -f "${BTRFS_MEMBER1:-}" "${BTRFS_MEMBER2:-}" + if [[ -z "${IMAGE_DIR:-}" ]]; then return fi @@ -55,6 +57,9 @@ export OPENSSL_CONFIG export OS_RELEASE export ROOT_GUID export SIGNATURE_GUID +export USR_GUID +export USR_SIGNATURE_GUID +export USR_VERITY_GUID export VERITY_GUID machine="$(uname -m)" @@ -62,51 +67,81 @@ if [[ "$machine" == "x86_64" ]]; then ROOT_GUID=4f68bce3-e8cd-4db1-96e7-fbcaf984b709 VERITY_GUID=2c7357ed-ebd2-46d9-aec1-23d437ec2bf5 SIGNATURE_GUID=41092b05-9fc8-4523-994f-2def0408b176 + USR_GUID=8484680c-9521-48c6-9c11-b0720656f69e + USR_VERITY_GUID=77ff5f63-e7b6-4633-acf4-1565b864c0e6 + USR_SIGNATURE_GUID=e7bb33fb-06cf-4e81-8273-e543b413e2e2 ARCHITECTURE="x86-64" elif [[ "$machine" =~ ^(i386|i686|x86)$ ]]; then ROOT_GUID=44479540-f297-41b2-9af7-d131d5f0458a VERITY_GUID=d13c5d3b-b5d1-422a-b29f-9454fdc89d76 SIGNATURE_GUID=5996fc05-109c-48de-808b-23fa0830b676 + USR_GUID=75250d76-8cc6-458e-bd66-bd47cc81a812 + USR_VERITY_GUID=8f461b0d-14ee-4e81-9aa9-049b6fb97abd + USR_SIGNATURE_GUID=974a71c0-de41-43c3-be5d-5c5ccd1ad2c0 ARCHITECTURE="x86" elif [[ "$machine" =~ ^(aarch64|aarch64_be|armv8b|armv8l)$ ]]; then ROOT_GUID=b921b045-1df0-41c3-af44-4c6f280d3fae VERITY_GUID=df3300ce-d69f-4c92-978c-9bfb0f38d820 SIGNATURE_GUID=6db69de6-29f4-4758-a7a5-962190f00ce3 + USR_GUID=b0e01050-ee5f-4390-949a-9101b17104e9 + USR_VERITY_GUID=6e11a4e7-fbca-4ded-b9e9-e1a512bb664e + USR_SIGNATURE_GUID=c23ce4ff-44bd-4b00-b2d4-b41b3419e02a ARCHITECTURE="arm64" elif [[ "$machine" == "arm" ]]; then ROOT_GUID=69dad710-2ce4-4e3c-b16c-21a1d49abed3 VERITY_GUID=7386cdf2-203c-47a9-a498-f2ecce45a2d6 SIGNATURE_GUID=42b0455f-eb11-491d-98d3-56145ba9d037 + USR_GUID=7d0359a3-02b3-4f0a-865c-654403e70625 + USR_VERITY_GUID=c215d751-7bcd-4649-be90-6627490a4c05 + USR_SIGNATURE_GUID=d7ff812f-37d1-4902-a810-d76ba57b975a ARCHITECTURE="arm" elif [[ "$machine" == "ia64" ]]; then ROOT_GUID=993d8d3d-f80e-4225-855a-9daf8ed7ea97 VERITY_GUID=86ed10d5-b607-45bb-8957-d350f23d0571 SIGNATURE_GUID=e98b36ee-32ba-4882-9b12-0ce14655f46a + USR_GUID=4301d2a6-4e3b-4b2a-bb94-9e0b2c4225ea + USR_VERITY_GUID=6a491e03-3be7-4545-8e38-83320e0ea880 + USR_SIGNATURE_GUID=8de58bc2-2a43-460d-b14e-a76e4a17b47f ARCHITECTURE="ia64" elif [[ "$machine" == "loongarch64" ]]; then ROOT_GUID=77055800-792c-4f94-b39a-98c91b762bb6 VERITY_GUID=f3393b22-e9af-4613-a948-9d3bfbd0c535 SIGNATURE_GUID=5afb67eb-ecc8-4f85-ae8e-ac1e7c50e7d0 + USR_GUID=e611c702-575c-4cbe-9a46-434fa0bf7e3f + USR_VERITY_GUID=f46b2c26-59ae-48f0-9106-c50ed47f673d + USR_SIGNATURE_GUID=b024f315-d330-444c-8461-44bbde524e99 ARCHITECTURE="loongarch64" elif [[ "$machine" == "s390x" ]]; then ROOT_GUID=5eead9a9-fe09-4a1e-a1d7-520d00531306 VERITY_GUID=b325bfbe-c7be-4ab8-8357-139e652d2f6b SIGNATURE_GUID=c80187a5-73a3-491a-901a-017c3fa953e9 + USR_GUID=8a4f5770-50aa-4ed3-874a-99b710db6fea + USR_VERITY_GUID=31741cc4-1a2a-4111-a581-e00b447d2d06 + USR_SIGNATURE_GUID=3f324816-667b-46ae-86ee-9b0c0c6c11b4 ARCHITECTURE="s390x" elif [[ "$machine" == "ppc64le" ]]; then ROOT_GUID=c31c45e6-3f39-412e-80fb-4809c4980599 VERITY_GUID=906bd944-4589-4aae-a4e4-dd983917446a SIGNATURE_GUID=d4a236e7-e873-4c07-bf1d-bf6cf7f1c3c6 + USR_GUID=15bb03af-77e7-4d4a-b12b-c0d084f7491c + USR_VERITY_GUID=ee2b9983-21e8-4153-86d9-b6901a54d1ce + USR_SIGNATURE_GUID=c8bfbd1e-268e-4521-8bba-bf314c399557 ARCHITECTURE="ppc64-le" elif [[ "$machine" == "riscv64" ]]; then ROOT_GUID=72ec70a6-cf74-40e6-bd49-4bda08e8f224 VERITY_GUID=b6ed5582-440b-4209-b8da-5ff7c419ea3d SIGNATURE_GUID=efe0f087-ea8d-4469-821a-4c2a96a8386a + USR_GUID=beaec34b-8442-439b-a40b-984381ed097d + USR_VERITY_GUID=8f1056be-9b05-47c4-81d6-be53128e5b54 + USR_SIGNATURE_GUID=d2f9000a-7a18-453f-b5cd-4d32f77a7b32 ARCHITECTURE="riscv64" elif [[ "$machine" == "riscv32" ]]; then ROOT_GUID=60d5a7fe-8e7d-435c-b714-3dd8162144e1 VERITY_GUID=ae0253be-1167-4007-ac68-43926c14c5de SIGNATURE_GUID=3a112a75-8729-4380-b4cf-764d79934448 + USR_GUID=b933fb22-5c3f-4f91-af90-e2bb0fa50702 + USR_VERITY_GUID=cb1ee4e3-8cd0-4136-a0a4-aa61a32e8730 + USR_SIGNATURE_GUID=c3836a13-3137-45ba-b583-b16c50fe5eb4 ARCHITECTURE="riscv32" else echo "Unexpected uname -m: $machine in TEST-50-DISSECT.sh, please fix me" @@ -197,6 +232,58 @@ udevadm lock --timeout=60 --device="${loop}p3" dd if="$MINIMAL_IMAGE.verity-sig" losetup -d "$loop" udevadm settle --timeout=60 +# Construct the same image with /usr verity partitions to exercise the usr-specific code paths. +usr_size="$(du --apparent-size -k "$MINIMAL_IMAGE.raw" | cut -f1)" +usr_verity_size="$(du --apparent-size -k "$MINIMAL_IMAGE.verity" | cut -f1)" +usr_signature_size=4 +truncate -s $(((8192+usr_size*2+usr_verity_size*2+usr_signature_size*2)*512)) "$MINIMAL_IMAGE.usr.gpt" +if [[ "$usr_size" -ge 1024 ]]; then + usr_size="$((usr_size/1024 + 1))MiB" +else + usr_size="${usr_size}KiB" +fi +usr_verity_size="$((usr_verity_size * 2))KiB" +usr_signature_size="$((usr_signature_size * 2))KiB" +uuid="$(head -c 32 "$MINIMAL_IMAGE.roothash" | sed -r 's/(.{8})(.{4})(.{4})(.{4})(.+)/\1-\2-\3-\4-\5/')" +echo -e "label: gpt\nsize=$usr_size, type=$USR_GUID, uuid=$uuid" | sfdisk "$MINIMAL_IMAGE.usr.gpt" +uuid="$(tail -c 32 "$MINIMAL_IMAGE.roothash" | sed -r 's/(.{8})(.{4})(.{4})(.{4})(.+)/\1-\2-\3-\4-\5/')" +echo -e "size=$usr_verity_size, type=$USR_VERITY_GUID, uuid=$uuid" | sfdisk "$MINIMAL_IMAGE.usr.gpt" --append +echo -e "size=$usr_signature_size, type=$USR_SIGNATURE_GUID" | sfdisk "$MINIMAL_IMAGE.usr.gpt" --append + +sfdisk --part-label "$MINIMAL_IMAGE.usr.gpt" 1 "Usr Partition" +sfdisk --part-label "$MINIMAL_IMAGE.usr.gpt" 2 "Usr Verity Partition" +sfdisk --part-label "$MINIMAL_IMAGE.usr.gpt" 3 "Usr Signature Partition" +loop="$(losetup --show -P -f "$MINIMAL_IMAGE.usr.gpt")" +partitions=( + "${loop:?}p1" + "${loop:?}p2" + "${loop:?}p3" +) +# The kernel sometimes(?) does not emit "add" uevent for loop block partition devices. +# Let's not expect the devices to be initialized. +udevadm wait --timeout=60 --settle --initialized=no "${partitions[@]}" +udevadm lock --timeout=60 --device="${loop}p1" dd if="$MINIMAL_IMAGE.raw" of="${loop}p1" +udevadm lock --timeout=60 --device="${loop}p2" dd if="$MINIMAL_IMAGE.verity" of="${loop}p2" +udevadm lock --timeout=60 --device="${loop}p3" dd if="$MINIMAL_IMAGE.verity-sig" of="${loop}p3" +losetup -d "$loop" +udevadm settle --timeout=60 + +# Pre-build the multi-device (raid1) btrfs members as mkfs.btrfs barfs later when there's a bunch of mounts +# for some reason +if command -v mkfs.btrfs >/dev/null; then + BTRFS_MEMBER1="$(mktemp /var/tmp/test-50-btrfs-member1.XXXXXXXXXX)" + BTRFS_MEMBER2="$(mktemp /var/tmp/test-50-btrfs-member2.XXXXXXXXXX)" + export BTRFS_MEMBER1 + export BTRFS_MEMBER2 + # mkfs.btrfs is known to be flaky in this environment (see above), so under 'set -e' tolerate a setup + # failure by degrading to skipping the btrfs MountImages= subtest (the consumer guards on -f) rather + # than aborting the whole TEST-50 run and taking down every unrelated subtest with it. + if ! { truncate -s 256M "$BTRFS_MEMBER1" "$BTRFS_MEMBER2" && mkfs.btrfs -draid1 -mraid1 "$BTRFS_MEMBER1" "$BTRFS_MEMBER2"; }; then + rm -f "$BTRFS_MEMBER1" "$BTRFS_MEMBER2" + BTRFS_MEMBER1='' BTRFS_MEMBER2='' + fi +fi + : "Run subtests" run_subtests diff --git a/test/units/TEST-50-DISSECT.sysext.sh b/test/units/TEST-50-DISSECT.sysext.sh index 60fb216ece674..fd62dcfe81740 100755 --- a/test/units/TEST-50-DISSECT.sysext.sh +++ b/test/units/TEST-50-DISSECT.sysext.sh @@ -1675,6 +1675,194 @@ run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h ) +( init_trap +: "Check EXTENSION_RESTART_UNITS= (re)starts units after merge and stops vanished ones on unmerge" +# Talks to the real service manager, skip the --root= variant (it is a no-op there) +if [ "$roots_dir" != "" ]; then + exit 0 +fi + +ext_name="test-restart-extension" +ext_dir="/var/lib/extensions/$ext_name" +host_unit="test-restart-host.service" +host_unit_file="/run/systemd/system/$host_unit" +ext_unit="test-restart-ext.service" + +cat >"$host_unit_file" </dev/null || :" +systemctl daemon-reload +systemctl start "$host_unit" +host_pid_before=$(systemctl show -P MainPID "$host_unit") + +mkdir -p "$ext_dir/usr/lib/extension-release.d" "$ext_dir/usr/lib/systemd/system" +prepend_trap "rm -rf ${ext_dir@Q}" +cat >"$ext_dir/usr/lib/extension-release.d/extension-release.$ext_name" <"$ext_dir/usr/lib/systemd/system/$ext_unit" <&2 "Unexpected restart of host unit" + exit 1 +fi +if systemctl --quiet is-active "$ext_unit"; then + echo >&2 "Unexpected start of extension unit" + exit 1 +fi +systemd-sysext unmerge --no-reload + +# merge: host unit is restarted (new PID), extension unit is started +systemd-sysext merge +host_pid_merged=$(systemctl show -P MainPID "$host_unit") +if [ "$host_pid_before" = "$host_pid_merged" ]; then + echo >&2 "Missing restart of host unit" + exit 1 +fi +if ! systemctl --quiet is-active "$ext_unit"; then + echo >&2 "Missing start of extension unit" + exit 1 +fi +host_pid_before_refresh="$host_pid_merged" +systemd-sysext refresh --always-refresh=yes +host_pid_merged=$(systemctl show -P MainPID "$host_unit") +if [ "$host_pid_before_refresh" = "$host_pid_merged" ]; then + echo >&2 "Missing restart of host unit after refresh" + exit 1 +fi +if ! systemctl --quiet is-active "$ext_unit"; then + echo >&2 "Missing start of extension unit" + exit 1 +fi + +# unmerge: host unit file is still in /run -> restart again (new PID) +# but extension unit file is gone -> fallback to StopUnit +systemd-sysext unmerge +host_pid_unmerged=$(systemctl show -P MainPID "$host_unit") +if [ "$host_pid_merged" = "$host_pid_unmerged" ]; then + echo >&2 "Missing restart of host unit" + exit 1 +fi +if systemctl --quiet is-active "$ext_unit"; then + echo >&2 "Missing stop of extension unit" + exit 1 +fi +) + +( init_trap +: "Check EXTENSION_RELOAD_OR_RESTART_UNITS= reloads units after merge and stops vanished ones on unmerge" +# Talks to the real service manager, skip the --root= variant (it is a no-op there) +if [ "$roots_dir" != "" ]; then + exit 0 +fi + +ext_name="test-reload-or-restart-extension" +ext_dir="/var/lib/extensions/$ext_name" +host_unit="test-reload-or-restart-host.service" +host_unit_file="/run/systemd/system/$host_unit" +ext_unit="test-reload-or-restart-ext.service" +host_stamp="/run/test-reload-or-restart-host.stamp" +ext_stamp="/run/test-reload-or-restart-ext.stamp" + +cat >"$host_unit_file" </dev/null || :" +systemctl daemon-reload +systemctl start "$host_unit" +host_pid_before=$(systemctl show -P MainPID "$host_unit") + +mkdir -p "$ext_dir/usr/lib/extension-release.d" "$ext_dir/usr/lib/systemd/system" +prepend_trap "rm -rf ${ext_dir@Q} ${ext_stamp@Q}" +cat >"$ext_dir/usr/lib/extension-release.d/extension-release.$ext_name" <"$ext_dir/usr/lib/systemd/system/$ext_unit" < reloaded (same PID, stamp file created), +# extension unit is started (no reload because it was not running yet) +rm -f "$host_stamp" "$ext_stamp" +systemd-sysext merge +host_pid_merged=$(systemctl show -P MainPID "$host_unit") +if [ "$host_pid_before" != "$host_pid_merged" ]; then + echo >&2 "Unexpected restart of host unit (should have reloaded)" + exit 1 +fi +if [ ! -e "$host_stamp" ]; then + echo >&2 "Host unit was not reloaded on merge (stamp missing)" + exit 1 +fi +if ! systemctl --quiet is-active "$ext_unit"; then + echo >&2 "Missing start of extension unit" + exit 1 +fi +ext_pid_merged=$(systemctl show -P MainPID "$ext_unit") + +# refresh: both units already running and reload-capable -> reloaded, MainPIDs unchanged, +# both stamp files re-created +rm -f "$host_stamp" "$ext_stamp" +systemd-sysext refresh --always-refresh=yes +host_pid_refreshed=$(systemctl show -P MainPID "$host_unit") +if [ "$host_pid_merged" != "$host_pid_refreshed" ]; then + echo >&2 "Unexpected restart of host unit on refresh (should have reloaded)" + exit 1 +fi +if [ ! -e "$host_stamp" ]; then + echo >&2 "Host unit was not reloaded on refresh (stamp missing)" + exit 1 +fi +ext_pid_refreshed=$(systemctl show -P MainPID "$ext_unit") +if [ "$ext_pid_merged" != "$ext_pid_refreshed" ]; then + echo >&2 "Unexpected restart of extension unit on refresh (should have reloaded)" + exit 1 +fi +if [ ! -e "$ext_stamp" ]; then + echo >&2 "Extension unit was not reloaded on refresh (stamp missing)" + exit 1 +fi + +# unmerge: host unit file still in /run -> reloaded again (same PID, stamp re-created) +# but extension unit file is gone -> fallback to StopUnit +rm -f "$host_stamp" "$ext_stamp" +systemd-sysext unmerge +host_pid_unmerged=$(systemctl show -P MainPID "$host_unit") +if [ "$host_pid_refreshed" != "$host_pid_unmerged" ]; then + echo >&2 "Unexpected restart of host unit on unmerge (should have reloaded)" + exit 1 +fi +if [ ! -e "$host_stamp" ]; then + echo >&2 "Host unit was not reloaded on unmerge (stamp missing)" + exit 1 +fi +if systemctl --quiet is-active "$ext_unit"; then + echo >&2 "Missing stop of extension unit" + exit 1 +fi +) + } # End of run_sysext_tests diff --git a/test/units/TEST-53-TIMER.RandomizedDelaySec-persistent.sh b/test/units/TEST-53-TIMER.RandomizedDelaySec-persistent.sh index af22daecc7668..e3005fe0f5df3 100755 --- a/test/units/TEST-53-TIMER.RandomizedDelaySec-persistent.sh +++ b/test/units/TEST-53-TIMER.RandomizedDelaySec-persistent.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # SPDX-License-Identifier: LGPL-2.1-or-later # -# Persistent timers (i.e. timers with Persitent=yes) save their last trigger timestamp to a persistent +# Persistent timers (i.e. timers with Persistent=yes) save their last trigger timestamp to a persistent # storage (a stamp file), which is loaded during subsequent boots. As mentioned in the man page, such timers # should be still affected by RandomizedDelaySec= during boot even if they already elapsed and would be then # triggered immediately. diff --git a/test/units/TEST-53-TIMER.restart-trigger.sh b/test/units/TEST-53-TIMER.restart-trigger.sh index 71de99cf2762e..6a4add9d638d5 100755 --- a/test/units/TEST-53-TIMER.restart-trigger.sh +++ b/test/units/TEST-53-TIMER.restart-trigger.sh @@ -25,13 +25,15 @@ EOF cat >"/run/systemd/system/$UNIT_NAME.service" <"$stress_ng_preflight_out" 2>&1; then + if grep -E "caught SIG(ILL|SEGV|BUS|FPE)" "$stress_ng_preflight_out" >/dev/null; then + STRESS_NG_BROKEN=1 + fi +fi +rm -f "$stress_ng_preflight_out" +unset stress_ng_preflight_out + # Activate swap file if we are in a VM if systemd-detect-virt --vm --quiet; then swapoff --all @@ -48,6 +62,13 @@ cat >/run/systemd/system/-.slice.d/99-oomd-test.conf </run/systemd/system/system.slice.d/99-oomd-test.conf </run/systemd/system/user@.service.d/99-oomd-test.conf </run/systemd/system/TEST-55-OOMD-testbloat.service.d/99-managed-oom-preference.conf </run/systemd/system/TEST-55-OOMD-testmunch.service.d/99-duration-test.conf </run/systemd/oomd/rules.d/testrule.oomrule <<'EOF' +[Rule] +MemoryPressureAbove=0% +Action=kill-all +LastingSec=0 +EOF + + systemctl reload systemd-oomd.service + + # Run a transient service with OOMRules=testrule that generates memory pressure + (! systemd-run --wait --unit=TEST-55-OOMD-testrules \ + -p MemoryHigh=3M \ + -p OOMRules=testrule \ + stress-ng --timeout 3m --vm 10 --vm-bytes 50M --vm-keep) + + # Verify in the journal that the rule triggered + journalctl --sync + journalctl -u systemd-oomd.service --since "-2min" | grep "Rule 'testrule' conditions met" >/dev/null + + # clean up + rm -f /run/systemd/oomd/rules.d/testrule.oomrule + systemctl reload systemd-oomd.service +} + +testcase_oom_rulesets_invalid_name() { + # Invalid rule names must be rejected at property-set time (filename_is_valid check). + # "foo/bar" contains a slash and "." and ".." are disallowed by filename_is_valid. + set +e + err=$(systemd-run --wait --unit=TEST-55-OOMD-badname1 -p 'OOMRules=foo/bar' true 2>&1) + rc=$? + set -e + [[ $rc -ne 0 ]] + echo "$err" | grep "Invalid rule name" >/dev/null + + set +e + err=$(systemd-run --wait --unit=TEST-55-OOMD-badname2 -p 'OOMRules=.' true 2>&1) + rc=$? + set -e + [[ $rc -ne 0 ]] + echo "$err" | grep "Invalid rule name" >/dev/null +} + +testcase_oom_rulesets_missing_warning() { + # A unit that references a ruleset which does not exist must produce a + # warn_missing_rulesets warning in oomd's journal (once, at subscription time). + mkdir -p /run/systemd/oomd/rules.d/ + rm -f /run/systemd/oomd/rules.d/absentrule.oomrule + systemctl reload systemd-oomd.service + + # Start a long-lived transient unit that references a ruleset that doesn't exist. + systemd-run --unit=TEST-55-OOMD-missing --remain-after-exit \ + -p OOMRules=absentrule \ + sleep infinity + + # Give oomd a moment to receive the subscription, then verify the warning fires once. + timeout 30 bash -c ' + until journalctl --sync && journalctl -u systemd-oomd.service --since "-1min" 2>/dev/null | grep "references undefined ruleset .absentrule." >/dev/null; do + sleep 1 + done + ' + + # And when we now add the ruleset and reload, oomd must pick it up without + # the unit needing to restart. Verify by checking for the debug-log line that + # reports the ruleset was registered. + cat >/run/systemd/oomd/rules.d/absentrule.oomrule <<'EOF' +[Rule] +SwapUsageMax=99% +Action=kill-all +LastingSec=0 +EOF + systemctl reload systemd-oomd.service + + journalctl --sync + journalctl -u systemd-oomd.service --since "-1min" | grep "Registered ruleset: absentrule" >/dev/null + + # cleanup + systemctl stop TEST-55-OOMD-missing.service + rm -f /run/systemd/oomd/rules.d/absentrule.oomrule + systemctl reload systemd-oomd.service +} + +testcase_oom_rulesets_lasting_sec() { + # A rule with LastingSec > 0 must NOT trigger during the waiting period. + # Baseline proof: with the same workload but LastingSec=0 (testcase_oom_rulesets + # above) oomd kills the unit within a couple of seconds, so an active unit after + # ~6 s demonstrates LastingSec is being respected. + [[ "$STRESS_NG_BROKEN" == "1" ]] && { echo "stress-ng is broken on this host, skipping ${FUNCNAME[0]}"; return 0; } + + mkdir -p /run/systemd/oomd/rules.d/ + cat >/run/systemd/oomd/rules.d/slowrule.oomrule <<'EOF' +[Rule] +MemoryPressureAbove=0% +Action=kill-all +LastingSec=1h +EOF + + systemctl reload systemd-oomd.service + + # Start the unit without --wait so we can check mid-run state. The + # stress-ng timeout bounds the test if anything goes wrong. + systemd-run --unit=TEST-55-OOMD-slowrule \ + -p MemoryHigh=3M \ + -p OOMRules=slowrule \ + stress-ng --timeout 15s --vm 10 --vm-bytes 50M --vm-keep + + # Wait long enough for oomd's 1s rule-check loop to evaluate the condition + # many times. With LastingSec=1h the kill must not fire. + sleep 6 + + # Unit must still be active — if it were killed, Result= would be oom-kill. + assert_eq "$(systemctl show TEST-55-OOMD-slowrule.service -P ActiveState)" "active" + assert_eq "$(systemctl show TEST-55-OOMD-slowrule.service -P Result)" "success" + + systemctl stop TEST-55-OOMD-slowrule.service 2>/dev/null || true + + # cleanup + rm -f /run/systemd/oomd/rules.d/slowrule.oomrule + systemctl reload systemd-oomd.service +} + testcase_prekill_hook() { + [[ "$STRESS_NG_BROKEN" == "1" ]] && { echo "stress-ng is broken on this host, skipping ${FUNCNAME[0]}"; return 0; } + cat >/run/systemd/oomd.conf.d/99-oomd-prekill-test.conf <<'EOF' [OOM] PrekillHookTimeoutSec=3s diff --git a/test/units/TEST-58-REPART.sh b/test/units/TEST-58-REPART.sh index f25cf42c4b3ee..88d486ebc0550 100755 --- a/test/units/TEST-58-REPART.sh +++ b/test/units/TEST-58-REPART.sh @@ -20,6 +20,13 @@ export PAGER=cat # Disable use of special glyphs such as → export SYSTEMD_UTF8=0 +# Sanitizer runs are significantly slower, so give udevadm wait 3 times longer timeouts +if [[ -v ASAN_OPTIONS || -v UBSAN_OPTIONS ]]; then + UDEVADM_WAIT_TIMEOUT=180 +else + UDEVADM_WAIT_TIMEOUT=60 +fi + seed=750b6cd5c4ae4012a15e7be3c29e6a47 esp_guid=C12A7328-F81F-11D2-BA4B-00A0C93EC93B @@ -378,7 +385,9 @@ $imgs/zzz7 : start= 6291416, size= 131072, type=3B8F8425-20E0-4F3B-907F fi loop="$(losetup -P --show --find "$imgs/zzz")" - udevadm wait --timeout=60 --settle "${loop:?}p7" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p7" + + cryptsetup luksDump "${loop}p7" | grep 'Flags:[[:space:]]*allow-discards' >/dev/null volume="test-repart-$RANDOM" @@ -396,6 +405,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() { @@ -447,8 +500,8 @@ EOF "raw_size" : 33554432, "size" : "-> 32M", "old_padding" : 0, - "raw_padding" : 0, - "padding" : "-> 0B", + "raw_padding" : 70234112, + "padding" : "-> 66.9M", "activity" : "create", "drop-in_files" : [ "$defs/root.conf.d/override1.conf", @@ -524,8 +577,8 @@ EOF "raw_size" : 33554432, "size" : "-> 32M", "old_padding" : 0, - "raw_padding" : 0, - "padding" : "-> 0B", + "raw_padding" : 36679680, + "padding" : "-> 34.9M", "activity" : "create" } ] @@ -653,6 +706,149 @@ EOF assert_in "$imgs/unaligned3 : start= 3662944, size= 17308536, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\"" "$output" } +testcase_output_order() { + local defs imgs output + + defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")" + imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")" + # shellcheck disable=SC2064 + trap "rm -rf '$defs' '$imgs'" RETURN + chmod 0755 "$defs" + + echo "*** Ensure the order of the partition list is correct ***" + + # make this one min size 20MiB so that it has to be assigned slot 4 + tee "$defs/01-home.conf" < 0B", + "activity" : "unchanged" + }, + { + "type" : "swap", + "label" : "swap", + "uuid" : "78c92db8-3d2b-4823-b0dc-792b78f66f1e", + "partno" : 3, + "file" : "$defs/02-swap.conf", + "node" : "$imgs/order4", + "offset" : 16777216, + "old_size" : 0, + "raw_size" : 14680064, + "size" : "-> 14M", + "old_padding" : 0, + "raw_padding" : 0, + "padding" : "-> 0B", + "activity" : "create" + }, + { + "type" : "esp", + "label" : "esp", + "uuid" : "91c30bc9-0187-4db6-81a2-c648294197f8", + "partno" : 2, + "file" : "$defs/03-esp.conf", + "node" : "$imgs/order3", + "offset" : 31457280, + "old_size" : 10485760, + "raw_size" : 26202112, + "size" : "10M -> 24.9M", + "old_padding" : 41922560, + "raw_padding" : 0, + "padding" : "39.9M -> 0B", + "activity" : "resize" + }, + { + "type" : "home", + "label" : "home", + "uuid" : "4980595d-d74a-483a-aa9e-9903879a0ee5", + "partno" : 4, + "file" : "$defs/01-home.conf", + "node" : "$imgs/order5", + "offset" : 57659392, + "old_size" : 0, + "raw_size" : 26206208, + "size" : "-> 24.9M", + "old_padding" : 0, + "raw_padding" : 0, + "padding" : "-> 0B", + "activity" : "create" + } +] +EOF + + output=$(sfdisk --dump "$imgs/order") + + assert_in "$imgs/order1 : start= 2048, size= 20480, type=${root_guid}," "$output" + assert_in "$imgs/order2 : start= 22528, size= 10240, type=${xbootldr_guid}," "$output" + assert_in "$imgs/order3 : start= 61440, size= 51176, type=${esp_guid}," "$output" + assert_in "$imgs/order4 : start= 32768, size= 28672, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F," "$output" + assert_in "$imgs/order5 : start= 112616, size= 51184, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915," "$output" +} + testcase_issue_21817() { local defs imgs output @@ -895,6 +1091,7 @@ EOF --dry-run=no \ --empty=create \ --size=auto \ + --split=yes \ --json=pretty \ --private-key="$defs/verity.key" \ --certificate="$defs/verity.crt" \ @@ -907,6 +1104,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" \ @@ -1004,7 +1208,7 @@ EOF # shellcheck disable=SC2064 trap "rm -rf '$defs' '$imgs' ; losetup -d '$loop'" RETURN ERR - udevadm wait --timeout=60 --settle "${loop:?}p1" "${loop:?}p2" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1" "${loop:?}p2" # Check that the verity block sizes are as expected veritysetup dump "${loop}p2" | grep 'Data block size:' | grep '4096' >/dev/null @@ -1064,7 +1268,7 @@ EOF # shellcheck disable=SC2064 trap "rm -rf '$defs' '$imgs' ; losetup -d '$loop'" RETURN ERR - udevadm wait --timeout=60 --settle "${loop:?}p1" "${loop:?}p2" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1" "${loop:?}p2" output=$(sfdisk -J "$loop") @@ -1146,7 +1350,7 @@ EOF fi loop=$(losetup -P --show -f "$imgs/zzz") - udevadm wait --timeout=60 --settle "${loop:?}p1" "${loop:?}p2" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1" "${loop:?}p2" # Test that /usr/def did not end up in the root partition but other files did. mkdir "$imgs/mnt" @@ -1208,6 +1412,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" </dev/null || true; losetup -d '$loop' 2>/dev/null || true; rm -rf '$defs' '$imgs'" RETURN echo "Loop device: $loop" - udevadm wait --timeout=60 --settle "${loop:?}p1" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1" mkdir -p "$imgs/mount" mount -t btrfs "${loop:?}p1" "$imgs/mount" @@ -1770,6 +1990,174 @@ testcase_varlink_list_devices() { varlinkctl call /run/systemd/io.systemd.Repart --graceful=io.systemd.Repart.NoCandidateDevices --collect io.systemd.Repart.ListCandidateDevices '{"ignoreEmpty":true,"ignoreRoot":true}' } +testcase_varlink_subscribe_devices() { + local imgs subscribe_log pre_existing_log loop loop2 mark non_sub_output + local sub_unit="test-repart-subscribe.service" + local pre_unit="test-repart-subscribe-pre.service" + + # The uevent monitor in the spawned systemd-repart only sees events from the host's kernel + # uevent namespace, which an nspawn container with --private-network filters away. Loopback + # creation also fails inside many container setups. + if systemd-detect-virt --container >/dev/null 2>&1; then + echo "Skipping subscribe tests inside container." + return + fi + + REPART="$(which systemd-repart)" + imgs="$(mktemp --directory "/var/tmp/test-repart.subscribe.XXXXXXXXXX")" + subscribe_log="$(mktemp "/var/tmp/test-repart.subscribe-log.XXXXXXXXXX")" + pre_existing_log="$(mktemp "/var/tmp/test-repart.pre-log.XXXXXXXXXX")" + loop="" + loop2="" + + # Single-quoted trap body so $loop / $loop2 are expanded at trap-fire time, not now. + # shellcheck disable=SC2016 + trap ' + systemctl stop "$sub_unit" "$pre_unit" 2>/dev/null || true + [[ -n "${loop:-}" ]] && systemd-dissect --detach "$loop" 2>/dev/null || true + [[ -n "${loop2:-}" ]] && systemd-dissect --detach "$loop2" 2>/dev/null || true + rm -rf "$imgs" "$subscribe_log" "$subscribe_log.err" "$pre_existing_log" "$pre_existing_log.err" + ' RETURN + + # systemd-dissect --attach requires a dissectable image; a plain ext4 single-filesystem image + # is the simplest thing it accepts. + truncate -s 50M "$imgs/loop.img" + truncate -s 50M "$imgs/loop2.img" + mkfs.ext4 -F -q -L test-repart-1 "$imgs/loop.img" + mkfs.ext4 -F -q -L test-repart-2 "$imgs/loop2.img" + + # Make sure no stale unit is sitting around from a previous failed run, then arrange cleanup. + systemctl reset-failed "$sub_unit" "$pre_unit" 2>/dev/null || true + systemctl stop "$sub_unit" "$pre_unit" 2>/dev/null || true + + # Start a varlinkctl subscriber as a transient Type=notify service. systemd-run blocks until the + # service notifies READY=1 -- which varlinkctl does on receipt of the first reply (see + # src/varlinkctl/varlinkctl.c reply_callback). For us that's either the first "add" of the + # initial enumeration or the "ready" sentinel; either way we are guaranteed the server-side + # monitor is up before we trigger any uevents. + start_subscriber() { + local unit="$1" log="$2" params="$3" + + systemd-run \ + --quiet \ + --unit="$unit" \ + --collect \ + --service-type=notify \ + --property=StandardOutput="truncate:$log" \ + --property=StandardError="truncate:$log.err" \ + -- \ + varlinkctl --more --timeout=infinity --json=short call \ + "$REPART" io.systemd.Repart.ListCandidateDevices "$params" + } + + # Poll for a regex match in $1 of the named log file ($3, default $subscribe_log), starting from + # byte offset $2. Prints the new byte offset (post-match) on stdout so the caller can advance. + expect_event() { + local pat="$1" mark_="$2" file="${3:-$subscribe_log}" + local deadline_=$((SECONDS + UDEVADM_WAIT_TIMEOUT)) + local cur + + while (( SECONDS < deadline_ )); do + cur=$(stat -c%s "$file" 2>/dev/null || echo 0) + if (( cur > mark_ )) && \ + tail -c +"$((mark_ + 1))" "$file" | grep -a -E "$pat" >/dev/null; then + printf '%s\n' "$cur" + return 0 + fi + sleep 0.1 + done + + echo "FAIL: pattern '$pat' did not appear within ${UDEVADM_WAIT_TIMEOUT}s. Output past offset $mark_:" >&2 + tail -c +"$((mark_ + 1))" "$file" >&2 || true + return 1 + } + + # === Test 1: subscribing produces a "ready" sentinel even on an empty initial set === + start_subscriber "$sub_unit" "$subscribe_log" \ + '{"ignoreRoot":true,"ignoreEmpty":true,"subscribe":true}' + mark=$(expect_event '"action":"ready"' 0) || return 1 + + # === Test 2: a freshly-added loopback produces an "add" event === + loop="$(systemd-dissect --attach --loop-ref=test-repart-1 "$imgs/loop.img")" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "$loop" + mark=$(expect_event "\"action\":\"add\".*\"node\":\"$loop\"" "$mark") || return 1 + + # === Test 3: detaching the loopback produces a "remove" event === + systemd-dissect --detach "$loop" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --removed --settle "$loop" + mark=$(expect_event "\"action\":\"remove\".*\"node\":\"$loop\"" "$mark") || return 1 + loop="" + + # === Test 4: five add/remove cycles in a row stay in sync === + for _ in 1 2 3 4 5; do + loop="$(systemd-dissect --attach --loop-ref=test-repart-1 "$imgs/loop.img")" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "$loop" + mark=$(expect_event "\"action\":\"add\".*\"node\":\"$loop\"" "$mark") || return 1 + + systemd-dissect --detach "$loop" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --removed --settle "$loop" + mark=$(expect_event "\"action\":\"remove\".*\"node\":\"$loop\"" "$mark") || return 1 + loop="" + done + + # === Test 5: two coexisting loop devices each produce their own events === + # Set them up (and tear them down) one at a time and wait for each device in turn, so events + # appear in a strict order and we can advance our mark monotonically. + loop="$(systemd-dissect --attach --loop-ref=test-repart-1 "$imgs/loop.img")" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "$loop" + mark=$(expect_event "\"action\":\"add\".*\"node\":\"$loop\"" "$mark") || return 1 + + loop2="$(systemd-dissect --attach --loop-ref=test-repart-2 "$imgs/loop2.img")" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "$loop2" + mark=$(expect_event "\"action\":\"add\".*\"node\":\"$loop2\"" "$mark") || return 1 + + systemd-dissect --detach "$loop2" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --removed --settle "$loop2" + mark=$(expect_event "\"action\":\"remove\".*\"node\":\"$loop2\"" "$mark") || return 1 + loop2="" + + systemd-dissect --detach "$loop" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --removed --settle "$loop" + mark=$(expect_event "\"action\":\"remove\".*\"node\":\"$loop\"" "$mark") || return 1 + loop="" + + # Tear down the first subscriber before starting the next one. + systemctl stop "$sub_unit" + + # === Test 6: a device that exists *before* subscribe starts shows up in the initial enumeration, + # and "ready" arrives only after it === + loop="$(systemd-dissect --attach --loop-ref=test-repart-1 "$imgs/loop.img")" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "$loop" + + start_subscriber "$pre_unit" "$pre_existing_log" \ + '{"ignoreRoot":true,"subscribe":true}' + + # systemd-run returned (Type=notify => first reply already received), but "ready" may not be + # written yet -- poll for it. + expect_event '"action":"ready"' 0 "$pre_existing_log" >/dev/null || return 1 + + # Everything up to (but excluding) "ready" is the initial enumeration; it must contain $loop. + if ! sed -n '/"action":"ready"/q;p' "$pre_existing_log" | \ + grep -a -E "\"action\":\"add\".*\"node\":\"$loop\"" >/dev/null; then + echo "FAIL: pre-existing loop device $loop not in initial enumeration:" >&2 + cat "$pre_existing_log" >&2 + return 1 + fi + + systemctl stop "$pre_unit" + + # === Test 7: without subscribe the reply has no "action" field (back-compat for older clients) === + non_sub_output="$(varlinkctl --collect --json=short call \ + "$REPART" --graceful=io.systemd.Repart.NoCandidateDevices \ + io.systemd.Repart.ListCandidateDevices '{"ignoreRoot":true}')" + assert_not_in '"action"' "$non_sub_output" + # Sanity-check: our $loop *is* in that output (so the assertion above wasn't vacuous). + assert_in "\"node\":\"$loop\"" "$non_sub_output" + + systemd-dissect --detach "$loop" + loop="" +} + testcase_get_size() { local defs @@ -1870,7 +2258,7 @@ EOF "$imgs/encint.img" loop="$(losetup -P --show --find "$imgs/encint.img")" - udevadm wait --timeout=60 --settle "${loop:?}p1" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1" volume="test-repart-luksint-$RANDOM" dmstatus="$imgs/dmsetup-$RANDOM" @@ -1878,7 +2266,7 @@ EOF touch "$imgs/empty-password" # the expectation for hmac-sha256 is 'integrity: hmac(sha256)' - cryptsetup luksDump "${loop}p1" | grep -q "integrity: $(echo "$1" | sed -r 's/^hmac-(.*)$/hmac(\1)/')" + cryptsetup luksDump "${loop}p1" | grep "integrity: $(echo "$1" | sed -r 's/^hmac-(.*)$/hmac(\1)/')" >/dev/null cryptsetup open --type=luks2 --key-file="$imgs/empty-password" "${loop}p1" "$volume" dmsetup status > "$dmstatus" @@ -1975,7 +2363,7 @@ EOF "$imgs/enckeyhash.img" loop="$(losetup -P --show --find "$imgs/enckeyhash.img")" - udevadm wait --timeout=60 --settle "${loop:?}p1" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1" touch "$imgs/empty-password" @@ -2035,7 +2423,7 @@ EOF "$imgs/fstabcrypttabrepart.img" loop="$(losetup -P --show --find "$imgs/fstabcrypttabrepart.img")" - udevadm wait --timeout=60 --settle "${loop:?}p1" + udevadm wait --timeout="$UDEVADM_WAIT_TIMEOUT" --settle "${loop:?}p1" touch "$imgs/empty-password" @@ -2059,6 +2447,145 @@ EOF losetup -d "$loop" } +testcase_block_device_replace() { + if [[ "$OFFLINE" == "yes" ]]; then + return 0 + fi + + if ! command -v btrfs >/dev/null; then + echo "btrfs not found, skipping." + return 0 + fi + + if ! command -v mkfs.btrfs >/dev/null; then + echo "mkfs.btrfs not found, skipping." + return 0 + fi + + local defs imgs btrfs_mntpoint_plain btrfs_mntpoint_encrypted + local loop loop_btrfs_plain loop_btrfs_encrypted + local dm_btrfs_encrypted encrypted_device + + btrfs_mntpoint_plain="$(mktemp --directory "/tmp/test-repart.btrfs-mntpoint-plain.XXXXXXXXXX")" + btrfs_mntpoint_encrypted="$(mktemp --directory "/tmp/test-repart.btrfs-mntpoint-encrypted.XXXXXXXXXX")" + defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")" + imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")" + # shellcheck disable=SC2064 + trap "rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + chmod 0755 "$defs" + + truncate --size 500M "${imgs}/btrfs-plain" + mkfs.btrfs "${imgs}/btrfs-plain" + loop_btrfs_plain="$(losetup --show --find "$imgs/btrfs-plain")" + # shellcheck disable=SC2064 + trap "losetup -d '${loop_btrfs_plain}'; rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + + mount "${loop_btrfs_plain}" "${btrfs_mntpoint_plain}" + echo tada >"${btrfs_mntpoint_plain}/magic-plain" + + # shellcheck disable=SC2064 + trap "umount '${btrfs_mntpoint_plain}'; losetup -d '${loop_btrfs_plain}'; rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + + truncate --size 500M "${imgs}/btrfs-encrypted" + mkfs.btrfs "${imgs}/btrfs-encrypted" + loop_btrfs_encrypted="$(losetup --show --find "$imgs/btrfs-encrypted")" + # shellcheck disable=SC2064 + trap "losetup -d '${loop_btrfs_encrypted}'; umount '${btrfs_mntpoint_plain}'; losetup -d '${loop_btrfs_plain}'; rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + + mount "${loop_btrfs_encrypted}" "${btrfs_mntpoint_encrypted}" + echo tada >"${btrfs_mntpoint_encrypted}/magic-encrypted" + + # shellcheck disable=SC2064 + trap "umount '${btrfs_mntpoint_encrypted}'; losetup -d '${loop_btrfs_encrypted}'; umount '${btrfs_mntpoint_plain}'; losetup -d '${loop_btrfs_plain}'; rm -rf '$defs' '$imgs' '$btrfs_mntpoint_plain' '$btrfs_mntpoint_encrypted'" RETURN + + truncate --size 2G "${imgs}/img" + + tee "$defs/01-plain.conf" </dev/null; do sleep 1; done' + else + # We never entered the rate limit state (e.g. on slow infra), so the recovery path above is not + # being exercised. Just make sure the mount units are cleaned up, nudging the manager if needed. + timeout 2m bash -c 'while systemctl list-units -t mount tmp-meow* | grep tmp-meow >/dev/null; do systemctl daemon-reload; sleep 10; done' fi - - # Verify that the mount units are always cleaned up at the end. - # Give some time for units to settle so we don't race between exiting the rate limit state and cleaning up the units. - timeout 2m bash -c 'while systemctl list-units -t mount tmp-meow* | grep tmp-meow >/dev/null; do systemctl daemon-reload; sleep 10; done' } mkdir -p /run/systemd/journald.conf.d diff --git a/test/units/TEST-64-UDEV-STORAGE.sh b/test/units/TEST-64-UDEV-STORAGE.sh index f7bc59ff44a1b..afab7d4ff4bb3 100755 --- a/test/units/TEST-64-UDEV-STORAGE.sh +++ b/test/units/TEST-64-UDEV-STORAGE.sh @@ -178,7 +178,7 @@ testcase_nvme_basic() { local expected_symlinks=() local i - for i in {0..4}; do + for i in {0..2}; do expected_symlinks+=( # both replace mode provides the same devlink /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef"$i" @@ -186,7 +186,7 @@ testcase_nvme_basic() { /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef"$i"_1 ) done - for i in {5..9}; do + for i in {3..5}; do expected_symlinks+=( # old replace mode /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl__deadbeef_"$i" @@ -196,7 +196,7 @@ testcase_nvme_basic() { /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____deadbeef__"$i"_1 ) done - for i in {10..14}; do + for i in {6..8}; do expected_symlinks+=( # old replace mode does not provide devlink, as serial contains "/" # newer replace mode @@ -205,7 +205,7 @@ testcase_nvme_basic() { /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____dead_beef_"$i"_1 ) done - for i in {15..19}; do + for i in {9..11}; do expected_symlinks+=( # old replace mode does not provide devlink, as serial contains "/" # newer replace mode @@ -222,7 +222,7 @@ testcase_nvme_basic() { test ! -e /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef lsblk --noheadings | grep "^nvme" - [[ "$(lsblk --noheadings | grep -c "^nvme")" -ge 20 ]] + [[ "$(lsblk --noheadings | grep -c "^nvme")" -ge 12 ]] } testcase_nvme_subsystem() { @@ -266,9 +266,8 @@ EOF testcase_multipath_basic_failover() { local dmpath i path wwid - . /etc/os-release - if [[ "${ID_LIKE:-}" == "alpine" ]]; then - echo "multipath on alpine/postmarketos is broken, skipping the test" | tee --append /skipped + if ! systemctl list-unit-files multipathd.service >/dev/null; then + echo "This test requires multipathd.service but it is not installed, skipping ..." | tee --append /skipped exit 77 fi @@ -849,6 +848,10 @@ EOF btrfs filesystem show helper_check_device_symlinks helper_check_device_units + # Wipe the btrfs signature from each partition first, otherwise the superblocks remain inside + # the disk's data area and would be discovered again as duplicate UUIDs after re-partitioning, + # which breaks subsequent runs of this test (e.g. after a VM reboot). + udevadm lock --timeout=30 --device="${devices[0]}" wipefs -a /dev/disk/by-partlabel/diskpart{1..4} udevadm lock --timeout=30 --device="${devices[0]}" wipefs -a "${devices[0]}" udevadm wait --settle --timeout=30 --removed /dev/disk/by-partlabel/diskpart{1..4} @@ -866,6 +869,12 @@ EOF btrfs filesystem show helper_check_device_symlinks helper_check_device_units + # Wipe the btrfs signatures so that subsequent sections (and runs of the test, e.g. after a VM + # reboot) don't see the stale UUID. + for ((i = 0; i < ${#devices[@]}; i++)); do + udevadm lock --timeout=30 --device="${devices[$i]}" wipefs -a "${devices[$i]}" + done + udevadm settle --timeout=30 echo "Multiple devices: using LUKS encrypted disks, data: raid1, metadata: raid1, mixed mode" uuid="deadbeef-dead-dead-beef-000000000003" @@ -941,7 +950,13 @@ EOF sed -i "/${mpoint##*/}/d" /etc/fstab : >/etc/crypttab rm -fr "$mpoint" + rm -f /etc/btrfs_keyfile systemctl daemon-reload + # Wipe LUKS headers from the underlying devices, so that if the VM is rebooted the disks don't retain + # stale LUKS signatures that would interfere with a re-run of the test. + for ((i = 0; i < ${#devices[@]}; i++)); do + udevadm lock --timeout=30 --device="${devices[$i]}" wipefs -a "${devices[$i]}" + done udevadm settle --timeout=30 } @@ -1333,6 +1348,12 @@ testcase_mdadm_lvm() { helper_check_device_units # Cleanup lvm vgchange -an "$vgroup" + # Wipe the LVM signature off the MD device, otherwise the underlying disks + # still hold the PV header at the same offset. If the VM is restarted (e.g. + # the test gets re-run because of a reboot), mdadm --create with the same + # UUID would expose the same data and udev would auto-trigger + # lvm-activate-${vgroup}.service, racing with the test's pvcreate. + wipefs --all "$raid_dev" mdadm -v --stop "$raid_dev" # Clear superblocks to make the MD device will not be restarted even if the VM is restarted. @@ -1340,6 +1361,10 @@ testcase_mdadm_lvm() { udevadm settle --timeout=30 # shellcheck disable=SC2046 mdadm -v --zero-superblock --force $(readlink -f "${devices[@]}") + # Also wipe any leftover signatures from the underlying disks for the same + # reason as above. + # shellcheck disable=SC2046 + wipefs --all $(readlink -f "${devices[@]}") udevadm settle --timeout=30 # Check if all expected symlinks were removed after the cleanup @@ -1350,6 +1375,32 @@ testcase_mdadm_lvm() { udevadm settle lsblk -a +# This test often fails because the VM reboots (eg: kernel panic) and the previous state is left unclean. +# Do the cleanups at the beginning too, to try and reduce flakiness. +mdadm --stop --scan || : +for _dm in /dev/mapper/encbtrfs[0-3] /dev/mapper/encdisk[0-3] /dev/mapper/lvmluksmap; do + [[ -e "$_dm" ]] || continue + cryptsetup close "$_dm" || : +done +unset _dm +for _vg in $(lvm vgs --noheadings -o vg_name 2>/dev/null \ + | awk '$1 ~ /^(MyTestGroup|iscsi_lvm|mdmirpar_vg)/ {print $1}'); do + lvm vgchange -an "$_vg" || : +done +unset _vg +udevadm settle --timeout=30 +_stale_devs=() +for _d in /dev/disk/by-id/scsi-0systemd_foobar_deadbeef*; do + [[ -e "$_d" ]] && _stale_devs+=("$_d") +done +if (( ${#_stale_devs[@]} > 0 )); then + # shellcheck disable=SC2046 + mdadm -v --zero-superblock --force $(readlink -f "${_stale_devs[@]}") || : + wipefs --all "${_stale_devs[@]}" || : +fi +unset _d _stale_devs +udevadm settle --timeout=30 + echo "Check if all symlinks under /dev/disk/ are valid (pre-test)" helper_check_device_symlinks diff --git a/test/units/TEST-65-ANALYZE.sh b/test/units/TEST-65-ANALYZE.sh index f98ececc53be5..a470883aff9ee 100755 --- a/test/units/TEST-65-ANALYZE.sh +++ b/test/units/TEST-65-ANALYZE.sh @@ -1015,10 +1015,24 @@ if systemd-analyze --version | grep -F "+ELFUTILS" >/dev/null; then # For some unknown reason the .note.dlopen sections are removed when building with sanitizers, so only # run this test if we're not running under sanitizers. - if [[ ! -v ASAN_OPTIONS ]]; then + # Also the linker removes them on CentOS 9 as binutils < 2.36 does not support the SHF_GNU_RETAIN flag + if [[ ! -v ASAN_OPTIONS ]] && ( . /etc/os-release; [[ ! "$ID $ID_LIKE" =~ centos|rhel ]] || systemd-analyze compare-versions "$VERSION_ID" ge 10 ); then shared="$(ldd /lib/systemd/systemd | grep shared | cut -d' ' -f3)" systemd-analyze dlopen-metadata "$shared" systemd-analyze dlopen-metadata --json=short "$shared" + + dlopen_json="$(systemd-analyze dlopen-metadata --json=short "$shared")" + assert_eq "$(echo "$dlopen_json" | jq 'type')" '"array"' + assert_ge "$(echo "$dlopen_json" | jq 'length')" 1 + # Every entry must have the four fields with the expected types and a valid priority. + assert_eq "$(echo "$dlopen_json" | jq 'all( + (.feature | type == "string") and + (.description | type == "string") and + (.priority | IN("required", "recommended", "suggested")) and + (.soname | type == "array" and length > 0 and all(type == "string")) + )')" 'true' + # libmount is unconditionally pulled into libsystemd-shared, so its note must be present and correct. + assert_eq "$(echo "$dlopen_json" | jq 'any(.feature == "mount" and (.soname | index("libmount.so.1") != null))')" 'true' fi fi diff --git a/test/units/TEST-67-INTEGRITY.sh b/test/units/TEST-67-INTEGRITY.sh index 667fa56343401..a57d7d9db279f 100755 --- a/test/units/TEST-67-INTEGRITY.sh +++ b/test/units/TEST-67-INTEGRITY.sh @@ -2,8 +2,6 @@ # SPDX-License-Identifier: LGPL-2.1-or-later set -euxo pipefail -. /etc/os-release - DM_NAME="integrity_test" DM_NODE="/dev/mapper/${DM_NAME}" DM_SERVICE="systemd-integritysetup@${DM_NAME}.service" @@ -119,16 +117,23 @@ EOF } for a in crc32c crc32 xxhash64 sha1 sha256; do - if [[ "$a" == crc32 && "${ID_LIKE:-}" == alpine ]]; then - # crc32 is not supported on alpine/postmarketos ?? - # -------- - # [ 22.419458] TEST-67-INTEGRITY.sh[3085]: + integritysetup format /dev/loop0 --batch-mode -I crc32 '' - # [ 22.433168] kernel: device-mapper: table: 253:0: integrity: Invalid internal hash (-ENOENT) - # [ 22.433220] TEST-67-INTEGRITY.sh[3475]: device-mapper: reload ioctl on temporary-cryptsetup-6b3b80ef-6854-4102-8239-6360f15af0c3 (253:0) failed: No such file or directory - # [ 22.433220] TEST-67-INTEGRITY.sh[3475]: Cannot format integrity for device /dev/loop0. - # [ 22.433835] kernel: device-mapper: ioctl: error adding target to table - # -------- - continue; + # dm-integrity uses crypto_alloc_shash() which triggers request_module() + # for the underlying hash algorithm when needed. That auto-load has been + # observed to fail flakily in some test environments, leading to errors + # like: + # kernel: device-mapper: table: NNN:N: integrity: Invalid internal hash (-ENOENT) + # integritysetup: Cannot format integrity for device /dev/loopN. + # Try to load the kernel module ahead of time to avoid that. Failure is + # acceptable here: the algorithm might be built-in (no module to load) or + # genuinely unsupported, in which case the next check will skip it. + modprobe -q "crypto-$a" || : + + # Some algorithms are not supported on certain platforms (e.g. crc32 is + # missing on Alpine/postmarketOS). Skip them at runtime to avoid spurious + # failures. + if ! grep -q -E "^name\s+: $a\$" /proc/crypto; then + echo "Algorithm '$a' is not supported on this system, skipping." + continue fi test_one "$a" 0 diff --git a/test/units/TEST-69-SHUTDOWN.py b/test/units/TEST-69-SHUTDOWN.py index 51569bf51ef34..a208e42f07847 100755 --- a/test/units/TEST-69-SHUTDOWN.py +++ b/test/units/TEST-69-SHUTDOWN.py @@ -10,56 +10,58 @@ def main(): # TODO: drop once https://bugs.debian.org/1075733 is fixed - with open("/usr/lib/os-release") as f: + with open('/usr/lib/os-release') as f: for line in f: - if line.startswith("ID="): - if "debian" in line or "ubuntu" in line: + if line.startswith('ID='): + if 'debian' in line or 'ubuntu' in line: sys.exit(77) - logger = logging.getLogger("test-shutdown") + logger = logging.getLogger('test-shutdown') consoles = [] for _ in range(2): # Use script to allocate a separate pseudo tty to run the login shell in. console = pexpect.spawn( - "script", ["--quiet", "--return", "--flush", "--command", "login -f root", "/dev/null"], + 'script', + ['--quiet', '--return', '--flush', '--command', 'login -f root', '/dev/null'], logfile=sys.stdout, - env={"TERM": "dumb"}, - encoding="utf-8", + env={'TERM': 'dumb'}, + encoding='utf-8', timeout=60, ) - logger.info("waiting for login prompt") - console.expect(".*# ", 10) + logger.info('waiting for login prompt') + console.expect('.*# ', 10) consoles += [console] - consoles[1].sendline("tty") - consoles[1].expect(r"/dev/(pts/\d+)") + consoles[1].sendline('tty') + consoles[1].expect(r'/dev/(pts/\d+)') pty = console.match.group(1) - logger.info("window 1 at tty %s", pty) + logger.info('window 1 at tty %s', pty) - logger.info("schedule reboot") - consoles[1].sendline("shutdown -r") + logger.info('schedule reboot') + consoles[1].sendline('shutdown -r') consoles[1].expect("Reboot scheduled for (?P.*), use 'shutdown -c' to cancel", 2) - date = consoles[1].match.group("date") - logger.info("reboot scheduled for %s", date) + date = consoles[1].match.group('date') + logger.info('reboot scheduled for %s', date) - logger.info("verify broadcast message") - consoles[0].expect(f"Broadcast message from root@H on {pty}", 2) - consoles[0].expect(f"The system will reboot at {date}!", 2) + logger.info('verify broadcast message') + consoles[0].expect(f'Broadcast message from root@H on {pty}', 2) + consoles[0].expect(f'The system will reboot at {date}!', 2) - logger.info("check show output") - consoles[1].sendline("shutdown --show") + logger.info('check show output') + consoles[1].sendline('shutdown --show') consoles[1].expect(f"Reboot scheduled for {date}, use 'shutdown -c' to cancel", 2) - logger.info("cancel shutdown") - consoles[1].sendline("shutdown -c") - consoles[0].expect("System shutdown has been cancelled", 2) + logger.info('cancel shutdown') + consoles[1].sendline('shutdown -c') + consoles[0].expect('System shutdown has been cancelled', 2) - consoles[0].sendline("> /testok") + consoles[0].sendline('> /testok') -if __name__ == "__main__": + +if __name__ == '__main__': main() # vim: sw=4 et diff --git a/test/units/TEST-70-TPM2.creds.sh b/test/units/TEST-70-TPM2.creds.sh index 15899d1057899..53ff862e18cd5 100755 --- a/test/units/TEST-70-TPM2.creds.sh +++ b/test/units/TEST-70-TPM2.creds.sh @@ -5,6 +5,12 @@ set -o pipefail export SYSTEMD_LOG_LEVEL=debug +at_exit() { + rm -f /tmp/testdata /tmp/testdata.encrypted +} + +trap at_exit EXIT + # Ensure that sandboxing doesn't stop creds from being accessible echo "test" >/tmp/testdata systemd-creds encrypt /tmp/testdata /tmp/testdata.encrypted --with-key=tpm2 @@ -12,5 +18,3 @@ systemd-creds encrypt /tmp/testdata /tmp/testdata.encrypted --with-key=tpm2 systemd-run -p PrivateDevices=yes -p LoadCredentialEncrypted=testdata.encrypted:/tmp/testdata.encrypted --pipe --wait systemd-creds cat testdata.encrypted | cmp - /tmp/testdata # SetCredentialEncrypted systemd-run -p PrivateDevices=yes -p SetCredentialEncrypted=testdata.encrypted:"$(cat /tmp/testdata.encrypted)" --pipe --wait systemd-creds cat testdata.encrypted | cmp - /tmp/testdata - -rm -f /tmp/testdata diff --git a/test/units/TEST-70-TPM2.cryptenroll.sh b/test/units/TEST-70-TPM2.cryptenroll.sh index f18ef020a75e9..684ff418f1dee 100755 --- a/test/units/TEST-70-TPM2.cryptenroll.sh +++ b/test/units/TEST-70-TPM2.cryptenroll.sh @@ -11,6 +11,12 @@ cryptenroll_wipe_and_check() {( grep -qE "Wiped slot [[:digit:]]+" /tmp/cryptenroll.out )} +at_exit() { + rm -f "${IMAGE:-}" "${VL_IMAGE:-}" /tmp/cryptenroll.out /tmp/password +} + +trap at_exit EXIT + # There is an external issue with libcryptsetup on ppc64 that hits 95% of Ubuntu ppc64 test runs, so skip it if [[ "$(uname -m)" == "ppc64le" ]]; then echo "Skipping systemd-cryptenroll tests on ppc64le, see https://github.com/systemd/systemd/issues/27716" @@ -27,13 +33,14 @@ chmod 0600 /tmp/password cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$IMAGE" /tmp/password # Enroll additional tokens, keys, and passwords to exercise the list and wipe stuff -systemd-cryptenroll --unlock-key-file=/tmp/password --tpm2-device=auto "$IMAGE" +# Use --tpm2-public-key= to suppress auto-loading any PCR public key from the host +systemd-cryptenroll --unlock-key-file=/tmp/password --tpm2-device=auto --tpm2-public-key= "$IMAGE" NEWPASSWORD="" systemd-cryptenroll --unlock-key-file=/tmp/password --password "$IMAGE" NEWPASSWORD=foo systemd-cryptenroll --unlock-key-file=/tmp/password --password "$IMAGE" for _ in {0..9}; do systemd-cryptenroll --unlock-key-file=/tmp/password --recovery-key "$IMAGE" done -PASSWORD="" NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-with-pin=true "$IMAGE" +PASSWORD="" NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-with-pin=true "$IMAGE" # Do some basic checks before we start wiping stuff systemd-cryptenroll "$IMAGE" systemd-cryptenroll "$IMAGE" | grep password @@ -60,15 +67,15 @@ systemd-cryptenroll --tpm2-pcrs=8 "$IMAGE" systemd-cryptenroll --tpm2-pcrs=boot-loader-code+boot-loader-config "$IMAGE" # Unlocking using TPM2 -PASSWORD=foo systemd-cryptenroll --tpm2-device=auto "$IMAGE" +PASSWORD=foo systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= "$IMAGE" systemd-cryptenroll --unlock-tpm2-device=auto --recovery-key "$IMAGE" -systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --wipe-slot=tpm2 "$IMAGE" +systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-public-key= --wipe-slot=tpm2 "$IMAGE" # Add PIN to TPM2 enrollment -NEWPIN=1234 systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-with-pin=yes "$IMAGE" +NEWPIN=1234 systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-public-key= --tpm2-with-pin=yes "$IMAGE" # Change PIN on TPM2 enrollment -PIN=1234 NEWPIN=4321 systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-with-pin=yes "$IMAGE" +PIN=1234 NEWPIN=4321 systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-public-key= --tpm2-with-pin=yes "$IMAGE" PIN=4321 systemd-cryptenroll --unlock-tpm2-device=auto --recovery-key "$IMAGE" (! systemd-cryptenroll --fido2-with-client-pin=false) @@ -94,3 +101,43 @@ PIN=4321 systemd-cryptenroll --unlock-tpm2-device=auto --recovery-key "$IMAGE" (! systemd-cryptenroll --fido2-device=auto --unlock-fido2-device=auto "$IMAGE") rm -f "$IMAGE" + +# Exercise the io.systemd.CryptEnroll Varlink interface with calls equivalent to the command line ones above. +CRYPTENROLL="$(command -v systemd-cryptenroll)" +VL_ADDRESS="exec:$CRYPTENROLL" +VL_IMAGE="$(mktemp /tmp/systemd-cryptenroll-varlink-XXX.image)" +truncate -s 20M "$VL_IMAGE" +cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$VL_IMAGE" /tmp/password + +# Enroll a recovery key, unlocking via key file (cf. systemd-cryptenroll --unlock-key-file= --recovery-key) +varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \ + "{\"node\":\"$VL_IMAGE\",\"mechanism\":\"recovery\",\"unlockKeyFile\":\"/tmp/password\"}" | grep recoveryKey >/dev/null + +# Enroll a password, unlocking via key file (cf. NEWPASSWORD=… systemd-cryptenroll --unlock-key-file= --password) +varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \ + "{\"node\":\"$VL_IMAGE\",\"mechanism\":\"password\",\"unlockKeyFile\":\"/tmp/password\",\"password\":\"varlinkpassword\"}" + +# Enroll a password, unlocking via the key file passed as a file descriptor instead of a path +varlinkctl --push-fd=3 call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \ + "{\"node\":\"$VL_IMAGE\",\"mechanism\":\"password\",\"unlockKeyFileDescriptor\":0,\"password\":\"fdpassword\"}" 3/dev/null +varlinkctl call --more "$VL_ADDRESS" io.systemd.CryptEnroll.ListSlots "{\"node\":\"$VL_IMAGE\"}" | grep '"type":"password"' >/dev/null + +# Enroll combined with a wipe of the recovery key slot (cf. systemd-cryptenroll --wipe-slot=recovery --password). +# The recovery key slot just got wiped, so it should be reported back in the (non-empty) wipedSlots output. +varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \ + "{\"node\":\"$VL_IMAGE\",\"mechanism\":\"password\",\"unlockKeyFile\":\"/tmp/password\",\"password\":\"wipepassword\",\"wipeTypes\":[\"recovery\"]}" | grep -E '"wipedSlots":\[[0-9]+(,[0-9]+)*\]' >/dev/null +(! varlinkctl call --more "$VL_ADDRESS" io.systemd.CryptEnroll.ListSlots "{\"node\":\"$VL_IMAGE\"}" | grep '"type":"recovery"' >/dev/null) + +# ListSlots without 'more' is refused +(! varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.ListSlots "{\"node\":\"$VL_IMAGE\"}") + +# PKCS#11 and TPM2 cannot be enrolled via this interface, so the Enroll() handler rejects them as invalid parameters +(! varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \ + "{\"node\":\"$VL_IMAGE\",\"mechanism\":\"tpm2\",\"unlockKeyFile\":\"/tmp/password\"}") +(! varlinkctl call "$VL_ADDRESS" io.systemd.CryptEnroll.Enroll \ + "{\"node\":\"$VL_IMAGE\",\"mechanism\":\"pkcs11\",\"unlockKeyFile\":\"/tmp/password\"}") + +rm -f "$VL_IMAGE" diff --git a/test/units/TEST-70-TPM2.cryptsetup.sh b/test/units/TEST-70-TPM2.cryptsetup.sh index c94d515ff9b82..92f6cdbaa8a22 100755 --- a/test/units/TEST-70-TPM2.cryptsetup.sh +++ b/test/units/TEST-70-TPM2.cryptsetup.sh @@ -31,10 +31,23 @@ tpm_check_failure_with_wrong_pin() { } at_exit() { + set +e + + umount /tmp/dditest.mnt + systemd-cryptsetup detach test-volume + systemd-cryptsetup detach dditest + # Evict the TPM primary key that we persisted if [[ -n "${PERSISTENT_HANDLE:-}" ]]; then tpm2_evictcontrol -c "$PERSISTENT_HANDLE" fi + + if [[ -n "${DEVICE:-}" ]]; then + systemd-dissect --detach "$DEVICE" + fi + + rm -rf /tmp/dditest /tmp/dditest.mnt + rm -f /tmp/dditest.raw "${IMAGE:-}" "${PRIMARY:-}" /tmp/passphrase /tmp/pcr.dat /tmp/srk.pub /tmp/srk2.pub } trap at_exit EXIT @@ -44,15 +57,16 @@ IMAGE="$(mktemp /tmp/systemd-cryptsetup-XXX.IMAGE)" truncate -s 20M "$IMAGE" echo -n passphrase >/tmp/passphrase +echo -n wrong_passphrase >/tmp/wrong_passphrase # Change file mode to avoid "/tmp/passphrase has 0644 mode that is too permissive" messages -chmod 0600 /tmp/passphrase +chmod 0600 /tmp/passphrase /tmp/wrong_passphrase cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$IMAGE" /tmp/passphrase # Unlocking via keyfile -systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto --tpm2-pcrs=7 "$IMAGE" +systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto --tpm2-public-key= --tpm2-pcrs=7 "$IMAGE" # Enroll unlock with SecureBoot (PCR 7) PCR policy -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-pcrs=7 "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume @@ -62,7 +76,7 @@ tpm2_pcrextend 7:sha256=00000000000000000000000000000000000000000000000000000000 # Enroll unlock with PCR+PIN policy systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-with-pin=true --tpm2-pcrs=7 "$IMAGE" +PASSWORD=passphrase NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-with-pin=true --tpm2-pcrs=7 "$IMAGE" PIN=123456 systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume @@ -90,7 +104,7 @@ tpm2_pcrextend 7:sha256=00000000000000000000000000000000000000000000000000000000 # Enroll unlock with PCR 0+7 systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" -PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-pcrs=0+7 "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume @@ -102,21 +116,21 @@ if tpm_has_pcr sha256 12; then # Enroll using an explicit PCR value (that does match current PCR value) systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) - PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="12:sha256=$EXPECTED_PCR_VALUE" "$IMAGE" + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-pcrs="12:sha256=$EXPECTED_PCR_VALUE" "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume # Same as above plus more PCRs without the value or alg specified systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) - PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="1,12:sha256=$EXPECTED_PCR_VALUE,3" "$IMAGE" + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-pcrs="1,12:sha256=$EXPECTED_PCR_VALUE,3" "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume # Same as above plus more PCRs with hash alg specified but hash value not specified systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) - PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="1:sha256,12:sha256=$EXPECTED_PCR_VALUE,3" "$IMAGE" + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-pcrs="1:sha256,12:sha256=$EXPECTED_PCR_VALUE,3" "$IMAGE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 systemd-cryptsetup detach test-volume @@ -125,7 +139,7 @@ if tpm_has_pcr sha256 12; then tpm2_pcrread -Q -o /tmp/pcr.dat sha256:12 CURRENT_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) EXPECTED_PCR_VALUE=$(cat /tmp/pcr.dat /tmp/pcr.dat | openssl dgst -sha256 -r | cut -d ' ' -f 1) - PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="12:sha256=$EXPECTED_PCR_VALUE" "$IMAGE" + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-public-key= --tpm2-pcrs="12:sha256=$EXPECTED_PCR_VALUE" "$IMAGE" (! systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1) tpm2_pcrextend "12:sha256=$CURRENT_PCR_VALUE" systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 @@ -143,7 +157,7 @@ if tpm_has_pcr sha256 12; then # --tpm2-device-key= requires OpenSSL >= 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 +167,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 +203,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 @@ -210,7 +224,8 @@ Format=ext4 CopyFiles=/tmp/dditest:/ Encrypt=tpm2 EOF - PASSWORD=passphrase systemd-repart --tpm2-device-key=/tmp/srk.pub --definitions=/tmp/dditest --empty=create --size=80M /tmp/dditest.raw --tpm2-pcrs= + # Use --tpm2-public-key-pcrs= to suppress auto-loading of the system PCR public key + PASSWORD=passphrase systemd-repart --tpm2-device-key=/tmp/srk.pub --tpm2-public-key-pcrs= --definitions=/tmp/dditest --empty=create --size=80M /tmp/dditest.raw --tpm2-pcrs= DEVICE="$(systemd-dissect --attach /tmp/dditest.raw)" udevadm wait --settle --timeout=10 "$DEVICE"p1 systemd-cryptsetup attach dditest "$DEVICE"p1 - tpm2-device=auto,headless=yes @@ -224,4 +239,11 @@ EOF rmdir /tmp/dditest fi -rm -f "$IMAGE" "$PRIMARY" +# Key file can contain a TPM blob but in case it doesn't fallback should also work. +systemd-cryptsetup attach test-volume "$IMAGE" /tmp/passphrase tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +# Negative test: invalid passphrase should not work. +(! systemd-cryptsetup attach test-volume "$IMAGE" /tmp/wrong_passphrase tpm2-device=auto,headless=1) + +rm -f "$IMAGE" "$PRIMARY" /tmp/passphrase /tmp/wrong_passphrase diff --git a/test/units/TEST-70-TPM2.measure.sh b/test/units/TEST-70-TPM2.measure.sh index bf30bd57b3340..90d6390da0964 100755 --- a/test/units/TEST-70-TPM2.measure.sh +++ b/test/units/TEST-70-TPM2.measure.sh @@ -14,6 +14,31 @@ if [[ ! -x "${SD_MEASURE:?}" ]]; then exit 0 fi +at_exit() { + set +e + + systemd-cryptsetup detach test-volume2 + rm -f "${IMAGE:-}" \ + /tmp/passphrase \ + /tmp/pcrsign-private.pem \ + /tmp/pcrsign-public.pem \ + /tmp/pcrsign.sig \ + /tmp/pcrsign.sig2 \ + /tmp/pcrsign.sig3 \ + /tmp/pcrsign.sig4 \ + /tmp/pcrsign.sig5 \ + /tmp/pcrsign.sig6 \ + /tmp/pcrsign.sig7 \ + /tmp/pcrtestdata \ + /tmp/pcrtestdata.encrypted \ + /tmp/result \ + /tmp/result.json \ + /tmp/tpmdata1 \ + /tmp/tpmdata2 +} + +trap at_exit EXIT + IMAGE="$(mktemp /tmp/systemd-measure-XXX.image)" echo HALLO >/tmp/tpmdata1 @@ -45,6 +70,12 @@ cat >/tmp/result.json </tmp/result </run/nvpcr/test2.nvpcr </run/nvpcr/aaa.nvpcr </run/nvpcr/zzz.nvpcr <:"). + grep -F '"nvIndexName":"login","string":"login:root:' /run/log/systemd/tpm2-measure.log >/dev/null + LOGIN_WORD_BY_NAME="$(login_last_word)" + + # Looking the same user up by numeric UID must yield the identical measured word + # (systemd-pcrextend uses USERDB_PARSE_NUMERIC, and systemd-pcrlogin@.service is instanced by UID). + "$SD_PCREXTEND" --login=0 + LOGIN_WORD_BY_UID="$(login_last_word)" + test "$LOGIN_WORD_BY_NAME" = "$LOGIN_WORD_BY_UID" + + # Direct tool invocations always re-extend (the once-per-boot guarantee lives in the unit's + # RemainAfterExit=yes, not in the tool), so the NvPCR value must have advanced. + LOGIN_DIGEST2="$(login_nvpcr_value)" + test "$LOGIN_DIGEST2" != "$LOGIN_DIGEST1" +fi + +systemd-analyze identify-tpm2 +udevadm test-builtin 'tpm2_id identify' /dev/tpmrm0 + +# systemd-dissect calls io.systemd.PCRExtend over Varlink to extend the verity NvPCR after activation, +# but systemd-pcrextend.socket has ConditionSecurity=measured-os which fails when the firmware did not +# initialize PCRs (e.g. when not booting via a signed UKI). Skip the rest in that case, otherwise the +# 'diff | grep' below would find no new measurement and fail. +if ! systemctl is-active --quiet systemd-pcrextend.socket; then + echo "systemd-pcrextend.socket not active, skipping verity NvPCR measurement check" + exit 0 +fi + mkdir -p /tmp/nvpcr/tree touch /tmp/nvpcr/tree/file diff --git a/test/units/TEST-70-TPM2.pcrextend.sh b/test/units/TEST-70-TPM2.pcrextend.sh index fbb6b30a122a8..ec330576b2531 100755 --- a/test/units/TEST-70-TPM2.pcrextend.sh +++ b/test/units/TEST-70-TPM2.pcrextend.sh @@ -19,6 +19,16 @@ at_exit() { # Dump the event log on fail, to make debugging a bit easier jq --seq --slurp /run/log/systemd/tpm2-measure.log </etc/systemd/hostname-wordlist/1 + printf 'happy\nsad\n' >/etc/systemd/hostname-wordlist/2 + printf 'octopus\nfalcon\n' >/etc/systemd/hostname-wordlist/3 + + # each '$' expands to a word from the list at its position + hostnamectl set-hostname '$-$-$' + H="$(hostname)" + assert_neq "$H" '$-$-$' + assert_eq "$(cat /etc/hostname)" '$-$-$' + IFS='-' read -r w1 w2 w3 <<<"$H" + grep -Fx -- "$w1" /etc/systemd/hostname-wordlist/1 >/dev/null + grep -Fx -- "$w2" /etc/systemd/hostname-wordlist/2 >/dev/null + grep -Fx -- "$w3" /etc/systemd/hostname-wordlist/3 >/dev/null + + # the choice is deterministic: setting the same template again yields the same name + hostnamectl set-hostname testhost + hostnamectl set-hostname '$-$-$' + assert_eq "$(hostname)" "$H" +} + teardown_hostnamed_alternate_paths() { set +eu @@ -320,6 +358,191 @@ EOF assert_in "CHASSIS=watch" "$(cat /run/alternate-path/mymachine-info)" } +testcase_get_machine_info() { + if [[ -f /etc/machine-info ]]; then + cp /etc/machine-info /tmp/machine-info.bak + fi + + trap restore_machine_info RETURN + + # Test all standard cached fields + cat >/etc/machine-info </etc/machine-info </run/systemd/system/systemd-sysupdated.service.d/override.conf</run/systemd/system/systemd-sysupdate@.service.d/override.conf<SHA256SUMS) + (cd "$WORKDIR/source" && rm -f BEST-BEFORE-* && sha256sum uki* part* dir-*.tar.gz linux* >SHA256SUMS) } update_checksums_with_best_before() { - (cd "$WORKDIR/source" && rm -f BEST-BEFORE-* && touch "BEST-BEFORE-$1" && sha256sum uki* part* dir-*.tar.gz "BEST-BEFORE-$1" >SHA256SUMS) + (cd "$WORKDIR/source" && rm -f BEST-BEFORE-* && touch "BEST-BEFORE-$1" && sha256sum uki* part* dir-*.tar.gz linux* "BEST-BEFORE-$1" >SHA256SUMS) } new_version() { local sector_size="${1:?}" local version="${2:?}" + local corrupt="${3:-}" # Create a pair of random partition payloads, and compress one. # To make not the initial bytes of part1-xxx.raw accidentally match one of the compression header, @@ -75,6 +93,10 @@ new_version() { dd if=/dev/urandom of="$WORKDIR/source/part2-$version.raw" bs="$sector_size" count=2048 gzip -k -f "$WORKDIR/source/part2-$version.raw" + # Create a file payload and a suffixed version + echo $RANDOM >"$WORKDIR/source/linux-$version.erofs" + echo $RANDOM >"$WORKDIR/source/linux-$version.erofs.caibx" + # Create a random "UKI" payload echo $RANDOM >"$WORKDIR/source/uki-$version.efi" @@ -90,11 +112,53 @@ new_version() { echo $RANDOM >"$WORKDIR/source/dir-$version/bar.txt" tar --numeric-owner -C "$WORKDIR/source/dir-$version/" -czf "$WORKDIR/source/dir-$version.tar.gz" . - update_checksums + if [[ "$corrupt" == "corrupt-checksum" ]]; then + # As requested, add a deliberately corrupt checksum for this file. This + # will get overwritten next time update_checksums() is called, but the + # integration test will probably have moved on to other things by then. + { + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea part1-$version.raw" + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea part2-$version.raw" + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea part2-$version.raw.gz" + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea linux-$version.erofs" + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea linux-$version.erofs.caibx" + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea uki-$version.efi" + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea uki-extra-$version.efi" + echo "abad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1deaabad1dea dir-$version.tar.gz" + } >> "$WORKDIR/source/SHA256SUMS" + else + update_checksums + fi +} + +check_no_new_update_available() { + local client="${1:?}" + + if [[ "$client" == "sysupdate-cli" ]]; then + (! "$SYSUPDATE" --verify=no check-new) + elif [[ "$client" == "varlink" ]]; then + (! varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.CheckNew '{"target":{"class":"host"}}') |& grep io.systemd.SysUpdate.NoUpdateNeeded >/dev/null + else + exit 1 + fi +} + +check_new_update_available() { + local client="${1:?}" + + if [[ "$client" == "sysupdate-cli" ]]; then + "$SYSUPDATE" --verify=no check-new + elif [[ "$client" == "varlink" ]]; then + varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.CheckNew '{"target":{"class":"host"}}' | grep available + else + exit 1 + fi } update_now() { local update_type="${1:?}" + local client="${2:?}" + local checks="${3:-}" # Update to newest version. First there should be an update ready, then we # do the update, and then there should not be any ready anymore @@ -105,7 +169,10 @@ update_now() { # modes. Some updates in the test suite need to be monolithic (e.g. when # repairing an installation), so that can be overridden via the local. - "$SYSUPDATE" --verify=no check-new + if [[ "$checks" != "no-checks" ]]; then + check_new_update_available "$client" + fi + if [[ "$update_type" == "monolithic" ]]; then "$SYSUPDATE" --verify=no update elif [[ "$update_type" == "split-offline" ]]; then @@ -115,9 +182,9 @@ update_now() { "$SYSUPDATE" --verify=no acquire "$SYSUPDATE" --verify=no update elif [[ "$update_type" == "updatectl" ]]; then - if $have_updatectl; then + if [[ -x "$UPDATECTL" ]]; then systemctl start systemd-sysupdated - updatectl update + "$UPDATECTL" update else # Gracefully fall back to sysupdate "$SYSUPDATE" --verify=no update @@ -125,7 +192,10 @@ update_now() { else exit 1 fi - (! "$SYSUPDATE" --verify=no check-new) + + if [[ "$checks" != "no-checks" ]]; then + check_no_new_update_available "$client" + fi } verify_version() { @@ -150,6 +220,10 @@ verify_version() { # Check the extra efi cmp "$WORKDIR/source/uki-extra-$version.efi" "$WORKDIR/xbootldr/EFI/Linux/uki_$version.efi.extra.d/extra.addon.efi" + + # Check the regular file and its suffixed version + cmp "$WORKDIR/source/linux-$version.erofs" "$WORKDIR/system/linux-$version.erofs" + cmp "$WORKDIR/source/linux-$version.erofs.caibx" "$WORKDIR/system/linux-$version.erofs.caibx" } verify_version_current() { @@ -169,6 +243,7 @@ verify_object_fields() { } for sector_size in "${SECTOR_SIZES[@]}"; do +for client in sysupdate-cli varlink; do for update_type in monolithic split-offline split updatectl; do # Disk size of: # - 1MB for GPT @@ -272,6 +347,36 @@ PathRelativeTo=boot MatchPattern=uki_@v.efi.extra.d/extra.addon.efi Mode=0444 InstancesMax=2 +EOF + + # Test with a transfer which contains one of the other transfers as a prefix + # of its files, to check pattern matching can distinguish the two. + cat >"$CONFIGDIR/06-sixth.transfer" <"$CONFIGDIR/07-seventh.transfer" <"$CONFIGDIR/optional.feature" </dev/null - update_now "monolithic" + update_now "monolithic" "$client" "$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null verify_version "$blockdev" "$sector_size" v3 1 verify_version_current "$blockdev" "$sector_size" v5 2 @@ -352,7 +457,7 @@ EOF mkdir "$CONFIGDIR/optional.feature.d" echo -e "[Feature]\nEnabled=true" > "$CONFIGDIR/optional.feature.d/enable.conf" "$SYSUPDATE" --offline list v5 | grep "incomplete" >/dev/null - update_now "monolithic" + update_now "$update_type" "$client" "$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null verify_version "$blockdev" "$sector_size" v3 1 verify_version_current "$blockdev" "$sector_size" v5 2 @@ -360,7 +465,7 @@ EOF # And now let's disable it and make sure it gets cleaned up rm -r "$CONFIGDIR/optional.feature.d" - (! "$SYSUPDATE" --verify=no check-new) + check_no_new_update_available "$client" "$SYSUPDATE" vacuum "$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null verify_version "$blockdev" "$sector_size" v3 1 @@ -370,17 +475,19 @@ EOF # Create sixth version, update using updatectl and verify it replaced the # correct version new_version "$sector_size" v6 - if $have_updatectl; then + if [[ -x "$UPDATECTL" ]]; then systemctl start systemd-sysupdated - "$SYSUPDATE" --verify=no check-new - updatectl update + check_new_update_available "$client" + "$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" + update_now "$update_type" "$client" fi # User-facing updatectl returns 0 if there's no updates, so use the low-level # utility to make sure we did upgrade - (! "$SYSUPDATE" --verify=no check-new ) + check_no_new_update_available "$client" verify_version_current "$blockdev" "$sector_size" v6 1 verify_version "$blockdev" "$sector_size" v5 2 @@ -388,13 +495,13 @@ EOF # testing for specific output, but this will at least catch obvious crashes # and allow updatectl to run under the various sanitizers. We create a # component so that updatectl has multiple targets to list. - if $have_updatectl; then + if [[ -x "$UPDATECTL" ]]; then mkdir -p /run/sysupdate.test.d/ cp "$CONFIGDIR/01-first.transfer" /run/sysupdate.test.d/01-first.transfer - verify_object_fields "$(updatectl list 2>&1)" - verify_object_fields "$(updatectl list host 2>&1)" - verify_object_fields "$(updatectl list host@v6 2>&1)" - updatectl check + verify_object_fields "$("$UPDATECTL" list 2>&1)" + verify_object_fields "$("$UPDATECTL" list host 2>&1)" + verify_object_fields "$("$UPDATECTL" list host@v6 2>&1)" + "$UPDATECTL" check rm -r /run/sysupdate.test.d fi @@ -433,7 +540,7 @@ MatchPattern=dir-@v InstancesMax=3 EOF - update_now "$update_type" + update_now "$update_type" "$client" verify_version "$blockdev" "$sector_size" v6 1 verify_version_current "$blockdev" "$sector_size" v7 2 @@ -456,14 +563,522 @@ EOF # (what .transfer files were called before v257) for i in "$CONFIGDIR/"*.conf; do echo mv "$i" "${i%.conf}.transfer"; done new_version "$sector_size" v8 - update_now "$update_type" + update_now "$update_type" "$client" verify_version_current "$blockdev" "$sector_size" v8 1 verify_version "$blockdev" "$sector_size" v7 2 + # Create a 9th version but corrupt the checksum in SHA256SUMS so pulling it + # fails when verifying the checksum, in order to create a current+partial + # state. Try to update again and verify that this results in an error. + # Vacuum the partial version, regenerate it on the server, try updating + # again and it should succeed. + new_version "$sector_size" v9 "corrupt-checksum" + (! update_now "$update_type" "$client") + "$SYSUPDATE" --offline list v9 | grep "partial" >/dev/null + verify_version_current "$blockdev" "$sector_size" v8 1 + # don’t verify the other part of the block device as it’s in an indeterminate state + (! update_now "$update_type" "$client" "no-checks") |& tee "$WORKDIR"/update_now-9 + cat "$WORKDIR"/update_now-9 + grep "is already acquired and partially installed. Vacuum it to try installing again." "$WORKDIR"/update_now-9 + "$SYSUPDATE" --offline vacuum |& grep "Removing old partial" >/dev/null + verify_version_current "$blockdev" "$sector_size" v8 1 + # don’t verify the other part of the block device as it’s in an indeterminate state + "$SYSUPDATE" --verify=no list v9 | grep "candidate" >/dev/null + new_version "$sector_size" v9 + update_now "$update_type" "$client" + verify_version "$blockdev" "$sector_size" v8 1 + verify_version_current "$blockdev" "$sector_size" v9 2 + + # Test that checking for an update on a non-existent target fails + # (for backwards compatibility reasons, the validation in sysupdate-cli is + # less strict) + if [[ "$client" == "sysupdate-cli" ]]; then + (! "$SYSUPDATE" --verify=no check-new --component=../) |& grep "Component name invalid" >/dev/null + elif [[ "$client" == "varlink" ]]; then + (! varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.CheckNew '{"target":{"class":"component","name":"../"}}') |& grep org.varlink.service.InvalidParameter >/dev/null + (! varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.CheckNew '{"target":{"class":"component","name":"doesnotexist"}}') |& grep io.systemd.SysUpdate.NoSuchTarget >/dev/null + else + exit 1 + fi + # Cleanup [[ -b "$blockdev" ]] && losetup --detach "$blockdev" rm "$BACKING_FILE" done done +done + +# Regression test for https://github.com/systemd/systemd/issues/41501 — check +# that a ‘default’ component is only listed by sysupdate if it’s fully configured +mv "$CONFIGDIR" "$CONFIGDIR.backup" +mkdir -p /run/sysupdate.some-component.d +tee /run/sysupdate.some-component.d/portable.transfer << EOF +[Transfer] +ChangeLog=https://example.com/changelog/@v +Verify=no + +[Source] +Type=url-tar +Path=https://example.com/does-not-matter/@v.tar.xz +MatchPattern=some-component_@v-portable.tar.xz + +[Target] +Type=directory +Path=/var/lib/portables +MatchPattern=some-component_@v +CurrentSymlink=some-component +EOF +"$SYSUPDATE" --json=short components | grep -F '{"default":false,"components":["some-component"]}' >/dev/null +mkdir /run/sysupdate.d +"$SYSUPDATE" --json=short components | grep -F '{"default":false,"components":["some-component"]}' >/dev/null + +# Regression test for https://github.com/systemd/systemd/issues/42330 — the +# 'pending'/'reboot' verbs and the '--reboot' switch compare the newest installed +# version against the booted OS version (IMAGE_VERSION= from os-release), which is +# unrelated to component versions. Selecting a component must therefore be refused +# rather than silently performing a bogus comparison. +(! "$SYSUPDATE" --component=some-component pending) |& grep -F 'may not be combined' >/dev/null +(! "$SYSUPDATE" --component=some-component reboot) |& grep -F 'may not be combined' >/dev/null +(! "$SYSUPDATE" --component=some-component update --reboot) |& grep -F 'may not be combined' >/dev/null + +# Clean up regression test +rmdir /run/sysupdate.d +rm -rf /run/sysupdate.some-component.d +mv "$CONFIGDIR.backup" "$CONFIGDIR" + +# Make sure the processing of compressed streams still handles uncompressed streams shorter than +# COMPRESSION_MAGIC_BYTES_MAX correctly. +rm -rf "$CONFIGDIR" "$WORKDIR/blobs" +mkdir -p "$CONFIGDIR" "$WORKDIR/blobs" +printf 'xx' >"$WORKDIR/source/tiny-v1.bin" +(cd "$WORKDIR/source" && sha256sum tiny-v1.bin >SHA256SUMS) +cat >"$CONFIGDIR/01-tiny-url.transfer" <"$WORKDIR/malformed-manifest/source/malformed-v1.bin" +hash_64=0000000000000000000000000000000000000000000000000000000000000000 +printf '%s\t\t *malformed-v1.bin\n' "${hash_64%??}" >"$WORKDIR/malformed-manifest/source/SHA256SUMS" +cat >"$WORKDIR/malformed-manifest/definitions/01-malformed-hash.transfer" <"$WORKDIR/malformed-manifest/check-new.log" +rc=$? +set -e +[[ $rc -ne 0 ]] +[[ $rc -ne 134 ]] +grep -F "Manifest hash at line 1 decoded to 31 bytes" "$WORKDIR/malformed-manifest/check-new.log" >/dev/null + +# Check the "cleanup" verb and the underlying install database. A successful +# update must record an install database entry for every transfer that installs +# into the file system, and those entries must cover all installed resources +# (regular files as well as directories). Once the transfer file owning some +# resources is removed, "systemd-sysupdate cleanup" must delete the now-orphaned +# resources (and their install database entries), while leaving resources that +# are still owned by a transfer file untouched. +INSTALLDB="/var/lib/systemd/sysupdate/installdb" +CLEANUP="$WORKDIR/cleanup" +rm -rf "$CONFIGDIR" "$INSTALLDB" "$CLEANUP" +mkdir -p "$CONFIGDIR" "$CLEANUP/source" "$CLEANUP/target" + +# The "alpha" transfer installs plain regular files, while the "beta" transfer +# installs whole directories (each populated with a couple of files), to exercise +# the recursive removal of orphaned directory resources during cleanup. +cleanup_new_version() { + local version="${1:?}" + echo "$RANDOM" >"$CLEANUP/source/alpha-$version.bin" + rm -rf "$CLEANUP/source/beta-$version" + mkdir -p "$CLEANUP/source/beta-$version" + echo "$RANDOM" >"$CLEANUP/source/beta-$version/one.txt" + echo "$RANDOM" >"$CLEANUP/source/beta-$version/two.txt" + (cd "$CLEANUP/source" && sha256sum alpha-* >SHA256SUMS) +} + +# Number of install database entries (symlinks) currently recorded. +installdb_count() { + if [[ -d "$INSTALLDB" ]]; then + find "$INSTALLDB" -mindepth 1 -maxdepth 1 -type l | wc -l + else + echo 0 + fi +} + +# Assert that every resource (file or directory) currently installed in the +# target directory is covered by at least one install database entry (i.e. its +# name matches a recorded pattern that points at the target directory). +assert_installdb_covers_target() { + local f base link tgt path pattern glob covered + for f in "$CLEANUP/target"/*; do + [[ -e "$f" ]] || continue + base="$(basename "$f")" + covered=0 + while read -r link; do + tgt="$(readlink "$link")" + # Entries are stored as "/./". + path="${tgt%%/./*}" + pattern="${tgt#*/./}" + # Translate the sysupdate pattern into a shell glob (only @v is used here). + glob="${pattern//@v/*}" + # shellcheck disable=SC2053 + if [[ "$path" == "$CLEANUP/target" && "$base" == $glob ]]; then + covered=1 + break + fi + done < <(find "$INSTALLDB" -mindepth 1 -maxdepth 1 -type l) + [[ "$covered" -eq 1 ]] || { echo "Installed resource '$f' not covered by install database" >&2; exit 1; } + done +} + +# Verify the installed beta- directory matches its source. +verify_beta_synced() { + local version="${1:?}" + test -d "$CLEANUP/target/beta-$version" + cmp "$CLEANUP/source/beta-$version/one.txt" "$CLEANUP/target/beta-$version/one.txt" + cmp "$CLEANUP/source/beta-$version/two.txt" "$CLEANUP/target/beta-$version/two.txt" +} + +cat >"$CONFIGDIR/01-alpha.transfer" <"$CONFIGDIR/02-beta.transfer" <), and "cleanup +# --component-all" must clean up orphaned resources across *all* of them in one +# go. Set up two components, install a resource into each, then drop both transfer +# files and run a single "cleanup --component-all" — it must remove both +# components' resources (and their install database entries). +COMPALL="$WORKDIR/component-all" +rm -rf "$COMPALL" /run/sysupdate.comp-a.d /run/sysupdate.comp-b.d \ + /var/lib/systemd/sysupdate/installdb.comp-a /var/lib/systemd/sysupdate/installdb.comp-b +mkdir -p "$COMPALL/source" "$COMPALL/target-a" "$COMPALL/target-b" \ + /run/sysupdate.comp-a.d /run/sysupdate.comp-b.d + +echo "$RANDOM" >"$COMPALL/source/comp-a-v1.bin" +echo "$RANDOM" >"$COMPALL/source/comp-b-v1.bin" +(cd "$COMPALL/source" && sha256sum comp-* >SHA256SUMS) + +cat >/run/sysupdate.comp-a.d/01-comp-a.transfer </run/sysupdate.comp-b.d/01-comp-b.transfer <"$CONFIGDIR/01-alpha.transfer" <"$CONFIGDIR/02-beta.transfer" <"$CONFIGDIR/01-alpha.transfer" </dev/null + +# A plain "cleanup" must still remove the orphaned alpha files. +"$SYSUPDATE" cleanup +test ! -f "$CLEANUP/target/alpha-v1.bin" +test ! -f "$CLEANUP/target/alpha-v2.bin" +[[ "$(installdb_count)" -eq 0 ]] + +rm -rf "$CONFIGDIR" "$INSTALLDB" "$CLEANUP" + +# Verify the notification callout: after a successful update, sysupdate must connect to every socket in +# /run/systemd/sysupdate/notify/ and invoke io.systemd.SysUpdate.Notify.OnCompletedUpdate(). We hook a tiny +# recorder socket into that directory that captures the request and replies with success. +NOTIFY_LOG="$WORKDIR/notify.log" +rm -f "$NOTIFY_LOG" + +cat >"$WORKDIR/notify-recorder.py" </run/systemd/system/test-sysupdate-notify-recorder.socket <"/run/systemd/system/test-sysupdate-notify-recorder@.service" <"$WORKDIR/source/notifytest-v1.bin" +(cd "$WORKDIR/source" && sha256sum notifytest-v1.bin >SHA256SUMS) +cat >"$CONFIGDIR/01-notifytest.transfer" </dev/null +jq -e '.parameters.version == "v1"' <<<"$notify_line" >/dev/null +jq -e '.parameters.resources | length >= 1' <<<"$notify_line" >/dev/null +jq -e '.parameters.resources | all(has("transfer"))' <<<"$notify_line" >/dev/null + +# A no-op update ("No update needed") must NOT emit a notification. +rm -f "$NOTIFY_LOG" +"$SYSUPDATE" --verify=no update +test ! -s "$NOTIFY_LOG" + +systemctl stop test-sysupdate-notify-recorder.socket +rm -f /run/systemd/system/test-sysupdate-notify-recorder.socket \ + /run/systemd/system/test-sysupdate-notify-recorder@.service +systemctl daemon-reload +rm -rf "$CONFIGDIR" "$WORKDIR/blobs" +rm -f "$WORKDIR/source/notifytest-v1.bin" "$WORKDIR/source/SHA256SUMS" \ + "$WORKDIR/notify-recorder.py" "$NOTIFY_LOG" touch /testok diff --git a/test/units/TEST-74-AUX-UTILS.ask-password.sh b/test/units/TEST-74-AUX-UTILS.ask-password.sh index 421ebb74b7841..ef0c1aa9e7da8 100755 --- a/test/units/TEST-74-AUX-UTILS.ask-password.sh +++ b/test/units/TEST-74-AUX-UTILS.ask-password.sh @@ -6,9 +6,15 @@ set -o pipefail # shellcheck source=test/units/util.sh . "$(dirname "$0")"/util.sh +KEY="" +SOCKET_DIR="" + at_exit() { set +e systemctl stop waldo-ask-pw-agent.service + systemctl stop test-askpw.service + [[ -n "$KEY" ]] && keyctl unlink "$KEY" @u + rm -rf "$SOCKET_DIR" } trap at_exit EXIT @@ -22,3 +28,23 @@ varlinkctl introspect /run/systemd/io.systemd.AskPassword systemd-run -u waldo-ask-pw-agent.service -p Environment=SYSTEMD_ASK_PASSWORD_AGENT_PASSWORD=waldo -p Type=notify systemd-tty-ask-password-agent --watch --console=/dev/console assert_eq "$(systemd-ask-password --no-tty)" "waldo" assert_eq "$(varlinkctl call /usr/bin/systemd-ask-password io.systemd.AskPassword.Ask '{"message":"foobar"}' | jq '.passwords[0]')" "\"waldo\"" + +# Per-request Varlink Ask flags must not leak across calls sharing one connection +SOCKET_DIR="$(mktemp -d)" +sock="$SOCKET_DIR/ask.sock" +KEY="$(keyctl add user test-askpw hunter2 @u)" + +systemd-run --unit=test-askpw.service -p Type=notify -p KeyringMode=shared \ + systemd-socket-activate --accept --fdname=varlink -l "$sock" -- \ + systemd-ask-password + +# timeoutUSec:0 disables the agent, so the keyring cache is the only source. +req_cached='{"method":"io.systemd.AskPassword.Ask","parameters":{"keyname":"test-askpw","timeoutUSec":0,"acceptCached":true}}' +req_plain='{"method":"io.systemd.AskPassword.Ask","parameters":{"keyname":"test-askpw","timeoutUSec":0}}' +printf '%s\0%s\0' "$req_cached" "$req_plain" | socat -t20 - "UNIX-CONNECT:$sock" >"$SOCKET_DIR/replies" +mapfile -d '' -t replies <"$SOCKET_DIR/replies" + +assert_eq "${#replies[@]}" "2" +assert_in "hunter2" "${replies[0]}" +assert_not_in "hunter2" "${replies[1]}" +assert_in "NoPasswordAvailable" "${replies[1]}" diff --git a/test/units/TEST-74-AUX-UTILS.busctl.sh b/test/units/TEST-74-AUX-UTILS.busctl.sh index b8ae8da568204..156cb02f0bcee 100755 --- a/test/units/TEST-74-AUX-UTILS.busctl.sh +++ b/test/units/TEST-74-AUX-UTILS.busctl.sh @@ -43,6 +43,12 @@ busctl call --json=short \ busctl call -j \ org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.DBus.Properties \ GetAll s "org.freedesktop.systemd1.Manager" | jq -c +# Do the same with D-Bus' org.freedesktop.DBus.Debug.Stats interface which also provides some +# complex signatures that caused issues during the JSON conversion in the past +# See: https://github.com/systemd/systemd/issues/32904 +busctl call --json=pretty \ + org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.Debug.Stats \ + GetStats | jq -c busctl call --verbose --timeout=60 --expect-reply=yes \ org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager \ ListUnitsByPatterns asas 1 "active" 2 "systemd-*.socket" "*.mount" @@ -76,7 +82,7 @@ systemd-run --quiet --service-type=notify --unit=test-busctl-wait-limited --pty systemd-run --quiet --service-type=notify --unit=test-busctl-wait-unlimited --pty \ -p Environment=SYSTEMD_LOG_LEVEL=debug \ -p ExecStartPost="sh -c '$(signal_emit_command 2)'" \ - busctl --timeout=3 --limit-messages=infinity wait /test org.freedesktop.fake1 TestSignal | + busctl --timeout=30 --limit-messages=infinity wait /test org.freedesktop.fake1 TestSignal | grep -Fc 's "success"' | xargs test 2 -eq busctl get-property org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager \ @@ -146,10 +152,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.firstboot.sh b/test/units/TEST-74-AUX-UTILS.firstboot.sh index 557bd3af01204..8c3c222c884d7 100755 --- a/test/units/TEST-74-AUX-UTILS.firstboot.sh +++ b/test/units/TEST-74-AUX-UTILS.firstboot.sh @@ -11,6 +11,14 @@ if ! command -v systemd-firstboot >/dev/null; then exit 0 fi +restore_etc_hostname() { + if [[ -e /tmp/etc-hostname.bak ]]; then + mv /tmp/etc-hostname.bak /etc/hostname + elif [[ -e /tmp/etc-hostname.absent ]]; then + rm -f /etc/hostname /tmp/etc-hostname.absent + fi +} + at_exit() { if [[ -n "${ROOT:-}" ]]; then ls -lR "$ROOT" @@ -22,6 +30,11 @@ at_exit() { rm -rf /etc/otherpath fi + [[ -n "${WROOT:-}" ]] && rm -rf "$WROOT" + [[ -n "${WCREDS:-}" ]] && rm -rf "$WCREDS" + [[ -n "${WWORDS:-}" ]] && rm -rf "$WWORDS" + restore_etc_hostname + restore_locale } @@ -82,6 +95,57 @@ readlink "$ROOT/etc/localtime" | grep "Europe/Berlin" >/dev/null systemd-firstboot --root="$ROOT" --hostname "foobar" grep -q "foobar" "$ROOT/etc/hostname" +# Hostname wildcard templates (see hostname(5)) must be accepted by both the --hostname option and the +# firstboot.hostname credential (both go through hostname_is_valid() with the wildcard flags). Since these +# invocations operate on an offline image (--root=), the template is written verbatim and only resolved +# later, on the target's first boot (the resolve-and-freeze path is taken only when running on the live +# system, where the machine ID is final). +WROOT=test-root-hostname-wildcard + +# '$' word token via --hostname, stored as-is +rm -rf "$WROOT"; mkdir -p "$WROOT" +systemd-firstboot --root="$WROOT" --hostname='$-$' +assert_eq "$(cat "$WROOT/etc/hostname")" '$-$' + +# '?' hex token via --hostname, stored as-is +rm -rf "$WROOT"; mkdir -p "$WROOT" +systemd-firstboot --root="$WROOT" --hostname='foo-????' +assert_eq "$(cat "$WROOT/etc/hostname")" 'foo-????' + +# the firstboot.hostname credential is accepted and written verbatim too +rm -rf "$WROOT"; mkdir -p "$WROOT" +WCREDS="$(mktemp -d)" +echo -n '$-$-????' >"$WCREDS/firstboot.hostname" +CREDENTIALS_DIRECTORY="$WCREDS" systemd-firstboot --root="$WROOT" +assert_eq "$(cat "$WROOT/etc/hostname")" '$-$-????' +rm -rf "$WCREDS" + +# an invalid hostname (disallowed character) is refused and nothing is written +rm -rf "$WROOT"; mkdir -p "$WROOT" +(! systemd-firstboot --root="$WROOT" --hostname='foo_bar') +[[ ! -e "$WROOT/etc/hostname" ]] + +rm -rf "$WROOT" + +# When run on the live system (no --root=) the machine ID is final, so the wildcards are resolved +# immediately and the concrete result is persisted, "freezing" the generated name (see hostname(5)). +if [[ -f /etc/hostname ]]; then + cp /etc/hostname /tmp/etc-hostname.bak +else + touch /tmp/etc-hostname.absent +fi +WWORDS="$(mktemp -d)" +printf 'wildly\nquietly\n' >"$WWORDS/1" +printf 'happy\nsad\n' >"$WWORDS/2" +SYSTEMD_HOSTNAME_WORDLIST_PATH="$WWORDS" systemd-firstboot --force --hostname='$-$-????' +H="$(cat /etc/hostname)" +IFS='-' read -r w1 w2 suffix <<<"$H" +grep -Fx -- "$w1" "$WWORDS/1" >/dev/null +grep -Fx -- "$w2" "$WWORDS/2" >/dev/null +[[ "$suffix" =~ ^[0-9a-f]{4}$ ]] +rm -rf "$WWORDS" +restore_etc_hostname + systemd-firstboot --root="$ROOT" --machine-id=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa grep -q "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "$ROOT/etc/machine-id" 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; then + echo "hostnamectl not found, skipping the test" + exit 0 +fi + +at_exit() { + set +e + + # Restore the original /etc/machine-info (if any) and make hostnamed read it again + if [[ -e /tmp/machine-info.bak ]]; then + mv /tmp/machine-info.bak /etc/machine-info + else + rm -f /etc/machine-info + fi + systemctl stop --job-mode=replace-irreversibly systemd-hostnamed.service + systemctl reset-failed systemd-hostnamed.service + + rm -fr "${ROOT:-}" +} + +trap at_exit EXIT + +# Read the TAGS= field out of /etc/machine-info (robust against the file being +# absent and against TAGS= being unset). +get_tags_from_file() ( + set +u + [[ -f /etc/machine-info ]] && . /etc/machine-info + echo "${TAGS:-}" +) + +if [[ -f /etc/machine-info ]]; then + cp /etc/machine-info /tmp/machine-info.bak +fi + +# Start from a clean slate, and make sure hostnamed re-reads the (now missing) file. +rm -f /etc/machine-info +systemctl stop --job-mode=replace-irreversibly systemd-hostnamed.service || : +systemctl reset-failed systemd-hostnamed.service || : + +# -------------------------------------------------------------------------------------------------- +# hostnamectl tags <-> /etc/machine-info +# -------------------------------------------------------------------------------------------------- + +# No tags are configured initially. +assert_eq "$(hostnamectl tags)" "" + +# Set some tags. Multiple arguments, colon-separated arguments, duplicates and arbitrary order are +# all accepted and normalized into a sorted, deduplicated, colon-separated list. +hostnamectl tags webserver:frontend frontend berlin +assert_eq "$(hostnamectl tags)" "berlin:frontend:webserver" + +# The very same list must show up in the TAGS= field of /etc/machine-info. +grep -qE '^TAGS="?berlin:frontend:webserver"?$' /etc/machine-info +assert_eq "$(get_tags_from_file)" "berlin:frontend:webserver" + +# Setting tags again replaces the previous list rather than extending it. +hostnamectl tags database +assert_eq "$(hostnamectl tags)" "database" +assert_eq "$(get_tags_from_file)" "database" + +# Invalid tags (only ASCII alphanumerics, '-' and '.' are allowed) are refused, leaving the +# previously configured tags untouched. +(! hostnamectl tags "invalid tag") +(! hostnamectl tags "invalid/tag") +assert_eq "$(hostnamectl tags)" "database" + +# Clearing all tags via a single empty string argument. When TAGS= was the only field, hostnamed +# removes /etc/machine-info altogether. +hostnamectl tags "" +assert_eq "$(hostnamectl tags)" "" +assert_eq "$(get_tags_from_file)" "" + +# -------------------------------------------------------------------------------------------------- +# ConditionMachineTag=/AssertMachineTag= in a transient unit +# -------------------------------------------------------------------------------------------------- + +hostnamectl tags alpha:beta:gamma +assert_eq "$(hostnamectl tags)" "alpha:beta:gamma" + +# When the condition matches, the unit is started and 'false' actually runs, so systemd-run fails. +# When the condition does not match, the unit is skipped (not failed) and systemd-run succeeds. +(! systemd-run --wait --pipe -p ConditionMachineTag=beta false) +systemd-run --wait --pipe -p ConditionMachineTag=delta false +# Globs are matched against each individual tag. +(! systemd-run --wait --pipe -p ConditionMachineTag='al*' false) +systemd-run --wait --pipe -p ConditionMachineTag='z*' false +# Negation inverts the result. +systemd-run --wait --pipe -p ConditionMachineTag='!beta' false +(! systemd-run --wait --pipe -p ConditionMachineTag='!delta' false) + +# Asserts behave like conditions, except a failing assert puts the unit into a failed state, which +# systemd-run propagates as a non-zero exit code. +systemd-run -p AssertMachineTag=beta -p Type=oneshot true +(! systemd-run -p AssertMachineTag=delta -p Type=oneshot true) + +# -------------------------------------------------------------------------------------------------- +# systemd-firstboot --machine-tags= and the firstboot.machine-tags credential +# -------------------------------------------------------------------------------------------------- + +if command -v systemd-firstboot >/dev/null; then + # The firstboot.machine-tags credential is split on ':', deduplicated and sorted, and written + # into /etc/machine-info underneath the target root. + ROOT="$(mktemp -d)" + systemd-run --wait --pipe --service-type=exec \ + -p SetCredential=firstboot.machine-tags:webserver:frontend:webserver:berlin \ + systemd-firstboot --root="$ROOT" + grep -qE '^TAGS="?berlin:frontend:webserver"?$' "$ROOT/etc/machine-info" + + # An invalid tag anywhere in the credential causes the whole list to be ignored, so no + # machine-info file is written. + rm -fr "$ROOT" + ROOT="$(mktemp -d)" + systemd-run --wait --pipe --service-type=exec \ + -p SetCredential=firstboot.machine-tags:'good:bad/tag' \ + systemd-firstboot --root="$ROOT" + test ! -e "$ROOT/etc/machine-info" + + # The --machine-tags= switch is normalized the same way and takes precedence over the credential. + rm -fr "$ROOT" + ROOT="$(mktemp -d)" + systemd-run --wait --pipe --service-type=exec \ + -p SetCredential=firstboot.machine-tags:ignored \ + systemd-firstboot --root="$ROOT" --machine-tags=database:cache:database + grep -qE '^TAGS="?cache:database"?$' "$ROOT/etc/machine-info" + + # An invalid tag passed on the command line is a hard error. + rm -fr "$ROOT" + ROOT="$(mktemp -d)" + (! systemd-firstboot --root="$ROOT" --machine-tags='good:bad/tag') +fi diff --git a/test/units/TEST-74-AUX-UTILS.path.sh b/test/units/TEST-74-AUX-UTILS.path.sh index 4547f53e24d81..14f7ef8ec062b 100755 --- a/test/units/TEST-74-AUX-UTILS.path.sh +++ b/test/units/TEST-74-AUX-UTILS.path.sh @@ -39,6 +39,7 @@ XDG_DOCUMENTS_DIR="$HOME/top/secret/documents" XDG_MUSIC_DIR="/tmp/vaporwave" XDG_PICTURES_DIR="$HOME/Pictures" XDG_VIDEOS_DIR="$HOME/🤔" +XDG_PROJECTS_DIR="$HOME/my-projects" EOF systemd-path --help @@ -66,12 +67,13 @@ assert_eq "$(systemd-path user-pictures)" "/root/Pictures" assert_eq "$(systemd-path user-public)" "/root/cat-pictures" assert_eq "$(systemd-path user-templates)" "/templates" assert_eq "$(systemd-path user-videos)" "/root/🤔" +assert_eq "$(systemd-path user-projects)" "/root/my-projects" # Remove the user-dirs.dir file and check the defaults rm -fv "$USER_DIRS_CONF" [[ ! -e "$USER_DIRS_CONF" ]] assert_eq "$(systemd-path user-desktop)" "/root/Desktop" -for dir in "" documents download music pictures public templates videos; do +for dir in "" documents download music pictures public templates videos projects; do assert_eq "$(systemd-path "user${dir:+-$dir}")" "/root" done diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index 876c5b2cad49e..06914fdc50324 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -16,9 +16,9 @@ REPORT=/usr/lib/systemd/systemd-report "$REPORT" metrics "$REPORT" metrics -j "$REPORT" metrics --no-legend -"$REPORT" describe-metrics -"$REPORT" describe-metrics -j -"$REPORT" describe-metrics --no-legend +"$REPORT" describe +"$REPORT" describe -j +"$REPORT" describe --no-legend "$REPORT" list-sources "$REPORT" list-sources -j "$REPORT" list-sources --no-legend @@ -26,12 +26,263 @@ REPORT=/usr/lib/systemd/systemd-report "$REPORT" metrics io "$REPORT" metrics io.systemd piff "$REPORT" metrics piff -"$REPORT" describe-metrics io -"$REPORT" describe-metrics io.systemd piff -"$REPORT" describe-metrics piff +"$REPORT" describe io +"$REPORT" describe io.systemd piff +"$REPORT" describe piff + +# test io.systemd.CGroup Metrics +systemctl start systemd-report-cgroup.socket +varlinkctl info /run/systemd/report/io.systemd.CGroup +varlinkctl list-methods /run/systemd/report/io.systemd.CGroup +varlinkctl --more call /run/systemd/report/io.systemd.CGroup io.systemd.Metrics.List {} +varlinkctl --more call /run/systemd/report/io.systemd.CGroup io.systemd.Metrics.Describe {} + +# CpuUsage emits one row per (cgroup, type) where type is total, user, or system. +# Confirm all three are present. +cgroup_metrics=$(varlinkctl --more --json=short call /run/systemd/report/io.systemd.CGroup io.systemd.Metrics.List {}) +echo "$cgroup_metrics" | grep '"name":"io.systemd.CGroup.CpuUsage"' | grep '"type":"total"' >/dev/null +echo "$cgroup_metrics" | grep '"name":"io.systemd.CGroup.CpuUsage"' | grep '"type":"user"' >/dev/null +echo "$cgroup_metrics" | grep '"name":"io.systemd.CGroup.CpuUsage"' | grep '"type":"system"' >/dev/null # test io.systemd.Network Metrics varlinkctl info /run/systemd/report/io.systemd.Network varlinkctl list-methods /run/systemd/report/io.systemd.Network varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics.List {} varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics.Describe {} + +varlinkctl --more --json=short call /run/systemd/report/io.systemd.Network io.systemd.Metrics.Describe {} | grep '"name":"io.systemd.Network.Address"' >/dev/null +# we do not send "lo" +net_metrics="$(varlinkctl call --more --json=short /run/systemd/report/io.systemd.Network io.systemd.Metrics.List {})" +(! echo "$net_metrics" | grep '"name":"io.systemd.Network.Address"' | grep '"object":"lo"' >/dev/null) +# add a scratch address and check that it shows up. +ip address add 192.0.2.1/32 dev lo +timeout 30 bash -c 'until varlinkctl call --more --json=short /run/systemd/report/io.systemd.Network io.systemd.Metrics.List {} | grep -F "192.0.2.1/32" >/dev/null; do sleep .5; done' +net_metrics="$(varlinkctl call --more --json=short /run/systemd/report/io.systemd.Network io.systemd.Metrics.List {})" +echo "$net_metrics" | grep '"name":"io.systemd.Network.Address"' | grep '"object":"lo"' | grep '"value":"192.0.2.1/32"' | grep '"family":"ipv4"' >/dev/null +ip address del 192.0.2.1/32 dev lo + +# test io.systemd.Basic Metrics +# ensure the socket is running, as some distros don't enable it by default +systemctl start systemd-report-basic.socket +varlinkctl info /run/systemd/report/io.systemd.Basic +varlinkctl list-methods /run/systemd/report/io.systemd.Basic +varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Metrics.List {} +varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Metrics.Describe {} + +id1="$(varlinkctl call --more /run/systemd/report/io.systemd.Basic io.systemd.Metrics.List {} | jq --seq -r 'select(.name == "io.systemd.Basic.OSRelease.ID") | .value')" +id2="$(. /etc/os-release; echo "$ID")" +[ "$id1" = "$id2" ] + +# test io.systemd.Basic load average and swap metrics +basic_metrics="$(varlinkctl call --more /run/systemd/report/io.systemd.Basic io.systemd.Metrics.List {})" +# NB: '| tostring' turns the numeric value into a raw string, so 'jq -r' prints it cleanly +# (numbers, unlike strings, are otherwise still emitted with the json-seq record separator). +basic_value() { echo "$basic_metrics" | jq --seq -r "select(.name == \"$1\") | .value | tostring"; } + +# The three classic load average fields must be present and numeric (jq's 'numbers' filter +# emits the value only if it is a JSON number, so a non-empty result confirms both). +for field in LoadAverage1Min LoadAverage5Min LoadAverage15Min; do + loadavg="$(echo "$basic_metrics" | jq --seq -r "select(.name == \"io.systemd.Basic.$field\") | .value | numbers | tostring")" + test -n "$loadavg" +done + +# SwapBytes must match the total the kernel reports in /proc/meminfo (which is in kB). +swap_reported="$(basic_value io.systemd.Basic.SwapBytes)" +swap_expected=$(( $(awk '/^SwapTotal:/ { print $2; found=1 } END { if (!found) print 0 }' /proc/meminfo) * 1024 )) +[ "$swap_reported" = "$swap_expected" ] + +# io.systemd.Manager.Version should be non-empty and match what `systemctl --version` reports +metrics_version="$(varlinkctl call --more /run/systemd/report/io.systemd.Manager io.systemd.Metrics.List {} | jq --seq -r 'select(.name == "io.systemd.Manager.Version") | .value')" +[ -n "$metrics_version" ] +systemctl --version | grep -F "($metrics_version)" >/dev/null + +# Boot timeline timestamps. The kernel CLOCK_MONOTONIC is 0 by definition, so only its realtime is +# reported; userspace reports both realtime and monotonic. We don't check FinishTimestamp here: +# the test runs as a service, so manager_check_finished() typically hasn't fired yet and the +# timestamp is unset (the metric is then suppressed). Likewise KernelTimestamp is cleared when +# systemd runs inside a container (see main.c), so the metric is suppressed there too. +manager_metrics="$(varlinkctl call --more /run/systemd/report/io.systemd.Manager io.systemd.Metrics.List {})" +metric_value() { echo "$manager_metrics" | jq --seq -r "select(.name == \"io.systemd.Manager.$1\") | .value | tostring"; } +if ! systemd-detect-virt --quiet --container; then + [ "$(metric_value KernelTimestamp.Realtime)" -gt 0 ] +fi +[ "$(metric_value UserspaceTimestamp.Realtime)" -gt 0 ] +[ "$(metric_value UserspaceTimestamp.Monotonic)" -gt 0 ] + +# test io.systemd.Basic.MachineInfo.* metrics, sourced from /etc/machine-info +if [ -e /etc/machine-info ]; then + MACHINE_INFO_BACKUP="$(mktemp)" + cp /etc/machine-info "$MACHINE_INFO_BACKUP" + MACHINE_INFO_EXISTED=1 +else + MACHINE_INFO_EXISTED=0 +fi + +restore_machine_info() { + set +e + if [ "$MACHINE_INFO_EXISTED" = 1 ]; then + cp "$MACHINE_INFO_BACKUP" /etc/machine-info + rm -f "$MACHINE_INFO_BACKUP" + else + rm -f /etc/machine-info + fi + set -e +} +trap restore_machine_info EXIT + +cat >/etc/machine-info </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" upload --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" \ + --extra-header='Authorization: Bearer magic string' + +# ----------------------------------------------------------------------------- +# Test the systemd-report-files@.service metric provider: files dropped into one +# of the report.files directories are exposed verbatim as io.systemd.Files.* +# metrics, keyed by their file name. +mkdir -p /run/systemd/report.files +REPORT_FILE_CONTENT="hello from the report files metric provider" +printf '%s' "$REPORT_FILE_CONTENT" >/run/systemd/report.files/testreportfile + +systemctl start systemd-report-files.socket +varlinkctl info /run/systemd/report/io.systemd.Files +varlinkctl list-methods /run/systemd/report/io.systemd.Files + +# The metric value must be the exact file contents. +files_metrics="$(varlinkctl call --more /run/systemd/report/io.systemd.Files io.systemd.Metrics.List {})" +files_value() { echo "$files_metrics" | jq --seq -r "select(.name == \"$1\") | .value"; } +[ "$(files_value io.systemd.Files.testreportfile)" = "$REPORT_FILE_CONTENT" ] + +# Describe must list the very same metric, of type string. +files_describe="$(varlinkctl call --more /run/systemd/report/io.systemd.Files io.systemd.Metrics.Describe {})" +echo "$files_describe" | jq --seq -r 'select(.name == "io.systemd.Files.testreportfile") | .type' | grep -wx string >/dev/null + +# Files that aren't valid UTF-8 text must be skipped silently (the value is a +# JSON string, so only text files can be reported). +printf '\xff\xfe binary garbage' >/run/systemd/report.files/binaryfile +files_metrics="$(varlinkctl call --more /run/systemd/report/io.systemd.Files io.systemd.Metrics.List {})" +[ -z "$(files_value io.systemd.Files.binaryfile)" ] +rm -f /run/systemd/report.files/binaryfile + +# ----------------------------------------------------------------------------- +# Test report signing through the plain software backend, driven entirely via +# the io.systemd.Report Varlink interface (the GenerateSigned method). The plain +# backend is only built/installed with OpenSSL support, so skip if unavailable. +if ! systemctl cat systemd-report-sign-plain.socket &>/dev/null; then + echo "systemd-report-sign-plain.socket is not installed, skipping report signing test." + exit 0 +fi + +systemctl start systemd-report.socket +systemctl start systemd-report-sign-plain.socket + +SIGN_WORK="$(mktemp -d)" + +# Ask systemd-report to generate a *signed* report over Varlink. The reply +# carries the signed report as base64-encoded JSON-SEQ data. +varlinkctl call /run/systemd/io.systemd.Report io.systemd.Report.GenerateSigned \ + '{"matches":["io.systemd.Manager.UnitsTotal"]}' | jq -r .reportData | base64 -d >"$SIGN_WORK/report.seq" + +# The first JSON-SEQ record is the report itself. This is exactly the byte +# sequence that got signed, including the leading record separator (0x1e) and +# the trailing newline, so 'head -n1' reproduces it verbatim. +head -n1 "$SIGN_WORK/report.seq" >"$SIGN_WORK/message.bin" +tr -d '\036' <"$SIGN_WORK/message.bin" | jq -e '.mediaType == "application/vnd.io.systemd.report"' >/dev/null + +# The remaining record(s) are signature objects. With only the plain backend +# enabled there is exactly one, with mechanism "plain". +sig_json="$(tail -n +2 "$SIGN_WORK/report.seq" | tr -d '\036')" +[ "$(echo "$sig_json" | jq -r .mechanism)" = "plain" ] +[ "$(echo "$sig_json" | jq -r .mediaType)" = "application/vnd.io.systemd.report.signature" ] + +# The sha256 recorded in the signature must match the digest of the report bytes. +[ "$(echo "$sig_json" | jq -r .sha256)" = "$(sha256sum "$SIGN_WORK/message.bin" | cut -d' ' -f1)" ] + +# Extract the embedded public key and the raw Ed25519 signature. +echo "$sig_json" | jq -r .data.key >"$SIGN_WORK/pub.pem" +echo "$sig_json" | jq -r .data.signature | base64 -d >"$SIGN_WORK/sig.bin" + +# The signature is made over the SHA256 digest bytes directly (Ed25519, the +# signer does not pre-hash), so verify the raw digest against the signature. +openssl dgst -sha256 -binary "$SIGN_WORK/message.bin" >"$SIGN_WORK/digest.bin" +openssl pkeyutl -verify -pubin -inkey "$SIGN_WORK/pub.pem" -rawin \ + -in "$SIGN_WORK/digest.bin" -sigfile "$SIGN_WORK/sig.bin" + +# The public key embedded in the report must be the very key the plain backend +# generated and persisted on disk. +diff <(openssl pkey -pubin -in "$SIGN_WORK/pub.pem" -pubout) \ + <(openssl pkey -pubin -in /var/lib/systemd/report.sign.plain/local.public -pubout) + +# A tampered report must no longer verify against the signature. +printf 'x' >>"$SIGN_WORK/message.bin" +openssl dgst -sha256 -binary "$SIGN_WORK/message.bin" >"$SIGN_WORK/digest-bad.bin" +if openssl pkeyutl -verify -pubin -inkey "$SIGN_WORK/pub.pem" -rawin \ + -in "$SIGN_WORK/digest-bad.bin" -sigfile "$SIGN_WORK/sig.bin"; then + echo "Signature verification unexpectedly succeeded for tampered report" >&2 + exit 1 +fi + +# ----------------------------------------------------------------------------- +# The plain backend also acts as a metric provider, exposing its public signing +# key as the io.systemd.Report.SignPlain.PublicKey metric. This is served off the +# very same socket as the signer, reachable in the metrics directory under +# /run/systemd/report/ (the signer endpoint is just a symlink into the root-only +# /run/systemd/report.sign/ directory pointing at the same socket). +varlinkctl info /run/systemd/report/io.systemd.Report.SignPlain +varlinkctl list-methods /run/systemd/report/io.systemd.Report.SignPlain + +# The exposed public key must be the very key persisted on disk. +sign_metrics="$(varlinkctl call --more /run/systemd/report/io.systemd.Report.SignPlain io.systemd.Metrics.List {})" +echo "$sign_metrics" | jq --seq -r 'select(.name == "io.systemd.Report.SignPlain.PublicKey") | .value' >"$SIGN_WORK/metric-pub.pem" +diff <(openssl pkey -pubin -in "$SIGN_WORK/metric-pub.pem" -pubout) \ + <(openssl pkey -pubin -in /var/lib/systemd/report.sign.plain/local.public -pubout) + +# Describe must list the very same metric, of type string. +varlinkctl call --more /run/systemd/report/io.systemd.Report.SignPlain io.systemd.Metrics.Describe {} | + jq --seq -r 'select(.name == "io.systemd.Report.SignPlain.PublicKey") | .type' | grep -wx string >/dev/null 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 <&2 + exit 0 +fi + +UNIT_SOCKET_PATH="/run/test-socket-xattr.sock" +RUN_SOCKET_PATH="/run/test-socket-xattr-run.sock" + +at_exit() { + set +e + systemctl stop test-socket-xattr.socket test-socket-xattr-run.socket + systemctl reset-failed test-socket-xattr-run.socket test-socket-xattr-run.service + rm -f /run/systemd/system/test-socket-xattr.socket + rm -f /run/systemd/system/test-socket-xattr.service + systemctl daemon-reload +} +trap at_exit EXIT + +# Read the single user.varlink extended attribute value off the given path. +read_role() { + getfattr --absolute-names --only-values --name=user.varlink "$1" +} + +# ------------------------------------------------------------------------------ +# 1) XAttr*= configured in a unit file on disk +# ------------------------------------------------------------------------------ + +# A matching .service unit is required to be able to start the .socket unit. It is +# never actually triggered here (we only check the listening socket), so a trivial +# service is sufficient. +cat >/run/systemd/system/test-socket-xattr.service </run/systemd/system/test-socket-xattr.socket </dev/null +systemctl show -P XAttrListen test-socket-xattr.socket | grep "user.varlink=listen" >/dev/null +systemctl show -P XAttrAccept test-socket-xattr.socket | grep "user.varlink=server" >/dev/null + +# ------------------------------------------------------------------------------ +# 2) XAttr*= set via "systemd-run --socket-property=" +# ------------------------------------------------------------------------------ + +rm -f "$RUN_SOCKET_PATH" + +# systemd-run synthesizes the matching test-socket-xattr-run.service for us. +systemd-run \ + --unit=test-socket-xattr-run \ + --service-type=oneshot \ + --remain-after-exit \ + --socket-property=ListenStream="$RUN_SOCKET_PATH" \ + --socket-property=SocketMode=0666 \ + --socket-property=XAttrEntryPoint=user.varlink=entrypoint \ + --socket-property=XAttrListen=user.varlink=listen \ + --socket-property=XAttrAccept=user.varlink=server \ + --socket-property=RemoveOnStop=true \ + true + +systemctl cat test-socket-xattr-run.socket + +# The XAttr*= settings must have been serialized into the transient socket unit. +grep "^XAttrEntryPoint=user.varlink=entrypoint$" /run/systemd/transient/test-socket-xattr-run.socket >/dev/null +grep "^XAttrListen=user.varlink=listen$" /run/systemd/transient/test-socket-xattr-run.socket >/dev/null +grep "^XAttrAccept=user.varlink=server$" /run/systemd/transient/test-socket-xattr-run.socket >/dev/null + +# And the transient socket must validate cleanly. +systemd-analyze verify --recursive-errors=no "/run/systemd/transient/test-socket-xattr-run.socket" + +# Wait for the transient socket to be bound, then check the entrypoint xattr. +test -S "$RUN_SOCKET_PATH" +[[ "$(read_role "$RUN_SOCKET_PATH")" == "entrypoint" ]] + +systemctl show -P XAttrEntryPoint test-socket-xattr-run.socket | grep "user.varlink=entrypoint" >/dev/null +systemctl show -P XAttrListen test-socket-xattr-run.socket | grep "user.varlink=listen" >/dev/null +systemctl show -P XAttrAccept test-socket-xattr-run.socket | grep "user.varlink=server" >/dev/null diff --git a/test/units/TEST-74-AUX-UTILS.sysusers.sh b/test/units/TEST-74-AUX-UTILS.sysusers.sh index 2a06e85bfd464..6e3fcac8c56e2 100755 --- a/test/units/TEST-74-AUX-UTILS.sysusers.sh +++ b/test/units/TEST-74-AUX-UTILS.sysusers.sh @@ -11,11 +11,13 @@ u unlockedtestuser - "An unlocked system user" / /bin/bash u! lockedtestuser - "A locked system user" / /bin/bash EOF -userdbctl -j user unlockedtestuser -userdbctl -j user lockedtestuser +if command -v userdbctl >/dev/null; then + userdbctl -j user unlockedtestuser + userdbctl -j user lockedtestuser -assert_eq "$(userdbctl -j user unlockedtestuser | jq .locked)" "null" -assert_eq "$(userdbctl -j user lockedtestuser | jq .locked)" "true" + assert_eq "$(userdbctl -j user unlockedtestuser | jq .locked)" "null" + assert_eq "$(userdbctl -j user lockedtestuser | jq .locked)" "true" +fi at_exit() { set +e diff --git a/test/units/TEST-74-AUX-UTILS.userdbctl.sh b/test/units/TEST-74-AUX-UTILS.userdbctl.sh index 40818bfa6fe26..dd57c6fee56b7 100755 --- a/test/units/TEST-74-AUX-UTILS.userdbctl.sh +++ b/test/units/TEST-74-AUX-UTILS.userdbctl.sh @@ -3,6 +3,11 @@ set -eux set -o pipefail +if ! command -v userdbctl >/dev/null; then + echo "userdbctl is not installed, skipping the test." + exit 0 +fi + # shellcheck source=test/units/util.sh . "$(dirname "$0")"/util.sh @@ -51,13 +56,13 @@ assert_eq "$(userdbctl user 2147352577 -j | jq -r .userName)" foreign-1 assert_eq "$(userdbctl user 2147418110 -j | jq -r .userName)" foreign-65534 # Make sure that -F shows same data as if we'd ask directly -userdbctl user root -j | userdbctl -F- user | cmp - <(userdbctl user root) -userdbctl user test-74-userdbctl -j | userdbctl -F- user | cmp - <(userdbctl user test-74-userdbctl) -userdbctl user 65534 -j | userdbctl -F- user | cmp - <(userdbctl user 65534) +userdbctl user root -j | userdbctl -F- user | cmp - <(userdbctl user root) +userdbctl user test-74-userdbctl -j | userdbctl -F- user | cmp - <(userdbctl user test-74-userdbctl) +userdbctl user 65534 -j | userdbctl -F- user | cmp - <(userdbctl user 65534) -userdbctl group root -j | userdbctl -F- group | cmp - <(userdbctl group root) -userdbctl group test-74-userdbctl -j | userdbctl -F- group | cmp - <(userdbctl group test-74-userdbctl) -userdbctl group 65534 -j | userdbctl -F- group | cmp - <(userdbctl group 65534) +userdbctl group root -j | userdbctl -F- group | cmp - <(userdbctl group root) +userdbctl group test-74-userdbctl -j | userdbctl -F- group | cmp - <(userdbctl group test-74-userdbctl) +userdbctl group 65534 -j | userdbctl -F- group | cmp - <(userdbctl group 65534) # Ensure NSS doesn't try to automount via open_tree if [[ ! -v ASAN_OPTIONS ]]; then @@ -81,3 +86,7 @@ userdbctl group "$DISK_GID" | grep -F 'io.systemd.NameServiceSwitch' >/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-job.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-job.sh new file mode 100755 index 0000000000000..032904b4e559c --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-job.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# test io.systemd.Job +varlinkctl introspect /run/systemd/io.systemd.Manager io.systemd.Job + +# List with no jobs pending — should return empty with --more +varlinkctl --more call /run/systemd/io.systemd.Manager io.systemd.Job.List '{}' --graceful=io.systemd.Job.NoSuchJob + +# Without --more and no filter, must fail (streaming required) +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{}') + +# Error cases: non-existent job ID, non-existent unit +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{"id": 999999}') +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{"unit": "non-existent.service"}') + +# Invalid inputs +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{"id": 0}') +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Job.List '{"unit": ""}') + +at_exit() { + systemctl stop varlink-test-job.service 2>/dev/null || true + rm -f /run/systemd/system/varlink-test-job.service + systemctl daemon-reload +} +trap at_exit EXIT + +# Create a job by starting a slow service, then test List/Cancel +cat >/run/systemd/system/varlink-test-job.service <&2 + exit 0 +fi + +ENTRYPOINT_PATH="/run/test-list-sockets-entrypoint.sock" +PLAIN_PATH="/run/test-list-sockets-plain.sock" + +at_exit() { + set +e + systemctl stop test-list-sockets-entrypoint.socket + systemctl reset-failed test-list-sockets-entrypoint.socket test-list-sockets-entrypoint.service + systemctl stop test-list-sockets-plain.socket + systemctl reset-failed test-list-sockets-plain.socket test-list-sockets-plain.service + rm -f "$ENTRYPOINT_PATH" "$PLAIN_PATH" +} +trap at_exit EXIT + +rm -f "$ENTRYPOINT_PATH" "$PLAIN_PATH" + +# A listening socket tagged as a Varlink entrypoint. +systemd-run \ + --unit=test-list-sockets-entrypoint \ + --service-type=oneshot \ + --remain-after-exit \ + --socket-property=ListenStream="$ENTRYPOINT_PATH" \ + --socket-property=SocketMode=0666 \ + --socket-property=XAttrEntryPoint=user.varlink=entrypoint \ + --socket-property=RemoveOnStop=true \ + true + +# A listening socket *without* the entrypoint marker, which must be ignored. +systemd-run \ + --unit=test-list-sockets-plain \ + --service-type=oneshot \ + --remain-after-exit \ + --socket-property=ListenStream="$PLAIN_PATH" \ + --socket-property=SocketMode=0666 \ + --socket-property=RemoveOnStop=true \ + true + +test -S "$ENTRYPOINT_PATH" +test -S "$PLAIN_PATH" + +# Plain text output should run cleanly and mention the entrypoint socket. +varlinkctl list-sockets +varlinkctl list-sockets | grep "$ENTRYPOINT_PATH" >/dev/null + +# JSON output is an array of {path, access} objects. The entrypoint socket must be +# present... +json="$(varlinkctl --json=short list-sockets)" +echo "$json" | jq -e --arg p "$ENTRYPOINT_PATH" 'any(.[]; .path == $p)' >/dev/null +# ...and carry an "access" field (either "yes" or "No (…)"). +echo "$json" | jq -e --arg p "$ENTRYPOINT_PATH" 'any(.[]; .path == $p and (.access | type == "string"))' >/dev/null + +# The socket without the entrypoint xattr must NOT be listed. +(! echo "$json" | jq -e --arg p "$PLAIN_PATH" 'any(.[]; .path == $p)' >/dev/null) + +# Stopping the socket unit must make the entrypoint disappear from the listing again. +systemctl stop test-list-sockets-entrypoint.socket +test ! -S "$ENTRYPOINT_PATH" +(! varlinkctl --json=short list-sockets | jq -e --arg p "$ENTRYPOINT_PATH" 'any(.[]; .path == $p)' >/dev/null) + +systemctl stop test-list-sockets-plain.socket +test ! -S "$PLAIN_PATH" diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-metrics.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-metrics.sh new file mode 100755 index 0000000000000..49d3373a7cbbd --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-metrics.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# test io.systemd.Metrics +varlinkctl info /run/systemd/report/io.systemd.Manager + +varlinkctl list-methods /run/systemd/report/io.systemd.Manager +varlinkctl list-methods -j /run/systemd/report/io.systemd.Manager io.systemd.Metrics | jq . + +varlinkctl introspect /run/systemd/report/io.systemd.Manager +varlinkctl introspect -j /run/systemd/report/io.systemd.Manager io.systemd.Metrics | jq . + +varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Metrics.List '{}' +varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Metrics.Describe '{}' diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-network.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-network.sh new file mode 100755 index 0000000000000..cae3d7a66e123 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-network.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# test io.systemd.Network +varlinkctl info /run/systemd/netif/io.systemd.Network +varlinkctl introspect /run/systemd/netif/io.systemd.Network io.systemd.Network +varlinkctl call /run/systemd/netif/io.systemd.Network io.systemd.Network.Describe '{}' diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh new file mode 100755 index 0000000000000..4c237fd6cb798 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl-unit.sh @@ -0,0 +1,335 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# test io.systemd.Unit +varlinkctl info /run/systemd/io.systemd.Manager +varlinkctl introspect /run/systemd/io.systemd.Manager io.systemd.Unit +varlinkctl --more call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target"}' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 1}}' +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' |& grep "called without 'more' flag" >/dev/null) +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "init.scope", "pid": {"pid": 1}}' +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": ""}') +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "non-existent.service"}') +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": -1}}' ) +(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target", "pid": {"pid": 1}}') +set +o pipefail +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "non-existent.service", "properties": {"Markers": ["needs-restart"]}}' |& grep "io.systemd.Unit.NoSuchUnit" +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "systemd-journald.service", "properties": {"LoadState": "foobar"}}' |& grep "io.systemd.Unit.PropertyNotSupported" +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 // empty' | tail -n 1) +test -n "$automount_id" +# Use jq to JSON-encode the unit name as it may contain backslash escapes (e.g. \x2d) that +# are not valid JSON escape sequences and would be rejected by varlinkctl's JSON parser. +automount_params=$(jq -cn --arg name "$automount_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | jq -e '.context.Automount' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$automount_params" | jq -e '.runtime.Automount' +# test for MountContext/Runtime (skip volatile run-user-*.mount to avoid GC race) +mount_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "mount" and .runtime.LoadState == "loaded" and (.context.ID | startswith("run-user-") | not)) .context.ID // empty' | tail -n 1) +test -n "$mount_id" +mount_params=$(jq -cn --arg name "$mount_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.context.Mount' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$mount_params" | jq -e '.runtime.Mount' +# test for PathContext/Runtime +path_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "path" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$path_id" +path_params=$(jq -cn --arg name "$path_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.context.Path' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$path_params" | jq -e '.runtime.Path' +# test for ServiceContext/Runtime (skip volatile user@*/user-runtime-dir@* to avoid GC race) +service_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "service" and .runtime.LoadState == "loaded" and (.context.ID | test("^(user|user-runtime-dir)@") | not)) .context.ID // empty' | tail -n 1) +test -n "$service_id" +service_params=$(jq -cn --arg name "$service_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.context.Service' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$service_params" | jq -e '.runtime.Service' +# test for ScopeContext/Runtime (skip volatile session-*.scope to avoid GC race) +scope_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "scope" and .runtime.LoadState == "loaded" and (.context.ID | startswith("session-") | not)) .context.ID // empty' | tail -n 1) +test -n "$scope_id" +scope_params=$(jq -cn --arg name "$scope_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.context.Scope' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$scope_params" | jq -e '.runtime.Scope' +# test for SocketContext/Runtime +socket_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "socket" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$socket_id" +socket_params=$(jq -cn --arg name "$socket_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.context.Socket' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$socket_params" | jq -e '.runtime.Socket' +# test for SwapContext/Runtime (swap units may not be present on all systems) +swap_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "swap" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +if test -n "$swap_id"; then + swap_params=$(jq -cn --arg name "$swap_id" '{name: $name}') + varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.context.Swap' + varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$swap_params" | jq -e '.runtime.Swap' +fi +# test for TimerContext/Runtime +timer_id=$(varlinkctl call --collect /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' | jq -r '.[] | select(.context.Type == "timer" and .runtime.LoadState == "loaded") .context.ID // empty' | tail -n 1) +test -n "$timer_id" +timer_params=$(jq -cn --arg name "$timer_id" '{name: $name}') +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.context.Timer' +varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List "$timer_params" | jq -e '.runtime.Timer' + +# test io.systemd.Unit in user manager +testuser_uid=$(id -u testuser) +systemd-run --wait --pipe --user --machine testuser@ \ + varlinkctl --more call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Unit.List '{}' + +# Test io.systemd.Unit.StartTransient +MANAGER_SOCKET="/run/systemd/io.systemd.Manager" + +TRANSIENT_UNITS=() +defer_transient_cleanup() { + TRANSIENT_UNITS+=("$1") +} +transient_cleanup() { + for u in "${TRANSIENT_UNITS[@]}"; do + systemctl stop "$u" 2>/dev/null || true + systemctl reset-failed "$u" 2>/dev/null || true + done +} +trap transient_cleanup EXIT + +# Basic oneshot transient service +defer_transient_cleanup varlink-transient-test.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | grep '"context"' >/dev/null +echo "$result" | grep '"runtime"' >/dev/null + +# Wait for completion +timeout 30 bash -c 'until systemctl show -P ActiveState varlink-transient-test.service | grep inactive >/dev/null; do sleep 0.5; done' +systemctl show -P Result varlink-transient-test.service | grep success >/dev/null + +# With explicit mode +defer_transient_cleanup varlink-transient-test2.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test2.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"mode":"fail"}') +echo "$result" | grep '"context"' >/dev/null + +# Streaming with notifyJobChanges: should get intermediate state updates and a final result +# Note: use --slurp + any() rather than 'select() -e' because in jq 1.6 (shipped on +# CentOS 9) -e checks only the last input record's output, so a select() that filters +# out the trailing record makes jq exit non-zero even when earlier records match. +defer_transient_cleanup varlink-transient-test3.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test3.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true}') +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.State == "waiting")' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null + +# Fire-and-forget: --more without notify flags should return immediately with context+runtime +defer_transient_cleanup varlink-transient-fireforget.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-fireforget.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}') +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .context)' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime)' >/dev/null + +# Streaming with notifyUnitChanges: should get unit state change notifications +defer_transient_cleanup varlink-transient-unitnotify.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-unitnotify.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyUnitChanges":true}') +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime.ActiveState)' >/dev/null + +# Streaming with both notifyJobChanges and notifyUnitChanges +defer_transient_cleanup varlink-transient-both.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-both.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}},"notifyJobChanges":true,"notifyUnitChanges":true}') +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.State)' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .runtime.ActiveState)' >/dev/null +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null + +# prepare for the error case below: create a long-running service, then try to create it again while it's active +defer_transient_cleanup varlink-transient-exists.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' +timeout 10 bash -c 'until systemctl is-active varlink-transient-exists.service; do sleep 0.5; done' + +# Multiple ExecStart commands (oneshot allows multiple) +defer_transient_cleanup varlink-transient-multi.service +result=$(varlinkctl call --more "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-multi.service","Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"},{"path":"/bin/true"}]}},"notifyJobChanges":true}') +printf '%s' "$result" | jq --seq --slurp -e 'any(.[]; .job.Result == "done")' >/dev/null + +# Transient service with Description and RemainAfterExit +defer_transient_cleanup varlink-transient-desc.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-desc.service","Description":"Test description property","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | jq -e '.context.Description == "Test description property"' +echo "$result" | jq -e '.context.Service.Type == "oneshot"' +echo "$result" | jq -e '.context.Service.RemainAfterExit == true' +echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/true"' +echo "$result" | jq -e '.runtime' + +# Transient service with explicit arguments +defer_transient_cleanup varlink-transient-args.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-args.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/echo","arguments":["/bin/echo","hello"]}]}}}') +echo "$result" | jq -e '.context' +echo "$result" | jq -e '.runtime' +echo "$result" | jq -e '.context.Service.ExecStart[0].path == "/bin/echo"' +echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/echo", "hello"]' +timeout 30 bash -c 'until systemctl is-active varlink-transient-args.service; do sleep 0.5; done' + +# Verify that omitting arguments defaults argv[0] to the path +defer_transient_cleanup varlink-transient-noargs.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-noargs.service","Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | jq -e '.context.Service.ExecStart[0].arguments == ["/bin/true"]' +timeout 30 bash -c 'until systemctl is-active varlink-transient-noargs.service; do sleep 0.5; done' + +# Exec.WorkingDirectory and Exec.Environment +defer_transient_cleanup varlink-transient-exec.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-exec.service","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false},"Environment":["FOO=bar","BAZ=qux"]},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | jq -e '.context.Exec.WorkingDirectory.path == "/tmp"' +echo "$result" | jq -e '.context.Exec.Environment | index("FOO=bar") != null' +echo "$result" | jq -e '.context.Exec.Environment | index("BAZ=qux") != null' +timeout 30 bash -c 'until systemctl is-active varlink-transient-exec.service; do sleep 0.5; done' +systemctl show -P WorkingDirectory varlink-transient-exec.service | grep '^/tmp$' >/dev/null +systemctl show -P Environment varlink-transient-exec.service | grep 'FOO=bar' >/dev/null +systemctl show -P Environment varlink-transient-exec.service | grep 'BAZ=qux' >/dev/null + +# WorkingDirectory with missingOK=true (path does not exist but unit still starts) +defer_transient_cleanup varlink-transient-wd-missing.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-wd-missing.service","Exec":{"WorkingDirectory":{"path":"/nonexistent/path","missingOK":true}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}' +timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-missing.service; do sleep 0.5; done' + +# WorkingDirectory with home=true, missingOK omitted (defaults to false) +defer_transient_cleanup varlink-transient-wd-home.service +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-wd-home.service","Exec":{"WorkingDirectory":{"home":true}},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}' +timeout 30 bash -c 'until systemctl is-active varlink-transient-wd-home.service; do sleep 0.5; done' +systemctl show -P WorkingDirectory varlink-transient-wd-home.service | grep '^~$' >/dev/null + +# Exec.SetCredential: pass a credential and verify the running process can read it +defer_transient_cleanup varlink-transient-cred.service +CRED_VALUE_B64=$(printf 'secret-value' | base64 -w0) +CRED_OUTPUT=$(mktemp) +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + "{\"context\":{\"ID\":\"varlink-transient-cred.service\",\"Exec\":{\"SetCredential\":[{\"id\":\"mycred\",\"value\":\"${CRED_VALUE_B64}\"}]},\"Service\":{\"Type\":\"oneshot\",\"RemainAfterExit\":true,\"ExecStart\":[{\"path\":\"/bin/sh\",\"arguments\":[\"/bin/sh\",\"-c\",\"cat \$CREDENTIALS_DIRECTORY/mycred > ${CRED_OUTPUT}\"]}]}}}" +timeout 30 bash -c "until systemctl is-active varlink-transient-cred.service; do sleep 0.5; done" +grep '^secret-value$' "$CRED_OUTPUT" >/dev/null +rm -f "$CRED_OUTPUT" + +# Exec.User, Exec.Group, Exec.SupplementaryGroups, Exec.Nice +# The nobody group is different on different distros so resolve here. +NOBODY_GROUP=$(id -gn nobody) +defer_transient_cleanup varlink-transient-ids.service +ids_payload=$(jq -cn --arg g "$NOBODY_GROUP" \ + '{context:{ID:"varlink-transient-ids.service", + Exec:{User:"nobody",Group:$g,SupplementaryGroups:[$g],Nice:5}, + Service:{Type:"oneshot",RemainAfterExit:true, + ExecStart:[{path:"/bin/true"}]}}}') +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient "$ids_payload") +echo "$result" | jq -e '.context.Exec.User == "nobody"' +echo "$result" | jq --arg g "$NOBODY_GROUP" -e '.context.Exec.Group == $g' +echo "$result" | jq --arg g "$NOBODY_GROUP" -e '.context.Exec.SupplementaryGroups == [$g]' +echo "$result" | jq -e '.context.Exec.Nice == 5' +timeout 30 bash -c 'until systemctl is-active varlink-transient-ids.service; do sleep 0.5; done' +systemctl show -P User varlink-transient-ids.service | grep '^nobody$' >/dev/null +systemctl show -P Group varlink-transient-ids.service | grep "^${NOBODY_GROUP}$" >/dev/null +systemctl show -P SupplementaryGroups varlink-transient-ids.service | grep "${NOBODY_GROUP}" >/dev/null +systemctl show -P Nice varlink-transient-ids.service | grep '^5$' >/dev/null + +# Exec.OOMScoreAdjust, Exec.UMask, Exec.NoNewPrivileges, Exec.MemoryDenyWriteExecute +# (int+validator, mode_t, two tristate-bool shapes) +defer_transient_cleanup varlink-transient-procctl.service +result=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-procctl.service","Exec":{"OOMScoreAdjust":250,"UMask":18,"NoNewPrivileges":true,"MemoryDenyWriteExecute":true},"Service":{"Type":"oneshot","RemainAfterExit":true,"ExecStart":[{"path":"/bin/true"}]}}}') +echo "$result" | jq -e '.context.Exec.OOMScoreAdjust == 250' +echo "$result" | jq -e '.context.Exec.UMask == 18' +echo "$result" | jq -e '.context.Exec.NoNewPrivileges == true' +timeout 30 bash -c 'until systemctl is-active varlink-transient-procctl.service; do sleep 0.5; done' +systemctl show -P OOMScoreAdjust varlink-transient-procctl.service | grep '^250$' >/dev/null +systemctl show -P UMask varlink-transient-procctl.service | grep '^0022$' >/dev/null +systemctl show -P NoNewPrivileges varlink-transient-procctl.service | grep '^yes$' >/dev/null +systemctl show -P MemoryDenyWriteExecute varlink-transient-procctl.service | grep '^yes$' >/dev/null + +# Error cases: verify specific varlink error types +set +o pipefail +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-exists.service","Service":{"ExecStart":[{"path":"/usr/bin/sleep","arguments":["/usr/bin/sleep","infinity"]}]}}}' |& grep "io.systemd.Unit.UnitExists" +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-test.target","Description":"test"}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" +# Apply-time and dispatch-time validation errors both surface as +# org.varlink.service.InvalidParameter, with the offending field name in the +# response parameters. Use --graceful to treat the expected error as success +# so jq can assert on the dumped parameters JSON directly. +expect_invalid_parameter() { + local payload="$1" field="$2" + varlinkctl call --graceful=org.varlink.service.InvalidParameter \ + "$MANAGER_SOCKET" io.systemd.Unit.StartTransient "$payload" \ + | jq -e --arg f "$field" '.parameter == $f' >/dev/null +} +defer_transient_cleanup varlink-transient-bad.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad.service","Service":{"Type":"simple"}}}' \ + "context" +# Invalid ExecStart path: exercises filename_or_absolute_path_is_valid() in transient_service_apply_properties() +defer_transient_cleanup varlink-transient-badpath.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-badpath.service","Service":{"Type":"simple","ExecStart":[{"path":""}]}}}' \ + "Service.ExecStart" +# Relative WorkingDirectory path is rejected +defer_transient_cleanup varlink-transient-bad-wd.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-wd.service","Exec":{"WorkingDirectory":{"path":"relative/path","missingOK":false}},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.WorkingDirectory" +# Malformed environment entry (not KEY=VALUE) +defer_transient_cleanup varlink-transient-bad-env.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-env.service","Exec":{"Environment":["not_an_env_var"]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.Environment" +# Invalid User= name is rejected at JSON dispatch time as a parameter error +defer_transient_cleanup varlink-transient-bad-user.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-user.service","Exec":{"User":"bad/user"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "context" +# Out-of-range Nice= value is rejected +defer_transient_cleanup varlink-transient-bad-nice.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-nice.service","Exec":{"Nice":100},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.Nice" +# Out-of-range OOMScoreAdjust= value is rejected +defer_transient_cleanup varlink-transient-bad-oom.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-oom.service","Exec":{"OOMScoreAdjust":9999},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.OOMScoreAdjust" +# Invalid credential ID +defer_transient_cleanup varlink-transient-bad-cred-id.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-cred-id.service","Exec":{"SetCredential":[{"id":"bad/id","value":"YWJj"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "Exec.SetCredential" +# Invalid base64 value for credential (rejected at JSON dispatch time as a parameter error) +defer_transient_cleanup varlink-transient-bad-cred-value.service +expect_invalid_parameter \ + '{"context":{"ID":"varlink-transient-bad-cred-value.service","Exec":{"SetCredential":[{"id":"mycred","value":"!!!not_base64!!!"}]},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' \ + "context" +# Exec on a unit type without an exec context (.slice) is rejected +varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-exec.slice","Exec":{"WorkingDirectory":{"path":"/tmp","missingOK":false}}}}' |& grep "io.systemd.Unit.UnitTypeNotSupported" +# Unknown field in Exec is rejected as PropertyNotSupported +defer_transient_cleanup varlink-transient-unknown-exec.service +unsupported_exec=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-unknown-exec.service","Exec":{"RootDirectory":"/tmp"},"Service":{"Type":"oneshot","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true) +echo "$unsupported_exec" | grep "io.systemd.Unit.PropertyNotSupported" +echo "$unsupported_exec" | grep "Exec.RootDirectory" +# Service field declared in the IDL but not yet settable at creation is rejected as PropertyNotSupported, +# and the offending sub-property is identified +defer_transient_cleanup varlink-transient-unknown-service.service +unsupported_service=$(varlinkctl call "$MANAGER_SOCKET" io.systemd.Unit.StartTransient \ + '{"context":{"ID":"varlink-transient-unknown-service.service","Service":{"Type":"oneshot","Restart":"always","ExecStart":[{"path":"/bin/true"}]}}}' 2>&1 || true) +echo "$unsupported_service" | grep "io.systemd.Unit.PropertyNotSupported" +echo "$unsupported_service" | grep "Service.Restart" +set -o pipefail + +transient_cleanup +trap - EXIT diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index dc5f952e59277..ed2e0cb73bd0e 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -106,7 +106,7 @@ rm_rf_sshbindir() { trap rm_rf_sshbindir EXIT # Create a fake "ssh" binary that validates everything works as expected if invoked for the "ssh-unix:" Varlink transport -cat > "$SSHBINDIR"/ssh <<'EOF' +cat >"$SSHBINDIR"/ssh <<'EOF' #!/usr/bin/env bash set -xe @@ -122,7 +122,7 @@ chmod +x "$SSHBINDIR"/ssh SYSTEMD_SSH="$SSHBINDIR/ssh" varlinkctl info ssh-unix:foobar:/run/systemd/journal/io.systemd.journal # Now build another fake "ssh" binary that does the same for "ssh-exec:" -cat > "$SSHBINDIR"/ssh <<'EOF' +cat >"$SSHBINDIR"/ssh <<'EOF' #!/usr/bin/env bash set -xe @@ -204,44 +204,14 @@ varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Describe '{}' varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Reload '{}' # This will disconnect and fail, as the manager reexec and drops connections varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Manager.Reexecute '{}' ||: - -# test io.systemd.Network -varlinkctl info /run/systemd/netif/io.systemd.Network -varlinkctl introspect /run/systemd/netif/io.systemd.Network io.systemd.Network -varlinkctl call /run/systemd/netif/io.systemd.Network io.systemd.Network.Describe '{}' - -# test io.systemd.Unit -varlinkctl info /run/systemd/io.systemd.Manager -varlinkctl introspect /run/systemd/io.systemd.Manager io.systemd.Unit -varlinkctl --more call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target"}' -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": 1}}' -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{}' |& grep "called without 'more' flag" >/dev/null) -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "init.scope", "pid": {"pid": 1}}' -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": ""}') -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "non-existent.service"}') -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": -1}}' ) -(! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "multi-user.target", "pid": {"pid": 1}}') -set +o pipefail -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "non-existent.service", "properties": {"Markers": ["needs-restart"]}}' |& grep "io.systemd.Unit.NoSuchUnit" -varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.SetProperties '{"runtime": true, "name": "systemd-journald.service", "properties": {"LoadState": "foobar"}}' |& grep "io.systemd.Unit.PropertyNotSupported" -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 io.systemd.Metrics -varlinkctl info /run/systemd/report/io.systemd.Manager - -varlinkctl list-methods /run/systemd/report/io.systemd.Manager -varlinkctl list-methods -j /run/systemd/report/io.systemd.Manager io.systemd.Metrics | jq . - -varlinkctl introspect /run/systemd/report/io.systemd.Manager -varlinkctl introspect -j /run/systemd/report/io.systemd.Manager io.systemd.Metrics | jq . - -varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Metrics.List {} -varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Metrics.Describe {} +# Wait for the manager to finish re-exec before proceeding — the user manager +# tests below use systemd-run which requires a functional PID 1. +for _ in {1..10}; do + if systemctl is-system-running 2>/dev/null | grep -E 'running|degraded' >/dev/null; then + break + fi + sleep 1 +done # test io.systemd.Manager in user manager testuser_uid=$(id -u testuser) @@ -252,6 +222,147 @@ systemd-run --wait --pipe --user --machine testuser@ \ systemd-run --wait --pipe --user --machine testuser@ \ varlinkctl call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Manager.Describe '{}' -# 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"}' >>" >/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..d9757b385596e 100755 --- a/test/units/TEST-75-RESOLVED.sh +++ b/test/units/TEST-75-RESOLVED.sh @@ -248,19 +248,14 @@ manual_testcase_01_resolvectl() { resolvectl dns hoge.foo 10.0.1.3 10.0.1.4 assert_in '10.0.1.1 10.0.1.2' "$(resolvectl dns hoge)" assert_in '10.0.1.3 10.0.1.4' "$(resolvectl dns hoge.foo)" - if ! RESOLVCONF=$(command -v resolvconf 2>/dev/null); then - TMPDIR=$(mktemp -d -p /tmp resolvconf-tests.XXXXXX) - RESOLVCONF="$TMPDIR"/resolvconf - ln -s "$(command -v resolvectl 2>/dev/null)" "$RESOLVCONF" - fi # DNS servers - echo nameserver 10.0.2.1 10.0.2.2 | "$RESOLVCONF" -a hoge - echo nameserver 10.0.2.3 10.0.2.4 | "$RESOLVCONF" -a hoge.foo + echo nameserver 10.0.2.1 10.0.2.2 | SYSTEMD_INVOKED_AS=resolvconf resolvectl -a hoge + echo nameserver 10.0.2.3 10.0.2.4 | SYSTEMD_INVOKED_AS=resolvconf resolvectl -a hoge.foo assert_in '10.0.2.1 10.0.2.2' "$(resolvectl dns hoge)" assert_in '10.0.2.3 10.0.2.4' "$(resolvectl dns hoge.foo)" - echo nameserver 10.0.3.1 10.0.3.2 | "$RESOLVCONF" -a hoge.inet.ipsec.192.168.35 - echo nameserver 10.0.3.3 10.0.3.4 | "$RESOLVCONF" -a hoge.foo.dhcp + echo nameserver 10.0.3.1 10.0.3.2 | SYSTEMD_INVOKED_AS=resolvconf resolvectl -a hoge.inet.ipsec.192.168.35 + echo nameserver 10.0.3.3 10.0.3.4 | SYSTEMD_INVOKED_AS=resolvconf resolvectl -a hoge.foo.dhcp assert_in '10.0.3.1 10.0.3.2' "$(resolvectl dns hoge)" assert_in '10.0.3.3 10.0.3.4' "$(resolvectl dns hoge.foo)" @@ -268,36 +263,37 @@ manual_testcase_01_resolvectl() { # without domain/search clears existing domain resolvectl domain hoge test-domain.example.com assert_in 'test-domain.example.com' "$(resolvectl domain hoge)" - echo nameserver 10.0.2.1 10.0.2.2 | "$RESOLVCONF" -a hoge + echo nameserver 10.0.2.1 10.0.2.2 | SYSTEMD_INVOKED_AS=resolvconf resolvectl -a hoge assert_not_in 'test-domain.example.com' "$(resolvectl domain hoge)" # cannot set domain without DNS servers - (! echo domain test-domain.example.com | "$RESOLVCONF" -a hoge) + (! echo domain test-domain.example.com | SYSTEMD_INVOKED_AS=resolvconf resolvectl -a hoge) # can set domain with DNS server(s) - echo -e "nameserver 10.0.2.1 10.0.2.2\ndomain test-domain1.example.com test-domain2.example.com\nsearch test-search-domain.example.com" | "$RESOLVCONF" -a hoge + echo -e "nameserver 10.0.2.1 10.0.2.2\ndomain test-domain1.example.com test-domain2.example.com\nsearch test-search-domain.example.com" | \ + SYSTEMD_INVOKED_AS=resolvconf resolvectl -a hoge assert_in 'test-domain1.example.com' "$(resolvectl domain hoge)" assert_in 'test-domain2.example.com' "$(resolvectl domain hoge)" assert_in 'test-search-domain.example.com' "$(resolvectl domain hoge)" # Tests for 'resolvconf -x' - echo nameserver 10.0.2.1 | "$RESOLVCONF" -x -a hoge + echo nameserver 10.0.2.1 | SYSTEMD_INVOKED_AS=resolvconf resolvectl -x -a hoge assert_in '~.' "$(resolvectl domain hoge)" resolvectl domain hoge "hoge.example.com" assert_in 'hoge.example.com' "$(resolvectl domain hoge)" assert_not_in '~.' "$(resolvectl domain hoge)" - echo -e "nameserver 10.0.2.1\ndomain test-domain.example.com" | "$RESOLVCONF" -x -a hoge + echo -e "nameserver 10.0.2.1\ndomain test-domain.example.com" | SYSTEMD_INVOKED_AS=resolvconf resolvectl -x -a hoge assert_in 'test-domain.example.com' "$(resolvectl domain hoge)" assert_in '~.' "$(resolvectl domain hoge)" # Tests for 'resolvconf -p' resolvectl default-route hoge yes assert_in 'yes' "$(resolvectl default-route hoge)" - echo nameserver 10.0.3.3 10.0.3.4 | "$RESOLVCONF" -p -a hoge + echo nameserver 10.0.3.3 10.0.3.4 | SYSTEMD_INVOKED_AS=resolvconf resolvectl -p -a hoge assert_in 'no' "$(resolvectl default-route hoge)" # Tests for 'resolvconf -d' resolvectl dns hoge 10.0.3.1 10.0.3.2 resolvectl domain hoge test-domain.example.com - "$RESOLVCONF" -d hoge + SYSTEMD_INVOKED_AS=resolvconf resolvectl -d hoge assert_not_in '10.0.3.1' "$(resolvectl dns hoge)" assert_not_in '10.0.3.2' "$(resolvectl dns hoge)" assert_not_in 'test-domain.example.com' "$(resolvectl domain hoge)" @@ -774,7 +770,7 @@ testcase_08_resolved() { grep -qE "^edns-extra-text.forwarded.test.+: SERVFAIL \(Censored: Nothing to see here!\)" "$RUN_OUT" (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-extra-text.forwarded.test"}') grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT" - grep -qF '{"rcode":2,"extendedDNSErrorCode":16,"extendedDNSErrorMessage":"Nothing to see here!"}' "$RUN_OUT" + grep -qF '{"rcode":2,"extendedDNSErrorCode":16,"extendedDNSErrorMessage":"Nothing to see here!","queryString":"edns-extra-text.forwarded.test"}' "$RUN_OUT" journalctl --sync journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(Censored: Nothing to see here!\)" @@ -783,7 +779,7 @@ testcase_08_resolved() { grep -qE "^edns-code-zero.forwarded.test:.+: SERVFAIL \(Other: 🐱\)" "$RUN_OUT" (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-code-zero.forwarded.test"}') grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT" - grep -qF '{"rcode":2,"extendedDNSErrorCode":0,"extendedDNSErrorMessage":"🐱"}' "$RUN_OUT" + grep -qF '{"rcode":2,"extendedDNSErrorCode":0,"extendedDNSErrorMessage":"🐱","queryString":"edns-code-zero.forwarded.test"}' "$RUN_OUT" journalctl --sync journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(Other: 🐱\)" @@ -792,7 +788,7 @@ testcase_08_resolved() { grep -qE "^edns-invalid-code.forwarded.test:.+: SERVFAIL \([0-9]+\)" "$RUN_OUT" (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-invalid-code.forwarded.test"}') grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT" - grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+}' "$RUN_OUT" + grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+,"queryString":"edns-invalid-code.forwarded.test"}' "$RUN_OUT" journalctl --sync journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(\d+\)" @@ -801,7 +797,7 @@ testcase_08_resolved() { grep -qE '^edns-invalid-code-with-extra-text.forwarded.test:.+: SERVFAIL \([0-9]+: Hello \[#\]\$%~ World\)' "$RUN_OUT" (! run varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name" : "edns-invalid-code-with-extra-text.forwarded.test"}') grep -qF "io.systemd.Resolve.DNSError" "$RUN_OUT" - grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+,"extendedDNSErrorMessage":"Hello \[#\]\$%~ World"}' "$RUN_OUT" + grep -qE '{"rcode":2,"extendedDNSErrorCode":[0-9]+,"extendedDNSErrorMessage":"Hello \[#\]\$%~ World","queryString":"edns-invalid-code-with-extra-text.forwarded.test"}' "$RUN_OUT" journalctl --sync journalctl -u systemd-resolved.service --cursor-file="$JOURNAL_CURSOR" --grep "Server returned error: SERVFAIL \(\d+: Hello \[\#\]\\$%~ World\)" } @@ -1262,13 +1258,13 @@ testcase_14_refuse_record_types() { grep -qF "127.128.0.5" "$RUN_OUT" (! run resolvectl query localhost5 --type=SRV) - grep -qF "DNS query type refused." "$RUN_OUT" + grep -qF "DNS query type refused" "$RUN_OUT" (! run resolvectl query localhost5 --type=TXT) - grep -qF "DNS query type refused." "$RUN_OUT" + grep -qF "DNS query type refused" "$RUN_OUT" (! run resolvectl query localhost5 --type=AAAA) - grep -qF "DNS query type refused." "$RUN_OUT" + grep -qF "DNS query type refused" "$RUN_OUT" run resolvectl query localhost5 --type=A grep -qF "127.128.0.5" "$RUN_OUT" @@ -1308,7 +1304,7 @@ testcase_14_refuse_record_types() { grep -qF "does not have any RR of the requested type" "$RUN_OUT" (! run resolvectl query localhost5 --type=AAAA) - grep -qF "DNS query type refused." "$RUN_OUT" + grep -qF "DNS query type refused" "$RUN_OUT" run resolvectl service _mysvc._tcp signed.test grep -qF "myservice.signed.test:1234" "$RUN_OUT" @@ -1387,7 +1383,7 @@ testcase_15_wait_online_dns() { echo "===== journalctl -u $unit =====" journalctl -b --no-pager --no-hostname --full -u "$unit" echo "==========" - rm -f "$override" + rm -f "$override" "$cursor_file" restart_resolved resolvectl revert dns0 } @@ -1396,6 +1392,7 @@ testcase_15_wait_online_dns() { local unit local override + local cursor_file unit="wait-online-dns-$(systemd-id128 new -u).service" override="/run/systemd/resolved.conf.d/90-global-dns.conf" @@ -1416,12 +1413,26 @@ testcase_15_wait_online_dns() { systemctl stop systemd-resolved.service systemctl start systemd-resolved-monitor.socket systemd-resolved-varlink.socket + # Capture a journal cursor before starting the unit so we can match only on + # log messages emitted afterwards. We deliberately do not filter on + # _SYSTEMD_UNIT= because journald may attach stale cgroup metadata + # (e.g. _SYSTEMD_UNIT=init.scope) to the very first messages emitted by a + # freshly-spawned process, before its cgroup migration into the new service + # is observed. Filtering by SYSLOG_IDENTIFIER and a cursor is not affected + # by that race. + cursor_file=$(mktemp) + journalctl -n 0 --cursor-file="$cursor_file" + # Begin systemd-networkd-wait-online --dns systemd-run -u "$unit" -p "Environment=SYSTEMD_LOG_LEVEL=debug" -p "Environment=SYSTEMD_LOG_TARGET=journal" --service-type=exec \ /usr/lib/systemd/systemd-networkd-wait-online --timeout=0 --dns --interface=dns0 - # Wait until it blocks waiting for updated DNS config - timeout 30 bash -c "journalctl -b -u $unit -f | grep -m1 'dns0: No.*DNS server is accessible'" >/dev/null + # Wait until it blocks waiting for updated DNS config. + # Note: don't use 'journalctl -f | grep -m1 ...' here. Once grep exits on + # match, journalctl -f will only notice the closed pipe on its next write + # attempt, which may never come for an otherwise idle unit, causing the + # pipeline to hang. + timeout 30 bash -c "until journalctl --after-cursor=\"\$(cat \"$cursor_file\")\" SYSLOG_IDENTIFIER=systemd-networkd-wait-online --grep 'dns0: No.*DNS server is accessible' >/dev/null 2>&1; do sleep 0.5; done" # Update the global configuration. Restart rather than reload systemd-resolved so that # systemd-networkd-wait-online has to re-connect to the varlink service. @@ -1436,10 +1447,10 @@ testcase_15_wait_online_dns() { journalctl --sync # Check that a disconnect happened, and was handled. - journalctl -b -u "$unit" --grep="DNS configuration monitor disconnected, reconnecting..." >/dev/null + journalctl --after-cursor="$(cat "$cursor_file")" SYSLOG_IDENTIFIER=systemd-networkd-wait-online --grep="DNS configuration monitor disconnected, reconnecting..." >/dev/null # Check that dns0 was found to be online. - journalctl -b -u "$unit" --grep="dns0: link is configured by networkd and online." >/dev/null + journalctl --after-cursor="$(cat "$cursor_file")" SYSLOG_IDENTIFIER=systemd-networkd-wait-online --grep="dns0: link is configured by networkd and online." >/dev/null } testcase_delegate() { @@ -1487,6 +1498,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 +if ! env --block-signal=SIGUSR1 true 2>/dev/null; then echo "env tool too old, can't block signals, skipping test." >&2 echo OK >/testok exit 0 diff --git a/test/units/TEST-79-MEMPRESS.sh b/test/units/TEST-79-MEMPRESS.sh deleted file mode 100755 index 065916096682e..0000000000000 --- a/test/units/TEST-79-MEMPRESS.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/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-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-80-NOTIFYACCESS.sh b/test/units/TEST-80-NOTIFYACCESS.sh index 0f7fec17ae878..4439e46362933 100755 --- a/test/units/TEST-80-NOTIFYACCESS.sh +++ b/test/units/TEST-80-NOTIFYACCESS.sh @@ -10,7 +10,7 @@ set -o pipefail mkfifo /tmp/syncfifo1 /tmp/syncfifo2 sync_in() { - read -r x < /tmp/syncfifo1 + read -r x > "$MYSCRIPT" <<'EOF' +cat >>"$MYSCRIPT" <<'EOF' #!/usr/bin/env bash set -eux set -o pipefail test "$FDSTORE" -eq 7 N="/tmp/$RANDOM" -echo $RANDOM > "$N" -systemd-notify --fd=4 --fdname=quux --pid=parent 4< "$N" +echo $RANDOM >"$N" +systemd-notify --fd=4 --fdname=quux --pid=parent 4<"$N" rm "$N" systemd-notify --ready exec sleep infinity diff --git a/test/units/TEST-82-SOFTREBOOT.sh b/test/units/TEST-82-SOFTREBOOT.sh index 03f6b78e57fb1..778bdbd7f8dc4 100755 --- a/test/units/TEST-82-SOFTREBOOT.sh +++ b/test/units/TEST-82-SOFTREBOOT.sh @@ -190,6 +190,17 @@ elif [ -f /run/TEST-82-SOFTREBOOT.touch ]; then test "$(systemctl show -P ActiveState TEST-82-SOFTREBOOT-nosurvive-sigterm.service)" != "active" test "$(systemctl show -P ActiveState TEST-82-SOFTREBOOT-nosurvive.service)" != "active" + # Regression test for #41789: the lingering user enabled on the first boot must + # have its user@.service started again after the soft reboot. Before the fix it + # was GC'd at logind startup and never restarted. Disable lingering again here, + # before the nextroot switch below (the third boot runs on a minimal overlay + # rootfs that does not have this user). terminate-user is best-effort: it can + # race the asynchronous disable-linger/UserStopDelaySec teardown. + linger_uid=$(id -u testuser) + timeout 30 bash -c "until systemctl is-active --quiet user@${linger_uid}.service; do sleep 1; done" + loginctl disable-linger testuser + loginctl terminate-user testuser || true + # This time we test the /run/nextroot/ root switching logic. (We synthesize a new rootfs from the old via overlayfs) mkdir -p /run/nextroot /tmp/nextroot-lower /original-root mount -t tmpfs tmpfs /tmp/nextroot-lower @@ -313,6 +324,18 @@ EOF systemd-run --wait false || true done + # Regression test for #41789: a lingering user's user@.service must come back + # after a soft reboot, the same way it does after a hardware reboot. It used + # to be garbage-collected at logind startup (before user_start() ran), because + # /run/systemd/users is preserved across soft-reboot and fed the user into the + # GC queue while its units were not active yet. Enable lingering for the + # pre-existing testuser here; the second boot asserts the service is active + # again. testuser lives in the persistent rootfs, so it survives the + # (non-nextroot) soft reboot into the second boot. + loginctl enable-linger testuser + linger_uid=$(id -u testuser) + timeout 30 bash -c "until systemctl is-active --quiet user@${linger_uid}.service; do sleep 1; done" + trigger_uevent # Now issue the soft reboot. We should be right back soon. diff --git a/test/units/TEST-83-BTRFS.sh b/test/units/TEST-83-BTRFS.sh deleted file mode 100755 index 4f10ae74682db..0000000000000 --- a/test/units/TEST-83-BTRFS.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: LGPL-2.1-or-later -set -eux -set -o pipefail - -FSTYPE="$(stat --file-system --format "%T" /)" - -if [[ "$FSTYPE" != "btrfs" ]]; then - echo "Root filesystem is $FSTYPE instead of btrfs, skipping" - exit 77 -fi - -TEST_BTRFS_OFFSET=/usr/lib/systemd/tests/unit-tests/manual/test-btrfs-physical-offset - -SWAPFILE=/var/tmp/swapfile - -btrfs filesystem mkswapfile -s 10m "$SWAPFILE" -sync -f "$SWAPFILE" - -offset_btrfs_progs="$(btrfs inspect-internal map-swapfile -r "$SWAPFILE")" -echo "btrfs-progs: $offset_btrfs_progs" - -offset_btrfs_util="$("$TEST_BTRFS_OFFSET" "$SWAPFILE")" -echo "btrfs-util: $offset_btrfs_util" - -(( offset_btrfs_progs == offset_btrfs_util )) - -rm -f "$SWAPFILE" - -/usr/lib/systemd/tests/unit-tests/manual/test-btrfs - -touch /testok diff --git a/test/units/TEST-86-MULTI-PROFILE-UKI.sh b/test/units/TEST-86-MULTI-PROFILE-UKI.sh index 5518cb656149f..72fb7b6f554af 100755 --- a/test/units/TEST-86-MULTI-PROFILE-UKI.sh +++ b/test/units/TEST-86-MULTI-PROFILE-UKI.sh @@ -64,6 +64,9 @@ elif [[ "$ID" == "profile1" ]]; then elif [[ "$ID" == "profile2" ]]; then grep testprofile2=1 /proc/cmdline rm /root/encrypted.raw + # Reset the default boot entry so a subsequent re-run of the test does not + # boot straight back into @profile2 (where encrypted.raw is now gone) and fail. + bootctl set-default "" else exit 1 fi diff --git a/test/units/TEST-87-AUX-UTILS-VM.bind-volume.sh b/test/units/TEST-87-AUX-UTILS-VM.bind-volume.sh new file mode 100755 index 0000000000000..6339e390936bb --- /dev/null +++ b/test/units/TEST-87-AUX-UTILS-VM.bind-volume.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# Test --bind-volume / machinectl bind-volume / unbind-volume integration with the +# StorageProvider Varlink interface. +# +# Exercises: +# - --bind-volume parser + runtime_directory_generic + Acquire round-trip +# - boot-time attach via DriveInfo (non-removable) +# - runtime hotplug via io.systemd.MachineInstance.AddStorage (removable) +# - runtime hot-remove via io.systemd.MachineInstance.RemoveStorage +# - StorageImmutable rejection for boot-time attached volumes +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 ! command -v storagectl >/dev/null 2>&1; then + echo "storagectl 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 + +# Storage providers are socket-activated; skip if the fs provider socket isn't present. +if ! test -S /run/systemd/io.systemd.StorageProvider/fs; then + echo "StorageProvider fs socket 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 + +WORKDIR="$(mktemp -d /tmp/test-bind-volume.XXXXXXXXXX)" + +at_exit() { + set +e + if [[ -n "${MACHINE:-}" ]]; then + if machinectl status "$MACHINE" &>/dev/null; then + machinectl terminate "$MACHINE" 2>/dev/null + timeout 10 bash -c "while machinectl status '$MACHINE' &>/dev/null; do sleep .5; done" 2>/dev/null + fi + fi + [[ -n "${VMSPAWN_PID:-}" ]] && { kill "$VMSPAWN_PID" 2>/dev/null; wait "$VMSPAWN_PID" 2>/dev/null; } + rm -rf "$WORKDIR" + rm -f /var/lib/storage/test-bind-volume-*.volume +} +trap at_exit EXIT + +# Build a minimal root for direct boot — guest just sleeps. +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" + +BOOT_VOL="test-bind-volume-boot-$$" +RUNTIME_VOL="test-bind-volume-runtime-$$" + +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 77 + fi + sleep .5 + done + " || { + local rc=$? + if [[ $rc -eq 77 ]]; then exit 0; fi + exit "$rc" + } +} + +# --- Boot the VM with one boot-time bind-volume --- +MACHINE="test-bind-volume-$$" +systemd-vmspawn \ + --machine="$MACHINE" \ + --ram=256M \ + --image="$WORKDIR/root.raw" \ + --bind-volume="fs:${BOOT_VOL}::create=new,size=64M,template=sparse-file" \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + root=/dev/vda rw \ + &>"$WORKDIR/vmspawn.log" & +VMSPAWN_PID=$! + +wait_for_machine "$MACHINE" "$VMSPAWN_PID" "$WORKDIR/vmspawn.log" +echo "Machine '$MACHINE' registered" + +VARLINK_ADDR=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.List "{\"name\":\"$MACHINE\"}" | jq -r '.controlAddress') +assert_neq "$VARLINK_ADDR" "null" + +varlinkctl call "$VARLINK_ADDR" io.systemd.MachineInstance.Describe '{}' \ + | jq -e '.running == true' >/dev/null +echo "VM running with boot-time bind-volume attached" + +# --- Hot-add a second volume via machinectl bind-volume (must succeed) --- +machinectl bind-volume "$MACHINE" \ + "fs:${RUNTIME_VOL}:virtio-scsi:create=new,size=32M,template=sparse-file" +echo "Hot-added runtime bind-volume succeeded" + +# --- Hot-remove the runtime-added volume (must succeed) --- +machinectl unbind-volume "$MACHINE" "fs:${RUNTIME_VOL}" +echo "Hot-removed runtime bind-volume succeeded" + +# --- Removing the boot-time volume must fail with StorageImmutable --- +if machinectl unbind-volume "$MACHINE" "fs:${BOOT_VOL}" 2>"$WORKDIR/unbind.err"; then + echo "ERROR: unbind-volume of boot-time volume should have failed" + cat "$WORKDIR/unbind.err" + exit 1 +fi +grep StorageImmutable "$WORKDIR/unbind.err" >/dev/null +echo "Boot-time bind-volume correctly rejected with StorageImmutable" + +# --- Removing a non-existent name must fail with NoSuchStorage --- +if machinectl unbind-volume "$MACHINE" "fs:no-such-volume-$$" 2>"$WORKDIR/unbind-noexist.err"; then + echo "ERROR: unbind-volume of non-existent name should have failed" + cat "$WORKDIR/unbind-noexist.err" + exit 1 +fi +grep NoSuchStorage "$WORKDIR/unbind-noexist.err" >/dev/null +echo "Non-existent unbind-volume correctly rejected with NoSuchStorage" + +machinectl terminate "$MACHINE" +timeout 10 bash -c "while machinectl status '$MACHINE' &>/dev/null; do sleep .5; done" +timeout 10 bash -c "while kill -0 '$VMSPAWN_PID' 2>/dev/null; do sleep .5; done" +echo "All bind-volume tests passed" diff --git a/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh b/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh index 7d26541ffa6f4..0b19acd0173cc 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.bootctl.sh @@ -101,11 +101,60 @@ 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 } +cleanup_file_version() { + if [[ -n "${FILE_VERSION_GARBAGE:-}" ]]; then + rm -f "$FILE_VERSION_GARBAGE" + unset FILE_VERSION_GARBAGE + fi + restore_esp +} + +testcase_bootctl_file_version() { + # Exercise get_file_version() (src/bootctl/bootctl-util.c), which reads the + # version marker that systemd-boot/-stub store in their ".sdmagic" PE + # section, e.g. "#### LoaderInfo: systemd-boot 257 ####". 'bootctl status' + # surfaces the inner part next to each .efi file it finds in the ESP, e.g. + # ".../systemd-bootx64.efi (systemd-boot 257)". + + backup_esp + trap cleanup_file_version RETURN ERR + + bootctl install + + local ESP + ESP="$(bootctl --print-esp-path)" + + # The freshly installed systemd-boot binary must be listed with the version + # extracted from its ".sdmagic" section. + bootctl status | grep -E "systemd-boot[a-z0-9]*\.efi \(systemd-boot [^)]+\)" >/dev/null + + # A non-PE/non-systemd file must be handled gracefully (get_file_version() + # returns -ESRCH): it is still listed, but without a version annotation, and + # 'bootctl status' must not fail. + FILE_VERSION_GARBAGE="$ESP/EFI/systemd/not-a-loader.efi" + echo "this is not a PE binary" >"$FILE_VERSION_GARBAGE" + + local status + status="$(bootctl status)" + grep -F "not-a-loader.efi" <<<"$status" >/dev/null + (! grep -E "not-a-loader\.efi .*\(" <<<"$status" >/dev/null) + + rm -f "$FILE_VERSION_GARBAGE" + unset FILE_VERSION_GARBAGE +} + cleanup_image() ( set +e @@ -367,7 +416,7 @@ testcase_00_secureboot() { bootctl status | grep "Secure Boot: enabled" >/dev/null # Ensure the addon is fully loaded and parsed - bootctl status | grep "global-addon: loader/addons/test.addon.efi" >/dev/null + bootctl status | grep "extra: /boot//loader/addons/test.addon.efi" >/dev/null bootctl status | grep "cmdline" | grep addonfoobar >/dev/null grep -q addonfoobar /proc/cmdline } @@ -390,4 +439,433 @@ testcase_install_varlink() { bootctl is-installed } +cleanup_link() { + if [[ -n "${LINK_WORKDIR:-}" ]]; then + rm -rf "$LINK_WORKDIR" + unset LINK_WORKDIR + fi + restore_esp +} + +testcase_bootctl_link() { + if ! command -v ukify >/dev/null; then + echo "ukify not found, skipping." + return 0 + fi + + backup_esp + LINK_WORKDIR="$(mktemp --directory /tmp/test-bootctl-link.XXXXXXXXXX)" + trap cleanup_link RETURN ERR + + # Ensure loader/entries directory is present + bootctl install --make-entry-directory=yes + + local ESP + ESP="$(bootctl --print-esp-path)" + + # Build a minimal UKI via ukify. The .linux content does not need to be a + # real kernel — bootctl link only requires a valid PE with .osrel (and the + # systemd-stub SBAT marker that pe_is_uki() checks for). + cat >"$LINK_WORKDIR/os-release" <<'EOF' +ID=testos +NAME="Test OS" +PRETTY_NAME="Test OS" +EOF + echo "fake-kernel" >"$LINK_WORKDIR/vmlinuz" + echo "fake-initrd" >"$LINK_WORKDIR/initrd" + echo "fake-sysext-data" >"$LINK_WORKDIR/hello.sysext.raw" + echo "fake-confext-data" >"$LINK_WORKDIR/hello.confext.raw" + echo "fake-credential" >"$LINK_WORKDIR/hello.cred" + + ukify build \ + --linux "$LINK_WORKDIR/vmlinuz" \ + --initrd "$LINK_WORKDIR/initrd" \ + --os-release "@$LINK_WORKDIR/os-release" \ + --uname "1.2.3-testkernel" \ + --cmdline "quiet" \ + --output "$LINK_WORKDIR/testuki.efi" + + # Pin an explicit entry token so the resulting filenames are deterministic + local TOKEN="systemdtest" + local BOOTCTL=(bootctl "--entry-token=literal:$TOKEN") + + # --- Test 1: basic link/unlink --- + "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" + + # Exactly one entry file should exist, named "${TOKEN}-commit_1.conf" + local ENTRY="$ESP/loader/entries/${TOKEN}-commit_1.conf" + test -f "$ENTRY" + test -f "$ESP/$TOKEN/testuki.efi" + + # Verify the entry file contents + grep "^title " "$ENTRY" >/dev/null + grep "^uki /${TOKEN}/testuki.efi\$" "$ENTRY" >/dev/null + grep "^version 1\$" "$ENTRY" >/dev/null + + # Make sure bootctl list sees it + bootctl list --json=short | grep -F "${TOKEN}-commit_1.conf" >/dev/null + + # Remove it again using the ID (entry IDs include the .conf suffix) + "${BOOTCTL[@]}" unlink "${TOKEN}-commit_1.conf" + test ! -e "$ENTRY" + test ! -e "$ESP/$TOKEN/testuki.efi" + + # --- Test 2: link with --entry-title/--entry-version/--entry-commit/--tries-left --- + "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" \ + --entry-title="My Funky Entry" \ + --entry-version="9.8.7" \ + --entry-commit=42 \ + --tries-left=3 + + ENTRY="$ESP/loader/entries/${TOKEN}-commit_42.9.8.7+3.conf" + test -f "$ENTRY" + test -f "$ESP/$TOKEN/testuki.efi" + + grep "^title My Funky Entry\$" "$ENTRY" >/dev/null + grep "^version 42.9.8.7\$" "$ENTRY" >/dev/null + grep "^uki /${TOKEN}/testuki.efi\$" "$ENTRY" >/dev/null + + # Unlink using the ID (the tries counter "+3" is stripped from the canonical ID) + "${BOOTCTL[@]}" unlink "${TOKEN}-commit_42.9.8.7.conf" + test ! -e "$ENTRY" + test ! -e "$ESP/$TOKEN/testuki.efi" + + # --- Test 3: link with extras (-X and --extra=) --- + "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" \ + --entry-commit=50 \ + -X "$LINK_WORKDIR/hello.sysext.raw" \ + --extra="$LINK_WORKDIR/hello.confext.raw" \ + -X "$LINK_WORKDIR/hello.cred" + + ENTRY="$ESP/loader/entries/${TOKEN}-commit_50.conf" + test -f "$ENTRY" + test -f "$ESP/$TOKEN/testuki.efi" + test -f "$ESP/$TOKEN/hello.sysext.raw" + test -f "$ESP/$TOKEN/hello.confext.raw" + test -f "$ESP/$TOKEN/hello.cred" + + grep "^extra /${TOKEN}/hello.sysext.raw\$" "$ENTRY" >/dev/null + grep "^extra /${TOKEN}/hello.confext.raw\$" "$ENTRY" >/dev/null + grep "^extra /${TOKEN}/hello.cred\$" "$ENTRY" >/dev/null + + # Unlink must also clean up the extra resources + "${BOOTCTL[@]}" unlink "${TOKEN}-commit_50.conf" + test ! -e "$ENTRY" + test ! -e "$ESP/$TOKEN/testuki.efi" + test ! -e "$ESP/$TOKEN/hello.sysext.raw" + test ! -e "$ESP/$TOKEN/hello.confext.raw" + test ! -e "$ESP/$TOKEN/hello.cred" + + # --- Test 4: --oldest drops the lowest commit first --- + "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" --entry-commit=10 + "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" --entry-commit=20 + "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" --entry-commit=30 + + test -f "$ESP/loader/entries/${TOKEN}-commit_10.conf" + test -f "$ESP/loader/entries/${TOKEN}-commit_20.conf" + test -f "$ESP/loader/entries/${TOKEN}-commit_30.conf" + test -f "$ESP/$TOKEN/testuki.efi" + + "${BOOTCTL[@]}" unlink --oldest=yes + test ! -e "$ESP/loader/entries/${TOKEN}-commit_10.conf" + test -f "$ESP/loader/entries/${TOKEN}-commit_20.conf" + test -f "$ESP/loader/entries/${TOKEN}-commit_30.conf" + test -f "$ESP/$TOKEN/testuki.efi" + + "${BOOTCTL[@]}" unlink --oldest=yes + test ! -e "$ESP/loader/entries/${TOKEN}-commit_20.conf" + test -f "$ESP/loader/entries/${TOKEN}-commit_30.conf" + test -f "$ESP/$TOKEN/testuki.efi" + + # --- Test 5: --dry-run leaves everything in place --- + "${BOOTCTL[@]}" --dry-run unlink "${TOKEN}-commit_30.conf" + test -f "$ESP/loader/entries/${TOKEN}-commit_30.conf" + test -f "$ESP/$TOKEN/testuki.efi" + + # Actually remove it now + "${BOOTCTL[@]}" unlink "${TOKEN}-commit_30.conf" + test ! -e "$ESP/loader/entries/${TOKEN}-commit_30.conf" + test ! -e "$ESP/$TOKEN/testuki.efi" + + # --- Test 6: invalid combinations are rejected --- + # Neither an ID nor --oldest + (! "${BOOTCTL[@]}" unlink) + # Both an ID and --oldest + (! "${BOOTCTL[@]}" unlink --oldest=yes "${TOKEN}-commit_1.conf") + + # --- Test 7: refusing to link when --keep-free cannot be satisfied --- + (! "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" --entry-commit=99 --keep-free=1T) + test ! -e "$ESP/loader/entries/${TOKEN}-commit_99.conf" + + # --- Test 8: refusing to re-link the same commit number --- + "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" --entry-commit=77 + (! "${BOOTCTL[@]}" link "$LINK_WORKDIR/testuki.efi" --entry-commit=77) + "${BOOTCTL[@]}" unlink "${TOKEN}-commit_77.conf" + + # --- Test 9: passing a non-UKI is rejected --- + (! "${BOOTCTL[@]}" link "$LINK_WORKDIR/vmlinuz") + + # === Varlink coverage === + # + # Exercise io.systemd.BootControl.Link/Unlink by forking bootctl as a + # varlink server via 'varlinkctl call '. Note the Varlink schema + # has no way to supply a literal entry token (unlike --entry-token= on + # the command line), so the token is chosen by bootctl from + # machine-id/os-release — we recover it from the returned id. + local BOOTCTL_BIN vreply vid vtoken + BOOTCTL_BIN="$(type -p bootctl)" + + # --- Test 10: Link + Unlink via varlink --- + vreply="$(varlinkctl call --json=short \ + --push-fd="$LINK_WORKDIR/testuki.efi" \ + "$BOOTCTL_BIN" io.systemd.BootControl.Link \ + '{"kernelFilename":"vluki.efi","kernelFileDescriptor":0}')" + vid="$(echo "$vreply" | jq -r '.ids[0]')" + test -n "$vid" + test "$vid" != "null" + vtoken="${vid%%-commit_*}" + test -n "$vtoken" + + test -f "$ESP/loader/entries/$vid" + test -f "$ESP/$vtoken/vluki.efi" + grep "^uki /$vtoken/vluki.efi\$" "$ESP/loader/entries/$vid" >/dev/null + + varlinkctl call --quiet "$BOOTCTL_BIN" io.systemd.BootControl.Unlink \ + "{\"id\":\"$vid\"}" + test ! -e "$ESP/loader/entries/$vid" + test ! -e "$ESP/$vtoken/vluki.efi" + + # --- Test 11: Link with entryTitle/entryVersion/entryCommit/triesLeft + extraFiles via varlink --- + vreply="$(varlinkctl call --json=short \ + --push-fd="$LINK_WORKDIR/testuki.efi" \ + --push-fd="$LINK_WORKDIR/hello.sysext.raw" \ + --push-fd="$LINK_WORKDIR/hello.cred" \ + "$BOOTCTL_BIN" io.systemd.BootControl.Link \ + '{"kernelFilename":"vluki2.efi","kernelFileDescriptor":0,"entryTitle":"Varlink Title","entryVersion":"2.3.4","entryCommit":111,"triesLeft":2,"extraFiles":[{"filename":"hello.sysext.raw","fileDescriptor":1},{"filename":"hello.cred","fileDescriptor":2}]}')" + vid="$(echo "$vreply" | jq -r '.ids[0]')" + # The returned id has the tries counter ("+2") stripped + assert_eq "$vid" "$vtoken-commit_111.2.3.4.conf" + # The on-disk entry filename includes the tries counter + local VENTRY="$ESP/loader/entries/$vtoken-commit_111.2.3.4+2.conf" + test -f "$VENTRY" + test -f "$ESP/$vtoken/vluki2.efi" + test -f "$ESP/$vtoken/hello.sysext.raw" + test -f "$ESP/$vtoken/hello.cred" + + grep "^title Varlink Title\$" "$VENTRY" >/dev/null + grep "^version 111.2.3.4\$" "$VENTRY" >/dev/null + grep "^extra /$vtoken/hello.sysext.raw\$" "$VENTRY" >/dev/null + grep "^extra /$vtoken/hello.cred\$" "$VENTRY" >/dev/null + + varlinkctl call --quiet "$BOOTCTL_BIN" io.systemd.BootControl.Unlink \ + "{\"id\":\"$vid\"}" + test ! -e "$VENTRY" + test ! -e "$ESP/$vtoken/vluki2.efi" + test ! -e "$ESP/$vtoken/hello.sysext.raw" + test ! -e "$ESP/$vtoken/hello.cred" + + # --- Test 12: Unlink oldest via varlink --- + local c + for c in 210 220 230; do + varlinkctl call --quiet \ + --push-fd="$LINK_WORKDIR/testuki.efi" \ + "$BOOTCTL_BIN" io.systemd.BootControl.Link \ + "{\"kernelFilename\":\"vluki3.efi\",\"kernelFileDescriptor\":0,\"entryCommit\":$c}" + done + test -f "$ESP/loader/entries/$vtoken-commit_210.conf" + test -f "$ESP/loader/entries/$vtoken-commit_220.conf" + test -f "$ESP/loader/entries/$vtoken-commit_230.conf" + + varlinkctl call --quiet "$BOOTCTL_BIN" io.systemd.BootControl.Unlink \ + '{"oldest":true}' + test ! -e "$ESP/loader/entries/$vtoken-commit_210.conf" + test -f "$ESP/loader/entries/$vtoken-commit_220.conf" + test -f "$ESP/loader/entries/$vtoken-commit_230.conf" + test -f "$ESP/$vtoken/vluki3.efi" + + # Clean up remaining entries + varlinkctl call --quiet "$BOOTCTL_BIN" io.systemd.BootControl.Unlink \ + "{\"id\":\"$vtoken-commit_220.conf\"}" + varlinkctl call --quiet "$BOOTCTL_BIN" io.systemd.BootControl.Unlink \ + "{\"id\":\"$vtoken-commit_230.conf\"}" + test ! -e "$ESP/loader/entries/$vtoken-commit_220.conf" + test ! -e "$ESP/loader/entries/$vtoken-commit_230.conf" + test ! -e "$ESP/$vtoken/vluki3.efi" + + # --- Test 13: Link with a non-UKI via varlink returns InvalidKernelImage --- + varlinkctl call --quiet \ + --push-fd="$LINK_WORKDIR/vmlinuz" \ + --graceful=io.systemd.BootControl.InvalidKernelImage \ + "$BOOTCTL_BIN" io.systemd.BootControl.Link \ + '{"kernelFilename":"notauki.efi","kernelFileDescriptor":0}' + + # --- Test 14: Unlink with invalid argument combinations is rejected --- + # Both id and oldest=true + (! varlinkctl call "$BOOTCTL_BIN" io.systemd.BootControl.Unlink \ + '{"id":"foo.conf","oldest":true}') + # Neither id nor oldest + (! varlinkctl call "$BOOTCTL_BIN" io.systemd.BootControl.Unlink '{}') + # Invalid id characters (e.g. a glob) + (! varlinkctl call "$BOOTCTL_BIN" io.systemd.BootControl.Unlink \ + '{"id":"foo*.conf"}') +} + +cleanup_link_auto() { + rm -rf /run/systemd/uki /etc/systemd/uki + if [[ -n "${LINK_WORKDIR:-}" ]]; then + rm -rf "$LINK_WORKDIR" + unset LINK_WORKDIR + fi + restore_esp +} + +testcase_bootctl_link_auto() { + if ! command -v ukify >/dev/null; then + echo "ukify not found, skipping." + return 0 + fi + + backup_esp + LINK_WORKDIR="$(mktemp --directory /tmp/test-bootctl-link-auto.XXXXXXXXXX)" + trap cleanup_link_auto RETURN ERR + + # Ensure loader/entries directory is present + bootctl install --make-entry-directory=yes + + local ESP + ESP="$(bootctl --print-esp-path)" + + cat >"$LINK_WORKDIR/os-release" <<'EOF' +ID=testos +NAME="Test OS" +PRETTY_NAME="Test OS" +EOF + echo "fake-kernel" >"$LINK_WORKDIR/vmlinuz" + echo "fake-initrd" >"$LINK_WORKDIR/initrd" + + # Two distinct UKIs, so we can tell which one was picked up. + ukify build \ + --linux "$LINK_WORKDIR/vmlinuz" \ + --initrd "$LINK_WORKDIR/initrd" \ + --os-release "@$LINK_WORKDIR/os-release" \ + --uname "1.2.3-testkernel" \ + --cmdline "quiet uki=a" \ + --output "$LINK_WORKDIR/uki_a.efi" + ukify build \ + --linux "$LINK_WORKDIR/vmlinuz" \ + --initrd "$LINK_WORKDIR/initrd" \ + --os-release "@$LINK_WORKDIR/os-release" \ + --uname "1.2.3-testkernel" \ + --cmdline "quiet uki=b" \ + --output "$LINK_WORKDIR/uki_b.efi" + + local TOKEN="systemdtest" + local BOOTCTL=(bootctl "--entry-token=literal:$TOKEN") + local ENTRY="$ESP/loader/entries/${TOKEN}-commit_1.conf" + + # --- Test 1: link-auto picks up kernel.efi + extras.d/ from /run/systemd/uki/ --- + rm -rf /run/systemd/uki + mkdir -p /run/systemd/uki/extras.d + cp "$LINK_WORKDIR/uki_a.efi" /run/systemd/uki/kernel.efi + echo "sysext-data" >/run/systemd/uki/extras.d/hello.sysext.raw + echo "confext-data" >/run/systemd/uki/extras.d/hello.confext.raw + echo "cred-data" >/run/systemd/uki/extras.d/hello.cred + + "${BOOTCTL[@]}" link-auto + + test -f "$ENTRY" + test -f "$ESP/$TOKEN/kernel.efi" + cmp "$LINK_WORKDIR/uki_a.efi" "$ESP/$TOKEN/kernel.efi" + test -f "$ESP/$TOKEN/hello.sysext.raw" + test -f "$ESP/$TOKEN/hello.confext.raw" + test -f "$ESP/$TOKEN/hello.cred" + grep "^uki /${TOKEN}/kernel.efi\$" "$ENTRY" >/dev/null + grep "^extra /${TOKEN}/hello.sysext.raw\$" "$ENTRY" >/dev/null + grep "^extra /${TOKEN}/hello.confext.raw\$" "$ENTRY" >/dev/null + grep "^extra /${TOKEN}/hello.cred\$" "$ENTRY" >/dev/null + + "${BOOTCTL[@]}" unlink "${TOKEN}-commit_1.conf" + test ! -e "$ENTRY" + test ! -e "$ESP/$TOKEN/kernel.efi" + + # --- Test 2: versioned kernel.efi.v/ and extras .v/ are resolved via vpick --- + rm -rf /run/systemd/uki + mkdir -p /run/systemd/uki/kernel.efi.v /run/systemd/uki/extras.d/hello.sysext.raw.v + cp "$LINK_WORKDIR/uki_a.efi" /run/systemd/uki/kernel.efi.v/kernel_1.0.efi + cp "$LINK_WORKDIR/uki_a.efi" /run/systemd/uki/kernel.efi.v/kernel_2.0.efi + echo "sysext-1" >/run/systemd/uki/extras.d/hello.sysext.raw.v/hello_1.0.sysext.raw + echo "sysext-2" >/run/systemd/uki/extras.d/hello.sysext.raw.v/hello_2.0.sysext.raw + + "${BOOTCTL[@]}" link-auto + + test -f "$ENTRY" + # vpick must select the newest version + test -f "$ESP/$TOKEN/kernel_2.0.efi" + test ! -e "$ESP/$TOKEN/kernel_1.0.efi" + test -f "$ESP/$TOKEN/hello_2.0.sysext.raw" + test ! -e "$ESP/$TOKEN/hello_1.0.sysext.raw" + grep "^uki /${TOKEN}/kernel_2.0.efi\$" "$ENTRY" >/dev/null + grep "^extra /${TOKEN}/hello_2.0.sysext.raw\$" "$ENTRY" >/dev/null + + "${BOOTCTL[@]}" unlink "${TOKEN}-commit_1.conf" + + # --- Test 3: priority — /etc/systemd/uki/ wins over /run/systemd/uki/ --- + rm -rf /run/systemd/uki /etc/systemd/uki + mkdir -p /run/systemd/uki/extras.d /etc/systemd/uki/extras.d + cp "$LINK_WORKDIR/uki_b.efi" /run/systemd/uki/kernel.efi + cp "$LINK_WORKDIR/uki_a.efi" /etc/systemd/uki/kernel.efi + echo "run-cred" >/run/systemd/uki/extras.d/hello.cred + echo "etc-cred" >/etc/systemd/uki/extras.d/hello.cred + + "${BOOTCTL[@]}" link-auto + + test -f "$ESP/$TOKEN/kernel.efi" + # The /etc copy (uki_a) must win over the /run copy (uki_b) + cmp "$LINK_WORKDIR/uki_a.efi" "$ESP/$TOKEN/kernel.efi" + cmp <(echo "etc-cred") "$ESP/$TOKEN/hello.cred" + + "${BOOTCTL[@]}" unlink "${TOKEN}-commit_1.conf" + rm -rf /etc/systemd/uki + + # --- Test 4: with nothing staged, link-auto is a successful no-op --- + rm -rf /run/systemd/uki + "${BOOTCTL[@]}" link-auto + # No entries referencing our token should remain. + local leftover + leftover="$(find "$ESP/loader/entries/" -name "*$TOKEN*" -print -quit 2>/dev/null)" + test -z "$leftover" + + # === Varlink coverage: io.systemd.BootControl.LinkAuto === + local BOOTCTL_BIN vreply vid vtoken + BOOTCTL_BIN="$(type -p bootctl)" + + # --- Test 5: LinkAuto discovers kernel.efi + extras --- + rm -rf /run/systemd/uki + mkdir -p /run/systemd/uki/extras.d + cp "$LINK_WORKDIR/uki_a.efi" /run/systemd/uki/kernel.efi + echo "cred-data" >/run/systemd/uki/extras.d/hello.cred + + vreply="$(varlinkctl call --json=short "$BOOTCTL_BIN" io.systemd.BootControl.LinkAuto '{}')" + vid="$(echo "$vreply" | jq -r '.ids[0]')" + test -n "$vid" + test "$vid" != "null" + vtoken="${vid%%-commit_*}" + test -n "$vtoken" + + test -f "$ESP/loader/entries/$vid" + test -f "$ESP/$vtoken/kernel.efi" + test -f "$ESP/$vtoken/hello.cred" + grep "^uki /$vtoken/kernel.efi\$" "$ESP/loader/entries/$vid" >/dev/null + + varlinkctl call --quiet "$BOOTCTL_BIN" io.systemd.BootControl.Unlink "{\"id\":\"$vid\"}" + test ! -e "$ESP/loader/entries/$vid" + test ! -e "$ESP/$vtoken/kernel.efi" + test ! -e "$ESP/$vtoken/hello.cred" + + # --- Test 6: LinkAuto with nothing staged returns an empty id list --- + rm -rf /run/systemd/uki + vreply="$(varlinkctl call --json=short "$BOOTCTL_BIN" io.systemd.BootControl.LinkAuto '{}')" + assert_eq "$(echo "$vreply" | jq -r '.ids | length')" "0" +} + run_testcases diff --git a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh index b3f702a2b0d7f..ae7b38ab029be 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.coredump.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.coredump.sh @@ -84,7 +84,7 @@ if cgroupfs_supports_user_xattrs; then mkdir -p "/run/systemd/system/systemd-nspawn@$CONTAINER.service.d" # Bind-mounting /etc into the container kinda defeats the purpose of --volatile=, # but we need the ASan-related overrides scattered across /etc - cat > "/run/systemd/system/systemd-nspawn@$CONTAINER.service.d/override.conf" <"/run/systemd/system/systemd-nspawn@$CONTAINER.service.d/override.conf" < 0' +coredumpctl info --json=short "$CORE_TEST_BIN" | jq -se 'all(.[]; .PID > 0)' +coredumpctl info --json=short "$CORE_TEST_BIN" | jq -se 'all(.[]; .Signal > 0)' +coredumpctl info --json=short "$CORE_TEST_BIN" | jq -se 'all(.[]; has("Executable"))' +coredumpctl info --json=short "$CORE_TEST_BIN" | jq -se 'all(.[]; has("Command"))' +coredumpctl info --json=short "$CORE_TEST_BIN" | jq -se 'all(.[]; has("Storage"))' + +# 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 + +# If COREDUMP_CODE= is present, check that the expected code is SI_USER (0). +if coredumpctl -F COREDUMP_CODE | grep "^0$" >/dev/null; then + coredumpctl info "$CORE_TEST_BIN" | grep --fixed-strings "Signal: 5 (TRAP) si_code: SI_USER" >/dev/null + coredumpctl info --json=short "$CORE_TEST_BIN" | jq -se 'any(.[]; .SignalCode == 0)' +fi coredumpctl debug --debugger=/bin/true "$CORE_TEST_BIN" SYSTEMD_DEBUGGER=/bin/true coredumpctl debug "$CORE_TEST_BIN" @@ -187,6 +209,8 @@ coredumpctl info "${CORE_TEST_UNPRIV_BIN##*/}" "${UNPRIV_CMD[@]}" coredumpctl "${UNPRIV_CMD[@]}" coredumpctl info "$CORE_TEST_UNPRIV_BIN" "${UNPRIV_CMD[@]}" coredumpctl info "${CORE_TEST_UNPRIV_BIN##*/}" +# Verify JSON output for unprivileged coredumps +"${UNPRIV_CMD[@]}" coredumpctl info --json=short "$CORE_TEST_UNPRIV_BIN" | jq (! "${UNPRIV_CMD[@]}" coredumpctl info --all "$CORE_TEST_BIN") (! "${UNPRIV_CMD[@]}" coredumpctl info --all "${CORE_TEST_BIN##*/}") # We should have a couple of externally stored coredumps diff --git a/test/units/TEST-87-AUX-UTILS-VM.pstore.sh b/test/units/TEST-87-AUX-UTILS-VM.pstore.sh index be5297fa52da9..d05c99b15076c 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.pstore.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.pstore.sh @@ -44,7 +44,7 @@ random_efi_timestamp() { printf "%0.10d" "$((1000000000 + RANDOM))"; } # The dmesg- filename contains the backend-type and the Common Platform Error Record, CPER, # record id, a 64-bit number. # -# Files are processed in reverse lexigraphical order so as to properly reconstruct original dmesg. +# Files are processed in reverse lexicographical order so as to properly reconstruct original dmesg. prepare_efi_logs() { local file="${1:?}" diff --git a/test/units/TEST-87-AUX-UTILS-VM.replace-storage.sh b/test/units/TEST-87-AUX-UTILS-VM.replace-storage.sh new file mode 100755 index 0000000000000..22ff57adf8338 --- /dev/null +++ b/test/units/TEST-87-AUX-UTILS-VM.replace-storage.sh @@ -0,0 +1,204 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# Test io.systemd.MachineInstance.ReplaceStorage — runtime hot-swap of an +# attached storage volume's backing file via QMP blockdev-reopen. +# +# Exercises: +# - happy-path replace of a runtime-attached drive +# - successive replaces (file_generation rotation, no node-name collisions) +# - StorageImmutable rejection for boot-time attached volumes +# - NoSuchStorage rejection for unknown names +# - clean RemoveStorage after a replace (proves both old and new file nodes +# are monitor-owned and properly cleaned up) +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 ! command -v storagectl >/dev/null 2>&1; then + echo "storagectl 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 + +if ! test -S /run/systemd/io.systemd.StorageProvider/fs; then + echo "StorageProvider fs socket not found, skipping" + exit 0 +fi + +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 + +WORKDIR="$(mktemp -d /tmp/test-replace-storage.XXXXXXXXXX)" + +at_exit() { + set +e + if [[ -n "${MACHINE:-}" ]]; then + if machinectl status "$MACHINE" &>/dev/null; then + machinectl terminate "$MACHINE" 2>/dev/null + timeout 10 bash -c "while machinectl status '$MACHINE' &>/dev/null; do sleep .5; done" 2>/dev/null + fi + fi + [[ -n "${VMSPAWN_PID:-}" ]] && { kill "$VMSPAWN_PID" 2>/dev/null; wait "$VMSPAWN_PID" 2>/dev/null; } + rm -rf "$WORKDIR" + rm -f /var/lib/storage/test-replace-storage-*.volume +} +trap at_exit EXIT + +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" + +BOOT_VOL="test-replace-storage-boot-$$" +RUNTIME_VOL="test-replace-storage-runtime-$$" + +# Backing files for ReplaceStorage. Regular files; --push-fd opens read-only. +truncate -s 32M "$WORKDIR/new-backing-1.raw" +truncate -s 32M "$WORKDIR/new-backing-2.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 77 + fi + sleep .5 + done + " || { + local rc=$? + if [[ $rc -eq 77 ]]; then exit 0; fi + exit "$rc" + } +} + +MACHINE="test-replace-storage-$$" +systemd-vmspawn \ + --machine="$MACHINE" \ + --ram=256M \ + --image="$WORKDIR/root.raw" \ + --bind-volume="fs:${BOOT_VOL}::create=new,size=64M,template=sparse-file" \ + --linux="$KERNEL" \ + --tpm=no \ + --console=headless \ + root=/dev/vda rw \ + &>"$WORKDIR/vmspawn.log" & +VMSPAWN_PID=$! + +wait_for_machine "$MACHINE" "$VMSPAWN_PID" "$WORKDIR/vmspawn.log" +echo "Machine '$MACHINE' registered" + +VARLINK_ADDR=$(varlinkctl call /run/systemd/machine/io.systemd.Machine \ + io.systemd.Machine.List "{\"name\":\"$MACHINE\"}" | jq -r '.controlAddress') +assert_neq "$VARLINK_ADDR" "null" + +# --- Hot-add a runtime volume (target for ReplaceStorage) --- +# virtio-scsi: vmspawn's hot-add path only allocates a PCIe root port for the +# scsi controller; bare virtio-blk hot-add fails on QEMU builds that don't +# auto-pick a free slot. Same backend code path either way. +machinectl bind-volume "$MACHINE" \ + "fs:${RUNTIME_VOL}:virtio-scsi:create=new,size=32M,template=sparse-file" +echo "Hot-added runtime bind-volume" + +# varlinkctl --push-fd= opens O_RDONLY; the runtime drive is RW so the +# server rejects an RO fd with EROFS. Open RW via bash and pass the numeric fd. + +# --- Test 1: happy-path replace --- +exec {NEW_FD}<>"$WORKDIR/new-backing-1.raw" +varlinkctl --push-fd="$NEW_FD" call "$VARLINK_ADDR" \ + io.systemd.MachineInstance.ReplaceStorage \ + "{\"fileDescriptorIndex\":0,\"name\":\"fs:${RUNTIME_VOL}\"}" +exec {NEW_FD}<&- +echo "Replace #1 succeeded" + +# --- Test 2: replace again (verify file_generation rotation) --- +exec {NEW_FD}<>"$WORKDIR/new-backing-2.raw" +varlinkctl --push-fd="$NEW_FD" call "$VARLINK_ADDR" \ + io.systemd.MachineInstance.ReplaceStorage \ + "{\"fileDescriptorIndex\":0,\"name\":\"fs:${RUNTIME_VOL}\"}" +exec {NEW_FD}<&- +echo "Replace #2 succeeded" + +# --- Test 3: replace boot-time drive must fail with StorageImmutable --- +if varlinkctl --push-fd="$WORKDIR/new-backing-1.raw" call "$VARLINK_ADDR" \ + io.systemd.MachineInstance.ReplaceStorage \ + "{\"fileDescriptorIndex\":0,\"name\":\"fs:${BOOT_VOL}\"}" 2>"$WORKDIR/replace-immutable.err"; then + echo "ERROR: ReplaceStorage of boot-time drive should have failed" + cat "$WORKDIR/replace-immutable.err" + exit 1 +fi +grep StorageImmutable "$WORKDIR/replace-immutable.err" >/dev/null +echo "Boot-time drive correctly rejected with StorageImmutable" + +# --- Test 4: replace non-existent name must fail with NoSuchStorage --- +if varlinkctl --push-fd="$WORKDIR/new-backing-1.raw" call "$VARLINK_ADDR" \ + io.systemd.MachineInstance.ReplaceStorage \ + "{\"fileDescriptorIndex\":0,\"name\":\"fs:does-not-exist-$$\"}" 2>"$WORKDIR/replace-nosuch.err"; then + echo "ERROR: ReplaceStorage of non-existent drive should have failed" + cat "$WORKDIR/replace-nosuch.err" + exit 1 +fi +grep NoSuchStorage "$WORKDIR/replace-nosuch.err" >/dev/null +echo "Non-existent drive correctly rejected with NoSuchStorage" + +# --- Test 5: RO fd to RW drive must fail with EROFS --- +# varlinkctl --push-fd= opens RO; runtime drive is RW. +# Capture both stdout and stderr: errnoName "EROFS" is in the JSON reply on +# stdout; stderr only carries the human-readable strerror. +if varlinkctl --push-fd="$WORKDIR/new-backing-1.raw" call "$VARLINK_ADDR" \ + io.systemd.MachineInstance.ReplaceStorage \ + "{\"fileDescriptorIndex\":0,\"name\":\"fs:${RUNTIME_VOL}\"}" &>"$WORKDIR/replace-rofs.err"; then + echo "ERROR: ReplaceStorage with RO fd should have failed" + cat "$WORKDIR/replace-rofs.err" + exit 1 +fi +grep EROFS "$WORKDIR/replace-rofs.err" >/dev/null +echo "RO fd to RW drive correctly rejected with EROFS" + +# --- Test 6: unbind after replace (proves new file node is monitor-owned and +# the format-then-file teardown order correctly cleans up both nodes) --- +machinectl unbind-volume "$MACHINE" "fs:${RUNTIME_VOL}" +echo "Unbind after replace succeeded (cleanup of both nodes works)" + +machinectl terminate "$MACHINE" +timeout 10 bash -c "while machinectl status '$MACHINE' &>/dev/null; do sleep .5; done" +timeout 10 bash -c "while kill -0 '$VMSPAWN_PID' 2>/dev/null; do sleep .5; done" +echo "All ReplaceStorage tests passed" diff --git a/test/units/TEST-87-AUX-UTILS-VM.storagectl.sh b/test/units/TEST-87-AUX-UTILS-VM.storagectl.sh new file mode 100755 index 0000000000000..a11a952a8e8da --- /dev/null +++ b/test/units/TEST-87-AUX-UTILS-VM.storagectl.sh @@ -0,0 +1,211 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +if ! command -v storagectl >/dev/null; then + echo "storagectl not found, skipping." + exit 77 +fi + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Unset $PAGER so we don't have to use --no-pager everywhere +export PAGER= + +# storagectl runs in a VM-only test +if systemd-detect-virt -cq ; then + echo "can't run in a container, skipping." + exit 77 +fi + +at_exit() { + set +e + + if [[ -n "${MOUNT_DIR:-}" ]] && mountpoint -q "$MOUNT_DIR"; then + umount "$MOUNT_DIR" + fi + if [[ -n "${LOOP:-}" ]]; then + systemd-dissect --detach "$LOOP" + fi + if [[ -n "${WORK_DIR:-}" ]]; then + rm -fr "$WORK_DIR" + fi + rm -fr /var/lib/storage/test-87-storage-*.volume +} +trap at_exit EXIT + +# The storage providers are socket-activated by sockets.target, so the listening +# AF_UNIX sockets should already exist. +test -S /run/systemd/io.systemd.StorageProvider/block +test -S /run/systemd/io.systemd.StorageProvider/fs + +WORK_DIR="$(mktemp -d /tmp/test-storagectl.XXXXXXXXXX)" +MOUNT_DIR="$WORK_DIR/mnt" +mkdir -p "$MOUNT_DIR" + +# --- storagectl basic --- + +storagectl --help +storagectl --version +storagectl help + +# Unknown verb / option +(! storagectl this-verb-does-not-exist) +(! storagectl --no-such-option providers) + +# --- storagectl providers --- + +storagectl providers +storagectl providers --no-legend +storagectl providers --no-pager +storagectl providers --json=pretty | jq . +storagectl providers --json=short | jq . + +providers_output="$(storagectl providers --no-legend)" +assert_in 'block' "$providers_output" +assert_in 'fs' "$providers_output" +assert_in 'yes' "$providers_output" + +# --- storagectl volumes --- + +# 'volumes' is the default verb +storagectl +storagectl volumes +storagectl volumes --no-legend +storagectl volumes --no-pager +storagectl volumes --json=pretty | jq . +storagectl volumes --json=short | jq . + +# Glob filter that matches nothing should not error +storagectl volumes 'no-such-volume-*' + +# --- storagectl templates --- + +storagectl templates +storagectl templates --no-legend --no-pager +storagectl templates --json=pretty | jq . +storagectl templates --json=short | jq --seq . + +templates_output="$(storagectl templates --no-legend)" +assert_in 'sparse-file' "$templates_output" +assert_in 'allocated-file' "$templates_output" +assert_in 'directory' "$templates_output" +assert_in 'subvolume' "$templates_output" + +# Glob filter +storagectl templates 'sparse-*' --no-legend | grep sparse-file >/dev/null +(! storagectl templates 'sparse-*' --no-legend | grep allocated-file >/dev/null) +storagectl templates 'no-such-template-*' + +# --- direct varlink calls --- + +varlinkctl introspect /run/systemd/io.systemd.StorageProvider/block io.systemd.StorageProvider +varlinkctl introspect /run/systemd/io.systemd.StorageProvider/fs io.systemd.StorageProvider + +# Block provider does not expose templates +varlinkctl call --more /run/systemd/io.systemd.StorageProvider/block \ + io.systemd.StorageProvider.ListTemplates '{}' \ + --graceful=io.systemd.StorageProvider.NoSuchTemplate + +# fs provider lists the four built-in templates +varlinkctl call --more --json=short /run/systemd/io.systemd.StorageProvider/fs \ + io.systemd.StorageProvider.ListTemplates '{}' | grep '"name":"sparse-file"' >/dev/null + +# Block provider rejects names not under /dev/ +varlinkctl call /run/systemd/io.systemd.StorageProvider/block \ + io.systemd.StorageProvider.Acquire '{"name":"/tmp/no-such-dev"}' \ + --graceful=io.systemd.StorageProvider.NoSuchVolume + +# fs provider rejects bad volume names (contain '/' → not a valid filename) +varlinkctl call /run/systemd/io.systemd.StorageProvider/fs \ + io.systemd.StorageProvider.Acquire '{"name":"bad/name"}' \ + --graceful=org.varlink.service.InvalidParameter + +# --- mount.storage: regular file via fs provider --- + +TESTVOL_REG="test-87-storage-reg-$RANDOM" +truncate -s 32M "/var/lib/storage/$TESTVOL_REG.volume" +mkfs.ext4 "/var/lib/storage/$TESTVOL_REG.volume" +mount -t storage.ext4 "fs:$TESTVOL_REG" "$MOUNT_DIR" +mountpoint -q "$MOUNT_DIR" +echo "hello reg" >"$MOUNT_DIR/hello" +umount "$MOUNT_DIR" + +# Volume now appears in 'storagectl volumes' +volumes_after_create="$(storagectl volumes "$TESTVOL_REG" --no-legend)" +assert_in "$TESTVOL_REG" "$volumes_after_create" +assert_in 'reg' "$volumes_after_create" + +# Re-mount existing (default storage.create=any) +mount -t storage.ext4 "fs:$TESTVOL_REG" "$MOUNT_DIR" +test -f "$MOUNT_DIR/hello" +umount "$MOUNT_DIR" + +# storage.create=open succeeds for existing volume +mount -t storage.ext4 -o "storage.create=open" "fs:$TESTVOL_REG" "$MOUNT_DIR" +umount "$MOUNT_DIR" + +# storage.create=new on existing volume must fail +(! mount -t storage.ext4 -o "storage.create=new,storage.create-size=16M" "fs:$TESTVOL_REG" "$MOUNT_DIR") + +# Read-only mount +mount -t storage.ext4 -o ro "fs:$TESTVOL_REG" "$MOUNT_DIR" +findmnt -n -o options "$MOUNT_DIR" | grep -E '(^|,)ro(,|$)' >/dev/null +(! touch "$MOUNT_DIR/readonly-test") +umount "$MOUNT_DIR" + +rm -f "/var/lib/storage/$TESTVOL_REG.volume" + +# storage.create=open on missing volume must fail +(! mount -t storage.ext4 -o "storage.create=open" "fs:test-87-storage-missing-$RANDOM" "$MOUNT_DIR") + +# --- mount.storage: directory volume via fs provider (requires idmapped mounts) --- + +TESTVOL_DIR="test-87-storage-dir-$RANDOM" +if mount -t storage "fs:$TESTVOL_DIR" "$MOUNT_DIR"; then + mountpoint -q "$MOUNT_DIR" + test -d "/var/lib/storage/$TESTVOL_DIR.volume/root" + echo "dir test" >"$MOUNT_DIR/hello" + test -f "/var/lib/storage/$TESTVOL_DIR.volume/root/hello" + umount "$MOUNT_DIR" + rm -fr "/var/lib/storage/$TESTVOL_DIR.volume" +else + echo "Directory volume mounting failed (idmapped mounts unsupported?), skipping." + rm -fr "/var/lib/storage/$TESTVOL_DIR.volume" +fi + +# --- mount.storage: block device via block provider --- + +truncate -s 32M "$WORK_DIR/block.img" +mkfs.ext4 -L sd-storage-blk "$WORK_DIR/block.img" +LOOP="$(systemd-dissect --attach --loop-ref=test-storagectl "$WORK_DIR/block.img")" + +mount -t storage.ext4 "block:$LOOP" "$MOUNT_DIR" +mountpoint -q "$MOUNT_DIR" +echo "hello blk" >"$MOUNT_DIR/hello" +umount "$MOUNT_DIR" + +# Read-only mount of the block volume +mount -t storage.ext4 -o ro "block:$LOOP" "$MOUNT_DIR" +findmnt -n -o options "$MOUNT_DIR" | grep -E '(^|,)ro(,|$)' >/dev/null +test -f "$MOUNT_DIR/hello" +umount "$MOUNT_DIR" + +# Block volume is enumerable; matchName globs over device node and aliases +varlinkctl call --more --json=short /run/systemd/io.systemd.StorageProvider/block \ + io.systemd.StorageProvider.ListVolumes "{\"matchName\":\"$LOOP\"}" | + grep '"type":"blk"' >/dev/null + +systemd-dissect --detach "$LOOP" +unset LOOP + +# --- error cases --- + +# Bad provider name (no such socket) +(! mount -t storage.ext4 "no-such-provider:foo" "$MOUNT_DIR") +# Bad volume specification (no colon) +(! mount -t storage.ext4 "no-colon-here" "$MOUNT_DIR") +# Refuse nested storage volumes (FS type "storage.storage") +(! mount -t storage.storage "fs:something" "$MOUNT_DIR") diff --git a/test/units/TEST-87-AUX-UTILS-VM.sysinstall.sh b/test/units/TEST-87-AUX-UTILS-VM.sysinstall.sh new file mode 100755 index 0000000000000..d4ca6e0a0d163 --- /dev/null +++ b/test/units/TEST-87-AUX-UTILS-VM.sysinstall.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +if ! command -v systemd-sysinstall >/dev/null; then + echo "systemd-sysinstall not found, skipping." + exit 0 +fi + +if ! command -v systemd-repart >/dev/null; then + echo "systemd-repart not found, skipping." + exit 0 +fi + +if ! command -v bootctl >/dev/null; then + echo "bootctl not found, skipping." + exit 0 +fi + +if ! command -v ukify >/dev/null; then + echo "ukify not found, skipping." + exit 0 +fi + +if [[ ! -d /usr/lib/systemd/boot/efi ]]; then + echo "sd-boot is not installed, skipping." + exit 0 +fi + +# We need a real environment to fiddle with loop devices. +if systemd-detect-virt -cq; then + echo "Running in a container, skipping." + exit 0 +fi + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +WORKDIR="$(mktemp --directory /tmp/test-sysinstall.XXXXXXXXXX)" +LOOPDEV="" +MOUNTED=0 + +cleanup() { + set +e + if [[ "$MOUNTED" -eq 1 ]]; then + umount -R "$WORKDIR/mnt" + MOUNTED=0 + fi + if [[ -n "$LOOPDEV" ]]; then + systemd-dissect --detach "$LOOPDEV" + LOOPDEV="" + fi + rm -rf "$WORKDIR" +} +trap cleanup EXIT + +# 1) Build a small fake "OS source" tree. systemd-sysinstall picks this up via +# the repart.sysinstall.d definitions: CopyFiles= seeds the new root +# partition with these files. +SOURCE_ROOT="$WORKDIR/sourceroot" +mkdir -p "$SOURCE_ROOT/usr/lib" "$SOURCE_ROOT/etc" + +cat >"$SOURCE_ROOT/usr/lib/os-release" <<'EOF' +ID=testos +NAME="Test OS" +PRETTY_NAME="Test OS for systemd-sysinstall" +VERSION_ID=1 +EOF +ln -s ../usr/lib/os-release "$SOURCE_ROOT/etc/os-release" + +# 2) Build a minimal UKI. bootctl link only requires a valid PE with .osrel and +# the systemd-stub SBAT marker, so the .linux/.initrd contents do not need +# to be a real kernel. +echo "fake-kernel" >"$WORKDIR/vmlinuz" +echo "fake-initrd" >"$WORKDIR/initrd" + +ukify build \ + --linux "$WORKDIR/vmlinuz" \ + --initrd "$WORKDIR/initrd" \ + --os-release "@$SOURCE_ROOT/usr/lib/os-release" \ + --uname "1.2.3-testkernel" \ + --cmdline "quiet" \ + --output "$WORKDIR/testuki.efi" + +# 3) Build a sysinstall partition definition: a single ESP plus a root +# partition seeded from the fake source tree. +DEFS="$WORKDIR/sysinstall.d" +mkdir -p "$DEFS" + +cat >"$DEFS/10-esp.conf" <"$DEFS/20-root.conf" </dev/null + +# The UKI file referenced in the entry must exist on the ESP. +UKI_PATH=$(awk '/^uki / { print $2 }' "$ENTRY") +test -n "$UKI_PATH" +test -f "$ESP$UKI_PATH" + +# bootctl install should have placed sd-boot on the ESP. +find "$ESP/EFI/systemd" -type f -iname 'systemd-boot*.efi' | grep . >/dev/null + +# The credential we passed via --set-credential= must have been encrypted and +# placed next to the UKI, and must be referenced as 'extra' from the entry. +UKI_DIR="$(dirname "$ESP$UKI_PATH")" +TOKEN_DIR="$(basename "$UKI_DIR")" +test -s "$UKI_DIR/marker.cred" +grep -E "^extra /$TOKEN_DIR/marker\.cred$" "$ENTRY" >/dev/null + +# Locale/keymap/timezone propagation is off, so those .cred files must NOT +# exist on the ESP. +test ! -e "$UKI_DIR/firstboot.locale.cred" +test ! -e "$UKI_DIR/firstboot.keymap.cred" +test ! -e "$UKI_DIR/firstboot.timezone.cred" + +# 8) The seeded files from the fake source tree must end up in the new root. +test -f "$MNT/usr/lib/os-release" +grep '^ID=testos$' "$MNT/usr/lib/os-release" >/dev/null 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..0a33cbd60c5f2 --- /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=60 "$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; } || : +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=60 "$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; } || : +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-RESTRICT-FSACCESS.config.sh b/test/units/TEST-90-RESTRICT-FSACCESS.config.sh new file mode 100755 index 0000000000000..81bd0ff4144b6 --- /dev/null +++ b/test/units/TEST-90-RESTRICT-FSACCESS.config.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Test RestrictFileSystemAccess= configuration parsing and graceful failure modes. +# +# Runs in a VM WITHOUT dm_verity.require_signatures=1, so enabling RestrictFileSystemAccess +# triggers the require_signatures error path without activating enforcement. +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +# RestrictFileSystemAccess= requires +BPF_FRAMEWORK at compile time +if systemctl --version | grep -F -- "-BPF_FRAMEWORK" >/dev/null; then + echo "BPF framework not compiled in, skipping" + exit 0 +fi + +HELPER=/usr/lib/systemd/tests/unit-tests/manual/test-bpf-restrict-fsaccess +CURSOR_FILE=/tmp/restrict-fsaccess-config.cursor + +cleanup() { + rm -f "$CURSOR_FILE" + rm -f /run/systemd/system.conf.d/50-restrict-fsaccess.conf +} +trap cleanup EXIT + +disable_restrict_fsaccess() { + rm -f /run/systemd/system.conf.d/50-restrict-fsaccess.conf +} + +# ------ Test case 1: Default (RestrictFileSystemAccess=no) — no log messages ------ + +testcase_default_no_messages() { + disable_restrict_fsaccess + + # Save a journal cursor so we only check messages from after this point. + journalctl -q -n 0 --cursor-file="$CURSOR_FILE" + + systemctl daemon-reexec + # daemon-reexec is synchronous: PID1 has completed startup (including any + # RestrictFileSystemAccess= setup) and is back on D-Bus by the time it returns. PID1 + # logs to kmsg synchronously, so messages are already in the journal. + + # No RestrictFileSystemAccess-related messages should appear + if journalctl --cursor-file="$CURSOR_FILE" -o cat _PID=1 | grep "bpf-restrict-fsaccess" >/dev/null 2>&1; then + echo "Unexpected RestrictFileSystemAccess log messages with RestrictFileSystemAccess=no" + return 1 + fi +} + +# ------ Test case 2: require_signatures check via helper binary ------ +# +# The helper binary runs the same precondition checks as PID1 (BPF LSM +# availability, dm-verity require_signatures). When require_signatures is +# off the check must fail — this verifies the C code gate without going +# through daemon-reexec (which would kill PID1). + +testcase_no_require_signatures_helper() { + if ! kernel_supports_lsm bpf; then + echo "BPF LSM not available, skipping require_signatures test" + return 0 + fi + + # Check that the kernel has the bdev_setintegrity LSM hook in BTF. + # Without it the skeleton fails to load and the check reports "BPF LSM + # is not available" which masks the real reason. + if command -v bpftool >/dev/null 2>&1; then + if ! bpftool btf dump file /sys/kernel/btf/vmlinux 2>/dev/null | grep 'bpf_lsm_bdev_setintegrity' >/dev/null; then + echo "Kernel lacks bdev_setintegrity LSM hook, skipping require_signatures test" + return 0 + fi + fi + + if [[ ! -x "$HELPER" ]]; then + echo "Helper binary not found, skipping" + return 0 + fi + + # This VM boots WITHOUT require_signatures. + if [[ -e /sys/module/dm_verity/parameters/require_signatures ]]; then + local val + val="$(cat /sys/module/dm_verity/parameters/require_signatures)" + if [[ "$val" == "Y" || "$val" == "1" ]]; then + echo "require_signatures already enabled, skipping (enforce VM covers this)" + return 0 + fi + fi + + # The helper's "check" command runs the same bpf_restrict_fsaccess_supported() + # and dm_verity_require_signatures() checks that PID1 uses. It must fail + # because require_signatures is not enabled. + if "$HELPER" check; then + echo "ERROR: helper check succeeded but require_signatures is not enabled" + return 1 + fi + echo "Helper correctly rejected setup: require_signatures not enabled" +} + +run_testcases diff --git a/test/units/TEST-90-RESTRICT-FSACCESS.dm-verity-keyring.sh b/test/units/TEST-90-RESTRICT-FSACCESS.dm-verity-keyring.sh new file mode 100755 index 0000000000000..b1c1de2583abf --- /dev/null +++ b/test/units/TEST-90-RESTRICT-FSACCESS.dm-verity-keyring.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Exercise the dedicated .dm-verity keyring trust path (kernel commit +# 033724b1c627, v7.0+): boot with linux-noinitrd so .platform stays empty, +# provision the mkosi cert into .dm-verity via keyctl, then verify a signed +# verity image still loads and execs under the BPF policy. +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +if systemctl --version | grep -F -- "-BPF_FRAMEWORK" >/dev/null; then + echo "BPF framework not compiled in, skipping" + exit 0 +fi + +if ! kernel_supports_lsm bpf; then + echo "BPF LSM not available in kernel, skipping" + exit 0 +fi + +if command -v bpftool >/dev/null 2>&1; then + if ! bpftool btf dump file /sys/kernel/btf/vmlinux 2>/dev/null | grep 'bpf_lsm_bdev_setintegrity' >/dev/null; then + echo "Kernel lacks bdev_setintegrity LSM hook, skipping" + exit 0 + fi +fi + +if [[ -v ASAN_OPTIONS ]]; then + echo "Skipping under sanitizers" + exit 0 +fi + +HELPER="/usr/lib/systemd/tests/unit-tests/manual/test-bpf-restrict-fsaccess" +if [[ ! -x "$HELPER" ]]; then + echo "ERROR: test-bpf-restrict-fsaccess helper not found at $HELPER" >&2 + exit 1 +fi + +# Helper exits 77 when systemd was built with bpf-framework=enabled but no +# vmlinux.h (HAVE_LSM_INTEGRITY_TYPE=0), so the BPF program isn't compiled in. +rc=0 +"$HELPER" check >/dev/null 2>&1 || rc=$? +if [[ "$rc" -eq 77 ]]; then + echo "test-bpf-restrict-fsaccess built without BPF attach support, skipping" + exit 0 +fi + +if [[ ! -e /sys/module/dm_verity/parameters/require_signatures ]]; then + modprobe dm_verity 2>/dev/null || true +fi +val="$(cat /sys/module/dm_verity/parameters/require_signatures 2>/dev/null || echo)" +if [[ "$val" != "Y" && "$val" != "1" ]]; then + echo "require_signatures not enabled, skipping" + exit 0 +fi + +# Provision the .dm-verity keyring. Empty description lets the kernel derive +# one from the X.509 subject so machine_supports_verity_keyring finds the CN. +keyid=$(openssl x509 -in /usr/share/mkosi.crt -outform DER | + keyctl padd asymmetric '' %:.dm-verity 2>/dev/null) || keyid="" +if [[ -z "$keyid" ]]; then + echo ".dm-verity keyring not provisionable (kernel < v7.0?), skipping" + exit 0 +fi +if ! keyctl restrict_keyring %:.dm-verity; then + keyctl unlink "$keyid" %:.dm-verity 2>/dev/null || true + echo "ERROR: keyctl restrict_keyring failed" >&2 + exit 1 +fi +echo "Provisioned .dm-verity keyring with mkosi.crt" + +at_exit() { + set +e + [[ -n "${HELPER_PID:-}" ]] && kill "$HELPER_PID" 2>/dev/null && wait "$HELPER_PID" 2>/dev/null || true + rm -rf /tmp/restrict-fsaccess-dvk-attach.out +} +trap at_exit EXIT + +HELPER_PID= +exec 3< <(exec "$HELPER" attach) +HELPER_PID=$! +while IFS= read -r -t 60 line <&3; do + echo "$line" + [[ "$line" == LINK_IDS=* ]] && break +done >/tmp/restrict-fsaccess-dvk-attach.out + +# Fail closed if helper died before printing the full handshake: an unattached +# program would let the subsequent verity exec test pass trivially. +if ! kill -0 "$HELPER_PID" 2>/dev/null; then + echo "ERROR: helper exited before BPF programs were attached" >&2 + exit 1 +fi +grep -E '^LINK_IDS="[^"]+"' /tmp/restrict-fsaccess-dvk-attach.out >/dev/null || { + echo "ERROR: helper did not report LINK_IDS, BPF programs not attached" >&2 + exit 1 +} + +# Run a binary off the signed minimal_0 verity image. Trust path is exclusively +# the .dm-verity keyring we just provisioned; .platform is empty under +# linux-noinitrd. +systemd-run --pipe --wait \ + --property RootImage=/usr/share/minimal_0.raw \ + bash --version >/dev/null +echo "Execution from signed dm-verity device (via .dm-verity keyring): OK" diff --git a/test/units/TEST-90-RESTRICT-FSACCESS.enforce.sh b/test/units/TEST-90-RESTRICT-FSACCESS.enforce.sh new file mode 100755 index 0000000000000..ad1093f44f4e0 --- /dev/null +++ b/test/units/TEST-90-RESTRICT-FSACCESS.enforce.sh @@ -0,0 +1,311 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Test RestrictFileSystemAccess= BPF enforcement. +# +# Uses a C test helper to load the BPF program with initramfs_s_dev set to the +# current rootfs s_dev, then verifies that execution from tmpfs is blocked +# while execution from the rootfs continues to work. If dm-verity signing +# support is available, also tests execution from a signed verity device. +# +# Requires the VM to be booted with dm-verity.require_signatures=1 on the +# kernel command line (set in the test's meson.build). +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Skip if prerequisites not met +if systemctl --version | grep -F -- "-BPF_FRAMEWORK" >/dev/null; then + echo "BPF framework not compiled in, skipping" + exit 0 +fi + +if ! kernel_supports_lsm bpf; then + echo "BPF LSM not available in kernel, skipping" + exit 0 +fi + +# Check that the kernel has the bdev_setintegrity LSM hook in BTF. +# Older kernels (e.g., CentOS 9 with 5.14) lack this hook entirely. +if command -v bpftool >/dev/null 2>&1; then + if ! bpftool btf dump file /sys/kernel/btf/vmlinux 2>/dev/null | grep 'bpf_lsm_bdev_setintegrity' >/dev/null; then + echo "Kernel lacks bdev_setintegrity LSM hook (required for RestrictFileSystemAccess=), skipping" + exit 0 + fi +fi + +if [[ -v ASAN_OPTIONS ]]; then + echo "Skipping enforcement test under sanitizers" + exit 0 +fi + +HELPER="/usr/lib/systemd/tests/unit-tests/manual/test-bpf-restrict-fsaccess" +if [[ ! -x "$HELPER" ]]; then + echo "ERROR: test-bpf-restrict-fsaccess helper not found at $HELPER" >&2 + exit 1 +fi + +# Helper exits 77 when systemd was built with bpf-framework=enabled but no +# vmlinux.h (HAVE_LSM_INTEGRITY_TYPE=0), so the BPF program isn't compiled in. +rc=0 +"$HELPER" check >/dev/null 2>&1 || rc=$? +if [[ "$rc" -eq 77 ]]; then + echo "test-bpf-restrict-fsaccess built without BPF attach support, skipping" + exit 0 +fi + +# require_signatures is read-only — must be set via kernel cmdline +if [[ ! -e /sys/module/dm_verity/parameters/require_signatures ]]; then + modprobe dm_verity 2>/dev/null || true +fi +if [[ ! -e /sys/module/dm_verity/parameters/require_signatures ]]; then + echo "dm_verity module not available, skipping enforcement test" + exit 0 +fi +val="$(cat /sys/module/dm_verity/parameters/require_signatures)" +if [[ "$val" != "Y" && "$val" != "1" ]]; then + echo "require_signatures not enabled (need dm-verity.require_signatures=1 on cmdline), skipping" + exit 0 +fi + +at_exit() { + set +e + # Kill the attach helper to detach BPF programs synchronously + [[ -n "${HELPER_PID:-}" ]] && kill "$HELPER_PID" 2>/dev/null && wait "$HELPER_PID" 2>/dev/null || true + # Clean up tmpfs test directories + umount /tmp/restrict-fsaccess-test 2>/dev/null || true + rm -rf /tmp/restrict-fsaccess-test + umount /tmp/restrict-fsaccess-baseline 2>/dev/null || true + rm -rf /tmp/restrict-fsaccess-baseline + # Clean up background processes + [[ -n "${SLEEP_PID:-}" ]] && kill "$SLEEP_PID" 2>/dev/null || true + rm -rf /tmp/restrict-fsaccess-attach.out +} +trap at_exit EXIT + +# ------ Baseline: verify tmpfs exec works WITHOUT our BPF ------ +# +# Keep the destination basename as "true": on systems shipping uutils-coreutils +# (or busybox) as a multicall binary, /usr/bin/true is a symlink and cp +# dereferences it, copying the multicall binary. The dispatcher selects the +# subcommand from basename(argv[0]), so the copy only behaves as true when +# invoked under that name. + +mkdir -p /tmp/restrict-fsaccess-baseline +mount -t tmpfs tmpfs /tmp/restrict-fsaccess-baseline +cp /usr/bin/true /tmp/restrict-fsaccess-baseline/true +chmod +x /tmp/restrict-fsaccess-baseline/true +if ! /tmp/restrict-fsaccess-baseline/true 2>/dev/null; then + echo "WARNING: tmpfs exec blocked BEFORE BPF attach (another LSM?)" >&2 + echo "Skipping enforcement test, baseline tmpfs exec fails" + umount /tmp/restrict-fsaccess-baseline; rm -rf /tmp/restrict-fsaccess-baseline + exit 0 +fi +echo "Baseline: tmpfs exec works without BPF" +umount /tmp/restrict-fsaccess-baseline; rm -rf /tmp/restrict-fsaccess-baseline + +# ------ Attach BPF with rootfs trusted ------ +# The helper attaches, prints map/prog/link IDs, then blocks holding FDs. +# Kill it to detach synchronously (close() drops last ref via bpf_link_put_direct). + +HELPER_PID= +exec 3< <(exec "$HELPER" attach) +HELPER_PID=$! + +# Read helper output line by line until LINK_IDS= (the last line before pause()). +# read -t 60 handles both timeout and helper crash (EOF on death). +while IFS= read -r -t 60 line <&3; do + echo "$line" + [[ "$line" == LINK_IDS=* ]] && break +done >/tmp/restrict-fsaccess-attach.out + +VERITY_MAP_ID=$(sed -n 's/^VERITY_MAP_ID=//p' /tmp/restrict-fsaccess-attach.out) +BSS_MAP_ID=$(sed -n 's/^BSS_MAP_ID=//p' /tmp/restrict-fsaccess-attach.out) +PROG_IDS=$(sed -n 's/^PROG_IDS="\(.*\)"$/\1/p' /tmp/restrict-fsaccess-attach.out) +LINK_IDS=$(sed -n 's/^LINK_IDS="\(.*\)"$/\1/p' /tmp/restrict-fsaccess-attach.out) +[[ -n "$VERITY_MAP_ID" ]] || { echo "ERROR: Failed to capture VERITY_MAP_ID from helper output" >&2; exit 1; } +[[ -n "$BSS_MAP_ID" ]] || { echo "ERROR: Failed to capture BSS_MAP_ID from helper output" >&2; exit 1; } +[[ -n "$PROG_IDS" ]] || { echo "ERROR: Failed to capture PROG_IDS from helper output" >&2; exit 1; } +[[ -n "$LINK_IDS" ]] || { echo "ERROR: Failed to capture LINK_IDS from helper output" >&2; exit 1; } + +# ------ Test: Rootfs execution still works ------ + +/usr/bin/true +echo "Rootfs execution: OK" + +# ------ Test: Execution from tmpfs is blocked ------ + +mkdir -p /tmp/restrict-fsaccess-test +mount -t tmpfs tmpfs /tmp/restrict-fsaccess-test + +# Copy a binary to tmpfs. Basename must stay "true" for multicall coreutils +# binaries (uutils, busybox) — see the baseline comment above. +cp /usr/bin/true /tmp/restrict-fsaccess-test/true +chmod +x /tmp/restrict-fsaccess-test/true + +# This should fail with EPERM +if /tmp/restrict-fsaccess-test/true 2>/dev/null; then + echo "ERROR: Execution from tmpfs should have been blocked!" >&2 + exit 1 +fi +echo "Execution from tmpfs blocked: OK" + +# ------ Test: PROT_EXEC mmap from tmpfs is blocked (mmap_file hook) ------ + +# Write a test file on the tmpfs mount for mmap/mprotect tests +dd if=/dev/zero of=/tmp/restrict-fsaccess-test/testfile bs=4096 count=1 2>/dev/null + +# File-backed PROT_EXEC mmap should be denied. +# The helper exits 0 if mmap succeeds (bad), 1 if denied (good). +if "$HELPER" mmap-exec /tmp/restrict-fsaccess-test/testfile; then + echo "ERROR: PROT_EXEC mmap of tmpfs file should have been blocked!" >&2 + exit 1 +fi +echo "PROT_EXEC mmap from tmpfs blocked: OK" + +# Anonymous PROT_EXEC mmap should be denied (NULL file — mmap_file hook) +if "$HELPER" anon-mmap-exec; then + echo "ERROR: Anonymous PROT_EXEC mmap should have been blocked!" >&2 + exit 1 +fi +echo "Anonymous PROT_EXEC mmap blocked: OK" + +# ------ Test: mprotect adding PROT_EXEC is blocked (file_mprotect hook) ------ + +# mmap PROT_READ then mprotect to PROT_EXEC — the file_mprotect hook should deny this. +if "$HELPER" mprotect-exec /tmp/restrict-fsaccess-test/testfile; then + echo "ERROR: mprotect PROT_EXEC on tmpfs file should have been blocked!" >&2 + exit 1 +fi +echo "mprotect PROT_EXEC from tmpfs blocked: OK" + +# ------ Test: Execution from signed dm-verity device ------ +# Trust path: .platform keyring (SecureBoot DB auto-enrolled by mkosi, made +# available by 'firmware': 'auto' in the test's meson.build). + +MINIMAL=/usr/share/minimal_0 +if machine_supports_verity_keyring; then + systemd-run --pipe --wait \ + --property RootImage="$MINIMAL.raw" \ + bash --version >/dev/null + echo "Execution from signed dm-verity device: OK" +else + echo "Verity keyring trust not available, skipping positive verity test" +fi + +# ------ Test: Guard blocks non-PID1 from obtaining BPF object FDs by ID ------ + +if command -v bpftool >/dev/null 2>&1 && [[ -n "${VERITY_MAP_ID:-}" ]]; then + # bpftool uses BPF_MAP_GET_FD_BY_ID / BPF_PROG_GET_FD_BY_ID / + # BPF_LINK_GET_FD_BY_ID internally. The guard should block these for + # our protected IDs since we're not PID1. + + # -- Map ID guard -- + if bpftool map show id "$VERITY_MAP_ID" 2>/dev/null; then + echo "ERROR: bpftool should not be able to access verity_devices map (ID $VERITY_MAP_ID)!" >&2 + exit 1 + fi + echo "Guard blocked verity_devices map access: OK (ID $VERITY_MAP_ID)" + + if [[ -n "${BSS_MAP_ID:-}" ]]; then + if bpftool map show id "$BSS_MAP_ID" 2>/dev/null; then + echo "ERROR: bpftool should not be able to access .bss map (ID $BSS_MAP_ID)!" >&2 + exit 1 + fi + echo "Guard blocked .bss map access: OK (ID $BSS_MAP_ID)" + fi + + # -- Prog ID guard (defense-in-depth) -- + if [[ -n "${PROG_IDS:-}" ]]; then + IFS=',' read -ra prog_ids <<< "$PROG_IDS" + for prog_id in "${prog_ids[@]}"; do + if bpftool prog show id "$prog_id" 2>/dev/null; then + echo "ERROR: bpftool should not be able to access protected prog (ID $prog_id)!" >&2 + exit 1 + fi + done + echo "Guard blocked prog access: OK (${#prog_ids[@]} IDs)" + fi + + # -- Link ID guard (defense-in-depth) -- + if [[ -n "${LINK_IDS:-}" ]]; then + IFS=',' read -ra link_ids <<< "$LINK_IDS" + for lid in "${link_ids[@]}"; do + if bpftool link show id "$lid" 2>/dev/null; then + echo "ERROR: bpftool should not be able to access protected link (ID $lid)!" >&2 + exit 1 + fi + done + echo "Guard blocked link access: OK (${#link_ids[@]} IDs)" + fi + + # Verify the guard doesn't block unrelated BPF operations. + # bpftool prog list uses BPF_PROG_GET_NEXT_ID which the guard doesn't + # intercept (it only blocks *_GET_FD_BY_ID for specific IDs). + bpftool prog list >/dev/null 2>&1 || true + echo "Unrelated BPF operations still work: OK" +else + echo "bpftool not available or map IDs not captured, skipping guard test" +fi + +# ------ Test: ptrace attach to PID1 is blocked ------ + +# dd from /proc/1/mem uses PTRACE_MODE_ATTACH_FSCREDS via mm_access(). +# Read from a valid mapped address (not offset 0 which is the unmapped NULL +# page and would fail with -EIO even without the guard). +PID1_ADDR=$(awk '/r-xp/ { split($1, a, "-"); print a[1]; exit }' /proc/1/maps) +if [[ -n "$PID1_ADDR" ]]; then + PID1_OFFSET=$((16#$PID1_ADDR)) + if ! dd if=/proc/1/mem of=/dev/null bs=1 count=1 skip="$PID1_OFFSET" iflag=skip_bytes 2>/dev/null; then + echo "Ptrace ATTACH access to PID1 blocked: OK" + else + echo "ERROR: /proc/1/mem read should have been blocked!" >&2 + exit 1 + fi +else + echo "WARNING: Could not determine mapped address for PID1, skipping ptrace test" +fi + +# Verify READ-level access to PID1 still works (monitoring tools need this) +if cat /proc/1/status >/dev/null 2>&1; then + echo "Ptrace READ access to PID1 allowed: OK" +else + echo "ERROR: /proc/1/status should still be readable!" >&2 + exit 1 +fi + +# Verify ptrace to non-PID1 processes is unaffected +SLEEP_PID= +sleep 60 & +SLEEP_PID=$! +if cat /proc/$SLEEP_PID/status >/dev/null 2>&1; then + echo "Ptrace access to non-PID1 unaffected: OK" +else + echo "ERROR: /proc/$SLEEP_PID/status should be readable!" >&2 + kill "$SLEEP_PID" 2>/dev/null || true + exit 1 +fi +kill "$SLEEP_PID" 2>/dev/null || true +wait "$SLEEP_PID" 2>/dev/null || true +SLEEP_PID= + +# ------ Detach and verify enforcement is lifted ------ +# Kill the helper process. close() on the link FDs goes through +# bpf_link_put_direct() which synchronously detaches the trampoline. + +kill "$HELPER_PID" +wait "$HELPER_PID" 2>/dev/null || true +HELPER_PID= +echo "Helper killed, BPF programs detached synchronously" + +if [[ -x /tmp/restrict-fsaccess-test/true ]]; then + /tmp/restrict-fsaccess-test/true + echo "Execution from tmpfs after detach: OK" +fi + +umount /tmp/restrict-fsaccess-test 2>/dev/null || true +rm -rf /tmp/restrict-fsaccess-test + +echo "All enforcement tests passed" diff --git a/test/units/TEST-90-RESTRICT-FSACCESS.sh b/test/units/TEST-90-RESTRICT-FSACCESS.sh new file mode 100755 index 0000000000000..9c2a033aa98d1 --- /dev/null +++ b/test/units/TEST-90-RESTRICT-FSACCESS.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +run_subtests + +touch /testok diff --git a/test/units/TEST-91-LIVEUPDATE.sh b/test/units/TEST-91-LIVEUPDATE.sh new file mode 100755 index 0000000000000..9fba388cfa53b --- /dev/null +++ b/test/units/TEST-91-LIVEUPDATE.sh @@ -0,0 +1,334 @@ +#!/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 + +export SYSTEMD_LOG_LEVEL=debug + +# This test verifies that the Live Update Orchestrator (LUO) integration works: +# - PID 1 can serialize fd stores and pass them to systemd-shutdown +# - systemd-shutdown can preserve fds in a LUO session before kexec +# - After kexec, PID 1 restores the fd stores from the LUO session +# +# The test requires KHO (Kexec HandOver) and LUO (Live Update Orchestrator) kernel support. + +if [[ ! -e /dev/liveupdate ]]; then + echo "/dev/liveupdate not available, skipping test" + exit 77 +fi + +# Ensure user units can also manage sessions +chmod 666 /dev/liveupdate + +TESTUSER_UID=$(id -u testuser) +TESTUSER_USER_SVC="user@${TESTUSER_UID}.service" + +at_exit() { + systemctl stop systemd-nspawn@fdstore.service ||: + machinectl terminate fdstore ||: + rm -rf /var/lib/machines/fdstore ||: + rm -f /run/systemd/nspawn/fdstore.nspawn +} + +trap at_exit EXIT + +# To test the late-load path also create units that appear at runtime. +# Three variants exercise different fragment scenarios on second boot: +# - late.service: fragment present before fds are observed (daemon-reload triggered) +# - late-noreload.service: fragment dropped only after kexec, never daemon-reloaded explicitly +# to exercise lazy load via systemctl start +# - late-zerofds.service: fragment on second boot sets FileDescriptorStoreMax=0, +# the previously stored fds must be dropped +write_late_unit() { + local scope="${1:?}" name="${2:?}" cmd="${3:?}" maxfd="${4:-20}" + local dir + + case "${scope}" in + system) dir=/run/systemd/system ;; + user) dir=/run/systemd/user ;; + *) echo "unknown scope: ${scope}" >&2; return 1 ;; + esac + + mkdir -p "${dir}" + cat >"${dir}/${name}.service" <"/run/systemd/system/TEST-91-LIVEUPDATE-session.service" </var/lib/machines/fdstore/sbin/init <<'EOF' +#!/usr/bin/env bash +set -e +exec /usr/bin/test-fdstore check +EOF + chmod +x /var/lib/machines/fdstore/sbin/init + mkdir -p /run/systemd/nspawn + cat >/run/systemd/nspawn/fdstore.nspawn <&2 "test-fdstore not available in the minimal container, skipping fdstore tests" + fi + + # late.service: rewrite the fragment with the second-boot ExecStart and + # exercise the daemon-reload + daemon-reexec preservation paths. + write_late_unit system TEST-91-LIVEUPDATE-late \ + "/usr/lib/systemd/tests/unit-tests/manual/test-luo check late" + + n_fds=$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-late.service) + test "$n_fds" -eq 3 + + systemctl daemon-reload + + # Verify the late unit doesn't get GC'ed during daemon-reload + n_fds=$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-late.service) + test "$n_fds" -eq 3 + + systemctl daemon-reexec + + # Verify the late unit doesn't get GC'ed during daemon-reexec + n_fds=$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-late.service) + test "$n_fds" -eq 3 + + systemctl start TEST-91-LIVEUPDATE-late.service + + # No-reload variant: drop a brand-new fragment file but never call + # daemon-reload. Lazy load via systemctl start must pick it up while + # preserving the LUO-restored fds. + write_late_unit system TEST-91-LIVEUPDATE-late-noreload \ + "/usr/lib/systemd/tests/unit-tests/manual/test-luo check late-noreload" + n_fds=$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-late-noreload.service) + test "$n_fds" -eq 3 + systemctl start TEST-91-LIVEUPDATE-late-noreload.service + + # Zero-fds variant: fragment on second boot sets FileDescriptorStoreMax=0, + # so the LUO-restored fds must be dropped on (lazy) load. + write_late_unit system TEST-91-LIVEUPDATE-late-zerofds \ + "bash -c 'test \"\${LISTEN_FDS:-0}\" -eq 0'" 0 + systemctl daemon-reload + n_fds=$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-late-zerofds.service) + test "$n_fds" -eq 0 + systemctl start TEST-91-LIVEUPDATE-late-zerofds.service + + # Verify that with FileDescriptorStorePreserve=on-success the fdstore is + # discarded once the unit enters the permanent failed state, while still + # being preserved across the transitionary failed states that precede + # each automated auto-restart attempt. Use Restart=on-failure with + # StartLimitBurst=2 so the manager runs the helper twice before + # giving up. The helper: + # - on the first attempt pushes an fd into the fdstore, becomes ready, + # and then crashes, + # - on subsequent attempts asserts that the previously stored fd is + # handed back via $LISTEN_FDS (proving the fdstore survived the + # auto-restart) and then crashes again. + # When the start-limit is hit the unit lands in the permanent failed + # state, at which point the fdstore must be empty. + cat >/run/TEST-91-LIVEUPDATE-failure.sh <<'EOF' +#!/usr/bin/env bash +set -eux +state_file=/run/TEST-91-LIVEUPDATE-failure.attempt +attempt=$(cat "$state_file" 2>/dev/null || echo 0) +attempt=$((attempt + 1)) +echo "$attempt" >"$state_file" +if [[ "$attempt" -eq 1 ]]; then + systemd-notify --fd=0 --fdname=mem /run/systemd/system/TEST-91-LIVEUPDATE-failure.service </var/lib/machines/fdstore/sbin/init <<'EOF' +#!/usr/bin/env bash +set -e +exec /usr/bin/test-fdstore store +EOF + chmod +x /var/lib/machines/fdstore/sbin/init + + mkdir -p /run/systemd/nspawn + cat >/run/systemd/nspawn/fdstore.nspawn <&2 "test-fdstore not available in the minimal container, skipping fdstore tests" + fi + + # Negative path: store a fd store entry that holds a child LUO session named + # like PID 1's own ("systemd"). On kexec PID 1 must refuse to serialize it + # (anti-hijack guard), so it must not be restored on the next boot. + write_late_unit system TEST-91-LIVEUPDATE-hijack \ + "/usr/lib/systemd/tests/unit-tests/manual/test-luo store-hijack" + systemctl start TEST-91-LIVEUPDATE-hijack.service + timeout 30s bash -c \ + "until [[ \"\$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-hijack.service)\" -ge 1 ]]; do sleep 0.5; done" + + # Write and start each late unit with distinct session name prefixes + # to avoid collisions in the LUO session namespace. + for variant in late late-noreload late-zerofds; do + write_late_unit system "TEST-91-LIVEUPDATE-${variant}" \ + "/usr/lib/systemd/tests/unit-tests/manual/test-luo store ${variant}" + systemctl start "TEST-91-LIVEUPDATE-${variant}.service" + + n_fds=$(systemctl show -P NFileDescriptorStore "TEST-91-LIVEUPDATE-${variant}.service") + test "$n_fds" -eq 3 + done + + # Test LUOSession= setting: start the unit and verify sessions are in its fd store + systemctl start TEST-91-LIVEUPDATE-session.service + n_fds=$(systemctl show -P NFileDescriptorStore TEST-91-LIVEUPDATE-session.service) + echo "Session unit fd store count: $n_fds" + test "$n_fds" -eq 2 # test-session-1 and test-session-2 + + # 'systemctl kexec' auto-loads the default boot entry (i.e. the booted UKI, + # via EFI LoaderEntrySelected/LoaderEntryDefault). Append a marker to the + # kernel command line so we can tell the two boots apart, and also the current + # cmdline that is added by mkosi, otherwise the test framework will break. + systemctl kexec --kernel-cmdline="$(cat /proc/cmdline) luo_nboot=1" + exit 0 +fi + +touch /testok +systemctl --no-block exit 123 diff --git a/test/units/TEST-92-TPM2-SWTPM.sh b/test/units/TEST-92-TPM2-SWTPM.sh new file mode 100755 index 0000000000000..8e2c7d1143280 --- /dev/null +++ b/test/units/TEST-92-TPM2-SWTPM.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Exercises the software TPM fallback (systemd-tpm2-swtpm.service) across reboots. The VM boots in EFI mode +# without a hardware/firmware TPM and with "systemd.tpm2_software_fallback=yes" (see the test's meson.build), +# so systemd-tpm2-generator manufactures a software TPM on the ESP in the initrd and chainloads swtpm. +# +# boot 0: the TPM is manufactured in the initrd; seal a secret to it and stash the blob. +# boot 1: the TPM state persisted on the ESP across the reboot, so the secret still unseals. Then mimic a +# manufacture that was interrupted before it completed (drop everything but the config files, so the +# ".manufactured" marker is gone) and reboot. +# boot 2: setup_swtpm() must notice the missing marker and re-manufacture, rather than mistaking the +# leftover config files for a complete TPM and starting swtpm against a stateless directory. +# +# See systemd-tpm2-swtpm.service(8). + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +CRED=/var/lib/systemd-tpm2-swtpm-test.cred +PLAINTEXT="swtpm round-trip" +# Marker (SWTPM_MANUFACTURED_MARKER) that manufacture_swtpm() indicates completion with. +MARKER=.manufactured + +if [[ -n "${ASAN_OPTIONS:-}" ]]; then + # swtpm_setup is not built with sanitizers, but does NSS lookups that pull in the ASan-instrumented + # libnss_systemd.so, which aborts with "ASan runtime does not come first". Skip under sanitizers. + echo "swtpm_setup does not work under sanitizers, skipping the test" | tee --append /skipped + exit 77 +fi + +if [[ ! -x /usr/lib/systemd/systemd-tpm2-swtpm ]] || ! command -v swtpm >/dev/null || [[ ! -d /sys/firmware/efi ]]; then + echo "Software TPM prerequisites missing, skipping the test" | tee --append /skipped + exit 77 +fi + +assert_swtpm_up() { + systemctl is-active systemd-tpm2-swtpm.service + timeout 30 bash -c 'until [[ -c /dev/tpmrm0 ]]; do sleep 1; done' + test -c /dev/tpm0 + # No firmware TPM here, so has-tpm2 reports "partial"; assert the software driver is present. + assert_in '\+driver' "$(systemd-analyze has-tpm2 || :)" +} + +# Locate swtpm's state directory on the ESP. +swtpm_state_dir() { + local d + for d in /boot/loader/swtpm /efi/loader/swtpm; do + [[ -d "$d" ]] && { echo "$d"; return 0; } + done + return 1 +} + +case "$REBOOT_COUNT" in + 0) + assert_swtpm_up + # Seal a secret to the software TPM and keep the blob for the next boot. + echo -n "$PLAINTEXT" >/tmp/swtpm-plaintext + systemd-creds encrypt --name= --with-key=tpm2 /tmp/swtpm-plaintext "$CRED" + systemd-creds decrypt --name= "$CRED" - | cmp /tmp/swtpm-plaintext - + systemctl_final reboot + exec sleep infinity + ;; + 1) + assert_swtpm_up + # Persistence: the TPM state survived the reboot on the ESP, so the blob still unseals. + echo -n "$PLAINTEXT" >/tmp/swtpm-plaintext + systemd-creds decrypt --name= "$CRED" - | cmp /tmp/swtpm-plaintext - + + # Mimic a manufacture interrupted after swtpm began writing TPM state but before the marker: stop + # swtpm, drop everything except the three config files, then leave a partial/corrupt state file + # behind. swtpm_setup --not-overwrite would refuse to recreate that, so recovery must clear it first. + statedir="$(swtpm_state_dir)" + systemctl stop systemd-tpm2-swtpm.service + find "$statedir" -mindepth 1 -maxdepth 1 -type f \ + ! -name swtpm-localca.conf ! -name swtpm-localca.options ! -name swtpm_setup.conf -delete + echo "corrupt" >"$statedir/tpm2-00.permall" + test -e "$statedir/swtpm_setup.conf" + test ! -e "$statedir/$MARKER" + systemctl_final reboot + exec sleep infinity + ;; + 2) + # setup_swtpm() must have re-manufactured instead of trusting the leftover config files: the marker is + # back and swtpm_setup re-ran swtpm_localca, recreating issuer-certificate.pem. The TPM must also work. + # Regression test for keying re-manufacture off an incomplete state directory. + assert_swtpm_up + statedir="$(swtpm_state_dir)" + test -e "$statedir/$MARKER" + test -e "$statedir/issuer-certificate.pem" + echo -n "$PLAINTEXT" >/tmp/swtpm-plaintext + systemd-creds encrypt --name= --with-key=tpm2 /tmp/swtpm-plaintext /tmp/swtpm-new.cred + systemd-creds decrypt --name= /tmp/swtpm-new.cred - | cmp /tmp/swtpm-plaintext - + touch /testok + ;; + *) + assert_not_reached + ;; +esac diff --git a/test/units/TEST-93-CLONESETUP.sh b/test/units/TEST-93-CLONESETUP.sh new file mode 100755 index 0000000000000..1d2ad36c53cb3 --- /dev/null +++ b/test/units/TEST-93-CLONESETUP.sh @@ -0,0 +1,344 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail +set -E + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +CLONESETUP_BIN=/usr/lib/systemd/systemd-clonesetup +# Test clonesetup generator and systemd-clonesetup +# Disable pager usage so interactive/manual runs do not block in `less`. +export SYSTEMD_PAGER=cat +export SYSTEMD_LESS= + +create_loop_triplet() { + local workdir="${1:?}" + local src_img dst_img meta_img + local loop_src loop_dst loop_meta + + src_img="$workdir/source.img" + dst_img="$workdir/dest.img" + meta_img="$workdir/meta.img" + + truncate -s 32M "$src_img" + truncate -s 32M "$dst_img" + truncate -s 8M "$meta_img" + + loop_src="$(losetup --show --find "$src_img")" + loop_dst="$(losetup --show --find "$dst_img")" + loop_meta="$(losetup --show --find "$meta_img")" + + # Wait for udev to process new loop devices before they are consumed. + udevadm settle --timeout=60 + + printf '%s %s %s\n' "$loop_src" "$loop_dst" "$loop_meta" +} + +cleanup_loop_triplet() { + local loop_src="${1:-}" + local loop_dst="${2:-}" + local loop_meta="${3:-}" + + [[ -n "$loop_src" ]] && losetup -d "$loop_src" + [[ -n "$loop_dst" ]] && losetup -d "$loop_dst" + [[ -n "$loop_meta" ]] && losetup -d "$loop_meta" +} + +at_exit() { + set +e + + rm -f /etc/clonetab + [[ -e /tmp/clonetab.bak ]] && cp -fv /tmp/clonetab.bak /etc/clonetab + dmsetup remove testclonesetup 2>/dev/null || true + + systemctl daemon-reload +} + +at_error() { + local rc="$?" + local source="${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}" + local line="${BASH_LINENO[0]:-0}" + local func="${FUNCNAME[1]:-main}" + + echo "ERROR: rc=$rc at $source:$line ($func): $BASH_COMMAND" >&2 + return "$rc" +} + +trap at_exit EXIT +trap at_error ERR + +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" + # wait for udev to create /dev/mapper/ node after DM device activation + udevadm settle --timeout=10 + 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 + +# Backup existing clonetab if any +[[ -e /etc/clonetab ]] && cp -fv /etc/clonetab /tmp/clonetab.bak + +# Create test clonetab +clonetab_create() { + local loop_name="${1:?}" + local loop_src="${2:?}" + local loop_dst="${3:?}" + local loop_meta="${4:?}" + local loop_options="${5:?}" + + cat >/etc/clonetab </etc/clonetab </etc/clonetab </etc/clonetab </etc/clonetab </etc/clonetab <= 7.0 still reject user.* xattrs on socket inodes (with EPERM/EOPNOTSUPP). + # Hence probe for actual support, mirroring socket_xattr_supported() in the + # C code, by binding a throwaway socket and trying to tag it. + socket="$(mktemp -u /run/socket-xattr-probe.XXXXXX.sock)" + xattr="user.probe_$RANDOM" + # shellcheck disable=SC2064 + trap "rm -f '$socket'" RETURN + + python3 -c 'import socket, sys; socket.socket(socket.AF_UNIX).bind(sys.argv[1])' "$socket" || return 1 + setfattr --name="$xattr" --value=1 "$socket" 2>/dev/null +} + tpm_has_pcr() { local algorithm="${1:?}" local pcr="${2:?}" @@ -401,6 +419,18 @@ EOF echo MARKER=1 >"$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 +543,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/tmpfiles.d/systemd.conf.in b/tmpfiles.d/systemd.conf.in index f601cb87f9d69..786b86d0069f7 100644 --- a/tmpfiles.d/systemd.conf.in +++ b/tmpfiles.d/systemd.conf.in @@ -19,6 +19,7 @@ d$ /run/systemd/users 0755 root root - d /run/systemd/machines 0755 root root - d$ /run/systemd/shutdown 0755 root root - d /run/systemd/dissect-root 0000 root root - +d /run/systemd/report.sign 0700 root root - d /run/log 0755 root root - diff --git a/tmpfiles.d/x11.conf b/tmpfiles.d/x11.conf index 0e38f50bb537d..e83229590a40f 100644 --- a/tmpfiles.d/x11.conf +++ b/tmpfiles.d/x11.conf @@ -9,10 +9,11 @@ # Make sure these are created by default so that nobody else can # or empty them at startup -D! /tmp/.X11-unix 1777 root root 10d -D! /tmp/.ICE-unix 1777 root root 10d -D! /tmp/.XIM-unix 1777 root root 10d -D! /tmp/.font-unix 1777 root root 10d +D /tmp/.X11-unix 1777 root root 1h +D /tmp/.ICE-unix 1777 root root 1h +D /tmp/.XIM-unix 1777 root root 1h +D /tmp/.font-unix 1777 root root 1h -# Unlink the X11 lock files +# Unlink the X11 lock files at boot, but exclude them from time-based cleanup later r! /tmp/.X[0-9]*-lock +x /tmp/.X[0-9]*-lock diff --git a/tools/analyze-dump-sort.py b/tools/analyze-dump-sort.py index a464a14bd1229..da6094f9961e7 100755 --- a/tools/analyze-dump-sort.py +++ b/tools/analyze-dump-sort.py @@ -58,6 +58,7 @@ def sort_dump(sourcefile, destfile=None): destfile.flush() return destfile + def parse_args(): p = argparse.ArgumentParser(description=__doc__) p.add_argument('one') @@ -65,6 +66,7 @@ def parse_args(): p.add_argument('--user', action='store_true') return p.parse_args() + if __name__ == '__main__': opts = parse_args() @@ -73,8 +75,7 @@ def parse_args(): two = sort_dump(open(opts.two)) else: user = ['--user'] if opts.user else [] - two = subprocess.run(['systemd-analyze', 'dump', *user], - capture_output=True, text=True, check=True) + two = subprocess.run(['systemd-analyze', 'dump', *user], capture_output=True, text=True, check=True) two = sort_dump(two.stdout.splitlines()) with subprocess.Popen(['diff', '-U10', one.name, two.name], stdout=subprocess.PIPE) as diff: subprocess.Popen(['less'], stdin=diff.stdout) diff --git a/tools/catalog-report.py b/tools/catalog-report.py index 060b1aae869e1..f7e67c2fb4cb5 100755 --- a/tools/catalog-report.py +++ b/tools/catalog-report.py @@ -36,9 +36,13 @@ def log_entry(entry): if 'CODE_FILE' in entry: # some of our code was using 'CODE_FUNCTION' instead of 'CODE_FUNC' - print('{}:{} {}'.format(entry.get('CODE_FILE', '???'), - entry.get('CODE_LINE', '???'), - entry.get('CODE_FUNC', None) or entry.get('CODE_FUNCTION', '???'))) + print( + '{}:{} {}'.format( + entry.get('CODE_FILE'), + entry.get('CODE_LINE', '???'), + entry.get('CODE_FUNC', None) or entry.get('CODE_FUNCTION', '???'), + ) + ) print(' {}'.format(entry.get('MESSAGE', 'no message!'))) for k, v in entry.items(): if k.startswith('CODE_') or k in {'MESSAGE_ID', 'MESSAGE'}: @@ -46,12 +50,13 @@ def log_entry(entry): print(f' {k}={v}') print() + if __name__ == '__main__': j = journal.Reader() logged = set() pattern = re.compile('@[A-Z0-9_]+@') - mids = { v:k for k,v in id128.__dict__.items() if k.startswith('SD_MESSAGE') } + mids = {v: k for k, v in id128.__dict__.items() if k.startswith('SD_MESSAGE')} for i, x in enumerate(j): if i % 1000 == 0: 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/check-efi-alignment.py b/tools/check-efi-alignment.py index abdeb22fdbdb3..b442a16f55621 100755 --- a/tools/check-efi-alignment.py +++ b/tools/check-efi-alignment.py @@ -15,22 +15,23 @@ def main(): pe = pefile.PE(sys.argv[1], fast_load=True) for section in pe.sections: - name = section.Name.rstrip(b"\x00").decode() + name = section.Name.rstrip(b'\x00').decode() file_addr = section.PointerToRawData virt_addr = section.VirtualAddress - print(f"{name:10s} file=0x{file_addr:08x} virt=0x{virt_addr:08x}") + print(f'{name:10s} file=0x{file_addr:08x} virt=0x{virt_addr:08x}') if file_addr % 512 != 0: - print(f"File address of {name} section is not aligned to 512 bytes", file=sys.stderr) + print(f'File address of {name} section is not aligned to 512 bytes', file=sys.stderr) return 1 if virt_addr % 512 != 0: - print(f"Virt address of {name} section is not aligned to 512 bytes", file=sys.stderr) + print(f'Virt address of {name} section is not aligned to 512 bytes', file=sys.stderr) return 1 + if __name__ == '__main__': if len(sys.argv) != 2: - print(f"Usage: {sys.argv[0]} pe-image") + print(f'Usage: {sys.argv[0]} pe-image') sys.exit(1) sys.exit(main()) diff --git a/tools/check-version-history.py b/tools/check-version-history.py index db32b6920ab52..39cf127b98df2 100755 --- a/tools/check-version-history.py +++ b/tools/check-version-history.py @@ -20,17 +20,14 @@ def find_undocumented_functions(pages, ignorelist): filename = os.path.basename(page) pagetree = tree.parse(page) - assert pagetree.getroot().tag == "refentry" + assert pagetree.getroot().tag == 'refentry' hist_section = pagetree.find("refsect1[title='History']") - for func in pagetree.findall(".//funcprototype/funcdef/function"): + for func in pagetree.findall('.//funcprototype/funcdef/function'): path = f"./refsynopsisdiv/funcsynopsis/funcprototype/funcdef/function[.='{func.text}']" assert pagetree.findall(path) == [func] - if ( - hist_section is None - or hist_section.find(f"para/function[.='{func.text}()']") is None - ): + if hist_section is None or hist_section.find(f"para/function[.='{func.text}()']") is None: if func.text not in ignorelist: undocumented.append((filename, func.text)) return undocumented @@ -39,22 +36,22 @@ def find_undocumented_functions(pages, ignorelist): def construct_path(element): tag = element.tag - if tag == "refentry": - return "." + if tag == 'refentry': + return '.' - predicate = "" - if tag == "varlistentry": - text = "".join(element.find("term").itertext()) + predicate = '' + if tag == 'varlistentry': + text = ''.join(element.find('term').itertext()) predicate = f'[term="{text}"]' - elif tag.startswith("refsect"): - text = "".join(element.find("title").itertext()) + elif tag.startswith('refsect'): + text = ''.join(element.find('title').itertext()) predicate = f'[title="{text}"]' - elif tag == "variablelist": + elif tag == 'variablelist': varlists = element.getparent().findall(tag) if len(varlists) > 1: - predicate = f"[{varlists.index(element)+1}]" + predicate = f'[{varlists.index(element) + 1}]' - return construct_path(element.getparent()) + "/" + tag + predicate + return construct_path(element.getparent()) + '/' + tag + predicate def find_undocumented_commands(pages, ignorelist): @@ -63,23 +60,21 @@ def find_undocumented_commands(pages, ignorelist): filename = os.path.basename(page) pagetree = tree.parse(page) - if pagetree.getroot().tag != "refentry": + if pagetree.getroot().tag != 'refentry': continue - for varlistentry in pagetree.findall("*//variablelist/varlistentry"): + for varlistentry in pagetree.findall('*//variablelist/varlistentry'): path = construct_path(varlistentry) assert pagetree.findall(path) == [varlistentry] - listitem = varlistentry.find("listitem") + listitem = varlistentry.find('listitem') parent = listitem if listitem is not None else varlistentry rev = parent.getchildren()[-1] - if ( - rev.get("href") != "version-info.xml" and - not path.startswith(tuple(entry[1] for entry in ignorelist if entry[0] == filename)) - ): - undocumented.append((filename, path)) + ignored_files = tuple(entry[1] for entry in ignorelist if entry[0] == filename) + if rev.get('href') != 'version-info.xml' and not path.startswith(ignored_files): + undocumented.append((filename, path)) return undocumented @@ -90,54 +85,44 @@ def process_pages(pages): for page in pages: filename = os.path.basename(page) - if filename.startswith("org.freedesktop."): # dbus + if filename.startswith('org.freedesktop.'): # dbus continue - if ( - filename.startswith("sd_") - or filename.startswith("sd-") - or filename.startswith("udev_") - ): + if filename.startswith('sd_') or filename.startswith('sd-') or filename.startswith('udev_'): function_pages.append(page) continue command_pages.append(page) - undocumented_commands = find_undocumented_commands( - command_pages, command_ignorelist - ) - undocumented_functions = find_undocumented_functions( - function_pages, function_ignorelist - ) + undocumented_commands = find_undocumented_commands(command_pages, command_ignorelist) + undocumented_functions = find_undocumented_functions(function_pages, function_ignorelist) return undocumented_commands, undocumented_functions -if __name__ == "__main__": - with open(os.path.join(os.path.dirname(__file__), "command_ignorelist")) as f: +if __name__ == '__main__': + with open(os.path.join(os.path.dirname(__file__), 'command_ignorelist')) as f: command_ignorelist = [] - for l in f.read().splitlines(): - if l.startswith("#"): + for line in f.read().splitlines(): + if line.startswith('#'): continue - fname, path = l.split(" ", 1) - path = path.replace("\\n", "\n") + fname, path = line.split(' ', 1) + path = path.replace('\\n', '\n') command_ignorelist.append((fname, path)) - with open(os.path.join(os.path.dirname(__file__), "function_ignorelist")) as f: + with open(os.path.join(os.path.dirname(__file__), 'function_ignorelist')) as f: function_ignorelist = f.read().splitlines() undocumented_commands, undocumented_functions = process_pages(sys.argv[1:]) if undocumented_commands or undocumented_functions: for filename, func in undocumented_functions: - print( - f"Function {func}() in {filename} isn't documented in the History section." - ) + print(f"Function {func}() in {filename} isn't documented in the History section.") for filename, path in undocumented_commands: - print(filename, path, "is undocumented") + print(filename, path, 'is undocumented') if undocumented_commands: print( - "Hint: if you reorganized this part of the documentation, " - "please update tools/commands_ignorelist." + 'Hint: if you reorganized this part of the documentation, ' + 'please update tools/commands_ignorelist.' ) sys.exit(1) diff --git a/tools/chromiumos/gen_autosuspend_rules.py b/tools/chromiumos/gen_autosuspend_rules.py index ee1682f6f3b0f..14c0010ba6b55 100755 --- a/tools/chromiumos/gen_autosuspend_rules.py +++ b/tools/chromiumos/gen_autosuspend_rules.py @@ -145,6 +145,10 @@ "33f8:01a2", # Rolling Wireless (RW135) "33f8:0115", + # Rolling Wireless (RW135R) + "33f8:1003", + # Rolling Wireless (RW350R USB) + "33f8:0802", # NetPrisma (LCUK54) "3731:0100", ] diff --git a/tools/command_ignorelist b/tools/command_ignorelist index 548acff8bd034..cb256d664726a 100644 --- a/tools/command_ignorelist +++ b/tools/command_ignorelist @@ -51,6 +51,15 @@ logind.conf.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="Kill logind.conf.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="InhibitDelayMaxSec="] machine-info.xml ./refsect1[title="Options"]/refsect2[title="Machine Information"]/variablelist/varlistentry[term="PRETTY_HOSTNAME="] machine-info.xml ./refsect1[title="Options"]/refsect2[title="Machine Information"]/variablelist/varlistentry[term="ICON_NAME="] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="auto"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="hex"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="flag"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="bool"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="uint8"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="uint16"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="time"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="string"] +networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="address"] os-release.xml ./refsect1[title="Options"]/refsect2[title="General information identifying the operating system"]/variablelist/varlistentry[term="NAME="] os-release.xml ./refsect1[title="Options"]/refsect2[title="Information about the version of the operating system"]/variablelist/varlistentry[term="VERSION="] os-release.xml ./refsect1[title="Options"]/refsect2[title="General information identifying the operating system"]/variablelist/varlistentry[term="ID="] diff --git a/tools/dbus_exporter.py b/tools/dbus_exporter.py index 854c7a848bbcb..ce6f8dbf78e8f 100755 --- a/tools/dbus_exporter.py +++ b/tools/dbus_exporter.py @@ -11,14 +11,15 @@ def extract_interfaces_xml(output_dir, executable): # as glibc looks at /proc/self/exe when resolving RPATH env = os.environ.copy() if not os.path.exists('/proc/self'): - env["LD_ORIGIN_PATH"] = executable.parent.as_posix() + env['LD_ORIGIN_PATH'] = executable.parent.as_posix() proc = run( args=[executable.absolute(), '--bus-introspect', 'list'], stdout=PIPE, env=env, check=True, - universal_newlines=True) + text=True, + ) interface_names = (x.split()[1] for x in proc.stdout.splitlines()) @@ -28,19 +29,25 @@ def extract_interfaces_xml(output_dir, executable): stdout=PIPE, env=env, check=True, - universal_newlines=True) + text=True, + ) interface_file_name = output_dir / (interface_name + '.xml') interface_file_name.write_text(proc.stdout) interface_file_name.chmod(0o644) + def main(): parser = ArgumentParser() - parser.add_argument('output', - type=Path) - parser.add_argument('executables', - nargs='+', - type=Path) + parser.add_argument( + 'output', + type=Path, + ) + parser.add_argument( + 'executables', + nargs='+', + type=Path, + ) args = parser.parse_args() @@ -50,5 +57,6 @@ def main(): for exe in args.executables: extract_interfaces_xml(args.output, exe) + if __name__ == '__main__': main() diff --git a/tools/dump-auxv.py b/tools/dump-auxv.py index 1abacda9c1fab..1eb93b0125137 100755 --- a/tools/dump-auxv.py +++ b/tools/dump-auxv.py @@ -85,24 +85,43 @@ 'AT_L3_CACHEGEOMETRY' : 47, 'AT_MINSIGSTKSZ' : 51, # Stack needed for signal delivery -} -AT_AUXV_NAMES = {v:k for k,v in AT_AUXV.items()} +} # fmt: skip +AT_AUXV_NAMES = {v: k for k, v in AT_AUXV.items()} + @click.command(help=__doc__) -@click.option('-b', '--big-endian', 'endian', - flag_value='>', - help='Input is big-endian') -@click.option('-l', '--little-endian', 'endian', - flag_value='<', - help='Input is little-endian') -@click.option('-3', '--32', 'field_width', - flag_value=32, - help='Input is 32-bit') -@click.option('-6', '--64', 'field_width', - flag_value=64, - help='Input is 64-bit') -@click.argument('file', - type=click.File(mode='rb')) +@click.option( + '-b', + '--big-endian', + 'endian', + flag_value='>', + help='Input is big-endian', +) +@click.option( + '-l', + '--little-endian', + 'endian', + flag_value='<', + help='Input is little-endian', +) +@click.option( + '-3', + '--32', + 'field_width', + flag_value=32, + help='Input is 32-bit', +) +@click.option( + '-6', + '--64', + 'field_width', + flag_value=64, + help='Input is 64-bit', +) +@click.argument( + 'file', + type=click.File(mode='rb'), +) def dump(endian, field_width, file): data = file.read() @@ -111,7 +130,7 @@ def dump(endian, field_width, file): if endian is None: endian = '@' - width = {32:'II', 64:'QQ'}[field_width] + width = {32: 'II', 64: 'QQ'}[field_width] format_str = f'{endian}{width}' print(f'# {format_str=}') @@ -137,5 +156,6 @@ def dump(endian, field_width, file): if not seen_null: print('# array not terminated with AT_NULL') + if __name__ == '__main__': dump() diff --git a/tools/elf2efi.py b/tools/elf2efi.py index ed705beb3169f..1442d1a723616 100755 --- a/tools/elf2efi.py +++ b/tools/elf2efi.py @@ -31,12 +31,12 @@ import time import typing from ctypes import ( + LittleEndianStructure, c_char, c_uint8, c_uint16, c_uint32, c_uint64, - LittleEndianStructure, sizeof, ) @@ -46,28 +46,28 @@ class PeCoffHeader(LittleEndianStructure): _fields_ = ( - ("Machine", c_uint16), - ("NumberOfSections", c_uint16), - ("TimeDateStamp", c_uint32), - ("PointerToSymbolTable", c_uint32), - ("NumberOfSymbols", c_uint32), - ("SizeOfOptionalHeader", c_uint16), - ("Characteristics", c_uint16), - ) + ('Machine', c_uint16), + ('NumberOfSections', c_uint16), + ('TimeDateStamp', c_uint32), + ('PointerToSymbolTable', c_uint32), + ('NumberOfSymbols', c_uint32), + ('SizeOfOptionalHeader', c_uint16), + ('Characteristics', c_uint16), + ) # fmt: skip class PeDataDirectory(LittleEndianStructure): _fields_ = ( - ("VirtualAddress", c_uint32), - ("Size", c_uint32), - ) + ('VirtualAddress', c_uint32), + ('Size', c_uint32), + ) # fmt: skip class PeRelocationBlock(LittleEndianStructure): _fields_ = ( - ("PageRVA", c_uint32), - ("BlockSize", c_uint32), - ) + ('PageRVA', c_uint32), + ('BlockSize', c_uint32), + ) # fmt: skip def __init__(self, PageRVA: int): super().__init__(PageRVA) @@ -76,64 +76,64 @@ def __init__(self, PageRVA: int): class PeRelocationEntry(LittleEndianStructure): _fields_ = ( - ("Offset", c_uint16, 12), - ("Type", c_uint16, 4), - ) + ('Offset', c_uint16, 12), + ('Type', c_uint16, 4), + ) # fmt: skip class PeOptionalHeaderStart(LittleEndianStructure): _fields_ = ( - ("Magic", c_uint16), - ("MajorLinkerVersion", c_uint8), - ("MinorLinkerVersion", c_uint8), - ("SizeOfCode", c_uint32), - ("SizeOfInitializedData", c_uint32), - ("SizeOfUninitializedData", c_uint32), - ("AddressOfEntryPoint", c_uint32), - ("BaseOfCode", c_uint32), - ) + ('Magic', c_uint16), + ('MajorLinkerVersion', c_uint8), + ('MinorLinkerVersion', c_uint8), + ('SizeOfCode', c_uint32), + ('SizeOfInitializedData', c_uint32), + ('SizeOfUninitializedData', c_uint32), + ('AddressOfEntryPoint', c_uint32), + ('BaseOfCode', c_uint32), + ) # fmt: skip class PeOptionalHeaderMiddle(LittleEndianStructure): _fields_ = ( - ("SectionAlignment", c_uint32), - ("FileAlignment", c_uint32), - ("MajorOperatingSystemVersion", c_uint16), - ("MinorOperatingSystemVersion", c_uint16), - ("MajorImageVersion", c_uint16), - ("MinorImageVersion", c_uint16), - ("MajorSubsystemVersion", c_uint16), - ("MinorSubsystemVersion", c_uint16), - ("Win32VersionValue", c_uint32), - ("SizeOfImage", c_uint32), - ("SizeOfHeaders", c_uint32), - ("CheckSum", c_uint32), - ("Subsystem", c_uint16), - ("DllCharacteristics", c_uint16), - ) + ('SectionAlignment', c_uint32), + ('FileAlignment', c_uint32), + ('MajorOperatingSystemVersion', c_uint16), + ('MinorOperatingSystemVersion', c_uint16), + ('MajorImageVersion', c_uint16), + ('MinorImageVersion', c_uint16), + ('MajorSubsystemVersion', c_uint16), + ('MinorSubsystemVersion', c_uint16), + ('Win32VersionValue', c_uint32), + ('SizeOfImage', c_uint32), + ('SizeOfHeaders', c_uint32), + ('CheckSum', c_uint32), + ('Subsystem', c_uint16), + ('DllCharacteristics', c_uint16), + ) # fmt: skip class PeOptionalHeaderEnd(LittleEndianStructure): _fields_ = ( - ("LoaderFlags", c_uint32), - ("NumberOfRvaAndSizes", c_uint32), - ("ExportTable", PeDataDirectory), - ("ImportTable", PeDataDirectory), - ("ResourceTable", PeDataDirectory), - ("ExceptionTable", PeDataDirectory), - ("CertificateTable", PeDataDirectory), - ("BaseRelocationTable", PeDataDirectory), - ("Debug", PeDataDirectory), - ("Architecture", PeDataDirectory), - ("GlobalPtr", PeDataDirectory), - ("TLSTable", PeDataDirectory), - ("LoadConfigTable", PeDataDirectory), - ("BoundImport", PeDataDirectory), - ("IAT", PeDataDirectory), - ("DelayImportDescriptor", PeDataDirectory), - ("CLRRuntimeHeader", PeDataDirectory), - ("Reserved", PeDataDirectory), - ) + ('LoaderFlags', c_uint32), + ('NumberOfRvaAndSizes', c_uint32), + ('ExportTable', PeDataDirectory), + ('ImportTable', PeDataDirectory), + ('ResourceTable', PeDataDirectory), + ('ExceptionTable', PeDataDirectory), + ('CertificateTable', PeDataDirectory), + ('BaseRelocationTable', PeDataDirectory), + ('Debug', PeDataDirectory), + ('Architecture', PeDataDirectory), + ('GlobalPtr', PeDataDirectory), + ('TLSTable', PeDataDirectory), + ('LoadConfigTable', PeDataDirectory), + ('BoundImport', PeDataDirectory), + ('IAT', PeDataDirectory), + ('DelayImportDescriptor', PeDataDirectory), + ('CLRRuntimeHeader', PeDataDirectory), + ('Reserved', PeDataDirectory), + ) # fmt: skip class PeOptionalHeader(LittleEndianStructure): @@ -141,47 +141,47 @@ class PeOptionalHeader(LittleEndianStructure): class PeOptionalHeader32(PeOptionalHeader): - _anonymous_ = ("Start", "Middle", "End") + _anonymous_ = ('Start', 'Middle', 'End') _fields_ = ( - ("Start", PeOptionalHeaderStart), - ("BaseOfData", c_uint32), - ("ImageBase", c_uint32), - ("Middle", PeOptionalHeaderMiddle), - ("SizeOfStackReserve", c_uint32), - ("SizeOfStackCommit", c_uint32), - ("SizeOfHeapReserve", c_uint32), - ("SizeOfHeapCommit", c_uint32), - ("End", PeOptionalHeaderEnd), - ) + ('Start', PeOptionalHeaderStart), + ('BaseOfData', c_uint32), + ('ImageBase', c_uint32), + ('Middle', PeOptionalHeaderMiddle), + ('SizeOfStackReserve', c_uint32), + ('SizeOfStackCommit', c_uint32), + ('SizeOfHeapReserve', c_uint32), + ('SizeOfHeapCommit', c_uint32), + ('End', PeOptionalHeaderEnd), + ) # fmt: skip class PeOptionalHeader32Plus(PeOptionalHeader): - _anonymous_ = ("Start", "Middle", "End") + _anonymous_ = ('Start', 'Middle', 'End') _fields_ = ( - ("Start", PeOptionalHeaderStart), - ("ImageBase", c_uint64), - ("Middle", PeOptionalHeaderMiddle), - ("SizeOfStackReserve", c_uint64), - ("SizeOfStackCommit", c_uint64), - ("SizeOfHeapReserve", c_uint64), - ("SizeOfHeapCommit", c_uint64), - ("End", PeOptionalHeaderEnd), - ) + ('Start', PeOptionalHeaderStart), + ('ImageBase', c_uint64), + ('Middle', PeOptionalHeaderMiddle), + ('SizeOfStackReserve', c_uint64), + ('SizeOfStackCommit', c_uint64), + ('SizeOfHeapReserve', c_uint64), + ('SizeOfHeapCommit', c_uint64), + ('End', PeOptionalHeaderEnd), + ) # fmt: skip class PeSection(LittleEndianStructure): _fields_ = ( - ("Name", c_char * 8), - ("VirtualSize", c_uint32), - ("VirtualAddress", c_uint32), - ("SizeOfRawData", c_uint32), - ("PointerToRawData", c_uint32), - ("PointerToRelocations", c_uint32), - ("PointerToLinenumbers", c_uint32), - ("NumberOfRelocations", c_uint16), - ("NumberOfLinenumbers", c_uint16), - ("Characteristics", c_uint32), - ) + ('Name', c_char * 8), + ('VirtualSize', c_uint32), + ('VirtualAddress', c_uint32), + ('SizeOfRawData', c_uint32), + ('PointerToRawData', c_uint32), + ('PointerToRelocations', c_uint32), + ('PointerToLinenumbers', c_uint32), + ('NumberOfRelocations', c_uint16), + ('NumberOfLinenumbers', c_uint16), + ('Characteristics', c_uint32), + ) # fmt: skip def __init__(self) -> None: super().__init__() @@ -195,30 +195,32 @@ def __init__(self) -> None: assert sizeof(PeOptionalHeader32) == 224 assert sizeof(PeOptionalHeader32Plus) == 240 +# fmt: off PE_CHARACTERISTICS_RX = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE PE_CHARACTERISTICS_RW = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE PE_CHARACTERISTICS_R = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ +# fmt: on IGNORE_SECTIONS = [ - ".eh_frame", - ".eh_frame_hdr", - ".ARM.exidx", - ".relro_padding", - ".sframe", + '.eh_frame', + '.eh_frame_hdr', + '.ARM.exidx', + '.relro_padding', + '.sframe', ] IGNORE_SECTION_TYPES = [ - "SHT_DYNAMIC", - "SHT_DYNSYM", - "SHT_GNU_ATTRIBUTES", - "SHT_GNU_HASH", - "SHT_HASH", - "SHT_NOTE", - "SHT_REL", - "SHT_RELA", - "SHT_RELR", - "SHT_STRTAB", - "SHT_SYMTAB", + 'SHT_DYNAMIC', + 'SHT_DYNSYM', + 'SHT_GNU_ATTRIBUTES', + 'SHT_GNU_HASH', + 'SHT_HASH', + 'SHT_NOTE', + 'SHT_REL', + 'SHT_RELA', + 'SHT_RELR', + 'SHT_STRTAB', + 'SHT_SYMTAB', ] # EFI mandates 4KiB memory pages. @@ -227,7 +229,7 @@ def __init__(self) -> None: # Nobody cares about DOS headers, so put the PE header right after. PE_OFFSET = 64 -PE_MAGIC = b"PE\0\0" +PE_MAGIC = b'PE\0\0' def align_to(x: int, align: int) -> int: @@ -239,8 +241,7 @@ def align_down(x: int, align: int) -> int: def next_section_address(sections: list[PeSection]) -> int: - return align_to(sections[-1].VirtualAddress + sections[-1].VirtualSize, - SECTION_ALIGNMENT) + return align_to(sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT) class BadSectionError(ValueError): @@ -256,29 +257,30 @@ def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]: relro = None for elf_seg in file.iter_segments(): - if elf_seg["p_type"] == "PT_LOAD" and elf_seg["p_align"] != SECTION_ALIGNMENT: - raise BadSectionError(f"ELF segment {elf_seg['p_type']} is not properly aligned" - f" ({elf_seg['p_align']} != {SECTION_ALIGNMENT})") - if elf_seg["p_type"] == "PT_GNU_RELRO": + if elf_seg['p_type'] == 'PT_LOAD' and elf_seg['p_align'] != SECTION_ALIGNMENT: + raise BadSectionError( + f'ELF segment {elf_seg["p_type"]} is not properly aligned ({elf_seg["p_align"]} != {SECTION_ALIGNMENT})' + ) + if elf_seg['p_type'] == 'PT_GNU_RELRO': relro = elf_seg for elf_s in file.iter_sections(): if ( - elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_ALLOC == 0 - or elf_s["sh_type"] in IGNORE_SECTION_TYPES + elf_s['sh_flags'] & elf.constants.SH_FLAGS.SHF_ALLOC == 0 + or elf_s['sh_type'] in IGNORE_SECTION_TYPES or elf_s.name in IGNORE_SECTIONS - or elf_s["sh_size"] == 0 + or elf_s['sh_size'] == 0 ): continue - if elf_s["sh_type"] not in ["SHT_PROGBITS", "SHT_NOBITS"]: - raise BadSectionError(f"Unknown section {elf_s.name} with type {elf_s['sh_type']}") + if elf_s['sh_type'] not in ['SHT_PROGBITS', 'SHT_NOBITS']: + raise BadSectionError(f'Unknown section {elf_s.name} with type {elf_s["sh_type"]}') if elf_s.name == '.got': # FIXME: figure out why those sections are inserted - print("WARNING: Non-empty .got section", file=sys.stderr) + print('WARNING: Non-empty .got section', file=sys.stderr) - if elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_EXECINSTR: + if elf_s['sh_flags'] & elf.constants.SH_FLAGS.SHF_EXECINSTR: rwx = PE_CHARACTERISTICS_RX - elif elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_WRITE: + elif elf_s['sh_flags'] & elf.constants.SH_FLAGS.SHF_WRITE: rwx = PE_CHARACTERISTICS_RW else: rwx = PE_CHARACTERISTICS_R @@ -293,11 +295,11 @@ def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]: if pe_s: # Insert padding to properly align the section. - pad_len = elf_s["sh_addr"] - pe_s.VirtualAddress - len(pe_s.data) + pad_len = elf_s['sh_addr'] - pe_s.VirtualAddress - len(pe_s.data) pe_s.data += bytearray(pad_len) + elf_s.data() else: pe_s = PeSection() - pe_s.VirtualAddress = elf_s["sh_addr"] + pe_s.VirtualAddress = elf_s['sh_addr'] pe_s.Characteristics = rwx pe_s.data = elf_s.data() @@ -306,8 +308,8 @@ def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]: def convert_sections( - file: elffile.ELFFile, - opt: PeOptionalHeader, + file: elffile.ELFFile, + opt: PeOptionalHeader, ) -> list[PeSection]: last_vma = (0, 0) sections = [] @@ -324,24 +326,25 @@ def convert_sections( pe_s.VirtualSize = len(pe_s.data) pe_s.SizeOfRawData = align_to(len(pe_s.data), FILE_ALIGNMENT) pe_s.Name = { - PE_CHARACTERISTICS_RX: b".text", - PE_CHARACTERISTICS_RW: b".data", - PE_CHARACTERISTICS_R: b".rodata", + PE_CHARACTERISTICS_RX: b'.text', + PE_CHARACTERISTICS_RW: b'.data', + PE_CHARACTERISTICS_R: b'.rodata', }[pe_s.Characteristics] # This can happen if not building with '-z separate-code'. if pe_s.VirtualAddress < sum(last_vma): - raise BadSectionError(f"Section {pe_s.Name.decode()!r} @0x{pe_s.VirtualAddress:x} overlaps" - f" previous section @0x{last_vma[0]:x}+0x{last_vma[1]:x}=@0x{sum(last_vma):x}") + raise BadSectionError( + f'Section {pe_s.Name.decode()!r} @{pe_s.VirtualAddress:#x} overlaps previous section @{last_vma[0]:#x}+{last_vma[1]:#x}=@{sum(last_vma):#x}' + ) last_vma = (pe_s.VirtualAddress, pe_s.VirtualSize) - if pe_s.Name == b".text": + if pe_s.Name == b'.text': opt.BaseOfCode = pe_s.VirtualAddress opt.SizeOfCode += pe_s.VirtualSize else: opt.SizeOfInitializedData += pe_s.VirtualSize - if pe_s.Name == b".data" and isinstance(opt, PeOptionalHeader32): + if pe_s.Name == b'.data' and isinstance(opt, PeOptionalHeader32): opt.BaseOfData = pe_s.VirtualAddress sections.append(pe_s) @@ -355,14 +358,14 @@ def copy_sections( input_names: str, sections: list[PeSection], ) -> None: - for name in input_names.split(","): + for name in input_names.split(','): elf_s = file.get_section_by_name(name) if not elf_s: continue if elf_s.data_alignment > 1 and SECTION_ALIGNMENT % elf_s.data_alignment != 0: - raise BadSectionError(f"ELF section {name} is not aligned") - if elf_s["sh_flags"] & (elf.constants.SH_FLAGS.SHF_EXECINSTR | elf.constants.SH_FLAGS.SHF_WRITE) != 0: - raise BadSectionError(f"ELF section {name} is not read-only data") + raise BadSectionError(f'ELF section {name} is not aligned') + if elf_s['sh_flags'] & (elf.constants.SH_FLAGS.SHF_EXECINSTR | elf.constants.SH_FLAGS.SHF_WRITE) != 0: # fmt: skip + raise BadSectionError(f'ELF section {name} is not read-only data') pe_s = PeSection() pe_s.Name = name.encode() @@ -381,18 +384,21 @@ def apply_elf_relative_relocation( sections: list[PeSection], addend_size: int, ) -> None: - [target] = [pe_s for pe_s in sections - if pe_s.VirtualAddress <= reloc["r_offset"] < pe_s.VirtualAddress + len(pe_s.data)] + [target] = [ + pe_s + for pe_s in sections + if pe_s.VirtualAddress <= reloc['r_offset'] < pe_s.VirtualAddress + len(pe_s.data) + ] - addend_offset = reloc["r_offset"] - target.VirtualAddress + addend_offset = reloc['r_offset'] - target.VirtualAddress if reloc.is_RELA(): - addend = reloc["r_addend"] + addend = reloc['r_addend'] else: addend = target.data[addend_offset : addend_offset + addend_size] - addend = int.from_bytes(addend, byteorder="little") + addend = int.from_bytes(addend, byteorder='little') - value = (image_base + addend).to_bytes(addend_size, byteorder="little") + value = (image_base + addend).to_bytes(addend_size, byteorder='little') target.data[addend_offset : addend_offset + addend_size] = value @@ -404,47 +410,44 @@ def convert_elf_reloc_table( pe_reloc_blocks: dict[int, PeRelocationBlock], ) -> None: NONE_RELOC = { - "EM_386": elf.enums.ENUM_RELOC_TYPE_i386["R_386_NONE"], - "EM_AARCH64": elf.enums.ENUM_RELOC_TYPE_AARCH64["R_AARCH64_NONE"], - "EM_ARM": elf.enums.ENUM_RELOC_TYPE_ARM["R_ARM_NONE"], - "EM_LOONGARCH": 0, - "EM_RISCV": 0, - "EM_X86_64": elf.enums.ENUM_RELOC_TYPE_x64["R_X86_64_NONE"], - }[file["e_machine"]] + 'EM_386': elf.enums.ENUM_RELOC_TYPE_i386['R_386_NONE'], + 'EM_AARCH64': elf.enums.ENUM_RELOC_TYPE_AARCH64['R_AARCH64_NONE'], + 'EM_ARM': elf.enums.ENUM_RELOC_TYPE_ARM['R_ARM_NONE'], + 'EM_LOONGARCH': 0, + 'EM_RISCV': 0, + 'EM_X86_64': elf.enums.ENUM_RELOC_TYPE_x64['R_X86_64_NONE'], + }[file['e_machine']] # fmt: skip RELATIVE_RELOC = { - "EM_386": elf.enums.ENUM_RELOC_TYPE_i386["R_386_RELATIVE"], - "EM_AARCH64": elf.enums.ENUM_RELOC_TYPE_AARCH64["R_AARCH64_RELATIVE"], - "EM_ARM": elf.enums.ENUM_RELOC_TYPE_ARM["R_ARM_RELATIVE"], - "EM_LOONGARCH": 3, - "EM_RISCV": 3, - "EM_X86_64": elf.enums.ENUM_RELOC_TYPE_x64["R_X86_64_RELATIVE"], - }[file["e_machine"]] + 'EM_386': elf.enums.ENUM_RELOC_TYPE_i386['R_386_RELATIVE'], + 'EM_AARCH64': elf.enums.ENUM_RELOC_TYPE_AARCH64['R_AARCH64_RELATIVE'], + 'EM_ARM': elf.enums.ENUM_RELOC_TYPE_ARM['R_ARM_RELATIVE'], + 'EM_LOONGARCH': 3, + 'EM_RISCV': 3, + 'EM_X86_64': elf.enums.ENUM_RELOC_TYPE_x64['R_X86_64_RELATIVE'], + }[file['e_machine']] # fmt: skip for reloc in elf_reloc_table.iter_relocations(): - if reloc["r_info_type"] == NONE_RELOC: + if reloc['r_info_type'] == NONE_RELOC: continue - if reloc["r_info_type"] == RELATIVE_RELOC: - apply_elf_relative_relocation(reloc, - elf_image_base, - sections, - file.elfclass // 8) + if reloc['r_info_type'] == RELATIVE_RELOC: + apply_elf_relative_relocation(reloc, elf_image_base, sections, file.elfclass // 8) # Now that the ELF relocation has been applied, we can create a PE relocation. - block_rva = reloc["r_offset"] & ~0xFFF + block_rva = reloc['r_offset'] & ~0xFFF if block_rva not in pe_reloc_blocks: pe_reloc_blocks[block_rva] = PeRelocationBlock(block_rva) entry = PeRelocationEntry() - entry.Offset = reloc["r_offset"] & 0xFFF + entry.Offset = reloc['r_offset'] & 0xFFF # REL_BASED_HIGHLOW or REL_BASED_DIR64 entry.Type = 3 if file.elfclass == 32 else 10 pe_reloc_blocks[block_rva].entries.append(entry) continue - raise BadSectionError(f"Unsupported relocation {reloc}") + raise BadSectionError(f'Unsupported relocation {reloc}') def convert_elf_relocations( @@ -453,27 +456,29 @@ def convert_elf_relocations( sections: list[PeSection], minimum_sections: int, ) -> typing.Optional[PeSection]: - dynamic = file.get_section_by_name(".dynamic") + dynamic = file.get_section_by_name('.dynamic') if dynamic is None: - raise BadSectionError("ELF .dynamic section is missing") + raise BadSectionError('ELF .dynamic section is missing') - [flags_tag] = dynamic.iter_tags("DT_FLAGS_1") - if not flags_tag["d_val"] & elf.enums.ENUM_DT_FLAGS_1["DF_1_PIE"]: - raise ValueError("ELF file is not a PIE") + [flags_tag] = dynamic.iter_tags('DT_FLAGS_1') + if not flags_tag['d_val'] & elf.enums.ENUM_DT_FLAGS_1['DF_1_PIE']: + raise ValueError('ELF file is not a PIE') # This checks that the ELF image base is 0. - symtab = file.get_section_by_name(".symtab") + symtab = file.get_section_by_name('.symtab') if symtab: - exe_start = symtab.get_symbol_by_name("__executable_start") - if exe_start and exe_start[0]["st_value"] != 0: - raise ValueError("Unexpected ELF image base") - - opt.SizeOfHeaders = align_to(PE_OFFSET - + len(PE_MAGIC) - + sizeof(PeCoffHeader) - + sizeof(opt) - + sizeof(PeSection) * max(len(sections) + 1, minimum_sections), - FILE_ALIGNMENT) + exe_start = symtab.get_symbol_by_name('__executable_start') + if exe_start and exe_start[0]['st_value'] != 0: + raise ValueError('Unexpected ELF image base') + + opt.SizeOfHeaders = align_to( + PE_OFFSET + + len(PE_MAGIC) + + sizeof(PeCoffHeader) + + sizeof(opt) + + sizeof(PeSection) * max(len(sections) + 1, minimum_sections), + FILE_ALIGNMENT, + ) # We use the basic VMA layout from the ELF image in the PE image. This could cause the first # section to overlap the PE image headers during runtime at VMA 0. We can simply apply a fixed @@ -482,23 +487,18 @@ def convert_elf_relocations( # the ELF portions of the image. segment_offset = 0 if sections[0].VirtualAddress < opt.SizeOfHeaders: - segment_offset = align_to(opt.SizeOfHeaders - sections[0].VirtualAddress, - SECTION_ALIGNMENT) + segment_offset = align_to(opt.SizeOfHeaders - sections[0].VirtualAddress, SECTION_ALIGNMENT) - opt.AddressOfEntryPoint = file["e_entry"] + segment_offset + opt.AddressOfEntryPoint = file['e_entry'] + segment_offset opt.BaseOfCode += segment_offset if isinstance(opt, PeOptionalHeader32): opt.BaseOfData += segment_offset pe_reloc_blocks: dict[int, PeRelocationBlock] = {} for reloc_type, reloc_table in dynamic.get_relocation_tables().items(): - if reloc_type not in ["REL", "RELA"]: - raise BadSectionError(f"Unsupported relocation type {reloc_type}") - convert_elf_reloc_table(file, - reloc_table, - opt.ImageBase + segment_offset, - sections, - pe_reloc_blocks) + if reloc_type not in ['REL', 'RELA']: + raise BadSectionError(f'Unsupported relocation type {reloc_type}') + convert_elf_reloc_table(file, reloc_table, opt.ImageBase + segment_offset, sections, pe_reloc_blocks) for pe_s in sections: pe_s.VirtualAddress += segment_offset @@ -524,7 +524,7 @@ def convert_elf_relocations( data += entry pe_reloc_s = PeSection() - pe_reloc_s.Name = b".reloc" + pe_reloc_s.Name = b'.reloc' pe_reloc_s.data = data pe_reloc_s.VirtualAddress = next_section_address(sections) pe_reloc_s.VirtualSize = len(data) @@ -543,9 +543,9 @@ def write_pe( opt: PeOptionalHeader, sections: list[PeSection], ) -> None: - file.write(b"MZ") + file.write(b'MZ') file.seek(0x3C, io.SEEK_SET) - file.write(PE_OFFSET.to_bytes(2, byteorder="little")) + file.write(PE_OFFSET.to_bytes(2, byteorder='little')) file.seek(PE_OFFSET, io.SEEK_SET) file.write(PE_MAGIC) file.write(coff) @@ -554,8 +554,9 @@ def write_pe( offset = opt.SizeOfHeaders for pe_s in sorted(sections, key=lambda s: s.VirtualAddress): if pe_s.VirtualAddress < opt.SizeOfHeaders: - raise BadSectionError(f"Section {pe_s.Name} @0x{pe_s.VirtualAddress:x} overlaps" - " PE headers ending at 0x{opt.SizeOfHeaders:x}") + raise BadSectionError( + f'Section {pe_s.Name} @{pe_s.VirtualAddress:#x} overlaps PE headers ending at {opt.SizeOfHeaders:#x}' + ) pe_s.PointerToRawData = offset file.write(pe_s) @@ -573,20 +574,20 @@ def write_pe( def elf2efi(args: argparse.Namespace) -> None: file = elffile.ELFFile(args.ELF) if not file.little_endian: - raise ValueError("ELF file is not little-endian") - if file["e_type"] not in ["ET_DYN", "ET_EXEC"]: - raise ValueError(f"Unsupported ELF type {file['e_type']}") + raise ValueError('ELF file is not little-endian') + if file['e_type'] not in ['ET_DYN', 'ET_EXEC']: + raise ValueError(f'Unsupported ELF type {file["e_type"]}') pe_arch = { - "EM_386": 0x014C, - "EM_AARCH64": 0xAA64, - "EM_ARM": 0x01C2, - "EM_LOONGARCH": 0x6232 if file.elfclass == 32 else 0x6264, - "EM_RISCV": 0x5032 if file.elfclass == 32 else 0x5064, - "EM_X86_64": 0x8664, - }.get(file["e_machine"]) + 'EM_386': 0x014C, + 'EM_AARCH64': 0xAA64, + 'EM_ARM': 0x01C2, + 'EM_LOONGARCH': 0x6232 if file.elfclass == 32 else 0x6264, + 'EM_RISCV': 0x5032 if file.elfclass == 32 else 0x5064, + 'EM_X86_64': 0x8664, + }.get(file['e_machine']) # fmt: skip if pe_arch is None: - raise ValueError(f"Unsupported ELF architecture {file['e_machine']}") + raise ValueError(f'Unsupported ELF architecture {file["e_machine"]}') coff = PeCoffHeader() opt = PeOptionalHeader32() if file.elfclass == 32 else PeOptionalHeader32Plus() @@ -605,7 +606,7 @@ def elf2efi(args: argparse.Namespace) -> None: coff.Machine = pe_arch coff.NumberOfSections = len(sections) - coff.TimeDateStamp = int(os.environ.get("SOURCE_DATE_EPOCH") or time.time()) + coff.TimeDateStamp = int(os.environ.get('SOURCE_DATE_EPOCH') or time.time()) coff.SizeOfOptionalHeader = sizeof(opt) # EXECUTABLE_IMAGE|LINE_NUMS_STRIPPED|LOCAL_SYMS_STRIPPED|DEBUG_STRIPPED # and (32BIT_MACHINE or LARGE_ADDRESS_AWARE) @@ -632,66 +633,64 @@ def elf2efi(args: argparse.Namespace) -> None: opt.NumberOfRvaAndSizes = N_DATA_DIRECTORY_ENTRIES if pe_reloc_s: - opt.BaseRelocationTable = PeDataDirectory( - pe_reloc_s.VirtualAddress, pe_reloc_s.VirtualSize - ) + opt.BaseRelocationTable = PeDataDirectory(pe_reloc_s.VirtualAddress, pe_reloc_s.VirtualSize) write_pe(args.PE, coff, opt, sections) def create_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(description="Convert ELF binaries to PE/EFI") + parser = argparse.ArgumentParser(description='Convert ELF binaries to PE/EFI') parser.add_argument( - "--version-major", + '--version-major', type=int, default=0, - help="Major image version of EFI image", + help='Major image version of EFI image', ) parser.add_argument( - "--version-minor", + '--version-minor', type=int, default=0, - help="Minor image version of EFI image", + help='Minor image version of EFI image', ) parser.add_argument( - "--efi-major", + '--efi-major', type=int, default=0, - help="Minimum major EFI subsystem version", + help='Minimum major EFI subsystem version', ) parser.add_argument( - "--efi-minor", + '--efi-minor', type=int, default=0, - help="Minimum minor EFI subsystem version", + help='Minimum minor EFI subsystem version', ) parser.add_argument( - "--subsystem", + '--subsystem', type=int, default=10, - help="PE subsystem", + help='PE subsystem', ) parser.add_argument( - "ELF", - type=argparse.FileType("rb"), - help="Input ELF file", + 'ELF', + type=argparse.FileType('rb'), + help='Input ELF file', ) parser.add_argument( - "PE", - type=argparse.FileType("wb"), - help="Output PE/EFI file", + 'PE', + type=argparse.FileType('wb'), + help='Output PE/EFI file', ) parser.add_argument( - "--minimum-sections", + '--minimum-sections', type=int, default=0, - help="Minimum number of sections to leave space for", + help='Minimum number of sections to leave space for', ) parser.add_argument( - "--copy-sections", + '--copy-sections', type=str, - default="", - help="Copy these sections if found", + default='', + help='Copy these sections if found', ) return parser @@ -701,5 +700,5 @@ def main() -> None: elf2efi(parser.parse_args()) -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/tools/fetch-distro.py b/tools/fetch-distro.py index 00b8eca4d1847..a94b80718687b 100755 --- a/tools/fetch-distro.py +++ b/tools/fetch-distro.py @@ -12,6 +12,7 @@ import subprocess from pathlib import Path + def parse_args(): p = argparse.ArgumentParser( description=__doc__, @@ -27,34 +28,35 @@ def parse_args(): default=True, ) p.add_argument( - '--update', '-u', + '--update', + '-u', action='store_true', default=False, ) p.add_argument('--profile') return p.parse_args() + def read_config(distro: str): cmd = ['mkosi', '--json', '-d', distro, '-f', 'summary'] if args.profile: cmd += ['--profile', args.profile] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') text = subprocess.check_output(cmd, text=True) data = json.loads(text) - images = {image["Image"]: image for image in data["Images"]} - return images["build"] + images = {image['Image']: image for image in data['Images']} + return images['build'] + def commit_file(distro: str, files: list[Path], commit: str, changes: str): - message = '\n'.join(( - f'mkosi: update {distro} commit reference to {commit}', - '', - changes)) + message = '\n'.join((f'mkosi: update {distro} commit reference to {commit}', '', changes)) cmd = ['git', 'commit', '-m', message, *(str(file) for file in files)] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) + def checkout_distro(args, distro: str, config: dict): url = config['Environment']['GIT_URL'] branch = config['Environment']['GIT_BRANCH'] @@ -74,28 +76,30 @@ def checkout_distro(args, distro: str, config: dict): reference = ['--reference-if-able=.'] if distro == 'debian' else [] cmd = [ - 'git', 'clone', url, + 'git', + 'clone', + url, f'--branch={branch}', *sparse, dest.as_posix(), *reference, ] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) # Sparse checkout if the package is in a subdirectory if subdir is not None: - cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'sparse-checkout', 'set', - '--no-cone', f'{subdir}'] - print(f"+ {shlex.join(cmd)}") + cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'sparse-checkout', 'set', '--no-cone', f'{subdir}'] + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'checkout', 'HEAD'] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) args.fetch = False # no need to fetch if we just cloned + def update_distro(args, distro: str, config: dict): branch = config['Environment']['GIT_BRANCH'] subdir = config['Environment'].get('GIT_SUBDIR') @@ -103,32 +107,46 @@ def update_distro(args, distro: str, config: dict): pkg_subdir = config['Environment']['PKG_SUBDIR'] if args.fetch: - cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'fetch', 'origin', '-v', - f'{branch}:remotes/origin/{branch}'] - print(f"+ {shlex.join(cmd)}") + cmd = [ + 'git', + '-C', f'pkg/{pkg_subdir}', + 'fetch', + 'origin', + '-v', + f'{branch}:remotes/origin/{branch}', + ] # fmt: skip + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'switch', branch] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) - cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'log', '-n1', '--format=%H', - f'refs/remotes/origin/{branch}'] + cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'log', '-n1', '--format=%H', f'refs/remotes/origin/{branch}'] if subdir is not None: cmd += [f'{subdir}'] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') new_commit = subprocess.check_output(cmd, text=True).strip() if old_commit == new_commit: print(f'{pkg_subdir}: commit {new_commit!s} is still fresh') return - cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'log', '--graph', '--no-merges', - '--pretty=oneline', '--no-decorate', '--abbrev-commit', '--abbrev=10', - f'{old_commit}..{new_commit}'] + cmd = [ + 'git', + '-C', f'pkg/{pkg_subdir}', + 'log', + '--graph', + '--no-merges', + '--pretty=oneline', + '--no-decorate', + '--abbrev-commit', + '--abbrev=10', + f'{old_commit}..{new_commit}', + ] # fmt: skip if subdir is not None: cmd += [f'{subdir}'] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') changes = subprocess.check_output(cmd, text=True).strip() conf_dir = Path('mkosi/mkosi.pkgenv/mkosi.conf.d') @@ -142,8 +160,8 @@ def update_distro(args, distro: str, config: dict): file.write_text(new) tocommit = [file] - if distro == "fedora": - packit = Path(".packit.yml") + if distro == 'fedora': + packit = Path('.packit.yml') s = packit.read_text() assert old_commit in s new = s.replace(old_commit, new_commit) @@ -155,6 +173,7 @@ def update_distro(args, distro: str, config: dict): else: raise ValueError(f'{distro}: hash {new_commit} not found under {conf_dir}') + if __name__ == '__main__': args = parse_args() diff --git a/tools/fetch-mkosi.py b/tools/fetch-mkosi.py index 6e6440d65ef9c..11e0a6aafa73d 100755 --- a/tools/fetch-mkosi.py +++ b/tools/fetch-mkosi.py @@ -7,15 +7,18 @@ """ import argparse +import re import shlex import subprocess -import re from pathlib import Path URL = 'https://github.com/systemd/mkosi' BRANCH = 'main' # We only want to ever use commits on upstream 'main' branch CONFIG = Path('mkosi/mkosi.conf') -WORKFLOWS = [Path('.github/workflows') / f for f in ['mkosi.yml', 'coverage.yml', 'linter.yml']] +WORKFLOWS = [ + Path('.github/workflows') / f for f in ['mkosi.yml', 'coverage.yml', 'linter.yml', 'unit-tests.yml'] +] + def parse_args(): p = argparse.ArgumentParser( @@ -26,59 +29,72 @@ def parse_args(): type=Path, ) p.add_argument( - '--update', '-u', + '--update', + '-u', action='store_true', default=False, ) return p.parse_args() + def read_config(): print(f'Reading {CONFIG}…') - matches = [m.group(1) - for line in open(CONFIG) - if (m := re.match('^MinimumVersion=commit:([a-z0-9]{40})$', - line.strip()))] + c = CONFIG.read_text() + matches = [ + m.group(1) for m in re.finditer('^\s*MinimumVersion=commit:([a-z0-9]{40})\s*$', c, re.MULTILINE) + ] assert len(matches) == 1 return matches[0] + def commit_file(files: list[Path], commit: str, changes: str): - message = '\n'.join(( - f'mkosi: update mkosi ref to {commit}', - '', - changes)) + message = '\n'.join((f'mkosi: update mkosi ref to {commit}', '', changes)) cmd = ['git', 'commit', '-m', message, *(str(file) for file in files)] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) + def checkout_mkosi(args): if args.dir.exists(): print(f'{args.dir} already exists.') return cmd = [ - 'git', 'clone', URL, + 'git', + 'clone', + URL, f'--branch={BRANCH}', args.dir.as_posix(), ] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) + def update_mkosi(args): old_commit = read_config() cmd = ['git', '-C', args.dir.as_posix(), 'rev-parse', f'refs/remotes/origin/{BRANCH}'] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') new_commit = subprocess.check_output(cmd, text=True).strip() if old_commit == new_commit: print(f'mkosi: commit {new_commit!s} is still fresh') return - cmd = ['git', '-C', args.dir.as_posix(), 'log', '--graph', '--no-merges', - '--pretty=oneline', '--no-decorate', '--abbrev-commit', '--abbrev=10', - f'{old_commit}..{new_commit}'] - print(f"+ {shlex.join(cmd)}") + cmd = [ + 'git', + '-C', args.dir.as_posix(), + 'log', + '--graph', + '--no-merges', + '--pretty=oneline', + '--no-decorate', + '--abbrev-commit', + '--abbrev=10', + f'{old_commit}..{new_commit}', + ] # fmt: skip + print(f'+ {shlex.join(cmd)}') changes = subprocess.check_output(cmd, text=True).strip() for f in [CONFIG, *WORKFLOWS]: @@ -91,6 +107,7 @@ def update_mkosi(args): commit_file([CONFIG, *WORKFLOWS], new_commit, changes) + if __name__ == '__main__': args = parse_args() checkout_mkosi(args) diff --git a/tools/find-unused-library-symbols.py b/tools/find-unused-library-symbols.py index 47f96df2ee44f..66083c0ea4620 100755 --- a/tools/find-unused-library-symbols.py +++ b/tools/find-unused-library-symbols.py @@ -39,10 +39,10 @@ def get_exported_symbols(library_path): ['nm', '--dynamic', '--defined-only', '--extern-only', library_path], capture_output=True, text=True, - check=True + check=True, ) except subprocess.CalledProcessError as e: - print(f"Error: Failed to run nm on {library_path}: {e}", file=sys.stderr) + print(f'Error: Failed to run nm on {library_path}: {e}', file=sys.stderr) sys.exit(1) except FileNotFoundError: print("Error: 'nm' command not found. Please install binutils.", file=sys.stderr) @@ -79,10 +79,10 @@ def get_undefined_symbols(executable_path): ['nm', '--dynamic', '--undefined-only', executable_path], capture_output=True, text=True, - check=True + check=True, ) except subprocess.CalledProcessError as e: - print(f"Warning: Failed to run nm on {executable_path}: {e}", file=sys.stderr) + print(f'Warning: Failed to run nm on {executable_path}: {e}', file=sys.stderr) return set() except FileNotFoundError: print("Error: 'nm' command not found. Please install binutils.", file=sys.stderr) @@ -112,7 +112,7 @@ def verify_executable_links_library(executable_path, library_name): ['ldd', executable_path], capture_output=True, text=True, - check=True + check=True, ) except (subprocess.CalledProcessError, FileNotFoundError): # If ldd fails or doesn't exist, we'll skip the verification @@ -137,14 +137,16 @@ def get_library_internal_references(library_path, exported_symbols): ['objdump', '-R', library_path], capture_output=True, text=True, - check=True + check=True, ) except subprocess.CalledProcessError as e: - print(f"Warning: Failed to run objdump on {library_path}: {e}", file=sys.stderr) + print(f'Warning: Failed to run objdump on {library_path}: {e}', file=sys.stderr) return set() except FileNotFoundError: - print("Warning: 'objdump' command not found. Internal references won't be detected.", - file=sys.stderr) + print( + "Warning: 'objdump' command not found. Internal references won't be detected.", + file=sys.stderr, + ) return set() internal_refs = set() @@ -181,7 +183,7 @@ def find_unused_symbols(library_path, executable_paths, verify_linkage=True): exported_symbols = get_exported_symbols(library_path) if not exported_symbols: - print(f"Warning: No exported symbols found in {library_path}", file=sys.stderr) + print(f'Warning: No exported symbols found in {library_path}', file=sys.stderr) return set(), set(), set() # Collect all symbols used by the executables @@ -194,8 +196,7 @@ def find_unused_symbols(library_path, executable_paths, verify_linkage=True): for exe_path in executable_paths: # Optionally verify linkage if verify_linkage and not verify_executable_links_library(exe_path, library_name): - print(f"Warning: {exe_path} does not appear to link against {library_name}", - file=sys.stderr) + print(f'Warning: {exe_path} does not appear to link against {library_name}', file=sys.stderr) undefined_symbols = get_undefined_symbols(exe_path) # Only count symbols that are actually exported by our library @@ -209,31 +210,31 @@ def find_unused_symbols(library_path, executable_paths, verify_linkage=True): def main(): parser = argparse.ArgumentParser( - description='Find unused exported symbols in a shared library' + description='Find unused exported symbols in a shared library', ) parser.add_argument( 'library', - help='Path to the shared library to analyze' + help='Path to the shared library to analyze', ) parser.add_argument( 'executables', nargs='+', - help='Paths to executables that link against the library' + help='Paths to executables that link against the library', ) parser.add_argument( '--no-verify-linkage', action='store_true', - help='Skip verification that executables actually link against the library' + help='Skip verification that executables actually link against the library', ) parser.add_argument( '--show-used', action='store_true', - help='Also show used symbols' + help='Also show used symbols', ) parser.add_argument( '--stats-only', action='store_true', - help='Only show statistics, not individual symbols' + help='Only show statistics, not individual symbols', ) args = parser.parse_args() @@ -241,7 +242,7 @@ def main(): # Verify library exists library_path = Path(args.library) if not library_path.exists(): - print(f"Error: Library not found: {library_path}", file=sys.stderr) + print(f'Error: Library not found: {library_path}', file=sys.stderr) sys.exit(1) # Verify executables exist @@ -249,47 +250,45 @@ def main(): for exe in args.executables: exe_path = Path(exe) if not exe_path.exists(): - print(f"Warning: Executable not found: {exe_path}", file=sys.stderr) + print(f'Warning: Executable not found: {exe_path}', file=sys.stderr) else: executable_paths.append(str(exe_path)) if not executable_paths: - print("Error: No valid executables provided", file=sys.stderr) + print('Error: No valid executables provided', file=sys.stderr) sys.exit(1) # Analyze symbols unused, exported, used = find_unused_symbols( - str(library_path), - executable_paths, - verify_linkage=not args.no_verify_linkage + str(library_path), executable_paths, verify_linkage=not args.no_verify_linkage ) # Print results - print(f"Analysis of {library_path.name}") - print("=" * 70) - print(f"Total exported symbols: {len(exported)}") - print(f" (excluding public API symbols starting with 'sd_')") - print(f"Used symbols: {len(used)}") - print(f"Unused symbols: {len(unused)}") - print(f"Usage rate: {len(used)/len(exported)*100:.1f}%" if exported else "N/A") + print(f'Analysis of {library_path.name}') + print('=' * 70) + print(f'Total exported symbols: {len(exported)}') + print(" (excluding public API symbols starting with 'sd_')") + print(f'Used symbols: {len(used)}') + print(f'Unused symbols: {len(unused)}') + print(f'Usage rate: {len(used) / len(exported) * 100:.1f}%' if exported else 'N/A') print() if not args.stats_only: if unused: - print("Unused symbols:") - print("-" * 70) + print('Unused symbols:') + print('-' * 70) for symbol in sorted(unused): - print(f" {symbol}") + print(f' {symbol}') print() else: - print("All exported symbols are used!") + print('All exported symbols are used!') print() if args.show_used and used: - print("Used symbols:") - print("-" * 70) + print('Used symbols:') + print('-' * 70) for symbol in sorted(used): - print(f" {symbol}") + print(f' {symbol}') print() # Exit with non-zero if there are unused symbols (useful for CI) diff --git a/tools/gdb-sd_dump_hashmaps.py b/tools/gdb-sd_dump_hashmaps.py index 596ee8d90c32b..d6fc2c4bb8e85 100755 --- a/tools/gdb-sd_dump_hashmaps.py +++ b/tools/gdb-sd_dump_hashmaps.py @@ -4,38 +4,39 @@ import gdb + class sd_dump_hashmaps(gdb.Command): "dump systemd's hashmaps" def __init__(self): - super().__init__("sd_dump_hashmaps", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) + super().__init__('sd_dump_hashmaps', gdb.COMMAND_DATA, gdb.COMPLETE_NONE) def invoke(self, arg, _from_tty): - d = gdb.parse_and_eval("hashmap_debug_list") - hashmap_type_info = gdb.parse_and_eval("hashmap_type_info") - uchar_t = gdb.lookup_type("unsigned char") - ulong_t = gdb.lookup_type("unsigned long") - debug_offset = gdb.parse_and_eval("(unsigned long)&((HashmapBase*)0)->debug") + d = gdb.parse_and_eval('hashmap_debug_list') + hashmap_type_info = gdb.parse_and_eval('hashmap_type_info') + uchar_t = gdb.lookup_type('unsigned char') + ulong_t = gdb.lookup_type('unsigned long') + debug_offset = gdb.parse_and_eval('(unsigned long)&((HashmapBase*)0)->debug') - print("type, hash, indirect, entries, max_entries, buckets, creator") + print('type, hash, indirect, entries, max_entries, buckets, creator') while d: - h = gdb.parse_and_eval(f"(HashmapBase*)((char*){int(d.cast(ulong_t))} - {debug_offset})") + h = gdb.parse_and_eval(f'(HashmapBase*)((char*){int(d.cast(ulong_t))} - {debug_offset})') - if h["has_indirect"]: - storage_ptr = h["indirect"]["storage"].cast(uchar_t.pointer()) - n_entries = h["indirect"]["n_entries"] - n_buckets = h["indirect"]["n_buckets"] + if h['has_indirect']: + storage_ptr = h['indirect']['storage'].cast(uchar_t.pointer()) + n_entries = h['indirect']['n_entries'] + n_buckets = h['indirect']['n_buckets'] else: - storage_ptr = h["direct"]["storage"].cast(uchar_t.pointer()) - n_entries = h["n_direct_entries"] - n_buckets = hashmap_type_info[h["type"]]["n_direct_buckets"] + storage_ptr = h['direct']['storage'].cast(uchar_t.pointer()) + n_entries = h['n_direct_entries'] + n_buckets = hashmap_type_info[h['type']]['n_direct_buckets'] - t = ["plain", "ordered", "set"][int(h["type"])] + t = ['plain', 'ordered', 'set'][int(h['type'])] - print(f'{t}, {h["hash_ops"]}, {bool(h["has_indirect"])}, {n_entries}, {d["max_entries"]}, {n_buckets}') + print(f'{t}, {h["hash_ops"]}, {bool(h["has_indirect"])}, {n_entries}, {d["max_entries"]}, {n_buckets}') # fmt: skip - if arg != "" and n_entries > 0: - dib_raw_addr = storage_ptr + hashmap_type_info[h["type"]]["entry_size"] * n_buckets + if arg != '' and n_entries > 0: + dib_raw_addr = storage_ptr + hashmap_type_info[h['type']]['entry_size'] * n_buckets histogram = {} for i in range(0, n_buckets): @@ -44,11 +45,11 @@ def invoke(self, arg, _from_tty): for dib in sorted(histogram): if dib != 255: - print(f"{dib:>3} {histogram[dib]:>8} {float(histogram[dib]/n_entries):.0%} of entries") + print(f'{dib:>3} {histogram[dib]:>8} {float(histogram[dib] / n_entries):.0%} of entries') # fmt: skip else: - print(f"{dib:>3} {histogram[dib]:>8} {float(histogram[dib]/n_buckets):.0%} of slots") - s = sum(dib*count for (dib, count) in histogram.items() if dib != 255) / n_entries - print(f"mean DIB of entries: {s}") + print(f'{dib:>3} {histogram[dib]:>8} {float(histogram[dib] / n_buckets):.0%} of slots') # fmt: skip + s = sum(dib * count for (dib, count) in histogram.items() if dib != 255) / n_entries + print(f'mean DIB of entries: {s}') blocks = [] current_len = 1 @@ -69,10 +70,11 @@ def invoke(self, arg, _from_tty): if len(blocks) > 1 and blocks[0][0] == blocks[0][1] and blocks[-1][0] == n_buckets - 1: blocks[0][1] += blocks[-1][1] blocks = blocks[0:-1] - print("max block: {}".format(max(blocks, key=lambda a: a[1]))) - print("sum block lens: {}".format(sum(b[1] for b in blocks))) - print("mean block len: {}".format(sum(b[1] for b in blocks) / len(blocks))) + print(f'max block: {max(blocks, key=lambda a: a[1])}') + print(f'sum block lens: {sum(b[1] for b in blocks)}') + print(f'mean block len: {sum(b[1] for b in blocks) / len(blocks)}') + + d = d['debug_list_next'] - d = d["debug_list_next"] sd_dump_hashmaps() diff --git a/tools/generate-gperfs.py b/tools/generate-gperfs.py index fc349b43faed5..e9e520247eb20 100755 --- a/tools/generate-gperfs.py +++ b/tools/generate-gperfs.py @@ -13,23 +13,23 @@ sys.exit(f'Usage: {sys.argv[0]} name prefix file [includes...]') name, prefix, file, *includes = sys.argv[1:] - includes = [f"#include {i}" for i in includes] + includes = [f'#include {i}' for i in includes] # Older versions of python don't allow backslashes # in f-strings so use chr(10) for newlines and chr(92) # for backslashes instead as a workaround. - print(f"""\ + print(f'''\ %{{ _Pragma("GCC diagnostic ignored {chr(92)}"-Wimplicit-fallthrough{chr(92)}"") #if __GNUC__ >= 15 _Pragma("GCC diagnostic ignored {chr(92)}"-Wzero-as-null-pointer-constant{chr(92)}"") #endif {chr(10).join(includes)} -%}}""") - print(f"""\ +%}}''') + print(f'''\ struct {name}_name {{ const char* name; int id; }}; %null-strings -%%""") +%%''') for line in open(file): - print("{0}, {1}{0}".format(line.rstrip(), prefix)) + print('{0}, {1}{0}'.format(line.rstrip(), prefix)) diff --git a/tools/list-discoverable-partitions.py b/tools/list-discoverable-partitions.py index a19bf1d6e2ee7..437be68d727f7 100755 --- a/tools/list-discoverable-partitions.py +++ b/tools/list-discoverable-partitions.py @@ -30,16 +30,15 @@ 'TILEGX': 'TILE-Gx', 'X86': 'x86', 'X86_64': 'amd64/x86_64', -} +} # fmt: skip TYPES = { - 'ROOT' : 'Root Partition', - 'ROOT_VERITY' : 'Root Verity Partition', - 'ROOT_VERITY_SIG' : 'Root Verity Signature Partition', - 'USR' : '`/usr/` Partition', - 'USR_VERITY' : '`/usr/` Verity Partition', - 'USR_VERITY_SIG' : '`/usr/` Verity Signature Partition', - + 'ROOT': 'Root Partition', + 'ROOT_VERITY': 'Root Verity Partition', + 'ROOT_VERITY_SIG': 'Root Verity Signature Partition', + 'USR': '`/usr/` Partition', + 'USR_VERITY': '`/usr/` Verity Partition', + 'USR_VERITY_SIG': '`/usr/` Verity Signature Partition', 'ESP': 'EFI System Partition', 'SRV': 'Server Data Partition', 'VAR': 'Variable Data Partition', @@ -49,7 +48,7 @@ 'USER_HOME': 'Per-user Home Partition', 'LINUX_GENERIC': 'Generic Linux Data Partition', 'XBOOTLDR': 'Extended Boot Loader Partition', -} +} # fmt: skip DESCRIPTIONS = { 'ROOT': ( @@ -57,56 +56,66 @@ 'On systems with matching architecture, the first partition with this type UUID on the disk ' 'containing the active EFI ESP is automatically mounted to the root directory `/`. ' 'If the partition is encrypted with LUKS or has dm-verity integrity data (see below), the ' - 'device mapper file will be named `/dev/mapper/root`.'), + 'device mapper file will be named `/dev/mapper/root`.', + ), 'USR': ( 'Any native, optionally in LUKS', - 'Similar semantics to root partition, but just the `/usr/` partition.'), + 'Similar semantics to root partition, but just the `/usr/` partition.', + ), 'ROOT_VERITY': ( 'A dm-verity superblock followed by hash data', 'Contains dm-verity integrity hash data for the matching root partition. If this feature is ' 'used the partition UUID of the root partition should be the first 128 bits of the root hash ' 'of the dm-verity hash data, and the partition UUID of this dm-verity partition should be the ' 'final 128 bits of it, so that the root partition and its Verity partition can be discovered ' - 'easily, simply by specifying the root hash.'), + 'easily, simply by specifying the root hash.', + ), 'USR_VERITY': ( 'A dm-verity superblock followed by hash data', - 'Similar semantics to root Verity partition, but just for the `/usr/` partition.'), + 'Similar semantics to root Verity partition, but just for the `/usr/` partition.', + ), 'ROOT_VERITY_SIG': ( 'A serialized JSON object, see below', - 'Contains a root hash and a PKCS#7 signature for it, permitting signed dm-verity GPT images.'), + 'Contains a root hash and a PKCS#7 signature for it, permitting signed dm-verity GPT images.', + ), 'USR_VERITY_SIG': ( 'A serialized JSON object, see below', - 'Similar semantics to root Verity signature partition, but just for the `/usr/` partition.'), - + 'Similar semantics to root Verity signature partition, but just for the `/usr/` partition.', + ), 'ESP': ( 'VFAT', 'The ESP used for the current boot is automatically mounted to `/efi/` (or `/boot/` as ' 'fallback), unless a different partition is mounted there (possibly via `/etc/fstab`, or ' 'because the Extended Boot Loader Partition — see below — exists) or the directory is ' 'non-empty on the root disk. This partition type is defined by the ' - '[UEFI Specification](http://www.uefi.org/specifications).'), + '[UEFI Specification](http://www.uefi.org/specifications).', + ), 'XBOOTLDR': ( 'Typically VFAT', 'The Extended Boot Loader Partition (XBOOTLDR) used for the current boot is automatically ' 'mounted to `/boot/`, unless a different partition is mounted there (possibly via ' '`/etc/fstab`) or the directory is non-empty on the root disk. This partition type ' 'is defined by the [Boot Loader ' - 'Specification](https://uapi-group.org/specifications/specs/boot_loader_specification).'), + 'Specification](https://uapi-group.org/specifications/specs/boot_loader_specification).', + ), 'SWAP': ( 'Swap, optionally in LUKS', 'All swap partitions on the disk containing the root partition are automatically enabled. ' 'If the partition is encrypted with LUKS, the device mapper file will be named ' - '`/dev/mapper/swap`. This partition type predates the Discoverable Partitions Specification.'), + '`/dev/mapper/swap`. This partition type predates the Discoverable Partitions Specification.', + ), 'HOME': ( 'Any native, optionally in LUKS', 'The first partition with this type UUID on the disk containing the root partition is ' 'automatically mounted to `/home/`. If the partition is encrypted with LUKS, the device ' - 'mapper file will be named `/dev/mapper/home`.'), + 'mapper file will be named `/dev/mapper/home`.', + ), 'SRV': ( 'Any native, optionally in LUKS', 'The first partition with this type UUID on the disk containing the root partition is ' 'automatically mounted to `/srv/`. If the partition is encrypted with LUKS, the device ' - 'mapper file will be named `/dev/mapper/srv`.'), + 'mapper file will be named `/dev/mapper/srv`.', + ), 'VAR': ( 'Any native, optionally in LUKS', 'The first partition with this type UUID on the disk containing the root partition is ' @@ -118,7 +127,8 @@ 'listed here) is inherently private to a specific installation and cannot possibly be ' 'shared between multiple OS installations on the same disk, and thus should be bound to ' 'a specific instance of the OS, identified by its machine ID. If the partition is ' - 'encrypted with LUKS, the device mapper file will be named `/dev/mapper/var`.'), + 'encrypted with LUKS, the device mapper file will be named `/dev/mapper/var`.', + ), 'TMP': ( 'Any native, optionally in LUKS', 'The first partition with this type UUID on the disk containing the root partition is ' @@ -127,19 +137,23 @@ 'is indeed `/var/tmp/`, not `/tmp/`. The latter is typically maintained in memory via ' '`tmpfs` and does not require a partition on disk. In some cases it might be ' 'desirable to make `/tmp/` persistent too, in which case it is recommended to make it ' - 'a symlink or bind mount to `/var/tmp/`, thus not requiring its own partition type UUID.'), + 'a symlink or bind mount to `/var/tmp/`, thus not requiring its own partition type UUID.', + ), 'USER_HOME': ( 'Any native, optionally in LUKS', 'A home partition of a user, managed by ' - '[`systemd-homed`](https://www.freedesktop.org/software/systemd/man/systemd-homed.html).'), + '[`systemd-homed`](https://www.freedesktop.org/software/systemd/man/systemd-homed.html).', + ), 'LINUX_GENERIC': ( 'Any native, optionally in LUKS', 'No automatic mounting takes place for other Linux data partitions. This partition type ' 'should be used for all partitions that carry Linux file systems. The installer needs ' 'to mount them explicitly via entries in `/etc/fstab`. Optionally, these partitions may ' - 'be encrypted with LUKS. This partition type predates the Discoverable Partitions Specification.'), + 'be encrypted with LUKS. This partition type predates the Discoverable Partitions Specification.', + ), } + def extract(file): for line in file: # print(line) @@ -148,7 +162,10 @@ def extract(file): continue name = line.split()[1] - if m2 := re.match(r'^(ROOT|USR)_([A-Z0-9]+|X86_64|PPC64_LE|MIPS_LE|MIPS64_LE)(|_VERITY|_VERITY_SIG)\s+SD_ID128_MAKE\((.*)\)', m.group(1)): + if m2 := re.match( + r'^(ROOT|USR)_([A-Z0-9]+|X86_64|PPC64_LE|MIPS_LE|MIPS64_LE)(|_VERITY|_VERITY_SIG)\s+SD_ID128_MAKE\((.*)\)', + m.group(1), + ): ptype, arch, suffix, u = m2.groups() u = uuid.UUID(u.replace(',', '')) assert arch in ARCHITECTURES, f'{arch} not in f{ARCHITECTURES}' @@ -165,6 +182,7 @@ def extract(file): else: raise ValueError(f'Failed to match: {m.group(1)}') + def generate(defines): prevtype = None @@ -188,6 +206,7 @@ def generate(defines): print(f'| _{tdesc}{adesc}_ | `{puuid}` `{name}` | {morea} | {moreb} |') + if __name__ == '__main__': known = extract(sys.stdin) generate(known) diff --git a/tools/make-directive-index.py b/tools/make-directive-index.py index 5398b452eff18..ddf07534ef30c 100755 --- a/tools/make-directive-index.py +++ b/tools/make-directive-index.py @@ -13,6 +13,7 @@ referring to {pages} individual manual pages. ''' + def _extract_directives(directive_groups, formatting, page): t = xml_parse(page) section = t.find('./refmeta/manvolnum').text @@ -21,12 +22,13 @@ def _extract_directives(directive_groups, formatting, page): storopt = directive_groups['options'] for variablelist in t.iterfind('.//variablelist'): klass = variablelist.attrib.get('class') - searchpath = variablelist.attrib.get('xpath','./varlistentry/term/varname') + searchpath = variablelist.attrib.get('xpath', './varlistentry/term/varname') storvar = directive_groups[klass or 'miscellaneous'] #